mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 04:53:49 +00:00
75ca2fb349
## Description: Purpose: to reduce redundant strings and avoid exceeding Crowdin’s text limit. - Replaced _enabled/_disabled text with shared ..._desc in SettingsModal.ts. <img width="432" height="645" alt="スクリーンショット 2025-10-17 21 38 47" src="https://github.com/user-attachments/assets/10942de4-1b2c-4df4-8811-95190463a2ab" /> ## 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: Aotumuri Co-authored-by: Evan <evanpelle@gmail.com>
461 lines
16 KiB
TypeScript
461 lines
16 KiB
TypeScript
import { html, LitElement } from "lit";
|
||
import { customElement, property, query, state } from "lit/decorators.js";
|
||
import structureIcon from "../../../../resources/images/CityIconWhite.svg";
|
||
import darkModeIcon from "../../../../resources/images/DarkModeIconWhite.svg";
|
||
import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg";
|
||
import exitIcon from "../../../../resources/images/ExitIconWhite.svg";
|
||
import explosionIcon from "../../../../resources/images/ExplosionIconWhite.svg";
|
||
import mouseIcon from "../../../../resources/images/MouseIconWhite.svg";
|
||
import ninjaIcon from "../../../../resources/images/NinjaIconWhite.svg";
|
||
import settingsIcon from "../../../../resources/images/SettingIconWhite.svg";
|
||
import treeIcon from "../../../../resources/images/TreeIconWhite.svg";
|
||
import musicIcon from "../../../../resources/images/music.svg";
|
||
import { EventBus } from "../../../core/EventBus";
|
||
import { UserSettings } from "../../../core/game/UserSettings";
|
||
import { AlternateViewEvent, RefreshGraphicsEvent } from "../../InputHandler";
|
||
import { PauseGameEvent } from "../../Transport";
|
||
import { translateText } from "../../Utils";
|
||
import SoundManager from "../../sound/SoundManager";
|
||
import { Layer } from "./Layer";
|
||
|
||
export class ShowSettingsModalEvent {
|
||
constructor(
|
||
public readonly isVisible: boolean = true,
|
||
public readonly shouldPause: boolean = false,
|
||
public readonly isPaused: boolean = false,
|
||
) {}
|
||
}
|
||
|
||
@customElement("settings-modal")
|
||
export class SettingsModal extends LitElement implements Layer {
|
||
public eventBus: EventBus;
|
||
public userSettings: UserSettings;
|
||
|
||
@state()
|
||
private isVisible: boolean = false;
|
||
|
||
@state()
|
||
private alternateView: boolean = false;
|
||
|
||
@query(".modal-overlay")
|
||
private modalOverlay!: HTMLElement;
|
||
|
||
@property({ type: Boolean })
|
||
shouldPause = false;
|
||
|
||
@property({ type: Boolean })
|
||
wasPausedWhenOpened = false;
|
||
|
||
init() {
|
||
SoundManager.setBackgroundMusicVolume(
|
||
this.userSettings.backgroundMusicVolume(),
|
||
);
|
||
SoundManager.setSoundEffectsVolume(this.userSettings.soundEffectsVolume());
|
||
this.eventBus.on(ShowSettingsModalEvent, (event) => {
|
||
this.isVisible = event.isVisible;
|
||
this.shouldPause = event.shouldPause;
|
||
this.wasPausedWhenOpened = event.isPaused;
|
||
this.pauseGame(true);
|
||
});
|
||
}
|
||
|
||
createRenderRoot() {
|
||
return this;
|
||
}
|
||
|
||
connectedCallback() {
|
||
super.connectedCallback();
|
||
window.addEventListener("click", this.handleOutsideClick, true);
|
||
window.addEventListener("keydown", this.handleKeyDown);
|
||
}
|
||
|
||
disconnectedCallback() {
|
||
window.removeEventListener("click", this.handleOutsideClick, true);
|
||
window.removeEventListener("keydown", this.handleKeyDown);
|
||
super.disconnectedCallback();
|
||
}
|
||
|
||
private handleOutsideClick = (event: MouseEvent) => {
|
||
if (
|
||
this.isVisible &&
|
||
this.modalOverlay &&
|
||
event.target === this.modalOverlay
|
||
) {
|
||
this.closeModal();
|
||
}
|
||
};
|
||
|
||
private handleKeyDown = (event: KeyboardEvent) => {
|
||
if (this.isVisible && event.key === "Escape") {
|
||
this.closeModal();
|
||
}
|
||
};
|
||
|
||
public openModal() {
|
||
this.isVisible = true;
|
||
document.body.style.overflow = "hidden";
|
||
this.requestUpdate();
|
||
}
|
||
|
||
public closeModal() {
|
||
this.isVisible = false;
|
||
document.body.style.overflow = "";
|
||
this.requestUpdate();
|
||
this.pauseGame(false);
|
||
}
|
||
|
||
private pauseGame(pause: boolean) {
|
||
if (this.shouldPause && !this.wasPausedWhenOpened)
|
||
this.eventBus.emit(new PauseGameEvent(pause));
|
||
}
|
||
|
||
private onTerrainButtonClick() {
|
||
this.alternateView = !this.alternateView;
|
||
this.eventBus.emit(new AlternateViewEvent(this.alternateView));
|
||
this.requestUpdate();
|
||
}
|
||
|
||
private onToggleEmojisButtonClick() {
|
||
this.userSettings.toggleEmojis();
|
||
this.requestUpdate();
|
||
}
|
||
|
||
private onToggleStructureSpritesButtonClick() {
|
||
this.userSettings.toggleStructureSprites();
|
||
this.requestUpdate();
|
||
}
|
||
|
||
private onToggleSpecialEffectsButtonClick() {
|
||
this.userSettings.toggleFxLayer();
|
||
this.requestUpdate();
|
||
}
|
||
|
||
private onToggleDarkModeButtonClick() {
|
||
this.userSettings.toggleDarkMode();
|
||
this.eventBus.emit(new RefreshGraphicsEvent());
|
||
this.requestUpdate();
|
||
}
|
||
|
||
private onToggleRandomNameModeButtonClick() {
|
||
this.userSettings.toggleRandomName();
|
||
this.requestUpdate();
|
||
}
|
||
|
||
private onToggleLeftClickOpensMenu() {
|
||
this.userSettings.toggleLeftClickOpenMenu();
|
||
this.requestUpdate();
|
||
}
|
||
|
||
private onTogglePerformanceOverlayButtonClick() {
|
||
this.userSettings.togglePerformanceOverlay();
|
||
this.requestUpdate();
|
||
}
|
||
|
||
private onExitButtonClick() {
|
||
// redirect to the home page
|
||
window.location.href = "/";
|
||
}
|
||
|
||
private onVolumeChange(event: Event) {
|
||
const volume = parseFloat((event.target as HTMLInputElement).value) / 100;
|
||
this.userSettings.setBackgroundMusicVolume(volume);
|
||
SoundManager.setBackgroundMusicVolume(volume);
|
||
this.requestUpdate();
|
||
}
|
||
|
||
private onSoundEffectsVolumeChange(event: Event) {
|
||
const volume = parseFloat((event.target as HTMLInputElement).value) / 100;
|
||
this.userSettings.setSoundEffectsVolume(volume);
|
||
SoundManager.setSoundEffectsVolume(volume);
|
||
this.requestUpdate();
|
||
}
|
||
|
||
render() {
|
||
if (!this.isVisible) {
|
||
return null;
|
||
}
|
||
|
||
return html`
|
||
<div
|
||
class="modal-overlay fixed inset-0 bg-black/50 backdrop-blur-sm z-[2000] flex items-center justify-center p-4"
|
||
@contextmenu=${(e: Event) => e.preventDefault()}
|
||
>
|
||
<div
|
||
class="bg-slate-800 border border-slate-600 rounded-lg shadow-xl max-w-md w-full max-h-[80vh] overflow-y-auto"
|
||
>
|
||
<div
|
||
class="flex items-center justify-between p-4 border-b border-slate-600"
|
||
>
|
||
<div class="flex items-center gap-2">
|
||
<img
|
||
src=${settingsIcon}
|
||
alt="settings"
|
||
width="24"
|
||
height="24"
|
||
style="vertical-align: middle;"
|
||
/>
|
||
<h2 class="text-xl font-semibold text-white">
|
||
${translateText("user_setting.tab_basic")}
|
||
</h2>
|
||
</div>
|
||
<button
|
||
class="text-slate-400 hover:text-white text-2xl font-bold leading-none"
|
||
@click=${this.closeModal}
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
|
||
<div class="p-4 space-y-3">
|
||
<div
|
||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded text-white transition-colors"
|
||
>
|
||
<img src=${musicIcon} alt="musicIcon" width="20" height="20" />
|
||
<div class="flex-1">
|
||
<div class="font-medium">
|
||
${translateText("user_setting.background_music_volume")}
|
||
</div>
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="100"
|
||
.value=${this.userSettings.backgroundMusicVolume() * 100}
|
||
@input=${this.onVolumeChange}
|
||
class="w-full border border-slate-500 rounded-lg"
|
||
/>
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${Math.round(this.userSettings.backgroundMusicVolume() * 100)}%
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded text-white transition-colors"
|
||
>
|
||
<img
|
||
src=${musicIcon}
|
||
alt="soundEffectsIcon"
|
||
width="20"
|
||
height="20"
|
||
/>
|
||
<div class="flex-1">
|
||
<div class="font-medium">
|
||
${translateText("user_setting.sound_effects_volume")}
|
||
</div>
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="100"
|
||
.value=${this.userSettings.soundEffectsVolume() * 100}
|
||
@input=${this.onSoundEffectsVolumeChange}
|
||
class="w-full border border-slate-500 rounded-lg"
|
||
/>
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${Math.round(this.userSettings.soundEffectsVolume() * 100)}%
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded text-white transition-colors"
|
||
@click="${this.onTerrainButtonClick}"
|
||
>
|
||
<img src=${treeIcon} alt="treeIcon" width="20" height="20" />
|
||
<div class="flex-1">
|
||
<div class="font-medium">
|
||
${translateText("user_setting.toggle_terrain")}
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${translateText("user_setting.toggle_view_desc")}
|
||
</div>
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${this.alternateView
|
||
? translateText("user_setting.on")
|
||
: translateText("user_setting.off")}
|
||
</div>
|
||
</button>
|
||
|
||
<button
|
||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded text-white transition-colors"
|
||
@click="${this.onToggleEmojisButtonClick}"
|
||
>
|
||
<img src=${emojiIcon} alt="emojiIcon" width="20" height="20" />
|
||
<div class="flex-1">
|
||
<div class="font-medium">
|
||
${translateText("user_setting.emojis_label")}
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${translateText("user_setting.emojis_desc")}
|
||
</div>
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${this.userSettings.emojis()
|
||
? translateText("user_setting.on")
|
||
: translateText("user_setting.off")}
|
||
</div>
|
||
</button>
|
||
|
||
<button
|
||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded text-white transition-colors"
|
||
@click="${this.onToggleDarkModeButtonClick}"
|
||
>
|
||
<img
|
||
src=${darkModeIcon}
|
||
alt="darkModeIcon"
|
||
width="20"
|
||
height="20"
|
||
/>
|
||
<div class="flex-1">
|
||
<div class="font-medium">
|
||
${translateText("user_setting.dark_mode_label")}
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${translateText("user_setting.dark_mode_desc")}
|
||
</div>
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${this.userSettings.darkMode()
|
||
? translateText("user_setting.on")
|
||
: translateText("user_setting.off")}
|
||
</div>
|
||
</button>
|
||
|
||
<button
|
||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded text-white transition-colors"
|
||
@click="${this.onToggleSpecialEffectsButtonClick}"
|
||
>
|
||
<img
|
||
src=${explosionIcon}
|
||
alt="specialEffects"
|
||
width="20"
|
||
height="20"
|
||
/>
|
||
<div class="flex-1">
|
||
<div class="font-medium">
|
||
${translateText("user_setting.special_effects_label")}
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${translateText("user_setting.special_effects_desc")}
|
||
</div>
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${this.userSettings.fxLayer()
|
||
? translateText("user_setting.on")
|
||
: translateText("user_setting.off")}
|
||
</div>
|
||
</button>
|
||
|
||
<button
|
||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded text-white transition-colors"
|
||
@click="${this.onToggleStructureSpritesButtonClick}"
|
||
>
|
||
<img
|
||
src=${structureIcon}
|
||
alt="structureSprites"
|
||
width="20"
|
||
height="20"
|
||
/>
|
||
<div class="flex-1">
|
||
<div class="font-medium">
|
||
${translateText("user_setting.structure_sprites_label")}
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${translateText("user_setting.structure_sprites_desc")}
|
||
</div>
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${this.userSettings.structureSprites()
|
||
? translateText("user_setting.on")
|
||
: translateText("user_setting.off")}
|
||
</div>
|
||
</button>
|
||
|
||
<button
|
||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded text-white transition-colors"
|
||
@click="${this.onToggleRandomNameModeButtonClick}"
|
||
>
|
||
<img src=${ninjaIcon} alt="ninjaIcon" width="20" height="20" />
|
||
<div class="flex-1">
|
||
<div class="font-medium">
|
||
${translateText("user_setting.anonymous_names_label")}
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${translateText("user_setting.anonymous_names_desc")}
|
||
</div>
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${this.userSettings.anonymousNames()
|
||
? translateText("user_setting.on")
|
||
: translateText("user_setting.off")}
|
||
</div>
|
||
</button>
|
||
|
||
<button
|
||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded text-white transition-colors"
|
||
@click="${this.onToggleLeftClickOpensMenu}"
|
||
>
|
||
<img src=${mouseIcon} alt="mouseIcon" width="20" height="20" />
|
||
<div class="flex-1">
|
||
<div class="font-medium">
|
||
${translateText("user_setting.left_click_menu")}
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${translateText("user_setting.left_click_desc")}
|
||
</div>
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${this.userSettings.leftClickOpensMenu()
|
||
? translateText("user_setting.on")
|
||
: translateText("user_setting.off")}
|
||
</div>
|
||
</button>
|
||
|
||
<button
|
||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded text-white transition-colors"
|
||
@click="${this.onTogglePerformanceOverlayButtonClick}"
|
||
>
|
||
<img
|
||
src=${settingsIcon}
|
||
alt="performanceIcon"
|
||
width="20"
|
||
height="20"
|
||
/>
|
||
<div class="flex-1">
|
||
<div class="font-medium">
|
||
${translateText("user_setting.performance_overlay_label")}
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${translateText("user_setting.performance_overlay_desc")}
|
||
</div>
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${this.userSettings.performanceOverlay()
|
||
? translateText("user_setting.on")
|
||
: translateText("user_setting.off")}
|
||
</div>
|
||
</button>
|
||
|
||
<div class="border-t border-slate-600 pt-3 mt-4">
|
||
<button
|
||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-red-600/20 rounded text-red-400 transition-colors"
|
||
@click="${this.onExitButtonClick}"
|
||
>
|
||
<img src=${exitIcon} alt="exitIcon" width="20" height="20" />
|
||
<div class="flex-1">
|
||
<div class="font-medium">
|
||
${translateText("user_setting.exit_game_label")}
|
||
</div>
|
||
<div class="text-sm text-slate-400">
|
||
${translateText("user_setting.exit_game_info")}
|
||
</div>
|
||
</div>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|