@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,728 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { FileNode } from '$shared/types/filesystem';
|
|
3
|
+
import LoadingSpinner from '../common/LoadingSpinner.svelte';
|
|
4
|
+
import MonacoEditor from '../common/MonacoEditor.svelte';
|
|
5
|
+
import { themeStore } from '$frontend/lib/stores/ui/theme.svelte';
|
|
6
|
+
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
7
|
+
import { getFileIcon } from '$frontend/lib/utils/file-icon-mappings';
|
|
8
|
+
import { getFolderIcon } from '$frontend/lib/utils/folder-icon-mappings';
|
|
9
|
+
import { onMount, onDestroy } from 'svelte';
|
|
10
|
+
import type { IconName } from '$shared/types/ui/icons';
|
|
11
|
+
import type { editor } from 'monaco-editor';
|
|
12
|
+
import { debug } from '$shared/utils/logger';
|
|
13
|
+
import ws from '$frontend/lib/utils/ws';
|
|
14
|
+
|
|
15
|
+
// Interface untuk MonacoEditor component
|
|
16
|
+
interface MonacoEditorComponent {
|
|
17
|
+
getEditor: () => editor.IStandaloneCodeEditor | null;
|
|
18
|
+
getValue: () => string;
|
|
19
|
+
setValue: (newValue: string) => void;
|
|
20
|
+
getLanguage: () => string;
|
|
21
|
+
setLanguage: (newLanguage: string) => void;
|
|
22
|
+
detectLanguageFromFilename: (filename: string) => string;
|
|
23
|
+
focus: () => void;
|
|
24
|
+
layout: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface Props {
|
|
28
|
+
file: FileNode | null;
|
|
29
|
+
content?: string;
|
|
30
|
+
savedContent?: string;
|
|
31
|
+
isLoading?: boolean;
|
|
32
|
+
error?: string;
|
|
33
|
+
onSave?: (filePath: string, content: string) => Promise<void>;
|
|
34
|
+
hideHeader?: boolean;
|
|
35
|
+
targetLine?: number;
|
|
36
|
+
onContentChange?: (content: string) => void;
|
|
37
|
+
wordWrap?: boolean;
|
|
38
|
+
onToggleWordWrap?: () => void;
|
|
39
|
+
externallyChanged?: boolean;
|
|
40
|
+
onForceReload?: () => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const {
|
|
44
|
+
file = null,
|
|
45
|
+
content = '',
|
|
46
|
+
savedContent: savedContentProp,
|
|
47
|
+
isLoading = false,
|
|
48
|
+
error = '',
|
|
49
|
+
onSave,
|
|
50
|
+
hideHeader = false,
|
|
51
|
+
targetLine = undefined,
|
|
52
|
+
onContentChange,
|
|
53
|
+
wordWrap = false,
|
|
54
|
+
onToggleWordWrap,
|
|
55
|
+
externallyChanged = false,
|
|
56
|
+
onForceReload
|
|
57
|
+
}: Props = $props();
|
|
58
|
+
|
|
59
|
+
// Theme state
|
|
60
|
+
const isDark = $derived(themeStore.isDark);
|
|
61
|
+
const monacoTheme = $derived(isDark ? 'vs-dark' : 'vs-light');
|
|
62
|
+
// Force remount Monaco Editor when theme or file changes
|
|
63
|
+
const themeKey = $derived(`monaco-${monacoTheme}-${file?.path || ''}`);
|
|
64
|
+
|
|
65
|
+
// Edit state - always in edit mode (no toggle)
|
|
66
|
+
let editableContent = $state('');
|
|
67
|
+
let isSaving = $state(false);
|
|
68
|
+
let hasChanges = $state(false);
|
|
69
|
+
|
|
70
|
+
// Derived state for save button
|
|
71
|
+
const canSave = $derived(hasChanges && !isSaving && !!file && !!onSave);
|
|
72
|
+
const saveButtonDisabled = $derived(!canSave);
|
|
73
|
+
|
|
74
|
+
let monacoEditorRef: MonacoEditorComponent | null = $state(null);
|
|
75
|
+
|
|
76
|
+
// Line highlighting state
|
|
77
|
+
let currentDecorations: string[] = $state([]);
|
|
78
|
+
|
|
79
|
+
// Image/binary blob URL state
|
|
80
|
+
let blobUrl = $state<string | null>(null);
|
|
81
|
+
|
|
82
|
+
// SVG view mode
|
|
83
|
+
let svgViewMode = $state<'visual' | 'code'>('visual');
|
|
84
|
+
|
|
85
|
+
// PDF blob URL
|
|
86
|
+
let pdfBlobUrl = $state<string | null>(null);
|
|
87
|
+
|
|
88
|
+
// Container ref for measuring height
|
|
89
|
+
let containerRef = $state<HTMLDivElement | null>(null);
|
|
90
|
+
let editorHeight = $state('0px'); // Default height
|
|
91
|
+
|
|
92
|
+
// Update editor height based on container
|
|
93
|
+
function updateEditorHeight() {
|
|
94
|
+
if (containerRef) {
|
|
95
|
+
const rect = containerRef.getBoundingClientRect();
|
|
96
|
+
// Get actual available height
|
|
97
|
+
const availableHeight = rect.height;
|
|
98
|
+
if (availableHeight > 0) {
|
|
99
|
+
const adjustedHeight = availableHeight;
|
|
100
|
+
editorHeight = `${Math.max(200, adjustedHeight)}px`;
|
|
101
|
+
} else {
|
|
102
|
+
const viewportHeight = window.innerHeight;
|
|
103
|
+
const estimatedHeight = viewportHeight - 200;
|
|
104
|
+
editorHeight = `${Math.max(400, estimatedHeight)}px`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Update height on mount and resize
|
|
110
|
+
onMount(() => {
|
|
111
|
+
setTimeout(updateEditorHeight, 100);
|
|
112
|
+
|
|
113
|
+
window.addEventListener('resize', updateEditorHeight);
|
|
114
|
+
|
|
115
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
116
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
|
117
|
+
e.preventDefault();
|
|
118
|
+
if (canSave) {
|
|
119
|
+
saveChanges();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
125
|
+
|
|
126
|
+
if (containerRef && typeof ResizeObserver !== 'undefined') {
|
|
127
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
128
|
+
updateEditorHeight();
|
|
129
|
+
});
|
|
130
|
+
resizeObserver.observe(containerRef);
|
|
131
|
+
|
|
132
|
+
return () => {
|
|
133
|
+
resizeObserver.disconnect();
|
|
134
|
+
window.removeEventListener('resize', updateEditorHeight);
|
|
135
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return () => {
|
|
140
|
+
window.removeEventListener('resize', updateEditorHeight);
|
|
141
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Cleanup blob URLs on destroy
|
|
146
|
+
onDestroy(() => {
|
|
147
|
+
if (blobUrl) {
|
|
148
|
+
URL.revokeObjectURL(blobUrl);
|
|
149
|
+
}
|
|
150
|
+
if (pdfBlobUrl) {
|
|
151
|
+
URL.revokeObjectURL(pdfBlobUrl);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Update height when container ref changes
|
|
156
|
+
$effect(() => {
|
|
157
|
+
if (containerRef) {
|
|
158
|
+
updateEditorHeight();
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
// Load binary content (images, PDF) via WebSocket when file changes
|
|
164
|
+
$effect(() => {
|
|
165
|
+
if (file && (isImageFile(file.name) || isPdfFile(file.name))) {
|
|
166
|
+
loadBinaryContent();
|
|
167
|
+
} else {
|
|
168
|
+
// Cleanup if not binary
|
|
169
|
+
if (blobUrl) {
|
|
170
|
+
URL.revokeObjectURL(blobUrl);
|
|
171
|
+
blobUrl = null;
|
|
172
|
+
}
|
|
173
|
+
if (pdfBlobUrl) {
|
|
174
|
+
URL.revokeObjectURL(pdfBlobUrl);
|
|
175
|
+
pdfBlobUrl = null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
async function loadBinaryContent() {
|
|
181
|
+
if (!file) return;
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const response = await ws.http('files:read-content', { path: file.path });
|
|
185
|
+
|
|
186
|
+
if (response.content) {
|
|
187
|
+
// Decode base64 to binary
|
|
188
|
+
const binaryString = atob(response.content);
|
|
189
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
190
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
191
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
192
|
+
}
|
|
193
|
+
const blob = new Blob([bytes], { type: response.contentType || 'application/octet-stream' });
|
|
194
|
+
|
|
195
|
+
if (isPdfFile(file.name)) {
|
|
196
|
+
if (pdfBlobUrl) URL.revokeObjectURL(pdfBlobUrl);
|
|
197
|
+
pdfBlobUrl = URL.createObjectURL(blob);
|
|
198
|
+
} else {
|
|
199
|
+
if (blobUrl) URL.revokeObjectURL(blobUrl);
|
|
200
|
+
blobUrl = URL.createObjectURL(blob);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} catch (error) {
|
|
204
|
+
debug.error('file', 'Failed to load binary content:', error);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Reference content for change detection (use savedContent if provided)
|
|
209
|
+
const referenceContent = $derived(savedContentProp !== undefined ? savedContentProp : content);
|
|
210
|
+
|
|
211
|
+
// Sync editable content when file or content changes (not when user types)
|
|
212
|
+
let lastSyncedContent = '';
|
|
213
|
+
let lastSyncedFilePath = '';
|
|
214
|
+
$effect(() => {
|
|
215
|
+
const currentFilePath = file?.path || '';
|
|
216
|
+
// Force sync when file changes OR when content changes
|
|
217
|
+
if (content !== lastSyncedContent || currentFilePath !== lastSyncedFilePath) {
|
|
218
|
+
lastSyncedContent = content;
|
|
219
|
+
lastSyncedFilePath = currentFilePath;
|
|
220
|
+
editableContent = content;
|
|
221
|
+
hasChanges = content !== referenceContent;
|
|
222
|
+
|
|
223
|
+
// Directly update Monaco editor to ensure content syncs
|
|
224
|
+
// (bypasses reactive bind:value chain which may not flush in async contexts)
|
|
225
|
+
const editor = monacoEditorRef?.getEditor();
|
|
226
|
+
if (editor && editor.getValue() !== content) {
|
|
227
|
+
editor.setValue(content);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Handle content changes from editor
|
|
233
|
+
function handleContentChange(newContent: string) {
|
|
234
|
+
hasChanges = newContent !== referenceContent;
|
|
235
|
+
onContentChange?.(newContent);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Update Monaco word wrap when prop changes
|
|
239
|
+
// Read wordWrap BEFORE the if-check so it's always tracked by $effect
|
|
240
|
+
$effect(() => {
|
|
241
|
+
const wrapValue: 'on' | 'off' = wordWrap ? 'on' : 'off';
|
|
242
|
+
const editor = monacoEditorRef?.getEditor();
|
|
243
|
+
if (editor) {
|
|
244
|
+
editor.updateOptions({ wordWrap: wrapValue });
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Handle line highlighting when targetLine changes
|
|
249
|
+
$effect(() => {
|
|
250
|
+
if (targetLine !== undefined && targetLine > 0) {
|
|
251
|
+
setTimeout(() => {
|
|
252
|
+
const editor = monacoEditorRef?.getEditor();
|
|
253
|
+
if (editor) {
|
|
254
|
+
editor.revealLineInCenter(targetLine);
|
|
255
|
+
|
|
256
|
+
const newDecorations = editor.deltaDecorations(currentDecorations, [
|
|
257
|
+
{
|
|
258
|
+
range: {
|
|
259
|
+
startLineNumber: targetLine,
|
|
260
|
+
startColumn: 1,
|
|
261
|
+
endLineNumber: targetLine,
|
|
262
|
+
endColumn: 1
|
|
263
|
+
},
|
|
264
|
+
options: {
|
|
265
|
+
isWholeLine: true,
|
|
266
|
+
className: 'line-highlight',
|
|
267
|
+
marginClassName: 'line-highlight-margin'
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
]);
|
|
271
|
+
|
|
272
|
+
currentDecorations = newDecorations;
|
|
273
|
+
|
|
274
|
+
setTimeout(() => {
|
|
275
|
+
if (editor) {
|
|
276
|
+
editor.deltaDecorations(currentDecorations, []);
|
|
277
|
+
currentDecorations = [];
|
|
278
|
+
}
|
|
279
|
+
}, 3000);
|
|
280
|
+
}
|
|
281
|
+
}, 100);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Save changes
|
|
286
|
+
async function saveChanges() {
|
|
287
|
+
if (!file || !onSave || !hasChanges) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
isSaving = true;
|
|
292
|
+
try {
|
|
293
|
+
await onSave(file.path, editableContent);
|
|
294
|
+
hasChanges = false;
|
|
295
|
+
} catch (error) {
|
|
296
|
+
debug.error('file', 'Failed to save file:', error);
|
|
297
|
+
} finally {
|
|
298
|
+
isSaving = false;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Get detected language from filename
|
|
303
|
+
function getDetectedLanguage(): string {
|
|
304
|
+
if (!file) return 'plaintext';
|
|
305
|
+
|
|
306
|
+
const ext = file.name.split('.').pop()?.toLowerCase();
|
|
307
|
+
if (!ext) return 'plaintext';
|
|
308
|
+
|
|
309
|
+
const languageMap: Record<string, string> = {
|
|
310
|
+
js: 'javascript', jsx: 'javascript', ts: 'typescript', tsx: 'typescript',
|
|
311
|
+
mjs: 'javascript', cjs: 'javascript',
|
|
312
|
+
html: 'html', htm: 'html', css: 'css', scss: 'scss', sass: 'sass', less: 'less',
|
|
313
|
+
py: 'python', pyx: 'python', pyi: 'python',
|
|
314
|
+
java: 'java', c: 'c', cpp: 'cpp', cxx: 'cpp', cc: 'cpp',
|
|
315
|
+
h: 'c', hpp: 'cpp', hxx: 'cpp',
|
|
316
|
+
cs: 'csharp', csx: 'csharp', go: 'go', rs: 'rust',
|
|
317
|
+
php: 'php', phtml: 'php', rb: 'ruby', rbw: 'ruby',
|
|
318
|
+
swift: 'swift', kt: 'kotlin', kts: 'kotlin',
|
|
319
|
+
scala: 'scala', sc: 'scala', r: 'r',
|
|
320
|
+
sh: 'shell', bash: 'shell', zsh: 'shell', fish: 'shell',
|
|
321
|
+
ps1: 'powershell', psm1: 'powershell', bat: 'bat', cmd: 'bat',
|
|
322
|
+
sql: 'sql', xml: 'xml', xsd: 'xml', xsl: 'xml',
|
|
323
|
+
json: 'json', jsonc: 'json', yaml: 'yaml', yml: 'yaml',
|
|
324
|
+
toml: 'toml', ini: 'ini', cfg: 'ini', conf: 'ini',
|
|
325
|
+
md: 'markdown', markdown: 'markdown',
|
|
326
|
+
dockerfile: 'dockerfile', lua: 'lua',
|
|
327
|
+
pl: 'perl', pm: 'perl', hs: 'haskell',
|
|
328
|
+
fs: 'fsharp', fsx: 'fsharp', clj: 'clojure', cljs: 'clojure',
|
|
329
|
+
erl: 'erlang', ex: 'elixir', exs: 'elixir',
|
|
330
|
+
dart: 'dart', sol: 'solidity',
|
|
331
|
+
graphql: 'graphql', gql: 'graphql',
|
|
332
|
+
svelte: 'html', vue: 'html',
|
|
333
|
+
gitignore: 'plaintext', env: 'plaintext', txt: 'plaintext', log: 'plaintext',
|
|
334
|
+
svg: 'xml'
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
return languageMap[ext] || 'plaintext';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Helper functions
|
|
341
|
+
function getDisplayIcon(fileName: string, isDirectory: boolean): IconName {
|
|
342
|
+
if (isDirectory) {
|
|
343
|
+
return getFolderIcon(fileName, false);
|
|
344
|
+
}
|
|
345
|
+
return getFileIcon(fileName);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function isImageFile(fileName: string): boolean {
|
|
349
|
+
const extension = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
|
|
350
|
+
return ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.bmp'].includes(extension);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function isSvgFile(fileName: string): boolean {
|
|
354
|
+
return fileName.toLowerCase().endsWith('.svg');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function isPdfFile(fileName: string): boolean {
|
|
358
|
+
return fileName.toLowerCase().endsWith('.pdf');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function isBinaryFile(fileName: string): boolean {
|
|
362
|
+
const extension = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
|
|
363
|
+
return ['.doc', '.docx', '.xls', '.xlsx', '.zip', '.tar', '.gz', '.exe', '.dll',
|
|
364
|
+
'.ppt', '.pptx', '.7z', '.rar', '.bz2', '.woff', '.woff2', '.ttf', '.eot', '.otf',
|
|
365
|
+
'.mp3', '.mp4', '.wav', '.avi', '.mkv', '.flv', '.sqlite', '.db'].includes(extension);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function isVisualFile(fileName: string): boolean {
|
|
369
|
+
return isImageFile(fileName) || isSvgFile(fileName) || isPdfFile(fileName);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function formatFileSize(bytes: number): string {
|
|
373
|
+
if (bytes === 0) return '0 B';
|
|
374
|
+
const k = 1024;
|
|
375
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
376
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
377
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function copyToClipboard() {
|
|
381
|
+
if (editableContent) {
|
|
382
|
+
navigator.clipboard.writeText(editableContent);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function downloadFile() {
|
|
387
|
+
if (file) {
|
|
388
|
+
if (editableContent) {
|
|
389
|
+
const blob = new Blob([editableContent], { type: 'text/plain' });
|
|
390
|
+
const url = URL.createObjectURL(blob);
|
|
391
|
+
const a = document.createElement('a');
|
|
392
|
+
a.href = url;
|
|
393
|
+
a.download = file.name;
|
|
394
|
+
a.click();
|
|
395
|
+
URL.revokeObjectURL(url);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
</script>
|
|
400
|
+
|
|
401
|
+
{#if file}
|
|
402
|
+
<div class="w-full h-full flex flex-col">
|
|
403
|
+
<!-- Header -->
|
|
404
|
+
{#if !hideHeader}
|
|
405
|
+
<div class="flex-shrink-0 flex items-center justify-between px-4 py-2.5 border-b border-slate-200 dark:border-slate-700">
|
|
406
|
+
<div class="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
|
|
407
|
+
<Icon name={getDisplayIcon(file.name, file.type === 'directory')} class="w-7 h-7" />
|
|
408
|
+
<div class="min-w-0 flex-1">
|
|
409
|
+
<h3 class="text-xs sm:text-sm font-bold text-slate-900 dark:text-slate-100 truncate">
|
|
410
|
+
{file.name}
|
|
411
|
+
</h3>
|
|
412
|
+
<p class="text-xs text-slate-600 dark:text-slate-400 truncate mt-0.5">
|
|
413
|
+
<span class="hidden sm:inline">{file.path} • </span> {formatFileSize(file.size || 0)}
|
|
414
|
+
</p>
|
|
415
|
+
</div>
|
|
416
|
+
</div>
|
|
417
|
+
|
|
418
|
+
<div class="flex items-center gap-1.5 sm:gap-1 flex-shrink-0">
|
|
419
|
+
<!-- SVG view mode toggle -->
|
|
420
|
+
{#if file && file.type === 'file' && isSvgFile(file.name)}
|
|
421
|
+
<div class="flex bg-slate-100 dark:bg-slate-800 rounded-lg overflow-hidden mr-1">
|
|
422
|
+
<button
|
|
423
|
+
class="flex px-2 py-1.5 text-xs font-medium transition-colors {svgViewMode === 'visual' ? 'bg-violet-600 text-white' : 'text-slate-600 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700'}"
|
|
424
|
+
onclick={() => { svgViewMode = 'visual'; }}
|
|
425
|
+
title="Visual preview"
|
|
426
|
+
>
|
|
427
|
+
<Icon name="lucide:eye" class="w-3.5 h-3.5" />
|
|
428
|
+
</button>
|
|
429
|
+
<button
|
|
430
|
+
class="flex px-2 py-1.5 text-xs font-medium transition-colors {svgViewMode === 'code' ? 'bg-violet-600 text-white' : 'text-slate-600 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700'}"
|
|
431
|
+
onclick={() => { svgViewMode = 'code'; }}
|
|
432
|
+
title="Code view"
|
|
433
|
+
>
|
|
434
|
+
<Icon name="lucide:code" class="w-3.5 h-3.5" />
|
|
435
|
+
</button>
|
|
436
|
+
</div>
|
|
437
|
+
{/if}
|
|
438
|
+
|
|
439
|
+
<!-- External change badge + refresh button -->
|
|
440
|
+
{#if externallyChanged && onForceReload}
|
|
441
|
+
<div class="flex items-center gap-1 mr-1">
|
|
442
|
+
<span class="text-[10px] px-1.5 py-0.5 rounded bg-amber-100 dark:bg-amber-900/50 text-amber-700 dark:text-amber-400 font-medium whitespace-nowrap">
|
|
443
|
+
Changed externally
|
|
444
|
+
</span>
|
|
445
|
+
<button
|
|
446
|
+
class="flex p-1.5 text-amber-600 dark:text-amber-400 hover:text-amber-700 dark:hover:text-amber-300 hover:bg-amber-50 dark:hover:bg-amber-900/30 rounded-lg transition-all duration-200"
|
|
447
|
+
onclick={onForceReload}
|
|
448
|
+
title="Reload file from disk (discard local changes)"
|
|
449
|
+
>
|
|
450
|
+
<Icon name="lucide:refresh-cw" class="w-4 h-4" />
|
|
451
|
+
</button>
|
|
452
|
+
</div>
|
|
453
|
+
{/if}
|
|
454
|
+
|
|
455
|
+
<!-- Actions for editable files -->
|
|
456
|
+
{#if file && file.type === 'file' && !isImageFile(file.name) && !isBinaryFile(file.name) && !isPdfFile(file.name) && !(isSvgFile(file.name) && svgViewMode === 'visual')}
|
|
457
|
+
<!-- Word Wrap toggle -->
|
|
458
|
+
{#if onToggleWordWrap}
|
|
459
|
+
<button
|
|
460
|
+
class="flex p-2 rounded-lg transition-all duration-200
|
|
461
|
+
{wordWrap ?
|
|
462
|
+
'text-violet-600 dark:text-violet-400 bg-violet-100 dark:bg-violet-900/50' :
|
|
463
|
+
'text-slate-600 dark:text-slate-400 hover:text-violet-600 dark:hover:text-violet-400 hover:bg-violet-50 dark:hover:bg-violet-900/30'
|
|
464
|
+
}"
|
|
465
|
+
onclick={onToggleWordWrap}
|
|
466
|
+
title="Toggle Word Wrap"
|
|
467
|
+
>
|
|
468
|
+
<Icon name="lucide:wrap-text" class="w-4 h-4" />
|
|
469
|
+
</button>
|
|
470
|
+
{/if}
|
|
471
|
+
<!-- Save button -->
|
|
472
|
+
<button
|
|
473
|
+
class="flex p-2 text-green-600 dark:text-green-400 hover:text-green-700 dark:hover:text-green-300 hover:bg-green-50 dark:hover:bg-green-900/30 rounded-lg transition-all duration-200 {saveButtonDisabled ? 'opacity-50 cursor-not-allowed' : ''}"
|
|
474
|
+
onclick={() => {
|
|
475
|
+
if (canSave) {
|
|
476
|
+
saveChanges();
|
|
477
|
+
}
|
|
478
|
+
}}
|
|
479
|
+
disabled={saveButtonDisabled}
|
|
480
|
+
title={saveButtonDisabled ? (hasChanges ? 'Saving...' : 'No changes to save') : 'Save changes (Ctrl+S)'}
|
|
481
|
+
>
|
|
482
|
+
{#if isSaving}
|
|
483
|
+
<div class="w-4 h-4 border-2 border-green-600 border-t-transparent rounded-full animate-spin"></div>
|
|
484
|
+
{:else}
|
|
485
|
+
<Icon name="lucide:save" class="w-4 h-4" />
|
|
486
|
+
{/if}
|
|
487
|
+
</button>
|
|
488
|
+
|
|
489
|
+
{#if editableContent}
|
|
490
|
+
<button
|
|
491
|
+
class="flex p-2 text-slate-600 dark:text-slate-400 hover:text-violet-600 dark:hover:text-violet-400 hover:bg-violet-50 dark:hover:bg-violet-900/30 rounded-lg transition-all duration-200"
|
|
492
|
+
onclick={copyToClipboard}
|
|
493
|
+
title="Copy content"
|
|
494
|
+
>
|
|
495
|
+
<Icon name="lucide:copy" class="w-4 h-4" />
|
|
496
|
+
</button>
|
|
497
|
+
{/if}
|
|
498
|
+
|
|
499
|
+
<button
|
|
500
|
+
class="flex p-2 text-slate-600 dark:text-slate-400 hover:text-violet-600 dark:hover:text-violet-400 hover:bg-violet-50 dark:hover:bg-violet-900/30 rounded-lg transition-all duration-200"
|
|
501
|
+
onclick={downloadFile}
|
|
502
|
+
title="Download file"
|
|
503
|
+
>
|
|
504
|
+
<Icon name="lucide:download" class="w-4 h-4" />
|
|
505
|
+
</button>
|
|
506
|
+
{:else if file && file.type === 'file'}
|
|
507
|
+
<!-- Non-editable file actions -->
|
|
508
|
+
<button
|
|
509
|
+
class="flex p-2 text-slate-600 dark:text-slate-400 hover:text-violet-600 dark:hover:text-violet-400 hover:bg-violet-50 dark:hover:bg-violet-900/30 rounded-lg transition-all duration-200"
|
|
510
|
+
onclick={downloadFile}
|
|
511
|
+
title="Download file"
|
|
512
|
+
>
|
|
513
|
+
<Icon name="lucide:download" class="w-4 h-4" />
|
|
514
|
+
</button>
|
|
515
|
+
{/if}
|
|
516
|
+
</div>
|
|
517
|
+
</div>
|
|
518
|
+
{/if}
|
|
519
|
+
|
|
520
|
+
<!-- Content -->
|
|
521
|
+
<div class="flex-1 overflow-hidden">
|
|
522
|
+
{#if isLoading}
|
|
523
|
+
<div class="flex items-center justify-center h-full">
|
|
524
|
+
<LoadingSpinner size="lg" />
|
|
525
|
+
</div>
|
|
526
|
+
{:else if error}
|
|
527
|
+
<div class="flex flex-col items-center justify-center h-full p-8">
|
|
528
|
+
<Icon name="lucide:triangle-alert" class="w-16 h-16 text-red-400 mb-4" />
|
|
529
|
+
<h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-2">
|
|
530
|
+
Unable to load file
|
|
531
|
+
</h3>
|
|
532
|
+
<p class="text-sm text-slate-500 dark:text-slate-400 text-center">
|
|
533
|
+
{error}
|
|
534
|
+
</p>
|
|
535
|
+
</div>
|
|
536
|
+
{:else if file.type === 'directory'}
|
|
537
|
+
<div class="flex flex-col items-center justify-center h-full p-8">
|
|
538
|
+
<Icon name="lucide:folder" class="w-16 h-16 text-slate-400 mb-4" />
|
|
539
|
+
<h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-2">
|
|
540
|
+
Directory Selected
|
|
541
|
+
</h3>
|
|
542
|
+
<p class="text-sm text-slate-500 dark:text-slate-400 text-center">
|
|
543
|
+
This is a directory. Select a file to view its content.
|
|
544
|
+
</p>
|
|
545
|
+
</div>
|
|
546
|
+
{:else if isImageFile(file.name)}
|
|
547
|
+
<!-- Image preview -->
|
|
548
|
+
<div class="flex items-center justify-center h-full p-4 overflow-hidden">
|
|
549
|
+
{#if blobUrl}
|
|
550
|
+
<img
|
|
551
|
+
src={blobUrl}
|
|
552
|
+
alt={file.name}
|
|
553
|
+
class="max-w-full max-h-full object-contain"
|
|
554
|
+
/>
|
|
555
|
+
{:else}
|
|
556
|
+
<LoadingSpinner size="lg" />
|
|
557
|
+
{/if}
|
|
558
|
+
</div>
|
|
559
|
+
{:else if isSvgFile(file.name)}
|
|
560
|
+
<!-- SVG: visual or code view -->
|
|
561
|
+
{#if svgViewMode === 'visual'}
|
|
562
|
+
<div class="flex items-center justify-center h-full p-4 overflow-auto bg-[url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%3E%3Crect%20width%3D%2220%22%20height%3D%2220%22%20fill%3D%22%23f0f0f0%22%2F%3E%3Crect%20width%3D%2210%22%20height%3D%2210%22%20fill%3D%22%23e0e0e0%22%2F%3E%3Crect%20x%3D%2210%22%20y%3D%2210%22%20width%3D%2210%22%20height%3D%2210%22%20fill%3D%22%23e0e0e0%22%2F%3E%3C%2Fsvg%3E')]">
|
|
563
|
+
{#if blobUrl}
|
|
564
|
+
<img
|
|
565
|
+
src={blobUrl}
|
|
566
|
+
alt={file.name}
|
|
567
|
+
class="max-w-full max-h-full object-contain"
|
|
568
|
+
/>
|
|
569
|
+
{:else if content}
|
|
570
|
+
<!-- Render SVG from content directly -->
|
|
571
|
+
<div class="max-w-full max-h-full flex items-center justify-center">
|
|
572
|
+
{@html content}
|
|
573
|
+
</div>
|
|
574
|
+
{:else}
|
|
575
|
+
<LoadingSpinner size="lg" />
|
|
576
|
+
{/if}
|
|
577
|
+
</div>
|
|
578
|
+
{:else}
|
|
579
|
+
<!-- SVG code view (editable) -->
|
|
580
|
+
<div class="h-full flex flex-col" bind:this={containerRef}>
|
|
581
|
+
<div class="flex-1 bg-slate-50 dark:bg-slate-950 overflow-hidden">
|
|
582
|
+
<div class="h-full flex flex-col">
|
|
583
|
+
<div class="flex-1">
|
|
584
|
+
{#key themeKey}
|
|
585
|
+
<MonacoEditor
|
|
586
|
+
bind:this={monacoEditorRef}
|
|
587
|
+
bind:value={editableContent}
|
|
588
|
+
language="xml"
|
|
589
|
+
readonly={false}
|
|
590
|
+
onChange={handleContentChange}
|
|
591
|
+
height={editorHeight}
|
|
592
|
+
options={{
|
|
593
|
+
minimap: { enabled: false },
|
|
594
|
+
wordWrap: 'off',
|
|
595
|
+
renderWhitespace: 'none',
|
|
596
|
+
mouseWheelZoom: false
|
|
597
|
+
}}
|
|
598
|
+
/>
|
|
599
|
+
{/key}
|
|
600
|
+
</div>
|
|
601
|
+
|
|
602
|
+
{#if hasChanges}
|
|
603
|
+
<div class="flex-shrink-0 p-4 bg-amber-50 dark:bg-amber-900/30 border-t border-amber-200 dark:border-amber-800">
|
|
604
|
+
<div class="text-xs text-amber-600 dark:text-amber-400 flex items-center gap-1">
|
|
605
|
+
<Icon name="lucide:circle-alert" class="w-3 h-3" />
|
|
606
|
+
Unsaved changes
|
|
607
|
+
</div>
|
|
608
|
+
</div>
|
|
609
|
+
{/if}
|
|
610
|
+
</div>
|
|
611
|
+
</div>
|
|
612
|
+
</div>
|
|
613
|
+
{/if}
|
|
614
|
+
{:else if isPdfFile(file.name)}
|
|
615
|
+
<!-- PDF preview -->
|
|
616
|
+
<div class="h-full w-full">
|
|
617
|
+
{#if pdfBlobUrl}
|
|
618
|
+
<iframe
|
|
619
|
+
src={pdfBlobUrl}
|
|
620
|
+
title={file.name}
|
|
621
|
+
class="w-full h-full border-0"
|
|
622
|
+
></iframe>
|
|
623
|
+
{:else}
|
|
624
|
+
<div class="flex items-center justify-center h-full">
|
|
625
|
+
<LoadingSpinner size="lg" />
|
|
626
|
+
</div>
|
|
627
|
+
{/if}
|
|
628
|
+
</div>
|
|
629
|
+
{:else if isBinaryFile(file.name)}
|
|
630
|
+
<div class="flex flex-col items-center justify-center h-full p-8">
|
|
631
|
+
<Icon name="lucide:file-text" class="w-16 h-16 text-slate-400 mb-4" />
|
|
632
|
+
<h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-2">
|
|
633
|
+
Binary File
|
|
634
|
+
</h3>
|
|
635
|
+
<p class="text-sm text-slate-500 dark:text-slate-400 text-center mb-4">
|
|
636
|
+
This file cannot be previewed in the browser.
|
|
637
|
+
</p>
|
|
638
|
+
<button
|
|
639
|
+
class="px-6 py-2.5 bg-violet-600 text-white rounded-xl hover:bg-violet-700 transition-all duration-200"
|
|
640
|
+
onclick={downloadFile}
|
|
641
|
+
>
|
|
642
|
+
Download File
|
|
643
|
+
</button>
|
|
644
|
+
</div>
|
|
645
|
+
{:else}
|
|
646
|
+
<!-- Code content (always in edit mode) -->
|
|
647
|
+
<div class="h-full flex flex-col" bind:this={containerRef}>
|
|
648
|
+
<div class="flex-1 bg-slate-50 dark:bg-slate-950 overflow-hidden">
|
|
649
|
+
<div class="h-full flex flex-col">
|
|
650
|
+
<div class="flex-1">
|
|
651
|
+
{#key themeKey}
|
|
652
|
+
<MonacoEditor
|
|
653
|
+
bind:this={monacoEditorRef}
|
|
654
|
+
bind:value={editableContent}
|
|
655
|
+
language={getDetectedLanguage()}
|
|
656
|
+
readonly={false}
|
|
657
|
+
onChange={handleContentChange}
|
|
658
|
+
height={editorHeight}
|
|
659
|
+
options={{
|
|
660
|
+
minimap: { enabled: false },
|
|
661
|
+
wordWrap: wordWrap ? 'on' : 'off',
|
|
662
|
+
renderWhitespace: 'none',
|
|
663
|
+
mouseWheelZoom: false
|
|
664
|
+
}}
|
|
665
|
+
/>
|
|
666
|
+
{/key}
|
|
667
|
+
</div>
|
|
668
|
+
|
|
669
|
+
</div>
|
|
670
|
+
</div>
|
|
671
|
+
</div>
|
|
672
|
+
{/if}
|
|
673
|
+
</div>
|
|
674
|
+
</div>
|
|
675
|
+
{:else}
|
|
676
|
+
<div class="h-full flex items-center justify-center">
|
|
677
|
+
<div class="text-center p-12">
|
|
678
|
+
<div class="bg-slate-100 dark:bg-slate-800 rounded-full w-20 h-20 flex items-center justify-center mx-auto mb-6">
|
|
679
|
+
<Icon name="lucide:file-text" class="w-10 h-10 text-slate-400" />
|
|
680
|
+
</div>
|
|
681
|
+
<h3 class="text-lg font-bold text-slate-900 dark:text-slate-100 mb-2">
|
|
682
|
+
No File Selected
|
|
683
|
+
</h3>
|
|
684
|
+
<p class="text-sm text-slate-600 dark:text-slate-400">
|
|
685
|
+
Select a file from the explorer to view its content.
|
|
686
|
+
</p>
|
|
687
|
+
</div>
|
|
688
|
+
</div>
|
|
689
|
+
{/if}
|
|
690
|
+
|
|
691
|
+
<style>
|
|
692
|
+
:global(.line-highlight) {
|
|
693
|
+
background-color: rgba(255, 235, 59, 0.3) !important;
|
|
694
|
+
animation: fade-out 3s ease-out forwards;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
:global(.line-highlight-margin) {
|
|
698
|
+
background-color: rgba(255, 235, 59, 0.5) !important;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
:global(.monaco-editor.vs-dark .line-highlight) {
|
|
702
|
+
background-color: rgba(255, 235, 59, 0.15) !important;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
:global(.monaco-editor.vs-dark .line-highlight-margin) {
|
|
706
|
+
background-color: rgba(255, 235, 59, 0.25) !important;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
@keyframes fade-out {
|
|
710
|
+
0% {
|
|
711
|
+
background-color: rgba(255, 235, 59, 0.5);
|
|
712
|
+
}
|
|
713
|
+
100% {
|
|
714
|
+
background-color: transparent;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
:global(.monaco-editor.vs-dark) {
|
|
719
|
+
@keyframes fade-out {
|
|
720
|
+
0% {
|
|
721
|
+
background-color: rgba(255, 235, 59, 0.15);
|
|
722
|
+
}
|
|
723
|
+
100% {
|
|
724
|
+
background-color: transparent;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
</style>
|