Skip to content

Commit

Permalink
add journal archiving
Browse files Browse the repository at this point in the history
- adds an archived state to journals. Archived journals remain searchable but do not show up by default in selectors when creating new documents
  • Loading branch information
cloverich committed Jan 20, 2024
1 parent 944b968 commit d73f373
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 11 deletions.
4 changes: 3 additions & 1 deletion src/electron/migrations/20211005142122.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/electron/migrations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 36 additions & 2 deletions src/hooks/stores/journals.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { observable } from "mobx";
import { observable, computed, toJS } from "mobx";
import { JournalResponse, IClient } from "../useClient";

export class JournalsStore {
Expand All @@ -8,14 +8,21 @@ 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[],
) {
this.journals = journals;
}

// create instance of store...
static async create(client: IClient) {
const journals = await client.journals.list();
return new JournalsStore(client, journals);
Expand Down Expand Up @@ -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;
22 changes: 22 additions & 0 deletions src/preload/client/journals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface JournalResponse {
name: string;
createdAt: string;
updatedAt: string;
archivedAt: string;
}

export type IJournalsClient = JournalsClient;
Expand Down Expand Up @@ -52,4 +53,25 @@ export class JournalsClient {
.run({ id: journal.id });
return this.list();
};

archive = (journal: { id: string }): Promise<JournalResponse[]> => {
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<JournalResponse[]> => {
this.db
.prepare("update journals set archivedAt = null where id = :id")
.run({
id: journal.id,
});

return this.list();
};
}
27 changes: 22 additions & 5 deletions src/views/edit/index.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<any>();
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 <EditLoadingComponent error={loadingError} />;
}

if (!document) {
if (!document || !journals) {
return <EditLoadingComponent />;
}

return <DocumentEditView document={document} journals={journals.journals} />;
return <DocumentEditView document={document} journals={journals} />;
}

interface DocumentEditProps {
Expand Down
89 changes: 87 additions & 2 deletions src/views/journals/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,20 +88,58 @@ function Journals(props: RouteProps) {
</Button>
</Pane>

<h2>Active Journals</h2>
<Table>
<Table.Head>
<Table.TextHeaderCell>Name</Table.TextHeaderCell>
<Table.TextHeaderCell>Id</Table.TextHeaderCell>
</Table.Head>
<Table.Body>
{store.journals.map((journal) => (
{store.active.map((journal) => (
<Table.Row key={journal.name}>
<Table.TextCell>{journal.name}</Table.TextCell>
<Table.TextCell style={{ textAlign: "center" }}>
<Badge>{journal.id}</Badge>
</Table.TextCell>
<Table.TextCell>
<Button onClick={() => removeJournal(journal)}>Remove</Button>
<JournalArchiveButton journal={journal} />
<Button
size="small"
marginRight={12}
intent="danger"
onClick={() => removeJournal(journal)}
>
Remove
</Button>
</Table.TextCell>
</Table.Row>
))}
</Table.Body>
</Table>

<h2>Archived Journals</h2>
<Table>
<Table.Head>
<Table.TextHeaderCell>Name</Table.TextHeaderCell>
<Table.TextHeaderCell>Id</Table.TextHeaderCell>
</Table.Head>
<Table.Body>
{store.archived.map((journal) => (
<Table.Row key={journal.name}>
<Table.TextCell>{journal.name}</Table.TextCell>
<Table.TextCell style={{ textAlign: "center" }}>
<Badge>{journal.id}</Badge>
</Table.TextCell>
<Table.TextCell>
<JournalArchiveButton journal={journal} />
<Button
size="small"
marginRight={12}
intent="danger"
onClick={() => removeJournal(journal)}
>
Remove
</Button>
</Table.TextCell>
</Table.Row>
))}
Expand All @@ -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 (
<Button
size="small"
marginRight={12}
onClick={() => toggleArchive(props.journal)}
>
Restore
</Button>
);
} else {
return (
<Button
size="small"
marginRight={12}
onClick={() => toggleArchive(props.journal)}
>
Archive
</Button>
);
}
}

export default observer(Journals);

0 comments on commit d73f373

Please sign in to comment.