diff --git a/docs/pr/project-browser/projects-dark.png b/docs/pr/project-browser/projects-dark.png index 1492b412..281e9c8d 100644 Binary files a/docs/pr/project-browser/projects-dark.png and b/docs/pr/project-browser/projects-dark.png differ diff --git a/docs/pr/project-browser/projects.png b/docs/pr/project-browser/projects.png index 71008fbc..fbbf4e66 100644 Binary files a/docs/pr/project-browser/projects.png and b/docs/pr/project-browser/projects.png differ diff --git a/docs/pr/project-browser/settings.png b/docs/pr/project-browser/settings.png index c7822e8c..7455ce8f 100644 Binary files a/docs/pr/project-browser/settings.png and b/docs/pr/project-browser/settings.png differ diff --git a/docs/pr/project-browser/shortcuts.png b/docs/pr/project-browser/shortcuts.png new file mode 100644 index 00000000..61982c2d Binary files /dev/null and b/docs/pr/project-browser/shortcuts.png differ diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index bdb46077..f666690e 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -42,7 +42,8 @@ import { loadEditorPreferences, saveEditorPreferences } from "./editorPreference import { getDefaultBorderRadiusPercent } from "./projectPersistence"; import { SliderControl } from "./SliderControl"; import { WallpaperGrid } from "./WallpaperGrid"; -import { KeyboardShortcutsDialog } from "./TutorialHelp"; +import { useShortcuts } from "@/contexts/ShortcutsContext"; +import { SettingsRow } from "./SettingsRow"; import type { AnnotationRegion, AnnotationType, @@ -1004,6 +1005,7 @@ export function SettingsPanel({ ); const [experimentalUpdatesEnabled, setExperimentalUpdatesEnabled] = useState(false); const [savingExperimentalUpdates, setSavingExperimentalUpdates] = useState(false); + const { openConfig: openShortcutsConfig } = useShortcuts(); const [internalActiveEffectSection] = useState("scene"); const activeEffectSection = activeEffectSectionProp ?? internalActiveEffectSection; const removeBackgroundStateRef = useRef<{ @@ -1793,7 +1795,7 @@ export function SettingsPanel({ style={{ scrollbarGutter: "stable" }} >
- + {tSettings("background.title")} @@ -2241,8 +2243,7 @@ export function SettingsPanel({ categories={advanced ? ["general", "motion", "advanced"] : ["general", "motion"]} > -
- {t("editor.theme.appearance", "Appearance")} + -
+ -
- {t("common.app.language", "Language")} + -
+
{advanced && (
{tSettings("updates.title", "Updates")} -
-
-
- {tSettings("updates.experimental", "Experimental updates")} -
-
- {tSettings( - "updates.experimentalDescription", - "This is the front line of user testing - highly experimental so expect bugs", - )} -
-
+ -
+
)}
-
-
-
- {tSettings( - "effects.autoApplyFreshRecordingZooms", - "Auto-apply fresh recording zooms", - )} -
-
- {tSettings( - "effects.autoApplyFreshRecordingZoomsDescription", - "Suggest cursor-follow zooms automatically when you open a new recording.", - )} -
-
+ -
-
-
-
- {tSettings("effects.connectZooms", "Connect neighboring zooms")} -
-
- {tSettings( - "effects.connectZoomsDescription", - "Smooth consecutive zoom regions into a continuous camera move.", - )} -
-
+ + -
+
@@ -2375,17 +2360,15 @@ export function SettingsPanel({
-
- {t("editor.keyboardShortcuts.title")} - -
+ + +
{advanced && showDevMotionControls ? ( -
+
@@ -2398,7 +2381,7 @@ export function SettingsPanel({ )}
- + DEV
diff --git a/src/components/video-editor/SettingsRow.tsx b/src/components/video-editor/SettingsRow.tsx new file mode 100644 index 00000000..2ea1fd06 --- /dev/null +++ b/src/components/video-editor/SettingsRow.tsx @@ -0,0 +1,29 @@ +import { Label, Description } from "@heroui/react"; +import type { ReactNode } from "react"; + +/** One spacing and typography contract for editor and dashboard preferences. */ +export function SettingsRow({ + title, + description, + children, + stacked = false, +}: { + title: ReactNode; + description?: ReactNode; + children: ReactNode; + stacked?: boolean; +}) { + return ( +
+
+ + {description && ( + {description} + )} +
+
{children}
+
+ ); +} diff --git a/src/components/video-editor/ShortcutsConfigDialog.tsx b/src/components/video-editor/ShortcutsConfigDialog.tsx index a7a958d8..15e33d54 100644 --- a/src/components/video-editor/ShortcutsConfigDialog.tsx +++ b/src/components/video-editor/ShortcutsConfigDialog.tsx @@ -1,3 +1,4 @@ +import { Kbd, Description, Modal } from "@heroui/react"; import { Keyboard, ArrowCounterClockwise as RotateCcw } from "@/components/ui/icons"; import { useCallback, useEffect, useState } from "react"; import { toast } from "@/components/ui/toast"; @@ -126,108 +127,112 @@ export function ShortcutsConfigDialog() { if (!open) handleClose(); }} > - - - - + + + + {t("shortcutsConfig.title")} -
-

- {t("shortcutsConfig.configurable")} -

- {SHORTCUT_ACTIONS.map((action) => { - const isCapturing = captureFor === action; - const hasConflict = conflict?.forAction === action; - return ( -
-
- - {SHORTCUT_LABELS[action]} - - -
- {hasConflict && conflict?.conflictWith.type === "configurable" && ( -
- - {t("shortcutsConfig.alreadyUsedBy", undefined, { - action: SHORTCUT_LABELS[ - conflict.conflictWith.action - ], - })} + +
+

+ {t("shortcutsConfig.configurable")} +

+ {SHORTCUT_ACTIONS.map((action) => { + const isCapturing = captureFor === action; + const hasConflict = conflict?.forAction === action; + return ( +
+
+ + {SHORTCUT_LABELS[action]} -
- - -
+
- )} + {hasConflict && + conflict?.conflictWith.type === "configurable" && ( +
+ + {t("shortcutsConfig.alreadyUsedBy", undefined, { + action: SHORTCUT_LABELS[ + conflict.conflictWith.action + ], + })} + +
+ + +
+
+ )} +
+ ); + })} +
+ +
+

+ {t("shortcutsConfig.fixed")} +

+ {FIXED_SHORTCUTS.map(({ label, display }) => ( +
+ {label} + {display}
- ); - })} -
+ ))} +
-
-

- {t("shortcutsConfig.fixed")} -

- {FIXED_SHORTCUTS.map(({ label, display }) => ( -
- {label} - - {display} - -
- ))} -
+ + {t("shortcutsConfig.instructions")} + + -

- {t("shortcutsConfig.instructions")} -

- - +
- - - - - {!isRaw && ( - setSort("recent")}> - Last edited + +
+ + + + + {!isRaw && ( + setSort("recent")}> + Last edited + + )} + setSort("created")}> + Last created - )} - setSort("created")}> - Last created - - setSort("name")}> - Name - - - - + setSort("name")}> + Name + + + + +
{selecting && (
diff --git a/src/components/video-editor/dashboard/DashboardSettings.tsx b/src/components/video-editor/dashboard/DashboardSettings.tsx index f159983c..68f9f43b 100644 --- a/src/components/video-editor/dashboard/DashboardSettings.tsx +++ b/src/components/video-editor/dashboard/DashboardSettings.tsx @@ -1,5 +1,6 @@ import { SettingsSections, SettingsCategory } from "../SettingsSections"; import { createContext, useContext, useEffect, useState, type ReactNode } from "react"; +import { SettingsRow } from "../SettingsRow"; import { Switch } from "@/components/ui/switch"; import { supportsHudCaptureProtection } from "@/lib/hudCaptureProtection"; import { Button } from "@/components/ui/button"; @@ -48,30 +49,29 @@ export function DashboardSettings({ onImportFile }: { onImportFile: () => Promis {settingsContent} -
-

Open video or project

+ -
+
-
-
-

Recordings folder

-

+ {recordings} -

-
+ + } + > -
+ {captureSupported && ( -
-
-

Hide HUD from recordings

-

- Only while recording. The idle HUD stays visible in captures. -

-
+ Promis }) } /> -
+ )}
{import.meta.env.DEV && ( -
-

Preview update UI

+ -
+ )}
-
-
-

Projects folder

- {directory && ( -

- {directory} -

- )} -
+ -
+

Named projects save automatically. Previews refresh when you return to Projects. diff --git a/src/components/video-editor/dashboard/DashboardSidebar.tsx b/src/components/video-editor/dashboard/DashboardSidebar.tsx index 1dab7f8a..21097509 100644 --- a/src/components/video-editor/dashboard/DashboardSidebar.tsx +++ b/src/components/video-editor/dashboard/DashboardSidebar.tsx @@ -1,14 +1,6 @@ +import { SidebarCards } from "./SidebarCards"; import { FolderRow } from "./FolderRow"; -import { - ArrowLeft, - Cloud, - File, - GearSix, - House, - Plus, - UploadSimple, - UserCircle, -} from "@/components/ui/icons"; +import { Cloud, File, GearSix, House, Plus, UserCircle } from "@/components/ui/icons"; import { Button } from "@/components/ui/button"; @@ -25,9 +17,6 @@ export function DashboardSidebar({ metadata, update, navClass, - run, - onImportFile, - onOpenChange, onSignIn, accountLabel, }: Pick< @@ -39,9 +28,6 @@ export function DashboardSidebar({ | "metadata" | "update" | "navClass" - | "run" - | "onImportFile" - | "onOpenChange" | "onSignIn" | "accountLabel" >) { @@ -142,22 +128,7 @@ export function DashboardSidebar({ ))}

- - + - ))} - {assignedFolders.length > 1 && ( - item.name) - .join(", ")}`} - title={assignedFolders - .slice(1) - .map((item) => item.name) - .join(", ")} - > - +{assignedFolders.length - 1} - - )} -
+ assignFolder(entry.path, id)} + /> + ); + const hidden = folders.slice(visible); + return ( +
+ {folders.slice(0, visible).map((folder) => chip(folder))} + {hidden.length > 0 && ( + folder.name).join(", ")}`} + title={hidden.map((folder) => folder.name).join(", ")} + > + +{hidden.length} + + )} + +
+ ); +} diff --git a/src/components/video-editor/dashboard/SidebarCards.tsx b/src/components/video-editor/dashboard/SidebarCards.tsx new file mode 100644 index 00000000..d5a581b5 --- /dev/null +++ b/src/components/video-editor/dashboard/SidebarCards.tsx @@ -0,0 +1,64 @@ +import { Card } from "@heroui/react"; +import { Button } from "@/components/ui/button"; +import { toast } from "@/components/ui/toast"; +import { sidebarCardConfig } from "./sidebarCardConfig"; + +export function SidebarCards() { + if (!sidebarCardConfig.enabled || sidebarCardConfig.cards.length === 0) return null; + return ( +
+ {sidebarCardConfig.cards.map((card) => ( + + {card.image && ( + {card.imageAlt + )} + + {card.heading} + {card.subheading && ( + + {card.subheading} + + )} + + {card.paragraph && ( + + {card.paragraph} + + )} + {card.href && ( + + + + )} + + ))} +
+ ); +} diff --git a/src/components/video-editor/dashboard/sidebarCardConfig.ts b/src/components/video-editor/dashboard/sidebarCardConfig.ts new file mode 100644 index 00000000..05b2e882 --- /dev/null +++ b/src/components/video-editor/dashboard/sidebarCardConfig.ts @@ -0,0 +1,25 @@ +export type SidebarCard = { + id: string; + image?: string; + imageAlt?: string; + heading: string; + subheading?: string; + paragraph?: string; + href?: string; + linkLabel?: string; +}; + +/** Code-only sidebar content. Empty cards or enabled:false hides this area. */ +export const sidebarCardConfig: { enabled: boolean; cards: SidebarCard[] } = { + enabled: true, + cards: [ + { + id: "placeholder", + image: "announcements/placeholder-1.svg", + imageAlt: "Placeholder banner", + heading: "Coming soon", + subheading: "News from Recordly", + paragraph: "A place for updates, tips, and announcements.", + }, + ], +}; diff --git a/src/components/video-editor/layout/EditorHeader.tsx b/src/components/video-editor/layout/EditorHeader.tsx index 1099ad50..18ec37b8 100644 --- a/src/components/video-editor/layout/EditorHeader.tsx +++ b/src/components/video-editor/layout/EditorHeader.tsx @@ -1,5 +1,6 @@ import { House, + FilmStrip, ArrowClockwise as Redo2, ArrowCounterClockwise as Undo2, } from "@/components/ui/icons"; @@ -19,6 +20,8 @@ import { EditorPresetMenu } from "./EditorPresetMenu"; const SHOW_PRESETS_BUTTON = false; type Props = { + clipsOpen: boolean; + onToggleClips: () => void; t: ReturnType["t"]; headerLeftControlsPaddingClass: string; project: ReturnType; @@ -105,6 +108,16 @@ export function EditorHeader(props: Props) { className={`editor-header-start flex min-w-0 items-center gap-1 ${headerLeftControlsPaddingClass}`} style={{ WebkitAppRegion: "no-drag" } as CSSProperties} > +