diff --git a/src/extension/editors/base.ts b/src/extension/editors/base.ts index eaced3c..7194b82 100644 --- a/src/extension/editors/base.ts +++ b/src/extension/editors/base.ts @@ -45,6 +45,13 @@ export abstract class BaseEditor extends BaseWebview implemen } }) ); + disposables.push( + vscode.workspace.onDidDeleteFiles((event) => { + if (event.files.map((uri) => uri.toString()).includes(document.uri.toString())) { + void webviewPanel.dispose(); + } + }) + ); disposables.push( vscode.workspace.onDidSaveTextDocument((event) => { if (event.uri.toString() === document.uri.toString()) { @@ -53,13 +60,6 @@ export abstract class BaseEditor extends BaseWebview implemen }) ); - // Create file system watcher - const watcher = vscode.workspace.createFileSystemWatcher(document.uri.fsPath); - watcher.onDidCreate(() => this.update(document, webview, true)); - watcher.onDidChange(() => this.update(document, webview, true)); - watcher.onDidDelete(() => this.update(document, webview, true)); - disposables.push(watcher); - // Add dispose listener webviewPanel.onDidDispose(() => { this.onClose(document, webview); diff --git a/src/extension/editors/project.ts b/src/extension/editors/project.ts index c422951..54fec32 100644 --- a/src/extension/editors/project.ts +++ b/src/extension/editors/project.ts @@ -6,6 +6,11 @@ import type {GlobalStoreMessage, ViewMessage} from '../types.js'; import {BaseEditor, type EditorWebviewArgs} from './base.js'; export class ProjectEditor extends BaseEditor { + private static readonly SAVE_DEBOUNCE_WAIT = 1000; + private saveDebounceTimer: ReturnType | undefined; + + private doIgnoreSave = false; + public static getViewType() { return 'edacation.project'; } @@ -32,6 +37,12 @@ export class ProjectEditor extends BaseEditor { } } + private async whileIgnoreSave(callback: () => Promise): Promise { + this.doIgnoreSave = true; + await callback(); + this.doIgnoreSave = false; + } + protected async onDidReceiveMessage( document: vscode.TextDocument, webview: vscode.Webview, @@ -57,11 +68,16 @@ export class ProjectEditor extends BaseEditor { return true; } - const edit = new vscode.WorkspaceEdit(); - edit.replace(document.uri, new vscode.Range(0, 0, document.lineCount, 0), message.document); - await vscode.workspace.applyEdit(edit); + await this.whileIgnoreSave(async () => { + const edit = new vscode.WorkspaceEdit(); + edit.replace(document.uri, new vscode.Range(0, 0, document.lineCount, 0), message.document); + await vscode.workspace.applyEdit(edit); + }); - await document.save(); + if (this.saveDebounceTimer) clearTimeout(this.saveDebounceTimer); + this.saveDebounceTimer = setTimeout(() => { + void this.whileIgnoreSave(async () => await document.save()); + }, ProjectEditor.SAVE_DEBOUNCE_WAIT); return true; } @@ -80,12 +96,8 @@ export class ProjectEditor extends BaseEditor { // Do nothing } - protected async update(document: vscode.TextDocument, webview: vscode.Webview, isDocumentChange: boolean) { - if (isDocumentChange) { - return; - } - - await vscode.commands.executeCommand('edacation-projects.focus'); + protected async update(document: vscode.TextDocument, webview: vscode.Webview, _isDocumentChange: boolean) { + if (this.doIgnoreSave) return; const project = this.projects.get(document.uri); diff --git a/src/views/project/src/App.vue b/src/views/project/src/App.vue index 77662f4..8ed4c02 100644 --- a/src/views/project/src/App.vue +++ b/src/views/project/src/App.vue @@ -16,7 +16,7 @@ import { import {vscode} from '../../vscode'; import EDAProject from './components/EDAProject.vue'; -import {state} from './state'; +import {ignoreSave, state} from './state'; provideVSCodeDesignSystem().register( vsCodeButton(), @@ -55,7 +55,9 @@ export default { switch (event.data.type) { case 'project': - this.state.project = event.data.project; + ignoreSave(() => { + this.state.project = event.data.project; + }) break; } } diff --git a/src/views/project/src/components/EDAProject.vue b/src/views/project/src/components/EDAProject.vue index 2c74498..6db40b2 100644 --- a/src/views/project/src/components/EDAProject.vue +++ b/src/views/project/src/components/EDAProject.vue @@ -4,19 +4,28 @@ import {defineComponent} from 'vue'; import {state as globalState} from '../state'; import EDATarget from './EDATarget.vue'; +import type { TargetConfiguration } from 'edacation'; export default defineComponent({ components: { EDATarget }, computed: { - targetIndex(): number | undefined { - console.log( - 'target index', - this.state.selectedTargetIndex, - this.state.selectedTargetIndex === 'all' ? undefined : parseInt(this.state.selectedTargetIndex) - ); - return this.state.selectedTargetIndex === 'all' ? undefined : parseInt(this.state.selectedTargetIndex); + targetIndex: { + get(): number | undefined { + if (this.targets.length === 0) return undefined; + + let selectedIndex = Number(this.state.selectedTargetIndex ?? 0); + if (selectedIndex < 0) selectedIndex = 0; + if (selectedIndex >= this.targets.length) selectedIndex = this.targets.length - 1; + return selectedIndex; + }, + set(index: number) { + this.state.selectedTargetIndex = index; + } + }, + targets(): TargetConfiguration[] { + return this.state.project?.configuration.targets ?? []; } }, data() { @@ -25,29 +34,46 @@ export default defineComponent({ }; }, methods: { - handleNameChange(event: Event) { - if (!this.state.project || !event.target) { - return; + getNewTargetId(): string { + const takenIds = this.targets.map(target => target.id); + + let index = this.targets.length + 1; + while (takenIds.includes(`target${index}`)) index++; + + return `target${index}`; + }, + getDuplicateTargetId(oldId: string): string { + const match = oldId.match(/^(.*)(\d)+$/) + let base: string; + let seq: number; + if (match) { + base = match[1]; + seq = Number(match[2]) + 1; + } else { + base = oldId; + seq = 1; } - this.state.project.name = (event.target as HTMLInputElement).value; + const takenIds = this.targets.map(target => target.id); + while (takenIds.includes(`${base}${seq}`)) seq++; + + return `${base}${seq}`; }, - handleTargetChange(event: Event) { - if (!event.target) { + handleNameChange(event: Event) { + if (!this.state.project || !event.target) { return; } - this.state.selectedTargetIndex = (event.target as HTMLSelectElement).value; + this.state.project.name = (event.target as HTMLInputElement).value; }, handleTargetAdd() { if (!this.state.project) { return; } - const index = this.state.project.configuration.targets.length + 1; this.state.project.configuration.targets.push({ - id: `target${index}`, - name: `Target ${index}`, + id: this.getNewTargetId(), + name: `Target ${this.targets.length + 1}`, vendor: 'generic', family: 'generic', device: 'generic', @@ -56,13 +82,30 @@ export default defineComponent({ // TODO: This does not work, because does not yet exist, due to sync issues // this.state.selectedTargetIndex = (index - 1).toString(); }, + handleTargetDuplicate() { + const targetIndex = this.targetIndex; + if (!this.state.project || targetIndex === undefined) { + return; + } + + const curTarget = this.targets[targetIndex]; + if (!curTarget) return; + + this.state.project.configuration.targets.push({ + ...curTarget, + id: this.getDuplicateTargetId(curTarget.id), + name: `Target ${this.targets.length + 1}` + }); + // TODO: This does not work, because does not yet exist, due to sync issues + // this.state.selectedTargetIndex = (index - 1).toString(); + }, handleTargetDelete() { if (!this.state.project || this.targetIndex === undefined) { return; } this.state.project.configuration.targets.splice(this.targetIndex, 1); - this.state.selectedTargetIndex = 'all'; + this.targetIndex = 0; } } }); @@ -77,26 +120,31 @@ export default defineComponent({

Targets

Select target to configure

- - Defaults for all targets - + + {{ target.name }} + Delete target -
+
Add target + Duplicate target
-

Error: At least one target is required.

+

Error: At least one target is required.

- +