Add data import and export
This commit is contained in:
parent
7a7b40e2ba
commit
222c718509
|
@ -0,0 +1,45 @@
|
|||
<script lang="ts">
|
||||
import { importDb } from '$lib/db/import-db';
|
||||
import Button from '../button.svelte';
|
||||
import Modal from '../modal.svelte';
|
||||
|
||||
export let open: boolean;
|
||||
|
||||
async function submit(event: SubmitEvent) {
|
||||
const form = event.target as HTMLFormElement;
|
||||
const formData = new FormData(form);
|
||||
const [dbFile] = formData.getAll('dbFile') as File[];
|
||||
|
||||
if (!dbFile || dbFile.name === '') {
|
||||
// TODO: message that no file was selected
|
||||
return;
|
||||
}
|
||||
|
||||
const importSucceeded = await importDb(dbFile);
|
||||
|
||||
if (importSucceeded === true) {
|
||||
form.reset();
|
||||
open = false;
|
||||
} else {
|
||||
// TODO: message that import failed
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal title="Import Notes" size="sm" bind:open>
|
||||
<form on:submit|preventDefault={submit}>
|
||||
<p>Select a backup file that you exported previously to restore your notes.</p>
|
||||
<p>
|
||||
<strong>Warning:</strong>
|
||||
This will overwrite all of your current notes.
|
||||
</p>
|
||||
<input type="file" name="dbFile" accept=".db" />
|
||||
<Button type="submit">Import</Button>
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
<style lang="scss">
|
||||
input {
|
||||
margin: 1rem 0 1.5rem;
|
||||
}
|
||||
</style>
|
|
@ -2,8 +2,17 @@
|
|||
import Modal from '../modal.svelte';
|
||||
import { settings } from '$lib/stores';
|
||||
import SettingsColorPicker from './settings-color-picker.svelte';
|
||||
import Button from '../button.svelte';
|
||||
import { exportDb } from '$lib/db/export-db';
|
||||
import SettingsImportModal from './settings-import-modal.svelte';
|
||||
|
||||
const { open, noteScale, defaultNoteColor } = settings;
|
||||
let importModalOpen = false;
|
||||
|
||||
function goToImport() {
|
||||
open.set(false);
|
||||
importModalOpen = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal title="Settings" size="sm" bind:open={$open}>
|
||||
|
@ -22,4 +31,23 @@
|
|||
<legend>Default Note Color</legend>
|
||||
<SettingsColorPicker bind:value={$defaultNoteColor} />
|
||||
</fieldset>
|
||||
<hr />
|
||||
<div class="settings-buttons">
|
||||
<Button on:click={exportDb}>Export Notes</Button>
|
||||
<Button secondary on:click={goToImport}>Import Notes</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<SettingsImportModal bind:open={importModalOpen} />
|
||||
|
||||
<style lang="scss">
|
||||
.settings-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
margin-top: 1.75rem;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,30 +1,16 @@
|
|||
import { Kysely, Migrator } from 'kysely';
|
||||
import { Kysely } from 'kysely';
|
||||
import { SQLocalKysely } from 'sqlocal/kysely';
|
||||
import type { Schema } from './schema';
|
||||
import { migrations } from './migrations/';
|
||||
import { migrate } from './migrator';
|
||||
|
||||
const sqlocal = new SQLocalKysely('sticky-notes.db');
|
||||
const kysely = new Kysely<Schema>({ dialect: sqlocal.dialect });
|
||||
|
||||
await sqlocal.sql`PRAGMA foreign_keys = ON`;
|
||||
|
||||
const migrator = new Migrator({
|
||||
db: kysely,
|
||||
provider: {
|
||||
async getMigrations() {
|
||||
// TODO: Dynamic import does not work in production build
|
||||
// because the imported chunk imports `sql` from the same
|
||||
// chunk that imports it, so circular dependency. Vite bug.
|
||||
// const { migrations } = await import('./migrations/');
|
||||
return migrations;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const migration = await migrator.migrateToLatest();
|
||||
|
||||
if (migration.error) {
|
||||
console.error(migration.error);
|
||||
try {
|
||||
await migrate(kysely);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
export const db = kysely;
|
||||
export const { getDatabaseFile, overwriteDatabaseFile } = sqlocal;
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { getDatabaseFile } from './client';
|
||||
|
||||
export async function exportDb() {
|
||||
const databaseFile = await getDatabaseFile();
|
||||
const fileUrl = URL.createObjectURL(databaseFile);
|
||||
|
||||
const now = new Date();
|
||||
const timestamp = now.toISOString().split('.')[0].replace(/\:/g, '-');
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = fileUrl;
|
||||
a.download = `notes-${timestamp}.db`;
|
||||
a.click();
|
||||
a.remove();
|
||||
|
||||
URL.revokeObjectURL(fileUrl);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { notes, tags } from '$lib/stores';
|
||||
import { db, overwriteDatabaseFile } from './client';
|
||||
import { migrate } from './migrator';
|
||||
|
||||
export async function importDb(dbFile: File): Promise<boolean> {
|
||||
try {
|
||||
await overwriteDatabaseFile(dbFile);
|
||||
await migrate(db);
|
||||
await tags.refreshTags();
|
||||
await notes.refreshNotes();
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { Kysely, Migrator, sql } from 'kysely';
|
||||
import { migrations } from './migrations/';
|
||||
import type { Schema } from './schema';
|
||||
|
||||
export async function migrate(db: Kysely<Schema>) {
|
||||
const migrator = new Migrator({
|
||||
db,
|
||||
provider: {
|
||||
async getMigrations() {
|
||||
return migrations;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await sql`PRAGMA foreign_keys = ON`.execute(db);
|
||||
const migration = await migrator.migrateToLatest();
|
||||
|
||||
if (migration.error) {
|
||||
console.error(migration.error);
|
||||
throw new Error('Migration failed');
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue