feat(midsummer): rain of flowers/frogs/Swedish flags, dots become frogs, drop jingle
Per request: remove the WebAudio jingle (+ its 🔊 toggle and sound state); replace the one-shot confetti with a continuous rain of 🌼🌸🐸🇸🇪🌿 over the screen (MidsummerRain, gated by the theme, reduced-motion aware, leak-free); and replace player-dot markers with frogs themselves (override the inline dot color/border) instead of a flower-crown on top. Still toggled by the 🐸 Midsommar switch. Includes rebuilt static bundle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
7141a38c5c
commit
d86bc48862
24 changed files with 129 additions and 202 deletions
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useLiveData } from '../hooks/useLiveData';
|
import { useLiveData } from '../hooks/useLiveData';
|
||||||
import { useMidsummerSound } from '../hooks/useMidsummerSound';
|
|
||||||
import { PlayerDashboardContent } from './windows/PlayerDashboardWindow';
|
import { PlayerDashboardContent } from './windows/PlayerDashboardWindow';
|
||||||
import { MidsummerBanner } from './midsummer/MidsummerBanner';
|
import { MidsummerBanner } from './midsummer/MidsummerBanner';
|
||||||
|
import { MidsummerRain } from './midsummer/MidsummerRain';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fullscreen "Player Dashboard" page — rendered when the React app loads
|
* Fullscreen "Player Dashboard" page — rendered when the React app loads
|
||||||
|
|
@ -16,7 +16,6 @@ import { MidsummerBanner } from './midsummer/MidsummerBanner';
|
||||||
*/
|
*/
|
||||||
export const PlayerDashboardFullPage: React.FC = () => {
|
export const PlayerDashboardFullPage: React.FC = () => {
|
||||||
const data = useLiveData();
|
const data = useLiveData();
|
||||||
useMidsummerSound();
|
|
||||||
const [version, setVersion] = useState('');
|
const [version, setVersion] = useState('');
|
||||||
|
|
||||||
// Set tab title.
|
// Set tab title.
|
||||||
|
|
@ -41,6 +40,7 @@ export const PlayerDashboardFullPage: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="ml-dashboard-page">
|
<div className="ml-dashboard-page">
|
||||||
<MidsummerBanner />
|
<MidsummerBanner />
|
||||||
|
<MidsummerRain />
|
||||||
<header className="ml-dashboard-header">
|
<header className="ml-dashboard-header">
|
||||||
<span className="ml-dashboard-title">👥 Player Dashboard</span>
|
<span className="ml-dashboard-title">👥 Player Dashboard</span>
|
||||||
<span className="ml-dashboard-count">{count} online</span>
|
<span className="ml-dashboard-count">{count} online</span>
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import { WindowRenderer } from '../windows/WindowRenderer';
|
||||||
import { RareNotification } from '../effects/RareNotification';
|
import { RareNotification } from '../effects/RareNotification';
|
||||||
import { DeathNotification } from '../effects/DeathNotification';
|
import { DeathNotification } from '../effects/DeathNotification';
|
||||||
import { usePlayerColors } from '../../hooks/usePlayerColors';
|
import { usePlayerColors } from '../../hooks/usePlayerColors';
|
||||||
import { useMidsummerSound } from '../../hooks/useMidsummerSound';
|
|
||||||
import { MidsummerBanner } from '../midsummer/MidsummerBanner';
|
import { MidsummerBanner } from '../midsummer/MidsummerBanner';
|
||||||
|
import { MidsummerRain } from '../midsummer/MidsummerRain';
|
||||||
import type { DashboardState } from '../../hooks/useLiveData';
|
import type { DashboardState } from '../../hooks/useLiveData';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -17,7 +17,6 @@ interface Props {
|
||||||
|
|
||||||
export const MapLayout: React.FC<Props> = ({ data }) => {
|
export const MapLayout: React.FC<Props> = ({ data }) => {
|
||||||
const getColor = usePlayerColors();
|
const getColor = usePlayerColors();
|
||||||
useMidsummerSound();
|
|
||||||
const [showHeatmap, setShowHeatmap] = useState(false);
|
const [showHeatmap, setShowHeatmap] = useState(false);
|
||||||
const [showPortals, setShowPortals] = useState(false);
|
const [showPortals, setShowPortals] = useState(false);
|
||||||
const [selectedPlayer, setSelectedPlayer] = useState<string | null>(null);
|
const [selectedPlayer, setSelectedPlayer] = useState<string | null>(null);
|
||||||
|
|
@ -45,6 +44,7 @@ export const MapLayout: React.FC<Props> = ({ data }) => {
|
||||||
<WindowManagerProvider>
|
<WindowManagerProvider>
|
||||||
<div className="ml-layout">
|
<div className="ml-layout">
|
||||||
<MidsummerBanner />
|
<MidsummerBanner />
|
||||||
|
<MidsummerRain />
|
||||||
<Sidebar
|
<Sidebar
|
||||||
players={players}
|
players={players}
|
||||||
vitals={vitalsMap}
|
vitals={vitalsMap}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useMidsummer } from '../../hooks/useMidsummer';
|
import { useMidsummer } from '../../hooks/useMidsummer';
|
||||||
import { playSmaGrodorna } from '../../hooks/useMidsummerSound';
|
|
||||||
|
|
||||||
/** 🐸 theme toggle + 🔊 jingle toggle, rendered among the sidebar tool links. */
|
/** 🐸 midsummer theme toggle, rendered among the sidebar tool links. */
|
||||||
export const FrogToggle: React.FC = () => {
|
export const FrogToggle: React.FC = () => {
|
||||||
const { enabled, toggle, soundOn, toggleSound } = useMidsummer();
|
const { enabled, toggle } = useMidsummer();
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<span
|
<span
|
||||||
className="ml-tool-link"
|
className="ml-tool-link"
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
|
|
@ -15,20 +13,5 @@ export const FrogToggle: React.FC = () => {
|
||||||
>
|
>
|
||||||
🐸 Midsommar {enabled ? 'on' : 'off'}
|
🐸 Midsommar {enabled ? 'on' : 'off'}
|
||||||
</span>
|
</span>
|
||||||
{enabled && (
|
|
||||||
<span
|
|
||||||
className="ml-tool-link"
|
|
||||||
style={{ cursor: 'pointer' }}
|
|
||||||
title={soundOn ? 'Mute the Små grodorna jingle' : 'Unmute the Små grodorna jingle'}
|
|
||||||
onClick={() => {
|
|
||||||
const turningOn = !soundOn;
|
|
||||||
toggleSound();
|
|
||||||
if (turningOn) playSmaGrodorna(); // this click is a user gesture
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{soundOn ? '🔊' : '🔇'} Jingle
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,9 @@
|
||||||
import React, { useEffect } from 'react';
|
import React from 'react';
|
||||||
import { useMidsummer } from '../../hooks/useMidsummer';
|
import { useMidsummer } from '../../hooks/useMidsummer';
|
||||||
import { burstConfetti } from './confetti';
|
|
||||||
|
|
||||||
const CONFETTI_FLAG = 'mo-midsummer-confetti';
|
/** Festive top strip shown while the theme is on. */
|
||||||
|
|
||||||
/** Festive top strip + a one-shot confetti burst on the first load of a
|
|
||||||
* session while the theme is on. */
|
|
||||||
export const MidsummerBanner: React.FC = () => {
|
export const MidsummerBanner: React.FC = () => {
|
||||||
const { enabled } = useMidsummer();
|
const { enabled } = useMidsummer();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!enabled) return;
|
|
||||||
if (sessionStorage.getItem(CONFETTI_FLAG)) return;
|
|
||||||
sessionStorage.setItem(CONFETTI_FLAG, '1');
|
|
||||||
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
|
||||||
burstConfetti();
|
|
||||||
}
|
|
||||||
}, [enabled]);
|
|
||||||
|
|
||||||
if (!enabled) return null;
|
if (!enabled) return null;
|
||||||
return (
|
return (
|
||||||
<div className="ms-banner" role="status">
|
<div className="ms-banner" role="status">
|
||||||
|
|
|
||||||
44
frontend/src/components/midsummer/MidsummerRain.tsx
Normal file
44
frontend/src/components/midsummer/MidsummerRain.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useMidsummer } from '../../hooks/useMidsummer';
|
||||||
|
|
||||||
|
// Flowers, frogs and Swedish flags drifting down over the screen.
|
||||||
|
const PIECES = ['🐸', '🌼', '🌸', '🇸🇪', '🌿'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continuous gentle rain of flowers, frogs and Swedish flags while the theme
|
||||||
|
* is on. Pure DOM + CSS (no React re-renders) so it doesn't fight the map's
|
||||||
|
* high-frequency telemetry updates. Cleans up its layer, interval and any
|
||||||
|
* in-flight pieces when the theme is toggled off or the component unmounts.
|
||||||
|
* Skipped entirely under prefers-reduced-motion.
|
||||||
|
*/
|
||||||
|
export const MidsummerRain: React.FC = () => {
|
||||||
|
const { enabled } = useMidsummer();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!enabled) return;
|
||||||
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||||
|
|
||||||
|
const layer = document.createElement('div');
|
||||||
|
layer.className = 'ms-rain';
|
||||||
|
document.body.appendChild(layer);
|
||||||
|
|
||||||
|
const spawn = () => {
|
||||||
|
const piece = document.createElement('span');
|
||||||
|
piece.textContent = PIECES[Math.floor(Math.random() * PIECES.length)];
|
||||||
|
piece.style.left = Math.floor(Math.random() * 100) + 'vw';
|
||||||
|
const dur = 6 + Math.random() * 5; // 6–11s to fall
|
||||||
|
piece.style.animationDuration = dur.toFixed(2) + 's';
|
||||||
|
piece.style.fontSize = 14 + Math.floor(Math.random() * 16) + 'px';
|
||||||
|
layer.appendChild(piece);
|
||||||
|
window.setTimeout(() => piece.remove(), dur * 1000 + 250);
|
||||||
|
};
|
||||||
|
|
||||||
|
const id = window.setInterval(spawn, 450);
|
||||||
|
return () => {
|
||||||
|
window.clearInterval(id);
|
||||||
|
layer.remove();
|
||||||
|
};
|
||||||
|
}, [enabled]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
const EMOJIS = ['🐸', '🌼', '🌸', '🥂', '🌿'];
|
|
||||||
|
|
||||||
/** One-shot falling-emoji burst. Self-removes after the animation. */
|
|
||||||
export function burstConfetti(count = 28): void {
|
|
||||||
const layer = document.createElement('div');
|
|
||||||
layer.className = 'ms-confetti';
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
const p = document.createElement('span');
|
|
||||||
p.textContent = EMOJIS[i % EMOJIS.length];
|
|
||||||
p.style.left = Math.floor(Math.random() * 100) + 'vw';
|
|
||||||
p.style.animationDelay = (Math.random() * 0.6).toFixed(2) + 's';
|
|
||||||
p.style.fontSize = 12 + Math.floor(Math.random() * 14) + 'px';
|
|
||||||
layer.appendChild(p);
|
|
||||||
}
|
|
||||||
document.body.appendChild(layer);
|
|
||||||
window.setTimeout(() => layer.remove(), 4200);
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react';
|
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react';
|
||||||
|
|
||||||
const KEY = 'mo-midsummer';
|
const KEY = 'mo-midsummer';
|
||||||
const SOUND_KEY = 'mo-midsummer-sound';
|
|
||||||
|
|
||||||
interface MidsummerCtx {
|
interface MidsummerCtx {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
toggle: () => void;
|
toggle: () => void;
|
||||||
soundOn: boolean;
|
|
||||||
toggleSound: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Ctx = createContext<MidsummerCtx | null>(null);
|
const Ctx = createContext<MidsummerCtx | null>(null);
|
||||||
|
|
@ -15,7 +12,6 @@ const Ctx = createContext<MidsummerCtx | null>(null);
|
||||||
export const MidsummerProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
export const MidsummerProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
// Default ON: only the literal "off" disables it.
|
// Default ON: only the literal "off" disables it.
|
||||||
const [enabled, setEnabled] = useState<boolean>(() => localStorage.getItem(KEY) !== 'off');
|
const [enabled, setEnabled] = useState<boolean>(() => localStorage.getItem(KEY) !== 'off');
|
||||||
const [soundOn, setSoundOn] = useState<boolean>(() => localStorage.getItem(SOUND_KEY) !== 'off');
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = document.documentElement;
|
const el = document.documentElement;
|
||||||
|
|
@ -24,15 +20,10 @@ export const MidsummerProvider: React.FC<{ children: React.ReactNode }> = ({ chi
|
||||||
localStorage.setItem(KEY, enabled ? 'on' : 'off');
|
localStorage.setItem(KEY, enabled ? 'on' : 'off');
|
||||||
}, [enabled]);
|
}, [enabled]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
localStorage.setItem(SOUND_KEY, soundOn ? 'on' : 'off');
|
|
||||||
}, [soundOn]);
|
|
||||||
|
|
||||||
const toggle = useCallback(() => setEnabled(e => !e), []);
|
const toggle = useCallback(() => setEnabled(e => !e), []);
|
||||||
const toggleSound = useCallback(() => setSoundOn(s => !s), []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Ctx.Provider value={{ enabled, toggle, soundOn, toggleSound }}>
|
<Ctx.Provider value={{ enabled, toggle }}>
|
||||||
{children}
|
{children}
|
||||||
</Ctx.Provider>
|
</Ctx.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useMidsummer } from './useMidsummer';
|
|
||||||
|
|
||||||
const JINGLE_FLAG = 'mo-midsummer-jingle';
|
|
||||||
|
|
||||||
let ctx: AudioContext | null = null;
|
|
||||||
|
|
||||||
// Public-domain "Små grodorna" opening phrase (approximation), as
|
|
||||||
// [frequencyHz, durationSeconds]. Cheerful major-key triangle tones.
|
|
||||||
const MELODY: [number, number][] = [
|
|
||||||
[392, 0.22], [392, 0.22], [392, 0.22], [440, 0.22], [494, 0.42],
|
|
||||||
[494, 0.22], [440, 0.22], [494, 0.22], [523, 0.22], [587, 0.5],
|
|
||||||
];
|
|
||||||
|
|
||||||
/** Play the jingle once. Safe to call from any user gesture. No-op if
|
|
||||||
* WebAudio is unavailable. Reuses a single AudioContext (no leak). */
|
|
||||||
export function playSmaGrodorna(): void {
|
|
||||||
try {
|
|
||||||
const AC = window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext;
|
|
||||||
if (!AC) return;
|
|
||||||
if (!ctx) ctx = new AC();
|
|
||||||
if (ctx.state === 'suspended') void ctx.resume();
|
|
||||||
let t = ctx.currentTime + 0.05;
|
|
||||||
for (const [freq, dur] of MELODY) {
|
|
||||||
const osc = ctx.createOscillator();
|
|
||||||
const gain = ctx.createGain();
|
|
||||||
osc.type = 'triangle';
|
|
||||||
osc.frequency.value = freq;
|
|
||||||
gain.gain.setValueAtTime(0.0001, t);
|
|
||||||
gain.gain.exponentialRampToValueAtTime(0.18, t + 0.02);
|
|
||||||
gain.gain.exponentialRampToValueAtTime(0.0001, t + dur);
|
|
||||||
osc.connect(gain).connect(ctx.destination);
|
|
||||||
osc.start(t);
|
|
||||||
osc.stop(t + dur);
|
|
||||||
t += dur;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
/* audio not available — ignore */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Plays the jingle once per session, on the first user gesture, when the
|
|
||||||
* theme and sound are both on. Browsers block audio before a gesture, so
|
|
||||||
* we wait for the first pointerdown/keydown. */
|
|
||||||
export function useMidsummerSound(): void {
|
|
||||||
const { enabled, soundOn } = useMidsummer();
|
|
||||||
useEffect(() => {
|
|
||||||
if (!enabled || !soundOn) return;
|
|
||||||
if (sessionStorage.getItem(JINGLE_FLAG)) return;
|
|
||||||
|
|
||||||
const fire = () => {
|
|
||||||
sessionStorage.setItem(JINGLE_FLAG, '1');
|
|
||||||
playSmaGrodorna();
|
|
||||||
cleanup();
|
|
||||||
};
|
|
||||||
const cleanup = () => {
|
|
||||||
window.removeEventListener('pointerdown', fire);
|
|
||||||
window.removeEventListener('keydown', fire);
|
|
||||||
};
|
|
||||||
window.addEventListener('pointerdown', fire);
|
|
||||||
window.addEventListener('keydown', fire);
|
|
||||||
return cleanup;
|
|
||||||
}, [enabled, soundOn]);
|
|
||||||
}
|
|
||||||
|
|
@ -83,20 +83,15 @@
|
||||||
.ms-maypole-ring { animation: none; }
|
.ms-maypole-ring { animation: none; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Replace each player marker with a frog (instead of the coloured dot). The
|
||||||
|
inline backgroundColor + base border on .ml-dot are overridden so only the
|
||||||
|
frog shows; the base blink animation on the selected dot still applies. */
|
||||||
:root[data-midsummer] .ml-dot {
|
:root[data-midsummer] .ml-dot {
|
||||||
|
background: transparent !important;
|
||||||
|
border: none !important;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
:root[data-midsummer] .ml-dot::before {
|
:root[data-midsummer] .ml-dot::before {
|
||||||
content: '🌸';
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: -9px;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
font-size: 9px;
|
|
||||||
line-height: 1;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
:root[data-midsummer] .ml-dot.ml-dot-selected::after {
|
|
||||||
content: '🐸';
|
content: '🐸';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
|
|
@ -106,6 +101,10 @@
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
:root[data-midsummer] .ml-dot.ml-dot-selected::before {
|
||||||
|
font-size: 21px;
|
||||||
|
filter: drop-shadow(0 0 3px #7ed957);
|
||||||
|
}
|
||||||
|
|
||||||
.ms-banner {
|
.ms-banner {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -123,21 +122,26 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.ms-confetti {
|
/* Continuous rain of flowers / frogs / Swedish flags (MidsummerRain). Each
|
||||||
|
piece sets its own animation-duration inline; pointer-events:none so it
|
||||||
|
never blocks the UI underneath. */
|
||||||
|
.ms-rain {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: 999998;
|
z-index: 9000;
|
||||||
}
|
}
|
||||||
.ms-confetti span {
|
.ms-rain span {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -32px;
|
top: -40px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
animation: ms-fall 3.6s linear forwards;
|
animation-name: ms-fall;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
}
|
}
|
||||||
@keyframes ms-fall {
|
@keyframes ms-fall {
|
||||||
to { transform: translateY(112vh) rotate(360deg); opacity: 0.25; }
|
to { transform: translateY(112vh) rotate(360deg); opacity: 0.3; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.ml-layout.ms-hop {
|
.ml-layout.ms-hop {
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
import{e as I,r as t,l as _,f as L,g as N,h as W,j as e,D as F}from"./index-BnDWPOwL.js";import"./react-yfL0ty4i.js";function R(c){try{return new Date(c).toISOString().slice(0,10)}catch{return c}}const q=({id:c,zIndex:x})=>{const{user:j}=I(),[g,k]=t.useState([]),[w,p]=t.useState(!0),[b,n]=t.useState(null),[l,C]=t.useState(""),[i,f]=t.useState(""),[h,y]=t.useState(!1),[d,S]=t.useState(!1),[A,u]=t.useState(null),[o,m]=t.useState(""),r=t.useCallback(async()=>{p(!0),n(null);try{const s=await _();k(s.users??[])}catch(s){n(String(s))}finally{p(!1)}},[]);t.useEffect(()=>{r()},[r]);const U=t.useCallback(async s=>{if(s.preventDefault(),!l.trim()||i.length<4){n("Username required and password must be at least 4 chars");return}S(!0),n(null);try{await L(l.trim(),i,h),C(""),f(""),y(!1),await r()}catch(a){n(String(a))}finally{S(!1)}},[l,i,h,r]),v=t.useCallback(async s=>{n(null);try{await N(s.id,{is_admin:!s.is_admin}),await r()}catch(a){n(String(a))}},[r]),D=t.useCallback(async s=>{if(o.length<4){n("Password must be at least 4 characters");return}n(null);try{await N(s,{password:o}),u(null),m("")}catch(a){n(String(a))}},[o]),P=t.useCallback(async s=>{if(confirm(`Delete user "${s.username}"? This cannot be undone.`)){n(null);try{await W(s.id),await r()}catch(a){n(String(a))}}},[r]);return e.jsx(F,{id:c,title:"🛡️ Admin · Users",zIndex:x,width:620,height:540,children:e.jsxs("div",{className:"ml-admin",children:[b&&e.jsx("div",{className:"ml-admin-error",children:b}),e.jsxs("section",{className:"ml-admin-section",children:[e.jsx("h3",{children:"Add user"}),e.jsxs("form",{onSubmit:U,className:"ml-admin-create",children:[e.jsx("input",{type:"text",placeholder:"Username",value:l,onChange:s=>C(s.target.value),disabled:d,autoComplete:"off"}),e.jsx("input",{type:"password",placeholder:"Password (min 4)",value:i,onChange:s=>f(s.target.value),disabled:d,autoComplete:"new-password"}),e.jsxs("label",{children:[e.jsx("input",{type:"checkbox",checked:h,onChange:s=>y(s.target.checked),disabled:d}),"admin"]}),e.jsx("button",{type:"submit",disabled:d||!l.trim()||i.length<4,children:d?"Adding…":"Add"})]})]}),e.jsxs("section",{className:"ml-admin-section",children:[e.jsxs("h3",{children:["Users ",w&&e.jsx("span",{className:"ml-admin-muted",children:"(loading…)"})]}),e.jsxs("table",{className:"ml-admin-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"ID"}),e.jsx("th",{children:"Username"}),e.jsx("th",{children:"Admin"}),e.jsx("th",{children:"Created"}),e.jsx("th",{children:"Actions"})]})}),e.jsxs("tbody",{children:[g.map(s=>{const a=j!=null&&j.username.toLowerCase()===s.username.toLowerCase();return e.jsxs("tr",{children:[e.jsx("td",{children:s.id}),e.jsxs("td",{children:[s.username,a&&e.jsx("span",{className:"ml-admin-muted",children:" (you)"})]}),e.jsx("td",{children:e.jsx("button",{className:"ml-admin-toggle",onClick:()=>v(s),title:"Click to toggle admin",children:s.is_admin?"✓":"–"})}),e.jsx("td",{children:R(s.created_at)}),e.jsx("td",{children:A===s.id?e.jsxs("span",{className:"ml-admin-pw-edit",children:[e.jsx("input",{type:"text",placeholder:"New password",value:o,onChange:E=>m(E.target.value),autoFocus:!0}),e.jsx("button",{onClick:()=>D(s.id),children:"Save"}),e.jsx("button",{onClick:()=>{u(null),m("")},children:"Cancel"})]}):e.jsxs(e.Fragment,{children:[e.jsx("button",{onClick:()=>{u(s.id),m("")},children:"Reset PW"}),!a&&e.jsx("button",{className:"ml-admin-danger",onClick:()=>P(s),children:"Delete"})]})})]},s.id)}),g.length===0&&!w&&e.jsx("tr",{children:e.jsx("td",{colSpan:5,className:"ml-admin-muted",children:"No users."})})]})]})]})]})})};export{q as AdminUsersWindow};
|
import{e as I,r as t,l as _,f as L,g as N,h as W,j as e,D as F}from"./index-BKI81v-M.js";import"./react-yfL0ty4i.js";function R(c){try{return new Date(c).toISOString().slice(0,10)}catch{return c}}const q=({id:c,zIndex:x})=>{const{user:j}=I(),[g,k]=t.useState([]),[w,p]=t.useState(!0),[b,n]=t.useState(null),[l,C]=t.useState(""),[i,f]=t.useState(""),[h,y]=t.useState(!1),[d,S]=t.useState(!1),[A,u]=t.useState(null),[o,m]=t.useState(""),r=t.useCallback(async()=>{p(!0),n(null);try{const s=await _();k(s.users??[])}catch(s){n(String(s))}finally{p(!1)}},[]);t.useEffect(()=>{r()},[r]);const U=t.useCallback(async s=>{if(s.preventDefault(),!l.trim()||i.length<4){n("Username required and password must be at least 4 chars");return}S(!0),n(null);try{await L(l.trim(),i,h),C(""),f(""),y(!1),await r()}catch(a){n(String(a))}finally{S(!1)}},[l,i,h,r]),v=t.useCallback(async s=>{n(null);try{await N(s.id,{is_admin:!s.is_admin}),await r()}catch(a){n(String(a))}},[r]),D=t.useCallback(async s=>{if(o.length<4){n("Password must be at least 4 characters");return}n(null);try{await N(s,{password:o}),u(null),m("")}catch(a){n(String(a))}},[o]),P=t.useCallback(async s=>{if(confirm(`Delete user "${s.username}"? This cannot be undone.`)){n(null);try{await W(s.id),await r()}catch(a){n(String(a))}}},[r]);return e.jsx(F,{id:c,title:"🛡️ Admin · Users",zIndex:x,width:620,height:540,children:e.jsxs("div",{className:"ml-admin",children:[b&&e.jsx("div",{className:"ml-admin-error",children:b}),e.jsxs("section",{className:"ml-admin-section",children:[e.jsx("h3",{children:"Add user"}),e.jsxs("form",{onSubmit:U,className:"ml-admin-create",children:[e.jsx("input",{type:"text",placeholder:"Username",value:l,onChange:s=>C(s.target.value),disabled:d,autoComplete:"off"}),e.jsx("input",{type:"password",placeholder:"Password (min 4)",value:i,onChange:s=>f(s.target.value),disabled:d,autoComplete:"new-password"}),e.jsxs("label",{children:[e.jsx("input",{type:"checkbox",checked:h,onChange:s=>y(s.target.checked),disabled:d}),"admin"]}),e.jsx("button",{type:"submit",disabled:d||!l.trim()||i.length<4,children:d?"Adding…":"Add"})]})]}),e.jsxs("section",{className:"ml-admin-section",children:[e.jsxs("h3",{children:["Users ",w&&e.jsx("span",{className:"ml-admin-muted",children:"(loading…)"})]}),e.jsxs("table",{className:"ml-admin-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"ID"}),e.jsx("th",{children:"Username"}),e.jsx("th",{children:"Admin"}),e.jsx("th",{children:"Created"}),e.jsx("th",{children:"Actions"})]})}),e.jsxs("tbody",{children:[g.map(s=>{const a=j!=null&&j.username.toLowerCase()===s.username.toLowerCase();return e.jsxs("tr",{children:[e.jsx("td",{children:s.id}),e.jsxs("td",{children:[s.username,a&&e.jsx("span",{className:"ml-admin-muted",children:" (you)"})]}),e.jsx("td",{children:e.jsx("button",{className:"ml-admin-toggle",onClick:()=>v(s),title:"Click to toggle admin",children:s.is_admin?"✓":"–"})}),e.jsx("td",{children:R(s.created_at)}),e.jsx("td",{children:A===s.id?e.jsxs("span",{className:"ml-admin-pw-edit",children:[e.jsx("input",{type:"text",placeholder:"New password",value:o,onChange:E=>m(E.target.value),autoFocus:!0}),e.jsx("button",{onClick:()=>D(s.id),children:"Save"}),e.jsx("button",{onClick:()=>{u(null),m("")},children:"Cancel"})]}):e.jsxs(e.Fragment,{children:[e.jsx("button",{onClick:()=>{u(s.id),m("")},children:"Reset PW"}),!a&&e.jsx("button",{className:"ml-admin-danger",onClick:()=>P(s),children:"Delete"})]})})]},s.id)}),g.length===0&&!w&&e.jsx("tr",{children:e.jsx("td",{colSpan:5,className:"ml-admin-muted",children:"No users."})})]})]})]})]})})};export{q as AdminUsersWindow};
|
||||||
|
|
@ -1 +1 @@
|
||||||
import{r as n,b as w,c as k,d as E,j as t,D as I}from"./index-BnDWPOwL.js";import"./react-yfL0ty4i.js";const h="overlord_agent_session_id";function v(){if(typeof crypto<"u"&&typeof crypto.randomUUID=="function")return crypto.randomUUID();const s=l=>Math.floor(Math.random()*l);return`${s(4294967296).toString(16).padStart(8,"0")}-${s(65536).toString(16).padStart(4,"0")}-4${s(4096).toString(16).padStart(3,"0")}-${(8+s(4)).toString(16)}${s(4096).toString(16).padStart(3,"0")}-${s(281474976710656).toString(16).padStart(12,"0")}`}function $(){try{const l=localStorage.getItem(h);if(l)return l}catch{}const s=v();try{localStorage.setItem(h,s)}catch{}return s}const _=({id:s,zIndex:l})=>{const[o,j]=n.useState(()=>$()),[d,i]=n.useState([]),[g,m]=n.useState(""),[r,f]=n.useState(!1),[p,x]=n.useState(!0),S=n.useRef(null);n.useEffect(()=>{let e=!1;return x(!0),w(o).then(a=>{if(e)return;const c=(a.messages??[]).map(y=>({role:y.role,text:y.text}));i(c)}).catch(()=>{e||i([])}).finally(()=>{e||x(!1)}),()=>{e=!0}},[o]),n.useEffect(()=>{const e=S.current;e&&(e.scrollTop=e.scrollHeight)},[d.length,r]);const u=n.useCallback(async()=>{const e=g.trim();if(!(!e||r)){m(""),i(a=>[...a,{role:"user",text:e}]),f(!0);try{const a=await k(e,o);i(c=>[...c,{role:a.is_error?"error":"assistant",text:a.result||"(no response)"}])}catch(a){i(c=>[...c,{role:"error",text:`Request failed: ${String(a)}`}])}finally{f(!1)}}},[g,r,o]),N=n.useCallback(async()=>{if(r)return;let e="";try{e=(await E()).session_id}catch{e=v()}try{localStorage.setItem(h,e)}catch{}j(e),i([]),m("")},[r]),b=n.useCallback(e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),u())},[u]);return t.jsx(I,{id:s,title:"🤖 Overlord Assistant",zIndex:l,width:520,height:620,children:t.jsxs("div",{className:"ml-agent",children:[t.jsxs("div",{className:"ml-agent-toolbar",children:[t.jsx("button",{className:"ml-agent-btn",onClick:N,disabled:r,children:"+ New Chat"}),t.jsxs("span",{className:"ml-agent-session",title:o,children:[o.slice(0,8),"…"]})]}),t.jsxs("div",{className:"ml-agent-messages",ref:S,children:[p&&d.length===0&&t.jsx("div",{className:"ml-agent-empty",children:"Loading conversation…"}),!p&&d.length===0&&t.jsx("div",{className:"ml-agent-empty",children:"Ask anything about the live game state — players, kills, inventory, suitbuilder, recent rares, etc."}),d.map((e,a)=>t.jsxs("div",{className:`ml-agent-msg ml-agent-${e.role}`,children:[t.jsx("div",{className:"ml-agent-role",children:e.role==="user"?"You":e.role==="assistant"?"Overlord":"Error"}),t.jsx("div",{className:"ml-agent-text",children:e.text})]},a)),r&&t.jsxs("div",{className:"ml-agent-msg ml-agent-assistant",children:[t.jsx("div",{className:"ml-agent-role",children:"Overlord"}),t.jsx("div",{className:"ml-agent-text ml-agent-thinking",children:"Thinking…"})]})]}),t.jsxs("form",{className:"ml-agent-form",onSubmit:e=>{e.preventDefault(),u()},children:[t.jsx("textarea",{className:"ml-agent-input",value:g,onChange:e=>m(e.target.value),onKeyDown:b,placeholder:r?"Waiting for response…":"Type a message — Enter to send, Shift+Enter for newline",disabled:r,rows:2}),t.jsx("button",{type:"submit",className:"ml-agent-send",disabled:r||!g.trim(),children:"Send"})]})]})})};export{_ as AgentWindow};
|
import{r as n,b as w,c as k,d as E,j as t,D as I}from"./index-BKI81v-M.js";import"./react-yfL0ty4i.js";const h="overlord_agent_session_id";function v(){if(typeof crypto<"u"&&typeof crypto.randomUUID=="function")return crypto.randomUUID();const s=l=>Math.floor(Math.random()*l);return`${s(4294967296).toString(16).padStart(8,"0")}-${s(65536).toString(16).padStart(4,"0")}-4${s(4096).toString(16).padStart(3,"0")}-${(8+s(4)).toString(16)}${s(4096).toString(16).padStart(3,"0")}-${s(281474976710656).toString(16).padStart(12,"0")}`}function $(){try{const l=localStorage.getItem(h);if(l)return l}catch{}const s=v();try{localStorage.setItem(h,s)}catch{}return s}const _=({id:s,zIndex:l})=>{const[o,j]=n.useState(()=>$()),[d,i]=n.useState([]),[g,m]=n.useState(""),[r,f]=n.useState(!1),[p,x]=n.useState(!0),S=n.useRef(null);n.useEffect(()=>{let e=!1;return x(!0),w(o).then(a=>{if(e)return;const c=(a.messages??[]).map(y=>({role:y.role,text:y.text}));i(c)}).catch(()=>{e||i([])}).finally(()=>{e||x(!1)}),()=>{e=!0}},[o]),n.useEffect(()=>{const e=S.current;e&&(e.scrollTop=e.scrollHeight)},[d.length,r]);const u=n.useCallback(async()=>{const e=g.trim();if(!(!e||r)){m(""),i(a=>[...a,{role:"user",text:e}]),f(!0);try{const a=await k(e,o);i(c=>[...c,{role:a.is_error?"error":"assistant",text:a.result||"(no response)"}])}catch(a){i(c=>[...c,{role:"error",text:`Request failed: ${String(a)}`}])}finally{f(!1)}}},[g,r,o]),N=n.useCallback(async()=>{if(r)return;let e="";try{e=(await E()).session_id}catch{e=v()}try{localStorage.setItem(h,e)}catch{}j(e),i([]),m("")},[r]),b=n.useCallback(e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),u())},[u]);return t.jsx(I,{id:s,title:"🤖 Overlord Assistant",zIndex:l,width:520,height:620,children:t.jsxs("div",{className:"ml-agent",children:[t.jsxs("div",{className:"ml-agent-toolbar",children:[t.jsx("button",{className:"ml-agent-btn",onClick:N,disabled:r,children:"+ New Chat"}),t.jsxs("span",{className:"ml-agent-session",title:o,children:[o.slice(0,8),"…"]})]}),t.jsxs("div",{className:"ml-agent-messages",ref:S,children:[p&&d.length===0&&t.jsx("div",{className:"ml-agent-empty",children:"Loading conversation…"}),!p&&d.length===0&&t.jsx("div",{className:"ml-agent-empty",children:"Ask anything about the live game state — players, kills, inventory, suitbuilder, recent rares, etc."}),d.map((e,a)=>t.jsxs("div",{className:`ml-agent-msg ml-agent-${e.role}`,children:[t.jsx("div",{className:"ml-agent-role",children:e.role==="user"?"You":e.role==="assistant"?"Overlord":"Error"}),t.jsx("div",{className:"ml-agent-text",children:e.text})]},a)),r&&t.jsxs("div",{className:"ml-agent-msg ml-agent-assistant",children:[t.jsx("div",{className:"ml-agent-role",children:"Overlord"}),t.jsx("div",{className:"ml-agent-text ml-agent-thinking",children:"Thinking…"})]})]}),t.jsxs("form",{className:"ml-agent-form",onSubmit:e=>{e.preventDefault(),u()},children:[t.jsx("textarea",{className:"ml-agent-input",value:g,onChange:e=>m(e.target.value),onKeyDown:b,placeholder:r?"Waiting for response…":"Type a message — Enter to send, Shift+Enter for newline",disabled:r,rows:2}),t.jsx("button",{type:"submit",className:"ml-agent-send",disabled:r||!g.trim(),children:"Send"})]})]})})};export{_ as AgentWindow};
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
||||||
import{u as c,j as r,D as d}from"./index-BnDWPOwL.js";import"./react-yfL0ty4i.js";const p=({id:n,zIndex:i,characters:a})=>{const{openWindow:s}=c(),e=Array.from(a.keys()).sort();return r.jsx(d,{id:n,title:"Combat Stats — Select Character",zIndex:i,width:300,height:400,children:r.jsx("div",{style:{flex:1,overflowY:"auto",padding:6},children:e.length===0?r.jsx("div",{style:{padding:12,color:"#666",textAlign:"center",fontSize:"0.8rem"},children:"No characters online"}):e.map(o=>r.jsx("div",{style:{padding:"5px 8px",cursor:"pointer",borderBottom:"1px solid #222",color:"#ccc",fontSize:"0.82rem"},onMouseEnter:t=>t.currentTarget.style.background="#2a2a2a",onMouseLeave:t=>t.currentTarget.style.background="",onClick:()=>s(`combat-${o}`,`Combat: ${o}`,o),children:o},o))})})};export{p as CombatPickerWindow};
|
import{u as c,j as r,D as d}from"./index-BKI81v-M.js";import"./react-yfL0ty4i.js";const p=({id:n,zIndex:i,characters:a})=>{const{openWindow:s}=c(),e=Array.from(a.keys()).sort();return r.jsx(d,{id:n,title:"Combat Stats — Select Character",zIndex:i,width:300,height:400,children:r.jsx("div",{style:{flex:1,overflowY:"auto",padding:6},children:e.length===0?r.jsx("div",{style:{padding:12,color:"#666",textAlign:"center",fontSize:"0.8rem"},children:"No characters online"}):e.map(o=>r.jsx("div",{style:{padding:"5px 8px",cursor:"pointer",borderBottom:"1px solid #222",color:"#ccc",fontSize:"0.82rem"},onMouseEnter:t=>t.currentTarget.style.background="#2a2a2a",onMouseLeave:t=>t.currentTarget.style.background="",onClick:()=>s(`combat-${o}`,`Combat: ${o}`,o),children:o},o))})})};export{p as CombatPickerWindow};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
||||||
import{r as c,j as t,D as u,a as f}from"./index-BnDWPOwL.js";import"./react-yfL0ty4i.js";const j=({id:p,zIndex:x})=>{const[s,h]=c.useState(null);c.useEffect(()=>{const e=async()=>{try{h(await f("/quest-status"))}catch{}};e();const o=setInterval(e,3e4);return()=>clearInterval(o)},[]);const i=s?Object.keys(s.quest_data).sort():[],l=new Set;if(s)for(const e of Object.values(s.quest_data))for(const o of Object.keys(e))l.add(o);const n=Array.from(l).sort();return t.jsx(u,{id:p,title:"Quest Status",zIndex:x,width:780,height:500,children:t.jsx("div",{style:{flex:1,overflow:"auto",fontSize:"0.72rem"},children:s?i.length===0?t.jsx("div",{style:{padding:20,color:"#666",textAlign:"center"},children:"No quest data available"}):t.jsxs("table",{style:{width:"100%",borderCollapse:"collapse"},children:[t.jsx("thead",{children:t.jsxs("tr",{style:{position:"sticky",top:0,background:"#1a1a1a",zIndex:1},children:[t.jsx("th",{style:{textAlign:"left",padding:"4px 8px",borderBottom:"1px solid #444",color:"#888",fontSize:"0.65rem",fontWeight:600,minWidth:140},children:"Character"}),n.map(e=>t.jsx("th",{style:{textAlign:"center",padding:"4px 6px",borderBottom:"1px solid #444",color:"#888",fontSize:"0.6rem",fontWeight:600,maxWidth:120,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},title:e,children:e.replace(" Timer","").replace(" Pickup","")},e))]})}),t.jsx("tbody",{children:i.map(e=>{const o=s.quest_data[e]||{};return t.jsxs("tr",{style:{borderBottom:"1px solid #222"},children:[t.jsx("td",{style:{padding:"3px 8px",color:"#ccc",fontWeight:500,whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis",maxWidth:160},children:e}),n.map(d=>{const r=o[d],a=r==="READY";return t.jsx("td",{style:{textAlign:"center",padding:"3px 6px",color:a?"#4c4":r?"#ca0":"#333",fontWeight:a?600:400,fontSize:a?"0.7rem":"0.68rem"},children:r||"—"},d)})]},e)})})]}):t.jsx("div",{style:{padding:20,color:"#666",textAlign:"center"},children:"Loading quest data..."})})})};export{j as QuestStatusWindow};
|
import{r as c,j as t,D as u,a as f}from"./index-BKI81v-M.js";import"./react-yfL0ty4i.js";const j=({id:p,zIndex:x})=>{const[s,h]=c.useState(null);c.useEffect(()=>{const e=async()=>{try{h(await f("/quest-status"))}catch{}};e();const o=setInterval(e,3e4);return()=>clearInterval(o)},[]);const i=s?Object.keys(s.quest_data).sort():[],l=new Set;if(s)for(const e of Object.values(s.quest_data))for(const o of Object.keys(e))l.add(o);const n=Array.from(l).sort();return t.jsx(u,{id:p,title:"Quest Status",zIndex:x,width:780,height:500,children:t.jsx("div",{style:{flex:1,overflow:"auto",fontSize:"0.72rem"},children:s?i.length===0?t.jsx("div",{style:{padding:20,color:"#666",textAlign:"center"},children:"No quest data available"}):t.jsxs("table",{style:{width:"100%",borderCollapse:"collapse"},children:[t.jsx("thead",{children:t.jsxs("tr",{style:{position:"sticky",top:0,background:"#1a1a1a",zIndex:1},children:[t.jsx("th",{style:{textAlign:"left",padding:"4px 8px",borderBottom:"1px solid #444",color:"#888",fontSize:"0.65rem",fontWeight:600,minWidth:140},children:"Character"}),n.map(e=>t.jsx("th",{style:{textAlign:"center",padding:"4px 6px",borderBottom:"1px solid #444",color:"#888",fontSize:"0.6rem",fontWeight:600,maxWidth:120,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},title:e,children:e.replace(" Timer","").replace(" Pickup","")},e))]})}),t.jsx("tbody",{children:i.map(e=>{const o=s.quest_data[e]||{};return t.jsxs("tr",{style:{borderBottom:"1px solid #222"},children:[t.jsx("td",{style:{padding:"3px 8px",color:"#ccc",fontWeight:500,whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis",maxWidth:160},children:e}),n.map(d=>{const r=o[d],a=r==="READY";return t.jsx("td",{style:{textAlign:"center",padding:"3px 6px",color:a?"#4c4":r?"#ca0":"#333",fontWeight:a?600:400,fontSize:a?"0.7rem":"0.68rem"},children:r||"—"},d)})]},e)})})]}):t.jsx("div",{style:{padding:20,color:"#666",textAlign:"center"},children:"Loading quest data..."})})})};export{j as QuestStatusWindow};
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
||||||
import{r as o,j as t,D as d}from"./index-BnDWPOwL.js";import"./react-yfL0ty4i.js";const c=[{title:"Kills per Hour",id:1},{title:"Memory (MB)",id:2},{title:"CPU (%)",id:3},{title:"Mem Handles",id:4}],m=[{label:"1H",value:"now-1h"},{label:"6H",value:"now-6h"},{label:"24H",value:"now-24h"},{label:"7D",value:"now-7d"}],v=({id:s,charName:a,zIndex:i})=>{const[l,r]=o.useState("now-24h"),n=e=>`/grafana/d-solo/dereth-tracker/dereth-tracker-dashboard?panelId=${e}&var-character=${encodeURIComponent(a)}&from=${l}&to=now&theme=light`;return t.jsxs(d,{id:s,title:`Stats: ${a}`,zIndex:i,width:750,height:480,children:[t.jsx("div",{className:"ml-stats-controls",children:m.map(e=>t.jsx("button",{className:`ml-stats-range-btn ${l===e.value?"active":""}`,onClick:()=>r(e.value),children:e.label},e.value))}),t.jsx("div",{className:"ml-stats-grid",children:c.map(e=>t.jsx("div",{className:"ml-stats-panel",children:t.jsx("iframe",{src:n(e.id),width:"100%",height:"100%",frameBorder:"0",title:e.title})},e.id))})]})};export{v as StatsWindow};
|
import{r as o,j as t,D as d}from"./index-BKI81v-M.js";import"./react-yfL0ty4i.js";const c=[{title:"Kills per Hour",id:1},{title:"Memory (MB)",id:2},{title:"CPU (%)",id:3},{title:"Mem Handles",id:4}],m=[{label:"1H",value:"now-1h"},{label:"6H",value:"now-6h"},{label:"24H",value:"now-24h"},{label:"7D",value:"now-7d"}],v=({id:s,charName:a,zIndex:i})=>{const[l,r]=o.useState("now-24h"),n=e=>`/grafana/d-solo/dereth-tracker/dereth-tracker-dashboard?panelId=${e}&var-character=${encodeURIComponent(a)}&from=${l}&to=now&theme=light`;return t.jsxs(d,{id:s,title:`Stats: ${a}`,zIndex:i,width:750,height:480,children:[t.jsx("div",{className:"ml-stats-controls",children:m.map(e=>t.jsx("button",{className:`ml-stats-range-btn ${l===e.value?"active":""}`,onClick:()=>r(e.value),children:e.label},e.value))}),t.jsx("div",{className:"ml-stats-grid",children:c.map(e=>t.jsx("div",{className:"ml-stats-panel",children:t.jsx("iframe",{src:n(e.id),width:"100%",height:"100%",frameBorder:"0",title:e.title})},e.id))})]})};export{v as StatsWindow};
|
||||||
|
|
@ -1 +1 @@
|
||||||
import{r as n,j as t,D as x,a as m}from"./index-BnDWPOwL.js";import"./react-yfL0ty4i.js";const v=({id:o,zIndex:c})=>{const[a,d]=n.useState([]);n.useEffect(()=>{const e=async()=>{try{const s=await m("/vital-sharing/peers");d(s.peers??[])}catch{}};e();const l=setInterval(e,5e3);return()=>clearInterval(l)},[]);const h=(e,l)=>l>0?Math.min(100,e/l*100):0;return t.jsx(x,{id:o,title:"Vital Sharing Network",zIndex:c,width:520,height:450,children:t.jsx("div",{style:{flex:1,overflowY:"auto",padding:6,fontSize:"0.75rem"},children:a.length===0?t.jsx("div",{style:{padding:16,color:"#666",textAlign:"center"},children:"No vital-sharing peers connected"}):a.map(e=>{var l,s,r;return t.jsxs("div",{style:{padding:"6px 8px",marginBottom:4,background:"#1f1f1f",borderRadius:3,border:"1px solid #333"},children:[t.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginBottom:3},children:[t.jsx("span",{style:{color:e.plugin_connected?"#4c4":"#a33",fontSize:"0.8rem"},children:"●"}),t.jsx("strong",{style:{flex:1},children:e.character_name}),e.subscribed&&t.jsx("span",{style:{color:"#6bf",fontSize:"0.65rem"},children:"[subscribed]"})]}),t.jsxs("div",{style:{color:"#666",fontSize:"0.68rem",marginBottom:3},children:["tags: ",((l=e.tags)==null?void 0:l.join(", "))||"none"]}),e.vitals&&e.vitals.max_health>0&&t.jsx("div",{style:{display:"flex",flexDirection:"column",gap:2},children:[{label:"HP",cur:e.vitals.current_health,max:e.vitals.max_health,bg:"#330000",fill:"#c44"},{label:"STA",cur:e.vitals.current_stamina,max:e.vitals.max_stamina,bg:"#331a00",fill:"#ca0"},{label:"MANA",cur:e.vitals.current_mana,max:e.vitals.max_mana,bg:"#001433",fill:"#48f"}].map(i=>t.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[t.jsx("span",{style:{width:32,color:"#888",fontSize:"0.65rem"},children:i.label}),t.jsx("div",{style:{flex:1,height:6,background:i.bg,borderRadius:3,overflow:"hidden"},children:t.jsx("div",{style:{width:`${h(i.cur,i.max)}%`,height:"100%",background:i.fill,borderRadius:3}})}),t.jsxs("span",{style:{width:60,textAlign:"right",fontSize:"0.65rem",color:"#888"},children:[i.cur,"/",i.max]})]},i.label))}),e.position&&t.jsxs("div",{style:{color:"#555",fontSize:"0.65rem",marginTop:2},children:[(s=e.position.ns)==null?void 0:s.toFixed(1),"N, ",(r=e.position.ew)==null?void 0:r.toFixed(1),"E"]})]},e.character_name)})})})};export{v as VitalSharingWindow};
|
import{r as n,j as t,D as x,a as m}from"./index-BKI81v-M.js";import"./react-yfL0ty4i.js";const v=({id:o,zIndex:c})=>{const[a,d]=n.useState([]);n.useEffect(()=>{const e=async()=>{try{const s=await m("/vital-sharing/peers");d(s.peers??[])}catch{}};e();const l=setInterval(e,5e3);return()=>clearInterval(l)},[]);const h=(e,l)=>l>0?Math.min(100,e/l*100):0;return t.jsx(x,{id:o,title:"Vital Sharing Network",zIndex:c,width:520,height:450,children:t.jsx("div",{style:{flex:1,overflowY:"auto",padding:6,fontSize:"0.75rem"},children:a.length===0?t.jsx("div",{style:{padding:16,color:"#666",textAlign:"center"},children:"No vital-sharing peers connected"}):a.map(e=>{var l,s,r;return t.jsxs("div",{style:{padding:"6px 8px",marginBottom:4,background:"#1f1f1f",borderRadius:3,border:"1px solid #333"},children:[t.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginBottom:3},children:[t.jsx("span",{style:{color:e.plugin_connected?"#4c4":"#a33",fontSize:"0.8rem"},children:"●"}),t.jsx("strong",{style:{flex:1},children:e.character_name}),e.subscribed&&t.jsx("span",{style:{color:"#6bf",fontSize:"0.65rem"},children:"[subscribed]"})]}),t.jsxs("div",{style:{color:"#666",fontSize:"0.68rem",marginBottom:3},children:["tags: ",((l=e.tags)==null?void 0:l.join(", "))||"none"]}),e.vitals&&e.vitals.max_health>0&&t.jsx("div",{style:{display:"flex",flexDirection:"column",gap:2},children:[{label:"HP",cur:e.vitals.current_health,max:e.vitals.max_health,bg:"#330000",fill:"#c44"},{label:"STA",cur:e.vitals.current_stamina,max:e.vitals.max_stamina,bg:"#331a00",fill:"#ca0"},{label:"MANA",cur:e.vitals.current_mana,max:e.vitals.max_mana,bg:"#001433",fill:"#48f"}].map(i=>t.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[t.jsx("span",{style:{width:32,color:"#888",fontSize:"0.65rem"},children:i.label}),t.jsx("div",{style:{flex:1,height:6,background:i.bg,borderRadius:3,overflow:"hidden"},children:t.jsx("div",{style:{width:`${h(i.cur,i.max)}%`,height:"100%",background:i.fill,borderRadius:3}})}),t.jsxs("span",{style:{width:60,textAlign:"right",fontSize:"0.65rem",color:"#888"},children:[i.cur,"/",i.max]})]},i.label))}),e.position&&t.jsxs("div",{style:{color:"#555",fontSize:"0.65rem",marginTop:2},children:[(s=e.position.ns)==null?void 0:s.toFixed(1),"N, ",(r=e.position.ew)==null?void 0:r.toFixed(1),"E"]})]},e.character_name)})})})};export{v as VitalSharingWindow};
|
||||||
File diff suppressed because one or more lines are too long
34
static/assets/index-BKI81v-M.js
Normal file
34
static/assets/index-BKI81v-M.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -8,9 +8,9 @@
|
||||||
<link rel="preload" as="image" href="/dereth.png" />
|
<link rel="preload" as="image" href="/dereth.png" />
|
||||||
<link rel="preload" as="image" href="/icons/0600127E.png" />
|
<link rel="preload" as="image" href="/icons/0600127E.png" />
|
||||||
<link rel="preload" as="fetch" href="/dungeon_tiles.json" crossorigin="anonymous" />
|
<link rel="preload" as="fetch" href="/dungeon_tiles.json" crossorigin="anonymous" />
|
||||||
<script type="module" crossorigin src="/assets/index-BnDWPOwL.js"></script>
|
<script type="module" crossorigin src="/assets/index-BKI81v-M.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/assets/react-yfL0ty4i.js">
|
<link rel="modulepreload" crossorigin href="/assets/react-yfL0ty4i.js">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-et4BWs63.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-1RR6uoxX.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue