Skip to content
Open
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
208 changes: 208 additions & 0 deletions frontend/src/ts/collections/presets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import { Preset } from "@monkeytype/schemas/presets";
import { queryCollectionOptions } from "@tanstack/query-db-collection";
import {
createCollection,
createOptimisticAction,
useLiveQuery,
} from "@tanstack/solid-db";
import Ape from "../ape";
import { queryClient } from "../queries";
import { baseKey } from "../queries/utils/keys";
import { ConfigGroupName } from "@monkeytype/schemas/configs";

export type PresetItem = Preset & { display: string };

const queryKeys = {
root: () => [...baseKey("presets", { isUserSpecific: true })],
};

function toPresetItem(preset: Preset): PresetItem {
return {
...preset,
display: preset.name.replaceAll("_", " "),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should we do the same as with tags? update the name to be without underscores, only convert when talking to the api?

};
}

// oxlint-disable-next-line typescript/explicit-function-return-type
export function usePresetsLiveQuery() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should we rename to ConfigPresets for more clarity? We have FilterPresets as well

return useLiveQuery((q) => {
return q
.from({ preset: presetsCollection })
.orderBy(({ preset }) => preset.name, "asc");
});
}

const presetsCollection = createCollection(
queryCollectionOptions({
staleTime: Infinity,
startSync: true,
queryKey: queryKeys.root(),

queryClient,
getKey: (it) => it._id,
queryFn: async () => {
return [] as PresetItem[];
},
}),
);

type ActionType = {
addPreset: {
name: string;
config: Preset["config"];
settingGroups: ConfigGroupName[] | undefined;
};
editPreset: {
presetId: string;
name: string;
config?: Preset["config"];
settingGroups?: ConfigGroupName[] | null;
};
deletePreset: {
presetId: string;
};
};

const actions = {
addPreset: createOptimisticAction<ActionType["addPreset"]>({
onMutate: ({ name, config, settingGroups }) => {
presetsCollection.insert({
_id: "temp-" + Date.now(),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use tempId helper

name,
display: name.replaceAll("_", " "),
config,
settingGroups,
});
},
mutationFn: async ({ name, config, settingGroups }) => {
const response = await Ape.presets.add({
body: {
name,
config,
...(settingGroups !== undefined && { settingGroups }),
},
});
if (response.status !== 200) {
throw new Error(`Failed to add preset: ${response.body.message}`);
}
presetsCollection.utils.writeInsert(
toPresetItem({
_id: response.body.data.presetId,
name: name,
settingGroups: settingGroups,
config: config,
}),
);
},
}),
editPreset: createOptimisticAction<ActionType["editPreset"]>({
onMutate: ({ presetId, name, config, settingGroups }) => {
presetsCollection.update(presetId, (preset) => {
preset.name = name;
preset.display = name.replaceAll("_", " ");

if (config !== undefined) {
preset.config = config;
}
if (settingGroups !== undefined) {
preset.settingGroups = settingGroups;
}
});
},
mutationFn: async ({ presetId, name, config, settingGroups }) => {
const existing = presetsCollection.get(presetId);

if (existing === undefined) {
throw new Error("Preset not found");
}

const response = await Ape.presets.save({
body: {
_id: presetId,
name: name,
...(config !== undefined && {
config: config,
settingGroups: settingGroups,
}),
},
});
if (response.status !== 200) {
throw new Error(`Failed to edit preset: ${response.body.message}`);
}

// if this is missing getPreset is out of sync
presetsCollection.utils.writeUpdate({
_id: presetId,
name,
display: name.replaceAll("_", " "),
...(config !== undefined && { config }),
...(settingGroups !== undefined && { settingGroups }),
});
},
}),
deletePreset: createOptimisticAction<ActionType["deletePreset"]>({
onMutate: ({ presetId }) => {
presetsCollection.delete(presetId);
},
mutationFn: async ({ presetId }) => {
const response = await Ape.presets.delete({
params: { presetId },
});
if (response.status !== 200) {
throw new Error(`Failed to delete preset: ${response.body.message}`);
}
presetsCollection.utils.writeDelete(presetId);
},
}),
};

// --- Public API ---

function getPresets(): PresetItem[] {
return [...presetsCollection.values()].sort((a, b) =>
a.name.localeCompare(b.name),
);
}

function getPreset(id: string): PresetItem | undefined {
return presetsCollection.get(id);
}

export function fillPresetsCollection(presets: Preset[]): void {
const presetItems = presets.map(toPresetItem);

presetsCollection.utils.writeBatch(() => {
Comment on lines +172 to +174
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

logout clears the presetsCollection

presetItems.forEach((item) => {
presetsCollection.utils.writeInsert(item);
});
});
}

export async function addPreset(
params: ActionType["addPreset"],
): Promise<void> {
const transaction = actions.addPreset(params);
await transaction.isPersisted.promise;
}

export async function editPreset(
params: ActionType["editPreset"],
): Promise<void> {
const transaction = actions.editPreset(params);
await transaction.isPersisted.promise;
}

export async function deletePreset(
params: ActionType["deletePreset"],
): Promise<void> {
const transaction = actions.deletePreset(params);
await transaction.isPersisted.promise;
}

/**
* Used for non reactive access. Do not use in Solid components.
*/
export const __nonReactive = {
getPresets,
getPreset,
};
8 changes: 4 additions & 4 deletions frontend/src/ts/commandline/lists/presets.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as DB from "../../db";
import * as ModesNotice from "../../elements/modes-notice";
import * as Settings from "../../pages/settings";
import * as PresetController from "../../controllers/preset-controller";
import * as EditPresetPopup from "../../modals/edit-preset";
import { isAuthenticated } from "../../states/core";
import { Command, CommandsSubgroup } from "../types";
import { __nonReactive } from "../../collections/presets";

const subgroup: CommandsSubgroup = {
title: "Presets...",
Expand All @@ -28,10 +28,10 @@ const commands: Command[] = [
];

function update(): void {
const snapshot = DB.getSnapshot();
const presets = __nonReactive.getPresets();
subgroup.list = [];
if (!snapshot?.presets || snapshot.presets.length === 0) return;
snapshot.presets.forEach((preset) => {
if (presets.length === 0) return;
presets.forEach((preset) => {
const dis = preset.display;

subgroup.list.push({
Expand Down
7 changes: 0 additions & 7 deletions frontend/src/ts/constants/default-snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
ModifiableTestActivityCalendar,
TestActivityCalendar,
} from "../elements/test-activity-calendar";
import { Preset } from "@monkeytype/schemas/presets";
import { Language } from "@monkeytype/schemas/languages";
import { ConnectionStatus } from "@monkeytype/schemas/connections";

Expand Down Expand Up @@ -69,17 +68,12 @@ export type Snapshot = Omit<
maxStreak: number;
isPremium: boolean;
streakHourOffset?: number;
presets: SnapshotPreset[];
xp: number;
testActivity?: ModifiableTestActivityCalendar;
testActivityByYear?: { [key: string]: TestActivityCalendar };
connections: Record<string, ConnectionStatus | "incoming">;
};

export type SnapshotPreset = Preset & {
display: string;
};

const defaultSnap = {
personalBests: {
time: {},
Expand All @@ -94,7 +88,6 @@ const defaultSnap = {
isPremium: false,
config: getDefaultConfig(),
customThemes: [],
presets: [],
banned: undefined,
verified: undefined,
lbMemory: { time: { 15: { english: 0 }, 60: { english: 0 } } },
Expand Down
29 changes: 5 additions & 24 deletions frontend/src/ts/controllers/preset-controller.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
import { Preset } from "@monkeytype/schemas/presets";

import { Config } from "../config/store";
import { applyConfig } from "../config/lifecycle";
import * as DB from "../db";
import {
showNoticeNotification,
showSuccessNotification,
} from "../states/notifications";
import { showSuccessNotification } from "../states/notifications";
import * as TestLogic from "../test/test-logic";
import {
clearActiveTags,
setTagActive,
saveActiveToLocalStorage,
} from "../collections/tags";
import { SnapshotPreset } from "../constants/default-snapshot";
import { saveFullConfigToLocalStorage } from "../config/persistence";
import * as ModesNotice from "../elements/modes-notice";
import { __nonReactive, type PresetItem } from "../collections/presets";

export async function apply(_id: string): Promise<void> {
const snapshot = DB.getSnapshot();
if (!snapshot) return;

const presetToApply = snapshot.presets?.find((preset) => preset._id === _id);
const presetToApply = __nonReactive.getPreset(_id);
if (presetToApply === undefined) {
return;
}
Expand Down Expand Up @@ -52,21 +47,7 @@ export async function apply(_id: string): Promise<void> {
showSuccessNotification("Preset applied", { durationMs: 2000 });
saveFullConfigToLocalStorage();
}
function isPartialPreset(preset: SnapshotPreset): boolean {
return preset.settingGroups !== undefined && preset.settingGroups !== null;
}

export async function getPreset(_id: string): Promise<Preset | undefined> {
const snapshot = DB.getSnapshot();
if (!snapshot) {
return;
}

const preset = snapshot.presets?.find((preset) => preset._id === _id);

if (preset === undefined) {
showNoticeNotification("Preset not found");
return;
}
return preset;
function isPartialPreset(preset: PresetItem): boolean {
return preset.settingGroups !== undefined && preset.settingGroups !== null;
}
29 changes: 4 additions & 25 deletions frontend/src/ts/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
import {
getDefaultSnapshot,
Snapshot,
SnapshotPreset,
SnapshotResult,
} from "./constants/default-snapshot";
import { getFirstDayOfTheWeek } from "./utils/date-and-time";
Expand All @@ -44,6 +43,7 @@ import { setXpBarData } from "./states/header";
import { FunboxMetadata } from "@monkeytype/funbox";
import { fillTagsCollection, __nonReactive } from "./collections/tags";
import { updateTagsInFilterStorage } from "./states/result-filters";
import { fillPresetsCollection } from "./collections/presets";

let dbSnapshot: Snapshot | undefined;
const firstDayOfTheWeek = getFirstDayOfTheWeek();
Expand Down Expand Up @@ -198,34 +198,13 @@ export async function initSnapshot(): Promise<Snapshot | false> {
snap.customThemes = userData.customThemes ?? [];

fillTagsCollection(userData.tags ?? []);
updateTagsInFilterStorage(userData.tags?.map((it) => it._id) ?? []);
fillPresetsCollection(presetsData ?? []);

if (presetsData !== undefined && presetsData !== null) {
const presetsWithDisplay = presetsData.map((preset) => {
return {
...preset,
display: preset.name.replace(/_/g, " "),
};
}) as SnapshotPreset[];
snap.presets = presetsWithDisplay;

snap.presets = snap.presets?.sort(
(a: SnapshotPreset, b: SnapshotPreset) => {
if (a.name > b.name) {
return 1;
} else if (a.name < b.name) {
return -1;
} else {
return 0;
}
},
);
}
fillResultFilterPresetsCollection(userData.resultFilterPresets ?? []);
updateTagsInFilterStorage(userData.tags?.map((it) => it._id) ?? []);

snap.connections = convertConnections(connectionsData);

fillResultFilterPresetsCollection(userData.resultFilterPresets ?? []);

dbSnapshot = snap;

return dbSnapshot;
Expand Down
Loading
Loading