graphwise-reactodia 0.0.1-TR11 → 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.
- package/dist/cjs/graphwise-reactodia.cjs.entry.js +170 -13
- package/dist/collection/components/graphwise-reactodia/i18n/translations.js +4 -1
- package/dist/collection/components/graphwise-reactodia/menu/graphwise-menu.js +37 -0
- package/dist/collection/components/graphwise-reactodia/reactodia-app.js +6 -4
- package/dist/collection/components/graphwise-reactodia/services/diagram/diagram.service.js +14 -0
- package/dist/collection/components/graphwise-reactodia/services/diagram-file/diagram-file.service.js +68 -0
- package/dist/esm/graphwise-reactodia.entry.js +170 -13
- package/dist/graphwise-reactodia/graphwise-reactodia.esm.js +1 -1
- package/dist/graphwise-reactodia/{p-81eeb4c3.entry.js → p-52bd1b0e.entry.js} +1 -1
- package/dist/types/components/graphwise-reactodia/menu/graphwise-menu.d.ts +9 -0
- package/dist/types/components/graphwise-reactodia/services/diagram/diagram.service.d.ts +11 -1
- package/dist/types/components/graphwise-reactodia/services/diagram-file/diagram-file.service.d.ts +24 -0
- package/package.json +1 -1
|
@@ -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
|
|
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$
|
|
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$
|
|
31395
|
+
DiagramService = DiagramService_1 = __decorate$4([
|
|
31334
31396
|
Service
|
|
31335
31397
|
], DiagramService);
|
|
31336
31398
|
|
|
31337
|
-
var __decorate$
|
|
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$
|
|
31414
|
+
WindowService = __decorate$3([
|
|
31353
31415
|
Service
|
|
31354
31416
|
], WindowService);
|
|
31355
31417
|
|
|
31356
|
-
var __decorate$
|
|
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$
|
|
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
|
|
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
|
|
@@ -31637,10 +31793,11 @@ function ReactodiaApp(props) {
|
|
|
31637
31793
|
const dataProvider = createDataProvider(props);
|
|
31638
31794
|
const isReload = props.isReload;
|
|
31639
31795
|
const savedDiagram = service(DiagramStorageService).load();
|
|
31796
|
+
const diagramService = service(DiagramService);
|
|
31640
31797
|
if (isReload) {
|
|
31641
31798
|
// Simply reload without using the current state. This may happen when the user switches the language at runtime.
|
|
31642
31799
|
// since there is no existing mechanism to re-translate the UI at runtime (not for the UI labels at least)
|
|
31643
|
-
await
|
|
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
|
|
31811
|
+
await diagramService.importDiagram(model, dataProvider, savedDiagram, signal);
|
|
31655
31812
|
}
|
|
31656
31813
|
}, [language]);
|
|
31657
31814
|
/**
|
|
@@ -31670,7 +31827,7 @@ function ReactodiaApp(props) {
|
|
|
31670
31827
|
defaultLanguage: language,
|
|
31671
31828
|
translations: translationsForLanguage(language),
|
|
31672
31829
|
typeStyleResolver: resolveTypeStyle
|
|
31673
|
-
}, reactExports.createElement(DefaultWorkspace, {}));
|
|
31830
|
+
}, reactExports.createElement(DefaultWorkspace, { menu: reactExports.createElement(GraphwiseMenu) }));
|
|
31674
31831
|
}
|
|
31675
31832
|
/**
|
|
31676
31833
|
* Mounts the Reactodia application into the given container and returns the React root.
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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.
|
package/dist/collection/components/graphwise-reactodia/services/diagram-file/diagram-file.service.js
ADDED
|
@@ -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 };
|