Update game timer UI (#2577)

## Description:

- use hh:mm:ss for timer format or mm:ss if no hours are present
- refactor classes and code to be simpler
- move timer inline with the buttons so the whole ui is smaller for more
game space
- update fast forward icon to better represent what it does
- move replay controls below game time ui

### Before:

<img width="218" height="155" alt="image"
src="https://github.com/user-attachments/assets/bfdbe571-3ec5-4c02-840b-112e8e3caab1"
/>
<img width="360" height="171" alt="image"
src="https://github.com/user-attachments/assets/ceee2923-fec8-4ebc-b9de-4b30a47b38a8"
/>
<img width="192" height="136" alt="image"
src="https://github.com/user-attachments/assets/8f8f464c-48e4-42c1-b378-51be2b1fc405"
/>


### After:

<img width="287" height="106" alt="image"
src="https://github.com/user-attachments/assets/30246189-9795-4822-b857-0af524cacb92"
/>
<img width="294" height="180" alt="image"
src="https://github.com/user-attachments/assets/065285ca-ae46-4481-9dd5-00d3bb4fcfb3"
/>
<img width="290" height="182" alt="image"
src="https://github.com/user-attachments/assets/c1b3da39-0785-4d50-8105-c028fa496963"
/>
<img width="213" height="88" alt="image"
src="https://github.com/user-attachments/assets/41e0a84c-2f68-4d24-9923-dd92bb70d161"
/>


## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

`rovi.`
This commit is contained in:
Roan
2025-12-10 22:51:14 +00:00
committed by GitHub
parent ae884cb902
commit 8dde30ebb6
4 changed files with 45 additions and 56 deletions
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-15 -15 203.206 130">
<polygon stroke-linejoin="round" fill="#fefffe" stroke="#fefffe" stroke-width="10"
points="0 0, 86.603 50, 0 100" />
<polygon stroke-linejoin="round" fill="#fefffe" stroke="#fefffe" stroke-width="10"
points="86.603 0, 173.206 50, 86.603 100" />
</svg>

After

Width:  |  Height:  |  Size: 334 B

+30 -47
View File
@@ -1,10 +1,9 @@
import { html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import exitIcon from "../../../../resources/images/ExitIconWhite.svg";
import FastForwardIconSolid from "../../../../resources/images/FastForwardIconSolidWhite.svg";
import pauseIcon from "../../../../resources/images/PauseIconWhite.svg";
import playIcon from "../../../../resources/images/PlayIconWhite.svg";
import replayRegularIcon from "../../../../resources/images/ReplayRegularIconWhite.svg";
import replaySolidIcon from "../../../../resources/images/ReplaySolidIconWhite.svg";
import settingsIcon from "../../../../resources/images/SettingIconWhite.svg";
import { EventBus } from "../../../core/EventBus";
import { GameType } from "../../../core/game/Game";
@@ -74,13 +73,17 @@ export class GameRightSidebar extends LitElement implements Layer {
}
private secondsToHms = (d: number): string => {
const pad = (n: number) => (n < 10 ? `0${n}` : n);
const h = Math.floor(d / 3600);
const m = Math.floor((d % 3600) / 60);
const s = Math.floor((d % 3600) % 60);
let time = d === 0 ? "-" : `${s}s`;
if (m > 0) time = `${m}m` + time;
if (h > 0) time = `${h}h` + time;
return time;
if (h !== 0) {
return `${pad(h)}:${pad(m)}:${pad(s)}`;
} else {
return `${pad(m)}:${pad(s)}`;
}
};
private toggleReplayPanel(): void {
@@ -116,46 +119,31 @@ export class GameRightSidebar extends LitElement implements Layer {
render() {
if (this.game === undefined) return html``;
const timerColor =
this.game.config().gameConfig().maxTimerValue !== undefined &&
this.timer < 60
? "text-red-400"
: "";
return html`
<aside
class=${`flex flex-col max-h-[calc(100vh-80px)] overflow-y-auto p-2 bg-gray-800/70 backdrop-blur-sm shadow-xs rounded-lg transition-transform duration-300 ease-out transform ${
class=${`w-fit flex flex-row items-center gap-3 py-2 px-3 bg-gray-800/70 backdrop-blur-sm shadow-xs rounded-lg transition-transform duration-300 ease-out transform text-white ${
this._isVisible ? "translate-x-0" : "translate-x-full"
}`}
@contextmenu=${(e: Event) => e.preventDefault()}
>
<div
class=${`flex justify-end items-center gap-2 text-white ${
this._isReplayVisible ? "mb-2" : ""
}`}
>
${this.maybeRenderReplayButtons()}
<div
class="w-6 h-6 cursor-pointer"
@click=${this.onSettingsButtonClick}
>
<img
src=${settingsIcon}
alt="settings"
width="20"
height="20"
style="vertical-align: middle;"
/>
</div>
<div class="w-6 h-6 cursor-pointer" @click=${this.onExitButtonClick}>
<img src=${exitIcon} alt="exit" width="20" height="20" />
</div>
<!-- In-game time -->
<div class=${timerColor}>${this.secondsToHms(this.timer)}</div>
<!-- Buttons -->
${this.maybeRenderReplayButtons()}
<div class="cursor-pointer" @click=${this.onSettingsButtonClick}>
<img src=${settingsIcon} alt="settings" width="20" height="20" />
</div>
<!-- Timer display below buttons -->
<div class="flex justify-center items-center mt-2">
<div
class="w-[70px] h-8 lg:w-24 lg:h-10 p-0.5 text-xs md:text-sm lg:text-base flex items-center justify-center text-white px-1"
style="${this.game.config().gameConfig().maxTimerValue !==
undefined && this.timer < 60
? "color: #ff8080;"
: ""}"
>
${this.secondsToHms(this.timer)}
</div>
<div class="cursor-pointer" @click=${this.onExitButtonClick}>
<img src=${exitIcon} alt="exit" width="20" height="20" />
</div>
</aside>
`;
@@ -163,25 +151,20 @@ export class GameRightSidebar extends LitElement implements Layer {
maybeRenderReplayButtons() {
if (this._isSinglePlayer || this.game?.config()?.isReplay()) {
return html` <div
class="w-6 h-6 cursor-pointer"
@click=${this.toggleReplayPanel}
>
return html` <div class="cursor-pointer" @click=${this.toggleReplayPanel}>
<img
src=${this._isReplayVisible ? replaySolidIcon : replayRegularIcon}
src=${FastForwardIconSolid}
alt="replay"
width="20"
height="20"
style="vertical-align: middle;"
/>
</div>
<div class="w-6 h-6 cursor-pointer" @click=${this.onPauseButtonClick}>
<div class="cursor-pointer" @click=${this.onPauseButtonClick}>
<img
src=${this.isPaused ? playIcon : pauseIcon}
alt="play/pause"
width="20"
height="20"
style="vertical-align: middle;"
/>
</div>`;
} else {
+7 -7
View File
@@ -66,15 +66,15 @@ export class ReplayPanel extends LitElement implements Layer {
return html`
<div
class="flex-shrink-0 bg-opacity-60 bg-gray-900 p-1 lg:p-2 rounded-es-sm lg:rounded-lg backdrop-blur-md"
class="p-2 bg-gray-800/70 backdrop-blur-sm shadow-xs rounded-lg"
@contextmenu=${(e: Event) => e.preventDefault()}
>
<label class="block mb-1 text-white" translate="no">
<label class="block mb-2 text-white" translate="no">
${this.game?.config()?.isReplay()
? translateText("replay_panel.replay_speed")
: translateText("replay_panel.game_speed")}
</label>
<div class="grid grid-cols-2 gap-1">
<div class="grid grid-cols-4 gap-2">
${this.renderSpeedButton(ReplaySpeedMultiplier.slow, "×0.5")}
${this.renderSpeedButton(ReplaySpeedMultiplier.normal, "×1")}
${this.renderSpeedButton(ReplaySpeedMultiplier.fast, "×2")}
@@ -88,12 +88,12 @@ export class ReplayPanel extends LitElement implements Layer {
}
private renderSpeedButton(value: ReplaySpeedMultiplier, label: string) {
const isActive = this._replaySpeedMultiplier === value;
const backgroundColor =
this._replaySpeedMultiplier === value ? "bg-blue-400" : "";
return html`
<button
class="text-white font-bold py-0 rounded border transition ${isActive
? "bg-blue-500 border-gray-400"
: "border-gray-500"}"
class="py-0.5 px-1 text-sm text-white rounded border transition border-gray-500 ${backgroundColor} hover:border-gray-200"
@click=${() => this.onReplaySpeedChange(value)}
>
${label}
+2 -2
View File
@@ -408,9 +408,9 @@
<game-starting-modal></game-starting-modal>
<game-top-bar></game-top-bar>
<unit-display></unit-display>
<div class="flex fixed top-4 right-4 z-[1000] items-start gap-2">
<replay-panel></replay-panel>
<div class="flex flex-col items-end fixed top-4 right-4 z-[1000] gap-2">
<game-right-sidebar></game-right-sidebar>
<replay-panel></replay-panel>
</div>
<settings-modal></settings-modal>
<player-panel></player-panel>