fix some random destructoring

This commit is contained in:
thororen 2026-09-24 16:59:57 -04:00
parent c95f667229
commit 4f5935be9b
No known key found for this signature in database
6 changed files with 175 additions and 196 deletions

View file

@ -227,14 +227,12 @@ function buildTooltipContent(el: Element, computed: CSSStyleDeclaration, rect: D
html += `<div class="${cl("color")}"><span class="${cl("swatch")}" style="--c:${computed.color}"></span>${colorVar}</div>`;
html += `<div class="${cl("hex")}">${hex}</div>`;
const { store } = settings;
if (store.showId) {
if (settings.store.showId) {
const { id } = el;
if (id) html += `<div class="${cl("info")}"><span class="${cl("label")}">id:</span> #${id}</div>`;
}
if (store.showClasses) {
if (settings.store.showClasses) {
const classes = el.className;
if (classes && typeof classes === "string") {
const truncated = classes.length > 60 ? classes.slice(0, 60) + "…" : classes;
@ -242,28 +240,28 @@ function buildTooltipContent(el: Element, computed: CSSStyleDeclaration, rect: D
}
}
if (store.showFont) {
if (settings.store.showFont) {
const font = computed.fontFamily.split(",")[0].replace(/["']/g, "");
const size = computed.fontSize;
html += `<div class="${cl("info")}"><span class="${cl("label")}">font:</span> ${font} ${size}</div>`;
}
if (store.showPadding) {
if (settings.store.showPadding) {
const padding = formatBoxValue(computed.padding);
if (padding) html += `<div class="${cl("info")}"><span class="${cl("label")}">padding:</span> ${padding}</div>`;
}
if (store.showMargin) {
if (settings.store.showMargin) {
const margin = formatBoxValue(computed.margin);
if (margin) html += `<div class="${cl("info")}"><span class="${cl("label")}">margin:</span> ${margin}</div>`;
}
if (store.showBorderRadius) {
if (settings.store.showBorderRadius) {
const radius = formatBoxValue(computed.borderRadius);
if (radius) html += `<div class="${cl("info")}"><span class="${cl("label")}">radius:</span> ${radius}</div>`;
}
if (store.showPosition) {
if (settings.store.showPosition) {
const pos = computed.position;
if (pos !== "static") {
const zIndex = computed.zIndex !== "auto" ? ` z:${computed.zIndex}` : "";
@ -271,7 +269,7 @@ function buildTooltipContent(el: Element, computed: CSSStyleDeclaration, rect: D
}
}
if (store.showDisplay) {
if (settings.store.showDisplay) {
const { display } = computed;
let extra = "";
if (display === "flex" || display === "inline-flex") {

View file

@ -449,10 +449,9 @@ function ServicePicker(props: {
function FallbackOrderSettings() {
const update = useForceUpdater();
const { store } = settings;
const [dragIndex, setDragIndex] = React.useState<number | null>(null);
const [order, setOrder] = React.useState<ServiceType[]>(() => {
const configured = (store.fallbackOrder || defaultFallbackOrder)
const configured = (settings.store.fallbackOrder || defaultFallbackOrder)
.split(/[\n,]/)
.map(entry => entry.trim())
.filter((entry): entry is ServiceType => Object.values(ServiceType).includes(entry as ServiceType));
@ -464,7 +463,7 @@ function FallbackOrderSettings() {
const commitOrder = (nextOrder: ServiceType[]) => {
setOrder(nextOrder);
store.fallbackOrder = nextOrder.join(",");
settings.store.fallbackOrder = nextOrder.join(",");
update();
};
@ -515,24 +514,12 @@ function FallbackOrderSettings() {
export function SettingsComponent() {
const update = useForceUpdater();
const { store } = settings;
const sharexFileInputRef = React.useRef<HTMLInputElement>(null);
const isNest = store.serviceType === ServiceType.NEST;
const isEzHost = store.serviceType === ServiceType.EZHOST;
const isEncryptingHost = store.serviceType === ServiceType.ENCRYPTINGHOST;
const isS3 = store.serviceType === ServiceType.S3;
const isZipline = store.serviceType === ServiceType.ZIPLINE;
const isCatbox = store.serviceType === ServiceType.CATBOX;
const isLitterbox = store.serviceType === ServiceType.LITTERBOX;
const isGofile = store.serviceType === ServiceType.GOFILE;
const isPixelVault = store.serviceType === ServiceType.PIXELVAULT;
const isPixelDrain = store.serviceType === ServiceType.PIXELDRAIN;
const isShareX = store.serviceType === ServiceType.SHAREX;
const isWebdav = store.serviceType === ServiceType.WEBDAV;
const { serviceType } = settings.store;
const validateShareXConfig = () => {
try {
parseShareXConfig(store.sharexConfig || "");
parseShareXConfig(settings.store.sharexConfig || "");
showToast("ShareX config is valid", Toasts.Type.SUCCESS);
} catch (error) {
const message = error instanceof Error ? error.message : "Invalid ShareX config";
@ -553,7 +540,7 @@ export function SettingsComponent() {
try {
const content = String(e.target?.result || "");
const parsed = parseShareXConfig(content);
store.sharexConfig = JSON.stringify(parsed, null, 2);
settings.store.sharexConfig = JSON.stringify(parsed, null, 2);
update();
showToast("Imported ShareX config", Toasts.Type.SUCCESS);
} catch (error) {
@ -569,79 +556,79 @@ export function SettingsComponent() {
<>
<SettingsSection id="upload-service" name="Upload Service" description="Choose where FileUpload sends new files.">
<ServicePicker
value={store.serviceType as ServiceType}
value={settings.store.serviceType as ServiceType}
onChange={service => {
store.serviceType = service;
settings.store.serviceType = service;
update();
}}
/>
</SettingsSection>
{isZipline && (
{serviceType === ServiceType.ZIPLINE && (
<SettingGroup name="Zipline" description="Connection details for your Zipline instance.">
<SettingTextInput
name="Service URL"
description="The URL of your Zipline instance"
value={store.serviceUrl}
onChange={v => store.serviceUrl = v}
value={settings.store.serviceUrl}
onChange={v => settings.store.serviceUrl = v}
placeholder="https://your-zipline-instance.com"
/>
<SettingTextInput
name="Zipline Token"
description="Your Zipline API authorization token"
value={store.ziplineToken}
onChange={v => store.ziplineToken = v}
value={settings.store.ziplineToken}
onChange={v => settings.store.ziplineToken = v}
placeholder="Your Zipline API token"
/>
<SettingTextInput
name="Folder ID"
description="Folder ID for uploads (leave empty for no folder)"
value={store.folderId}
onChange={v => store.folderId = v}
value={settings.store.folderId}
onChange={v => settings.store.folderId = v}
placeholder="Leave empty for no folder"
/>
</SettingGroup>
)}
{isEzHost && (
{serviceType === ServiceType.EZHOST && (
<SettingGroup name="E-Z Host" description="Connection details for E-Z Host uploads.">
<SettingTextInput
name="E-Z Host API Key"
description="Your E-Z Host API key"
value={store.ezHostKey}
onChange={v => store.ezHostKey = v}
value={settings.store.ezHostKey}
onChange={v => settings.store.ezHostKey = v}
placeholder="Your E-Z Host API key"
/>
</SettingGroup>
)}
{isNest && (
{serviceType === ServiceType.NEST && (
<SettingGroup name="Nest" description="Connection details for Nest uploads.">
<SettingTextInput
name="Nest Token"
description="Your Nest API authorization token"
value={store.nestToken}
onChange={v => store.nestToken = v}
value={settings.store.nestToken}
onChange={v => settings.store.nestToken = v}
placeholder="Your Nest API token"
/>
</SettingGroup>
)}
{isEncryptingHost && (
{serviceType === ServiceType.ENCRYPTINGHOST && (
<SettingGroup name="Encrypting.host" description="Connection details for Encrypting.host uploads.">
<SettingTextInput
name="Encrypting.host API Key"
description="Your Encrypting.host API key"
value={(store as { encryptingHostKey?: string; }).encryptingHostKey || ""}
onChange={v => (store as { encryptingHostKey?: string; }).encryptingHostKey = v}
value={(settings.store as { encryptingHostKey?: string; }).encryptingHostKey || ""}
onChange={v => (settings.store as { encryptingHostKey?: string; }).encryptingHostKey = v}
placeholder="Your Encrypting.host API key"
/>
<SettingsSection id="url-style" name="URL Style" description="How Encrypting.host should format returned links.">
<Select
options={encryptingHostUrlStyleOptions}
isSelected={v => v === (store as { encryptingHostUrlStyle?: string; }).encryptingHostUrlStyle}
isSelected={v => v === (settings.store as { encryptingHostUrlStyle?: string; }).encryptingHostUrlStyle}
select={v => {
(store as { encryptingHostUrlStyle?: string; }).encryptingHostUrlStyle = v;
(settings.store as { encryptingHostUrlStyle?: string; }).encryptingHostUrlStyle = v;
update();
}}
serialize={v => v}
@ -650,122 +637,122 @@ export function SettingsComponent() {
</SettingsSection>
<SettingsSection id="domains-json" name="Domains JSON" description={"JSON array of domains to use, for example [\"offensive\"]."}>
<TextArea
value={(store as { encryptingHostDomains?: string; }).encryptingHostDomains || ""}
value={(settings.store as { encryptingHostDomains?: string; }).encryptingHostDomains || ""}
rows={3}
placeholder='["offensive"]'
onChange={v => (store as { encryptingHostDomains?: string; }).encryptingHostDomains = v}
onChange={v => (settings.store as { encryptingHostDomains?: string; }).encryptingHostDomains = v}
/>
</SettingsSection>
<SettingTextInput
name="Embed Title"
description="Optional title for embed style responses."
value={(store as { encryptingHostTitle?: string; }).encryptingHostTitle || ""}
onChange={v => (store as { encryptingHostTitle?: string; }).encryptingHostTitle = v}
value={(settings.store as { encryptingHostTitle?: string; }).encryptingHostTitle || ""}
onChange={v => (settings.store as { encryptingHostTitle?: string; }).encryptingHostTitle = v}
placeholder="Optional title"
/>
<SettingTextInput
name="Embed Color"
description="Optional color for embed style responses."
value={(store as { encryptingHostColor?: string; }).encryptingHostColor || ""}
onChange={v => (store as { encryptingHostColor?: string; }).encryptingHostColor = v}
value={(settings.store as { encryptingHostColor?: string; }).encryptingHostColor || ""}
onChange={v => (settings.store as { encryptingHostColor?: string; }).encryptingHostColor = v}
placeholder="Optional color"
/>
<SettingTextInput
name="Fake Link"
description="Optional fake link value for fakelink style responses."
value={(store as { encryptingHostFakelink?: string; }).encryptingHostFakelink || ""}
onChange={v => (store as { encryptingHostFakelink?: string; }).encryptingHostFakelink = v}
value={(settings.store as { encryptingHostFakelink?: string; }).encryptingHostFakelink || ""}
onChange={v => (settings.store as { encryptingHostFakelink?: string; }).encryptingHostFakelink = v}
placeholder="Optional fake link"
/>
</SettingGroup>
)}
{isS3 && (
{serviceType === ServiceType.S3 && (
<SettingGroup name="S3-Compatible Storage" description="Connection details and object naming for your bucket.">
<SettingTextInput
name="S3 Endpoint URL"
description="S3-compatible endpoint (e.g. https://<accountid>.r2.cloudflarestorage.com)"
value={store.s3Endpoint}
onChange={v => store.s3Endpoint = v}
value={settings.store.s3Endpoint}
onChange={v => settings.store.s3Endpoint = v}
placeholder="https://your-endpoint.example.com"
/>
<SettingTextInput
name="Bucket Name"
description="Bucket to upload into"
value={store.s3Bucket}
onChange={v => store.s3Bucket = v}
value={settings.store.s3Bucket}
onChange={v => settings.store.s3Bucket = v}
placeholder="my-bucket"
/>
<SettingTextInput
name="Region"
description="AWS region or auto for Cloudflare R2"
value={store.s3Region}
onChange={v => store.s3Region = v}
value={settings.store.s3Region}
onChange={v => settings.store.s3Region = v}
placeholder="auto"
/>
<SettingTextInput
name="Access Key ID"
description="S3-compatible access key"
value={store.s3AccessKeyId}
onChange={v => store.s3AccessKeyId = v}
value={settings.store.s3AccessKeyId}
onChange={v => settings.store.s3AccessKeyId = v}
placeholder="Your access key ID"
/>
<SettingTextInput
name="Secret Access Key"
description="S3-compatible secret key"
value={store.s3SecretAccessKey}
onChange={v => store.s3SecretAccessKey = v}
value={settings.store.s3SecretAccessKey}
onChange={v => settings.store.s3SecretAccessKey = v}
placeholder="Your secret access key"
/>
<SettingTextInput
name="Session Token"
description="Optional temporary credential token"
value={store.s3SessionToken}
onChange={v => store.s3SessionToken = v}
value={settings.store.s3SessionToken}
onChange={v => settings.store.s3SessionToken = v}
placeholder="Optional session token"
/>
<SettingTextInput
name="Public Base URL"
description="Optional public URL base to use for returned links"
value={store.s3PublicUrl}
onChange={v => store.s3PublicUrl = v}
value={settings.store.s3PublicUrl}
onChange={v => settings.store.s3PublicUrl = v}
placeholder="https://cdn.example.com"
/>
<SettingTextInput
name="Object Key Prefix"
description="Optional folder/prefix inside the bucket"
value={store.s3Prefix}
onChange={v => store.s3Prefix = v}
value={settings.store.s3Prefix}
onChange={v => settings.store.s3Prefix = v}
placeholder="uploads/discord"
/>
<SettingSwitch
name="Use Path-Style Endpoint"
description="Use endpoint/bucket/key format, recommended for R2."
checked={store.s3ForcePathStyle}
onChange={v => store.s3ForcePathStyle = v}
checked={settings.store.s3ForcePathStyle}
onChange={v => settings.store.s3ForcePathStyle = v}
/>
</SettingGroup>
)}
{isCatbox && (
{serviceType === ServiceType.CATBOX && (
<SettingGroup name="Catbox" description="Optional account binding for Catbox uploads.">
<SettingTextInput
name="Catbox Userhash"
description="Your Catbox userhash for account binding, leave empty for anonymous uploads."
value={store.catboxUserhash}
onChange={v => store.catboxUserhash = v}
value={settings.store.catboxUserhash}
onChange={v => settings.store.catboxUserhash = v}
placeholder="Your Catbox userhash"
/>
</SettingGroup>
)}
{isLitterbox && (
{serviceType === ServiceType.LITTERBOX && (
<SettingsSection id="litterbox-expiry" name="Litterbox Expiry" description="How long uploads are retained">
<Select
options={litterboxOptions}
isSelected={v => v === store.litterboxExpiry}
isSelected={v => v === settings.store.litterboxExpiry}
select={v => {
store.litterboxExpiry = v;
settings.store.litterboxExpiry = v;
update();
}}
serialize={v => v}
@ -774,43 +761,43 @@ export function SettingsComponent() {
</SettingsSection>
)}
{isGofile && (
{serviceType === ServiceType.GOFILE && (
<SettingGroup name="GoFile" description="Optional account binding for GoFile uploads.">
<SettingTextInput
name="GoFile Token"
description="Optional GoFile token to upload into your account."
value={store.gofileToken}
onChange={v => store.gofileToken = v}
value={settings.store.gofileToken}
onChange={v => settings.store.gofileToken = v}
placeholder="Optional GoFile token"
/>
</SettingGroup>
)}
{isPixelVault && (
{serviceType === ServiceType.PIXELVAULT && (
<SettingGroup name="PixelVault" description="Connection details for PixelVault uploads.">
<SettingTextInput
name="PixelVault Upload Key"
description="Your PixelVault authorization key."
value={store.pixelVaultKey}
onChange={v => store.pixelVaultKey = v}
value={settings.store.pixelVaultKey}
onChange={v => settings.store.pixelVaultKey = v}
placeholder="Your PixelVault upload key"
/>
</SettingGroup>
)}
{isPixelDrain && (
{serviceType === ServiceType.PIXELDRAIN && (
<SettingGroup name="PixelDrain" description="Optional account binding for PixelDrain uploads.">
<SettingTextInput
name="PixelDrain API Key"
description="Optional PixelDrain API key for authenticated uploads. Leave empty for anonymous uploads."
value={store.pixelDrainKey}
onChange={v => store.pixelDrainKey = v}
value={settings.store.pixelDrainKey}
onChange={v => settings.store.pixelDrainKey = v}
placeholder="Your PixelDrain API key"
/>
</SettingGroup>
)}
{isShareX && (
{serviceType === ServiceType.SHAREX && (
<SettingGroup name="ShareX Custom Uploader" description="Paste, import, or validate a ShareX custom uploader config.">
<SettingsSection
id="sharex-custom-uploader-config"
@ -818,10 +805,10 @@ export function SettingsComponent() {
description="Paste your ShareX custom uploader JSON (.sxcu/.json). DestinationType must include FileUploader or ImageUploader."
>
<TextArea
value={store.sharexConfig}
value={settings.store.sharexConfig}
rows={10}
placeholder='{"RequestMethod":"POST","RequestURL":"https://example.com/api/upload","Body":"MultipartFormData"}'
onChange={v => store.sharexConfig = v}
onChange={v => settings.store.sharexConfig = v}
/>
</SettingsSection>
<SettingsSection id="sharex-config-actions" name="ShareX Config Actions" description="Import from file or validate pasted config">
@ -840,34 +827,34 @@ export function SettingsComponent() {
</SettingGroup>
)}
{isWebdav && (
{serviceType === ServiceType.WEBDAV && (
<SettingGroup name="WebDAV" description="Connection details for WebDAV servers (Nextcloud, Owncloud, etc.).">
<SettingTextInput
name="Server URL"
description="Base WebDAV URL (e.g. https://nextcloud.example.com/remote.php/dav/files/username)"
value={store.webdavUrl}
onChange={v => store.webdavUrl = v}
value={settings.store.webdavUrl}
onChange={v => settings.store.webdavUrl = v}
placeholder="https://nextcloud.example.com/remote.php/dav/files/username"
/>
<SettingTextInput
name="Username"
description="WebDAV username"
value={store.webdavUsername}
onChange={v => store.webdavUsername = v}
value={settings.store.webdavUsername}
onChange={v => settings.store.webdavUsername = v}
placeholder="username"
/>
<SettingTextInput
name="Password or App Token"
description="WebDAV password or app token"
value={store.webdavPassword}
onChange={v => store.webdavPassword = v}
value={settings.store.webdavPassword}
onChange={v => settings.store.webdavPassword = v}
placeholder="password or app token"
/>
<SettingTextInput
name="Upload Directory"
description="Optional subdirectory on the server to upload into (e.g. uploads)"
value={store.webdavDirectory}
onChange={v => store.webdavDirectory = v}
value={settings.store.webdavDirectory}
onChange={v => settings.store.webdavDirectory = v}
placeholder="Leave empty for root directory"
/>
<SettingsSection id="server-type" name="Server Type" description="Select your WebDAV server type. Nextcloud and ownCloud will create a public share link. Generic returns the raw file URL.">
@ -877,16 +864,16 @@ export function SettingsComponent() {
{ label: "ownCloud", value: "owncloud" },
{ label: "Generic WebDAV", value: "generic" }
]}
isSelected={v => v === store.webdavServerType}
isSelected={v => v === settings.store.webdavServerType}
select={v => {
store.webdavServerType = v;
settings.store.webdavServerType = v;
update();
}}
serialize={v => v}
placeholder="Select server type"
/>
</SettingsSection>
{store.webdavServerType !== "generic" && (
{settings.store.webdavServerType !== "generic" && (
<SettingsSection id="share-link-format" name="Share Link Format" description="How to return the public share link. Share Page links to a web page; Direct Download links straight to the file; Markdown Link wraps the share page in a clickable filename.">
<Select
options={[
@ -894,9 +881,9 @@ export function SettingsComponent() {
{ label: "Direct Download", value: "direct-download" },
{ label: "Markdown Link", value: "markdown" }
]}
isSelected={v => v === store.webdavShareType}
isSelected={v => v === settings.store.webdavShareType}
select={v => {
store.webdavShareType = v;
settings.store.webdavShareType = v;
update();
}}
serialize={v => v}
@ -911,27 +898,27 @@ export function SettingsComponent() {
<SettingSwitch
name="Strip Query Parameters"
description="Strip query parameters from the uploaded file URL."
checked={store.stripQueryParams}
onChange={v => store.stripQueryParams = v}
checked={settings.store.stripQueryParams}
onChange={v => settings.store.stripQueryParams = v}
/>
<SettingSwitch
name="Use Embed Proxy"
description="Wrap uploaded video links with an embed proxy service for better Discord previews."
checked={store.embedProxyEnabled}
checked={settings.store.embedProxyEnabled}
onChange={v => {
store.embedProxyEnabled = v;
settings.store.embedProxyEnabled = v;
update();
}}
/>
{store.embedProxyEnabled && (
{settings.store.embedProxyEnabled && (
<SettingsSection id="embed-proxy-service" name="Embed Proxy Service" description="Choose which embed proxy service to use for uploaded video links">
<Select
options={embedProxyOptions}
isSelected={v => v === store.embedProxyService}
isSelected={v => v === settings.store.embedProxyService}
select={v => {
store.embedProxyService = v;
settings.store.embedProxyService = v;
update();
}}
serialize={v => v}
@ -943,43 +930,43 @@ export function SettingsComponent() {
<SettingSwitch
name="Convert APNG to GIF"
description="Convert APNG files to GIF format."
checked={store.apngToGif}
onChange={v => store.apngToGif = v}
checked={settings.store.apngToGif}
onChange={v => settings.store.apngToGif = v}
/>
<SettingSwitch
name="Preserve Original Filename"
description="Use the original filename instead of naming uploads as upload.ext."
checked={Boolean((store as { preserveOriginalFilename?: boolean; }).preserveOriginalFilename)}
onChange={v => (store as { preserveOriginalFilename?: boolean; }).preserveOriginalFilename = v}
checked={Boolean((settings.store as { preserveOriginalFilename?: boolean; }).preserveOriginalFilename)}
onChange={v => (settings.store as { preserveOriginalFilename?: boolean; }).preserveOriginalFilename = v}
/>
<SettingSwitch
name="Auto Copy URL"
description="Automatically copy the uploaded file URL to clipboard."
checked={store.autoCopy}
onChange={v => store.autoCopy = v}
checked={settings.store.autoCopy}
onChange={v => settings.store.autoCopy = v}
/>
<SettingSwitch
name="Disable Fallback Uploaders"
description="Only use the selected uploader without trying fallback hosts."
checked={store.disableFallbacks}
onChange={v => store.disableFallbacks = v}
checked={settings.store.disableFallbacks}
onChange={v => settings.store.disableFallbacks = v}
/>
<SettingSwitch
name="Insert URL into Chat Input"
description="After upload, insert the resulting URL into the current chat input."
checked={store.autoSend}
onChange={v => store.autoSend = v}
checked={settings.store.autoSend}
onChange={v => settings.store.autoSend = v}
/>
<SettingSwitch
name="Format Inserted URL"
description="Wrap inserted URLs in angle brackets to avoid Discord preview embedding."
checked={store.autoFormat}
onChange={v => store.autoFormat = v}
checked={settings.store.autoFormat}
onChange={v => settings.store.autoFormat = v}
/>
</SettingGroup>
@ -987,29 +974,29 @@ export function SettingsComponent() {
<SettingSwitch
name="Bypass Discord Upload Button"
description="Use FileUpload when uploading through Discord's file picker."
checked={Boolean((store as { bypassDiscordUpload?: boolean; }).bypassDiscordUpload)}
onChange={v => (store as { bypassDiscordUpload?: boolean; }).bypassDiscordUpload = v}
checked={Boolean((settings.store as { bypassDiscordUpload?: boolean; }).bypassDiscordUpload)}
onChange={v => (settings.store as { bypassDiscordUpload?: boolean; }).bypassDiscordUpload = v}
/>
<SettingSwitch
name="Auto Upload Pasted Files"
description="Automatically upload files from clipboard to image host when pasting in chat input."
checked={store.autoUploadPastedFiles}
onChange={v => store.autoUploadPastedFiles = v}
checked={settings.store.autoUploadPastedFiles}
onChange={v => settings.store.autoUploadPastedFiles = v}
/>
<SettingSwitch
name="Respect Discord File Size Limit"
description="Use FileUpload only for files larger than your current Discord upload limit."
checked={store.bypassDiscordUploadOnlyOverLimit}
onChange={v => store.bypassDiscordUploadOnlyOverLimit = v}
checked={settings.store.bypassDiscordUploadOnlyOverLimit}
onChange={v => settings.store.bypassDiscordUploadOnlyOverLimit = v}
/>
<SettingTextInput
name="Allowed File Types"
description="Comma-separated list of extensions (e.g. png,jpg,gif). Leave empty to allow all."
value={store.uploadAllowedFileTypes}
onChange={v => store.uploadAllowedFileTypes = v}
value={settings.store.uploadAllowedFileTypes}
onChange={v => settings.store.uploadAllowedFileTypes = v}
placeholder="png,jpg,gif,mp4,webp"
/>
</SettingGroup>
@ -1018,8 +1005,8 @@ export function SettingsComponent() {
<SettingTextInput
name="CORS Proxy URL"
description="CORS proxy used for web uploads. Leave empty to use the default proxy."
value={store.corsProxyUrl || ""}
onChange={v => store.corsProxyUrl = v}
value={settings.store.corsProxyUrl || ""}
onChange={v => settings.store.corsProxyUrl = v}
placeholder="https://your-cors-proxy.example.com"
/>
@ -1036,9 +1023,9 @@ export function SettingsComponent() {
{ label: "5 minutes", value: 300000, default: true },
{ label: "10 minutes", value: 600000 }
]}
isSelected={v => v === (store.uploadTimeoutMs || 300000)}
isSelected={v => v === (settings.store.uploadTimeoutMs || 300000)}
select={v => {
store.uploadTimeoutMs = v;
settings.store.uploadTimeoutMs = v;
update();
}}
serialize={v => v}

View file

@ -39,8 +39,6 @@ function InstallInstructions() {
}
function LyricsProviderSettings() {
const { store } = settings;
return (
<>
<SettingsSection id="lyrics-provider" name="Lyrics Provider" description="Where lyrics are fetched from.">
@ -49,14 +47,14 @@ function LyricsProviderSettings() {
{ value: Provider.Lrclib, label: "LRCLIB", default: true },
{ value: Provider.Spotify, label: "Spotify (Musixmatch)" },
]}
isSelected={v => v === store.lyricsProvider}
select={v => { store.lyricsProvider = v as Provider; }}
isSelected={v => v === settings.store.lyricsProvider}
select={v => { settings.store.lyricsProvider = v as Provider; }}
serialize={v => v}
placeholder="Select a lyrics provider"
/>
</SettingsSection>
{store.lyricsProvider === Provider.Spotify && (
{settings.store.lyricsProvider === Provider.Spotify && (
<SettingsSection
id="spotify-lyrics-provider"
name="Spotify Lyrics API Base URL"
@ -64,9 +62,9 @@ function LyricsProviderSettings() {
>
<TextInput
type="text"
value={store.spotifyLyricsApiUrl}
value={settings.store.spotifyLyricsApiUrl}
onChange={v => {
store.spotifyLyricsApiUrl = v;
settings.store.spotifyLyricsApiUrl = v;
void clearLyricsCache();
showToast("Lyrics cache purged", Toasts.Type.SUCCESS);
}}

View file

@ -605,24 +605,23 @@ async function joinRandomVoice() {
return;
}
const { store } = settings;
ChannelActions.selectVoiceChannel(channelId);
if (store.autoNavigate) {
if (settings.store.autoNavigate) {
ChannelRouter.transitionToChannel(channelId);
}
const postJoinActions: PostJoinAction[] = [];
if (store.selfMute && !MediaEngineStore.isSelfMute()) {
if (settings.store.selfMute && !MediaEngineStore.isSelfMute()) {
postJoinActions.push(() => VoiceActions.toggleSelfMute());
}
if (store.selfDeafen && !MediaEngineStore.isSelfDeaf()) {
if (settings.store.selfDeafen && !MediaEngineStore.isSelfDeaf()) {
postJoinActions.push(() => VoiceActions.toggleSelfDeaf());
}
if (store.autoCamera) {
if (settings.store.autoCamera) {
postJoinActions.push(enableCamera);
}
if (store.autoStream) {
if (settings.store.autoStream) {
postJoinActions.push(() => startChannelStream(channel));
}
@ -646,16 +645,15 @@ function RandomVoiceButton({ iconForeground, hideTooltips, nameplate }: UserArea
function RandomVoiceMenu({ onClose }: { onClose(): void; }) {
const [, rerender] = React.useReducer(value => value + 1, 0);
const { store } = settings;
const guilds = getRenderableGuilds();
const allServerIds = guilds.map(guild => guild.id);
const selectedServerIds = getSelectedServerIds(store) ?? allServerIds;
const selectedServerIds = getSelectedServerIds(settings.store) ?? allServerIds;
const update = <K extends keyof typeof store>(key: K, value: typeof store[K]) => {
store[key] = value;
const update = <K extends keyof typeof settings.store>(key: K, value: typeof settings.store[K]) => {
settings.store[key] = value;
rerender();
};
const toggle = <K extends SelfSettingKey | StateFilterKey | "includeStates" | "avoidStates">(key: K) => update(key, !store[key]);
const toggle = <K extends SelfSettingKey | StateFilterKey | "includeStates" | "avoidStates">(key: K) => update(key, !settings.store[key]);
const selectAllServers = () => {
setServerIds(guilds.map(guild => guild.id));
rerender();
@ -675,7 +673,7 @@ function RandomVoiceMenu({ onClose }: { onClose(): void; }) {
const setSlider = <K extends "UserAmount" | "spacesLeft" | "vcLimit">(key: K) =>
debounce((value: number) => {
store[key] = Math.round(value);
settings.store[key] = Math.round(value);
rerender();
}, 50);
@ -717,7 +715,7 @@ function RandomVoiceMenu({ onClose }: { onClose(): void; }) {
key={key}
id={`random-voice-filter-${key}`}
label={label}
checked={store[key]}
checked={settings.store[key]}
action={() => toggle(key)}
/>
))}
@ -725,15 +723,15 @@ function RandomVoiceMenu({ onClose }: { onClose(): void; }) {
<Menu.MenuCheckboxItem
id="random-voice-include-states"
label="Include Filters"
checked={store.includeStates}
disabled={store.avoidStates || !hasStateFilters(store)}
checked={settings.store.includeStates}
disabled={settings.store.avoidStates || !hasStateFilters(settings.store)}
action={() => toggle("includeStates")}
/>
<Menu.MenuCheckboxItem
id="random-voice-avoid-states"
label="Avoid Filters"
checked={store.avoidStates}
disabled={store.includeStates || !hasStateFilters(store)}
checked={settings.store.avoidStates}
disabled={settings.store.includeStates || !hasStateFilters(settings.store)}
action={() => toggle("avoidStates")}
/>
</>
@ -746,8 +744,8 @@ function RandomVoiceMenu({ onClose }: { onClose(): void; }) {
label: "User Amount",
sliderKey: "UserAmount",
operationKey: "UserAmountOperation",
sliderValue: store.UserAmount,
operationValue: store.UserAmountOperation,
sliderValue: settings.store.UserAmount,
operationValue: settings.store.UserAmountOperation,
onOperationChange: value => update("UserAmountOperation", value),
onSliderChange: setSlider("UserAmount"),
})}
@ -759,8 +757,8 @@ function RandomVoiceMenu({ onClose }: { onClose(): void; }) {
label: "Spaces Left",
sliderKey: "spacesLeft",
operationKey: "spacesLeftOperation",
sliderValue: store.spacesLeft,
operationValue: store.spacesLeftOperation,
sliderValue: settings.store.spacesLeft,
operationValue: settings.store.spacesLeftOperation,
onOperationChange: value => update("spacesLeftOperation", value),
onSliderChange: setSlider("spacesLeft"),
})}
@ -772,8 +770,8 @@ function RandomVoiceMenu({ onClose }: { onClose(): void; }) {
label: "Voice Limit",
sliderKey: "vcLimit",
operationKey: "vcLimitOperation",
sliderValue: store.vcLimit,
operationValue: store.vcLimitOperation,
sliderValue: settings.store.vcLimit,
operationValue: settings.store.vcLimitOperation,
onOperationChange: value => update("vcLimitOperation", value),
onSliderChange: setSlider("vcLimit"),
})}
@ -787,7 +785,7 @@ function RandomVoiceMenu({ onClose }: { onClose(): void; }) {
key={key}
id={`random-voice-setting-${key}`}
label={label}
checked={store[key]}
checked={settings.store[key]}
action={() => toggle(key)}
/>
))}

View file

@ -89,13 +89,12 @@ async function fetchMediaData(): Promise<JfMediaData | null> {
}
async function getActivity(): Promise<Activity | null> {
const { store } = settings;
const mediaData = await fetchMediaData();
if (!mediaData) return null;
let richPresenceType: number;
if (store.jf_overrideType !== "off") {
richPresenceType = parseInt(store.jf_overrideType as string, 10);
if (settings.store.jf_overrideType !== "off") {
richPresenceType = parseInt(settings.store.jf_overrideType as string, 10);
} else {
richPresenceType = mediaData.type === "Audio" ? 2 : 3;
}
@ -111,25 +110,25 @@ async function getActivity(): Promise<Activity | null> {
.replace(/\{year\}/g, mediaData.year?.toString() || "");
let appName: string;
const nameSetting = store.jf_nameDisplay || "default";
const nameSetting = settings.store.jf_nameDisplay || "default";
switch (nameSetting) {
case "full":
if (mediaData.type === "Episode" && mediaData.seriesName) {
appName = store.jf_privacyMode
appName = settings.store.jf_privacyMode
? `${mediaData.seriesName} - [Episode Hidden]`
: `${mediaData.seriesName} - ${mediaData.name}`;
} else if (mediaData.type === "Audio") {
appName = store.jf_privacyMode
appName = settings.store.jf_privacyMode
? "[Track Hidden]"
: `${mediaData.artist || "Unknown Artist"} - ${mediaData.name}`;
} else {
appName = store.jf_privacyMode ? "[Movie Hidden]" : mediaData.name || "Jellyfin";
appName = settings.store.jf_privacyMode ? "[Movie Hidden]" : mediaData.name || "Jellyfin";
}
break;
case "custom":
appName = templateReplace(store.jf_customName || "{name} on Jellyfin");
if (store.jf_privacyMode) {
appName = templateReplace(settings.store.jf_customName || "{name} on Jellyfin");
if (settings.store.jf_privacyMode) {
appName = appName
.replace(mediaData.name || "", "[Title Hidden]")
.replace(mediaData.seriesName || "", "[Series Hidden]")
@ -141,13 +140,13 @@ async function getActivity(): Promise<Activity | null> {
if (mediaData.type === "Episode" && mediaData.seriesName) {
appName = mediaData.seriesName;
} else {
appName = store.jf_privacyMode ? "[Media Hidden]" : mediaData.name || "Jellyfin";
appName = settings.store.jf_privacyMode ? "[Media Hidden]" : mediaData.name || "Jellyfin";
}
break;
}
const assets = {
large_image: !store.jf_privacyMode && mediaData.imageUrl
large_image: !settings.store.jf_privacyMode && mediaData.imageUrl
? await getAsset(mediaData.imageUrl) : undefined,
large_text: mediaData.seriesName || mediaData.album || undefined,
};
@ -155,9 +154,9 @@ async function getActivity(): Promise<Activity | null> {
const getDetails = () => {
let details: string;
if (mediaData.type === "Episode" && mediaData.seriesName)
details = store.jf_privacyMode ? "Watching a TV Show" : mediaData.seriesName;
details = settings.store.jf_privacyMode ? "Watching a TV Show" : mediaData.seriesName;
else
details = store.jf_privacyMode ? "Watching Something" : mediaData.name;
details = settings.store.jf_privacyMode ? "Watching Something" : mediaData.name;
if (mediaData.isPaused) details += " - Paused";
return details;
};
@ -169,7 +168,7 @@ async function getActivity(): Promise<Activity | null> {
let episodeFormat = "";
const season = mediaData.seasonNumber;
const episode = mediaData.episodeNumber;
const format = store.jf_episodeFormat || "long";
const format = settings.store.jf_episodeFormat || "long";
if (season != null && episode != null) {
switch (format) {
@ -189,10 +188,10 @@ async function getActivity(): Promise<Activity | null> {
episodeFormat = format === "fulltext" ? `Episode ${episode}` : `E${episode.toString().padStart(2, "0")}`;
}
state = (store.jf_showEpisodeName && mediaData.name && !store.jf_privacyMode)
state = (settings.store.jf_showEpisodeName && mediaData.name && !settings.store.jf_privacyMode)
? `${episodeFormat} - ${mediaData.name}`
: episodeFormat;
} else if (store.jf_privacyMode) {
} else if (settings.store.jf_privacyMode) {
state = mediaData.type === "Audio" ? "Listening to music" : (mediaData.year ? "(????)" : undefined);
} else {
state = mediaData.artist || (mediaData.year ? `(${mediaData.year})` : undefined);

View file

@ -55,13 +55,12 @@ export default definePlugin({
],
filterCodecs(codecs: CodecInfo[]) {
const { store } = settings;
const disabled: Record<string, boolean> = {
AV1: store.disableAv1Codec,
H265: store.disableH265Codec,
H264: store.disableH264Codec,
VP8: store.disableVP8Codec,
VP9: store.disableVP9Codec,
AV1: settings.store.disableAv1Codec,
H265: settings.store.disableH265Codec,
H264: settings.store.disableH264Codec,
VP8: settings.store.disableVP8Codec,
VP9: settings.store.disableVP9Codec,
};
return codecs.map(c => disabled[c.name] ? { ...c, encode: false } : c);
},