1
Fork 0

Rework UserLabelsData to Array<Value<UserLabel>>.

This commit is contained in:
Bauke 2023-06-26 12:10:08 +02:00
parent 15c5beaecd
commit d7ddbe4620
Signed by: Bauke
GPG Key ID: C1C0F29952BCF558
5 changed files with 110 additions and 67 deletions

View File

@ -40,7 +40,7 @@ export class AutocompleteFeature extends Component<Props, State> {
return value.textContent!.replace(/^@/, "").toLowerCase(); return value.textContent!.replace(/^@/, "").toLowerCase();
}), }),
...props.userLabels.map((value) => value.username), ...props.userLabels.map(({value}) => value.username),
].sort((a, b) => a.localeCompare(b)); ].sort((a, b) => a.localeCompare(b));
this.state = { this.state = {

View File

@ -1,6 +1,10 @@
import debounce from "debounce"; import debounce from "debounce";
import {Component, render} from "preact"; import {Component, render} from "preact";
import {type UserLabelsData, saveUserLabels} from "../../storage/common.js"; import {
type UserLabelsData,
createValueUserLabel,
saveUserLabels,
} from "../../storage/common.js";
import { import {
createElementFromString, createElementFromString,
isColorBright, isColorBright,
@ -62,17 +66,17 @@ export class UserLabelsFeature extends Component<Props, State> {
const sortedLabels = userLabels.sort((a, b): number => { const sortedLabels = userLabels.sort((a, b): number => {
if (inTopicListing) { if (inTopicListing) {
// If we're in the topic listing sort with highest priority first. // If we're in the topic listing sort with highest priority first.
if (a.priority !== b.priority) { if (a.value.priority !== b.value.priority) {
return b.priority - a.priority; return b.value.priority - a.value.priority;
} }
} else if (a.priority !== b.priority) { } else if (a.value.priority !== b.value.priority) {
// If we're not in the topic listing, sort with lowest priority first. // If we're not in the topic listing, sort with lowest priority first.
// We will add elements backwards, so the first label will be // We will add elements backwards, so the first label will be
// behind all the other labels. // behind all the other labels.
return a.priority - b.priority; return a.value.priority - b.value.priority;
} }
return b.text.localeCompare(a.text); return b.value.text.localeCompare(a.value.text);
}); });
for (const element of elements) { for (const element of elements) {
@ -85,7 +89,7 @@ export class UserLabelsFeature extends Component<Props, State> {
} }
const userLabels = sortedLabels.filter( const userLabels = sortedLabels.filter(
(value) => ({value}) =>
value.username.toLowerCase() === username && value.username.toLowerCase() === username &&
(onlyID === undefined ? true : value.id === onlyID), (onlyID === undefined ? true : value.id === onlyID),
); );
@ -121,23 +125,26 @@ export class UserLabelsFeature extends Component<Props, State> {
} }
for (const userLabel of userLabels) { for (const userLabel of userLabels) {
const bright = isColorBright(userLabel.color.trim()) const bright = isColorBright(userLabel.value.color.trim())
? "trx-bright" ? "trx-bright"
: ""; : "";
const label = createElementFromString<HTMLSpanElement>(`<span const label = createElementFromString<HTMLSpanElement>(`<span
data-trx-label-id="${userLabel.id}" data-trx-label-id="${userLabel.value.id}"
class="trx-user-label ${bright}" class="trx-user-label ${bright}"
> >
${userLabel.text} ${userLabel.value.text}
</span>`); </span>`);
label.addEventListener("click", (event: MouseEvent) => { label.addEventListener("click", (event: MouseEvent) => {
this.editLabelHandler(event, userLabel.id); this.editLabelHandler(event, userLabel.value.id);
}); });
element.after(label); element.after(label);
label.setAttribute("style", `background-color: ${userLabel.color};`); label.setAttribute(
"style",
`background-color: ${userLabel.value.color};`,
);
// If we're in the topic listing, stop after adding 1 label. // If we're in the topic listing, stop after adding 1 label.
if (inTopicListing) { if (inTopicListing) {
@ -181,7 +188,7 @@ export class UserLabelsFeature extends Component<Props, State> {
if (this.state.target === target && !this.state.hidden) { if (this.state.target === target && !this.state.hidden) {
this.hide(); this.hide();
} else { } else {
const label = this.props.userLabels.find((value) => value.id === id); const label = this.props.userLabels.find(({value}) => value.id === id);
if (label === undefined) { if (label === undefined) {
log( log(
"User Labels: Tried to edit label with ID that could not be found.", "User Labels: Tried to edit label with ID that could not be found.",
@ -193,7 +200,11 @@ export class UserLabelsFeature extends Component<Props, State> {
this.setState({ this.setState({
hidden: false, hidden: false,
target, target,
...label, color: label.value.color,
id: label.value.id,
priority: label.value.priority,
text: label.value.text,
username: label.value.username,
}); });
} }
}; };
@ -233,28 +244,33 @@ export class UserLabelsFeature extends Component<Props, State> {
if (id === undefined) { if (id === undefined) {
let newId = 1; let newId = 1;
if (userLabels.length > 0) { if (userLabels.length > 0) {
newId = userLabels.sort((a, b) => b.id - a.id)[0].id + 1; newId =
userLabels.sort((a, b) => b.value.id - a.value.id)[0].value.id + 1;
} }
userLabels.push({ userLabels.push(
color, await createValueUserLabel({
id: newId, color,
priority, id: newId,
text, priority,
username, text,
}); username,
}),
);
this.addLabelsToUsernames(querySelectorAll(".link-user"), newId); this.addLabelsToUsernames(querySelectorAll(".link-user"), newId);
} else { } else {
const index = userLabels.findIndex((value) => value.id === id); const index = userLabels.findIndex(({value}) => value.id === id);
userLabels.splice(index, 1); userLabels.splice(index, 1);
userLabels.push({ userLabels.push(
id, await createValueUserLabel({
color, id,
priority, color,
text, priority,
username, text,
}); username,
}),
);
const elements = querySelectorAll(`[data-trx-label-id="${id}"]`); const elements = querySelectorAll(`[data-trx-label-id="${id}"]`);
const bright = isColorBright(color); const bright = isColorBright(color);
@ -283,7 +299,7 @@ export class UserLabelsFeature extends Component<Props, State> {
} }
const {userLabels} = this.props; const {userLabels} = this.props;
const index = userLabels.findIndex((value) => value.id === id); const index = userLabels.findIndex(({value}) => value.id === id);
if (index === undefined) { if (index === undefined) {
log( log(
`User Labels: Tried to remove label with ID ${id} that could not be found.`, `User Labels: Tried to remove label with ID ${id} that could not be found.`,

View File

@ -10,6 +10,7 @@ import {
type UserLabel, type UserLabel,
fromStorage, fromStorage,
Feature, Feature,
createValueUserLabel,
saveUserLabels, saveUserLabels,
} from "../storage/common.js"; } from "../storage/common.js";
import "../scss/index.scss"; import "../scss/index.scss";
@ -42,28 +43,31 @@ class App extends Component<Props, State> {
}; };
} }
addNewLabel = () => { addNewLabel = async () => {
const {newLabelUsername, userLabels} = this.state; const {newLabelUsername, userLabels} = this.state;
if (!isValidTildesUsername(newLabelUsername)) { if (!isValidTildesUsername(newLabelUsername)) {
return; return;
} }
const existingUserLabel = userLabels.find( const existingUserLabel = userLabels.find(
({username}) => username.toLowerCase() === newLabelUsername.toLowerCase(), ({value: {username}}) =>
username.toLowerCase() === newLabelUsername.toLowerCase(),
); );
let id = 1; let id = 1;
if (userLabels.length > 0) { if (userLabels.length > 0) {
id = userLabels.sort((a, b) => b.id - a.id)[0].id + 1; id = userLabels.sort((a, b) => b.value.id - a.value.id)[0].value.id + 1;
} }
userLabels.push({ userLabels.push(
color: "#ff00ff", await createValueUserLabel({
id, color: "#ff00ff",
priority: 0, id,
text: "New Label", priority: 0,
username: existingUserLabel?.username ?? newLabelUsername, text: "New Label",
}); username: existingUserLabel?.value.username ?? newLabelUsername,
}),
);
this.setState({userLabels}); this.setState({userLabels});
}; };
@ -73,7 +77,9 @@ class App extends Component<Props, State> {
}; };
editUserLabel = (event: Event, targetId: number, key: keyof UserLabel) => { editUserLabel = (event: Event, targetId: number, key: keyof UserLabel) => {
const index = this.state.userLabels.findIndex(({id}) => id === targetId); const index = this.state.userLabels.findIndex(
({value: {id}}) => id === targetId,
);
if (index === -1) { if (index === -1) {
log(`Tried to edit UserLabel with unknown ID: ${targetId}`); log(`Tried to edit UserLabel with unknown ID: ${targetId}`);
return; return;
@ -82,9 +88,9 @@ class App extends Component<Props, State> {
const newValue = (event.target as HTMLInputElement).value; const newValue = (event.target as HTMLInputElement).value;
// eslint-disable-next-line unicorn/prefer-ternary // eslint-disable-next-line unicorn/prefer-ternary
if (key === "id" || key === "priority") { if (key === "id" || key === "priority") {
this.state.userLabels[index][key] = Number(newValue); this.state.userLabels[index].value[key] = Number(newValue);
} else { } else {
this.state.userLabels[index][key] = newValue; this.state.userLabels[index].value[key] = newValue;
} }
this.setState({ this.setState({
@ -94,7 +100,9 @@ class App extends Component<Props, State> {
}; };
removeUserLabel = (targetId: number) => { removeUserLabel = (targetId: number) => {
const userLabels = this.state.userLabels.filter(({id}) => id !== targetId); const userLabels = this.state.userLabels.filter(
({value: {id}}) => id !== targetId,
);
this.setState({ this.setState({
hasUnsavedChanges: true, hasUnsavedChanges: true,
userLabels, userLabels,
@ -109,13 +117,14 @@ class App extends Component<Props, State> {
render() { render() {
const {hasUnsavedChanges, newLabelUsername, userLabels} = this.state; const {hasUnsavedChanges, newLabelUsername, userLabels} = this.state;
userLabels.sort((a, b) => a.username.localeCompare(b.username)); userLabels.sort((a, b) => a.value.username.localeCompare(b.value.username));
const labelGroups = new Map<string, UserLabel[]>(); const labelGroups = new Map<string, UserLabel[]>();
for (const label of userLabels) { for (const label of userLabels) {
const group = labelGroups.get(label.username) ?? []; const username = label.value.username.toLowerCase();
group.push(label); const group = labelGroups.get(username) ?? [];
labelGroups.set(label.username, group); group.push(label.value);
labelGroups.set(username, group);
} }
const labels: JSX.Element[] = []; const labels: JSX.Element[] = [];

View File

@ -1,4 +1,4 @@
import {createValue} from "@holllo/webextension-storage"; import {createValue, type Value} from "@holllo/webextension-storage";
import browser from "webextension-polyfill"; import browser from "webextension-polyfill";
export enum Feature { export enum Feature {
@ -36,7 +36,7 @@ export type UserLabel = {
username: string; username: string;
}; };
export type UserLabelsData = UserLabel[]; export type UserLabelsData = Array<Value<UserLabel>>;
export type UsernameColor = { export type UsernameColor = {
color: string; color: string;
@ -46,6 +46,21 @@ export type UsernameColor = {
export type UsernameColorsData = UsernameColor[]; export type UsernameColorsData = UsernameColor[];
/**
* Create a {@link Value}-wrapped {@link UserLabel}.
*/
export async function createValueUserLabel(
userLabel: UserLabel,
): Promise<UserLabelsData[number]> {
return createValue<UserLabel>({
deserialize: (input) => JSON.parse(input) as UserLabel,
serialize: (input) => JSON.stringify(input),
key: `${Feature.UserLabels}-${userLabel.id}`,
value: userLabel,
storage: browser.storage.sync,
});
}
/** /**
* Get all user labels from storage and combine them into a single array. * Get all user labels from storage and combine them into a single array.
*/ */
@ -57,7 +72,9 @@ export async function collectUserLabels(): Promise<UserLabelsData> {
continue; continue;
} }
userLabels.push(JSON.parse(value as string) as UserLabel); userLabels.push(
await createValueUserLabel(JSON.parse(value as string) as UserLabel),
);
} }
return userLabels; return userLabels;
@ -74,19 +91,8 @@ export async function collectUserLabels(): Promise<UserLabelsData> {
export async function saveUserLabels( export async function saveUserLabels(
userLabels: UserLabelsData, userLabels: UserLabelsData,
): Promise<void> { ): Promise<void> {
const storage = await browser.storage.sync.get();
for (const key of Object.keys(storage)) {
if (!key.startsWith(Feature.UserLabels)) {
continue;
}
await browser.storage.sync.remove(key);
}
for (const label of userLabels) { for (const label of userLabels) {
await browser.storage.sync.set({ await label.save();
[`${Feature.UserLabels}-${label.id}`]: JSON.stringify(label),
});
} }
} }

View File

@ -1,7 +1,13 @@
import {setup} from "@holllo/test"; import {setup} from "@holllo/test";
import {type Migration} from "@holllo/migration-helper"; import {type Migration} from "@holllo/migration-helper";
import browser from "webextension-polyfill"; import browser from "webextension-polyfill";
import {Data, Feature, fromStorage, saveUserLabels} from "../common.js"; import {
Data,
Feature,
createValueUserLabel,
fromStorage,
saveUserLabels,
} from "../common.js";
import {v112DeserializeData, v112Sample, type V112Settings} from "./v1-1-2.js"; import {v112DeserializeData, v112Sample, type V112Settings} from "./v1-1-2.js";
export const migrations: Array<Migration<string>> = [ export const migrations: Array<Migration<string>> = [
@ -11,7 +17,13 @@ export const migrations: Array<Migration<string>> = [
const deserialized = v112DeserializeData(data); const deserialized = v112DeserializeData(data);
data.data.userLabels = deserialized.userLabels; data.data.userLabels = deserialized.userLabels;
data.data.usernameColors = deserialized.usernameColors; data.data.usernameColors = deserialized.usernameColors;
await saveUserLabels(data.data.userLabels);
const userLabels = [];
for (const userLabel of data.data.userLabels) {
userLabels.push(await createValueUserLabel(userLabel));
}
await saveUserLabels(userLabels);
const hideVotes = await fromStorage(Feature.HideVotes); const hideVotes = await fromStorage(Feature.HideVotes);
hideVotes.value = { hideVotes.value = {