Implement design review changes

This commit is contained in:
Jorik Schellekens 2020-09-16 00:19:52 +01:00
parent 471c9cd21d
commit 4d456c2799
34 changed files with 450 additions and 114 deletions

View File

@ -20,6 +20,7 @@ limitations under the License.
background-color: $app-background;
background-image: url('./imgs/background.svg');
background-repeat: no-repeat;
background-size: stretch;
background-position: 50% -20%;
}
@ -32,7 +33,7 @@ limitations under the License.
.topSpacer {
@include spacer;
height: 20vh;
height: 10vh;
}
.bottomSpacer {

View File

@ -21,6 +21,7 @@ import CreateLinkTile from './components/CreateLinkTile';
import MatrixTile from './components/MatrixTile';
import Tile from './components/Tile';
import LinkRouter from './pages/LinkRouter';
import Footer from './components/Footer';
import './App.scss';
@ -32,7 +33,6 @@ const App: React.FC = () => {
let page = (
<>
<CreateLinkTile />
<hr />
</>
);
@ -50,12 +50,18 @@ const App: React.FC = () => {
}
return (
<SingleColumn>
<div className="topSpacer" />
<GlobalContext>{page}</GlobalContext>
<MatrixTile />
<div className="bottomSpacer" />
</SingleColumn>
<GlobalContext>
<SingleColumn>
<div className="topSpacer" />
{page}
<div>
<MatrixTile isLink={!!location.hash} />
<br />
<Footer />
</div>
<div className="bottomSpacer" />
</SingleColumn>
</GlobalContext>
);
};

View File

@ -19,7 +19,7 @@ import classNames from 'classnames';
import { Room, User } from 'matrix-cypher';
import { getMediaQueryFromMCX } from '../utils/cypher-wrapper';
import logo from '../imgs/matrix-logo.svg';
import logo from '../imgs/chat-icon.svg';
import './Avatar.scss';

View File

@ -14,12 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
@import "../color-scheme";
@import '../color-scheme';
.button {
width: 100%;
padding: 1rem;
height: 48px;
border-radius: 2rem;
border: 0;
@ -28,6 +29,31 @@ limitations under the License.
font-size: 14px;
font-weight: 500;
&:hover {
cursor: pointer;
}
position: relative;
.buttonIcon {
position: absolute;
height: 24px;
width: 24px;
left: 18px;
top: 12px;
}
}
.buttonSecondary {
background-color: $background;
color: $foreground;
border: 1px solid $foreground;
}
.errorButton:hover {
cursor: not-allowed;
}
.buttonHighlight {

View File

@ -27,3 +27,7 @@ export const WithText: React.FC = () => (
{text('label', 'Hello Story Book')}
</Button>
);
export const Secondary: React.FC = () => (
<Button secondary>Secondary button</Button>
);

View File

@ -22,6 +22,9 @@ import './Button.scss';
interface IProps extends React.ButtonHTMLAttributes<Element> {
// Briefly display these instead of the children onClick
flashChildren?: React.ReactNode;
secondary?: boolean;
icon?: string;
flashIcon?: string;
}
/**
@ -31,7 +34,16 @@ const Button: React.FC<
IProps & React.RefAttributes<HTMLButtonElement>
> = React.forwardRef(
(
{ onClick, children, flashChildren, className, ...props }: IProps,
{
onClick,
children,
flashChildren,
className,
secondary,
icon,
flashIcon,
...props
}: IProps,
ref: React.Ref<HTMLButtonElement>
) => {
const [wasClicked, setWasClicked] = React.useState(false);
@ -51,8 +63,15 @@ const Button: React.FC<
const classNames = classnames('button', className, {
buttonHighlight: wasClicked,
buttonSecondary: secondary,
});
const iconSrc = wasClicked && flashIcon ? flashIcon : icon;
const buttonIcon = icon ? (
<img className="buttonIcon" src={iconSrc} alt="" />
) : null;
return (
<button
className={classNames}
@ -60,6 +79,7 @@ const Button: React.FC<
ref={ref}
{...props}
>
{buttonIcon}
{content}
</button>
);

View File

@ -38,7 +38,7 @@ const ClientSelection: React.FC<IProps> = ({ link }: IProps) => {
}}
checked={rememberSelection}
>
Remember my selection for future invites in this browser
Remember choice for future invites in this browser
</StyledCheckbox>
<StyledCheckbox
onChange={(): void => {
@ -79,7 +79,6 @@ const ClientSelection: React.FC<IProps> = ({ link }: IProps) => {
return (
<div className="advanced">
{options}
<h4>Clients you can accept this invite with</h4>
<ClientList link={link} rememberSelection={rememberSelection} />
{clearSelection}
</div>

View File

@ -19,7 +19,7 @@ limitations under the License.
.clientTile {
display: flex;
flex-direction: row;
align-items: center;
align-items: flex-start;
min-height: 150px;
width: 100%;
@ -28,7 +28,10 @@ limitations under the License.
> img {
flex-shrink: 0;
height: 130px;
height: 116px;
width: 116px;
margin-right: 14px;
border-radius: 16px;
}
> div {
@ -47,11 +50,10 @@ limitations under the License.
}
.button {
margin: 5px;
width: 50%;
}
}
border: 1px solid $borders;
border-radius: 8px;
padding: 15px;
@ -59,8 +61,8 @@ limitations under the License.
// For the chevron
position: relative;
&::hover {
background-color: $grey;
&:hover {
background-color: $app-background;
}
}
@ -68,12 +70,4 @@ limitations under the License.
position: relative;
width: 100%;
&::after {
// TODO: add chevron top right
position: absolute;
right: 10px;
top: 5px;
content: '>';
}
}

View File

@ -29,7 +29,6 @@ limitations under the License.
display: grid;
row-gap: 24px;
align-self: center;
padding: 0 30px;
}
> a {
@ -39,4 +38,56 @@ limitations under the License.
h1 {
word-break: break-all;
}
.createLinkReset {
height: 40px;
width: 40px;
border-radius: 100%;
border: 1px solid lighten($grey, 50%);
background: $background;
padding: 6px;
position: relative;
> div {
// This is a terrible case of faking it till
// we make it. It will break. I'm so sorry
position: absolute;
display: none;
width: max-content;
top: -35px;
left: -17px;
border-radius: 30px;
padding: 5px 15px;
background: $background;
word-wrap: none;
}
img {
height: 100%;
width: 100%;
border: 0;
filter: invert(12%);
}
&:hover {
border: 0;
background: $foreground;
cursor: pointer;
> div {
display: block;
}
}
}
}

View File

@ -19,11 +19,13 @@ import { Formik, Form } from 'formik';
import Tile from './Tile';
import Button from './Button';
import TextButton from './TextButton';
import Input from './Input';
import { parseHash } from '../parser/parser';
import { LinkKind } from '../parser/types';
import linkIcon from '../imgs/link.svg';
import copyIcon from '../imgs/copy.svg';
import tickIcon from '../imgs/tick.svg';
import refreshIcon from '../imgs/refresh.svg';
import './CreateLinkTile.scss';
interface ILinkNotCreatedTileProps {
@ -38,11 +40,16 @@ interface FormValues {
function validate(values: FormValues): Partial<FormValues> {
const errors: Partial<FormValues> = {};
if (values.identifier === '') {
errors.identifier = '';
return errors;
}
const parse = parseHash(values.identifier);
if (parse.kind === LinkKind.ParseFailed) {
errors.identifier =
"That link doesn't look right. Double check the details.";
"That identifier doesn't look right. Double check the details.";
}
return errors;
@ -72,14 +79,26 @@ const LinkNotCreatedTile: React.FC<ILinkNotCreatedTileProps> = (
);
}}
>
<Form>
<Input
name={'identifier'}
type={'text'}
placeholder="#room:example.com, @user:example.com"
/>
<Button type="submit">Get Link</Button>
</Form>
{(formik): JSX.Element => (
<Form>
<Input
name={'identifier'}
type={'text'}
placeholder="#room:example.com, @user:example.com"
autoFocus
/>
<Button
type="submit"
icon={linkIcon}
disabled={!!formik.errors.identifier}
className={
formik.errors.identifier ? 'errorButton' : ''
}
>
Create Link
</Button>
</Form>
)}
</Formik>
</Tile>
);
@ -102,14 +121,20 @@ const LinkCreatedTile: React.FC<ILinkCreatedTileProps> = (props) => {
return (
<Tile className="createLinkTile">
<TextButton onClick={(): void => props.setLink('')}>
Create another lnk
</TextButton>
<button
className="createLinkReset"
onClick={(): void => props.setLink('')}
>
<div>New link</div>
<img src={refreshIcon} />
</button>
<a href={props.link}>
<h1>{props.link}</h1>
</a>
<Button
flashChildren={'Copied'}
icon={copyIcon}
flashIcon={tickIcon}
onClick={(): void => {
navigator.clipboard.writeText(props.link);
}}

View File

@ -0,0 +1,33 @@
/*
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';
.footer {
display: grid;
grid-auto-flow: column;
justify-content: center;
column-gap: 5px;
* {
color: $font;
}
.textButton {
margin: 0;
padding: 0;
}
}

67
src/components/Footer.tsx Normal file
View File

@ -0,0 +1,67 @@
/*
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 HSContext, {
HSOptions,
ActionType as HSACtionType,
} from '../contexts/HSContext';
import ClientContext, {
ActionType as ClientActionType,
} from '../contexts/ClientContext';
import TextButton from './TextButton';
import './Footer.scss';
const Footer: React.FC = () => {
const [hsState, hsDispatch] = useContext(HSContext);
const [clientState, clientDispatch] = useContext(ClientContext);
const clear =
hsState.option !== HSOptions.Unset || clientState.clientId !== null ? (
<>
{' · '}
<TextButton
onClick={(): void => {
hsDispatch({
action: HSACtionType.Clear,
});
clientDispatch({
action: ClientActionType.ClearClient,
});
}}
>
Clear preferences
</TextButton>
</>
) : null;
return (
<div className="footer">
<a href="https://github.com/matrix-org/matrix.to">
A github project
</a>
{' · '}
<a href="https://github.com/matrix-org/matrix.to/tree/matrix-two/src/clients">
Add your client
</a>
{clear}
</div>
);
};
export default Footer;

View File

@ -46,6 +46,7 @@ limitations under the License.
width: 62px;
padding: 11px;
border-radius: 100%;
margin-left: 14px;
}
}

View File

@ -17,6 +17,7 @@ limitations under the License.
import React from 'react';
import HomeserverOptions from './HomeserverOptions';
import { LinkKind } from '../parser/types';
export default {
title: 'HomeserverOptions',
@ -29,4 +30,13 @@ export default {
},
};
export const Default: React.FC = () => <HomeserverOptions />;
export const Default: React.FC = () => (
<HomeserverOptions
link={{
identifier: '#banter:matrix.org',
arguments: { vias: [] },
kind: LinkKind.Alias,
originalLink: 'This is all made up',
}}
/>
);

View File

@ -23,12 +23,14 @@ import HSContext, { TempHSContext, ActionType } from '../contexts/HSContext';
import icon from '../imgs/telecom-mast.svg';
import Button from './Button';
import Input from './Input';
import Toggle from './Toggle';
import StyledCheckbox from './StyledCheckbox';
import { SafeLink } from '../parser/types';
import './HomeserverOptions.scss';
interface IProps {}
interface IProps {
link: SafeLink;
}
interface FormValues {
HSUrl: string;
@ -44,16 +46,19 @@ function validateURL(values: FormValues): Partial<FormValues> {
return errors;
}
const HomeserverOptions: React.FC<IProps> = () => {
const HomeserverOptions: React.FC<IProps> = ({ link }: IProps) => {
const HSStateDispatcher = useContext(HSContext)[1];
const TempHSStateDispatcher = useContext(TempHSContext)[1];
const [rememberSelection, setRemeberSelection] = useState(false);
const [usePrefered, setUsePrefered] = useState(false);
// Select which disaptcher to use based on whether we're writing
// the choice to localstorage
const dispatcher = rememberSelection
? HSStateDispatcher
: TempHSStateDispatcher;
const hsInput = usePrefered ? (
const hsInput = (
<Formik
initialValues={{
HSUrl: '',
@ -63,23 +68,36 @@ const HomeserverOptions: React.FC<IProps> = () => {
dispatcher({ action: ActionType.SetHS, HSURL: HSUrl })
}
>
<Form>
<Input
type="text"
name="HSUrl"
placeholder="https://example.com"
/>
<Button type="submit">Set HS</Button>
</Form>
{({ values, errors }): JSX.Element => (
<Form>
<Input
muted={!values.HSUrl}
type="text"
name="HSUrl"
placeholder="https://example.com"
/>
{values.HSUrl && !errors.HSUrl ? (
<Button secondary type="submit">
Use {values.HSUrl}
</Button>
) : null}
</Form>
)}
</Formik>
) : null;
);
return (
<Tile className="homeserverOptions">
<div className="homeserverOptionsDescription">
<div>
<h3>About {link.identifier}</h3>
<p>
Let's locate a homeserver to show you more information.
Select a homeserver to learn more about{' '}
{link.identifier}. <br />
The homeserver will provide metadata about the link such
as an avatar or description. Homeservers will be able to
relate your ip to resources you've opened invites for in
matrix.to
</p>
</div>
<img
@ -94,18 +112,14 @@ const HomeserverOptions: React.FC<IProps> = () => {
Remember my choice.
</StyledCheckbox>
<Button
secondary
onClick={(): void => {
dispatcher({ action: ActionType.SetAny });
}}
>
Use any homeserver
</Button>
<Toggle
checked={usePrefered}
onChange={(): void => setUsePrefered(!usePrefered)}
>
Use my prefered homeserver only
</Toggle>
{hsInput}
</Tile>
);

View File

@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
@import "../color-scheme";
@import "../error";
@import '../color-scheme';
@import '../error';
.input {
width: 100%;
@ -23,7 +23,8 @@ limitations under the License.
background: $background;
border: 1px solid $font;
border: 1px solid $foreground;
font: lighten($grey, 60%);
border-radius: 24px;
font-size: 14px;
@ -32,9 +33,18 @@ limitations under the License.
&.error {
@include error;
}
&:focus {
border: 1px solid $font;
font: $font;
}
}
.inputError {
@include error;
text-align: center;
}
.inputMuted {
border-color: lighten($grey, 60%);
}

View File

@ -20,12 +20,13 @@ import { useField } from 'formik';
import './Input.scss';
interface IProps extends React.InputHTMLAttributes<Element> {
interface IProps extends React.InputHTMLAttributes<HTMLElement> {
name: string;
type: string;
muted?: boolean;
}
const Input: React.FC<IProps> = ({ className, ...props }) => {
const Input: React.FC<IProps> = ({ className, muted, ...props }) => {
const [field, meta] = useField(props);
const error =
@ -35,6 +36,7 @@ const Input: React.FC<IProps> = ({ className, ...props }) => {
const classNames = classnames('input', className, {
error: meta.error,
inputMuted: !!muted,
});
return (

View File

@ -25,4 +25,9 @@ limitations under the License.
justify-content: space-between;
row-gap: 20px;
}
hr {
width: 100%;
margin: 0;
}
}

View File

@ -25,7 +25,6 @@ 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;
@ -39,10 +38,8 @@ const InviteTile: React.FC<IProps> = ({ children, client, link }: IProps) => {
let advanced: React.ReactNode;
if (client === null) {
invite = showAdvanced ? (
<FakeProgress />
) : (
<Button onClick={() => setShowAdvanced(!showAdvanced)}>
invite = showAdvanced ? null : (
<Button onClick={(): void => setShowAdvanced(!showAdvanced)}>
Accept invite
</Button>
);
@ -89,7 +86,9 @@ const InviteTile: React.FC<IProps> = ({ children, client, link }: IProps) => {
if (client === null) {
advanced = (
<>
<h4>Pick an app to accept the invite with</h4>
<hr />
<h3>Almost done!</h3>
<p>Pick a client to open {link.identifier}</p>
<ClientSelection link={link} />
</>
);
@ -104,12 +103,15 @@ const InviteTile: React.FC<IProps> = ({ children, client, link }: IProps) => {
}
}
advanced = advanced ? (
<div className="inviteTileClientSelection">{advanced}</div>
) : null;
return (
<>
<Tile className="inviteTile">
{children}
{invite}
<div className="inviteTileClientSelection">{advanced}</div>
{advanced}
</Tile>
</>
);

View File

@ -47,13 +47,12 @@ const invite = async ({
link: SafeLink;
}): Promise<JSX.Element> => {
// TODO: replace with client fetch
const defaultClient = await client(clientAddress);
switch (link.kind) {
case LinkKind.Alias:
return (
<RoomPreviewWithTopic
room={
await getRoomFromAlias(defaultClient, link.identifier)
await getRoomFromAlias(clientAddress, link.identifier)
}
/>
);
@ -61,14 +60,14 @@ const invite = async ({
case LinkKind.RoomId:
return (
<RoomPreviewWithTopic
room={await getRoomFromId(defaultClient, link.identifier)}
room={await getRoomFromId(clientAddress, link.identifier)}
/>
);
case LinkKind.UserId:
return (
<UserPreview
user={await getUser(defaultClient, link.identifier)}
user={await getUser(clientAddress, link.identifier)}
userId={link.identifier}
/>
);
@ -76,10 +75,10 @@ const invite = async ({
case LinkKind.Permalink:
return (
<EventPreview
room={await getRoomFromPermalink(defaultClient, link)}
room={await getRoomFromPermalink(clientAddress, link)}
event={
await getEvent(
defaultClient,
await client(clientAddress),
link.roomLink,
link.eventId
)
@ -128,7 +127,7 @@ const LinkPreview: React.FC<IProps> = ({ link }: IProps) => {
checked={showHSOptions}
onChange={(): void => setShowHSOPtions(!showHSOptions)}
>
Show more information
About {link.identifier}
</Toggle>
</>
);
@ -136,7 +135,7 @@ const LinkPreview: React.FC<IProps> = ({ link }: IProps) => {
content = (
<>
{content}
<HomeserverOptions />
<HomeserverOptions link={link} />
</>
);
}
@ -164,7 +163,9 @@ const LinkPreview: React.FC<IProps> = ({ link }: IProps) => {
originalLink: '',
}}
/>
) : null;
) : (
<p style={{ margin: '0 0 10px 0' }}>You're invited to join</p>
);
return (
<InviteTile client={client} link={link}>

View File

@ -21,15 +21,30 @@ import logo from '../imgs/matrix-logo.svg';
import './MatrixTile.scss';
const MatrixTile: React.FC = () => {
interface IProps {
isLink?: boolean;
}
const MatrixTile: React.FC<IProps> = ({ isLink }: IProps) => {
const copy = isLink ? (
<div>
This invite uses <a href="https://matrix.org">Matrix</a>, an open
network for secure, decentralized communication.
</div>
) : (
<div>
Matrix.to is a stateless URL redirecting service for the{' '}
<a href="https://matrix.org">Matrix</a> ecosystem.
</div>
);
return (
<Tile className="matrixTile">
<img src={logo} alt="matrix-logo" />
<div>
This invite uses <a href="https://matrix.org">Matrix</a>, an
open network for secure, decentralized communication.
</div>
</Tile>
<div>
<Tile className="matrixTile">
<img src={logo} alt="matrix-logo" />
{copy}
</Tile>
</div>
);
};

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
@import "../color-scheme";
@import '../color-scheme';
.tile {
background-color: $background;
@ -30,4 +30,5 @@ limitations under the License.
p {
color: $grey;
}
transition: width 2s, height 2s, transform 2s;
}

View File

@ -21,7 +21,7 @@ import chevron from '../imgs/chevron-down.svg';
import './Toggle.scss';
interface IProps extends React.InputHTMLAttributes<Element> {
children?: React.ReactChild;
children?: React.ReactNode;
}
const Toggle: React.FC<IProps> = ({ children, ...props }: IProps) => (

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
@import "../color-scheme";
@import '../color-scheme';
.userPreview {
width: 100%;
@ -70,5 +70,17 @@ limitations under the License.
.avatar {
flex-grow: 0;
flex-shrink: 0;
height: 32px;
width: 32px;
}
&.centeredMiniUserPreview {
h1 {
width: unset;
text-align: center;
}
img {
display: none;
}
}
}

View File

@ -16,6 +16,7 @@ limitations under the License.
import React, { useState, useEffect } from 'react';
import { client, User, getUserDetails } from 'matrix-cypher';
import classNames from 'classnames';
import icon from '../imgs/chat-icon.svg';
import Avatar, { UserAvatar } from './Avatar';
@ -54,8 +55,12 @@ export const InviterPreview: React.FC<InviterPreviewProps> = ({
) : (
<Avatar label={`Placeholder icon for ${userId}`} avatarUrl={icon} />
);
const className = classNames('miniUserPreview', {
centeredMiniUserPreview: !user,
});
return (
<div className="miniUserPreview">
<div className={className}>
<div>
<h1>
Invited by <b>{user ? user.displayname : userId}</b>

View File

@ -101,6 +101,8 @@ export const ClientContext = React.createContext<
[State, React.Dispatch<Action>]
>([initialState, (): void => {}]);
export default ClientContext;
// Quick rename to make importing easier
export const ClientProvider = ClientContext.Provider;
export const ClientConsumer = ClientContext.Consumer;

View File

@ -50,6 +50,7 @@ export type State = TypeOf<typeof STATE_SCHEMA>;
export enum ActionType {
SetHS = 'SET_HS',
SetAny = 'SET_ANY',
Clear = 'CLEAR',
}
export interface SetHS {
@ -61,13 +62,17 @@ export interface SetAny {
action: ActionType.SetAny;
}
export type Action = SetHS | SetAny;
export interface Clear {
action: ActionType.Clear;
}
export type Action = SetHS | SetAny | Clear;
export const INITIAL_STATE: State = {
option: HSOptions.Unset,
};
export const unpersistedReducer = (state: State, action: Action): State => {
export const unpersistedReducer = (_state: State, action: Action): State => {
switch (action.action) {
case ActionType.SetAny:
return {
@ -78,8 +83,10 @@ export const unpersistedReducer = (state: State, action: Action): State => {
option: HSOptions.TrustedHSOnly,
hs: action.HSURL,
};
default:
return state;
case ActionType.Clear:
return {
option: HSOptions.Unset,
};
}
};

4
src/imgs/copy.svg Normal file
View File

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5 15H6C4.89543 15 4 14.1046 4 13V6C4 4.89543 4.89543 4 6 4H13C14.1046 4 15 4.89543 15 6V9.5" stroke="white" stroke-width="1.5"/>
<rect x="9" y="9" width="11" height="11" rx="2" stroke="white" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 328 B

3
src/imgs/link.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5285 6.54089L13.0273 6.04207C14.4052 4.66426 16.6259 4.65104 17.9874 6.01253C19.349 7.37402 19.3357 9.59466 17.9579 10.9725L15.5878 13.3425C14.21 14.7203 11.9893 14.7335 10.6277 13.372M11.4717 17.4589L10.9727 17.9579C9.59481 19.3357 7.37409 19.349 6.01256 17.9875C4.65102 16.626 4.66426 14.4053 6.04211 13.0275L8.41203 10.6577C9.78988 9.27988 12.0106 9.26665 13.3721 10.6281" stroke="white" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 549 B

3
src/imgs/refresh.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.6498 6.35001C16.0198 4.72001 13.7098 3.78001 11.1698 4.04001C7.49978 4.41001 4.47978 7.39001 4.06978 11.06C3.51978 15.91 7.26978 20 11.9998 20C15.1898 20 17.9298 18.13 19.2098 15.44C19.5298 14.77 19.0498 14 18.3098 14C17.9398 14 17.5898 14.2 17.4298 14.53C16.2998 16.96 13.5898 18.5 10.6298 17.84C8.40978 17.35 6.61978 15.54 6.14978 13.32C5.30978 9.44001 8.25978 6.00001 11.9998 6.00001C13.6598 6.00001 15.1398 6.69001 16.2198 7.78001L14.7098 9.29001C14.0798 9.92001 14.5198 11 15.4098 11H18.9998C19.5498 11 19.9998 10.55 19.9998 10V6.41001C19.9998 5.52001 18.9198 5.07001 18.2898 5.70001L17.6498 6.35001Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 738 B

View File

@ -1,3 +1,3 @@
<svg width="10" height="8" viewBox="0 0 10 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.979065 4L3.63177 7L8.93718 1" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.5 12.5L8.84497 15.845C9.71398 16.714 11.1538 16.601 11.8767 15.6071L18.5 6.5" stroke="white" stroke-width="2" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 207 B

After

Width:  |  Height:  |  Size: 250 B

View File

@ -47,6 +47,7 @@ h1 {
font-size: 24px;
line-height: 32px;
text-align: center;
color: $foreground;
}
h4 {

View File

@ -53,7 +53,6 @@ const LinkRouter: React.FC<IProps> = ({ link }: IProps) => {
feedback = (
<>
<LinkPreview link={parsedLink} />
<hr />
{client}
</>
);

View File

@ -20,6 +20,7 @@ limitations under the License.
import {
Client,
client,
Room,
RoomAlias,
User,
@ -59,7 +60,8 @@ export const fallbackRoom = ({
const roomAlias_ = roomAlias ? roomAlias : identifier;
return {
aliases: [roomAlias_],
topic: 'Unable to find room details.',
topic:
'No details available. This might be a private room. You can still join below.',
canonical_alias: roomAlias_,
name: roomAlias_,
num_joined_members: 0,
@ -75,18 +77,24 @@ export const fallbackRoom = ({
* a `fallbackRoom`
*/
export async function getRoomFromAlias(
client: Client,
clientURL: string,
roomAlias: string
): Promise<Room> {
let resolvedRoomAlias: RoomAlias;
let resolvedClient: Client;
try {
resolvedRoomAlias = await getRoomIdFromAlias(client, roomAlias);
resolvedClient = await client(clientURL);
resolvedRoomAlias = await getRoomIdFromAlias(resolvedClient, roomAlias);
} catch {
return fallbackRoom({ identifier: roomAlias });
}
try {
return await searchPublicRooms(client, resolvedRoomAlias.room_id);
return await searchPublicRooms(
resolvedClient,
resolvedRoomAlias.room_id
);
} catch {
return fallbackRoom({
identifier: roomAlias,
@ -101,11 +109,12 @@ export async function getRoomFromAlias(
* a `fallbackRoom`
*/
export async function getRoomFromId(
client: Client,
clientURL: string,
roomId: string
): Promise<Room> {
try {
return await searchPublicRooms(client, roomId);
const resolvedClient = await client(clientURL);
return await searchPublicRooms(resolvedClient, roomId);
} catch {
return fallbackRoom({ identifier: roomId });
}
@ -114,9 +123,13 @@ export async function getRoomFromId(
/*
* Tries to fetch user details. If it fails it uses a `fallbackUser`
*/
export async function getUser(client: Client, userId: string): Promise<User> {
export async function getUser(
clientURL: string,
userId: string
): Promise<User> {
try {
return await getUserDetails(client, userId);
const resolvedClient = await client(clientURL);
return await getUserDetails(resolvedClient, userId);
} catch {
return fallbackUser(userId);
}
@ -127,7 +140,7 @@ export async function getUser(client: Client, userId: string): Promise<User> {
* a `fallbackRoom`
*/
export async function getRoomFromPermalink(
client: Client,
client: string,
link: Permalink
): Promise<Room> {
switch (link.roomKind) {