From a9b9cf6bf50f08723896659634f2e6afc17bcd42 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 29 Apr 2026 00:37:56 +0200 Subject: chore: switch from biome to eslint + prettier; fix ton of lint issues --- .../editor/panels/assets/assets-panel-store.tsx | 4 +- .../editor/panels/assets/views/assets.tsx | 7 +- .../editor/panels/assets/views/settings/index.tsx | 32 +- .../hooks/use-keyframed-color-property.ts | 192 +++++------ .../hooks/use-keyframed-number-property.ts | 350 ++++++++++----------- .../panels/properties/hooks/use-property-draft.ts | 43 ++- .../components/editor/panels/properties/index.tsx | 7 +- .../panels/properties/stores/properties-store.ts | 9 +- 8 files changed, 329 insertions(+), 315 deletions(-) (limited to 'apps/web/src/components') diff --git a/apps/web/src/components/editor/panels/assets/assets-panel-store.tsx b/apps/web/src/components/editor/panels/assets/assets-panel-store.tsx index 1539d305..b412de7b 100644 --- a/apps/web/src/components/editor/panels/assets/assets-panel-store.tsx +++ b/apps/web/src/components/editor/panels/assets/assets-panel-store.tsx @@ -93,7 +93,7 @@ interface AssetsPanelStore { setMediaViewMode: (mode: MediaViewMode) => void; mediaSortBy: MediaSortKey; mediaSortOrder: MediaSortOrder; - setMediaSort: (key: MediaSortKey, order: MediaSortOrder) => void; + setMediaSort: (args: { key: MediaSortKey; order: MediaSortOrder }) => void; } export const useAssetsPanelStore = create()( @@ -109,7 +109,7 @@ export const useAssetsPanelStore = create()( setMediaViewMode: (mode) => set({ mediaViewMode: mode }), mediaSortBy: "name", mediaSortOrder: "asc", - setMediaSort: (key, order) => + setMediaSort: ({ key, order }) => set({ mediaSortBy: key, mediaSortOrder: order }), }), { diff --git a/apps/web/src/components/editor/panels/assets/views/assets.tsx b/apps/web/src/components/editor/panels/assets/views/assets.tsx index c071fdc7..00cb015f 100644 --- a/apps/web/src/components/editor/panels/assets/views/assets.tsx +++ b/apps/web/src/components/editor/panels/assets/views/assets.tsx @@ -139,9 +139,12 @@ export function MediaView() { const handleSort = ({ key }: { key: MediaSortKey }) => { if (mediaSortBy === key) { - setMediaSort(key, mediaSortOrder === "asc" ? "desc" : "asc"); + setMediaSort({ + key, + order: mediaSortOrder === "asc" ? "desc" : "asc", + }); } else { - setMediaSort(key, "asc"); + setMediaSort({ key, order: "asc" }); } }; diff --git a/apps/web/src/components/editor/panels/assets/views/settings/index.tsx b/apps/web/src/components/editor/panels/assets/views/settings/index.tsx index bf3a1c16..5edbeab3 100644 --- a/apps/web/src/components/editor/panels/assets/views/settings/index.tsx +++ b/apps/web/src/components/editor/panels/assets/views/settings/index.tsx @@ -1,6 +1,6 @@ "use client"; -import { useRef, useState } from "react"; +import { useState } from "react"; import { PanelView } from "@/components/editor/panels/assets/views/base-panel"; import { Select, @@ -34,6 +34,10 @@ import type { TCanvasSize } from "@/project/types"; type SettingsView = "project-info" | "background"; +function isSettingsView(value: string): value is SettingsView { + return value === "project-info" || value === "background"; +} + const PRESET_LABELS: Record = { "1:1": "1:1", "16:9": "16:9", @@ -73,23 +77,20 @@ function useCanvasDimensionDraft({ value: number; onCommit: (value: number) => void; }) { - const pendingValueRef = useRef(value); - const syncedValueRef = useRef(value); - - if (syncedValueRef.current !== value) { - syncedValueRef.current = value; - pendingValueRef.current = value; - } + const [pendingValue, setPendingValue] = useState(value); return usePropertyDraft({ displayValue: formatCanvasDimension({ value }), parse: (input) => parseCanvasDimension({ input }), + onStartEditing: () => { + setPendingValue(value); + }, onPreview: (nextValue) => { - pendingValueRef.current = nextValue; + setPendingValue(nextValue); }, onCommit: () => { - if (pendingValueRef.current !== value) { - onCommit(pendingValueRef.current); + if (pendingValue !== value) { + onCommit(pendingValue); } }, }); @@ -212,7 +213,14 @@ export function SettingsView() { contentClassName="px-0" scrollClassName="pt-0" actions={ - setView(v as SettingsView)}> + { + if (isSettingsView(value)) { + setView(value); + } + }} + > Project info Background diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts index f6216208..91d29fd7 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts @@ -1,96 +1,96 @@ -import { useEditor } from "@/editor/use-editor"; -import { - getKeyframeAtTime, - hasKeyframesForPath, - upsertElementKeyframe, -} from "@/animation"; -import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types"; -import type { TimelineElement } from "@/timeline"; -import type { MediaTime } from "@/wasm"; - -export function useKeyframedColorProperty({ - trackId, - elementId, - animations, - propertyPath, - localTime, - isPlayheadWithinElementRange, - resolvedColor, - buildBaseUpdates, -}: { - trackId: string; - elementId: string; - animations: ElementAnimations | undefined; - propertyPath: AnimationPropertyPath; - localTime: MediaTime; - isPlayheadWithinElementRange: boolean; - resolvedColor: string; - buildBaseUpdates: ({ value }: { value: string }) => Partial; -}) { - const editor = useEditor(); - - const hasAnimatedKeyframes = hasKeyframesForPath({ animations, propertyPath }); - const keyframeAtTime = isPlayheadWithinElementRange - ? getKeyframeAtTime({ animations, propertyPath, time: localTime }) - : null; - const keyframeIdAtTime = keyframeAtTime?.id ?? null; - const isKeyframedAtTime = keyframeAtTime !== null; - const shouldUseAnimatedChannel = - hasAnimatedKeyframes && isPlayheadWithinElementRange; - - const onChange = ({ color }: { color: string }) => { - if (shouldUseAnimatedChannel) { - editor.timeline.previewElements({ - updates: [ - { - trackId, - elementId, - updates: { - animations: upsertElementKeyframe({ - animations, - propertyPath, - time: localTime, - value: color, - }), - }, - }, - ], - }); - return; - } - - editor.timeline.previewElements({ - updates: [{ trackId, elementId, updates: buildBaseUpdates({ value: color }) }], - }); - }; - - const onChangeEnd = () => editor.timeline.commitPreview(); - - const toggleKeyframe = () => { - if (!isPlayheadWithinElementRange) { - return; - } - - if (keyframeIdAtTime) { - editor.timeline.removeKeyframes({ - keyframes: [{ trackId, elementId, propertyPath, keyframeId: keyframeIdAtTime }], - }); - return; - } - - editor.timeline.upsertKeyframes({ - keyframes: [ - { trackId, elementId, propertyPath, time: localTime, value: resolvedColor }, - ], - }); - }; - - return { - isKeyframedAtTime, - hasAnimatedKeyframes, - keyframeIdAtTime, - onChange, - onChangeEnd, - toggleKeyframe, - }; -} +import { useEditor } from "@/editor/use-editor"; +import { + getKeyframeAtTime, + hasKeyframesForPath, + upsertElementKeyframe, +} from "@/animation"; +import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types"; +import type { TimelineElement } from "@/timeline"; +import type { MediaTime } from "@/wasm"; + +export function useKeyframedColorProperty({ + trackId, + elementId, + animations, + propertyPath, + localTime, + isPlayheadWithinElementRange, + resolvedColor, + buildBaseUpdates, +}: { + trackId: string; + elementId: string; + animations: ElementAnimations | undefined; + propertyPath: AnimationPropertyPath; + localTime: MediaTime; + isPlayheadWithinElementRange: boolean; + resolvedColor: string; + buildBaseUpdates: ({ value }: { value: string }) => Partial; +}) { + const editor = useEditor(); + + const hasAnimatedKeyframes = hasKeyframesForPath({ animations, propertyPath }); + const keyframeAtTime = isPlayheadWithinElementRange + ? getKeyframeAtTime({ animations, propertyPath, time: localTime }) + : null; + const keyframeIdAtTime = keyframeAtTime?.id ?? null; + const isKeyframedAtTime = keyframeAtTime !== null; + const shouldUseAnimatedChannel = + hasAnimatedKeyframes && isPlayheadWithinElementRange; + + const onChange = ({ color }: { color: string }) => { + if (shouldUseAnimatedChannel) { + editor.timeline.previewElements({ + updates: [ + { + trackId, + elementId, + updates: { + animations: upsertElementKeyframe({ + animations, + propertyPath, + time: localTime, + value: color, + }), + }, + }, + ], + }); + return; + } + + editor.timeline.previewElements({ + updates: [{ trackId, elementId, updates: buildBaseUpdates({ value: color }) }], + }); + }; + + const onChangeEnd = () => editor.timeline.commitPreview(); + + const toggleKeyframe = () => { + if (!isPlayheadWithinElementRange) { + return; + } + + if (keyframeIdAtTime) { + editor.timeline.removeKeyframes({ + keyframes: [{ trackId, elementId, propertyPath, keyframeId: keyframeIdAtTime }], + }); + return; + } + + editor.timeline.upsertKeyframes({ + keyframes: [ + { trackId, elementId, propertyPath, time: localTime, value: resolvedColor }, + ], + }); + }; + + return { + isKeyframedAtTime, + hasAnimatedKeyframes, + keyframeIdAtTime, + onChange, + onChangeEnd, + toggleKeyframe, + }; +} diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts index d52f7d97..610c2253 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts @@ -1,175 +1,175 @@ -import { useEditor } from "@/editor/use-editor"; -import { - getKeyframeAtTime, - hasKeyframesForPath, - upsertElementKeyframe, -} from "@/animation"; -import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types"; -import type { TimelineElement } from "@/timeline"; -import { snapToStep } from "@/utils/math"; -import { usePropertyDraft } from "./use-property-draft"; -import type { MediaTime } from "@/wasm"; - -export function useKeyframedNumberProperty({ - trackId, - elementId, - animations, - propertyPath, - localTime, - isPlayheadWithinElementRange, - displayValue, - parse, - valueAtPlayhead, - step, - buildBaseUpdates, - buildAdditionalKeyframes, -}: { - trackId: string; - elementId: string; - animations: ElementAnimations | undefined; - propertyPath: AnimationPropertyPath; - localTime: MediaTime; - isPlayheadWithinElementRange: boolean; - displayValue: string; - parse: (input: string) => number | null; - valueAtPlayhead: number; - step?: number; - buildBaseUpdates: ({ value }: { value: number }) => Partial; - buildAdditionalKeyframes?: ({ - value, - }: { value: number }) => Array<{ propertyPath: AnimationPropertyPath; value: number }>; -}) { - const editor = useEditor(); - const snapValue = (value: number) => - step != null ? snapToStep({ value, step }) : value; - - const hasAnimatedKeyframes = hasKeyframesForPath({ animations, propertyPath }); - const keyframeAtTime = isPlayheadWithinElementRange - ? getKeyframeAtTime({ animations, propertyPath, time: localTime }) - : null; - const keyframeIdAtTime = keyframeAtTime?.id ?? null; - const isKeyframedAtTime = keyframeAtTime !== null; - const shouldUseAnimatedChannel = - hasAnimatedKeyframes && isPlayheadWithinElementRange; - - const previewValue = ({ value }: { value: number }) => { - const nextValue = snapValue(value); - if (shouldUseAnimatedChannel) { - const additionalKeyframes = buildAdditionalKeyframes?.({ value: nextValue }) ?? []; - const updatedAnimations = [ - { propertyPath, value: nextValue }, - ...additionalKeyframes, - ].reduce( - (currentAnimations, keyframe) => - upsertElementKeyframe({ - animations: currentAnimations, - propertyPath: keyframe.propertyPath, - time: localTime, - value: keyframe.value, - }), - animations, - ); - editor.timeline.previewElements({ - updates: [ - { - trackId, - elementId, - updates: { animations: updatedAnimations }, - }, - ], - }); - return; - } - - editor.timeline.previewElements({ - updates: [ - { - trackId, - elementId, - updates: buildBaseUpdates({ value: nextValue }), - }, - ], - }); - }; - - const propertyDraft = usePropertyDraft({ - displayValue, - parse: (input) => { - const parsedValue = parse(input); - return parsedValue === null ? null : snapValue(parsedValue); - }, - onPreview: (value) => previewValue({ value }), - onCommit: () => editor.timeline.commitPreview(), - }); - - const toggleKeyframe = () => { - if (!isPlayheadWithinElementRange) { - return; - } - - if (keyframeIdAtTime) { - editor.timeline.removeKeyframes({ - keyframes: [ - { - trackId, - elementId, - propertyPath, - keyframeId: keyframeIdAtTime, - }, - ], - }); - return; - } - - editor.timeline.upsertKeyframes({ - keyframes: [ - { - trackId, - elementId, - propertyPath, - time: localTime, - value: snapValue(valueAtPlayhead), - }, - ], - }); - }; - - const commitValue = ({ value }: { value: number }) => { - const nextValue = snapValue(value); - if (shouldUseAnimatedChannel) { - const additionalKeyframes = buildAdditionalKeyframes?.({ value: nextValue }) ?? []; - editor.timeline.upsertKeyframes({ - keyframes: [ - { trackId, elementId, propertyPath, time: localTime, value: nextValue }, - ...additionalKeyframes.map((keyframe) => ({ - trackId, - elementId, - propertyPath: keyframe.propertyPath, - time: localTime, - value: keyframe.value, - })), - ], - }); - return; - } - - editor.timeline.updateElements({ - updates: [ - { - trackId, - elementId, - patch: buildBaseUpdates({ value: nextValue }), - }, - ], - }); - }; - - return { - ...propertyDraft, - hasAnimatedKeyframes, - isKeyframedAtTime, - keyframeIdAtTime, - toggleKeyframe, - commitValue, - }; -} +import { useEditor } from "@/editor/use-editor"; +import { + getKeyframeAtTime, + hasKeyframesForPath, + upsertElementKeyframe, +} from "@/animation"; +import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types"; +import type { TimelineElement } from "@/timeline"; +import { snapToStep } from "@/utils/math"; +import { usePropertyDraft } from "./use-property-draft"; +import type { MediaTime } from "@/wasm"; + +export function useKeyframedNumberProperty({ + trackId, + elementId, + animations, + propertyPath, + localTime, + isPlayheadWithinElementRange, + displayValue, + parse, + valueAtPlayhead, + step, + buildBaseUpdates, + buildAdditionalKeyframes, +}: { + trackId: string; + elementId: string; + animations: ElementAnimations | undefined; + propertyPath: AnimationPropertyPath; + localTime: MediaTime; + isPlayheadWithinElementRange: boolean; + displayValue: string; + parse: (input: string) => number | null; + valueAtPlayhead: number; + step?: number; + buildBaseUpdates: ({ value }: { value: number }) => Partial; + buildAdditionalKeyframes?: ({ + value, + }: { value: number }) => Array<{ propertyPath: AnimationPropertyPath; value: number }>; +}) { + const editor = useEditor(); + const snapValue = (value: number) => + step != null ? snapToStep({ value, step }) : value; + + const hasAnimatedKeyframes = hasKeyframesForPath({ animations, propertyPath }); + const keyframeAtTime = isPlayheadWithinElementRange + ? getKeyframeAtTime({ animations, propertyPath, time: localTime }) + : null; + const keyframeIdAtTime = keyframeAtTime?.id ?? null; + const isKeyframedAtTime = keyframeAtTime !== null; + const shouldUseAnimatedChannel = + hasAnimatedKeyframes && isPlayheadWithinElementRange; + + const previewValue = ({ value }: { value: number }) => { + const nextValue = snapValue(value); + if (shouldUseAnimatedChannel) { + const additionalKeyframes = buildAdditionalKeyframes?.({ value: nextValue }) ?? []; + const updatedAnimations = [ + { propertyPath, value: nextValue }, + ...additionalKeyframes, + ].reduce( + (currentAnimations, keyframe) => + upsertElementKeyframe({ + animations: currentAnimations, + propertyPath: keyframe.propertyPath, + time: localTime, + value: keyframe.value, + }), + animations, + ); + editor.timeline.previewElements({ + updates: [ + { + trackId, + elementId, + updates: { animations: updatedAnimations }, + }, + ], + }); + return; + } + + editor.timeline.previewElements({ + updates: [ + { + trackId, + elementId, + updates: buildBaseUpdates({ value: nextValue }), + }, + ], + }); + }; + + const propertyDraft = usePropertyDraft({ + displayValue, + parse: (input) => { + const parsedValue = parse(input); + return parsedValue === null ? null : snapValue(parsedValue); + }, + onPreview: (value) => previewValue({ value }), + onCommit: () => editor.timeline.commitPreview(), + }); + + const toggleKeyframe = () => { + if (!isPlayheadWithinElementRange) { + return; + } + + if (keyframeIdAtTime) { + editor.timeline.removeKeyframes({ + keyframes: [ + { + trackId, + elementId, + propertyPath, + keyframeId: keyframeIdAtTime, + }, + ], + }); + return; + } + + editor.timeline.upsertKeyframes({ + keyframes: [ + { + trackId, + elementId, + propertyPath, + time: localTime, + value: snapValue(valueAtPlayhead), + }, + ], + }); + }; + + const commitValue = ({ value }: { value: number }) => { + const nextValue = snapValue(value); + if (shouldUseAnimatedChannel) { + const additionalKeyframes = buildAdditionalKeyframes?.({ value: nextValue }) ?? []; + editor.timeline.upsertKeyframes({ + keyframes: [ + { trackId, elementId, propertyPath, time: localTime, value: nextValue }, + ...additionalKeyframes.map((keyframe) => ({ + trackId, + elementId, + propertyPath: keyframe.propertyPath, + time: localTime, + value: keyframe.value, + })), + ], + }); + return; + } + + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId, + patch: buildBaseUpdates({ value: nextValue }), + }, + ], + }); + }; + + return { + ...propertyDraft, + hasAnimatedKeyframes, + isKeyframedAtTime, + keyframeIdAtTime, + toggleKeyframe, + commitValue, + }; +} diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-property-draft.ts b/apps/web/src/components/editor/panels/properties/hooks/use-property-draft.ts index 0e7ae6a7..7e8f6e40 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-property-draft.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-property-draft.ts @@ -1,4 +1,4 @@ -import { useReducer, useRef } from "react"; +import { useState } from "react"; import { evaluateMathExpression } from "@/utils/math"; function looksLikeExpression({ input }: { input: string }): boolean { @@ -14,59 +14,56 @@ export function usePropertyDraft({ parse, onPreview, onCommit, + onStartEditing, supportsExpressions = true, }: { displayValue: string; parse: (input: string) => T | null; onPreview: (value: T) => void; onCommit: () => void; + onStartEditing?: () => void; supportsExpressions?: boolean; }) { - const [, forceRender] = useReducer( - (renderVersion: number) => renderVersion + 1, - 0, - ); - const isEditing = useRef(false); - const draft = useRef(""); + const [isEditing, setIsEditing] = useState(false); + const [draft, setDraft] = useState(""); return { - displayValue: isEditing.current ? draft.current : sourceDisplay, + displayValue: isEditing ? draft : sourceDisplay, scrubTo: (value: number) => { const parsed = parse(String(value)); if (parsed !== null) onPreview(parsed); }, commitScrub: onCommit, onFocus: () => { - isEditing.current = true; - draft.current = sourceDisplay; - forceRender(); + setIsEditing(true); + setDraft(sourceDisplay); + onStartEditing?.(); }, onChange: ( event: React.ChangeEvent, ) => { - draft.current = event.target.value; - forceRender(); + const nextDraft = event.target.value; + setDraft(nextDraft); - const parsed = parse(event.target.value); + const parsed = parse(nextDraft); if (parsed !== null) { onPreview(parsed); } }, - onBlur: () => { - if ( - supportsExpressions && - looksLikeExpression({ input: draft.current }) - ) { - const evaluated = evaluateMathExpression({ input: draft.current }); + onBlur: ( + event: React.FocusEvent, + ) => { + const nextDraft = event.target.value; + if (supportsExpressions && looksLikeExpression({ input: nextDraft })) { + const evaluated = evaluateMathExpression({ input: nextDraft }); if (evaluated !== null) { const parsed = parse(String(evaluated)); if (parsed !== null) onPreview(parsed); } } onCommit(); - isEditing.current = false; - draft.current = ""; - forceRender(); + setIsEditing(false); + setDraft(""); }, }; } diff --git a/apps/web/src/components/editor/panels/properties/index.tsx b/apps/web/src/components/editor/panels/properties/index.tsx index 52fa42b5..d946027d 100644 --- a/apps/web/src/components/editor/panels/properties/index.tsx +++ b/apps/web/src/components/editor/panels/properties/index.tsx @@ -71,7 +71,12 @@ export function PropertiesPanel() {