update library opening strategy

This commit is contained in:
axolotle
2021-06-10 18:26:14 +02:00
parent ddff9313cb
commit ecc5253f38
9 changed files with 169 additions and 160 deletions
+4
View File
@@ -79,4 +79,8 @@ export default {
opacity: .25; opacity: .25;
} }
} }
#app > header {
border-bottom: $line;
}
</style> </style>
+2 -2
View File
@@ -53,8 +53,8 @@ $grid-breakpoints: (
xs: 0, xs: 0,
sm: 576px, sm: 576px,
md: 1024px, md: 1024px,
// lg: 992px, lg: 1200px,
// xl: 1200px xl: 1440px,
); );
$layout-bp: md; $layout-bp: md;
+1 -2
View File
@@ -10,8 +10,7 @@ import store from './store'
Vue.use(BootstrapVue, { Vue.use(BootstrapVue, {
BButton: { pill: true }, BButton: { pill: true },
BCardHeader: { headerBgVariant: 'white' }, BOverlay: { blur: null, spinnerType: 'grow', opacity: 0 }
BCardFooter: { footerBgVariant: 'white' }
}) })
Vue.use(Meta) Vue.use(Meta)
Vue.use(Vue2TouchEvents) Vue.use(Vue2TouchEvents)
+2 -1
View File
@@ -41,5 +41,6 @@
"siblings": "Textes rebonds", "siblings": "Textes rebonds",
"text": { "text": {
"read": "Voir le texte" "read": "Voir le texte"
} },
"from": "à partir de:"
} }
+46 -120
View File
@@ -1,101 +1,55 @@
<template> <template>
<div class="library"> <div class="library">
<!-- BACKGROUND (mode) --> <!-- BACKGROUND (mode) -->
<component :is="mode" @open="openText" /> <component :is="'library-' + mode" @open-node="openNode" />
<!-- FOREGROUND (texts) --> <!-- FOREGROUND (nodes) -->
<div v-if="parents.length" class="main-texts-container"> <node-book
<section v-if="nodebook.length"
v-for="(parent, i) in parents" :key="parent.id" :nodes="nodebook"
class="main-text-container text-break" class="library-node-book no-events-container"
:style="`--n: ${i};`" @open-node="openNode"
:class="{ 'text-blur': i !== parents.length - 1 }" @close-node="closeNode"
> />
<text-card
:id="parent.id"
:children="parent.children"
class="text-card-main"
@open="openText" @close="closeText"
@click.native="onMainTextClick(i)"
/>
<section class="sub-texts-container">
<text-card
v-for="(id, j) in parent.children" :key="id"
:id="id"
class="text-card-sub text-break"
:style="`--n: ${j};`"
@close="closeText(parent.id, $event)"
@click.native="onSubTextClick(i, j)"
/>
</section>
</section>
</div>
</div> </div>
</template> </template>
<script> <script>
import { CardList, CardMap, TreeMap } from './library' import { mapGetters } from 'vuex'
import TextCard from '@/components/text/TextCard'
import {
LibraryList,
LibraryMap,
LibraryTree
} from './library'
import { NodeBook } from '@/components/layouts'
export default { export default {
name: 'Library', name: 'Library',
components: { components: {
CardList, LibraryList,
CardMap, LibraryMap,
TreeMap, LibraryTree,
TextCard NodeBook
}, },
props: { props: {
mode: { type: String, required: true }, query: { type: Object, required: true }
texts: { type: Array, required: true }
}, },
computed: { computed: {
parents () { ...mapGetters(['mode', 'nodebook'])
return this.texts.map(text => { },
const [id, ...children] = text
return { id, children } watch: {
}) query (query) {
this.$store.dispatch('UPDATE_NODEBOOK', query)
} }
}, },
methods: { methods: {
openText (id, childId) {
const parent = this.parents.find(p => p.id === id)
if (parent && (childId === undefined || parent.children.includes(childId))) return
if (!parent) {
this.parents.push({ id, children: childId ? [childId] : [] })
} else if (childId) {
parent.children.push(childId)
}
this.updateQuery(this.parents)
},
closeText (id, childId) {
const parent = this.parents.find(p => p.id === id)
if (!childId) {
this.updateQuery(this.parents.filter(p => p !== parent))
} else {
parent.children = parent.children.filter(childId_ => childId_ !== childId)
this.updateQuery(this.parents)
}
},
updateQuery (parents) {
// Update the route's query (will not reload the page) and let vue determine what changed.
this.$router.push({
query: {
mode: this.mode,
texts: parents.map(parent => [parent.id, ...parent.children])
}
})
},
onMainTextClick (mainTextIndex) { onMainTextClick (mainTextIndex) {
if (mainTextIndex === this.texts.length - 1) return if (mainTextIndex === this.texts.length - 1) return
this.parents.push(this.parents.splice(mainTextIndex, 1)[0]) this.parents.push(this.parents.splice(mainTextIndex, 1)[0])
@@ -118,7 +72,19 @@ export default {
if (needUpdate) { if (needUpdate) {
this.updateQuery(this.parents) this.updateQuery(this.parents)
} }
},
openNode (parentId, childId) {
this.$store.dispatch('OPEN_NODE', [parentId, childId])
},
closeNode (parentId, childId) {
this.$store.dispatch('CLOSE_NODE', [parentId, childId])
} }
},
created () {
this.$store.dispatch('UPDATE_NODEBOOK', this.query)
} }
} }
</script> </script>
@@ -126,53 +92,13 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.library { .library {
height: 100%; height: 100%;
width: 100%;
overflow: hidden;
}
.main-texts-container { &-node-book {
position: relative; position: relative;
top: -100%; margin-bottom: -100%;
// compensate header border
height: 100%; height: calc(100% + 2px);
pointer-events: none; top: calc(-100% - 2px);
}
.main-text-container {
border-top: 2px solid $black;
display: flex;
width: 100%;
> * {
flex-basis: 50%;
} }
} }
.sub-texts-container {
position: relative;
height: 100%;
}
.text-card {
pointer-events: auto;
width: 100%;
}
.text-break {
position: absolute;
height: calc(100% - (var(--n) * #{$text-card-header-height}));
top: calc(var(--n) * #{$text-card-header-height});
z-index: var(--n);
}
.text-blur::before {
content: '';
pointer-events: none;
position: absolute;
z-index: 1;
display: block;
width: 100%;
height: 100%;
background-color: rgba($white, .5);
}
</style> </style>
+19 -20
View File
@@ -6,14 +6,14 @@
> >
<b-form-radio-group <b-form-radio-group
id="mode-select" id="mode-select"
v-model="currentMode" :options="modes" v-model="activeMode" :options="modes"
name="mode-select" :aria-describedby="ariaDescribedby" name="mode-select" :aria-describedby="ariaDescribedby"
buttons button-variant="outline-dark" buttons button-variant="outline-dark"
/> />
</b-form-group> </b-form-group>
<b-form-group <b-form-group
v-if="currentMode !== 'tree-map'" v-if="mode !== 'tree'"
:label="$t('options.filters.title')" :label="$t('options.filters.title')"
> >
<b-form-group <b-form-group
@@ -22,8 +22,7 @@
> >
<multiple-tags-select <multiple-tags-select
id="tags-select" :button-text="$t('options.filters.choices.tags')" id="tags-select" :button-text="$t('options.filters.choices.tags')"
v-model="selectedTags" :options="tags" v-model="selectedTags" :options="tagsOptions"
:disabled="true"
/> />
</b-form-group> </b-form-group>
@@ -80,20 +79,13 @@ export default {
MultipleTagsSelect MultipleTagsSelect
}, },
props: {
show: { type: Boolean, required: true },
mode: { type: String, required: true },
tags: { type: Array, default: () => ([]) }
},
data () { data () {
return { return {
modes: [ modes: [
{ text: this.$t('options.display.choices.tree-map'), value: 'tree-map' }, { text: this.$t('options.display.choices.tree-map'), value: 'tree' },
{ text: this.$t('options.display.choices.card-map'), value: 'card-map' }, { text: this.$t('options.display.choices.card-map'), value: 'map' },
{ text: this.$t('options.display.choices.card-list'), value: 'card-list' } { text: this.$t('options.display.choices.card-list'), value: 'list' }
], ],
currentMode: this.mode,
selectedTags: [], selectedTags: [],
strangeness: 0, strangeness: 0,
textDepartId: undefined, textDepartId: undefined,
@@ -102,7 +94,11 @@ export default {
}, },
computed: { computed: {
...mapGetters(['nodesDepartsOptions']), ...mapGetters(['mode', 'nodebook', 'nodesDepartsOptions', 'tagsOptions']),
show () {
return this.nodebook.length === 0
},
nodeDepartId: { nodeDepartId: {
get () { get () {
@@ -111,12 +107,15 @@ export default {
set (value) { set (value) {
this.$store.dispatch('SET_NODE_DEPART_ID', value) this.$store.dispatch('SET_NODE_DEPART_ID', value)
} }
} },
},
watch: { activeMode: {
currentMode (mode) { get () {
this.$router.push({ query: { mode } }) return this.mode
},
set (value) {
this.$store.dispatch('UPDATE_QUERY_MODE', value)
}
} }
} }
} }
+1 -13
View File
@@ -28,19 +28,7 @@ export default [
options: () => import(/* webpackChunkName: "library" */ '@/pages/library/LibraryOptions') options: () => import(/* webpackChunkName: "library" */ '@/pages/library/LibraryOptions')
}, },
props: { props: {
default: ({ query }) => { default: ({ query }) => ({ query })
let { mode = 'tree-map', texts = [] } = query
if (typeof texts === 'string') texts = [texts]
// In case of a reload or direct link, vue-router doesn't turn the query string into an array.
if (texts && texts.length && typeof texts[0] === 'string') {
texts = texts.map(text => text.split(',').map(id => parseInt(id)))
}
return { mode, texts }
},
options: ({ query }) => ({
show: !('texts' in query && query.texts.length),
mode: query.mode || 'tree-map'
})
} }
}, },
+83 -2
View File
@@ -1,10 +1,12 @@
import Vue from 'vue' import Vue from 'vue'
import router from '@/router'
import api from '@/api' import api from '@/api'
import { import {
AllTags AllTags
} from '@/api/queries' } from '@/api/queries'
import { import {
parseNodesQueryParams,
getUniqueNodesIds, getUniqueNodesIds,
getRelatedNodesIds, getRelatedNodesIds,
buildTree buildTree
@@ -16,10 +18,38 @@ export default {
tags: undefined, tags: undefined,
strangeness: [0, 1, 2, 3, 4], strangeness: [0, 1, 2, 3, 4],
nodeDepartId: undefined, nodeDepartId: undefined,
trees: {} trees: {},
mode: undefined,
nodebook: undefined
}, },
mutations: { mutations: {
'SET_MODE' (state, mode) {
state.mode = mode
},
'SET_NODEBOOK' (state, nodes) {
state.nodebook = nodes
},
'ADD_NODEBOOK_NODE' (state, [stack, parentId, childId]) {
console.log('ADD_NODEBOOK_NODE', stack, parentId, childId)
if (stack === undefined) {
stack = childId === undefined ? [parentId] : [parentId, childId]
state.nodebook.push(stack)
} else {
stack.push(childId)
}
},
'REMOVE_NODEBOOK_NODE' (state, [stack, nodeId]) {
if (stack[0] === nodeId) {
state.nodebook.splice(state.nodebook.indexOf(stack), 1)
} else {
stack.splice(stack.indexOf(nodeId), 1)
}
},
'SET_TAGS' (state, tags) { 'SET_TAGS' (state, tags) {
state.tags = tags state.tags = tags
}, },
@@ -34,9 +64,30 @@ export default {
}, },
actions: { actions: {
async 'UPDATE_NODEBOOK' ({ state, commit, dispatch }, query) {
const { mode = 'tree', nodebook } = parseNodesQueryParams(query)
commit('SET_MODE', mode)
await dispatch('GET_NODES', { ids: [].concat(...nodebook), dataLevel: 'full' })
commit('SET_NODEBOOK', nodebook)
},
'UPDATE_QUERY_NODES' ({ state }, from) {
const query = {
mode: state.mode,
nodes: [...state.nodebook.map(ids => ids.join(','))]
}
router.push({ query })
},
'UPDATE_QUERY_MODE' (store, mode) {
if (router.currentRoute.query.mode === mode) return
router.push({ query: { mode } })
},
async 'INIT_LIBRARY' ({ state, commit, dispatch, rootState }) { async 'INIT_LIBRARY' ({ state, commit, dispatch, rootState }) {
const departIds = await dispatch('GET_ALL_NODES_IDS', 'depart') const departIds = await dispatch('GET_ALL_NODES_IDS', 'depart')
await dispatch('GET_NODES', { ids: departIds, dataLevel: 'initial' }) await dispatch('GET_NODES', { ids: departIds, dataLevel: 'initial' })
dispatch('GET_ALL_TAGS')
return departIds return departIds
}, },
@@ -78,12 +129,42 @@ export default {
} }
return getters.nodeTree return getters.nodeTree
},
async 'OPEN_NODE' ({ state, commit, dispatch }, [parentId, childId]) {
const stack = state.nodebook.find(stack => stack[0] === parentId)
if (stack && (childId === undefined || stack.includes(childId))) return
commit('ADD_NODEBOOK_NODE', [stack, parentId, childId])
dispatch('UPDATE_QUERY_NODES')
},
'CLOSE_NODE' ({ state, commit, dispatch }, [parentId, childId]) {
const stack = state.nodebook.find(stack => stack.includes(parentId) && (childId ? stack.includes(childId) : true))
commit('REMOVE_NODEBOOK_NODE', [stack, childId || parentId])
dispatch('UPDATE_QUERY_NODES')
} }
}, },
getters: { getters: {
mode: (state) => state.mode,
nodebook: (state, getters, rootState) => {
if (!state.nodebook) return []
return state.nodebook.map(([parentId, ...childrenIds]) => {
return {
parent: rootState.nodes[parentId],
children: childrenIds.map(id => rootState.nodes[id])
}
})
},
activeNodes: (state) => {
if (!state.nodebook) return []
return state.nodebook.reduce((acc, ids) => [...acc, ...ids], [])
},
tagsOptions: state => { tagsOptions: state => {
if (state.tags === undefined) return if (state.tags === undefined) return []
return state.tags.map(tag => ({ value: tag.id, text: tag.name })) return state.tags.map(tag => ({ value: tag.id, text: tag.name }))
}, },
+11
View File
@@ -29,6 +29,17 @@ export const VARIANT_IDS = Object.fromEntries(
) )
export function parseNodesQueryParams ({ mode, nodes = [] }) {
let nodebook = typeof nodes === 'string' ? [nodes] : nodes
nodebook = nodebook.map(ids => {
return typeof ids === 'string'
? ids.split(',').map(id => parseInt(id))
: [...ids]
})
return { mode, nodebook }
}
export function getUniqueNodesIds (tree) { export function getUniqueNodesIds (tree) {
function extractId (ids, node) { function extractId (ids, node) {
ids.add(node.id) ids.add(node.id)