diff --git a/package.json b/package.json index 5050b7e5..a671d0d0 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,8 @@ "@protobuf-ts/grpcweb-transport": "^2.9.1", "@protobuf-ts/runtime": "^2.9.1", "@tauri-apps/api": "^1.5.1", + "codejar": "^4.2.0", + "highlight.js": "^11.9.0", "idb-keyval": "^6.2.1", "svelte-google-materialdesign-icons": "^0.8.2", "zod": "^3.22.4" diff --git a/src/lib/GlobalCssProperties.json b/src/lib/GlobalCssProperties.json index 9f5b3a2f..2d20ddb7 100644 --- a/src/lib/GlobalCssProperties.json +++ b/src/lib/GlobalCssProperties.json @@ -95,6 +95,18 @@ 0.39901960784, 0.43823529411 ], + "--editor-keyword-color": [ + "display-p3", + 0.52549019608, + 0.58823529411, + 0.98823529412 + ], + "--editor-comment-color": [ + "display-p3", + 0.52549019608, + 0.52549019608, + 0.52549019608 + ], "--engine-ui-underline-color": ["display-p3", 1, 1, 1], "--engine-ui-error-underline-color": [ "display-p3", @@ -247,6 +259,12 @@ 0.14666666666, 0.17019607843 ], + "--editor-keyword-color": [ + "display-p3", + 0.52549019608, + 0.58823529411, + 0.98823529412 + ], "--engine-ui-underline-color": ["display-p3", 1, 1, 1], "--engine-ui-error-underline-color": [ "display-p3", diff --git a/src/lib/classes/styling/ZodSchemas/CSSVariables.ts b/src/lib/classes/styling/ZodSchemas/CSSVariables.ts index 6d1a4ebc..cdb01a55 100644 --- a/src/lib/classes/styling/ZodSchemas/CSSVariables.ts +++ b/src/lib/classes/styling/ZodSchemas/CSSVariables.ts @@ -34,6 +34,8 @@ export const ColorVariables = z "--sidebar-text-color": ColorAttribute, "--sidebar-element-color": ColorAttribute, "--sidebar-element-hover-color": ColorAttribute, + "--editor-keyword-color": ColorAttribute, + "--editor-comment-color": ColorAttribute, "--engine-ui-underline-color": ColorAttribute, "--engine-ui-error-underline-color": ColorAttribute, "--engine-ui-input-text-placeholder-color": ColorAttribute, diff --git a/src/lib/components/canvas/Canvas.svelte b/src/lib/components/canvas/Canvas.svelte new file mode 100644 index 00000000..91d3c239 --- /dev/null +++ b/src/lib/components/canvas/Canvas.svelte @@ -0,0 +1,8 @@ + + + + diff --git a/src/lib/components/canvas/CanvasNav.svelte b/src/lib/components/canvas/CanvasNav.svelte new file mode 100644 index 00000000..0a7a6086 --- /dev/null +++ b/src/lib/components/canvas/CanvasNav.svelte @@ -0,0 +1,65 @@ + + + + + diff --git a/src/lib/components/canvas/state.ts b/src/lib/components/canvas/state.ts new file mode 100644 index 00000000..ff940457 --- /dev/null +++ b/src/lib/components/canvas/state.ts @@ -0,0 +1,54 @@ +import { Component, GlobalDeclarations, System } from "$lib/classes/automaton"; +import { activeView } from "$lib/globalState/activeProject"; +import { writable } from "svelte/store"; +import { editor } from "$lib/components/editor/state"; + +export enum CanvasModes { + Draw, + Editor, +} + +export enum CanvasSupports { + OnlyDraw, + OnlyEditor, + Both, +} + +export const canvasSupports = writable(CanvasSupports.OnlyEditor); +export const canvasModes = writable(CanvasModes.Editor); + +canvasSupports.subscribe((newSupports) => { + canvasModes.update((currentMode) => { + if ( + newSupports == CanvasSupports.OnlyEditor && + currentMode == CanvasModes.Draw + ) + return CanvasModes.Editor; + else if ( + newSupports == CanvasSupports.OnlyDraw && + currentMode == CanvasModes.Editor + ) + return CanvasModes.Draw; + else return currentMode; + }); +}); + +activeView.subscribe((view) => { + if (view instanceof Component) { + canvasSupports.set(CanvasSupports.Both); + editor.change.set(view.declarations); + editor.push.set((code: string) => { + view.declarations = code; + }); + } else if (view instanceof System) { + canvasSupports.set(CanvasSupports.OnlyDraw); + } else if (view instanceof GlobalDeclarations) { + canvasSupports.set(CanvasSupports.OnlyEditor); + editor.change.set(view.declarations); + editor.push.set((code: string) => { + view.declarations = code; + }); + } else { + canvasSupports.set(CanvasSupports.OnlyEditor); + } +}); diff --git a/src/lib/components/editor/Editor.svelte b/src/lib/components/editor/Editor.svelte new file mode 100644 index 00000000..b07188b7 --- /dev/null +++ b/src/lib/components/editor/Editor.svelte @@ -0,0 +1,131 @@ + + +
+ + diff --git a/src/lib/components/editor/state.ts b/src/lib/components/editor/state.ts new file mode 100644 index 00000000..9941e3e5 --- /dev/null +++ b/src/lib/components/editor/state.ts @@ -0,0 +1,17 @@ +import { writable } from "svelte/store"; + +/** + * A handler for the editor + * */ +export const editor = { + /** + * The writable containing the function that runs whenever the editor pushes + * */ + push: writable((s: string) => { + s; + }), + /** + * The writable that whenever set overwrites what is displayed in the editor + * */ + change: writable(""), +}; diff --git a/src/lib/components/project/globalDeclaration/GlobalDeclaration.svelte b/src/lib/components/project/globalDeclaration/GlobalDeclaration.svelte index d45cce9d..f0dbe21c 100644 --- a/src/lib/components/project/globalDeclaration/GlobalDeclaration.svelte +++ b/src/lib/components/project/globalDeclaration/GlobalDeclaration.svelte @@ -1,8 +1,12 @@ diff --git a/src/lib/components/settings/state.ts b/src/lib/components/settings/state.ts new file mode 100644 index 00000000..4deec4d1 --- /dev/null +++ b/src/lib/components/settings/state.ts @@ -0,0 +1,3 @@ +import { writable } from "svelte/store"; + +export const showSettings = writable(false); diff --git a/src/lib/components/svgView/SvgView.svelte b/src/lib/components/svgView/SvgView.svelte index 67273447..43dfb235 100644 --- a/src/lib/components/svgView/SvgView.svelte +++ b/src/lib/components/svgView/SvgView.svelte @@ -11,6 +11,8 @@ import Location from "$lib/components/svgView/Location.svelte"; import { System } from "$lib/classes/automaton"; + export let isHidden: boolean; + /** * The parent svg element that the entire view is shown with. */ @@ -62,6 +64,7 @@ on:pointerdown={(event) => panzoom?.handleDown(event)} on:wheel={(event) => panzoom?.zoomWithWheel(event)} class="panzoom-parent" + style={isHidden ? "visibility:hidden; height:0px;" : "visibility:visible;"} > >(); @@ -186,9 +190,11 @@ class ActiveViewStore implements Writable { "Cannot set an active view when no project is open", ); } else if ( - ![...projectValue.components, ...projectValue.systems].includes( - value, - ) + ![ + ...projectValue.components, + ...projectValue.systems, + projectValue.globalDeclarations, + ].includes(value) ) { throw new TypeError( "Cannot set an active view that is not part of the active project", @@ -246,9 +252,11 @@ function closeActiveViewIfDeleted() { if (activeViewValue === undefined) return; if ( !projectValue || - ![...projectValue.components, ...projectValue.systems].includes( - activeViewValue, - ) + ![ + ...projectValue.components, + ...projectValue.systems, + projectValue.globalDeclarations, + ].includes(activeViewValue) ) { activeView.set(undefined); } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 1048c621..c8af29d9 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -4,13 +4,14 @@ import TopBar from "$lib/components/topBar/TopBar.svelte"; import SidePanel from "$lib/components/sidePanel/SidePanel.svelte"; import { SidePanelEnum } from "$lib/components/sidePanel/SidePanelEnum"; - import SvgView from "$lib/components/svgView/SvgView.svelte"; import Console from "$lib/components/console/Console.svelte"; import ProjectNav from "$lib/components/project/ProjectNav.svelte"; import GlobalDeclaration from "$lib/components/project/globalDeclaration/GlobalDeclaration.svelte"; import Queries from "$lib/components/query/Queries.svelte"; import QueryNav from "$lib/components/query/QueryNav.svelte"; import ProjectItems from "$lib/components/project/ProjectItems.svelte"; + import Canvas from "$lib/components/canvas/Canvas.svelte"; + import CanvasNav from "$lib/components/canvas/CanvasNav.svelte"; @@ -31,9 +32,8 @@
- - - + +
@@ -66,12 +66,6 @@ overflow: hidden; } - .inner-nav2 { - background-color: var(--canvas-topbar-color); - color: var(--navigationbar-text-color); - border: none; - } - .canvas { color: var(--canvas-text-color); display: flex; diff --git a/tests/lib/components/editor/editor.test.ts b/tests/lib/components/editor/editor.test.ts new file mode 100644 index 00000000..cf1f143d --- /dev/null +++ b/tests/lib/components/editor/editor.test.ts @@ -0,0 +1,107 @@ +import { test, expect } from "@playwright/test"; + +test.beforeEach(async ({ page }) => { + await page.goto("/"); + await page.getByRole("button", { name: "Ecdar university" }).click(); +}); + +test("Write in editor", async ({ page }) => { + await page + .locator("#administration-1") + .getByRole("button", { name: "folder special Administration 1" }) + .click(); + const span1 = await page + .locator("#editor") + .locator(".editor-text") + .locator("span") + .innerHTML(); + expect(span1).toBe("clock"); + await expect(page.getByText("clock z;")).toBeVisible(); + + await page.locator("#editor").locator(".editor-text").click(); + for (let i = 0; i <= 8; i++) { + await page.keyboard.press("ArrowRight"); + } + await page.keyboard.press("Enter"); + + await page.keyboard.type("clock x;"); + + await page + .locator("#administration-2") + .getByRole("button", { name: "folder special Administration 2" }) + .click(); + await page + .locator("#administration-1") + .getByRole("button", { name: "folder special Administration 1" }) + .click(); + + await expect(page.getByText("clock x;")).toBeVisible(); +}); + +test("Correct line numbers in editor", async ({ page }) => { + function randomLineNumber(min: number, max: number) { + return Math.floor(Math.random() * (max - min + 1) + min); + } + + await page + .locator("#coffee-machine-1") + .getByRole("button", { name: "folder special Coffee Machine 1" }) + .click(); + await page.locator("#editor").locator(".editor-text").click(); + + const createLines = randomLineNumber(5, 30); + + for (let i = 0; i < createLines; i++) { + await page.keyboard.press("Enter"); + } + + await expect(page.locator(".editor-linenum")).toHaveCount(createLines + 1); + + const deleteLines = randomLineNumber(5, createLines); + + for (let i = 0; i < deleteLines; i++) { + await page.keyboard.press("Backspace"); + } + + await expect(page.locator(".editor-linenum")).toHaveCount( + createLines + 1 - deleteLines, + ); +}); + +test("Change between editor- and svg-view", async ({ page }) => { + await page + .locator("#administration-1") + .getByRole("button", { name: "folder special Administration 1" }) + .click(); + await expect(page.getByText("clock z;")).toBeVisible(); + + const toggleDraw = await page + .locator("#canvas-nav") + .locator("svg") + .getAttribute("id"); + expect(toggleDraw).toBe("toggle-draw"); + + await page.locator("#canvas-nav").getByRole("button").click(); + + await expect(page.getByText("L0")).toBeVisible(); + const toggleEditor = await page + .locator("#canvas-nav") + .locator("svg") + .getAttribute("id"); + expect(toggleEditor).toBe("toggle-editor"); +}); + +test("Pressing Global Declarations, removes the button to change between draw and editor", async ({ + page, +}) => { + await page + .getByRole("button", { name: "description Global Declarations" }) + .click(); + + const canvasNav = await page + .locator("#canvas-nav") + .locator("div") + .innerHTML(); + + expect(canvasNav).toBe(" "); +}); diff --git a/yarn.lock b/yarn.lock index b7835cb2..f3e40248 100644 --- a/yarn.lock +++ b/yarn.lock @@ -916,6 +916,11 @@ code-red@^1.0.3: estree-walker "^3.0.3" periscopic "^3.1.0" +codejar@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/codejar/-/codejar-4.2.0.tgz#ad72aa63f794a16900a1b079eaf44acae84460ca" + integrity sha512-U8OZe+2B400W5nSSbXxaoboBN5i1hxWdBZJ9kcTy0DBuc4muwkPE/ph/MGX4yooIE8hztfNLPNU1CbcNYch69A== + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" @@ -1382,6 +1387,11 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +highlight.js@^11.9.0: + version "11.9.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0" + integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw== + idb-keyval@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33"