Merge branch 'develop' into jaywink/hosting-provider-iframe

pull/21833/head
Jason Robinson 2020-12-28 11:22:05 +02:00
commit 6843737a33
21 changed files with 602 additions and 108 deletions

View File

@ -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)

View File

@ -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": {
@ -27,11 +27,12 @@
"matrix-gen-i18n": "scripts/gen-i18n.js",
"matrix-prune-i18n": "scripts/prune-i18n.js"
},
"main": "./lib/index.js",
"typings": "./lib/index.d.ts",
"main": "./src/index.js",
"matrix_src_main": "./src/index.js",
"matrix_lib_main": "./lib/index.js",
"matrix_lib_typings": "./lib/index.d.ts",
"scripts": {
"prepare": "yarn build",
"prepublishOnly": "yarn build",
"i18n": "matrix-gen-i18n",
"prunei18n": "matrix-prune-i18n",
"diff-i18n": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && ./scripts/gen-i18n.js && node scripts/compare-file.js src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json",

View File

@ -15,7 +15,7 @@ limitations under the License.
*/
.mx_InteractiveAuthEntryComponents_emailWrapper {
padding-right: 60px;
padding-right: 100px;
position: relative;
margin-top: 32px;
margin-bottom: 32px;

View File

@ -41,6 +41,8 @@ limitations under the License.
padding: 0 0 0 24px;
font-size: inherit;
margin-top: 8px;
display: block;
text-align: start;
&::before {
content: '';

View File

@ -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;

View File

@ -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;
}
}

View File

@ -7,7 +7,6 @@ scripts/fetchdep.sh matrix-org matrix-js-sdk
pushd matrix-js-sdk
yarn link
yarn install $@
yarn build
popd
yarn link matrix-js-sdk

View File

@ -20,6 +20,7 @@ popd
yarn link matrix-js-sdk
yarn link
yarn install
yarn reskindex
# Finally, set up element-web
scripts/fetchdep.sh vector-im element-web

View File

@ -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',

View File

@ -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.

View File

@ -166,6 +166,32 @@ export const Commands = [
},
category: CommandCategories.messages,
}),
new Command({
command: 'tableflip',
args: '<message>',
description: _td('Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message'),
runFn: function(roomId, args) {
let message = '(╯°□°)╯︵ ┻━┻';
if (args) {
message = message + ' ' + args;
}
return success(MatrixClientPeg.get().sendTextMessage(roomId, message));
},
category: CommandCategories.messages,
}),
new Command({
command: 'unflip',
args: '<message>',
description: _td('Prepends ┬──┬ ( ゜-゜ノ) to a plain-text message'),
runFn: function(roomId, args) {
let message = '┬──┬ ( ゜-゜ノ)';
if (args) {
message = message + ' ' + args;
}
return success(MatrixClientPeg.get().sendTextMessage(roomId, message));
},
category: CommandCategories.messages,
}),
new Command({
command: 'lenny',
args: '<message>',

View File

@ -45,8 +45,13 @@ const SSOButton: React.FC<ISSOButtonProps> = ({
};
let icon;
if (idp && idp.icon && idp.icon.startsWith("https://")) {
icon = <img src={idp.icon} height="24" width="24" alt={label} />;
if (typeof idp?.icon === "string" && (idp.icon.startsWith("mxc://") || idp.icon.startsWith("https://"))) {
icon = <img
src={matrixClient.mxcUrlToHttp(idp.icon, 24, 24, "crop", true)}
height="24"
width="24"
alt={label}
/>;
}
const classes = classNames("mx_SSOButton", {

View File

@ -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>;

View File

@ -24,7 +24,7 @@ import { ActionPayload } from '../../../dispatcher/payloads';
import CallHandler from '../../../CallHandler';
import RoomAvatar from '../avatars/RoomAvatar';
import FormButton from '../elements/FormButton';
import { CallState } from 'matrix-js-sdk/lib/webrtc/call';
import { CallState } from 'matrix-js-sdk/src/webrtc/call';
interface IProps {
}

View 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);
}
}

View File

@ -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>,
];

View 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);
}
}
}
}

View File

@ -411,6 +411,8 @@
"Command error": "Command error",
"Usage": "Usage",
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Prepends ¯\\_(ツ)_/¯ to a plain-text message",
"Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message": "Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message",
"Prepends ┬──┬ ( ゜-゜ノ) to a plain-text message": "Prepends ┬──┬ ( ゜-゜ノ) to a plain-text message",
"Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message",
"Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown",
"Sends a message as html, without interpreting it as markdown": "Sends a message as html, without interpreting it as markdown",
@ -847,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",

View File

@ -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';

View File

@ -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");

View File

@ -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"
@ -6429,6 +6429,13 @@ lru-cache@^5.1.1:
dependencies:
yallist "^3.0.2"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
make-dir@^2.0.0, make-dir@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
@ -6513,8 +6520,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"
@ -6812,9 +6819,9 @@ node-modules-regexp@^1.0.0:
integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
node-notifier@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.0.tgz#a7eee2d51da6d0f7ff5094bc7108c911240c1620"
integrity sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA==
version "8.0.1"
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.1.tgz#f86e89bbc925f2b068784b31f382afdc6ca56be1"
integrity sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==
dependencies:
growly "^1.3.0"
is-wsl "^2.2.0"
@ -8354,9 +8361,11 @@ semver@7.0.0:
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
semver@^7.2.1, semver@^7.3.2:
version "7.3.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
version "7.3.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
dependencies:
lru-cache "^6.0.0"
serialize-javascript@^2.1.2:
version "2.1.2"
@ -9506,9 +9515,9 @@ uuid@^3.3.2:
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^8.3.0:
version "8.3.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31"
integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
v8-compile-cache@^2.0.3:
version "2.1.1"
@ -9798,6 +9807,11 @@ yallist@^3.0.2:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yargs-parser@^10.0.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8"