mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-03 18:30:36 +00:00
store & reference pattern by name (#1766)
## Description: Store pattern by name instead of value. The worker replaces the pattern name with it's base64 when joining. This ensures the client & server are never out of sync after patterns are updated. * removed resizeObserver on the territory modal, it was causing some race conditions, and the modal is not resizable so it's unnecessary. * Moved PatternSchema to CosmeticSchema ## 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 - [x] I have read and accepted the CLA agreement (only required once). ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
This commit is contained in:
@@ -11,6 +11,8 @@ import "./components/Maps";
|
||||
import { handlePurchase, patterns } from "./Cosmetics";
|
||||
import { translateText } from "./Utils";
|
||||
|
||||
const BUTTON_WIDTH = 150;
|
||||
|
||||
@customElement("territory-patterns-modal")
|
||||
export class TerritoryPatternsModal extends LitElement {
|
||||
@query("o-modal") private modalEl!: HTMLElement & {
|
||||
@@ -19,22 +21,16 @@ export class TerritoryPatternsModal extends LitElement {
|
||||
};
|
||||
|
||||
public previewButton: HTMLElement | null = null;
|
||||
public buttonWidth: number = 150;
|
||||
|
||||
@state() private selectedPattern: string | undefined;
|
||||
@state() private selectedPattern: Pattern | undefined;
|
||||
|
||||
@state() private lockedPatterns: string[] = [];
|
||||
@state() private lockedReasons: Record<string, string> = {};
|
||||
@state() private hoveredPattern: Pattern | null = null;
|
||||
@state() private hoverPosition = { x: 0, y: 0 };
|
||||
|
||||
@state() private keySequence: string[] = [];
|
||||
@state() private showChocoPattern = false;
|
||||
|
||||
private patterns: Pattern[] = [];
|
||||
private me: UserMeResponse | null = null;
|
||||
|
||||
public resizeObserver: ResizeObserver;
|
||||
private patterns: Map<string, Pattern> = new Map();
|
||||
|
||||
private userSettings: UserSettings = new UserSettings();
|
||||
|
||||
@@ -47,17 +43,11 @@ export class TerritoryPatternsModal extends LitElement {
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
window.addEventListener("keydown", this.handleKeyDown);
|
||||
this.selectedPattern = this.userSettings.getSelectedPattern();
|
||||
this.updateComplete.then(() => {
|
||||
const containers = this.renderRoot.querySelectorAll(".preview-container");
|
||||
if (this.resizeObserver) {
|
||||
containers.forEach((container) =>
|
||||
this.resizeObserver.observe(container),
|
||||
);
|
||||
}
|
||||
this.updatePreview();
|
||||
this.open().then(() => {
|
||||
this.updatePreview();
|
||||
});
|
||||
});
|
||||
this.open();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
@@ -67,7 +57,10 @@ export class TerritoryPatternsModal extends LitElement {
|
||||
|
||||
async onUserMe(userMeResponse: UserMeResponse | null) {
|
||||
this.patterns = await patterns(userMeResponse);
|
||||
this.me = userMeResponse;
|
||||
const storedPatternName = this.userSettings.getSelectedPatternName();
|
||||
if (storedPatternName) {
|
||||
this.selectedPattern = this.patterns.get(storedPatternName);
|
||||
}
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
@@ -123,7 +116,7 @@ export class TerritoryPatternsModal extends LitElement {
|
||||
}
|
||||
|
||||
private renderPatternButton(pattern: Pattern): TemplateResult {
|
||||
const isSelected = this.selectedPattern === pattern.pattern;
|
||||
const isSelected = this.selectedPattern?.name === pattern.name;
|
||||
|
||||
return html`
|
||||
<div style="flex: 0 1 calc(25% - 1rem); max-width: calc(25% - 1rem);">
|
||||
@@ -134,7 +127,7 @@ export class TerritoryPatternsModal extends LitElement {
|
||||
: "bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"}
|
||||
${pattern.product !== null ? "opacity-50 cursor-not-allowed" : ""}"
|
||||
@click=${() =>
|
||||
pattern.product === null && this.selectPattern(pattern.pattern)}
|
||||
pattern.product === null && this.selectPattern(pattern)}
|
||||
@mouseenter=${(e: MouseEvent) => this.handleMouseEnter(pattern, e)}
|
||||
@mousemove=${(e: MouseEvent) => this.handleMouseMove(e)}
|
||||
@mouseleave=${() => this.handleMouseLeave()}
|
||||
@@ -157,8 +150,8 @@ export class TerritoryPatternsModal extends LitElement {
|
||||
>
|
||||
${this.renderPatternPreview(
|
||||
pattern.pattern,
|
||||
this.buttonWidth,
|
||||
this.buttonWidth,
|
||||
BUTTON_WIDTH,
|
||||
BUTTON_WIDTH,
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
@@ -182,8 +175,8 @@ export class TerritoryPatternsModal extends LitElement {
|
||||
|
||||
private renderPatternGrid(): TemplateResult {
|
||||
const buttons: TemplateResult[] = [];
|
||||
for (const pattern of this.patterns) {
|
||||
if (!this.showChocoPattern && pattern.name === "choco") continue;
|
||||
for (const [name, pattern] of this.patterns) {
|
||||
if (!this.showChocoPattern && name === "choco") continue;
|
||||
|
||||
const result = this.renderPatternButton(pattern);
|
||||
buttons.push(result);
|
||||
@@ -218,7 +211,7 @@ export class TerritoryPatternsModal extends LitElement {
|
||||
overflow: hidden;
|
||||
"
|
||||
>
|
||||
${this.renderBlankPreview(this.buttonWidth, this.buttonWidth)}
|
||||
${this.renderBlankPreview(BUTTON_WIDTH, BUTTON_WIDTH)}
|
||||
</div>
|
||||
</button>
|
||||
${buttons}
|
||||
@@ -239,22 +232,31 @@ export class TerritoryPatternsModal extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
public open() {
|
||||
this.modalEl?.open();
|
||||
window.addEventListener("keydown", this.handleKeyDown);
|
||||
public async open() {
|
||||
this.isActive = true;
|
||||
this.requestUpdate();
|
||||
|
||||
// Wait for the DOM to be updated and the o-modal element to be available
|
||||
await this.updateComplete;
|
||||
|
||||
// Now modalEl should be available
|
||||
if (this.modalEl) {
|
||||
this.modalEl.open();
|
||||
} else {
|
||||
console.warn("modalEl is still null after updateComplete");
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", this.handleKeyDown);
|
||||
}
|
||||
|
||||
public close() {
|
||||
this.isActive = false;
|
||||
this.modalEl?.close();
|
||||
window.removeEventListener("keydown", this.handleKeyDown);
|
||||
this.resizeObserver?.disconnect();
|
||||
this.isActive = false;
|
||||
}
|
||||
|
||||
private selectPattern(pattern: string | undefined) {
|
||||
this.userSettings.setSelectedPattern(pattern);
|
||||
private selectPattern(pattern: Pattern | undefined) {
|
||||
this.userSettings.setSelectedPatternName(pattern?.name);
|
||||
this.selectedPattern = pattern;
|
||||
this.updatePreview();
|
||||
this.close();
|
||||
@@ -314,24 +316,14 @@ export class TerritoryPatternsModal extends LitElement {
|
||||
|
||||
public updatePreview() {
|
||||
if (this.previewButton === null) return;
|
||||
const preview = this.renderPatternPreview(this.selectedPattern, 48, 48);
|
||||
const preview = this.renderPatternPreview(
|
||||
this.selectedPattern?.pattern,
|
||||
48,
|
||||
48,
|
||||
);
|
||||
render(preview, this.previewButton);
|
||||
}
|
||||
|
||||
private setLockedPatterns(lockedPatterns: string[], reason: string) {
|
||||
this.lockedPatterns = [...this.lockedPatterns, ...lockedPatterns];
|
||||
this.lockedReasons = {
|
||||
...this.lockedReasons,
|
||||
...lockedPatterns.reduce(
|
||||
(acc, key) => {
|
||||
acc[key] = reason;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
private handleMouseEnter(pattern: Pattern, event: MouseEvent) {
|
||||
if (pattern.product !== null) {
|
||||
this.hoveredPattern = pattern;
|
||||
@@ -360,13 +352,21 @@ export function generatePreviewDataUrl(
|
||||
height?: number,
|
||||
): string {
|
||||
pattern ??= DEFAULT_PATTERN_B64;
|
||||
const patternLookupKey = `${pattern}-${width}-${height}`;
|
||||
|
||||
if (patternCache.has(pattern)) {
|
||||
return patternCache.get(pattern)!;
|
||||
if (patternCache.has(patternLookupKey)) {
|
||||
return patternCache.get(patternLookupKey)!;
|
||||
}
|
||||
|
||||
// Calculate canvas size
|
||||
const decoder = new PatternDecoder(pattern, base64url.decode);
|
||||
let decoder: PatternDecoder;
|
||||
try {
|
||||
decoder = new PatternDecoder(pattern, base64url.decode);
|
||||
} catch (e) {
|
||||
console.error("Error decoding pattern", e);
|
||||
return "";
|
||||
}
|
||||
|
||||
const scaledWidth = decoder.scaledWidth();
|
||||
const scaledHeight = decoder.scaledHeight();
|
||||
|
||||
@@ -403,6 +403,6 @@ export function generatePreviewDataUrl(
|
||||
// Create a data URL
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
const dataUrl = canvas.toDataURL("image/png");
|
||||
patternCache.set(pattern, dataUrl);
|
||||
patternCache.set(patternLookupKey, dataUrl);
|
||||
return dataUrl;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user