auto-git:
[add] src/serialization/editor-draft-storage.ts
This commit is contained in:
569
src/serialization/editor-draft-storage.ts
Normal file
569
src/serialization/editor-draft-storage.ts
Normal file
@@ -0,0 +1,569 @@
|
|||||||
|
import {
|
||||||
|
createEmptyProjectDocument,
|
||||||
|
type ProjectDocument
|
||||||
|
} from "../document/scene-document";
|
||||||
|
import {
|
||||||
|
formatSceneDiagnosticSummary,
|
||||||
|
validateProjectDocument
|
||||||
|
} from "../document/scene-document-validation";
|
||||||
|
import {
|
||||||
|
cloneViewportLayoutState,
|
||||||
|
type ViewportLayoutState
|
||||||
|
} from "../viewport-three/viewport-layout";
|
||||||
|
|
||||||
|
import {
|
||||||
|
DEFAULT_SCENE_DRAFT_STORAGE_KEY,
|
||||||
|
loadSceneDocumentDraft,
|
||||||
|
parseViewportLayoutState,
|
||||||
|
saveSceneDocumentDraft,
|
||||||
|
type KeyValueStorage,
|
||||||
|
type LoadOrCreateSceneDocumentResult,
|
||||||
|
type LoadSceneDocumentDraftResult,
|
||||||
|
type SaveSceneDocumentDraftResult
|
||||||
|
} from "./local-draft-storage";
|
||||||
|
import { parseProjectDocumentJson } from "./scene-document-json";
|
||||||
|
|
||||||
|
const EDITOR_DRAFT_DATABASE_NAME = "webeditor3d-editor-drafts";
|
||||||
|
const EDITOR_DRAFT_DATABASE_VERSION = 1;
|
||||||
|
const EDITOR_DRAFT_OBJECT_STORE = "drafts";
|
||||||
|
const EDITOR_DOCUMENT_DRAFT_FORMAT = "webeditor3d.editor-document-draft.v1";
|
||||||
|
const EDITOR_VIEWPORT_DRAFT_FORMAT = "webeditor3d.editor-viewport-draft.v1";
|
||||||
|
|
||||||
|
export type EditorDraftSaveResult = SaveSceneDocumentDraftResult;
|
||||||
|
export type EditorDraftLoadResult = LoadSceneDocumentDraftResult;
|
||||||
|
|
||||||
|
export interface EditorDraftStorage {
|
||||||
|
saveDocumentDraft(
|
||||||
|
document: ProjectDocument,
|
||||||
|
fallbackViewportLayoutState?: ViewportLayoutState | null
|
||||||
|
): Promise<EditorDraftSaveResult>;
|
||||||
|
saveViewportLayoutDraft(
|
||||||
|
viewportLayoutState: ViewportLayoutState,
|
||||||
|
fallbackDocument?: ProjectDocument
|
||||||
|
): Promise<EditorDraftSaveResult>;
|
||||||
|
loadDraft(): Promise<EditorDraftLoadResult>;
|
||||||
|
flushEmergencyFallback?(
|
||||||
|
document: ProjectDocument,
|
||||||
|
viewportLayoutState: ViewportLayoutState | null
|
||||||
|
): EditorDraftSaveResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BrowserEditorDraftStorageAccessResult {
|
||||||
|
storage: EditorDraftStorage | null;
|
||||||
|
diagnostic: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EditorDocumentDraftRecord {
|
||||||
|
format: typeof EDITOR_DOCUMENT_DRAFT_FORMAT;
|
||||||
|
savedAt: number;
|
||||||
|
document: ProjectDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EditorViewportDraftRecord {
|
||||||
|
format: typeof EDITOR_VIEWPORT_DRAFT_FORMAT;
|
||||||
|
savedAt: number;
|
||||||
|
viewportLayoutState: ViewportLayoutState;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IndexedDbRequestLike<T> {
|
||||||
|
result: T;
|
||||||
|
error: DOMException | null;
|
||||||
|
onsuccess: ((event: Event) => void) | null;
|
||||||
|
onerror: ((event: Event) => void) | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorDetail(error: unknown): string {
|
||||||
|
if (error instanceof Error && error.message.trim().length > 0) {
|
||||||
|
return error.message.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown error.";
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSavedResult(message = "Autosave updated."): EditorDraftSaveResult {
|
||||||
|
return {
|
||||||
|
status: "saved",
|
||||||
|
message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSkippedResult(message: string): EditorDraftSaveResult {
|
||||||
|
return {
|
||||||
|
status: "skipped",
|
||||||
|
message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createErrorResult(prefix: string, error: unknown): EditorDraftSaveResult {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
message: `${prefix} ${getErrorDetail(error)}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertProjectDocumentDraftIsValid(document: ProjectDocument) {
|
||||||
|
const validation = validateProjectDocument(document, {
|
||||||
|
terrainSampleValues: "skip"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (validation.errors.length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Project document draft has ${validation.errors.length} validation error(s): ${formatSceneDiagnosticSummary(validation.errors)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseProjectDocumentDraft(value: unknown): ProjectDocument {
|
||||||
|
return parseProjectDocumentJson(JSON.stringify(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return typeof value === "object" && value !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEditorDocumentDraftRecord(
|
||||||
|
value: unknown
|
||||||
|
): value is EditorDocumentDraftRecord {
|
||||||
|
return isRecord(value) && value.format === EDITOR_DOCUMENT_DRAFT_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEditorViewportDraftRecord(
|
||||||
|
value: unknown
|
||||||
|
): value is EditorViewportDraftRecord {
|
||||||
|
return isRecord(value) && value.format === EDITOR_VIEWPORT_DRAFT_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDocumentRecordKey(draftKey: string): string {
|
||||||
|
return `${draftKey}:document`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getViewportRecordKey(draftKey: string): string {
|
||||||
|
return `${draftKey}:viewport`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestToPromise<T>(request: IDBRequest<T>): Promise<T> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
request.onsuccess = () => resolve(request.result);
|
||||||
|
request.onerror = () => reject(request.error ?? new Error("IndexedDB request failed."));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function transactionDone(transaction: IDBTransaction): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
transaction.oncomplete = () => resolve();
|
||||||
|
transaction.onerror = () =>
|
||||||
|
reject(transaction.error ?? new Error("IndexedDB transaction failed."));
|
||||||
|
transaction.onabort = () =>
|
||||||
|
reject(transaction.error ?? new Error("IndexedDB transaction aborted."));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditorDraftDatabase(
|
||||||
|
indexedDb: IDBFactory = indexedDB
|
||||||
|
): Promise<IDBDatabase> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = indexedDb.open(
|
||||||
|
EDITOR_DRAFT_DATABASE_NAME,
|
||||||
|
EDITOR_DRAFT_DATABASE_VERSION
|
||||||
|
);
|
||||||
|
|
||||||
|
request.onupgradeneeded = () => {
|
||||||
|
const database = request.result;
|
||||||
|
|
||||||
|
if (!database.objectStoreNames.contains(EDITOR_DRAFT_OBJECT_STORE)) {
|
||||||
|
database.createObjectStore(EDITOR_DRAFT_OBJECT_STORE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
request.onsuccess = () => resolve(request.result);
|
||||||
|
request.onerror = () =>
|
||||||
|
reject(request.error ?? new Error("IndexedDB database open failed."));
|
||||||
|
request.onblocked = () =>
|
||||||
|
reject(new Error("IndexedDB database upgrade was blocked."));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IndexedDbEditorDraftStorage implements EditorDraftStorage {
|
||||||
|
constructor(
|
||||||
|
private readonly database: IDBDatabase,
|
||||||
|
private readonly draftKey = DEFAULT_SCENE_DRAFT_STORAGE_KEY,
|
||||||
|
private readonly legacyStorage: KeyValueStorage | null = null
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async saveDocumentDraft(
|
||||||
|
document: ProjectDocument,
|
||||||
|
_fallbackViewportLayoutState: ViewportLayoutState | null = null
|
||||||
|
): Promise<EditorDraftSaveResult> {
|
||||||
|
try {
|
||||||
|
assertProjectDocumentDraftIsValid(document);
|
||||||
|
await this.putRecord(getDocumentRecordKey(this.draftKey), {
|
||||||
|
format: EDITOR_DOCUMENT_DRAFT_FORMAT,
|
||||||
|
savedAt: Date.now(),
|
||||||
|
document
|
||||||
|
} satisfies EditorDocumentDraftRecord);
|
||||||
|
return createSavedResult();
|
||||||
|
} catch (error) {
|
||||||
|
return createErrorResult("Autosave could not be saved.", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveViewportLayoutDraft(
|
||||||
|
viewportLayoutState: ViewportLayoutState
|
||||||
|
): Promise<EditorDraftSaveResult> {
|
||||||
|
try {
|
||||||
|
await this.putRecord(getViewportRecordKey(this.draftKey), {
|
||||||
|
format: EDITOR_VIEWPORT_DRAFT_FORMAT,
|
||||||
|
savedAt: Date.now(),
|
||||||
|
viewportLayoutState: cloneViewportLayoutState(viewportLayoutState)
|
||||||
|
} satisfies EditorViewportDraftRecord);
|
||||||
|
return createSavedResult();
|
||||||
|
} catch (error) {
|
||||||
|
return createErrorResult("Autosave could not be saved.", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadDraft(): Promise<EditorDraftLoadResult> {
|
||||||
|
try {
|
||||||
|
const documentRecord = await this.getRecord(
|
||||||
|
getDocumentRecordKey(this.draftKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (documentRecord === undefined) {
|
||||||
|
return this.loadLegacyDraft();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEditorDocumentDraftRecord(documentRecord)) {
|
||||||
|
throw new Error("IndexedDB document draft has an unsupported format.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewportRecord = await this.getRecord(
|
||||||
|
getViewportRecordKey(this.draftKey)
|
||||||
|
);
|
||||||
|
let viewportLayoutState: ViewportLayoutState | null = null;
|
||||||
|
|
||||||
|
if (viewportRecord !== undefined) {
|
||||||
|
if (!isEditorViewportDraftRecord(viewportRecord)) {
|
||||||
|
throw new Error("IndexedDB viewport draft has an unsupported format.");
|
||||||
|
}
|
||||||
|
|
||||||
|
viewportLayoutState = parseViewportLayoutState(
|
||||||
|
viewportRecord.viewportLayoutState
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "loaded",
|
||||||
|
document: parseProjectDocumentDraft(documentRecord.document),
|
||||||
|
viewportLayoutState,
|
||||||
|
message: "Recovered latest autosave."
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
message: `Stored autosave could not be loaded. ${getErrorDetail(error)}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flushEmergencyFallback(
|
||||||
|
document: ProjectDocument,
|
||||||
|
viewportLayoutState: ViewportLayoutState | null
|
||||||
|
): EditorDraftSaveResult {
|
||||||
|
if (this.legacyStorage === null) {
|
||||||
|
return createSkippedResult(
|
||||||
|
"Emergency localStorage autosave skipped because browser local storage is unavailable."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return saveSceneDocumentDraft(
|
||||||
|
this.legacyStorage,
|
||||||
|
document,
|
||||||
|
viewportLayoutState,
|
||||||
|
this.draftKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getRecord(key: string): Promise<unknown | undefined> {
|
||||||
|
const transaction = this.database.transaction(
|
||||||
|
EDITOR_DRAFT_OBJECT_STORE,
|
||||||
|
"readonly"
|
||||||
|
);
|
||||||
|
const store = transaction.objectStore(EDITOR_DRAFT_OBJECT_STORE);
|
||||||
|
const result = await requestToPromise(store.get(key));
|
||||||
|
await transactionDone(transaction);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async putRecord(key: string, value: unknown): Promise<void> {
|
||||||
|
const transaction = this.database.transaction(
|
||||||
|
EDITOR_DRAFT_OBJECT_STORE,
|
||||||
|
"readwrite"
|
||||||
|
);
|
||||||
|
const store = transaction.objectStore(EDITOR_DRAFT_OBJECT_STORE);
|
||||||
|
|
||||||
|
store.put(value, key);
|
||||||
|
await transactionDone(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadLegacyDraft(): EditorDraftLoadResult {
|
||||||
|
if (this.legacyStorage === null) {
|
||||||
|
return {
|
||||||
|
status: "missing",
|
||||||
|
message: "No autosave was found."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadSceneDocumentDraft(this.legacyStorage, this.draftKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LocalStorageEditorDraftStorage implements EditorDraftStorage {
|
||||||
|
private latestDocument: ProjectDocument | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly storage: KeyValueStorage,
|
||||||
|
private readonly draftKey = DEFAULT_SCENE_DRAFT_STORAGE_KEY
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async saveDocumentDraft(
|
||||||
|
document: ProjectDocument,
|
||||||
|
fallbackViewportLayoutState: ViewportLayoutState | null = null
|
||||||
|
): Promise<EditorDraftSaveResult> {
|
||||||
|
this.latestDocument = document;
|
||||||
|
return saveSceneDocumentDraft(
|
||||||
|
this.storage,
|
||||||
|
document,
|
||||||
|
fallbackViewportLayoutState,
|
||||||
|
this.draftKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveViewportLayoutDraft(
|
||||||
|
viewportLayoutState: ViewportLayoutState,
|
||||||
|
fallbackDocument?: ProjectDocument
|
||||||
|
): Promise<EditorDraftSaveResult> {
|
||||||
|
const document = fallbackDocument ?? this.latestDocument;
|
||||||
|
|
||||||
|
if (document === null) {
|
||||||
|
return createSkippedResult(
|
||||||
|
"Viewport autosave skipped because no document draft is available for localStorage fallback."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return saveSceneDocumentDraft(
|
||||||
|
this.storage,
|
||||||
|
document,
|
||||||
|
viewportLayoutState,
|
||||||
|
this.draftKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadDraft(): Promise<EditorDraftLoadResult> {
|
||||||
|
const result = loadSceneDocumentDraft(this.storage, this.draftKey);
|
||||||
|
|
||||||
|
if (result.status === "loaded") {
|
||||||
|
this.latestDocument = result.document;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
flushEmergencyFallback(
|
||||||
|
document: ProjectDocument,
|
||||||
|
viewportLayoutState: ViewportLayoutState | null
|
||||||
|
): EditorDraftSaveResult {
|
||||||
|
this.latestDocument = document;
|
||||||
|
return saveSceneDocumentDraft(
|
||||||
|
this.storage,
|
||||||
|
document,
|
||||||
|
viewportLayoutState,
|
||||||
|
this.draftKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MemoryEditorDraftStorage implements EditorDraftStorage {
|
||||||
|
private documentRecord: EditorDocumentDraftRecord | null = null;
|
||||||
|
private viewportRecord: EditorViewportDraftRecord | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly options: {
|
||||||
|
legacyStorage?: KeyValueStorage | null;
|
||||||
|
draftKey?: string;
|
||||||
|
} = {}
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async saveDocumentDraft(
|
||||||
|
document: ProjectDocument
|
||||||
|
): Promise<EditorDraftSaveResult> {
|
||||||
|
try {
|
||||||
|
assertProjectDocumentDraftIsValid(document);
|
||||||
|
this.documentRecord = {
|
||||||
|
format: EDITOR_DOCUMENT_DRAFT_FORMAT,
|
||||||
|
savedAt: Date.now(),
|
||||||
|
document
|
||||||
|
};
|
||||||
|
return createSavedResult();
|
||||||
|
} catch (error) {
|
||||||
|
return createErrorResult("Autosave could not be saved.", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveViewportLayoutDraft(
|
||||||
|
viewportLayoutState: ViewportLayoutState
|
||||||
|
): Promise<EditorDraftSaveResult> {
|
||||||
|
this.viewportRecord = {
|
||||||
|
format: EDITOR_VIEWPORT_DRAFT_FORMAT,
|
||||||
|
savedAt: Date.now(),
|
||||||
|
viewportLayoutState: cloneViewportLayoutState(viewportLayoutState)
|
||||||
|
};
|
||||||
|
return createSavedResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadDraft(): Promise<EditorDraftLoadResult> {
|
||||||
|
if (this.documentRecord === null) {
|
||||||
|
const legacyStorage = this.options.legacyStorage ?? null;
|
||||||
|
|
||||||
|
if (legacyStorage !== null) {
|
||||||
|
return loadSceneDocumentDraft(
|
||||||
|
legacyStorage,
|
||||||
|
this.options.draftKey ?? DEFAULT_SCENE_DRAFT_STORAGE_KEY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "missing",
|
||||||
|
message: "No autosave was found."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "loaded",
|
||||||
|
document: parseProjectDocumentDraft(this.documentRecord.document),
|
||||||
|
viewportLayoutState:
|
||||||
|
this.viewportRecord === null
|
||||||
|
? null
|
||||||
|
: parseViewportLayoutState(this.viewportRecord.viewportLayoutState),
|
||||||
|
message: "Recovered latest autosave."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
flushEmergencyFallback(
|
||||||
|
document: ProjectDocument,
|
||||||
|
viewportLayoutState: ViewportLayoutState | null
|
||||||
|
): EditorDraftSaveResult {
|
||||||
|
const legacyStorage = this.options.legacyStorage ?? null;
|
||||||
|
|
||||||
|
if (legacyStorage === null) {
|
||||||
|
return createSkippedResult(
|
||||||
|
"Emergency localStorage autosave skipped because browser local storage is unavailable."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return saveSceneDocumentDraft(
|
||||||
|
legacyStorage,
|
||||||
|
document,
|
||||||
|
viewportLayoutState,
|
||||||
|
this.options.draftKey ?? DEFAULT_SCENE_DRAFT_STORAGE_KEY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createBrowserEditorDraftStorage(options: {
|
||||||
|
legacyStorage: KeyValueStorage | null;
|
||||||
|
draftKey?: string;
|
||||||
|
indexedDb?: IDBFactory | null;
|
||||||
|
}): Promise<BrowserEditorDraftStorageAccessResult> {
|
||||||
|
const draftKey = options.draftKey ?? DEFAULT_SCENE_DRAFT_STORAGE_KEY;
|
||||||
|
const indexedDb =
|
||||||
|
options.indexedDb === undefined
|
||||||
|
? typeof indexedDB === "undefined"
|
||||||
|
? null
|
||||||
|
: indexedDB
|
||||||
|
: options.indexedDb;
|
||||||
|
|
||||||
|
if (indexedDb !== null) {
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
storage: new IndexedDbEditorDraftStorage(
|
||||||
|
await openEditorDraftDatabase(indexedDb),
|
||||||
|
draftKey,
|
||||||
|
options.legacyStorage
|
||||||
|
),
|
||||||
|
diagnostic: null
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (options.legacyStorage !== null) {
|
||||||
|
return {
|
||||||
|
storage: new LocalStorageEditorDraftStorage(
|
||||||
|
options.legacyStorage,
|
||||||
|
draftKey
|
||||||
|
),
|
||||||
|
diagnostic: `IndexedDB autosave is unavailable; using localStorage fallback. ${getErrorDetail(error)}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
storage: null,
|
||||||
|
diagnostic: `IndexedDB autosave is unavailable and localStorage fallback is unavailable. ${getErrorDetail(error)}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.legacyStorage !== null) {
|
||||||
|
return {
|
||||||
|
storage: new LocalStorageEditorDraftStorage(options.legacyStorage, draftKey),
|
||||||
|
diagnostic: "IndexedDB autosave is unavailable; using localStorage fallback."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
storage: null,
|
||||||
|
diagnostic: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadOrCreateEditorDraft(
|
||||||
|
storage: EditorDraftStorage | null
|
||||||
|
): Promise<LoadOrCreateSceneDocumentResult> {
|
||||||
|
if (storage === null) {
|
||||||
|
return {
|
||||||
|
document: createEmptyProjectDocument(),
|
||||||
|
viewportLayoutState: null,
|
||||||
|
diagnostic: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const draftResult = await storage.loadDraft();
|
||||||
|
|
||||||
|
switch (draftResult.status) {
|
||||||
|
case "loaded":
|
||||||
|
return {
|
||||||
|
document: draftResult.document,
|
||||||
|
viewportLayoutState: draftResult.viewportLayoutState,
|
||||||
|
diagnostic: draftResult.message
|
||||||
|
};
|
||||||
|
case "missing":
|
||||||
|
return {
|
||||||
|
document: createEmptyProjectDocument(),
|
||||||
|
viewportLayoutState: null,
|
||||||
|
diagnostic: null
|
||||||
|
};
|
||||||
|
case "error":
|
||||||
|
return {
|
||||||
|
document: createEmptyProjectDocument(),
|
||||||
|
viewportLayoutState: null,
|
||||||
|
diagnostic: `${draftResult.message} Starting with a fresh empty document.`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createResolvedIndexedDbRequest<T>(
|
||||||
|
result: T
|
||||||
|
): IndexedDbRequestLike<T> {
|
||||||
|
return {
|
||||||
|
result,
|
||||||
|
error: null,
|
||||||
|
onsuccess: null,
|
||||||
|
onerror: null
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user