@slidev/client 0.50.0 → 0.51.0-beta.2
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/builtin/VSwitch.ts +3 -3
- package/composables/useClicks.ts +1 -1
- package/composables/useHideCursorIdle.ts +35 -0
- package/composables/useNav.ts +3 -3
- package/composables/useTimer.ts +1 -1
- package/composables/useWakeLock.ts +10 -6
- package/constants.ts +0 -1
- package/internals/Badge.vue +48 -0
- package/internals/{DevicesList.vue → DevicesSelectors.vue} +14 -4
- package/internals/FormItem.vue +6 -3
- package/internals/FormSlider.vue +68 -0
- package/internals/MenuButton.vue +2 -2
- package/internals/NavControls.vue +16 -9
- package/internals/QuickOverview.vue +24 -4
- package/internals/RecordingControls.vue +2 -2
- package/internals/RecordingDialog.vue +4 -14
- package/internals/ScreenCaptureMirror.vue +45 -0
- package/internals/SegmentControl.vue +29 -0
- package/internals/SelectList.vue +1 -9
- package/internals/Settings.vue +104 -30
- package/internals/SlideContainer.vue +33 -18
- package/internals/SlidesShow.vue +4 -5
- package/internals/SyncControls.vue +103 -0
- package/logic/color.ts +64 -0
- package/logic/snapshot.ts +73 -52
- package/package.json +12 -13
- package/pages/export.vue +24 -22
- package/pages/notes.vue +3 -3
- package/pages/play.vue +24 -2
- package/pages/presenter.vue +37 -18
- package/setup/root.ts +31 -24
- package/state/drawings.ts +5 -1
- package/state/index.ts +1 -56
- package/state/shared.ts +0 -7
- package/state/snapshot.ts +1 -1
- package/state/storage.ts +97 -0
- package/styles/index.css +11 -1
- package/uno.config.ts +1 -0
- package/logic/hmr.ts +0 -3
package/pages/export.vue
CHANGED
|
@@ -3,8 +3,7 @@ import type { ScreenshotSession } from '../logic/screenshot'
|
|
|
3
3
|
import { sleep } from '@antfu/utils'
|
|
4
4
|
import { parseRangeString } from '@slidev/parser/utils'
|
|
5
5
|
import { useHead } from '@unhead/vue'
|
|
6
|
-
import { provideLocal, useElementSize,
|
|
7
|
-
|
|
6
|
+
import { provideLocal, useElementSize, useStyleTag, watchDebounced } from '@vueuse/core'
|
|
8
7
|
import { computed, ref, useTemplateRef, watch } from 'vue'
|
|
9
8
|
import { useRouter } from 'vue-router'
|
|
10
9
|
import { useDarkMode } from '../composables/useDarkMode'
|
|
@@ -16,8 +15,9 @@ import ExportPdfTip from '../internals/ExportPdfTip.vue'
|
|
|
16
15
|
import FormCheckbox from '../internals/FormCheckbox.vue'
|
|
17
16
|
import FormItem from '../internals/FormItem.vue'
|
|
18
17
|
import PrintSlide from '../internals/PrintSlide.vue'
|
|
18
|
+
import SegmentControl from '../internals/SegmentControl.vue'
|
|
19
19
|
import { isScreenshotSupported, startScreenshotSession } from '../logic/screenshot'
|
|
20
|
-
import { skipExportPdfTip } from '../state'
|
|
20
|
+
import { captureDelay, skipExportPdfTip } from '../state'
|
|
21
21
|
import Play from './play.vue'
|
|
22
22
|
|
|
23
23
|
const { slides, isPrintWithClicks, hasNext, go, next, currentSlideNo, clicks, printRange } = useNav()
|
|
@@ -29,7 +29,6 @@ const scale = computed(() => containerWidth.value / slideWidth.value)
|
|
|
29
29
|
const contentMarginBottom = computed(() => `${contentHeight.value * (scale.value - 1)}px`)
|
|
30
30
|
const rangesRaw = ref('')
|
|
31
31
|
const initialWait = ref(1000)
|
|
32
|
-
const delay = useLocalStorage('slidev-export-capture-delay', 400, { listenToStorageChanges: false })
|
|
33
32
|
type ScreenshotResult = { slideIndex: number, clickIndex: number, dataUrl: string }[]
|
|
34
33
|
const screenshotSession = ref<ScreenshotSession | null>(null)
|
|
35
34
|
const capturedImages = ref<ScreenshotResult | null>(null)
|
|
@@ -70,7 +69,7 @@ async function capturePngs() {
|
|
|
70
69
|
|
|
71
70
|
go(1, 0, true)
|
|
72
71
|
|
|
73
|
-
await sleep(initialWait.value +
|
|
72
|
+
await sleep(initialWait.value + captureDelay.value)
|
|
74
73
|
while (true) {
|
|
75
74
|
if (!screenshotSession.value) {
|
|
76
75
|
break
|
|
@@ -81,9 +80,9 @@ async function capturePngs() {
|
|
|
81
80
|
dataUrl: screenshotSession.value.screenshot(document.getElementById('slide-content')!),
|
|
82
81
|
})
|
|
83
82
|
if (hasNext.value) {
|
|
84
|
-
await sleep(
|
|
83
|
+
await sleep(captureDelay.value)
|
|
85
84
|
next()
|
|
86
|
-
await sleep(
|
|
85
|
+
await sleep(captureDelay.value)
|
|
87
86
|
}
|
|
88
87
|
else {
|
|
89
88
|
break
|
|
@@ -227,8 +226,15 @@ if (import.meta.hot) {
|
|
|
227
226
|
<FormItem title="Range">
|
|
228
227
|
<input v-model="rangesRaw" type="text" :placeholder="`1-${slides.length}`">
|
|
229
228
|
</FormItem>
|
|
230
|
-
<FormItem title="
|
|
231
|
-
<
|
|
229
|
+
<FormItem title="Color Mode">
|
|
230
|
+
<SegmentControl
|
|
231
|
+
v-model="isDark"
|
|
232
|
+
:options="[
|
|
233
|
+
{ value: false, label: 'Light' },
|
|
234
|
+
{ value: true, label: 'Dark' },
|
|
235
|
+
]"
|
|
236
|
+
:disabled="isColorSchemaConfigured"
|
|
237
|
+
/>
|
|
232
238
|
</FormItem>
|
|
233
239
|
<FormItem title="With clicks">
|
|
234
240
|
<FormCheckbox v-model="isPrintWithClicks" />
|
|
@@ -238,8 +244,8 @@ if (import.meta.hot) {
|
|
|
238
244
|
<div class="min-w-fit" flex="~ col gap-3">
|
|
239
245
|
<div border="~ main rounded-lg" p3 flex="~ col gap-2">
|
|
240
246
|
<h2>Export as Vector File</h2>
|
|
241
|
-
<div class="flex flex-col gap-2
|
|
242
|
-
<button @click="pdf">
|
|
247
|
+
<div class="flex flex-col gap-2 min-w-max">
|
|
248
|
+
<button class="slidev-form-button" @click="pdf">
|
|
243
249
|
PDF
|
|
244
250
|
</button>
|
|
245
251
|
</div>
|
|
@@ -253,27 +259,27 @@ if (import.meta.hot) {
|
|
|
253
259
|
If you encounter issues, please use a modern Chromium-based browser,
|
|
254
260
|
or export via the CLI.
|
|
255
261
|
</div>
|
|
256
|
-
<div class="flex flex-col gap-2
|
|
257
|
-
<button @click="pptx">
|
|
262
|
+
<div class="flex flex-col gap-2 min-w-max">
|
|
263
|
+
<button class="slidev-form-button" @click="pptx">
|
|
258
264
|
PPTX
|
|
259
265
|
</button>
|
|
260
|
-
<button @click="pngsGz">
|
|
266
|
+
<button class="slidev-form-button" @click="pngsGz">
|
|
261
267
|
PNGs.gz
|
|
262
268
|
</button>
|
|
263
269
|
</div>
|
|
264
270
|
<div w-full h-1px border="t main" my2 />
|
|
265
271
|
<div class="relative flex flex-col gap-2 flex-nowrap">
|
|
266
|
-
<div class="flex flex-col gap-2
|
|
267
|
-
<button v-if="capturedImages" class="flex justify-center items-center gap-2" @click="capturedImages = null">
|
|
272
|
+
<div class="flex flex-col gap-2 min-w-max">
|
|
273
|
+
<button v-if="capturedImages" class="slidev-form-button flex justify-center items-center gap-2" @click="capturedImages = null">
|
|
268
274
|
<span class="i-carbon:trash-can inline-block text-xl" />
|
|
269
275
|
Clear Captured Images
|
|
270
276
|
</button>
|
|
271
|
-
<button v-else class="flex justify-center items-center gap-2" @click="capturePngs">
|
|
277
|
+
<button v-else class="slidev-form-button flex justify-center items-center gap-2" @click="capturePngs">
|
|
272
278
|
<div class="i-carbon:camera-action inline-block text-xl" />
|
|
273
279
|
Pre-capture Slides as Images
|
|
274
280
|
</button>
|
|
275
281
|
<FormItem title="Delay" description="Delay between capturing each slide in milliseconds.<br>Increase this value if slides are captured incompletely. <br>(Not related to PDF export)">
|
|
276
|
-
<input v-model="
|
|
282
|
+
<input v-model="captureDelay" type="number" step="50" min="50">
|
|
277
283
|
</FormItem>
|
|
278
284
|
</div>
|
|
279
285
|
</div>
|
|
@@ -325,10 +331,6 @@ if (import.meta.hot) {
|
|
|
325
331
|
}
|
|
326
332
|
}
|
|
327
333
|
|
|
328
|
-
button {
|
|
329
|
-
--uno: 'w-full rounded bg-gray:10 px-4 py-2 hover:bg-gray/20';
|
|
330
|
-
}
|
|
331
|
-
|
|
332
334
|
label {
|
|
333
335
|
--uno: text-xl flex gap-2 items-center select-none;
|
|
334
336
|
|
package/pages/notes.vue
CHANGED
|
@@ -19,7 +19,7 @@ const { isFullscreen, toggle: toggleFullscreen } = fullscreen
|
|
|
19
19
|
|
|
20
20
|
const scroller = ref<HTMLDivElement>()
|
|
21
21
|
const fontSize = useLocalStorage('slidev-notes-font-size', 18)
|
|
22
|
-
const pageNo = computed(() => sharedState.
|
|
22
|
+
const pageNo = computed(() => sharedState.page)
|
|
23
23
|
const currentRoute = computed(() => slides.value.find(i => i.no === pageNo.value))
|
|
24
24
|
|
|
25
25
|
watch(pageNo, () => {
|
|
@@ -36,8 +36,8 @@ function decreaseFontSize() {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const clicksContext = computed(() => {
|
|
39
|
-
const clicks = sharedState.
|
|
40
|
-
const total = sharedState.
|
|
39
|
+
const clicks = sharedState.clicks
|
|
40
|
+
const total = sharedState.clicksTotal
|
|
41
41
|
return createClicksContextBase(ref(clicks), undefined, total)
|
|
42
42
|
})
|
|
43
43
|
</script>
|
package/pages/play.vue
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { useStyleTag } from '@vueuse/core'
|
|
3
3
|
import { computed, ref, shallowRef } from 'vue'
|
|
4
4
|
import { useDrawings } from '../composables/useDrawings'
|
|
5
|
+
import { useHideCursorIdle } from '../composables/useHideCursorIdle'
|
|
5
6
|
import { useNav } from '../composables/useNav'
|
|
6
7
|
import { useSwipeControls } from '../composables/useSwipeControls'
|
|
7
8
|
import { useWakeLock } from '../composables/useWakeLock'
|
|
@@ -12,9 +13,9 @@ import SlideContainer from '../internals/SlideContainer.vue'
|
|
|
12
13
|
import SlidesShow from '../internals/SlidesShow.vue'
|
|
13
14
|
import { onContextMenu } from '../logic/contextMenu'
|
|
14
15
|
import { registerShortcuts } from '../logic/shortcuts'
|
|
15
|
-
import { editorHeight, editorWidth, isEditorVertical, isScreenVertical, showEditor } from '../state'
|
|
16
|
+
import { editorHeight, editorWidth, isEditorVertical, isScreenVertical, showEditor, viewerCssFilter, viewerCssFilterDefaults } from '../state'
|
|
16
17
|
|
|
17
|
-
const { next, prev, isPrintMode } = useNav()
|
|
18
|
+
const { next, prev, isPrintMode, isPresenter } = useNav()
|
|
18
19
|
const { isDrawing } = useDrawings()
|
|
19
20
|
|
|
20
21
|
const root = ref<HTMLDivElement>()
|
|
@@ -35,6 +36,7 @@ useSwipeControls(root)
|
|
|
35
36
|
registerShortcuts()
|
|
36
37
|
if (__SLIDEV_FEATURE_WAKE_LOCK__)
|
|
37
38
|
useWakeLock()
|
|
39
|
+
useHideCursorIdle(computed(() => !isPresenter.value && !isPrintMode.value))
|
|
38
40
|
|
|
39
41
|
if (import.meta.hot) {
|
|
40
42
|
useStyleTag(computed(() => showEditor.value
|
|
@@ -59,6 +61,25 @@ const persistNav = computed(() => isScreenVertical.value || showEditor.value)
|
|
|
59
61
|
const SideEditor = shallowRef<any>()
|
|
60
62
|
if (__DEV__ && __SLIDEV_FEATURE_EDITOR__)
|
|
61
63
|
import('../internals/SideEditor.vue').then(v => SideEditor.value = v.default)
|
|
64
|
+
|
|
65
|
+
const contentStyle = computed(() => {
|
|
66
|
+
let filter = ''
|
|
67
|
+
|
|
68
|
+
if (viewerCssFilter.value.brightness !== viewerCssFilterDefaults.brightness)
|
|
69
|
+
filter += `brightness(${viewerCssFilter.value.brightness}) `
|
|
70
|
+
if (viewerCssFilter.value.contrast !== viewerCssFilterDefaults.contrast)
|
|
71
|
+
filter += `contrast(${viewerCssFilter.value.contrast}) `
|
|
72
|
+
if (viewerCssFilter.value.sepia !== viewerCssFilterDefaults.sepia)
|
|
73
|
+
filter += `sepia(${viewerCssFilter.value.sepia}) `
|
|
74
|
+
if (viewerCssFilter.value.hueRotate !== viewerCssFilterDefaults.hueRotate)
|
|
75
|
+
filter += `hue-rotate(${viewerCssFilter.value.hueRotate}deg) `
|
|
76
|
+
if (viewerCssFilter.value.invert)
|
|
77
|
+
filter += 'invert(1) '
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
filter,
|
|
81
|
+
}
|
|
82
|
+
})
|
|
62
83
|
</script>
|
|
63
84
|
|
|
64
85
|
<template>
|
|
@@ -69,6 +90,7 @@ if (__DEV__ && __SLIDEV_FEATURE_EDITOR__)
|
|
|
69
90
|
<SlideContainer
|
|
70
91
|
:style="{ background: 'var(--slidev-slide-container-background, black)' }"
|
|
71
92
|
is-main
|
|
93
|
+
:content-style="contentStyle"
|
|
72
94
|
@pointerdown="onClick"
|
|
73
95
|
@contextmenu="onContextMenu"
|
|
74
96
|
>
|
package/pages/presenter.vue
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useHead } from '@unhead/vue'
|
|
3
|
-
import { useMouse, useWindowFocus } from '@vueuse/core'
|
|
3
|
+
import { useLocalStorage, useMouse, useWindowFocus } from '@vueuse/core'
|
|
4
4
|
import { computed, onMounted, reactive, ref, shallowRef, watch } from 'vue'
|
|
5
5
|
import { createFixedClicks } from '../composables/useClicks'
|
|
6
6
|
import { useDrawings } from '../composables/useDrawings'
|
|
@@ -18,6 +18,8 @@ import NavControls from '../internals/NavControls.vue'
|
|
|
18
18
|
import NoteEditable from '../internals/NoteEditable.vue'
|
|
19
19
|
import NoteStatic from '../internals/NoteStatic.vue'
|
|
20
20
|
import QuickOverview from '../internals/QuickOverview.vue'
|
|
21
|
+
import ScreenCaptureMirror from '../internals/ScreenCaptureMirror.vue'
|
|
22
|
+
import SegmentControl from '../internals/SegmentControl.vue'
|
|
21
23
|
import SlideContainer from '../internals/SlideContainer.vue'
|
|
22
24
|
import SlidesShow from '../internals/SlidesShow.vue'
|
|
23
25
|
import SlideWrapper from '../internals/SlideWrapper.vue'
|
|
@@ -26,6 +28,7 @@ import { registerShortcuts } from '../logic/shortcuts'
|
|
|
26
28
|
import { decreasePresenterFontSize, increasePresenterFontSize, presenterLayout, presenterNotesFontSize, showEditor, showPresenterCursor } from '../state'
|
|
27
29
|
import { sharedState } from '../state/shared'
|
|
28
30
|
|
|
31
|
+
const inFocus = useWindowFocus()
|
|
29
32
|
const main = ref<HTMLDivElement>()
|
|
30
33
|
|
|
31
34
|
registerShortcuts()
|
|
@@ -49,7 +52,7 @@ useHead({ title: `Presenter - ${slidesTitle}` })
|
|
|
49
52
|
|
|
50
53
|
const notesEditing = ref(false)
|
|
51
54
|
|
|
52
|
-
const { timer,
|
|
55
|
+
const { timer, isTimerActive, resetTimer, toggleTimer } = useTimer()
|
|
53
56
|
|
|
54
57
|
const clicksCtxMap = computed(() => slides.value.map(route => createFixedClicks(route)))
|
|
55
58
|
const nextFrame = computed(() => {
|
|
@@ -74,6 +77,7 @@ watch(
|
|
|
74
77
|
{ immediate: true },
|
|
75
78
|
)
|
|
76
79
|
|
|
80
|
+
const mainSlideMode = useLocalStorage<'slides' | 'mirror'>('slidev-presenter-main-slide-mode', 'slides')
|
|
77
81
|
const SideEditor = shallowRef<any>()
|
|
78
82
|
if (__DEV__ && __SLIDEV_FEATURE_EDITOR__)
|
|
79
83
|
import('../internals/SideEditor.vue').then(v => SideEditor.value = v.default)
|
|
@@ -86,7 +90,7 @@ onMounted(() => {
|
|
|
86
90
|
|
|
87
91
|
watch(
|
|
88
92
|
() => {
|
|
89
|
-
if (!focus.value || isDrawing.value || !showPresenterCursor.value)
|
|
93
|
+
if (!focus.value || isDrawing.value || !showPresenterCursor.value || !slidesContainer)
|
|
90
94
|
return undefined
|
|
91
95
|
|
|
92
96
|
const rect = slidesContainer.getBoundingClientRect()
|
|
@@ -106,25 +110,39 @@ onMounted(() => {
|
|
|
106
110
|
</script>
|
|
107
111
|
|
|
108
112
|
<template>
|
|
109
|
-
<div class="bg-main h-full slidev-presenter">
|
|
113
|
+
<div class="bg-main h-full slidev-presenter" pt-2px>
|
|
110
114
|
<div class="grid-container" :class="`layout${presenterLayout}`">
|
|
111
115
|
<div ref="main" class="relative grid-section main flex flex-col">
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
<div flex="~ gap-4 items-center" border="b main" p1>
|
|
117
|
+
<span op50 px2>Current</span>
|
|
118
|
+
<div flex-auto />
|
|
119
|
+
<SegmentControl
|
|
120
|
+
v-model="mainSlideMode"
|
|
121
|
+
:options="[
|
|
122
|
+
{ label: 'Slides', value: 'slides' },
|
|
123
|
+
{ label: 'Screen Mirror', value: 'mirror' },
|
|
124
|
+
]"
|
|
125
|
+
/>
|
|
126
|
+
</div>
|
|
127
|
+
<template v-if="mainSlideMode === 'mirror'">
|
|
128
|
+
<ScreenCaptureMirror />
|
|
129
|
+
</template>
|
|
130
|
+
<template v-else>
|
|
131
|
+
<SlideContainer
|
|
132
|
+
key="main"
|
|
133
|
+
class="p-2 lg:p-4 flex-auto"
|
|
134
|
+
is-main
|
|
135
|
+
@contextmenu="onContextMenu"
|
|
136
|
+
>
|
|
137
|
+
<SlidesShow render-context="presenter" />
|
|
138
|
+
</SlideContainer>
|
|
139
|
+
</template>
|
|
140
|
+
|
|
120
141
|
<ClicksSlider
|
|
121
142
|
:key="currentSlideRoute?.no"
|
|
122
143
|
:clicks-context="getPrimaryClicks(currentSlideRoute)"
|
|
123
144
|
class="w-full pb2 px4 flex-none"
|
|
124
145
|
/>
|
|
125
|
-
<div class="absolute left-0 top-0 bg-main border-b border-r border-main px2 py1 op50 text-sm">
|
|
126
|
-
Current
|
|
127
|
-
</div>
|
|
128
146
|
</div>
|
|
129
147
|
<div class="relative grid-section next flex flex-col p-2 lg:p-4">
|
|
130
148
|
<SlideContainer v-if="nextFrame && nextFrameClicksCtx" key="next">
|
|
@@ -165,7 +183,8 @@ onMounted(() => {
|
|
|
165
183
|
:style="{ fontSize: `${presenterNotesFontSize}em` }"
|
|
166
184
|
:clicks-context="clicksContext"
|
|
167
185
|
/>
|
|
168
|
-
<div
|
|
186
|
+
<div border-t border-main />
|
|
187
|
+
<div class="py-1 px-2 text-sm transition" :class="inFocus ? '' : 'op25'">
|
|
169
188
|
<IconButton title="Increase font size" @click="increasePresenterFontSize">
|
|
170
189
|
<div class="i-carbon:zoom-in" />
|
|
171
190
|
</IconButton>
|
|
@@ -182,14 +201,14 @@ onMounted(() => {
|
|
|
182
201
|
</div>
|
|
183
202
|
</div>
|
|
184
203
|
<div class="grid-section bottom flex">
|
|
185
|
-
<NavControls :persist="true" />
|
|
204
|
+
<NavControls :persist="true" class="transition" :class="inFocus ? '' : 'op25'" />
|
|
186
205
|
<div flex-auto />
|
|
187
206
|
<div class="group flex items-center justify-center pl-4 select-none">
|
|
188
207
|
<div class="w-22px cursor-pointer">
|
|
189
208
|
<div class="i-carbon:time group-hover:hidden text-xl" />
|
|
190
209
|
<div class="group-not-hover:hidden flex flex-col items-center">
|
|
191
210
|
<div class="relative op-80 hover:op-100" @click="toggleTimer">
|
|
192
|
-
<div v-if="
|
|
211
|
+
<div v-if="isTimerActive" class="i-carbon:pause text-lg" />
|
|
193
212
|
<div v-else class="i-carbon:play" />
|
|
194
213
|
</div>
|
|
195
214
|
<div class="op-80 hover:op-100" @click="resetTimer">
|
package/setup/root.ts
CHANGED
|
@@ -8,9 +8,9 @@ import { useNav } from '../composables/useNav'
|
|
|
8
8
|
import { usePrintStyles } from '../composables/usePrintStyles'
|
|
9
9
|
import { injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionSlidevContext, TRUST_ORIGINS } from '../constants'
|
|
10
10
|
import { configs, slidesTitle } from '../env'
|
|
11
|
-
import { skipTransition } from '../logic/hmr'
|
|
12
11
|
import { getSlidePath } from '../logic/slides'
|
|
13
12
|
import { makeId } from '../logic/utils'
|
|
13
|
+
import { hmrSkipTransition, syncDirections } from '../state'
|
|
14
14
|
import { initDrawingState } from '../state/drawings'
|
|
15
15
|
import { initSharedState, onPatch, patch } from '../state/shared'
|
|
16
16
|
|
|
@@ -58,30 +58,28 @@ export default function setupRoot() {
|
|
|
58
58
|
initDrawingState(`${slidesTitle} - drawings`)
|
|
59
59
|
|
|
60
60
|
const id = `${location.origin}_${makeId()}`
|
|
61
|
+
const syncType = computed(() => isPresenter.value ? 'presenter' : 'viewer')
|
|
61
62
|
|
|
62
63
|
// update shared state
|
|
63
64
|
function updateSharedState() {
|
|
65
|
+
const shouldSend = isPresenter.value
|
|
66
|
+
? syncDirections.value.presenterSend
|
|
67
|
+
: syncDirections.value.viewerSend
|
|
68
|
+
|
|
69
|
+
if (!shouldSend)
|
|
70
|
+
return
|
|
64
71
|
if (isNotesViewer.value || isPrintMode.value)
|
|
65
72
|
return
|
|
66
|
-
|
|
67
73
|
// we allow Presenter mode, or Viewer mode from trusted origins to update the shared state
|
|
68
74
|
if (!isPresenter.value && !TRUST_ORIGINS.includes(location.host.split(':')[0]))
|
|
69
75
|
return
|
|
70
76
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
patch('clicksTotal', clicksContext.value.total)
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
patch('viewerPage', +currentSlideNo.value)
|
|
78
|
-
patch('viewerClicks', clicksContext.value.current)
|
|
79
|
-
patch('viewerClicksTotal', clicksContext.value.total)
|
|
80
|
-
}
|
|
81
|
-
|
|
77
|
+
patch('page', +currentSlideNo.value)
|
|
78
|
+
patch('clicks', clicksContext.value.current)
|
|
79
|
+
patch('clicksTotal', clicksContext.value.total)
|
|
82
80
|
patch('lastUpdate', {
|
|
83
81
|
id,
|
|
84
|
-
type:
|
|
82
|
+
type: syncType.value,
|
|
85
83
|
time: new Date().getTime(),
|
|
86
84
|
})
|
|
87
85
|
}
|
|
@@ -90,17 +88,26 @@ export default function setupRoot() {
|
|
|
90
88
|
watch(clicksContext, updateSharedState)
|
|
91
89
|
|
|
92
90
|
onPatch((state) => {
|
|
91
|
+
const shouldReceive = isPresenter.value
|
|
92
|
+
? syncDirections.value.presenterReceive
|
|
93
|
+
: syncDirections.value.viewerReceive
|
|
94
|
+
if (!shouldReceive)
|
|
95
|
+
return
|
|
93
96
|
if (!hasPrimarySlide.value || isPrintMode.value)
|
|
94
97
|
return
|
|
95
|
-
if (state.lastUpdate?.type ===
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
if (state.lastUpdate?.type === syncType.value)
|
|
99
|
+
return
|
|
100
|
+
if ((+state.page === +currentSlideNo.value && +clicksContext.value.current === +state.clicks))
|
|
101
|
+
return
|
|
102
|
+
// if (state.lastUpdate?.type === 'presenter') {
|
|
103
|
+
hmrSkipTransition.value = false
|
|
104
|
+
router.replace({
|
|
105
|
+
path: getSlidePath(state.page, isPresenter.value),
|
|
106
|
+
query: {
|
|
107
|
+
...router.currentRoute.value.query,
|
|
108
|
+
clicks: state.clicks || 0,
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
// }
|
|
105
112
|
})
|
|
106
113
|
}
|
package/state/drawings.ts
CHANGED
|
@@ -9,4 +9,8 @@ export const {
|
|
|
9
9
|
onUpdate: onDrawingUpdate,
|
|
10
10
|
patch: patchDrawingState,
|
|
11
11
|
state: drawingState,
|
|
12
|
-
} = createSyncState<DrawingsState>(
|
|
12
|
+
} = createSyncState<DrawingsState>(
|
|
13
|
+
serverDrawingState,
|
|
14
|
+
serverDrawingState,
|
|
15
|
+
__SLIDEV_FEATURE_DRAWINGS_PERSIST__,
|
|
16
|
+
)
|
package/state/index.ts
CHANGED
|
@@ -1,56 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { breakpointsTailwind, isClient, useActiveElement, useBreakpoints, useFullscreen, useLocalStorage, useMagicKeys, useToggle, useWindowSize } from '@vueuse/core'
|
|
3
|
-
import { computed, ref, shallowRef } from 'vue'
|
|
4
|
-
import { slideAspect } from '../env'
|
|
5
|
-
|
|
6
|
-
export const showRecordingDialog = ref(false)
|
|
7
|
-
export const showInfoDialog = ref(false)
|
|
8
|
-
export const showGotoDialog = ref(false)
|
|
9
|
-
export const showOverview = ref(false)
|
|
10
|
-
|
|
11
|
-
export const shortcutsEnabled = ref(true)
|
|
12
|
-
export const breakpoints = useBreakpoints({
|
|
13
|
-
xs: 460,
|
|
14
|
-
...breakpointsTailwind,
|
|
15
|
-
})
|
|
16
|
-
export const windowSize = useWindowSize()
|
|
17
|
-
export const magicKeys = useMagicKeys()
|
|
18
|
-
export const isScreenVertical = computed(() => windowSize.height.value - windowSize.width.value / slideAspect.value > 120)
|
|
19
|
-
export const fullscreen = useFullscreen(isClient ? document.body : null)
|
|
20
|
-
|
|
21
|
-
export const activeElement = useActiveElement()
|
|
22
|
-
export const isInputting = computed(() => ['INPUT', 'TEXTAREA'].includes(activeElement.value?.tagName || ''))
|
|
23
|
-
export const isOnFocus = computed(() => ['BUTTON', 'A'].includes(activeElement.value?.tagName || ''))
|
|
24
|
-
|
|
25
|
-
export const currentCamera = useLocalStorage<string>('slidev-camera', 'default', { listenToStorageChanges: false })
|
|
26
|
-
export const currentMic = useLocalStorage<string>('slidev-mic', 'default', { listenToStorageChanges: false })
|
|
27
|
-
export const slideScale = useLocalStorage<number>('slidev-scale', 0)
|
|
28
|
-
export const wakeLockEnabled = useLocalStorage('slidev-wake-lock', true)
|
|
29
|
-
export const skipExportPdfTip = useLocalStorage('slidev-skip-export-pdf-tip', false)
|
|
30
|
-
|
|
31
|
-
export const showPresenterCursor = useLocalStorage('slidev-presenter-cursor', true, { listenToStorageChanges: false })
|
|
32
|
-
export const showEditor = useLocalStorage('slidev-show-editor', false, { listenToStorageChanges: false })
|
|
33
|
-
export const isEditorVertical = useLocalStorage('slidev-editor-vertical', false, { listenToStorageChanges: false })
|
|
34
|
-
export const editorWidth = useLocalStorage('slidev-editor-width', isClient ? window.innerWidth * 0.4 : 318, { listenToStorageChanges: false })
|
|
35
|
-
export const editorHeight = useLocalStorage('slidev-editor-height', isClient ? window.innerHeight * 0.4 : 300, { listenToStorageChanges: false })
|
|
36
|
-
|
|
37
|
-
export const activeDragElement = shallowRef<DragElementState | null>(null)
|
|
38
|
-
|
|
39
|
-
export const presenterNotesFontSize = useLocalStorage('slidev-presenter-font-size', 1, { listenToStorageChanges: false })
|
|
40
|
-
export const presenterLayout = useLocalStorage('slidev-presenter-layout', 1, { listenToStorageChanges: false })
|
|
41
|
-
|
|
42
|
-
export function togglePresenterLayout() {
|
|
43
|
-
presenterLayout.value = presenterLayout.value + 1
|
|
44
|
-
if (presenterLayout.value > 3)
|
|
45
|
-
presenterLayout.value = 1
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function increasePresenterFontSize() {
|
|
49
|
-
presenterNotesFontSize.value = Math.min(2, presenterNotesFontSize.value + 0.1)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function decreasePresenterFontSize() {
|
|
53
|
-
presenterNotesFontSize.value = Math.max(0.5, presenterNotesFontSize.value - 0.1)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export const toggleOverview = useToggle(showOverview)
|
|
1
|
+
export * from './storage'
|
package/state/shared.ts
CHANGED
|
@@ -11,10 +11,6 @@ export interface SharedState {
|
|
|
11
11
|
y: number
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
viewerPage: number
|
|
15
|
-
viewerClicks: number
|
|
16
|
-
viewerClicksTotal: number
|
|
17
|
-
|
|
18
14
|
lastUpdate?: {
|
|
19
15
|
id: string
|
|
20
16
|
type: 'presenter' | 'viewer'
|
|
@@ -26,9 +22,6 @@ const { init, onPatch, onUpdate, patch, state } = createSyncState<SharedState>(s
|
|
|
26
22
|
page: 1,
|
|
27
23
|
clicks: 0,
|
|
28
24
|
clicksTotal: 0,
|
|
29
|
-
viewerPage: 1,
|
|
30
|
-
viewerClicks: 0,
|
|
31
|
-
viewerClicksTotal: 0,
|
|
32
25
|
})
|
|
33
26
|
|
|
34
27
|
export {
|
package/state/snapshot.ts
CHANGED
package/state/storage.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { DragElementState } from '../composables/useDragElements'
|
|
2
|
+
import { breakpointsTailwind, isClient, useActiveElement, useBreakpoints, useFullscreen, useLocalStorage, useMagicKeys, useToggle, useWindowSize } from '@vueuse/core'
|
|
3
|
+
import { computed, ref, shallowRef } from 'vue'
|
|
4
|
+
import { slideAspect } from '../env'
|
|
5
|
+
|
|
6
|
+
export const showRecordingDialog = ref(false)
|
|
7
|
+
export const showInfoDialog = ref(false)
|
|
8
|
+
export const showGotoDialog = ref(false)
|
|
9
|
+
export const showOverview = ref(false)
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Skip slides transition when triggered by HMR.
|
|
13
|
+
* Will reset automatically after user navigations
|
|
14
|
+
*/
|
|
15
|
+
export const hmrSkipTransition = ref(false)
|
|
16
|
+
export const disableTransition = ref(false)
|
|
17
|
+
|
|
18
|
+
export const shortcutsEnabled = ref(true)
|
|
19
|
+
export const breakpoints = useBreakpoints({
|
|
20
|
+
xs: 460,
|
|
21
|
+
...breakpointsTailwind,
|
|
22
|
+
})
|
|
23
|
+
export const windowSize = useWindowSize()
|
|
24
|
+
export const magicKeys = useMagicKeys()
|
|
25
|
+
export const isScreenVertical = computed(() => windowSize.height.value - windowSize.width.value / slideAspect.value > 120)
|
|
26
|
+
export const fullscreen = useFullscreen(isClient ? document.body : null)
|
|
27
|
+
|
|
28
|
+
export const activeElement = useActiveElement()
|
|
29
|
+
export const isInputting = computed(() => ['INPUT', 'TEXTAREA'].includes(activeElement.value?.tagName || ''))
|
|
30
|
+
export const isOnFocus = computed(() => ['BUTTON', 'A'].includes(activeElement.value?.tagName || ''))
|
|
31
|
+
|
|
32
|
+
export const currentCamera = useLocalStorage<string>('slidev-camera', 'default', { listenToStorageChanges: false })
|
|
33
|
+
export const currentMic = useLocalStorage<string>('slidev-mic', 'default', { listenToStorageChanges: false })
|
|
34
|
+
export const slideScale = useLocalStorage<number>('slidev-scale', 0)
|
|
35
|
+
export const wakeLockEnabled = useLocalStorage('slidev-wake-lock', true)
|
|
36
|
+
export const hideCursorIdle = useLocalStorage('slidev-hide-cursor-idle', true)
|
|
37
|
+
export const skipExportPdfTip = useLocalStorage('slidev-skip-export-pdf-tip', false)
|
|
38
|
+
export const captureDelay = useLocalStorage('slidev-export-capture-delay', 400, { listenToStorageChanges: false })
|
|
39
|
+
|
|
40
|
+
export const showPresenterCursor = useLocalStorage('slidev-presenter-cursor', true, { listenToStorageChanges: false })
|
|
41
|
+
export const showEditor = useLocalStorage('slidev-show-editor', false, { listenToStorageChanges: false })
|
|
42
|
+
export const isEditorVertical = useLocalStorage('slidev-editor-vertical', false, { listenToStorageChanges: false })
|
|
43
|
+
export const editorWidth = useLocalStorage('slidev-editor-width', isClient ? window.innerWidth * 0.4 : 318, { listenToStorageChanges: false })
|
|
44
|
+
export const editorHeight = useLocalStorage('slidev-editor-height', isClient ? window.innerHeight * 0.4 : 300, { listenToStorageChanges: false })
|
|
45
|
+
|
|
46
|
+
export const activeDragElement = shallowRef<DragElementState | null>(null)
|
|
47
|
+
|
|
48
|
+
export const presenterNotesFontSize = useLocalStorage('slidev-presenter-font-size', 1, { listenToStorageChanges: false })
|
|
49
|
+
export const presenterLayout = useLocalStorage('slidev-presenter-layout', 1, { listenToStorageChanges: false })
|
|
50
|
+
|
|
51
|
+
export const viewerCssFilterDefaults = {
|
|
52
|
+
invert: false,
|
|
53
|
+
contrast: 1,
|
|
54
|
+
brightness: 1,
|
|
55
|
+
hueRotate: 0,
|
|
56
|
+
saturate: 1,
|
|
57
|
+
sepia: 0,
|
|
58
|
+
}
|
|
59
|
+
export const viewerCssFilter = useLocalStorage(
|
|
60
|
+
'slidev-viewer-css-filter',
|
|
61
|
+
viewerCssFilterDefaults,
|
|
62
|
+
{ listenToStorageChanges: false, mergeDefaults: true, deep: true },
|
|
63
|
+
)
|
|
64
|
+
export const hasViewerCssFilter = computed(() => {
|
|
65
|
+
return (Object.keys(viewerCssFilterDefaults) as (keyof typeof viewerCssFilterDefaults)[])
|
|
66
|
+
.some(k => viewerCssFilter.value[k] !== viewerCssFilterDefaults[k])
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
export function togglePresenterLayout() {
|
|
70
|
+
presenterLayout.value = presenterLayout.value + 1
|
|
71
|
+
if (presenterLayout.value > 3)
|
|
72
|
+
presenterLayout.value = 1
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function increasePresenterFontSize() {
|
|
76
|
+
presenterNotesFontSize.value = Math.min(2, presenterNotesFontSize.value + 0.1)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function decreasePresenterFontSize() {
|
|
80
|
+
presenterNotesFontSize.value = Math.max(0.5, presenterNotesFontSize.value - 0.1)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const toggleOverview = useToggle(showOverview)
|
|
84
|
+
|
|
85
|
+
export const syncDirections = useLocalStorage(
|
|
86
|
+
'slidev-sync-directions',
|
|
87
|
+
{
|
|
88
|
+
viewerSend: true,
|
|
89
|
+
viewerReceive: true,
|
|
90
|
+
presenterSend: true,
|
|
91
|
+
presenterReceive: true,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
listenToStorageChanges: false,
|
|
95
|
+
mergeDefaults: true,
|
|
96
|
+
},
|
|
97
|
+
)
|
package/styles/index.css
CHANGED
|
@@ -22,7 +22,7 @@ html {
|
|
|
22
22
|
user-select: none;
|
|
23
23
|
outline: none;
|
|
24
24
|
cursor: pointer;
|
|
25
|
-
@apply inline-flex items-center justify-center opacity-75 transition duration-200 ease-in-out align-middle rounded p-1;
|
|
25
|
+
@apply inline-flex items-center justify-center opacity-75 transition duration-200 ease-in-out align-middle rounded p-1 relative;
|
|
26
26
|
@apply hover:(opacity-100 bg-gray-400 bg-opacity-10);
|
|
27
27
|
@apply focus-visible:(opacity-100 outline outline-2 outline-offset-2 outline-black dark:outline-white);
|
|
28
28
|
@apply md:p-2;
|
|
@@ -121,6 +121,16 @@ html {
|
|
|
121
121
|
transform: translateY(0.1em);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
.slidev-form-button {
|
|
125
|
+
--uno: text-white px-4 py-1 rounded border-b-2;
|
|
126
|
+
--uno: 'bg-gray-400:50 border-gray-800:50';
|
|
127
|
+
--uno: 'hover:(bg-gray-400:75 border-gray8:75)';
|
|
128
|
+
}
|
|
129
|
+
.slidev-form-button.primary {
|
|
130
|
+
--uno: bg-teal-600 border-teal-800;
|
|
131
|
+
--uno: 'hover:(bg-teal-500 border-teal-700)';
|
|
132
|
+
}
|
|
133
|
+
|
|
124
134
|
/* Transform the position back for Rough Notation (v-mark) */
|
|
125
135
|
.rough-annotation {
|
|
126
136
|
transform: scale(calc(1 / var(--slidev-slide-scale)));
|
package/uno.config.ts
CHANGED
package/logic/hmr.ts
DELETED