1+ // ui/lib/src/setup/view/timeControl.ts
12import type { Prop } from '@/index' ;
23import { hl , type VNode } from '@/view' ;
34import type { InputValue } from '../interfaces' ;
45import {
56 timeModes ,
67 sliderTimes ,
8+ sliderInitVal ,
9+ timeVToTime ,
710 incrementVToIncrement ,
811 daysVToDays ,
912 type TimeControl ,
@@ -18,6 +21,35 @@ const showTime = (v: number) => {
1821 return v . toString ( ) ;
1922} ;
2023
24+ const PRESETS = {
25+ standard : [
26+ // Common non-default time controls.
27+ { lim : 0.25 , inc : 0 } ,
28+ { lim : 0.5 , inc : 0 } ,
29+ { lim : 0 , inc : 1 } ,
30+ { lim : 1 , inc : 1 } ,
31+ { lim : 2 , inc : 0 } ,
32+ { lim : 8 , inc : 0 } ,
33+ { lim : 5 , inc : 5 } ,
34+ { lim : 10 , inc : 3 } ,
35+ { lim : 15 , inc : 0 } ,
36+ ] ,
37+ nonStandard : [
38+ // mirrors modules/pool/src/main/PoolList.scala
39+ { lim : 1 , inc : 0 } ,
40+ { lim : 2 , inc : 1 } ,
41+ { lim : 3 , inc : 0 } ,
42+ { lim : 3 , inc : 2 } ,
43+ { lim : 5 , inc : 0 } ,
44+ { lim : 5 , inc : 3 } ,
45+ { lim : 10 , inc : 0 } ,
46+ { lim : 10 , inc : 5 } ,
47+ { lim : 15 , inc : 10 } ,
48+ { lim : 30 , inc : 0 } ,
49+ { lim : 30 , inc : 20 } ,
50+ ] ,
51+ } ;
52+
2153const blindModeTimePickers = ( tc : TimeControl ) => {
2254 return [
2355 renderTimeModePicker ( tc ) ,
@@ -95,35 +127,95 @@ const inputRange = (min: number, max: number, prop: Prop<InputValue>, classes?:
95127 hl ( 'input.range' , {
96128 class : classes ,
97129 attrs : { type : 'range' , min, max, value : prop ( ) } ,
130+ hook : {
131+ update : ( _ : VNode , vnode : VNode ) => {
132+ const el = vnode . elm as HTMLInputElement ;
133+ el . value = prop ( ) . toString ( ) ;
134+ } ,
135+ } ,
98136 on : { input : ( e : Event ) => prop ( parseFloat ( ( e . target as HTMLInputElement ) . value ) ) } ,
99137 } ) ;
100138
101- export const timePickerAndSliders = ( tc : TimeControl , minimumTimeRequiredIfReal : number = 0 ) : VNode =>
102- hl (
103- 'div.config-group' ,
104- site . blindMode
105- ? blindModeTimePickers ( tc )
106- : [
107- renderTimeModePicker ( tc ) ,
108- tc . mode ( ) === 'realTime' &&
109- hl ( 'div.time-choice.range' , [
110- `${ i18n . site . minutesPerSide } : ` ,
111- hl ( 'span' , showTime ( tc . time ( ) ) ) ,
112- inputRange ( 0 , 38 , tc . timeV , {
113- failure : ! tc . realTimeValid ( minimumTimeRequiredIfReal ) ,
114- } ) ,
115- ] ) ,
116- tc . mode ( ) === 'realTime'
117- ? hl ( 'div.increment-choice.range' , [
118- `${ i18n . site . incrementInSeconds } : ` ,
119- hl ( 'span' , `${ tc . increment ( ) } ` ) ,
120- inputRange ( 0 , 30 , tc . incrementV , { failure : ! tc . realTimeValid ( minimumTimeRequiredIfReal ) } ) ,
121- ] )
122- : tc . mode ( ) === 'correspondence' &&
123- hl ( 'div.days-choice.range' , [
124- `${ i18n . site . daysPerTurn } : ` ,
125- hl ( 'span' , `${ tc . days ( ) } ` ) ,
126- inputRange ( 1 , 7 , tc . daysV ) ,
127- ] ) ,
128- ] ,
129- ) ;
139+ export const timePickerAndSliders = (
140+ tc : TimeControl ,
141+ minimumTimeRequiredIfReal : number = 0 ,
142+ variant ?: string ,
143+ gameType ?: 'hook' | 'friend' | 'ai' ,
144+ ) : VNode => {
145+ if ( site . blindMode ) return hl ( 'div.config-group' , blindModeTimePickers ( tc ) ) ;
146+
147+ const activeMode = tc . mode ( ) ;
148+ const showTabs = tc . canSelectMode ( ) ;
149+
150+ const tabs = showTabs
151+ ? hl (
152+ 'div.tabs-horiz' ,
153+ tc . modes . map ( mode =>
154+ hl (
155+ 'span' ,
156+ {
157+ class : { active : activeMode === mode } ,
158+ on : { click : ( ) => tc . mode ( mode ) } ,
159+ } ,
160+ timeModes . find ( m => m . key === mode ) ?. name || mode ,
161+ ) ,
162+ ) ,
163+ )
164+ : null ;
165+
166+ let panelContent : VNode | null = null ;
167+
168+ if ( activeMode === 'realTime' ) {
169+ const isStandard = ! variant || variant === 'standard' || variant === 'fromPosition' ;
170+ const currentPresets = gameType === 'hook' && isStandard ? PRESETS . standard : PRESETS . nonStandard ;
171+
172+ panelContent = hl ( 'div.time-panel' , [
173+ hl (
174+ 'div.presets' ,
175+ currentPresets . map ( p =>
176+ hl (
177+ 'button.preset-btn' ,
178+ {
179+ on : {
180+ click : ( ) => {
181+ tc . timeV ( sliderInitVal ( p . lim , timeVToTime , 100 , 9 ) ) ;
182+ tc . incrementV ( sliderInitVal ( p . inc , incrementVToIncrement , 100 , 0 ) ) ;
183+ } ,
184+ } ,
185+ } ,
186+ `${ showTime ( p . lim ) } +${ p . inc } ` ,
187+ ) ,
188+ ) ,
189+ ) ,
190+ hl ( 'div.sliders-grid' , [
191+ hl ( 'div.slider-container' , [
192+ hl ( 'div.label-row' , [
193+ hl ( 'label' , i18n . site . minutesPerSide ) ,
194+ hl ( 'span.val-box' , showTime ( tc . time ( ) ) ) ,
195+ ] ) ,
196+ inputRange ( 0 , 38 , tc . timeV , {
197+ failure : ! tc . realTimeValid ( minimumTimeRequiredIfReal ) ,
198+ } ) ,
199+ ] ) ,
200+ hl ( 'div.slider-container' , [
201+ hl ( 'div.label-row' , [
202+ hl ( 'label' , i18n . site . incrementInSeconds ) ,
203+ hl ( 'span.val-box' , tc . increment ( ) . toString ( ) ) ,
204+ ] ) ,
205+ inputRange ( 0 , 30 , tc . incrementV , { failure : ! tc . realTimeValid ( minimumTimeRequiredIfReal ) } ) ,
206+ ] ) ,
207+ ] ) ,
208+ ] ) ;
209+ } else if ( activeMode === 'correspondence' ) {
210+ panelContent = hl ( 'div.time-panel' , [
211+ hl ( 'div.slider-container.full-width' , [
212+ hl ( 'div.label-row' , [ hl ( 'label' , i18n . site . daysPerTurn ) , hl ( 'span.val-box' , tc . days ( ) . toString ( ) ) ] ) ,
213+ inputRange ( 1 , 7 , tc . daysV ) ,
214+ ] ) ,
215+ ] ) ;
216+ } else if ( activeMode === 'unlimited' ) {
217+ panelContent = hl ( 'div.time-panel' , i18n . site . unlimitedDesc ) ;
218+ }
219+
220+ return hl ( 'div.config-group.time-control-tabs' , [ tabs , panelContent ] ) ;
221+ } ;
0 commit comments