Skip to content
Snippets Groups Projects
Unverified Commit 993e0ba4 authored by Bruno Windels's avatar Bruno Windels Committed by GitHub
Browse files

Merge pull request #171 from matrix-org/bwindels/addnewversion

Merge new version
parents 685950ba b18123d0
Branches
No related tags found
No related merge requests found
Showing
with 0 additions and 1501 deletions
/*
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.
*/
$app-background: #f4f4f4;
$background: #ffffff;
$foreground: #000000;
$font: #333333;
$grey: #666666;
$accent: #0098d4;
$error: #d6001c;
$link: #0098d4;
$borders: #f4f4f4;
@mixin unreal-focus {
outline-width: 2px;
outline-style: solid;
outline-color: Highlight;
/* WebKit gets its native focus styles. */
@media (-webkit-min-device-pixel-ratio: 0) {
outline-color: -webkit-focus-ring-color;
outline-style: auto;
}
}
/*
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 {
LinkedClient,
Maturity,
ClientKind,
ClientId,
Platform,
AppleStoreLink,
PlayStoreLink,
FDroidLink,
} from './types';
import { LinkKind } from '../parser/types';
import logo from '../imgs/element.svg';
export const Element: LinkedClient = {
kind: ClientKind.LINKED_CLIENT,
name: 'Element',
author: 'Element',
logo: logo,
homepage: 'https://element.io',
maturity: Maturity.STABLE,
description: 'Fully-featured Matrix client',
platforms: [Platform.Desktop, Platform.Android, Platform.iOS],
experimental: false,
clientId: ClientId.Element,
toUrl: (link) => {
const params = link.arguments.originalParams.toString();
const prefixedParams = params ? `?${params}` : '';
switch (link.kind) {
case LinkKind.Alias:
case LinkKind.RoomId:
return new URL(
`https://app.element.io/#/room/${link.identifier}${prefixedParams}`
);
case LinkKind.UserId:
return new URL(
`https://app.element.io/#/user/${link.identifier}${prefixedParams}`
);
case LinkKind.Permalink:
return new URL(
`https://app.element.io/#/room/${link.identifier}${prefixedParams}`
);
case LinkKind.GroupId:
return new URL(
`https://app.element.io/#/group/${link.identifier}${prefixedParams}`
);
}
},
linkSupport: () => true,
installLinks: [
new AppleStoreLink('vector', 'id1083446067'),
new PlayStoreLink('im.vector.app'),
new FDroidLink('im.vector.app'),
],
};
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',
platforms: [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}`
);
}
},
linkSupport: () => true,
installLinks: [],
};
/*
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/fractal.png';
const Fractal: TextClient = {
kind: ClientKind.TEXT_CLIENT,
name: 'Fractal',
logo: logo,
author: 'Daniel Garcia Moreno',
homepage: 'https://github.com/poljar/weechat-matrix',
maturity: Maturity.BETA,
experimental: false,
platforms: [Platform.Desktop],
clientId: ClientId.Fractal,
toInviteString: (link) => {
switch (link.kind) {
case LinkKind.Alias:
case LinkKind.RoomId:
case LinkKind.UserId:
return (
<span>
Click the '+' button in the top right and paste the
identifier
</span>
);
default:
return <span>Fractal doesn't support this kind of link</span>;
}
},
copyString: (link) => {
switch (link.kind) {
case LinkKind.Alias:
case LinkKind.RoomId:
case LinkKind.UserId:
return `${link.identifier}`;
default:
return '';
}
},
linkSupport: (link) => {
switch (link.kind) {
case LinkKind.Alias:
case LinkKind.RoomId:
case LinkKind.UserId:
return true;
default:
return false;
}
},
description: 'Fractal is a Matrix Client written in Rust',
installLinks: [],
};
export default Fractal;
/*
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/nheko.svg';
const Nheko: TextClient = {
kind: ClientKind.TEXT_CLIENT,
name: 'Nheko',
logo: logo,
author: 'mujx, red_sky, deepbluev7, Konstantinos Sideris',
homepage: 'https://github.com/Nheko-Reborn/nheko',
maturity: Maturity.BETA,
experimental: false,
platforms: [Platform.Desktop],
clientId: ClientId.Nheko,
toInviteString: (link) => {
switch (link.kind) {
case LinkKind.Alias:
case LinkKind.RoomId:
return (
<span>
Type{' '}
<code>
/join{' '}
<b className="matrixIdentifier">
{link.identifier}
</b>
</code>
</span>
);
case LinkKind.UserId:
return (
<span>
Type{' '}
<code>
/invite{' '}
<b className="matrixIdentifier">
{link.identifier}
</b>
</code>
</span>
);
default:
return <span>Nheko doesn't support this kind of link</span>;
}
},
copyString: (link) => {
switch (link.kind) {
case LinkKind.Alias:
case LinkKind.RoomId:
return `/join ${link.identifier}`;
case LinkKind.UserId:
return `/invite ${link.identifier}`;
default:
return '';
}
},
linkSupport: (link) => {
switch (link.kind) {
case LinkKind.Alias:
case LinkKind.RoomId:
case LinkKind.UserId:
return true;
default:
return false;
}
},
description:
'A native desktop app for Matrix that feels more like a mainstream chat app.',
installLinks: [],
};
export default Nheko;
/*
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,
platforms: [Platform.Desktop],
clientId: ClientId.WeeChat,
toInviteString: (link) => {
switch (link.kind) {
case LinkKind.Alias:
case LinkKind.RoomId:
return (
<span>
Type{' '}
<code>
/join{' '}
<b className="matrixIdentifier">
{link.identifier}
</b>
</code>
</span>
);
case LinkKind.UserId:
return (
<span>
Type{' '}
<code>
/invite{' '}
<b className="matrixIdentifier">
{link.identifier}
</b>
</code>
</span>
);
default:
return <span>Weechat doesn't support this kind of link</span>;
}
},
copyString: (link) => {
switch (link.kind) {
case LinkKind.Alias:
case LinkKind.RoomId:
return `/join ${link.identifier}`;
case LinkKind.UserId:
return `/invite ${link.identifier}`;
default:
return '';
}
},
linkSupport: (link) => {
switch (link.kind) {
case LinkKind.Alias:
case LinkKind.RoomId:
case LinkKind.UserId:
return true;
default:
return false;
}
},
description: 'Command-line Matrix interface using Weechat',
installLinks: [],
};
export default Weechat;
/*
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 { Client } from './types';
import { Element, ElementDevelop } from './Element';
import Weechat from './Weechat';
import Nheko from './Nheko';
import Fractal from './Fractal';
/*
* All the supported clients of matrix.to
*/
const clients: Client[] = [Element, Weechat, Nheko, Fractal, 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,
[Nheko.clientId]: Nheko,
[Fractal.clientId]: Fractal,
};
/*
* All the supported clients of matrix.to
*/
export default clients;
/*
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 { SafeLink } from '../parser/types';
/*
* A collection of descriptive tags that can be added to
* a clients description.
*/
export enum Platform {
iOS = 'iOS',
Android = 'ANDROID',
Desktop = 'DESKTOP',
}
/*
* A collection of states used for describing a clients maturity.
*/
export enum Maturity {
ALPHA = 'ALPHA',
LATE_ALPHA = 'LATE ALPHA',
BETA = 'BETA',
LATE_BETA = 'LATE_BETA',
STABLE = 'STABLE',
}
/*
* Used for constructing the discriminated union of all client types.
*/
export enum ClientKind {
LINKED_CLIENT = 'LINKED_CLIENT',
TEXT_CLIENT = 'TEXT_CLIENT',
}
export enum ClientId {
Element = 'element.io',
ElementDevelop = 'develop.element.io',
WeeChat = 'weechat',
Nheko = 'nheko',
Fractal = 'fractal',
}
/**
* Define a native distribution channel for a client.
* E.g App store for apple, PlayStore or F-Droid for Android
*/
export interface InstallLink {
createInstallURL(deepLink: SafeLink) : string;
// in AppleStoreLink, we can set the cookie here for deeplinking
// onInstallChosen(deepLink: SafeLink);
platform: Platform;
channelId: string;
description: string;
}
export class AppleStoreLink implements InstallLink {
constructor(private org: string, private appId: string) {}
createInstallURL(deepLink: SafeLink) : string {
return `https://apps.apple.com/app/${encodeURIComponent(this.org)}/${encodeURIComponent(this.appId)}`;
}
get platform() : Platform {
return Platform.iOS;
}
get channelId(): string {
return "apple-app-store";
}
get description() {
return "Download on the App Store";
}
}
export class PlayStoreLink implements InstallLink {
constructor(private appId: string) {}
createInstallURL(deepLink: SafeLink) : string {
return `https://play.google.com/store/apps/details?id=${encodeURIComponent(this.appId)}&referrer=${encodeURIComponent(deepLink.originalLink)}`;
}
get platform() : Platform {
return Platform.Android;
}
get channelId(): string {
return "play-store";
}
get description() {
return "Get it on Google Play";
}
}
export class FDroidLink implements InstallLink {
constructor(private appId: string) {}
createInstallURL(deepLink: SafeLink) : string {
return `https://f-droid.org/packages/${encodeURIComponent(this.appId)}`;
}
get platform() : Platform {
return Platform.Android;
}
get channelId(): string {
return "fdroid";
}
get description() {
return "Get it on F-Droid";
}
}
/*
* The descriptive details of a client
*/
export interface ClientDescription {
name: string;
author: string;
homepage: string;
logo: string;
description: string;
platforms: Platform[];
maturity: Maturity;
clientId: ClientId;
experimental: boolean;
linkSupport: (link: SafeLink) => boolean;
installLinks: InstallLink[];
}
/*
* A client which can be opened using a link with the matrix resource.
*/
export interface LinkedClient extends ClientDescription {
kind: ClientKind.LINKED_CLIENT;
toUrl(parsedLink: SafeLink): URL;
}
/*
* A client which provides isntructions for how to access the descired
* resource.
*/
export interface TextClient extends ClientDescription {
kind: ClientKind.TEXT_CLIENT;
toInviteString(parsedLink: SafeLink): JSX.Element;
copyString(parsedLink: SafeLink): string;
}
/*
* A description for a client as well as a method for converting matrix.to
* links to the client's specific representation.
*/
export type Client = LinkedClient | TextClient;
/*
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.
*/
// disable camelcase check because our object keys come
// from the matrix spec
/* eslint-disable @typescript-eslint/camelcase */
import React from 'react';
import { UserAvatar } from './Avatar';
export default {
title: 'Avatar',
parameters: {
design: {
type: 'figma',
url:
'https://www.figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=143%3A5853',
},
},
};
export const Default: React.FC<{}> = () => (
<UserAvatar
user={{
avatar_url: 'mxc://matrix.org/EqMZYbAYhREvHXvYFyfxOlkf',
displayname: 'Jorik Schellekens',
}}
userId="@jorik:matrix.org"
/>
);
/*
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, { useEffect, useState } from 'react';
import classNames from 'classnames';
import { Group, Room, User } from '../matrix-cypher';
import useHSs from '../utils/getHS';
import { getThumbnailURI } from '../utils/cypher-wrapper';
import logo from '../imgs/chat-icon.svg';
import './Avatar.scss';
const AVATAR_SIZE = 96;
interface IProps {
className?: string;
avatarUrl: string;
label: string;
}
const Avatar: React.FC<IProps> = ({ className, avatarUrl, label }: IProps) => {
const [src, setSrc] = useState(avatarUrl);
useEffect(() => {
setSrc(avatarUrl ? avatarUrl : logo);
}, [avatarUrl]);
const _className = classNames('avatar', className, {
avatarNoCrop: src === logo,
});
return (
<img
src={src}
onError={(): void => setSrc(logo)}
alt={label}
className={_className}
/>
);
};
interface IPropsUserAvatar {
user: User;
userId: string;
}
export const UserAvatar: React.FC<IPropsUserAvatar> = ({
user,
userId,
}: IPropsUserAvatar) => {
const [hs] = useHSs({ identifier: userId });
return (
<Avatar
avatarUrl={getThumbnailURI(
hs,
AVATAR_SIZE,
AVATAR_SIZE,
user.avatar_url
)}
label={user.displayname ? user.displayname : userId}
/>
);
};
interface IPropsRoomAvatar {
room: Room;
}
export const RoomAvatar: React.FC<IPropsRoomAvatar> = ({
room,
}: IPropsRoomAvatar) => {
const [hs] = useHSs({ identifier: room.room_id });
return (
<Avatar
avatarUrl={getThumbnailURI(
hs,
AVATAR_SIZE,
AVATAR_SIZE,
room.avatar_url
)}
label={room.name || room.room_id}
/>
);
};
interface IPropsGroupAvatar {
group: Group;
groupId: string;
}
export const GroupAvatar: React.FC<IPropsGroupAvatar> = ({
group,
groupId,
}: IPropsGroupAvatar) => {
const [hs] = useHSs({ identifier: groupId });
return (
<Avatar
avatarUrl={getThumbnailURI(
hs,
AVATAR_SIZE,
AVATAR_SIZE,
group.avatar_url
)}
label={group.name}
/>
);
};
export default Avatar;
/*
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 { action } from '@storybook/addon-actions';
import { text } from '@storybook/addon-knobs';
import Button from './Button';
export default { title: 'Button' };
export const WithText: React.FC = () => (
<Button onClick={action('clicked')}>
{text('label', 'Hello Story Book')}
</Button>
);
export const Secondary: React.FC = () => (
<Button secondary>Secondary button</Button>
);
/*
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 './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;
}
/**
* Like a normal button except it will flash content when clicked.
*/
const Button: React.FC<
IProps & React.RefAttributes<HTMLButtonElement>
> = React.forwardRef(
(
{
onClick,
children,
flashChildren,
className,
secondary,
icon,
flashIcon,
...props
}: IProps,
ref: React.Ref<HTMLButtonElement>
) => {
const [wasClicked, setWasClicked] = React.useState(false);
const wrappedOnClick: React.MouseEventHandler = (e) => {
if (onClick) {
onClick(e);
}
setWasClicked(true);
window.setTimeout(() => {
setWasClicked(false);
}, 1000);
};
const content = wasClicked && flashChildren ? flashChildren : children;
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}
onClick={wrappedOnClick}
ref={ref}
{...props}
>
{buttonIcon}
{content}
</button>
);
}
);
export default Button;
/*
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;
}
/*
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<IProps> = ({ 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;
}
for (const platform of client.platforms) {
switch (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;
}
if (!client.linkSupport(link)) {
showClient = false;
}
return showClient;
};
const clientLi = (client: Client): JSX.Element => (
<li
key={client.clientId}
onClick={(): void =>
rememberSelection
? clientDispatcher({
action: ActionType.SetClient,
clientId: client.clientId,
})
: undefined
}
>
<ClientTile client={client} link={link} />
</li>
);
return (
<ul className="clientList">
{Clients.filter(showClient).map(clientLi)}
</ul>
);
};
export default ClientList;
/*
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';
import StyledCheckbox from './StyledCheckbox';
interface IProps {
link: SafeLink;
}
const ClientSelection: React.FC<IProps> = ({ link }: IProps) => {
const [clientState, clientStateDispatch] = useContext(ClientContext);
const [rememberSelection, setRememberSelection] = useState(false);
const options = (
<div className="advancedOptions">
<StyledCheckbox
onChange={(): void => {
setRememberSelection(!rememberSelection);
}}
checked={rememberSelection}
>
Remember for future invites in this browser
</StyledCheckbox>
<StyledCheckbox
onChange={(): void => {
clientStateDispatch({
action: ActionType.ToggleShowOnlyDeviceClients,
});
}}
checked={clientState.showOnlyDeviceClients}
>
Show only clients suggested for this device
</StyledCheckbox>
<StyledCheckbox
onChange={(): void => {
clientStateDispatch({
action: ActionType.ToggleShowExperimentalClients,
});
}}
checked={clientState.showExperimentalClients}
>
Show experimental clients
</StyledCheckbox>
</div>
);
const clearSelection =
clientState.clientId !== null ? (
<Button
onClick={(): void =>
clientStateDispatch({
action: ActionType.ClearClient,
})
}
>
Clear my default client
</Button>
) : null;
return (
<div className="advanced">
{options}
<ClientList link={link} rememberSelection={rememberSelection} />
{clearSelection}
</div>
);
};
export default ClientSelection;
/*
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 classNames from 'classnames';
import { UAContext } from '@quentin-sommer/react-useragent';
import { Client, ClientKind, Platform } from '../clients/types';
import { SafeLink } from '../parser/types';
import Tile from './Tile';
import Button from './Button';
import appStoreBadge from '../imgs/app-store-us-alt.svg';
import playStoreBadge from '../imgs/google-play-us.svg';
import fdroidBadge from '../imgs/fdroid-badge.png';
import './ClientTile.scss';
interface IProps {
client: Client;
link: SafeLink;
}
interface IInstallBadgeImages {
[index: string]: string;
}
const installBadgeImages : IInstallBadgeImages = {
"fdroid": fdroidBadge,
"apple-app-store": appStoreBadge,
"play-store": playStoreBadge
};
const ClientTile: React.FC<IProps> = ({ client, link }: IProps) => {
const inviteLine =
client.kind === ClientKind.TEXT_CLIENT ? (
<p>{client.toInviteString(link)}</p>
) : null;
const { uaResults } = useContext(UAContext);
const className = classNames('clientTile', {
clientTileLink: client.kind === ClientKind.LINKED_CLIENT,
});
let inviteButton: JSX.Element = <></>;
const matchingInstallLinks = client.installLinks.filter((installLink) => {
if ((uaResults as any).ios) {
return installLink.platform === Platform.iOS;
} else if ((uaResults as any).android) {
return installLink.platform === Platform.Android;
} else {
return false;
}
});
const hasNativeClient = matchingInstallLinks.length > 0;
let installButtons = undefined;
if (matchingInstallLinks.length) {
installButtons = <p>{matchingInstallLinks.map((installLink) => {
return <a
rel="noopener noreferrer"
aria-label={installLink.description}
key={installLink.channelId}
href={installLink.createInstallURL(link)}
className="installLink"
target="_blank">
<img src={installBadgeImages[installLink.channelId]} alt={installLink.description} />
</a>;
})}</p>;
}
if (client.kind === ClientKind.LINKED_CLIENT) {
inviteButton = <Button>Accept invite</Button>;
} else {
const copyString = client.copyString(link);
if (copyString !== '') {
inviteButton = (
<Button
onClick={() => navigator.clipboard?.writeText(copyString)}
flashChildren="Invite copied"
>
Copy invite
</Button>
);
}
}
let clientTile = (
<Tile className={className}>
<img src={client.logo} alt={client.name + ' logo'} />
<div>
<h1>{client.name}</h1>
<p>{client.description}</p>
{installButtons}
{inviteLine}
{inviteButton}
</div>
</Tile>
);
if (client.kind === ClientKind.LINKED_CLIENT) {
if (!hasNativeClient) {
clientTile = (
<a href={client.toUrl(link).toString()}>{clientTile}</a>
);
}
}
return clientTile;
};
export default ClientTile;
/*
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 CreateLinkTile from './CreateLinkTile';
export default {
title: 'CreateLinkTile',
parameters: {
design: {
type: 'figma',
url:
'https://figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=59%3A1',
},
},
};
export const Default: React.FC = () => <CreateLinkTile />;
/*
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, { useEffect, useRef } from 'react';
import { Formik, Form } from 'formik';
import Tile from './Tile';
import Button from './Button';
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 {
setLink: React.Dispatch<React.SetStateAction<string>>;
}
interface FormValues {
identifier: string;
}
// Hacky use of types here
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 identifier doesn't look right. Double check the details.";
}
return errors;
}
const LinkNotCreatedTile: React.FC<ILinkNotCreatedTileProps> = (
props: ILinkNotCreatedTileProps
) => {
return (
<Tile className="createLinkTile">
<h1>
Create shareable links to Matrix rooms, users or messages
without being tied to any app
</h1>
<Formik
initialValues={{
identifier: '',
}}
validate={validate}
onSubmit={(values): void => {
props.setLink(
document.location.protocol +
'//' +
document.location.host +
'/#/' +
values.identifier
);
}}
>
{(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>
);
};
interface ILinkCreatedTileProps {
link: string;
setLink: React.Dispatch<React.SetStateAction<string>>;
}
const LinkCreatedTile: React.FC<ILinkCreatedTileProps> = (props) => {
const buttonRef = useRef<HTMLButtonElement>(null);
// Focus button on render
useEffect((): void => {
if (buttonRef && buttonRef.current) {
buttonRef.current.focus();
}
});
return (
<Tile className="createLinkTile">
<button
className="createLinkReset"
onClick={(): void => props.setLink('')}
>
<div>New link</div>
<img src={refreshIcon} alt="Go back to matrix.to home page" />
</button>
<a className="matrixIdentifier" href={props.link}>
{props.link}
</a>
<Button
flashChildren={'Copied'}
icon={copyIcon}
flashIcon={tickIcon}
onClick={(): void => {
navigator.clipboard?.writeText(props.link);
}}
ref={buttonRef}
>
Copy Link
</Button>
</Tile>
);
};
const CreateLinkTile: React.FC = () => {
const [link, setLink] = React.useState('');
if (!link) {
return <LinkNotCreatedTile setLink={setLink} />;
} else {
return <LinkCreatedTile link={link} setLink={setLink} />;
}
};
export default CreateLinkTile;
/*
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 { SafeLink } from '../parser/types';
import Avatar from './Avatar';
import './DefaultPreview.scss';
import genericRoomPreview from '../imgs/chat-icon.svg';
interface IProps {
link: SafeLink;
}
const DefaultPreview: React.FC<IProps> = ({ link }: IProps) => {
return (
<div className="defaultPreview">
<Avatar
avatarUrl={genericRoomPreview}
label={`Generic icon representing ${link.identifier}`}
/>
<h1 className="matrixIdentifier">{link.identifier}</h1>
</div>
);
};
export default DefaultPreview;
/*
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 chevron from '../imgs/chevron-down.svg';
import './Details.scss';
interface IProps extends React.InputHTMLAttributes<Element> {
children?: React.ReactNode;
}
const Details: React.FC<IProps> = ({ children, ...props }: IProps) => (
<label className="details">
{children}
<input type="checkbox" {...props} />
<img src={chevron} alt="" />
</label>
);
export default Details;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment