LibraryMap.vue 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. <template>
  2. <b-overlay :show="nodes === undefined" class="h-100" z-index="0">
  3. <map-zoom
  4. id="library-map" ref="map"
  5. :min-zoom="0.2" :max-zoom="2" :initial-zoom="0.5"
  6. >
  7. <foreignObject
  8. v-for="node in filteredNodes" :key="'f-' + node.data.id"
  9. :style="`--x: ${node.x}px; --y: ${node.y}px; --r: ${node.rotate}deg;`"
  10. >
  11. <node-view
  12. :id="'preview-node-' + node.data.id"
  13. :class="{ 'hidden': previewNode === node.data }"
  14. :node="node.data"
  15. mode="card"
  16. :hidden="node.hidden"
  17. @click.native="onNodeClick(node.data)"
  18. :style="`--opacity: ${getStrangenessOpacity(strangeness, node.data)};`"
  19. />
  20. </foreignObject>
  21. </map-zoom>
  22. <legend-toggle>
  23. <h6>Mode Aléatoire</h6>
  24. <p>Ce mode vous propose 3 textes de départ tirés aléatoirement ainsi que leurs textes produits et leurs textes rebonds.</p>
  25. <p>Pour tirer aléatoirement 3 nouveaux textes, cliquez sur <i>Mélanger</i> dans le menu d'options.</p>
  26. </legend-toggle>
  27. <node-preview-zone
  28. v-if="dataNodes"
  29. v-model="previewNode" :nodes="dataNodes"
  30. @open-node="onPreviewNodeClick"
  31. />
  32. </b-overlay>
  33. </template>
  34. <script>
  35. import { randomUniform } from 'd3'
  36. import { forceSimulation, forceCollide, forceManyBody } from 'd3-force'
  37. import { mapGetters } from 'vuex'
  38. import { searchInNode, tagsInNode, getRelation, getStrangenessOpacity } from '@/store/utils'
  39. import { MapZoom, NodePreviewZone } from '@/components/layouts'
  40. import { NodeView } from '@/components/nodes'
  41. export default {
  42. name: 'LibraryMap',
  43. components: {
  44. MapZoom,
  45. NodePreviewZone,
  46. NodeView
  47. },
  48. data () {
  49. return {
  50. simulation: forceSimulation(),
  51. nodes: undefined,
  52. previewNode: null
  53. }
  54. },
  55. computed: {
  56. ...mapGetters(['randomNodes', 'search', 'tags', 'strangeness']),
  57. filteredNodes () {
  58. if (!this.nodes) return
  59. const search = this.search.toLowerCase()
  60. this.nodes.forEach(node => {
  61. Object.assign(node, {
  62. hidden: !(tagsInNode(this.tags, node.data.tags) && searchInNode(search, node.data))
  63. })
  64. })
  65. return this.nodes
  66. },
  67. dataNodes () {
  68. if (!this.nodes) return
  69. return this.nodes.map(node => node.data)
  70. }
  71. },
  72. methods: {
  73. getStrangenessOpacity,
  74. onOpen (node) {
  75. this.$parent.$emit('open-node', getRelation(node))
  76. },
  77. onNodeClick (node) {
  78. this.previewNode = node
  79. this.$root.$emit('bv::show::popover', 'preview-node-' + node.id)
  80. },
  81. onPreviewNodeClick (ids) {
  82. this.$root.$emit('bv::hide::popover', 'preview-node-' + this.previewNode.id)
  83. this.$emit('open-node', ids)
  84. this.previewNode = null
  85. }
  86. },
  87. watch: {
  88. randomNodes (randomNodes) {
  89. if (!randomNodes) {
  90. this.nodes = undefined
  91. return
  92. }
  93. // JS-BP
  94. const radius = window.innerWidth < 769 ? 700 / 1.5 : 700
  95. this.nodes = randomNodes.map((node, i) => {
  96. return { x: 0, rotate: randomUniform(-25, 25)(), data: node }
  97. })
  98. this.simulation.nodes(this.nodes)
  99. .force('attract', forceManyBody().strength(1))
  100. .force('collision', forceCollide().radius(radius / 2))
  101. .alpha(0.5).restart()
  102. this.$refs.map.reset()
  103. }
  104. },
  105. created () {
  106. this.$store.dispatch('INIT_LIBRARY_MAP')
  107. }
  108. }
  109. </script>
  110. <style lang="scss">
  111. #library-map {
  112. foreignObject {
  113. width: $node-card-width-sm;
  114. height: $node-card-height-sm;
  115. x: var(--x);
  116. y: var(--y);
  117. transform-origin: var(--x) var(--y);
  118. transform: rotate(var(--r)) translate(-#{$node-card-width / 2}, -#{$node-card-height / 2});
  119. overflow: visible;
  120. @include media-breakpoint-up($size-bp) {
  121. width: $node-card-width;
  122. height: $node-card-height;
  123. }
  124. .node-view {
  125. cursor: pointer;
  126. }
  127. }
  128. .node-view {
  129. width: $node-card-width-sm;
  130. opacity: var(--opacity);
  131. @include media-breakpoint-up($size-bp) {
  132. width: $node-card-width;
  133. }
  134. &-wrapper {
  135. height: $node-card-height-sm;
  136. @include media-breakpoint-up($size-bp) {
  137. height: $node-card-height;
  138. }
  139. }
  140. &.hidden {
  141. border: $line;
  142. border-style: dashed;
  143. background-color: transparent;
  144. .node-view-wrapper {
  145. opacity: 0;
  146. }
  147. }
  148. }
  149. }
  150. </style>