[feature] add laser pointer (#1412)
This PR adds a laser pointer. It's also available in readonly rooms. ![Kapture 2023-05-18 at 17 00 18](https://github.com/tldraw/tldraw/assets/23072548/4f638dff-8c17-4f9d-8177-4a63a524b7fd) ### Change Type - [x] `minor` — New Feature ### Test Plan 1. Select the laser pointer tool 2. Draw some lasers. ### Release Notes - Adds the laser pointer tool.
This commit is contained in:
parent
9c28d8a6bd
commit
1eb1f89cd1
27 changed files with 357 additions and 35 deletions
|
@ -32,6 +32,7 @@
|
||||||
--color-text: #fff;
|
--color-text: #fff;
|
||||||
--color-background: #1d1d1d;
|
--color-background: #1d1d1d;
|
||||||
--color-accent: #f3c14b;
|
--color-accent: #f3c14b;
|
||||||
|
|
||||||
--color-tint-6: rgb(186, 186, 186);
|
--color-tint-6: rgb(186, 186, 186);
|
||||||
|
|
||||||
--shadow-small: 0px 0px 16px -2px rgba(0, 0, 0, 0.52), 0px 0px 4px 0px rgba(0, 0, 0, 0.62);
|
--shadow-small: 0px 0px 16px -2px rgba(0, 0, 0, 0.52), 0px 0px 4px 0px rgba(0, 0, 0, 0.62);
|
||||||
|
|
6
assets/icons/icon/tool-laser.svg
Normal file
6
assets/icons/icon/tool-laser.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3.52185 26.4772L7.55602 22.443" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3.52166 20.6267L5.88012 19.7974" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M10.2014 24.1187L9.37213 26.4772" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M27 7.36364L13.1112 21.2524C12.916 21.4477 12.5994 21.4477 12.4041 21.2524L8.7476 17.5959C8.55233 17.4006 8.55233 17.084 8.7476 16.8888L22.6364 3L27 7.36364Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 723 B |
|
@ -190,6 +190,7 @@
|
||||||
"tool.asset": "Asset",
|
"tool.asset": "Asset",
|
||||||
"tool.frame": "Frame",
|
"tool.frame": "Frame",
|
||||||
"tool.note": "Note",
|
"tool.note": "Note",
|
||||||
|
"tool.laser": "Laser",
|
||||||
"tool.embed": "Embed",
|
"tool.embed": "Embed",
|
||||||
"tool.text": "Text",
|
"tool.text": "Text",
|
||||||
"menu.title": "Menu",
|
"menu.title": "Menu",
|
||||||
|
|
1
packages/assets/imports.d.ts
vendored
1
packages/assets/imports.d.ts
vendored
|
@ -151,6 +151,7 @@ export function getAssetUrlsByImport(opts?: AssetUrlOptions): {
|
||||||
'tool-frame': string
|
'tool-frame': string
|
||||||
'tool-hand': string
|
'tool-hand': string
|
||||||
'tool-highlighter': string
|
'tool-highlighter': string
|
||||||
|
'tool-laser': string
|
||||||
'tool-line': string
|
'tool-line': string
|
||||||
'tool-media': string
|
'tool-media': string
|
||||||
'tool-note': string
|
'tool-note': string
|
||||||
|
|
|
@ -162,6 +162,7 @@ import iconsToolEraser from './icons/icon/tool-eraser.svg'
|
||||||
import iconsToolFrame from './icons/icon/tool-frame.svg'
|
import iconsToolFrame from './icons/icon/tool-frame.svg'
|
||||||
import iconsToolHand from './icons/icon/tool-hand.svg'
|
import iconsToolHand from './icons/icon/tool-hand.svg'
|
||||||
import iconsToolHighlighter from './icons/icon/tool-highlighter.svg'
|
import iconsToolHighlighter from './icons/icon/tool-highlighter.svg'
|
||||||
|
import iconsToolLaser from './icons/icon/tool-laser.svg'
|
||||||
import iconsToolLine from './icons/icon/tool-line.svg'
|
import iconsToolLine from './icons/icon/tool-line.svg'
|
||||||
import iconsToolMedia from './icons/icon/tool-media.svg'
|
import iconsToolMedia from './icons/icon/tool-media.svg'
|
||||||
import iconsToolNote from './icons/icon/tool-note.svg'
|
import iconsToolNote from './icons/icon/tool-note.svg'
|
||||||
|
@ -392,6 +393,7 @@ export function getAssetUrlsByImport(opts) {
|
||||||
'tool-frame': formatAssetUrl(iconsToolFrame, opts),
|
'tool-frame': formatAssetUrl(iconsToolFrame, opts),
|
||||||
'tool-hand': formatAssetUrl(iconsToolHand, opts),
|
'tool-hand': formatAssetUrl(iconsToolHand, opts),
|
||||||
'tool-highlighter': formatAssetUrl(iconsToolHighlighter, opts),
|
'tool-highlighter': formatAssetUrl(iconsToolHighlighter, opts),
|
||||||
|
'tool-laser': formatAssetUrl(iconsToolLaser, opts),
|
||||||
'tool-line': formatAssetUrl(iconsToolLine, opts),
|
'tool-line': formatAssetUrl(iconsToolLine, opts),
|
||||||
'tool-media': formatAssetUrl(iconsToolMedia, opts),
|
'tool-media': formatAssetUrl(iconsToolMedia, opts),
|
||||||
'tool-note': formatAssetUrl(iconsToolNote, opts),
|
'tool-note': formatAssetUrl(iconsToolNote, opts),
|
||||||
|
|
1
packages/assets/urls.d.ts
vendored
1
packages/assets/urls.d.ts
vendored
|
@ -151,6 +151,7 @@ export function getAssetUrlsByMetaUrl(opts?: AssetUrlOptions): {
|
||||||
'tool-frame': string
|
'tool-frame': string
|
||||||
'tool-hand': string
|
'tool-hand': string
|
||||||
'tool-highlighter': string
|
'tool-highlighter': string
|
||||||
|
'tool-laser': string
|
||||||
'tool-line': string
|
'tool-line': string
|
||||||
'tool-media': string
|
'tool-media': string
|
||||||
'tool-note': string
|
'tool-note': string
|
||||||
|
|
|
@ -498,6 +498,10 @@ export function getAssetUrlsByMetaUrl(opts) {
|
||||||
new URL('./icons/icon/tool-highlighter.svg', import.meta.url).href,
|
new URL('./icons/icon/tool-highlighter.svg', import.meta.url).href,
|
||||||
opts
|
opts
|
||||||
),
|
),
|
||||||
|
'tool-laser': formatAssetUrl(
|
||||||
|
new URL('./icons/icon/tool-laser.svg', import.meta.url).href,
|
||||||
|
opts
|
||||||
|
),
|
||||||
'tool-line': formatAssetUrl(
|
'tool-line': formatAssetUrl(
|
||||||
new URL('./icons/icon/tool-line.svg', import.meta.url).href,
|
new URL('./icons/icon/tool-line.svg', import.meta.url).href,
|
||||||
opts
|
opts
|
||||||
|
|
|
@ -73,6 +73,7 @@
|
||||||
--color-primary: #2f80ed;
|
--color-primary: #2f80ed;
|
||||||
--color-warn: #d10b0b;
|
--color-warn: #d10b0b;
|
||||||
--color-text: #000000;
|
--color-text: #000000;
|
||||||
|
--color-laser: #ff0000;
|
||||||
--palette-black: #1d1d1d;
|
--palette-black: #1d1d1d;
|
||||||
--palette-blue: #4263eb;
|
--palette-blue: #4263eb;
|
||||||
--palette-green: #099268;
|
--palette-green: #099268;
|
||||||
|
@ -154,6 +155,7 @@
|
||||||
--color-primary: #2f80ed;
|
--color-primary: #2f80ed;
|
||||||
--color-warn: #d10b0b;
|
--color-warn: #d10b0b;
|
||||||
--color-text: #f8f9fa;
|
--color-text: #f8f9fa;
|
||||||
|
--color-laser: #ff0000;
|
||||||
--palette-black: #e1e1e1;
|
--palette-black: #e1e1e1;
|
||||||
--palette-blue: #4156be;
|
--palette-blue: #4156be;
|
||||||
--palette-green: #3b7b5e;
|
--palette-green: #3b7b5e;
|
||||||
|
|
|
@ -9,6 +9,10 @@ export class ScribbleManager implements TLScribble {
|
||||||
size
|
size
|
||||||
color
|
color
|
||||||
opacity
|
opacity
|
||||||
|
delay
|
||||||
|
|
||||||
|
timeoutMs = 0
|
||||||
|
delayRemaining = 0
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
private onUpdate: (scribble: TLScribble) => void
|
private onUpdate: (scribble: TLScribble) => void
|
||||||
|
@ -24,13 +28,15 @@ export class ScribbleManager implements TLScribble {
|
||||||
size?: TLScribble['size']
|
size?: TLScribble['size']
|
||||||
color?: TLScribble['color']
|
color?: TLScribble['color']
|
||||||
opacity?: TLScribble['opacity']
|
opacity?: TLScribble['opacity']
|
||||||
|
delay?: TLScribble['delay']
|
||||||
}) {
|
}) {
|
||||||
const { size = 20, color = 'accent', opacity = 0.8, onComplete, onUpdate } = opts
|
const { size = 20, color = 'accent', opacity = 0.8, delay = 0, onComplete, onUpdate } = opts
|
||||||
|
|
||||||
this.onUpdate = onUpdate
|
this.onUpdate = onUpdate
|
||||||
this.onComplete = onComplete
|
this.onComplete = onComplete
|
||||||
this.size = size
|
this.size = size
|
||||||
this.color = color
|
this.color = color
|
||||||
|
this.delay = delay
|
||||||
this.opacity = opacity
|
this.opacity = opacity
|
||||||
this.points = [] as Vec2dModel[]
|
this.points = [] as Vec2dModel[]
|
||||||
this.state = 'starting' as TLScribble['state']
|
this.state = 'starting' as TLScribble['state']
|
||||||
|
@ -38,6 +44,8 @@ export class ScribbleManager implements TLScribble {
|
||||||
this.prev = null
|
this.prev = null
|
||||||
this.next = null
|
this.next = null
|
||||||
|
|
||||||
|
this.delayRemaining = this.delay
|
||||||
|
|
||||||
this.resume()
|
this.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +63,7 @@ export class ScribbleManager implements TLScribble {
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
stop = () => {
|
stop = () => {
|
||||||
|
this.delayRemaining = Math.min(this.delayRemaining, 200)
|
||||||
this.state = 'stopping'
|
this.state = 'stopping'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +91,7 @@ export class ScribbleManager implements TLScribble {
|
||||||
size: this.size,
|
size: this.size,
|
||||||
color: this.color,
|
color: this.color,
|
||||||
opacity: this.opacity,
|
opacity: this.opacity,
|
||||||
|
delay: this.delay,
|
||||||
points: [...this.points],
|
points: [...this.points],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,10 +100,13 @@ export class ScribbleManager implements TLScribble {
|
||||||
this.onUpdate(this.getScribble())
|
this.onUpdate(this.getScribble())
|
||||||
}
|
}
|
||||||
|
|
||||||
timeoutMs = 0
|
|
||||||
|
|
||||||
tick: TLTickEvent = (elapsed) => {
|
tick: TLTickEvent = (elapsed) => {
|
||||||
this.timeoutMs += elapsed
|
this.timeoutMs += elapsed
|
||||||
|
|
||||||
|
if (this.delayRemaining > 0) {
|
||||||
|
this.delayRemaining = Math.max(0, this.delayRemaining - elapsed)
|
||||||
|
}
|
||||||
|
|
||||||
if (this.timeoutMs >= 16) {
|
if (this.timeoutMs >= 16) {
|
||||||
this.timeoutMs = 0
|
this.timeoutMs = 0
|
||||||
}
|
}
|
||||||
|
@ -106,37 +119,45 @@ export class ScribbleManager implements TLScribble {
|
||||||
this.prev = next
|
this.prev = next
|
||||||
points.push(next)
|
points.push(next)
|
||||||
|
|
||||||
if (points.length > 8) {
|
if (this.delayRemaining === 0) {
|
||||||
points.shift()
|
if (points.length > 8) {
|
||||||
|
points.shift()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateScribble()
|
this.updateScribble()
|
||||||
} else {
|
} else {
|
||||||
// While not moving, shrink the scribble from the start
|
// While not moving, shrink the scribble from the start
|
||||||
if (timeoutMs === 0 && points.length > 1) {
|
if (timeoutMs === 0) {
|
||||||
points.shift()
|
if (points.length > 1) {
|
||||||
this.updateScribble()
|
points.shift()
|
||||||
|
this.updateScribble()
|
||||||
|
} else {
|
||||||
|
this.delayRemaining = this.delay
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'stopping': {
|
case 'stopping': {
|
||||||
if (timeoutMs === 0) {
|
if (this.delayRemaining === 0) {
|
||||||
// If the scribble is down to one point, we're done!
|
if (timeoutMs === 0) {
|
||||||
if (points.length === 1) {
|
// If the scribble is down to one point, we're done!
|
||||||
this.state = 'paused'
|
if (points.length === 1) {
|
||||||
this.onComplete()
|
this.state = 'paused'
|
||||||
return
|
this.onComplete()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop the scribble's size
|
||||||
|
this.size *= 0.9
|
||||||
|
|
||||||
|
// Drop the scribble's first point (its tail)
|
||||||
|
points.shift()
|
||||||
|
|
||||||
|
// otherwise, update the scribble
|
||||||
|
this.updateScribble()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop the scribble's size
|
|
||||||
this.size *= 0.9
|
|
||||||
|
|
||||||
// Drop the scribble's first point (its tail)
|
|
||||||
points.shift()
|
|
||||||
|
|
||||||
// otherwise, update the scribble
|
|
||||||
this.updateScribble()
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { TLEraserTool } from './TLEraserTool/TLEraserTool'
|
||||||
import { TLFrameTool } from './TLFrameTool/TLFrameTool'
|
import { TLFrameTool } from './TLFrameTool/TLFrameTool'
|
||||||
import { TLGeoTool } from './TLGeoTool/TLGeoTool'
|
import { TLGeoTool } from './TLGeoTool/TLGeoTool'
|
||||||
import { TLHandTool } from './TLHandTool/TLHandTool'
|
import { TLHandTool } from './TLHandTool/TLHandTool'
|
||||||
|
import { TLLaserTool } from './TLLaserTool/TLLaserTool'
|
||||||
import { TLLineTool } from './TLLineTool/TLLineTool'
|
import { TLLineTool } from './TLLineTool/TLLineTool'
|
||||||
import { TLNoteTool } from './TLNoteTool/TLNoteTool'
|
import { TLNoteTool } from './TLNoteTool/TLNoteTool'
|
||||||
import { TLSelectTool } from './TLSelectTool/TLSelectTool'
|
import { TLSelectTool } from './TLSelectTool/TLSelectTool'
|
||||||
|
@ -27,6 +28,7 @@ export class RootState extends StateNode {
|
||||||
TLNoteTool,
|
TLNoteTool,
|
||||||
TLFrameTool,
|
TLFrameTool,
|
||||||
TLZoomTool,
|
TLZoomTool,
|
||||||
|
TLLaserTool,
|
||||||
]
|
]
|
||||||
|
|
||||||
onKeyDown: TLEventHandlers['onKeyDown'] = (info) => {
|
onKeyDown: TLEventHandlers['onKeyDown'] = (info) => {
|
||||||
|
|
|
@ -32,6 +32,10 @@ export class Erasing extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
private startScribble = () => {
|
private startScribble = () => {
|
||||||
|
if (this.scribble.tick) {
|
||||||
|
this.app.off('tick', this.scribble?.tick)
|
||||||
|
}
|
||||||
|
|
||||||
this.scribble = new ScribbleManager({
|
this.scribble = new ScribbleManager({
|
||||||
onUpdate: this.onScribbleUpdate,
|
onUpdate: this.onScribbleUpdate,
|
||||||
onComplete: this.onScribbleComplete,
|
onComplete: this.onScribbleComplete,
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { StateNode } from '../StateNode'
|
||||||
|
|
||||||
|
import { Idle } from './children/Idle'
|
||||||
|
import { Lasering } from './children/Lasering'
|
||||||
|
|
||||||
|
export class TLLaserTool extends StateNode {
|
||||||
|
static override id = 'laser'
|
||||||
|
static initial = 'idle'
|
||||||
|
static children = () => [Idle, Lasering]
|
||||||
|
|
||||||
|
onEnter = () => {
|
||||||
|
this.app.setCursor({ type: 'cross' })
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { TLEventHandlers } from '../../../types/event-types'
|
||||||
|
import { StateNode } from '../../StateNode'
|
||||||
|
|
||||||
|
export class Idle extends StateNode {
|
||||||
|
static override id = 'idle'
|
||||||
|
|
||||||
|
onPointerDown: TLEventHandlers['onPointerDown'] = (info) => {
|
||||||
|
this.parent.transition('lasering', info)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { TLScribble } from '@tldraw/tlschema'
|
||||||
|
import { ScribbleManager } from '../../../managers/ScribbleManager'
|
||||||
|
import { TLEventHandlers } from '../../../types/event-types'
|
||||||
|
import { StateNode } from '../../StateNode'
|
||||||
|
|
||||||
|
export class Lasering extends StateNode {
|
||||||
|
static override id = 'lasering'
|
||||||
|
|
||||||
|
scribble = {} as ScribbleManager
|
||||||
|
|
||||||
|
override onEnter = () => {
|
||||||
|
this.startScribble()
|
||||||
|
this.pushPointToScribble()
|
||||||
|
}
|
||||||
|
|
||||||
|
override onExit = () => {
|
||||||
|
this.app.setErasingIds([])
|
||||||
|
this.scribble.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override onPointerMove = () => {
|
||||||
|
this.pushPointToScribble()
|
||||||
|
}
|
||||||
|
|
||||||
|
override onPointerUp = () => {
|
||||||
|
this.complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
private startScribble = () => {
|
||||||
|
if (this.scribble.tick) {
|
||||||
|
this.app.off('tick', this.scribble?.tick)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scribble = new ScribbleManager({
|
||||||
|
onUpdate: this.onScribbleUpdate,
|
||||||
|
onComplete: this.onScribbleComplete,
|
||||||
|
color: 'laser',
|
||||||
|
opacity: 0.7,
|
||||||
|
size: 4,
|
||||||
|
delay: 1200,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.app.on('tick', this.scribble.tick)
|
||||||
|
}
|
||||||
|
|
||||||
|
private pushPointToScribble = () => {
|
||||||
|
const { x, y } = this.app.inputs.currentPagePoint
|
||||||
|
this.scribble.addPoint(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
private onScribbleUpdate = (scribble: TLScribble) => {
|
||||||
|
this.app.setScribble(scribble)
|
||||||
|
}
|
||||||
|
|
||||||
|
private onScribbleComplete = () => {
|
||||||
|
this.app.off('tick', this.scribble.tick)
|
||||||
|
this.app.setScribble(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override onCancel: TLEventHandlers['onCancel'] = () => {
|
||||||
|
this.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
override onComplete: TLEventHandlers['onComplete'] = () => {
|
||||||
|
this.complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
private complete() {
|
||||||
|
this.parent.transition('idle', {})
|
||||||
|
}
|
||||||
|
|
||||||
|
private cancel() {
|
||||||
|
this.parent.transition('idle', {})
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,8 +8,6 @@ import { StateNode } from '../../StateNode'
|
||||||
export class ScribbleBrushing extends StateNode {
|
export class ScribbleBrushing extends StateNode {
|
||||||
static override id = 'scribble_brushing'
|
static override id = 'scribble_brushing'
|
||||||
|
|
||||||
static canActivateInReadOnly = true
|
|
||||||
|
|
||||||
hits = new Set<TLShapeId>()
|
hits = new Set<TLShapeId>()
|
||||||
|
|
||||||
size = 0
|
size = 0
|
||||||
|
@ -61,6 +59,10 @@ export class ScribbleBrushing extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
private startScribble = () => {
|
private startScribble = () => {
|
||||||
|
if (this.scribble.tick) {
|
||||||
|
this.app.off('tick', this.scribble?.tick)
|
||||||
|
}
|
||||||
|
|
||||||
this.scribble = new ScribbleManager({
|
this.scribble = new ScribbleManager({
|
||||||
onUpdate: this.onScribbleUpdate,
|
onUpdate: this.onScribbleUpdate,
|
||||||
onComplete: this.onScribbleComplete,
|
onComplete: this.onScribbleComplete,
|
||||||
|
|
|
@ -25,6 +25,7 @@ export const DefaultScribble: TLScribbleComponent = ({
|
||||||
start: { taper: true, easing: EASINGS.linear },
|
start: { taper: true, easing: EASINGS.linear },
|
||||||
last: scribble.state === 'stopping',
|
last: scribble.state === 'stopping',
|
||||||
simulatePressure: false,
|
simulatePressure: false,
|
||||||
|
streamline: 0.32,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -534,7 +534,7 @@ export const TL_SPLINE_TYPES: Set<"cubic" | "line">;
|
||||||
export const TL_STYLE_TYPES: Set<"align" | "arrowheadEnd" | "arrowheadStart" | "color" | "dash" | "fill" | "font" | "geo" | "icon" | "labelColor" | "opacity" | "size" | "spline" | "verticalAlign">;
|
export const TL_STYLE_TYPES: Set<"align" | "arrowheadEnd" | "arrowheadStart" | "color" | "dash" | "fill" | "font" | "geo" | "icon" | "labelColor" | "opacity" | "size" | "spline" | "verticalAlign">;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const TL_UI_COLOR_TYPES: Set<"accent" | "black" | "muted-1" | "selection-fill" | "selection-stroke" | "white">;
|
export const TL_UI_COLOR_TYPES: Set<"accent" | "black" | "laser" | "muted-1" | "selection-fill" | "selection-stroke" | "white">;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export interface TLAlignStyle extends TLBaseStyle {
|
export interface TLAlignStyle extends TLBaseStyle {
|
||||||
|
@ -1158,6 +1158,7 @@ export type TLScribble = {
|
||||||
color: TLUiColorType;
|
color: TLUiColorType;
|
||||||
opacity: number;
|
opacity: number;
|
||||||
state: SetValue<typeof TL_SCRIBBLE_STATES>;
|
state: SetValue<typeof TL_SCRIBBLE_STATES>;
|
||||||
|
delay: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
|
@ -1369,7 +1370,7 @@ export type TLVideoShapeProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const uiColorTypeValidator: T.Validator<"accent" | "black" | "muted-1" | "selection-fill" | "selection-stroke" | "white">;
|
export const uiColorTypeValidator: T.Validator<"accent" | "black" | "laser" | "muted-1" | "selection-fill" | "selection-stroke" | "white">;
|
||||||
|
|
||||||
// @internal (undocumented)
|
// @internal (undocumented)
|
||||||
export const USER_COLORS: string[];
|
export const USER_COLORS: string[];
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { imageAssetMigrations } from './assets/TLImageAsset'
|
||||||
import { videoAssetMigrations } from './assets/TLVideoAsset'
|
import { videoAssetMigrations } from './assets/TLVideoAsset'
|
||||||
import { instanceTypeMigrations } from './records/TLInstance'
|
import { instanceTypeMigrations } from './records/TLInstance'
|
||||||
import { instancePageStateMigrations } from './records/TLInstancePageState'
|
import { instancePageStateMigrations } from './records/TLInstancePageState'
|
||||||
|
import { instancePresenceTypeMigrations } from './records/TLInstancePresence'
|
||||||
import { rootShapeTypeMigrations, TLShape } from './records/TLShape'
|
import { rootShapeTypeMigrations, TLShape } from './records/TLShape'
|
||||||
import { userDocumentTypeMigrations, userDocumentVersions } from './records/TLUserDocument'
|
import { userDocumentTypeMigrations, userDocumentVersions } from './records/TLUserDocument'
|
||||||
import { userPresenceTypeMigrations } from './records/TLUserPresence'
|
import { userPresenceTypeMigrations } from './records/TLUserPresence'
|
||||||
|
@ -738,7 +739,129 @@ describe('Removing isReadOnly from user_document', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
/* --- PUT YOU
|
describe('Adds delay to scribble', () => {
|
||||||
|
const { up, down } = instanceTypeMigrations.migrators[10]
|
||||||
|
|
||||||
|
test('up has no effect when scribble is null', () => {
|
||||||
|
expect(
|
||||||
|
up({
|
||||||
|
scribble: null,
|
||||||
|
})
|
||||||
|
).toEqual({ scribble: null })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('up adds the delay property', () => {
|
||||||
|
expect(
|
||||||
|
up({
|
||||||
|
scribble: {
|
||||||
|
points: [{ x: 0, y: 0 }],
|
||||||
|
size: 4,
|
||||||
|
color: 'black',
|
||||||
|
opacity: 1,
|
||||||
|
state: 'starting',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
scribble: {
|
||||||
|
points: [{ x: 0, y: 0 }],
|
||||||
|
size: 4,
|
||||||
|
color: 'black',
|
||||||
|
opacity: 1,
|
||||||
|
state: 'starting',
|
||||||
|
delay: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('down has no effect when scribble is null', () => {
|
||||||
|
expect(down({ scribble: null })).toEqual({ scribble: null })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('removes the delay property', () => {
|
||||||
|
expect(
|
||||||
|
down({
|
||||||
|
scribble: {
|
||||||
|
points: [{ x: 0, y: 0 }],
|
||||||
|
size: 4,
|
||||||
|
color: 'black',
|
||||||
|
opacity: 1,
|
||||||
|
state: 'starting',
|
||||||
|
delay: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
scribble: {
|
||||||
|
points: [{ x: 0, y: 0 }],
|
||||||
|
size: 4,
|
||||||
|
color: 'black',
|
||||||
|
opacity: 1,
|
||||||
|
state: 'starting',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Adds delay to scribble', () => {
|
||||||
|
const { up, down } = instancePresenceTypeMigrations.migrators[1]
|
||||||
|
|
||||||
|
test('up has no effect when scribble is null', () => {
|
||||||
|
expect(
|
||||||
|
up({
|
||||||
|
scribble: null,
|
||||||
|
})
|
||||||
|
).toEqual({ scribble: null })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('up adds the delay property', () => {
|
||||||
|
expect(
|
||||||
|
up({
|
||||||
|
scribble: {
|
||||||
|
points: [{ x: 0, y: 0 }],
|
||||||
|
size: 4,
|
||||||
|
color: 'black',
|
||||||
|
opacity: 1,
|
||||||
|
state: 'starting',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
scribble: {
|
||||||
|
points: [{ x: 0, y: 0 }],
|
||||||
|
size: 4,
|
||||||
|
color: 'black',
|
||||||
|
opacity: 1,
|
||||||
|
state: 'starting',
|
||||||
|
delay: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('down has no effect when scribble is null', () => {
|
||||||
|
expect(down({ scribble: null })).toEqual({ scribble: null })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('removes the delay property', () => {
|
||||||
|
expect(
|
||||||
|
down({
|
||||||
|
scribble: {
|
||||||
|
points: [{ x: 0, y: 0 }],
|
||||||
|
size: 4,
|
||||||
|
color: 'black',
|
||||||
|
opacity: 1,
|
||||||
|
state: 'starting',
|
||||||
|
delay: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
scribble: {
|
||||||
|
points: [{ x: 0, y: 0 }],
|
||||||
|
size: 4,
|
||||||
|
color: 'black',
|
||||||
|
opacity: 1,
|
||||||
|
state: 'starting',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
/* --- PUT YOUR MIGRATIONS TESTS ABOVE HERE --- */
|
/* --- PUT YOUR MIGRATIONS TESTS ABOVE HERE --- */
|
||||||
|
|
||||||
|
|
|
@ -105,13 +105,14 @@ const Versions = {
|
||||||
RemoveAlignJustify: 7,
|
RemoveAlignJustify: 7,
|
||||||
AddZoom: 8,
|
AddZoom: 8,
|
||||||
AddVerticalAlign: 9,
|
AddVerticalAlign: 9,
|
||||||
|
AddScribbleDelay: 10,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export const instanceTypeMigrations = defineMigrations({
|
export const instanceTypeMigrations = defineMigrations({
|
||||||
firstVersion: Versions.Initial,
|
firstVersion: Versions.Initial,
|
||||||
// STEP 2: Update the current version to point to your latest version
|
// STEP 2: Update the current version to point to your latest version
|
||||||
currentVersion: Versions.AddVerticalAlign,
|
currentVersion: Versions.AddScribbleDelay,
|
||||||
// STEP 3: Add an up+down migration for the new version here
|
// STEP 3: Add an up+down migration for the new version here
|
||||||
migrators: {
|
migrators: {
|
||||||
[Versions.AddTransparentExportBgs]: {
|
[Versions.AddTransparentExportBgs]: {
|
||||||
|
@ -227,6 +228,21 @@ export const instanceTypeMigrations = defineMigrations({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[Versions.AddScribbleDelay]: {
|
||||||
|
up: (instance) => {
|
||||||
|
if (instance.scribble !== null) {
|
||||||
|
return { ...instance, scribble: { ...instance.scribble, delay: 0 } }
|
||||||
|
}
|
||||||
|
return { ...instance }
|
||||||
|
},
|
||||||
|
down: (instance) => {
|
||||||
|
if (instance.scribble !== null) {
|
||||||
|
const { delay: _delay, ...rest } = instance.scribble
|
||||||
|
return { ...instance, scribble: rest }
|
||||||
|
}
|
||||||
|
return { ...instance }
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -70,20 +70,36 @@ export const instancePresenceTypeValidator: T.Validator<TLInstancePresence> = T.
|
||||||
// It should be 1 higher than the current version
|
// It should be 1 higher than the current version
|
||||||
const Versions = {
|
const Versions = {
|
||||||
Initial: 0,
|
Initial: 0,
|
||||||
|
AddScribbleDelay: 1,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const userPresenceTypeMigrations = defineMigrations({
|
export const instancePresenceTypeMigrations = defineMigrations({
|
||||||
// STEP 2: Update the current version to point to your latest version
|
// STEP 2: Update the current version to point to your latest version
|
||||||
currentVersion: Versions.Initial,
|
|
||||||
firstVersion: Versions.Initial,
|
firstVersion: Versions.Initial,
|
||||||
|
currentVersion: Versions.AddScribbleDelay,
|
||||||
migrators: {
|
migrators: {
|
||||||
// STEP 3: Add an up+down migration for the new version here
|
// STEP 3: Add an up+down migration for the new version here
|
||||||
|
[Versions.AddScribbleDelay]: {
|
||||||
|
up: (instance) => {
|
||||||
|
if (instance.scribble !== null) {
|
||||||
|
return { ...instance, scribble: { ...instance.scribble, delay: 0 } }
|
||||||
|
}
|
||||||
|
return { ...instance }
|
||||||
|
},
|
||||||
|
down: (instance) => {
|
||||||
|
if (instance.scribble !== null) {
|
||||||
|
const { delay: _delay, ...rest } = instance.scribble
|
||||||
|
return { ...instance, scribble: rest }
|
||||||
|
}
|
||||||
|
return { ...instance }
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export const TLInstancePresence = createRecordType<TLInstancePresence>('instance_presence', {
|
export const TLInstancePresence = createRecordType<TLInstancePresence>('instance_presence', {
|
||||||
migrations: userPresenceTypeMigrations,
|
migrations: instancePresenceTypeMigrations,
|
||||||
validator: instancePresenceTypeValidator,
|
validator: instancePresenceTypeValidator,
|
||||||
scope: 'presence',
|
scope: 'presence',
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,6 +9,7 @@ export const TL_UI_COLOR_TYPES = new Set([
|
||||||
'black',
|
'black',
|
||||||
'selection-stroke',
|
'selection-stroke',
|
||||||
'selection-fill',
|
'selection-fill',
|
||||||
|
'laser',
|
||||||
'muted-1',
|
'muted-1',
|
||||||
] as const)
|
] as const)
|
||||||
|
|
||||||
|
@ -70,6 +71,7 @@ export type TLScribble = {
|
||||||
color: TLUiColorType
|
color: TLUiColorType
|
||||||
opacity: number
|
opacity: number
|
||||||
state: SetValue<typeof TL_SCRIBBLE_STATES>
|
state: SetValue<typeof TL_SCRIBBLE_STATES>
|
||||||
|
delay: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
@ -79,6 +81,7 @@ export const scribbleTypeValidator: T.Validator<TLScribble> = T.object({
|
||||||
color: uiColorTypeValidator,
|
color: uiColorTypeValidator,
|
||||||
opacity: T.number,
|
opacity: T.number,
|
||||||
state: T.setEnum(TL_SCRIBBLE_STATES),
|
state: T.setEnum(TL_SCRIBBLE_STATES),
|
||||||
|
delay: T.number,
|
||||||
})
|
})
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -71,6 +71,7 @@ export function ToolbarSchemaProvider({ overrides, children }: ToolbarSchemaProv
|
||||||
toolbarItem(tools.line),
|
toolbarItem(tools.line),
|
||||||
toolbarItem(tools.frame),
|
toolbarItem(tools.frame),
|
||||||
toolbarItem(tools.embed),
|
toolbarItem(tools.embed),
|
||||||
|
toolbarItem(tools.laser),
|
||||||
]
|
]
|
||||||
|
|
||||||
if (overrides) {
|
if (overrides) {
|
||||||
|
|
|
@ -177,6 +177,17 @@ export function ToolsProvider({ overrides, children }: ToolsProviderProps) {
|
||||||
trackEvent('select-tool', { source, id: 'note' })
|
trackEvent('select-tool', { source, id: 'note' })
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'laser',
|
||||||
|
label: 'tool.laser',
|
||||||
|
readonlyOk: true,
|
||||||
|
icon: 'tool-laser',
|
||||||
|
kbd: 'k',
|
||||||
|
onSelect(source) {
|
||||||
|
app.setSelectedTool('laser')
|
||||||
|
trackEvent('select-tool', { source, id: 'laser' })
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'embed',
|
id: 'embed',
|
||||||
label: 'tool.embed',
|
label: 'tool.embed',
|
||||||
|
|
|
@ -194,6 +194,7 @@ export type TLTranslationKey =
|
||||||
| 'tool.asset'
|
| 'tool.asset'
|
||||||
| 'tool.frame'
|
| 'tool.frame'
|
||||||
| 'tool.note'
|
| 'tool.note'
|
||||||
|
| 'tool.laser'
|
||||||
| 'tool.embed'
|
| 'tool.embed'
|
||||||
| 'tool.text'
|
| 'tool.text'
|
||||||
| 'menu.title'
|
| 'menu.title'
|
||||||
|
|
|
@ -194,6 +194,7 @@ export const DEFAULT_TRANSLATION = {
|
||||||
'tool.asset': 'Asset',
|
'tool.asset': 'Asset',
|
||||||
'tool.frame': 'Frame',
|
'tool.frame': 'Frame',
|
||||||
'tool.note': 'Note',
|
'tool.note': 'Note',
|
||||||
|
'tool.laser': 'Laser',
|
||||||
'tool.embed': 'Embed',
|
'tool.embed': 'Embed',
|
||||||
'tool.text': 'Text',
|
'tool.text': 'Text',
|
||||||
'menu.title': 'Menu',
|
'menu.title': 'Menu',
|
||||||
|
|
|
@ -142,6 +142,7 @@ export type TLUiIconType =
|
||||||
| 'tool-frame'
|
| 'tool-frame'
|
||||||
| 'tool-hand'
|
| 'tool-hand'
|
||||||
| 'tool-highlighter'
|
| 'tool-highlighter'
|
||||||
|
| 'tool-laser'
|
||||||
| 'tool-line'
|
| 'tool-line'
|
||||||
| 'tool-media'
|
| 'tool-media'
|
||||||
| 'tool-note'
|
| 'tool-note'
|
||||||
|
@ -305,6 +306,7 @@ export const TLUiIconTypes = [
|
||||||
'tool-frame',
|
'tool-frame',
|
||||||
'tool-hand',
|
'tool-hand',
|
||||||
'tool-highlighter',
|
'tool-highlighter',
|
||||||
|
'tool-laser',
|
||||||
'tool-line',
|
'tool-line',
|
||||||
'tool-media',
|
'tool-media',
|
||||||
'tool-note',
|
'tool-note',
|
||||||
|
|
Loading…
Reference in a new issue