diff --git a/package.json b/package.json
index 586b537..28b1609 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,6 @@
"@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",
diff --git a/src/App.scss b/src/App.scss
index 0f0855e..7aff62a 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -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 {
diff --git a/src/App.tsx b/src/App.tsx
index 5f60456..376f7b6 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -14,13 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
+import React, { useState, useEffect } 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 Footer from './components/Footer';
import './App.scss';
@@ -32,13 +33,25 @@ const App: React.FC = () => {
let page = (
<>
-
>
);
- if (location.hash) {
- if (location.hash.startsWith('#/')) {
- page = ;
+ const [hash, setHash] = useState(location.hash);
+
+ console.log(hash);
+ useEffect(() => {
+ // Some hacky uri decoding
+ if (location.href.split('/').length > 4) {
+ location.href = decodeURIComponent(location.href);
+ }
+
+ window.onhashchange = () => setHash(location.hash);
+ console.log('why');
+ }, []);
+
+ if (hash) {
+ if (hash.startsWith('#/')) {
+ page = ;
} else {
page = (
@@ -50,12 +63,18 @@ const App: React.FC = () => {
}
return (
-
-
- {page}
-
-
-
+
+
+
+ {page}
+
+
+
+
+
+
+
+
);
};
diff --git a/src/clients/Element.io.ts b/src/clients/Element.io.ts
index 6d4f6c1..8a346ae 100644
--- a/src/clients/Element.io.ts
+++ b/src/clients/Element.io.ts
@@ -56,6 +56,7 @@ const Element: LinkedClient = {
);
}
},
+ linkSupport: () => true,
};
export const ElementDevelop: LinkedClient = {
@@ -90,5 +91,6 @@ export const ElementDevelop: LinkedClient = {
);
}
},
+ linkSupport: () => true,
};
export default Element;
diff --git a/src/clients/Fractal.tsx b/src/clients/Fractal.tsx
new file mode 100644
index 0000000..a0caaac
--- /dev/null
+++ b/src/clients/Fractal.tsx
@@ -0,0 +1,69 @@
+/*
+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,
+ platform: Platform.Desktop,
+ clientId: ClientId.Fractal,
+ toInviteString: (link) => {
+ switch (link.kind) {
+ case LinkKind.Alias:
+ case LinkKind.RoomId:
+ case LinkKind.UserId:
+ return Click the '+' button in the top right;
+ default:
+ return Weechat doesn't support this kind of link;
+ }
+ },
+ 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: 'Command-line Matrix interface using Weechat',
+};
+
+export default Fractal;
diff --git a/src/clients/Nheko.tsx b/src/clients/Nheko.tsx
new file mode 100644
index 0000000..a0954d1
--- /dev/null
+++ b/src/clients/Nheko.tsx
@@ -0,0 +1,85 @@
+/*
+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,
+ platform: Platform.Desktop,
+ clientId: ClientId.Nheko,
+ 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 Nheko 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 '';
+ }
+ },
+ 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.',
+};
+
+export default Nheko;
diff --git a/src/clients/Weechat.tsx b/src/clients/Weechat.tsx
index ff6b528..cd15769 100644
--- a/src/clients/Weechat.tsx
+++ b/src/clients/Weechat.tsx
@@ -68,6 +68,17 @@ const Weechat: TextClient = {
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',
};
diff --git a/src/clients/index.ts b/src/clients/index.ts
index ba39676..c3686bb 100644
--- a/src/clients/index.ts
+++ b/src/clients/index.ts
@@ -18,11 +18,13 @@ import { Client } from './types';
import Element, { ElementDevelop } from './Element.io';
import Weechat from './Weechat';
+import Nheko from './Nheko';
+import Fractal from './Fractal';
/*
* All the supported clients of matrix.to
*/
-const clients: Client[] = [Element, Weechat, ElementDevelop];
+const clients: Client[] = [Element, Weechat, Nheko, Fractal, ElementDevelop];
/*
* A map from sharer string to client.
@@ -33,6 +35,8 @@ export const clientMap: { [key: string]: Client } = {
[Element.clientId]: Element,
[Weechat.clientId]: Weechat,
[ElementDevelop.clientId]: ElementDevelop,
+ [Nheko.clientId]: Nheko,
+ [Fractal.clientId]: Fractal,
};
/*
diff --git a/src/clients/types.ts b/src/clients/types.ts
index cdaed27..5a93f15 100644
--- a/src/clients/types.ts
+++ b/src/clients/types.ts
@@ -49,6 +49,8 @@ export enum ClientId {
Element = 'element.io',
ElementDevelop = 'develop.element.io',
WeeChat = 'weechat',
+ Nheko = 'nheko',
+ Fractal = 'fractal',
}
/*
@@ -64,6 +66,7 @@ export interface ClientDescription {
maturity: Maturity;
clientId: ClientId;
experimental: boolean;
+ linkSupport: (link: SafeLink) => boolean;
}
/*
diff --git a/src/components/Avatar.scss b/src/components/Avatar.scss
index 7895a82..794064a 100644
--- a/src/components/Avatar.scss
+++ b/src/components/Avatar.scss
@@ -14,11 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-@import "../color-scheme";
+@import '../color-scheme';
.avatar {
border-radius: 100%;
border: 1px solid $borders;
- height: 50px;
- width: 50px;
+ height: 60px;
+ width: 60px;
+}
+
+.avatarNoCrop {
+ border-radius: 0;
}
diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx
index 003d004..f150c63 100644
--- a/src/components/Avatar.tsx
+++ b/src/components/Avatar.tsx
@@ -16,10 +16,10 @@ limitations under the License.
import React, { useEffect, useState } from 'react';
import classNames from 'classnames';
-import { Room, User } from 'matrix-cypher';
+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';
diff --git a/src/components/Button.scss b/src/components/Button.scss
index c34c521..c1a6d70 100644
--- a/src/components/Button.scss
+++ b/src/components/Button.scss
@@ -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 {
diff --git a/src/components/Button.stories.tsx b/src/components/Button.stories.tsx
index 1f5d2e5..25cfee8 100644
--- a/src/components/Button.stories.tsx
+++ b/src/components/Button.stories.tsx
@@ -27,3 +27,7 @@ export const WithText: React.FC = () => (
{text('label', 'Hello Story Book')}
);
+
+export const Secondary: React.FC = () => (
+
+);
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index a347254..fd031f2 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -22,6 +22,9 @@ import './Button.scss';
interface IProps extends React.ButtonHTMLAttributes {
// 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
> = React.forwardRef(
(
- { onClick, children, flashChildren, className, ...props }: IProps,
+ {
+ onClick,
+ children,
+ flashChildren,
+ className,
+ secondary,
+ icon,
+ flashIcon,
+ ...props
+ }: IProps,
ref: React.Ref
) => {
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 ? (
+
+ ) : null;
+
return (
);
diff --git a/src/components/ClientList.tsx b/src/components/ClientList.tsx
index fbef28f..4d23d37 100644
--- a/src/components/ClientList.tsx
+++ b/src/components/ClientList.tsx
@@ -63,6 +63,10 @@ const ClientList: React.FC = ({ link, rememberSelection }: IProps) => {
showClient = false;
}
+ if (!client.linkSupport(link)) {
+ showClient = false;
+ }
+
return showClient;
};
diff --git a/src/components/ClientSelection.tsx b/src/components/ClientSelection.tsx
index bb58642..9989b29 100644
--- a/src/components/ClientSelection.tsx
+++ b/src/components/ClientSelection.tsx
@@ -38,7 +38,7 @@ const ClientSelection: React.FC = ({ link }: IProps) => {
}}
checked={rememberSelection}
>
- Remember my selection for future invites in this browser
+ Remember choice for future invites in this browser
{
@@ -79,7 +79,6 @@ const ClientSelection: React.FC = ({ link }: IProps) => {
return (
{options}
-
Clients you can accept this invite with
{clearSelection}
diff --git a/src/components/ClientTile.scss b/src/components/ClientTile.scss
index 0b8f091..996c56f 100644
--- a/src/components/ClientTile.scss
+++ b/src/components/ClientTile.scss
@@ -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 {
@@ -42,16 +45,17 @@ limitations under the License.
}
p {
- margin-right: 20px;
+ margin-right: 8px;
text-align: left;
}
.button {
- margin: 5px;
+ height: 40px;
+ width: 130px;
+ margin-top: 16px;
}
}
- border: 1px solid $borders;
border-radius: 8px;
padding: 15px;
@@ -59,8 +63,8 @@ limitations under the License.
// For the chevron
position: relative;
- &::hover {
- background-color: $grey;
+ &:hover {
+ background-color: $app-background;
}
}
@@ -68,12 +72,4 @@ limitations under the License.
position: relative;
width: 100%;
-
- &::after {
- // TODO: add chevron top right
- position: absolute;
- right: 10px;
- top: 5px;
- content: '>';
- }
}
diff --git a/src/components/CreateLinkTile.scss b/src/components/CreateLinkTile.scss
index ada3856..5a300ce 100644
--- a/src/components/CreateLinkTile.scss
+++ b/src/components/CreateLinkTile.scss
@@ -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;
+ }
+ }
+ }
}
diff --git a/src/components/CreateLinkTile.tsx b/src/components/CreateLinkTile.tsx
index 3553289..12ffd54 100644
--- a/src/components/CreateLinkTile.tsx
+++ b/src/components/CreateLinkTile.tsx
@@ -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 {
const errors: Partial = {};
+ 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;
@@ -71,15 +78,28 @@ const LinkNotCreatedTile: React.FC = (
values.identifier
);
}}
+ validateOnChange={false}
>
-
+ {(formik): JSX.Element => (
+
+ )}
);
@@ -102,14 +122,20 @@ const LinkCreatedTile: React.FC = (props) => {
return (
- props.setLink('')}>
- Create another link
-
+
{props.link}