mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-12-29 19:32:26 +08:00
refactor: Use class for validateWithIndicator (@fehmer) (#7151)
In preparation for the ElementWithUtils refactoring
This commit is contained in:
parent
227a522024
commit
71b5d22159
7 changed files with 89 additions and 97 deletions
|
|
@ -142,80 +142,75 @@ export type ValidationOptions<T> = (T extends string
|
|||
callback?: (result: ValidationResult) => void;
|
||||
};
|
||||
|
||||
export type ValidatedHtmlInputElement = HTMLInputElement & {
|
||||
getValidationResult: () => ValidationResult;
|
||||
setValue: (val: string | null) => void;
|
||||
triggerValidation: () => void;
|
||||
};
|
||||
/**
|
||||
* adds an 'InputIndicator` to the given `inputElement` and updates its status depending on the given validation
|
||||
* @param inputElement
|
||||
* @param options
|
||||
*/
|
||||
export function validateWithIndicator<T>(
|
||||
inputElement: HTMLInputElement,
|
||||
options: ValidationOptions<T>
|
||||
): ValidatedHtmlInputElement {
|
||||
//use indicator
|
||||
const indicator = new InputIndicator(inputElement, {
|
||||
success: {
|
||||
icon: "fa-check",
|
||||
level: 1,
|
||||
},
|
||||
failed: {
|
||||
icon: "fa-times",
|
||||
level: -1,
|
||||
},
|
||||
warning: {
|
||||
icon: "fa-exclamation-triangle",
|
||||
level: 1,
|
||||
},
|
||||
checking: {
|
||||
icon: "fa-circle-notch",
|
||||
spinIcon: true,
|
||||
level: 0,
|
||||
},
|
||||
});
|
||||
|
||||
let currentStatus: ValidationResult = {
|
||||
export class ValidatedHtmlInputElement<T = string> {
|
||||
public native: HTMLInputElement;
|
||||
private indicator: InputIndicator;
|
||||
private currentStatus: ValidationResult = {
|
||||
status: "checking",
|
||||
};
|
||||
const callback = (result: ValidationResult): void => {
|
||||
currentStatus = result;
|
||||
if (result.status === "failed" || result.status === "warning") {
|
||||
indicator.show(result.status, result.errorMessage);
|
||||
} else {
|
||||
indicator.show(result.status);
|
||||
}
|
||||
options.callback?.(result);
|
||||
};
|
||||
|
||||
const handler = createInputEventHandler(
|
||||
callback,
|
||||
options,
|
||||
"inputValueConvert" in options ? options.inputValueConvert : undefined
|
||||
);
|
||||
constructor(inputElement: HTMLInputElement, options: ValidationOptions<T>) {
|
||||
this.native = inputElement;
|
||||
|
||||
inputElement.addEventListener("input", handler);
|
||||
this.indicator = new InputIndicator(inputElement, {
|
||||
success: {
|
||||
icon: "fa-check",
|
||||
level: 1,
|
||||
},
|
||||
failed: {
|
||||
icon: "fa-times",
|
||||
level: -1,
|
||||
},
|
||||
warning: {
|
||||
icon: "fa-exclamation-triangle",
|
||||
level: 1,
|
||||
},
|
||||
checking: {
|
||||
icon: "fa-circle-notch",
|
||||
spinIcon: true,
|
||||
level: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const result = inputElement as ValidatedHtmlInputElement;
|
||||
result.getValidationResult = () => {
|
||||
return currentStatus;
|
||||
};
|
||||
result.setValue = (val: string | null) => {
|
||||
inputElement.value = val ?? "";
|
||||
const callback = (result: ValidationResult): void => {
|
||||
this.currentStatus = result;
|
||||
if (result.status === "failed" || result.status === "warning") {
|
||||
this.indicator.show(result.status, result.errorMessage);
|
||||
} else {
|
||||
this.indicator.show(result.status);
|
||||
}
|
||||
options.callback?.(result);
|
||||
};
|
||||
|
||||
const handler = createInputEventHandler(
|
||||
callback,
|
||||
options,
|
||||
"inputValueConvert" in options ? options.inputValueConvert : undefined
|
||||
);
|
||||
|
||||
inputElement.addEventListener("input", handler);
|
||||
}
|
||||
|
||||
getValidationResult(): ValidationResult {
|
||||
return this.currentStatus;
|
||||
}
|
||||
setValue(val: string | null): this {
|
||||
this.native.value = val ?? "";
|
||||
if (val === null) {
|
||||
indicator.hide();
|
||||
currentStatus = { status: "checking" };
|
||||
this.indicator.hide();
|
||||
this.currentStatus = { status: "checking" };
|
||||
} else {
|
||||
inputElement.dispatchEvent(new Event("input"));
|
||||
this.native.dispatchEvent(new Event("input"));
|
||||
}
|
||||
};
|
||||
result.triggerValidation = () => {
|
||||
inputElement.dispatchEvent(new Event("input"));
|
||||
};
|
||||
|
||||
return result;
|
||||
return this;
|
||||
}
|
||||
getValue(): string {
|
||||
return this.native.value;
|
||||
}
|
||||
triggerValidation(): void {
|
||||
this.native.dispatchEvent(new Event("input"));
|
||||
}
|
||||
}
|
||||
|
||||
export type ConfigInputOptions<K extends ConfigKey, T = ConfigType[K]> = {
|
||||
|
|
@ -260,7 +255,7 @@ export function handleConfigInput<T extends ConfigKey>({
|
|||
if (validation !== undefined) {
|
||||
const schema = ConfigSchema.shape[configName] as ZodType;
|
||||
|
||||
validateWithIndicator(input, {
|
||||
new ValidatedHtmlInputElement(input, {
|
||||
schema: validation.schema ? schema : undefined,
|
||||
//@ts-expect-error this is fine
|
||||
isValid: validation.isValid,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { getfpsLimit, fpsLimitSchema, setfpsLimit } from "../../anim";
|
||||
import { validateWithIndicator } from "../input-validation";
|
||||
import { ValidatedHtmlInputElement } from "../input-validation";
|
||||
import * as Notifications from "../notifications";
|
||||
|
||||
const section = document.querySelector(
|
||||
|
|
@ -10,7 +10,7 @@ const button = section.querySelector(
|
|||
"button[data-fpsLimit='native']"
|
||||
) as HTMLButtonElement;
|
||||
|
||||
const input = validateWithIndicator(
|
||||
const input = new ValidatedHtmlInputElement(
|
||||
section.querySelector('input[type="number"]') as HTMLInputElement,
|
||||
{
|
||||
schema: fpsLimitSchema,
|
||||
|
|
@ -24,7 +24,7 @@ export function update(): void {
|
|||
input.setValue(null);
|
||||
button.classList.add("active");
|
||||
} else {
|
||||
input.value = fpsLimit.toString();
|
||||
input.setValue(fpsLimit.toString());
|
||||
button.classList.remove("active");
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ function save(value: number): void {
|
|||
|
||||
function saveFromInput(): void {
|
||||
if (input.getValidationResult().status !== "success") return;
|
||||
const val = parseInt(input.value, 10);
|
||||
const val = parseInt(input.getValue(), 10);
|
||||
save(val);
|
||||
}
|
||||
|
||||
|
|
@ -47,10 +47,10 @@ button.addEventListener("click", () => {
|
|||
update();
|
||||
});
|
||||
|
||||
input.addEventListener("keypress", (e) => {
|
||||
input.native.addEventListener("keypress", (e) => {
|
||||
if (e.key === "Enter") {
|
||||
saveFromInput();
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener("focusout", (e) => saveFromInput());
|
||||
input.native.addEventListener("focusout", (e) => saveFromInput());
|
||||
|
|
|
|||
|
|
@ -21,10 +21,7 @@ import {
|
|||
} from "@monkeytype/schemas/configs";
|
||||
import { getDefaultConfig } from "../constants/default-config";
|
||||
import { SnapshotPreset } from "../constants/default-snapshot";
|
||||
import {
|
||||
ValidatedHtmlInputElement,
|
||||
validateWithIndicator,
|
||||
} from "../elements/input-validation";
|
||||
import { ValidatedHtmlInputElement } from "../elements/input-validation";
|
||||
|
||||
const state = {
|
||||
presetType: "full" as PresetType,
|
||||
|
|
@ -50,7 +47,7 @@ export function show(action: string, id?: string, name?: string): void {
|
|||
$("#editPresetModal .modal .text").addClass("hidden");
|
||||
addCheckBoxes();
|
||||
if (!presetNameEl) {
|
||||
presetNameEl = validateWithIndicator(
|
||||
presetNameEl = new ValidatedHtmlInputElement(
|
||||
document.querySelector(
|
||||
"#editPresetModal .modal input"
|
||||
) as HTMLInputElement,
|
||||
|
|
@ -64,7 +61,7 @@ export function show(action: string, id?: string, name?: string): void {
|
|||
$("#editPresetModal .modal .popupTitle").html("Add new preset");
|
||||
$("#editPresetModal .modal .submit").html(`add`);
|
||||
presetNameEl?.setValue(null);
|
||||
presetNameEl?.parentElement?.classList.remove("hidden");
|
||||
presetNameEl?.native.parentElement?.classList.remove("hidden");
|
||||
$("#editPresetModal .modal input").removeClass("hidden");
|
||||
$(
|
||||
"#editPresetModal .modal label.changePresetToCurrentCheckbox"
|
||||
|
|
@ -79,7 +76,7 @@ export function show(action: string, id?: string, name?: string): void {
|
|||
$("#editPresetModal .modal .popupTitle").html("Edit preset");
|
||||
$("#editPresetModal .modal .submit").html(`save`);
|
||||
presetNameEl?.setValue(name);
|
||||
presetNameEl?.parentElement?.classList.remove("hidden");
|
||||
presetNameEl?.native.parentElement?.classList.remove("hidden");
|
||||
|
||||
$("#editPresetModal .modal input").removeClass("hidden");
|
||||
$(
|
||||
|
|
@ -108,7 +105,7 @@ export function show(action: string, id?: string, name?: string): void {
|
|||
$("#editPresetModal .modal .inputs").addClass("hidden");
|
||||
$("#editPresetModal .modal .presetType").addClass("hidden");
|
||||
$("#editPresetModal .modal .presetNameTitle").addClass("hidden");
|
||||
presetNameEl?.parentElement?.classList.add("hidden");
|
||||
presetNameEl?.native.parentElement?.classList.add("hidden");
|
||||
}
|
||||
updateUI();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import * as Loader from "../elements/loader";
|
|||
import { subscribe as subscribeToSignUpEvent } from "../observables/google-sign-up-event";
|
||||
import AnimatedModal from "../utils/animated-modal";
|
||||
import { resetIgnoreAuthCallback } from "../firebase";
|
||||
import { validateWithIndicator } from "../elements/input-validation";
|
||||
import { ValidatedHtmlInputElement } from "../elements/input-validation";
|
||||
import { UserNameSchema } from "@monkeytype/schemas/users";
|
||||
import { remoteValidation } from "../utils/remote-validation";
|
||||
|
||||
|
|
@ -153,7 +153,7 @@ function disableInput(): void {
|
|||
nameInputEl.disabled = true;
|
||||
}
|
||||
|
||||
validateWithIndicator(nameInputEl, {
|
||||
new ValidatedHtmlInputElement(nameInputEl, {
|
||||
schema: UserNameSchema,
|
||||
isValid: remoteValidation(
|
||||
async (name) => Ape.users.getNameAvailability({ params: { name } }),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import * as CustomText from "../test/custom-text";
|
|||
import * as Notifications from "../elements/notifications";
|
||||
import * as CustomTextState from "../states/custom-text-name";
|
||||
import AnimatedModal, { ShowOptions } from "../utils/animated-modal";
|
||||
import { validateWithIndicator } from "../elements/input-validation";
|
||||
import { ValidatedHtmlInputElement } from "../elements/input-validation";
|
||||
import { z } from "zod";
|
||||
|
||||
type IncomingData = {
|
||||
|
|
@ -17,7 +17,7 @@ const state: State = {
|
|||
textToSave: [],
|
||||
};
|
||||
|
||||
const validatedInput = validateWithIndicator(
|
||||
const validatedInput = new ValidatedHtmlInputElement(
|
||||
$("#saveCustomTextModal .textName")[0] as HTMLInputElement,
|
||||
{
|
||||
debounceDelay: 500,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
UserEmailSchema,
|
||||
UserNameSchema,
|
||||
} from "@monkeytype/schemas/users";
|
||||
import { validateWithIndicator } from "../elements/input-validation";
|
||||
import { ValidatedHtmlInputElement } from "../elements/input-validation";
|
||||
import { isDevEnvironment } from "../utils/misc";
|
||||
import { z } from "zod";
|
||||
import { remoteValidation } from "../utils/remote-validation";
|
||||
|
|
@ -72,7 +72,7 @@ export function getSignupData(): SignupData | false {
|
|||
const nameInputEl = document.querySelector(
|
||||
".page.pageLogin .register.side input.usernameInput"
|
||||
) as HTMLInputElement;
|
||||
validateWithIndicator(nameInputEl, {
|
||||
new ValidatedHtmlInputElement(nameInputEl, {
|
||||
schema: UserNameSchema,
|
||||
isValid: remoteValidation(
|
||||
async (name) => Ape.users.getNameAvailability({ params: { name } }),
|
||||
|
|
@ -90,7 +90,7 @@ let disposableEmailModule: typeof import("disposable-email-domains-js") | null =
|
|||
null;
|
||||
let moduleLoadAttempted = false;
|
||||
|
||||
const emailInputEl = validateWithIndicator(
|
||||
const emailInputEl = new ValidatedHtmlInputElement(
|
||||
document.querySelector(
|
||||
".page.pageLogin .register.side input.emailInput"
|
||||
) as HTMLInputElement,
|
||||
|
|
@ -143,7 +143,7 @@ const emailInputEl = validateWithIndicator(
|
|||
}
|
||||
);
|
||||
|
||||
emailInputEl.addEventListener("focus", async () => {
|
||||
emailInputEl.native.addEventListener("focus", async () => {
|
||||
if (!moduleLoadAttempted) {
|
||||
moduleLoadAttempted = true;
|
||||
try {
|
||||
|
|
@ -157,9 +157,9 @@ emailInputEl.addEventListener("focus", async () => {
|
|||
const emailVerifyInputEl = document.querySelector(
|
||||
".page.pageLogin .register.side input.verifyEmailInput"
|
||||
) as HTMLInputElement;
|
||||
validateWithIndicator(emailVerifyInputEl, {
|
||||
new ValidatedHtmlInputElement(emailVerifyInputEl, {
|
||||
isValid: async (emailVerify: string) => {
|
||||
return emailInputEl.value === emailVerify
|
||||
return emailInputEl.getValue() === emailVerify
|
||||
? true
|
||||
: "verify email not matching email";
|
||||
},
|
||||
|
|
@ -168,13 +168,13 @@ validateWithIndicator(emailVerifyInputEl, {
|
|||
registerForm.email =
|
||||
emailInputEl.getValidationResult().status === "success" &&
|
||||
result.status === "success"
|
||||
? emailInputEl.value
|
||||
? emailInputEl.getValue()
|
||||
: undefined;
|
||||
updateSignupButton();
|
||||
},
|
||||
});
|
||||
|
||||
const passwordInputEl = validateWithIndicator(
|
||||
const passwordInputEl = new ValidatedHtmlInputElement(
|
||||
document.querySelector(
|
||||
".page.pageLogin .register.side .passwordInput"
|
||||
) as HTMLInputElement,
|
||||
|
|
@ -192,9 +192,9 @@ const passwordInputEl = validateWithIndicator(
|
|||
const passwordVerifyInputEl = document.querySelector(
|
||||
".page.pageLogin .register.side .verifyPasswordInput"
|
||||
) as HTMLInputElement;
|
||||
validateWithIndicator(passwordVerifyInputEl, {
|
||||
new ValidatedHtmlInputElement(passwordVerifyInputEl, {
|
||||
isValid: async (passwordVerify: string) => {
|
||||
return passwordInputEl.value === passwordVerify
|
||||
return passwordInputEl.getValue() === passwordVerify
|
||||
? true
|
||||
: "verify password not matching password";
|
||||
},
|
||||
|
|
@ -203,7 +203,7 @@ validateWithIndicator(passwordVerifyInputEl, {
|
|||
registerForm.password =
|
||||
passwordInputEl.getValidationResult().status === "success" &&
|
||||
result.status === "success"
|
||||
? passwordInputEl.value
|
||||
? passwordInputEl.getValue()
|
||||
: undefined;
|
||||
updateSignupButton();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import * as Notifications from "../elements/notifications";
|
|||
import * as ConnectionState from "../states/connection";
|
||||
import {
|
||||
IsValidResponse,
|
||||
ValidatedHtmlInputElement,
|
||||
Validation,
|
||||
ValidationOptions,
|
||||
ValidationResult,
|
||||
validateWithIndicator as withValidation,
|
||||
} from "../elements/input-validation";
|
||||
|
||||
type CommonInput<TType, TValue> = {
|
||||
|
|
@ -351,7 +351,7 @@ export class SimpleModal {
|
|||
debounceDelay: input.validation.debounceDelay,
|
||||
};
|
||||
|
||||
withValidation(element, options);
|
||||
new ValidatedHtmlInputElement(element, options);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue