@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,505 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { browser } from '$frontend/lib/app-environment';
|
|
3
|
+
import { onMount, onDestroy } from 'svelte';
|
|
4
|
+
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
5
|
+
import AvatarBubble from '$frontend/lib/components/common/AvatarBubble.svelte';
|
|
6
|
+
import { sessionState } from '$frontend/lib/stores/core/sessions.svelte';
|
|
7
|
+
import { projectState } from '$frontend/lib/stores/core/projects.svelte';
|
|
8
|
+
import { presenceState } from '$frontend/lib/stores/core/presence.svelte';
|
|
9
|
+
import { userStore } from '$frontend/lib/stores/features/user.svelte';
|
|
10
|
+
import { workspaceState, type PanelId } from '$frontend/lib/stores/ui/workspace.svelte';
|
|
11
|
+
import type { IconName } from '$shared/types/ui/icons';
|
|
12
|
+
import type { DeviceSize } from '$frontend/lib/constants/preview';
|
|
13
|
+
import { DEVICE_VIEWPORTS } from '$frontend/lib/constants/preview';
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
panelId: PanelId;
|
|
17
|
+
chatPanelRef?: any;
|
|
18
|
+
filesPanelRef?: any;
|
|
19
|
+
terminalPanelRef?: any;
|
|
20
|
+
previewPanelRef?: any;
|
|
21
|
+
gitPanelRef?: any;
|
|
22
|
+
onHistoryOpen?: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
panelId,
|
|
27
|
+
chatPanelRef,
|
|
28
|
+
filesPanelRef,
|
|
29
|
+
terminalPanelRef,
|
|
30
|
+
previewPanelRef,
|
|
31
|
+
gitPanelRef,
|
|
32
|
+
onHistoryOpen
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
const panel = $derived(workspaceState.panels[panelId]);
|
|
36
|
+
const iconName = $derived((panel?.icon ?? 'lucide:box') as IconName);
|
|
37
|
+
|
|
38
|
+
// Mobile detection
|
|
39
|
+
let isMobile = $state(false);
|
|
40
|
+
|
|
41
|
+
// Chat session users (other users in the same chat session, excluding self)
|
|
42
|
+
const chatSessionUsers = $derived.by(() => {
|
|
43
|
+
if (panelId !== 'chat') return [];
|
|
44
|
+
const projectId = projectState.currentProject?.id;
|
|
45
|
+
const chatSessionId = sessionState.currentSession?.id;
|
|
46
|
+
const currentUserId = userStore.currentUser?.id;
|
|
47
|
+
if (!projectId || !chatSessionId) return [];
|
|
48
|
+
const status = presenceState.statuses.get(projectId);
|
|
49
|
+
if (!status?.chatSessionUsers) return [];
|
|
50
|
+
const users = status.chatSessionUsers[chatSessionId] || [];
|
|
51
|
+
return currentUserId ? users.filter(u => u.userId !== currentUserId) : users;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Chat session users popover
|
|
55
|
+
let showChatUsersPopover = $state(false);
|
|
56
|
+
let chatUsersContainer = $state<HTMLDivElement | null>(null);
|
|
57
|
+
|
|
58
|
+
function toggleChatUsersPopover(e: MouseEvent) {
|
|
59
|
+
e.stopPropagation();
|
|
60
|
+
showChatUsersPopover = !showChatUsersPopover;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
$effect(() => {
|
|
64
|
+
if (showChatUsersPopover) {
|
|
65
|
+
const handleClickOutside = (e: MouseEvent) => {
|
|
66
|
+
if (chatUsersContainer && !chatUsersContainer.contains(e.target as Node)) {
|
|
67
|
+
showChatUsersPopover = false;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
document.addEventListener('click', handleClickOutside, true);
|
|
71
|
+
return () => document.removeEventListener('click', handleClickOutside, true);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Preview panel device dropdown state
|
|
76
|
+
let showDeviceDropdown = $state(false);
|
|
77
|
+
|
|
78
|
+
// Git remote dropdown state
|
|
79
|
+
let showRemoteDropdown = $state(false);
|
|
80
|
+
|
|
81
|
+
function toggleDeviceDropdown() {
|
|
82
|
+
showDeviceDropdown = !showDeviceDropdown;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function closeDeviceDropdown() {
|
|
86
|
+
showDeviceDropdown = false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function selectDevice(size: DeviceSize) {
|
|
90
|
+
previewPanelRef?.panelActions?.setDeviceSize(size);
|
|
91
|
+
closeDeviceDropdown();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function handleResize() {
|
|
95
|
+
if (browser) {
|
|
96
|
+
isMobile = window.innerWidth < 1024;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
onMount(() => {
|
|
101
|
+
handleResize();
|
|
102
|
+
if (browser) {
|
|
103
|
+
window.addEventListener('resize', handleResize);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
onDestroy(() => {
|
|
108
|
+
if (browser) {
|
|
109
|
+
window.removeEventListener('resize', handleResize);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
</script>
|
|
113
|
+
|
|
114
|
+
<header
|
|
115
|
+
class="flex items-center justify-between shrink-0 {isMobile
|
|
116
|
+
? 'h-11 pb-2 px-4 bg-white/90 dark:bg-slate-900/98 border-b border-slate-200 dark:border-slate-800'
|
|
117
|
+
: 'py-2.5 px-3.5 bg-slate-100 dark:bg-slate-800/80 border-b border-slate-200 dark:border-slate-800'}"
|
|
118
|
+
>
|
|
119
|
+
<div class="flex items-center gap-2.5 text-sm font-medium text-slate-900 dark:text-slate-100">
|
|
120
|
+
<Icon name={iconName} class="w-4 h-4 text-violet-600" />
|
|
121
|
+
<span>{panel?.title ?? 'Panel'}</span>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div class="flex items-center">
|
|
125
|
+
<!-- Panel-specific actions -->
|
|
126
|
+
<div class="flex items-center gap-1.5">
|
|
127
|
+
{#if panelId === 'chat'}
|
|
128
|
+
{#if chatSessionUsers.length > 0}
|
|
129
|
+
<div class="relative" bind:this={chatUsersContainer}>
|
|
130
|
+
<div class="flex items-center -space-x-1.5 mr-1 cursor-pointer" title="Users in this session" onclick={toggleChatUsersPopover}>
|
|
131
|
+
{#each chatSessionUsers.slice(0, 3) as user}
|
|
132
|
+
<AvatarBubble {user} size="sm" />
|
|
133
|
+
{/each}
|
|
134
|
+
{#if chatSessionUsers.length > 3}
|
|
135
|
+
<span class="w-5 h-5 rounded-full bg-gradient-to-br from-slate-500 to-slate-600 dark:from-slate-600 dark:to-slate-700 text-white text-4xs font-bold flex items-center justify-center border-2 border-white dark:border-slate-800 z-10">
|
|
136
|
+
+{chatSessionUsers.length - 3}
|
|
137
|
+
</span>
|
|
138
|
+
{/if}
|
|
139
|
+
</div>
|
|
140
|
+
{#if showChatUsersPopover}
|
|
141
|
+
<div class="absolute top-full right-0 mt-2 py-2 px-1 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg shadow-lg z-50 min-w-[160px]">
|
|
142
|
+
<div class="px-2 pb-1.5 text-left text-xs font-semibold text-slate-500 dark:text-slate-400">
|
|
143
|
+
In this session ({chatSessionUsers.length})
|
|
144
|
+
</div>
|
|
145
|
+
{#each chatSessionUsers as user}
|
|
146
|
+
<div class="flex items-center gap-2 px-2 py-1.5 rounded-md">
|
|
147
|
+
<AvatarBubble {user} size="sm" showName={true} />
|
|
148
|
+
</div>
|
|
149
|
+
{/each}
|
|
150
|
+
</div>
|
|
151
|
+
{/if}
|
|
152
|
+
</div>
|
|
153
|
+
{/if}
|
|
154
|
+
<button
|
|
155
|
+
type="button"
|
|
156
|
+
class="flex items-center justify-center {isMobile ? 'w-9 h-9' : 'w-6 h-6'} bg-transparent border-none rounded-md text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
|
|
157
|
+
onclick={onHistoryOpen}
|
|
158
|
+
title="Switch Session"
|
|
159
|
+
>
|
|
160
|
+
<Icon name="lucide:history" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
161
|
+
</button>
|
|
162
|
+
{#if sessionState.messages.length > 0}
|
|
163
|
+
<button
|
|
164
|
+
type="button"
|
|
165
|
+
class="flex items-center justify-center {isMobile ? 'w-9 h-9' : 'w-6 h-6'} bg-transparent border-none rounded-md text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
|
|
166
|
+
onclick={() => chatPanelRef?.panelActions?.checkpoints()}
|
|
167
|
+
title="Restore Checkpoint"
|
|
168
|
+
>
|
|
169
|
+
<Icon name="lucide:undo-2" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
170
|
+
</button>
|
|
171
|
+
{/if}
|
|
172
|
+
<button
|
|
173
|
+
type="button"
|
|
174
|
+
class="flex items-center justify-center {isMobile ? 'w-9 h-9' : 'w-6 h-6'} bg-transparent border-none rounded-md text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
|
|
175
|
+
onclick={() => chatPanelRef?.panelActions?.newChat()}
|
|
176
|
+
title="New Chat"
|
|
177
|
+
>
|
|
178
|
+
<Icon name="lucide:plus" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
179
|
+
</button>
|
|
180
|
+
{:else if panelId === 'files'}
|
|
181
|
+
<!-- Hide view mode toggles when in two-column mode -->
|
|
182
|
+
{#if !filesPanelRef?.panelActions?.isTwoColumnMode()}
|
|
183
|
+
<div class="flex gap-1 bg-slate-100/80 dark:bg-slate-800/50 rounded-md">
|
|
184
|
+
<button
|
|
185
|
+
type="button"
|
|
186
|
+
class="flex items-center justify-center {isMobile ? 'w-9 h-9' : 'w-6 h-6'} bg-transparent border-none rounded text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100 disabled:opacity-40 disabled:cursor-not-allowed
|
|
187
|
+
{filesPanelRef?.panelActions?.getViewMode() === 'tree'
|
|
188
|
+
? 'bg-violet-500/15 dark:bg-violet-500/25 text-violet-600'
|
|
189
|
+
: ''}"
|
|
190
|
+
onclick={() => filesPanelRef?.panelActions?.setViewMode('tree')}
|
|
191
|
+
title="Tree View"
|
|
192
|
+
>
|
|
193
|
+
<Icon name="lucide:folder-tree" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
194
|
+
</button>
|
|
195
|
+
<button
|
|
196
|
+
type="button"
|
|
197
|
+
class="flex items-center justify-center {isMobile ? 'w-9 h-9' : 'w-6 h-6'} bg-transparent border-none rounded text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100 disabled:opacity-40 disabled:cursor-not-allowed
|
|
198
|
+
{filesPanelRef?.panelActions?.getViewMode() === 'viewer'
|
|
199
|
+
? 'bg-violet-500/15 dark:bg-violet-500/25 text-violet-600'
|
|
200
|
+
: ''}"
|
|
201
|
+
onclick={() => filesPanelRef?.panelActions?.setViewMode('viewer')}
|
|
202
|
+
disabled={!filesPanelRef?.panelActions?.canShowViewer()}
|
|
203
|
+
title="File Viewer"
|
|
204
|
+
>
|
|
205
|
+
<Icon name="lucide:file-code" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
206
|
+
</button>
|
|
207
|
+
</div>
|
|
208
|
+
{/if}
|
|
209
|
+
{:else if panelId === 'terminal'}
|
|
210
|
+
<button
|
|
211
|
+
type="button"
|
|
212
|
+
class="flex items-center justify-center {isMobile ? 'w-9 h-9' : 'w-6 h-6'} bg-transparent border-none rounded-md text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
|
|
213
|
+
onclick={() => terminalPanelRef?.panelActions?.handleClear()}
|
|
214
|
+
title="Clear Terminal"
|
|
215
|
+
>
|
|
216
|
+
<Icon name="lucide:trash-2" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
217
|
+
</button>
|
|
218
|
+
{#if !isMobile || terminalPanelRef?.panelActions?.isExecuting()}
|
|
219
|
+
<button
|
|
220
|
+
type="button"
|
|
221
|
+
class="flex items-center justify-center {isMobile ? 'w-9 h-9' : 'w-6 h-6'} bg-transparent border-none rounded-md {isMobile ? 'text-red-600 dark:text-red-400' : 'text-slate-500'} cursor-pointer transition-all duration-150 hover:bg-red-500/10 hover:text-red-600 dark:hover:text-red-400 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
222
|
+
onclick={() => terminalPanelRef?.panelActions?.handleCancel()}
|
|
223
|
+
disabled={terminalPanelRef?.panelActions?.isCancelling()}
|
|
224
|
+
title="{isMobile ? 'Cancel Command (Ctrl+C)' : 'Send Ctrl+C Signal'}"
|
|
225
|
+
>
|
|
226
|
+
{#if terminalPanelRef?.panelActions?.isCancelling()}
|
|
227
|
+
<div
|
|
228
|
+
class="{isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} border-2 border-red-500/20 border-t-red-600 rounded-full animate-spin"
|
|
229
|
+
></div>
|
|
230
|
+
{:else}
|
|
231
|
+
<Icon name="lucide:square" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
232
|
+
{/if}
|
|
233
|
+
</button>
|
|
234
|
+
{/if}
|
|
235
|
+
{:else if panelId === 'preview'}
|
|
236
|
+
<!-- Connection status indicator -->
|
|
237
|
+
<!-- {@const sessionInfo = previewPanelRef?.panelActions?.getSessionInfo()}
|
|
238
|
+
{@const isStreamReady = previewPanelRef?.panelActions?.getIsStreamReady()}
|
|
239
|
+
{@const errorMessage = previewPanelRef?.panelActions?.getErrorMessage()}
|
|
240
|
+
{@const url = previewPanelRef?.panelActions?.getUrl()}
|
|
241
|
+
|
|
242
|
+
{#if url}
|
|
243
|
+
<div class="flex items-center gap-1.5 {isMobile ? 'px-2 h-9' : 'px-2 h-6'} rounded-md">
|
|
244
|
+
<div class="relative">
|
|
245
|
+
{#if !sessionInfo}
|
|
246
|
+
<span class="w-2 h-2 rounded-full block bg-amber-400"></span>
|
|
247
|
+
{:else if errorMessage}
|
|
248
|
+
<span class="w-2 h-2 rounded-full block bg-red-500"></span>
|
|
249
|
+
{:else if isStreamReady}
|
|
250
|
+
<span class="w-2 h-2 rounded-full block bg-emerald-500"></span>
|
|
251
|
+
<span class="absolute inset-0 w-2 h-2 bg-emerald-500 rounded-full animate-ping opacity-75"></span>
|
|
252
|
+
{:else}
|
|
253
|
+
<span class="w-2 h-2 rounded-full block bg-blue-400 animate-pulse"></span>
|
|
254
|
+
{/if}
|
|
255
|
+
</div>
|
|
256
|
+
<span class="text-xs font-medium {
|
|
257
|
+
!sessionInfo ? 'text-amber-600 dark:text-amber-400' :
|
|
258
|
+
errorMessage ? 'text-red-600 dark:text-red-400' :
|
|
259
|
+
isStreamReady ? 'text-emerald-600 dark:text-emerald-400' :
|
|
260
|
+
'text-blue-600 dark:text-blue-400'
|
|
261
|
+
}">
|
|
262
|
+
{!sessionInfo ? 'Ready' : errorMessage ? 'Offline' : isStreamReady ? 'Online' : 'Connecting'}
|
|
263
|
+
</span>
|
|
264
|
+
</div>
|
|
265
|
+
{/if} -->
|
|
266
|
+
|
|
267
|
+
<!-- Device size dropdown -->
|
|
268
|
+
<div class="relative {isMobile ? '' : 'mr-1.5'}">
|
|
269
|
+
<button
|
|
270
|
+
type="button"
|
|
271
|
+
class="flex items-center justify-center gap-1.5 {isMobile ? 'px-2 h-9' : 'px-1 h-6'} bg-transparent border-none rounded-md text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
|
|
272
|
+
onclick={toggleDeviceDropdown}
|
|
273
|
+
title="Select device size"
|
|
274
|
+
>
|
|
275
|
+
{#if previewPanelRef?.panelActions?.getDeviceSize() === 'desktop'}
|
|
276
|
+
<Icon name="lucide:monitor" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
277
|
+
<span class="text-xs font-medium">Desktop</span>
|
|
278
|
+
{:else if previewPanelRef?.panelActions?.getDeviceSize() === 'laptop'}
|
|
279
|
+
<Icon name="lucide:laptop" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
280
|
+
<span class="text-xs font-medium">Laptop</span>
|
|
281
|
+
{:else if previewPanelRef?.panelActions?.getDeviceSize() === 'tablet'}
|
|
282
|
+
<Icon name="lucide:tablet" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
283
|
+
<span class="text-xs font-medium">Tablet</span>
|
|
284
|
+
{:else}
|
|
285
|
+
<Icon name="lucide:smartphone" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
286
|
+
<span class="text-xs font-medium">Mobile</span>
|
|
287
|
+
{/if}
|
|
288
|
+
<Icon name="lucide:chevron-down" class={isMobile ? 'w-3.5 h-3.5' : 'w-3 h-3'} />
|
|
289
|
+
</button>
|
|
290
|
+
|
|
291
|
+
<!-- Dropdown menu -->
|
|
292
|
+
{#if showDeviceDropdown}
|
|
293
|
+
<div
|
|
294
|
+
class="fixed inset-0 z-40"
|
|
295
|
+
onclick={closeDeviceDropdown}
|
|
296
|
+
></div>
|
|
297
|
+
<div class="absolute top-full right-0 mt-1 z-50 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg shadow-lg overflow-hidden {isMobile ? 'min-w-44' : 'min-w-40'}">
|
|
298
|
+
<button
|
|
299
|
+
type="button"
|
|
300
|
+
class="flex items-center gap-2.5 w-full px-3 {isMobile ? 'py-2.5' : 'py-2'} text-left text-sm bg-transparent border-none cursor-pointer transition-all duration-150 hover:bg-violet-500/10 {previewPanelRef?.panelActions?.getDeviceSize() === 'desktop' ? 'bg-violet-500/5 text-violet-600' : 'text-slate-700 dark:text-slate-300'}"
|
|
301
|
+
onclick={() => selectDevice('desktop')}
|
|
302
|
+
>
|
|
303
|
+
<Icon name="lucide:monitor" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
304
|
+
<div class="flex-1">
|
|
305
|
+
<div class="font-medium">Desktop</div>
|
|
306
|
+
<div class="text-xs text-slate-500 dark:text-slate-400">
|
|
307
|
+
{DEVICE_VIEWPORTS.desktop.width}×{DEVICE_VIEWPORTS.desktop.height}
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
{#if previewPanelRef?.panelActions?.getDeviceSize() === 'desktop'}
|
|
311
|
+
<Icon name="lucide:check" class="{isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} text-violet-600" />
|
|
312
|
+
{/if}
|
|
313
|
+
</button>
|
|
314
|
+
<button
|
|
315
|
+
type="button"
|
|
316
|
+
class="flex items-center gap-2.5 w-full px-3 {isMobile ? 'py-2.5' : 'py-2'} text-left text-sm bg-transparent border-none cursor-pointer transition-all duration-150 hover:bg-violet-500/10 {previewPanelRef?.panelActions?.getDeviceSize() === 'laptop' ? 'bg-violet-500/5 text-violet-600' : 'text-slate-700 dark:text-slate-300'}"
|
|
317
|
+
onclick={() => selectDevice('laptop')}
|
|
318
|
+
>
|
|
319
|
+
<Icon name="lucide:laptop" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
320
|
+
<div class="flex-1">
|
|
321
|
+
<div class="font-medium">Laptop</div>
|
|
322
|
+
<div class="text-xs text-slate-500 dark:text-slate-400">
|
|
323
|
+
{DEVICE_VIEWPORTS.laptop.width}×{DEVICE_VIEWPORTS.laptop.height}
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
{#if previewPanelRef?.panelActions?.getDeviceSize() === 'laptop'}
|
|
327
|
+
<Icon name="lucide:check" class="{isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} text-violet-600" />
|
|
328
|
+
{/if}
|
|
329
|
+
</button>
|
|
330
|
+
<button
|
|
331
|
+
type="button"
|
|
332
|
+
class="flex items-center gap-2.5 w-full px-3 {isMobile ? 'py-2.5' : 'py-2'} text-left text-sm bg-transparent border-none cursor-pointer transition-all duration-150 hover:bg-violet-500/10 {previewPanelRef?.panelActions?.getDeviceSize() === 'tablet' ? 'bg-violet-500/5 text-violet-600' : 'text-slate-700 dark:text-slate-300'}"
|
|
333
|
+
onclick={() => selectDevice('tablet')}
|
|
334
|
+
>
|
|
335
|
+
<Icon name="lucide:tablet" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
336
|
+
<div class="flex-1">
|
|
337
|
+
<div class="font-medium">Tablet</div>
|
|
338
|
+
<div class="text-xs text-slate-500 dark:text-slate-400">
|
|
339
|
+
{DEVICE_VIEWPORTS.tablet.width}×{DEVICE_VIEWPORTS.tablet.height}
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
{#if previewPanelRef?.panelActions?.getDeviceSize() === 'tablet'}
|
|
343
|
+
<Icon name="lucide:check" class="{isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} text-violet-600" />
|
|
344
|
+
{/if}
|
|
345
|
+
</button>
|
|
346
|
+
<button
|
|
347
|
+
type="button"
|
|
348
|
+
class="flex items-center gap-2.5 w-full px-3 {isMobile ? 'py-2.5' : 'py-2'} text-left text-sm bg-transparent border-none cursor-pointer transition-all duration-150 hover:bg-violet-500/10 {previewPanelRef?.panelActions?.getDeviceSize() === 'mobile' ? 'bg-violet-500/5 text-violet-600' : 'text-slate-700 dark:text-slate-300'}"
|
|
349
|
+
onclick={() => selectDevice('mobile')}
|
|
350
|
+
>
|
|
351
|
+
<Icon name="lucide:smartphone" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
352
|
+
<div class="flex-1">
|
|
353
|
+
<div class="font-medium">Mobile</div>
|
|
354
|
+
<div class="text-xs text-slate-500 dark:text-slate-400">
|
|
355
|
+
{DEVICE_VIEWPORTS.mobile.width}×{DEVICE_VIEWPORTS.mobile.height}
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
{#if previewPanelRef?.panelActions?.getDeviceSize() === 'mobile'}
|
|
359
|
+
<Icon name="lucide:check" class="{isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} text-violet-600" />
|
|
360
|
+
{/if}
|
|
361
|
+
</button>
|
|
362
|
+
</div>
|
|
363
|
+
{/if}
|
|
364
|
+
</div>
|
|
365
|
+
|
|
366
|
+
<!-- Rotation toggle -->
|
|
367
|
+
<button
|
|
368
|
+
type="button"
|
|
369
|
+
class="flex items-center justify-center gap-1.5 {isMobile ? 'px-2 h-9' : 'px-1 h-6'} bg-transparent border-none rounded-md text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
|
|
370
|
+
onclick={() => previewPanelRef?.panelActions?.toggleRotation()}
|
|
371
|
+
title="Toggle orientation"
|
|
372
|
+
>
|
|
373
|
+
<Icon name="lucide:rotate-cw" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
374
|
+
<span class="text-xs font-medium">
|
|
375
|
+
{previewPanelRef?.panelActions?.getRotation() === 'portrait' ? 'Portrait' : 'Landscape'}
|
|
376
|
+
</span>
|
|
377
|
+
</button>
|
|
378
|
+
|
|
379
|
+
<!-- Scale info badge -->
|
|
380
|
+
<div class="flex items-center gap-1.5 {isMobile ? 'px-2.5 h-9 bg-transparent' : 'px-2 h-6 bg-slate-100/60 dark:bg-slate-800/40'} rounded-md text-xs font-medium text-slate-500">
|
|
381
|
+
<Icon name="lucide:move-diagonal" class={isMobile ? 'w-4 h-4' : 'w-3.5 h-3.5'} />
|
|
382
|
+
<span>{Math.round((previewPanelRef?.panelActions?.getScale() || 1) * 100)}%</span>
|
|
383
|
+
</div>
|
|
384
|
+
{:else if panelId === 'git'}
|
|
385
|
+
<!-- View mode toggles (only in single-column mode, like Files panel) -->
|
|
386
|
+
{#if !gitPanelRef?.panelActions?.isTwoColumnMode()}
|
|
387
|
+
<div class="flex gap-1 bg-slate-100/80 dark:bg-slate-800/50 rounded-md">
|
|
388
|
+
<button
|
|
389
|
+
type="button"
|
|
390
|
+
class="flex items-center justify-center {isMobile ? 'w-9 h-9' : 'w-6 h-6'} bg-transparent border-none rounded text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100
|
|
391
|
+
{gitPanelRef?.panelActions?.getViewMode() === 'list'
|
|
392
|
+
? 'bg-violet-500/15 dark:bg-violet-500/25 text-violet-600'
|
|
393
|
+
: ''}"
|
|
394
|
+
onclick={() => gitPanelRef?.panelActions?.setViewMode('list')}
|
|
395
|
+
title="Changes List"
|
|
396
|
+
>
|
|
397
|
+
<Icon name="lucide:list" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
398
|
+
</button>
|
|
399
|
+
<button
|
|
400
|
+
type="button"
|
|
401
|
+
class="flex items-center justify-center {isMobile ? 'w-9 h-9' : 'w-6 h-6'} bg-transparent border-none rounded text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100 disabled:opacity-40 disabled:cursor-not-allowed
|
|
402
|
+
{gitPanelRef?.panelActions?.getViewMode() === 'diff'
|
|
403
|
+
? 'bg-violet-500/15 dark:bg-violet-500/25 text-violet-600'
|
|
404
|
+
: ''}"
|
|
405
|
+
onclick={() => gitPanelRef?.panelActions?.setViewMode('diff')}
|
|
406
|
+
disabled={!gitPanelRef?.panelActions?.canShowDiff()}
|
|
407
|
+
title="Diff Viewer"
|
|
408
|
+
>
|
|
409
|
+
<Icon name="lucide:file-diff" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
410
|
+
</button>
|
|
411
|
+
</div>
|
|
412
|
+
{/if}
|
|
413
|
+
{@const hasRemotes = gitPanelRef?.panelActions?.getHasRemotes()}
|
|
414
|
+
{@const remoteName = gitPanelRef?.panelActions?.getSelectedRemote() || 'origin'}
|
|
415
|
+
{@const gitRemotes = gitPanelRef?.panelActions?.getRemotes() || []}
|
|
416
|
+
|
|
417
|
+
<!-- Remote selector -->
|
|
418
|
+
{#if hasRemotes}
|
|
419
|
+
<div class="relative">
|
|
420
|
+
<button
|
|
421
|
+
type="button"
|
|
422
|
+
class="flex items-center gap-1 {isMobile ? 'h-9 px-2' : 'h-6 px-1.5'} bg-transparent border-none rounded-md text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
|
|
423
|
+
onclick={() => showRemoteDropdown = !showRemoteDropdown}
|
|
424
|
+
title="Select remote"
|
|
425
|
+
>
|
|
426
|
+
<Icon name="lucide:globe" class={isMobile ? 'w-4 h-4' : 'w-3.5 h-3.5'} />
|
|
427
|
+
<span class="text-xs font-medium">{remoteName}</span>
|
|
428
|
+
{#if gitRemotes.length > 1}
|
|
429
|
+
<Icon name="lucide:chevron-down" class="w-3 h-3" />
|
|
430
|
+
{/if}
|
|
431
|
+
</button>
|
|
432
|
+
|
|
433
|
+
{#if showRemoteDropdown && gitRemotes.length > 1}
|
|
434
|
+
<div class="fixed inset-0 z-40" onclick={() => showRemoteDropdown = false}></div>
|
|
435
|
+
<div class="absolute top-full right-0 mt-1 z-50 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg shadow-lg overflow-hidden min-w-36">
|
|
436
|
+
{#each gitRemotes as remote (remote.name)}
|
|
437
|
+
<button
|
|
438
|
+
type="button"
|
|
439
|
+
class="flex items-center gap-2 w-full px-3 py-2 text-left text-xs bg-transparent border-none cursor-pointer transition-all duration-150 hover:bg-violet-500/10
|
|
440
|
+
{remote.name === remoteName ? 'text-violet-600 font-medium' : 'text-slate-700 dark:text-slate-300'}"
|
|
441
|
+
onclick={() => { gitPanelRef?.panelActions?.setSelectedRemote(remote.name); showRemoteDropdown = false; }}
|
|
442
|
+
>
|
|
443
|
+
<Icon name="lucide:globe" class="w-3.5 h-3.5 shrink-0" />
|
|
444
|
+
<div class="flex-1 min-w-0">
|
|
445
|
+
<div>{remote.name}</div>
|
|
446
|
+
<div class="text-[10px] text-slate-400 truncate font-mono">{remote.fetchUrl}</div>
|
|
447
|
+
</div>
|
|
448
|
+
{#if remote.name === remoteName}
|
|
449
|
+
<Icon name="lucide:check" class="w-3.5 h-3.5 text-violet-600 shrink-0" />
|
|
450
|
+
{/if}
|
|
451
|
+
</button>
|
|
452
|
+
{/each}
|
|
453
|
+
</div>
|
|
454
|
+
{/if}
|
|
455
|
+
</div>
|
|
456
|
+
{:else if gitPanelRef?.panelActions?.getIsRepo()}
|
|
457
|
+
<span class="text-xs text-slate-400 italic px-1">no remote</span>
|
|
458
|
+
{/if}
|
|
459
|
+
|
|
460
|
+
<!-- Fetch -->
|
|
461
|
+
<button
|
|
462
|
+
type="button"
|
|
463
|
+
class="flex items-center justify-center gap-1 {isMobile ? 'h-9 px-2' : 'h-6 px-1.5'} bg-transparent border-none rounded-md text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
464
|
+
onclick={() => gitPanelRef?.panelActions?.fetch()}
|
|
465
|
+
disabled={gitPanelRef?.panelActions?.getIsFetching() || !hasRemotes}
|
|
466
|
+
title={hasRemotes ? `Fetch from ${remoteName}` : 'No remote configured'}
|
|
467
|
+
>
|
|
468
|
+
{#if gitPanelRef?.panelActions?.getIsFetching()}
|
|
469
|
+
<div class="{isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} border-2 border-slate-300/30 border-t-slate-500 rounded-full animate-spin"></div>
|
|
470
|
+
{:else}
|
|
471
|
+
<Icon name="lucide:cloud-download" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
472
|
+
{/if}
|
|
473
|
+
</button>
|
|
474
|
+
<!-- Pull -->
|
|
475
|
+
<button
|
|
476
|
+
type="button"
|
|
477
|
+
class="flex items-center justify-center gap-1 {isMobile ? 'h-9 px-2' : 'h-6 px-1.5'} bg-transparent border-none rounded-md text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
478
|
+
onclick={() => gitPanelRef?.panelActions?.pull()}
|
|
479
|
+
disabled={gitPanelRef?.panelActions?.getIsPulling() || !hasRemotes}
|
|
480
|
+
title={hasRemotes ? `Pull from ${remoteName}` : 'No remote configured'}
|
|
481
|
+
>
|
|
482
|
+
{#if gitPanelRef?.panelActions?.getIsPulling()}
|
|
483
|
+
<div class="{isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} border-2 border-slate-300/30 border-t-slate-500 rounded-full animate-spin"></div>
|
|
484
|
+
{:else}
|
|
485
|
+
<Icon name="lucide:arrow-down-to-line" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
486
|
+
{/if}
|
|
487
|
+
</button>
|
|
488
|
+
<!-- Push -->
|
|
489
|
+
<button
|
|
490
|
+
type="button"
|
|
491
|
+
class="flex items-center justify-center gap-1 {isMobile ? 'h-9 px-2' : 'h-6 px-1.5'} bg-transparent border-none rounded-md text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
492
|
+
onclick={() => gitPanelRef?.panelActions?.push()}
|
|
493
|
+
disabled={gitPanelRef?.panelActions?.getIsPushing() || !hasRemotes}
|
|
494
|
+
title={hasRemotes ? `Push to ${remoteName}` : 'No remote configured'}
|
|
495
|
+
>
|
|
496
|
+
{#if gitPanelRef?.panelActions?.getIsPushing()}
|
|
497
|
+
<div class="{isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} border-2 border-slate-300/30 border-t-slate-500 rounded-full animate-spin"></div>
|
|
498
|
+
{:else}
|
|
499
|
+
<Icon name="lucide:arrow-up-from-line" class={isMobile ? 'w-4.5 h-4.5' : 'w-4 h-4'} />
|
|
500
|
+
{/if}
|
|
501
|
+
</button>
|
|
502
|
+
{/if}
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
</header>
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { fade, scale } from 'svelte/transition';
|
|
3
|
+
import { cubicOut } from 'svelte/easing';
|
|
4
|
+
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
5
|
+
import LayoutPreview from '$frontend/lib/components/settings/appearance/LayoutPreview.svelte';
|
|
6
|
+
import {
|
|
7
|
+
workspaceState,
|
|
8
|
+
builtInPresets,
|
|
9
|
+
applyLayoutPreset,
|
|
10
|
+
type LayoutPreset
|
|
11
|
+
} from '$frontend/lib/stores/ui/workspace.svelte';
|
|
12
|
+
import { settings } from '$frontend/lib/stores/features/settings.svelte';
|
|
13
|
+
import { clickOutside } from '$frontend/lib/utils/click-outside';
|
|
14
|
+
import type { IconName } from '$shared/types/ui/icons';
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
collapsed?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { collapsed = false }: Props = $props();
|
|
21
|
+
|
|
22
|
+
let isOpen = $state(false);
|
|
23
|
+
|
|
24
|
+
const isCollapsed = $derived(collapsed || workspaceState.navigatorCollapsed);
|
|
25
|
+
|
|
26
|
+
// Group presets by category
|
|
27
|
+
const presetCategories = [
|
|
28
|
+
{
|
|
29
|
+
name: 'Single Panel',
|
|
30
|
+
presets: builtInPresets.slice(0, 5)
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'Two Panels',
|
|
34
|
+
presets: builtInPresets.slice(5, 13)
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'Three Panels',
|
|
38
|
+
presets: builtInPresets.slice(13, 22)
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'Four Panels',
|
|
42
|
+
presets: builtInPresets.slice(22, 26)
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'Five Panels',
|
|
46
|
+
presets: builtInPresets.slice(26, 28)
|
|
47
|
+
}
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
// Filter visible presets and categories
|
|
51
|
+
const visibleCategories = $derived(
|
|
52
|
+
presetCategories
|
|
53
|
+
.map((category) => ({
|
|
54
|
+
...category,
|
|
55
|
+
presets: category.presets.filter(
|
|
56
|
+
(preset) => settings.layoutPresetVisibility[preset.id] !== false
|
|
57
|
+
)
|
|
58
|
+
}))
|
|
59
|
+
.filter((category) => category.presets.length > 0)
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
function toggleMenu() {
|
|
63
|
+
isOpen = !isOpen;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function handleApplyPreset(preset: LayoutPreset) {
|
|
67
|
+
applyLayoutPreset(preset);
|
|
68
|
+
isOpen = false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function handleClickOutside() {
|
|
72
|
+
isOpen = false;
|
|
73
|
+
}
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<div class="relative" use:clickOutside={handleClickOutside}>
|
|
77
|
+
{#if isCollapsed}
|
|
78
|
+
<!-- Collapsed: Icon Only -->
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
class="flex items-center justify-center w-9 h-9 bg-transparent border-none rounded-lg text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100
|
|
82
|
+
{isOpen ? 'bg-violet-500/10 text-slate-900 dark:text-slate-100' : ''}"
|
|
83
|
+
onclick={toggleMenu}
|
|
84
|
+
aria-label="Layout Presets"
|
|
85
|
+
aria-expanded={isOpen}
|
|
86
|
+
title="Layout Presets"
|
|
87
|
+
>
|
|
88
|
+
<Icon name="lucide:layout-grid" class="w-5 h-5" />
|
|
89
|
+
</button>
|
|
90
|
+
{:else}
|
|
91
|
+
<!-- Expanded: Full Width -->
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
class="flex items-center gap-2.5 w-full py-2.5 px-3 bg-transparent border-none rounded-lg text-slate-500 text-sm cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100
|
|
95
|
+
{isOpen ? 'bg-violet-500/10 text-slate-900 dark:text-slate-100' : ''}"
|
|
96
|
+
onclick={toggleMenu}
|
|
97
|
+
aria-label="Layout Presets"
|
|
98
|
+
aria-expanded={isOpen}
|
|
99
|
+
>
|
|
100
|
+
<Icon name="lucide:layout-grid" class="w-4 h-4" />
|
|
101
|
+
<span class="flex-1 text-left">Layout Presets</span>
|
|
102
|
+
</button>
|
|
103
|
+
{/if}
|
|
104
|
+
|
|
105
|
+
{#if isOpen}
|
|
106
|
+
<div
|
|
107
|
+
class="absolute bottom-full left-0 mb-1 w-[280px] bg-white dark:bg-slate-800 border border-violet-500/20 rounded-lg shadow-2xl shadow-slate-900/20 dark:shadow-black/40 z-50 overflow-hidden"
|
|
108
|
+
transition:scale={{ duration: 150, easing: cubicOut, start: 0.95, opacity: 0 }}
|
|
109
|
+
>
|
|
110
|
+
<div class="py-1.5 max-h-[32rem] overflow-y-auto">
|
|
111
|
+
<!-- Layout Presets Header -->
|
|
112
|
+
<div
|
|
113
|
+
class="px-3 py-1.5 text-xs font-semibold text-slate-600 dark:text-slate-500 uppercase tracking-wider"
|
|
114
|
+
>
|
|
115
|
+
Layout Presets
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<!-- Categories -->
|
|
119
|
+
{#each visibleCategories as category, categoryIndex}
|
|
120
|
+
<!-- Category Name -->
|
|
121
|
+
<div
|
|
122
|
+
class="px-3 py-1.5 mt-2 text-xs font-medium text-violet-600 dark:text-violet-400 uppercase tracking-wide"
|
|
123
|
+
>
|
|
124
|
+
{category.name}
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<!-- Presets in Category - 2 Column Grid -->
|
|
128
|
+
<div class="grid grid-cols-2 gap-2 px-3">
|
|
129
|
+
{#each category.presets as preset}
|
|
130
|
+
<button
|
|
131
|
+
type="button"
|
|
132
|
+
class="flex items-center p-2.5 bg-transparent border border-slate-200 dark:border-slate-800 rounded-lg text-slate-700 dark:text-slate-300 text-sm text-left cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:border-violet-500/20 {workspaceState.activePresetId ===
|
|
133
|
+
preset.id
|
|
134
|
+
? 'bg-violet-500/5 border-violet-500/30'
|
|
135
|
+
: ''}"
|
|
136
|
+
onclick={() => handleApplyPreset(preset)}
|
|
137
|
+
>
|
|
138
|
+
<div class="flex flex-col gap-1 flex-1 min-w-0">
|
|
139
|
+
<div class="flex justify-between">
|
|
140
|
+
<span class="font-medium text-xs">{preset.name}</span>
|
|
141
|
+
{#if workspaceState.activePresetId === preset.id}
|
|
142
|
+
<Icon name="lucide:check" class="w-3.5 h-3.5 text-violet-600 dark:text-violet-400 shrink-0" />
|
|
143
|
+
{/if}
|
|
144
|
+
</div>
|
|
145
|
+
<!-- Visual Preview -->
|
|
146
|
+
<div class="w-full">
|
|
147
|
+
<LayoutPreview layout={preset.layout} size="small" />
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</button>
|
|
151
|
+
{/each}
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<!-- Divider (except for last category) -->
|
|
155
|
+
{#if categoryIndex < visibleCategories.length - 1}
|
|
156
|
+
<div class="my-2 mx-3 border-t border-slate-200 dark:border-slate-800"></div>
|
|
157
|
+
{/if}
|
|
158
|
+
{/each}
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
{/if}
|
|
162
|
+
</div>
|