
This diff adds an initial version of the highlighter shape. At this stage, it's a complete copy of the draw tool minus the following features: * Fills * Stroke types * Closed shapes I've created a new shape util (a copy-paste of the draw one with stuff renamed/deleted) but reused the state chart nodes for the draw shape. Currently this new tool looks exactly like the draw tool, but that'll be changing soon!  The UI here is extremely WIP. The highlighter tool is behind a feature flag, but once enabled is accessible through the tool bar. There's a first-draft highlighter icon (i didn't spend much time on this, it's not super legible on non-retina displays yet imo), and the tool is bound to the `i` key (any better suggestions? `h` is taken by the hand tool) ### The plan 1. initial highlighter shape/tool #1401 **>you are here<** 2. sandwich rendering for highlighter shapes #1418 3. shape styling - new colours and sizes, lightweight perfect freehand changes ### Change Type - [x] `minor` — New Feature ### Test Plan (not yet) ### Release Notes [internal only change layout ground work for highlighter]
131 lines
4.3 KiB
TypeScript
131 lines
4.3 KiB
TypeScript
import {
|
|
CLIENT_FIXUP_SCRIPT,
|
|
InstanceRecordType,
|
|
TLDOCUMENT_ID,
|
|
TLDefaultShape,
|
|
TLInstanceId,
|
|
TLInstancePresence,
|
|
TLRecord,
|
|
TLShape,
|
|
TLStore,
|
|
TLStoreProps,
|
|
createTLSchema,
|
|
} from '@tldraw/tlschema'
|
|
import { Migrations, RecordType, Store, StoreSchema, StoreSnapshot } from '@tldraw/tlstore'
|
|
import { Signal, computed } from 'signia'
|
|
import { TLArrowUtil } from '../app/shapeutils/TLArrowUtil/TLArrowUtil'
|
|
import { TLBookmarkUtil } from '../app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
|
|
import { TLDrawUtil } from '../app/shapeutils/TLDrawUtil/TLDrawUtil'
|
|
import { TLEmbedUtil } from '../app/shapeutils/TLEmbedUtil/TLEmbedUtil'
|
|
import { TLFrameUtil } from '../app/shapeutils/TLFrameUtil/TLFrameUtil'
|
|
import { TLGeoUtil } from '../app/shapeutils/TLGeoUtil/TLGeoUtil'
|
|
import { TLGroupUtil } from '../app/shapeutils/TLGroupUtil/TLGroupUtil'
|
|
import { TLHighlightUtil } from '../app/shapeutils/TLHighlightUtil/TLHighlightUtil'
|
|
import { TLImageUtil } from '../app/shapeutils/TLImageUtil/TLImageUtil'
|
|
import { TLLineUtil } from '../app/shapeutils/TLLineUtil/TLLineUtil'
|
|
import { TLNoteUtil } from '../app/shapeutils/TLNoteUtil/TLNoteUtil'
|
|
import { TLShapeUtilConstructor } from '../app/shapeutils/TLShapeUtil'
|
|
import { TLTextUtil } from '../app/shapeutils/TLTextUtil/TLTextUtil'
|
|
import { TLVideoUtil } from '../app/shapeutils/TLVideoUtil/TLVideoUtil'
|
|
import { StateNodeConstructor } from '../app/statechart/StateNode'
|
|
import { TLUserPreferences, getUserPreferences, setUserPreferences } from './TLUserPreferences'
|
|
|
|
// Secret shape types that don't have a shape util yet
|
|
type ShapeTypesNotImplemented = 'icon'
|
|
|
|
const DEFAULT_SHAPE_UTILS: {
|
|
[K in Exclude<TLDefaultShape['type'], ShapeTypesNotImplemented>]: TLShapeUtilConstructor<any>
|
|
} = {
|
|
arrow: TLArrowUtil,
|
|
bookmark: TLBookmarkUtil,
|
|
draw: TLDrawUtil,
|
|
embed: TLEmbedUtil,
|
|
frame: TLFrameUtil,
|
|
geo: TLGeoUtil,
|
|
group: TLGroupUtil,
|
|
image: TLImageUtil,
|
|
line: TLLineUtil,
|
|
note: TLNoteUtil,
|
|
text: TLTextUtil,
|
|
video: TLVideoUtil,
|
|
highlight: TLHighlightUtil,
|
|
}
|
|
|
|
/** @public */
|
|
export type TldrawEditorConfigOptions = {
|
|
tools?: readonly StateNodeConstructor[]
|
|
shapes?: Record<
|
|
string,
|
|
{
|
|
util: TLShapeUtilConstructor<any>
|
|
validator?: { validate: <T>(record: T) => T }
|
|
migrations?: Migrations
|
|
}
|
|
>
|
|
/** @internal */
|
|
derivePresenceState?: (store: TLStore) => Signal<TLInstancePresence | null>
|
|
userPreferences?: Signal<TLUserPreferences>
|
|
setUserPreferences?: (userPreferences: TLUserPreferences) => void
|
|
}
|
|
|
|
/** @public */
|
|
export class TldrawEditorConfig {
|
|
// Custom tools
|
|
readonly tools: readonly StateNodeConstructor[]
|
|
|
|
// Custom shape utils
|
|
readonly shapeUtils: Record<TLShape['type'], TLShapeUtilConstructor<any>>
|
|
|
|
// The record used for TLShape incorporating any custom shapes
|
|
readonly TLShape: RecordType<TLShape, 'type' | 'props' | 'index' | 'parentId'>
|
|
|
|
// The schema used for the store incorporating any custom shapes
|
|
readonly storeSchema: StoreSchema<TLRecord, TLStoreProps>
|
|
readonly derivePresenceState: (store: TLStore) => Signal<TLInstancePresence | null>
|
|
readonly userPreferences: Signal<TLUserPreferences>
|
|
readonly setUserPreferences: (userPreferences: TLUserPreferences) => void
|
|
|
|
constructor(opts = {} as TldrawEditorConfigOptions) {
|
|
const { shapes = {}, tools = [], derivePresenceState } = opts
|
|
|
|
this.tools = tools
|
|
this.derivePresenceState = derivePresenceState ?? (() => computed('presence', () => null))
|
|
this.userPreferences =
|
|
opts.userPreferences ?? computed('userPreferences', () => getUserPreferences())
|
|
this.setUserPreferences = opts.setUserPreferences ?? setUserPreferences
|
|
|
|
this.shapeUtils = {
|
|
...DEFAULT_SHAPE_UTILS,
|
|
...Object.fromEntries(Object.entries(shapes).map(([k, v]) => [k, v.util])),
|
|
}
|
|
|
|
this.storeSchema = createTLSchema({
|
|
customShapes: shapes,
|
|
})
|
|
|
|
this.TLShape = this.storeSchema.types.shape as RecordType<
|
|
TLShape,
|
|
'type' | 'props' | 'index' | 'parentId'
|
|
>
|
|
}
|
|
|
|
createStore(config: {
|
|
/** The store's initial data. */
|
|
initialData?: StoreSnapshot<TLRecord>
|
|
instanceId: TLInstanceId
|
|
}): TLStore {
|
|
let initialData = config.initialData
|
|
if (initialData) {
|
|
initialData = CLIENT_FIXUP_SCRIPT(initialData)
|
|
}
|
|
|
|
return new Store<TLRecord, TLStoreProps>({
|
|
schema: this.storeSchema,
|
|
initialData,
|
|
props: {
|
|
instanceId: config?.instanceId ?? InstanceRecordType.createId(),
|
|
documentId: TLDOCUMENT_ID,
|
|
},
|
|
})
|
|
}
|
|
}
|