diff --git a/.gitignore b/.gitignore index e45af171..9f877c05 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,4 @@ lerna-debug.log* src/userplugins ExtensionCache/ -/settings +settings/ diff --git a/.github/ISSUE_TEMPLATE/blank.yml b/.jforgejo/ISSUE_TEMPLATE/blank.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/blank.yml rename to .jforgejo/ISSUE_TEMPLATE/blank.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.jforgejo/ISSUE_TEMPLATE/bug_report.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/bug_report.yml rename to .jforgejo/ISSUE_TEMPLATE/bug_report.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.jforgejo/ISSUE_TEMPLATE/config.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/config.yml rename to .jforgejo/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/developer-banner.png b/.jforgejo/ISSUE_TEMPLATE/developer-banner.png similarity index 100% rename from .github/ISSUE_TEMPLATE/developer-banner.png rename to .jforgejo/ISSUE_TEMPLATE/developer-banner.png diff --git a/.github/workflows/build.yml b/.jforgejo/workflows/build.yml similarity index 100% rename from .github/workflows/build.yml rename to .jforgejo/workflows/build.yml diff --git a/.github/workflows/codeberg-mirror.yml b/.jforgejo/workflows/codeberg-mirror.yml similarity index 100% rename from .github/workflows/codeberg-mirror.yml rename to .jforgejo/workflows/codeberg-mirror.yml diff --git a/.github/workflows/publish.yml b/.jforgejo/workflows/publish.yml similarity index 100% rename from .github/workflows/publish.yml rename to .jforgejo/workflows/publish.yml diff --git a/.github/workflows/reportBrokenPlugins.yml b/.jforgejo/workflows/reportBrokenPlugins.yml similarity index 100% rename from .github/workflows/reportBrokenPlugins.yml rename to .jforgejo/workflows/reportBrokenPlugins.yml diff --git a/.github/workflows/test.yml b/.jforgejo/workflows/test.yml similarity index 100% rename from .github/workflows/test.yml rename to .jforgejo/workflows/test.yml diff --git a/.vscode/settings.json b/.vscode/settings.json index fe123188..fa543b38 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,14 +13,11 @@ "typescript.format.semicolons": "insert", "typescript.preferences.quoteStyle": "double", "javascript.preferences.quoteStyle": "double", + "gitlens.remotes": [ { "domain": "codeberg.org", "type": "Gitea" } - ], - "css.format.spaceAroundSelectorSeparator": true, - "[css]": { - "editor.defaultFormatter": "vscode.css-language-features" - } -} \ No newline at end of file + ] +} diff --git a/README.md b/README.md index 52d3c1b6..05454c9e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ +# A fork. A fork. We're a fork. + +Installing is the same as the Vencord devs laid out: [(Install)](https://docs.vencord.dev/installing/) +* Beyond that, be sure to clone *this* repository instead of their upstream one. + * `git clone https://git.dorkbutt.lol/dorkbutt/vencord` +* After completing the steps, go to Settings > Vencord > Plugins and search for +"FORKED - usrbg" and enable it. Be sure to tweak its settings! + + # Vencord ![](https://img.shields.io/github/package-json/v/Vendicated/Vencord?style=for-the-badge&logo=github&logoColor=d3869b&label=&color=1d2021&labelColor=282828) diff --git a/package.json b/package.json index 4f05e969..577bf39d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.13.0", + "version": "1.12.6", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { diff --git a/packages/discord-types/CONTRIBUTING.md b/packages/discord-types/CONTRIBUTING.md deleted file mode 100644 index 3bef4092..00000000 --- a/packages/discord-types/CONTRIBUTING.md +++ /dev/null @@ -1 +0,0 @@ -Hint: https://docs.discord.food is an incredible resource and allows you to copy paste complete enums and interfaces diff --git a/packages/discord-types/enums/activity.ts b/packages/discord-types/enums/activity.ts deleted file mode 100644 index 513a65de..00000000 --- a/packages/discord-types/enums/activity.ts +++ /dev/null @@ -1,30 +0,0 @@ -export const enum ActivityType { - PLAYING = 0, - STREAMING = 1, - LISTENING = 2, - WATCHING = 3, - CUSTOM_STATUS = 4, - COMPETING = 5, - HANG_STATUS = 6 -} - -export const enum ActivityFlags { - INSTANCE = 1 << 0, - JOIN = 1 << 1, - /** @deprecated */ - SPECTATE = 1 << 2, - /** @deprecated */ - JOIN_REQUEST = 1 << 3, - SYNC = 1 << 4, - PLAY = 1 << 5, - PARTY_PRIVACY_FRIENDS = 1 << 6, - PARTY_PRIVACY_VOICE_CHANNEL = 1 << 7, - EMBEDDED = 1 << 8, - CONTEXTLESS = 1 << 9 -} - -export const enum ActivityStatusDisplayType { - NAME = 0, - STATE = 1, - DETAILS = 2 -} diff --git a/packages/discord-types/enums/channel.ts b/packages/discord-types/enums/channel.ts deleted file mode 100644 index 7aae546b..00000000 --- a/packages/discord-types/enums/channel.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const enum ChannelType { - GUILD_TEXT = 0, - DM = 1, - GUILD_VOICE = 2, - GROUP_DM = 3, - GUILD_CATEGORY = 4, - GUILD_ANNOUNCEMENT = 5, - ANNOUNCEMENT_THREAD = 10, - PUBLIC_THREAD = 11, - PRIVATE_THREAD = 12, - GUILD_STAGE_VOICE = 13, - GUILD_DIRECTORY = 14, - GUILD_FORUM = 15, - GUILD_MEDIA = 16 -} \ No newline at end of file diff --git a/packages/discord-types/enums/index.ts b/packages/discord-types/enums/index.ts index a25ff3b5..62074d46 100644 --- a/packages/discord-types/enums/index.ts +++ b/packages/discord-types/enums/index.ts @@ -1,5 +1 @@ -export * from "./activity"; -export * from "./channel"; export * from "./commands"; -export * from "./messages"; -export * from "./misc"; diff --git a/packages/discord-types/enums/messages.ts b/packages/discord-types/enums/messages.ts deleted file mode 100644 index 8aa6fbb3..00000000 --- a/packages/discord-types/enums/messages.ts +++ /dev/null @@ -1,596 +0,0 @@ -export const enum StickerType { - /** an official sticker in a pack */ - STANDARD = 1, - /** a sticker uploaded to a guild for the guild's members */ - GUILD = 2 -} - -export const enum StickerFormatType { - PNG = 1, - APNG = 2, - LOTTIE = 3, - GIF = 4 -} - -export const enum MessageType { - /** - * A default message (see below) - * - * Value: 0 - * Name: DEFAULT - * Rendered Content: "{content}" - * Deletable: true - */ - DEFAULT = 0, - /** - * A message sent when a user is added to a group DM or thread - * - * Value: 1 - * Name: RECIPIENT_ADD - * Rendered Content: "{author} added {mentions [0] } to the {group/thread}." - * Deletable: false - */ - RECIPIENT_ADD = 1, - /** - * A message sent when a user is removed from a group DM or thread - * - * Value: 2 - * Name: RECIPIENT_REMOVE - * Rendered Content: "{author} removed {mentions [0] } from the {group/thread}." - * Deletable: false - */ - RECIPIENT_REMOVE = 2, - /** - * A message sent when a user creates a call in a private channel - * - * Value: 3 - * Name: CALL - * Rendered Content: participated ? "{author} started a call{ended ? " that lasted {duration}" : " — Join the call"}." : "You missed a call from {author} that lasted {duration}." - * Deletable: false - */ - CALL = 3, - /** - * A message sent when a group DM or thread's name is changed - * - * Value: 4 - * Name: CHANNEL_NAME_CHANGE - * Rendered Content: "{author} changed the {is_forum ? "post title" : "channel name"}: {content} " - * Deletable: false - */ - CHANNEL_NAME_CHANGE = 4, - /** - * A message sent when a group DM's icon is changed - * - * Value: 5 - * Name: CHANNEL_ICON_CHANGE - * Rendered Content: "{author} changed the channel icon." - * Deletable: false - */ - CHANNEL_ICON_CHANGE = 5, - /** - * A message sent when a message is pinned in a channel - * - * Value: 6 - * Name: CHANNEL_PINNED_MESSAGE - * Rendered Content: "{author} pinned a message to this channel." - * Deletable: true - */ - CHANNEL_PINNED_MESSAGE = 6, - /** - * A message sent when a user joins a guild - * - * Value: 7 - * Name: USER_JOIN - * Rendered Content: See user join message type , obtained via the formula timestamp_ms % 13 - * Deletable: true - */ - USER_JOIN = 7, - /** - * A message sent when a user subscribes to (boosts) a guild - * - * Value: 8 - * Name: PREMIUM_GUILD_SUBSCRIPTION - * Rendered Content: "{author} just boosted the server{content ? " {content} times"}!" - * Deletable: true - */ - PREMIUM_GUILD_SUBSCRIPTION = 8, - /** - * A message sent when a user subscribes to (boosts) a guild to tier 1 - * - * Value: 9 - * Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_1 - * Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 1! " - * Deletable: true - */ - PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9, - /** - * A message sent when a user subscribes to (boosts) a guild to tier 2 - * - * Value: 10 - * Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_2 - * Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 2! " - * Deletable: true - */ - PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10, - /** - * A message sent when a user subscribes to (boosts) a guild to tier 3 - * - * Value: 11 - * Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_3 - * Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 3! " - * Deletable: true - */ - PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11, - /** - * A message sent when a news channel is followed - * - * Value: 12 - * Name: CHANNEL_FOLLOW_ADD - * Rendered Content: "{author} has added {content} to this channel. Its most important updates will show up here." - * Deletable: true - */ - CHANNEL_FOLLOW_ADD = 12, - /** - * A message sent when a guild is disqualified from discovery - * - * Value: 14 - * Name: GUILD_DISCOVERY_DISQUALIFIED - * Rendered Content: "This server has been removed from Server Discovery because it no longer passes all the requirements. Check Server Settings for more details." - * Deletable: true - */ - GUILD_DISCOVERY_DISQUALIFIED = 14, - /** - * A message sent when a guild requalifies for discovery - * - * Value: 15 - * Name: GUILD_DISCOVERY_REQUALIFIED - * Rendered Content: "This server is eligible for Server Discovery again and has been automatically relisted!" - * Deletable: true - */ - GUILD_DISCOVERY_REQUALIFIED = 15, - /** - * A message sent when a guild has failed discovery requirements for a week - * - * Value: 16 - * Name: GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING - * Rendered Content: "This server has failed Discovery activity requirements for 1 week. If this server fails for 4 weeks in a row, it will be automatically removed from Discovery." - * Deletable: true - */ - GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16, - /** - * A message sent when a guild has failed discovery requirements for 3 weeks - * - * Value: 17 - * Name: GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING - * Rendered Content: "This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery." - * Deletable: true - */ - GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17, - /** - * A message sent when a thread is created - * - * Value: 18 - * Name: THREAD_CREATED - * Rendered Content: "{author} started a thread: {content} . See all threads." - * Deletable: true - */ - THREAD_CREATED = 18, - /** - * A message sent when a user replies to a message - * - * Value: 19 - * Name: REPLY - * Rendered Content: "{content}" - * Deletable: true - */ - REPLY = 19, - /** - * A message sent when a user uses a slash command - * - * Value: 20 - * Name: CHAT_INPUT_COMMAND - * Rendered Content: "{content}" - * Deletable: true - */ - CHAT_INPUT_COMMAND = 20, - /** - * A message sent when a thread starter message is added to a thread - * - * Value: 21 - * Name: THREAD_STARTER_MESSAGE - * Rendered Content: "{referenced_message?.content}" ?? "Sorry, we couldn't load the first message in this thread" - * Deletable: false - */ - THREAD_STARTER_MESSAGE = 21, - /** - * A message sent to remind users to invite friends to a guild - * - * Value: 22 - * Name: GUILD_INVITE_REMINDER - * Rendered Content: "Wondering who to invite?\nStart by inviting anyone who can help you build the server!" - * Deletable: true - */ - GUILD_INVITE_REMINDER = 22, - /** - * A message sent when a user uses a context menu command - * - * Value: 23 - * Name: CONTEXT_MENU_COMMAND - * Rendered Content: "{content}" - * Deletable: true - */ - CONTEXT_MENU_COMMAND = 23, - /** - * A message sent when auto moderation takes an action - * - * Value: 24 - * Name: AUTO_MODERATION_ACTION - * Rendered Content: Special embed rendered from embeds[0] - * Deletable: true 1 - */ - AUTO_MODERATION_ACTION = 24, - /** - * A message sent when a user purchases or renews a role subscription - * - * Value: 25 - * Name: ROLE_SUBSCRIPTION_PURCHASE - * Rendered Content: "{author} {is_renewal ? "renewed" : "joined"} {role_subscription.tier_name} and has been a subscriber of {guild} for {role_subscription.total_months_subscribed} month(?s)!" - * Deletable: true - */ - ROLE_SUBSCRIPTION_PURCHASE = 25, - /** - * A message sent when a user is upsold to a premium interaction - * - * Value: 26 - * Name: INTERACTION_PREMIUM_UPSELL - * Rendered Content: "{content}" - * Deletable: true - */ - INTERACTION_PREMIUM_UPSELL = 26, - /** - * A message sent when a stage channel starts - * - * Value: 27 - * Name: STAGE_START - * Rendered Content: "{author} started {content} " - * Deletable: true - */ - STAGE_START = 27, - /** - * A message sent when a stage channel ends - * - * Value: 28 - * Name: STAGE_END - * Rendered Content: "{author} ended {content} " - * Deletable: true - */ - STAGE_END = 28, - /** - * A message sent when a user starts speaking in a stage channel - * - * Value: 29 - * Name: STAGE_SPEAKER - * Rendered Content: "{author} is now a speaker." - * Deletable: true - */ - STAGE_SPEAKER = 29, - /** - * A message sent when a user raises their hand in a stage channel - * - * Value: 30 - * Name: STAGE_RAISE_HAND - * Rendered Content: "{author} requested to speak." - * Deletable: true - */ - STAGE_RAISE_HAND = 30, - /** - * A message sent when a stage channel's topic is changed - * - * Value: 31 - * Name: STAGE_TOPIC - * Rendered Content: "{author} changed the Stage topic: {content} " - * Deletable: true - */ - STAGE_TOPIC = 31, - /** - * A message sent when a user purchases an application premium subscription - * - * Value: 32 - * Name: GUILD_APPLICATION_PREMIUM_SUBSCRIPTION - * Rendered Content: "{author} upgraded {application ?? "a deleted application"} to premium for this server!" - * Deletable: true - */ - GUILD_APPLICATION_PREMIUM_SUBSCRIPTION = 32, - /** - * A message sent when a user gifts a premium (Nitro) referral - * - * Value: 35 - * Name: PREMIUM_REFERRAL - * Rendered Content: "{content}" - * Deletable: true - */ - PREMIUM_REFERRAL = 35, - /** - * A message sent when a user enabled lockdown for the guild - * - * Value: 36 - * Name: GUILD_INCIDENT_ALERT_MODE_ENABLED - * Rendered Content: "{author} enabled security actions until {content}." - * Deletable: true - */ - GUILD_INCIDENT_ALERT_MODE_ENABLED = 36, - /** - * A message sent when a user disables lockdown for the guild - * - * Value: 37 - * Name: GUILD_INCIDENT_ALERT_MODE_DISABLED - * Rendered Content: "{author} disabled security actions." - * Deletable: true - */ - GUILD_INCIDENT_ALERT_MODE_DISABLED = 37, - /** - * A message sent when a user reports a raid for the guild - * - * Value: 38 - * Name: GUILD_INCIDENT_REPORT_RAID - * Rendered Content: "{author} reported a raid in {guild}." - * Deletable: true - */ - GUILD_INCIDENT_REPORT_RAID = 38, - /** - * A message sent when a user reports a false alarm for the guild - * - * Value: 39 - * Name: GUILD_INCIDENT_REPORT_FALSE_ALARM - * Rendered Content: "{author} reported a false alarm in {guild}." - * Deletable: true - */ - GUILD_INCIDENT_REPORT_FALSE_ALARM = 39, - /** - * A message sent when no one sends a message in the current channel for 1 hour - * - * Value: 40 - * Name: GUILD_DEADCHAT_REVIVE_PROMPT - * Rendered Content: "{content}" - * Deletable: true - */ - GUILD_DEADCHAT_REVIVE_PROMPT = 40, - /** - * A message sent when a user buys another user a gift - * - * Value: 41 - * Name: CUSTOM_GIFT - * Rendered Content: Special embed rendered from embeds[0].url and gift_info - * Deletable: true - */ - CUSTOM_GIFT = 41, - /** - * Value: 42 - * Name: GUILD_GAMING_STATS_PROMPT - * Rendered Content: "{content}" - * Deletable: true - */ - GUILD_GAMING_STATS_PROMPT = 42, - /** - * A message sent when a user purchases a guild product - * - * Value: 44 - * Name: PURCHASE_NOTIFICATION - * Rendered Content: "{author} has purchased {purchase_notification.guild_product_purchase.product_name}!" - * Deletable: true - */ - PURCHASE_NOTIFICATION = 44, - /** - * A message sent when a poll is finalized - * - * Value: 46 - * Name: POLL_RESULT - * Rendered Content: Special embed rendered from embeds[0] - * Deletable: true - */ - POLL_RESULT = 46, - /** - * A message sent by the Discord Updates account when a new changelog is posted - * - * Value: 47 - * Name: CHANGELOG - * Rendered Content: "{content}" - * Deletable: true - */ - CHANGELOG = 47, - /** - * A message sent when a Nitro promotion is triggered - * - * Value: 48 - * Name: NITRO_NOTIFICATION - * Rendered Content: Special embed rendered from content - * Deletable: true - */ - NITRO_NOTIFICATION = 48, - /** - * A message sent when a voice channel is linked to a lobby - * - * Value: 49 - * Name: CHANNEL_LINKED_TO_LOBBY - * Rendered Content: "{content}" - * Deletable: true - */ - CHANNEL_LINKED_TO_LOBBY = 49, - /** - * A local-only ephemeral message sent when a user is prompted to gift Nitro to a friend on their friendship anniversary - * - * Value: 50 - * Name: GIFTING_PROMPT - * Rendered Content: Special embed - * Deletable: true - */ - GIFTING_PROMPT = 50, - /** - * A local-only message sent when a user receives an in-game message NUX - * - * Value: 51 - * Name: IN_GAME_MESSAGE_NUX - * Rendered Content: "{author} messaged you from {application.name}. In-game chat may not include rich messaging features such as images, polls, or apps. Learn More " - * Deletable: true - */ - IN_GAME_MESSAGE_NUX = 51, - /** - * A message sent when a user accepts a guild join request - * - * Value: 52 - * Name: GUILD_JOIN_REQUEST_ACCEPT_NOTIFICATION 2 - * Rendered Content: "{join_request.user}'s application to {content} was approved! Welcome!" - * Deletable: true - */ - GUILD_JOIN_REQUEST_ACCEPT_NOTIFICATION = 52, - /** - * A message sent when a user rejects a guild join request - * - * Value: 53 - * Name: GUILD_JOIN_REQUEST_REJECT_NOTIFICATION 2 - * Rendered Content: "{join_request.user}'s application to {content} was rejected." - * Deletable: true - */ - GUILD_JOIN_REQUEST_REJECT_NOTIFICATION = 53, - /** - * A message sent when a user withdraws a guild join request - * - * Value: 54 - * Name: GUILD_JOIN_REQUEST_WITHDRAWN_NOTIFICATION 2 - * Rendered Content: "{join_request.user}'s application to {content} has been withdrawn." - * Deletable: true - */ - GUILD_JOIN_REQUEST_WITHDRAWN_NOTIFICATION = 54, - /** - * A message sent when a user upgrades to HD streaming - * - * Value: 55 - * Name: HD_STREAMING_UPGRADED - * Rendered Content: "{author} activated HD Splash Potion " - * Deletable: true - */ - HD_STREAMING_UPGRADED = 55, - /** - * A message sent when a user resolves a moderation report by deleting the offending message - * - * Value: 58 - * Name: REPORT_TO_MOD_DELETED_MESSAGE - * Rendered Content: "{author} deleted the message" - * Deletable: true - */ - REPORT_TO_MOD_DELETED_MESSAGE = 58, - /** - * A message sent when a user resolves a moderation report by timing out the offending user - * - * Value: 59 - * Name: REPORT_TO_MOD_TIMEOUT_USER - * Rendered Content: "{author} timed out {mentions [0] }" - * Deletable: true - */ - REPORT_TO_MOD_TIMEOUT_USER = 59, - /** - * A message sent when a user resolves a moderation report by kicking the offending user - * - * Value: 60 - * Name: REPORT_TO_MOD_KICK_USER - * Rendered Content: "{author} kicked {mentions [0] }" - * Deletable: true - */ - REPORT_TO_MOD_KICK_USER = 60, - /** - * A message sent when a user resolves a moderation report by banning the offending user - * - * Value: 61 - * Name: REPORT_TO_MOD_BAN_USER - * Rendered Content: "{author} banned {mentions [0] }" - * Deletable: true - */ - REPORT_TO_MOD_BAN_USER = 61, - /** - * A message sent when a user resolves a moderation report - * - * Value: 62 - * Name: REPORT_TO_MOD_CLOSED_REPORT - * Rendered Content: "{author} resolved this flag" - * Deletable: true - */ - REPORT_TO_MOD_CLOSED_REPORT = 62, - /** - * A message sent when a user adds a new emoji to a guild - * - * Value: 63 - * Name: EMOJI_ADDED - * Rendered Content: "{author} added a new emoji, {content} :{emoji.name}: " - * Deletable: true - */ - EMOJI_ADDED = 63, -} - -export const enum MessageFlags { - /** - * Message has been published to subscribed channels (via Channel Following) - * - * Value: 1 << 0 - */ - CROSSPOSTED = 1 << 0, - /** - * Message originated from a message in another channel (via Channel Following) - */ - IS_CROSSPOST = 1 << 1, - /** - * Embeds will not be included when serializing this message - */ - SUPPRESS_EMBEDS = 1 << 2, - /** - * Source message for this crosspost has been deleted (via Channel Following) - */ - SOURCE_MESSAGE_DELETED = 1 << 3, - /** - * Message came from the urgent message system - */ - URGENT = 1 << 4, - /** - * Message has an associated thread, with the same ID as the message - */ - HAS_THREAD = 1 << 5, - /** - * Message is only visible to the user who invoked the interaction - */ - EPHEMERAL = 1 << 6, - /** - * Message is an interaction response and the bot is "thinking" - */ - LOADING = 1 << 7, - /** - * Some roles were not mentioned and added to the thread - */ - FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8, - /** - * Message is hidden from the guild's feed - */ - GUILD_FEED_HIDDEN = 1 << 9, - /** - * Message contains a link that impersonates Discord - */ - SHOULD_SHOW_LINK_NOT_DISCORD_WARNING = 1 << 10, - /** - * Message will not trigger push and desktop notifications - */ - SUPPRESS_NOTIFICATIONS = 1 << 12, - /** - * Message's audio attachment is rendered as a voice message - */ - IS_VOICE_MESSAGE = 1 << 13, - /** - * Message has a forwarded message snapshot attached - */ - HAS_SNAPSHOT = 1 << 14, - /** - * Message contains components from version 2 of the UI kit - */ - IS_COMPONENTS_V2 = 1 << 15, - /** - * Message was triggered by the social layer integration - */ - SENT_BY_SOCIAL_LAYER_INTEGRATION = 1 << 16, -} diff --git a/packages/discord-types/enums/misc.ts b/packages/discord-types/enums/misc.ts deleted file mode 100644 index d1419ba3..00000000 --- a/packages/discord-types/enums/misc.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const enum CloudUploadPlatform { - REACT_NATIVE = 0, - WEB = 1, -} diff --git a/packages/discord-types/src/classes.d.ts b/packages/discord-types/src/classes.d.ts index a7260048..752ba3f0 100644 --- a/packages/discord-types/src/classes.d.ts +++ b/packages/discord-types/src/classes.d.ts @@ -1,3 +1,8 @@ +export interface ImageModalClasses { + image: string, + modal: string, +} + export interface ButtonWrapperClasses { hoverScale: string; buttonWrapper: string; diff --git a/packages/discord-types/src/common/Activity.d.ts b/packages/discord-types/src/common/Activity.d.ts deleted file mode 100644 index d513780a..00000000 --- a/packages/discord-types/src/common/Activity.d.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "../../enums"; - -export interface ActivityAssets { - large_image?: string; - large_text?: string; - small_image?: string; - small_text?: string; -} - -export interface ActivityButton { - label: string; - url: string; -} - -export interface Activity { - name: string; - application_id: string; - type: ActivityType; - state?: string; - state_url?: string; - details?: string; - details_url?: string; - url?: string; - flags: ActivityFlags; - status_display_type?: ActivityStatusDisplayType; - timestamps?: { - start?: number; - end?: number; - }; - assets?: ActivityAssets; - buttons?: string[]; - metadata?: { - button_urls?: Array; - }; -} - diff --git a/packages/discord-types/src/common/index.d.ts b/packages/discord-types/src/common/index.d.ts index 65ad8856..5e50e96e 100644 --- a/packages/discord-types/src/common/index.d.ts +++ b/packages/discord-types/src/common/index.d.ts @@ -1,4 +1,3 @@ -export * from "./Activity"; export * from "./Application"; export * from "./Channel"; export * from "./Guild"; diff --git a/packages/discord-types/src/common/messages/Message.d.ts b/packages/discord-types/src/common/messages/Message.d.ts index c2af2496..38010caf 100644 --- a/packages/discord-types/src/common/messages/Message.d.ts +++ b/packages/discord-types/src/common/messages/Message.d.ts @@ -2,9 +2,8 @@ import { CommandOption } from './Commands'; import { User, UserJSON } from '../User'; import { Embed, EmbedJSON } from './Embed'; import { DiscordRecord } from "../Record"; -import { MessageFlags, MessageType, StickerFormatType } from "../../../enums"; -/* +/** * TODO: looks like discord has moved over to Date instead of Moment; */ export class Message extends DiscordRecord { @@ -35,7 +34,7 @@ export class Message extends DiscordRecord { customRenderedContent: unknown; editedTimestamp: Date; embeds: Embed[]; - flags: MessageFlags; + flags: number; giftCodes: string[]; id: string; interaction: { @@ -84,23 +83,20 @@ export class Message extends DiscordRecord { channel_id: string; message_id: string; } | undefined; - messageSnapshots: { - message: Message; - }[]; nick: unknown; // probably a string nonce: string | undefined; pinned: boolean; reactions: MessageReaction[]; state: string; stickerItems: { - format_type: StickerFormatType; + format_type: number; id: string; name: string; }[]; stickers: unknown[]; timestamp: moment.Moment; tts: boolean; - type: MessageType; + type: number; webhookId: string | undefined; /** @@ -121,13 +117,10 @@ export class Message extends DiscordRecord { removeReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message; getChannelId(): string; - hasFlag(flag: MessageFlags): boolean; + hasFlag(flag: number): boolean; isCommandType(): boolean; isEdited(): boolean; isSystemDM(): boolean; - - /** Vencord added */ - deleted?: boolean; } /** A smaller Message object found in FluxDispatcher and elsewhere. */ @@ -196,9 +189,3 @@ export interface MessageReaction { emoji: ReactionEmoji; me: boolean; } - -// Object.keys(findByProps("REPLYABLE")).map(JSON.stringify).join("|") -export type MessageTypeSets = Record< - "UNDELETABLE" | "GUILD_DISCOVERY_STATUS" | "USER_MESSAGE" | "NOTIFIABLE_SYSTEM_MESSAGE" | "REPLYABLE" | "FORWARDABLE" | "REFERENCED_MESSAGE_AVAILABLE" | "AVAILABLE_IN_GUILD_FEED" | "DEADCHAT_PROMPTS" | "NON_COLLAPSIBLE" | "NON_PARSED" | "AUTOMOD_INCIDENT_ACTIONS" | "SELF_MENTIONABLE_SYSTEM" | "SCHEDULABLE", - Set ->; diff --git a/packages/discord-types/src/common/messages/Sticker.d.ts b/packages/discord-types/src/common/messages/Sticker.d.ts deleted file mode 100644 index 744b0623..00000000 --- a/packages/discord-types/src/common/messages/Sticker.d.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { StickerFormatType, StickerType } from "../../../enums"; - -interface BaseSticker { - asset: string; - available: boolean; - description: string; - format_type: StickerFormatType; - id: string; - name: string; - sort_value?: number; - /** a comma separated string */ - tags: string; -} - -export interface PackSticker extends BaseSticker { - pack_id: string; - type: StickerType.STANDARD; -} - -export interface GuildSticker extends BaseSticker { - guild_id: string; - type: StickerType.GUILD; -} - -export type Sticker = PackSticker | GuildSticker; - -export interface PremiumStickerPack { - banner_asset_id?: string; - cover_sticker_id?: string; - description: string; - id: string; - name: string; - sku_id: string; - stickers: PackSticker[]; -} diff --git a/packages/discord-types/src/common/messages/index.d.ts b/packages/discord-types/src/common/messages/index.d.ts index 23987546..245e971e 100644 --- a/packages/discord-types/src/common/messages/index.d.ts +++ b/packages/discord-types/src/common/messages/index.d.ts @@ -2,4 +2,3 @@ export * from "./Commands"; export * from "./Message"; export * from "./Embed"; export * from "./Emoji"; -export * from "./Sticker"; diff --git a/packages/discord-types/src/components.d.ts b/packages/discord-types/src/components.d.ts index f9e778d8..8b2fd1cf 100644 --- a/packages/discord-types/src/components.d.ts +++ b/packages/discord-types/src/components.d.ts @@ -43,7 +43,12 @@ export type FormDivider = ComponentType<{ }>; -export type FormText = ComponentType; +export type FormText = ComponentType & TextProps> & { Types: FormTextTypes; }; export type Tooltip = ComponentType<{ text: ReactNode | ComponentType; @@ -239,10 +244,8 @@ export type TextInput = ComponentType; }; -// FIXME: this is wrong, it's not actually just HTMLTextAreaElement export type TextArea = ComponentType, "onChange"> & { onChange(v: string): void; - inputRef?: Ref; }>; interface SelectOption { @@ -469,66 +472,19 @@ export type MaskedLink = ComponentType>; -export interface ScrollerBaseProps { +export type ScrollerThin = ComponentType>; -interface BaseListItem { - anchorId: any; - listIndex: number; - offsetTop: number; - section: number; -} -interface ListSection extends BaseListItem { - type: "section"; -} -interface ListRow extends BaseListItem { - type: "row"; - row: number; - rowIndex: number; -} - -export type ListScrollerThin = ComponentType React.ReactNode; - renderRow: (item: ListRow) => React.ReactNode; - renderFooter?: (item: any) => React.ReactNode; - renderSidebar?: (listVisible: boolean, sidebarVisible: boolean) => React.ReactNode; - wrapSection?: (section: number, children: React.ReactNode) => React.ReactNode; - - sectionHeight: number; - rowHeight: number; - footerHeight?: number; - sidebarHeight?: number; - - chunkSize?: number; - - paddingTop?: number; - paddingBottom?: number; - fade?: boolean; - onResize?: Function; - getAnchorId?: any; - - innerTag?: string; - innerId?: string; - innerClassName?: string; - innerRole?: string; - innerAriaLabel?: string; - // Yes, Discord uses this casing - innerAriaMultiselectable?: boolean; - innerAriaOrientation?: "vertical" | "horizontal"; -}>; - export type Clickable = (props: PropsWithChildren> & { tag?: T; }) => ReactNode; diff --git a/packages/discord-types/src/index.d.ts b/packages/discord-types/src/index.d.ts index fdbd65f1..6d9356b4 100644 --- a/packages/discord-types/src/index.d.ts +++ b/packages/discord-types/src/index.d.ts @@ -4,7 +4,6 @@ export * from "./components"; export * from "./flux"; export * from "./fluxEvents"; export * from "./menu"; -export * from "./modules"; export * from "./stores"; export * from "./utils"; export * as Webpack from "../webpack"; diff --git a/packages/discord-types/src/modules/CloudUpload.d.ts b/packages/discord-types/src/modules/CloudUpload.d.ts deleted file mode 100644 index 02192ed5..00000000 --- a/packages/discord-types/src/modules/CloudUpload.d.ts +++ /dev/null @@ -1,74 +0,0 @@ -import EventEmitter from "events"; -import { CloudUploadPlatform } from "../../enums"; - -interface BaseUploadItem { - platform: CloudUploadPlatform; - id?: string; - origin?: string; - isThumbnail?: boolean; - clip?: unknown; -} - -export interface ReactNativeUploadItem extends BaseUploadItem { - platform: CloudUploadPlatform.REACT_NATIVE; - uri: string; - filename?: string; - mimeType?: string; - durationSecs?: number; - waveform?: string; - isRemix?: boolean; -} - -export interface WebUploadItem extends BaseUploadItem { - platform: CloudUploadPlatform.WEB; - file: File; -} - -export type CloudUploadItem = ReactNativeUploadItem | WebUploadItem; - -export class CloudUpload extends EventEmitter { - constructor(item: CloudUploadItem, channelId: string, showLargeMessageDialog?: boolean, reactNativeFileIndex?: number); - - channelId: string; - classification: string; - clip: unknown; - contentHash: unknown; - currentSize: number; - description: string | null; - durationSecs: number | undefined; - etag: string | undefined; - error: unknown; - filename: string; - id: string; - isImage: boolean; - isRemix: boolean | undefined; - isThumbnail: boolean; - isVideo: boolean; - item: { - file: File; - platform: CloudUploadPlatform; - origin: string; - }; - loaded: number; - mimeType: string; - origin: string; - postCompressionSize: number | undefined; - preCompressionSize: number; - responseUrl: string; - sensitive: boolean; - showLargeMessageDialog: boolean; - spoiler: boolean; - startTime: number; - status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED" | "REMOVED_FROM_MSG_DRAFT"; - uniqueId: string; - uploadedFilename: string; - waveform: string | undefined; - - // there are many more methods than just these but I didn't find them particularly useful - upload(): Promise; - cancel(): void; - delete(): Promise; - getSize(): number; - maybeConvertToWebP(): Promise; - removeFromMsgDraft(): void; -} diff --git a/packages/discord-types/src/modules/index.d.ts b/packages/discord-types/src/modules/index.d.ts deleted file mode 100644 index fe0c1e24..00000000 --- a/packages/discord-types/src/modules/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./CloudUpload"; diff --git a/packages/discord-types/src/stores/AuthenticationStore.d.ts b/packages/discord-types/src/stores/AuthenticationStore.d.ts deleted file mode 100644 index 3ad2354f..00000000 --- a/packages/discord-types/src/stores/AuthenticationStore.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { FluxStore } from ".."; - -export class AuthenticationStore extends FluxStore { - /** - * Gets the id of the current user - */ - getId(): string; - - // This Store has a lot more methods related to everything Auth, but they really should - // not be needed, so they are not typed -} diff --git a/packages/discord-types/src/stores/GuildRoleStore.d.ts b/packages/discord-types/src/stores/GuildRoleStore.d.ts index faf586a0..bf0d4042 100644 --- a/packages/discord-types/src/stores/GuildRoleStore.d.ts +++ b/packages/discord-types/src/stores/GuildRoleStore.d.ts @@ -1,8 +1,7 @@ import { FluxStore, Role } from ".."; -// TODO: add the rest of the methods for GuildRoleStore export class GuildRoleStore extends FluxStore { getRole(guildId: string, roleId: string): Role; - getSortedRoles(guildId: string): Role[]; - getRolesSnapshot(guildId: string): Record; + getRoles(guildId: string): Record; + getAllGuildRoles(): Record>; } diff --git a/packages/discord-types/src/stores/RelationshipStore.d.ts b/packages/discord-types/src/stores/RelationshipStore.d.ts index 9aceac47..5d1a08af 100644 --- a/packages/discord-types/src/stores/RelationshipStore.d.ts +++ b/packages/discord-types/src/stores/RelationshipStore.d.ts @@ -15,11 +15,6 @@ export class RelationshipStore extends FluxStore { isFriend(userId: string): boolean; isBlocked(userId: string): boolean; isIgnored(userId: string): boolean; - /** - * @see {@link isBlocked} - * @see {@link isIgnored} - */ - isBlockedOrIgnored(userId: string): boolean; getSince(userId: string): string; getMutableRelationships(): Map; diff --git a/packages/discord-types/src/stores/StickersStore.d.ts b/packages/discord-types/src/stores/StickersStore.d.ts deleted file mode 100644 index fdfb012c..00000000 --- a/packages/discord-types/src/stores/StickersStore.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { FluxStore, GuildSticker, PremiumStickerPack, Sticker } from ".."; - -export type StickerGuildMap = Map; - -export class StickersStore extends FluxStore { - getAllGuildStickers(): StickerGuildMap; - getRawStickersByGuild(): StickerGuildMap; - getPremiumPacks(): PremiumStickerPack[]; - - getStickerById(id: string): Sticker | undefined; - getStickerPack(id: string): PremiumStickerPack | undefined; - getStickersByGuildId(guildId: string): Sticker[] | undefined; - - isPremiumPack(id: string): boolean; -} diff --git a/packages/discord-types/src/stores/StreamerModeStore.d.ts b/packages/discord-types/src/stores/StreamerModeStore.d.ts deleted file mode 100644 index efa6cfc9..00000000 --- a/packages/discord-types/src/stores/StreamerModeStore.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { FluxStore } from "@vencord/discord-types"; - -export class StreamerModeStore extends FluxStore { - get autoToggle(): boolean; - get disableNotifications(): boolean; - get disableSounds(): boolean; - get enableContentProtection(): boolean; - get enabled(): boolean; - get hideInstantInvites(): boolean; - get hidePersonalInformation(): boolean; -} diff --git a/packages/discord-types/src/stores/TypingStore.d.ts b/packages/discord-types/src/stores/TypingStore.d.ts deleted file mode 100644 index 0ebe994f..00000000 --- a/packages/discord-types/src/stores/TypingStore.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { FluxStore } from ".."; - -export class TypingStore extends FluxStore { - /** - * returns a map of user ids to timeout ids - */ - getTypingUsers(channelId: string): Record; - isTyping(channelId: string, userId: string): boolean; -} diff --git a/packages/discord-types/src/stores/VoiceStateStore.d.ts b/packages/discord-types/src/stores/VoiceStateStore.d.ts deleted file mode 100644 index 84540d99..00000000 --- a/packages/discord-types/src/stores/VoiceStateStore.d.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { DiscordRecord } from "../common"; -import { FluxStore } from "./FluxStore"; - -export type UserVoiceStateRecords = Record; -export type VoiceStates = Record; - -export interface VoiceState extends DiscordRecord { - userId: string; - channelId: string | null | undefined; - sessionId: string | null | undefined; - mute: boolean; - deaf: boolean; - selfMute: boolean; - selfDeaf: boolean; - selfVideo: boolean; - selfStream: boolean | undefined; - suppress: boolean; - requestToSpeakTimestamp: string | null | undefined; - discoverable: boolean; - - isVoiceMuted(): boolean; - isVoiceDeafened(): boolean; -} - -export class VoiceStateStore extends FluxStore { - getAllVoiceStates(): VoiceStates; - - getVoiceStates(guildId?: string | null): UserVoiceStateRecords; - getVoiceStatesForChannel(channelId: string): UserVoiceStateRecords; - getVideoVoiceStatesForChannel(channelId: string): UserVoiceStateRecords; - - getVoiceState(guildId: string | null, userId: string): VoiceState | undefined; - getUserVoiceChannelId(guildId: string | null, userId: string): string | undefined; - getVoiceStateForChannel(channelId: string, userId?: string): VoiceState | undefined; - getVoiceStateForUser(userId: string): VoiceState | undefined; - - getCurrentClientVoiceChannelId(guildId: string | null): string | undefined; - isCurrentClientInVoiceChannel(): boolean; - - isInChannel(channelId: string, userId?: string): boolean; - hasVideo(channelId: string): boolean; -} diff --git a/packages/discord-types/src/stores/index.d.ts b/packages/discord-types/src/stores/index.d.ts index a843445b..23045832 100644 --- a/packages/discord-types/src/stores/index.d.ts +++ b/packages/discord-types/src/stores/index.d.ts @@ -1,5 +1,4 @@ // please keep in alphabetical order -export * from "./AuthenticationStore"; export * from "./ChannelStore"; export * from "./DraftStore"; export * from "./EmojiStore"; @@ -11,13 +10,9 @@ export * from "./MessageStore"; export * from "./RelationshipStore"; export * from "./SelectedChannelStore"; export * from "./SelectedGuildStore"; -export * from "./StickersStore"; -export * from "./StreamerModeStore"; export * from "./ThemeStore"; -export * from "./TypingStore"; export * from "./UserProfileStore"; export * from "./UserStore"; -export * from "./VoiceStateStore"; export * from "./WindowStore"; /** diff --git a/packages/discord-types/src/utils.d.ts b/packages/discord-types/src/utils.d.ts index 14b8fa50..77b6f88b 100644 --- a/packages/discord-types/src/utils.d.ts +++ b/packages/discord-types/src/utils.d.ts @@ -6,15 +6,13 @@ import type { FluxEvents } from "./fluxEvents"; export { FluxEvents }; -type FluxEventsAutoComplete = LiteralUnion; - export interface FluxDispatcher { _actionHandlers: any; _subscriptions: any; - dispatch(event: { [key: string]: unknown; type: FluxEventsAutoComplete; }): Promise; + dispatch(event: { [key: string]: unknown; type: FluxEvents; }): Promise; isDispatching(): boolean; - subscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void; - unsubscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void; + subscribe(event: FluxEvents, callback: (data: any) => void): void; + unsubscribe(event: FluxEvents, callback: (data: any) => void): void; wait(callback: () => void): void; } diff --git a/src/Vencord.ts b/src/Vencord.ts index 00f29347..f18c7347 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -16,9 +16,6 @@ * along with this program. If not, see . */ -// DO NOT REMOVE UNLESS YOU WISH TO FACE THE WRATH OF THE CIRCULAR DEPENDENCY DEMON!!!!!!! -import "~plugins"; - export * as Api from "./api"; export * as Components from "./components"; export * as Plugins from "./plugins"; @@ -32,8 +29,7 @@ export { PlainSettings, Settings }; import "./utils/quickCss"; import "./webpack/patchWebpack"; -import { openUpdaterModal } from "@components/settings/tabs/updater"; -import { IS_WINDOWS } from "@utils/constants"; +import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab"; import { StartAt } from "@utils/types"; import { get as dsGet } from "./api/DataStore"; @@ -165,7 +161,7 @@ init(); document.addEventListener("DOMContentLoaded", () => { startAllPlugins(StartAt.DOMContentLoaded); - if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && IS_WINDOWS) { + if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && navigator.platform.toLowerCase().startsWith("win")) { document.head.append(Object.assign(document.createElement("style"), { id: "vencord-native-titlebar-style", textContent: "[class*=titleBar]{display: none!important}" diff --git a/src/api/Badges.ts b/src/api/Badges.ts index 1345b26d..ee2f3a30 100644 --- a/src/api/Badges.ts +++ b/src/api/Badges.ts @@ -17,9 +17,10 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; -import BadgeAPIPlugin from "plugins/_api/badges"; import { ComponentType, HTMLProps } from "react"; +import Plugins from "~plugins"; + export const enum BadgePosition { START, END @@ -34,9 +35,7 @@ export interface ProfileBadge { image?: string; link?: string; /** Action to perform when you click the badge */ - onClick?(event: React.MouseEvent, props: ProfileBadge & BadgeUserArgs): void; - /** Action to perform when you right click the badge */ - onContextMenu?(event: React.MouseEvent, props: BadgeUserArgs & BadgeUserArgs): void; + onClick?(event: React.MouseEvent, props: BadgeUserArgs): void; /** Should the user display this badge? */ shouldShow?(userInfo: BadgeUserArgs): boolean; /** Optional props (e.g. style) for the badge, ignored for component badges */ @@ -78,34 +77,21 @@ export function removeProfileBadge(badge: ProfileBadge) { export function _getBadges(args: BadgeUserArgs) { const badges = [] as ProfileBadge[]; for (const badge of Badges) { - if (badge.shouldShow && !badge.shouldShow(args)) { - continue; - } + if (!badge.shouldShow || badge.shouldShow(args)) { + const b = badge.getBadges + ? badge.getBadges(args).map(b => { + b.component &&= ErrorBoundary.wrap(b.component, { noop: true }); + return b; + }) + : [{ ...badge, ...args }]; - const b = badge.getBadges - ? badge.getBadges(args).map(badge => ({ - ...args, - ...badge, - component: badge.component && ErrorBoundary.wrap(badge.component, { noop: true }) - })) - : [{ ...args, ...badge }]; - - if (badge.position === BadgePosition.START) { - badges.unshift(...b); - } else { - badges.push(...b); + badge.position === BadgePosition.START + ? badges.unshift(...b) + : badges.push(...b); } } - - const donorBadges = BadgeAPIPlugin.getDonorBadges(args.userId); - if (donorBadges) { - badges.unshift( - ...donorBadges.map(badge => ({ - ...args, - ...badge, - })) - ); - } + const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.userId); + if (donorBadges) badges.unshift(...donorBadges); return badges; } diff --git a/src/api/MessageEvents.ts b/src/api/MessageEvents.ts index 54d40000..8b1d9e78 100644 --- a/src/api/MessageEvents.ts +++ b/src/api/MessageEvents.ts @@ -17,7 +17,7 @@ */ import { Logger } from "@utils/Logger"; -import type { Channel, CloudUpload, CustomEmoji, Message } from "@vencord/discord-types"; +import type { Channel, CustomEmoji, Message } from "@vencord/discord-types"; import { MessageStore } from "@webpack/common"; import type { Promisable } from "type-fest"; @@ -30,6 +30,30 @@ export interface MessageObject { tts: boolean; } +export interface Upload { + classification: string; + currentSize: number; + description: string | null; + filename: string; + id: string; + isImage: boolean; + isVideo: boolean; + item: { + file: File; + platform: number; + }; + loaded: number; + mimeType: string; + preCompressionSize: number; + responseUrl: string; + sensitive: boolean; + showLargeMessageDialog: boolean; + spoiler: boolean; + status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED"; + uniqueId: string; + uploadedFilename: string; +} + export interface MessageReplyOptions { messageReference: Message["messageReference"]; allowedMentions?: { @@ -38,9 +62,9 @@ export interface MessageReplyOptions { }; } -export interface MessageOptions { +export interface MessageExtra { stickers?: string[]; - uploads?: CloudUpload[]; + uploads?: Upload[]; replyOptions: MessageReplyOptions; content: string; channel: Channel; @@ -48,17 +72,17 @@ export interface MessageOptions { openWarningPopout: (props: any) => any; } -export type MessageSendListener = (channelId: string, messageObj: MessageObject, options: MessageOptions) => Promisable; +export type MessageSendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable; export type MessageEditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable; const sendListeners = new Set(); const editListeners = new Set(); -export async function _handlePreSend(channelId: string, messageObj: MessageObject, options: MessageOptions, replyOptions: MessageReplyOptions) { - options.replyOptions = replyOptions; +export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) { + extra.replyOptions = replyOptions; for (const listener of sendListeners) { try { - const result = await listener(channelId, messageObj, options); + const result = await listener(channelId, messageObj, extra); if (result?.cancel) { return true; } diff --git a/src/api/MessageUpdater.ts b/src/api/MessageUpdater.ts index afbb04f0..7c4b3d5c 100644 --- a/src/api/MessageUpdater.ts +++ b/src/api/MessageUpdater.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { Message } from "@vencord/discord-types"; +import { FluxStore, Message } from "@vencord/discord-types"; import { MessageCache, MessageStore } from "@webpack/common"; /** @@ -24,5 +24,5 @@ export function updateMessage(channelId: string, messageId: string, fields?: Par }); MessageCache.commit(newChannelMessageCache); - MessageStore.emitChange(); + (MessageStore as unknown as FluxStore).emitChange(); } diff --git a/src/api/Notices.tsx b/src/api/Notices.ts similarity index 73% rename from src/api/Notices.tsx rename to src/api/Notices.ts index b4c44b88..6d20087a 100644 --- a/src/api/Notices.tsx +++ b/src/api/Notices.ts @@ -16,10 +16,7 @@ * along with this program. If not, see . */ -import ErrorBoundary from "@components/ErrorBoundary"; -import { isPrimitiveReactNode } from "@utils/react"; import { waitFor } from "@webpack"; -import { ReactNode } from "react"; let NoticesModule: any; waitFor(m => m.show && m.dismiss && !m.suppressAll, m => NoticesModule = m); @@ -39,11 +36,7 @@ export function nextNotice() { } } -export function showNotice(message: ReactNode, buttonText: string, onOkClick: () => void) { - const notice = isPrimitiveReactNode(message) - ? message - : "Error Showing Notice"}>{message}; - - noticesQueue.push(["GENERIC", notice, buttonText, onOkClick]); +export function showNotice(message: string, buttonText: string, onOkClick: () => void) { + noticesQueue.push(["GENERIC", message, buttonText, onOkClick]); if (!currentNotice) nextNotice(); } diff --git a/src/api/Notifications/NotificationComponent.tsx b/src/api/Notifications/NotificationComponent.tsx index 52e56d5d..d07143c4 100644 --- a/src/api/Notifications/NotificationComponent.tsx +++ b/src/api/Notifications/NotificationComponent.tsx @@ -104,7 +104,9 @@ export default ErrorBoundary.wrap(function NotificationComponent({ - {richBody ??

{body}

} +
+ {richBody ??

{body}

} +
{image && } diff --git a/src/api/Notifications/notificationLog.tsx b/src/api/Notifications/notificationLog.tsx index dca7679e..5df31d4c 100644 --- a/src/api/Notifications/notificationLog.tsx +++ b/src/api/Notifications/notificationLog.tsx @@ -20,10 +20,10 @@ import * as DataStore from "@api/DataStore"; import { Settings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import { Flex } from "@components/Flex"; -import { openNotificationSettingsModal } from "@components/settings/tabs/vencord/NotificationSettings"; -import { closeModal, ModalCloseButton, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { openNotificationSettingsModal } from "@components/VencordSettings/NotificationSettings"; +import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; -import { Alerts, Button, Forms, ListScrollerThin, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; +import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; import { nanoid } from "nanoid"; import type { DispatchWithoutAction } from "react"; @@ -103,9 +103,21 @@ export function useLogs() { function NotificationEntry({ data }: { data: PersistentNotificationData; }) { const [removing, setRemoving] = useState(false); + const ref = React.useRef(null); + + useEffect(() => { + const div = ref.current!; + + const setHeight = () => { + if (div.clientHeight === 0) return requestAnimationFrame(setHeight); + div.style.height = `${div.clientHeight}px`; + }; + + setHeight(); + }, []); return ( -
+
deleteNotification(data.timestamp), 200); }} richBody={ -
-
{data.body}
+
+ {data.body}
} /> -
+
); } @@ -139,14 +151,9 @@ export function NotificationLog({ log, pending }: { log: PersistentNotificationD ); return ( - null} - renderRow={item => } - /> +
+ {log.map(n => )} +
); } @@ -154,15 +161,15 @@ function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void const [log, pending] = useLogs(); return ( - + Notification Log -
+ -
+ diff --git a/src/api/Notifications/styles.css b/src/api/Notifications/styles.css index 471fbf01..10d3c0cf 100644 --- a/src/api/Notifications/styles.css +++ b/src/api/Notifications/styles.css @@ -4,13 +4,17 @@ display: flex; flex-direction: column; color: var(--text-default); - background-color: var(--background-base-low); + background-color: var(--background-base-lower-alt); border-radius: 6px; overflow: hidden; cursor: pointer; width: 100%; } +.visual-refresh .vc-notification-root { + background-color: var(--background-base-low); +} + .vc-notification-root:not(.vc-notification-log-wrapper > .vc-notification-root) { position: absolute; z-index: 2147483647; @@ -28,7 +32,6 @@ .vc-notification-content { width: 100%; - overflow: hidden; } .vc-notification-header { @@ -78,11 +81,6 @@ width: 100%; } -.vc-notification-log-modal { - max-width: 962px; - width: clamp(var(--modal-width-large, 800px), 962px, 85vw); -} - .vc-notification-log-empty { height: 218px; background: url("/assets/b36de980b174d7b798c89f35c116e5c6.svg") center no-repeat; @@ -90,23 +88,19 @@ } .vc-notification-log-container { + display: flex; + flex-direction: column; padding: 1em; - max-height: min(750px, 75vh); - width: 100%; + overflow: hidden; } .vc-notification-log-wrapper { - height: 120px; - width: 100%; - padding-bottom: 16px; - box-sizing: border-box; transition: 200ms ease; transition-property: height, opacity; +} - /* stylelint-disable-next-line no-descending-specificity */ - .vc-notification-root { - height: 104px; - } +.vc-notification-log-wrapper:not(:last-child) { + margin-bottom: 1em; } .vc-notification-log-removing { @@ -115,18 +109,9 @@ margin-bottom: 1em; } -.vc-notification-log-body-wrapper { +.vc-notification-log-body { display: flex; flex-direction: column; - width: 100%; - box-sizing: border-box; -} - -.vc-notification-log-body { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - line-height: 1.2em; } .vc-notification-log-timestamp { @@ -138,4 +123,4 @@ .vc-notification-log-danger-btn { color: var(--white-500); background-color: var(--button-danger-background); -} \ No newline at end of file +} diff --git a/src/components/settings/PluginBadge.tsx b/src/components/Badge.tsx similarity index 90% rename from src/components/settings/PluginBadge.tsx rename to src/components/Badge.tsx index 5208fdea..61b36e97 100644 --- a/src/components/settings/PluginBadge.tsx +++ b/src/components/Badge.tsx @@ -16,9 +16,9 @@ * along with this program. If not, see . */ -export function AddonBadge({ text, color }) { +export function Badge({ text, color }) { return ( -
. */ -import { Heart } from "@components/Heart"; import { ButtonProps } from "@vencord/discord-types"; import { Button } from "@webpack/common"; +import { Heart } from "./Heart"; + export default function DonateButton({ look = Button.Looks.LINK, color = Button.Colors.TRANSPARENT, diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index 9862c1b4..4c5b0ca0 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -27,6 +27,7 @@ interface BaseIconProps extends IconProps { } type IconProps = JSX.IntrinsicElements["svg"]; +type ImageProps = JSX.IntrinsicElements["img"]; function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren) { return ( diff --git a/src/components/settings/tabs/plugins/ContributorModal.tsx b/src/components/PluginSettings/ContributorModal.tsx similarity index 98% rename from src/components/settings/tabs/plugins/ContributorModal.tsx rename to src/components/PluginSettings/ContributorModal.tsx index 4794a084..378ee34c 100644 --- a/src/components/settings/tabs/plugins/ContributorModal.tsx +++ b/src/components/PluginSettings/ContributorModal.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import "./ContributorModal.css"; +import "./contributorModal.css"; import { useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; @@ -19,8 +19,8 @@ import { Forms, showToast, useEffect, useMemo, UserProfileStore, useStateFromSto import Plugins from "~plugins"; +import { PluginCard } from "."; import { GithubButton, WebsiteButton } from "./LinkIconButton"; -import { PluginCard } from "./PluginCard"; const cl = classNameFactory("vc-author-modal-"); diff --git a/src/components/settings/tabs/plugins/LinkIconButton.css b/src/components/PluginSettings/LinkIconButton.css similarity index 77% rename from src/components/settings/tabs/plugins/LinkIconButton.css rename to src/components/PluginSettings/LinkIconButton.css index 97db0780..1055d6c7 100644 --- a/src/components/settings/tabs/plugins/LinkIconButton.css +++ b/src/components/PluginSettings/LinkIconButton.css @@ -2,11 +2,11 @@ height: 32px; width: 32px; border-radius: 50%; - border: 4px solid var(--background-base-lowest); + border: 4px solid var(--background-tertiary); box-sizing: border-box } .vc-settings-modal-links { display: flex; gap: 0.2em; -} \ No newline at end of file +} diff --git a/src/components/settings/tabs/plugins/LinkIconButton.tsx b/src/components/PluginSettings/LinkIconButton.tsx similarity index 95% rename from src/components/settings/tabs/plugins/LinkIconButton.tsx rename to src/components/PluginSettings/LinkIconButton.tsx index 9f9ecfeb..4dae0e1e 100644 --- a/src/components/settings/tabs/plugins/LinkIconButton.tsx +++ b/src/components/PluginSettings/LinkIconButton.tsx @@ -6,10 +6,11 @@ import "./LinkIconButton.css"; -import { GithubIcon, WebsiteIcon } from "@components/Icons"; import { getTheme, Theme } from "@utils/discord"; import { MaskedLink, Tooltip } from "@webpack/common"; +import { GithubIcon, WebsiteIcon } from ".."; + export function GithubLinkIcon() { const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF"; return ; diff --git a/src/components/settings/tabs/plugins/PluginModal.css b/src/components/PluginSettings/PluginModal.css similarity index 100% rename from src/components/settings/tabs/plugins/PluginModal.css rename to src/components/PluginSettings/PluginModal.css diff --git a/src/components/settings/tabs/plugins/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx similarity index 51% rename from src/components/settings/tabs/plugins/PluginModal.tsx rename to src/components/PluginSettings/PluginModal.tsx index 3332b1fc..ddbcf8b8 100644 --- a/src/components/settings/tabs/plugins/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -23,32 +23,41 @@ import { useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; -import { debounce } from "@shared/debounce"; import { gitRemote } from "@shared/vencordUserAgent"; import { proxyLazy } from "@utils/lazy"; import { Margins } from "@utils/margins"; -import { classes, isObjectEmpty } from "@utils/misc"; -import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { isObjectEmpty } from "@utils/misc"; +import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { OptionType, Plugin } from "@utils/types"; import { User } from "@vencord/discord-types"; -import { findByPropsLazy } from "@webpack"; -import { Clickable, FluxDispatcher, Forms, React, Text, Tooltip, useEffect, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; +import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common"; import { Constructor } from "type-fest"; import { PluginMeta } from "~plugins"; -import { OptionComponentMap } from "./components"; +import { + ISettingCustomElementProps, + ISettingElementProps, + SettingBooleanComponent, + SettingCustomComponent, + SettingNumericComponent, + SettingSelectComponent, + SettingSliderComponent, + SettingTextComponent +} from "./components"; import { openContributorModal } from "./ContributorModal"; import { GithubButton, WebsiteButton } from "./LinkIconButton"; const cl = classNameFactory("vc-plugin-modal-"); +const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); const UserRecord: Constructor> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any; interface PluginModalProps extends ModalProps { plugin: Plugin; - onRestartNeeded(key: string): void; + onRestartNeeded(): void; } function makeDummyUser(user: { username: string; id?: string; avatar?: string; }) { @@ -59,22 +68,39 @@ function makeDummyUser(user: { username: string; id?: string; avatar?: string; } /** To stop discord making unwanted requests... */ bot: true, }); - FluxDispatcher.dispatch({ type: "USER_UPDATE", user: newUser, }); - return newUser; } +const Components: Record | ISettingCustomElementProps>> = { + [OptionType.STRING]: SettingTextComponent, + [OptionType.NUMBER]: SettingNumericComponent, + [OptionType.BIGINT]: SettingNumericComponent, + [OptionType.BOOLEAN]: SettingBooleanComponent, + [OptionType.SELECT]: SettingSelectComponent, + [OptionType.SLIDER]: SettingSliderComponent, + [OptionType.COMPONENT]: SettingCustomComponent, + [OptionType.CUSTOM]: () => null, +}; + export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) { + const [authors, setAuthors] = React.useState[]>([]); + const pluginSettings = useSettings().plugins[plugin.name]; + + const [tempSettings, setTempSettings] = React.useState>({}); + + const [errors, setErrors] = React.useState>({}); + const [saveError, setSaveError] = React.useState(null); + + const canSubmit = () => Object.values(errors).every(e => !e); + const hasSettings = Boolean(pluginSettings && plugin.options && !isObjectEmpty(plugin.options)); - const [authors, setAuthors] = useState[]>([]); - - useEffect(() => { + React.useEffect(() => { (async () => { for (const user of plugin.authors.slice(0, 6)) { const author = user.id @@ -87,40 +113,63 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti })(); }, [plugin.authors]); - function renderSettings() { - if (!hasSettings || !plugin.options) - return There are no settings for this plugin.; + async function saveAndClose() { + if (!plugin.options) { + onClose(); + return; + } - const options = Object.entries(plugin.options).map(([key, setting]) => { - if (setting.type === OptionType.CUSTOM || setting.hidden) return null; - - function onChange(newValue: any) { - const option = plugin.options?.[key]; - if (!option || option.type === OptionType.CUSTOM) return; - - pluginSettings[key] = newValue; - - if (option.restartNeeded) onRestartNeeded(key); + if (plugin.beforeSave) { + const result = await Promise.resolve(plugin.beforeSave(tempSettings)); + if (result !== true) { + setSaveError(result); + return; } + } - const Component = OptionComponentMap[setting.type]; - return ( - - ); - }); + let restartNeeded = false; + for (const [key, value] of Object.entries(tempSettings)) { + const option = plugin.options[key]; + pluginSettings[key] = value; - return ( -
- {options} -
- ); + if (option.type === OptionType.CUSTOM) continue; + if (option?.restartNeeded) restartNeeded = true; + } + if (restartNeeded) onRestartNeeded(); + onClose(); + } + + function renderSettings() { + if (!hasSettings || !plugin.options) { + return There are no settings for this plugin.; + } else { + const options = Object.entries(plugin.options).map(([key, setting]) => { + if (setting.type === OptionType.CUSTOM || setting.hidden) return null; + + function onChange(newValue: any) { + setTempSettings(s => ({ ...s, [key]: newValue })); + } + + function onError(hasError: boolean) { + setErrors(e => ({ ...e, [key]: hasError })); + } + + const Component = Components[setting.type]; + return ( + + ); + }); + + return {options}; + } } function renderMoreUsers(_label: string, count: number) { @@ -143,16 +192,38 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti ); } + /* + function switchToPopout() { + onClose(); + + const PopoutKey = `DISCORD_VENCORD_PLUGIN_SETTINGS_MODAL_${plugin.name}`; + PopoutActions.open( + PopoutKey, + () => PopoutActions.close(PopoutKey)} + /> + ); + } + */ + const pluginMeta = PluginMeta[plugin.name]; return ( - - {plugin.name} + + {plugin.name} + + {/* + + */} - - + {plugin.description} @@ -169,14 +240,16 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
)}
- Authors -
+ Authors +
(
- {!!plugin.settingsAboutComponent && ( -
+
- +
)} - - - Settings + + Settings {renderSettings()} + {hasSettings && + + + + + {({ onMouseEnter, onMouseLeave }) => ( + + )} + + + {saveError && Error while saving: {saveError}} + + } ); } -export function openPluginModal(plugin: Plugin, onRestartNeeded?: (pluginName: string, key: string) => void) { +export function openPluginModal(plugin: Plugin, onRestartNeeded?: (pluginName: string) => void) { openModal(modalProps => ( onRestartNeeded?.(plugin.name, key)} + onRestartNeeded={() => onRestartNeeded?.(plugin.name)} /> )); } diff --git a/src/components/PluginSettings/components/SettingBooleanComponent.tsx b/src/components/PluginSettings/components/SettingBooleanComponent.tsx new file mode 100644 index 00000000..e5219e45 --- /dev/null +++ b/src/components/PluginSettings/components/SettingBooleanComponent.tsx @@ -0,0 +1,63 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { wordsFromCamel, wordsToTitle } from "@utils/text"; +import { PluginOptionBoolean } from "@utils/types"; +import { Forms, React, Switch } from "@webpack/common"; + +import { ISettingElementProps } from "."; + +export function SettingBooleanComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps) { + const def = pluginSettings[id] ?? option.default; + + const [state, setState] = React.useState(def ?? false); + const [error, setError] = React.useState(null); + + React.useEffect(() => { + onError(error !== null); + }, [error]); + + function handleChange(newValue: boolean): void { + const isValid = option.isValid?.call(definedSettings, newValue) ?? true; + if (typeof isValid === "string") setError(isValid); + else if (!isValid) setError("Invalid input provided."); + else { + setError(null); + setState(newValue); + onChange(newValue); + } + } + + return ( + + + {wordsToTitle(wordsFromCamel(id))} + + {error && {error}} + + ); +} + diff --git a/src/components/settings/tabs/plugins/components/ComponentSetting.tsx b/src/components/PluginSettings/components/SettingCustomComponent.tsx similarity index 76% rename from src/components/settings/tabs/plugins/components/ComponentSetting.tsx rename to src/components/PluginSettings/components/SettingCustomComponent.tsx index c43b7276..25e8c9c6 100644 --- a/src/components/settings/tabs/plugins/components/ComponentSetting.tsx +++ b/src/components/PluginSettings/components/SettingCustomComponent.tsx @@ -18,8 +18,8 @@ import { PluginOptionComponent } from "@utils/types"; -import { ComponentSettingProps } from "./Common"; +import { ISettingCustomElementProps } from "."; -export function ComponentSetting({ option, onChange }: ComponentSettingProps) { - return option.component({ setValue: onChange, option }); +export function SettingCustomComponent({ option, onChange, onError }: ISettingCustomElementProps) { + return option.component({ setValue: onChange, setError: onError, option }); } diff --git a/src/components/settings/tabs/plugins/components/NumberSetting.tsx b/src/components/PluginSettings/components/SettingNumericComponent.tsx similarity index 59% rename from src/components/settings/tabs/plugins/components/NumberSetting.tsx rename to src/components/PluginSettings/components/SettingNumericComponent.tsx index 108ddc3e..b724717d 100644 --- a/src/components/settings/tabs/plugins/components/NumberSetting.tsx +++ b/src/components/PluginSettings/components/SettingNumericComponent.tsx @@ -16,49 +16,58 @@ * along with this program. If not, see . */ +import { Margins } from "@utils/margins"; +import { wordsFromCamel, wordsToTitle } from "@utils/text"; import { OptionType, PluginOptionNumber } from "@utils/types"; -import { React, TextInput, useState } from "@webpack/common"; +import { Forms, React, TextInput } from "@webpack/common"; -import { resolveError, SettingProps, SettingsSection } from "./Common"; +import { ISettingElementProps } from "."; const MAX_SAFE_NUMBER = BigInt(Number.MAX_SAFE_INTEGER); -export function NumberSetting({ option, pluginSettings, definedSettings, id, onChange }: SettingProps) { +export function SettingNumericComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps) { function serialize(value: any) { if (option.type === OptionType.BIGINT) return BigInt(value); return Number(value); } - const [state, setState] = useState(`${pluginSettings[id] ?? option.default ?? 0}`); - const [error, setError] = useState(null); + const [state, setState] = React.useState(`${pluginSettings[id] ?? option.default ?? 0}`); + const [error, setError] = React.useState(null); - function handleChange(newValue: any) { + React.useEffect(() => { + onError(error !== null); + }, [error]); + + function handleChange(newValue) { const isValid = option.isValid?.call(definedSettings, newValue) ?? true; - setError(resolveError(isValid)); - - if (isValid === true) { - onChange(serialize(newValue)); - } + setError(null); + if (typeof isValid === "string") setError(isValid); + else if (!isValid) setError("Invalid input provided."); if (option.type === OptionType.NUMBER && BigInt(newValue) >= MAX_SAFE_NUMBER) { setState(`${Number.MAX_SAFE_INTEGER}`); + onChange(serialize(newValue)); } else { setState(newValue); + onChange(serialize(newValue)); } } return ( - + + {wordsToTitle(wordsFromCamel(id))} + {option.description} - + {error && {error}} + ); } diff --git a/src/components/settings/tabs/plugins/components/SelectSetting.tsx b/src/components/PluginSettings/components/SettingSelectComponent.tsx similarity index 56% rename from src/components/settings/tabs/plugins/components/SelectSetting.tsx rename to src/components/PluginSettings/components/SettingSelectComponent.tsx index 4c912c51..5f1cd027 100644 --- a/src/components/settings/tabs/plugins/components/SelectSetting.tsx +++ b/src/components/PluginSettings/components/SettingSelectComponent.tsx @@ -16,41 +16,50 @@ * along with this program. If not, see . */ +import { Margins } from "@utils/margins"; +import { wordsFromCamel, wordsToTitle } from "@utils/text"; import { PluginOptionSelect } from "@utils/types"; -import { React, Select, useState } from "@webpack/common"; +import { Forms, React, Select } from "@webpack/common"; -import { resolveError, SettingProps, SettingsSection } from "./Common"; +import { ISettingElementProps } from "."; -export function SelectSetting({ option, pluginSettings, definedSettings, onChange, id }: SettingProps) { +export function SettingSelectComponent({ option, pluginSettings, definedSettings, onChange, onError, id }: ISettingElementProps) { const def = pluginSettings[id] ?? option.options?.find(o => o.default)?.value; - const [state, setState] = useState(def ?? null); - const [error, setError] = useState(null); + const [state, setState] = React.useState(def ?? null); + const [error, setError] = React.useState(null); - function handleChange(newValue: any) { + React.useEffect(() => { + onError(error !== null); + }, [error]); + + function handleChange(newValue) { const isValid = option.isValid?.call(definedSettings, newValue) ?? true; - - setState(newValue); - setError(resolveError(isValid)); - - if (isValid === true) { + if (typeof isValid === "string") setError(isValid); + else if (!isValid) setError("Invalid input provided."); + else { + setError(null); + setState(newValue); onChange(newValue); } } return ( - + + {wordsToTitle(wordsFromCamel(id))} + {option.description}