@slidev/client 0.50.0-beta.9 → 0.51.0-beta.1

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 (57) hide show
  1. package/composables/useClicks.ts +6 -5
  2. package/composables/useDragElements.ts +30 -24
  3. package/composables/useNav.ts +17 -15
  4. package/composables/usePrintStyles.ts +28 -0
  5. package/composables/useTimer.ts +1 -1
  6. package/constants.ts +1 -0
  7. package/internals/Badge.vue +48 -0
  8. package/internals/ClicksSlider.vue +1 -1
  9. package/internals/ContextMenu.vue +1 -1
  10. package/internals/{DevicesList.vue → DevicesSelectors.vue} +12 -4
  11. package/internals/DragControl.vue +1 -1
  12. package/internals/DrawingControls.vue +1 -1
  13. package/internals/ExportPdfTip.vue +90 -0
  14. package/internals/FormCheckbox.vue +16 -0
  15. package/internals/FormItem.vue +41 -0
  16. package/internals/IconButton.vue +7 -2
  17. package/internals/MenuButton.vue +2 -2
  18. package/internals/Modal.vue +1 -1
  19. package/internals/NavControls.vue +21 -9
  20. package/internals/PrintContainer.vue +2 -21
  21. package/internals/PrintSlide.vue +4 -3
  22. package/internals/PrintSlideClick.vue +11 -3
  23. package/internals/QuickOverview.vue +2 -2
  24. package/internals/RecordingControls.vue +2 -2
  25. package/internals/RecordingDialog.vue +4 -14
  26. package/internals/ScreenCaptureMirror.vue +45 -0
  27. package/internals/SegmentControl.vue +29 -0
  28. package/internals/SelectList.vue +1 -5
  29. package/internals/Settings.vue +16 -3
  30. package/internals/SideEditor.vue +1 -1
  31. package/internals/SlidesShow.vue +7 -3
  32. package/internals/SyncControls.vue +73 -0
  33. package/internals/WebCamera.vue +2 -2
  34. package/layouts/error.vue +5 -1
  35. package/logic/color.ts +62 -0
  36. package/logic/dark.ts +11 -0
  37. package/logic/screenshot.ts +61 -0
  38. package/logic/shortcuts.ts +36 -35
  39. package/logic/slides.ts +2 -1
  40. package/modules/v-mark.ts +6 -0
  41. package/package.json +20 -18
  42. package/pages/export.vue +365 -0
  43. package/pages/notes.vue +3 -3
  44. package/pages/overview.vue +1 -1
  45. package/pages/play.vue +1 -4
  46. package/pages/presenter.vue +46 -18
  47. package/pages/print.vue +0 -2
  48. package/setup/monaco.ts +14 -14
  49. package/setup/root.ts +37 -25
  50. package/setup/routes.ts +23 -12
  51. package/state/drawings.ts +5 -1
  52. package/state/index.ts +1 -55
  53. package/state/shared.ts +0 -7
  54. package/state/storage.ts +70 -0
  55. package/styles/index.css +15 -4
  56. package/uno.config.ts +15 -0
  57. package/internals/PrintStyle.vue +0 -16
@@ -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">
@@ -247,6 +266,15 @@ onMounted(() => {
247
266
  'bottom bottom';
248
267
  }
249
268
 
269
+ .grid-container.layout3 {
270
+ grid-template-columns: 2fr 3fr;
271
+ grid-template-rows: 1fr 1fr min-content;
272
+ grid-template-areas:
273
+ 'note next'
274
+ 'main next'
275
+ 'bottom bottom';
276
+ }
277
+
250
278
  @media (max-aspect-ratio: 3/5) {
251
279
  .grid-container.layout1 {
252
280
  grid-template-columns: 1fr;
package/pages/print.vue CHANGED
@@ -3,7 +3,6 @@ import { recomputeAllPoppers } from 'floating-vue'
3
3
  import { onMounted, watchEffect } from 'vue'
4
4
  import { useNav } from '../composables/useNav'
5
5
  import PrintContainer from '../internals/PrintContainer.vue'
6
- import PrintStyle from '../internals/PrintStyle.vue'
7
6
  import { windowSize } from '../state'
8
7
 
9
8
  const { isPrintMode } = useNav()
@@ -21,7 +20,6 @@ onMounted(() => {
21
20
  </script>
22
21
 
23
22
  <template>
24
- <PrintStyle v-if="isPrintMode" />
25
23
  <div id="page-root" class="grid grid-cols-[1fr_max-content]">
26
24
  <PrintContainer
27
25
  class="w-full h-full"
package/setup/monaco.ts CHANGED
@@ -64,22 +64,22 @@ const setup = createSingletonPromise(async () => {
64
64
 
65
65
  const ata = configs.monacoTypesSource === 'cdn'
66
66
  ? setupTypeAcquisition({
67
- projectName: 'TypeScript Playground',
68
- typescript: ts as any, // Version mismatch. No problem found so far.
69
- logger: console,
70
- delegate: {
71
- receivedFile: (code: string, path: string) => {
72
- defaults.addExtraLib(code, `file://${path}`)
73
- const uri = monaco.Uri.file(path)
74
- if (monaco.editor.getModel(uri) === null)
75
- monaco.editor.createModel(code, 'javascript', uri)
76
- },
77
- progress: (downloaded: number, total: number) => {
67
+ projectName: 'TypeScript Playground',
68
+ typescript: ts as any, // Version mismatch. No problem found so far.
69
+ logger: console,
70
+ delegate: {
71
+ receivedFile: (code: string, path: string) => {
72
+ defaults.addExtraLib(code, `file://${path}`)
73
+ const uri = monaco.Uri.file(path)
74
+ if (monaco.editor.getModel(uri) === null)
75
+ monaco.editor.createModel(code, 'javascript', uri)
76
+ },
77
+ progress: (downloaded: number, total: number) => {
78
78
  // eslint-disable-next-line no-console
79
- console.debug(`[Typescript ATA] ${downloaded} / ${total}`)
79
+ console.debug(`[Typescript ATA] ${downloaded} / ${total}`)
80
+ },
80
81
  },
81
- },
82
- })
82
+ })
83
83
  : () => { }
84
84
 
85
85
  monaco.languages.register({ id: 'vue' })
package/setup/root.ts CHANGED
@@ -5,11 +5,13 @@ import { useRouter } from 'vue-router'
5
5
  import { createFixedClicks } from '../composables/useClicks'
6
6
  import { useEmbeddedControl } from '../composables/useEmbeddedCtrl'
7
7
  import { useNav } from '../composables/useNav'
8
+ import { usePrintStyles } from '../composables/usePrintStyles'
8
9
  import { injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionSlidevContext, TRUST_ORIGINS } from '../constants'
9
10
  import { configs, slidesTitle } from '../env'
10
11
  import { skipTransition } from '../logic/hmr'
11
12
  import { getSlidePath } from '../logic/slides'
12
13
  import { makeId } from '../logic/utils'
14
+ import { syncDirections } from '../state'
13
15
  import { initDrawingState } from '../state/drawings'
14
16
  import { initSharedState, onPatch, patch } from '../state/shared'
15
17
 
@@ -43,6 +45,7 @@ export default function setupRoot() {
43
45
  hasPrimarySlide,
44
46
  isNotesViewer,
45
47
  isPresenter,
48
+ isPrintMode,
46
49
  } = useNav()
47
50
 
48
51
  useHead({
@@ -50,34 +53,34 @@ export default function setupRoot() {
50
53
  htmlAttrs: configs.htmlAttrs,
51
54
  })
52
55
 
56
+ usePrintStyles()
57
+
53
58
  initSharedState(`${slidesTitle} - shared`)
54
59
  initDrawingState(`${slidesTitle} - drawings`)
55
60
 
56
61
  const id = `${location.origin}_${makeId()}`
62
+ const syncType = computed(() => isPresenter.value ? 'presenter' : 'viewer')
57
63
 
58
64
  // update shared state
59
65
  function updateSharedState() {
60
- if (isNotesViewer.value)
61
- return
66
+ const shouldSend = isPresenter.value
67
+ ? syncDirections.value.presenterSend
68
+ : syncDirections.value.viewerSend
62
69
 
70
+ if (!shouldSend)
71
+ return
72
+ if (isNotesViewer.value || isPrintMode.value)
73
+ return
63
74
  // we allow Presenter mode, or Viewer mode from trusted origins to update the shared state
64
75
  if (!isPresenter.value && !TRUST_ORIGINS.includes(location.host.split(':')[0]))
65
76
  return
66
77
 
67
- if (isPresenter.value) {
68
- patch('page', +currentSlideNo.value)
69
- patch('clicks', clicksContext.value.current)
70
- patch('clicksTotal', clicksContext.value.total)
71
- }
72
- else {
73
- patch('viewerPage', +currentSlideNo.value)
74
- patch('viewerClicks', clicksContext.value.current)
75
- patch('viewerClicksTotal', clicksContext.value.total)
76
- }
77
-
78
+ patch('page', +currentSlideNo.value)
79
+ patch('clicks', clicksContext.value.current)
80
+ patch('clicksTotal', clicksContext.value.total)
78
81
  patch('lastUpdate', {
79
82
  id,
80
- type: isPresenter.value ? 'presenter' : 'viewer',
83
+ type: syncType.value,
81
84
  time: new Date().getTime(),
82
85
  })
83
86
  }
@@ -86,17 +89,26 @@ export default function setupRoot() {
86
89
  watch(clicksContext, updateSharedState)
87
90
 
88
91
  onPatch((state) => {
89
- if (!hasPrimarySlide.value)
92
+ const shouldReceive = isPresenter.value
93
+ ? syncDirections.value.presenterReceive
94
+ : syncDirections.value.viewerReceive
95
+ if (!shouldReceive)
90
96
  return
91
- if (state.lastUpdate?.type === 'presenter' && (+state.page !== +currentSlideNo.value || +clicksContext.value.current !== +state.clicks)) {
92
- skipTransition.value = false
93
- router.replace({
94
- path: getSlidePath(state.page, isPresenter.value),
95
- query: {
96
- ...router.currentRoute.value.query,
97
- clicks: state.clicks || 0,
98
- },
99
- })
100
- }
97
+ if (!hasPrimarySlide.value || isPrintMode.value)
98
+ return
99
+ if (state.lastUpdate?.type === syncType.value)
100
+ return
101
+ if ((+state.page === +currentSlideNo.value && +clicksContext.value.current === +state.clicks))
102
+ return
103
+ // if (state.lastUpdate?.type === 'presenter') {
104
+ skipTransition.value = false
105
+ router.replace({
106
+ path: getSlidePath(state.page, isPresenter.value),
107
+ query: {
108
+ ...router.currentRoute.value.query,
109
+ clicks: state.clicks || 0,
110
+ },
111
+ })
112
+ // }
101
113
  })
102
114
  }
package/setup/routes.ts CHANGED
@@ -5,21 +5,21 @@ import setups from '#slidev/setups/routes'
5
5
  export default function setupRoutes() {
6
6
  const routes: RouteRecordRaw[] = []
7
7
 
8
- if (__SLIDEV_FEATURE_PRESENTER__) {
9
- function passwordGuard(to: RouteLocationNormalized) {
10
- if (!configs.remote || configs.remote === to.query.password)
8
+ function passwordGuard(to: RouteLocationNormalized) {
9
+ if (!configs.remote || configs.remote === to.query.password)
10
+ return true
11
+ if (configs.remote && to.query.password === undefined) {
12
+ // eslint-disable-next-line no-alert
13
+ const password = prompt('Enter password')
14
+ if (configs.remote === password)
11
15
  return true
12
- if (configs.remote && to.query.password === undefined) {
13
- // eslint-disable-next-line no-alert
14
- const password = prompt('Enter password')
15
- if (configs.remote === password)
16
- return true
17
- }
18
- if (to.params.no)
19
- return { path: `/${to.params.no}` }
20
- return { path: '' }
21
16
  }
17
+ if (to.params.no)
18
+ return { path: `/${to.params.no}` }
19
+ return { path: '' }
20
+ }
22
21
 
22
+ if (__SLIDEV_FEATURE_PRESENTER__) {
23
23
  routes.push(
24
24
  {
25
25
  name: 'entry',
@@ -64,6 +64,17 @@ export default function setupRoutes() {
64
64
  )
65
65
  }
66
66
 
67
+ if (__SLIDEV_FEATURE_BROWSER_EXPORTER__) {
68
+ routes.push(
69
+ {
70
+ name: 'export',
71
+ path: '/export/:no?',
72
+ component: () => import('../pages/export.vue'),
73
+ beforeEnter: passwordGuard,
74
+ },
75
+ )
76
+ }
77
+
67
78
  routes.push(
68
79
  {
69
80
  name: 'play',
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,55 +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
-
30
- export const showPresenterCursor = useLocalStorage('slidev-presenter-cursor', true, { listenToStorageChanges: false })
31
- export const showEditor = useLocalStorage('slidev-show-editor', false, { listenToStorageChanges: false })
32
- export const isEditorVertical = useLocalStorage('slidev-editor-vertical', false, { listenToStorageChanges: false })
33
- export const editorWidth = useLocalStorage('slidev-editor-width', isClient ? window.innerWidth * 0.4 : 318, { listenToStorageChanges: false })
34
- export const editorHeight = useLocalStorage('slidev-editor-height', isClient ? window.innerHeight * 0.4 : 300, { listenToStorageChanges: false })
35
-
36
- export const activeDragElement = shallowRef<DragElementState | null>(null)
37
-
38
- export const presenterNotesFontSize = useLocalStorage('slidev-presenter-font-size', 1, { listenToStorageChanges: false })
39
- export const presenterLayout = useLocalStorage('slidev-presenter-layout', 1, { listenToStorageChanges: false })
40
-
41
- export function togglePresenterLayout() {
42
- presenterLayout.value = presenterLayout.value + 1
43
- if (presenterLayout.value > 2)
44
- presenterLayout.value = 1
45
- }
46
-
47
- export function increasePresenterFontSize() {
48
- presenterNotesFontSize.value = Math.min(2, presenterNotesFontSize.value + 0.1)
49
- }
50
-
51
- export function decreasePresenterFontSize() {
52
- presenterNotesFontSize.value = Math.max(0.5, presenterNotesFontSize.value - 0.1)
53
- }
54
-
55
- 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 {
@@ -0,0 +1,70 @@
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)
57
+
58
+ export const syncDirections = useLocalStorage(
59
+ 'slidev-sync-directions',
60
+ {
61
+ viewerSend: true,
62
+ viewerReceive: true,
63
+ presenterSend: true,
64
+ presenterReceive: true,
65
+ },
66
+ {
67
+ listenToStorageChanges: false,
68
+ mergeDefaults: true,
69
+ },
70
+ )
package/styles/index.css CHANGED
@@ -8,7 +8,9 @@ body,
8
8
  height: 100vh;
9
9
  height: calc(var(--vh, 1vh) * 100);
10
10
  overflow: hidden;
11
- @apply font-sans;
11
+ print-color-adjust: exact;
12
+ -webkit-print-color-adjust: exact;
13
+ --uno: font-sans bg-main;
12
14
  }
13
15
 
14
16
  html {
@@ -17,11 +19,10 @@ html {
17
19
 
18
20
  .slidev-icon-btn {
19
21
  aspect-ratio: 1;
20
- display: inline-block;
21
22
  user-select: none;
22
23
  outline: none;
23
24
  cursor: pointer;
24
- @apply 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;
25
26
  @apply hover:(opacity-100 bg-gray-400 bg-opacity-10);
26
27
  @apply focus-visible:(opacity-100 outline outline-2 outline-offset-2 outline-black dark:outline-white);
27
28
  @apply md:p-2;
@@ -120,6 +121,16 @@ html {
120
121
  transform: translateY(0.1em);
121
122
  }
122
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
+
123
134
  /* Transform the position back for Rough Notation (v-mark) */
124
135
  .rough-annotation {
125
136
  transform: scale(calc(1 / var(--slidev-slide-scale)));
@@ -129,7 +140,7 @@ html {
129
140
  position: fixed;
130
141
  }
131
142
 
132
- #twoslash-container .v-popper__wrapper {
143
+ #twoslash-container .v-popper__wrapper:not(.no-slide-scale > *) {
133
144
  transform: scale(calc(1 * var(--slidev-slide-scale)));
134
145
  transform-origin: 30px top;
135
146
  }
package/uno.config.ts CHANGED
@@ -13,6 +13,9 @@ export default defineConfig({
13
13
  safelist: [
14
14
  '!opacity-0',
15
15
  'prose',
16
+ // See https://github.com/slidevjs/slidev/issues/1705
17
+ 'grid-rows-[1fr_max-content]',
18
+ 'grid-cols-[1fr_max-content]',
16
19
  ],
17
20
  shortcuts: {
18
21
  'bg-main': 'bg-white dark:bg-[#121212]',
@@ -27,6 +30,18 @@ export default defineConfig({
27
30
  'abs-b': 'absolute bottom-0 left-0 right-0',
28
31
  'abs-bl': 'absolute bottom-0 left-0',
29
32
  'abs-br': 'absolute bottom-0 right-0',
33
+
34
+ 'z-drawing': 'z-10',
35
+ 'z-camera': 'z-15',
36
+ 'z-dragging': 'z-18',
37
+ 'z-menu': 'z-20',
38
+ 'z-label': 'z-40',
39
+ 'z-nav': 'z-50',
40
+ 'z-context-menu': 'z-60',
41
+ 'z-modal': 'z-70',
42
+ 'z-focus-indicator': 'z-200',
43
+
44
+ 'slidev-glass-effect': 'shadow-xl backdrop-blur-8 border border-main bg-main bg-opacity-75!',
30
45
  },
31
46
  // Slidev Specific Variants, probably extrat to a preset later
32
47
  variants: [
@@ -1,16 +0,0 @@
1
- <script setup lang="ts">
2
- import type { Slots } from 'vue'
3
- import { h } from 'vue'
4
- import { slideHeight, slideWidth } from '../env'
5
-
6
- function vStyle<Props>(props: Props, { slots }: { slots: Slots }) {
7
- if (slots.default)
8
- return h('style', slots.default())
9
- }
10
- </script>
11
-
12
- <template>
13
- <vStyle>
14
- @page { size: {{ slideWidth }}px {{ slideHeight }}px; margin: 0px; }
15
- </vStyle>
16
- </template>