overwrite modal

This commit is contained in:
rushiiMachine 2024-06-24 22:22:31 -07:00
parent f7e07bb797
commit 18c0e1d57f
No known key found for this signature in database
GPG key ID: DCBE5952BB3B6420
5 changed files with 136 additions and 10 deletions

View file

@ -13,7 +13,7 @@ import { fetchTimezone, fetchTimezonesBulk, Snowflake } from "./api";
import settings, { TimezoneOverwrites } from "./settings"; import settings, { TimezoneOverwrites } from "./settings";
// TODO: cache invalidation // TODO: cache invalidation
const TimezoneCache = createStore("UsersTimezoneCache", "TimezoneCache"); const TimezoneCache = createStore("TimezoneCache", "TimezoneCache");
// A list of callbacks that will trigger on a completed debounced bulk fetch // A list of callbacks that will trigger on a completed debounced bulk fetch
type BulkFetchCallback = (timezone: string | null) => void; type BulkFetchCallback = (timezone: string | null) => void;
@ -54,10 +54,10 @@ export async function getUserTimezone(
immediate: boolean = false, immediate: boolean = false,
force: boolean = false, force: boolean = false,
): Promise<string | null> { ): Promise<string | null> {
const overwrites = settings.store.timezoneOverwrites ?? {} as TimezoneOverwrites; const overwrites: TimezoneOverwrites = settings.store.timezoneOverwrites ?? {};
const useApi = settings.store.enableApi;
const overwrite = overwrites[userId]; const overwrite = overwrites[userId];
if (overwrite || !useApi) return overwrite ?? null; if (overwrite !== undefined) return overwrite;
if (!settings.store.enableApi) return null;
if (!force) { if (!force) {
const cachedTimezone = await DataStore.get<string | null>(userId, TimezoneCache); const cachedTimezone = await DataStore.get<string | null>(userId, TimezoneCache);

View file

@ -6,13 +6,26 @@
import "./styles.css"; import "./styles.css";
import { ErrorBoundary } from "@components/index"; import { ErrorBoundary, Link } from "@components/index";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { React, Tooltip, useEffect, useState } from "@webpack/common"; import { Button, Forms, React, SearchableSelect, Text, Tooltip, useEffect, useState } from "@webpack/common";
import { Snowflake } from "./api"; import { Snowflake } from "./api";
import { getUserTimezone } from "./cache"; import { getUserTimezone } from "./cache";
import { formatTimestamp } from "./utils"; import { formatTimestamp, getTimezonesLazy } from "./utils";
import {
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalProps,
ModalRoot,
openModal,
} from "@utils/modal";
import { Margins } from "@utils/margins";
import { SelectOption } from "@webpack/types";
import settings, { TimezoneOverwrites } from "./settings";
import { classes } from "@utils/misc";
// Based on Syncxv's vc-timezones user plugin // // Based on Syncxv's vc-timezones user plugin //
@ -82,10 +95,116 @@ function LocalTimestampInner(props: LocalTimestampProps): JSX.Element | null {
text={longTime} text={longTime}
> >
{toolTipProps => <> {toolTipProps => <>
<span className={classes} {...toolTipProps}> <span {...toolTipProps}
className={classes}
onClick={() => {
toolTipProps.onClick();
openModal(modalProps =>
<TimezoneOverrideModal
userId={props.userId}
modalProps={modalProps} />,
);
}}
>
{shortTimeFormatted} {shortTimeFormatted}
</span> </span>
</>} </>}
</Tooltip> </Tooltip>
</>; </>;
} }
interface TimezoneOverrideModalProps {
userId: string,
modalProps: ModalProps,
}
export function TimezoneOverrideModal(props: TimezoneOverrideModalProps) {
const [availableTimezones, setAvailableTimezones] = useState<SelectOption[]>();
const [timezone, setTimezone] = useState<string | "NONE" | undefined>();
useEffect(() => {
getTimezonesLazy().then(timezones => {
const options: SelectOption[] = timezones.map(tz => {
const offset = new Intl.DateTimeFormat(undefined, { timeZone: tz, timeZoneName: "shortOffset" })
.formatToParts(Date.now())
.find(part => part.type === "timeZoneName")!.value;
return { label: `${tz} (${offset})`, value: tz };
});
options.unshift({
label: "None (Ignore TimezoneDB)",
value: "NONE", // I would use null but SearchableSelect is bugged in that null values get converted into undefined
});
options.unshift({
label: "Auto (Retrieved from TimezoneDB)",
value: undefined,
});
setAvailableTimezones(options);
});
const overwrites: TimezoneOverwrites = settings.store.timezoneOverwrites ?? {};
const overwrite = overwrites[props.userId];
setTimezone(overwrite === null ? "NONE" : overwrite);
}, []);
function saveOverwrite() {
if (availableTimezones === undefined) return;
const overwrites: TimezoneOverwrites = settings.store.timezoneOverwrites ?? {};
if (timezone === undefined) {
delete overwrites[props.userId];
} else if (timezone === "NONE") {
overwrites[props.userId] = null;
} else {
overwrites[props.userId] = timezone;
}
settings.store.timezoneOverwrites = overwrites;
props.modalProps.onClose();
}
return <ModalRoot {...props.modalProps}>
<ModalHeader className="vc-timezone-modal-header">
<Forms.FormTitle tag="h2">
Set Timezone Override for User
</Forms.FormTitle>
<ModalCloseButton onClick={props.modalProps.onClose} />
</ModalHeader>
<ModalContent className="vc-timezone-modal-content">
<Text variant="text-md/normal">
This override will only be visible locally.
<br />
To set your own Timezone for other users to see,
click <Link onClick={/* TODO */ _ => _}>here</Link> to
authorize TimezoneDB.
</Text>
<section className={classes(Margins.bottom16, Margins.top16)}>
<SearchableSelect
options={availableTimezones ?? []}
value={availableTimezones?.find(opt => opt.value === timezone)}
placeholder="Select a Timezone"
maxVisibleItems={7}
closeOnSelect={true}
onChange={setTimezone}
/>
</section>
</ModalContent>
<ModalFooter className="vc-timezone-modal-footer">
<Button
color={Button.Colors.BRAND}
disabled={availableTimezones === undefined}
onClick={saveOverwrite}>
Save
</Button>
<Button color={Button.Colors.RED} onClick={props.modalProps.onClose}>
Cancel
</Button>
</ModalFooter>
</ModalRoot>;
}

View file

@ -23,7 +23,7 @@ import { Text } from "@webpack/common";
import { Snowflake } from "./api"; import { Snowflake } from "./api";
export type TimezoneOverwrites = Record<Snowflake, string>; export type TimezoneOverwrites = Record<Snowflake, string | null>;
const settings = definePluginSettings({ const settings = definePluginSettings({
enableApi: { enableApi: {

View file

@ -25,6 +25,7 @@
.timezone-message-item { .timezone-message-item {
margin-left: 4px; margin-left: 4px;
cursor: pointer;
} }
.vc-timezone-modal-header { .vc-timezone-modal-header {

View file

@ -27,6 +27,7 @@ export function formatTimestamp(
day: "numeric", day: "numeric",
hour: "numeric", hour: "numeric",
minute: "numeric", minute: "numeric",
timeZoneName: "shortOffset",
}; };
const formatter = new Intl.DateTimeFormat(locale, { const formatter = new Intl.DateTimeFormat(locale, {
@ -50,7 +51,12 @@ async function getTimezones(): Promise<string[]> {
} }
} }
return await fetch(TIMEZONE_LIST).then(res => res.json()); try {
return await fetch(TIMEZONE_LIST).then(res => res.json());
} catch (e) {
new Logger("Timezones").error("Failed to fetch external timezones list", e);
return [];
}
} }
export const getTimezonesLazy = makeLazy(getTimezones, 2); export const getTimezonesLazy = makeLazy(getTimezones, 2);