Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions src/client/ColorInput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { LitElement, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { UserSettings } from "../core/game/UserSettings";

export const COLOR_MODE_CHANGED_EVENT = "color-mode-changed";

/**
* A small button that sits next to the Skin/Pattern button on the main menu.
* Clicking it cycles through color modes: Random → Custom → Team.
* In "custom" mode a native color picker is also shown.
*/
@customElement("color-input")
export class ColorInput extends LitElement {
@state() private mode: "random" | "custom" | "team" = "random";
@state() private primaryColor = "#2196f3";

private userSettings = new UserSettings();

createRenderRoot() {
return this;
}

connectedCallback() {
super.connectedCallback();
this.mode = this.userSettings.colorMode();
this.primaryColor = this.userSettings.customPrimaryColor();
}

private cycleMode() {
const next: Record<string, "random" | "custom" | "team"> = {
random: "custom",
custom: "team",
team: "random",
};
const newMode = next[this.mode];
this.mode = newMode;
this.userSettings.setColorMode(newMode);
window.dispatchEvent(new CustomEvent(COLOR_MODE_CHANGED_EVENT));
}

private onColorChange(e: Event) {
const color = (e.target as HTMLInputElement).value;
this.primaryColor = color;
this.userSettings.setCustomPrimaryColor(color);
// Use a slightly darker shade as secondary color
this.userSettings.setCustomSecondaryColor(color);
window.dispatchEvent(new CustomEvent(COLOR_MODE_CHANGED_EVENT));
}

private modeLabel(): string {
if (this.mode === "custom") return "Custom";
if (this.mode === "team") return "Team";
return "Random";
Comment on lines +51 to +53
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Localize new UI strings via translateText().

These user-facing labels/titles are hardcoded and bypass localization.

As per coding guidelines: src/client/**/*.{ts,tsx}: All user-visible text must go through translateText() function with corresponding entries in resources/lang/en.json.

Also applies to: 68-69, 78-78, 83-83, 87-87, 99-99

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/ColorInput.ts` around lines 51 - 53, Replace hardcoded user-facing
strings in ColorInput.ts with calls to translateText(): locate the branch that
returns "Custom"/"Team"/"Random" (the getter or method referencing this.mode)
and change each literal to translateText('colorMode.custom') /
translateText('colorMode.team') / translateText('colorMode.random') (or similar
keys), then add corresponding entries in resources/lang/en.json; apply the same
treatment for the other occurrences noted (lines around 68-69, 78, 83, 87, 99)
so every visible label uses translateText() rather than raw strings.

}

private modeIcon(): string {
if (this.mode === "custom") return "🎨";
if (this.mode === "team") return "🤝";
return "🎲";
}

render() {
return html`
<div class="relative flex flex-col items-center gap-1 h-full">
<!-- Main button — same style as pattern-input -->
<button
id="color-input-btn"
title="Color Mode: ${this.modeLabel()} (click to change)"
class="pattern-btn m-0 p-0 w-full h-full flex cursor-pointer flex-col justify-center items-center focus:outline-none focus:ring-0 transition-all duration-200 hover:scale-105 bg-surface hover:brightness-[1.08] active:brightness-[0.95] hover:shadow-[var(--shadow-action-card-hover)] rounded-lg overflow-hidden gap-1"
@click=${this.cycleMode}
>
${this.mode === "custom"
? html`
<div
class="w-7 h-7 rounded-md border-2 border-white/30"
style="background:${this.primaryColor}"
></div>
<span class="text-[7px] leading-tight font-black text-white uppercase">Custom</span>
`
: this.mode === "team"
? html`
<span class="text-xl leading-none">🤝</span>
<span class="text-[7px] leading-tight font-black text-white uppercase">Team</span>
`
: html`
<span class="text-xl leading-none">🎲</span>
<span class="text-[7px] leading-tight font-black text-white uppercase">Random</span>
`}
</button>

<!-- Hidden native color picker — only shown in custom mode -->
${this.mode === "custom"
? html`
<input
type="color"
id="color-input-picker"
.value=${this.primaryColor}
@input=${this.onColorChange}
title="Pick your territory color"
class="absolute bottom-0 left-0 w-full h-1 opacity-0 cursor-pointer"
style="height:100%;top:0;"
/>
Comment on lines +91 to +102
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# First, let's read the ColorInput.ts file to see the full context
head -n 120 src/client/ColorInput.ts | tail -n +70

Repository: openfrontio/OpenFrontIO

Length of output: 1459


🏁 Script executed:

# Check if the file uses translateText() function
rg "translateText" src/client/ColorInput.ts -A 2 -B 2

Repository: openfrontio/OpenFrontIO

Length of output: 49


🏁 Script executed:

# Look for all user-visible strings in the file
rg -A 3 -B 3 'title=|placeholder=|textContent' src/client/ColorInput.ts

Repository: openfrontio/OpenFrontIO

Length of output: 938


🏁 Script executed:

# Check the resources/lang/en.json for existing translations
head -n 50 resources/lang/en.json

Repository: openfrontio/OpenFrontIO

Length of output: 1452


🏁 Script executed:

# Look for how mode cycling button is implemented and attached
rg -A 10 "onModeChange|modeButton|mode-cycle" src/client/ColorInput.ts

Repository: openfrontio/OpenFrontIO

Length of output: 49


🏁 Script executed:

# Check the modeLabel() method to see if it returns localized text
rg -A 5 "modeLabel" src/client/ColorInput.ts

Repository: openfrontio/OpenFrontIO

Length of output: 715


🏁 Script executed:

# Check how the file is structured - parent container for button and input
rg -B 15 "color-input-picker" src/client/ColorInput.ts | head -n 30

Repository: openfrontio/OpenFrontIO

Length of output: 694


🏁 Script executed:

# Verify the exact parent container structure
ast-grep --pattern 'render() {
  $$$
  <button
    id="color-input-btn"
    $$$
  >
  $$$
  </button>
  $$$
}'

Repository: openfrontio/OpenFrontIO

Length of output: 49


Color picker overlay blocks mode-cycle button and text lacks translations.

In custom mode, the transparent color input (w-full h-1 + height:100%) covers the button underneath, preventing clicks to cycle modes. Additionally, user-visible text ("Custom", "Team", "Random", "Pick your territory color", "Color Mode...") must use translateText() with entries in resources/lang/en.json per guidelines.

Suggested fix for overlay
-                class="absolute bottom-0 left-0 w-full h-1 opacity-0 cursor-pointer"
-                style="height:100%;top:0;"
+                class="absolute inset-x-0 top-1 mx-auto w-7 h-7 opacity-0 cursor-pointer"

Also refactor modeLabel() to use translateText() and add translation keys to language file.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<!-- Hidden native color picker — only shown in custom mode -->
${this.mode === "custom"
? html`
<input
type="color"
id="color-input-picker"
.value=${this.primaryColor}
@input=${this.onColorChange}
title="Pick your territory color"
class="absolute bottom-0 left-0 w-full h-1 opacity-0 cursor-pointer"
style="height:100%;top:0;"
/>
${this.mode === "custom"
? html`
<input
type="color"
id="color-input-picker"
.value=${this.primaryColor}
`@input`=${this.onColorChange}
title="Pick your territory color"
class="absolute inset-x-0 top-1 mx-auto w-7 h-7 opacity-0 cursor-pointer"
/>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/ColorInput.ts` around lines 91 - 102, The transparent color <input
id="color-input-picker"> is currently stretched over the whole control (class
"w-full h-1" + style "height:100%") and blocks clicks to the mode-cycle button;
restrict its hit area to only the color swatch (make it a small, absolutely
positioned element over the swatch rather than full width, or adjust CSS to
remove full-width/100% height) and ensure it uses the existing onColorChange
handler. Also refactor the modeLabel() function to call translateText() for
user-facing strings and add corresponding keys (e.g. "colorMode.custom",
"colorMode.team", "colorMode.random", "colorMode.pickTooltip",
"colorMode.label") to resources/lang/en.json so "Custom", "Team", "Random",
"Pick your territory color", and "Color Mode..." all come from translations.

`
: ""}
</div>
`;
}
}
13 changes: 12 additions & 1 deletion src/client/components/PlayPage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { assetUrl } from "../../core/AssetUrls";
import "../ColorInput";
import "./NewsBox";

@customElement("play-page")
Expand Down Expand Up @@ -91,6 +92,11 @@ export class PlayPage extends LitElement {
adaptive-size
class="shrink-0 lg:hidden"
></pattern-input>
<!-- Color mode button (mobile) -->
<color-input
id="color-input-mobile"
class="shrink-0 lg:hidden h-10 w-10"
></color-input>
<flag-input
id="flag-input-mobile"
show-select-label
Expand All @@ -99,13 +105,18 @@ export class PlayPage extends LitElement {
</div>
</div>

<!-- Skin + flag: right col -->
<!-- Skin + Color + Flag: right col (desktop) -->
<div class="hidden lg:flex h-[60px] gap-2">
<pattern-input
id="pattern-input-desktop"
show-select-label
class="flex-1 h-full"
></pattern-input>
<!-- Color mode button (desktop) — sits right next to skin -->
<color-input
id="color-input-desktop"
class="h-full w-[60px]"
></color-input>
<flag-input
id="flag-input-desktop"
show-select-label
Expand Down
53 changes: 53 additions & 0 deletions src/client/components/baseComponents/setting/SettingColor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators.js";

@customElement("setting-color")
export class SettingColor extends LitElement {
@property() label = "Color";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Default label should be localized.

The default visible label "Color" is hardcoded.

As per coding guidelines: src/client/**/*.{ts,tsx}: All user-visible text must go through translateText() function with corresponding entries in resources/lang/en.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/components/baseComponents/setting/SettingColor.ts` at line 6, The
default visible label on the SettingColor component is hardcoded as "Color";
change the `@property`() label initializer in SettingColor (the label property) to
call translateText(...) instead (e.g., translateText('setting.color')) and add
the matching key/value ("setting.color": "Color") to resources/lang/en.json so
all user-visible text is localized; use the translateText function name and
ensure the key follows existing naming conventions.

@property() description = "";
@property() value = "#000000";

createRenderRoot() {
return this;
}

private handleChange(e: Event) {
const input = e.target as HTMLInputElement;
this.value = input.value;

// Dispatch an event so the parent component can listen to it
this.dispatchEvent(new CustomEvent("change", {
detail: { value: this.value },
bubbles: true,
composed: true
}));
}

render() {
return html`
<label
class="flex flex-row items-center justify-between w-full p-4 bg-white/5 border border-white/10 rounded-xl hover:bg-white/10 transition-all gap-4 cursor-pointer"
>
<div class="flex flex-col flex-1 min-w-0 mr-4">
<div class="text-white font-bold text-base block mb-1">
${this.label}
</div>
<div class="text-white/50 text-sm leading-snug">
${this.description}
</div>
</div>

<div class="relative shrink-0 flex items-center gap-2">
<div class="text-xs font-mono text-white/50">${this.value}</div>
<input
type="color"
class="h-8 w-8 cursor-pointer rounded-md bg-transparent border-0 p-0"
.value=${this.value}
@input=${this.handleChange}
@change=${this.handleChange}
/>
</div>
</label>
`;
}
}
Loading
Loading