Migrate to using random IDs for documents
This commit is contained in:
@@ -8,6 +8,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/app.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+14
-1
@@ -9,6 +9,8 @@ import {
|
|||||||
import Chooser from './Chooser';
|
import Chooser from './Chooser';
|
||||||
import Editor from './Editor';
|
import Editor from './Editor';
|
||||||
import { HashRouter, Route, useNavigate } from '@solidjs/router';
|
import { HashRouter, Route, useNavigate } from '@solidjs/router';
|
||||||
|
import { defaultVault } from '../sync';
|
||||||
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
interface NavbarProps {
|
interface NavbarProps {
|
||||||
fileId: Accessor<string | null>;
|
fileId: Accessor<string | null>;
|
||||||
@@ -28,6 +30,17 @@ export function useNavbar(): NavbarProps {
|
|||||||
function Layout(props: any) {
|
function Layout(props: any) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [fileId, setFileId] = createSignal<string | null>(null);
|
const [fileId, setFileId] = createSignal<string | null>(null);
|
||||||
|
const title = () => {
|
||||||
|
const id = fileId();
|
||||||
|
if (id === null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return (defaultVault.doc.getMap() as Y.Map<any>)
|
||||||
|
.get('docs')
|
||||||
|
.get(id)
|
||||||
|
.get('title') as string;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavbarContext.Provider value={{ fileId, setFileId }}>
|
<NavbarContext.Provider value={{ fileId, setFileId }}>
|
||||||
@@ -36,7 +49,7 @@ function Layout(props: any) {
|
|||||||
<Show when={fileId() !== null}>
|
<Show when={fileId() !== null}>
|
||||||
<button onclick={() => navigate('/')}>Back</button>
|
<button onclick={() => navigate('/')}>Back</button>
|
||||||
</Show>
|
</Show>
|
||||||
<div class="title">{fileId() ?? 'Choose a file'}</div>
|
<div class="title">{title() ?? 'Choose a file'}</div>
|
||||||
</div>
|
</div>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,20 +2,26 @@ import { For, onMount } from 'solid-js';
|
|||||||
import './Chooser.css';
|
import './Chooser.css';
|
||||||
import { useNavbar } from './App';
|
import { useNavbar } from './App';
|
||||||
import { useNavigate } from '@solidjs/router';
|
import { useNavigate } from '@solidjs/router';
|
||||||
|
import { defaultVault } from '../sync';
|
||||||
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
function Chooser() {
|
function Chooser() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { setFileId } = useNavbar();
|
const { setFileId } = useNavbar();
|
||||||
onMount(() => setFileId(null));
|
onMount(() => setFileId(null));
|
||||||
|
|
||||||
const files = ['foo', 'bar', 'baz'];
|
const docsMap = defaultVault.doc.getMap().get('docs') as Y.Map<any>;
|
||||||
|
const titles: Record<string, string> = {};
|
||||||
|
for (const [id, docInfo] of docsMap.entries()) {
|
||||||
|
titles[id] = docInfo.get('title');
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<For each={files}>
|
<For each={Object.entries(titles)}>
|
||||||
{(availableFile) => (
|
{([id, title]: [string, string]) => (
|
||||||
<button class="file" onclick={() => navigate(`doc/${availableFile}`)}>
|
<button class="file" onclick={() => navigate(`doc/${id}`)}>
|
||||||
{availableFile}
|
{title}
|
||||||
{/* {availableFile === props.file() ? '(*)' : ''} */}
|
{/* {availableFile === props.file() ? '(*)' : ''} */}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+59
-15
@@ -19,21 +19,6 @@ const db = await openDB(DB_NAME, 3, {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
async function getOrCreateVault() {
|
|
||||||
const records = await db.getAll(VAULT_STORE);
|
|
||||||
if (records.length > 1) {
|
|
||||||
throw new Error(`Expected 1 vault record, got ${records.length}`);
|
|
||||||
} else if (records.length === 0) {
|
|
||||||
const key = generateId();
|
|
||||||
const value = { id: key };
|
|
||||||
await db.add(VAULT_STORE, value, key);
|
|
||||||
records.push(value);
|
|
||||||
}
|
|
||||||
return records[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
await getOrCreateVault();
|
|
||||||
|
|
||||||
export class LocalDocument {
|
export class LocalDocument {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly doc: Y.Doc;
|
readonly doc: Y.Doc;
|
||||||
@@ -46,6 +31,7 @@ export class LocalDocument {
|
|||||||
|
|
||||||
const flush = _.debounce(() => this.flush(), 1000);
|
const flush = _.debounce(() => this.flush(), 1000);
|
||||||
this.doc.on('update', (update: Uint8Array) => {
|
this.doc.on('update', (update: Uint8Array) => {
|
||||||
|
// TODO: Is this generating update events on load?
|
||||||
this.updates.push(update);
|
this.updates.push(update);
|
||||||
flush();
|
flush();
|
||||||
});
|
});
|
||||||
@@ -56,6 +42,7 @@ export class LocalDocument {
|
|||||||
LOCAL_UPDATE_STORE,
|
LOCAL_UPDATE_STORE,
|
||||||
IDBKeyRange.bound([id, -Infinity], [id, Infinity])
|
IDBKeyRange.bound([id, -Infinity], [id, Infinity])
|
||||||
);
|
);
|
||||||
|
// console.log(`load(${id}): got ${updates.length} updates`);
|
||||||
const result = new LocalDocument(id);
|
const result = new LocalDocument(id);
|
||||||
Y.applyUpdate(result.doc, Y.mergeUpdates(updates));
|
Y.applyUpdate(result.doc, Y.mergeUpdates(updates));
|
||||||
return result;
|
return result;
|
||||||
@@ -76,3 +63,60 @@ export class LocalDocument {
|
|||||||
this.doc.destroy();
|
this.doc.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getOrCreateVaultDoc() {
|
||||||
|
const records = await db.getAll(VAULT_STORE);
|
||||||
|
if (records.length > 1) {
|
||||||
|
throw new Error(`Expected 1 vault record, got ${records.length}`);
|
||||||
|
} else if (records.length === 0) {
|
||||||
|
const key = generateId();
|
||||||
|
const value = { id: key };
|
||||||
|
await db.add(VAULT_STORE, value, key);
|
||||||
|
records.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = records[0].id as string;
|
||||||
|
if (!id) {
|
||||||
|
throw new Error("Vault record didn't have id");
|
||||||
|
}
|
||||||
|
const vault = await LocalDocument.load(id);
|
||||||
|
const vaultMap = vault.doc.getMap();
|
||||||
|
if (!vaultMap.has('id')) {
|
||||||
|
vaultMap.set('id', id);
|
||||||
|
}
|
||||||
|
if (!vaultMap.has('docs')) {
|
||||||
|
vaultMap.set('docs', new Y.Map());
|
||||||
|
}
|
||||||
|
return vault;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultVault = await getOrCreateVaultDoc();
|
||||||
|
|
||||||
|
const docsMap = defaultVault.doc.getMap().get('docs') as Y.Map<any>;
|
||||||
|
if (docsMap.size === 0) {
|
||||||
|
console.log('Running vault migration...');
|
||||||
|
for (const oldKey of ['foo', 'bar', 'baz']) {
|
||||||
|
const newId = generateId();
|
||||||
|
const docInfo = new Y.Map([['title', oldKey]]);
|
||||||
|
docsMap.set(newId, docInfo);
|
||||||
|
|
||||||
|
const tx = db.transaction(LOCAL_UPDATE_STORE, 'readwrite');
|
||||||
|
const recordKeys = (await tx.store.getAllKeys(
|
||||||
|
IDBKeyRange.bound([oldKey, -Infinity], [oldKey, Infinity])
|
||||||
|
)) as [string, number][];
|
||||||
|
console.log(
|
||||||
|
`Found ${recordKeys.length} records for old document key ${oldKey}`
|
||||||
|
);
|
||||||
|
for (const oldRecordKey of recordKeys) {
|
||||||
|
const record = await tx.store.get(oldRecordKey);
|
||||||
|
const newRecordKey = [newId, oldRecordKey[1]];
|
||||||
|
await tx.store.put(record, newRecordKey);
|
||||||
|
}
|
||||||
|
await tx.store.delete(
|
||||||
|
IDBKeyRange.bound([oldKey, -Infinity], [oldKey, Infinity])
|
||||||
|
);
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(window as any).Y = Y;
|
||||||
|
|||||||
Reference in New Issue
Block a user