diff --git a/src/effects/fireworks/index.ts b/src/effects/fireworks/index.ts new file mode 100644 index 0000000000..7c84e31f1d --- /dev/null +++ b/src/effects/fireworks/index.ts @@ -0,0 +1,167 @@ +/* + Copyright 2020 Nurjin Jafar + Copyright 2020 Nordeck IT + Consulting GmbH. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import ICanvasEffect from '../ICanvasEffect'; + +export type FireworksOptions = { + /** + * the maximum number of the fireworks + */ + maxCount: number, + /** + * the alpha opacity of the fireworks (between 0 and 1, where 1 is opaque and 0 is invisible) + */ + gravity: number, + probability: number, +} + +type FireworksParticle = { + color: string, + x: number, + y: number, + vx: number, + vy: number, + alpha: number, + w: number, + h: number +} + +export const DefaultOptions: FireworksOptions = { + maxCount: 500, + gravity: 0.05, + probability: 0.04, +}; + +export default class Fireworks implements ICanvasEffect { + private readonly options: FireworksOptions; + + constructor(options: { [key: string]: any }) { + this.options = {...DefaultOptions, ...options}; + } + + private context: CanvasRenderingContext2D | null = null; + private supportsAnimationFrame = window.requestAnimationFrame || + function(callback) { + window.setTimeout(callback, 1000/60); + }; + private particles: Array = []; + public isRunning: boolean; + + public start = async (canvas: HTMLCanvasElement, timeout = 4000) => { + if (!canvas) { + return; + } + this.isRunning = true; + this.context = canvas.getContext('2d'); + this.supportsAnimationFrame.call(window, this.updateWorld); + if (timeout) { + window.setTimeout(this.stop, timeout); + } + } + + private updateWorld = () => { + if (!this.isRunning) return; + this.update(); + this.paint(); + this.supportsAnimationFrame.call(window, this.updateWorld); + } + private update = () => { + if (this.particles.length < this.options.maxCount && Math.random() < this.options.probability) { + this.createFirework(); + } + const alive = []; + for (let i=0; i { + if (!this.context || !this.context.canvas) return; + this.context.globalCompositeOperation = 'source-over'; + this.context.fillStyle = "rgba(0,0,0,0.2)"; + this.context.canvas.style.opacity = "0.8"; + this.context.fillRect(0, 0, this.context.canvas.width, this.context.canvas.height); + this.context.globalCompositeOperation = 'lighter'; + for (let i=0; i { + if (!this.context || !this.context.canvas) return; + const width = this.context.canvas.width; + const height = this.context.canvas.height; + const xPoint = Math.random() * (width - 200) + 100; + const yPoint = Math.random() * (height - 200) + 100; + const nFire = Math.random() * 50 + 100; + const color = "rgb("+(~~(Math.random()*200+55))+"," + +(~~(Math.random()*200+55))+","+(~~(Math.random()*200+55))+")"; + for (let i=0; i{}; + particle.color = color; + particle.w = particle.h = Math.random() * 4 + 1; + particle.x = xPoint - particle.w / 2; + particle.y = yPoint - particle.h / 2; + particle.vx = (Math.random()-0.5)*10; + particle.vy = (Math.random()-0.5)*10; + particle.alpha = Math.random()*.5+.5; + const vy = Math.sqrt(25 - particle.vx * particle.vx); + if (Math.abs(particle.vy) > vy) { + particle.vy = particle.vy > 0 ? vy: -vy; + } + this.particles.push(particle); + } + } + + public stop = async () => { + this.isRunning = false; + this.particles = []; + this.context.canvas.style.opacity = "1"; + this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height); + } + + private drawParticle = (particle: FireworksParticle): void => { + if (!this.context || !this.context.canvas) { + return; + } + this.context.save(); + this.context.beginPath(); + + this.context.translate(particle.x + particle.w / 2, particle.y + particle.h / 2); + this.context.arc(0, 0, particle.w, 0, Math.PI * 2); + this.context.fillStyle = particle.color; + this.context.globalAlpha = particle.alpha; + + this.context.closePath(); + this.context.fill(); + this.context.restore(); + } + + + private move = (particle: FireworksParticle) => { + particle.x += particle.vx; + particle.vy += this.options.gravity; + particle.y += particle.vy; + particle.alpha -= 0.01; + return !(particle.x <= -particle.w || particle.x >= screen.width || + particle.y >= screen.height || + particle.alpha <= 0); + } +} diff --git a/src/effects/index.ts b/src/effects/index.ts index 16a0851070..27ed73622f 100644 --- a/src/effects/index.ts +++ b/src/effects/index.ts @@ -64,6 +64,11 @@ type ConfettiOptions = { * use gradient instead of solid particle color */ gradient: boolean, +}; +type FireworksOptions = { + maxCount: number, + gravity: number, + probability: number, } /** @@ -84,6 +89,18 @@ export const CHAT_EFFECTS: Array> = [ gradient: false, }, } as Effect, + { + emojis: ['🎆'], + msgType: 'nic.custom.fireworks', + command: 'fireworks', + description: () => _td("Sends the given message with fireworks"), + fallbackMessage: () => _t("sends fireworks") + "🎆", + options: { + maxCount: 500, + gravity: 0.05, + probability: 0.04, + }, + } as Effect, ]; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index eb1d0a632e..47d0eedf6f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -849,6 +849,8 @@ "sends confetti": "sends confetti", "You held the call Resume": "You held the call Resume", "%(peerName)s held the call": "%(peerName)s held the call", + "sends fireworks": "sends fireworks", + "Sends the given message with fireworks": "Sends the given message with fireworks", "Video Call": "Video Call", "Voice Call": "Voice Call", "Fill Screen": "Fill Screen",