From 5750a9cc48c7a0be0b90fd824d85849e821f33e0 Mon Sep 17 00:00:00 2001 From: TheGreenPig <67547385+TheGreenPig@users.noreply.github.com> Date: Sat, 14 Sep 2024 18:57:15 +0200 Subject: [PATCH 1/9] New Plugin FileViewer --- src/components/Icons.tsx | 30 ++++ src/main/index.ts | 2 +- src/plugins/fileViewer/README.md | 3 + src/plugins/fileViewer/cache.ts | 62 +++++++++ src/plugins/fileViewer/fileViewer.css | 29 ++++ src/plugins/fileViewer/index.tsx | 188 ++++++++++++++++++++++++++ src/plugins/fileViewer/native.ts | 28 ++++ src/utils/constants.ts | 4 + 8 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 src/plugins/fileViewer/README.md create mode 100644 src/plugins/fileViewer/cache.ts create mode 100644 src/plugins/fileViewer/fileViewer.css create mode 100644 src/plugins/fileViewer/index.tsx create mode 100644 src/plugins/fileViewer/native.ts diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index fa142a18c..8bb36ba4b 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -407,6 +407,36 @@ export function PencilIcon(props: IconProps) { ); } +export function PreviewVisible(props: IconProps) { + return ( + + + + ); +} + +export function PreviewInvisible(props: IconProps) { + return ( + + + + ); +} + const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg"; const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg"; const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg"; diff --git a/src/main/index.ts b/src/main/index.ts index 5519d47ac..5cda4decb 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -93,7 +93,7 @@ if (IS_VESKTOP || !IS_VANILLA) { if (header) { const csp = parsePolicy(headers[header][0]); - for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) { + for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src", "frame-src", "object-src"]) { csp[directive] ??= []; csp[directive].push("*", "blob:", "data:", "vencord:", "'unsafe-inline'"); } diff --git a/src/plugins/fileViewer/README.md b/src/plugins/fileViewer/README.md new file mode 100644 index 000000000..11bf76ca9 --- /dev/null +++ b/src/plugins/fileViewer/README.md @@ -0,0 +1,3 @@ +# FileViewer + +Allows you to Preview PDF Files without having to download them diff --git a/src/plugins/fileViewer/cache.ts b/src/plugins/fileViewer/cache.ts new file mode 100644 index 000000000..0606da6c7 --- /dev/null +++ b/src/plugins/fileViewer/cache.ts @@ -0,0 +1,62 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Logger } from "@utils/Logger"; + +export class LRUCache { + private cache: Map; + private maxSize: number; + + constructor() { + this.cache = new Map(); + this.maxSize = 50; + } + + get(key: string): string | undefined { + if (!this.cache.has(key)) return undefined; + + const value = this.cache.get(key)!; + this.cache.delete(key); + this.cache.set(key, value); + return value; + } + + set(key: string, value: string) { + if (this.cache.has(key)) { + this.cache.delete(key); + } else if (this.cache.size >= this.maxSize) { + const oldestKey = this.cache.keys().next().value!; + this.cache.delete(oldestKey); + } + + this.cache.set(key, value); + } + + delete(key: string) { + if (!this.cache.has(key)) return; + URL.revokeObjectURL(this.cache.get(key)!); + this.cache.delete(key); + } + + clear() { + for (const key of this.cache.keys()) { + URL.revokeObjectURL(this.cache.get(key)!); + } + this.cache.clear(); + } + + setMaxSize(maxSize: number) { + if (maxSize < 1) { + new Logger("FileViewer").error("Cache size must be at least 1"); + return; + } + this.maxSize = maxSize; + } + + getMaxSize() { + return this.maxSize; + } +} diff --git a/src/plugins/fileViewer/fileViewer.css b/src/plugins/fileViewer/fileViewer.css new file mode 100644 index 000000000..fc72cc2fa --- /dev/null +++ b/src/plugins/fileViewer/fileViewer.css @@ -0,0 +1,29 @@ +.file-viewer.container { + resize: both; + overflow: hidden; + max-width: 100%; + min-width: 432px; + max-height: 80vh; + min-height: 500px; + box-sizing: border-box; + display: none; /* Will be set with buildCss */ +} + +.file-viewer.preview { + width: 100%; + flex-grow: 1; + border: none; +} + +.file-viewer.toggle { + justify-self: center; + display: flex; + justify-content: center; + margin-right: 8px; + color: var(--interactive-normal); + cursor: pointer; +} + +.file-viewer.toggle:hover { + color: var(--interactive-hover); +} diff --git a/src/plugins/fileViewer/index.tsx b/src/plugins/fileViewer/index.tsx new file mode 100644 index 000000000..b30208a5f --- /dev/null +++ b/src/plugins/fileViewer/index.tsx @@ -0,0 +1,188 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./fileViewer.css"; + +import { get, set } from "@api/DataStore"; +import { addAccessory, removeAccessory } from "@api/MessageAccessories"; +import { updateMessage } from "@api/MessageUpdater"; +import { definePluginSettings } from "@api/Settings"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { PreviewInvisible, PreviewVisible } from "@components/Icons"; +import { Devs } from "@utils/constants"; +import { Logger } from "@utils/Logger"; +import definePlugin, { OptionType, PluginNative } from "@utils/types"; +import { Tooltip, useEffect, useState } from "@webpack/common"; + +import { LRUCache } from "./cache"; + +const Native = VencordNative.pluginHelpers.FileViewer as PluginNative; + +const settings = definePluginSettings({ + autoEmptyCache: { + type: OptionType.BOOLEAN, + description: "Automatically remove the cached PDF file when the component is unmounted. Turning this on will increase load times for PDFs that have already been viewed, but may consume less memory.", + default: false + }, + persistPreviewState: { + type: OptionType.BOOLEAN, + description: "Persist the state of opened/closed File Previews across channel switches and reloads.", + default: false + }, + cacheSize: { + type: OptionType.SLIDER, + description: "Maximum number of PDF files to cache (after that, the least recently used file will be removed). Lower this value if you're running out of memory.", + default: 50, + restartNeeded: true, + markers: [10, 20, 30, 40, 50, 75, 100], + stickToMarkers: true, + } +}); + +const objectUrlsCache = new LRUCache(); +const STORE_KEY = "FileViewer_PersistVisible"; + +let style: HTMLStyleElement; + +interface Attachment { + id: string; + filename: string; + size: number; + url: string; + proxy_url: string; + content_type: string; + content_scan_version: number; + title: string; + spoiler: boolean; + previewBlobUrl?: string; +} + +const stripLink = (url: string) => url.replace("https://cdn.discordapp.com/attachments/", "").split("/").slice(0, 2).join("-"); +function FilePreview({ attachment }: { attachment: Attachment; }) { + const { previewBlobUrl } = attachment; + + if (!previewBlobUrl) return null; + + return previewBlobUrl &&
; +} + +async function buildCss() { + const visiblePreviews: Set | undefined = await get(STORE_KEY); + const elements = [...(visiblePreviews || [])].map(url => `#file-viewer-${stripLink(url)}`).join(","); + style.textContent = ` + :is(${elements}) { + display: flex !important; + } + `; +} + +function PreviewButton({ attachment, channelId, messageId }: { attachment: Attachment; channelId: string; messageId: string; }) { + const [visible, setVisible] = useState(null); + const [url, setUrl] = useState(); + + const initPdfData = async () => { + const cachedUrl = objectUrlsCache.get(attachment.url); + if (cachedUrl) { + setUrl(cachedUrl); + return; + } + try { + const buffer = await Native.getBufferResponse(attachment.url); + const file = new File([buffer], attachment.filename, { type: attachment.content_type }); + const blobUrl = URL.createObjectURL(file); + objectUrlsCache.set(attachment.url, blobUrl); + setUrl(blobUrl); + } catch (error) { + console.log(error); + } + }; + + useEffect(() => { + get(STORE_KEY).then(async data => { + if (visible === null) { + setVisible(settings.store.persistPreviewState ? (data ?? new Set()).has(attachment.url) : false); + } else { + const persistSet = (data ?? new Set()); + if (visible) persistSet.add(attachment.url); + else persistSet.delete(attachment.url); + await set(STORE_KEY, persistSet); + buildCss(); + } + }); + if (visible && !url) initPdfData(); + }, [visible]); + + useEffect(() => { + attachment.previewBlobUrl = url; + updateMessage(channelId, messageId,); + return () => { + if (url && settings.store.autoEmptyCache) { + objectUrlsCache.delete(attachment.url); + } + }; + }, [url]); + + return + {tooltipProps => ( +
{ + setVisible(v => !v); + }} + > + {visible ? : } +
+ )} +
; +} + +export default definePlugin({ + name: "FileViewer", + description: "Preview PDF Files without having to download them", + authors: [Devs.AGreenPig], + dependencies: ["MessageAccessoriesAPI", "MessageUpdaterAPI",], + settings, + patches: [ + { + find: "Messages.IMG_ALT_ATTACHMENT_FILE_TYPE.format", + replacement: { + match: /function\((.+?)\)(.+Fragment.{0,500}newMosaicStyle.{0,500}?children:\[)(.+?\])/, + replace: "function($1)$2$self.renderPreviewButton($1),$3" + } + } + ], + start() { + objectUrlsCache.setMaxSize(Math.round(settings.store.cacheSize)); + new Logger("FileViewer").info(`Initialized LRU Cache with size ${objectUrlsCache.getMaxSize()}`); + addAccessory("fileViewer", props => { + const pdfAttachments = props.message.attachments.filter(a => a.content_type === "application/pdf"); + if (!pdfAttachments.length) return null; + + return ( + + {pdfAttachments.map((attachment, index) => ( + + ))} + + ); + }, -1); + + style = document.createElement("style"); + style.id = "VencordFileViewer"; + document.head.appendChild(style); + }, + renderPreviewButton(e) { + if (e.item.originalItem.content_type !== "application/pdf") return null; + return ; + }, + stop() { + objectUrlsCache.clear(); + removeAccessory("fileViewer"); + style.remove(); + } +}); diff --git a/src/plugins/fileViewer/native.ts b/src/plugins/fileViewer/native.ts new file mode 100644 index 000000000..d8b96469a --- /dev/null +++ b/src/plugins/fileViewer/native.ts @@ -0,0 +1,28 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { IpcMainInvokeEvent } from "electron"; +import { request } from "https"; + +export async function getBufferResponse(_: IpcMainInvokeEvent, url: string) { + return new Promise((resolve, reject) => { + const req = request(new URL(url), { method: "GET" }, res => { + const chunks: Uint8Array[] = []; + + res.on("data", function (chunk) { + chunks.push(chunk); + }); + + res.on("end", function () { + const body = Buffer.concat(chunks); + resolve(body); + }); + }); + req.on("error", reject); + req.end(); + }); +} + diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 0572fa102..5d465e0e4 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -575,6 +575,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "RamziAH", id: 1279957227612147747n, }, + AGreenPig: { + name: "AGreenPig", + id: 427179231164760066n, + } } satisfies Record); // iife so #__PURE__ works correctly From ecbd6ceb1def6e18645ed906c109ee7cceb554bf Mon Sep 17 00:00:00 2001 From: TheGreenPig <67547385+TheGreenPig@users.noreply.github.com> Date: Sun, 15 Sep 2024 00:25:48 +0200 Subject: [PATCH 2/9] Improve patch regex --- src/plugins/fileViewer/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/fileViewer/index.tsx b/src/plugins/fileViewer/index.tsx index b30208a5f..4ed68f25d 100644 --- a/src/plugins/fileViewer/index.tsx +++ b/src/plugins/fileViewer/index.tsx @@ -66,7 +66,7 @@ function FilePreview({ attachment }: { attachment: Attachment; }) { if (!previewBlobUrl) return null; - return previewBlobUrl &&
; + return
; } async function buildCss() { @@ -151,8 +151,8 @@ export default definePlugin({ { find: "Messages.IMG_ALT_ATTACHMENT_FILE_TYPE.format", replacement: { - match: /function\((.+?)\)(.+Fragment.{0,500}newMosaicStyle.{0,500}?children:\[)(.+?\])/, - replace: "function($1)$2$self.renderPreviewButton($1),$3" + match: /newMosaicStyle,\i\),children:\[(?<=}=(\i);.+?)/, + replace: "$&$self.renderPreviewButton($1)," } } ], From fa3f53930c762d09384ac0291560ce5b1c489b21 Mon Sep 17 00:00:00 2001 From: TheGreenPig <67547385+TheGreenPig@users.noreply.github.com> Date: Tue, 17 Sep 2024 23:54:31 +0200 Subject: [PATCH 3/9] Remove init Cache logging Co-authored-by: v --- src/plugins/fileViewer/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/fileViewer/index.tsx b/src/plugins/fileViewer/index.tsx index 4ed68f25d..b8c01040a 100644 --- a/src/plugins/fileViewer/index.tsx +++ b/src/plugins/fileViewer/index.tsx @@ -158,7 +158,6 @@ export default definePlugin({ ], start() { objectUrlsCache.setMaxSize(Math.round(settings.store.cacheSize)); - new Logger("FileViewer").info(`Initialized LRU Cache with size ${objectUrlsCache.getMaxSize()}`); addAccessory("fileViewer", props => { const pdfAttachments = props.message.attachments.filter(a => a.content_type === "application/pdf"); if (!pdfAttachments.length) return null; From fce53b95354a14da810ca2d5d7534a6acc6ed703 Mon Sep 17 00:00:00 2001 From: TheGreenPig <67547385+TheGreenPig@users.noreply.github.com> Date: Tue, 17 Sep 2024 23:55:22 +0200 Subject: [PATCH 4/9] Add ErrorBoundary Co-authored-by: v --- src/plugins/fileViewer/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/fileViewer/index.tsx b/src/plugins/fileViewer/index.tsx index b8c01040a..a82e81ed2 100644 --- a/src/plugins/fileViewer/index.tsx +++ b/src/plugins/fileViewer/index.tsx @@ -175,7 +175,7 @@ export default definePlugin({ style.id = "VencordFileViewer"; document.head.appendChild(style); }, - renderPreviewButton(e) { + renderPreviewButton: ErrorBoundary.wrap(e => { if (e.item.originalItem.content_type !== "application/pdf") return null; return ; }, From 6c2222e295fb4ce9c4e65d0f892ee9294c856b42 Mon Sep 17 00:00:00 2001 From: TheGreenPig <67547385+TheGreenPig@users.noreply.github.com> Date: Wed, 18 Sep 2024 01:47:49 +0200 Subject: [PATCH 5/9] Update name, remove css building, remove cache size setting --- src/components/Icons.tsx | 30 ------ .../cache.ts | 18 +--- .../index.tsx | 94 ++++++++----------- .../pdfViewer.css} | 12 ++- 4 files changed, 48 insertions(+), 106 deletions(-) rename src/plugins/{fileViewer => pdfViewer.desktop}/cache.ts (78%) rename src/plugins/{fileViewer => pdfViewer.desktop}/index.tsx (62%) rename src/plugins/{fileViewer/fileViewer.css => pdfViewer.desktop/pdfViewer.css} (69%) diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index 8bb36ba4b..fa142a18c 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -407,36 +407,6 @@ export function PencilIcon(props: IconProps) { ); } -export function PreviewVisible(props: IconProps) { - return ( - - - - ); -} - -export function PreviewInvisible(props: IconProps) { - return ( - - - - ); -} - const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg"; const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg"; const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg"; diff --git a/src/plugins/fileViewer/cache.ts b/src/plugins/pdfViewer.desktop/cache.ts similarity index 78% rename from src/plugins/fileViewer/cache.ts rename to src/plugins/pdfViewer.desktop/cache.ts index 0606da6c7..45f90728d 100644 --- a/src/plugins/fileViewer/cache.ts +++ b/src/plugins/pdfViewer.desktop/cache.ts @@ -4,15 +4,13 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { Logger } from "@utils/Logger"; - export class LRUCache { private cache: Map; private maxSize: number; - constructor() { + constructor(maxSize: number) { this.cache = new Map(); - this.maxSize = 50; + this.maxSize = maxSize; } get(key: string): string | undefined { @@ -47,16 +45,4 @@ export class LRUCache { } this.cache.clear(); } - - setMaxSize(maxSize: number) { - if (maxSize < 1) { - new Logger("FileViewer").error("Cache size must be at least 1"); - return; - } - this.maxSize = maxSize; - } - - getMaxSize() { - return this.maxSize; - } } diff --git a/src/plugins/fileViewer/index.tsx b/src/plugins/pdfViewer.desktop/index.tsx similarity index 62% rename from src/plugins/fileViewer/index.tsx rename to src/plugins/pdfViewer.desktop/index.tsx index a82e81ed2..28ca5f102 100644 --- a/src/plugins/fileViewer/index.tsx +++ b/src/plugins/pdfViewer.desktop/index.tsx @@ -4,22 +4,20 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import "./fileViewer.css"; +import "./pdfViewer.css"; import { get, set } from "@api/DataStore"; import { addAccessory, removeAccessory } from "@api/MessageAccessories"; import { updateMessage } from "@api/MessageUpdater"; import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; -import { PreviewInvisible, PreviewVisible } from "@components/Icons"; import { Devs } from "@utils/constants"; -import { Logger } from "@utils/Logger"; import definePlugin, { OptionType, PluginNative } from "@utils/types"; -import { Tooltip, useEffect, useState } from "@webpack/common"; +import { Icons, Spinner, Tooltip, useEffect, useState } from "@webpack/common"; import { LRUCache } from "./cache"; -const Native = VencordNative.pluginHelpers.FileViewer as PluginNative; +const Native = VencordNative.pluginHelpers.PdfViewer as PluginNative; const settings = definePluginSettings({ autoEmptyCache: { @@ -32,20 +30,10 @@ const settings = definePluginSettings({ description: "Persist the state of opened/closed File Previews across channel switches and reloads.", default: false }, - cacheSize: { - type: OptionType.SLIDER, - description: "Maximum number of PDF files to cache (after that, the least recently used file will be removed). Lower this value if you're running out of memory.", - default: 50, - restartNeeded: true, - markers: [10, 20, 30, 40, 50, 75, 100], - stickToMarkers: true, - } }); -const objectUrlsCache = new LRUCache(); -const STORE_KEY = "FileViewer_PersistVisible"; - -let style: HTMLStyleElement; +const objectUrlsCache = new LRUCache(20); +const STORE_KEY = "PdfViewer_PersistVisible"; interface Attachment { id: string; @@ -58,25 +46,19 @@ interface Attachment { title: string; spoiler: boolean; previewBlobUrl?: string; + previewVisible?: boolean; } -const stripLink = (url: string) => url.replace("https://cdn.discordapp.com/attachments/", "").split("/").slice(0, 2).join("-"); function FilePreview({ attachment }: { attachment: Attachment; }) { - const { previewBlobUrl } = attachment; + const { previewBlobUrl, previewVisible } = attachment; - if (!previewBlobUrl) return null; + if (!previewVisible) return null; - return
; -} - -async function buildCss() { - const visiblePreviews: Set | undefined = await get(STORE_KEY); - const elements = [...(visiblePreviews || [])].map(url => `#file-viewer-${stripLink(url)}`).join(","); - style.textContent = ` - :is(${elements}) { - display: flex !important; - } - `; + return ( +
+ {previewBlobUrl ? : } +
+ ); } function PreviewButton({ attachment, channelId, messageId }: { attachment: Attachment; channelId: string; messageId: string; }) { @@ -92,6 +74,7 @@ function PreviewButton({ attachment, channelId, messageId }: { attachment: Attac try { const buffer = await Native.getBufferResponse(attachment.url); const file = new File([buffer], attachment.filename, { type: attachment.content_type }); + const blobUrl = URL.createObjectURL(file); objectUrlsCache.set(attachment.url, blobUrl); setUrl(blobUrl); @@ -100,24 +83,31 @@ function PreviewButton({ attachment, channelId, messageId }: { attachment: Attac } }; + const updateVisibility = async () => { + const data: Set = await get(STORE_KEY) ?? new Set(); + + if (visible === null) { + setVisible(settings.store.persistPreviewState ? data.has(attachment.url) : false); + } else { + if (visible) data.add(attachment.url); + else data.delete(attachment.url); + + await set(STORE_KEY, data); + + attachment.previewVisible = visible; + updateMessage(channelId, messageId); + } + }; + useEffect(() => { - get(STORE_KEY).then(async data => { - if (visible === null) { - setVisible(settings.store.persistPreviewState ? (data ?? new Set()).has(attachment.url) : false); - } else { - const persistSet = (data ?? new Set()); - if (visible) persistSet.add(attachment.url); - else persistSet.delete(attachment.url); - await set(STORE_KEY, persistSet); - buildCss(); - } - }); + updateVisibility(); + if (visible && !url) initPdfData(); }, [visible]); useEffect(() => { attachment.previewBlobUrl = url; - updateMessage(channelId, messageId,); + updateMessage(channelId, messageId); return () => { if (url && settings.store.autoEmptyCache) { objectUrlsCache.delete(attachment.url); @@ -129,20 +119,20 @@ function PreviewButton({ attachment, channelId, messageId }: { attachment: Attac {tooltipProps => (
{ setVisible(v => !v); }} > - {visible ? : } + {visible ? : }
)} ; } export default definePlugin({ - name: "FileViewer", + name: "PdfViewer", description: "Preview PDF Files without having to download them", authors: [Devs.AGreenPig], dependencies: ["MessageAccessoriesAPI", "MessageUpdaterAPI",], @@ -157,8 +147,7 @@ export default definePlugin({ } ], start() { - objectUrlsCache.setMaxSize(Math.round(settings.store.cacheSize)); - addAccessory("fileViewer", props => { + addAccessory("pdfViewer", props => { const pdfAttachments = props.message.attachments.filter(a => a.content_type === "application/pdf"); if (!pdfAttachments.length) return null; @@ -170,18 +159,13 @@ export default definePlugin({ ); }, -1); - - style = document.createElement("style"); - style.id = "VencordFileViewer"; - document.head.appendChild(style); }, renderPreviewButton: ErrorBoundary.wrap(e => { if (e.item.originalItem.content_type !== "application/pdf") return null; return ; - }, + }), stop() { objectUrlsCache.clear(); - removeAccessory("fileViewer"); - style.remove(); + removeAccessory("pdfViewer"); } }); diff --git a/src/plugins/fileViewer/fileViewer.css b/src/plugins/pdfViewer.desktop/pdfViewer.css similarity index 69% rename from src/plugins/fileViewer/fileViewer.css rename to src/plugins/pdfViewer.desktop/pdfViewer.css index fc72cc2fa..71bc83f45 100644 --- a/src/plugins/fileViewer/fileViewer.css +++ b/src/plugins/pdfViewer.desktop/pdfViewer.css @@ -1,4 +1,4 @@ -.file-viewer.container { +.vc-pdf-viewer-container { resize: both; overflow: hidden; max-width: 100%; @@ -6,16 +6,18 @@ max-height: 80vh; min-height: 500px; box-sizing: border-box; - display: none; /* Will be set with buildCss */ + display: flex; + justify-content: center; + align-items: center; } -.file-viewer.preview { +.vc-pdf-viewer-preview { width: 100%; flex-grow: 1; border: none; } -.file-viewer.toggle { +.vc-pdf-viewer-toggle { justify-self: center; display: flex; justify-content: center; @@ -24,6 +26,6 @@ cursor: pointer; } -.file-viewer.toggle:hover { +.vc-pdf-viewer-toggle:hover { color: var(--interactive-hover); } From f20de977b37c07973a2b1b31b71f4af190520d0d Mon Sep 17 00:00:00 2001 From: TheGreenPig <67547385+TheGreenPig@users.noreply.github.com> Date: Wed, 18 Sep 2024 01:48:34 +0200 Subject: [PATCH 6/9] Stricter url fetch rules, use fetch, add image to readme --- src/plugins/fileViewer/README.md | 3 --- src/plugins/fileViewer/native.ts | 28 ------------------------- src/plugins/pdfViewer.desktop/README.md | 5 +++++ src/plugins/pdfViewer.desktop/native.ts | 28 +++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 31 deletions(-) delete mode 100644 src/plugins/fileViewer/README.md delete mode 100644 src/plugins/fileViewer/native.ts create mode 100644 src/plugins/pdfViewer.desktop/README.md create mode 100644 src/plugins/pdfViewer.desktop/native.ts diff --git a/src/plugins/fileViewer/README.md b/src/plugins/fileViewer/README.md deleted file mode 100644 index 11bf76ca9..000000000 --- a/src/plugins/fileViewer/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# FileViewer - -Allows you to Preview PDF Files without having to download them diff --git a/src/plugins/fileViewer/native.ts b/src/plugins/fileViewer/native.ts deleted file mode 100644 index d8b96469a..000000000 --- a/src/plugins/fileViewer/native.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { IpcMainInvokeEvent } from "electron"; -import { request } from "https"; - -export async function getBufferResponse(_: IpcMainInvokeEvent, url: string) { - return new Promise((resolve, reject) => { - const req = request(new URL(url), { method: "GET" }, res => { - const chunks: Uint8Array[] = []; - - res.on("data", function (chunk) { - chunks.push(chunk); - }); - - res.on("end", function () { - const body = Buffer.concat(chunks); - resolve(body); - }); - }); - req.on("error", reject); - req.end(); - }); -} - diff --git a/src/plugins/pdfViewer.desktop/README.md b/src/plugins/pdfViewer.desktop/README.md new file mode 100644 index 000000000..70fad73fa --- /dev/null +++ b/src/plugins/pdfViewer.desktop/README.md @@ -0,0 +1,5 @@ +# PdfViewer + +Allows you to Preview PDF Files without having to download them + +![](https://github.com/user-attachments/assets/f403dff5-5edc-4e57-8503-73f709df4842) diff --git a/src/plugins/pdfViewer.desktop/native.ts b/src/plugins/pdfViewer.desktop/native.ts new file mode 100644 index 000000000..29850446d --- /dev/null +++ b/src/plugins/pdfViewer.desktop/native.ts @@ -0,0 +1,28 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { IpcMainInvokeEvent } from "electron"; + +const urlChecks = [ + (url: URL) => url.host === "cdn.discordapp.com", + (url: URL) => url.pathname.startsWith("/attachments/"), + (url: URL) => url.pathname.endsWith(".pdf") +]; + +export async function getBufferResponse(_: IpcMainInvokeEvent, url: string): Promise { + const urlObj = new URL(url); + if (!urlChecks.every(check => check(urlObj))) { + throw new Error("Invalid URL"); + } + + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to fetch: ${response.statusText}`); + } + + const arrayBuffer = await response.arrayBuffer(); + return Buffer.from(arrayBuffer); +} From 754802e2126b34c2e4fc7084f5cd93991602e1d7 Mon Sep 17 00:00:00 2001 From: TheGreenPig <67547385+TheGreenPig@users.noreply.github.com> Date: Wed, 18 Sep 2024 01:49:16 +0200 Subject: [PATCH 7/9] Add Spinner component Co-Authored-By: Kyuuhachi --- src/webpack/common/components.ts | 6 +++++- src/webpack/common/types/components.d.ts | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index 8a2807ffe..1a238abab 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -49,6 +49,8 @@ export let ScrollerThin: t.ScrollerThin; export let Clickable: t.Clickable; export let Avatar: t.Avatar; export let FocusLock: t.FocusLock; +export let Spinner: t.Spinner; +export let SpinnerType: t.SpinnerType; // token lagger real /** css colour resolver stuff, no clue what exactly this does, just copied usage from Discord */ export let useToken: t.useToken; @@ -82,7 +84,9 @@ waitFor(["FormItem", "Button"], m => { Clickable, Avatar, FocusLock, - Heading + Heading, + Spinner, + SpinnerType, } = m); Forms = m; }); diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts index 6c7623339..822797a47 100644 --- a/src/webpack/common/types/components.d.ts +++ b/src/webpack/common/types/components.d.ts @@ -502,3 +502,22 @@ export type Avatar = ComponentType; }>>; + +export declare enum SpinnerType { + WANDERING_CUBES = "wanderingCubes", + CHASING_DOTS = "chasingDots", + PULSING_ELLIPSIS = "pulsingEllipsis", + SPINNING_CIRCLE = "spinningCircle", + SPINNING_CIRCLE_SIMPLE = "spinningCircleSimple", + LOW_MOTION = "lowMotion", +} + +export type Spinner = ComponentType<{ + type?: SpinnerType; + animated?: boolean; + className?: string; + itemClassName?: string; + "aria-label"?: string; +}> & { + Type: typeof SpinnerType; +}; From c274698b90cefbc52e1759a63d3592060a2d7aeb Mon Sep 17 00:00:00 2001 From: TheGreenPig <67547385+TheGreenPig@users.noreply.github.com> Date: Wed, 18 Sep 2024 02:06:38 +0200 Subject: [PATCH 8/9] Fix spinner position --- src/plugins/pdfViewer.desktop/index.tsx | 2 +- src/plugins/pdfViewer.desktop/pdfViewer.css | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plugins/pdfViewer.desktop/index.tsx b/src/plugins/pdfViewer.desktop/index.tsx index 28ca5f102..9443245f9 100644 --- a/src/plugins/pdfViewer.desktop/index.tsx +++ b/src/plugins/pdfViewer.desktop/index.tsx @@ -56,7 +56,7 @@ function FilePreview({ attachment }: { attachment: Attachment; }) { return (
- {previewBlobUrl ? : } + {previewBlobUrl ? :
}
); } diff --git a/src/plugins/pdfViewer.desktop/pdfViewer.css b/src/plugins/pdfViewer.desktop/pdfViewer.css index 71bc83f45..ff53b9f26 100644 --- a/src/plugins/pdfViewer.desktop/pdfViewer.css +++ b/src/plugins/pdfViewer.desktop/pdfViewer.css @@ -8,7 +8,6 @@ box-sizing: border-box; display: flex; justify-content: center; - align-items: center; } .vc-pdf-viewer-preview { From f7e7895453de09103157e21975fba1d9d28f9951 Mon Sep 17 00:00:00 2001 From: v Date: Wed, 18 Sep 2024 22:20:12 +0200 Subject: [PATCH 9/9] some room to breathe --- src/plugins/pdfViewer.desktop/index.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/plugins/pdfViewer.desktop/index.tsx b/src/plugins/pdfViewer.desktop/index.tsx index 9443245f9..59085a84e 100644 --- a/src/plugins/pdfViewer.desktop/index.tsx +++ b/src/plugins/pdfViewer.desktop/index.tsx @@ -137,6 +137,7 @@ export default definePlugin({ authors: [Devs.AGreenPig], dependencies: ["MessageAccessoriesAPI", "MessageUpdaterAPI",], settings, + patches: [ { find: "Messages.IMG_ALT_ATTACHMENT_FILE_TYPE.format", @@ -146,6 +147,7 @@ export default definePlugin({ } } ], + start() { addAccessory("pdfViewer", props => { const pdfAttachments = props.message.attachments.filter(a => a.content_type === "application/pdf"); @@ -160,12 +162,15 @@ export default definePlugin({ ); }, -1); }, - renderPreviewButton: ErrorBoundary.wrap(e => { - if (e.item.originalItem.content_type !== "application/pdf") return null; - return ; - }), + stop() { objectUrlsCache.clear(); removeAccessory("pdfViewer"); } + + renderPreviewButton: ErrorBoundary.wrap(e => { + if (e.item.originalItem.content_type !== "application/pdf") return null; + return ; + }, { noop: true }), + });