1
Fork 0
tildes-reextended/source/options/user-label-editor.tsx

270 lines
7.6 KiB
TypeScript

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(<App userLabels={userLabels} />, document.body);
});
type Props = {
userLabels: UserLabelsData;
};
type State = {
newLabelUsername: string;
unsavedUserLabelIds: Set<number>;
userLabels: UserLabelsData;
userLabelsToRemove: UserLabelsData;
};
class App extends Component<Props, State> {
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();
userLabels.push(
await createValueUserLabel({
color: "#ff00ff",
id,
priority: 0,
text: "New Label",
username: existingUserLabel?.value.username ?? newLabelUsername,
}),
);
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<string, UserLabel[]>();
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}) => (
<span style={{background: color}} class="label-preview">
{text}
</span>
));
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(
<li class={hasUnsavedChanges ? "unsaved-changes" : ""} key={label.id}>
<div>
{index === 0 ? <label>Text</label> : undefined}
<input
onInput={textHandler}
placeholder="Text"
value={label.text}
/>
</div>
<div>
{index === 0 ? <label>Color</label> : undefined}
<input
onInput={colorHandler}
placeholder="Color"
value={label.color}
/>
</div>
<div>
{index === 0 ? <label>Priority</label> : undefined}
<input
onInput={priorityHandler}
placeholder="Priority"
type="number"
value={label.priority}
/>
</div>
<div>
{index === 0 ? <label>Controls</label> : undefined}
<button class="button destructive" onClick={removeHandler}>
Remove
</button>
</div>
</li>,
);
}
labels.push(
<div class="group">
<h2>
{username} {labelPreviews}
</h2>
<ul>{userLabels}</ul>
</div>,
);
}
const anyUnsavedChanges = unsavedUserLabelIds.size > 0;
return (
<>
<header class="page-header">
<h1>
<img src="/tildes-reextended.png" />
User Label Editor
</h1>
</header>
<main class="page-main user-label-editor">
<p class="info">
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.
<br />
<b>Changes are not automatically saved!</b>
<br />
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.
</p>
<div class="main-controls">
<input
onInput={this.onNewUsernameInput}
placeholder="Username"
value={newLabelUsername}
/>
<button class="button" onClick={this.addNewLabel}>
Add New Label
</button>
<button
class={`save-button button ${
anyUnsavedChanges ? "unsaved-changes" : ""
}`}
onClick={this.saveUserLabels}
>
Save All Changes{anyUnsavedChanges ? "*" : ""}
</button>
</div>
<div class="groups">{labels}</div>
</main>
</>
);
}
}