LCOV - code coverage report
Current view: top level - src/store - index.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 17 78 21.8 %
Date: 2025-06-29 02:18:36 Functions: 4 15 26.7 %

          Line data    Source code
       1             : // @ts-check
       2             : 
       3             : import Vuex from "vuex";
       4             : import Vue from "vue";
       5             : import { forEach, isString, merge } from "lodash";
       6             : import createPersistedState from "vuex-persistedstate";
       7             : import d from "debug";
       8             : import {
       9             :   createStore,
      10             :   storeOptions
      11             : } from "@cern/base-vue";
      12             : 
      13             : import ui from "./modules/ui";
      14             : import {
      15             :   default as selection,
      16             :   plugin as selectionPlugin
      17             : } from "./modules/selection";
      18             : import loggerPlugin from "./modules/logger";
      19             : import engine from "./modules/engine";
      20             : import VuexHistory from "./modules/history";
      21             : 
      22             : import { setAttribute } from "../utils/element";
      23             : 
      24           1 : const debug = d("app:store");
      25             : 
      26           1 : Vue.use(Vuex);
      27             : 
      28             : /**
      29             :  * @typedef {V.ActionContext<AppStore.State>} ActionContext
      30             :  * @typedef {{
      31             :  *   stateIndex: number, propertyName: string, relationIndex: number,
      32             :  *   isTransform: boolean
      33             :  * }} SavedSelection
      34             :  */
      35             : 
      36             : /**
      37             :  * @param {Element|string} value
      38             :  */
      39             : function createFragment(value) {
      40           1 :   if (isString(value)) {
      41             :     // document.createRange().createContextualFragment(value) would use HTML NS
      42           0 :     const frag = document.createElementNS("http://www.w3.org/2000/svg", "svg");
      43           0 :     frag.innerHTML = value;
      44           0 :     if (!frag || !frag.firstElementChild) { throw new Error(`invalid fragment: ${value}`); }
      45           0 :     return frag.firstElementChild;
      46             :   }
      47           1 :   return value;
      48             : }
      49             : 
      50             : /** @type {V.Store<AppStore.State>} */
      51           1 : merge(storeOptions, /** @type {V.Module<AppStore.State>} */({
      52             :   state: {
      53             :   },
      54             :   modules: {
      55             :     engine, selection, ui
      56             :   },
      57             :   actions: {
      58             :     /**
      59             :      * @brief add element to currently selected state
      60             :      * @param {Element|string} value
      61             :      */
      62             :     async add(context, value) {
      63           1 :       return Promise.resolve()
      64           1 :       .then(() => createFragment(value))
      65             :       .then((value) => {
      66           1 :         const frag = /** @type {Element} */ (context.state.engine.fragment?.cloneNode(true));
      67           1 :         const selection = context.getters["selection/getFragmentSelection"](frag);
      68           1 :         if (value.tagName === "property" || value.tagName === "computed") {
      69           1 :           if (!selection?.state) { throw new Error("no state selected"); }
      70           1 :           selection.state.append(value);
      71           1 :           debug("adding property");
      72             :         }
      73           0 :         else if (value.tagName === "transition") {
      74           0 :           if (!selection?.state) { throw new Error("no state selected"); }
      75           0 :           if (selection?.relation) {
      76           0 :             selection.relation.append(value);
      77             :           }
      78             :           else {
      79           0 :             selection.state.prepend(value);
      80             :           }
      81           0 :           debug("adding transition");
      82             :         }
      83             :         else {
      84           0 :           if (!selection?.property) { throw new Error("no property selected"); }
      85           0 :           selection.property.append(value);
      86           0 :           debug("adding relation");
      87             :         }
      88           1 :         return context.dispatch("engine/loadFragment", frag);
      89             :       });
      90             :     },
      91             :     /**
      92             :      * @brief remove currently selected property or relation
      93             :      * @param {"property"|"relation"|"transition"|"computed"} what
      94             :      */
      95             :     async remove(context, what) {
      96           0 :       return Promise.resolve()
      97             :       .then(() => {
      98           0 :         const frag = /** @type {Element} */ (context.state.engine.fragment?.cloneNode(true));
      99           0 :         const selection = context.getters["selection/getFragmentSelection"](frag);
     100           0 :         if (what === "property" || what === "computed") {
     101           0 :           if (!selection.property) { throw new Error("no property selected"); }
     102           0 :           debug("removing property");
     103           0 :           selection.property.remove();
     104             :         }
     105           0 :         else if (what === "relation") {
     106           0 :           if (!selection.relation) { throw new Error("no relation selected"); }
     107           0 :           debug("removing relation");
     108           0 :           selection.relation.remove();
     109             :         }
     110           0 :         else if (what === "transition") {
     111           0 :           if (!selection.transition) { throw new Error("no transition selected"); }
     112           0 :           debug("removing transition");
     113           0 :           selection.transition.remove();
     114             :         }
     115             :         else {
     116           0 :           throw new Error(`unknown remove request: ${what}`);
     117             :         }
     118           0 :         return context.dispatch("engine/loadFragment", frag);
     119             :       });
     120             :     },
     121             :     /**
     122             :      * @brief edit properties on currently selected property or relation
     123             :      * @param {{ what: "property"|"relation"|"transition"|"computed", values: any }} params
     124             :      */
     125             :     async edit(context, params) {
     126           0 :       return Promise.resolve()
     127             :       .then(() => {
     128           0 :         const frag = /** @type {Element} */ (context.state.engine.fragment?.cloneNode(true));
     129           0 :         const selection = context.getters["selection/getFragmentSelection"](frag);
     130           0 :         if (params.what === "property" || params.what === "computed") {
     131           0 :           if (!selection.property) { throw new Error("no property selected"); }
     132           0 :           forEach(params.values, (value, name) => {
     133           0 :             setAttribute(selection.property, name, value);
     134             :           });
     135             :         }
     136           0 :         else if (params.what === "relation") {
     137           0 :           if (!selection.relation) { throw new Error("no relation selected"); }
     138           0 :           forEach(params.values, (value, name) => {
     139           0 :             setAttribute(selection.relation, name, value);
     140             :           });
     141             :         }
     142             :         else {
     143           0 :           throw new Error(`unknown edit request: ${params.what}`);
     144             :         }
     145           0 :         debug("editing $o", params);
     146           0 :         return context.dispatch("engine/loadFragment", frag);
     147             :       });
     148             :     },
     149             :     /**
     150             :      *
     151             :      * @param {Element|string} value
     152             :      */
     153             :     async replace(context, value) {
     154           0 :       return Promise.resolve()
     155           0 :       .then(() => createFragment(value))
     156             :       .then((value) => {
     157           0 :         const frag = /** @type {Element} */ (context.state.engine.fragment?.cloneNode(true));
     158           0 :         const selection = context.getters["selection/getFragmentSelection"](frag);
     159           0 :         if (value.tagName === "property" || value.tagName === "computed") {
     160           0 :           if (!selection?.property) { throw new Error("no property selected"); }
     161           0 :           debug("replacing property");
     162           0 :           selection.property.replaceWith(value);
     163             :         }
     164           0 :         else if (value.tagName === "transition") {
     165           0 :           if (!selection?.transition) { throw new Error("no transition selected"); }
     166           0 :           debug("replacing transition");
     167           0 :           selection.transition.replaceWith(value);
     168             :         }
     169             :         else {
     170           0 :           if (!selection?.relation) { throw new Error("no relation selected"); }
     171           0 :           debug("replacing relation");
     172           0 :           selection.relation.replaceWith(value);
     173             :         }
     174             : 
     175           0 :         return context.dispatch("engine/loadFragment", frag);
     176             :       });
     177             :     }
     178             :   },
     179             :   plugins: [
     180             :     createPersistedState({
     181             :       key: `${window.location.pathname}/ssvg-editor/vuex`,
     182             :       paths: [ "ui.showKeyHints", "ui.viewMode", "engine.text", "selection.path" ],
     183             :       rehydrated(store) {
     184           0 :         store.dispatch("engine/init");
     185             :       }
     186             :     }),
     187             :     selectionPlugin,
     188             :     loggerPlugin
     189             :   ]
     190             : }));
     191           1 : const store = createStore();
     192           1 : export const History = new VuexHistory(store, "engine.text", 20);
     193           1 : History.on("item", (data) => store.dispatch("engine/load", data));
     194             : 
     195             : export default store;

Generated by: LCOV version 1.16