Forráskód Böngészése

Refine the `converse-gif` component

JC Brand 4 hónapja
szülő
commit
30ba5a4a2c

+ 1 - 1
src/shared/components/image.js

@@ -1,5 +1,5 @@
 import { api, u } from "@converse/headless";
-import tplGif from 'templates/gif.js';
+import tplGif from 'shared/texture/templates/gif.js';
 import tplImage from 'templates/image.js';
 import { CustomElement } from './element.js';
 import { shouldRenderMediaFromURL } from 'utils/url.js';

+ 1 - 1
src/shared/components/gif.js → src/shared/gif/component.js

@@ -4,7 +4,7 @@ import { api } from '@converse/headless';
 import { getHyperlinkTemplate } from 'utils/html.js';
 import { html } from 'lit';
 
-import './styles/gif.scss';
+import './styles.scss';
 
 export default class ConverseGIFElement extends CustomElement {
     static get properties () {

+ 56 - 66
src/shared/gif/index.js

@@ -1,24 +1,14 @@
-import { log } from '@converse/headless';
-import { getOpenPromise } from '@converse/openpromise';
-import { parseGIF, decompressFrames } from 'gifuct-js';
+import { log } from "@converse/headless";
+import { getOpenPromise } from "@converse/openpromise";
+import { parseGIF, decompressFrames } from "gifuct-js";
 
 export default class ConverseGif {
     /**
      * Creates a new ConverseGif instance
-     * @param { import('lit').LitElement } el
-     * @param { Object } [options]
-     * @param { Number } [options.width] - The width, in pixels, of the canvas
-     * @param { Number } [options.height] - The height, in pixels, of the canvas
-     * @param { Boolean } [options.loop=true] - Setting this to `true` will enable looping of the gif
-     * @param { Boolean } [options.autoplay=true] - Same as the rel:autoplay attribute above, this arg overrides the img tag info.
-     * @param { Number } [options.max_width] - Scale images over max_width down to max_width. Helpful with mobile.
-     * @param { Function } [options.onIterationEnd] - Add a callback for when the gif reaches the end of a single loop (one iteration). The first argument passed will be the gif HTMLElement.
-     * @param { Boolean } [options.show_progress_bar=true]
-     * @param { String } [options.progress_bg_color='rgba(0,0,0,0.4)']
-     * @param { String } [options.progress_color='rgba(255,0,22,.8)']
-     * @param { Number } [options.progress_bar_height=5]
+     * @param {import('lit').LitElement} el
+     * @param {import("./types").ConverseGifOptions} opts
      */
-    constructor (el, opts) {
+    constructor(el, opts) {
         this.options = Object.assign(
             {
                 width: null,
@@ -26,22 +16,22 @@ export default class ConverseGif {
                 autoplay: true,
                 loop: true,
                 show_progress_bar: true,
-                progress_bg_color: 'rgba(0,0,0,0.4)',
-                progress_color: 'rgba(255,0,22,.8)',
+                progress_bg_color: "rgba(0,0,0,0.4)",
+                progress_color: "rgba(255,0,22,.8)",
                 progress_bar_height: 5,
             },
             opts
         );
 
         this.el = el;
-        this.gif_el = el.querySelector('img');
-        this.canvas = el.querySelector('canvas');
-        this.ctx = this.canvas.getContext('2d');
+        this.gif_el = el.querySelector("img");
+        this.canvas = el.querySelector("canvas");
+        this.ctx = this.canvas.getContext("2d");
 
         // Offscreen canvas with full gif
-        this.offscreenCanvas = document.createElement('canvas');
+        this.offscreenCanvas = document.createElement("canvas");
         // Offscreen canvas for patches
-        this.patchCanvas = document.createElement('canvas');
+        this.patchCanvas = document.createElement("canvas");
 
         this.ctx_scaled = false;
         this.frames = [];
@@ -58,7 +48,7 @@ export default class ConverseGif {
         this.initialize();
     }
 
-    async initialize () {
+    async initialize() {
         if (this.options.width && this.options.height) {
             this.setSizes(this.options.width, this.options.height);
         }
@@ -66,7 +56,7 @@ export default class ConverseGif {
         requestAnimationFrame(() => this.handleGIFResponse(data));
     }
 
-    initPlayer () {
+    initPlayer() {
         if (this.load_error) return;
 
         if (!(this.options.width && this.options.height)) {
@@ -87,7 +77,7 @@ export default class ConverseGif {
      * Gets the index of the frame "up next"
      * @returns {number}
      */
-    getNextFrameNo () {
+    getNextFrameNo() {
         if (this.frames.length === 0) {
             return 0;
         }
@@ -96,11 +86,11 @@ export default class ConverseGif {
 
     /**
      * Called once we've looped through all frames in the GIF
-     * @returns { Boolean } - Returns `true` if the GIF is now paused (i.e. further iterations are not desired)
+     * @returns {Boolean} - Returns `true` if the GIF is now paused (i.e. further iterations are not desired)
      */
-    onIterationEnd () {
+    onIterationEnd() {
         this.iteration_count++;
-        this.options.onIterationEnd?.(this);
+        this.options.onIterationEnd?.(this.el);
         if (!this.options.loop) {
             this.pause();
             return true;
@@ -125,14 +115,14 @@ export default class ConverseGif {
      * `renderImage(0)` needs to be called *before* this method, otherwise the
      * animation will incorrectly start from frame #1 (this is done in `initPlayer`).
      *
-     * @param { DOMHighResTimeStamp } timestamp - The timestamp as returned by `requestAnimationFrame`
-     * @param { DOMHighResTimeStamp } previous_timestamp - The timestamp from the previous iteration of this method.
+     * @param {DOMHighResTimeStamp} timestamp - The timestamp as returned by `requestAnimationFrame`
+     * @param {DOMHighResTimeStamp} previous_timestamp - The timestamp from the previous iteration of this method.
      * We need this in order to calculate whether we have waited long enough to
      * show the next frame.
-     * @param { Number } frame_delay - The delay (in 1/100th of a second)
+     * @param {Number} frame_delay - The delay (in 1/100th of a second)
      * before the currently being shown frame should be replaced by a new one.
      */
-    onAnimationFrame (timestamp, previous_timestamp, frame_delay) {
+    onAnimationFrame(timestamp, previous_timestamp, frame_delay) {
         if (!this.playing) {
             return;
         }
@@ -152,18 +142,18 @@ export default class ConverseGif {
         requestAnimationFrame((ts) => this.onAnimationFrame(ts, timestamp, delay));
     }
 
-    setSizes (w, h) {
+    setSizes(w, h) {
         this.canvas.width = w * this.getCanvasScale();
         this.canvas.height = h * this.getCanvasScale();
 
         this.offscreenCanvas.width = w;
         this.offscreenCanvas.height = h;
-        this.offscreenCanvas.style.width = w + 'px';
-        this.offscreenCanvas.style.height = h + 'px';
-        this.offscreenCanvas.getContext('2d').setTransform(1, 0, 0, 1, 0, 0);
+        this.offscreenCanvas.style.width = w + "px";
+        this.offscreenCanvas.style.height = h + "px";
+        this.offscreenCanvas.getContext("2d").setTransform(1, 0, 0, 1, 0, 0);
     }
 
-    doShowProgress (pos, length, draw) {
+    doShowProgress(pos, length, draw) {
         if (draw && this.options.show_progress_bar) {
             let height = this.options.progress_bar_height;
             const top = (this.canvas.height - height) / (this.ctx_scaled ? this.getCanvasScale() : 1);
@@ -184,7 +174,7 @@ export default class ConverseGif {
      * a map of handler functions.
      * @param {ArrayBuffer} data - The GIF file data, as returned by the server
      */
-    handleGIFResponse (data) {
+    handleGIFResponse(data) {
         try {
             const gif = parseGIF(data);
             this.hdr = gif.header;
@@ -198,10 +188,10 @@ export default class ConverseGif {
         !this.options.autoplay && this.drawPlayIcon();
     }
 
-    drawError () {
-        this.ctx.fillStyle = 'black';
+    drawError() {
+        this.ctx.fillStyle = "black";
         this.ctx.fillRect(0, 0, this.options.width, this.options.height);
-        this.ctx.strokeStyle = 'red';
+        this.ctx.strokeStyle = "red";
         this.ctx.lineWidth = 3;
         this.ctx.moveTo(0, 0);
         this.ctx.lineTo(this.options.width, this.options.height);
@@ -210,7 +200,7 @@ export default class ConverseGif {
         this.ctx.stroke();
     }
 
-    showError () {
+    showError() {
         this.load_error = true;
         this.hdr = {
             width: this.gif_el.width,
@@ -221,10 +211,10 @@ export default class ConverseGif {
         this.el.requestUpdate();
     }
 
-    manageDisposal (i) {
+    manageDisposal(i) {
         if (i <= 0) return;
 
-        const offscreenContext = this.offscreenCanvas.getContext('2d');
+        const offscreenContext = this.offscreenCanvas.getContext("2d");
         const disposal = this.frames[i - 1].disposalType;
         /*
          *  Disposal method indicates the way in which the graphic is to
@@ -271,7 +261,7 @@ export default class ConverseGif {
      * Draws a gif frame at a specific index inside the canvas.
      * @param {boolean} show_pause_on_hover - The frame index
      */
-    renderImage (show_pause_on_hover = true) {
+    renderImage(show_pause_on_hover = true) {
         if (!this.frames.length) return;
 
         let i = this.frame_idx;
@@ -283,8 +273,8 @@ export default class ConverseGif {
         this.manageDisposal(i);
 
         const frame = this.frames[i];
-        const patchContext = this.patchCanvas.getContext('2d');
-        const offscreenContext = this.offscreenCanvas.getContext('2d');
+        const patchContext = this.patchCanvas.getContext("2d");
+        const offscreenContext = this.offscreenCanvas.getContext("2d");
         const dims = frame.dims;
         if (
             !this.frameImageData ||
@@ -317,7 +307,7 @@ export default class ConverseGif {
     /**
      * Start playing the gif
      */
-    play () {
+    play() {
         this.playing = true;
         requestAnimationFrame((ts) => this.onAnimationFrame(ts, 0, 0));
     }
@@ -325,19 +315,19 @@ export default class ConverseGif {
     /**
      * Pause the gif
      */
-    pause () {
+    pause() {
         this.playing = false;
         requestAnimationFrame(() => this.drawPlayIcon());
     }
 
-    drawPauseIcon () {
+    drawPauseIcon() {
         if (!this.playing) return;
 
         // Clear the potential play button by re-rendering the current frame
         this.renderImage(false);
 
         // Draw dark overlay
-        this.ctx.fillStyle = 'rgb(0, 0, 0, 0.25)';
+        this.ctx.fillStyle = "rgb(0, 0, 0, 0.25)";
         this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
 
         const icon_size = this.canvas.height * 0.1;
@@ -346,30 +336,30 @@ export default class ConverseGif {
         this.ctx.beginPath();
         this.ctx.moveTo(this.canvas.width / 2 - icon_size / 2, this.canvas.height / 2 - icon_size);
         this.ctx.lineTo(this.canvas.width / 2 - icon_size / 2, this.canvas.height / 2 + icon_size);
-        this.ctx.fillStyle = 'rgb(200, 200, 200, 0.75)';
+        this.ctx.fillStyle = "rgb(200, 200, 200, 0.75)";
         this.ctx.stroke();
 
         this.ctx.beginPath();
         this.ctx.moveTo(this.canvas.width / 2 + icon_size / 2, this.canvas.height / 2 - icon_size);
         this.ctx.lineTo(this.canvas.width / 2 + icon_size / 2, this.canvas.height / 2 + icon_size);
-        this.ctx.fillStyle = 'rgb(200, 200, 200, 0.75)';
+        this.ctx.fillStyle = "rgb(200, 200, 200, 0.75)";
         this.ctx.stroke();
 
         // Draw circle
         this.ctx.lineWidth = this.canvas.height * 0.02;
-        this.ctx.strokeStyle = 'rgb(200, 200, 200, 0.75)';
+        this.ctx.strokeStyle = "rgb(200, 200, 200, 0.75)";
         this.ctx.beginPath();
         this.ctx.arc(this.canvas.width / 2, this.canvas.height / 2, icon_size * 1.5, 0, 2 * Math.PI);
         this.ctx.stroke();
     }
 
-    drawPlayIcon () {
+    drawPlayIcon() {
         if (this.playing) return;
 
         // Clear the potential pause button by re-rendering the current frame
         this.renderImage(false);
         // Draw dark overlay
-        this.ctx.fillStyle = 'rgb(0, 0, 0, 0.25)';
+        this.ctx.fillStyle = "rgb(0, 0, 0, 0.25)";
         this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
 
         // Draw triangle
@@ -379,19 +369,19 @@ export default class ConverseGif {
         region.lineTo(this.canvas.width / 2 - triangle_size / 2, this.canvas.height / 2 + triangle_size);
         region.lineTo(this.canvas.width / 2 - triangle_size / 2, this.canvas.height / 2 - triangle_size);
         region.closePath();
-        this.ctx.fillStyle = 'rgb(200, 200, 200, 0.75)';
+        this.ctx.fillStyle = "rgb(200, 200, 200, 0.75)";
         this.ctx.fill(region);
 
         // Draw circle
         const circle_size = triangle_size * 1.5;
         this.ctx.lineWidth = this.canvas.height * 0.02;
-        this.ctx.strokeStyle = 'rgb(200, 200, 200, 0.75)';
+        this.ctx.strokeStyle = "rgb(200, 200, 200, 0.75)";
         this.ctx.beginPath();
         this.ctx.arc(this.canvas.width / 2, this.canvas.height / 2, circle_size, 0, 2 * Math.PI);
         this.ctx.stroke();
     }
 
-    getCanvasScale () {
+    getCanvasScale() {
         let scale;
         if (this.options.max_width && this.hdr && this.lsd.width > this.options.max_width) {
             scale = this.options.max_width / this.lsd.width;
@@ -403,16 +393,16 @@ export default class ConverseGif {
 
     /**
      * Makes an HTTP request to fetch a GIF
-     * @param { String } url
-     * @returns { Promise<ArrayBuffer> } Returns a promise which resolves with the response data.
+     * @param {string} url
+     * @returns {Promise<ArrayBuffer>} Returns a promise which resolves with the response data.
      */
-    fetchGIF (url) {
+    fetchGIF(url) {
         const promise = getOpenPromise();
         const h = new XMLHttpRequest();
-        h.open('GET', url, true);
-        h.responseType = 'arraybuffer';
+        h.open("GET", url, true);
+        h.responseType = "arraybuffer";
 
-        h?.overrideMimeType('text/plain; charset=x-user-defined');
+        h?.overrideMimeType("text/plain; charset=x-user-defined");
         h.onload = () => {
             if (h.status != 200) {
                 this.showError();

+ 0 - 0
src/shared/components/styles/gif.scss → src/shared/gif/styles.scss


+ 12 - 0
src/shared/gif/types.ts

@@ -0,0 +1,12 @@
+export interface ConverseGifOptions {
+    autoplay?: boolean;
+    height?: number; // The height, in pixels, of the canvas
+    loop?: boolean; // Setting this to `true` will enable looping of the gif
+    max_width?: number; // Scale images over max_width down to max_width. Helpful with mobile.
+    onIterationEnd?: (gifElement: HTMLElement) => void; // Callback for when the gif reaches the end of a single loop (one iteration)
+    progress_bar_height?: number; // Default is 5
+    progress_bg_color?: string; // Default is 'rgba(0,0,0,0.4)'
+    progress_color?: string; // Default is 'rgba(255,0,22,.8)'
+    show_progress_bar?: boolean;
+    width?: number; // The width, in pixels, of the canvas
+}

+ 1 - 1
src/templates/gif.js → src/shared/texture/templates/gif.js

@@ -1,5 +1,5 @@
 import { html } from 'lit';
-import 'shared/components/gif.js';
+import 'shared/gif/component.js';
 
 /**
  * @param {string} url

+ 1 - 1
src/shared/texture/texture.js

@@ -3,7 +3,7 @@ import { until } from 'lit/directives/until.js';
 import { Directive, directive } from 'lit/directive.js';
 import { api, u } from '@converse/headless';
 import tplAudio from 'templates/audio.js';
-import tplGif from 'templates/gif.js';
+import tplGif from './templates/gif.js';
 import tplImage from 'templates/image.js';
 import tplVideo from 'templates/video.js';
 import tplSpotify from 'templates/spotify.js';

+ 36 - 0
src/types/shared/gif/component.d.ts

@@ -0,0 +1,36 @@
+export default class ConverseGIFElement extends CustomElement {
+    static get properties(): {
+        autoplay: {
+            type: BooleanConstructor;
+        };
+        noloop: {
+            type: BooleanConstructor;
+        };
+        progress_color: {
+            type: StringConstructor;
+        };
+        fallback: {
+            type: StringConstructor;
+        };
+        src: {
+            type: StringConstructor;
+        };
+    };
+    src: any;
+    autoplay: boolean;
+    noloop: boolean;
+    fallback: string;
+    progress_color: any;
+    initGIF(): void;
+    supergif: ConverseGif;
+    updated(changed: any): void;
+    render(): string | import("utils/html.js").TemplateResult;
+    renderErrorFallback(): string | import("utils/html.js").TemplateResult;
+    setHover(): void;
+    hover_timeout: NodeJS.Timeout;
+    unsetHover(): void;
+    onControlsClicked(ev: any): void;
+}
+import { CustomElement } from 'shared/components/element.js';
+import ConverseGif from 'shared/gif/index.js';
+//# sourceMappingURL=component.d.ts.map

+ 20 - 21
src/types/shared/gif/index.d.ts

@@ -1,21 +1,20 @@
 export default class ConverseGif {
     /**
      * Creates a new ConverseGif instance
-     * @param { import('lit').LitElement } el
-     * @param { Object } [options]
-     * @param { Number } [options.width] - The width, in pixels, of the canvas
-     * @param { Number } [options.height] - The height, in pixels, of the canvas
-     * @param { Boolean } [options.loop=true] - Setting this to `true` will enable looping of the gif
-     * @param { Boolean } [options.autoplay=true] - Same as the rel:autoplay attribute above, this arg overrides the img tag info.
-     * @param { Number } [options.max_width] - Scale images over max_width down to max_width. Helpful with mobile.
-     * @param { Function } [options.onIterationEnd] - Add a callback for when the gif reaches the end of a single loop (one iteration). The first argument passed will be the gif HTMLElement.
-     * @param { Boolean } [options.show_progress_bar=true]
-     * @param { String } [options.progress_bg_color='rgba(0,0,0,0.4)']
-     * @param { String } [options.progress_color='rgba(255,0,22,.8)']
-     * @param { Number } [options.progress_bar_height=5]
+     * @param {import('lit').LitElement} el
+     * @param {import("./types").ConverseGifOptions} opts
      */
-    constructor(el: import("lit").LitElement, opts: any);
-    options: any;
+    constructor(el: import("lit").LitElement, opts: import("./types").ConverseGifOptions);
+    options: {
+        width: any;
+        height: any;
+        autoplay: boolean;
+        loop: boolean;
+        show_progress_bar: boolean;
+        progress_bg_color: string;
+        progress_color: string;
+        progress_bar_height: number;
+    } & import("./types").ConverseGifOptions;
     el: import("lit").LitElement;
     gif_el: HTMLImageElement;
     canvas: HTMLCanvasElement;
@@ -25,7 +24,7 @@ export default class ConverseGif {
     ctx_scaled: boolean;
     frames: any[];
     load_error: boolean;
-    playing: any;
+    playing: boolean;
     frame_idx: number;
     iteration_count: number;
     start: any;
@@ -41,7 +40,7 @@ export default class ConverseGif {
     getNextFrameNo(): number;
     /**
      * Called once we've looped through all frames in the GIF
-     * @returns { Boolean } - Returns `true` if the GIF is now paused (i.e. further iterations are not desired)
+     * @returns {Boolean} - Returns `true` if the GIF is now paused (i.e. further iterations are not desired)
      */
     onIterationEnd(): boolean;
     /**
@@ -61,11 +60,11 @@ export default class ConverseGif {
      * `renderImage(0)` needs to be called *before* this method, otherwise the
      * animation will incorrectly start from frame #1 (this is done in `initPlayer`).
      *
-     * @param { DOMHighResTimeStamp } timestamp - The timestamp as returned by `requestAnimationFrame`
-     * @param { DOMHighResTimeStamp } previous_timestamp - The timestamp from the previous iteration of this method.
+     * @param {DOMHighResTimeStamp} timestamp - The timestamp as returned by `requestAnimationFrame`
+     * @param {DOMHighResTimeStamp} previous_timestamp - The timestamp from the previous iteration of this method.
      * We need this in order to calculate whether we have waited long enough to
      * show the next frame.
-     * @param { Number } frame_delay - The delay (in 1/100th of a second)
+     * @param {Number} frame_delay - The delay (in 1/100th of a second)
      * before the currently being shown frame should be replaced by a new one.
      */
     onAnimationFrame(timestamp: DOMHighResTimeStamp, previous_timestamp: DOMHighResTimeStamp, frame_delay: number): void;
@@ -118,8 +117,8 @@ export default class ConverseGif {
     getCanvasScale(): number;
     /**
      * Makes an HTTP request to fetch a GIF
-     * @param { String } url
-     * @returns { Promise<ArrayBuffer> } Returns a promise which resolves with the response data.
+     * @param {string} url
+     * @returns {Promise<ArrayBuffer>} Returns a promise which resolves with the response data.
      */
     fetchGIF(url: string): Promise<ArrayBuffer>;
 }

+ 13 - 0
src/types/shared/gif/types.d.ts

@@ -0,0 +1,13 @@
+export interface ConverseGifOptions {
+    autoplay?: boolean;
+    height?: number;
+    loop?: boolean;
+    max_width?: number;
+    onIterationEnd?: (gifElement: HTMLElement) => void;
+    progress_bar_height?: number;
+    progress_bg_color?: string;
+    progress_color?: string;
+    show_progress_bar?: boolean;
+    width?: number;
+}
+//# sourceMappingURL=types.d.ts.map

+ 3 - 0
src/types/shared/texture/templates/gif.d.ts

@@ -0,0 +1,3 @@
+declare function _default(url: string, hide_url: boolean): import("lit").TemplateResult<1>;
+export default _default;
+//# sourceMappingURL=gif.d.ts.map