import {Component, render, type JSX} from "preact"; import {type Value} from "@holllo/webextension-storage"; import { initializeGlobals, isValidTildesUsername, log, } from "../utilities/exports.js"; import { type UserLabelsData, type UserLabel, fromStorage, Feature, createValueUserLabel, saveUserLabels, newUserLabelId, } from "../storage/exports.js"; import "../scss/index.scss"; import "../scss/user-label-editor.scss"; window.addEventListener("load", async () => { initializeGlobals(); const userLabels = await fromStorage(Feature.UserLabels); render(, document.body); }); type Props = { userLabels: UserLabelsData; }; type State = { newLabelUsername: string; unsavedUserLabelIds: Set; userLabels: UserLabelsData; userLabelsToRemove: UserLabelsData; }; class App extends Component { constructor(props: Props) { super(props); this.state = { newLabelUsername: "", unsavedUserLabelIds: new Set(), userLabels: props.userLabels, userLabelsToRemove: [], }; } addNewLabel = async () => { const {newLabelUsername, unsavedUserLabelIds, userLabels} = this.state; if (!isValidTildesUsername(newLabelUsername)) { return; } const existingUserLabel = userLabels.find( ({value: {username}}) => username.toLowerCase() === newLabelUsername.toLowerCase(), ); const id = await newUserLabelId(); const userLabel = await createValueUserLabel({ color: "#ff00ff", id, priority: 0, text: "New Label", username: existingUserLabel?.value.username ?? newLabelUsername, }); await userLabel.save(); userLabels.push(userLabel); unsavedUserLabelIds.add(id); this.setState({unsavedUserLabelIds, userLabels}); }; onNewUsernameInput = (event: Event) => { const username = (event.target as HTMLInputElement).value; this.setState({newLabelUsername: username}); }; editUserLabel = (event: Event, targetId: number, key: keyof UserLabel) => { const {unsavedUserLabelIds} = this.state; const index = this.state.userLabels.findIndex( ({value: {id}}) => id === targetId, ); if (index === -1) { log(`Tried to edit UserLabel with unknown ID: ${targetId}`); return; } const newValue = (event.target as HTMLInputElement).value; // eslint-disable-next-line unicorn/prefer-ternary if (key === "id" || key === "priority") { this.state.userLabels[index].value[key] = Number(newValue); } else { this.state.userLabels[index].value[key] = newValue; } unsavedUserLabelIds.add(targetId); this.setState({ unsavedUserLabelIds, userLabels: this.state.userLabels, }); }; removeUserLabel = async (targetId: number) => { const {unsavedUserLabelIds, userLabels, userLabelsToRemove} = this.state; const index = userLabels.findIndex(({value}) => value.id === targetId); userLabelsToRemove.push(...userLabels.splice(index, 1)); unsavedUserLabelIds.add(targetId); this.setState({ unsavedUserLabelIds, userLabels, userLabelsToRemove, }); }; saveUserLabels = async () => { for (const userLabel of this.state.userLabelsToRemove) { await userLabel.remove(); } this.props.userLabels = this.state.userLabels; void saveUserLabels(this.props.userLabels); this.setState({unsavedUserLabelIds: new Set(), userLabelsToRemove: []}); }; render() { const {newLabelUsername, unsavedUserLabelIds, userLabels} = this.state; userLabels.sort((a, b) => a.value.username.localeCompare(b.value.username)); const labelGroups = new Map(); for (const label of userLabels) { const username = label.value.username.toLowerCase(); const group = labelGroups.get(username) ?? []; group.push(label.value); labelGroups.set(username, group); } const labels: JSX.Element[] = []; for (const [username, group] of labelGroups) { group.sort((a, b) => a.priority === b.priority ? a.text.localeCompare(b.text) : b.priority - a.priority, ); const labelPreviews: JSX.Element[] = group.map(({color, text}) => ( {text} )); group.sort((a, b) => a.id - b.id); const userLabels: JSX.Element[] = []; for (const [index, label] of group.entries()) { const textHandler = (event: Event) => { this.editUserLabel(event, label.id, "text"); }; const colorHandler = (event: Event) => { this.editUserLabel(event, label.id, "color"); }; const priorityHandler = (event: Event) => { this.editUserLabel(event, label.id, "priority"); }; const removeHandler = async () => { await this.removeUserLabel(label.id); }; const hasUnsavedChanges = unsavedUserLabelIds.has(label.id); userLabels.push(
  • {index === 0 ? : undefined}
    {index === 0 ? : undefined}
    {index === 0 ? : undefined}
    {index === 0 ? : undefined}
  • , ); } labels.push(

    {username} {labelPreviews}

      {userLabels}
    , ); } const anyUnsavedChanges = unsavedUserLabelIds.size > 0; return ( <>

    To add a new label, enter the username for who you'd like to add the label for, then press the Add New Label button.
    Changes are not automatically saved!
    If there are any unsaved changes an asterisk will appear in the Save All Changes button. To undo all unsaved changes simply refresh the page.

    {labels}
    ); } }