refactor: commandline (#5180)

* initial rewrite

* remove title

* remove from dom on hide

* actual command dom
show active
keep active in view
update active index on keypress

* global event only shows commandline

* impr(dev): showing notifications with unhandled errors

* rename functions

* handle hover functions
handle nesting
handle exec
attaching handlers once

* void promise

* no need for async

* move font preview to ui

* add configkey to command

* add function to build single list

* add mouse mode
clearing preview on hide
add config icon

* using new function

* use fw icon

* fix incorrect type

* extract logic

* add support for input commands

* unused error

* update single list

* chevron icon margin

* only focusing when no popups and words are visible

* extract condition

* better type

* remove no icon

* only showing when nothing is already visible

* commandline lists no longer modify the commandline element
dynamically changing which funbox commnands are available
modified searching approach
showing active settings in single mode again
calling before list if needed

* ignore keydown on page transition

* add other ways to show the commandline

* always clearing previews

* incorrect icon being used

* extract logic

* support quick single list mode by starting input with >

* fix test words not being focused

* showing all if in quick single mode

* remove unused code

* rename entry to command

* remove more unused code

* add data- prefix

* rename to data-command-id

* fix input commands not refocusing words

* fix fontsize change not working as intended

* set active index to 0 when going back

* keeping active command in view

* remove console logs

* fix quick single mode

* move file

* add footer events

* add option for subgroup override
fix issue where commands would get hidden after clearing input field
fixed auto scrolling in mouse mode

* rename commands to lists

* add background after to avoid flashing

* getting config key from active subgroup

* updating active command after removing hidden class but before animating

* fix nasty hover behavior

* updating active before animation showing, keeping active after

* add keymap event

* remove comments

* invert if, use return, combine ifs to reduce nesting

* add test event

* popups events

* fix some commands not showing up on a single list

* use new animated modal

* use regex escaping function from misc

* add singlelistdisplay and singlelistdisplaynoicon properties

* add more navigation aliases

* not adding alias if not needed

* rework command filtering

* fix active icon not working

* add custom hide handlers

* fix active command not being kept in view after showing

* unused imports

* remove commandlinelists imports from other files

* delete old file

* remove unused file

* import commandline dynamically

* fix: if skeleton has wrapper, append before building animated modal

* using new parameters

* save skeleton on ready

* rename folder

* add util to get async modules

* remove empty imports

* remove unnecessary code

* unnecessary void

* catching errors and notifying user

* better error message

* making sure all the lists are fetched before getting the single command list

* add tab navigation
This commit is contained in:
Jack 2024-03-05 19:34:35 +01:00 committed by GitHub
parent 226f5de472
commit 3c4212b718
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 930 additions and 1077 deletions

View file

@ -1095,8 +1095,8 @@
</div>
</div>
</div>
<div id="commandLineWrapper" class="hidden">
<div id="commandLine">
<dialog id="commandLine" class="modalWrapper hidden">
<div class="modal">
<div
style="
display: grid;
@ -1105,17 +1105,14 @@
"
>
<div class="searchicon">
<i class="fas fa-search"></i>
<i class="fas fa-fw fa-search"></i>
</div>
<input type="text" class="input" placeholder="Type to search" />
</div>
<div class="separator hidden"></div>
<div class="suggestions"></div>
</div>
<div id="commandInput" class="hidden">
<input type="text" class="input" placeholder="input" />
</div>
</div>
</dialog>
<div id="newResultFilterPresetPopupWrapper" class="popupWrapper hidden">
<div id="newResultFilterPresetPopup">
<div class="title">Add new filter preset</div>

View file

@ -1,4 +1,4 @@
#commandLineWrapper {
#commandLine {
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.75);
@ -9,40 +9,12 @@
display: grid;
justify-items: center;
align-items: start;
padding: 5rem 2rem;
padding: 6rem 2rem;
grid-template-columns: 1fr;
transition: background 0.125s;
overflow: hidden;
#commandInput {
max-width: 600px;
width: 100%;
background: var(--bg-color);
border-radius: var(--roundness);
input {
background: var(--bg-color);
padding: 1rem;
color: var(--main-color);
border: none;
outline: none;
font-size: 1rem;
width: 100%;
border-radius: var(--roundness);
&:focus-visible {
box-shadow: none;
}
}
.shiftEnter {
padding: 0.5rem 1rem;
font-size: 0.75rem;
line-height: 0.75rem;
color: var(--sub-color);
text-align: center;
}
}
#commandLine {
.modal {
max-width: 600px;
width: 100%;
background: var(--bg-color);
@ -50,6 +22,8 @@
// outline: 0.25rem solid transparent;
box-shadow: 0 0 0 0.2em transparent;
transition: outline 0.125s;
padding: 0;
display: block;
.searchicon {
color: var(--sub-color);
@ -70,31 +44,17 @@
}
}
.separator {
background: black;
width: 100%;
height: 1px;
margin-bottom: 0.5rem;
}
.listTitle {
color: var(--text-color);
padding: 0.5rem 1rem;
font-size: 0.75rem;
line-height: 0.75rem;
}
.suggestions {
display: block;
@extend .ffscroll;
overflow-y: scroll;
max-height: calc(100vh - 10rem - 3rem);
max-height: calc(100vh - 12rem - 3rem);
display: grid;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
.entry {
.command {
padding: 0.5rem 1rem;
font-size: 0.75rem;
line-height: 0.75rem;
@ -102,6 +62,10 @@
display: grid;
grid-template-columns: auto 1fr;
.chevronIcon {
margin: 0 0.5rem;
}
div {
pointer-events: none;
}
@ -126,27 +90,6 @@
border-radius: 0 0 var(--roundness) var(--roundness);
}
&.activeMouse {
color: var(--bg-color) !important;
background: var(--text-color) !important;
cursor: pointer;
.fas,
.far,
.fab {
color: var(--bg-color);
}
}
&.activeKeyboard {
color: var(--bg-color) !important;
background: var(--text-color) !important;
.fas,
.far,
.fab {
color: var(--bg-color);
}
}
&.active {
color: var(--bg-color) !important;
background: var(--text-color) !important;
@ -157,12 +100,6 @@
}
}
// &:hover {
// color: var(--text-color);
// background: var(--sub-color);
// cursor: pointer;
// }
&.withThemeBubbles {
grid-template-columns: auto 1fr auto;
.themeBubbles {
@ -183,7 +120,7 @@
&.noBackground {
background: none;
#commandLine {
.modal {
box-shadow: 0 0 0 0.2em var(--sub-alt-color);
// outline: 0.25rem solid var(--sub-alt-color);
}

View file

@ -0,0 +1,588 @@
import * as Focus from "../test/focus";
import * as CommandlineLists from "./lists";
import Config from "../config";
import * as AnalyticsController from "../controllers/analytics-controller";
import * as ThemeController from "../controllers/theme-controller";
import { clearFontPreview } from "../ui";
import AnimatedModal from "../popups/animated-modal";
import * as Notifications from "../elements/notifications";
type CommandlineMode = "search" | "input";
type InputModeParams = {
command: MonkeyTypes.Command | null;
placeholder: string | null;
value: string | null;
icon: string | null;
};
let activeIndex = 0;
let usingSingleList = false;
let inputValue = "";
let activeCommand: MonkeyTypes.Command | null = null;
let mouseMode = false;
let mode: CommandlineMode = "search";
let inputModeParams: InputModeParams = {
command: null,
placeholder: "",
value: "",
icon: "",
};
let subgroupOverride: MonkeyTypes.CommandsSubgroup | null = null;
function removeCommandlineBackground(): void {
$("#commandLine").addClass("noBackground");
if (Config.showOutOfFocusWarning) {
$("#words").removeClass("blurred");
}
}
function addCommandlineBackground(): void {
$("#commandLine").removeClass("noBackground");
const isWordsFocused = $("#wordsInput").is(":focus");
if (Config.showOutOfFocusWarning && !isWordsFocused) {
$("#words").addClass("blurred");
}
}
type ShowSettings = {
subgroupOverride?: MonkeyTypes.CommandsSubgroup | string;
singleListOverride?: boolean;
};
export function show(settings?: ShowSettings): void {
void modal.show({
beforeAnimation: async (modal) => {
mouseMode = false;
inputValue = "";
activeIndex = 0;
mode = "search";
inputModeParams = {
command: null,
placeholder: null,
value: null,
icon: null,
};
if (settings?.subgroupOverride !== undefined) {
if (typeof settings.subgroupOverride === "string") {
const exists = CommandlineLists.doesListExist(
settings.subgroupOverride
);
if (exists) {
subgroupOverride = CommandlineLists.getList(
settings.subgroupOverride as CommandlineLists.ListsObjectKeys
);
} else {
subgroupOverride = null;
usingSingleList = Config.singleListCommandLine === "on";
Notifications.add(
`Command list ${settings.subgroupOverride} not found`,
0
);
}
} else {
subgroupOverride = settings.subgroupOverride;
}
usingSingleList = false;
} else {
subgroupOverride = null;
usingSingleList = Config.singleListCommandLine === "on";
}
if (settings?.singleListOverride) {
usingSingleList = settings.singleListOverride;
}
activeCommand = null;
Focus.set(false);
CommandlineLists.setStackToDefault();
await beforeList();
updateInput();
await filterSubgroup();
await showCommands();
await updateActiveCommand();
setTimeout(() => {
// instead of waiting for the animation to finish,
// we focus just after it begins to increase responsivenes
// (you can type while the animation is running)
modal.querySelector("input")?.focus();
keepActiveCommandInView();
}, 0);
},
afterAnimation: async (modal) => {
modal.querySelector("input")?.focus();
},
});
}
function hide(): void {
clearFontPreview();
void ThemeController.clearPreview();
void modal.hide({
afterAnimation: async () => {
addCommandlineBackground();
},
});
}
async function goBackOrHide(): Promise<void> {
if (mode === "input") {
mode = "search";
inputModeParams = {
command: null,
placeholder: null,
value: null,
icon: null,
};
updateInput("");
await filterSubgroup();
await showCommands();
await updateActiveCommand();
return;
}
if (CommandlineLists.getStackLength() > 1) {
CommandlineLists.popFromStack();
activeIndex = 0;
updateInput("");
await filterSubgroup();
await showCommands();
await updateActiveCommand();
} else {
hide();
}
}
async function filterSubgroup(): Promise<void> {
// const configKey = getSubgroup().configKey;
const list = await getList();
const inputNoQuickSingle = inputValue
.replace(/^>/gi, "")
.toLowerCase()
.trim();
const inputSplit =
inputNoQuickSingle.length === 0 ? [] : inputNoQuickSingle.split(" ");
const displayMatchCounts: number[] = [];
const aliasMatchCounts: number[] = [];
for (const command of list) {
const isAvailable = command.available?.() ?? true;
if (!isAvailable) {
displayMatchCounts.push(-1);
aliasMatchCounts.push(-1);
continue;
}
if (inputNoQuickSingle.length === 0 || inputSplit.length === 0) {
displayMatchCounts.push(0);
aliasMatchCounts.push(0);
continue;
}
const displaySplit = (
usingSingleList
? (command.singleListDisplayNoIcon ?? "") || command.display
: command.display
)
.toLowerCase()
.split(" ");
const displayMatchArray: (null | number)[] = displaySplit.map(() => null);
const aliasSplit = command.alias?.toLowerCase().split(" ") ?? [];
const aliasMatchArray: (null | number)[] = aliasSplit.map(() => null);
for (const [inputIndex, input] of inputSplit.entries()) {
for (const [displayIndex, display] of displaySplit.entries()) {
const matchedInputIndex = displayMatchArray[displayIndex] as
| null
| number;
if (
display.startsWith(input) &&
matchedInputIndex === null &&
!displayMatchArray.includes(inputIndex)
) {
displayMatchArray[displayIndex] = inputIndex;
}
}
for (const [aliasIndex, alias] of aliasSplit.entries()) {
const matchedAliasIndex = aliasMatchArray[aliasIndex] as null | number;
if (
alias.startsWith(input) &&
matchedAliasIndex === null &&
!aliasMatchArray.includes(inputIndex)
) {
aliasMatchArray[aliasIndex] = inputIndex;
}
}
}
const displayMatchCount = displayMatchArray.filter(
(i) => i !== null
).length;
const aliasMatchCount = aliasMatchArray.filter((i) => i !== null).length;
displayMatchCounts.push(displayMatchCount);
aliasMatchCounts.push(aliasMatchCount);
}
for (const [index, command] of list.entries()) {
if (
(displayMatchCounts[index] as number) >= inputSplit.length ||
(aliasMatchCounts[index] as number) >= inputSplit.length
) {
command.found = true;
} else {
command.found = false;
}
}
}
function hideCommands(): void {
const element = document.querySelector("#commandLine .suggestions");
if (element === null) {
throw new Error("Commandline element not found");
}
element.innerHTML = "";
}
async function getSubgroup(): Promise<MonkeyTypes.CommandsSubgroup> {
if (subgroupOverride !== null) {
return subgroupOverride;
}
if (usingSingleList) {
return CommandlineLists.getSingleSubgroup();
}
return CommandlineLists.getTopOfStack();
}
async function getList(): Promise<MonkeyTypes.Command[]> {
return (await getSubgroup()).list;
}
async function beforeList(): Promise<void> {
(await getSubgroup()).beforeList?.();
}
async function showCommands(): Promise<void> {
const element = document.querySelector("#commandLine .suggestions");
if (element === null) {
throw new Error("Commandline element not found");
}
if (inputValue === "" && usingSingleList) {
element.innerHTML = "";
return;
}
const list = (await getList()).filter((c) => c.found === true);
let html = "";
let index = 0;
let firstActive: null | number = null;
for (const command of list) {
if (command.found !== true) continue;
let icon = command.icon ?? "fa-chevron-right";
const faIcon = icon.startsWith("fa-");
if (!faIcon) {
icon = `<div class="textIcon">${icon}</div>`;
} else {
icon = `<i class="fas fa-fw ${icon}"></i>`;
}
let configIcon = "";
const configKey = command.configKey ?? (await getSubgroup()).configKey;
if (configKey !== undefined) {
const valueIsIncluded =
command.configValueMode === "include" &&
(
Config[configKey] as (
| string
| number
| boolean
| number[]
| undefined
)[]
).includes(command.configValue);
const valueIsTheSame = Config[configKey] === command.configValue;
if (valueIsIncluded || valueIsTheSame) {
firstActive = firstActive ?? index;
configIcon = `<i class="fas fa-fw fa-check"></i>`;
} else {
configIcon = `<i class="fas fa-fw"></i>`;
}
}
const iconHTML = `<div class="icon">${
usingSingleList || configIcon === "" ? icon : configIcon
}</div>`;
let customStyle = "";
if (command.customStyle !== undefined && command.customStyle !== "") {
customStyle = command.customStyle;
}
let display = command.display;
if (usingSingleList) {
display = (command.singleListDisplay ?? "") || command.display;
display = display.replace(
`<i class="fas fa-fw fa-chevron-right chevronIcon"></i>`,
`<i class="fas fa-fw fa-chevron-right chevronIcon"></i>` + configIcon
);
}
if (command.id.startsWith("changeTheme") && command.customData) {
html += `<div class="command withThemeBubbles" data-command-id="${command.id}" data-index="${index}" style="${customStyle}">
${iconHTML}<div>${display}</div>
<div class="themeBubbles" style="background: ${command.customData["bgColor"]};outline: 0.25rem solid ${command.customData["bgColor"]};">
<div class="themeBubble" style="background: ${command.customData["mainColor"]}"></div>
<div class="themeBubble" style="background: ${command.customData["subColor"]}"></div>
<div class="themeBubble" style="background: ${command.customData["textColor"]}"></div>
</div>
</div>`;
} else {
html += `<div class="command" data-command-id="${command.id}" data-index="${index}" style="${customStyle}">${iconHTML}<div>${display}</div></div>`;
}
index++;
}
if (firstActive !== null && !usingSingleList) {
activeIndex = firstActive;
}
element.innerHTML = html;
for (const command of element.querySelectorAll(".command")) {
command.addEventListener("mouseenter", async () => {
if (!mouseMode) return;
activeIndex = parseInt(command.getAttribute("data-index") ?? "0");
await updateActiveCommand();
});
command.addEventListener("mouseleave", async () => {
if (!mouseMode) return;
activeIndex = parseInt(command.getAttribute("data-index") ?? "0");
await updateActiveCommand();
});
command.addEventListener("click", async () => {
activeIndex = parseInt(command.getAttribute("data-index") ?? "0");
await runActiveCommand();
});
}
}
async function updateActiveCommand(): Promise<void> {
const elements = [
...document.querySelectorAll("#commandLine .suggestions .command"),
];
for (const element of elements) {
element.classList.remove("active");
}
const element = elements[activeIndex];
const command = (await getList()).filter((c) => c.found)[activeIndex];
activeCommand = command ?? null;
if (element === undefined || command === undefined) {
clearFontPreview();
void ThemeController.clearPreview();
addCommandlineBackground();
return;
}
element.classList.add("active");
keepActiveCommandInView();
clearFontPreview();
if (/changeTheme.+/gi.test(command.id)) {
removeCommandlineBackground();
} else {
void ThemeController.clearPreview();
addCommandlineBackground();
}
command.hover?.();
}
function handleInputSubmit(): void {
if (inputModeParams.command === null) {
throw new Error("Can't handle input submit - command is null");
}
const value = inputValue;
inputModeParams.command.exec?.(value);
void AnalyticsController.log("usedCommandLine", {
command: inputModeParams.command.id,
});
hide();
}
async function runActiveCommand(): Promise<void> {
if (activeCommand === null) return;
const command = activeCommand;
if (command.input) {
const escaped = command.display.split("</i>")[1] ?? command.display;
mode = "input";
inputModeParams = {
command: command,
placeholder: escaped,
value: command.defaultValue?.() ?? "",
icon: command.icon ?? "fa-chevron-right",
};
updateInput(inputModeParams.value as string);
hideCommands();
} else if (command.subgroup) {
if (command.subgroup.beforeList) {
command.subgroup.beforeList();
}
CommandlineLists.pushToStack(
command.subgroup as MonkeyTypes.CommandsSubgroup
);
await beforeList();
updateInput("");
await filterSubgroup();
await showCommands();
await updateActiveCommand();
} else {
command.exec?.();
const isSticky = command.sticky ?? false;
if (!isSticky) {
void AnalyticsController.log("usedCommandLine", { command: command.id });
hide();
} else {
await beforeList();
await filterSubgroup();
await showCommands();
await updateActiveCommand();
}
}
}
function keepActiveCommandInView(): void {
if (mouseMode) return;
try {
const scroll =
Math.abs(
($(".suggestions").offset()?.top as number) -
($(".command.active").offset()?.top as number) -
($(".suggestions").scrollTop() as number)
) -
($(".suggestions").outerHeight() as number) / 2 +
($($(".command")[0] as HTMLElement).outerHeight() as number);
$(".suggestions").scrollTop(scroll);
} catch (e) {
if (e instanceof Error) {
console.log("could not scroll suggestions: " + e.message);
}
}
}
function updateInput(setInput?: string): void {
const iconElement: HTMLElement | null = document.querySelector(
"#commandLine .searchicon"
);
const element: HTMLInputElement | null =
document.querySelector("#commandLine input");
if (element === null || iconElement === null) {
throw new Error("Commandline element or icon element not found");
}
if (setInput !== undefined) {
inputValue = setInput;
}
element.value = inputValue;
if (mode === "input") {
if (inputModeParams.icon !== null) {
iconElement.innerHTML = `<i class="fas fa-fw ${inputModeParams.icon}"></i>`;
}
if (inputModeParams.placeholder !== null) {
element.placeholder = inputModeParams.placeholder;
}
if (inputModeParams.value !== null) {
element.value = inputModeParams.value;
//select the text inside
element.setSelectionRange(0, element.value.length);
}
} else {
iconElement.innerHTML = '<i class="fas fa-search"></i>';
element.placeholder = "Search...";
}
}
async function incrementActiveIndex(): Promise<void> {
activeIndex++;
if (activeIndex >= (await getList()).filter((c) => c.found).length) {
activeIndex = 0;
}
await updateActiveCommand();
}
async function decrementActiveIndex(): Promise<void> {
activeIndex--;
if (activeIndex < 0) {
activeIndex = (await getList()).filter((c) => c.found).length - 1;
}
await updateActiveCommand();
}
const modal = new AnimatedModal("commandLine", "popups", undefined, {
customEscapeHandler: (): void => {
hide();
},
customWrapperClickHandler: (): void => {
hide();
},
setup: (modal): void => {
const input = modal.querySelector("input") as HTMLInputElement;
input.addEventListener("input", async (e) => {
inputValue = (e.target as HTMLInputElement).value;
if (subgroupOverride === null) {
if (Config.singleListCommandLine === "on") {
usingSingleList = true;
} else {
usingSingleList = inputValue.startsWith(">");
}
}
if (mode !== "search") return;
mouseMode = false;
activeIndex = 0;
await filterSubgroup();
await showCommands();
await updateActiveCommand();
});
input.addEventListener("keydown", async (e) => {
mouseMode = false;
if (e.key === "ArrowUp") {
e.preventDefault();
await decrementActiveIndex();
}
if (e.key === "ArrowDown") {
e.preventDefault();
await incrementActiveIndex();
}
if (e.key === "Tab") {
e.preventDefault();
if (e.shiftKey) {
await decrementActiveIndex();
} else {
await incrementActiveIndex();
}
}
if (e.key === "Enter") {
e.preventDefault();
if (mode === "search") {
await runActiveCommand();
} else if (mode === "input") {
handleInputSubmit();
} else {
throw new Error("Unknown mode, can't handle enter press");
}
}
if (e.key === "Escape") {
await goBackOrHide();
}
});
modal.addEventListener("mousemove", (e) => {
mouseMode = true;
});
},
});

View file

@ -1,862 +0,0 @@
import * as ThemeController from "../controllers/theme-controller";
import Config, * as UpdateConfig from "../config";
import * as Focus from "../test/focus";
import * as CommandlineLists from "./commands";
import * as TestUI from "../test/test-ui";
import * as DB from "../db";
import * as Notifications from "../elements/notifications";
import * as AnalyticsController from "../controllers/analytics-controller";
import * as PageTransition from "../states/page-transition";
import * as TestWords from "../test/test-words";
import * as ActivePage from "../states/active-page";
import { isAuthenticated } from "../firebase";
import {
isAnyPopupVisible,
isElementVisible,
isPopupVisible,
} from "../utils/misc";
import { update as updateCustomThemesList } from "./lists/custom-themes-list";
import * as Skeleton from "../utils/skeleton";
import * as ManualRestart from "../test/manual-restart-tracker";
const wrapperId = "commandLineWrapper";
let commandLineMouseMode = false;
let themeChosen = false;
let activeIndex = 0;
type State = {
usingSingleList: boolean;
};
const state: State = {
usingSingleList: false,
};
function showInput(
command: string,
placeholder: string,
inputValue = ""
): void {
$("#commandLineWrapper").removeClass("hidden");
$("#commandLine").addClass("hidden");
$("#commandInput").removeClass("hidden");
$("#commandInput input").attr("placeholder", placeholder);
$("#commandInput input").val(inputValue);
$("#commandInput input").trigger("focus");
$("#commandInput input").attr("command", "");
$("#commandInput input").attr("command", command);
if (inputValue != "") {
$("#commandInput input").trigger("select");
}
}
function isSingleListCommandLineActive(): boolean {
return state.usingSingleList;
}
function removeCommandlineBackground(): void {
$("#commandLineWrapper").addClass("noBackground");
if (Config.showOutOfFocusWarning) {
$("#words").removeClass("blurred");
}
}
function addCommandlineBackground(): void {
$("#commandLineWrapper").removeClass("noBackground");
if (Config.showOutOfFocusWarning) {
$("#words").addClass("blurred");
}
}
function showFound(): void {
$("#commandLine .suggestions").empty();
let commandsHTML = "";
const list = CommandlineLists.getCurrent();
let index = 0;
for (const obj of list.list) {
if (obj.found && (obj.available !== undefined ? obj.available() : true)) {
let icon = obj.icon ?? "fa-chevron-right";
const faIcon = icon.startsWith("fa-");
if (!faIcon) {
icon = `<div class="textIcon">${icon}</div>`;
} else {
icon = `<i class="fas fa-fw ${icon}"></i>`;
}
if (list.configKey) {
if (
(obj.configValueMode !== undefined &&
obj.configValueMode === "include" &&
(
Config[list.configKey] as (
| string
| number
| boolean
| number[]
| undefined
)[]
).includes(obj.configValue)) ||
Config[list.configKey] === obj.configValue
) {
icon = `<i class="fas fa-fw fa-check"></i>`;
} else {
icon = `<i class="fas fa-fw"></i>`;
}
}
let iconHTML = `<div class="icon">${icon}</div>`;
if (obj.noIcon && !isSingleListCommandLineActive()) {
iconHTML = "";
}
let customStyle = "";
if (obj.customStyle !== undefined && obj.customStyle !== "") {
customStyle = obj.customStyle;
}
if (obj.id.startsWith("changeTheme") && obj.customData) {
commandsHTML += `<div class="entry withThemeBubbles" command="${obj.id}" index="${index}" style="${customStyle}">
${iconHTML}<div>${obj.display}</div>
<div class="themeBubbles" style="background: ${obj.customData["bgColor"]};outline: 0.25rem solid ${obj.customData["bgColor"]};">
<div class="themeBubble" style="background: ${obj.customData["mainColor"]}"></div>
<div class="themeBubble" style="background: ${obj.customData["subColor"]}"></div>
<div class="themeBubble" style="background: ${obj.customData["textColor"]}"></div>
</div>
</div>`;
} else {
commandsHTML += `<div class="entry" command="${obj.id}" index="${index}" style="${customStyle}">${iconHTML}<div>${obj.display}</div></div>`;
}
index++;
}
}
$("#commandLine .suggestions").html(commandsHTML);
if ($("#commandLine .suggestions .entry").length === 0) {
$("#commandLine .separator").css({ height: 0, margin: 0 });
} else {
$("#commandLine .separator").css({
height: "1px",
"margin-bottom": ".5rem",
});
}
const entries = $("#commandLine .suggestions .entry");
if (entries.length > 0) {
try {
for (const obj of list.list) {
if (obj.found) {
if (/changeTheme.+/gi.test(obj.id)) {
removeCommandlineBackground();
} else {
addCommandlineBackground();
}
if (
(!/theme/gi.test(obj.id) || obj.id === "toggleCustomTheme") &&
!(ThemeController.randomTheme ?? "")
) {
void ThemeController.clearPreview();
}
if (!/font/gi.test(obj.id)) {
UpdateConfig.previewFontFamily(Config.fontFamily);
}
if (obj.hover && !obj.id.startsWith("changeTheme")) obj.hover();
return;
}
}
} catch (e) {}
}
$("#commandLine .listTitle").remove();
}
function updateSuggested(): void {
const rawInputStr = $("#commandLine input").val() as string;
const inputVal = rawInputStr
.toLowerCase()
.split(" ")
.filter((s, i) => s || i === 0); //remove empty entries after first
const list = CommandlineLists.getCurrent();
if (list.beforeList) list.beforeList();
if (
inputVal[0] === "" &&
Config.singleListCommandLine === "on" &&
CommandlineLists.current.length === 1
) {
for (const obj of list.list) {
obj.found = false;
}
showFound();
return;
}
// -1 means that we can set the activeIndex as normal at the end
// otherwise, this is what to set activeIndex to
let setIndex = -1;
//ignore the preceeding ">"s in the command line input
if (inputVal[0]?.startsWith(">")) {
inputVal[0] = inputVal[0].replace(/^>+/, "");
}
if (inputVal[0] === "" && inputVal.length === 1) {
for (const obj of list.list) {
if (obj.visible !== false) obj.found = true;
}
} else {
let shownItemsCount = 0;
for (const lItem of list.list) {
let foundcount = 0;
for (const obj2 of inputVal) {
if (obj2 === "") return;
const escaped = obj2.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
const re = new RegExp("\\b" + escaped, "g");
const res = lItem.display.toLowerCase().match(re);
const res2 =
lItem.alias !== undefined
? lItem.alias.toLowerCase().match(re)
: null;
if (lItem.display === rawInputStr) {
setIndex = shownItemsCount;
foundcount = inputVal.length;
break;
} else if (
(res != null && res.length > 0) ||
(res2 != null && res2.length > 0)
) {
foundcount++;
} else {
foundcount--;
}
}
if (foundcount > inputVal.length - 1) {
lItem.found = true;
shownItemsCount++;
} else {
lItem.found = false;
}
}
}
showFound();
if (setIndex !== -1) {
activeIndex = setIndex;
} else {
// display background hover effect for selected language
const scrollTarget = $(".suggestions .entry .icon i.fa-check");
const entryIndex = scrollTarget.parent().parent().attr("index");
if (entryIndex !== undefined) {
activeIndex = parseInt(entryIndex);
} else {
activeIndex = 0;
}
}
updateActiveEntry();
keepActiveEntryInView();
}
function show(): void {
themeChosen = false;
activeIndex = 0;
commandLineMouseMode = false;
//take last element of array
if (isElementVisible(".page.pageLoading")) return;
Focus.set(false);
Skeleton.append(wrapperId, "popups");
$("#commandLine").removeClass("hidden");
$("#commandInput").addClass("hidden");
if (state.usingSingleList) {
$("#commandLine").addClass("allCommands");
}
if (!isPopupVisible(wrapperId)) {
$("#commandLineWrapper")
.stop(true, true)
.css("opacity", 0)
.removeClass("hidden")
.animate(
{
opacity: 1,
},
125
);
}
$("#commandLine input").val("");
updateSuggested();
$("#commandLine input").trigger("focus");
}
function hide(shouldFocusTestUI = true): void {
UpdateConfig.previewFontFamily(Config.fontFamily);
// applyCustomThemeColors();
if (!(ThemeController.randomTheme ?? "")) {
void ThemeController.clearPreview();
}
$("#commandLineWrapper")
.stop(true, true)
.css("opacity", 1)
.animate(
{
opacity: 0,
},
125,
() => {
addCommandlineBackground();
$("#commandLineWrapper").addClass("hidden");
$("#commandLine").removeClass("allCommands");
state.usingSingleList = false;
if (shouldFocusTestUI) {
TestUI.focusWords();
}
Skeleton.remove(wrapperId);
}
);
if (shouldFocusTestUI) {
TestUI.focusWords();
}
}
function trigger(command: string): void {
let subgroup = false;
let input = false;
let shouldFocusTestUI = true;
const list = CommandlineLists.getCurrent();
let sticky = false;
ManualRestart.set();
for (const obj of list.list) {
if (obj.id === command) {
if (obj.shouldFocusTestUI !== undefined) {
shouldFocusTestUI = obj.shouldFocusTestUI;
}
if (obj.input) {
input = true;
const escaped = obj.display.split("</i>")[1] ?? obj.display;
showInput(obj.id, escaped, obj.defaultValue ? obj.defaultValue() : "");
} else if (obj.subgroup) {
subgroup = true;
if (obj.subgroup.beforeList) {
obj.subgroup.beforeList();
}
CommandlineLists.current.push(
obj.subgroup as MonkeyTypes.CommandsSubgroup
);
show();
} else {
if (obj.exec) obj.exec();
if (obj.sticky === true) {
sticky = true;
}
}
}
}
if (!subgroup && !input && !sticky) {
void AnalyticsController.log("usedCommandLine", { command });
hide(shouldFocusTestUI);
}
}
function getCommands(
command: MonkeyTypes.Command,
parentCommand?: MonkeyTypes.Command
): MonkeyTypes.Command[] {
const ret: MonkeyTypes.Command[] = [];
if (command.subgroup) {
const currentCommand = {
...command,
subgroup: {
...command.subgroup,
list: [],
},
};
command.subgroup.beforeList?.();
for (const cmd of command.subgroup.list) {
ret.push(...getCommands(cmd, currentCommand));
}
} else {
if (parentCommand) {
const parentCommandDisplay = parentCommand.display.replace(
/\s?\.\.\.$/g,
""
);
let configIcon = "";
const parentKey = parentCommand.subgroup?.configKey;
const currentValue = command.configValue;
if (parentKey !== undefined && currentValue !== undefined) {
if (
(command.configValueMode === "include" &&
(Config[parentKey] as unknown[]).includes(currentValue)) ||
Config[parentKey] === currentValue
) {
configIcon = `<i class="fas fa-fw fa-check"></i>`;
} else {
configIcon = `<i class="fas fa-fw"></i>`;
}
}
const displayString =
parentCommandDisplay +
" > " +
(command.noIcon ? "" : configIcon) +
command.display;
const newCommand = {
...command,
display: displayString,
icon: parentCommand.icon,
alias: (parentCommand.alias ?? "") + " " + (command.alias ?? ""),
visible: (parentCommand.visible ?? true) && (command.visible ?? true),
available: (): boolean => {
return (
(parentCommand?.available?.() ?? true) &&
(command?.available?.() ?? true)
);
},
};
ret.push(newCommand);
} else {
ret.push(command);
}
}
return ret;
}
function generateSingleListOfCommands(): {
title: string;
list: MonkeyTypes.Command[];
} {
const allCommands: MonkeyTypes.Command[] = [];
for (const command of CommandlineLists.commands.list) {
allCommands.push(...getCommands(command));
}
return {
title: "All Commands",
list: allCommands,
};
}
function useSingleListCommandLine(sshow = true): void {
const allCommands = generateSingleListOfCommands();
// if (Config.singleListCommandLine === "manual") {
// CommandlineLists.pushCurrent(allCommands);
// } else if (Config.singleListCommandLine === "on") {
CommandlineLists.setCurrent([allCommands]);
// }
if (Config.singleListCommandLine != "manual") {
state.usingSingleList = true;
$("#commandLine").addClass("allCommands");
}
if (sshow) show();
}
function restoreOldCommandLine(sshow = true): void {
if (isSingleListCommandLineActive()) {
state.usingSingleList = false;
$("#commandLine").removeClass("allCommands");
CommandlineLists.setCurrent(
CommandlineLists.current.filter((l) => l.title != "All Commands")
);
if (CommandlineLists.current.length < 1) {
CommandlineLists.setCurrent([CommandlineLists.commands]);
}
}
if (sshow) show();
}
function updateActiveEntry(): void {
$(`#commandLineWrapper #commandLine .suggestions .entry`).removeClass(
"active"
);
$(
`#commandLineWrapper #commandLine .suggestions .entry[index=${activeIndex}]`
).addClass("active");
}
function keepActiveEntryInView(): void {
try {
const scroll =
Math.abs(
($(".suggestions").offset()?.top as number) -
($(".entry.active").offset()?.top as number) -
($(".suggestions").scrollTop() as number)
) -
($(".suggestions").outerHeight() as number) / 2 +
($($(".entry")[0] as HTMLElement).outerHeight() as number);
$(".suggestions").scrollTop(scroll);
} catch (e) {
if (e instanceof Error) {
console.log("could not scroll suggestions: " + e.message);
}
}
}
$("#commandLineWrapper #commandLine input").on("input", () => {
commandLineMouseMode = false;
updateSuggested();
});
$(document).ready(() => {
$(document).on("keydown", (event) => {
if (PageTransition.get()) return;
// opens command line if escape or ctrl/cmd + shift + p
if (
((event.key === "Escape" && Config.quickRestart !== "esc") ||
(event.key?.toLowerCase() === "p" &&
(event.metaKey || event.ctrlKey) &&
event.shiftKey) ||
((event.key === "Tab" || event.key === "Escape") &&
Config.quickRestart === "esc")) &&
isPopupVisible(wrapperId)
) {
if (CommandlineLists.current.length > 1) {
CommandlineLists.current.pop();
state.usingSingleList = false;
$("#commandLine").removeClass("allCommands");
show();
} else {
hide();
}
UpdateConfig.previewFontFamily(Config.fontFamily);
return;
}
if (
(event.key === "Escape" && Config.quickRestart !== "esc") ||
(event.key === "Tab" &&
Config.quickRestart === "esc" &&
!TestWords.hasTab &&
!event.shiftKey) ||
(event.key === "Tab" &&
Config.quickRestart === "esc" &&
TestWords.hasTab &&
event.shiftKey) ||
(event.key &&
event.key.toLowerCase() === "p" &&
(event.metaKey || event.ctrlKey) &&
event.shiftKey)
) {
const popupVisible = isAnyPopupVisible();
const miniResultPopupVisible = isElementVisible(
".pageAccount .miniResultChartWrapper"
);
if (popupVisible || miniResultPopupVisible) return;
if (Config.quickRestart === "esc" && ActivePage.get() === "login") return;
event.preventDefault();
if (Config.singleListCommandLine === "on") {
useSingleListCommandLine(false);
} else {
CommandlineLists.setCurrent([CommandlineLists.commands]);
}
show();
}
});
});
$("#commandInput input").on("keydown", (e) => {
if (e.key === "Enter") {
//enter
e.preventDefault();
const command = $("#commandInput input").attr("command");
const value = $("#commandInput input").val() as string;
const list = CommandlineLists.getCurrent();
for (const obj of list.list) {
if (obj.id === command) {
if (obj.exec) obj.exec(value);
}
}
void AnalyticsController.log("usedCommandLine", { command: command ?? "" });
hide();
}
return;
});
$(document).on("mousemove", () => {
if (!commandLineMouseMode) commandLineMouseMode = true;
});
$("#commandLineWrapper #commandLine").on(
"mouseenter",
".suggestions .entry",
(e) => {
if (!commandLineMouseMode) return;
activeIndex = parseInt($(e.target).attr("index") ?? "0");
updateActiveEntry();
}
);
$("#commandLineWrapper #commandLine").on(
"mouseleave",
".suggestions .entry",
(e) => {
if (!commandLineMouseMode) return;
activeIndex = parseInt($(e.target).attr("index") ?? "0");
updateActiveEntry();
}
);
$("#commandLineWrapper #commandLine .suggestions").on("mouseover", (e) => {
if (!commandLineMouseMode) return;
const hoverId = $(e.target).attr("command");
try {
const list = CommandlineLists.getCurrent();
for (const obj of list.list) {
if (obj.id === hoverId) {
if (/changeTheme.+/gi.test(obj.id)) {
removeCommandlineBackground();
} else {
addCommandlineBackground();
}
if (
(!/theme/gi.test(obj.id) || obj.id === "toggleCustomTheme") &&
!(ThemeController.randomTheme ?? "")
) {
void ThemeController.clearPreview();
}
if (!/font/gi.test(obj.id)) {
UpdateConfig.previewFontFamily(Config.fontFamily);
}
if (obj.hover && !themeChosen) obj.hover();
}
}
} catch (e) {}
});
$("#commandLineWrapper #commandLine").on(
"click",
".suggestions .entry",
(e) => {
themeChosen = true;
trigger($(e.currentTarget).attr("command") as string);
}
);
$("#commandLineWrapper").on("mousedown", (e) => {
if ($(e.target).attr("id") === "commandLineWrapper") {
hide();
UpdateConfig.previewFontFamily(Config.fontFamily);
// if (Config.customTheme === true) {
// applyCustomThemeColors();
// } else {
// setTheme(Config.theme, true);
// }
}
});
//might come back to it later
// function shiftCommand(){
// let activeEntries = $("#commandLineWrapper #commandLine .suggestions .entry.active");
// activeEntries.each((_index, activeEntry) => {
// let commandId = activeEntry.getAttribute('command');
// let foundCommand = null;
// CommandlineLists.defaultCommands.list.forEach(command => {
// if(foundCommand === null && command.id === commandId){
// foundCommand = command;
// }
// })
// if(foundCommand.shift){
// $(activeEntry).find('div').text(foundCommand.shift.display);
// }
// })
// }
// let shiftedCommands = false;
// $(document).on("keydown", (e) => {
// if (e.key === "Shift") {
// if(shiftedCommands === false){
// shiftedCommands = true;
// shiftCommand();
// }
// }
// });
// $(document).keyup((e) => {
// if (e.key === "Shift") {
// shiftedCommands = false;
// }
// });
$(document).on("keydown", (e) => {
// if (isPreviewingTheme) {
// console.log("applying theme");
// applyCustomThemeColors();
// previewTheme(Config.theme, false);
// }
if (isPopupVisible(wrapperId)) {
$("#commandLine input").trigger("focus");
commandLineMouseMode = false;
if (e.key === ">" && Config.singleListCommandLine === "manual") {
if (!isSingleListCommandLineActive()) {
state.usingSingleList = true;
useSingleListCommandLine(false);
return;
} else if ($("#commandLine input").val() === ">") {
//so that it will ignore succeeding ">" when input is already ">"
e.preventDefault();
return;
}
}
if (e.key === "Backspace" || e.key === "Delete") {
setTimeout(() => {
const inputVal = $("#commandLine input").val() as string;
if (
Config.singleListCommandLine === "manual" &&
isSingleListCommandLineActive() &&
!inputVal.startsWith(">")
) {
restoreOldCommandLine(false);
updateSuggested();
}
}, 1);
}
if (e.key === "Enter") {
//enter
e.preventDefault();
const command = $(".suggestions .entry.active").attr("command") as string;
trigger(command);
return;
}
if (
e.key === "ArrowUp" ||
e.key === "ArrowDown" ||
e.key === "Tab" ||
// Should only branch if ctrl is held to allow the letters to still be typed
(e.ctrlKey &&
(e.key === "p" || e.key === "n" || e.key === "j" || e.key === "k"))
) {
e.preventDefault();
$("#commandLineWrapper #commandLine .suggestions .entry").off(
"mouseenter mouseleave"
);
const entries = $(".suggestions .entry");
if (
e.key === "ArrowUp" ||
(e.key === "Tab" && e.shiftKey && Config.quickRestart !== "esc") ||
// Don't need to check for ctrl because that was already done above
e.key === "p" ||
e.key === "k"
) {
if (activeIndex === 0) {
activeIndex = entries.length - 1;
} else {
activeIndex--;
}
}
if (
e.key === "ArrowDown" ||
(e.key === "Tab" && !e.shiftKey && Config.quickRestart !== "esc") ||
e.key === "n" ||
e.key === "j"
) {
if (activeIndex + 1 === entries.length) {
activeIndex = 0;
} else {
activeIndex++;
}
}
updateActiveEntry();
keepActiveEntryInView();
try {
const list = CommandlineLists.getCurrent();
const activeCommandId = $(
"#commandLineWrapper #commandLine .suggestions .entry.active"
).attr("command");
for (const obj of list.list) {
if (obj.id === activeCommandId) {
if (/changeTheme.+/gi.test(obj.id)) {
removeCommandlineBackground();
} else {
addCommandlineBackground();
}
if (
(!/theme/gi.test(obj.id) || obj.id === "toggleCustomTheme") &&
!(ThemeController.randomTheme ?? "")
) {
void ThemeController.clearPreview();
}
if (!/font/gi.test(obj.id)) {
UpdateConfig.previewFontFamily(Config.fontFamily);
}
if (obj.hover) obj.hover();
}
}
} catch (e) {}
return false;
}
}
return;
});
$("#commandLineMobileButton").on("click", () => {
if (Config.singleListCommandLine === "on") {
useSingleListCommandLine(false);
} else {
CommandlineLists.setCurrent([CommandlineLists.commands]);
}
show();
});
$("#keymap").on("click", ".r5 .keySpace", () => {
CommandlineLists.setCurrent([CommandlineLists.getList("keymapLayouts")]);
show();
});
$(".pageTest").on("click", "#testModesNotice .textButton", (event) => {
const attr = $(event.currentTarget).attr(
"commands"
) as CommandlineLists.ListsObjectKeys;
if (attr === undefined) return;
const commands = CommandlineLists.getList(attr);
if (commands !== undefined) {
CommandlineLists.pushCurrent(commands);
show();
}
});
$("footer").on("click", ".leftright .right .current-theme", (e) => {
if (e.shiftKey) {
if (!Config.customTheme) {
if (isAuthenticated()) {
if ((DB.getSnapshot()?.customThemes?.length ?? 0) < 1) {
Notifications.add("No custom themes!", 0);
UpdateConfig.setCustomTheme(false);
// UpdateConfig.setCustomThemeId("");
return;
}
// if (!DB.getCustomThemeById(Config.customThemeId)) {
// // Turn on the first custom theme
// const firstCustomThemeId = DB.getSnapshot().customThemes[0]._id;
// UpdateConfig.setCustomThemeId(firstCustomThemeId);
// }
}
UpdateConfig.setCustomTheme(true);
} else UpdateConfig.setCustomTheme(false);
} else {
if (Config.customTheme) updateCustomThemesList();
CommandlineLists.setCurrent([
Config.customTheme
? CommandlineLists.getList("customThemesList")
: CommandlineLists.getList("themes"),
]);
show();
}
});
$(".supportButtons button.ads").on("click", () => {
CommandlineLists.pushCurrent(CommandlineLists.getList("enableAds"));
show();
});
$(document.body).on("click", "#supportMeWrapper button.ads", () => {
CommandlineLists.pushCurrent(CommandlineLists.getList("enableAds"));
show();
});
Skeleton.save(wrapperId);

View file

@ -102,7 +102,8 @@ import * as TestStats from "../test/test-stats";
import * as QuoteSearchPopup from "../popups/quote-search-popup";
import * as FPSCounter from "../elements/fps-counter";
Misc.getLayoutsList()
const layoutsPromise = Misc.getLayoutsList();
layoutsPromise
.then((layouts) => {
updateLayoutsCommands(layouts);
updateKeymapLayoutsCommands(layouts);
@ -113,7 +114,8 @@ Misc.getLayoutsList()
);
});
Misc.getLanguageList()
const languagesPromise = Misc.getLanguageList();
languagesPromise
.then((languages) => {
updateLanguagesCommands(languages);
})
@ -123,7 +125,8 @@ Misc.getLanguageList()
);
});
Misc.getFunboxList()
const funboxPromise = Misc.getFunboxList();
funboxPromise
.then((funboxes) => {
updateFunboxCommands(funboxes);
if (FunboxCommands[0]?.subgroup) {
@ -138,7 +141,8 @@ Misc.getFunboxList()
);
});
Misc.getFontsList()
const fontsPromise = Misc.getFontsList();
fontsPromise
.then((fonts) => {
updateFontFamilyCommands(fonts);
})
@ -148,7 +152,8 @@ Misc.getFontsList()
);
});
Misc.getThemesList()
const themesPromise = Misc.getThemesList();
themesPromise
.then((themes) => {
updateThemesCommands(themes);
})
@ -158,7 +163,8 @@ Misc.getThemesList()
);
});
Misc.getChallengeList()
const challengesPromise = Misc.getChallengeList();
challengesPromise
.then((challenges) => {
updateLoadChallengeCommands(challenges);
})
@ -491,6 +497,10 @@ const lists = {
blindMode: BlindModeCommands[0]?.subgroup,
};
export function doesListExist(listName: string): boolean {
return lists[listName as ListsObjectKeys] !== undefined;
}
export function getList(
listName: ListsObjectKeys
): MonkeyTypes.CommandsSubgroup {
@ -502,20 +512,140 @@ export function getList(
return list;
}
export let current: MonkeyTypes.CommandsSubgroup[] = [];
let stack: MonkeyTypes.CommandsSubgroup[] = [];
current = [commands];
stack = [commands];
export function getStackLength(): number {
return stack.length;
}
export type ListsObjectKeys = keyof typeof lists;
export function setCurrent(val: MonkeyTypes.CommandsSubgroup[]): void {
current = val;
export function setStackToDefault(): void {
setStack([commands]);
}
export function pushCurrent(val: MonkeyTypes.CommandsSubgroup): void {
current.push(val);
export function setStack(val: MonkeyTypes.CommandsSubgroup[]): void {
stack = val;
}
export function getCurrent(): MonkeyTypes.CommandsSubgroup {
return current[current.length - 1] as MonkeyTypes.CommandsSubgroup;
export function pushToStack(val: MonkeyTypes.CommandsSubgroup): void {
stack.push(val);
}
export function popFromStack(): void {
stack.pop();
}
export function getTopOfStack(): MonkeyTypes.CommandsSubgroup {
return stack[stack.length - 1] as MonkeyTypes.CommandsSubgroup;
}
let singleList: MonkeyTypes.CommandsSubgroup | undefined;
export async function getSingleSubgroup(): Promise<MonkeyTypes.CommandsSubgroup> {
await Promise.allSettled([
layoutsPromise,
languagesPromise,
funboxPromise,
fontsPromise,
themesPromise,
challengesPromise,
]);
if (singleList) return singleList;
// const
const singleCommands: MonkeyTypes.Command[] = [];
const beforeListFunctions: (() => void)[] = [];
for (const command of commands.list) {
const ret = buildSingleListCommands(command);
singleCommands.push(...ret.commands);
beforeListFunctions.push(...ret.beforeListFunctions);
}
singleList = {
title: "All commands",
list: singleCommands,
beforeList: (): void => {
for (const func of beforeListFunctions) {
func();
}
},
};
return singleList;
}
type SingleList = {
commands: MonkeyTypes.Command[];
beforeListFunctions: (() => void)[];
};
function buildSingleListCommands(
command: MonkeyTypes.Command,
parentCommand?: MonkeyTypes.Command
): SingleList {
const commands: MonkeyTypes.Command[] = [];
const beforeListFunctions: (() => void)[] = [];
if (command.subgroup) {
const currentCommand = {
...command,
subgroup: {
...command.subgroup,
list: [],
},
};
if (command.subgroup.beforeList) {
beforeListFunctions.push(command.subgroup.beforeList);
}
for (const cmd of command.subgroup.list) {
commands.push(...buildSingleListCommands(cmd, currentCommand).commands);
}
} else {
if (parentCommand) {
const parentCommandDisplay = parentCommand.display.replace(
/\s?\.\.\.$/g,
""
);
const singleListDisplay =
parentCommandDisplay +
'<i class="fas fa-fw fa-chevron-right chevronIcon"></i>' +
command.display;
const singleListDisplayNoIcon =
parentCommandDisplay + " " + command.display;
let newAlias: string | undefined = undefined;
if ((parentCommand.alias ?? "") || (command.alias ?? "")) {
newAlias = [parentCommand.alias, command.alias]
.filter(Boolean)
.join(" ");
}
const newCommand = {
...command,
singleListDisplay,
singleListDisplayNoIcon,
configKey: parentCommand.subgroup?.configKey,
icon: parentCommand.icon,
alias: newAlias,
visible: (parentCommand.visible ?? true) && (command.visible ?? true),
available: (): boolean => {
return (
(parentCommand?.available?.() ?? true) &&
(command?.available?.() ?? true)
);
},
};
commands.push(newCommand);
} else {
commands.push(command);
}
}
return {
commands,
beforeListFunctions,
};
}

View file

@ -1,4 +1,5 @@
import Config, * as UpdateConfig from "../../config";
import * as UpdateConfig from "../../config";
import * as UI from "../../ui";
const subgroup: MonkeyTypes.CommandsSubgroup = {
title: "Font family...",
@ -24,7 +25,7 @@ function update(fonts: MonkeyTypes.FontObject[]): void {
configValue: configVal,
customStyle: `font-family: ${font.name}`,
hover: (): void => {
UpdateConfig.previewFontFamily(font.name);
UI.previewFontFamily(font.name);
},
exec: (): void => {
UpdateConfig.setFontFamily(font.name.replace(/ /g, "_"));
@ -36,7 +37,7 @@ function update(fonts: MonkeyTypes.FontObject[]): void {
display: "custom...",
input: true,
hover: (): void => {
UpdateConfig.previewFontFamily(Config.fontFamily);
UI.clearFontPreview();
},
exec: (name) => {
if (name === undefined || name === "") return;

View file

@ -2,6 +2,7 @@ import * as Funbox from "../../test/funbox/funbox";
import * as TestLogic from "../../test/test-logic";
import * as ManualRestart from "../../test/manual-restart-tracker";
import Config from "../../config";
import { areFunboxesCompatible } from "../../test/funbox/funbox-validation";
const subgroup: MonkeyTypes.CommandsSubgroup = {
title: "Funbox...",
@ -38,6 +39,7 @@ function update(funboxes: MonkeyTypes.FunboxMetadata[]): void {
display: "none",
configValue: "none",
alias: "off",
sticky: true,
exec: (): void => {
ManualRestart.set();
if (Funbox.setFunbox("none")) {
@ -45,71 +47,25 @@ function update(funboxes: MonkeyTypes.FunboxMetadata[]): void {
}
},
});
funboxes.forEach((funbox) => {
let dis;
if (Config.funbox.includes(funbox.name)) {
dis =
'<i class="fas fa-fw fa-check"></i>' + funbox.name.replace(/_/g, " ");
} else {
dis = '<i class="fas fa-fw"></i>' + funbox.name.replace(/_/g, " ");
}
for (const funbox of funboxes) {
subgroup.list.push({
id: "changeFunbox" + funbox.name,
noIcon: true,
display: dis,
// visible: Funbox.isFunboxCompatible(funbox.name, funbox.type),
display: funbox.name.replace(/_/g, " "),
available: () => {
if (Config.funbox.split("#").includes(funbox.name)) return true;
return areFunboxesCompatible(Config.funbox, funbox.name);
},
sticky: true,
alias: funbox.alias,
configValue: funbox.name,
configValueMode: "include",
exec: (): void => {
Funbox.toggleFunbox(funbox.name);
ManualRestart.set();
TestLogic.restart();
for (const funbox of funboxes) {
// subgroup.list[i].visible = Funbox.isFunboxCompatible(funboxes[i].name, funboxes[i].type);
let txt = funbox.name.replace(/_/g, " ");
if (Config.funbox.includes(funbox.name)) {
txt = '<i class="fas fa-fw fa-check"></i>' + txt;
} else {
txt = '<i class="fas fa-fw"></i>' + txt;
}
if ($("#commandLine").hasClass("allCommands")) {
$(
`#commandLine .suggestions .entry[command='changeFunbox${funbox.name}']`
).html(
`<div class="icon"><i class="fas fa-fw fa-gamepad"></i></div><div>Funbox > ` +
txt
);
} else {
$(
`#commandLine .suggestions .entry[command='changeFunbox${funbox.name}']`
).html(txt);
}
}
if (funboxes.length > 0) {
const noneTxt =
Config.funbox === "none"
? `<i class="fas fa-fw fa-check"></i>none`
: `<i class="fas fa-fw"></i>none`;
if ($("#commandLine").hasClass("allCommands")) {
$(
`#commandLine .suggestions .entry[command='changeFunboxNone']`
).html(
`<div class="icon"><i class="fas fa-fw fa-gamepad"></i></div><div>Funbox > ` +
noneTxt
);
} else {
$(
`#commandLine .suggestions .entry[command='changeFunboxNone']`
).html(noneTxt);
}
}
},
});
});
}
}
export default commands;

View file

@ -21,7 +21,6 @@ function update(challenges: MonkeyTypes.Challenge[]): void {
challenges.forEach((challenge) => {
subgroup.list.push({
id: "loadChallenge" + capitalizeFirstLetterOfEachWord(challenge.name),
noIcon: true,
display: challenge.display,
exec: async (): Promise<void> => {
navigate("/");

View file

@ -5,7 +5,7 @@ const commands: MonkeyTypes.Command[] = [
{
id: "viewTypingPage",
display: "View Typing Page",
alias: "start begin type test",
alias: "navigate go to start begin type test",
icon: "fa-keyboard",
exec: (): void => {
navigate("/");
@ -14,6 +14,7 @@ const commands: MonkeyTypes.Command[] = [
{
id: "viewLeaderboards",
display: "View Leaderboards",
alias: "navigate go to",
icon: "fa-crown",
exec: (): void => {
$("header nav .textButton.view-leaderboards").trigger("click");
@ -22,6 +23,7 @@ const commands: MonkeyTypes.Command[] = [
{
id: "viewAbout",
display: "View About Page",
alias: "navigate go to",
icon: "fa-info",
exec: (): void => {
navigate("/about");
@ -30,6 +32,7 @@ const commands: MonkeyTypes.Command[] = [
{
id: "viewSettings",
display: "View Settings Page",
alias: "navigate go to",
icon: "fa-cog",
exec: (): void => {
navigate("/settings");
@ -39,8 +42,8 @@ const commands: MonkeyTypes.Command[] = [
{
id: "viewAccount",
display: "View Account Page",
alias: "navigate go to stats",
icon: "fa-user",
alias: "stats",
exec: (): void => {
$("header nav .textButton.view-account").hasClass("hidden")
? navigate("/login")

View file

@ -66,19 +66,10 @@ function update(): void {
},
});
DB.getSnapshot()?.tags?.forEach((tag) => {
let dis = tag.display;
if (tag.active === true) {
dis = '<i class="fas fa-fw fa-check"></i>' + dis;
} else {
dis = '<i class="fas fa-fw"></i>' + dis;
}
for (const tag of snapshot.tags) {
subgroup.list.push({
id: "toggleTag" + tag._id,
noIcon: true,
display: dis,
display: tag.display,
sticky: true,
exec: async (): Promise<void> => {
TagController.toggle(tag._id);
@ -88,29 +79,9 @@ function update(): void {
await PaceCaret.init();
void ModesNotice.update();
}
let txt = tag.display;
if (tag.active === true) {
txt = '<i class="fas fa-fw fa-check"></i>' + txt;
} else {
txt = '<i class="fas fa-fw"></i>' + txt;
}
if ($("#commandLine").hasClass("allCommands")) {
$(
`#commandLine .suggestions .entry[command='toggleTag${tag._id}']`
).html(
`<div class="icon"><i class="fas fa-fw fa-tag"></i></div><div>Tags > ` +
txt
);
} else {
$(
`#commandLine .suggestions .entry[command='toggleTag${tag._id}']`
).html(txt);
}
},
});
});
}
subgroup.list.push({
id: "createTag",
display: "Create tag",

View file

@ -1232,19 +1232,6 @@ export function setQuickRestartMode(
return true;
}
export function previewFontFamily(font: string): boolean {
if (!isConfigValueValid("preview font family", font, ["string"])) {
return false;
}
document.documentElement.style.setProperty(
"--font",
'"' + font.replace(/_/g, " ") + '", "Roboto Mono", "Vazirmatn"'
);
return true;
}
//font family
export function setFontFamily(font: string, nosave?: boolean): boolean {
if (!isConfigValueValid("font family", font, ["string"])) return false;

View file

@ -0,0 +1,39 @@
import Config, * as UpdateConfig from "../config";
import { isAuthenticated } from "../firebase";
import * as DB from "../db";
import * as Notifications from "../elements/notifications";
import { getCommandline } from "../utils/async-modules";
document
.querySelector("footer #commandLineButton")
?.addEventListener("click", async () => {
(await getCommandline()).show({
singleListOverride: false,
});
});
document
.querySelector("footer .right .current-theme")
?.addEventListener("click", async (event) => {
const e = event as MouseEvent;
if (e.shiftKey) {
if (Config.customTheme) {
UpdateConfig.setCustomTheme(false);
return;
}
if (
isAuthenticated() &&
(DB.getSnapshot()?.customThemes?.length ?? 0) < 1
) {
Notifications.add("No custom themes!", 0);
UpdateConfig.setCustomTheme(false);
return;
}
UpdateConfig.setCustomTheme(true);
} else {
const subgroup = Config.customTheme ? "customThemesList" : "themes";
(await getCommandline()).show({
subgroupOverride: subgroup,
});
}
});

View file

@ -0,0 +1,30 @@
import * as Misc from "../utils/misc";
import * as PageTransition from "../states/page-transition";
import Config from "../config";
import * as TestWords from "../test/test-words";
import { getCommandline } from "../utils/async-modules";
document.addEventListener("keydown", async (e) => {
if (PageTransition.get()) return;
if (
(e.key === "Escape" && Config.quickRestart !== "esc") ||
(e.key === "Tab" &&
Config.quickRestart === "esc" &&
!TestWords.hasTab &&
!e.shiftKey) ||
(e.key === "Tab" &&
Config.quickRestart === "esc" &&
TestWords.hasTab &&
e.shiftKey) ||
(e.key.toLowerCase() === "p" && (e.metaKey || e.ctrlKey) && e.shiftKey)
) {
const popupVisible = Misc.isAnyPopupVisible();
const miniResultPopupVisible = Misc.isElementVisible(
".pageAccount .miniResultChartWrapper"
);
if (!popupVisible && !miniResultPopupVisible) {
(await getCommandline()).show();
}
}
});

View file

@ -0,0 +1,7 @@
import { getCommandline } from "../utils/async-modules";
$("#keymap").on("click", ".r5 .keySpace", async () => {
(await getCommandline()).show({
subgroupOverride: "keymapLayouts",
});
});

View file

@ -0,0 +1,5 @@
import { getCommandline } from "../utils/async-modules";
$("#popups").on("click", "#supportMeWrapper button.ads", async () => {
(await getCommandline()).show({ subgroupOverride: "enableAds" });
});

View file

@ -0,0 +1,7 @@
import { getCommandline } from "../utils/async-modules";
$(".pageTest").on("click", "#testModesNotice .textButton", async (event) => {
const attr = $(event.currentTarget).attr("commands");
if (attr === undefined) return;
(await getCommandline()).show({ subgroupOverride: attr });
});

View file

@ -34,7 +34,6 @@ import "./popups/google-sign-up-popup";
import "./popups/result-tags-popup";
import * as Account from "./pages/account";
import "./elements/leaderboards";
import "./commandline/index";
import "./elements/no-css";
import { egVideoListener } from "./popups/video-ad-popup";
import "./states/connection";
@ -43,6 +42,11 @@ import "./elements/fps-counter";
import "./controllers/profile-search-controller";
import "./version";
import { isDevEnvironment } from "./utils/misc";
import "./event-handlers/global";
import "./event-handlers/footer";
import "./event-handlers/keymap";
import "./event-handlers/test";
import "./event-handlers/popups";
function addToGlobal(items: Record<string, unknown>): void {
for (const [name, item] of Object.entries(items)) {

View file

@ -56,6 +56,10 @@ export default class AnimatedModal {
}
this.skeletonAppendParent = appendTo;
if (Skeleton.has(wrapperId)) {
Skeleton.append(wrapperId, this.skeletonAppendParent);
}
const dialogElement = document.getElementById(wrapperId);
const modalElement = document.querySelector(
`#${wrapperId} > .modal`

View file

@ -14,6 +14,7 @@ import Konami from "konami";
import { log } from "./controllers/analytics-controller";
import { envConfig } from "./constants/env-config";
import * as ServerConfiguration from "./ape/server-configuration";
import * as Skeleton from "./utils/skeleton";
if (Misc.isDevEnvironment()) {
$("footer .currentVersion .text").text("localhost");
@ -108,6 +109,8 @@ $(document).ready(() => {
}
});
}
Skeleton.save("commandLine");
});
window.onerror = function (message, url, line, column, error): void {

View file

@ -63,10 +63,9 @@ ConfigEvent.subscribe((eventKey, eventValue, nosave) => {
void debouncedZipfCheck();
}
if (eventKey === "fontSize" && !nosave) {
setTimeout(() => {
updateWordsHeight(true);
updateWordsInputPosition(true);
}, 0);
OutOfFocus.hide();
updateWordsHeight(true);
updateWordsInputPosition(true);
}
if (eventKey === "theme") void applyBurstHeatmap();
@ -128,9 +127,7 @@ export function reset(): void {
}
export function focusWords(): void {
if (!$("#wordsWrapper").hasClass("hidden")) {
$("#wordsInput").trigger("focus");
}
$("#wordsInput").trigger("focus");
}
export function blurWords(): void {

View file

@ -296,21 +296,23 @@ declare namespace MonkeyTypes {
type Command = {
id: string;
display: string;
singleListDisplay?: string;
singleListDisplayNoIcon?: string;
subgroup?: CommandsSubgroup;
found?: boolean;
icon?: string;
noIcon?: boolean;
sticky?: boolean;
alias?: string;
input?: boolean;
visible?: boolean;
customStyle?: string;
defaultValue?: () => string;
configKey?: keyof SharedTypes.Config;
configValue?: string | number | boolean | number[];
configValueMode?: string;
configValueMode?: "include";
exec?: (input?: string) => void;
hover?: () => void;
available?: () => void;
available?: () => boolean;
shouldFocusTestUI?: boolean;
customData?: Record<string, string>;
};

View file

@ -9,6 +9,21 @@ import * as TestUI from "./test/test-ui";
import { get as getActivePage } from "./states/active-page";
import { isDevEnvironment } from "./utils/misc";
let isPreviewingFont = false;
export function previewFontFamily(font: string): void {
document.documentElement.style.setProperty(
"--font",
'"' + font.replace(/_/g, " ") + '", "Roboto Mono", "Vazirmatn"'
);
isPreviewingFont = true;
}
export function clearFontPreview(): void {
if (!isPreviewingFont) return;
previewFontFamily(Config.fontFamily);
isPreviewingFont = false;
}
function updateKeytips(): void {
const modifierKey = window.navigator.userAgent.toLowerCase().includes("mac")
? "cmd"

View file

@ -0,0 +1,29 @@
import * as Loader from "../elements/loader";
import * as Notifications from "../elements/notifications";
import { createErrorMessage } from "./misc";
export async function getCommandline(): Promise<
typeof import("../commandline/commandline.js")
> {
try {
Loader.show();
const module = await import("../commandline/commandline.js");
Loader.hide();
return module;
} catch (e) {
Loader.hide();
if (
e instanceof Error &&
e.message.includes("Failed to fetch dynamically imported module")
) {
Notifications.add(
"Failed to load commandline module: could not fetch",
-1
);
} else {
const msg = createErrorMessage(e, "Failed to load commandline module");
Notifications.add(msg, -1);
}
throw e;
}
}

View file

@ -27,3 +27,7 @@ export function append(id: string, parent: SkeletonAppendParents): void {
const popup = skeletons.get(id) as HTMLElement;
parents[parent].append(popup);
}
export function has(id: string): boolean {
return skeletons.has(id);
}

View file

@ -59,7 +59,7 @@ body::before {
background: var(--theme-accent-stripe-color);
}
#commandLineWrapper #commandLine {
#commandLine .modal {
--roundness: 0; /* making command line sharp */
}