graphwise-reactodia 0.0.1-TR10 → 0.0.1-TR12

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.
@@ -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
@@ -128,10 +129,11 @@ function ReactodiaApp(props) {
128
129
  const dataProvider = createDataProvider(props);
129
130
  const isReload = props.isReload;
130
131
  const savedDiagram = service(DiagramStorageService).load();
132
+ const diagramService = service(DiagramService);
131
133
  if (isReload) {
132
134
  // Simply reload without using the current state. This may happen when the user switches the language at runtime.
133
135
  // 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 });
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,7 @@ function ReactodiaApp(props) {
161
163
  defaultLanguage: language,
162
164
  translations: translationsForLanguage(language),
163
165
  typeStyleResolver: resolveTypeStyle
164
- }, createElement(DefaultWorkspace, {}));
166
+ }, createElement(DefaultWorkspace, { menu: createElement(GraphwiseMenu) }));
165
167
  }
166
168
  /**
167
169
  * Mounts the Reactodia application into the given container and returns the React root.
@@ -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.
@@ -0,0 +1,68 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
4
+ r = Reflect.decorate(decorators, target, key, desc);
5
+ else
6
+ for (var i = decorators.length - 1; i >= 0; i--)
7
+ if (d = decorators[i])
8
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
9
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
10
+ };
11
+ var DiagramFileService_1;
12
+ import { Service } from "../../providers/service/service";
13
+ import { service } from "../../providers/service/service-inject";
14
+ import { WindowService } from "../window/window.service";
15
+ /**
16
+ * Service for downloading and uploading the diagram as a file. The file content is the same
17
+ * {@link SerializedDiagram} that is persisted to the local store.
18
+ */
19
+ let DiagramFileService = DiagramFileService_1 = class DiagramFileService {
20
+ /**
21
+ * Downloads the given diagram serialization as a file with the given name.
22
+ */
23
+ download(diagram, fileName) {
24
+ const window = service(WindowService).getWindow();
25
+ const blob = new Blob([JSON.stringify(diagram)], { type: DiagramFileService_1.MIME_TYPE });
26
+ const url = window.URL.createObjectURL(blob);
27
+ const link = window.document.createElement('a');
28
+ link.href = url;
29
+ link.download = fileName;
30
+ link.click();
31
+ window.URL.revokeObjectURL(url);
32
+ }
33
+ /**
34
+ * Reads a diagram serialization from the given file.
35
+ *
36
+ * @throws Error when the file is not valid JSON or is not a serialized diagram.
37
+ */
38
+ async read(file) {
39
+ let diagram;
40
+ try {
41
+ diagram = JSON.parse(await file.text());
42
+ }
43
+ catch (error) {
44
+ throw new Error(`File "${file.name}" is not valid JSON: ${error.message}`);
45
+ }
46
+ if (!this.isSerializedDiagram(diagram)) {
47
+ throw new Error(`File "${file.name}" is not a serialized diagram`);
48
+ }
49
+ return diagram;
50
+ }
51
+ /**
52
+ * Checks if the provided value is a serialized diagram
53
+ */
54
+ isSerializedDiagram(value) {
55
+ var _a, _b;
56
+ const diagram = value;
57
+ return (diagram === null || diagram === void 0 ? void 0 : diagram['@type']) === 'Diagram'
58
+ && Array.isArray((_a = diagram.layoutData) === null || _a === void 0 ? void 0 : _a.elements)
59
+ && Array.isArray((_b = diagram.layoutData) === null || _b === void 0 ? void 0 : _b.links);
60
+ }
61
+ };
62
+ DiagramFileService.ID = 'DiagramFileService';
63
+ DiagramFileService.FILE_EXTENSION = '.json';
64
+ DiagramFileService.MIME_TYPE = 'application/json';
65
+ DiagramFileService = DiagramFileService_1 = __decorate([
66
+ Service
67
+ ], DiagramFileService);
68
+ export { DiagramFileService };