Convert rendering to jsx
This commit is contained in:
parent
0f72d048ee
commit
d1b632e83c
25 changed files with 422 additions and 513 deletions
|
@ -1,4 +1,5 @@
|
|||
import type { Ability } from "@proscenium/playbill";
|
||||
import { HTML } from "./base/html";
|
||||
|
||||
interface AbilityCardProps {
|
||||
ability: Ability;
|
||||
|
@ -21,18 +22,18 @@ export function AbilityCard({ ability }: AbilityCardProps) {
|
|||
|
||||
const costList =
|
||||
costs.length > 0
|
||||
? `<ul class="ability-costs">${costs.join("\n")}</ul>`
|
||||
? <ul className="ability-costs">{costs}</ul>
|
||||
: "";
|
||||
|
||||
const classList = `ability ability-${ability.type}`;
|
||||
|
||||
return <div className={classList} data-component-id={ability.id}>
|
||||
<div className="ability-header">
|
||||
<h4>${ability.name}</h4>
|
||||
<span className="ability-type">${ability.type}</span>
|
||||
<span className="ability-cost-xp">${ability.xp} XP</span>
|
||||
${costList}
|
||||
<h4>{ability.name}</h4>
|
||||
<span className="ability-type">{ability.type}</span>
|
||||
<span className="ability-cost-xp">{ability.xp} XP</span>
|
||||
{costList}
|
||||
</div>
|
||||
<div className="ability-content" dangerouslySetInnerHTML={{ __html: ability.description }} />
|
||||
<HTML className="ability-content" html={ability.description} />
|
||||
</div>
|
||||
}
|
||||
|
|
8
src/render/html/component/base/html.tsx
Normal file
8
src/render/html/component/base/html.tsx
Normal file
|
@ -0,0 +1,8 @@
|
|||
interface HTMLWrapperProps {
|
||||
html: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function HTML({ html, className }: HTMLWrapperProps) {
|
||||
return <div className={className} dangerouslySetInnerHTML={{ __html: html }} />;
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
import { html } from "#lib";
|
||||
|
||||
interface SectionProps {
|
||||
type: string;
|
||||
title: string;
|
||||
content: string;
|
||||
preInfo?: string;
|
||||
info?: string;
|
||||
componentId?: string;
|
||||
leftCornerTag?: string;
|
||||
rightCornerTag?: string;
|
||||
}
|
||||
|
||||
export async function Section({
|
||||
type,
|
||||
title,
|
||||
content,
|
||||
preInfo = "",
|
||||
info = "",
|
||||
componentId,
|
||||
leftCornerTag = "",
|
||||
rightCornerTag = "",
|
||||
}: SectionProps): Promise<string> {
|
||||
const dataTags = componentId ? `data-component-id="${componentId}"` : "";
|
||||
return html`
|
||||
<section class="section ${type}" ${dataTags}>
|
||||
<div class="section-header ${type}-header">
|
||||
${preInfo}
|
||||
<h2>${title}</h2>
|
||||
${info}
|
||||
</div>
|
||||
<div class="section-tag section-tag--left">${leftCornerTag}</div>
|
||||
<div class="section-tag section-tag--right">${rightCornerTag}</div>
|
||||
<div class="section-content ${type}-content">
|
||||
${content}
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
}
|
|
@ -4,7 +4,7 @@ import React from "react";
|
|||
interface SectionProps {
|
||||
type: string;
|
||||
title: string;
|
||||
content: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
preInfo?: React.ReactNode;
|
||||
info?: React.ReactNode;
|
||||
componentId?: string;
|
||||
|
@ -15,7 +15,7 @@ interface SectionProps {
|
|||
export function Section({
|
||||
type,
|
||||
title,
|
||||
content,
|
||||
children,
|
||||
preInfo,
|
||||
info,
|
||||
componentId,
|
||||
|
@ -26,8 +26,6 @@ export function Section({
|
|||
const headerClasses = classNames("section-header", `${type}-header`);
|
||||
const contentClasses = classNames("section-content", `${type}-content`);
|
||||
|
||||
const contentDiv = typeof content === "string" ? <div className={contentClasses} dangerouslySetInnerHTML={{ __html: content }} /> : <div className={contentClasses}>{content}</div>;
|
||||
|
||||
return <section className={sectionClasses} data-component-id={componentId}>
|
||||
<div className={headerClasses}>
|
||||
{preInfo}
|
||||
|
@ -36,7 +34,9 @@ export function Section({
|
|||
</div>
|
||||
<div className="section-tag section-tag--left">{leftCornerTag}</div>
|
||||
<div className="section-tag section-tag--right">{rightCornerTag}</div>
|
||||
{contentDiv}
|
||||
<div className={contentClasses}>
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
import { html } from "#lib";
|
||||
import { Section } from "./base/section";
|
||||
|
||||
export async function GlossaryTable(
|
||||
glossary: Record<string, string[]>,
|
||||
): Promise<string> {
|
||||
const terms = Object.entries(glossary).map(([term, defs]) => {
|
||||
return html`<dt>${term}</dt>${defs.map((d) => html`<dd>${d}</dd>`).join("\n")}`;
|
||||
});
|
||||
|
||||
const content = html`
|
||||
<div class="glossary-content">
|
||||
<dl>
|
||||
${terms.join("\n")}
|
||||
</dl>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return Section({
|
||||
type: "glossary",
|
||||
componentId: "glossary",
|
||||
title: "Glossary",
|
||||
content,
|
||||
});
|
||||
}
|
23
src/render/html/component/glossary.tsx
Normal file
23
src/render/html/component/glossary.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { Section } from "./base/section";
|
||||
|
||||
interface GlossaryTableProps {
|
||||
glossary: Record<string, string[]>;
|
||||
}
|
||||
|
||||
export function GlossaryTable(
|
||||
{ glossary }: GlossaryTableProps,
|
||||
) {
|
||||
return <Section type="glossary" componentId="glossary" title="Glossary">
|
||||
<div className="glossary-content">
|
||||
<dl>
|
||||
{Object.entries(glossary).map(([term, defs]) => {
|
||||
return <div key={term}>
|
||||
<dt>{term}</dt>
|
||||
{defs.map((d, i) => <dd key={i}>{d}</dd>)}
|
||||
</div>
|
||||
})}
|
||||
</dl>
|
||||
</div>
|
||||
</Section>
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import type { Playbill } from "@proscenium/playbill";
|
||||
import { html } from "#lib";
|
||||
|
||||
export async function PageHeader(playbill: Playbill): Promise<string> {
|
||||
return html`
|
||||
<header>
|
||||
<h1 class="header-title">${playbill.name}</h1>
|
||||
<p class="header-byline">A Playbill by ${playbill.author}</p>
|
||||
<p><small class="header-info">Version ${playbill.version}</small></p>
|
||||
</header>
|
||||
`;
|
||||
}
|
13
src/render/html/component/header.tsx
Normal file
13
src/render/html/component/header.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import type { Playbill } from "@proscenium/playbill";
|
||||
|
||||
interface PlaybillHeaderProps {
|
||||
playbill: Playbill;
|
||||
}
|
||||
|
||||
export function PageHeader({ playbill }: PlaybillHeaderProps) {
|
||||
return <header>
|
||||
<h1 className="header-title">{playbill.name}</h1>
|
||||
<p className="header-byline">A Playbill by {playbill.author} </p>
|
||||
<p> <small className="header-info"> Version {playbill.version}</small></p>
|
||||
</header>
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
import type { Item } from "@proscenium/playbill";
|
||||
import { capitalize } from "lodash-es";
|
||||
import { html, renderMarkdown } from "#lib";
|
||||
import { Section } from "./base/section";
|
||||
|
||||
export async function ItemCard(item: Item): Promise<string> {
|
||||
const renderedMarkdown = await renderMarkdown(item.description);
|
||||
|
||||
let itemTypeDescriptor = "";
|
||||
|
||||
switch (item.type) {
|
||||
case "wielded":
|
||||
itemTypeDescriptor = `Wielded (${item.hands} ${item.hands === 1 ? "Hand" : "Hands"})`;
|
||||
break;
|
||||
case "worn":
|
||||
itemTypeDescriptor = `Worn (${item.slot})`;
|
||||
break;
|
||||
case "trinket":
|
||||
itemTypeDescriptor = "Trinket";
|
||||
break;
|
||||
}
|
||||
|
||||
const affinity =
|
||||
item.affinity !== "none"
|
||||
? html`<p class="item-affinity ${item.affinity}">${item.affinity} Affinity</p>`
|
||||
: "";
|
||||
|
||||
const damageType = item.damage?.type === "arc" ? "Arcane" : "Physical";
|
||||
const damageAmount = item.damage?.amount ?? 0;
|
||||
const damageRating =
|
||||
damageAmount > 0
|
||||
? html`<p class="item-damage ${damageType}">${damageAmount} ${damageType} Damage</p>`
|
||||
: "";
|
||||
|
||||
const info = html`
|
||||
<div class="item-info">
|
||||
${affinity}
|
||||
${damageRating}
|
||||
</div>
|
||||
`;
|
||||
|
||||
return Section({
|
||||
type: "item",
|
||||
componentId: item.id,
|
||||
title: item.name,
|
||||
content: renderedMarkdown,
|
||||
info,
|
||||
rightCornerTag: capitalize(item.rarity),
|
||||
leftCornerTag: itemTypeDescriptor,
|
||||
});
|
||||
}
|
53
src/render/html/component/item.tsx
Normal file
53
src/render/html/component/item.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import type { Item } from "@proscenium/playbill";
|
||||
import { capitalize } from "lodash-es";
|
||||
import { Section } from "./base/section";
|
||||
import { HTML } from "./base/html";
|
||||
|
||||
interface ItemSectionProps {
|
||||
item: Item;
|
||||
}
|
||||
|
||||
export function ItemCard({ item }: ItemSectionProps) {
|
||||
let itemTypeDescriptor = "";
|
||||
|
||||
switch (item.type) {
|
||||
case "wielded":
|
||||
itemTypeDescriptor = `Wielded (${item.hands} ${item.hands === 1 ? "Hand" : "Hands"})`;
|
||||
break;
|
||||
case "worn":
|
||||
itemTypeDescriptor = `Worn (${item.slot})`;
|
||||
break;
|
||||
case "trinket":
|
||||
itemTypeDescriptor = "Trinket";
|
||||
break;
|
||||
}
|
||||
|
||||
const infoParts: React.ReactNode[] = [];
|
||||
|
||||
if (item.affinity !== "none") {
|
||||
infoParts.push(
|
||||
<p key="affinity" className={`item-affinity ${item.affinity}`}>
|
||||
{item.affinity} Affinity
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.damage !== null && item.damage.amount > 0) {
|
||||
infoParts.push(
|
||||
<p key="damage" className={`item-damage ${item.damage.type}`}>
|
||||
{item.damage.amount} {capitalize(item.damage.type)} Damage
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return <Section
|
||||
type="item"
|
||||
componentId={item.id}
|
||||
title={item.name}
|
||||
info={<div className="item-info">{infoParts}</div>}
|
||||
rightCornerTag={capitalize(item.rarity)}
|
||||
leftCornerTag={itemTypeDescriptor}
|
||||
>
|
||||
<HTML html={item.description} />
|
||||
</Section>
|
||||
}
|
|
@ -1,8 +1,13 @@
|
|||
import type { Method, Playbill } from "@proscenium/playbill";
|
||||
import { AbilityCard } from "./ability";
|
||||
import { Section } from "./base/react-section";
|
||||
import { Section } from "./base/section";
|
||||
|
||||
export function MethodSection(method: Method, playbill: Playbill) {
|
||||
interface MethodSectionProps {
|
||||
method: Method;
|
||||
playbill: Playbill;
|
||||
}
|
||||
|
||||
export function MethodSection({ method, playbill }: MethodSectionProps) {
|
||||
const ranks = method.abilities.map((rank, i) => {
|
||||
return <div className="method-rank" key={i}>
|
||||
<h3>Rank {i + 1}</h3>
|
||||
|
@ -20,9 +25,9 @@ export function MethodSection(method: Method, playbill: Playbill) {
|
|||
type="method"
|
||||
componentId={method.id}
|
||||
title={method.name}
|
||||
preInfo={<p className="method-curator">{method.curator}</p>}
|
||||
info={<p className="method-description">{method.description}</p>}
|
||||
content={<div className="method-ranks">{ranks}</div>}
|
||||
/>
|
||||
|
||||
preInfo={<p className="method-curator">{method.curator}'s Method of</p>}
|
||||
info={<p className="method-description" dangerouslySetInnerHTML={{ __html: method.description }}></p>}
|
||||
>
|
||||
<div className="method-ranks">{ranks}</div>
|
||||
</Section>
|
||||
}
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
import type {
|
||||
ImageResource,
|
||||
Resource,
|
||||
TableResource,
|
||||
TextResource,
|
||||
} from "@proscenium/playbill";
|
||||
import { html, renderMarkdown } from "#lib";
|
||||
import { Section } from "./base/section";
|
||||
|
||||
async function TextResourceSection(resource: TextResource): Promise<string> {
|
||||
const renderedMarkdown = await renderMarkdown(resource.description);
|
||||
|
||||
const info = resource.categories
|
||||
.map((category) => {
|
||||
return html`<li>${category}</li>`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return Section({
|
||||
type: "resource",
|
||||
componentId: resource.id,
|
||||
title: resource.name,
|
||||
content: renderedMarkdown,
|
||||
info: `<ul class="resource-categories">${info}</ul>`,
|
||||
});
|
||||
}
|
||||
|
||||
async function ImageResourceSection(resource: ImageResource): Promise<string> {
|
||||
const renderedMarkdown = await renderMarkdown(resource.description);
|
||||
|
||||
const info = resource.categories
|
||||
.map((category) => {
|
||||
return html`<li>${category}</li>`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const fig = html`
|
||||
<figure class="resource-image">
|
||||
<img src="${resource.url}" alt="${resource.name}" />
|
||||
<figcaption>${renderedMarkdown}</figcaption>
|
||||
</figure>
|
||||
`;
|
||||
|
||||
return Section({
|
||||
type: "resource",
|
||||
componentId: resource.id,
|
||||
title: resource.name,
|
||||
content: fig,
|
||||
info: `<ul class="resource-categories">${info}</ul>`,
|
||||
});
|
||||
}
|
||||
|
||||
async function TableResourceSection(resource: TableResource): Promise<string> {
|
||||
const renderedMarkdown = await renderMarkdown(resource.description);
|
||||
|
||||
const info = resource.categories
|
||||
.map((category) => {
|
||||
return html`<li>${category}</li>`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const tableData = resource.data;
|
||||
|
||||
const [headerRow, ...rows] = tableData;
|
||||
|
||||
let tableHTML = html`<table class="resource-table"><thead><tr>`;
|
||||
|
||||
for (const header of headerRow) {
|
||||
tableHTML += html`<th>${await renderMarkdown(header)}</th>`;
|
||||
}
|
||||
|
||||
tableHTML += html`</tr></thead><tbody>`;
|
||||
|
||||
for (const row of rows) {
|
||||
tableHTML += html`<tr>`;
|
||||
for (const cell of row) {
|
||||
tableHTML += html`<td>${await renderMarkdown(cell)}</td>`;
|
||||
}
|
||||
tableHTML += html`</tr>`;
|
||||
}
|
||||
|
||||
tableHTML += html`</tbody></table>`;
|
||||
|
||||
return Section({
|
||||
type: "resource",
|
||||
componentId: resource.id,
|
||||
title: resource.name,
|
||||
content: renderedMarkdown + tableHTML,
|
||||
info: `<ul class="resource-categories">${info}</ul>`,
|
||||
});
|
||||
}
|
||||
|
||||
export async function ResourceSection(resource: Resource): Promise<string> {
|
||||
switch (resource.type) {
|
||||
case "text":
|
||||
return TextResourceSection(resource);
|
||||
case "image":
|
||||
return ImageResourceSection(resource);
|
||||
case "table":
|
||||
return TableResourceSection(resource);
|
||||
}
|
||||
}
|
87
src/render/html/component/resource.tsx
Normal file
87
src/render/html/component/resource.tsx
Normal file
|
@ -0,0 +1,87 @@
|
|||
import type {
|
||||
ImageResource,
|
||||
Resource,
|
||||
TableResource,
|
||||
TextResource,
|
||||
} from "@proscenium/playbill";
|
||||
import { Section } from "./base/section";
|
||||
import { HTML } from "./base/html";
|
||||
|
||||
interface TextResourceSectionProps {
|
||||
resource: TextResource;
|
||||
}
|
||||
|
||||
function TextResourceSection({ resource }: TextResourceSectionProps) {
|
||||
return <Section
|
||||
type="resource"
|
||||
componentId={resource.id}
|
||||
title={resource.name}
|
||||
info={<ul className="resource-categories">{
|
||||
resource.categories.map((category) => <li key={category}>{category}</li>)
|
||||
}</ul>}
|
||||
><HTML html={resource.description} /></Section>
|
||||
}
|
||||
|
||||
interface ImageResourceSectionProps {
|
||||
resource: ImageResource;
|
||||
}
|
||||
|
||||
function ImageResourceSection({ resource }: ImageResourceSectionProps) {
|
||||
return <Section
|
||||
type="resource"
|
||||
componentId={resource.id}
|
||||
title={resource.name}
|
||||
info={<ul className="resource-categories">{
|
||||
resource.categories.map((category) => <li key={category}>{category}</li>)
|
||||
}</ul>}
|
||||
>
|
||||
<figure className="resource-image">
|
||||
<img src={resource.url} alt={resource.name} />
|
||||
<figcaption dangerouslySetInnerHTML={{ __html: resource.description }} />
|
||||
</figure>
|
||||
</Section>
|
||||
}
|
||||
|
||||
interface TableResourceSectionProps {
|
||||
resource: TableResource;
|
||||
}
|
||||
|
||||
function TableResourceSection({ resource }: TableResourceSectionProps) {
|
||||
const [headers, ...rows] = resource.data;
|
||||
return <Section
|
||||
type="resource"
|
||||
componentId={resource.id}
|
||||
title={resource.name}
|
||||
info={<ul className="resource-categories">{
|
||||
resource.categories.map((category) => <li key={category}>{category}</li>)
|
||||
}</ul>}
|
||||
>
|
||||
<table className="resource-table">
|
||||
<thead>
|
||||
<tr>
|
||||
{headers.map((header) => <th key={header} dangerouslySetInnerHTML={{ __html: header }} />)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row, i) => <tr key={i}>{
|
||||
row.map((cell, j) => <td key={j} dangerouslySetInnerHTML={{ __html: cell }} />)
|
||||
}</tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
</Section>
|
||||
}
|
||||
|
||||
interface ResourceSectionProps {
|
||||
resource: Resource;
|
||||
}
|
||||
|
||||
export function ResourceSection({ resource }: ResourceSectionProps) {
|
||||
switch (resource.type) {
|
||||
case "text":
|
||||
return <TextResourceSection resource={resource} />;
|
||||
case "image":
|
||||
return <ImageResourceSection resource={resource} />;
|
||||
case "table":
|
||||
return <TableResourceSection resource={resource} />;
|
||||
}
|
||||
}
|
|
@ -1,11 +1,17 @@
|
|||
import type { Rule } from "@proscenium/playbill";
|
||||
import { Section } from "./base/react-section";
|
||||
import { Section } from "./base/section";
|
||||
import { HTML } from "./base/html";
|
||||
|
||||
export function RuleSection(rule: Rule) {
|
||||
interface RuleSectionProps {
|
||||
rule: Rule;
|
||||
}
|
||||
|
||||
export function RuleSection({ rule }: RuleSectionProps) {
|
||||
return <Section
|
||||
title={rule.name}
|
||||
componentId={rule.id}
|
||||
content={rule.description}
|
||||
type="rule"
|
||||
/>;
|
||||
>
|
||||
<HTML html={rule.description} />
|
||||
</Section>
|
||||
}
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
import type { Playbill, Species } from "@proscenium/playbill";
|
||||
import { html, renderMarkdown } from "#lib";
|
||||
import { AbilityCard } from "./ability";
|
||||
import { Section } from "./base/section";
|
||||
|
||||
export async function SpeciesSection(species: Species, playbill: Playbill) {
|
||||
const descriptionHTML = await renderMarkdown(species.description);
|
||||
const hpString = species.hp >= 0 ? `+${species.hp}` : `-${species.hp}`;
|
||||
const apString = species.ap >= 0 ? `+${species.ap}` : `-${species.ap}`;
|
||||
const epString = species.ep >= 0 ? `+${species.ep}` : `-${species.ep}`;
|
||||
|
||||
const hasAbilities = species.abilities.length > 0;
|
||||
|
||||
const abilitySection = hasAbilities
|
||||
? html`
|
||||
<div class="species-abilities">
|
||||
<h3>Innate Abilities</h3>
|
||||
</div>
|
||||
`
|
||||
: "";
|
||||
|
||||
const sectionContent = html`
|
||||
<div class="species-stats">
|
||||
|
||||
<div class="species-stat-hp">
|
||||
<h4 class="hp species-stat-hp-title" title="Health Points">HP</h4>
|
||||
<p class="species-stat-hp-value">${hpString}</p>
|
||||
</div>
|
||||
|
||||
<div class="species-stat-ap">
|
||||
<h4 class="ap species-stat-ap-title" title="Action Points">AP</h4>
|
||||
<p class="species-stat-ap-value">${apString}</p>
|
||||
</div>
|
||||
|
||||
<div class="species-stat-ep">
|
||||
<h4 class="ep species-stat-ep-title" title="Exhaustion Points">EP</h4>
|
||||
<p class="species-stat-ep-value">${epString}</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="species-talents">
|
||||
|
||||
<div class="species-talent-muscle">
|
||||
<h4 class="species-talent-muscle-title muscle">Muscle</h4>
|
||||
<p class="species-talent-muscle-value ${species.muscle}">${species.muscle}</p>
|
||||
</div>
|
||||
|
||||
<div class="species-talent-focus">
|
||||
<h4 class="species-talent-focus-title focus">Focus</h4>
|
||||
<p class="species-talent-focus-value ${species.focus}">${species.focus}</p>
|
||||
</div>
|
||||
|
||||
<div class="species-talent-knowledge">
|
||||
<h4 class="species-talent-knowledge-title knowledge">Knowledge</h4>
|
||||
<p class="species-talent-knowledge-value ${species.knowledge}">${species.knowledge}</p>
|
||||
</div>
|
||||
|
||||
<div class="species-talent-charm">
|
||||
<h4 class="species-talent-charm-title charm">Charm</h4>
|
||||
<p class="species-talent-charm-value ${species.charm}">${species.charm}</p>
|
||||
</div>
|
||||
|
||||
<div class="species-talent-cunning">
|
||||
<h4 class="species-talent-cunning-title cunning">Cunning</h4>
|
||||
<p class="species-talent-cunning-value ${species.cunning}">${species.cunning}</p>
|
||||
</div>
|
||||
|
||||
<div class="species-talent-spark">
|
||||
<h4 class="species-talent-spark-title spark">Spark</h4>
|
||||
<p class="species-talent-spark-value ${species.spark}">${species.spark}</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="species-content">
|
||||
${descriptionHTML}
|
||||
</div>
|
||||
|
||||
|
||||
${abilitySection}
|
||||
`;
|
||||
|
||||
return Section({
|
||||
type: "species",
|
||||
componentId: species.id,
|
||||
title: species.name,
|
||||
content: sectionContent,
|
||||
info: "<p>Species</p>",
|
||||
});
|
||||
}
|
91
src/render/html/component/species.tsx
Normal file
91
src/render/html/component/species.tsx
Normal file
|
@ -0,0 +1,91 @@
|
|||
import type { Playbill, Species } from "@proscenium/playbill";
|
||||
import { Section } from "./base/section";
|
||||
import { AbilityCard } from "./ability";
|
||||
import { HTML } from "./base/html";
|
||||
|
||||
interface SpeciesSectionProps {
|
||||
species: Species;
|
||||
playbill: Playbill;
|
||||
}
|
||||
|
||||
export function SpeciesSection({ species, playbill }: SpeciesSectionProps) {
|
||||
const hpString = species.hp >= 0 ? `+${species.hp}` : `-${species.hp}`;
|
||||
const apString = species.ap >= 0 ? `+${species.ap}` : `-${species.ap}`;
|
||||
const epString = species.ep >= 0 ? `+${species.ep}` : `-${species.ep}`;
|
||||
|
||||
const hasAbilities = species.abilities.length > 0;
|
||||
|
||||
const abilitySection = hasAbilities
|
||||
? (<div className="species-abilities">
|
||||
<h3>Innate Abilities</h3>
|
||||
{species.abilities.map((abilityId) => {
|
||||
return <AbilityCard ability={playbill.abilities[abilityId]} key={abilityId} />;
|
||||
})}
|
||||
</div>)
|
||||
: "";
|
||||
|
||||
const sectionContent = <>
|
||||
<div className="species-stats">
|
||||
|
||||
<div className="species-stat-hp">
|
||||
<h4 className="hp species-stat-hp-title" title="Health Points">HP</h4>
|
||||
<p className="species-stat-hp-value">{hpString}</p>
|
||||
</div>
|
||||
|
||||
<div className="species-stat-ap">
|
||||
<h4 className="ap species-stat-ap-title" title="Action Points">AP</h4>
|
||||
<p className="species-stat-ap-value">{apString}</p>
|
||||
</div>
|
||||
|
||||
<div className="species-stat-ep">
|
||||
<h4 className="ep species-stat-ep-title" title="Exhaustion Points">EP</h4>
|
||||
<p className="species-stat-ep-value">{epString}</p>
|
||||
</div>
|
||||
|
||||
</div >
|
||||
|
||||
<div className="species-talents">
|
||||
|
||||
<div className="species-talent-muscle">
|
||||
<h4 className="species-talent-muscle-title muscle">Muscle</h4>
|
||||
<p className={`species-talent-muscle-value ${species.muscle}`}>{species.muscle}</p>
|
||||
</div>
|
||||
|
||||
<div className="species-talent-focus">
|
||||
<h4 className="species-talent-focus-title focus">Focus</h4>
|
||||
<p className={`species-talent-focus-value ${species.focus}`}>{species.focus}</p>
|
||||
</div>
|
||||
|
||||
<div className="species-talent-knowledge">
|
||||
<h4 className="species-talent-knowledge-title knowledge">Knowledge</h4>
|
||||
<p className={`species-talent-knowledge-value ${species.knowledge}`}>{species.knowledge}</p>
|
||||
</div>
|
||||
|
||||
<div className="species-talent-charm">
|
||||
<h4 className="species-talent-charm-title charm">Charm</h4>
|
||||
<p className={`species-talent-charm-value ${species.charm}`}>{species.charm}</p>
|
||||
</div>
|
||||
|
||||
<div className="species-talent-cunning">
|
||||
<h4 className="species-talent-cunning-title cunning">Cunning</h4>
|
||||
<p className={`species-talent-cunning-value ${species.cunning}`}>{species.cunning}</p>
|
||||
</div>
|
||||
|
||||
<div className="species-talent-spark">
|
||||
<h4 className="species-talent-spark-title spark">Spark</h4>
|
||||
<p className={`species-talent-spark-value ${species.spark}`}>{species.spark}</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<HTML className="species-content" html={species.description} />
|
||||
{abilitySection}
|
||||
</>
|
||||
|
||||
return <Section
|
||||
type="species"
|
||||
componentId={species.id}
|
||||
title={species.name}
|
||||
info={<p>Species</p>}
|
||||
>{sectionContent}</Section>
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { Playbill } from "@proscenium/playbill";
|
||||
import { Playbill, type TaggedComponent } from "@proscenium/playbill";
|
||||
import sortBy from "lodash-es/sortBy";
|
||||
import { html, renderMarkdown } from "#lib";
|
||||
import { renderMarkdown } from "#lib";
|
||||
import { GlossaryTable } from "./component/glossary";
|
||||
import { PageHeader } from "./component/header";
|
||||
import { ItemCard } from "./component/item";
|
||||
|
@ -14,6 +14,22 @@ interface HTMLRenderOptions {
|
|||
styles?: string;
|
||||
}
|
||||
|
||||
async function processMarkdownInComponent(entry: TaggedComponent) {
|
||||
entry.component.description = await renderMarkdown(entry.component.description);
|
||||
|
||||
if (entry.type === "resource" && entry.component.type === "table") {
|
||||
const newData: string[][] = [];
|
||||
for (const row of entry.component.data) {
|
||||
const newRow: string[] = [];
|
||||
for (const cell of row) {
|
||||
newRow.push(await renderMarkdown(cell));
|
||||
}
|
||||
newData.push(newRow);
|
||||
}
|
||||
entry.component.data = newData;
|
||||
}
|
||||
}
|
||||
|
||||
export async function renderPlaybillToHTML(
|
||||
playbillToRender: Playbill,
|
||||
opts: HTMLRenderOptions = {},
|
||||
|
@ -21,46 +37,40 @@ export async function renderPlaybillToHTML(
|
|||
// Make a copy of the playbill to avoid modifying the original
|
||||
// and compile all description markdown
|
||||
const playbill = Playbill.fromSerialized(playbillToRender.serialize());
|
||||
await playbill.processComponents(renderMarkdown);
|
||||
|
||||
// Render various resources
|
||||
const resources = (
|
||||
await Promise.all(Object.values(playbill.resources).map(ResourceSection))
|
||||
).join("\n");
|
||||
|
||||
const methods = (
|
||||
await Promise.all(
|
||||
Object.values(playbill.methods).map((method) => {
|
||||
return MethodSection(method, playbill);
|
||||
}),
|
||||
)
|
||||
).join("\n");
|
||||
|
||||
const species = (
|
||||
await Promise.all(
|
||||
Object.values(playbill.species).map((species) => {
|
||||
return SpeciesSection(species, playbill);
|
||||
}),
|
||||
)
|
||||
).join("\n");
|
||||
|
||||
const items = (
|
||||
await Promise.all(
|
||||
Object.values(playbill.items).map((item) => {
|
||||
return ItemCard(item);
|
||||
}),
|
||||
)
|
||||
).join("\n");
|
||||
|
||||
await playbill.processComponents(processMarkdownInComponent);
|
||||
const rulesByOrder = sortBy(Object.values(playbill.rules), "order");
|
||||
const rules = rulesByOrder.map(RuleSection);
|
||||
const glossaryHTML = await GlossaryTable(playbill.glossary);
|
||||
|
||||
// Prepare stylesheet
|
||||
const css = opts.styles ? html`<style>${opts.styles}</style>` : "";
|
||||
const css = opts.styles ? `<style>${opts.styles}</style>` : "";
|
||||
|
||||
const body = renderToString(<>
|
||||
<article id="species" className="view">
|
||||
{Object.values(playbill.species).map((species) => <SpeciesSection key={species.id} species={species} playbill={playbill} />)}
|
||||
</article>
|
||||
|
||||
<article id="methods" className="view">
|
||||
{Object.values(playbill.methods).map((method) => <MethodSection key={method.id} method={method} playbill={playbill} />)}
|
||||
</article>
|
||||
|
||||
<article id="items" className="view">
|
||||
{Object.values(playbill.items).map((item) => <ItemCard key={item.id} item={item} />)}
|
||||
</article>
|
||||
|
||||
<article id="rules" className="view">
|
||||
{rulesByOrder.map((rule) => <RuleSection key={rule.id} rule={rule} />)}
|
||||
</article>
|
||||
|
||||
<article id="glossary" className="view">
|
||||
{<GlossaryTable glossary={playbill.glossary} />}
|
||||
</article>
|
||||
|
||||
<article id="resources" className="view">
|
||||
{Object.values(playbill.resources).map((resource) => <ResourceSection key={resource.id} resource={resource} />)}
|
||||
</article>
|
||||
</>);
|
||||
|
||||
// Main document
|
||||
const doc = html`
|
||||
const doc = `
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
@ -126,7 +136,7 @@ export async function renderPlaybillToHTML(
|
|||
</script>
|
||||
</head>
|
||||
<body>
|
||||
${await PageHeader(playbill)}
|
||||
${renderToString(<PageHeader playbill={playbill} />)}
|
||||
<blockquote class="overview"><p>${playbill.description}</p></blockquote>
|
||||
<nav>
|
||||
<button onclick="hideAllAndShow('species')" id="species-nav">Species</button>
|
||||
|
@ -137,31 +147,7 @@ export async function renderPlaybillToHTML(
|
|||
<button onclick="hideAllAndShow('resources')" id="resources-nav">Resources</button>
|
||||
<button onclick="showAllViews()" id="all-nav">Show All</button>
|
||||
</nav>
|
||||
<article id="species" class="view">
|
||||
${species}
|
||||
</article>
|
||||
|
||||
<article id="methods" class="view">
|
||||
${methods}
|
||||
</article>
|
||||
|
||||
<article id="items" class="view">
|
||||
${items}
|
||||
</article>
|
||||
|
||||
<article id="rules" class="view">
|
||||
${renderToString(rules)}
|
||||
</article>
|
||||
|
||||
<article id="glossary" class="view">
|
||||
${glossaryHTML}
|
||||
</article>
|
||||
|
||||
<article id="resources" class="view">
|
||||
${resources}
|
||||
</article>
|
||||
|
||||
</article>
|
||||
${body}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue