@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/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, useLocalStorage, useStyleTag, watchDebounced } from '@vueuse/core'
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 + delay.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(delay.value)
83
+ await sleep(captureDelay.value)
85
84
  next()
86
- await sleep(delay.value)
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="Dark mode">
231
- <FormCheckbox v-model="isDark" :disabled="isColorSchemaConfigured" />
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 items-start min-w-max">
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 items-start min-w-max">
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 items-start min-w-max">
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="delay" type="number" step="50" min="50">
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.lastUpdate?.type === 'viewer' ? sharedState.viewerPage : sharedState.page)
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.lastUpdate?.type === 'viewer' ? sharedState.viewerClicks : sharedState.clicks
40
- const total = sharedState.lastUpdate?.type === 'viewer' ? sharedState.viewerClicksTotal : sharedState.clicksTotal
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
  >
@@ -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, isTimerAvctive, resetTimer, toggleTimer } = useTimer()
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
- <SlideContainer
113
- key="main"
114
- class="p-2 lg:p-4 flex-auto"
115
- is-main
116
- @contextmenu="onContextMenu"
117
- >
118
- <SlidesShow render-context="presenter" />
119
- </SlideContainer>
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 class="border-t border-main py-1 px-2 text-sm">
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="isTimerAvctive" class="i-carbon:pause text-lg" />
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
- if (isPresenter.value) {
72
- patch('page', +currentSlideNo.value)
73
- patch('clicks', clicksContext.value.current)
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: isPresenter.value ? 'presenter' : 'viewer',
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 === 'presenter' && (+state.page !== +currentSlideNo.value || +clicksContext.value.current !== +state.clicks)) {
96
- skipTransition.value = false
97
- router.replace({
98
- path: getSlidePath(state.page, isPresenter.value),
99
- query: {
100
- ...router.currentRoute.value.query,
101
- clicks: state.clicks || 0,
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>(serverDrawingState, serverDrawingState, __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
12
+ } = createSyncState<DrawingsState>(
13
+ serverDrawingState,
14
+ serverDrawingState,
15
+ __SLIDEV_FEATURE_DRAWINGS_PERSIST__,
16
+ )
package/state/index.ts CHANGED
@@ -1,56 +1 @@
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
- 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
@@ -1,7 +1,7 @@
1
1
  import serverSnapshotState from 'server-reactive:snapshots?diff'
2
2
  import { createSyncState } from './syncState'
3
3
 
4
- export type SnapshotState = Record<number, {
4
+ export type SnapshotState = Record<string, {
5
5
  revision: string
6
6
  image: string
7
7
  }>
@@ -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
@@ -39,6 +39,7 @@ export default defineConfig({
39
39
  'z-nav': 'z-50',
40
40
  'z-context-menu': 'z-60',
41
41
  'z-modal': 'z-70',
42
+ 'z-focus-indicator': 'z-200',
42
43
 
43
44
  'slidev-glass-effect': 'shadow-xl backdrop-blur-8 border border-main bg-main bg-opacity-75!',
44
45
  },
package/logic/hmr.ts DELETED
@@ -1,3 +0,0 @@
1
- import { ref } from 'vue'
2
-
3
- export const skipTransition = ref(false)