2024-07-10 15:46:09 +00:00
|
|
|
import { Signal } from '@tldraw/state'
|
2023-09-08 17:04:53 +00:00
|
|
|
import {
|
|
|
|
SerializedStore,
|
|
|
|
Store,
|
|
|
|
StoreSchema,
|
|
|
|
StoreSchemaOptions,
|
|
|
|
StoreSnapshot,
|
|
|
|
} from '@tldraw/store'
|
2024-02-14 17:53:30 +00:00
|
|
|
import { IndexKey, annotateError, structuredClone } from '@tldraw/utils'
|
[1/4] Blob storage in TLStore (#4068)
Reworks the store to include information about how blob assets
(images/videos) are stored/retrieved. This replaces the old
internal-only `assetOptions` prop, and supplements the existing
`registerExternalAssetHandler` API.
Previously, `registerExternalAssetHandler` had two responsibilities:
1. Extracting asset metadata
2. Uploading the asset and returning its URL
Existing `registerExternalAssetHandler` implementation will still work,
but now uploading is the responsibility of a new `editor.uploadAsset`
method which calls the new store-based upload method. Our default asset
handlers extract metadata, then call that new API. I think this is a
pretty big improvement over what we had before: overriding uploads was a
pretty common ask, but doing so meant having to copy paste our metadata
extraction which felt pretty fragile. Just in this codebase, we had a
bunch of very slightly different metadata extraction code-paths that had
been copy-pasted around then diverged over time. Now, you can change how
uploads work without having to mess with metadata extraction and
vice-versa.
As part of this we also:
1. merge the old separate asset indexeddb store with the main one.
because this warrants some pretty big migration stuff, i refactored our
indexed-db helpers to work around an instance instead of being free
functions
2. move our existing asset stuff over to the new approach
3. add a new hook in `sync-react` to create a demo store with the new
assets
### Change type
- [x] `api`
### Release notes
Introduce a new `assets` option for the store, describing how to save
and retrieve asset blobs like images & videos from e.g. a user-content
CDN. These are accessible through `editor.uploadAsset` and
`editor.resolveAssetUrl`. This supplements the existing
`registerExternalAssetHandler` API: `registerExternalAssetHandler` is
for customising metadata extraction, and should call
`editor.uploadAsset` to save assets. Existing
`registerExternalAssetHandler` calls will still work, but if you're only
using them to configure uploads and don't want to customise metadata
extraction, consider switching to the new `assets` store prop.
2024-07-10 13:00:18 +00:00
|
|
|
import { TLAsset } from './records/TLAsset'
|
Independent instance state persistence (#1493)
This PR
- Removes UserDocumentRecordType
- moving isSnapMode to user preferences
- moving isGridMode and isPenMode to InstanceRecordType
- deleting the other properties which are no longer needed.
- Creates a separate pipeline for persisting instance state.
Previously the instance state records were stored alongside the document
state records, and in order to load the state for a particular instance
(in our case, a particular tab) you needed to pass the 'instanceId'
prop. This prop ended up totally pervading the public API and people ran
into all kinds of issues with it, e.g. using the same instance id in
multiple editor instances.
There was also an issue whereby it was hard for us to clean up old
instance state so the idb table ended up bloating over time.
This PR makes it so that rather than passing an instanceId, you load the
instance state yourself while creating the store. It provides tools to
make that easy.
- Undoes the assumption that we might have more than one instance's
state in the store.
- Like `document`, `instance` now has a singleton id
`instance:instance`.
- Page state ids and camera ids are no longer random, but rather derive
from the page they belong to. This is like having a foreign primary key
in SQL databases. It's something i'd love to support fully as part of
the RecordType/Store api.
Tests to do
- [x] Test Migrations
- [x] Test Store.listen filtering
- [x] Make type sets in Store public and readonly
- [x] Test RecordType.createId
- [x] Test Instance state snapshot loading/exporting
- [x] Manual test File I/O
- [x] Manual test Vscode extension with multiple tabs
- [x] Audit usages of store.query
- [x] Audit usages of changed types: InstanceRecordType, 'instance',
InstancePageStateRecordType, 'instance_page_state', 'user_document',
'camera', CameraRecordType, InstancePresenceRecordType,
'instance_presence'
- [x] Test user preferences
- [x] Manual test isSnapMode and isGridMode and isPenMode
- [ ] Test indexedDb functions
- [x] Add instanceId stuff back
### Change Type
- [x] `major` — Breaking Change
### Test Plan
1. Add a step-by-step description of how to test your PR here.
2.
- [ ] Unit Tests
- [ ] Webdriver tests
### Release Notes
- Add a brief release note for your PR here.
2023-06-05 14:11:07 +00:00
|
|
|
import { CameraRecordType, TLCameraId } from './records/TLCamera'
|
2023-05-26 13:37:59 +00:00
|
|
|
import { DocumentRecordType, TLDOCUMENT_ID } from './records/TLDocument'
|
2023-06-16 10:33:47 +00:00
|
|
|
import { TLINSTANCE_ID } from './records/TLInstance'
|
Independent instance state persistence (#1493)
This PR
- Removes UserDocumentRecordType
- moving isSnapMode to user preferences
- moving isGridMode and isPenMode to InstanceRecordType
- deleting the other properties which are no longer needed.
- Creates a separate pipeline for persisting instance state.
Previously the instance state records were stored alongside the document
state records, and in order to load the state for a particular instance
(in our case, a particular tab) you needed to pass the 'instanceId'
prop. This prop ended up totally pervading the public API and people ran
into all kinds of issues with it, e.g. using the same instance id in
multiple editor instances.
There was also an issue whereby it was hard for us to clean up old
instance state so the idb table ended up bloating over time.
This PR makes it so that rather than passing an instanceId, you load the
instance state yourself while creating the store. It provides tools to
make that easy.
- Undoes the assumption that we might have more than one instance's
state in the store.
- Like `document`, `instance` now has a singleton id
`instance:instance`.
- Page state ids and camera ids are no longer random, but rather derive
from the page they belong to. This is like having a foreign primary key
in SQL databases. It's something i'd love to support fully as part of
the RecordType/Store api.
Tests to do
- [x] Test Migrations
- [x] Test Store.listen filtering
- [x] Make type sets in Store public and readonly
- [x] Test RecordType.createId
- [x] Test Instance state snapshot loading/exporting
- [x] Manual test File I/O
- [x] Manual test Vscode extension with multiple tabs
- [x] Audit usages of store.query
- [x] Audit usages of changed types: InstanceRecordType, 'instance',
InstancePageStateRecordType, 'instance_page_state', 'user_document',
'camera', CameraRecordType, InstancePresenceRecordType,
'instance_presence'
- [x] Test user preferences
- [x] Manual test isSnapMode and isGridMode and isPenMode
- [ ] Test indexedDb functions
- [x] Add instanceId stuff back
### Change Type
- [x] `major` — Breaking Change
### Test Plan
1. Add a step-by-step description of how to test your PR here.
2.
- [ ] Unit Tests
- [ ] Webdriver tests
### Release Notes
- Add a brief release note for your PR here.
2023-06-05 14:11:07 +00:00
|
|
|
import { PageRecordType, TLPageId } from './records/TLPage'
|
|
|
|
import { InstancePageStateRecordType, TLInstancePageStateId } from './records/TLPageState'
|
2023-05-26 13:37:59 +00:00
|
|
|
import { PointerRecordType, TLPOINTER_ID } from './records/TLPointer'
|
2023-06-03 20:46:53 +00:00
|
|
|
import { TLRecord } from './records/TLRecord'
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
function sortByIndex<T extends { index: string }>(a: T, b: T) {
|
|
|
|
if (a.index < b.index) {
|
|
|
|
return -1
|
|
|
|
} else if (a.index > b.index) {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
function redactRecordForErrorReporting(record: any) {
|
|
|
|
if (record.typeName === 'asset') {
|
|
|
|
if ('src' in record) {
|
|
|
|
record.src = '<redacted>'
|
|
|
|
}
|
|
|
|
|
|
|
|
if ('src' in record.props) {
|
|
|
|
record.props.src = '<redacted>'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @public */
|
|
|
|
export type TLStoreSchema = StoreSchema<TLRecord, TLStoreProps>
|
|
|
|
|
|
|
|
/** @public */
|
2023-06-27 12:25:55 +00:00
|
|
|
export type TLSerializedStore = SerializedStore<TLRecord>
|
|
|
|
|
|
|
|
/** @public */
|
2023-09-08 17:04:53 +00:00
|
|
|
export type TLStoreSnapshot = StoreSnapshot<TLRecord>
|
2023-04-25 11:01:25 +00:00
|
|
|
|
[1/4] Blob storage in TLStore (#4068)
Reworks the store to include information about how blob assets
(images/videos) are stored/retrieved. This replaces the old
internal-only `assetOptions` prop, and supplements the existing
`registerExternalAssetHandler` API.
Previously, `registerExternalAssetHandler` had two responsibilities:
1. Extracting asset metadata
2. Uploading the asset and returning its URL
Existing `registerExternalAssetHandler` implementation will still work,
but now uploading is the responsibility of a new `editor.uploadAsset`
method which calls the new store-based upload method. Our default asset
handlers extract metadata, then call that new API. I think this is a
pretty big improvement over what we had before: overriding uploads was a
pretty common ask, but doing so meant having to copy paste our metadata
extraction which felt pretty fragile. Just in this codebase, we had a
bunch of very slightly different metadata extraction code-paths that had
been copy-pasted around then diverged over time. Now, you can change how
uploads work without having to mess with metadata extraction and
vice-versa.
As part of this we also:
1. merge the old separate asset indexeddb store with the main one.
because this warrants some pretty big migration stuff, i refactored our
indexed-db helpers to work around an instance instead of being free
functions
2. move our existing asset stuff over to the new approach
3. add a new hook in `sync-react` to create a demo store with the new
assets
### Change type
- [x] `api`
### Release notes
Introduce a new `assets` option for the store, describing how to save
and retrieve asset blobs like images & videos from e.g. a user-content
CDN. These are accessible through `editor.uploadAsset` and
`editor.resolveAssetUrl`. This supplements the existing
`registerExternalAssetHandler` API: `registerExternalAssetHandler` is
for customising metadata extraction, and should call
`editor.uploadAsset` to save assets. Existing
`registerExternalAssetHandler` calls will still work, but if you're only
using them to configure uploads and don't want to customise metadata
extraction, consider switching to the new `assets` store prop.
2024-07-10 13:00:18 +00:00
|
|
|
/** @public */
|
|
|
|
export interface TLAssetContext {
|
|
|
|
screenScale: number
|
|
|
|
steppedScreenScale: number
|
|
|
|
dpr: number
|
|
|
|
networkEffectiveType: string | null
|
|
|
|
shouldResolveToOriginal: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A `TLAssetStore` sits alongside the main {@link TLStore} and is responsible for storing and
|
|
|
|
* retrieving large assets such as images. Generally, this should be part of a wider sync system:
|
|
|
|
*
|
|
|
|
* - By default, the store is in-memory only, so `TLAssetStore` converts images to data URLs
|
|
|
|
* - When using
|
|
|
|
* {@link @tldraw/editor#TldrawEditorWithoutStoreProps.persistenceKey | `persistenceKey`}, the
|
|
|
|
* store is synced to the browser's local IndexedDB, so `TLAssetStore` stores images there too
|
|
|
|
* - When using a multiplayer sync server, you would implement `TLAssetStore` to upload images to
|
|
|
|
* e.g. an S3 bucket.
|
|
|
|
*
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export interface TLAssetStore {
|
|
|
|
/**
|
|
|
|
* Upload an asset to your storage, returning a URL that can be used to refer to the asset
|
|
|
|
* long-term.
|
|
|
|
*
|
|
|
|
* @param asset - Information & metadata about the asset being uploaded
|
|
|
|
* @param file - The `File` to be uploaded
|
|
|
|
* @returns A promise that resolves to the URL of the uploaded asset
|
|
|
|
*/
|
|
|
|
upload(asset: TLAsset, file: File): Promise<string>
|
|
|
|
/**
|
|
|
|
* Resolve an asset to a URL. This is used when rendering the asset in the editor. By default,
|
|
|
|
* this will just use `asset.props.src`, the URL returned by `upload()`. This can be used to
|
|
|
|
* rewrite that URL to add access credentials, or optimized the asset for how it's currently
|
|
|
|
* being displayed using the {@link TLAssetContext | information provided}.
|
|
|
|
*
|
|
|
|
* @param asset - the asset being resolved
|
|
|
|
* @param ctx - information about the current environment and where the asset is being used
|
|
|
|
* @returns The URL of the resolved asset, or `null` if the asset is not available
|
|
|
|
*/
|
|
|
|
resolve(asset: TLAsset, ctx: TLAssetContext): Promise<string | null> | string | null
|
|
|
|
}
|
|
|
|
|
2023-04-25 11:01:25 +00:00
|
|
|
/** @public */
|
2024-05-22 15:55:49 +00:00
|
|
|
export interface TLStoreProps {
|
2023-06-01 18:46:26 +00:00
|
|
|
defaultName: string
|
[1/4] Blob storage in TLStore (#4068)
Reworks the store to include information about how blob assets
(images/videos) are stored/retrieved. This replaces the old
internal-only `assetOptions` prop, and supplements the existing
`registerExternalAssetHandler` API.
Previously, `registerExternalAssetHandler` had two responsibilities:
1. Extracting asset metadata
2. Uploading the asset and returning its URL
Existing `registerExternalAssetHandler` implementation will still work,
but now uploading is the responsibility of a new `editor.uploadAsset`
method which calls the new store-based upload method. Our default asset
handlers extract metadata, then call that new API. I think this is a
pretty big improvement over what we had before: overriding uploads was a
pretty common ask, but doing so meant having to copy paste our metadata
extraction which felt pretty fragile. Just in this codebase, we had a
bunch of very slightly different metadata extraction code-paths that had
been copy-pasted around then diverged over time. Now, you can change how
uploads work without having to mess with metadata extraction and
vice-versa.
As part of this we also:
1. merge the old separate asset indexeddb store with the main one.
because this warrants some pretty big migration stuff, i refactored our
indexed-db helpers to work around an instance instead of being free
functions
2. move our existing asset stuff over to the new approach
3. add a new hook in `sync-react` to create a demo store with the new
assets
### Change type
- [x] `api`
### Release notes
Introduce a new `assets` option for the store, describing how to save
and retrieve asset blobs like images & videos from e.g. a user-content
CDN. These are accessible through `editor.uploadAsset` and
`editor.resolveAssetUrl`. This supplements the existing
`registerExternalAssetHandler` API: `registerExternalAssetHandler` is
for customising metadata extraction, and should call
`editor.uploadAsset` to save assets. Existing
`registerExternalAssetHandler` calls will still work, but if you're only
using them to configure uploads and don't want to customise metadata
extraction, consider switching to the new `assets` store prop.
2024-07-10 13:00:18 +00:00
|
|
|
assets: TLAssetStore
|
2024-07-10 13:15:44 +00:00
|
|
|
/**
|
|
|
|
* Called an {@link @tldraw/editor#Editor} connected to this store is mounted.
|
|
|
|
*/
|
|
|
|
onEditorMount: (editor: unknown) => void | (() => void)
|
2024-07-10 15:46:09 +00:00
|
|
|
multiplayerStatus: Signal<'online' | 'offline'> | null
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** @public */
|
|
|
|
export type TLStore = Store<TLRecord, TLStoreProps>
|
|
|
|
|
|
|
|
/** @public */
|
|
|
|
export const onValidationFailure: StoreSchemaOptions<
|
|
|
|
TLRecord,
|
|
|
|
TLStoreProps
|
|
|
|
>['onValidationFailure'] = ({ error, phase, record, recordBefore }): TLRecord => {
|
|
|
|
const isExistingValidationIssue =
|
|
|
|
// if we're initializing the store for the first time, we should
|
|
|
|
// allow invalid records so people can load old buggy data:
|
|
|
|
phase === 'initialize'
|
|
|
|
|
|
|
|
annotateError(error, {
|
|
|
|
tags: {
|
|
|
|
origin: 'store.validateRecord',
|
|
|
|
storePhase: phase,
|
|
|
|
isExistingValidationIssue,
|
|
|
|
},
|
|
|
|
extras: {
|
|
|
|
recordBefore: recordBefore
|
|
|
|
? redactRecordForErrorReporting(structuredClone(recordBefore))
|
|
|
|
: undefined,
|
|
|
|
recordAfter: redactRecordForErrorReporting(structuredClone(record)),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
throw error
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDefaultPages() {
|
2023-10-13 11:18:07 +00:00
|
|
|
return [
|
2024-02-14 17:53:30 +00:00
|
|
|
PageRecordType.create({
|
|
|
|
id: 'page:page' as TLPageId,
|
|
|
|
name: 'Page 1',
|
|
|
|
index: 'a1' as IndexKey,
|
|
|
|
meta: {},
|
|
|
|
}),
|
2023-10-13 11:18:07 +00:00
|
|
|
]
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** @internal */
|
[1/4] Blob storage in TLStore (#4068)
Reworks the store to include information about how blob assets
(images/videos) are stored/retrieved. This replaces the old
internal-only `assetOptions` prop, and supplements the existing
`registerExternalAssetHandler` API.
Previously, `registerExternalAssetHandler` had two responsibilities:
1. Extracting asset metadata
2. Uploading the asset and returning its URL
Existing `registerExternalAssetHandler` implementation will still work,
but now uploading is the responsibility of a new `editor.uploadAsset`
method which calls the new store-based upload method. Our default asset
handlers extract metadata, then call that new API. I think this is a
pretty big improvement over what we had before: overriding uploads was a
pretty common ask, but doing so meant having to copy paste our metadata
extraction which felt pretty fragile. Just in this codebase, we had a
bunch of very slightly different metadata extraction code-paths that had
been copy-pasted around then diverged over time. Now, you can change how
uploads work without having to mess with metadata extraction and
vice-versa.
As part of this we also:
1. merge the old separate asset indexeddb store with the main one.
because this warrants some pretty big migration stuff, i refactored our
indexed-db helpers to work around an instance instead of being free
functions
2. move our existing asset stuff over to the new approach
3. add a new hook in `sync-react` to create a demo store with the new
assets
### Change type
- [x] `api`
### Release notes
Introduce a new `assets` option for the store, describing how to save
and retrieve asset blobs like images & videos from e.g. a user-content
CDN. These are accessible through `editor.uploadAsset` and
`editor.resolveAssetUrl`. This supplements the existing
`registerExternalAssetHandler` API: `registerExternalAssetHandler` is
for customising metadata extraction, and should call
`editor.uploadAsset` to save assets. Existing
`registerExternalAssetHandler` calls will still work, but if you're only
using them to configure uploads and don't want to customise metadata
extraction, consider switching to the new `assets` store prop.
2024-07-10 13:00:18 +00:00
|
|
|
export function createIntegrityChecker(store: Store<TLRecord, TLStoreProps>): () => void {
|
Independent instance state persistence (#1493)
This PR
- Removes UserDocumentRecordType
- moving isSnapMode to user preferences
- moving isGridMode and isPenMode to InstanceRecordType
- deleting the other properties which are no longer needed.
- Creates a separate pipeline for persisting instance state.
Previously the instance state records were stored alongside the document
state records, and in order to load the state for a particular instance
(in our case, a particular tab) you needed to pass the 'instanceId'
prop. This prop ended up totally pervading the public API and people ran
into all kinds of issues with it, e.g. using the same instance id in
multiple editor instances.
There was also an issue whereby it was hard for us to clean up old
instance state so the idb table ended up bloating over time.
This PR makes it so that rather than passing an instanceId, you load the
instance state yourself while creating the store. It provides tools to
make that easy.
- Undoes the assumption that we might have more than one instance's
state in the store.
- Like `document`, `instance` now has a singleton id
`instance:instance`.
- Page state ids and camera ids are no longer random, but rather derive
from the page they belong to. This is like having a foreign primary key
in SQL databases. It's something i'd love to support fully as part of
the RecordType/Store api.
Tests to do
- [x] Test Migrations
- [x] Test Store.listen filtering
- [x] Make type sets in Store public and readonly
- [x] Test RecordType.createId
- [x] Test Instance state snapshot loading/exporting
- [x] Manual test File I/O
- [x] Manual test Vscode extension with multiple tabs
- [x] Audit usages of store.query
- [x] Audit usages of changed types: InstanceRecordType, 'instance',
InstancePageStateRecordType, 'instance_page_state', 'user_document',
'camera', CameraRecordType, InstancePresenceRecordType,
'instance_presence'
- [x] Test user preferences
- [x] Manual test isSnapMode and isGridMode and isPenMode
- [ ] Test indexedDb functions
- [x] Add instanceId stuff back
### Change Type
- [x] `major` — Breaking Change
### Test Plan
1. Add a step-by-step description of how to test your PR here.
2.
- [ ] Unit Tests
- [ ] Webdriver tests
### Release Notes
- Add a brief release note for your PR here.
2023-06-05 14:11:07 +00:00
|
|
|
const $pageIds = store.query.ids('page')
|
2023-05-12 11:39:36 +00:00
|
|
|
|
|
|
|
const ensureStoreIsUsable = (): void => {
|
|
|
|
// make sure we have exactly one document
|
|
|
|
if (!store.has(TLDOCUMENT_ID)) {
|
2023-06-01 18:46:26 +00:00
|
|
|
store.put([DocumentRecordType.create({ id: TLDOCUMENT_ID, name: store.props.defaultName })])
|
2023-05-12 11:39:36 +00:00
|
|
|
return ensureStoreIsUsable()
|
|
|
|
}
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-05-25 09:54:29 +00:00
|
|
|
if (!store.has(TLPOINTER_ID)) {
|
2023-05-26 13:37:59 +00:00
|
|
|
store.put([PointerRecordType.create({ id: TLPOINTER_ID })])
|
2023-05-25 09:54:29 +00:00
|
|
|
return ensureStoreIsUsable()
|
|
|
|
}
|
|
|
|
|
2023-05-12 11:39:36 +00:00
|
|
|
// make sure there is at least one page
|
2023-11-13 11:51:22 +00:00
|
|
|
const pageIds = $pageIds.get()
|
Independent instance state persistence (#1493)
This PR
- Removes UserDocumentRecordType
- moving isSnapMode to user preferences
- moving isGridMode and isPenMode to InstanceRecordType
- deleting the other properties which are no longer needed.
- Creates a separate pipeline for persisting instance state.
Previously the instance state records were stored alongside the document
state records, and in order to load the state for a particular instance
(in our case, a particular tab) you needed to pass the 'instanceId'
prop. This prop ended up totally pervading the public API and people ran
into all kinds of issues with it, e.g. using the same instance id in
multiple editor instances.
There was also an issue whereby it was hard for us to clean up old
instance state so the idb table ended up bloating over time.
This PR makes it so that rather than passing an instanceId, you load the
instance state yourself while creating the store. It provides tools to
make that easy.
- Undoes the assumption that we might have more than one instance's
state in the store.
- Like `document`, `instance` now has a singleton id
`instance:instance`.
- Page state ids and camera ids are no longer random, but rather derive
from the page they belong to. This is like having a foreign primary key
in SQL databases. It's something i'd love to support fully as part of
the RecordType/Store api.
Tests to do
- [x] Test Migrations
- [x] Test Store.listen filtering
- [x] Make type sets in Store public and readonly
- [x] Test RecordType.createId
- [x] Test Instance state snapshot loading/exporting
- [x] Manual test File I/O
- [x] Manual test Vscode extension with multiple tabs
- [x] Audit usages of store.query
- [x] Audit usages of changed types: InstanceRecordType, 'instance',
InstancePageStateRecordType, 'instance_page_state', 'user_document',
'camera', CameraRecordType, InstancePresenceRecordType,
'instance_presence'
- [x] Test user preferences
- [x] Manual test isSnapMode and isGridMode and isPenMode
- [ ] Test indexedDb functions
- [x] Add instanceId stuff back
### Change Type
- [x] `major` — Breaking Change
### Test Plan
1. Add a step-by-step description of how to test your PR here.
2.
- [ ] Unit Tests
- [ ] Webdriver tests
### Release Notes
- Add a brief release note for your PR here.
2023-06-05 14:11:07 +00:00
|
|
|
if (pageIds.size === 0) {
|
2023-05-12 11:39:36 +00:00
|
|
|
store.put(getDefaultPages())
|
|
|
|
return ensureStoreIsUsable()
|
|
|
|
}
|
2023-04-25 11:01:25 +00:00
|
|
|
|
Independent instance state persistence (#1493)
This PR
- Removes UserDocumentRecordType
- moving isSnapMode to user preferences
- moving isGridMode and isPenMode to InstanceRecordType
- deleting the other properties which are no longer needed.
- Creates a separate pipeline for persisting instance state.
Previously the instance state records were stored alongside the document
state records, and in order to load the state for a particular instance
(in our case, a particular tab) you needed to pass the 'instanceId'
prop. This prop ended up totally pervading the public API and people ran
into all kinds of issues with it, e.g. using the same instance id in
multiple editor instances.
There was also an issue whereby it was hard for us to clean up old
instance state so the idb table ended up bloating over time.
This PR makes it so that rather than passing an instanceId, you load the
instance state yourself while creating the store. It provides tools to
make that easy.
- Undoes the assumption that we might have more than one instance's
state in the store.
- Like `document`, `instance` now has a singleton id
`instance:instance`.
- Page state ids and camera ids are no longer random, but rather derive
from the page they belong to. This is like having a foreign primary key
in SQL databases. It's something i'd love to support fully as part of
the RecordType/Store api.
Tests to do
- [x] Test Migrations
- [x] Test Store.listen filtering
- [x] Make type sets in Store public and readonly
- [x] Test RecordType.createId
- [x] Test Instance state snapshot loading/exporting
- [x] Manual test File I/O
- [x] Manual test Vscode extension with multiple tabs
- [x] Audit usages of store.query
- [x] Audit usages of changed types: InstanceRecordType, 'instance',
InstancePageStateRecordType, 'instance_page_state', 'user_document',
'camera', CameraRecordType, InstancePresenceRecordType,
'instance_presence'
- [x] Test user preferences
- [x] Manual test isSnapMode and isGridMode and isPenMode
- [ ] Test indexedDb functions
- [x] Add instanceId stuff back
### Change Type
- [x] `major` — Breaking Change
### Test Plan
1. Add a step-by-step description of how to test your PR here.
2.
- [ ] Unit Tests
- [ ] Webdriver tests
### Release Notes
- Add a brief release note for your PR here.
2023-06-05 14:11:07 +00:00
|
|
|
const getFirstPageId = () => [...pageIds].map((id) => store.get(id)!).sort(sortByIndex)[0].id!
|
|
|
|
|
2023-05-12 11:39:36 +00:00
|
|
|
// make sure we have state for the current user's current tab
|
Independent instance state persistence (#1493)
This PR
- Removes UserDocumentRecordType
- moving isSnapMode to user preferences
- moving isGridMode and isPenMode to InstanceRecordType
- deleting the other properties which are no longer needed.
- Creates a separate pipeline for persisting instance state.
Previously the instance state records were stored alongside the document
state records, and in order to load the state for a particular instance
(in our case, a particular tab) you needed to pass the 'instanceId'
prop. This prop ended up totally pervading the public API and people ran
into all kinds of issues with it, e.g. using the same instance id in
multiple editor instances.
There was also an issue whereby it was hard for us to clean up old
instance state so the idb table ended up bloating over time.
This PR makes it so that rather than passing an instanceId, you load the
instance state yourself while creating the store. It provides tools to
make that easy.
- Undoes the assumption that we might have more than one instance's
state in the store.
- Like `document`, `instance` now has a singleton id
`instance:instance`.
- Page state ids and camera ids are no longer random, but rather derive
from the page they belong to. This is like having a foreign primary key
in SQL databases. It's something i'd love to support fully as part of
the RecordType/Store api.
Tests to do
- [x] Test Migrations
- [x] Test Store.listen filtering
- [x] Make type sets in Store public and readonly
- [x] Test RecordType.createId
- [x] Test Instance state snapshot loading/exporting
- [x] Manual test File I/O
- [x] Manual test Vscode extension with multiple tabs
- [x] Audit usages of store.query
- [x] Audit usages of changed types: InstanceRecordType, 'instance',
InstancePageStateRecordType, 'instance_page_state', 'user_document',
'camera', CameraRecordType, InstancePresenceRecordType,
'instance_presence'
- [x] Test user preferences
- [x] Manual test isSnapMode and isGridMode and isPenMode
- [ ] Test indexedDb functions
- [x] Add instanceId stuff back
### Change Type
- [x] `major` — Breaking Change
### Test Plan
1. Add a step-by-step description of how to test your PR here.
2.
- [ ] Unit Tests
- [ ] Webdriver tests
### Release Notes
- Add a brief release note for your PR here.
2023-06-05 14:11:07 +00:00
|
|
|
const instanceState = store.get(TLINSTANCE_ID)
|
2023-05-12 11:39:36 +00:00
|
|
|
if (!instanceState) {
|
|
|
|
store.put([
|
2023-06-16 10:33:47 +00:00
|
|
|
store.schema.types.instance.create({
|
Independent instance state persistence (#1493)
This PR
- Removes UserDocumentRecordType
- moving isSnapMode to user preferences
- moving isGridMode and isPenMode to InstanceRecordType
- deleting the other properties which are no longer needed.
- Creates a separate pipeline for persisting instance state.
Previously the instance state records were stored alongside the document
state records, and in order to load the state for a particular instance
(in our case, a particular tab) you needed to pass the 'instanceId'
prop. This prop ended up totally pervading the public API and people ran
into all kinds of issues with it, e.g. using the same instance id in
multiple editor instances.
There was also an issue whereby it was hard for us to clean up old
instance state so the idb table ended up bloating over time.
This PR makes it so that rather than passing an instanceId, you load the
instance state yourself while creating the store. It provides tools to
make that easy.
- Undoes the assumption that we might have more than one instance's
state in the store.
- Like `document`, `instance` now has a singleton id
`instance:instance`.
- Page state ids and camera ids are no longer random, but rather derive
from the page they belong to. This is like having a foreign primary key
in SQL databases. It's something i'd love to support fully as part of
the RecordType/Store api.
Tests to do
- [x] Test Migrations
- [x] Test Store.listen filtering
- [x] Make type sets in Store public and readonly
- [x] Test RecordType.createId
- [x] Test Instance state snapshot loading/exporting
- [x] Manual test File I/O
- [x] Manual test Vscode extension with multiple tabs
- [x] Audit usages of store.query
- [x] Audit usages of changed types: InstanceRecordType, 'instance',
InstancePageStateRecordType, 'instance_page_state', 'user_document',
'camera', CameraRecordType, InstancePresenceRecordType,
'instance_presence'
- [x] Test user preferences
- [x] Manual test isSnapMode and isGridMode and isPenMode
- [ ] Test indexedDb functions
- [x] Add instanceId stuff back
### Change Type
- [x] `major` — Breaking Change
### Test Plan
1. Add a step-by-step description of how to test your PR here.
2.
- [ ] Unit Tests
- [ ] Webdriver tests
### Release Notes
- Add a brief release note for your PR here.
2023-06-05 14:11:07 +00:00
|
|
|
id: TLINSTANCE_ID,
|
|
|
|
currentPageId: getFirstPageId(),
|
2023-05-12 11:39:36 +00:00
|
|
|
exportBackground: true,
|
|
|
|
}),
|
|
|
|
])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-05-12 11:39:36 +00:00
|
|
|
return ensureStoreIsUsable()
|
Independent instance state persistence (#1493)
This PR
- Removes UserDocumentRecordType
- moving isSnapMode to user preferences
- moving isGridMode and isPenMode to InstanceRecordType
- deleting the other properties which are no longer needed.
- Creates a separate pipeline for persisting instance state.
Previously the instance state records were stored alongside the document
state records, and in order to load the state for a particular instance
(in our case, a particular tab) you needed to pass the 'instanceId'
prop. This prop ended up totally pervading the public API and people ran
into all kinds of issues with it, e.g. using the same instance id in
multiple editor instances.
There was also an issue whereby it was hard for us to clean up old
instance state so the idb table ended up bloating over time.
This PR makes it so that rather than passing an instanceId, you load the
instance state yourself while creating the store. It provides tools to
make that easy.
- Undoes the assumption that we might have more than one instance's
state in the store.
- Like `document`, `instance` now has a singleton id
`instance:instance`.
- Page state ids and camera ids are no longer random, but rather derive
from the page they belong to. This is like having a foreign primary key
in SQL databases. It's something i'd love to support fully as part of
the RecordType/Store api.
Tests to do
- [x] Test Migrations
- [x] Test Store.listen filtering
- [x] Make type sets in Store public and readonly
- [x] Test RecordType.createId
- [x] Test Instance state snapshot loading/exporting
- [x] Manual test File I/O
- [x] Manual test Vscode extension with multiple tabs
- [x] Audit usages of store.query
- [x] Audit usages of changed types: InstanceRecordType, 'instance',
InstancePageStateRecordType, 'instance_page_state', 'user_document',
'camera', CameraRecordType, InstancePresenceRecordType,
'instance_presence'
- [x] Test user preferences
- [x] Manual test isSnapMode and isGridMode and isPenMode
- [ ] Test indexedDb functions
- [x] Add instanceId stuff back
### Change Type
- [x] `major` — Breaking Change
### Test Plan
1. Add a step-by-step description of how to test your PR here.
2.
- [ ] Unit Tests
- [ ] Webdriver tests
### Release Notes
- Add a brief release note for your PR here.
2023-06-05 14:11:07 +00:00
|
|
|
} else if (!pageIds.has(instanceState.currentPageId)) {
|
|
|
|
store.put([{ ...instanceState, currentPageId: getFirstPageId() }])
|
2023-05-12 11:39:36 +00:00
|
|
|
return ensureStoreIsUsable()
|
|
|
|
}
|
2023-04-25 11:01:25 +00:00
|
|
|
|
Independent instance state persistence (#1493)
This PR
- Removes UserDocumentRecordType
- moving isSnapMode to user preferences
- moving isGridMode and isPenMode to InstanceRecordType
- deleting the other properties which are no longer needed.
- Creates a separate pipeline for persisting instance state.
Previously the instance state records were stored alongside the document
state records, and in order to load the state for a particular instance
(in our case, a particular tab) you needed to pass the 'instanceId'
prop. This prop ended up totally pervading the public API and people ran
into all kinds of issues with it, e.g. using the same instance id in
multiple editor instances.
There was also an issue whereby it was hard for us to clean up old
instance state so the idb table ended up bloating over time.
This PR makes it so that rather than passing an instanceId, you load the
instance state yourself while creating the store. It provides tools to
make that easy.
- Undoes the assumption that we might have more than one instance's
state in the store.
- Like `document`, `instance` now has a singleton id
`instance:instance`.
- Page state ids and camera ids are no longer random, but rather derive
from the page they belong to. This is like having a foreign primary key
in SQL databases. It's something i'd love to support fully as part of
the RecordType/Store api.
Tests to do
- [x] Test Migrations
- [x] Test Store.listen filtering
- [x] Make type sets in Store public and readonly
- [x] Test RecordType.createId
- [x] Test Instance state snapshot loading/exporting
- [x] Manual test File I/O
- [x] Manual test Vscode extension with multiple tabs
- [x] Audit usages of store.query
- [x] Audit usages of changed types: InstanceRecordType, 'instance',
InstancePageStateRecordType, 'instance_page_state', 'user_document',
'camera', CameraRecordType, InstancePresenceRecordType,
'instance_presence'
- [x] Test user preferences
- [x] Manual test isSnapMode and isGridMode and isPenMode
- [ ] Test indexedDb functions
- [x] Add instanceId stuff back
### Change Type
- [x] `major` — Breaking Change
### Test Plan
1. Add a step-by-step description of how to test your PR here.
2.
- [ ] Unit Tests
- [ ] Webdriver tests
### Release Notes
- Add a brief release note for your PR here.
2023-06-05 14:11:07 +00:00
|
|
|
// make sure we have page states and cameras for all the pages
|
|
|
|
const missingPageStateIds = new Set<TLInstancePageStateId>()
|
|
|
|
const missingCameraIds = new Set<TLCameraId>()
|
|
|
|
for (const id of pageIds) {
|
|
|
|
const pageStateId = InstancePageStateRecordType.createId(id)
|
|
|
|
if (!store.has(pageStateId)) {
|
|
|
|
missingPageStateIds.add(pageStateId)
|
2023-05-12 11:39:36 +00:00
|
|
|
}
|
Independent instance state persistence (#1493)
This PR
- Removes UserDocumentRecordType
- moving isSnapMode to user preferences
- moving isGridMode and isPenMode to InstanceRecordType
- deleting the other properties which are no longer needed.
- Creates a separate pipeline for persisting instance state.
Previously the instance state records were stored alongside the document
state records, and in order to load the state for a particular instance
(in our case, a particular tab) you needed to pass the 'instanceId'
prop. This prop ended up totally pervading the public API and people ran
into all kinds of issues with it, e.g. using the same instance id in
multiple editor instances.
There was also an issue whereby it was hard for us to clean up old
instance state so the idb table ended up bloating over time.
This PR makes it so that rather than passing an instanceId, you load the
instance state yourself while creating the store. It provides tools to
make that easy.
- Undoes the assumption that we might have more than one instance's
state in the store.
- Like `document`, `instance` now has a singleton id
`instance:instance`.
- Page state ids and camera ids are no longer random, but rather derive
from the page they belong to. This is like having a foreign primary key
in SQL databases. It's something i'd love to support fully as part of
the RecordType/Store api.
Tests to do
- [x] Test Migrations
- [x] Test Store.listen filtering
- [x] Make type sets in Store public and readonly
- [x] Test RecordType.createId
- [x] Test Instance state snapshot loading/exporting
- [x] Manual test File I/O
- [x] Manual test Vscode extension with multiple tabs
- [x] Audit usages of store.query
- [x] Audit usages of changed types: InstanceRecordType, 'instance',
InstancePageStateRecordType, 'instance_page_state', 'user_document',
'camera', CameraRecordType, InstancePresenceRecordType,
'instance_presence'
- [x] Test user preferences
- [x] Manual test isSnapMode and isGridMode and isPenMode
- [ ] Test indexedDb functions
- [x] Add instanceId stuff back
### Change Type
- [x] `major` — Breaking Change
### Test Plan
1. Add a step-by-step description of how to test your PR here.
2.
- [ ] Unit Tests
- [ ] Webdriver tests
### Release Notes
- Add a brief release note for your PR here.
2023-06-05 14:11:07 +00:00
|
|
|
const cameraId = CameraRecordType.createId(id)
|
|
|
|
if (!store.has(cameraId)) {
|
|
|
|
missingCameraIds.add(cameraId)
|
2023-05-12 11:39:36 +00:00
|
|
|
}
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
Independent instance state persistence (#1493)
This PR
- Removes UserDocumentRecordType
- moving isSnapMode to user preferences
- moving isGridMode and isPenMode to InstanceRecordType
- deleting the other properties which are no longer needed.
- Creates a separate pipeline for persisting instance state.
Previously the instance state records were stored alongside the document
state records, and in order to load the state for a particular instance
(in our case, a particular tab) you needed to pass the 'instanceId'
prop. This prop ended up totally pervading the public API and people ran
into all kinds of issues with it, e.g. using the same instance id in
multiple editor instances.
There was also an issue whereby it was hard for us to clean up old
instance state so the idb table ended up bloating over time.
This PR makes it so that rather than passing an instanceId, you load the
instance state yourself while creating the store. It provides tools to
make that easy.
- Undoes the assumption that we might have more than one instance's
state in the store.
- Like `document`, `instance` now has a singleton id
`instance:instance`.
- Page state ids and camera ids are no longer random, but rather derive
from the page they belong to. This is like having a foreign primary key
in SQL databases. It's something i'd love to support fully as part of
the RecordType/Store api.
Tests to do
- [x] Test Migrations
- [x] Test Store.listen filtering
- [x] Make type sets in Store public and readonly
- [x] Test RecordType.createId
- [x] Test Instance state snapshot loading/exporting
- [x] Manual test File I/O
- [x] Manual test Vscode extension with multiple tabs
- [x] Audit usages of store.query
- [x] Audit usages of changed types: InstanceRecordType, 'instance',
InstancePageStateRecordType, 'instance_page_state', 'user_document',
'camera', CameraRecordType, InstancePresenceRecordType,
'instance_presence'
- [x] Test user preferences
- [x] Manual test isSnapMode and isGridMode and isPenMode
- [ ] Test indexedDb functions
- [x] Add instanceId stuff back
### Change Type
- [x] `major` — Breaking Change
### Test Plan
1. Add a step-by-step description of how to test your PR here.
2.
- [ ] Unit Tests
- [ ] Webdriver tests
### Release Notes
- Add a brief release note for your PR here.
2023-06-05 14:11:07 +00:00
|
|
|
|
|
|
|
if (missingPageStateIds.size > 0) {
|
|
|
|
store.put(
|
|
|
|
[...missingPageStateIds].map((id) =>
|
|
|
|
InstancePageStateRecordType.create({
|
|
|
|
id,
|
|
|
|
pageId: InstancePageStateRecordType.parseId(id) as TLPageId,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if (missingCameraIds.size > 0) {
|
|
|
|
store.put([...missingCameraIds].map((id) => CameraRecordType.create({ id })))
|
|
|
|
}
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
2023-05-12 11:39:36 +00:00
|
|
|
|
|
|
|
return ensureStoreIsUsable
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|