Rework UserLabelsData to Array<Value<UserLabel>>.
This commit is contained in:
parent
15c5beaecd
commit
d7ddbe4620
|
@ -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 = {
|
||||
|
|
|
@ -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({
|
||||
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({
|
||||
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.`,
|
||||
|
|
|
@ -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({
|
||||
userLabels.push(
|
||||
await createValueUserLabel({
|
||||
color: "#ff00ff",
|
||||
id,
|
||||
priority: 0,
|
||||
text: "New Label",
|
||||
username: existingUserLabel?.username ?? newLabelUsername,
|
||||
});
|
||||
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[] = [];
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
Loading…
Reference in New Issue