Line data Source code
1 : // @ts-check
2 : import Vue from "vue";
3 : import { assign, cloneDeep, defaults, findIndex, forEach, get, has, size } from "lodash";
4 : import { getKeyTimes, getPropertyByName, getRelationValues } from "../../utils/ssvg";
5 : import { getAttribute } from "../../utils/element";
6 : import d from "debug";
7 :
8 1 : const debug = d("app:store");
9 :
10 : /** @type {V.Module<AppStore.Selection>} */
11 1 : const mod = {
12 : namespaced: true,
13 1 : state: () => ({
14 : state: null,
15 : property: null,
16 : relation: null,
17 : transition: null,
18 : normalizedValue: null,
19 : path: null,
20 : keyTime: null,
21 : isOnKeyTime: false,
22 : keyTimes: null,
23 : tvalues: null,
24 : selectorHelper: null
25 : }),
26 : getters: {
27 : selectedElement(state) {
28 4 : if (state.transition) {
29 0 : return state.transition;
30 : }
31 4 : else if (state.relation) {
32 1 : return state.relation;
33 : }
34 : else {
35 3 : return state.property;
36 : }
37 : },
38 : isTransform: (state) => (
39 0 : get(state, [ "relation", "tagName" ]) === "transform"),
40 : computedKeyTimes: (state) => {
41 2 : if (state.keyTimes) {
42 1 : return state.keyTimes;
43 : }
44 1 : else if (state.tvalues && size(state.tvalues.values) >= 2) {
45 0 : const last = (state.tvalues.values.length - 1);
46 0 : return state.tvalues.values.map(
47 0 : (/** @type {any} */ val, /** @type {number} */ idx) => idx / last);
48 : }
49 1 : return (state.relation !== null) ? [ 0, 1 ] : null;
50 : },
51 : /**
52 : * @brief return the selection in given fragment
53 : * @return {(fragment: Element|null) => Partial<AppStore.Selection>|null}
54 : */
55 : getFragmentSelection(state) {
56 : /* eslint-disable-next-line complexity */
57 1 : return (/** @type {Element|null} */ fragment) => {
58 6 : if (!fragment || !state.path) { return null; }
59 4 : const elt = fragment.getElementsByTagName("state").item(state.path[0]);
60 :
61 4 : const property = state.path?.[1] ? getPropertyByName(elt, state.path[1]) : null;
62 4 : const relation = (property && state.path?.[2]) ?
63 : property.getElementsByTagName(state.path[2].tagName)
64 : .item(state.path[2].index) : null;
65 4 : let transition = (relation && state.path?.[3] !== null) ?
66 : relation.getElementsByTagName("transition")
67 : .item(state.path[3]) : null;
68 4 : if (!property && state.path?.[3] !== null) {
69 0 : transition = elt?.getElementsByTagName("transition")?.item(0) ?? null;
70 : }
71 :
72 4 : return { state: elt, property, relation, transition };
73 : };
74 : }
75 : },
76 : mutations: {
77 : /**
78 : * @param {AppStore.Selection} state
79 : * @param {string} name
80 : */
81 : remove(state, name) {
82 0 : Vue.delete(state, name);
83 : },
84 : /**
85 : * @brief set current selection
86 : * @param {AppStore.Selection} state
87 : * @param {any} values
88 : */ // eslint-disable-next-line complexity
89 : update(state, values) {
90 12 : assign(state, values);
91 12 : if (has(values, "relation")) {
92 8 : state.tvalues = values.relation ?
93 : getRelationValues(values.relation) : null;
94 : // @ts-ignore
95 8 : state.keyTimes = values.relation ?
96 : getKeyTimes(values.relation) : null;
97 8 : if (state.tvalues && !state.keyTimes) {
98 2 : state.keyTimes = state.tvalues.values.map(
99 8 : (val, idx) => idx / (state.tvalues.values.length - 1));
100 : }
101 : }
102 12 : var idx = 0;
103 12 : for (const k of (state.keyTimes || [])) {
104 7 : if (state.normalizedValue < k + 0.005) { break; }
105 4 : idx += 1;
106 : }
107 12 : state.keyTime = idx;
108 12 : state.isOnKeyTime =
109 : (Math.abs(get(state.keyTimes, [ state.keyTime ], 0) - state.normalizedValue) <= 0.005);
110 : },
111 : /**
112 : * @brief clear current selection
113 : * @param {AppStore.Selection} state
114 : */
115 : clear(state) {
116 0 : forEach(state, (v, k) => {
117 0 : /** @type {{[k: string]: any}} */(state)[k] = null;
118 : });
119 : }
120 : },
121 : actions: {
122 : /**
123 : *
124 : * @param {{ state: Element|null, property: Element|null, relation: Element|null, transition: Element|null }} values
125 : */
126 : select(context, values) {
127 : /* ensure all parameters are set */
128 3 : defaults(values, { state: null, property: null, relation: null, transition: null });
129 3 : const stateIndex = findIndex(context.rootState.engine?.stateElements, (s) => s === values.state);
130 3 : const propertyName = getAttribute(values.property, "name");
131 :
132 3 : let relation = null;
133 3 : let transition = null;
134 3 : if (values.relation) {
135 2 : const tagName = values.relation?.tagName;
136 2 : const rels = values.property?.getElementsByTagName(tagName);
137 2 : const index = findIndex(rels, (r) => r === values.relation);
138 2 : if (index >= 0) {
139 2 : relation = { index, tagName: values.relation?.tagName };
140 2 : if (values.transition) {
141 0 : const trans = values.relation.getElementsByTagName("transition");
142 0 : transition = findIndex(trans, (r) => r === values.transition);
143 : }
144 : }
145 : }
146 1 : else if (!values.property && values.transition) {
147 0 : transition = 0;
148 : }
149 3 : const path = (stateIndex >= 0) ? [ stateIndex, propertyName ? propertyName : null, relation, transition ] : null;
150 3 : debug("selecting %o -> %o", values, path);
151 3 : context.commit("update", { ...values, path });
152 : },
153 : restore(context) {
154 5 : const selection = context.getters.getFragmentSelection(context.rootState?.engine?.engine?.svg);
155 5 : if (selection && context.state.path) {
156 3 : selection.path = [
157 : context.state.path[0],
158 : selection.property ? context.state.path[1] : null,
159 : selection.relation ? cloneDeep(context.state.path[2]) : null,
160 : selection.transition ? context.state.path[3] : null
161 : ];
162 3 : debug("restoring selection: %o -> %o", context.state.path, selection);
163 3 : context.commit("update", selection);
164 : }
165 : else {
166 2 : debug("restoring empty selection");
167 2 : context.commit("update", { relation: null, property: null, state: null, transition: null });
168 : }
169 : }
170 : }
171 : };
172 : export default mod;
173 :
174 : /**
175 : * @param {V.Store<AppStore.State>} store
176 : */
177 : export function plugin(store) {
178 6 : store.watch((state) => get(state, [ "engine", "engine" ]),
179 5 : () => store.dispatch("selection/restore"));
180 : }
|