Skip to content

Commit

Permalink
feat: support tweaking sync up/down, related #1987
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Dec 26, 2024
1 parent 8bf724d commit e70539c
Show file tree
Hide file tree
Showing 14 changed files with 229 additions and 116 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,21 @@ ensureDevicesListPermissions()
</script>

<template>
<div class="text-sm">
<SelectList v-model="currentCamera" title="Camera" :items="camerasItems" />
<SelectList v-model="currentMic" title="Microphone" :items="microphonesItems" />
<div text-sm flex="~ col gap-2">
<SelectList
v-model="currentCamera"
title="Camera"
:items="camerasItems"
/>
<SelectList
v-model="currentMic"
title="Microphone"
:items="microphonesItems"
/>
<SelectList
v-if="mimeTypeItems.length"
v-model="mimeType"
title="mimeType"
title="Video Format"
:items="mimeTypeItems"
/>
</div>
Expand Down
4 changes: 2 additions & 2 deletions packages/client/internals/MenuButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ onClickOutside(el, () => {
<KeepAlive>
<div
v-if="value"
class="rounded-md bg-main text-main shadow-xl absolute bottom-10 left-0 z-menu"
dark:border="~ main"
class="bg-main text-main shadow-xl absolute bottom-10 left-0 z-menu"
border="~ main rounded-md"
>
<slot name="menu" />
</div>
Expand Down
22 changes: 14 additions & 8 deletions packages/client/internals/NavControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { downloadPDF } from '../utils'
import IconButton from './IconButton.vue'
import MenuButton from './MenuButton.vue'
import Settings from './Settings.vue'
import SyncControls from './SyncControls.vue'
import VerticalDivider from './VerticalDivider.vue'
Expand Down Expand Up @@ -48,7 +49,7 @@ function onMouseLeave() {
const barStyle = computed(() => props.persist
? 'text-$slidev-controls-foreground bg-transparent'
: 'rounded-md bg-main shadow-xl dark:border dark:border-main')
: 'rounded-md bg-main shadow-xl border border-main')
const RecordingControls = shallowRef<any>()
if (__SLIDEV_FEATURE_RECORD__)
Expand Down Expand Up @@ -130,19 +131,15 @@ if (__SLIDEV_FEATURE_RECORD__)
>
<div class="i-carbon:text-annotation-toggle" />
</IconButton>

<IconButton v-if="isPresenter" title="Toggle Presenter Layout" class="aspect-ratio-initial flex items-center" @click="togglePresenterLayout">
<div class="i-carbon:template" />
{{ presenterLayout }}
</IconButton>
</template>

<template v-if="!__DEV__">
<IconButton v-if="configs.download" title="Download as PDF" @click="downloadPDF">
<div class="i-carbon:download" />
</IconButton>
</template>

<template v-if="__SLIDEV_FEATURE_BROWSER_EXPORTER__">
<template v-if="__SLIDEV_FEATURE_BROWSER_EXPORTER__ && !isEmbedded && !isPresenter">
<IconButton title="Browser Exporter" to="/export">
<div class="i-carbon:document-pdf" />
</IconButton>
Expand All @@ -156,7 +153,16 @@ if (__SLIDEV_FEATURE_RECORD__)
<div class="i-carbon:information" />
</IconButton>

<template v-if="!isPresenter && !isEmbedded">
<template v-if="!isEmbedded">
<VerticalDivider />

<IconButton v-if="isPresenter" title="Toggle Presenter Layout" class="aspect-ratio-initial flex items-center" @click="togglePresenterLayout">
<div class="i-carbon:template" />
{{ presenterLayout }}
</IconButton>

<SyncControls v-if="__SLIDEV_FEATURE_PRESENTER__" />

<MenuButton>
<template #button>
<IconButton title="More Options">
Expand Down
4 changes: 2 additions & 2 deletions packages/client/internals/RecordingControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useLocalStorage } from '@vueuse/core'
import { onMounted, watch } from 'vue'
import { recorder } from '../logic/recording'
import { currentCamera, showRecordingDialog } from '../state'
import DevicesList from './DevicesList.vue'
import DevicesSelectors from './DevicesSelectors.vue'
import IconButton from './IconButton.vue'
import MenuButton from './MenuButton.vue'
Expand Down Expand Up @@ -59,7 +59,7 @@ onMounted(() => {
</IconButton>
</template>
<template #menu>
<DevicesList />
<DevicesSelectors />
</template>
</MenuButton>
</template>
4 changes: 2 additions & 2 deletions packages/client/internals/RecordingDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { useVModel } from '@vueuse/core'
import { nextTick } from 'vue'
import { getFilename, mimeType, recordCamera, recorder, recordingName } from '../logic/recording'
import DevicesList from './DevicesList.vue'
import DevicesSelectors from './DevicesSelectors.vue'
import Modal from './Modal.vue'
const props = defineProps({
Expand Down Expand Up @@ -72,7 +72,7 @@ async function start() {
</div>
</div>
</div>
<DevicesList />
<DevicesSelectors />
</div>
<div class="flex my-1">
<button class="cancel" @click="close">
Expand Down
6 changes: 1 addition & 5 deletions packages/client/internals/SelectList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@ const value = useVModel(props, 'modelValue', emit, { passive: true })
</template>

<style lang="postcss" scoped>
.select-list {
@apply my-2;
}
.item {
@apply flex rounded whitespace-nowrap py-1 gap-1 px-2 cursor-default hover:bg-gray-400 hover:bg-opacity-10;
Expand All @@ -57,6 +53,6 @@ const value = useVModel(props, 'modelValue', emit, { passive: true })
}
.title {
@apply text-xs uppercase opacity-50 tracking-widest px-7 py-1 select-none text-nowrap;
@apply text-sm op75 px4 pt2 pb1 select-none text-nowrap font-bold border-t border-main;
}
</style>
16 changes: 13 additions & 3 deletions packages/client/internals/Settings.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<script setup lang="ts">
import type { SelectionItem } from './types'
import { useWakeLock } from '@vueuse/core'
import { useNav } from '../composables/useNav'
import { slideScale, wakeLockEnabled } from '../state'
import SelectList from './SelectList.vue'
const { isPresenter } = useNav()
const scaleItems: SelectionItem<number>[] = [
{
display: 'Fit',
Expand All @@ -30,11 +33,18 @@ const wakeLockItems: SelectionItem<boolean>[] = [
</script>

<template>
<div class="text-sm select-none mb-2">
<SelectList v-model="slideScale" title="Scale" :items="scaleItems" />
<div text-sm select-none flex="~ col gap-2" min-w-30>
<SelectList
v-if="!isPresenter"
v-model="slideScale"
title="Scale"
:items="scaleItems"
/>
<SelectList
v-if="__SLIDEV_FEATURE_WAKE_LOCK__ && isSupported"
v-model="wakeLockEnabled" title="Wake lock" :items="wakeLockItems"
v-model="wakeLockEnabled"
title="Wake lock"
:items="wakeLockItems"
/>
</div>
</template>
73 changes: 73 additions & 0 deletions packages/client/internals/SyncControls.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useNav } from '../composables/useNav'
import { syncDirections } from '../state'
import IconButton from './IconButton.vue'
import MenuButton from './MenuButton.vue'
import SelectList from './SelectList.vue'
const { isPresenter } = useNav()
const shouldReceive = computed({
get: () => isPresenter.value
? syncDirections.value.presenterReceive
: syncDirections.value.viewerReceive,
set(v) {
if (isPresenter.value) {
syncDirections.value.presenterReceive = v
}
else {
syncDirections.value.viewerReceive = v
}
},
})
const shouldSend = computed({
get: () => isPresenter.value
? syncDirections.value.presenterSend
: syncDirections.value.viewerSend,
set(v) {
if (isPresenter.value) {
syncDirections.value.presenterSend = v
}
else {
syncDirections.value.viewerSend = v
}
},
})
</script>

<template>
<MenuButton>
<template #button>
<IconButton title="Change sync settings">
<div class="i-ph:arrow-up-bold mx--1.2 scale-x-80" :class="shouldSend ? 'text-green6 dark:text-green' : 'op30'" />
<div class="i-ph:arrow-down-bold mx--1.2 scale-x-80" :class="shouldReceive ? 'text-green6 dark:text-green' : 'op30'" />
</IconButton>
</template>
<template #menu>
<div text-sm flex="~ col gap-2">
<div px4 pt3 ws-nowrap>
<span op75>Slides navigation syncing for </span>
<span font-bold text-primary>{{ isPresenter.value ? 'presenter' : 'viewer' }}</span>
</div>
<SelectList
v-model="shouldSend"
title="Send Changes"
:items="[
{ value: true, display: 'On' },
{ value: false, display: 'Off' },
]"
/>
<SelectList
v-model="shouldReceive"
title="Receive Changes"
:items="[
{ value: true, display: 'On' },
{ value: false, display: 'Off' },
]"
/>
</div>
</template>
</MenuButton>
</template>
6 changes: 3 additions & 3 deletions packages/client/pages/notes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const { isFullscreen, toggle: toggleFullscreen } = fullscreen
const scroller = ref<HTMLDivElement>()
const fontSize = useLocalStorage('slidev-notes-font-size', 18)
const pageNo = computed(() => sharedState.lastUpdate?.type === 'viewer' ? sharedState.viewerPage : sharedState.page)
const pageNo = computed(() => sharedState.page)
const currentRoute = computed(() => slides.value.find(i => i.no === pageNo.value))
watch(pageNo, () => {
Expand All @@ -36,8 +36,8 @@ function decreaseFontSize() {
}
const clicksContext = computed(() => {
const clicks = sharedState.lastUpdate?.type === 'viewer' ? sharedState.viewerClicks : sharedState.clicks
const total = sharedState.lastUpdate?.type === 'viewer' ? sharedState.viewerClicksTotal : sharedState.clicksTotal
const clicks = sharedState.clicks
const total = sharedState.clicksTotal
return createClicksContextBase(ref(clicks), undefined, total)
})
</script>
Expand Down
54 changes: 31 additions & 23 deletions packages/client/setup/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { configs, slidesTitle } from '../env'
import { skipTransition } from '../logic/hmr'
import { getSlidePath } from '../logic/slides'
import { makeId } from '../logic/utils'
import { syncDirections } from '../state'
import { initDrawingState } from '../state/drawings'
import { initSharedState, onPatch, patch } from '../state/shared'

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

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

// update shared state
function updateSharedState() {
const shouldSend = isPresenter.value
? syncDirections.value.presenterSend
: syncDirections.value.viewerSend

if (!shouldSend)
return
if (isNotesViewer.value || isPrintMode.value)
return

// we allow Presenter mode, or Viewer mode from trusted origins to update the shared state
if (!isPresenter.value && !TRUST_ORIGINS.includes(location.host.split(':')[0]))
return

if (isPresenter.value) {
patch('page', +currentSlideNo.value)
patch('clicks', clicksContext.value.current)
patch('clicksTotal', clicksContext.value.total)
}
else {
patch('viewerPage', +currentSlideNo.value)
patch('viewerClicks', clicksContext.value.current)
patch('viewerClicksTotal', clicksContext.value.total)
}

patch('page', +currentSlideNo.value)
patch('clicks', clicksContext.value.current)
patch('clicksTotal', clicksContext.value.total)
patch('lastUpdate', {
id,
type: isPresenter.value ? 'presenter' : 'viewer',
type: syncType.value,
time: new Date().getTime(),
})
}
Expand All @@ -90,17 +89,26 @@ export default function setupRoot() {
watch(clicksContext, updateSharedState)

onPatch((state) => {
const shouldReceive = isPresenter.value
? syncDirections.value.presenterReceive
: syncDirections.value.viewerReceive
if (!shouldReceive)
return
if (!hasPrimarySlide.value || isPrintMode.value)
return
if (state.lastUpdate?.type === 'presenter' && (+state.page !== +currentSlideNo.value || +clicksContext.value.current !== +state.clicks)) {
skipTransition.value = false
router.replace({
path: getSlidePath(state.page, isPresenter.value),
query: {
...router.currentRoute.value.query,
clicks: state.clicks || 0,
},
})
}
if (state.lastUpdate?.type === syncType.value)
return
if ((+state.page === +currentSlideNo.value && +clicksContext.value.current === +state.clicks))
return
// if (state.lastUpdate?.type === 'presenter') {
skipTransition.value = false
router.replace({
path: getSlidePath(state.page, isPresenter.value),
query: {
...router.currentRoute.value.query,
clicks: state.clicks || 0,
},
})
// }
})
}
6 changes: 5 additions & 1 deletion packages/client/state/drawings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ export const {
onUpdate: onDrawingUpdate,
patch: patchDrawingState,
state: drawingState,
} = createSyncState<DrawingsState>(serverDrawingState, serverDrawingState, __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
} = createSyncState<DrawingsState>(
serverDrawingState,
serverDrawingState,
__SLIDEV_FEATURE_DRAWINGS_PERSIST__,
)
Loading

0 comments on commit e70539c

Please sign in to comment.