1
Fork 0

Fix user labels storage issue.

This commit is contained in:
Bauke 2023-06-24 14:00:27 +02:00
parent e03fcec09c
commit 5fb151807e
Signed by: Bauke
GPG Key ID: C1C0F29952BCF558
6 changed files with 72 additions and 30 deletions

View File

@ -40,6 +40,9 @@
"xo": { "xo": {
"extends": "@bauke/eslint-config", "extends": "@bauke/eslint-config",
"prettier": true, "prettier": true,
"rules": {
"no-await-in-loop": "off"
},
"space": true "space": true
} }
} }

View File

@ -1,7 +1,6 @@
import debounce from "debounce"; import debounce from "debounce";
import {Component, render} from "preact"; import {Component, render} from "preact";
import {type Value} from "@holllo/webextension-storage"; import {type UserLabelsData, saveUserLabels} from "../../storage/common.js";
import {type UserLabelsData} from "../../storage/common.js";
import { import {
createElementFromString, createElementFromString,
isColorBright, isColorBright,
@ -13,7 +12,7 @@ import {
type Props = { type Props = {
anonymizeUsernamesEnabled: boolean; anonymizeUsernamesEnabled: boolean;
userLabels: Value<UserLabelsData>; userLabels: UserLabelsData;
}; };
type State = { type State = {
@ -70,7 +69,7 @@ export class UserLabelsFeature extends Component<Props, State> {
// Sort the labels by priority or alphabetically, so 2 labels with the same // Sort the labels by priority or alphabetically, so 2 labels with the same
// priority will be sorted alphabetically. // priority will be sorted alphabetically.
const sortedLabels = userLabels.value.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.priority !== b.priority) {
@ -97,7 +96,7 @@ export class UserLabelsFeature extends Component<Props, State> {
const userLabels = sortedLabels.filter( const userLabels = sortedLabels.filter(
(value) => (value) =>
value.username === username && value.username.toLowerCase() === username &&
(onlyID === undefined ? true : value.id === onlyID), (onlyID === undefined ? true : value.id === onlyID),
); );
@ -192,9 +191,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.value.find( const label = this.props.userLabels.find((value) => value.id === id);
(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.",
@ -252,11 +249,11 @@ export class UserLabelsFeature extends Component<Props, State> {
// If no ID is present then save a new label otherwise edit the existing one. // If no ID is present then save a new label otherwise edit the existing one.
if (id === undefined) { if (id === undefined) {
let newId = 1; let newId = 1;
if (userLabels.value.length > 0) { if (userLabels.length > 0) {
newId = userLabels.value.sort((a, b) => b.id - a.id)[0].id + 1; newId = userLabels.sort((a, b) => b.id - a.id)[0].id + 1;
} }
userLabels.value.push({ userLabels.push({
color, color,
id: newId, id: newId,
priority, priority,
@ -266,9 +263,9 @@ export class UserLabelsFeature extends Component<Props, State> {
this.addLabelsToUsernames(querySelectorAll(".link-user"), newId); this.addLabelsToUsernames(querySelectorAll(".link-user"), newId);
} else { } else {
const index = userLabels.value.findIndex((value) => value.id === id); const index = userLabels.findIndex((value) => value.id === id);
userLabels.value.splice(index, 1); userLabels.splice(index, 1);
userLabels.value.push({ userLabels.push({
id, id,
color, color,
priority, priority,
@ -289,7 +286,7 @@ export class UserLabelsFeature extends Component<Props, State> {
} }
} }
await userLabels.save(); await saveUserLabels(userLabels);
this.props.userLabels = userLabels; this.props.userLabels = userLabels;
this.hide(); this.hide();
}; };
@ -303,7 +300,7 @@ export class UserLabelsFeature extends Component<Props, State> {
} }
const {userLabels} = this.props; const {userLabels} = this.props;
const index = userLabels.value.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.`,
@ -316,8 +313,8 @@ export class UserLabelsFeature extends Component<Props, State> {
value.remove(); value.remove();
} }
userLabels.value.splice(index, 1); userLabels.splice(index, 1);
await userLabels.save(); await saveUserLabels(userLabels);
this.props.userLabels = userLabels; this.props.userLabels = userLabels;
this.hide(); this.hide();
}; };

View File

@ -102,7 +102,7 @@ async function initialize() {
<AutocompleteFeature <AutocompleteFeature
anonymizeUsernamesEnabled={anonymizeUsernamesEnabled} anonymizeUsernamesEnabled={anonymizeUsernamesEnabled}
knownGroups={knownGroups.value} knownGroups={knownGroups.value}
userLabels={userLabels.value} userLabels={userLabels}
/> />
); );
} }

View File

@ -10,6 +10,7 @@ import {
type UserLabel, type UserLabel,
fromStorage, fromStorage,
Feature, Feature,
saveUserLabels,
} from "../storage/common.js"; } from "../storage/common.js";
import "../scss/index.scss"; import "../scss/index.scss";
import "../scss/user-label-editor.scss"; import "../scss/user-label-editor.scss";
@ -21,7 +22,7 @@ window.addEventListener("load", async () => {
}); });
type Props = { type Props = {
userLabels: Value<UserLabelsData>; userLabels: UserLabelsData;
}; };
type State = { type State = {
@ -37,7 +38,7 @@ class App extends Component<Props, State> {
this.state = { this.state = {
hasUnsavedChanges: false, hasUnsavedChanges: false,
newLabelUsername: "", newLabelUsername: "",
userLabels: props.userLabels.value, userLabels: props.userLabels,
}; };
} }
@ -101,8 +102,8 @@ class App extends Component<Props, State> {
}; };
saveUserLabels = () => { saveUserLabels = () => {
this.props.userLabels.value = this.state.userLabels; this.props.userLabels = this.state.userLabels;
void this.props.userLabels.save(); void saveUserLabels(this.props.userLabels);
this.setState({hasUnsavedChanges: false}); this.setState({hasUnsavedChanges: false});
}; };

View File

@ -33,6 +33,7 @@
.group { .group {
border: 1px solid var(--blue); border: 1px solid var(--blue);
overflow: scroll;
padding: 16px; padding: 16px;
h2 { h2 {
@ -40,6 +41,7 @@
display: flex; display: flex;
gap: 8px; gap: 8px;
margin-bottom: 16px; margin-bottom: 16px;
overflow: scroll;
} }
input { input {

View File

@ -45,6 +45,50 @@ export type UsernameColor = {
export type UsernameColorsData = UsernameColor[]; export type UsernameColorsData = UsernameColor[];
/**
* Get all user labels from storage and combine them into a single array.
*/
export async function collectUserLabels(): Promise<UserLabelsData> {
const storage = await browser.storage.sync.get();
const userLabels = [];
for (const [key, value] of Object.entries(storage)) {
if (!key.startsWith(Feature.UserLabels)) {
continue;
}
userLabels.push(JSON.parse(value as string) as UserLabel);
}
return userLabels;
}
/**
* Save all user labels to storage under individual keys.
*
* They are stored under individual keys so that we don't run into storage quota
* limits. If it was stored under a single key we would only be able to fit
* around 80-100 labels before hitting the limit.
* @param userLabels The user labels array to save.
*/
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),
});
}
}
export const storageValues = { export const storageValues = {
[Data.EnabledFeatures]: createValue({ [Data.EnabledFeatures]: createValue({
deserialize: (input) => new Set(JSON.parse(input) as Feature[]), deserialize: (input) => new Set(JSON.parse(input) as Feature[]),
@ -79,13 +123,8 @@ export const storageValues = {
}, },
storage: browser.storage.sync, storage: browser.storage.sync,
}), }),
[Feature.UserLabels]: createValue({ // eslint-disable-next-line unicorn/prefer-top-level-await
deserialize: (input) => JSON.parse(input) as UserLabelsData, [Feature.UserLabels]: collectUserLabels(),
serialize: (input) => JSON.stringify(Array.from(input)),
key: Feature.UserLabels,
value: [],
storage: browser.storage.sync,
}),
[Feature.UsernameColors]: createValue({ [Feature.UsernameColors]: createValue({
deserialize: (input) => JSON.parse(input) as UsernameColorsData, deserialize: (input) => JSON.parse(input) as UsernameColorsData,
serialize: (input) => JSON.stringify(Array.from(input)), serialize: (input) => JSON.stringify(Array.from(input)),