diff options
| author | dakkar <dakkar@thenautilus.net> | 2024-05-31 19:03:32 +0000 |
|---|---|---|
| committer | dakkar <dakkar@thenautilus.net> | 2024-05-31 19:03:32 +0000 |
| commit | e818c56faa796e7bc7ee43617a70357f89256f58 (patch) | |
| tree | 0e5e024579333222721536424088c57f8d827b43 /packages/frontend/src/scripts | |
| parent | merge: style: automatically detect RTL on all MFM content (!482) (diff) | |
| parent | changed grabbing theme color for dot to match the other things in this project (diff) | |
| download | sharkey-e818c56faa796e7bc7ee43617a70357f89256f58.tar.gz sharkey-e818c56faa796e7bc7ee43617a70357f89256f58.tar.bz2 sharkey-e818c56faa796e7bc7ee43617a70357f89256f58.zip | |
merge: Feature/favicon notification dot (!474)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/474
Approved-by: dakkar <dakkar@thenautilus.net>
Approved-by: Luna <her@mint.lgbt>
Approved-by: Marie <marie@kaifa.ch>
Diffstat (limited to 'packages/frontend/src/scripts')
| -rw-r--r-- | packages/frontend/src/scripts/favicon-dot.ts | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/packages/frontend/src/scripts/favicon-dot.ts b/packages/frontend/src/scripts/favicon-dot.ts new file mode 100644 index 0000000000..e338f55f72 --- /dev/null +++ b/packages/frontend/src/scripts/favicon-dot.ts @@ -0,0 +1,114 @@ +import tinycolor from 'tinycolor2'; + +class FavIconDot { + canvas: HTMLCanvasElement; + src: string | null = null; + ctx: CanvasRenderingContext2D | null = null; + faviconImage: HTMLImageElement | null = null; + faviconEL: HTMLLinkElement | undefined; + hasLoaded: Promise<void> | undefined; + + constructor() { + this.canvas = document.createElement('canvas'); + } + + /** + * Must be called before calling any other functions + */ + public async setup() { + const element: HTMLLinkElement = await this.getOrMakeFaviconElement(); + + this.faviconEL = element; + this.src = this.faviconEL.getAttribute('href'); + this.ctx = this.canvas.getContext('2d'); + + this.faviconImage = document.createElement('img'); + + this.hasLoaded = new Promise((resolve, reject) => { + (this.faviconImage as HTMLImageElement).addEventListener('load', () => { + this.canvas.width = (this.faviconImage as HTMLImageElement).width; + this.canvas.height = (this.faviconImage as HTMLImageElement).height; + resolve(); + }); + (this.faviconImage as HTMLImageElement).addEventListener('error', () => { + reject('Failed to create favicon img element'); + }); + }); + + this.faviconImage.src = this.faviconEL.href; + } + + private async getOrMakeFaviconElement(): Promise<HTMLLinkElement> { + return new Promise((resolve, reject) => { + const favicon = (document.querySelector('link[rel=icon]') ?? this.createFaviconElem()) as HTMLLinkElement; + favicon.addEventListener('load', () => { + resolve(favicon); + }); + + favicon.onerror = () => { + reject('Failed to load favicon'); + }; + resolve(favicon); + }); + } + + private createFaviconElem() { + const newLink = document.createElement('link'); + newLink.setAttribute('rel', 'icon'); + newLink.setAttribute('href', '/favicon.ico'); + newLink.setAttribute('type', 'image/x-icon'); + + document.head.appendChild(newLink); + return newLink; + } + + private drawIcon() { + if (!this.ctx || !this.faviconImage) return; + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + this.ctx.drawImage(this.faviconImage, 0, 0, this.faviconImage.width, this.faviconImage.height); + } + + private drawDot() { + if (!this.ctx || !this.faviconImage) return; + this.ctx.beginPath(); + this.ctx.arc(this.faviconImage.width - 10, 10, 10, 0, 2 * Math.PI); + const computedStyle = getComputedStyle(document.documentElement); + this.ctx.fillStyle = tinycolor(computedStyle.getPropertyValue('--navIndicator')).toHexString(); + this.ctx.strokeStyle = 'white'; + this.ctx.fill(); + this.ctx.stroke(); + } + + private setFavicon() { + if (this.faviconEL) this.faviconEL.href = this.canvas.toDataURL('image/png'); + } + + async setVisible(isVisible: boolean) { + // Wait for it to have loaded the icon + await this.hasLoaded; + this.drawIcon(); + if (isVisible) this.drawDot(); + this.setFavicon(); + } +} + +let icon: FavIconDot | undefined = undefined; + +export function setFavIconDot(visible: boolean) { + const setIconVisibility = async () => { + if (!icon) { + icon = new FavIconDot(); + await icon.setup(); + } + + (icon as FavIconDot).setVisible(visible); + }; + + // If document is already loaded, set visibility immediately + if (document.readyState === 'complete') { + setIconVisibility(); + } else { + // Otherwise, set visibility when window loads + window.addEventListener('load', setIconVisibility); + } +} |