1
Fork 0

Merge branch 'feat/25-random_username_colors' into 'main'

Add option to randomize username colors

https://gitlab.com/tildes-community/tildes-reextended/-/merge_requests/1
This commit is contained in:
Bauke 2023-07-09 15:15:11 +00:00
commit 144c9c667b
7 changed files with 85 additions and 14 deletions

View File

@ -1,18 +1,20 @@
import {log, querySelectorAll} from "../../utilities/exports.js";
import { log, querySelectorAll, getColorFromStringHash, isColorBright } from "../../utilities/exports.js";
import { type UsernameColorsData } from "../../storage/exports.js";
export function runUsernameColorsFeature(
export async function runUsernameColorsFeature(
data: UsernameColorsData,
anonymizeUsernamesEnabled: boolean,
randomizeUsernameColorsEnabled: boolean,
) {
const count = usernameColors(data, anonymizeUsernamesEnabled);
const count = await usernameColors(data, anonymizeUsernamesEnabled, randomizeUsernameColorsEnabled);
log(`Username Colors: Applied ${count} colors.`);
}
function usernameColors(
async function usernameColors(
data: UsernameColorsData,
anonymizeUsernamesEnabled: boolean,
): number {
randomizeUsernameColorsEnabled: boolean,
): Promise<number> {
const usernameColors = new Map<string, string>();
for (const {
value: { color, username: usernames },
@ -40,12 +42,18 @@ function usernameColors(
}
element.classList.add("trx-username-colors");
const color = usernameColors.get(target);
if (color === undefined) {
let color = usernameColors.get(target);
if (color) {
element.style.color = color;
} else if (randomizeUsernameColorsEnabled) {
const randomColor = await getColorFromStringHash(target);
const fontColor = isColorBright(randomColor) ? "#000" : "#FFF"
element.style.setProperty("--background-color", randomColor);
element.style.setProperty("--text-color", fontColor);
element.classList.add("trx-colored-username")
} else {
continue;
}
element.style.color = color;
count += 1;
}

View File

@ -95,7 +95,8 @@ async function initialize() {
if (enabledFeatures.value.has(Feature.UsernameColors)) {
observerFeatures.push(async () => {
const data = await fromStorage(Feature.UsernameColors);
runUsernameColorsFeature(data, anonymizeUsernamesEnabled);
const randomizeUsernameColors = await fromStorage(Data.RandomizeUsernameColors);
runUsernameColorsFeature(data, anonymizeUsernamesEnabled, randomizeUsernameColors.value);
});
}

View File

@ -4,15 +4,18 @@ import {
type UsernameColorsData,
type UsernameColor,
Feature,
Data,
createValueUsernamecolor,
fromStorage,
} from "../../storage/exports.js";
import {Setting, type SettingProps} from "./index.js";
import { Value } from "@holllo/webextension-storage";
type State = {
previewChecked: "off" | "foreground" | "background";
usernameColors: UsernameColorsData;
usernameColorsToRemove: UsernameColorsData;
randomizeChecked: Value<boolean>;
};
export class UsernameColorsSetting extends Component<SettingProps, State> {
@ -23,11 +26,12 @@ export class UsernameColorsSetting extends Component<SettingProps, State> {
previewChecked: "off",
usernameColors: undefined!,
usernameColorsToRemove: [],
randomizeChecked: undefined!,
};
}
async componentDidMount() {
this.setState({usernameColors: await fromStorage(Feature.UsernameColors)});
this.setState({usernameColors: await fromStorage(Feature.UsernameColors), randomizeChecked: await fromStorage(Data.RandomizeUsernameColors)});
}
addNewColor = async () => {
@ -100,6 +104,13 @@ export class UsernameColorsSetting extends Component<SettingProps, State> {
this.setState({previewChecked});
};
toggleRandomized = () => {
const randomizeChecked = this.state.randomizeChecked;
randomizeChecked.value = !randomizeChecked.value;
void randomizeChecked.save();
this.setState({randomizeChecked})
}
onInput = (event: Event, id: number, key: "color" | "username") => {
const colorIndex = this.state.usernameColors.findIndex(
({value}) => value.id === id,
@ -115,7 +126,7 @@ export class UsernameColorsSetting extends Component<SettingProps, State> {
};
render() {
const {previewChecked, usernameColors} = this.state;
const {previewChecked, usernameColors, randomizeChecked} = this.state;
if (usernameColors === undefined) {
return;
}
@ -163,6 +174,7 @@ export class UsernameColorsSetting extends Component<SettingProps, State> {
);
});
return (
<Setting {...this.props}>
<p class="info">
@ -170,6 +182,9 @@ export class UsernameColorsSetting extends Component<SettingProps, State> {
<br />
You can enter multiple usernames separated by a comma if you want them
to use the same color.
<br />
If randomize is selected then all usernames will be given a random background color.
This will not override colors you have manually assigned.
</p>
<div class="username-colors-controls">
@ -184,6 +199,19 @@ export class UsernameColorsSetting extends Component<SettingProps, State> {
<button class="button" onClick={this.saveChanges}>
Save Changes
</button>
<ul class="checkbox-list">
<li>
<label>
<input
type="checkbox"
checked={randomizeChecked.value}
onClick={this.toggleRandomized}
/>
Randomize Username Colors
</label>
</li>
</ul>
</div>
{editors}

View File

@ -2,3 +2,10 @@
/* stylelint-disable-next-line declaration-no-important */
display: none !important;
}
.trx-colored-username {
background-color: var(--background-color);
border-radius: 3px;
color: var(--text-color);
padding: 2px 3px;
}

View File

@ -22,5 +22,6 @@ export enum Data {
EnabledFeatures = "enabled-features",
KnownGroups = "known-groups",
LatestActiveFeatureTab = "latest-active-feature-tab",
RandomizeUsernameColors = "randomize-username-colors",
Version = "data-version",
}

View File

@ -58,6 +58,13 @@ export const storageValues = {
value: "2.0.0",
storage: browser.storage.sync,
}),
[Data.RandomizeUsernameColors]: createValue({
deserialize: (input) => JSON.parse(input) as boolean,
serialize: (input) => JSON.stringify(input),
key: Data.RandomizeUsernameColors,
value: false,
storage: browser.storage.sync,
}),
[Feature.HideTopics]: collectHideTopicsData(),
[Feature.HideVotes]: createValue({
deserialize: (input) => JSON.parse(input) as HideVotesData,

View File

@ -16,3 +16,22 @@ export function pluralize(
return plural ?? singular + "s";
}
/** Return a hash for a given username */
export async function hashString(str: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(str)
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashString = hashArray
.map((b) => b.toString())
.join("");
return hashString;
}
/** Return a color hex code based on hash of username string */
export async function getColorFromStringHash(username: string): Promise<string> {
const usernameHash = parseInt(await hashString(username));
const color = Math.abs(usernameHash % parseInt("0xFFFFFF")).toString(16)
return `#${color}`.padEnd(7, "0")
}