update library opening strategy
This commit is contained in:
@@ -79,4 +79,8 @@ export default {
|
|||||||
opacity: .25;
|
opacity: .25;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#app > header {
|
||||||
|
border-bottom: $line;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -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
@@ -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'
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -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 }))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user