Rework UI code so selected routines can be called from different places

Mainly, there needs to be a way to always go back to the UI that
called another, wherever that is.
master
Travis Evans 2025-05-03 21:32:56 -05:00
parent 6fe2a36a2a
commit 71494ebb38
1 changed files with 255 additions and 108 deletions

View File

@ -198,6 +198,8 @@ function update_status_widget(timer) {
} }
// UI modes //
class TimerView { class TimerView {
// Primary UI for displaying and operating a timer. The // Primary UI for displaying and operating a timer. The
// PrimitiveTimer object is passed to the constructor as a // PrimitiveTimer object is passed to the constructor as a
@ -472,14 +474,14 @@ class TimerView {
// Execute a UI action represented by the string `action`. // Execute a UI action represented by the string `action`.
if (action === 'start/stop') { if (action === 'start/stop') {
return this.start_stop_timer() this.start_stop_timer()
} else if (action === 'edit_start') { } else if (action === 'edit_start') {
// Display the timer start edit UI switch_UI(
this.stop(); new TimerEditStart(
CURRENT_UI = new TimerViewMenu(this.timer); this.timer,
CURRENT_UI.edit_start( () => { switch_UI(new TimerView(this.timer)); }
() => { switch_UI(new TimerView(this.timer)); }, )
); );
} }
} }
@ -487,12 +489,19 @@ class TimerView {
class TimerFormatView { class TimerFormatView {
// UI for selecting the display format of a timer. The // UI for selecting the display format of a timer.
// PrimitiveTimer object is passed to the constructor as a
// parameter. constructor(timer, back) {
// `timer` is the current PrimitiveTimer object being edited.
// `back` is the function that activates the previous UI to return
// to when the format selection is exited. It is passed `true` if
// the format change was confirmed and `false` if it was canceled.
// If `back` is not specified, a default back handler is used that
// returns to the TimerView if accepted or TimerViewMenu if
// canceled.
constructor(timer) {
this.timer = timer; this.timer = timer;
this.back = back || this._back;
this.layout = null; this.layout = null;
this.listeners = {}; this.listeners = {};
@ -509,6 +518,15 @@ class TimerFormatView {
} }
} }
_back(ok) {
// Default back handler
if (ok) {
switch_UI(new TimerView(this.timer));
} else {
switch_UI(new TimerViewMenu(this.timer));
}
}
start() { start() {
// Initialize, display, and activate the UI // Initialize, display, and activate the UI
@ -743,28 +761,59 @@ class TimerFormatView {
tt.SETTINGS.format[row_id] = FORMAT_MENU[this.format_idx[row_id]]; tt.SETTINGS.format[row_id] = FORMAT_MENU[this.format_idx[row_id]];
} }
tt.set_settings_dirty(); tt.set_settings_dirty();
switch_UI(new TimerView(this.timer)); this.back(true);
} }
cancel() { cancel() {
// Return to TimerViewMenu without saving changes // Return to TimerViewMenu without saving changes
switch_UI(new TimerViewMenu(this.timer)); this.back(false);
} }
} }
class TimerViewMenu { class TimerViewMenu {
// UI for displaying the timer menu. The PrimitiveTimer object is // UI for displaying the timer menu.
// passed to the constructor as a parameter.
constructor(timer, back) {
// `timer` is the PrimitiveTimer object whose menu is being
// displayed. `back` is a function that activates the previous UI
// to return to when the menu is exited.
constructor(timer) {
this.timer = timer; this.timer = timer;
this.back = back || this._back;
}
_back() {
// Default back handler
// Return to TimerView for the current timer
switch_UI(new TimerView(this.timer));
} }
start() { start() {
// Display and activate the top menu of the timer view menu. // Display and activate the timer view menu.
this.top_menu(); const menu = {
'': {
title: this.timer.display_name(),
back: (() => { this.back(); }),
},
'Reset': () => { switch_UI(new ResetTimer(this.timer)); },
'Timers': () => { switch_UI(new TimerMenu(tt.TIMERS, this.timer)); },
'Edit': () => { switch_UI(new TimerEditMenu(this.timer)); },
'Format': () => { switch_UI(new TimerFormatView(this.timer)); },
'Add': () => {
tt.set_timers_dirty();
const new_timer = tt.add_timer(tt.TIMERS, this.timer);
switch_UI(new TimerEditMenu(new_timer));
},
'Delete': () => { switch_UI(new DeleteTimer(this.timer)); },
};
if (tt.TIMERS.length <= 1) {
// Prevent user deleting last timer
delete menu.Delete;
}
E.showMenu(menu);
} }
stop() { stop() {
@ -773,85 +822,143 @@ class TimerViewMenu {
E.showMenu(); E.showMenu();
} }
back() { }
// Return to the timer view
// (i.e. the timer that was previously displayed)
class ResetTimer {
// UI for resetting a timer.
constructor(timer, back) {
// `timer` is the PrimitiveTimer object to reset.
// `back` is a function that activates the previous UI to return
// to when the menu is exited. It is passed `true` if the timer is
// reset and `false` if it is canceled. If `back` is not
// specified, a default back handler is used that returns to
// TimerView if accepted or TimerViewMenu if canceled.
this.timer = timer;
this.back = back || this._back;
}
_back(ok) {
// Default back handler
if (ok) {
switch_UI(new TimerView(this.timer)); switch_UI(new TimerView(this.timer));
} else {
switch_UI(new TimerViewMenu(this.timer));
}
} }
top_menu(back) { start() {
// Display the top-level menu for the timer // Display and activate the reset timer confirmation menu.
// `back` is optional and specifies the previous routine to return to
console.log('top_menu: ' + back);
const top_menu = { const menu = {
'': {
title: this.timer.display_name(),
back: back || (() => { this.back(); }),
},
'Reset': () => { E.showMenu(reset_menu); },
'Timers': () => {
switch_UI(new TimerMenu(tt.TIMERS, this.timer));
},
'Edit': () => { this.edit_menu() },
'Format': () => {
switch_UI(new TimerFormatView(this.timer));
},
'Add': () => {
tt.set_timers_dirty();
const new_timer = tt.add_timer(tt.TIMERS, this.timer);
const timer_view_menu = new TimerViewMenu(new_timer);
timer_view_menu.edit_menu();
},
'Delete': () => { E.showMenu(delete_menu); },
};
if (tt.TIMERS.length <= 1) {
// Prevent user deleting last timer
delete top_menu.Delete;
}
const reset_menu = {
'': { '': {
title: 'Confirm reset', title: 'Confirm reset',
back: () => { E.showMenu(top_menu); } back: () => { this.back(false); }
}, },
'Reset': () => { 'Reset': () => {
this.timer.reset(); this.timer.reset();
tt.set_timers_dirty(); tt.set_timers_dirty();
this.back(); this.back(true);
}, },
'Cancel': () => { E.showMenu(top_menu); }, 'Cancel': () => { this.back(false); },
}; };
const delete_menu = { E.showMenu(menu);
'': {
title: 'Confirm delete',
back: () => { E.showMenu(top_menu); }
},
'Delete': () => {
tt.set_timers_dirty();
switch_UI(new TimerView(tt.delete_timer(tt.TIMERS, this.timer)));
},
'Cancel': () => { E.showMenu(top_menu); },
};
E.showMenu(top_menu);
} }
edit_menu(back) { stop() {
// Display the edit menu for the timer. This can be called in // Shut down the UI and clean up listeners and handlers
// place of `start` to jump directly to the edit menu.
// `back` is optional and specifies the previous routine to return to E.showMenu();
console.log('edit_menu: ' + back); }
}
class DeleteTimer {
// UI for deleting a timer.
constructor(timer, back) {
// `timer` is the PrimitiveTimer object to delete. `back` is a
// function that activates the previous UI to return to when the
// menu is exited. It is passed `true` for the first parameter if
// the timer is deleted and `false` if it is canceled. For the
// second parameter, it is passed the same timer if canceled or
// another existing timer in the list if the given timer was
// deleted. If `back` is not specified, a default back handler is
// used that returns to TimerView if accepted or TimerViewMenu if
// canceled.
this.timer = timer;
this.back = back || this._back;
}
_back(ok, timer) {
// Default back handler
if (ok) {
switch_UI(new TimerView(timer));
} else {
switch_UI(new TimerViewMenu(timer));
}
}
start() {
// Display and activate the delete timer confirmation menu.
const menu = {
'': {
title: 'Confirm delete',
back: () => { this.back(false, this.timer); }
},
'Delete': () => {
ok = true;
tt.set_timers_dirty();
this.back(true, tt.delete_timer(tt.TIMERS, this.timer));
},
'Cancel': () => { this.back(false, this.timer) },
};
E.showMenu(menu);
}
stop() {
// Shut down the UI and clean up listeners and handlers
E.showMenu();
}
}
class TimerEditMenu {
// UI for editing a timer.
constructor(timer, back) {
// `timer` is the PrimitiveTimer object to edit. `back` is a
// function that activates the previous UI to return to when the
// menu is exited. If `back` is not specified, a default back
// handler is used that returns to TimerViewMenu.
this.timer = timer;
this.back = back || this._back;
}
_back() {
// Default back handler
switch_UI(new TimerViewMenu(this.timer));
}
start() {
// Display the edit menu for the timer.
let keyboard = null; let keyboard = null;
try { keyboard = require("textinput"); } catch (e) {} try { keyboard = require("textinput"); } catch (e) {}
const edit_menu = { const menu = {
'': { '': {
title: 'Edit: ' + this.timer.display_name(), title: 'Edit: ' + this.timer.display_name(),
back: back || (() => { this.top_menu() }), back: () => { this.back(); }
}, },
'Name': { 'Name': {
value: this.timer.name, value: this.timer.name,
@ -860,12 +967,12 @@ class TimerViewMenu {
keyboard.input({text:this.timer.name}).then(text => { keyboard.input({text:this.timer.name}).then(text => {
this.timer.name = text; this.timer.name = text;
tt.set_timers_dirty(); tt.set_timers_dirty();
setTimeout(() => { this.edit_menu(); }, 0); switch_UI(new TimerViewMenu(this.timer));
}); });
}, 0); }, 0);
} }
}, },
'Start': () => { this.edit_start(); }, 'Start': () => { switch_UI(new TimerEditStart(this.timer)); },
'At end': { 'At end': {
// Option to auto-start another timer when this one ends // Option to auto-start another timer when this one ends
format: v => v === -1 format: v => v === -1
@ -899,17 +1006,45 @@ class TimerViewMenu {
}; };
if (!keyboard) { if (!keyboard) {
delete edit_menu.Name; // Hide the Name menu item if text input module is not available
delete menu.Name;
} }
E.showMenu(edit_menu); E.showMenu(menu);
} }
edit_start(back) { stop() {
// Shut down the UI and clean up listeners and handlers
E.showMenu();
}
}
class TimerEditStart {
// UI for editing the timer's starting value.
constructor(timer, back) {
// `timer` is the PrimitiveTimer object to edit. `back` is a
// function that activates the previous UI to return to when the
// menu is exited. It is passed `true` if the timer is edited and
// `false` if it is canceled. If `back` is not specified, a
// default back handler is used that returns to TimerView.
this.timer = timer;
this.back = back || this._back;
}
_back(ok) {
// Default back handler
switch_UI(new TimerEditMenu(this.timer));
}
start() {
// Display the edit > start menu for the timer // Display the edit > start menu for the timer
// (i.e. the timer's starting value)
// `back` is optional and specifies the previous routine to return to var ok = false;
console.log('edit_start: ' + back);
let origin_hms = { let origin_hms = {
h: Math.floor(this.timer.origin / 3600), h: Math.floor(this.timer.origin / 3600),
@ -940,60 +1075,72 @@ class TimerViewMenu {
wrap_3: true, wrap_3: true,
separator_1: ':', separator_1: ':',
separator_2: ':', separator_2: ':',
back: back || (() => { this.edit_menu(); }), back: () => { this.back(ok); },
onchange: (h, m, s) => { onchange: (h, m, s) => {
ok = true;
this.timer.origin = h * 3600 + m * 60 + s; this.timer.origin = h * 3600 + m * 60 + s;
tt.set_timers_dirty(); tt.set_timers_dirty();
} }
}); });
} }
}
class TimerMenu {
// UI for displaying the list of timers. The list of
// PrimitiveTimer objects is passed to the constructor as a
// parameter. The currently focused timer is passed as the
// second parameter.
constructor(timers, focused_timer) {
this.timers = timers;
this.focused_timer = focused_timer;
}
start() {
// Display the top timer menu
this.top_menu();
}
stop() { stop() {
// Shut down the UI and clean up listeners and handlers // Shut down the UI and clean up listeners and handlers
E.showMenu(); E.showMenu();
} }
back() {
// Return to the timer's menu
switch_UI(new TimerViewMenu(this.focused_timer));
} }
top_menu() {
// Display the top-level menu for the timer list class TimerMenu {
// UI for choosing among the list of defined timers.
constructor(timers, focused_timer, back) {
// `timers` is the list of PrimitiveTimer objects to display.
// `focused_timer` is the PrimitiveTimer object that is currently
// being displayed. `back` is a function that activates the
// previous UI to return to when the menu is exited. It is passed
// the selected timer object if a timer is selected or `null` if
// the menu is canceled, and the last-focused timer object. If not
// specified, a default back handler is used that returns to
// TimerView for the selected timer or TimerViewMenu if canceled.
this.timers = timers;
this.focused_timer = focused_timer;
this.back = back || this._back;
}
_back(timer, focused_timer) {
// Default back handler
if (timer) {
switch_UI(new TimerView(timer));
} else {
switch_UI(new TimerViewMenu(focused_timer));
}
}
start() {
// Display the timer menu
let menu = { let menu = {
'': { '': {
title: "Timers", title: "Timers",
back: this.back.bind(this) back: () => { this.back(null, this.focused_timer); }
} }
}; };
this.timers.forEach((timer) => { this.timers.forEach((timer) => {
menu[timer.display_status() + ' ' + timer.display_name()] = menu[timer.display_status() + ' ' + timer.display_name()] =
() => { switch_UI(new TimerView(timer)); }; () => { this.back(timer, this.focused_timer); };
}); });
E.showMenu(menu); E.showMenu(menu);
} }
stop() {
// Shut down the UI and clean up listeners and handlers
E.showMenu();
}
} }