graphwise-reactodia 0.0.1-TR7 → 0.0.1-TR9

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 (42) hide show
  1. package/README.md +32 -0
  2. package/dist/cjs/graphwise-reactodia.cjs.entry.js +309 -41
  3. package/dist/cjs/index.cjs.js +7 -0
  4. package/dist/collection/components/graphwise-reactodia/graphwise-reactodia.js +10 -10
  5. package/dist/collection/components/graphwise-reactodia/models/clear-diagram-storage-event.js +5 -0
  6. package/dist/collection/components/graphwise-reactodia/models/subscription-list.js +35 -0
  7. package/dist/collection/components/graphwise-reactodia/models/triple.js +1 -0
  8. package/dist/collection/components/graphwise-reactodia/providers/service/service-inject.js +15 -0
  9. package/dist/collection/components/graphwise-reactodia/providers/service/service.js +19 -0
  10. package/dist/collection/components/graphwise-reactodia/providers/service/service.provider.js +20 -0
  11. package/dist/collection/components/graphwise-reactodia/reactodia-app.js +61 -28
  12. package/dist/collection/components/graphwise-reactodia/services/diagram/diagram.service.js +54 -0
  13. package/dist/collection/components/graphwise-reactodia/services/diagram-storage/diagram-storage.service.js +55 -0
  14. package/dist/collection/components/graphwise-reactodia/services/event/event.service.js +35 -0
  15. package/dist/collection/components/graphwise-reactodia/services/window/window.service.js +24 -0
  16. package/dist/collection/components/graphwise-reactodia/testing/storage.mock.js +35 -0
  17. package/dist/collection/index.js +1 -1
  18. package/dist/collection/pages/graphwise-reactodia/main.js +17 -7
  19. package/dist/esm/graphwise-reactodia.entry.js +306 -38
  20. package/dist/esm/index.js +6 -0
  21. package/dist/graphwise-reactodia/graphwise-reactodia.esm.js +1 -1
  22. package/dist/graphwise-reactodia/index.esm.js +1 -0
  23. package/dist/graphwise-reactodia/p-5b907998.entry.js +1 -0
  24. package/dist/types/components/graphwise-reactodia/i18n/translations.d.ts +2 -6
  25. package/dist/types/components/graphwise-reactodia/models/clear-diagram-storage-event.d.ts +5 -0
  26. package/dist/types/components/graphwise-reactodia/models/reactodia-app-props.d.ts +6 -7
  27. package/dist/types/components/graphwise-reactodia/models/reactodia-config.d.ts +8 -6
  28. package/dist/types/components/graphwise-reactodia/models/subscription-list.d.ts +26 -0
  29. package/dist/types/components/graphwise-reactodia/models/subscription.d.ts +4 -0
  30. package/dist/types/components/graphwise-reactodia/models/{reactodia-seed-link.d.ts → triple.d.ts} +2 -6
  31. package/dist/types/components/graphwise-reactodia/providers/service/service-inject.d.ts +13 -0
  32. package/dist/types/components/graphwise-reactodia/providers/service/service.d.ts +28 -0
  33. package/dist/types/components/graphwise-reactodia/providers/service/service.provider.d.ts +16 -0
  34. package/dist/types/components/graphwise-reactodia/services/diagram/diagram.service.d.ts +26 -0
  35. package/dist/types/components/graphwise-reactodia/services/diagram-storage/diagram-storage.service.d.ts +26 -0
  36. package/dist/types/components/graphwise-reactodia/services/event/event.service.d.ts +14 -0
  37. package/dist/types/components/graphwise-reactodia/services/window/window.service.d.ts +7 -0
  38. package/dist/types/components/graphwise-reactodia/testing/storage.mock.d.ts +18 -0
  39. package/dist/types/index.d.ts +1 -1
  40. package/package.json +9 -3
  41. package/dist/graphwise-reactodia/p-eb362faa.entry.js +0 -1
  42. /package/dist/collection/components/graphwise-reactodia/models/{reactodia-seed-link.js → subscription.js} +0 -0
package/README.md CHANGED
@@ -34,6 +34,38 @@ npm run build
34
34
  (`reactodia:install` + `reactodia:compile`), then runs `reactodia:dedupe-react` to remove the fork's own
35
35
  `react`/`react-dom` copies so the bundle ends up with a single React instance.
36
36
 
37
+ ## Seeding the canvas
38
+
39
+ By default the canvas starts empty and the user populates it through the workspace search bar. The host can
40
+ instead pre-populate it on startup by passing seed data through `config` (see `ReactodiaAppProps`). Seeding is
41
+ skipped when an `initialDiagram` is restored (e.g. after a language-change remount). There are two mechanisms,
42
+ handled in `reactodia-app.ts`:
43
+
44
+ ### `seedIris` — lazy seed
45
+
46
+ `config.seedIris` is a list of element(node) IRIs. Each IRI is placed as a placeholder node, then `seedCanvas` calls
47
+ the model's `requestData()` so both the element data **and** its links are fetched from the SPARQL endpoint.
48
+ Use this when the relationships are persisted in the repository and can be resolved lazily.
49
+
50
+ ### `seedGraph` — pre-resolved seed
51
+
52
+ `config.seedGraph` is a list of `Triple` objects (mirroring GraphDB's `rest/explore-graph` shape).
53
+ `seedGraphCanvas` places each `source`/`target` as an element and draws the supplied links directly, then
54
+ requests **element data only** (`requestElementData`) — it does *not* ask the endpoint for links.
55
+
56
+ Use this for the result of a CONSTRUCT/DESCRIBE query, whose relationships are computed and therefore not
57
+ persisted in the DB, so they can't be fetched lazily and must be supplied by the host.
58
+
59
+ `Triple` (`models/triple.ts`):
60
+
61
+ | Field | Description |
62
+ |-----------------|----------------------------------------------------------|
63
+ | `source` | Source element IRI. |
64
+ | `target` | Target element IRI. |
65
+ | `rawPredicates` | Full (absolute) predicate IRIs — used as the link types. |
66
+
67
+ When both are supplied `seedGraph` takes precedence over `seedIris`.
68
+
37
69
  ## Editing the fork
38
70
 
39
71
  Reactodia lives in the `reactodia-workspace/` submodule, which is its own git repo (remote:
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-CII9aUQC.js');
3
+ var index$1 = require('./index-CII9aUQC.js');
4
+ var index = require('./index.cjs.js');
4
5
 
5
6
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
6
7
 
@@ -18579,11 +18580,15 @@ function addWatermark(svg, viewBox, watermarkSvg) {
18579
18580
  image.setAttribute("opacity", "0.3");
18580
18581
  svg.insertBefore(image, svg.firstChild);
18581
18582
  }
18583
+ function stripPseudoElements(selectorText) {
18584
+ if (!selectorText.includes("::")) return selectorText;
18585
+ return selectorText.split(",").map((selector) => selector.replace(/::[a-zA-Z-]+/g, "").trim()).filter(Boolean).join(", ");
18586
+ }
18582
18587
  function collectAppliedCssFromDocument(targetSubtree) {
18583
18588
  const appliedRules = [];
18584
18589
  enumerateStylesheets((rule) => {
18585
- const selectorWithoutPseudo = rule.selectorText.replace(/::[a-zA-Z-]+$/, "");
18586
- if (targetSubtree.querySelector(selectorWithoutPseudo)) appliedRules.push(rule);
18590
+ const selector = stripPseudoElements(rule.selectorText);
18591
+ if (selector && targetSubtree.querySelector(selector)) appliedRules.push(rule);
18587
18592
  });
18588
18593
  return appliedRules;
18589
18594
  }
@@ -30642,6 +30647,241 @@ const TRANSLATIONS = {
30642
30647
  [LanguageKey.FR]: frTranslation
30643
30648
  };
30644
30649
 
30650
+ /**
30651
+ * Provider for all {@link Service} instances. Services are singletons: there is only one
30652
+ * instance of each service, created on demand and cached here so every consumer shares it.
30653
+ */
30654
+ class ServiceProvider {
30655
+ /**
30656
+ * Returns the instance of the given service type, creating and caching it on first request.
30657
+ *
30658
+ * @param type The service type to retrieve.
30659
+ * @returns The shared instance of the service.
30660
+ * @template T The type of the service to retrieve.
30661
+ */
30662
+ static get(type) {
30663
+ if (!ServiceProvider.SERVICE_INSTANCES.has(type.ID)) {
30664
+ ServiceProvider.SERVICE_INSTANCES.set(type.ID, new type());
30665
+ }
30666
+ return ServiceProvider.SERVICE_INSTANCES.get(type.ID);
30667
+ }
30668
+ }
30669
+ ServiceProvider.SERVICE_INSTANCES = new Map();
30670
+
30671
+ /**
30672
+ * Injects the singleton instance of the given service class from the {@link ServiceProvider}.
30673
+ *
30674
+ * @param serviceClass The class of the service to inject.
30675
+ * @returns The shared instance of the specified service class.
30676
+ * @template T The type of the service to inject.
30677
+ * @example
30678
+ * ```typescript
30679
+ * const diagramStateService = service(DiagramStateService);
30680
+ * ```
30681
+ */
30682
+ function service(serviceClass) {
30683
+ return ServiceProvider.get(serviceClass);
30684
+ }
30685
+
30686
+ /**
30687
+ * Decorator for a service class.
30688
+ * It receives the class and simply type checks it with the {@link Service} interface.
30689
+ * Works as a contract, which enforces the service class to have a static ID member.
30690
+ * We can't simply implement the interface, since this will enforce the ID to be an instance member, which is not what we want.
30691
+ *
30692
+ * @param _serviceClass The decorated service class, checked for its ID and discarded.
30693
+ * @template T The type of the service instance.
30694
+ * @example
30695
+ * ```TypeScript
30696
+ * @Service
30697
+ * export class WindowService {
30698
+ * static readonly ID = 'WindowService';
30699
+ * }
30700
+ * ```
30701
+ */
30702
+ function Service(_serviceClass) {
30703
+ // no implementation needed. We only use the type checking of the parameter
30704
+ }
30705
+
30706
+ var __decorate$3 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
30707
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
30708
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
30709
+ 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;
30710
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
30711
+ };
30712
+ var DiagramService_1;
30713
+ /**
30714
+ * Service for diagram related operations and subscriptions
30715
+ */
30716
+ let DiagramService = DiagramService_1 = class DiagramService {
30717
+ /**
30718
+ * Subscribes to diagram changes. On every debounced change the current layout is
30719
+ * exported and passed to the callback.
30720
+ * Debounce time for the callback trigger is needed, because certain events (like moving a node) can trigger
30721
+ * multiple `historyChanged` events in a short time.
30722
+ *
30723
+ * @param model The diagram model to observe.
30724
+ * @param onChangeCallback Called with the exported layout when the diagram changes.
30725
+ * @returns A {@link Subscription} that, when called, removes the listener and drops any
30726
+ * pending notification.
30727
+ */
30728
+ subscribeToDiagramChange(model, onChangeCallback) {
30729
+ let notifyTimer;
30730
+ const onHistoryChange = () => {
30731
+ if (notifyTimer) {
30732
+ clearTimeout(notifyTimer);
30733
+ }
30734
+ notifyTimer = setTimeout(() => {
30735
+ notifyTimer = undefined;
30736
+ onChangeCallback(model.exportLayout());
30737
+ }, DiagramService_1.NOTIFY_DEBOUNCE_MS);
30738
+ };
30739
+ model.history.events.on(DiagramService_1.HISTORY_CHANGED_EVENT, onHistoryChange);
30740
+ return () => {
30741
+ model.history.events.off(DiagramService_1.HISTORY_CHANGED_EVENT, onHistoryChange);
30742
+ if (notifyTimer) {
30743
+ clearTimeout(notifyTimer);
30744
+ }
30745
+ };
30746
+ }
30747
+ };
30748
+ DiagramService.ID = 'DiagramService';
30749
+ DiagramService.NOTIFY_DEBOUNCE_MS = 300;
30750
+ DiagramService.HISTORY_CHANGED_EVENT = 'historyChanged';
30751
+ DiagramService = DiagramService_1 = __decorate$3([
30752
+ Service
30753
+ ], DiagramService);
30754
+
30755
+ var __decorate$2 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
30756
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
30757
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
30758
+ 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;
30759
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
30760
+ };
30761
+ /**
30762
+ * Service that provides access to browser window-related functionality.
30763
+ */
30764
+ let WindowService = class WindowService {
30765
+ getWindow() {
30766
+ return window;
30767
+ }
30768
+ };
30769
+ WindowService.ID = 'WindowService';
30770
+ WindowService = __decorate$2([
30771
+ Service
30772
+ ], WindowService);
30773
+
30774
+ var __decorate$1 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
30775
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
30776
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
30777
+ 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;
30778
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
30779
+ };
30780
+ var DiagramStorageService_1;
30781
+ /**
30782
+ * Service for local store-related diagram operations
30783
+ */
30784
+ let DiagramStorageService = DiagramStorageService_1 = class DiagramStorageService {
30785
+ /**
30786
+ * Load the persisted diagram from the local store, or `undefined` when nothing is stored (or the stored value
30787
+ * is unreadable).
30788
+ */
30789
+ load() {
30790
+ const raw = this.storage.getItem(DiagramStorageService_1.STORAGE_KEY);
30791
+ if (!raw) {
30792
+ return undefined;
30793
+ }
30794
+ return JSON.parse(raw);
30795
+ }
30796
+ /**
30797
+ * Persists the given diagram serialization.
30798
+ */
30799
+ save(diagram) {
30800
+ this.storage.setItem(DiagramStorageService_1.STORAGE_KEY, JSON.stringify(diagram));
30801
+ }
30802
+ /**
30803
+ * Removes the persisted diagram. Used when a new view (a seed) should take precedence over
30804
+ * previously saved edits.
30805
+ */
30806
+ clear() {
30807
+ this.storage.removeItem(DiagramStorageService_1.STORAGE_KEY);
30808
+ }
30809
+ /**
30810
+ * Local storage getter
30811
+ */
30812
+ get storage() {
30813
+ return service(WindowService).getWindow().localStorage;
30814
+ }
30815
+ };
30816
+ DiagramStorageService.ID = 'DiagramStorageService';
30817
+ DiagramStorageService.STORAGE_KEY = 'diagram.state';
30818
+ DiagramStorageService = DiagramStorageService_1 = __decorate$1([
30819
+ Service
30820
+ ], DiagramStorageService);
30821
+
30822
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
30823
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
30824
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
30825
+ 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;
30826
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
30827
+ };
30828
+ /**
30829
+ * Service for event based operations
30830
+ */
30831
+ let EventService = class EventService {
30832
+ /**
30833
+ * Subscribes to the host's "clear persisted diagram" command.
30834
+ *
30835
+ * @param onClear Called whenever the event fires.
30836
+ * @returns A {@link Subscription} that, when called, removes the listener.
30837
+ */
30838
+ subscribeToClearDiagramStorage(onClear) {
30839
+ const target = service(WindowService).getWindow();
30840
+ target.addEventListener(index.CLEAR_DIAGRAM_STORAGE_EVENT, onClear);
30841
+ return () => target.removeEventListener(index.CLEAR_DIAGRAM_STORAGE_EVENT, onClear);
30842
+ }
30843
+ };
30844
+ EventService.ID = 'EventService';
30845
+ EventService = __decorate([
30846
+ Service
30847
+ ], EventService);
30848
+
30849
+ /**
30850
+ * A collection of {@link Subscription}s that can be released together. Components collect the
30851
+ * subscriptions they open and call {@link unsubscribeAll} on teardown so nothing is left
30852
+ * listening.
30853
+ */
30854
+ class SubscriptionList {
30855
+ constructor(subscriptions = []) {
30856
+ this.items = subscriptions;
30857
+ }
30858
+ /**
30859
+ * Adds a subscription to the list.
30860
+ */
30861
+ add(subscription) {
30862
+ this.items.push(subscription);
30863
+ }
30864
+ /**
30865
+ * Adds multiple subscriptions to the list.
30866
+ */
30867
+ addAll(subscriptions) {
30868
+ this.items.push(...subscriptions);
30869
+ }
30870
+ /**
30871
+ * Removes a subscription from the list without unsubscribing it.
30872
+ */
30873
+ remove(subscription) {
30874
+ this.items = this.items.filter((sub) => sub !== subscription);
30875
+ }
30876
+ /**
30877
+ * Calls every subscription (unsubscribing it) and clears the list.
30878
+ */
30879
+ unsubscribeAll() {
30880
+ this.items.forEach((subscription) => subscription());
30881
+ this.items = [];
30882
+ }
30883
+ }
30884
+
30645
30885
  /**
30646
30886
  * Reactodia ships English as its built-in default bundle, so English needs no override.
30647
30887
  * Other languages are layered on top as partial bundles; unknown codes fall back to English.
@@ -30659,6 +30899,12 @@ function translationsForLanguage(language) {
30659
30899
  * runs outside React and therefore cannot call the `useWorkspace` hook.
30660
30900
  */
30661
30901
  let workspaceContext = null;
30902
+ /**
30903
+ * Active listeners that persist diagram edits to local storage. Populated when the workspace
30904
+ * mounts and released in {@link unmountReactodia}, so persistence follows the same lifecycle as
30905
+ * the React root.
30906
+ */
30907
+ const subscriptions = new SubscriptionList();
30662
30908
  /**
30663
30909
  * Builds a Reactodia {@link SparqlDataProvider} for the given endpoint using the supplied
30664
30910
  * query preset. The host owns the query configuration and passes it in via props; when none
@@ -30669,15 +30915,16 @@ function createDataProvider(props) {
30669
30915
  return new SparqlDataProvider({
30670
30916
  endpointUrl: currentRepository,
30671
30917
  queryMethod: 'POST',
30672
- queryFunction: config.queryFunction,
30918
+ queryFunction: config.queryFunction
30673
30919
  }, providerSettings);
30674
30920
  }
30675
30921
  /**
30676
30922
  * Places the seed entities on the canvas. Each IRI is added as a placeholder element, and data is then loaded for the
30677
30923
  * respective nodes using the {@link DataDiagramModel.requestData} method.
30678
30924
  */
30679
- async function seedCanvas(context, seed, signal) {
30925
+ async function seedIrisToCanvas(context, dataProvider, seed, signal) {
30680
30926
  const { model, performLayout } = context;
30927
+ await model.createNewDiagram({ dataProvider, signal });
30681
30928
  for (const iri of seed) {
30682
30929
  model.createElement(iri);
30683
30930
  }
@@ -30685,18 +30932,13 @@ async function seedCanvas(context, seed, signal) {
30685
30932
  await performLayout({ signal });
30686
30933
  }
30687
30934
  /**
30688
- * Places a pre-resolved graph (e.g. a CONSTRUCT query result) on the canvas. The relationships
30689
- * are computed and not persisted in the repository, so the edges are drawn directly from the
30690
- * supplied data instead of being fetched. The node IRIs do exist in the repository, so only
30691
- * their display data is hydrated via {@link DataDiagramModel.requestElementData}.
30692
- *
30693
- * Note we use `requestElementData` rather than `requestData`: the latter would also query the
30694
- * provider for *all* persisted links between the seeded nodes, over-fetching beyond the
30695
- * CONSTRUCT's selection. Hydrating elements alone keeps the canvas to exactly the supplied
30696
- * edges, while further expansion continues lazily against the SPARQL provider.
30935
+ * Places a pre-resolved graph (e.g. a CONSTRUCT query result) on the canvas. The difference between this and
30936
+ * {@link seedIrisToCanvas} is that this method does not request link data from the SPARQL endpoint, since describe/construct
30937
+ * queries are not persisted in the DB. Here we expect provided links and query for element data only
30697
30938
  */
30698
- async function seedGraphCanvas(context, links, signal) {
30939
+ async function seedGraphToCanvas(context, dataProvider, links, signal) {
30699
30940
  const { model, performLayout } = context;
30941
+ await model.createNewDiagram({ dataProvider, signal });
30700
30942
  const uniqueIris = new Set();
30701
30943
  links.forEach((link) => {
30702
30944
  var _a;
@@ -30714,11 +30956,30 @@ async function seedGraphCanvas(context, links, signal) {
30714
30956
  });
30715
30957
  });
30716
30958
  });
30717
- // Request data only for elements.
30718
- // Link data is not available in the DB, since we are displaying construct/describe queries
30719
30959
  await model.requestElementData(Array.from(uniqueIris));
30720
30960
  await performLayout({ signal });
30721
30961
  }
30962
+ /**
30963
+ * Subscribes to the model's changes so diagram edits are persisted to local storage. The
30964
+ * subscription is tracked in {@link subscriptions} so it is released together with the React
30965
+ * root in {@link unmountReactodia}.
30966
+ */
30967
+ function onDiagramChange(model) {
30968
+ const storage = service(DiagramStorageService);
30969
+ const diagramService = service(DiagramService);
30970
+ subscriptions.add(diagramService.subscribeToDiagramChange(model, (diagram) => storage.save(diagram)));
30971
+ }
30972
+ /**
30973
+ * Subscribes to the host's "clear persisted diagram" command so the saved layout is dropped on
30974
+ * request (e.g. before a fresh seed should take precedence over previously saved edits). The
30975
+ * subscription is tracked in {@link subscriptions} so it is released together with the React
30976
+ * root in {@link unmountReactodia}.
30977
+ */
30978
+ function onClearDiagramStorage() {
30979
+ const storage = service(DiagramStorageService);
30980
+ const eventService = service(EventService);
30981
+ subscriptions.add(eventService.subscribeToClearDiagramStorage(() => storage.clear()));
30982
+ }
30722
30983
  /**
30723
30984
  * The Reactodia workspace React component.
30724
30985
  *
@@ -30729,26 +30990,32 @@ async function seedGraphCanvas(context, links, signal) {
30729
30990
  * provider's lookup. With a seed, the seeded nodes are placed on the canvas on startup.
30730
30991
  */
30731
30992
  function ReactodiaApp(props) {
30732
- const { language, initialDiagram, config } = props;
30993
+ const { language, config } = props;
30994
+ const currentDiagram = props.isReload ? exportReactodiaLayout() : undefined;
30733
30995
  const { onMount } = useLoadedWorkspace(async ({ context, signal }) => {
30734
30996
  var _a, _b;
30735
30997
  workspaceContext = context;
30736
30998
  const { model } = context;
30999
+ onDiagramChange(model);
31000
+ onClearDiagramStorage();
30737
31001
  const dataProvider = createDataProvider(props);
30738
- // A language change remounts this component to rebuild the workspace with the new
30739
- // translation bundle; restoring the exported diagram keeps the user's canvas intact.
30740
- // This is done because there is no existing mechanism to re-translate the UI at runtime (not for the UI labels at least)
30741
- if (initialDiagram) {
30742
- await model.importLayout({ dataProvider, diagram: initialDiagram, signal });
31002
+ const isReload = props.isReload;
31003
+ const savedDiagram = service(DiagramStorageService).load();
31004
+ if (isReload) {
31005
+ // Simply reload without using the current state. This may happen when the user switches the language at runtime.
31006
+ // since there is no existing mechanism to re-translate the UI at runtime (not for the UI labels at least)
31007
+ await model.importLayout({ dataProvider, diagram: currentDiagram, signal });
31008
+ }
31009
+ else if ((_a = config.seedIris) === null || _a === void 0 ? void 0 : _a.length) {
31010
+ await seedIrisToCanvas(context, dataProvider, config.seedIris, signal);
31011
+ }
31012
+ else if ((_b = config.seedGraph) === null || _b === void 0 ? void 0 : _b.length) {
31013
+ await seedGraphToCanvas(context, dataProvider, config.seedGraph, signal);
30743
31014
  }
30744
31015
  else {
30745
- await model.createNewDiagram({ dataProvider, signal });
30746
- if ((_a = config.seedGraph) === null || _a === void 0 ? void 0 : _a.length) {
30747
- await seedGraphCanvas(context, config.seedGraph, signal);
30748
- }
30749
- else if ((_b = config.seedIris) === null || _b === void 0 ? void 0 : _b.length) {
30750
- await seedCanvas(context, config.seedIris, signal);
30751
- }
31016
+ // Restore the previously saved layout when present, otherwise start empty. Either way this binds the
31017
+ // data provider (savedDiagram is undefined -> empty diagram), so the unified search/lookup works.
31018
+ await model.importLayout({ dataProvider, diagram: savedDiagram, signal });
30752
31019
  }
30753
31020
  }, [language]);
30754
31021
  /**
@@ -30799,6 +31066,7 @@ function exportReactodiaLayout() {
30799
31066
  * Unmounts the Reactodia application.
30800
31067
  */
30801
31068
  function unmountReactodia(root) {
31069
+ subscriptions.unsubscribeAll();
30802
31070
  root.unmount();
30803
31071
  }
30804
31072
 
@@ -30806,7 +31074,7 @@ const graphwiseReactodiaCss = "graphwise-reactodia{display:block;width:100%;heig
30806
31074
 
30807
31075
  const GraphwiseReactodia = class {
30808
31076
  constructor(hostRef) {
30809
- index.registerInstance(this, hostRef);
31077
+ index$1.registerInstance(this, hostRef);
30810
31078
  /**
30811
31079
  * UI language code (e.g. `en`, `fr`) for the Reactodia interface. Defaults to English.
30812
31080
  */
@@ -30817,13 +31085,13 @@ const GraphwiseReactodia = class {
30817
31085
  }
30818
31086
  onLanguageChange() {
30819
31087
  // The translations are evaluated by the workspace at construction, so the only way
30820
- // to re-translate the UI is to re-create the graph. We carry the
30821
- // current diagram across the remount so switching languages does not wipe the canvas.
30822
- const diagram = exportReactodiaLayout();
30823
- this.renderGraph(diagram);
31088
+ // to re-translate the UI is to re-create the graph.
31089
+ this.renderGraph(true);
30824
31090
  }
30825
31091
  connectedCallback() {
30826
- this.renderGraph();
31092
+ if (this.config) {
31093
+ this.renderGraph();
31094
+ }
30827
31095
  }
30828
31096
  disconnectedCallback() {
30829
31097
  if (this.reactRoot) {
@@ -30832,9 +31100,9 @@ const GraphwiseReactodia = class {
30832
31100
  }
30833
31101
  }
30834
31102
  render() {
30835
- return (index.h(index.Host, { key: '63c58ee1d28c4866aa66d3dcf4893879de4d09f6' }));
31103
+ return (index$1.h(index$1.Host, { key: 'b33d3709a5d7b1fdde8d528d036d9dbc962d6c76' }));
30836
31104
  }
30837
- renderGraph(initialDiagram) {
31105
+ renderGraph(isReloading) {
30838
31106
  var _a;
30839
31107
  if (!this.currentRepository) {
30840
31108
  throw new Error('currentRepository is required');
@@ -30846,20 +31114,20 @@ const GraphwiseReactodia = class {
30846
31114
  return;
30847
31115
  }
30848
31116
  const props = {
30849
- initialDiagram,
31117
+ isReload: isReloading,
30850
31118
  currentRepository: this.currentRepository,
30851
31119
  config: this.config,
30852
31120
  language: this.language,
30853
31121
  providerSettings: this.providerSettings,
30854
31122
  };
30855
- if (this.reactRoot && !initialDiagram) {
31123
+ if (this.reactRoot && !isReloading) {
30856
31124
  void updateReactodia(props);
30857
31125
  }
30858
31126
  else {
30859
31127
  this.reactRoot = mountReactodia(this.hostElement, props);
30860
31128
  }
30861
31129
  }
30862
- get hostElement() { return index.getElement(this); }
31130
+ get hostElement() { return index$1.getElement(this); }
30863
31131
  static get watchers() { return {
30864
31132
  "currentRepository": ["onProviderSettingsChange"],
30865
31133
  "providerSettings": ["onProviderSettingsChange"],
@@ -1,2 +1,9 @@
1
1
  'use strict';
2
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
+ exports.CLEAR_DIAGRAM_STORAGE_EVENT = CLEAR_DIAGRAM_STORAGE_EVENT;
@@ -1,5 +1,5 @@
1
1
  import { h, Host } from "@stencil/core";
2
- import { exportReactodiaLayout, mountReactodia, unmountReactodia, updateReactodia } from "./reactodia-app";
2
+ import { mountReactodia, unmountReactodia, updateReactodia } from "./reactodia-app";
3
3
  import { LanguageKey } from "./i18n/language-key";
4
4
  /**
5
5
  * A web component that renders a graph with the Reactodia workspace.
@@ -24,13 +24,13 @@ export class GraphwiseReactodia {
24
24
  }
25
25
  onLanguageChange() {
26
26
  // The translations are evaluated by the workspace at construction, so the only way
27
- // to re-translate the UI is to re-create the graph. We carry the
28
- // current diagram across the remount so switching languages does not wipe the canvas.
29
- const diagram = exportReactodiaLayout();
30
- this.renderGraph(diagram);
27
+ // to re-translate the UI is to re-create the graph.
28
+ this.renderGraph(true);
31
29
  }
32
30
  connectedCallback() {
33
- this.renderGraph();
31
+ if (this.config) {
32
+ this.renderGraph();
33
+ }
34
34
  }
35
35
  disconnectedCallback() {
36
36
  if (this.reactRoot) {
@@ -39,9 +39,9 @@ export class GraphwiseReactodia {
39
39
  }
40
40
  }
41
41
  render() {
42
- return (h(Host, { key: '63c58ee1d28c4866aa66d3dcf4893879de4d09f6' }));
42
+ return (h(Host, { key: 'b33d3709a5d7b1fdde8d528d036d9dbc962d6c76' }));
43
43
  }
44
- renderGraph(initialDiagram) {
44
+ renderGraph(isReloading) {
45
45
  var _a;
46
46
  if (!this.currentRepository) {
47
47
  throw new Error('currentRepository is required');
@@ -53,13 +53,13 @@ export class GraphwiseReactodia {
53
53
  return;
54
54
  }
55
55
  const props = {
56
- initialDiagram,
56
+ isReload: isReloading,
57
57
  currentRepository: this.currentRepository,
58
58
  config: this.config,
59
59
  language: this.language,
60
60
  providerSettings: this.providerSettings,
61
61
  };
62
- if (this.reactRoot && !initialDiagram) {
62
+ if (this.reactRoot && !isReloading) {
63
63
  void updateReactodia(props);
64
64
  }
65
65
  else {
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Event name for clearing the local store state of the diagram. There is an event listener listening for it, which
3
+ * will clear the local store state, whenever this event is fired.
4
+ */
5
+ export const CLEAR_DIAGRAM_STORAGE_EVENT = 'graphwise-reactodia:clear-diagram-storage';
@@ -0,0 +1,35 @@
1
+ /**
2
+ * A collection of {@link Subscription}s that can be released together. Components collect the
3
+ * subscriptions they open and call {@link unsubscribeAll} on teardown so nothing is left
4
+ * listening.
5
+ */
6
+ export class SubscriptionList {
7
+ constructor(subscriptions = []) {
8
+ this.items = subscriptions;
9
+ }
10
+ /**
11
+ * Adds a subscription to the list.
12
+ */
13
+ add(subscription) {
14
+ this.items.push(subscription);
15
+ }
16
+ /**
17
+ * Adds multiple subscriptions to the list.
18
+ */
19
+ addAll(subscriptions) {
20
+ this.items.push(...subscriptions);
21
+ }
22
+ /**
23
+ * Removes a subscription from the list without unsubscribing it.
24
+ */
25
+ remove(subscription) {
26
+ this.items = this.items.filter((sub) => sub !== subscription);
27
+ }
28
+ /**
29
+ * Calls every subscription (unsubscribing it) and clears the list.
30
+ */
31
+ unsubscribeAll() {
32
+ this.items.forEach((subscription) => subscription());
33
+ this.items = [];
34
+ }
35
+ }
@@ -0,0 +1,15 @@
1
+ import { ServiceProvider } from "./service.provider";
2
+ /**
3
+ * Injects the singleton instance of the given service class from the {@link ServiceProvider}.
4
+ *
5
+ * @param serviceClass The class of the service to inject.
6
+ * @returns The shared instance of the specified service class.
7
+ * @template T The type of the service to inject.
8
+ * @example
9
+ * ```typescript
10
+ * const diagramStateService = service(DiagramStateService);
11
+ * ```
12
+ */
13
+ export function service(serviceClass) {
14
+ return ServiceProvider.get(serviceClass);
15
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Decorator for a service class.
3
+ * It receives the class and simply type checks it with the {@link Service} interface.
4
+ * Works as a contract, which enforces the service class to have a static ID member.
5
+ * We can't simply implement the interface, since this will enforce the ID to be an instance member, which is not what we want.
6
+ *
7
+ * @param _serviceClass The decorated service class, checked for its ID and discarded.
8
+ * @template T The type of the service instance.
9
+ * @example
10
+ * ```TypeScript
11
+ * @Service
12
+ * export class WindowService {
13
+ * static readonly ID = 'WindowService';
14
+ * }
15
+ * ```
16
+ */
17
+ export function Service(_serviceClass) {
18
+ // no implementation needed. We only use the type checking of the parameter
19
+ }