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 (
+
+
) }