mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-02-24 15:35:11 +00:00
use components from vc-timezones
This commit is contained in:
parent
925629f85d
commit
1b145a5695
7 changed files with 229 additions and 212 deletions
|
@ -1,5 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
|
import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
|
|
||||||
import settings from "./settings";
|
import settings from "./settings";
|
||||||
|
|
||||||
export type Snowflake = string;
|
export type Snowflake = string;
|
||||||
|
@ -22,10 +29,10 @@ export async function fetchTimezonesBulk(ids: Snowflake[]): Promise<Record<Snowf
|
||||||
const json: BulkFetchResponse = await req.json();
|
const json: BulkFetchResponse = await req.json();
|
||||||
if ("error" in json) throw "API Error: " + json.error;
|
if ("error" in json) throw "API Error: " + json.error;
|
||||||
|
|
||||||
let parsed: Record<Snowflake, string | null> = {};
|
const parsed: Record<Snowflake, string | null> = {};
|
||||||
|
|
||||||
for (const userId of Object.keys(json)) {
|
for (const userId of Object.keys(json)) {
|
||||||
parsed[userId] = json[userId].timezoneId;
|
parsed[userId] = json[userId]?.timezoneId ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsed;
|
return parsed;
|
||||||
|
@ -48,7 +55,7 @@ export async function fetchTimezone(userId: Snowflake): Promise<string | null |
|
||||||
const json: UserFetchResponse = await req.json();
|
const json: UserFetchResponse = await req.json();
|
||||||
|
|
||||||
if ("error" in json) {
|
if ("error" in json) {
|
||||||
if (json.error == "not_found") return null;
|
if (json.error === "not_found") return null;
|
||||||
|
|
||||||
throw "API Error: " + json.error;
|
throw "API Error: " + json.error;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
import * as DataStore from "@api/DataStore";
|
import * as DataStore from "@api/DataStore";
|
||||||
import { createStore } from "@api/DataStore";
|
import { createStore } from "@api/DataStore";
|
||||||
import { fetchTimezone, fetchTimezonesBulk, Snowflake } from "./api";
|
|
||||||
import settings, { TimezoneOverwrites } from "./settings";
|
|
||||||
import { debounce } from "@shared/debounce";
|
import { debounce } from "@shared/debounce";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
|
|
||||||
|
import { fetchTimezone, fetchTimezonesBulk, Snowflake } from "./api";
|
||||||
|
import settings, { TimezoneOverwrites } from "./settings";
|
||||||
|
|
||||||
// TODO: cache invalidation
|
// TODO: cache invalidation
|
||||||
const TimezoneCache = createStore("UsersTimezoneCache", "TimezoneCache");
|
const TimezoneCache = createStore("UsersTimezoneCache", "TimezoneCache");
|
||||||
|
|
||||||
|
@ -16,14 +23,14 @@ let BulkFetchQueue: BulkFetchCallbacks = {};
|
||||||
// Executes all queued requests and calls their callbacks
|
// Executes all queued requests and calls their callbacks
|
||||||
const debounceProcessBulkQueue = debounce(processBulkQueue, 750);
|
const debounceProcessBulkQueue = debounce(processBulkQueue, 750);
|
||||||
|
|
||||||
async function processBulkQueue(attempt: number = 1, existingCallbacks?: BulkFetchCallbacks) {
|
async function processBulkQueue(attempt: number = 1, retryQueue?: BulkFetchCallbacks) {
|
||||||
if (attempt > 3) {
|
if (attempt > 3) {
|
||||||
new Logger("Timezones").warn("Bulk queue fetch ran out of retries!");
|
new Logger("Timezones").warn("Bulk queue fetch ran out of retries!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const callbacks = existingCallbacks ?? BulkFetchQueue;
|
const callbacks = retryQueue ?? BulkFetchQueue;
|
||||||
if (!existingCallbacks) BulkFetchQueue = {};
|
if (!retryQueue) BulkFetchQueue = {};
|
||||||
|
|
||||||
const timezones = await fetchTimezonesBulk(Object.keys(callbacks));
|
const timezones = await fetchTimezonesBulk(Object.keys(callbacks));
|
||||||
if (!timezones) {
|
if (!timezones) {
|
||||||
|
@ -47,9 +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 = settings.store.timezoneOverwrites ?? {} as TimezoneOverwrites;
|
||||||
|
const useApi = settings.store.enableApi;
|
||||||
const overwrite = overwrites[userId];
|
const overwrite = overwrites[userId];
|
||||||
if (overwrite) return overwrite ?? null;
|
if (overwrite || !useApi) return overwrite ?? null;
|
||||||
|
|
||||||
if (!force) {
|
if (!force) {
|
||||||
const cachedTimezone = await DataStore.get<string | null>(userId, TimezoneCache);
|
const cachedTimezone = await DataStore.get<string | null>(userId, TimezoneCache);
|
||||||
|
@ -61,9 +69,10 @@ export async function getUserTimezone(
|
||||||
if (immediate) {
|
if (immediate) {
|
||||||
let tries = 3;
|
let tries = 3;
|
||||||
while (tries-- > 0) {
|
while (tries-- > 0) {
|
||||||
let timezone = await fetchTimezone(userId);
|
const timezone = await fetchTimezone(userId);
|
||||||
if (timezone === undefined) continue;
|
if (timezone === undefined) continue;
|
||||||
|
|
||||||
|
DataStore.set(userId, timezone, TimezoneCache).catch(_ => _);
|
||||||
return timezone;
|
return timezone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
88
src/plugins/timezones/components.tsx
Normal file
88
src/plugins/timezones/components.tsx
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
|
import { ErrorBoundary } from "@components/index";
|
||||||
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
import { React, Tooltip, useEffect, useState } from "@webpack/common";
|
||||||
|
|
||||||
|
import { Snowflake } from "./api";
|
||||||
|
import { getUserTimezone } from "./cache";
|
||||||
|
import { formatTimestamp } from "./utils";
|
||||||
|
|
||||||
|
// Based on Syncxv's vc-timezones user plugin //
|
||||||
|
|
||||||
|
const messageClasses = findByPropsLazy("timestamp", "compact", "contentOnly");
|
||||||
|
|
||||||
|
interface LocalTimestampProps {
|
||||||
|
userId: Snowflake;
|
||||||
|
timestamp?: Date;
|
||||||
|
type: "message" | "profile";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LocalTimestamp(props: LocalTimestampProps): JSX.Element {
|
||||||
|
return <ErrorBoundary noop={true} wrappedProps={props}>
|
||||||
|
<LocalTimestampInner {...props} />
|
||||||
|
</ErrorBoundary>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function LocalTimestampInner(props: LocalTimestampProps): JSX.Element | null {
|
||||||
|
const [timezone, setTimezone] = useState<string | null>();
|
||||||
|
const [timestamp, setTimestamp] = useState(props.timestamp ?? Date.now());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!timezone) {
|
||||||
|
getUserTimezone(props.userId, props.type === "profile").then(setTimezone);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let timer: NodeJS.Timeout;
|
||||||
|
|
||||||
|
if (props.type === "profile") {
|
||||||
|
setTimestamp(Date.now());
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const delay = (60 - now.getSeconds()) * 1000 + 1000 - now.getMilliseconds();
|
||||||
|
|
||||||
|
timer = setTimeout(() => setTimestamp(Date.now()), delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => timer && clearTimeout(timer);
|
||||||
|
}, [timezone, timestamp]);
|
||||||
|
|
||||||
|
if (!timezone) return null;
|
||||||
|
|
||||||
|
const longTime = formatTimestamp(timezone, timestamp, true);
|
||||||
|
const shortTime = formatTimestamp(timezone, timestamp, false);
|
||||||
|
const shortTimeFormatted = props.type === "message"
|
||||||
|
? `• ${shortTime}`
|
||||||
|
: shortTime;
|
||||||
|
|
||||||
|
const classes = props.type === "message"
|
||||||
|
? `timezone-message-item ${messageClasses.timestamp}`
|
||||||
|
: "timezone-profile-item";
|
||||||
|
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<Tooltip
|
||||||
|
position="top"
|
||||||
|
// @ts-ignore
|
||||||
|
delay={750}
|
||||||
|
allowOverflow={false}
|
||||||
|
spacing={8}
|
||||||
|
hideOnClick={true}
|
||||||
|
tooltipClassName="timezone-tooltip"
|
||||||
|
text={longTime}
|
||||||
|
>
|
||||||
|
{toolTipProps => <>
|
||||||
|
<span className={classes} {...toolTipProps}>
|
||||||
|
{shortTimeFormatted}
|
||||||
|
</span>
|
||||||
|
</>}
|
||||||
|
</Tooltip>
|
||||||
|
</>;
|
||||||
|
}
|
|
@ -6,22 +6,15 @@
|
||||||
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
|
||||||
import { SearchableSelect, Text, Toasts, useEffect, UserStore, useState } from "@webpack/common";
|
|
||||||
import { Message, User } from "discord-types/general";
|
import { Message, User } from "discord-types/general";
|
||||||
|
|
||||||
|
import { LocalTimestamp } from "./components";
|
||||||
import settings, { SettingsComponent } from "./settings";
|
import settings, { SettingsComponent } from "./settings";
|
||||||
import { CogWheel, DeleteIcon } from "@components/Icons";
|
|
||||||
import { classes } from "@utils/misc";
|
|
||||||
import { useForceUpdater } from "@utils/react";
|
|
||||||
|
|
||||||
const classNames = findByPropsLazy("customStatusSection");
|
|
||||||
const styles = findByPropsLazy("timestampInline");
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "Timezones",
|
name: "Timezones",
|
||||||
description: "Set and display the local times of you and other users via TimezoneDB",
|
description: "Set and display the local times of you and other users via TimezoneDB",
|
||||||
authors: [Devs.rushii, Devs.mantikafasi, Devs.Aria, Devs.Arjix],
|
authors: [Devs.rushii, Devs.Aria, Devs.mantikafasi, Devs.Arjix],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
settingsAboutComponent: SettingsComponent,
|
settingsAboutComponent: SettingsComponent,
|
||||||
|
@ -44,197 +37,50 @@ export default definePlugin({
|
||||||
// replace: "return [$1, $self.getProfileTimezonesComponent(arguments[0])] }",
|
// replace: "return [$1, $self.getProfileTimezonesComponent(arguments[0])] }",
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
{
|
// {
|
||||||
// TODO: fix this
|
// // TODO: fix this
|
||||||
// thank you https://github.com/Syncxv/vc-timezones/blob/master/index.tsx for saving me from painful work
|
// // thank you https://github.com/Syncxv/vc-timezones/blob/master/index.tsx for saving me from painful work
|
||||||
find: ".badgesContainer,{",
|
// find: ".badgesContainer,{",
|
||||||
|
// replacement: {
|
||||||
|
// match: /id:\(0,\i\.getMessageTimestampId\)\(\i\),timestamp.{1,50}}\),/,
|
||||||
|
// replace: "$&,$self.getTimezonesComponent(arguments[0]),",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
|
||||||
|
// Based on Syncxv's vc-timezones user plugin //
|
||||||
|
...[".NITRO_BANNER,", "=!1,canUsePremiumCustomization:"].map(find => ({
|
||||||
|
find,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /id:\(0,\i\.getMessageTimestampId\)\(\i\),timestamp.{1,50}}\),/,
|
match: /(?<=hasProfileEffect.+?)children:\[/,
|
||||||
replace: "$&,$self.getTimezonesComponent(arguments[0]),",
|
replace: "$&$self.renderProfileTimezone(arguments[0]),",
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
find: "\"Message Username\"",
|
||||||
|
replacement: {
|
||||||
|
// thanks https://github.com/Syncxv/vc-timezones/pull/4
|
||||||
|
match: /(?<=isVisibleOnlyOnHover.+?)id:.{1,11},timestamp.{1,50}}\),/,
|
||||||
|
replace: "$&,$self.renderMessageTimezone(arguments[0]),",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
// TODO: make this not ugly (port vc-timezones plugin)
|
renderProfileTimezone: (props?: { user?: User; }) => {
|
||||||
getProfileTimezonesComponent: ({ user }: { user: User; }) => {
|
if (!settings.store.displayInProfile || !props?.user?.id) return null;
|
||||||
const { displayInProfile } = settings.use(["displayInProfile"]);
|
|
||||||
|
|
||||||
const [timezone, setTimezone] = useState<string | undefined>();
|
return <LocalTimestamp
|
||||||
const [isInEditMode, setIsInEditMode] = useState(false);
|
userId={props.user.id}
|
||||||
const [timezones, setTimezones] = useState<string[]>([]);
|
type="profile"
|
||||||
|
/>;
|
||||||
const forceUpdate = useForceUpdater();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
useTimezones().then(setTimezones);
|
|
||||||
getUserTimezone(user.id, preference).then(tz => setTimezone(tz));
|
|
||||||
|
|
||||||
// Rerender every 10 seconds to stay in sync.
|
|
||||||
const interval = setInterval(forceUpdate, 10 * 1000);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, [preference]);
|
|
||||||
|
|
||||||
if (!showInProfile)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Text variant="text-sm/normal" className={classNames.customStatusSection}
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
...(isInEditMode ? {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
} : {}),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{!isInEditMode &&
|
|
||||||
<span
|
|
||||||
style={{ fontSize: "1.2em", cursor: (timezone ? "pointer" : "") }}
|
|
||||||
onClick={() => {
|
|
||||||
if (timezone) {
|
|
||||||
Toasts.show({
|
|
||||||
type: Toasts.Type.MESSAGE,
|
|
||||||
message: timezone,
|
|
||||||
id: Toasts.genId(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(timezone) ? getTimeString(timezone) : "No timezone set"}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
{isInEditMode && (
|
|
||||||
<span style={{ width: "90%" }}>
|
|
||||||
<SearchableSelect
|
|
||||||
placeholder="Pick a timezone"
|
|
||||||
options={timezones.map(tz => ({ label: tz, value: tz }))}
|
|
||||||
value={timezone ? { label: timezone, value: timezone } : undefined}
|
|
||||||
onChange={value => {
|
|
||||||
setTimezone(value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<span style={
|
|
||||||
isInEditMode ? {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-around",
|
|
||||||
width: "60%",
|
|
||||||
marginTop: "5%",
|
|
||||||
} : {
|
|
||||||
marginLeft: "2%",
|
|
||||||
display: "flex",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CogWheel
|
|
||||||
style={{ cursor: "pointer", padding: "2px", border: "2px solid grey", borderRadius: "50px" }}
|
|
||||||
onClick={() => {
|
|
||||||
if (!isInEditMode) {
|
|
||||||
setIsInEditMode(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!timezone) {
|
|
||||||
setIsInEditMode(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DataStore.update(DATASTORE_KEY, (oldValue: TimezoneDB | undefined) => {
|
|
||||||
oldValue = oldValue || {};
|
|
||||||
oldValue[user.id] = timezone;
|
|
||||||
return oldValue;
|
|
||||||
}).then(() => {
|
|
||||||
Toasts.show({
|
|
||||||
type: Toasts.Type.SUCCESS,
|
|
||||||
message: "Timezone set!",
|
|
||||||
id: Toasts.genId(),
|
|
||||||
});
|
|
||||||
|
|
||||||
setIsInEditMode(false);
|
|
||||||
}).catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
Toasts.show({
|
|
||||||
type: Toasts.Type.FAILURE,
|
|
||||||
message: "Something went wrong, please try again later.",
|
|
||||||
id: Toasts.genId(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
color="var(--primary-330)"
|
|
||||||
height="16"
|
|
||||||
width="16"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{isInEditMode &&
|
|
||||||
<DeleteIcon
|
|
||||||
style={{
|
|
||||||
cursor: "pointer",
|
|
||||||
padding: "2px",
|
|
||||||
border: "2px solid grey",
|
|
||||||
borderRadius: "50px",
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
DataStore.update(DATASTORE_KEY, (oldValue: TimezoneDB | undefined) => {
|
|
||||||
oldValue = oldValue || {};
|
|
||||||
delete oldValue[user.id];
|
|
||||||
return oldValue;
|
|
||||||
}).then(async () => {
|
|
||||||
Toasts.show({
|
|
||||||
type: Toasts.Type.SUCCESS,
|
|
||||||
message: "Timezone removed!",
|
|
||||||
id: Toasts.genId(),
|
|
||||||
});
|
|
||||||
setIsInEditMode(false);
|
|
||||||
setTimezone(await getUserTimezone(user.id, preference));
|
|
||||||
}).catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
Toasts.show({
|
|
||||||
type: Toasts.Type.FAILURE,
|
|
||||||
message: "Something went wrong, please try again later.",
|
|
||||||
id: Toasts.genId(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
color="var(--red-360)"
|
|
||||||
height="16"
|
|
||||||
width="16"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getTimezonesComponent: ({ message }: { message: Message; }) => {
|
renderMessageTimezone: (props?: { message?: Message; }) => {
|
||||||
console.log(message);
|
if (!settings.store.displayInChat || !props?.message) return null;
|
||||||
|
|
||||||
const { showInChat, preference } = settings.use(["preference", "showInChat"]);
|
return <LocalTimestamp
|
||||||
const [timeString, setTimeString] = useState<string>();
|
userId={props.message.author.id}
|
||||||
|
timestamp={props.message.timestamp as unknown as Date}
|
||||||
if (!showInChat || message.author.id === UserStore.getCurrentUser()?.id)
|
type="message"
|
||||||
return null;
|
/>;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!showInChat) return;
|
|
||||||
|
|
||||||
(async function() {
|
|
||||||
const timezone = await getUserTimezone(message.author.id, preference);
|
|
||||||
const timestamp = (message.timestamp as unknown) as Date; // discord-types is outdated
|
|
||||||
setTimeString(timezone && "• " + getTimeString(timezone, timestamp));
|
|
||||||
})();
|
|
||||||
}, [showInChat, preference]);
|
|
||||||
|
|
||||||
return <>
|
|
||||||
<span className={classes(styles.timestampInline, styles.timestamp)}>
|
|
||||||
{timeString}
|
|
||||||
</span>
|
|
||||||
</>;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,9 +17,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { OptionType } from "@utils/types";
|
|
||||||
import { Text } from "@webpack/common";
|
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
|
import { IPluginOptionComponentProps, OptionType } from "@utils/types";
|
||||||
|
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>;
|
||||||
|
@ -48,8 +49,13 @@ const settings = definePluginSettings({
|
||||||
timezoneOverwrites: {
|
timezoneOverwrites: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
description: "Local overwrites for users' timezones",
|
description: "Local overwrites for users' timezones",
|
||||||
component: () => <></> // TODO: settings component to manage local overwrites,
|
component: props => <>
|
||||||
}
|
<TimezoneOverwritesSetting
|
||||||
|
setValue={props.setValue}
|
||||||
|
setError={props.setError}
|
||||||
|
option={props.option} />
|
||||||
|
</>,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default settings;
|
export default settings;
|
||||||
|
@ -69,3 +75,7 @@ export function SettingsComponent(): JSX.Element {
|
||||||
</Text>
|
</Text>
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TimezoneOverwritesSetting(props: IPluginOptionComponentProps): JSX.Element {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
43
src/plugins/timezones/styles.css
Normal file
43
src/plugins/timezones/styles.css
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/** Based on Syncxv's vc-timezones user plugin **/
|
||||||
|
|
||||||
|
.timezone-profile-item {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: 28px 16px 4px;
|
||||||
|
background: var(--profile-body-background-color, var(--background-primary));
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="topSection"] .timezone-profile-item {
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timezone-message-item {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-timezone-modal-header {
|
||||||
|
justify-content: space-between;
|
||||||
|
align-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-timezone-modal-header h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-timezone-modal-content {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-timezone-modal-footer {
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timezone-tooltip {
|
||||||
|
max-width: none !important;
|
||||||
|
white-space: nowrap
|
||||||
|
}
|
|
@ -4,19 +4,33 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { findStoreLazy } from "@webpack";
|
|
||||||
import { Logger } from "@utils/Logger";
|
|
||||||
import { makeLazy } from "@utils/lazy";
|
import { makeLazy } from "@utils/lazy";
|
||||||
|
import { Logger } from "@utils/Logger";
|
||||||
|
import { findStoreLazy } from "@webpack";
|
||||||
|
|
||||||
const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore");
|
const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore");
|
||||||
const TIMEZONE_LIST = "https://gist.githubusercontent.com/ArjixWasTaken/e321f856f98676505efb90aad82feff1/raw/91034ee32eff93a7cb62d10702f6b1d01e0309e6/timezones.json";
|
const TIMEZONE_LIST = "https://gist.githubusercontent.com/ArjixWasTaken/e321f856f98676505efb90aad82feff1/raw/91034ee32eff93a7cb62d10702f6b1d01e0309e6/timezones.json";
|
||||||
|
|
||||||
export function formatTimestamp(timezone: string, timestamp: Date = new Date()): string | undefined {
|
export function formatTimestamp(
|
||||||
|
timezone: string,
|
||||||
|
timestamp: number | Date | undefined,
|
||||||
|
long: boolean,
|
||||||
|
): string | undefined {
|
||||||
try {
|
try {
|
||||||
const locale = UserSettingsProtoStore.settings.localization.locale.value;
|
const locale = UserSettingsProtoStore.settings.localization.locale.value;
|
||||||
|
const options: Intl.DateTimeFormatOptions = !long
|
||||||
|
? { hour: "numeric", minute: "numeric" }
|
||||||
|
: {
|
||||||
|
weekday: "long",
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
};
|
||||||
|
|
||||||
const formatter = new Intl.DateTimeFormat(locale, {
|
const formatter = new Intl.DateTimeFormat(locale, {
|
||||||
hour: "numeric",
|
...options,
|
||||||
minute: "numeric",
|
|
||||||
timeZone: timezone,
|
timeZone: timezone,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue