Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 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
194 changes: 194 additions & 0 deletions frontend/src/ts/collections/presets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
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("_", " "),
};
}

// oxlint-disable-next-line typescript/explicit-function-return-type
export function usePresetsLiveQuery() {
return useLiveQuery((q) => {
return q.from({ preset: presetsCollection }).select((p) => ({ ...p }));
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.

lets order by name here and in the getPresets function and remove from the fillPresetsCollection

});
}

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(),
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}`);
}
},
}),
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.delete(presetId);
},
}),
};

// --- Public API ---

// todo: this might not be reactive
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A new Todo was discovered. If it is not a priority right now,consider marking it for later attention.
todo: this might not be reactive

export function getPresets(): PresetItem[] {
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.

ig getPresets and getPreset is for legacy/non-reactive code. Can we mark it somehow? maybe suffix with Legacy or nonReactive or we handle it like __testing and export a __nonReactive and add a lint rule to not use __nonReactive in tsx files?

return [...presetsCollection.values()];
}

// todo: this might not be reactive
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A new Todo was discovered. If it is not a priority right now,consider marking it for later attention.
todo: this might not be reactive

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

export function fillPresetsCollection(presets: Preset[]): void {
const presetItems = presets
.map(toPresetItem)
.sort((a, b) => a.name.localeCompare(b.name));

presetsCollection.utils.writeBatch(() => {
presetsCollection.forEach((preset) => {
presetsCollection.utils.writeDelete(preset._id);
});
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;
}
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 { getPresets } 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 = 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 @@ -12,7 +12,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 @@ -79,18 +78,13 @@ export type Snapshot = Omit<
isPremium: boolean;
streakHourOffset?: number;
tags: SnapshotUserTag[];
presets: SnapshotPreset[];
results?: SnapshotResult<Mode>[];
xp: number;
testActivity?: ModifiableTestActivityCalendar;
testActivityByYear?: { [key: string]: TestActivityCalendar };
connections: Record<string, ConnectionStatus | "incoming">;
};

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

const defaultSnap = {
results: undefined,
personalBests: {
Expand All @@ -106,7 +100,6 @@ const defaultSnap = {
isPremium: false,
config: getDefaultConfig(),
customThemes: [],
presets: [],
tags: [],
banned: undefined,
verified: undefined,
Expand Down
32 changes: 8 additions & 24 deletions frontend/src/ts/controllers/preset-controller.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
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 * as TagController from "./tag-controller";
import { SnapshotPreset } from "../constants/default-snapshot";
import { saveFullConfigToLocalStorage } from "../config/persistence";
import {
getPreset as getPresetFromCollection,
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 = getPresetFromCollection(_id);
if (presetToApply === undefined) {
return;
}
Expand Down Expand Up @@ -46,21 +44,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;
}
24 changes: 2 additions & 22 deletions frontend/src/ts/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
import {
getDefaultSnapshot,
Snapshot,
SnapshotPreset,
SnapshotResult,
SnapshotUserTag,
} from "./constants/default-snapshot";
Expand All @@ -42,6 +41,7 @@ import {
import { XpBreakdown } from "@monkeytype/schemas/results";
import { setXpBarData } from "./states/header";
import { FunboxMetadata } from "@monkeytype/funbox";
import { fillPresetsCollection } from "./collections/presets";

let dbSnapshot: Snapshot | undefined;
const firstDayOfTheWeek = getFirstDayOfTheWeek();
Expand Down Expand Up @@ -231,27 +231,7 @@ export async function initSnapshot(): Promise<Snapshot | false> {
}
});

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;
}
},
);
}
fillPresetsCollection(presetsData ?? []);

snap.connections = convertConnections(connectionsData);

Expand Down
Loading
Loading