LCOV - code coverage report
Current view: top level - src/store/modules - selection.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 57 69 82.6 %
Date: 2025-06-29 02:18:36 Functions: 14 20 70.0 %

          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             : }

Generated by: LCOV version 1.16