From b2c03dc8bf5e2da6ce118501f67888a05d0ce3f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Sat, 16 Nov 2024 13:29:58 +0100 Subject: [PATCH 1/8] refactor(#235): global status bar support with selection info from CodeMirror editors --- package.json | 2 +- .../component/VCodeEditorStatusBar.vue | 39 -------- .../VCodeEditorStatusBarSelection.vue | 52 ---------- .../component/VInlineQueryEditor.vue | 4 + .../code-editor/component/VPreviewEditor.vue | 5 + .../code-editor/component/VQueryEditor.vue | 11 ++- .../workspaceStatusBarIntegration.ts | 97 +++++++++++++++++++ src/modules/i18n/en.json | 25 ++++- .../workspace/service/WorkspaceService.ts | 51 ++++++++++ .../component/ActiveEditorStatus.vue | 77 +++++++++++++++ .../component/WorkspaceStatusBar.vue | 56 +++++++++++ .../status-bar/model/ActiveEditorStatus.ts | 33 +++++++ src/modules/workspace/store/workspaceStore.ts | 7 +- src/modules/workspace/view/MainView.vue | 4 +- yarn.lock | 8 +- 15 files changed, 362 insertions(+), 109 deletions(-) delete mode 100644 src/modules/code-editor/component/VCodeEditorStatusBar.vue delete mode 100644 src/modules/code-editor/component/VCodeEditorStatusBarSelection.vue create mode 100644 src/modules/code-editor/extension/workspaceStatusBarIntegration.ts create mode 100644 src/modules/workspace/status-bar/component/ActiveEditorStatus.vue create mode 100644 src/modules/workspace/status-bar/component/WorkspaceStatusBar.vue create mode 100644 src/modules/workspace/status-bar/model/ActiveEditorStatus.ts diff --git a/package.json b/package.json index 92c5af09..16110b24 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "@connectrpc/connect-web": "^1.4.0", "@connectrpc/protoc-gen-connect-es": "^1.4.0", "@ddietr/codemirror-themes": "^1.4.2", - "@lukashornych/codemirror-lang-evitaql": "1.2.5", + "@lukashornych/codemirror-lang-evitaql": "1.3.0", "@mdi/font": "7.0.96", "@types/dompurify": "^3.0.4", "@types/keymaster": "^1.6.33", diff --git a/src/modules/code-editor/component/VCodeEditorStatusBar.vue b/src/modules/code-editor/component/VCodeEditorStatusBar.vue deleted file mode 100644 index c0249100..00000000 --- a/src/modules/code-editor/component/VCodeEditorStatusBar.vue +++ /dev/null @@ -1,39 +0,0 @@ - - - - - diff --git a/src/modules/code-editor/component/VCodeEditorStatusBarSelection.vue b/src/modules/code-editor/component/VCodeEditorStatusBarSelection.vue deleted file mode 100644 index 1bcda254..00000000 --- a/src/modules/code-editor/component/VCodeEditorStatusBarSelection.vue +++ /dev/null @@ -1,52 +0,0 @@ - - - - - diff --git a/src/modules/code-editor/component/VInlineQueryEditor.vue b/src/modules/code-editor/component/VInlineQueryEditor.vue index 8926dc4b..2c5840dd 100644 --- a/src/modules/code-editor/component/VInlineQueryEditor.vue +++ b/src/modules/code-editor/component/VInlineQueryEditor.vue @@ -23,8 +23,11 @@ import { computed, ref } from 'vue' import { EditorView } from 'codemirror' import { Keymap, useKeymap } from '@/modules/keymap/service/Keymap' import { Command } from '@/modules/keymap/model/Command' +import { workspaceStatusBarIntegration } from '@/modules/code-editor/extension/workspaceStatusBarIntegration' +import { useWorkspaceService, WorkspaceService } from '@/modules/workspace/service/WorkspaceService' const keymap: Keymap = useKeymap() +const workspaceService: WorkspaceService = useWorkspaceService() const props = withDefaults( defineProps<{ @@ -71,6 +74,7 @@ const extensions: Extension[] = [ ]), dracula, EditorState.transactionFilter.of(tr => tr.newDoc.lines > 1 ? [] : tr), + workspaceStatusBarIntegration(workspaceService), ...props.additionalExtensions ] diff --git a/src/modules/code-editor/component/VPreviewEditor.vue b/src/modules/code-editor/component/VPreviewEditor.vue index 40009064..d8158cf0 100644 --- a/src/modules/code-editor/component/VPreviewEditor.vue +++ b/src/modules/code-editor/component/VPreviewEditor.vue @@ -9,6 +9,10 @@ import { basicSetup, EditorView } from 'codemirror' import { dracula } from '@ddietr/codemirror-themes/dracula.js' import { ref } from 'vue' import { ViewUpdate } from '@codemirror/view' +import { useWorkspaceService, WorkspaceService } from '@/modules/workspace/service/WorkspaceService' +import { workspaceStatusBarIntegration } from '@/modules/code-editor/extension/workspaceStatusBarIntegration' + +const workspaceService: WorkspaceService = useWorkspaceService() const props = withDefaults( defineProps<{ @@ -29,6 +33,7 @@ const extensions: Extension[] = [ basicSetup, dracula, EditorState.readOnly.of(true), + workspaceStatusBarIntegration(workspaceService), ...props.additionalExtensions ] diff --git a/src/modules/code-editor/component/VQueryEditor.vue b/src/modules/code-editor/component/VQueryEditor.vue index ffd3fc03..991c00f8 100644 --- a/src/modules/code-editor/component/VQueryEditor.vue +++ b/src/modules/code-editor/component/VQueryEditor.vue @@ -9,7 +9,10 @@ import { keymap, ViewUpdate } from '@codemirror/view' import { basicSetup, EditorView } from 'codemirror' import { dracula } from '@ddietr/codemirror-themes/dracula.js' import { ref } from 'vue' -import VCodeEditorStatusBar from '@/modules/code-editor/component/VCodeEditorStatusBar.vue' +import { workspaceStatusBarIntegration } from '@/modules/code-editor/extension/workspaceStatusBarIntegration' +import { useWorkspaceService, WorkspaceService } from '@/modules/workspace/service/WorkspaceService' + +const workspaceService: WorkspaceService = useWorkspaceService() const props = withDefaults( defineProps<{ @@ -37,6 +40,7 @@ const extensions: Extension[] = [ ]), basicSetup, dracula, + workspaceStatusBarIntegration(workspaceService), ...props.additionalExtensions ] @@ -72,9 +76,6 @@ defineExpose<{ @update:model-value="$emit('update:modelValue', $event)" style="height: 100%; cursor: text;" /> - - - @@ -85,6 +86,6 @@ defineExpose<{ left: 0; right: 0; top: 0; - bottom: 2rem; + bottom: 0; } diff --git a/src/modules/code-editor/extension/workspaceStatusBarIntegration.ts b/src/modules/code-editor/extension/workspaceStatusBarIntegration.ts new file mode 100644 index 00000000..34b99e05 --- /dev/null +++ b/src/modules/code-editor/extension/workspaceStatusBarIntegration.ts @@ -0,0 +1,97 @@ +import { EditorView } from 'codemirror' +import { AnnotationType, Extension, Line, StateField, Annotation, EditorState } from '@codemirror/state' +import { EditorSelection } from '@/modules/workspace/status-bar/model/ActiveEditorStatus' +import Immutable from 'immutable' +import { language, Language } from '@codemirror/language' +import { jsonLanguage } from '@codemirror/lang-json' +import { evitaQLQueryLanguage, evitaQLConstraintListLanguage } from '@lukashornych/codemirror-lang-evitaql' +import { graphqlLanguage } from 'cm6-graphql' +import { WorkspaceService } from '@/modules/workspace/service/WorkspaceService' + +function resolveLanguageCode(view: EditorView): string { + const currentLanguage: Language | null = view.state.facet(language) + let languageCode: string = '' + if (currentLanguage == undefined) { + languageCode = 'plain' + } else if (currentLanguage === jsonLanguage) { + languageCode = 'JSON' + } else if (currentLanguage === evitaQLQueryLanguage) { + languageCode = 'evitaQL' + } else if (currentLanguage === evitaQLConstraintListLanguage) { + languageCode = 'evitaQL (constraint mode)' + } else if (currentLanguage === graphqlLanguage) { + languageCode = 'GraphQL' + } + return languageCode +} + +/** + * Integration of global workspace status bar for an editor. It injects + * info into the global workspace status bar if the editor is active. + * + * @param workspaceService workspace service instance to access status bar API + */ +export function workspaceStatusBarIntegration(workspaceService: WorkspaceService): Extension[] { + const activationAnnotation: AnnotationType = Annotation.define() + + const activatedField: StateField = StateField.define({ + create() { return false }, + update(value, tr) { + const activation: boolean | undefined = tr.annotation(activationAnnotation) + if (activation != undefined) { + return activation + } else { + return value + } + } + }) + + const lifecycleHandler: Extension = EditorView.domEventObservers({ + focus(event, view) { + if (!view.state.field(activatedField)) { + workspaceService.activateEditorStatus( + resolveLanguageCode(view), + view.state.facet(EditorState.tabSize) + ) + view.dispatch({ + annotations: activationAnnotation.of(true) + }) + } + }, + blur(event, view) { + if (view.state.field(activatedField)) { + workspaceService.deactivateEditorStatus() + view.dispatch({ + annotations: activationAnnotation.of(false) + }) + } + } + }) + + const updatePropagator: Extension = EditorView.updateListener.of((update) => { + const newSelections: EditorSelection[] = update.state.selection.ranges.map(range => { + const headLine: Line = update.state.doc.lineAt(range.head) + const anchorLine: Line = update.state.doc.lineAt(range.anchor) + const headPositionInLine: number = range.head - headLine.from + 1 + const selectedCharacterCount: number = Math.abs(range.anchor - range.head) + const lineBreaks: number = Math.abs(anchorLine.number - headLine.number) + + return new EditorSelection( + headLine.number, + headPositionInLine, + selectedCharacterCount, + lineBreaks + ) + }) + + if (update.state.field(activatedField)) { + workspaceService.updateEditorStatus(Immutable.List(newSelections)) + } + }) + + return [ + activatedField, + lifecycleHandler, + updatePropagator + ] +} diff --git a/src/modules/i18n/en.json b/src/modules/i18n/en.json index 5c021f2d..5a50bc65 100644 --- a/src/modules/i18n/en.json +++ b/src/modules/i18n/en.json @@ -37,11 +37,26 @@ "list": { "empty": "No items found." }, - "codeEditor": { - "status": { - "cursorPosition": "Ln {line}, Col {column}", - "selectionRange": "{charsCount} chars", - "selectionRanges": "{linesCount} line breaks" + "statusBar": { + "activeEditorStatus": { + "selections": { + "label": "Text selections in the active editor", + "value": { + "multipleSelections": "{count} selections", + "singleSelection": { + "cursorPosition": "Ln {line}, Col {column}", + "selectionRange": "{charsCount} chars", + "lineBreaks": "{lineBreaks} line breaks" + } + } + }, + "tabSize": { + "label": "Current tab size configuration in the active editor", + "value": "{tabSize} spaces" + }, + "language": { + "label": "Supported language in the active editor" + } } }, "dialog": { diff --git a/src/modules/workspace/service/WorkspaceService.ts b/src/modules/workspace/service/WorkspaceService.ts index adb781ff..8b3ae7a8 100644 --- a/src/modules/workspace/service/WorkspaceService.ts +++ b/src/modules/workspace/service/WorkspaceService.ts @@ -31,6 +31,8 @@ import { BackupViewerTabDefinition } from '@/modules/backup-viewer/model/BackupV import { JfrViewerTabDefinition } from '@/modules/jfr-viewer/model/JfrViewerTabDefinition' import { BackupViewerTabFactory } from '@/modules/backup-viewer/service/BackupViewerTabFactory' import { JfrViewerTabFactory } from '@/modules/jfr-viewer/service/JfrViewerTabFactory' +import Immutable from 'immutable' +import { ActiveEditorStatus, EditorSelection } from '@/modules/workspace/status-bar/model/ActiveEditorStatus' const openedTabsStorageKey: string = 'openedTabs' const tabHistoryStorageKey: string = 'tabHistory' @@ -337,6 +339,55 @@ export class WorkspaceService { const serializedTabHistory: string = JSON.stringify(Array.from(this.store.tabHistory.entries())) this.labStorage.set(tabHistoryStorageKey, LZString.compressToEncodedURIComponent(serializedTabHistory)) } + + /** + * Returns active editor status provided by some editor. If any. + */ + getActiveEditorStatus(): ActiveEditorStatus | undefined { + return this.store.activeEditorStatus as ActiveEditorStatus | undefined + } + + /** + * Activates a new status for currently active editor. + * Should be called by an editor when it gains focus. + * + * @param language editor language configuration + * @param tabSize editor tab size configuration + */ + activateEditorStatus(language: string, + tabSize: number): void { + if (this.store.activeEditorStatus != null) { + throw new UnexpectedError('There is already one activated editor ' + + 'status. Cannot activate another one before the current one is deactivated.') + } + this.store.activeEditorStatus = new ActiveEditorStatus(language, tabSize) + } + + /** + * Updates data for currently active editor. Should be called for every + * editor change. + * + * @param newSelections new selections in the active editor + */ + updateEditorStatus(newSelections: Immutable.List): void { + if (this.store.activeEditorStatus == undefined) { + console.warn('There is no active editor status, yet it is being updated.') + } else { + (this.store.activeEditorStatus as ActiveEditorStatus).selections = newSelections + } + } + + /** + * Deactivates currently activated editor. Should be called by an editor + * when it loses focus. + */ + deactivateEditorStatus(): void { + if (this.store.activeEditorStatus == null) { + console.warn('There is no active editor status, yet it is being deactivated.') + } else { + this.store.activeEditorStatus = undefined + } + } } export const useWorkspaceService = (): WorkspaceService => { diff --git a/src/modules/workspace/status-bar/component/ActiveEditorStatus.vue b/src/modules/workspace/status-bar/component/ActiveEditorStatus.vue new file mode 100644 index 00000000..cb9b43e6 --- /dev/null +++ b/src/modules/workspace/status-bar/component/ActiveEditorStatus.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/modules/workspace/status-bar/component/WorkspaceStatusBar.vue b/src/modules/workspace/status-bar/component/WorkspaceStatusBar.vue new file mode 100644 index 00000000..0227858f --- /dev/null +++ b/src/modules/workspace/status-bar/component/WorkspaceStatusBar.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/src/modules/workspace/status-bar/model/ActiveEditorStatus.ts b/src/modules/workspace/status-bar/model/ActiveEditorStatus.ts new file mode 100644 index 00000000..a4628433 --- /dev/null +++ b/src/modules/workspace/status-bar/model/ActiveEditorStatus.ts @@ -0,0 +1,33 @@ +import Immutable from 'immutable' + +/** + * Hold status info about active editor, i.e. editor which is being written in. + */ +export class ActiveEditorStatus { + readonly language: string + readonly tabSize: number + selections: Immutable.List + + constructor(language: string, tabSize: number) { + this.language = language + this.tabSize = tabSize + this.selections = Immutable.List() + } +} + +/** + * Defines a single text selection in an active editor + */ +export class EditorSelection { + readonly line: number + readonly column: number + readonly selectedCharacterCount: number + readonly lineBreaks: number + + constructor(line: number, headPositionInLine: number, selectedCharacterCount: number, lineBreaks: number) { + this.line = line + this.column = headPositionInLine + this.selectedCharacterCount = selectedCharacterCount + this.lineBreaks = lineBreaks + } +} diff --git a/src/modules/workspace/store/workspaceStore.ts b/src/modules/workspace/store/workspaceStore.ts index bb77bed8..3387a803 100644 --- a/src/modules/workspace/store/workspaceStore.ts +++ b/src/modules/workspace/store/workspaceStore.ts @@ -2,6 +2,7 @@ import { defineStore } from 'pinia' import { ref, Ref } from 'vue' import { TabDefinition } from '@/modules/workspace/tab/model/TabDefinition' import { TabData } from '@/modules/workspace/tab/model/TabData' +import { ActiveEditorStatus } from '@/modules/workspace/status-bar/model/ActiveEditorStatus' /** * Defines Pinia store for entire workspace @@ -12,10 +13,14 @@ export const useWorkspaceStore = defineStore('workspace', () => { const tabData: Ref>> = ref>>(new Map()) const tabHistory: Ref> = ref>(new Map()) + // status bar + const activeEditorStatus: Ref = ref(undefined) + return { tabDefinitions, tabData, - tabHistory + tabHistory, + activeEditorStatus } }) diff --git a/src/modules/workspace/view/MainView.vue b/src/modules/workspace/view/MainView.vue index 453e8b3e..ed0ef319 100644 --- a/src/modules/workspace/view/MainView.vue +++ b/src/modules/workspace/view/MainView.vue @@ -5,14 +5,14 @@ import { PanelType } from '@/modules/workspace/panel/model/PanelType' import WorkspacePanel from '@/modules/workspace/panel/component/WorkspacePanel.vue' import ConnectionExplorerPanel from '@/modules/connection/explorer/component/ConnectionExplorerPanel.vue' import WorkspaceTabWindowList from '@/modules/workspace/tab/component/WorkspaceTabWindowList.vue' - +import WorkspaceStatusBar from '@/modules/workspace/status-bar/component/WorkspaceStatusBar.vue' const panel = ref(PanelType.Explorer) diff --git a/yarn.lock b/yarn.lock index 8359f99a..2497db12 100644 --- a/yarn.lock +++ b/yarn.lock @@ -633,10 +633,10 @@ "@lezer/highlight" "^1.0.0" "@lezer/lr" "^1.4.0" -"@lukashornych/codemirror-lang-evitaql@1.2.5": - version "1.2.5" - resolved "https://registry.yarnpkg.com/@lukashornych/codemirror-lang-evitaql/-/codemirror-lang-evitaql-1.2.5.tgz#032ad44ab2767d6ecac2d88393b8691571685cd4" - integrity sha512-hrPHnIvCpwEVUCd1Hw8zxXghxfdrKFVF2U9nfsKiqJu36c7iKp3cJDESdySV59bNeOmf0U8ijA7TKbK9JRHXDw== +"@lukashornych/codemirror-lang-evitaql@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@lukashornych/codemirror-lang-evitaql/-/codemirror-lang-evitaql-1.3.0.tgz#eb0bd33cbed83678f99814c7eafdfed43db8d15a" + integrity sha512-VYTSE1Lvp5K73Y+1v7FVkX0eMDGWRRAT4jbsI2PpZOyCX8E/RKMAfpkpGgEUf/oIVJ9j6cbKWkqQdVGKUbxYBw== dependencies: "@codemirror/autocomplete" "6.0.0" "@codemirror/language" "^6.0.0" From bb131cb5e9fca8fd0b598cee7909c78f05d2f276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Sat, 16 Nov 2024 13:35:54 +0100 Subject: [PATCH 2/8] fix(#235): support missing languages in status bar integration --- .../extension/workspaceStatusBarIntegration.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/modules/code-editor/extension/workspaceStatusBarIntegration.ts b/src/modules/code-editor/extension/workspaceStatusBarIntegration.ts index 34b99e05..87aaeb80 100644 --- a/src/modules/code-editor/extension/workspaceStatusBarIntegration.ts +++ b/src/modules/code-editor/extension/workspaceStatusBarIntegration.ts @@ -6,15 +6,19 @@ import { language, Language } from '@codemirror/language' import { jsonLanguage } from '@codemirror/lang-json' import { evitaQLQueryLanguage, evitaQLConstraintListLanguage } from '@lukashornych/codemirror-lang-evitaql' import { graphqlLanguage } from 'cm6-graphql' +import { xmlLanguage } from '@codemirror/lang-xml' +import { yamlLanguage } from '@codemirror/lang-yaml' import { WorkspaceService } from '@/modules/workspace/service/WorkspaceService' function resolveLanguageCode(view: EditorView): string { const currentLanguage: Language | null = view.state.facet(language) - let languageCode: string = '' - if (currentLanguage == undefined) { - languageCode = 'plain' - } else if (currentLanguage === jsonLanguage) { + let languageCode: string = 'plain' + if (currentLanguage === jsonLanguage) { languageCode = 'JSON' + } else if (currentLanguage === yamlLanguage) { + languageCode = 'YAML' + } else if (currentLanguage === xmlLanguage) { + languageCode = 'XML' } else if (currentLanguage === evitaQLQueryLanguage) { languageCode = 'evitaQL' } else if (currentLanguage === evitaQLConstraintListLanguage) { From 8c3a2afc562fd72bc4afa06b08c4069c2ce2637f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Sun, 17 Nov 2024 19:17:52 +0100 Subject: [PATCH 3/8] feat(#235): global subject path support --- documentation/developer/guidelines.md | 14 ++-- .../backup-viewer/components/BackupViewer.vue | 22 +++++- .../model/BackupViewerTabDefinition.ts | 7 +- src/modules/base/component/VTabToolbar.vue | 6 +- .../base/component/VTabToolbarTitle.vue | 34 +++++++++ .../workspaceStatusBarIntegration.ts | 6 +- .../explorer/component/CatalogItem.vue | 15 ++-- .../explorer/component/CollectionItem.vue | 6 +- .../explorer/component/ConnectionItem.vue | 17 +++-- .../ConnectionSubjectPath.ts | 17 +++++ .../viewer/component/EntityViewer.vue | 34 ++++++--- .../viewer/component/Toolbar.vue | 3 +- .../model/EntityViewerTabDefinition.ts | 6 +- .../service/EntityViewerTabFactory.ts | 7 +- .../viewer/component/ErrorViewer.vue | 27 +++++-- .../model/ErrorViewerTabDefinition.ts | 6 +- .../service/ErrorViewerTabFactory.ts | 2 +- .../console/component/EvitaQLConsole.vue | 26 ++++++- .../model/EvitaQLConsoleTabDefinition.ts | 6 +- .../console/component/GraphQLConsole.vue | 42 ++++++++--- .../model/GraphQLConsoleTabDefinition.ts | 6 +- src/modules/i18n/en.json | 24 ++++++- .../jfr-viewer/components/JfrViewer.vue | 22 +++++- .../model/JfrViewerTabDefinition.ts | 6 +- .../keymap/viewer/component/KeymapViewer.vue | 23 +++++- .../model/KeymapViewerTabDefinition.ts | 6 +- .../SchemaViewerModuleRegistrar.ts | 13 +++- .../viewer/component/SchemaViewer.vue | 42 ++++++++++- .../model/AssociatedDataSchemaPointer.ts | 12 ++-- .../model/CatalogAttributeSchemaPointer.ts | 12 ++-- .../viewer/model/CatalogSchemaPointer.ts | 12 ++-- .../model/EntityAttributeSchemaPointer.ts | 13 ++-- .../viewer/model/EntitySchemaPointer.ts | 12 ++-- .../model/ReferenceAttributeSchemaPointer.ts | 23 ++++-- .../viewer/model/ReferenceSchemaPointer.ts | 12 ++-- .../viewer/model/SchemaPointer.ts | 21 ++++-- .../schema-viewer/viewer/model/SchemaType.ts | 10 +++ .../AbstractSchemaPathFactory.ts | 62 ++++++++++++++++ .../AssociatedDataSchemaPathFactory.ts | 42 +++++++++++ .../CatalogAttributeSchemaPathFactory.ts | 31 ++++++++ .../CatalogSchemaPathFactory.ts | 27 +++++++ .../DelegatingSchemaPathFactory.ts | 71 +++++++++++++++++++ .../EntityAttributeSchemaPathFactory.ts | 42 +++++++++++ .../EntitySchemaPathFactory.ts | 31 ++++++++ .../ReferenceAttributeSchemaPathFactory.ts | 52 ++++++++++++++ .../ReferenceSchemaPathFactory.ts | 42 +++++++++++ .../schema-path-factory/SchemaPathFactory.ts | 12 ++++ .../model/SchemaViewerTabDefinition.ts | 6 +- .../service/SchemaViewerTabFactory.ts | 2 +- .../server-viewer/component/ServerViewer.vue | 22 +++++- .../model/ServerViewerTabDefinition.ts | 7 +- .../task-viewer/components/TaskViewer.vue | 22 +++++- .../model/TaskViewerTabDefinition.ts | 6 +- .../workspace/service/WorkspaceService.ts | 50 ++++++++++++- ...ctiveEditorStatus.vue => EditorStatus.vue} | 4 +- .../component/WorkspaceStatusBar.vue | 25 ++++--- .../subject-path-status/SubjectPathStatus.vue | 35 +++++++++ .../SubjectPathStatusItem.vue | 46 ++++++++++++ .../SubjectPathStatusItemDelimiter.vue | 17 +++++ .../model/editor-status/ActiveEditorStatus.ts | 18 +++++ .../EditorSelection.ts} | 17 ----- .../model/subject-path-status/SubjectPath.ts | 14 ++++ .../subject-path-status/SubjectPathItem.ts | 22 ++++++ .../subject-path-status/SubjectPathStatus.ts | 47 ++++++++++++ .../subject-path-status/SystemSubjectPath.ts | 16 +++++ src/modules/workspace/store/workspaceStore.ts | 5 +- .../workspace/tab/component/TabWindow.vue | 43 +++++++++-- .../tab/component/WorkspaceTabWindowList.vue | 9 ++- .../workspace/tab/model/TabComponentEvents.ts | 2 +- .../workspace/tab/model/TabComponentExpose.ts | 11 +++ 70 files changed, 1267 insertions(+), 163 deletions(-) create mode 100644 src/modules/base/component/VTabToolbarTitle.vue create mode 100644 src/modules/connection/workspace/status-bar/model/subject-path-status/ConnectionSubjectPath.ts create mode 100644 src/modules/schema-viewer/viewer/model/SchemaType.ts create mode 100644 src/modules/schema-viewer/viewer/service/schema-path-factory/AbstractSchemaPathFactory.ts create mode 100644 src/modules/schema-viewer/viewer/service/schema-path-factory/AssociatedDataSchemaPathFactory.ts create mode 100644 src/modules/schema-viewer/viewer/service/schema-path-factory/CatalogAttributeSchemaPathFactory.ts create mode 100644 src/modules/schema-viewer/viewer/service/schema-path-factory/CatalogSchemaPathFactory.ts create mode 100644 src/modules/schema-viewer/viewer/service/schema-path-factory/DelegatingSchemaPathFactory.ts create mode 100644 src/modules/schema-viewer/viewer/service/schema-path-factory/EntityAttributeSchemaPathFactory.ts create mode 100644 src/modules/schema-viewer/viewer/service/schema-path-factory/EntitySchemaPathFactory.ts create mode 100644 src/modules/schema-viewer/viewer/service/schema-path-factory/ReferenceAttributeSchemaPathFactory.ts create mode 100644 src/modules/schema-viewer/viewer/service/schema-path-factory/ReferenceSchemaPathFactory.ts create mode 100644 src/modules/schema-viewer/viewer/service/schema-path-factory/SchemaPathFactory.ts rename src/modules/workspace/status-bar/component/{ActiveEditorStatus.vue => EditorStatus.vue} (93%) create mode 100644 src/modules/workspace/status-bar/component/subject-path-status/SubjectPathStatus.vue create mode 100644 src/modules/workspace/status-bar/component/subject-path-status/SubjectPathStatusItem.vue create mode 100644 src/modules/workspace/status-bar/component/subject-path-status/SubjectPathStatusItemDelimiter.vue create mode 100644 src/modules/workspace/status-bar/model/editor-status/ActiveEditorStatus.ts rename src/modules/workspace/status-bar/model/{ActiveEditorStatus.ts => editor-status/EditorSelection.ts} (53%) create mode 100644 src/modules/workspace/status-bar/model/subject-path-status/SubjectPath.ts create mode 100644 src/modules/workspace/status-bar/model/subject-path-status/SubjectPathItem.ts create mode 100644 src/modules/workspace/status-bar/model/subject-path-status/SubjectPathStatus.ts create mode 100644 src/modules/workspace/status-bar/model/subject-path-status/SystemSubjectPath.ts create mode 100644 src/modules/workspace/tab/model/TabComponentExpose.ts diff --git a/documentation/developer/guidelines.md b/documentation/developer/guidelines.md index 56cd334f..af4721ca 100644 --- a/documentation/developer/guidelines.md +++ b/documentation/developer/guidelines.md @@ -31,13 +31,13 @@ export function useService(): Service { } ``` -For component tree dependency injection a `dependecies.ts` file should be created where the components for a feature are +For component tree dependency injection a `dependecies.ts` file should be created where the components for a feature are places with proper injection keys and `provideX` and `injectX` methods so that the keys are not spread across components. ### UI Complex components that access data should adhere to the [Model-View-ViewModel architecture](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel), -preferably in conjunction with the [mediator pattern](https://en.wikipedia.org/wiki/Mediator_pattern) in form of +preferably in conjunction with the [mediator pattern](https://en.wikipedia.org/wiki/Mediator_pattern) in form of custom service for the component to abstract access to generic services. ![Component-service hierarchy](assets/component-service-hierarchy.svg) @@ -56,13 +56,15 @@ VConfirmDialogButton ##### Tab windows +TODO document approach to creating new tab window types and available tooling + Use `VTabToolbar` for toolbars within tab window content. ##### Lists Use `VListItemDivider` for delimiting list items in **each** non-menu lists. -Use `VListItemLazyIterator` if you need client-side with "load next" pagination for lists. Usually useful for +Use `VListItemLazyIterator` if you need client-side with "load next" pagination for lists. Usually useful for optimizing GUI rendering for lots of components. ##### Expansion panels @@ -111,13 +113,13 @@ The `master` branch is for the released versions of the evitaLab only. The `dev` code is at, and to where the feature branches are merged into. Finally, feature branches are created for each issues to fix a bug or create new feature. These are then merged into the `dev` branch for eventual release. -When fixing a bug or creating new feature, **always** create new feature branch from the `dev` branch. Or for hotfixes, +When fixing a bug or creating new feature, **always** create new feature branch from the `dev` branch. Or for hotfixes, create new bug fixing branch from the `master`, but such branch cannot do more than fix a bug in non-breaking way. ### Commits -We use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) for Git commit messages and pull requests -for 2 reasons: +We use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) for Git commit messages and pull requests +for 2 reasons: - the commits are more transparent - we have GitHub CI/CD hooked onto it, to automatically build and version the evitaLab \ No newline at end of file diff --git a/src/modules/backup-viewer/components/BackupViewer.vue b/src/modules/backup-viewer/components/BackupViewer.vue index 064a247c..974571ef 100644 --- a/src/modules/backup-viewer/components/BackupViewer.vue +++ b/src/modules/backup-viewer/components/BackupViewer.vue @@ -14,6 +14,13 @@ import TaskList from '@/modules/task-viewer/components/TaskList.vue' import BackupList from '@/modules/backup-viewer/components/BackupList.vue' import BackupCatalogButton from '@/modules/backup-viewer/components/BackupCatalogButton.vue' import RestoreLocalBackupFileButton from '@/modules/backup-viewer/components/RestoreLocalBackupFileButton.vue' +import { TabComponentExpose } from '@/modules/workspace/tab/model/TabComponentExpose' +import { SubjectPath } from '@/modules/workspace/status-bar/model/subject-path-status/SubjectPath' +import { + ConnectionSubjectPath +} from '@/modules/connection/workspace/status-bar/model/subject-path-status/ConnectionSubjectPath' +import { SubjectPathItem } from '@/modules/workspace/status-bar/model/subject-path-status/SubjectPathItem' +import { BackupViewerTabDefinition } from '@/modules/backup-viewer/model/BackupViewerTabDefinition' const shownTaskStates: TaskState[] = [TaskState.WaitingForPrecondition, TaskState.Running, TaskState.Queued, TaskState.Failed] const shownTaskTypes: string[] = [backupTaskName, restoreTaskName] @@ -22,11 +29,22 @@ const { t } = useI18n() const props = defineProps>() const emit = defineEmits() +defineExpose({ + path(): SubjectPath | undefined { + return new ConnectionSubjectPath( + props.params.connection, + [SubjectPathItem.significant( + BackupViewerTabDefinition.icon(), + t('backupViewer.title') + )] + ) + } +}) const taskListRef = ref() const backupListRef = ref() -const path: List = List([t('backupViewer.title')]) +const title: List = List.of(t('backupViewer.title')) const backupsInPreparationPresent = ref(false) @@ -44,7 +62,7 @@ emit('ready')