| 1 | import type { DragElementState } from '../composables/useDragElements' |
| 2 | import { breakpointsTailwind, isClient, useActiveElement, useBreakpoints, useFullscreen, useLocalStorage, useMagicKeys, useToggle, useWindowSize } from '@vueuse/core' |
| 3 | import { computed, reactive, 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 | /** |
| 12 | * Skip slides transition when triggered by HMR. |
| 13 | * Will reset automatically after user navigations |
| 14 | */ |
| 15 | export const hmrSkipTransition = ref(false) |
| 16 | export const disableTransition = ref(false) |
| 17 | |
| 18 | export const shortcutsEnabled = ref(true) |
| 19 | |
| 20 | // Use a locking mechanism to support multiple simultaneous locks |
| 21 | // and avoid race conditions. Race conditions may occur, for example, |
| 22 | // when locking shortcuts on editor focus and moving from one editor |
| 23 | // to another, as blur events can be triggered after focus. |
| 24 | const shortcutsLocks = reactive(new Set<symbol>()) |
| 25 | |
| 26 | export const shortcutsLocked = computed(() => shortcutsLocks.size > 0) |
| 27 | |
| 28 | export function lockShortcuts() { |
| 29 | const lock = Symbol('shortcuts lock') |
| 30 | shortcutsLocks.add(lock) |
| 31 | return () => { |
| 32 | shortcutsLocks.delete(lock) |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | export const breakpoints = useBreakpoints({ |
| 37 | xs: 460, |
| 38 | ...breakpointsTailwind, |
| 39 | }) |
| 40 | export const windowSize = useWindowSize() |
| 41 | export const magicKeys = useMagicKeys() |
| 42 | export const isScreenVertical = computed(() => windowSize.height.value - windowSize.width.value / slideAspect.value > 120) |
| 43 | export const fullscreen = useFullscreen(isClient ? document.body : null) |
| 44 | |
| 45 | export const activeElement = useActiveElement() |
| 46 | export const isInputting = computed(() => ['INPUT', 'TEXTAREA'].includes(activeElement.value?.tagName || '')) |
| 47 | export const isOnFocus = computed(() => ['BUTTON', 'A'].includes(activeElement.value?.tagName || '')) |
| 48 | |
| 49 | export const currentCamera = useLocalStorage<string>('slidev-camera', 'default', { listenToStorageChanges: false }) |
| 50 | export const currentMic = useLocalStorage<string>('slidev-mic', 'default', { listenToStorageChanges: false }) |
| 51 | export const slideScale = useLocalStorage<number>('slidev-scale', 0) |
| 52 | export const wakeLockEnabled = useLocalStorage('slidev-wake-lock', true) |
| 53 | export const hideCursorIdle = useLocalStorage('slidev-hide-cursor-idle', true) |
| 54 | export const skipExportPdfTip = useLocalStorage('slidev-skip-export-pdf-tip', false) |
| 55 | export const captureDelay = useLocalStorage('slidev-export-capture-delay', 400, { listenToStorageChanges: false }) |
| 56 | |
| 57 | export const showPresenterCursor = useLocalStorage('slidev-presenter-cursor', true, { listenToStorageChanges: false }) |
| 58 | |
| 59 | export const cursorStyle = useLocalStorage<'cursor' | 'laser'>('slidev-cursor-style', 'cursor', { listenToStorageChanges: false }) |
| 60 | |
| 61 | export function togglePresenterCursor() { |
| 62 | showPresenterCursor.value = !showPresenterCursor.value |
| 63 | } |
| 64 | |
| 65 | export const showEditor = useLocalStorage('slidev-show-editor', false, { listenToStorageChanges: false }) |
| 66 | export const isEditorVertical = useLocalStorage('slidev-editor-vertical', false, { listenToStorageChanges: false }) |
| 67 | export const editorWidth = useLocalStorage('slidev-editor-width', isClient ? window.innerWidth * 0.4 : 318, { listenToStorageChanges: false }) |
| 68 | export const editorHeight = useLocalStorage('slidev-editor-height', isClient ? window.innerHeight * 0.4 : 300, { listenToStorageChanges: false }) |
| 69 | |
| 70 | export const activeDragElement = shallowRef<DragElementState | null>(null) |
| 71 | |
| 72 | export const presenterNotesFontSize = useLocalStorage('slidev-presenter-font-size', 1, { listenToStorageChanges: false }) |
| 73 | export const presenterLayout = useLocalStorage('slidev-presenter-layout', 1, { listenToStorageChanges: false }) |
| 74 | |
| 75 | export const viewerCssFilterDefaults = { |
| 76 | invert: false, |
| 77 | contrast: 1, |
| 78 | brightness: 1, |
| 79 | hueRotate: 0, |
| 80 | saturate: 1, |
| 81 | sepia: 0, |
| 82 | } |
| 83 | export const viewerCssFilter = useLocalStorage( |
| 84 | 'slidev-viewer-css-filter', |
| 85 | viewerCssFilterDefaults, |
| 86 | { listenToStorageChanges: false, mergeDefaults: true, deep: true }, |
| 87 | ) |
| 88 | export const hasViewerCssFilter = computed(() => { |
| 89 | return (Object.keys(viewerCssFilterDefaults) as (keyof typeof viewerCssFilterDefaults)[]) |
| 90 | .some(k => viewerCssFilter.value[k] !== viewerCssFilterDefaults[k]) |
| 91 | }) |
| 92 | |
| 93 | export function togglePresenterLayout() { |
| 94 | presenterLayout.value = presenterLayout.value + 1 |
| 95 | if (presenterLayout.value > 3) |
| 96 | presenterLayout.value = 1 |
| 97 | } |
| 98 | |
| 99 | export function increasePresenterFontSize() { |
| 100 | presenterNotesFontSize.value = Math.min(2, presenterNotesFontSize.value + 0.1) |
| 101 | } |
| 102 | |
| 103 | export function decreasePresenterFontSize() { |
| 104 | presenterNotesFontSize.value = Math.max(0.5, presenterNotesFontSize.value - 0.1) |
| 105 | } |
| 106 | |
| 107 | export const toggleOverview = useToggle(showOverview) |
| 108 | |
| 109 | export const syncDirections = useLocalStorage( |
| 110 | 'slidev-sync-directions', |
| 111 | { |
| 112 | viewerSend: true, |
| 113 | viewerReceive: true, |
| 114 | presenterSend: true, |
| 115 | presenterReceive: true, |
| 116 | }, |
| 117 | { |
| 118 | listenToStorageChanges: false, |
| 119 | mergeDefaults: true, |
| 120 | }, |
| 121 | ) |
| 122 |