Skip to content

Commit

Permalink
Improve GLSP UI Extension mechanism for easier re-use of HTML elements
Browse files Browse the repository at this point in the history
- Allow more fine-grained definition of container and parent
- Allow more fine-grained
- Replace hard-coded styles with 'hidden' class
- Rework index file to define structure and use grid for layouting
- Remove workaround for toolbar height (48px adjustment)
- Ensure Quick Action UI is still rendering correctly
  • Loading branch information
martin-fleck-at committed Mar 20, 2024
1 parent 4393495 commit 4af96b4
Show file tree
Hide file tree
Showing 15 changed files with 151 additions and 81 deletions.
11 changes: 7 additions & 4 deletions integration/standalone/index.html
Expand Up @@ -10,9 +10,12 @@
</head>

<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="sprotty" class="main-widget"></div>
<div id="inscription"></div>
<script type="module" src="/src/index.ts"></script>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="process-editor">
<header id="ivy-tool-bar"></header>
<main id="sprotty" class="main-widget"></main>
<aside id="inscription-ui"></aside>
</div>
<script type="module" src="/src/index.ts"></script>
</body>
</html>
29 changes: 15 additions & 14 deletions integration/standalone/src/index.css
Expand Up @@ -5,26 +5,27 @@ body {
overflow: hidden;
}

body {
#process-editor {
display: grid;
grid-template-areas: 'sprotty inscription';
grid-auto-columns: minmax(250px, 100%) min-content;
grid-template-columns: 1fr auto; /* Adjust the auto value based on sidebar width */
grid-template-rows: auto 1fr; /* Adjust based on toolbar height */
height: 100vh; /* Full viewport height */
}

#sprotty {
grid-area: sprotty;
height: 100vh;
}
#inscription {
grid-area: inscription;
background-color: var(--glsp-editor-background);
height: 100vh;
grid-column: 1 / 2;
grid-row: 2 / 3;
}
#inscription.hidden {
display: none;

#ivy-tool-bar {
grid-column: 1 / 2;
grid-row: 1 / 2;
}

.main-widget {
position: relative;
#inscription-ui {
grid-column: 2 / 3;
grid-row: 1 / 3; /* Spanning across both the rows */
background-color: var(--glsp-editor-background);
}

.sprotty svg {
Expand Down
5 changes: 0 additions & 5 deletions packages/editor/src/diagram/di.config.ts
Expand Up @@ -5,7 +5,6 @@ import {
CustomFeatures,
DeleteElementContextMenuItemProvider,
FeatureModule,
GLSPProjectionView,
GModelElement,
IView,
LogLevel,
Expand All @@ -17,7 +16,6 @@ import {
moveFeature,
selectFeature
} from '@eclipse-glsp/client';
import { DefaultTypes } from '@eclipse-glsp/protocol';
import { interfaces } from 'inversify';

import { ShowGridAction } from '@axonivy/process-editor-protocol';
Expand All @@ -44,7 +42,6 @@ import {
EndEventNode,
EventNode,
GatewayNode,
IvyGGraph,
LaneNode,
MulitlineEditLabel,
RotateLabel,
Expand Down Expand Up @@ -78,8 +75,6 @@ const ivyDiagramModule = new FeatureModule((bind, unbind, isBound, rebind) => {

const context = { bind, unbind, isBound, rebind };

configureIvyModelElement(DefaultTypes.GRAPH, IvyGGraph, GLSPProjectionView);

configureStartEvent(EventStartTypes.START);
configureStartEvent(EventStartTypes.START_ERROR);
configureStartEvent(EventStartTypes.START_SIGNAL);
Expand Down
10 changes: 9 additions & 1 deletion packages/editor/src/diagram/diagram.css
Expand Up @@ -9,7 +9,8 @@ body {
.sprotty {
user-select: none;
}
.sprotty > div:focus-visible {
.sprotty > div:focus-visible,
.sprotty-graph:focus {
outline: none;
}
.sprotty svg:where(.sprotty-graph, .sprotty-empty) {
Expand Down Expand Up @@ -306,3 +307,10 @@ g[class^='end'] .sprotty-node {
svg {
border: none;
}

/* UI Extension */
.ui-extension.hidden {
display: none;
visibility: hidden;
opacity: 0;
}
5 changes: 0 additions & 5 deletions packages/editor/src/diagram/model.ts
Expand Up @@ -12,7 +12,6 @@ import {
editFeature,
editLabelFeature,
fadeFeature,
GGraph,
hoverFeedbackFeature,
isBoundsAware,
isEditableLabel,
Expand Down Expand Up @@ -47,10 +46,6 @@ import { WithCustomIcon } from './icon/model';
import { ActivityTypes, EdgeTypes, LabelType, LaneTypes } from './view-types';
import { multipleOutgoingEdgesFeature } from '../ui-tools/quick-action/edge/model';

export class IvyGGraph extends GGraph {
scroll = { x: 0, y: -48 };
}

export class LaneNode extends RectangularNode implements WithEditableLabel, ArgsAware {
static readonly DEFAULT_FEATURES = [
boundsFeature,
Expand Down
1 change: 1 addition & 0 deletions packages/editor/src/index.ts
Expand Up @@ -28,5 +28,6 @@ export * from './ui-tools/tool-bar/button';
export * from './ui-tools/tool-bar/tool-bar';
export * from './ui-tools/viewport/viewport-commands';
export * from './ui-tools/viewport/viewport-bar';
export * from './utils/ivy-ui-extension';

export * from './ivy-glsp-jsonrpc-client';
10 changes: 5 additions & 5 deletions packages/editor/src/jump/jump-out-ui.ts
@@ -1,5 +1,4 @@
import {
AbstractUIExtension,
Action,
CommandExecutionContext,
CommandReturn,
Expand All @@ -16,26 +15,27 @@ import { inject, injectable, postConstruct } from 'inversify';
import { IvyIcons } from '@axonivy/ui-icons';
import { createElement, createIcon } from '../utils/ui-utils';
import { JumpAction } from '@axonivy/process-editor-protocol';
import { IvyUIExtension } from '../utils/ivy-ui-extension';

@injectable()
export class JumpOutUi extends AbstractUIExtension {
export class JumpOutUi extends IvyUIExtension {
static readonly ID = 'jumpOutUi';

@inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: IActionDispatcher;
@inject(TYPES.IFeedbackActionDispatcher) protected readonly feedbackDispatcher: IFeedbackActionDispatcher;
@inject(SelectionService) protected selectionService: SelectionService;
@inject(EditorContextService) protected readonly editorContext: EditorContextService;

public id(): string {
id(): string {
return JumpOutUi.ID;
}

public containerClass(): string {
containerClass(): string {
return 'jump-out-container';
}

@postConstruct()
postConstruct(): void {
protected postConstruct(): void {
this.feedbackDispatcher.registerFeedback(this, [JumpOutFeedbackAction.create()]);
}

Expand Down
12 changes: 5 additions & 7 deletions packages/editor/src/ui-tools/quick-action/quick-action-ui.ts
@@ -1,5 +1,4 @@
import {
AbstractUIExtension,
Action,
Bounds,
BoundsAware,
Expand Down Expand Up @@ -33,9 +32,10 @@ import { Menu } from '../menu/menu';
import { isQuickActionAware } from './model';
import { QuickAction, QuickActionLocation, QuickActionProvider } from './quick-action';
import { InfoQuickActionMenu, QuickActionMenu, ShowInfoQuickActionMenuAction, ShowQuickActionMenuAction } from './quick-action-menu-ui';
import { IvyUIExtension } from '../../utils/ivy-ui-extension';

@injectable()
export class QuickActionUI extends AbstractUIExtension implements IActionHandler, ISelectionListener {
export class QuickActionUI extends IvyUIExtension implements IActionHandler, ISelectionListener {
static readonly ID = 'quickActionsUi';
private activeQuickActions: QuickAction[] = [];
private activeQuickActionBtn?: HTMLElement;
Expand Down Expand Up @@ -145,10 +145,6 @@ export class QuickActionUI extends AbstractUIExtension implements IActionHandler
);
}

hide(): void {
super.hide();
}

protected onBeforeShow(containerElement: HTMLElement, root: Readonly<GModelRoot>, ...contextElementIds: string[]): void {
containerElement.innerHTML = '';
const elements = getElements(contextElementIds, root);
Expand Down Expand Up @@ -214,8 +210,10 @@ export class QuickActionUI extends AbstractUIExtension implements IActionHandler
}

private createQuickActionsBar(containerElement: HTMLElement, parentBounds: Bounds, drawSelectionBox = false): HTMLElement {
const diagramContainer = document.getElementById(this.diagramContainerId);
const offset = diagramContainer?.offsetTop ?? 0;
containerElement.style.left = `${parentBounds.x + parentBounds.width / 2}px`;
containerElement.style.top = `${parentBounds.y + parentBounds.height}px`;
containerElement.style.top = `${offset + parentBounds.y + parentBounds.height}px`;
if (drawSelectionBox) {
const selectionDiv = createElement('div', ['multi-selection-box']);
selectionDiv.style.marginLeft = `-${parentBounds.width / 2}px`;
Expand Down
2 changes: 0 additions & 2 deletions packages/editor/src/ui-tools/tool-bar/tool-bar.css
@@ -1,12 +1,10 @@
.ivy-tool-bar {
position: absolute;
inset: 0;
width: 100%;
height: 48px;
background: var(--glsp-tool-bar-bg);
box-shadow: var(--glsp-box-shadow);
z-index: 10;
container: toolbar / inline-size;
}
.tool-bar-header {
margin: 6px;
Expand Down
4 changes: 2 additions & 2 deletions packages/editor/src/ui-tools/tool-bar/tool-bar.ts
@@ -1,5 +1,4 @@
import {
AbstractUIExtension,
Action,
DisposableCollection,
EditorContextService,
Expand Down Expand Up @@ -37,11 +36,12 @@ import {
import { ShowToolBarOptionsMenuAction } from './options/action';
import { ToolBarOptionsMenu } from './options/options-menu-ui';
import { ShowToolBarMenuAction, ToolBarMenu } from './tool-bar-menu';
import { IvyUIExtension } from '../../utils/ivy-ui-extension';

const CLICKED_CSS_CLASS = 'clicked';

@injectable()
export class ToolBar extends AbstractUIExtension implements IActionHandler, IEditModeListener, ISelectionListener {
export class ToolBar extends IvyUIExtension implements IActionHandler, IEditModeListener, ISelectionListener {
static readonly ID = 'ivy-tool-bar';

@inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher;
Expand Down
9 changes: 5 additions & 4 deletions packages/editor/src/ui-tools/viewport/viewport-bar.ts
@@ -1,6 +1,5 @@
import {
Action,
AbstractUIExtension,
EditorContextService,
GLSPActionDispatcher,
IActionHandler,
Expand All @@ -9,17 +8,19 @@ import {
IToolManager,
SetUIExtensionVisibilityAction,
SetViewportAction,
TYPES
, SelectionService } from '@eclipse-glsp/client';
TYPES,
SelectionService
} from '@eclipse-glsp/client';
import { inject, injectable } from 'inversify';
import { CenterButton, FitToScreenButton, OriginScreenButton, ViewportBarButton } from './button';

import { createElement, createIcon } from '../../utils/ui-utils';
import { QuickActionUI } from '../quick-action/quick-action-ui';
import { EnableViewportAction, SetViewportZoomAction } from '@axonivy/process-editor-protocol';
import { IvyUIExtension } from '../../utils/ivy-ui-extension';

@injectable()
export class ViewportBar extends AbstractUIExtension implements IActionHandler {
export class ViewportBar extends IvyUIExtension implements IActionHandler {
static readonly ID = 'ivy-viewport-bar';

@inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher;
Expand Down
2 changes: 1 addition & 1 deletion packages/editor/src/ui-tools/viewport/viewport-commands.ts
Expand Up @@ -53,7 +53,7 @@ export class OriginViewportCommand extends BoundsAwareViewportCommand {
}

getNewViewport(_bounds: Bounds, _model: GModelRoot): Viewport | undefined {
return { zoom: 1, scroll: { x: 0, y: -48 } };
return { zoom: 1, scroll: { x: 0, y: 0 } };
}
}

Expand Down
94 changes: 94 additions & 0 deletions packages/editor/src/utils/ivy-ui-extension.ts
@@ -0,0 +1,94 @@
import { AbstractUIExtension } from '@eclipse-glsp/client';
import { injectable } from 'inversify';

@injectable()
export abstract class IvyUIExtension extends AbstractUIExtension {
static UI_EXTENSION_CLASS = 'ui-extension';

protected get diagramContainerId(): string {
return this.options.baseDiv;
}

protected get parentContainerSelector(): string {
return '#' + this.diagramContainerId;
}

protected get containerSelector(): string {
return '#' + this.id();
}

protected get initialized(): boolean {
return !!this.containerElement;
}

protected initialize(): boolean {
if (this.initialized) {
return true;
}
try {
this.containerElement = this.getOrCreateContainer();
this.initializeContainer(this.containerElement);
this.initializeContents(this.containerElement);
} catch (error) {
const msg = error instanceof Error ? error.message : `Could not retrieve container element for UI extension ${this.id}`;
this.logger.error(this, msg);
return false;
}
return true;
}

protected getOrCreateContainer(): HTMLElement {
if (this.containerElement) {
return this.containerElement;
}
const existingContainer = this.getContainer();
if (existingContainer) {
return existingContainer;
}
const parent = this.getParentContainer();
if (!parent || !parent.isConnected) {
throw new Error(`Could not obtain attached parent for initializing UI extension ${this.id}`);
}
const container = this.createContainer(parent);
this.insertContainerIntoParent(container, parent);
return container;
}

protected getContainer(): HTMLElement | null {
return document.querySelector<HTMLElement>(this.containerSelector);
}

protected createContainer(parent: HTMLElement): HTMLElement {
const container = document.createElement('div');
container.id = parent.id + '_' + this.id();
return container;
}

protected initializeContainer(container: HTMLElement): void {
container.classList.add(IvyUIExtension.UI_EXTENSION_CLASS, this.containerClass());
}

protected getParentContainer(): HTMLElement {
return document.querySelector<HTMLElement>(this.parentContainerSelector)!;
}

protected insertContainerIntoParent(container: HTMLElement, parent: HTMLElement): void {
parent.insertBefore(container, parent.firstChild);
}

protected setContainerVisible(visible: boolean): void {
if (visible) {
this.containerElement?.classList.remove('hidden');
} else {
this.containerElement?.classList.add('hidden');
}
}

protected isContainerVisible(): boolean {
return this.containerElement && !this.containerElement.classList.contains('hidden');
}

protected toggleContainerVisible(): void {
this.setContainerVisible(!this.isContainerVisible());
}
}

0 comments on commit 4af96b4

Please sign in to comment.