diff --git a/package.json b/package.json
index b4af7ab..586b537 100644
--- a/package.json
+++ b/package.json
@@ -3,13 +3,14 @@
"version": "0.1.0",
"private": true,
"dependencies": {
+ "@quentin-sommer/react-useragent": "^3.1.0",
"classnames": "^2.2.6",
"formik": "^2.1.4",
"matrix-cypher": "^0.1.12",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1",
- "yup": "^0.29.1"
+ "zod": "^1.10.3"
},
"scripts": {
"start": "react-scripts start",
@@ -68,14 +69,12 @@
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
- "@types/react-router-dom": "^5.1.5",
"@types/yup": "^0.29.3",
"eslint-config-matrix-org": "^0.1.0",
"husky": "^4.2.5",
"lint-staged": "^10.2.7",
"node-sass": "^4.14.1",
"prettier": "^2.0.5",
- "react-router-dom": "^5.2.0",
"storybook-addon-designs": "^5.4.0",
"ts-jest": "^26.1.4",
"typescript": "~3.7.2"
diff --git a/src/App.scss b/src/App.scss
index 1d4ff36..0f0855e 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -14,10 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-@import "./color-scheme";
+@import './color-scheme';
#root {
background-color: $app-background;
+ background-image: url('./imgs/background.svg');
+ background-repeat: no-repeat;
+ background-position: 50% -20%;
}
@mixin spacer {
diff --git a/src/App.tsx b/src/App.tsx
index 4d7e44e..5f60456 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -14,33 +14,36 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
+import React from 'react';
-import SingleColumn from "./layouts/SingleColumn";
-import CreateLinkTile from "./components/CreateLinkTile";
-import MatrixTile from "./components/MatrixTile";
-import Tile from "./components/Tile";
-import LinkRouter from "./pages/LinkRouter";
+import SingleColumn from './layouts/SingleColumn';
+import CreateLinkTile from './components/CreateLinkTile';
+import MatrixTile from './components/MatrixTile';
+import Tile from './components/Tile';
+import LinkRouter from './pages/LinkRouter';
-import "./App.scss";
+import './App.scss';
+
+import GlobalContext from './contexts/GlobalContext';
/* eslint-disable no-restricted-globals */
const App: React.FC = () => {
let page = (
<>
-
{" "}
+
+
>
);
+
if (location.hash) {
- console.log(location.hash);
- if (location.hash.startsWith("#/")) {
+ if (location.hash.startsWith('#/')) {
page = ;
} else {
page = (
- Links should be in the format {location.host}/#/{"<"}
- matrix-resource-identifier{">"}
+ Links should be in the format {location.host}/#/{'<'}
+ matrix-resource-identifier{'>'}
);
}
@@ -49,7 +52,7 @@ const App: React.FC = () => {
return (
- {page}
+ {page}
diff --git a/src/clients/Element.io.ts b/src/clients/Element.io.ts
index 402f97f..6d4f6c1 100644
--- a/src/clients/Element.io.ts
+++ b/src/clients/Element.io.ts
@@ -14,9 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { LinkedClient, Maturity, ClientKind } from './types';
+import {
+ LinkedClient,
+ Maturity,
+ ClientKind,
+ ClientId,
+ Platform,
+} from './types';
import { LinkKind } from '../parser/types';
-import logo from './element.svg';
+import logo from '../imgs/element.svg';
const Element: LinkedClient = {
kind: ClientKind.LINKED_CLIENT,
@@ -26,7 +32,9 @@ const Element: LinkedClient = {
homepage: 'https://element.io',
maturity: Maturity.STABLE,
description: 'Fully-featured Matrix client for the Web',
- tags: [],
+ platform: Platform.Desktop,
+ experimental: false,
+ clientId: ClientId.Element,
toUrl: (link) => {
switch (link.kind) {
case LinkKind.Alias:
@@ -50,4 +58,37 @@ const Element: LinkedClient = {
},
};
+export const ElementDevelop: LinkedClient = {
+ kind: ClientKind.LINKED_CLIENT,
+ name: 'Element Develop',
+ author: 'Element',
+ logo: logo,
+ homepage: 'https://element.io',
+ maturity: Maturity.STABLE,
+ description: 'Fully-featured Matrix client for the Web',
+ platform: Platform.Desktop,
+ experimental: true,
+ clientId: ClientId.ElementDevelop,
+ toUrl: (link) => {
+ switch (link.kind) {
+ case LinkKind.Alias:
+ case LinkKind.RoomId:
+ return new URL(
+ `https://develop.element.io/#/room/${link.identifier}`
+ );
+ case LinkKind.UserId:
+ return new URL(
+ `https://develop.element.io/#/user/${link.identifier}`
+ );
+ case LinkKind.Permalink:
+ return new URL(
+ `https://develop.element.io/#/room/${link.identifier}`
+ );
+ case LinkKind.GroupId:
+ return new URL(
+ `https://develop.element.io/#/group/${link.identifier}`
+ );
+ }
+ },
+};
export default Element;
diff --git a/src/clients/Weechat.tsx b/src/clients/Weechat.tsx
new file mode 100644
index 0000000..ff6b528
--- /dev/null
+++ b/src/clients/Weechat.tsx
@@ -0,0 +1,74 @@
+/*
+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 React from 'react';
+
+import { TextClient, Maturity, ClientKind, ClientId, Platform } from './types';
+
+import { LinkKind } from '../parser/types';
+
+import logo from '../imgs/weechat.svg';
+
+const Weechat: TextClient = {
+ kind: ClientKind.TEXT_CLIENT,
+ name: 'Weechat',
+ logo: logo,
+ author: 'Poljar',
+ homepage: 'https://github.com/poljar/weechat-matrix',
+ maturity: Maturity.LATE_BETA,
+ experimental: false,
+ platform: Platform.Desktop,
+ clientId: ClientId.WeeChat,
+ toInviteString: (link) => {
+ switch (link.kind) {
+ case LinkKind.Alias:
+ case LinkKind.RoomId:
+ return (
+
+ Type{' '}
+
+ /join {link.identifier}
+
+
+ );
+ case LinkKind.UserId:
+ return (
+
+ Type{' '}
+
+ /invite {link.identifier}
+
+
+ );
+ default:
+ return Weechat doesn't support this kind of link ;
+ }
+ },
+ copyString: (link) => {
+ switch (link.kind) {
+ case LinkKind.Alias:
+ case LinkKind.RoomId:
+ return `/join ${link.identifier}`;
+ case LinkKind.UserId:
+ return `/invite ${link.identifier}`;
+ default:
+ return '';
+ }
+ },
+ description: 'Command-line Matrix interface using Weechat',
+};
+
+export default Weechat;
diff --git a/src/clients/index.ts b/src/clients/index.ts
index 0fc102f..ba39676 100644
--- a/src/clients/index.ts
+++ b/src/clients/index.ts
@@ -16,12 +16,24 @@ limitations under the License.
import { Client } from './types';
-import Element from './Element.io';
+import Element, { ElementDevelop } from './Element.io';
+import Weechat from './Weechat';
/*
* All the supported clients of matrix.to
*/
-const clients: Client[] = [Element];
+const clients: Client[] = [Element, Weechat, ElementDevelop];
+
+/*
+ * A map from sharer string to client.
+ * Configured by hand so we can change the mappings
+ * easily later.
+ */
+export const clientMap: { [key: string]: Client } = {
+ [Element.clientId]: Element,
+ [Weechat.clientId]: Weechat,
+ [ElementDevelop.clientId]: ElementDevelop,
+};
/*
* All the supported clients of matrix.to
diff --git a/src/clients/types.ts b/src/clients/types.ts
index 8daea91..cdaed27 100644
--- a/src/clients/types.ts
+++ b/src/clients/types.ts
@@ -20,10 +20,10 @@ import { SafeLink } from '../parser/types';
* A collection of descriptive tags that can be added to
* a clients description.
*/
-export enum Tag {
- IOS = 'IOS',
- ANDROID = 'ANDROID',
- DESKTOP = 'DESKTOP',
+export enum Platform {
+ iOS = 'iOS',
+ Android = 'ANDROID',
+ Desktop = 'DESKTOP',
}
/*
@@ -45,6 +45,12 @@ export enum ClientKind {
TEXT_CLIENT = 'TEXT_CLIENT',
}
+export enum ClientId {
+ Element = 'element.io',
+ ElementDevelop = 'develop.element.io',
+ WeeChat = 'weechat',
+}
+
/*
* The descriptive details of a client
*/
@@ -54,8 +60,10 @@ export interface ClientDescription {
homepage: string;
logo: string;
description: string;
- tags: Tag[];
+ platform: Platform;
maturity: Maturity;
+ clientId: ClientId;
+ experimental: boolean;
}
/*
@@ -72,7 +80,8 @@ export interface LinkedClient extends ClientDescription {
*/
export interface TextClient extends ClientDescription {
kind: ClientKind.TEXT_CLIENT;
- toInviteString(parsedLink: SafeLink): string;
+ toInviteString(parsedLink: SafeLink): JSX.Element;
+ copyString(parsedLink: SafeLink): string;
}
/*
diff --git a/src/components/ClientList.scss b/src/components/ClientList.scss
new file mode 100644
index 0000000..24c8823
--- /dev/null
+++ b/src/components/ClientList.scss
@@ -0,0 +1,22 @@
+/*
+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.
+*/
+
+.clientList {
+ display: grid;
+ row-gap: 20px;
+ list-style-type: none;
+ padding-inline-start: 0;
+}
diff --git a/src/components/ClientList.tsx b/src/components/ClientList.tsx
new file mode 100644
index 0000000..fbef28f
--- /dev/null
+++ b/src/components/ClientList.tsx
@@ -0,0 +1,92 @@
+/*
+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 React, { useContext } from 'react';
+import { UAContext } from '@quentin-sommer/react-useragent';
+
+import { SafeLink } from '../parser/types';
+import { ActionType, ClientContext } from '../contexts/ClientContext';
+import Clients from '../clients';
+import { Client, Platform } from '../clients/types';
+import ClientTile from './ClientTile';
+
+import './ClientList.scss';
+
+interface IProps {
+ link: SafeLink;
+ rememberSelection: boolean;
+}
+
+const ClientList: React.FC = ({ link, rememberSelection }: IProps) => {
+ const [
+ { showOnlyDeviceClients, showExperimentalClients },
+ clientDispatcher,
+ ] = useContext(ClientContext);
+ const { uaResults } = useContext(UAContext);
+
+ /*
+ * Function to decide whether a client is shown
+ */
+ const showClient = (client: Client): boolean => {
+ let showClient = false;
+
+ if (!showOnlyDeviceClients || uaResults === {}) {
+ showClient = true;
+ }
+
+ switch (client.platform) {
+ case Platform.Desktop:
+ showClient = showClient || !(uaResults as any).mobile;
+ break;
+ case Platform.iOS:
+ showClient = showClient || (uaResults as any).ios;
+ break;
+ case Platform.Android:
+ showClient = showClient || (uaResults as any).android;
+ break;
+ }
+
+ if (!showExperimentalClients && client.experimental) {
+ showClient = false;
+ }
+
+ return showClient;
+ };
+
+ const clientLi = (client: Client): JSX.Element => (
+
+ rememberSelection
+ ? clientDispatcher({
+ action: ActionType.SetClient,
+ clientId: client.clientId,
+ })
+ : undefined
+ }
+ >
+
+
+ );
+
+ return (
+
+ {Clients.filter(showClient).map(clientLi)}
+
+ );
+};
+
+export default ClientList;
diff --git a/src/components/ClientSelection.scss b/src/components/ClientSelection.scss
new file mode 100644
index 0000000..f4977b7
--- /dev/null
+++ b/src/components/ClientSelection.scss
@@ -0,0 +1,27 @@
+/*
+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.
+*/
+
+.advanced {
+ display: grid;
+ row-gap: 20px;
+
+ .advancedOptions {
+ display: flex;
+ flex-direction: column;
+
+ align-items: flex-start;
+ }
+}
diff --git a/src/components/ClientSelection.tsx b/src/components/ClientSelection.tsx
new file mode 100644
index 0000000..8a41d4a
--- /dev/null
+++ b/src/components/ClientSelection.tsx
@@ -0,0 +1,94 @@
+/*
+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 React, { useContext, useState } from 'react';
+
+import './ClientSelection.scss';
+import { ActionType, ClientContext } from '../contexts/ClientContext';
+import ClientList from './ClientList';
+import { SafeLink } from '../parser/types';
+import Button from './Button';
+
+interface IProps {
+ link: SafeLink;
+}
+
+const ClientSelection: React.FC = ({ link }: IProps) => {
+ const [clientState, clientStateDispatch] = useContext(ClientContext);
+ const [rememberSelection, setRememberSelection] = useState(false);
+ const options = (
+
+
+ {
+ setRememberSelection(!rememberSelection);
+ }}
+ checked={rememberSelection}
+ />
+ Remember my selection for future invites in this browser
+
+
+ {
+ clientStateDispatch({
+ action: ActionType.ToggleShowOnlyDeviceClients,
+ });
+ }}
+ checked={clientState.showOnlyDeviceClients}
+ />
+ Show only clients suggested for this device
+
+
+ {
+ clientStateDispatch({
+ action: ActionType.ToggleShowExperimentalClients,
+ });
+ }}
+ checked={clientState.showExperimentalClients}
+ />
+ Show experimental clients
+
+
+ );
+
+ const clearSelection =
+ clientState.clientId !== null ? (
+
+ clientStateDispatch({
+ action: ActionType.ClearClient,
+ })
+ }
+ >
+ Clear my default client
+
+ ) : null;
+
+ return (
+
+ {options}
+
Clients you can accept this invite with
+
+ {clearSelection}
+
+ );
+};
+
+export default ClientSelection;
diff --git a/src/components/ClientTile.scss b/src/components/ClientTile.scss
new file mode 100644
index 0000000..0b8f091
--- /dev/null
+++ b/src/components/ClientTile.scss
@@ -0,0 +1,79 @@
+/*
+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 '../color-scheme';
+
+.clientTile {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ min-height: 150px;
+ width: 100%;
+
+ color: $foreground;
+
+ > img {
+ flex-shrink: 0;
+ height: 130px;
+ }
+
+ > div {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ h1 {
+ text-align: left;
+ font-size: 14px;
+ line-height: 24px;
+ }
+
+ p {
+ margin-right: 20px;
+ text-align: left;
+ }
+
+ .button {
+ margin: 5px;
+ }
+ }
+
+ border: 1px solid $borders;
+ border-radius: 8px;
+
+ padding: 15px;
+
+ // For the chevron
+ position: relative;
+
+ &::hover {
+ background-color: $grey;
+ }
+}
+
+.clientTileLink {
+ position: relative;
+
+ width: 100%;
+
+ &::after {
+ // TODO: add chevron top right
+ position: absolute;
+ right: 10px;
+ top: 5px;
+ content: '>';
+ }
+}
diff --git a/src/components/ClientTile.tsx b/src/components/ClientTile.tsx
new file mode 100644
index 0000000..ba3e57d
--- /dev/null
+++ b/src/components/ClientTile.tsx
@@ -0,0 +1,75 @@
+/*
+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 React from 'react';
+import classNames from 'classnames';
+
+import { Client, ClientKind } from '../clients/types';
+import { SafeLink } from '../parser/types';
+import Tile from './Tile';
+import Button from './Button';
+
+import './ClientTile.scss';
+
+interface IProps {
+ client: Client;
+ link: SafeLink;
+}
+
+const ClientTile: React.FC = ({ client, link }: IProps) => {
+ const inviteLine =
+ client.kind === ClientKind.TEXT_CLIENT ? (
+ {client.toInviteString(link)}
+ ) : null;
+
+ const className = classNames('clientTile', {
+ clientTileLink: client.kind === ClientKind.LINKED_CLIENT,
+ });
+
+ const inviteButton =
+ client.kind === ClientKind.LINKED_CLIENT ? (
+ Accept invite
+ ) : (
+
+ navigator.clipboard.writeText(client.copyString(link))
+ }
+ flashChildren="Invite copied"
+ >
+ Copy invite
+
+ );
+
+ let clientTile = (
+
+
+
+
{client.name}
+
{client.description}
+ {inviteLine}
+ {inviteButton}
+
+
+ );
+
+ if (client.kind === ClientKind.LINKED_CLIENT) {
+ clientTile = {clientTile} ;
+ }
+
+ return clientTile;
+};
+
+export default ClientTile;
diff --git a/src/components/CreateLinkTile.scss b/src/components/CreateLinkTile.scss
index d5aadc5..ada3856 100644
--- a/src/components/CreateLinkTile.scss
+++ b/src/components/CreateLinkTile.scss
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+@import '../color-scheme';
+
.createLinkTile {
background: none;
@@ -29,4 +31,12 @@ limitations under the License.
align-self: center;
padding: 0 30px;
}
+
+ > a {
+ color: $foreground;
+ }
+
+ h1 {
+ word-break: break-all;
+ }
}
diff --git a/src/components/CreateLinkTile.tsx b/src/components/CreateLinkTile.tsx
index 63a57d3..3553289 100644
--- a/src/components/CreateLinkTile.tsx
+++ b/src/components/CreateLinkTile.tsx
@@ -15,12 +15,14 @@ limitations under the License.
*/
import React, { useEffect, useRef } from 'react';
+import { Formik, Form } from 'formik';
+
import Tile from './Tile';
import Button from './Button';
import TextButton from './TextButton';
import Input from './Input';
-import { Formik, Form } from 'formik';
-import * as Yup from 'yup';
+import { parseHash } from '../parser/parser';
+import { LinkKind } from '../parser/types';
import './CreateLinkTile.scss';
@@ -28,6 +30,24 @@ interface ILinkNotCreatedTileProps {
setLink: React.Dispatch>;
}
+interface FormValues {
+ identifier: string;
+}
+
+// Hacky use of types here
+function validate(values: FormValues): Partial {
+ const errors: Partial = {};
+
+ const parse = parseHash(values.identifier);
+
+ if (parse.kind === LinkKind.ParseFailed) {
+ errors.identifier =
+ "That link doesn't look right. Double check the details.";
+ }
+
+ return errors;
+}
+
const LinkNotCreatedTile: React.FC = (
props: ILinkNotCreatedTileProps
) => {
@@ -41,21 +61,13 @@ const LinkNotCreatedTile: React.FC = (
initialValues={{
identifier: '',
}}
- validationSchema={Yup.object({
- identifier: Yup.string()
- .test(
- 'is-identifier',
- "That link doesn't look right. Double check the details.",
- (link) => link
- )
- .required('Required'),
- })}
+ validate={validate}
onSubmit={(values): void => {
props.setLink(
document.location.protocol +
'//' +
document.location.host +
- '/' +
+ '/#/' +
values.identifier
);
}}
@@ -91,9 +103,11 @@ const LinkCreatedTile: React.FC = (props) => {
return (
props.setLink('')}>
- Create another lnk
+ Create another link
- {props.link}
+
+ {props.link}
+
{
@@ -109,7 +123,6 @@ const LinkCreatedTile: React.FC = (props) => {
const CreateLinkTile: React.FC = () => {
const [link, setLink] = React.useState('');
- console.log(link);
if (!link) {
return ;
} else {
diff --git a/src/components/FakeProgress.scss b/src/components/FakeProgress.scss
new file mode 100644
index 0000000..32cd224
--- /dev/null
+++ b/src/components/FakeProgress.scss
@@ -0,0 +1,31 @@
+/*
+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 '../color-scheme';
+
+.fakeProgress {
+ width: 100%;
+ height: 4px;
+ background-color: lighten($grey, 50%);
+ border-radius: 4px;
+
+ > div {
+ width: 60%;
+ height: 100%;
+ background-color: $foreground;
+ border-radius: 4px;
+ }
+}
diff --git a/src/components/FakeProgress.tsx b/src/components/FakeProgress.tsx
new file mode 100644
index 0000000..587a7e3
--- /dev/null
+++ b/src/components/FakeProgress.tsx
@@ -0,0 +1,27 @@
+/*
+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 React from 'react';
+
+import './FakeProgress.scss';
+
+const FakeProgress = () => (
+
+);
+
+export default FakeProgress;
diff --git a/src/components/InviteTile.scss b/src/components/InviteTile.scss
index d1efbd2..0421927 100644
--- a/src/components/InviteTile.scss
+++ b/src/components/InviteTile.scss
@@ -15,11 +15,14 @@ limitations under the License.
*/
.inviteTile {
- .button {
- margin-top: 24px;
- }
+ display: grid;
+ row-gap: 24px;
- > .textButton {
- margin-top: 28px;
+ .inviteTileClientSelection {
+ margin: 0 5%;
+ display: grid;
+
+ justify-content: space-between;
+ row-gap: 20px;
}
}
diff --git a/src/components/InviteTile.tsx b/src/components/InviteTile.tsx
index bf200fc..a35b2c7 100644
--- a/src/components/InviteTile.tsx
+++ b/src/components/InviteTile.tsx
@@ -14,43 +14,104 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
+import React, { useState } from 'react';
import './InviteTile.scss';
import Tile from './Tile';
import LinkButton from './LinkButton';
-import TextButton from './TextButton';
+import Button from './Button';
+import ClientSelection from './ClientSelection';
import { Client, ClientKind } from '../clients/types';
import { SafeLink } from '../parser/types';
+import TextButton from './TextButton';
+import FakeProgress from './FakeProgress';
interface IProps {
children?: React.ReactNode;
- client: Client;
+ client: Client | null;
link: SafeLink;
}
const InviteTile: React.FC = ({ children, client, link }: IProps) => {
+ const [showAdvanced, setShowAdvanced] = useState(false);
let invite: React.ReactNode;
- switch (client.kind) {
- case ClientKind.LINKED_CLIENT:
- invite = (
-
- Accept invite
-
+ let advanced: React.ReactNode;
+
+ if (client === null) {
+ invite = showAdvanced ? (
+
+ ) : (
+ setShowAdvanced(!showAdvanced)}>
+ Accept invite
+
+ );
+ } else {
+ let inviteUseString: string;
+
+ switch (client.kind) {
+ case ClientKind.LINKED_CLIENT:
+ invite = (
+
+ Accept invite
+
+ );
+ inviteUseString = `Accepting will open ${link.identifier} in ${client.name}.`;
+ break;
+ case ClientKind.TEXT_CLIENT:
+ // TODO: copy to clipboard
+ invite = {client.toInviteString(link)}
;
+ navigator.clipboard.writeText(client.copyString(link));
+ inviteUseString = `These are instructions for ${client.name}.`;
+ break;
+ }
+
+ const advancedToggle = (
+
+ {inviteUseString}
+ setShowAdvanced(!showAdvanced)}
+ >
+ Change Client.
+
+
+ );
+
+ invite = (
+ <>
+ {invite}
+ {advancedToggle}
+ >
+ );
+ }
+
+ if (showAdvanced) {
+ if (client === null) {
+ advanced = (
+ <>
+ Pick an app to accept the invite with
+
+ >
);
- break;
- case ClientKind.TEXT_CLIENT:
- invite = {client.toInviteString(link)}
;
- break;
+ } else {
+ advanced = (
+ <>
+
+ Change app
+
+ >
+ );
+ }
}
return (
-
- {children}
- {invite}
- Advanced options
-
+ <>
+
+ {children}
+ {invite}
+ {advanced}
+
+ >
);
};
diff --git a/src/components/InvitingClientTile.stories.tsx b/src/components/InvitingClientTile.stories.tsx
new file mode 100644
index 0000000..6cac6ee
--- /dev/null
+++ b/src/components/InvitingClientTile.stories.tsx
@@ -0,0 +1,23 @@
+/*
+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 React from 'react';
+
+import ClientTile from './InvitingClientTile';
+
+export default { title: 'ClientTile' };
+
+export const Element = ;
diff --git a/src/components/InvitingClientTile.tsx b/src/components/InvitingClientTile.tsx
new file mode 100644
index 0000000..2d22a6c
--- /dev/null
+++ b/src/components/InvitingClientTile.tsx
@@ -0,0 +1,58 @@
+/*
+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 React from 'react';
+import Tile from './Tile';
+
+import { clientMap } from '../clients';
+import './MatrixTile.scss';
+
+interface IProps {
+ clientName: string;
+}
+
+const InvitingClientTile: React.FC = ({ clientName }: IProps) => {
+ const client = clientMap[clientName];
+
+ if (!client) {
+ return (
+
+ {/* TODO: add gh link */}
+
+ The client that created this link "{clientName}" is not a
+ recognised client. If this is a mistake and you'd like a
+ nice advertisement for it here please{' '}
+
+ open a pr
+
+ .
+
+
+ );
+ }
+
+ return (
+
+
+
+ {client.description}
+
+ );
+};
+
+export default InvitingClientTile;
diff --git a/src/components/LinkButton.tsx b/src/components/LinkButton.tsx
index ebada6d..375f926 100644
--- a/src/components/LinkButton.tsx
+++ b/src/components/LinkButton.tsx
@@ -21,8 +21,14 @@ import './Button.scss';
interface IProps extends React.LinkHTMLAttributes {}
-const LinkButton: React.FC = ({ className, ...props }: IProps) => (
-
+const LinkButton: React.FC = ({
+ className,
+ children,
+ ...props
+}: IProps) => (
+
+ {children}
+
);
export default LinkButton;
diff --git a/src/components/LinkPreview.tsx b/src/components/LinkPreview.tsx
index 9df012b..8796751 100644
--- a/src/components/LinkPreview.tsx
+++ b/src/components/LinkPreview.tsx
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useContext } from 'react';
import { getEvent, client } from 'matrix-cypher';
import { RoomPreviewWithTopic } from './RoomPreview';
@@ -22,13 +22,14 @@ import InviteTile from './InviteTile';
import { SafeLink, LinkKind } from '../parser/types';
import UserPreview from './UserPreview';
import EventPreview from './EventPreview';
-import Clients from '../clients';
+import { clientMap } from '../clients';
import {
getRoomFromId,
getRoomFromAlias,
getRoomFromPermalink,
getUser,
} from '../utils/cypher-wrapper';
+import { ClientContext } from '../contexts/ClientContext';
interface IProps {
link: SafeLink;
@@ -91,8 +92,19 @@ const LinkPreview: React.FC = ({ link }: IProps) => {
(async (): Promise => setContent(await invite({ link })))();
}, [link]);
+ const [{ clientId }] = useContext(ClientContext);
+
+ // Select which client to link to
+ const displayClientId = clientId
+ ? clientId
+ : link.arguments.client
+ ? link.arguments.client
+ : null;
+
+ const client = displayClientId ? clientMap[displayClientId] : null;
+
return (
-
+
{content}
);
diff --git a/src/components/RoomPreview.tsx b/src/components/RoomPreview.tsx
index a5a6225..e7e5404 100644
--- a/src/components/RoomPreview.tsx
+++ b/src/components/RoomPreview.tsx
@@ -26,11 +26,15 @@ interface IProps {
}
const RoomPreview: React.FC = ({ room }: IProps) => {
- const roomAlias = room.aliases ? room.aliases[0] : room.room_id;
+ const roomAlias = room.canonical_alias
+ ? room.canonical_alias
+ : room.aliases
+ ? room.aliases[0]
+ : room.room_id;
return (
-
{room.name ? room.name : room.room_id}
+
{room.name ? room.name : roomAlias}
{room.num_joined_members.toLocaleString()} members
{roomAlias}
diff --git a/src/contexts/ClientContext.ts b/src/contexts/ClientContext.ts
index cb530ce..0c78807 100644
--- a/src/contexts/ClientContext.ts
+++ b/src/contexts/ClientContext.ts
@@ -15,54 +15,92 @@ limitations under the License.
*/
import React from 'react';
+import { object, string, boolean, TypeOf } from 'zod';
-import { prefixFetch, Client, discoverServer } from 'matrix-cypher';
+import { ClientId } from '../clients/types';
+import { persistReducer } from '../utils/localStorage';
-type State = {
- clientURL: string;
- client: Client;
-}[];
+const STATE_SCHEMA = object({
+ clientId: string().nullable(),
+ showOnlyDeviceClients: boolean(),
+ showExperimentalClients: boolean(),
+});
+
+type State = TypeOf;
// Actions are a discriminated union.
-export enum ActionTypes {
- AddClient = 'ADD_CLIENT',
- RemoveClient = 'REMOVE_CLIENT',
+export enum ActionType {
+ SetClient = 'SET_CLIENT',
+ ClearClient = 'CLEAR_CLIENT',
+ ToggleShowOnlyDeviceClients = 'TOGGLE_SHOW_ONLY_DEVICE_CLIENTS',
+ ToggleShowExperimentalClients = 'TOGGLE_SHOW_EXPERIMENTAL_CLIENTS',
}
-export interface AddClient {
- action: ActionTypes.AddClient;
- clientURL: string;
+interface SetClient {
+ action: ActionType.SetClient;
+ clientId: ClientId;
}
-export interface RemoveClient {
- action: ActionTypes.RemoveClient;
- clientURL: string;
+interface ClearClient {
+ action: ActionType.ClearClient;
}
-export type Action = AddClient | RemoveClient;
+interface ToggleShowOnlyDeviceClients {
+ action: ActionType.ToggleShowOnlyDeviceClients;
+}
-export const INITIAL_STATE: State = [];
-export const reducer = async (state: State, action: Action): Promise => {
- switch (action.action) {
- case ActionTypes.AddClient:
- return state.filter((x) => x.clientURL !== action.clientURL);
+interface ToggleShowExperimentalClients {
+ action: ActionType.ToggleShowExperimentalClients;
+}
- case ActionTypes.RemoveClient:
- if (!state.filter((x) => x.clientURL === action.clientURL)) {
- const resolvedURL = await discoverServer(action.clientURL);
- state.push({
- clientURL: resolvedURL,
- client: prefixFetch(resolvedURL),
- });
- }
- }
- return state;
+export type Action =
+ | SetClient
+ | ClearClient
+ | ToggleShowOnlyDeviceClients
+ | ToggleShowExperimentalClients;
+
+const INITIAL_STATE: State = {
+ clientId: null,
+ showOnlyDeviceClients: true,
+ showExperimentalClients: false,
};
-// The null is a hack to make the type checker happy
-// create context does not need an argument
-const { Provider, Consumer } = React.createContext(null);
+export const [initialState, reducer] = persistReducer(
+ 'default-client',
+ INITIAL_STATE,
+ STATE_SCHEMA,
+ (state: State, action: Action): State => {
+ switch (action.action) {
+ case ActionType.SetClient:
+ return {
+ ...state,
+ clientId: action.clientId,
+ };
+ case ActionType.ToggleShowOnlyDeviceClients:
+ return {
+ ...state,
+ showOnlyDeviceClients: !state.showOnlyDeviceClients,
+ };
+ case ActionType.ToggleShowExperimentalClients:
+ return {
+ ...state,
+ showExperimentalClients: !state.showExperimentalClients,
+ };
+ case ActionType.ClearClient:
+ return {
+ ...state,
+ clientId: null,
+ };
+ }
+ }
+);
+
+// The defualt reducer needs to be overwritten with the one above
+// after it's been put through react's useReducer
+export const ClientContext = React.createContext<
+ [State, React.Dispatch]
+>([initialState, (): void => {}]);
// Quick rename to make importing easier
-export const ClientProvider = Provider;
-export const ClientConsumer = Consumer;
+export const ClientProvider = ClientContext.Provider;
+export const ClientConsumer = ClientContext.Consumer;
diff --git a/src/contexts/GlobalContext.tsx b/src/contexts/GlobalContext.tsx
new file mode 100644
index 0000000..20b4401
--- /dev/null
+++ b/src/contexts/GlobalContext.tsx
@@ -0,0 +1,36 @@
+/*
+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 React, { useReducer } from 'react';
+import { UserAgentProvider } from '@quentin-sommer/react-useragent';
+
+import {
+ ClientProvider,
+ reducer as clientReducer,
+ initialState as clientInitialState,
+} from './ClientContext';
+
+interface IProps {
+ children: React.ReactNode;
+}
+
+export default ({ children }: IProps): JSX.Element => (
+
+
+ {children}
+
+
+);
diff --git a/src/contexts/HSContext.ts b/src/contexts/HSContext.ts
new file mode 100644
index 0000000..e6afb95
--- /dev/null
+++ b/src/contexts/HSContext.ts
@@ -0,0 +1,113 @@
+/*
+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 React from 'react';
+import { string, object, union, literal, TypeOf } from 'zod';
+
+import { persistReducer } from '../utils/localStorage';
+
+//import { prefixFetch, Client, discoverServer } from 'matrix-cypher';
+
+enum HSOptions {
+ // The homeserver contact policy hasn't
+ // been set yet.
+ Unset = 'UNSET',
+ // Matrix.to should only contact a single provided homeserver
+ TrustedClientOnly = 'TRUSTED_CLIENT_ONLY',
+ // Matrix.to may contact any homeserver it requires
+ Any = 'ANY',
+ // Matrix.to may not contact any homeservers
+ None = 'NONE',
+}
+
+const STATE_SCHEMA = union([
+ object({
+ option: literal(HSOptions.Unset),
+ }),
+ object({
+ option: literal(HSOptions.None),
+ }),
+ object({
+ option: literal(HSOptions.Any),
+ }),
+ object({
+ option: literal(HSOptions.TrustedClientOnly),
+ hs: string(),
+ }),
+]);
+
+type State = TypeOf;
+
+// TODO: rename actions to something with more meaning out of context
+export enum ActionTypes {
+ SetHS = 'SET_HS',
+ SetAny = 'SET_ANY',
+ SetNone = 'SET_NONE',
+}
+
+export interface SetHS {
+ action: ActionTypes.SetHS;
+ HSURL: string;
+}
+
+export interface SetAny {
+ action: ActionTypes.SetAny;
+}
+
+export interface SetNone {
+ action: ActionTypes.SetNone;
+}
+
+export type Action = SetHS | SetAny | SetNone;
+
+export const INITIAL_STATE: State = {
+ option: HSOptions.Unset,
+};
+
+export const [initialState, reducer] = persistReducer(
+ 'home-server-options',
+ INITIAL_STATE,
+ STATE_SCHEMA,
+ (state: State, action: Action): State => {
+ switch (action.action) {
+ case ActionTypes.SetNone:
+ return {
+ option: HSOptions.None,
+ };
+ case ActionTypes.SetAny:
+ return {
+ option: HSOptions.Any,
+ };
+ case ActionTypes.SetHS:
+ return {
+ option: HSOptions.TrustedClientOnly,
+ hs: action.HSURL,
+ };
+ default:
+ return state;
+ }
+ }
+);
+
+// The defualt reducer needs to be overwritten with the one above
+// after it's been put through react's useReducer
+const { Provider, Consumer } = React.createContext<
+ [State, React.Dispatch]
+>([initialState, (): void => {}]);
+
+// Quick rename to make importing easier
+export const HSProvider = Provider;
+export const HSConsumer = Consumer;
diff --git a/src/imgs/background.svg b/src/imgs/background.svg
new file mode 100644
index 0000000..c5ad331
--- /dev/null
+++ b/src/imgs/background.svg
@@ -0,0 +1,221 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/clients/element.svg b/src/imgs/element.svg
similarity index 100%
rename from src/clients/element.svg
rename to src/imgs/element.svg
diff --git a/src/imgs/weechat.svg b/src/imgs/weechat.svg
new file mode 100644
index 0000000..96b92b1
--- /dev/null
+++ b/src/imgs/weechat.svg
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
diff --git a/src/index.scss b/src/index.scss
index 83fd0f2..a9b22d8 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-@import "color-scheme";
+@import 'color-scheme';
// CSS reset
@@ -39,7 +39,7 @@ body,
color: $font;
- overflow: scroll;
+ overflow: auto;
}
h1 {
@@ -49,6 +49,11 @@ h1 {
text-align: center;
}
+h4 {
+ text-align: left;
+ width: 100%;
+}
+
a {
color: $link;
text-decoration: none;
diff --git a/src/pages/LinkRouter.tsx b/src/pages/LinkRouter.tsx
index fe4a2a1..a455c7d 100644
--- a/src/pages/LinkRouter.tsx
+++ b/src/pages/LinkRouter.tsx
@@ -18,6 +18,7 @@ import React from 'react';
import Tile from '../components/Tile';
import LinkPreview from '../components/LinkPreview';
+import InvitingClientTile from '../components/InvitingClientTile';
import { parseHash } from '../parser/parser';
import { LinkKind } from '../parser/types';
@@ -28,9 +29,9 @@ interface IProps {
const LinkRouter: React.FC = ({ link }: IProps) => {
// our room id's will be stored in the hash
const parsedLink = parseHash(link);
- console.log({ link });
let feedback: JSX.Element;
+ let client: JSX.Element = <>>;
switch (parsedLink.kind) {
case LinkKind.ParseFailed:
feedback = (
@@ -41,7 +42,21 @@ const LinkRouter: React.FC = ({ link }: IProps) => {
);
break;
default:
- feedback = ;
+ if (parsedLink.arguments.client) {
+ client = (
+
+ );
+ }
+
+ feedback = (
+ <>
+
+
+ {client}
+ >
+ );
}
return feedback;
diff --git a/src/utils/localStorage.ts b/src/utils/localStorage.ts
new file mode 100644
index 0000000..b93a2fa
--- /dev/null
+++ b/src/utils/localStorage.ts
@@ -0,0 +1,59 @@
+/*
+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 {
+ Schema,
+} from 'zod';
+import React from 'react';
+
+/*
+ * Initialises local storage to initial value if
+ * a value matching the schema is not in storage.
+ */
+export function persistReducer(
+ stateKey: string,
+ initialState: T,
+ schema: Schema,
+ reducer: React.Reducer,
+): [T, React.Reducer] {
+ let currentState = initialState;
+ // Try to load state from local storage
+ const stateInStorage = localStorage.getItem(stateKey);
+ if (stateInStorage) {
+ try {
+ // Validate state type
+ const parsedState = JSON.parse(stateInStorage);
+ if (parsedState as T) {
+ currentState = schema.parse(parsedState);
+ }
+ } catch (e) {
+ // if invalid delete state
+ localStorage.setItem(stateKey, JSON.stringify(initialState));
+ }
+ } else {
+ localStorage.setItem(stateKey, JSON.stringify(initialState));
+ }
+
+ return [
+ currentState,
+ (state: T, action: A) => {
+ // state passed to this reducer is the source of truth
+ const newState = reducer(state, action);
+ localStorage.setItem(stateKey, JSON.stringify(newState));
+ return newState;
+ },
+ ];
+}
diff --git a/yarn.lock b/yarn.lock
index 280197a..f92850c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1133,7 +1133,7 @@
dependencies:
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.10.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.11.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==
@@ -1512,6 +1512,13 @@
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
+"@quentin-sommer/react-useragent@^3.1.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@quentin-sommer/react-useragent/-/react-useragent-3.1.0.tgz#8aa823fed7da5034edbf5bb1dc3cefd3d19473ea"
+ integrity sha512-twLVX4A7GpHCEcr5M3c97QR88t+uSZUjvNSTZMqoY5vjPXhpAcw0ZlWpMF6Nlg7Ofkwo3pZa3paQ9Nt7VJYHOA==
+ dependencies:
+ ua-parser-js "^0.7.20"
+
"@reach/router@^1.2.1":
version "1.3.4"
resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.3.4.tgz#d2574b19370a70c80480ed91f3da840136d10f8c"
@@ -2345,23 +2352,6 @@
dependencies:
"@types/react" "*"
-"@types/react-router-dom@^5.1.5":
- version "5.1.5"
- resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.5.tgz#7c334a2ea785dbad2b2dcdd83d2cf3d9973da090"
- integrity sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw==
- dependencies:
- "@types/history" "*"
- "@types/react" "*"
- "@types/react-router" "*"
-
-"@types/react-router@*":
- version "5.1.8"
- resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.8.tgz#4614e5ba7559657438e17766bb95ef6ed6acc3fa"
- integrity sha512-HzOyJb+wFmyEhyfp4D4NYrumi+LQgQL/68HvJO+q6XtuHSDvw6Aqov7sCAhjbNq3bUPgPqbdvjXC5HeB2oEAPg==
- dependencies:
- "@types/history" "*"
- "@types/react" "*"
-
"@types/react-syntax-highlighter@11.0.4":
version "11.0.4"
resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.4.tgz#d86d17697db62f98046874f62fdb3e53a0bbc4cd"
@@ -6614,11 +6604,6 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
-fn-name@~3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c"
- integrity sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA==
-
focus-lock@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.7.0.tgz#b2bfb0ca7beacc8710a1ff74275fe0dc60a1d88a"
@@ -7209,18 +7194,6 @@ highlight.js@~9.13.0:
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e"
integrity sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A==
-history@^4.9.0:
- version "4.10.1"
- resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
- integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
- dependencies:
- "@babel/runtime" "^7.1.2"
- loose-envify "^1.2.0"
- resolve-pathname "^3.0.0"
- tiny-invariant "^1.0.2"
- tiny-warning "^1.0.0"
- value-equal "^1.0.1"
-
hmac-drbg@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@@ -7230,7 +7203,7 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
-hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0:
+hoist-non-react-statics@^3.3.0:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -8078,11 +8051,6 @@ is-wsl@^2.1.1:
dependencies:
is-docker "^2.0.0"
-isarray@0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
- integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
-
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -9010,7 +8978,7 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
-lodash-es@^4.17.11, lodash-es@^4.17.14:
+lodash-es@^4.17.14:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
@@ -9102,7 +9070,7 @@ loglevelnext@^1.0.1:
es6-symbol "^3.1.1"
object.assign "^4.1.0"
-loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
+loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -9231,10 +9199,10 @@ material-colors@^1.2.1:
resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==
-matrix-cypher@^0.1.11:
- version "0.1.11"
- resolved "https://registry.yarnpkg.com/matrix-cypher/-/matrix-cypher-0.1.11.tgz#3152f47c097943592a12729f9c9340f56b130393"
- integrity sha512-fwEntUxC79ycItPl8PRhiJD1oBgOSrMaOjkmt3eYHvmj1F4ylUyxRl3pJICAqZdUeBpbRrMFDhg7oWtGp9v2Ng==
+matrix-cypher@^0.1.12:
+ version "0.1.13"
+ resolved "https://registry.yarnpkg.com/matrix-cypher/-/matrix-cypher-0.1.13.tgz#a8aadaaf7c15d0b85cca3c8d31f7ee007cefa65a"
+ integrity sha512-n0PiKVD4On3Vwvm8iXlYZhpTh5edwWVnd1U4nEjNEbjX9MyclxeSsBja4R1w+8NV2WIxV35iypC2y6u+ElX2zg==
dependencies:
cross-fetch "^3.0.5"
promise.any "^2.0.1"
@@ -9435,14 +9403,6 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
-mini-create-react-context@^0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz#df60501c83151db69e28eac0ef08b4002efab040"
- integrity sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==
- dependencies:
- "@babel/runtime" "^7.5.5"
- tiny-warning "^1.0.3"
-
mini-css-extract-plugin@0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e"
@@ -10373,13 +10333,6 @@ path-to-regexp@0.1.7:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
-path-to-regexp@^1.7.0:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
- integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
- dependencies:
- isarray "0.0.1"
-
path-type@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
@@ -11386,11 +11339,6 @@ prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1,
object-assign "^4.1.1"
react-is "^16.8.1"
-property-expr@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.2.tgz#fff2a43919135553a3bc2fdd94bdb841965b2330"
- integrity sha512-bc/5ggaYZxNkFKj374aLbEDqVADdYaLcFo8XBkishUWbaAdjlphaBFns9TvRA2pUseVL/wMFmui9X3IdNDU37g==
-
property-information@^5.0.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.5.0.tgz#4dc075d493061a82e2b7d096f406e076ed859943"
@@ -11777,7 +11725,7 @@ react-inspector@^4.0.0:
is-dom "^1.0.9"
prop-types "^15.6.1"
-react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.9.0:
+react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.9.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -11831,35 +11779,6 @@ react-redux@^7.0.2:
prop-types "^15.7.2"
react-is "^16.9.0"
-react-router-dom@^5.2.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662"
- integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==
- dependencies:
- "@babel/runtime" "^7.1.2"
- history "^4.9.0"
- loose-envify "^1.3.1"
- prop-types "^15.6.2"
- react-router "5.2.0"
- tiny-invariant "^1.0.2"
- tiny-warning "^1.0.0"
-
-react-router@5.2.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293"
- integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==
- dependencies:
- "@babel/runtime" "^7.1.2"
- history "^4.9.0"
- hoist-non-react-statics "^3.1.0"
- loose-envify "^1.3.1"
- mini-create-react-context "^0.4.0"
- path-to-regexp "^1.7.0"
- prop-types "^15.6.2"
- react-is "^16.6.0"
- tiny-invariant "^1.0.2"
- tiny-warning "^1.0.0"
-
react-scripts@3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.4.1.tgz#f551298b5c71985cc491b9acf3c8e8c0ae3ada0a"
@@ -12353,11 +12272,6 @@ resolve-from@^5.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
-resolve-pathname@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd"
- integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
-
resolve-url-loader@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.1.tgz#28931895fa1eab9be0647d3b2958c100ae3c0bf0"
@@ -13516,11 +13430,6 @@ symbol.prototype.description@^1.0.0:
es-abstract "^1.17.0-next.1"
has-symbols "^1.0.1"
-synchronous-promise@^2.0.13:
- version "2.0.13"
- resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.13.tgz#9d8c165ddee69c5a6542862b405bc50095926702"
- integrity sha512-R9N6uDkVsghHePKh1TEqbnLddO2IY25OcsksyFp/qBe7XYd0PVbKEWxhcdMhpLzE1I6skj5l4aEZ3CRxcbArlA==
-
table@^5.2.3:
version "5.4.6"
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
@@ -13678,12 +13587,7 @@ tiny-emitter@^2.0.0:
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
-tiny-invariant@^1.0.2:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
- integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
-
-tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3:
+tiny-warning@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
@@ -13757,11 +13661,6 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
-toposort@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
- integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=
-
tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0, tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
@@ -13919,6 +13818,11 @@ typescript@~3.7.2:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==
+ua-parser-js@^0.7.20:
+ version "0.7.21"
+ resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777"
+ integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==
+
unfetch@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.1.0.tgz#6ec2dd0de887e58a4dee83a050ded80ffc4137db"
@@ -14129,11 +14033,6 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
-value-equal@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
- integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
-
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
@@ -14799,20 +14698,12 @@ yargs@^13.3.0, yargs@^13.3.2:
y18n "^4.0.0"
yargs-parser "^13.1.2"
-yup@^0.29.1:
- version "0.29.3"
- resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.3.tgz#69a30fd3f1c19f5d9e31b1cf1c2b851ce8045fea"
- integrity sha512-RNUGiZ/sQ37CkhzKFoedkeMfJM0vNQyaz+wRZJzxdKE7VfDeVKH8bb4rr7XhRLbHJz5hSjoDNwMEIaKhuMZ8gQ==
- dependencies:
- "@babel/runtime" "^7.10.5"
- fn-name "~3.0.0"
- lodash "^4.17.15"
- lodash-es "^4.17.11"
- property-expr "^2.0.2"
- synchronous-promise "^2.0.13"
- toposort "^2.0.2"
-
zod@^1.10.2:
version "1.10.2"
resolved "https://registry.yarnpkg.com/zod/-/zod-1.10.2.tgz#fd2585bfcabc6a1dee267815ce0a036f184a5473"
integrity sha512-/T7CBnpJNf2hOlFburyOSP56nedBqrhwTgrwTKp3xPcZzfdRgRXORVHgDLMOIUQUVJyZbDrQqbS2RHuU/2XmHg==
+
+zod@^1.10.3:
+ version "1.10.3"
+ resolved "https://registry.yarnpkg.com/zod/-/zod-1.10.3.tgz#a69e60a96115d8968020e42aa0d7d1903e3e50c7"
+ integrity sha512-TxvLXUx7red0MKzx85sOwcoAWSsO+ChnYIsPzV/rWouS57yagZZlPp2bBTaXOu685lZE8H9vQK2nJs1vxleQTA==