NodeView.vue 5.8 KB

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