Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions plugins/tidyup/src/App.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
/* Your Plugin CSS */

[data-framer-theme="light"] {
--preview-frame-bg: #fff;
--preview-border: rgba(0, 0, 0, 0.05);
}

[data-framer-theme="dark"] {
--preview-frame-bg: rgba(187, 187, 187, 0.2);
--preview-border: rgba(255, 255, 255, 0.07);
}

main {
display: flex;
flex-direction: column;
Expand All @@ -26,10 +36,6 @@ main {
width: 100%;
}

[data-framer-theme="light"] .preview-frame {
background: white;
}

[data-framer-theme="dark"] .preview-frame {
background: rgba(187, 187, 187, 0.2);
.preview-frame {
background: var(--preview-frame-bg);
}
107 changes: 74 additions & 33 deletions plugins/tidyup/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
isDesignPageNode,
isVectorSetNode,
isWebPageNode,
supportsName,
supportsPins,
useIsAllowedTo,
} from "framer-plugin"
Expand Down Expand Up @@ -67,6 +68,10 @@

const noRectByGroundNodeId: RectByGroundNodeId = {}

type NameByGroundNodeId = Record<string, string | null>

const noNameByGroundNodeId: NameByGroundNodeId = {}

function isDeepEqual(a: unknown, b: unknown): boolean {
if (a === b) return true
if (isArray(a) && isArray(b)) {
Expand All @@ -82,15 +87,17 @@
return false
}

function useGroundNodeRects() {
function useGroundNodes() {
const root = useCanvasRoot()
const selection = useSelection()

const [rects, setRects] = useState<RectByGroundNodeId>(noRectByGroundNodeId)
const [names, setNames] = useState<NameByGroundNodeId>(noNameByGroundNodeId)

useEffect(() => {
if (!root) {
setRects(noRectByGroundNodeId)
setNames(noNameByGroundNodeId)
return
}

Expand Down Expand Up @@ -121,14 +128,17 @@
}

const result: RectByGroundNodeId = {}
const resultNames: NameByGroundNodeId = {}

for (const groundNode of groundNodes) {
const rect = await groundNode.getRect()
if (!active) return
result[groundNode.id] = rect
resultNames[groundNode.id] = supportsName(groundNode) ? groundNode.name : null
}

setRects(current => (isDeepEqual(current, result) ? current : result))
setNames(current => (isDeepEqual(current, resultNames) ? current : resultNames))
}

void getRects()
Expand All @@ -138,7 +148,7 @@
}
}, [root, selection])

return rects
return { rects, names }
}

interface RectWithId extends Rect {
Expand All @@ -147,6 +157,7 @@

function getSortedRects(
rects: RectByGroundNodeId,
names: NameByGroundNodeId,
layout: Layout,
sorting: Sorting,
columnCount: number,
Expand Down Expand Up @@ -181,6 +192,13 @@
return areaB - areaA
})
break
case "name":
result.sort((a, b) => {
const nameA = names[a.id] ?? ""
const nameB = names[b.id] ?? ""
return nameA.localeCompare(nameB, undefined, { numeric: true, sensitivity: "base" })
})
break
default:
assertNever(sorting)
}
Expand All @@ -198,6 +216,17 @@

break
}
case "vertical": {
let currentY = 0

for (const rect of result) {
rect.x = 0
rect.y = currentY
currentY += rect.height + rowGap
}

break
}
case "grid": {
const maxSize = getMaxSize(result)
result.forEach((rect, index) => {
Expand Down Expand Up @@ -330,11 +359,24 @@
}
}

const allLayouts = ["horizontal", "grid", "random"] as const
const allLayouts = ["horizontal", "vertical", "grid", "random"] as const
const layoutTitles = {
horizontal: "Horizontal",
vertical: "Vertical",
grid: "Grid",
random: "Random",
} as const
const LayoutSchema = v.union(allLayouts.map(layout => v.literal(layout)))
type Layout = v.InferOutput<typeof LayoutSchema>

const allSortings = ["position", "width", "height", "area"] as const
const allSortings = ["position", "width", "height", "area", "name"] as const
const sortingTitles = {
position: "Position",
width: "Width",
height: "Height",
area: "Area",
name: "Name (A → Z)",
} as const
const SortingSchema = v.union(allSortings.map(sorting => v.literal(sorting)))
type Sorting = v.InferOutput<typeof SortingSchema>

Expand All @@ -343,17 +385,17 @@

export function App() {
const isAllowedToSetAttributes = useIsAllowedTo("setAttributes")
const rects = useGroundNodeRects()
const { rects, names } = useGroundNodes()

const isEnabled = Object.keys(rects).length > 1

const [transitionEnabled, setTransitionEnabled] = useState(false)

const [layout, setLayout] = useLocaleStorageState("layout", "horizontal", LayoutSchema)
const [sorting, setSorting] = useLocaleStorageState("sorting", "position", SortingSchema)
const [columnCount, setColumnCount] = useLocaleStorageState("columnCount", 3, ColumnCountSchema)
const [columnGap, setColumnGap] = useLocaleStorageState("columnGap", 100, GapSchema)
const [rowGap, setRowGap] = useLocaleStorageState("rowGap", 100, GapSchema)
const [layout, setLayout] = useLocalStorageState("layout", "horizontal", LayoutSchema)
const [sorting, setSorting] = useLocalStorageState("sorting", "position", SortingSchema)
const [columnCount, setColumnCount] = useLocalStorageState("columnCount", 3, ColumnCountSchema)
const [columnGap, setColumnGap] = useLocalStorageState("columnGap", 100, GapSchema)
const [rowGap, setRowGap] = useLocalStorageState("rowGap", 100, GapSchema)

const previewElement = useRef<HTMLDivElement | null>(null)
const previewSize = useElementSize({
Expand All @@ -367,8 +409,8 @@
const [randomKey, randomize] = useReducer((state: number) => state + 1, 0)

const sortedRects = useMemo(
() => getSortedRects(rects, layout, sorting, columnCount, columnGap, rowGap),
[rects, layout, sorting, columnCount, columnGap, rowGap, randomKey]
() => getSortedRects(rects, names, layout, sorting, columnCount, columnGap, rowGap),
[rects, names, layout, sorting, columnCount, columnGap, rowGap, randomKey]

Check warning on line 413 in plugins/tidyup/src/App.tsx

View workflow job for this annotation

GitHub Actions / eslint

React Hook useMemo has an unnecessary dependency: 'randomKey'. Either exclude it or remove the dependency array
)
const boundingBox = getBoundingBox(sortedRects)
const [previewScale, previewOffset] = getPreviewScaleAndOffset(boundingBox, previewSize)
Expand All @@ -386,6 +428,7 @@
width: "100%",
borderRadius: 10,
backgroundColor: "var(--framer-color-bg-tertiary)",
boxShadow: "inset 0 0 0 1px var(--preview-border)",
overflow: "hidden",
cursor: layout === "random" ? "pointer" : "default",
}}
Expand Down Expand Up @@ -468,7 +511,7 @@
>
{allLayouts.map(layout => (
<option key={layout} value={layout}>
{uppercaseFirstCharacter(layout)}
{layoutTitles[layout]}
</option>
))}
</select>
Expand All @@ -485,7 +528,7 @@
>
{allSortings.map(sorting => (
<option key={sorting} value={sorting}>
{uppercaseFirstCharacter(sorting)}
{sortingTitles[sorting]}
</option>
))}
</select>
Expand All @@ -503,20 +546,22 @@
/>
</Row>
)}
<Row title={layout === "random" ? "Min Gap" : layout === "grid" ? "Column Gap" : "Gap"}>
<Stepper
value={columnGap}
min={0}
step={10}
onChange={value => {
assert(v.is(GapSchema, value))
setColumnGap(value)
setTransitionEnabled(true)
}}
/>
</Row>
{layout === "grid" && (
<Row title="Row Gap">
{layout !== "vertical" && (
<Row title={layout === "random" ? "Min Gap" : layout === "grid" ? "Column Gap" : "Gap"}>
<Stepper
value={columnGap}
min={0}
step={10}
onChange={value => {
assert(v.is(GapSchema, value))
setColumnGap(value)
setTransitionEnabled(true)
}}
/>
</Row>
)}
{(layout === "grid" || layout === "vertical") && (
<Row title={layout === "vertical" ? "Gap" : "Row Gap"}>
<Stepper
value={rowGap}
min={0}
Expand Down Expand Up @@ -575,10 +620,6 @@
throw Error(`Should never happen: ${String(condition)}`)
}

function uppercaseFirstCharacter(value: string) {
return value.charAt(0).toUpperCase() + value.slice(1)
}

function isArray(value: unknown): value is unknown[] {
return Array.isArray(value)
}
Expand All @@ -587,7 +628,7 @@
return typeof value === "object" && value !== null && !isArray(value)
}

function useLocaleStorageState<const TSchema extends v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>>(
function useLocalStorageState<const TSchema extends v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>>(
key: string,
defaultValue: v.InferOutput<TSchema>,
schema: TSchema
Expand Down Expand Up @@ -669,7 +710,7 @@
return () => {
window.removeEventListener("resize", updateSizeWhenNeeded)
}
}, [ref, stableOnChange, ...deps])

Check warning on line 713 in plugins/tidyup/src/App.tsx

View workflow job for this annotation

GitHub Actions / eslint

React Hook useLayoutEffect has a spread element in its dependency array. This means we can't statically verify whether you've passed the correct dependencies

return size
}
Loading