NodeView.vue 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  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"
  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"
  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. }
  62. },
  63. computed: {
  64. loading () {
  65. const dataLevel = this.mode === 'view' ? 2 : 1
  66. return this.node === undefined || this.node.dataLevel < dataLevel
  67. },
  68. nodeVariant () {
  69. return this.node !== undefined ? this.node.variant : this.variant
  70. },
  71. nodeType () {
  72. if (this.forceType) return this.forceType
  73. return this.node !== undefined ? this.node.type : this.type
  74. }
  75. },
  76. methods: {
  77. onScroll ({ target = this.$el }, fix = false) {
  78. const prev = this.scrollValue
  79. this.scrollValue = target.scrollTop
  80. if (this.scrollValue && !prev) {
  81. const h = this.$refs.header.$el.getBoundingClientRect().height
  82. this.$nextTick(() => {
  83. this.h = h - this.$refs.header.$el.getBoundingClientRect().height
  84. })
  85. }
  86. if (fix) {
  87. setTimeout(() => {
  88. if (this.scrollValue > 0 && target.scrollTop === 0) {
  89. target.scrollTop = this.scrollValue
  90. }
  91. }, 50)
  92. }
  93. },
  94. },
  95. mounted () {
  96. this.scrolling = this.$el.scrollTop !== 0
  97. this.$el.addEventListener('scroll', this.onScroll)
  98. },
  99. beforeDestroy () {
  100. this.$el.removeEventListener('scroll', this.onScroll)
  101. }
  102. }
  103. </script>
  104. <style lang="scss">
  105. .node-view {
  106. position: relative;
  107. &-wrapper {
  108. display: flex;
  109. flex-direction: column;
  110. }
  111. // ╭─╴╭─┐┌─╮┌─╮
  112. // │ ├─┤├┬╯│ │
  113. // ╰─╴╵ ╵╵ ╰└─╯
  114. &-card {
  115. width: 100%;
  116. .node-view-wrapper {
  117. pointer-events: none;
  118. width: 100%;
  119. height: 100%;
  120. overflow: hidden;
  121. }
  122. &.node-view-depart {
  123. box-shadow: .5rem .5rem 1rem rgba($black, .25);
  124. .node-view-child-list-group {
  125. box-shadow: .5rem .5rem 1rem rgba($black, .25);
  126. }
  127. }
  128. }
  129. &-card &-header {
  130. padding: $node-card-spacer-sm-y $node-card-spacer-sm-x 0;
  131. @include media-breakpoint-up($size-bp) {
  132. padding: $node-card-spacer-y $node-card-spacer-x 0;
  133. }
  134. }
  135. &-card &-footer {
  136. padding: $node-card-spacer-sm-y $node-card-spacer-sm-x $node-card-spacer-sm-y * 2;
  137. position: sticky;
  138. bottom: 0;
  139. @include media-breakpoint-up($size-bp) {
  140. padding: $node-card-spacer-y $node-card-spacer-x $node-card-spacer-y * 2;
  141. }
  142. }
  143. // ╷ ╷╶┬╴┌─╴╷╷╷
  144. // │╭╯ │ ├─╴│││
  145. // ╰╯ ╶┴╴╰─╴╰╯╯
  146. &-view {
  147. width: 100%;
  148. @include media-breakpoint-up($layout-bp) {
  149. height: 100%;
  150. overflow-y: auto;
  151. .node-view-wrapper {
  152. min-height: 100%;
  153. }
  154. }
  155. }
  156. &-view &-header {
  157. padding: $node-view-spacer-sm-y $node-view-spacer-sm-x 0;
  158. @include media-breakpoint-up($size-bp) {
  159. padding: $node-view-spacer-y $node-view-spacer-x $node-view-spacer-y * .75;
  160. h4 {
  161. margin-bottom: 0;
  162. }
  163. }
  164. @include media-breakpoint-up($layout-bp) {
  165. position: sticky;
  166. top: 0;
  167. z-index: 1;
  168. &-ref.scrolling {
  169. h4 {
  170. font-size: 1.2rem;
  171. }
  172. .edition {
  173. font-size: 0.8rem;
  174. }
  175. .btn-url {
  176. width: 20px;
  177. height: 20px;
  178. }
  179. }
  180. }
  181. }
  182. &-view &-body {
  183. @include media-breakpoint-up($layout-bp) {
  184. max-height: 100%;
  185. &.scrolling {
  186. clip-path: inset(var(--scroll-top) 0px 0px 0px);
  187. &.node-view-body-ref {
  188. padding-top: var(--h-height);
  189. }
  190. }
  191. }
  192. }
  193. &-view &-footer {
  194. padding: $node-view-spacer-sm-y $node-view-spacer-sm-x;
  195. @include media-breakpoint-up($size-bp) {
  196. padding: $node-view-spacer-y $node-view-spacer-x;
  197. }
  198. @include media-breakpoint-up($layout-bp) {
  199. position: sticky;
  200. bottom: 0;
  201. }
  202. }
  203. @each $color, $value in $theme-colors {
  204. &-#{$color} {
  205. background-color: lighten($value, 3.25%);
  206. .node-view-header,
  207. .node-view-footer {
  208. background-color: lighten($value, 3.25%);
  209. }
  210. }
  211. }
  212. }
  213. </style>