From db8e67420f8291c14b6a5a8d3b2a81483bd3a416 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Mon, 25 Nov 2024 08:28:22 -0500 Subject: [PATCH 1/6] Start on a Candela Obscura plugin --- .envrc | 1 + visions/server/src/asset_db.rs | 2 + visions/ui/src/App.css | 1 - visions/ui/src/App.tsx | 5 + visions/ui/src/plugins/Candela/Charsheet.css | 19 +++ visions/ui/src/plugins/Candela/Charsheet.tsx | 151 +++++++++++++++++++ visions/ui/src/plugins/Candela/index.tsx | 4 + 7 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 visions/ui/src/plugins/Candela/Charsheet.css create mode 100644 visions/ui/src/plugins/Candela/Charsheet.tsx create mode 100644 visions/ui/src/plugins/Candela/index.tsx diff --git a/.envrc b/.envrc index 3550a30..c3792f6 100644 --- a/.envrc +++ b/.envrc @@ -1 +1,2 @@ +mkdir .direnv use flake diff --git a/visions/server/src/asset_db.rs b/visions/server/src/asset_db.rs index d32fa82..62364e5 100644 --- a/visions/server/src/asset_db.rs +++ b/visions/server/src/asset_db.rs @@ -7,6 +7,7 @@ use std::{ use mime::Mime; use serde::{Deserialize, Serialize}; use thiserror::Error; +use typeshare::typeshare; #[derive(Debug, Error)] pub enum Error { @@ -32,6 +33,7 @@ impl From for Error { } #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[typeshare] pub struct AssetId(String); impl Display for AssetId { diff --git a/visions/ui/src/App.css b/visions/ui/src/App.css index 74b5e05..2d74043 100644 --- a/visions/ui/src/App.css +++ b/visions/ui/src/App.css @@ -1,5 +1,4 @@ .App { - text-align: center; } .App-logo { diff --git a/visions/ui/src/App.tsx b/visions/ui/src/App.tsx index 8efbba2..c0f28a4 100644 --- a/visions/ui/src/App.tsx +++ b/visions/ui/src/App.tsx @@ -5,6 +5,7 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import { GmView } from './views/GmView/GmView'; import { WebsocketProvider } from './components/WebsocketProvider'; import { PlayerView } from './views/PlayerView/PlayerView'; +import Candela from './plugins/Candela'; interface AppProps { client: Client; @@ -27,6 +28,10 @@ const App = ({ client }: AppProps) => { { path: "/", element: websocketUrl ? :
+ }, + { + path: "/candela", + element: } ]); return ( diff --git a/visions/ui/src/plugins/Candela/Charsheet.css b/visions/ui/src/plugins/Candela/Charsheet.css new file mode 100644 index 0000000..c940466 --- /dev/null +++ b/visions/ui/src/plugins/Candela/Charsheet.css @@ -0,0 +1,19 @@ +.charsheet__header { + display: flex; +} + +.charsheet__header > div { + margin: 8px; + width: 33%; +} + +.charsheet__body { + display: flex; +} + +.charsheet__body > div { + margin: 8px; + width: 33%; +} + + diff --git a/visions/ui/src/plugins/Candela/Charsheet.tsx b/visions/ui/src/plugins/Candela/Charsheet.tsx new file mode 100644 index 0000000..44729e6 --- /dev/null +++ b/visions/ui/src/plugins/Candela/Charsheet.tsx @@ -0,0 +1,151 @@ +import React from 'react'; +import './Charsheet.css'; + +export type Guage = { + current: number, + max: number, +} + +export type Action = { + guilded: boolean, + score: number, +} + +export type Actions = { [key: string]: Action } + +export type ActionGroup = { + drives: Guage, + resistances: Guage, + actions: Actions, +} + +type Nerve = { + drives: Guage, + resistances: Guage, + move: Action, + strike: Action, + control: Action, +} + +type Cunning = { + drives: Guage, + resistances: Guage, + sway: Action, + read: Action, + hide: Action, +} + +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, + role: string, + + nerve: Nerve, + cunning: Cunning, + intuition: Intuition, +} + +interface CharsheetProps { + sheet: Charsheet, +} + +interface ActionDriveProps { + groupName: string, + group: Nerve | Cunning | Intuition, +} + +interface GuageProps { + current: number, + max: number, +} + +const GuageElement = ({ current, max }: GuageProps) => { + +} + +const ActionDriveElement = ({ groupName, group }: ActionDriveProps) => { + + if ("move" in group) { + return
-- Nerve --
; + } else if ("sway" in group) { + return
-- Cunning --
; + } else { + return
-- Intuition --
; + } +} + +const CharsheetElement_ = ({ sheet }: CharsheetProps) => { + return (
+
+
Candela Obscura
+
+

{sheet.name}

+

{sheet.pronouns}

+

{sheet.circle}

+
+
+

{sheet.style}

+

{sheet.catalyst}

+

{sheet.question}

+
+
+
+
+ + + +
+
Role and Specialty
+
Marks, Scars, Relationships
+
+
); +} + +export const CharsheetElement = () => { + 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?', + role: 'Slink', + nerve: { + drives: { current: 2, max: 2 }, + resistances: { current: 0, max: 3 }, + move: { guilded: false, score: 0 }, + strike: { guilded: false, score: 0 }, + control: { guilded: true, score: 0 }, + }, + cunning: { + drives: { current: 1, max: 1 }, + resistances: { current: 0, max: 3 }, + sway: { guilded: false, score: 0 }, + read: { guilded: false, score: 0 }, + hide: { guilded: false, score: 0 }, + }, + intuition: { + drives: { current: 0, max: 0 }, + resistances: { current: 0, max: 3 }, + survey: { guilded: false, score: 0 }, + focus: { guilded: false, score: 0 }, + sense: { guilded: false, score: 0 }, + } + }; + + return +} diff --git a/visions/ui/src/plugins/Candela/index.tsx b/visions/ui/src/plugins/Candela/index.tsx new file mode 100644 index 0000000..37a562e --- /dev/null +++ b/visions/ui/src/plugins/Candela/index.tsx @@ -0,0 +1,4 @@ +import { Charsheet, CharsheetElement } from './Charsheet'; + +export default { CharsheetElement }; + -- 2.44.1 From 311cd9c9a59ab8eacda8beabe13be12d60d79a47 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 27 Nov 2024 09:37:48 -0500 Subject: [PATCH 2/6] Set up rendering and formatting for actions and action groups --- visions/server/Taskfile.yml | 14 +++ visions/ui/Taskfile.yml | 7 ++ visions/ui/src/plugins/Candela/Charsheet.css | 36 ++++++ visions/ui/src/plugins/Candela/Charsheet.tsx | 109 ++++++++++++++----- 4 files changed, 138 insertions(+), 28 deletions(-) create mode 100644 visions/server/Taskfile.yml create mode 100644 visions/ui/Taskfile.yml diff --git a/visions/server/Taskfile.yml b/visions/server/Taskfile.yml new file mode 100644 index 0000000..58152e8 --- /dev/null +++ b/visions/server/Taskfile.yml @@ -0,0 +1,14 @@ +version: '3' + +tasks: + build: + cmds: + - cargo build + + test: + cmds: + - cargo watch -x test + + server: + cmds: + - cargo watch -x run diff --git a/visions/ui/Taskfile.yml b/visions/ui/Taskfile.yml new file mode 100644 index 0000000..823ef51 --- /dev/null +++ b/visions/ui/Taskfile.yml @@ -0,0 +1,7 @@ +version: '3' + +tasks: + dev: + cmds: + - npm run start + diff --git a/visions/ui/src/plugins/Candela/Charsheet.css b/visions/ui/src/plugins/Candela/Charsheet.css index c940466..6c63076 100644 --- a/visions/ui/src/plugins/Candela/Charsheet.css +++ b/visions/ui/src/plugins/Candela/Charsheet.css @@ -16,4 +16,40 @@ width: 33%; } +.action-group { + position: relative; + border: 2px solid black; + border-radius: 4px; + padding-left: 8px; + padding-bottom: 8px; +} + +.action-group:before { + content: " "; + position: absolute; + z-index: -1; + top: 2px; + left: 2px; + right: 2px; + bottom: 2px; + border: 2px solid black; +} + +.action-group > h1 { + margin: 4px; + margin-left: -4px; + padding-left: 4px; + background-color: black; + color: white; +} + +.action-group__action { + margin: 2px; +} + +.action-group__dots { + margin: 0px; + padding: 0px; + padding-left: 16px; +} diff --git a/visions/ui/src/plugins/Candela/Charsheet.tsx b/visions/ui/src/plugins/Candela/Charsheet.tsx index 44729e6..09cf46e 100644 --- a/visions/ui/src/plugins/Candela/Charsheet.tsx +++ b/visions/ui/src/plugins/Candela/Charsheet.tsx @@ -7,7 +7,7 @@ export type Guage = { } export type Action = { - guilded: boolean, + gilded: boolean, score: number, } @@ -20,6 +20,7 @@ export type ActionGroup = { } type Nerve = { + type_: "nerve", drives: Guage, resistances: Guage, move: Action, @@ -28,6 +29,7 @@ type Nerve = { } type Cunning = { + type_: "cunning", drives: Guage, resistances: Guage, sway: Action, @@ -36,6 +38,7 @@ type Cunning = { } type Intuition = { + type_: "intuition", drives: Guage, resistances: Guage, survey: Action, @@ -62,11 +65,6 @@ interface CharsheetProps { sheet: Charsheet, } -interface ActionDriveProps { - groupName: string, - group: Nerve | Cunning | Intuition, -} - interface GuageProps { current: number, max: number, @@ -76,15 +74,67 @@ const GuageElement = ({ current, max }: GuageProps) => { } -const ActionDriveElement = ({ groupName, group }: ActionDriveProps) => { +function assertNever(value: never) { + throw new Error("Unexpected value: " + value); +} - if ("move" in group) { - return
-- Nerve --
; - } else if ("sway" in group) { - return
-- Cunning --
; - } else { - return
-- Intuition --
; +interface ActionElementProps { + name: string, + gilded: boolean, + value: number, +} + +const ActionElement = ({name, gilded, value}: ActionElementProps) => { + let dots = []; + for (let i = 0; i < value; i++) { + dots.push("\u25ef"); } + let diamond = gilded ? "\u25c6" : "\u25c7"; + return (
+

{diamond} {name}

+
{dots}
+
); +} + +interface ActionGroupElementProps { + group: Nerve | Cunning | Intuition; +} + +const ActionGroupElement = ({group}: ActionGroupElementProps) => { + var title; + var elements = []; + + switch (group.type_) { + case "nerve": { + title =
Nerve
+ elements.push(); + elements.push(); + elements.push(); + break + } + case "cunning": { + title =
Cunning
+ elements.push(); + elements.push(); + elements.push(); + break + } + case "intuition": { + title =
Intuition
+ elements.push(); + elements.push(); + elements.push(); + break + } + default: { + assertNever(group); + } + } + + return (
+

{title}

+ {elements} +
) } const CharsheetElement_ = ({ sheet }: CharsheetProps) => { @@ -104,9 +154,9 @@ const CharsheetElement_ = ({ sheet }: CharsheetProps) => {
- - - + + +
Role and Specialty
Marks, Scars, Relationships
@@ -125,26 +175,29 @@ export const CharsheetElement = () => { question: 'What were the contents of that book?', role: 'Slink', nerve: { + type_: "nerve", drives: { current: 2, max: 2 }, resistances: { current: 0, max: 3 }, - move: { guilded: false, score: 0 }, - strike: { guilded: false, score: 0 }, - control: { guilded: true, score: 0 }, - }, + 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: { guilded: false, score: 0 }, - read: { guilded: false, score: 0 }, - hide: { guilded: false, score: 0 }, - }, + 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: { guilded: false, score: 0 }, - focus: { guilded: false, score: 0 }, - sense: { guilded: false, score: 0 }, - } + survey: { gilded: false, score: 0 }, + focus: { gilded: false, score: 0 }, + sense: { gilded: false, score: 0 }, + } as Intuition }; return -- 2.44.1 From 0202b7bd59c1722aa4222a2d87f32a4f5ed8ec3a Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 27 Nov 2024 10:56:11 -0500 Subject: [PATCH 3/6] Set up a drive guage for candela drives --- visions/ui/src/App.tsx | 5 ++ visions/ui/src/plugins/Candela/Charsheet.css | 57 ++++++++++++++++++- visions/ui/src/plugins/Candela/Charsheet.tsx | 30 ++++------ .../plugins/Candela/DriveGuage/DriveGuage.css | 44 ++++++++++++++ .../plugins/Candela/DriveGuage/DriveGuage.tsx | 23 ++++++++ visions/ui/src/views/Design/Design.css | 4 ++ visions/ui/src/views/Design/Design.tsx | 19 +++++++ 7 files changed, 162 insertions(+), 20 deletions(-) create mode 100644 visions/ui/src/plugins/Candela/DriveGuage/DriveGuage.css create mode 100644 visions/ui/src/plugins/Candela/DriveGuage/DriveGuage.tsx create mode 100644 visions/ui/src/views/Design/Design.css create mode 100644 visions/ui/src/views/Design/Design.tsx diff --git a/visions/ui/src/App.tsx b/visions/ui/src/App.tsx index c0f28a4..fb49c72 100644 --- a/visions/ui/src/App.tsx +++ b/visions/ui/src/App.tsx @@ -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: + }, + { + path: "/design", + element: } ]); return ( diff --git a/visions/ui/src/plugins/Candela/Charsheet.css b/visions/ui/src/plugins/Candela/Charsheet.css index 6c63076..d3d2440 100644 --- a/visions/ui/src/plugins/Candela/Charsheet.css +++ b/visions/ui/src/plugins/Candela/Charsheet.css @@ -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; +} diff --git a/visions/ui/src/plugins/Candela/Charsheet.tsx b/visions/ui/src/plugins/Candela/Charsheet.tsx index 09cf46e..3d679cb 100644 --- a/visions/ui/src/plugins/Candela/Charsheet.tsx +++ b/visions/ui/src/plugins/Candela/Charsheet.tsx @@ -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 =
Nerve
+ title =
Nerve
elements.push(); elements.push(); elements.push(); break } case "cunning": { - title =
Cunning
+ title =
Cunning
elements.push(); elements.push(); elements.push(); break } case "intuition": { - title =
Intuition
+ title =
Intuition
elements.push(); elements.push(); elements.push(); @@ -132,7 +124,7 @@ const ActionGroupElement = ({group}: ActionGroupElementProps) => { } return (
-

{title}

+ {title} {elements}
) } diff --git a/visions/ui/src/plugins/Candela/DriveGuage/DriveGuage.css b/visions/ui/src/plugins/Candela/DriveGuage/DriveGuage.css new file mode 100644 index 0000000..74cbac1 --- /dev/null +++ b/visions/ui/src/plugins/Candela/DriveGuage/DriveGuage.css @@ -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; +} diff --git a/visions/ui/src/plugins/Candela/DriveGuage/DriveGuage.tsx b/visions/ui/src/plugins/Candela/DriveGuage/DriveGuage.tsx new file mode 100644 index 0000000..c159144 --- /dev/null +++ b/visions/ui/src/plugins/Candela/DriveGuage/DriveGuage.tsx @@ -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(
+ {i < current ?
 
: +
 
} + {i < max ?
 
: +
 
} +
) + } + components.splice(3, 1,
 
); + return (
+ {components} +
) +} diff --git a/visions/ui/src/views/Design/Design.css b/visions/ui/src/views/Design/Design.css new file mode 100644 index 0000000..96ef6a1 --- /dev/null +++ b/visions/ui/src/views/Design/Design.css @@ -0,0 +1,4 @@ +.section { + border: 2px solid black; + border-radius: 4px; +} diff --git a/visions/ui/src/views/Design/Design.tsx b/visions/ui/src/views/Design/Design.tsx new file mode 100644 index 0000000..45b248e --- /dev/null +++ b/visions/ui/src/views/Design/Design.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { DriveGuage } from '../../plugins/Candela/DriveGuage/DriveGuage'; + +const DriveGuages = () => { + return (
+
+ +
+
+ +
+
); +} + +export const DesignPage = () => { + return (
+ +
); +} -- 2.44.1 From b382c68382b16f1c14a45667ef72d741c83eb663 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 27 Nov 2024 18:40:14 -0500 Subject: [PATCH 4/6] Add role and specialty --- visions/ui/src/plugins/Candela/Charsheet.tsx | 42 ++++++++++++++++--- .../plugins/Candela/DriveGuage/DriveGuage.tsx | 3 +- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/visions/ui/src/plugins/Candela/Charsheet.tsx b/visions/ui/src/plugins/Candela/Charsheet.tsx index 3d679cb..7d77c8b 100644 --- a/visions/ui/src/plugins/Candela/Charsheet.tsx +++ b/visions/ui/src/plugins/Candela/Charsheet.tsx @@ -59,11 +59,15 @@ export type Charsheet = { style: string, catalyst: string, question: string, - role: string, nerve: Nerve, cunning: Cunning, intuition: Intuition, + + role: string, + role_abilities: string[], + specialty: string, + specialty_abilities: string[], } interface CharsheetProps { @@ -129,6 +133,27 @@ const ActionGroupElement = ({ group }: ActionGroupElementProps) => {
) } +interface AbilitiesElementProps { + role: string + role_abilities: string[] + specialty: string + specialty_abilities: string[] +} + +const AbilitiesElement = ({ role, role_abilities, specialty, specialty_abilities }: AbilitiesElementProps) => { + return (
+

ROLE: {role}

+
    + {role_abilities.map((ability) =>
  • {ability}
  • )} +
+

SPECIALTY: {role}

+
    + {specialty_abilities.map((ability) =>
  • {ability}
  • )} +
+
+ ); +} + const CharsheetElement_ = ({ sheet }: CharsheetProps) => { return (
@@ -150,7 +175,7 @@ const CharsheetElement_ = ({ sheet }: CharsheetProps) => {
-
Role and Specialty
+
Marks, Scars, Relationships
); @@ -165,10 +190,9 @@ export const CharsheetElement = () => { style: 'dapper gentleman', catalyst: 'a cursed book', question: 'What were the contents of that book?', - role: 'Slink', nerve: { type_: "nerve", - drives: { current: 2, max: 2 }, + drives: { current: 1, max: 2 }, resistances: { current: 0, max: 3 }, move: { gilded: false, score: 2 }, strike: { gilded: false, score: 1 }, @@ -189,7 +213,15 @@ export const CharsheetElement = () => { survey: { gilded: false, score: 0 }, focus: { gilded: false, score: 0 }, sense: { gilded: false, score: 0 }, - } as Intuition + } 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/DriveGuage/DriveGuage.tsx b/visions/ui/src/plugins/Candela/DriveGuage/DriveGuage.tsx index c159144..aee61cc 100644 --- a/visions/ui/src/plugins/Candela/DriveGuage/DriveGuage.tsx +++ b/visions/ui/src/plugins/Candela/DriveGuage/DriveGuage.tsx @@ -16,7 +16,8 @@ export const DriveGuage = ({ current, max }: Guage) => {
 
} ) } - components.splice(3, 1,
 
); + components.splice(3, 0,
 
); + components.splice(7, 0,
 
); return (
{components}
) -- 2.44.1 From d3db9d60c2fa37201d58b2d2161c96fd4559a2f9 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 28 Nov 2024 21:32:13 -0500 Subject: [PATCH 5/6] Fix asset providing --- visions/server/Taskfile.yml | 2 +- visions/server/src/asset_db.rs | 30 ++++++++++++++++++++---------- visions/server/src/core.rs | 1 + visions/server/src/handlers.rs | 3 ++- visions/server/src/main.rs | 4 ++-- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/visions/server/Taskfile.yml b/visions/server/Taskfile.yml index 58152e8..8785126 100644 --- a/visions/server/Taskfile.yml +++ b/visions/server/Taskfile.yml @@ -9,6 +9,6 @@ tasks: cmds: - cargo watch -x test - server: + dev: cmds: - cargo watch -x run diff --git a/visions/server/src/asset_db.rs b/visions/server/src/asset_db.rs index 62364e5..4e724e5 100644 --- a/visions/server/src/asset_db.rs +++ b/visions/server/src/asset_db.rs @@ -1,7 +1,5 @@ use std::{ - collections::{hash_map::Iter, HashMap}, - fmt::{self, Display}, - io::Read, + collections::{hash_map::Iter, HashMap}, fmt::{self, Display}, fs, io::Read, path::PathBuf }; use mime::Mime; @@ -36,6 +34,12 @@ impl From for Error { #[typeshare] pub struct AssetId(String); +impl AssetId { + pub fn as_str<'a>(&'a self) -> &'a str { + &self.0 + } +} + impl Display for AssetId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "AssetId({})", self.0) @@ -75,19 +79,25 @@ pub struct FsAssets { } impl FsAssets { - pub fn new() -> Self { - Self { - assets: HashMap::new(), - } - } + pub fn new(path: PathBuf) -> Self { + let dir = fs::read_dir(path).unwrap(); + let mut assets = HashMap::new(); - fn assets<'a>(&'a self) -> impl Iterator { - self.assets.keys() + for dir_ent in dir { + println!("{:?}", dir_ent); + let path = dir_ent.unwrap().path(); + let file_name = path.file_name().unwrap().to_str().unwrap(); + assets.insert(AssetId::from(file_name), path.to_str().unwrap().to_owned()); + } + Self { + assets, + } } } impl Assets for FsAssets { fn assets<'a>(&'a self) -> AssetIter<'a> { + println!("FsAssets assets: {:?}", self.assets); AssetIter(self.assets.iter()) } diff --git a/visions/server/src/core.rs b/visions/server/src/core.rs index d157a65..d8c8b69 100644 --- a/visions/server/src/core.rs +++ b/visions/server/src/core.rs @@ -99,6 +99,7 @@ impl Core { } pub fn available_images(&self) -> Vec { + println!("available_images"); self.0 .read() .unwrap() diff --git a/visions/server/src/handlers.rs b/visions/server/src/handlers.rs index ae31f61..4597d5a 100644 --- a/visions/server/src/handlers.rs +++ b/visions/server/src/handlers.rs @@ -63,7 +63,7 @@ pub async fn handle_available_images(core: Core) -> impl Reply { let image_paths: Vec = core .available_images() .into_iter() - .map(|path| format!("{}", path)) + .map(|path| format!("{}", path.as_str())) .collect(); Ok(Response::builder() @@ -116,6 +116,7 @@ pub async fn handle_connect_websocket( client_id: String, ) -> impl Reply { ws.on_upgrade(move |socket| { + println!("upgrading websocket"); let core = core.clone(); async move { let (mut ws_sender, _) = socket.split(); diff --git a/visions/server/src/main.rs b/visions/server/src/main.rs index 36d5323..4f433c6 100644 --- a/visions/server/src/main.rs +++ b/visions/server/src/main.rs @@ -5,7 +5,7 @@ use handlers::{ }; use std::{ convert::Infallible, - net::{IpAddr, Ipv4Addr, SocketAddr}, + net::{IpAddr, Ipv4Addr, SocketAddr}, path::PathBuf, }; use warp::{ // header, @@ -96,7 +96,7 @@ async fn handle_rejection(err: warp::Rejection) -> Result Date: Thu, 28 Nov 2024 22:28:41 -0500 Subject: [PATCH 6/6] Add a side panel character sheet --- visions/ui/package-lock.json | 6 + visions/ui/package.json | 5 +- .../ui/src/components/Guages/SimpleGuage.css | 0 .../ui/src/components/Guages/SimpleGuage.tsx | 8 ++ .../ui/src/components/Tabletop/Tabletop.css | 6 +- .../ui/src/components/Tabletop/Tabletop.tsx | 2 +- .../ui/src/components/Thumbnail/Thumbnail.tsx | 2 +- visions/ui/src/components/index.ts | 5 + visions/ui/src/plugins/Candela/Charsheet.tsx | 70 +-------- .../ui/src/plugins/Candela/CharsheetPanel.css | 24 ++++ .../ui/src/plugins/Candela/CharsheetPanel.tsx | 135 ++++++++++++++++++ visions/ui/src/plugins/Candela/index.tsx | 9 +- visions/ui/src/plugins/Candela/types.ts | 65 +++++++++ visions/ui/src/views/GmView/GmView.tsx | 4 +- .../ui/src/views/PlayerView/PlayerView.css | 5 +- .../ui/src/views/PlayerView/PlayerView.tsx | 8 +- 16 files changed, 265 insertions(+), 89 deletions(-) create mode 100644 visions/ui/src/components/Guages/SimpleGuage.css create mode 100644 visions/ui/src/components/Guages/SimpleGuage.tsx create mode 100644 visions/ui/src/components/index.ts create mode 100644 visions/ui/src/plugins/Candela/CharsheetPanel.css create mode 100644 visions/ui/src/plugins/Candela/CharsheetPanel.tsx create mode 100644 visions/ui/src/plugins/Candela/types.ts 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 && playing field}
+ return
{backgroundUrl && playing field}
} 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 (
{name}
{value}
); +} + + +interface ActionGroupElementProps { + group: Nerve | Cunning | Intuition; +} + +const ActionGroupElement = ({ group }: ActionGroupElementProps) => { + var title; + var elements = []; + + switch (group.type_) { + case "nerve": { + title =
Nerve
+ elements.push(); + elements.push(); + elements.push(); + break + } + case "cunning": { + title =
Cunning
+ elements.push(); + elements.push(); + elements.push(); + break + } + case "intuition": { + title =
Intuition
+ 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 (
+
+
) } -- 2.44.1