Compare commits

..

3 Commits

14 changed files with 73 additions and 49 deletions

View File

@@ -63,7 +63,7 @@ export function AutoSaveTextarea({
<textarea <textarea
value={value} value={value}
onChange={handleChange} onChange={handleChange}
className={`w-full min-h-[4rem] p-2 rounded border bg-slate-800 text-slate-100 border-slate-700 focus:outline-none focus:ring-2 focus:ring-violet-500 transition-colors ${flash ? "ring-2 ring-emerald-400 border-emerald-400 bg-emerald-950" : ""} ${className}`} className={`w-full min-h-[6em] field-sizing-content p-2 rounded border bg-slate-800 text-slate-100 border-slate-700 focus:outline-none focus:ring-2 focus:ring-violet-500 transition-colors ${flash ? "ring-2 ring-emerald-400 border-emerald-400 bg-emerald-950" : ""} ${className}`}
{...props} {...props}
/> />
) : ( ) : (

View File

@@ -40,11 +40,11 @@ export function DocumentList<T extends Document>({
return ( return (
<section className="w-full max-w-2xl mx-auto"> <section className="w-full max-w-2xl mx-auto">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between my-4">
<h2 className="text-xl font-bold text-slate-100">{title}</h2> <h2 className="text-xl font-bold text-slate-100">{title}</h2>
<button <button
type="button" type="button"
className="inline-flex items-center justify-center rounded-full bg-violet-600 hover:bg-violet-700 text-white w-9 h-9 focus:outline-none focus:ring-2 focus:ring-violet-400" className="inline-flex items-center justify-center rounded-full bg-violet-600 hover:bg-violet-700 text-white w-8 h-8 focus:outline-none focus:ring-2 focus:ring-violet-400"
aria-label="Add new item" aria-label="Add new item"
onClick={() => setOpen(true)} onClick={() => setOpen(true)}
> >

View File

@@ -3,8 +3,9 @@ import { pb } from "@/lib/pocketbase";
import type { Document, RelationshipType } from "@/lib/types"; import type { Document, RelationshipType } from "@/lib/types";
import { useState } from "react"; import { useState } from "react";
import { Loader } from "./Loader"; import { Loader } from "./Loader";
import { DocumentForm } from "./documents/DocumentForm"; import { NewRelatedDocumentForm } from "./documents/NewRelatedDocumentForm";
import { DocumentRow } from "./documents/DocumentRow"; import { DocumentRow } from "./documents/DocumentRow";
import { displayName } from "@/lib/relationships";
interface RelationshipListProps { interface RelationshipListProps {
root: Document; root: Document;
@@ -61,14 +62,12 @@ export function RelationshipList({
return ( return (
<DocumentList <DocumentList
title={ title={displayName(relationshipType)}
relationshipType.charAt(0).toUpperCase() + relationshipType.slice(1)
}
items={items} items={items}
error={error} error={error}
renderRow={(document) => <DocumentRow document={document} />} renderRow={(document) => <DocumentRow document={document} />}
newItemForm={(onSubmit) => ( newItemForm={(onSubmit) => (
<DocumentForm <NewRelatedDocumentForm
campaignId={root.campaign} campaignId={root.campaign}
relationshipType={relationshipType} relationshipType={relationshipType}
onCreate={async (doc: Document) => { onCreate={async (doc: Document) => {

View File

@@ -1,10 +1,10 @@
import { RelationshipType, type CampaignId, type Document } from "@/lib/types"; import { RelationshipType, type CampaignId, type Document } from "@/lib/types";
import { LocationForm } from "./location/LocationForm"; import { NewLocationForm } from "./location/NewLocationForm";
import { MonsterForm } from "./monsters/MonsterForm"; import { NewMonsterForm } from "./monsters/NewMonsterForm";
import { NpcForm } from "./npc/NpcForm"; import { NewNpcForm } from "./npc/NewNpcForm";
import { SceneForm } from "./scene/SceneForm"; import { NewSceneForm } from "./scene/NewSceneForm";
import { SecretForm } from "./secret/SecretForm"; import { NewSecretForm } from "./secret/NewSecretForm";
import { TreasureForm } from "./treasure/TreasureForm"; import { NewTreasureForm } from "./treasure/NewTreasureForm";
function assertUnreachable(_x: never): never { function assertUnreachable(_x: never): never {
throw new Error("DocumentForm switch is not exhaustive"); throw new Error("DocumentForm switch is not exhaustive");
@@ -13,7 +13,7 @@ function assertUnreachable(_x: never): never {
/** /**
* Renders a form for any document type depending on the relationship. * Renders a form for any document type depending on the relationship.
*/ */
export const DocumentForm = ({ export const NewRelatedDocumentForm = ({
campaignId, campaignId,
relationshipType, relationshipType,
onCreate, onCreate,
@@ -24,19 +24,19 @@ export const DocumentForm = ({
}) => { }) => {
switch (relationshipType) { switch (relationshipType) {
case RelationshipType.Locations: case RelationshipType.Locations:
return <LocationForm campaign={campaignId} onCreate={onCreate} />; return <NewLocationForm campaign={campaignId} onCreate={onCreate} />;
case RelationshipType.Monsters: case RelationshipType.Monsters:
return <MonsterForm campaign={campaignId} onCreate={onCreate} />; return <NewMonsterForm campaign={campaignId} onCreate={onCreate} />;
case RelationshipType.Npcs: case RelationshipType.Npcs:
return <NpcForm campaign={campaignId} onCreate={onCreate} />; return <NewNpcForm campaign={campaignId} onCreate={onCreate} />;
case RelationshipType.Secrets: case RelationshipType.Secrets:
return <SecretForm campaign={campaignId} onCreate={onCreate} />; return <NewSecretForm campaign={campaignId} onCreate={onCreate} />;
case RelationshipType.Treasures:
return <NewTreasureForm campaign={campaignId} onCreate={onCreate} />;
case RelationshipType.Scenes:
return <NewSceneForm campaign={campaignId} onCreate={onCreate} />;
case RelationshipType.DiscoveredIn: case RelationshipType.DiscoveredIn:
return "Form not supported here"; return "Form not supported here";
case RelationshipType.Treasures:
return <TreasureForm campaign={campaignId} onCreate={onCreate} />;
case RelationshipType.Scenes:
return <SceneForm campaign={campaignId} onCreate={onCreate} />;
} }
return assertUnreachable(relationshipType); return assertUnreachable(relationshipType);

View File

@@ -5,7 +5,7 @@ import { pb } from "@/lib/pocketbase";
/** /**
* Renders a form to add a new location. Calls onCreate with the new location document. * Renders a form to add a new location. Calls onCreate with the new location document.
*/ */
export const LocationForm = ({ export const NewLocationForm = ({
campaign, campaign,
onCreate, onCreate,
}: { }: {

View File

@@ -5,7 +5,7 @@ import { pb } from "@/lib/pocketbase";
/** /**
* Renders a form to add a new monster. Calls onCreate with the new monster document. * Renders a form to add a new monster. Calls onCreate with the new monster document.
*/ */
export const MonsterForm = ({ export const NewMonsterForm = ({
campaign, campaign,
onCreate, onCreate,
}: { }: {

View File

@@ -5,7 +5,7 @@ import { pb } from "@/lib/pocketbase";
/** /**
* Renders a form to add a new npc. Calls onCreate with the new npc document. * Renders a form to add a new npc. Calls onCreate with the new npc document.
*/ */
export const NpcForm = ({ export const NewNpcForm = ({
campaign, campaign,
onCreate, onCreate,
}: { }: {

View File

@@ -7,7 +7,7 @@ import { pb } from "@/lib/pocketbase";
/** /**
* Renders a form to add a new scene. Calls onCreate with the new scene document. * Renders a form to add a new scene. Calls onCreate with the new scene document.
*/ */
export const SceneForm = ({ export const NewSceneForm = ({
campaign, campaign,
onCreate, onCreate,
}: { }: {

View File

@@ -7,7 +7,7 @@ import { pb } from "@/lib/pocketbase";
/** /**
* Renders a form to add a new secret. Calls onCreate with the new secret document. * Renders a form to add a new secret. Calls onCreate with the new secret document.
*/ */
export const SecretForm = ({ export const NewSecretForm = ({
campaign, campaign,
onCreate, onCreate,
}: { }: {

View File

@@ -1,7 +1,7 @@
import { AutoSaveTextarea } from "@/components/AutoSaveTextarea"; import { AutoSaveTextarea } from "@/components/AutoSaveTextarea";
import type { Session } from "@/lib/types"; import type { Session } from "@/lib/types";
export const SessionForm = ({ export const EditSessionForm = ({
session, session,
onSubmit, onSubmit,
}: { }: {

View File

@@ -7,7 +7,7 @@ import { pb } from "@/lib/pocketbase";
/** /**
* Renders a form to add a new treasure. Calls onCreate with the new treasure document. * Renders a form to add a new treasure. Calls onCreate with the new treasure document.
*/ */
export const TreasureForm = ({ export const NewTreasureForm = ({
campaign, campaign,
onCreate, onCreate,
}: { }: {

5
src/lib/relationships.ts Normal file
View File

@@ -0,0 +1,5 @@
import type { RelationshipType } from "./types";
export function displayName(relationshipType: RelationshipType) {
return relationshipType.charAt(0).toUpperCase() + relationshipType.slice(1);
}

View File

@@ -11,7 +11,7 @@ export const Route = createFileRoute("/_app")({
function AppHeader() { function AppHeader() {
const { user, logout, isLoading } = useAuth(); const { user, logout, isLoading } = useAuth();
return ( return (
<header className="flex items-center justify-between px-8 py-4 border-b border-slate-700 bg-slate-900"> <header className="flex flex-wrap items-center justify-between px-8 py-4 border-b border-slate-700 bg-slate-900">
<h1 className="text-2xl font-bold text-slate-100 m-0"> <h1 className="text-2xl font-bold text-slate-100 m-0">
DM's Table Companion DM's Table Companion
</h1> </h1>

View File

@@ -8,7 +8,9 @@ import {
type Document, type Document,
} from "@/lib/types"; } from "@/lib/types";
import { RelationshipList } from "@/components/RelationshipList"; import { RelationshipList } from "@/components/RelationshipList";
import { SessionForm } from "@/components/documents/session/SessionForm"; import { EditSessionForm } from "@/components/documents/session/EditSessionForm";
import { displayName } from "@/lib/relationships";
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from "@headlessui/react";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_app/_authenticated/document/$documentId", "/_app/_authenticated/document/$documentId",
@@ -47,8 +49,17 @@ function RouteComponent() {
console.log("Parsed data: ", relationships); console.log("Parsed data: ", relationships);
const relationshipList = [
RelationshipType.Scenes,
RelationshipType.Secrets,
RelationshipType.Locations,
RelationshipType.Npcs,
RelationshipType.Monsters,
RelationshipType.Treasures,
];
return ( return (
<div className="max-w-xl mx-auto py-8"> <div className="max-w-xl mx-auto py-2 px-4">
<Link <Link
to="/document/$documentId/print" to="/document/$documentId/print"
params={{ documentId: session.id }} params={{ documentId: session.id }}
@@ -56,22 +67,31 @@ function RouteComponent() {
> >
Print Print
</Link> </Link>
<SessionForm session={session as Session} onSubmit={handleSaveSession} /> <EditSessionForm
{[ session={session as Session}
RelationshipType.Scenes, onSubmit={handleSaveSession}
RelationshipType.Secrets, />
RelationshipType.Locations, <TabGroup>
RelationshipType.Npcs, <TabList className="flex flex-row flex-wrap gap-1 mt-2">
RelationshipType.Monsters, {relationshipList.map((relationshipType) => (
RelationshipType.Treasures, <Tab className="px-3 py-2 rounded bg-slate-800 text-slate-100 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-violet-500 data-selected:bg-violet-900 data-selected:border-violet-700">
].map((relationshipType) => ( {displayName(relationshipType)}
</Tab>
))}
</TabList>
<TabPanels>
{relationshipList.map((relationshipType) => (
<TabPanel>
<RelationshipList <RelationshipList
key={relationshipType} key={relationshipType}
root={session} root={session}
relationshipType={relationshipType} relationshipType={relationshipType}
items={relationships[relationshipType] ?? []} items={relationships[relationshipType] ?? []}
/> />
</TabPanel>
))} ))}
</TabPanels>
</TabGroup>
</div> </div>
); );
} }