From 565605934246ff4321aec51e19183c1bd2b24e2a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 25 Nov 2020 18:18:20 +0100 Subject: [PATCH] server selection WIP --- src/components/HomeserverOptions.scss | 14 ++- src/components/HomeserverOptions.tsx | 174 ++++++++++++++------------ src/components/Input.tsx | 1 + src/contexts/HSContext.ts | 16 ++- src/utils/getHS.ts | 30 +++-- 5 files changed, 141 insertions(+), 94 deletions(-) diff --git a/src/components/HomeserverOptions.scss b/src/components/HomeserverOptions.scss index c4d0533..8d39c39 100644 --- a/src/components/HomeserverOptions.scss +++ b/src/components/HomeserverOptions.scss @@ -60,8 +60,16 @@ limitations under the License. } } - form { - display: grid; - row-gap: 25px; + + .serverChoices { + display: flex; + flex-direction: column; + } + + .serverChoices label { + display: flex; + gap: 10px; + margin: 8px 0; + align-items: center; } } diff --git a/src/components/HomeserverOptions.tsx b/src/components/HomeserverOptions.tsx index 452a2e3..3a61099 100644 --- a/src/components/HomeserverOptions.tsx +++ b/src/components/HomeserverOptions.tsx @@ -15,16 +15,18 @@ limitations under the License. */ import React, { useContext, useState } from 'react'; -import { Formik, Form } from 'formik'; +import { Formik, Form, Field } 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 TextButton from './TextButton'; import Input from './Input'; import StyledCheckbox from './StyledCheckbox'; import { SafeLink } from '../parser/types'; +import { getHSFromIdentifier } from "../utils/getHS"; import './HomeserverOptions.scss'; @@ -34,103 +36,119 @@ interface IProps { interface FormValues { HSUrl: string; + HSOtherUrl: string; +} + +interface SubmitEvent extends Event { + submitter: HTMLFormElement; } function validateURL(values: FormValues): Partial { const errors: Partial = {}; - try { - string().url().parse(values.HSUrl); - } catch { - errors.HSUrl = - 'This must be a valid homeserver URL, starting with https://'; + if (values.HSUrl === "other") { + try { + string().url().parse(hsToURL(values.HSOtherUrl)); + } catch { + errors.HSOtherUrl = + 'This must be a valid homeserver URL'; + } } return errors; } +function hsToURL(hs: string): string { + if (!hs.startsWith("http://") && !hs.startsWith("https://")) { + return "https://" + hs; + } + return hs; +} + +function getChosenHS(values: FormValues) { + return values.HSUrl === "other" ? values.HSOtherUrl : values.HSUrl; +} + +function getHSDomain(hs: string) { + try { + // TODO: take port as well + return new URL(hsToURL(hs)).hostname; + } catch (err) { + return; + } +} + const HomeserverOptions: React.FC = ({ link }: IProps) => { const HSStateDispatcher = useContext(HSContext)[1]; const TempHSStateDispatcher = useContext(TempHSContext)[1]; - const [rememberSelection, setRemeberSelection] = useState(false); + const [askEveryTime, setAskEveryTime] = useState(false); + const [showHSPicker, setShowHSPicker] = useState(false); // Select which disaptcher to use based on whether we're writing // the choice to localstorage - const dispatcher = rememberSelection - ? HSStateDispatcher - : TempHSStateDispatcher; + const dispatcher = askEveryTime + ? TempHSStateDispatcher + : HSStateDispatcher; - const hsInput = ( - - dispatcher({ action: ActionType.SetHS, HSURL: HSUrl }) - } - > - {({ values, errors }): JSX.Element => ( -
- - {values.HSUrl && !errors.HSUrl ? ( - - ) : null} -
- )} -
- ); + let topSection; + const identifierHS = getHSFromIdentifier(link.identifier) || ""; + const homeservers = [identifierHS, ... link.arguments.vias]; + + const chosenHS = "home.server"; + const continueWithout = dispatcher({ action: ActionType.SetNone})}>continue without a preview; + let instructions; + if (showHSPicker) { + instructions =

View this link using {chosenHS} to preview content or {continueWithout}.

+ } else { + const useAnotherServer = setShowHSPicker(true)}>use another server; + instructions =

View this link using {chosenHS} to preview content, or you can {useAnotherServer} or {continueWithout}.

+ } return ( -
- View this link using matrix.org to preview content, or you can - use another server or continue without a preview. -
-
- Ask every time - -
-
-
-

- About  - - {link.identifier} - -

-

- A homeserver will show you metadata about the link, like - a description. Homeservers will be able to relate your - IP to things you've opened invites for in matrix.to. -

-
- Icon making it clear that connections may be made with external services -
- setRemeberSelection(e.target.checked)} - > - Remember my choice - - - {hsInput} + validate={validateURL} + onSubmit={(values): void => { + dispatcher({ action: ActionType.SetHS, HSURL: getHSDomain(getChosenHS(values)) || "" }); + }}> + {({ values, errors }): JSX.Element => { + let hsOptions; + if (showHSPicker) { + const radios = homeservers.map(hs => { + return ; + }); + const otherHSField = values.HSUrl === "other" ? + : undefined; + hsOptions =
+ {radios} + +
; + } + return
+ {hsOptions} +
+ setAskEveryTime(e.target.checked)} + >Ask every time + +
+
; + }} + ;
); }; diff --git a/src/components/Input.tsx b/src/components/Input.tsx index 21ce342..a302754 100644 --- a/src/components/Input.tsx +++ b/src/components/Input.tsx @@ -23,6 +23,7 @@ import './Input.scss'; interface IProps extends React.InputHTMLAttributes { name: string; type: string; + required?: boolean; muted?: boolean; } diff --git a/src/contexts/HSContext.ts b/src/contexts/HSContext.ts index 4105af6..f2c9ab7 100644 --- a/src/contexts/HSContext.ts +++ b/src/contexts/HSContext.ts @@ -25,6 +25,8 @@ export enum HSOptions { Unset = 'UNSET', // Matrix.to should only contact a single provided homeserver TrustedHSOnly = 'TRUSTED_CLIENT_ONLY', + // Matrix.to may not contact any homeserver + None = 'NONE', // Matrix.to may contact any homeserver it requires Any = 'ANY', } @@ -33,6 +35,9 @@ const STATE_SCHEMA = union([ object({ option: literal(HSOptions.Unset), }), + object({ + option: literal(HSOptions.None), + }), object({ option: literal(HSOptions.Any), }), @@ -48,6 +53,7 @@ export type State = TypeOf; export enum ActionType { SetHS = 'SET_HS', SetAny = 'SET_ANY', + SetNone = 'SET_NONE', Clear = 'CLEAR', } @@ -56,6 +62,10 @@ export interface SetHS { HSURL: string; } +export interface SetNone { + action: ActionType.SetNone; +} + export interface SetAny { action: ActionType.SetAny; } @@ -64,7 +74,7 @@ export interface Clear { action: ActionType.Clear; } -export type Action = SetHS | SetAny | Clear; +export type Action = SetHS | SetAny | SetNone | Clear; export const INITIAL_STATE: State = { option: HSOptions.Unset, @@ -76,6 +86,10 @@ export const unpersistedReducer = (_state: State, action: Action): State => { return { option: HSOptions.Any, }; + case ActionType.SetNone: + return { + option: HSOptions.None, + }; case ActionType.SetHS: return { option: HSOptions.TrustedHSOnly, diff --git a/src/utils/getHS.ts b/src/utils/getHS.ts index 237ef4f..5411aff 100644 --- a/src/utils/getHS.ts +++ b/src/utils/getHS.ts @@ -22,6 +22,19 @@ import HSContext, { } from '../contexts/HSContext'; import { SafeLink } from '../parser/types'; +export function getHSFromIdentifier(identifier: string) { + try { + const match = identifier.match(/^.*:(?.*)$/); + if (match && match.groups) { + return match.groups.server; + } + } catch (e) { + console.error(`Could parse user identifier: ${identifier}`); + console.error(e); + } + return; +} + function selectedClient({ link, identifier, hsOptions }: { link?: SafeLink, identifier?: string; @@ -35,23 +48,16 @@ function selectedClient({ link, identifier, hsOptions }: { ] : []; const identifierHS: string[] = []; - try { - if (identifier) { - const match = identifier.match(/^.*:(?.*)$/); - if (match && match.groups) { - const server = match.groups.server; - if (server) { - identifierHS.push(server); - } - } + if (identifier) { + const server = getHSFromIdentifier(identifier); + if (server) { + identifierHS.push(server); } - } catch (e) { - console.error(`Could parse user identifier: ${identifier}`); - console.error(e); } switch (hsOptions.option) { case HSOptions.Unset: + case HSOptions.None: return []; case HSOptions.TrustedHSOnly: return [hsOptions.hs];