From d4bb80f17f75bddcc7197dab542857d705fd4096 Mon Sep 17 00:00:00 2001 From: Anneke Kleppe Date: Wed, 9 Oct 2024 13:13:35 +0200 Subject: [PATCH] Implemented keyboard shortcuts for copy, paste etc --- notes/freon-bugs.md | 6 + .../src/lib/components/FreonComponent.svelte | 142 ++++++++++----- .../core/src/ast-utils/AstActionExecutor.ts | 165 ++++++++++++++++++ packages/core/src/ast-utils/index.ts | 1 + packages/core/src/editor/FreErrorDecorator.ts | 2 +- .../server/modelstore/Model7/extra23.json | 1 + .../modelstore/Model7/extra23Public.json | 1 + .../server/modelstore/Model8/AnnesUnit.json | 165 ++++++++++++++++-- .../modelstore/Model8/AnnesUnitPublic.json | 165 ++++++++++++++++-- .../src/lib/language/EditorRequestsHandler.ts | 119 ++----------- .../src/lib/language/EditorState.ts | 41 +---- 11 files changed, 577 insertions(+), 231 deletions(-) create mode 100644 packages/core/src/ast-utils/AstActionExecutor.ts diff --git a/notes/freon-bugs.md b/notes/freon-bugs.md index aeaab3320..a4e69d880 100644 --- a/notes/freon-bugs.md +++ b/notes/freon-bugs.md @@ -61,3 +61,9 @@ In the external tester project the replacers for properties do not react upon an # LoggerSettings from webapp-lib to webapp-starter See heading + +# Vulnerabilities and warnings when starting webapp + +There are 4 vulnerabilities in node_modules. There are a number of warnings at the startup of the webapp. + +# Not a bug, but todo: new format for all files on server/modelstore diff --git a/packages/core-svelte/src/lib/components/FreonComponent.svelte b/packages/core-svelte/src/lib/components/FreonComponent.svelte index f9917e48f..e6d55bbe0 100644 --- a/packages/core-svelte/src/lib/components/FreonComponent.svelte +++ b/packages/core-svelte/src/lib/components/FreonComponent.svelte @@ -14,12 +14,16 @@ ARROW_LEFT, DELETE, ENTER, - ARROW_RIGHT, isNullOrUndefined, isTableRowBox, isElementBox + ARROW_RIGHT, + isNullOrUndefined, + isTableRowBox, + isElementBox, + AstActionExecutor } from "@freon4dsl/core" import RenderComponent from "./RenderComponent.svelte"; import ContextMenu from "./ContextMenu.svelte"; import { afterUpdate, onMount, tick } from "svelte"; - import { contextMenu, contextMenuVisible, selectedBoxes, viewport, componentId } from "./svelte-utils/index.js"; + import { contextMenu, contextMenuVisible, selectedBoxes, viewport, componentId } from "$lib/components/svelte-utils/index.js"; let LOGGER = new FreLogger("FreonComponent");//.mute(); export let editor: FreEditor; @@ -35,58 +39,101 @@ const onKeyDown = (event: KeyboardEvent) => { LOGGER.log("FreonComponent onKeyDown: " + event.key + " ctrl: " + event.ctrlKey + " alt: " + event.altKey + " shift: " + event.shiftKey); - // if (event.ctrlKey) { - // if (!event.altKey) { - // if (event.key === 'z') { // ctrl-z - // // todo UNDO - // } else if (event.key === 'h') { // ctrl-h - // // todo SEARCH - // event.stopPropagation(); - // } else if (event.key === 'y') { // ctrl-y - // // todo REDO - // event.stopPropagation(); - // } else if (event.key === 'x') { // ctrl-x - // // todo CUT - // event.stopPropagation(); - // } else if (event.key === 'x') { // ctrl-a - // // todo SELECT ALL in focused control - // event.stopPropagation(); - // } else if (event.key === 'c') { // ctrl-c - // // todo COPY - // } else if (event.key === 'v') { // ctrl-v - // // todo PASTE - // } - // } - // if (event.key === 'z') { // ctrl-alt-z - // // todo REDO - // } - // } else { - // if (event.altKey && event.key === BACKSPACE) { // alt-backspace - // // TODO UNDO - // } else if (!event.ctrlKey && event.altKey && event.shiftKey) { // alt-shift-backspace - // // TODO REDO - // } - // } - if (event.ctrlKey || event.altKey) { - switch (event.key) { - case ARROW_UP: // ctrl-arrow-up or alt-arrow-up - editor.selectParent(); - stopEvent(event); - break; - case ARROW_DOWN: // ctrl-arrow-down or alt-arrow-down - editor.selectFirstLeafChildBox(); - stopEvent(event); - break; + if (event.ctrlKey) { + if (!event.altKey) { + switch (event.key) { + case ARROW_UP: // ctrl-arrow-up => select element above + editor.selectParent(); + stopEvent(event); + break; + case ARROW_DOWN: // ctrl-arrow-down => select element beneath + editor.selectFirstLeafChildBox(); + stopEvent(event); + break; + case 'z': // ctrl-z => UNDO + if (!(event.target instanceof HTMLInputElement)) { + AstActionExecutor.getInstance(editor).undo(); + stopEvent(event); + } + break; + case'y': // ctrl-y => REDO + if (!(event.target instanceof HTMLInputElement)) { + AstActionExecutor.getInstance(editor).redo(); + stopEvent(event); + } + break; + case'x': // ctrl-x => CUT + if (!(event.target instanceof HTMLInputElement)) { + AstActionExecutor.getInstance(editor).cut(); + stopEvent(event); + } + break; + case'c': // ctrl-c => COPY + if (!(event.target instanceof HTMLInputElement)) { + AstActionExecutor.getInstance(editor).copy(); + stopEvent(event); + } + break; + case'v': // ctrl-v => PASTE + if (!(event.target instanceof HTMLInputElement)) { + AstActionExecutor.getInstance(editor).paste(); + stopEvent(event); + } + break; + case'h': // ctrl-h => SEARCH + // todo + stopEvent(event); + break; + case'a': // ctrl-a => SELECT ALL in focused control + // todo + // stopEvent(event); + break; + } + } else { + switch (event.key) { + case 'z': // ctrl-alt-z => REDO + if (!(event.target instanceof HTMLInputElement)) { + AstActionExecutor.getInstance(editor).redo(); + stopEvent(event); + } + break; + } + } + } else if (event.altKey) { // NO ctrl + if (event.shiftKey) { + switch (event.key) { + case BACKSPACE: // alt-shift-backspace => REDO + if (!(event.target instanceof HTMLInputElement)) { + AstActionExecutor.getInstance(editor).redo(); + stopEvent(event); + } + break; + } + } else { // NO shift + switch (event.key) { + case BACKSPACE: // alt-backspace => UNDO + if (!(event.target instanceof HTMLInputElement)) { + AstActionExecutor.getInstance(editor).undo(); + stopEvent(event); + } + break; + case ARROW_UP: // alt-arrow-up + editor.selectParent(); + stopEvent(event); + break; + case ARROW_DOWN: // alt-arrow-down + editor.selectFirstLeafChildBox(); + stopEvent(event); + break; + } } - } else if (event.shiftKey) { + } else if (event.shiftKey) { // NO ctrl, NO alt switch (event.key) { case TAB: // shift-tab editor.selectPreviousLeaf(); stopEvent(event); break; } - } else if (event.altKey) { - // All alt keys here } else { // No meta key pressed switch (event.key) { @@ -96,7 +143,6 @@ stopEvent(event); break; case DELETE: - LOGGER.log("FreonComponent - DELETE") editor.deleteBox(editor.selectedBox); stopEvent(event); break; diff --git a/packages/core/src/ast-utils/AstActionExecutor.ts b/packages/core/src/ast-utils/AstActionExecutor.ts new file mode 100644 index 000000000..416ce786d --- /dev/null +++ b/packages/core/src/ast-utils/AstActionExecutor.ts @@ -0,0 +1,165 @@ +import type {FreModelUnit, FreNode, FreOwnerDescriptor} from "../ast/index.js"; +import {AST, FreUndoManager} from "../change-manager/index.js"; +import {FreErrorSeverity} from "../validator/index.js"; +import {Box, FreEditor, isActionBox, isActionTextBox, isListBox} from "../editor/index.js"; +import {FreLanguage} from "../language/index.js"; +import {FreLogger} from "../logging/index.js"; +import {runInAction} from "mobx"; + +const LOGGER = new FreLogger("AstActionExecutor"); // .mute(); + +export class AstActionExecutor { + private static instance: AstActionExecutor | null = null; + private editor: FreEditor; + + static getInstance(editor: FreEditor): AstActionExecutor { + if (AstActionExecutor.instance === null) { + AstActionExecutor.instance = new AstActionExecutor(); + } + AstActionExecutor.instance.editor = editor; + return AstActionExecutor.instance; + } + + redo() { + if (this.editor.rootElement.freIsUnit()) { + const unitInEditor = this.editor.rootElement as FreModelUnit; + LOGGER.log(`redo called: '${FreUndoManager.getInstance().nextRedoAsText(unitInEditor)}' currentunit '${unitInEditor?.name}'`); + if (!!unitInEditor) { + FreUndoManager.getInstance().executeRedo(unitInEditor); + } + } + } + + undo() { + if (this.editor.rootElement.freIsUnit()) { + const unitInEditor = this.editor.rootElement as FreModelUnit; + LOGGER.log(`undo called: '${FreUndoManager.getInstance().nextUndoAsText(unitInEditor)}' currentunit '${unitInEditor?.name}'`); + if (!!unitInEditor) { + FreUndoManager.getInstance().executeUndo(unitInEditor); + } + } + } + + cut() { + LOGGER.log("cut called"); + const tobecut: FreNode = this.editor.selectedElement; + if (!!tobecut) { + this.deleteElement(tobecut); + this.editor.copiedElement = tobecut; + // console.log("element " + this.editor.copiedElement.freId() + " is stored "); + } else { + this.editor.setUserMessage("Nothing selected", FreErrorSeverity.Warning); + } + } + + copy() { + LOGGER.log("copy called"); + const tobecopied: FreNode = this.editor.selectedElement; + if (!!tobecopied) { + this.editor.copiedElement = tobecopied.copy(); + // console.log("element " + this.editor.copiedElement.freId() + " is stored "); + } else { + this.editor.setUserMessage("Nothing selected", FreErrorSeverity.Warning); + } + } + + paste() { + LOGGER.log("paste called"); + const tobepasted = this.editor.copiedElement; + if (!!tobepasted) { + const currentSelection: Box = this.editor.selectedBox; + const element: FreNode = currentSelection.node; + if (!!currentSelection) { + if (isActionTextBox(currentSelection)) { + if (isActionBox(currentSelection.parent)) { + if ( + FreLanguage.getInstance().metaConformsToType( + tobepasted, + currentSelection.parent.conceptName, + ) + ) { + // allow subtypes + // console.log("found text box for " + currentSelection.parent.conceptName + ", " + currentSelection.parent.propertyName); + this.pasteInElement(element, currentSelection.parent.propertyName); + } else { + this.editor.setUserMessage( + "Cannot paste an " + tobepasted.freLanguageConcept() + " here.", + FreErrorSeverity.Warning, + ); + } + } + } else if (isListBox(currentSelection.parent)) { + if (FreLanguage.getInstance().metaConformsToType(tobepasted, element.freLanguageConcept())) { + // allow subtypes + // console.log('pasting in ' + currentSelection.role + ', prop: ' + currentSelection.parent.propertyName); + this.pasteInElement( + element.freOwnerDescriptor().owner, + currentSelection.parent.propertyName, + element.freOwnerDescriptor().propertyIndex + 1, + ); + } else { + this.editor.setUserMessage( + "Cannot paste an " + tobepasted.freLanguageConcept() + " here.", + FreErrorSeverity.Warning, + ); + } + } else { + // todo other pasting options ... + } + } else { + this.editor.setUserMessage( + "Cannot paste an " + tobepasted.freLanguageConcept() + " here.", + FreErrorSeverity.Warning, + ); + } + } else { + this.editor.setUserMessage("Nothing to be pasted", FreErrorSeverity.Warning); + return; + } + } + + deleteElement(tobeDeleted: FreNode) { + if (!!tobeDeleted) { + // find the owner of the element to be deleted and remove the element there + const owner: FreNode = tobeDeleted.freOwner(); + const desc: FreOwnerDescriptor = tobeDeleted.freOwnerDescriptor(); + if (!!desc) { + // console.log("deleting " + desc.propertyName + "[" + desc.propertyIndex + "]"); + if (desc.propertyIndex !== null && desc.propertyIndex !== undefined && desc.propertyIndex >= 0) { + const propList = owner[desc.propertyName]; + if (Array.isArray(propList) && propList.length > desc.propertyIndex) { + AST.change(() => propList.splice(desc.propertyIndex, 1)); + } + } else { + AST.change(() => (owner[desc.propertyName] = null)); + } + } else { + console.error( + "deleting of " + tobeDeleted.freId() + " not succeeded, because owner descriptor is empty.", + ); + } + } + } + + private pasteInElement(element: FreNode, propertyName: string, index?: number) { + const property = element[propertyName]; + const toBePastedIn: FreNode = this.editor.copiedElement; + runInAction( () => { + this.editor.copiedElement = toBePastedIn.copy(); + }) + if (Array.isArray(property)) { + // console.log('List before: [' + property.map(x => x.freId()).join(', ') + ']'); + AST.change(() => { + if (index !== null && index !== undefined && index > 0) { + property.splice(index, 0, toBePastedIn); + } else { + property.push(toBePastedIn); + } + }); + // console.log('List after: [' + property.map(x => x.freId()).join(', ') + ']'); + } else { + // console.log('property ' + propertyName + ' is no list'); + AST.change(() => (element[propertyName] = toBePastedIn)); + } + } +} diff --git a/packages/core/src/ast-utils/index.ts b/packages/core/src/ast-utils/index.ts index 32e3efa27..ac60c7653 100644 --- a/packages/core/src/ast-utils/index.ts +++ b/packages/core/src/ast-utils/index.ts @@ -1,3 +1,4 @@ +export * from "./AstActionExecutor.js"; export * from "./AstWorker.js"; export * from "./AstWalker.js"; export * from "./AstUtil.js"; diff --git a/packages/core/src/editor/FreErrorDecorator.ts b/packages/core/src/editor/FreErrorDecorator.ts index 975636721..af7cf0a63 100644 --- a/packages/core/src/editor/FreErrorDecorator.ts +++ b/packages/core/src/editor/FreErrorDecorator.ts @@ -89,7 +89,7 @@ export class FreErrorDecorator { public gatherMessagesForGutter() { if (isNullOrUndefined(this.erroneousBoxes[0]) || isNullOrUndefined(this.erroneousBoxes[0].actualY) || this.erroneousBoxes[0].actualY === -1) { // Too early, wait for the rendering to be done - console.log("Setting errors: gathering for gutter - TOO EARLY") + // console.log("Setting errors: gathering for gutter - TOO EARLY") return; } // Sort the erroneous boxes based on their y-coordinate, because we want to gather all messages on the same 'line' diff --git a/packages/server/modelstore/Model7/extra23.json b/packages/server/modelstore/Model7/extra23.json index 13673c15f..b7ab4a586 100644 --- a/packages/server/modelstore/Model7/extra23.json +++ b/packages/server/modelstore/Model7/extra23.json @@ -38,6 +38,7 @@ } ], "references": [], + "annotations": [], "parent": null } ] diff --git a/packages/server/modelstore/Model7/extra23Public.json b/packages/server/modelstore/Model7/extra23Public.json index 13673c15f..b7ab4a586 100644 --- a/packages/server/modelstore/Model7/extra23Public.json +++ b/packages/server/modelstore/Model7/extra23Public.json @@ -38,6 +38,7 @@ } ], "references": [], + "annotations": [], "parent": null } ] diff --git a/packages/server/modelstore/Model8/AnnesUnit.json b/packages/server/modelstore/Model8/AnnesUnit.json index 11ca588fa..f44c5dc40 100644 --- a/packages/server/modelstore/Model8/AnnesUnit.json +++ b/packages/server/modelstore/Model8/AnnesUnit.json @@ -1,28 +1,157 @@ { - "$typename": "ExampleUnit", - "name": "AnnesUnit", - "entities": [ + "serializationFormatVersion": "2023.1", + "languages": [], + "nodes": [ { - "$typename": "Entity", - "name": "Robert", - "attributes": [ + "id": "ID-18", + "classifier": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-ExampleUnit" + }, + "properties": [ { - "$typename": "Attribute", - "name": "leeftijd", - "declaredType": "Integer" + "property": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-ExampleUnit-name" + }, + "value": "AnnesUnit" } ], - "methods": [ + "containments": [ { - "$typename": "Method", - "name": "", - "body": null, - "parameters": [], - "declaredType": null + "containment": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-ExampleUnit-entities" + }, + "children": [ + "ID-19" + ] + }, + { + "containment": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-ExampleUnit-methods" + }, + "children": [] + } + ], + "references": [], + "annotations": [], + "parent": null + }, + { + "id": "ID-19", + "classifier": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Entity" + }, + "properties": [ + { + "property": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-BaseType-name" + }, + "value": "Robert" + } + ], + "containments": [ + { + "containment": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Entity-attributes" + }, + "children": [ + "ID-20" + ] + }, + { + "containment": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Entity-methods" + }, + "children": [ + "ID-21" + ] + } + ], + "references": [], + "annotations": [], + "parent": "ID-18" + }, + { + "id": "ID-20", + "classifier": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Attribute" + }, + "properties": [ + { + "property": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Attribute-name" + }, + "value": "leeftijd" + } + ], + "containments": [], + "references": [ + { + "reference": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Attribute-declaredType" + }, + "targets": [ + { + "resolveInfo": "Integer", + "reference": "ID-2" + } + ] + } + ], + "annotations": [], + "parent": "ID-19" + }, + { + "id": "ID-21", + "classifier": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Method" + }, + "properties": [ + { + "property": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Method-name" + }, + "value": "" + } + ], + "containments": [ + { + "containment": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Method-parameters" + }, + "children": [] } ], - "baseEntity": null + "references": [], + "annotations": [], + "parent": "ID-19" } - ], - "methods": [] + ] } \ No newline at end of file diff --git a/packages/server/modelstore/Model8/AnnesUnitPublic.json b/packages/server/modelstore/Model8/AnnesUnitPublic.json index 11ca588fa..f44c5dc40 100644 --- a/packages/server/modelstore/Model8/AnnesUnitPublic.json +++ b/packages/server/modelstore/Model8/AnnesUnitPublic.json @@ -1,28 +1,157 @@ { - "$typename": "ExampleUnit", - "name": "AnnesUnit", - "entities": [ + "serializationFormatVersion": "2023.1", + "languages": [], + "nodes": [ { - "$typename": "Entity", - "name": "Robert", - "attributes": [ + "id": "ID-18", + "classifier": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-ExampleUnit" + }, + "properties": [ { - "$typename": "Attribute", - "name": "leeftijd", - "declaredType": "Integer" + "property": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-ExampleUnit-name" + }, + "value": "AnnesUnit" } ], - "methods": [ + "containments": [ { - "$typename": "Method", - "name": "", - "body": null, - "parameters": [], - "declaredType": null + "containment": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-ExampleUnit-entities" + }, + "children": [ + "ID-19" + ] + }, + { + "containment": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-ExampleUnit-methods" + }, + "children": [] + } + ], + "references": [], + "annotations": [], + "parent": null + }, + { + "id": "ID-19", + "classifier": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Entity" + }, + "properties": [ + { + "property": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-BaseType-name" + }, + "value": "Robert" + } + ], + "containments": [ + { + "containment": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Entity-attributes" + }, + "children": [ + "ID-20" + ] + }, + { + "containment": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Entity-methods" + }, + "children": [ + "ID-21" + ] + } + ], + "references": [], + "annotations": [], + "parent": "ID-18" + }, + { + "id": "ID-20", + "classifier": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Attribute" + }, + "properties": [ + { + "property": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Attribute-name" + }, + "value": "leeftijd" + } + ], + "containments": [], + "references": [ + { + "reference": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Attribute-declaredType" + }, + "targets": [ + { + "resolveInfo": "Integer", + "reference": "ID-2" + } + ] + } + ], + "annotations": [], + "parent": "ID-19" + }, + { + "id": "ID-21", + "classifier": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Method" + }, + "properties": [ + { + "property": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Method-name" + }, + "value": "" + } + ], + "containments": [ + { + "containment": { + "language": "-default-key-Example", + "version": "2023.1", + "key": "-default-key-Method-parameters" + }, + "children": [] } ], - "baseEntity": null + "references": [], + "annotations": [], + "parent": "ID-19" } - ], - "methods": [] + ] } \ No newline at end of file diff --git a/packages/webapp-lib/src/lib/language/EditorRequestsHandler.ts b/packages/webapp-lib/src/lib/language/EditorRequestsHandler.ts index b6f4aadc5..bf20a8a79 100644 --- a/packages/webapp-lib/src/lib/language/EditorRequestsHandler.ts +++ b/packages/webapp-lib/src/lib/language/EditorRequestsHandler.ts @@ -1,16 +1,10 @@ import { - Box, FreProjectionHandler, - isActionBox, - isActionTextBox, - isListBox, - FreLanguage, FreError, FreLogger, - FreUndoManager, FreSearcher, - FreErrorSeverity, type FreEnvironment, + AstActionExecutor, } from "@freon4dsl/core"; import type { FreNode } from "@freon4dsl/core"; import { runInAction } from "mobx"; @@ -23,8 +17,7 @@ import { searchTab, } from "../components/stores/InfoPanelStore.js"; import { EditorState } from "./EditorState.js"; -import { setUserMessage } from "../components/stores/UserMessageStore.js"; -import { WebappConfigurator } from "../WebappConfigurator.js"; +import { WebappConfigurator } from "$lib/WebappConfigurator.js"; const LOGGER = new FreLogger("EditorRequestsHandler"); // .mute(); @@ -61,113 +54,27 @@ export class EditorRequestsHandler { // this.validate(); } - /** - * Makes sure that the editor shows the current unit using the projections selected or unselected by the user - * @param name - */ - // disableProjection(name: string): void { - // LOGGER.log("disabling Projection " + name); - // const proj = this.langEnv.editor.projection; - // if (proj instanceof FreProjectionHandler) { - // proj.disableProjection(name); - // } - // } - - redo() { - const unitInEditor = EditorState.getInstance().currentUnit; - LOGGER.log(`redo called: '${FreUndoManager.getInstance().nextRedoAsText(unitInEditor)}' currentunit '${unitInEditor?.name}'` ); - if (!!unitInEditor) { - FreUndoManager.getInstance().executeRedo(unitInEditor); - } + redo = (): void => { + AstActionExecutor.getInstance(this.langEnv.editor).redo(); } - undo() { - const unitInEditor = EditorState.getInstance().currentUnit; - LOGGER.log(`undo called: '${FreUndoManager.getInstance().nextUndoAsText(unitInEditor)}' currentunit '${unitInEditor?.name}'` ); - if (!!unitInEditor) { - FreUndoManager.getInstance().executeUndo(unitInEditor); - } + undo = (): void => { + AstActionExecutor.getInstance(this.langEnv.editor).undo(); } - cut() { - LOGGER.log("cut called"); - const tobecut: FreNode = this.langEnv.editor.selectedElement; - if (!!tobecut) { - EditorState.getInstance().deleteElement(tobecut); - this.langEnv.editor.copiedElement = tobecut; - // console.log("element " + this.langEnv.editor.copiedElement.freId() + " is stored "); - } else { - setUserMessage("Nothing selected", FreErrorSeverity.Warning); - } + cut = (): void => { + AstActionExecutor.getInstance(this.langEnv.editor).cut(); } - copy() { - LOGGER.log("copy called"); - const tobecopied: FreNode = this.langEnv.editor.selectedElement; - if (!!tobecopied) { - this.langEnv.editor.copiedElement = tobecopied.copy(); - // console.log("element " + this.langEnv.editor.copiedElement.freId() + " is stored "); - } else { - setUserMessage("Nothing selected", FreErrorSeverity.Warning); - } + copy = (): void => { + AstActionExecutor.getInstance(this.langEnv.editor).copy(); } - paste() { - LOGGER.log("paste called"); - const tobepasted = this.langEnv.editor.copiedElement; - if (!!tobepasted) { - const currentSelection: Box = this.langEnv.editor.selectedBox; - const element: FreNode = currentSelection.node; - if (!!currentSelection) { - if (isActionTextBox(currentSelection)) { - if (isActionBox(currentSelection.parent)) { - if ( - FreLanguage.getInstance().metaConformsToType( - tobepasted, - currentSelection.parent.conceptName, - ) - ) { - // allow subtypes - // console.log("found text box for " + currentSelection.parent.conceptName + ", " + currentSelection.parent.propertyName); - EditorState.getInstance().pasteInElement(element, currentSelection.parent.propertyName); - } else { - setUserMessage( - "Cannot paste an " + tobepasted.freLanguageConcept() + " here.", - FreErrorSeverity.Warning, - ); - } - } - } else if (isListBox(currentSelection.parent)) { - if (FreLanguage.getInstance().metaConformsToType(tobepasted, element.freLanguageConcept())) { - // allow subtypes - // console.log('pasting in ' + currentSelection.role + ', prop: ' + currentSelection.parent.propertyName); - EditorState.getInstance().pasteInElement( - element.freOwnerDescriptor().owner, - currentSelection.parent.propertyName, - element.freOwnerDescriptor().propertyIndex + 1, - ); - } else { - setUserMessage( - "Cannot paste an " + tobepasted.freLanguageConcept() + " here.", - FreErrorSeverity.Warning, - ); - } - } else { - // todo other pasting options ... - } - } else { - setUserMessage( - "Cannot paste an " + tobepasted.freLanguageConcept() + " here.", - FreErrorSeverity.Warning, - ); - } - } else { - setUserMessage("Nothing to be pasted", FreErrorSeverity.Warning); - return; - } + paste = (): void => { + AstActionExecutor.getInstance(this.langEnv.editor).paste(); } - validate() { + validate = (): void => { LOGGER.log("validate called"); errorsLoaded.set(false); activeTab.set(errorTab); diff --git a/packages/webapp-lib/src/lib/language/EditorState.ts b/packages/webapp-lib/src/lib/language/EditorState.ts index db73cb14a..b8949a218 100644 --- a/packages/webapp-lib/src/lib/language/EditorState.ts +++ b/packages/webapp-lib/src/lib/language/EditorState.ts @@ -47,6 +47,7 @@ export class EditorState { this.modelStore.addCurrentModelListener(this.modelChanged); } + // todo see whether we can use only the editor.rootElement as currentUnit private __currentUnit: FreModelUnit = null; get currentUnit(): FreModelUnit { return this.__currentUnit; @@ -366,45 +367,5 @@ export class EditorState { } } - deleteElement(tobeDeleted: FreNode) { - if (!!tobeDeleted) { - // find the owner of the element to be deleted and remove the element there - const owner: FreNode = tobeDeleted.freOwner(); - const desc: FreOwnerDescriptor = tobeDeleted.freOwnerDescriptor(); - if (!!desc) { - // console.log("deleting " + desc.propertyName + "[" + desc.propertyIndex + "]"); - if (desc.propertyIndex !== null && desc.propertyIndex !== undefined && desc.propertyIndex >= 0) { - const propList = owner[desc.propertyName]; - if (Array.isArray(propList) && propList.length > desc.propertyIndex) { - AST.change(() => propList.splice(desc.propertyIndex, 1)); - } - } else { - AST.change(() => (owner[desc.propertyName] = null)); - } - } else { - console.error( - "deleting of " + tobeDeleted.freId() + " not succeeded, because owner descriptor is empty.", - ); - } - } - } - pasteInElement(element: FreNode, propertyName: string, index?: number) { - const property = element[propertyName]; - // todo make new copy to keep in 'this.langEnv.editor.copiedElement' - if (Array.isArray(property)) { - // console.log('List before: [' + property.map(x => x.freId()).join(', ') + ']'); - AST.change(() => { - if (index !== null && index !== undefined && index > 0) { - property.splice(index, 0, this.langEnv.editor.copiedElement); - } else { - property.push(this.langEnv.editor.copiedElement); - } - }); - // console.log('List after: [' + property.map(x => x.freId()).join(', ') + ']'); - } else { - // console.log('property ' + propertyName + ' is no list'); - AST.change(() => (element[propertyName] = this.langEnv.editor.copiedElement)); - } - } }