{
  "version": 3,
  "sources": ["../../../../src/lib/utils/tldr/file.ts"],
  "sourcesContent": ["import {\n\tEditor,\n\tFileHelpers,\n\tMigrationFailureReason,\n\tMigrationResult,\n\tRecordId,\n\tResult,\n\tSerializedSchema,\n\tSerializedSchemaV1,\n\tSerializedSchemaV2,\n\tSerializedStore,\n\tT,\n\tTLAssetId,\n\tTLRecord,\n\tTLSchema,\n\tTLStore,\n\tUnknownRecord,\n\tcreateTLStore,\n\texhaustiveSwitchError,\n\tfetch,\n\ttransact,\n} from '@tldraw/editor'\nimport { TLUiToastsContextType } from '../../ui/context/toasts'\nimport { TLUiTranslationKey } from '../../ui/hooks/useTranslation/TLUiTranslationKey'\nimport { buildFromV1Document } from '../tldr/buildFromV1Document'\n\n/** @public */\nexport const TLDRAW_FILE_MIMETYPE = 'application/vnd.tldraw+json' as const\n\n/** @public */\nexport const TLDRAW_FILE_EXTENSION = '.tldr' as const\n\n// When incrementing this, you'll need to update parseTldrawJsonFile to handle\n// both your new changes and the old file format\nconst LATEST_TLDRAW_FILE_FORMAT_VERSION = 1\n\n/** @public */\nexport interface TldrawFile {\n\ttldrawFileFormatVersion: number\n\tschema: SerializedSchema\n\trecords: UnknownRecord[]\n}\n\nconst schemaV1 = T.object<SerializedSchemaV1>({\n\tschemaVersion: T.literal(1),\n\tstoreVersion: T.positiveInteger,\n\trecordVersions: T.dict(\n\t\tT.string,\n\t\tT.object({\n\t\t\tversion: T.positiveInteger,\n\t\t\tsubTypeVersions: T.dict(T.string, T.positiveInteger).optional(),\n\t\t\tsubTypeKey: T.string.optional(),\n\t\t})\n\t),\n})\n\nconst schemaV2 = T.object<SerializedSchemaV2>({\n\tschemaVersion: T.literal(2),\n\tsequences: T.dict(T.string, T.positiveInteger),\n})\n\nconst tldrawFileValidator: T.Validator<TldrawFile> = T.object({\n\ttldrawFileFormatVersion: T.nonZeroInteger,\n\tschema: T.numberUnion('schemaVersion', {\n\t\t1: schemaV1,\n\t\t2: schemaV2,\n\t}),\n\trecords: T.arrayOf(\n\t\tT.object({\n\t\t\tid: T.string as any as T.Validator<RecordId<any>>,\n\t\t\ttypeName: T.string,\n\t\t}).allowUnknownProperties()\n\t),\n})\n\n/** @public */\nexport function isV1File(data: any) {\n\ttry {\n\t\tif (data.document?.version) {\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/** @public */\nexport type TldrawFileParseError =\n\t| { type: 'v1File'; data: any }\n\t| { type: 'notATldrawFile'; cause: unknown }\n\t| { type: 'fileFormatVersionTooNew'; version: number }\n\t| { type: 'migrationFailed'; reason: MigrationFailureReason }\n\t| { type: 'invalidRecords'; cause: unknown }\n\n/** @public */\nexport function parseTldrawJsonFile({\n\tjson,\n\tschema,\n}: {\n\tschema: TLSchema\n\tjson: string\n}): Result<TLStore, TldrawFileParseError> {\n\t// first off, we parse .json file and check it matches the general shape of\n\t// a tldraw file\n\tlet data\n\ttry {\n\t\tdata = tldrawFileValidator.validate(JSON.parse(json))\n\t} catch (e) {\n\t\t// could be a v1 file!\n\t\ttry {\n\t\t\tdata = JSON.parse(json)\n\t\t\tif (isV1File(data)) {\n\t\t\t\treturn Result.err({ type: 'v1File', data })\n\t\t\t}\n\t\t} catch {\n\t\t\t// noop\n\t\t}\n\n\t\treturn Result.err({ type: 'notATldrawFile', cause: e })\n\t}\n\n\t// if the file format version isn't supported, we can't open it - it's\n\t// probably from a newer version of tldraw\n\tif (data.tldrawFileFormatVersion > LATEST_TLDRAW_FILE_FORMAT_VERSION) {\n\t\treturn Result.err({\n\t\t\ttype: 'fileFormatVersionTooNew',\n\t\t\tversion: data.tldrawFileFormatVersion,\n\t\t})\n\t}\n\n\t// even if the file version is up to date, it might contain old-format\n\t// records. lets create a store with the records and migrate it to the\n\t// latest version\n\tlet migrationResult: MigrationResult<SerializedStore<TLRecord>>\n\tlet storeSnapshot: SerializedStore<TLRecord>\n\ttry {\n\t\tconst records = pruneUnusedAssets(data.records as TLRecord[])\n\t\tstoreSnapshot = Object.fromEntries(records.map((r) => [r.id, r]))\n\t\tmigrationResult = schema.migrateStoreSnapshot({ store: storeSnapshot, schema: data.schema })\n\t} catch (e) {\n\t\t// junk data in the migration\n\t\treturn Result.err({ type: 'invalidRecords', cause: e })\n\t}\n\t// if the migration failed, we can't open the file\n\tif (migrationResult.type === 'error') {\n\t\treturn Result.err({ type: 'migrationFailed', reason: migrationResult.reason })\n\t}\n\n\t// at this stage, the store should have records at the latest versions, so\n\t// we should be able to validate them. if any of the records at this stage\n\t// are invalid, we don't open the file\n\ttry {\n\t\treturn Result.ok(\n\t\t\tcreateTLStore({\n\t\t\t\tsnapshot: { store: storeSnapshot, schema: data.schema },\n\t\t\t\tschema,\n\t\t\t})\n\t\t)\n\t} catch (e) {\n\t\t// junk data in the records (they're not validated yet!) could cause the\n\t\t// migrations to crash. We treat any throw from a migration as an\n\t\t// invalid record\n\t\treturn Result.err({ type: 'invalidRecords', cause: e })\n\t}\n}\n\nfunction pruneUnusedAssets(records: TLRecord[]) {\n\tconst usedAssets = new Set<TLAssetId>()\n\tfor (const record of records) {\n\t\tif (record.typeName === 'shape' && 'assetId' in record.props && record.props.assetId) {\n\t\t\tusedAssets.add(record.props.assetId)\n\t\t}\n\t}\n\treturn records.filter((r) => r.typeName !== 'asset' || usedAssets.has(r.id))\n}\n\n/** @public */\nexport async function serializeTldrawJson(editor: Editor): Promise<string> {\n\tconst records: TLRecord[] = []\n\tfor (const record of editor.store.allRecords()) {\n\t\tswitch (record.typeName) {\n\t\t\tcase 'asset':\n\t\t\t\tif (\n\t\t\t\t\trecord.type !== 'bookmark' &&\n\t\t\t\t\trecord.props.src &&\n\t\t\t\t\t!record.props.src.startsWith('data:')\n\t\t\t\t) {\n\t\t\t\t\tlet assetSrcToSave\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlet src = record.props.src\n\t\t\t\t\t\tif (!src.startsWith('http')) {\n\t\t\t\t\t\t\tsrc =\n\t\t\t\t\t\t\t\t(await editor.resolveAssetUrl(record.id, { shouldResolveToOriginal: true })) || ''\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// try to save the asset as a base64 string\n\t\t\t\t\t\tassetSrcToSave = await FileHelpers.blobToDataUrl(await (await fetch(src)).blob())\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// if that fails, just save the original src\n\t\t\t\t\t\tassetSrcToSave = record.props.src\n\t\t\t\t\t}\n\n\t\t\t\t\trecords.push({\n\t\t\t\t\t\t...record,\n\t\t\t\t\t\tprops: {\n\t\t\t\t\t\t\t...record.props,\n\t\t\t\t\t\t\tsrc: assetSrcToSave,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t} else {\n\t\t\t\t\trecords.push(record)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tdefault:\n\t\t\t\trecords.push(record)\n\t\t\t\tbreak\n\t\t}\n\t}\n\n\treturn JSON.stringify({\n\t\ttldrawFileFormatVersion: LATEST_TLDRAW_FILE_FORMAT_VERSION,\n\t\tschema: editor.store.schema.serialize(),\n\t\trecords: pruneUnusedAssets(records),\n\t})\n}\n\n/** @public */\nexport async function serializeTldrawJsonBlob(editor: Editor): Promise<Blob> {\n\treturn new Blob([await serializeTldrawJson(editor)], { type: TLDRAW_FILE_MIMETYPE })\n}\n\n/** @internal */\nexport async function parseAndLoadDocument(\n\teditor: Editor,\n\tdocument: string,\n\tmsg: (id: TLUiTranslationKey | Exclude<string, TLUiTranslationKey>) => string,\n\taddToast: TLUiToastsContextType['addToast'],\n\tonV1FileLoad?: () => void,\n\tforceDarkMode?: boolean\n) {\n\tconst parseFileResult = parseTldrawJsonFile({\n\t\tschema: editor.store.schema,\n\t\tjson: document,\n\t})\n\tif (!parseFileResult.ok) {\n\t\tlet description\n\t\tswitch (parseFileResult.error.type) {\n\t\t\tcase 'notATldrawFile':\n\t\t\t\teditor.annotateError(parseFileResult.error.cause, {\n\t\t\t\t\torigin: 'file-system.open.parse',\n\t\t\t\t\twillCrashApp: false,\n\t\t\t\t\ttags: { parseErrorType: parseFileResult.error.type },\n\t\t\t\t})\n\t\t\t\treportError(parseFileResult.error.cause)\n\t\t\t\tdescription = msg('file-system.file-open-error.not-a-tldraw-file')\n\t\t\t\tbreak\n\t\t\tcase 'fileFormatVersionTooNew':\n\t\t\t\tdescription = msg('file-system.file-open-error.file-format-version-too-new')\n\t\t\t\tbreak\n\t\t\tcase 'migrationFailed':\n\t\t\t\tif (parseFileResult.error.reason === MigrationFailureReason.TargetVersionTooNew) {\n\t\t\t\t\tdescription = msg('file-system.file-open-error.file-format-version-too-new')\n\t\t\t\t} else {\n\t\t\t\t\tdescription = msg('file-system.file-open-error.generic-corrupted-file')\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tcase 'invalidRecords':\n\t\t\t\teditor.annotateError(parseFileResult.error.cause, {\n\t\t\t\t\torigin: 'file-system.open.parse',\n\t\t\t\t\twillCrashApp: false,\n\t\t\t\t\ttags: { parseErrorType: parseFileResult.error.type },\n\t\t\t\t})\n\t\t\t\treportError(parseFileResult.error.cause)\n\t\t\t\tdescription = msg('file-system.file-open-error.generic-corrupted-file')\n\t\t\t\tbreak\n\t\t\tcase 'v1File': {\n\t\t\t\tbuildFromV1Document(editor, parseFileResult.error.data.document)\n\t\t\t\tonV1FileLoad?.()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\texhaustiveSwitchError(parseFileResult.error, 'type')\n\t\t}\n\t\taddToast({\n\t\t\ttitle: msg('file-system.file-open-error.title'),\n\t\t\tdescription,\n\t\t\tseverity: 'error',\n\t\t})\n\n\t\treturn\n\t}\n\n\t// tldraw file contain the full state of the app,\n\t// including ephemeral data. it up to the opener to\n\t// decide what to restore and what to retain. Here, we\n\t// just restore everything, so if the user has opened\n\t// this file before they'll get their camera etc.\n\t// restored. we could change this in the future.\n\ttransact(() => {\n\t\teditor.loadSnapshot(parseFileResult.value.getStoreSnapshot())\n\t\teditor.clearHistory()\n\n\t\tconst bounds = editor.getCurrentPageBounds()\n\t\tif (bounds) {\n\t\t\teditor.zoomToBounds(bounds, { targetZoom: 1, immediate: true })\n\t\t}\n\t})\n\n\tif (forceDarkMode) editor.user.updateUserPreferences({ colorScheme: 'dark' })\n}\n"],
  "mappings": "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",
  "names": []
}
