@myrialabs/clopen 0.0.1
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/.env.example +6 -0
- package/.github/workflows/release.yml +60 -0
- package/.github/workflows/test.yml +40 -0
- package/CONTRIBUTING.md +499 -0
- package/LICENSE +21 -0
- package/README.md +209 -0
- package/backend/index.ts +156 -0
- package/backend/lib/chat/helpers.ts +42 -0
- package/backend/lib/chat/index.ts +2 -0
- package/backend/lib/chat/stream-manager.ts +1126 -0
- package/backend/lib/database/README.md +77 -0
- package/backend/lib/database/index.ts +119 -0
- package/backend/lib/database/migrations/001_create_projects_table.ts +31 -0
- package/backend/lib/database/migrations/002_create_chat_sessions_table.ts +33 -0
- package/backend/lib/database/migrations/003_create_messages_table.ts +32 -0
- package/backend/lib/database/migrations/004_create_prompt_templates_table.ts +34 -0
- package/backend/lib/database/migrations/005_create_settings_table.ts +24 -0
- package/backend/lib/database/migrations/006_add_user_to_messages.ts +58 -0
- package/backend/lib/database/migrations/007_create_stream_states_table.ts +41 -0
- package/backend/lib/database/migrations/008_create_message_snapshots_table.ts +62 -0
- package/backend/lib/database/migrations/009_add_delta_snapshot_fields.ts +41 -0
- package/backend/lib/database/migrations/010_add_soft_delete_and_branch_support.ts +70 -0
- package/backend/lib/database/migrations/011_git_like_commit_graph.ts +156 -0
- package/backend/lib/database/migrations/012_add_file_change_statistics.ts +41 -0
- package/backend/lib/database/migrations/013_checkpoint_tree_state.ts +118 -0
- package/backend/lib/database/migrations/014_add_engine_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/015_add_model_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/016_create_user_projects_table.ts +34 -0
- package/backend/lib/database/migrations/017_add_current_session_to_user_projects.ts +32 -0
- package/backend/lib/database/migrations/018_create_claude_accounts_table.ts +24 -0
- package/backend/lib/database/migrations/019_add_claude_account_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/020_add_snapshot_tree_hash.ts +32 -0
- package/backend/lib/database/migrations/021_drop_prompt_templates_table.ts +33 -0
- package/backend/lib/database/migrations/index.ts +154 -0
- package/backend/lib/database/queries/checkpoint-queries.ts +87 -0
- package/backend/lib/database/queries/engine-queries.ts +75 -0
- package/backend/lib/database/queries/index.ts +9 -0
- package/backend/lib/database/queries/message-queries.ts +472 -0
- package/backend/lib/database/queries/project-queries.ts +117 -0
- package/backend/lib/database/queries/session-queries.ts +271 -0
- package/backend/lib/database/queries/settings-queries.ts +34 -0
- package/backend/lib/database/queries/snapshot-queries.ts +326 -0
- package/backend/lib/database/queries/utils-queries.ts +59 -0
- package/backend/lib/database/seeders/index.ts +13 -0
- package/backend/lib/database/seeders/settings_seeder.ts +84 -0
- package/backend/lib/database/utils/connection.ts +174 -0
- package/backend/lib/database/utils/index.ts +4 -0
- package/backend/lib/database/utils/migration-runner.ts +118 -0
- package/backend/lib/database/utils/seeder-runner.ts +121 -0
- package/backend/lib/engine/adapters/claude/environment.ts +164 -0
- package/backend/lib/engine/adapters/claude/error-handler.ts +60 -0
- package/backend/lib/engine/adapters/claude/index.ts +1 -0
- package/backend/lib/engine/adapters/claude/path-utils.ts +38 -0
- package/backend/lib/engine/adapters/claude/stream.ts +177 -0
- package/backend/lib/engine/adapters/opencode/index.ts +2 -0
- package/backend/lib/engine/adapters/opencode/message-converter.ts +862 -0
- package/backend/lib/engine/adapters/opencode/server.ts +104 -0
- package/backend/lib/engine/adapters/opencode/stream.ts +755 -0
- package/backend/lib/engine/index.ts +196 -0
- package/backend/lib/engine/types.ts +58 -0
- package/backend/lib/files/file-operations.ts +478 -0
- package/backend/lib/files/file-reading.ts +308 -0
- package/backend/lib/files/file-watcher.ts +383 -0
- package/backend/lib/files/path-browsing.ts +382 -0
- package/backend/lib/git/git-executor.ts +88 -0
- package/backend/lib/git/git-parser.ts +411 -0
- package/backend/lib/git/git-service.ts +505 -0
- package/backend/lib/mcp/README.md +1144 -0
- package/backend/lib/mcp/config.ts +316 -0
- package/backend/lib/mcp/index.ts +35 -0
- package/backend/lib/mcp/project-context.ts +236 -0
- package/backend/lib/mcp/servers/browser-automation/actions.ts +156 -0
- package/backend/lib/mcp/servers/browser-automation/browser.ts +419 -0
- package/backend/lib/mcp/servers/browser-automation/index.ts +791 -0
- package/backend/lib/mcp/servers/browser-automation/inspection.ts +501 -0
- package/backend/lib/mcp/servers/helper.ts +143 -0
- package/backend/lib/mcp/servers/index.ts +45 -0
- package/backend/lib/mcp/servers/weather/get-temperature.ts +56 -0
- package/backend/lib/mcp/servers/weather/index.ts +31 -0
- package/backend/lib/mcp/stdio-server.ts +103 -0
- package/backend/lib/mcp/types.ts +65 -0
- package/backend/lib/preview/browser/browser-audio-capture.ts +86 -0
- package/backend/lib/preview/browser/browser-console-manager.ts +263 -0
- package/backend/lib/preview/browser/browser-dialog-handler.ts +222 -0
- package/backend/lib/preview/browser/browser-interaction-handler.ts +421 -0
- package/backend/lib/preview/browser/browser-mcp-control.ts +415 -0
- package/backend/lib/preview/browser/browser-native-ui-handler.ts +512 -0
- package/backend/lib/preview/browser/browser-navigation-tracker.ts +104 -0
- package/backend/lib/preview/browser/browser-pool.ts +357 -0
- package/backend/lib/preview/browser/browser-preview-service.ts +882 -0
- package/backend/lib/preview/browser/browser-tab-manager.ts +935 -0
- package/backend/lib/preview/browser/browser-video-capture.ts +695 -0
- package/backend/lib/preview/browser/scripts/audio-stream.ts +292 -0
- package/backend/lib/preview/browser/scripts/cursor-tracking.ts +85 -0
- package/backend/lib/preview/browser/scripts/video-stream.ts +438 -0
- package/backend/lib/preview/browser/types.ts +359 -0
- package/backend/lib/preview/index.ts +24 -0
- package/backend/lib/project/index.ts +2 -0
- package/backend/lib/project/status-manager.ts +182 -0
- package/backend/lib/shared/index.ts +2 -0
- package/backend/lib/shared/port-utils.ts +25 -0
- package/backend/lib/shared/process-manager.ts +281 -0
- package/backend/lib/snapshot/blob-store.ts +227 -0
- package/backend/lib/snapshot/gitignore.ts +307 -0
- package/backend/lib/snapshot/helpers.ts +397 -0
- package/backend/lib/snapshot/snapshot-service.ts +483 -0
- package/backend/lib/terminal/helpers.ts +14 -0
- package/backend/lib/terminal/index.ts +8 -0
- package/backend/lib/terminal/pty-manager.ts +4 -0
- package/backend/lib/terminal/pty-session-manager.ts +387 -0
- package/backend/lib/terminal/shell-utils.ts +313 -0
- package/backend/lib/terminal/stream-manager.ts +293 -0
- package/backend/lib/tunnel/global-tunnel-manager.ts +243 -0
- package/backend/lib/tunnel/project-tunnel-manager.ts +311 -0
- package/backend/lib/user/helpers.ts +87 -0
- package/backend/lib/utils/ws.ts +944 -0
- package/backend/lib/vite-dev.ts +353 -0
- package/backend/middleware/cors.ts +15 -0
- package/backend/middleware/error-handler.ts +49 -0
- package/backend/middleware/logger.ts +9 -0
- package/backend/types/api.ts +24 -0
- package/backend/ws/README.md +1505 -0
- package/backend/ws/chat/background.ts +198 -0
- package/backend/ws/chat/index.ts +21 -0
- package/backend/ws/chat/stream.ts +707 -0
- package/backend/ws/engine/claude/accounts.ts +401 -0
- package/backend/ws/engine/claude/index.ts +13 -0
- package/backend/ws/engine/claude/status.ts +43 -0
- package/backend/ws/engine/index.ts +14 -0
- package/backend/ws/engine/opencode/index.ts +11 -0
- package/backend/ws/engine/opencode/status.ts +30 -0
- package/backend/ws/engine/utils.ts +36 -0
- package/backend/ws/files/index.ts +30 -0
- package/backend/ws/files/read.ts +189 -0
- package/backend/ws/files/search.ts +453 -0
- package/backend/ws/files/watch.ts +124 -0
- package/backend/ws/files/write.ts +143 -0
- package/backend/ws/git/branch.ts +106 -0
- package/backend/ws/git/commit.ts +39 -0
- package/backend/ws/git/conflict.ts +68 -0
- package/backend/ws/git/diff.ts +69 -0
- package/backend/ws/git/index.ts +24 -0
- package/backend/ws/git/log.ts +41 -0
- package/backend/ws/git/remote.ts +214 -0
- package/backend/ws/git/staging.ts +84 -0
- package/backend/ws/git/status.ts +90 -0
- package/backend/ws/index.ts +69 -0
- package/backend/ws/mcp/index.ts +61 -0
- package/backend/ws/messages/crud.ts +74 -0
- package/backend/ws/messages/index.ts +14 -0
- package/backend/ws/preview/browser/cleanup.ts +129 -0
- package/backend/ws/preview/browser/console.ts +114 -0
- package/backend/ws/preview/browser/interact.ts +513 -0
- package/backend/ws/preview/browser/mcp.ts +129 -0
- package/backend/ws/preview/browser/native-ui.ts +235 -0
- package/backend/ws/preview/browser/stats.ts +55 -0
- package/backend/ws/preview/browser/tab-info.ts +126 -0
- package/backend/ws/preview/browser/tab.ts +166 -0
- package/backend/ws/preview/browser/webcodecs.ts +293 -0
- package/backend/ws/preview/index.ts +146 -0
- package/backend/ws/projects/crud.ts +113 -0
- package/backend/ws/projects/index.ts +25 -0
- package/backend/ws/projects/presence.ts +46 -0
- package/backend/ws/projects/status.ts +116 -0
- package/backend/ws/sessions/crud.ts +327 -0
- package/backend/ws/sessions/index.ts +33 -0
- package/backend/ws/settings/crud.ts +112 -0
- package/backend/ws/settings/index.ts +14 -0
- package/backend/ws/snapshot/index.ts +17 -0
- package/backend/ws/snapshot/restore.ts +173 -0
- package/backend/ws/snapshot/timeline.ts +141 -0
- package/backend/ws/system/index.ts +14 -0
- package/backend/ws/system/operations.ts +49 -0
- package/backend/ws/terminal/index.ts +40 -0
- package/backend/ws/terminal/persistence.ts +153 -0
- package/backend/ws/terminal/session.ts +382 -0
- package/backend/ws/terminal/stream.ts +79 -0
- package/backend/ws/tunnel/index.ts +14 -0
- package/backend/ws/tunnel/operations.ts +91 -0
- package/backend/ws/types.ts +20 -0
- package/backend/ws/user/crud.ts +156 -0
- package/backend/ws/user/index.ts +14 -0
- package/bin/clopen.ts +307 -0
- package/bun.lock +1352 -0
- package/frontend/App.svelte +34 -0
- package/frontend/app.css +313 -0
- package/frontend/lib/app-environment.ts +10 -0
- package/frontend/lib/components/chat/ChatInterface.svelte +407 -0
- package/frontend/lib/components/chat/formatters/ErrorMessage.svelte +57 -0
- package/frontend/lib/components/chat/formatters/MessageFormatter.svelte +224 -0
- package/frontend/lib/components/chat/formatters/TextMessage.svelte +395 -0
- package/frontend/lib/components/chat/formatters/Tools.svelte +70 -0
- package/frontend/lib/components/chat/formatters/index.ts +3 -0
- package/frontend/lib/components/chat/input/ChatInput.svelte +421 -0
- package/frontend/lib/components/chat/input/components/ChatInputActions.svelte +78 -0
- package/frontend/lib/components/chat/input/components/DragDropOverlay.svelte +30 -0
- package/frontend/lib/components/chat/input/components/EditModeIndicator.svelte +33 -0
- package/frontend/lib/components/chat/input/components/EngineModelPicker.svelte +619 -0
- package/frontend/lib/components/chat/input/components/FileAttachmentPreview.svelte +48 -0
- package/frontend/lib/components/chat/input/components/LoadingIndicator.svelte +31 -0
- package/frontend/lib/components/chat/input/composables/use-animations.svelte.ts +201 -0
- package/frontend/lib/components/chat/input/composables/use-chat-actions.svelte.ts +148 -0
- package/frontend/lib/components/chat/input/composables/use-file-handling.svelte.ts +216 -0
- package/frontend/lib/components/chat/input/composables/use-input-state.svelte.ts +357 -0
- package/frontend/lib/components/chat/input/composables/use-textarea-resize.svelte.ts +57 -0
- package/frontend/lib/components/chat/message/ChatMessage.svelte +478 -0
- package/frontend/lib/components/chat/message/ChatMessages.svelte +541 -0
- package/frontend/lib/components/chat/message/DateSeparator.svelte +86 -0
- package/frontend/lib/components/chat/message/MessageBubble.svelte +86 -0
- package/frontend/lib/components/chat/message/MessageHeader.svelte +157 -0
- package/frontend/lib/components/chat/modal/DebugModal.svelte +59 -0
- package/frontend/lib/components/chat/modal/TokenUsageModal.svelte +124 -0
- package/frontend/lib/components/chat/shared/index.ts +2 -0
- package/frontend/lib/components/chat/shared/utils.ts +116 -0
- package/frontend/lib/components/chat/tools/BashOutputTool.svelte +35 -0
- package/frontend/lib/components/chat/tools/BashTool.svelte +46 -0
- package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +139 -0
- package/frontend/lib/components/chat/tools/EditTool.svelte +48 -0
- package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +32 -0
- package/frontend/lib/components/chat/tools/GlobTool.svelte +51 -0
- package/frontend/lib/components/chat/tools/GrepTool.svelte +90 -0
- package/frontend/lib/components/chat/tools/KillShellTool.svelte +26 -0
- package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +31 -0
- package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +38 -0
- package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +34 -0
- package/frontend/lib/components/chat/tools/ReadTool.svelte +41 -0
- package/frontend/lib/components/chat/tools/TaskTool.svelte +64 -0
- package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +75 -0
- package/frontend/lib/components/chat/tools/WebFetchTool.svelte +35 -0
- package/frontend/lib/components/chat/tools/WebSearchTool.svelte +84 -0
- package/frontend/lib/components/chat/tools/WriteTool.svelte +33 -0
- package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +79 -0
- package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +408 -0
- package/frontend/lib/components/chat/tools/components/FileHeader.svelte +45 -0
- package/frontend/lib/components/chat/tools/components/InfoLine.svelte +19 -0
- package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +27 -0
- package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +54 -0
- package/frontend/lib/components/chat/tools/components/index.ts +7 -0
- package/frontend/lib/components/chat/tools/index.ts +26 -0
- package/frontend/lib/components/chat/widgets/FloatingTodoList.svelte +249 -0
- package/frontend/lib/components/chat/widgets/TokenUsage.svelte +78 -0
- package/frontend/lib/components/checkpoint/TimelineModal.svelte +391 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineEdge.svelte +26 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineGraph.svelte +87 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineNode.svelte +108 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineVersionGroup.svelte +59 -0
- package/frontend/lib/components/checkpoint/timeline/animation.ts +168 -0
- package/frontend/lib/components/checkpoint/timeline/config.ts +44 -0
- package/frontend/lib/components/checkpoint/timeline/graph-builder.ts +304 -0
- package/frontend/lib/components/checkpoint/timeline/types.ts +65 -0
- package/frontend/lib/components/checkpoint/timeline/utils.ts +53 -0
- package/frontend/lib/components/common/Alert.svelte +139 -0
- package/frontend/lib/components/common/AvatarBubble.svelte +56 -0
- package/frontend/lib/components/common/Button.svelte +71 -0
- package/frontend/lib/components/common/Card.svelte +102 -0
- package/frontend/lib/components/common/Checkbox.svelte +48 -0
- package/frontend/lib/components/common/Dialog.svelte +249 -0
- package/frontend/lib/components/common/FolderBrowser.svelte +843 -0
- package/frontend/lib/components/common/Icon.svelte +58 -0
- package/frontend/lib/components/common/Input.svelte +72 -0
- package/frontend/lib/components/common/Lightbox.svelte +233 -0
- package/frontend/lib/components/common/LoadingScreen.svelte +52 -0
- package/frontend/lib/components/common/LoadingSpinner.svelte +48 -0
- package/frontend/lib/components/common/Modal.svelte +177 -0
- package/frontend/lib/components/common/ModalProvider.svelte +28 -0
- package/frontend/lib/components/common/ModelSelector.svelte +110 -0
- package/frontend/lib/components/common/MonacoEditor.svelte +569 -0
- package/frontend/lib/components/common/NotificationToast.svelte +113 -0
- package/frontend/lib/components/common/PageTemplate.svelte +76 -0
- package/frontend/lib/components/common/ProjectUserAvatars.svelte +79 -0
- package/frontend/lib/components/common/Select.svelte +98 -0
- package/frontend/lib/components/common/Textarea.svelte +80 -0
- package/frontend/lib/components/common/ThemeToggle.svelte +44 -0
- package/frontend/lib/components/common/lucide-icons.ts +1642 -0
- package/frontend/lib/components/common/material-icons.ts +1082 -0
- package/frontend/lib/components/common/xterm/XTerm.svelte +796 -0
- package/frontend/lib/components/common/xterm/index.ts +16 -0
- package/frontend/lib/components/common/xterm/terminal-config.ts +68 -0
- package/frontend/lib/components/common/xterm/types.ts +31 -0
- package/frontend/lib/components/common/xterm/xterm-service.ts +353 -0
- package/frontend/lib/components/files/FileNode.svelte +384 -0
- package/frontend/lib/components/files/FileTree.svelte +681 -0
- package/frontend/lib/components/files/FileViewer.svelte +728 -0
- package/frontend/lib/components/files/SearchResults.svelte +303 -0
- package/frontend/lib/components/git/BranchManager.svelte +458 -0
- package/frontend/lib/components/git/ChangesSection.svelte +107 -0
- package/frontend/lib/components/git/CommitForm.svelte +76 -0
- package/frontend/lib/components/git/ConflictResolver.svelte +158 -0
- package/frontend/lib/components/git/DiffViewer.svelte +364 -0
- package/frontend/lib/components/git/FileChangeItem.svelte +97 -0
- package/frontend/lib/components/git/GitButton.svelte +33 -0
- package/frontend/lib/components/git/GitLog.svelte +361 -0
- package/frontend/lib/components/git/GitModal.svelte +80 -0
- package/frontend/lib/components/history/HistoryModal.svelte +563 -0
- package/frontend/lib/components/history/HistoryView.svelte +615 -0
- package/frontend/lib/components/index.ts +34 -0
- package/frontend/lib/components/preview/browser/BrowserPreview.svelte +549 -0
- package/frontend/lib/components/preview/browser/components/Canvas.svelte +1058 -0
- package/frontend/lib/components/preview/browser/components/ConsolePanel.svelte +757 -0
- package/frontend/lib/components/preview/browser/components/Container.svelte +450 -0
- package/frontend/lib/components/preview/browser/components/ContextMenu.svelte +236 -0
- package/frontend/lib/components/preview/browser/components/SelectDropdown.svelte +224 -0
- package/frontend/lib/components/preview/browser/components/Toolbar.svelte +339 -0
- package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +36 -0
- package/frontend/lib/components/preview/browser/core/cleanup.svelte.ts +155 -0
- package/frontend/lib/components/preview/browser/core/coordinator.svelte.ts +837 -0
- package/frontend/lib/components/preview/browser/core/interactions.svelte.ts +113 -0
- package/frontend/lib/components/preview/browser/core/mcp-handlers.svelte.ts +296 -0
- package/frontend/lib/components/preview/browser/core/native-ui-handlers.svelte.ts +391 -0
- package/frontend/lib/components/preview/browser/core/stream-handler.svelte.ts +231 -0
- package/frontend/lib/components/preview/browser/core/tab-manager.svelte.ts +210 -0
- package/frontend/lib/components/preview/browser/core/tab-operations.svelte.ts +239 -0
- package/frontend/lib/components/preview/index.ts +2 -0
- package/frontend/lib/components/settings/SettingsModal.svelte +235 -0
- package/frontend/lib/components/settings/SettingsView.svelte +36 -0
- package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +51 -0
- package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +160 -0
- package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +76 -0
- package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +917 -0
- package/frontend/lib/components/settings/general/AdvancedSettings.svelte +187 -0
- package/frontend/lib/components/settings/general/DataManagementSettings.svelte +203 -0
- package/frontend/lib/components/settings/general/GeneralSettings.svelte +10 -0
- package/frontend/lib/components/settings/model/ModelSettings.svelte +357 -0
- package/frontend/lib/components/settings/notifications/NotificationSettings.svelte +205 -0
- package/frontend/lib/components/settings/user/UserSettings.svelte +197 -0
- package/frontend/lib/components/terminal/Terminal.svelte +368 -0
- package/frontend/lib/components/terminal/TerminalTabs.svelte +87 -0
- package/frontend/lib/components/terminal/TerminalView.svelte +55 -0
- package/frontend/lib/components/tunnel/TunnelActive.svelte +142 -0
- package/frontend/lib/components/tunnel/TunnelButton.svelte +54 -0
- package/frontend/lib/components/tunnel/TunnelInactive.svelte +284 -0
- package/frontend/lib/components/tunnel/TunnelModal.svelte +47 -0
- package/frontend/lib/components/tunnel/TunnelQRCode.svelte +49 -0
- package/frontend/lib/components/workspace/DesktopNavigator.svelte +382 -0
- package/frontend/lib/components/workspace/MobileNavigator.svelte +403 -0
- package/frontend/lib/components/workspace/PanelContainer.svelte +100 -0
- package/frontend/lib/components/workspace/PanelHeader.svelte +505 -0
- package/frontend/lib/components/workspace/ViewMenu.svelte +162 -0
- package/frontend/lib/components/workspace/WorkspaceLayout.svelte +169 -0
- package/frontend/lib/components/workspace/layout/DesktopLayout.svelte +15 -0
- package/frontend/lib/components/workspace/layout/MobileLayout.svelte +17 -0
- package/frontend/lib/components/workspace/layout/split-pane/Container.svelte +42 -0
- package/frontend/lib/components/workspace/layout/split-pane/Handle.svelte +85 -0
- package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +37 -0
- package/frontend/lib/components/workspace/panels/ChatPanel.svelte +274 -0
- package/frontend/lib/components/workspace/panels/FilesPanel.svelte +1261 -0
- package/frontend/lib/components/workspace/panels/GitPanel.svelte +1560 -0
- package/frontend/lib/components/workspace/panels/PreviewPanel.svelte +150 -0
- package/frontend/lib/components/workspace/panels/TerminalPanel.svelte +73 -0
- package/frontend/lib/constants/preview.ts +45 -0
- package/frontend/lib/services/chat/chat.service.ts +704 -0
- package/frontend/lib/services/chat/index.ts +7 -0
- package/frontend/lib/services/notification/global-stream-monitor.ts +86 -0
- package/frontend/lib/services/notification/index.ts +8 -0
- package/frontend/lib/services/notification/push.service.ts +144 -0
- package/frontend/lib/services/notification/sound.service.ts +127 -0
- package/frontend/lib/services/preview/browser/browser-console.service.ts +61 -0
- package/frontend/lib/services/preview/browser/browser-webcodecs.service.ts +1499 -0
- package/frontend/lib/services/preview/browser/mcp-integration.svelte.ts +67 -0
- package/frontend/lib/services/preview/index.ts +23 -0
- package/frontend/lib/services/project/index.ts +8 -0
- package/frontend/lib/services/project/status.service.ts +159 -0
- package/frontend/lib/services/snapshot/snapshot.service.ts +47 -0
- package/frontend/lib/services/terminal/background/index.ts +130 -0
- package/frontend/lib/services/terminal/background/session-restore.ts +274 -0
- package/frontend/lib/services/terminal/background/stream-manager.ts +286 -0
- package/frontend/lib/services/terminal/index.ts +14 -0
- package/frontend/lib/services/terminal/persistence.service.ts +260 -0
- package/frontend/lib/services/terminal/project.service.ts +953 -0
- package/frontend/lib/services/terminal/session.service.ts +364 -0
- package/frontend/lib/services/terminal/terminal.service.ts +369 -0
- package/frontend/lib/stores/core/app.svelte.ts +117 -0
- package/frontend/lib/stores/core/files.svelte.ts +73 -0
- package/frontend/lib/stores/core/presence.svelte.ts +48 -0
- package/frontend/lib/stores/core/projects.svelte.ts +317 -0
- package/frontend/lib/stores/core/sessions.svelte.ts +383 -0
- package/frontend/lib/stores/features/claude-accounts.svelte.ts +58 -0
- package/frontend/lib/stores/features/models.svelte.ts +89 -0
- package/frontend/lib/stores/features/settings.svelte.ts +87 -0
- package/frontend/lib/stores/features/terminal.svelte.ts +701 -0
- package/frontend/lib/stores/features/tunnel.svelte.ts +161 -0
- package/frontend/lib/stores/features/user.svelte.ts +96 -0
- package/frontend/lib/stores/ui/chat-input.svelte.ts +57 -0
- package/frontend/lib/stores/ui/chat-model.svelte.ts +61 -0
- package/frontend/lib/stores/ui/dialog.svelte.ts +59 -0
- package/frontend/lib/stores/ui/edit-mode.svelte.ts +214 -0
- package/frontend/lib/stores/ui/notification.svelte.ts +166 -0
- package/frontend/lib/stores/ui/settings-modal.svelte.ts +88 -0
- package/frontend/lib/stores/ui/theme.svelte.ts +179 -0
- package/frontend/lib/stores/ui/workspace.svelte.ts +754 -0
- package/frontend/lib/types/native-ui.ts +73 -0
- package/frontend/lib/utils/chat/date-separator.ts +39 -0
- package/frontend/lib/utils/chat/message-grouper.ts +219 -0
- package/frontend/lib/utils/chat/message-processor.ts +135 -0
- package/frontend/lib/utils/chat/tool-handler.ts +161 -0
- package/frontend/lib/utils/chat/virtual-scroll.svelte.ts +142 -0
- package/frontend/lib/utils/click-outside.ts +20 -0
- package/frontend/lib/utils/context-manager.ts +257 -0
- package/frontend/lib/utils/file-icon-mappings.ts +769 -0
- package/frontend/lib/utils/folder-icon-mappings.ts +1030 -0
- package/frontend/lib/utils/git-status.ts +68 -0
- package/frontend/lib/utils/platform.ts +113 -0
- package/frontend/lib/utils/port-check.ts +65 -0
- package/frontend/lib/utils/terminalFormatter.ts +207 -0
- package/frontend/lib/utils/theme.ts +6 -0
- package/frontend/lib/utils/tree-visualizer.ts +320 -0
- package/frontend/lib/utils/ws.ts +44 -0
- package/frontend/main.ts +13 -0
- package/index.html +70 -0
- package/package.json +111 -0
- package/scripts/generate-icons.ts +87 -0
- package/scripts/pre-publish-check.sh +142 -0
- package/scripts/setup-hooks.sh +134 -0
- package/scripts/validate-branch-name.sh +47 -0
- package/scripts/validate-commit-msg.sh +42 -0
- package/shared/constants/engines.ts +134 -0
- package/shared/types/database/connection.ts +16 -0
- package/shared/types/database/index.ts +6 -0
- package/shared/types/database/schema.ts +141 -0
- package/shared/types/engine/index.ts +45 -0
- package/shared/types/filesystem/index.ts +22 -0
- package/shared/types/git.ts +171 -0
- package/shared/types/messaging/index.ts +239 -0
- package/shared/types/messaging/tool.ts +526 -0
- package/shared/types/network/api.ts +18 -0
- package/shared/types/network/index.ts +5 -0
- package/shared/types/stores/app.ts +23 -0
- package/shared/types/stores/dialog.ts +21 -0
- package/shared/types/stores/index.ts +3 -0
- package/shared/types/stores/settings.ts +15 -0
- package/shared/types/terminal/index.ts +44 -0
- package/shared/types/ui/components.ts +61 -0
- package/shared/types/ui/icons.ts +23 -0
- package/shared/types/ui/index.ts +22 -0
- package/shared/types/ui/notifications.ts +14 -0
- package/shared/types/ui/theme.ts +12 -0
- package/shared/types/websocket/index.ts +43 -0
- package/shared/types/window.d.ts +13 -0
- package/shared/utils/anonymous-user.ts +168 -0
- package/shared/utils/async.ts +10 -0
- package/shared/utils/diff-calculator.ts +184 -0
- package/shared/utils/file-type-detection.ts +166 -0
- package/shared/utils/logger.ts +158 -0
- package/shared/utils/message-formatter.ts +79 -0
- package/shared/utils/path.ts +47 -0
- package/shared/utils/ws-client.ts +768 -0
- package/shared/utils/ws-server.ts +660 -0
- package/static/audio/notification.ogg +0 -0
- package/static/favicon.svg +8 -0
- package/static/fonts/dm-sans/dm-sans-italic-latin-ext.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-italic-latin.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-normal-latin-ext.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-normal-latin.woff2 +0 -0
- package/static/fonts/dm-sans.css +96 -0
- package/svelte.config.js +20 -0
- package/vite.config.ts +33 -0
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// import { onMount } from 'svelte';
|
|
3
|
+
import Button from './Button.svelte';
|
|
4
|
+
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
5
|
+
import Modal from './Modal.svelte';
|
|
6
|
+
import Dialog from './Dialog.svelte';
|
|
7
|
+
import { debug } from '$shared/utils/logger';
|
|
8
|
+
import ws from '$frontend/lib/utils/ws';
|
|
9
|
+
import { settings } from '$frontend/lib/stores/features/settings.svelte';
|
|
10
|
+
|
|
11
|
+
interface FileItem {
|
|
12
|
+
name: string;
|
|
13
|
+
type: 'file' | 'directory';
|
|
14
|
+
path: string;
|
|
15
|
+
modified?: string;
|
|
16
|
+
children?: FileItem[];
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Props {
|
|
21
|
+
isOpen: boolean;
|
|
22
|
+
onClose: () => void;
|
|
23
|
+
onSelect: (folderPath: string, folderName: string) => void;
|
|
24
|
+
currentProjectPath?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let { isOpen = $bindable(), onClose, onSelect, currentProjectPath }: Props = $props();
|
|
28
|
+
|
|
29
|
+
let currentPath = $state('');
|
|
30
|
+
let items: FileItem[] = $state([]);
|
|
31
|
+
let loading = $state(false);
|
|
32
|
+
let showLoadingSpinner = $state(false);
|
|
33
|
+
let error = $state('');
|
|
34
|
+
let selectedPath = $state('');
|
|
35
|
+
let manualPath = $state('');
|
|
36
|
+
let availableDrives: FileItem[] = $state([]);
|
|
37
|
+
let recentProjects: string[] = $state([]);
|
|
38
|
+
let loadingTimeout: number | null = null;
|
|
39
|
+
let showCreateFolder = $state(false);
|
|
40
|
+
let newFolderName = $state('');
|
|
41
|
+
let showDeleteFolder = $state(false);
|
|
42
|
+
let folderToDelete: FileItem | null = $state(null);
|
|
43
|
+
let deleteFolderConfirmName = $state('');
|
|
44
|
+
|
|
45
|
+
// Derived: whether directory access is restricted
|
|
46
|
+
const hasRestrictions = $derived(settings.allowedBasePaths && settings.allowedBasePaths.length > 0);
|
|
47
|
+
|
|
48
|
+
// Detect backend OS from current path (drive letter = Windows)
|
|
49
|
+
const isWindows = $derived(/^[A-Za-z]:/.test(currentPath));
|
|
50
|
+
|
|
51
|
+
// OS-appropriate placeholder for the path input
|
|
52
|
+
const pathPlaceholder = $derived(
|
|
53
|
+
isWindows ? 'e.g. C:\\Users\\username' : 'e.g. /home/username'
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Normalize path separators to forward slash and strip trailing slashes
|
|
57
|
+
function normalizePath(p: string): string {
|
|
58
|
+
let n = p.replace(/\\/g, '/');
|
|
59
|
+
// Keep trailing slash only for drive roots like "C:/"
|
|
60
|
+
if (n.length > 1 && !n.match(/^[A-Za-z]:\/$/)) {
|
|
61
|
+
n = n.replace(/\/+$/, '');
|
|
62
|
+
}
|
|
63
|
+
return n;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Compare two paths, case-insensitively on Windows paths
|
|
67
|
+
function pathsEqual(a: string, b: string): boolean {
|
|
68
|
+
const na = normalizePath(a);
|
|
69
|
+
const nb = normalizePath(b);
|
|
70
|
+
// Windows paths start with a drive letter
|
|
71
|
+
if (/^[A-Za-z]:/.test(na) || /^[A-Za-z]:/.test(nb)) {
|
|
72
|
+
return na.toLowerCase() === nb.toLowerCase();
|
|
73
|
+
}
|
|
74
|
+
return na === nb;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check if a path is within a base path
|
|
78
|
+
function isWithinBase(path: string, base: string): boolean {
|
|
79
|
+
const np = normalizePath(path);
|
|
80
|
+
const nb = normalizePath(base);
|
|
81
|
+
const isWindows = /^[A-Za-z]:/.test(np) || /^[A-Za-z]:/.test(nb);
|
|
82
|
+
if (isWindows) {
|
|
83
|
+
const npl = np.toLowerCase();
|
|
84
|
+
const nbl = nb.toLowerCase();
|
|
85
|
+
return npl === nbl || npl.startsWith(nbl + '/');
|
|
86
|
+
}
|
|
87
|
+
return np === nb || np.startsWith(nb + '/');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check if a path is accessible (within allowed base paths)
|
|
91
|
+
function isPathAllowed(path: string): boolean {
|
|
92
|
+
if (!settings.allowedBasePaths || settings.allowedBasePaths.length === 0) return true;
|
|
93
|
+
return settings.allowedBasePaths.some(base => isWithinBase(path, base));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check if current path is at the restriction boundary (cannot go up)
|
|
97
|
+
const atRestrictionBoundary = $derived(
|
|
98
|
+
hasRestrictions && settings.allowedBasePaths.some(base => pathsEqual(currentPath, base))
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Get available drives/mount points for all platforms
|
|
102
|
+
async function loadAvailableDrives() {
|
|
103
|
+
try {
|
|
104
|
+
const data = await ws.http('files:browse-path', { path: 'drives' });
|
|
105
|
+
if (data.children) {
|
|
106
|
+
availableDrives = data.children as FileItem[];
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
debug.warn('session', 'Failed to get available drives/mount points');
|
|
110
|
+
availableDrives = [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Get recent projects from database
|
|
115
|
+
async function loadRecentProjects() {
|
|
116
|
+
try {
|
|
117
|
+
const projects = await ws.http('projects:list', {});
|
|
118
|
+
if (projects) {
|
|
119
|
+
// Extract paths from projects, sorted by last_opened_at DESC
|
|
120
|
+
recentProjects = projects.map((project: any) => project.path);
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
debug.warn('session', 'Failed to get recent projects');
|
|
124
|
+
recentProjects = [];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Get user's home directory or current working directory
|
|
129
|
+
async function getInitialPath(): Promise<string> {
|
|
130
|
+
// If restrictions are set, start at the first allowed base path
|
|
131
|
+
if (settings.allowedBasePaths && settings.allowedBasePaths.length > 0) {
|
|
132
|
+
return settings.allowedBasePaths[0];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const data = await ws.http('files:browse-path', { path: 'home' });
|
|
137
|
+
if (data.path) {
|
|
138
|
+
return data.path;
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
debug.warn('session', 'Failed to get home directory, using current working directory');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Fallback to current working directory
|
|
145
|
+
try {
|
|
146
|
+
const data = await ws.http('files:browse-path', { path: '.' });
|
|
147
|
+
if (data.path) {
|
|
148
|
+
return data.path;
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
debug.warn('session', 'Failed to get current directory');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Platform-specific fallback
|
|
155
|
+
if (typeof window !== 'undefined') {
|
|
156
|
+
if (navigator.userAgent.includes('Windows')) {
|
|
157
|
+
return 'C:\\';
|
|
158
|
+
} else {
|
|
159
|
+
// Unix-like systems (Linux, macOS, etc.)
|
|
160
|
+
return '/';
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Final fallback
|
|
165
|
+
return '/';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Check if a folder path is a recent project
|
|
169
|
+
function isRecentProject(folderPath: string): boolean {
|
|
170
|
+
return recentProjects.includes(folderPath);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check if a folder path is the active project
|
|
174
|
+
function isActiveProject(folderPath: string): boolean {
|
|
175
|
+
return currentProjectPath !== undefined && currentProjectPath === folderPath;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Navigate to a specific common location
|
|
179
|
+
async function navigateToLocation(location: 'home' | 'cwd' | 'drives' | string) {
|
|
180
|
+
if (location === 'home') {
|
|
181
|
+
const path = await getInitialPath();
|
|
182
|
+
loadDirectory(path);
|
|
183
|
+
} else if (location === 'cwd') {
|
|
184
|
+
loadDirectory('.');
|
|
185
|
+
} else if (location === 'drives') {
|
|
186
|
+
loadDirectory('drives');
|
|
187
|
+
} else {
|
|
188
|
+
// Normalize path separators to match backend OS.
|
|
189
|
+
// Use currentPath to detect OS if available, else detect from the path itself.
|
|
190
|
+
let path = location;
|
|
191
|
+
const backendIsWindows = currentPath
|
|
192
|
+
? /^[A-Za-z]:/.test(currentPath)
|
|
193
|
+
: /^[A-Za-z]:/.test(path);
|
|
194
|
+
if (backendIsWindows) {
|
|
195
|
+
path = path.replace(/\//g, '\\');
|
|
196
|
+
} else {
|
|
197
|
+
path = path.replace(/\\/g, '/');
|
|
198
|
+
}
|
|
199
|
+
loadDirectory(path);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function loadDirectory(path: string) {
|
|
204
|
+
loading = true;
|
|
205
|
+
error = '';
|
|
206
|
+
|
|
207
|
+
// Clear any existing timeout
|
|
208
|
+
if (loadingTimeout) {
|
|
209
|
+
clearTimeout(loadingTimeout);
|
|
210
|
+
loadingTimeout = null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Only show loading spinner after 150ms to prevent flickering for fast operations
|
|
214
|
+
loadingTimeout = setTimeout(() => {
|
|
215
|
+
if (loading) {
|
|
216
|
+
showLoadingSpinner = true;
|
|
217
|
+
}
|
|
218
|
+
}, 150) as unknown as number;
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
// ws.http() now unwraps response - returns data directly or throws error
|
|
222
|
+
const fileData = await ws.http('files:browse-path', { path });
|
|
223
|
+
|
|
224
|
+
currentPath = fileData.path;
|
|
225
|
+
|
|
226
|
+
// Enforce access restrictions
|
|
227
|
+
if (!isPathAllowed(currentPath)) {
|
|
228
|
+
error = `Access restricted. Allowed paths: ${settings.allowedBasePaths.join(', ')}`;
|
|
229
|
+
items = [];
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Auto-select current directory when loading
|
|
234
|
+
selectedPath = fileData.path;
|
|
235
|
+
|
|
236
|
+
if (fileData.type === 'directory' && fileData.children) {
|
|
237
|
+
// Filter to show only directories and common project files
|
|
238
|
+
items = (fileData.children as FileItem[])
|
|
239
|
+
.sort((a, b) => {
|
|
240
|
+
// Directories first, then files
|
|
241
|
+
if (a.type !== b.type) {
|
|
242
|
+
return a.type === 'directory' ? -1 : 1;
|
|
243
|
+
}
|
|
244
|
+
return a.name.localeCompare(b.name);
|
|
245
|
+
});
|
|
246
|
+
} else {
|
|
247
|
+
items = [];
|
|
248
|
+
}
|
|
249
|
+
} catch (err) {
|
|
250
|
+
error = err instanceof Error ? err.message : 'Failed to load directory';
|
|
251
|
+
items = [];
|
|
252
|
+
} finally {
|
|
253
|
+
loading = false;
|
|
254
|
+
showLoadingSpinner = false;
|
|
255
|
+
|
|
256
|
+
// Clear timeout if still pending
|
|
257
|
+
if (loadingTimeout) {
|
|
258
|
+
clearTimeout(loadingTimeout);
|
|
259
|
+
loadingTimeout = null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function navigateToParent() {
|
|
265
|
+
// Special case: if we're viewing drives list
|
|
266
|
+
if (currentPath === 'drives') {
|
|
267
|
+
return; // Can't go higher than drives list
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Handle both Windows and Unix paths properly
|
|
271
|
+
const pathSeparator = currentPath.includes('\\') ? '\\' : '/';
|
|
272
|
+
const pathParts = currentPath.split(pathSeparator).filter(part => part !== '');
|
|
273
|
+
|
|
274
|
+
if (pathParts.length === 0) {
|
|
275
|
+
// Already at root, can't go higher
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (pathParts.length === 1) {
|
|
280
|
+
// We're at a drive root like "C:" on Windows, go to drives list
|
|
281
|
+
if (pathSeparator === '\\' && pathParts[0].match(/^[A-Z]:$/)) {
|
|
282
|
+
navigateToLocation('drives');
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
// On Unix, we're at root
|
|
286
|
+
loadDirectory('/');
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Remove last part and rejoin
|
|
291
|
+
pathParts.pop();
|
|
292
|
+
let parentPath = pathParts.join(pathSeparator);
|
|
293
|
+
|
|
294
|
+
// Ensure proper format
|
|
295
|
+
if (pathSeparator === '\\') {
|
|
296
|
+
// Windows: ensure we have drive letter with backslash
|
|
297
|
+
if (pathParts.length === 1 && pathParts[0].match(/^[A-Z]:$/)) {
|
|
298
|
+
parentPath = pathParts[0] + '\\';
|
|
299
|
+
} else if (!parentPath.endsWith('\\')) {
|
|
300
|
+
parentPath = pathParts.join(pathSeparator) + '\\';
|
|
301
|
+
}
|
|
302
|
+
} else {
|
|
303
|
+
// Unix: ensure we start with /
|
|
304
|
+
if (!parentPath.startsWith('/')) {
|
|
305
|
+
parentPath = '/' + parentPath;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
loadDirectory(parentPath);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function navigateToFolder(folderPath: string) {
|
|
313
|
+
loadDirectory(folderPath);
|
|
314
|
+
// Note: selectedPath is now auto-set in loadDirectory
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function selectFolder(folderPath: string) {
|
|
318
|
+
selectedPath = folderPath;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function confirmSelection() {
|
|
322
|
+
if (selectedPath) {
|
|
323
|
+
// Handle both Windows and Unix paths properly
|
|
324
|
+
const pathSeparator = selectedPath.includes('\\') ? '\\' : '/';
|
|
325
|
+
const folderName = selectedPath.split(pathSeparator).pop() || selectedPath;
|
|
326
|
+
onSelect(selectedPath, folderName);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
function navigateToManualPath() {
|
|
332
|
+
if (manualPath.trim()) {
|
|
333
|
+
let path = manualPath.trim();
|
|
334
|
+
// Normalize path separators to match backend OS
|
|
335
|
+
if (isWindows) {
|
|
336
|
+
path = path.replace(/\//g, '\\');
|
|
337
|
+
} else {
|
|
338
|
+
path = path.replace(/\\/g, '/');
|
|
339
|
+
}
|
|
340
|
+
loadDirectory(path);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function selectCurrentFolder() {
|
|
345
|
+
if (currentPath) {
|
|
346
|
+
selectFolder(currentPath);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function handleManualPathKeydown(event: KeyboardEvent) {
|
|
351
|
+
if (event.key === 'Enter') {
|
|
352
|
+
navigateToManualPath();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
357
|
+
if (event.key === 'Escape') {
|
|
358
|
+
if (showCreateFolder) {
|
|
359
|
+
showCreateFolder = false;
|
|
360
|
+
newFolderName = '';
|
|
361
|
+
} else {
|
|
362
|
+
onClose();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function createNewFolder() {
|
|
368
|
+
if (!newFolderName.trim()) return;
|
|
369
|
+
|
|
370
|
+
const folderPath = currentPath.includes('\\')
|
|
371
|
+
? `${currentPath}\\${newFolderName.trim()}`
|
|
372
|
+
: `${currentPath}/${newFolderName.trim()}`;
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
// Create folder via WebSocket
|
|
376
|
+
const data = await ws.http('files:create-directory', {
|
|
377
|
+
dirPath: folderPath
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Store current path before clearing dialog state
|
|
381
|
+
const pathToReload = currentPath;
|
|
382
|
+
|
|
383
|
+
// Close create folder dialog
|
|
384
|
+
showCreateFolder = false;
|
|
385
|
+
newFolderName = '';
|
|
386
|
+
|
|
387
|
+
// Add a small delay to ensure file system operation is complete
|
|
388
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
389
|
+
|
|
390
|
+
// Reload current directory using the main loadDirectory function
|
|
391
|
+
if (pathToReload) {
|
|
392
|
+
loadDirectory(pathToReload);
|
|
393
|
+
} else {
|
|
394
|
+
// Fallback to current path if somehow it's empty
|
|
395
|
+
const fallbackPath = currentPath || '.';
|
|
396
|
+
loadDirectory(fallbackPath);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Select the new folder after reload starts
|
|
400
|
+
selectFolder(folderPath);
|
|
401
|
+
} catch (err) {
|
|
402
|
+
error = err instanceof Error ? err.message : 'Failed to create folder';
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function openDeleteDialog(item: FileItem) {
|
|
407
|
+
folderToDelete = item;
|
|
408
|
+
deleteFolderConfirmName = '';
|
|
409
|
+
showDeleteFolder = true;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async function deleteFolder() {
|
|
413
|
+
if (!folderToDelete || deleteFolderConfirmName !== folderToDelete.name) return;
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
const folderPath = folderToDelete.path;
|
|
417
|
+
|
|
418
|
+
// Delete folder via WebSocket
|
|
419
|
+
await ws.http('files:delete', {
|
|
420
|
+
filePath: folderPath,
|
|
421
|
+
force: true // Allow deletion of non-empty directories
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Store current path before clearing dialog state
|
|
425
|
+
const pathToReload = currentPath;
|
|
426
|
+
|
|
427
|
+
// Clear selection if deleted folder was selected
|
|
428
|
+
if (selectedPath === folderPath) {
|
|
429
|
+
selectedPath = pathToReload;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Close delete dialog
|
|
433
|
+
showDeleteFolder = false;
|
|
434
|
+
folderToDelete = null;
|
|
435
|
+
deleteFolderConfirmName = '';
|
|
436
|
+
|
|
437
|
+
// Add a small delay to ensure file system operation is complete
|
|
438
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
439
|
+
|
|
440
|
+
// Reload current directory using the main loadDirectory function
|
|
441
|
+
if (pathToReload) {
|
|
442
|
+
loadDirectory(pathToReload);
|
|
443
|
+
} else {
|
|
444
|
+
// Fallback to current path if somehow it's empty
|
|
445
|
+
const fallbackPath = currentPath || '.';
|
|
446
|
+
loadDirectory(fallbackPath);
|
|
447
|
+
}
|
|
448
|
+
} catch (err) {
|
|
449
|
+
error = err instanceof Error ? err.message : 'Failed to delete folder';
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Initialize when dialog opens
|
|
454
|
+
$effect(() => {
|
|
455
|
+
if (isOpen) {
|
|
456
|
+
// Load available drives/mount points for all platforms
|
|
457
|
+
loadAvailableDrives();
|
|
458
|
+
|
|
459
|
+
// Load recent projects from database
|
|
460
|
+
loadRecentProjects();
|
|
461
|
+
|
|
462
|
+
getInitialPath().then(path => {
|
|
463
|
+
loadDirectory(path);
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// Sync manualPath with currentPath
|
|
469
|
+
$effect(() => {
|
|
470
|
+
manualPath = currentPath;
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// Cleanup function to clear timeout when component unmounts
|
|
474
|
+
$effect(() => {
|
|
475
|
+
return () => {
|
|
476
|
+
if (loadingTimeout) {
|
|
477
|
+
clearTimeout(loadingTimeout);
|
|
478
|
+
loadingTimeout = null;
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
});
|
|
482
|
+
</script>
|
|
483
|
+
|
|
484
|
+
<svelte:window onkeydown={handleKeydown} />
|
|
485
|
+
|
|
486
|
+
<Modal
|
|
487
|
+
bind:isOpen={isOpen}
|
|
488
|
+
onClose={onClose}
|
|
489
|
+
size="xl"
|
|
490
|
+
className=""
|
|
491
|
+
>
|
|
492
|
+
{#snippet header()}
|
|
493
|
+
<div class="bg-slate-50 dark:bg-slate-800">
|
|
494
|
+
<div class="flex items-center justify-between px-4 md:px-6 pt-4 mb-2">
|
|
495
|
+
<div>
|
|
496
|
+
<h2 class="text-lg font-semibold text-slate-900 dark:text-slate-100">
|
|
497
|
+
Select Project Folder
|
|
498
|
+
</h2>
|
|
499
|
+
</div>
|
|
500
|
+
<button
|
|
501
|
+
type="button"
|
|
502
|
+
class="p-2 rounded-lg text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
|
|
503
|
+
onclick={onClose}
|
|
504
|
+
aria-label="Close modal"
|
|
505
|
+
>
|
|
506
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
507
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
508
|
+
</svg>
|
|
509
|
+
</button>
|
|
510
|
+
</div>
|
|
511
|
+
|
|
512
|
+
<!-- Path navigation -->
|
|
513
|
+
<div class="px-6 pb-4">
|
|
514
|
+
<div class="flex items-center space-x-2 text-sm">
|
|
515
|
+
<button
|
|
516
|
+
onclick={navigateToParent}
|
|
517
|
+
disabled={currentPath === '/' || loading || atRestrictionBoundary}
|
|
518
|
+
class="flex p-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
519
|
+
aria-label="Go to parent directory"
|
|
520
|
+
>
|
|
521
|
+
<Icon name="lucide:arrow-up" class="text-slate-600 dark:text-slate-300" />
|
|
522
|
+
</button>
|
|
523
|
+
|
|
524
|
+
<input
|
|
525
|
+
bind:value={manualPath}
|
|
526
|
+
onkeydown={handleManualPathKeydown}
|
|
527
|
+
class="flex-1 font-mono text-slate-700 dark:text-slate-200 bg-white dark:bg-slate-800 px-3 py-2 rounded-lg border border-slate-200 dark:border-slate-600 focus:outline-none focus:ring-2 focus:ring-violet-500/20 focus:border-violet-500 dark:focus:border-violet-400"
|
|
528
|
+
placeholder={pathPlaceholder}
|
|
529
|
+
/>
|
|
530
|
+
<button
|
|
531
|
+
onclick={navigateToManualPath}
|
|
532
|
+
disabled={!manualPath.trim()}
|
|
533
|
+
class="flex p-3 rounded-lg bg-violet-500 dark:bg-violet-600 hover:bg-violet-600 dark:hover:bg-violet-700 text-white disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
534
|
+
aria-label="Navigate to path"
|
|
535
|
+
>
|
|
536
|
+
<Icon name="lucide:arrow-right" class="text-slate-100" />
|
|
537
|
+
</button>
|
|
538
|
+
|
|
539
|
+
{#if showLoadingSpinner}
|
|
540
|
+
<div class="animate-spin rounded-full h-4 w-4 border-2 border-violet-500 border-t-transparent"></div>
|
|
541
|
+
{/if}
|
|
542
|
+
</div>
|
|
543
|
+
|
|
544
|
+
<!-- Quick actions -->
|
|
545
|
+
<div class="flex items-center justify-between mt-3">
|
|
546
|
+
<div class="flex items-center gap-2 flex-wrap">
|
|
547
|
+
{#if hasRestrictions}
|
|
548
|
+
<!-- Restricted mode: show allowed base paths as quick access -->
|
|
549
|
+
{#each settings.allowedBasePaths as basePath (basePath)}
|
|
550
|
+
<button
|
|
551
|
+
onclick={() => navigateToLocation(basePath)}
|
|
552
|
+
class="px-3 py-1.5 text-xs rounded-lg bg-slate-200 dark:bg-slate-700 hover:bg-slate-300 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-300 transition-colors"
|
|
553
|
+
title="Go to {basePath}"
|
|
554
|
+
>
|
|
555
|
+
<Icon name="lucide:folder-lock" class="inline mr-1" />
|
|
556
|
+
{basePath.split(/[/\\]/).filter(Boolean).pop() || basePath}
|
|
557
|
+
</button>
|
|
558
|
+
{/each}
|
|
559
|
+
{:else}
|
|
560
|
+
<!-- Normal mode: home, system, drive buttons -->
|
|
561
|
+
<button
|
|
562
|
+
onclick={() => navigateToLocation('home')}
|
|
563
|
+
class="px-3 py-1.5 text-xs rounded-lg bg-slate-200 dark:bg-slate-700 hover:bg-slate-300 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-300 transition-colors"
|
|
564
|
+
title="Go to home directory"
|
|
565
|
+
>
|
|
566
|
+
<Icon name="lucide:house" class="inline mr-1" />
|
|
567
|
+
Home
|
|
568
|
+
</button>
|
|
569
|
+
<button
|
|
570
|
+
onclick={() => navigateToLocation('drives')}
|
|
571
|
+
class="px-3 py-1.5 text-xs rounded-lg bg-slate-200 dark:bg-slate-700 hover:bg-slate-300 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-300 transition-colors"
|
|
572
|
+
title="Browse system locations"
|
|
573
|
+
>
|
|
574
|
+
<Icon name="lucide:monitor" class="inline mr-1" />
|
|
575
|
+
System
|
|
576
|
+
</button>
|
|
577
|
+
|
|
578
|
+
<!-- Dynamic location buttons (all platforms) -->
|
|
579
|
+
{#if availableDrives.length > 0}
|
|
580
|
+
{#each availableDrives as location (location.path)}
|
|
581
|
+
<button
|
|
582
|
+
onclick={() => navigateToLocation(location.path)}
|
|
583
|
+
class="px-3 py-1.5 text-xs rounded-lg bg-slate-200 dark:bg-slate-700 hover:bg-slate-300 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-300 transition-colors"
|
|
584
|
+
title="Go to {location.name}"
|
|
585
|
+
>
|
|
586
|
+
{#if location.path.match(/^[A-Z]:\\/i)}
|
|
587
|
+
<!-- Windows drive -->
|
|
588
|
+
<Icon name="lucide:hard-drive" class="inline mr-1" />
|
|
589
|
+
{location.path.replace(/\\/g, '')}
|
|
590
|
+
{:else if location.path === '/'}
|
|
591
|
+
<!-- Unix root -->
|
|
592
|
+
<Icon name="lucide:folder-root" class="inline mr-1" />
|
|
593
|
+
/
|
|
594
|
+
{:else if location.path.startsWith('/Volumes')}
|
|
595
|
+
<!-- macOS volume -->
|
|
596
|
+
<Icon name="lucide:disc" class="inline mr-1" />
|
|
597
|
+
{location.path.split('/').pop()}
|
|
598
|
+
{:else if location.path.includes('/mnt') || location.path.includes('/media')}
|
|
599
|
+
<!-- Linux mount point -->
|
|
600
|
+
<Icon name="lucide:hard-drive" class="inline mr-1" />
|
|
601
|
+
{location.path.split('/').pop()}
|
|
602
|
+
{:else}
|
|
603
|
+
<!-- Generic location -->
|
|
604
|
+
<Icon name="lucide:folder" class="inline mr-1" />
|
|
605
|
+
{location.path.split('/').pop() || location.name}
|
|
606
|
+
{/if}
|
|
607
|
+
</button>
|
|
608
|
+
{/each}
|
|
609
|
+
{/if}
|
|
610
|
+
{/if}
|
|
611
|
+
</div>
|
|
612
|
+
|
|
613
|
+
<div class="flex items-center space-x-2">
|
|
614
|
+
<button
|
|
615
|
+
onclick={() => showCreateFolder = true}
|
|
616
|
+
class="px-3 py-1.5 text-xs rounded-lg bg-slate-200 dark:bg-slate-700 hover:bg-slate-300 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-300 transition-colors"
|
|
617
|
+
title="Create new folder in current directory"
|
|
618
|
+
>
|
|
619
|
+
<Icon name="lucide:folder-plus" class="inline sm:mr-1" />
|
|
620
|
+
<span class="hidden sm:inline">New Folder</span>
|
|
621
|
+
</button>
|
|
622
|
+
<!-- <button
|
|
623
|
+
onclick={selectCurrentFolder}
|
|
624
|
+
disabled={!currentPath}
|
|
625
|
+
class="px-3 py-1.5 text-xs rounded-lg bg-slate-200 dark:bg-slate-700 hover:bg-slate-300 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-300 transition-colors"
|
|
626
|
+
>
|
|
627
|
+
<Icon name="lucide:check" class="inline mr-1" />
|
|
628
|
+
Use Current Folder
|
|
629
|
+
</button> -->
|
|
630
|
+
</div>
|
|
631
|
+
</div>
|
|
632
|
+
</div>
|
|
633
|
+
</div>
|
|
634
|
+
{/snippet}
|
|
635
|
+
|
|
636
|
+
{#snippet children()}
|
|
637
|
+
<!-- Scrollable directory contents -->
|
|
638
|
+
{#if error}
|
|
639
|
+
<div class="flex items-center justify-center py-12">
|
|
640
|
+
<div class="text-center">
|
|
641
|
+
<Icon name="lucide:triangle-alert" class="text-4xl text-red-500 mx-auto mb-4" />
|
|
642
|
+
<p class="text-red-600 dark:text-red-400 font-medium">Error Loading Directory</p>
|
|
643
|
+
<p class="text-sm text-slate-600 dark:text-slate-400 mt-2">{error}</p>
|
|
644
|
+
<Button
|
|
645
|
+
onclick={() => loadDirectory(currentPath)}
|
|
646
|
+
variant="outline"
|
|
647
|
+
size="sm"
|
|
648
|
+
class="mt-4"
|
|
649
|
+
>
|
|
650
|
+
Try Again
|
|
651
|
+
</Button>
|
|
652
|
+
</div>
|
|
653
|
+
</div>
|
|
654
|
+
{:else if showLoadingSpinner && items.length === 0}
|
|
655
|
+
<div class="flex items-center justify-center py-12">
|
|
656
|
+
<div class="text-center">
|
|
657
|
+
<div class="animate-spin rounded-full h-8 w-8 border-2 border-violet-500 border-t-transparent mx-auto mb-4"></div>
|
|
658
|
+
<p class="text-slate-600 dark:text-slate-400">Loading directory...</p>
|
|
659
|
+
</div>
|
|
660
|
+
</div>
|
|
661
|
+
{:else if items.length === 0}
|
|
662
|
+
<div class="flex items-center justify-center py-12">
|
|
663
|
+
<div class="text-center">
|
|
664
|
+
<Icon name="lucide:folder-x" class="text-4xl text-slate-400 mx-auto mb-4" />
|
|
665
|
+
<p class="text-slate-600 dark:text-slate-400">No folders found</p>
|
|
666
|
+
<p class="text-sm text-slate-500 dark:text-slate-500 mt-2">This directory doesn't contain any subdirectories</p>
|
|
667
|
+
</div>
|
|
668
|
+
</div>
|
|
669
|
+
{:else}
|
|
670
|
+
<div class="space-y-2 transition-opacity duration-300 {loading ? 'opacity-75' : 'opacity-100'}">
|
|
671
|
+
{#each items as item (item.path)}
|
|
672
|
+
<div
|
|
673
|
+
class="flex items-center space-x-3 py-3 px-4 rounded-xl border transition-all duration-200 cursor-pointer {selectedPath === item.path
|
|
674
|
+
? 'bg-violet-50 dark:bg-violet-900/20 border-violet-200 dark:border-violet-700'
|
|
675
|
+
: isActiveProject(item.path)
|
|
676
|
+
? 'bg-violet-50/50 dark:bg-violet-900/10 border-violet-300 dark:border-violet-700/50 hover:bg-violet-100/50 dark:hover:bg-violet-900/20'
|
|
677
|
+
: isRecentProject(item.path)
|
|
678
|
+
? 'bg-green-50 dark:bg-green-900/10 border-green-200 dark:border-green-800/50 hover:bg-green-100 dark:hover:bg-green-900/20'
|
|
679
|
+
: 'bg-slate-50 dark:bg-slate-800/30 border-slate-200 dark:border-slate-700 hover:bg-slate-100 dark:hover:bg-slate-800/50'}"
|
|
680
|
+
onclick={() => {
|
|
681
|
+
if (item.type === 'directory') {
|
|
682
|
+
navigateToFolder(item.path);
|
|
683
|
+
}
|
|
684
|
+
}}
|
|
685
|
+
role="button"
|
|
686
|
+
tabindex="0"
|
|
687
|
+
onkeydown={(e) => {
|
|
688
|
+
if (e.key === 'Enter') {
|
|
689
|
+
if (item.type === 'directory') {
|
|
690
|
+
navigateToFolder(item.path);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}}
|
|
694
|
+
>
|
|
695
|
+
<div class="flex-shrink-0 relative">
|
|
696
|
+
{#if item.type === 'directory'}
|
|
697
|
+
<Icon
|
|
698
|
+
name="lucide:folder"
|
|
699
|
+
class="text-xl {selectedPath === item.path
|
|
700
|
+
? 'text-violet-600 dark:text-violet-400'
|
|
701
|
+
: isActiveProject(item.path)
|
|
702
|
+
? 'text-violet-600 dark:text-violet-400'
|
|
703
|
+
: isRecentProject(item.path)
|
|
704
|
+
? 'text-green-600 dark:text-green-400'
|
|
705
|
+
: 'text-slate-500 dark:text-slate-400'}"
|
|
706
|
+
/>
|
|
707
|
+
{#if isActiveProject(item.path)}
|
|
708
|
+
<div class="absolute -top-1 -right-1">
|
|
709
|
+
<Icon
|
|
710
|
+
name="lucide:star"
|
|
711
|
+
class="text-xs text-violet-600 dark:text-violet-400 bg-white dark:bg-slate-800 rounded-full p-0.5"
|
|
712
|
+
/>
|
|
713
|
+
</div>
|
|
714
|
+
{:else if isRecentProject(item.path)}
|
|
715
|
+
<div class="absolute -top-1 -right-1">
|
|
716
|
+
<Icon
|
|
717
|
+
name="lucide:clock"
|
|
718
|
+
class="text-xs text-green-600 dark:text-green-400 bg-white dark:bg-slate-800 rounded-full p-0.5"
|
|
719
|
+
/>
|
|
720
|
+
</div>
|
|
721
|
+
{/if}
|
|
722
|
+
{:else}
|
|
723
|
+
<Icon
|
|
724
|
+
name="lucide:file-text"
|
|
725
|
+
class="text-xl text-slate-500 dark:text-slate-400"
|
|
726
|
+
/>
|
|
727
|
+
{/if}
|
|
728
|
+
</div>
|
|
729
|
+
<div class="flex-1 min-w-0">
|
|
730
|
+
<div class="flex items-center gap-2">
|
|
731
|
+
<p class="font-medium text-slate-900 dark:text-slate-100 truncate">
|
|
732
|
+
{item.name}
|
|
733
|
+
</p>
|
|
734
|
+
{#if isActiveProject(item.path)}
|
|
735
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-violet-100 dark:bg-violet-900/30 text-violet-800 dark:text-violet-300 border border-violet-200 dark:border-violet-800">
|
|
736
|
+
Active Project
|
|
737
|
+
</span>
|
|
738
|
+
{:else if isRecentProject(item.path)}
|
|
739
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300 border border-green-200 dark:border-green-800">
|
|
740
|
+
Recent Project
|
|
741
|
+
</span>
|
|
742
|
+
{/if}
|
|
743
|
+
</div>
|
|
744
|
+
{#if item.modified}
|
|
745
|
+
<p class="text-xs text-slate-500 dark:text-slate-400">
|
|
746
|
+
Modified {new Date(item.modified).toLocaleDateString()}
|
|
747
|
+
</p>
|
|
748
|
+
{/if}
|
|
749
|
+
</div>
|
|
750
|
+
{#if item.type === 'directory'}
|
|
751
|
+
<div class="flex-shrink-0 flex items-center gap-2">
|
|
752
|
+
<button
|
|
753
|
+
onclick={(e) => {
|
|
754
|
+
e.stopPropagation();
|
|
755
|
+
openDeleteDialog(item);
|
|
756
|
+
}}
|
|
757
|
+
class="flex p-1.5 rounded-lg hover:bg-red-100 dark:hover:bg-red-900/20 transition-colors group"
|
|
758
|
+
title="Delete folder"
|
|
759
|
+
aria-label="Delete {item.name}"
|
|
760
|
+
>
|
|
761
|
+
<Icon
|
|
762
|
+
name="lucide:trash-2"
|
|
763
|
+
class="text-sm text-slate-400 dark:text-slate-500 group-hover:text-red-500 dark:group-hover:text-red-400"
|
|
764
|
+
/>
|
|
765
|
+
</button>
|
|
766
|
+
<Icon
|
|
767
|
+
name="lucide:chevron-right"
|
|
768
|
+
class="text-sm text-slate-400 dark:text-slate-500"
|
|
769
|
+
/>
|
|
770
|
+
</div>
|
|
771
|
+
{/if}
|
|
772
|
+
</div>
|
|
773
|
+
{/each}
|
|
774
|
+
</div>
|
|
775
|
+
{/if}
|
|
776
|
+
{/snippet}
|
|
777
|
+
|
|
778
|
+
{#snippet footer()}
|
|
779
|
+
<div class="flex items-end justify-between flex-wrap sm:flex-nowrap gap-3 w-full">
|
|
780
|
+
{#if selectedPath}
|
|
781
|
+
<div class="w-full">
|
|
782
|
+
<p class="text-sm text-slate-600 dark:text-slate-400 mb-2">Selected folder:</p>
|
|
783
|
+
<div class="font-mono text-sm bg-slate-50 dark:bg-slate-800 px-3 py-2 rounded-lg border border-slate-200 dark:border-slate-600 text-slate-700 dark:text-slate-200 break-all">
|
|
784
|
+
{selectedPath}
|
|
785
|
+
</div>
|
|
786
|
+
</div>
|
|
787
|
+
{/if}
|
|
788
|
+
|
|
789
|
+
<div class="flex items-center justify-between gap-3 flex-none w-full sm:w-auto">
|
|
790
|
+
<!-- <div class="flex items-center gap-3 ml-auto"> -->
|
|
791
|
+
<!-- <Button variant="outline" onclick={onClose}>
|
|
792
|
+
Cancel
|
|
793
|
+
</Button> -->
|
|
794
|
+
<Button
|
|
795
|
+
onclick={confirmSelection}
|
|
796
|
+
disabled={!selectedPath}
|
|
797
|
+
class="disabled:opacity-50 disabled:cursor-not-allowed w-full sm:w-auto"
|
|
798
|
+
>
|
|
799
|
+
Select Project
|
|
800
|
+
</Button>
|
|
801
|
+
<!-- </div> -->
|
|
802
|
+
</div>
|
|
803
|
+
</div>
|
|
804
|
+
{/snippet}
|
|
805
|
+
</Modal>
|
|
806
|
+
|
|
807
|
+
<!-- Create Folder Dialog -->
|
|
808
|
+
<Dialog
|
|
809
|
+
bind:isOpen={showCreateFolder}
|
|
810
|
+
onClose={() => {
|
|
811
|
+
showCreateFolder = false;
|
|
812
|
+
newFolderName = '';
|
|
813
|
+
}}
|
|
814
|
+
title="Create New Folder"
|
|
815
|
+
type="info"
|
|
816
|
+
message={`Create a new folder in: ${currentPath}`}
|
|
817
|
+
bind:inputValue={newFolderName}
|
|
818
|
+
inputPlaceholder="Enter folder name"
|
|
819
|
+
confirmText="Create"
|
|
820
|
+
cancelText="Cancel"
|
|
821
|
+
showCancel={true}
|
|
822
|
+
onConfirm={createNewFolder}
|
|
823
|
+
/>
|
|
824
|
+
|
|
825
|
+
<!-- Delete Folder Dialog -->
|
|
826
|
+
<Dialog
|
|
827
|
+
bind:isOpen={showDeleteFolder}
|
|
828
|
+
onClose={() => {
|
|
829
|
+
showDeleteFolder = false;
|
|
830
|
+
folderToDelete = null;
|
|
831
|
+
deleteFolderConfirmName = '';
|
|
832
|
+
}}
|
|
833
|
+
title="Delete Folder"
|
|
834
|
+
type="warning"
|
|
835
|
+
message={`This action cannot be undone. To delete "${folderToDelete?.name || ''}", type the folder name exactly as shown:`}
|
|
836
|
+
bind:inputValue={deleteFolderConfirmName}
|
|
837
|
+
inputPlaceholder="Type folder name to confirm"
|
|
838
|
+
confirmText="Delete"
|
|
839
|
+
cancelText="Cancel"
|
|
840
|
+
showCancel={true}
|
|
841
|
+
confirmDisabled={deleteFolderConfirmName !== folderToDelete?.name}
|
|
842
|
+
onConfirm={deleteFolder}
|
|
843
|
+
/>
|