graphwise-reactodia 0.0.1-TR19 → 0.0.1-TR20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +40 -0
  2. package/dist/cjs/graphwise-reactodia.cjs.entry.js +192 -43
  3. package/dist/cjs/index.cjs.js +16 -4
  4. package/dist/cjs/node-action-DkJ5ct17.js +96 -0
  5. package/dist/collection/components/graphwise-reactodia/graphwise-reactodia.css +6 -3
  6. package/dist/collection/components/graphwise-reactodia/graphwise-reactodia.js +2 -2
  7. package/dist/collection/components/graphwise-reactodia/menu/export-diagram-action.js +19 -0
  8. package/dist/collection/components/graphwise-reactodia/menu/graphwise-menu.js +19 -35
  9. package/dist/collection/components/graphwise-reactodia/menu/import-diagram-action.js +31 -0
  10. package/dist/collection/components/graphwise-reactodia/models/error/error-code.js +2 -0
  11. package/dist/collection/components/graphwise-reactodia/models/sparql/sparql-query-function.js +1 -0
  12. package/dist/collection/components/graphwise-reactodia/models/sparql/sparql-query-params.js +1 -0
  13. package/dist/collection/components/graphwise-reactodia/models/ui/menu-config.js +1 -0
  14. package/dist/collection/components/graphwise-reactodia/models/ui/menu-item.js +18 -0
  15. package/dist/collection/components/graphwise-reactodia/models/ui/node-action.js +22 -0
  16. package/dist/collection/components/graphwise-reactodia/models/ui/resolved-toolbar-position.js +1 -0
  17. package/dist/collection/components/graphwise-reactodia/models/ui/resolved-ui-config.js +1 -0
  18. package/dist/collection/components/graphwise-reactodia/models/ui/toolbar-dock.js +14 -0
  19. package/dist/collection/components/graphwise-reactodia/models/ui/toolbar-position.js +1 -0
  20. package/dist/collection/components/graphwise-reactodia/models/ui/ui-config.js +1 -0
  21. package/dist/collection/components/graphwise-reactodia/node-action/node-actions.js +22 -0
  22. package/dist/collection/components/graphwise-reactodia/reactodia-app.js +9 -7
  23. package/dist/collection/components/graphwise-reactodia/ui-config/default-ui-config.js +30 -0
  24. package/dist/collection/components/graphwise-reactodia/ui-config/ui-config-mapper.js +51 -0
  25. package/dist/collection/components/graphwise-reactodia/utils/object-util.js +30 -0
  26. package/dist/collection/index.js +3 -0
  27. package/dist/collection/pages/graphwise-reactodia/main.js +14 -0
  28. package/dist/esm/graphwise-reactodia.entry.js +181 -32
  29. package/dist/esm/index.js +1 -1
  30. package/dist/esm/node-action-fwJ3Zupw.js +94 -0
  31. package/dist/graphwise-reactodia/graphwise-reactodia.esm.js +1 -1
  32. package/dist/graphwise-reactodia/index.esm.js +1 -1
  33. package/dist/graphwise-reactodia/p-c3d8a69a.entry.js +1 -0
  34. package/dist/graphwise-reactodia/p-fwJ3Zupw.js +1 -0
  35. package/dist/types/components/graphwise-reactodia/graphwise-reactodia.d.ts +3 -3
  36. package/dist/types/components/graphwise-reactodia/menu/export-diagram-action.d.ts +4 -0
  37. package/dist/types/components/graphwise-reactodia/menu/graphwise-menu.d.ts +4 -7
  38. package/dist/types/components/graphwise-reactodia/menu/import-diagram-action.d.ts +4 -0
  39. package/dist/types/components/graphwise-reactodia/models/error/error-code.d.ts +3 -1
  40. package/dist/types/components/graphwise-reactodia/models/reactodia-config.d.ts +6 -1
  41. package/dist/types/components/graphwise-reactodia/models/sparql/sparql-query-function.d.ts +6 -0
  42. package/dist/types/components/graphwise-reactodia/models/sparql/sparql-query-params.d.ts +12 -0
  43. package/dist/types/components/graphwise-reactodia/models/ui/menu-config.d.ts +8 -0
  44. package/dist/types/components/graphwise-reactodia/models/ui/menu-item.d.ts +17 -0
  45. package/dist/types/components/graphwise-reactodia/models/ui/node-action.d.ts +21 -0
  46. package/dist/types/components/graphwise-reactodia/models/ui/resolved-toolbar-position.d.ts +8 -0
  47. package/dist/types/components/graphwise-reactodia/models/ui/resolved-ui-config.d.ts +12 -0
  48. package/dist/types/components/graphwise-reactodia/models/ui/toolbar-dock.d.ts +13 -0
  49. package/dist/types/components/graphwise-reactodia/models/ui/toolbar-position.d.ts +11 -0
  50. package/dist/types/components/graphwise-reactodia/models/ui/ui-config.d.ts +17 -0
  51. package/dist/types/components/graphwise-reactodia/node-action/node-actions.d.ts +6 -0
  52. package/dist/types/components/graphwise-reactodia/ui-config/default-ui-config.d.ts +5 -0
  53. package/dist/types/components/graphwise-reactodia/ui-config/ui-config-mapper.d.ts +14 -0
  54. package/dist/types/components/graphwise-reactodia/utils/object-util.d.ts +15 -0
  55. package/dist/types/components.d.ts +2 -2
  56. package/dist/types/index.d.ts +9 -0
  57. package/package.json +4 -1
  58. package/dist/cjs/error-code-BjU_V9e8.js +0 -37
  59. package/dist/esm/error-code-HfC639tc.js +0 -35
  60. package/dist/graphwise-reactodia/p-HfC639tc.js +0 -1
  61. package/dist/graphwise-reactodia/p-bf7f09d2.entry.js +0 -1
package/README.md CHANGED
@@ -66,6 +66,45 @@ persisted in the DB, so they can't be fetched lazily and must be supplied by the
66
66
 
67
67
  When both are supplied `seedGraph` takes precedence over `seedIris`.
68
68
 
69
+ ## UI configuration
70
+
71
+ `config.ui` (`UiConfig`) changes the default UI configuration. It is JSON and read once on mount. It is partial and
72
+ merged over the default: objects are merged key by key, lists replace the default whole.
73
+
74
+ The default UI configuration:
75
+
76
+ ```ts
77
+ {
78
+ mainToolbar: {dock: ToolbarDock.NW},
79
+ actionsToolbar: {dock: ToolbarDock.SW},
80
+ menu: {items: [CLEAR_ALL, EXPORT_PNG, EXPORT_SVG, PRINT, EXPORT_JSON, IMPORT_JSON]},
81
+ nodeActions: [GROUP, REMOVE, EXPAND, OPEN_LINK, CONNECTIONS, ADD_TO_FILTER, ANNOTATE, ESTABLISH_LINK]
82
+ }
83
+ ```
84
+
85
+ | Field | Description |
86
+ |------------------|------------------------------------------------------------------------------------|
87
+ | `mainToolbar` | `ToolbarPosition` of the toolbar with the menu and the search. |
88
+ | `actionsToolbar` | `ToolbarPosition` of the toolbar with undo, redo and layout. |
89
+ | `menu.items` | `MenuItem`s of the main menu, in order. An empty list hides the menu. |
90
+ | `nodeActions` | `NodeAction`s shown around a selected node. |
91
+
92
+ `ToolbarPosition` is `{dock?: ToolbarDock, offsetX?: number, offsetY?: number}`.
93
+
94
+ E.g. the actions toolbar at the top right, without the link node action:
95
+
96
+ ```ts
97
+ element.config = {
98
+ queryFunction,
99
+ ui: {
100
+ actionsToolbar: {dock: ToolbarDock.NE},
101
+ nodeActions: [GROUP, REMOVE, EXPAND, CONNECTIONS, ADD_TO_FILTER, ANNOTATE, ESTABLISH_LINK]
102
+ }
103
+ };
104
+ ```
105
+
106
+ An unknown value is skipped and reported as `unknown-ui-config`; an unknown dock keeps the default toolbar position.
107
+
69
108
  ## Events
70
109
 
71
110
  The element sends every event to the host through one DOM event, `graphwiseReactodiaEvent`. The event
@@ -118,6 +157,7 @@ The element dispatches an `error` event for every error it reports; the codes be
118
157
  | `diagram-import` | The uploaded file was rejected or its import failed. |
119
158
  | `local-diagram-read` | Reading the diagram from local storage failed. |
120
159
  | `local-diagram-save` | Saving the diagram in local storage failed. |
160
+ | `unknown-ui-config` | `config.ui` has an unknown value, which is skipped. |
121
161
 
122
162
  Failed PNG/SVG exports and printing are not reported.
123
163
 
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var index = require('./index-DtzCybH4.js');
4
- var errorCode = require('./error-code-BjU_V9e8.js');
4
+ var nodeAction = require('./node-action-DkJ5ct17.js');
5
5
 
6
6
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
7
7
 
@@ -30927,10 +30927,10 @@ function getContentFittingBoxForLayout(state) {
30927
30927
  }
30928
30928
 
30929
30929
  const graphwise_toolbar_action$1 = {
30930
- "download_diagram.label": "Export to JSON",
30931
- "download_diagram.title": "Export the diagram to a JSON file",
30932
- "upload_diagram.label": "Import from JSON",
30933
- "upload_diagram.title": "Replace the diagram with one from a JSON file"
30930
+ "export_json.label": "Export to JSON",
30931
+ "export_json.title": "Export the diagram to a JSON file",
30932
+ "import_json.label": "Import from JSON",
30933
+ "import_json.title": "Replace the diagram with one from a JSON file"
30934
30934
  };
30935
30935
  var enTranslation = {
30936
30936
  graphwise_toolbar_action: graphwise_toolbar_action$1
@@ -31191,10 +31191,10 @@ const toolbar = {
31191
31191
  "menu_toggle.title": "Ouvrir le menu"
31192
31192
  };
31193
31193
  const graphwise_toolbar_action = {
31194
- "download_diagram.label": "Exporter en JSON",
31195
- "download_diagram.title": "Exporter le diagramme en fichier JSON",
31196
- "upload_diagram.label": "Importer depuis JSON",
31197
- "upload_diagram.title": "Remplacer le diagramme par celui d'un fichier JSON"
31194
+ "export_json.label": "Exporter en JSON",
31195
+ "export_json.title": "Exporter le diagramme en fichier JSON",
31196
+ "import_json.label": "Importer depuis JSON",
31197
+ "import_json.title": "Remplacer le diagramme par celui d'un fichier JSON"
31198
31198
  };
31199
31199
  const toolbar_action = {
31200
31200
  "clear_all.command": "Tout effacer",
@@ -31510,8 +31510,8 @@ let EventService = class EventService {
31510
31510
  */
31511
31511
  subscribeToClearDiagramStorage(onClear) {
31512
31512
  const target = service(WindowService).getWindow();
31513
- target.addEventListener(errorCode.CLEAR_DIAGRAM_STORAGE_EVENT, onClear);
31514
- return () => target.removeEventListener(errorCode.CLEAR_DIAGRAM_STORAGE_EVENT, onClear);
31513
+ target.addEventListener(nodeAction.CLEAR_DIAGRAM_STORAGE_EVENT, onClear);
31514
+ return () => target.removeEventListener(nodeAction.CLEAR_DIAGRAM_STORAGE_EVENT, onClear);
31515
31515
  }
31516
31516
  };
31517
31517
  EventService.ID = 'EventService';
@@ -31562,7 +31562,7 @@ let ErrorService = ErrorService_1 = class ErrorService {
31562
31562
  return;
31563
31563
  }
31564
31564
  causes.forEach((cause) => this.reported.add(cause));
31565
- this.eventService.emit({ type: errorCode.EventType.ERROR, payload: { code, error: thrown } });
31565
+ this.eventService.emit({ type: nodeAction.EventType.ERROR, payload: { code, error: thrown } });
31566
31566
  }
31567
31567
  /**
31568
31568
  * Whether any link of the given cause chain is an abort or is already reported.
@@ -31616,7 +31616,7 @@ let DiagramStorageService = DiagramStorageService_1 = class DiagramStorageServic
31616
31616
  return JSON.parse(raw);
31617
31617
  }
31618
31618
  catch (error) {
31619
- this.errorService.report(errorCode.ErrorCode.LOCAL_DIAGRAM_READ, error);
31619
+ this.errorService.report(nodeAction.ErrorCode.LOCAL_DIAGRAM_READ, error);
31620
31620
  return undefined;
31621
31621
  }
31622
31622
  }
@@ -31628,7 +31628,7 @@ let DiagramStorageService = DiagramStorageService_1 = class DiagramStorageServic
31628
31628
  this.storage.setItem(DiagramStorageService_1.STORAGE_KEY, JSON.stringify(diagram));
31629
31629
  }
31630
31630
  catch (error) {
31631
- this.errorService.report(errorCode.ErrorCode.LOCAL_DIAGRAM_SAVE, error);
31631
+ this.errorService.report(nodeAction.ErrorCode.LOCAL_DIAGRAM_SAVE, error);
31632
31632
  }
31633
31633
  }
31634
31634
  /**
@@ -31740,6 +31740,65 @@ const resetColors = () => {
31740
31740
  typeToIndex.clear();
31741
31741
  };
31742
31742
 
31743
+ /**
31744
+ * Merges an object with default values. Ported from the GraphDB Workbench `ObjectUtil.mergeWithDefaults`.
31745
+ *
31746
+ * For each property in `defaults`, if the value in `object` is `null` or `undefined`, the default value is used instead.
31747
+ * Nested objects are merged recursively. Arrays and other non-plain values are treated as single values and are not
31748
+ * merged.
31749
+ *
31750
+ * The returned object is a new object and does not share references with the inputs. The inputs must be
31751
+ * structured-cloneable.
31752
+ *
31753
+ * @param object Source object whose values take precedence over defaults.
31754
+ * @param defaults Default values applied when properties are missing or null.
31755
+ * @returns A new object containing the merged result.
31756
+ */
31757
+ function mergeWithDefaults(object, defaults) {
31758
+ const result = structuredClone(object);
31759
+ Object.entries(defaults).forEach(([key, defaultValue]) => {
31760
+ const value = result[key];
31761
+ if (value === null || value === undefined) {
31762
+ result[key] = structuredClone(defaultValue);
31763
+ }
31764
+ else if (isPlainObject(value) && isPlainObject(defaultValue)) {
31765
+ result[key] = mergeWithDefaults(value, defaultValue);
31766
+ }
31767
+ });
31768
+ return result;
31769
+ }
31770
+ function isPlainObject(value) {
31771
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
31772
+ }
31773
+
31774
+ /**
31775
+ * The default UI configuration: Reactodia's behaviour, plus the diagram download and upload in the menu.
31776
+ */
31777
+ const DEFAULT_UI_CONFIG = {
31778
+ mainToolbar: { dock: nodeAction.ToolbarDock.NW },
31779
+ actionsToolbar: { dock: nodeAction.ToolbarDock.SW },
31780
+ menu: {
31781
+ items: [
31782
+ nodeAction.MenuItem.CLEAR_ALL,
31783
+ nodeAction.MenuItem.EXPORT_PNG,
31784
+ nodeAction.MenuItem.EXPORT_SVG,
31785
+ nodeAction.MenuItem.PRINT,
31786
+ nodeAction.MenuItem.EXPORT_JSON,
31787
+ nodeAction.MenuItem.IMPORT_JSON
31788
+ ]
31789
+ },
31790
+ nodeActions: [
31791
+ nodeAction.NodeAction.GROUP,
31792
+ nodeAction.NodeAction.REMOVE,
31793
+ nodeAction.NodeAction.EXPAND,
31794
+ nodeAction.NodeAction.OPEN_LINK,
31795
+ nodeAction.NodeAction.CONNECTIONS,
31796
+ nodeAction.NodeAction.ADD_TO_FILTER,
31797
+ nodeAction.NodeAction.ANNOTATE,
31798
+ nodeAction.NodeAction.ESTABLISH_LINK
31799
+ ]
31800
+ };
31801
+
31743
31802
  var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
31744
31803
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
31745
31804
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -31802,37 +31861,125 @@ DiagramFileService = DiagramFileService_1 = __decorate([
31802
31861
  ], DiagramFileService);
31803
31862
 
31804
31863
  /**
31805
- * The workspace main menu: Reactodia's default actions, followed by the diagram download and upload.
31806
- *
31807
- * Passing a `menu` to the `DefaultWorkspace` replaces its default actions, so they are listed here again.
31808
- * Rendered inside the workspace, so the actions can use the workspace hooks.
31864
+ * Exports the diagram as a JSON file.
31809
31865
  */
31810
- function GraphwiseMenu() {
31866
+ function ExportDiagramAction() {
31811
31867
  const { model } = useWorkspace();
31812
31868
  const t = useTranslation();
31813
31869
  const diagramFileService = service(DiagramFileService);
31814
- const diagramService = service(DiagramService);
31815
- const errorService = service(ErrorService);
31816
- const downloadDiagram = () => {
31870
+ const exportDiagram = () => {
31817
31871
  const timestamp = new Date().toISOString().replace(/[-:]|\.\d+Z$/g, '');
31818
31872
  diagramFileService.download(model.exportLayout(), `diagram-${timestamp}${DiagramFileService.FILE_EXTENSION}`);
31819
31873
  };
31820
- const uploadDiagram = async (file) => {
31874
+ return reactExports.createElement(ToolbarAction,
31875
+ // The same icon as ToolbarActionSave. We don't use that action, since it disables saving on refresh
31876
+ { className: 'reactodia-toolbar-action__save', title: t.text('graphwise_toolbar_action.export_json.title'), onSelect: exportDiagram }, t.text('graphwise_toolbar_action.export_json.label'));
31877
+ }
31878
+
31879
+ /**
31880
+ * Imports a diagram from a JSON file into the workspace.
31881
+ */
31882
+ function ImportDiagramAction() {
31883
+ const { model } = useWorkspace();
31884
+ const t = useTranslation();
31885
+ const diagramFileService = service(DiagramFileService);
31886
+ const diagramService = service(DiagramService);
31887
+ const errorService = service(ErrorService);
31888
+ const importDiagram = async (file) => {
31821
31889
  try {
31822
31890
  const diagram = await diagramFileService.read(file);
31823
31891
  await diagramService.importDiagram(model, model.dataProvider, diagram);
31824
31892
  }
31825
31893
  catch (error) {
31826
- errorService.report(errorCode.ErrorCode.DIAGRAM_IMPORT, error);
31894
+ errorService.report(nodeAction.ErrorCode.DIAGRAM_IMPORT, error);
31827
31895
  }
31828
31896
  };
31829
- return reactExports.createElement(reactExports.Fragment, null, reactExports.createElement(ToolbarActionClearAll, {}), reactExports.createElement(ToolbarActionExport, { kind: 'exportRaster' }), reactExports.createElement(ToolbarActionExport, { kind: 'exportSvg' }), reactExports.createElement(ToolbarActionExport, { kind: 'print' }), reactExports.createElement(ToolbarAction,
31830
- // The same icon as ToolbarActionSave. We dont use that action, since it disables saving on refresh
31831
- { className: 'reactodia-toolbar-action__save', title: t.text('graphwise_toolbar_action.download_diagram.title'), onSelect: downloadDiagram }, t.text('graphwise_toolbar_action.download_diagram.label')), reactExports.createElement(ToolbarActionOpen, {
31832
- title: t.text('graphwise_toolbar_action.upload_diagram.title'),
31897
+ return reactExports.createElement(ToolbarActionOpen, {
31898
+ title: t.text('graphwise_toolbar_action.import_json.title'),
31833
31899
  fileAccept: DiagramFileService.FILE_EXTENSION,
31834
- onSelect: (file) => void uploadDiagram(file)
31835
- }, t.text('graphwise_toolbar_action.upload_diagram.label')));
31900
+ onSelect: (file) => void importDiagram(file)
31901
+ }, t.text('graphwise_toolbar_action.import_json.label'));
31902
+ }
31903
+
31904
+ /**
31905
+ * Each menu entry.
31906
+ *
31907
+ * Passing a `menu` to the `DefaultWorkspace` replaces its default actions, so they are listed here again.
31908
+ */
31909
+ const MENU_ITEMS = {
31910
+ [nodeAction.MenuItem.CLEAR_ALL]: (key) => reactExports.createElement(ToolbarActionClearAll, { key }),
31911
+ [nodeAction.MenuItem.EXPORT_PNG]: (key) => reactExports.createElement(ToolbarActionExport, { key, kind: 'exportRaster' }),
31912
+ [nodeAction.MenuItem.EXPORT_SVG]: (key) => reactExports.createElement(ToolbarActionExport, { key, kind: 'exportSvg' }),
31913
+ [nodeAction.MenuItem.PRINT]: (key) => reactExports.createElement(ToolbarActionExport, { key, kind: 'print' }),
31914
+ [nodeAction.MenuItem.EXPORT_JSON]: (key) => reactExports.createElement(ExportDiagramAction, { key }),
31915
+ [nodeAction.MenuItem.IMPORT_JSON]: (key) => reactExports.createElement(ImportDiagramAction, { key })
31916
+ };
31917
+ /**
31918
+ * The menu entries for the given menu items.
31919
+ */
31920
+ function createMenuItems(items) {
31921
+ return items.map((item) => MENU_ITEMS[item](item));
31922
+ }
31923
+
31924
+ /**
31925
+ * Each node action at its position in the fork's default `Halo`.
31926
+ */
31927
+ const NODE_ACTIONS = {
31928
+ [nodeAction.NodeAction.GROUP]: (key) => reactExports.createElement(SelectionActionGroup, { key, dock: 'nw', dockColumn: 1 }),
31929
+ [nodeAction.NodeAction.REMOVE]: (key) => reactExports.createElement(SelectionActionRemove, { key, dock: 'ne' }),
31930
+ [nodeAction.NodeAction.EXPAND]: (key) => reactExports.createElement(SelectionActionExpand, { key, dock: 's' }),
31931
+ [nodeAction.NodeAction.OPEN_LINK]: (key) => reactExports.createElement(SelectionActionAnchor, { key, dock: 'w' }),
31932
+ [nodeAction.NodeAction.CONNECTIONS]: (key) => reactExports.createElement(SelectionActionConnections, { key, dock: 'e' }),
31933
+ [nodeAction.NodeAction.ADD_TO_FILTER]: (key) => reactExports.createElement(SelectionActionAddToFilter, { key, dock: 'se' }),
31934
+ [nodeAction.NodeAction.ANNOTATE]: (key) => reactExports.createElement(SelectionActionAnnotate, { key, dock: 'se', dockColumn: 1 }),
31935
+ [nodeAction.NodeAction.ESTABLISH_LINK]: (key) => reactExports.createElement(SelectionActionEstablishLink, { key, dock: 'sw' })
31936
+ };
31937
+ /**
31938
+ * The `Halo` children for the given node actions.
31939
+ */
31940
+ function createNodeActions(actions) {
31941
+ return actions.map((action) => NODE_ACTIONS[action](action));
31942
+ }
31943
+
31944
+ const errorService$2 = service(ErrorService);
31945
+ /**
31946
+ * Merges the host's UI configuration over the default and maps the result to `DefaultWorkspace` props. A `null` or
31947
+ * missing field keeps the default; objects merge key by key and lists replace the default whole. Unknown values are
31948
+ * reported and left out; a repeated list entry is kept once.
31949
+ *
31950
+ * @param ui The host's UI configuration.
31951
+ */
31952
+ function toWorkspaceProps(ui) {
31953
+ const resolved = mergeWithDefaults(ui !== null && ui !== void 0 ? ui : {}, DEFAULT_UI_CONFIG);
31954
+ const menuItems = [...new Set(resolved.menu.items)].filter((item) => isValid(item, nodeAction.MenuItem));
31955
+ const nodeActions = [...new Set(resolved.nodeActions)].filter((action) => isValid(action, nodeAction.NodeAction));
31956
+ return {
31957
+ menu: menuItems.length ? reactExports.createElement(reactExports.Fragment, null, createMenuItems(menuItems)) : null,
31958
+ mainToolbar: toToolbarProps(resolved.mainToolbar, DEFAULT_UI_CONFIG.mainToolbar),
31959
+ actionsToolbar: toToolbarProps(resolved.actionsToolbar, DEFAULT_UI_CONFIG.actionsToolbar),
31960
+ halo: { children: createNodeActions(nodeActions) }
31961
+ };
31962
+ }
31963
+ /**
31964
+ * Maps a toolbar position to the `DefaultWorkspace` toolbar props. With an unknown dock, `defaultPosition` is used
31965
+ * whole, since the offsets are relative to the dock.
31966
+ */
31967
+ function toToolbarProps(position, defaultPosition) {
31968
+ const { dock, offsetX, offsetY } = isValid(position.dock, nodeAction.ToolbarDock) ? position : defaultPosition;
31969
+ return { dock, dockOffsetX: offsetX, dockOffsetY: offsetY };
31970
+ }
31971
+ /**
31972
+ * Whether `value` is one of the `known` values. Reports it as unknown UI configuration when it is not.
31973
+ *
31974
+ * @param value The value from the UI configuration.
31975
+ * @param known The known values, e.g. an enum.
31976
+ */
31977
+ function isValid(value, known) {
31978
+ const isValid = Object.values(known).includes(value);
31979
+ if (!isValid) {
31980
+ errorService$2.report(nodeAction.ErrorCode.UNKNOWN_UI_CONFIG, new Error(`Unknown UI configuration value: "${value}"`));
31981
+ }
31982
+ return isValid;
31836
31983
  }
31837
31984
 
31838
31985
  /**
@@ -31859,6 +32006,11 @@ let workspaceContext = null;
31859
32006
  */
31860
32007
  const subscriptions = new SubscriptionList();
31861
32008
  let diagramStorageSubscription;
32009
+ /**
32010
+ * The `DefaultWorkspace` props from the host's UI configuration. Kept across reloads, so unknown values are reported
32011
+ * once, and cleared in {@link unmountReactodia}.
32012
+ */
32013
+ let uiConfigProps;
31862
32014
  const diagramService = service(DiagramService);
31863
32015
  const diagramStorageService = service(DiagramStorageService);
31864
32016
  const eventService$1 = service(EventService);
@@ -31868,7 +32020,7 @@ const errorService$1 = service(ErrorService);
31868
32020
  */
31869
32021
  function emitTextSearch(method, params) {
31870
32022
  if (method === 'lookup' && params.text) {
31871
- eventService$1.emit({ type: errorCode.EventType.TEXT_SEARCH });
32023
+ eventService$1.emit({ type: nodeAction.EventType.TEXT_SEARCH });
31872
32024
  }
31873
32025
  }
31874
32026
  /**
@@ -31881,7 +32033,7 @@ const reportDataFetchError = async (_method, params, body) => {
31881
32033
  }
31882
32034
  catch (error) {
31883
32035
  if (!((_a = params.signal) === null || _a === void 0 ? void 0 : _a.aborted)) {
31884
- errorService$1.report(errorCode.ErrorCode.DATA_FETCH, error);
32036
+ errorService$1.report(nodeAction.ErrorCode.DATA_FETCH, error);
31885
32037
  }
31886
32038
  throw error;
31887
32039
  }
@@ -32020,7 +32172,7 @@ function ReactodiaApp(props) {
32020
32172
  }
32021
32173
  catch (error) {
32022
32174
  if (!signal.aborted) {
32023
- errorService$1.report(errorCode.ErrorCode.DIAGRAM_LOAD, error);
32175
+ errorService$1.report(nodeAction.ErrorCode.DIAGRAM_LOAD, error);
32024
32176
  }
32025
32177
  // rethrow, so the workspace shows the failure on its loading spinner
32026
32178
  throw error;
@@ -32042,17 +32194,13 @@ function ReactodiaApp(props) {
32042
32194
  defaultLanguage: language,
32043
32195
  translations: translationsForLanguage(language),
32044
32196
  typeStyleResolver: resolveTypeStyle
32045
- }, reactExports.createElement(DefaultWorkspace, {
32046
- colorScheme: props.theme,
32047
- menu: reactExports.createElement(GraphwiseMenu),
32048
- // place Undo/redo/layout actions in top right corner.
32049
- actionsToolbar: { dock: 'ne' }
32050
- }));
32197
+ }, reactExports.createElement(DefaultWorkspace, Object.assign({ colorScheme: props.theme }, uiConfigProps)));
32051
32198
  }
32052
32199
  /**
32053
32200
  * Mounts the Reactodia application into the given container and returns the React root.
32054
32201
  */
32055
32202
  function mountReactodia(container, props) {
32203
+ uiConfigProps !== null && uiConfigProps !== void 0 ? uiConfigProps : (uiConfigProps = toWorkspaceProps(props.config.ui));
32056
32204
  const root = clientExports.createRoot(container);
32057
32205
  root.render(reactExports.createElement(ReactodiaApp, props));
32058
32206
  return root;
@@ -32072,7 +32220,7 @@ async function updateReactodia(props) {
32072
32220
  await model.createNewDiagram({ dataProvider });
32073
32221
  }
32074
32222
  catch (error) {
32075
- errorService$1.report(errorCode.ErrorCode.DIAGRAM_LOAD, error);
32223
+ errorService$1.report(nodeAction.ErrorCode.DIAGRAM_LOAD, error);
32076
32224
  }
32077
32225
  }
32078
32226
  /**
@@ -32087,10 +32235,11 @@ function exportReactodiaLayout() {
32087
32235
  */
32088
32236
  function unmountReactodia(root) {
32089
32237
  subscriptions.unsubscribeAll();
32238
+ uiConfigProps = undefined;
32090
32239
  root.unmount();
32091
32240
  }
32092
32241
 
32093
- const graphwiseReactodiaCss = "graphwise-reactodia{--_graphwise-reactodia-type-color-01:var(--graphwise-reactodia-type-color-01);--_graphwise-reactodia-type-color-02:var(--graphwise-reactodia-type-color-02);--_graphwise-reactodia-type-color-03:var(--graphwise-reactodia-type-color-03);--_graphwise-reactodia-type-color-04:var(--graphwise-reactodia-type-color-04);--_graphwise-reactodia-type-color-05:var(--graphwise-reactodia-type-color-05);--_graphwise-reactodia-type-color-06:var(--graphwise-reactodia-type-color-06);--_graphwise-reactodia-type-color-07:var(--graphwise-reactodia-type-color-07);--_graphwise-reactodia-type-color-08:var(--graphwise-reactodia-type-color-08);--_graphwise-reactodia-type-color-09:var(--graphwise-reactodia-type-color-09);--_graphwise-reactodia-type-color-10:var(--graphwise-reactodia-type-color-10);--_graphwise-reactodia-type-color-11:var(--graphwise-reactodia-type-color-11);--_graphwise-reactodia-type-color-12:var(--graphwise-reactodia-type-color-12);--_graphwise-reactodia-type-color-13:var(--graphwise-reactodia-type-color-13);--_graphwise-reactodia-type-color-14:var(--graphwise-reactodia-type-color-14);--_graphwise-reactodia-type-color-15:var(--graphwise-reactodia-type-color-15);--_graphwise-reactodia-type-color-16:var(--graphwise-reactodia-type-color-16);--_graphwise-reactodia-type-color-17:var(--graphwise-reactodia-type-color-17);--_graphwise-reactodia-type-color-18:var(--graphwise-reactodia-type-color-18);--_graphwise-reactodia-type-color-19:var(--graphwise-reactodia-type-color-19);--_graphwise-reactodia-type-color-20:var(--graphwise-reactodia-type-color-20);--_graphwise-reactodia-type-color-contrast:var(--graphwise-reactodia-type-color-contrast, #fff)}graphwise-reactodia{display:block;width:100%;height:100%}graphwise-reactodia .reactodia-container{position:relative;width:100%;height:100%}graphwise-reactodia .reactodia-selection-action__link{display:none}graphwise-reactodia .reactodia-standard-element__thumbnail:not(:has(img)){background:var(--reactodia-element-style-color);border-radius:50%;color:var(--_graphwise-reactodia-type-color-contrast)}";
32242
+ const graphwiseReactodiaCss = "graphwise-reactodia{--_graphwise-reactodia-type-color-01:var(--graphwise-reactodia-type-color-01);--_graphwise-reactodia-type-color-02:var(--graphwise-reactodia-type-color-02);--_graphwise-reactodia-type-color-03:var(--graphwise-reactodia-type-color-03);--_graphwise-reactodia-type-color-04:var(--graphwise-reactodia-type-color-04);--_graphwise-reactodia-type-color-05:var(--graphwise-reactodia-type-color-05);--_graphwise-reactodia-type-color-06:var(--graphwise-reactodia-type-color-06);--_graphwise-reactodia-type-color-07:var(--graphwise-reactodia-type-color-07);--_graphwise-reactodia-type-color-08:var(--graphwise-reactodia-type-color-08);--_graphwise-reactodia-type-color-09:var(--graphwise-reactodia-type-color-09);--_graphwise-reactodia-type-color-10:var(--graphwise-reactodia-type-color-10);--_graphwise-reactodia-type-color-11:var(--graphwise-reactodia-type-color-11);--_graphwise-reactodia-type-color-12:var(--graphwise-reactodia-type-color-12);--_graphwise-reactodia-type-color-13:var(--graphwise-reactodia-type-color-13);--_graphwise-reactodia-type-color-14:var(--graphwise-reactodia-type-color-14);--_graphwise-reactodia-type-color-15:var(--graphwise-reactodia-type-color-15);--_graphwise-reactodia-type-color-16:var(--graphwise-reactodia-type-color-16);--_graphwise-reactodia-type-color-17:var(--graphwise-reactodia-type-color-17);--_graphwise-reactodia-type-color-18:var(--graphwise-reactodia-type-color-18);--_graphwise-reactodia-type-color-19:var(--graphwise-reactodia-type-color-19);--_graphwise-reactodia-type-color-20:var(--graphwise-reactodia-type-color-20);--_graphwise-reactodia-type-color-contrast:var(--graphwise-reactodia-type-color-contrast, #fff)}graphwise-reactodia{display:block;width:100%;height:100%}graphwise-reactodia .reactodia-container{position:relative;width:100%;height:100%}graphwise-reactodia .reactodia-standard-element__thumbnail:not(:has(img)){background:var(--reactodia-element-style-color);border-radius:50%;color:var(--_graphwise-reactodia-type-color-contrast)}graphwise-reactodia .reactodia-note-annotation{overflow:auto}graphwise-reactodia .reactodia-note-annotation__editor{overflow-wrap:anywhere}";
32094
32243
 
32095
32244
  const errorService = service(ErrorService);
32096
32245
  const eventService = service(EventService);
@@ -32141,7 +32290,7 @@ const GraphwiseReactodia = class {
32141
32290
  }
32142
32291
  }
32143
32292
  render() {
32144
- return (index.h(index.Host, { key: '2e8541c791f478484c4bad64ad758df99aba2059' }));
32293
+ return (index.h(index.Host, { key: 'b5997ab551d1afe62b4a81166aca98a7d62f4e35' }));
32145
32294
  }
32146
32295
  subscribeToEvents() {
32147
32296
  this.subscriptions.add(eventService.subscribe((detail) => this.eventEmitter.emit(detail)));
@@ -32180,7 +32329,7 @@ const GraphwiseReactodia = class {
32180
32329
  }
32181
32330
  reportMissingPropError(message) {
32182
32331
  const error = new Error(message);
32183
- errorService.report(errorCode.ErrorCode.MISSING_REQUIRED_PROP, error);
32332
+ errorService.report(nodeAction.ErrorCode.MISSING_REQUIRED_PROP, error);
32184
32333
  return error;
32185
32334
  }
32186
32335
  get hostElement() { return index.getElement(this); }
@@ -1,19 +1,31 @@
1
1
  'use strict';
2
2
 
3
- var errorCode = require('./error-code-BjU_V9e8.js');
3
+ var nodeAction = require('./node-action-DkJ5ct17.js');
4
4
 
5
5
  /**
6
6
  * Name of the DOM event the `graphwise-reactodia` element dispatches for every event.
7
7
  */
8
8
  const GRAPHWISE_REACTODIA_EVENT = 'graphwiseReactodiaEvent';
9
9
 
10
- exports.CLEAR_DIAGRAM_STORAGE_EVENT = errorCode.CLEAR_DIAGRAM_STORAGE_EVENT;
10
+ exports.CLEAR_DIAGRAM_STORAGE_EVENT = nodeAction.CLEAR_DIAGRAM_STORAGE_EVENT;
11
11
  Object.defineProperty(exports, "ErrorCode", {
12
12
  enumerable: true,
13
- get: function () { return errorCode.ErrorCode; }
13
+ get: function () { return nodeAction.ErrorCode; }
14
14
  });
15
15
  Object.defineProperty(exports, "EventType", {
16
16
  enumerable: true,
17
- get: function () { return errorCode.EventType; }
17
+ get: function () { return nodeAction.EventType; }
18
+ });
19
+ Object.defineProperty(exports, "MenuItem", {
20
+ enumerable: true,
21
+ get: function () { return nodeAction.MenuItem; }
22
+ });
23
+ Object.defineProperty(exports, "NodeAction", {
24
+ enumerable: true,
25
+ get: function () { return nodeAction.NodeAction; }
26
+ });
27
+ Object.defineProperty(exports, "ToolbarDock", {
28
+ enumerable: true,
29
+ get: function () { return nodeAction.ToolbarDock; }
18
30
  });
19
31
  exports.GRAPHWISE_REACTODIA_EVENT = GRAPHWISE_REACTODIA_EVENT;
@@ -0,0 +1,96 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Event name for clearing the local store state of the diagram. There is an event listener listening for it, which
5
+ * will clear the local store state, whenever this event is fired.
6
+ */
7
+ const CLEAR_DIAGRAM_STORAGE_EVENT = 'graphwise-reactodia:clear-diagram-storage';
8
+
9
+ /**
10
+ * The kind of event the wrapper announces to the host. It selects the payload of an {@link EventDetail}.
11
+ */
12
+ exports.EventType = void 0;
13
+ (function (EventType) {
14
+ EventType["ERROR"] = "error";
15
+ EventType["TEXT_SEARCH"] = "text-search";
16
+ })(exports.EventType || (exports.EventType = {}));
17
+
18
+ /**
19
+ * The stable category of a reported error. The host uses it to choose its own message.
20
+ */
21
+ exports.ErrorCode = void 0;
22
+ (function (ErrorCode) {
23
+ /** A required prop is missing. */
24
+ ErrorCode["MISSING_REQUIRED_PROP"] = "missing-required-prop";
25
+ /** A SPARQL data provider call failed. */
26
+ ErrorCode["DATA_FETCH"] = "data-fetch";
27
+ /** The startup load, a reload or a repository switch failed. */
28
+ ErrorCode["DIAGRAM_LOAD"] = "diagram-load";
29
+ /** The uploaded file was rejected or its import failed. */
30
+ ErrorCode["DIAGRAM_IMPORT"] = "diagram-import";
31
+ /** Reading the diagram from local storage failed. */
32
+ ErrorCode["LOCAL_DIAGRAM_READ"] = "local-diagram-read";
33
+ /** Saving the diagram in local storage failed. */
34
+ ErrorCode["LOCAL_DIAGRAM_SAVE"] = "local-diagram-save";
35
+ /** The host's UI configuration has an unknown value, which is skipped. */
36
+ ErrorCode["UNKNOWN_UI_CONFIG"] = "unknown-ui-config";
37
+ })(exports.ErrorCode || (exports.ErrorCode = {}));
38
+
39
+ /**
40
+ * The side or corner of the canvas a toolbar is docked to.
41
+ */
42
+ exports.ToolbarDock = void 0;
43
+ (function (ToolbarDock) {
44
+ ToolbarDock["NW"] = "nw";
45
+ ToolbarDock["N"] = "n";
46
+ ToolbarDock["NE"] = "ne";
47
+ ToolbarDock["E"] = "e";
48
+ ToolbarDock["SE"] = "se";
49
+ ToolbarDock["S"] = "s";
50
+ ToolbarDock["SW"] = "sw";
51
+ ToolbarDock["W"] = "w";
52
+ })(exports.ToolbarDock || (exports.ToolbarDock = {}));
53
+
54
+ /**
55
+ * An entry of the workspace main menu.
56
+ */
57
+ exports.MenuItem = void 0;
58
+ (function (MenuItem) {
59
+ /** Removes all nodes from the diagram. */
60
+ MenuItem["CLEAR_ALL"] = "clear-all";
61
+ /** Exports the diagram as a PNG image. */
62
+ MenuItem["EXPORT_PNG"] = "export-png";
63
+ /** Exports the diagram as an SVG image. */
64
+ MenuItem["EXPORT_SVG"] = "export-svg";
65
+ /** Prints the diagram. */
66
+ MenuItem["PRINT"] = "print";
67
+ /** Exports the diagram as a JSON file. */
68
+ MenuItem["EXPORT_JSON"] = "export-json";
69
+ /** Imports a diagram from a JSON file. */
70
+ MenuItem["IMPORT_JSON"] = "import-json";
71
+ })(exports.MenuItem || (exports.MenuItem = {}));
72
+
73
+ /**
74
+ * A button shown around a selected node that acts on it.
75
+ */
76
+ exports.NodeAction = void 0;
77
+ (function (NodeAction) {
78
+ /** Groups or ungroups the selected nodes. */
79
+ NodeAction["GROUP"] = "group";
80
+ /** Removes the node from the diagram. */
81
+ NodeAction["REMOVE"] = "remove";
82
+ /** Expands or collapses the node to show its properties. */
83
+ NodeAction["EXPAND"] = "expand";
84
+ /** Opens the node IRI. */
85
+ NodeAction["OPEN_LINK"] = "open-link";
86
+ /** Opens the menu to navigate to connected nodes. */
87
+ NodeAction["CONNECTIONS"] = "connections";
88
+ /** Searches for nodes connected to this one. */
89
+ NodeAction["ADD_TO_FILTER"] = "add-to-filter";
90
+ /** Adds an annotation to the node. */
91
+ NodeAction["ANNOTATE"] = "annotate";
92
+ /** Draws a new link from the node; for an entity, only in authoring mode. */
93
+ NodeAction["ESTABLISH_LINK"] = "establish-link";
94
+ })(exports.NodeAction || (exports.NodeAction = {}));
95
+
96
+ exports.CLEAR_DIAGRAM_STORAGE_EVENT = CLEAR_DIAGRAM_STORAGE_EVENT;
@@ -53,11 +53,14 @@ graphwise-reactodia .reactodia-container {
53
53
  width: 100%;
54
54
  height: 100%;
55
55
  }
56
- graphwise-reactodia .reactodia-selection-action__link {
57
- display: none;
58
- }
59
56
  graphwise-reactodia .reactodia-standard-element__thumbnail:not(:has(img)) {
60
57
  background: var(--reactodia-element-style-color);
61
58
  border-radius: 50%;
62
59
  color: var(--_graphwise-reactodia-type-color-contrast);
60
+ }
61
+ graphwise-reactodia .reactodia-note-annotation {
62
+ overflow: auto;
63
+ }
64
+ graphwise-reactodia .reactodia-note-annotation__editor {
65
+ overflow-wrap: anywhere;
63
66
  }
@@ -65,7 +65,7 @@ export class GraphwiseReactodia {
65
65
  }
66
66
  }
67
67
  render() {
68
- return (h(Host, { key: '2e8541c791f478484c4bad64ad758df99aba2059' }));
68
+ return (h(Host, { key: 'b5997ab551d1afe62b4a81166aca98a7d62f4e35' }));
69
69
  }
70
70
  subscribeToEvents() {
71
71
  this.subscriptions.add(eventService.subscribe((detail) => this.eventEmitter.emit(detail)));
@@ -158,7 +158,7 @@ export class GraphwiseReactodia {
158
158
  "optional": true,
159
159
  "docs": {
160
160
  "tags": [],
161
- "text": "Host-supplied configuration: the SPARQL `queryFunction` transport and an optional `seed`\nset of entities to pre-populate the canvas with. A DOM property (an object, not an\nattribute) passed in from outside the wrapper.\n\nRead once on mount; not watched, as it only sets up the data source and initial canvas."
161
+ "text": "Host-supplied configuration: the SPARQL `queryFunction` transport, an optional `seed`\nset of entities to pre-populate the canvas with and an optional `ui` configuration.\nA DOM property (an object, not an attribute) passed in from outside the wrapper.\n\nRead once on mount; not watched, as it only sets up the data source and initial canvas."
162
162
  },
163
163
  "getter": false,
164
164
  "setter": false
@@ -0,0 +1,19 @@
1
+ import { createElement } from "react";
2
+ import { ToolbarAction, useTranslation, useWorkspace } from "@reactodia/workspace";
3
+ import { service } from "../providers/service/service-inject";
4
+ import { DiagramFileService } from "../services/diagram-file/diagram-file.service";
5
+ /**
6
+ * Exports the diagram as a JSON file.
7
+ */
8
+ export function ExportDiagramAction() {
9
+ const { model } = useWorkspace();
10
+ const t = useTranslation();
11
+ const diagramFileService = service(DiagramFileService);
12
+ const exportDiagram = () => {
13
+ const timestamp = new Date().toISOString().replace(/[-:]|\.\d+Z$/g, '');
14
+ diagramFileService.download(model.exportLayout(), `diagram-${timestamp}${DiagramFileService.FILE_EXTENSION}`);
15
+ };
16
+ return createElement(ToolbarAction,
17
+ // The same icon as ToolbarActionSave. We don't use that action, since it disables saving on refresh
18
+ { className: 'reactodia-toolbar-action__save', title: t.text('graphwise_toolbar_action.export_json.title'), onSelect: exportDiagram }, t.text('graphwise_toolbar_action.export_json.label'));
19
+ }