Create a renderer for Candela Obscura character sheets #275
|
@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
|
|||
import './App.css';
|
||||
import { Client } from './client';
|
||||
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||
import { DesignPage } from './views/Design/Design';
|
||||
import { GmView } from './views/GmView/GmView';
|
||||
import { WebsocketProvider } from './components/WebsocketProvider';
|
||||
import { PlayerView } from './views/PlayerView/PlayerView';
|
||||
|
@ -32,6 +33,10 @@ const App = ({ client }: AppProps) => {
|
|||
{
|
||||
path: "/candela",
|
||||
element: <Candela.CharsheetElement />
|
||||
},
|
||||
{
|
||||
path: "/design",
|
||||
element: <DesignPage />
|
||||
}
|
||||
]);
|
||||
return (
|
||||
|
|
|
@ -36,7 +36,10 @@
|
|||
border: 2px solid black;
|
||||
}
|
||||
|
||||
.action-group > h1 {
|
||||
.action-group__header {
|
||||
display: flex;
|
||||
font-size: xx-large;
|
||||
justify-content: space-between;
|
||||
margin: 4px;
|
||||
margin-left: -4px;
|
||||
padding-left: 4px;
|
||||
|
@ -53,3 +56,55 @@
|
|||
padding: 0px;
|
||||
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 './Charsheet.css';
|
||||
import { DriveGuage } from './DriveGuage/DriveGuage';
|
||||
|
||||
function assertNever(value: never) {
|
||||
throw new Error("Unexpected value: " + value);
|
||||
}
|
||||
|
||||
export type Guage = {
|
||||
current: number,
|
||||
|
@ -65,26 +70,13 @@ interface CharsheetProps {
|
|||
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 {
|
||||
name: string,
|
||||
gilded: boolean,
|
||||
value: number,
|
||||
}
|
||||
|
||||
const ActionElement = ({name, gilded, value}: ActionElementProps) => {
|
||||
const ActionElement = ({ name, gilded, value }: ActionElementProps) => {
|
||||
let dots = [];
|
||||
for (let i = 0; i < value; i++) {
|
||||
dots.push("\u25ef");
|
||||
|
@ -100,27 +92,27 @@ interface ActionGroupElementProps {
|
|||
group: Nerve | Cunning | Intuition;
|
||||
}
|
||||
|
||||
const ActionGroupElement = ({group}: ActionGroupElementProps) => {
|
||||
const ActionGroupElement = ({ group }: ActionGroupElementProps) => {
|
||||
var title;
|
||||
var elements = [];
|
||||
|
||||
switch (group.type_) {
|
||||
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="Strike" gilded={group.strike.gilded} value={group.strike.score} />);
|
||||
elements.push(<ActionElement name="Control" gilded={group.control.gilded} value={group.control.score} />);
|
||||
break
|
||||
}
|
||||
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="Read" gilded={group.read.gilded} value={group.read.score} />);
|
||||
elements.push(<ActionElement name="Hide" gilded={group.hide.gilded} value={group.hide.score} />);
|
||||
break
|
||||
}
|
||||
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="Focus" gilded={group.focus.gilded} value={group.focus.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">
|
||||
<h1> {title} </h1>
|
||||
{title}
|
||||
{elements}
|
||||
</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