library.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. import Vue from 'vue'
  2. import router from '@/router'
  3. import api from '@/api'
  4. import {
  5. parseNodesQueryParams,
  6. getUniqueNodesIds,
  7. getRelatedNodesIds,
  8. getRandomIds,
  9. parseTree,
  10. buildTree
  11. } from '@/store/utils.js'
  12. export default {
  13. state: {
  14. trees: {},
  15. randomNodes: undefined,
  16. nodebook: [],
  17. // LibraryOptions options
  18. tagsOptions: [],
  19. // LibraryOptions state
  20. mode: undefined,
  21. nodeDepartId: undefined,
  22. search: '',
  23. tags: [],
  24. strangeness: undefined
  25. },
  26. mutations: {
  27. // ╷ ╶┬╴┌─╮┌─╮╭─┐┌─╮╷ ╷
  28. // │ │ │╶┤├┬╯├─┤├┬╯╰─┤
  29. // ╰─╴╶┴╴└─╯╵ ╰╵ ╵╵ ╰╶─╯
  30. 'SET_NODEBOOK' (state, nodes) {
  31. state.nodebook = nodes
  32. },
  33. 'ADD_NODEBOOK_NODE' (state, [stack, parentId, childId]) {
  34. if (stack === undefined) {
  35. stack = childId === undefined ? [parentId] : [parentId, childId]
  36. state.nodebook.push(stack)
  37. return
  38. }
  39. if (stack !== undefined && parentId !== undefined) {
  40. state.nodebook.push(state.nodebook.splice(state.nodebook.indexOf(stack), 1)[0])
  41. }
  42. if (childId) {
  43. if (stack.includes(childId)) {
  44. stack.push(stack.splice(stack.indexOf(childId), 1)[0])
  45. } else {
  46. stack.push(childId)
  47. }
  48. }
  49. },
  50. 'REMOVE_NODEBOOK_NODE' (state, [stack, nodeId]) {
  51. if (stack[0] === nodeId) {
  52. state.nodebook.splice(state.nodebook.indexOf(stack), 1)
  53. } else {
  54. stack.splice(stack.indexOf(nodeId), 1)
  55. }
  56. },
  57. 'ADD_NODE_TREE' (state, [id, tree]) {
  58. Vue.set(state.trees, id, tree)
  59. },
  60. // ╭─╮┌─╮╶┬╴╶┬╴╭─╮╭╮╷╭─╴
  61. // │ │├─╯ │ │ │ ││││╰─╮
  62. // ╰─╯╵ ╵ ╶┴╴╰─╯╵╰╯╶─╯
  63. 'SET_TAGS_OPTIONS' (state, nodes) {
  64. const tags = nodes.reduce((tags, node) => tags.concat(node.tags || []), [])
  65. state.tagsOptions = Array.from(new Set(tags.map(tag => tag.name)))
  66. },
  67. 'UPDATE_TAGS' (state, tags) {
  68. state.tags = tags
  69. },
  70. 'UPDATE_STRANGENESS' (state, strangenessLvl) {
  71. state.strangeness = strangenessLvl
  72. },
  73. 'SET_MODE' (state, mode) {
  74. state.mode = mode
  75. },
  76. 'SET_NODE_DEPART_ID' (state, id) {
  77. state.nodeDepartId = id
  78. },
  79. 'SET_RANDOM_NODES' (state, ids) {
  80. state.randomNodes = ids
  81. },
  82. 'SET_SEARCH' (state, str) {
  83. state.search = str
  84. }
  85. },
  86. actions: {
  87. // ╷ ╶┬╴┌─╮┌─╮╭─┐┌─╮╷ ╷
  88. // │ │ │╶┤├┬╯├─┤├┬╯╰─┤
  89. // ╰─╴╶┴╴└─╯╵ ╰╵ ╵╵ ╰╶─╯
  90. async 'INIT_LIBRARY' ({ state, commit, dispatch, rootState }) {
  91. const departIds = await dispatch('GET_ALL_NODES_IDS', 'depart')
  92. await dispatch('GET_NODES', { ids: departIds, dataLevel: 'initial' })
  93. if (state.nodebook && state.nodebook.length) {
  94. commit('UPDATE_OPTIONS_VISIBILITY', false)
  95. }
  96. return departIds
  97. },
  98. async 'INIT_LIBRARY_TREE' ({ state, commit, dispatch, rootState }) {
  99. const ids = await dispatch('INIT_LIBRARY')
  100. return dispatch('SET_NODE_DEPART_ID', ids[ids.length - 1])
  101. },
  102. async 'INIT_LIBRARY_MAP' ({ state, commit, dispatch }) {
  103. await dispatch('INIT_LIBRARY')
  104. return dispatch('ROLL_RANDOM_NODES')
  105. },
  106. async 'INIT_LIBRARY_LIST' ({ state, commit, dispatch }) {
  107. const departIds = await dispatch('INIT_LIBRARY')
  108. const nodes = await dispatch('GET_NODES', { ids: departIds, dataLevel: 'partial' })
  109. commit('SET_TAGS_OPTIONS', nodes)
  110. return nodes
  111. },
  112. async 'UPDATE_NODEBOOK' ({ state, commit, dispatch }, query) {
  113. const { mode = 'tree', nodebook } = parseNodesQueryParams(query)
  114. commit('SET_MODE', mode)
  115. const ids = [].concat(...nodebook)
  116. await dispatch('GET_NODES', { ids, dataLevel: 'full' })
  117. commit('SET_NODEBOOK', nodebook)
  118. commit('ADD_HISTORY_ENTRIES', ids)
  119. },
  120. // ╭╮╷╭─╮┌─╮┌─╴╭─╴
  121. // ││││ ││ │├─╴╰─╮
  122. // ╵╰╯╰─╯└─╯╰─╴╶─╯
  123. 'UPDATE_QUERY_NODES' ({ state }) {
  124. const query = {
  125. mode: state.mode,
  126. nodes: [...state.nodebook.map(ids => ids.join(','))]
  127. }
  128. if (router.currentRoute.name !== 'library') {
  129. router.push({ name: 'library', query })
  130. } else {
  131. router.push({ query })
  132. }
  133. },
  134. async 'OPEN_NODE' ({ state, commit, dispatch }, { parentId, childId }) {
  135. if (state.nodebook === undefined || !state.nodebook.length) {
  136. commit('UPDATE_OPTIONS_VISIBILITY', false)
  137. }
  138. const stack = state.nodebook.find(stack => stack[0] === parentId)
  139. commit('ADD_NODEBOOK_NODE', [stack, parentId, childId])
  140. dispatch('UPDATE_QUERY_NODES')
  141. commit('ADD_HISTORY_ENTRIES', [childId || parentId])
  142. },
  143. 'CLOSE_NODE' ({ state, commit, dispatch }, { parentId, childId }) {
  144. const stack = state.nodebook.find(stack => stack.includes(parentId) && (childId ? stack.includes(childId) : true))
  145. commit('REMOVE_NODEBOOK_NODE', [stack, childId || parentId])
  146. if (!state.nodebook.length) {
  147. commit('UPDATE_OPTIONS_VISIBILITY', true)
  148. }
  149. dispatch('UPDATE_QUERY_NODES')
  150. },
  151. // ╭─╮┌─╮╶┬╴╶┬╴╭─╮╭╮╷╭─╴
  152. // │ │├─╯ │ │ │ ││││╰─╮
  153. // ╰─╯╵ ╵ ╶┴╴╰─╯╵╰╯╶─╯
  154. async 'SET_NODE_DEPART_ID' ({ state, rootState, commit, dispatch, getters }, id) {
  155. if (state.nodeDepartId === id) return
  156. commit('SET_NODE_DEPART_ID', id)
  157. if (!(id in state.trees)) {
  158. const treeData = parseTree(await api.queryRecursiveNodes([id]))
  159. const nodes = await dispatch('GET_NODES', { ids: getUniqueNodesIds(treeData), dataLevel: 'initial' })
  160. commit('ADD_NODE_TREE', [id, buildTree(treeData, nodes)])
  161. }
  162. return getters.nodeTree
  163. },
  164. async 'ROLL_RANDOM_NODES' ({ state, rootState, commit, dispatch }) {
  165. commit('SET_RANDOM_NODES', undefined)
  166. const ids = getRandomIds(rootState.ids.depart)
  167. const departNodes = await dispatch('GET_NODES', { ids, dataLevel: 'partial' })
  168. const relatedIds = getRelatedNodesIds(departNodes, ids)
  169. const relatedNodes = await dispatch('GET_NODES', { ids: relatedIds, dataLevel: 'partial' })
  170. const nodes = [...departNodes, ...relatedNodes]
  171. commit('SET_RANDOM_NODES', [...ids, ...relatedIds])
  172. commit('SET_TAGS_OPTIONS', nodes)
  173. },
  174. 'UPDATE_QUERY_MODE' (store, mode) {
  175. if (router.currentRoute.query.mode === mode) return
  176. router.push({ query: { mode } })
  177. }
  178. },
  179. getters: {
  180. // LibraryOptions state
  181. mode: state => state.mode,
  182. nodeDepartId: state => state.nodeDepartId,
  183. search: state => state.search,
  184. tags: state => state.tags,
  185. strangeness: state => state.strangeness,
  186. // LibraryOptions options
  187. tagsOptions: state => state.tagsOptions,
  188. nodesDepartsOptions: (state, getters, rootState) => {
  189. const departIds = rootState.ids.depart
  190. if (departIds === undefined) return
  191. const nodes = departIds.map(id => rootState.nodes[id])
  192. if (nodes.some(node => node === undefined)) return
  193. return nodes.map(node => {
  194. const firstAuthor = node.authors !== null ? node.authors[0].name : 'Pad de noms'
  195. return {
  196. text: `${firstAuthor}, ${node.title} (${node.id})`,
  197. value: node.id
  198. }
  199. })
  200. },
  201. // Library
  202. nodebook: (state, getters, rootState) => {
  203. return state.nodebook.map(([parentId, ...childrenIds]) => {
  204. return {
  205. parent: rootState.nodes[parentId],
  206. children: childrenIds.map(id => rootState.nodes[id])
  207. }
  208. })
  209. },
  210. // LibraryList
  211. orderedTextsDepart: (state, getters, rootState) => {
  212. const departIds = rootState.ids.depart
  213. if (departIds === undefined || rootState.nodes[departIds[0]] === undefined) return
  214. const nodesDict = {}
  215. for (const node of rootState.ids.depart.map(id => rootState.nodes[id])) {
  216. if (!node.authors) {
  217. if (!('~' in nodesDict)) nodesDict['~'] = []
  218. nodesDict['~'].push(node)
  219. } else {
  220. node.authors.forEach((author, i) => {
  221. const firstChar = author.last_name[0].toUpperCase()
  222. if (!(firstChar in nodesDict)) nodesDict[firstChar] = []
  223. nodesDict[firstChar].push({ last_name: author.last_name, node })
  224. })
  225. }
  226. }
  227. return Object.entries(nodesDict).sort((a, b) => a[0] < b[0] ? -1 : 1).map(group => {
  228. if (group[0] === '~') return group
  229. return [group[0], group[1].sort((a, b) => {
  230. const prev = a.last_name.toLowerCase()
  231. const next = b.last_name.toLowerCase()
  232. if (prev === next) return 0
  233. return prev < next ? -1 : 1
  234. }).map(node => node.node)]
  235. })
  236. },
  237. // LibraryMap
  238. randomNodes: (state, getters, rootState) => {
  239. if (state.randomNodes === undefined) return
  240. return state.randomNodes.map(id => rootState.nodes[id])
  241. },
  242. // LibraryTree
  243. nodeTree: state => {
  244. return state.trees[state.nodeDepartId] || { nodes: [], links: [] }
  245. },
  246. // Commons
  247. activeNodes: state => {
  248. if (!state.nodebook) return []
  249. return state.nodebook.reduce((acc, ids) => [...acc, ...ids], [])
  250. }
  251. }
  252. }