diff --git a/examples/browser-app.ts b/examples/browser-app.ts index ee6a51e..854727a 100644 --- a/examples/browser-app.ts +++ b/examples/browser-app.ts @@ -23,6 +23,7 @@ import runRandomGraphDistributed from "./random-graph-distributed/src/standalone import runSvgPreRendered from "./svg/src/standalone"; import runMulticore from "./multicore/src/multicore"; import runFlowchart from "./flowchart/src/standalone"; +import runJsxample from "./jsxample/src/standalone"; const appDiv = document.getElementById('sprotty-app'); if (appDiv) { @@ -41,6 +42,8 @@ if (appDiv) { runMulticore(); else if (appMode === 'flowchart') runFlowchart(); + else if (appMode === 'jsxample') + runJsxample(); else throw new Error('Dunno what to do :-('); } diff --git a/examples/index.html b/examples/index.html index 0a418e4..f427c62 100644 --- a/examples/index.html +++ b/examples/index.html @@ -30,6 +30,8 @@

Without Server

If you zoom in, the communication channels between the cores appear.
  • Flowchart:
    A flowchart with custom views for nodes and edges. The labels on nodes and edges are editable and the nodes and edges are moveable.
  • +
  • JSXample:
    + A demonstration how to use React alike components in Sprotty views.
  • With Server

    diff --git a/examples/jsxample/css/diagram.css b/examples/jsxample/css/diagram.css new file mode 100644 index 0000000..d5bc402 --- /dev/null +++ b/examples/jsxample/css/diagram.css @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2024 TypeFox 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 + ********************************************************************************/ + + :root { + --black: #000; + --white: #fff; + --stroke-normal: 2; + --stroke-medium: 3; + --stroke-bold: 5; + --red-500: #f44336; + --yellow-500: #ffeb3b; + --amber-500: #ffc107; +} + +/* graph */ +.sprotty-graph { + font-size: 15pt; +} + +/* nodes */ +.sprotty-node { + stroke: var(--black); + stroke-width: var(--stroke-normal); + fill: var(--white); +} \ No newline at end of file diff --git a/examples/jsxample/css/page.css b/examples/jsxample/css/page.css new file mode 100644 index 0000000..6e242fa --- /dev/null +++ b/examples/jsxample/css/page.css @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2024 TypeFox 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 + ********************************************************************************/ + + + .footer { + margin-top: 10px; + text-align: right; + font-size: 10px; + color: #888; + + display: flex; + justify-content: space-between; +} + +.shortcuts { + text-align: left; +} + +.copyright { + text-align: right; +} + +.help { + margin-top: 24px; + text-align: right; + font-size: 16px; + color: #888; +} + +svg { + margin-top: 15px; + width: 100%; + height: 600px; + border-style: solid; + border-width: 1px; + border-color: #bbb; +} diff --git a/examples/jsxample/jsxample.html b/examples/jsxample/jsxample.html new file mode 100644 index 0000000..b4d605c --- /dev/null +++ b/examples/jsxample/jsxample.html @@ -0,0 +1,57 @@ + + + + + + Sprotty JSXample + + + + + + + + + +
    +
    +
    +

    Sprotty JSXample

    +
    +
    + Help +
    +
    +
    +
    +
    +
    + +
    +
    + + + \ No newline at end of file diff --git a/examples/jsxample/src/di.config.ts b/examples/jsxample/src/di.config.ts new file mode 100644 index 0000000..b1944a6 --- /dev/null +++ b/examples/jsxample/src/di.config.ts @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2024 TypeFox 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 { Container, ContainerModule } from "inversify"; +import { + ConsoleLogger, + LocalModelSource, + LogLevel, + SGraphImpl, + SGraphView, + SLabelImpl, + SLabelView, + SNodeImpl, + TYPES, + configureModelElement, + configureViewerOptions, + loadDefaultModules} from "sprotty"; +import { ExampleNodeView } from "./views"; + +export default (containerId: string) => { + require('../css/diagram.css'); + + const jsxampleModule = new ContainerModule((bind, unbind, isBound, rebind) => { + bind(TYPES.ModelSource).to(LocalModelSource).inSingletonScope(); + rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); + rebind(TYPES.LogLevel).toConstantValue(LogLevel.log); + + const context = { bind, unbind, isBound, rebind }; + + configureModelElement(context, 'graph', SGraphImpl, SGraphView); + configureModelElement(context, 'node', SNodeImpl, ExampleNodeView); + configureModelElement(context, 'label', SLabelImpl, SLabelView); + + configureViewerOptions(context, { + needsClientLayout: true, + }); + }); + + const container = new Container(); + loadDefaultModules(container); + container.load(jsxampleModule); + return container; +}; diff --git a/examples/jsxample/src/standalone.ts b/examples/jsxample/src/standalone.ts new file mode 100644 index 0000000..66300fc --- /dev/null +++ b/examples/jsxample/src/standalone.ts @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2024 TypeFox 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 { LocalModelSource, TYPES } from 'sprotty'; +import createContainer from './di.config'; + +export default async function runJsxample() { + const container = createContainer('sprotty'); + const modelSource = container.get(TYPES.ModelSource); + + const graph = { + id: 'root', + type: 'graph', + children: [ + { + id: 'node1', + type: 'node', + position: { x: 100, y: 100 }, + layout: 'stack', + layoutOptions: { + hAlign: 'center', + vAlign: 'center', + paddingTop: 10, + paddingBottom: 10, + paddingLeft: 10, + paddingRight: 10, + minWidth: 300, + minHeight: 100 + }, + children: [ + { + id: 'label1', + type: 'label', + text: 'View enhanced with JSX function components.', + position: { x: 150, y: 50 } + } + ] + } + ] + }; + modelSource.setModel(graph); +} diff --git a/examples/jsxample/src/views.tsx b/examples/jsxample/src/views.tsx new file mode 100644 index 0000000..0de3126 --- /dev/null +++ b/examples/jsxample/src/views.tsx @@ -0,0 +1,69 @@ +/******************************************************************************** + * Copyright (c) 2024 TypeFox 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 + ********************************************************************************/ +/** @jsx svg */ +import { SLabelImpl, svg } from 'sprotty'; + +import { injectable } from "inversify"; +import { VNode } from "snabbdom"; +import { IViewArgs, RenderingContext, SShapeElementImpl, ShapeView } from "sprotty"; + +@injectable() +export class ExampleNodeView extends ShapeView { + override render(node: Readonly, context: RenderingContext, args?: IViewArgs): VNode | undefined { + if (!this.isVisible(node, context)) { + return undefined; + } + + return + + {SVGRectComponent({ + colour: '#999', + position: { x: 0, y: node.size.height - 20 }, size: { width: node.size.width, height: 20 } + })} + + {context.renderChildren(node)} + ; + } +} + +export class ExampleLabelView extends ShapeView { + override render(node: Readonly, context: RenderingContext, args?: IViewArgs): VNode | undefined { + return {node.text}; + } +} + +interface TestProps { + name?: string; + colour: string; + position: { x: number, y: number }; + size?: { width: number, height: number }; +} + +function SVGRectComponent(props: TestProps) { + return + + {props.name ? {props.name} : ''} + ; +} diff --git a/packages/sprotty/src/lib/jsx.ts b/packages/sprotty/src/lib/jsx.ts index a374be2..6bfb373 100644 --- a/packages/sprotty/src/lib/jsx.ts +++ b/packages/sprotty/src/lib/jsx.ts @@ -48,9 +48,14 @@ function normalizeAttrs(source: VNodeData | null, defNS: string, namespace?: str Object.keys(source).forEach(key => { if (key === 'key' || key === 'classNames' || key === 'selector') return; const idx = key.indexOf('-'); - if (idx > 0) - addAttr(key.slice(0, idx), key.slice(idx + 1), source[key]); - else if (!data[key]) + if (idx > 0) { + const modname = key.slice(0, idx); + if (modulesNS.includes(modname)) { + addAttr(modname, key.slice(idx + 1), source[key]); + } else { + addAttr(defNS, key, source[key]); + } + } else if (!data[key]) addAttr(defNS, key, source[key]); }); return data; @@ -63,7 +68,10 @@ function normalizeAttrs(source: VNodeData | null, defNS: string, namespace?: str // eslint-disable-next-line @typescript-eslint/naming-convention function JSX(namespace?: string, defNS: string = 'props') { - return (tag: FunctionComponent | string, attrs: VNodeData | null, ...children: JsxVNodeChild[]) => jsx(tag, normalizeAttrs(attrs, defNS, namespace), children); + return (tag: FunctionComponent | string, attrs: VNodeData | null, ...children: JsxVNodeChild[]) => { + const isComponent = typeof tag === 'function'; + return jsx(tag, (isComponent ? attrs : normalizeAttrs(attrs, defNS, namespace)), children); + }; } const html = JSX();