Tldraw/packages/editor/src/lib/config/TldrawEditorConfig.tsx

132 wiersze
4.3 KiB
TypeScript
Czysty Zwykły widok Historia

2023-04-25 11:01:25 +00:00
import {
CLIENT_FIXUP_SCRIPT,
InstanceRecordType,
2023-04-25 11:01:25 +00:00
TLDOCUMENT_ID,
TLDefaultShape,
2023-04-25 11:01:25 +00:00
TLInstanceId,
derived presence state (#1204) This PR adds - A new `TLInstancePresence` record type, to collect info about the presence state in a particular instance of the editor. This will eventually be used to sync presence data instead of sending instance-only state across the wire. - **Record Scopes** `RecordType` now has a `scope` property which can be one of three things: - `document`: the record belongs to the document and should be synced and persisted freely. Currently: `TLDocument`, `TLPage`, `TLShape`, and `TLAsset` - `instance`: the record belongs to a single instance of the store and should not be synced at all. It should not be persisted directly in most cases, but rather compiled into a kind of 'instance configuration' to store alongside the local document data so that when reopening the associated document it can remember some of the previous instance state. Currently: `TLInstance`, `TLInstancePageState`, `TLCamera`, `TLUser`, `TLUserDocument`, `TLUserPresence` - `presence`: the record belongs to a single instance of the store and should not be persisted, but may be synced using the special presence sync protocol. Currently just `TLInstancePresence` This sets us up for the following changes, which are gonna be pretty high-impact in terms of integrating tldraw into existing systems: - Removing `instanceId` as a config option. Each instance gets a randomly generated ID. - We'd replace it with an `instanceConfig` option that has stuff like selectedIds, camera positions, and so on. Then it's up to library users to get and reinstate the instance config at persistence boundaries. - Removing `userId` as config option, and removing the `TLUser` type altogether. - We might need to revisit when doing auth-enabled features like locking shapes, but I suspect that will be separate.
2023-04-27 18:03:19 +00:00
TLInstancePresence,
2023-04-25 11:01:25 +00:00
TLRecord,
TLShape,
TLStore,
TLStoreProps,
createTLSchema,
2023-04-25 11:01:25 +00:00
} 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'
2023-04-25 11:01:25 +00:00
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
}
2023-04-25 11:01:25 +00:00
/** @public */
export class TldrawEditorConfig {
// Custom tools
2023-04-25 11:01:25 +00:00
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'>
2023-04-25 11:01:25 +00:00
// 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])),
}
2023-04-25 11:01:25 +00:00
this.storeSchema = createTLSchema({
customShapes: shapes,
})
2023-04-25 11:01:25 +00:00
this.TLShape = this.storeSchema.types.shape as RecordType<
TLShape,
'type' | 'props' | 'index' | 'parentId'
>
2023-04-25 11:01:25 +00:00
}
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>({
2023-04-25 11:01:25 +00:00
schema: this.storeSchema,
initialData,
props: {
instanceId: config?.instanceId ?? InstanceRecordType.createId(),
2023-04-25 11:01:25 +00:00
documentId: TLDOCUMENT_ID,
},
})
}
}