mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-16 21:24:59 +08:00
Merge branch 'develop' into improve-image-view
This commit is contained in:
commit
743b91d7a1
108
CHANGELOG.md
108
CHANGELOG.md
@ -1,3 +1,111 @@
|
||||
Changes in [3.11.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.11.1) (2020-12-21)
|
||||
=====================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.11.0...v3.11.1)
|
||||
|
||||
* Upgrade JS SDK to 9.4.1
|
||||
|
||||
Changes in [3.11.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.11.0) (2020-12-21)
|
||||
=====================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.11.0-rc.2...v3.11.0)
|
||||
|
||||
* Upgrade JS SDK to 9.4.0
|
||||
* [Release] Look for emoji in the body that will be displayed
|
||||
[\#5519](https://github.com/matrix-org/matrix-react-sdk/pull/5519)
|
||||
* [Release] Recognise `*.element.io` links as Element permalinks
|
||||
[\#5516](https://github.com/matrix-org/matrix-react-sdk/pull/5516)
|
||||
* [Release] Fixes for call UI
|
||||
[\#5513](https://github.com/matrix-org/matrix-react-sdk/pull/5513)
|
||||
* [RELEASE] Add a snowfall chat effect (with /snowfall command)
|
||||
[\#5512](https://github.com/matrix-org/matrix-react-sdk/pull/5512)
|
||||
* [Release] Fix padding in confirmation email registration prompt
|
||||
[\#5502](https://github.com/matrix-org/matrix-react-sdk/pull/5502)
|
||||
|
||||
Changes in [3.11.0-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.11.0-rc.2) (2020-12-16)
|
||||
===============================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.11.0-rc.1...v3.11.0-rc.2)
|
||||
|
||||
* Upgrade JS SDK to 9.4.0-rc.2
|
||||
|
||||
Changes in [3.11.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.11.0-rc.1) (2020-12-16)
|
||||
===============================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.10.0...v3.11.0-rc.1)
|
||||
|
||||
* Upgrade JS SDK to 9.4.0-rc.1
|
||||
* Translations update from Weblate
|
||||
[\#5497](https://github.com/matrix-org/matrix-react-sdk/pull/5497)
|
||||
* Unregister from the dispatcher in CallHandler
|
||||
[\#5495](https://github.com/matrix-org/matrix-react-sdk/pull/5495)
|
||||
* Better adhere to MSC process
|
||||
[\#5496](https://github.com/matrix-org/matrix-react-sdk/pull/5496)
|
||||
* Use random pickle key on all platforms
|
||||
[\#5483](https://github.com/matrix-org/matrix-react-sdk/pull/5483)
|
||||
* Fix mx_MemberList icons
|
||||
[\#5492](https://github.com/matrix-org/matrix-react-sdk/pull/5492)
|
||||
* Convert InviteDialog to TypeScript
|
||||
[\#5491](https://github.com/matrix-org/matrix-react-sdk/pull/5491)
|
||||
* Add keyboard shortcut for emoji reactions
|
||||
[\#5425](https://github.com/matrix-org/matrix-react-sdk/pull/5425)
|
||||
* Run chat effects on events sent by widgets too
|
||||
[\#5488](https://github.com/matrix-org/matrix-react-sdk/pull/5488)
|
||||
* Fix being unable to pin widgets
|
||||
[\#5487](https://github.com/matrix-org/matrix-react-sdk/pull/5487)
|
||||
* Line 1 / 2 Support
|
||||
[\#5468](https://github.com/matrix-org/matrix-react-sdk/pull/5468)
|
||||
* Remove impossible labs feature: sending hidden read receipts
|
||||
[\#5484](https://github.com/matrix-org/matrix-react-sdk/pull/5484)
|
||||
* Fix height of Remote Video in call
|
||||
[\#5456](https://github.com/matrix-org/matrix-react-sdk/pull/5456)
|
||||
* Add UI for hold functionality
|
||||
[\#5446](https://github.com/matrix-org/matrix-react-sdk/pull/5446)
|
||||
* Allow SearchBox to expand to fill width
|
||||
[\#5411](https://github.com/matrix-org/matrix-react-sdk/pull/5411)
|
||||
* Use room alias in generated permalink for rooms
|
||||
[\#5451](https://github.com/matrix-org/matrix-react-sdk/pull/5451)
|
||||
* Only show confetti if the current room is receiving an appropriate event
|
||||
[\#5482](https://github.com/matrix-org/matrix-react-sdk/pull/5482)
|
||||
* Throttle RoomState.members handler to improve performance
|
||||
[\#5481](https://github.com/matrix-org/matrix-react-sdk/pull/5481)
|
||||
* Handle manual hs urls better for the server picker
|
||||
[\#5477](https://github.com/matrix-org/matrix-react-sdk/pull/5477)
|
||||
* Add Olm as a dev dependency for types
|
||||
[\#5479](https://github.com/matrix-org/matrix-react-sdk/pull/5479)
|
||||
* Hide Invite to this room CTA if no permission
|
||||
[\#5476](https://github.com/matrix-org/matrix-react-sdk/pull/5476)
|
||||
* Fix width of underline in server picker dialog
|
||||
[\#5478](https://github.com/matrix-org/matrix-react-sdk/pull/5478)
|
||||
* Fix confetti room unread state check
|
||||
[\#5475](https://github.com/matrix-org/matrix-react-sdk/pull/5475)
|
||||
* Show confetti in a chat room on command or emoji
|
||||
[\#5140](https://github.com/matrix-org/matrix-react-sdk/pull/5140)
|
||||
* Fix inverted settings default value
|
||||
[\#5391](https://github.com/matrix-org/matrix-react-sdk/pull/5391)
|
||||
* Improve usability of the Server Picker Dialog
|
||||
[\#5474](https://github.com/matrix-org/matrix-react-sdk/pull/5474)
|
||||
* Fix typos in some strings
|
||||
[\#5473](https://github.com/matrix-org/matrix-react-sdk/pull/5473)
|
||||
* Bump highlight.js from 10.1.2 to 10.4.1
|
||||
[\#5472](https://github.com/matrix-org/matrix-react-sdk/pull/5472)
|
||||
* Remove old app test script path
|
||||
[\#5471](https://github.com/matrix-org/matrix-react-sdk/pull/5471)
|
||||
* add support for giving reason when redacting
|
||||
[\#5260](https://github.com/matrix-org/matrix-react-sdk/pull/5260)
|
||||
* Add support for Netlify to fetchdep script
|
||||
[\#5469](https://github.com/matrix-org/matrix-react-sdk/pull/5469)
|
||||
* Nest other layers inside on automation
|
||||
[\#5467](https://github.com/matrix-org/matrix-react-sdk/pull/5467)
|
||||
* Rebrand various CI scripts and modules
|
||||
[\#5466](https://github.com/matrix-org/matrix-react-sdk/pull/5466)
|
||||
* Add more widget sanity checking
|
||||
[\#5462](https://github.com/matrix-org/matrix-react-sdk/pull/5462)
|
||||
* Fix React complaining about unknown DOM props
|
||||
[\#5465](https://github.com/matrix-org/matrix-react-sdk/pull/5465)
|
||||
* Jump to home page when leaving a room
|
||||
[\#5464](https://github.com/matrix-org/matrix-react-sdk/pull/5464)
|
||||
* Fix SSO buttons for Social Logins
|
||||
[\#5463](https://github.com/matrix-org/matrix-react-sdk/pull/5463)
|
||||
* Social Login and login delight tweaks
|
||||
[\#5426](https://github.com/matrix-org/matrix-react-sdk/pull/5426)
|
||||
|
||||
Changes in [3.10.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.10.0) (2020-12-07)
|
||||
=====================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.10.0-rc.1...v3.10.0)
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-react-sdk",
|
||||
"version": "3.10.0",
|
||||
"version": "3.11.1",
|
||||
"description": "SDK for matrix.org using React",
|
||||
"author": "matrix.org",
|
||||
"repository": {
|
||||
|
@ -18,10 +18,7 @@ limitations under the License.
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
bottom: 72px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
z-index: 100;
|
||||
box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08);
|
||||
|
||||
// Disable pointer events for Jitsi widgets to function. Direct
|
||||
// calls have their own cursor and behaviour, but we need to make
|
||||
@ -49,8 +46,10 @@ limitations under the License.
|
||||
|
||||
.mx_IncomingCallBox {
|
||||
min-width: 250px;
|
||||
background-color: $primary-bg-color;
|
||||
background-color: $secondary-accent-color;
|
||||
padding: 8px;
|
||||
box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08);
|
||||
border-radius: 8px;
|
||||
|
||||
pointer-events: initial; // restore pointer events so the user can accept/decline
|
||||
cursor: pointer;
|
||||
|
@ -35,6 +35,10 @@ limitations under the License.
|
||||
|
||||
.mx_CallView_pip {
|
||||
width: 320px;
|
||||
padding-bottom: 8px;
|
||||
margin-top: 10px;
|
||||
box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08);
|
||||
border-radius: 8px;
|
||||
|
||||
.mx_CallView_voice {
|
||||
height: 180px;
|
||||
@ -84,50 +88,6 @@ limitations under the License.
|
||||
border-radius: 2000px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
background-image: url('$(res)/img/voip/paused.svg');
|
||||
background-position: center;
|
||||
background-size: 40px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.mx_CallView_pip &::after {
|
||||
background-size: 30px;
|
||||
}
|
||||
}
|
||||
.mx_BaseAvatar {
|
||||
filter: blur(20px);
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_CallView_voice_secondaryAvatarContainer {
|
||||
border-radius: 2000px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
background-image: url('$(res)/img/voip/paused.svg');
|
||||
background-position: center;
|
||||
background-size: 40px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.mx_CallView_pip &::after {
|
||||
background-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -341,7 +341,8 @@ export default class CallHandler {
|
||||
title: _t("Answered Elsewhere"),
|
||||
description: _t("The call was answered on another device."),
|
||||
});
|
||||
} else {
|
||||
} else if (oldState !== CallState.Fledgling) {
|
||||
// don't play the end-call sound for calls that never got off the ground
|
||||
this.play(AudioID.CallEnd);
|
||||
}
|
||||
}
|
||||
@ -616,6 +617,18 @@ export default class CallHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true if we are currently in any call where we haven't put the remote party on hold
|
||||
*/
|
||||
hasAnyUnheldCall() {
|
||||
for (const call of this.calls.values()) {
|
||||
if (call.state === CallState.Ended) continue;
|
||||
if (!call.isRemoteOnHold()) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async startCallApp(roomId: string, type: string) {
|
||||
dis.dispatch({
|
||||
action: 'appsDrawer',
|
||||
|
@ -163,7 +163,7 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to
|
||||
attribs.target = '_blank'; // by default
|
||||
|
||||
const transformed = tryTransformPermalinkToLocalHref(attribs.href);
|
||||
if (transformed !== attribs.href || attribs.href.match(linkifyMatrix.VECTOR_URL_PATTERN)) {
|
||||
if (transformed !== attribs.href || attribs.href.match(linkifyMatrix.ELEMENT_URL_PATTERN)) {
|
||||
attribs.href = transformed;
|
||||
delete attribs.target;
|
||||
}
|
||||
@ -438,13 +438,14 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
|
||||
delete sanitizeParams.textFilter;
|
||||
}
|
||||
|
||||
const contentBody = isDisplayedWithHtml ? safeBody : strippedBody;
|
||||
if (opts.returnString) {
|
||||
return isDisplayedWithHtml ? safeBody : strippedBody;
|
||||
return contentBody;
|
||||
}
|
||||
|
||||
let emojiBody = false;
|
||||
if (!opts.disableBigEmoji && bodyHasEmoji) {
|
||||
let contentBodyTrimmed = strippedBody !== undefined ? strippedBody.trim() : '';
|
||||
let contentBodyTrimmed = contentBody !== undefined ? contentBody.trim() : '';
|
||||
|
||||
// Ignore spaces in body text. Emojis with spaces in between should
|
||||
// still be counted as purely emoji messages.
|
||||
|
@ -19,7 +19,7 @@ import React, { createRef, CSSProperties, ReactNode } from 'react';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import CallHandler from '../../../CallHandler';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
import VideoFeed, { VideoFeedType } from "./VideoFeed";
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import { CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||
@ -423,7 +423,9 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||
const isOnHold = this.state.isLocalOnHold || this.state.isRemoteOnHold;
|
||||
let onHoldText = null;
|
||||
if (this.state.isRemoteOnHold) {
|
||||
onHoldText = _t("You held the call <a>Resume</a>", {}, {
|
||||
const holdString = CallHandler.sharedInstance().hasAnyUnheldCall() ?
|
||||
_td("You held the call <a>Switch</a>") : _td("You held the call <a>Resume</a>");
|
||||
onHoldText = _t(holdString, {}, {
|
||||
a: sub => <AccessibleButton kind="link" onClick={this.onCallResumeClick}>
|
||||
{sub}
|
||||
</AccessibleButton>,
|
||||
@ -478,20 +480,6 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||
mx_CallView_voice: true,
|
||||
mx_CallView_voice_hold: isOnHold,
|
||||
});
|
||||
let secondaryCallAvatar: ReactNode;
|
||||
|
||||
if (this.props.secondaryCall) {
|
||||
const secAvatarSize = this.props.pipMode ? 40 : 100;
|
||||
secondaryCallAvatar = <div className="mx_CallView_voice_secondaryAvatarContainer"
|
||||
style={{width: secAvatarSize, height: secAvatarSize}}
|
||||
>
|
||||
<RoomAvatar
|
||||
room={secCallRoom}
|
||||
height={secAvatarSize}
|
||||
width={secAvatarSize}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
contentView = <div className={classes} onMouseMove={this.onMouseMove}>
|
||||
<div className="mx_CallView_voice_avatarsContainer">
|
||||
@ -502,7 +490,6 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||
width={avatarSize}
|
||||
/>
|
||||
</div>
|
||||
{secondaryCallAvatar}
|
||||
</div>
|
||||
<div className="mx_CallView_voice_holdText">{onHoldText}</div>
|
||||
{callControls}
|
||||
@ -546,7 +533,7 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||
<AccessibleButton element='span' onClick={this.onSecondaryRoomAvatarClick}>
|
||||
<RoomAvatar room={secCallRoom} height={16} width={16} />
|
||||
<span className="mx_CallView_secondaryCall_roomName">
|
||||
{_t("%(name)s paused", { name: secCallRoom.name })}
|
||||
{_t("%(name)s on hold", { name: secCallRoom.name })}
|
||||
</span>
|
||||
</AccessibleButton>
|
||||
</span>;
|
||||
|
174
src/effects/fireworks/index.ts
Normal file
174
src/effects/fireworks/index.ts
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
Copyright 2020 Nurjin Jafar
|
||||
Copyright 2020 Nordeck IT + Consulting GmbH.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import ICanvasEffect from '../ICanvasEffect';
|
||||
|
||||
export type FireworksOptions = {
|
||||
/**
|
||||
* max fireworks count
|
||||
*/
|
||||
maxCount: number;
|
||||
/**
|
||||
* gravity value that firework adds to shift from it's start position
|
||||
*/
|
||||
gravity: number;
|
||||
}
|
||||
|
||||
type FireworksParticle = {
|
||||
/**
|
||||
* color
|
||||
*/
|
||||
color: string;
|
||||
/**
|
||||
* x,y are the point where the particle starts to position on canvas
|
||||
*/
|
||||
x: number;
|
||||
y: number;
|
||||
/**
|
||||
* vx,vy shift values from x and y
|
||||
*/
|
||||
vx: number;
|
||||
vy: number;
|
||||
/**
|
||||
* the alpha opacity of the firework particle (between 0 and 1, where 1 is opaque and 0 is invisible)
|
||||
*/
|
||||
alpha: number;
|
||||
/**
|
||||
* w,h width and height
|
||||
*/
|
||||
w: number;
|
||||
h: number;
|
||||
}
|
||||
|
||||
export const DefaultOptions: FireworksOptions = {
|
||||
maxCount: 500,
|
||||
gravity: 0.05,
|
||||
};
|
||||
|
||||
export default class Fireworks implements ICanvasEffect {
|
||||
private readonly options: FireworksOptions;
|
||||
|
||||
constructor(options: { [key: string]: any }) {
|
||||
this.options = {...DefaultOptions, ...options};
|
||||
}
|
||||
|
||||
private context: CanvasRenderingContext2D | null = null;
|
||||
private supportsAnimationFrame = window.requestAnimationFrame;
|
||||
private particles: Array<FireworksParticle> = [];
|
||||
public isRunning: boolean;
|
||||
|
||||
public start = async (canvas: HTMLCanvasElement, timeout = 3000) => {
|
||||
if (!canvas) {
|
||||
return;
|
||||
}
|
||||
this.isRunning = true;
|
||||
this.context = canvas.getContext('2d');
|
||||
this.supportsAnimationFrame.call(window, this.updateWorld);
|
||||
if (timeout) {
|
||||
window.setTimeout(this.stop, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
private updateWorld = () => {
|
||||
if (!this.isRunning && this.particles.length === 0) return;
|
||||
this.update();
|
||||
this.paint();
|
||||
this.supportsAnimationFrame.call(window, this.updateWorld);
|
||||
}
|
||||
|
||||
private update = () => {
|
||||
if (this.particles.length < this.options.maxCount && this.isRunning) {
|
||||
this.createFirework();
|
||||
}
|
||||
const alive = [];
|
||||
for (let i=0; i<this.particles.length; i++) {
|
||||
if (this.move(this.particles[i])) {
|
||||
alive.push(this.particles[i]);
|
||||
}
|
||||
}
|
||||
this.particles = alive;
|
||||
}
|
||||
|
||||
private paint = () => {
|
||||
if (!this.context || !this.context.canvas) return;
|
||||
this.context.globalCompositeOperation = 'destination-out';
|
||||
this.context.fillStyle = "rgba(0,0,0,0.5)";
|
||||
this.context.fillRect(0, 0, this.context.canvas.width, this.context.canvas.height);
|
||||
this.context.globalCompositeOperation = 'lighter';
|
||||
for (let i=0; i<this.particles.length; i++) {
|
||||
this.drawParticle(this.particles[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private createFirework = () => {
|
||||
if (!this.context || !this.context.canvas) return;
|
||||
const width = this.context.canvas.width;
|
||||
const height = this.context.canvas.height;
|
||||
const xPoint = Math.random() * (width - 200) + 100;
|
||||
const yPoint = Math.random() * (height - 200) + 100;
|
||||
const nFire = Math.random() * 50 + 100;
|
||||
const color = "rgb("+(~~(Math.random()*200+55))+","
|
||||
+(~~(Math.random()*200+55))+","+(~~(Math.random()*200+55))+")";
|
||||
for (let i=0; i<nFire; i++) {
|
||||
const particle = <FireworksParticle>{};
|
||||
particle.color = color;
|
||||
particle.w = particle.h = Math.random() * 4 + 1;
|
||||
particle.x = xPoint - particle.w / 2;
|
||||
particle.y = yPoint - particle.h / 2;
|
||||
particle.vx = (Math.random()-0.5)*10;
|
||||
particle.vy = (Math.random()-0.5)*10;
|
||||
particle.alpha = Math.random()*.5+.5;
|
||||
const vy = Math.sqrt(25 - particle.vx * particle.vx);
|
||||
if (Math.abs(particle.vy) > vy) {
|
||||
particle.vy = particle.vy > 0 ? vy: -vy;
|
||||
}
|
||||
this.particles.push(particle);
|
||||
}
|
||||
}
|
||||
|
||||
public stop = async () => {
|
||||
this.isRunning = false;
|
||||
}
|
||||
|
||||
private drawParticle = (particle: FireworksParticle): void => {
|
||||
if (!this.context || !this.context.canvas) {
|
||||
return;
|
||||
}
|
||||
this.context.save();
|
||||
this.context.beginPath();
|
||||
|
||||
this.context.translate(particle.x + particle.w / 2, particle.y + particle.h / 2);
|
||||
this.context.arc(0, 0, particle.w, 0, Math.PI * 2);
|
||||
this.context.fillStyle = particle.color;
|
||||
this.context.globalAlpha = particle.alpha;
|
||||
|
||||
this.context.closePath();
|
||||
this.context.fill();
|
||||
this.context.restore();
|
||||
}
|
||||
|
||||
|
||||
private move = (particle: FireworksParticle) => {
|
||||
particle.x += particle.vx;
|
||||
particle.vy += this.options.gravity;
|
||||
particle.y += particle.vy;
|
||||
particle.alpha -= 0.01;
|
||||
return !(particle.x <= -particle.w || particle.x >= screen.width ||
|
||||
particle.y >= screen.height ||
|
||||
particle.alpha <= 0);
|
||||
}
|
||||
}
|
@ -47,23 +47,47 @@ type ConfettiOptions = {
|
||||
/**
|
||||
* max confetti count
|
||||
*/
|
||||
maxCount: number,
|
||||
maxCount: number;
|
||||
/**
|
||||
* particle animation speed
|
||||
*/
|
||||
speed: number,
|
||||
speed: number;
|
||||
/**
|
||||
* the confetti animation frame interval in milliseconds
|
||||
*/
|
||||
frameInterval: number,
|
||||
frameInterval: number;
|
||||
/**
|
||||
* the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible)
|
||||
*/
|
||||
alpha: number,
|
||||
alpha: number;
|
||||
/**
|
||||
* use gradient instead of solid particle color
|
||||
*/
|
||||
gradient: boolean,
|
||||
gradient: boolean;
|
||||
};
|
||||
type FireworksOptions = {
|
||||
/**
|
||||
* max fireworks count
|
||||
*/
|
||||
maxCount: number;
|
||||
/**
|
||||
* gravity value that firework adds to shift from it's start position
|
||||
*/
|
||||
gravity: number;
|
||||
}
|
||||
type SnowfallOptions = {
|
||||
/**
|
||||
* The maximum number of snowflakes to render at a given time
|
||||
*/
|
||||
maxCount: number;
|
||||
/**
|
||||
* The amount of gravity to apply to the snowflakes
|
||||
*/
|
||||
gravity: number;
|
||||
/**
|
||||
* The amount of drift (horizontal sway) to apply to the snowflakes. Each snowflake varies.
|
||||
*/
|
||||
maxDrift: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,6 +108,29 @@ export const CHAT_EFFECTS: Array<Effect<{ [key: string]: any }>> = [
|
||||
gradient: false,
|
||||
},
|
||||
} as Effect<ConfettiOptions>,
|
||||
{
|
||||
emojis: ['🎆'],
|
||||
msgType: 'nic.custom.fireworks',
|
||||
command: 'fireworks',
|
||||
description: () => _td("Sends the given message with fireworks"),
|
||||
fallbackMessage: () => _t("sends fireworks") + " 🎆",
|
||||
options: {
|
||||
maxCount: 500,
|
||||
gravity: 0.05,
|
||||
},
|
||||
} as Effect<FireworksOptions>,
|
||||
{
|
||||
emojis: ['❄', '🌨'],
|
||||
msgType: 'io.element.effect.snowfall',
|
||||
command: 'snowfall',
|
||||
description: () => _td("Sends the given message with snowfall"),
|
||||
fallbackMessage: () => _t("sends snowfall") + " ❄",
|
||||
options: {
|
||||
maxCount: 200,
|
||||
gravity: 0.05,
|
||||
maxDrift: 5,
|
||||
},
|
||||
} as Effect<SnowfallOptions>,
|
||||
];
|
||||
|
||||
|
||||
|
148
src/effects/snowfall/index.ts
Normal file
148
src/effects/snowfall/index.ts
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import ICanvasEffect from '../ICanvasEffect';
|
||||
import { arrayFastClone } from "../../utils/arrays";
|
||||
|
||||
export type SnowfallOptions = {
|
||||
/**
|
||||
* The maximum number of snowflakes to render at a given time
|
||||
*/
|
||||
maxCount: number;
|
||||
/**
|
||||
* The amount of gravity to apply to the snowflakes
|
||||
*/
|
||||
gravity: number;
|
||||
/**
|
||||
* The amount of drift (horizontal sway) to apply to the snowflakes. Each snowflake varies.
|
||||
*/
|
||||
maxDrift: number;
|
||||
}
|
||||
|
||||
type Snowflake = {
|
||||
x: number;
|
||||
y: number;
|
||||
xCol: number;
|
||||
diameter: number;
|
||||
maximumDrift: number;
|
||||
gravity: number;
|
||||
}
|
||||
|
||||
export const DefaultOptions: SnowfallOptions = {
|
||||
maxCount: 200,
|
||||
gravity: 0.05,
|
||||
maxDrift: 5,
|
||||
};
|
||||
|
||||
const KEY_FRAME_INTERVAL = 15; // 15ms, roughly
|
||||
|
||||
export default class Snowfall implements ICanvasEffect {
|
||||
private readonly options: SnowfallOptions;
|
||||
|
||||
constructor(options: { [key: string]: any }) {
|
||||
this.options = {...DefaultOptions, ...options};
|
||||
}
|
||||
|
||||
private context: CanvasRenderingContext2D | null = null;
|
||||
private particles: Array<Snowflake> = [];
|
||||
private lastAnimationTime: number;
|
||||
|
||||
public isRunning: boolean;
|
||||
|
||||
public start = async (canvas: HTMLCanvasElement, timeout = 3000) => {
|
||||
if (!canvas) {
|
||||
return;
|
||||
}
|
||||
this.context = canvas.getContext('2d');
|
||||
this.particles = [];
|
||||
const count = this.options.maxCount;
|
||||
while (this.particles.length < count) {
|
||||
this.particles.push(this.resetParticle({} as Snowflake, canvas.width, canvas.height));
|
||||
}
|
||||
this.isRunning = true;
|
||||
requestAnimationFrame(this.renderLoop);
|
||||
if (timeout) {
|
||||
window.setTimeout(this.stop, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
public stop = async () => {
|
||||
this.isRunning = false;
|
||||
}
|
||||
|
||||
private resetParticle = (particle: Snowflake, width: number, height: number): Snowflake => {
|
||||
particle.x = Math.random() * width;
|
||||
particle.y = Math.random() * -height;
|
||||
particle.xCol = particle.x;
|
||||
particle.diameter = (Math.random() * 7) + 4;
|
||||
particle.maximumDrift = (Math.random() * this.options.maxDrift) + 3.5;
|
||||
particle.gravity = this.options.gravity + (Math.random() * 6) + 4;
|
||||
return particle;
|
||||
}
|
||||
|
||||
private renderLoop = (): 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);
|
||||
} else {
|
||||
const timeDelta = Date.now() - this.lastAnimationTime;
|
||||
if (timeDelta >= KEY_FRAME_INTERVAL || !this.lastAnimationTime) {
|
||||
// Clear the screen first
|
||||
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
|
||||
|
||||
this.lastAnimationTime = Date.now();
|
||||
this.animateAndRenderSnowflakes();
|
||||
}
|
||||
requestAnimationFrame(this.renderLoop);
|
||||
}
|
||||
};
|
||||
|
||||
private animateAndRenderSnowflakes() {
|
||||
if (!this.context || !this.context.canvas) {
|
||||
return;
|
||||
}
|
||||
const height = this.context.canvas.height;
|
||||
for (const particle of arrayFastClone(this.particles)) {
|
||||
particle.y += particle.gravity;
|
||||
|
||||
// We treat the drift as a sine function to have a more fluid-like movement instead
|
||||
// of a pong-like movement off walls of the X column. This means that for
|
||||
// $x=A\sin(\frac{2\pi}{P}y)$ we use the `maximumDrift` as the amplitude (A) and a
|
||||
// large multiplier to create a very long waveform through P.
|
||||
const peakDistance = 75 * particle.maximumDrift;
|
||||
const PI2 = Math.PI * 2;
|
||||
particle.x = particle.maximumDrift * Math.sin((PI2 / peakDistance) * particle.y);
|
||||
particle.x += particle.xCol; // bring the particle to the right place
|
||||
|
||||
const radius = particle.diameter / 2;
|
||||
this.context.save();
|
||||
this.context.beginPath();
|
||||
this.context.ellipse(particle.x, particle.y, radius, radius, 0, 0, 360);
|
||||
this.context.fillStyle = '#eaeaea'; // grey so it shows up on the light theme
|
||||
this.context.fill();
|
||||
this.context.closePath();
|
||||
this.context.restore();
|
||||
|
||||
// Remove any dead snowflakes
|
||||
const maxBounds = radius * 4; // make sure it's *really* off screen
|
||||
if (particle.y > (height + maxBounds)) {
|
||||
const idx = this.particles.indexOf(particle);
|
||||
this.particles.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -849,13 +849,18 @@
|
||||
"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!",
|
||||
"Sends the given message with confetti": "Sends the given message with confetti",
|
||||
"sends confetti": "sends confetti",
|
||||
"Sends the given message with fireworks": "Sends the given message with fireworks",
|
||||
"sends fireworks": "sends fireworks",
|
||||
"Sends the given message with snowfall": "Sends the given message with snowfall",
|
||||
"sends snowfall": "sends snowfall",
|
||||
"You held the call <a>Switch</a>": "You held the call <a>Switch</a>",
|
||||
"You held the call <a>Resume</a>": "You held the call <a>Resume</a>",
|
||||
"%(peerName)s held the call": "%(peerName)s held the call",
|
||||
"Video Call": "Video Call",
|
||||
"Voice Call": "Voice Call",
|
||||
"Fill Screen": "Fill Screen",
|
||||
"Return to call": "Return to call",
|
||||
"%(name)s paused": "%(name)s paused",
|
||||
"%(name)s on hold": "%(name)s on hold",
|
||||
"Unknown caller": "Unknown caller",
|
||||
"Incoming voice call": "Incoming voice call",
|
||||
"Incoming video call": "Incoming video call",
|
||||
|
@ -183,12 +183,14 @@ const escapeRegExp = function(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
};
|
||||
|
||||
// Recognise URLs from both our local vector and official vector as vector.
|
||||
// anyone else really should be using matrix.to.
|
||||
matrixLinkify.VECTOR_URL_PATTERN = "^(?:https?://)?(?:"
|
||||
+ escapeRegExp(window.location.host + window.location.pathname) + "|"
|
||||
+ "(?:www\\.)?(?:riot|vector)\\.im/(?:app|beta|staging|develop)/"
|
||||
+ ")(#.*)";
|
||||
// Recognise URLs from both our local and official Element deployments.
|
||||
// Anyone else really should be using matrix.to.
|
||||
matrixLinkify.ELEMENT_URL_PATTERN =
|
||||
"^(?:https?://)?(?:" +
|
||||
escapeRegExp(window.location.host + window.location.pathname) + "|" +
|
||||
"(?:www\\.)?(?:riot|vector)\\.im/(?:app|beta|staging|develop)/|" +
|
||||
"(?:app|beta|staging|develop)\\.element\\.io/" +
|
||||
")(#.*)";
|
||||
|
||||
matrixLinkify.MATRIXTO_URL_PATTERN = "^(?:https?://)?(?:www\\.)?matrix\\.to/#/(([#@!+]).*)";
|
||||
matrixLinkify.MATRIXTO_MD_LINK_PATTERN =
|
||||
@ -253,7 +255,7 @@ matrixLinkify.options = {
|
||||
target: function(href, type) {
|
||||
if (type === 'url') {
|
||||
const transformed = tryTransformPermalinkToLocalHref(href);
|
||||
if (transformed !== href || href.match(matrixLinkify.VECTOR_URL_PATTERN)) {
|
||||
if (transformed !== href || href.match(matrixLinkify.ELEMENT_URL_PATTERN)) {
|
||||
return null;
|
||||
} else {
|
||||
return '_blank';
|
||||
|
@ -331,7 +331,7 @@ export function tryTransformPermalinkToLocalHref(permalink: string): string {
|
||||
return permalink;
|
||||
}
|
||||
|
||||
const m = permalink.match(matrixLinkify.VECTOR_URL_PATTERN);
|
||||
const m = permalink.match(matrixLinkify.ELEMENT_URL_PATTERN);
|
||||
if (m) {
|
||||
return m[1];
|
||||
}
|
||||
@ -365,7 +365,7 @@ export function getPrimaryPermalinkEntity(permalink: string): string {
|
||||
|
||||
// If not a permalink, try the vector patterns.
|
||||
if (!permalinkParts) {
|
||||
const m = permalink.match(matrixLinkify.VECTOR_URL_PATTERN);
|
||||
const m = permalink.match(matrixLinkify.ELEMENT_URL_PATTERN);
|
||||
if (m) {
|
||||
// A bit of a hack, but it gets the job done
|
||||
const handler = new ElementPermalinkConstructor("http://localhost");
|
||||
|
10
yarn.lock
10
yarn.lock
@ -5058,9 +5058,9 @@ inherits@2, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
ini@^1.3.5, ini@~1.3.0:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
|
||||
version "1.3.7"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84"
|
||||
integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==
|
||||
|
||||
inquirer@^7.0.0:
|
||||
version "7.3.2"
|
||||
@ -6513,8 +6513,8 @@ mathml-tag-names@^2.0.1:
|
||||
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
|
||||
|
||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
||||
version "9.3.0"
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/ff6612f9d0aa1a7c08b65a0b41c5ab997506016f"
|
||||
version "9.4.1"
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/1717fcf499b943517213f2a81c41ffec0b50748e"
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
another-json "^0.2.0"
|
||||
|
Loading…
Reference in New Issue
Block a user