fixing react-spring and some other stuff I guess

main
Mari 2 years ago
parent 04d373b6b4
commit 0ea544a952
  1. 2
      README.md
  2. 12
      package-lock.json
  3. 3
      package.json
  4. 6
      public/index.html
  5. 38
      src/App.css
  6. 48
      src/App.tsx
  7. 155
      src/CharacterEditor.ts
  8. 127
      src/CharacterStatus.css
  9. 421
      src/CharacterStatus.tsx
  10. 11
      src/FixedInterpolation.ts
  11. 11
      src/SpringyValueHook.ts
  12. BIN
      src/default-background.jpg
  13. 144
      src/default-portrait.svg
  14. 8
      src/index.css
  15. 4
      src/index.tsx
  16. 6
      src/resource_bar.tsx

@ -2,3 +2,5 @@
* src/fabula-points.svg: https://game-icons.net/1x1/lorc/star-swirl.html
* src/ultima-points.svg: https://game-icons.net/1x1/lorc/evil-moon.html
* src/default-portrait.svg: https://pixabay.com/vectors/woman-profile-silhouette-people-5786062/
* src/default-background.jpg: https://www.wallpaperflare.com/dark-insubstantial-spotlight-art-lighting-equipment-no-people-wallpaper-geayr/download

12
package-lock.json generated

@ -24,6 +24,9 @@
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
"devDependencies": {
"@types/bootstrap": "^5.2.6"
}
},
"node_modules/@adobe/css-tools": {
@ -3900,6 +3903,15 @@
"@types/node": "*"
}
},
"node_modules/@types/bootstrap": {
"version": "5.2.6",
"resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.6.tgz",
"integrity": "sha512-BlAc3YATdasbHoxMoBWODrSF6qwQO/E9X8wVxCCSa6rWjnaZfpkr2N6pUMCY6jj2+wf0muUtLySbvU9etX6YqA==",
"dev": true,
"dependencies": {
"@popperjs/core": "^2.9.2"
}
},
"node_modules/@types/connect": {
"version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",

@ -43,5 +43,8 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/bootstrap": "^5.2.6"
}
}

@ -3,6 +3,7 @@
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<base href="%PUBLIC_URL%/">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
@ -15,6 +16,11 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<style>
html {
background: #222;
}
</style>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.

@ -1,38 +0,0 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@ -1,11 +1,12 @@
import React, {ChangeEvent, useCallback, useMemo, useState} from 'react';
import './App.css';
import {Col, Container, Form, InputGroup, Row,} from "react-bootstrap";
import {Character, CharacterStatus, SPType} from "./CharacterStatus";
import {Character, CharacterStatus, hpToHealth, SPType} from "./CharacterStatus";
import DefaultPortrait from "./default-portrait.svg"
function App() {
const [maxHp, _setMaxHp] = useState(1)
const [hp, _setHp] = useState(1)
const [maxHp, _setMaxHp] = useState(50)
const [hp, _setHp] = useState(40)
const setHp = useCallback(function (v: number) {
v = Math.floor(v)
if (Number.isNaN(v) || v < 0 || v > maxHp || v === hp) {
@ -27,8 +28,8 @@ function App() {
(e: ChangeEvent<HTMLInputElement>) => setHp(parseInt(e.target.value)), [setHp])
const onMaxHpChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => setHpMax(parseInt(e.target.value)), [setHpMax])
const [maxMp, _setMaxMp] = useState(6)
const [mp, _setMp] = useState(6)
const [maxMp, _setMaxMp] = useState(1)
const [mp, _setMp] = useState(1)
const setMp = useCallback(function (v: number) {
v = Math.floor(v)
if (Number.isNaN(v) || v < 0 || v > maxMp || v === mp) {
@ -53,31 +54,32 @@ function App() {
const character = useMemo<Character>(() => ({
name: "Test",
level: 50,
hp: 40,
maxHp: 50,
level: 26,
hp: hp,
maxHp: maxHp,
mp: 40,
maxMp: 50,
ip: mp,
maxIp: maxMp,
sp: 1,
spType: SPType.UltimaPoints,
turnsLeft: hp,
turnsTotal: maxHp,
}), [hp, maxHp, mp, maxMp])
return (
ip: 3,
maxIp: 6,
sp: 3,
spType: SPType.FabulaPoints,
portraitUrl: DefaultPortrait,
health: hpToHealth(hp, maxHp),
turnsTotal: 3,
turnsLeft: 2,}), [hp, maxHp, mp, maxMp])
return <React.Fragment>
<Container fluid>
<Row>
<Col>
<InputGroup>
<InputGroup.Text>Max HP</InputGroup.Text>
<Form.Control type="numeric" max="9999" min="0" step="1" value={maxHp} onChange={onMaxHpChange} />
<Form.Control type="number" max="9999" min="0" step="1" value={maxHp} onChange={onMaxHpChange} />
</InputGroup>
</Col>
<Col>
<InputGroup>
<InputGroup.Text>Current HP</InputGroup.Text>
<Form.Control type="numeric" max={maxHp} min="0" step="1" value={hp} onChange={onHpChange} />
<Form.Control type="number" max={maxHp} min="0" step="1" value={hp} onChange={onHpChange} />
</InputGroup>
</Col>
</Row>
@ -85,21 +87,19 @@ function App() {
<Col>
<InputGroup>
<InputGroup.Text>Max MP</InputGroup.Text>
<Form.Control type="numeric" max="9999" min="0" step="1" value={maxMp} onChange={onMaxMpChange} />
<Form.Control type="number" max="9999" min="0" step="1" value={maxMp} onChange={onMaxMpChange} />
</InputGroup>
</Col>
<Col>
<InputGroup>
<InputGroup.Text>Current MP</InputGroup.Text>
<Form.Control type="numeric" max={maxMp} min="0" step="1" value={mp} onChange={onMpChange} />
<Form.Control type="number" max={maxMp} min="0" step="1" value={mp} onChange={onMpChange} />
</InputGroup>
</Col>
</Row>
<Row>
<CharacterStatus character={character} />
</Row>
</Container>
);
<CharacterStatus character={character} active={false} />
</React.Fragment>;
}
export default App;

@ -0,0 +1,155 @@
import {Character} from "./CharacterStatus";
interface CharacterPrivacySettings {
readonly showCharacter: boolean
readonly showHp: boolean
readonly showHealth: boolean
readonly showMp: boolean
readonly showIp: boolean
readonly showSp: boolean
readonly showName: boolean
readonly showPortrait: boolean
readonly showTurns: boolean
readonly showStatuses: boolean
readonly showLevel: boolean
}
enum CharacterPrivacy {
Friend = "friend",
ScannedEnemy = "scanned enemy",
LightlyScannedEnemy = "lightly scanned enemy",
UnscannedEnemy = "unscanned enemy",
SecretiveEnemy = "secretive",
Hidden = "hidden",
}
const CharacterPrivacySettings: Record<CharacterPrivacy, CharacterPrivacySettings> = {
[CharacterPrivacy.Friend]: {
showCharacter: true,
showHp: true,
showHealth: true,
showMp: true,
showIp: true,
showSp: true,
showName: true,
showPortrait: true,
showTurns: true,
showStatuses: true,
showLevel: true,
},
[CharacterPrivacy.ScannedEnemy]: {
showCharacter: true,
showHp: true,
showHealth: true,
showMp: true,
showIp: false,
showSp: true,
showName: true,
showPortrait: true,
showTurns: true,
showStatuses: true,
showLevel: true,
},
[CharacterPrivacy.LightlyScannedEnemy]: {
showCharacter: true,
showHp: true,
showHealth: true,
showMp: false,
showIp: false,
showSp: true,
showName: true,
showPortrait: true,
showTurns: true,
showStatuses: true,
showLevel: true,
},
[CharacterPrivacy.UnscannedEnemy]: {
showCharacter: true,
showHp: false,
showHealth: true,
showMp: false,
showIp: false,
showSp: true,
showName: true,
showPortrait: true,
showTurns: true,
showStatuses: true,
showLevel: false,
},
[CharacterPrivacy.SecretiveEnemy]: {
showCharacter: true,
showHp: false,
showHealth: true,
showMp: false,
showIp: false,
showSp: false,
showName: false,
showPortrait: true,
showTurns: true,
showStatuses: true,
showLevel: false,
},
[CharacterPrivacy.Hidden]: {
showCharacter: false,
showHp: false,
showHealth: false,
showMp: false,
showIp: false,
showSp: false,
showName: false,
showPortrait: false,
showTurns: false,
showStatuses: false,
showLevel: false,
}
}
function applyPrivacy(c: EditableCharacter): Character|null {
const p = CharacterPrivacySettings[c.privacy ?? CharacterPrivacy.Hidden]
if (!p.showCharacter) {
return null
}
const out: {-readonly [Field in keyof EditableCharacter]?: EditableCharacter[Field]} = Object.assign({}, c)
delete out.privacy
if (!p.showHp) {
delete out.hp
delete out.maxHp
if (!p.showHealth) {
delete out.health
}
}
if (!p.showMp) {
delete out.mp
delete out.maxMp
}
if (!p.showIp) {
delete out.ip
delete out.maxIp
}
if (!p.showSp) {
delete out.sp
delete out.spType
}
if (!p.showName) {
delete out.name
}
if (!p.showPortrait) {
delete out.portraitUrl
}
if (!p.showTurns) {
delete out.turnsLeft
delete out.turnsTotal
delete out.canAct
}
if (!p.showStatuses) {
delete out.statuses
}
if (!p.showLevel) {
delete out.level
}
return out
}
interface EditableCharacter extends Character {
privacy: CharacterPrivacy
}

@ -2,13 +2,13 @@
height: 150px;
width: 500px;
position: relative;
background-color: #454;
box-sizing: content-box;
}
.characterHeader {
position: absolute;
left: 160px;
bottom: 60px;
bottom: 55px;
z-index: 4;
}
@ -45,19 +45,19 @@
width: calc(100% - 30px);
margin: 2px 2px 8px 5px;
bottom: 0;
box-sizing: content-box;
}
.characterHpValue, .characterMpValue, .characterIpValue {
.characterHpValue, .characterMpValue, .characterIpValue, .characterHealthText {
color: white;
font-family: sans-serif;
font-weight: bold;
font-style: italic;
font-size: 60px;
text-align: right;
-webkit-text-stroke: 1px black;
text-shadow: 2px 2px rgba(0, 0, 0, 0.5);
position: absolute;
bottom: 5px;
bottom: 0;
}
.characterHp {
@ -68,21 +68,34 @@
bottom: 28px;
overflow: visible;
z-index: 1;
box-sizing: content-box;
}
.characterHpBar {
height: 25px;
}
.characterHpValue, .characterHealthText {
right: 5px;
transition: color 0.3s ease-in;
}
.characterHpValue {
font-size: 60px;
}
.characterHealthText {
font-size: 30px;
right: 5px;
bottom: 15px;
transition: color 0.3s ease-in;
}
.characterMp, .characterIp {
position: absolute;
bottom: 0;
height: 40px;
box-sizing: content-box;
}
.characterMp {
@ -106,21 +119,16 @@
right: 10px;
}
.characterMpBar {
height: 20px;
}
.characterMpValue {
font-size: 40px;
}
.characterPortrait {
position: absolute;
top: 10px;
bottom: 15px;
left: 50px;
width: 125px;
background: no-repeat center / cover;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
background-blend-mode: soft-light;
z-index: 0;
}
@ -133,18 +141,42 @@
font-size: 40px;
font-weight: bold;
-webkit-text-stroke: 2px black;
text-shadow: 2px 2px 2px black ;
text-shadow: 2px 2px 2px black;
line-height: 40px;
text-align: center;
box-sizing: border-box;
border: 2px solid black;
border-radius: 9px 0;
color: white;
transition: background-color 0.3s;
}
.characterTurnsNone {
display: none;
}
.characterTurnsReady {
background-color: deepskyblue;
}
.characterTurnsDone {
background-color: darkblue;
background-color: mediumblue;
}
.characterTurnsKO {
background-color: #333;
}
.characterTurnsCantAct {
background-color: #666;
}
.characterTurnsActive {
background-color: mediumspringgreen;
}
.characterTurnsHighTurns {
background-color: paleturquoise;
}
.characterSp {
@ -152,12 +184,12 @@
color: white;
line-height: 40px;
-webkit-text-stroke: 2px black;
text-shadow: 2px 2px 2px black ;
text-shadow: 2px 2px 2px black;
font-size: 30px;
letter-spacing: -3px;
text-align: center;
font-weight: bold;
bottom: 5px;
top: 50px;
left: 5px;
width: 40px;
height: 40px;
@ -179,3 +211,62 @@
.characterSp:hover {
opacity: 100%;
}
.characterStatuses {
position: absolute;
top: 5px;
right: 5px;
left: 180px;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
}
.characterStatusIcon {
position: relative;
width: 36px;
height: 48px;
background-size: contain;
margin-left: 5px;
overflow: visible;
}
.characterStatusIconCountBadge {
display: block;
color: white;
text-shadow: 2px 2px 0 black;
-webkit-text-stroke: 1px black;
font-size: 25px;
letter-spacing: -3px;
text-align: center;
font-weight: bold;
position: absolute;
top: 0;
right: 0;
}
.characterStatusName, .characterHelpName {
font-weight: bold;
}
.characterStatusCount, .characterHelpValue {
margin-left: 5px;
font-style: italic;
font-size: smaller;
}
.characterStatusHeader, .characterHelpHeader {
text-align: left;
}
.characterStatusDescription, .characterHelpDescription {
text-align: left;
font-size: smaller;
}
.characterStatusCount::before, .characterHelpValue::before {
content: "("
}
.characterStatusCount::after, .characterHelpValue::after {
content: ")"
}

@ -1,16 +1,13 @@
import { animated } from "@react-spring/web";
import {animated, useSpring} from "@react-spring/web";
import {ReactElement, useMemo} from "react";
import {
evaluateResourceBarStyles,
ResourceBarColors,
ResourceBarStyles
} from "./resource_bar";
import {evaluateResourceBarStyles, ResourceBarColors, ResourceBarStyles} from "./resource_bar";
import {isDefined} from "./type_check";
import {
SpringyValueInterpolatables,
useSpringyValue
} from "./SpringyValueHook";
import {SpringyValueInterpolatables, useSpringyValue} from "./SpringyValueHook";
import "./CharacterStatus.css";
import DefaultPortrait from "./default-portrait.svg";
import OverlayTrigger from "react-bootstrap/OverlayTrigger";
import Tooltip from "react-bootstrap/Tooltip";
import {to} from "@react-spring/web";
export enum CharacterHealth {
Full = "Full",
@ -21,13 +18,129 @@ export enum CharacterHealth {
KO = "KO",
}
export function healthToColor(health: CharacterHealth | undefined): string {
switch (health) {
case CharacterHealth.Full:
return "#cfc"
case CharacterHealth.Healthy:
return "#efe"
case CharacterHealth.Crisis:
return "#ffa"
case CharacterHealth.Peril:
return "#faa"
case CharacterHealth.KO:
return "#f66"
case CharacterHealth.Wounded:
default:
return "#fff"
}
}
export function healthToFraction(health: CharacterHealth | undefined): number {
switch (health) {
case CharacterHealth.Full:
return 1
case CharacterHealth.Healthy:
return 0.95
case CharacterHealth.Crisis:
return 0.40
case CharacterHealth.Peril:
return 0.05
case CharacterHealth.KO:
return 0
case CharacterHealth.Wounded:
case undefined:
default:
return 0.75
}
}
export function hpToHealth(hp: number | undefined, maxHp: number | undefined): CharacterHealth | undefined {
if (!(isDefined(hp) && isDefined(maxHp)) || maxHp <= 0) {
return undefined
}
if (Math.round(hp) >= maxHp) {
return CharacterHealth.Full
} else if (Math.round(hp) * 10 >= maxHp * 9) {
return CharacterHealth.Healthy
} else if (Math.round(hp) * 2 > maxHp) {
return CharacterHealth.Wounded
} else if (Math.round(hp) * 10 > maxHp) {
return CharacterHealth.Crisis
} else if (Math.round(hp) >= 1) {
return CharacterHealth.Peril
} else {
return CharacterHealth.KO
}
}
export function healthToBounds(health: CharacterHealth | undefined): string {
switch (health) {
case CharacterHealth.Full:
return "100%"
case CharacterHealth.Healthy:
return "90-99%"
case CharacterHealth.Wounded:
return "51-99%"
case CharacterHealth.Crisis:
return "11-50%"
case CharacterHealth.Peril:
return "1-10%"
case CharacterHealth.KO:
return "0%"
default:
return "???"
}
}
export enum CharacterTurnState {
None = "None",
Ready = "Ready",
HighTurns = "HighTurns",
Done = "Done",
CantAct = "CantAct",
KO = "KO",
Downed = "Downed",
Active = "Active",
}
export function turnStateToTitle(state: CharacterTurnState): string {
switch (state) {
case CharacterTurnState.Active:
return "Active"
case CharacterTurnState.Ready:
return "Ready"
case CharacterTurnState.HighTurns:
return "Multiple Turns"
case CharacterTurnState.Done:
return "Done"
case CharacterTurnState.CantAct:
return "Can't Act"
case CharacterTurnState.Downed:
return "Downed"
case CharacterTurnState.None:
default:
return "None"
}
}
export function turnStateToDescription(state: CharacterTurnState): string {
switch (state) {
case CharacterTurnState.Active:
return "Currently taking a turn."
case CharacterTurnState.Ready:
return "Has not acted yet this round."
case CharacterTurnState.HighTurns:
return "Has %c% turns left out of %m% turns. Must still alternate with opponents."
case CharacterTurnState.Done:
return "Has finished acting this round."
case CharacterTurnState.CantAct:
return "Is currently unable to act."
case CharacterTurnState.Downed:
return "Has 0 HP. Is currently down and out of the action and unable to act."
case CharacterTurnState.None:
default:
return "Cannot take turns."
}
}
export enum SPType {
@ -35,28 +148,41 @@ export enum SPType {
FabulaPoints = "Fabula",
}
function spTypeToDescription(sp: SPType): string {
switch (sp) {
case SPType.UltimaPoints:
return ("The number of Ultima Points. Ultima Points can be used to make a getaway, "
+ "recover MP and clear status effects, or perform special villainy moves.")
case SPType.FabulaPoints:
return ("The number of Fabula Points. Fabula Points can be used to buy rerolls by "
+ "invoking your Traits, boost your rolls by invoking your Bonds, or add elements to the story.")
}
}
export interface StatusEffect {
name: string
iconUrl: string
readonly name: string
readonly count?: number
readonly iconUrl: string
readonly description?: string
}
export interface Character {
portraitUrl?: string
name?: string
level?: number
hp?: number
maxHp?: number
health?: CharacterHealth
mp?: number
maxMp?: number
ip?: number
maxIp?: number
sp?: number
spType?: SPType
turnsLeft?: number
turnsTotal?: number
canAct?: boolean
statuses?: StatusEffect[]
readonly portraitUrl?: string
readonly name?: string
readonly level?: number
readonly hp?: number
readonly maxHp?: number
readonly health?: CharacterHealth
readonly mp?: number
readonly maxMp?: number
readonly ip?: number
readonly maxIp?: number
readonly sp?: number
readonly spType?: SPType
readonly turnsLeft?: number
readonly turnsTotal?: number
readonly canAct?: boolean
readonly statuses?: readonly StatusEffect[]
}
const hpBarStyle: SpringyValueInterpolatables<ResourceBarStyles> = {
@ -96,28 +222,35 @@ const ipBarStyle: SpringyValueInterpolatables<ResourceBarStyles> = {
},
}
export function CharacterStatus({character}: {character: Character}): ReactElement {
const {name, level, health} = character
export function CharacterStatus({character, active}: {character: Character, active: boolean}): ReactElement {
const {name, level, health, statuses} = character
const {hp, maxHp} = character
const {interpolate: hpInterpolate} = useSpringyValue({
current: hp,
max: maxHp,
flash: isDefined(maxHp) && isDefined(hp) && hp * 2 < maxHp && hp > 0,
const effectiveMaxHp = maxHp ?? 100
const effectiveHp = hp ?? (healthToFraction(health) * effectiveMaxHp)
const {springs: [, , {v: hpRecentSpring}], flashSpring: {v: hpFlashSpring}, interpolate: hpInterpolate} = useSpringyValue({
current: effectiveHp,
max: effectiveMaxHp,
flash: effectiveHp * 2 <= effectiveMaxHp && effectiveHp > 0,
})
const {hpText, hpBarStyleInterpolated} = useMemo(() => {
if (isDefined(hp) && isDefined(maxHp) && maxHp > 0) {
const {hpText, hpTextStyleInterpolated, hpBarStyleInterpolated} = useMemo(() => {
if ((isDefined(hp) && isDefined(maxHp)) || isDefined(health)) {
return {
hpText: hpInterpolate(({recentValue}) => `${Math.round(recentValue)}`),
hpText: isDefined(hp)
? to([hpRecentSpring], recentValue => `${Math.round(recentValue)}`)
: to([hpRecentSpring], recentValue => hpToHealth(recentValue, maxHp) ?? "???"),
hpBarStyleInterpolated: evaluateResourceBarStyles(hpBarStyle, hpInterpolate),
hpTextStyleInterpolated: {
color: to([hpRecentSpring], recentValue => healthToColor(hpToHealth(recentValue, maxHp)))
}
}
} else {
return {}
}
}, [hp, maxHp, hpInterpolate])
}, [hp, health, maxHp, hpRecentSpring, hpInterpolate])
const {mp, maxMp} = character
const {interpolate: mpInterpolate} = useSpringyValue({
const {springs: [, , {v: mpRecentSpring}], interpolate: mpInterpolate} = useSpringyValue({
current: mp,
max: maxMp,
flash: false,
@ -125,16 +258,16 @@ export function CharacterStatus({character}: {character: Character}): ReactEleme
const {mpText, mpBarStyleInterpolated} = useMemo(() => {
if (isDefined(mp) && isDefined(maxMp) && maxMp > 0) {
return {
mpText: mpInterpolate(({recentValue}) => `${Math.round(recentValue)}`),
mpText: to([mpRecentSpring], (recentValue) => `${Math.round(recentValue)}`),
mpBarStyleInterpolated: evaluateResourceBarStyles(mpBarStyle, mpInterpolate),
}
} else {
return {}
}
}, [mp, maxMp, mpInterpolate])
}, [mp, maxMp, mpRecentSpring, mpInterpolate])
const {ip, maxIp} = character
const {interpolate: ipInterpolate} = useSpringyValue({
const {springs: [, , {v: ipRecentSpring}], interpolate: ipInterpolate} = useSpringyValue({
current: ip,
max: maxIp,
flash: false,
@ -142,91 +275,221 @@ export function CharacterStatus({character}: {character: Character}): ReactEleme
const {ipText, ipBarStyleInterpolated} = useMemo(() => {
if (isDefined(ip) && isDefined(maxIp) && maxIp > 0) {
return {
ipText: ipInterpolate(({recentValue}) => `${Math.round(recentValue)}`),
ipText: to([ipRecentSpring], recentValue => `${Math.round(recentValue)}`),
ipBarStyleInterpolated: evaluateResourceBarStyles(ipBarStyle, ipInterpolate),
}
} else {
return {}
}
}, [ip, maxIp, ipInterpolate])
}, [ip, maxIp, ipRecentSpring, ipInterpolate])
const {sp, spType} = character
const {interpolate: spInterpolate} = useSpringyValue({
const {springs: [, , {v: spRecentSpring}]} = useSpringyValue({
current: sp,
flash: isDefined(spType) && isDefined(sp) && sp > 0,
})
const {spText} = useMemo(() => {
if (isDefined(sp) && isDefined(spType)) {
return {
spText: spInterpolate(({recentValue}) => recentValue.toFixed(0))
spText: to([spRecentSpring], (recentValue) => recentValue.toFixed(0))
}
} else {
return {}
}
}, [sp, spType, spInterpolate])
}, [sp, spType, spRecentSpring])
const {turnsLeft, turnsTotal, canAct} = character
const {turnsState, turnsText} = useMemo(() => {
if (isDefined(turnsTotal) && hp === 0 && isDefined(maxHp) && maxHp > 0) {
if (!isDefined(turnsTotal) || !isDefined(turnsLeft)) {
return {
turnsState: CharacterTurnState.KO,
turnsState: CharacterTurnState.None
}
} else if (isDefined(turnsTotal) && (canAct === false || turnsTotal === 0)) {
} else if (active) {
return {
turnsState: CharacterTurnState.CantAct,
turnsState: CharacterTurnState.Active,
turnsText: "🞂",
}
} else if (isDefined(turnsTotal) && turnsLeft === 0) {
} else if (hp === 0 && isDefined(maxHp) && maxHp > 0) {
return {
turnsState: CharacterTurnState.Done,
turnsState: CharacterTurnState.Downed,
turnsText: (isDefined(turnsTotal) && turnsLeft === 0) ? "✓" : "",
}
} else if (turnsTotal === 1 && turnsLeft === 1) {
} else if (canAct === false || turnsTotal === 0) {
return {
turnsState: CharacterTurnState.Ready,
turnsState: CharacterTurnState.CantAct,
}
} else if (isDefined(turnsTotal) && turnsTotal > 1 && isDefined(turnsLeft)) {
} else if (turnsLeft === 0) {
return {
turnsState: CharacterTurnState.Done,
turnsText: "✓"
}
} else if (turnsTotal > 1) {
return {
turnsState: CharacterTurnState.HighTurns,
turnsText: `${turnsLeft}`
}
} else {
return {
turnsState: CharacterTurnState.None
turnsState: CharacterTurnState.Ready,
}
}
}, [active, hp, maxHp, canAct, turnsLeft, turnsTotal])
const {portraitUrl} = character
const effectivePortraitUrl = portraitUrl ?? DefaultPortrait
const portraitFilterInterpolated = useMemo(() => {
return to([hpRecentSpring], recentValue => {
const filter = {
color: 100,
brightness: 100,
}
if (isDefined(effectiveMaxHp) && Math.round(recentValue) < 1 && effectiveMaxHp > 0) {
filter.color *= 0.50
filter.brightness *= 0.25
}
if (canAct === false || turnsTotal === 0) {
filter.color *= 0.50
filter.brightness *= 0.50
}
if (isDefined(turnsTotal) && turnsLeft === 0) {
filter.color *= 0.75
filter.brightness *= 0.75
}
return filter
})
}, [hpRecentSpring, effectiveMaxHp, turnsTotal, turnsLeft, canAct])
const {brightness: brightnessSpring, grayscale: grayscaleSpring} = useSpring({
grayscale: to([portraitFilterInterpolated], ({color}) => 100 - color),
brightness: to([portraitFilterInterpolated], ({brightness}) => brightness),
})
const characterPortraitStyleInterpolated = useMemo(() => {
return {
backgroundImage: to([hpFlashSpring], (flashValue: number) => {
return `radial-gradient(closest-side, rgb(75% 0% 0% / ${Math.round(100 * flashValue)}%), transparent), url("${effectivePortraitUrl}")`
}),
filter: to([brightnessSpring, grayscaleSpring],
(brightness: number, grayscale: number) =>
`grayscale(${grayscale}%) brightness(${brightness}%)`)
}
}, [hp, maxHp, canAct, turnsLeft, turnsTotal])
}, [brightnessSpring, grayscaleSpring, hpFlashSpring, effectivePortraitUrl])
const hpTooltip = <Tooltip>
<div className={"characterHelpHeader"}>
<span className={"characterHelpName"}>{isDefined(maxHp) && isDefined(hp) ? "Health Points" : health}</span>
<span className={"characterHelpValue"}>{isDefined(maxHp) && isDefined(hp) ? `${hp}/${maxHp}` : (healthToBounds(health))}</span></div>
{<div className={"characterHelpDescription"}>
{isDefined(hp)
? "Health Points, or HP. HP represent a character's determination and will. "
+ "HP are lost when taking damage. When a character's HP reach 0, they are defeated."
: "The enemy's condition, giving a rough estimate of its current HP."}
</div>}
</Tooltip>
const mpTooltip = <Tooltip>
<div className={"characterHelpHeader"}>
<span className={"characterHelpName"}>Mind Points</span>
{isDefined(maxMp) && isDefined(mp) && <span className={"characterHelpValue"}>{mp}/{maxMp}</span>}
</div>
<div className={"characterHelpDescription"}>
Mind Points, or MP. MP represent a character's focus and energy. MP are spent to use all manner of abilities.
</div>
</Tooltip>
const ipTooltip = <Tooltip>
<div className={"characterHelpHeader"}>
<span className={"characterHelpName"}>Inventory Points</span>
{isDefined(maxIp) && isDefined(ip) && <span className={"characterHelpValue"}>{ip}/{maxIp}</span>}
</div>
<div className={"characterHelpDescription"}>
Inventory Points, or IP. IP represent a character's stock of prepared items. IP are spent to use items.
</div>
</Tooltip>
return <div className="characterStatus">
<div className={"characterPortrait"} />
<animated.div className={"characterPortrait"} style={characterPortraitStyleInterpolated} />
{isDefined(turnsState) &&
<div className={"characterTurns characterTurns" + turnsState}>{turnsText}</div>}
<OverlayTrigger delay={{show: 750, hide: 0}} overlay={
<Tooltip>
<div className={"characterHelpHeader"}>
<span className={"characterHelpName"}>{turnStateToTitle(turnsState)}</span>
{isDefined(turnsTotal) && isDefined(turnsLeft) && turnsTotal > 1 && <span className={"characterHelpValue"}>{turnsLeft}/{turnsTotal}</span>}</div>
{<div className={"characterHelpDescription"}>
{isDefined(turnsTotal) && isDefined(turnsLeft) &&
turnStateToDescription(turnsState)
.replaceAll("%c%", turnsLeft.toFixed(0))
.replaceAll("%m%", turnsTotal.toFixed(0))}
</div>}
</Tooltip>
} placement={"right"}>
<div className={"characterTurns characterTurns" + turnsState}>{turnsText}</div>
</OverlayTrigger>}
<div className={"characterHeader"}>
{isDefined(level) &&
<div className="characterLevel">
<div className="characterLevel">
<span className="characterLevelLabel">Lv</span>
<span className="characterLevelValue">{level}</span>
</div>}
{isDefined(name) &&
<div className={"characterName characterName" + (health ?? "Unknown")}>{name}</div>}
<span className="characterLevelValue">{level ?? "??"}</span>
</div>
<div className={"characterName"}>{name ?? "???"}</div>
</div>
{isDefined(hpText) &&
<div className={"characterHp"}>
<animated.div className={"characterHpBar"} style={hpBarStyleInterpolated} />
<animated.div className={"characterHpValue"}>{hpText}</animated.div>
<OverlayTrigger delay={{show: 750, hide: 0}} overlay={hpTooltip} placement={"top"}>
<animated.div className={"characterHpBar"} style={hpBarStyleInterpolated} />
</OverlayTrigger>
<OverlayTrigger delay={{show: 750, hide: 0}} overlay={hpTooltip} placement={"top"}>
<animated.div
className={isDefined(hp) ? "characterHpValue" : "characterHealthText"}
style={hpTextStyleInterpolated}>{hpText}</animated.div>
</OverlayTrigger>
</div>}
{isDefined(mpText) &&
<div className={"characterMp"}>
<animated.div className={"characterMpBar"} style={mpBarStyleInterpolated} />
<animated.div className={"characterMpValue"}>{mpText}</animated.div>
<OverlayTrigger delay={{show: 750, hide: 0}} overlay={mpTooltip} placement={"top"}>
<animated.div className={"characterMpBar"} style={mpBarStyleInterpolated} />
</OverlayTrigger>
<OverlayTrigger delay={{show: 750, hide: 0}} overlay={mpTooltip} placement={"top"}>
<animated.div className={"characterMpValue"}>{mpText}</animated.div>
</OverlayTrigger>
</div>}
{isDefined(ipText) &&
<div className={"characterIp"}>
<animated.div className={"characterIpBar"} style={ipBarStyleInterpolated} />
<animated.div className={"characterIpValue"}>{ipText}</animated.div>
</div>}
<div className={"characterIp"}>
<OverlayTrigger delay={{show: 750, hide: 0}} overlay={ipTooltip} placement={"top"}>
<animated.div className={"characterIpBar"} style={ipBarStyleInterpolated} />
</OverlayTrigger>
<OverlayTrigger delay={{show: 750, hide: 0}} overlay={ipTooltip} placement={"top"}>
<animated.div className={"characterIpValue"}>{ipText}</animated.div>
</OverlayTrigger>
</div>
}
{isDefined(spText) &&
<animated.div className={"characterSp characterSp" + spType}>
<animated.span className={"characterSpValue characterSpValue" + spType}>
{spText}</animated.span>
</animated.div>}
<OverlayTrigger delay={{show: 750, hide: 0}} overlay={
<Tooltip>
<div className={"characterHelpHeader"}>
<span className={"characterHelpName"}>{spType} Points</span>
<span className={"characterHelpValue"}>{sp}</span></div>
{isDefined(spType) && <div className={"characterHelpDescription"}>
{spTypeToDescription(spType)}
</div>}
</Tooltip>
} placement={"right"}>
<animated.div className={"characterSp characterSp" + spType}>
<animated.span className={"characterSpValue characterSpValue" + spType}>
{spText}</animated.span>
</animated.div>
</OverlayTrigger>}
{isDefined(statuses) &&
<div className={"characterStatuses"}>
{statuses.map(({name, count, description, iconUrl}) =>
<OverlayTrigger key={iconUrl} delay={{show: 300, hide: 0}} overlay={
<Tooltip>
<div className={"characterStatusHeader"}>
<span className={"characterStatusName"}>{name}</span>
{isDefined(count) && <span className={"characterStatusCount"}>{count}</span>}</div>
{isDefined(description) && <div className={"characterStatusDescription"}>
{isDefined(count) ? description.replaceAll("%c%", count.toFixed(0)) : description}
</div>}
</Tooltip>
} placement={"bottom"}>
<div className={"characterStatusIcon"} style={{backgroundImage: `url("${iconUrl}")`}}><span className={"characterStatusIconCountBadge"}>{count}</span></div>
</OverlayTrigger>
)}
</div>}
</div>
}

@ -0,0 +1,11 @@
import {Interpolation, InterpolatorArgs, Globals} from "@react-spring/web";
import {getAnimated} from "@react-spring/animated";
export class FixedInterpolation<Input = any, Output = any> extends Interpolation<Input, Output> {
constructor(readonly source: unknown, args: InterpolatorArgs<Input, Output>) {
super(source, args);
getAnimated(this)!.setValue(this._get())
}
}
Globals.assign({to: (source, args) => new FixedInterpolation(source, args)})

@ -1,5 +1,6 @@
import {useCallback, useMemo, useState} from "react";
import {Interpolation, SpringConfig, SpringValue, to, useSpring, useTrail} from "@react-spring/web";
import {AnimatedProps, SpringConfig, SpringValue, to, useSpring, useTrail} from "@react-spring/web";
import {FluidValue} from "@react-spring/shared";
export interface UseSpringyValueProps {
current?: number
@ -38,11 +39,9 @@ export function isSpringyValueLiteral<T>(value: SpringyValueInterpolatable<T>):
export type SpringyValueInterpolator<T> = (v: SpringyValues) => T
export type SpringyValueInterpolatable<T> = SpringyValueLiteral<T> | Exclude<T, Function> | SpringyValueInterpolator<T>
export type SpringyValueInterpolate = <T>(v: SpringyValueInterpolatable<T>) => SpringyValueInterpolated<T>
export type SpringyValueInterpolated<T> = T | Interpolation<T>
export type SpringyValueInterpolated<T> = T | FluidValue<T>
export type SpringyValueInterpolatables<TargetType extends object> =
{[Property in keyof TargetType]: SpringyValueInterpolatable<TargetType[Property]>}
export type SpringyValueInterpolateds<TargetType extends object> =
{[Property in keyof TargetType]: SpringyValueInterpolated<TargetType[Property]>}
export function evaluateSpringyValueInterpolator<T>(f: SpringyValueInterpolatable<T>, v: SpringyValues): T {
if (f instanceof Function) {
@ -53,12 +52,12 @@ export function evaluateSpringyValueInterpolator<T>(f: SpringyValueInterpolatabl
return f
}
}
export function interpolateSpringyValueInterpolatables<T extends object>(values: SpringyValueInterpolatables<T>, interpolator: SpringyValueInterpolate): SpringyValueInterpolateds<T> {
export function interpolateSpringyValueInterpolatables<T extends object>(values: SpringyValueInterpolatables<T>, interpolator: SpringyValueInterpolate): AnimatedProps<T> {
const result: {[key: string]: SpringyValueInterpolated<any>} = {}
for (const [key, value] of Object.entries(values) as [string, SpringyValueInterpolatable<any>][]) {
result[key] = interpolator(value)
}
return result as SpringyValueInterpolateds<T>
return result as AnimatedProps<T>
}
const DEFAULT_SPRING_DELAYS = [0, 500, 50] as const

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 1599.9999 1600"
width="1600"
height="1600"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg">
<defs
id="defs915">
<linearGradient
id="linearGradient5377">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop5373" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop5375" />
</linearGradient>
<linearGradient
id="linearGradient1054">
<stop
style="stop-color:#000000;stop-opacity:0.99948096"
offset="0"
id="stop1050" />
<stop
style="stop-color:#ffffff;stop-opacity:1"
offset="1"
id="stop1052" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient1054"
id="linearGradient1056"
x1="2140.6294"
y1="5081.3457"
x2="2140.6294"
y2="5033.2822"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,4.221984,0,-16430.245)" />
<mask
id="mask2110">
<rect
style="fill:url(#linearGradient2114);fill-opacity:1;stroke:none;stroke-width:14.3833;stroke-linecap:round;paint-order:stroke fill markers"
id="rect2112"
width="4222.1011"
height="5094.7212"
x="-65.37365"
y="4.697618" />
</mask>
<linearGradient
xlink:href="#linearGradient1054"
id="linearGradient2114"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,4.221984,0,-16430.245)"
x1="2140.6294"
y1="5081.3457"
x2="2140.6294"
y2="5033.2822" />
<linearGradient
xlink:href="#linearGradient5377"
id="linearGradient5379"
x1="15.906018"
y1="813.49632"
x2="1270.3829"
y2="813.49632"
gradientUnits="userSpaceOnUse" />
<filter
style="color-interpolation-filters:sRGB"
id="filter7419"
x="-0.05386134"
y="-0.04236477"
width="1.1076622"
height="1.0847282">
<feGaussianBlur
stdDeviation="26.158905"
id="feGaussianBlur7421" />
</filter>
<mask
id="mask7562">
<rect
style="fill:url(#linearGradient7566);fill-opacity:1;stroke:none;stroke-width:33.7408;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect7564"
width="1586.8367"
height="1606.506"
x="15.906018"
y="6.9880676" />
</mask>
<linearGradient
xlink:href="#linearGradient1054"
id="linearGradient7566"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.2649389,0,0,1,9.0999676,13.498639)"
x1="754.01465"
y1="1595.9495"
x2="754.01465"
y2="1432.2393" />
<mask
id="mask7568">
<rect
style="fill:url(#linearGradient7572);fill-opacity:1;stroke:none;stroke-width:33.7408;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect7570"
width="1586.8367"
height="1606.506"
x="-115.69707"
y="-103.96293" />
</mask>
<linearGradient
xlink:href="#linearGradient1054"
id="linearGradient7572"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.2649389,0,0,1,-122.50312,-97.452358)"
x1="754.01465"
y1="1595.9495"
x2="754.01465"
y2="1432.2393" />
</defs>
<g
id="g7363"
transform="translate(121.07761,97.452358)"
mask="url(#mask7568)"
style="opacity:0.326049;fill:#7e7e7e;fill-opacity:1;stroke:#010101;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter7419)">
<path
d="m 1270.383,1613.494 c -419.394,0 -838.789,0 -1258.183,0 0,-16.796 -3.765,-38.564 -0.004,-54.863 3.525,-15.279 20.723,-27.003 32.097,-38.347 84.341,-84.124 169.069,-163.467 262.075,-237.796 26.127,-20.88 57.88,-54.938 90.008,-65.648 15.142,-5.418 26.541,-15.883 34.434,-29.429 25.974,-44.58 13.397,-9.705 3.962,-133.038 -3.114,-40.711 -25.58,-94.508 -43.866,-120.881 -20.253,-29.212 -62.116,-20.301 -108.968,-34.72 -19.986,-6.151 -39.725,-13.046 -60.237,-17.296 -10.09,-2.091 -38.06,0.896 -58.875,-25.159 -35.93,-44.973 3.072,-90.371 8.804,-99.947 18.265,-30.515 -7.554,-33.229 -2.224,-49.714 4.857,-15.021 13.144,-26.884 30.071,-30.016 2.403,-0.445 6.141,1.079 6.62,-3.108 0.382,-3.338 -2.608,-4.906 -4.833,-6.179 -18.033,-10.322 -18.458,-41.958 -8.578,-50.327 7.075,-5.993 12.79,-13.253 19.339,-19.727 14.042,-13.883 12.793,-14.218 -4.489,-23.92 -23.609,-13.254 -29.473,-39.43 -14.862,-62.228 7.686,-11.993 18.615,-20.149 29.289,-28.766 70.939,-57.266 61.677,-36.484 91.766,-66.421 10.022,-9.971 -18.57,-8.245 -18.635,-28.83 2.988,-0.432 4.678,1.157 6.629,1.948 10.844,4.397 23.848,0.618 27.985,-10.136 9.724,-25.274 2.111,-43.253 -1.983,-53.262 -2.814,-6.88 -0.647,-19.326 4.123,-20.759 28.6,-8.594 52.044,-101.91 103.003,-137.217 3.953,-2.739 38.216,-23.971 33.234,-38.225 -2.697,-7.717 0.602,-14.256 0.55,-21.294 -0.036,-4.869 -5.987,-12.842 8.155,-32.706 59.616,-83.736 353.009,-103.995 549.229,76.456 26.517,24.386 63.766,78.213 90.96,149.637 73.218,192.305 43.89,374.189 58.12,693.757 1.56,35.023 7.572,70.25 7.572,105.333 0,52.473 12.871,108.09 19.082,160.248 6.166,51.78 9.283,105.81 24.718,155.816 21.843,70.765 32.998,75.736 36.657,148.116 0.827,16.384 7.268,31.954 7.255,48.648 z"
id="path7361"
style="fill:#555555;fill-opacity:1;stroke:#010101;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
<g
id="g4587"
transform="translate(-10.525481,-13.498639)"
mask="url(#mask7562)"
style="fill-opacity:1;fill:#323232;stroke:#010101;stroke-opacity:1;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none">
<path
d="m 1270.383,1613.494 c -419.394,0 -838.789,0 -1258.183,0 0,-16.796 -3.765,-38.564 -0.004,-54.863 3.525,-15.279 20.723,-27.003 32.097,-38.347 84.341,-84.124 169.069,-163.467 262.075,-237.796 26.127,-20.88 57.88,-54.938 90.008,-65.648 15.142,-5.418 26.541,-15.883 34.434,-29.429 25.974,-44.58 13.397,-9.705 3.962,-133.038 -3.114,-40.711 -25.58,-94.508 -43.866,-120.881 -20.253,-29.212 -62.116,-20.301 -108.968,-34.72 -19.986,-6.151 -39.725,-13.046 -60.237,-17.296 -10.09,-2.091 -38.06,0.896 -58.875,-25.159 -35.93,-44.973 3.072,-90.371 8.804,-99.947 18.265,-30.515 -7.554,-33.229 -2.224,-49.714 4.857,-15.021 13.144,-26.884 30.071,-30.016 2.403,-0.445 6.141,1.079 6.62,-3.108 0.382,-3.338 -2.608,-4.906 -4.833,-6.179 -18.033,-10.322 -18.458,-41.958 -8.578,-50.327 7.075,-5.993 12.79,-13.253 19.339,-19.727 14.042,-13.883 12.793,-14.218 -4.489,-23.92 -23.609,-13.254 -29.473,-39.43 -14.862,-62.228 7.686,-11.993 18.615,-20.149 29.289,-28.766 70.939,-57.266 61.677,-36.484 91.766,-66.421 10.022,-9.971 -18.57,-8.245 -18.635,-28.83 2.988,-0.432 4.678,1.157 6.629,1.948 10.844,4.397 23.848,0.618 27.985,-10.136 9.724,-25.274 2.111,-43.253 -1.983,-53.262 -2.814,-6.88 -0.647,-19.326 4.123,-20.759 28.6,-8.594 52.044,-101.91 103.003,-137.217 3.953,-2.739 38.216,-23.971 33.234,-38.225 -2.697,-7.717 0.602,-14.256 0.55,-21.294 -0.036,-4.869 -5.987,-12.842 8.155,-32.706 59.616,-83.736 353.009,-103.995 549.229,76.456 26.517,24.386 63.766,78.213 90.96,149.637 73.218,192.305 43.89,374.189 58.12,693.757 1.56,35.023 7.572,70.25 7.572,105.333 0,52.473 12.871,108.09 19.082,160.248 6.166,51.78 9.283,105.81 24.718,155.816 21.843,70.765 32.998,75.736 36.657,148.116 0.827,16.384 7.268,31.954 7.255,48.648 z"
id="path4585"
style="fill-opacity:1;fill:#323232;stroke:#010101;stroke-opacity:1;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

@ -1,4 +1,12 @@
body {
background-repeat: no-repeat;
background-position: center center;
background-size: cover;
background-attachment: fixed;
background-image: url("./default-background.jpg");
background-color: #222;
min-height: 100vh;
min-width: 100vw;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',

@ -1,8 +1,10 @@
import "./FixedInterpolation";
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import 'bootstrap/dist/css/bootstrap.css';
import './index.css';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement

@ -3,10 +3,10 @@ import {
evaluateSpringyValueInterpolator, interpolateSpringyValueInterpolatables,
SpringyValueInterpolatables,
SpringyValueInterpolate,
SpringyValueInterpolateds,
SpringyValues
} from "./SpringyValueHook";
import {isDefined} from "./type_check";
import {AnimatedProps} from "@react-spring/web";
export interface ResourceBarColors {
filledColor: CSS.Property.Color
@ -64,7 +64,7 @@ export const DEFAULT_BAR_COLORS: ResourceBarColors = {
export const DEFAULT_BAR_DIRECTION: string = "to right"
export function evaluateResourceBarStyles(barStyle: SpringyValueInterpolatables<ResourceBarStyles>, interpolate: SpringyValueInterpolate) {
export function evaluateResourceBarStyles(barStyle: SpringyValueInterpolatables<ResourceBarStyles>, interpolate: SpringyValueInterpolate): AnimatedProps<CSS.Properties> {
const intermediate: SpringyValueInterpolatables<ResourceBarStyles> = Object.assign({}, barStyle)
delete intermediate.background
delete intermediate.foreground
@ -77,5 +77,5 @@ export function evaluateResourceBarStyles(barStyle: SpringyValueInterpolatables<
evaluateSpringyValueInterpolator(barStyle["barDirection"], v) ?? DEFAULT_BAR_DIRECTION),
evaluateSpringyValueInterpolator(barStyle["background"], v) as string | undefined
].filter((s) => !!s).join(', ')
return interpolateSpringyValueInterpolatables(intermediate as SpringyValueInterpolateds<CSS.Properties>, interpolate)
return interpolateSpringyValueInterpolatables(intermediate as SpringyValueInterpolatables<CSS.Properties>, interpolate)
}
Loading…
Cancel
Save