NodeView.vue 5.7 KB

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