From df7f47f71823c471f937c5e08ae5969a19951816 Mon Sep 17 00:00:00 2001 From: axolotle Date: Fri, 4 Jun 2021 10:38:33 +0200 Subject: [PATCH] update store with data formating and query actions --- src/store/index.js | 7 +- src/store/modules/library.js | 125 +++++++++++++++++++++++++++++++++++ src/store/texts.js | 114 ++++++++++++++++++++++++++++++++ src/store/utils.js | 101 ++++++++++++++++++++++++++++ 4 files changed, 344 insertions(+), 3 deletions(-) create mode 100644 src/store/modules/library.js create mode 100644 src/store/texts.js create mode 100644 src/store/utils.js diff --git a/src/store/index.js b/src/store/index.js index c2b3c72..9c188c7 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,13 +1,14 @@ import Vue from 'vue' import Vuex from 'vuex' -import texts from './modules/texts' - +import texts from './texts' +import library from './modules/library' Vue.use(Vuex) export default new Vuex.Store({ + ...texts, modules: { - texts + library } }) diff --git a/src/store/modules/library.js b/src/store/modules/library.js new file mode 100644 index 0000000..d145a70 --- /dev/null +++ b/src/store/modules/library.js @@ -0,0 +1,125 @@ +import Vue from 'vue' + +import api from '@/api' +import { + AllTags +} from '@/api/queries' +import { + getUniqueNodesIds, + getRelatedNodesIds, + buildTree +} from '@/store/utils.js' + + +export default { + state: { + tags: undefined, + strangeness: [0, 1, 2, 3, 4], + nodeDepartId: undefined, + trees: {} + }, + + mutations: { + 'SET_TAGS' (state, tags) { + state.tags = tags + }, + + 'SET_NODE_DEPART_ID' (state, id) { + state.nodeDepartId = id + }, + + 'ADD_NODE_TREE' (state, [id, tree]) { + Vue.set(state.trees, id, tree) + } + }, + + actions: { + async 'INIT_LIBRARY' ({ state, commit, dispatch, rootState }) { + const departIds = await dispatch('GET_ALL_NODES_IDS', 'depart') + await dispatch('GET_NODES', { ids: departIds, dataLevel: 'initial' }) + return departIds + }, + + async 'INIT_LIBRARY_TREE' ({ state, commit, dispatch, rootState }) { + const ids = await dispatch('INIT_LIBRARY') + return dispatch('SET_NODE_DEPART_ID', ids[ids.length - 1]) + }, + + async 'INIT_LIBRARY_MAP' ({ state, commit, dispatch }) { + const departIds = await dispatch('INIT_LIBRARY') + const departNodes = await dispatch('GET_NODES', { ids: departIds.slice(0, 3), dataLevel: 'partial' }) + const relatedIds = getRelatedNodesIds(departNodes) + const relatedNodes = await dispatch('GET_NODES', { ids: relatedIds, dataLevel: 'partial' }) + return [...departNodes, ...relatedNodes] + }, + + async 'INIT_LIBRARY_LIST' ({ state, commit, dispatch }) { + const departIds = await dispatch('INIT_LIBRARY') + return dispatch('GET_NODES', { ids: departIds, dataLevel: 'partial' }) + }, + + 'GET_ALL_TAGS' ({ state, commit }) { + if (state.tags !== undefined) return state.tags + return api.query(AllTags).then(data => { + commit('SET_TAGS', data.tags) + return state.tags + }) + }, + + async 'SET_NODE_DEPART_ID' ({ state, rootState, commit, dispatch, getters }, id) { + if (state.nodeDepartId === id) return + + commit('SET_NODE_DEPART_ID', id) + + if (!(id in state.trees)) { + const treeData = await api.queryRecursiveNodes([id]) + const nodes = await dispatch('GET_NODES', { ids: getUniqueNodesIds(treeData), dataLevel: 'initial' }) + commit('ADD_NODE_TREE', [id, buildTree(treeData, nodes)]) + } + + return getters.nodeTree + } + }, + + getters: { + tagsOptions: state => { + if (state.tags === undefined) return + return state.tags.map(tag => ({ value: tag.id, text: tag.name })) + }, + + nodesDepartsOptions: (state, getters, rootState) => { + const departIds = rootState.ids.depart + if (departIds === undefined || rootState.nodes[departIds[0]] === undefined) return + return departIds.map(id => { + const text = rootState.nodes[id] + const firstAuthor = text.authors !== null ? text.authors[0].name : 'Pad de noms' + return { + text: `${firstAuthor}, ${text.title} (${id})`, + value: id + } + }) + }, + + orderedTextsDepart: (state, getters, rootState) => { + const departIds = rootState.ids.depart + if (departIds === undefined || rootState.nodes[departIds[0]] === undefined) return + return rootState.ids.depart.map(id => rootState.nodes[id]).sort((a, b) => { + if (!b.authors) return -1 + if (!a.authors) return +1 + if (a.authors[0].last_name < b.authors[0].last_name) return -1 + if (a.authors[0].last_name > b.authors[0].last_name) return 1 + return 0 + }).reduce((dict, text) => { + const firstChar = text.authors ? text.authors[0].last_name[0] : 'Pas de noms' + if (!(firstChar in dict)) dict[firstChar] = [] + dict[firstChar].push(text) + return dict + }, {}) + }, + + nodeTree: (state, rootState) => { + if (state.nodeDepartId === undefined) return + return state.trees[state.nodeDepartId] + } + } +} diff --git a/src/store/texts.js b/src/store/texts.js new file mode 100644 index 0000000..d7ede52 --- /dev/null +++ b/src/store/texts.js @@ -0,0 +1,114 @@ +import Vue from 'vue' + +import api from '@/api' +import { + DATA_LEVELS, + ID_VARIANTS, + RELATIONS // FIXME REMOVE WHEN DB MAJ +} from '@/store/utils' +import { + AllNodesOfVariant +} from '@/api/queries' + + +export default { + state: { + nodes: {}, + + history: [], + + // IDS + ids: { + // depart: undefined, + // kit: undefined, + // creation: undefined + } + }, + + mutations: { + 'SET_ALL_NODES_IDS' (state, [variant, nodes]) { + Vue.set(state.ids, variant, nodes.map(({ id }) => id)) + }, + + 'ADD_NODES' (state, [nodes, dataLevel]) { + for (const node of nodes) { + const stateNode = node.id in state.nodes ? state.nodes[node.id] : undefined + // FIXME UPDATE AFTER DB MAJ + if (node.variant === null || Array.isArray(node.variant)) { + node.variant = node.variant !== null ? ID_VARIANTS[node.variant[0].id] : 'black' + } + if (node.type) { + node.type = node.type === 'texte' ? 'Textref' : 'Textprod' + } + // FIXME REMOVE AFTER DB MAJ + for (const relation of RELATIONS) { + if (relation in node && node[relation]) { + node[relation].forEach(subNode => { + subNode.variant = subNode.variant !== null ? ID_VARIANTS[subNode.variant[0].id] : 'black' + }) + } + } + if (!stateNode || stateNode.dataLevel < dataLevel) { + node.dataLevel = dataLevel + } + Vue.set(state.nodes, node.id, stateNode ? { ...stateNode, ...node } : node) + } + } + }, + + actions: { + async 'GET_ALL_NODES_IDS' ({ state, commit }, variant) { + if (state.ids[variant] === undefined) { + // FIXME UPDATE WHEN QUERY OK + // await api.query(AllNodesOfVariant, { variantId: VARIANT_IDS[variant] }).then(data => { + await api.query(AllNodesOfVariant).then(data => { + commit('SET_ALL_NODES_IDS', [variant, data.nodes]) + }) + } + return state.ids[variant] + }, + + async 'GET_NODES' ({ state, commit, getters }, { ids, dataLevel = 'partial' }) { + const lvl = DATA_LEVELS[dataLevel] + const nodesIdsToQuery = [] + let lowestLvl = lvl + for (const id of ids) { + const nodeLvl = id in state.nodes ? state.nodes[id].dataLevel : -1 + if (nodeLvl < lvl) { + nodesIdsToQuery.push(id) + } + if (nodeLvl < lowestLvl) { + lowestLvl = nodeLvl + } + } + + if (nodesIdsToQuery.length) { + const levelsToQuery = Array(lvl - lowestLvl).fill(lowestLvl + 1).map((v, i) => v + i) + await api.queryNodes(nodesIdsToQuery, levelsToQuery).then(data => { + commit('ADD_NODES', [data.nodes, lvl]) + }) + } + + return getters.nodes(ids) + }, + + async 'GET_NODE' ({ state, commit, getters }, { id, dataLevel = 'full' }) { + const lvl = DATA_LEVELS[dataLevel] + const nodeLvl = id in state.nodes ? state.nodes[id].dataLevel : -1 + if (nodeLvl < lvl) { + const levelsToQuery = Array(lvl - nodeLvl).fill(nodeLvl + 1).map((v, i) => v + i) + await api.queryNode(id, levelsToQuery).then(data => { + commit('ADD_NODES', [[data.node], lvl]) + }) + } + + return getters.node(id) + } + }, + + getters: { + // Args getters + nodes: state => ids => ids.map(id => state.nodes[id]), + node: state => id => state.nodes[id] + } +} diff --git a/src/store/utils.js b/src/store/utils.js new file mode 100644 index 0000000..ecf7a96 --- /dev/null +++ b/src/store/utils.js @@ -0,0 +1,101 @@ +import { hierarchy } from 'd3-hierarchy' + + +export const RELATIONS = [ + 'parents', + 'siblings', + 'children' +] + +export const DATA_LEVELS = { + initial: 0, + partial: 1, + full: 2 +} + +export const ID_VARIANTS = { + 9: 'depart', + 22: 'critique', + 63: 'echo', + 6: 'reflexion', + 7: 'lecture', + 8: 'sensible', + 23: 'kit' + // undefined: 'creation' +} + +export const VARIANT_IDS = Object.fromEntries( + Object.entries(ID_VARIANTS).map(([key, value]) => [value, key]) +) + + +export function getUniqueNodesIds (tree) { + function extractId (ids, node) { + ids.add(node.id) + for (const relation of RELATIONS) { + if (relation in node && node[relation]) { + for (const subNode of node[relation]) { + extractId(ids, subNode) + } + } + } + return ids + } + return Array.from(extractId(new Set(), tree)) +} + + +export function getRelatedNodesIds (nodes) { + const ids = new Set() + for (const node of nodes) { + for (const relation of RELATIONS) { + if (relation in node && node[relation]) { + node[relation].forEach(({ id }) => { + ids.add(id) + }) + } + } + } + return Array.from(ids) +} + + +export function buildTree (treeData, nodesData) { + const uniqueIds = [] + const nodes = [] + const links = [] + const h = hierarchy(treeData, (node) => { + return RELATIONS.reduce((acc, relation) => { + if (node[relation]) { + node[relation].forEach((item, i) => { + item.linkType = relation + }) + return [...acc, ...node[relation]] + } + return acc + }, []) + }) + + h.each(node => { + node.id = node.data.id + if (!uniqueIds.includes(node.id)) { + uniqueIds.push(node.id) + node.data = nodesData.find(n => n.id === node.id) + nodes.push(node) + if (node.children) { + node.children.forEach(child => { + const asSource = links.find(link => link.source === node.id && link.target === child.data.id) + const asTarget = links.find(link => link.source === child.data.id && link.target === node.id) + if (!asSource && !asTarget) { + links.push({ source: node.id, target: child.data.id, linkType: child.data.linkType }) + } + }) + } + } + }) + + return { + nodes, + links + } +}