mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-11-18 14:55:34 +08:00
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:
parent
226f5de472
commit
3c4212b718
25 changed files with 930 additions and 1077 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
588
frontend/src/ts/commandline/commandline.ts
Normal file
588
frontend/src/ts/commandline/commandline.ts
Normal 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;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -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);
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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("/");
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
39
frontend/src/ts/event-handlers/footer.ts
Normal file
39
frontend/src/ts/event-handlers/footer.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
});
|
||||
30
frontend/src/ts/event-handlers/global.ts
Normal file
30
frontend/src/ts/event-handlers/global.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
7
frontend/src/ts/event-handlers/keymap.ts
Normal file
7
frontend/src/ts/event-handlers/keymap.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { getCommandline } from "../utils/async-modules";
|
||||
|
||||
$("#keymap").on("click", ".r5 .keySpace", async () => {
|
||||
(await getCommandline()).show({
|
||||
subgroupOverride: "keymapLayouts",
|
||||
});
|
||||
});
|
||||
5
frontend/src/ts/event-handlers/popups.ts
Normal file
5
frontend/src/ts/event-handlers/popups.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { getCommandline } from "../utils/async-modules";
|
||||
|
||||
$("#popups").on("click", "#supportMeWrapper button.ads", async () => {
|
||||
(await getCommandline()).show({ subgroupOverride: "enableAds" });
|
||||
});
|
||||
7
frontend/src/ts/event-handlers/test.ts
Normal file
7
frontend/src/ts/event-handlers/test.ts
Normal 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 });
|
||||
});
|
||||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
8
frontend/src/ts/types/types.d.ts
vendored
8
frontend/src/ts/types/types.d.ts
vendored
|
|
@ -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>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
29
frontend/src/ts/utils/async-modules.ts
Normal file
29
frontend/src/ts/utils/async-modules.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ body::before {
|
|||
background: var(--theme-accent-stripe-color);
|
||||
}
|
||||
|
||||
#commandLineWrapper #commandLine {
|
||||
#commandLine .modal {
|
||||
--roundness: 0; /* making command line sharp */
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue