@@ -1,4 +1,4 @@
import { useEffect , useMemo , useState , type CSSProperties } from "react" ;
import { useEffect , useMemo , useRef , useState , type CSSProperties } from "react" ;
import { classById } from "../app/characterCatalog" ;
import {
ACTION_CONTROLS ,
@@ -10,7 +10,10 @@ import {
type ActionBindingLayer ,
} from "../game/actionBindings" ;
import { useCombatStore } from "../game/combatStore" ;
import { defaultActionBarForCharacter } from "../game/abilityCatalog" ;
import { useGameStore } from "../game/store" ;
import { actionKeyLabel , assignDesktopAbility , resetDesktopAbilities , type UiLayout } from "../game/uiLayout" ;
import { currentUiDevice , useUiLayoutStore } from "../game/uiLayoutStore" ;
import { getControllerHeldTokens , subscribeControllerToken } from "../input/controller" ;
import { INPUT_ACTIONS , controllerBindingMatches , inputKeyForEvent } from "../input/keybinds" ;
import { getInputPreferences , isCapturingInput } from "../input/keybindRuntime" ;
@@ -29,27 +32,38 @@ function AbilityBindingIcon({ icon, sigil, color }: { icon?: string; sigil: stri
}
type RestoreScope = "layer" | "all" ;
type BindingLayout = "controller" | "wow" ;
type BindingControl = ActionBindingControl | number ;
interface RestoreRequest {
scope : RestoreScope ;
layout : BindingLayout ;
layer : ActionBindingLayer ;
bar : number ;
cancelFocusId : string ;
completeFocusId : string ;
}
const layerControlId = ( layer : ActionBindingLayer ) = > ` binding-layer- ${ layer } ` ;
const slotControlId = ( control : ActionBindingControl ) = > ` binding-slot- ${ control } ` ;
const barControlId = ( bar : number ) = > ` binding-bar- ${ bar } ` ;
const slotControlId = ( control : BindingControl ) = > typeof control === "number" ? ` binding-wow-slot- ${ control } ` : ` binding-slot- ${ control } ` ;
const choiceControlId = ( abilityId : string ) = > ` binding-choice- ${ abilityId } ` ;
const MAIN_ACTION_NEIGHBORS : Readonly < Record < string , NonNullable < MenuAction [ "neighbors" ] > > > = {
"binding-layout-controller" : {
up : "binding-restore-layer" , down : "binding-layer-primary" , left : "binding-layout-wow" , right : "binding-layout-wow" ,
} ,
"binding-layout-wow" : {
up : "binding-restore-all" , down : "binding-layer-tertiary" , left : "binding-layout-controller" , right : "binding-layout-controller" ,
} ,
"binding-layer-primary" : {
up : "binding-restore-lay er" , down : "binding-slot-dpad-left" , left : "binding-layer-tertiary" , right : "binding-layer-secondary" ,
up : "binding-layout-controll er" , down : "binding-slot-dpad-left" , left : "binding-layer-tertiary" , right : "binding-layer-secondary" ,
} ,
"binding-layer-secondary" : {
up : "binding-restore-lay er" , down : "binding-slot-r1" , left : "binding-layer-primary" , right : "binding-layer-tertiary" ,
up : "binding-layout-controll er" , down : "binding-slot-r1" , left : "binding-layer-primary" , right : "binding-layer-tertiary" ,
} ,
"binding-layer-tertiary" : {
up : "binding-restore-all " , down : "binding-slot-r2" , left : "binding-layer-secondary" , right : "binding-layer-primary" ,
up : "binding-layout-wow " , down : "binding-slot-r2" , left : "binding-layer-secondary" , right : "binding-layer-primary" ,
} ,
"binding-slot-r1" : {
up : "binding-layer-secondary" , down : "binding-slot-face-top" , left : "binding-slot-dpad-right" , right : "binding-slot-r2" ,
@@ -84,19 +98,30 @@ const MAIN_ACTION_NEIGHBORS: Readonly<Record<string, NonNullable<MenuAction["nei
} ;
function AbilityBindingsDialog() {
const panel = useRef < HTMLElement > ( null ) ;
const keyLabel = useInputBindingLabels ( ) ;
const bindingChord = ( layer : ActionBindingLayer , control : ActionBindingControl ) = > ` ${ keyLabel ( "controller" , ` ability: ${ layer } : ${ control } ` ) } / ${ keyLabel ( "pc" , ` ability: ${ layer } : ${ control } ` ) } ` ;
const openOverlay = useGameStore ( ( state ) = > state . openOverlay ) ;
const classId = useCombatStore ( ( state ) = > state . classId ) ;
const secondaryClassId = useCombatStore ( ( state ) = > state . secondaryClassId ) ;
const level = useCombatStore ( ( state ) = > state . level ) ;
const abilities = useCombatStore ( ( state ) = > state . abilities ) ;
const bindings = useCombatStore ( ( state ) = > state . actionBindings ) ;
const pcLayout = useUiLayoutStore ( ( state ) = > state . preferences . current . pc ) ;
const [ layout , setLayout ] = useState < BindingLayout > ( ( ) = > currentUiDevice ( ) === "pc" && pcLayout . wowBars ? "wow" : "controller" ) ;
const [ bar , setBar ] = useState ( 0 ) ;
const [ layer , setLayer ] = useState < ActionBindingLayer > ( "primary" ) ;
const [ editingControl , setEditingControl ] = useState < ActionBindingControl | null > ( null ) ;
const [ editingControl , setEditingControl ] = useState < BindingControl | null > ( null ) ;
const [ restoreRequest , setRestoreRequest ] = useState < RestoreRequest | null > ( null ) ;
const [ notice , setNotice ] = useState ( "Choose a button, then choose the ability it should use." ) ;
const classColor = classById ( classId ) . color ;
const editingAbilityId = editingControl ? bindings [ actionBindingId ( layer , editingControl ) ] : null ;
const isWow = layout === "wow" ;
const assignment = ( control : BindingControl ) = > typeof control === "number"
? pcLayout . bars [ bar ] . slots [ control ] . abilityId : bindings [ actionBindingId ( layer , control ) ] ;
const controlLabel = ( control : BindingControl ) = > typeof control === "number"
? ` Bar ${ bar + 1 } · Button ${ control + 1 } ${ pcLayout . bars [ bar ] . slots [ control ] . key ? ` · ${ actionKeyLabel ( pcLayout . bars [ bar ] . slots [ control ] . key ) } ` : "" } `
: bindingChord ( layer , control ) ;
const editingAbilityId = editingControl !== null ? assignment ( editingControl ) : null ;
const pickerAbilities = useMemo ( ( ) = > {
const ordered = abilities
. filter ( ( ability ) = > ! ability . passive )
@@ -109,6 +134,31 @@ function AbilityBindingsDialog() {
: ordered ;
} , [ abilities , editingAbilityId ] ) ;
const close = ( ) = > openOverlay ( "pause" ) ;
const saveWowLayout = ( update : ( current : UiLayout ) = > UiLayout ) = > {
try {
const ui = useUiLayoutStore . getState ( ) ;
ui . persist ( { . . . ui . preferences , current : { . . . ui . preferences . current , pc : update ( ui . preferences . current . pc ) } } ) ;
return true ;
} catch ( error ) {
setNotice ( error instanceof Error ? error . message : "Could not save WoW ability bindings." ) ;
return false ;
}
} ;
const setBinding = ( control : BindingControl , abilityId : string | null ) = > {
if ( typeof control === "number" ) return saveWowLayout ( ( current ) = > assignDesktopAbility ( current , bar , control , abilityId ) ) ;
useCombatStore . getState ( ) . setActionBinding ( layer , control , abilityId ) ;
return true ;
} ;
const chooseLayout = ( next : BindingLayout ) = > {
setLayout ( next ) ; setEditingControl ( null ) ; setRestoreRequest ( null ) ;
setNotice ( ` Choose a ${ next === "wow" ? "WoW bar" : "controller" } button, then choose its ability. ` ) ;
controller . select ( ` binding-layout- ${ next } ` ) ;
} ;
const chooseBar = ( next : number ) = > {
setBar ( next ) ; setEditingControl ( null ) ; setRestoreRequest ( null ) ;
setNotice ( ` Bar ${ next + 1 } selected. ` ) ;
controller . select ( barControlId ( next ) ) ;
} ;
const chooseLayer = ( next : ActionBindingLayer ) = > {
setLayer ( next ) ;
@@ -117,40 +167,42 @@ function AbilityBindingsDialog() {
setNotice ( ` ${ ACTION_LAYER_LABELS [ next ] } layer selected. ` ) ;
controller . select ( layerControlId ( next ) ) ;
} ;
const beginEditing = ( control : ActionBindingControl ) = > {
const abilityId = bindings [ actionBindingId ( layer , control ) ] ;
const beginEditing = ( control : BindingControl ) = > {
const abilityId = assignment ( control ) ;
setEditingControl ( control ) ;
setRestoreRequest ( null ) ;
controller . select ( abilityId ? choiceControlId ( abilityId ) : "binding-choice-empty" ) ;
} ;
const cancelEditing = ( ) = > {
if ( ! editingControl ) return ;
if ( editingControl === null ) return ;
const control = editingControl ;
setEditingControl ( null ) ;
controller . select ( slotControlId ( control ) ) ;
} ;
const assign = ( abilityId : string | null ) = > {
if ( ! editingControl ) return ;
if ( editingControl === null ) return ;
const control = editingControl ;
const ability = abilityId ? abilities . find ( ( candidate ) = > candidate . id === abilityId ) ? ? null : null ;
useCombatStore . getState ( ) . setActionBinding ( layer , control , abilityId ) ;
setNotice ( ` ${ bindingChord ( layer , control ) } : ${ ability ? . name ? ? "Empty" } . ` ) ;
if ( ! setBinding ( control , abilityId ) ) return ;
setNotice ( ` ${ controlLabel ( control ) } : ${ ability ? . name ? ? "Empty" } . Saved. ` ) ;
setEditingControl ( null ) ;
controller . select ( slotControlId ( control ) ) ;
} ;
const clearSelected = ( control : ActionBindingControl | null = editingControl ) = > {
if ( ! control ) return ;
useCombatStore . getState ( ) . setActionBinding ( layer , control , null ) ;
setNotice ( ` ${ bindingChord ( layer , control ) } cleared. ` ) ;
const clearSelected = ( control : BindingControl | null = editingControl ) = > {
if ( control === null ) return ;
if ( ! setBinding ( control , null ) ) return ;
setNotice ( ` ${ controlLabel ( control ) } cleared. Saved. ` ) ;
setEditingControl ( null ) ;
controller . select ( slotControlId ( control ) ) ;
} ;
const requestRestore = ( scope : RestoreScope , focusId : string ) = > {
setRestoreRequest ( {
scope ,
layout ,
layer ,
bar ,
cancelFocusId : focusId ,
completeFocusId : editingControl ? slotControlId ( editingControl ) : focusId ,
completeFocusId : editingControl !== null ? slotControlId ( editingControl ) : focusId ,
} ) ;
controller . select ( "binding-confirm-cancel" ) ;
} ;
@@ -163,7 +215,11 @@ function AbilityBindingsDialog() {
const confirmRestore = ( ) = > {
if ( ! restoreRequest ) return ;
const request = restoreRequest ;
if ( request . scope === "layer " ) {
if ( request . layout === "wow " ) {
const defaults = defaultActionBarForCharacter ( classId , secondaryClassId , level ) ;
if ( ! saveWowLayout ( ( current ) = > resetDesktopAbilities ( current , defaults , request . scope === "layer" ? request.bar : undefined ) ) ) return ;
setNotice ( request . scope === "layer" ? ` Bar ${ request . bar + 1 } abilities restored. Saved. ` : "All WoW bar abilities restored. Saved." ) ;
} else if ( request . scope === "layer" ) {
useCombatStore . getState ( ) . resetActionBindingLayer ( request . layer ) ;
setNotice ( ` ${ ACTION_LAYER_LABELS [ request . layer ] } layer restored. ` ) ;
} else {
@@ -190,53 +246,72 @@ function AbilityBindingsDialog() {
} ,
] ;
}
if ( editingControl ) {
if ( editingControl !== null ) {
return [
. . . pickerAbilities . map ( ( ability ) = > ( { id : choiceControlId ( ability . id ) , run : ( ) = > assign ( ability . id ) } ) ) ,
{ id : "binding-choice-empty" , run : ( ) = > assign ( null ) } ,
] ;
}
return [
. . . ACTION_LAYERS . map ( ( nextLayer ) = > {
const id = layerControlId ( nextLayer ) ;
return { id , run : ( ) = > chooseLayer ( nextLayer ) , neighbors : MAIN_ACTION_NEIGHBORS [ id ] } ;
} ) ,
. . . ACTION_CONTROLS . map ( ( control ) = > {
const id = slotControlId ( control ) ;
return { id , run : ( ) = > beginEditing ( control ) , neighbors : MAIN_ACTION_NEIGHBORS [ id ] } ;
} ) ,
. . . ( [ "controller" , "wow" ] as const ) . map ( ( next ) = > ( {
id : ` binding-layout- ${ next } ` , run : ( ) = > chooseLayout ( next ) ,
neighbors : isWow ? { up : "binding-restore-all" , down : barControlId ( bar ) , left : ` binding-layout- ${ next === "wow" ? "controller" : "wow" } ` , right : ` binding-layout- ${ next === "wow" ? "controller" : "wow" } ` } : MAIN_ACTION_NEIGHBORS [ ` binding-layout- ${ next } ` ] ,
} ) ) ,
. . . ( isWow ? [
. . . pcLayout . bars . map ( ( _ , index ) = > ( { id : barControlId ( index ) , run : ( ) = > chooseBar ( index ) , neighbors : {
up : "binding-layout-wow" , down : slotControlId ( index ) , left : barControlId ( ( index + 4 ) % 5 ) , right : barControlId ( ( index + 1 ) % 5 ) ,
} } ) ) ,
. . . pcLayout . bars [ bar ] . slots . map ( ( _ , slot ) = > ( { id : slotControlId ( slot ) , run : ( ) = > beginEditing ( slot ) , neighbors : {
up : slot < 4 ? barControlId ( bar ) : slotControlId ( slot - 4 ) ,
down : slot >= 8 ? "binding-restore-layer" : slotControlId ( slot + 4 ) ,
left : slotControlId ( ( slot + 11 ) % 12 ) , right : slotControlId ( ( slot + 1 ) % 12 ) ,
} } ) ) ,
] : [
. . . ACTION_LAYERS . map ( ( nextLayer ) = > {
const id = layerControlId ( nextLayer ) ;
return { id , run : ( ) = > chooseLayer ( nextLayer ) , neighbors : MAIN_ACTION_NEIGHBORS [ id ] } ;
} ) ,
. . . ACTION_CONTROLS . map ( ( control ) = > {
const id = slotControlId ( control ) ;
return { id , run : ( ) = > beginEditing ( control ) , neighbors : MAIN_ACTION_NEIGHBORS [ id ] } ;
} ) ,
] ) ,
{
id : "binding-restore-layer" ,
run : ( ) = > requestRestore ( "layer" , "binding-restore-layer" ) ,
neighbors : MAIN_ACTION_NEIGHBORS [ "binding-restore-layer" ] ,
neighbors : isWow ? { up : slotControlId ( 8 ) , down : "binding-layout-controller" , left : "binding-restore-all" , right : "binding-restore-all" } : MAIN_ACTION_NEIGHBORS [ "binding-restore-layer" ] ,
} ,
{
id : "binding-restore-all" ,
run : ( ) = > requestRestore ( "all" , "binding-restore-all" ) ,
neighbors : MAIN_ACTION_NEIGHBORS [ "binding-restore-all" ] ,
neighbors : isWow ? { up : slotControlId ( 11 ) , down : "binding-layout-wow" , left : "binding-restore-layer" , right : "binding-restore-layer" } : MAIN_ACTION_NEIGHBORS [ "binding-restore-all" ] ,
} ,
] ;
} , [ abilities , bindings , editingControl , layer , pickerAbilities , restoreRequest ] ) ;
} , [ abilities , bindings , classId , secondaryClassId , level , editingControl , layer , layout , bar , pcLayout , pickerAbilities , restoreRequest ] ) ;
const controller = useMenuController ( actions , {
columns : restoreRequest ? 2 : editingControl ? 1 : 3 ,
columns : restoreRequest ? 2 : editingControl !== null ? 1 : isWow ? 4 : 3 ,
initialId : restoreRequest
? "binding-confirm-cancel"
: editingControl
: editingControl !== null
? editingAbilityId ? choiceControlId ( editingAbilityId ) : "binding-choice-empty"
: layerControlId ( layer ) ,
onBack : restoreRequest ? cancelRestore : editingControl ? cancelEditing : close ,
: isWow ? barControlId ( bar ) : layerControlId ( layer ) ,
onBack : restoreRequest ? cancelRestore : editingControl !== null ? cancelEditing : close ,
} ) ;
useEffect ( ( ) = > {
panel . current ? . querySelector ( ".is-controller-selected" ) ? . scrollIntoView ( { block : "nearest" } ) ;
} , [ controller . selectedId ] ) ;
useEffect ( ( ) = > {
const run = ( id : string | undefined ) = > {
if ( restoreRequest ) return ;
if ( id === "editor-layer-2" ) chooseLayer ( "secondary" ) ;
else if ( id === "editor-layer-3" ) chooseLayer ( "tertiary" ) ;
if ( id === "editor-layer-2" ) { if ( isWow ) chooseBar ( 1 ) ; else chooseLayer ( "secondary" ) ; }
else if ( id === "editor-layer-3" ) { if ( isWow ) chooseBar ( 2 ) ; else chooseLayer ( "tertiary" ) ; }
else if ( id === "editor-clear" ) {
if ( editingControl ) clearSelected ( editingControl ) ;
if ( editingControl !== null ) clearSelected ( editingControl ) ;
else {
const prefix = "binding-slot-" ;
if ( controller . selectedId . startsWith ( prefix ) ) clearSelected ( controller . selectedId . slice ( prefix . length ) as ActionBindingControl ) ;
const prefix = isWow ? "binding-wow-slot-" : "binding-slot-" ;
if ( controller . selectedId . startsWith ( prefix ) ) clearSelected ( isWow ? Number ( controller . selectedId . slice ( prefix . length ) ) : controller . selectedId . slice ( prefix . length ) as ActionBindingControl ) ;
}
} else if ( id === "editor-reset" ) requestRestore ( "layer" , controller . selectedId ) ;
} ;
@@ -252,7 +327,7 @@ function AbilityBindingsDialog() {
} ;
window . addEventListener ( "keydown" , onKey ) ;
return ( ) = > { unsubscribe ( ) ; window . removeEventListener ( "keydown" , onKey ) ; } ;
} , [ controller . selectedId , editingControl , layer , restoreRequest ] ) ;
} , [ controller . selectedId , editingControl , layer , layout , bar , pcLayout , restoreRequest ] ) ;
const bindingSlot = ( control : ActionBindingControl ) = > {
const definition = ACTION_CONTROL_DEFINITIONS [ control ] ;
@@ -282,16 +357,32 @@ function AbilityBindingsDialog() {
return (
< div className = "modal-backdrop game-panel-backdrop" role = "presentation" >
< section className = "game-panel ability-bindings-panel" role = "dialog" aria-modal = "true" aria-labelledby = "ability-bindings-title" >
< section ref = { panel } className = "game-panel ability-bindings-panel" role = "dialog" aria-modal = "true" aria-labelledby = "ability-bindings-title" >
< header className = "game-panel__header" >
< div >
< p className = "eyebrow" > Controller loadout · 24 customizable slots < / p >
< p className = "eyebrow" > { isWow ? "WoW loadout · 5 bars · 60 customizable slots" : "Controller loadout · 24 customizable slots" } < / p >
< h2 id = "ability-bindings-title" > Ability controls < / h2 >
< / div >
< button type = "button" className = "icon-button" onClick = { close } aria-label = "Return to pause menu" > × < / button >
< / header >
< nav className = "ability-bindings__layer s" aria-label = "Ability binding layers " >
< nav className = "ability-bindings__layout s" aria-label = "Action bar style " >
{ ( [ "controller" , "wow" ] as const ) . map ( ( next ) = > (
< ControllerButton key = { next } controlId = { ` binding-layout- ${ next } ` } selectedId = { controller . selectedId } select = { controller . select }
type = "button" aria-pressed = { layout === next } className = { layout === next ? "is-active" : "" } onClick = { ( ) = > chooseLayout ( next ) } >
{ next === "wow" ? "WoW bars" : "Controller bars" }
< / ControllerButton >
) ) }
< / nav >
{ isWow ? < nav className = "ability-bindings__layers ability-bindings__bars" aria-label = "WoW action bars" >
{ pcLayout . bars . map ( ( _ , index ) = > (
< ControllerButton key = { index } controlId = { barControlId ( index ) } selectedId = { controller . selectedId } select = { controller . select }
type = "button" aria-pressed = { bar === index } className = { bar === index ? "is-active" : "" } onClick = { ( ) = > chooseBar ( index ) } >
< kbd > { index + 1 } < / kbd > < span > < strong > Bar { index + 1 } < / strong > < small > { pcLayout . wowBars && index < pcLayout . barCount ? "Enabled" : "Inactive" } < / small > < / span >
< / ControllerButton >
) ) }
< / nav > : < nav className = "ability-bindings__layers" aria-label = "Ability binding layers" >
{ ACTION_LAYERS . map ( ( nextLayer ) = > (
< ControllerButton
key = { nextLayer }
@@ -307,10 +398,31 @@ function AbilityBindingsDialog() {
< span > < strong > { ACTION_LAYER_LABELS [ nextLayer ] } < / strong > < small > { nextLayer === "primary" ? "No modifier" : ` Hold ${ ACTION_LAYER_LABELS [ nextLayer ] } ` } < / small > < / span >
< / ControllerButton >
) ) }
< / nav >
< / nav > }
< div className = "ability-bindings__layout" >
< section className = "ability-bindings__diagram " aria-label = { ` ${ ACTION_LAYER_LABELS [ layer ] } controller layout ` } >
{ isWow ? < section className = "ability-bindings__wow " aria-label = { ` Bar ${ bar + 1 } ability assignments ` } >
< div className = "ability-bindings__wow-slots" >
{ pcLayout . bars [ bar ] . slots . map ( ( slot , index ) = > {
const ability = abilities . find ( ( candidate ) = > candidate . id === slot . abilityId ) ;
const locked = Boolean ( ability && ability . unlockLevel > level ) ;
const name = ability ? . name ? ? ( slot . abilityId ? "Unavailable for this character" : "Empty" ) ;
return < ControllerButton key = { index } controlId = { slotControlId ( index ) } selectedId = { controller . selectedId } select = { controller . select }
type = "button" className = { ` ability-binding-slot ${ locked ? "is-locked" : "" } ${ editingControl === index ? "is-editing" : "" } ` }
aria-label = { ` ${ controlLabel ( index ) } : ${ name } ${ locked ? ` , unlocks at level ${ ability ! . unlockLevel } ` : "" } ` }
aria-pressed = { editingControl === index } title = { ` ${ controlLabel ( index ) } : ${ name } ` } onClick = { ( ) = > beginEditing ( index ) } >
< kbd > { actionKeyLabel ( slot . key ) || "—" } < / kbd >
{ ability ? < AbilityBindingIcon icon = { ability . icon } sigil = { ability . sigil } color = { classColor } / > : < span className = "ability-bindings__empty" aria-hidden = "true" > + < / span > }
< span > < strong > Button { index + 1 } < / strong > < small > { locked ? ` ${ name } · Level ${ ability ! . unlockLevel } ` : name } < / small > < / span >
< / ControllerButton > ;
} ) }
< / div >
< p className = "ability-bindings__wow-note" > { ! pcLayout . wowBars ? "Enable WoW action bars in UI layout to use these assignments on PC." : bar >= pcLayout . barCount ? "This bar is inactive. Increase the visible bar count in UI layout to use it." : "Choose a button to change its ability. Changes save immediately." } < / p >
< div className = "ability-bindings__restore" >
< ControllerButton controlId = "binding-restore-layer" selectedId = { controller . selectedId } select = { controller . select } type = "button" onClick = { ( ) = > requestRestore ( "layer" , "binding-restore-layer" ) } > Restore Bar { bar + 1 } < / ControllerButton >
< ControllerButton controlId = "binding-restore-all" selectedId = { controller . selectedId } select = { controller . select } type = "button" onClick = { ( ) = > requestRestore ( "all" , "binding-restore-all" ) } > Restore all WoW bars < / ControllerButton >
< / div >
< / section > : < section className = "ability-bindings__diagram" aria-label = { ` ${ ACTION_LAYER_LABELS [ layer ] } controller layout ` } >
< div className = "ability-bindings__shoulders" >
{ bindingSlot ( "r1" ) }
{ bindingSlot ( "r2" ) }
@@ -330,16 +442,20 @@ function AbilityBindingsDialog() {
< ControllerButton controlId = "binding-restore-layer" selectedId = { controller . selectedId } select = { controller . select } type = "button" onClick = { ( ) = > requestRestore ( "layer" , "binding-restore-layer" ) } > Restore { ACTION_LAYER_LABELS [ layer ] } < / ControllerButton >
< ControllerButton controlId = "binding-restore-all" selectedId = { controller . selectedId } select = { controller . select } type = "button" onClick = { ( ) = > requestRestore ( "all" , "binding-restore-all" ) } > Restore all < / ControllerButton >
< / div >
< / section >
< / section > }
< aside className = "ability-bindings__picker" aria-live = "polite" >
{ restoreRequest ? (
< >
< p className = "eyebrow" > Confirm controller reset < / p >
< h3 > { restoreRequest . scope === "all "
< p className = "eyebrow" > Confirm { restoreRequest . layout === "wow" ? "WoW bar" : "controller" } reset < / p >
< h3 > { restoreRequest . layout === "wow "
? restoreRequest . scope === "all" ? "Restore all WoW bar abilities?" : ` Restore Bar ${ restoreRequest . bar + 1 } abilities? `
: restoreRequest . scope === "all"
? "Restore all ability controls?"
: ` Restore the ${ ACTION_LAYER_LABELS [ restoreRequest . layer ] } layer? ` } < / h3 >
< p className = "ability-bindings__confirm-warning" > { restoreRequest . scope === "all "
< p className = "ability-bindings__confirm-warning" > { restoreRequest . layout === "wow "
? ` This replaces ${ restoreRequest . scope === "all" ? "all 60 WoW slots" : "this bar's 12 slots" } with the class defaults. Keyboard shortcuts and bar layout are kept. `
: restoreRequest . scope === "all"
? "This replaces every customized controller slot with the class defaults."
: ` This replaces all eight ${ ACTION_LAYER_LABELS [ restoreRequest . layer ] } slots with their class defaults. ` } < / p >
< div className = "ability-bindings__choices ability-bindings__choices--confirm" >
@@ -353,9 +469,9 @@ function AbilityBindingsDialog() {
< / ControllerButton >
< / div >
< / >
) : editingControl ? (
) : editingControl !== null ? (
< >
< p className = "eyebrow" > Assign { bindingChord ( layer , editingControl ) } < / p >
< p className = "eyebrow" > Assign { controlLabel ( editingControl ) } < / p >
< h3 > Choose an ability < / h3 >
< div className = "ability-bindings__choices" >
{ pickerAbilities . map ( ( ability ) = > (
@@ -372,19 +488,19 @@ function AbilityBindingsDialog() {
< / >
) : (
< >
< p className = "eyebrow" > { ACTION_LAYER_LABELS [ layer ] } layer < / p >
< h3 > Select a controller button < / h3 >
< p > Each layer has the four face buttons , R1 , R2 , and both horizontal D - pad directions . Abilities may be reused on more than one button . < / p >
< p className = "ability-bindings__notice" role = "status" > { notice } < / p >
< p className = "eyebrow" > { isWow ? ` Bar ${ bar + 1 } ` : ` ${ ACTION_LAYER_LABELS [ layer ] } layer ` } < / p >
< h3 > Select a { isWow ? "WoW bar" : "controller" } button < / h3 >
< p > { isWow ? "Each WoW bar has 12 buttons. Assign abilities on any of the five bars, including inactive bars. Change keyboard shortcuts in Options → Keybinds." : "Each layer has the four face buttons, R1, R2, and both horizontal D-pad directions. Abilities may be reused on more than one button." } < / p >
< / >
) }
< / aside >
< / div >
< p className = "ability-bindings__notice" role = "status" > { notice } < / p >
< footer className = "game-panel__footer" >
{ restoreRequest
? < > < span > < kbd > { keyLabel ( "controller" , "menu-confirm" ) } < / kbd > Choose < / span > < span > < kbd > { keyLabel ( "controller" , "menu-back" ) } < / kbd > Cancel < / span > < / >
: < > < span > < kbd > { keyLabel ( "controller" , "menu-confirm" ) } < / kbd > Assign < / span > < span > < kbd > { keyLabel ( "controller" , "editor-clear" ) } < / kbd > Clear < / span > < span > < kbd > { keyLabel ( "controller" , "menu-back" ) } < / kbd > Back < / span > < span > < kbd > { keyLabel ( "controller" , "editor-reset" ) } < / kbd > Restore layer < / span > < / > }
: < > < span > < kbd > { keyLabel ( "controller" , "menu-confirm" ) } < / kbd > Assign < / span > < span > < kbd > { keyLabel ( "controller" , "editor-clear" ) } < / kbd > Clear < / span > < span > < kbd > { keyLabel ( "controller" , "menu-back" ) } < / kbd > Back < / span > < span > < kbd > { keyLabel ( "controller" , "editor-reset" ) } < / kbd > Restore { isWow ? "bar" : "layer" } < / span > < / > }
< / footer >
< / section >
< / div >