diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index d12f5bc..797b24e 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -102,7 +102,7 @@ class Direct extends Guild { const div = document.createElement("div"); div.classList.add("flexltr", "liststyle"); user.bind(div); - div.append(user.buildpfp()); + div.append(user.buildpfp(undefined, div)); const userinfos = document.createElement("div"); userinfos.classList.add("flexttb"); @@ -417,7 +417,7 @@ class Group extends Channel { const myhtml = document.createElement("span"); myhtml.classList.add("ellipsis"); myhtml.textContent = this.name; - div.appendChild(this.user.buildpfp()); + div.appendChild(this.user.buildpfp(undefined, div)); div.appendChild(myhtml); (div as any).myinfo = this; div.onclick = (_) => { diff --git a/src/webpage/embed.ts b/src/webpage/embed.ts index e5eba7a..0c3bdc2 100644 --- a/src/webpage/embed.ts +++ b/src/webpage/embed.ts @@ -1,7 +1,7 @@ import {Message} from "./message.js"; import {MarkDown} from "./markdown.js"; import {embedjson, invitejson} from "./jsontypes.js"; -import {getapiurls, getBulkUsers, getInstances, Specialuser} from "./utils/utils.js"; +import {createImg, getapiurls, getBulkUsers, getInstances, Specialuser} from "./utils/utils.js"; import {Guild} from "./guild.js"; import {I18n} from "./i18n.js"; import {ImagesDisplay} from "./disimg.js"; @@ -171,7 +171,7 @@ class Embed { return div; } generateImage() { - const img = document.createElement("img"); + const img = createImg(this.json.thumbnail.proxy_url); img.classList.add("messageimg"); img.onclick = function () { const full = new ImagesDisplay([ @@ -179,7 +179,6 @@ class Embed { ]); full.show(); }; - img.src = this.json.thumbnail.proxy_url; if (this.json.thumbnail.width) { let scale = 1; const max = 96 * 3; diff --git a/src/webpage/file.ts b/src/webpage/file.ts index 1ac1af8..b213c03 100644 --- a/src/webpage/file.ts +++ b/src/webpage/file.ts @@ -3,6 +3,7 @@ import {filejson} from "./jsontypes.js"; import {ImagesDisplay} from "./disimg.js"; import {makePlayBox, MediaPlayer} from "./media.js"; import {I18n} from "./i18n.js"; +import {createImg} from "./utils/utils.js"; class File { owner: Message | null; id: string; @@ -47,9 +48,10 @@ class File { this.width /= scale; this.height /= scale; } + if (this.content_type.startsWith("image/")) { const div = document.createElement("div"); - const img = document.createElement("img"); + const img = createImg(src); if (!fullScreen) { img.classList.add("messageimg"); div.classList.add("messageimgdiv"); @@ -66,7 +68,6 @@ class File { full.show(); } }; - img.src = src; div.append(img); if (this.width && !fullScreen) { div.style.maxWidth = this.width + "px"; diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index 551713c..8e48644 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -19,6 +19,7 @@ import {User} from "./user.js"; import {I18n} from "./i18n.js"; import {Emoji} from "./emoji.js"; import {webhookMenu} from "./webhooks.js"; +import {createImg} from "./utils/utils.js"; class Guild extends SnowFlake { owner!: Localuser; @@ -745,9 +746,8 @@ class Guild extends SnowFlake { icon = guild.icon; } if (icon !== null) { - const img = document.createElement("img"); + const img = createImg(guild.info.cdn + "/icons/" + guild.id + "/" + icon + ".png"); img.classList.add("pfp", "servericon"); - img.src = guild.info.cdn + "/icons/" + guild.id + "/" + icon + ".png"; divy.appendChild(img); if (guild instanceof Guild) { img.onclick = () => { diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 486cb94..d90c3b9 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -3,7 +3,7 @@ import {Channel} from "./channel.js"; import {Direct} from "./direct.js"; import {AVoice} from "./audio/voice.js"; import {User} from "./user.js"; -import {getapiurls, getBulkUsers, SW} from "./utils/utils.js"; +import {createImg, getapiurls, getBulkUsers, SW} from "./utils/utils.js"; import {getBulkInfo, setTheme, Specialuser} from "./utils/utils.js"; import { channeljson, @@ -88,8 +88,7 @@ class Localuser { const userInfo = document.createElement("div"); userInfo.classList.add("flexltr", "switchtable"); - const pfp = document.createElement("img"); - pfp.src = specialUser.pfpsrc; + const pfp = createImg(specialUser.pfpsrc); pfp.classList.add("pfp"); userInfo.append(pfp); @@ -1244,24 +1243,26 @@ class Localuser { content.classList.add("discovery-guild"); if (guild.banner) { - const banner = document.createElement("img"); + const banner = createImg( + this.info.cdn + "/icons/" + guild.id + "/" + guild.banner + ".png?size=256", + ); banner.classList.add("banner"); banner.crossOrigin = "anonymous"; - banner.src = this.info.cdn + "/icons/" + guild.id + "/" + guild.banner + ".png?size=256"; banner.alt = ""; content.appendChild(banner); } const nameContainer = document.createElement("div"); nameContainer.classList.add("flex"); - const img = document.createElement("img"); + const img = createImg( + this.info.cdn + + (guild.icon + ? "/icons/" + guild.id + "/" + guild.icon + ".png?size=48" + : "/embed/avatars/3.png"), + ); img.classList.add("icon"); img.crossOrigin = "anonymous"; - img.src = - this.info.cdn + - (guild.icon - ? "/icons/" + guild.id + "/" + guild.icon + ".png?size=48" - : "/embed/avatars/3.png"); + img.alt = ""; nameContainer.appendChild(img); @@ -1797,6 +1798,20 @@ class Localuser { initState: !this.perminfo.user.disableColors, }, ); + const gifSettings = ["hover", "always", "never"] as const; + accessibility.addSelect( + I18n.accessibility.playGif(), + (i) => { + localStorage.setItem("gifSetting", gifSettings[i]); + }, + gifSettings.map((_) => I18n.accessibility.gifSettings[_]()), + { + defaultIndex: + ((gifSettings as readonly string[]).indexOf( + localStorage.getItem("gifSetting") as string, + ) + 1 || 1) - 1, + }, + ); } { const connections = settings.addButton(I18n.getTranslation("localuser.connections")); @@ -1891,15 +1906,14 @@ class Localuser { const container = document.createElement("div"); if (application.cover_image || application.icon) { - const cover = document.createElement("img"); - cover.crossOrigin = "anonymous"; - cover.src = + const cover = createImg( this.info.cdn + - "/app-icons/" + - application.id + - "/" + - (application.cover_image || application.icon) + - ".png?size=256"; + "/app-icons/" + + application.id + + "/" + + (application.cover_image || application.icon) + + ".png?size=256", + ); cover.alt = ""; cover.loading = "lazy"; container.appendChild(cover); diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 1a4fb44..1a8778b 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -574,7 +574,7 @@ class Message extends SnowFlake { this.message_reference || !messageTypes.has(premessage.type); if (combine) { - const pfp = this.author.buildpfp(); + const pfp = this.author.buildpfp(undefined, div); this.author.bind(pfp, this.guild, false); pfpRow.appendChild(pfp); } diff --git a/src/webpage/user.ts b/src/webpage/user.ts index 6edd6e1..68c908c 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -11,6 +11,7 @@ import {I18n} from "./i18n.js"; import {Direct} from "./direct.js"; import {Hover} from "./hover.js"; import {Dialog} from "./settings.js"; +import {createImg} from "./utils/utils.js"; class User extends SnowFlake { owner: Localuser; @@ -456,10 +457,9 @@ class User extends SnowFlake { } } - buildpfp(guild: Guild | void | Member | null): HTMLImageElement { - const pfp = document.createElement("img"); + buildpfp(guild: Guild | void | Member | null, hoverElm: void | HTMLElement): HTMLImageElement { + const pfp = createImg(this.getpfpsrc(), undefined, hoverElm); pfp.loading = "lazy"; - pfp.src = this.getpfpsrc(); pfp.classList.add("pfp"); pfp.classList.add("userid:" + this.id); if (guild) { @@ -467,9 +467,9 @@ class User extends SnowFlake { if (guild instanceof Guild) { const memb = await Member.resolveMember(this, guild); if (!memb) return; - pfp.src = memb.getpfpsrc(); + pfp.setSrcs(memb.getpfpsrc()); } else { - pfp.src = guild.getpfpsrc(); + pfp.setSrcs(guild.getpfpsrc()); } })(); } @@ -479,7 +479,7 @@ class User extends SnowFlake { async buildstatuspfp(guild: Guild | void | Member | null): Promise { const div = document.createElement("div"); div.classList.add("pfpDiv"); - const pfp = this.buildpfp(guild); + const pfp = this.buildpfp(guild, div); div.append(pfp); const status = document.createElement("div"); status.classList.add("statusDiv"); @@ -560,9 +560,10 @@ class User extends SnowFlake { changepfp(update: string | null): void { this.avatar = update; this.hypotheticalpfp = false; - const src = this.getpfpsrc(); - Array.from(document.getElementsByClassName("userid:" + this.id)).forEach((element) => { - (element as HTMLImageElement).src = src; + //const src = this.getpfpsrc(); + Array.from(document.getElementsByClassName("userid:" + this.id)).forEach((_element) => { + //(element as HTMLImageElement).src = src; + //FIXME }); } @@ -719,12 +720,14 @@ class User extends SnowFlake { for (const badgejson of badges) { const badge = document.createElement(badgejson.link ? "a" : "div"); badge.classList.add("badge"); - const img = document.createElement("img"); + let src: string; if (URL.canParse(badgejson.icon)) { - img.src = badgejson.icon; + src = badgejson.icon; } else { - img.src = this.info.cdn + "/badge-icons/" + badgejson.icon + ".png"; + src = this.info.cdn + "/badge-icons/" + badgejson.icon + ".png"; } + const img = createImg(src, undefined, badgediv); + badge.append(img); let hovertxt: string; if (badgejson.translate) { @@ -810,11 +813,11 @@ class User extends SnowFlake { return div; } getBanner(guild: Guild | null | Member): HTMLImageElement { - const banner = document.createElement("img"); + const banner = createImg(undefined); const bsrc = this.getBannerUrl(); if (bsrc) { - banner.src = bsrc; + banner.setSrcs(bsrc); banner.classList.add("banner"); } @@ -822,7 +825,7 @@ class User extends SnowFlake { if (guild instanceof Member) { const bsrc = guild.getBannerUrl(); if (bsrc) { - banner.src = bsrc; + banner.setSrcs(bsrc); banner.classList.add("banner"); } } else { @@ -830,7 +833,7 @@ class User extends SnowFlake { if (!memb) return; const bsrc = memb.getBannerUrl(); if (bsrc) { - banner.src = bsrc; + banner.setSrcs(bsrc); banner.classList.add("banner"); } }); diff --git a/src/webpage/utils/utils.ts b/src/webpage/utils/utils.ts index f30dd54..b07b918 100644 --- a/src/webpage/utils/utils.ts +++ b/src/webpage/utils/utils.ts @@ -583,6 +583,63 @@ export async function getapiurls(str: string): Promise< } return false; } +function isAnimated(src: string) { + return src.endsWith(".apng") || src.endsWith(".gif"); +} +const staticImgMap = new Map>(); +export function createImg( + src: string | undefined, + staticsrc: string | void, + elm: HTMLElement | void, +) { + const settings = + localStorage.getItem("gifSetting") || ("hover" as "hover") || "always" || "never"; + const img = document.createElement("img"); + elm ||= img; + img.crossOrigin = "anonymous"; + img.onload = async () => { + if (settings === "always") return; + if (!src) return; + if (isAnimated(src) && !staticsrc) { + let s = staticImgMap.get(src); + if (s) { + staticsrc = await s; + } else { + staticImgMap.set( + src, + new Promise(async (res) => { + const c = new OffscreenCanvas(img.naturalWidth, img.naturalHeight); + const ctx = c.getContext("2d"); + if (!ctx) return; + ctx.drawImage(img, 0, 0); + const blob = await c.convertToBlob(); + res(URL.createObjectURL(blob)); + }), + ); + staticsrc = (await staticImgMap.get(src)) as string; + } + img.src = staticsrc; + } + }; + elm.onmouseover = () => { + if (settings === "never") return; + if (img.src !== src && src) { + img.src = src; + } + }; + elm.onmouseleave = () => { + if (staticsrc && settings !== "always") { + img.src = staticsrc; + } + }; + img.src = settings !== "always" ? staticsrc || src || "" : src || ""; + return Object.assign(img, { + setSrcs: (nsrc: string, nstaticsrc: string | void) => { + src = nsrc; + staticsrc = nstaticsrc; + }, + }); +} /** * * This function takes in a string and checks if the string is a valid instance diff --git a/src/webpage/webhooks.ts b/src/webpage/webhooks.ts index 4437c0a..93ac879 100644 --- a/src/webpage/webhooks.ts +++ b/src/webpage/webhooks.ts @@ -155,7 +155,7 @@ async function webhookMenu( const nameBox = document.createElement("div"); nameBox.classList.add("flexttb"); nameBox.append(name); - const pfp = user.buildpfp(); + const pfp = user.buildpfp(undefined, div); div.append(pfp, nameBox); form.addHTMLArea(div); diff --git a/translations/en.json b/translations/en.json index 89fac01..4cf0821 100644 --- a/translations/en.json +++ b/translations/en.json @@ -147,7 +147,13 @@ "spoiler": "Spoiler", "accessibility": { "name": "Accessibility", - "roleColors": "Disable role colors" + "roleColors": "Disable role colors", + "playGif": "Play gifs:", + "gifSettings": { + "hover": "Hover", + "always": "Always", + "never": "Never" + } }, "searchGifs": "Search Tenor", "channel": {