@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.
- package/composables/useClicks.ts +6 -5
- package/composables/useDragElements.ts +30 -24
- package/composables/useNav.ts +17 -15
- package/composables/usePrintStyles.ts +28 -0
- package/composables/useTimer.ts +1 -1
- package/constants.ts +1 -0
- package/internals/Badge.vue +48 -0
- package/internals/ClicksSlider.vue +1 -1
- package/internals/ContextMenu.vue +1 -1
- package/internals/{DevicesList.vue → DevicesSelectors.vue} +12 -4
- package/internals/DragControl.vue +1 -1
- package/internals/DrawingControls.vue +1 -1
- package/internals/ExportPdfTip.vue +90 -0
- package/internals/FormCheckbox.vue +16 -0
- package/internals/FormItem.vue +41 -0
- package/internals/IconButton.vue +7 -2
- package/internals/MenuButton.vue +2 -2
- package/internals/Modal.vue +1 -1
- package/internals/NavControls.vue +21 -9
- package/internals/PrintContainer.vue +2 -21
- package/internals/PrintSlide.vue +4 -3
- package/internals/PrintSlideClick.vue +11 -3
- package/internals/QuickOverview.vue +2 -2
- 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 -5
- package/internals/Settings.vue +16 -3
- package/internals/SideEditor.vue +1 -1
- package/internals/SlidesShow.vue +7 -3
- package/internals/SyncControls.vue +73 -0
- package/internals/WebCamera.vue +2 -2
- package/layouts/error.vue +5 -1
- package/logic/color.ts +62 -0
- package/logic/dark.ts +11 -0
- package/logic/screenshot.ts +61 -0
- package/logic/shortcuts.ts +36 -35
- package/logic/slides.ts +2 -1
- package/modules/v-mark.ts +6 -0
- package/package.json +20 -18
- package/pages/export.vue +365 -0
- package/pages/notes.vue +3 -3
- package/pages/overview.vue +1 -1
- package/pages/play.vue +1 -4
- package/pages/presenter.vue +46 -18
- package/pages/print.vue +0 -2
- package/setup/monaco.ts +14 -14
- package/setup/root.ts +37 -25
- package/setup/routes.ts +23 -12
- package/state/drawings.ts +5 -1
- package/state/index.ts +1 -55
- package/state/shared.ts +0 -7
- package/state/storage.ts +70 -0
- package/styles/index.css +15 -4
- package/uno.config.ts +15 -0
- package/internals/PrintStyle.vue +0 -16
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">
|
|
@@ -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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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:
|
|
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
|
-
|
|
92
|
+
const shouldReceive = isPresenter.value
|
|
93
|
+
? syncDirections.value.presenterReceive
|
|
94
|
+
: syncDirections.value.viewerReceive
|
|
95
|
+
if (!shouldReceive)
|
|
90
96
|
return
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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>(
|
|
12
|
+
} = createSyncState<DrawingsState>(
|
|
13
|
+
serverDrawingState,
|
|
14
|
+
serverDrawingState,
|
|
15
|
+
__SLIDEV_FEATURE_DRAWINGS_PERSIST__,
|
|
16
|
+
)
|
package/state/index.ts
CHANGED
|
@@ -1,55 +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
|
-
|
|
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 {
|
package/state/storage.ts
ADDED
|
@@ -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
|
-
|
|
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: [
|
package/internals/PrintStyle.vue
DELETED
|
@@ -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>
|