From 4394a20f87922e768852dd693b7d3eb8ef3f90f4 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Tue, 18 Aug 2020 09:56:38 +0200 Subject: [PATCH 0001/1014] setting added to User Settings -> Preferences -> Timeline as an opt out for users with german translation --- .../views/settings/tabs/user/PreferencesUserSettingsTab.js | 1 + src/i18n/strings/de_DE.json | 3 ++- src/i18n/strings/en_EN.json | 1 + src/settings/Settings.ts | 5 +++++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index a77815a68c..6ed2fc2e39 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -49,6 +49,7 @@ export default class PreferencesUserSettingsTab extends React.Component { 'showAvatarChanges', 'showDisplaynameChanges', 'showImages', + 'dontShowChatEffects', ]; static ADVANCED_SETTINGS = [ diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 09dbcb2e18..edfe21d9d6 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2361,5 +2361,6 @@ "%(brand)s encountered an error during upload of:": "%(brand)s hat einen Fehler festgestellt beim hochladen von:", "Use your account to sign in to the latest version of the app at ": "Verwende dein Konto um dich an der neusten Version der App anzumelden", "We’re excited to announce Riot is now Element!": "Wir freuen uns bekanntzugeben: Riot ist jetzt Element!", - "Learn more at element.io/previously-riot": "Erfahre mehr unter element.io/previously-riot" + "Learn more at element.io/previously-riot": "Erfahre mehr unter element.io/previously-riot", + "Don't show chat effects": "Chat Effekte nicht zeigen" } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 974a96406f..98aee655fe 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -503,6 +503,7 @@ "Manually verify all remote sessions": "Manually verify all remote sessions", "IRC display name width": "IRC display name width", "Enable experimental, compact IRC style layout": "Enable experimental, compact IRC style layout", + "Don't show chat effects": "Don't show chat effects", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", "Uploading report": "Uploading report", diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 714d80f983..59a3a4799b 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -586,4 +586,9 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td("Enable experimental, compact IRC style layout"), default: false, }, + "dontShowChatEffects": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td("Don't show chat effects"), + default: false, + }, }; From ecd4d6e19ef58f6c0b99a94890a5cd82a53e7c2a Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Tue, 18 Aug 2020 17:57:51 +0200 Subject: [PATCH 0002/1014] test commit for confetti changes --- src/SlashCommands.tsx | 13 ++ src/components/structures/RoomView.js | 7 +- src/components/views/elements/Confetti.js | 209 ++++++++++++++++++++++ src/i18n/strings/de_DE.json | 3 +- src/i18n/strings/en_EN.json | 1 + 5 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 src/components/views/elements/Confetti.js diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 2063ad3149..2d4d484899 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -44,6 +44,7 @@ import { ensureDMExists } from "./createRoom"; import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload"; import { Action } from "./dispatcher/actions"; import { EffectiveMembership, getEffectiveMembership } from "./utils/membership"; +import {func} from "prop-types"; // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 interface HTMLInputEvent extends Event { @@ -1026,6 +1027,18 @@ export const Commands = [ }, category: CommandCategories.actions, }), + new Command({ + command: "confetti", + description: _td("Throws confetti animation in the chat room"), + args: '/confetti + ', + runFn: function(roomId, args, command) { + return success((async () => { + const cli = MatrixClientPeg.get(); + await cli.sendHtmlMessage(roomId, args); + })()); + }, + category: CommandCategories.messages, + }), // Command definitions for autocompletion ONLY: // /me is special because its not handled by SlashCommands.js and is instead done inside the Composer classes diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 9a61523941..85cb1df848 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -57,6 +57,7 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import { shieldStatusForRoom } from '../../utils/ShieldUtils'; import {Action} from "../../dispatcher/actions"; import {SettingLevel} from "../../settings/SettingLevel"; +import Confetti from "../views/elements/Confetti"; const DEBUG = false; let debuglog = function() {}; @@ -67,7 +68,7 @@ if (DEBUG) { // using bind means that we get to keep useful line numbers in the console debuglog = console.log.bind(console); } - +let confetti; export default createReactClass({ displayName: 'RoomView', propTypes: { @@ -624,12 +625,14 @@ export default createReactClass({ ev.preventDefault(); } }, - onAction: function(payload) { switch (payload.action) { case 'message_send_failed': case 'message_sent': this._checkIfAlone(this.state.room); + confetti = new Confetti('100', '100'); + console.log('confetti sent'); + confetti.animateConfetti('test', 'message'); break; case 'post_sticker_message': this.injectSticker( diff --git a/src/components/views/elements/Confetti.js b/src/components/views/elements/Confetti.js new file mode 100644 index 0000000000..e9dc2c34c0 --- /dev/null +++ b/src/components/views/elements/Confetti.js @@ -0,0 +1,209 @@ +import React from "react"; +import SettingsStore from "../../../../lib/settings/SettingsStore"; +import PropTypes from "prop-types"; + +export default class Confetti extends React.Component { + displayName: 'confetti'; + constructor(props) { + super(props); + this.animateConfetti = this.animateConfetti.bind(this); + this.confetti.start = this.startConfetti; + this.startConfetti = this.startConfetti.bind(this); + this.confetti.stop = this.stopConfetti; + this.confetti.remove = this.removeConfetti; + this.confetti.isRunning = this.isConfettiRunning; + } + static propTypes = { + width: PropTypes.string.isRequired, + height: PropTypes.string.isRequired, + } + confetti = { + //set max confetti count + maxCount: 150, + //set the particle animation speed + speed: 3, + //the confetti animation frame interval in milliseconds + frameInterval: 15, + //the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) + alpha: 1.0, + start: null, + }; + colors = ["rgba(30,144,255,", "rgba(107,142,35,", "rgba(255,215,0,", + "rgba(255,192,203,", "rgba(106,90,205,", "rgba(173,216,230,", + "rgba(238,130,238,", "rgba(152,251,152,", "rgba(70,130,180,", + "rgba(244,164,96,", "rgba(210,105,30,", "rgba(220,20,60,"]; + streamingConfetti = false; + animationTimer = null; + lastFrameTime = Date.now(); + particles = []; + waveAngle = 0; + context = null; + supportsAnimationFrame = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame; + + resetParticle(particle, width, height) { + particle.color = this.colors[(Math.random() * this.colors.length) | 0] + (this.confetti.alpha + ")"); + particle.color2 = this.colors[(Math.random() * this.colors.length) | 0] + (this.confetti.alpha + ")"); + particle.x = Math.random() * width; + particle.y = Math.random() * height - height; + particle.diameter = Math.random() * 10 + 5; + particle.tilt = Math.random() * 10 - 10; + particle.tiltAngleIncrement = Math.random() * 0.07 + 0.05; + particle.tiltAngle = Math.random() * Math.PI; + return particle; + } + + startConfetti(timeout) { + const width = window.innerWidth; + const height = window.innerHeight; + window.requestAnimationFrame = () => { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + return window.setTimeout(callback, this.confetti.frameInterval); + }; + }; + let canvas = document.getElementById("confetti-canvas"); + if (canvas === null) { + canvas = document.createElement("canvas"); + canvas.setAttribute("id", "confetti-canvas"); + canvas.setAttribute("style", "display:block;z-index:999999;pointer-events:none;position:fixed;top:0"); + document.body.prepend(canvas); + canvas.width = width; + canvas.height = height; + window.addEventListener("resize", function () { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + }, true); + this.context = canvas.getContext("2d"); + } else if (this.context === null) { + this.context = canvas.getContext("2d"); + } + const count = this.confetti.maxCount; + while (this.particles.length < count) { + this.particles.push(this.resetParticle({}, width, height)); + } + this.streamingConfetti = true; + this.runAnimation(); + if (timeout) { + window.setTimeout(this.stopConfetti, timeout); + } + } + + stopConfetti() { + this.streamingConfetti = false; + } + + runAnimation() { + if (this.particles.length === 0) { + this.context.clearRect(0, 0, window.innerWidth, window.innerHeight); + this.animationTimer = null; + } else { + const now = Date.now(); + const delta = now - this.lastFrameTime; + if (!this.supportsAnimationFrame || delta > this.confetti.frameInterval) { + this.context.clearRect(0, 0, window.innerWidth, window.innerHeight); + this.updateParticles(); + this.drawParticles(this.context); + this.lastFrameTime = now - (delta % this.confetti.frameInterval); + } + this.animationTimer = requestAnimationFrame(this.runAnimation); + } + } + + removeConfetti() { + stop(); + this.particles = []; + } + + isConfettiRunning() { + return this.streamingConfetti; + } + + drawParticles(context) { + let particle; + let x; + let x2; + let y2; + for (let i = 0; i < this.particles.length; i++) { + particle = this.particles[i]; + context.beginPath(); + context.lineWidth = particle.diameter; + x2 = particle.x + particle.tilt; + x = x2 + particle.diameter / 2; + y2 = particle.y + particle.tilt + particle.diameter / 2; + context.strokeStyle = particle.color; + context.moveTo(x, particle.y); + context.lineTo(x2, y2); + context.stroke(); + } + } + + updateParticles() { + const width = window.innerWidth; + const height = window.innerHeight; + let particle; + this.waveAngle += 0.01; + for (let i = 0; i < this.particles.length; i++) { + particle = this.particles[i]; + if (!this.streamingConfetti && particle.y < -15) { + particle.y = height + 100; + } else { + particle.tiltAngle += particle.tiltAngleIncrement; + particle.x += Math.sin(this.waveAngle) - 0.5; + particle.y += (Math.cos(this.waveAngle) + particle.diameter + this.confetti.speed) * 0.5; + particle.tilt = Math.sin(particle.tiltAngle) * 15; + } + if (particle.x > width + 20 || particle.x < -20 || particle.y > height) { + if (this.streamingConfetti && this.particles.length <= this.confetti.maxCount) { + this.resetParticle(particle, width, height); + } else { + this.particles.splice(i, 1); + i--; + } + } + } + } + + convertToHex(content) { + const contentBodyToHexArray = []; + let hex; + for (let i = 0; i < content.body.length; i++) { + hex = content.body.codePointAt(i).toString(16); + contentBodyToHexArray.push(hex); + } + return contentBodyToHexArray; + } + + isChatEffectsDisabled() { + console.log('return value', SettingsStore.getValue('dontShowChatEffects')); + return SettingsStore.getValue('dontShowChatEffects'); + } + + isConfettiEmoji(content) { + const hexArray = this.convertToHex(content); + return !!(hexArray.includes('1f389') || hexArray.includes('1f38a')); + } + + animateConfetti(userId, message) { + // const shortendUserId = userId.slice(1).split(":").slice(0, 1); + console.log('in animate confetti method'); + if (!this.isChatEffectsDisabled()) { + this.confetti.start(3000); + } + if (!message) { + return ('*' + userId + ' throws confetti '); + } + } + + render() { + return ( ); + } +} diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index edfe21d9d6..e4311c2111 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2362,5 +2362,6 @@ "Use your account to sign in to the latest version of the app at ": "Verwende dein Konto um dich an der neusten Version der App anzumelden", "We’re excited to announce Riot is now Element!": "Wir freuen uns bekanntzugeben: Riot ist jetzt Element!", "Learn more at element.io/previously-riot": "Erfahre mehr unter element.io/previously-riot", - "Don't show chat effects": "Chat Effekte nicht zeigen" + "Don't show chat effects": "Chat Effekte nicht zeigen", + "Throws confetti animation in the chat room": "Throws confetti animation in the chat room" } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 98aee655fe..f09ec685ee 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -213,6 +213,7 @@ "Thank you!": "Thank you!", "Opens chat with the given user": "Opens chat with the given user", "Sends a message to the given user": "Sends a message to the given user", + "Throws confetti animation in the chat room": "Throws confetti animation in the chat room", "Displays action": "Displays action", "Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", From 69227dd456bb9d7d78e16157dcabda2603345ae3 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 10:26:20 +0200 Subject: [PATCH 0003/1014] translations added --- src/i18n/strings/de_DE.json | 3 ++- src/i18n/strings/en_EN.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index e4311c2111..5e5639942b 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2363,5 +2363,6 @@ "We’re excited to announce Riot is now Element!": "Wir freuen uns bekanntzugeben: Riot ist jetzt Element!", "Learn more at element.io/previously-riot": "Erfahre mehr unter element.io/previously-riot", "Don't show chat effects": "Chat Effekte nicht zeigen", - "Throws confetti animation in the chat room": "Throws confetti animation in the chat room" + "Sends the given message with confetti": "Sendet die Nachricht mit Konfetti", + " sends confetti": " sendet Konfetti" } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f09ec685ee..efd68d06a6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -213,7 +213,8 @@ "Thank you!": "Thank you!", "Opens chat with the given user": "Opens chat with the given user", "Sends a message to the given user": "Sends a message to the given user", - "Throws confetti animation in the chat room": "Throws confetti animation in the chat room", + "Sends the given message with confetti": "Sends the given message with confetti", + " sends confetti": " sends confetti", "Displays action": "Displays action", "Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", From 34cee20140d4da373fc0be630da4e11709409ed9 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 10:43:41 +0200 Subject: [PATCH 0004/1014] added confetti on command /confetti --- src/SlashCommands.tsx | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 2d4d484899..8322512b73 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -45,6 +45,7 @@ import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload"; import { Action } from "./dispatcher/actions"; import { EffectiveMembership, getEffectiveMembership } from "./utils/membership"; import {func} from "prop-types"; +import SettingsStore from "./settings/SettingsStore"; // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 interface HTMLInputEvent extends Event { @@ -1029,15 +1030,24 @@ export const Commands = [ }), new Command({ command: "confetti", - description: _td("Throws confetti animation in the chat room"), - args: '/confetti + ', - runFn: function(roomId, args, command) { + description: _td("Sends the given message with confetti"), + args: '', + runFn: function(roomId, args) { return success((async () => { - const cli = MatrixClientPeg.get(); - await cli.sendHtmlMessage(roomId, args); + const cli = MatrixClientPeg.get(); + const userId = cli.getUserId(); + const userName = userId.slice(1).split(":").slice(0, 1); + const isChatEffectsDisabled = SettingsStore.getValue('dontShowChatEffects'); + if (!args || isChatEffectsDisabled) { + args = '*' + userName + _td(' sends confetti'); + } + if (!isChatEffectsDisabled) { + dis.dispatch({action: 'confetti'}); + } + cli.sendHtmlMessage(roomId, args); })()); }, - category: CommandCategories.messages, + category: CommandCategories.actions, }), // Command definitions for autocompletion ONLY: From a7567b2e31bb403a05490a299e7ca17fd595760c Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 10:44:32 +0200 Subject: [PATCH 0005/1014] confetti animationsd handeled on roomViewTimeline --- src/components/structures/RoomView.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 85cb1df848..e48063530d 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -57,7 +57,7 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import { shieldStatusForRoom } from '../../utils/ShieldUtils'; import {Action} from "../../dispatcher/actions"; import {SettingLevel} from "../../settings/SettingLevel"; -import Confetti from "../views/elements/Confetti"; +import {animateConfetti, forceStopConfetti} from "../views/elements/Confetti"; const DEBUG = false; let debuglog = function() {}; @@ -68,7 +68,6 @@ if (DEBUG) { // using bind means that we get to keep useful line numbers in the console debuglog = console.log.bind(console); } -let confetti; export default createReactClass({ displayName: 'RoomView', propTypes: { @@ -511,6 +510,7 @@ export default createReactClass({ this.context.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); this.context.removeListener("crossSigning.keysChanged", this.onCrossSigningKeysChanged); + this.context.removeListener("Event.decrypted", this.onEventDecrypted); } window.removeEventListener('beforeunload', this.onPageUnload); @@ -630,9 +630,9 @@ export default createReactClass({ case 'message_send_failed': case 'message_sent': this._checkIfAlone(this.state.room); - confetti = new Confetti('100', '100'); - console.log('confetti sent'); - confetti.animateConfetti('test', 'message'); + break; + case 'confetti': + animateConfetti(this._roomView.current.offsetWidth); break; case 'post_sticker_message': this.injectSticker( @@ -750,6 +750,18 @@ export default createReactClass({ }); } } + if (!SettingsStore.getValue('dontShowChatEffects')) { + this.context.on('Event.decrypted', this.onEventDecrypted); + } + }, + onEventDecrypted(ev) { + if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; + this.handleConfetti(); + }, + handleConfetti() { + if (this.context.isInitialSyncComplete()) { + dis.dispatch({action: 'confetti'}); + } }, onRoomName: function(room) { @@ -786,6 +798,7 @@ export default createReactClass({ this._calculateRecommendedVersion(room); this._updateE2EStatus(room); this._updatePermissions(room); + forceStopConfetti(); }, _calculateRecommendedVersion: async function(room) { From 77de63bf4b06c5235e00c74673f2c0082a064195 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 10:44:49 +0200 Subject: [PATCH 0006/1014] confetti file added --- src/components/views/elements/Confetti.js | 252 +++++++++++----------- 1 file changed, 121 insertions(+), 131 deletions(-) diff --git a/src/components/views/elements/Confetti.js b/src/components/views/elements/Confetti.js index e9dc2c34c0..df2b004ce0 100644 --- a/src/components/views/elements/Confetti.js +++ b/src/components/views/elements/Confetti.js @@ -1,52 +1,48 @@ -import React from "react"; -import SettingsStore from "../../../../lib/settings/SettingsStore"; -import PropTypes from "prop-types"; +const confetti = { + //set max confetti count + maxCount: 150, + //syarn addet the particle animation speed + speed: 3, + //the confetti animation frame interval in milliseconds + frameInterval: 15, + //the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) + alpha: 1.0, + //call to start confetti animation (with optional timeout in milliseconds) + start: null, + //call to stop adding confetti + stop: null, + //call to stop the confetti animation and remove all confetti immediately + remove: null, + isRunning: null, + //call and returns true or false depending on whether the animation is running + animate: null, +}; -export default class Confetti extends React.Component { - displayName: 'confetti'; - constructor(props) { - super(props); - this.animateConfetti = this.animateConfetti.bind(this); - this.confetti.start = this.startConfetti; - this.startConfetti = this.startConfetti.bind(this); - this.confetti.stop = this.stopConfetti; - this.confetti.remove = this.removeConfetti; - this.confetti.isRunning = this.isConfettiRunning; - } - static propTypes = { - width: PropTypes.string.isRequired, - height: PropTypes.string.isRequired, - } - confetti = { - //set max confetti count - maxCount: 150, - //set the particle animation speed - speed: 3, - //the confetti animation frame interval in milliseconds - frameInterval: 15, - //the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) - alpha: 1.0, - start: null, - }; - colors = ["rgba(30,144,255,", "rgba(107,142,35,", "rgba(255,215,0,", - "rgba(255,192,203,", "rgba(106,90,205,", "rgba(173,216,230,", - "rgba(238,130,238,", "rgba(152,251,152,", "rgba(70,130,180,", - "rgba(244,164,96,", "rgba(210,105,30,", "rgba(220,20,60,"]; - streamingConfetti = false; - animationTimer = null; - lastFrameTime = Date.now(); - particles = []; - waveAngle = 0; - context = null; - supportsAnimationFrame = window.requestAnimationFrame || +(function() { + confetti.start = startConfetti; + confetti.stop = stopConfetti; + confetti.remove = removeConfetti; + confetti.isRunning = isConfettiRunning; + confetti.animate = animateConfetti; + const supportsAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame; + const colors = ["rgba(30,144,255,", "rgba(107,142,35,", "rgba(255,215,0,", + "rgba(255,192,203,", "rgba(106,90,205,", "rgba(173,216,230,", + "rgba(238,130,238,", "rgba(152,251,152,", "rgba(70,130,180,", + "rgba(244,164,96,", "rgba(210,105,30,", "rgba(220,20,60,"]; + let streamingConfetti = false; + let animationTimer = null; + let lastFrameTime = Date.now(); + let particles = []; + let waveAngle = 0; + let context = null; - resetParticle(particle, width, height) { - particle.color = this.colors[(Math.random() * this.colors.length) | 0] + (this.confetti.alpha + ")"); - particle.color2 = this.colors[(Math.random() * this.colors.length) | 0] + (this.confetti.alpha + ")"); + function resetParticle(particle, width, height) { + particle.color = colors[(Math.random() * colors.length) | 0] + (confetti.alpha + ")"); + particle.color2 = colors[(Math.random() * colors.length) | 0] + (confetti.alpha + ")"); particle.x = Math.random() * width; particle.y = Math.random() * height - height; particle.diameter = Math.random() * 10 + 5; @@ -56,154 +52,148 @@ export default class Confetti extends React.Component { return particle; } - startConfetti(timeout) { - const width = window.innerWidth; + function runAnimation() { + if (particles.length === 0) { + context.clearRect(0, 0, window.innerWidth, window.innerHeight); + animationTimer = null; + } else { + const now = Date.now(); + const delta = now - lastFrameTime; + if (!supportsAnimationFrame || delta > confetti.frameInterval) { + context.clearRect(0, 0, window.innerWidth, window.innerHeight); + updateParticles(); + drawParticles(context); + lastFrameTime = now - (delta % confetti.frameInterval); + } + animationTimer = requestAnimationFrame(runAnimation); + } + } + + function startConfetti(roomWidth, timeout) { + const width = roomWidth; const height = window.innerHeight; - window.requestAnimationFrame = () => { + window.requestAnimationFrame = (function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || - function(callback) { - return window.setTimeout(callback, this.confetti.frameInterval); + function (callback) { + return window.setTimeout(callback, confetti.frameInterval); }; - }; + })(); let canvas = document.getElementById("confetti-canvas"); if (canvas === null) { canvas = document.createElement("canvas"); canvas.setAttribute("id", "confetti-canvas"); - canvas.setAttribute("style", "display:block;z-index:999999;pointer-events:none;position:fixed;top:0"); + canvas.setAttribute("style", "display:block;z-index:999999;pointer-events:none;position:fixed;top:0; right:0"); document.body.prepend(canvas); canvas.width = width; canvas.height = height; - window.addEventListener("resize", function () { - canvas.width = window.innerWidth; + window.addEventListener("resize", function() { + canvas.width = roomWidth; canvas.height = window.innerHeight; }, true); - this.context = canvas.getContext("2d"); - } else if (this.context === null) { - this.context = canvas.getContext("2d"); + context = canvas.getContext("2d"); + } else if (context === null) { + context = canvas.getContext("2d"); } - const count = this.confetti.maxCount; - while (this.particles.length < count) { - this.particles.push(this.resetParticle({}, width, height)); + const count = confetti.maxCount; + while (particles.length < count) { + particles.push(resetParticle({}, width, height)); } - this.streamingConfetti = true; - this.runAnimation(); + streamingConfetti = true; + runAnimation(); if (timeout) { - window.setTimeout(this.stopConfetti, timeout); + window.setTimeout(stopConfetti, timeout); } } - stopConfetti() { - this.streamingConfetti = false; + function stopConfetti() { + streamingConfetti = false; } - runAnimation() { - if (this.particles.length === 0) { - this.context.clearRect(0, 0, window.innerWidth, window.innerHeight); - this.animationTimer = null; - } else { - const now = Date.now(); - const delta = now - this.lastFrameTime; - if (!this.supportsAnimationFrame || delta > this.confetti.frameInterval) { - this.context.clearRect(0, 0, window.innerWidth, window.innerHeight); - this.updateParticles(); - this.drawParticles(this.context); - this.lastFrameTime = now - (delta % this.confetti.frameInterval); - } - this.animationTimer = requestAnimationFrame(this.runAnimation); - } - } - - removeConfetti() { + function removeConfetti() { stop(); - this.particles = []; + particles = []; } - isConfettiRunning() { - return this.streamingConfetti; + function isConfettiRunning() { + return streamingConfetti; } - drawParticles(context) { + function drawParticles(context) { let particle; - let x; - let x2; - let y2; - for (let i = 0; i < this.particles.length; i++) { - particle = this.particles[i]; + let x; let x2; let y2; + for (let i = 0; i < particles.length; i++) { + particle = particles[i]; context.beginPath(); context.lineWidth = particle.diameter; x2 = particle.x + particle.tilt; x = x2 + particle.diameter / 2; y2 = particle.y + particle.tilt + particle.diameter / 2; - context.strokeStyle = particle.color; + if (confetti.gradient) { + const gradient = context.createLinearGradient(x, particle.y, x2, y2); + gradient.addColorStop("0", particle.color); + gradient.addColorStop("1.0", particle.color2); + context.strokeStyle = gradient; + } else { + context.strokeStyle = particle.color; + } context.moveTo(x, particle.y); context.lineTo(x2, y2); context.stroke(); } } - updateParticles() { + function updateParticles() { const width = window.innerWidth; const height = window.innerHeight; let particle; - this.waveAngle += 0.01; - for (let i = 0; i < this.particles.length; i++) { - particle = this.particles[i]; - if (!this.streamingConfetti && particle.y < -15) { + waveAngle += 0.01; + for (let i = 0; i < particles.length; i++) { + particle = particles[i]; + if (!streamingConfetti && particle.y < -15) { particle.y = height + 100; } else { particle.tiltAngle += particle.tiltAngleIncrement; - particle.x += Math.sin(this.waveAngle) - 0.5; - particle.y += (Math.cos(this.waveAngle) + particle.diameter + this.confetti.speed) * 0.5; + particle.x += Math.sin(waveAngle) - 0.5; + particle.y += (Math.cos(waveAngle) + particle.diameter + confetti.speed) * 0.5; particle.tilt = Math.sin(particle.tiltAngle) * 15; } if (particle.x > width + 20 || particle.x < -20 || particle.y > height) { - if (this.streamingConfetti && this.particles.length <= this.confetti.maxCount) { - this.resetParticle(particle, width, height); + if (streamingConfetti && particles.length <= confetti.maxCount) { + resetParticle(particle, width, height); } else { - this.particles.splice(i, 1); + particles.splice(i, 1); i--; } } } } +})(); - convertToHex(content) { - const contentBodyToHexArray = []; - let hex; +export function convertToHex(content) { + const contentBodyToHexArray = []; + let hex; + if (content.body) { for (let i = 0; i < content.body.length; i++) { hex = content.body.codePointAt(i).toString(16); contentBodyToHexArray.push(hex); } - return contentBodyToHexArray; - } - - isChatEffectsDisabled() { - console.log('return value', SettingsStore.getValue('dontShowChatEffects')); - return SettingsStore.getValue('dontShowChatEffects'); - } - - isConfettiEmoji(content) { - const hexArray = this.convertToHex(content); - return !!(hexArray.includes('1f389') || hexArray.includes('1f38a')); - } - - animateConfetti(userId, message) { - // const shortendUserId = userId.slice(1).split(":").slice(0, 1); - console.log('in animate confetti method'); - if (!this.isChatEffectsDisabled()) { - this.confetti.start(3000); - } - if (!message) { - return ('*' + userId + ' throws confetti '); - } - } - - render() { - return ( ); } + return contentBodyToHexArray; +} + +export function isConfettiEmoji(content) { + const hexArray = convertToHex(content); + return !!(hexArray.includes('1f389') || hexArray.includes('1f38a')); +} + +export function animateConfetti(roomWidth) { + confetti.start(roomWidth, 3000); +} +export function forceStopConfetti() { + console.log('confetti should stop'); + confetti.remove(); } From 03b2a529ef681e0e0777af57418f74ebba458954 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 11:29:46 +0200 Subject: [PATCH 0007/1014] remove unused var --- src/components/views/elements/Confetti.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/Confetti.js b/src/components/views/elements/Confetti.js index df2b004ce0..371c26a4b5 100644 --- a/src/components/views/elements/Confetti.js +++ b/src/components/views/elements/Confetti.js @@ -34,7 +34,7 @@ const confetti = { "rgba(238,130,238,", "rgba(152,251,152,", "rgba(70,130,180,", "rgba(244,164,96,", "rgba(210,105,30,", "rgba(220,20,60,"]; let streamingConfetti = false; - let animationTimer = null; + // let animationTimer = null; let lastFrameTime = Date.now(); let particles = []; let waveAngle = 0; @@ -55,7 +55,7 @@ const confetti = { function runAnimation() { if (particles.length === 0) { context.clearRect(0, 0, window.innerWidth, window.innerHeight); - animationTimer = null; + //animationTimer = null; } else { const now = Date.now(); const delta = now - lastFrameTime; @@ -65,7 +65,7 @@ const confetti = { drawParticles(context); lastFrameTime = now - (delta % confetti.frameInterval); } - animationTimer = requestAnimationFrame(runAnimation); + requestAnimationFrame(runAnimation); } } From 2a8b1e0ccd58628214a496b0b25f18ad96755997 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 11:55:23 +0200 Subject: [PATCH 0008/1014] remove space before function parentheses and maximum allowed line --- src/components/views/elements/Confetti.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/Confetti.js b/src/components/views/elements/Confetti.js index 371c26a4b5..7d4faa3a17 100644 --- a/src/components/views/elements/Confetti.js +++ b/src/components/views/elements/Confetti.js @@ -72,13 +72,13 @@ const confetti = { function startConfetti(roomWidth, timeout) { const width = roomWidth; const height = window.innerHeight; - window.requestAnimationFrame = (function () { + window.requestAnimationFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || - function (callback) { + function(callback) { return window.setTimeout(callback, confetti.frameInterval); }; })(); @@ -86,7 +86,8 @@ const confetti = { if (canvas === null) { canvas = document.createElement("canvas"); canvas.setAttribute("id", "confetti-canvas"); - canvas.setAttribute("style", "display:block;z-index:999999;pointer-events:none;position:fixed;top:0; right:0"); + canvas.setAttribute("style", + "display:block;z-index:999999;pointer-events:none;position:fixed;top:0; right:0"); document.body.prepend(canvas); canvas.width = width; canvas.height = height; From b79cf1e7ad00cd06ae0b38c8b37612877ec59481 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 13:59:11 +0200 Subject: [PATCH 0009/1014] updated translated string --- src/SlashCommands.tsx | 2 +- src/i18n/strings/de_DE.json | 1 - src/i18n/strings/en_EN.json | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 8322512b73..ba0aea73f0 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -1039,7 +1039,7 @@ export const Commands = [ const userName = userId.slice(1).split(":").slice(0, 1); const isChatEffectsDisabled = SettingsStore.getValue('dontShowChatEffects'); if (!args || isChatEffectsDisabled) { - args = '*' + userName + _td(' sends confetti'); + args = _t("* %(userName)s sends confetti", {userName}); } if (!isChatEffectsDisabled) { dis.dispatch({action: 'confetti'}); diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 5e5639942b..46ce139e6e 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2364,5 +2364,4 @@ "Learn more at element.io/previously-riot": "Erfahre mehr unter element.io/previously-riot", "Don't show chat effects": "Chat Effekte nicht zeigen", "Sends the given message with confetti": "Sendet die Nachricht mit Konfetti", - " sends confetti": " sendet Konfetti" } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index efd68d06a6..78a2d51c56 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -214,7 +214,7 @@ "Opens chat with the given user": "Opens chat with the given user", "Sends a message to the given user": "Sends a message to the given user", "Sends the given message with confetti": "Sends the given message with confetti", - " sends confetti": " sends confetti", + "* %(userName)s sends confetti": "* %(userName)s sends confetti", "Displays action": "Displays action", "Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", From 5b7ccb5a7837e134d28795b7cb8ddc68716ca7c2 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 14:04:20 +0200 Subject: [PATCH 0010/1014] fix indentation spaces and readability line spaces --- src/components/structures/RoomView.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index e48063530d..240d300751 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -68,6 +68,7 @@ if (DEBUG) { // using bind means that we get to keep useful line numbers in the console debuglog = console.log.bind(console); } + export default createReactClass({ displayName: 'RoomView', propTypes: { @@ -624,6 +625,7 @@ export default createReactClass({ ev.stopPropagation(); ev.preventDefault(); } + }, onAction: function(payload) { switch (payload.action) { @@ -632,7 +634,7 @@ export default createReactClass({ this._checkIfAlone(this.state.room); break; case 'confetti': - animateConfetti(this._roomView.current.offsetWidth); + animateConfetti(this._roomView.current.offsetWidth); break; case 'post_sticker_message': this.injectSticker( @@ -756,12 +758,12 @@ export default createReactClass({ }, onEventDecrypted(ev) { if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; - this.handleConfetti(); + this.handleConfetti(); }, handleConfetti() { if (this.context.isInitialSyncComplete()) { - dis.dispatch({action: 'confetti'}); - } + dis.dispatch({action: 'confetti'}); + } }, onRoomName: function(room) { From f1c7139711f87dd818f9143fc6ec032e9ce41509 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 14:25:06 +0200 Subject: [PATCH 0011/1014] remove not needed comma --- src/i18n/strings/de_DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 46ce139e6e..b5a69d7e72 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2363,5 +2363,5 @@ "We’re excited to announce Riot is now Element!": "Wir freuen uns bekanntzugeben: Riot ist jetzt Element!", "Learn more at element.io/previously-riot": "Erfahre mehr unter element.io/previously-riot", "Don't show chat effects": "Chat Effekte nicht zeigen", - "Sends the given message with confetti": "Sendet die Nachricht mit Konfetti", + "Sends the given message with confetti": "Sendet die Nachricht mit Konfetti" } From eef654e0e3189a8fcba03c8463d62ecbe2a3e745 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Tue, 25 Aug 2020 11:09:10 +0200 Subject: [PATCH 0012/1014] fix indentation and remove console.log --- src/components/views/elements/Confetti.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/elements/Confetti.js b/src/components/views/elements/Confetti.js index 7d4faa3a17..b0f88dedb7 100644 --- a/src/components/views/elements/Confetti.js +++ b/src/components/views/elements/Confetti.js @@ -192,9 +192,8 @@ export function isConfettiEmoji(content) { } export function animateConfetti(roomWidth) { - confetti.start(roomWidth, 3000); + confetti.start(roomWidth, 3000); } export function forceStopConfetti() { - console.log('confetti should stop'); confetti.remove(); } From d41ffb1b4be9eeff5330c9e3ca5891cf22bb7f46 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Tue, 25 Aug 2020 11:09:57 +0200 Subject: [PATCH 0013/1014] pass ev to handleConfetti in order to check content before dispatch --- src/components/structures/RoomView.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 240d300751..b24d6efa2a 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -57,7 +57,7 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import { shieldStatusForRoom } from '../../utils/ShieldUtils'; import {Action} from "../../dispatcher/actions"; import {SettingLevel} from "../../settings/SettingLevel"; -import {animateConfetti, forceStopConfetti} from "../views/elements/Confetti"; +import {animateConfetti, forceStopConfetti, isConfettiEmoji} from "../views/elements/Confetti"; const DEBUG = false; let debuglog = function() {}; @@ -758,11 +758,13 @@ export default createReactClass({ }, onEventDecrypted(ev) { if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; - this.handleConfetti(); + this.handleConfetti(ev); }, - handleConfetti() { + handleConfetti(ev) { if (this.context.isInitialSyncComplete()) { - dis.dispatch({action: 'confetti'}); + if (isConfettiEmoji(ev.getContent())) { + dis.dispatch({action: 'confetti'}); + } } }, From 43f266bfe333c2b6c5ece0be86ea5089e5e11c80 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Tue, 25 Aug 2020 11:11:20 +0200 Subject: [PATCH 0014/1014] remove unused import fix if condition trying (pass the dispatcher to sendHtmlMessage) --- src/SlashCommands.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index ba0aea73f0..6b321ce092 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -44,7 +44,6 @@ import { ensureDMExists } from "./createRoom"; import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload"; import { Action } from "./dispatcher/actions"; import { EffectiveMembership, getEffectiveMembership } from "./utils/membership"; -import {func} from "prop-types"; import SettingsStore from "./settings/SettingsStore"; // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 @@ -1038,13 +1037,11 @@ export const Commands = [ const userId = cli.getUserId(); const userName = userId.slice(1).split(":").slice(0, 1); const isChatEffectsDisabled = SettingsStore.getValue('dontShowChatEffects'); - if (!args || isChatEffectsDisabled) { + if ((!args) || (!args && isChatEffectsDisabled)) { args = _t("* %(userName)s sends confetti", {userName}); } - if (!isChatEffectsDisabled) { - dis.dispatch({action: 'confetti'}); - } - cli.sendHtmlMessage(roomId, args); + cli.sendHtmlMessage(roomId, args, + dis.dispatch({action: 'confetti'})); })()); }, category: CommandCategories.actions, From cc71531493df1e5e095b7f173909cbb4606a4f16 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Tue, 25 Aug 2020 13:36:04 +0200 Subject: [PATCH 0015/1014] reverted German language translations --- src/i18n/strings/de_DE.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 2cea1519df..3d5ba3722e 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2409,6 +2409,4 @@ "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "Wenn du jetzt abbrichst, kannst du verschlüsselte Nachrichten und Daten verlieren, wenn du den Zugriff auf deine Logins verlierst.", "You can also set up Secure Backup & manage your keys in Settings.": "Du kannst auch in den Einstellungen eine Sicherung erstellen & deine Schlüssel verwalten.", "Set up Secure backup": "Sicheres Backup einrichten" - "Don't show chat effects": "Chat Effekte nicht zeigen", - "Sends the given message with confetti": "Sendet die Nachricht mit Konfetti" } From 4527755f7e2df6f3b6622f1cd740469df608d587 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Tue, 25 Aug 2020 16:18:01 +0200 Subject: [PATCH 0016/1014] updated translation --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 029551eb34..223e063762 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -210,7 +210,7 @@ "Opens chat with the given user": "Opens chat with the given user", "Sends a message to the given user": "Sends a message to the given user", "Sends the given message with confetti": "Sends the given message with confetti", - "* %(userName)s sends confetti": "* %(userName)s sends confetti", + "sends confetti": "sends confetti", "Displays action": "Displays action", "Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", From 5753c964317ab20b1682874416d00cdf9e6c5820 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Tue, 25 Aug 2020 16:39:57 +0200 Subject: [PATCH 0017/1014] a workaround to make ocnfetti work on recipient side. changed the implementation of on.Event.decrypted function --- src/SlashCommands.tsx | 13 +++++++------ src/components/structures/RoomView.js | 13 ++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index abd4f5449b..03aec46e46 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -1029,15 +1029,16 @@ export const Commands = [ args: '', runFn: function(roomId, args) { return success((async () => { - const cli = MatrixClientPeg.get(); - const userId = cli.getUserId(); - const userName = userId.slice(1).split(":").slice(0, 1); const isChatEffectsDisabled = SettingsStore.getValue('dontShowChatEffects'); if ((!args) || (!args && isChatEffectsDisabled)) { - args = _t("* %(userName)s sends confetti", {userName}); + args = _t("sends confetti"); + MatrixClientPeg.get().sendEmoteMessage(roomId, args); + } else { + MatrixClientPeg.get().sendHtmlMessage(roomId, args); + } + if (!isChatEffectsDisabled) { + dis.dispatch({action: 'confetti'}); } - cli.sendHtmlMessage(roomId, args, - dis.dispatch({action: 'confetti'})); })()); }, category: CommandCategories.actions, diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index dfc92526c7..d5ccbf1c8c 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -511,7 +511,6 @@ export default createReactClass({ this.context.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); this.context.removeListener("crossSigning.keysChanged", this.onCrossSigningKeysChanged); - this.context.removeListener("Event.decrypted", this.onEventDecrypted); } window.removeEventListener('beforeunload', this.onPageUnload); @@ -753,16 +752,16 @@ export default createReactClass({ } } if (!SettingsStore.getValue('dontShowChatEffects')) { - this.context.on('Event.decrypted', this.onEventDecrypted); + this.context.on("Event.decrypted", (ev) => { + if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; + this.handleConfetti(ev); + }); } }, - onEventDecrypted(ev) { - if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; - this.handleConfetti(ev); - }, handleConfetti(ev) { if (this.context.isInitialSyncComplete()) { - if (isConfettiEmoji(ev.getContent())) { + const messageBody = _t('sends confetti'); + if (isConfettiEmoji(ev.getContent()) || ev.getContent().body === messageBody) { dis.dispatch({action: 'confetti'}); } } From 95051a42b1f2755f52a980ef4521edc88ab728b0 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Wed, 26 Aug 2020 18:56:23 +0200 Subject: [PATCH 0018/1014] checking for unreadMessages before sending confetti throwing the confetti on the sender's side change sendHtmlMessage to sendTextMessage in slashCommands --- src/SlashCommands.tsx | 2 +- src/components/structures/RoomView.js | 17 ++++++++++------- .../views/rooms/SendMessageComposer.js | 7 +++++++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 03aec46e46..28eaa8123b 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -1034,7 +1034,7 @@ export const Commands = [ args = _t("sends confetti"); MatrixClientPeg.get().sendEmoteMessage(roomId, args); } else { - MatrixClientPeg.get().sendHtmlMessage(roomId, args); + MatrixClientPeg.get().sendTextMessage(roomId, args); } if (!isChatEffectsDisabled) { dis.dispatch({action: 'confetti'}); diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index d5ccbf1c8c..92f43c75ca 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -189,6 +189,7 @@ export default createReactClass({ this.context.on("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.on("userTrustStatusChanged", this.onUserVerificationChanged); this.context.on("crossSigning.keysChanged", this.onCrossSigningKeysChanged); + this.context.on("Event.decrypted", this.onEventDecrypted); // Start listening for RoomViewStore updates this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); this._rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this._onRightPanelStoreUpdate); @@ -511,6 +512,7 @@ export default createReactClass({ this.context.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); this.context.removeListener("crossSigning.keysChanged", this.onCrossSigningKeysChanged); + this.context.removeListener("Event.decrypted", this.onEventDecrypted); } window.removeEventListener('beforeunload', this.onPageUnload); @@ -751,15 +753,16 @@ export default createReactClass({ }); } } - if (!SettingsStore.getValue('dontShowChatEffects')) { - this.context.on("Event.decrypted", (ev) => { - if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; - this.handleConfetti(ev); - }); - } + }, + onEventDecrypted(ev) { + if (!SettingsStore.getValue('dontShowChatEffects')) { + if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || + this.state.room.getUnreadNotificationCount() === 0) return; + this.handleConfetti(ev); + } }, handleConfetti(ev) { - if (this.context.isInitialSyncComplete()) { + if (this.state.matrixClientIsReady) { const messageBody = _t('sends confetti'); if (isConfettiEmoji(ev.getContent()) || ev.getContent().body === messageBody) { dis.dispatch({action: 'confetti'}); diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 6a7b2fc753..0b873a9bab 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -44,6 +44,8 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import RateLimitedFunc from '../../../ratelimitedfunc'; import {Action} from "../../../dispatcher/actions"; +import {isConfettiEmoji} from "../elements/Confetti"; +import SettingsStore from "../../../settings/SettingsStore"; function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) { const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent); @@ -313,6 +315,11 @@ export default class SendMessageComposer extends React.Component { }); } dis.dispatch({action: "message_sent"}); + if (!SettingsStore.getValue('dontShowChatEffects')) { + if (isConfettiEmoji(content)) { + dis.dispatch({action: 'confetti'}); + } + } } this.sendHistoryManager.save(this.model); From 43ff97c1789be080b8ec69e3045d7a262e3dfd31 Mon Sep 17 00:00:00 2001 From: Daniel Maslowski Date: Wed, 9 Sep 2020 20:35:26 +0200 Subject: [PATCH 0019/1014] Add support for giving reason when redacting Signed-off-by: Daniel Maslowski --- src/components/views/context_menus/MessageContextMenu.js | 4 +++- src/components/views/dialogs/ConfirmRedactDialog.js | 8 +++++--- src/i18n/strings/en_EN.json | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index d760c8defa..b6d27e45f9 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -145,7 +145,7 @@ export default class MessageContextMenu extends React.Component { onRedactClick = () => { const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog"); Modal.createTrackedDialog('Confirm Redact Dialog', '', ConfirmRedactDialog, { - onFinished: async (proceed) => { + onFinished: async (proceed, reason) => { if (!proceed) return; const cli = MatrixClientPeg.get(); @@ -153,6 +153,8 @@ export default class MessageContextMenu extends React.Component { await cli.redactEvent( this.props.mxEvent.getRoomId(), this.props.mxEvent.getId(), + undefined, + reason ? { reason } : {}, ); } catch (e) { const code = e.errcode || e.statusCode; diff --git a/src/components/views/dialogs/ConfirmRedactDialog.js b/src/components/views/dialogs/ConfirmRedactDialog.js index 3106df1d5b..2216f9a93a 100644 --- a/src/components/views/dialogs/ConfirmRedactDialog.js +++ b/src/components/views/dialogs/ConfirmRedactDialog.js @@ -23,15 +23,17 @@ import { _t } from '../../../languageHandler'; */ export default class ConfirmRedactDialog extends React.Component { render() { - const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog'); + const TextInputDialog = sdk.getComponent('views.dialogs.TextInputDialog'); return ( - - + ); } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 65374ea3ec..ecc4bd2f4c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1624,6 +1624,7 @@ "Removing…": "Removing…", "Confirm Removal": "Confirm Removal", "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.", + "Reason (optional)": "Reason (optional)", "Clear all data in this session?": "Clear all data in this session?", "Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.", "Clear all data": "Clear all data", From 6e97baf09f80364d157233a8567c2c1bc106f302 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 12:53:17 +0200 Subject: [PATCH 0020/1014] Added license --- src/components/views/elements/Confetti.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/components/views/elements/Confetti.js b/src/components/views/elements/Confetti.js index b0f88dedb7..bef67cddcc 100644 --- a/src/components/views/elements/Confetti.js +++ b/src/components/views/elements/Confetti.js @@ -1,3 +1,23 @@ +/* +MIT License +Copyright (c) 2018 MathuSum Mut +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + const confetti = { //set max confetti count maxCount: 150, From 3358ed27921d137a2880f00dc0a1c603126b9bca Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 12:57:57 +0200 Subject: [PATCH 0021/1014] Use arrow functions --- src/components/structures/RoomView.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 92f43c75ca..53a964fbb8 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -754,14 +754,14 @@ export default createReactClass({ } } }, - onEventDecrypted(ev) { - if (!SettingsStore.getValue('dontShowChatEffects')) { - if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || - this.state.room.getUnreadNotificationCount() === 0) return; - this.handleConfetti(ev); - } + onEventDecrypted: (ev) => { + if (!SettingsStore.getValue('dontShowChatEffects')) { + if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || + this.state.room.getUnreadNotificationCount() === 0) return; + this.handleConfetti(ev); + } }, - handleConfetti(ev) { + handleConfetti: (ev) => { if (this.state.matrixClientIsReady) { const messageBody = _t('sends confetti'); if (isConfettiEmoji(ev.getContent()) || ev.getContent().body === messageBody) { From 4106f70218ef41e6101752e388b54b481cbe0576 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 13:24:22 +0200 Subject: [PATCH 0022/1014] Fixed merge error --- src/settings/Settings.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 99be728acc..b9ad834c83 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -626,6 +626,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td("Don't show chat effects"), default: false, + }, "Widgets.pinned": { supportedLevels: LEVELS_ROOM_OR_ACCOUNT, default: {}, From 929cc48cef28431cc059055240396ec7a2d173bb Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 13:30:52 +0200 Subject: [PATCH 0023/1014] Fixed eslint error --- src/components/structures/RoomView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 709864bff6..7094a8de1b 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -247,7 +247,7 @@ export default class RoomView extends React.Component { this.context.on("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.on("userTrustStatusChanged", this.onUserVerificationChanged); this.context.on("crossSigning.keysChanged", this.onCrossSigningKeysChanged); - this.context.on("Event.decrypted", this.onEventDecrypted); + this.context.on("Event.decrypted", this.onEventDecrypted); this.context.on("event", this.onEvent); // Start listening for RoomViewStore updates this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); From 3e8e817a3d51b11c6bffefcff2edbef0dd053e6f Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 13:35:18 +0200 Subject: [PATCH 0024/1014] Fixed merge error --- src/components/structures/RoomView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 7094a8de1b..f3ec8b8104 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -693,7 +693,7 @@ export default class RoomView extends React.Component { this.checkIfAlone(this.state.room); break; case 'confetti': - animateConfetti(this._roomView.current.offsetWidth); + animateConfetti(this.roomView.current.offsetWidth); break; case 'post_sticker_message': this.injectSticker( From 41160ff08e827e63851a3823be442d9395771ca6 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 13:54:09 +0200 Subject: [PATCH 0025/1014] Render confetti the react way --- src/components/structures/RoomView.tsx | 8 +++- src/components/views/elements/Confetti.js | 30 ++++---------- .../views/elements/ConfettiOverlay.tsx | 41 +++++++++++++++++++ 3 files changed, 56 insertions(+), 23 deletions(-) create mode 100644 src/components/views/elements/ConfettiOverlay.tsx diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index f3ec8b8104..0905005cf7 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -73,6 +73,7 @@ import TintableSvg from "../views/elements/TintableSvg"; import {XOR} from "../../@types/common"; import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; import { CallState, CallType, MatrixCall } from "matrix-js-sdk/lib/webrtc/call"; +import ConfettiOverlay from "../views/elements/ConfettiOverlay"; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -693,7 +694,7 @@ export default class RoomView extends React.Component { this.checkIfAlone(this.state.room); break; case 'confetti': - animateConfetti(this.roomView.current.offsetWidth); + //TODO: animateConfetti(this.roomView.current.offsetWidth); break; case 'post_sticker_message': this.injectSticker( @@ -853,7 +854,7 @@ export default class RoomView extends React.Component { this.calculateRecommendedVersion(room); this.updateE2EStatus(room); this.updatePermissions(room); - forceStopConfetti(); + //TODO: forceStopConfetti(); }; private async calculateRecommendedVersion(room: Room) { @@ -2072,6 +2073,9 @@ export default class RoomView extends React.Component { return (
+ {this.roomView.current && + + } { + const resize = () => { + const canvas = canvasRef.current; + canvas.height = window.innerHeight; + }; + const canvas = canvasRef.current; + canvas.width = roomWidth; + canvas.height = window.innerHeight; + window.addEventListener("resize", resize, true); + animateConfetti(canvas, roomWidth); + return () => { + window.removeEventListener("resize", resize); + forceStopConfetti(); + }; + }, []); + // on roomWidth change + + useEffect(() => { + const canvas = canvasRef.current; + canvas.width = roomWidth; + }, [roomWidth]); + return ( + + ) +} \ No newline at end of file From 607e33febaa4a6142776fe0126f850d782cca143 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 21:25:01 +0200 Subject: [PATCH 0026/1014] Extensibility, TypeScript and lazy loading --- src/SlashCommands.tsx | 10 +- src/components/structures/RoomView.tsx | 38 +--- src/components/views/elements/Confetti.js | 207 ------------------ .../views/elements/ConfettiOverlay.tsx | 41 ---- .../views/elements/effects/EffectsOverlay.tsx | 77 +++++++ .../views/elements/effects/ICanvasEffect.ts | 5 + .../views/elements/effects/confetti/index.ts | 197 +++++++++++++++++ .../views/rooms/SendMessageComposer.js | 8 +- .../tabs/user/PreferencesUserSettingsTab.js | 2 +- src/i18n/strings/en_EN.json | 2 +- src/settings/Settings.ts | 6 +- 11 files changed, 296 insertions(+), 297 deletions(-) delete mode 100644 src/components/views/elements/Confetti.js delete mode 100644 src/components/views/elements/ConfettiOverlay.tsx create mode 100644 src/components/views/elements/effects/EffectsOverlay.tsx create mode 100644 src/components/views/elements/effects/ICanvasEffect.ts create mode 100644 src/components/views/elements/effects/confetti/index.ts diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 87dc1ccdfd..316249d74d 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -77,6 +77,7 @@ export const CommandCategories = { "actions": _td("Actions"), "admin": _td("Admin"), "advanced": _td("Advanced"), + "effects": _td("Effects"), "other": _td("Other"), }; @@ -1045,19 +1046,16 @@ export const Commands = [ args: '', runFn: function(roomId, args) { return success((async () => { - const isChatEffectsDisabled = SettingsStore.getValue('dontShowChatEffects'); - if ((!args) || (!args && isChatEffectsDisabled)) { + if (!args) { args = _t("sends confetti"); MatrixClientPeg.get().sendEmoteMessage(roomId, args); } else { MatrixClientPeg.get().sendTextMessage(roomId, args); } - if (!isChatEffectsDisabled) { - dis.dispatch({action: 'confetti'}); - } + dis.dispatch({action: 'effects.confetti'}); })()); }, - category: CommandCategories.actions, + category: CommandCategories.effects, }), // Command definitions for autocompletion ONLY: diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 0905005cf7..1b47386789 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -56,7 +56,6 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import {E2EStatus, shieldStatusForRoom} from '../../utils/ShieldUtils'; import {Action} from "../../dispatcher/actions"; import {SettingLevel} from "../../settings/SettingLevel"; -import {animateConfetti, forceStopConfetti, isConfettiEmoji} from "../views/elements/Confetti"; import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; import {IMatrixClientCreds} from "../../MatrixClientPeg"; import ScrollPanel from "./ScrollPanel"; @@ -73,7 +72,7 @@ import TintableSvg from "../views/elements/TintableSvg"; import {XOR} from "../../@types/common"; import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; import { CallState, CallType, MatrixCall } from "matrix-js-sdk/lib/webrtc/call"; -import ConfettiOverlay from "../views/elements/ConfettiOverlay"; +import EffectsOverlay from "../views/elements/effects/EffectsOverlay"; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -248,8 +247,6 @@ export default class RoomView extends React.Component { this.context.on("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.on("userTrustStatusChanged", this.onUserVerificationChanged); this.context.on("crossSigning.keysChanged", this.onCrossSigningKeysChanged); - this.context.on("Event.decrypted", this.onEventDecrypted); - this.context.on("event", this.onEvent); // Start listening for RoomViewStore updates this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate); @@ -570,8 +567,6 @@ export default class RoomView extends React.Component { this.context.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); this.context.removeListener("crossSigning.keysChanged", this.onCrossSigningKeysChanged); - this.context.removeListener("Event.decrypted", this.onEventDecrypted); - this.context.removeListener("event", this.onEvent); } window.removeEventListener('beforeunload', this.onPageUnload); @@ -693,9 +688,6 @@ export default class RoomView extends React.Component { case 'message_sent': this.checkIfAlone(this.state.room); break; - case 'confetti': - //TODO: animateConfetti(this.roomView.current.offsetWidth); - break; case 'post_sticker_message': this.injectSticker( payload.data.content.url, @@ -804,28 +796,6 @@ export default class RoomView extends React.Component { } }; - private onEventDecrypted = (ev) => { - if (!SettingsStore.getValue('dontShowChatEffects')) { - if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || - this.state.room.getUnreadNotificationCount() === 0) return; - this.handleConfetti(ev); - } - }; - - private onEvent = (ev) => { - if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; - this.handleConfetti(ev); - }; - - private handleConfetti = (ev) => { - if (this.state.matrixClientIsReady) { - const messageBody = _t('sends confetti'); - if (isConfettiEmoji(ev.getContent()) || ev.getContent().body === messageBody) { - dis.dispatch({action: 'confetti'}); - } - } - }; - private onRoomName = (room: Room) => { if (this.state.room && room.roomId == this.state.room.roomId) { this.forceUpdate(); @@ -2070,11 +2040,13 @@ export default class RoomView extends React.Component { mx_RoomView_inCall: Boolean(activeCall), }); + const showChatEffects = SettingsStore.getValue('showChatEffects'); + return (
- {this.roomView.current && - + {showChatEffects && this.roomView.current && + } confetti.frameInterval) { - context.clearRect(0, 0, window.innerWidth, window.innerHeight); - updateParticles(); - drawParticles(context); - lastFrameTime = now - (delta % confetti.frameInterval); - } - requestAnimationFrame(runAnimation); - } - } - - function startConfetti(canvas, roomWidth, timeout) { - window.requestAnimationFrame = (function() { - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(callback) { - return window.setTimeout(callback, confetti.frameInterval); - }; - })(); - if (context === null) { - context = canvas.getContext("2d"); - } - const count = confetti.maxCount; - while (particles.length < count) { - particles.push(resetParticle({}, canvas.width, canvas.height)); - } - streamingConfetti = true; - runAnimation(); - if (timeout) { - window.setTimeout(stopConfetti, timeout); - } - } - - function stopConfetti() { - streamingConfetti = false; - } - - function removeConfetti() { - stop(); - particles = []; - } - - function isConfettiRunning() { - return streamingConfetti; - } - - function drawParticles(context) { - let particle; - let x; let x2; let y2; - for (let i = 0; i < particles.length; i++) { - particle = particles[i]; - context.beginPath(); - context.lineWidth = particle.diameter; - x2 = particle.x + particle.tilt; - x = x2 + particle.diameter / 2; - y2 = particle.y + particle.tilt + particle.diameter / 2; - if (confetti.gradient) { - const gradient = context.createLinearGradient(x, particle.y, x2, y2); - gradient.addColorStop("0", particle.color); - gradient.addColorStop("1.0", particle.color2); - context.strokeStyle = gradient; - } else { - context.strokeStyle = particle.color; - } - context.moveTo(x, particle.y); - context.lineTo(x2, y2); - context.stroke(); - } - } - - function updateParticles() { - const width = window.innerWidth; - const height = window.innerHeight; - let particle; - waveAngle += 0.01; - for (let i = 0; i < particles.length; i++) { - particle = particles[i]; - if (!streamingConfetti && particle.y < -15) { - particle.y = height + 100; - } else { - particle.tiltAngle += particle.tiltAngleIncrement; - particle.x += Math.sin(waveAngle) - 0.5; - particle.y += (Math.cos(waveAngle) + particle.diameter + confetti.speed) * 0.5; - particle.tilt = Math.sin(particle.tiltAngle) * 15; - } - if (particle.x > width + 20 || particle.x < -20 || particle.y > height) { - if (streamingConfetti && particles.length <= confetti.maxCount) { - resetParticle(particle, width, height); - } else { - particles.splice(i, 1); - i--; - } - } - } - } -})(); - -export function convertToHex(content) { - const contentBodyToHexArray = []; - let hex; - if (content.body) { - for (let i = 0; i < content.body.length; i++) { - hex = content.body.codePointAt(i).toString(16); - contentBodyToHexArray.push(hex); - } - } - return contentBodyToHexArray; -} - -export function isConfettiEmoji(content) { - const hexArray = convertToHex(content); - return !!(hexArray.includes('1f389') || hexArray.includes('1f38a')); -} - -export function animateConfetti(canvas, roomWidth) { - confetti.start(canvas, roomWidth, 3000); -} -export function forceStopConfetti() { - confetti.remove(); -} diff --git a/src/components/views/elements/ConfettiOverlay.tsx b/src/components/views/elements/ConfettiOverlay.tsx deleted file mode 100644 index 63d38d834c..0000000000 --- a/src/components/views/elements/ConfettiOverlay.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, {useEffect, useRef} from 'react'; -import {animateConfetti, forceStopConfetti} from './Confetti.js'; - -export default function ConfettiOverlay({roomWidth}) { - const canvasRef = useRef(null); - // on mount - useEffect(() => { - const resize = () => { - const canvas = canvasRef.current; - canvas.height = window.innerHeight; - }; - const canvas = canvasRef.current; - canvas.width = roomWidth; - canvas.height = window.innerHeight; - window.addEventListener("resize", resize, true); - animateConfetti(canvas, roomWidth); - return () => { - window.removeEventListener("resize", resize); - forceStopConfetti(); - }; - }, []); - // on roomWidth change - - useEffect(() => { - const canvas = canvasRef.current; - canvas.width = roomWidth; - }, [roomWidth]); - return ( - - ) -} \ No newline at end of file diff --git a/src/components/views/elements/effects/EffectsOverlay.tsx b/src/components/views/elements/effects/EffectsOverlay.tsx new file mode 100644 index 0000000000..1f8e7a97ad --- /dev/null +++ b/src/components/views/elements/effects/EffectsOverlay.tsx @@ -0,0 +1,77 @@ +import React, {FunctionComponent, useEffect, useRef} from 'react'; +import dis from '../../../../dispatcher/dispatcher'; +import ICanvasEffect from './ICanvasEffect.js'; + +type EffectsOverlayProps = { + roomWidth: number; +} + +const EffectsOverlay: FunctionComponent = ({roomWidth}) => { + const canvasRef = useRef(null); + const effectsRef = useRef>(new Map()); + + const resize = () => { + canvasRef.current.height = window.innerHeight; + }; + + const lazyLoadEffectModule = async (name: string): Promise => { + if(!name) return null; + let effect = effectsRef.current[name] ?? null; + if(effect === null) { + try { + var { default: Effect } = await import(`./${name}`); + effect = new Effect(); + effectsRef.current[name] = effect; + } catch (err) { + console.warn('Unable to load effect module at \'./${name}\'.', err) + } + } + return effect; + } + + const onAction = (payload: { action: string }) => { + const actionPrefix = 'effects.'; + if(payload.action.indexOf(actionPrefix) === 0) { + const effect = payload.action.substr(actionPrefix.length); + lazyLoadEffectModule(effect).then((module) => module?.start(canvasRef.current)); + } + }; + + // on mount + useEffect(() => { + const dispatcherRef = dis.register(onAction); + const canvas = canvasRef.current; + canvas.width = roomWidth; + canvas.height = window.innerHeight; + window.addEventListener('resize', resize, true); + + return () => { + dis.unregister(dispatcherRef); + window.removeEventListener('resize', resize); + for(const effect in effectsRef.current) { + effectsRef.current[effect]?.stop(); + } + }; + }, []); + + // on roomWidth change + useEffect(() => { + canvasRef.current.width = roomWidth; + }, [roomWidth]); + + return ( + + ) +} + +export default EffectsOverlay; \ No newline at end of file diff --git a/src/components/views/elements/effects/ICanvasEffect.ts b/src/components/views/elements/effects/ICanvasEffect.ts new file mode 100644 index 0000000000..c463235880 --- /dev/null +++ b/src/components/views/elements/effects/ICanvasEffect.ts @@ -0,0 +1,5 @@ +export default interface ICanvasEffect { + start: (canvas: HTMLCanvasElement, timeout?: number) => Promise, + stop: () => Promise, + isRunning: boolean +} \ No newline at end of file diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts new file mode 100644 index 0000000000..dd4e869078 --- /dev/null +++ b/src/components/views/elements/effects/confetti/index.ts @@ -0,0 +1,197 @@ +import ICanvasEffect from '../ICanvasEffect' + +declare global { + interface Window { + mozRequestAnimationFrame: any; + oRequestAnimationFrame: any; + msRequestAnimationFrame: any; + } +} + +export type ConfettiOptions = { + maxCount: number, + speed: number, + frameInterval: number, + alpha: number, + gradient: boolean, +} + +type ConfettiParticle = { + color: string, + color2: string, + x: number, + y: number, + diameter: number, + tilt: number, + tiltAngleIncrement: number, + tiltAngle: number, +} + +const DefaultOptions: ConfettiOptions = { + //set max confetti count + maxCount: 150, + //syarn addet the particle animation speed + speed: 3, + //the confetti animation frame interval in milliseconds + frameInterval: 15, + //the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) + alpha: 1.0, + //use gradient instead of solid particle color + gradient: false, +}; + +export default class Confetti implements ICanvasEffect { + private readonly options: ConfettiOptions; + + constructor(options: ConfettiOptions = DefaultOptions) { + this.options = options; + } + + private context: CanvasRenderingContext2D | null; + private supportsAnimationFrame = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame; + private colors = ['rgba(30,144,255,', 'rgba(107,142,35,', 'rgba(255,215,0,', + 'rgba(255,192,203,', 'rgba(106,90,205,', 'rgba(173,216,230,', + 'rgba(238,130,238,', 'rgba(152,251,152,', 'rgba(70,130,180,', + 'rgba(244,164,96,', 'rgba(210,105,30,', 'rgba(220,20,60,']; + + private lastFrameTime = Date.now(); + private particles: Array = []; + private waveAngle = 0; + + public isRunning: boolean; + + public start = async (canvas: HTMLCanvasElement, timeout?: number) => { + if(!canvas) { + return; + } + window.requestAnimationFrame = (function () { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function (callback) { + return window.setTimeout(callback, this.options.frameInterval); + }; + })(); + if (this.context === null) { + this.context = canvas.getContext('2d'); + } + const count = this.options.maxCount; + while (this.particles.length < count) { + this.particles.push(this.resetParticle({} as ConfettiParticle, canvas.width, canvas.height)); + } + this.isRunning = true; + this.runAnimation(); + if (timeout) { + window.setTimeout(this.stop, timeout || 3000); + } + } + + public stop = async () => { + this.isRunning = false; + this.particles = []; + } + + private resetParticle = (particle: ConfettiParticle, width: number, height: number): ConfettiParticle => { + particle.color = this.colors[(Math.random() * this.colors.length) | 0] + (this.options.alpha + ')'); + particle.color2 = this.colors[(Math.random() * this.colors.length) | 0] + (this.options.alpha + ')'); + particle.x = Math.random() * width; + particle.y = Math.random() * height - height; + particle.diameter = Math.random() * 10 + 5; + particle.tilt = Math.random() * 10 - 10; + particle.tiltAngleIncrement = Math.random() * 0.07 + 0.05; + particle.tiltAngle = Math.random() * Math.PI; + return particle; + } + + private runAnimation = (): void => { + if (this.particles.length === 0) { + this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height); + //animationTimer = null; + } else { + const now = Date.now(); + const delta = now - this.lastFrameTime; + if (!this.supportsAnimationFrame || delta > this.options.frameInterval) { + this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height); + this.updateParticles(); + this.drawParticles(this.context); + this.lastFrameTime = now - (delta % this.options.frameInterval); + } + requestAnimationFrame(this.runAnimation); + } + } + + + private drawParticles = (context: CanvasRenderingContext2D): void => { + let particle; + let x; let x2; let y2; + for (let i = 0; i < this.particles.length; i++) { + particle = this.particles[i]; + this.context.beginPath(); + context.lineWidth = particle.diameter; + x2 = particle.x + particle.tilt; + x = x2 + particle.diameter / 2; + y2 = particle.y + particle.tilt + particle.diameter / 2; + if (this.options.gradient) { + const gradient = context.createLinearGradient(x, particle.y, x2, y2); + gradient.addColorStop(0, particle.color); + gradient.addColorStop(1.0, particle.color2); + context.strokeStyle = gradient; + } else { + context.strokeStyle = particle.color; + } + context.moveTo(x, particle.y); + context.lineTo(x2, y2); + context.stroke(); + } + } + + private updateParticles = () => { + const width = this.context.canvas.width; + const height = this.context.canvas.height; + let particle: ConfettiParticle; + this.waveAngle += 0.01; + for (let i = 0; i < this.particles.length; i++) { + particle = this.particles[i]; + if (!this.isRunning && particle.y < -15) { + particle.y = height + 100; + } else { + particle.tiltAngle += particle.tiltAngleIncrement; + particle.x += Math.sin(this.waveAngle) - 0.5; + particle.y += (Math.cos(this.waveAngle) + particle.diameter + this.options.speed) * 0.5; + particle.tilt = Math.sin(particle.tiltAngle) * 15; + } + if (particle.x > width + 20 || particle.x < -20 || particle.y > height) { + if (this.isRunning && this.particles.length <= this.options.maxCount) { + this.resetParticle(particle, width, height); + } else { + this.particles.splice(i, 1); + i--; + } + } + } + } +} + +const convertToHex = (data: string): Array => { + const contentBodyToHexArray = []; + if (!data) return contentBodyToHexArray; + let hex; + if (data) { + for (let i = 0; i < data.length; i++) { + hex = data.codePointAt(i).toString(16); + contentBodyToHexArray.push(hex); + } + } + return contentBodyToHexArray; +} + +export const isConfettiEmoji = (content: { msgtype: string, body: string }): boolean => { + const hexArray = convertToHex(content.body); + return !!(hexArray.includes('1f389') || hexArray.includes('1f38a')); +} diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index d148d38b23..4fbea9d043 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -42,7 +42,7 @@ import {Key} from "../../../Keyboard"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import RateLimitedFunc from '../../../ratelimitedfunc'; import {Action} from "../../../dispatcher/actions"; -import {isConfettiEmoji} from "../elements/Confetti"; +import {isConfettiEmoji} from "../elements/effects/confetti"; import SettingsStore from "../../../settings/SettingsStore"; function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) { @@ -318,10 +318,8 @@ export default class SendMessageComposer extends React.Component { }); } dis.dispatch({action: "message_sent"}); - if (!SettingsStore.getValue('dontShowChatEffects')) { - if (isConfettiEmoji(content)) { - dis.dispatch({action: 'confetti'}); - } + if (isConfettiEmoji(content)) { + dis.dispatch({action: 'effects.confetti'}); } } diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 95d0f4be46..078d4dd2c7 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -49,7 +49,7 @@ export default class PreferencesUserSettingsTab extends React.Component { 'showAvatarChanges', 'showDisplaynameChanges', 'showImages', - 'dontShowChatEffects', + 'showChatEffects', 'Pill.shouldShowPillAvatar', ]; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4fc7a3ad25..c3943eb764 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -510,7 +510,7 @@ "Manually verify all remote sessions": "Manually verify all remote sessions", "IRC display name width": "IRC display name width", "Enable experimental, compact IRC style layout": "Enable experimental, compact IRC style layout", - "Don't show chat effects": "Don't show chat effects", + "Show chat effects": "Show chat effects", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", "Uploading logs": "Uploading logs", diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b9ad834c83..ab4665c401 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -622,10 +622,10 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td("Enable experimental, compact IRC style layout"), default: false, }, - "dontShowChatEffects": { + "showChatEffects": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, - displayName: _td("Don't show chat effects"), - default: false, + displayName: _td("Show chat effects"), + default: true, }, "Widgets.pinned": { supportedLevels: LEVELS_ROOM_OR_ACCOUNT, From 6d98335368b82ed6c7d5539bacfbd2f424fd8be7 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 21:28:22 +0200 Subject: [PATCH 0027/1014] Removed old todo --- src/components/structures/RoomView.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 1b47386789..2c3aa4793a 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -824,7 +824,6 @@ export default class RoomView extends React.Component { this.calculateRecommendedVersion(room); this.updateE2EStatus(room); this.updatePermissions(room); - //TODO: forceStopConfetti(); }; private async calculateRecommendedVersion(room: Room) { From 48633f76ab92c5238a4f2e4c99f73d2598129f64 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 23:10:43 +0200 Subject: [PATCH 0028/1014] added event handling back --- src/SlashCommands.tsx | 2 +- src/components/structures/RoomView.tsx | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 316249d74d..68be5de0a0 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -1047,7 +1047,7 @@ export const Commands = [ runFn: function(roomId, args) { return success((async () => { if (!args) { - args = _t("sends confetti"); + args = "sends confetti"; MatrixClientPeg.get().sendEmoteMessage(roomId, args); } else { MatrixClientPeg.get().sendTextMessage(roomId, args); diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 2c3aa4793a..f975a7cc6e 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -73,6 +73,7 @@ import {XOR} from "../../@types/common"; import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; import { CallState, CallType, MatrixCall } from "matrix-js-sdk/lib/webrtc/call"; import EffectsOverlay from "../views/elements/effects/EffectsOverlay"; +import { isConfettiEmoji } from '../views/elements/effects/confetti'; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -247,6 +248,8 @@ export default class RoomView extends React.Component { this.context.on("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.on("userTrustStatusChanged", this.onUserVerificationChanged); this.context.on("crossSigning.keysChanged", this.onCrossSigningKeysChanged); + this.context.on("Event.decrypted", this.onEventDecrypted); + this.context.on("event", this.onEvent); // Start listening for RoomViewStore updates this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate); @@ -567,6 +570,8 @@ export default class RoomView extends React.Component { this.context.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); this.context.removeListener("crossSigning.keysChanged", this.onCrossSigningKeysChanged); + this.context.removeListener("Event.decrypted", this.onEventDecrypted); + this.context.removeListener("event", this.onEvent); } window.removeEventListener('beforeunload', this.onPageUnload); @@ -795,6 +800,26 @@ export default class RoomView extends React.Component { } } }; + + private onEventDecrypted = (ev) => { + if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || + this.state.room.getUnreadNotificationCount() === 0) return; + this.handleConfetti(ev); + }; + + private onEvent = (ev) => { + if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; + this.handleConfetti(ev); + }; + + private handleConfetti = (ev) => { + if (this.state.matrixClientIsReady) { + const messageBody = 'sends confetti'; + if (isConfettiEmoji(ev.getContent()) || ev.getContent().body === messageBody) { + dis.dispatch({action: 'effects.confetti'}); + } + } + }; private onRoomName = (room: Room) => { if (this.state.room && room.roomId == this.state.room.roomId) { From 1c6d28b861c8b6bee20c29056733ef08cc3dfa76 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Wed, 21 Oct 2020 13:37:36 +0200 Subject: [PATCH 0029/1014] refactoring roomView / slashCommands / SendMessageComposer with the new effects configurations and fix confetti animation timeout --- src/SlashCommands.tsx | 39 +++++++++++-------- src/components/structures/RoomView.tsx | 18 +++++---- .../views/elements/effects/ICanvasEffect.ts | 4 +- .../views/elements/effects/confetti/index.ts | 26 ++----------- .../views/elements/effects/effectUtilities.ts | 3 ++ .../views/elements/effects/index.ts | 11 ++++++ .../views/rooms/SendMessageComposer.js | 10 +++-- 7 files changed, 59 insertions(+), 52 deletions(-) create mode 100644 src/components/views/elements/effects/effectUtilities.ts create mode 100644 src/components/views/elements/effects/index.ts diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 68be5de0a0..3f51614028 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -46,6 +46,7 @@ import { EffectiveMembership, getEffectiveMembership, leaveRoomBehaviour } from import SdkConfig from "./SdkConfig"; import SettingsStore from "./settings/SettingsStore"; import {UIFeature} from "./settings/UIFeature"; +import effects from "./components/views/elements/effects" // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 interface HTMLInputEvent extends Event { @@ -1040,22 +1041,28 @@ export const Commands = [ }, category: CommandCategories.actions, }), - new Command({ - command: "confetti", - description: _td("Sends the given message with confetti"), - args: '', - runFn: function(roomId, args) { - return success((async () => { - if (!args) { - args = "sends confetti"; - MatrixClientPeg.get().sendEmoteMessage(roomId, args); - } else { - MatrixClientPeg.get().sendTextMessage(roomId, args); - } - dis.dispatch({action: 'effects.confetti'}); - })()); - }, - category: CommandCategories.effects, + ...effects.map((effect) => { + return new Command({ + command: effect.command, + description: effect.description(), + args: '', + runFn: function(roomId, args) { + return success((async () => { + if (!args) { + args = effect.fallbackMessage(); + MatrixClientPeg.get().sendEmoteMessage(roomId, args); + } else { + const content = { + msgtype: effect.msgType, + body: args, + }; + MatrixClientPeg.get().sendMessage(roomId, content); + } + dis.dispatch({action: `effects.${effect.command}`}); + })()); + }, + category: CommandCategories.effects, + }) }), // Command definitions for autocompletion ONLY: diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index f975a7cc6e..0b7aa08288 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -73,7 +73,8 @@ import {XOR} from "../../@types/common"; import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; import { CallState, CallType, MatrixCall } from "matrix-js-sdk/lib/webrtc/call"; import EffectsOverlay from "../views/elements/effects/EffectsOverlay"; -import { isConfettiEmoji } from '../views/elements/effects/confetti'; +import {containsEmoji} from '../views/elements/effects/effectUtilities'; +import effects from '../views/elements/effects' const DEBUG = false; let debuglog = function(msg: string) {}; @@ -800,10 +801,9 @@ export default class RoomView extends React.Component { } } }; - + private onEventDecrypted = (ev) => { - if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || - this.state.room.getUnreadNotificationCount() === 0) return; + if (ev.isDecryptionFailure()) return; this.handleConfetti(ev); }; @@ -813,11 +813,13 @@ export default class RoomView extends React.Component { }; private handleConfetti = (ev) => { + if (this.state.room.getUnreadNotificationCount() === 0) return; if (this.state.matrixClientIsReady) { - const messageBody = 'sends confetti'; - if (isConfettiEmoji(ev.getContent()) || ev.getContent().body === messageBody) { - dis.dispatch({action: 'effects.confetti'}); - } + effects.map(effect => { + if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) { + dis.dispatch({action: `effects.${effect.command}`}); + } + }) } }; diff --git a/src/components/views/elements/effects/ICanvasEffect.ts b/src/components/views/elements/effects/ICanvasEffect.ts index c463235880..a8b9a83514 100644 --- a/src/components/views/elements/effects/ICanvasEffect.ts +++ b/src/components/views/elements/effects/ICanvasEffect.ts @@ -1,5 +1,5 @@ export default interface ICanvasEffect { - start: (canvas: HTMLCanvasElement, timeout?: number) => Promise, + start: (canvas: HTMLCanvasElement, timeout: number) => Promise, stop: () => Promise, isRunning: boolean -} \ No newline at end of file +} diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index dd4e869078..c5874311c5 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -47,7 +47,7 @@ export default class Confetti implements ICanvasEffect { this.options = options; } - private context: CanvasRenderingContext2D | null; + private context: CanvasRenderingContext2D | null = null; private supportsAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || @@ -64,7 +64,7 @@ export default class Confetti implements ICanvasEffect { public isRunning: boolean; - public start = async (canvas: HTMLCanvasElement, timeout?: number) => { + public start = async (canvas: HTMLCanvasElement, timeout = 3000) => { if(!canvas) { return; } @@ -88,13 +88,13 @@ export default class Confetti implements ICanvasEffect { this.isRunning = true; this.runAnimation(); if (timeout) { - window.setTimeout(this.stop, timeout || 3000); + window.setTimeout(this.stop, timeout); } } public stop = async () => { this.isRunning = false; - this.particles = []; + // this.particles = []; } private resetParticle = (particle: ConfettiParticle, width: number, height: number): ConfettiParticle => { @@ -177,21 +177,3 @@ export default class Confetti implements ICanvasEffect { } } } - -const convertToHex = (data: string): Array => { - const contentBodyToHexArray = []; - if (!data) return contentBodyToHexArray; - let hex; - if (data) { - for (let i = 0; i < data.length; i++) { - hex = data.codePointAt(i).toString(16); - contentBodyToHexArray.push(hex); - } - } - return contentBodyToHexArray; -} - -export const isConfettiEmoji = (content: { msgtype: string, body: string }): boolean => { - const hexArray = convertToHex(content.body); - return !!(hexArray.includes('1f389') || hexArray.includes('1f38a')); -} diff --git a/src/components/views/elements/effects/effectUtilities.ts b/src/components/views/elements/effects/effectUtilities.ts new file mode 100644 index 0000000000..927b445a61 --- /dev/null +++ b/src/components/views/elements/effects/effectUtilities.ts @@ -0,0 +1,3 @@ +export const containsEmoji = (content: { msgtype: string, body: string }, emojis: Array): boolean => { + return emojis.some((emoji) => content.body.includes(emoji)); +} diff --git a/src/components/views/elements/effects/index.ts b/src/components/views/elements/effects/index.ts new file mode 100644 index 0000000000..d4c12fa7ce --- /dev/null +++ b/src/components/views/elements/effects/index.ts @@ -0,0 +1,11 @@ +import {_t, _td} from "../../../../languageHandler"; + +export default [ + { + emojis: ['🎊', '🎉'], + msgType: 'nic.custom.confetti', + command: 'confetti', + description: () => _td("Sends the given message with confetti"), + fallbackMessage: () => _t("sends confetti") + " 🎉", + }, +] diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 4fbea9d043..94ad934067 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -42,8 +42,8 @@ import {Key} from "../../../Keyboard"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import RateLimitedFunc from '../../../ratelimitedfunc'; import {Action} from "../../../dispatcher/actions"; -import {isConfettiEmoji} from "../elements/effects/confetti"; -import SettingsStore from "../../../settings/SettingsStore"; +import {containsEmoji} from "../elements/effects/effectUtilities"; +import effects from '../elements/effects'; function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) { const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent); @@ -318,9 +318,11 @@ export default class SendMessageComposer extends React.Component { }); } dis.dispatch({action: "message_sent"}); - if (isConfettiEmoji(content)) { - dis.dispatch({action: 'effects.confetti'}); + effects.map( (effect) => { + if (containsEmoji(content, effect.emojis)) { + dis.dispatch({action: `effects.${effect.command}`}); } + }); } this.sendHistoryManager.save(this.model, replyToEvent); From d4ec1dd7750f5a901eaf079a7b5fd8153701aed3 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 13:56:58 +0200 Subject: [PATCH 0030/1014] Refactoring --- src/components/structures/RoomView.tsx | 2 +- .../views/elements/effects/confetti/index.ts | 1 - src/components/views/elements/effects/index.ts | 16 ++++++++++++++-- .../views/rooms/SendMessageComposer.js | 6 +++--- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 0b7aa08288..c84a3bf783 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -815,7 +815,7 @@ export default class RoomView extends React.Component { private handleConfetti = (ev) => { if (this.state.room.getUnreadNotificationCount() === 0) return; if (this.state.matrixClientIsReady) { - effects.map(effect => { + effects.forEach(effect => { if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) { dis.dispatch({action: `effects.${effect.command}`}); } diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index c5874311c5..e8a139387b 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -94,7 +94,6 @@ export default class Confetti implements ICanvasEffect { public stop = async () => { this.isRunning = false; - // this.particles = []; } private resetParticle = (particle: ConfettiParticle, width: number, height: number): ConfettiParticle => { diff --git a/src/components/views/elements/effects/index.ts b/src/components/views/elements/effects/index.ts index d4c12fa7ce..6311135c1e 100644 --- a/src/components/views/elements/effects/index.ts +++ b/src/components/views/elements/effects/index.ts @@ -1,6 +1,14 @@ import {_t, _td} from "../../../../languageHandler"; -export default [ +type Effect = { + emojis: Array; + msgType: string; + command: string; + description: () => string; + fallbackMessage: () => string; +} + +const effects: Array = [ { emojis: ['🎊', '🎉'], msgType: 'nic.custom.confetti', @@ -8,4 +16,8 @@ export default [ description: () => _td("Sends the given message with confetti"), fallbackMessage: () => _t("sends confetti") + " 🎉", }, -] +]; + +export default effects; + + diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 94ad934067..a413a9917c 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -318,10 +318,10 @@ export default class SendMessageComposer extends React.Component { }); } dis.dispatch({action: "message_sent"}); - effects.map( (effect) => { + effects.forEach((effect) => { if (containsEmoji(content, effect.emojis)) { - dis.dispatch({action: `effects.${effect.command}`}); - } + dis.dispatch({action: `effects.${effect.command}`}); + } }); } From 047c29731b319bdf48027ae1e2966100763530f4 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 14:15:27 +0200 Subject: [PATCH 0031/1014] Added missing translation --- src/components/views/elements/effects/ICanvasEffect.ts | 2 +- src/i18n/strings/en_EN.json | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/effects/ICanvasEffect.ts b/src/components/views/elements/effects/ICanvasEffect.ts index a8b9a83514..71210d7ec3 100644 --- a/src/components/views/elements/effects/ICanvasEffect.ts +++ b/src/components/views/elements/effects/ICanvasEffect.ts @@ -1,5 +1,5 @@ export default interface ICanvasEffect { - start: (canvas: HTMLCanvasElement, timeout: number) => Promise, + start: (canvas: HTMLCanvasElement, timeout?: number) => Promise, stop: () => Promise, isRunning: boolean } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c3943eb764..974658aa3d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -148,6 +148,7 @@ "Messages": "Messages", "Actions": "Actions", "Advanced": "Advanced", + "Effects": "Effects", "Other": "Other", "Command error": "Command error", "Usage": "Usage", @@ -211,8 +212,6 @@ "Send a bug report with logs": "Send a bug report with logs", "Opens chat with the given user": "Opens chat with the given user", "Sends a message to the given user": "Sends a message to the given user", - "Sends the given message with confetti": "Sends the given message with confetti", - "sends confetti": "sends confetti", "Displays action": "Displays action", "Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", @@ -1580,6 +1579,8 @@ "Sign in with single sign-on": "Sign in with single sign-on", "And %(count)s more...|other": "And %(count)s more...", "Home": "Home", + "Sends the given message with confetti": "Sends the given message with confetti", + "sends confetti": "sends confetti", "Enter a server name": "Enter a server name", "Looks good": "Looks good", "Can't find this server or its room list": "Can't find this server or its room list", From c7d535d2d3d07ae6490365cb242d1f1e1f1d25a3 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 14:29:25 +0200 Subject: [PATCH 0032/1014] Fixed some formatting issues --- .../views/elements/effects/EffectsOverlay.tsx | 18 +++++++++--------- .../views/elements/effects/confetti/index.ts | 6 +++--- src/components/views/elements/effects/index.ts | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/views/elements/effects/EffectsOverlay.tsx b/src/components/views/elements/effects/EffectsOverlay.tsx index 1f8e7a97ad..437d1f127f 100644 --- a/src/components/views/elements/effects/EffectsOverlay.tsx +++ b/src/components/views/elements/effects/EffectsOverlay.tsx @@ -1,4 +1,4 @@ -import React, {FunctionComponent, useEffect, useRef} from 'react'; +import React, { FunctionComponent, useEffect, useRef } from 'react'; import dis from '../../../../dispatcher/dispatcher'; import ICanvasEffect from './ICanvasEffect.js'; @@ -6,7 +6,7 @@ type EffectsOverlayProps = { roomWidth: number; } -const EffectsOverlay: FunctionComponent = ({roomWidth}) => { +const EffectsOverlay: FunctionComponent = ({ roomWidth }) => { const canvasRef = useRef(null); const effectsRef = useRef>(new Map()); @@ -15,9 +15,9 @@ const EffectsOverlay: FunctionComponent = ({roomWidth}) => }; const lazyLoadEffectModule = async (name: string): Promise => { - if(!name) return null; + if (!name) return null; let effect = effectsRef.current[name] ?? null; - if(effect === null) { + if (effect === null) { try { var { default: Effect } = await import(`./${name}`); effect = new Effect(); @@ -31,7 +31,7 @@ const EffectsOverlay: FunctionComponent = ({roomWidth}) => const onAction = (payload: { action: string }) => { const actionPrefix = 'effects.'; - if(payload.action.indexOf(actionPrefix) === 0) { + if (payload.action.indexOf(actionPrefix) === 0) { const effect = payload.action.substr(actionPrefix.length); lazyLoadEffectModule(effect).then((module) => module?.start(canvasRef.current)); } @@ -44,11 +44,11 @@ const EffectsOverlay: FunctionComponent = ({roomWidth}) => canvas.width = roomWidth; canvas.height = window.innerHeight; window.addEventListener('resize', resize, true); - - return () => { + + return () => { dis.unregister(dispatcherRef); window.removeEventListener('resize', resize); - for(const effect in effectsRef.current) { + for (const effect in effectsRef.current) { effectsRef.current[effect]?.stop(); } }; @@ -58,7 +58,7 @@ const EffectsOverlay: FunctionComponent = ({roomWidth}) => useEffect(() => { canvasRef.current.width = roomWidth; }, [roomWidth]); - + return ( { - if(!canvas) { + if (!canvas) { return; } - window.requestAnimationFrame = (function () { + window.requestAnimationFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || - function (callback) { + function(callback) { return window.setTimeout(callback, this.options.frameInterval); }; })(); diff --git a/src/components/views/elements/effects/index.ts b/src/components/views/elements/effects/index.ts index 6311135c1e..8a95b1c9d0 100644 --- a/src/components/views/elements/effects/index.ts +++ b/src/components/views/elements/effects/index.ts @@ -1,4 +1,4 @@ -import {_t, _td} from "../../../../languageHandler"; +import { _t, _td } from "../../../../languageHandler"; type Effect = { emojis: Array; From 8728e12242d0a751407e9a2c08c95ecaa5844dfd Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 14:43:09 +0200 Subject: [PATCH 0033/1014] Some code optimizations --- .../views/elements/effects/EffectsOverlay.tsx | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/components/views/elements/effects/EffectsOverlay.tsx b/src/components/views/elements/effects/EffectsOverlay.tsx index 437d1f127f..4b40f7cbb1 100644 --- a/src/components/views/elements/effects/EffectsOverlay.tsx +++ b/src/components/views/elements/effects/EffectsOverlay.tsx @@ -10,16 +10,12 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) = const canvasRef = useRef(null); const effectsRef = useRef>(new Map()); - const resize = () => { - canvasRef.current.height = window.innerHeight; - }; - const lazyLoadEffectModule = async (name: string): Promise => { if (!name) return null; let effect = effectsRef.current[name] ?? null; if (effect === null) { try { - var { default: Effect } = await import(`./${name}`); + const { default: Effect } = await import(`./${name}`); effect = new Effect(); effectsRef.current[name] = effect; } catch (err) { @@ -27,41 +23,41 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) = } } return effect; - } - - const onAction = (payload: { action: string }) => { - const actionPrefix = 'effects.'; - if (payload.action.indexOf(actionPrefix) === 0) { - const effect = payload.action.substr(actionPrefix.length); - lazyLoadEffectModule(effect).then((module) => module?.start(canvasRef.current)); - } }; - // on mount useEffect(() => { + const resize = () => { + canvasRef.current.height = window.innerHeight; + }; + const onAction = (payload: { action: string }) => { + const actionPrefix = 'effects.'; + if (payload.action.indexOf(actionPrefix) === 0) { + const effect = payload.action.substr(actionPrefix.length); + lazyLoadEffectModule(effect).then((module) => module?.start(canvasRef.current)); + } + } const dispatcherRef = dis.register(onAction); const canvas = canvasRef.current; - canvas.width = roomWidth; canvas.height = window.innerHeight; window.addEventListener('resize', resize, true); return () => { dis.unregister(dispatcherRef); window.removeEventListener('resize', resize); - for (const effect in effectsRef.current) { - effectsRef.current[effect]?.stop(); + const currentEffects = effectsRef.current; + for (const effect in currentEffects) { + const effectModule: ICanvasEffect = currentEffects[effect]; + if(effectModule && effectModule.isRunning) { + effectModule.stop(); + } } }; }, []); - // on roomWidth change - useEffect(() => { - canvasRef.current.width = roomWidth; - }, [roomWidth]); - return ( = ({ roomWidth }) = ) } -export default EffectsOverlay; \ No newline at end of file +export default EffectsOverlay; From 88475617955c72a538269259a448f54f8e5e27fd Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 14:48:11 +0200 Subject: [PATCH 0034/1014] Added additional module exports --- src/components/views/elements/effects/EffectsOverlay.tsx | 2 +- src/components/views/elements/effects/confetti/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/effects/EffectsOverlay.tsx b/src/components/views/elements/effects/EffectsOverlay.tsx index 4b40f7cbb1..0ff2b228ad 100644 --- a/src/components/views/elements/effects/EffectsOverlay.tsx +++ b/src/components/views/elements/effects/EffectsOverlay.tsx @@ -2,7 +2,7 @@ import React, { FunctionComponent, useEffect, useRef } from 'react'; import dis from '../../../../dispatcher/dispatcher'; import ICanvasEffect from './ICanvasEffect.js'; -type EffectsOverlayProps = { +export type EffectsOverlayProps = { roomWidth: number; } diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index 3cb7db5ec4..e45961006b 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -27,7 +27,7 @@ type ConfettiParticle = { tiltAngle: number, } -const DefaultOptions: ConfettiOptions = { +export const DefaultOptions: ConfettiOptions = { //set max confetti count maxCount: 150, //syarn addet the particle animation speed From 6f4c5d1b080f8ddcbd6053f6778f52d6f15f2125 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 14:56:04 +0200 Subject: [PATCH 0035/1014] fixed build error --- src/components/views/elements/effects/EffectsOverlay.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/effects/EffectsOverlay.tsx b/src/components/views/elements/effects/EffectsOverlay.tsx index 0ff2b228ad..803fd18042 100644 --- a/src/components/views/elements/effects/EffectsOverlay.tsx +++ b/src/components/views/elements/effects/EffectsOverlay.tsx @@ -43,11 +43,12 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) = return () => { dis.unregister(dispatcherRef); - window.removeEventListener('resize', resize); - const currentEffects = effectsRef.current; + window.removeEventListener('resize', resize); + // eslint-disable-next-line react-hooks/exhaustive-deps + const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored for (const effect in currentEffects) { const effectModule: ICanvasEffect = currentEffects[effect]; - if(effectModule && effectModule.isRunning) { + if (effectModule && effectModule.isRunning) { effectModule.stop(); } } From 906686b640ff22812f3cb628bd3f8267af6c8144 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 15:06:05 +0200 Subject: [PATCH 0036/1014] Fixed more linter warnings --- src/components/views/elements/effects/confetti/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index e45961006b..4537683030 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -68,13 +68,13 @@ export default class Confetti implements ICanvasEffect { if (!canvas) { return; } - window.requestAnimationFrame = (function() { + window.requestAnimationFrame = (function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || - function(callback) { + function (callback) { return window.setTimeout(callback, this.options.frameInterval); }; })(); From 2f83771eab27a2e6441a34224c9acbd120c6c5b8 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 15:15:26 +0200 Subject: [PATCH 0037/1014] Fixed more eslint errors --- src/components/views/elements/effects/EffectsOverlay.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/effects/EffectsOverlay.tsx b/src/components/views/elements/effects/EffectsOverlay.tsx index 803fd18042..5ec3566f18 100644 --- a/src/components/views/elements/effects/EffectsOverlay.tsx +++ b/src/components/views/elements/effects/EffectsOverlay.tsx @@ -43,7 +43,7 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) = return () => { dis.unregister(dispatcherRef); - window.removeEventListener('resize', resize); + window.removeEventListener('resize', resize); // eslint-disable-next-line react-hooks/exhaustive-deps const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored for (const effect in currentEffects) { From 335774b6ff3bd558f11eea9ca58e2f1d7f73c262 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 16:03:22 +0200 Subject: [PATCH 0038/1014] Fixed more linter errors --- src/components/views/elements/effects/confetti/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index 4537683030..7428651490 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -1,4 +1,4 @@ -import ICanvasEffect from '../ICanvasEffect' +import ICanvasEffect from '../ICanvasEffect'; declare global { interface Window { @@ -68,13 +68,13 @@ export default class Confetti implements ICanvasEffect { if (!canvas) { return; } - window.requestAnimationFrame = (function () { + window.requestAnimationFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || - function (callback) { + function(callback) { return window.setTimeout(callback, this.options.frameInterval); }; })(); From fbe2d7e0f86ceae6511b6dc553a70d428b773290 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 16:15:15 +0200 Subject: [PATCH 0039/1014] Optimized naming --- src/components/structures/RoomView.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 817b2d2cea..1a18ece008 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -822,15 +822,15 @@ export default class RoomView extends React.Component { private onEventDecrypted = (ev) => { if (ev.isDecryptionFailure()) return; - this.handleConfetti(ev); + this.handleEffects(ev); }; private onEvent = (ev) => { if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; - this.handleConfetti(ev); + this.handleEffects(ev); }; - private handleConfetti = (ev) => { + private handleEffects = (ev) => { if (this.state.room.getUnreadNotificationCount() === 0) return; if (this.state.matrixClientIsReady) { effects.forEach(effect => { From cb79e38377165b4cb233caa804fadc9272e5e2a8 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 17:04:01 +0200 Subject: [PATCH 0040/1014] Better initialization and check if canvas gets unmounted --- .../views/elements/effects/confetti/index.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index 7428651490..b613c32043 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -78,9 +78,8 @@ export default class Confetti implements ICanvasEffect { return window.setTimeout(callback, this.options.frameInterval); }; })(); - if (this.context === null) { - this.context = canvas.getContext('2d'); - } + this.context = canvas.getContext('2d'); + this.particles = []; const count = this.options.maxCount; while (this.particles.length < count) { this.particles.push(this.resetParticle({} as ConfettiParticle, canvas.width, canvas.height)); @@ -109,9 +108,11 @@ export default class Confetti implements ICanvasEffect { } private runAnimation = (): void => { + if (!this.context || !this.context.canvas) { + return; + } if (this.particles.length === 0) { this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height); - //animationTimer = null; } else { const now = Date.now(); const delta = now - this.lastFrameTime; @@ -127,6 +128,9 @@ export default class Confetti implements ICanvasEffect { private drawParticles = (context: CanvasRenderingContext2D): void => { + if (!this.context || !this.context.canvas) { + return; + } let particle; let x; let x2; let y2; for (let i = 0; i < this.particles.length; i++) { @@ -151,6 +155,9 @@ export default class Confetti implements ICanvasEffect { } private updateParticles = () => { + if (!this.context || !this.context.canvas) { + return; + } const width = this.context.canvas.width; const height = this.context.canvas.height; let particle: ConfettiParticle; From 3ea4560019f111c5ce73382644d6d6aae9fb753a Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 17:58:54 +0200 Subject: [PATCH 0041/1014] Moved effect options to configuration --- .../views/elements/effects/EffectsOverlay.tsx | 12 +++++---- .../views/elements/effects/ICanvasEffect.ts | 4 +++ .../views/elements/effects/confetti/index.ts | 4 +-- .../views/elements/effects/index.ts | 25 ++++++++++++++++++- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/components/views/elements/effects/EffectsOverlay.tsx b/src/components/views/elements/effects/EffectsOverlay.tsx index 5ec3566f18..b2ecec8753 100644 --- a/src/components/views/elements/effects/EffectsOverlay.tsx +++ b/src/components/views/elements/effects/EffectsOverlay.tsx @@ -1,6 +1,7 @@ import React, { FunctionComponent, useEffect, useRef } from 'react'; import dis from '../../../../dispatcher/dispatcher'; -import ICanvasEffect from './ICanvasEffect.js'; +import ICanvasEffect, { ICanvasEffectConstructable } from './ICanvasEffect.js'; +import effects from './index' export type EffectsOverlayProps = { roomWidth: number; @@ -8,15 +9,16 @@ export type EffectsOverlayProps = { const EffectsOverlay: FunctionComponent = ({ roomWidth }) => { const canvasRef = useRef(null); - const effectsRef = useRef>(new Map()); + const effectsRef = useRef>(new Map()); const lazyLoadEffectModule = async (name: string): Promise => { if (!name) return null; - let effect = effectsRef.current[name] ?? null; + let effect: ICanvasEffect | null = effectsRef.current[name] || null; if (effect === null) { + const options = effects.find((e) => e.command === name)?.options try { - const { default: Effect } = await import(`./${name}`); - effect = new Effect(); + const { default: Effect }: { default: ICanvasEffectConstructable } = await import(`./${name}`); + effect = new Effect(options); effectsRef.current[name] = effect; } catch (err) { console.warn('Unable to load effect module at \'./${name}\'.', err) diff --git a/src/components/views/elements/effects/ICanvasEffect.ts b/src/components/views/elements/effects/ICanvasEffect.ts index 71210d7ec3..c2a3046c8f 100644 --- a/src/components/views/elements/effects/ICanvasEffect.ts +++ b/src/components/views/elements/effects/ICanvasEffect.ts @@ -1,3 +1,7 @@ +export interface ICanvasEffectConstructable { + new(options?: { [key: string]: any }): ICanvasEffect +} + export default interface ICanvasEffect { start: (canvas: HTMLCanvasElement, timeout?: number) => Promise, stop: () => Promise, diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index b613c32043..07b6f1632a 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -43,8 +43,8 @@ export const DefaultOptions: ConfettiOptions = { export default class Confetti implements ICanvasEffect { private readonly options: ConfettiOptions; - constructor(options: ConfettiOptions = DefaultOptions) { - this.options = options; + constructor(options: { [key: string]: any }) { + this.options = {...DefaultOptions, ...options}; } private context: CanvasRenderingContext2D | null = null; diff --git a/src/components/views/elements/effects/index.ts b/src/components/views/elements/effects/index.ts index 8a95b1c9d0..3986d6e841 100644 --- a/src/components/views/elements/effects/index.ts +++ b/src/components/views/elements/effects/index.ts @@ -1,11 +1,22 @@ import { _t, _td } from "../../../../languageHandler"; -type Effect = { +export type Effect = { emojis: Array; msgType: string; command: string; description: () => string; fallbackMessage: () => string; + options: { + [key: string]: any + } +} + +type ConfettiOptions = { + maxCount: number, + speed: number, + frameInterval: number, + alpha: number, + gradient: boolean, } const effects: Array = [ @@ -15,6 +26,18 @@ const effects: Array = [ command: 'confetti', description: () => _td("Sends the given message with confetti"), fallbackMessage: () => _t("sends confetti") + " 🎉", + options: { + //set max confetti count + maxCount: 150, + //syarn addet the particle animation speed + speed: 3, + //the confetti animation frame interval in milliseconds + frameInterval: 15, + //the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) + alpha: 1.0, + //use gradient instead of solid particle color + gradient: false, + } as ConfettiOptions, }, ]; From 1c556c97d3a23d95389ef3f1ce7be642a1885195 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 19:06:10 +0200 Subject: [PATCH 0042/1014] Added some code documentation --- .../views/elements/effects/ICanvasEffect.ts | 20 +++++++ .../views/elements/effects/confetti/index.ts | 20 +++++-- .../views/elements/effects/effectUtilities.ts | 5 ++ .../views/elements/effects/index.ts | 53 ++++++++++++++----- 4 files changed, 81 insertions(+), 17 deletions(-) diff --git a/src/components/views/elements/effects/ICanvasEffect.ts b/src/components/views/elements/effects/ICanvasEffect.ts index c2a3046c8f..400f42af73 100644 --- a/src/components/views/elements/effects/ICanvasEffect.ts +++ b/src/components/views/elements/effects/ICanvasEffect.ts @@ -1,9 +1,29 @@ +/** + * Defines the constructor of a canvas based room effect + */ export interface ICanvasEffectConstructable { + /** + * @param {{[key:string]:any}} options? Optional animation options + * @returns ICanvasEffect Returns a new instance of the canvas effect + */ new(options?: { [key: string]: any }): ICanvasEffect } +/** + * Defines the interface of a canvas based room effect + */ export default interface ICanvasEffect { + /** + * @param {HTMLCanvasElement} canvas The canvas instance as the render target of the animation + * @param {number} timeout? A timeout that defines the runtime of the animation (defaults to false) + */ start: (canvas: HTMLCanvasElement, timeout?: number) => Promise, + /** + * Stops the current animation + */ stop: () => Promise, + /** + * Returns a value that defines if the animation is currently running + */ isRunning: boolean } diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index 07b6f1632a..29f70d1a57 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -9,10 +9,25 @@ declare global { } export type ConfettiOptions = { + /** + * max confetti count + */ maxCount: number, + /** + * particle animation speed + */ speed: number, + /** + * the confetti animation frame interval in milliseconds + */ frameInterval: number, + /** + * the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) + */ alpha: number, + /** + * use gradient instead of solid particle color + */ gradient: boolean, } @@ -28,15 +43,10 @@ type ConfettiParticle = { } export const DefaultOptions: ConfettiOptions = { - //set max confetti count maxCount: 150, - //syarn addet the particle animation speed speed: 3, - //the confetti animation frame interval in milliseconds frameInterval: 15, - //the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) alpha: 1.0, - //use gradient instead of solid particle color gradient: false, }; diff --git a/src/components/views/elements/effects/effectUtilities.ts b/src/components/views/elements/effects/effectUtilities.ts index 927b445a61..212c477b39 100644 --- a/src/components/views/elements/effects/effectUtilities.ts +++ b/src/components/views/elements/effects/effectUtilities.ts @@ -1,3 +1,8 @@ +/** + * Checks a message if it contains one of the provided emojis + * @param {Object} content The message + * @param {Array} emojis The list of emojis to check for + */ export const containsEmoji = (content: { msgtype: string, body: string }, emojis: Array): boolean => { return emojis.some((emoji) => content.body.includes(emoji)); } diff --git a/src/components/views/elements/effects/index.ts b/src/components/views/elements/effects/index.ts index 3986d6e841..0f01f2624e 100644 --- a/src/components/views/elements/effects/index.ts +++ b/src/components/views/elements/effects/index.ts @@ -1,25 +1,59 @@ import { _t, _td } from "../../../../languageHandler"; -export type Effect = { +export type Effect = { + /** + * one or more emojis that will trigger this effect + */ emojis: Array; + /** + * the matrix message type that will trigger this effect + */ msgType: string; + /** + * the room command to trigger this effect + */ command: string; + /** + * a function that returns the translated description of the effect + */ description: () => string; + /** + * a function that returns the translated fallback message. this message will be shown if the user did not provide a custom message + */ fallbackMessage: () => string; - options: { - [key: string]: any - } + /** + * animation options + */ + options: TOptions; } type ConfettiOptions = { + /** + * max confetti count + */ maxCount: number, + /** + * particle animation speed + */ speed: number, + /** + * the confetti animation frame interval in milliseconds + */ frameInterval: number, + /** + * the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) + */ alpha: number, + /** + * use gradient instead of solid particle color + */ gradient: boolean, } -const effects: Array = [ +/** + * This configuration defines room effects that can be triggered by custom message types and emojis + */ +const effects: Array> = [ { emojis: ['🎊', '🎉'], msgType: 'nic.custom.confetti', @@ -27,18 +61,13 @@ const effects: Array = [ description: () => _td("Sends the given message with confetti"), fallbackMessage: () => _t("sends confetti") + " 🎉", options: { - //set max confetti count maxCount: 150, - //syarn addet the particle animation speed speed: 3, - //the confetti animation frame interval in milliseconds frameInterval: 15, - //the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) alpha: 1.0, - //use gradient instead of solid particle color gradient: false, - } as ConfettiOptions, - }, + }, + } as Effect, ]; export default effects; From 46eb5cdb1b036f2e696cd97fa9540c8ff18285be Mon Sep 17 00:00:00 2001 From: MaHa-Nordeck Date: Thu, 22 Oct 2020 14:01:16 +0200 Subject: [PATCH 0043/1014] Minor improvements * Made color generation dependant on gradient usage. * Changed a loop to use for of --- .../views/elements/effects/confetti/index.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index 29f70d1a57..309fc9dd9c 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -107,11 +107,15 @@ export default class Confetti implements ICanvasEffect { private resetParticle = (particle: ConfettiParticle, width: number, height: number): ConfettiParticle => { particle.color = this.colors[(Math.random() * this.colors.length) | 0] + (this.options.alpha + ')'); - particle.color2 = this.colors[(Math.random() * this.colors.length) | 0] + (this.options.alpha + ')'); + if(this.options.gradient) { + particle.color2 = this.colors[(Math.random() * this.colors.length) | 0] + (this.options.alpha + ')'); + } else { + particle.color2 = particle.color; + } particle.x = Math.random() * width; - particle.y = Math.random() * height - height; + particle.y = Math.random() * -height; particle.diameter = Math.random() * 10 + 5; - particle.tilt = Math.random() * 10 - 10; + particle.tilt = Math.random() * -10; particle.tiltAngleIncrement = Math.random() * 0.07 + 0.05; particle.tiltAngle = Math.random() * Math.PI; return particle; @@ -141,10 +145,8 @@ export default class Confetti implements ICanvasEffect { if (!this.context || !this.context.canvas) { return; } - let particle; let x; let x2; let y2; - for (let i = 0; i < this.particles.length; i++) { - particle = this.particles[i]; + for (const particle of this.particles) { this.context.beginPath(); context.lineWidth = particle.diameter; x2 = particle.x + particle.tilt; From 674060ed9373b1a2ca210449e4a4110415ce21ba Mon Sep 17 00:00:00 2001 From: MaHa-Nordeck Date: Thu, 22 Oct 2020 15:28:30 +0200 Subject: [PATCH 0044/1014] fixed spacing --- src/components/views/elements/effects/confetti/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index 309fc9dd9c..aee8f54a3a 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -107,7 +107,7 @@ export default class Confetti implements ICanvasEffect { private resetParticle = (particle: ConfettiParticle, width: number, height: number): ConfettiParticle => { particle.color = this.colors[(Math.random() * this.colors.length) | 0] + (this.options.alpha + ')'); - if(this.options.gradient) { + if (this.options.gradient) { particle.color2 = this.colors[(Math.random() * this.colors.length) | 0] + (this.options.alpha + ')'); } else { particle.color2 = particle.color; From 19395f3c3cf6076b097553da7873a045721d0ff9 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 26 Oct 2020 16:37:45 +0100 Subject: [PATCH 0045/1014] null checks added --- src/components/structures/RoomView.tsx | 16 ++++++++-------- .../views/elements/effects/effectUtilities.ts | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 1a18ece008..57c9afb17b 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -831,14 +831,14 @@ export default class RoomView extends React.Component { }; private handleEffects = (ev) => { - if (this.state.room.getUnreadNotificationCount() === 0) return; - if (this.state.matrixClientIsReady) { - effects.forEach(effect => { - if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) { - dis.dispatch({action: `effects.${effect.command}`}); - } - }) - } + if (!this.state.room || + !this.state.matrixClientIsReady || + this.state.room.getUnreadNotificationCount() === 0) return; + effects.forEach(effect => { + if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) { + dis.dispatch({action: `effects.${effect.command}`}); + } + }) }; private onRoomName = (room: Room) => { diff --git a/src/components/views/elements/effects/effectUtilities.ts b/src/components/views/elements/effects/effectUtilities.ts index 212c477b39..e94287c745 100644 --- a/src/components/views/elements/effects/effectUtilities.ts +++ b/src/components/views/elements/effects/effectUtilities.ts @@ -4,5 +4,5 @@ * @param {Array} emojis The list of emojis to check for */ export const containsEmoji = (content: { msgtype: string, body: string }, emojis: Array): boolean => { - return emojis.some((emoji) => content.body.includes(emoji)); + return emojis.some((emoji) => content.body && content.body.includes(emoji)); } From 20f3ab029320adb87d9384b4bf8504be59ad1b2f Mon Sep 17 00:00:00 2001 From: su-ex Date: Tue, 3 Nov 2020 20:41:59 +0100 Subject: [PATCH 0046/1014] Fix inverted settings default value Currently it doesn't matter what's set in the default property once the invertedSettingName property exists --- src/settings/SettingsStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 7c05e4b500..82f169f498 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -42,7 +42,7 @@ for (const key of Object.keys(SETTINGS)) { if (SETTINGS[key].invertedSettingName) { // Invert now so that the rest of the system will invert it back // to what was intended. - invertedDefaultSettings[key] = !SETTINGS[key].default; + invertedDefaultSettings[SETTINGS[key].invertedSettingName] = !SETTINGS[key].default; } } From eccd74cfc9da41e7a28d7dd1558321e4613e7441 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Mon, 9 Nov 2020 20:12:23 -0600 Subject: [PATCH 0047/1014] Allow SearchBox to expand to fill width Signed-off-by: Aaron Raimist --- res/css/structures/_SearchBox.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/structures/_SearchBox.scss b/res/css/structures/_SearchBox.scss index 23ee06f7b3..6b9b2ee3aa 100644 --- a/res/css/structures/_SearchBox.scss +++ b/res/css/structures/_SearchBox.scss @@ -15,7 +15,7 @@ limitations under the License. */ .mx_SearchBox { - flex: 1 1 0; + flex: 1 1 0 !important; min-width: 0; &.mx_SearchBox_blurred:not(:hover) { From ba8d02a808ec039fc6f9cc29bcd53479564f53c1 Mon Sep 17 00:00:00 2001 From: macekj Date: Tue, 17 Nov 2020 17:36:58 -0500 Subject: [PATCH 0048/1014] add quick shortcut emoji feature and tests Signed-off-by: macekj --- src/autocomplete/EmojiProvider.tsx | 2 +- .../views/rooms/BasicMessageComposer.tsx | 4 +- .../views/rooms/SendMessageComposer.js | 61 +++++++++++++++++++ src/editor/parts.ts | 4 +- .../views/rooms/SendMessageComposer-test.js | 42 ++++++++++++- 5 files changed, 107 insertions(+), 6 deletions(-) diff --git a/src/autocomplete/EmojiProvider.tsx b/src/autocomplete/EmojiProvider.tsx index 705474f8d0..d4791d69f1 100644 --- a/src/autocomplete/EmojiProvider.tsx +++ b/src/autocomplete/EmojiProvider.tsx @@ -34,7 +34,7 @@ const LIMIT = 20; // Match for ascii-style ";-)" emoticons or ":wink:" shortcodes provided by emojibase // anchored to only match from the start of parts otherwise it'll show emoji suggestions whilst typing matrix IDs -const EMOJI_REGEX = new RegExp('(' + EMOTICON_REGEX.source + '|(?:^|\\s):[+-\\w]*:?)$', 'g'); +const EMOJI_REGEX = new RegExp('(' + EMOTICON_REGEX.source + '|(?:^|\\s|(?<=^\\+)):[+-\\w]*:?)$', 'g'); interface IEmojiShort { emoji: IEmoji; diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 311a4734fd..43316e90f2 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -47,7 +47,7 @@ import AutocompleteWrapperModel from "../../../editor/autocomplete"; import DocumentPosition from "../../../editor/position"; import {ICompletion} from "../../../autocomplete/Autocompleter"; -const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$'); +const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s|(?<=^\\+))(' + EMOTICON_REGEX.source + ')\\s$'); const IS_MAC = navigator.platform.indexOf("Mac") !== -1; @@ -524,7 +524,7 @@ export default class BasicMessageEditor extends React.Component const position = model.positionForOffset(caret.offset, caret.atNodeEnd); const range = model.startRange(position); range.expandBackwardsWhile((index, offset, part) => { - return part.text[offset] !== " " && ( + return part.text[offset] !== " " && part.text[offset] !== "+" && ( part.type === "plain" || part.type === "pill-candidate" || part.type === "command" diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 78b1dd85db..4f5243a765 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -43,6 +43,8 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; import RateLimitedFunc from '../../../ratelimitedfunc'; import {Action} from "../../../dispatcher/actions"; import CountlyAnalytics from "../../../CountlyAnalytics"; +import {MatrixClientPeg} from "../../../MatrixClientPeg"; +import EMOJI_REGEX from 'emojibase-regex'; function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) { const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent); @@ -88,6 +90,25 @@ export function createMessageContent(model, permalinkCreator, replyToEvent) { return content; } +// exported for tests +export function isQuickReaction(model) { + const parts = model.parts; + if (parts.length == 0) return false; + let text = parts[0].text; + text += parts[1] ? parts[1].text : ""; + // shortcut takes the form "+:emoji:" or "+ :emoji:"" + // can be in 1 or 2 parts + if (parts.length <= 2) { + const hasShortcut = text.startsWith("+") || text.startsWith("+ "); + const emojiMatch = text.match(EMOJI_REGEX); + if (hasShortcut && emojiMatch && emojiMatch.length == 1) { + return emojiMatch[0] === text.substring(1) || + emojiMatch[0] === text.substring(2); + } + } + return false; +} + export default class SendMessageComposer extends React.Component { static propTypes = { room: PropTypes.object.isRequired, @@ -216,6 +237,41 @@ export default class SendMessageComposer extends React.Component { return false; } + _sendQuickReaction() { + const timeline = this.props.room.getLiveTimeline(); + const events = timeline.getEvents(); + const reaction = this.model.parts[1].text; + for (let i = events.length - 1; i >= 0; i--) { + if (events[i].getType() === "m.room.message") { + let shouldReact = true; + const lastMessage = events[i]; + const userId = MatrixClientPeg.get().getUserId(); + const messageReactions = this.props.room.getUnfilteredTimelineSet() + .getRelationsForEvent(lastMessage.getId(), "m.annotation", "m.reaction"); + + // if we have already sent this reaction, don't redact but don't re-send + if (messageReactions) { + const myReactionEvents = messageReactions.getAnnotationsBySender()[userId] || []; + const myReactionKeys = [...myReactionEvents] + .filter(event => !event.isRedacted()) + .map(event => event.getRelation().key); + shouldReact = !myReactionKeys.includes(reaction); + } + if (shouldReact) { + MatrixClientPeg.get().sendEvent(lastMessage.getRoomId(), "m.reaction", { + "m.relates_to": { + "rel_type": "m.annotation", + "event_id": lastMessage.getId(), + "key": reaction, + }, + }); + dis.dispatch({action: "message_sent"}); + } + break; + } + } + } + _getSlashCommand() { const commandText = this.model.parts.reduce((text, part) => { // use mxid to textify user pills in a command @@ -303,6 +359,11 @@ export default class SendMessageComposer extends React.Component { } } + if (isQuickReaction(this.model)) { + shouldSend = false; + this._sendQuickReaction(); + } + const replyToEvent = this.props.replyToEvent; if (shouldSend) { const startTime = CountlyAnalytics.getTimestamp(); diff --git a/src/editor/parts.ts b/src/editor/parts.ts index 5ed0c0529f..8a7ccfcb7b 100644 --- a/src/editor/parts.ts +++ b/src/editor/parts.ts @@ -190,7 +190,9 @@ abstract class PlainBasePart extends BasePart { return true; } // only split if the previous character is a space - return this._text[offset - 1] !== " "; + // or if it is a + and this is a : + return this._text[offset - 1] !== " " && + (this._text[offset - 1] !== "+" || chr !== ":"); } return true; } diff --git a/test/components/views/rooms/SendMessageComposer-test.js b/test/components/views/rooms/SendMessageComposer-test.js index 83a9388609..6eeac7ceea 100644 --- a/test/components/views/rooms/SendMessageComposer-test.js +++ b/test/components/views/rooms/SendMessageComposer-test.js @@ -18,8 +18,10 @@ import Adapter from "enzyme-adapter-react-16"; import { configure, mount } from "enzyme"; import React from "react"; import {act} from "react-dom/test-utils"; - -import SendMessageComposer, {createMessageContent} from "../../../../src/components/views/rooms/SendMessageComposer"; +import SendMessageComposer, { + createMessageContent, + isQuickReaction, +} from "../../../../src/components/views/rooms/SendMessageComposer"; import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; import EditorModel from "../../../../src/editor/model"; import {createPartCreator, createRenderer} from "../../../editor/mock"; @@ -227,6 +229,42 @@ describe('', () => { }); }); }); + + describe("isQuickReaction", () => { + it("correctly detects quick reaction", () => { + const model = new EditorModel([], createPartCreator(), createRenderer()); + model.update("+😊", "insertText", {offset: 3, atNodeEnd: true}); + + const isReaction = isQuickReaction(model); + + expect(isReaction).toBeTruthy(); + }); + + it("correctly detects quick reaction with space", () => { + const model = new EditorModel([], createPartCreator(), createRenderer()); + model.update("+ 😊", "insertText", {offset: 4, atNodeEnd: true}); + + const isReaction = isQuickReaction(model); + + expect(isReaction).toBeTruthy(); + }); + + it("correctly rejects quick reaction with extra text", () => { + const model = new EditorModel([], createPartCreator(), createRenderer()); + const model2 = new EditorModel([], createPartCreator(), createRenderer()); + const model3 = new EditorModel([], createPartCreator(), createRenderer()); + const model4 = new EditorModel([], createPartCreator(), createRenderer()); + model.update("+😊hello", "insertText", {offset: 8, atNodeEnd: true}); + model2.update(" +😊", "insertText", {offset: 4, atNodeEnd: true}); + model3.update("+ 😊😊", "insertText", {offset: 6, atNodeEnd: true}); + model4.update("+smiley", "insertText", {offset: 7, atNodeEnd: true}); + + expect(isQuickReaction(model)).toBeFalsy(); + expect(isQuickReaction(model2)).toBeFalsy(); + expect(isQuickReaction(model3)).toBeFalsy(); + expect(isQuickReaction(model4)).toBeFalsy(); + }); + }); }); From 2ffdfaef68bb2ae1e5d2fbcc7d1a2ee658f4dcb6 Mon Sep 17 00:00:00 2001 From: macekj Date: Tue, 24 Nov 2020 11:42:53 -0500 Subject: [PATCH 0049/1014] remove unnecessary lookbehind and comment emoticon regex Signed-off-by: macekj --- src/components/views/rooms/BasicMessageComposer.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 43316e90f2..1fa2ad681f 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -47,7 +47,8 @@ import AutocompleteWrapperModel from "../../../editor/autocomplete"; import DocumentPosition from "../../../editor/position"; import {ICompletion} from "../../../autocomplete/Autocompleter"; -const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s|(?<=^\\+))(' + EMOTICON_REGEX.source + ')\\s$'); +// matches emoticons which follow the start of a line, whitespace, or a plus at the start of a line +const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s|^\\+)(' + EMOTICON_REGEX.source + ')\\s$'); const IS_MAC = navigator.platform.indexOf("Mac") !== -1; From 2a02e57a953154dc15f0fd14f0321d95e3bcb3c9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 26 Nov 2020 14:35:09 +0000 Subject: [PATCH 0050/1014] Add UI for hold functionality --- res/css/_components.scss | 1 + .../views/context_menus/_CallContextMenu.scss | 23 +++ res/css/views/voip/_CallView.scss | 100 ++++++++++++ src/components/structures/ContextMenu.tsx | 6 +- .../views/context_menus/CallContextMenu.tsx | 51 ++++++ src/components/views/voip/CallView.tsx | 152 ++++++++++++++++-- src/i18n/strings/en_EN.json | 4 + 7 files changed, 320 insertions(+), 17 deletions(-) create mode 100644 res/css/views/context_menus/_CallContextMenu.scss create mode 100644 src/components/views/context_menus/CallContextMenu.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 445ed70ff4..414e2dc6a6 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -53,6 +53,7 @@ @import "./views/avatars/_MemberStatusMessageAvatar.scss"; @import "./views/avatars/_PulsedAvatar.scss"; @import "./views/avatars/_WidgetAvatar.scss"; +@import "./views/context_menus/_CallContextMenu.scss"; @import "./views/context_menus/_IconizedContextMenu.scss"; @import "./views/context_menus/_MessageContextMenu.scss"; @import "./views/context_menus/_StatusMessageContextMenu.scss"; diff --git a/res/css/views/context_menus/_CallContextMenu.scss b/res/css/views/context_menus/_CallContextMenu.scss new file mode 100644 index 0000000000..55b73b0344 --- /dev/null +++ b/res/css/views/context_menus/_CallContextMenu.scss @@ -0,0 +1,23 @@ +/* +Copyright 2020 New Vector Ltd + +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. +*/ + +.mx_CallContextMenu_item { + width: 205px; + height: 40px; + padding-left: 16px; + line-height: 40px; + vertical-align: center; +} diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 2b87181b1e..d5e58c94c5 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -43,17 +43,99 @@ limitations under the License. .mx_CallView_voice { position: relative; display: flex; + flex-direction: column; align-items: center; justify-content: center; background-color: $inverted-bg-color; } +.mx_CallView_voice_hold { + // This masks the avatar image so when it's blurred, the edge is still crisp + .mx_CallView_voice_avatarContainer { + border-radius: 2000px; + overflow: hidden; + position: relative; + &::after { + position: absolute; + content: ''; + width: 20px; + height: 20px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-image: url('$(res)/img/voip/paused.svg'); + background-position: center; + background-size: cover; + } + } + .mx_BaseAvatar { + filter: blur(20px); + overflow: hidden; + } +} + +.mx_CallView_voice_holdText { + height: 16px; + color: $accent-fg-color; + .mx_AccessibleButton_hasKind { + padding: 0px; + } +} + .mx_CallView_video { width: 100%; position: relative; z-index: 30; } +.mx_CallView_video_hold { + overflow: hidden; + + // we keep these around in the DOM: it saved wiring them up again when the call + // is resumed and keeps the container the right size + .mx_VideoFeed { + visibility: hidden; + } +} + +.mx_CallView_video_holdBackground { + position: absolute; + width: 100%; + height: 100%; + left: 0; + right: 0; + background-repeat: no-repeat; + background-size: cover; + background-position: center; + filter: blur(20px); +} + +.mx_CallView_video_holdContent { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-weight: bold; + color: $accent-fg-color; + text-align: center; + + &::before { + display: block; + margin-left: auto; + margin-right: auto; + content: ''; + width: 20px; + height: 20px; + background-image: url('$(res)/img/voip/paused.svg'); + background-position: center; + background-size: cover; + } + .mx_AccessibleButton_hasKind { + display: block; + padding: 0px; + } +} + .mx_CallView_header { height: 44px; display: flex; @@ -173,6 +255,12 @@ limitations under the License. } } +// Makes the alignment correct +.mx_CallView_callControls_nothing { + margin-right: auto; + cursor: initial; +} + .mx_CallView_callControls_button_micOn { &::before { background-image: url('$(res)/img/voip/mic-on.svg'); @@ -203,6 +291,18 @@ limitations under the License. } } +.mx_CallView_callControls_button_more { + margin-left: auto; + &::before { + background-image: url('$(res)/img/voip/more.svg'); + } +} + +.mx_CallView_callControls_button_more_hidden { + margin-left: auto; + cursor: initial; +} + .mx_CallView_callControls_button_invisible { visibility: hidden; pointer-events: none; diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index fa0d6682dd..190b231b74 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -398,7 +398,7 @@ export const toRightOf = (elementRect: DOMRect, chevronOffset = 12) => { }; // Placement method for to position context menu right-aligned and flowing to the left of elementRect -export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None) => { +export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None, vPadding = 0) => { const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace }; const buttonRight = elementRect.right + window.pageXOffset; @@ -408,9 +408,9 @@ export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None menuOptions.right = window.innerWidth - buttonRight; // Align the menu vertically on whichever side of the button has more space available. if (buttonBottom < window.innerHeight / 2) { - menuOptions.top = buttonBottom; + menuOptions.top = buttonBottom + vPadding; } else { - menuOptions.bottom = window.innerHeight - buttonTop; + menuOptions.bottom = (window.innerHeight - buttonTop) + vPadding; } return menuOptions; diff --git a/src/components/views/context_menus/CallContextMenu.tsx b/src/components/views/context_menus/CallContextMenu.tsx new file mode 100644 index 0000000000..31e82c19b1 --- /dev/null +++ b/src/components/views/context_menus/CallContextMenu.tsx @@ -0,0 +1,51 @@ +/* +Copyright 2020 New Vector Ltd + +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 React from 'react'; +import PropTypes from 'prop-types'; +import { _t } from '../../../languageHandler'; +import { ContextMenu, IProps as IContextMenuProps, MenuItem } from '../../structures/ContextMenu'; +import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; + +interface IProps extends IContextMenuProps { + call: MatrixCall; +} + +export default class CallContextMenu extends React.Component { + static propTypes = { + // js-sdk User object. Not required because it might not exist. + user: PropTypes.object, + }; + + constructor(props) { + super(props); + } + + onHoldUnholdClick = () => { + this.props.call.setRemoteOnHold(!this.props.call.isRemoteOnHold()); + this.props.onFinished(); + } + + render() { + const holdUnholdCaption = this.props.call.isRemoteOnHold() ? _t("Resume") : _t("Hold"); + + return + + {holdUnholdCaption} + + ; + } +} diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index db6d2b7ae0..c9f5db77e6 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef } from 'react'; +import React, { createRef, CSSProperties } from 'react'; import Room from 'matrix-js-sdk/src/models/room'; import dis from '../../../dispatcher/dispatcher'; import CallHandler from '../../../CallHandler'; @@ -28,6 +28,9 @@ import { CallEvent } from 'matrix-js-sdk/src/webrtc/call'; import classNames from 'classnames'; import AccessibleButton from '../elements/AccessibleButton'; import {isOnlyCtrlOrCmdKeyEvent, Key} from '../../../Keyboard'; +import {aboveLeftOf, ChevronFace, ContextMenuButton} from '../../structures/ContextMenu'; +import CallContextMenu from '../context_menus/CallContextMenu'; +import { avatarUrlForMember } from '../../../Avatar'; interface IProps { // js-sdk room object. If set, we will only show calls for the given @@ -51,10 +54,12 @@ interface IProps { interface IState { call: MatrixCall; isLocalOnHold: boolean, + isRemoteOnHold: boolean, micMuted: boolean, vidMuted: boolean, callState: CallState, controlsVisible: boolean, + showMoreMenu: boolean, } function getFullScreenElement() { @@ -89,11 +94,14 @@ const CONTROLS_HIDE_DELAY = 1000; // Height of the header duplicated from CSS because we need to subtract it from our max // height to get the max height of the video const HEADER_HEIGHT = 44; +const CONTEXT_MENU_VPADDING = 8; // How far the context menu sits above the button (px) export default class CallView extends React.Component { private dispatcherRef: string; private contentRef = createRef(); private controlsHideTimer: number = null; + private contextMenuButton = createRef(); + constructor(props: IProps) { super(props); @@ -101,10 +109,12 @@ export default class CallView extends React.Component { this.state = { call, isLocalOnHold: call ? call.isLocalOnHold() : null, + isRemoteOnHold: call ? call.isRemoteOnHold() : null, micMuted: call ? call.isMicrophoneMuted() : null, vidMuted: call ? call.isLocalVideoMuted() : null, callState: call ? call.state : null, controlsVisible: true, + showMoreMenu: false, } this.updateCallListeners(null, call); @@ -149,11 +159,16 @@ export default class CallView extends React.Component { this.setState({ call: newCall, isLocalOnHold: newCall ? newCall.isLocalOnHold() : null, + isRemoteOnHold: newCall ? newCall.isRemoteOnHold() : null, micMuted: newCall ? newCall.isMicrophoneMuted() : null, vidMuted: newCall ? newCall.isLocalVideoMuted() : null, callState: newCall ? newCall.state : null, controlsVisible: newControlsVisible, }); + } else { + this.setState({ + callState: newCall ? newCall.state : null, + }); } if (!newCall && getFullScreenElement()) { exitFullscreen(); @@ -187,16 +202,30 @@ export default class CallView extends React.Component { private updateCallListeners(oldCall: MatrixCall, newCall: MatrixCall) { if (oldCall === newCall) return; - if (oldCall) oldCall.removeListener(CallEvent.HoldUnhold, this.onCallHoldUnhold); - if (newCall) newCall.on(CallEvent.HoldUnhold, this.onCallHoldUnhold); + if (oldCall) { + oldCall.removeListener(CallEvent.LocalHoldUnhold, this.onCallLocalHoldUnhold); + oldCall.removeListener(CallEvent.RemoteHoldUnhold, this.onCallRemoteHoldUnhold); + } + if (newCall) { + newCall.on(CallEvent.LocalHoldUnhold, this.onCallLocalHoldUnhold); + newCall.on(CallEvent.RemoteHoldUnhold, this.onCallRemoteHoldUnhold); + } } - private onCallHoldUnhold = () => { + private onCallLocalHoldUnhold = () => { this.setState({ isLocalOnHold: this.state.call ? this.state.call.isLocalOnHold() : null, }); }; + private onCallRemoteHoldUnhold = () => { + this.setState({ + isRemoteOnHold: this.state.call ? this.state.call.isRemoteOnHold() : null, + // update both here because isLocalOnHold changes when we hold the call too + isLocalOnHold: this.state.call ? this.state.call.isLocalOnHold() : null, + }); + }; + private onFullscreenClick = () => { dis.dispatch({ action: 'video_fullscreen', @@ -223,6 +252,8 @@ export default class CallView extends React.Component { } private showControls() { + if (this.state.showMoreMenu) return; + if (!this.state.controlsVisible) { this.setState({ controlsVisible: true, @@ -252,6 +283,25 @@ export default class CallView extends React.Component { this.setState({vidMuted: newVal}); } + private onMoreClick = () => { + if (this.controlsHideTimer) { + clearTimeout(this.controlsHideTimer); + this.controlsHideTimer = null; + } + + this.setState({ + showMoreMenu: true, + controlsVisible: true, + }); + } + + private closeContextMenu = () => { + this.setState({ + showMoreMenu: false, + }); + this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY); + } + // we register global shortcuts here, they *must not conflict* with local shortcuts elsewhere or both will fire // Note that this assumes we always have a callview on screen at any given time // CallHandler would probably be a better place for this @@ -292,14 +342,32 @@ export default class CallView extends React.Component { }); } + private onCallResumeClick = () => { + this.state.call.setRemoteOnHold(false); + } + public render() { if (!this.state.call) return null; const client = MatrixClientPeg.get(); const callRoom = client.getRoom(this.state.call.roomId); + let contextMenu; + let callControls; if (this.props.room) { + if (this.state.showMoreMenu) { + contextMenu = ; + } + const micClasses = classNames({ mx_CallView_callControls_button: true, mx_CallView_callControls_button_micOn: !this.state.micMuted, @@ -333,17 +401,29 @@ export default class CallView extends React.Component { mx_CallView_callControls_hidden: !this.state.controlsVisible, }); - const vidMuteButton = this.state.call.type === CallType.Video ?
: null; + // The 'more' button actions are only relevant in a connected call + // When not connected, we have to put something there to make the flexbox alignment correct + const contextMenuButton = this.state.callState === CallState.Connected ? :
; + + // in the near future, the dial pad button will go on the left. For now, it's the nothing button + // because something needs to have margin-right: auto to make the alignment correct. callControls =
-
+ -
{ dis.dispatch({ @@ -355,6 +435,7 @@ export default class CallView extends React.Component { {vidMuteButton}
+ {contextMenuButton}
; } @@ -362,24 +443,66 @@ export default class CallView extends React.Component { // for voice calls (fills the bg) let contentView: React.ReactNode; + const isOnHold = this.state.isLocalOnHold || this.state.isRemoteOnHold; + let onHoldText = null; + if (this.state.isRemoteOnHold) { + onHoldText = _t("You held the call Resume", {}, { + a: sub => + {sub} + , + }); + } else if (this.state.isLocalOnHold) { + onHoldText = _t("%(peerName)s held the call", { + peerName: this.state.call.getOpponentMember().name, + }); + } + if (this.state.call.type === CallType.Video) { + let onHoldContent = null; + let onHoldBackground = null; + const backgroundStyle: CSSProperties = {}; + const containerClasses = classNames({ + mx_CallView_video: true, + mx_CallView_video_hold: isOnHold, + }); + if (isOnHold) { + onHoldContent =
+ {onHoldText} +
; + const backgroundAvatarUrl = avatarUrlForMember( + // is it worth getting the size of the div to pass here? + this.state.call.getOpponentMember(), 1024, 1024, 'crop', + ); + backgroundStyle.backgroundImage = 'url(' + backgroundAvatarUrl + ')'; + onHoldBackground =
; + } + // if we're fullscreen, we don't want to set a maxHeight on the video element. const maxVideoHeight = getFullScreenElement() ? null : this.props.maxVideoHeight - HEADER_HEIGHT; - contentView =
+ contentView =
+ {onHoldBackground} + {onHoldContent} {callControls}
; } else { const avatarSize = this.props.room ? 200 : 75; - contentView =
- + const classes = classNames({ + mx_CallView_voice: true, + mx_CallView_voice_hold: isOnHold, + }); + contentView =
+
+ +
+
{onHoldText}
{callControls}
; } @@ -431,6 +554,7 @@ export default class CallView extends React.Component { return
{header} {contentView} + {contextMenu}
; } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0d50128f32..de1c20be1b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -836,6 +836,8 @@ "When rooms are upgraded": "When rooms are upgraded", "My Ban List": "My Ban List", "This is your list of users/servers you have blocked - don't leave the room!": "This is your list of users/servers you have blocked - don't leave the room!", + "You held the call Resume": "You held the call Resume", + "%(peerName)s held the call": "%(peerName)s held the call", "Video Call": "Video Call", "Voice Call": "Voice Call", "Fill Screen": "Fill Screen", @@ -2231,6 +2233,8 @@ "Warning: You should only set up key backup from a trusted computer.": "Warning: You should only set up key backup from a trusted computer.", "Access your secure message history and set up secure messaging by entering your recovery key.": "Access your secure message history and set up secure messaging by entering your recovery key.", "If you've forgotten your recovery key you can ": "If you've forgotten your recovery key you can ", + "Resume": "Resume", + "Hold": "Hold", "Reject invitation": "Reject invitation", "Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?", "Unable to reject invite": "Unable to reject invite", From 5b7ad079d2471051f7d5c921ed0f76b0149ead5f Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 26 Nov 2020 14:55:07 +0000 Subject: [PATCH 0051/1014] Add SVGs --- res/img/voip/more.svg | 17 +++++++++++++++++ res/img/voip/paused.svg | 3 +++ 2 files changed, 20 insertions(+) create mode 100644 res/img/voip/more.svg create mode 100644 res/img/voip/paused.svg diff --git a/res/img/voip/more.svg b/res/img/voip/more.svg new file mode 100644 index 0000000000..7990f6bcff --- /dev/null +++ b/res/img/voip/more.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/res/img/voip/paused.svg b/res/img/voip/paused.svg new file mode 100644 index 0000000000..a967bf8ddf --- /dev/null +++ b/res/img/voip/paused.svg @@ -0,0 +1,3 @@ + + + From ede67684e4d436be9602c3e570079dfa036617af Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Thu, 26 Nov 2020 18:27:35 +0100 Subject: [PATCH 0052/1014] Removed trailing space --- src/SlashCommands.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index c7d5b1b08c..45c7251c3b 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -1096,7 +1096,7 @@ export const Commands = [ category: CommandCategories.messages, hideCompletionAfterSpace: true, }), - + ...effects.map((effect) => { return new Command({ command: effect.command, From c7706ac3d54a4fa2de79f2e7f85a4e2fae8f3a22 Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Wed, 21 Oct 2020 15:22:35 +0300 Subject: [PATCH 0053/1014] Add possibility for hosting provider IFrame Add config option hosting_signup_iframe that will render an action in the UserMenu and ProfileSettings for setting up a host with some hosting provider that provides an HTML to do that. Add iframe for hosting provider signup content --- res/css/_components.scss | 1 + .../views/dialogs/_HostingProviderDialog.scss | 44 +++++++++++++++++++ .../structures/HostingProviderDialog.tsx | 33 ++++++++++++++ .../structures/HostingProviderTrigger.tsx | 39 ++++++++++++++++ src/components/structures/UserMenu.tsx | 12 +++++ .../views/settings/ProfileSettings.js | 9 ++++ 6 files changed, 138 insertions(+) create mode 100644 res/css/views/dialogs/_HostingProviderDialog.scss create mode 100644 src/components/structures/HostingProviderDialog.tsx create mode 100644 src/components/structures/HostingProviderTrigger.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 445ed70ff4..a021a41be5 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -72,6 +72,7 @@ @import "./views/dialogs/_EditCommunityPrototypeDialog.scss"; @import "./views/dialogs/_FeedbackDialog.scss"; @import "./views/dialogs/_GroupAddressPicker.scss"; +@import "./views/dialogs/_HostingProviderDialog.scss"; @import "./views/dialogs/_IncomingSasDialog.scss"; @import "./views/dialogs/_InviteDialog.scss"; @import "./views/dialogs/_KeyboardShortcutsDialog.scss"; diff --git a/res/css/views/dialogs/_HostingProviderDialog.scss b/res/css/views/dialogs/_HostingProviderDialog.scss new file mode 100644 index 0000000000..0b0bdd4884 --- /dev/null +++ b/res/css/views/dialogs/_HostingProviderDialog.scss @@ -0,0 +1,44 @@ +/* +Copyright 2020 Element + +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. +*/ + +.mx_HostingProviderDialog .mx_Dialog { + width: 60%; + height: 70%; + overflow: hidden; + padding: 0; + max-width: initial; + max-height: initial; + position: relative; +} + +.mx_HostingProviderDialog_container { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + iframe { + width: 100%; + height: 100%; + border: none; + background-color: #fff; + } +} + +.mx_HostingProviderTrigger { + cursor: pointer; +} diff --git a/src/components/structures/HostingProviderDialog.tsx b/src/components/structures/HostingProviderDialog.tsx new file mode 100644 index 0000000000..0ee3d32b3f --- /dev/null +++ b/src/components/structures/HostingProviderDialog.tsx @@ -0,0 +1,33 @@ +/* +Copyright 2020 Element + +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 * as React from "react"; +import SdkConfig from "../../SdkConfig"; + +interface IProps {} + +interface IState {} + +export default class HostingProviderDialog extends React.PureComponent { + public render(): React.ReactNode { + const hostingSignupUrl = SdkConfig.get().hosting_signup_iframe; + return ( +
+