Skip to content

Commit

Permalink
Implemented keyboard shortcuts for copy, paste etc
Browse files Browse the repository at this point in the history
  • Loading branch information
annekekleppe committed Oct 9, 2024
1 parent db07f5f commit d4bb80f
Show file tree
Hide file tree
Showing 11 changed files with 577 additions and 231 deletions.
6 changes: 6 additions & 0 deletions notes/freon-bugs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
142 changes: 94 additions & 48 deletions packages/core-svelte/src/lib/components/FreonComponent.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -96,7 +143,6 @@
stopEvent(event);
break;
case DELETE:
LOGGER.log("FreonComponent - DELETE")
editor.deleteBox(editor.selectedBox);
stopEvent(event);
break;
Expand Down
165 changes: 165 additions & 0 deletions packages/core/src/ast-utils/AstActionExecutor.ts
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
1 change: 1 addition & 0 deletions packages/core/src/ast-utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./AstActionExecutor.js";
export * from "./AstWorker.js";
export * from "./AstWalker.js";
export * from "./AstUtil.js";
2 changes: 1 addition & 1 deletion packages/core/src/editor/FreErrorDecorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
1 change: 1 addition & 0 deletions packages/server/modelstore/Model7/extra23.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
}
],
"references": [],
"annotations": [],
"parent": null
}
]
Expand Down
1 change: 1 addition & 0 deletions packages/server/modelstore/Model7/extra23Public.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
}
],
"references": [],
"annotations": [],
"parent": null
}
]
Expand Down
Loading

0 comments on commit d4bb80f

Please sign in to comment.