mirror of
https://github.com/Equicord/Equicord.git
synced 2025-01-18 13:23:28 -05:00
Add Timezones
This commit is contained in:
parent
74dcba6968
commit
f78c8cef26
6 changed files with 325 additions and 18 deletions
|
@ -23,7 +23,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
|
|||
- Request for plugins from Discord.
|
||||
|
||||
<details>
|
||||
<summary>Extra included plugins (122 additional plugins)</summary>
|
||||
<summary>Extra included plugins (123 additional plugins)</summary>
|
||||
|
||||
- AllCallTimers by MaxHerbold and D3SOX
|
||||
- AltKrispSwitch by newwares
|
||||
|
@ -129,6 +129,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
|
|||
- TeX by Kyuuhachi
|
||||
- TextToSpeech by Samwich
|
||||
- ThemeLibrary by Fafa
|
||||
- Timezones by Aria
|
||||
- Title by Kyuuhachi
|
||||
- TosuRPC by AutumnVN
|
||||
- Translate+ by Prince527 (Using Translate by Ven)
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import "./style.css";
|
||||
|
||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
@ -38,6 +38,10 @@ export default definePlugin({
|
|||
description: "Group your commonly visited channels in tabs, like a browser",
|
||||
authors: [Devs.TheSun, Devs.TheKodeToad, EquicordDevs.keifufu, Devs.Nickyux],
|
||||
dependencies: ["ContextMenuAPI"],
|
||||
contextMenus: {
|
||||
"channel-mention-context": contextMenuPatch,
|
||||
"channel-context": contextMenuPatch
|
||||
},
|
||||
patches: [
|
||||
// add the channel tab container at the top
|
||||
{
|
||||
|
@ -91,16 +95,6 @@ export default definePlugin({
|
|||
|
||||
settings,
|
||||
|
||||
start() {
|
||||
addContextMenuPatch("channel-mention-context", contextMenuPatch);
|
||||
addContextMenuPatch("channel-context", contextMenuPatch);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeContextMenuPatch("channel-mention-context", contextMenuPatch);
|
||||
removeContextMenuPatch("channel-context", contextMenuPatch);
|
||||
},
|
||||
|
||||
containerHeight: 0,
|
||||
|
||||
render({ currentChannel, children }: {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Menu } from "@webpack/common";
|
||||
|
@ -26,11 +26,9 @@ export default definePlugin({
|
|||
name: "EmojiDumper",
|
||||
description: "Context menu to dump and download a server's emojis.",
|
||||
authors: [EquicordDevs.Cortex, Devs.Samwich, EquicordDevs.Woosh],
|
||||
start() {
|
||||
addContextMenuPatch(["guild-context", "guild-header-popout"], Patch);
|
||||
},
|
||||
stop() {
|
||||
removeContextMenuPatch(["guild-context", "guild-header-popout"], Patch);
|
||||
contextMenus: {
|
||||
"guild-context": Patch,
|
||||
"guild-header-popout": Patch
|
||||
}
|
||||
});
|
||||
|
||||
|
|
84
src/equicordplugins/timezones/TimezoneModal.tsx
Normal file
84
src/equicordplugins/timezones/TimezoneModal.tsx
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
|
||||
import { Button, Forms, SearchableSelect, useMemo, useState } from "@webpack/common";
|
||||
|
||||
import { DATASTORE_KEY, timezones } from ".";
|
||||
|
||||
export async function setUserTimezone(userId: string, timezone: string | null) {
|
||||
timezones[userId] = timezone;
|
||||
await DataStore.set(DATASTORE_KEY, timezones);
|
||||
}
|
||||
|
||||
const cl = classNameFactory("vc-timezone-");
|
||||
|
||||
export function SetTimezoneModal({ userId, modalProps }: { userId: string, modalProps: ModalProps; }) {
|
||||
const [currentValue, setCurrentValue] = useState<string | null>(timezones[userId] ?? null);
|
||||
|
||||
const options = useMemo(() => {
|
||||
return Intl.supportedValuesOf("timeZone").map(timezone => {
|
||||
const offset = new Intl.DateTimeFormat(undefined, { timeZone: timezone, timeZoneName: "short" })
|
||||
.formatToParts(new Date())
|
||||
.find(part => part.type === "timeZoneName")!.value;
|
||||
|
||||
return { label: `${timezone} (${offset})`, value: timezone };
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ModalRoot {...modalProps}>
|
||||
<ModalHeader className={cl("modal-header")}>
|
||||
<Forms.FormTitle tag="h2">
|
||||
Timezones
|
||||
</Forms.FormTitle>
|
||||
<ModalCloseButton onClick={modalProps.onClose} />
|
||||
</ModalHeader>
|
||||
|
||||
<ModalContent className={cl("modal-content")}>
|
||||
<section className={Margins.bottom16}>
|
||||
<Forms.FormTitle tag="h3">
|
||||
Select Timezone
|
||||
</Forms.FormTitle>
|
||||
|
||||
<SearchableSelect
|
||||
options={options}
|
||||
value={options.find(o => o.value === currentValue)}
|
||||
placeholder={"Select a Timezone"}
|
||||
maxVisibleItems={5}
|
||||
closeOnSelect={true}
|
||||
onChange={v => setCurrentValue(v)}
|
||||
/>
|
||||
</section>
|
||||
</ModalContent>
|
||||
|
||||
<ModalFooter className={cl("modal-footer")}>
|
||||
<Button
|
||||
color={Button.Colors.RED}
|
||||
onClick={async () => {
|
||||
await setUserTimezone(userId, null);
|
||||
modalProps.onClose();
|
||||
}}
|
||||
>
|
||||
Delete Timezone
|
||||
</Button>
|
||||
<Button
|
||||
color={Button.Colors.BRAND}
|
||||
disabled={currentValue === null}
|
||||
onClick={async () => {
|
||||
await setUserTimezone(userId, currentValue!);
|
||||
modalProps.onClose();
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
189
src/equicordplugins/timezones/index.tsx
Normal file
189
src/equicordplugins/timezones/index.tsx
Normal file
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./styles.css";
|
||||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { i18n, Menu, Tooltip, useEffect, useState } from "@webpack/common";
|
||||
import { Message, User } from "discord-types/general";
|
||||
|
||||
import { SetTimezoneModal } from "./TimezoneModal";
|
||||
|
||||
export const DATASTORE_KEY = "vencord-timezones";
|
||||
|
||||
export let timezones: Record<string, string | null> = {};
|
||||
(async () => {
|
||||
timezones = await DataStore.get<Record<string, string>>(DATASTORE_KEY) || {};
|
||||
})();
|
||||
|
||||
const classes = findByPropsLazy("timestamp", "compact", "contentOnly");
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
"24h Time": {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show time in 24h format",
|
||||
default: false
|
||||
},
|
||||
|
||||
showMessageHeaderTime: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show time in message headers",
|
||||
default: true
|
||||
},
|
||||
|
||||
showProfileTime: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show time in profiles",
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
function getTime(timezone: string, timestamp: string | number, props: Intl.DateTimeFormatOptions = {}) {
|
||||
const date = new Date(timestamp);
|
||||
const formatter = new Intl.DateTimeFormat(i18n?.getLocale?.() ?? "en-US", {
|
||||
hour12: !settings.store["24h Time"],
|
||||
timeZone: timezone,
|
||||
...props
|
||||
});
|
||||
return formatter.format(date);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
userId: string;
|
||||
timestamp?: string;
|
||||
type: "message" | "profile";
|
||||
}
|
||||
const TimestampComponent = ErrorBoundary.wrap(({ userId, timestamp, type }: Props) => {
|
||||
const [currentTime, setCurrentTime] = useState(timestamp || Date.now());
|
||||
const timezone = timezones[userId];
|
||||
|
||||
useEffect(() => {
|
||||
let timer: NodeJS.Timeout;
|
||||
|
||||
if (type === "profile") {
|
||||
setCurrentTime(Date.now());
|
||||
|
||||
const now = new Date();
|
||||
const delay = (60 - now.getSeconds()) * 1000 + 1000 - now.getMilliseconds();
|
||||
|
||||
timer = setTimeout(() => {
|
||||
setCurrentTime(Date.now());
|
||||
}, delay);
|
||||
}
|
||||
|
||||
return () => timer && clearTimeout(timer);
|
||||
}, [type, currentTime]);
|
||||
|
||||
if (!timezone) return null;
|
||||
|
||||
const shortTime = getTime(timezone, currentTime, { hour: "numeric", minute: "numeric" });
|
||||
const longTime = getTime(timezone, currentTime, {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
});
|
||||
return (
|
||||
<Tooltip
|
||||
position="top"
|
||||
// @ts-ignore
|
||||
delay={750}
|
||||
allowOverflow={false}
|
||||
spacing={8}
|
||||
hideOnClick={true}
|
||||
tooltipClassName="timezone-tooltip"
|
||||
text={longTime}
|
||||
>
|
||||
{toolTipProps => {
|
||||
return (
|
||||
<span
|
||||
{...toolTipProps}
|
||||
className={type === "message" ? `timezone-message-item ${classes.timestamp}` : "timezone-profile-item"}
|
||||
>
|
||||
{
|
||||
type === "message" ? `(${shortTime})` : shortTime
|
||||
}
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
</Tooltip>
|
||||
);
|
||||
}, { noop: true });
|
||||
|
||||
const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: { user: User; }) => {
|
||||
if (user?.id == null) return;
|
||||
|
||||
const setTimezoneItem = (
|
||||
<Menu.MenuItem
|
||||
label="Set Timezone"
|
||||
id="set-timezone"
|
||||
action={() => openModal(modalProps => <SetTimezoneModal userId={user.id} modalProps={modalProps} />)}
|
||||
/>
|
||||
);
|
||||
|
||||
children.push(<Menu.MenuSeparator />, setTimezoneItem);
|
||||
|
||||
};
|
||||
|
||||
|
||||
export default definePlugin({
|
||||
name: "Timezone",
|
||||
authors: [Devs.Aria],
|
||||
description: "Shows the local time of users in profiles and message headers",
|
||||
contextMenus: {
|
||||
"user-context": userContextMenuPatch
|
||||
},
|
||||
|
||||
patches: [
|
||||
// stolen from ViewIcons
|
||||
...[".NITRO_BANNER,", "=!1,canUsePremiumCustomization:"].map(find => ({
|
||||
find,
|
||||
replacement: {
|
||||
match: /(?<=hasProfileEffect.+?)children:\[/,
|
||||
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]),"
|
||||
}
|
||||
}
|
||||
],
|
||||
settings,
|
||||
getTime,
|
||||
|
||||
|
||||
renderProfileTimezone: (props?: { user?: User; }) => {
|
||||
if (!settings.store.showProfileTime || !props?.user?.id) return null;
|
||||
|
||||
return <TimestampComponent
|
||||
userId={props.user.id}
|
||||
type="profile"
|
||||
/>;
|
||||
},
|
||||
|
||||
renderMessageTimezone: (props?: { message?: Message; }) => {
|
||||
if (!settings.store.showMessageHeaderTime || !props?.message) return null;
|
||||
|
||||
return <TimestampComponent
|
||||
userId={props.message.author.id}
|
||||
timestamp={props.message.timestamp.toISOString()}
|
||||
type="message"
|
||||
/>;
|
||||
}
|
||||
});
|
41
src/equicordplugins/timezones/styles.css
Normal file
41
src/equicordplugins/timezones/styles.css
Normal file
|
@ -0,0 +1,41 @@
|
|||
.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 {
|
||||
place-content: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.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
|
||||
}
|
Loading…
Reference in a new issue