@slidev/client 0.48.0-beta.2 → 0.48.0-beta.20
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.
- package/App.vue +7 -0
- package/builtin/Arrow.vue +2 -4
- package/builtin/CodeBlockWrapper.vue +14 -6
- package/builtin/KaTexBlockWrapper.vue +5 -4
- package/builtin/Mermaid.vue +4 -3
- package/builtin/Monaco.vue +109 -92
- package/builtin/RenderWhen.vue +3 -3
- package/builtin/ShikiMagicMove.vue +50 -0
- package/builtin/SlideCurrentNo.vue +2 -3
- package/builtin/SlidesTotal.vue +3 -4
- package/builtin/SlidevVideo.vue +8 -6
- package/builtin/Toc.vue +3 -3
- package/builtin/TocList.vue +3 -2
- package/builtin/Tweet.vue +3 -22
- package/builtin/VClick.ts +2 -1
- package/builtin/VClickGap.vue +3 -5
- package/builtin/VClicks.ts +1 -1
- package/composables/useClicks.ts +34 -16
- package/constants.ts +58 -8
- package/context.ts +73 -0
- package/env.ts +3 -12
- package/internals/ClicksSlider.vue +93 -0
- package/internals/Controls.vue +2 -2
- package/internals/DrawingControls.vue +39 -9
- package/internals/DrawingLayer.vue +3 -3
- package/internals/Goto.vue +5 -4
- package/internals/IconButton.vue +7 -3
- package/internals/InfoDialog.vue +1 -1
- package/internals/Modal.vue +1 -1
- package/internals/NavControls.vue +4 -5
- package/internals/NoteDisplay.vue +131 -8
- package/internals/NoteEditable.vue +128 -0
- package/internals/NoteStatic.vue +8 -6
- package/internals/PrintContainer.vue +4 -3
- package/internals/PrintSlide.vue +8 -2
- package/internals/PrintSlideClick.vue +5 -7
- package/internals/{SlidesOverview.vue → QuickOverview.vue} +21 -10
- package/internals/RecordingControls.vue +1 -1
- package/internals/RecordingDialog.vue +5 -6
- package/internals/{Editor.vue → SideEditor.vue} +7 -3
- package/internals/SlideContainer.vue +12 -9
- package/internals/SlideWrapper.ts +28 -12
- package/internals/SlidesShow.vue +7 -8
- package/layouts/two-cols-header.vue +9 -3
- package/logic/drawings.ts +6 -3
- package/logic/nav.ts +11 -8
- package/logic/note.ts +7 -7
- package/main.ts +8 -4
- package/modules/context.ts +4 -3
- package/modules/mermaid.ts +6 -7
- package/modules/{directives.ts → v-click.ts} +15 -15
- package/modules/v-mark.ts +159 -0
- package/package.json +26 -16
- package/{internals/EntrySelect.vue → pages/entry.vue} +7 -0
- package/{internals/NotesView.vue → pages/notes.vue} +5 -3
- package/pages/overview.vue +229 -0
- package/{internals/Play.vue → pages/play.vue} +15 -12
- package/{internals/PresenterPrint.vue → pages/presenter/print.vue} +12 -7
- package/{internals/Presenter.vue → pages/presenter.vue} +108 -100
- package/{internals/Print.vue → pages/print.vue} +3 -4
- package/routes.ts +27 -51
- package/setup/codemirror.ts +8 -3
- package/setup/monaco.ts +108 -44
- package/setup/root.ts +2 -2
- package/shim-vue.d.ts +35 -0
- package/shim.d.ts +1 -13
- package/state/index.ts +10 -10
- package/styles/code.css +7 -3
- package/styles/index.css +68 -7
- package/styles/katex.css +1 -1
- package/styles/layouts-base.css +17 -12
- package/styles/monaco.css +27 -0
- package/styles/vars.css +1 -0
- package/uno.config.ts +14 -2
- package/iframes/monaco/index.css +0 -28
- package/iframes/monaco/index.html +0 -7
- package/iframes/monaco/index.ts +0 -260
- package/internals/NoteEditor.vue +0 -88
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
|
|
3
|
+
import { useHead } from '@unhead/vue'
|
|
4
|
+
import type { RouteRecordRaw } from 'vue-router'
|
|
5
|
+
import type { ClicksContext } from 'packages/types'
|
|
6
|
+
import { configs } from '../env'
|
|
7
|
+
import { openInEditor, rawRoutes } from '../logic/nav'
|
|
8
|
+
import { useFixedClicks } from '../composables/useClicks'
|
|
9
|
+
import { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'
|
|
10
|
+
import { getSlideClass } from '../utils'
|
|
11
|
+
import SlideContainer from '../internals/SlideContainer.vue'
|
|
12
|
+
import SlideWrapper from '../internals/SlideWrapper'
|
|
13
|
+
import DrawingPreview from '../internals/DrawingPreview.vue'
|
|
14
|
+
import IconButton from '../internals/IconButton.vue'
|
|
15
|
+
import NoteEditable from '../internals/NoteEditable.vue'
|
|
16
|
+
import ClicksSlider from '../internals/ClicksSlider.vue'
|
|
17
|
+
import { CLICKS_MAX } from '../constants'
|
|
18
|
+
|
|
19
|
+
const cardWidth = 450
|
|
20
|
+
|
|
21
|
+
const slideTitle = configs.titleTemplate.replace('%s', configs.title || 'Slidev')
|
|
22
|
+
useHead({
|
|
23
|
+
title: `Overview - ${slideTitle}`,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const blocks: Map<number, HTMLElement> = reactive(new Map())
|
|
27
|
+
const activeBlocks = ref<number[]>([])
|
|
28
|
+
const edittingNote = ref<number | null>(null)
|
|
29
|
+
const wordCounts = computed(() => rawRoutes.map(route => wordCount(route.meta?.slide?.note || '')))
|
|
30
|
+
const totalWords = computed(() => wordCounts.value.reduce((a, b) => a + b, 0))
|
|
31
|
+
const totalClicks = computed(() => rawRoutes.map(route => getSlideClicks(route)).reduce((a, b) => a + b, 0))
|
|
32
|
+
|
|
33
|
+
const clicksContextMap = new WeakMap<RouteRecordRaw, ClicksContext>()
|
|
34
|
+
function getClicksContext(route: RouteRecordRaw) {
|
|
35
|
+
// We create a local clicks context to calculate the total clicks of the slide
|
|
36
|
+
if (!clicksContextMap.has(route))
|
|
37
|
+
clicksContextMap.set(route, useFixedClicks(route, CLICKS_MAX))
|
|
38
|
+
return clicksContextMap.get(route)!
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getSlideClicks(route: RouteRecordRaw) {
|
|
42
|
+
return route.meta?.clicks || getClicksContext(route)?.total
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function wordCount(str: string) {
|
|
46
|
+
return str.match(/[\w\d\’\'-]+/gi)?.length || 0
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isElementInViewport(el: HTMLElement) {
|
|
50
|
+
const rect = el.getBoundingClientRect()
|
|
51
|
+
const delta = 20
|
|
52
|
+
return (
|
|
53
|
+
rect.top >= 0 - delta
|
|
54
|
+
&& rect.left >= 0 - delta
|
|
55
|
+
&& rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + delta
|
|
56
|
+
&& rect.right <= (window.innerWidth || document.documentElement.clientWidth) + delta
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function checkActiveBlocks() {
|
|
61
|
+
const active: number[] = []
|
|
62
|
+
Array.from(blocks.entries())
|
|
63
|
+
.forEach(([idx, el]) => {
|
|
64
|
+
if (isElementInViewport(el))
|
|
65
|
+
active.push(idx)
|
|
66
|
+
})
|
|
67
|
+
activeBlocks.value = active
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function openSlideInNewTab(path: string) {
|
|
71
|
+
const a = document.createElement('a')
|
|
72
|
+
a.target = '_blank'
|
|
73
|
+
a.href = path
|
|
74
|
+
a.click()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function scrollToSlide(idx: number) {
|
|
78
|
+
const el = blocks.get(idx)
|
|
79
|
+
if (el)
|
|
80
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function onMarkerClick(e: MouseEvent, clicks: number, route: RouteRecordRaw) {
|
|
84
|
+
const ctx = getClicksContext(route)
|
|
85
|
+
if (ctx.current === clicks)
|
|
86
|
+
ctx.current = CLICKS_MAX
|
|
87
|
+
else
|
|
88
|
+
ctx.current = clicks
|
|
89
|
+
e.preventDefault()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
onMounted(() => {
|
|
93
|
+
nextTick(() => {
|
|
94
|
+
checkActiveBlocks()
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
</script>
|
|
98
|
+
|
|
99
|
+
<template>
|
|
100
|
+
<div class="h-screen w-screen of-hidden flex">
|
|
101
|
+
<nav class="h-full flex flex-col border-r border-main p2 select-none">
|
|
102
|
+
<div class="flex flex-col flex-auto items-center justify-center group gap-1">
|
|
103
|
+
<div
|
|
104
|
+
v-for="(route, idx) of rawRoutes"
|
|
105
|
+
:key="route.path"
|
|
106
|
+
class="relative"
|
|
107
|
+
>
|
|
108
|
+
<button
|
|
109
|
+
class="relative transition duration-300 w-8 h-8 rounded hover:bg-active hover:op100"
|
|
110
|
+
:class="activeBlocks.includes(idx) ? 'op100 text-primary bg-gray:5' : 'op20'"
|
|
111
|
+
@click="scrollToSlide(idx)"
|
|
112
|
+
>
|
|
113
|
+
<div>{{ idx + 1 }}</div>
|
|
114
|
+
</button>
|
|
115
|
+
<div
|
|
116
|
+
v-if="route.meta?.slide?.title"
|
|
117
|
+
class="pointer-events-none select-none absolute left-110% bg-main top-50% translate-y--50% ws-nowrap z-10 px2 shadow-xl rounded border border-main transition duration-400 op0 group-hover:op100"
|
|
118
|
+
:class="activeBlocks.includes(idx) ? 'text-primary' : 'text-main important-text-op-50'"
|
|
119
|
+
>
|
|
120
|
+
{{ route.meta?.slide?.title }}
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
<IconButton
|
|
125
|
+
v-if="!isColorSchemaConfigured"
|
|
126
|
+
:title="isDark ? 'Switch to light mode theme' : 'Switch to dark mode theme'"
|
|
127
|
+
@click="toggleDark()"
|
|
128
|
+
>
|
|
129
|
+
<carbon-moon v-if="isDark" />
|
|
130
|
+
<carbon-sun v-else />
|
|
131
|
+
</IconButton>
|
|
132
|
+
</nav>
|
|
133
|
+
<main
|
|
134
|
+
class="flex-1 h-full of-auto"
|
|
135
|
+
:style="`grid-template-columns: repeat(auto-fit,minmax(${cardWidth}px,1fr))`"
|
|
136
|
+
@scroll="checkActiveBlocks"
|
|
137
|
+
>
|
|
138
|
+
<div
|
|
139
|
+
v-for="(route, idx) of rawRoutes"
|
|
140
|
+
:key="route.path"
|
|
141
|
+
:ref="el => blocks.set(idx, el as any)"
|
|
142
|
+
class="relative border-t border-main of-hidden flex gap-4 min-h-50 group"
|
|
143
|
+
>
|
|
144
|
+
<div class="select-none w-13 text-right my4 flex flex-col gap-1 items-end">
|
|
145
|
+
<div class="text-3xl op20 mb2">
|
|
146
|
+
{{ idx + 1 }}
|
|
147
|
+
</div>
|
|
148
|
+
<IconButton
|
|
149
|
+
class="mr--3 op0 group-hover:op80"
|
|
150
|
+
title="Play in new tab"
|
|
151
|
+
@click="openSlideInNewTab(route.path)"
|
|
152
|
+
>
|
|
153
|
+
<carbon:presentation-file />
|
|
154
|
+
</IconButton>
|
|
155
|
+
<IconButton
|
|
156
|
+
v-if="route.meta?.slide"
|
|
157
|
+
class="mr--3 op0 group-hover:op80"
|
|
158
|
+
title="Open in editor"
|
|
159
|
+
@click="openInEditor(`${route.meta.slide.filepath}:${route.meta.slide.start}`)"
|
|
160
|
+
>
|
|
161
|
+
<carbon:cics-program />
|
|
162
|
+
</IconButton>
|
|
163
|
+
</div>
|
|
164
|
+
<div class="flex flex-col gap-2 my5">
|
|
165
|
+
<div
|
|
166
|
+
class="border rounded border-main overflow-hidden bg-main select-none h-max"
|
|
167
|
+
@dblclick="openSlideInNewTab(route.path)"
|
|
168
|
+
>
|
|
169
|
+
<SlideContainer
|
|
170
|
+
:key="route.path"
|
|
171
|
+
:width="cardWidth"
|
|
172
|
+
:clicks-disabled="true"
|
|
173
|
+
class="pointer-events-none important:[&_*]:select-none"
|
|
174
|
+
>
|
|
175
|
+
<SlideWrapper
|
|
176
|
+
:is="route.component"
|
|
177
|
+
v-if="route?.component"
|
|
178
|
+
:clicks-context="getClicksContext(route)"
|
|
179
|
+
:class="getSlideClass(route)"
|
|
180
|
+
:route="route"
|
|
181
|
+
render-context="overview"
|
|
182
|
+
/>
|
|
183
|
+
<DrawingPreview :page="+route.path" />
|
|
184
|
+
</SlideContainer>
|
|
185
|
+
</div>
|
|
186
|
+
<ClicksSlider
|
|
187
|
+
v-if="getSlideClicks(route)"
|
|
188
|
+
mt-2
|
|
189
|
+
:clicks-context="getClicksContext(route)"
|
|
190
|
+
class="w-full"
|
|
191
|
+
/>
|
|
192
|
+
</div>
|
|
193
|
+
<div class="py3 mt-0.5 mr--8 ml--4 op0 transition group-hover:op100">
|
|
194
|
+
<IconButton
|
|
195
|
+
title="Edit Note"
|
|
196
|
+
class="rounded-full w-9 h-9 text-sm"
|
|
197
|
+
:class="edittingNote === idx ? 'important:op0' : ''"
|
|
198
|
+
@click="edittingNote = idx"
|
|
199
|
+
>
|
|
200
|
+
<carbon:pen />
|
|
201
|
+
</IconButton>
|
|
202
|
+
</div>
|
|
203
|
+
<NoteEditable
|
|
204
|
+
:no="idx"
|
|
205
|
+
class="max-w-250 w-250 text-lg rounded p3"
|
|
206
|
+
:auto-height="true"
|
|
207
|
+
:editing="edittingNote === idx"
|
|
208
|
+
:clicks-context="getClicksContext(route)"
|
|
209
|
+
@dblclick="edittingNote !== idx ? edittingNote = idx : null"
|
|
210
|
+
@update:editing="edittingNote = null"
|
|
211
|
+
@marker-click="(e, clicks) => onMarkerClick(e, clicks, route)"
|
|
212
|
+
/>
|
|
213
|
+
<div
|
|
214
|
+
v-if="wordCounts[idx] > 0"
|
|
215
|
+
class="select-none absolute bottom-0 right-0 bg-main rounded-tl p2 op35 text-xs"
|
|
216
|
+
>
|
|
217
|
+
{{ wordCounts[idx] }} words
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</main>
|
|
221
|
+
<div class="absolute top-0 right-0 px3 py1.5 border-b border-l rounded-lb bg-main border-main select-none">
|
|
222
|
+
<div class="text-xs op50">
|
|
223
|
+
{{ rawRoutes.length }} slides ·
|
|
224
|
+
{{ totalClicks + rawRoutes.length - 1 }} clicks ·
|
|
225
|
+
{{ totalWords }} words
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</template>
|
|
@@ -4,12 +4,12 @@ import { isEditorVertical, isScreenVertical, showEditor, slideScale, windowSize
|
|
|
4
4
|
import { isEmbedded, isPrintMode, next, prev, useSwipeControls } from '../logic/nav'
|
|
5
5
|
import { isDrawing } from '../logic/drawings'
|
|
6
6
|
import { registerShortcuts } from '../logic/shortcuts'
|
|
7
|
-
import { configs
|
|
8
|
-
import Controls from '
|
|
9
|
-
import SlideContainer from '
|
|
10
|
-
import NavControls from '
|
|
11
|
-
import SlidesShow from '
|
|
12
|
-
import PrintStyle from '
|
|
7
|
+
import { configs } from '../env'
|
|
8
|
+
import Controls from '../internals/Controls.vue'
|
|
9
|
+
import SlideContainer from '../internals/SlideContainer.vue'
|
|
10
|
+
import NavControls from '../internals/NavControls.vue'
|
|
11
|
+
import SlidesShow from '../internals/SlidesShow.vue'
|
|
12
|
+
import PrintStyle from '../internals/PrintStyle.vue'
|
|
13
13
|
|
|
14
14
|
registerShortcuts()
|
|
15
15
|
|
|
@@ -31,18 +31,21 @@ useSwipeControls(root)
|
|
|
31
31
|
|
|
32
32
|
const persistNav = computed(() => isScreenVertical.value || showEditor.value)
|
|
33
33
|
|
|
34
|
-
const
|
|
34
|
+
const SideEditor = shallowRef<any>()
|
|
35
35
|
if (__DEV__ && __SLIDEV_FEATURE_EDITOR__)
|
|
36
|
-
import('
|
|
36
|
+
import('../internals/SideEditor.vue').then(v => SideEditor.value = v.default)
|
|
37
37
|
|
|
38
38
|
const DrawingControls = shallowRef<any>()
|
|
39
39
|
if (__SLIDEV_FEATURE_DRAWINGS__)
|
|
40
|
-
import('
|
|
40
|
+
import('../internals/DrawingControls.vue').then(v => DrawingControls.value = v.default)
|
|
41
41
|
</script>
|
|
42
42
|
|
|
43
43
|
<template>
|
|
44
44
|
<PrintStyle v-if="isPrintMode" />
|
|
45
|
-
<div
|
|
45
|
+
<div
|
|
46
|
+
id="page-root" ref="root" class="grid"
|
|
47
|
+
:class="isEditorVertical ? 'grid-rows-[1fr_max-content]' : 'grid-cols-[1fr_max-content]'"
|
|
48
|
+
>
|
|
46
49
|
<SlideContainer
|
|
47
50
|
class="w-full h-full"
|
|
48
51
|
:style="{ background: 'var(--slidev-slide-container-background, black)' }"
|
|
@@ -70,8 +73,8 @@ if (__SLIDEV_FEATURE_DRAWINGS__)
|
|
|
70
73
|
</template>
|
|
71
74
|
</SlideContainer>
|
|
72
75
|
|
|
73
|
-
<template v-if="__DEV__ && __SLIDEV_FEATURE_EDITOR__ &&
|
|
74
|
-
<
|
|
76
|
+
<template v-if="__DEV__ && __SLIDEV_FEATURE_EDITOR__ && SideEditor && showEditor">
|
|
77
|
+
<SideEditor :resize="true" />
|
|
75
78
|
</template>
|
|
76
79
|
</div>
|
|
77
80
|
<Controls />
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { computed } from 'vue'
|
|
3
3
|
import { useStyleTag } from '@vueuse/core'
|
|
4
4
|
import { useHead } from '@unhead/vue'
|
|
5
|
-
import { configs
|
|
6
|
-
import { rawRoutes, total } from '
|
|
7
|
-
import NoteDisplay from '
|
|
5
|
+
import { configs } from '../../env'
|
|
6
|
+
import { rawRoutes, total } from '../../logic/nav'
|
|
7
|
+
import NoteDisplay from '../../internals/NoteDisplay.vue'
|
|
8
8
|
|
|
9
9
|
useStyleTag(`
|
|
10
10
|
@page {
|
|
@@ -24,7 +24,9 @@ html #page-root {
|
|
|
24
24
|
}
|
|
25
25
|
`)
|
|
26
26
|
|
|
27
|
-
useHead({
|
|
27
|
+
useHead({
|
|
28
|
+
title: `Notes - ${configs.title}`,
|
|
29
|
+
})
|
|
28
30
|
|
|
29
31
|
const slidesWithNote = computed(() => rawRoutes
|
|
30
32
|
.map(route => route.meta?.slide)
|
|
@@ -32,7 +34,7 @@ const slidesWithNote = computed(() => rawRoutes
|
|
|
32
34
|
</script>
|
|
33
35
|
|
|
34
36
|
<template>
|
|
35
|
-
<div id="page-root"
|
|
37
|
+
<div id="page-root">
|
|
36
38
|
<div class="m-4">
|
|
37
39
|
<div class="mb-10">
|
|
38
40
|
<h1 class="text-4xl font-bold mt-2">
|
|
@@ -54,9 +56,12 @@ const slidesWithNote = computed(() => rawRoutes
|
|
|
54
56
|
<div class="flex-auto" />
|
|
55
57
|
</div>
|
|
56
58
|
</h2>
|
|
57
|
-
<NoteDisplay
|
|
59
|
+
<NoteDisplay
|
|
60
|
+
:note-html="slide!.noteHTML"
|
|
61
|
+
class="max-w-full"
|
|
62
|
+
/>
|
|
58
63
|
</div>
|
|
59
|
-
<hr v-if="index < slidesWithNote.length - 1" class="border-
|
|
64
|
+
<hr v-if="index < slidesWithNote.length - 1" class="border-main mb-8">
|
|
60
65
|
</div>
|
|
61
66
|
</div>
|
|
62
67
|
</div>
|
|
@@ -2,25 +2,26 @@
|
|
|
2
2
|
import { useHead } from '@unhead/vue'
|
|
3
3
|
import { computed, onMounted, reactive, ref, shallowRef, watch } from 'vue'
|
|
4
4
|
import { useMouse, useWindowFocus } from '@vueuse/core'
|
|
5
|
-
import { clicksContext, currentPage, currentRoute, hasNext, nextRoute, queryClicks, rawRoutes, total, useSwipeControls } from '../logic/nav'
|
|
5
|
+
import { clicksContext, currentPage, currentRoute, currentSlideId, hasNext, nextRoute, queryClicks, rawRoutes, total, useSwipeControls } from '../logic/nav'
|
|
6
6
|
import { decreasePresenterFontSize, increasePresenterFontSize, presenterLayout, presenterNotesFontSize, showEditor, showOverview, showPresenterCursor } from '../state'
|
|
7
|
-
import { configs
|
|
7
|
+
import { configs } from '../env'
|
|
8
8
|
import { sharedState } from '../state/shared'
|
|
9
9
|
import { registerShortcuts } from '../logic/shortcuts'
|
|
10
10
|
import { getSlideClass } from '../utils'
|
|
11
11
|
import { useTimer } from '../logic/utils'
|
|
12
12
|
import { isDrawing } from '../logic/drawings'
|
|
13
|
-
import { useFixedClicks } from '../composables/useClicks'
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
import DrawingControls from '
|
|
23
|
-
import IconButton from '
|
|
13
|
+
import { useFixedClicks, usePrimaryClicks } from '../composables/useClicks'
|
|
14
|
+
import SlideWrapper from '../internals/SlideWrapper'
|
|
15
|
+
import SlideContainer from '../internals/SlideContainer.vue'
|
|
16
|
+
import NavControls from '../internals/NavControls.vue'
|
|
17
|
+
import QuickOverview from '../internals/QuickOverview.vue'
|
|
18
|
+
import NoteEditable from '../internals/NoteEditable.vue'
|
|
19
|
+
import NoteStatic from '../internals/NoteStatic.vue'
|
|
20
|
+
import Goto from '../internals/Goto.vue'
|
|
21
|
+
import SlidesShow from '../internals/SlidesShow.vue'
|
|
22
|
+
import DrawingControls from '../internals/DrawingControls.vue'
|
|
23
|
+
import IconButton from '../internals/IconButton.vue'
|
|
24
|
+
import ClicksSlider from '../internals/ClicksSlider.vue'
|
|
24
25
|
|
|
25
26
|
const main = ref<HTMLDivElement>()
|
|
26
27
|
|
|
@@ -45,16 +46,23 @@ const nextFrame = computed(() => {
|
|
|
45
46
|
else
|
|
46
47
|
return null
|
|
47
48
|
})
|
|
49
|
+
|
|
48
50
|
const nextFrameClicksCtx = computed(() => {
|
|
49
51
|
return nextFrame.value && clicksCtxMap[+nextFrame.value[0].path - 1]
|
|
50
52
|
})
|
|
51
|
-
watch([currentRoute, queryClicks], () => {
|
|
52
|
-
nextFrameClicksCtx.value && (nextFrameClicksCtx.value[0].value = nextFrame.value![1])
|
|
53
|
-
}, { immediate: true })
|
|
54
53
|
|
|
55
|
-
|
|
54
|
+
watch(
|
|
55
|
+
[currentRoute, queryClicks],
|
|
56
|
+
() => {
|
|
57
|
+
if (nextFrameClicksCtx.value)
|
|
58
|
+
nextFrameClicksCtx.value.current = nextFrame.value![1]
|
|
59
|
+
},
|
|
60
|
+
{ immediate: true },
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
const SideEditor = shallowRef<any>()
|
|
56
64
|
if (__DEV__ && __SLIDEV_FEATURE_EDITOR__)
|
|
57
|
-
import('
|
|
65
|
+
import('../internals/SideEditor.vue').then(v => SideEditor.value = v.default)
|
|
58
66
|
|
|
59
67
|
// sync presenter cursor
|
|
60
68
|
onMounted(() => {
|
|
@@ -86,68 +94,64 @@ onMounted(() => {
|
|
|
86
94
|
<template>
|
|
87
95
|
<div class="bg-main h-full slidev-presenter">
|
|
88
96
|
<div class="grid-container" :class="`layout${presenterLayout}`">
|
|
89
|
-
<div class="grid-section
|
|
90
|
-
<img src="../assets/logo-title-horizontal.png" class="ml-2 my-auto h-10 py-1 lg:h-14 lg:py-2" style="height: 3.5rem;" alt="Slidev logo">
|
|
91
|
-
<div class="flex-auto" />
|
|
92
|
-
<div
|
|
93
|
-
class="timer-btn my-auto relative w-22px h-22px cursor-pointer text-lg"
|
|
94
|
-
opacity="50 hover:100"
|
|
95
|
-
@click="resetTimer"
|
|
96
|
-
>
|
|
97
|
-
<carbon:time class="absolute" />
|
|
98
|
-
<carbon:renew class="absolute opacity-0" />
|
|
99
|
-
</div>
|
|
100
|
-
<div class="text-2xl pl-2 pr-6 my-auto tabular-nums">
|
|
101
|
-
{{ timer }}
|
|
102
|
-
</div>
|
|
103
|
-
</div>
|
|
104
|
-
<div ref="main" class="relative grid-section main flex flex-col p-2 lg:p-4" :style="themeVars">
|
|
97
|
+
<div ref="main" class="relative grid-section main flex flex-col">
|
|
105
98
|
<SlideContainer
|
|
106
99
|
key="main"
|
|
107
|
-
class="h-full w-full"
|
|
100
|
+
class="h-full w-full p-2 lg:p-4 flex-auto"
|
|
108
101
|
>
|
|
109
102
|
<template #default>
|
|
110
103
|
<SlidesShow render-context="presenter" />
|
|
111
104
|
</template>
|
|
112
105
|
</SlideContainer>
|
|
113
|
-
<
|
|
114
|
-
|
|
106
|
+
<ClicksSlider
|
|
107
|
+
:key="currentRoute?.path"
|
|
108
|
+
:clicks-context="usePrimaryClicks(currentRoute)"
|
|
109
|
+
class="w-full pb2 px4 flex-none"
|
|
110
|
+
/>
|
|
111
|
+
<div class="absolute left-0 top-0 bg-main border-b border-r border-main px2 py1 op50 text-sm">
|
|
112
|
+
Current
|
|
115
113
|
</div>
|
|
116
114
|
</div>
|
|
117
|
-
<div class="relative grid-section next flex flex-col p-2 lg:p-4"
|
|
115
|
+
<div class="relative grid-section next flex flex-col p-2 lg:p-4">
|
|
118
116
|
<SlideContainer
|
|
119
117
|
v-if="nextFrame && nextFrameClicksCtx"
|
|
120
118
|
key="next"
|
|
121
119
|
class="h-full w-full"
|
|
122
120
|
>
|
|
123
121
|
<SlideWrapper
|
|
124
|
-
:is="nextFrame[0].component as any"
|
|
122
|
+
:is="(nextFrame[0].component as any)"
|
|
125
123
|
:key="nextFrame[0].path"
|
|
126
|
-
:clicks-context="nextFrameClicksCtx
|
|
124
|
+
:clicks-context="nextFrameClicksCtx"
|
|
127
125
|
:class="getSlideClass(nextFrame[0])"
|
|
128
126
|
:route="nextFrame[0]"
|
|
129
127
|
render-context="previewNext"
|
|
130
128
|
/>
|
|
131
129
|
</SlideContainer>
|
|
132
|
-
<div class="
|
|
133
|
-
|
|
130
|
+
<div class="absolute left-0 top-0 bg-main border-b border-r border-main px2 py1 op50 text-sm">
|
|
131
|
+
Next
|
|
134
132
|
</div>
|
|
135
133
|
</div>
|
|
136
134
|
<!-- Notes -->
|
|
137
|
-
<div v-if="__DEV__ && __SLIDEV_FEATURE_EDITOR__ &&
|
|
138
|
-
<
|
|
135
|
+
<div v-if="__DEV__ && __SLIDEV_FEATURE_EDITOR__ && SideEditor && showEditor" class="grid-section note of-auto">
|
|
136
|
+
<SideEditor />
|
|
139
137
|
</div>
|
|
140
138
|
<div v-else class="grid-section note grid grid-rows-[1fr_min-content] overflow-hidden">
|
|
141
|
-
<
|
|
139
|
+
<NoteEditable
|
|
142
140
|
v-if="__DEV__"
|
|
141
|
+
:key="`edit-${currentSlideId}`"
|
|
142
|
+
v-model:editing="notesEditing"
|
|
143
|
+
:no="currentSlideId"
|
|
143
144
|
class="w-full max-w-full h-full overflow-auto p-2 lg:p-4"
|
|
144
|
-
:
|
|
145
|
+
:clicks-context="clicksContext"
|
|
145
146
|
:style="{ fontSize: `${presenterNotesFontSize}em` }"
|
|
146
147
|
/>
|
|
147
148
|
<NoteStatic
|
|
148
149
|
v-else
|
|
150
|
+
:key="`static-${currentSlideId}`"
|
|
151
|
+
:no="currentSlideId"
|
|
149
152
|
class="w-full max-w-full h-full overflow-auto p-2 lg:p-4"
|
|
150
153
|
:style="{ fontSize: `${presenterNotesFontSize}em` }"
|
|
154
|
+
:clicks-context="clicksContext"
|
|
151
155
|
/>
|
|
152
156
|
<div class="border-t border-main py-1 px-2 text-sm">
|
|
153
157
|
<IconButton title="Increase font size" @click="increasePresenterFontSize">
|
|
@@ -165,116 +169,120 @@ onMounted(() => {
|
|
|
165
169
|
</IconButton>
|
|
166
170
|
</div>
|
|
167
171
|
</div>
|
|
168
|
-
<div class="grid-section bottom">
|
|
172
|
+
<div class="grid-section bottom flex">
|
|
169
173
|
<NavControls :persist="true" />
|
|
174
|
+
<div flex-auto />
|
|
175
|
+
<div
|
|
176
|
+
class="timer-btn my-auto relative w-22px h-22px cursor-pointer text-lg"
|
|
177
|
+
opacity="50 hover:100"
|
|
178
|
+
@click="resetTimer"
|
|
179
|
+
>
|
|
180
|
+
<carbon:time class="absolute" />
|
|
181
|
+
<carbon:renew class="absolute opacity-0" />
|
|
182
|
+
</div>
|
|
183
|
+
<div class="text-2xl pl-2 pr-6 my-auto tabular-nums">
|
|
184
|
+
{{ timer }}
|
|
185
|
+
</div>
|
|
170
186
|
</div>
|
|
171
187
|
<DrawingControls v-if="__SLIDEV_FEATURE_DRAWINGS__" />
|
|
172
188
|
</div>
|
|
173
189
|
<div class="progress-bar">
|
|
174
190
|
<div
|
|
175
|
-
class="progress h-
|
|
191
|
+
class="progress h-3px bg-primary transition-all"
|
|
176
192
|
:style="{ width: `${(currentPage - 1) / (total - 1) * 100}%` }"
|
|
177
193
|
/>
|
|
178
194
|
</div>
|
|
179
195
|
</div>
|
|
180
196
|
<Goto />
|
|
181
|
-
<
|
|
197
|
+
<QuickOverview v-model="showOverview" />
|
|
182
198
|
</template>
|
|
183
199
|
|
|
184
|
-
<style
|
|
200
|
+
<style scoped>
|
|
185
201
|
.slidev-presenter {
|
|
186
202
|
--slidev-controls-foreground: current;
|
|
187
203
|
}
|
|
188
204
|
|
|
189
|
-
.timer-btn:hover {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
@apply opacity-100;
|
|
195
|
-
}
|
|
205
|
+
.timer-btn:hover > :first-child {
|
|
206
|
+
opacity: 0;
|
|
207
|
+
}
|
|
208
|
+
.timer-btn:hover > :last-child {
|
|
209
|
+
opacity: 1;
|
|
196
210
|
}
|
|
197
211
|
|
|
198
212
|
.section-title {
|
|
199
|
-
|
|
213
|
+
--uno: px-4 py-2 text-xl;
|
|
200
214
|
}
|
|
201
215
|
|
|
202
216
|
.grid-container {
|
|
203
|
-
|
|
217
|
+
--uno: bg-gray/20;
|
|
218
|
+
height: 100%;
|
|
219
|
+
width: 100%;
|
|
204
220
|
display: grid;
|
|
205
221
|
gap: 1px 1px;
|
|
206
222
|
}
|
|
207
223
|
|
|
208
224
|
.grid-container.layout1 {
|
|
209
225
|
grid-template-columns: 1fr 1fr;
|
|
210
|
-
grid-template-rows:
|
|
226
|
+
grid-template-rows: 2fr 1fr min-content;
|
|
211
227
|
grid-template-areas:
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
"bottom bottom";
|
|
228
|
+
'main main'
|
|
229
|
+
'note next'
|
|
230
|
+
'bottom bottom';
|
|
216
231
|
}
|
|
217
232
|
|
|
218
233
|
.grid-container.layout2 {
|
|
219
234
|
grid-template-columns: 3fr 2fr;
|
|
220
|
-
grid-template-rows:
|
|
235
|
+
grid-template-rows: 2fr 1fr min-content;
|
|
221
236
|
grid-template-areas:
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
"bottom bottom";
|
|
237
|
+
'note main'
|
|
238
|
+
'note next'
|
|
239
|
+
'bottom bottom';
|
|
226
240
|
}
|
|
227
241
|
|
|
228
242
|
@media (max-aspect-ratio: 3/5) {
|
|
229
243
|
.grid-container.layout1 {
|
|
230
244
|
grid-template-columns: 1fr;
|
|
231
|
-
grid-template-rows:
|
|
245
|
+
grid-template-rows: 1fr 1fr 1fr min-content;
|
|
232
246
|
grid-template-areas:
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
"bottom";
|
|
247
|
+
'main'
|
|
248
|
+
'note'
|
|
249
|
+
'next'
|
|
250
|
+
'bottom';
|
|
238
251
|
}
|
|
239
252
|
}
|
|
240
253
|
|
|
241
254
|
@media (min-aspect-ratio: 1/1) {
|
|
242
255
|
.grid-container.layout1 {
|
|
243
256
|
grid-template-columns: 1fr 1.1fr 0.9fr;
|
|
244
|
-
grid-template-rows:
|
|
257
|
+
grid-template-rows: 1fr 2fr min-content;
|
|
245
258
|
grid-template-areas:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
"bottom bottom bottom";
|
|
259
|
+
'main main next'
|
|
260
|
+
'main main note'
|
|
261
|
+
'bottom bottom bottom';
|
|
250
262
|
}
|
|
251
263
|
}
|
|
252
264
|
|
|
253
265
|
.progress-bar {
|
|
254
|
-
|
|
266
|
+
--uno: fixed left-0 right-0 top-0;
|
|
255
267
|
}
|
|
256
268
|
|
|
257
269
|
.grid-section {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
&.top {
|
|
261
|
-
grid-area: top;
|
|
262
|
-
}
|
|
263
|
-
&.main {
|
|
264
|
-
grid-area: main;
|
|
265
|
-
}
|
|
266
|
-
&.next {
|
|
267
|
-
grid-area: next;
|
|
268
|
-
}
|
|
269
|
-
&.note {
|
|
270
|
-
grid-area: note;
|
|
271
|
-
}
|
|
272
|
-
&.bottom {
|
|
273
|
-
grid-area: bottom;
|
|
274
|
-
}
|
|
270
|
+
--uno: bg-main;
|
|
275
271
|
}
|
|
276
272
|
|
|
277
|
-
.
|
|
278
|
-
|
|
273
|
+
.grid-section.top {
|
|
274
|
+
grid-area: top;
|
|
275
|
+
}
|
|
276
|
+
.grid-section.main {
|
|
277
|
+
grid-area: main;
|
|
278
|
+
}
|
|
279
|
+
.grid-section.next {
|
|
280
|
+
grid-area: next;
|
|
281
|
+
}
|
|
282
|
+
.grid-section.note {
|
|
283
|
+
grid-area: note;
|
|
284
|
+
}
|
|
285
|
+
.grid-section.bottom {
|
|
286
|
+
grid-area: bottom;
|
|
279
287
|
}
|
|
280
288
|
</style>
|