Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions frontend/src/html/popups.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,6 @@
</div>
</dialog>

<dialog id="devOptionsModal" class="modalWrapper hidden">
<div class="modal">
<div class="title">Dev options</div>
<button class="generateData">generate data</button>
<button class="quickLogin">quick login</button>
<button class="toggleMediaQueryDebug">toggle media query debug</button>
<button class="showTestNotifications">show test notifications</button>
<button class="showRealWordsInput">show real words input</button>
<button class="xpBarTest">xp bar test</button>
<button class="toggleFakeChartData">toggle fake chart data</button>
<button class="toggleCaretDebug">toggle caret debug</button>
<button class="disableSlowTimerFail">disable slow timer fail</button>
</div>
</dialog>

<dialog id="alertsPopup" class="modalWrapper hidden">
<div class="modal">
<button class="mobileClose">
Expand Down
18 changes: 0 additions & 18 deletions frontend/src/styles/core.scss
Original file line number Diff line number Diff line change
Expand Up @@ -269,24 +269,6 @@ kbd {
}
}

#devButtons {
position: fixed;
left: 0;
top: 10rem;
display: grid;
grid-auto-flow: row;
gap: 0.5rem;
text-decoration: none;
z-index: 999999999;
border-radius: 0 1rem 1rem 0;

.button {
padding: 1rem;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}

.avatar {
--size: 1em;

Expand Down
6 changes: 0 additions & 6 deletions frontend/src/styles/popups.scss
Original file line number Diff line number Diff line change
Expand Up @@ -777,12 +777,6 @@ body.darkMode {
}
}

#devOptionsModal {
.modal {
max-width: 400px;
}
}

#shareTestSettingsModal {
.modal {
max-width: 600px;
Expand Down
37 changes: 36 additions & 1 deletion frontend/src/ts/components/layout/overlays/Overlays.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { JSXElement } from "solid-js";
import { JSXElement, Show } from "solid-js";
import { envConfig } from "virtual:env-config";

import { getIsScreenshotting } from "../../../signals/core";
import { showModal } from "../../../stores/modals";
import { cn } from "../../../utils/cn";
import { isDevEnvironment } from "../../../utils/misc";
import { Button } from "../../common/Button";
import { Fa } from "../../common/Fa";
import { ScrollToTop } from "../footer/ScrollToTop";
import { Banners } from "./Banners";
Expand Down Expand Up @@ -34,6 +37,38 @@ export function Overlays(): JSXElement {
<MediaQueryDebugger />
<LoaderBar />
<FpsCounter />
<Show when={isDevEnvironment()}>
<DevButtons />
</Show>
</>
);
}

function DevButtons(): JSXElement {
return (
<div class="fixed top-30 left-0 z-10000 flex w-max flex-col gap-2 text-xs">
<Button
href={`${envConfig.backendUrl}/configure/`}
ariaLabel={{
text: "Configure server",
position: "right",
}}
fa={{
icon: "fa-server",
}}
class="rounded-tl-none rounded-bl-none p-2"
/>
<Button
ariaLabel={{
text: "Dev options",
position: "right",
}}
onClick={() => showModal("DevOptions")}
fa={{
icon: "fa-flask",
}}
class="rounded-tl-none rounded-bl-none p-2"
/>
</div>
);
}
158 changes: 158 additions & 0 deletions frontend/src/ts/components/modals/DevOptionsModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { createSignal, For, JSXElement } from "solid-js";
import { envConfig } from "virtual:env-config";

import { signIn } from "../../auth";
import * as Notifications from "../../elements/notifications";
import { update } from "../../elements/xp-bar";
import { getInputElement } from "../../input/input-element";
import { showPopup } from "../../modals/simple-modals";
import { showLoaderBar, hideLoaderBar } from "../../signals/loader-bar";
import { hideModal } from "../../stores/modals";
import { toggleUserFakeChartData } from "../../test/result";
import { disableSlowTimerFail } from "../../test/test-timer";
import { FaSolidIcon } from "../../types/font-awesome";
import { setMediaQueryDebugLevel } from "../../ui";
import { toggleCaretDebug } from "../../utils/caret";
import { createErrorMessage } from "../../utils/misc";
import { AnimatedModal } from "../common/AnimatedModal";
import { Button } from "../common/Button";

const [mediaQueryDebugLevel, setLocalMediaQueryDebugLevel] = createSignal(0);

type DevButton = {
icon: FaSolidIcon;
label: () => string;
onClick: () => void;
};

export function DevOptionsModal(): JSXElement {
const buttons: DevButton[] = [
{
icon: "fa-database",
label: () => "Generate Data",
onClick: () => showPopup("devGenerateData"),
},
{
icon: "fa-bell",
label: () => "Test Notifications",
onClick: () => {
Notifications.add("This is a test", 1, { duration: 0 });
Notifications.add("This is a test", 0, { duration: 0 });
Notifications.add("This is a test", -1, {
duration: 0,
details: { test: true, error: "Example error message" },
});
hideModal("DevOptions");
},
},
{
icon: "fa-ruler",
label: () => `Media Query Debug (${mediaQueryDebugLevel()})`,
onClick: () => {
const next =
mediaQueryDebugLevel() >= 2 ? 0 : mediaQueryDebugLevel() + 1;
setLocalMediaQueryDebugLevel(next);
Notifications.add(`Setting media query debug level to ${next}`, 5);
setMediaQueryDebugLevel(next);
},
},
{
icon: "fa-eye",
label: () => "Show Real Words Input",
onClick: () => {
const el = getInputElement();
el.style.opacity = "1";
el.style.marginTop = "1.5em";
el.style.caretColor = "red";
hideModal("DevOptions");
},
},
{
icon: "fa-sign-in-alt",
label: () => "Quick Login",
onClick: () => {
if (
envConfig.quickLoginEmail === undefined ||
envConfig.quickLoginPassword === undefined
) {
Notifications.add(
"Quick login credentials not set. Add QUICK_LOGIN_EMAIL and QUICK_LOGIN_PASSWORD to your frontend .env file.",
-1,
);
return;
}
showLoaderBar();
void signIn(
envConfig.quickLoginEmail,
envConfig.quickLoginPassword,
true,
)
.then((result) => {
if (!result.success) {
Notifications.add(result.message, -1);
}
})
.catch((error: unknown) => {
Notifications.add(
createErrorMessage(error, "Quick login failed"),
-1,
);
})
.finally(() => {
hideLoaderBar();
});
hideModal("DevOptions");
},
},
{
icon: "fa-star",
label: () => "XP Bar Test",
onClick: () => {
setTimeout(() => {
void update(1000000, 20800, {
base: 100,
fullAccuracy: 200,
accPenalty: 300,
quote: 400,
punctuation: 500,
streak: 10_000,
configMultiplier: 2,
});
}, 500);
hideModal("DevOptions");
},
},
{
icon: "fa-chart-bar",
label: () => "Toggle Fake Chart Data",
onClick: toggleUserFakeChartData,
},
{
icon: "fa-i-cursor",
label: () => "Toggle Caret Debug",
onClick: toggleCaretDebug,
},
{
icon: "fa-clock",
label: () => "Disable Slow Timer Fail",
onClick: disableSlowTimerFail,
},
];

return (
<AnimatedModal id="DevOptions" title="Dev Options">
<div class="flex flex-col gap-4">
<For each={buttons}>
{(btn) => (
<Button
type="button"
onClick={btn.onClick}
fa={{ icon: btn.icon, fixedWidth: true }}
text={btn.label()}
/>
)}
</For>
</div>
</AnimatedModal>
);
}
12 changes: 11 additions & 1 deletion frontend/src/ts/components/modals/Modals.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import { JSXElement } from "solid-js";
import { JSXElement, Show, Suspense, lazy } from "solid-js";

import { isDevEnvironment } from "../../utils/misc";
import { ContactModal } from "./ContactModal";
import { SupportModal } from "./SupportModal";
import { VersionHistoryModal } from "./VersionHistoryModal";

const DevOptionsModal = lazy(async () =>
import("./DevOptionsModal").then((m) => ({ default: m.DevOptionsModal })),
);

export function Modals(): JSXElement {
return (
<>
<VersionHistoryModal />
<ContactModal />
<SupportModal />
<Show when={isDevEnvironment()}>
<Suspense fallback={null}>
<DevOptionsModal />
</Suspense>
</Show>
</>
Comment on lines 4 to 23
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DevOptionsModal is now always imported + rendered in Modals, which ships dev-only actions (quick login, test toggles) in production bundles and makes them theoretically triggerable (e.g. via any future call to showModal("DevOptions")). Gate rendering behind isDevEnvironment() and preferably lazy-load the modal component (dynamic import) to keep dev-only code out of prod bundles.

Copilot uses AI. Check for mistakes.
);
}
9 changes: 1 addition & 8 deletions frontend/src/ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@ import "./elements/no-css";
import { egVideoListener } from "./popups/video-ad-popup";
import "./states/connection";
import "./test/tts";
import { isDevEnvironment, addToGlobal } from "./utils/misc";
import { addToGlobal } from "./utils/misc";
import * as Focus from "./test/focus";
import { fetchLatestVersion } from "./utils/version";
import { getDevOptionsModal } from "./utils/async-modules";
import * as Sentry from "./sentry";
import * as Cookies from "./cookies";
import "./elements/psa";
Expand Down Expand Up @@ -97,10 +96,4 @@ addToGlobal({
qsr: qsr,
});

if (isDevEnvironment()) {
void getDevOptionsModal().then((module) => {
module.appendButton();
});
}

mountComponents();
Loading