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();
}),
...props.userLabels.map((value) => value.username),
...props.userLabels.map(({value}) => value.username),
].sort((a, b) => a.localeCompare(b));
this.state = {

View File

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

View File

@ -10,6 +10,7 @@ import {
type UserLabel,
fromStorage,
Feature,
createValueUserLabel,
saveUserLabels,
} from "../storage/common.js";
import "../scss/index.scss";
@ -42,28 +43,31 @@ class App extends Component<Props, State> {
};
}
addNewLabel = () => {
addNewLabel = async () => {
const {newLabelUsername, userLabels} = this.state;
if (!isValidTildesUsername(newLabelUsername)) {
return;
}
const existingUserLabel = userLabels.find(
({username}) => username.toLowerCase() === newLabelUsername.toLowerCase(),
({value: {username}}) =>
username.toLowerCase() === newLabelUsername.toLowerCase(),
);
let id = 1;
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({
color: "#ff00ff",
id,
priority: 0,
text: "New Label",
username: existingUserLabel?.username ?? newLabelUsername,
});
userLabels.push(
await createValueUserLabel({
color: "#ff00ff",
id,
priority: 0,
text: "New Label",
username: existingUserLabel?.value.username ?? newLabelUsername,
}),
);
this.setState({userLabels});
};
@ -73,7 +77,9 @@ class App extends Component<Props, State> {
};
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) {
log(`Tried to edit UserLabel with unknown ID: ${targetId}`);
return;
@ -82,9 +88,9 @@ class App extends Component<Props, State> {
const newValue = (event.target as HTMLInputElement).value;
// eslint-disable-next-line unicorn/prefer-ternary
if (key === "id" || key === "priority") {
this.state.userLabels[index][key] = Number(newValue);
this.state.userLabels[index].value[key] = Number(newValue);
} else {
this.state.userLabels[index][key] = newValue;
this.state.userLabels[index].value[key] = newValue;
}
this.setState({
@ -94,7 +100,9 @@ class App extends Component<Props, State> {
};
removeUserLabel = (targetId: number) => {
const userLabels = this.state.userLabels.filter(({id}) => id !== targetId);
const userLabels = this.state.userLabels.filter(
({value: {id}}) => id !== targetId,
);
this.setState({
hasUnsavedChanges: true,
userLabels,
@ -109,13 +117,14 @@ class App extends Component<Props, State> {
render() {
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[]>();
for (const label of userLabels) {
const group = labelGroups.get(label.username) ?? [];
group.push(label);
labelGroups.set(label.username, group);
const username = label.value.username.toLowerCase();
const group = labelGroups.get(username) ?? [];
group.push(label.value);
labelGroups.set(username, group);
}
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";
export enum Feature {
@ -36,7 +36,7 @@ export type UserLabel = {
username: string;
};
export type UserLabelsData = UserLabel[];
export type UserLabelsData = Array<Value<UserLabel>>;
export type UsernameColor = {
color: string;
@ -46,6 +46,21 @@ export type 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.
*/
@ -57,7 +72,9 @@ export async function collectUserLabels(): Promise<UserLabelsData> {
continue;
}
userLabels.push(JSON.parse(value as string) as UserLabel);
userLabels.push(
await createValueUserLabel(JSON.parse(value as string) as UserLabel),
);
}
return userLabels;
@ -74,19 +91,8 @@ export async function collectUserLabels(): Promise<UserLabelsData> {
export async function saveUserLabels(
userLabels: UserLabelsData,
): 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) {
await browser.storage.sync.set({
[`${Feature.UserLabels}-${label.id}`]: JSON.stringify(label),
});
await label.save();
}
}

View File

@ -1,7 +1,13 @@
import {setup} from "@holllo/test";
import {type Migration} from "@holllo/migration-helper";
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";
export const migrations: Array<Migration<string>> = [
@ -11,7 +17,13 @@ export const migrations: Array<Migration<string>> = [
const deserialized = v112DeserializeData(data);
data.data.userLabels = deserialized.userLabels;
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);
hideVotes.value = {