diff --git a/visions/ui/package-lock.json b/visions/ui/package-lock.json
index 12ee1ca..aac5bfd 100644
--- a/visions/ui/package-lock.json
+++ b/visions/ui/package-lock.json
@@ -17,6 +17,7 @@
"@types/react-dom": "^18.3.1",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
+ "classnames": "^2.5.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router": "^6.28.0",
@@ -5257,6 +5258,11 @@
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz",
"integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA=="
},
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
+ },
"node_modules/clean-css": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
diff --git a/visions/ui/package.json b/visions/ui/package.json
index e62d518..65707a6 100644
--- a/visions/ui/package.json
+++ b/visions/ui/package.json
@@ -12,6 +12,7 @@
"@types/react-dom": "^18.3.1",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
+ "classnames": "^2.5.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router": "^6.28.0",
@@ -19,8 +20,8 @@
"react-scripts": "5.0.1",
"react-use-websocket": "^4.11.1",
"typescript": "^4.9.5",
- "web-vitals": "^2.1.4",
- "visions-types": "../visions-types"
+ "visions-types": "../visions-types",
+ "web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
diff --git a/visions/ui/src/components/Guages/SimpleGuage.css b/visions/ui/src/components/Guages/SimpleGuage.css
new file mode 100644
index 0000000..e69de29
diff --git a/visions/ui/src/components/Guages/SimpleGuage.tsx b/visions/ui/src/components/Guages/SimpleGuage.tsx
new file mode 100644
index 0000000..c258c02
--- /dev/null
+++ b/visions/ui/src/components/Guages/SimpleGuage.tsx
@@ -0,0 +1,8 @@
+import React from 'react';
+
+interface GuageProps {
+ current: number,
+ max: number,
+}
+
+export const SimpleGuage = ({ current, max }: GuageProps) => <> {current} / {max}>
diff --git a/visions/ui/src/components/Tabletop/Tabletop.css b/visions/ui/src/components/Tabletop/Tabletop.css
index 19f7799..9e47269 100644
--- a/visions/ui/src/components/Tabletop/Tabletop.css
+++ b/visions/ui/src/components/Tabletop/Tabletop.css
@@ -1,7 +1,3 @@
-.playing-field__background {
- flex-grow: 1;
-}
-
-.playing-field__background > img {
+.tabletop > img {
max-width: 100%;
}
diff --git a/visions/ui/src/components/Tabletop/Tabletop.tsx b/visions/ui/src/components/Tabletop/Tabletop.tsx
index 466bb76..efa21bf 100644
--- a/visions/ui/src/components/Tabletop/Tabletop.tsx
+++ b/visions/ui/src/components/Tabletop/Tabletop.tsx
@@ -10,5 +10,5 @@ interface TabletopElementProps {
export const TabletopElement = ({ backgroundColor, backgroundUrl }: TabletopElementProps) => {
const tabletopColorStyle = `rgb(${backgroundColor.red}, ${backgroundColor.green}, ${backgroundColor.blue})`;
- return
{backgroundUrl &&
}
+ return {backgroundUrl &&
}
}
diff --git a/visions/ui/src/components/Thumbnail/Thumbnail.tsx b/visions/ui/src/components/Thumbnail/Thumbnail.tsx
index c63f4dd..7f5f5ab 100644
--- a/visions/ui/src/components/Thumbnail/Thumbnail.tsx
+++ b/visions/ui/src/components/Thumbnail/Thumbnail.tsx
@@ -8,7 +8,7 @@ interface ThumbnailProps {
onclick?: () => void;
}
-export const ThumbnailComponent = ({ id, url, onclick }: ThumbnailProps) => {
+export const ThumbnailElement = ({ id, url, onclick }: ThumbnailProps) => {
const clickHandler = () => {
if (onclick) { onclick(); }
}
diff --git a/visions/ui/src/components/index.ts b/visions/ui/src/components/index.ts
new file mode 100644
index 0000000..64551ca
--- /dev/null
+++ b/visions/ui/src/components/index.ts
@@ -0,0 +1,5 @@
+import { ThumbnailElement } from './Thumbnail/Thumbnail'
+import { TabletopElement } from './Tabletop/Tabletop'
+import { SimpleGuage } from './Guages/SimpleGuage'
+
+export default { ThumbnailElement, TabletopElement, SimpleGuage }
diff --git a/visions/ui/src/plugins/Candela/Charsheet.tsx b/visions/ui/src/plugins/Candela/Charsheet.tsx
index 7d77c8b..c6095f4 100644
--- a/visions/ui/src/plugins/Candela/Charsheet.tsx
+++ b/visions/ui/src/plugins/Candela/Charsheet.tsx
@@ -1,74 +1,8 @@
import React from 'react';
+import { assertNever } from '.';
import './Charsheet.css';
import { DriveGuage } from './DriveGuage/DriveGuage';
-
-function assertNever(value: never) {
- throw new Error("Unexpected value: " + value);
-}
-
-export type Guage = {
- current: number,
- max: number,
-}
-
-export type Action = {
- gilded: boolean,
- score: number,
-}
-
-export type Actions = { [key: string]: Action }
-
-export type ActionGroup = {
- drives: Guage,
- resistances: Guage,
- actions: Actions,
-}
-
-type Nerve = {
- type_: "nerve",
- drives: Guage,
- resistances: Guage,
- move: Action,
- strike: Action,
- control: Action,
-}
-
-type Cunning = {
- type_: "cunning",
- drives: Guage,
- resistances: Guage,
- sway: Action,
- read: Action,
- hide: Action,
-}
-
-type Intuition = {
- type_: "intuition",
- drives: Guage,
- resistances: Guage,
- survey: Action,
- focus: Action,
- sense: Action,
-}
-
-export type Charsheet = {
- type_: string,
- name: string,
- pronouns: string
- circle: string
- style: string,
- catalyst: string,
- question: string,
-
- nerve: Nerve,
- cunning: Cunning,
- intuition: Intuition,
-
- role: string,
- role_abilities: string[],
- specialty: string,
- specialty_abilities: string[],
-}
+import { Charsheet, Nerve, Cunning, Intuition } from './types';
interface CharsheetProps {
sheet: Charsheet,
diff --git a/visions/ui/src/plugins/Candela/CharsheetPanel.css b/visions/ui/src/plugins/Candela/CharsheetPanel.css
new file mode 100644
index 0000000..bca2e0a
--- /dev/null
+++ b/visions/ui/src/plugins/Candela/CharsheetPanel.css
@@ -0,0 +1,24 @@
+.candela-panel-actions {
+ border: 1px solid black;
+ padding: 4px;
+ margin: 2px;
+}
+
+.candela-panel-actions__header {
+ display: flex;
+ padding: 2px;
+ padding-left: 4px;
+ padding-right: 4px;
+ justify-content: space-between;
+ background-color: black;
+ color: white;
+}
+
+.candela-panel-actions__action {
+ display: flex;
+ justify-content: space-between;
+}
+
+.candela-panel-actions__action_gilded {
+ font-weight: bold;
+}
diff --git a/visions/ui/src/plugins/Candela/CharsheetPanel.tsx b/visions/ui/src/plugins/Candela/CharsheetPanel.tsx
new file mode 100644
index 0000000..ea3d82c
--- /dev/null
+++ b/visions/ui/src/plugins/Candela/CharsheetPanel.tsx
@@ -0,0 +1,135 @@
+import React from 'react';
+import { assertNever } from '.';
+import { SimpleGuage } from '../../components/Guages/SimpleGuage';
+import { Charsheet, Nerve, Cunning, Intuition } from './types';
+import './CharsheetPanel.css';
+import classNames from 'classnames';
+
+interface CharsheetPanelProps {
+ sheet: Charsheet;
+}
+
+interface ActionElementProps {
+ name: string,
+ gilded: boolean,
+ value: number,
+}
+
+const ActionElement = ({ name, gilded, value }: ActionElementProps) => {
+ const className = gilded ? "candela-panel-actions__action_gilded" : "candela-panel-actions__action";
+ return ();
+}
+
+
+interface ActionGroupElementProps {
+ group: Nerve | Cunning | Intuition;
+}
+
+const ActionGroupElement = ({ group }: ActionGroupElementProps) => {
+ var title;
+ var elements = [];
+
+ switch (group.type_) {
+ case "nerve": {
+ title =
+ elements.push();
+ elements.push();
+ elements.push();
+ break
+ }
+ case "cunning": {
+ title =
+ elements.push();
+ elements.push();
+ elements.push();
+ break
+ }
+ case "intuition": {
+ title =
+ elements.push();
+ elements.push();
+ elements.push();
+ break
+ }
+ default: {
+ assertNever(group);
+ }
+ }
+
+ return (
+ {title}
+ {elements}
+
)
+}
+
+
+const CharsheetPanelElement_ = ({ sheet }: CharsheetPanelProps) => {
+ return (
+
+
{sheet.name} ({sheet.pronouns})
+
{sheet.specialty}
+
+
+
+
+
+
+ {sheet.role_abilities.map((ability) => - {ability}
)}
+ {sheet.specialty_abilities.map((ability) => - {ability}
)}
+
+
+
);
+}
+
+export const CharsheetPanelElement = () => {
+ const sheet = {
+ type_: 'Candela',
+ name: "Soren Jensen",
+ pronouns: 'he/him',
+ circle: 'Circle of the Bluest Sky',
+ style: 'dapper gentleman',
+ catalyst: 'a cursed book',
+ question: 'What were the contents of that book?',
+ nerve: {
+ type_: "nerve",
+ drives: { current: 1, max: 2 },
+ resistances: { current: 0, max: 3 },
+ move: { gilded: false, score: 2 },
+ strike: { gilded: false, score: 1 },
+ control: { gilded: true, score: 0 },
+ } as Nerve,
+ cunning: {
+ type_: "cunning",
+ drives: { current: 1, max: 1 },
+ resistances: { current: 0, max: 3 },
+ sway: { gilded: false, score: 0 },
+ read: { gilded: false, score: 0 },
+ hide: { gilded: false, score: 0 },
+ } as Cunning,
+ intuition: {
+ type_: "intuition",
+ drives: { current: 0, max: 0 },
+ resistances: { current: 0, max: 3 },
+ survey: { gilded: false, score: 0 },
+ focus: { gilded: false, score: 0 },
+ sense: { gilded: false, score: 0 },
+ } as Intuition,
+ role: 'Slink',
+ role_abilities: [
+ 'Scout: If you have time to observe a location, you can spend 1 Intuition to ask a question: What do I notice here that others do not see? What in this place might be of use to us? What path should we follow?',
+ ],
+ specialty: 'Detective',
+ specialty_abilities: [
+ "Mind Palace: When you want to figure out how two clues might relate or what path they should point you towards, burn 1 Intution resistance. The GM will give you the information you've deduced.",
+ ],
+ };
+
+ return
+}
diff --git a/visions/ui/src/plugins/Candela/index.tsx b/visions/ui/src/plugins/Candela/index.tsx
index 37a562e..818120b 100644
--- a/visions/ui/src/plugins/Candela/index.tsx
+++ b/visions/ui/src/plugins/Candela/index.tsx
@@ -1,4 +1,9 @@
-import { Charsheet, CharsheetElement } from './Charsheet';
+import { CharsheetElement } from './Charsheet';
+import { CharsheetPanelElement } from './CharsheetPanel';
-export default { CharsheetElement };
+export function assertNever(value: never) {
+ throw new Error("Unexpected value: " + value);
+}
+
+export default { CharsheetElement, CharsheetPanelElement };
diff --git a/visions/ui/src/plugins/Candela/types.ts b/visions/ui/src/plugins/Candela/types.ts
new file mode 100644
index 0000000..555fefa
--- /dev/null
+++ b/visions/ui/src/plugins/Candela/types.ts
@@ -0,0 +1,65 @@
+
+export type Guage = {
+ current: number,
+ max: number,
+}
+
+export type Action = {
+ gilded: boolean,
+ score: number,
+}
+
+export type Actions = { [key: string]: Action }
+
+export type ActionGroup = {
+ drives: Guage,
+ resistances: Guage,
+ actions: Actions,
+}
+
+export type Nerve = {
+ type_: "nerve",
+ drives: Guage,
+ resistances: Guage,
+ move: Action,
+ strike: Action,
+ control: Action,
+}
+
+export type Cunning = {
+ type_: "cunning",
+ drives: Guage,
+ resistances: Guage,
+ sway: Action,
+ read: Action,
+ hide: Action,
+}
+
+export type Intuition = {
+ type_: "intuition",
+ drives: Guage,
+ resistances: Guage,
+ survey: Action,
+ focus: Action,
+ sense: Action,
+}
+
+export type Charsheet = {
+ type_: string,
+ name: string,
+ pronouns: string
+ circle: string
+ style: string,
+ catalyst: string,
+ question: string,
+
+ nerve: Nerve,
+ cunning: Cunning,
+ intuition: Intuition,
+
+ role: string,
+ role_abilities: string[],
+ specialty: string,
+ specialty_abilities: string[],
+}
+
diff --git a/visions/ui/src/views/GmView/GmView.tsx b/visions/ui/src/views/GmView/GmView.tsx
index 9b8550f..cc697f3 100644
--- a/visions/ui/src/views/GmView/GmView.tsx
+++ b/visions/ui/src/views/GmView/GmView.tsx
@@ -1,7 +1,7 @@
import React, { useContext, useEffect, useState } from 'react';
import { Client, PlayingField } from '../../client';
import { TabletopElement } from '../../components/Tabletop/Tabletop';
-import { ThumbnailComponent } from '../../components/Thumbnail/Thumbnail';
+import { ThumbnailElement } from '../../components/Thumbnail/Thumbnail';
import { WebsocketContext } from '../../components/WebsocketProvider';
import './GmView.css';
@@ -20,7 +20,7 @@ export const GmView = ({ client }: GmViewProps) => {
const backgroundUrl = tabletop.backgroundImage ? client.imageUrl(tabletop.backgroundImage) : undefined;
return (
- {images.map((imageName) => { client.setBackgroundImage(imageName); }} />)}
+ {images.map((imageName) => { client.setBackgroundImage(imageName); }} />)}
)
diff --git a/visions/ui/src/views/PlayerView/PlayerView.css b/visions/ui/src/views/PlayerView/PlayerView.css
index 4e91924..daf3bec 100644
--- a/visions/ui/src/views/PlayerView/PlayerView.css
+++ b/visions/ui/src/views/PlayerView/PlayerView.css
@@ -4,15 +4,12 @@
}
.player-view__left-panel {
- flex-grow: 0;
min-width: 100px;
max-width: 20%;
}
.player-view__right-panel {
- flex-grow: 0;
- min-width: 100px;
- max-width: 20%;
+ width: 25%;
}
diff --git a/visions/ui/src/views/PlayerView/PlayerView.tsx b/visions/ui/src/views/PlayerView/PlayerView.tsx
index c4c351e..586d46f 100644
--- a/visions/ui/src/views/PlayerView/PlayerView.tsx
+++ b/visions/ui/src/views/PlayerView/PlayerView.tsx
@@ -3,6 +3,7 @@ import './PlayerView.css';
import { WebsocketContext } from '../../components/WebsocketProvider';
import { Client } from '../../client';
import { TabletopElement } from '../../components/Tabletop/Tabletop';
+import Candela from '../../plugins/Candela';
interface PlayerViewProps {
client: Client;
@@ -15,10 +16,9 @@ export const PlayerView = ({ client }: PlayerViewProps) => {
const tabletopColorStyle = `rgb(${backgroundColor.red}, ${backgroundColor.green}, ${backgroundColor.blue})`;
const backgroundUrl = tabletop.backgroundImage ? client.imageUrl(tabletop.backgroundImage) : undefined;
- return (
-
Left Side
-
-
Right Side
+ return (
)
}