graphwise-reactodia 0.0.1-TR11 → 0.0.1-TR13

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 (25) hide show
  1. package/dist/cjs/graphwise-reactodia.cjs.entry.js +192 -26
  2. package/dist/cjs/graphwise-reactodia.cjs.js +1 -1
  3. package/dist/cjs/loader.cjs.js +1 -1
  4. package/dist/collection/components/graphwise-reactodia/graphwise-reactodia.css +3 -0
  5. package/dist/collection/components/graphwise-reactodia/graphwise-reactodia.js +13 -9
  6. package/dist/collection/components/graphwise-reactodia/i18n/translations.js +4 -1
  7. package/dist/collection/components/graphwise-reactodia/menu/graphwise-menu.js +37 -0
  8. package/dist/collection/components/graphwise-reactodia/reactodia-app.js +17 -10
  9. package/dist/collection/components/graphwise-reactodia/services/diagram/diagram.service.js +14 -0
  10. package/dist/collection/components/graphwise-reactodia/services/diagram-file/diagram-file.service.js +68 -0
  11. package/dist/collection/pages/graphwise-reactodia/main.js +6 -0
  12. package/dist/collection/pages/graphwise-reactodia/owl-rdfs-settings.js +195 -0
  13. package/dist/esm/graphwise-reactodia.entry.js +192 -26
  14. package/dist/esm/graphwise-reactodia.js +1 -1
  15. package/dist/esm/loader.js +1 -1
  16. package/dist/graphwise-reactodia/graphwise-reactodia.esm.js +1 -1
  17. package/dist/graphwise-reactodia/{p-81eeb4c3.entry.js → p-420b9dce.entry.js} +1 -1
  18. package/dist/types/components/graphwise-reactodia/graphwise-reactodia.d.ts +5 -5
  19. package/dist/types/components/graphwise-reactodia/menu/graphwise-menu.d.ts +9 -0
  20. package/dist/types/components/graphwise-reactodia/models/reactodia-app-props.d.ts +4 -4
  21. package/dist/types/components/graphwise-reactodia/reactodia-app.d.ts +3 -2
  22. package/dist/types/components/graphwise-reactodia/services/diagram/diagram.service.d.ts +11 -1
  23. package/dist/types/components/graphwise-reactodia/services/diagram-file/diagram-file.service.d.ts +24 -0
  24. package/dist/types/components.d.ts +2 -2
  25. package/package.json +2 -2
@@ -23868,6 +23868,35 @@ function ToolbarAction(props) {
23868
23868
  });
23869
23869
  }
23870
23870
  /**
23871
+ * Toolbar action component to open a file selection dialog.
23872
+ *
23873
+ * @category Components
23874
+ */
23875
+ function ToolbarActionOpen(props) {
23876
+ const { className, fileAccept, onSelect, children, ...otherProps } = props;
23877
+ const inputRef = reactExports.useRef(null);
23878
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [/* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarAction, {
23879
+ ...otherProps,
23880
+ className: clsx(className, `${CLASS_NAME$8}__open`),
23881
+ onSelect: () => {
23882
+ inputRef.current?.click();
23883
+ },
23884
+ children
23885
+ }), /* @__PURE__ */ jsxRuntimeExports.jsx("input", {
23886
+ ref: inputRef,
23887
+ role: "none",
23888
+ type: "file",
23889
+ className: `${CLASS_NAME$8}__open-input`,
23890
+ accept: fileAccept,
23891
+ onChange: (e) => {
23892
+ if (e.currentTarget.files) {
23893
+ const file = e.currentTarget.files[0];
23894
+ onSelect(file);
23895
+ }
23896
+ }
23897
+ })] });
23898
+ }
23899
+ /**
23871
23900
  * Toolbar action component to clear diagram content.
23872
23901
  *
23873
23902
  * Clearing the diagram adds a command to the command history.
@@ -30846,6 +30875,16 @@ function getContentFittingBoxForLayout(state) {
30846
30875
  };
30847
30876
  }
30848
30877
 
30878
+ const graphwise_toolbar_action$1 = {
30879
+ "download_diagram.label": "Export to JSON",
30880
+ "download_diagram.title": "Export the diagram to a JSON file",
30881
+ "upload_diagram.label": "Import from JSON",
30882
+ "upload_diagram.title": "Replace the diagram with one from a JSON file"
30883
+ };
30884
+ var enTranslation = {
30885
+ graphwise_toolbar_action: graphwise_toolbar_action$1
30886
+ };
30887
+
30849
30888
  const authoring_state = {
30850
30889
  "entity_action_delete.label": "supprimer",
30851
30890
  "entity_action_delete.title": "Supprimer l'entité",
@@ -31100,6 +31139,12 @@ const standard_link = {
31100
31139
  const toolbar = {
31101
31140
  "menu_toggle.title": "Ouvrir le menu"
31102
31141
  };
31142
+ const graphwise_toolbar_action = {
31143
+ "download_diagram.label": "Exporter en JSON",
31144
+ "download_diagram.title": "Exporter le diagramme en fichier JSON",
31145
+ "upload_diagram.label": "Importer depuis JSON",
31146
+ "upload_diagram.title": "Remplacer le diagramme par celui d'un fichier JSON"
31147
+ };
31103
31148
  const toolbar_action = {
31104
31149
  "clear_all.command": "Tout effacer",
31105
31150
  "clear_all.title": "Supprimer tous les éléments et liens du diagramme",
@@ -31210,6 +31255,7 @@ var frTranslation = {
31210
31255
  standard_element: standard_element,
31211
31256
  standard_link: standard_link,
31212
31257
  toolbar: toolbar,
31258
+ graphwise_toolbar_action: graphwise_toolbar_action,
31213
31259
  toolbar_action: toolbar_action,
31214
31260
  unified_search: unified_search,
31215
31261
  visual_authoring: visual_authoring,
@@ -31224,8 +31270,10 @@ var LanguageKey;
31224
31270
  LanguageKey["FR"] = "fr";
31225
31271
  })(LanguageKey || (LanguageKey = {}));
31226
31272
 
31227
- // English is provided by default from reactodia so we don't need to include it here, unless we override it
31273
+ // English is provided by default from reactodia, so the English bundle holds only our own keys and overrides. If
31274
+ // A key is missing, reactodia defaults to its own translation.
31228
31275
  const TRANSLATIONS = {
31276
+ [LanguageKey.EN]: enTranslation,
31229
31277
  [LanguageKey.FR]: frTranslation
31230
31278
  };
31231
31279
 
@@ -31285,7 +31333,7 @@ function Service(_serviceClass) {
31285
31333
  // no implementation needed. We only use the type checking of the parameter
31286
31334
  }
31287
31335
 
31288
- var __decorate$3 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
31336
+ var __decorate$4 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
31289
31337
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
31290
31338
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
31291
31339
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -31296,6 +31344,20 @@ var DiagramService_1;
31296
31344
  * Service for diagram related operations and subscriptions
31297
31345
  */
31298
31346
  let DiagramService = DiagramService_1 = class DiagramService {
31347
+ /**
31348
+ * Replaces the diagram content with the given serialization. Element data is requested from the data provider and
31349
+ * the links between the imported elements are validated against it.
31350
+ *
31351
+ * @param model The diagram model to import into.
31352
+ * @param dataProvider The data provider to bind to the diagram.
31353
+ * @param diagram The serialization to import. When `undefined`, the diagram is cleared.
31354
+ * @param signal Optional signal to abort the import.
31355
+ */
31356
+ importDiagram(model, dataProvider, diagram, signal) {
31357
+ // selection is not cleared on import, so we clear it manually to avoid painting the previous selection
31358
+ model.setSelection([]);
31359
+ return model.importLayout({ dataProvider, diagram, signal, validateLinks: true });
31360
+ }
31299
31361
  /**
31300
31362
  * Subscribes to diagram changes. On every debounced change the current layout is
31301
31363
  * exported and passed to the callback.
@@ -31330,11 +31392,11 @@ let DiagramService = DiagramService_1 = class DiagramService {
31330
31392
  DiagramService.ID = 'DiagramService';
31331
31393
  DiagramService.NOTIFY_DEBOUNCE_MS = 300;
31332
31394
  DiagramService.HISTORY_CHANGED_EVENT = 'historyChanged';
31333
- DiagramService = DiagramService_1 = __decorate$3([
31395
+ DiagramService = DiagramService_1 = __decorate$4([
31334
31396
  Service
31335
31397
  ], DiagramService);
31336
31398
 
31337
- var __decorate$2 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
31399
+ var __decorate$3 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
31338
31400
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
31339
31401
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
31340
31402
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -31349,11 +31411,11 @@ let WindowService = class WindowService {
31349
31411
  }
31350
31412
  };
31351
31413
  WindowService.ID = 'WindowService';
31352
- WindowService = __decorate$2([
31414
+ WindowService = __decorate$3([
31353
31415
  Service
31354
31416
  ], WindowService);
31355
31417
 
31356
- var __decorate$1 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
31418
+ var __decorate$2 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
31357
31419
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
31358
31420
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
31359
31421
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -31397,11 +31459,11 @@ let DiagramStorageService = DiagramStorageService_1 = class DiagramStorageServic
31397
31459
  };
31398
31460
  DiagramStorageService.ID = 'DiagramStorageService';
31399
31461
  DiagramStorageService.STORAGE_KEY = 'diagram.state';
31400
- DiagramStorageService = DiagramStorageService_1 = __decorate$1([
31462
+ DiagramStorageService = DiagramStorageService_1 = __decorate$2([
31401
31463
  Service
31402
31464
  ], DiagramStorageService);
31403
31465
 
31404
- var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
31466
+ var __decorate$1 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
31405
31467
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
31406
31468
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
31407
31469
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -31424,7 +31486,7 @@ let EventService = class EventService {
31424
31486
  }
31425
31487
  };
31426
31488
  EventService.ID = 'EventService';
31427
- EventService = __decorate([
31489
+ EventService = __decorate$1([
31428
31490
  Service
31429
31491
  ], EventService);
31430
31492
 
@@ -31518,8 +31580,102 @@ const resetColors = () => {
31518
31580
  typeToIndex.clear();
31519
31581
  };
31520
31582
 
31583
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
31584
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
31585
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
31586
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
31587
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
31588
+ };
31589
+ var DiagramFileService_1;
31590
+ /**
31591
+ * Service for downloading and uploading the diagram as a file. The file content is the same
31592
+ * {@link SerializedDiagram} that is persisted to the local store.
31593
+ */
31594
+ let DiagramFileService = DiagramFileService_1 = class DiagramFileService {
31595
+ /**
31596
+ * Downloads the given diagram serialization as a file with the given name.
31597
+ */
31598
+ download(diagram, fileName) {
31599
+ const window = service(WindowService).getWindow();
31600
+ const blob = new Blob([JSON.stringify(diagram)], { type: DiagramFileService_1.MIME_TYPE });
31601
+ const url = window.URL.createObjectURL(blob);
31602
+ const link = window.document.createElement('a');
31603
+ link.href = url;
31604
+ link.download = fileName;
31605
+ link.click();
31606
+ window.URL.revokeObjectURL(url);
31607
+ }
31608
+ /**
31609
+ * Reads a diagram serialization from the given file.
31610
+ *
31611
+ * @throws Error when the file is not valid JSON or is not a serialized diagram.
31612
+ */
31613
+ async read(file) {
31614
+ let diagram;
31615
+ try {
31616
+ diagram = JSON.parse(await file.text());
31617
+ }
31618
+ catch (error) {
31619
+ throw new Error(`File "${file.name}" is not valid JSON: ${error.message}`);
31620
+ }
31621
+ if (!this.isSerializedDiagram(diagram)) {
31622
+ throw new Error(`File "${file.name}" is not a serialized diagram`);
31623
+ }
31624
+ return diagram;
31625
+ }
31626
+ /**
31627
+ * Checks if the provided value is a serialized diagram
31628
+ */
31629
+ isSerializedDiagram(value) {
31630
+ var _a, _b;
31631
+ const diagram = value;
31632
+ return (diagram === null || diagram === void 0 ? void 0 : diagram['@type']) === 'Diagram'
31633
+ && Array.isArray((_a = diagram.layoutData) === null || _a === void 0 ? void 0 : _a.elements)
31634
+ && Array.isArray((_b = diagram.layoutData) === null || _b === void 0 ? void 0 : _b.links);
31635
+ }
31636
+ };
31637
+ DiagramFileService.ID = 'DiagramFileService';
31638
+ DiagramFileService.FILE_EXTENSION = '.json';
31639
+ DiagramFileService.MIME_TYPE = 'application/json';
31640
+ DiagramFileService = DiagramFileService_1 = __decorate([
31641
+ Service
31642
+ ], DiagramFileService);
31643
+
31644
+ /**
31645
+ * The workspace main menu: Reactodia's default actions, followed by the diagram download and upload.
31646
+ *
31647
+ * Passing a `menu` to the `DefaultWorkspace` replaces its default actions, so they are listed here again.
31648
+ * Rendered inside the workspace, so the actions can use the workspace hooks.
31649
+ */
31650
+ function GraphwiseMenu() {
31651
+ const { model } = useWorkspace();
31652
+ const t = useTranslation();
31653
+ const diagramFileService = service(DiagramFileService);
31654
+ const diagramService = service(DiagramService);
31655
+ const downloadDiagram = () => {
31656
+ const timestamp = new Date().toISOString().replace(/[-:]|\.\d+Z$/g, '');
31657
+ diagramFileService.download(model.exportLayout(), `diagram-${timestamp}${DiagramFileService.FILE_EXTENSION}`);
31658
+ };
31659
+ const uploadDiagram = async (file) => {
31660
+ try {
31661
+ const diagram = await diagramFileService.read(file);
31662
+ await diagramService.importDiagram(model, model.dataProvider, diagram);
31663
+ }
31664
+ catch (error) {
31665
+ console.error('Failed to upload the diagram', error);
31666
+ }
31667
+ };
31668
+ 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,
31669
+ // The same icon as ToolbarActionSave. We dont use that action, since it disables saving on refresh
31670
+ { 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, {
31671
+ title: t.text('graphwise_toolbar_action.upload_diagram.title'),
31672
+ fileAccept: DiagramFileService.FILE_EXTENSION,
31673
+ onSelect: (file) => void uploadDiagram(file)
31674
+ }, t.text('graphwise_toolbar_action.upload_diagram.label')));
31675
+ }
31676
+
31521
31677
  /**
31522
- * Reactodia ships English as its built-in default bundle, so English needs no override.
31678
+ * Reactodia ships English as its built-in default bundle, so the English bundle only adds our own keys.
31523
31679
  * Other languages are layered on top as partial bundles; unknown codes fall back to English.
31524
31680
  *
31525
31681
  * Reactodia never re-reads the translations, so the component must be remounted
@@ -31543,8 +31699,7 @@ let workspaceContext = null;
31543
31699
  const subscriptions = new SubscriptionList();
31544
31700
  /**
31545
31701
  * Builds a Reactodia {@link SparqlDataProvider} for the given endpoint using the supplied
31546
- * query preset. The host owns the query configuration and passes it in via props; when none
31547
- * is provided we fall back to Reactodia's generic {@link OwlRdfsSettings} OWL/RDFS preset.
31702
+ * query preset. The host owns the query configuration and always passes it in via props.
31548
31703
  */
31549
31704
  function createDataProvider(props) {
31550
31705
  const { currentRepository, config, providerSettings } = props;
@@ -31637,10 +31792,12 @@ function ReactodiaApp(props) {
31637
31792
  const dataProvider = createDataProvider(props);
31638
31793
  const isReload = props.isReload;
31639
31794
  const savedDiagram = service(DiagramStorageService).load();
31795
+ const diagramService = service(DiagramService);
31640
31796
  if (isReload) {
31641
- // Simply reload without using the current state. This may happen when the user switches the language at runtime.
31642
- // since there is no existing mechanism to re-translate the UI at runtime (not for the UI labels at least)
31643
- await model.importLayout({ dataProvider, diagram: currentDiagram, signal, validateLinks: true });
31797
+ // Reload the diagram that is currently on the canvas. This may happen when the host changes the provider
31798
+ // settings, or when the user switches the language at runtime, since there is no existing mechanism to
31799
+ // re-translate the UI at runtime (not for the UI labels at least)
31800
+ await diagramService.importDiagram(model, dataProvider, currentDiagram, signal);
31644
31801
  }
31645
31802
  else if ((_a = config.seedIris) === null || _a === void 0 ? void 0 : _a.length) {
31646
31803
  await seedIrisToCanvas(context, dataProvider, config.seedIris, signal);
@@ -31651,7 +31808,7 @@ function ReactodiaApp(props) {
31651
31808
  else {
31652
31809
  // Restore the previously saved layout when present, otherwise start empty. Either way this binds the
31653
31810
  // data provider (savedDiagram is undefined -> empty diagram), so the unified search/lookup works.
31654
- await model.importLayout({ dataProvider, diagram: savedDiagram, signal, validateLinks: true });
31811
+ await diagramService.importDiagram(model, dataProvider, savedDiagram, signal);
31655
31812
  }
31656
31813
  }, [language]);
31657
31814
  /**
@@ -31670,7 +31827,11 @@ function ReactodiaApp(props) {
31670
31827
  defaultLanguage: language,
31671
31828
  translations: translationsForLanguage(language),
31672
31829
  typeStyleResolver: resolveTypeStyle
31673
- }, reactExports.createElement(DefaultWorkspace, {}));
31830
+ }, reactExports.createElement(DefaultWorkspace, {
31831
+ menu: reactExports.createElement(GraphwiseMenu),
31832
+ // place Undo/redo/layout actions in top right corner.
31833
+ actionsToolbar: { dock: 'ne' }
31834
+ }));
31674
31835
  }
31675
31836
  /**
31676
31837
  * Mounts the Reactodia application into the given container and returns the React root.
@@ -31681,8 +31842,9 @@ function mountReactodia(container, props) {
31681
31842
  return root;
31682
31843
  }
31683
31844
  /**
31684
- * Re-points the diagram at a new SPARQL endpoint or query preset by recreating it with a
31685
- * fresh data provider. The canvas is reset to empty, matching the initial mount.
31845
+ * Re-points the diagram at a new SPARQL endpoint by recreating it with a fresh data provider.
31846
+ * The canvas is reset to empty, matching the initial mount, since the data of another repository
31847
+ * has nothing to do with the current graph.
31686
31848
  */
31687
31849
  async function updateReactodia(props) {
31688
31850
  if (!workspaceContext) {
@@ -31707,7 +31869,7 @@ function unmountReactodia(root) {
31707
31869
  root.unmount();
31708
31870
  }
31709
31871
 
31710
- const graphwiseReactodiaCss = "graphwise-reactodia{display:block;width:100%;height:75vh}graphwise-reactodia .reactodia-container{position:relative;width:100%;height:100%}";
31872
+ const graphwiseReactodiaCss = "graphwise-reactodia{display:block;width:100%;height:75vh}graphwise-reactodia .reactodia-container{position:relative;width:100%;height:100%}graphwise-reactodia .reactodia-selection-action__link{display:none}";
31711
31873
 
31712
31874
  const GraphwiseReactodia = class {
31713
31875
  constructor(hostRef) {
@@ -31717,12 +31879,13 @@ const GraphwiseReactodia = class {
31717
31879
  */
31718
31880
  this.language = LanguageKey.EN;
31719
31881
  }
31720
- onProviderSettingsChange() {
31882
+ onCurrentRepositoryChange() {
31721
31883
  this.renderGraph();
31722
31884
  }
31723
- onLanguageChange() {
31885
+ onLanguageOrSettingsChange() {
31724
31886
  // The translations are evaluated by the workspace at construction, so the only way
31725
31887
  // to re-translate the UI is to re-create the graph.
31888
+ // Setting changes should keep the current diagram despite possible
31726
31889
  this.renderGraph(true);
31727
31890
  }
31728
31891
  onThemeChange() {
@@ -31741,7 +31904,7 @@ const GraphwiseReactodia = class {
31741
31904
  }
31742
31905
  }
31743
31906
  render() {
31744
- return (index$1.h(index$1.Host, { key: 'f3247ee3254896e4e4987bd2fa0e3dfce1ce1d2a' }));
31907
+ return (index$1.h(index$1.Host, { key: '6f1115b91eac308ef155978dfe1482f152c38b1b' }));
31745
31908
  }
31746
31909
  renderGraph(isReloading) {
31747
31910
  var _a;
@@ -31751,6 +31914,9 @@ const GraphwiseReactodia = class {
31751
31914
  if (!((_a = this.config) === null || _a === void 0 ? void 0 : _a.queryFunction)) {
31752
31915
  throw new Error('config.queryFunction is required');
31753
31916
  }
31917
+ if (!this.providerSettings) {
31918
+ throw new Error('providerSettings is required');
31919
+ }
31754
31920
  if (!this.hostElement) {
31755
31921
  return;
31756
31922
  }
@@ -31770,9 +31936,9 @@ const GraphwiseReactodia = class {
31770
31936
  }
31771
31937
  get hostElement() { return index$1.getElement(this); }
31772
31938
  static get watchers() { return {
31773
- "currentRepository": ["onProviderSettingsChange"],
31774
- "providerSettings": ["onProviderSettingsChange"],
31775
- "language": ["onLanguageChange"],
31939
+ "currentRepository": ["onCurrentRepositoryChange"],
31940
+ "language": ["onLanguageOrSettingsChange"],
31941
+ "providerSettings": ["onLanguageOrSettingsChange"],
31776
31942
  "theme": ["onThemeChange"]
31777
31943
  }; }
31778
31944
  };
@@ -19,7 +19,7 @@ var patchBrowser = () => {
19
19
 
20
20
  patchBrowser().then(async (options) => {
21
21
  await appGlobals.globalScripts();
22
- return index.bootstrapLazy([["graphwise-reactodia.cjs",[[0,"graphwise-reactodia",{"currentRepository":[1,"current-repository"],"config":[16],"language":[1],"providerSettings":[16,"provider-settings"],"theme":[1]},null,{"currentRepository":["onProviderSettingsChange"],"providerSettings":["onProviderSettingsChange"],"language":["onLanguageChange"],"theme":["onThemeChange"]}]]]], options);
22
+ return index.bootstrapLazy([["graphwise-reactodia.cjs",[[0,"graphwise-reactodia",{"currentRepository":[1,"current-repository"],"config":[16],"language":[1],"providerSettings":[16,"provider-settings"],"theme":[1]},null,{"currentRepository":["onCurrentRepositoryChange"],"language":["onLanguageOrSettingsChange"],"providerSettings":["onLanguageOrSettingsChange"],"theme":["onThemeChange"]}]]]], options);
23
23
  });
24
24
 
25
25
  exports.setNonce = index.setNonce;
@@ -6,7 +6,7 @@ var appGlobals = require('./app-globals-CcJr5lkF.js');
6
6
  const defineCustomElements = async (win, options) => {
7
7
  if (typeof window === 'undefined') return undefined;
8
8
  await appGlobals.globalScripts();
9
- return index.bootstrapLazy([["graphwise-reactodia.cjs",[[0,"graphwise-reactodia",{"currentRepository":[1,"current-repository"],"config":[16],"language":[1],"providerSettings":[16,"provider-settings"],"theme":[1]},null,{"currentRepository":["onProviderSettingsChange"],"providerSettings":["onProviderSettingsChange"],"language":["onLanguageChange"],"theme":["onThemeChange"]}]]]], options);
9
+ return index.bootstrapLazy([["graphwise-reactodia.cjs",[[0,"graphwise-reactodia",{"currentRepository":[1,"current-repository"],"config":[16],"language":[1],"providerSettings":[16,"provider-settings"],"theme":[1]},null,{"currentRepository":["onCurrentRepositoryChange"],"language":["onLanguageOrSettingsChange"],"providerSettings":["onLanguageOrSettingsChange"],"theme":["onThemeChange"]}]]]], options);
10
10
  };
11
11
 
12
12
  exports.setNonce = index.setNonce;
@@ -7,4 +7,7 @@ graphwise-reactodia .reactodia-container {
7
7
  position: relative;
8
8
  width: 100%;
9
9
  height: 100%;
10
+ }
11
+ graphwise-reactodia .reactodia-selection-action__link {
12
+ display: none;
10
13
  }
@@ -20,12 +20,13 @@ export class GraphwiseReactodia {
20
20
  */
21
21
  this.language = LanguageKey.EN;
22
22
  }
23
- onProviderSettingsChange() {
23
+ onCurrentRepositoryChange() {
24
24
  this.renderGraph();
25
25
  }
26
- onLanguageChange() {
26
+ onLanguageOrSettingsChange() {
27
27
  // The translations are evaluated by the workspace at construction, so the only way
28
28
  // to re-translate the UI is to re-create the graph.
29
+ // Setting changes should keep the current diagram despite possible
29
30
  this.renderGraph(true);
30
31
  }
31
32
  onThemeChange() {
@@ -44,7 +45,7 @@ export class GraphwiseReactodia {
44
45
  }
45
46
  }
46
47
  render() {
47
- return (h(Host, { key: 'f3247ee3254896e4e4987bd2fa0e3dfce1ce1d2a' }));
48
+ return (h(Host, { key: '6f1115b91eac308ef155978dfe1482f152c38b1b' }));
48
49
  }
49
50
  renderGraph(isReloading) {
50
51
  var _a;
@@ -54,6 +55,9 @@ export class GraphwiseReactodia {
54
55
  if (!((_a = this.config) === null || _a === void 0 ? void 0 : _a.queryFunction)) {
55
56
  throw new Error('config.queryFunction is required');
56
57
  }
58
+ if (!this.providerSettings) {
59
+ throw new Error('providerSettings is required');
60
+ }
57
61
  if (!this.hostElement) {
58
62
  return;
59
63
  }
@@ -172,7 +176,7 @@ export class GraphwiseReactodia {
172
176
  "optional": true,
173
177
  "docs": {
174
178
  "tags": [],
175
- "text": "Query preset for the SPARQL data provider, owned and configured by the host. A DOM\nproperty (an object, not an attribute) passed in from outside the wrapper. When omitted,\nthe data provider falls back to Reactodia's generic OWL/RDFS preset. Changing it rebuilds\nthe data provider and resets the canvas."
179
+ "text": "Query preset for the SPARQL data provider, owned and configured by the host. A DOM\nproperty (an object, not an attribute) passed in from outside the wrapper. Required: the\nwrapper has no default preset and throws when it is missing. Changing it rebuilds the data\nprovider and reloads the graph with it, keeping the current diagram."
176
180
  },
177
181
  "getter": false,
178
182
  "setter": false
@@ -202,13 +206,13 @@ export class GraphwiseReactodia {
202
206
  static get watchers() {
203
207
  return [{
204
208
  "propName": "currentRepository",
205
- "methodName": "onProviderSettingsChange"
206
- }, {
207
- "propName": "providerSettings",
208
- "methodName": "onProviderSettingsChange"
209
+ "methodName": "onCurrentRepositoryChange"
209
210
  }, {
210
211
  "propName": "language",
211
- "methodName": "onLanguageChange"
212
+ "methodName": "onLanguageOrSettingsChange"
213
+ }, {
214
+ "propName": "providerSettings",
215
+ "methodName": "onLanguageOrSettingsChange"
212
216
  }, {
213
217
  "propName": "theme",
214
218
  "methodName": "onThemeChange"
@@ -1,6 +1,9 @@
1
+ import enTranslation from "./en.reactodia.translation.json";
1
2
  import frTranslation from "./fr.reactodia.translation.json";
2
3
  import { LanguageKey } from "./language-key";
3
- // English is provided by default from reactodia so we don't need to include it here, unless we override it
4
+ // English is provided by default from reactodia, so the English bundle holds only our own keys and overrides. If
5
+ // A key is missing, reactodia defaults to its own translation.
4
6
  export const TRANSLATIONS = {
7
+ [LanguageKey.EN]: enTranslation,
5
8
  [LanguageKey.FR]: frTranslation
6
9
  };
@@ -0,0 +1,37 @@
1
+ import { createElement, Fragment } from "react";
2
+ import { ToolbarAction, ToolbarActionClearAll, ToolbarActionExport, ToolbarActionOpen, useTranslation, useWorkspace } from "@reactodia/workspace";
3
+ import { service } from "../providers/service/service-inject";
4
+ import { DiagramService } from "../services/diagram/diagram.service";
5
+ import { DiagramFileService } from "../services/diagram-file/diagram-file.service";
6
+ /**
7
+ * The workspace main menu: Reactodia's default actions, followed by the diagram download and upload.
8
+ *
9
+ * Passing a `menu` to the `DefaultWorkspace` replaces its default actions, so they are listed here again.
10
+ * Rendered inside the workspace, so the actions can use the workspace hooks.
11
+ */
12
+ export function GraphwiseMenu() {
13
+ const { model } = useWorkspace();
14
+ const t = useTranslation();
15
+ const diagramFileService = service(DiagramFileService);
16
+ const diagramService = service(DiagramService);
17
+ const downloadDiagram = () => {
18
+ const timestamp = new Date().toISOString().replace(/[-:]|\.\d+Z$/g, '');
19
+ diagramFileService.download(model.exportLayout(), `diagram-${timestamp}${DiagramFileService.FILE_EXTENSION}`);
20
+ };
21
+ const uploadDiagram = async (file) => {
22
+ try {
23
+ const diagram = await diagramFileService.read(file);
24
+ await diagramService.importDiagram(model, model.dataProvider, diagram);
25
+ }
26
+ catch (error) {
27
+ console.error('Failed to upload the diagram', error);
28
+ }
29
+ };
30
+ return createElement(Fragment, null, createElement(ToolbarActionClearAll, {}), createElement(ToolbarActionExport, { kind: 'exportRaster' }), createElement(ToolbarActionExport, { kind: 'exportSvg' }), createElement(ToolbarActionExport, { kind: 'print' }), createElement(ToolbarAction,
31
+ // The same icon as ToolbarActionSave. We dont use that action, since it disables saving on refresh
32
+ { className: 'reactodia-toolbar-action__save', title: t.text('graphwise_toolbar_action.download_diagram.title'), onSelect: downloadDiagram }, t.text('graphwise_toolbar_action.download_diagram.label')), createElement(ToolbarActionOpen, {
33
+ title: t.text('graphwise_toolbar_action.upload_diagram.title'),
34
+ fileAccept: DiagramFileService.FILE_EXTENSION,
35
+ onSelect: (file) => void uploadDiagram(file)
36
+ }, t.text('graphwise_toolbar_action.upload_diagram.label')));
37
+ }
@@ -9,8 +9,9 @@ import { DiagramStorageService } from "./services/diagram-storage/diagram-storag
9
9
  import { EventService } from "./services/event/event.service";
10
10
  import { SubscriptionList } from "./models/subscription-list";
11
11
  import { resolveTypeStyle } from "./styles/type-style.resolver";
12
+ import { GraphwiseMenu } from "./menu/graphwise-menu";
12
13
  /**
13
- * Reactodia ships English as its built-in default bundle, so English needs no override.
14
+ * Reactodia ships English as its built-in default bundle, so the English bundle only adds our own keys.
14
15
  * Other languages are layered on top as partial bundles; unknown codes fall back to English.
15
16
  *
16
17
  * Reactodia never re-reads the translations, so the component must be remounted
@@ -34,8 +35,7 @@ let workspaceContext = null;
34
35
  const subscriptions = new SubscriptionList();
35
36
  /**
36
37
  * Builds a Reactodia {@link SparqlDataProvider} for the given endpoint using the supplied
37
- * query preset. The host owns the query configuration and passes it in via props; when none
38
- * is provided we fall back to Reactodia's generic {@link OwlRdfsSettings} OWL/RDFS preset.
38
+ * query preset. The host owns the query configuration and always passes it in via props.
39
39
  */
40
40
  function createDataProvider(props) {
41
41
  const { currentRepository, config, providerSettings } = props;
@@ -128,10 +128,12 @@ function ReactodiaApp(props) {
128
128
  const dataProvider = createDataProvider(props);
129
129
  const isReload = props.isReload;
130
130
  const savedDiagram = service(DiagramStorageService).load();
131
+ const diagramService = service(DiagramService);
131
132
  if (isReload) {
132
- // Simply reload without using the current state. This may happen when the user switches the language at runtime.
133
- // since there is no existing mechanism to re-translate the UI at runtime (not for the UI labels at least)
134
- await model.importLayout({ dataProvider, diagram: currentDiagram, signal, validateLinks: true });
133
+ // Reload the diagram that is currently on the canvas. This may happen when the host changes the provider
134
+ // settings, or when the user switches the language at runtime, since there is no existing mechanism to
135
+ // re-translate the UI at runtime (not for the UI labels at least)
136
+ await diagramService.importDiagram(model, dataProvider, currentDiagram, signal);
135
137
  }
136
138
  else if ((_a = config.seedIris) === null || _a === void 0 ? void 0 : _a.length) {
137
139
  await seedIrisToCanvas(context, dataProvider, config.seedIris, signal);
@@ -142,7 +144,7 @@ function ReactodiaApp(props) {
142
144
  else {
143
145
  // Restore the previously saved layout when present, otherwise start empty. Either way this binds the
144
146
  // data provider (savedDiagram is undefined -> empty diagram), so the unified search/lookup works.
145
- await model.importLayout({ dataProvider, diagram: savedDiagram, signal, validateLinks: true });
147
+ await diagramService.importDiagram(model, dataProvider, savedDiagram, signal);
146
148
  }
147
149
  }, [language]);
148
150
  /**
@@ -161,7 +163,11 @@ function ReactodiaApp(props) {
161
163
  defaultLanguage: language,
162
164
  translations: translationsForLanguage(language),
163
165
  typeStyleResolver: resolveTypeStyle
164
- }, createElement(DefaultWorkspace, {}));
166
+ }, createElement(DefaultWorkspace, {
167
+ menu: createElement(GraphwiseMenu),
168
+ // place Undo/redo/layout actions in top right corner.
169
+ actionsToolbar: { dock: 'ne' }
170
+ }));
165
171
  }
166
172
  /**
167
173
  * Mounts the Reactodia application into the given container and returns the React root.
@@ -172,8 +178,9 @@ export function mountReactodia(container, props) {
172
178
  return root;
173
179
  }
174
180
  /**
175
- * Re-points the diagram at a new SPARQL endpoint or query preset by recreating it with a
176
- * fresh data provider. The canvas is reset to empty, matching the initial mount.
181
+ * Re-points the diagram at a new SPARQL endpoint by recreating it with a fresh data provider.
182
+ * The canvas is reset to empty, matching the initial mount, since the data of another repository
183
+ * has nothing to do with the current graph.
177
184
  */
178
185
  export async function updateReactodia(props) {
179
186
  if (!workspaceContext) {
@@ -14,6 +14,20 @@ import { Service } from "../../providers/service/service";
14
14
  * Service for diagram related operations and subscriptions
15
15
  */
16
16
  let DiagramService = DiagramService_1 = class DiagramService {
17
+ /**
18
+ * Replaces the diagram content with the given serialization. Element data is requested from the data provider and
19
+ * the links between the imported elements are validated against it.
20
+ *
21
+ * @param model The diagram model to import into.
22
+ * @param dataProvider The data provider to bind to the diagram.
23
+ * @param diagram The serialization to import. When `undefined`, the diagram is cleared.
24
+ * @param signal Optional signal to abort the import.
25
+ */
26
+ importDiagram(model, dataProvider, diagram, signal) {
27
+ // selection is not cleared on import, so we clear it manually to avoid painting the previous selection
28
+ model.setSelection([]);
29
+ return model.importLayout({ dataProvider, diagram, signal, validateLinks: true });
30
+ }
17
31
  /**
18
32
  * Subscribes to diagram changes. On every debounced change the current layout is
19
33
  * exported and passed to the callback.