tldraw/packages/editor/src/lib/config/TldrawEditorConfig.tsx
alex 674a829d1f
[1/3] initial highlighter shape/tool (#1401)
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!

![Kapture 2023-05-17 at 15 37
33](https://github.com/tldraw/tldraw/assets/1489520/982e78f4-6495-4a68-aa51-c8f7b5bcdd01)

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]
2023-06-01 12:46:13 +00:00

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,
},
})
}
}