2026-05-31 13:54:31 +02:00

182 lines
4.6 KiB
TypeScript

import type { Brush, Options as DrauuOptions, DrawingMode } from 'drauu'
import { createSharedComposable, toReactive, useLocalStorage } from '@vueuse/core'
import { createDrauu } from 'drauu'
import { computed, markRaw, nextTick, reactive, ref, watch } from 'vue'
import { configs } from '../env'
import { isInputting } from '../state'
import { drawingState, onPatchDrawingState, patchDrawingState } from '../state/drawings'
import { useNav } from './useNav'
export const useDrawings = createSharedComposable(() => {
const { currentSlideNo, isPresenter } = useNav()
const brushColors = [
'#ff595e',
'#ffca3a',
'#8ac926',
'#1982c4',
'#6a4c93',
'#ffffff',
'#000000',
]
const drawingEnabled = useLocalStorage('slidev-drawing-enabled', false)
const drawingPinned = useLocalStorage('slidev-drawing-pinned', false)
const brush = toReactive(useLocalStorage<Brush>('slidev-drawing-brush', {
color: brushColors[0],
size: 4,
mode: 'stylus',
}))
const isDrawing = ref(false)
const canUndo = ref(false)
const canRedo = ref(false)
const canClear = ref(false)
const _mode = ref<DrawingMode | 'arrow'>('stylus')
const syncUp = computed(() => configs.drawings.syncAll || isPresenter.value)
let disableDump = false
const drawingMode = computed({
get() {
return _mode.value
},
set(v: DrawingMode | 'arrow') {
_mode.value = v
if (v === 'arrow') {
// eslint-disable-next-line ts/no-use-before-define
drauu.mode = 'line'
brush.arrowEnd = true
}
else {
// eslint-disable-next-line ts/no-use-before-define
drauu.mode = v
brush.arrowEnd = false
}
},
})
const drauuOptions: DrauuOptions = reactive({
brush,
acceptsInputTypes: computed(() => (drawingEnabled.value && (!configs.drawings.presenterOnly || isPresenter.value)) ? undefined : ['pen' as const]),
coordinateTransform: false,
})
const drauu = markRaw(createDrauu(drauuOptions))
function clearDrauu() {
drauu.clear()
if (syncUp.value)
patchDrawingState(currentSlideNo.value, '')
}
function updateState() {
canRedo.value = drauu.canRedo()
canUndo.value = drauu.canUndo()
canClear.value = !!drauu.el?.children.length
}
function loadCanvas(page?: number) {
disableDump = true
const data = drawingState[page || currentSlideNo.value]
if (data != null)
drauu.load(data)
else
drauu.clear()
updateState()
disableDump = false
}
drauu.on('changed', () => {
updateState()
if (!disableDump) {
const dump = drauu.dump()
const key = currentSlideNo.value
if ((drawingState[key] || '') !== dump && syncUp.value)
patchDrawingState(key, drauu.dump())
}
})
onPatchDrawingState((state) => {
disableDump = true
if (state[currentSlideNo.value] != null)
drauu.load(state[currentSlideNo.value] || '')
disableDump = false
updateState()
})
nextTick(() => {
watch(currentSlideNo, () => {
if (!drauu.mounted)
return
loadCanvas()
}, { immediate: true })
})
drauu.on('start', () => isDrawing.value = true)
drauu.on('end', () => isDrawing.value = false)
window.addEventListener('keydown', (e) => {
if (!drawingEnabled.value || isInputting.value)
return
const noModifier = !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey
let handled = true
if (e.code === 'KeyZ' && (e.ctrlKey || e.metaKey)) {
if (e.shiftKey)
drauu.redo()
else
drauu.undo()
}
else if (e.code === 'Escape') {
drawingEnabled.value = false
}
else if (e.code === 'KeyL' && noModifier) {
drawingMode.value = 'line'
}
else if (e.code === 'KeyA' && noModifier) {
drawingMode.value = 'arrow'
}
else if (e.code === 'KeyS' && noModifier) {
drawingMode.value = 'stylus'
}
else if (e.code === 'KeyR' && noModifier) {
drawingMode.value = 'rectangle'
}
else if (e.code === 'KeyE' && noModifier) {
drawingMode.value = 'ellipse'
}
else if (e.code === 'KeyC' && noModifier) {
clearDrauu()
}
else if (e.code.startsWith('Digit') && noModifier && +e.code[5] <= brushColors.length) {
brush.color = brushColors[+e.code[5] - 1]
}
else {
handled = false
}
if (handled) {
e.preventDefault()
e.stopPropagation()
}
}, false)
return {
brush,
brushColors,
canClear,
canRedo,
canUndo,
clear: clearDrauu,
drauu,
drauuOptions,
drawingEnabled,
drawingMode,
drawingPinned,
drawingState,
isDrawing,
loadCanvas,
}
})