library.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  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 }, { dataLevel = 'initial' } = {}) {
  91. await dispatch('GET_ALL_NODES_IDS', { variant: 'depart', dataLevel })
  92. if (state.nodebook && state.nodebook.length) {
  93. commit('UPDATE_OPTIONS_VISIBILITY', false)
  94. }
  95. return rootState.ids.depart
  96. },
  97. async 'INIT_LIBRARY_TREE' ({ state, commit, dispatch, rootState }) {
  98. const ids = await dispatch('INIT_LIBRARY')
  99. return dispatch('SET_NODE_DEPART_ID', ids[ids.length - 1])
  100. },
  101. async 'INIT_LIBRARY_MAP' ({ state, commit, dispatch }) {
  102. await dispatch('INIT_LIBRARY')
  103. return dispatch('ROLL_RANDOM_NODES')
  104. },
  105. async 'INIT_LIBRARY_LIST' ({ state, commit, dispatch }) {
  106. const departIds = await dispatch('INIT_LIBRARY', { dataLevel: 'partial' })
  107. const nodes = await dispatch('GET_NODES', { ids: departIds, dataLevel: 'partial' })
  108. commit('SET_TAGS_OPTIONS', nodes)
  109. return nodes
  110. },
  111. async 'UPDATE_NODEBOOK' ({ state, commit, dispatch }, query) {
  112. const { mode = 'tree', nodebook } = parseNodesQueryParams(query)
  113. commit('SET_MODE', mode)
  114. const ids = [].concat(...nodebook)
  115. await dispatch('GET_NODES', { ids, dataLevel: 'full' })
  116. commit('SET_NODEBOOK', nodebook)
  117. commit('ADD_HISTORY_ENTRIES', ids)
  118. },
  119. // ╭╮╷╭─╮┌─╮┌─╴╭─╴
  120. // ││││ ││ │├─╴╰─╮
  121. // ╵╰╯╰─╯└─╯╰─╴╶─╯
  122. 'UPDATE_QUERY_NODES' ({ state }) {
  123. const query = {
  124. mode: state.mode,
  125. nodes: [...state.nodebook.map(ids => ids.join(','))]
  126. }
  127. if (router.currentRoute.name !== 'library') {
  128. router.push({ name: 'library', query })
  129. } else {
  130. router.push({ query })
  131. }
  132. },
  133. async 'OPEN_NODE' ({ state, commit, dispatch }, { parentId, childId }) {
  134. if (state.nodebook === undefined || !state.nodebook.length) {
  135. commit('UPDATE_OPTIONS_VISIBILITY', false)
  136. }
  137. const stack = state.nodebook.find(stack => stack[0] === parentId)
  138. commit('ADD_NODEBOOK_NODE', [stack, parentId, childId])
  139. dispatch('UPDATE_QUERY_NODES')
  140. commit('ADD_HISTORY_ENTRIES', [childId || parentId])
  141. },
  142. 'CLOSE_NODE' ({ state, commit, dispatch }, { parentId, childId }) {
  143. const stack = state.nodebook.find(stack => stack.includes(parentId) && (childId ? stack.includes(childId) : true))
  144. commit('REMOVE_NODEBOOK_NODE', [stack, childId || parentId])
  145. if (!state.nodebook.length) {
  146. commit('UPDATE_OPTIONS_VISIBILITY', true)
  147. }
  148. dispatch('UPDATE_QUERY_NODES')
  149. },
  150. // ╭─╮┌─╮╶┬╴╶┬╴╭─╮╭╮╷╭─╴
  151. // │ │├─╯ │ │ │ ││││╰─╮
  152. // ╰─╯╵ ╵ ╶┴╴╰─╯╵╰╯╶─╯
  153. async 'SET_NODE_DEPART_ID' ({ state, rootState, commit, dispatch, getters }, id) {
  154. if (state.nodeDepartId === id) return
  155. commit('SET_NODE_DEPART_ID', id)
  156. if (!(id in state.trees)) {
  157. const treeData = parseTree(await api.queryRecursiveNodes([id]))
  158. const nodes = await dispatch('GET_NODES', { ids: getUniqueNodesIds(treeData), dataLevel: 'initial' })
  159. commit('ADD_NODE_TREE', [id, buildTree(treeData, nodes)])
  160. }
  161. return getters.nodeTree
  162. },
  163. async 'ROLL_RANDOM_NODES' ({ state, rootState, commit, dispatch }) {
  164. commit('SET_RANDOM_NODES', undefined)
  165. const ids = getRandomIds(rootState.ids.depart)
  166. const departNodes = await dispatch('GET_NODES', { ids, dataLevel: 'partial' })
  167. const relatedIds = getRelatedNodesIds(departNodes, ids)
  168. const relatedNodes = await dispatch('GET_NODES', { ids: relatedIds, dataLevel: 'partial' })
  169. const nodes = [...departNodes, ...relatedNodes]
  170. commit('SET_RANDOM_NODES', [...ids, ...relatedIds])
  171. commit('SET_TAGS_OPTIONS', nodes)
  172. },
  173. 'UPDATE_QUERY_MODE' (store, mode) {
  174. if (router.currentRoute.query.mode === mode) return
  175. router.push({ query: { mode } })
  176. }
  177. },
  178. getters: {
  179. // LibraryOptions state
  180. mode: state => state.mode,
  181. nodeDepartId: state => state.nodeDepartId,
  182. search: state => state.search,
  183. tags: state => state.tags,
  184. strangeness: state => state.strangeness,
  185. // LibraryOptions options
  186. tagsOptions: state => state.tagsOptions,
  187. nodesDeparts: (state, getters, rootState) => {
  188. const departIds = rootState.ids.depart
  189. if (departIds === undefined) return
  190. const nodes = departIds.map(id => rootState.nodes[id])
  191. if (nodes.some(node => node === undefined)) return
  192. return nodes
  193. },
  194. nodesDepartsOptions: (state, getters, rootState) => {
  195. const nodes = getters.nodesDeparts
  196. if (nodes === undefined) return
  197. return nodes.sort((a, b) => {
  198. const prev = a.authors[0].last_name.toLowerCase()
  199. const next = b.authors[0].last_name.toLowerCase()
  200. if (prev === next) return 0
  201. return prev < next ? -1 : 1
  202. })
  203. },
  204. // Library
  205. nodebook: (state, getters, rootState) => {
  206. return state.nodebook.map(([parentId, ...childrenIds]) => {
  207. return {
  208. parent: rootState.nodes[parentId],
  209. children: childrenIds.map(id => rootState.nodes[id])
  210. }
  211. })
  212. },
  213. // LibraryList
  214. orderedTextsDepart: (state, getters, rootState) => {
  215. const departIds = rootState.ids.depart
  216. if (departIds === undefined || rootState.nodes[departIds[0]] === undefined) return
  217. const nodesDict = {}
  218. for (const node of rootState.ids.depart.map(id => rootState.nodes[id])) {
  219. if (!node.authors) {
  220. if (!('~' in nodesDict)) nodesDict['~'] = []
  221. nodesDict['~'].push(node)
  222. } else {
  223. node.authors.forEach((author, i) => {
  224. const firstChar = author.last_name[0].toUpperCase()
  225. if (!(firstChar in nodesDict)) nodesDict[firstChar] = []
  226. nodesDict[firstChar].push({ last_name: author.last_name, node })
  227. })
  228. }
  229. }
  230. return Object.entries(nodesDict).sort((a, b) => a[0] < b[0] ? -1 : 1).map(group => {
  231. if (group[0] === '~') return group
  232. return [group[0], group[1].sort((a, b) => {
  233. const prev = a.last_name.toLowerCase()
  234. const next = b.last_name.toLowerCase()
  235. return prev.localeCompare(next)
  236. }).map(node => node.node)]
  237. })
  238. },
  239. // LibraryMap
  240. randomNodes: (state, getters, rootState) => {
  241. if (state.randomNodes === undefined) return
  242. return state.randomNodes.map(id => rootState.nodes[id])
  243. },
  244. // LibraryTree
  245. nodeTree: state => {
  246. return state.trees[state.nodeDepartId] || { nodes: [], links: [] }
  247. },
  248. // Commons
  249. activeNodes: state => {
  250. if (!state.nodebook) return []
  251. return state.nodebook.reduce((acc, ids) => [...acc, ...ids], [])
  252. }
  253. }
  254. }