mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-27 17:27:32 +08:00
created input indicator class to be used in many places
This commit is contained in:
parent
b029d86460
commit
48fffe1b99
7 changed files with 164 additions and 144 deletions
|
|
@ -391,3 +391,37 @@ key {
|
|||
color: var(--bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.inputAndIndicator {
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
position: relative;
|
||||
.statusIndicator {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
/* background: red; */
|
||||
display: grid;
|
||||
grid-template-columns: 2.25rem;
|
||||
grid-template-rows: 2.25rem;
|
||||
place-items: center center;
|
||||
cursor: pointer;
|
||||
|
||||
.indicator {
|
||||
grid-column: 1/2;
|
||||
grid-row: 1/2;
|
||||
&.level-1 {
|
||||
color: var(--error-color);
|
||||
}
|
||||
&.level0 {
|
||||
color: var(--sub-color);
|
||||
}
|
||||
&.level1 {
|
||||
color: var(--main-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,45 +34,6 @@
|
|||
grid-area: form;
|
||||
}
|
||||
}
|
||||
&.register {
|
||||
.inputAndIndicator {
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
position: relative;
|
||||
.checkStatus {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
/* background: red; */
|
||||
display: grid;
|
||||
grid-template-columns: 2.25rem;
|
||||
grid-template-rows: 2.25rem;
|
||||
place-items: center center;
|
||||
cursor: pointer;
|
||||
|
||||
.checking,
|
||||
.available,
|
||||
.unavailable,
|
||||
.taken {
|
||||
grid-column: 1/2;
|
||||
grid-row: 1/2;
|
||||
}
|
||||
.checking {
|
||||
color: var(--sub-color);
|
||||
}
|
||||
.available {
|
||||
color: var(--main-color);
|
||||
}
|
||||
.unavailable,
|
||||
.taken {
|
||||
color: var(--error-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
|
|
|
|||
71
frontend/src/ts/elements/input-indicator.ts
Normal file
71
frontend/src/ts/elements/input-indicator.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
interface InputIndicatorOption {
|
||||
icon: string;
|
||||
spinIcon?: true;
|
||||
message?: string;
|
||||
level: -1 | 0 | 1;
|
||||
}
|
||||
|
||||
export class InputIndicator {
|
||||
private parentElement: JQuery<HTMLElement>;
|
||||
private options: Record<string, InputIndicatorOption>;
|
||||
|
||||
constructor(
|
||||
parentElement: JQuery<HTMLElement>,
|
||||
options: Record<string, InputIndicatorOption>
|
||||
) {
|
||||
if (!parentElement.hasClass("inputAndIndicator")) {
|
||||
throw new Error("Parent element must have class 'inputAndIndicator'");
|
||||
}
|
||||
this.parentElement = parentElement;
|
||||
this.options = options;
|
||||
|
||||
let indicator = `<div class="statusIndicator">`;
|
||||
|
||||
for (const [optionId, option] of Object.entries(options)) {
|
||||
indicator += `
|
||||
<div
|
||||
class="indicator level${option.level} hidden"
|
||||
data-option-id="${optionId}"
|
||||
|
||||
${
|
||||
//todo: find a good value for this
|
||||
(option.message?.length ?? 0) > 20
|
||||
? `data-balloon-length="large"`
|
||||
: ""
|
||||
}
|
||||
data-balloon-pos="up"
|
||||
${option.message ? `aria-label="${option.message}"` : ""}
|
||||
>
|
||||
<i class="fas fa-fw ${option.icon} ${
|
||||
option.spinIcon ? "fa-spin" : ""
|
||||
}"></i>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
indicator += `</div>`;
|
||||
|
||||
parentElement.append(indicator);
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.parentElement.find(".statusIndicator div").addClass("hidden");
|
||||
}
|
||||
|
||||
show(optionId: keyof typeof this.options, messageOverride?: string): void {
|
||||
this.hide();
|
||||
|
||||
const indicator = this.parentElement.find(`[data-option-id="${optionId}"]`);
|
||||
|
||||
indicator.removeClass("hidden");
|
||||
|
||||
if (messageOverride) {
|
||||
if (messageOverride.length > 20) {
|
||||
indicator.attr("data-balloon-length", "large");
|
||||
} else {
|
||||
indicator.removeAttr("data-balloon-length");
|
||||
}
|
||||
indicator.attr("aria-label", messageOverride);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import { debounce } from "throttle-debounce";
|
|||
import Ape from "../ape";
|
||||
import Page from "./page";
|
||||
import * as Notifications from "../elements/notifications";
|
||||
import { InputIndicator } from "../elements/input-indicator";
|
||||
|
||||
export function enableInputs(): void {
|
||||
$(".pageLogin .button").removeClass("disabled");
|
||||
|
|
@ -21,34 +22,28 @@ export function hidePreloader(): void {
|
|||
$(".pageLogin .preloader").addClass("hidden");
|
||||
}
|
||||
|
||||
function updateIndicator(
|
||||
state: "checking" | "available" | "unavailable" | "taken" | "none",
|
||||
balloon?: string
|
||||
): void {
|
||||
$(".page.pageLogin .register.side .checkStatus .checking").addClass("hidden");
|
||||
$(".page.pageLogin .register.side .checkStatus .available").addClass(
|
||||
"hidden"
|
||||
);
|
||||
$(".page.pageLogin .register.side .checkStatus .unavailable").addClass(
|
||||
"hidden"
|
||||
);
|
||||
$(".page.pageLogin .register.side .checkStatus .taken").addClass("hidden");
|
||||
if (state !== "none") {
|
||||
$(".page.pageLogin .register.side .checkStatus ." + state).removeClass(
|
||||
"hidden"
|
||||
);
|
||||
if (balloon) {
|
||||
$(".page.pageLogin .register.side .checkStatus ." + state).attr(
|
||||
"aria-label",
|
||||
balloon
|
||||
);
|
||||
} else {
|
||||
$(".page.pageLogin .register.side .checkStatus ." + state).removeAttr(
|
||||
"aria-label"
|
||||
);
|
||||
}
|
||||
const nameIndicator = new InputIndicator(
|
||||
$(".page.pageLogin .register.side .username.inputAndIndicator"),
|
||||
{
|
||||
available: {
|
||||
icon: "fa-check",
|
||||
level: 1,
|
||||
},
|
||||
unavailable: {
|
||||
icon: "fa-times",
|
||||
level: -1,
|
||||
},
|
||||
taken: {
|
||||
icon: "fa-times",
|
||||
level: -1,
|
||||
},
|
||||
checking: {
|
||||
icon: "fa-circle-notch",
|
||||
spinIcon: true,
|
||||
level: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const checkNameDebounced = debounce(1000, async () => {
|
||||
const val = $(
|
||||
|
|
@ -58,22 +53,22 @@ const checkNameDebounced = debounce(1000, async () => {
|
|||
const response = await Ape.users.getNameAvailability(val);
|
||||
|
||||
if (response.status === 200) {
|
||||
updateIndicator("available", response.message);
|
||||
nameIndicator.show("available", response.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status == 422) {
|
||||
updateIndicator("unavailable", response.message);
|
||||
nameIndicator.show("unavailable", response.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status == 409) {
|
||||
updateIndicator("taken", response.message);
|
||||
nameIndicator.show("taken", response.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
updateIndicator("unavailable");
|
||||
nameIndicator.show("unavailable", response.message);
|
||||
return Notifications.add(
|
||||
"Failed to check name availability: " + response.message,
|
||||
-1
|
||||
|
|
@ -87,9 +82,9 @@ $(".page.pageLogin .register.side .usernameInput").on("input", () => {
|
|||
".page.pageLogin .register.side .usernameInput"
|
||||
).val() as string;
|
||||
if (val === "") {
|
||||
return updateIndicator("none");
|
||||
return nameIndicator.hide();
|
||||
} else {
|
||||
updateIndicator("checking");
|
||||
nameIndicator.show("checking");
|
||||
checkNameDebounced();
|
||||
}
|
||||
}, 1);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import * as TestLogic from "../test/test-logic";
|
|||
import * as DB from "../db";
|
||||
import * as Loader from "../elements/loader";
|
||||
import { subscribe as subscribeToSignUpEvent } from "../observables/google-sign-up-event";
|
||||
import { InputIndicator } from "../elements/input-indicator";
|
||||
|
||||
let signedInUser: UserCredential | undefined = undefined;
|
||||
|
||||
|
|
@ -130,27 +131,6 @@ async function apply(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
function updateIndicator(
|
||||
state: "checking" | "available" | "unavailable" | "taken" | "none",
|
||||
balloon?: string
|
||||
): void {
|
||||
$("#googleSignUpPopup .checkStatus .checking").addClass("hidden");
|
||||
$("#googleSignUpPopup .checkStatus .available").addClass("hidden");
|
||||
$("#googleSignUpPopup .checkStatus .unavailable").addClass("hidden");
|
||||
$("#googleSignUpPopup .checkStatus .taken").addClass("hidden");
|
||||
if (state !== "none") {
|
||||
$("#googleSignUpPopup .checkStatus ." + state).removeClass("hidden");
|
||||
if (balloon) {
|
||||
$("#googleSignUpPopup .checkStatus ." + state).attr(
|
||||
"aria-label",
|
||||
balloon
|
||||
);
|
||||
} else {
|
||||
$("#googleSignUpPopup .checkStatus ." + state).removeAttr("aria-label");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function enableButton(): void {
|
||||
$("#googleSignUpPopup .button").removeClass("disabled");
|
||||
}
|
||||
|
|
@ -173,29 +153,52 @@ $("#googleSignUpPopupWrapper").on("mousedown", (e) => {
|
|||
}
|
||||
});
|
||||
|
||||
const nameIndicator = new InputIndicator(
|
||||
$("#googleSignUpPopup .inputAndIndicator"),
|
||||
{
|
||||
available: {
|
||||
icon: "fa-check",
|
||||
level: 1,
|
||||
},
|
||||
unavailable: {
|
||||
icon: "fa-times",
|
||||
level: -1,
|
||||
},
|
||||
taken: {
|
||||
icon: "fa-times",
|
||||
level: -1,
|
||||
},
|
||||
checking: {
|
||||
icon: "fa-circle-notch",
|
||||
spinIcon: true,
|
||||
level: 0,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const checkNameDebounced = debounce(1000, async () => {
|
||||
const val = $("#googleSignUpPopup input").val() as string;
|
||||
if (!val) return;
|
||||
const response = await Ape.users.getNameAvailability(val);
|
||||
|
||||
if (response.status === 200) {
|
||||
updateIndicator("available", response.message);
|
||||
nameIndicator.show("available", response.message);
|
||||
enableButton();
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status == 422) {
|
||||
updateIndicator("unavailable", response.message);
|
||||
nameIndicator.show("unavailable", response.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status == 409) {
|
||||
updateIndicator("taken", response.message);
|
||||
nameIndicator.show("taken", response.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
updateIndicator("unavailable");
|
||||
nameIndicator.show("unavailable");
|
||||
return Notifications.add(
|
||||
"Failed to check name availability: " + response.message,
|
||||
-1
|
||||
|
|
@ -208,9 +211,9 @@ $("#googleSignUpPopup input").on("input", () => {
|
|||
disableButton();
|
||||
const val = $("#googleSignUpPopup input").val() as string;
|
||||
if (val === "") {
|
||||
return updateIndicator("none");
|
||||
return nameIndicator.hide();
|
||||
} else {
|
||||
updateIndicator("checking");
|
||||
nameIndicator.show("checking");
|
||||
checkNameDebounced();
|
||||
}
|
||||
}, 1);
|
||||
|
|
|
|||
|
|
@ -5,35 +5,13 @@
|
|||
<div class="register side">
|
||||
<div class="title">register</div>
|
||||
<form action="" autocomplete="nope">
|
||||
<div class="inputAndIndicator">
|
||||
<div class="inputAndIndicator username">
|
||||
<input
|
||||
type="text"
|
||||
class="usernameInput"
|
||||
placeholder="username"
|
||||
autocomplete="new-username"
|
||||
/>
|
||||
<div class="checkStatus">
|
||||
<div
|
||||
class="checking hidden"
|
||||
data-balloon-length="large"
|
||||
data-balloon-pos="up"
|
||||
>
|
||||
<i class="fas fa-fw fa-circle-notch fa-spin"></i>
|
||||
</div>
|
||||
<div class="available hidden" data-balloon-pos="up">
|
||||
<i class="fas fa-fw fa-check"></i>
|
||||
</div>
|
||||
<div
|
||||
class="unavailable hidden"
|
||||
data-balloon-length="large"
|
||||
data-balloon-pos="up"
|
||||
>
|
||||
<i class="fas fa-fw fa-times"></i>
|
||||
</div>
|
||||
<div class="taken hidden" data-balloon-pos="up">
|
||||
<i class="fas fa-fw fa-user-friends"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="email" placeholder="email" autocomplete="new-email" />
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -476,28 +476,6 @@
|
|||
<div class="text">You need to choose a username before continuing</div>
|
||||
<div class="inputAndIndicator">
|
||||
<input type="text" placeholder="username" />
|
||||
<div class="checkStatus">
|
||||
<div
|
||||
class="checking hidden"
|
||||
data-balloon-length="large"
|
||||
data-balloon-pos="up"
|
||||
>
|
||||
<i class="fas fa-fw fa-circle-notch fa-spin"></i>
|
||||
</div>
|
||||
<div class="available hidden" data-balloon-pos="up">
|
||||
<i class="fas fa-fw fa-check"></i>
|
||||
</div>
|
||||
<div
|
||||
class="unavailable hidden"
|
||||
data-balloon-length="large"
|
||||
data-balloon-pos="up"
|
||||
>
|
||||
<i class="fas fa-fw fa-times"></i>
|
||||
</div>
|
||||
<div class="taken hidden" data-balloon-pos="up">
|
||||
<i class="fas fa-fw fa-user-friends"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button disabled">Sign up</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue