Fix drawing bugs and process touch events (#7412)

Signed-off-by: Nikolay Chunosov <Chunosov.N@gmail.com>
This commit is contained in:
Chunosov 2024-12-10 14:46:30 +07:00 committed by GitHub
parent a4a458b06c
commit f1dbf21146
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 424 additions and 171 deletions

View File

@ -16,7 +16,14 @@
import { Analytics } from '@hcengineering/analytics'
import { resizeObserver } from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import { drawing, type DrawingCmd, type DrawingData, type DrawingTool, type DrawTextCmd } from '../drawing'
import {
drawing,
makeCommandId,
type DrawingCmd,
type DrawingData,
type DrawingTool,
type DrawTextCmd
} from '../drawing'
import DrawingBoardToolbar from './DrawingBoardToolbar.svelte'
export let active = false
@ -38,7 +45,8 @@
let oldReadonly: boolean
let oldDrawings: DrawingData[]
let modified = false
let changingCmdIndex: number | undefined
let changingCmdId: string | undefined
let cmdEditor: HTMLDivElement | undefined
$: updateToolbarPosition(readonly, board, toolbar)
$: updateEditableState(drawings, readonly)
@ -63,14 +71,15 @@
commands = []
} else {
// Edit current content as a new drawing
commands = [...commands]
commands = commands.map((cmd) => ({ ...cmd, id: cmd.id ?? makeCommandId() }))
}
modified = false
}
} else {
commands = undefined
}
changingCmdIndex = undefined
changingCmdId = undefined
cmdEditor = undefined
oldDrawings = drawings
oldReadonly = readonly
}
@ -105,33 +114,40 @@
function addCommand (cmd: DrawingCmd): void {
if (commands !== undefined) {
commands = [...commands, cmd]
changingCmdIndex = undefined
changingCmdId = undefined
cmdEditor = undefined
modified = true
}
}
function showCommandProps (index: number): void {
changingCmdIndex = index
const anyCmd = commands?.[index]
if (anyCmd?.type === 'text') {
const cmd = anyCmd as DrawTextCmd
penColor = cmd.color
fontSize = cmd.fontSize
function showCommandProps (id: string): void {
changingCmdId = id
for (const cmd of commands ?? []) {
if (cmd.id === id) {
if (cmd.type === 'text') {
const textCmd = cmd as DrawTextCmd
penColor = textCmd.color
fontSize = textCmd.fontSize
}
break
}
}
}
function changeCommand (index: number, cmd: DrawingCmd): void {
function changeCommand (cmd: DrawingCmd): void {
if (commands !== undefined) {
commands = commands.map((c, i) => (i === index ? cmd : c))
changingCmdIndex = undefined
commands = commands.map((c) => (c.id === cmd.id ? cmd : c))
changingCmdId = undefined
cmdEditor = undefined
modified = true
}
}
function deleteCommand (index: number): void {
function deleteCommand (id: string): void {
if (commands !== undefined) {
commands = commands.filter((_, i) => i !== index)
changingCmdIndex = undefined
commands = commands.filter((c) => c.id !== id)
changingCmdId = undefined
cmdEditor = undefined
modified = true
}
}
@ -159,19 +175,23 @@
penWidth,
eraserWidth,
fontSize,
changingCmdIndex,
changingCmdId,
cmdAdded: addCommand,
cmdChanging: showCommandProps,
cmdChanged: changeCommand,
cmdUnchanged: () => {
changingCmdIndex = undefined
changingCmdId = undefined
},
cmdDeleted: deleteCommand
cmdDeleted: deleteCommand,
editorCreated: (editor) => {
cmdEditor = editor
}
}}
>
{#if !readonly}
<DrawingBoardToolbar
placeInside={toolbarInside}
{cmdEditor}
bind:toolbar
bind:tool
bind:penColor

View File

@ -53,6 +53,7 @@
export let placeInside = false
export let showPanTool = false
export let toolbar: HTMLDivElement | undefined
export let cmdEditor: HTMLDivElement | undefined
let colorSelector: HTMLInputElement
let penColors: string[] = defaultColors
@ -91,12 +92,14 @@
penColors = penColors.filter((c: string) => c !== penColor)
localStorage.setItem(storageKey.colors, JSON.stringify(penColors))
selectColor(penColors[0])
focusEditor()
break
}
case 'reset-colors': {
penColors = defaultColors
localStorage.removeItem(storageKey.colors)
selectColor(penColors[0])
focusEditor()
break
}
case undefined: {
@ -115,6 +118,7 @@
penColors = [...penColors, penColor]
localStorage.setItem(storageKey.colors, JSON.stringify(penColors))
}
focusEditor()
}
function selectColor (color: string): void {
@ -148,6 +152,15 @@
function updateFontSize (): void {
localStorage.setItem(storageKey.fontSize, fontSize.toString())
focusEditor()
}
function focusEditor (): void {
setTimeout(() => {
if (cmdEditor !== undefined) {
cmdEditor.focus()
}
}, 100)
}
</script>
@ -243,6 +256,7 @@
tool = 'pen'
}
selectColor(color)
focusEditor()
}}
>
<div slot="content" class="colorIcon" style:background={color} />

View File

@ -30,16 +30,18 @@ export interface DrawingProps {
eraserWidth?: number
fontSize?: number
defaultCursor?: string
changingCmdIndex?: number
changingCmdId?: string
cmdAdded?: (cmd: DrawingCmd) => void
cmdChanging?: (index: number) => void
cmdUnchanged?: (index: number) => void
cmdChanged?: (index: number, cmd: DrawingCmd) => void
cmdDeleted?: (index: number) => void
cmdChanging?: (id: string) => void
cmdUnchanged?: (id: string) => void
cmdChanged?: (cmd: DrawingCmd) => void
cmdDeleted?: (id: string) => void
editorCreated?: (editor: HTMLDivElement) => void
panned?: (offset: Point) => void
}
export interface DrawingCmd {
id: string
type: 'line' | 'text'
}
@ -71,6 +73,10 @@ function avgPoint (p1: Point, p2: Point): Point {
const maxTextLength = 500
export const makeCommandId = (): string => {
return crypto.randomUUID().toString()
}
const crossSvg = `<svg height="8" width="8" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="m1.29 2.71 5.3 5.29-5.3 5.29c-.92.92.49 2.34 1.41 1.41l5.3-5.29 5.29 5.3c.92.92 2.34-.49 1.41-1.41l-5.29-5.3 5.3-5.29c.92-.93-.49-2.34-1.42-1.42l-5.29 5.3-5.29-5.3c-.93-.92-2.34.49-1.42 1.42z"/>
</svg>`
@ -294,13 +300,15 @@ export function drawing (
draw.eraserWidth = props.eraserWidth ?? draw.eraserWidth
draw.fontSize = props.fontSize ?? draw.fontSize
draw.offset = props.offset ?? draw.offset
updateCanvasCursor()
updateCanvasTouchAction()
interface LiveTextBox {
pos: Point
box: HTMLDivElement
editor: HTMLDivElement
cmdIndex: number
cmdId: string
}
let liveTextBox: LiveTextBox | undefined
@ -328,6 +336,61 @@ export function drawing (
})
resizeObserver.observe(canvas)
let touchId: number | undefined
function findTouch (touches: TouchList, id: number | undefined = touchId): Touch | undefined {
for (let i = 0; i < touches.length; i++) {
const touch = touches[i]
if (touch.identifier === id) {
return touch
}
}
}
function touchToNodePoint (touch: Touch, node: HTMLElement): Point {
const rect = node.getBoundingClientRect()
return {
x: touch.clientX - rect.left,
y: touch.clientY - rect.top
}
}
function pointerToNodePoint (e: PointerEvent): Point {
return { x: e.offsetX, y: e.offsetY }
}
canvas.ontouchstart = (e) => {
if (readonly) {
return
}
const touch = e.changedTouches[0]
touchId = touch.identifier
drawStart(touchToNodePoint(touch, canvas))
}
canvas.ontouchmove = (e) => {
if (readonly) {
return
}
const touch = findTouch(e.changedTouches)
if (touch !== undefined) {
drawContinue(touchToNodePoint(touch, canvas))
}
}
canvas.ontouchend = (e) => {
if (readonly) {
return
}
const touch = findTouch(e.changedTouches)
if (touch !== undefined) {
drawEnd(touchToNodePoint(touch, canvas))
}
touchId = undefined
}
canvas.ontouchcancel = canvas.ontouchend
canvas.onpointerdown = (e) => {
if (readonly) {
return
@ -337,16 +400,7 @@ export function drawing (
}
e.preventDefault()
canvas.setPointerCapture(e.pointerId)
const x = e.offsetX
const y = e.offsetY
draw.on = true
draw.points = []
prevPos = { x, y }
if (draw.isDrawingTool()) {
draw.addPoint(x, y)
}
drawStart(pointerToNodePoint(e))
}
canvas.onpointermove = (e) => {
@ -354,35 +408,7 @@ export function drawing (
return
}
e.preventDefault()
const x = e.offsetX
const y = e.offsetY
if (draw.isDrawingTool()) {
const w = draw.cursorWidth()
canvasCursor.style.left = `${x - w / 2}px`
canvasCursor.style.top = `${y - w / 2}px`
if (draw.on) {
if (Math.hypot(prevPos.x - x, prevPos.y - y) < draw.minLineLength) {
return
}
draw.drawLive(x, y)
prevPos = { x, y }
}
}
if (draw.on && draw.tool === 'pan') {
requestAnimationFrame(() => {
draw.offset.x += x - prevPos.x
draw.offset.y += y - prevPos.y
replayCommands()
prevPos = { x, y }
})
}
if (draw.on && draw.tool === 'text') {
prevPos = { x, y }
}
drawContinue(pointerToNodePoint(e))
}
canvas.onpointerup = (e) => {
@ -391,24 +417,11 @@ export function drawing (
}
e.preventDefault()
canvas.releasePointerCapture(e.pointerId)
if (draw.on) {
if (draw.isDrawingTool()) {
draw.drawLive(e.offsetX, e.offsetY, true)
storeLineCommand()
} else if (draw.tool === 'pan') {
props.panned?.(draw.offset)
} else if (draw.tool === 'text') {
if (liveTextBox !== undefined) {
storeTextCommand()
} else {
const cmdIndex = findTextCommand(prevPos)
props.cmdChanging?.(cmdIndex)
}
}
draw.on = false
}
drawEnd(pointerToNodePoint(e))
}
canvas.onpointercancel = canvas.onpointerup
canvas.onpointerenter = () => {
if (!readonly && draw.isDrawingTool()) {
canvasCursor.style.visibility = 'visible'
@ -421,26 +434,86 @@ export function drawing (
}
}
function findTextCommand (mousePos: Point): number {
function drawStart (p: Point): void {
draw.on = true
draw.points = []
prevPos = p
if (draw.isDrawingTool()) {
draw.addPoint(p.x, p.y)
}
}
function drawContinue (p: Point): void {
if (draw.isDrawingTool()) {
const w = draw.cursorWidth()
canvasCursor.style.left = `${p.x - w / 2}px`
canvasCursor.style.top = `${p.y - w / 2}px`
if (draw.on) {
if (Math.hypot(prevPos.x - p.x, prevPos.y - p.y) < draw.minLineLength) {
return
}
draw.drawLive(p.x, p.y)
prevPos = p
}
}
if (draw.on && draw.tool === 'pan') {
requestAnimationFrame(() => {
draw.offset.x += p.x - prevPos.x
draw.offset.y += p.y - prevPos.y
replayCommands()
prevPos = p
})
}
if (draw.on && draw.tool === 'text') {
prevPos = p
}
}
function drawEnd (p: Point): void {
if (draw.on) {
if (draw.isDrawingTool()) {
draw.drawLive(p.x, p.y, true)
storeLineCommand()
} else if (draw.tool === 'pan') {
props.panned?.(draw.offset)
} else if (draw.tool === 'text') {
if (liveTextBox !== undefined) {
storeTextCommand()
closeLiveTextBox()
} else {
const cmd = findTextCommand(prevPos)
props.cmdChanging?.(cmd?.id ?? '')
}
}
draw.on = false
}
}
function findTextCommand (mousePos: Point): DrawTextCmd | undefined {
const pos = draw.mouseToCanvasPoint(mousePos)
for (let i = commands.length - 1; i >= 0; i--) {
const anyCmd = commands[i]
if (anyCmd.type === 'text') {
const cmd = anyCmd as DrawTextCmd
if (draw.isPointInText(pos, cmd)) {
return i
return cmd
}
}
}
return -1
return undefined
}
function makeLiveTextBox (cmdIndex: number): void {
function makeLiveTextBox (cmdId: string): void {
let pos = prevPos
let existingCmd: DrawTextCmd | undefined
if (cmdIndex >= 0 && commands[cmdIndex]?.type === 'text') {
existingCmd = commands[cmdIndex] as DrawTextCmd
pos = draw.canvasToMousePoint(existingCmd.pos)
for (const cmd of commands) {
if (cmd.id === cmdId && cmd.type === 'text') {
existingCmd = cmd as DrawTextCmd
pos = draw.canvasToMousePoint(existingCmd.pos)
break
}
}
const padding = 6
@ -455,6 +528,7 @@ export function drawing (
box.style.borderRadius = 'var(--small-BorderRadius)'
box.style.padding = `${padding}px`
box.style.background = 'var(--theme-popup-header)'
box.style.touchAction = 'none'
box.addEventListener('mousedown', (e) => {
e.stopPropagation()
})
@ -513,19 +587,18 @@ export function drawing (
if (e.key === 'Escape') {
e.preventDefault()
if (liveTextBox !== undefined) {
const cmdIndex = liveTextBox.cmdIndex
if (cmdIndex >= 0) {
// reset changingCmdIndex in clients
setTimeout(() => {
props.cmdUnchanged?.(cmdIndex)
}, 0)
}
const cmdId = liveTextBox.cmdId
// reset changingCmdId in clients
setTimeout(() => {
props.cmdUnchanged?.(cmdId)
}, 0)
}
closeLiveTextBox()
replayCommands()
} else if (e.key === 'Enter' && e.ctrlKey) {
e.preventDefault()
storeTextCommand()
closeLiveTextBox()
}
})
box.appendChild(editor)
@ -562,44 +635,43 @@ export function drawing (
return handle
}
const moveTextBox = (dx: number, dy: number): void => {
let newX = box.offsetLeft + dx
let newY = box.offsetTop + dy
// For screenshots the canvas always has the same size as the underlying image
// and we should not be able to drag the text box outside of the screenshot
if (props.autoSize !== true) {
newX = Math.max(0, newX)
newY = Math.max(0, newY)
if (newX + box.offsetWidth > node.clientWidth) {
newX = node.clientWidth - box.offsetWidth
}
if (newY + box.offsetHeight > node.clientHeight) {
newY = node.clientHeight - box.offsetHeight
}
}
box.style.left = `${newX}px`
box.style.top = `${newY}px`
if (liveTextBox !== undefined) {
liveTextBox.pos.x = newX + padding
liveTextBox.pos.y = newY + padding
}
}
const dragHandle = makeHandle()
dragHandle.style.left = `-${handleSize / 2}px`
dragHandle.style.cursor = 'grab'
dragHandle.style.touchAction = 'none'
dragHandle.addEventListener('pointerdown', (e) => {
e.preventDefault()
dragHandle.style.cursor = 'grabbing'
dragHandle.setPointerCapture(e.pointerId)
const x = e.clientX
const y = e.clientY
const dragStart = { x, y }
let prevPos = { x: e.clientX, y: e.clientY }
const pointerMove = (e: PointerEvent): void => {
e.preventDefault()
const x = e.clientX
const y = e.clientY
const dx = x - dragStart.x
const dy = y - dragStart.y
dragStart.x = x
dragStart.y = y
let newX = box.offsetLeft + dx
let newY = box.offsetTop + dy
// For screenshots the canvas always has the same size as the underlying image
// and we should not be able to drag the text box outside of the screenshot
if (props.autoSize !== true) {
newX = Math.max(0, newX)
newY = Math.max(0, newY)
if (newX + box.offsetWidth > node.clientWidth) {
newX = node.clientWidth - box.offsetWidth
}
if (newY + box.offsetHeight > node.clientHeight) {
newY = node.clientHeight - box.offsetHeight
}
}
box.style.left = `${newX}px`
box.style.top = `${newY}px`
if (liveTextBox !== undefined) {
liveTextBox.pos.x = newX + padding
liveTextBox.pos.y = newY + padding
}
const p = { x: e.clientX, y: e.clientY }
moveTextBox(p.x - prevPos.x, p.y - prevPos.y)
prevPos = p
}
const pointerUp = (e: PointerEvent): void => {
setTimeout(() => {
@ -610,9 +682,37 @@ export function drawing (
dragHandle.releasePointerCapture(e.pointerId)
dragHandle.removeEventListener('pointermove', pointerMove)
dragHandle.removeEventListener('pointerup', pointerUp)
dragHandle.removeEventListener('pointercancel', pointerUp)
}
dragHandle.addEventListener('pointermove', pointerMove)
dragHandle.addEventListener('pointerup', pointerUp)
dragHandle.addEventListener('pointercancel', pointerUp)
})
dragHandle.addEventListener('touchstart', (e) => {
dragHandle.style.cursor = 'grabbing'
const touch = e.changedTouches[0]
const touchId = touch.identifier
let prevPos = touchToNodePoint(touch, dragHandle)
const touchMove = (e: TouchEvent): void => {
const touch = findTouch(e.changedTouches, touchId)
if (touch !== undefined) {
const p = touchToNodePoint(touch, dragHandle)
moveTextBox(p.x - prevPos.x, p.y - prevPos.y)
prevPos = p
}
}
const touchEnd = (e: TouchEvent): void => {
setTimeout(() => {
editor.focus()
}, 100)
dragHandle.style.cursor = 'grab'
dragHandle.removeEventListener('touchmove', touchMove)
dragHandle.removeEventListener('touchend', touchEnd)
dragHandle.removeEventListener('touchcancel', touchEnd)
}
dragHandle.addEventListener('touchmove', touchMove)
dragHandle.addEventListener('touchend', touchEnd)
dragHandle.addEventListener('touchcancel', touchEnd)
})
box.appendChild(dragHandle)
@ -622,20 +722,21 @@ export function drawing (
deleteButton.innerHTML = crossSvg
deleteButton.addEventListener('click', () => {
node.removeChild(box)
if (liveTextBox?.cmdIndex !== undefined) {
props.cmdDeleted?.(liveTextBox.cmdIndex)
if (liveTextBox?.cmdId !== undefined) {
props.cmdDeleted?.(liveTextBox.cmdId)
}
liveTextBox = undefined
})
box.appendChild(deleteButton)
node.appendChild(box)
liveTextBox = { box, editor, pos, cmdIndex }
liveTextBox = { box, editor, pos, cmdId }
updateLiveTextBox()
setTimeout(() => {
editor.focus()
}, 100)
selectAll()
props.editorCreated?.(editor)
}
function updateLiveTextBox (): void {
@ -658,7 +759,9 @@ export function drawing (
if (liveTextBox !== undefined) {
const text = (liveTextBox.editor.innerText ?? '').trim()
if (text !== '') {
const cmdId = liveTextBox.cmdId
const cmd: DrawTextCmd = {
id: cmdId === '' ? makeCommandId() : cmdId,
type: 'text',
text,
pos: draw.mouseToCanvasPoint(liveTextBox.pos),
@ -666,10 +769,9 @@ export function drawing (
fontFace: draw.fontFace,
color: draw.penColor
}
const cmdIndex = liveTextBox.cmdIndex
const notify = (): void => {
if (cmdIndex >= 0) {
props.cmdChanged?.(cmdIndex, cmd)
if (cmdId !== '') {
props.cmdChanged?.(cmd)
} else {
props.cmdAdded?.(cmd)
}
@ -680,7 +782,7 @@ export function drawing (
notify()
}
} else {
props.cmdUnchanged?.(liveTextBox.cmdIndex)
props.cmdUnchanged?.(liveTextBox.cmdId)
}
}
}
@ -689,6 +791,7 @@ export function drawing (
if (draw.points.length > 0) {
const erasing = draw.tool === 'erase'
const cmd: DrawLineCmd = {
id: makeCommandId(),
type: 'line',
lineWidth: erasing ? draw.eraserWidth : draw.penWidth,
erasing,
@ -726,13 +829,17 @@ export function drawing (
}
}
function updateCanvasTouchAction (): void {
canvas.style.touchAction = readonly ? 'unset' : 'none'
}
function replayCommands (): void {
draw.ctx.reset()
for (let i = 0; i < commands.length; i++) {
if (liveTextBox?.cmdIndex === i) {
for (const cmd of commands) {
if (cmd.id !== undefined && liveTextBox?.cmdId === cmd.id) {
continue
}
draw.drawCommand(commands[i])
draw.drawCommand(cmd)
}
}
@ -774,9 +881,10 @@ export function drawing (
}
if (props.readonly !== readonly) {
readonly = props.readonly ?? false
updateCanvasTouchAction()
updateCursor = true
}
if (props.changingCmdIndex === undefined) {
if (props.changingCmdId === undefined) {
if (liveTextBox !== undefined) {
storeTextCommand(true)
closeLiveTextBox()
@ -784,9 +892,9 @@ export function drawing (
}
} else {
if (liveTextBox === undefined) {
makeLiveTextBox(props.changingCmdIndex)
makeLiveTextBox(props.changingCmdId)
replay = true
} else if (liveTextBox.cmdIndex !== props.changingCmdIndex) {
} else if (liveTextBox.cmdId !== props.changingCmdId) {
storeTextCommand(true)
closeLiveTextBox()
replay = true

View File

@ -13,7 +13,14 @@
// limitations under the License.
-->
<script lang="ts">
import { DrawingBoardToolbar, DrawingCmd, DrawingTool, DrawTextCmd, drawing } from '@hcengineering/presentation'
import {
DrawingBoardToolbar,
DrawingCmd,
DrawingTool,
DrawTextCmd,
drawing,
makeCommandId
} from '@hcengineering/presentation'
import { Loading } from '@hcengineering/ui'
import { onMount, onDestroy } from 'svelte'
import { Array as YArray, Map as YMap } from 'yjs'
@ -34,11 +41,14 @@
let fontSize: number
let commands: DrawingCmd[] = []
let offset: { x: number, y: number } = { x: 0, y: 0 }
let changingCmdIndex: number | undefined
let changingCmdId: string | undefined
let cmdEditor: HTMLDivElement | undefined
let toolbar: HTMLDivElement
let oldSelected = false
let oldReadonly = false
$: onSelectedChanged(selected)
$: onReadonlyChanged(readonly)
function listenSavedCommands (): void {
commands = savedCmds.toArray()
@ -50,25 +60,79 @@
// offset = savedProps.get('offset')
}
function showCommandProps (index: number): void {
changingCmdIndex = index
const anyCmd = commands[index]
if (anyCmd?.type === 'text') {
const cmd = anyCmd as DrawTextCmd
penColor = cmd.color
fontSize = cmd.fontSize
function showCommandProps (id: string): void {
changingCmdId = id
for (const cmd of commands) {
if (cmd.id === id) {
if (cmd.type === 'text') {
const textCmd = cmd as DrawTextCmd
penColor = textCmd.color
fontSize = textCmd.fontSize
}
break
}
}
}
function changeCommand (cmd: DrawingCmd): void {
let index = -1
for (let i = 0; i < savedCmds.length; i++) {
if (savedCmds.get(i).id === cmd.id) {
savedCmds.delete(i)
index = i
break
}
}
if (index >= 0) {
savedCmds.insert(index, [cmd])
} else {
savedCmds.push([cmd])
}
changingCmdId = undefined
cmdEditor = undefined
}
function deleteCommand (id: string): void {
for (let i = 0; i < savedCmds.length; i++) {
if (savedCmds.get(i).id === id) {
savedCmds.delete(i)
break
}
}
changingCmdId = undefined
cmdEditor = undefined
}
function onSelectedChanged (selected: boolean): void {
if (oldSelected !== selected) {
if (oldSelected && !selected && changingCmdIndex !== undefined) {
changingCmdIndex = undefined
if (oldSelected && !selected && changingCmdId !== undefined) {
changingCmdId = undefined
cmdEditor = undefined
}
oldSelected = selected
}
}
function onReadonlyChanged (readonly: boolean): void {
if (oldReadonly !== readonly) {
if (!readonly) {
let allHaveIds = true
for (let i = 0; i < savedCmds.length; i++) {
if (savedCmds.get(i).id === undefined) {
allHaveIds = false
break
}
}
if (!allHaveIds) {
const cmds = savedCmds.toArray()
savedCmds.delete(0, savedCmds.length)
savedCmds.push(cmds.map((cmd) => ({ ...cmd, id: cmd.id ?? makeCommandId() })))
}
}
oldReadonly = readonly
}
}
onMount(() => {
commands = savedCmds.toArray()
// offset = savedProps.get('offset')
@ -109,27 +173,23 @@
penWidth,
eraserWidth,
fontSize,
changingCmdIndex,
changingCmdId,
cmdAdded: (cmd) => {
savedCmds.push([cmd])
changingCmdIndex = undefined
changingCmdId = undefined
},
cmdChanging: showCommandProps,
cmdChanged: (index, cmd) => {
savedCmds.delete(index)
savedCmds.insert(index, [cmd])
changingCmdIndex = undefined
},
cmdChanged: changeCommand,
cmdUnchanged: () => {
changingCmdIndex = undefined
},
cmdDeleted: (index) => {
savedCmds.delete(index)
changingCmdIndex = undefined
changingCmdId = undefined
},
cmdDeleted: deleteCommand,
panned: (newOffset) => {
offset = newOffset
// savedProps.set('offset', offset)
},
editorCreated: (editor) => {
cmdEditor = editor
}
}}
>
@ -142,6 +202,7 @@
<DrawingBoardToolbar
placeInside={true}
showPanTool={true}
{cmdEditor}
bind:toolbar
bind:tool
bind:penColor

View File

@ -35,23 +35,43 @@
let resizer: HTMLElement
let startY: number
let resizedHeight: number | undefined
let resizerTouchId: number | undefined
let loading = true
let loadingTimer: any
function resizeStart (y: number): void {
const height = node.attrs.height ?? defaultHeight
startY = y - height
resizedHeight = height
}
function resizeContinue (y: number): void {
resizedHeight = Math.max(minHeight, y - startY)
resizedHeight = Math.min(maxHeight, resizedHeight)
}
function resizeFinish (): void {
if (resizedHeight !== undefined) {
if (typeof getPos === 'function') {
const tr = editor.state.tr.setNodeMarkup(getPos(), undefined, { ...node.attrs, height: resizedHeight })
editor.view.dispatch(tr)
}
resizedHeight = undefined
}
}
function onResizerPointerDown (e: PointerEvent): void {
e.preventDefault()
const height = node.attrs.height ?? defaultHeight
startY = e.clientY - height
resizedHeight = height
resizer.setPointerCapture(e.pointerId)
resizer.addEventListener('pointermove', onResizerPointerMove)
resizer.addEventListener('pointerup', onResizerPointerUp)
resizer.addEventListener('pointercancel', onResizerPointerUp)
resizeStart(e.clientY)
}
function onResizerPointerMove (e: PointerEvent): void {
e.preventDefault()
resizedHeight = Math.max(minHeight, e.clientY - startY)
resizedHeight = Math.min(maxHeight, resizedHeight)
resizeContinue(e.clientY)
}
function onResizerPointerUp (e: PointerEvent): void {
@ -59,11 +79,35 @@
resizer.releasePointerCapture(e.pointerId)
resizer.removeEventListener('pointermove', onResizerPointerMove)
resizer.removeEventListener('pointerup', onResizerPointerUp)
if (typeof getPos === 'function') {
const tr = editor.state.tr.setNodeMarkup(getPos(), undefined, { ...node.attrs, height: resizedHeight })
editor.view.dispatch(tr)
resizer.removeEventListener('pointercancel', onResizerPointerUp)
resizeFinish()
}
function onResizerTouchStart (e: TouchEvent): void {
const touch = e.changedTouches[0]
resizerTouchId = touch.identifier
resizer.addEventListener('touchmove', onResizerTouchMove)
resizer.addEventListener('touchend', onResizerTouchEnd)
resizer.addEventListener('touchcancel', onResizerTouchEnd)
resizeStart(touch.clientY)
}
function onResizerTouchMove (e: TouchEvent): void {
for (let i = 0; i < e.changedTouches.length; i++) {
const touch = e.changedTouches[i]
if (touch.identifier === resizerTouchId) {
resizeContinue(touch.clientY)
return
}
}
resizedHeight = undefined
}
function onResizerTouchEnd (): void {
resizer.removeEventListener('touchmove', onResizerTouchMove)
resizer.removeEventListener('touchend', onResizerTouchEnd)
resizer.removeEventListener('touchcancel', onResizerTouchEnd)
resizerTouchId = undefined
resizeFinish()
}
onMount(() => {
@ -110,7 +154,12 @@
/>
</div>
{#if selected}
<div class="handle resizer" bind:this={resizer} on:pointerdown={onResizerPointerDown}>
<div
class="handle resizer"
bind:this={resizer}
on:pointerdown={onResizerPointerDown}
on:touchstart={onResizerTouchStart}
>
<svg viewBox="0 0 60 4" height="4" width="60" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path
d="m60 2a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2 2 2 0 0 1 2 2zm-8 0a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2 2 2 0 0 1 2 2zm-8 0a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2 2 2 0 0 1 2 2zm-8 0a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2 2 2 0 0 1 2 2zm-8 0a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2 2 2 0 0 1 2 2zm-8 0a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2 2 2 0 0 1 2 2zm-8 0a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2 2 2 0 0 1 2 2zm-8 0a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2 2 2 0 0 1 2 2z"
@ -147,6 +196,7 @@
align-items: center;
justify-content: center;
opacity: 0.5;
touch-action: none;
&:hover {
opacity: 1;