mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 07:40:43 +00:00
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:
@@ -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 |
@@ -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 {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user