mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2026-02-04 14:39:02 +08:00
impr(save custom text modal): add validation for custom text name input
This commit is contained in:
parent
42f6a16c62
commit
f025b121cb
1 changed files with 37 additions and 61 deletions
|
|
@ -1,11 +1,13 @@
|
|||
import * as CustomText from "../test/custom-text";
|
||||
import * as Notifications from "../elements/notifications";
|
||||
import * as CustomTextState from "../states/custom-text-name";
|
||||
import { InputIndicator } from "../elements/input-indicator";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import AnimatedModal, { ShowOptions } from "../utils/animated-modal";
|
||||
import { validateWithIndicator } from "../elements/input-validation";
|
||||
import { z } from "zod";
|
||||
|
||||
let indicator: InputIndicator | undefined;
|
||||
type IncomingData = {
|
||||
text: string[];
|
||||
};
|
||||
|
||||
type State = {
|
||||
textToSave: string[];
|
||||
|
|
@ -15,6 +17,35 @@ const state: State = {
|
|||
textToSave: [],
|
||||
};
|
||||
|
||||
const validatedInput = validateWithIndicator(
|
||||
$("#saveCustomTextModal .textName")[0] as HTMLInputElement,
|
||||
{
|
||||
debounceDelay: 500,
|
||||
schema: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(32)
|
||||
.regex(/^[\w\s-]+$/, {
|
||||
message:
|
||||
"Name can only contain letters, numbers, spaces, underscores and hyphens",
|
||||
}),
|
||||
isValid: async (value) => {
|
||||
const checkbox = $("#saveCustomTextModal .isLongText").prop(
|
||||
"checked"
|
||||
) as boolean;
|
||||
const names = CustomText.getCustomTextNames(checkbox);
|
||||
return !names.includes(value) ? true : "Duplicate name";
|
||||
},
|
||||
callback: (result) => {
|
||||
if (result.status === "success") {
|
||||
$("#saveCustomTextModal button.save").prop("disabled", false);
|
||||
} else {
|
||||
$("#saveCustomTextModal button.save").prop("disabled", true);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export async function show(options: ShowOptions<IncomingData>): Promise<void> {
|
||||
state.textToSave = [];
|
||||
void modal.show({
|
||||
|
|
@ -28,10 +59,6 @@ export async function show(options: ShowOptions<IncomingData>): Promise<void> {
|
|||
});
|
||||
}
|
||||
|
||||
function hide(): void {
|
||||
void modal.hide();
|
||||
}
|
||||
|
||||
function save(): boolean {
|
||||
const name = $("#saveCustomTextModal .textName").val() as string;
|
||||
const checkbox = $("#saveCustomTextModal .isLongText").prop(
|
||||
|
|
@ -59,69 +86,18 @@ function save(): boolean {
|
|||
}
|
||||
}
|
||||
|
||||
function updateIndicatorAndButton(): void {
|
||||
const val = $("#saveCustomTextModal .textName").val() as string;
|
||||
const checkbox = $("#saveCustomTextModal .isLongText").prop(
|
||||
"checked"
|
||||
) as boolean;
|
||||
|
||||
if (!val) {
|
||||
indicator?.hide();
|
||||
$("#saveCustomTextModal button.save").prop("disabled", true);
|
||||
} else {
|
||||
const names = CustomText.getCustomTextNames(checkbox);
|
||||
if (names.includes(val)) {
|
||||
indicator?.show("unavailable");
|
||||
$("#saveCustomTextModal button.save").prop("disabled", true);
|
||||
} else {
|
||||
indicator?.show("available");
|
||||
$("#saveCustomTextModal button.save").prop("disabled", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updateInputAndButtonDebounced = debounce(500, updateIndicatorAndButton);
|
||||
|
||||
async function setup(modalEl: HTMLElement): Promise<void> {
|
||||
indicator = new InputIndicator($("#saveCustomTextModal .textName"), {
|
||||
available: {
|
||||
icon: "fa-check",
|
||||
level: 1,
|
||||
},
|
||||
unavailable: {
|
||||
icon: "fa-times",
|
||||
level: -1,
|
||||
},
|
||||
loading: {
|
||||
icon: "fa-circle-notch",
|
||||
spinIcon: true,
|
||||
level: 0,
|
||||
},
|
||||
});
|
||||
modalEl.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
if (save()) hide();
|
||||
});
|
||||
modalEl.querySelector(".textName")?.addEventListener("input", (e) => {
|
||||
const val = (e.target as HTMLInputElement).value;
|
||||
if (val.length > 0) {
|
||||
indicator?.show("loading");
|
||||
updateInputAndButtonDebounced();
|
||||
if (validatedInput.isValid() === true && save()) {
|
||||
void modal.hide();
|
||||
}
|
||||
});
|
||||
modalEl.querySelector(".isLongText")?.addEventListener("input", (e) => {
|
||||
const val = (e.target as HTMLInputElement).value;
|
||||
if (val.length > 0) {
|
||||
indicator?.show("loading");
|
||||
updateInputAndButtonDebounced();
|
||||
}
|
||||
validatedInput.triggerValidation();
|
||||
});
|
||||
}
|
||||
|
||||
type IncomingData = {
|
||||
text: string[];
|
||||
};
|
||||
|
||||
const modal = new AnimatedModal<IncomingData>({
|
||||
dialogId: "saveCustomTextModal",
|
||||
setup,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue