diff --git a/frontend/src/styles/popups.scss b/frontend/src/styles/popups.scss
index b205e273e190..258333a1732e 100644
--- a/frontend/src/styles/popups.scss
+++ b/frontend/src/styles/popups.scss
@@ -292,17 +292,6 @@ body.darkMode {
}
}
-#editResultTagsModal {
- .modal {
- max-width: 600px;
- .buttons {
- display: grid;
- gap: 0.5rem;
- grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
- }
- }
-}
-
#streakHourOffsetModal {
.modal {
max-width: 500px;
diff --git a/frontend/src/ts/components/modals/EditResultTagsModal.tsx b/frontend/src/ts/components/modals/EditResultTagsModal.tsx
new file mode 100644
index 000000000000..2146e173ea29
--- /dev/null
+++ b/frontend/src/ts/components/modals/EditResultTagsModal.tsx
@@ -0,0 +1,99 @@
+import { createEffect, createSignal, For } from "solid-js";
+
+import { updateTags } from "../../collections/results";
+import { useTagsLiveQuery } from "../../collections/tags";
+import { getSelectedResult } from "../../states/edit-result-tags";
+import { hideLoaderBar, showLoaderBar } from "../../states/loader-bar";
+import { hideModal } from "../../states/modals";
+import {
+ showErrorNotification,
+ showSuccessNotification,
+} from "../../states/notifications";
+import { updateTagsAfterEdit } from "../../test/result";
+import { areUnsortedArraysEqual } from "../../utils/arrays";
+import { createErrorMessage } from "../../utils/error";
+import { AnimatedModal } from "../common/AnimatedModal";
+import { Button } from "../common/Button";
+
+export function EditResultTagsModal() {
+ const [selectedTagIds, setSelectedTagIds] = createSignal
>(
+ new Set([]),
+ );
+
+ const tags = useTagsLiveQuery();
+
+ createEffect(() => {
+ const resultTags = getSelectedResult()?.tags ?? [];
+ const knownTagIds = new Set(tags().map((tag) => tag._id));
+ const filtered = resultTags.filter((id) => knownTagIds.has(id));
+ setSelectedTagIds(new Set(filtered));
+ });
+
+ return (
+
+
+
+ {(tag) => (
+
+
+
+ );
+}
diff --git a/frontend/src/ts/components/modals/Modals.tsx b/frontend/src/ts/components/modals/Modals.tsx
index 632e0cc13172..50e377d50947 100644
--- a/frontend/src/ts/components/modals/Modals.tsx
+++ b/frontend/src/ts/components/modals/Modals.tsx
@@ -6,6 +6,7 @@ import { CookiesModal } from "./CookiesModal";
import { CustomTestDurationModal } from "./CustomTestDurationModal";
import { CustomTextModal } from "./CustomTextModal";
import { CustomWordAmountModal } from "./CustomWordAmountModal";
+import { EditResultTagsModal } from "./EditResultTagsModal";
import { LastSignedOutResultModal } from "./LastSignedOutResultModal";
import { MobileTestConfigModal } from "./MobileTestConfigModal";
import { AddPresetModal } from "./preset/AddPresetModal";
@@ -40,6 +41,7 @@ export function Modals(): JSXElement {
+
>
);
}
diff --git a/frontend/src/ts/components/pages/account/Table.tsx b/frontend/src/ts/components/pages/account/Table.tsx
index 26f538695331..121a07460b9b 100644
--- a/frontend/src/ts/components/pages/account/Table.tsx
+++ b/frontend/src/ts/components/pages/account/Table.tsx
@@ -6,8 +6,8 @@ import { Accessor, createMemo, createSignal, JSXElement, Show } from "solid-js";
import { type TagItem, useTagsLiveQuery } from "../../../collections/tags";
import { SnapshotResult } from "../../../constants/default-snapshot";
-import * as EditResultTagsModal from "../../../modals/edit-result-tags";
import { getFormatting } from "../../../states/core";
+import { showEditResultTagsModal } from "../../../states/edit-result-tags";
import { showModal } from "../../../states/modals";
import { showNoticeNotification } from "../../../states/notifications";
import { cn } from "../../../utils/cn";
@@ -261,11 +261,11 @@ function getColumns({
);
return;
}
- EditResultTagsModal.show(
- info.row.original._id,
- info.getValue(),
- "accountPage",
- );
+
+ showEditResultTagsModal({
+ _id: info.row.original._id,
+ tags: info.getValue(),
+ });
}}
/>
);
diff --git a/frontend/src/ts/event-handlers/test.ts b/frontend/src/ts/event-handlers/test.ts
index d85fc367b256..d12d6585ef33 100644
--- a/frontend/src/ts/event-handlers/test.ts
+++ b/frontend/src/ts/event-handlers/test.ts
@@ -1,5 +1,4 @@
import { Config } from "../config/store";
-import * as EditResultTagsModal from "../modals/edit-result-tags";
import { __nonReactive } from "../collections/tags";
import {
showNoticeNotification,
@@ -12,6 +11,7 @@ import { navigate } from "../controllers/route-controller";
import { getMode2 } from "../utils/misc";
import { qs } from "../utils/dom";
import { getCurrentQuote } from "../states/test";
+import { showEditResultTagsModal } from "../states/edit-result-tags";
const testPage = qs(".pageTest");
@@ -25,7 +25,7 @@ testPage?.onChild("click", ".tags .editTagsButton", () => {
"data-active-tag-ids",
) ?? "";
const tags = activeTagIds === "" ? [] : activeTagIds.split(",");
- EditResultTagsModal.show(resultid, tags, "resultPage");
+ showEditResultTagsModal({ _id: resultid, tags, source: "resultPage" });
}
});
diff --git a/frontend/src/ts/modals/edit-result-tags.ts b/frontend/src/ts/modals/edit-result-tags.ts
deleted file mode 100644
index a92b144c65c5..000000000000
--- a/frontend/src/ts/modals/edit-result-tags.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import { showLoaderBar, hideLoaderBar } from "../states/loader-bar";
-import {
- showErrorNotification,
- showSuccessNotification,
-} from "../states/notifications";
-import { areUnsortedArraysEqual } from "../utils/arrays";
-import * as TestResult from "../test/result";
-import AnimatedModal from "../utils/animated-modal";
-import { __nonReactive } from "../collections/tags";
-import { updateTags } from "../collections/results";
-import { createErrorMessage } from "../utils/error";
-
-type State = {
- resultId: string;
- startingTags: string[];
- tags: string[];
- source: "accountPage" | "resultPage";
-};
-
-const state: State = {
- resultId: "",
- startingTags: [],
- tags: [],
- source: "accountPage",
-};
-
-export function show(
- resultId: string,
- tags: string[],
- source: "accountPage" | "resultPage",
-): void {
- if (resultId === "") {
- showErrorNotification(
- "Failed to show edit result tags modal: result id is empty",
- );
- return;
- }
-
- const knownTagIds = new Set(__nonReactive.getTags().map((it) => it._id));
- tags = tags.filter((it) => knownTagIds.has(it));
-
- state.resultId = resultId;
- state.startingTags = [...tags];
- state.tags = [...tags];
- state.source = source;
-
- void modal.show({
- beforeAnimation: async (): Promise => {
- appendButtons();
- updateActiveButtons();
- },
- });
-}
-
-function hide(): void {
- void modal.hide();
-}
-
-function appendButtons(): void {
- const buttonsEl = modal.getModal().qs(".buttons");
-
- if (buttonsEl === null) {
- showErrorNotification(
- "Failed to append buttons to edit result tags modal: could not find buttons element",
- );
- return;
- }
-
- const tagIds = new Set([
- ...__nonReactive.getTags().map((tag) => tag._id),
- ...state.tags,
- ]);
-
- buttonsEl.empty();
- for (const tagId of tagIds) {
- const tag = __nonReactive.getTag(tagId);
- const button = document.createElement("button");
- button.classList.add("toggleTag");
- button.setAttribute("data-tag-id", tagId);
- button.textContent = tag?.name ?? tag?._id ?? "unknown tag"; //this shouldnt happen?
- button.addEventListener("click", (e) => {
- toggleTag(tagId);
- updateActiveButtons();
- });
- buttonsEl.append(button);
- }
-}
-
-function updateActiveButtons(): void {
- const buttons = modal.getModal().qsa(".buttons button");
- for (const button of buttons) {
- const tagid: string = button.getAttribute("data-tag-id") ?? "";
- if (state.tags.includes(tagid)) {
- button.addClass("active");
- } else {
- button.removeClass("active");
- }
- }
-}
-
-function toggleTag(tagId: string): void {
- if (state.tags.includes(tagId)) {
- state.tags = state.tags.filter((el) => el !== tagId);
- } else {
- state.tags.push(tagId);
- }
-}
-
-async function save(): Promise {
- showLoaderBar();
- try {
- await updateTags({
- resultId: state.resultId,
- currentTagIds: state.startingTags,
- newTagIds: state.tags,
- afterUpdate: ({ tagPbs }) => {
- if (state.source === "resultPage") {
- TestResult.updateTagsAfterEdit(state.tags, tagPbs);
- }
- },
- });
- hideLoaderBar();
- } catch (e) {
- hideLoaderBar();
- const message = createErrorMessage(e, "Failed to update tags");
- showErrorNotification(message);
- return;
- }
-
- //if got no freaking idea why this is needed
- //but update tags somehow adds undefined to the end of the array
- //i tried spreading, json parsing - nothing helped.
- state.tags = state.tags.filter((el) => el !== undefined);
- showSuccessNotification("Tags updated", { durationMs: 2000 });
-}
-
-const modal = new AnimatedModal({
- dialogId: "editResultTagsModal",
- setup: async (modalEl): Promise => {
- modalEl.qs("button.saveButton")?.on("click", (e) => {
- if (areUnsortedArraysEqual(state.startingTags, state.tags)) {
- hide();
- return;
- }
- hide();
- void save();
- });
- },
-});
diff --git a/frontend/src/ts/states/edit-result-tags.ts b/frontend/src/ts/states/edit-result-tags.ts
new file mode 100644
index 000000000000..58206a3f4a77
--- /dev/null
+++ b/frontend/src/ts/states/edit-result-tags.ts
@@ -0,0 +1,23 @@
+import { Result } from "@monkeytype/schemas/results";
+import { Mode } from "@monkeytype/schemas/shared";
+import { createSignal } from "solid-js";
+import { showModal } from "./modals";
+import { showErrorNotification } from "./notifications";
+
+type IdAndTags = Pick, "_id" | "tags"> & { source?: "resultPage" };
+const [getSelectedResult, setSelectedResult] = createSignal(
+ null,
+);
+
+export { getSelectedResult };
+
+export function showEditResultTagsModal(options: IdAndTags): void {
+ if (options._id === "") {
+ showErrorNotification(
+ "Failed to show edit result tags modal: result id is empty",
+ );
+ return;
+ }
+ setSelectedResult(options);
+ showModal("EditResultTags");
+}
diff --git a/frontend/src/ts/states/modals.ts b/frontend/src/ts/states/modals.ts
index 0cb81f089530..8103c3763142 100644
--- a/frontend/src/ts/states/modals.ts
+++ b/frontend/src/ts/states/modals.ts
@@ -31,7 +31,8 @@ export type ModalId =
| "EditPresetModal"
| "EditProfile"
| "ViewApeKey"
- | "LastSignedOutResult";
+ | "LastSignedOutResult"
+ | "EditResultTags";
export type ModalVisibility = {
visible: boolean;