mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2024-09-20 07:16:17 +08:00
feat(settings): allow partial presets (@amarnathsama, @miodec, @fehmer) (#5813)
This commit is contained in:
parent
dd93fdbf02
commit
8a6c81669e
|
@ -24,9 +24,15 @@ describe("PresetController", () => {
|
|||
_id: new ObjectId(),
|
||||
uid: "123456789",
|
||||
name: "test2",
|
||||
config: { language: "polish" },
|
||||
settingGroups: ["hideElements"],
|
||||
config: {
|
||||
showKeyTips: true,
|
||||
capsLockWarning: true,
|
||||
showOutOfFocusWarning: true,
|
||||
showAverage: "off",
|
||||
},
|
||||
};
|
||||
|
||||
//@ts-expect-error
|
||||
getPresetsMock.mockResolvedValue([presetOne, presetTwo]);
|
||||
|
||||
//WHEN
|
||||
|
@ -47,7 +53,13 @@ describe("PresetController", () => {
|
|||
{
|
||||
_id: presetTwo._id.toHexString(),
|
||||
name: "test2",
|
||||
config: { language: "polish" },
|
||||
settingGroups: ["hideElements"],
|
||||
config: {
|
||||
showKeyTips: true,
|
||||
capsLockWarning: true,
|
||||
showOutOfFocusWarning: true,
|
||||
showAverage: "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -81,7 +93,7 @@ describe("PresetController", () => {
|
|||
addPresetMock.mockReset();
|
||||
});
|
||||
|
||||
it("should add the users preset", async () => {
|
||||
it("should add the users full preset", async () => {
|
||||
//GIVEN
|
||||
addPresetMock.mockResolvedValue({ presetId: "1" });
|
||||
|
||||
|
@ -110,6 +122,65 @@ describe("PresetController", () => {
|
|||
config: { language: "english", tags: ["one", "two"] },
|
||||
});
|
||||
});
|
||||
it("should add the users partial preset", async () => {
|
||||
//GIVEN
|
||||
addPresetMock.mockResolvedValue({ presetId: "1" });
|
||||
|
||||
//WHEN
|
||||
const { body } = await mockApp
|
||||
.post("/presets")
|
||||
.set("authorization", "Uid 123456789")
|
||||
.accept("application/json")
|
||||
.send({
|
||||
name: "new",
|
||||
settingGroups: ["hideElements"],
|
||||
config: {
|
||||
showKeyTips: true,
|
||||
capsLockWarning: true,
|
||||
showOutOfFocusWarning: true,
|
||||
showAverage: "off",
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
//THEN
|
||||
expect(body).toStrictEqual({
|
||||
message: "Preset created",
|
||||
data: { presetId: "1" },
|
||||
});
|
||||
|
||||
expect(addPresetMock).toHaveBeenCalledWith("123456789", {
|
||||
name: "new",
|
||||
settingGroups: ["hideElements"],
|
||||
config: {
|
||||
showKeyTips: true,
|
||||
capsLockWarning: true,
|
||||
showOutOfFocusWarning: true,
|
||||
showAverage: "off",
|
||||
},
|
||||
});
|
||||
});
|
||||
it("should fail for no setting groups in partial presets", async () => {
|
||||
//WHEN
|
||||
const { body } = await mockApp
|
||||
.post("/presets")
|
||||
.set("authorization", "Uid 123456789")
|
||||
.accept("application/json")
|
||||
.send({
|
||||
name: "update",
|
||||
settingGroups: [],
|
||||
config: {},
|
||||
})
|
||||
.expect(422);
|
||||
|
||||
expect(body).toStrictEqual({
|
||||
message: "Invalid request data schema",
|
||||
validationErrors: [
|
||||
`"settingGroups" Array must contain at least 1 element(s)`,
|
||||
],
|
||||
});
|
||||
expect(addPresetMock).not.toHaveBeenCalled();
|
||||
});
|
||||
it("should not fail with emtpy config", async () => {
|
||||
//GIVEN
|
||||
|
||||
|
@ -149,7 +220,7 @@ describe("PresetController", () => {
|
|||
});
|
||||
expect(addPresetMock).not.toHaveBeenCalled();
|
||||
});
|
||||
it("should not fail with invalid preset", async () => {
|
||||
it("should fail with invalid preset", async () => {
|
||||
//WHEN
|
||||
const { body } = await mockApp
|
||||
.post("/presets")
|
||||
|
@ -178,6 +249,32 @@ describe("PresetController", () => {
|
|||
],
|
||||
});
|
||||
|
||||
expect(addPresetMock).not.toHaveBeenCalled();
|
||||
});
|
||||
it("should fail with duplicate group settings in partial preset", async () => {
|
||||
//WHEN
|
||||
const { body } = await mockApp
|
||||
.post("/presets")
|
||||
.set("authorization", "Uid 123456789")
|
||||
.accept("application/json")
|
||||
.send({
|
||||
name: "new",
|
||||
settingGroups: ["hideElements", "hideElements"],
|
||||
config: {
|
||||
showKeyTips: true,
|
||||
capsLockWarning: true,
|
||||
showOutOfFocusWarning: true,
|
||||
showAverage: "off",
|
||||
},
|
||||
})
|
||||
.expect(422);
|
||||
|
||||
//THEN
|
||||
expect(body).toStrictEqual({
|
||||
message: "Invalid request data schema",
|
||||
validationErrors: [`"settingGroups" No duplicates allowed.`],
|
||||
});
|
||||
|
||||
expect(addPresetMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -220,6 +317,46 @@ describe("PresetController", () => {
|
|||
config: { language: "english", tags: ["one", "two"] },
|
||||
});
|
||||
});
|
||||
it("should update the users partial preset", async () => {
|
||||
//GIVEN
|
||||
editPresetMock.mockResolvedValue({} as any);
|
||||
|
||||
//WHEN
|
||||
const { body } = await mockApp
|
||||
.patch("/presets")
|
||||
.set("authorization", "Uid 123456789")
|
||||
.accept("application/json")
|
||||
.send({
|
||||
_id: "1",
|
||||
name: "new",
|
||||
settingGroups: ["hideElements"],
|
||||
config: {
|
||||
showKeyTips: true,
|
||||
capsLockWarning: true,
|
||||
showOutOfFocusWarning: true,
|
||||
showAverage: "off",
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
//THEN
|
||||
expect(body).toStrictEqual({
|
||||
message: "Preset updated",
|
||||
data: null,
|
||||
});
|
||||
|
||||
expect(editPresetMock).toHaveBeenCalledWith("123456789", {
|
||||
_id: "1",
|
||||
name: "new",
|
||||
settingGroups: ["hideElements"],
|
||||
config: {
|
||||
showKeyTips: true,
|
||||
capsLockWarning: true,
|
||||
showOutOfFocusWarning: true,
|
||||
showAverage: "off",
|
||||
},
|
||||
});
|
||||
});
|
||||
it("should not fail with emtpy config", async () => {
|
||||
//GIVEN
|
||||
|
||||
|
@ -256,15 +393,11 @@ describe("PresetController", () => {
|
|||
|
||||
expect(body).toStrictEqual({
|
||||
message: "Invalid request data schema",
|
||||
validationErrors: [
|
||||
`"_id" Required`,
|
||||
`"name" Required`,
|
||||
`"config" Required`,
|
||||
],
|
||||
validationErrors: [`"_id" Required`, `"name" Required`],
|
||||
});
|
||||
expect(editPresetMock).not.toHaveBeenCalled();
|
||||
});
|
||||
it("should not fail with invalid preset", async () => {
|
||||
it("should fail with invalid preset", async () => {
|
||||
//WHEN
|
||||
const { body } = await mockApp
|
||||
.patch("/presets")
|
||||
|
@ -274,6 +407,7 @@ describe("PresetController", () => {
|
|||
_id: "1",
|
||||
name: "update",
|
||||
extra: "extra",
|
||||
settingGroups: ["mappers"],
|
||||
config: {
|
||||
extra: "extra",
|
||||
autoSwitchTheme: "yes",
|
||||
|
@ -286,6 +420,7 @@ describe("PresetController", () => {
|
|||
expect(body).toStrictEqual({
|
||||
message: "Invalid request data schema",
|
||||
validationErrors: [
|
||||
`"settingGroups.0" Invalid enum value. Expected 'test' | 'behavior' | 'input' | 'sound' | 'caret' | 'appearance' | 'theme' | 'hideElements' | 'ads' | 'hidden', received 'mappers'`,
|
||||
`"config.autoSwitchTheme" Expected boolean, received string`,
|
||||
`"config.confidenceMode" Invalid enum value. Expected 'off' | 'on' | 'max', received 'pretty'`,
|
||||
`"config" Unrecognized key(s) in object: 'extra'`,
|
||||
|
@ -293,6 +428,33 @@ describe("PresetController", () => {
|
|||
],
|
||||
});
|
||||
|
||||
expect(editPresetMock).not.toHaveBeenCalled();
|
||||
});
|
||||
it("should fail with duplicate group settings in partial preset", async () => {
|
||||
//WHEN
|
||||
const { body } = await mockApp
|
||||
.patch("/presets")
|
||||
.set("authorization", "Uid 123456789")
|
||||
.accept("application/json")
|
||||
.send({
|
||||
_id: "1",
|
||||
name: "new",
|
||||
settingGroups: ["hideElements", "hideElements"],
|
||||
config: {
|
||||
showKeyTips: true,
|
||||
capsLockWarning: true,
|
||||
showOutOfFocusWarning: true,
|
||||
showAverage: "off",
|
||||
},
|
||||
})
|
||||
.expect(422);
|
||||
|
||||
//THEN
|
||||
expect(body).toStrictEqual({
|
||||
message: "Invalid request data schema",
|
||||
validationErrors: [`"settingGroups" No duplicates allowed.`],
|
||||
});
|
||||
|
||||
expect(editPresetMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,8 +13,12 @@ describe("PresetDal", () => {
|
|||
});
|
||||
const second = await PresetDal.addPreset(uid, {
|
||||
name: "second",
|
||||
settingGroups: ["hideElements"],
|
||||
config: {
|
||||
ads: "result",
|
||||
showKeyTips: true,
|
||||
capsLockWarning: true,
|
||||
showOutOfFocusWarning: true,
|
||||
showAverage: "off",
|
||||
},
|
||||
});
|
||||
await PresetDal.addPreset("unknown", { name: "unknown", config: {} });
|
||||
|
@ -36,7 +40,13 @@ describe("PresetDal", () => {
|
|||
_id: new ObjectId(second.presetId),
|
||||
uid: uid,
|
||||
name: "second",
|
||||
config: { ads: "result" },
|
||||
settingGroups: ["hideElements"],
|
||||
config: {
|
||||
showKeyTips: true,
|
||||
capsLockWarning: true,
|
||||
showOutOfFocusWarning: true,
|
||||
showAverage: "off",
|
||||
},
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
@ -160,7 +170,7 @@ describe("PresetDal", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("should edit with name only", async () => {
|
||||
it("should edit with name only - full preset", async () => {
|
||||
//GIVEN
|
||||
const uid = new ObjectId().toHexString();
|
||||
const first = (
|
||||
|
@ -174,7 +184,6 @@ describe("PresetDal", () => {
|
|||
await PresetDal.editPreset(uid, {
|
||||
_id: first,
|
||||
name: "newName",
|
||||
config: {},
|
||||
});
|
||||
expect(await PresetDal.getPresets(uid)).toEqual(
|
||||
expect.arrayContaining([
|
||||
|
@ -187,6 +196,44 @@ describe("PresetDal", () => {
|
|||
])
|
||||
);
|
||||
});
|
||||
it("should edit with name only - partial preset", async () => {
|
||||
//GIVEN
|
||||
const uid = new ObjectId().toHexString();
|
||||
const first = (
|
||||
await PresetDal.addPreset(uid, {
|
||||
name: "first",
|
||||
settingGroups: ["hideElements"],
|
||||
config: {
|
||||
showKeyTips: true,
|
||||
capsLockWarning: true,
|
||||
showOutOfFocusWarning: true,
|
||||
showAverage: "off",
|
||||
},
|
||||
})
|
||||
).presetId;
|
||||
|
||||
//WHEN empty
|
||||
await PresetDal.editPreset(uid, {
|
||||
_id: first,
|
||||
name: "newName",
|
||||
});
|
||||
expect(await PresetDal.getPresets(uid)).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
_id: new ObjectId(first),
|
||||
uid: uid,
|
||||
name: "newName",
|
||||
settingGroups: ["hideElements"],
|
||||
config: {
|
||||
showKeyTips: true,
|
||||
capsLockWarning: true,
|
||||
showOutOfFocusWarning: true,
|
||||
showAverage: "off",
|
||||
},
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
it("should not edit present not matching uid", async () => {
|
||||
//GIVEN
|
||||
const uid = new ObjectId().toHexString();
|
||||
|
@ -219,6 +266,85 @@ describe("PresetDal", () => {
|
|||
])
|
||||
);
|
||||
});
|
||||
it("should edit when partial is edited to full", async () => {
|
||||
//GIVEN
|
||||
const uid = new ObjectId().toHexString();
|
||||
const first = (
|
||||
await PresetDal.addPreset(uid, {
|
||||
name: "first",
|
||||
settingGroups: ["hideElements"],
|
||||
config: {
|
||||
showKeyTips: true,
|
||||
capsLockWarning: true,
|
||||
showOutOfFocusWarning: true,
|
||||
showAverage: "off",
|
||||
},
|
||||
})
|
||||
).presetId;
|
||||
//WHEN
|
||||
await PresetDal.editPreset(uid, {
|
||||
_id: first,
|
||||
name: "newName",
|
||||
settingGroups: null,
|
||||
config: { ads: "off" },
|
||||
});
|
||||
|
||||
//THEN
|
||||
expect(await PresetDal.getPresets(uid)).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
_id: new ObjectId(first),
|
||||
uid: uid,
|
||||
name: "newName",
|
||||
config: { ads: "off" },
|
||||
settingGroups: null,
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
it("should edit when full is edited to partial", async () => {
|
||||
//GIVEN
|
||||
const uid = new ObjectId().toHexString();
|
||||
const first = (
|
||||
await PresetDal.addPreset(uid, {
|
||||
name: "first",
|
||||
config: {
|
||||
ads: "off",
|
||||
},
|
||||
})
|
||||
).presetId;
|
||||
|
||||
//WHEN
|
||||
await PresetDal.editPreset(uid, {
|
||||
_id: first,
|
||||
name: "newName",
|
||||
settingGroups: ["hideElements"],
|
||||
config: {
|
||||
showKeyTips: true,
|
||||
capsLockWarning: true,
|
||||
showOutOfFocusWarning: true,
|
||||
showAverage: "off",
|
||||
},
|
||||
});
|
||||
|
||||
//THEN
|
||||
expect(await PresetDal.getPresets(uid)).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
_id: new ObjectId(first),
|
||||
uid: uid,
|
||||
name: "newName",
|
||||
settingGroups: ["hideElements"],
|
||||
config: {
|
||||
showKeyTips: true,
|
||||
capsLockWarning: true,
|
||||
showOutOfFocusWarning: true,
|
||||
showAverage: "off",
|
||||
},
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("removePreset", () => {
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
import * as PresetDAL from "../../dal/preset";
|
||||
import { MonkeyResponse2 } from "../../utils/monkey-response";
|
||||
import { replaceObjectId } from "../../utils/misc";
|
||||
import { Preset } from "@monkeytype/contracts/schemas/presets";
|
||||
import { EditPresetRequest } from "@monkeytype/contracts/schemas/presets";
|
||||
|
||||
export async function getPresets(
|
||||
req: MonkeyTypes.Request2
|
||||
|
@ -35,7 +35,7 @@ export async function addPreset(
|
|||
}
|
||||
|
||||
export async function editPreset(
|
||||
req: MonkeyTypes.Request2<undefined, Preset>
|
||||
req: MonkeyTypes.Request2<undefined, EditPresetRequest>
|
||||
): Promise<MonkeyResponse2> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import MonkeyError from "../utils/error";
|
||||
import * as db from "../init/db";
|
||||
import { ObjectId, type Filter, Collection, type WithId } from "mongodb";
|
||||
import { Preset } from "@monkeytype/contracts/schemas/presets";
|
||||
import {
|
||||
EditPresetRequest,
|
||||
Preset,
|
||||
} from "@monkeytype/contracts/schemas/presets";
|
||||
import { omit } from "lodash";
|
||||
|
||||
const MAX_PRESETS = 10;
|
||||
|
||||
|
@ -56,15 +60,21 @@ export async function addPreset(
|
|||
};
|
||||
}
|
||||
|
||||
export async function editPreset(uid: string, preset: Preset): Promise<void> {
|
||||
const config = preset.config;
|
||||
const presetUpdates =
|
||||
config !== undefined && config !== null && Object.keys(config).length > 0
|
||||
? { name: preset.name, config }
|
||||
: { name: preset.name };
|
||||
export async function editPreset(
|
||||
uid: string,
|
||||
preset: EditPresetRequest
|
||||
): Promise<void> {
|
||||
const update: Partial<Omit<Preset, "_id">> = omit(preset, "_id");
|
||||
if (
|
||||
preset.config === undefined ||
|
||||
preset.config === null ||
|
||||
Object.keys(preset.config).length === 0
|
||||
) {
|
||||
delete update.config;
|
||||
}
|
||||
|
||||
await getPresetsCollection().updateOne(getPresetKeyFilter(uid, preset._id), {
|
||||
$set: presetUpdates,
|
||||
$set: update,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import { migrateConfig } from "../../src/ts/utils/config";
|
||||
import DefaultConfig from "../../src/ts/constants/default-config";
|
||||
import { PartialConfig } from "@monkeytype/contracts/schemas/configs";
|
||||
import {
|
||||
PartialConfig,
|
||||
ShowAverageSchema,
|
||||
} from "@monkeytype/contracts/schemas/configs";
|
||||
|
||||
describe("config.ts", () => {
|
||||
describe("migrateConfig", () => {
|
||||
|
|
|
@ -953,14 +953,30 @@
|
|||
</dialog>
|
||||
<dialog id="editPresetModal" class="modalWrapper hidden">
|
||||
<form class="modal">
|
||||
<div class="title"></div>
|
||||
<input type="text" title="presets" />
|
||||
<label class="checkbox">
|
||||
<div class="title popupTitle"></div>
|
||||
<div class="group">
|
||||
<div class="presetNameTitle">name</div>
|
||||
<input type="text" title="presets" />
|
||||
</div>
|
||||
<label class="changePresetToCurrentCheckbox checkbox">
|
||||
<input type="checkbox" />
|
||||
Change preset to current settings
|
||||
</label>
|
||||
<div class="text"></div>
|
||||
<button type="submit">add</button>
|
||||
<div class="inputs">
|
||||
<div class="presetType group" data-id="presetType">
|
||||
<div class="title">Preset Type</div>
|
||||
<div class="presetTypeButtonGroup">
|
||||
<button value="full" type="button">full</button>
|
||||
<button value="partial" type="button">partial</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="partialPresetGroups group">
|
||||
<div class="title">partial groups</div>
|
||||
<div class="checkboxList"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text deletePrompt"></div>
|
||||
<button class="submit" type="submit">add</button>
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog id="shareCustomThemeModal" class="modalWrapper hidden">
|
||||
|
|
|
@ -1478,6 +1478,62 @@ body.darkMode {
|
|||
#editPresetModal {
|
||||
.modal {
|
||||
max-width: 450px;
|
||||
gap: 1rem;
|
||||
.presetNameTitle {
|
||||
font-size: 0.75rem;
|
||||
color: var(--sub-color);
|
||||
text-transform: lowercase;
|
||||
}
|
||||
}
|
||||
|
||||
.group {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
font-size: 0.75rem;
|
||||
.title {
|
||||
color: var(--sub-color);
|
||||
text-transform: lowercase;
|
||||
}
|
||||
.partialPresetGroups {
|
||||
.checkboxList {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
.title {
|
||||
color: var(--text-color);
|
||||
text-transform: lowercase;
|
||||
}
|
||||
.checkboxTitlePair {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
button {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.presetType {
|
||||
.presetTypeButtonGroup {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
button {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2064,8 +2064,8 @@ export async function loadFromLocalStorage(): Promise<void> {
|
|||
loadDone();
|
||||
}
|
||||
|
||||
export function getConfigChanges(): MonkeyTypes.PresetConfig {
|
||||
const configChanges = {} as MonkeyTypes.PresetConfig;
|
||||
export function getConfigChanges(): MonkeyTypes.ConfigChanges {
|
||||
const configChanges: MonkeyTypes.ConfigChanges = {};
|
||||
typedKeys(config)
|
||||
.filter((key) => {
|
||||
return config[key] !== DefaultConfig[key];
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { Preset } from "@monkeytype/contracts/schemas/presets";
|
||||
import * as UpdateConfig from "../config";
|
||||
import * as DB from "../db";
|
||||
import * as Notifications from "../elements/notifications";
|
||||
import * as TestLogic from "../test/test-logic";
|
||||
import { migrateConfig } from "../utils/config";
|
||||
import { migrateConfig, replaceLegacyValues } from "../utils/config";
|
||||
import * as TagController from "./tag-controller";
|
||||
|
||||
export async function apply(_id: string): Promise<void> {
|
||||
|
@ -10,19 +11,29 @@ export async function apply(_id: string): Promise<void> {
|
|||
if (!snapshot) return;
|
||||
|
||||
const presetToApply = snapshot.presets?.find((preset) => preset._id === _id);
|
||||
|
||||
if (presetToApply === undefined) {
|
||||
Notifications.add("Preset not found", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
await UpdateConfig.apply(migrateConfig(presetToApply.config));
|
||||
TagController.clear(true);
|
||||
if (presetToApply.config.tags) {
|
||||
for (const tagId of presetToApply.config.tags) {
|
||||
TagController.set(tagId, true, false);
|
||||
if (isPartialPreset(presetToApply)) {
|
||||
const combinedConfig = {
|
||||
...UpdateConfig.getConfigChanges(),
|
||||
...replaceLegacyValues(presetToApply.config),
|
||||
};
|
||||
await UpdateConfig.apply(migrateConfig(combinedConfig));
|
||||
} else {
|
||||
await UpdateConfig.apply(migrateConfig(presetToApply.config));
|
||||
}
|
||||
if (
|
||||
!isPartialPreset(presetToApply) ||
|
||||
presetToApply.settingGroups?.includes("behavior")
|
||||
) {
|
||||
TagController.clear(true);
|
||||
if (presetToApply.config.tags) {
|
||||
for (const tagId of presetToApply.config.tags) {
|
||||
TagController.set(tagId, true, false);
|
||||
}
|
||||
TagController.saveActiveToLocalStorage();
|
||||
}
|
||||
TagController.saveActiveToLocalStorage();
|
||||
}
|
||||
TestLogic.restart();
|
||||
Notifications.add("Preset applied", 1, {
|
||||
|
@ -30,3 +41,21 @@ export async function apply(_id: string): Promise<void> {
|
|||
});
|
||||
UpdateConfig.saveFullConfigToLocalStorage();
|
||||
}
|
||||
function isPartialPreset(preset: MonkeyTypes.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) {
|
||||
Notifications.add("Preset not found", 0);
|
||||
return;
|
||||
}
|
||||
return preset;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,27 @@ import * as Settings from "../pages/settings";
|
|||
import * as Notifications from "../elements/notifications";
|
||||
import * as ConnectionState from "../states/connection";
|
||||
import AnimatedModal from "../utils/animated-modal";
|
||||
import {
|
||||
ActiveSettingGroups,
|
||||
ActiveSettingGroupsSchema,
|
||||
PresetSettingGroup,
|
||||
PresetSettingGroupSchema,
|
||||
PresetType,
|
||||
PresetTypeSchema,
|
||||
} from "@monkeytype/contracts/schemas/presets";
|
||||
import { getPreset } from "../controllers/preset-controller";
|
||||
import defaultConfig from "../constants/default-config";
|
||||
|
||||
const state = {
|
||||
presetType: "full" as PresetType,
|
||||
checkboxes: new Map(
|
||||
PresetSettingGroupSchema.options.map((key: PresetSettingGroup) => [
|
||||
key,
|
||||
true,
|
||||
])
|
||||
),
|
||||
setPresetToCurrent: false,
|
||||
};
|
||||
|
||||
export function show(action: string, id?: string, name?: string): void {
|
||||
if (!ConnectionState.get()) {
|
||||
|
@ -19,22 +40,33 @@ export function show(action: string, id?: string, name?: string): void {
|
|||
focusFirstInput: true,
|
||||
beforeAnimation: async () => {
|
||||
$("#editPresetModal .modal .text").addClass("hidden");
|
||||
addCheckBoxes();
|
||||
if (action === "add") {
|
||||
$("#editPresetModal .modal").attr("data-action", "add");
|
||||
$("#editPresetModal .modal .title").html("Add new preset");
|
||||
$("#editPresetModal .modal button").html(`add`);
|
||||
$("#editPresetModal .modal .popupTitle").html("Add new preset");
|
||||
$("#editPresetModal .modal .submit").html(`add`);
|
||||
$("#editPresetModal .modal input").val("");
|
||||
$("#editPresetModal .modal input").removeClass("hidden");
|
||||
$("#editPresetModal .modal label").addClass("hidden");
|
||||
$(
|
||||
"#editPresetModal .modal label.changePresetToCurrentCheckbox"
|
||||
).addClass("hidden");
|
||||
$("#editPresetModal .modal .inputs").removeClass("hidden");
|
||||
$("#editPresetModal .modal .presetType").removeClass("hidden");
|
||||
$("#editPresetModal .modal .presetNameTitle").removeClass("hidden");
|
||||
state.presetType = "full";
|
||||
} else if (action === "edit" && id !== undefined && name !== undefined) {
|
||||
$("#editPresetModal .modal").attr("data-action", "edit");
|
||||
$("#editPresetModal .modal").attr("data-preset-id", id);
|
||||
$("#editPresetModal .modal .title").html("Edit preset");
|
||||
$("#editPresetModal .modal button").html(`save`);
|
||||
$("#editPresetModal .modal .popupTitle").html("Edit preset");
|
||||
$("#editPresetModal .modal .submit").html(`save`);
|
||||
$("#editPresetModal .modal input").val(name);
|
||||
$("#editPresetModal .modal input").removeClass("hidden");
|
||||
$("#editPresetModal .modal label input").prop("checked", false);
|
||||
$("#editPresetModal .modal label").removeClass("hidden");
|
||||
$(
|
||||
"#editPresetModal .modal label.changePresetToCurrentCheckbox"
|
||||
).removeClass("hidden");
|
||||
$("#editPresetModal .modal .presetNameTitle").removeClass("hidden");
|
||||
state.setPresetToCurrent = false;
|
||||
await updateEditPresetUI();
|
||||
} else if (
|
||||
action === "remove" &&
|
||||
id !== undefined &&
|
||||
|
@ -42,19 +74,132 @@ export function show(action: string, id?: string, name?: string): void {
|
|||
) {
|
||||
$("#editPresetModal .modal").attr("data-action", "remove");
|
||||
$("#editPresetModal .modal").attr("data-preset-id", id);
|
||||
$("#editPresetModal .modal .title").html("Delete preset");
|
||||
$("#editPresetModal .modal button").html("delete");
|
||||
$("#editPresetModal .modal .popupTitle").html("Delete preset");
|
||||
$("#editPresetModal .modal .submit").html("delete");
|
||||
$("#editPresetModal .modal input").addClass("hidden");
|
||||
$("#editPresetModal .modal label").addClass("hidden");
|
||||
$(
|
||||
"#editPresetModal .modal label.changePresetToCurrentCheckbox"
|
||||
).addClass("hidden");
|
||||
$("#editPresetModal .modal .text").removeClass("hidden");
|
||||
$("#editPresetModal .modal .text").text(
|
||||
$("#editPresetModal .modal .deletePrompt").text(
|
||||
`Are you sure you want to delete the preset ${name}?`
|
||||
);
|
||||
$("#editPresetModal .modal .inputs").addClass("hidden");
|
||||
$("#editPresetModal .modal .presetType").addClass("hidden");
|
||||
$("#editPresetModal .modal .presetNameTitle").addClass("hidden");
|
||||
}
|
||||
updateUI();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function initializeEditState(id: string): Promise<void> {
|
||||
for (const key of state.checkboxes.keys()) {
|
||||
state.checkboxes.set(key, false);
|
||||
}
|
||||
const edittedPreset = await getPreset(id);
|
||||
if (edittedPreset === undefined) {
|
||||
Notifications.add("Preset not found", -1);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
edittedPreset.settingGroups === undefined ||
|
||||
edittedPreset.settingGroups === null
|
||||
) {
|
||||
state.presetType = "full";
|
||||
for (const key of state.checkboxes.keys()) {
|
||||
state.checkboxes.set(key, true);
|
||||
}
|
||||
} else {
|
||||
state.presetType = "partial";
|
||||
edittedPreset.settingGroups.forEach((currentActiveSettingGroup) =>
|
||||
state.checkboxes.set(currentActiveSettingGroup, true)
|
||||
);
|
||||
}
|
||||
state.setPresetToCurrent = false;
|
||||
updateUI();
|
||||
}
|
||||
|
||||
function addCheckboxListeners(): void {
|
||||
PresetSettingGroupSchema.options.forEach(
|
||||
(settingGroup: PresetSettingGroup) => {
|
||||
const checkboxInput = $(
|
||||
`#editPresetModal .modal .checkboxList .checkboxTitlePair[data-id="${settingGroup}"] input`
|
||||
);
|
||||
checkboxInput.on("change", (e) => {
|
||||
state.checkboxes.set(
|
||||
settingGroup,
|
||||
checkboxInput.prop("checked") as boolean
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const presetToCurrentCheckbox = $(
|
||||
`#editPresetModal .modal .changePresetToCurrentCheckbox input`
|
||||
);
|
||||
presetToCurrentCheckbox.on("change", async () => {
|
||||
state.setPresetToCurrent = presetToCurrentCheckbox.prop("checked");
|
||||
await updateEditPresetUI();
|
||||
});
|
||||
}
|
||||
|
||||
function addCheckBoxes(): void {
|
||||
function camelCaseToSpaced(input: string): string {
|
||||
return input.replace(/([a-z])([A-Z])/g, "$1 $2");
|
||||
}
|
||||
const settingGroupListEl = $(
|
||||
"#editPresetModal .modal .inputs .checkboxList"
|
||||
).empty();
|
||||
PresetSettingGroupSchema.options.forEach((currSettingGroup) => {
|
||||
const currSettingGroupTitle = camelCaseToSpaced(currSettingGroup);
|
||||
const settingGroupCheckbox: string = `<label class="checkboxTitlePair" data-id="${currSettingGroup}">
|
||||
<input type="checkbox" />
|
||||
<div class="title">${currSettingGroupTitle}</div>
|
||||
</label>`;
|
||||
settingGroupListEl.append(settingGroupCheckbox);
|
||||
});
|
||||
for (const key of state.checkboxes.keys()) {
|
||||
state.checkboxes.set(key, true);
|
||||
}
|
||||
addCheckboxListeners();
|
||||
}
|
||||
|
||||
function updateUI(): void {
|
||||
PresetSettingGroupSchema.options.forEach(
|
||||
(settingGroup: PresetSettingGroup) => {
|
||||
$(
|
||||
`#editPresetModal .modal .checkboxList .checkboxTitlePair[data-id="${settingGroup}"] input`
|
||||
).prop("checked", state.checkboxes.get(settingGroup));
|
||||
}
|
||||
);
|
||||
$(`#editPresetModal .modal .presetType button`).removeClass("active");
|
||||
$(
|
||||
`#editPresetModal .modal .presetType button[value="${state.presetType}"]`
|
||||
).addClass("active");
|
||||
$(`#editPresetModal .modal .partialPresetGroups`).removeClass("hidden");
|
||||
if (state.presetType === "full") {
|
||||
$(`#editPresetModal .modal .partialPresetGroups`).addClass("hidden");
|
||||
}
|
||||
}
|
||||
async function updateEditPresetUI(): Promise<void> {
|
||||
$("#editPresetModal .modal label.changePresetToCurrentCheckbox input").prop(
|
||||
"checked",
|
||||
state.setPresetToCurrent
|
||||
);
|
||||
if (state.setPresetToCurrent) {
|
||||
const presetId = $("#editPresetModal .modal").attr(
|
||||
"data-preset-id"
|
||||
) as string;
|
||||
await initializeEditState(presetId);
|
||||
$("#editPresetModal .modal .inputs").removeClass("hidden");
|
||||
$("#editPresetModal .modal .presetType").removeClass("hidden");
|
||||
} else {
|
||||
$("#editPresetModal .modal .inputs").addClass("hidden");
|
||||
$("#editPresetModal .modal .presetType").addClass("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function hide(): void {
|
||||
void modal.hide();
|
||||
}
|
||||
|
@ -71,28 +216,47 @@ async function apply(): Promise<void> {
|
|||
"checked"
|
||||
);
|
||||
|
||||
let configChanges: MonkeyTypes.ConfigChanges = {};
|
||||
const snapshotPresets = DB.getSnapshot()?.presets ?? [];
|
||||
|
||||
if ((updateConfig && action === "edit") || action === "add") {
|
||||
configChanges = Config.getConfigChanges();
|
||||
|
||||
const tags = DB.getSnapshot()?.tags ?? [];
|
||||
|
||||
const activeTagIds: string[] = tags
|
||||
.filter((tag: MonkeyTypes.UserTag) => tag.active)
|
||||
.map((tag: MonkeyTypes.UserTag) => tag._id);
|
||||
configChanges.tags = activeTagIds;
|
||||
if (action === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const snapshotPresets = DB.getSnapshot()?.presets ?? [];
|
||||
const noPartialGroupSelected: boolean =
|
||||
["add", "edit"].includes(action) &&
|
||||
state.presetType === "partial" &&
|
||||
Array.from(state.checkboxes.values()).every((val: boolean) => !val);
|
||||
if (noPartialGroupSelected) {
|
||||
Notifications.add(
|
||||
"At least one setting group must be active while saving partial presets",
|
||||
0
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const noPresetName: boolean =
|
||||
["add", "edit"].includes(action) &&
|
||||
presetName.replace(/^_+|_+$/g, "").length === 0; //all whitespace names are rejected
|
||||
if (noPresetName) {
|
||||
Notifications.add("Preset name cannot be empty", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
hide();
|
||||
|
||||
Loader.show();
|
||||
|
||||
if (action === "add") {
|
||||
const configChanges = getConfigChanges();
|
||||
const activeSettingGroups = getActiveSettingGroupsFromState();
|
||||
const response = await Ape.presets.add({
|
||||
body: { name: presetName, config: configChanges },
|
||||
body: {
|
||||
name: presetName,
|
||||
config: configChanges,
|
||||
...(state.presetType === "partial" && {
|
||||
settingGroups: activeSettingGroups,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status !== 200 || response.body.data === null) {
|
||||
|
@ -108,16 +272,32 @@ async function apply(): Promise<void> {
|
|||
snapshotPresets.push({
|
||||
name: presetName,
|
||||
config: configChanges,
|
||||
...(state.presetType === "partial" && {
|
||||
settingGroups: activeSettingGroups,
|
||||
}),
|
||||
display: propPresetName,
|
||||
_id: response.body.data.presetId,
|
||||
} as MonkeyTypes.SnapshotPreset);
|
||||
}
|
||||
} else if (action === "edit") {
|
||||
const preset = snapshotPresets.filter(
|
||||
(preset: MonkeyTypes.SnapshotPreset) => preset._id === presetId
|
||||
)[0] as MonkeyTypes.SnapshotPreset;
|
||||
if (preset === undefined) {
|
||||
Notifications.add("Preset not found", -1);
|
||||
return;
|
||||
}
|
||||
const configChanges = getConfigChanges();
|
||||
const activeSettingGroups: ActiveSettingGroups | null =
|
||||
state.presetType === "partial" ? getActiveSettingGroupsFromState() : null;
|
||||
const response = await Ape.presets.save({
|
||||
body: {
|
||||
_id: presetId,
|
||||
name: presetName,
|
||||
config: configChanges,
|
||||
...(updateConfig && {
|
||||
config: configChanges,
|
||||
settingGroups: activeSettingGroups,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -125,13 +305,16 @@ async function apply(): Promise<void> {
|
|||
Notifications.add("Failed to edit preset: " + response.body.message, -1);
|
||||
} else {
|
||||
Notifications.add("Preset updated", 1);
|
||||
const preset = snapshotPresets.filter(
|
||||
(preset: MonkeyTypes.SnapshotPreset) => preset._id === presetId
|
||||
)[0] as MonkeyTypes.SnapshotPreset;
|
||||
|
||||
preset.name = presetName;
|
||||
preset.display = presetName.replace(/_/g, " ");
|
||||
if (updateConfig) {
|
||||
preset.config = configChanges;
|
||||
if (state.presetType === "partial") {
|
||||
preset.settingGroups = getActiveSettingGroupsFromState();
|
||||
} else {
|
||||
preset.settingGroups = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (action === "remove") {
|
||||
|
@ -158,12 +341,197 @@ async function apply(): Promise<void> {
|
|||
Loader.hide();
|
||||
}
|
||||
|
||||
function getSettingGroup(configFieldName: string): PresetSettingGroup {
|
||||
const themeSettings = [
|
||||
"theme",
|
||||
"themeLight",
|
||||
"themeDark",
|
||||
"autoSwitchTheme",
|
||||
"customTheme",
|
||||
"customThemeColors",
|
||||
"favThemes",
|
||||
"flipTestColors",
|
||||
"colorfulMode",
|
||||
"randomTheme",
|
||||
"customBackground",
|
||||
"customBackgroundSize",
|
||||
"customBackgroundFilter",
|
||||
];
|
||||
const hideElementsSettings = [
|
||||
"showKeyTips",
|
||||
"capsLockWarning",
|
||||
"showOutOfFocusWarning",
|
||||
"showAverage",
|
||||
];
|
||||
const caretSettings = [
|
||||
"smoothCaret",
|
||||
"caretStyle",
|
||||
"paceCaretStyle",
|
||||
"paceCaret",
|
||||
"paceCaretCustomSpeed",
|
||||
"repeatedPace",
|
||||
];
|
||||
const behaviorSettings = [
|
||||
"quickRestart",
|
||||
"difficulty",
|
||||
"blindMode",
|
||||
"funbox",
|
||||
"alwaysShowWordsHistory",
|
||||
"singleListCommandLine",
|
||||
"minWpm",
|
||||
"minWpmCustomSpeed",
|
||||
"minAcc",
|
||||
"minAccCustom",
|
||||
"repeatQuotes",
|
||||
"customLayoutfluid",
|
||||
"minBurst",
|
||||
"minBurstCustomSpeed",
|
||||
"britishEnglish",
|
||||
"tags",
|
||||
];
|
||||
const testSettings = [
|
||||
"punctuation",
|
||||
"words",
|
||||
"time",
|
||||
"numbers",
|
||||
"mode",
|
||||
"quoteLength",
|
||||
"language",
|
||||
"burstHeatmap",
|
||||
];
|
||||
const appearanceSettings = [
|
||||
"fontSize",
|
||||
"timerStyle",
|
||||
"liveSpeedStyle",
|
||||
"liveAccStyle",
|
||||
"liveBurstStyle",
|
||||
"timerColor",
|
||||
"timerOpacity",
|
||||
"showAllLines",
|
||||
"keymapMode",
|
||||
"keymapStyle",
|
||||
"keymapLegendStyle",
|
||||
"keymapLayout",
|
||||
"keymapShowTopRow",
|
||||
"keymapSize",
|
||||
"fontFamily",
|
||||
"smoothLineScroll",
|
||||
"alwaysShowDecimalPlaces",
|
||||
"startGraphsAtZero",
|
||||
"highlightMode",
|
||||
"tapeMode",
|
||||
"typingSpeedUnit",
|
||||
"maxLineWidth",
|
||||
];
|
||||
const inputSettings = [
|
||||
"freedomMode",
|
||||
"quickEnd",
|
||||
"layout",
|
||||
"confidenceMode",
|
||||
"indicateTypos",
|
||||
"stopOnError",
|
||||
"hideExtraLetters",
|
||||
"strictSpace",
|
||||
"oppositeShiftMode",
|
||||
"lazyMode",
|
||||
];
|
||||
const soundSettings = ["playSoundOnError", "playSoundOnClick", "soundVolume"];
|
||||
const hiddenSettings = ["accountChart", "monkey", "monkeyPowerLevel"];
|
||||
const adsSettings = ["ads"];
|
||||
|
||||
if (themeSettings.includes(configFieldName)) {
|
||||
return "theme";
|
||||
} else if (hideElementsSettings.includes(configFieldName)) {
|
||||
return "hideElements";
|
||||
} else if (caretSettings.includes(configFieldName)) {
|
||||
return "caret";
|
||||
} else if (behaviorSettings.includes(configFieldName)) {
|
||||
return "behavior";
|
||||
} else if (testSettings.includes(configFieldName)) {
|
||||
return "test";
|
||||
} else if (appearanceSettings.includes(configFieldName)) {
|
||||
return "appearance";
|
||||
} else if (inputSettings.includes(configFieldName)) {
|
||||
return "input";
|
||||
} else if (soundSettings.includes(configFieldName)) {
|
||||
return "sound";
|
||||
} else if (hiddenSettings.includes(configFieldName)) {
|
||||
return "hidden";
|
||||
} else if (adsSettings.includes(configFieldName)) {
|
||||
return "ads";
|
||||
}
|
||||
|
||||
throw new Error(`${configFieldName} setting not part of any setting group`);
|
||||
}
|
||||
|
||||
function getPartialConfigChanges(
|
||||
configChanges: MonkeyTypes.ConfigChanges
|
||||
): MonkeyTypes.ConfigChanges {
|
||||
const activeConfigChanges: MonkeyTypes.ConfigChanges = {};
|
||||
Object.keys(defaultConfig)
|
||||
.filter(
|
||||
(settingName) =>
|
||||
state.checkboxes.get(getSettingGroup(settingName)) === true
|
||||
)
|
||||
.forEach((settingName) => {
|
||||
//@ts-expect-error this is fine
|
||||
activeConfigChanges[settingName] =
|
||||
//@ts-expect-error this is fine
|
||||
configChanges[settingName] !== undefined
|
||||
? //@ts-expect-error this is fine
|
||||
configChanges[settingName]
|
||||
: //@ts-expect-error this is fine
|
||||
defaultConfig[settingName];
|
||||
});
|
||||
return activeConfigChanges;
|
||||
}
|
||||
function getActiveSettingGroupsFromState(): ActiveSettingGroups {
|
||||
return ActiveSettingGroupsSchema.parse(
|
||||
Array.from(state.checkboxes.entries())
|
||||
.filter(([, value]) => value)
|
||||
.map(([key]) => key)
|
||||
);
|
||||
}
|
||||
function getConfigChanges(): MonkeyTypes.ConfigChanges {
|
||||
const activeConfigChanges =
|
||||
state.presetType === "partial"
|
||||
? getPartialConfigChanges(Config.getConfigChanges())
|
||||
: Config.getConfigChanges();
|
||||
const tags = DB.getSnapshot()?.tags ?? [];
|
||||
|
||||
const activeTagIds: string[] = tags
|
||||
.filter((tag: MonkeyTypes.UserTag) => tag.active)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
.map((tag: MonkeyTypes.UserTag) => tag._id);
|
||||
|
||||
const setTags: boolean =
|
||||
state.presetType === "full" || state.checkboxes.get("behavior") === true;
|
||||
return {
|
||||
...activeConfigChanges,
|
||||
...(setTags && {
|
||||
tags: activeTagIds,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
async function setup(modalEl: HTMLElement): Promise<void> {
|
||||
modalEl.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
void apply();
|
||||
});
|
||||
PresetTypeSchema.options.forEach((presetType) => {
|
||||
const presetOption = modalEl.querySelector(
|
||||
`.presetType button[value="${presetType}"]`
|
||||
);
|
||||
if (presetOption === null) return;
|
||||
|
||||
presetOption.addEventListener("click", () => {
|
||||
state.presetType = presetType;
|
||||
updateUI();
|
||||
});
|
||||
});
|
||||
}
|
||||
const modal = new AnimatedModal({
|
||||
dialogId: "editPresetModal",
|
||||
setup: async (modalEl): Promise<void> => {
|
||||
modalEl.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
void apply();
|
||||
});
|
||||
},
|
||||
setup,
|
||||
});
|
||||
|
|
10
frontend/src/ts/types/types.d.ts
vendored
10
frontend/src/ts/types/types.d.ts
vendored
|
@ -179,10 +179,6 @@ declare namespace MonkeyTypes {
|
|||
hasCSS?: boolean;
|
||||
};
|
||||
|
||||
type PresetConfig = {
|
||||
tags: string[];
|
||||
} & import("@monkeytype/contracts/schemas/configs").Config;
|
||||
|
||||
type SnapshotPreset =
|
||||
import("@monkeytype/contracts/schemas/presets").Preset & {
|
||||
display: string;
|
||||
|
@ -197,9 +193,9 @@ declare namespace MonkeyTypes {
|
|||
_id: string;
|
||||
} & RawCustomTheme;
|
||||
|
||||
type ConfigChanges = {
|
||||
tags?: string[];
|
||||
} & Partial<import("@monkeytype/contracts/schemas/configs").Config>;
|
||||
type ConfigChanges = Partial<
|
||||
import("@monkeytype/contracts/schemas/configs").Config
|
||||
>;
|
||||
|
||||
type LeaderboardMemory = {
|
||||
time: {
|
||||
|
|
|
@ -26,7 +26,7 @@ function mergeWithDefaultConfig(config: PartialConfig): Config {
|
|||
return mergedConfig;
|
||||
}
|
||||
|
||||
function replaceLegacyValues(
|
||||
export function replaceLegacyValues(
|
||||
configObj: ConfigSchemas.PartialConfig
|
||||
): ConfigSchemas.PartialConfig {
|
||||
//@ts-expect-error
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
MonkeyResponseSchema,
|
||||
responseWithData,
|
||||
} from "./schemas/api";
|
||||
import { PresetSchema } from "./schemas/presets";
|
||||
import { EditPresetRequestSchema, PresetSchema } from "./schemas/presets";
|
||||
import { IdSchema } from "./schemas/util";
|
||||
|
||||
export const GetPresetResponseSchema = responseWithData(z.array(PresetSchema));
|
||||
|
@ -60,7 +60,7 @@ export const presetsContract = c.router(
|
|||
description: "Update an existing preset for the current user.",
|
||||
method: "PATCH",
|
||||
path: "",
|
||||
body: PresetSchema.strict(),
|
||||
body: EditPresetRequestSchema.strict(),
|
||||
responses: {
|
||||
200: MonkeyResponseSchema,
|
||||
},
|
||||
|
|
|
@ -8,11 +8,57 @@ export const PresetNameSchema = z
|
|||
.max(16);
|
||||
export type PresentName = z.infer<typeof PresetNameSchema>;
|
||||
|
||||
export const PresetTypeSchema = z.enum(["full", "partial"]);
|
||||
export type PresetType = z.infer<typeof PresetTypeSchema>;
|
||||
|
||||
export const PresetSettingGroupSchema = z.enum([
|
||||
"test",
|
||||
"behavior",
|
||||
"input",
|
||||
"sound",
|
||||
"caret",
|
||||
"appearance",
|
||||
"theme",
|
||||
"hideElements",
|
||||
"ads",
|
||||
"hidden",
|
||||
]);
|
||||
export type PresetSettingGroup = z.infer<typeof PresetSettingGroupSchema>;
|
||||
export const ActiveSettingGroupsSchema = z
|
||||
.array(PresetSettingGroupSchema)
|
||||
.min(1)
|
||||
.superRefine((settingList, ctx) => {
|
||||
PresetSettingGroupSchema.options.forEach(
|
||||
(presetSettingGroup: PresetSettingGroup) => {
|
||||
const duplicateElemExits: boolean =
|
||||
settingList.filter(
|
||||
(settingGroup: PresetSettingGroup) =>
|
||||
settingGroup === presetSettingGroup
|
||||
).length > 1;
|
||||
if (duplicateElemExits) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `No duplicates allowed.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
export type ActiveSettingGroups = z.infer<typeof ActiveSettingGroupsSchema>;
|
||||
|
||||
export const PresetSchema = z.object({
|
||||
_id: IdSchema,
|
||||
name: PresetNameSchema,
|
||||
settingGroups: ActiveSettingGroupsSchema.nullable().optional(),
|
||||
config: PartialConfigSchema.extend({
|
||||
tags: z.array(TagSchema).optional(),
|
||||
}),
|
||||
});
|
||||
export type Preset = z.infer<typeof PresetSchema>;
|
||||
|
||||
export const EditPresetRequestSchema = PresetSchema.partial({
|
||||
config: true,
|
||||
settingGroups: true,
|
||||
});
|
||||
|
||||
export type EditPresetRequest = z.infer<typeof EditPresetRequestSchema>;
|
||||
|
|
Loading…
Reference in a new issue