From 7e92800c12e9161059f6dc650fc9818ddabdf98b Mon Sep 17 00:00:00 2001 From: youtsuho <110821381+youtsuhodev@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:53:36 +0200 Subject: [PATCH 1/8] feat: custom invite modal with shelter plugin (#1125) --- src/@types/legcordWindow.d.ts | 5 + src/discord/preload/mods/shelter.mts | 1 + src/discord/rpcProcess.ts | 3 +- src/discord/tray.ts | 3 +- src/discord/window.ts | 16 +- src/shelter/invite/InviteModal.module.css | 299 ++++++++++++++++++++++ src/shelter/invite/InviteModal.tsx | 223 ++++++++++++++++ src/shelter/invite/index.tsx | 22 ++ src/shelter/invite/plugin.json | 5 + 9 files changed, 571 insertions(+), 6 deletions(-) create mode 100644 src/shelter/invite/InviteModal.module.css create mode 100644 src/shelter/invite/InviteModal.tsx create mode 100644 src/shelter/invite/index.tsx create mode 100644 src/shelter/invite/plugin.json diff --git a/src/@types/legcordWindow.d.ts b/src/@types/legcordWindow.d.ts index 424c9e4..ac0ec2b 100644 --- a/src/@types/legcordWindow.d.ts +++ b/src/@types/legcordWindow.d.ts @@ -143,8 +143,13 @@ export interface LegcordRPC { }) => void; } +export interface LegcordInvite { + show: (code: string) => void; +} + declare global { interface Window { legcordRPC?: LegcordRPC; + legcordInvite?: LegcordInvite; } } diff --git a/src/discord/preload/mods/shelter.mts b/src/discord/preload/mods/shelter.mts index 90a2d54..50cc518 100644 --- a/src/discord/preload/mods/shelter.mts +++ b/src/discord/preload/mods/shelter.mts @@ -8,6 +8,7 @@ const requiredPlugins: Record { const json = JSON.parse(message); if (json.type === "invite") { - navigateTo(window, `/invite/${json.code}`); + window.webContents.executeJavaScript(`window.legcordInvite?.show(${JSON.stringify(json.code)})`); } else if (json.type === "activity") { console.log("activity pulse"); console.log(json.data); diff --git a/src/discord/tray.ts b/src/discord/tray.ts index ee3c861..33f483f 100644 --- a/src/discord/tray.ts +++ b/src/discord/tray.ts @@ -1,7 +1,6 @@ import { join } from "node:path"; import { app, Menu, nativeImage, Tray } from "electron"; import { getConfig } from "../common/config.js"; -import { navigateTo } from "../common/dom.js"; import { setForceQuit } from "../common/forceQuit.js"; import { getLang } from "../common/lang.js"; import { getDisplayVersion } from "../common/version.js"; @@ -60,7 +59,7 @@ export function createTray() { label: getLang("tray-supportServer"), click() { mainWindows.forEach((mainWindow) => { - navigateTo(mainWindow, "/invite/TnhxcqynZ2"); + mainWindow.webContents.executeJavaScript(`window.legcordInvite?.show(${JSON.stringify("TnhxcqynZ2")})`); }); }, }, diff --git a/src/discord/window.ts b/src/discord/window.ts index e95a9b9..834f1b3 100644 --- a/src/discord/window.ts +++ b/src/discord/window.ts @@ -173,7 +173,13 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void { console.log(commandLine); const lastArg = commandLine.pop(); if (lastArg?.startsWith("discord://-")) { - navigateTo(passedWindow, lastArg.replace("discord://-", "")); + const url = lastArg.replace("discord://-", ""); + const inviteMatch = url.match(/^\/invite\/([a-zA-Z0-9_-]+)/); + if (inviteMatch) { + passedWindow.webContents.executeJavaScript(`window.legcordInvite?.show(${JSON.stringify(inviteMatch[1])})`); + } else { + navigateTo(passedWindow, url); + } } } } else { @@ -309,7 +315,13 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void { passedWindow.setTouchBar(mainTouchBar); app.on("open-url", (_event, url) => { - navigateTo(passedWindow, url.replace("discord://-", "")); + const resolved = url.replace("discord://-", ""); + const inviteMatch = resolved.match(/^\/invite\/([a-zA-Z0-9_-]+)/); + if (inviteMatch) { + passedWindow.webContents.executeJavaScript(`window.legcordInvite?.show(${JSON.stringify(inviteMatch[1])})`); + } else { + navigateTo(passedWindow, resolved); + } }); passedWindow.webContents.on("page-title-updated", (e, title) => { diff --git a/src/shelter/invite/InviteModal.module.css b/src/shelter/invite/InviteModal.module.css new file mode 100644 index 0000000..aa8dc6b --- /dev/null +++ b/src/shelter/invite/InviteModal.module.css @@ -0,0 +1,299 @@ +.inviteContainer { + overflow: hidden; + border-radius: 0 0 4px 4px; +} + +.banner { + position: relative; + width: 100%; + height: 128px; + background: var(--background-tertiary); + overflow: hidden; +} + +.bannerImage { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.bannerGradient { + position: absolute; + inset: 0; + background: linear-gradient(180deg, transparent 60%, var(--background-primary) 100%); +} + +.bannerFallbackBg { + width: 100%; + height: 100%; +} + +.iconSection { + display: flex; + justify-content: center; + margin-top: -40px; + position: relative; + z-index: 1; +} + +.serverIcon { + width: 80px; + height: 80px; + border-radius: 20px; + border: 6px solid var(--background-primary); + background: var(--background-tertiary); + object-fit: cover; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); +} + +.serverIconPlaceholder { + width: 80px; + height: 80px; + border-radius: 20px; + border: 6px solid var(--background-primary); + background: var(--brand-experiment); + display: flex; + align-items: center; + justify-content: center; + font-size: 32px; + font-weight: 700; + color: var(--white); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); +} + +.body { + padding: 12px 24px 24px; + text-align: center; +} + +.serverName { + font-size: 22px; + font-weight: 700; + color: var(--header-primary); + margin-bottom: 4px; + word-break: break-word; + line-height: 1.3; +} + +.serverDescription { + font-size: 14px; + color: var(--text-muted); + margin-bottom: 16px; + line-height: 1.4; + word-break: break-word; +} + +.stats { + display: flex; + justify-content: center; + gap: 24px; + margin-bottom: 20px; +} + +.stat { + display: flex; + align-items: center; + gap: 6px; + font-size: 14px; + color: var(--text-muted); +} + +.statIcon { + width: 16px; + height: 16px; + color: var(--interactive-normal); + flex-shrink: 0; +} + +.statValue { + font-weight: 600; + color: var(--header-primary); +} + +.actions { + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; +} + +.joinButton { + width: 100%; + padding: 14px 24px; + background: var(--brand-experiment); + color: var(--white); + border: none; + border-radius: 8px; + font-size: 15px; + font-weight: 600; + cursor: pointer; + transition: background 0.15s ease; + line-height: 1; +} + +.joinButton:hover { + background: var(--brand-experiment-hover); +} + +.joinButton:active { + background: var(--brand-experiment-active); + transform: translateY(1px); +} + +.cancelButton { + width: 100%; + padding: 14px 24px; + background: transparent; + color: var(--interactive-normal); + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: color 0.15s ease, background 0.15s ease; + line-height: 1; +} + +.cancelButton:hover { + color: var(--interactive-hover); + background: var(--background-modifier-hover); +} + +.invitedBy { + font-size: 12px; + color: var(--text-muted); + margin-bottom: 16px; +} + +.invitedBy a { + color: var(--text-link); + text-decoration: none; + font-weight: 500; +} + +.invitedBy a:hover { + text-decoration: underline; +} + +.boostInfo { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--text-muted); + background: var(--background-secondary); + padding: 4px 10px; + border-radius: 12px; + margin-bottom: 16px; +} + +.boostInfo svg { + color: var(--guild-boosting-pink); +} + +/* Loading state */ +.loadingContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 16px; + padding: 80px 24px; + color: var(--text-muted); + font-size: 14px; + min-height: 200px; +} + +.spinner { + width: 36px; + height: 36px; + border: 3px solid var(--background-modifier-accent); + border-top-color: var(--interactive-active); + border-radius: 50%; + animation: spin 0.7s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.loadingPulse { + display: flex; + gap: 4px; + align-items: center; +} + +.loadingPulse span { + width: 6px; + height: 6px; + background: var(--text-muted); + border-radius: 50%; + animation: pulse 1.2s ease-in-out infinite; +} + +.loadingPulse span:nth-child(2) { + animation-delay: 0.2s; +} + +.loadingPulse span:nth-child(3) { + animation-delay: 0.4s; +} + +@keyframes pulse { + 0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); } + 40% { opacity: 1; transform: scale(1.2); } +} + +/* Error state */ +.errorContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 16px; + padding: 60px 24px; + min-height: 200px; +} + +.errorIcon { + width: 48px; + height: 48px; + border-radius: 50%; + background: var(--status-danger-background); + color: var(--status-danger); + display: flex; + align-items: center; + justify-content: center; +} + +.errorText { + font-size: 15px; + color: var(--text-normal); + text-align: center; + line-height: 1.4; +} + +.errorSubtext { + font-size: 13px; + color: var(--text-muted); + text-align: center; +} + +.retryButton { + margin-top: 8px; + padding: 10px 24px; + background: var(--background-secondary); + color: var(--interactive-normal); + border: 1px solid var(--background-modifier-accent); + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: background 0.15s ease; +} + +.retryButton:hover { + background: var(--background-secondary-alt); + color: var(--interactive-hover); +} diff --git a/src/shelter/invite/InviteModal.tsx b/src/shelter/invite/InviteModal.tsx new file mode 100644 index 0000000..becf88d --- /dev/null +++ b/src/shelter/invite/InviteModal.tsx @@ -0,0 +1,223 @@ +import { createSignal, onMount } from "solid-js"; +import classes from "./InviteModal.module.css"; + +const { + ui: { ModalRoot, ModalBody, ModalSizes }, +} = shelter; + +interface InviteInfo { + code: string; + guild?: { + id: string; + name: string; + icon: string | null; + banner: string | null; + description: string | null; + features: string[]; + vanity_url_code: string | null; + approximate_member_count: number; + approximate_presence_count: number; + }; + channel?: { + id: string; + name: string; + type: number; + }; + inviter?: { + id: string; + username: string; + discriminator: string; + avatar: string | null; + }; + expires_at: string | null; +} + +function navigateToInvite(code: string) { + const path = `/invite/${encodeURIComponent(code)}`; + history.pushState({}, null, path); + window.dispatchEvent(new PopStateEvent("popstate", {})); +} + +export const InviteModal = (props: { close: () => void; code: string }) => { + const [invite, setInvite] = createSignal(null); + const [loading, setLoading] = createSignal(true); + const [error, setError] = createSignal(null); + + const fetchInvite = async () => { + setLoading(true); + setError(null); + try { + const res = await fetch( + `https://discord.com/api/v10/invites/${encodeURIComponent(props.code)}?with_counts=true&with_expiration=true`, + { credentials: "include" }, + ); + if (!res.ok) { + if (res.status === 404) { + setError("This invite is invalid or has expired."); + } else { + setError(`Failed to load invite (${res.status})`); + } + setLoading(false); + return; + } + const data = (await res.json()) as InviteInfo; + setInvite(data); + setLoading(false); + } catch { + setError("Could not connect to Discord."); + setLoading(false); + } + }; + + onMount(fetchInvite); + + const handleJoin = () => { + navigateToInvite(props.code); + props.close(); + }; + + const guildName = () => invite()?.guild?.name ?? invite()?.channel?.name ?? "Unknown Server"; + + const guildIcon = () => { + const g = invite()?.guild; + if (!g?.icon || !g?.id) return null; + const ext = g.icon.startsWith("a_") ? "gif" : "png"; + return `https://cdn.discordapp.com/icons/${g.id}/${g.icon}.${ext}?size=160`; + }; + + const guildBanner = () => { + const g = invite()?.guild; + if (!g?.banner || !g?.id) return null; + return `https://cdn.discordapp.com/banners/${g.id}/${g.banner}.png?size=480`; + }; + + const memberCount = () => invite()?.guild?.approximate_member_count; + const presenceCount = () => invite()?.guild?.approximate_presence_count; + + const avatarUrl = () => { + const inv = invite()?.inviter; + if (!inv) return null; + if (inv.avatar) { + const ext = inv.avatar.startsWith("a_") ? "gif" : "png"; + return `https://cdn.discordapp.com/avatars/${inv.id}/${inv.avatar}.${ext}?size=40`; + } + const defaultIndex = Number(inv.discriminator) % 5; + return `https://cdn.discordapp.com/embed/avatars/${defaultIndex}.png`; + }; + + return ( + + +
+ {loading() && ( +
+
+ Loading invite... +
+ + + +
+
+ )} + {error() && ( +
+
+ + + + + +
+
{error()}
+
This invite link may be broken or expired.
+ +
+ )} + {invite() && !loading() && !error() && ( + <> +
+ {guildBanner() ? ( + + ) : ( +
+ )} +
+
+
+ {guildIcon() ? ( + + ) : ( +
+ {guildName().charAt(0).toUpperCase()} +
+ )} +
+
+
{guildName()}
+ {invite()?.guild?.description && ( +
{invite()?.guild?.description}
+ )} + {invite()?.guild?.features?.includes("BOOSTS_ENABLED") && ( +
+ + + + Boost Server +
+ )} + {(memberCount() !== undefined || presenceCount() !== undefined) && ( +
+ {memberCount() !== undefined && ( +
+ + + + + + + {memberCount()!.toLocaleString()} + Members +
+ )} + {presenceCount() !== undefined && ( +
+ + + + + {presenceCount()!.toLocaleString()} + Online +
+ )} +
+ )} + {invite()?.inviter && ( +
+ Invited by{" "} + + {invite()!.inviter!.username} + +
+ )} +
+ + +
+
+ + )} +
+ + + ); +}; diff --git a/src/shelter/invite/index.tsx b/src/shelter/invite/index.tsx new file mode 100644 index 0000000..50c9e87 --- /dev/null +++ b/src/shelter/invite/index.tsx @@ -0,0 +1,22 @@ +import { InviteModal } from "./InviteModal.jsx"; + +const { + ui: { openModal }, + util: { log }, +} = shelter; + +export function onLoad() { + log("Legcord Invite Handler"); + + window.legcordInvite = { + show: (code: string) => { + openModal(({ close }: { close: () => void }) => ( + + )); + }, + }; +} + +export function onUnload() { + window.legcordInvite = undefined; +} diff --git a/src/shelter/invite/plugin.json b/src/shelter/invite/plugin.json new file mode 100644 index 0000000..85e8d61 --- /dev/null +++ b/src/shelter/invite/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "Legcord Invites", + "author": "Legcord", + "description": "Custom invite handling for Discord invite URLs" +} From 4b8394f196ea637e609c36c574bfedff64f21be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20=C5=BBurek?= <37928912+smartfrigde@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:54:29 +0200 Subject: [PATCH 2/8] Revert "feat: custom invite modal with shelter plugin" (#1126) --- src/@types/legcordWindow.d.ts | 5 - src/discord/preload/mods/shelter.mts | 1 - src/discord/rpcProcess.ts | 3 +- src/discord/tray.ts | 3 +- src/discord/window.ts | 16 +- src/shelter/invite/InviteModal.module.css | 299 ---------------------- src/shelter/invite/InviteModal.tsx | 223 ---------------- src/shelter/invite/index.tsx | 22 -- src/shelter/invite/plugin.json | 5 - 9 files changed, 6 insertions(+), 571 deletions(-) delete mode 100644 src/shelter/invite/InviteModal.module.css delete mode 100644 src/shelter/invite/InviteModal.tsx delete mode 100644 src/shelter/invite/index.tsx delete mode 100644 src/shelter/invite/plugin.json diff --git a/src/@types/legcordWindow.d.ts b/src/@types/legcordWindow.d.ts index ac0ec2b..424c9e4 100644 --- a/src/@types/legcordWindow.d.ts +++ b/src/@types/legcordWindow.d.ts @@ -143,13 +143,8 @@ export interface LegcordRPC { }) => void; } -export interface LegcordInvite { - show: (code: string) => void; -} - declare global { interface Window { legcordRPC?: LegcordRPC; - legcordInvite?: LegcordInvite; } } diff --git a/src/discord/preload/mods/shelter.mts b/src/discord/preload/mods/shelter.mts index 50cc518..90a2d54 100644 --- a/src/discord/preload/mods/shelter.mts +++ b/src/discord/preload/mods/shelter.mts @@ -8,7 +8,6 @@ const requiredPlugins: Record { const json = JSON.parse(message); if (json.type === "invite") { - window.webContents.executeJavaScript(`window.legcordInvite?.show(${JSON.stringify(json.code)})`); + navigateTo(window, `/invite/${json.code}`); } else if (json.type === "activity") { console.log("activity pulse"); console.log(json.data); diff --git a/src/discord/tray.ts b/src/discord/tray.ts index 33f483f..ee3c861 100644 --- a/src/discord/tray.ts +++ b/src/discord/tray.ts @@ -1,6 +1,7 @@ import { join } from "node:path"; import { app, Menu, nativeImage, Tray } from "electron"; import { getConfig } from "../common/config.js"; +import { navigateTo } from "../common/dom.js"; import { setForceQuit } from "../common/forceQuit.js"; import { getLang } from "../common/lang.js"; import { getDisplayVersion } from "../common/version.js"; @@ -59,7 +60,7 @@ export function createTray() { label: getLang("tray-supportServer"), click() { mainWindows.forEach((mainWindow) => { - mainWindow.webContents.executeJavaScript(`window.legcordInvite?.show(${JSON.stringify("TnhxcqynZ2")})`); + navigateTo(mainWindow, "/invite/TnhxcqynZ2"); }); }, }, diff --git a/src/discord/window.ts b/src/discord/window.ts index 834f1b3..e95a9b9 100644 --- a/src/discord/window.ts +++ b/src/discord/window.ts @@ -173,13 +173,7 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void { console.log(commandLine); const lastArg = commandLine.pop(); if (lastArg?.startsWith("discord://-")) { - const url = lastArg.replace("discord://-", ""); - const inviteMatch = url.match(/^\/invite\/([a-zA-Z0-9_-]+)/); - if (inviteMatch) { - passedWindow.webContents.executeJavaScript(`window.legcordInvite?.show(${JSON.stringify(inviteMatch[1])})`); - } else { - navigateTo(passedWindow, url); - } + navigateTo(passedWindow, lastArg.replace("discord://-", "")); } } } else { @@ -315,13 +309,7 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void { passedWindow.setTouchBar(mainTouchBar); app.on("open-url", (_event, url) => { - const resolved = url.replace("discord://-", ""); - const inviteMatch = resolved.match(/^\/invite\/([a-zA-Z0-9_-]+)/); - if (inviteMatch) { - passedWindow.webContents.executeJavaScript(`window.legcordInvite?.show(${JSON.stringify(inviteMatch[1])})`); - } else { - navigateTo(passedWindow, resolved); - } + navigateTo(passedWindow, url.replace("discord://-", "")); }); passedWindow.webContents.on("page-title-updated", (e, title) => { diff --git a/src/shelter/invite/InviteModal.module.css b/src/shelter/invite/InviteModal.module.css deleted file mode 100644 index aa8dc6b..0000000 --- a/src/shelter/invite/InviteModal.module.css +++ /dev/null @@ -1,299 +0,0 @@ -.inviteContainer { - overflow: hidden; - border-radius: 0 0 4px 4px; -} - -.banner { - position: relative; - width: 100%; - height: 128px; - background: var(--background-tertiary); - overflow: hidden; -} - -.bannerImage { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} - -.bannerGradient { - position: absolute; - inset: 0; - background: linear-gradient(180deg, transparent 60%, var(--background-primary) 100%); -} - -.bannerFallbackBg { - width: 100%; - height: 100%; -} - -.iconSection { - display: flex; - justify-content: center; - margin-top: -40px; - position: relative; - z-index: 1; -} - -.serverIcon { - width: 80px; - height: 80px; - border-radius: 20px; - border: 6px solid var(--background-primary); - background: var(--background-tertiary); - object-fit: cover; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); -} - -.serverIconPlaceholder { - width: 80px; - height: 80px; - border-radius: 20px; - border: 6px solid var(--background-primary); - background: var(--brand-experiment); - display: flex; - align-items: center; - justify-content: center; - font-size: 32px; - font-weight: 700; - color: var(--white); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); -} - -.body { - padding: 12px 24px 24px; - text-align: center; -} - -.serverName { - font-size: 22px; - font-weight: 700; - color: var(--header-primary); - margin-bottom: 4px; - word-break: break-word; - line-height: 1.3; -} - -.serverDescription { - font-size: 14px; - color: var(--text-muted); - margin-bottom: 16px; - line-height: 1.4; - word-break: break-word; -} - -.stats { - display: flex; - justify-content: center; - gap: 24px; - margin-bottom: 20px; -} - -.stat { - display: flex; - align-items: center; - gap: 6px; - font-size: 14px; - color: var(--text-muted); -} - -.statIcon { - width: 16px; - height: 16px; - color: var(--interactive-normal); - flex-shrink: 0; -} - -.statValue { - font-weight: 600; - color: var(--header-primary); -} - -.actions { - display: flex; - flex-direction: column; - gap: 8px; - width: 100%; -} - -.joinButton { - width: 100%; - padding: 14px 24px; - background: var(--brand-experiment); - color: var(--white); - border: none; - border-radius: 8px; - font-size: 15px; - font-weight: 600; - cursor: pointer; - transition: background 0.15s ease; - line-height: 1; -} - -.joinButton:hover { - background: var(--brand-experiment-hover); -} - -.joinButton:active { - background: var(--brand-experiment-active); - transform: translateY(1px); -} - -.cancelButton { - width: 100%; - padding: 14px 24px; - background: transparent; - color: var(--interactive-normal); - border: none; - border-radius: 8px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: color 0.15s ease, background 0.15s ease; - line-height: 1; -} - -.cancelButton:hover { - color: var(--interactive-hover); - background: var(--background-modifier-hover); -} - -.invitedBy { - font-size: 12px; - color: var(--text-muted); - margin-bottom: 16px; -} - -.invitedBy a { - color: var(--text-link); - text-decoration: none; - font-weight: 500; -} - -.invitedBy a:hover { - text-decoration: underline; -} - -.boostInfo { - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 12px; - color: var(--text-muted); - background: var(--background-secondary); - padding: 4px 10px; - border-radius: 12px; - margin-bottom: 16px; -} - -.boostInfo svg { - color: var(--guild-boosting-pink); -} - -/* Loading state */ -.loadingContainer { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 16px; - padding: 80px 24px; - color: var(--text-muted); - font-size: 14px; - min-height: 200px; -} - -.spinner { - width: 36px; - height: 36px; - border: 3px solid var(--background-modifier-accent); - border-top-color: var(--interactive-active); - border-radius: 50%; - animation: spin 0.7s linear infinite; -} - -@keyframes spin { - to { transform: rotate(360deg); } -} - -.loadingPulse { - display: flex; - gap: 4px; - align-items: center; -} - -.loadingPulse span { - width: 6px; - height: 6px; - background: var(--text-muted); - border-radius: 50%; - animation: pulse 1.2s ease-in-out infinite; -} - -.loadingPulse span:nth-child(2) { - animation-delay: 0.2s; -} - -.loadingPulse span:nth-child(3) { - animation-delay: 0.4s; -} - -@keyframes pulse { - 0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); } - 40% { opacity: 1; transform: scale(1.2); } -} - -/* Error state */ -.errorContainer { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 16px; - padding: 60px 24px; - min-height: 200px; -} - -.errorIcon { - width: 48px; - height: 48px; - border-radius: 50%; - background: var(--status-danger-background); - color: var(--status-danger); - display: flex; - align-items: center; - justify-content: center; -} - -.errorText { - font-size: 15px; - color: var(--text-normal); - text-align: center; - line-height: 1.4; -} - -.errorSubtext { - font-size: 13px; - color: var(--text-muted); - text-align: center; -} - -.retryButton { - margin-top: 8px; - padding: 10px 24px; - background: var(--background-secondary); - color: var(--interactive-normal); - border: 1px solid var(--background-modifier-accent); - border-radius: 8px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: background 0.15s ease; -} - -.retryButton:hover { - background: var(--background-secondary-alt); - color: var(--interactive-hover); -} diff --git a/src/shelter/invite/InviteModal.tsx b/src/shelter/invite/InviteModal.tsx deleted file mode 100644 index becf88d..0000000 --- a/src/shelter/invite/InviteModal.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import { createSignal, onMount } from "solid-js"; -import classes from "./InviteModal.module.css"; - -const { - ui: { ModalRoot, ModalBody, ModalSizes }, -} = shelter; - -interface InviteInfo { - code: string; - guild?: { - id: string; - name: string; - icon: string | null; - banner: string | null; - description: string | null; - features: string[]; - vanity_url_code: string | null; - approximate_member_count: number; - approximate_presence_count: number; - }; - channel?: { - id: string; - name: string; - type: number; - }; - inviter?: { - id: string; - username: string; - discriminator: string; - avatar: string | null; - }; - expires_at: string | null; -} - -function navigateToInvite(code: string) { - const path = `/invite/${encodeURIComponent(code)}`; - history.pushState({}, null, path); - window.dispatchEvent(new PopStateEvent("popstate", {})); -} - -export const InviteModal = (props: { close: () => void; code: string }) => { - const [invite, setInvite] = createSignal(null); - const [loading, setLoading] = createSignal(true); - const [error, setError] = createSignal(null); - - const fetchInvite = async () => { - setLoading(true); - setError(null); - try { - const res = await fetch( - `https://discord.com/api/v10/invites/${encodeURIComponent(props.code)}?with_counts=true&with_expiration=true`, - { credentials: "include" }, - ); - if (!res.ok) { - if (res.status === 404) { - setError("This invite is invalid or has expired."); - } else { - setError(`Failed to load invite (${res.status})`); - } - setLoading(false); - return; - } - const data = (await res.json()) as InviteInfo; - setInvite(data); - setLoading(false); - } catch { - setError("Could not connect to Discord."); - setLoading(false); - } - }; - - onMount(fetchInvite); - - const handleJoin = () => { - navigateToInvite(props.code); - props.close(); - }; - - const guildName = () => invite()?.guild?.name ?? invite()?.channel?.name ?? "Unknown Server"; - - const guildIcon = () => { - const g = invite()?.guild; - if (!g?.icon || !g?.id) return null; - const ext = g.icon.startsWith("a_") ? "gif" : "png"; - return `https://cdn.discordapp.com/icons/${g.id}/${g.icon}.${ext}?size=160`; - }; - - const guildBanner = () => { - const g = invite()?.guild; - if (!g?.banner || !g?.id) return null; - return `https://cdn.discordapp.com/banners/${g.id}/${g.banner}.png?size=480`; - }; - - const memberCount = () => invite()?.guild?.approximate_member_count; - const presenceCount = () => invite()?.guild?.approximate_presence_count; - - const avatarUrl = () => { - const inv = invite()?.inviter; - if (!inv) return null; - if (inv.avatar) { - const ext = inv.avatar.startsWith("a_") ? "gif" : "png"; - return `https://cdn.discordapp.com/avatars/${inv.id}/${inv.avatar}.${ext}?size=40`; - } - const defaultIndex = Number(inv.discriminator) % 5; - return `https://cdn.discordapp.com/embed/avatars/${defaultIndex}.png`; - }; - - return ( - - -
- {loading() && ( -
-
- Loading invite... -
- - - -
-
- )} - {error() && ( -
-
- - - - - -
-
{error()}
-
This invite link may be broken or expired.
- -
- )} - {invite() && !loading() && !error() && ( - <> -
- {guildBanner() ? ( - - ) : ( -
- )} -
-
-
- {guildIcon() ? ( - - ) : ( -
- {guildName().charAt(0).toUpperCase()} -
- )} -
-
-
{guildName()}
- {invite()?.guild?.description && ( -
{invite()?.guild?.description}
- )} - {invite()?.guild?.features?.includes("BOOSTS_ENABLED") && ( -
- - - - Boost Server -
- )} - {(memberCount() !== undefined || presenceCount() !== undefined) && ( -
- {memberCount() !== undefined && ( -
- - - - - - - {memberCount()!.toLocaleString()} - Members -
- )} - {presenceCount() !== undefined && ( -
- - - - - {presenceCount()!.toLocaleString()} - Online -
- )} -
- )} - {invite()?.inviter && ( -
- Invited by{" "} - - {invite()!.inviter!.username} - -
- )} -
- - -
-
- - )} -
- - - ); -}; diff --git a/src/shelter/invite/index.tsx b/src/shelter/invite/index.tsx deleted file mode 100644 index 50c9e87..0000000 --- a/src/shelter/invite/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { InviteModal } from "./InviteModal.jsx"; - -const { - ui: { openModal }, - util: { log }, -} = shelter; - -export function onLoad() { - log("Legcord Invite Handler"); - - window.legcordInvite = { - show: (code: string) => { - openModal(({ close }: { close: () => void }) => ( - - )); - }, - }; -} - -export function onUnload() { - window.legcordInvite = undefined; -} diff --git a/src/shelter/invite/plugin.json b/src/shelter/invite/plugin.json deleted file mode 100644 index 85e8d61..0000000 --- a/src/shelter/invite/plugin.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Legcord Invites", - "author": "Legcord", - "description": "Custom invite handling for Discord invite URLs" -} From 0d80100e44dc94636cc110bbb5be1883492b9435 Mon Sep 17 00:00:00 2001 From: youtsuho <110821381+youtsuhodev@users.noreply.github.com> Date: Sun, 19 Jul 2026 19:03:28 +0200 Subject: [PATCH 3/8] ci: fix biome lint errors and apply formatting (#1117) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 530b752..8eb7431 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,6 @@ "optionalDependencies": { "@vencord/venmic": "^6.1.0" }, - "packageManager": "pnpm@11.4.0+sha512.f0febc7e37552ab485494a914241b338e0b3580b93d54ce31f00933015880863129038a1b4ae4e414a0ee63ac35bf21197e990172c4a68256450b5636310968f", + "packageManager": "pnpm@11.10.0", "package-manager-strict": false } From 526d6dcce8202cf52fceafa918b2dbc4a63f1098 Mon Sep 17 00:00:00 2001 From: youtsuho <110821381+youtsuhodev@users.noreply.github.com> Date: Sun, 19 Jul 2026 19:03:43 +0200 Subject: [PATCH 4/8] perf: cache theme list with fs.watch to avoid readdirSync (#1118) --- src/@types/legcordWindow.d.ts | 1 + src/common/themes.ts | 149 +++++++++++++++++++--- src/discord/ipc.ts | 30 +++-- src/discord/preload/bridge.ts | 1 + src/shelter/settings/pages/ThemesPage.tsx | 8 ++ 5 files changed, 158 insertions(+), 31 deletions(-) diff --git a/src/@types/legcordWindow.d.ts b/src/@types/legcordWindow.d.ts index 424c9e4..9cb9ebd 100644 --- a/src/@types/legcordWindow.d.ts +++ b/src/@types/legcordWindow.d.ts @@ -78,6 +78,7 @@ export interface LegcordWindow { uninstall: (id: string) => void; set: (id: string, state: boolean) => void; getThemes: () => Readonly; + refresh: () => Readonly; openQuickCss: () => void; edit: (id: string) => void; folder: (id: string) => void; diff --git a/src/common/themes.ts b/src/common/themes.ts index 68be9db..8a79dce 100644 --- a/src/common/themes.ts +++ b/src/common/themes.ts @@ -112,6 +112,111 @@ function getThemeManifest(themeId: string): ThemeManifest | null { } } +// Performance optimization: Cached theme list with directory watcher +const THEME_LIST_CACHE_TTL = 2000; +let themeListCache: ThemeManifest[] | null = null; +let themeListCacheTime = 0; +let themeWatcher: fs.FSWatcher | null = null; +let themeListRefreshTimeout: NodeJS.Timeout | null = null; + +function importLooseThemeFiles(): void { + try { + if (!fs.existsSync(themesFolder)) return; + const entries = fs.readdirSync(themesFolder); + for (const entry of entries) { + const entryPath = path.join(themesFolder, entry); + const stat = fs.statSync(entryPath); + if (stat.isFile() && (entry.endsWith(".css") || entry.endsWith(".theme.css"))) { + const code = fs.readFileSync(entryPath, "utf8"); + installThemeFromCode(code); + try { + fs.unlinkSync(entryPath); + } catch {} + } + } + } catch (err) { + console.error("[Theme Manager] Failed to import loose theme files:", err); + } +} + +function refreshThemeListCache(): ThemeManifest[] { + try { + if (!fs.existsSync(themesFolder)) { + themeListCache = []; + themeListCacheTime = Date.now(); + return themeListCache; + } + const themes: ThemeManifest[] = []; + const entries = fs.readdirSync(themesFolder); + for (const entry of entries) { + const manifestPath = path.join(themesFolder, entry, "manifest.json"); + if (fs.existsSync(manifestPath)) { + const manifest = getThemeManifest(entry); + if (manifest) { + themes.push({ ...manifest, id: entry }); + } + } + } + themeListCache = themes; + themeListCacheTime = Date.now(); + return themes; + } catch (err) { + console.error("[Theme Manager] Failed to refresh theme list cache:", err); + return themeListCache ?? []; + } +} + +export function getCachedThemeList(): ThemeManifest[] { + const now = Date.now(); + if (themeListCache && now - themeListCacheTime < THEME_LIST_CACHE_TTL) { + return themeListCache; + } + importLooseThemeFiles(); + return refreshThemeListCache(); +} + +export function invalidateThemeListCache(): void { + themeListCache = null; +} + +function debouncedRefreshThemeList(): void { + if (themeListRefreshTimeout) { + clearTimeout(themeListRefreshTimeout); + } + themeListRefreshTimeout = setTimeout(() => { + refreshThemeListCache(); + themeListRefreshTimeout = null; + }, 500); +} + +export function startThemeWatcher(): void { + if (themeWatcher) return; + try { + if (!fs.existsSync(themesFolder)) { + fs.mkdirSync(themesFolder, { recursive: true }); + } + themeWatcher = fs.watch(themesFolder, { recursive: true }, (eventType, filename) => { + if (filename && (filename.endsWith("manifest.json") || eventType === "rename")) { + debouncedRefreshThemeList(); + } + }); + refreshThemeListCache(); + } catch (err) { + console.error("[Theme Manager] Failed to start theme watcher:", err); + } +} + +export function stopThemeWatcher(): void { + if (themeWatcher) { + themeWatcher.close(); + themeWatcher = null; + } + if (themeListRefreshTimeout) { + clearTimeout(themeListRefreshTimeout); + themeListRefreshTimeout = null; + } +} + export function injectThemesMain(browserWindow: BrowserWindow): void { if (process.argv.includes("--safe-mode")) return; if (!fs.existsSync(themesFolder)) { @@ -123,11 +228,13 @@ export function injectThemesMain(browserWindow: BrowserWindow): void { const files = fs.readdirSync(themesFolder); for (const file of files) { const themePath = path.join(themesFolder, file); - if (fs.statSync(themePath).isFile() && file.endsWith(".DS_Store")) { + if (fs.statSync(themePath).isFile() && (file.endsWith(".css") || file.endsWith(".theme.css"))) { console.log(`[Theme Manager] Local theme detected: ${themePath}`); - installTheme(themePath).then(() => { + const code = fs.readFileSync(themePath, "utf8"); + installThemeFromCode(code); + try { fs.unlinkSync(themePath); - }); + } catch {} } else { try { const themeFile = getThemeManifest(file); @@ -168,10 +275,11 @@ export function uninstallTheme(id: string) { fs.rmdirSync(path.join(themesFolder, `${id}-BD`), { recursive: true }); console.log(`Removed ${id} folder`); } + themeManifestCache.delete(id); + invalidateThemeListCache(); } export function setThemeEnabled(id: string, enabled: boolean) { - // Performance optimization: Use cached manifest if available let manifest = getThemeManifest(id); if (!manifest) { manifest = JSON.parse(fs.readFileSync(path.join(themesFolder, id, "/manifest.json"), "utf8")) as ThemeManifest; @@ -196,8 +304,25 @@ export function setThemeEnabled(id: string, enabled: boolean) { manifest.enabled = enabled; fs.writeFileSync(`${themesFolder}/${id}/manifest.json`, JSON.stringify(manifest)); - // Performance optimization: Invalidate cache themeManifestCache.delete(id); + invalidateThemeListCache(); +} + +function installThemeFromCode(code: string, linkOrPath?: string): void { + const manifest = parseBDManifest(code); + const themePath = path.join(themesFolder, `${manifest.name?.replace(" ", "-")}-BD`); + if (!fs.existsSync(themePath)) { + fs.mkdirSync(themePath); + console.log(`Created ${manifest.name} folder`); + } + if (linkOrPath && manifest.updateSrc === undefined) { + manifest.updateSrc = linkOrPath; + } + if (code.includes(".titlebar")) manifest.supportsLegcordTitlebar = true; + else manifest.supportsLegcordTitlebar = false; + fs.writeFileSync(path.join(themePath, "manifest.json"), JSON.stringify(manifest)); + fs.writeFileSync(path.join(themePath, "src.css"), code); + invalidateThemeListCache(); } export async function installTheme(linkOrPath: string) { @@ -209,19 +334,7 @@ export async function installTheme(linkOrPath: string) { } else { code = fs.readFileSync(linkOrPath, "utf8"); } - const manifest = parseBDManifest(code); - const themePath = path.join(themesFolder, `${manifest.name?.replace(" ", "-")}-BD`); - if (!fs.existsSync(themePath)) { - fs.mkdirSync(themePath); - console.log(`Created ${manifest.name} folder`); - } - if (isLinkImport && manifest.updateSrc === undefined) { - manifest.updateSrc = linkOrPath; - } - if (code.includes(".titlebar")) manifest.supportsLegcordTitlebar = true; - else manifest.supportsLegcordTitlebar = false; - fs.writeFileSync(path.join(themePath, "manifest.json"), JSON.stringify(manifest)); - fs.writeFileSync(path.join(themePath, "src.css"), code); + installThemeFromCode(code, isLinkImport ? linkOrPath : undefined); } export function initQuickCss(browserWindow: BrowserWindow) { diff --git a/src/discord/ipc.ts b/src/discord/ipc.ts index de5c1c9..127e36d 100644 --- a/src/discord/ipc.ts +++ b/src/discord/ipc.ts @@ -1,4 +1,4 @@ -import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import type { Game } from "arrpc"; @@ -21,7 +21,16 @@ import { import { getConfig, getConfigLocation, setConfig, setConfigBulk } from "../common/config.js"; import { addDetectable, getDetectables, removeDetectable } from "../common/detectables.js"; import { getLang, getLangName, getRawLang, setLang } from "../common/lang.js"; -import { disableQuickCss, initQuickCss, installTheme, setThemeEnabled, uninstallTheme } from "../common/themes.js"; +import { + disableQuickCss, + getCachedThemeList, + initQuickCss, + installTheme, + invalidateThemeListCache, + setThemeEnabled, + startThemeWatcher, + uninstallTheme, +} from "../common/themes.js"; import { getDisplayVersion, getVersion } from "../common/version.js"; import { openCssEditor } from "../cssEditor/main.js"; import { getAppliedFlags, handleRestart } from "../main.js"; @@ -65,6 +74,7 @@ let ipcRegistered = false; export function registerIpc(passedWindow: BrowserWindow): void { if (ipcRegistered) return; ipcRegistered = true; + startThemeWatcher(); ipcMain.handle("getShelterBundle", () => { return { js: ifExistsRead(path.join(app.getPath("userData"), "shelter.js")), @@ -169,17 +179,11 @@ export function registerIpc(passedWindow: BrowserWindow): void { }); ipcMain.on("getThemes", (event) => { - const themes = []; - const themeFolders = readdirSync(themesPath); - for (const folder of themeFolders) { - if (existsSync(`${themesPath}/${folder}/manifest.json`)) { - const manifest = JSON.parse( - readFileSync(`${themesPath}/${folder}/manifest.json`, "utf8"), - ) as ThemeManifest; - themes.push({ ...manifest, id: folder }); - } - } - event.returnValue = themes; + event.returnValue = getCachedThemeList(); + }); + ipcMain.on("refreshThemesCache", (event) => { + invalidateThemeListCache(); + event.returnValue = getCachedThemeList(); }); ipcMain.on("splashEnd", () => { diff --git a/src/discord/preload/bridge.ts b/src/discord/preload/bridge.ts index 81c492a..09bad96 100644 --- a/src/discord/preload/bridge.ts +++ b/src/discord/preload/bridge.ts @@ -102,6 +102,7 @@ contextBridge.exposeInMainWorld("legcord", { uninstall: (id: string) => ipcRenderer.send("uninstallTheme", id), edit: (id: string) => ipcRenderer.send("editTheme", id), getThemes: () => ipcRenderer.sendSync("getThemes") as ThemeManifest[], + refresh: () => ipcRenderer.sendSync("refreshThemesCache") as ThemeManifest[], openImportPicker: () => ipcRenderer.send("openImportPicker"), set: (id: string, state: boolean) => ipcRenderer.send("setThemeEnabled", id, state), folder: (id: string) => ipcRenderer.send("openThemeFolder", id), diff --git a/src/shelter/settings/pages/ThemesPage.tsx b/src/shelter/settings/pages/ThemesPage.tsx index f5c328c..ad6fc6e 100644 --- a/src/shelter/settings/pages/ThemesPage.tsx +++ b/src/shelter/settings/pages/ThemesPage.tsx @@ -60,6 +60,14 @@ export function ThemesPage() { +
Date: Sun, 19 Jul 2026 19:05:19 +0200 Subject: [PATCH 5/8] refactor(themes): redesigned themes page UI with modern card layout (#1108) --- assets/lang/en-US.json | 1 + .../settings/components/ThemesCard.module.css | 102 +++++++++++++----- .../settings/components/ThemesCard.tsx | 59 ++++++---- src/shelter/settings/pages/ThemesPage.tsx | 4 +- .../settings/pages/ThemesPages.module.css | 26 +++-- 5 files changed, 134 insertions(+), 58 deletions(-) diff --git a/assets/lang/en-US.json b/assets/lang/en-US.json index 6948db9..530ce6d 100644 --- a/assets/lang/en-US.json +++ b/assets/lang/en-US.json @@ -293,6 +293,7 @@ "themes-openThemesFolder": "Open themes folder", "themes-import": "Import", "themes-importUrlPlaceholder": "https://raw.githubusercontent.com/... [.theme.css]", + "themes-installed": "Installed Themes", "games-registeredGames": "Registered Games", "games-refreshList": "Refresh list", "games-add": "Add", diff --git a/src/shelter/settings/components/ThemesCard.module.css b/src/shelter/settings/components/ThemesCard.module.css index e66ac8d..ba3f19e 100644 --- a/src/shelter/settings/components/ThemesCard.module.css +++ b/src/shelter/settings/components/ThemesCard.module.css @@ -1,45 +1,95 @@ .card { - border: 1px solid var(--background-accent); - border-radius: 4px; - padding: 0.5rem; - margin-top: 0.5rem; - flex-wrap: wrap; + border: 1px solid var(--background-modifier-accent); + border-radius: 8px; + padding: 1rem; + margin-top: 0.75rem; + background: var(--background-secondary); + transition: + box-shadow 0.2s ease, + border-color 0.2s ease; } -.switch { - margin-left: auto; +.card:hover { + border-color: var(--background-modifier-selected); + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); +} + +.topRow { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; } .info { - align-self: flex-start; + flex: 1; + min-width: 0; } -.eyebrow { - margin-bottom: 0px; - color: var(--text-strong); +.title { + margin: 0; + word-break: break-word; } + +.author { + font-size: 0.85rem; + color: var(--text-muted); + margin-top: 0.15rem; +} + +.description { + margin-top: 0.5rem; + font-size: 0.9rem; + color: var(--text-normal); + line-height: 1.4; +} + +.actions { + display: flex; + gap: 0.25rem; + margin-top: 0.75rem; + padding-top: 0.75rem; + border-top: 1px solid var(--background-modifier-accent); +} + .btn { + display: inline-flex; + align-items: center; + justify-content: center; border: none; - background: none; - color: var(--interactive-text-active); + background: transparent; + width: 32px; + height: 32px; + border-radius: 6px; + cursor: pointer; + color: var(--interactive-normal); + transition: + background 0.15s ease, + color 0.15s ease; +} - transition: opacity 250ms; +.btn:hover { + background: var(--background-modifier-hover); + color: var(--interactive-hover); +} - &:hover { - color: var(--interactive-text-hover); - } +.btnDanger:hover { + background: rgba(218, 55, 60, 0.15); + color: #da373c; } .icon { - height: 20px; - margin-top: 2px; + height: 18px; + width: 18px; + filter: brightness(0) invert(0.7); + pointer-events: none; +} + +.btn:hover .icon { filter: brightness(0) invert(1); } -.mainInfo { - flex-wrap: wrap; - align-items: baseline; - justify-content: center; - gap: 5px; - margin-bottom: -0.5em; - display: flex; + +.btnDanger:hover .icon { + filter: brightness(0) saturate(1) invert(27%) sepia(82%) saturate(3000%) hue-rotate(346deg) brightness(94%) + contrast(90%); } diff --git a/src/shelter/settings/components/ThemesCard.tsx b/src/shelter/settings/components/ThemesCard.tsx index 803bf69..862062b 100644 --- a/src/shelter/settings/components/ThemesCard.tsx +++ b/src/shelter/settings/components/ThemesCard.tsx @@ -1,4 +1,4 @@ -import { createSignal } from "solid-js"; +import { createSignal, Show } from "solid-js"; import type { ThemeManifest } from "../../../@types/themeManifest.js"; import { refreshThemes } from "../settings.js"; import classes from "./ThemesCard.module.css"; @@ -14,7 +14,7 @@ export const ThemesCard = (props: { theme: ThemeManifest }) => { function toggleTheme(state: boolean) { setSwitchState(state); if (props.theme.id) { - window.legcord.themes.set(props.theme.id, switchState()); + window.legcord.themes.set(props.theme.id, state); } refreshThemes(); } @@ -47,35 +47,48 @@ export const ThemesCard = (props: { theme: ThemeManifest }) => { window.legcord.themes.folder(props.theme.id); } } + const showUpdate = !!props.theme.updateSrc; return (
-
-
+
+
{props.theme.name}
-
{store.i18n["themes-by"]}
-
- {props.theme.author} -
-
- +
+ {store.i18n["themes-by"]} {props.theme.author}
-
{props.theme.description}
+ +
+ +
{props.theme.description}
+
+
+ + + + + +
- - - -
); }; diff --git a/src/shelter/settings/pages/ThemesPage.tsx b/src/shelter/settings/pages/ThemesPage.tsx index ad6fc6e..b311791 100644 --- a/src/shelter/settings/pages/ThemesPage.tsx +++ b/src/shelter/settings/pages/ThemesPage.tsx @@ -46,7 +46,7 @@ export function ThemesPage() { > {store.i18n["settings-quickCss"]} -
+
+
+
{t["themes-installed"]}
{(theme: ThemeManifest) => } ); diff --git a/src/shelter/settings/pages/ThemesPages.module.css b/src/shelter/settings/pages/ThemesPages.module.css index 01d2c98..e10bc40 100644 --- a/src/shelter/settings/pages/ThemesPages.module.css +++ b/src/shelter/settings/pages/ThemesPages.module.css @@ -1,13 +1,23 @@ -.buttonBox { +.toolbar { display: flex; - justify-content: space-between; - border: 1px solid var(--background-modifier-accent); - padding: 3px; - border-radius: 6px; + gap: 0.5rem; + flex-wrap: wrap; + margin-top: 0.75rem; } + .addBox { display: flex; - justify-content: space-between; - padding: 3px; - margin-top: 1rem; + gap: 0.5rem; + margin-top: 0.5rem; + align-items: center; +} + +.addBox > :first-child { + flex: 1; +} + +.divider { + margin: 1.5rem 0 0.5rem; + border: none; + border-top: 1px solid var(--background-modifier-accent); } From 1bedb9e43157ddc18031aaf9a5c23ea2461d178c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 19:05:33 +0200 Subject: [PATCH 6/8] chore(deps): bump adm-zip from 0.5.17 to 0.6.0 in the npm_and_yarn group across 1 directory (#1124) Signed-off-by: dependabot[bot] --- package.json | 4 +- pnpm-lock.yaml | 107 +++++++++++++++++++++++++++---------------------- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index 8eb7431..abb27dc 100644 --- a/package.json +++ b/package.json @@ -48,13 +48,13 @@ "lucide-solid": "^1.16.0", "rolldown": "1.0.3", "rollup-plugin-copy": "^3.5.0", - "solid-js": "^1.9.13", + "solid-js": "^1.9.14", "solid-motionone": "^1.0.4", "typescript": "^6.0.3", "xml-formatter": "^3.7.0" }, "dependencies": { - "adm-zip": "^0.5.17", + "adm-zip": "^0.6.0", "arrpc": "github:Legcord/arrpc#efe7589762470d32b9ba10d529be5acc23cd0e19", "electron-context-menu": "^4.1.2", "electron-is-dev": "^3.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c64720b..fc12a63 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: .: dependencies: adm-zip: - specifier: ^0.5.17 - version: 0.5.17 + specifier: ^0.6.0 + version: 0.6.0 arrpc: specifier: github:Legcord/arrpc#efe7589762470d32b9ba10d529be5acc23cd0e19 version: https://codeload.github.com/Legcord/arrpc/tar.gz/efe7589762470d32b9ba10d529be5acc23cd0e19 @@ -44,7 +44,7 @@ importers: version: 2.4.16 '@rolldown-plugin/solid': specifier: ^0.0.4 - version: 0.0.4(csstype@3.2.3)(rolldown@1.0.3)(seroval@1.5.4)(solid-js@1.9.13) + version: 0.0.4(csstype@3.2.3)(rolldown@1.0.3)(seroval@1.5.5)(solid-js@1.9.14) '@rollup/plugin-esm-shim': specifier: ^0.1.8 version: 0.1.8 @@ -71,7 +71,7 @@ importers: version: 26.8.1(dmg-builder@26.8.1)(electron-builder-squirrel-windows@26.8.1) babel-preset-solid: specifier: ^1.9.12 - version: 1.9.12(@babel/core@8.0.0-rc.6)(solid-js@1.9.13) + version: 1.9.12(@babel/core@8.0.0-rc.6)(solid-js@1.9.14) electron: specifier: 42.3.3 version: 42.3.3 @@ -80,7 +80,7 @@ importers: version: 26.8.1(electron-builder-squirrel-windows@26.8.1) lucide-solid: specifier: ^1.16.0 - version: 1.16.0(solid-js@1.9.13) + version: 1.16.0(solid-js@1.9.14) rolldown: specifier: 1.0.3 version: 1.0.3 @@ -88,11 +88,11 @@ importers: specifier: ^3.5.0 version: 3.5.0 solid-js: - specifier: ^1.9.13 - version: 1.9.13 + specifier: ^1.9.14 + version: 1.9.14 solid-motionone: specifier: ^1.0.4 - version: 1.0.4(solid-js@1.9.13) + version: 1.0.4(solid-js@1.9.14) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -1007,9 +1007,9 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - adm-zip@0.5.17: - resolution: {integrity: sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==} - engines: {node: '>=12.0'} + adm-zip@0.6.0: + resolution: {integrity: sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==} + engines: {node: '>=14.0'} agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} @@ -1073,7 +1073,7 @@ packages: engines: {node: '>=8'} arrpc@https://codeload.github.com/Legcord/arrpc/tar.gz/efe7589762470d32b9ba10d529be5acc23cd0e19: - resolution: {gitHosted: true, tarball: https://codeload.github.com/Legcord/arrpc/tar.gz/efe7589762470d32b9ba10d529be5acc23cd0e19} + resolution: {gitHosted: true, integrity: sha512-oxxPz88l9/VDvmgkepTGlhstBkt2kAO4SqxUjRu+pql4mQ2GSagDCjsYv4Lg3KnuazbuqRkl0K5KzOUzdBBJZQ==, tarball: https://codeload.github.com/Legcord/arrpc/tar.gz/efe7589762470d32b9ba10d529be5acc23cd0e19} version: 3.5.0 hasBin: true @@ -1557,6 +1557,10 @@ packages: resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==} engines: {node: '>=14.14'} + fs-extra@11.3.6: + resolution: {integrity: sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==} + engines: {node: '>=14.14'} + fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -2342,14 +2346,14 @@ packages: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} engines: {node: '>=10'} - seroval-plugins@1.5.4: - resolution: {integrity: sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==} + seroval-plugins@1.5.5: + resolution: {integrity: sha512-+BDhqYM6CEn3x09v44dpa9p6974FuUB2dxk+Ctn04k0cO1Zt6QODTXfmEZK0eBaTe/fJBvP4NMGuNJ+R8T+QMg==} engines: {node: '>=10'} peerDependencies: seroval: ^1.0 - seroval@1.5.4: - resolution: {integrity: sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==} + seroval@1.5.5: + resolution: {integrity: sha512-bSjOuPcwPKLSJNhr9+bZxA20nQxVle5J5MNsYRVE6cIg7KpRLXGupymePavu0jrxlPiPsr4xGZSB8yUY2sH2sw==} engines: {node: '>=10'} set-blocking@2.0.0: @@ -2389,8 +2393,8 @@ packages: solid-js@1.6.16: resolution: {integrity: sha512-Ng4CahvLlpGA3BXIMiiMdwhI8WpObZ8gXqm97GCKR4+MpnODs6Pdpco+tmVCY/4ZDFaEKDxz7WRLAAdoXdlY7w==} - solid-js@1.9.13: - resolution: {integrity: sha512-6hJeJMOcEX8ktqjpDoJZEmld3ijvcvWBDtiXBm7f4332SiFN66QeAQI1REQshvyUoISsSeJ4PHDauKYbwao9JQ==} + solid-js@1.9.14: + resolution: {integrity: sha512-sAEXC0Kk0S1EDg+8ysEWJDbYhA3RRoEjwuySUGlKIemeo0I5YZfOyumNjNs9Sv3y2nmhD+0rW66ag2HsMuQiGQ==} solid-motionone@1.0.4: resolution: {integrity: sha512-aqEjgecoO9raDFznu/dEci7ORSmA26Kjj9J4Cn1Gyr0GZuOVdvsNxdxClTL9J40Aq/uYFx4GLwC8n70fMLHiuA==} @@ -3071,7 +3075,7 @@ snapshots: dependencies: cross-dirname: 0.1.0 debug: 4.4.3 - fs-extra: 11.3.5 + fs-extra: 11.3.6 minimist: 1.2.8 postject: 1.0.0-alpha.6 transitivePeerDependencies: @@ -3320,7 +3324,7 @@ snapshots: '@parcel/watcher-win32-x64': 2.5.6 optional: true - '@rolldown-plugin/solid@0.0.4(csstype@3.2.3)(rolldown@1.0.3)(seroval@1.5.4)(solid-js@1.9.13)': + '@rolldown-plugin/solid@0.0.4(csstype@3.2.3)(rolldown@1.0.3)(seroval@1.5.5)(solid-js@1.9.14)': dependencies: '@babel/core': 8.0.0-rc.6 '@babel/helper-module-imports': 8.0.0-rc.6 @@ -3333,8 +3337,8 @@ snapshots: html-entities: 2.6.0 parse5: 8.0.1 rolldown: 1.0.3 - seroval: 1.5.4 - solid-js: 1.9.13 + seroval: 1.5.5 + solid-js: 1.9.14 '@rolldown/binding-android-arm64@1.0.3': optional: true @@ -3432,23 +3436,23 @@ snapshots: '@sindresorhus/is@4.6.0': {} - '@solid-primitives/props@3.2.3(solid-js@1.9.13)': + '@solid-primitives/props@3.2.3(solid-js@1.9.14)': dependencies: - '@solid-primitives/utils': 6.4.0(solid-js@1.9.13) - solid-js: 1.9.13 + '@solid-primitives/utils': 6.4.0(solid-js@1.9.14) + solid-js: 1.9.14 - '@solid-primitives/refs@1.1.3(solid-js@1.9.13)': + '@solid-primitives/refs@1.1.3(solid-js@1.9.14)': dependencies: - '@solid-primitives/utils': 6.4.0(solid-js@1.9.13) - solid-js: 1.9.13 + '@solid-primitives/utils': 6.4.0(solid-js@1.9.14) + solid-js: 1.9.14 - '@solid-primitives/transition-group@1.1.2(solid-js@1.9.13)': + '@solid-primitives/transition-group@1.1.2(solid-js@1.9.14)': dependencies: - solid-js: 1.9.13 + solid-js: 1.9.14 - '@solid-primitives/utils@6.4.0(solid-js@1.9.13)': + '@solid-primitives/utils@6.4.0(solid-js@1.9.14)': dependencies: - solid-js: 1.9.13 + solid-js: 1.9.14 '@szmarczak/http-timer@4.0.6': dependencies: @@ -3575,7 +3579,7 @@ snapshots: acorn@8.16.0: {} - adm-zip@0.5.17: {} + adm-zip@0.6.0: {} agent-base@6.0.2: dependencies: @@ -3712,12 +3716,12 @@ snapshots: html-entities: 2.3.3 parse5: 7.3.0 - babel-preset-solid@1.9.12(@babel/core@8.0.0-rc.6)(solid-js@1.9.13): + babel-preset-solid@1.9.12(@babel/core@8.0.0-rc.6)(solid-js@1.9.14): dependencies: '@babel/core': 8.0.0-rc.6 babel-plugin-jsx-dom-expressions: 0.40.7(@babel/core@8.0.0-rc.6) optionalDependencies: - solid-js: 1.9.13 + solid-js: 1.9.14 balanced-match@1.0.2: {} @@ -4260,6 +4264,13 @@ snapshots: jsonfile: 6.2.1 universalify: 2.0.1 + fs-extra@11.3.6: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + optional: true + fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -4634,9 +4645,9 @@ snapshots: lru-cache@7.18.3: {} - lucide-solid@1.16.0(solid-js@1.9.13): + lucide-solid@1.16.0(solid-js@1.9.14): dependencies: - solid-js: 1.9.13 + solid-js: 1.9.14 magic-string@0.30.21: dependencies: @@ -5037,11 +5048,11 @@ snapshots: type-fest: 0.13.1 optional: true - seroval-plugins@1.5.4(seroval@1.5.4): + seroval-plugins@1.5.5(seroval@1.5.5): dependencies: - seroval: 1.5.4 + seroval: 1.5.5 - seroval@1.5.4: {} + seroval@1.5.5: {} set-blocking@2.0.0: optional: true @@ -5079,21 +5090,21 @@ snapshots: dependencies: csstype: 3.2.3 - solid-js@1.9.13: + solid-js@1.9.14: dependencies: csstype: 3.2.3 - seroval: 1.5.4 - seroval-plugins: 1.5.4(seroval@1.5.4) + seroval: 1.5.5 + seroval-plugins: 1.5.5(seroval@1.5.5) - solid-motionone@1.0.4(solid-js@1.9.13): + solid-motionone@1.0.4(solid-js@1.9.14): dependencies: '@motionone/dom': 10.18.0 '@motionone/utils': 10.18.0 - '@solid-primitives/props': 3.2.3(solid-js@1.9.13) - '@solid-primitives/refs': 1.1.3(solid-js@1.9.13) - '@solid-primitives/transition-group': 1.1.2(solid-js@1.9.13) + '@solid-primitives/props': 3.2.3(solid-js@1.9.14) + '@solid-primitives/refs': 1.1.3(solid-js@1.9.14) + '@solid-primitives/transition-group': 1.1.2(solid-js@1.9.14) csstype: 3.2.3 - solid-js: 1.9.13 + solid-js: 1.9.14 sort-keys-length@1.0.1: dependencies: From 5963a451cad6e8eab0c76cf3bac0020570ba09c3 Mon Sep 17 00:00:00 2001 From: smartfrigde <37928912+smartfrigde@users.noreply.github.com> Date: Sun, 19 Jul 2026 21:15:40 +0200 Subject: [PATCH 7/8] fix: type check --- package.json | 2 +- pnpm-lock.yaml | 60 +++++++++---------------------------------- src/discord/window.ts | 7 ++++- 3 files changed, 19 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index abb27dc..6e47de8 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@xmldom/xmldom": "^0.9.10", "app-builder-lib": "^26.8.1", "babel-preset-solid": "^1.9.12", - "electron": "42.3.3", + "electron": "43.1.1", "electron-builder": "26.8.1", "lucide-solid": "^1.16.0", "rolldown": "1.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc12a63..1de1012 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,8 +73,8 @@ importers: specifier: ^1.9.12 version: 1.9.12(@babel/core@8.0.0-rc.6)(solid-js@1.9.14) electron: - specifier: 42.3.3 - version: 42.3.3 + specifier: 43.1.1 + version: 43.1.1 electron-builder: specifier: 26.8.1 version: 26.8.1(electron-builder-squirrel-windows@26.8.1) @@ -370,6 +370,10 @@ packages: resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==} engines: {node: '>= 8.9.0'} + '@electron-internal/extract-zip@1.0.4': + resolution: {integrity: sha512-Zr1Vs7E9tpCNhZHDAbFVXc2gEVCG9RqPDjrno5+bdgB6LRAuvgyMHJut4NCVyYwtAieapMzc3fiQ3CSTi75ARg==} + engines: {node: '>=22.12.0'} + '@electron/asar@3.4.1': resolution: {integrity: sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==} engines: {node: '>=10.12.0'} @@ -975,9 +979,6 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@types/yauzl@2.10.3': - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@uwu/lune@1.6.2': resolution: {integrity: sha512-0/e1zFGm7pBISyuNvhKMqdg4gEQOpBtf8wdquJaUDqA0enrV55iwMWrrCdJHlfx77K1D8hjnAUGvCXxwFebg5Q==} hasBin: true @@ -1158,9 +1159,6 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - buffer-crc32@0.2.13: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -1410,8 +1408,8 @@ packages: resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==} engines: {node: '>=8.0.0'} - electron@42.3.3: - resolution: {integrity: sha512-0MwYp9wTb7TrtTalOYqeW+suqd9T/Znstr/nDLKqFGIjHdBZX339guo3mQqTPURRZ/UQmYM4uMpzKpI5wLptfQ==} + electron@43.1.1: + resolution: {integrity: sha512-I5c5vfuVvaXpWx3IZdwvXgxQW44+e7OP1wXGVQkogLeSFSkUZ6sLCcWV05AdEcs65AO5tAIJJwbp7ixw+LdarA==} engines: {node: '>= 22.12.0'} hasBin: true @@ -1494,11 +1492,6 @@ packages: resolution: {integrity: sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==} engines: {node: '>=4'} - extract-zip@2.0.1: - resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} - engines: {node: '>= 10.17.0'} - hasBin: true - extsprintf@1.4.1: resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==} engines: {'0': node >=0.6.0} @@ -2154,9 +2147,6 @@ packages: resolution: {integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==} engines: {node: '>=12', npm: '>=6'} - pend@1.2.0: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2650,10 +2640,6 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} - yauzl@3.3.1: - resolution: {integrity: sha512-RNPCUkiE/ZgO4w8i9U5yDQVHaFDdnzaFANElRvpJteCspvmv2VqrRb9lvS6odVD+jqI/zDsxAHJVsafpcheVQQ==} - engines: {node: '>=12'} - yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2990,6 +2976,8 @@ snapshots: ajv: 6.15.0 ajv-keywords: 3.5.2(ajv@6.15.0) + '@electron-internal/extract-zip@1.0.4': {} + '@electron/asar@3.4.1': dependencies: commander: 5.1.0 @@ -3532,11 +3520,6 @@ snapshots: dependencies: '@types/node': 25.9.1 - '@types/yauzl@2.10.3': - dependencies: - '@types/node': 25.9.1 - optional: true - '@uwu/lune@1.6.2': dependencies: '@babel/core': 7.29.7 @@ -3761,8 +3744,6 @@ snapshots: node-releases: 2.0.46 update-browserslist-db: 1.2.3(browserslist@4.28.2) - buffer-crc32@0.2.13: {} - buffer-from@1.1.2: {} buffer@5.7.1: @@ -4107,11 +4088,11 @@ snapshots: transitivePeerDependencies: - supports-color - electron@42.3.3: + electron@43.1.1: dependencies: + '@electron-internal/extract-zip': 1.0.4 '@electron/get': 5.0.0 '@types/node': 24.12.4 - extract-zip: 2.0.1 transitivePeerDependencies: - supports-color @@ -4196,16 +4177,6 @@ snapshots: ext-list: 2.2.2 sort-keys-length: 1.0.1 - extract-zip@2.0.1: - dependencies: - debug: 4.4.3 - get-stream: 5.2.0 - yauzl: 3.3.1 - optionalDependencies: - '@types/yauzl': 2.10.3 - transitivePeerDependencies: - - supports-color - extsprintf@1.4.1: optional: true @@ -4830,8 +4801,6 @@ snapshots: pe-library@0.4.1: {} - pend@1.2.0: {} - picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -5337,11 +5306,6 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yauzl@3.3.1: - dependencies: - buffer-crc32: 0.2.13 - pend: 1.2.0 - yocto-queue@0.1.0: {} zod@3.25.76: {} diff --git a/src/discord/window.ts b/src/discord/window.ts index e95a9b9..2d8b0d2 100644 --- a/src/discord/window.ts +++ b/src/discord/window.ts @@ -550,7 +550,12 @@ export function createWindow() { const mainWindow = new BrowserWindow(browserWindowOptions); // Restore by position + size directly to match saveWindowState roundtrip. - if (storedBounds.x !== undefined && storedBounds.y !== undefined) { + if ( + storedBounds.x !== undefined && + storedBounds.y !== undefined && + storedBounds.width !== undefined && + storedBounds.height !== undefined + ) { mainWindow.setPosition(storedBounds.x, storedBounds.y); mainWindow.setSize(storedBounds.width, storedBounds.height); } From a934aa50b11916ce42fb027ddc47306b3362a82c Mon Sep 17 00:00:00 2001 From: smartfrigde <37928912+smartfrigde@users.noreply.github.com> Date: Sun, 19 Jul 2026 22:34:30 +0200 Subject: [PATCH 8/8] feat: add showExperimentalPluginMenu toggle --- assets/lang/en-US.json | 2 ++ src/@types/settings.d.ts | 2 ++ src/common/config.ts | 2 ++ src/shelter/settings/index.ts | 7 +++++-- src/shelter/settings/pages/SettingsPage.tsx | 7 +++++++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/assets/lang/en-US.json b/assets/lang/en-US.json index 530ce6d..59fce9a 100644 --- a/assets/lang/en-US.json +++ b/assets/lang/en-US.json @@ -64,6 +64,8 @@ "settings-csp-strict": "Strict", "settings-csp-none": "None", "settings-csp-vanilla": "Vanilla", + "settings-showExperimentalPluginMenu": "Show experimental plugin menu", + "settings-showExperimentalPluginMenu-desc": "Show the experimental plugin menu in settings. This is a temporary setting until the plugin system is fully implemented.", "settings-firstTimeCrash": "We're setting things up for you!", "settings-firstTimeCrash-desc": "Settings are not available on a first-time launch. Please use a button below to restart and settings should be ready after the restart.", "settings-useSystemCssEditor": "Use system CSS editor", diff --git a/src/@types/settings.d.ts b/src/@types/settings.d.ts index bc09c56..46923ae 100644 --- a/src/@types/settings.d.ts +++ b/src/@types/settings.d.ts @@ -77,4 +77,6 @@ export interface Settings { extendedPluginAbilities: boolean; supportBannerDismissed: boolean; pluginStates?: Record; + // Remove below once the plugin system is fully implemented. + showExperimentalPluginMenu: boolean; } diff --git a/src/common/config.ts b/src/common/config.ts index f2791e3..06c5031 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -67,6 +67,7 @@ const defaults: Settings = { extendedPluginAbilities: false, quickCss: true, supportBannerDismissed: false, + showExperimentalPluginMenu: false, pluginStates: {}, }; @@ -80,6 +81,7 @@ const safeMode: Settings = { vaapi: false, additionalArguments: "", extendedPluginAbilities: false, + showExperimentalPluginMenu: false, quickCss: false, }; diff --git a/src/shelter/settings/index.ts b/src/shelter/settings/index.ts index 90e89fe..9d605ea 100644 --- a/src/shelter/settings/index.ts +++ b/src/shelter/settings/index.ts @@ -25,15 +25,18 @@ const { let settingsCleanups: (() => void)[] = []; function registerSections(): (() => void)[] { - return [ + const array = [ registerSection("divider"), registerSection("header", "Legcord"), registerSection("section", "legcord-settings", "Settings", SettingsPage, { icon: SettingsSidebarIcon }), registerSection("section", "legcord-themes", "Themes", ThemesPage, { icon: ThemesSidebarIcon }), - registerSection("section", "legcord-plugins", "Plugins", PluginsPage, { icon: PluginsSidebarIcon }), registerSection("section", "legcord-keybinds", "Keybinds", KeybindsPage, { icon: KeybindsSidebarIcon }), registerSection("section", "legcord-games", "Games", RegisteredGamesPage, { icon: GamesSidebarIcon }), ]; + if (window.legcord.settings.getConfig().showExperimentalPluginMenu) { + array.push(registerSection("section", "legcord-plugins", "Plugins", PluginsPage, { icon: PluginsSidebarIcon })); + } + return array; } function restartRequired(payload: { event: string; properties: { origin_pane: string } }) { diff --git a/src/shelter/settings/pages/SettingsPage.tsx b/src/shelter/settings/pages/SettingsPage.tsx index 92285ad..9386d75 100644 --- a/src/shelter/settings/pages/SettingsPage.tsx +++ b/src/shelter/settings/pages/SettingsPage.tsx @@ -383,6 +383,13 @@ export function SettingsPage() {
{store.i18n["settings-category-debug"]}
+ setConfig("showExperimentalPluginMenu", e, true)} + > + {store.i18n["settings-showExperimentalPluginMenu"]} +