Create a renderer for Candela Obscura character sheets #275
|
@ -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 (
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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,19 +70,6 @@ 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,
|
||||||
|
@ -106,21 +98,21 @@ const ActionGroupElement = ({group}: ActionGroupElementProps) => {
|
||||||
|
|
||||||
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>)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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"> </div> :
|
||||||
|
<div className="drive-guage__top"> </div>}
|
||||||
|
{i < max ? <div className="drive-guage__bottom drive-guage__bottom_filled"> </div> :
|
||||||
|
<div className="drive-guage__bottom"> </div>}
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
components.splice(3, 1, <div className="drive-guage__spacer"> </div>);
|
||||||
|
return (<div className="drive-guage">
|
||||||
|
{components}
|
||||||
|
</div>)
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
.section {
|
||||||
|
border: 2px solid black;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
|
@ -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>);
|
||||||
|
}
|
Loading…
Reference in New Issue