auricui-skill 0.0.0-stage → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +29 -2
- package/bin/install.mjs +66 -0
- package/package.json +29 -4
- package/skill/SKILL.md +64 -0
- package/skill/agents/openai.yaml +4 -0
- package/skill/assets/examples/ApprovalCard.tsx +8 -0
- package/skill/assets/examples/BasicApp.tsx +38 -0
- package/skill/assets/examples/BriefingCard.tsx +8 -0
- package/skill/assets/examples/ChatControls.tsx +49 -0
- package/skill/assets/examples/ChatPanel.tsx +73 -0
- package/skill/assets/examples/Checkbox.tsx +13 -0
- package/skill/assets/examples/Combobox.tsx +14 -0
- package/skill/assets/examples/Drawer.tsx +10 -0
- package/skill/assets/examples/MemoryCard.tsx +9 -0
- package/skill/assets/examples/MessageList.tsx +8 -0
- package/skill/assets/examples/Theme.tsx +14 -0
- package/skill/assets/examples/ToolCall.tsx +8 -0
- package/skill/assets/examples/Tree.tsx +15 -0
- package/skill/assets/examples/examples.css +11 -0
- package/skill/assets/library/auricui-0.1.0-preview.1.tgz +0 -0
- package/skill/assets/library/auricui-0.1.0-preview.3.tgz +0 -0
- package/skill/assets/library/auricui-0.1.0-preview.4.tgz +0 -0
- package/skill/assets/upstream/LICENSE +21 -0
- package/skill/assets/upstream/apps/desktop/chat/src/App.tsx +1099 -0
- package/skill/assets/upstream/apps/desktop/chat/src/app-bridge.ts +154 -0
- package/skill/assets/upstream/apps/desktop/chat/src/chat.css +1812 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/AddSkill.tsx +223 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/AgentActivityRow.tsx +716 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/AgentIcons.tsx +46 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/AttachmentCard.tsx +34 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/AttachmentPreview.tsx +331 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/BalanceNotice.tsx +22 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/BrandLogo.tsx +35 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/ComposerCard.tsx +182 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/ConversationSessionBar.tsx +292 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/CopyDiagnostics.tsx +24 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/Divider.tsx +47 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/FileChangesList.tsx +119 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/Markdown.tsx +198 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/ModelPopover.tsx +280 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/NoticeCard.tsx +87 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/Popover.tsx +195 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/PreviewFrame.tsx +129 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/ProjectConversation.tsx +852 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/ProjectStarters.tsx +118 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/Tooltip.tsx +191 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/TurnLog.tsx +583 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/TurnQuestionCard.tsx +133 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/dock-pane-bar.ts +14 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/project-version.ts +37 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/MentionMenuPlugin.tsx +372 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptEditor.tsx +476 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptPill.tsx +399 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptReferenceNode.tsx +164 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/index.ts +31 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/mention-menu.ts +206 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/prompt-text.ts +171 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/useStickToBottom.ts +320 -0
- package/skill/assets/upstream/apps/desktop/chat/src/components/useTicker.ts +55 -0
- package/skill/assets/upstream/apps/desktop/chat/src/i18n/index.ts +42 -0
- package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/en-US.ts +408 -0
- package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/es.ts +407 -0
- package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/ja.ts +407 -0
- package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/ko.ts +403 -0
- package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/words.ts +6 -0
- package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/zh-CN.ts +407 -0
- package/skill/assets/upstream/apps/desktop/chat/src/i18n/lookup.ts +12 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-activity-runs.ts +154 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-menu-rows.ts +41 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-refs.ts +105 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-transcript.ts +440 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/asset-lists.ts +75 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/attachment-place.ts +17 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/balance.ts +16 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-layer.ts +10 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-menu.ts +18 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-store.ts +101 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/composer-attachments.ts +189 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/desktop-attachments.ts +178 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/desktop-bridge.ts +291 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/developer.ts +39 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/esc-stop.ts +23 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/file-changes.ts +32 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/live-activity.ts +20 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/local-agent-run.ts +1174 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/local-sessions.ts +25 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/model-calls.ts +87 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/pretty-json.ts +12 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/preview-top.ts +15 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/project-resources.ts +66 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/project-sessions.ts +66 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/project-versions.ts +116 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/ref-pictures.ts +119 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/relative-time.ts +46 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/skill-pages.ts +29 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/step-file.ts +23 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/structural-share.ts +52 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/studio-film.ts +44 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/studio-transfer.ts +40 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/unprompted.ts +27 -0
- package/skill/assets/upstream/apps/desktop/chat/src/lib/upload-limits.ts +53 -0
- package/skill/assets/upstream/apps/desktop/chat/src/mount.tsx +19 -0
- package/skill/assets/upstream/apps/desktop/chat/src/state/local-agent-store.ts +296 -0
- package/skill/assets/upstream/apps/desktop/chat/src/studio-refs.ts +307 -0
- package/skill/assets/upstream/apps/desktop/package.json +51 -0
- package/skill/assets/upstream/apps/desktop/shell/bridge.ts +415 -0
- package/skill/assets/upstream/apps/desktop/shell/index.html +27 -0
- package/skill/assets/upstream/apps/desktop/shell/main.tsx +17 -0
- package/skill/assets/upstream/apps/desktop/shell/shell.css +17 -0
- package/skill/assets/upstream/apps/desktop/shell/vite.config.ts +65 -0
- package/skill/assets/upstream/apps/desktop/src/agent-definitions.mjs +31 -0
- package/skill/assets/upstream/packages/openfilm/LICENSE +21 -0
- package/skill/assets/upstream/packages/openfilm/package.json +88 -0
- package/skill/assets/upstream/packages/openfilm/src/film-doc.d.mts +125 -0
- package/skill/assets/upstream/packages/openfilm/src/film-doc.mjs +986 -0
- package/skill/assets/upstream/packages/openfilm/src/overrides.mjs +73 -0
- package/skill/assets/upstream/packages/openfilm/src/timeline.mjs +1280 -0
- package/skill/assets/upstream/packages/openfilm/studio/server/bridge.js +209 -0
- package/skill/assets/upstream/packages/openfilm/studio/server/film-subtitle.mjs +785 -0
- package/skill/assets/upstream/packages/openfilm/studio/server/ops.mjs +403 -0
- package/skill/assets/upstream/packages/openfilm/studio/server/stage.js +1649 -0
- package/skill/assets/upstream/packages/openfilm/studio/server/track-layout.mjs +43 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/App.tsx +156 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/api.ts +328 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/AudioMeters.tsx +188 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BlockThumbs.tsx +84 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BlockWaveform.tsx +201 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BrandLoadingScreen.tsx +56 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BrandLogo.tsx +25 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Card.tsx +89 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ColorPicker.tsx +204 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ConfirmDialog.tsx +96 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ContextMenu.tsx +422 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/DesktopDialog.tsx +271 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ExamplesGallery.tsx +372 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmFullscreenBar.tsx +147 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmInspector.tsx +2310 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmStageSelect.tsx +3296 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FocusNav.tsx +28 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FontFamilyPicker.tsx +293 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FramePicker.tsx +69 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/InspectorPanel.tsx +25 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Launcher.tsx +38 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/LayersPanel.tsx +277 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/LiveTime.tsx +101 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MediaPool.tsx +1413 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MediaPreview.tsx +188 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MenuSelect.tsx +161 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MgOutline.tsx +24 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/OperationHistory.tsx +184 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PaneDivider.tsx +292 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PasteAttrsDialog.tsx +69 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PlaybackMenu.tsx +110 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Popover.tsx +159 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectCover.tsx +60 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectHistoryControls.tsx +748 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectLayoutMenu.tsx +82 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectMenu.tsx +111 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectSources.tsx +587 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectView.tsx +3222 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectsLibrary.tsx +567 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectsView.tsx +1593 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PropertiesSection.tsx +418 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ResourceInspector.tsx +100 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SelectionToolbar.tsx +157 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SkillsLibrary.tsx +504 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SourcePreview.tsx +464 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SpendAsks.tsx +88 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/StageMaskOverlay.tsx +299 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/StageTransformFrame.tsx +246 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleInspector.module.css +46 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleInspector.tsx +884 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleOverlay.tsx +148 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleRow.tsx +419 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/TabStrip.tsx +188 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Timeline.tsx +4911 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/TimelineMarkers.tsx +329 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Toast.tsx +55 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Tooltip.tsx +193 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/UpdateButton.tsx +104 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ViewerBar.tsx +98 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/WhatsNew.tsx +147 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/dock-pane-bar.ts +16 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/export/ExportCenter.tsx +1502 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/export/ExportTray.tsx +284 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/film-stage-hit.ts +18 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/inspector-fields.tsx +863 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/inspector-look.tsx +1357 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/player-controls/controls-bar.tsx +397 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/player-controls/transport.tsx +294 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/replace-drop.ts +20 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/AgentsSection.tsx +613 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/CustomServices.tsx +104 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/DiagnosticsGroup.tsx +202 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ModelsSection.tsx +179 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ProviderMark.tsx +24 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ProvidersSection.tsx +177 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/SettingsModal.tsx +511 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/SettingsSelect.tsx +84 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ShortcutsSection.tsx +291 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/StorageGroup.tsx +119 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/theme.ts +47 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ui.tsx +139 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/use-settings.ts +10 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/stage-menu.tsx +283 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/timeline-head-extras.tsx +91 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/typing-target.ts +45 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-editor-tools.tsx +408 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-page-changes.ts +48 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-project-history.ts +113 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/useBlockDrag.ts +639 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/Preview.tsx +426 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/player.ts +558 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/preview-frames.ts +36 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/steps.ts +36 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/undo.ts +156 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/use-film.ts +212 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/use-preview-renders.ts +89 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/globals.css +559 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/en.ts +1901 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/es.ts +1900 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/index.ts +130 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/ja.ts +1900 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/ko.ts +1896 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/words.ts +6 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/zh-CN.ts +1901 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/agent-row.ts +21 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/asset-timeline.ts +469 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/audio-levels.ts +60 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/batch-map.ts +28 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/browser.ts +10 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/canvas-fit.ts +116 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/chat-marks.ts +114 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-attrs.ts +102 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-fade.ts +70 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-look.ts +520 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-mask.ts +536 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-replace.ts +60 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/component-props.ts +128 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/context-length.ts +12 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/desktop-build.ts +44 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/diagnostics.ts +68 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/double-press.ts +22 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/dropped-overrides.ts +139 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/export-jobs.ts +191 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/export-spec.ts +261 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/field-input.ts +155 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-clip-edits.ts +82 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-frame.ts +134 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-overrides.ts +107 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-shape.ts +172 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film.ts +80 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/font-menu.ts +236 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/font-styles.ts +129 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/freeze-frame.ts +49 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/host.ts +438 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/inspect-scrub.ts +26 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/keymap-store.ts +74 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/keymap.ts +529 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/layers-tree.ts +133 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/look.ts +83 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/loudness.ts +58 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/markers.ts +152 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-bar.ts +58 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-folders.ts +45 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-place.ts +102 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-queue.ts +107 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-selection.ts +64 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/menu-position.ts +77 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/mg-transform.ts +111 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/mixed.ts +3 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/page-changes.ts +45 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/picture-files.ts +19 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/preview-prefs.ts +46 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/preview-renders.ts +71 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-delete.ts +20 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-folders.ts +144 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-history.ts +188 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-sources.ts +283 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/projects-page.ts +40 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/provider-marks.ts +35 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/providers.ts +163 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/resource-drag.ts +131 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/retry-image.ts +17 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/saved-insert.ts +40 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/saved.ts +125 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/selection-toolbar.ts +158 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/shortcut-hint.ts +21 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/similar-layers.ts +46 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/solo.ts +19 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-clip.ts +60 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-crop.ts +232 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-gesture.ts +277 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-layers.ts +71 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-mask.ts +176 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-measure.ts +53 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-types.ts +155 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/studio-refs.ts +248 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/studio-version.ts +41 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/subtitle-cues.ts +238 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/subtitles.ts +263 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/tab-menu.ts +6 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/template-controls.ts +131 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/theme-changes.ts +31 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-cache.ts +63 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-cell.ts +8 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-frame.ts +76 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-pick.ts +42 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timecode.ts +161 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-clipboard.ts +279 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-drag.ts +589 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-edit.ts +1138 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-keys.ts +63 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-layout.ts +1038 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-nav.ts +160 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-prefs.ts +73 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-split.ts +40 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-view.ts +105 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/title.ts +39 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/track-heights.ts +55 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/track-lock.ts +27 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/transitions.ts +459 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/type-metrics.ts +83 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-film-subtitles.ts +111 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-fonts.ts +181 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-loaded-thumbs.ts +91 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-edits.ts +134 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-font.ts +51 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-style.ts +68 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/waveform-columns.ts +48 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/web-update.ts +41 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/whats-new.ts +401 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/workspace-resources.ts +373 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/src/main.tsx +10 -0
- package/skill/assets/upstream/packages/openfilm/studio/ui/vite.config.ts +12 -0
- package/skill/assets/upstream/packages/shared/package.json +45 -0
- package/skill/assets/upstream/packages/shared/src/agent-activity.ts +263 -0
- package/skill/assets/upstream/packages/shared/src/contract.ts +285 -0
- package/skill/assets/upstream/packages/shared/src/index.ts +17 -0
- package/skill/assets/upstream/packages/shared/src/markdown-blocks.ts +252 -0
- package/skill/assets/upstream/packages/shared/src/modality.ts +4 -0
- package/skill/assets/upstream/packages/shared/src/prompt-reference.ts +113 -0
- package/skill/assets/upstream/packages/shared/src/title.ts +76 -0
- package/skill/assets/upstream/packages/shared/src/turn.ts +68 -0
- package/skill/assets/upstream/packages/shared/src/video.ts +26 -0
- package/skill/references/agent-components.md +33 -0
- package/skill/references/api-index.json +2693 -0
- package/skill/references/chat-components.md +65 -0
- package/skill/references/components.md +23 -0
- package/skill/references/core-components.md +44 -0
- package/skill/references/dependencies.md +22 -0
- package/skill/references/esm-package.md +48 -0
- package/skill/references/integration.md +43 -0
- package/skill/references/library-manifest.json +12 -0
- package/skill/references/source-manifest.json +325 -0
- package/skill/references/studio-components.md +24 -0
- package/skill/references/theme-motion.md +49 -0
- package/skill/references/troubleshooting.md +27 -0
- package/skill/references/verification.md +13 -0
- package/skill/scripts/check_dependencies.py +109 -0
- package/skill/scripts/inspect_api.py +60 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The audio meters: the film's sound as it plays, left and right, peak (the light bar, with a mark that
|
|
3
|
+
* holds the loudest of late) and RMS (the solid bar inside it), on a dBFS scale. A channel that reached 0 dBFS lights
|
|
4
|
+
* its clip light at the top until it is clicked.
|
|
5
|
+
*
|
|
6
|
+
* Drawn on a canvas once a frame while the film plays, from the player's mix (editor/player.ts levels); paused, the
|
|
7
|
+
* bars fall back and then nothing runs at all.
|
|
8
|
+
*/
|
|
9
|
+
import * as React from 'react';
|
|
10
|
+
|
|
11
|
+
import { useT } from '@/i18n';
|
|
12
|
+
import {
|
|
13
|
+
channelLevel, fallTo, formatDb, holdPeak, meterPosition, METER_FLOOR_DB, METER_MARKS, type HeldPeak,
|
|
14
|
+
} from '@/lib/audio-levels';
|
|
15
|
+
import type { MixSamples } from '@/editor/player';
|
|
16
|
+
import { useThemeChanges } from '@/lib/theme-changes';
|
|
17
|
+
|
|
18
|
+
/** How wide the meters are, px: two bars and their scale. */
|
|
19
|
+
export const METERS_W = 44;
|
|
20
|
+
|
|
21
|
+
const CLIP_H = 7;
|
|
22
|
+
const TOP = CLIP_H + 5;
|
|
23
|
+
const BOTTOM = 6;
|
|
24
|
+
const BAR_W = 6;
|
|
25
|
+
const BAR_GAP = 2;
|
|
26
|
+
const BARS_X = 4;
|
|
27
|
+
|
|
28
|
+
/** The bar's colors along the scale: green, yellow from −18 dB, red from −6 dB. */
|
|
29
|
+
function paint(ctx: CanvasRenderingContext2D, top: number, height: number, alpha: number) {
|
|
30
|
+
const g = ctx.createLinearGradient(0, top + height, 0, top);
|
|
31
|
+
const at = (db: number) => meterPosition(db);
|
|
32
|
+
g.addColorStop(0, `rgba(63,191,111,${alpha})`);
|
|
33
|
+
g.addColorStop(at(-18), `rgba(63,191,111,${alpha})`);
|
|
34
|
+
g.addColorStop(at(-17), `rgba(232,197,71,${alpha})`);
|
|
35
|
+
g.addColorStop(at(-7), `rgba(232,197,71,${alpha})`);
|
|
36
|
+
g.addColorStop(at(-6), `rgba(229,72,77,${alpha})`);
|
|
37
|
+
g.addColorStop(1, `rgba(229,72,77,${alpha})`);
|
|
38
|
+
return g;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type Shown = { peak: number; rms: number; held: HeldPeak & { shown: number } };
|
|
42
|
+
const quiet = (): Shown => ({ peak: METER_FLOOR_DB, rms: METER_FLOOR_DB, held: { db: METER_FLOOR_DB, at: 0, shown: METER_FLOOR_DB } });
|
|
43
|
+
|
|
44
|
+
export function AudioMeters({ levels, playing }: {
|
|
45
|
+
/** The mix's latest samples, or null when nothing plays (see Player.levels). */
|
|
46
|
+
levels: () => MixSamples | null;
|
|
47
|
+
playing: boolean;
|
|
48
|
+
}) {
|
|
49
|
+
const t = useT();
|
|
50
|
+
const canvasRef = React.useRef<HTMLCanvasElement | null>(null);
|
|
51
|
+
const boxRef = React.useRef<HTMLDivElement | null>(null);
|
|
52
|
+
const shown = React.useRef<[Shown, Shown]>([quiet(), quiet()]);
|
|
53
|
+
const [clipped, setClipped] = React.useState<[boolean, boolean]>([false, false]);
|
|
54
|
+
const clippedRef = React.useRef(clipped);
|
|
55
|
+
clippedRef.current = clipped;
|
|
56
|
+
const [size, setSize] = React.useState({ w: METERS_W, h: 0 });
|
|
57
|
+
const [peakText, setPeakText] = React.useState(METER_FLOOR_DB);
|
|
58
|
+
|
|
59
|
+
React.useLayoutEffect(() => {
|
|
60
|
+
const el = boxRef.current;
|
|
61
|
+
if (!el) return undefined;
|
|
62
|
+
const ro = new ResizeObserver(() => setSize({ w: el.clientWidth, h: el.clientHeight }));
|
|
63
|
+
ro.observe(el);
|
|
64
|
+
setSize({ w: el.clientWidth, h: el.clientHeight });
|
|
65
|
+
return () => ro.disconnect();
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
/** One picture of the bars and the scale. */
|
|
69
|
+
const draw = React.useCallback(() => {
|
|
70
|
+
const canvas = canvasRef.current;
|
|
71
|
+
if (!canvas || size.h <= TOP + BOTTOM) return;
|
|
72
|
+
const dpr = window.devicePixelRatio || 1;
|
|
73
|
+
if (canvas.width !== Math.round(size.w * dpr) || canvas.height !== Math.round(size.h * dpr)) {
|
|
74
|
+
canvas.width = Math.round(size.w * dpr);
|
|
75
|
+
canvas.height = Math.round(size.h * dpr);
|
|
76
|
+
}
|
|
77
|
+
const ctx = canvas.getContext('2d');
|
|
78
|
+
if (!ctx) return;
|
|
79
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
80
|
+
ctx.clearRect(0, 0, size.w, size.h);
|
|
81
|
+
const h = size.h - TOP - BOTTOM;
|
|
82
|
+
const y = (db: number) => TOP + h * (1 - meterPosition(db));
|
|
83
|
+
const styles = getComputedStyle(canvas);
|
|
84
|
+
const faint = styles.getPropertyValue('--tl-faint').trim() || 'rgba(128,128,128,0.6)';
|
|
85
|
+
const lane = styles.getPropertyValue('--tl-lane').trim() || 'rgba(128,128,128,0.15)';
|
|
86
|
+
/* the scale: a line and a number every 6 dB, a number every 12 */
|
|
87
|
+
ctx.font = '8px ui-sans-serif, system-ui, sans-serif';
|
|
88
|
+
ctx.textBaseline = 'middle';
|
|
89
|
+
const scaleX = BARS_X + 2 * BAR_W + BAR_GAP + 3;
|
|
90
|
+
for (const mark of METER_MARKS) {
|
|
91
|
+
const my = Math.round(y(mark)) + 0.5;
|
|
92
|
+
ctx.fillStyle = faint;
|
|
93
|
+
ctx.fillRect(scaleX, my, 3, 1);
|
|
94
|
+
if (mark % 12 === 0) ctx.fillText(String(Math.abs(mark)), scaleX + 5, my);
|
|
95
|
+
}
|
|
96
|
+
shown.current.forEach((ch, i) => {
|
|
97
|
+
const x = BARS_X + i * (BAR_W + BAR_GAP);
|
|
98
|
+
ctx.fillStyle = lane;
|
|
99
|
+
ctx.fillRect(x, TOP, BAR_W, h);
|
|
100
|
+
/* peak: the light bar; RMS: the solid one */
|
|
101
|
+
ctx.fillStyle = paint(ctx, TOP, h, 0.45);
|
|
102
|
+
ctx.fillRect(x, y(ch.peak), BAR_W, TOP + h - y(ch.peak));
|
|
103
|
+
ctx.fillStyle = paint(ctx, TOP, h, 1);
|
|
104
|
+
ctx.fillRect(x, y(ch.rms), BAR_W, TOP + h - y(ch.rms));
|
|
105
|
+
if (ch.held.shown > METER_FLOOR_DB) {
|
|
106
|
+
ctx.fillStyle = ch.held.shown >= -6 ? '#e5484d' : ch.held.shown >= -18 ? '#e8c547' : '#d9f2e2';
|
|
107
|
+
ctx.fillRect(x, Math.round(y(ch.held.shown)), BAR_W, 2);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}, [size]);
|
|
111
|
+
|
|
112
|
+
/* its colors are the theme's, read as it draws: when the theme changes it draws again, or it would keep the old
|
|
113
|
+
theme's bars until the film plays */
|
|
114
|
+
const theme = useThemeChanges();
|
|
115
|
+
React.useEffect(() => { draw(); }, [draw, theme]);
|
|
116
|
+
|
|
117
|
+
/* while playing (and while the bars fall back after), once a frame; nothing otherwise */
|
|
118
|
+
React.useEffect(() => {
|
|
119
|
+
let frame = 0;
|
|
120
|
+
let last = performance.now();
|
|
121
|
+
const tick = () => {
|
|
122
|
+
const now = performance.now();
|
|
123
|
+
const dt = now - last;
|
|
124
|
+
last = now;
|
|
125
|
+
const samples = playing ? levels() : null;
|
|
126
|
+
let moving = false;
|
|
127
|
+
const clips: [boolean, boolean] = [...clippedRef.current];
|
|
128
|
+
shown.current = shown.current.map((ch, i) => {
|
|
129
|
+
const level = samples ? channelLevel(samples[i as 0 | 1]) : { peakDb: METER_FLOOR_DB, rmsDb: METER_FLOOR_DB, clipped: false };
|
|
130
|
+
if (level.clipped) clips[i] = true;
|
|
131
|
+
/* stopped, the bars drop four times as fast: what is left of them says nothing */
|
|
132
|
+
const fall = samples ? dt : dt * 4;
|
|
133
|
+
const next: Shown = {
|
|
134
|
+
peak: fallTo(ch.peak, level.peakDb, fall),
|
|
135
|
+
rms: fallTo(ch.rms, level.rmsDb, fall),
|
|
136
|
+
held: holdPeak(ch.held, level.peakDb, now),
|
|
137
|
+
};
|
|
138
|
+
if (next.peak > METER_FLOOR_DB || next.held.shown > METER_FLOOR_DB) moving = true;
|
|
139
|
+
return next;
|
|
140
|
+
}) as [Shown, Shown];
|
|
141
|
+
if (clips[0] !== clippedRef.current[0] || clips[1] !== clippedRef.current[1]) setClipped(clips);
|
|
142
|
+
draw();
|
|
143
|
+
if (playing || moving) frame = requestAnimationFrame(tick);
|
|
144
|
+
else setPeakText(METER_FLOOR_DB);
|
|
145
|
+
};
|
|
146
|
+
frame = requestAnimationFrame(tick);
|
|
147
|
+
/* the readout: the loudest held peak, a few times a second (a number that changes every frame cannot be read) */
|
|
148
|
+
const readout = playing ? window.setInterval(() => setPeakText(Math.max(shown.current[0].held.shown, shown.current[1].held.shown)), 250) : 0;
|
|
149
|
+
return () => { cancelAnimationFrame(frame); window.clearInterval(readout); };
|
|
150
|
+
}, [playing, levels, draw]);
|
|
151
|
+
|
|
152
|
+
const clipLabel = t('editorTools.metersClip');
|
|
153
|
+
return (
|
|
154
|
+
<div
|
|
155
|
+
className="flex h-full shrink-0 flex-col border-l"
|
|
156
|
+
style={{ width: METERS_W, borderColor: 'var(--tl-line)', background: 'var(--tl-head)' }}
|
|
157
|
+
data-audio-meters=""
|
|
158
|
+
aria-label={t('editorTools.meters')}
|
|
159
|
+
role="group"
|
|
160
|
+
>
|
|
161
|
+
<div className="flex h-[34px] shrink-0 items-center justify-center font-mono text-[9px] tabular-nums" style={{ color: peakText >= -0.05 ? '#e5484d' : 'var(--tl-faint)' }}
|
|
162
|
+
title={t('editorTools.metersPeak')}>
|
|
163
|
+
{formatDb(peakText)}
|
|
164
|
+
</div>
|
|
165
|
+
<div ref={boxRef} className="relative min-h-0 flex-1">
|
|
166
|
+
<canvas ref={canvasRef} className="absolute inset-0 h-full w-full" style={{ width: size.w, height: size.h }} />
|
|
167
|
+
{/* the clip lights: lit when a channel reached 0 dBFS; a click puts them out */}
|
|
168
|
+
{([0, 1] as const).map((i) => (
|
|
169
|
+
<button
|
|
170
|
+
key={i}
|
|
171
|
+
type="button"
|
|
172
|
+
aria-label={`${clipLabel} (${i ? 'R' : 'L'})`}
|
|
173
|
+
title={clipLabel}
|
|
174
|
+
data-meter-clip={clipped[i] ? '1' : '0'}
|
|
175
|
+
onClick={() => setClipped([false, false])}
|
|
176
|
+
className="absolute top-0 rounded-[1px]"
|
|
177
|
+
style={{
|
|
178
|
+
left: BARS_X + i * (BAR_W + BAR_GAP),
|
|
179
|
+
width: BAR_W,
|
|
180
|
+
height: CLIP_H,
|
|
181
|
+
background: clipped[i] ? '#e5484d' : 'var(--tl-lane)',
|
|
182
|
+
}}
|
|
183
|
+
/>
|
|
184
|
+
))}
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { THUMB_CELL_PX } from '@/lib/thumb-cache';
|
|
4
|
+
import { thumbCellStyle } from '@/lib/thumb-cell';
|
|
5
|
+
import { pickShot } from '@/lib/thumb-pick';
|
|
6
|
+
import { retryImage } from '@/lib/retry-image';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The row of thumbnails on a picture clip. The pictures come from outside (the `thumbs` prop); this only lays them
|
|
10
|
+
* out, on the source's own time grid (cells at whole steps from the source's start), cut by the clip's edges — as in
|
|
11
|
+
* any editor: trimming a clip crops its first and last pictures, it does not start the row again at its new edge.
|
|
12
|
+
* Laid out from the clip's left edge instead, a trim drew the first picture whole and the next one over it, and the
|
|
13
|
+
* row jumped to a new grid on every release.
|
|
14
|
+
*
|
|
15
|
+
* Every cell gets a picture, even one shot for another cell: zooming changes which time each cell stands for, and
|
|
16
|
+
* waiting for exact shots would blank the row on every zoom. So each cell takes the nearest shot (lib/thumb-pick:
|
|
17
|
+
* the cell straddling the clip's start stands for that start, and a shot from inside the clip comes first), with no
|
|
18
|
+
* distance limit — a far picture beats a half-empty clip that looks broken; exact shots replace it as they arrive.
|
|
19
|
+
*/
|
|
20
|
+
export function BlockThumbs({
|
|
21
|
+
thumbs, startMs, endMs, pxPerMs, height, topOffset = 0, thumbWidthPx = THUMB_CELL_PX, visiblePx,
|
|
22
|
+
}: {
|
|
23
|
+
thumbs: ReadonlyMap<number, string>;
|
|
24
|
+
startMs: number;
|
|
25
|
+
endMs: number;
|
|
26
|
+
pxPerMs: number;
|
|
27
|
+
height: number;
|
|
28
|
+
/** How far below the clip's top to start: the info strip (name + length) is there and must stay visible. */
|
|
29
|
+
topOffset?: number;
|
|
30
|
+
thumbWidthPx?: number;
|
|
31
|
+
/**
|
|
32
|
+
* The stretch worth drawing (px from the clip's left edge); the whole clip without. A long source zoomed in is
|
|
33
|
+
* hundreds of cells while a screen shows dozens. The window is rounded to whole screens (see Timeline thumbWindow).
|
|
34
|
+
*/
|
|
35
|
+
visiblePx?: { from: number; to: number } | undefined;
|
|
36
|
+
}): React.ReactElement | null {
|
|
37
|
+
/* the shot times, sorted: the one for each cell is found by binary search */
|
|
38
|
+
const times = React.useMemo(
|
|
39
|
+
() => [...thumbs.keys()].sort((a, b) => a - b),
|
|
40
|
+
[thumbs],
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const cells = React.useMemo(() => {
|
|
44
|
+
if (!(pxPerMs > 0) || height <= 0 || !times.length) return [];
|
|
45
|
+
const stepMs = thumbWidthPx / pxPerMs;
|
|
46
|
+
const out: { left: number; url: string }[] = [];
|
|
47
|
+
/* the cell straddling the clip's start begins before it (a negative left) and is cut by the clip's edge */
|
|
48
|
+
const first = Math.floor(startMs / stepMs) * stepMs;
|
|
49
|
+
for (let t = first; t < endMs; t += stepMs) {
|
|
50
|
+
const left = (t - startMs) * pxPerMs;
|
|
51
|
+
if (visiblePx && (left + thumbWidthPx < visiblePx.from || left > visiblePx.to)) continue;
|
|
52
|
+
const shot = pickShot(times, Math.round(t), startMs, endMs);
|
|
53
|
+
const url = shot == null ? undefined : thumbs.get(shot);
|
|
54
|
+
if (!url) continue;
|
|
55
|
+
out.push({ left, url });
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}, [thumbs, times, startMs, endMs, pxPerMs, height, thumbWidthPx, visiblePx]);
|
|
59
|
+
|
|
60
|
+
if (!cells.length) return null;
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
className="pointer-events-none absolute inset-x-0 bottom-0 overflow-hidden"
|
|
65
|
+
style={{ top: topOffset }}
|
|
66
|
+
>
|
|
67
|
+
{/* keyed by index: on zoom every cell's left changes, and keying by it would remount the whole row */}
|
|
68
|
+
{cells.map((cell, i) => (
|
|
69
|
+
<img
|
|
70
|
+
key={i}
|
|
71
|
+
src={cell.url}
|
|
72
|
+
alt=""
|
|
73
|
+
draggable={false}
|
|
74
|
+
className="absolute top-0 object-cover"
|
|
75
|
+
style={thumbCellStyle(cell.left, thumbWidthPx, height)}
|
|
76
|
+
/* a picture that fails is hidden (the clip color shows), better than a broken-image icon, and asked for
|
|
77
|
+
again a little later; it comes back when it loads */
|
|
78
|
+
onError={(e) => { e.currentTarget.style.visibility = 'hidden'; retryImage(e.currentTarget); }}
|
|
79
|
+
onLoad={(e) => { e.currentTarget.style.visibility = ''; }}
|
|
80
|
+
/>
|
|
81
|
+
))}
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The waveform on a clip. Without it a sound track is plain bars: no telling where someone speaks, where they breathe,
|
|
3
|
+
* where it is silent — and "did this cut land mid-word" can only be seen on the waveform.
|
|
4
|
+
*
|
|
5
|
+
* Drawn from the bottom, not mirrored: sound rows are short, and a mirrored waveform leaves half the height each way.
|
|
6
|
+
* A skyline still reads when low. On a canvas, not hundreds of divs: dozens of clips with
|
|
7
|
+
* hundreds of peaks each would be tens of thousands of nodes.
|
|
8
|
+
*
|
|
9
|
+
* Peaks come from the server: GET media?what=wave&path= → `{ duration (s), peaks }`, 40 peaks a second, 0..1;
|
|
10
|
+
* 503 while the file is still being written.
|
|
11
|
+
*/
|
|
12
|
+
import * as React from 'react';
|
|
13
|
+
import { normalizeWavePeaks, waveformColumns } from '@/lib/waveform-columns';
|
|
14
|
+
import { mediaFetch } from '@/lib/media-queue';
|
|
15
|
+
import { useThemeChanges } from '@/lib/theme-changes';
|
|
16
|
+
|
|
17
|
+
/** A sound's envelope: the whole file's peaks and its length (to turn peak indexes into ms). */
|
|
18
|
+
interface Wave {
|
|
19
|
+
peaks: number[];
|
|
20
|
+
durationMs: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Peaks per second the server sends; the length when the answer has none. */
|
|
24
|
+
const PEAKS_PER_SECOND = 40;
|
|
25
|
+
|
|
26
|
+
/** At most this many device pixels per canvas: past the browser's limit a canvas goes blank. */
|
|
27
|
+
const MAX_CANVAS_PX = 4096;
|
|
28
|
+
/** A change this soon after the last drawing is drawn once the changes stop for as long (ms). */
|
|
29
|
+
const BURST_MS = 150;
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* Drawings waiting, done a few a frame (FRAME_BUDGET_MS of them at most): a zoom of a film with hundreds of sounds redrew
|
|
33
|
+
* them all in one go, a long freeze. A newer drawing of a canvas replaces its older one; the stretched old bitmap holds
|
|
34
|
+
* until it is done.
|
|
35
|
+
*/
|
|
36
|
+
const FRAME_BUDGET_MS = 6;
|
|
37
|
+
const waiting = new Map<HTMLCanvasElement, () => void>();
|
|
38
|
+
let drawing = 0;
|
|
39
|
+
function drawSoon(el: HTMLCanvasElement, draw: () => void) {
|
|
40
|
+
waiting.set(el, draw);
|
|
41
|
+
if (!drawing) drawing = requestAnimationFrame(drawSome);
|
|
42
|
+
}
|
|
43
|
+
function drawSome() {
|
|
44
|
+
drawing = 0;
|
|
45
|
+
const start = performance.now();
|
|
46
|
+
for (const [el, draw] of waiting) {
|
|
47
|
+
waiting.delete(el);
|
|
48
|
+
draw();
|
|
49
|
+
if (performance.now() - start > FRAME_BUDGET_MS) break;
|
|
50
|
+
}
|
|
51
|
+
if (waiting.size) drawing = requestAnimationFrame(drawSome);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* the color a waveform is drawn in, read once per color and theme: read from each canvas, each drawing laid out the
|
|
55
|
+
page's styles again */
|
|
56
|
+
const colors = new Map<string, string>();
|
|
57
|
+
function colorOf(el: HTMLCanvasElement, color: string, theme: unknown): string {
|
|
58
|
+
const key = `${String(theme)}\0${color}`;
|
|
59
|
+
let c = colors.get(key);
|
|
60
|
+
if (!c) {
|
|
61
|
+
c = getComputedStyle(el).color || color;
|
|
62
|
+
if (colors.size > 64) colors.clear();
|
|
63
|
+
colors.set(key, c);
|
|
64
|
+
}
|
|
65
|
+
return c;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Waveforms already fetched: the same sound is drawn again on every drag and zoom. */
|
|
69
|
+
const CACHE = new Map<string, Wave>();
|
|
70
|
+
const PENDING = new Map<string, Promise<Wave | null>>();
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The waveform, asked again while the server says it is not ready yet (503: a file an agent has just written).
|
|
74
|
+
* About half a minute at most; after that the block stays plain until its address changes.
|
|
75
|
+
*/
|
|
76
|
+
async function fetchWhenReady(url: string, tries = 30): Promise<Response> {
|
|
77
|
+
const res = await mediaFetch(url);
|
|
78
|
+
if (res.status !== 503 || tries <= 1) return res;
|
|
79
|
+
const wait = Math.min(Math.max(Number(res.headers.get('retry-after')) || 1, 0.5), 5) * 1000;
|
|
80
|
+
await new Promise((done) => setTimeout(done, wait));
|
|
81
|
+
return fetchWhenReady(url, tries - 1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function loadWave(url: string): Promise<Wave | null> {
|
|
85
|
+
if (CACHE.has(url)) return CACHE.get(url) ?? null;
|
|
86
|
+
const already = PENDING.get(url);
|
|
87
|
+
if (already) return already;
|
|
88
|
+
const task = fetchWhenReady(url)
|
|
89
|
+
.then(async (r) => {
|
|
90
|
+
if (!r.ok) return null;
|
|
91
|
+
const body = (await r.json()) as { peaks?: number[]; duration?: number; durationMs?: number };
|
|
92
|
+
if (!body.peaks?.length) return null;
|
|
93
|
+
const durationMs = typeof body.durationMs === 'number' ? body.durationMs
|
|
94
|
+
: typeof body.duration === 'number' ? body.duration * 1000
|
|
95
|
+
: (body.peaks.length / PEAKS_PER_SECOND) * 1000;
|
|
96
|
+
return { peaks: normalizeWavePeaks(body.peaks), durationMs };
|
|
97
|
+
})
|
|
98
|
+
.catch(() => null)
|
|
99
|
+
.then((wave) => {
|
|
100
|
+
if (wave) {
|
|
101
|
+
CACHE.set(url, wave);
|
|
102
|
+
while (CACHE.size > 256) CACHE.delete(CACHE.keys().next().value!);
|
|
103
|
+
}
|
|
104
|
+
PENDING.delete(url);
|
|
105
|
+
return wave;
|
|
106
|
+
});
|
|
107
|
+
PENDING.set(url, task);
|
|
108
|
+
return task;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function BlockWaveform({
|
|
112
|
+
url, width, height, color, inMs = 0, durMs, visiblePx,
|
|
113
|
+
}: {
|
|
114
|
+
url: string;
|
|
115
|
+
width: number;
|
|
116
|
+
height: number;
|
|
117
|
+
/**
|
|
118
|
+
* The waveform's color; may be a CSS variable (`var(--tl-wave)`): a canvas cannot read variables, so it is set as
|
|
119
|
+
* the element's `color` and read back computed, following the theme.
|
|
120
|
+
*/
|
|
121
|
+
color: string;
|
|
122
|
+
/**
|
|
123
|
+
* Draw only this stretch (px within the clip). Zoomed in, a clip can be tens of thousands of pixels wide and only a
|
|
124
|
+
* slice is on screen: a canvas over the whole clip would be stretched past the limit (blurry) or burn memory on what
|
|
125
|
+
* cannot be seen.
|
|
126
|
+
*/
|
|
127
|
+
visiblePx?: { from: number; to: number };
|
|
128
|
+
/**
|
|
129
|
+
* Where in the source the clip starts and how much it plays. The envelope is the whole file's; without taking off
|
|
130
|
+
* the in point the whole file is squeezed into the clip and the waveform lies about where the sound is.
|
|
131
|
+
*/
|
|
132
|
+
inMs?: number;
|
|
133
|
+
durMs: number;
|
|
134
|
+
}): React.ReactElement | null {
|
|
135
|
+
const ref = React.useRef<HTMLCanvasElement | null>(null);
|
|
136
|
+
const [loaded, setLoaded] = React.useState(() => ({ url, wave: CACHE.get(url) ?? null }));
|
|
137
|
+
// A new edit must never display the previous arrangement while its request is in flight.
|
|
138
|
+
const wave = loaded.url === url ? loaded.wave : CACHE.get(url) ?? null;
|
|
139
|
+
|
|
140
|
+
React.useEffect(() => {
|
|
141
|
+
let alive = true;
|
|
142
|
+
/* the same wave already in hand (from the cache) renders nothing: a clip drawn as it scrolls into view is not
|
|
143
|
+
drawn twice */
|
|
144
|
+
void loadWave(url).then((wave) => { if (alive) setLoaded((cur) => (cur.url === url && cur.wave === wave ? cur : { url, wave })); });
|
|
145
|
+
return () => { alive = false; };
|
|
146
|
+
}, [url]);
|
|
147
|
+
|
|
148
|
+
/* its color is read as it draws: a theme changed, it draws again */
|
|
149
|
+
const theme = useThemeChanges();
|
|
150
|
+
const from = Math.max(0, Math.min(width, Math.floor(visiblePx?.from ?? 0)));
|
|
151
|
+
const to = Math.max(from, Math.min(width, Math.ceil(visiblePx?.to ?? width)));
|
|
152
|
+
const sliceW = to - from;
|
|
153
|
+
|
|
154
|
+
/* when it was last drawn: a change soon after is part of a burst (a zoom, a scroll) */
|
|
155
|
+
const drawnAt = React.useRef(-Infinity);
|
|
156
|
+
React.useEffect(() => {
|
|
157
|
+
const el = ref.current;
|
|
158
|
+
if (!el || !wave?.peaks.length || sliceW <= 0 || height <= 0) return undefined;
|
|
159
|
+
/* drawn soon (see drawSoon); in a burst of zoom or scroll steps, once the burst stops (the stretched old bitmap
|
|
160
|
+
holds meanwhile): redrawn at every step, the waveforms were most of what a zoom cost */
|
|
161
|
+
const draw = () => {
|
|
162
|
+
drawnAt.current = performance.now();
|
|
163
|
+
/* capped in device pixels, not CSS pixels: capping CSS pixels would also drop Retina's 2×, and this layer is
|
|
164
|
+
thin enough that blur makes it useless. Full ratio while it fits, down to 1× at the cap. */
|
|
165
|
+
const drawW = Math.min(Math.round(sliceW), MAX_CANVAS_PX);
|
|
166
|
+
const dpr = Math.max(1, Math.min(window.devicePixelRatio || 1, MAX_CANVAS_PX / drawW));
|
|
167
|
+
el.width = Math.max(1, Math.round(drawW * dpr));
|
|
168
|
+
el.height = Math.max(1, Math.round(height * dpr));
|
|
169
|
+
const ctx = el.getContext('2d');
|
|
170
|
+
if (!ctx) return;
|
|
171
|
+
ctx.scale(dpr, dpr);
|
|
172
|
+
ctx.clearRect(0, 0, drawW, height);
|
|
173
|
+
ctx.fillStyle = colorOf(el, color, theme);
|
|
174
|
+
|
|
175
|
+
/* one column per pixel: more peaks than pixels takes the maximum (sampling would miss a drum hit and draw
|
|
176
|
+
silence); fewer peaks than pixels interpolates (nearest-peak steps are the sampling grid, not the sound) */
|
|
177
|
+
const sliceIn = inMs + (width > 0 ? (from / width) * durMs : 0);
|
|
178
|
+
const sliceDur = width > 0 ? (sliceW / width) * durMs : durMs;
|
|
179
|
+
const columns = waveformColumns(wave, sliceIn, sliceDur, drawW);
|
|
180
|
+
/* one path, one fill: thousands of fillRect calls would be thousands of draws */
|
|
181
|
+
ctx.beginPath();
|
|
182
|
+
for (let x = 0; x < drawW; x += 1) {
|
|
183
|
+
const v = columns[x]!;
|
|
184
|
+
const h = Math.max(1, v * height);
|
|
185
|
+
ctx.rect(x, height - h, 1, h);
|
|
186
|
+
}
|
|
187
|
+
ctx.fill();
|
|
188
|
+
};
|
|
189
|
+
const timer = window.setTimeout(() => drawSoon(el, draw), performance.now() - drawnAt.current < BURST_MS ? BURST_MS : 0);
|
|
190
|
+
return () => { window.clearTimeout(timer); waiting.delete(el); };
|
|
191
|
+
}, [wave, width, height, color, inMs, durMs, from, sliceW, theme]);
|
|
192
|
+
|
|
193
|
+
if (!wave?.peaks.length || sliceW <= 0) return null;
|
|
194
|
+
return (
|
|
195
|
+
<canvas
|
|
196
|
+
ref={ref}
|
|
197
|
+
className="pointer-events-none absolute bottom-0"
|
|
198
|
+
style={{ left: from, width: sliceW, height, color }}
|
|
199
|
+
/>
|
|
200
|
+
);
|
|
201
|
+
}
|
package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BrandLoadingScreen.tsx
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full-screen loading: the logo and a moving bar, while a project opens.
|
|
3
|
+
*
|
|
4
|
+
* Opening a project means finding (or making) it and then reading its film. Both involve a
|
|
5
|
+
* noticeable wait, and during that wait **the screen must change**: when the UI sits still,
|
|
6
|
+
* people do not think "it is loading", they think "did I miss the click?" and click again.
|
|
7
|
+
*
|
|
8
|
+
* So no skeleton is drawn here. A skeleton guesses what will appear, and a wrong guess is
|
|
9
|
+
* costly: a new project lands on an empty picture, while a skeleton would first show blocks
|
|
10
|
+
* that are not there, making the page flash when loading ends. A full-screen background with
|
|
11
|
+
* the logo guesses nothing; what lands is covered up, not corrected.
|
|
12
|
+
*
|
|
13
|
+
* Two places show it in turn: App while it finds the project to open, then ProjectView while
|
|
14
|
+
* that project loads. Both look identical, so the handoff has no visible seam; the user sees
|
|
15
|
+
* one loading screen that stays lit throughout, not two that flash in turn.
|
|
16
|
+
*/
|
|
17
|
+
import { useT } from '@/i18n';
|
|
18
|
+
import { BrandLogo } from './BrandLogo';
|
|
19
|
+
|
|
20
|
+
export function BrandLoadingScreen({
|
|
21
|
+
labelKey,
|
|
22
|
+
}: {
|
|
23
|
+
/** What is being done (an i18n key): both callers pass the same one and get the same sentence. */
|
|
24
|
+
labelKey?: string;
|
|
25
|
+
}) {
|
|
26
|
+
const t = useT();
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
role="status"
|
|
30
|
+
aria-live="polite"
|
|
31
|
+
aria-busy
|
|
32
|
+
/* No entrance animation. This screen is mounted twice (App first, then ProjectView), and
|
|
33
|
+
the second mount would replay the entrance, flashing the whole screen at the handoff,
|
|
34
|
+
which is worse than having no loading screen. */
|
|
35
|
+
/* The background is --panel, the same as the workspace's when it lands, so when the loading
|
|
36
|
+
screen gives way the content just grows in and the base color never moves. */
|
|
37
|
+
className="workspace-shell z-[9000] flex flex-col items-center justify-center bg-[var(--panel)]"
|
|
38
|
+
>
|
|
39
|
+
{/* Kept small: an enlarged logo in the middle of the screen looks like a splash ad, while
|
|
40
|
+
this screen only means "one moment". The bar below is what moves. */}
|
|
41
|
+
<span className="text-[var(--text)]"><BrandLogo size={34} /></span>
|
|
42
|
+
|
|
43
|
+
{labelKey ? (
|
|
44
|
+
<p className="mt-6 text-[14px] font-medium tracking-[-0.01em] text-[var(--text)]">
|
|
45
|
+
{t(labelKey)}
|
|
46
|
+
</p>
|
|
47
|
+
) : null}
|
|
48
|
+
|
|
49
|
+
{/* The bar is fake: opening a project cannot report a percentage. It only says one thing
|
|
50
|
+
here: something is still moving. A line frozen in one place would look stuck. */}
|
|
51
|
+
<div className="mt-5 h-[3px] w-[180px] overflow-hidden rounded-full bg-[var(--border)]">
|
|
52
|
+
<div className="openfilm-loading-bar h-full w-1/3 rounded-full bg-[var(--text)]/60" />
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* The mark: a playhead over two clips, forming an F, in the surrounding text's color. The clips are cut away around
|
|
5
|
+
* the playhead (a real gap, so whatever is behind shows through), which keeps the playhead's shape in one color.
|
|
6
|
+
*/
|
|
7
|
+
const VIEWBOX = '8 6 84 92';
|
|
8
|
+
const PLAYHEAD = 'M17 12H37Q40 12 40 15V27Q40 29 38.5 30.5L31 37V88Q31 92 27 92Q23 92 23 88V37L15.5 30.5Q14 29 14 27V15Q14 12 17 12Z';
|
|
9
|
+
|
|
10
|
+
export function BrandLogo({ size = 18 }: { size?: number }) {
|
|
11
|
+
const gap = `openfilm-gap-${React.useId().replace(/:/g, '')}`;
|
|
12
|
+
return (
|
|
13
|
+
<svg width={size} height={size} viewBox={VIEWBOX} fill="currentColor" className="block shrink-0" role="img" aria-label="OpenFilm">
|
|
14
|
+
<mask id={gap} maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
|
|
15
|
+
<rect width="100" height="100" fill="#fff" />
|
|
16
|
+
<path d={PLAYHEAD} stroke="#000" strokeWidth="7" strokeLinejoin="round" />
|
|
17
|
+
</mask>
|
|
18
|
+
<g mask={`url(#${gap})`}>
|
|
19
|
+
<rect x="22" y="12" width="64" height="20" rx="5" />
|
|
20
|
+
<rect x="25" y="44" width="47" height="19" rx="5" />
|
|
21
|
+
</g>
|
|
22
|
+
<path d={PLAYHEAD} />
|
|
23
|
+
</svg>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one card shell (confirmations, notices) and its buttons: background and border follow the theme, radius 12,
|
|
3
|
+
* one button height (28) in three weights — primary (filled), secondary (outlined), quiet (text only) — plus danger.
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { X } from 'lucide-react';
|
|
7
|
+
import { useT } from '@/i18n';
|
|
8
|
+
|
|
9
|
+
/** The shell of things that float up (menus, dropdowns): the card's shadow on the floating surface. */
|
|
10
|
+
export const POPOVER_CHROME: React.CSSProperties = {
|
|
11
|
+
background: 'var(--bg-surface)',
|
|
12
|
+
border: '1px solid var(--border-strong)',
|
|
13
|
+
borderRadius: 12,
|
|
14
|
+
boxShadow: '0 16px 40px -8px rgba(0,0,0,0.4), 0 4px 12px rgba(0,0,0,0.18)',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function Card({
|
|
18
|
+
icon,
|
|
19
|
+
title,
|
|
20
|
+
body,
|
|
21
|
+
onClose,
|
|
22
|
+
children,
|
|
23
|
+
role,
|
|
24
|
+
className = '',
|
|
25
|
+
}: {
|
|
26
|
+
icon?: React.ReactNode;
|
|
27
|
+
title: React.ReactNode;
|
|
28
|
+
body?: React.ReactNode;
|
|
29
|
+
onClose?: () => void;
|
|
30
|
+
children?: React.ReactNode;
|
|
31
|
+
role?: React.AriaRole;
|
|
32
|
+
className?: string;
|
|
33
|
+
}) {
|
|
34
|
+
const t = useT();
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
{...(role ? { role } : {})}
|
|
38
|
+
className={`relative rounded-[12px] border border-[var(--card-border)] bg-[var(--card-bg)] px-3.5 pb-3 pt-3 shadow-[0_8px_24px_-12px_rgba(0,0,0,0.35)] ${className}`}
|
|
39
|
+
>
|
|
40
|
+
{onClose ? (
|
|
41
|
+
<div className="absolute right-2 top-2 flex items-center gap-0.5">
|
|
42
|
+
<button
|
|
43
|
+
type="button"
|
|
44
|
+
onClick={onClose}
|
|
45
|
+
aria-label={t('project.close')}
|
|
46
|
+
className="flex h-6 w-6 items-center justify-center rounded-md text-[var(--text-faint)] transition hover:bg-[var(--bg-hover)] hover:text-[var(--text)]"
|
|
47
|
+
>
|
|
48
|
+
<X size={14} strokeWidth={1.75} />
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
) : null}
|
|
52
|
+
<div className={`flex items-start gap-2 ${onClose ? 'pr-7' : ''}`}>
|
|
53
|
+
{icon ? <span className="mt-[2px] shrink-0 text-[var(--text-muted)]">{icon}</span> : null}
|
|
54
|
+
<div className="min-w-0 flex-1">
|
|
55
|
+
<h3 className="text-[13px] font-medium leading-snug text-[var(--text)]">{title}</h3>
|
|
56
|
+
{body ? <div className="mt-1 whitespace-pre-line break-words text-[12.5px] leading-relaxed text-[var(--text-muted)]">{body}</div> : null}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
{children}
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function CardActions({ children, className = '' }: { children: React.ReactNode; className?: string }) {
|
|
65
|
+
return <div className={`mt-3 flex items-center justify-end gap-2 ${className}`}>{children}</div>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type Weight = 'primary' | 'secondary' | 'quiet' | 'danger';
|
|
69
|
+
|
|
70
|
+
const WEIGHT: Record<Weight, string> = {
|
|
71
|
+
primary: 'bg-[var(--text)] text-[var(--panel)] hover:opacity-90',
|
|
72
|
+
secondary: 'text-[var(--text)] ring-1 ring-inset ring-[var(--border-strong)] hover:bg-[var(--bg-hover)]',
|
|
73
|
+
quiet: 'text-[var(--text-muted)] hover:text-[var(--text)]',
|
|
74
|
+
danger: 'bg-[var(--err)] text-white hover:brightness-110',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const CardButton = React.forwardRef<
|
|
78
|
+
HTMLButtonElement,
|
|
79
|
+
React.ButtonHTMLAttributes<HTMLButtonElement> & { weight?: Weight }
|
|
80
|
+
>(function CardButton({ weight = 'secondary', className = '', type = 'button', ...props }, ref) {
|
|
81
|
+
return (
|
|
82
|
+
<button
|
|
83
|
+
{...props}
|
|
84
|
+
ref={ref}
|
|
85
|
+
type={type}
|
|
86
|
+
className={`inline-flex h-7 shrink-0 items-center justify-center gap-1.5 rounded-lg px-3 text-[12px] font-medium outline-none transition focus-visible:ring-2 focus-visible:ring-[var(--text)]/30 disabled:pointer-events-none disabled:opacity-50 ${WEIGHT[weight]} ${className}`}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
});
|