[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-background: #1d1d1d;
|
||||
--color-accent: #f3c14b;
|
||||
|
||||
--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);
|
||||
|
|
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.frame": "Frame",
|
||||
"tool.note": "Note",
|
||||
"tool.laser": "Laser",
|
||||
"tool.embed": "Embed",
|
||||
"tool.text": "Text",
|
||||
"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-hand': string
|
||||
'tool-highlighter': string
|
||||
'tool-laser': string
|
||||
'tool-line': string
|
||||
'tool-media': 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 iconsToolHand from './icons/icon/tool-hand.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 iconsToolMedia from './icons/icon/tool-media.svg'
|
||||
import iconsToolNote from './icons/icon/tool-note.svg'
|
||||
|
@ -392,6 +393,7 @@ export function getAssetUrlsByImport(opts) {
|
|||
'tool-frame': formatAssetUrl(iconsToolFrame, opts),
|
||||
'tool-hand': formatAssetUrl(iconsToolHand, opts),
|
||||
'tool-highlighter': formatAssetUrl(iconsToolHighlighter, opts),
|
||||
'tool-laser': formatAssetUrl(iconsToolLaser, opts),
|
||||
'tool-line': formatAssetUrl(iconsToolLine, opts),
|
||||
'tool-media': formatAssetUrl(iconsToolMedia, 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-hand': string
|
||||
'tool-highlighter': string
|
||||
'tool-laser': string
|
||||
'tool-line': string
|
||||
'tool-media': string
|
||||
'tool-note': string
|
||||
|
|
|
@ -498,6 +498,10 @@ export function getAssetUrlsByMetaUrl(opts) {
|
|||
new URL('./icons/icon/tool-highlighter.svg', import.meta.url).href,
|
||||
opts
|
||||
),
|
||||
'tool-laser': formatAssetUrl(
|
||||
new URL('./icons/icon/tool-laser.svg', import.meta.url).href,
|
||||
opts
|
||||
),
|
||||
'tool-line': formatAssetUrl(
|
||||
new URL('./icons/icon/tool-line.svg', import.meta.url).href,
|
||||
opts
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
--color-primary: #2f80ed;
|
||||
--color-warn: #d10b0b;
|
||||
--color-text: #000000;
|
||||
--color-laser: #ff0000;
|
||||
--palette-black: #1d1d1d;
|
||||
--palette-blue: #4263eb;
|
||||
--palette-green: #099268;
|
||||
|
@ -154,6 +155,7 @@
|
|||
--color-primary: #2f80ed;
|
||||
--color-warn: #d10b0b;
|
||||
--color-text: #f8f9fa;
|
||||
--color-laser: #ff0000;
|
||||
--palette-black: #e1e1e1;
|
||||
--palette-blue: #4156be;
|
||||
--palette-green: #3b7b5e;
|
||||
|
|
|
@ -9,6 +9,10 @@ export class ScribbleManager implements TLScribble {
|
|||
size
|
||||
color
|
||||
opacity
|
||||
delay
|
||||
|
||||
timeoutMs = 0
|
||||
delayRemaining = 0
|
||||
|
||||
// Callbacks
|
||||
private onUpdate: (scribble: TLScribble) => void
|
||||
|
@ -24,13 +28,15 @@ export class ScribbleManager implements TLScribble {
|
|||
size?: TLScribble['size']
|
||||
color?: TLScribble['color']
|
||||
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.onComplete = onComplete
|
||||
this.size = size
|
||||
this.color = color
|
||||
this.delay = delay
|
||||
this.opacity = opacity
|
||||
this.points = [] as Vec2dModel[]
|
||||
this.state = 'starting' as TLScribble['state']
|
||||
|
@ -38,6 +44,8 @@ export class ScribbleManager implements TLScribble {
|
|||
this.prev = null
|
||||
this.next = null
|
||||
|
||||
this.delayRemaining = this.delay
|
||||
|
||||
this.resume()
|
||||
}
|
||||
|
||||
|
@ -55,6 +63,7 @@ export class ScribbleManager implements TLScribble {
|
|||
* @public
|
||||
*/
|
||||
stop = () => {
|
||||
this.delayRemaining = Math.min(this.delayRemaining, 200)
|
||||
this.state = 'stopping'
|
||||
}
|
||||
|
||||
|
@ -82,6 +91,7 @@ export class ScribbleManager implements TLScribble {
|
|||
size: this.size,
|
||||
color: this.color,
|
||||
opacity: this.opacity,
|
||||
delay: this.delay,
|
||||
points: [...this.points],
|
||||
}
|
||||
}
|
||||
|
@ -90,10 +100,13 @@ export class ScribbleManager implements TLScribble {
|
|||
this.onUpdate(this.getScribble())
|
||||
}
|
||||
|
||||
timeoutMs = 0
|
||||
|
||||
tick: TLTickEvent = (elapsed) => {
|
||||
this.timeoutMs += elapsed
|
||||
|
||||
if (this.delayRemaining > 0) {
|
||||
this.delayRemaining = Math.max(0, this.delayRemaining - elapsed)
|
||||
}
|
||||
|
||||
if (this.timeoutMs >= 16) {
|
||||
this.timeoutMs = 0
|
||||
}
|
||||
|
@ -106,37 +119,45 @@ export class ScribbleManager implements TLScribble {
|
|||
this.prev = next
|
||||
points.push(next)
|
||||
|
||||
if (points.length > 8) {
|
||||
points.shift()
|
||||
if (this.delayRemaining === 0) {
|
||||
if (points.length > 8) {
|
||||
points.shift()
|
||||
}
|
||||
}
|
||||
|
||||
this.updateScribble()
|
||||
} else {
|
||||
// While not moving, shrink the scribble from the start
|
||||
if (timeoutMs === 0 && points.length > 1) {
|
||||
points.shift()
|
||||
this.updateScribble()
|
||||
if (timeoutMs === 0) {
|
||||
if (points.length > 1) {
|
||||
points.shift()
|
||||
this.updateScribble()
|
||||
} else {
|
||||
this.delayRemaining = this.delay
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'stopping': {
|
||||
if (timeoutMs === 0) {
|
||||
// If the scribble is down to one point, we're done!
|
||||
if (points.length === 1) {
|
||||
this.state = 'paused'
|
||||
this.onComplete()
|
||||
return
|
||||
if (this.delayRemaining === 0) {
|
||||
if (timeoutMs === 0) {
|
||||
// If the scribble is down to one point, we're done!
|
||||
if (points.length === 1) {
|
||||
this.state = 'paused'
|
||||
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
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { TLEraserTool } from './TLEraserTool/TLEraserTool'
|
|||
import { TLFrameTool } from './TLFrameTool/TLFrameTool'
|
||||
import { TLGeoTool } from './TLGeoTool/TLGeoTool'
|
||||
import { TLHandTool } from './TLHandTool/TLHandTool'
|
||||
import { TLLaserTool } from './TLLaserTool/TLLaserTool'
|
||||
import { TLLineTool } from './TLLineTool/TLLineTool'
|
||||
import { TLNoteTool } from './TLNoteTool/TLNoteTool'
|
||||
import { TLSelectTool } from './TLSelectTool/TLSelectTool'
|
||||
|
@ -27,6 +28,7 @@ export class RootState extends StateNode {
|
|||
TLNoteTool,
|
||||
TLFrameTool,
|
||||
TLZoomTool,
|
||||
TLLaserTool,
|
||||
]
|
||||
|
||||
onKeyDown: TLEventHandlers['onKeyDown'] = (info) => {
|
||||
|
|
|
@ -32,6 +32,10 @@ export class Erasing extends StateNode {
|
|||
}
|
||||
|
||||
private startScribble = () => {
|
||||
if (this.scribble.tick) {
|
||||
this.app.off('tick', this.scribble?.tick)
|
||||
}
|
||||
|
||||
this.scribble = new ScribbleManager({
|
||||
onUpdate: this.onScribbleUpdate,
|
||||
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 {
|
||||
static override id = 'scribble_brushing'
|
||||
|
||||
static canActivateInReadOnly = true
|
||||
|
||||
hits = new Set<TLShapeId>()
|
||||
|
||||
size = 0
|
||||
|
@ -61,6 +59,10 @@ export class ScribbleBrushing extends StateNode {
|
|||
}
|
||||
|
||||
private startScribble = () => {
|
||||
if (this.scribble.tick) {
|
||||
this.app.off('tick', this.scribble?.tick)
|
||||
}
|
||||
|
||||
this.scribble = new ScribbleManager({
|
||||
onUpdate: this.onScribbleUpdate,
|
||||
onComplete: this.onScribbleComplete,
|
||||
|
|
|
@ -25,6 +25,7 @@ export const DefaultScribble: TLScribbleComponent = ({
|
|||
start: { taper: true, easing: EASINGS.linear },
|
||||
last: scribble.state === 'stopping',
|
||||
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">;
|
||||
|
||||
// @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)
|
||||
export interface TLAlignStyle extends TLBaseStyle {
|
||||
|
@ -1158,6 +1158,7 @@ export type TLScribble = {
|
|||
color: TLUiColorType;
|
||||
opacity: number;
|
||||
state: SetValue<typeof TL_SCRIBBLE_STATES>;
|
||||
delay: number;
|
||||
};
|
||||
|
||||
// @public
|
||||
|
@ -1369,7 +1370,7 @@ export type TLVideoShapeProps = {
|
|||
};
|
||||
|
||||
// @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)
|
||||
export const USER_COLORS: string[];
|
||||
|
|
|
@ -5,6 +5,7 @@ import { imageAssetMigrations } from './assets/TLImageAsset'
|
|||
import { videoAssetMigrations } from './assets/TLVideoAsset'
|
||||
import { instanceTypeMigrations } from './records/TLInstance'
|
||||
import { instancePageStateMigrations } from './records/TLInstancePageState'
|
||||
import { instancePresenceTypeMigrations } from './records/TLInstancePresence'
|
||||
import { rootShapeTypeMigrations, TLShape } from './records/TLShape'
|
||||
import { userDocumentTypeMigrations, userDocumentVersions } from './records/TLUserDocument'
|
||||
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 --- */
|
||||
|
||||
|
|
|
@ -105,13 +105,14 @@ const Versions = {
|
|||
RemoveAlignJustify: 7,
|
||||
AddZoom: 8,
|
||||
AddVerticalAlign: 9,
|
||||
AddScribbleDelay: 10,
|
||||
} as const
|
||||
|
||||
/** @public */
|
||||
export const instanceTypeMigrations = defineMigrations({
|
||||
firstVersion: Versions.Initial,
|
||||
// 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
|
||||
migrators: {
|
||||
[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
|
||||
const Versions = {
|
||||
Initial: 0,
|
||||
AddScribbleDelay: 1,
|
||||
} as const
|
||||
|
||||
export const userPresenceTypeMigrations = defineMigrations({
|
||||
export const instancePresenceTypeMigrations = defineMigrations({
|
||||
// STEP 2: Update the current version to point to your latest version
|
||||
currentVersion: Versions.Initial,
|
||||
firstVersion: Versions.Initial,
|
||||
currentVersion: Versions.AddScribbleDelay,
|
||||
migrators: {
|
||||
// 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 */
|
||||
export const TLInstancePresence = createRecordType<TLInstancePresence>('instance_presence', {
|
||||
migrations: userPresenceTypeMigrations,
|
||||
migrations: instancePresenceTypeMigrations,
|
||||
validator: instancePresenceTypeValidator,
|
||||
scope: 'presence',
|
||||
})
|
||||
|
|
|
@ -9,6 +9,7 @@ export const TL_UI_COLOR_TYPES = new Set([
|
|||
'black',
|
||||
'selection-stroke',
|
||||
'selection-fill',
|
||||
'laser',
|
||||
'muted-1',
|
||||
] as const)
|
||||
|
||||
|
@ -70,6 +71,7 @@ export type TLScribble = {
|
|||
color: TLUiColorType
|
||||
opacity: number
|
||||
state: SetValue<typeof TL_SCRIBBLE_STATES>
|
||||
delay: number
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
@ -79,6 +81,7 @@ export const scribbleTypeValidator: T.Validator<TLScribble> = T.object({
|
|||
color: uiColorTypeValidator,
|
||||
opacity: T.number,
|
||||
state: T.setEnum(TL_SCRIBBLE_STATES),
|
||||
delay: T.number,
|
||||
})
|
||||
|
||||
/** @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.frame),
|
||||
toolbarItem(tools.embed),
|
||||
toolbarItem(tools.laser),
|
||||
]
|
||||
|
||||
if (overrides) {
|
||||
|
|
|
@ -177,6 +177,17 @@ export function ToolsProvider({ overrides, children }: ToolsProviderProps) {
|
|||
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',
|
||||
label: 'tool.embed',
|
||||
|
|
|
@ -194,6 +194,7 @@ export type TLTranslationKey =
|
|||
| 'tool.asset'
|
||||
| 'tool.frame'
|
||||
| 'tool.note'
|
||||
| 'tool.laser'
|
||||
| 'tool.embed'
|
||||
| 'tool.text'
|
||||
| 'menu.title'
|
||||
|
|
|
@ -194,6 +194,7 @@ export const DEFAULT_TRANSLATION = {
|
|||
'tool.asset': 'Asset',
|
||||
'tool.frame': 'Frame',
|
||||
'tool.note': 'Note',
|
||||
'tool.laser': 'Laser',
|
||||
'tool.embed': 'Embed',
|
||||
'tool.text': 'Text',
|
||||
'menu.title': 'Menu',
|
||||
|
|
|
@ -142,6 +142,7 @@ export type TLUiIconType =
|
|||
| 'tool-frame'
|
||||
| 'tool-hand'
|
||||
| 'tool-highlighter'
|
||||
| 'tool-laser'
|
||||
| 'tool-line'
|
||||
| 'tool-media'
|
||||
| 'tool-note'
|
||||
|
@ -305,6 +306,7 @@ export const TLUiIconTypes = [
|
|||
'tool-frame',
|
||||
'tool-hand',
|
||||
'tool-highlighter',
|
||||
'tool-laser',
|
||||
'tool-line',
|
||||
'tool-media',
|
||||
'tool-note',
|
||||
|
|
Loading…
Reference in a new issue