Add gdpr options

This commit is contained in:
Jorik Schellekens
2020-09-13 14:58:37 +01:00
parent 41b803fbe1
commit 85fab36308
18 changed files with 644 additions and 72 deletions
+26 -31
View File
@@ -21,6 +21,7 @@ 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;
@@ -31,40 +32,34 @@ const ClientSelection: React.FC<IProps> = ({ link }: IProps) => {
const [rememberSelection, setRememberSelection] = useState(false);
const options = (
<div className="advancedOptions">
<label>
<input
type="checkbox"
onChange={(): void => {
setRememberSelection(!rememberSelection);
}}
checked={rememberSelection}
/>
<StyledCheckbox
onChange={(): void => {
setRememberSelection(!rememberSelection);
}}
checked={rememberSelection}
>
Remember my selection for future invites in this browser
</label>
<label>
<input
type="checkbox"
onChange={(): void => {
clientStateDispatch({
action: ActionType.ToggleShowOnlyDeviceClients,
});
}}
checked={clientState.showOnlyDeviceClients}
/>
</StyledCheckbox>
<StyledCheckbox
onChange={(): void => {
clientStateDispatch({
action: ActionType.ToggleShowOnlyDeviceClients,
});
}}
checked={clientState.showOnlyDeviceClients}
>
Show only clients suggested for this device
</label>
<label>
<input
type="checkbox"
onChange={(): void => {
clientStateDispatch({
action: ActionType.ToggleShowExperimentalClients,
});
}}
checked={clientState.showExperimentalClients}
/>
</StyledCheckbox>
<StyledCheckbox
onChange={(): void => {
clientStateDispatch({
action: ActionType.ToggleShowExperimentalClients,
});
}}
checked={clientState.showExperimentalClients}
>
Show experimental clients
</label>
</StyledCheckbox>
</div>
);
+22
View File
@@ -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.
*/
.defaultPreview {
.avatar {
border-radius: 0;
border: 0;
}
}
+42
View File
@@ -0,0 +1,42 @@
/*
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>{link.identifier}</h1>
</div>
);
};
export default DefaultPreview;
+56
View File
@@ -0,0 +1,56 @@
/*
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';
.homeserverOptions {
display: grid;
row-gap: 20px;
background: $app-background;
text-align: left;
> * {
width: 100%;
}
.homeserverOptionsDescription {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
> p {
flex-grow: 1;
}
> img {
flex-shrink: 0;
flex-grow: 0;
background-color: $background;
height: 62px;
width: 62px;
padding: 11px;
border-radius: 100%;
}
}
form {
display: grid;
row-gap: 25px;
}
}
@@ -0,0 +1,32 @@
/*
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 HomeserverOptions from './HomeserverOptions';
export default {
title: 'HomeserverOptions',
parameters: {
design: {
type: 'figma',
url:
'https://figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=143%3A5853',
},
},
};
export const Default: React.FC = () => <HomeserverOptions />;
+114
View File
@@ -0,0 +1,114 @@
/*
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 { Formik, Form } from 'formik';
import { string } from 'zod';
import Tile from './Tile';
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 './HomeserverOptions.scss';
interface IProps {}
interface FormValues {
HSUrl: string;
}
function validateURL(values: FormValues): Partial<FormValues> {
const errors: Partial<FormValues> = {};
try {
string().url().parse(values.HSUrl);
} catch {
errors.HSUrl = 'This must be a valid url';
}
return errors;
}
const HomeserverOptions: React.FC<IProps> = () => {
const HSStateDispatcher = useContext(HSContext)[1];
const TempHSStateDispatcher = useContext(TempHSContext)[1];
const [rememberSelection, setRemeberSelection] = useState(false);
const [usePrefered, setUsePrefered] = useState(false);
const dispatcher = rememberSelection
? HSStateDispatcher
: TempHSStateDispatcher;
const hsInput = usePrefered ? (
<Formik
initialValues={{
HSUrl: '',
}}
validate={validateURL}
onSubmit={({ HSUrl }): void =>
dispatcher({ action: ActionType.SetHS, HSURL: HSUrl })
}
>
<Form>
<Input
type="text"
name="HSUrl"
placeholder="https://example.com"
/>
<Button type="submit">Set HS</Button>
</Form>
</Formik>
) : null;
return (
<Tile className="homeserverOptions">
<div className="homeserverOptionsDescription">
<div>
<p>
Let's locate a homeserver to show you more information.
</p>
</div>
<img
src={icon}
alt="Icon making it clear that connections may be made with external services"
/>
</div>
<StyledCheckbox
checked={rememberSelection}
onChange={(e): void => setRemeberSelection(e.target.checked)}
>
Remember my choice.
</StyledCheckbox>
<Button
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>
);
};
export default HomeserverOptions;
+87 -8
View File
@@ -22,6 +22,8 @@ import InviteTile from './InviteTile';
import { SafeLink, LinkKind } from '../parser/types';
import UserPreview from './UserPreview';
import EventPreview from './EventPreview';
import HomeserverOptions from './HomeserverOptions';
import DefaultPreview from './DefaultPreview';
import { clientMap } from '../clients';
import {
getRoomFromId,
@@ -30,16 +32,26 @@ import {
getUser,
} from '../utils/cypher-wrapper';
import { ClientContext } from '../contexts/ClientContext';
import HSContext, {
TempHSContext,
HSOptions,
State as HSState,
} from '../contexts/HSContext';
import Toggle from './Toggle';
interface IProps {
link: SafeLink;
}
const LOADING: JSX.Element = <>Generating invite</>;
const invite = async ({ link }: { link: SafeLink }): Promise<JSX.Element> => {
const invite = async ({
clientAddress,
link,
}: {
clientAddress: string;
link: SafeLink;
}): Promise<JSX.Element> => {
// TODO: replace with client fetch
const defaultClient = await client('https://matrix.org');
const defaultClient = await client(clientAddress);
switch (link.kind) {
case LinkKind.Alias:
return (
@@ -85,12 +97,79 @@ const invite = async ({ link }: { link: SafeLink }): Promise<JSX.Element> => {
}
};
const LinkPreview: React.FC<IProps> = ({ link }: IProps) => {
const [content, setContent] = useState(LOADING);
interface PreviewProps extends IProps {
client: string;
}
const Preview: React.FC<PreviewProps> = ({ link, client }: PreviewProps) => {
const [content, setContent] = useState(<DefaultPreview link={link} />);
// TODO: support multiple clients with vias
useEffect(() => {
(async (): Promise<void> => setContent(await invite({ link })))();
}, [link]);
(async (): Promise<void> =>
setContent(
await invite({
clientAddress: client,
link,
})
))();
}, [link, client]);
return content;
};
function selectedClient(link: SafeLink, hsOptions: HSState): string[] {
switch (hsOptions.option) {
case HSOptions.Unset:
return [];
case HSOptions.None:
return [];
case HSOptions.TrustedHSOnly:
return [hsOptions.hs];
case HSOptions.Any:
return [
'https://' + link.identifier.split(':')[1],
...link.arguments.vias,
];
}
}
const LinkPreview: React.FC<IProps> = ({ link }: IProps) => {
let content: JSX.Element;
const [showHSOptions, setShowHSOPtions] = useState(false);
const [hsOptions] = useContext(HSContext);
const [tempHSState] = useContext(TempHSContext);
if (
hsOptions.option === HSOptions.Unset &&
tempHSState.option === HSOptions.Unset
) {
content = (
<>
<DefaultPreview link={link} />
<Toggle
checked={showHSOptions}
onChange={(): void => setShowHSOPtions(!showHSOptions)}
>
Show more information
</Toggle>
</>
);
if (showHSOptions) {
content = (
<>
{content}
<HomeserverOptions />
</>
);
}
} else {
const clients =
tempHSState.option !== HSOptions.Unset
? selectedClient(link, tempHSState)
: selectedClient(link, hsOptions);
content = <Preview link={link} client={clients[0]} />;
}
const [{ clientId }] = useContext(ClientContext);
+61
View File
@@ -0,0 +1,61 @@
/*
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';
@import '../mixins';
.styledCheckbox {
display: flex;
align-items: center;
input[type='checkbox'] {
appearance: none;
margin: 0;
padding: 0;
&:checked + div {
background: $foreground;
img {
display: block;
}
}
&.focus-visible {
& + div {
@include unreal-focus;
}
}
}
.styledCheckboxWrapper {
display: flex;
margin-right: 5px;
border: 2px solid $foreground;
box-sizing: border-box;
border-radius: 4px;
height: 16px;
width: 16px;
img {
height: 100%;
width: 100%;
display: none;
}
}
}
+41
View File
@@ -0,0 +1,41 @@
/*
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.
*/
/*
* Stolen from the matrix-react-sdk
*/
import React from 'react';
import tick from '../imgs/tick.svg';
import './StyledCheckbox.scss';
interface IProps extends React.InputHTMLAttributes<HTMLInputElement> {}
const StyledCheckbox: React.FC<IProps> = ({
children,
className,
...otherProps
}: IProps) => (
<label className="styledCheckbox">
<input {...otherProps} type="checkbox" />
{/* Using the div to center the image */}
<div className="styledCheckboxWrapper">
<img src={tick} />
</div>
{children}
</label>
);
export default StyledCheckbox;
+40
View File
@@ -0,0 +1,40 @@
/*
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 '../mixins';
.toggle {
display: flex;
> input[type='checkbox'] {
// Remove the OS's representation
margin: 0;
padding: 0;
appearance: none;
&.focus-visible {
& + img {
@include unreal-focus;
}
}
&:checked {
& + img {
transform: rotate(180deg);
}
}
}
}
+35
View File
@@ -0,0 +1,35 @@
/*
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 './Toggle.scss';
interface IProps extends React.InputHTMLAttributes<Element> {
children?: React.ReactChild;
}
const Toggle: React.FC<IProps> = ({ children, ...props }: IProps) => (
<label className="toggle">
{children}
<input type="checkbox" {...props} />
<img src={chevron} />
</label>
);
export default Toggle;