@slidev/client 0.48.0-beta.20 → 0.48.0-beta.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/builtin/CodeBlockWrapper.vue +23 -25
  2. package/builtin/ShikiMagicMove.vue +63 -13
  3. package/builtin/SlidevVideo.vue +1 -1
  4. package/builtin/Toc.vue +1 -1
  5. package/builtin/TocList.vue +1 -1
  6. package/composables/useClicks.ts +16 -15
  7. package/composables/useContext.ts +4 -9
  8. package/composables/useNav.ts +182 -44
  9. package/composables/useSwipeControls.ts +40 -0
  10. package/composables/useTocTree.ts +63 -0
  11. package/constants.ts +2 -3
  12. package/context.ts +3 -6
  13. package/env.ts +4 -4
  14. package/internals/Goto.vue +2 -2
  15. package/internals/NavControls.vue +7 -5
  16. package/internals/NoteStatic.vue +1 -1
  17. package/internals/PrintContainer.vue +8 -7
  18. package/internals/PrintSlide.vue +6 -13
  19. package/internals/PrintSlideClick.vue +11 -13
  20. package/internals/QuickOverview.vue +10 -10
  21. package/internals/SideEditor.vue +3 -3
  22. package/internals/SlideContainer.vue +6 -6
  23. package/internals/SlideLoading.vue +19 -0
  24. package/internals/SlideWrapper.ts +12 -12
  25. package/internals/SlidesShow.vue +13 -10
  26. package/layouts/error.vue +5 -0
  27. package/logic/drawings.ts +7 -7
  28. package/logic/nav-state.ts +20 -0
  29. package/logic/nav.ts +49 -259
  30. package/logic/note.ts +2 -2
  31. package/logic/overview.ts +2 -2
  32. package/logic/route.ts +10 -1
  33. package/logic/slides.ts +19 -0
  34. package/logic/transition.ts +50 -0
  35. package/logic/utils.ts +24 -0
  36. package/modules/context.ts +7 -12
  37. package/package.json +8 -7
  38. package/pages/notes.vue +2 -3
  39. package/pages/overview.vue +19 -21
  40. package/pages/play.vue +2 -1
  41. package/pages/presenter/print.vue +2 -2
  42. package/pages/presenter.vue +15 -14
  43. package/routes.ts +6 -14
  44. package/setup/root.ts +6 -7
  45. package/setup/shortcuts.ts +2 -1
  46. package/shim-vue.d.ts +3 -0
  47. package/state/index.ts +1 -1
  48. package/styles/code.css +2 -2
  49. package/utils.ts +15 -2
package/context.ts CHANGED
@@ -1,6 +1,5 @@
1
- import { ref, shallowRef, toRef } from 'vue'
1
+ import { ref, toRef } from 'vue'
2
2
  import { injectLocal, objectOmit, provideLocal } from '@vueuse/core'
3
- import { useFixedClicks } from './composables/useClicks'
4
3
  import {
5
4
  FRONTMATTER_FIELDS,
6
5
  HEADMATTER_FIELDS,
@@ -13,15 +12,13 @@ import {
13
12
  injectionSlidevContext,
14
13
  } from './constants'
15
14
 
16
- const clicksContextFallback = shallowRef(useFixedClicks())
17
-
18
15
  /**
19
16
  * Get the current slide context, should be called inside the setup function of a component inside slide
20
17
  */
21
18
  export function useSlideContext() {
22
19
  const $slidev = injectLocal(injectionSlidevContext)!
23
20
  const $nav = toRef($slidev, 'nav')
24
- const $clicksContext = injectLocal(injectionClicksContext, clicksContextFallback)!.value
21
+ const $clicksContext = injectLocal(injectionClicksContext)!.value
25
22
  const $clicks = toRef($clicksContext, 'current')
26
23
  const $page = injectLocal(injectionCurrentPage)!
27
24
  const $renderContext = injectLocal(injectionRenderContext)!
@@ -51,7 +48,7 @@ export function provideFrontmatter(frontmatter: Record<string, any>) {
51
48
  } = useSlideContext()
52
49
 
53
50
  // update frontmatter in router to make HMR work better
54
- const route = $slidev.nav.rawRoutes.find(i => i.path === String($page.value))
51
+ const route = $slidev.nav.slides.find(i => i.no === $page.value)
55
52
  if (route?.meta?.slide?.frontmatter) {
56
53
  for (const key of Object.keys(route.meta.slide.frontmatter)) {
57
54
  if (!(key in frontmatter))
package/env.ts CHANGED
@@ -1,15 +1,15 @@
1
- import { computed } from 'vue'
1
+ import { computed, ref } from 'vue'
2
2
  import { objectMap } from '@antfu/utils'
3
3
  import configs from '#slidev/configs'
4
4
 
5
5
  export { configs }
6
6
 
7
- export const slideAspect = configs.aspectRatio ?? (16 / 9)
8
- export const slideWidth = configs.canvasWidth ?? 980
7
+ export const slideAspect = ref(configs.aspectRatio ?? (16 / 9))
8
+ export const slideWidth = ref(configs.canvasWidth ?? 980)
9
9
 
10
10
  // To honor the aspect ratio more as possible, we need to approximate the height to the next integer.
11
11
  // Doing this, we will prevent on print, to create an additional empty white page after each page.
12
- export const slideHeight = Math.ceil(slideWidth / slideAspect)
12
+ export const slideHeight = computed(() => Math.ceil(slideWidth.value / slideAspect.value))
13
13
 
14
14
  export const themeVars = computed(() => {
15
15
  return objectMap(configs.themeConfig || {}, (k, v) => [`--slidev-theme-${k}`, v])
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, ref, watch } from 'vue'
3
3
  import Fuse from 'fuse.js'
4
- import { go, rawRoutes } from '../logic/nav'
4
+ import { go, slides } from '../logic/nav'
5
5
  import { activeElement, showGotoDialog } from '../state'
6
6
  import Titles from '#slidev/titles.md'
7
7
 
@@ -16,7 +16,7 @@ function notNull<T>(value: T | null | undefined): value is T {
16
16
  return value !== null && value !== undefined
17
17
  }
18
18
 
19
- const fuse = computed(() => new Fuse(rawRoutes.map(i => i.meta?.slide).filter(notNull), {
19
+ const fuse = computed(() => new Fuse(slides.value.map(i => i.meta?.slide).filter(notNull), {
20
20
  keys: ['no', 'title'],
21
21
  threshold: 0.3,
22
22
  shouldSort: true,
@@ -1,7 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, ref, shallowRef } from 'vue'
3
3
  import { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'
4
- import { currentPage, downloadPDF, hasNext, hasPrev, isEmbedded, isPresenter, next, presenterPassword, prev, showPresenter, total } from '../logic/nav'
4
+ import { downloadPDF } from '../utils'
5
+ import { currentRoute, currentSlideNo, getSlidePath, hasNext, hasPrev, isEmbedded, isPresenter, isPresenterAvailable, next, prev, total } from '../logic/nav'
5
6
  import { activeElement, breakpoints, fullscreen, presenterLayout, showEditor, showInfoDialog, showPresenterCursor, toggleOverview, togglePresenterLayout } from '../state'
6
7
  import { brush, drawingEnabled } from '../logic/drawings'
7
8
  import { configs } from '../env'
@@ -21,9 +22,10 @@ const props = defineProps({
21
22
  const md = breakpoints.smaller('md')
22
23
  const { isFullscreen, toggle: toggleFullscreen } = fullscreen
23
24
 
25
+ const presenterPassword = computed(() => currentRoute.value.query.password)
24
26
  const query = computed(() => presenterPassword.value ? `?password=${presenterPassword.value}` : '')
25
- const presenterLink = computed(() => `/presenter/${currentPage.value}${query.value}`)
26
- const nonPresenterLink = computed(() => `/${currentPage.value}${query.value}`)
27
+ const presenterLink = computed(() => `${getSlidePath(currentSlideNo.value, true)}${query.value}`)
28
+ const nonPresenterLink = computed(() => `${getSlidePath(currentSlideNo.value, false)}${query.value}`)
27
29
 
28
30
  const root = ref<HTMLDivElement>()
29
31
  function onMouseLeave() {
@@ -107,7 +109,7 @@ if (__SLIDEV_FEATURE_DRAWINGS__)
107
109
  <RouterLink v-if="isPresenter" :to="nonPresenterLink" class="slidev-icon-btn" title="Play Mode">
108
110
  <carbon:presentation-file />
109
111
  </RouterLink>
110
- <RouterLink v-if="__SLIDEV_FEATURE_PRESENTER__ && showPresenter" :to="presenterLink" class="slidev-icon-btn" title="Presenter Mode">
112
+ <RouterLink v-if="__SLIDEV_FEATURE_PRESENTER__ && isPresenterAvailable" :to="presenterLink" class="slidev-icon-btn" title="Presenter Mode">
111
113
  <carbon:user-speaker />
112
114
  </RouterLink>
113
115
 
@@ -156,7 +158,7 @@ if (__SLIDEV_FEATURE_DRAWINGS__)
156
158
 
157
159
  <div class="h-40px flex" p="l-1 t-0.5 r-2" text="sm leading-2">
158
160
  <div class="my-auto">
159
- {{ currentPage }}
161
+ {{ currentSlideNo }}
160
162
  <span class="opacity-50">/ {{ total }}</span>
161
163
  </div>
162
164
  </div>
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import type { ClicksContext } from 'packages/types'
2
+ import type { ClicksContext } from '@slidev/types'
3
3
  import { useSlideInfo } from '../logic/note'
4
4
  import NoteDisplay from './NoteDisplay.vue'
5
5
 
@@ -4,7 +4,7 @@ import { computed } from 'vue'
4
4
  import { provideLocal } from '@vueuse/core'
5
5
  import { configs, slideAspect, slideWidth } from '../env'
6
6
  import { injectionSlideScale } from '../constants'
7
- import { route as currentRoute, rawRoutes } from '../logic/nav'
7
+ import { currentRoute, slides } from '../logic/nav'
8
8
  import PrintSlide from './PrintSlide.vue'
9
9
 
10
10
  const props = defineProps<{
@@ -12,17 +12,18 @@ const props = defineProps<{
12
12
  }>()
13
13
 
14
14
  const width = computed(() => props.width)
15
- const height = computed(() => props.width / slideAspect)
15
+ const height = computed(() => props.width / slideAspect.value)
16
16
 
17
17
  const screenAspect = computed(() => width.value / height.value)
18
18
 
19
19
  const scale = computed(() => {
20
- if (screenAspect.value < slideAspect)
21
- return width.value / slideWidth
22
- return (height.value * slideAspect) / slideWidth
20
+ if (screenAspect.value < slideAspect.value)
21
+ return width.value / slideWidth.value
22
+ return (height.value * slideAspect.value) / slideWidth.value
23
23
  })
24
24
 
25
- let routes = rawRoutes
25
+ // In print mode, the routes will never change. So we don't need reactivity here.
26
+ let routes = slides.value
26
27
  if (currentRoute.value.query.range) {
27
28
  const r = parseRangeString(routes.length, currentRoute.value.query.range as string)
28
29
  routes = r.map(i => routes[i - 1])
@@ -38,7 +39,7 @@ provideLocal(injectionSlideScale, scale)
38
39
  <template>
39
40
  <div id="print-container" :class="className">
40
41
  <div id="print-content">
41
- <PrintSlide v-for="route of routes" :key="route.path" :route="route" />
42
+ <PrintSlide v-for="route of routes" :key="route.no" :route="route" />
42
43
  </div>
43
44
  <slot name="controls" />
44
45
  </div>
@@ -1,30 +1,23 @@
1
1
  <script setup lang="ts">
2
- import type { RouteRecordRaw } from 'vue-router'
3
- import { computed } from 'vue'
4
- import { useNav } from '../composables/useNav'
2
+ import type { SlideRoute } from '@slidev/types'
3
+ import { useFixedNav } from '../composables/useNav'
5
4
  import { useFixedClicks } from '../composables/useClicks'
6
5
  import PrintSlideClick from './PrintSlideClick.vue'
7
6
 
8
- const props = defineProps<{ route: RouteRecordRaw }>()
9
-
10
- const route = computed(() => props.route)
11
- const nav = useNav(route)
12
- const clicks0 = useFixedClicks(route.value, 0)
7
+ const { route } = defineProps<{ route: SlideRoute }>()
8
+ const clicks0 = useFixedClicks(route, 0)
13
9
  </script>
14
10
 
15
11
  <template>
16
12
  <PrintSlideClick
17
13
  :clicks-context="clicks0"
18
- :nav="nav"
19
- :route="route"
14
+ :nav="useFixedNav(route, clicks0)"
20
15
  />
21
16
  <template v-if="!clicks0.disabled">
22
17
  <PrintSlideClick
23
18
  v-for="i of clicks0.total"
24
19
  :key="i"
25
- :clicks-context="useFixedClicks(route, i)"
26
- :nav="nav"
27
- :route="route"
20
+ :nav="useFixedNav(route, useFixedClicks(route, i))"
28
21
  />
29
22
  </template>
30
23
  </template>
@@ -1,26 +1,24 @@
1
1
  <script setup lang="ts">
2
- import type { RouteRecordRaw } from 'vue-router'
3
2
  import { computed, reactive, shallowRef } from 'vue'
4
- import type { ClicksContext } from '@slidev/types'
5
3
  import { provideLocal } from '@vueuse/core'
6
4
  import { injectionSlidevContext } from '../constants'
7
5
  import { configs, slideHeight, slideWidth } from '../env'
8
6
  import { getSlideClass } from '../utils'
9
- import type { SlidevContextNav } from '../modules/context'
7
+ import type { SlidevContextNav } from '../composables/useNav'
10
8
  import SlideWrapper from './SlideWrapper'
11
9
 
12
10
  import GlobalTop from '#slidev/global-components/top'
13
11
  import GlobalBottom from '#slidev/global-components/bottom'
14
12
 
15
- const props = defineProps<{
16
- clicksContext: ClicksContext
13
+ const { nav } = defineProps<{
17
14
  nav: SlidevContextNav
18
- route: RouteRecordRaw
19
15
  }>()
20
16
 
17
+ const route = computed(() => nav.currentSlideRoute.value)
18
+
21
19
  const style = computed(() => ({
22
- height: `${slideHeight}px`,
23
- width: `${slideWidth}px`,
20
+ height: `${slideHeight.value}px`,
21
+ width: `${slideWidth.value}px`,
24
22
  }))
25
23
 
26
24
  const DrawingPreview = shallowRef<any>()
@@ -28,11 +26,11 @@ if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
28
26
  import('./DrawingPreview.vue').then(v => (DrawingPreview.value = v.default))
29
27
 
30
28
  const id = computed(() =>
31
- `${props.route.path.toString().padStart(3, '0')}-${(props.nav.clicks.value + 1).toString().padStart(2, '0')}`,
29
+ `${route.value.no.toString().padStart(3, '0')}-${(nav.clicks.value + 1).toString().padStart(2, '0')}`,
32
30
  )
33
31
 
34
32
  provideLocal(injectionSlidevContext, reactive({
35
- nav: props.nav,
33
+ nav,
36
34
  configs,
37
35
  themeConfigs: computed(() => configs.themeConfig),
38
36
  }))
@@ -43,8 +41,8 @@ provideLocal(injectionSlidevContext, reactive({
43
41
  <GlobalBottom />
44
42
 
45
43
  <SlideWrapper
46
- :is="route?.component!"
47
- :clicks-context="clicksContext"
44
+ :is="route.component!"
45
+ :clicks-context="nav.clicksContext.value"
48
46
  :class="getSlideClass(route)"
49
47
  :route="route"
50
48
  />
@@ -55,7 +53,7 @@ provideLocal(injectionSlidevContext, reactive({
55
53
  && DrawingPreview
56
54
  "
57
55
  >
58
- <DrawingPreview :page="+route.path" />
56
+ <DrawingPreview :page="route.no" />
59
57
  </template>
60
58
 
61
59
  <GlobalTop />
@@ -2,7 +2,7 @@
2
2
  import { useEventListener, useVModel } from '@vueuse/core'
3
3
  import { computed, ref, watchEffect } from 'vue'
4
4
  import { breakpoints, showOverview, windowSize } from '../state'
5
- import { currentPage, go as goSlide, rawRoutes } from '../logic/nav'
5
+ import { currentSlideNo, go as goSlide, slides } from '../logic/nav'
6
6
  import { currentOverviewPage, overviewRowCount } from '../logic/overview'
7
7
  import { useFixedClicks } from '../composables/useClicks'
8
8
  import { getSlideClass } from '../utils'
@@ -78,17 +78,17 @@ useEventListener('keypress', (e) => {
78
78
  keyboardBuffer.value += String(num)
79
79
 
80
80
  // beyond the number of slides, reset
81
- if (+keyboardBuffer.value >= rawRoutes.length) {
81
+ if (+keyboardBuffer.value >= slides.value.length) {
82
82
  keyboardBuffer.value = ''
83
83
  return
84
84
  }
85
85
 
86
- const extactMatch = rawRoutes.findIndex(i => i.path === keyboardBuffer.value)
86
+ const extactMatch = slides.value.findIndex(i => `/${i.no}` === keyboardBuffer.value)
87
87
  if (extactMatch !== -1)
88
88
  currentOverviewPage.value = extactMatch + 1
89
89
 
90
90
  // When the input number is the largest at the number of digits, we go to that page directly.
91
- if (+keyboardBuffer.value * 10 > rawRoutes.length) {
91
+ if (+keyboardBuffer.value * 10 > slides.value.length) {
92
92
  go(+keyboardBuffer.value)
93
93
  keyboardBuffer.value = ''
94
94
  }
@@ -97,7 +97,7 @@ useEventListener('keypress', (e) => {
97
97
  watchEffect(() => {
98
98
  // Watch currentPage, make sure every time we open overview,
99
99
  // we focus on the right page.
100
- currentOverviewPage.value = currentPage.value
100
+ currentOverviewPage.value = currentSlideNo.value
101
101
  // Watch rowCount, make sure up and down shortcut work correctly.
102
102
  overviewRowCount.value = rowCount.value
103
103
  })
@@ -120,17 +120,17 @@ watchEffect(() => {
120
120
  :style="`grid-template-columns: repeat(auto-fit,minmax(${cardWidth}px,1fr))`"
121
121
  >
122
122
  <div
123
- v-for="(route, idx) of rawRoutes"
124
- :key="route.path"
123
+ v-for="(route, idx) of slides"
124
+ :key="route.no"
125
125
  class="relative"
126
126
  >
127
127
  <div
128
128
  class="inline-block border rounded overflow-hidden bg-main hover:border-primary transition"
129
129
  :class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border-primary' : 'border-main'"
130
- @click="go(+route.path)"
130
+ @click="go(route.no)"
131
131
  >
132
132
  <SlideContainer
133
- :key="route.path"
133
+ :key="route.no"
134
134
  :width="cardWidth"
135
135
  :clicks-disabled="true"
136
136
  class="pointer-events-none"
@@ -143,7 +143,7 @@ watchEffect(() => {
143
143
  :route="route"
144
144
  render-context="overview"
145
145
  />
146
- <DrawingPreview :page="+route.path" />
146
+ <DrawingPreview :page="route.no" />
147
147
  </SlideContainer>
148
148
  </div>
149
149
  <div
@@ -3,7 +3,7 @@ import { throttledWatch, useEventListener } from '@vueuse/core'
3
3
  import { computed, nextTick, onMounted, ref, watch } from 'vue'
4
4
  import { activeElement, editorHeight, editorWidth, isInputting, showEditor, isEditorVertical as vertical } from '../state'
5
5
  import { useCodeMirror } from '../setup/codemirror'
6
- import { currentSlideId, openInEditor } from '../logic/nav'
6
+ import { currentSlideNo, openInEditor } from '../logic/nav'
7
7
  import { useDynamicSlideInfo } from '../logic/note'
8
8
  import IconButton from './IconButton.vue'
9
9
 
@@ -19,7 +19,7 @@ const frontmatter = ref<any>({})
19
19
  const contentInput = ref<HTMLTextAreaElement>()
20
20
  const noteInput = ref<HTMLTextAreaElement>()
21
21
 
22
- const { info, update } = useDynamicSlideInfo(currentSlideId)
22
+ const { info, update } = useDynamicSlideInfo(currentSlideNo)
23
23
 
24
24
  watch(
25
25
  info,
@@ -103,7 +103,7 @@ onMounted(async () => {
103
103
  })
104
104
  })
105
105
 
106
- watch(currentSlideId, () => {
106
+ watch(currentSlideNo, () => {
107
107
  contentEditor.clearHistory()
108
108
  noteEditor.clearHistory()
109
109
  }, { flush: 'post' })
@@ -25,7 +25,7 @@ const root = ref<HTMLDivElement>()
25
25
  const element = useElementSize(root)
26
26
 
27
27
  const width = computed(() => props.width ? props.width : element.width.value)
28
- const height = computed(() => props.width ? props.width / slideAspect : element.height.value)
28
+ const height = computed(() => props.width ? props.width / slideAspect.value : element.height.value)
29
29
 
30
30
  if (props.width) {
31
31
  watchEffect(() => {
@@ -41,14 +41,14 @@ const screenAspect = computed(() => width.value / height.value)
41
41
  const scale = computed(() => {
42
42
  if (props.scale && !isPrintMode.value)
43
43
  return props.scale
44
- if (screenAspect.value < slideAspect)
45
- return width.value / slideWidth
46
- return height.value * slideAspect / slideWidth
44
+ if (screenAspect.value < slideAspect.value)
45
+ return width.value / slideWidth.value
46
+ return height.value * slideAspect.value / slideWidth.value
47
47
  })
48
48
 
49
49
  const style = computed(() => ({
50
- 'height': `${slideHeight}px`,
51
- 'width': `${slideWidth}px`,
50
+ 'height': `${slideHeight.value}px`,
51
+ 'width': `${slideWidth.value}px`,
52
52
  'transform': `translate(-50%, -50%) scale(${scale.value})`,
53
53
  '--slidev-slide-scale': scale.value,
54
54
  }))
@@ -0,0 +1,19 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, ref } from 'vue'
3
+
4
+ const timeout = ref(false)
5
+ onMounted(() => {
6
+ setTimeout(() => {
7
+ timeout.value = true
8
+ }, 200)
9
+ })
10
+ </script>
11
+
12
+ <template>
13
+ <div class="h-full w-full flex items-center justify-center gap-2">
14
+ <template v-if="timeout">
15
+ <div class="i-svg-spinners-90-ring-with-bg text-xl" />
16
+ <div>Loading slide...</div>
17
+ </template>
18
+ </div>
19
+ </template>
@@ -1,9 +1,9 @@
1
- import { computed, defineComponent, h, ref, toRef } from 'vue'
1
+ import { computed, defineAsyncComponent, defineComponent, h, ref, toRef } from 'vue'
2
2
  import type { PropType } from 'vue'
3
3
  import { provideLocal } from '@vueuse/core'
4
- import type { ClicksContext, RenderContext } from '@slidev/types'
5
- import type { RouteRecordRaw } from 'vue-router'
4
+ import type { ClicksContext, RenderContext, SlideRoute } from '@slidev/types'
6
5
  import { injectionActive, injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionRoute } from '../constants'
6
+ import SlideLoading from './SlideLoading.vue'
7
7
 
8
8
  export default defineComponent({
9
9
  name: 'SlideWrapper',
@@ -21,17 +21,16 @@ export default defineComponent({
21
21
  default: false,
22
22
  },
23
23
  is: {
24
- type: Object,
25
24
  required: true,
26
25
  },
27
26
  route: {
28
- type: Object as PropType<RouteRecordRaw>,
27
+ type: Object as PropType<SlideRoute>,
29
28
  required: true,
30
29
  },
31
30
  },
32
31
  setup(props) {
33
32
  provideLocal(injectionRoute, props.route)
34
- provideLocal(injectionCurrentPage, ref(+props.route.path))
33
+ provideLocal(injectionCurrentPage, ref(props.route.no))
35
34
  provideLocal(injectionRenderContext, ref(props.renderContext as RenderContext))
36
35
  provideLocal(injectionActive, toRef(props, 'active'))
37
36
  provideLocal(injectionClicksContext, toRef(props, 'clicksContext'))
@@ -48,11 +47,12 @@ export default defineComponent({
48
47
  }
49
48
  })
50
49
 
51
- return {
52
- style,
53
- }
54
- },
55
- render() {
56
- return h(this.$props.is, { style: this.style })
50
+ const SlideComponent = defineAsyncComponent({
51
+ loader: (props.is as any),
52
+ delay: 300,
53
+ loadingComponent: SlideLoading,
54
+ })
55
+
56
+ return () => h(SlideComponent, { style: style.value })
57
57
  },
58
58
  })
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { TransitionGroup, computed, shallowRef, watch } from 'vue'
3
- import { currentRoute, isPresenter, nextRoute, rawRoutes, transition } from '../logic/nav'
3
+ import { currentSlideRoute, currentTransition, isPresenter, nextRoute, slides } from '../logic/nav'
4
4
  import { getSlideClass } from '../utils'
5
5
  import { useViewTransition } from '../composables/useViewTransition'
6
6
  import { skipTransition } from '../composables/hmr'
@@ -16,9 +16,9 @@ defineProps<{
16
16
  }>()
17
17
 
18
18
  // preload next route
19
- watch(currentRoute, () => {
20
- if (currentRoute.value?.meta && currentRoute.value.meta.preload !== false)
21
- currentRoute.value.meta.__preloaded = true
19
+ watch(currentSlideRoute, () => {
20
+ if (currentSlideRoute.value?.meta && currentSlideRoute.value.meta.preload !== false)
21
+ currentSlideRoute.value.meta.__preloaded = true
22
22
  if (nextRoute.value?.meta && nextRoute.value.meta.preload !== false)
23
23
  nextRoute.value.meta.__preloaded = true
24
24
  }, { immediate: true })
@@ -29,7 +29,7 @@ const DrawingLayer = shallowRef<any>()
29
29
  if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
30
30
  import('./DrawingLayer.vue').then(v => DrawingLayer.value = v.default)
31
31
 
32
- const loadedRoutes = computed(() => rawRoutes.filter(r => r.meta?.__preloaded || r === currentRoute.value))
32
+ const loadedRoutes = computed(() => slides.value.filter(r => r.meta?.__preloaded || r === currentSlideRoute.value))
33
33
 
34
34
  function onAfterLeave() {
35
35
  // After transition, we disable it so HMR won't trigger it again
@@ -45,22 +45,25 @@ function onAfterLeave() {
45
45
  <!-- Slides -->
46
46
  <component
47
47
  :is="hasViewTransition ? 'div' : TransitionGroup"
48
- v-bind="skipTransition ? {} : transition"
48
+ v-bind="skipTransition ? {} : currentTransition"
49
49
  id="slideshow"
50
50
  tag="div"
51
51
  @after-leave="onAfterLeave"
52
52
  >
53
- <template v-for="route of loadedRoutes" :key="route.path">
53
+ <div
54
+ v-for="route of loadedRoutes"
55
+ v-show="route === currentSlideRoute"
56
+ :key="route.no"
57
+ >
54
58
  <SlideWrapper
55
- :is="route?.component as any"
56
- v-show="route === currentRoute"
59
+ :is="route.component!"
57
60
  :clicks-context="usePrimaryClicks(route)"
58
61
  :class="getSlideClass(route)"
59
62
  :route="route"
60
63
  :render-context="renderContext"
61
64
  class="overflow-hidden"
62
65
  />
63
- </template>
66
+ </div>
64
67
  </component>
65
68
 
66
69
  <!-- Global Top -->
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div class="px-4 py-10 text-center text-red-700 dark:text-red-500 font-bold font-mono">
3
+ An error occurred on this slide. Check the terminal for more information.
4
+ </div>
5
+ </template>
package/logic/drawings.ts CHANGED
@@ -5,7 +5,7 @@ import { toReactive, useLocalStorage } from '@vueuse/core'
5
5
  import { drawingState, onPatch, patch } from '../state/drawings'
6
6
  import { configs } from '../env'
7
7
  import { isInputting } from '../state'
8
- import { currentPage, isPresenter } from './nav'
8
+ import { currentSlideNo, isPresenter } from './nav'
9
9
 
10
10
  export const brushColors = [
11
11
  '#ff595e',
@@ -63,7 +63,7 @@ export const drauu = markRaw(createDrauu(drauuOptions))
63
63
  export function clearDrauu() {
64
64
  drauu.clear()
65
65
  if (syncUp.value)
66
- patch(currentPage.value, '')
66
+ patch(currentSlideNo.value, '')
67
67
  }
68
68
 
69
69
  export function updateState() {
@@ -74,7 +74,7 @@ export function updateState() {
74
74
 
75
75
  export function loadCanvas(page?: number) {
76
76
  disableDump = true
77
- const data = drawingState[page || currentPage.value]
77
+ const data = drawingState[page || currentSlideNo.value]
78
78
  if (data != null)
79
79
  drauu.load(data)
80
80
  else
@@ -87,7 +87,7 @@ drauu.on('changed', () => {
87
87
  updateState()
88
88
  if (!disableDump) {
89
89
  const dump = drauu.dump()
90
- const key = currentPage.value
90
+ const key = currentSlideNo.value
91
91
  if ((drawingState[key] || '') !== dump && syncUp.value)
92
92
  patch(key, drauu.dump())
93
93
  }
@@ -95,14 +95,14 @@ drauu.on('changed', () => {
95
95
 
96
96
  onPatch((state) => {
97
97
  disableDump = true
98
- if (state[currentPage.value] != null)
99
- drauu.load(state[currentPage.value] || '')
98
+ if (state[currentSlideNo.value] != null)
99
+ drauu.load(state[currentSlideNo.value] || '')
100
100
  disableDump = false
101
101
  updateState()
102
102
  })
103
103
 
104
104
  nextTick(() => {
105
- watch(currentPage, () => {
105
+ watch(currentSlideNo, () => {
106
106
  if (!drauu.mounted)
107
107
  return
108
108
  loadCanvas()
@@ -0,0 +1,20 @@
1
+ import { computed } from 'vue'
2
+ import { logicOr } from '@vueuse/math'
3
+ import { configs } from '../env'
4
+ import { router } from '../routes'
5
+ import { getSlide, slides } from './slides'
6
+
7
+ export const currentRoute = computed(() => router.currentRoute.value)
8
+
9
+ export const isPrintMode = computed(() => currentRoute.value.query.print !== undefined)
10
+ export const isPrintWithClicks = computed(() => currentRoute.value.query.print === 'clicks')
11
+ export const isEmbedded = computed(() => currentRoute.value.query.embedded !== undefined)
12
+ export const isPlaying = computed(() => currentRoute.value.name === 'play')
13
+ export const isPresenter = computed(() => currentRoute.value.name === 'presenter')
14
+ export const isNotesViewer = computed(() => currentRoute.value.name === 'notes')
15
+ export const isPresenterAvailable = computed(() => !isPresenter.value && (!configs.remote || currentRoute.value.query.password === configs.remote))
16
+
17
+ export const hasPrimarySlide = logicOr(isPlaying, isPresenter)
18
+
19
+ export const currentSlideNo = computed(() => hasPrimarySlide.value ? getSlide(currentRoute.value.params.no as string)?.no ?? 1 : 1)
20
+ export const currentSlideRoute = computed(() => slides.value[currentSlideNo.value - 1])