Makes campaigns load all types of docs and then link to the docs
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
||||
import { Fragment, useCallback, useState } from "react";
|
||||
|
||||
type Props<T extends AnyDocument> = {
|
||||
title: React.ReactNode;
|
||||
title?: React.ReactNode;
|
||||
error?: React.ReactNode;
|
||||
items: T[];
|
||||
renderRow: (item: T) => React.ReactNode;
|
||||
@@ -49,7 +49,7 @@ export function DocumentList<T extends AnyDocument>({
|
||||
return (
|
||||
<section className="w-full">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-bold text-slate-100">{title}</h2>
|
||||
{title && <h2 className="text-xl font-bold text-slate-100">{title}</h2>}
|
||||
<div className="flex gap-2">
|
||||
{isEditing && (
|
||||
<button
|
||||
|
||||
51
src/components/campaign/CampaignDocuments.tsx
Normal file
51
src/components/campaign/CampaignDocuments.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
type AnyDocument,
|
||||
type CampaignId,
|
||||
type DocumentType,
|
||||
} from "@/lib/types";
|
||||
import { useDocumentCache } from "@/context/document/hooks";
|
||||
import { DocumentList } from "../DocumentList";
|
||||
import { getAllDocumentsOfType } from "@/context/document/state";
|
||||
import { DocumentRow } from "../documents/DocumentRow";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export type Props = {
|
||||
campaignId: CampaignId;
|
||||
docType: DocumentType;
|
||||
};
|
||||
|
||||
export const CampaignDocuments = ({ campaignId, docType }: Props) => {
|
||||
const { cache, dispatch } = useDocumentCache();
|
||||
|
||||
const items = getAllDocumentsOfType(docType, cache);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchDocuments() {
|
||||
const documents: AnyDocument[] = await pb
|
||||
.collection("documents")
|
||||
.getFullList({
|
||||
filter: `campaign = "${campaignId}" && type = "${docType}"`,
|
||||
sort: "created",
|
||||
});
|
||||
|
||||
for (const doc of documents) {
|
||||
dispatch({
|
||||
type: "setDocument",
|
||||
doc,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fetchDocuments();
|
||||
}, [campaignId, docType]);
|
||||
|
||||
return (
|
||||
<DocumentList
|
||||
items={items}
|
||||
renderRow={(doc) => <DocumentRow document={doc} />}
|
||||
newItemForm={() => <div>New Item Form</div>}
|
||||
removeItem={() => console.error("TODO")}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,13 +1,25 @@
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { FormattedText } from "../FormattedText";
|
||||
import type { DocumentId } from "@/lib/types";
|
||||
|
||||
export type Props = {
|
||||
id: DocumentId;
|
||||
title?: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export const BasicPreview = ({ title, description }: Props) => {
|
||||
export const BasicPreview = ({ id, title, description }: Props) => {
|
||||
return (
|
||||
<div>
|
||||
<Link
|
||||
to="/document/$documentId/$"
|
||||
params={{
|
||||
documentId: id,
|
||||
}}
|
||||
className="!no-underline text-violet-400 hover:underline hover:text-violet-500"
|
||||
>
|
||||
View
|
||||
</Link>
|
||||
{title && <h4 className="font-bold">{title}</h4>}
|
||||
{description && <FormattedText>{description}</FormattedText>}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { makeDocumentPath, useDocumentPath } from "@/lib/documentPath";
|
||||
import type { DocumentId, RelationshipType } from "@/lib/types";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import type { DocumentId } from "@/lib/types";
|
||||
import { Link, useParams, useSearch } from "@tanstack/react-router";
|
||||
|
||||
export type Props = React.PropsWithChildren<{
|
||||
childDocId: DocumentId;
|
||||
@@ -8,13 +8,37 @@ export type Props = React.PropsWithChildren<{
|
||||
}>;
|
||||
|
||||
export function DocumentLink({ childDocId, className, children }: Props) {
|
||||
const { documentId, relationshipType } = useDocumentPath();
|
||||
const docPath = useDocumentPath();
|
||||
|
||||
const params = useParams({
|
||||
strict: false,
|
||||
});
|
||||
|
||||
const campaignSearch = useSearch({
|
||||
from: "/_app/_authenticated/campaigns/$campaignId",
|
||||
shouldThrow: false,
|
||||
});
|
||||
|
||||
const to = params.campaignId
|
||||
? `/campaigns/${params.campaignId}`
|
||||
: docPath
|
||||
? makeDocumentPath(
|
||||
docPath.documentId,
|
||||
docPath?.relationshipType,
|
||||
childDocId,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const search = campaignSearch
|
||||
? { tab: campaignSearch.tab, docId: childDocId }
|
||||
: undefined;
|
||||
|
||||
if (to === undefined) {
|
||||
throw new Error("Not in a document or campaign context");
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={makeDocumentPath(documentId, relationshipType, childDocId)}
|
||||
className={className}
|
||||
>
|
||||
<Link to={to} search={search} className={className}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -35,17 +35,19 @@ const ShowDocument = ({ doc }: { doc: AnyDocument }) => {
|
||||
case "location":
|
||||
return (
|
||||
<BasicPreview
|
||||
id={doc.id}
|
||||
title={doc.data.name}
|
||||
description={doc.data.description}
|
||||
/>
|
||||
);
|
||||
|
||||
case "monster":
|
||||
return <BasicPreview title={doc.data.name} />;
|
||||
return <BasicPreview id={doc.id} title={doc.data.name} />;
|
||||
|
||||
case "npc":
|
||||
return (
|
||||
<BasicPreview
|
||||
id={doc.id}
|
||||
title={doc.data.name}
|
||||
description={doc.data.description}
|
||||
/>
|
||||
@@ -53,16 +55,20 @@ const ShowDocument = ({ doc }: { doc: AnyDocument }) => {
|
||||
|
||||
case "session":
|
||||
return (
|
||||
<BasicPreview title={doc.created} description={doc.data.strongStart} />
|
||||
<BasicPreview
|
||||
id={doc.id}
|
||||
title={doc.created}
|
||||
description={doc.data.strongStart}
|
||||
/>
|
||||
);
|
||||
|
||||
case "secret":
|
||||
return <BasicPreview title={doc.data.text} />;
|
||||
return <BasicPreview id={doc.id} title={doc.data.text} />;
|
||||
|
||||
case "scene":
|
||||
return <BasicPreview description={doc.data.text} />;
|
||||
return <BasicPreview id={doc.id} description={doc.data.text} />;
|
||||
|
||||
case "treasure":
|
||||
return <BasicPreview title={doc.data.text} />;
|
||||
return <BasicPreview id={doc.id} title={doc.data.text} />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -41,8 +41,9 @@ export function DocumentView({
|
||||
navigation={
|
||||
<>
|
||||
<Link
|
||||
to={CampaignRoute.to}
|
||||
to="/campaigns/$campaignId"
|
||||
params={{ campaignId: doc.campaign }}
|
||||
search={{ tab: "sessions" }}
|
||||
className="text-slate-400 hover:text-violet-400 text-sm underline underline-offset-2 transition-colors"
|
||||
>
|
||||
← Back to campaign
|
||||
|
||||
@@ -23,7 +23,7 @@ export function TabbedLayout({
|
||||
{tabs}
|
||||
</div>
|
||||
<div
|
||||
className={`grow p-2 bg-slate-800 border-t border-b border-r border-slate-700 ${flyout && "hidden"} md:block`}
|
||||
className={`grow md:w-md p-2 bg-slate-800 border-t border-b border-r border-slate-700 ${flyout && "hidden"} md:block`}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user