diff --git a/src/electron/migrations/20211005142122.sql b/src/electron/migrations/20211005142122.sql index dc3e80e..1adbd0e 100644 --- a/src/electron/migrations/20211005142122.sql +++ b/src/electron/migrations/20211005142122.sql @@ -13,8 +13,10 @@ CREATE TABLE IF NOT EXISTS "nodes" ( CREATE TABLE IF NOT EXISTS "journals" ( "id" TEXT NOT NULL PRIMARY KEY, "name" TEXT NOT NULL, + -- TODO: These defaults need to use timezone (same for documents) "createdAt" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + "updatedAt" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + "archivedAt" TEXT ); -- CreateTable diff --git a/src/electron/migrations/index.js b/src/electron/migrations/index.js index 49e58c7..14f40d4 100644 --- a/src/electron/migrations/index.js +++ b/src/electron/migrations/index.js @@ -2,7 +2,7 @@ const fs = require("fs"); const path = require("path"); const DB = require("better-sqlite3"); -// A hacky "migration" script after bailing on Prisma and realiing +// A hacky "migration" script after bailing on Prisma and realizing // better-sqlite3 is not compatible with knex yet :| // https://github.com/knex/knex/issues/4511 // todo: real migrations, backup database while migrating diff --git a/src/hooks/stores/journals.ts b/src/hooks/stores/journals.ts index 82165e0..95c47c7 100644 --- a/src/hooks/stores/journals.ts +++ b/src/hooks/stores/journals.ts @@ -1,4 +1,4 @@ -import { observable } from "mobx"; +import { observable, computed, toJS } from "mobx"; import { JournalResponse, IClient } from "../useClient"; export class JournalsStore { @@ -8,6 +8,14 @@ export class JournalsStore { @observable error: Error | null = null; @observable journals: JournalResponse[]; + @computed get active() { + return this.journals.filter((j) => !j.archivedAt); + } + + @computed get archived() { + return this.journals.filter((j) => !!j.archivedAt); + } + constructor( private client: IClient, journals: JournalResponse[], @@ -15,7 +23,6 @@ export class JournalsStore { this.journals = journals; } - // create instance of store... static async create(client: IClient) { const journals = await client.journals.list(); return new JournalsStore(client, journals); @@ -66,6 +73,33 @@ export class JournalsStore { } this.saving = false; }; + + toggleArchive = async (journal: JournalResponse) => { + this.saving = true; + + // If I don't do this, the call to archive / unarchive will error with + // "Object could not be cloned". It fails before executing the function, + // so I guess its an error with Proxy objects being passed to preload + // scripts. That is... concerning. + journal = toJS(journal); + + try { + if (journal.archivedAt) { + this.journals = await this.client.journals.unarchive(journal); + } else { + this.journals = await this.client.journals.archive(journal); + } + } catch (err: any) { + console.error(`Error toggling archive for journal ${journal.name}:`, err); + this.saving = false; + + // NOTE: Otherwise this returns success, I'm unsure why the + // other calls are storing the error? + throw err; + } + + this.saving = false; + }; } export type IJournalStore = JournalsStore; diff --git a/src/preload/client/journals.ts b/src/preload/client/journals.ts index fec39a1..0177352 100644 --- a/src/preload/client/journals.ts +++ b/src/preload/client/journals.ts @@ -18,6 +18,7 @@ export interface JournalResponse { name: string; createdAt: string; updatedAt: string; + archivedAt: string; } export type IJournalsClient = JournalsClient; @@ -52,4 +53,25 @@ export class JournalsClient { .run({ id: journal.id }); return this.list(); }; + + archive = (journal: { id: string }): Promise => { + this.db + .prepare("update journals set archivedAt = :archivedAt where id = :id") + .run({ + id: journal.id, + archivedAt: new Date().toISOString(), + }); + + return this.list(); + }; + + unarchive = (journal: { id: string }): Promise => { + this.db + .prepare("update journals set archivedAt = null where id = :id") + .run({ + id: journal.id, + }); + + return this.list(); + }; } diff --git a/src/views/edit/index.tsx b/src/views/edit/index.tsx index d447ac5..d21ae04 100644 --- a/src/views/edit/index.tsx +++ b/src/views/edit/index.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from "react"; +import React, { useContext, useState, useEffect } from "react"; import { observer } from "mobx-react-lite"; import Editor from "./editor"; import { Pane, Button, Popover, Menu, Position } from "evergreen-ui"; @@ -17,22 +17,39 @@ import { DebugView } from "./DebugView"; // Loads document, with loading and error placeholders function DocumentLoadingContainer() { - const journals = useContext(JournalsStoreContext); + const journalsStore = useContext(JournalsStoreContext); const { document: documentId } = useParams(); const { document, loadingError } = useEditableDocument( - journals.journals, + journalsStore.journals, documentId, ); + // Filter journals to non-archived ones, but must also add + // the current document's journal if its archived + const [journals, setJournals] = useState(); + useEffect(() => { + if (!document) return; + + const journals = journalsStore.journals.filter((j) => { + if (j.archivedAt) { + return j.id === document.journalId; + } else { + return !j.archivedAt; + } + }); + + setJournals(journals); + }, [document, loadingError]); + if (loadingError) { return ; } - if (!document) { + if (!document || !journals) { return ; } - return ; + return ; } interface DocumentEditProps { diff --git a/src/views/journals/index.tsx b/src/views/journals/index.tsx index ad69c26..07971d1 100644 --- a/src/views/journals/index.tsx +++ b/src/views/journals/index.tsx @@ -88,20 +88,58 @@ function Journals(props: RouteProps) { +

Active Journals

Name Id - {store.journals.map((journal) => ( + {store.active.map((journal) => ( {journal.name} {journal.id} - + + + + + ))} + +
+ +

Archived Journals

+ + + Name + Id + + + {store.archived.map((journal) => ( + + {journal.name} + + {journal.id} + + + + ))} @@ -111,4 +149,51 @@ function Journals(props: RouteProps) { ); } +/** + * UI and hook for toggling archive state on a journal + */ +function JournalArchiveButton(props: { journal: JournalResponse }) { + const store = useContext(JournalsStoreContext); + + async function toggleArchive(journal: JournalResponse) { + const isArchiving = !!journal.archivedAt; + + if (confirm(`Are you sure you want to archive ${journal.name}?`)) { + try { + await store.toggleArchive(journal); + if (isArchiving) { + toaster.success(`Successfully archived ${journal.name}`); + } else { + toaster.success(`Successfully restored ${journal.name}`); + } + } catch (err) { + console.error(err); + toaster.danger("There was an error archiving the journal"); + } + } + } + + if (props.journal.archivedAt) { + return ( + + ); + } else { + return ( + + ); + } +} + export default observer(Journals);