From 92f6d0b6fbce59bec42160d6a8bd353e6ce09c08 Mon Sep 17 00:00:00 2001 From: nighca Date: Mon, 23 Dec 2024 18:44:28 +0800 Subject: [PATCH] Code-editor Tabs & Console --- .../preprocessing/common/ProcessItem.vue | 2 +- .../common/markdown-vue/MarkdownView.ts | 12 +- .../code-text-editor/monaco.ts | 2 +- .../editor/code-editor/CodeLink.vue | 76 +++++++ .../editor/code-editor/code-editor.ts | 5 + .../components/editor/code-editor/common.ts | 15 ++ .../components/editor/code-editor/context.ts | 5 + .../editor/code-editor/ui/CodeEditorUI.vue | 55 ++++- .../editor/code-editor/ui/ZoomControl.vue | 51 +++++ .../ui/api-reference/APIReferenceItem.vue | 19 +- .../editor/code-editor/ui/code-editor-ui.ts | 28 ++- .../editor/code-editor/ui/common.ts | 2 +- .../definition/DefinitionOverviewWrapper.vue | 6 + .../ui/diagnostics/DiagnosticsUI.vue | 2 +- .../ui/document-tab/DocumentTab.vue | 93 ++++++++ .../ui/document-tab/DocumentTabs.vue | 102 +++++++++ .../code-editor/ui/markdown/CodeLink.ts | 61 +++++ .../code-editor/ui/markdown/CodeLink.vue | 92 -------- .../code-editor/ui/markdown/CodeView.vue | 6 +- .../code-editor/ui/markdown/MarkdownView.vue | 2 +- .../components/editor/panels/ConsolePanel.vue | 90 +++++++- .../components/editor/panels/EditorPanels.vue | 4 +- .../editor/preview/EditorPreview.vue | 76 +++++-- .../editor/preview/InPlaceRunner.vue | 15 +- .../editor/preview/RunnerContainer.vue | 211 +----------------- .../src/components/project/ProjectItem.vue | 2 +- .../project/runner/ProjectRunner.vue | 12 +- .../project/runner/v1/ProjectRunnerV1.vue | 7 +- spx-gui/src/components/ui/UIButton.vue | 4 +- .../src/components/ui/UIConfigProvider.vue | 2 +- spx-gui/src/components/ui/UIIconButton.vue | 4 +- spx-gui/src/components/ui/icons/UIIcon.vue | 4 + .../src/components/ui/icons/close-circle.svg | 3 + .../src/components/ui/icons/play-hollow.svg | 3 + spx-gui/src/components/ui/tokens/colors.ts | 9 +- spx-gui/src/models/runtime.ts | 37 +-- 36 files changed, 747 insertions(+), 372 deletions(-) create mode 100644 spx-gui/src/components/editor/code-editor/CodeLink.vue create mode 100644 spx-gui/src/components/editor/code-editor/ui/ZoomControl.vue create mode 100644 spx-gui/src/components/editor/code-editor/ui/document-tab/DocumentTab.vue create mode 100644 spx-gui/src/components/editor/code-editor/ui/document-tab/DocumentTabs.vue create mode 100644 spx-gui/src/components/editor/code-editor/ui/markdown/CodeLink.ts delete mode 100644 spx-gui/src/components/editor/code-editor/ui/markdown/CodeLink.vue create mode 100644 spx-gui/src/components/ui/icons/close-circle.svg create mode 100644 spx-gui/src/components/ui/icons/play-hollow.svg diff --git a/spx-gui/src/components/asset/preprocessing/common/ProcessItem.vue b/spx-gui/src/components/asset/preprocessing/common/ProcessItem.vue index 1c0fe5d22..17510a439 100644 --- a/spx-gui/src/components/asset/preprocessing/common/ProcessItem.vue +++ b/spx-gui/src/components/asset/preprocessing/common/ProcessItem.vue @@ -59,7 +59,7 @@ defineProps<{ opacity: 0; color: var(--ui-color-grey-100); border-radius: 50%; - background-color: var(--ui-color-green-200); + background-color: var(--ui-color-green-main); } .name { diff --git a/spx-gui/src/components/common/markdown-vue/MarkdownView.ts b/spx-gui/src/components/common/markdown-vue/MarkdownView.ts index 37c54b79b..9339667bb 100644 --- a/spx-gui/src/components/common/markdown-vue/MarkdownView.ts +++ b/spx-gui/src/components/common/markdown-vue/MarkdownView.ts @@ -106,14 +106,18 @@ function renderHastNode(node: hast.Node, components: Components, key?: string | function renderHastElement(element: hast.Element, components: Components, key?: string | number): VNode { let props: Record let type: string | Component - let children: VRendered | (() => VRendered) + let children: VRendered | (() => VRendered) | undefined const customComponents = components.custom ?? {} if (Object.prototype.hasOwnProperty.call(customComponents, element.tagName)) { type = customComponents[element.tagName] props = hastProps2VueProps(element.properties) - // Use function slot for custom components to avoid Vue warning: - // [Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance. - children = () => element.children.map((c, i) => renderHastNode(c, components, i)) + if (element.children.length === 0) { + children = undefined + } else { + // Use function slot for custom components to avoid Vue warning: + // [Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance. + children = () => element.children.map((c, i) => renderHastNode(c, components, i)) + } } else if ( // Render code blocks with `components.codeBlock` // TODO: It may be simpler to recognize & process code blocks based on mdast instead of hast diff --git a/spx-gui/src/components/editor/code-editor-legacy/code-text-editor/monaco.ts b/spx-gui/src/components/editor/code-editor-legacy/code-text-editor/monaco.ts index a36c16c3b..1a61e6b0f 100644 --- a/spx-gui/src/components/editor/code-editor-legacy/code-text-editor/monaco.ts +++ b/spx-gui/src/components/editor/code-editor-legacy/code-text-editor/monaco.ts @@ -48,7 +48,7 @@ export function initMonaco( { token: 'string', foreground: color.green[300] }, { token: 'operator', foreground: color.blue.main }, { token: 'number', foreground: color.blue[600] }, - { token: 'keyword', foreground: color.red[300] }, + { token: 'keyword', foreground: color.red[600] }, { token: 'typeKeywords', foreground: color.purple.main }, { token: 'brackets', foreground: color.title } ], diff --git a/spx-gui/src/components/editor/code-editor/CodeLink.vue b/spx-gui/src/components/editor/code-editor/CodeLink.vue new file mode 100644 index 000000000..c1def5931 --- /dev/null +++ b/spx-gui/src/components/editor/code-editor/CodeLink.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/spx-gui/src/components/editor/code-editor/code-editor.ts b/spx-gui/src/components/editor/code-editor/code-editor.ts index 572467a6b..0d15f88d2 100644 --- a/spx-gui/src/components/editor/code-editor/code-editor.ts +++ b/spx-gui/src/components/editor/code-editor/code-editor.ts @@ -485,6 +485,11 @@ export class CodeEditor extends Disposable { if (idx !== -1) this.uis.splice(idx, 1) } + getAttachedUI() { + if (this.uis.length === 0) return null + return this.uis[this.uis.length - 1] + } + init() { this.lspClient.init() } diff --git a/spx-gui/src/components/editor/code-editor/common.ts b/spx-gui/src/components/editor/code-editor/common.ts index a292ee21d..f37c2b673 100644 --- a/spx-gui/src/components/editor/code-editor/common.ts +++ b/spx-gui/src/components/editor/code-editor/common.ts @@ -537,3 +537,18 @@ export function textDocumentId2ResourceModelId( } return null } + +export function textDocumentIdEq(a: TextDocumentIdentifier | null, b: TextDocumentIdentifier | null) { + if (a == null || b == null) return a === b + return a.uri === b.uri +} + +export function textDocumentId2CodeFileName(id: TextDocumentIdentifier) { + const codeFilePath = getCodeFilePath(id.uri) + if (stageCodeFilePaths.includes(codeFilePath)) { + return { en: 'Stage', zh: '舞台' } + } else { + const spriteName = codeFilePath.replace(/\.spx$/, '') + return { en: spriteName, zh: spriteName } + } +} diff --git a/spx-gui/src/components/editor/code-editor/context.ts b/spx-gui/src/components/editor/code-editor/context.ts index 72e5bd8ae..96509dcd1 100644 --- a/spx-gui/src/components/editor/code-editor/context.ts +++ b/spx-gui/src/components/editor/code-editor/context.ts @@ -15,6 +15,7 @@ import { CodeEditor } from './code-editor' export type CodeEditorCtx = { attachUI(ui: ICodeEditorUI): void detachUI(ui: ICodeEditorUI): void + getAttachedUI(): ICodeEditorUI | null getMonaco(): Monaco getTextDocument: (id: TextDocumentIdentifier) => TextDocument | null formatTextDocument(id: TextDocumentIdentifier): Promise @@ -159,6 +160,10 @@ export function useProvideCodeEditorCtx( if (editorRef.value == null) throw new Error('Code editor not initialized') editorRef.value.detachUI(ui) }, + getAttachedUI() { + if (editorRef.value == null) throw new Error('Code editor not initialized') + return editorRef.value.getAttachedUI() + }, getMonaco() { if (monacoRef.value == null) throw new Error('Monaco not initialized') return monacoRef.value diff --git a/spx-gui/src/components/editor/code-editor/ui/CodeEditorUI.vue b/spx-gui/src/components/editor/code-editor/ui/CodeEditorUI.vue index 866d39671..ddbe7d29d 100644 --- a/spx-gui/src/components/editor/code-editor/ui/CodeEditorUI.vue +++ b/spx-gui/src/components/editor/code-editor/ui/CodeEditorUI.vue @@ -59,6 +59,8 @@ import CopilotUI from './copilot/CopilotUI.vue' import DiagnosticsUI from './diagnostics/DiagnosticsUI.vue' import ResourceReferenceUI from './resource-reference/ResourceReferenceUI.vue' import ContextMenuUI from './context-menu/ContextMenuUI.vue' +import DocumentTabs from './document-tab/DocumentTabs.vue' +import ZoomControl from './ZoomControl.vue' const props = defineProps<{ codeFilePath: string @@ -117,13 +119,17 @@ const uiRef = computed(() => { ) }) -const monacoEditorOptions: monaco.editor.IStandaloneEditorConstructionOptions = { +const initialFontSize = 12 +const fontSize = useLocalStorage('spx-gui-code-font-size', initialFontSize) + +const monacoEditorOptions = computed(() => ({ language: 'spx', theme, tabSize, insertSpaces, + fontSize: fontSize.value, contextmenu: false -} +})) const monacEditorInitDataRef = shallowRef(null) @@ -141,6 +147,13 @@ watch( signal.throwIfAborted() ui.init(...initData) + ui.editor.onDidChangeConfiguration((e) => { + const fontSizeId = ui.monaco.editor.EditorOption.fontSize + if (e.hasChanged(fontSizeId)) { + fontSize.value = ui.editor.getOptions().get(fontSizeId) + } + }) + codeEditorCtx.attachUI(ui) signal.addEventListener('abort', () => { codeEditorCtx.detachUI(ui) @@ -200,6 +213,19 @@ watchEffect((onCleanup) => { ) signal.addEventListener('abort', endResizing) }) + +function zoomIn() { + uiRef.value.editor.trigger('keyboard', `editor.action.fontZoomIn`, {}) +} + +function zoomOut() { + uiRef.value.editor.trigger('keyboard', `editor.action.fontZoomOut`, {}) +} + +function zoomReset() { + uiRef.value.editor.updateOptions({ fontSize: initialFontSize }) + uiRef.value.editor.trigger('keyboard', `editor.action.fontZoomReset`, {}) +} @@ -323,10 +353,31 @@ watchEffect((onCleanup) => { .monaco-editor { flex: 1 1 0; min-width: 0; + margin: 12px 0; } :global(.code-editor-content-widget) { z-index: 10; // Ensure content widget is above other elements, especially cursor padding: 2px 0; // Gap between content widget and text } + +.right-sidebar { + padding: 12px 8px; + flex: 0 0 auto; + min-width: 0; + min-height: 0; + display: flex; + flex-direction: column; + justify-content: space-between; + gap: 40px; + + .document-tabs { + flex: 0 1 auto; + min-height: 0; + } + + .zoom-control { + flex: 0 0 auto; + } +} diff --git a/spx-gui/src/components/editor/code-editor/ui/ZoomControl.vue b/spx-gui/src/components/editor/code-editor/ui/ZoomControl.vue new file mode 100644 index 000000000..134beba07 --- /dev/null +++ b/spx-gui/src/components/editor/code-editor/ui/ZoomControl.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/spx-gui/src/components/editor/code-editor/ui/api-reference/APIReferenceItem.vue b/spx-gui/src/components/editor/code-editor/ui/api-reference/APIReferenceItem.vue index 8555093ab..58de4c2d1 100644 --- a/spx-gui/src/components/editor/code-editor/ui/api-reference/APIReferenceItem.vue +++ b/spx-gui/src/components/editor/code-editor/ui/api-reference/APIReferenceItem.vue @@ -45,15 +45,16 @@ function handleExplain() { - + diff --git a/spx-gui/src/components/editor/code-editor/ui/markdown/MarkdownView.vue b/spx-gui/src/components/editor/code-editor/ui/markdown/MarkdownView.vue index 1f6527ee7..4c0ec3f66 100644 --- a/spx-gui/src/components/editor/code-editor/ui/markdown/MarkdownView.vue +++ b/spx-gui/src/components/editor/code-editor/ui/markdown/MarkdownView.vue @@ -4,7 +4,7 @@ import { useI18n, type LocaleMessage } from '@/utils/i18n' import MarkdownView from '@/components/common/markdown-vue/MarkdownView' import type { MarkdownStringFlag } from '../../common' import DefinitionItem from '../definition/DefinitionItem.vue' -import CodeLink from './CodeLink.vue' +import CodeLink from './CodeLink' import CodeBlock from './CodeBlock.vue' import ResourcePreview from './ResourcePreview.vue' import DiagnosticItem from './DiagnosticItem.vue' diff --git a/spx-gui/src/components/editor/panels/ConsolePanel.vue b/spx-gui/src/components/editor/panels/ConsolePanel.vue index 5121b1352..08a5851b1 100644 --- a/spx-gui/src/components/editor/panels/ConsolePanel.vue +++ b/spx-gui/src/components/editor/panels/ConsolePanel.vue @@ -1,10 +1,41 @@