graphwise-reactodia 0.0.1-TR6 → 0.0.1-TR8

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 (43) hide show
  1. package/README.md +32 -0
  2. package/dist/cjs/graphwise-reactodia.cjs.entry.js +281 -27
  3. package/dist/cjs/index.cjs.js +7 -0
  4. package/dist/collection/components/graphwise-reactodia/graphwise-reactodia.js +11 -11
  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/subscription.js +1 -0
  8. package/dist/collection/components/graphwise-reactodia/models/triple.js +1 -0
  9. package/dist/collection/components/graphwise-reactodia/providers/service/service-inject.js +15 -0
  10. package/dist/collection/components/graphwise-reactodia/providers/service/service.js +1 -0
  11. package/dist/collection/components/graphwise-reactodia/providers/service/service.provider.js +20 -0
  12. package/dist/collection/components/graphwise-reactodia/reactodia-app.js +85 -13
  13. package/dist/collection/components/graphwise-reactodia/services/diagram/diagram.service.js +39 -0
  14. package/dist/collection/components/graphwise-reactodia/services/diagram-storage/diagram-storage.service.js +42 -0
  15. package/dist/collection/components/graphwise-reactodia/services/event/event.service.js +22 -0
  16. package/dist/collection/components/graphwise-reactodia/services/window/window.service.js +8 -0
  17. package/dist/collection/components/graphwise-reactodia/testing/storage.mock.js +35 -0
  18. package/dist/collection/index.js +1 -1
  19. package/dist/collection/pages/graphwise-reactodia/main.js +29 -4
  20. package/dist/esm/graphwise-reactodia.entry.js +278 -24
  21. package/dist/esm/index.js +6 -0
  22. package/dist/graphwise-reactodia/graphwise-reactodia.esm.js +1 -1
  23. package/dist/graphwise-reactodia/index.esm.js +1 -0
  24. package/dist/graphwise-reactodia/p-80cb6eef.entry.js +1 -0
  25. package/dist/types/components/graphwise-reactodia/graphwise-reactodia.d.ts +1 -1
  26. package/dist/types/components/graphwise-reactodia/i18n/translations.d.ts +2 -6
  27. package/dist/types/components/graphwise-reactodia/models/clear-diagram-storage-event.d.ts +5 -0
  28. package/dist/types/components/graphwise-reactodia/models/reactodia-app-props.d.ts +6 -7
  29. package/dist/types/components/graphwise-reactodia/models/reactodia-config.d.ts +10 -2
  30. package/dist/types/components/graphwise-reactodia/models/subscription-list.d.ts +26 -0
  31. package/dist/types/components/graphwise-reactodia/models/subscription.d.ts +4 -0
  32. package/dist/types/components/graphwise-reactodia/models/triple.d.ts +19 -0
  33. package/dist/types/components/graphwise-reactodia/providers/service/service-inject.d.ts +13 -0
  34. package/dist/types/components/graphwise-reactodia/providers/service/service.d.ts +6 -0
  35. package/dist/types/components/graphwise-reactodia/providers/service/service.provider.d.ts +18 -0
  36. package/dist/types/components/graphwise-reactodia/services/diagram/diagram.service.d.ts +28 -0
  37. package/dist/types/components/graphwise-reactodia/services/diagram-storage/diagram-storage.service.d.ts +26 -0
  38. package/dist/types/components/graphwise-reactodia/services/event/event.service.d.ts +14 -0
  39. package/dist/types/components/graphwise-reactodia/services/window/window.service.d.ts +7 -0
  40. package/dist/types/components/graphwise-reactodia/testing/storage.mock.d.ts +18 -0
  41. package/dist/types/index.d.ts +1 -1
  42. package/package.json +9 -3
  43. package/dist/graphwise-reactodia/p-5a330bae.entry.js +0 -1
@@ -1,4 +1,5 @@
1
1
  import { r as registerInstance, h, H as Host, g as getElement } from './index-Cypw7ydc.js';
2
+ import { CLEAR_DIAGRAM_STORAGE_EVENT } from './index.js';
2
3
 
3
4
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
4
5
 
@@ -18577,11 +18578,15 @@ function addWatermark(svg, viewBox, watermarkSvg) {
18577
18578
  image.setAttribute("opacity", "0.3");
18578
18579
  svg.insertBefore(image, svg.firstChild);
18579
18580
  }
18581
+ function stripPseudoElements(selectorText) {
18582
+ if (!selectorText.includes("::")) return selectorText;
18583
+ return selectorText.split(",").map((selector) => selector.replace(/::[a-zA-Z-]+/g, "").trim()).filter(Boolean).join(", ");
18584
+ }
18580
18585
  function collectAppliedCssFromDocument(targetSubtree) {
18581
18586
  const appliedRules = [];
18582
18587
  enumerateStylesheets((rule) => {
18583
- const selectorWithoutPseudo = rule.selectorText.replace(/::[a-zA-Z-]+$/, "");
18584
- if (targetSubtree.querySelector(selectorWithoutPseudo)) appliedRules.push(rule);
18588
+ const selector = stripPseudoElements(rule.selectorText);
18589
+ if (selector && targetSubtree.querySelector(selector)) appliedRules.push(rule);
18585
18590
  });
18586
18591
  return appliedRules;
18587
18592
  }
@@ -30640,6 +30645,188 @@ const TRANSLATIONS = {
30640
30645
  [LanguageKey.FR]: frTranslation
30641
30646
  };
30642
30647
 
30648
+ /**
30649
+ * Provider for all {@link Service} instances. Services are singletons: there is only one
30650
+ * instance of each service, created on demand and cached here so every consumer shares it.
30651
+ */
30652
+ class ServiceProvider {
30653
+ /**
30654
+ * Returns the instance of the given service type, creating and caching it on first request.
30655
+ *
30656
+ * @param type The service type to retrieve.
30657
+ * @returns The shared instance of the service.
30658
+ * @template T The type of the service to retrieve.
30659
+ */
30660
+ static get(type) {
30661
+ if (!ServiceProvider.SERVICE_INSTANCES.has(type.name)) {
30662
+ ServiceProvider.SERVICE_INSTANCES.set(type.name, new type());
30663
+ }
30664
+ return ServiceProvider.SERVICE_INSTANCES.get(type.name);
30665
+ }
30666
+ }
30667
+ ServiceProvider.SERVICE_INSTANCES = new Map();
30668
+
30669
+ /**
30670
+ * Injects the singleton instance of the given service class from the {@link ServiceProvider}.
30671
+ *
30672
+ * @param serviceClass The class of the service to inject.
30673
+ * @returns The shared instance of the specified service class.
30674
+ * @template T The type of the service to inject.
30675
+ * @example
30676
+ * ```typescript
30677
+ * const diagramStateService = service(DiagramStateService);
30678
+ * ```
30679
+ */
30680
+ function service(serviceClass) {
30681
+ return ServiceProvider.get(serviceClass);
30682
+ }
30683
+
30684
+ /**
30685
+ * Service for diagram related operations and subscriptions
30686
+ */
30687
+ class DiagramService {
30688
+ /**
30689
+ * Subscribes to diagram changes. On every debounced change the current layout is
30690
+ * exported and passed to the callback
30691
+ *
30692
+ * @param model The diagram model to observe.
30693
+ * @param onChangeCallback Called with the exported layout when the diagram changes.
30694
+ * @returns A {@link Subscription} that, when called, removes the listener and drops any
30695
+ * pending notification.
30696
+ */
30697
+ subscribeToDiagramChange(model, onChangeCallback) {
30698
+ let notifyTimer;
30699
+ const onHistoryChange = () => {
30700
+ if (notifyTimer) {
30701
+ clearTimeout(notifyTimer);
30702
+ }
30703
+ notifyTimer = setTimeout(() => {
30704
+ notifyTimer = undefined;
30705
+ onChangeCallback(model.exportLayout());
30706
+ }, DiagramService.NOTIFY_DEBOUNCE_MS);
30707
+ };
30708
+ model.history.events.on(DiagramService.HISTORY_CHANGED_EVENT, onHistoryChange);
30709
+ return () => {
30710
+ model.history.events.off(DiagramService.HISTORY_CHANGED_EVENT, onHistoryChange);
30711
+ if (notifyTimer) {
30712
+ clearTimeout(notifyTimer);
30713
+ }
30714
+ };
30715
+ }
30716
+ }
30717
+ /**
30718
+ * Debounce time for the callback trigger. This is needed, because certain events (like moving a node) can trigger
30719
+ * multiple `historyChanged` events in a short time.
30720
+ */
30721
+ DiagramService.NOTIFY_DEBOUNCE_MS = 300;
30722
+ DiagramService.HISTORY_CHANGED_EVENT = 'historyChanged';
30723
+
30724
+ /**
30725
+ * Service that provides access to browser window-related functionality.
30726
+ */
30727
+ class WindowService {
30728
+ getWindow() {
30729
+ return window;
30730
+ }
30731
+ }
30732
+
30733
+ /**
30734
+ * Service for local store-related diagram operations
30735
+ */
30736
+ class DiagramStorageService {
30737
+ /**
30738
+ * Load the persisted diagram from the local store, or `undefined` when nothing is stored (or the stored value
30739
+ * is unreadable).
30740
+ */
30741
+ load() {
30742
+ var _a;
30743
+ const raw = (_a = this.storage) === null || _a === void 0 ? void 0 : _a.getItem(DiagramStorageService.STORAGE_KEY);
30744
+ if (!raw) {
30745
+ return undefined;
30746
+ }
30747
+ return JSON.parse(raw);
30748
+ }
30749
+ /**
30750
+ * Persists the given diagram serialization.
30751
+ */
30752
+ save(diagram) {
30753
+ var _a;
30754
+ (_a = this.storage) === null || _a === void 0 ? void 0 : _a.setItem(DiagramStorageService.STORAGE_KEY, JSON.stringify(diagram));
30755
+ }
30756
+ /**
30757
+ * Removes the persisted diagram. Used when a new view (a seed) should take precedence over
30758
+ * previously saved edits.
30759
+ */
30760
+ clear() {
30761
+ var _a;
30762
+ (_a = this.storage) === null || _a === void 0 ? void 0 : _a.removeItem(DiagramStorageService.STORAGE_KEY);
30763
+ }
30764
+ /**
30765
+ * Local storage getter
30766
+ */
30767
+ get storage() {
30768
+ var _a;
30769
+ return (_a = service(WindowService).getWindow()) === null || _a === void 0 ? void 0 : _a.localStorage;
30770
+ }
30771
+ }
30772
+ DiagramStorageService.STORAGE_KEY = 'diagram.state';
30773
+
30774
+ /**
30775
+ * Service for event based operations
30776
+ */
30777
+ class EventService {
30778
+ /**
30779
+ * Subscribes to the host's "clear persisted diagram" command.
30780
+ *
30781
+ * @param onClear Called whenever the event fires.
30782
+ * @returns A {@link Subscription} that, when called, removes the listener.
30783
+ */
30784
+ subscribeToClearDiagramStorage(onClear) {
30785
+ const target = service(WindowService).getWindow();
30786
+ if (!target) {
30787
+ return () => undefined;
30788
+ }
30789
+ target.addEventListener(CLEAR_DIAGRAM_STORAGE_EVENT, onClear);
30790
+ return () => target.removeEventListener(CLEAR_DIAGRAM_STORAGE_EVENT, onClear);
30791
+ }
30792
+ }
30793
+
30794
+ /**
30795
+ * A collection of {@link Subscription}s that can be released together. Components collect the
30796
+ * subscriptions they open and call {@link unsubscribeAll} on teardown so nothing is left
30797
+ * listening.
30798
+ */
30799
+ class SubscriptionList {
30800
+ constructor(subscriptions = []) {
30801
+ this.items = subscriptions;
30802
+ }
30803
+ /**
30804
+ * Adds a subscription to the list.
30805
+ */
30806
+ add(subscription) {
30807
+ this.items.push(subscription);
30808
+ }
30809
+ /**
30810
+ * Adds multiple subscriptions to the list.
30811
+ */
30812
+ addAll(subscriptions) {
30813
+ this.items.push(...subscriptions);
30814
+ }
30815
+ /**
30816
+ * Removes a subscription from the list without unsubscribing it.
30817
+ */
30818
+ remove(subscription) {
30819
+ this.items = this.items.filter((sub) => sub !== subscription);
30820
+ }
30821
+ /**
30822
+ * Calls every subscription (unsubscribing it) and clears the list.
30823
+ */
30824
+ unsubscribeAll() {
30825
+ this.items.forEach((subscription) => subscription());
30826
+ this.items = [];
30827
+ }
30828
+ }
30829
+
30643
30830
  /**
30644
30831
  * Reactodia ships English as its built-in default bundle, so English needs no override.
30645
30832
  * Other languages are layered on top as partial bundles; unknown codes fall back to English.
@@ -30657,6 +30844,12 @@ function translationsForLanguage(language) {
30657
30844
  * runs outside React and therefore cannot call the `useWorkspace` hook.
30658
30845
  */
30659
30846
  let workspaceContext = null;
30847
+ /**
30848
+ * Active listeners that persist diagram edits to local storage. Populated when the workspace
30849
+ * mounts and released in {@link unmountReactodia}, so persistence follows the same lifecycle as
30850
+ * the React root.
30851
+ */
30852
+ const subscriptions = new SubscriptionList();
30660
30853
  /**
30661
30854
  * Builds a Reactodia {@link SparqlDataProvider} for the given endpoint using the supplied
30662
30855
  * query preset. The host owns the query configuration and passes it in via props; when none
@@ -30667,21 +30860,71 @@ function createDataProvider(props) {
30667
30860
  return new SparqlDataProvider({
30668
30861
  endpointUrl: currentRepository,
30669
30862
  queryMethod: 'POST',
30670
- queryFunction: config.queryFunction,
30863
+ queryFunction: config.queryFunction
30671
30864
  }, providerSettings);
30672
30865
  }
30673
30866
  /**
30674
30867
  * Places the seed entities on the canvas. Each IRI is added as a placeholder element, and data is then loaded for the
30675
30868
  * respective nodes using the {@link DataDiagramModel.requestData} method.
30676
30869
  */
30677
- async function seedCanvas(context, seed, signal) {
30870
+ async function seedIrisToCanvas(context, dataProvider, seed, signal) {
30678
30871
  const { model, performLayout } = context;
30872
+ await model.createNewDiagram({ dataProvider, signal });
30679
30873
  for (const iri of seed) {
30680
30874
  model.createElement(iri);
30681
30875
  }
30682
30876
  await model.requestData();
30683
30877
  await performLayout({ signal });
30684
30878
  }
30879
+ /**
30880
+ * Places a pre-resolved graph (e.g. a CONSTRUCT query result) on the canvas. The difference between this and
30881
+ * {@link seedIrisToCanvas} is that this method does not request link data from the SPARQL endpoint, since describe/construct
30882
+ * queries are not persisted in the DB. Here we expect provided links and query for element data only
30883
+ */
30884
+ async function seedGraphToCanvas(context, dataProvider, links, signal) {
30885
+ const { model, performLayout } = context;
30886
+ await model.createNewDiagram({ dataProvider, signal });
30887
+ const uniqueIris = new Set();
30888
+ links.forEach((link) => {
30889
+ var _a;
30890
+ uniqueIris.add(link.source);
30891
+ uniqueIris.add(link.target);
30892
+ // this is a createOrGet, regardless of the name, so we don't need to worry about duplicates.
30893
+ model.createElement(link.source);
30894
+ model.createElement(link.target);
30895
+ (_a = link.rawPredicates) === null || _a === void 0 ? void 0 : _a.forEach((predicate) => {
30896
+ model.createLinks({
30897
+ linkTypeId: predicate,
30898
+ sourceId: link.source,
30899
+ targetId: link.target,
30900
+ properties: {}
30901
+ });
30902
+ });
30903
+ });
30904
+ await model.requestElementData(Array.from(uniqueIris));
30905
+ await performLayout({ signal });
30906
+ }
30907
+ /**
30908
+ * Subscribes to the model's changes so diagram edits are persisted to local storage. The
30909
+ * subscription is tracked in {@link subscriptions} so it is released together with the React
30910
+ * root in {@link unmountReactodia}.
30911
+ */
30912
+ function onDiagramChange(model) {
30913
+ const storage = service(DiagramStorageService);
30914
+ const diagramService = service(DiagramService);
30915
+ subscriptions.add(diagramService.subscribeToDiagramChange(model, (diagram) => storage.save(diagram)));
30916
+ }
30917
+ /**
30918
+ * Subscribes to the host's "clear persisted diagram" command so the saved layout is dropped on
30919
+ * request (e.g. before a fresh seed should take precedence over previously saved edits). The
30920
+ * subscription is tracked in {@link subscriptions} so it is released together with the React
30921
+ * root in {@link unmountReactodia}.
30922
+ */
30923
+ function onClearDiagramStorage() {
30924
+ const storage = service(DiagramStorageService);
30925
+ const eventService = service(EventService);
30926
+ subscriptions.add(eventService.subscribeToClearDiagramStorage(() => storage.clear()));
30927
+ }
30685
30928
  /**
30686
30929
  * The Reactodia workspace React component.
30687
30930
  *
@@ -30692,22 +30935,32 @@ async function seedCanvas(context, seed, signal) {
30692
30935
  * provider's lookup. With a seed, the seeded nodes are placed on the canvas on startup.
30693
30936
  */
30694
30937
  function ReactodiaApp(props) {
30695
- const { language, initialDiagram, config } = props;
30938
+ const { language, config } = props;
30939
+ const currentDiagram = props.isReload ? exportReactodiaLayout() : undefined;
30696
30940
  const { onMount } = useLoadedWorkspace(async ({ context, signal }) => {
30941
+ var _a, _b;
30697
30942
  workspaceContext = context;
30698
30943
  const { model } = context;
30944
+ onDiagramChange(model);
30945
+ onClearDiagramStorage();
30699
30946
  const dataProvider = createDataProvider(props);
30700
- // A language change remounts this component to rebuild the workspace with the new
30701
- // translation bundle; restoring the exported diagram keeps the user's canvas intact.
30702
- // This is done because there is no existing mechanism to re-translate the UI at runtime (not for the UI labels at least)
30703
- if (initialDiagram) {
30704
- await model.importLayout({ dataProvider, diagram: initialDiagram, signal });
30947
+ const isReload = props.isReload;
30948
+ const savedDiagram = service(DiagramStorageService).load();
30949
+ if (isReload) {
30950
+ // Simply reload without using the current state. This may happen when the user switches the language at runtime.
30951
+ // since there is no existing mechanism to re-translate the UI at runtime (not for the UI labels at least)
30952
+ await model.importLayout({ dataProvider, diagram: currentDiagram, signal });
30953
+ }
30954
+ else if ((_a = config.seedIris) === null || _a === void 0 ? void 0 : _a.length) {
30955
+ await seedIrisToCanvas(context, dataProvider, config.seedIris, signal);
30956
+ }
30957
+ else if ((_b = config.seedGraph) === null || _b === void 0 ? void 0 : _b.length) {
30958
+ await seedGraphToCanvas(context, dataProvider, config.seedGraph, signal);
30705
30959
  }
30706
30960
  else {
30707
- await model.createNewDiagram({ dataProvider, signal });
30708
- if (config.seedIris) {
30709
- await seedCanvas(context, config.seedIris, signal);
30710
- }
30961
+ // Restore the previously saved layout when present, otherwise start empty. Either way this binds the
30962
+ // data provider (savedDiagram is undefined -> empty diagram), so the unified search/lookup works.
30963
+ await model.importLayout({ dataProvider, diagram: savedDiagram, signal });
30711
30964
  }
30712
30965
  }, [language]);
30713
30966
  /**
@@ -30758,6 +31011,7 @@ function exportReactodiaLayout() {
30758
31011
  * Unmounts the Reactodia application.
30759
31012
  */
30760
31013
  function unmountReactodia(root) {
31014
+ subscriptions.unsubscribeAll();
30761
31015
  root.unmount();
30762
31016
  }
30763
31017
 
@@ -30776,13 +31030,13 @@ const GraphwiseReactodia = class {
30776
31030
  }
30777
31031
  onLanguageChange() {
30778
31032
  // The translations are evaluated by the workspace at construction, so the only way
30779
- // to re-translate the UI is to re-create the graph. We carry the
30780
- // current diagram across the remount so switching languages does not wipe the canvas.
30781
- const diagram = exportReactodiaLayout();
30782
- this.renderGraph(diagram);
31033
+ // to re-translate the UI is to re-create the graph.
31034
+ this.renderGraph(true);
30783
31035
  }
30784
- componentDidLoad() {
30785
- this.renderGraph();
31036
+ connectedCallback() {
31037
+ if (this.config) {
31038
+ this.renderGraph();
31039
+ }
30786
31040
  }
30787
31041
  disconnectedCallback() {
30788
31042
  if (this.reactRoot) {
@@ -30791,9 +31045,9 @@ const GraphwiseReactodia = class {
30791
31045
  }
30792
31046
  }
30793
31047
  render() {
30794
- return (h(Host, { key: '8ef8d44db9d8e5eae72474c71c3c586f1f267d9b' }));
31048
+ return (h(Host, { key: 'b33d3709a5d7b1fdde8d528d036d9dbc962d6c76' }));
30795
31049
  }
30796
- renderGraph(initialDiagram) {
31050
+ renderGraph(isReloading) {
30797
31051
  var _a;
30798
31052
  if (!this.currentRepository) {
30799
31053
  throw new Error('currentRepository is required');
@@ -30805,13 +31059,13 @@ const GraphwiseReactodia = class {
30805
31059
  return;
30806
31060
  }
30807
31061
  const props = {
30808
- initialDiagram,
31062
+ isReload: isReloading,
30809
31063
  currentRepository: this.currentRepository,
30810
31064
  config: this.config,
30811
31065
  language: this.language,
30812
31066
  providerSettings: this.providerSettings,
30813
31067
  };
30814
- if (this.reactRoot && !initialDiagram) {
31068
+ if (this.reactRoot && !isReloading) {
30815
31069
  void updateReactodia(props);
30816
31070
  }
30817
31071
  else {
package/dist/esm/index.js CHANGED
@@ -1 +1,7 @@
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
+ const CLEAR_DIAGRAM_STORAGE_EVENT = 'graphwise-reactodia:clear-diagram-storage';
1
6
 
7
+ export { CLEAR_DIAGRAM_STORAGE_EVENT };
@@ -1 +1 @@
1
- import{p as e,b as r}from"./p-Cypw7ydc.js";export{s as setNonce}from"./p-Cypw7ydc.js";import{g as t}from"./p-CalY52z6.js";(()=>{const r=import.meta.url,t={};return""!==r&&(t.resourcesUrl=new URL(".",r).href),e(t)})().then((async e=>(await t(),r([["p-5a330bae",[[0,"graphwise-reactodia",{currentRepository:[1,"current-repository"],config:[16],language:[1],providerSettings:[16,"provider-settings"]},null,{currentRepository:["onProviderSettingsChange"],providerSettings:["onProviderSettingsChange"],language:["onLanguageChange"]}]]]],e))));
1
+ import{p as e,b as r}from"./p-Cypw7ydc.js";export{s as setNonce}from"./p-Cypw7ydc.js";import{g as t}from"./p-CalY52z6.js";(()=>{const r=import.meta.url,t={};return""!==r&&(t.resourcesUrl=new URL(".",r).href),e(t)})().then((async e=>(await t(),r([["p-80cb6eef",[[0,"graphwise-reactodia",{currentRepository:[1,"current-repository"],config:[16],language:[1],providerSettings:[16,"provider-settings"]},null,{currentRepository:["onProviderSettingsChange"],providerSettings:["onProviderSettingsChange"],language:["onLanguageChange"]}]]]],e))));
@@ -0,0 +1 @@
1
+ const a="graphwise-reactodia:clear-diagram-storage";export{a as CLEAR_DIAGRAM_STORAGE_EVENT}