diff --git a/applications/klighd-cli/package.json b/applications/klighd-cli/package.json index d4f65a73..9bc971db 100644 --- a/applications/klighd-cli/package.json +++ b/applications/klighd-cli/package.json @@ -45,7 +45,7 @@ "pino-pretty": "^4.7.1", "reflect-metadata": "^0.2.1", "setimmediate": "^1.0.5", - "sprotty-protocol": "^1.1.0", + "sprotty-protocol": "^1.3.0", "stream-browserify": "^3.0.0", "vscode-languageserver-protocol": "^3.17.5", "vscode-ws-jsonrpc": "^0.2.0", diff --git a/applications/klighd-vscode/package.json b/applications/klighd-vscode/package.json index 3d5f8eb5..24686502 100644 --- a/applications/klighd-vscode/package.json +++ b/applications/klighd-vscode/package.json @@ -181,8 +181,8 @@ "inversify": "^6.0.2", "nanoid": "^3.1.23", "reflect-metadata": "^0.2.1", - "sprotty": "^1.1.0", - "sprotty-protocol": "^1.1.0", + "sprotty": "^1.3.0", + "sprotty-protocol": "^1.3.0", "sprotty-vscode": "^1.0.0", "sprotty-vscode-protocol": "^1.0.0", "sprotty-vscode-webview": "^1.0.0", diff --git a/packages/klighd-core/package.json b/packages/klighd-core/package.json index 0fa68b1e..6abb4798 100644 --- a/packages/klighd-core/package.json +++ b/packages/klighd-core/package.json @@ -30,8 +30,8 @@ "file-saver": "^2.0.5", "inversify": "^6.0.2", "snabbdom": "^3.5.1", - "sprotty": "^1.1.0", - "sprotty-protocol": "^1.1.0" + "sprotty": "^1.3.0", + "sprotty-protocol": "^1.3.0" }, "devDependencies": { "@types/chai": "^4.3.11", diff --git a/packages/klighd-core/src/actions/actions.ts b/packages/klighd-core/src/actions/actions.ts index 9a3fc6f9..0ec3ceea 100644 --- a/packages/klighd-core/src/actions/actions.ts +++ b/packages/klighd-core/src/actions/actions.ts @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2019-2022 by + * Copyright 2019-2024 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group @@ -16,8 +16,17 @@ */ // We follow Sprotty's way of redeclaring the interface and its create function, so disable this lint check for this file. /* eslint-disable no-redeclare */ -import { ExportSvgAction, RequestExportSvgAction, SGraphImpl } from 'sprotty' -import { Action, FitToScreenAction, generateRequestId, RequestAction, ResponseAction } from 'sprotty-protocol' +import { SGraphImpl } from 'sprotty' +import { + Action, + ExportSvgAction, + ExportSvgOptions, + FitToScreenAction, + generateRequestId, + RequestAction, + RequestExportSvgAction, + ResponseAction, +} from 'sprotty-protocol' import { SKGraphModelRenderer } from '../skgraph-model-renderer' import { KImage } from '../skgraph-models' @@ -181,10 +190,11 @@ export namespace SendModelContextAction { export type KlighdRequestExportSvgAction = RequestExportSvgAction export namespace KlighdRequestExportSvgAction { - export function create(): RequestAction { + export function create(options?: ExportSvgOptions): KlighdRequestExportSvgAction { return { kind: RequestExportSvgAction.KIND, requestId: generateRequestId(), + options, } } } @@ -198,12 +208,18 @@ export interface KlighdExportSvgAction extends ExportSvgAction { export namespace KlighdExportSvgAction { export const KIND = 'exportSvg' - export function create(svg: string, requestId: string, uri: string): KlighdExportSvgAction { + export function create( + svg: string, + requestId: string, + uri: string, + options?: ExportSvgOptions + ): KlighdExportSvgAction { return { kind: KIND, svg, responseId: requestId, uri, + options, } } } diff --git a/packages/klighd-core/src/feather-icons-snabbdom/feather-icons-snabbdom.tsx b/packages/klighd-core/src/feather-icons-snabbdom/feather-icons-snabbdom.tsx index 1dd821cf..4992eb5b 100644 --- a/packages/klighd-core/src/feather-icons-snabbdom/feather-icons-snabbdom.tsx +++ b/packages/klighd-core/src/feather-icons-snabbdom/feather-icons-snabbdom.tsx @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2021-2023 by + * Copyright 2021-2024 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group @@ -23,13 +23,10 @@ import { html } from 'sprotty' // eslint-disable-line @typescript-eslint/no-unus /** * Add the feather icon with the given icon ID to a snabbdom VNode as used in sprotty. * - * @param paramProps properties containing the ID of the feather icon. + * @param props properties containing the ID of the feather icon. * @returns The SVG VNode resulting from this feather icon ID. */ -export function FeatherIcon(paramProps: { iconId: FeatherIconNames }): VNode { - // Something goes wrong with snabbdom functional components as that the props are nested in an - // addional props property, which is removed here. - const props = (paramProps as any).props as { iconId: FeatherIconNames } +export function FeatherIcon(props: { iconId: FeatherIconNames }): VNode { // Imitates what feather would usually do, all attributes are put in the styles (if possible) and // the classes are written in as well. Missing are the xmlns and viewBox, but they do not seem to // be necessary anyways. diff --git a/packages/klighd-core/src/klighd-svg-exporter.ts b/packages/klighd-core/src/klighd-svg-exporter.ts index 2151522f..ad941be3 100644 --- a/packages/klighd-core/src/klighd-svg-exporter.ts +++ b/packages/klighd-core/src/klighd-svg-exporter.ts @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2021-2023 by + * Copyright 2021-2024 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group @@ -17,59 +17,55 @@ import { injectable } from 'inversify' import { SModelRootImpl, SvgExporter } from 'sprotty' -import { RequestAction } from 'sprotty-protocol' -import { KlighdExportSvgAction } from './actions/actions' -/* global document, Element, HTMLIFrameElement, SVGSVGElement, XMLSerializer */ +import { KlighdExportSvgAction, KlighdRequestExportSvgAction } from './actions/actions' +/* global document, Document, Element */ @injectable() export class KlighdSvgExporter extends SvgExporter { - export(root: SModelRootImpl, request?: RequestAction): void { + export(root: SModelRootImpl, request?: KlighdRequestExportSvgAction): void { + // Same as Sprotty's SvgExporter.export, but with KlighdExportSvgAction instead of + // ExportSvgAction to have a better default export name based on the root ID (its model URI). if (typeof document !== 'undefined') { - const div = document.getElementById(this.options.hiddenDiv) - if (div !== null && div.firstElementChild && div.firstElementChild.tagName === 'svg') { - const svgElement = div.firstElementChild as SVGSVGElement - const svg = this.createSvg(svgElement, root) - this.actionDispatcher.dispatch( - KlighdExportSvgAction.create(svg, request ? request.requestId : '', root.id) - ) + const hiddenDiv = document.getElementById(this.options.hiddenDiv) + if (hiddenDiv === null) { + this.log.warn(this, `Element with id ${this.options.hiddenDiv} not found. Nothing to export.`) + return } + + const svgElement = hiddenDiv.querySelector('svg') + if (svgElement === null) { + this.log.warn(this, `No svg element found in ${this.options.hiddenDiv} div. Nothing to export.`) + return + } + const svg = this.createSvg(svgElement, root, request?.options ?? {}, request) + this.actionDispatcher.dispatch( + KlighdExportSvgAction.create(svg, request ? request.requestId : '', root.id, request?.options) + ) } } - protected createSvg(svgElementOrig: SVGSVGElement, root: SModelRootImpl): string { - const serializer = new XMLSerializer() - const svgCopy = serializer.serializeToString(svgElementOrig) - const iframe: HTMLIFrameElement = document.createElement('iframe') - document.body.appendChild(iframe) - if (!iframe.contentWindow) throw new Error('IFrame has no contentWindow') - const docCopy = iframe.contentWindow.document - docCopy.open() - docCopy.write(svgCopy) - docCopy.close() - const svgElementNew = docCopy.getElementById(svgElementOrig.id)! - this.copyStyles(svgElementOrig, svgElementNew, []) - svgElementNew.setAttribute('version', '1.1') - // Somehow this is always 1. - svgElementNew.setAttribute('opacity', '1') - const bounds = this.getBounds(root) - svgElementNew.setAttribute('viewBox', `${bounds.x} ${bounds.y} ${bounds.width} ${bounds.height}`) - const svgCode = serializer.serializeToString(svgElementNew) - document.body.removeChild(iframe) - return svgCode + protected copyStyles(_source: Element, _target: Element, _skippedProperties: string[]): void { + // Just don't copy the styles. This would overwrite any styles set by the SVG renderer and we do not need any other styles that may get copied here. + // So overwrite Sprotty's copyStyles method with an empty method. } - protected copyStyles(source: Element, target: Element, skipedProperties: string[]): void { - source.getAttributeNames().forEach((key) => { - if (!skipedProperties.includes(key)) { - const value = source.getAttribute(key) - if (value) target.setAttribute(key, value) - } - }) - // IE doesn't retrun anything on source.children - for (let i = 0; i < source.childNodes.length; ++i) { - const sourceChild = source.childNodes[i] - const targetChild = target.childNodes[i] - if (sourceChild instanceof Element) this.copyStyles(sourceChild, targetChild as Element, []) + protected getBounds(root: SModelRootImpl, document: Document) { + const svgElement = document.querySelector('svg') + if (svgElement) { + // Get the actual bounding box of the SVG element, including the stroke width. + // should use { stroke: true } argument here, but it's not supported in chromium. + const box = svgElement.getBBox() + // Instead, remove the x/y offset and assume that the diagram is at 0/0 and that the offset on the other side is the same. + const xOffset = box.x + const yOffset = box.y + box.x = 0 + box.y = 0 + box.width += xOffset * 2 + box.height += yOffset * 2 + + return box } + + return super.getBounds(root, document) } } diff --git a/packages/klighd-core/src/options/components/option-inputs.tsx b/packages/klighd-core/src/options/components/option-inputs.tsx index 54ea9c43..8491e6fa 100644 --- a/packages/klighd-core/src/options/components/option-inputs.tsx +++ b/packages/klighd-core/src/options/components/option-inputs.tsx @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2021-2022 by + * Copyright 2021-2024 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group @@ -34,8 +34,6 @@ type CheckOptionProps = BaseProps /** Render a labeled checkbox input. */ export function CheckOption(props: CheckOptionProps): VNode { - // The sprotty jsx function always puts an additional 'props' key around the element, requiring this hack. - props = (props as any as { props: CheckOptionProps }).props return (