From 74ea66083d04ffc43cd628b3ff64b4597b22f6ae Mon Sep 17 00:00:00 2001 From: Bauke Date: Wed, 28 Jun 2023 18:46:27 +0200 Subject: [PATCH] Rework UsernameColorsData to be stored individual keys. --- .../features/username-colors.ts | 4 +- source/content-scripts/setup.tsx | 2 +- source/options/components/username-colors.tsx | 59 ++++++++++++------- source/storage/exports.ts | 24 ++------ source/storage/migrations/migrations.test.ts | 9 ++- source/storage/migrations/migrations.ts | 15 +++-- source/storage/username-color.ts | 58 ++++++++++++++++++ 7 files changed, 123 insertions(+), 48 deletions(-) create mode 100644 source/storage/username-color.ts diff --git a/source/content-scripts/features/username-colors.ts b/source/content-scripts/features/username-colors.ts index 7bcfee1..e09ce3d 100644 --- a/source/content-scripts/features/username-colors.ts +++ b/source/content-scripts/features/username-colors.ts @@ -14,7 +14,9 @@ function usernameColors( anonymizeUsernamesEnabled: boolean, ): number { const usernameColors = new Map(); - for (const {color, username: usernames} of data) { + for (const { + value: {color, username: usernames}, + } of data) { for (const username of usernames.split(",")) { usernameColors.set(username.trim().toLowerCase(), color); } diff --git a/source/content-scripts/setup.tsx b/source/content-scripts/setup.tsx index 20f1482..96c8764 100644 --- a/source/content-scripts/setup.tsx +++ b/source/content-scripts/setup.tsx @@ -86,7 +86,7 @@ async function initialize() { if (enabledFeatures.value.has(Feature.UsernameColors)) { observerFeatures.push(async () => { const data = await fromStorage(Feature.UsernameColors); - runUsernameColorsFeature(data.value, anonymizeUsernamesEnabled); + runUsernameColorsFeature(data, anonymizeUsernamesEnabled); }); } diff --git a/source/options/components/username-colors.tsx b/source/options/components/username-colors.tsx index 9d13b7f..a078e2b 100644 --- a/source/options/components/username-colors.tsx +++ b/source/options/components/username-colors.tsx @@ -1,17 +1,18 @@ import {Component} from "preact"; -import {type Value} from "@holllo/webextension-storage"; import {log} from "../../utilities/exports.js"; import { type UsernameColorsData, type UsernameColor, Feature, + createValueUsernamecolor, fromStorage, } from "../../storage/exports.js"; import {Setting, type SettingProps} from "./index.js"; type State = { previewChecked: "off" | "foreground" | "background"; - usernameColors: Value; + usernameColors: UsernameColorsData; + usernameColorsToRemove: UsernameColorsData; }; export class UsernameColorsSetting extends Component { @@ -21,6 +22,7 @@ export class UsernameColorsSetting extends Component { this.state = { previewChecked: "off", usernameColors: undefined!, + usernameColorsToRemove: [], }; } @@ -28,35 +30,50 @@ export class UsernameColorsSetting extends Component { this.setState({usernameColors: await fromStorage(Feature.UsernameColors)}); } - addNewColor = () => { + addNewColor = async () => { let id = 1; - if (this.state.usernameColors.value.length > 0) { + if (this.state.usernameColors.length > 0) { id = - this.state.usernameColors.value.sort((a, b) => b.id - a.id)[0].id + 1; + this.state.usernameColors.sort((a, b) => b.value.id - a.value.id)[0] + .value.id + 1; } - const newColor: UsernameColor = { + const newColor = await createValueUsernamecolor({ color: "", id, username: "", - }; + }); - this.state.usernameColors.value.push(newColor); + this.state.usernameColors.push(newColor); this.setState({ usernameColors: this.state.usernameColors, }); }; - removeColor = (targetId: number) => { - const targetIndex = this.state.usernameColors.value.findIndex( - ({id}) => id === targetId, + removeColor = async (targetId: number) => { + const targetIndex = this.state.usernameColors.findIndex( + ({value}) => value.id === targetId, ); - this.state.usernameColors.value.splice(targetIndex, 1); - this.setState({usernameColors: this.state.usernameColors}); + const usernameColorsToRemove = this.state.usernameColorsToRemove; + usernameColorsToRemove.push( + ...this.state.usernameColors.splice(targetIndex, 1), + ); + this.setState({ + usernameColors: this.state.usernameColors, + usernameColorsToRemove, + }); }; saveChanges = async () => { - await this.state.usernameColors.save(); + for (const usernameColor of this.state.usernameColorsToRemove) { + await usernameColor.remove(); + } + + for (const usernameColor of this.state.usernameColors) { + await usernameColor.save(); + } + + this.setState({usernameColorsToRemove: []}); }; togglePreview = async () => { @@ -84,8 +101,8 @@ export class UsernameColorsSetting extends Component { }; onInput = (event: Event, id: number, key: "color" | "username") => { - const colorIndex = this.state.usernameColors.value.findIndex( - (color) => color.id === id, + const colorIndex = this.state.usernameColors.findIndex( + ({value}) => value.id === id, ); if (colorIndex === -1) { log(`Tried to edit unknown UsernameColor ID: ${id}`); @@ -93,7 +110,7 @@ export class UsernameColorsSetting extends Component { } const newValue = (event.target as HTMLInputElement).value; - this.state.usernameColors.value[colorIndex][key] = newValue; + this.state.usernameColors[colorIndex].value[key] = newValue; this.setState({usernameColors: this.state.usernameColors}); }; @@ -103,9 +120,9 @@ export class UsernameColorsSetting extends Component { return; } - usernameColors.value.sort((a, b) => a.id - b.id); + usernameColors.sort((a, b) => a.value.id - b.value.id); - const editors = usernameColors.value.map(({color, id, username}) => { + const editors = usernameColors.map(({value: {color, id, username}}) => { const style: Record = {}; if (previewChecked === "background") { style.backgroundColor = color; @@ -121,8 +138,8 @@ export class UsernameColorsSetting extends Component { this.onInput(event, id, "color"); }; - const removeHandler = () => { - this.removeColor(id); + const removeHandler = async () => { + await this.removeColor(id); }; return ( diff --git a/source/storage/exports.ts b/source/storage/exports.ts index 9578325..de6d11c 100644 --- a/source/storage/exports.ts +++ b/source/storage/exports.ts @@ -1,10 +1,12 @@ -import {createValue, type Value} from "@holllo/webextension-storage"; +import {createValue} from "@holllo/webextension-storage"; import browser from "webextension-polyfill"; -import {collectUserLabels} from "./user-label.js"; import {Data, Feature} from "./enums.js"; +import {collectUsernameColors} from "./username-color.js"; +import {collectUserLabels} from "./user-label.js"; -export * from "./user-label.js"; export * from "./enums.js"; +export * from "./username-color.js"; +export * from "./user-label.js"; export type HideVotesData = { otherComments: boolean; @@ -13,14 +15,6 @@ export type HideVotesData = { ownTopics: boolean; }; -export type UsernameColor = { - color: string; - id: number; - username: string; -}; - -export type UsernameColorsData = UsernameColor[]; - export const storageValues = { [Data.EnabledFeatures]: createValue({ deserialize: (input) => new Set(JSON.parse(input) as Feature[]), @@ -63,13 +57,7 @@ export const storageValues = { storage: browser.storage.sync, }), [Feature.UserLabels]: collectUserLabels(), - [Feature.UsernameColors]: createValue({ - deserialize: (input) => JSON.parse(input) as UsernameColorsData, - serialize: (input) => JSON.stringify(Array.from(input)), - key: Feature.UsernameColors, - value: [], - storage: browser.storage.sync, - }), + [Feature.UsernameColors]: collectUsernameColors(), }; type StorageValues = typeof storageValues; diff --git a/source/storage/migrations/migrations.test.ts b/source/storage/migrations/migrations.test.ts index e2a5e02..b2a2a4d 100644 --- a/source/storage/migrations/migrations.test.ts +++ b/source/storage/migrations/migrations.test.ts @@ -38,10 +38,15 @@ await setup("Migrations", async (group) => { break; } - case Feature.UsernameColors: { + case `${Feature.UsernameColors}-4`: { + test.equals(value, '{"color":"red","id":4,"username":"Test"}'); + break; + } + + case `${Feature.UsernameColors}-18`: { test.equals( value, - '[{"color":"red","id":4,"username":"Test"},{"color":"green","id":18,"username":"AnotherTest"}]', + '{"color":"green","id":18,"username":"AnotherTest"}', ); break; } diff --git a/source/storage/migrations/migrations.ts b/source/storage/migrations/migrations.ts index 6c20065..4fbcdda 100644 --- a/source/storage/migrations/migrations.ts +++ b/source/storage/migrations/migrations.ts @@ -2,11 +2,13 @@ import {type Migration} from "@holllo/migration-helper"; import { Data, Feature, + createValueUsernamecolor, createValueUserLabel, fromStorage, + saveUsernameColors, saveUserLabels, } from "../exports.js"; -import {v112DeserializeData, v112Sample, type V112Settings} from "./v1-1-2.js"; +import {v112DeserializeData, type V112Settings} from "./v1-1-2.js"; export const migrations: Array> = [ { @@ -16,11 +18,18 @@ export const migrations: Array> = [ data.data.userLabels = deserialized.userLabels; data.data.usernameColors = deserialized.usernameColors; + const usernameColors = []; const userLabels = []; + + for (const usernameColor of data.data.usernameColors) { + usernameColors.push(await createValueUsernamecolor(usernameColor)); + } + for (const userLabel of data.data.userLabels) { userLabels.push(await createValueUserLabel(userLabel)); } + await saveUsernameColors(usernameColors); await saveUserLabels(userLabels); const hideVotes = await fromStorage(Feature.HideVotes); @@ -40,10 +49,6 @@ export const migrations: Array> = [ version.value = "2.0.0"; await version.save(); - const usernameColors = await fromStorage(Feature.UsernameColors); - usernameColors.value = data.data.usernameColors; - await usernameColors.save(); - const enabledFeatures = await fromStorage(Data.EnabledFeatures); for (const [key, value] of Object.entries(data.features)) { if (value) { diff --git a/source/storage/username-color.ts b/source/storage/username-color.ts new file mode 100644 index 0000000..77b4742 --- /dev/null +++ b/source/storage/username-color.ts @@ -0,0 +1,58 @@ +import {type Value, createValue} from "@holllo/webextension-storage"; +import browser from "webextension-polyfill"; +import {Feature} from "./enums.js"; + +export type UsernameColor = { + color: string; + id: number; + username: string; +}; + +export type UsernameColorsData = Array>; + +/** + * Create a {@link Value}-wrapped {@link UsernameColor}. + */ +export async function createValueUsernamecolor( + usernameColor: UsernameColor, +): Promise { + return createValue({ + deserialize: (input) => JSON.parse(input) as UsernameColor, + serialize: (input) => JSON.stringify(input), + key: `${Feature.UsernameColors}-${usernameColor.id}`, + value: usernameColor, + storage: browser.storage.sync, + }); +} + +/** + * Get all username colors from storage and combine them into a single array. + */ +export async function collectUsernameColors(): Promise { + const storage = await browser.storage.sync.get(); + const userLabels = []; + for (const [key, value] of Object.entries(storage)) { + if (!key.startsWith(Feature.UsernameColors)) { + continue; + } + + userLabels.push( + await createValueUsernamecolor( + JSON.parse(value as string) as UsernameColor, + ), + ); + } + + return userLabels; +} + +/** + * Save all username colors to storage under individual keys. + */ +export async function saveUsernameColors( + usernameColors: UsernameColorsData, +): Promise { + for (const usernameColor of usernameColors) { + await usernameColor.save(); + } +}