| 1 | <script setup lang="ts"> |
| 2 | import { Dropdown } from 'floating-vue' |
| 3 | import { ref } from 'vue' |
| 4 | import { useDrawings } from '../composables/useDrawings' |
| 5 | import Draggable from './Draggable.vue' |
| 6 | import IconButton from './IconButton.vue' |
| 7 | import VerticalDivider from './VerticalDivider.vue' |
| 8 | |
| 9 | const { |
| 10 | brush, |
| 11 | canClear, |
| 12 | canRedo, |
| 13 | canUndo, |
| 14 | clear, |
| 15 | drauu, |
| 16 | drawingEnabled, |
| 17 | drawingMode, |
| 18 | drawingPinned, |
| 19 | brushColors, |
| 20 | } = useDrawings() |
| 21 | |
| 22 | const strokeWidthDropdownShown = ref(false) |
| 23 | |
| 24 | function showStrokeWidthDropdown(event: Event) { |
| 25 | event.preventDefault() |
| 26 | event.stopPropagation() |
| 27 | requestAnimationFrame(() => { |
| 28 | requestAnimationFrame(() => { |
| 29 | strokeWidthDropdownShown.value = true |
| 30 | }) |
| 31 | }) |
| 32 | } |
| 33 | |
| 34 | function showStrokeWidthDropdownForPointer(event: PointerEvent) { |
| 35 | if (event.pointerType !== 'mouse') |
| 36 | showStrokeWidthDropdown(event) |
| 37 | } |
| 38 | |
| 39 | function undo() { |
| 40 | drauu.undo() |
| 41 | } |
| 42 | function redo() { |
| 43 | drauu.redo() |
| 44 | } |
| 45 | |
| 46 | let lastDrawingMode: typeof drawingMode.value = 'stylus' |
| 47 | function setDrawingMode(mode: typeof drawingMode.value) { |
| 48 | drawingMode.value = mode |
| 49 | drawingEnabled.value = true |
| 50 | if (mode !== 'eraseLine') |
| 51 | lastDrawingMode = mode |
| 52 | } |
| 53 | function setBrushColor(color: string) { |
| 54 | brush.value.color = color |
| 55 | drawingEnabled.value = true |
| 56 | drawingMode.value = lastDrawingMode |
| 57 | } |
| 58 | </script> |
| 59 | |
| 60 | <template> |
| 61 | <Draggable |
| 62 | v-if="drawingEnabled || drawingPinned" |
| 63 | class="flex flex-wrap text-xl p-2 gap-1 rounded-md bg-main shadow transition-opacity duration-200 z-nav border border-main" |
| 64 | :class="!drawingEnabled && drawingPinned ? 'opacity-40 hover:opacity-90' : ''" |
| 65 | storage-key="slidev-drawing-pos" |
| 66 | :initial-x="10" |
| 67 | :initial-y="10" |
| 68 | > |
| 69 | <IconButton title="Draw with stylus" :class="{ shallow: drawingMode !== 'stylus' }" @click="setDrawingMode('stylus')"> |
| 70 | <div class="i-carbon:pen" /> |
| 71 | </IconButton> |
| 72 | <IconButton title="Draw a line" :class="{ shallow: drawingMode !== 'line' }" @click="setDrawingMode('line')"> |
| 73 | <svg width="1em" height="1em" class="-mt-0.5" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"> |
| 74 | <path d="M21.71 3.29a1 1 0 0 0-1.42 0l-18 18a1 1 0 0 0 0 1.42a1 1 0 0 0 1.42 0l18-18a1 1 0 0 0 0-1.42z" fill="currentColor" /> |
| 75 | </svg> |
| 76 | </IconButton> |
| 77 | <IconButton title="Draw an arrow" :class="{ shallow: drawingMode !== 'arrow' }" @click="setDrawingMode('arrow')"> |
| 78 | <div class="i-carbon:arrow-up-right" /> |
| 79 | </IconButton> |
| 80 | <IconButton title="Draw an ellipse" :class="{ shallow: drawingMode !== 'ellipse' }" @click="setDrawingMode('ellipse')"> |
| 81 | <div class="i-carbon:radio-button" /> |
| 82 | </IconButton> |
| 83 | <IconButton title="Draw a rectangle" :class="{ shallow: drawingMode !== 'rectangle' }" @click="setDrawingMode('rectangle')"> |
| 84 | <div class="i-carbon:checkbox" /> |
| 85 | </IconButton> |
| 86 | <IconButton title="Erase" :class="{ shallow: drawingMode !== 'eraseLine' }" @click="setDrawingMode('eraseLine')"> |
| 87 | <div class="i-carbon:erase" /> |
| 88 | </IconButton> |
| 89 | |
| 90 | <VerticalDivider /> |
| 91 | |
| 92 | <Dropdown v-model:shown="strokeWidthDropdownShown" :triggers="[]"> |
| 93 | <IconButton title="Adjust stroke width" :class="{ shallow: drawingMode === 'eraseLine' }" @click="showStrokeWidthDropdown" @pointerdown="showStrokeWidthDropdownForPointer" @pointerup="showStrokeWidthDropdownForPointer" @touchend="showStrokeWidthDropdown"> |
| 94 | <svg viewBox="0 0 32 32" width="1.2em" height="1.2em"> |
| 95 | <line x1="2" y1="15" x2="22" y2="4" stroke="currentColor" stroke-width="1" stroke-linecap="round" /> |
| 96 | <line x1="2" y1="24" x2="28" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" /> |
| 97 | <line x1="7" y1="31" x2="29" y2="19" stroke="currentColor" stroke-width="3" stroke-linecap="round" /> |
| 98 | </svg> |
| 99 | </IconButton> |
| 100 | <template #popper> |
| 101 | <div class="flex bg-main p-2"> |
| 102 | <div class="inline-block w-7 text-center"> |
| 103 | {{ brush.size }} |
| 104 | </div> |
| 105 | <div class="pt-.5"> |
| 106 | <input v-model="brush.size" type="range" min="1" max="15" @change="drawingMode = lastDrawingMode"> |
| 107 | </div> |
| 108 | </div> |
| 109 | </template> |
| 110 | </Dropdown> |
| 111 | <IconButton |
| 112 | v-for="color of brushColors" |
| 113 | :key="color" |
| 114 | title="Set brush color" |
| 115 | :class="brush.color === color && drawingMode !== 'eraseLine' ? 'active' : 'shallow'" |
| 116 | @click="setBrushColor(color)" |
| 117 | > |
| 118 | <div |
| 119 | class="w-6 h-6 transition-all transform border" |
| 120 | :class="brush.color !== color ? 'rounded-1/2 scale-85 border-white' : 'rounded-md border-gray-300/50'" |
| 121 | :style="drawingEnabled ? { background: color } : { borderColor: color }" |
| 122 | /> |
| 123 | </IconButton> |
| 124 | |
| 125 | <VerticalDivider /> |
| 126 | |
| 127 | <IconButton title="Undo" :class="{ disabled: !canUndo }" @click="undo()"> |
| 128 | <div class="i-carbon:undo" /> |
| 129 | </IconButton> |
| 130 | <IconButton title="Redo" :class="{ disabled: !canRedo }" @click="redo()"> |
| 131 | <div class="i-carbon:redo" /> |
| 132 | </IconButton> |
| 133 | <IconButton title="Delete" :class="{ disabled: !canClear }" @click="clear()"> |
| 134 | <div class="i-carbon:trash-can" /> |
| 135 | </IconButton> |
| 136 | |
| 137 | <VerticalDivider /> |
| 138 | <IconButton :title="drawingPinned ? 'Unpin drawing' : 'Pin drawing'" :class="{ shallow: !drawingPinned }" @click="drawingPinned = !drawingPinned"> |
| 139 | <div v-show="drawingPinned" class="i-carbon:pin-filled transform -rotate-45" /> |
| 140 | <div v-show="!drawingPinned" class="i-carbon:pin" /> |
| 141 | </IconButton> |
| 142 | <IconButton |
| 143 | v-if="drawingEnabled" |
| 144 | :title="drawingPinned ? 'Drawing pinned' : 'Drawing unpinned'" |
| 145 | :class="{ shallow: !drawingEnabled }" |
| 146 | @click="drawingEnabled = !drawingEnabled" |
| 147 | > |
| 148 | <div v-show="drawingPinned" class="i-carbon:error" /> |
| 149 | <div v-show="!drawingPinned" class="i-carbon:close-outline" /> |
| 150 | </IconButton> |
| 151 | </Draggable> |
| 152 | </template> |
| 153 | |
| 154 | <style> |
| 155 | .v-popper--theme-dropdown .v-popper__arrow-inner { |
| 156 | --uno: border-main; |
| 157 | } |
| 158 | </style> |
| 159 |