123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- <template>
- <b-overlay :show="nodeTree.nodes.length === 0" class="h-100" z-index="0">
- <map-zoom
- id="library-tree"
- :min-zoom="0.3" :max-zoom="1" :center="center"
- :key="nodeDepartId"
- >
- <path
- v-for="link in nodeTree.links"
- :key="`${link.source.data.id}_${link.target.data.id}`"
- :d="lineGenerator([link.source, link.target])"
- :class="['svg-link', (link.linkType || link.target.data.linkType)]"
- />
- <circle
- v-for="node in nodeTree.nodes"
- :key="'circle' + node.data.id"
- :cx="node.x"
- :cy="node.y"
- class="svg-dot"
- :id="'preview-node-' + node.data.id"
- :class="['svg-dot-' + node.data.variant, { 'origin': node.parent === null }]"
- tabindex="0"
- @focus="activeNode = node"
- @mouseenter="activeNode = node"
- @mouseleave="activeNode = null"
- @blur="activeNode = null"
- @click.stop="onNodeClick(node.data)"
- @keyup.enter="onNodeClick(node.data)"
- />
- <g>
- <rect class="svg-overlay" />
- <foreignObject
- v-if="activeNode"
- :x="activeNode.x"
- :y="activeNode.y"
- >
- <dot-button
- :variant="activeNode.data.variant"
- active hovered
- @click="onNodeClick(activeNode.data)"
- >
- <template v-if="activeNode.data.type === 'prod' && !(activeNode.data.preTitle || activeNode.data.italTitle)">
- {{ $t('variants.' + activeNode.data.variant) }}<br>
- </template>
- <node-view-title v-else :node="activeNode.data" block />
- </dot-button>
- </foreignObject>
- </g>
- </map-zoom>
- <legend-toggle>
- <h6>Mode Arborescent</h6>
- <p>Ce mode vous permet d'observer l'arbre des relations d'un texte de départ.</p>
- Types de relation :
- <ul>
- <li><span class="children" /> Textes produits</li>
- <li><span class="siblings" /> Textes rebonds</li>
- </ul>
- </legend-toggle>
- <node-preview-zone v-model="previewNode" :nodes="dataNodes" @open-node="onPreviewNodeClick" />
- </b-overlay>
- </template>
- <script>
- import { mapGetters } from 'vuex'
- import { line } from 'd3-shape'
- import { forceSimulation, forceLink, forceManyBody, forceX, forceY } from 'd3-force'
- import { MapZoom, NodePreviewZone } from '@/components/layouts'
- import { NodeViewTitle } from '@/components/nodes'
- export default {
- name: 'LibraryTree',
- components: {
- MapZoom,
- NodePreviewZone,
- NodeViewTitle
- },
- data () {
- return {
- activeNode: null,
- previewNode: null
- }
- },
- computed: {
- ...mapGetters(['nodeDepartId', 'nodeTree']),
- dataNodes () {
- return this.nodeTree.nodes.map(node => node.data)
- },
- // ONE TIME GETTER
- simulation () {
- return forceSimulation()
- .force('charge', forceManyBody().strength((d) => {
- if (d.data.linkType === 'parents') return -2000
- if (d.data.linkType === 'siblings') return -500
- return -1000
- }))
- .force('link', forceLink().id(d => d.id).distance((d) => {
- if (d.linkType === 'parents') return 200
- if (d.linkType === 'siblings') return 100
- return 0
- }).strength(0.5))
- .force('x', forceX())
- .force('y', forceY())
- },
- center () {
- const { x, y } = this.nodeTree.nodes.length ? this.nodeTree.nodes[0] : { x: 0, y: 0 }
- return { x, y }
- },
- // ONE TIME GETTER
- lineGenerator () {
- return line().x(node => node.x).y(node => node.y)
- }
- },
- watch: {
- nodeTree (tree) {
- this.activeNode = null
- this.previewNode = null
- this.simulation.nodes(tree.nodes)
- this.simulation.force('link').links(tree.links)
- this.simulation.alpha(0.5).restart()
- }
- },
- methods: {
- onNodeClick (node) {
- this.$store.dispatch('GET_NODE', { id: node.id, dataLevel: 'partial' })
- this.previewNode = node
- this.$root.$emit('bv::show::popover', 'preview-node-' + node.id)
- },
- onPreviewNodeClick (ids) {
- this.$root.$emit('bv::hide::popover', 'preview-node-' + this.previewNode.id)
- this.$emit('open-node', ids)
- this.previewNode = null
- }
- },
- created () {
- this.$store.dispatch('INIT_LIBRARY_TREE')
- }
- }
- </script>
- <style lang="scss" scoped>
- .svg {
- &-link {
- stroke: grey;
- vector-effect: non-scaling-stroke;
- &.siblings {
- stroke-dasharray: 4;
- }
- &.parents {
- stroke: red;
- opacity: .3;
- }
- }
- &-dot {
- cursor: pointer;
- r: 9.5px;
- @each $color, $value in $theme-colors {
- &-#{$color} {
- fill: $value;
- @if $color == 'depart' {
- stroke: $black;
- stroke-width: 3px;
- vector-effect: non-scaling-stroke;
- }
- }
- }
- &.origin {
- r: 15px;
- }
- }
- &-overlay {
- width: 100%;
- height: 100%;
- x: -50%;
- y: -50%;
- fill: transparent;
- pointer-events: none;
- }
- }
- foreignObject {
- height: 1px;
- width: 1px;
- overflow: visible;
- }
- .dot-btn {
- @media (hover: none) {
- display: none;
- }
- transform: translate(-50%, -50%);
- pointer-events: none;
- min-width: 200px;
- word-break: unset;
- white-space: unset;
- border-radius: 19px !important;
- .node-view-title {
- min-width: 200px;
- max-width: 400px;
- padding-bottom: .25rem;
- }
- &-depart {
- box-shadow: none;
- }
- h6 {
- margin: 0;
- }
- }
- .legend-toggle {
- ul {
- list-style: none;
- padding: 0;
- margin-top: .5rem;
- li {
- font-style: italic;
- margin-left: 1rem;
- }
- }
- span {
- display: inline-block;
- height: 0px;
- width: 15px;
- margin-right: .5rem;
- }
- .children {
- border-top: 3px dashed grey;
- }
- .siblings {
- border-top: 3px solid grey;
- }
- }
- </style>
|