2023-04-25 11:01:25 +00:00
|
|
|
import {
|
|
|
|
CLIENT_FIXUP_SCRIPT,
|
2023-05-26 13:37:59 +00:00
|
|
|
InstanceRecordType,
|
2023-04-25 11:01:25 +00:00
|
|
|
TLDOCUMENT_ID,
|
2023-05-24 10:48:31 +00:00
|
|
|
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,
|
2023-05-24 10:48:31 +00:00
|
|
|
createTLSchema,
|
2023-04-25 11:01:25 +00:00
|
|
|
} from '@tldraw/tlschema'
|
2023-05-24 10:48:31 +00:00
|
|
|
import { Migrations, RecordType, Store, StoreSchema, StoreSnapshot } from '@tldraw/tlstore'
|
2023-05-25 09:54:29 +00:00
|
|
|
import { Signal, computed } from 'signia'
|
2023-05-23 12:32:42 +00:00
|
|
|
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'
|
2023-06-01 12:46:13 +00:00
|
|
|
import { TLHighlightUtil } from '../app/shapeutils/TLHighlightUtil/TLHighlightUtil'
|
2023-05-23 12:32:42 +00:00
|
|
|
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'
|
2023-05-25 09:54:29 +00:00
|
|
|
import { TLUserPreferences, getUserPreferences, setUserPreferences } from './TLUserPreferences'
|
2023-05-23 12:32:42 +00:00
|
|
|
|
2023-05-24 10:48:31 +00:00
|
|
|
// 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,
|
2023-06-01 12:46:13 +00:00
|
|
|
highlight: TLHighlightUtil,
|
2023-05-23 12:32:42 +00:00
|
|
|
}
|
|
|
|
|
2023-05-24 10:48:31 +00:00
|
|
|
/** @public */
|
|
|
|
export type TldrawEditorConfigOptions = {
|
2023-05-23 12:32:42 +00:00
|
|
|
tools?: readonly StateNodeConstructor[]
|
2023-05-24 10:48:31 +00:00
|
|
|
shapes?: Record<
|
|
|
|
string,
|
|
|
|
{
|
|
|
|
util: TLShapeUtilConstructor<any>
|
|
|
|
validator?: { validate: <T>(record: T) => T }
|
|
|
|
migrations?: Migrations
|
|
|
|
}
|
|
|
|
>
|
2023-05-23 12:32:42 +00:00
|
|
|
/** @internal */
|
|
|
|
derivePresenceState?: (store: TLStore) => Signal<TLInstancePresence | null>
|
2023-05-25 09:54:29 +00:00
|
|
|
userPreferences?: Signal<TLUserPreferences>
|
|
|
|
setUserPreferences?: (userPreferences: TLUserPreferences) => void
|
2023-05-23 12:32:42 +00:00
|
|
|
}
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
/** @public */
|
|
|
|
export class TldrawEditorConfig {
|
2023-05-24 10:48:31 +00:00
|
|
|
// Custom tools
|
2023-04-25 11:01:25 +00:00
|
|
|
readonly tools: readonly StateNodeConstructor[]
|
|
|
|
|
2023-05-23 13:35:11 +00:00
|
|
|
// Custom shape utils
|
2023-05-24 10:48:31 +00:00
|
|
|
readonly shapeUtils: Record<TLShape['type'], TLShapeUtilConstructor<any>>
|
2023-05-23 12:32:42 +00:00
|
|
|
|
2023-05-24 10:48:31 +00:00
|
|
|
// 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
|
|
|
|
2023-05-24 10:48:31 +00:00
|
|
|
// The schema used for the store incorporating any custom shapes
|
|
|
|
readonly storeSchema: StoreSchema<TLRecord, TLStoreProps>
|
2023-05-25 09:54:29 +00:00
|
|
|
readonly derivePresenceState: (store: TLStore) => Signal<TLInstancePresence | null>
|
|
|
|
readonly userPreferences: Signal<TLUserPreferences>
|
|
|
|
readonly setUserPreferences: (userPreferences: TLUserPreferences) => void
|
2023-05-23 12:32:42 +00:00
|
|
|
|
2023-05-24 10:48:31 +00:00
|
|
|
constructor(opts = {} as TldrawEditorConfigOptions) {
|
|
|
|
const { shapes = {}, tools = [], derivePresenceState } = opts
|
2023-05-23 12:32:42 +00:00
|
|
|
|
2023-05-24 10:48:31 +00:00
|
|
|
this.tools = tools
|
2023-05-25 09:54:29 +00:00
|
|
|
this.derivePresenceState = derivePresenceState ?? (() => computed('presence', () => null))
|
|
|
|
this.userPreferences =
|
|
|
|
opts.userPreferences ?? computed('userPreferences', () => getUserPreferences())
|
|
|
|
this.setUserPreferences = opts.setUserPreferences ?? setUserPreferences
|
2023-05-23 12:32:42 +00:00
|
|
|
|
2023-05-24 10:48:31 +00:00
|
|
|
this.shapeUtils = {
|
|
|
|
...DEFAULT_SHAPE_UTILS,
|
|
|
|
...Object.fromEntries(Object.entries(shapes).map(([k, v]) => [k, v.util])),
|
2023-05-23 12:32:42 +00:00
|
|
|
}
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-05-24 10:48:31 +00:00
|
|
|
this.storeSchema = createTLSchema({
|
|
|
|
customShapes: shapes,
|
|
|
|
})
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-05-09 14:40:58 +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)
|
|
|
|
}
|
2023-05-25 09:54:29 +00:00
|
|
|
|
|
|
|
return new Store<TLRecord, TLStoreProps>({
|
2023-04-25 11:01:25 +00:00
|
|
|
schema: this.storeSchema,
|
|
|
|
initialData,
|
|
|
|
props: {
|
2023-05-26 13:37:59 +00:00
|
|
|
instanceId: config?.instanceId ?? InstanceRecordType.createId(),
|
2023-04-25 11:01:25 +00:00
|
|
|
documentId: TLDOCUMENT_ID,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|