[Bug][Refactor] fix username-finder issues + code & visual improvements (#4055)
* fix username-finder issues & refactor `login-form-ui-handler` - reduce redundancy - add hover effect for interactable game objects - add error handler for "No save files found!" - Make user finder errors support i18n * add `disableInteractive` to mockContainer
This commit is contained in:
parent
39f3572c1b
commit
f3bdaa12ca
|
@ -51,5 +51,7 @@
|
||||||
"renamePokemon": "Rename Pokémon",
|
"renamePokemon": "Rename Pokémon",
|
||||||
"rename": "Rename",
|
"rename": "Rename",
|
||||||
"nickname": "Nickname",
|
"nickname": "Nickname",
|
||||||
"errorServerDown": "Oops! There was an issue contacting the server.\n\nYou may leave this window open,\nthe game will automatically reconnect."
|
"errorServerDown": "Oops! There was an issue contacting the server.\n\nYou may leave this window open,\nthe game will automatically reconnect.",
|
||||||
|
"noSaves": "You don't have any save files on record!",
|
||||||
|
"tooManySaves": "You have too many save files on record!"
|
||||||
}
|
}
|
|
@ -208,4 +208,5 @@ export default class MockContainer implements MockGameObject {
|
||||||
return this.list;
|
return this.list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disableInteractive = vi.fn();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,21 @@ import { addTextObject, TextStyle } from "./text";
|
||||||
import { addWindow } from "./ui-theme";
|
import { addWindow } from "./ui-theme";
|
||||||
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
|
|
||||||
|
interface BuildInteractableImageOpts {
|
||||||
|
scale?: number;
|
||||||
|
x?: number;
|
||||||
|
y?: number;
|
||||||
|
origin?: { x: number; y: number };
|
||||||
|
}
|
||||||
|
|
||||||
export default class LoginFormUiHandler extends FormModalUiHandler {
|
export default class LoginFormUiHandler extends FormModalUiHandler {
|
||||||
|
private readonly ERR_USERNAME: string = "invalid username";
|
||||||
|
private readonly ERR_PASSWORD: string = "invalid password";
|
||||||
|
private readonly ERR_ACCOUNT_EXIST: string = "account doesn't exist";
|
||||||
|
private readonly ERR_PASSWORD_MATCH: string = "password doesn't match";
|
||||||
|
private readonly ERR_NO_SAVES: string = "No save files found";
|
||||||
|
private readonly ERR_TOO_MANY_SAVES: string = "Too many save files found";
|
||||||
|
|
||||||
private googleImage: Phaser.GameObjects.Image;
|
private googleImage: Phaser.GameObjects.Image;
|
||||||
private discordImage: Phaser.GameObjects.Image;
|
private discordImage: Phaser.GameObjects.Image;
|
||||||
private usernameInfoImage: Phaser.GameObjects.Image;
|
private usernameInfoImage: Phaser.GameObjects.Image;
|
||||||
|
@ -21,8 +35,23 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
setup(): void {
|
setup(): void {
|
||||||
|
|
||||||
super.setup();
|
super.setup();
|
||||||
|
this.buildExternalPartyContainer();
|
||||||
|
|
||||||
|
this.infoContainer = this.scene.add.container(0, 0);
|
||||||
|
|
||||||
|
this.usernameInfoImage = this.buildInteractableImage("settings_icon", "username-info-icon", {
|
||||||
|
x: 20,
|
||||||
|
scale: 0.5
|
||||||
|
});
|
||||||
|
|
||||||
|
this.infoContainer.add(this.usernameInfoImage);
|
||||||
|
this.getUi().add(this.infoContainer);
|
||||||
|
this.infoContainer.setVisible(false);
|
||||||
|
this.infoContainer.disableInteractive();
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildExternalPartyContainer() {
|
||||||
this.externalPartyContainer = this.scene.add.container(0, 0);
|
this.externalPartyContainer = this.scene.add.container(0, 0);
|
||||||
this.externalPartyContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 12, this.scene.game.canvas.height / 12), Phaser.Geom.Rectangle.Contains);
|
this.externalPartyContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 12, this.scene.game.canvas.height / 12), Phaser.Geom.Rectangle.Contains);
|
||||||
this.externalPartyTitle = addTextObject(this.scene, 0, 4, "", TextStyle.SETTINGS_LABEL);
|
this.externalPartyTitle = addTextObject(this.scene, 0, 4, "", TextStyle.SETTINGS_LABEL);
|
||||||
|
@ -31,23 +60,8 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
|
||||||
this.externalPartyContainer.add(this.externalPartyBg);
|
this.externalPartyContainer.add(this.externalPartyBg);
|
||||||
this.externalPartyContainer.add(this.externalPartyTitle);
|
this.externalPartyContainer.add(this.externalPartyTitle);
|
||||||
|
|
||||||
this.infoContainer = this.scene.add.container(0, 0);
|
this.googleImage = this.buildInteractableImage("google", "google-icon");
|
||||||
this.infoContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 12, this.scene.game.canvas.height / 12), Phaser.Geom.Rectangle.Contains);
|
this.discordImage = this.buildInteractableImage("discord", "discord-icon");
|
||||||
|
|
||||||
const googleImage = this.scene.add.image(0, 0, "google");
|
|
||||||
googleImage.setOrigin(0, 0);
|
|
||||||
googleImage.setScale(0.07);
|
|
||||||
googleImage.setInteractive();
|
|
||||||
googleImage.setName("google-icon");
|
|
||||||
this.googleImage = googleImage;
|
|
||||||
|
|
||||||
const discordImage = this.scene.add.image(20, 0, "discord");
|
|
||||||
discordImage.setOrigin(0, 0);
|
|
||||||
discordImage.setScale(0.07);
|
|
||||||
discordImage.setInteractive();
|
|
||||||
discordImage.setName("discord-icon");
|
|
||||||
|
|
||||||
this.discordImage = discordImage;
|
|
||||||
|
|
||||||
this.externalPartyContainer.add(this.googleImage);
|
this.externalPartyContainer.add(this.googleImage);
|
||||||
this.externalPartyContainer.add(this.discordImage);
|
this.externalPartyContainer.add(this.discordImage);
|
||||||
|
@ -55,59 +69,52 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
|
||||||
this.externalPartyContainer.add(this.googleImage);
|
this.externalPartyContainer.add(this.googleImage);
|
||||||
this.externalPartyContainer.add(this.discordImage);
|
this.externalPartyContainer.add(this.discordImage);
|
||||||
this.externalPartyContainer.setVisible(false);
|
this.externalPartyContainer.setVisible(false);
|
||||||
|
|
||||||
const usernameInfoImage = this.scene.add.image(20, 0, "settings_icon");
|
|
||||||
usernameInfoImage.setOrigin(0, 0);
|
|
||||||
usernameInfoImage.setScale(0.5);
|
|
||||||
usernameInfoImage.setInteractive();
|
|
||||||
usernameInfoImage.setName("username-info-icon");
|
|
||||||
this.usernameInfoImage = usernameInfoImage;
|
|
||||||
|
|
||||||
this.infoContainer.add(this.usernameInfoImage);
|
|
||||||
this.getUi().add(this.infoContainer);
|
|
||||||
this.infoContainer.setVisible(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getModalTitle(config?: ModalConfig): string {
|
override getModalTitle(_config?: ModalConfig): string {
|
||||||
return i18next.t("menu:login");
|
return i18next.t("menu:login");
|
||||||
}
|
}
|
||||||
|
|
||||||
getFields(config?: ModalConfig): string[] {
|
override getFields(_config?: ModalConfig): string[] {
|
||||||
return [ i18next.t("menu:username"), i18next.t("menu:password") ];
|
return [ i18next.t("menu:username"), i18next.t("menu:password") ];
|
||||||
}
|
}
|
||||||
|
|
||||||
getWidth(config?: ModalConfig): number {
|
override getWidth(_config?: ModalConfig): number {
|
||||||
return 160;
|
return 160;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMargin(config?: ModalConfig): [number, number, number, number] {
|
override getMargin(_config?: ModalConfig): [number, number, number, number] {
|
||||||
return [ 0, 0, 48, 0 ];
|
return [ 0, 0, 48, 0 ];
|
||||||
}
|
}
|
||||||
|
|
||||||
getButtonLabels(config?: ModalConfig): string[] {
|
override getButtonLabels(_config?: ModalConfig): string[] {
|
||||||
return [ i18next.t("menu:login"), i18next.t("menu:register")];
|
return [ i18next.t("menu:login"), i18next.t("menu:register")];
|
||||||
}
|
}
|
||||||
|
|
||||||
getReadableErrorMessage(error: string): string {
|
override getReadableErrorMessage(error: string): string {
|
||||||
const colonIndex = error?.indexOf(":");
|
const colonIndex = error?.indexOf(":");
|
||||||
if (colonIndex > 0) {
|
if (colonIndex > 0) {
|
||||||
error = error.slice(0, colonIndex);
|
error = error.slice(0, colonIndex);
|
||||||
}
|
}
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case "invalid username":
|
case this.ERR_USERNAME:
|
||||||
return i18next.t("menu:invalidLoginUsername");
|
return i18next.t("menu:invalidLoginUsername");
|
||||||
case "invalid password":
|
case this.ERR_PASSWORD:
|
||||||
return i18next.t("menu:invalidLoginPassword");
|
return i18next.t("menu:invalidLoginPassword");
|
||||||
case "account doesn't exist":
|
case this.ERR_ACCOUNT_EXIST:
|
||||||
return i18next.t("menu:accountNonExistent");
|
return i18next.t("menu:accountNonExistent");
|
||||||
case "password doesn't match":
|
case this.ERR_PASSWORD_MATCH:
|
||||||
return i18next.t("menu:unmatchingPassword");
|
return i18next.t("menu:unmatchingPassword");
|
||||||
|
case this.ERR_NO_SAVES:
|
||||||
|
return i18next.t("menu:noSaves");
|
||||||
|
case this.ERR_TOO_MANY_SAVES:
|
||||||
|
return i18next.t("menu:tooManySaves");
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.getReadableErrorMessage(error);
|
return super.getReadableErrorMessage(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
show(args: any[]): boolean {
|
override show(args: any[]): boolean {
|
||||||
if (super.show(args)) {
|
if (super.show(args)) {
|
||||||
|
|
||||||
const config = args[0] as ModalConfig;
|
const config = args[0] as ModalConfig;
|
||||||
|
@ -148,17 +155,16 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
override clear() {
|
||||||
super.clear();
|
super.clear();
|
||||||
this.externalPartyContainer.setVisible(false);
|
this.externalPartyContainer.setVisible(false);
|
||||||
this.infoContainer.setVisible(false);
|
this.infoContainer.setVisible(false);
|
||||||
|
this.setMouseCursorStyle("default"); //reset cursor
|
||||||
|
|
||||||
this.discordImage.off("pointerdown");
|
[this.discordImage, this.googleImage, this.usernameInfoImage].forEach((img) => img.off("pointerdown"));
|
||||||
this.googleImage.off("pointerdown");
|
|
||||||
this.usernameInfoImage.off("pointerdown");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
processExternalProvider(config: ModalConfig) : void {
|
private processExternalProvider(config: ModalConfig) : void {
|
||||||
this.externalPartyTitle.setText(i18next.t("menu:orUse") ?? "");
|
this.externalPartyTitle.setText(i18next.t("menu:orUse") ?? "");
|
||||||
this.externalPartyTitle.setX(20+this.externalPartyTitle.text.length);
|
this.externalPartyTitle.setX(20+this.externalPartyTitle.text.length);
|
||||||
this.externalPartyTitle.setVisible(true);
|
this.externalPartyTitle.setVisible(true);
|
||||||
|
@ -205,6 +211,7 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
|
||||||
label: dataKeys[i].replace(keyToFind, ""),
|
label: dataKeys[i].replace(keyToFind, ""),
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.scene.ui.revertMode();
|
this.scene.ui.revertMode();
|
||||||
|
this.infoContainer.disableInteractive();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -213,8 +220,13 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
|
||||||
options: options,
|
options: options,
|
||||||
delay: 1000
|
delay: 1000
|
||||||
});
|
});
|
||||||
|
this.infoContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width, this.scene.game.canvas.height), Phaser.Geom.Rectangle.Contains);
|
||||||
} else {
|
} else {
|
||||||
return onFail("You have too many save files to use this");
|
if (dataKeys.length > 2) {
|
||||||
|
return onFail(this.ERR_TOO_MANY_SAVES);
|
||||||
|
} else {
|
||||||
|
return onFail(this.ERR_NO_SAVES);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -236,4 +248,21 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
|
||||||
alpha: 1
|
alpha: 1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildInteractableImage(texture: string, name: string, opts: BuildInteractableImageOpts = {}) {
|
||||||
|
const {
|
||||||
|
scale = 0.07,
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
origin = { x: 0, y: 0 }
|
||||||
|
} = opts;
|
||||||
|
const img = this.scene.add.image(x, y, texture);
|
||||||
|
img.setName(name);
|
||||||
|
img.setOrigin(origin.x, origin.y);
|
||||||
|
img.setScale(scale);
|
||||||
|
img.setInteractive();
|
||||||
|
this.addInteractionHoverEffect(img);
|
||||||
|
|
||||||
|
return img;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,29 +57,35 @@ export abstract class ModalUiHandler extends UiHandler {
|
||||||
|
|
||||||
const buttonLabels = this.getButtonLabels();
|
const buttonLabels = this.getButtonLabels();
|
||||||
|
|
||||||
const buttonTopMargin = this.getButtonTopMargin();
|
|
||||||
|
|
||||||
for (const label of buttonLabels) {
|
for (const label of buttonLabels) {
|
||||||
const buttonLabel = addTextObject(this.scene, 0, 8, label, TextStyle.TOOLTIP_CONTENT);
|
this.addButton(label);
|
||||||
buttonLabel.setOrigin(0.5, 0.5);
|
|
||||||
|
|
||||||
const buttonBg = addWindow(this.scene, 0, 0, buttonLabel.getBounds().width + 8, 16, false, false, 0, 0, WindowVariant.THIN);
|
|
||||||
buttonBg.setOrigin(0.5, 0);
|
|
||||||
buttonBg.setInteractive(new Phaser.Geom.Rectangle(0, 0, buttonBg.width, buttonBg.height), Phaser.Geom.Rectangle.Contains);
|
|
||||||
|
|
||||||
const buttonContainer = this.scene.add.container(0, buttonTopMargin);
|
|
||||||
|
|
||||||
this.buttonBgs.push(buttonBg);
|
|
||||||
this.buttonContainers.push(buttonContainer);
|
|
||||||
|
|
||||||
buttonContainer.add(buttonBg);
|
|
||||||
buttonContainer.add(buttonLabel);
|
|
||||||
this.modalContainer.add(buttonContainer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.modalContainer.setVisible(false);
|
this.modalContainer.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addButton(label: string) {
|
||||||
|
const buttonTopMargin = this.getButtonTopMargin();
|
||||||
|
const buttonLabel = addTextObject(this.scene, 0, 8, label, TextStyle.TOOLTIP_CONTENT);
|
||||||
|
buttonLabel.setOrigin(0.5, 0.5);
|
||||||
|
|
||||||
|
const buttonBg = addWindow(this.scene, 0, 0, buttonLabel.getBounds().width + 8, 16, false, false, 0, 0, WindowVariant.THIN);
|
||||||
|
buttonBg.setOrigin(0.5, 0);
|
||||||
|
buttonBg.setInteractive(new Phaser.Geom.Rectangle(0, 0, buttonBg.width, buttonBg.height), Phaser.Geom.Rectangle.Contains);
|
||||||
|
|
||||||
|
const buttonContainer = this.scene.add.container(0, buttonTopMargin);
|
||||||
|
|
||||||
|
this.buttonBgs.push(buttonBg);
|
||||||
|
this.buttonContainers.push(buttonContainer);
|
||||||
|
|
||||||
|
buttonContainer.add(buttonBg);
|
||||||
|
buttonContainer.add(buttonLabel);
|
||||||
|
|
||||||
|
this.addInteractionHoverEffect(buttonBg);
|
||||||
|
|
||||||
|
this.modalContainer.add(buttonContainer);
|
||||||
|
}
|
||||||
|
|
||||||
show(args: any[]): boolean {
|
show(args: any[]): boolean {
|
||||||
if (args.length >= 1 && "buttonActions" in args[0]) {
|
if (args.length >= 1 && "buttonActions" in args[0]) {
|
||||||
super.show(args);
|
super.show(args);
|
||||||
|
@ -135,4 +141,20 @@ export abstract class ModalUiHandler extends UiHandler {
|
||||||
|
|
||||||
this.buttonBgs.map(bg => bg.off("pointerdown"));
|
this.buttonBgs.map(bg => bg.off("pointerdown"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a hover effect to a game object which changes the cursor to a `pointer` and tints it slighly
|
||||||
|
* @param gameObject the game object to add hover events/effects to
|
||||||
|
*/
|
||||||
|
protected addInteractionHoverEffect(gameObject: Phaser.GameObjects.Image | Phaser.GameObjects.NineSlice | Phaser.GameObjects.Sprite) {
|
||||||
|
gameObject.on("pointerover", () => {
|
||||||
|
this.setMouseCursorStyle("pointer");
|
||||||
|
gameObject.setTint(0xbbbbbb);
|
||||||
|
});
|
||||||
|
|
||||||
|
gameObject.on("pointerout", () => {
|
||||||
|
this.setMouseCursorStyle("default");
|
||||||
|
gameObject.clearTint();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,15 @@ export default abstract class UiHandler {
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the style of the mouse cursor.
|
||||||
|
* @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/cursor}
|
||||||
|
* @param cursorStyle cursor style to apply
|
||||||
|
*/
|
||||||
|
protected setMouseCursorStyle(cursorStyle: "pointer" | "default") {
|
||||||
|
this.scene.input.manager.canvas.style.cursor = cursorStyle;
|
||||||
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.active = false;
|
this.active = false;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue