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();
|
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 = {
|
||||||
|
|
|
@ -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(
|
||||||
|
await createValueUserLabel({
|
||||||
color,
|
color,
|
||||||
id: newId,
|
id: newId,
|
||||||
priority,
|
priority,
|
||||||
text,
|
text,
|
||||||
username,
|
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(
|
||||||
|
await createValueUserLabel({
|
||||||
id,
|
id,
|
||||||
color,
|
color,
|
||||||
priority,
|
priority,
|
||||||
text,
|
text,
|
||||||
username,
|
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.`,
|
||||||
|
|
|
@ -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(
|
||||||
|
await createValueUserLabel({
|
||||||
color: "#ff00ff",
|
color: "#ff00ff",
|
||||||
id,
|
id,
|
||||||
priority: 0,
|
priority: 0,
|
||||||
text: "New Label",
|
text: "New Label",
|
||||||
username: existingUserLabel?.username ?? newLabelUsername,
|
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[] = [];
|
||||||
|
|
|
@ -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),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
Loading…
Reference in New Issue