From 01776027200f23de9cc49ff40d3d6bd478cdd181 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 20 May 2026 14:16:50 +0200 Subject: [PATCH] GLSP-31: Update workflow example (standalone) Part-of: eclipse-glsp/glsp#1693 - Redesign standalone example app - Central diagram widget - Proper title bar - Additional toggleable section for keyboard bindings Add/introduce following features - Context menu support - Server-side provided context menus - Navigation/Marker menu entries - Edit task menu entries - Title bar with - Diagram menu similar to Theia integration - Including layout commands - Dirty state marker - Undo/Redo buttons - Theming support - 5 seletable themes - light/dark mode variants - Theme switcher in title bar - Extended urlParameter configuration - grid,readonly,mcp,theme,mode Fixes eclipse-glsp/glsp#31 - Fixes an issue with the GLSP Projection view which did not calculate scrollbars correctly for the diagrams that are off-center (don't start at 0,0) - Fixes an issue with the GLSPWebSocketConnectionHandler which did not properly try to reconnect if it was launched before the server was available - Add comment section to claude.md --- .gitignore | 2 + CLAUDE.md | 11 + .../src/workflow-diagram-module.ts | 4 +- .../workflow-glsp/src/workflow-startup.ts | 13 +- examples/workflow-glsp/src/workflow-views.tsx | 4 +- examples/workflow-standalone/README.md | 19 + examples/workflow-standalone/app/diagram.html | 500 +++- examples/workflow-standalone/css/app.css | 616 +++++ .../css/command-palette.css | 58 +- .../workflow-standalone/css/context-menu.css | 95 + examples/workflow-standalone/css/diagram.css | 227 +- examples/workflow-standalone/css/feedback.css | 79 + .../workflow-standalone/css/shortcuts.css | 131 + examples/workflow-standalone/css/themes.css | 261 ++ .../workflow-standalone/css/tool-palette.css | 204 ++ examples/workflow-standalone/esbuild.mjs | 113 + examples/workflow-standalone/package.json | 20 +- examples/workflow-standalone/scripts/start.ts | 12 +- .../src/common/di.config.ts | 10 +- .../standalone-context-menu-module.ts | 30 + .../standalone-context-menu-service.ts | 165 ++ ...rkflow-standalone-context-menu-provider.ts | 106 + .../standalone-task-editor-module.ts | 1 - .../common/features/title-bar/diagram-menu.ts | 136 + .../features/title-bar/title-bar-module.ts | 28 + .../features/title-bar/title-bar-toolbar.ts | 62 + .../window-resize/window-resize-module.ts | 28 + .../features/window-resize/window-resize.ts | 248 ++ .../workflow-standalone/webpack.config.js | 116 - package.json | 5 +- .../keyboard-pointer/keyboard-pointer.ts | 15 +- .../command-palette/command-palette.ts | 6 +- packages/client/src/utils/viewpoint-util.ts | 24 +- .../client/src/views/glsp-projection-view.tsx | 59 +- .../jsonrpc/ws-connection-provider.ts | 65 +- yarn.lock | 2205 ++--------------- 36 files changed, 3447 insertions(+), 2231 deletions(-) create mode 100644 examples/workflow-standalone/css/app.css create mode 100644 examples/workflow-standalone/css/context-menu.css create mode 100644 examples/workflow-standalone/css/feedback.css create mode 100644 examples/workflow-standalone/css/shortcuts.css create mode 100644 examples/workflow-standalone/css/themes.css create mode 100644 examples/workflow-standalone/css/tool-palette.css create mode 100644 examples/workflow-standalone/esbuild.mjs create mode 100644 examples/workflow-standalone/src/common/features/context-menu/standalone-context-menu-module.ts create mode 100644 examples/workflow-standalone/src/common/features/context-menu/standalone-context-menu-service.ts create mode 100644 examples/workflow-standalone/src/common/features/context-menu/workflow-standalone-context-menu-provider.ts create mode 100644 examples/workflow-standalone/src/common/features/title-bar/diagram-menu.ts create mode 100644 examples/workflow-standalone/src/common/features/title-bar/title-bar-module.ts create mode 100644 examples/workflow-standalone/src/common/features/title-bar/title-bar-toolbar.ts create mode 100644 examples/workflow-standalone/src/common/features/window-resize/window-resize-module.ts create mode 100644 examples/workflow-standalone/src/common/features/window-resize/window-resize.ts delete mode 100644 examples/workflow-standalone/webpack.config.js diff --git a/.gitignore b/.gitignore index e08ea5bc..0dbc0b70 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ examples/workflow-standalone/server .claude/local.settings.json .claude/plans + +.playwright-cli diff --git a/CLAUDE.md b/CLAUDE.md index d2958a72..2edf4af9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,6 +16,17 @@ Eclipse GLSP Client monorepo. Provides the sprotty-based client framework for th - After completing any code changes, always run the `/verify` skill before reporting completion - If verification fails, run the `/fix` skill to auto-fix issues, then re-run `/verify` +## Commenting Style + +- **TSDoc (`/** ... \*/`) on the public API\*\*: document exported interfaces, types, classes, methods, and notable properties/getters. Describe intent and behavior, not the obvious signature. +- **Cross-reference with `{@link Symbol}`** instead of writing bare type/method names in prose. +- **Document non-trivial methods** with `@param`/`@returns` (and `@throws` where relevant). Skip them for self-explanatory one-liners. +- **Deprecations** use the fixed form `/** @deprecated Use {@link Replacement} instead */`. +- **Inline `//` comments explain _why_, not _what_** — keep them short and lowercase, and reserve them for non-obvious decisions or rationale. +- **Mark known limitations** with `// FIXME:` / `// TODO:`, and justify suppressions with `// eslint-disable-next-line `. +- Don't restate code in comments; let clear naming carry the _what_. +- Copyright headers are required on every file but are handled by `/verify` + `/fix` — don't add them manually. + ## Inter-Package Import Rules These are enforced by ESLint and are easy to get wrong: diff --git a/examples/workflow-glsp/src/workflow-diagram-module.ts b/examples/workflow-glsp/src/workflow-diagram-module.ts index e23ccfc6..2bde8e52 100644 --- a/examples/workflow-glsp/src/workflow-diagram-module.ts +++ b/examples/workflow-glsp/src/workflow-diagram-module.ts @@ -54,7 +54,7 @@ import '../css/diagram.css'; import { taskEditorModule } from './direct-task-editing/task-editor-module'; import { BranchingNode, CategoryNode, Icon, SynchronizationNode, TaskNode, WeightedEdge } from './model'; import { WorkflowSnapper } from './workflow-snapper'; -import { WorkflowStartup } from './workflow-startup'; +import { GridDefaultVisible, WorkflowStartup } from './workflow-startup'; import { IconView, WorkflowEdgeView } from './workflow-views'; export const workflowDiagramModule = new FeatureModule( @@ -92,6 +92,8 @@ export const workflowDiagramModule = new FeatureModule( }); bindAsService(context, TYPES.IDiagramStartup, WorkflowStartup); + bind(GridDefaultVisible).toConstantValue(true); + bindOrRebind(context, TYPES.ISnapper).to(WorkflowSnapper); }, { featureId: Symbol('workflowDiagram') } diff --git a/examples/workflow-glsp/src/workflow-startup.ts b/examples/workflow-glsp/src/workflow-startup.ts index c42df907..6c7e7496 100644 --- a/examples/workflow-glsp/src/workflow-startup.ts +++ b/examples/workflow-glsp/src/workflow-startup.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2024 EclipseSource and others. + * Copyright (c) 2024-2026 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,13 +18,22 @@ import { IDiagramStartup, IGridManager } from '@eclipse-glsp/client'; import { MaybePromise, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; +/** + * Injection symbol for the initial grid visibility applied by the {@link WorkflowStartup}. + * Defaults to `true` (grid shown); rebind to override (e.g. the standalone app derives it from a url + * parameter). + */ +export const GridDefaultVisible = Symbol('GridDefaultVisible'); + @injectable() export class WorkflowStartup implements IDiagramStartup { rank = -1; @inject(TYPES.IGridManager) @optional() protected gridManager?: IGridManager; + @inject(GridDefaultVisible) @optional() protected gridDefaultVisible?: boolean; + preRequestModel(): MaybePromise { - this.gridManager?.setGridVisible(true); + this.gridManager?.setGridVisible(this.gridDefaultVisible ?? true); } } diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index dbb2c2e2..1ce4acde 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -41,7 +41,7 @@ export class WorkflowEdgeView extends PolylineEdgeViewWithGapsOnIntersections { ); @@ -77,7 +77,7 @@ export class IconView extends ShapeView { d={icon} /> - + {context.renderChildren(element)} ); diff --git a/examples/workflow-standalone/README.md b/examples/workflow-standalone/README.md index c9a5aaba..10d7fcab 100644 --- a/examples/workflow-standalone/README.md +++ b/examples/workflow-standalone/README.md @@ -100,3 +100,22 @@ All `start` and `dev` scripts support the following flags: - `--client-port ` – Set the webpack dev server port (default: 8082 in Node mode, 8083 in Browser mode) The server bundle download can also be skipped by setting the `SKIP_DOWNLOAD=true` environment variable. + +## URL Parameters + +The running application reads the following query parameters from the diagram URL. They are independent and can be combined, e.g.: + +``` +http://localhost:8082/diagram.html?grid&theme=ember&mode=dark +``` + +| Parameter | Values | Default | Description | +| ---------- | ------------------------------------------------ | ------------- | ---------------------------------------------------------------------------------------------- | +| `readonly` | _flag_ (presence only) | editable | Open the diagram in read-only mode (editing disabled). | +| `grid` | _flag_ (presence only) | off | Show the background grid. | +| `theme` | `tide`, `graphite`, `ember`, `orchid`, `verdant` | `tide` | Set the color theme. Persisted in local storage, so it also updates the in-app theme switcher. | +| `mode` | `light`, `dark` | OS preference | Set light or dark appearance. Persisted in local storage. | +| `mcp` | _flag_ (presence only) | off | Enable the MCP server integration (Node mode only). Also available via the `*:mcp` scripts. | + +Flag parameters only need to be present (their value is ignored), e.g. `?grid` or `?readonly`. +Invalid `theme`/`mode` values are ignored and fall back to the stored value or, for `mode`, the OS preference. diff --git a/examples/workflow-standalone/app/diagram.html b/examples/workflow-standalone/app/diagram.html index a83e6041..c3928615 100644 --- a/examples/workflow-standalone/app/diagram.html +++ b/examples/workflow-standalone/app/diagram.html @@ -1,32 +1,488 @@ - + - - + + + + + + -
+
+
+
+ +
+ Workflow Editor + Demo +
+ +
+ + + +
+ + +
+ + +
+ +
+ +
+ +
+
+
+ +
+
+ + Keyboard Shortcuts +
+
+
+
+ Navigate + Scroll Zoom in / out + Middle Drag Pan canvas + Arrow keys Move viewport / element + Ctrl Shift F Fit to screen + Ctrl Shift C Center selected + N Global navigation mode + Alt N Local navigation mode +
+
+ Zoom + + - Zoom viewport / element + Ctrl 0 Reset viewport + Ctrl + Zoom in via grid +
+
+ Edit + Ctrl Z Undo + Ctrl Y Redo + Del Delete + Ctrl A Select all + Esc Deselect all + Ctrl S Save + Ctrl Shift E Export SVG +
+
+ Resize (selected) + Alt A Enter resize mode + + - Grow / shrink + Ctrl 0 Reset size + Esc Exit resize mode +
+
+ Tools & Accessibility + Alt P Focus palette + Alt G Focus diagram + Alt H Show shortcut help + Ctrl Space Command palette + Ctrl F Search elements + Ctrl . Ctrl , Next / prev marker + 1 Select tool + 2 Eraser + 3 Marquee + 4 Validate + 5 Search +
+
+
+
+
+
+ + diff --git a/examples/workflow-standalone/css/app.css b/examples/workflow-standalone/css/app.css new file mode 100644 index 00000000..0df29888 --- /dev/null +++ b/examples/workflow-standalone/css/app.css @@ -0,0 +1,616 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/* + * Application frame: shell, card, title bar, theme switcher, canvas, footer. + * Single CSS entry point — imports the theme layer and the diagram styling. + */ + +@import './themes.css'; +@import './diagram.css'; + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html, +body { + width: 100%; + height: 100%; + overflow: hidden; + font-family: var(--font-body); + color: var(--text-1); + background: var(--app-bg); + -webkit-font-smoothing: antialiased; + transition: + background 0.4s ease, + color 0.4s ease; +} + +/* --- App Shell + Atmosphere --- */ + +.app-shell { + position: relative; + display: flex; + justify-content: center; + align-items: center; + height: 100%; + padding: 14px; +} + +.app-shell::before { + content: ''; + position: absolute; + inset: 0; + background: var(--atmosphere); + transition: background 0.4s ease; + pointer-events: none; +} + +.app-card { + position: relative; + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + max-width: 1600px; + max-height: 920px; + border-radius: 14px; + overflow: hidden; + background: var(--card-bg); + border: 1px solid var(--card-border); + box-shadow: var(--card-shadow); + transition: + background 0.4s ease, + border-color 0.4s ease, + box-shadow 0.4s ease; + /* `backwards` not `both`: a lingering transform would offset position:fixed overlays */ + animation: card-in 0.5s cubic-bezier(0.22, 1, 0.36, 1) backwards; +} + +@keyframes card-in { + from { + opacity: 0; + transform: translateY(8px) scale(0.992); + } + to { + opacity: 1; + transform: none; + } +} + +/* --- Window Resize Handles --- */ + +/* Overlay sibling of the card; only the handles themselves capture pointer events. */ +.window-resize-layer { + position: absolute; + inset: 0; + pointer-events: none; + z-index: 40; +} + +.window-resize-handle { + position: absolute; + pointer-events: auto; + touch-action: none; +} + +/* Corners sit above the edge handles so their diagonal cursor wins at overlaps. */ +.window-resize-handle--nw, +.window-resize-handle--ne, +.window-resize-handle--sw, +.window-resize-handle--se { + z-index: 1; +} + +/* --- Card Header (Title Bar) --- */ + +.card-header { + display: flex; + align-items: center; + gap: 14px; + padding: 13px 22px; + border-bottom: 1px solid var(--hairline); + flex-shrink: 0; + position: relative; + /* keep the header (and its theme dropdown) above the diagram tool palette */ + z-index: 30; +} + +.app-logo svg { + display: block; + height: 30px; + width: auto; +} + +.app-logo svg path { + fill: var(--logo); + transition: fill 0.4s ease; +} + +.app-divider { + width: 1px; + height: 20px; + background: var(--hairline); +} + +.app-title { + font-family: var(--font-display); + font-size: 16px; + font-weight: 600; + color: var(--text-1); + letter-spacing: -0.01em; +} + +.app-badge { + font-family: var(--font-mono); + font-size: 9.5px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--accent); + background: var(--accent-soft); + padding: 3px 7px; + border-radius: 4px; +} + +.app-spacer { + flex: 1; +} + +.app-links { + display: flex; + align-items: center; + gap: 2px; +} + +.app-link { + display: inline-flex; + align-items: center; + gap: 5px; + font-size: 12px; + font-weight: 500; + color: var(--text-3); + text-decoration: none; + padding: 5px 9px; + border-radius: 6px; + transition: + color 0.15s, + background 0.15s; +} + +.app-link:hover { + color: var(--text-2); + background: var(--surface-hover); +} + +.app-link svg { + width: 13px; + height: 13px; + flex-shrink: 0; +} + +/* --- Title Bar Menu Bar --- */ + +.app-menubar { + display: flex; + align-items: center; + gap: 2px; +} + +.menubar-item { + font-family: inherit; + font-size: 13px; + font-weight: 500; + color: var(--text-2); + background: transparent; + border: none; + padding: 5px 10px; + border-radius: 6px; + cursor: pointer; + transition: + color 0.15s, + background 0.15s; +} + +.menubar-item:hover, +.menubar-item.active { + color: var(--text-1); + background: var(--surface-active); +} + +/* --- Title Bar Toolbar --- */ + +.title-bar-toolbar { + display: flex; + align-items: center; + gap: 2px; + margin-right: 6px; +} + +.toolbar-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border: none; + border-radius: 6px; + background: transparent; + color: var(--text-3); + cursor: pointer; + transition: + color 0.15s, + background 0.15s; +} + +.toolbar-btn:hover { + color: var(--text-1); + background: var(--surface-hover); +} + +.toolbar-btn:active { + background: var(--surface-active); +} + +.toolbar-btn svg { + width: 14px; + height: 14px; +} + +.dirty-indicator { + width: 7px; + height: 7px; + border-radius: 50%; + background: transparent; + margin-right: 4px; + transition: + background 0.2s, + box-shadow 0.2s; +} + +.dirty-indicator.active { + background: var(--warning); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--warning) 22%, transparent); +} + +/* --- Theme Switcher --- */ + +.theme-switcher { + position: relative; +} + +.theme-trigger { + display: inline-flex; + align-items: center; + gap: 8px; + font-family: inherit; + font-size: 12px; + font-weight: 500; + color: var(--text-2); + background: var(--surface-sunken); + border: 1px solid var(--hairline); + padding: 5px 8px 5px 7px; + border-radius: 8px; + cursor: pointer; + transition: + color 0.15s, + background 0.15s, + border-color 0.15s; +} + +.theme-trigger:hover { + color: var(--text-1); + background: var(--surface-hover); + border-color: var(--overlay-border); +} + +.theme-swatch { + width: 16px; + height: 16px; + border-radius: 5px; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12); + flex-shrink: 0; +} + +.theme-trigger .theme-mode-glyph { + width: 13px; + height: 13px; + color: var(--text-3); +} + +.theme-trigger .theme-caret { + width: 9px; + height: 9px; + color: var(--text-3); + transition: transform 0.2s ease; +} + +.theme-switcher.open .theme-caret { + transform: rotate(180deg); +} + +.theme-menu { + position: absolute; + top: calc(100% + 8px); + right: 0; + width: 250px; + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + border: 1px solid var(--overlay-border); + border-radius: 12px; + box-shadow: var(--overlay-shadow); + padding: 6px; + z-index: 100; + opacity: 0; + transform: translateY(-6px) scale(0.98); + transform-origin: top right; + pointer-events: none; + transition: + opacity 0.16s ease, + transform 0.16s ease; +} + +.theme-switcher.open .theme-menu { + opacity: 1; + transform: none; + pointer-events: auto; +} + +.theme-menu-label { + font-size: 9.5px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.07em; + color: var(--text-3); + padding: 6px 8px 5px; +} + +/* Light / dark segmented toggle */ +.mode-toggle { + display: flex; + gap: 4px; + padding: 2px; + margin: 0 4px 4px; + background: var(--surface-sunken); + border-radius: 9px; +} + +.mode-btn { + flex: 1; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + font-family: inherit; + font-size: 12px; + font-weight: 600; + color: var(--text-3); + background: transparent; + border: none; + padding: 6px 0; + border-radius: 7px; + cursor: pointer; + transition: + color 0.13s, + background 0.13s, + box-shadow 0.13s; +} + +.mode-btn svg { + width: 14px; + height: 14px; +} + +.mode-btn:hover { + color: var(--text-1); +} + +.mode-btn.active { + color: var(--accent); + background: var(--card-bg); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12); +} + +.theme-option { + display: flex; + align-items: center; + gap: 11px; + width: 100%; + text-align: left; + font-family: inherit; + background: transparent; + border: none; + border-radius: 9px; + padding: 8px; + cursor: pointer; + color: var(--text-1); + transition: background 0.13s; +} + +.theme-option:hover { + background: var(--surface-hover); +} + +.theme-option-swatch { + width: 34px; + height: 34px; + border-radius: 8px; + flex-shrink: 0; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12); + position: relative; +} + +.theme-option-text { + display: flex; + flex-direction: column; + gap: 1px; + min-width: 0; +} + +.theme-option-name { + font-size: 13px; + font-weight: 600; + letter-spacing: -0.01em; +} + +.theme-option-desc { + font-size: 11px; + color: var(--text-3); +} + +.theme-option-check { + margin-left: auto; + width: 16px; + height: 16px; + color: var(--accent); + opacity: 0; + flex-shrink: 0; +} + +.theme-option.selected .theme-option-check { + opacity: 1; +} + +.theme-option.selected .theme-option-name { + color: var(--accent); +} + +/* --- Diagram Canvas --- */ + +.card-canvas { + flex: 1; + position: relative; + background: var(--canvas-bg); + min-height: 0; + transition: background 0.4s ease; + /* own stacking context so the tool palette stays below the header dropdown */ + z-index: 1; +} + +#sprotty { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +} + +#sprotty svg { + width: 100%; + height: 100%; +} + +/* --- Card Footer (Shortcuts) --- */ + +.card-footer { + flex-shrink: 0; + border-top: 1px solid var(--hairline); +} + +.shortcuts-toggle { + display: flex; + align-items: center; + gap: 6px; + padding: 10px 22px; + font-size: 12px; + font-weight: 500; + color: var(--text-3); + cursor: pointer; + user-select: none; +} + +.shortcuts-toggle:hover { + color: var(--text-2); +} + +.shortcuts-chevron { + display: inline-block; + font-size: 9px; + transition: transform 0.2s ease; +} + +.shortcuts-chevron.open { + transform: rotate(90deg); +} + +.shortcuts-panel { + max-height: 0; + overflow: hidden; + transition: max-height 0.25s ease; +} + +.shortcuts-panel.open { + max-height: 320px; + overflow-y: auto; +} + +.shortcuts-inner { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 4px 28px; + padding: 0 22px 14px; +} + +.shortcuts-section { + display: flex; + flex-direction: column; + gap: 1px; +} + +.shortcuts-section-title { + font-size: 9px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-3); + padding-bottom: 2px; +} + +.shortcut-item { + display: flex; + align-items: center; + gap: 7px; + font-size: 11px; + color: var(--text-2); + line-height: 1.6; +} + +.shortcut-item kbd { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + padding: 2px 5px; + font-family: var(--font-mono); + font-size: 9px; + font-weight: 500; + color: var(--text-2); + background: var(--surface-sunken); + border: 1px solid var(--hairline); + border-radius: 4px; + white-space: nowrap; +} + +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 1px; +} diff --git a/examples/workflow-standalone/css/command-palette.css b/examples/workflow-standalone/css/command-palette.css index 6eec88c8..273ffa61 100644 --- a/examples/workflow-standalone/css/command-palette.css +++ b/examples/workflow-standalone/css/command-palette.css @@ -39,7 +39,7 @@ .command-palette.validation.error input, .command-palette.validation.error input:focus { - color: var(--glsp-error-foregroundd); + color: var(--glsp-error-foreground); outline-color: var(--glsp-error-foreground); } @@ -58,3 +58,59 @@ background-color: var(--glsp-warning-foreground); color: black; } + +/* --- Container, input & suggestions --- */ + +.command-palette { + font-family: var(--font-body); + border-radius: 10px; + box-shadow: var(--overlay-shadow); + overflow: hidden; +} + +.command-palette input { + font-family: var(--font-body); + font-size: 14px; + padding: 10px 14px; + border: none; + border-bottom: 1px solid var(--hairline); + outline: none; + color: var(--text-1); + background: var(--surface-solid); +} + +.command-palette input::placeholder { + color: var(--text-3); +} + +.command-palette-suggestions { + background: var(--surface-solid); + border: none; + border-radius: 0 0 10px 10px; + box-shadow: none; + font-size: 13px; + color: var(--text-2); +} + +.command-palette-suggestions > div { + padding: 6px 14px; +} + +.command-palette-suggestions .group { + background: var(--surface-sunken); + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-2); + padding: 6px 14px; +} + +.command-palette-suggestions > div:hover:not(.group) { + background: var(--surface-hover); +} + +.command-palette-suggestions > div.selected { + background: var(--accent-soft); + color: var(--accent); +} diff --git a/examples/workflow-standalone/css/context-menu.css b/examples/workflow-standalone/css/context-menu.css new file mode 100644 index 00000000..c67b9ad4 --- /dev/null +++ b/examples/workflow-standalone/css/context-menu.css @@ -0,0 +1,95 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.glsp-context-menu-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; +} + +.glsp-context-menu { + position: fixed; + min-width: 160px; + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + border: 1px solid var(--overlay-border); + border-radius: 8px; + padding: 4px 0; + box-shadow: var(--overlay-shadow); + font-family: var(--font-body); + font-size: 13px; + z-index: 1001; +} + +.glsp-context-menu-item { + display: flex; + align-items: center; + padding: 6px 12px; + cursor: pointer; + color: var(--text-1); + user-select: none; + position: relative; +} + +.glsp-context-menu-item:hover { + background: var(--surface-hover); +} + +.glsp-context-menu-item.disabled { + color: var(--text-3); + cursor: default; + pointer-events: none; +} + +.glsp-context-menu-indicator { + width: 16px; + font-size: 11px; + text-align: center; + flex-shrink: 0; + color: var(--text-2); +} + +.glsp-context-menu-label { + flex: 1; +} + +.glsp-context-menu-chevron { + font-size: 10px; + color: var(--text-3); + margin-left: 8px; +} + +.glsp-context-menu-separator { + height: 1px; + margin: 4px 0; + background: var(--hairline); +} + +/* Submenus */ +.glsp-context-menu-submenu { + display: none; + position: absolute; + left: 100%; + top: -4px; +} + +.glsp-context-menu-item.has-submenu:hover > .glsp-context-menu-submenu { + display: block; +} diff --git a/examples/workflow-standalone/css/diagram.css b/examples/workflow-standalone/css/diagram.css index 9ac2d766..a9b1f3f5 100644 --- a/examples/workflow-standalone/css/diagram.css +++ b/examples/workflow-standalone/css/diagram.css @@ -13,70 +13,259 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -:root { - --glsp-info-foreground: blue; + +/* + * Diagram/SVG styling. Colors resolve to theme tokens from themes.css; this only + * re-skins the base @eclipse-glsp/client and workflow-glsp styles. Overlay + * components live in the sibling files imported below (kept first for cascade order). + */ + +@import './tool-palette.css'; +@import './command-palette.css'; +@import './context-menu.css'; +@import './shortcuts.css'; +@import './feedback.css'; + +.sprotty svg { + border: none; +} + +.sprotty-graph { + font-family: var(--font-body); +} + +.sprotty-text { + font-family: var(--font-body); } .sprotty-graph, .grid-background { - background: rgb(179, 196, 202); + background: var(--canvas-bg); +} + +.grid-background .sprotty-graph, +.grid-background.sprotty-graph { + /* feed the themed grid line color into the base grid.css variable */ + --grid-color: var(--grid-line); } +/* --- Nodes & Edges --- */ + .sprotty-node { - fill: #cdc; - stroke: rgb(0, 0, 0); + fill: var(--node-fill); + stroke: var(--node-stroke); + /* No SVG `filter` on nodes: under the viewport zoom transform a `drop-shadow` + filter gets clipped to its rectangular region and, worse, the browser leaves a + displaced copy of the filtered node (an offset "ghost" outline) on zoom/pan. + Depth/feedback is done with crisp strokes instead, which scale cleanly. */ + filter: none; + transition: + stroke 0.15s ease, + stroke-width 0.15s ease, + opacity 0.15s ease; } .sprotty-edge { - stroke: black; + stroke: var(--edge); + stroke-linecap: round; + stroke-linejoin: round; } .sprotty-edge.arrow { - fill: black; + fill: var(--edge); } .sprotty-edge.selected { - stroke: #844; + stroke: var(--selection); } .sprotty-edge.selected > .arrow { - fill: #844; - stroke: #844; + fill: var(--selection); + stroke: var(--selection); } .forkOrJoin > .sprotty-node { - fill: black; + fill: var(--node-fork); } .forkOrJoin > .sprotty-node.selected { - stroke: rgb(87, 87, 214); + stroke: var(--selection); } polygon.sprotty-node { - stroke: black; + fill: var(--node-poly-fill); + stroke: var(--node-poly-stroke); + stroke-width: 1.25; } +/* --- Selection & Hover Feedback --- */ + +/* crisp stroke outlines (no SVG filters) so feedback stays sharp at any zoom level */ .sprotty-node.selected { - stroke: rgb(87, 87, 214); + stroke: var(--selection); + stroke-width: 2.5px; +} + +.sprotty-node.mouseover:not(.selected) { + /* dim on hover, matching the base client / master behavior; no SVG filter so nothing + ghosts or clips under the zoom transform (the opacity transition eases it in) */ + opacity: 60%; +} + +.sprotty-edge.mouseover:not(.selected) { + stroke: var(--accent); + opacity: 85%; +} + +.sprotty-edge.mouseover:not(.selected) > .arrow { + fill: var(--accent); + stroke: var(--accent); +} + +/* --- Resize & Routing Handles --- */ + +.sprotty-resize-handle, +.sprotty-edge > .sprotty-routing-handle { + fill: var(--handle); + stroke: var(--handle-ring); + stroke-width: 1.5px; + transition: r 0.12s ease; +} + +.sprotty-resize-handle.selected, +.sprotty-resize-handle.active, +.sprotty-edge > .sprotty-routing-handle.selected { + fill: var(--handle); + r: 6px; +} + +.sprotty-resize-handle.movement-not-allowed, +.sprotty-resize-handle.resize-not-allowed { + fill: var(--glsp-error-foreground); + stroke: var(--handle-ring); +} + +.sprotty-edge > .sprotty-routing-handle.mouseover { + stroke: var(--accent); + stroke-width: 1.5; +} + +.sprotty-node.marquee { + fill: var(--accent); + opacity: 0.16; +} + +/* --- Diagram Labels --- */ + +.sprotty-graph .sprotty-label:not(.heading) { + fill: var(--diagram-label); +} + +/* --- Workflow Node Theming (re-skins workflow-glsp node fills) --- */ + +.task.automated > .sprotty-node { + fill: var(--node-automated); } +.task.manual > .sprotty-node { + fill: var(--node-manual); +} + +.category > .sprotty-node { + fill: var(--node-category); +} + +.category .category > .sprotty-node { + fill: var(--node-category-nested); + stroke: var(--node-category-nested-stroke); + stroke-width: 1; +} + +.task .sprotty-label, +.category .sprotty-label { + fill: var(--node-label); +} + +.task .sprotty-label.heading, +.category .sprotty-label.heading { + fill: color-mix(in srgb, var(--node-label) 82%, transparent); +} + +.icon path { + fill: color-mix(in srgb, var(--node-label) 95%, transparent); +} + +.sprotty-edge.weighted.low:not(.selected, .navigable-element, .search-highlighted), +.sprotty-edge.weighted.low:not(.selected, .navigable-element, .search-highlighted) .arrow { + stroke: var(--edge-low); +} + +.sprotty-edge.weighted.low:not(.selected, .navigable-element, .search-highlighted) .arrow { + fill: var(--edge-low); +} + +.sprotty-edge.weighted:not(.selected, .navigable-element, .search-highlighted), +.sprotty-edge.weighted:not(.selected, .navigable-element, .search-highlighted) .arrow, +.sprotty-edge.weighted.medium:not(.selected, .navigable-element, .search-highlighted), +.sprotty-edge.weighted.medium:not(.selected, .navigable-element, .search-highlighted) .arrow { + stroke: var(--edge-medium); +} + +.sprotty-edge.weighted:not(.selected, .navigable-element, .search-highlighted) .arrow, +.sprotty-edge.weighted.medium:not(.selected, .navigable-element, .search-highlighted) .arrow { + fill: var(--edge-medium); +} + +.sprotty-edge.weighted.high:not(.selected, .navigable-element, .search-highlighted), +.sprotty-edge.weighted.high:not(.selected, .navigable-element, .search-highlighted) .arrow { + stroke: var(--edge-high); +} + +.sprotty-edge.weighted.high:not(.selected, .navigable-element, .search-highlighted) .arrow { + fill: var(--edge-high); +} + +/* --- Projection / Minimap Bars --- */ + .bordered-projection-bar { - border-color: #a1a1a1; + border-color: var(--hairline); } .sprotty-viewport { border-width: 1px; - border-color: #555555; + border-color: var(--node-stroke); } .sprotty-projection-bar.horizontal.bordered-projection-bar { - height: 15px; + height: 12px; } .sprotty-projection-bar.vertical.bordered-projection-bar { - width: 15px; + width: 12px; } .projection-scroll-bar { - background-color: #555555; + background-color: var(--text-3); + border-radius: 3px; +} + +.glsp-projection { + background-color: var(--text-3); + border-radius: 2px; + opacity: 0.7; +} + +.glsp-projection.sprotty-issue.sprotty-error { + background-color: var(--glsp-error-foreground); + opacity: 1; +} + +.glsp-projection.sprotty-issue.sprotty-warning { + background-color: var(--glsp-warning-foreground); + opacity: 1; +} + +.glsp-projection.sprotty-issue.sprotty-info { + background-color: var(--glsp-info-foreground); + opacity: 1; } diff --git a/examples/workflow-standalone/css/feedback.css b/examples/workflow-standalone/css/feedback.css new file mode 100644 index 00000000..8e52a37b --- /dev/null +++ b/examples/workflow-standalone/css/feedback.css @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/* Transient feedback UI: toasts, autocomplete palette, sprotty popups. */ + +/* --- Toast --- */ + +.toast { + left: 20px; + bottom: 20px; +} + +.toast-container { + font-family: var(--font-body); + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + color: var(--text-1); + border: 1px solid var(--overlay-border); + border-radius: 8px; + padding: 8px 14px; + font-size: 12px; + box-shadow: var(--overlay-shadow); +} + +/* --- Autocomplete Palette --- */ + +.autocomplete-palette { + font-family: var(--font-body); +} + +.autocomplete-palette input { + font-family: var(--font-body); + font-size: 14px; + padding: 10px 14px; + border: 1px solid var(--overlay-border); + border-radius: 10px; + outline: none; + color: var(--text-1); + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + box-shadow: var(--overlay-shadow); +} + +.autocomplete-palette input:focus { + border-color: var(--accent); + box-shadow: + var(--overlay-shadow), + 0 0 0 2px var(--accent-glow); +} + +/* --- Sprotty Popup (Tooltip) --- */ + +.sprotty-popup { + font-family: var(--font-body); + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + border: 1px solid var(--overlay-border); + border-radius: 8px; + box-shadow: var(--overlay-shadow); + color: var(--text-2); + font-size: 12px; + padding: 8px 12px; +} diff --git a/examples/workflow-standalone/css/shortcuts.css b/examples/workflow-standalone/css/shortcuts.css new file mode 100644 index 00000000..5bdf7cb6 --- /dev/null +++ b/examples/workflow-standalone/css/shortcuts.css @@ -0,0 +1,131 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/* Keyboard grid + keyboard-shortcuts overlay. */ + +/* --- Keyboard Grid Overlay --- */ + +.grid-item { + border-color: var(--overlay-border); +} + +.grid-item-number { + background: var(--surface-solid); + border: 1.5px solid var(--overlay-border); + color: var(--text-2); + font-family: var(--font-mono); + font-weight: 600; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); + display: flex; + align-items: center; + justify-content: center; +} + +/* --- Keyboard Shortcuts Overlay --- */ + +.keyboard-shortcuts-menu { + font-family: var(--font-body); + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + border: 1px solid var(--overlay-border); + border-radius: 12px; + box-shadow: var(--overlay-shadow); + color: var(--text-1); + font-size: 13px; + line-height: 1.5; +} + +.keyboard-shortcuts-menu h3 { + font-size: 14px; + font-weight: 700; + color: var(--text-1); + padding: 12px 16px; + margin: 0; + border-bottom: 1px solid var(--hairline); +} + +.keyboard-shortcuts-container { + padding: 8px 16px 16px; + color: var(--text-2); + font-size: 13px; +} + +.menu-header { + background: var(--surface-sunken); + padding: 6px 16px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-2); + border-bottom: 1px solid var(--hairline); +} + +.shortcut-entry-container { + padding: 3px 0; + color: var(--text-2); + font-size: 13px; +} + +.column-title { + font-weight: 700; + color: var(--text-1); + font-size: 13px; + padding-top: 8px; +} + +.keyboard-shortcuts-menu kbd { + background: var(--surface-sunken); + border: 1px solid var(--overlay-border); + border-radius: 4px; + color: var(--text-2); + font-family: var(--font-mono); + font-size: 11px; + font-weight: 500; + padding: 2px 6px; + box-shadow: none; + text-shadow: none; +} + +#key-shortcut-close-btn { + top: 10px; + right: 10px; + background: transparent; + color: var(--text-3); + font-size: 18px; + border-radius: 6px; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +} + +#key-shortcut-close-btn:hover { + background: var(--surface-hover); + color: var(--text-2); +} + +.shortcut-table thead th { + border-bottom: 1px solid var(--overlay-border); + font-weight: 600; + color: var(--text-2); + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.04em; +} diff --git a/examples/workflow-standalone/css/themes.css b/examples/workflow-standalone/css/themes.css new file mode 100644 index 00000000..217cc926 --- /dev/null +++ b/examples/workflow-standalone/css/themes.css @@ -0,0 +1,261 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/* + * Theme layer: all tokens, keyed on data-mode (light/dark) and data-theme (5 families). + * Each family's --selection sits outside its node palette so selected outlines stay visible. + */ + +/* ---------- Cross-theme constants ---------- */ + +:root { + /* Single external font (Outfit) for the whole app, shared across all themes; mono and the + fallbacks stay on system fonts so no other web fonts are loaded. */ + --font-body: 'Outfit', system-ui, 'Segoe UI', sans-serif; + --font-display: var(--font-body); + --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + --node-label: #ffffff; + --handle: var(--selection); + --handle-ring: var(--card-bg); + --danger: #e11d48; + --warning: #f59e0b; + --info: var(--accent); +} + +/* ---------- Mode neutrals ---------- */ + +:root, +[data-mode='light'] { + color-scheme: light; + + /* neutral surfaces carry a few percent of the family accent for a cohesive tint */ + --app-bg: color-mix(in srgb, var(--accent) 7%, #e9edf2); + --card-bg: color-mix(in srgb, var(--accent) 3%, #ffffff); + --card-border: rgba(15, 23, 42, 0.07); + --card-shadow: 0 1px 3px rgba(2, 6, 23, 0.08), 0 18px 44px -18px rgba(2, 6, 23, 0.22); + --hairline: rgba(15, 23, 42, 0.07); + + --canvas-bg: color-mix(in srgb, var(--accent) 5%, #f5f7fa); + --grid-line: rgba(15, 23, 42, 0.055); + + --surface: rgba(255, 255, 255, 0.86); + --surface-solid: color-mix(in srgb, var(--accent) 3%, #ffffff); + --surface-sunken: rgba(15, 23, 42, 0.04); + --surface-hover: rgba(15, 23, 42, 0.05); + --surface-active: rgba(15, 23, 42, 0.08); + --overlay-border: rgba(15, 23, 42, 0.09); + --overlay-shadow: 0 8px 32px rgba(2, 6, 23, 0.12), 0 2px 6px rgba(2, 6, 23, 0.05); + --blur: 16px; + + --text-1: #0f172a; + --text-2: #475569; + --text-3: #94a3b8; + --text-4: #b4bdca; + + --node-fill: #e2e8f0; + --node-stroke: #cbd5e1; + --node-poly-fill: #cbd5e1; + --node-poly-stroke: #94a3b8; + --node-fork: #334155; + --diagram-label: #1e293b; + --edge: #64748b; +} + +[data-mode='dark'] { + color-scheme: dark; + + /* tinted a touch more for a richer colored-dark feel */ + --app-bg: color-mix(in srgb, var(--accent) 10%, #0a0d13); + --card-bg: color-mix(in srgb, var(--accent) 7%, #11151e); + --card-border: rgba(255, 255, 255, 0.08); + --card-shadow: 0 1px 3px rgba(0, 0, 0, 0.5), 0 22px 50px -18px rgba(0, 0, 0, 0.7); + --hairline: rgba(255, 255, 255, 0.07); + + --canvas-bg: color-mix(in srgb, var(--accent) 8%, #0c1017); + --grid-line: rgba(255, 255, 255, 0.045); + + --surface: rgba(24, 30, 41, 0.84); + --surface-solid: color-mix(in srgb, var(--accent) 8%, #181e29); + --surface-sunken: rgba(255, 255, 255, 0.045); + --surface-hover: rgba(255, 255, 255, 0.07); + --surface-active: rgba(255, 255, 255, 0.11); + --overlay-border: rgba(255, 255, 255, 0.1); + --overlay-shadow: 0 12px 40px rgba(0, 0, 0, 0.55), 0 2px 6px rgba(0, 0, 0, 0.4); + --blur: 18px; + + --text-1: #e7eaf0; + --text-2: #9aa4b3; + --text-3: #6b7585; + --text-4: #4d5664; + + --node-fill: #232b39; + --node-stroke: #344052; + --node-poly-fill: #2a3344; + --node-poly-stroke: #3d4a5e; + --node-fork: #8a93a3; + --diagram-label: #cbd5e1; + --edge: #6b7585; +} + +/* ---------- Family identity ---------- */ + +:root, +[data-theme='tide'] { + --accent: #0ea5b7; + --accent-strong: #0b8c9c; + --accent-soft: rgba(14, 165, 183, 0.12); + --accent-on: #ffffff; + --accent-glow: rgba(14, 165, 183, 0.34); + --logo: #0ea5b7; + --atmosphere: radial-gradient(900px 620px at 10% -5%, rgba(14, 165, 183, 0.14), transparent 60%), + radial-gradient(820px 600px at 102% 104%, rgba(84, 112, 184, 0.13), transparent 55%); + + --node-automated: #0d9aac; + --node-manual: #f0795a; + --node-category: #5470b8; + --node-category-nested: #6f8fce; + --node-category-nested-stroke: #3f5fa0; + --edge-low: #9db4dc; + --edge-medium: #5570b5; + --edge-high: #3a58a8; + + --selection: #7c5cff; + --selection-glow: rgba(124, 92, 255, 0.45); +} + +[data-theme='graphite'] { + --accent: #3b9eff; + --accent-strong: #5cb0ff; + --accent-soft: rgba(59, 158, 255, 0.16); + --accent-on: #051321; + --accent-glow: rgba(59, 158, 255, 0.45); + --logo: #3b9eff; + --atmosphere: radial-gradient(1000px 720px at 0% -8%, rgba(59, 158, 255, 0.14), transparent 60%), + radial-gradient(900px 700px at 104% 106%, rgba(47, 159, 176, 0.1), transparent 55%); + + --node-automated: #3187d4; + --node-manual: #2f9fb0; + --node-category: #5b82c5; + --node-category-nested: #6f95d6; + --node-category-nested-stroke: #3f63a0; + --edge-low: #8fa6cf; + --edge-medium: #5a78bd; + --edge-high: #3d5fb0; + + --selection: #fb5fa0; + --selection-glow: rgba(251, 95, 160, 0.45); +} + +[data-theme='ember'] { + --accent: #c2410c; + --accent-strong: #9a3412; + --accent-soft: rgba(194, 65, 12, 0.12); + --accent-on: #fff7ed; + --accent-glow: rgba(194, 65, 12, 0.34); + --logo: #c2410c; + --atmosphere: radial-gradient(900px 620px at 8% -5%, rgba(194, 65, 12, 0.14), transparent 60%), + radial-gradient(820px 600px at 104% 106%, rgba(202, 138, 4, 0.16), transparent 55%); + + --node-automated: #c2410c; + --node-manual: #4f7d6e; + --node-category: #a16207; + --node-category-nested: #bd7e1f; + --node-category-nested-stroke: #825009; + --edge-low: #c2b08f; + --edge-medium: #a3814a; + --edge-high: #c2410c; + + --selection: #0ea5e9; + --selection-glow: rgba(14, 165, 233, 0.45); +} + +[data-theme='orchid'] { + --accent: #d946ef; + --accent-strong: #e879f9; + --accent-soft: rgba(217, 70, 239, 0.18); + --accent-on: #11061a; + --accent-glow: rgba(217, 70, 239, 0.5); + --logo: #d946ef; + --atmosphere: radial-gradient(1000px 720px at 14% -8%, rgba(217, 70, 239, 0.18), transparent 60%), + radial-gradient(900px 700px at 104% 108%, rgba(34, 206, 192, 0.13), transparent 55%); + + --node-automated: #d437e8; + --node-manual: #22cec0; + --node-category: #7c7cf0; + --node-category-nested: #9a9af6; + --node-category-nested-stroke: #5b5be0; + --edge-low: #7c6f9e; + --edge-medium: #a855f7; + --edge-high: #d946ef; + + --selection: #f5b313; + --selection-glow: rgba(245, 179, 19, 0.5); +} + +[data-theme='verdant'] { + --accent: #10b981; + --accent-strong: #0e9f6e; + --accent-soft: rgba(16, 185, 129, 0.14); + --accent-on: #04231a; + --accent-glow: rgba(16, 185, 129, 0.4); + --logo: #10b981; + --atmosphere: radial-gradient(940px 660px at 10% -6%, rgba(16, 185, 129, 0.14), transparent 60%), + radial-gradient(840px 620px at 104% 106%, rgba(44, 122, 158, 0.13), transparent 55%); + + --node-automated: #0e9f6e; + --node-manual: #d08700; + --node-category: #2c7a9e; + --node-category-nested: #3b93b8; + --node-category-nested-stroke: #236781; + --edge-low: #8fb8a6; + --edge-medium: #3f8f6e; + --edge-high: #0e7a52; + + --selection: #8b5cf6; + --selection-glow: rgba(139, 92, 246, 0.46); +} + +/* ---------- GLSP semantic-color bridge ---------- */ + +:root { + --glsp-error-foreground: var(--danger, #e11d48); + --glsp-warning-foreground: var(--warning, #f59e0b); + --glsp-info-foreground: var(--info, #0ea5b7); + --glsp-navigation-highlight: var(--accent-glow, rgba(14, 165, 183, 0.25)); + --glsp-search-highlight: var(--accent, #0ea5b7); +} + +/* ---------- Theme-switcher swatch gradients ---------- */ +.theme-swatch[data-theme='tide'], +.theme-option[data-theme='tide'] .theme-option-swatch { + background: linear-gradient(135deg, #0ea5b7 0%, #5470b8 55%, #f0795a 100%); +} +.theme-swatch[data-theme='graphite'], +.theme-option[data-theme='graphite'] .theme-option-swatch { + background: linear-gradient(135deg, #3b9eff 0%, #2f9fb0 55%, #fb5fa0 100%); +} +.theme-swatch[data-theme='ember'], +.theme-option[data-theme='ember'] .theme-option-swatch { + background: linear-gradient(135deg, #c2410c 0%, #a16207 55%, #4f7d6e 100%); +} +.theme-swatch[data-theme='orchid'], +.theme-option[data-theme='orchid'] .theme-option-swatch { + background: linear-gradient(135deg, #d946ef 0%, #7c7cf0 55%, #22cec0 100%); +} +.theme-swatch[data-theme='verdant'], +.theme-option[data-theme='verdant'] .theme-option-swatch { + background: linear-gradient(135deg, #10b981 0%, #2c7a9e 55%, #d08700 100%); +} diff --git a/examples/workflow-standalone/css/tool-palette.css b/examples/workflow-standalone/css/tool-palette.css new file mode 100644 index 00000000..f2a34882 --- /dev/null +++ b/examples/workflow-standalone/css/tool-palette.css @@ -0,0 +1,204 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/* Tool palette re-skin. */ + +.tool-palette { + font-family: var(--font-body); + background: transparent; + backdrop-filter: none; + -webkit-backdrop-filter: none; + border: none; + box-shadow: none; + color: var(--text-1); + font-size: 13px; + right: 40px; + top: 32px; +} + +.tool-palette.collapsible-palette { + overflow-x: visible; + overflow-y: clip; +} + +.palette-header { + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + border: 1px solid var(--overlay-border); + border-bottom: 1px solid var(--hairline); + border-radius: 12px 12px 0 0; + padding: 8px 10px; +} + +.header-icon { + color: var(--text-2); + font-weight: 600; + font-size: 12px; +} + +.header-tools { + gap: 2px; +} + +.header-tools i { + border: 1px solid transparent; + border-radius: 6px; + padding: 5px; + margin-right: 1px; + color: var(--text-2); +} + +.header-tools i:hover { + background: var(--surface-hover); + color: var(--text-1); +} + +.header-tools .clicked { + background: var(--accent-soft); + border: 1px solid transparent; + color: var(--accent); +} + +.palette-body { + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + border: 1px solid var(--overlay-border); + border-top: none; + border-radius: 0 0 12px 12px; + box-shadow: var(--overlay-shadow); + padding: 4px 0; +} + +.tool-group { + background: transparent; +} + +.group-header { + background: var(--surface-sunken); + color: var(--text-2); + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + padding: 6px 12px; + border-bottom: 1px solid var(--hairline); + margin-top: 2px; +} + +.group-header:hover { + background: var(--surface-hover); +} + +.group-header i { + padding: 0.2em; + color: var(--text-2); + font-size: 12px; +} + +.tool-button { + background: transparent; + padding: 6px 12px 6px 24px; + border-radius: 6px; + margin: 1px 4px; + color: var(--text-1); + font-size: 13px; + position: relative; +} + +.tool-button i { + color: var(--text-2); + margin-right: 8px; + display: inline-flex; + align-items: center; +} + +.tool-button:hover { + background: var(--surface-hover); +} + +.tool-button.clicked { + background: var(--accent-soft); + color: var(--accent); +} + +.tool-button.clicked i { + color: var(--accent); +} + +.search-input { + background: var(--surface-sunken); + border: 1px solid var(--overlay-border); + border-radius: 6px; + padding: 6px 8px; + font-family: inherit; + color: var(--text-1); + font-size: 13px; +} + +.search-input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 2px var(--accent-glow); +} + +.minimize-palette-button .codicon::before { + font-size: 18px; +} + +.accessibility-tool-palette .tool-button .key-shortcut, +.accessibility-tool-palette .header-tools .key-shortcut { + background: var(--surface-solid); + border: 1.5px solid var(--overlay-border); + color: var(--text-2); + font-family: var(--font-mono); + font-weight: 600; + font-size: 0.7rem; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06); +} + +.accessibility-show-shortcuts:focus-within .header-tools .key-shortcut, +.accessibility-show-shortcuts:focus-within .tool-button .key-shortcut { + display: flex; + align-items: center; + justify-content: center; +} + +.minimize-palette-button { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 8px; + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + border: 1px solid var(--overlay-border); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); + color: var(--text-2); + cursor: pointer; +} + +.minimize-palette-button:hover { + color: var(--text-1); + background: var(--surface-hover); +} + +.minimize-palette-button .codicon::before { + font-size: 16px; +} diff --git a/examples/workflow-standalone/esbuild.mjs b/examples/workflow-standalone/esbuild.mjs new file mode 100644 index 00000000..25e250c4 --- /dev/null +++ b/examples/workflow-standalone/esbuild.mjs @@ -0,0 +1,113 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { spawn } from 'child_process'; +import esbuild from 'esbuild'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const appRoot = path.resolve(__dirname, 'app'); +const serverDir = path.resolve(__dirname, 'server'); + +const args = process.argv.slice(2); +const isBrowser = args.includes('--browser'); +const isWatch = args.includes('--watch'); // dev: rebuild + live-reload +const isServe = args.includes('--serve'); // start: serve the built bundle, no watch/live-reload +const isMcp = args.includes('--mcp'); +const noOpen = args.includes('--no-open'); + +// full-page live reload: subscribe to esbuild's change stream (served on the dev port). Over file:// +// there is no EventSource endpoint, so the guard turns this into a harmless no-op for production builds. +const liveReloadBanner = { + js: ";(() => { if (typeof EventSource !== 'undefined') { new EventSource('/esbuild').addEventListener('change', () => location.reload()); } })();" +}; + +// replaces CopyWebpackPlugin: the browser entry loads the worker via the stable 'wf-glsp-server-webworker.js' name +function copyWebWorker() { + const source = path.resolve(serverDir, 'wf-glsp-server-web.js'); + const target = path.resolve(appRoot, 'wf-glsp-server-webworker.js'); + fs.copyFileSync(source, target); + if (fs.existsSync(source + '.map')) { + fs.copyFileSync(source + '.map', target + '.map'); + } +} + +// mirror webpack's DefinePlugin; only injected for the node/websocket entry +const nodeDefine = { + GLSP_SERVER_HOST: JSON.stringify(process.env.GLSP_SERVER_HOST || 'localhost'), + GLSP_SERVER_PORT: JSON.stringify(process.env.GLSP_SERVER_PORT || '8081'), + GLSP_MCP_SERVER_PORT: JSON.stringify(process.env.GLSP_MCP_SERVER_PORT || '64577'), + GLSP_SOURCE_URI: JSON.stringify(path.resolve(appRoot, 'example1.wf')) +}; + +/** @type {import('esbuild').BuildOptions} */ +const buildOptions = { + entryPoints: [path.resolve(__dirname, 'src', isBrowser ? 'browser/app.ts' : 'node/app.ts')], + outdir: appRoot, + entryNames: 'bundle', // -> app/bundle.js + app/bundle.css + assetNames: '[name]-[hash]', // -> app/codicon-.ttf, referenced relatively from bundle.css + bundle: true, + sourcemap: true, + format: 'iife', // diagram.html loads bundle.js via a classic