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,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markers on the timeline (lib/markers): pins on their own row under the ruler for the film's, notches on the clips for
|
|
3
|
+
* a clip's. A click goes to a marker and opens its card (its name, its color, delete), a drag moves a pin along its
|
|
4
|
+
* row, a right-click opens the card too. M (or the toolbar's button) adds one at the playhead (M on one opens its card),
|
|
5
|
+
* ⇧M / ⌥M go to the next / the one before.
|
|
6
|
+
*/
|
|
7
|
+
import * as React from 'react';
|
|
8
|
+
import { createPortal } from 'react-dom';
|
|
9
|
+
import { Trash2 } from 'lucide-react';
|
|
10
|
+
|
|
11
|
+
import { useT } from '@/i18n';
|
|
12
|
+
import { MARKER_COLORS, MARKER_NAME_MAX, MARKER_PAINT, type Marker, type MarkerColor, type PlacedMarker } from '@/lib/markers';
|
|
13
|
+
import { formatTimecode, snapToFrame } from '@/lib/timecode';
|
|
14
|
+
|
|
15
|
+
import type { MarkerLaneView } from './Timeline';
|
|
16
|
+
|
|
17
|
+
export interface MarkerActions {
|
|
18
|
+
onSeek: (ms: number) => void;
|
|
19
|
+
onChange: (id: string, change: { name?: string; color?: MarkerColor; t?: number }) => void;
|
|
20
|
+
onRemove: (id: string) => void;
|
|
21
|
+
/** The marker whose card is asked for (M again on a marker), and that it was opened. */
|
|
22
|
+
naming: string | null;
|
|
23
|
+
onNamed: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The card open for one marker, under (x, y) on the screen; `name`: its name field takes the keys at once. */
|
|
27
|
+
type Open = { id: string; x: number; y: number; name: boolean } | null;
|
|
28
|
+
|
|
29
|
+
/** The card shared by the pins and the notches, and what opens it. */
|
|
30
|
+
function useMarkerCard(list: readonly PlacedMarker[], actions: MarkerActions) {
|
|
31
|
+
const [open, setOpen] = React.useState<Open>(null);
|
|
32
|
+
const placed = open ? list.find((p) => p.marker.id === open.id) : undefined;
|
|
33
|
+
/* M on a marker already there: its card, its name field ready (the playhead's knob may cover the pin itself) */
|
|
34
|
+
const { naming, onNamed } = actions;
|
|
35
|
+
React.useEffect(() => {
|
|
36
|
+
if (!naming || !list.some((p) => p.marker.id === naming)) return;
|
|
37
|
+
const el = document.querySelector(`[data-marker="${CSS.escape(naming)}"], [data-clip-marker="${CSS.escape(naming)}"]`);
|
|
38
|
+
const r = el?.getBoundingClientRect();
|
|
39
|
+
if (r) setOpen({ id: naming, x: r.left + r.width / 2, y: r.bottom, name: true });
|
|
40
|
+
onNamed();
|
|
41
|
+
}, [naming, list, onNamed]);
|
|
42
|
+
/* a marker that went (undone, its clip deleted): its card goes with it */
|
|
43
|
+
React.useEffect(() => { if (open && !placed) setOpen(null); }, [open, placed]);
|
|
44
|
+
const card = placed && open ? createPortal(
|
|
45
|
+
<MarkerCard key={placed.marker.id} placed={placed} x={open.x} y={open.y} focusName={open.name} actions={actions} onClose={() => setOpen(null)} />,
|
|
46
|
+
document.body,
|
|
47
|
+
) : null;
|
|
48
|
+
/** Open a marker's card under its pin. */
|
|
49
|
+
const openAt = (el: Element, p: PlacedMarker, name = false) => {
|
|
50
|
+
const r = el.getBoundingClientRect();
|
|
51
|
+
setOpen({ id: p.marker.id, x: r.left + r.width / 2, y: r.bottom, name });
|
|
52
|
+
};
|
|
53
|
+
return { open, setOpen, openAt, card };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A marker's card: its name (typed in, kept on Enter or when the card is left), its color (kept at a click), where it
|
|
58
|
+
* is, and delete (also Delete / ⌫ while the card has the keys). Esc or a press elsewhere closes it, the name kept.
|
|
59
|
+
*/
|
|
60
|
+
function MarkerCard({ placed, x, y, focusName, actions, onClose }: {
|
|
61
|
+
placed: PlacedMarker; x: number; y: number; focusName: boolean; actions: MarkerActions; onClose: () => void;
|
|
62
|
+
}) {
|
|
63
|
+
const t = useT();
|
|
64
|
+
const { marker } = placed;
|
|
65
|
+
const box = React.useRef<HTMLDivElement | null>(null);
|
|
66
|
+
const field = React.useRef<HTMLInputElement | null>(null);
|
|
67
|
+
const keepName = () => {
|
|
68
|
+
const name = field.current?.value ?? '';
|
|
69
|
+
if (name.trim() !== (marker.name ?? '')) actions.onChange(marker.id, { name });
|
|
70
|
+
};
|
|
71
|
+
const close = () => { keepName(); onClose(); };
|
|
72
|
+
const closeRef = React.useRef(close);
|
|
73
|
+
closeRef.current = close;
|
|
74
|
+
React.useEffect(() => {
|
|
75
|
+
if (focusName) { field.current?.focus(); field.current?.select(); } else box.current?.focus();
|
|
76
|
+
/* a press elsewhere: the name as typed is kept, the card goes */
|
|
77
|
+
const away = (e: PointerEvent) => { if (!box.current?.contains(e.target as Node)) closeRef.current(); };
|
|
78
|
+
window.addEventListener('pointerdown', away, true);
|
|
79
|
+
return () => window.removeEventListener('pointerdown', away, true);
|
|
80
|
+
}, [focusName]);
|
|
81
|
+
const W = 224;
|
|
82
|
+
return (
|
|
83
|
+
<div
|
|
84
|
+
ref={box}
|
|
85
|
+
tabIndex={-1}
|
|
86
|
+
role="dialog"
|
|
87
|
+
aria-label={marker.name || t('editorTools.markerUntitled')}
|
|
88
|
+
data-popover-panel=""
|
|
89
|
+
data-marker-card={marker.id}
|
|
90
|
+
/* portaled, but React still bubbles its events up to the pin's ruler or clip (which take the press for
|
|
91
|
+
themselves, and the field would never get the keys): the card's presses stay in the card */
|
|
92
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
93
|
+
onClick={(e) => e.stopPropagation()}
|
|
94
|
+
onDoubleClick={(e) => e.stopPropagation()}
|
|
95
|
+
onContextMenu={(e) => e.stopPropagation()}
|
|
96
|
+
onKeyDown={(e) => {
|
|
97
|
+
/* the card's keys are its own: not the timeline's (Delete there deletes clips) */
|
|
98
|
+
e.stopPropagation();
|
|
99
|
+
if (e.key === 'Escape') { e.preventDefault(); close(); return; }
|
|
100
|
+
if ((e.key === 'Delete' || e.key === 'Backspace') && e.target === box.current) { e.preventDefault(); actions.onRemove(marker.id); onClose(); }
|
|
101
|
+
}}
|
|
102
|
+
className="fixed z-[10001] flex flex-col gap-2 rounded-[8px] border p-2 shadow-[var(--shadow-lg)] outline-none"
|
|
103
|
+
style={{ left: Math.max(8, Math.min(x - W / 2, window.innerWidth - W - 8)), top: y + 6, width: W, background: 'var(--tl-head)', borderColor: 'var(--tl-line)' }}
|
|
104
|
+
>
|
|
105
|
+
<div className="flex items-center gap-1.5">
|
|
106
|
+
<span className="shrink-0"><MarkerGlyph size={13} fill={MARKER_PAINT[marker.color]} /></span>
|
|
107
|
+
<input
|
|
108
|
+
ref={field}
|
|
109
|
+
defaultValue={marker.name ?? ''}
|
|
110
|
+
maxLength={MARKER_NAME_MAX}
|
|
111
|
+
aria-label={t('editorTools.markerName')}
|
|
112
|
+
placeholder={t('editorTools.markerUntitled')}
|
|
113
|
+
onKeyDown={(e) => {
|
|
114
|
+
e.stopPropagation();
|
|
115
|
+
if (e.key === 'Enter') { e.preventDefault(); close(); }
|
|
116
|
+
if (e.key === 'Escape') { e.preventDefault(); close(); }
|
|
117
|
+
}}
|
|
118
|
+
onBlur={keepName}
|
|
119
|
+
className="h-[24px] min-w-0 flex-1 rounded-[4px] bg-[var(--bg)] px-1.5 text-[12px] text-[var(--text)] outline-none ring-1 ring-[var(--tl-line)] focus:ring-[var(--tl-accent)]"
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
<div className="flex items-center gap-1" role="radiogroup" aria-label={t('editorTools.markerColor')}>
|
|
123
|
+
{MARKER_COLORS.map((c) => (
|
|
124
|
+
<button
|
|
125
|
+
key={c}
|
|
126
|
+
type="button"
|
|
127
|
+
role="radio"
|
|
128
|
+
aria-checked={marker.color === c}
|
|
129
|
+
aria-label={t(`editorTools.colors.${c}`)}
|
|
130
|
+
title={t(`editorTools.colors.${c}`)}
|
|
131
|
+
onClick={() => actions.onChange(marker.id, { color: c })}
|
|
132
|
+
className="flex h-[22px] w-[22px] items-center justify-center rounded-full outline-none transition hover:bg-black/5 focus-visible:ring-1 focus-visible:ring-[var(--tl-accent)] dark:hover:bg-white/10"
|
|
133
|
+
>
|
|
134
|
+
<span className="block h-3 w-3 rounded-full" style={{ background: MARKER_PAINT[c], boxShadow: marker.color === c ? `0 0 0 2px var(--tl-head), 0 0 0 3.5px ${MARKER_PAINT[c]}` : undefined }} />
|
|
135
|
+
</button>
|
|
136
|
+
))}
|
|
137
|
+
</div>
|
|
138
|
+
<div className="flex items-center justify-between border-t pt-1.5" style={{ borderColor: 'var(--tl-line)' }}>
|
|
139
|
+
<span className="font-mono text-[10.5px] tabular-nums" style={{ color: 'var(--tl-faint)' }}>{formatTimecode(placed.ms)}</span>
|
|
140
|
+
<button
|
|
141
|
+
type="button"
|
|
142
|
+
onClick={() => { actions.onRemove(marker.id); onClose(); }}
|
|
143
|
+
className="flex items-center gap-1 rounded-[4px] px-1.5 py-0.5 text-[11px] outline-none transition hover:bg-black/5 focus-visible:ring-1 focus-visible:ring-[var(--tl-accent)] dark:hover:bg-white/10"
|
|
144
|
+
style={{ color: 'var(--text-dim)' }}
|
|
145
|
+
>
|
|
146
|
+
<Trash2 size={12} aria-hidden />{t('editorTools.markerDelete')}
|
|
147
|
+
</button>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** A marker's name and time, on a tag in its color beside its pin (hovered, or while it is dragged). */
|
|
154
|
+
function MarkerTag({ marker, ms, flip }: { marker: Marker; ms: number; flip: boolean }) {
|
|
155
|
+
const t = useT();
|
|
156
|
+
return (
|
|
157
|
+
<span
|
|
158
|
+
className="pointer-events-none absolute top-1/2 z-[3] flex h-[14px] items-center gap-1 whitespace-nowrap rounded-[3px] px-[5px] text-[10px] font-medium leading-none tabular-nums shadow-[0_1px_3px_rgba(0,0,0,.35)]"
|
|
159
|
+
style={{ left: 0, background: MARKER_PAINT[marker.color], color: '#111', transform: `translate(${flip ? 'calc(-100% - 9px)' : '9px'}, -50%)` }}
|
|
160
|
+
>
|
|
161
|
+
<span className="max-w-[160px] truncate">{marker.name || t('editorTools.markerUntitled')}</span>
|
|
162
|
+
<span style={{ opacity: 0.65 }}>{formatTimecode(ms, { hours: false })}</span>
|
|
163
|
+
</span>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The marker's shape: a tab pointing down at its moment. Outlined for the toolbar's button, filled for a pin. */
|
|
168
|
+
export function MarkerGlyph({ size = 16, fill, ring }: { size?: number; fill?: string; ring?: boolean }) {
|
|
169
|
+
if (!fill) {
|
|
170
|
+
return (
|
|
171
|
+
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" aria-hidden>
|
|
172
|
+
<path d="M4 2.5h8a1 1 0 0 1 1 1v6.3a1 1 0 0 1-.38.78l-4 3.2a1 1 0 0 1-1.24 0l-4-3.2A1 1 0 0 1 3 9.8V3.5a1 1 0 0 1 1-1Z" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
|
|
173
|
+
</svg>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return (
|
|
177
|
+
<svg width={size * 0.8} height={size} viewBox="0 0 10 12.5" aria-hidden className="block overflow-visible">
|
|
178
|
+
<path
|
|
179
|
+
d="M1.2.5h7.6a.7.7 0 0 1 .7.7v6.9a.7.7 0 0 1-.26.54L5.44 11.8a.7.7 0 0 1-.88 0L.76 8.64A.7.7 0 0 1 .5 8.1V1.2a.7.7 0 0 1 .7-.7Z"
|
|
180
|
+
fill={fill}
|
|
181
|
+
stroke={ring ? '#fff' : 'rgba(0,0,0,.4)'}
|
|
182
|
+
strokeWidth={ring ? 1.4 : 0.8}
|
|
183
|
+
/>
|
|
184
|
+
</svg>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The film's markers on their row under the ruler: a pin each. Hovered: its name and time, and its line through the
|
|
190
|
+
* timeline. Dragged: it moves (snapping as clips do), its line and time shown all the way. A click goes there and
|
|
191
|
+
* opens its card; a right-click opens the card with its name ready.
|
|
192
|
+
*/
|
|
193
|
+
export function LaneMarkers({ placed, lane, actions }: { placed: readonly PlacedMarker[]; lane: MarkerLaneView; actions: MarkerActions }) {
|
|
194
|
+
const { open, openAt, card } = useMarkerCard(placed, actions);
|
|
195
|
+
const [moving, setMoving] = React.useState<{ id: string; ms: number } | null>(null);
|
|
196
|
+
const [hover, setHover] = React.useState<string | null>(null);
|
|
197
|
+
const { pxPerMs, widthPx, snap, guide } = lane;
|
|
198
|
+
/* the click after a drag is not one */
|
|
199
|
+
const wasDragged = React.useRef(false);
|
|
200
|
+
const press = (e: React.PointerEvent, p: PlacedMarker) => {
|
|
201
|
+
e.preventDefault();
|
|
202
|
+
e.stopPropagation();
|
|
203
|
+
if (e.button !== 0) return;
|
|
204
|
+
const el = e.currentTarget as HTMLElement;
|
|
205
|
+
el.setPointerCapture(e.pointerId);
|
|
206
|
+
const x0 = e.clientX;
|
|
207
|
+
const color = MARKER_PAINT[p.marker.color];
|
|
208
|
+
let to = p.ms;
|
|
209
|
+
let dragged = false;
|
|
210
|
+
const move = (ev: PointerEvent) => {
|
|
211
|
+
if (!dragged && Math.abs(ev.clientX - x0) < 4) return;
|
|
212
|
+
dragged = true;
|
|
213
|
+
to = snapToFrame(snap(Math.max(0, p.ms + (ev.clientX - x0) / pxPerMs)));
|
|
214
|
+
setMoving({ id: p.marker.id, ms: to });
|
|
215
|
+
guide({ ms: to, color, held: true });
|
|
216
|
+
};
|
|
217
|
+
const up = () => {
|
|
218
|
+
el.removeEventListener('pointermove', move);
|
|
219
|
+
el.removeEventListener('pointerup', up);
|
|
220
|
+
el.removeEventListener('pointercancel', up);
|
|
221
|
+
setMoving(null);
|
|
222
|
+
guide(null);
|
|
223
|
+
if (dragged && to !== p.ms) actions.onChange(p.marker.id, { t: to / 1000 });
|
|
224
|
+
wasDragged.current = dragged;
|
|
225
|
+
};
|
|
226
|
+
el.addEventListener('pointermove', move);
|
|
227
|
+
el.addEventListener('pointerup', up);
|
|
228
|
+
el.addEventListener('pointercancel', up);
|
|
229
|
+
};
|
|
230
|
+
const film = placed.filter((p) => !p.clip);
|
|
231
|
+
/* where each pin is drawn, left to right: a name runs up to the next pin */
|
|
232
|
+
const xs = film.map((p) => (moving?.id === p.marker.id ? moving.ms : p.ms) * pxPerMs).sort((a, b) => a - b);
|
|
233
|
+
return (
|
|
234
|
+
<>
|
|
235
|
+
{film.map((p) => {
|
|
236
|
+
const ms = moving?.id === p.marker.id ? moving.ms : p.ms;
|
|
237
|
+
const x = ms * pxPerMs;
|
|
238
|
+
const on = open?.id === p.marker.id;
|
|
239
|
+
const tagged = moving?.id === p.marker.id || (hover === p.marker.id && !on);
|
|
240
|
+
const room = Math.min(160, (xs.find((o) => o > x) ?? widthPx) - x - 14);
|
|
241
|
+
return (
|
|
242
|
+
<div
|
|
243
|
+
key={p.marker.id}
|
|
244
|
+
className="absolute inset-y-0 z-[2] flex w-[14px] -translate-x-1/2 cursor-ew-resize items-center justify-center"
|
|
245
|
+
/* one at the very start is drawn whole, a few px in (its line stays on the moment) */
|
|
246
|
+
style={{ left: Math.max(x, 7) }}
|
|
247
|
+
data-marker={p.marker.id}
|
|
248
|
+
aria-label={p.marker.name || undefined}
|
|
249
|
+
onPointerDown={(e) => press(e, p)}
|
|
250
|
+
onPointerEnter={() => {
|
|
251
|
+
setHover(p.marker.id);
|
|
252
|
+
if (!moving) guide({ ms: p.ms, color: MARKER_PAINT[p.marker.color], held: false });
|
|
253
|
+
}}
|
|
254
|
+
onPointerLeave={() => {
|
|
255
|
+
setHover((h) => (h === p.marker.id ? null : h));
|
|
256
|
+
if (!moving) guide(null);
|
|
257
|
+
}}
|
|
258
|
+
onClick={(e) => {
|
|
259
|
+
/* a click: there, and its card */
|
|
260
|
+
e.stopPropagation();
|
|
261
|
+
if (wasDragged.current) { wasDragged.current = false; return; }
|
|
262
|
+
actions.onSeek(p.ms);
|
|
263
|
+
openAt(e.currentTarget, p);
|
|
264
|
+
}}
|
|
265
|
+
onDoubleClick={(e) => e.stopPropagation()}
|
|
266
|
+
onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); openAt(e.currentTarget, p, true); }}
|
|
267
|
+
>
|
|
268
|
+
<MarkerGlyph size={12} fill={MARKER_PAINT[p.marker.color]} ring={on} />
|
|
269
|
+
{tagged ? (
|
|
270
|
+
<span className="absolute inset-y-0 left-1/2">
|
|
271
|
+
<MarkerTag marker={p.marker} ms={ms} flip={x > widthPx - 180} />
|
|
272
|
+
</span>
|
|
273
|
+
) : p.marker.name && room >= 16 ? (
|
|
274
|
+
/* its name beside it, as far as the next pin */
|
|
275
|
+
<span
|
|
276
|
+
className="pointer-events-none absolute left-[12px] top-1/2 -translate-y-1/2 truncate whitespace-nowrap text-[10px] leading-none"
|
|
277
|
+
style={{ maxWidth: room, color: 'var(--tl-text)' }}
|
|
278
|
+
data-marker-name=""
|
|
279
|
+
>
|
|
280
|
+
{p.marker.name}
|
|
281
|
+
</span>
|
|
282
|
+
) : null}
|
|
283
|
+
</div>
|
|
284
|
+
);
|
|
285
|
+
})}
|
|
286
|
+
{card}
|
|
287
|
+
</>
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** A clip's markers on its row: notches along the bottom of the clip, where each moment of its file is. */
|
|
292
|
+
export function ClipMarkers({ placed, pxPerMs, height, actions }: {
|
|
293
|
+
/** this track's clip markers */
|
|
294
|
+
placed: readonly PlacedMarker[];
|
|
295
|
+
pxPerMs: number;
|
|
296
|
+
height: number;
|
|
297
|
+
actions: MarkerActions;
|
|
298
|
+
}) {
|
|
299
|
+
const t = useT();
|
|
300
|
+
const { openAt, card } = useMarkerCard(placed, actions);
|
|
301
|
+
if (!placed.length) return card;
|
|
302
|
+
return (
|
|
303
|
+
<>
|
|
304
|
+
{placed.map((p) => (
|
|
305
|
+
<div
|
|
306
|
+
key={p.marker.id}
|
|
307
|
+
className="absolute z-[15] cursor-pointer"
|
|
308
|
+
data-clip-marker={p.marker.id}
|
|
309
|
+
style={{ left: p.ms * pxPerMs - 4, top: height - 10, width: 8, height: 9 }}
|
|
310
|
+
title={`${p.marker.name || t('editorTools.markerUntitled')} · ${formatTimecode(p.ms)}`}
|
|
311
|
+
onPointerDown={(e) => {
|
|
312
|
+
if (e.button !== 0) return;
|
|
313
|
+
e.preventDefault();
|
|
314
|
+
e.stopPropagation();
|
|
315
|
+
actions.onSeek(p.ms);
|
|
316
|
+
openAt(e.currentTarget, p);
|
|
317
|
+
}}
|
|
318
|
+
onDoubleClick={(e) => e.stopPropagation()}
|
|
319
|
+
onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); openAt(e.currentTarget, p, true); }}
|
|
320
|
+
>
|
|
321
|
+
<svg width="8" height="9" viewBox="0 0 8 9" aria-hidden className="block">
|
|
322
|
+
<path d="M0 9 L4 1 L8 9 Z" fill={MARKER_PAINT[p.marker.color]} stroke="rgba(0,0,0,0.45)" strokeWidth="0.8" />
|
|
323
|
+
</svg>
|
|
324
|
+
</div>
|
|
325
|
+
))}
|
|
326
|
+
{card}
|
|
327
|
+
</>
|
|
328
|
+
);
|
|
329
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one-line pill at the bottom center that goes away on its own (4s): "the button did nothing, and here is why"
|
|
3
|
+
* (a notice), or something failed (an error: red text, role alert).
|
|
4
|
+
*
|
|
5
|
+
* const toast = useToast();
|
|
6
|
+
* toast.show('The video isn’t ready yet'); toast.showError('Rename failed. Please retry.');
|
|
7
|
+
* <Toast toast={toast.current} />
|
|
8
|
+
*/
|
|
9
|
+
import React from 'react';
|
|
10
|
+
|
|
11
|
+
export type ToastMessage = { text: string; error?: boolean };
|
|
12
|
+
|
|
13
|
+
const TOAST_MS = 4000;
|
|
14
|
+
|
|
15
|
+
export interface ToastState {
|
|
16
|
+
/** What is showing now (null: nothing). */
|
|
17
|
+
current: ToastMessage | null;
|
|
18
|
+
/** Show a notice; replaces whatever is showing and restarts the 4s. */
|
|
19
|
+
show: (text: string) => void;
|
|
20
|
+
/** Show an error. */
|
|
21
|
+
showError: (text: string) => void;
|
|
22
|
+
clear: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function useToast(): ToastState {
|
|
26
|
+
const [current, setCurrent] = React.useState<ToastMessage | null>(null);
|
|
27
|
+
const timer = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
28
|
+
React.useEffect(() => () => { if (timer.current) clearTimeout(timer.current); }, []);
|
|
29
|
+
const put = React.useCallback((message: ToastMessage | null) => {
|
|
30
|
+
setCurrent(message);
|
|
31
|
+
if (timer.current) clearTimeout(timer.current);
|
|
32
|
+
timer.current = message ? setTimeout(() => setCurrent(null), TOAST_MS) : null;
|
|
33
|
+
}, []);
|
|
34
|
+
const show = React.useCallback((text: string) => put({ text }), [put]);
|
|
35
|
+
const showError = React.useCallback((text: string) => put({ text, error: true }), [put]);
|
|
36
|
+
const clear = React.useCallback(() => put(null), [put]);
|
|
37
|
+
return React.useMemo(() => ({ current, show, showError, clear }), [current, show, showError, clear]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function Toast({ toast }: { toast: ToastMessage | null }) {
|
|
41
|
+
if (!toast) return null;
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
/* keyed by the text so a new message rises again instead of swapping silently */
|
|
45
|
+
key={toast.text}
|
|
46
|
+
role={toast.error ? 'alert' : 'status'}
|
|
47
|
+
className={`pointer-events-none fixed bottom-6 left-1/2 z-[10200] -translate-x-1/2 rounded-full border bg-[var(--surface)] px-4 py-2 text-[12.5px] font-medium shadow-[var(--shadow-lg)] ${
|
|
48
|
+
toast.error ? 'border-[var(--err)]/40 text-[var(--err)]' : 'border-[var(--border)] text-[var(--text)]'
|
|
49
|
+
}`}
|
|
50
|
+
style={{ animation: 'openfilm-rise 0.16s ease-out both' }}
|
|
51
|
+
>
|
|
52
|
+
{toast.text}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hover tooltip: the kind that appears after half a second.
|
|
3
|
+
*
|
|
4
|
+
* Does not use the browser's native `title`: its delay is set by the OS (a second or two in
|
|
5
|
+
* practice), while a row of icon buttons on a toolbar relies on it to say "what is this".
|
|
6
|
+
* By the time it appears, the user has already clicked to find out. Mature editors use about half a second.
|
|
7
|
+
*
|
|
8
|
+
* The delay applies only the **first** time: moving from one button to another within half a
|
|
9
|
+
* second shows the tooltip immediately. When the hand is already sweeping along the toolbar,
|
|
10
|
+
* waiting half a second for each one is more annoying than no tooltip at all.
|
|
11
|
+
*
|
|
12
|
+
* Placement: above the button by default. Most buttons in the workspace (player bar,
|
|
13
|
+
* timeline toolbar) sit along the bottom edge or in the middle of the screen, so a tooltip
|
|
14
|
+
* rising upward blocks nothing. The top bar is the exception: the window edge is right above
|
|
15
|
+
* it, so its tooltips must go below (`side="bottom"`).
|
|
16
|
+
*
|
|
17
|
+
* Shortcuts are written as `Name (⌘Z)`, after the name and one step dimmer. The modifier
|
|
18
|
+
* follows the platform (see lib/shortcut-hint): ⌘ on Mac, Ctrl elsewhere; hard-coding one
|
|
19
|
+
* would leave people on the other platform pressing keys that do nothing.
|
|
20
|
+
*/
|
|
21
|
+
import * as React from 'react';
|
|
22
|
+
import { createPortal } from 'react-dom';
|
|
23
|
+
|
|
24
|
+
/** How long to wait on the first hover. */
|
|
25
|
+
const DELAY_MS = 500;
|
|
26
|
+
/** How long after leaving the pointer still counts as "in this area"; moving to another button within this time skips the wait. */
|
|
27
|
+
const WARM_MS = 600;
|
|
28
|
+
|
|
29
|
+
let warmUntil = 0;
|
|
30
|
+
|
|
31
|
+
/** Minimum distance between the bubble and the window edge. */
|
|
32
|
+
const EDGE_PAD = 8;
|
|
33
|
+
/** The arrow may go at most this far toward the bubble's ends; further and it would climb onto the rounded corner, and the missing corner would look like a rendering bug. */
|
|
34
|
+
const ARROW_INSET = 12;
|
|
35
|
+
|
|
36
|
+
export function Tooltip({
|
|
37
|
+
label, shortcut, side = 'top', children,
|
|
38
|
+
}: {
|
|
39
|
+
/** Empty string/undefined = no tooltip right now (e.g. a "Restore" button sitting on the current version); the child is passed through untouched. */
|
|
40
|
+
label: string | undefined;
|
|
41
|
+
/** Shortcut (the string already arranged for the platform, from shortcutHint). Shown as `Name (⌘Z)`. */
|
|
42
|
+
shortcut?: string;
|
|
43
|
+
/** Which side the tooltip appears on. Top-bar buttons use `bottom`; everything else defaults to above. */
|
|
44
|
+
side?: 'top' | 'bottom';
|
|
45
|
+
/** The element the tooltip attaches to. Must accept a ref and mouse events. */
|
|
46
|
+
children: React.ReactElement;
|
|
47
|
+
}): React.ReactElement {
|
|
48
|
+
const [at, setAt] = React.useState<{ x: number; y: number } | null>(null);
|
|
49
|
+
const timer = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
50
|
+
const host = React.useRef<HTMLElement | null>(null);
|
|
51
|
+
const bubble = React.useRef<HTMLDivElement | null>(null);
|
|
52
|
+
/** Only after measuring do we know how far to shift back and where the arrow goes. Do not paint before then (see visibility below). */
|
|
53
|
+
const [fit, setFit] = React.useState<{ shift: number; arrow: number } | null>(null);
|
|
54
|
+
|
|
55
|
+
/* Buttons near the edge: a bubble centered on the button would stick halfway out of the
|
|
56
|
+
window, and that half is cropped along with its text, often the first few words. So after
|
|
57
|
+
measuring we push it back, and move the arrow the opposite way by the same amount: the
|
|
58
|
+
bubble shifts but the tip still points at its button, otherwise "which one is this about"
|
|
59
|
+
would be a guess.
|
|
60
|
+
useLayoutEffect rather than useEffect: this must finish before the browser paints. */
|
|
61
|
+
React.useLayoutEffect(() => {
|
|
62
|
+
if (!at) {
|
|
63
|
+
setFit(null);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const el = bubble.current;
|
|
67
|
+
if (!el) return;
|
|
68
|
+
const w = el.offsetWidth;
|
|
69
|
+
const half = w / 2;
|
|
70
|
+
const min = EDGE_PAD + half;
|
|
71
|
+
const max = window.innerWidth - EDGE_PAD - half;
|
|
72
|
+
/* When the window is narrower than the bubble, min exceeds max and clamping gives an absurd position; center it then and let both sides overflow equally. */
|
|
73
|
+
const want = max < min ? window.innerWidth / 2 : Math.min(Math.max(at.x, min), max);
|
|
74
|
+
const shift = want - at.x;
|
|
75
|
+
setFit({
|
|
76
|
+
shift: Math.round(shift),
|
|
77
|
+
arrow: Math.round(Math.min(Math.max(half - shift, ARROW_INSET), w - ARROW_INSET)),
|
|
78
|
+
});
|
|
79
|
+
}, [at, label, shortcut]);
|
|
80
|
+
|
|
81
|
+
const clear = React.useCallback(() => {
|
|
82
|
+
if (timer.current) clearTimeout(timer.current);
|
|
83
|
+
timer.current = null;
|
|
84
|
+
}, []);
|
|
85
|
+
|
|
86
|
+
const show = React.useCallback(() => {
|
|
87
|
+
const el = host.current;
|
|
88
|
+
if (!el) return;
|
|
89
|
+
const r = el.getBoundingClientRect();
|
|
90
|
+
setAt({
|
|
91
|
+
x: Math.round(r.left + r.width / 2),
|
|
92
|
+
y: Math.round(side === 'bottom' ? r.bottom : r.top),
|
|
93
|
+
});
|
|
94
|
+
}, [side]);
|
|
95
|
+
|
|
96
|
+
const onEnter = React.useCallback((e: React.MouseEvent) => {
|
|
97
|
+
host.current = e.currentTarget as HTMLElement;
|
|
98
|
+
clear();
|
|
99
|
+
if (Date.now() < warmUntil) {
|
|
100
|
+
show();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
timer.current = setTimeout(show, DELAY_MS);
|
|
104
|
+
}, [clear, show]);
|
|
105
|
+
|
|
106
|
+
const onLeave = React.useCallback(() => {
|
|
107
|
+
clear();
|
|
108
|
+
if (at) warmUntil = Date.now() + WARM_MS;
|
|
109
|
+
setAt(null);
|
|
110
|
+
}, [at, clear]);
|
|
111
|
+
|
|
112
|
+
React.useEffect(() => clear, [clear]);
|
|
113
|
+
|
|
114
|
+
if (!label) return children;
|
|
115
|
+
|
|
116
|
+
/* Chain the existing handlers before attaching ours: `cloneElement` overrides rather than
|
|
117
|
+
merges. Overriding directly would make the wrapped element's own `onMouseDown`
|
|
118
|
+
**silently disappear** (toolbar buttons rely on it to avoid taking focus). */
|
|
119
|
+
const own = children.props as Partial<React.DOMAttributes<HTMLElement>>;
|
|
120
|
+
const chain = <E extends React.SyntheticEvent>(
|
|
121
|
+
mine: (e: E) => void,
|
|
122
|
+
theirs?: (e: E) => void,
|
|
123
|
+
) => (e: E) => { theirs?.(e); mine(e); };
|
|
124
|
+
|
|
125
|
+
const child = React.cloneElement(children, {
|
|
126
|
+
onMouseEnter: chain(onEnter, own.onMouseEnter),
|
|
127
|
+
onMouseLeave: chain(onLeave, own.onMouseLeave),
|
|
128
|
+
/* Hide on press: a tooltip left floating after a click would cover the part of the UI that just changed. */
|
|
129
|
+
onMouseDown: chain(onLeave, own.onMouseDown),
|
|
130
|
+
} as Partial<React.HTMLAttributes<HTMLElement>>);
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<>
|
|
134
|
+
{child}
|
|
135
|
+
{at && typeof document !== 'undefined'
|
|
136
|
+
? createPortal(
|
|
137
|
+
<div
|
|
138
|
+
ref={bubble}
|
|
139
|
+
role="tooltip"
|
|
140
|
+
className={`pointer-events-none fixed z-[10200] -translate-x-1/2 whitespace-nowrap rounded-[8px] px-2.5 py-[5px] text-[12px] font-medium shadow-md ${
|
|
141
|
+
side === 'bottom' ? '' : '-translate-y-full'
|
|
142
|
+
}`}
|
|
143
|
+
style={{
|
|
144
|
+
left: at.x + (fit?.shift ?? 0),
|
|
145
|
+
/* The arrow tip sits 2px from the button: the bubble should hug the button, not hang in mid-air. */
|
|
146
|
+
top: side === 'bottom' ? at.y + 2 + ARROW_H : at.y - 2 - ARROW_H,
|
|
147
|
+
/* The first frame has not measured the shift yet; do not paint, so it is not seen jumping from the wrong position. */
|
|
148
|
+
visibility: fit ? 'visible' : 'hidden',
|
|
149
|
+
background: 'var(--tooltip-bg)',
|
|
150
|
+
color: 'var(--tooltip-text)',
|
|
151
|
+
border: '1px solid var(--tooltip-border)',
|
|
152
|
+
}}
|
|
153
|
+
>
|
|
154
|
+
{label}
|
|
155
|
+
{shortcut ? (
|
|
156
|
+
/* ASCII parentheses. One step dimmer but the same color; switching to a separate gray looks muddy on a dark bubble. */
|
|
157
|
+
<span style={{ opacity: 0.55, marginLeft: 5 }}>({shortcut})</span>
|
|
158
|
+
) : null}
|
|
159
|
+
<span
|
|
160
|
+
aria-hidden
|
|
161
|
+
className="absolute block"
|
|
162
|
+
style={{
|
|
163
|
+
left: fit?.arrow ?? '50%',
|
|
164
|
+
width: ARROW_BOX,
|
|
165
|
+
height: ARROW_BOX,
|
|
166
|
+
background: 'var(--tooltip-bg)',
|
|
167
|
+
/* A square rotated 45 degrees, with only its two outward-facing edges stroked, overlaps the bubble's border to form one continuous arrow. */
|
|
168
|
+
...(side === 'bottom'
|
|
169
|
+
? {
|
|
170
|
+
top: -ARROW_BOX / 2 - 0.5,
|
|
171
|
+
transform: 'translateX(-50%) rotate(45deg)',
|
|
172
|
+
borderTop: '1px solid var(--tooltip-border)',
|
|
173
|
+
borderLeft: '1px solid var(--tooltip-border)',
|
|
174
|
+
}
|
|
175
|
+
: {
|
|
176
|
+
bottom: -ARROW_BOX / 2 - 0.5,
|
|
177
|
+
transform: 'translateX(-50%) rotate(45deg)',
|
|
178
|
+
borderBottom: '1px solid var(--tooltip-border)',
|
|
179
|
+
borderRight: '1px solid var(--tooltip-border)',
|
|
180
|
+
}),
|
|
181
|
+
}}
|
|
182
|
+
/>
|
|
183
|
+
</div>,
|
|
184
|
+
document.body,
|
|
185
|
+
)
|
|
186
|
+
: null}
|
|
187
|
+
</>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Side length of the arrow square; after rotation, the height it sticks out of the bubble is half the diagonal. */
|
|
192
|
+
const ARROW_BOX = 7;
|
|
193
|
+
const ARROW_H = Math.round((ARROW_BOX * Math.SQRT2) / 2);
|