NodeView.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. <template>
  2. <article
  3. class="node-view"
  4. :class="['node-view-' + mode, 'node-view-' + type, 'node-view-' + nodeVariant, { 'preview': preview }]"
  5. >
  6. <div v-if="!loading" class="node-view-wrapper" :id="`node-${mode}-${node.id}`">
  7. <component
  8. :is="'node-view-header-' + nodeType"
  9. v-bind="{ node, mode, showOrigin }"
  10. class="node-view-header"
  11. :class="{ scrolling }"
  12. />
  13. <node-view-body v-bind="{ node, type: nodeType, mode }" :class="{ scrolling }"/>
  14. <node-view-footer
  15. v-bind="{ node, mode, type: nodeType, preview, showOrigin }"
  16. class="node-view-footer"
  17. />
  18. </div>
  19. <node-view-child-list-group
  20. v-if="!loading && preview && node.children && node.children.length"
  21. :children="node.children"
  22. />
  23. <b-overlay
  24. :show="loading"
  25. :spinner-variant="nodeVariant === 'depart' ? 'dark' : 'light'"
  26. no-wrap
  27. />
  28. </article>
  29. </template>
  30. <script>
  31. import {
  32. NodeViewChildListGroup,
  33. NodeViewHeaderRef,
  34. NodeViewHeaderProd,
  35. NodeViewBody,
  36. NodeViewFooter
  37. } from '@/components/nodes'
  38. export default {
  39. name: 'NodeView',
  40. components: {
  41. NodeViewChildListGroup,
  42. NodeViewHeaderRef,
  43. NodeViewHeaderProd,
  44. NodeViewFooter,
  45. NodeViewBody
  46. },
  47. props: {
  48. node: { type: Object, default: undefined },
  49. variant: { type: String, default: 'dark' },
  50. type: { type: String, default: 'ref' },
  51. forceType: { type: String, default: null },
  52. mode: { type: String, default: 'view' },
  53. preview: { type: Boolean, default: false },
  54. showOrigin: { type: Boolean, default: false }
  55. },
  56. data () {
  57. return {
  58. scrolling: false
  59. }
  60. },
  61. computed: {
  62. loading () {
  63. const dataLevel = this.mode === 'view' ? 2 : 1
  64. return this.node === undefined || this.node.dataLevel < dataLevel
  65. },
  66. nodeVariant () {
  67. return this.node !== undefined ? this.node.variant : this.variant
  68. },
  69. nodeType () {
  70. if (this.forceType) return this.forceType
  71. return this.node !== undefined ? this.node.type : this.type
  72. }
  73. },
  74. methods: {
  75. onScroll ({ target }) {
  76. const prev = this.scrolling
  77. this.scrolling = target.scrollTop !== 0
  78. if (this.scrolling && !prev) {
  79. this.$el.scrollTo(0, 1)
  80. }
  81. }
  82. },
  83. mounted () {
  84. this.scrolling = this.$el.scrollTop !== 0
  85. this.$el.addEventListener('scroll', this.onScroll)
  86. },
  87. beforeDestroy () {
  88. this.$el.removeEventListener('scroll', this.onScroll)
  89. }
  90. }
  91. </script>
  92. <style lang="scss">
  93. .node-view {
  94. position: relative;
  95. &-wrapper {
  96. display: flex;
  97. flex-direction: column;
  98. }
  99. // ╭─╴╭─┐┌─╮┌─╮
  100. // │ ├─┤├┬╯│ │
  101. // ╰─╴╵ ╵╵ ╰└─╯
  102. &-card {
  103. width: 100%;
  104. .node-view-wrapper {
  105. pointer-events: none;
  106. width: 100%;
  107. height: 100%;
  108. overflow: hidden;
  109. }
  110. &.node-view-depart {
  111. box-shadow: .5rem .5rem 1rem rgba($black, .25);
  112. .node-view-child-list-group {
  113. box-shadow: .5rem .5rem 1rem rgba($black, .25);
  114. }
  115. }
  116. }
  117. &-card &-header {
  118. padding: $node-card-spacer-sm-y $node-card-spacer-sm-x 0;
  119. @include media-breakpoint-up($size-bp) {
  120. padding: $node-card-spacer-y $node-card-spacer-x 0;
  121. }
  122. }
  123. &-card &-footer {
  124. padding: $node-card-spacer-sm-y $node-card-spacer-sm-x $node-card-spacer-sm-y * 2;
  125. position: sticky;
  126. bottom: 0;
  127. @include media-breakpoint-up($size-bp) {
  128. padding: $node-card-spacer-y $node-card-spacer-x $node-card-spacer-y * 2;
  129. }
  130. }
  131. // ╷ ╷╶┬╴┌─╴╷╷╷
  132. // │╭╯ │ ├─╴│││
  133. // ╰╯ ╶┴╴╰─╴╰╯╯
  134. &-view {
  135. width: 100%;
  136. @include media-breakpoint-up($layout-bp) {
  137. height: 100%;
  138. overflow-y: auto;
  139. .node-view-wrapper {
  140. min-height: 100%;
  141. }
  142. }
  143. }
  144. &-view &-header {
  145. padding: $node-view-spacer-sm-y $node-view-spacer-sm-x 0;
  146. @include media-breakpoint-up($size-bp) {
  147. padding: $node-view-spacer-y $node-view-spacer-x 0;
  148. }
  149. @include media-breakpoint-up($layout-bp) {
  150. position: sticky;
  151. top: 0;
  152. z-index: 1;
  153. &-ref.scrolling {
  154. h4 {
  155. font-size: 1.2rem;
  156. }
  157. .edition {
  158. font-size: 0.8rem;
  159. }
  160. }
  161. }
  162. }
  163. &-view &-body {
  164. @include media-breakpoint-up($layout-bp) {
  165. &-ref.scrolling {
  166. padding-top: 4rem;
  167. }
  168. }
  169. }
  170. &-view &-footer {
  171. padding: $node-view-spacer-sm-y $node-view-spacer-sm-x;
  172. @include media-breakpoint-up($size-bp) {
  173. padding: $node-view-spacer-y $node-view-spacer-x;
  174. }
  175. @include media-breakpoint-up($layout-bp) {
  176. position: sticky;
  177. bottom: 0;
  178. }
  179. }
  180. @each $color, $value in $theme-colors {
  181. &-#{$color} {
  182. background-color: lighten($value, 3.25%);
  183. .node-view-header,
  184. .node-view-footer {
  185. background-color: lighten($value, 3.25%);
  186. }
  187. }
  188. }
  189. }
  190. </style>