Create a renderer for Candela Obscura character sheets #275

Merged
savanni merged 6 commits from charsheet-plugin into main 2024-11-29 14:42:49 +00:00
7 changed files with 162 additions and 20 deletions
Showing only changes of commit 0202b7bd59 - Show all commits

View File

@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import './App.css'; import './App.css';
import { Client } from './client'; import { Client } from './client';
import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { DesignPage } from './views/Design/Design';
import { GmView } from './views/GmView/GmView'; import { GmView } from './views/GmView/GmView';
import { WebsocketProvider } from './components/WebsocketProvider'; import { WebsocketProvider } from './components/WebsocketProvider';
import { PlayerView } from './views/PlayerView/PlayerView'; import { PlayerView } from './views/PlayerView/PlayerView';
@ -32,6 +33,10 @@ const App = ({ client }: AppProps) => {
{ {
path: "/candela", path: "/candela",
element: <Candela.CharsheetElement /> element: <Candela.CharsheetElement />
},
{
path: "/design",
element: <DesignPage />
} }
]); ]);
return ( return (

View File

@ -36,7 +36,10 @@
border: 2px solid black; border: 2px solid black;
} }
.action-group > h1 { .action-group__header {
display: flex;
font-size: xx-large;
justify-content: space-between;
margin: 4px; margin: 4px;
margin-left: -4px; margin-left: -4px;
padding-left: 4px; padding-left: 4px;
@ -53,3 +56,55 @@
padding: 0px; padding: 0px;
padding-left: 16px; padding-left: 16px;
} }
.action-group__guage-column {
width: 10px;
height: 100%;
margin: 2px;
}
.action-group__guage-top {
position: relative;
background-color: white;
margin-bottom: 2px;
}
.action-group__guage-top:before {
content: " ";
position: absolute;
z-index: -1;
top: 2px;
right: 2px;
bottom: 2px;
left: 2px;
border: 1px solid black;
}
.action-group__guage-top_filled {
background-color: black;
margin-bottom: 2px;
}
.action-group__guage-bottom {
background-color: white;
height: 8px;
}
.action-group__guage-bottom_filled {
background-color: black;
height: 8px;
}
.action-group__guage-bottom:before {
content: " ";
position: absolute;
z-index: -1;
top: 1px;
left: 1px;
right: 1px;
bottom: 1px;
}
.action-group__guage {
display: flex;
}

View File

@ -1,5 +1,10 @@
import React from 'react'; import React from 'react';
import './Charsheet.css'; import './Charsheet.css';
import { DriveGuage } from './DriveGuage/DriveGuage';
function assertNever(value: never) {
throw new Error("Unexpected value: " + value);
}
export type Guage = { export type Guage = {
current: number, current: number,
@ -65,26 +70,13 @@ interface CharsheetProps {
sheet: Charsheet, sheet: Charsheet,
} }
interface GuageProps {
current: number,
max: number,
}
const GuageElement = ({ current, max }: GuageProps) => {
}
function assertNever(value: never) {
throw new Error("Unexpected value: " + value);
}
interface ActionElementProps { interface ActionElementProps {
name: string, name: string,
gilded: boolean, gilded: boolean,
value: number, value: number,
} }
const ActionElement = ({name, gilded, value}: ActionElementProps) => { const ActionElement = ({ name, gilded, value }: ActionElementProps) => {
let dots = []; let dots = [];
for (let i = 0; i < value; i++) { for (let i = 0; i < value; i++) {
dots.push("\u25ef"); dots.push("\u25ef");
@ -100,27 +92,27 @@ interface ActionGroupElementProps {
group: Nerve | Cunning | Intuition; group: Nerve | Cunning | Intuition;
} }
const ActionGroupElement = ({group}: ActionGroupElementProps) => { const ActionGroupElement = ({ group }: ActionGroupElementProps) => {
var title; var title;
var elements = []; var elements = [];
switch (group.type_) { switch (group.type_) {
case "nerve": { case "nerve": {
title = <div> Nerve </div> title = <div className="action-group__header"> Nerve <DriveGuage current={group.drives.current} max={group.drives.max} /> </div>
elements.push(<ActionElement name="Move" gilded={group.move.gilded} value={group.move.score} />); elements.push(<ActionElement name="Move" gilded={group.move.gilded} value={group.move.score} />);
elements.push(<ActionElement name="Strike" gilded={group.strike.gilded} value={group.strike.score} />); elements.push(<ActionElement name="Strike" gilded={group.strike.gilded} value={group.strike.score} />);
elements.push(<ActionElement name="Control" gilded={group.control.gilded} value={group.control.score} />); elements.push(<ActionElement name="Control" gilded={group.control.gilded} value={group.control.score} />);
break break
} }
case "cunning": { case "cunning": {
title = <div> Cunning </div> title = <div className="action-group__header"> Cunning <DriveGuage current={group.drives.current} max={group.drives.max} /> </div>
elements.push(<ActionElement name="Sway" gilded={group.sway.gilded} value={group.sway.score} />); elements.push(<ActionElement name="Sway" gilded={group.sway.gilded} value={group.sway.score} />);
elements.push(<ActionElement name="Read" gilded={group.read.gilded} value={group.read.score} />); elements.push(<ActionElement name="Read" gilded={group.read.gilded} value={group.read.score} />);
elements.push(<ActionElement name="Hide" gilded={group.hide.gilded} value={group.hide.score} />); elements.push(<ActionElement name="Hide" gilded={group.hide.gilded} value={group.hide.score} />);
break break
} }
case "intuition": { case "intuition": {
title = <div> Intuition </div> title = <div className="action-group__header"> Intuition <DriveGuage current={group.drives.current} max={group.drives.max} /> </div>
elements.push(<ActionElement name="Survey" gilded={group.survey.gilded} value={group.survey.score} />); elements.push(<ActionElement name="Survey" gilded={group.survey.gilded} value={group.survey.score} />);
elements.push(<ActionElement name="Focus" gilded={group.focus.gilded} value={group.focus.score} />); elements.push(<ActionElement name="Focus" gilded={group.focus.gilded} value={group.focus.score} />);
elements.push(<ActionElement name="Sense" gilded={group.sense.gilded} value={group.sense.score} />); elements.push(<ActionElement name="Sense" gilded={group.sense.gilded} value={group.sense.score} />);
@ -132,7 +124,7 @@ const ActionGroupElement = ({group}: ActionGroupElementProps) => {
} }
return (<div className="action-group"> return (<div className="action-group">
<h1> {title} </h1> {title}
{elements} {elements}
</div>) </div>)
} }

View File

@ -0,0 +1,44 @@
.drive-guages_on-light {
background-color: white;
}
.drive-guage {
display: flex;
}
.drive-guage__element {
border: 1px solid black;
margin: 1px;
background-color: white;
}
.drive-guage__spacer {
margin: 1px;
}
.drive-guage__top {
margin: 1px;
width: 8px;
border: 1px solid black;
background-color: white;
}
.drive-guage__top_filled {
background-color: black;
}
.drive-guage__bottom {
margin: 1px;
border: 1px solid black;
width: 8px;
height: 8px;
background-color: white;
}
.drive-guage__bottom_filled {
background-color: black;
}
.drive-guages_on-dark {
background-color: black;
}

View File

@ -0,0 +1,23 @@
import React from 'react';
import './DriveGuage.css';
interface Guage {
current: number;
max: number;
}
export const DriveGuage = ({ current, max }: Guage) => {
let components = [];
for (let i = 0; i < 9; i++) {
components.push(<div className="drive-guage__element">
{i < current ? <div className="drive-guage__top drive-guage__top_filled">&nbsp;</div> :
<div className="drive-guage__top">&nbsp;</div>}
{i < max ? <div className="drive-guage__bottom drive-guage__bottom_filled">&nbsp;</div> :
<div className="drive-guage__bottom">&nbsp;</div>}
</div>)
}
components.splice(3, 1, <div className="drive-guage__spacer">&nbsp;</div>);
return (<div className="drive-guage">
{components}
</div>)
}

View File

@ -0,0 +1,4 @@
.section {
border: 2px solid black;
border-radius: 4px;
}

View File

@ -0,0 +1,19 @@
import React from 'react';
import { DriveGuage } from '../../plugins/Candela/DriveGuage/DriveGuage';
const DriveGuages = () => {
return (<div className="section">
<div className="drive-guages_on-light">
<DriveGuage current={2} max={4} />
</div>
<div className="drive-guages_on-dark">
<DriveGuage current={2} max={4} />
</div>
</div>);
}
export const DesignPage = () => {
return (<div>
<DriveGuages />
</div>);
}