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 = ( +
    + + + +
    + ); + + const clearSelection = + clientState.clientId !== null ? ( + + ) : 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 ? ( + + ) : ( + + ); + + let clientTile = ( + + {client.name +
    +

    {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}

    +
    + ); + } 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.name} +

    + Invite created with {client.name} +

    +
    {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==