diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 00589222..e3696bba 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,6 @@ # We use the Node-16 on Debian Bullseye. -ARG VARIANT="16-bullseye" -FROM mcr.microsoft.com/devcontainers/typescript-node:0-${VARIANT} +ARG VARIANT="20-bookworm" +FROM mcr.microsoft.com/devcontainers/typescript-node:${VARIANT} # Install OS packages needed for building Theia. RUN apt-get update \ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1890c9a2..fbcb8252 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -51,6 +51,7 @@ "DISPLAY": ":0", "WAYLAND_DISPLAY": "wayland-0", "XDG_RUNTIME_DIR": "/mnt/wslg/runtime-dir", - "PULSE_SERVER": "/mnt/wslg/PulseServer" + "PULSE_SERVER": "/mnt/wslg/PulseServer", + "NODE_OPTIONS": "--max-old-space-size=8192" } } diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 00f85c46..110b8f4c 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,7 +2,7 @@ on: workflow_call: env: - NODE_VERSION: 16.20.0 + NODE_VERSION: 20.x PYTHON_VERSION: 3.11.4 defaults: @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2019, ubuntu-latest, macos-11] + os: [windows-2019, ubuntu-latest, macos-12] runs-on: ${{ matrix.os }} timeout-minutes: 60 @@ -30,13 +30,13 @@ jobs: # Setup Node - name: Setup Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} # Setup Python - name: Setup Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} @@ -57,7 +57,7 @@ jobs: # Upload Unit Test Results (The different files for the OSes will end up in the same artifact). - name: Upload Unit Test Results if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: unit-test-results # Include the unit-test-results folders (which is in the root of the workspace). @@ -70,12 +70,12 @@ jobs: run: yarn --cwd ./e2e-tests/ playwright:install - name: Run Playwright tests if: runner.os == 'Linux' - uses: coactions/setup-xvfb@v1 + uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 # v1.0.1 with: run: yarn ui-test - name: Upload PlayWrite test report if: always() && runner.os == 'Linux' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: allure-results path: e2e-tests/allure-results/ @@ -95,13 +95,13 @@ jobs: steps: # Download the test results artifacts. - name: Download Unit Test Results - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: unit-test-results path: unit-test-results # Publish Test Results - name: Publish Unit Test Results - uses: EnricoMi/publish-unit-test-result-action@v2 + uses: EnricoMi/publish-unit-test-result-action@30eadd5010312f995f0d3b3cff7fe2984f69409e # v2.16.1 with: check_name: Unit Test Results files: | diff --git a/.github/workflows/cicd-main.yml b/.github/workflows/cicd-main.yml index c3f49450..48fcc8cf 100644 --- a/.github/workflows/cicd-main.yml +++ b/.github/workflows/cicd-main.yml @@ -24,10 +24,10 @@ jobs: steps: # Setup the GitHub Pages, if it doesn't exist yet. - name: Setup Pages - uses: actions/configure-pages@v3 + uses: actions/configure-pages@v5 # Download the test results artifacts. - name: Download Test Results - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: allure-results path: allure-results @@ -40,7 +40,7 @@ jobs: path: gh-pages # Generate the Allure Report - name: Generate Allure Report - uses: simple-elf/allure-report-action@master + uses: simple-elf/allure-report-action@58e6590adf6d8f196a0d771bf8a00e6921086a62 # v.1.9 with: # Where to find the allure results. allure_results: allure-results @@ -53,10 +53,10 @@ jobs: gh_pages: gh-pages # Upload allure-history report to github-pages artifact. - name: Upload Pages - uses: actions/upload-pages-artifact@v2 + uses: actions/upload-pages-artifact@v3 with: path: 'allure-history' # Deploy the github-pages artifact to GitHub pages. - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v2 + uses: actions/deploy-pages@v4 diff --git a/.vscode/launch.json b/.vscode/launch.json index 60819594..5fe64715 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -36,9 +36,6 @@ "skipFiles": ["/**"], "outFiles": [ "${workspaceFolder}/applications/electron-app/lib/**/*.js", - "${workspaceFolder}/applications/electron-app/src-gen/frontend/electron-main.js", - "${workspaceFolder}/applications/electron-app/src-gen/backend/server.js", - "${workspaceFolder}/applications/electron-app/src-gen/backend/main.js", "${workspaceFolder}/extensions/*/out/**/*.js", "${workspaceFolder}/extensions/*/out/**/*.cjs", "${workspaceFolder}/packages/*/lib/**/*.js", @@ -54,7 +51,7 @@ "type": "node", "request": "launch", "name": "Launch CrossModel Browser Backend", - "program": "${workspaceRoot}/applications/browser-app/src-gen/backend/main.js", + "program": "${workspaceRoot}/applications/browser-app/lib/backend/main.js", "cwd": "${workspaceFolder}/applications/browser-app", "protocol": "inspector", "args": [ @@ -76,9 +73,6 @@ "skipFiles": ["/**"], "outFiles": [ "${workspaceFolder}/applications/electron-app/lib/**/*.js", - "${workspaceFolder}/applications/electron-app/src-gen/frontend/electron-main.js", - "${workspaceFolder}/applications/electron-app/src-gen/backend/server.js", - "${workspaceFolder}/applications/electron-app/src-gen/backend/main.js", "${workspaceFolder}/extensions/*/out/**/*.js", "${workspaceFolder}/extensions/*/out/**/*.cjs", "${workspaceFolder}/packages/*/lib/**/*.js", diff --git a/applications/browser-app/package.json b/applications/browser-app/package.json index acaa1963..d56c44da 100644 --- a/applications/browser-app/package.json +++ b/applications/browser-app/package.json @@ -14,37 +14,38 @@ "email": "devops@crossbreeze.nl" }, "scripts": { + "bundle": "yarn rebuild && theia build --mode development && yarn download:plugins", "clean": "theia clean && rimraf lib && rimraf src-gen", "download:plugins": "theia download:plugins", - "prepare": "theia build --mode development && yarn download:plugins", "rebuild": "theia rebuild:browser --cacheRoot ../..", - "start": "yarn rebuild && theia start --plugins=local-dir:plugins", + "start": "theia start --plugins=local-dir:plugins", "test": "jest --passWithNoTests", "watch": "theia build --watch --mode development" }, "dependencies": { + "@crossbreeze/composite-editor": "0.0.0", "@crossbreeze/core": "0.0.0", "@crossbreeze/form-client": "0.0.0", "@crossbreeze/glsp-client": "0.0.0", "@crossbreeze/model-service": "^1.0.0", "@crossbreeze/product": "0.0.0", "@crossbreeze/property-view": "^1.0.0", - "@theia/core": "1.43.1", - "@theia/editor": "1.43.1", - "@theia/filesystem": "1.43.1", - "@theia/markers": "1.43.1", - "@theia/messages": "1.43.1", - "@theia/monaco": "1.43.1", - "@theia/navigator": "1.43.1", - "@theia/plugin-ext": "1.43.1", - "@theia/plugin-ext-vscode": "1.43.1", - "@theia/preferences": "1.43.1", - "@theia/process": "1.43.1", - "@theia/terminal": "1.43.1", - "@theia/workspace": "1.43.1" + "@theia/core": "1.49.1", + "@theia/editor": "1.49.1", + "@theia/filesystem": "1.49.1", + "@theia/markers": "1.49.1", + "@theia/messages": "1.49.1", + "@theia/monaco": "1.49.1", + "@theia/navigator": "1.49.1", + "@theia/plugin-ext": "1.49.1", + "@theia/plugin-ext-vscode": "1.49.1", + "@theia/preferences": "1.49.1", + "@theia/process": "1.49.1", + "@theia/terminal": "1.49.1", + "@theia/workspace": "1.49.1" }, "devDependencies": { - "@theia/cli": "1.43.1" + "@theia/cli": "1.49.1" }, "productName": "CrossModel Community Edition", "theia": { @@ -57,7 +58,13 @@ "files.associations": { "*.port": "ignore" } - } + }, + "reloadOnReconnect": true + } + }, + "backend": { + "config": { + "frontendConnectionTimeout": 86400000 } }, "generator": { diff --git a/applications/browser-app/webpack.config.js b/applications/browser-app/webpack.config.js index 3a462d78..98b34bf5 100644 --- a/applications/browser-app/webpack.config.js +++ b/applications/browser-app/webpack.config.js @@ -3,15 +3,19 @@ * To reset delete this file and rerun theia build again. */ // @ts-check -const config = require('./gen-webpack.config.js'); +const configs = require('./gen-webpack.config.js'); +const nodeConfig = require('./gen-webpack.node.config.js'); /** * Expose bundled modules on window.theia.moduleName namespace, e.g. * window['theia']['@theia/core/lib/common/uri']. * Such syntax can be used by external code, for instance, for testing. -config.module.rules.push({ +configs[0].module.rules.push({ test: /\.js$/, loader: require.resolve('@theia/application-manager/lib/expose-loader') }); */ -module.exports = config; +module.exports = [ + ...configs, + nodeConfig.config +]; diff --git a/applications/electron-app/package.json b/applications/electron-app/package.json index 72ecd7f6..f3d39b78 100644 --- a/applications/electron-app/package.json +++ b/applications/electron-app/package.json @@ -15,8 +15,7 @@ }, "main": "scripts/electron-main.js", "scripts": { - "build": "yarn bundle", - "bundle": "yarn rebuild && theia build --mode development", + "bundle": "cross-env NODE_OPTIONS=--max-old-space-size=8192 && yarn rebuild && theia build --mode development && yarn download:plugins", "clean": "theia clean && rimraf lib && rimraf src-gen", "deploy": "rimraf dist && electron-builder -c.mac.identity=null --publish always", "download:plugins": "theia download:plugins", @@ -25,36 +24,36 @@ "package:post": "rimraf plugins/crossmodel-lang* && yarn --cwd ../../extensions/crossmodel-lang symlink", "package:pre": "rimraf dist && rimraf plugins/crossmodel-lang && yarn package:extensions", "package:preview": "yarn package:pre && electron-builder -c.mac.identity=null --dir && yarn package:post", - "prepare": "cross-env NODE_OPTIONS=--max-old-space-size=8192 theia build --mode development && yarn download:plugins", "rebuild": "theia rebuild:electron --cacheRoot ../..", "start": "cross-env NODE_ENV=development theia start --plugins=local-dir:plugins", "test": "jest --passWithNoTests", "watch": "theia build --watch --mode development" }, "dependencies": { + "@crossbreeze/composite-editor": "0.0.0", "@crossbreeze/core": "0.0.0", "@crossbreeze/form-client": "0.0.0", "@crossbreeze/glsp-client": "0.0.0", "@crossbreeze/model-service": "^1.0.0", "@crossbreeze/product": "0.0.0", "@crossbreeze/property-view": "^1.0.0", - "@theia/core": "1.43.1", - "@theia/editor": "1.43.1", - "@theia/electron": "1.43.1", - "@theia/filesystem": "1.43.1", - "@theia/markers": "1.43.1", - "@theia/messages": "1.43.1", - "@theia/monaco": "1.43.1", - "@theia/navigator": "1.43.1", - "@theia/plugin-ext": "1.43.1", - "@theia/plugin-ext-vscode": "1.43.1", - "@theia/preferences": "1.43.1", - "@theia/process": "1.43.1", - "@theia/terminal": "1.43.1", - "@theia/workspace": "1.43.1" + "@theia/core": "1.49.1", + "@theia/editor": "1.49.1", + "@theia/electron": "1.49.1", + "@theia/filesystem": "1.49.1", + "@theia/markers": "1.49.1", + "@theia/messages": "1.49.1", + "@theia/monaco": "1.49.1", + "@theia/navigator": "1.49.1", + "@theia/plugin-ext": "1.49.1", + "@theia/plugin-ext-vscode": "1.49.1", + "@theia/preferences": "1.49.1", + "@theia/process": "1.49.1", + "@theia/terminal": "1.49.1", + "@theia/workspace": "1.49.1" }, "devDependencies": { - "@theia/cli": "1.43.1", + "@theia/cli": "1.49.1", "electron": "^23.2.4", "electron-builder": "^23.6.0" }, @@ -64,6 +63,7 @@ "frontend": { "config": { "applicationName": "CrossModel Community Edition", + "reloadOnReconnect": true, "preferences": { "security.workspace.trust.enabled": false, "files.associations": { @@ -72,6 +72,11 @@ } } }, + "backend": { + "config": { + "frontendConnectionTimeout": -1 + } + }, "generator": { "config": { "preloadTemplate": "./resources/preload.html" diff --git a/applications/electron-app/webpack.config.js b/applications/electron-app/webpack.config.js index d8216a67..98b34bf5 100644 --- a/applications/electron-app/webpack.config.js +++ b/applications/electron-app/webpack.config.js @@ -10,9 +10,12 @@ const nodeConfig = require('./gen-webpack.node.config.js'); * Expose bundled modules on window.theia.moduleName namespace, e.g. * window['theia']['@theia/core/lib/common/uri']. * Such syntax can be used by external code, for instance, for testing. -config.module.rules.push({ +configs[0].module.rules.push({ test: /\.js$/, loader: require.resolve('@theia/application-manager/lib/expose-loader') }); */ -module.exports = [...configs, nodeConfig.config]; +module.exports = [ + ...configs, + nodeConfig.config +]; diff --git a/docs/PrerequisitesWindows.md b/docs/PrerequisitesWindows.md index f91da188..2ce8e4dc 100644 --- a/docs/PrerequisitesWindows.md +++ b/docs/PrerequisitesWindows.md @@ -37,12 +37,21 @@ Open a new PowerShell window (this is needed after installing scoop) and execute ```powershell scoop install python@3.11.4 -scoop install yarn@1.22.19 +scoop install yarn@1.22.22 scoop install nvm -nvm install 16.20.0 -npm config set msvs_version 2022 --global +nvm install 20 +nvm use 20 +npm config edit ``` +In the editor which opens, add a line at the end with the following contents: + +``` +msvs_version=2022 +``` + +Save the file and close it. + ## Troubleshooting ### Node-Gyp issue diff --git a/e2e-tests/package.json b/e2e-tests/package.json index 3a3d7542..27733531 100644 --- a/e2e-tests/package.json +++ b/e2e-tests/package.json @@ -8,17 +8,16 @@ "email": "devops@crossbreeze.nl" }, "scripts": { - "build": "tsc -b && npx playwright install chromium", - "clean": "rimraf lib tsconfig.tsbuildinfo", + "build": "yarn && yarn clean && tsc --incremental && yarn lint && yarn playwright:install", + "clean": "rimraf lib *.tsbuildinfo .eslintcache", "lint": "eslint -c ../.eslintrc.js --ext .ts ./src", - "playwright:install": "yarn playwright install --with-deps", - "prepare": "yarn clean && yarn build && yarn lint", + "playwright:install": "playwright install chromium", "test": "", - "ui-test": "yarn playwright test" + "ui-test": "yarn build && yarn playwright test" }, "dependencies": { "@playwright/test": "^1.37.1", - "@theia/playwright": "1.43.1" + "@theia/playwright": "1.49.1" }, "devDependencies": { "allure-playwright": "^2.9.2" diff --git a/e2e-tests/src/fixtures/crossmodel-fixture.ts b/e2e-tests/src/fixtures/crossmodel-fixture.ts index 78d21df4..a0022efc 100644 --- a/e2e-tests/src/fixtures/crossmodel-fixture.ts +++ b/e2e-tests/src/fixtures/crossmodel-fixture.ts @@ -8,10 +8,9 @@ import { CrossModelWorkspace } from '../page-objects/crossmodel-workspace'; export let page: Page; export let app: CrossModelApp; -test.beforeAll(async ({ browser }) => { - page = await browser.newPage(); +test.beforeAll(async ({ browser, playwright }) => { const ws = new CrossModelWorkspace(['src/resources/sample-workspace']); - app = await CrossModelApp.load(page, ws); + app = await CrossModelApp.load({ browser, playwright }, ws); }); export default test; diff --git a/e2e-tests/src/page-objects/crossmodel-app.ts b/e2e-tests/src/page-objects/crossmodel-app.ts index 1eae5305..11d903c8 100644 --- a/e2e-tests/src/page-objects/crossmodel-app.ts +++ b/e2e-tests/src/page-objects/crossmodel-app.ts @@ -1,6 +1,15 @@ /******************************************************************************** * Copyright (c) 2023 CrossBreeze. ********************************************************************************/ -import { TheiaApp } from '@theia/playwright'; +import { PlaywrightWorkerArgs } from '@playwright/test'; +import { TheiaApp, TheiaAppFactory, TheiaAppLoader, TheiaPlaywrightTestConfig } from '@theia/playwright'; +import { CrossModelWorkspace } from './crossmodel-workspace'; -export class CrossModelApp extends TheiaApp {} +export class CrossModelApp extends TheiaApp { + public static async load( + args: TheiaPlaywrightTestConfig & PlaywrightWorkerArgs, + workspace: CrossModelWorkspace + ): Promise { + return TheiaAppLoader.load(args, workspace, CrossModelApp as TheiaAppFactory); + } +} diff --git a/e2e-tests/src/tests/crossmodel-explorer-view.spec.ts b/e2e-tests/src/tests/crossmodel-explorer-view.spec.ts index 9b932e39..7fc0f3e4 100644 --- a/e2e-tests/src/tests/crossmodel-explorer-view.spec.ts +++ b/e2e-tests/src/tests/crossmodel-explorer-view.spec.ts @@ -1,12 +1,28 @@ /******************************************************************************** * Copyright (c) 2023 CrossBreeze. ********************************************************************************/ -import { expect } from '@playwright/test'; +import { expect, Page } from '@playwright/test'; import test, { app } from '../fixtures/crossmodel-fixture'; import { CrossModelExplorerView } from '../page-objects/crossmodel-explorer-view'; let explorer: CrossModelExplorerView; +async function checkOpenWithItem(page: Page, text: string): Promise { + // Locate all elements matching the selector + const elements = await page.$$('.quick-input-list .monaco-highlighted-label'); + + // Iterate over elements to check for visibility and text content + for (const element of elements) { + if (await element.isVisible()) { + const textContent = await element.textContent(); + if (textContent?.includes(text)) { + return true; + } + } + } + return false; +} + test.describe('CrossModel Explorer View', () => { test.beforeAll(async ({ browser }) => { explorer = await app.openView(CrossModelExplorerView); @@ -20,8 +36,9 @@ test.describe('CrossModel Explorer View', () => { const menu = await file.openContextMenu(); expect(await menu.isOpen()).toBe(true); // Expect the Code and Form editor to be in the Open With menu option. - expect(await menu.menuItemByNamePath('Open With', 'Code Editor')).toBeDefined(); - expect(await menu.menuItemByNamePath('Open With', 'Form Editor')).toBeDefined(); + await menu.clickMenuItem('Open With...'); + expect(await checkOpenWithItem(explorer.page, 'Text Editor')).toBeTruthy(); + expect(await checkOpenWithItem(explorer.page, 'Form Editor')).toBeTruthy(); await menu.close(); }); @@ -32,8 +49,9 @@ test.describe('CrossModel Explorer View', () => { const menu = await file.openContextMenu(); expect(await menu.isOpen()).toBe(true); // Expect the Code and Form editor to be in the Open With menu option. - expect(await menu.menuItemByNamePath('Open With', 'Code Editor')).toBeDefined(); - expect(await menu.menuItemByNamePath('Open With', 'Form Editor')).toBeDefined(); + await menu.clickMenuItem('Open With...'); + expect(await checkOpenWithItem(explorer.page, 'Text Editor')).toBeTruthy(); + expect(await checkOpenWithItem(explorer.page, 'Form Editor')).toBeTruthy(); await menu.close(); }); @@ -44,8 +62,9 @@ test.describe('CrossModel Explorer View', () => { const menu = await file.openContextMenu(); expect(await menu.isOpen()).toBe(true); // Expect the Code and Form editor to be in the Open With menu option. - expect(await menu.menuItemByNamePath('Open With', 'Code Editor')).toBeDefined(); - expect(await menu.menuItemByNamePath('Open With', 'System Diagram')).toBeDefined(); + await menu.clickMenuItem('Open With...'); + expect(await checkOpenWithItem(explorer.page, 'Text Editor')).toBeTruthy(); + expect(await checkOpenWithItem(explorer.page, 'System Diagram')).toBeTruthy(); await menu.close(); }); }); diff --git a/extensions/crossmodel-lang/package.json b/extensions/crossmodel-lang/package.json index 4391d1e0..c1b3eb71 100644 --- a/extensions/crossmodel-lang/package.json +++ b/extensions/crossmodel-lang/package.json @@ -94,14 +94,15 @@ ], "dependencies": { "@crossbreeze/protocol": "0.0.0", - "@eclipse-glsp/layout-elk": "2.0.0", - "@eclipse-glsp/server": "2.0.0", + "@eclipse-glsp/layout-elk": "2.2.1", + "@eclipse-glsp/server": "2.2.1", "chalk": "~4.1.2", "chevrotain": "~11.0.3", "commander": "~10.0.0", "langium": "~2.1.3", "prettier": "^3.1.0", "type-fest": "^4.18.2", + "uuid": "~10.0.0", "vscode-languageclient": "9.0.1", "vscode-languageserver": "9.0.1", "vscode-languageserver-protocol": "^3.17.5", diff --git a/extensions/crossmodel-lang/src/glsp-server/mapping-diagram/command-palette/add-source-object-action-provider.ts b/extensions/crossmodel-lang/src/glsp-server/mapping-diagram/command-palette/add-source-object-action-provider.ts index e3b6fa24..7ab97f8b 100644 --- a/extensions/crossmodel-lang/src/glsp-server/mapping-diagram/command-palette/add-source-object-action-provider.ts +++ b/extensions/crossmodel-lang/src/glsp-server/mapping-diagram/command-palette/add-source-object-action-provider.ts @@ -1,11 +1,9 @@ /******************************************************************************** * Copyright (c) 2023 CrossBreeze. ********************************************************************************/ -import { AddSourceObjectOperation } from '@crossbreeze/protocol'; -import { LabeledAction } from '@eclipse-glsp/protocol'; -import { Args, CommandPaletteActionProvider, GModelElement, Point } from '@eclipse-glsp/server'; +import { AddSourceObjectOperation, codiconCSSString } from '@crossbreeze/protocol'; +import { Args, CommandPaletteActionProvider, GModelElement, LabeledAction, Point } from '@eclipse-glsp/server'; import { inject, injectable } from 'inversify'; -import { codiconCSSString } from 'sprotty'; import { SourceObject } from '../../../language-server/generated/ast.js'; import { MappingModelState } from '../model/mapping-model-state.js'; diff --git a/extensions/crossmodel-lang/src/glsp-server/mapping-diagram/mapping-diagram-module.ts b/extensions/crossmodel-lang/src/glsp-server/mapping-diagram/mapping-diagram-module.ts index c72bcb5e..96d75769 100644 --- a/extensions/crossmodel-lang/src/glsp-server/mapping-diagram/mapping-diagram-module.ts +++ b/extensions/crossmodel-lang/src/glsp-server/mapping-diagram/mapping-diagram-module.ts @@ -14,6 +14,7 @@ import { ModelSubmissionHandler, OperationHandlerConstructor, SourceModelStorage, + ToolPaletteItemProvider, bindAsService } from '@eclipse-glsp/server'; import { injectable } from 'inversify'; @@ -31,6 +32,7 @@ import { MappingDiagramConfiguration } from './mapping-diagram-configuration.js' import { MappingDiagramGModelFactory } from './model/mapping-diagram-gmodel-factory.js'; import { MappingModelIndex } from './model/mapping-model-index.js'; import { MappingModelState } from './model/mapping-model-state.js'; +import { MappingToolPaletteProvider } from './tool-palette/mapping-tool-palette-provider.js'; /** * Provides configuration about our mapping diagrams. @@ -80,4 +82,8 @@ export class MappingDiagramModule extends DiagramModule { protected override bindModelSubmissionHandler(): BindingTarget { return CrossModelSubmitHandler; } + + protected override bindToolPaletteItemProvider(): BindingTarget | undefined { + return MappingToolPaletteProvider; + } } diff --git a/extensions/crossmodel-lang/src/glsp-server/mapping-diagram/tool-palette/mapping-tool-palette-provider.ts b/extensions/crossmodel-lang/src/glsp-server/mapping-diagram/tool-palette/mapping-tool-palette-provider.ts new file mode 100644 index 00000000..602a8aac --- /dev/null +++ b/extensions/crossmodel-lang/src/glsp-server/mapping-diagram/tool-palette/mapping-tool-palette-provider.ts @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ +import { EnableToolsAction, activateDefaultToolsAction, activateDeleteToolAction } from '@crossbreeze/protocol'; +import { Args, MaybePromise, PaletteItem, ToolPaletteItemProvider } from '@eclipse-glsp/server'; +import { injectable } from 'inversify'; + +@injectable() +export class MappingToolPaletteProvider extends ToolPaletteItemProvider { + override getItems(_args?: Args | undefined): MaybePromise { + return [ + { + id: 'default-tool', + sortString: '1', + label: 'Select', + icon: 'inspect', + actions: [activateDefaultToolsAction()] + }, + { + id: 'delete-tool', + sortString: '2', + label: 'Delete', + icon: 'chrome-close', + actions: [activateDeleteToolAction()] + }, + { + id: 'source-object-create-tool', + sortString: '3', + label: 'Create Source Object', + icon: 'empty-window', + actions: [EnableToolsAction.create(['source-object-creation-tool'])] + }, + { + id: 'mapping-create-tool', + sortString: '4', + label: 'Create Mapping', + icon: 'git-compare', + actions: [EnableToolsAction.create(['mapping-edge-creation-tool'])] + } + ]; + } +} diff --git a/extensions/crossmodel-lang/src/glsp-server/system-diagram/command-palette/add-entity-action-provider.ts b/extensions/crossmodel-lang/src/glsp-server/system-diagram/command-palette/add-entity-action-provider.ts index 3ec89c80..c868ffa7 100644 --- a/extensions/crossmodel-lang/src/glsp-server/system-diagram/command-palette/add-entity-action-provider.ts +++ b/extensions/crossmodel-lang/src/glsp-server/system-diagram/command-palette/add-entity-action-provider.ts @@ -1,11 +1,9 @@ /******************************************************************************** * Copyright (c) 2023 CrossBreeze. ********************************************************************************/ -import { AddEntityOperation } from '@crossbreeze/protocol'; -import { EditorContext, LabeledAction } from '@eclipse-glsp/protocol'; -import { ContextActionsProvider, ModelState, Point } from '@eclipse-glsp/server'; +import { AddEntityOperation, codiconCSSString } from '@crossbreeze/protocol'; +import { ContextActionsProvider, EditorContext, LabeledAction, ModelState, Point } from '@eclipse-glsp/server'; import { inject, injectable } from 'inversify'; -import { codiconCSSString } from 'sprotty'; import { EntityNode } from '../../../language-server/generated/ast.js'; import { SystemModelState } from '../model/system-model-state.js'; diff --git a/extensions/crossmodel-lang/src/glsp-server/system-diagram/handler/create-entity-operation-handler.ts b/extensions/crossmodel-lang/src/glsp-server/system-diagram/handler/create-entity-operation-handler.ts new file mode 100644 index 00000000..4b1e8126 --- /dev/null +++ b/extensions/crossmodel-lang/src/glsp-server/system-diagram/handler/create-entity-operation-handler.ts @@ -0,0 +1,82 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ +import { ENTITY_NODE_TYPE } from '@crossbreeze/protocol'; +import { + ActionDispatcher, + Command, + CreateNodeOperation, + JsonCreateNodeOperationHandler, + MaybePromise, + ModelState, + Point +} from '@eclipse-glsp/server'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import { URI, Utils as UriUtils } from 'vscode-uri'; +import { CrossModelRoot, Entity, EntityNode, isCrossModelRoot } from '../../../language-server/generated/ast.js'; +import { Utils } from '../../../language-server/util/uri-util.js'; +import { CrossModelCommand } from '../../common/cross-model-command.js'; +import { SystemModelState } from '../model/system-model-state.js'; + +@injectable() +export class SystemDiagramCreateEntityOperationHandler extends JsonCreateNodeOperationHandler { + override label = 'Create Entity'; + elementTypeIds = [ENTITY_NODE_TYPE]; + + @inject(ModelState) protected override modelState!: SystemModelState; + @inject(ActionDispatcher) protected actionDispatcher!: ActionDispatcher; + + override createCommand(operation: CreateNodeOperation): MaybePromise { + return new CrossModelCommand(this.modelState, () => this.createNode(operation)); + } + + protected async createNode(operation: CreateNodeOperation): Promise { + const entity = await this.createAndSaveEntity(operation); + if (!entity) { + return; + } + const container = this.modelState.systemDiagram; + const location = this.getLocation(operation) ?? Point.ORIGIN; + const node: EntityNode = { + $type: EntityNode, + $container: container, + id: this.modelState.idProvider.findNextId(EntityNode, entity.name + 'Node', container), + entity: { + $refText: this.modelState.idProvider.getNodeId(entity) || entity.id || '', + ref: entity + }, + x: location.x, + y: location.y, + width: 10, + height: 10 + }; + container.nodes.push(node); + } + + /** + * Creates a new entity and stores it on a file on the file system. + */ + protected async createAndSaveEntity(operation: CreateNodeOperation): Promise { + // create entity, serialize and re-read to ensure everything is up to date and linked properly + const entityRoot: CrossModelRoot = { $type: 'CrossModelRoot' }; + const id = this.modelState.idProvider.findNextId(Entity, 'NewEntity'); + const entity: Entity = { + $type: 'Entity', + $container: entityRoot, + id, + name: id, + attributes: [] + }; + + const dirName = UriUtils.joinPath(UriUtils.dirname(URI.parse(this.modelState.semanticUri)), '..', 'entities'); + const targetUri = UriUtils.joinPath(dirName, entity.id + '.entity.cm'); + const uri = Utils.findNewUri(targetUri); + + entityRoot.entity = entity; + const text = this.modelState.semanticSerializer.serialize(entityRoot); + + await this.modelState.modelService.save({ uri: uri.toString(), model: text, clientId: this.modelState.clientId }); + const root = await this.modelState.modelService.request(uri.toString(), isCrossModelRoot); + return root?.entity; + } +} diff --git a/extensions/crossmodel-lang/src/glsp-server/system-diagram/handler/create-edge-operation-handler.ts b/extensions/crossmodel-lang/src/glsp-server/system-diagram/handler/create-relationship-operation-handler.ts similarity index 95% rename from extensions/crossmodel-lang/src/glsp-server/system-diagram/handler/create-edge-operation-handler.ts rename to extensions/crossmodel-lang/src/glsp-server/system-diagram/handler/create-relationship-operation-handler.ts index f5e73e7b..7773183e 100644 --- a/extensions/crossmodel-lang/src/glsp-server/system-diagram/handler/create-edge-operation-handler.ts +++ b/extensions/crossmodel-lang/src/glsp-server/system-diagram/handler/create-relationship-operation-handler.ts @@ -19,7 +19,7 @@ import { CrossModelCommand } from '../../common/cross-model-command.js'; import { SystemModelState } from '../model/system-model-state.js'; @injectable() -export class SystemDiagramCreateEdgeOperationHandler extends JsonCreateEdgeOperationHandler { +export class SystemDiagramCreateRelationsshipOperationHandler extends JsonCreateEdgeOperationHandler { override label = '1:1 Relationship'; elementTypeIds = [RELATIONSHIP_EDGE_TYPE]; @@ -84,7 +84,7 @@ export class SystemDiagramCreateEdgeOperationHandler extends JsonCreateEdgeOpera // search for unique file name for the relationship and use file base name as relationship name // if the user doesn't rename any files we should end up with unique names ;-) - const dirName = UriUtils.dirname(URI.parse(this.modelState.semanticUri)); + const dirName = UriUtils.joinPath(UriUtils.dirname(URI.parse(this.modelState.semanticUri)), '..', 'relationships'); const targetUri = UriUtils.joinPath(dirName, relationship.id + '.relationship.cm'); const uri = Utils.findNewUri(targetUri); diff --git a/extensions/crossmodel-lang/src/glsp-server/system-diagram/system-diagram-module.ts b/extensions/crossmodel-lang/src/glsp-server/system-diagram/system-diagram-module.ts index 38441563..a402e151 100644 --- a/extensions/crossmodel-lang/src/glsp-server/system-diagram/system-diagram-module.ts +++ b/extensions/crossmodel-lang/src/glsp-server/system-diagram/system-diagram-module.ts @@ -14,6 +14,7 @@ import { MultiBinding, OperationHandlerConstructor, SourceModelStorage, + ToolPaletteItemProvider, bindAsService } from '@eclipse-glsp/server'; import { injectable } from 'inversify'; @@ -24,13 +25,15 @@ import { CrossModelSubmitHandler } from '../common/cross-model-submission-handle import { SystemDiagramAddEntityActionProvider } from './command-palette/add-entity-action-provider.js'; import { SystemDiagramAddEntityOperationHandler } from './handler/add-entity-operation-handler.js'; import { SystemDiagramChangeBoundsOperationHandler } from './handler/change-bounds-operation-handler.js'; -import { SystemDiagramCreateEdgeOperationHandler } from './handler/create-edge-operation-handler.js'; +import { SystemDiagramCreateEntityOperationHandler } from './handler/create-entity-operation-handler.js'; +import { SystemDiagramCreateRelationsshipOperationHandler as SystemDiagramCreateRelationshipOperationHandler } from './handler/create-relationship-operation-handler.js'; import { SystemDiagramDeleteOperationHandler } from './handler/delete-operation-handler.js'; import { SystemDiagramDropEntityOperationHandler } from './handler/drop-entity-operation-handler.js'; import { SystemDiagramGModelFactory } from './model/system-diagram-gmodel-factory.js'; +import { SystemModelIndex } from './model/system-model-index.js'; import { SystemModelState } from './model/system-model-state.js'; import { SystemDiagramConfiguration } from './system-diagram-configuration.js'; -import { SystemModelIndex } from './model/system-model-index.js'; +import { SystemToolPaletteProvider } from './tool-palette/system-tool-palette-provider.js'; /** * Provides configuration about our system diagrams. @@ -50,10 +53,11 @@ export class SystemDiagramModule extends DiagramModule { protected override configureOperationHandlers(binding: InstanceMultiBinding): void { super.configureOperationHandlers(binding); binding.add(SystemDiagramChangeBoundsOperationHandler); // move + resize behavior - binding.add(SystemDiagramCreateEdgeOperationHandler); // create 1:1 relationship + binding.add(SystemDiagramCreateRelationshipOperationHandler); // create 1:1 relationship binding.add(SystemDiagramDeleteOperationHandler); // delete elements binding.add(SystemDiagramDropEntityOperationHandler); binding.add(SystemDiagramAddEntityOperationHandler); + binding.add(SystemDiagramCreateEntityOperationHandler); } protected override configureContextActionProviders(binding: MultiBinding): void { @@ -78,4 +82,8 @@ export class SystemDiagramModule extends DiagramModule { protected override bindModelSubmissionHandler(): BindingTarget { return CrossModelSubmitHandler; } + + protected override bindToolPaletteItemProvider(): BindingTarget | undefined { + return SystemToolPaletteProvider; + } } diff --git a/extensions/crossmodel-lang/src/glsp-server/system-diagram/tool-palette/system-tool-palette-provider.ts b/extensions/crossmodel-lang/src/glsp-server/system-diagram/tool-palette/system-tool-palette-provider.ts new file mode 100644 index 00000000..30df2b8b --- /dev/null +++ b/extensions/crossmodel-lang/src/glsp-server/system-diagram/tool-palette/system-tool-palette-provider.ts @@ -0,0 +1,62 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ +import { + ENTITY_NODE_TYPE, + ModelStructure, + RELATIONSHIP_EDGE_TYPE, + activateDefaultToolsAction, + activateDeleteToolAction +} from '@crossbreeze/protocol'; +import { + Args, + MaybePromise, + PaletteItem, + ToolPaletteItemProvider, + TriggerEdgeCreationAction, + TriggerNodeCreationAction +} from '@eclipse-glsp/server'; +import { injectable } from 'inversify'; + +@injectable() +export class SystemToolPaletteProvider extends ToolPaletteItemProvider { + override getItems(_args?: Args | undefined): MaybePromise { + return [ + { + id: 'default-tool', + sortString: '1', + label: 'Select & Move', + icon: 'inspect', + actions: [activateDefaultToolsAction()] + }, + { + id: 'hide-tool', + sortString: '2', + label: 'Hide', + icon: 'eye-closed', + actions: [activateDeleteToolAction()] + }, + { + id: 'entity-show-tool', + sortString: '3', + label: 'Show Entity', + icon: 'eye', + actions: [TriggerNodeCreationAction.create(ENTITY_NODE_TYPE, { args: { type: 'show' } })] + }, + { + id: 'entity-create-tool', + sortString: '4', + label: 'Create Entity', + icon: ModelStructure.Entity.ICON, + actions: [TriggerNodeCreationAction.create(ENTITY_NODE_TYPE, { args: { type: 'create' } })] + }, + { + id: 'relationship-create-tool', + sortString: '5', + label: 'Create 1:1 Relationship', + icon: ModelStructure.Relationship.ICON, + actions: [TriggerEdgeCreationAction.create(RELATIONSHIP_EDGE_TYPE)] + } + ]; + } +} diff --git a/extensions/crossmodel-lang/src/model-server/open-text-document-manager.ts b/extensions/crossmodel-lang/src/model-server/open-text-document-manager.ts index c5f343a4..1f8e25c3 100644 --- a/extensions/crossmodel-lang/src/model-server/open-text-document-manager.ts +++ b/extensions/crossmodel-lang/src/model-server/open-text-document-manager.ts @@ -13,6 +13,7 @@ import { LangiumDocument, LangiumDocuments } from 'langium'; +import * as path from 'path'; import { Disposable } from 'vscode-languageserver'; import { TextDocumentIdentifier, TextDocumentItem, VersionedTextDocumentIdentifier } from 'vscode-languageserver-protocol'; import { TextDocument } from 'vscode-languageserver-textdocument'; @@ -20,7 +21,6 @@ import { URI } from 'vscode-uri'; import { CrossModelLanguageMetaData } from '../language-server/generated/module.js'; import { AddedSharedModelServices } from './model-module.js'; import { OpenableTextDocuments } from './openable-text-documents.js'; - export interface UpdateInfo { changed: URI[]; deleted: URI[]; @@ -138,6 +138,8 @@ export class OpenTextDocumentManager { async save(uri: string, text: string, clientId: string): Promise { const vscUri = URI.parse(uri); + const dirName = path.dirname(vscUri.fsPath); + fs.mkdirSync(dirName, { recursive: true }); fs.writeFileSync(vscUri.fsPath, text); this.textDocuments.notifyDidSaveTextDocument({ textDocument: TextDocumentIdentifier.create(uri), text }, clientId); } diff --git a/package.json b/package.json index cff81304..c8cefbf4 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "format": "yarn prettier-eslint --write '**/*.{ts,tsx,js,cjs,mjs,css}' '**/package.json'", "postinstall": "theia check:theia-version", "lint": "lerna run lint", - "prepare": "lerna run prepare", + "prepare": "lerna run prepare && yarn theia:bundle", "start:browser": "yarn theia:browser start", "start:electron": "yarn theia:electron start", "start:verdaccio": "yarn verdaccio --config verdaccio-config.yaml", @@ -22,6 +22,7 @@ "test:cjs": "jest --config=configs/jest.config.js --passWithNoTests", "test:esm": "yarn --cwd extensions/crossmodel-lang test", "theia:browser": "yarn --cwd applications/browser-app", + "theia:bundle": "yarn theia:electron bundle && yarn theia:browser bundle", "theia:electron": "yarn --cwd applications/electron-app", "ui-test": "yarn --cwd e2e-tests ui-test", "watch": "lerna run --parallel watch" @@ -56,7 +57,7 @@ "verdaccio": "^5.23.1" }, "engines": { - "node": ">=14.18.0", + "node": ">=16", "yarn": ">=1.7.0 <2" } } diff --git a/packages/composite-editor/jest.config.js b/packages/composite-editor/jest.config.js new file mode 100644 index 00000000..b46f736c --- /dev/null +++ b/packages/composite-editor/jest.config.js @@ -0,0 +1,7 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +const baseConfig = require('../../configs/base.jest.config'); + +module.exports = { + ...baseConfig, + displayName: 'Core' +}; diff --git a/packages/composite-editor/package.json b/packages/composite-editor/package.json new file mode 100644 index 00000000..a9d1255d --- /dev/null +++ b/packages/composite-editor/package.json @@ -0,0 +1,45 @@ +{ + "name": "@crossbreeze/composite-editor", + "version": "0.0.0", + "private": true, + "description": "CrossModel Editor Contribution", + "keywords": [ + "theia-extension" + ], + "homepage": "https://github.com/CrossBreezeNL/crossmodel", + "repository": { + "type": "git", + "url": "git+https://github.com/CrossBreezeNL/crossmodel" + }, + "license": "AGPL-3.0-or-later", + "author": { + "name": "CrossBreeze", + "email": "devops@crossbreeze.nl" + }, + "files": [ + "lib", + "src" + ], + "scripts": { + "build": "tsc -b", + "clean": "rimraf lib && rimraf tsconfig.tsbuildinfo", + "lint": "eslint -c ../../.eslintrc.js --ext .ts,.tsx ./src", + "prepare": "yarn clean && yarn build", + "test": "jest --passWithNoTests", + "watch": "tsc -w" + }, + "dependencies": { + "@crossbreeze/core": "0.0.0", + "@crossbreeze/form-client": "0.0.0", + "@crossbreeze/glsp-client": "0.0.0", + "@eclipse-glsp/theia-integration": "2.2.1", + "@theia/core": "1.49.1", + "@theia/editor": "1.49.1", + "@theia/editor-preview": "1.49.1" + }, + "theiaExtensions": [ + { + "frontend": "lib/browser/editor-frontend-module" + } + ] +} diff --git a/packages/composite-editor/src/browser/cm-editor-manager.ts b/packages/composite-editor/src/browser/cm-editor-manager.ts new file mode 100644 index 00000000..ee894c2b --- /dev/null +++ b/packages/composite-editor/src/browser/cm-editor-manager.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ + +import { URI } from '@theia/core'; +import { injectable } from '@theia/core/shared/inversify'; +import { EditorPreviewManager } from '@theia/editor-preview/lib/browser/editor-preview-manager'; +import { EditorOpenerOptions, EditorWidget } from '@theia/editor/lib/browser'; + +/** + * Customization of the editor preview manager that changes the visibility of the `revealSelection` method. + * This ensures tha the `CompositeEditor` can also use this method + */ +@injectable() +export class CrossModelEditorManager extends EditorPreviewManager { + public override revealSelection(widget: EditorWidget, input?: EditorOpenerOptions, uri?: URI): void { + super.revealSelection(widget, input, uri); + } +} diff --git a/packages/composite-editor/src/browser/composite-editor-open-handler.ts b/packages/composite-editor/src/browser/composite-editor-open-handler.ts new file mode 100644 index 00000000..10060ce0 --- /dev/null +++ b/packages/composite-editor/src/browser/composite-editor-open-handler.ts @@ -0,0 +1,68 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ + +import { ModelFileExtensions, ModelFileType } from '@crossbreeze/protocol'; +import { RecursivePartial, URI } from '@theia/core'; +import { NavigatableWidgetOpenHandler, NavigatableWidgetOptions, OpenWithHandler, OpenWithService } from '@theia/core/lib/browser'; +import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { Range } from '@theia/core/shared/vscode-languageserver-types'; +import { EditorOpenerOptions } from '@theia/editor/lib/browser'; +import { CompositeEditor } from './composite-editor'; +export interface CompositeEditorOptions extends NavigatableWidgetOptions { + widgetId: string; + selection?: RecursivePartial; + fileType: Exclude; +} + +export +@injectable() +class CompositeEditorOpenHandler extends NavigatableWidgetOpenHandler implements OpenWithHandler { + static readonly ID = 'cm-composite-editor-handler'; + static readonly PRIORITY = 2000; + + @inject(OpenWithService) + protected readonly openWithService: OpenWithService; + + readonly id = CompositeEditorOpenHandler.ID; + readonly label = 'Composite Editor'; + + @postConstruct() + protected override init(): void { + super.init(); + this.openWithService.registerHandler(this); + } + + protected override createWidgetOptions(resourceUri: URI, options?: EditorOpenerOptions): CompositeEditorOptions { + const { kind, uri } = super.createWidgetOptions(resourceUri, options); + const widgetId = createCompositeEditorId(uri); + const fileType = ModelFileExtensions.getFileType(uri); + if (fileType === undefined || fileType === 'Generic') { + throw new Error(`Cannot open a composite editor for the file type ${fileType}`); + } + return { + kind, + uri, + widgetId, + fileType + }; + } + + override async open(uri: URI, options?: EditorOpenerOptions): Promise { + const widget = await super.open(uri, options); + if (options?.selection) { + widget.revealCodeTab(options); + } + return widget; + } + + canHandle(uri: URI, _options?: EditorOpenerOptions): number { + const fileType = ModelFileExtensions.getFileType(uri.path.base); + return fileType !== undefined && fileType !== 'Generic' ? CompositeEditorOpenHandler.PRIORITY : -1; + } +} + +export function createCompositeEditorId(uri: string, counter?: number): string { + // ensure we create a unique ID + return CompositeEditorOpenHandler.ID + `:${uri}` + (counter !== undefined ? `:${counter}` : ''); +} diff --git a/packages/composite-editor/src/browser/composite-editor.ts b/packages/composite-editor/src/browser/composite-editor.ts new file mode 100644 index 00000000..bc52aad7 --- /dev/null +++ b/packages/composite-editor/src/browser/composite-editor.ts @@ -0,0 +1,229 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ + +import { CrossModelWidgetOptions } from '@crossbreeze/core/lib/browser'; +import { FormEditorOpenHandler, FormEditorWidget } from '@crossbreeze/form-client/lib/browser'; +import { MappingDiagramManager, SystemDiagramManager } from '@crossbreeze/glsp-client/lib/browser/'; +import { MappingDiagramLanguage, SystemDiagramLanguage } from '@crossbreeze/glsp-client/lib/common'; +import { codiconCSSString, ModelFileType } from '@crossbreeze/protocol'; +import { FocusStateChangedAction, toTypeGuard } from '@eclipse-glsp/client'; +import { GLSPDiagramWidget, GLSPDiagramWidgetContainer, GLSPDiagramWidgetOptions } from '@eclipse-glsp/theia-integration'; +import { GLSPDiagramLanguage } from '@eclipse-glsp/theia-integration/lib/common'; +import { Emitter, Event, URI } from '@theia/core'; +import { + BaseWidget, + BoxLayout, + LabelProvider, + Message, + Navigatable, + NavigatableWidgetOptions, + Saveable, + SaveOptions, + TabPanel, + Widget, + WidgetManager +} from '@theia/core/lib/browser'; +import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { EditorPreviewWidgetFactory } from '@theia/editor-preview/lib/browser/editor-preview-widget-factory'; +import { EditorOpenerOptions, EditorWidget } from '@theia/editor/lib/browser'; +import { CrossModelEditorManager } from './cm-editor-manager'; +import { CompositeEditorOptions } from './composite-editor-open-handler'; + +@injectable() +export class CompositeEditor extends BaseWidget implements Saveable, Navigatable, Partial { + @inject(CrossModelWidgetOptions) protected options: CompositeEditorOptions; + @inject(LabelProvider) protected labelProvider: LabelProvider; + @inject(WidgetManager) protected widgetManager: WidgetManager; + @inject(CrossModelEditorManager) protected editorManager: CrossModelEditorManager; + + // We can ignore the autosave property. Child widgets will handle auto saving. + autoSave: 'off' | 'afterDelay' | 'onFocusChange' | 'onWindowChange' = 'off'; + protected tabPanel: TabPanel; + + protected onDirtyChangedEmitter = new Emitter(); + get onDirtyChanged(): Event { + return this.onDirtyChangedEmitter.event; + } + + protected _dirty = false; + get dirty(): boolean { + return this._dirty; + } + protected set dirty(value: boolean) { + this._dirty = value; + } + + protected _resourceUri?: URI; + protected get resourceUri(): URI { + if (!this._resourceUri) { + this._resourceUri = new URI(this.options.uri); + } + return this._resourceUri; + } + + get uri(): string { + return this.options.uri; + } + + get fileType(): Exclude { + return this.options.fileType; + } + + get diagramWidget(): GLSPDiagramWidget | undefined { + if (this.tabPanel.currentWidget instanceof GLSPDiagramWidget) { + return this.tabPanel.currentWidget; + } + return undefined; + } + + protected get saveables(): ReadonlyArray { + return this.tabPanel.widgets.map(widget => Saveable.get(widget)).filter((saveable): saveable is Saveable => !!saveable); + } + + @postConstruct() + protected init(): void { + this.id = this.options.widgetId; + this.addClass('cm-composite-editor'); + this.title.closable = true; + this.title.label = this.labelProvider.getName(this.resourceUri); + this.title.iconClass = ModelFileType.getIconClass(this.fileType) ?? ''; + this.initializeContent(); + } + + protected async initializeContent(): Promise { + const layout = (this.layout = new BoxLayout({ direction: 'top-to-bottom', spacing: 0 })); + this.tabPanel = new TabPanel({ tabPlacement: 'bottom', tabsMovable: false }); + BoxLayout.setStretch(this.tabPanel, 1); + this.tabPanel.currentChanged.connect((_, event) => this.handleCurrentWidgetChanged(event)); + layout.addWidget(this.tabPanel); + + const primateWidget = await this.createPrimaryWidget(); + this.tabPanel.addWidget(primateWidget); + const codeWidget = await this.createCodeWidget(); + this.tabPanel.addWidget(codeWidget); + + // Hook up dirty state change listeners + this.saveables.forEach(saveable => { + this.toDispose.push( + saveable.onDirtyChanged(() => { + this.handleWidgetDirtyStateChanged(); + }) + ); + }); + + this.update(); + } + + getResourceUri(): URI { + return new URI(this.options.uri); + } + + protected override onAfterAttach(msg: Message): void { + super.onAfterAttach(msg); + } + + protected override onActivateRequest(msg: Message): void { + super.onActivateRequest(msg); + this.tabPanel.currentWidget?.activate(); + } + + protected handleCurrentWidgetChanged(event: TabPanel.ICurrentChangedArgs): void { + // Forward focus state changes to the diagram widget + if (event.previousWidget instanceof GLSPDiagramWidget && event.previousWidget.hasFocus) { + event.previousWidget.actionDispatcher.dispatch(FocusStateChangedAction.create(false)); + } else if (event.currentWidget instanceof GLSPDiagramWidget && !event.currentWidget.hasFocus) { + event.currentWidget.actionDispatcher.dispatch(FocusStateChangedAction.create(true)); + } + } + + protected handleWidgetDirtyStateChanged(): void { + const dirty = this.saveables.some(saveable => saveable.dirty); + if (this.dirty !== dirty) { + this.dirty = dirty; + this.onDirtyChangedEmitter.fire(undefined); + } + } + + async save(options?: SaveOptions): Promise { + for (const saveable of this.saveables) { + await saveable.save(options); + } + } + + protected override onCloseRequest(msg: Message): void { + this.tabPanel.widgets.forEach(widget => widget.close()); + super.onCloseRequest(msg); + this.dispose(); + } + + protected createDiagramWidgetOptions(language: GLSPDiagramLanguage, label?: string): GLSPDiagramWidgetOptions { + return { + diagramType: language.diagramType, + kind: 'navigatable', + uri: this.uri, + iconClass: language.iconClass ?? codiconCSSString('type-hierarchy-sub'), + label: label ?? this.labelProvider.getName(this.resourceUri), + editMode: 'editable' + }; + } + + protected async createPrimaryWidget(): Promise { + switch (this.fileType) { + case 'Entity': + return this.getFormWidget(); + case 'Relationship': + return this.getFormWidget(); + case 'SystemDiagram': + return this.createSystemDiagramWidget(); + case 'Mapping': + return this.createMappingDiagramWidget(); + } + } + + protected async createCodeWidget(): Promise { + const { kind, uri, counter } = this.options; + const options: NavigatableWidgetOptions = { kind, uri, counter }; + const codeWidget = await this.widgetManager.getOrCreateWidget(EditorPreviewWidgetFactory.ID, options); + codeWidget.title.label = 'Code Editor'; + return codeWidget; + } + + protected async getFormWidget(): Promise { + const { kind, uri, counter } = this.options; + const options: NavigatableWidgetOptions = { kind, uri, counter }; + const formEditor = await this.widgetManager.getOrCreateWidget(FormEditorOpenHandler.ID, options); + this.toDispose.push( + formEditor.onModelSet(() => { + formEditor.title.label = 'Form Editor'; + }) + ); + return formEditor; + } + + protected async createSystemDiagramWidget(): Promise { + const diagramOptions = this.createDiagramWidgetOptions(SystemDiagramLanguage, 'System Diagram'); + return this.widgetManager.getOrCreateWidget(SystemDiagramManager.ID, diagramOptions); + } + + protected async createMappingDiagramWidget(): Promise { + const diagramOptions = this.createDiagramWidgetOptions(MappingDiagramLanguage, 'Mapping Diagram'); + return this.widgetManager.getOrCreateWidget(MappingDiagramManager.ID, diagramOptions); + } + + protected getCodeWidget(): EditorWidget | undefined { + return this.tabPanel.widgets.find(toTypeGuard(EditorWidget)); + } + + createMoveToUri(resourceUri: URI): URI | undefined { + return resourceUri; + } + + revealCodeTab(options: EditorOpenerOptions): void { + const codeWidget = this.getCodeWidget(); + if (codeWidget) { + this.tabPanel.currentWidget = codeWidget; + this.editorManager.revealSelection(codeWidget, options, this.resourceUri); + } + } +} diff --git a/packages/composite-editor/src/browser/editor-frontend-module.ts b/packages/composite-editor/src/browser/editor-frontend-module.ts new file mode 100644 index 00000000..05cd95aa --- /dev/null +++ b/packages/composite-editor/src/browser/editor-frontend-module.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ +import { CrossModelWidgetOptions } from '@crossbreeze/core/lib/browser'; +import { OpenHandler, WidgetFactory } from '@theia/core/lib/browser'; +import { ContainerModule } from '@theia/core/shared/inversify'; +import { EditorPreviewManager } from '@theia/editor-preview/lib/browser/editor-preview-manager'; +import { CrossModelEditorManager } from './cm-editor-manager'; +import { CompositeEditor } from './composite-editor'; +import { CompositeEditorOpenHandler, CompositeEditorOptions } from './composite-editor-open-handler'; + +export default new ContainerModule((bind, _unbind, _isBound, rebind) => { + bind(CrossModelEditorManager).toSelf().inSingletonScope(); + rebind(EditorPreviewManager).toService(CrossModelEditorManager); + + bind(CompositeEditorOpenHandler).toSelf().inSingletonScope(); + bind(OpenHandler).toService(CompositeEditorOpenHandler); + bind(WidgetFactory).toDynamicValue(context => ({ + id: CompositeEditorOpenHandler.ID, // must match the id in the open handler + createWidget: (options: CompositeEditorOptions) => { + const container = context.container.createChild(); + container.bind(CrossModelWidgetOptions).toConstantValue(options); + return container.resolve(CompositeEditor); + } + })); +}); diff --git a/packages/composite-editor/tsconfig.json b/packages/composite-editor/tsconfig.json new file mode 100644 index 00000000..67ef1d2a --- /dev/null +++ b/packages/composite-editor/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../configs/base.tsconfig.json", + "compilerOptions": { + "composite": true, + "rootDir": "src", + "outDir": "lib" + }, + "include": ["src"] +} diff --git a/packages/core/package.json b/packages/core/package.json index 791c9b52..8f2184c5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -32,11 +32,11 @@ "dependencies": { "@crossbreeze/model-service": "^1.0.0", "@crossbreeze/protocol": "0.0.0", - "@theia/core": "1.43.1", - "@theia/filesystem": "1.43.1", - "@theia/markers": "1.43.1", - "@theia/outline-view": "1.43.1", - "@theia/plugin-ext": "1.43.1", + "@theia/core": "1.49.1", + "@theia/filesystem": "1.49.1", + "@theia/markers": "1.49.1", + "@theia/outline-view": "1.49.1", + "@theia/plugin-ext": "1.49.1", "type-fest": "^4.18.2", "yaml": "^2.4.1" }, diff --git a/packages/core/src/browser/cm-file-label-provider.ts b/packages/core/src/browser/cm-file-label-provider.ts index 4e04854f..eb10940e 100644 --- a/packages/core/src/browser/cm-file-label-provider.ts +++ b/packages/core/src/browser/cm-file-label-provider.ts @@ -32,19 +32,19 @@ export class CrossModelLabelProvider implements LabelProviderContribution, TreeD getIcon(node: FileStatNode): string { if (this.isSystemDirectory(node)) { - return ModelStructure.System.ICON + ' default-folder-icon'; + return ModelStructure.System.ICON_CLASS + ' default-folder-icon'; } if (this.isSystemDirectory(node.parent) && node.fileStat.name === ModelStructure.Entity.FOLDER) { - return ModelStructure.Entity.ICON + ' default-folder-icon'; + return ModelStructure.Entity.ICON_CLASS + ' default-folder-icon'; } if (this.isSystemDirectory(node.parent) && node.fileStat.name === ModelStructure.Relationship.FOLDER) { - return ModelStructure.Relationship.ICON + ' default-folder-icon'; + return ModelStructure.Relationship.ICON_CLASS + ' default-folder-icon'; } if (this.isSystemDirectory(node.parent) && node.fileStat.name === ModelStructure.SystemDiagram.FOLDER) { - return ModelStructure.SystemDiagram.ICON + ' default-folder-icon'; + return ModelStructure.SystemDiagram.ICON_CLASS + ' default-folder-icon'; } if (this.isSystemDirectory(node.parent) && node.fileStat.name === ModelStructure.Mapping.FOLDER) { - return ModelStructure.Mapping.ICON + ' default-folder-icon'; + return ModelStructure.Mapping.ICON_CLASS + ' default-folder-icon'; } return this.labelProvider.getIcon(node.fileStat); } diff --git a/packages/core/src/browser/import-export-contribution.ts b/packages/core/src/browser/import-export-contribution.ts index 021be262..51017f13 100644 --- a/packages/core/src/browser/import-export-contribution.ts +++ b/packages/core/src/browser/import-export-contribution.ts @@ -303,7 +303,7 @@ export class ImportExportContribution implements CommandContribution, MenuContri const modelName = this.readModelId(document) ?? ModelFileExtensions.getName(origin.base); const modelContent = this.readModelContent(document); const modelType = ModelFileExtensions.detectFileType(modelContent) ?? ModelFileType.Generic; - const modelExtension = ModelFileExtensions.getFileExtension(modelType) ?? origin.ext; + const modelExtension = ModelFileType.getFileExtension(modelType) ?? origin.ext; const relativePath = this.subFolder(modelType) + modelName + modelExtension; filesToWrite.set(relativePath, modelContent); } diff --git a/packages/core/src/browser/model-widget.tsx b/packages/core/src/browser/model-widget.tsx index 4b470bf5..66ba06ef 100644 --- a/packages/core/src/browser/model-widget.tsx +++ b/packages/core/src/browser/model-widget.tsx @@ -57,6 +57,9 @@ export class CrossModelWidget extends ReactWidget implements Saveable { protected model?: Model; protected error: string | undefined; + protected readonly onModelSetEmitter = new Emitter(); + onModelSet: Event = this.onModelSetEmitter.event; + @postConstruct() init(): void { this.id = this.options.widgetId; @@ -94,6 +97,7 @@ export class CrossModelWidget extends ReactWidget implements Saveable { this.setDirty(false); this.update(); this.focusInput(); + this.onModelSetEmitter.fire(this.model); } private updateTitle(uri?: URI): void { diff --git a/packages/core/src/browser/new-element-contribution.ts b/packages/core/src/browser/new-element-contribution.ts index c16e388f..300ca4a5 100644 --- a/packages/core/src/browser/new-element-contribution.ts +++ b/packages/core/src/browser/new-element-contribution.ts @@ -53,7 +53,7 @@ const NEW_ELEMENT_TEMPLATES: NewElementTemplate[] = [ label: 'Entity', fileExtension: ModelFileExtensions.Entity, category: TEMPLATE_CATEGORY, - iconClass: ModelStructure.Entity.ICON, + iconClass: ModelStructure.Entity.ICON_CLASS, content: name => INITIAL_ENTITY_CONTENT.replace(/\$\{name\}/gi, quote(name)).replace(/\$\{id\}/gi, toId(name)) }, { @@ -61,7 +61,7 @@ const NEW_ELEMENT_TEMPLATES: NewElementTemplate[] = [ label: 'Relationship', fileExtension: ModelFileExtensions.Relationship, category: TEMPLATE_CATEGORY, - iconClass: ModelStructure.Relationship.ICON, + iconClass: ModelStructure.Relationship.ICON_CLASS, content: name => INITIAL_RELATIONSHIP_CONTENT.replace(/\$\{name\}/gi, quote(name)).replace(/\$\{id\}/gi, toId(name)) }, { @@ -69,7 +69,7 @@ const NEW_ELEMENT_TEMPLATES: NewElementTemplate[] = [ label: 'SystemDiagram', fileExtension: ModelFileExtensions.SystemDiagram, category: TEMPLATE_CATEGORY, - iconClass: ModelStructure.SystemDiagram.ICON, + iconClass: ModelStructure.SystemDiagram.ICON_CLASS, content: name => INITIAL_DIAGRAM_CONTENT.replace(/\$\{name\}/gi, quote(name)).replace(/\$\{id\}/gi, toId(name)) }, { @@ -77,7 +77,7 @@ const NEW_ELEMENT_TEMPLATES: NewElementTemplate[] = [ label: 'Mapping', fileExtension: ModelFileExtensions.Mapping, category: TEMPLATE_CATEGORY, - iconClass: ModelStructure.Mapping.ICON, + iconClass: ModelStructure.Mapping.ICON_CLASS, content: name => INITIAL_MAPPING_CONTENT.replace(/\$\{name\}/gi, quote(name)).replace(/\$\{id\}/gi, toId(name)) } ]; diff --git a/packages/core/src/node/cm-env-variable-server.ts b/packages/core/src/node/cm-env-variable-server.ts index aaeb495a..65df6ae2 100644 --- a/packages/core/src/node/cm-env-variable-server.ts +++ b/packages/core/src/node/cm-env-variable-server.ts @@ -1,8 +1,8 @@ /******************************************************************************** * Copyright (c) 2023 CrossBreeze. ********************************************************************************/ +import { FileUri } from '@theia/core/lib/common/file-uri'; import { EnvVariablesServerImpl } from '@theia/core/lib/node/env-variables'; -import { FileUri } from '@theia/core/lib/node/file-uri'; import { injectable } from 'inversify'; import { homedir } from 'os'; import * as path from 'path'; diff --git a/packages/form-client/package.json b/packages/form-client/package.json index 85c720f2..2100ee8b 100644 --- a/packages/form-client/package.json +++ b/packages/form-client/package.json @@ -33,7 +33,7 @@ "@crossbreeze/model-service": "^1.0.0", "@crossbreeze/protocol": "0.0.0", "@crossbreeze/react-model-ui": "0.0.0", - "@theia/core": "1.43.1", + "@theia/core": "1.49.1", "fast-deep-equal": "3.1.3", "p-debounce": "2.1.0" }, diff --git a/packages/form-client/src/browser/form-client-frontend-module.ts b/packages/form-client/src/browser/form-client-frontend-module.ts index cc1de9e0..21dc5b2f 100644 --- a/packages/form-client/src/browser/form-client-frontend-module.ts +++ b/packages/form-client/src/browser/form-client-frontend-module.ts @@ -2,14 +2,16 @@ * Copyright (c) 2023 CrossBreeze. ********************************************************************************/ -import { NavigatableWidgetOptions, OpenHandler, WidgetFactory } from '@theia/core/lib/browser'; +import { CrossModelWidgetOptions } from '@crossbreeze/core/lib/browser'; +import { FrontendApplicationContribution, NavigatableWidgetOptions, OpenHandler, WidgetFactory } from '@theia/core/lib/browser'; import { ContainerModule } from '@theia/core/shared/inversify'; import { FormEditorOpenHandler, createFormEditorId } from './form-editor-open-handler'; import { FormEditorWidget, FormEditorWidgetOptions } from './form-editor-widget'; -import { CrossModelWidgetOptions } from '@crossbreeze/core/lib/browser'; export default new ContainerModule(bind => { - bind(OpenHandler).to(FormEditorOpenHandler).inSingletonScope(); + bind(FormEditorOpenHandler).toSelf().inSingletonScope(); + bind(OpenHandler).toService(FormEditorOpenHandler); + bind(FrontendApplicationContribution).toService(FormEditorOpenHandler); bind(WidgetFactory).toDynamicValue(context => ({ id: FormEditorOpenHandler.ID, // must match the id in the open handler createWidget: (navigatableOptions: NavigatableWidgetOptions) => { diff --git a/packages/form-client/src/browser/form-editor-open-handler.ts b/packages/form-client/src/browser/form-editor-open-handler.ts index cecc4e1e..4afb516a 100644 --- a/packages/form-client/src/browser/form-editor-open-handler.ts +++ b/packages/form-client/src/browser/form-editor-open-handler.ts @@ -2,20 +2,35 @@ * Copyright (c) 2023 CrossBreeze. ********************************************************************************/ -import { MaybePromise, nls } from '@theia/core'; -import { NavigatableWidgetOpenHandler } from '@theia/core/lib/browser'; +import { nls } from '@theia/core'; +import { FrontendApplicationContribution, NavigatableWidgetOpenHandler, OpenWithHandler, OpenWithService } from '@theia/core/lib/browser'; import URI from '@theia/core/lib/common/uri'; -import { injectable } from '@theia/core/shared/inversify'; +import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; import { FormEditorWidget } from './form-editor-widget'; @injectable() -export class FormEditorOpenHandler extends NavigatableWidgetOpenHandler { +export class FormEditorOpenHandler + extends NavigatableWidgetOpenHandler + implements OpenWithHandler, FrontendApplicationContribution +{ static ID = 'form-editor-opener'; readonly id = FormEditorOpenHandler.ID; // must match the id of the widget factory readonly label = nls.localize('form-client/form-editor', 'Form Editor'); - canHandle(uri: URI): MaybePromise { + @inject(OpenWithService) protected readonly openWithService: OpenWithService; + + initialize(): void { + // ensure this class is instantiated early + } + + @postConstruct() + protected override init(): void { + this.openWithService.registerHandler(this); + super.init(); + } + + canHandle(uri: URI): number { return uri.path.ext === '.cm' ? 1 : -1; } } diff --git a/packages/form-client/src/browser/index.ts b/packages/form-client/src/browser/index.ts new file mode 100644 index 00000000..cc74aaba --- /dev/null +++ b/packages/form-client/src/browser/index.ts @@ -0,0 +1,5 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ +export * from './form-editor-open-handler'; +export * from './form-editor-widget'; diff --git a/packages/glsp-client/package.json b/packages/glsp-client/package.json index c17565b0..6850af0d 100644 --- a/packages/glsp-client/package.json +++ b/packages/glsp-client/package.json @@ -32,16 +32,16 @@ "dependencies": { "@crossbreeze/core": "0.0.0", "@crossbreeze/protocol": "0.0.0", - "@eclipse-glsp/client": "2.0.0", - "@eclipse-glsp/theia-integration": "2.0.0", - "@theia/core": "1.43.1", - "@theia/filesystem": "1.43.1", - "@theia/markers": "1.43.1", - "@theia/navigator": "1.43.1", - "@theia/outline-view": "1.43.1", - "@theia/output": "1.43.1", - "@theia/preferences": "1.43.1", - "@theia/task": "1.43.1" + "@eclipse-glsp/client": "2.2.1", + "@eclipse-glsp/theia-integration": "2.2.1", + "@theia/core": "1.49.1", + "@theia/filesystem": "1.49.1", + "@theia/markers": "1.49.1", + "@theia/navigator": "1.49.1", + "@theia/outline-view": "1.49.1", + "@theia/output": "1.49.1", + "@theia/preferences": "1.49.1", + "@theia/task": "1.49.1" }, "theiaExtensions": [ { diff --git a/packages/glsp-client/src/browser/cross-model-command-palette.ts b/packages/glsp-client/src/browser/cross-model-command-palette.ts new file mode 100644 index 00000000..acad8ce5 --- /dev/null +++ b/packages/glsp-client/src/browser/cross-model-command-palette.ts @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ + +import { AddEntityOperation } from '@crossbreeze/protocol'; +import { + Action, + GModelRoot, + GlspCommandPalette, + InsertIndicator, + LabeledAction, + Point, + getAbsoluteClientBounds +} from '@eclipse-glsp/client'; +import { injectable } from '@theia/core/shared/inversify'; + +@injectable() +export class CrossModelCommandPalette extends GlspCommandPalette { + protected visible = false; + protected creationPosition?: Point; + + protected override onBeforeShow(containerElement: HTMLElement, root: Readonly, ...contextElementIds: string[]): void { + if (contextElementIds.length === 1) { + const element = root.index.getById(contextElementIds[0]); + if (element instanceof InsertIndicator) { + this.creationPosition = element.position; + const bounds = getAbsoluteClientBounds(element, this.domHelper, this.viewerOptions); + containerElement.style.left = `${bounds.x}px`; + containerElement.style.top = `${bounds.y}px`; + containerElement.style.width = `${this.defaultWidth}px`; + return; + } + } + super.onBeforeShow(containerElement, root, ...contextElementIds); + } + + protected override executeAction(input: LabeledAction | Action | Action[]): void { + if (this.creationPosition && LabeledAction.is(input) && AddEntityOperation.is(input.actions[0])) { + const action = input.actions[0]; + action.position = this.creationPosition; + return super.executeAction(action); + } + super.executeAction(input); + } + + override hide(): void { + super.hide(); + this.creationPosition = undefined; + this.visible = false; + } +} diff --git a/packages/glsp-client/src/browser/cross-model-delete-tool.ts b/packages/glsp-client/src/browser/cross-model-delete-tool.ts new file mode 100644 index 00000000..ee13296a --- /dev/null +++ b/packages/glsp-client/src/browser/cross-model-delete-tool.ts @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ + +import { + Action, + DeleteElementOperation, + DeleteToolMouseListener, + GModelElement, + MouseDeleteTool, + findParentByFeature, + isDeletable +} from '@eclipse-glsp/client'; +import { injectable } from '@theia/core/shared/inversify'; + +export class CrossModelMouseDeleteTool extends MouseDeleteTool { + protected override deleteToolMouseListener: DeleteToolMouseListener = new CrossModelDeleteMouseListener(); +} + +@injectable() +export class CrossModelDeleteMouseListener extends DeleteToolMouseListener { + override mouseUp(target: GModelElement, event: MouseEvent): Action[] { + const deletableParent = findParentByFeature(target, isDeletable); + if (deletableParent === undefined) { + return []; + } + const result: Action[] = []; + result.push(DeleteElementOperation.create([deletableParent.id])); + return result; + } +} diff --git a/packages/glsp-client/src/browser/cross-model-diagram-startup.ts b/packages/glsp-client/src/browser/cross-model-diagram-startup.ts new file mode 100644 index 00000000..68df9abf --- /dev/null +++ b/packages/glsp-client/src/browser/cross-model-diagram-startup.ts @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ +import { GridManager, IDiagramStartup, MaybePromise } from '@eclipse-glsp/client'; +import { inject, injectable, optional } from '@theia/core/shared/inversify'; + +@injectable() +export class CrossModelDiagramStartup implements IDiagramStartup { + rank = -1; + + @inject(GridManager) @optional() protected gridManager?: GridManager; + + preRequestModel(): MaybePromise { + this.gridManager?.setGridVisible(true); + } +} diff --git a/packages/glsp-client/src/browser/cross-model-tool-palette.ts b/packages/glsp-client/src/browser/cross-model-tool-palette.ts new file mode 100644 index 00000000..55d8b401 --- /dev/null +++ b/packages/glsp-client/src/browser/cross-model-tool-palette.ts @@ -0,0 +1,115 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ +import { + Action, + EnableDefaultToolsAction, + FitToScreenAction, + ICommand, + PaletteItem, + RequestContextActions, + SetContextActions, + SetModelAction, + ToolPalette, + UpdateModelAction, + createIcon +} from '@eclipse-glsp/client'; +import { injectable } from '@theia/core/shared/inversify'; +const CLICKED_CSS_CLASS = 'clicked'; + +@injectable() +export class CrossModelToolPalette extends ToolPalette { + protected readonly defaultToolsBtnId = 'default-tool'; + protected override initializeContents(containerElement: HTMLElement): void { + this.createHeader(); + this.createBody(); + this.changeActiveButton(this.defaultToolsButton); + containerElement.setAttribute('aria-label', 'Tool-Palette'); + } + + protected override createHeaderTitle(): HTMLElement { + const header = document.createElement('div'); + header.classList.add('header-icon'); + header.appendChild(createIcon('tools')); + header.insertAdjacentText('beforeend', 'Toolbox'); + return header; + } + + protected override createHeaderTools(): HTMLElement { + const headerTools = document.createElement('div'); + headerTools.classList.add('header-tools'); + + const resetViewportButton = this.createResetViewportButton(); + headerTools.appendChild(resetViewportButton); + + const fitToScreenButton = this.createFitToScreenButton(); + headerTools.appendChild(fitToScreenButton); + + if (this.gridManager) { + const toggleGridButton = this.createToggleGridButton(); + headerTools.appendChild(toggleGridButton); + } + + return headerTools; + } + + protected override createToolButton(item: PaletteItem, index: number): HTMLElement { + const button = super.createToolButton(item, index); + if (item.id === this.defaultToolsBtnId) { + this.defaultToolsButton = button; + } + return button; + } + + protected createFitToScreenButton(): HTMLElement { + const fitToScreenButton = createIcon('screen-full'); + fitToScreenButton.title = 'Fit to Screen'; + fitToScreenButton.onclick = _event => { + this.actionDispatcher.dispatch(FitToScreenAction.create([])); + fitToScreenButton.focus(); + }; + fitToScreenButton.ariaLabel = fitToScreenButton.title; + fitToScreenButton.tabIndex = 1; + return fitToScreenButton; + } + + protected override async setPaletteItems(): Promise { + super.setPaletteItems(); + this.changeActiveButton(); + const requestAction = RequestContextActions.create({ + contextId: ToolPalette.ID, + editorContext: { + selectedElementIds: [] + } + }); + const response = await this.actionDispatcher.request(requestAction); + this.paletteItems = response.actions.map(action => action as PaletteItem); + this.dynamic = this.paletteItems.some(item => this.hasDynamicAction(item)); + } + + override changeActiveButton(button?: HTMLElement): void { + if (this.lastActiveButton) { + this.lastActiveButton.classList.remove(CLICKED_CSS_CLASS); + } + if (button) { + button.classList.add(CLICKED_CSS_CLASS); + this.lastActiveButton = button; + } else if (this.defaultToolsButton) { + this.defaultToolsButton.classList.add(CLICKED_CSS_CLASS); + this.lastActiveButton = this.defaultToolsButton; + this.defaultToolsButton.focus(); + } + } + + override handle(action: Action): ICommand | Action | void { + if (UpdateModelAction.is(action) || SetModelAction.is(action)) { + this.reloadPaletteBody(); + } else if (EnableDefaultToolsAction.is(action)) { + this.changeActiveButton(this.defaultToolsButton); + if (this.focusTracker.hasFocus) { + // if focus was deliberately taken do not restore focus to the palette + this.focusTracker.diagramElement?.focus(); + } + } + } +} diff --git a/packages/glsp-client/src/browser/crossmodel-client-contribution.ts b/packages/glsp-client/src/browser/crossmodel-client-contribution.ts index f3822caf..05d3d545 100644 --- a/packages/glsp-client/src/browser/crossmodel-client-contribution.ts +++ b/packages/glsp-client/src/browser/crossmodel-client-contribution.ts @@ -1,7 +1,7 @@ /******************************************************************************** * Copyright (c) 2023 CrossBreeze. ********************************************************************************/ -import { Action, ActionMessage, ActionMessageHandler, ConnectionProvider, GLSPClient, JsonrpcGLSPClient } from '@eclipse-glsp/protocol'; +import { Action, ActionMessage, ActionMessageHandler, ConnectionProvider, GLSPClient, JsonrpcGLSPClient } from '@eclipse-glsp/client'; import { BaseGLSPClientContribution, TheiaJsonrpcGLSPClient } from '@eclipse-glsp/theia-integration'; import { Emitter } from '@theia/core'; import { Deferred } from '@theia/core/lib/common/promise-util'; diff --git a/packages/glsp-client/src/browser/crossmodel-diagram-module.ts b/packages/glsp-client/src/browser/crossmodel-diagram-module.ts index fb04dfed..14e9bb12 100644 --- a/packages/glsp-client/src/browser/crossmodel-diagram-module.ts +++ b/packages/glsp-client/src/browser/crossmodel-diagram-module.ts @@ -2,31 +2,33 @@ * Copyright (c) 2024 CrossBreeze. ********************************************************************************/ -import { - ConsoleLogger, - LogLevel, - SetViewportAction, - TYPES, - bindAsService, - bindOrRebind, - configureActionHandler -} from '@eclipse-glsp/client'; -import { TheiaGLSPSelectionForwarder } from '@eclipse-glsp/theia-integration'; +import { GRID } from '@crossbreeze/protocol'; +import { ConsoleLogger, GlspCommandPalette, LogLevel, MouseDeleteTool, TYPES, ToolPalette, bindAsService } from '@eclipse-glsp/client'; +import { GlspSelectionDataService, TheiaGLSPSelectionForwarder } from '@eclipse-glsp/theia-integration'; import { ContainerModule, interfaces } from '@theia/core/shared/inversify'; -import { GridAlignmentHandler } from './crossmodel-grid-handler'; -import { CrossModelGridSnapper } from './crossmodel-grid-snapper'; +import { CrossModelCommandPalette } from './cross-model-command-palette'; +import { CrossModelMouseDeleteTool } from './cross-model-delete-tool'; +import { CrossModelDiagramStartup } from './cross-model-diagram-startup'; +import { CrossModelToolPalette } from './cross-model-tool-palette'; import { CrossModelGLSPSelectionDataService } from './crossmodel-selection-data-service'; -import { CrossModelSelectionDataService, CrossModelTheiaGLSPSelectionForwarder } from './crossmodel-selection-forwarder'; export function createCrossModelDiagramModule(registry: interfaces.ContainerModuleCallBack): ContainerModule { return new ContainerModule((bind, unbind, isBound, rebind, unbindAsync, onActivation, onDeactivation) => { + const context = { bind, unbind, isBound, rebind }; rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); rebind(TYPES.LogLevel).toConstantValue(LogLevel.warn); - bindAsService(bind, CrossModelSelectionDataService, CrossModelGLSPSelectionDataService); - bind(CrossModelTheiaGLSPSelectionForwarder).toSelf().inSingletonScope(); - bindOrRebind({ bind, isBound, rebind }, TheiaGLSPSelectionForwarder).toService(CrossModelTheiaGLSPSelectionForwarder); - bind(TYPES.ISnapper).to(CrossModelGridSnapper); - configureActionHandler({ bind, isBound }, SetViewportAction.KIND, GridAlignmentHandler); + rebind(TYPES.Grid).toConstantValue(GRID); + bind(CrossModelToolPalette).toSelf().inSingletonScope(); + bind(CrossModelMouseDeleteTool).toSelf().inSingletonScope(); + rebind(MouseDeleteTool).toService(CrossModelMouseDeleteTool); + rebind(ToolPalette).toService(CrossModelToolPalette); + bindAsService(context, GlspSelectionDataService, CrossModelGLSPSelectionDataService); + bindAsService(context, TYPES.IDiagramStartup, CrossModelDiagramStartup); registry(bind, unbind, isBound, rebind, unbindAsync, onActivation, onDeactivation); + bind(CrossModelCommandPalette).toSelf().inSingletonScope(); + rebind(GlspCommandPalette).toService(CrossModelCommandPalette); + + // there is a bug in the GLSP client release that will be fixed with 2.2.1 but for now we need to workaround + bind('selectionListener').toService(TheiaGLSPSelectionForwarder); }); } diff --git a/packages/glsp-client/src/browser/crossmodel-grid-handler.ts b/packages/glsp-client/src/browser/crossmodel-grid-handler.ts deleted file mode 100644 index 12c947db..00000000 --- a/packages/glsp-client/src/browser/crossmodel-grid-handler.ts +++ /dev/null @@ -1,26 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 CrossBreeze. - ********************************************************************************/ -import { GRID } from '@crossbreeze/protocol'; -import { IActionHandler, Point, SetViewportAction, TYPES, ViewerOptions } from '@eclipse-glsp/client'; -import { inject, injectable } from '@theia/core/shared/inversify'; - -@injectable() -export class GridAlignmentHandler implements IActionHandler { - @inject(TYPES.ViewerOptions) protected options: ViewerOptions; - - handle(action: SetViewportAction): void { - const graphDiv = document.querySelector(`#${this.options.baseDiv} .sprotty-graph`); - if (graphDiv) { - const adaptedPosition = multiply(Point.subtract(GRID, action.newViewport.scroll), action.newViewport.zoom); - const adaptedSize = multiply(GRID, action.newViewport.zoom); - - graphDiv.style.backgroundPosition = `${adaptedPosition.x}px ${adaptedPosition.y}px`; - graphDiv.style.backgroundSize = `${adaptedSize.x}px ${adaptedSize.y}px`; - } - } -} - -function multiply(point: Point, factor: number): Point { - return { x: point.x * factor, y: point.y * factor }; -} diff --git a/packages/glsp-client/src/browser/crossmodel-grid-snapper.ts b/packages/glsp-client/src/browser/crossmodel-grid-snapper.ts deleted file mode 100644 index 50e6646f..00000000 --- a/packages/glsp-client/src/browser/crossmodel-grid-snapper.ts +++ /dev/null @@ -1,13 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 CrossBreeze. - ********************************************************************************/ -import { GRID } from '@crossbreeze/protocol'; -import { GridSnapper } from '@eclipse-glsp/client'; -import { injectable } from '@theia/core/shared/inversify'; - -@injectable() -export class CrossModelGridSnapper extends GridSnapper { - constructor() { - super(GRID); - } -} diff --git a/packages/glsp-client/src/browser/crossmodel-selection-data-service.ts b/packages/glsp-client/src/browser/crossmodel-selection-data-service.ts index b120940c..893a8076 100644 --- a/packages/glsp-client/src/browser/crossmodel-selection-data-service.ts +++ b/packages/glsp-client/src/browser/crossmodel-selection-data-service.ts @@ -3,12 +3,12 @@ ********************************************************************************/ import { CrossReference, REFERENCE_CONTAINER_TYPE, REFERENCE_PROPERTY, REFERENCE_VALUE, RenderProps } from '@crossbreeze/protocol'; import { GModelElement, GModelRoot, hasArgs } from '@eclipse-glsp/client'; +import { GlspSelectionDataService } from '@eclipse-glsp/theia-integration'; import { isDefined } from '@theia/core'; import { injectable } from '@theia/core/shared/inversify'; -import { CrossModelSelectionDataService } from './crossmodel-selection-forwarder'; @injectable() -export class CrossModelGLSPSelectionDataService extends CrossModelSelectionDataService { +export class CrossModelGLSPSelectionDataService extends GlspSelectionDataService { async getSelectionData(root: Readonly, selectedElementIds: string[]): Promise { const selection = selectedElementIds.map(id => root.index.getById(id)).filter(isDefined); return getSelectionDataFor(selection); diff --git a/packages/glsp-client/src/browser/crossmodel-selection-forwarder.ts b/packages/glsp-client/src/browser/crossmodel-selection-forwarder.ts deleted file mode 100644 index 1336d200..00000000 --- a/packages/glsp-client/src/browser/crossmodel-selection-forwarder.ts +++ /dev/null @@ -1,53 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 CrossBreeze. - ********************************************************************************/ - -import { GModelRoot } from '@eclipse-glsp/client'; -import { GlspSelection, GlspSelectionData, TheiaGLSPSelectionForwarder, getDiagramWidget } from '@eclipse-glsp/theia-integration'; -import { ApplicationShell } from '@theia/core/lib/browser'; -import { inject, injectable, optional, postConstruct } from '@theia/core/shared/inversify'; - -@injectable() -export abstract class CrossModelSelectionDataService { - abstract getSelectionData(root: Readonly, selectedElementIds: string[]): Promise; -} - -@injectable() -export class CrossModelTheiaGLSPSelectionForwarder extends TheiaGLSPSelectionForwarder { - @inject(CrossModelSelectionDataService) - @optional() - protected readonly dataService?: CrossModelSelectionDataService; - - @inject(ApplicationShell) - protected shell: ApplicationShell; - - @postConstruct() - protected init(): void { - this.shell.onDidChangeActiveWidget(() => { - const activeDiagramWidget = getDiagramWidget(this.shell); - if (activeDiagramWidget) { - // re-store selection from diagram to the global scope - this.selectionChanged( - activeDiagramWidget.editorContext.modelRoot, - activeDiagramWidget.editorContext.selectedElements.map(element => element.id) - ); - } - }); - } - - override selectionChanged(root: Readonly, selectedElements: string[]): void { - this.handleSelectionChanged(selectedElements, root); - } - - override async handleSelectionChanged(selectedElementsIDs: string[], root?: Readonly): Promise { - const sourceUri = await this.getSourceUri(); - const additionalSelectionData = (await this.dataService?.getSelectionData(root!, selectedElementsIDs)) ?? undefined; - const glspSelection: GlspSelection = { - selectedElementsIDs, - additionalSelectionData, - widgetId: this.viewerOptions.baseDiv, - sourceUri - }; - this.theiaSelectionService.selection = glspSelection; - } -} diff --git a/packages/glsp-client/src/browser/index.ts b/packages/glsp-client/src/browser/index.ts new file mode 100644 index 00000000..8b95af85 --- /dev/null +++ b/packages/glsp-client/src/browser/index.ts @@ -0,0 +1,8 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ +export * from './mapping-diagram/mapping-diagram-manager'; +export * from './mapping-diagram/mapping-diagram-widget'; + +export * from './system-diagram/system-diagram-manager'; +export * from './system-diagram/system-diagram-widget'; diff --git a/packages/glsp-client/src/browser/mapping-diagram/edge-creation-tool/drag-creation-tool.ts b/packages/glsp-client/src/browser/mapping-diagram/edge-creation-tool/drag-creation-tool.ts index 1c5b7406..fba4bb82 100644 --- a/packages/glsp-client/src/browser/mapping-diagram/edge-creation-tool/drag-creation-tool.ts +++ b/packages/glsp-client/src/browser/mapping-diagram/edge-creation-tool/drag-creation-tool.ts @@ -8,6 +8,7 @@ import { BaseEditTool, DragAwareMouseListener, GModelElement, + ModifyCSSFeedbackAction, Point, TriggerEdgeCreationAction, findParentByFeature @@ -17,6 +18,8 @@ import { AttributeCompartment } from '../../model'; import { TargetObjectNode } from '../model'; import { MappingEdgeCreationArgs } from './mapping-edge-creation-tool'; +const CSS_MAPPING_CREATION = 'mapping-creation'; + /** * A tool that is always enabled to track when the user wants to start using our edge creation tool. */ @@ -30,6 +33,13 @@ export class DragEdgeCreationTool extends BaseEditTool { override enable(): void { this.toDisposeOnDisable.push(this.mouseTool.registerListener(new DragEdgeCreationMouseListener())); + const toolFeedback = this.createFeedbackEmitter() + .add( + ModifyCSSFeedbackAction.create({ add: [CSS_MAPPING_CREATION] }), + ModifyCSSFeedbackAction.create({ remove: [CSS_MAPPING_CREATION] }) + ) + .submit(); + this.toDisposeOnDisable.push(toolFeedback); } } diff --git a/packages/glsp-client/src/browser/mapping-diagram/edge-creation-tool/edge-creation-tool-module.ts b/packages/glsp-client/src/browser/mapping-diagram/edge-creation-tool/edge-creation-tool-module.ts index c02ecb55..27093118 100644 --- a/packages/glsp-client/src/browser/mapping-diagram/edge-creation-tool/edge-creation-tool-module.ts +++ b/packages/glsp-client/src/browser/mapping-diagram/edge-creation-tool/edge-creation-tool-module.ts @@ -20,7 +20,7 @@ import { NoScrollOverNodeListener } from './scroll-mouse-listener'; export const mappingEdgeCreationToolModule = new FeatureModule( (bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; - bindAsService(context, TYPES.IDefaultTool, DragEdgeCreationTool); + bindAsService(context, TYPES.ITool, DragEdgeCreationTool); rebind(EdgeCreationTool).to(MappingEdgeCreationTool).inSingletonScope(); rebind(GLSPScrollMouseListener).to(NoScrollOverNodeListener).inSingletonScope(); rebind(RestoreViewportHandler).to(CrossModelRestoreViewportHandler).inSingletonScope(); diff --git a/packages/glsp-client/src/browser/mapping-diagram/edge-creation-tool/mapping-edge-creation-tool.ts b/packages/glsp-client/src/browser/mapping-diagram/edge-creation-tool/mapping-edge-creation-tool.ts index d4d2f936..1f49de7a 100644 --- a/packages/glsp-client/src/browser/mapping-diagram/edge-creation-tool/mapping-edge-creation-tool.ts +++ b/packages/glsp-client/src/browser/mapping-diagram/edge-creation-tool/mapping-edge-creation-tool.ts @@ -2,42 +2,41 @@ * Copyright (c) 2024 CrossBreeze. ********************************************************************************/ -import { createLeftPortId, createRightPortId } from '@crossbreeze/protocol'; +import { createLeftPortId, createRightPortId, EnableDefaultToolsAction, EnableToolsAction } from '@crossbreeze/protocol'; import { Action, AnchorComputerRegistry, Args, Bounds, - CursorCSS, Disposable, EdgeCreationTool, EdgeCreationToolMouseListener, FeedbackEdgeEndMovingMouseListener, + findChildrenAtPosition, + findParentByFeature, GConnectableElement, + getAbsolutePosition, GLSPActionDispatcher, GModelElement, HoverFeedbackAction, IFeedbackActionDispatcher, + isBoundsAware, ITypeHintProvider, MoveAction, Point, - TriggerEdgeCreationAction, - cursorFeedbackAction, - findChildrenAtPosition, - findParentByFeature, - getAbsolutePosition, - isBoundsAware, - toAbsoluteBounds + toAbsoluteBounds, + TriggerEdgeCreationAction } from '@eclipse-glsp/client'; import { DrawFeedbackEdgeAction, FeedbackEdgeEnd, - RemoveFeedbackEdgeAction, - feedbackEdgeEndId + feedbackEdgeEndId, + RemoveFeedbackEdgeAction } from '@eclipse-glsp/client/lib/features/tools/edge-creation/dangling-edge-feedback'; import { injectable } from 'inversify'; import { AttributeCompartment } from '../../model'; import { SourceObjectNode, TargetObjectNode } from '../model'; +import { DragEdgeCreationTool } from './drag-creation-tool'; export type AttributeParent = 'target-object' | 'source-object'; @@ -58,25 +57,19 @@ export function revertAttributeParent(parent: AttributeParent): AttributeParent export class MappingEdgeCreationTool extends EdgeCreationTool { protected override triggerAction: MappingEdgeCreationAction; - override doEnable(): void { - const feedbackListener = new MappingEdgeEndMovingListener(this.triggerAction, this.anchorRegistry, this.feedbackDispatcher); + protected override trackFeedbackEdge(): void { + const mouseMovingFeedback = new MappingEdgeEndMovingListener(this.triggerAction, this.anchorRegistry, this.feedbackDispatcher); + this.toDisposeOnDisable.push(mouseMovingFeedback, this.mouseTool.registerListener(mouseMovingFeedback)); + } + + protected override creationListener(): void { const creationListener = new MappingEdgeCreationToolMouseListener( this.triggerAction, this.actionDispatcher, this.typeHintProvider, this ); - - this.toDisposeOnDisable.push( - creationListener, - feedbackListener, - this.mouseTool.registerListener(creationListener), - this.mouseTool.registerListener(feedbackListener), - this.registerFeedback([cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED)], this, [ - RemoveFeedbackEdgeAction.create(), - cursorFeedbackAction() - ]) - ); + this.toDisposeOnDisable.push(creationListener, this.mouseTool.registerListener(creationListener)); } } @@ -105,7 +98,7 @@ export class MappingEdgeEndMovingListener extends FeedbackEdgeEndMovingMouseList const target = findChildrenAtPosition(root, position).reverse()[0]; if (this.lastSnappedElement) { - actions.push(HoverFeedbackAction.create({ mouseoverElement: this.lastSnappedElement.id, mouseIsOver: false })); + this.feedback.add(HoverFeedbackAction.create({ mouseoverElement: this.lastSnappedElement.id, mouseIsOver: false })); } const attributeCompartment = findAttribute(target, this.expectedParent); @@ -116,22 +109,20 @@ export class MappingEdgeEndMovingListener extends FeedbackEdgeEndMovingMouseList const anchor = this.computeAbsoluteAnchor(targetPort, Bounds.center(toAbsoluteBounds(edge.source))); this.lastSnappedElement = attributeCompartment; if (Point.euclideanDistance(anchor, edgeEnd.position) > 1) { - actions.push(MoveAction.create([{ elementId: edgeEnd.id, toPosition: anchor }], { animate: false })); + this.feedback.add(MoveAction.create([{ elementId: edgeEnd.id, toPosition: anchor }], { animate: false })); } } } else { - actions.push(MoveAction.create([{ elementId: edgeEnd.id, toPosition: position }], { animate: false })); + this.feedback.add(MoveAction.create([{ elementId: edgeEnd.id, toPosition: position }], { animate: false })); this.lastSnappedElement = undefined; this.feedbackDispatcher.registerFeedback(this, actions); } if (this.lastSnappedElement) { - actions.push(HoverFeedbackAction.create({ mouseoverElement: this.lastSnappedElement.id, mouseIsOver: true })); + this.feedback.add(HoverFeedbackAction.create({ mouseoverElement: this.lastSnappedElement.id, mouseIsOver: true })); } - if (actions.length > 0) { - this.feedbackDispatcher.registerFeedback(this, actions); - } + this.feedback.submit(); return []; } } @@ -145,9 +136,12 @@ export class MappingEdgeCreationToolMouseListener extends EdgeCreationToolMouseL ) { super(triggerAction, actionDispatcher, typeHintProvider, tool); this.source = createPortId(this.triggerAction.args.sourceAttributeId, this.triggerAction.args.sourceAttributeParent); - this.tool.registerFeedback([ - DrawFeedbackEdgeAction.create({ elementTypeId: this.triggerAction.elementTypeId, sourceId: this.source }) - ]); + this.feedbackEdgeFeedback + .add( + DrawFeedbackEdgeAction.create({ elementTypeId: this.triggerAction.elementTypeId, sourceId: this.source }), + RemoveFeedbackEdgeAction.create() + ) + .submit(); } override mouseOver(target: GModelElement, _event: MouseEvent): Action[] { @@ -163,8 +157,14 @@ export class MappingEdgeCreationToolMouseListener extends EdgeCreationToolMouseL return [this.updateEdgeFeedback()]; } - dispose(): void { - // do nothing + override nonDraggingMouseUp(element: GModelElement, event: MouseEvent): Action[] { + const actions = super.nonDraggingMouseUp(element, event); + const enableDefaultToolsIndex = actions.findIndex(action => EnableDefaultToolsAction.is(action)); + if (enableDefaultToolsIndex >= 0) { + actions.splice(enableDefaultToolsIndex, 1); + actions.push(EnableToolsAction.create([DragEdgeCreationTool.ID])); + } + return actions; } } diff --git a/packages/glsp-client/src/browser/mapping-diagram/mapping-diagram-configuration.ts b/packages/glsp-client/src/browser/mapping-diagram/mapping-diagram-configuration.ts index 857e7568..acb3b949 100644 --- a/packages/glsp-client/src/browser/mapping-diagram/mapping-diagram-configuration.ts +++ b/packages/glsp-client/src/browser/mapping-diagram/mapping-diagram-configuration.ts @@ -10,30 +10,31 @@ import { TARGET_OBJECT_NODE_TYPE } from '@crossbreeze/protocol'; import { + ContainerConfiguration, configureDefaultModelElements, configureModelElement, + gridModule, hoverFeedbackFeature, initializeDiagramContainer, - selectFeature, - toolPaletteModule + selectFeature } from '@eclipse-glsp/client'; -import { ContainerConfiguration } from '@eclipse-glsp/protocol'; import { GLSPDiagramConfiguration } from '@eclipse-glsp/theia-integration'; import { Container } from '@theia/core/shared/inversify/index'; import { MappingDiagramLanguage } from '../../common/crossmodel-diagram-language'; import { createCrossModelDiagramModule } from '../crossmodel-diagram-module'; +import { AttributeCompartment } from '../model'; import { AttributeCompartmentView } from '../views'; import { mappingEdgeCreationToolModule } from './edge-creation-tool/edge-creation-tool-module'; import { AttributeMappingEdge, SourceNumberNode, SourceObjectNode, SourceStringNode, TargetObjectNode } from './model'; +import { sourceObjectCreationToolModule } from './source-object-creation-tool/source-object-creation-tool-module'; import { AttributeMappingEdgeView, SourceNumberNodeView, SourceObjectNodeView, SourceStringNodeView, TargetObjectNodeView } from './views'; -import { AttributeCompartment } from '../model'; export class MappingDiagramConfiguration extends GLSPDiagramConfiguration { diagramType: string = MappingDiagramLanguage.diagramType; configureContainer(container: Container, ...containerConfiguration: ContainerConfiguration): Container { - return initializeDiagramContainer(container, ...containerConfiguration, mappingDiagramModule, mappingEdgeCreationToolModule, { - remove: toolPaletteModule + return initializeDiagramContainer(container, ...containerConfiguration, { + add: [gridModule, mappingDiagramModule, mappingEdgeCreationToolModule, sourceObjectCreationToolModule] }); } } diff --git a/packages/glsp-client/src/browser/mapping-diagram/mapping-diagram-manager.ts b/packages/glsp-client/src/browser/mapping-diagram/mapping-diagram-manager.ts index bfd53552..e82254a0 100644 --- a/packages/glsp-client/src/browser/mapping-diagram/mapping-diagram-manager.ts +++ b/packages/glsp-client/src/browser/mapping-diagram/mapping-diagram-manager.ts @@ -2,19 +2,20 @@ * Copyright (c) 2024 CrossBreeze. ********************************************************************************/ +import { codiconCSSString } from '@eclipse-glsp/client'; import { GLSPDiagramManager } from '@eclipse-glsp/theia-integration'; import { URI } from '@theia/core'; -import { WidgetOpenerOptions } from '@theia/core/lib/browser'; +import { OpenWithHandler, WidgetOpenerOptions } from '@theia/core/lib/browser'; import { injectable } from '@theia/core/shared/inversify'; import { MappingDiagramLanguage } from '../../common/crossmodel-diagram-language'; -import { codiconCSSString } from '@eclipse-glsp/client'; export interface ProblemMarkerOpenerOptions extends WidgetOpenerOptions { selection?: Range; } @injectable() -export class MappingDiagramManager extends GLSPDiagramManager { +export class MappingDiagramManager extends GLSPDiagramManager implements OpenWithHandler { + static readonly ID = 'mapping-diagram-manager'; get label(): string { return MappingDiagramLanguage.label; } @@ -35,6 +36,9 @@ export class MappingDiagramManager extends GLSPDiagramManager { return MappingDiagramLanguage.contributionId; } + override get id(): string { + return MappingDiagramManager.ID; + } override canHandle(uri: URI, options?: ProblemMarkerOpenerOptions | undefined): number { if (options?.selection) { return 0; diff --git a/packages/glsp-client/src/browser/mapping-diagram/source-object-creation-tool/source-object-creation-tool-module.ts b/packages/glsp-client/src/browser/mapping-diagram/source-object-creation-tool/source-object-creation-tool-module.ts new file mode 100644 index 00000000..340b2362 --- /dev/null +++ b/packages/glsp-client/src/browser/mapping-diagram/source-object-creation-tool/source-object-creation-tool-module.ts @@ -0,0 +1,14 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ + +import { bindAsService, FeatureModule, TYPES } from '@eclipse-glsp/client'; +import { SourceObjectCreationTool } from './source-object-creation-tool'; + +export const sourceObjectCreationToolModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bindAsService(context, TYPES.ITool, SourceObjectCreationTool); + }, + { featureId: Symbol('sourceObjectCreationTool') } +); diff --git a/packages/glsp-client/src/browser/mapping-diagram/source-object-creation-tool/source-object-creation-tool.ts b/packages/glsp-client/src/browser/mapping-diagram/source-object-creation-tool/source-object-creation-tool.ts new file mode 100644 index 00000000..c8a51d13 --- /dev/null +++ b/packages/glsp-client/src/browser/mapping-diagram/source-object-creation-tool/source-object-creation-tool.ts @@ -0,0 +1,52 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ +import { + Action, + BaseEditTool, + CursorCSS, + cursorFeedbackAction, + Disposable, + FeedbackEmitter, + GModelElement, + MouseListener, + SetUIExtensionVisibilityAction +} from '@eclipse-glsp/client'; +import { injectable } from '@theia/core/shared/inversify'; +import { CrossModelCommandPalette } from '../../cross-model-command-palette'; + +@injectable() +export class SourceObjectCreationTool extends BaseEditTool { + static readonly ID = 'source-object-creation-tool'; + + get id(): string { + return SourceObjectCreationTool.ID; + } + override enable(): void { + const mouseFeedback = new SourceObjectCreationMouseListener(this); + this.toDisposeOnDisable.push(mouseFeedback, this.mouseTool.registerListener(mouseFeedback)); + } +} + +@injectable() +export class SourceObjectCreationMouseListener extends MouseListener implements Disposable { + cursorFeedback: FeedbackEmitter; + constructor(protected tool: SourceObjectCreationTool) { + super(); + this.cursorFeedback = tool.createFeedbackEmitter(); + this.cursorFeedback.add(cursorFeedbackAction(CursorCSS.NODE_CREATION), cursorFeedbackAction()).submit(); + } + + override mouseUp(_target: GModelElement, _event: MouseEvent): (Action | Promise)[] { + return [ + SetUIExtensionVisibilityAction.create({ + extensionId: CrossModelCommandPalette.ID, + visible: true + }) + ]; + } + + dispose(): void { + this.cursorFeedback.dispose(); + } +} diff --git a/packages/glsp-client/src/browser/system-diagram/edge-creation-tool/system-edge-creation-tool.ts b/packages/glsp-client/src/browser/system-diagram/edge-creation-tool/system-edge-creation-tool.ts index efcea54a..24aa96b2 100644 --- a/packages/glsp-client/src/browser/system-diagram/edge-creation-tool/system-edge-creation-tool.ts +++ b/packages/glsp-client/src/browser/system-diagram/edge-creation-tool/system-edge-creation-tool.ts @@ -10,8 +10,7 @@ import { Disposable, DragAwareMouseListener, EdgeCreationTool, - EnableDefaultToolsAction, - FeedbackEdgeEndMovingMouseListener, + FeedbackEmitter, GEdge, GLSPActionDispatcher, GModelElement, @@ -22,8 +21,7 @@ import { TriggerEdgeCreationAction, cursorFeedbackAction, findParentByFeature, - isConnectable, - isCtrlOrCmd + isConnectable } from '@eclipse-glsp/client'; import { DrawFeedbackEdgeAction, @@ -35,20 +33,14 @@ const CSS_EDGE_CREATION = 'edge-creation'; @injectable() export class SystemEdgeCreationTool extends EdgeCreationTool { - override doEnable(): void { - const mouseMovingFeedback = new FeedbackEdgeEndMovingMouseListener(this.anchorRegistry, this.feedbackDispatcher); - const listener = new SystemEdgeCreationToolMouseListener(this.triggerAction, this.actionDispatcher, this.typeHintProvider, this); - this.toDisposeOnDisable.push( - listener, - mouseMovingFeedback, - this.mouseTool.registerListener(listener), - this.mouseTool.registerListener(mouseMovingFeedback), - this.registerFeedback([], this, [ - RemoveFeedbackEdgeAction.create(), - cursorFeedbackAction(), - ModifyCSSFeedbackAction.create({ remove: [CSS_EDGE_CREATION] }) - ]) + protected override creationListener(): void { + const creationListener = new SystemEdgeCreationToolMouseListener( + this.triggerAction, + this.actionDispatcher, + this.typeHintProvider, + this ); + this.toDisposeOnDisable.push(creationListener, this.mouseTool.registerListener(creationListener)); } } @@ -68,8 +60,9 @@ export class SystemEdgeCreationToolMouseListener extends DragAwareMouseListener protected proxyEdge: GEdge; protected dragContext?: DragConnectionContext; - protected mouseMoveFeedback?: Disposable; - protected sourceFeedback?: Disposable; + protected mouseMoveFeedback: FeedbackEmitter; + protected sourceFeedback: FeedbackEmitter; + protected feedbackEdgeFeedback: FeedbackEmitter; constructor( protected triggerAction: TriggerEdgeCreationAction, @@ -80,6 +73,9 @@ export class SystemEdgeCreationToolMouseListener extends DragAwareMouseListener super(); this.proxyEdge = new GEdge(); this.proxyEdge.type = triggerAction.elementTypeId; + this.feedbackEdgeFeedback = tool.createFeedbackEmitter(); + this.mouseMoveFeedback = tool.createFeedbackEmitter(); + this.sourceFeedback = tool.createFeedbackEmitter(); } protected isSourceSelected(): boolean { @@ -109,9 +105,13 @@ export class SystemEdgeCreationToolMouseListener extends DragAwareMouseListener if (dragDistance > 3) { // assign source if possible this.source = this.dragContext.element.id; - this.tool.registerFeedback([ - DrawFeedbackEdgeAction.create({ elementTypeId: this.triggerAction.elementTypeId, sourceId: this.source }) - ]); + this.feedbackEdgeFeedback + .add( + DrawFeedbackEdgeAction.create({ elementTypeId: this.triggerAction.elementTypeId, sourceId: this.source }), + RemoveFeedbackEdgeAction.create() + ) + .submit(); + this.dragContext = undefined; } } @@ -133,18 +133,17 @@ export class SystemEdgeCreationToolMouseListener extends DragAwareMouseListener args: this.triggerAction.args }) ); - if (!isCtrlOrCmd(event)) { - result.push(EnableDefaultToolsAction.create()); - } } } - this.reinitialize(); + this.dispose(); + this.updateFeedback(target, event); return result; } - override nonDraggingMouseUp(_element: GModelElement, event: MouseEvent): Action[] { - this.reinitialize(); - return [EnableDefaultToolsAction.create()]; + override nonDraggingMouseUp(element: GModelElement, event: MouseEvent): Action[] { + this.dispose(); + this.updateFeedback(element, event); + return []; } protected canConnect(element: GModelElement | undefined, role: 'source' | 'target'): boolean { @@ -161,11 +160,12 @@ export class SystemEdgeCreationToolMouseListener extends DragAwareMouseListener // source element feedback if (this.isSourceSelected()) { - this.sourceFeedback = this.tool.registerFeedback( - [HoverFeedbackAction.create({ mouseoverElement: this.source!, mouseIsOver: true })], - this.proxyEdge, - [HoverFeedbackAction.create({ mouseoverElement: this.source!, mouseIsOver: false })] - ); + this.sourceFeedback + .add( + HoverFeedbackAction.create({ mouseoverElement: this.source!, mouseIsOver: true }), + HoverFeedbackAction.create({ mouseoverElement: this.source!, mouseIsOver: false }) + ) + .submit(); } // cursor feedback @@ -190,10 +190,13 @@ export class SystemEdgeCreationToolMouseListener extends DragAwareMouseListener ); } - protected registerFeedback(feedbackActions: Action[], cleanupActions?: Action[]): Disposable { - this.mouseMoveFeedback?.dispose(); - this.mouseMoveFeedback = this.tool.registerFeedback(feedbackActions, this, cleanupActions); - return this.mouseMoveFeedback; + protected registerFeedback(feedbackActions: Action[], cleanupActions?: Action[]): void { + this.mouseMoveFeedback.dispose(); + feedbackActions.forEach(action => this.mouseMoveFeedback.add(action)); + if (cleanupActions) { + this.mouseMoveFeedback.add(undefined, cleanupActions); + } + this.mouseMoveFeedback.submit(); } protected calculateContext(target: GModelElement, event: MouseEvent, previousContext?: ConnectionContext): ConnectionContext { @@ -212,16 +215,12 @@ export class SystemEdgeCreationToolMouseListener extends DragAwareMouseListener return context; } - protected reinitialize(): void { + override dispose(): void { + super.dispose(); this.source = undefined; this.target = undefined; - this.tool.registerFeedback([RemoveFeedbackEdgeAction.create()]); - this.dragContext = undefined; - this.mouseMoveFeedback?.dispose(); - this.sourceFeedback?.dispose(); - } - - dispose(): void { - this.reinitialize(); + this.feedbackEdgeFeedback.dispose(); + this.mouseMoveFeedback.dispose(); + this.sourceFeedback.dispose(); } } diff --git a/packages/glsp-client/src/browser/system-diagram/node-creation-tool/node-creation-tool-module.ts b/packages/glsp-client/src/browser/system-diagram/node-creation-tool/node-creation-tool-module.ts new file mode 100644 index 00000000..8af1569d --- /dev/null +++ b/packages/glsp-client/src/browser/system-diagram/node-creation-tool/node-creation-tool-module.ts @@ -0,0 +1,14 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ + +import { FeatureModule, NodeCreationTool, nodeCreationToolModule, viewportModule } from '@eclipse-glsp/client'; +import { SystemNodeCreationTool } from './system-node-creation-tool'; + +export const systemNodeCreationModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + context.rebind(NodeCreationTool).to(SystemNodeCreationTool).inSingletonScope(); + }, + { requires: [nodeCreationToolModule, viewportModule] } +); diff --git a/packages/glsp-client/src/browser/system-diagram/node-creation-tool/system-node-creation-tool.ts b/packages/glsp-client/src/browser/system-diagram/node-creation-tool/system-node-creation-tool.ts new file mode 100644 index 00000000..9414dc2e --- /dev/null +++ b/packages/glsp-client/src/browser/system-diagram/node-creation-tool/system-node-creation-tool.ts @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ + +import { + Action, + Disposable, + DisposableCollection, + GModelElement, + GhostElement, + NodeCreationTool, + NodeCreationToolMouseListener, + SetUIExtensionVisibilityAction, + TrackedInsert +} from '@eclipse-glsp/client'; +import { injectable } from '@theia/core/shared/inversify'; +import { CrossModelCommandPalette } from '../../cross-model-command-palette'; + +@injectable() +export class SystemNodeCreationTool extends NodeCreationTool { + protected override createNodeCreationListener(ghostElement: GhostElement): Disposable { + const toolListener = new SystemNodeCreationToolMouseListener(this.triggerAction, this, ghostElement); + return new DisposableCollection(toolListener, this.mouseTool.registerListener(toolListener)); + } +} + +export class SystemNodeCreationToolMouseListener extends NodeCreationToolMouseListener { + protected override isContinuousMode(_ctx: GModelElement, _event: MouseEvent): boolean { + return true; + } + + protected override getCreateOperation(ctx: GModelElement, event: MouseEvent, insert: TrackedInsert): Action { + if (this.triggerAction.args?.type === 'show') { + return SetUIExtensionVisibilityAction.create({ + extensionId: CrossModelCommandPalette.ID, + visible: true, + contextElementsId: [this.ghostElementId] + }); + } else if (this.triggerAction.args?.type === 'create') { + return super.getCreateOperation(ctx, event, insert); + } + throw new Error('Invalid node creation type'); + } +} diff --git a/packages/glsp-client/src/browser/system-diagram/system-diagram-configuration.ts b/packages/glsp-client/src/browser/system-diagram/system-diagram-configuration.ts index 966d4aba..c45c1d0f 100644 --- a/packages/glsp-client/src/browser/system-diagram/system-diagram-configuration.ts +++ b/packages/glsp-client/src/browser/system-diagram/system-diagram-configuration.ts @@ -2,8 +2,13 @@ * Copyright (c) 2023 CrossBreeze. ********************************************************************************/ import { ATTRIBUTE_COMPARTMENT_TYPE, ENTITY_NODE_TYPE, RELATIONSHIP_EDGE_TYPE } from '@crossbreeze/protocol'; -import { configureDefaultModelElements, configureModelElement, initializeDiagramContainer, selectModule } from '@eclipse-glsp/client'; -import { ContainerConfiguration } from '@eclipse-glsp/protocol'; +import { + ContainerConfiguration, + configureDefaultModelElements, + configureModelElement, + gridModule, + initializeDiagramContainer +} from '@eclipse-glsp/client'; import { GLSPDiagramConfiguration } from '@eclipse-glsp/theia-integration'; import { Container } from '@theia/core/shared/inversify/index'; import { SystemDiagramLanguage } from '../../common/crossmodel-diagram-language'; @@ -12,6 +17,7 @@ import { AttributeCompartment } from '../model'; import { AttributeCompartmentView } from '../views'; import { systemEdgeCreationToolModule } from './edge-creation-tool/edge-creation-tool-module'; import { EntityNode, RelationshipEdge } from './model'; +import { systemNodeCreationModule } from './node-creation-tool/node-creation-tool-module'; import { systemSelectModule } from './select-tool/select-tool-module'; import { EntityNodeView, RelationshipEdgeView } from './views'; @@ -22,11 +28,12 @@ export class SystemDiagramConfiguration extends GLSPDiagramConfiguration { return initializeDiagramContainer( container, { - add: systemSelectModule, - remove: selectModule + replace: systemSelectModule }, ...containerConfiguration, + gridModule, systemDiagramModule, + systemNodeCreationModule, systemEdgeCreationToolModule ); } diff --git a/packages/glsp-client/src/browser/system-diagram/system-diagram-frontend-module.ts b/packages/glsp-client/src/browser/system-diagram/system-diagram-frontend-module.ts index 0888acae..2f63fd0a 100644 --- a/packages/glsp-client/src/browser/system-diagram/system-diagram-frontend-module.ts +++ b/packages/glsp-client/src/browser/system-diagram/system-diagram-frontend-module.ts @@ -7,12 +7,14 @@ import { DiagramConfiguration, GLSPClientContribution, GLSPDiagramWidget, - GLSPTheiaFrontendModule + GLSPTheiaFrontendModule, + registerDiagramManager } from '@eclipse-glsp/theia-integration'; import { SystemDiagramLanguage } from '../../common/crossmodel-diagram-language'; import { CrossModelClientContribution } from '../crossmodel-client-contribution'; import { SystemDiagramConfiguration } from './system-diagram-configuration'; +import { SystemDiagramManager } from './system-diagram-manager'; import { SystemDiagramWidget } from './system-diagram-widget'; export class SystemDiagramModule extends GLSPTheiaFrontendModule { @@ -32,6 +34,11 @@ export class SystemDiagramModule extends GLSPTheiaFrontendModule { super.bindDiagramWidgetFactory(context); context.rebind(GLSPDiagramWidget).to(SystemDiagramWidget); } + + override configureDiagramManager(context: ContainerContext): void { + context.bind(SystemDiagramManager).toSelf().inSingletonScope(); + registerDiagramManager(context.bind, SystemDiagramManager, false); + } } export default new SystemDiagramModule(); diff --git a/packages/glsp-client/src/browser/system-diagram/system-diagram-manager.ts b/packages/glsp-client/src/browser/system-diagram/system-diagram-manager.ts new file mode 100644 index 00000000..ea2b9bc6 --- /dev/null +++ b/packages/glsp-client/src/browser/system-diagram/system-diagram-manager.ts @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ + +import { codiconCSSString } from '@eclipse-glsp/client'; +import { GLSPDiagramManager } from '@eclipse-glsp/theia-integration'; +import { OpenWithHandler } from '@theia/core/lib/browser'; +import { injectable } from '@theia/core/shared/inversify'; +import { SystemDiagramLanguage } from '../../common/crossmodel-diagram-language'; + +@injectable() +export class SystemDiagramManager extends GLSPDiagramManager implements OpenWithHandler { + static readonly ID = 'system-diagram-manager'; + + get label(): string { + return SystemDiagramLanguage.label; + } + + override get iconClass(): string { + return SystemDiagramLanguage.iconClass ?? codiconCSSString('type-hierarchy-sub'); + } + + override get fileExtensions(): string[] { + return SystemDiagramLanguage.fileExtensions; + } + + override get diagramType(): string { + return SystemDiagramLanguage.diagramType; + } + + override get contributionId(): string { + return SystemDiagramLanguage.contributionId; + } + + override get id(): string { + return SystemDiagramManager.ID; + } +} diff --git a/packages/glsp-client/src/common/index.ts b/packages/glsp-client/src/common/index.ts new file mode 100644 index 00000000..dd8a83ff --- /dev/null +++ b/packages/glsp-client/src/common/index.ts @@ -0,0 +1,4 @@ +/******************************************************************************** + * Copyright (c) 2024 CrossBreeze. + ********************************************************************************/ +export * from './crossmodel-diagram-language'; diff --git a/packages/glsp-client/style/diagram.css b/packages/glsp-client/style/diagram.css index d1d88ec2..99e6ac59 100644 --- a/packages/glsp-client/style/diagram.css +++ b/packages/glsp-client/style/diagram.css @@ -6,8 +6,6 @@ --sprotty-background: var(--theia-layout-color3); --sprotty-edge: var(--theia-editor-foreground); --sprotty-border: var(--theia-editor-foreground); - - --background-image: url('data:image/svg+xml;utf8,'); } /* Standard sprotty */ @@ -22,8 +20,6 @@ .sprotty-graph { font-size: 15pt; height: 100%; - background: var(--sprotty-background); - background-image: var(--background-image); background-size: 11px; } @@ -34,6 +30,13 @@ cursor: ew-resize; } +.sprotty + .mapping-creation:not(.edge-check-pending-mode):not(.edge-creation-select-source-mode):not(.edge-creation-select-target-mode):not( + .edge-reconnect-select-target-mode:not(.edge-modification-not-allowed-mode) + ) { + cursor: ew-resize; +} + .sprotty text { font-family: monospace; } @@ -103,7 +106,7 @@ stroke-linecap: round; } -.sprotty[id^='system-diagram'] .tool-palette { +/* .sprotty[id^='system-diagram'] .tool-palette { top: 11px; right: 11px; width: 150px; @@ -120,7 +123,6 @@ outline-width: 0; } -/* we know there is only the 1:1 relationship tool for now, if we ever need more styling, better to create a complete custom toolbar */ .sprotty[id^='system-diagram'] .tool-palette .tool-button::before { margin-right: var(--theia-ui-padding); font-size: var(--theia-icon-size); @@ -133,7 +135,7 @@ .sprotty[id^='system-diagram'] .tool-palette .tool-button.clicked { background: var(--theia-activityBarBadge-background); color: var(--theia-activityBarBadge-foreground); -} +} */ .command-palette { animation: none; @@ -143,3 +145,27 @@ transform: scale(0.0105) translate(-400px, 1375px); fill: #000; } + +.grid-background .sprotty-graph, +.grid-background.sprotty-graph { + --background-image: url('data:image/svg+xml;utf8,'); + background-image: var(--background-image); + background-size: var(--grid-background-width) var(--grid-background-height); + background-position: var(--grid-background-x) var(--grid-background-y); +} + +.sprotty .header-icon { + display: flex; +} + +.sprotty .header-icon i { + display: none; +} + +.sprotty .header-icon i { + margin-right: 0.2em; +} + +.sprotty .palette-header { + flex-direction: row; +} diff --git a/packages/model-service/package.json b/packages/model-service/package.json index 0fb2525b..b114df6a 100644 --- a/packages/model-service/package.json +++ b/packages/model-service/package.json @@ -33,7 +33,7 @@ }, "dependencies": { "@crossbreeze/protocol": "^0.0.0", - "@theia/core": "1.43.1" + "@theia/core": "1.49.1" }, "theiaExtensions": [ { diff --git a/packages/product/package.json b/packages/product/package.json index d8263c82..33311ff6 100644 --- a/packages/product/package.json +++ b/packages/product/package.json @@ -30,9 +30,9 @@ "watch": "tsc -w" }, "dependencies": { - "@theia/core": "1.43.1", - "@theia/getting-started": "1.43.1", - "@theia/workspace": "1.43.1" + "@theia/core": "1.49.1", + "@theia/getting-started": "1.49.1", + "@theia/workspace": "1.49.1" }, "theiaExtensions": [ { diff --git a/packages/property-view/package.json b/packages/property-view/package.json index 2d20215d..1dfded29 100644 --- a/packages/property-view/package.json +++ b/packages/property-view/package.json @@ -35,10 +35,10 @@ "@crossbreeze/model-service": "^1.0.0", "@crossbreeze/protocol": "^0.0.0", "@crossbreeze/react-model-ui": "0.0.0", - "@eclipse-glsp/theia-integration": "2.0.0", - "@theia/core": "1.43.1", - "@theia/filesystem": "1.43.1", - "@theia/property-view": "1.43.1" + "@eclipse-glsp/theia-integration": "2.2.1", + "@theia/core": "1.49.1", + "@theia/filesystem": "1.49.1", + "@theia/property-view": "1.49.1" }, "theiaExtensions": [ { diff --git a/packages/protocol/package.json b/packages/protocol/package.json index f9f89e43..c14cf4ad 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -28,8 +28,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.0.0", + "@eclipse-glsp/protocol": "2.2.1", "langium": "~2.1.3", - "vscode-jsonrpc": "^8.0.2" + "vscode-jsonrpc": "8.2.0" } } diff --git a/packages/protocol/src/glsp/actions.ts b/packages/protocol/src/glsp/actions.ts index 9d0dc1ae..4591c762 100644 --- a/packages/protocol/src/glsp/actions.ts +++ b/packages/protocol/src/glsp/actions.ts @@ -1,7 +1,7 @@ /******************************************************************************** * Copyright (c) 2023 CrossBreeze. ********************************************************************************/ -import { Operation, Point, hasArrayProp, hasObjectProp, hasStringProp } from '@eclipse-glsp/protocol'; +import { Action, Operation, Point, hasArrayProp, hasObjectProp, hasStringProp } from '@eclipse-glsp/protocol'; export interface DropEntityOperation extends Operation { kind: typeof DropEntityOperation.KIND; @@ -80,3 +80,80 @@ export namespace AddSourceObjectOperation { }; } } + +// Copy definitions from (default) client-local glsp tool actions that we want to send from the server as well +export interface EnableToolsAction extends Action { + kind: typeof EnableToolsAction.KIND; + toolIds: string[]; +} + +export namespace EnableToolsAction { + export const KIND = 'enable-tools'; + + export function is(object: unknown): object is EnableToolsAction { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'toolIds'); + } + + export function create(toolIds: string[]): EnableToolsAction { + return { + kind: KIND, + toolIds + }; + } +} + +/** + * Action to disable the currently active tools and enable the default tools instead. + */ +export interface EnableDefaultToolsAction extends Action { + kind: typeof EnableDefaultToolsAction.KIND; +} + +export namespace EnableDefaultToolsAction { + export const KIND = 'enable-default-tools'; + + export function is(object: unknown): object is EnableToolsAction { + return Action.hasKind(object, KIND); + } + + export function create(): EnableDefaultToolsAction { + return { + kind: KIND + }; + } +} + +/** + * Action to set the visibility state of the UI extension with the specified `id`. + */ +export interface SetUIExtensionVisibilityAction extends Action { + kind: typeof SetUIExtensionVisibilityAction.KIND; + extensionId: string; + visible: boolean; + contextElementsId: string[]; +} + +export namespace SetUIExtensionVisibilityAction { + export const KIND = 'setUIExtensionVisibility'; + + export function create(options: { + extensionId: string; + visible: boolean; + contextElementsId?: string[]; + }): SetUIExtensionVisibilityAction { + return { + kind: KIND, + extensionId: options.extensionId, + visible: options.visible, + contextElementsId: options.contextElementsId ?? [] + }; + } +} + +export function activateDefaultToolsAction(): Action { + return EnableDefaultToolsAction.create(); +} + +export function activateDeleteToolAction(): Action { + return EnableToolsAction.create(['glsp.delete-mouse']); +} diff --git a/packages/protocol/src/model.ts b/packages/protocol/src/model.ts index b15ce0b9..a801cee9 100644 --- a/packages/protocol/src/model.ts +++ b/packages/protocol/src/model.ts @@ -2,14 +2,46 @@ * Copyright (c) 2024 CrossBreeze. ********************************************************************************/ -export const ModelFileType = { +const ModelFileTypeValues = { Generic: 'Generic', Entity: 'Entity', Relationship: 'Relationship', Mapping: 'Mapping', SystemDiagram: 'SystemDiagram' } as const; -export type ModelFileType = (typeof ModelFileType)[keyof typeof ModelFileType]; + +export const ModelFileType = { + ...ModelFileTypeValues, + getIconClass: (type: ModelFileType) => { + switch (type) { + case 'Entity': + return ModelStructure.Entity.ICON_CLASS; + case 'Relationship': + return ModelStructure.Relationship.ICON_CLASS; + case 'SystemDiagram': + return ModelStructure.SystemDiagram.ICON_CLASS; + case 'Mapping': + return ModelStructure.Mapping.ICON_CLASS; + default: + return undefined; + } + }, + getFileExtension(type: ModelFileType): string | undefined { + switch (type) { + case 'Entity': + return ModelFileExtensions.Entity; + case 'Generic': + return ModelFileExtensions.Generic; + case 'Mapping': + return ModelFileExtensions.Mapping; + case 'Relationship': + return ModelFileExtensions.Relationship; + case 'SystemDiagram': + return ModelFileExtensions.SystemDiagram; + } + } +} as const; +export type ModelFileType = (typeof ModelFileTypeValues)[keyof typeof ModelFileTypeValues]; export const ModelFileExtensions = { Generic: '.cm', @@ -81,6 +113,25 @@ export const ModelFileExtensions = { return undefined; }, + getIconClass(uri: string): string | undefined { + const fileType = this.getFileType(uri); + if (!fileType) { + return undefined; + } + switch (fileType) { + case 'Entity': + return ModelStructure.Entity.ICON_CLASS; + case 'Relationship': + return ModelStructure.Relationship.ICON_CLASS; + case 'SystemDiagram': + return ModelStructure.SystemDiagram.ICON_CLASS; + case 'Mapping': + return ModelStructure.Mapping.ICON_CLASS; + default: + return ''; + } + }, + detectFileType(content: string): ModelFileType | undefined { if (content.startsWith('entity')) { return 'Entity'; @@ -97,49 +148,38 @@ export const ModelFileExtensions = { return undefined; }, - getFileExtension(type: ModelFileType): string | undefined { - switch (type) { - case 'Entity': - return ModelFileExtensions.Entity; - case 'Generic': - return ModelFileExtensions.Generic; - case 'Mapping': - return ModelFileExtensions.Mapping; - case 'Relationship': - return ModelFileExtensions.Relationship; - case 'SystemDiagram': - return ModelFileExtensions.SystemDiagram; - } - }, - detectFileExtension(content: string): string | undefined { const type = this.detectFileType(content); return type ? this.detectFileExtension(type) : undefined; } } as const; -export namespace ModelStructure { - export const System = { - ICON: 'codicon codicon-globe' - }; - - export const Entity = { +export const ModelStructure = { + System: { + ICON_CLASS: 'codicon codicon-globe', + ICON: 'globe' + }, + Entity: { FOLDER: 'entities', - ICON: 'codicon codicon-git-commit' - }; + ICON_CLASS: 'codicon codicon-git-commit', + ICON: 'git-commit' + }, - export const Relationship = { + Relationship: { FOLDER: 'relationships', - ICON: 'codicon codicon-git-compare' - }; + ICON_CLASS: 'codicon codicon-git-compare', + ICON: 'git-compare' + }, - export const SystemDiagram = { + SystemDiagram: { FOLDER: 'diagrams', - ICON: 'codicon codicon-type-hierarchy-sub' - }; + ICON_CLASS: 'codicon codicon-type-hierarchy-sub', + ICON: 'type-hierarchy-sub' + }, - export const Mapping = { + Mapping: { FOLDER: 'mappings', - ICON: 'codicon codicon-group-by-ref-type' - }; -} + ICON_CLASS: 'codicon codicon-group-by-ref-type', + ICON: 'group-by-ref-type' + } +}; diff --git a/packages/protocol/src/util.ts b/packages/protocol/src/util.ts index f12d63dd..205ab3c2 100644 --- a/packages/protocol/src/util.ts +++ b/packages/protocol/src/util.ts @@ -42,3 +42,7 @@ export function toId(text: string): string { // prefix with '_' if necessary return '_' + id; } + +export function codiconCSSString(icon: string): string { + return `codicon codicon-${icon}`; +} diff --git a/packages/react-model-ui/src/views/form/EntityForm.tsx b/packages/react-model-ui/src/views/form/EntityForm.tsx index 3fc2050f..24806dd4 100644 --- a/packages/react-model-ui/src/views/form/EntityForm.tsx +++ b/packages/react-model-ui/src/views/form/EntityForm.tsx @@ -17,7 +17,7 @@ export function EntityForm(): React.ReactElement { const entity = useEntity(); return ( -
+ element.label; return ( - + = 2.1.2 < 3" -iconv-lite@^0.6.0, iconv-lite@^0.6.2: +iconv-lite@0.6.3, iconv-lite@^0.6.0, iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -8855,7 +9043,7 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== -inversify@^6.0.1: +inversify@^6.0.1, inversify@~6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/inversify/-/inversify-6.0.2.tgz#dc7fa0348213d789d35ffb719dea9685570989c7" integrity sha512-i9m8j/7YIv4mDuYXUAcrpKPSaju/CIly9AHK5jvCBeoiM/2KEsuCQTTP+rzSWWpLYWRukdXFSl6ZTk2/uumbiA== @@ -9117,11 +9305,6 @@ is-promise@^2.1.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-redirect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" - integrity sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw== - is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -9235,7 +9418,7 @@ is-windows@^1.0.0: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-wsl@^2.2.0: +is-wsl@^2.1.1, is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -9819,6 +10002,16 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json-stable-stringify@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz#52d4361b47d49168bcc4e564189a42e5a7439454" + integrity sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg== + dependencies: + call-bind "^1.0.5" + isarray "^2.0.5" + jsonify "^0.0.1" + object-keys "^1.1.1" + json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -9862,6 +10055,11 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonify@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" + integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== + jsonparse@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" @@ -9965,6 +10163,13 @@ kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +klaw-sync@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== + dependencies: + graceful-fs "^4.1.11" + kleur@4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" @@ -10313,7 +10518,7 @@ lodash.throttle@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== -lodash@4, lodash@4.17.21, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.21: +lodash@4, lodash@4.17.21, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -10636,6 +10841,14 @@ micromatch@^4.0.0, micromatch@^4.0.4: braces "^3.0.2" picomatch "^2.3.1" +micromatch@^4.0.2: + version "4.0.7" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" + integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" @@ -10648,7 +10861,7 @@ mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.27, mime-types@~2.1.19, dependencies: mime-db "1.52.0" -mime@1.6.0, mime@^1.3.4, mime@^1.4.1: +mime@1.6.0, mime@^1.3.4, mime@^1.4.1, mime@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -10844,7 +11057,7 @@ mkdirp@1.0.4, mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -"mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@~0.5.0, mkdirp@~0.5.1: +"mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@^0.5.6, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -10924,26 +11137,26 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -msgpackr-extract@^2.0.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-2.2.0.tgz#4bb749b58d9764cfdc0d91c7977a007b08e8f262" - integrity sha512-0YcvWSv7ZOGl9Od6Y5iJ3XnPww8O7WLcpYMDwX+PAA/uXLDtyw94PJv9GLQV/nnp3cWlDhMoyKZIQLrx33sWog== +msgpackr-extract@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz#e9d87023de39ce714872f9e9504e3c1996d61012" + integrity sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA== dependencies: - node-gyp-build-optional-packages "5.0.3" + node-gyp-build-optional-packages "5.2.2" optionalDependencies: - "@msgpackr-extract/msgpackr-extract-darwin-arm64" "2.2.0" - "@msgpackr-extract/msgpackr-extract-darwin-x64" "2.2.0" - "@msgpackr-extract/msgpackr-extract-linux-arm" "2.2.0" - "@msgpackr-extract/msgpackr-extract-linux-arm64" "2.2.0" - "@msgpackr-extract/msgpackr-extract-linux-x64" "2.2.0" - "@msgpackr-extract/msgpackr-extract-win32-x64" "2.2.0" - -msgpackr@1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.6.1.tgz#4f3c94d6a5b819b838ffc736eddaf60eba436d20" - integrity sha512-Je+xBEfdjtvA4bKaOv8iRhjC8qX2oJwpYH4f7JrG4uMVJVmnmkAT4pjKdbztKprGj3iwjcxPzb5umVZ02Qq3tA== + "@msgpackr-extract/msgpackr-extract-darwin-arm64" "3.0.3" + "@msgpackr-extract/msgpackr-extract-darwin-x64" "3.0.3" + "@msgpackr-extract/msgpackr-extract-linux-arm" "3.0.3" + "@msgpackr-extract/msgpackr-extract-linux-arm64" "3.0.3" + "@msgpackr-extract/msgpackr-extract-linux-x64" "3.0.3" + "@msgpackr-extract/msgpackr-extract-win32-x64" "3.0.3" + +msgpackr@^1.10.1: + version "1.11.0" + resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.11.0.tgz#8321d52333048cadc749f56385e3231e65337091" + integrity sha512-I8qXuuALqJe5laEBYoFykChhSXLikZmUhccjGsPuSJ/7uPip2TJ7lwdIQwWSAi0jGZDXv4WOP8Qg65QZRuXxXw== optionalDependencies: - msgpackr-extract "^2.0.2" + msgpackr-extract "^3.0.2" multer@1.4.4-lts.1: version "1.4.4-lts.1" @@ -10982,16 +11195,19 @@ nan@^2.14.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== -nano@^9.0.5: - version "9.0.5" - resolved "https://registry.yarnpkg.com/nano/-/nano-9.0.5.tgz#2b767819f612907a3ac09b21f2929d4097407262" - integrity sha512-fEAhwAdXh4hDDnC8cYJtW6D8ivOmpvFAqT90+zEuQREpRkzA/mJPcI4EKv15JUdajaqiLTXNoKK6PaRF+/06DQ== +nan@^2.17.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3" + integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw== + +nano@^10.1.3: + version "10.1.3" + resolved "https://registry.yarnpkg.com/nano/-/nano-10.1.3.tgz#5cb1ad14add4c9c82d53a79159848dafa84e7a13" + integrity sha512-q/hKQJJH3FhkkuJ3ojbgDph2StlSXFBPNkpZBZlsvZDbuYfxKJ4VtunEeilthcZtuIplIk1zVX5o2RgKTUTO+Q== dependencies: - "@types/tough-cookie" "^4.0.0" - axios "^0.21.1" - axios-cookiejar-support "^1.0.1" - qs "^6.9.4" - tough-cookie "^4.0.0" + axios "^1.6.2" + node-abort-controller "^3.0.1" + qs "^6.11.0" nanoclone@^0.2.1: version "0.2.1" @@ -11062,6 +11278,11 @@ node-abi@^2.21.0, node-abi@^2.7.0: dependencies: semver "^5.4.1" +node-abort-controller@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" + integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== + node-addon-api@^1.6.3: version "1.7.2" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" @@ -11096,10 +11317,12 @@ node-fetch@2.6.7, node-fetch@cjs: dependencies: whatwg-url "^5.0.0" -node-gyp-build-optional-packages@5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz#92a89d400352c44ad3975010368072b41ad66c17" - integrity sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA== +node-gyp-build-optional-packages@5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz#522f50c2d53134d7f3a76cd7255de4ab6c96a3a4" + integrity sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw== + dependencies: + detect-libc "^2.0.1" node-gyp-build@^4.2.1: version "4.6.1" @@ -11135,12 +11358,12 @@ node-loader@^2.0.0: dependencies: loader-utils "^2.0.0" -node-pty@0.11.0-beta17: - version "0.11.0-beta17" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.11.0-beta17.tgz#7df6a60dced6bf7a3a282b65cf51980c68954af6" - integrity sha512-JALo4LgYKmzmmXI23CIfS6DpCuno647YJpNg3RT6jCKTHWrt+RHeB6JAlb/pJG9dFNSeaiIAWD+0waEg2AzlfA== +node-pty@0.11.0-beta24: + version "0.11.0-beta24" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.11.0-beta24.tgz#084841017187656edaf14b459946c4a1d7cf8392" + integrity sha512-CzItw3hitX+wnpw9dHA/A+kcbV7ETNKrsyQJ+s0ZGzsu70+CSGuIGPLPfMnAc17vOrQktxjyRQfaqij75GVJFw== dependencies: - nan "^2.14.0" + nan "^2.17.0" node-releases@^2.0.13: version "2.0.13" @@ -11373,6 +11596,14 @@ onetime@^6.0.0: dependencies: mimic-fn "^4.0.0" +open@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + open@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" @@ -11383,6 +11614,11 @@ open@^9.1.0: is-inside-container "^1.0.0" is-wsl "^2.2.0" +opener@^1.5.1: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + optionator@^0.9.3: version "0.9.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" @@ -11520,6 +11756,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +pako@^1.0.4: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -11591,6 +11832,27 @@ parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +patch-package@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-8.0.0.tgz#d191e2f1b6e06a4624a0116bcb88edd6714ede61" + integrity sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA== + dependencies: + "@yarnpkg/lockfile" "^1.1.0" + chalk "^4.1.2" + ci-info "^3.7.0" + cross-spawn "^7.0.3" + find-yarn-workspace-root "^2.0.0" + fs-extra "^9.0.0" + json-stable-stringify "^1.0.2" + klaw-sync "^6.0.0" + minimist "^1.2.6" + open "^7.4.2" + rimraf "^2.6.3" + semver "^7.5.3" + slash "^2.0.0" + tmp "^0.0.33" + yaml "^2.2.2" + path-browserify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" @@ -11753,11 +12015,6 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -pify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" - integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== - pinkie-promise@^2.0.0, pinkie-promise@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -11855,6 +12112,15 @@ plist@^3.0.1, plist@^3.0.4: base64-js "^1.5.1" xmlbuilder "^15.1.1" +portfinder@^1.0.28: + version "1.0.32" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.32.tgz#2fe1b9e58389712429dc2bea5beb2146146c7f81" + integrity sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg== + dependencies: + async "^2.6.4" + debug "^3.2.7" + mkdirp "^0.5.6" + postcss-modules-extract-imports@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" @@ -12139,7 +12405,7 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== -psl@^1.1.28, psl@^1.1.33: +psl@^1.1.28: version "1.9.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== @@ -12220,7 +12486,14 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" -qs@^6.9.1, qs@^6.9.4: +qs@^6.11.0, qs@^6.4.0: + version "6.12.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.1.tgz#39422111ca7cbdb70425541cba20c7d7b216599a" + integrity sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ== + dependencies: + side-channel "^1.0.6" + +qs@^6.9.1: version "6.11.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== @@ -12232,11 +12505,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== -querystringify@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" - integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -12341,7 +12609,7 @@ react-is@^18.0.0, react-is@^18.2.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -react-perfect-scrollbar@^1.5.3: +react-perfect-scrollbar@^1.5.3, react-perfect-scrollbar@^1.5.8: version "1.5.8" resolved "https://registry.yarnpkg.com/react-perfect-scrollbar/-/react-perfect-scrollbar-1.5.8.tgz#380959387a325c5c9d0268afc08b3f73ed5b3078" integrity sha512-bQ46m70gp/HJtiBOF3gRzBISSZn8FFGNxznTdmTG8AAwpxG1bJCyn7shrgjEvGSQ5FJEafVEiosY+ccER11OSA== @@ -12766,7 +13034,7 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@2, rimraf@^2.6.1, rimraf@^2.6.2: +rimraf@2, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -12940,6 +13208,11 @@ schema-utils@^4.0.0: ajv-formats "^2.1.1" ajv-keywords "^5.1.0" +secure-compare@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3" + integrity sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw== + seek-bzip@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.6.tgz#35c4171f55a680916b52a07859ecf3b5857f21c4" @@ -13046,6 +13319,18 @@ set-function-length@^1.1.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + set-function-name@^2.0.0, set-function-name@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" @@ -13121,6 +13406,16 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -13178,6 +13473,11 @@ slash@^1.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" integrity sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg== +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -13211,7 +13511,7 @@ smart-buffer@^4.0.2, smart-buffer@^4.2.0: resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== -snabbdom@^3.5.1: +snabbdom@~3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/snabbdom/-/snabbdom-3.5.1.tgz#25f80ef15b194baea703d9d5441892e369de18e1" integrity sha512-wHMNIOjkm/YNE5EM3RCbr/+DVgPg6AqQAX1eOxO46zYNvCXjKP5Y865tqQj3EXnaMBjkxmQA5jFuDpDK/dbfiA== @@ -13431,25 +13731,21 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -sprotty-protocol@0.15.0-next.044bba2.13: - version "0.15.0-next.044bba2.13" - resolved "https://registry.yarnpkg.com/sprotty-protocol/-/sprotty-protocol-0.15.0-next.044bba2.13.tgz#10d5c36b00a8a24f62007336743c7e98d56e205b" - integrity sha512-DmWdIXFeZ5Gwa9uicbTNUkJMfUZa62w76Gb1w9lpYDs2S21HFZU4bvPuMD+yN31V/ChX5TGPCCTHmHLJ3Oe85w== - -sprotty-protocol@1.0.0, sprotty-protocol@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/sprotty-protocol/-/sprotty-protocol-1.0.0.tgz#b22e2da7e10b168debdc17feb61c4b832f01f614" - integrity sha512-p1H+ihcOmj0LEk2atcwOnYQPm0WByaOB1yX7fd869ONfQ5R+7x0X20YPdVLeCWmnhsszC/Rf91ojwaQiNIiHNA== +sprotty-protocol@1.2.0, sprotty-protocol@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/sprotty-protocol/-/sprotty-protocol-1.2.0.tgz#cfd6d637f2670a3d641997bb5add27cb1bddb57a" + integrity sha512-SHu61Qiw7bAD2nyRqdOASSihVNbeEuKI7cQx+o9EeyLpbmXKX6NTcGSVpxmWztHUIP0I6gZhKnkhF/BWo46mUQ== -sprotty@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/sprotty/-/sprotty-1.0.0.tgz#f575a97064ec90f08c468aa3f98a8b0df6887230" - integrity sha512-PaGVT1Qc19Mjl1X29UtMm1TNqz9k4AT1QgaXc04njSBKDsSfskS0OupV+oV5m/igbf+juOcHwcbcZdhAxF7RjQ== +sprotty@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/sprotty/-/sprotty-1.2.0.tgz#6c377b36fc6d410bcb65aff0d3893076f84bc8d8" + integrity sha512-/YL1+S+pLhV+hF0Z9C4vQGuaVv9NVsDgEqRnF+vevvdbeio1w8lfGxOMKjzY7DHcVDBQoKe0kbKJXvMr3f/RsA== dependencies: - autocompleter "^9.1.0" + autocompleter "^9.1.2" file-saver "^2.0.5" - snabbdom "^3.5.1" - sprotty-protocol "^1.0.0" + inversify "~6.0.2" + snabbdom "~3.5.1" + sprotty-protocol "^1.2.0" tinyqueue "^2.0.3" sshpk@^1.7.0: @@ -13533,14 +13829,6 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-replace-loader@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-replace-loader/-/string-replace-loader-3.1.0.tgz#11ac6ee76bab80316a86af358ab773193dd57a4f" - integrity sha512-5AOMUZeX5HE/ylKDnEa/KKBqvlnFmRZudSOjVJHxhoJg9QYTwl1rECx7SLR8BBH7tfxb4Rp7EM2XVfQFxIhsbQ== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -14057,16 +14345,6 @@ toposort@^2.0.2: resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== -tough-cookie@^4.0.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" - integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== - dependencies: - psl "^1.1.33" - punycode "^2.1.1" - universalify "^0.2.0" - url-parse "^1.5.3" - tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -14207,6 +14485,11 @@ tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.6.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== +tslib@^2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== + tsscmp@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" @@ -14429,6 +14712,13 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== +union@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/union/-/union-0.5.0.tgz#b2c11be84f60538537b846edb9ba266ba0090075" + integrity sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA== + dependencies: + qs "^6.4.0" + unique-filename@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-3.0.0.tgz#48ba7a5a16849f5080d26c760c86cf5cf05770ea" @@ -14448,11 +14738,6 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -universalify@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" - integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== - universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -14508,14 +14793,6 @@ url-join@^4.0.1: resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== -url-parse@^1.5.3: - version "1.5.10" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" - integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - use-immer@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/use-immer/-/use-immer-0.9.0.tgz#66e4e8f7ab75df45e96dfd5c56337f9fd49db9fd" @@ -14543,11 +14820,6 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@7.0.3, uuid@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" - integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== - uuid@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" @@ -14558,11 +14830,26 @@ uuid@^3.0.1, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.0.0, uuid@^8.3.2: +uuid@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" + integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== + +uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + +uuid@~10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294" + integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -14709,7 +14996,7 @@ vhost@^3.0.2: resolved "https://registry.yarnpkg.com/vhost/-/vhost-3.0.2.tgz#2fb1decd4c466aa88b0f9341af33dc1aff2478d5" integrity sha512-S3pJdWrpFWrKMboRU4dLYgMrTgoPALsmYwOvyebK2M6X95b9kQrjZy5rwl3uzzpfpENe/XrNYu/2U+e7/bmT5g== -vscode-jsonrpc@8.2.0, vscode-jsonrpc@^8.0.2: +vscode-jsonrpc@8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9" integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA== @@ -14871,6 +15158,13 @@ webpack@^5.76.0: watchpack "^2.4.0" webpack-sources "^3.2.3" +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== + dependencies: + iconv-lite "0.6.3" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -15158,16 +15452,26 @@ xterm-addon-fit@^0.5.0: resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.5.0.tgz#2d51b983b786a97dcd6cde805e700c7f913bc596" integrity sha512-DsS9fqhXHacEmsPxBJZvfj2la30Iz9xk+UKjhQgnYNkrUIN5CYLbw7WEfz117c7+S86S/tpHPfvNxJsF5/G8wQ== -xterm-addon-search@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.8.2.tgz#be7aa74d5ff12c901707c6ff674229f214318032" - integrity sha512-I1863mjn8P6uVrqm/X+btalVsqjAKLhnhpbP7SavAOpEkI1jJhbHU2UTp7NjeRtcKTks6UWk/ycgds5snDSejg== +xterm-addon-fit@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.8.0.tgz#48ca99015385141918f955ca7819e85f3691d35f" + integrity sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw== + +xterm-addon-search@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0.tgz#21286f4db48aa949fbefce34bb8bc0c9d3cec627" + integrity sha512-sDUwG4CnqxUjSEFh676DlS3gsh3XYCzAvBPSvJ5OPgF3MRL3iHLPfsb06doRicLC2xXNpeG2cWk8x1qpESWJMA== xterm@^4.16.0: version "4.19.0" resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0.tgz#c0f9d09cd61de1d658f43ca75f992197add9ef6d" integrity sha512-c3Cp4eOVsYY5Q839dR5IejghRPpxciGmLWWaP9g+ppfMeBChMeLa1DCA+pmX/jyDZ+zxFOmlJL/82qVdayVoGQ== +xterm@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0.tgz#867daf9cc826f3d45b5377320aabd996cb0fce46" + integrity sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg== + y18n@^3.2.1: version "3.2.2" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" @@ -15203,6 +15507,11 @@ yaml@^1.10.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.2.2: + version "2.4.5" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.5.tgz#60630b206dd6d84df97003d33fc1ddf6296cca5e" + integrity sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg== + yaml@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.2.tgz#7a2b30f2243a5fc299e1f14ca58d475ed4bc5362"