Android build v1.1.12
This commit is contained in:
+123
-1
@@ -97,14 +97,34 @@ export type DualScreenWorkshopState = {
|
||||
}>
|
||||
}
|
||||
|
||||
export type DualScreenSetupState = {
|
||||
contentType: 'dungeon' | 'raid'
|
||||
title: string
|
||||
subtitle: string
|
||||
description: string
|
||||
initials: string
|
||||
difficultyName: string
|
||||
itemLevel: number
|
||||
experience: number
|
||||
lockedReason?: string
|
||||
stats: {
|
||||
health: string
|
||||
damage: string
|
||||
xp: string
|
||||
loot: string
|
||||
}
|
||||
}
|
||||
|
||||
type DualScreenMessage =
|
||||
| { type: 'combat-state'; state: DualScreenCombatState }
|
||||
| { type: 'workshop-state'; state: DualScreenWorkshopState }
|
||||
| { type: 'setup-state'; state: DualScreenSetupState }
|
||||
| { type: 'companion-ready' }
|
||||
| { type: 'companion-heartbeat' }
|
||||
| { type: 'control-action'; action: InputAction }
|
||||
| { type: 'combat-ended' }
|
||||
| { type: 'workshop-ended' }
|
||||
| { type: 'setup-ended' }
|
||||
|
||||
type DualScreenContextValue = {
|
||||
enabled: boolean
|
||||
@@ -160,6 +180,14 @@ function shouldRelayBottomAction(
|
||||
return action === 'pause' || action === 'back'
|
||||
}
|
||||
|
||||
function shouldRelaySetupAction(
|
||||
action: InputAction,
|
||||
state: DualScreenSetupState | null,
|
||||
) {
|
||||
if (!state) return false
|
||||
return action.startsWith('navigate') || action === 'confirm' || action === 'back'
|
||||
}
|
||||
|
||||
export function DualScreenProvider({ children }: { children: ReactNode }) {
|
||||
const [enabled, setEnabledState] = useState(
|
||||
() => localStorage.getItem(STORAGE_KEY) === 'true',
|
||||
@@ -361,6 +389,9 @@ export function useDualScreenWorkshopPublisher(
|
||||
}
|
||||
channel.onmessage = (event: MessageEvent<DualScreenMessage>) => {
|
||||
if (event.data.type === 'companion-ready') publish()
|
||||
if (event.data.type === 'control-action') {
|
||||
dispatchExternalGameAction(event.data.action, 'controller')
|
||||
}
|
||||
}
|
||||
publish()
|
||||
return () => {
|
||||
@@ -377,9 +408,53 @@ export function useDualScreenWorkshopPublisher(
|
||||
}, [enabled, state])
|
||||
}
|
||||
|
||||
export function useDualScreenSetupPublisher(
|
||||
state: DualScreenSetupState | null,
|
||||
enabled: boolean,
|
||||
) {
|
||||
const stateRef = useRef(state)
|
||||
useEffect(() => {
|
||||
stateRef.current = state
|
||||
}, [state])
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || !state) return
|
||||
const channel = createChannel()
|
||||
if (!channel) return
|
||||
const publish = () => {
|
||||
if (stateRef.current) {
|
||||
channel.postMessage({
|
||||
type: 'setup-state',
|
||||
state: stateRef.current,
|
||||
} satisfies DualScreenMessage)
|
||||
}
|
||||
}
|
||||
channel.onmessage = (event: MessageEvent<DualScreenMessage>) => {
|
||||
if (event.data.type === 'companion-ready') publish()
|
||||
if (event.data.type === 'control-action') {
|
||||
dispatchExternalGameAction(event.data.action, 'controller')
|
||||
}
|
||||
}
|
||||
publish()
|
||||
return () => {
|
||||
channel.postMessage({ type: 'setup-ended' } satisfies DualScreenMessage)
|
||||
channel.close()
|
||||
}
|
||||
}, [enabled, state])
|
||||
|
||||
useEffect(() => {
|
||||
const channel = createChannel()
|
||||
if (!enabled || !channel) return
|
||||
if (state) channel.postMessage({ type: 'setup-state', state } satisfies DualScreenMessage)
|
||||
else channel.postMessage({ type: 'setup-ended' } satisfies DualScreenMessage)
|
||||
channel.close()
|
||||
}, [enabled, state])
|
||||
}
|
||||
|
||||
export function DualScreenBottomDisplay() {
|
||||
const [state, setState] = useState<DualScreenCombatState | null>(loadRecentSnapshot)
|
||||
const [workshopState, setWorkshopState] = useState<DualScreenWorkshopState | null>(null)
|
||||
const [setupState, setSetupState] = useState<DualScreenSetupState | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const channel = createChannel()
|
||||
@@ -389,13 +464,21 @@ export function DualScreenBottomDisplay() {
|
||||
if (event.data.type === 'combat-state') {
|
||||
setState(event.data.state)
|
||||
setWorkshopState(null)
|
||||
setSetupState(null)
|
||||
}
|
||||
if (event.data.type === 'workshop-state') {
|
||||
setWorkshopState(event.data.state)
|
||||
setState(null)
|
||||
setSetupState(null)
|
||||
}
|
||||
if (event.data.type === 'setup-state') {
|
||||
setSetupState(event.data.state)
|
||||
setState(null)
|
||||
setWorkshopState(null)
|
||||
}
|
||||
if (event.data.type === 'combat-ended') setState(null)
|
||||
if (event.data.type === 'workshop-ended') setWorkshopState(null)
|
||||
if (event.data.type === 'setup-ended') setSetupState(null)
|
||||
}
|
||||
announce()
|
||||
const timer = window.setInterval(() => {
|
||||
@@ -414,7 +497,10 @@ export function DualScreenBottomDisplay() {
|
||||
}
|
||||
|
||||
useGameAction((action) => {
|
||||
if (!shouldRelayBottomAction(action, state)) return
|
||||
if (
|
||||
!shouldRelayBottomAction(action, state)
|
||||
&& !shouldRelaySetupAction(action, setupState)
|
||||
) return
|
||||
sendAction(action)
|
||||
})
|
||||
|
||||
@@ -452,6 +538,42 @@ export function DualScreenBottomDisplay() {
|
||||
)
|
||||
}
|
||||
|
||||
if (!state && setupState) {
|
||||
return (
|
||||
<main className="dual-bottom-display setup-bottom-display">
|
||||
<header className="dual-controls-header">
|
||||
<div>
|
||||
<p className="eyebrow">{setupState.contentType}</p>
|
||||
<h1>{setupState.title}</h1>
|
||||
<small>{setupState.subtitle}</small>
|
||||
</div>
|
||||
<div className="dual-controls-progress">
|
||||
<span>{setupState.difficultyName}</span>
|
||||
<span>iLvl {setupState.itemLevel}</span>
|
||||
<span>{setupState.experience} XP</span>
|
||||
</div>
|
||||
</header>
|
||||
<section className="setup-bottom-summary">
|
||||
<span className={`dungeon-art ${setupState.contentType === 'raid' ? 'raid-art' : ''}`}>
|
||||
{setupState.initials}
|
||||
</span>
|
||||
<div>
|
||||
<p className="eyebrow">Selected Run</p>
|
||||
<h2>{setupState.title}</h2>
|
||||
<p>{setupState.description}</p>
|
||||
{setupState.lockedReason && <small>{setupState.lockedReason}</small>}
|
||||
</div>
|
||||
</section>
|
||||
<dl className="setup-bottom-stats">
|
||||
<div><dt>Health</dt><dd>{setupState.stats.health}</dd></div>
|
||||
<div><dt>Damage</dt><dd>{setupState.stats.damage}</dd></div>
|
||||
<div><dt>XP</dt><dd>{setupState.stats.xp}</dd></div>
|
||||
<div><dt>Loot</dt><dd>{setupState.stats.loot}</dd></div>
|
||||
</dl>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
if (!state) {
|
||||
return (
|
||||
<main className="dual-bottom-display dual-bottom-waiting">
|
||||
|
||||
Reference in New Issue
Block a user