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,567 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Projects popover (the top bar's Projects button; with the app's tabs, their `+`, and the window with no tab open):
|
|
3
|
+
* a large centered dialog to switch project, start one, open a folder, get a film from GitHub, rename, delete, or
|
|
4
|
+
* file projects in folders of the list; or, switched to "Examples", to start from an example (ExamplesGallery). Its
|
|
5
|
+
* projects are ProjectsView; this file holds the data and actions.
|
|
6
|
+
*
|
|
7
|
+
* Studio's projects are folders on disk: deleting one moves its folder to the system's Trash. One whose folder is gone
|
|
8
|
+
* (moved, renamed or deleted outside Studio: `missing`) cannot be opened; it can be located (the folder it is in now)
|
|
9
|
+
* or taken off the list.
|
|
10
|
+
*
|
|
11
|
+
* A folder is picked with the app's own dialog where there is an app (lib/host.ts `folders`), else with Studio's
|
|
12
|
+
* folder browser; one without a film is opened only once the person agrees to start a film in it.
|
|
13
|
+
*
|
|
14
|
+
* GET /api/projects → { projects }; POST /api/projects { name } | { path } → { project };
|
|
15
|
+
* PATCH /api/projects/:id { name } → { project } (renames the folder); DELETE /api/projects/:id (to the Trash);
|
|
16
|
+
* the list's folders: see lib/project-folders.ts
|
|
17
|
+
*/
|
|
18
|
+
import React from 'react';
|
|
19
|
+
import { createPortal } from 'react-dom';
|
|
20
|
+
import { Loader2 } from 'lucide-react';
|
|
21
|
+
import { api, ApiError, LIBRARY_EVENT } from '@/api';
|
|
22
|
+
import { useT } from '@/i18n';
|
|
23
|
+
import { folderApi, folderCounts, folderTrail, newProjectIn, type ProjectFolder } from '@/lib/project-folders';
|
|
24
|
+
import { stillThere } from '@/lib/project-delete';
|
|
25
|
+
import { studioHost } from '@/lib/host';
|
|
26
|
+
import { SourceError, sourcesApi } from '@/lib/project-sources';
|
|
27
|
+
import { LIST_WAIT_MS, within } from '@/lib/projects-page';
|
|
28
|
+
import { ConfirmDialog } from './ConfirmDialog';
|
|
29
|
+
import { ProjectsView, type LibraryProject } from './ProjectsView';
|
|
30
|
+
import { FolderBrowserDialog, GitHubDialog } from './ProjectSources';
|
|
31
|
+
import { ExamplesGallery, TitleSwitch } from './ExamplesGallery';
|
|
32
|
+
import { ProjectCoverContext, posterUrl, useProjectCover } from './ProjectCover';
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
/* each card's picture: its own cover, else the film's middle frame, drawn by Studio (cached until film.html changes);
|
|
36
|
+
none for a project whose folder is gone (its card shows its color) */
|
|
37
|
+
const posterOf = (p: LibraryProject) => (p.missing ? undefined : posterUrl(p));
|
|
38
|
+
|
|
39
|
+
function useProjectLibrary(open: boolean) {
|
|
40
|
+
const t = useT();
|
|
41
|
+
const [projects, setProjects] = React.useState<LibraryProject[]>([]);
|
|
42
|
+
const [folders, setFolders] = React.useState<ProjectFolder[]>([]);
|
|
43
|
+
const [loaded, setLoaded] = React.useState(false);
|
|
44
|
+
/** the list could not be read (Studio refused, or did not answer in time): said, with a way to try again */
|
|
45
|
+
const [loadFailed, setLoadFailed] = React.useState(false);
|
|
46
|
+
const [tries, setTries] = React.useState(0);
|
|
47
|
+
const [error, setError] = React.useState<string | null>(null);
|
|
48
|
+
const errorTimer = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
49
|
+
React.useEffect(() => () => { if (errorTimer.current) clearTimeout(errorTimer.current); }, []);
|
|
50
|
+
|
|
51
|
+
const flash = React.useCallback((message: string) => {
|
|
52
|
+
setError(message);
|
|
53
|
+
if (errorTimer.current) clearTimeout(errorTimer.current);
|
|
54
|
+
errorTimer.current = setTimeout(() => setError(null), 4000);
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
/* fetched fresh on every open: the agent or another window may have made or removed one meanwhile. Waited for a
|
|
58
|
+
while only: a list that never comes is said, not shown as a spinner for ever (one shown already stays) */
|
|
59
|
+
React.useEffect(() => {
|
|
60
|
+
if (!open) return undefined;
|
|
61
|
+
let alive = true;
|
|
62
|
+
setLoadFailed(false);
|
|
63
|
+
Promise.all([within(api.projects(), LIST_WAIT_MS), within(folderApi.list(), LIST_WAIT_MS)]).then(
|
|
64
|
+
([list, folderList]) => {
|
|
65
|
+
if (!alive) return;
|
|
66
|
+
setProjects(list.map((p) => ({ ...p, poster: posterOf(p) })));
|
|
67
|
+
setFolders(folderList);
|
|
68
|
+
setLoaded(true);
|
|
69
|
+
},
|
|
70
|
+
() => { if (alive) setLoadFailed(true); },
|
|
71
|
+
);
|
|
72
|
+
return () => { alive = false; };
|
|
73
|
+
}, [open, tries]);
|
|
74
|
+
const retryLoad = React.useCallback(() => setTries((n) => n + 1), []);
|
|
75
|
+
/* shown again (the app's projects page when the last tab closes, a window brought back): read afresh, as it may
|
|
76
|
+
have changed meanwhile */
|
|
77
|
+
React.useEffect(() => {
|
|
78
|
+
if (!open) return undefined;
|
|
79
|
+
const back = () => { if (document.visibilityState === 'visible') retryLoad(); };
|
|
80
|
+
document.addEventListener('visibilitychange', back);
|
|
81
|
+
return () => document.removeEventListener('visibilitychange', back);
|
|
82
|
+
}, [open, retryLoad]);
|
|
83
|
+
|
|
84
|
+
const reloadFolders = React.useCallback(() => {
|
|
85
|
+
void folderApi.list().then(setFolders, () => {});
|
|
86
|
+
/* a card keeps the picture it shows, unless its cover changed meanwhile (from the editor, or another window) */
|
|
87
|
+
void api.projects().then((list) => setProjects((cur) => list.map((p) => {
|
|
88
|
+
const shown = cur.find((c) => c.id === p.id);
|
|
89
|
+
return { ...p, poster: shown && shown.cover === p.cover ? shown.poster ?? posterOf(p) : posterOf(p) };
|
|
90
|
+
})), () => {});
|
|
91
|
+
}, []);
|
|
92
|
+
|
|
93
|
+
/** A project's cover set or taken off (null): its card asks for its picture again. */
|
|
94
|
+
const coverChanged = React.useCallback((id: string, cover: number | null) => {
|
|
95
|
+
setProjects((list) => list.map((p) => {
|
|
96
|
+
if (p.id !== id) return p;
|
|
97
|
+
const next = { ...p, cover: cover ?? undefined };
|
|
98
|
+
return { ...next, poster: posterOf(next) };
|
|
99
|
+
}));
|
|
100
|
+
}, []);
|
|
101
|
+
/* the library moved (Settings): its projects, where they are now */
|
|
102
|
+
React.useEffect(() => {
|
|
103
|
+
if (!open) return undefined;
|
|
104
|
+
window.addEventListener(LIBRARY_EVENT, reloadFolders);
|
|
105
|
+
return () => window.removeEventListener(LIBRARY_EVENT, reloadFolders);
|
|
106
|
+
}, [open, reloadFolders]);
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* How many of `ids` an agent is working in right now, read afresh (an agent may have started or finished since the
|
|
110
|
+
* list was read); each card's mark follows.
|
|
111
|
+
*/
|
|
112
|
+
async function workingIn(ids: string[]): Promise<number> {
|
|
113
|
+
const list = await api.projects().catch(() => null);
|
|
114
|
+
if (!list) return 0;
|
|
115
|
+
const now = new Map(list.map((p) => [p.id, Boolean(p.working)]));
|
|
116
|
+
setProjects((cur) => cur.map((p) => (now.has(p.id) && Boolean(p.working) !== now.get(p.id) ? { ...p, working: now.get(p.id) } : p)));
|
|
117
|
+
return ids.filter((id) => now.get(id)).length;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** What to say when an agent is working in `n` of the projects asked to be deleted (`of` of them). */
|
|
121
|
+
const agentRunning = (n: number, of: number) => (n === 1 && of === 1 ? t('projects.agentRunningDelete') : t('projects.agentRunningDeleteMany').replace('{n}', String(n)));
|
|
122
|
+
|
|
123
|
+
/** Delete projects (their folders go to the Trash), optimistically; the ones that fail come back. Returns the ids deleted. */
|
|
124
|
+
async function deleteProjects(ids: string[]): Promise<Set<string>> {
|
|
125
|
+
const before = projects;
|
|
126
|
+
const drop = new Set(ids);
|
|
127
|
+
setProjects((list) => list.filter((p) => !drop.has(p.id)));
|
|
128
|
+
/* null: deleted; else why not ('agent-running': an agent started in it since the list was read) */
|
|
129
|
+
/* `lost`: no answer came (the request was cut off, as when the page showing one of them leaves it) */
|
|
130
|
+
const results = await Promise.all(ids.map((id) => api.forget(id).then(() => null, (e: unknown) => ({
|
|
131
|
+
id, working: e instanceof ApiError && e.body.code === 'agent-running', lost: !(e instanceof ApiError),
|
|
132
|
+
}))));
|
|
133
|
+
/* the ones still on the list (lib/project-delete): one gone already, or whose lost answer was a delete, was deleted */
|
|
134
|
+
const failed = await stillThere(results.filter((r): r is { id: string; working: boolean; lost: boolean } => r !== null),
|
|
135
|
+
() => api.projects().then((list) => list.map((p) => p.id)));
|
|
136
|
+
if (failed.length) {
|
|
137
|
+
const back = new Set(failed.map((f) => f.id));
|
|
138
|
+
setProjects((list) => [...list, ...before.filter((p) => back.has(p.id)).map((p) => (failed.find((f) => f.id === p.id)?.working ? { ...p, working: true } : p))]);
|
|
139
|
+
const working = failed.filter((f) => f.working).length;
|
|
140
|
+
flash(working ? agentRunning(working, ids.length) : t('project.deleteFailed'));
|
|
141
|
+
}
|
|
142
|
+
const gone = new Set(failed.map((f) => f.id));
|
|
143
|
+
return new Set(ids.filter((id) => !gone.has(id)));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function createFolder(name: string, parentId: string | null = null): Promise<ProjectFolder | null> {
|
|
147
|
+
try {
|
|
148
|
+
const folder = await folderApi.create(name, parentId);
|
|
149
|
+
setFolders((list) => [...list, folder]);
|
|
150
|
+
return folder;
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** A folder into another (null: the top level), with what it holds. */
|
|
157
|
+
function moveFolder(id: string, parentId: string | null) {
|
|
158
|
+
setFolders((list) => list.map((f) => (f.id === id ? { ...f, parentId: parentId ?? undefined } : f)));
|
|
159
|
+
folderApi.moveFolder(id, parentId).then(
|
|
160
|
+
(folder) => setFolders((list) => list.map((f) => (f.id === id ? folder : f))),
|
|
161
|
+
() => reloadFolders(),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function renameFolder(id: string, name: string) {
|
|
166
|
+
setFolders((list) => list.map((f) => (f.id === id ? { ...f, name } : f)));
|
|
167
|
+
folderApi.rename(id, name).then(
|
|
168
|
+
(folder) => setFolders((list) => list.map((f) => (f.id === id ? folder : f))),
|
|
169
|
+
() => reloadFolders(),
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function moveProject(projectId: string, folderId: string | null) {
|
|
174
|
+
setProjects((list) => list.map((p) => (p.id === projectId ? { ...p, folderId: folderId ?? undefined } : p)));
|
|
175
|
+
folderApi.move(projectId, folderId).catch(() => reloadFolders());
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Delete a folder and the projects in it: the folder leaves the list, then each project is
|
|
180
|
+
* deleted as one is (its folder to the Trash). A project that fails stays, at the top level. Returns the ids deleted.
|
|
181
|
+
*/
|
|
182
|
+
async function deleteFolder(folder: ProjectFolder): Promise<Set<string>> {
|
|
183
|
+
/* the folders inside it go with it */
|
|
184
|
+
const gone = new Set([folder.id, ...folders.filter((f) => folderTrail(folders, f.id).some((up) => up.id === folder.id)).map((f) => f.id)]);
|
|
185
|
+
setFolders((list) => list.filter((f) => !gone.has(f.id)));
|
|
186
|
+
let inside: string[];
|
|
187
|
+
try {
|
|
188
|
+
inside = await folderApi.remove(folder.id);
|
|
189
|
+
} catch {
|
|
190
|
+
flash(t('project.deleteFailed'));
|
|
191
|
+
reloadFolders();
|
|
192
|
+
return new Set();
|
|
193
|
+
}
|
|
194
|
+
setProjects((list) => list.map((p) => (p.folderId && gone.has(p.folderId) ? { ...p, folderId: undefined } : p)));
|
|
195
|
+
return deleteProjects(inside);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function renameProject(id: string, name: string) {
|
|
199
|
+
const before = projects;
|
|
200
|
+
setProjects((list) => list.map((p) => (p.id === id ? { ...p, name } : p)));
|
|
201
|
+
try {
|
|
202
|
+
const renamed = await api.rename(id, name);
|
|
203
|
+
/* the server cleans the name for the file system: show what the folder is really called */
|
|
204
|
+
setProjects((list) => list.map((p) => (p.id === id ? { ...p, ...renamed } : p)));
|
|
205
|
+
/* its tab says the new name, also while that tab has no page to say it */
|
|
206
|
+
const tab = (await studioHost?.tabs?.list().catch(() => null))?.tabs.find((x) => x.projectId === id);
|
|
207
|
+
if (tab) void studioHost?.tabs?.retitle?.(tab.id, renamed.name);
|
|
208
|
+
} catch (e) {
|
|
209
|
+
setProjects(before);
|
|
210
|
+
/* a name that is taken: retrying can't help, so the message says what to do instead */
|
|
211
|
+
flash(e instanceof ApiError && e.body.code === 'agent-running' ? t('projects.agentRunningRename')
|
|
212
|
+
: e instanceof ApiError && e.body.code === 'export-running' ? t('projects.exportRunningRename')
|
|
213
|
+
: e instanceof ApiError && e.status === 409 ? t('projects.renameTaken').replace('{name}', name.trim()) : t('project.renameFailed'));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { projects, folders, loaded, loadFailed, retryLoad, error, flash, reload: reloadFolders, agentRunning, workingIn, deleteProjects, renameProject, createFolder, renameFolder, moveFolder, moveProject, deleteFolder, coverChanged };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** The launcher's two views: the person's projects, and the examples to start from (ExamplesGallery). */
|
|
221
|
+
export type LauncherView = 'projects' | 'examples';
|
|
222
|
+
|
|
223
|
+
/** The switch between them, in the title row (the Community's is the same). */
|
|
224
|
+
/** How long the projects window waits for the tab it opened to cover it before it closes anyway. */
|
|
225
|
+
const COVER_WAIT_MS = 4000;
|
|
226
|
+
|
|
227
|
+
export function LauncherSwitch({ view, onView }: { view: LauncherView; onView: (view: LauncherView) => void }) {
|
|
228
|
+
const t = useT();
|
|
229
|
+
return <TitleSwitch items={[{ id: 'projects' as const, label: t('launcher.projects') }, { id: 'examples' as const, label: t('launcher.examples') }]} value={view} onChange={onView} />;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
export function ProjectsPopover({
|
|
234
|
+
open,
|
|
235
|
+
currentProjectId,
|
|
236
|
+
onOpen,
|
|
237
|
+
onCurrentRemoved,
|
|
238
|
+
onClose,
|
|
239
|
+
}: {
|
|
240
|
+
open: boolean;
|
|
241
|
+
/** The project open in the editor ("" when none: Studio has no project to show, and this window is all there is). */
|
|
242
|
+
currentProjectId: string;
|
|
243
|
+
/**
|
|
244
|
+
* Go to this project. The popover shows "Opening project" until `currentProjectId` changes, then closes; picking
|
|
245
|
+
* the project already open just closes it.
|
|
246
|
+
*/
|
|
247
|
+
onOpen: (projectId: string) => void;
|
|
248
|
+
/** The project open in the editor was deleted: leave it (its API answers 404 from now on). */
|
|
249
|
+
onCurrentRemoved: () => void;
|
|
250
|
+
/** Missing: the window cannot be closed (there is no project behind it). */
|
|
251
|
+
onClose?: () => void;
|
|
252
|
+
}) {
|
|
253
|
+
const t = useT();
|
|
254
|
+
const lib = useProjectLibrary(open);
|
|
255
|
+
const { flash } = lib;
|
|
256
|
+
const cover = useProjectCover(lib.coverChanged, React.useCallback(() => flash(t('projects.coverFailed')), [flash, t]));
|
|
257
|
+
/* each time it opens: the projects (the examples are a switch away) */
|
|
258
|
+
const [view, setView] = React.useState<LauncherView>('projects');
|
|
259
|
+
React.useEffect(() => { if (open) setView('projects'); }, [open]);
|
|
260
|
+
const showView = setView;
|
|
261
|
+
const [pendingDelete, setPendingDelete] = React.useState<string[] | null>(null);
|
|
262
|
+
const [pendingFolder, setPendingFolder] = React.useState<ProjectFolder | null>(null);
|
|
263
|
+
const [busy, setBusy] = React.useState<'creating' | 'opening' | null>(null);
|
|
264
|
+
/* Studio's folder browser (no app to pick folders), "Get from GitHub", and a folder without a film to start one in */
|
|
265
|
+
const [browser, setBrowser] = React.useState<{ mode: 'open' } | { mode: 'locate'; project: LibraryProject } | null>(null);
|
|
266
|
+
const [github, setGithub] = React.useState<{ url?: string; start?: boolean; title?: string } | null>(null);
|
|
267
|
+
const [askStart, setAskStart] = React.useState<string | null>(null);
|
|
268
|
+
const close = React.useCallback(() => onClose?.(), [onClose]);
|
|
269
|
+
|
|
270
|
+
React.useEffect(() => {
|
|
271
|
+
if (!open) return undefined;
|
|
272
|
+
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape' && !e.defaultPrevented && !pendingDelete && !pendingFolder && !askStart) close(); };
|
|
273
|
+
window.addEventListener('keydown', onKey);
|
|
274
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
275
|
+
}, [open, close, pendingDelete, pendingFolder, askStart]);
|
|
276
|
+
|
|
277
|
+
const go = (projectId: string, kind: 'creating' | 'opening') => {
|
|
278
|
+
/* the project already open: nowhere to go, and an "Opening" that would never end */
|
|
279
|
+
if (projectId === currentProjectId) { close(); return; }
|
|
280
|
+
/* with tabs it opens in its own tab, not this page: this window stays until that tab covers it (closed first, the
|
|
281
|
+
page behind would show for a moment), then it is done here; a few seconds at most */
|
|
282
|
+
const tabs = studioHost?.tabs;
|
|
283
|
+
if (tabs) {
|
|
284
|
+
setBusy(kind);
|
|
285
|
+
let stop = () => {};
|
|
286
|
+
const timer = setTimeout(() => done(), COVER_WAIT_MS);
|
|
287
|
+
const done = () => { stop(); clearTimeout(timer); setBusy(null); close(); };
|
|
288
|
+
stop = tabs.onCovered?.(done) ?? (() => {});
|
|
289
|
+
onOpen(projectId);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
setBusy(kind);
|
|
293
|
+
onOpen(projectId);
|
|
294
|
+
};
|
|
295
|
+
/* closed: nothing in progress shows when it opens again */
|
|
296
|
+
React.useEffect(() => { if (!open) setBusy(null); }, [open]);
|
|
297
|
+
/* another project opened: done */
|
|
298
|
+
const latestClose = React.useRef(close);
|
|
299
|
+
latestClose.current = close;
|
|
300
|
+
const wasOpen = React.useRef(open);
|
|
301
|
+
wasOpen.current = open;
|
|
302
|
+
const shownFor = React.useRef(currentProjectId);
|
|
303
|
+
React.useEffect(() => {
|
|
304
|
+
if (shownFor.current === currentProjectId) return;
|
|
305
|
+
shownFor.current = currentProjectId;
|
|
306
|
+
setBusy(null);
|
|
307
|
+
if (wasOpen.current) latestClose.current();
|
|
308
|
+
}, [currentProjectId]);
|
|
309
|
+
|
|
310
|
+
/** A new project in `folderId` (null: the top level), named as Studio names it (Untitled, Untitled 2…), opened at once: it is renamed later. */
|
|
311
|
+
async function newProject(folderId: string | null) {
|
|
312
|
+
setBusy('creating');
|
|
313
|
+
try {
|
|
314
|
+
const project = await newProjectIn(folderId, { create: () => api.newProject(t('project.untitled')), move: folderApi.move });
|
|
315
|
+
go(project.id, 'creating');
|
|
316
|
+
} catch {
|
|
317
|
+
setBusy(null);
|
|
318
|
+
lib.flash(t('projects.localProjectFailed'));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const message = (e: unknown) => (e instanceof Error ? e.message : String(e));
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Open a folder as a project: the one dropped, else the one the app asks the person for, else (no app) Studio's
|
|
326
|
+
* folder browser.
|
|
327
|
+
*/
|
|
328
|
+
async function openFolder(path?: string) {
|
|
329
|
+
if (!path && !studioHost?.folders) { setBrowser({ mode: 'open' }); return; }
|
|
330
|
+
const folder = path ?? await studioHost?.folders?.pick();
|
|
331
|
+
if (!folder) return;
|
|
332
|
+
try {
|
|
333
|
+
await openPicked(folder, false);
|
|
334
|
+
} catch (e) {
|
|
335
|
+
lib.flash(t('projects.openFolderFailed').replace('{error}', message(e)));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* A folder picked: opened when it holds a film; one without is asked about first (`start`: the person already chose
|
|
341
|
+
* to start a film there). Throws what to say.
|
|
342
|
+
*/
|
|
343
|
+
async function openPicked(folder: string, start: boolean) {
|
|
344
|
+
let project: { id: string };
|
|
345
|
+
setBusy('opening');
|
|
346
|
+
try {
|
|
347
|
+
project = start ? await api.openFolder(folder) : await sourcesApi.openExisting(folder);
|
|
348
|
+
} catch (e) {
|
|
349
|
+
setBusy(null);
|
|
350
|
+
if (e instanceof SourceError && e.code === 'no-film') { setAskStart(folder); return; }
|
|
351
|
+
if (e instanceof SourceError && e.code === 'not-found') throw new Error(t('projects.browse.error.notFound'));
|
|
352
|
+
throw e;
|
|
353
|
+
}
|
|
354
|
+
setBrowser(null);
|
|
355
|
+
go(project.id, 'opening');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** A project whose folder is gone, pointed at the folder it is in now: the app's dialog, else Studio's browser. */
|
|
359
|
+
async function locate(project: LibraryProject) {
|
|
360
|
+
if (!studioHost?.folders) { setBrowser({ mode: 'locate', project }); return; }
|
|
361
|
+
const folder = await studioHost.folders.pick();
|
|
362
|
+
if (!folder) return;
|
|
363
|
+
try {
|
|
364
|
+
await locateTo(project, folder);
|
|
365
|
+
} catch (e) {
|
|
366
|
+
lib.flash(t('projects.locateFailed').replace('{error}', message(e)));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function locateTo(project: LibraryProject, folder: string) {
|
|
371
|
+
try {
|
|
372
|
+
await sourcesApi.locate(project.id, folder);
|
|
373
|
+
} catch (e) {
|
|
374
|
+
if (e instanceof SourceError && e.code === 'no-film') throw new Error(t('projects.locateNoFilm'));
|
|
375
|
+
throw e;
|
|
376
|
+
}
|
|
377
|
+
setBrowser(null);
|
|
378
|
+
lib.reload();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/* a folder dropped on the list is opened (where the app can tell where it is) */
|
|
382
|
+
const folderOf = (e: React.DragEvent) => [...e.dataTransfer.items].find((item) => item.webkitGetAsEntry()?.isDirectory)?.getAsFile() ?? null;
|
|
383
|
+
const onDragOver = (e: React.DragEvent) => {
|
|
384
|
+
if (studioHost?.folders && e.dataTransfer.types.includes('Files')) { e.preventDefault(); e.dataTransfer.dropEffect = 'copy'; }
|
|
385
|
+
};
|
|
386
|
+
const onDrop = (e: React.DragEvent) => {
|
|
387
|
+
if (!studioHost?.folders) return;
|
|
388
|
+
e.preventDefault();
|
|
389
|
+
const file = folderOf(e);
|
|
390
|
+
const path = file ? studioHost.folders.pathOf(file) : '';
|
|
391
|
+
if (path) void openFolder(path);
|
|
392
|
+
else lib.flash(t('projects.dropFolder'));
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
if (!open) return null;
|
|
396
|
+
|
|
397
|
+
/* what the delete dialog says: a project whose folder is gone only leaves the list (the server keeps to that too) */
|
|
398
|
+
const pendingMissing = (pendingDelete ?? []).filter((id) => lib.projects.find((p) => p.id === id)?.missing).length;
|
|
399
|
+
const onlyMissing = pendingDelete != null && pendingMissing === pendingDelete.length;
|
|
400
|
+
const deleteTitle = !pendingDelete ? ''
|
|
401
|
+
: onlyMissing ? t('projects.removeTitle')
|
|
402
|
+
: pendingDelete.length > 1 ? t('confirm.deleteManyTitle').replace('{n}', String(pendingDelete.length)) : t('confirm.deleteTitle');
|
|
403
|
+
const deleteDesc = !pendingDelete ? ''
|
|
404
|
+
: onlyMissing ? t('projects.removeDesc')
|
|
405
|
+
: `${t(pendingDelete.length > 1 ? 'confirm.deleteManyDesc' : 'confirm.deleteDesc')}${pendingMissing ? ` ${t('projects.removeAlso')}` : ''}`;
|
|
406
|
+
/* what deleting it takes: its projects and those of the folders inside it */
|
|
407
|
+
const folderHeld = pendingFolder ? (folderCounts(lib.projects, lib.folders)[pendingFolder.id] ?? 0) : 0;
|
|
408
|
+
|
|
409
|
+
const content = (
|
|
410
|
+
<>
|
|
411
|
+
<div className="fixed inset-0 z-[10040] bg-black/60" onMouseDown={close} />
|
|
412
|
+
<div
|
|
413
|
+
role="dialog"
|
|
414
|
+
aria-label={t('project.projects')}
|
|
415
|
+
aria-modal="true"
|
|
416
|
+
className="fixed left-1/2 top-1/2 z-[10050] flex -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-2xl border border-[var(--border-strong)] bg-[var(--app-content-bg)] shadow-[0_24px_64px_rgba(0,0,0,0.35)]"
|
|
417
|
+
style={{
|
|
418
|
+
width: 'min(1080px, calc(100vw - 64px))',
|
|
419
|
+
height: 'min(760px, calc(100vh - 96px))',
|
|
420
|
+
}}
|
|
421
|
+
onDragOver={onDragOver}
|
|
422
|
+
onDrop={onDrop}
|
|
423
|
+
>
|
|
424
|
+
<div className="relative flex min-h-0 flex-1 flex-col overflow-hidden">
|
|
425
|
+
{cover.picker}
|
|
426
|
+
{view === 'examples' ? (
|
|
427
|
+
<ExamplesGallery
|
|
428
|
+
mode="start"
|
|
429
|
+
heading={<LauncherSwitch view={view} onView={showView} />}
|
|
430
|
+
onClose={onClose}
|
|
431
|
+
onStart={(example) => setGithub({ url: example.url, start: true, title: example.title })}
|
|
432
|
+
/>
|
|
433
|
+
) : lib.loaded ? (
|
|
434
|
+
<ProjectCoverContext.Provider value={cover.actions}>
|
|
435
|
+
<ProjectsView
|
|
436
|
+
heading={<LauncherSwitch view={view} onView={showView} />}
|
|
437
|
+
projects={lib.projects}
|
|
438
|
+
creating={busy !== null}
|
|
439
|
+
onNewProject={(folderId) => void newProject(folderId)}
|
|
440
|
+
onOpenFolder={() => void openFolder()}
|
|
441
|
+
onGetFromGitHub={() => setGithub({})}
|
|
442
|
+
onLocate={(project) => void locate(project)}
|
|
443
|
+
/* its folder is gone: there is nothing to open, and the editor would only say so */
|
|
444
|
+
onOpen={(project) => (project.missing ? lib.flash(t('projects.missingOpen')) : go(project.id, 'opening'))}
|
|
445
|
+
onRename={(projectId, name) => void lib.renameProject(projectId, name)}
|
|
446
|
+
/* an agent working in one: said at once, before asking (the server refuses it too) */
|
|
447
|
+
onDelete={(ids) => void lib.workingIn(ids).then((working) => {
|
|
448
|
+
if (working) lib.flash(lib.agentRunning(working, ids.length));
|
|
449
|
+
else setPendingDelete(ids);
|
|
450
|
+
})}
|
|
451
|
+
folders={lib.folders}
|
|
452
|
+
onCreateFolder={lib.createFolder}
|
|
453
|
+
onRenameFolder={lib.renameFolder}
|
|
454
|
+
onDeleteFolder={(folder) => {
|
|
455
|
+
const inside = lib.projects.filter((p) => p.folderId === folder.id).map((p) => p.id);
|
|
456
|
+
void lib.workingIn(inside).then((working) => {
|
|
457
|
+
if (working) lib.flash(lib.agentRunning(working, inside.length));
|
|
458
|
+
else setPendingFolder(folder);
|
|
459
|
+
});
|
|
460
|
+
}}
|
|
461
|
+
onMoveToFolder={lib.moveProject}
|
|
462
|
+
onMoveFolder={lib.moveFolder}
|
|
463
|
+
onClose={onClose}
|
|
464
|
+
/>
|
|
465
|
+
</ProjectCoverContext.Provider>
|
|
466
|
+
) : lib.loadFailed ? (
|
|
467
|
+
<div role="alert" className="flex flex-1 flex-col items-center justify-center gap-3 text-[13px] text-[var(--text-muted)]">
|
|
468
|
+
{t('projects.loadFailed')}
|
|
469
|
+
<button
|
|
470
|
+
type="button"
|
|
471
|
+
onClick={lib.retryLoad}
|
|
472
|
+
className="h-8 rounded-lg bg-[var(--surface-2)] px-3 text-[12.5px] font-medium text-[var(--text)] transition hover:bg-[var(--bg-hover)]"
|
|
473
|
+
>
|
|
474
|
+
{t('projects.loadRetry')}
|
|
475
|
+
</button>
|
|
476
|
+
</div>
|
|
477
|
+
) : (
|
|
478
|
+
<div className="flex flex-1 items-center justify-center text-[var(--text-muted)]">
|
|
479
|
+
<Loader2 size={18} className="animate-spin" />
|
|
480
|
+
</div>
|
|
481
|
+
)}
|
|
482
|
+
</div>
|
|
483
|
+
{busy ? (
|
|
484
|
+
<div className="absolute inset-0 z-20 flex items-center justify-center bg-[var(--app-content-bg)]/70">
|
|
485
|
+
<div className="flex items-center gap-2 rounded-lg bg-[var(--surface)] px-4 py-3 text-sm shadow-lg">
|
|
486
|
+
<Loader2 size={16} className="animate-spin" />{t(busy === 'creating' ? 'projects.creating' : 'projects.opening')}
|
|
487
|
+
</div>
|
|
488
|
+
</div>
|
|
489
|
+
) : null}
|
|
490
|
+
{lib.error ? (
|
|
491
|
+
<div role="alert" className="pointer-events-none absolute bottom-4 left-1/2 z-30 -translate-x-1/2 rounded-full border border-[var(--err)]/40 bg-[var(--surface)] px-4 py-2 text-[12.5px] font-medium text-[var(--err)] shadow-[var(--shadow-lg)]">
|
|
492
|
+
{lib.error}
|
|
493
|
+
</div>
|
|
494
|
+
) : null}
|
|
495
|
+
</div>
|
|
496
|
+
<ConfirmDialog
|
|
497
|
+
open={!!pendingDelete}
|
|
498
|
+
title={deleteTitle}
|
|
499
|
+
description={deleteDesc}
|
|
500
|
+
confirmLabel={onlyMissing ? t('projects.removeFromList') : t('confirm.delete')}
|
|
501
|
+
cancelLabel={t('confirm.cancel')}
|
|
502
|
+
danger={!onlyMissing}
|
|
503
|
+
onCancel={() => setPendingDelete(null)}
|
|
504
|
+
onConfirm={() => {
|
|
505
|
+
const ids = pendingDelete;
|
|
506
|
+
setPendingDelete(null);
|
|
507
|
+
if (!ids) return;
|
|
508
|
+
void lib.deleteProjects(ids).then((deleted) => {
|
|
509
|
+
/* their tabs close together, this page's own among them */
|
|
510
|
+
if (deleted.size) void studioHost?.tabs?.gone?.([...deleted]);
|
|
511
|
+
if (deleted.has(currentProjectId)) onCurrentRemoved();
|
|
512
|
+
});
|
|
513
|
+
}}
|
|
514
|
+
/>
|
|
515
|
+
{browser ? (
|
|
516
|
+
<FolderBrowserDialog
|
|
517
|
+
mode={browser.mode}
|
|
518
|
+
title={browser.mode === 'locate' ? t('projects.locateTitle').replace('{name}', browser.project.name) : t('projects.browse.title')}
|
|
519
|
+
subtitle={browser.mode === 'locate' ? browser.project.path : undefined}
|
|
520
|
+
onPick={(path, start) => (browser.mode === 'locate' ? locateTo(browser.project, path) : openPicked(path, start))}
|
|
521
|
+
onClose={() => setBrowser(null)}
|
|
522
|
+
/>
|
|
523
|
+
) : null}
|
|
524
|
+
{github ? (
|
|
525
|
+
<GitHubDialog
|
|
526
|
+
url={github.url}
|
|
527
|
+
start={github.start}
|
|
528
|
+
title={github.title}
|
|
529
|
+
onDone={(projectId) => { setGithub(null); go(projectId, 'opening'); }}
|
|
530
|
+
onClose={() => setGithub(null)}
|
|
531
|
+
/>
|
|
532
|
+
) : null}
|
|
533
|
+
<ConfirmDialog
|
|
534
|
+
open={!!askStart}
|
|
535
|
+
title={t('projects.startFilmTitle')}
|
|
536
|
+
description={t('projects.startFilmDesc').replace('{name}', (askStart ?? '').split(/[\\/]/).filter(Boolean).pop() ?? '')}
|
|
537
|
+
confirmLabel={t('projects.startFilm')}
|
|
538
|
+
cancelLabel={t('confirm.cancel')}
|
|
539
|
+
onCancel={() => setAskStart(null)}
|
|
540
|
+
onConfirm={() => {
|
|
541
|
+
const folder = askStart;
|
|
542
|
+
setAskStart(null);
|
|
543
|
+
if (folder) void openPicked(folder, true).catch((e) => lib.flash(t('projects.openFolderFailed').replace('{error}', message(e))));
|
|
544
|
+
}}
|
|
545
|
+
/>
|
|
546
|
+
<ConfirmDialog
|
|
547
|
+
open={!!pendingFolder}
|
|
548
|
+
title={pendingFolder ? `${t('projects.deleteFolder')} · ${pendingFolder.name}` : ''}
|
|
549
|
+
description={t(folderHeld === 0 ? 'projects.deleteFolderHintEmpty' : folderHeld === 1 ? 'projects.deleteFolderHintOne' : 'projects.deleteFolderHint').replace('{n}', String(folderHeld))}
|
|
550
|
+
confirmLabel={t('confirm.delete')}
|
|
551
|
+
cancelLabel={t('confirm.cancel')}
|
|
552
|
+
danger
|
|
553
|
+
onCancel={() => setPendingFolder(null)}
|
|
554
|
+
onConfirm={() => {
|
|
555
|
+
const folder = pendingFolder;
|
|
556
|
+
setPendingFolder(null);
|
|
557
|
+
if (!folder) return;
|
|
558
|
+
void lib.deleteFolder(folder).then((deleted) => {
|
|
559
|
+
if (deleted.size) void studioHost?.tabs?.gone?.([...deleted]);
|
|
560
|
+
if (deleted.has(currentProjectId)) onCurrentRemoved();
|
|
561
|
+
});
|
|
562
|
+
}}
|
|
563
|
+
/>
|
|
564
|
+
</>
|
|
565
|
+
);
|
|
566
|
+
return createPortal(content, document.body);
|
|
567
|
+
}
|