Skip to content

Commit e70539c

Browse files
committed
feat: support tweaking sync up/down, related #1987
1 parent 8bf724d commit e70539c

File tree

14 files changed

+229
-116
lines changed

14 files changed

+229
-116
lines changed

packages/client/internals/DevicesList.vue renamed to packages/client/internals/DevicesSelectors.vue

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,21 @@ ensureDevicesListPermissions()
4343
</script>
4444

4545
<template>
46-
<div class="text-sm">
47-
<SelectList v-model="currentCamera" title="Camera" :items="camerasItems" />
48-
<SelectList v-model="currentMic" title="Microphone" :items="microphonesItems" />
46+
<div text-sm flex="~ col gap-2">
47+
<SelectList
48+
v-model="currentCamera"
49+
title="Camera"
50+
:items="camerasItems"
51+
/>
52+
<SelectList
53+
v-model="currentMic"
54+
title="Microphone"
55+
:items="microphonesItems"
56+
/>
4957
<SelectList
5058
v-if="mimeTypeItems.length"
5159
v-model="mimeType"
52-
title="mimeType"
60+
title="Video Format"
5361
:items="mimeTypeItems"
5462
/>
5563
</div>

packages/client/internals/MenuButton.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ onClickOutside(el, () => {
3030
<KeepAlive>
3131
<div
3232
v-if="value"
33-
class="rounded-md bg-main text-main shadow-xl absolute bottom-10 left-0 z-menu"
34-
dark:border="~ main"
33+
class="bg-main text-main shadow-xl absolute bottom-10 left-0 z-menu"
34+
border="~ main rounded-md"
3535
>
3636
<slot name="menu" />
3737
</div>

packages/client/internals/NavControls.vue

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { downloadPDF } from '../utils'
1010
import IconButton from './IconButton.vue'
1111
import MenuButton from './MenuButton.vue'
1212
import Settings from './Settings.vue'
13+
import SyncControls from './SyncControls.vue'
1314
1415
import VerticalDivider from './VerticalDivider.vue'
1516
@@ -48,7 +49,7 @@ function onMouseLeave() {
4849
4950
const barStyle = computed(() => props.persist
5051
? 'text-$slidev-controls-foreground bg-transparent'
51-
: 'rounded-md bg-main shadow-xl dark:border dark:border-main')
52+
: 'rounded-md bg-main shadow-xl border border-main')
5253
5354
const RecordingControls = shallowRef<any>()
5455
if (__SLIDEV_FEATURE_RECORD__)
@@ -130,19 +131,15 @@ if (__SLIDEV_FEATURE_RECORD__)
130131
>
131132
<div class="i-carbon:text-annotation-toggle" />
132133
</IconButton>
133-
134-
<IconButton v-if="isPresenter" title="Toggle Presenter Layout" class="aspect-ratio-initial flex items-center" @click="togglePresenterLayout">
135-
<div class="i-carbon:template" />
136-
{{ presenterLayout }}
137-
</IconButton>
138134
</template>
135+
139136
<template v-if="!__DEV__">
140137
<IconButton v-if="configs.download" title="Download as PDF" @click="downloadPDF">
141138
<div class="i-carbon:download" />
142139
</IconButton>
143140
</template>
144141

145-
<template v-if="__SLIDEV_FEATURE_BROWSER_EXPORTER__">
142+
<template v-if="__SLIDEV_FEATURE_BROWSER_EXPORTER__ && !isEmbedded && !isPresenter">
146143
<IconButton title="Browser Exporter" to="/export">
147144
<div class="i-carbon:document-pdf" />
148145
</IconButton>
@@ -156,7 +153,16 @@ if (__SLIDEV_FEATURE_RECORD__)
156153
<div class="i-carbon:information" />
157154
</IconButton>
158155

159-
<template v-if="!isPresenter && !isEmbedded">
156+
<template v-if="!isEmbedded">
157+
<VerticalDivider />
158+
159+
<IconButton v-if="isPresenter" title="Toggle Presenter Layout" class="aspect-ratio-initial flex items-center" @click="togglePresenterLayout">
160+
<div class="i-carbon:template" />
161+
{{ presenterLayout }}
162+
</IconButton>
163+
164+
<SyncControls v-if="__SLIDEV_FEATURE_PRESENTER__" />
165+
160166
<MenuButton>
161167
<template #button>
162168
<IconButton title="More Options">

packages/client/internals/RecordingControls.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useLocalStorage } from '@vueuse/core'
33
import { onMounted, watch } from 'vue'
44
import { recorder } from '../logic/recording'
55
import { currentCamera, showRecordingDialog } from '../state'
6-
import DevicesList from './DevicesList.vue'
6+
import DevicesSelectors from './DevicesSelectors.vue'
77
import IconButton from './IconButton.vue'
88
import MenuButton from './MenuButton.vue'
99
@@ -59,7 +59,7 @@ onMounted(() => {
5959
</IconButton>
6060
</template>
6161
<template #menu>
62-
<DevicesList />
62+
<DevicesSelectors />
6363
</template>
6464
</MenuButton>
6565
</template>

packages/client/internals/RecordingDialog.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { useVModel } from '@vueuse/core'
33
import { nextTick } from 'vue'
44
import { getFilename, mimeType, recordCamera, recorder, recordingName } from '../logic/recording'
5-
import DevicesList from './DevicesList.vue'
5+
import DevicesSelectors from './DevicesSelectors.vue'
66
import Modal from './Modal.vue'
77
88
const props = defineProps({
@@ -72,7 +72,7 @@ async function start() {
7272
</div>
7373
</div>
7474
</div>
75-
<DevicesList />
75+
<DevicesSelectors />
7676
</div>
7777
<div class="flex my-1">
7878
<button class="cancel" @click="close">

packages/client/internals/SelectList.vue

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,6 @@ const value = useVModel(props, 'modelValue', emit, { passive: true })
4444
</template>
4545

4646
<style lang="postcss" scoped>
47-
.select-list {
48-
@apply my-2;
49-
}
50-
5147
.item {
5248
@apply flex rounded whitespace-nowrap py-1 gap-1 px-2 cursor-default hover:bg-gray-400 hover:bg-opacity-10;
5349
@@ -57,6 +53,6 @@ const value = useVModel(props, 'modelValue', emit, { passive: true })
5753
}
5854
5955
.title {
60-
@apply text-xs uppercase opacity-50 tracking-widest px-7 py-1 select-none text-nowrap;
56+
@apply text-sm op75 px4 pt2 pb1 select-none text-nowrap font-bold border-t border-main;
6157
}
6258
</style>

packages/client/internals/Settings.vue

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
<script setup lang="ts">
22
import type { SelectionItem } from './types'
33
import { useWakeLock } from '@vueuse/core'
4+
import { useNav } from '../composables/useNav'
45
import { slideScale, wakeLockEnabled } from '../state'
56
import SelectList from './SelectList.vue'
67
8+
const { isPresenter } = useNav()
9+
710
const scaleItems: SelectionItem<number>[] = [
811
{
912
display: 'Fit',
@@ -30,11 +33,18 @@ const wakeLockItems: SelectionItem<boolean>[] = [
3033
</script>
3134

3235
<template>
33-
<div class="text-sm select-none mb-2">
34-
<SelectList v-model="slideScale" title="Scale" :items="scaleItems" />
36+
<div text-sm select-none flex="~ col gap-2" min-w-30>
37+
<SelectList
38+
v-if="!isPresenter"
39+
v-model="slideScale"
40+
title="Scale"
41+
:items="scaleItems"
42+
/>
3543
<SelectList
3644
v-if="__SLIDEV_FEATURE_WAKE_LOCK__ && isSupported"
37-
v-model="wakeLockEnabled" title="Wake lock" :items="wakeLockItems"
45+
v-model="wakeLockEnabled"
46+
title="Wake lock"
47+
:items="wakeLockItems"
3848
/>
3949
</div>
4050
</template>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue'
3+
import { useNav } from '../composables/useNav'
4+
import { syncDirections } from '../state'
5+
import IconButton from './IconButton.vue'
6+
import MenuButton from './MenuButton.vue'
7+
import SelectList from './SelectList.vue'
8+
9+
const { isPresenter } = useNav()
10+
11+
const shouldReceive = computed({
12+
get: () => isPresenter.value
13+
? syncDirections.value.presenterReceive
14+
: syncDirections.value.viewerReceive,
15+
set(v) {
16+
if (isPresenter.value) {
17+
syncDirections.value.presenterReceive = v
18+
}
19+
else {
20+
syncDirections.value.viewerReceive = v
21+
}
22+
},
23+
})
24+
25+
const shouldSend = computed({
26+
get: () => isPresenter.value
27+
? syncDirections.value.presenterSend
28+
: syncDirections.value.viewerSend,
29+
set(v) {
30+
if (isPresenter.value) {
31+
syncDirections.value.presenterSend = v
32+
}
33+
else {
34+
syncDirections.value.viewerSend = v
35+
}
36+
},
37+
})
38+
</script>
39+
40+
<template>
41+
<MenuButton>
42+
<template #button>
43+
<IconButton title="Change sync settings">
44+
<div class="i-ph:arrow-up-bold mx--1.2 scale-x-80" :class="shouldSend ? 'text-green6 dark:text-green' : 'op30'" />
45+
<div class="i-ph:arrow-down-bold mx--1.2 scale-x-80" :class="shouldReceive ? 'text-green6 dark:text-green' : 'op30'" />
46+
</IconButton>
47+
</template>
48+
<template #menu>
49+
<div text-sm flex="~ col gap-2">
50+
<div px4 pt3 ws-nowrap>
51+
<span op75>Slides navigation syncing for </span>
52+
<span font-bold text-primary>{{ isPresenter.value ? 'presenter' : 'viewer' }}</span>
53+
</div>
54+
<SelectList
55+
v-model="shouldSend"
56+
title="Send Changes"
57+
:items="[
58+
{ value: true, display: 'On' },
59+
{ value: false, display: 'Off' },
60+
]"
61+
/>
62+
<SelectList
63+
v-model="shouldReceive"
64+
title="Receive Changes"
65+
:items="[
66+
{ value: true, display: 'On' },
67+
{ value: false, display: 'Off' },
68+
]"
69+
/>
70+
</div>
71+
</template>
72+
</MenuButton>
73+
</template>

packages/client/pages/notes.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const { isFullscreen, toggle: toggleFullscreen } = fullscreen
1919
2020
const scroller = ref<HTMLDivElement>()
2121
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)
2323
const currentRoute = computed(() => slides.value.find(i => i.no === pageNo.value))
2424
2525
watch(pageNo, () => {
@@ -36,8 +36,8 @@ function decreaseFontSize() {
3636
}
3737
3838
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
4141
return createClicksContextBase(ref(clicks), undefined, total)
4242
})
4343
</script>

packages/client/setup/root.ts

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { configs, slidesTitle } from '../env'
1111
import { skipTransition } from '../logic/hmr'
1212
import { getSlidePath } from '../logic/slides'
1313
import { makeId } from '../logic/utils'
14+
import { syncDirections } from '../state'
1415
import { initDrawingState } from '../state/drawings'
1516
import { initSharedState, onPatch, patch } from '../state/shared'
1617

@@ -58,30 +59,28 @@ export default function setupRoot() {
5859
initDrawingState(`${slidesTitle} - drawings`)
5960

6061
const id = `${location.origin}_${makeId()}`
62+
const syncType = computed(() => isPresenter.value ? 'presenter' : 'viewer')
6163

6264
// update shared state
6365
function updateSharedState() {
66+
const shouldSend = isPresenter.value
67+
? syncDirections.value.presenterSend
68+
: syncDirections.value.viewerSend
69+
70+
if (!shouldSend)
71+
return
6472
if (isNotesViewer.value || isPrintMode.value)
6573
return
66-
6774
// we allow Presenter mode, or Viewer mode from trusted origins to update the shared state
6875
if (!isPresenter.value && !TRUST_ORIGINS.includes(location.host.split(':')[0]))
6976
return
7077

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-
78+
patch('page', +currentSlideNo.value)
79+
patch('clicks', clicksContext.value.current)
80+
patch('clicksTotal', clicksContext.value.total)
8281
patch('lastUpdate', {
8382
id,
84-
type: isPresenter.value ? 'presenter' : 'viewer',
83+
type: syncType.value,
8584
time: new Date().getTime(),
8685
})
8786
}
@@ -90,17 +89,26 @@ export default function setupRoot() {
9089
watch(clicksContext, updateSharedState)
9190

9291
onPatch((state) => {
92+
const shouldReceive = isPresenter.value
93+
? syncDirections.value.presenterReceive
94+
: syncDirections.value.viewerReceive
95+
if (!shouldReceive)
96+
return
9397
if (!hasPrimarySlide.value || isPrintMode.value)
9498
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-
}
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+
// }
105113
})
106114
}

0 commit comments

Comments
 (0)