Line data Source code
1 : // @ts-check
2 :
3 : import Vue from "vue";
4 : import { SSVGEngine } from "@cern/ssvg-engine";
5 : import { find, forEach, get } from "lodash";
6 : import { getDomSelectors } from "../../utils";
7 :
8 : /** @type {V.Module<AppStore.SSVGEngine>} */
9 1 : const mod = {
10 : namespaced: true,
11 1 : state: () => ({
12 : text: null, // complete svg as text
13 : fragment: null, // complete svg as Node tree
14 : engine: null, // SSVG engine
15 : state: null, // current ssvg state
16 : stateMin: null, // min values for states (auto type support)
17 : stateMax: null, // max values for states (auto type support)
18 : directEngine: null, // SSVG engine with no animations
19 : stateElements: null, // state elements list
20 : svgSelectors: null /* possible selectors for the document */
21 : }),
22 : getters: {
23 : svgSelectorsSet(state) {
24 0 : return new Set(state.svgSelectors);
25 : }
26 : },
27 : mutations: {
28 : /**
29 : * @param {AppStore.SSVGEngine} state
30 : */
31 : release(state) {
32 0 : if (state.engine) {
33 0 : state.engine.disconnect();
34 : }
35 0 : if (state.directEngine) {
36 0 : state.directEngine.disconnect();
37 : }
38 0 : forEach(state, (s, name) => {
39 0 : Vue.set(state, name, null);
40 : });
41 : },
42 : /**
43 : * @brief update the engine state
44 : * @param {AppStore.SSVGEngine} state
45 : * @param {any} value
46 : */
47 : updateState(state, value) {
48 9 : if (state.engine) {
49 8 : state.engine.updateState(value);
50 8 : mod?.mutations?.updateMinMax(state, value);
51 : /* update computed properties */
52 8 : state.state = state.engine.getState();
53 : }
54 9 : if (state.directEngine) {
55 8 : state.directEngine.updateState(value);
56 : }
57 : },
58 : /**
59 : * @brief set an new engine
60 : * @param {AppStore.SSVGEngine} state
61 : * @param {any} engine
62 : */
63 : updateEngine(state, engine) {
64 5 : state.engine?.disconnect();
65 5 : state.directEngine?.disconnect();
66 :
67 5 : state.fragment = engine.fragment;
68 5 : state.text = state.fragment?.outerHTML ?? null;
69 5 : state.engine = engine.engine;
70 5 : state.directEngine = engine.directEngine;
71 5 : state.svgSelectors = getDomSelectors(get(state.engine, [ "svg" ]));
72 5 : state.stateElements = state.engine.svg.getElementsByTagName("state");
73 5 : state.stateMin = null;
74 5 : state.stateMax = null;
75 5 : state.state = state.engine.getState();
76 : },
77 : /**
78 : * @brief update min/max values
79 : * @param {AppStore.SSVGEngine} state
80 : * @param {any} value
81 : * @details used to keep track of min/max for "auto" variables
82 : */
83 : updateMinMax(state, value) {
84 8 : state.stateMin = state.stateMin ?? {};
85 8 : state.stateMax = state.stateMax ?? {};
86 8 : forEach(value, (v, k) => {
87 23 : if (typeof v === "number") {
88 8 : if (v > (state.stateMax[k] ?? 1)) {
89 3 : state.stateMax[k] = v;
90 : }
91 5 : else if (v < (state.stateMin[k] ?? 0)) {
92 0 : state.stateMin[k] = v;
93 : }
94 : }
95 : else {
96 15 : state.stateMin[k] = state.stateMax[k] = null;
97 : }
98 : });
99 : }
100 : },
101 : actions: {
102 : /**
103 : * @param {string} text
104 : */
105 : async load(context, text) {
106 5 : return Promise.resolve()
107 : .then(() => {
108 5 : var frag = document.createRange().createContextualFragment(text);
109 5 : if (!frag) { throw "invalid XML"; }
110 :
111 5 : const element = find(frag.children, { nodeName: "svg" });
112 5 : if (!element || (element.nodeName !== "svg")) {
113 0 : throw new Error("not an SVG document");
114 : }
115 5 : return context.dispatch("loadFragment", element);
116 : });
117 : },
118 : /**
119 : * @param {Element} element
120 : */
121 : async loadFragment(context, element) {
122 6 : const oldState = context.state.state;
123 6 : return Promise.resolve()
124 : .then(() => {
125 6 : const fragStates = element.getElementsByTagName("state");
126 6 : if (fragStates.length === 0) {
127 0 : element.insertBefore(
128 : document.createElementNS("http://www.w3.org/2000/svg", "state"),
129 : element.firstChild);
130 : }
131 :
132 6 : const engine = new SSVGEngine(/** @type {Element} */(element.cloneNode(true)));
133 :
134 5 : const directElement = /** @type {Element} */ (element.cloneNode(true));
135 5 : directElement.querySelectorAll("state transition").forEach((item) => {
136 3 : item.setAttribute("duration", "0");
137 3 : item.setAttribute("delay", "0");
138 : });
139 5 : return {
140 : fragment: Object.freeze(element),
141 : engine: Object.freeze(engine),
142 : directEngine: Object.freeze(new SSVGEngine(directElement))
143 : };
144 : })
145 5 : .then((value) => context.commit("updateEngine", value))
146 : .then(() => {
147 5 : if (oldState) { context.commit("updateState", oldState); }
148 : });
149 : },
150 : async reload(context) {
151 0 : const oldState = context.state.state;
152 0 : await context.dispatch("loadFragment", context.state.fragment);
153 0 : context.commit("updateState", oldState);
154 : },
155 : async init(context) {
156 0 : if (context.state.text) {
157 0 : return context.dispatch("load", context.state.text);
158 : }
159 : }
160 : }
161 : };
162 : export default mod;
|