feature: vor und nachteile

This commit is contained in:
Hikyu
2026-05-31 13:54:31 +02:00
parent 552a947a1c
commit e3dad17446
23270 changed files with 3024794 additions and 200 deletions
+12
View File
@@ -0,0 +1,12 @@
import serverDrawingState from 'server-reactive:drawings?diff'
import { createSyncState } from './syncState'
export type DrawingsState = Record<number, string | undefined>
export const {
init: initDrawingState,
onPatch: onPatchDrawingState,
onUpdate: onDrawingUpdate,
patch: patchDrawingState,
state: drawingState,
} = createSyncState<DrawingsState>(serverDrawingState, serverDrawingState, __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
+56
View File
@@ -0,0 +1,56 @@
import type { DragElementState } from '../composables/useDragElements'
import { breakpointsTailwind, isClient, useActiveElement, useBreakpoints, useFullscreen, useLocalStorage, useMagicKeys, useToggle, useWindowSize } from '@vueuse/core'
import { computed, ref, shallowRef } from 'vue'
import { slideAspect } from '../env'
export const showRecordingDialog = ref(false)
export const showInfoDialog = ref(false)
export const showGotoDialog = ref(false)
export const showOverview = ref(false)
export const shortcutsEnabled = ref(true)
export const breakpoints = useBreakpoints({
xs: 460,
...breakpointsTailwind,
})
export const windowSize = useWindowSize()
export const magicKeys = useMagicKeys()
export const isScreenVertical = computed(() => windowSize.height.value - windowSize.width.value / slideAspect.value > 120)
export const fullscreen = useFullscreen(isClient ? document.body : null)
export const activeElement = useActiveElement()
export const isInputting = computed(() => ['INPUT', 'TEXTAREA'].includes(activeElement.value?.tagName || ''))
export const isOnFocus = computed(() => ['BUTTON', 'A'].includes(activeElement.value?.tagName || ''))
export const currentCamera = useLocalStorage<string>('slidev-camera', 'default', { listenToStorageChanges: false })
export const currentMic = useLocalStorage<string>('slidev-mic', 'default', { listenToStorageChanges: false })
export const slideScale = useLocalStorage<number>('slidev-scale', 0)
export const wakeLockEnabled = useLocalStorage('slidev-wake-lock', true)
export const skipExportPdfTip = useLocalStorage('slidev-skip-export-pdf-tip', false)
export const showPresenterCursor = useLocalStorage('slidev-presenter-cursor', true, { listenToStorageChanges: false })
export const showEditor = useLocalStorage('slidev-show-editor', false, { listenToStorageChanges: false })
export const isEditorVertical = useLocalStorage('slidev-editor-vertical', false, { listenToStorageChanges: false })
export const editorWidth = useLocalStorage('slidev-editor-width', isClient ? window.innerWidth * 0.4 : 318, { listenToStorageChanges: false })
export const editorHeight = useLocalStorage('slidev-editor-height', isClient ? window.innerHeight * 0.4 : 300, { listenToStorageChanges: false })
export const activeDragElement = shallowRef<DragElementState | null>(null)
export const presenterNotesFontSize = useLocalStorage('slidev-presenter-font-size', 1, { listenToStorageChanges: false })
export const presenterLayout = useLocalStorage('slidev-presenter-layout', 1, { listenToStorageChanges: false })
export function togglePresenterLayout() {
presenterLayout.value = presenterLayout.value + 1
if (presenterLayout.value > 3)
presenterLayout.value = 1
}
export function increasePresenterFontSize() {
presenterNotesFontSize.value = Math.min(2, presenterNotesFontSize.value + 0.1)
}
export function decreasePresenterFontSize() {
presenterNotesFontSize.value = Math.max(0.5, presenterNotesFontSize.value - 0.1)
}
export const toggleOverview = useToggle(showOverview)
+40
View File
@@ -0,0 +1,40 @@
import serverState from 'server-reactive:nav'
import { createSyncState } from './syncState'
export interface SharedState {
page: number
clicks: number
clicksTotal: number
cursor?: {
x: number
y: number
}
viewerPage: number
viewerClicks: number
viewerClicksTotal: number
lastUpdate?: {
id: string
type: 'presenter' | 'viewer'
time: number
}
}
const { init, onPatch, onUpdate, patch, state } = createSyncState<SharedState>(serverState, {
page: 1,
clicks: 0,
clicksTotal: 0,
viewerPage: 1,
viewerClicks: 0,
viewerClicksTotal: 0,
})
export {
init as initSharedState,
onPatch,
onUpdate as onSharedUpdate,
patch,
state as sharedState,
}
+13
View File
@@ -0,0 +1,13 @@
import serverSnapshotState from 'server-reactive:snapshots?diff'
import { createSyncState } from './syncState'
export type SnapshotState = Record<number, {
revision: string
image: string
}>
export const snapshotState = createSyncState<SnapshotState>(
serverSnapshotState,
serverSnapshotState,
true,
)
+122
View File
@@ -0,0 +1,122 @@
import { reactive, ref, toRaw, watch } from 'vue'
export type SyncWrite<State extends object> = (state: State, updating?: boolean) => void
export interface Sync {
enabled?: boolean
init: <State extends object>(channelKey: string, onUpdate: (data: Partial<State>) => void, state: State, persist?: boolean) => SyncWrite<State> | undefined
}
interface BuiltinSync extends Sync {
channels: BroadcastChannel[]
disable: () => void
listener?: (event: StorageEvent) => void
}
const builtinSync: BuiltinSync = {
channels: [],
enabled: true,
init<State extends object>(channelKey: string, onUpdate: (data: Partial<State>) => void, state: State, persist = false) {
let stateChannel: BroadcastChannel
if (!__SLIDEV_HAS_SERVER__ && !persist) {
stateChannel = new BroadcastChannel(channelKey)
stateChannel.addEventListener('message', (event: MessageEvent<Partial<State>>) => onUpdate(event.data))
this.channels.push(stateChannel)
}
else if (!__SLIDEV_HAS_SERVER__ && persist) {
this.listener = function (event: StorageEvent) {
if (event && event.key === channelKey && event.newValue)
onUpdate(JSON.parse(event.newValue) as Partial<State>)
}
window.addEventListener('storage', this.listener)
const serializedState = window.localStorage.getItem(channelKey)
if (serializedState)
onUpdate(JSON.parse(serializedState) as Partial<State>)
}
return (state: State, updating = false) => {
if (this.enabled) {
if (!persist && stateChannel && !updating)
stateChannel.postMessage(toRaw(state))
if (persist && !updating)
window.localStorage.setItem(channelKey, JSON.stringify(state))
}
}
},
disable() {
this.enabled = false
this.channels.forEach(channel => channel.close())
if (this.listener) {
window.removeEventListener('storage', this.listener)
}
},
}
const syncInterfaces: Sync[] = reactive([builtinSync])
const channels: Map<string, { onUpdate: (data: Partial<object>) => void, persist?: boolean, state: object }> = new Map()
const syncWrites = ref<Record<string, SyncWrite<object>[]>>({})
export function disableBuiltinSync() {
builtinSync.disable()
}
export function addSyncMethod(sync: Sync) {
syncInterfaces.push(sync)
for (const [channelKey, { onUpdate, persist, state }] of channels.entries()) {
const write = sync.init(channelKey, onUpdate, state, persist)
if (write) {
syncWrites.value[channelKey].push(write)
}
}
}
export function createSyncState<State extends object>(serverState: State, defaultState: State, persist = false) {
const onPatchCallbacks: ((state: State) => void)[] = []
let patching = false
let updating = false
let patchingTimeout: NodeJS.Timeout
let updatingTimeout: NodeJS.Timeout
const state = __SLIDEV_HAS_SERVER__
? reactive<State>(serverState) as State
: reactive<State>(defaultState) as State
function onPatch(fn: (state: State) => void) {
onPatchCallbacks.push(fn)
}
function patch<K extends keyof State>(key: K, value: State[K]) {
if (state[key] === value)
return
clearTimeout(patchingTimeout)
patching = true
state[key] = value
patchingTimeout = setTimeout(() => patching = false, 0)
}
function onUpdate(patch: Partial<State>) {
if (!patching) {
clearTimeout(updatingTimeout)
updating = true
Object.entries(patch).forEach(([key, value]) => {
state[key as keyof State] = value as State[keyof State]
})
updatingTimeout = setTimeout(() => updating = false, 0)
}
}
function init(channelKey: string) {
channels.set(channelKey, { onUpdate, persist, state })
syncWrites.value[channelKey] = syncInterfaces
.map(sync => sync.init<State>(channelKey, onUpdate, state, persist))
.filter((x): x is SyncWrite<object> => Boolean(x))
function onStateChanged() {
syncWrites.value[channelKey].forEach(write => write?.(toRaw(state), updating))
if (!patching)
onPatchCallbacks.forEach((fn: (state: State) => void) => fn(state))
}
watch(state, onStateChanged, { deep: true })
}
return { init, onPatch, onUpdate, patch, state }
}