chore: add server switcher (#2717)
* wip * wip2 * refactor: use radio button group * chore: add persisting host per workspace * chore: header style * chore: update server picker title style * chore: margin * chore: label * chore: remove separator * rename migration to latest snjs version --------- Co-authored-by: Aman Harwara <amanharwara@protonmail.com>
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ChangeEventHandler, FunctionComponent, ReactNode, useCallback, useEffect, useState } from 'react'
|
||||
import { FunctionComponent, ReactNode, useCallback, useEffect, useState } from 'react'
|
||||
import Checkbox from '@/Components/Checkbox/Checkbox'
|
||||
import DecoratedInput from '@/Components/Input/DecoratedInput'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { useApplication } from '../ApplicationProvider'
|
||||
import ServerPicker from './ServerPicker/ServerPicker'
|
||||
|
||||
type Props = {
|
||||
disabled?: boolean
|
||||
@@ -22,7 +23,7 @@ const AdvancedOptions: FunctionComponent<Props> = ({
|
||||
}) => {
|
||||
const application = useApplication()
|
||||
|
||||
const { server, setServer, enableServerOption, setEnableServerOption } = application.accountMenuController
|
||||
const { server, setServer } = application.accountMenuController
|
||||
const [showAdvanced, setShowAdvanced] = useState(false)
|
||||
|
||||
const [isPrivateUsername, setIsPrivateUsername] = useState(false)
|
||||
@@ -71,9 +72,8 @@ const AdvancedOptions: FunctionComponent<Props> = ({
|
||||
if (!isRecoveryCodes) {
|
||||
setIsPrivateUsername(false)
|
||||
setIsStrictSignin(false)
|
||||
setEnableServerOption(false)
|
||||
}
|
||||
}, [isRecoveryCodes, setIsPrivateUsername, setIsStrictSignin, setEnableServerOption, onRecoveryCodesChange])
|
||||
}, [isRecoveryCodes, setIsPrivateUsername, setIsStrictSignin, onRecoveryCodesChange])
|
||||
|
||||
const handleRecoveryCodesChange = useCallback(
|
||||
(recoveryCodes: string) => {
|
||||
@@ -85,19 +85,10 @@ const AdvancedOptions: FunctionComponent<Props> = ({
|
||||
[onRecoveryCodesChange],
|
||||
)
|
||||
|
||||
const handleServerOptionChange: ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
(e) => {
|
||||
if (e.target instanceof HTMLInputElement) {
|
||||
setEnableServerOption(e.target.checked)
|
||||
}
|
||||
},
|
||||
[setEnableServerOption],
|
||||
)
|
||||
|
||||
const handleSyncServerChange = useCallback(
|
||||
(server: string) => {
|
||||
(server: string, websocketUrl?: string) => {
|
||||
setServer(server)
|
||||
application.setCustomHost(server).catch(console.error)
|
||||
application.setCustomHost(server, websocketUrl).catch(console.error)
|
||||
},
|
||||
[application, setServer],
|
||||
)
|
||||
@@ -124,100 +115,87 @@ const AdvancedOptions: FunctionComponent<Props> = ({
|
||||
</div>
|
||||
</button>
|
||||
{showAdvanced ? (
|
||||
<div className="my-2 px-3">
|
||||
{children}
|
||||
<>
|
||||
<div className="my-2 px-3">
|
||||
{children}
|
||||
|
||||
<div className="mb-1 flex items-center justify-between">
|
||||
<Checkbox
|
||||
name="private-workspace"
|
||||
label="Private username mode"
|
||||
checked={isPrivateUsername}
|
||||
disabled={disabled || isRecoveryCodes}
|
||||
onChange={handleIsPrivateUsernameChange}
|
||||
/>
|
||||
<a href="https://standardnotes.com/help/80" target="_blank" rel="noopener noreferrer" title="Learn more">
|
||||
<Icon type="info" className="text-neutral" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{isPrivateUsername && (
|
||||
<>
|
||||
<DecoratedInput
|
||||
className={{ container: 'mb-2' }}
|
||||
left={[<Icon type="account-circle" className="text-neutral" />]}
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={privateUsername}
|
||||
onChange={handlePrivateUsernameNameChange}
|
||||
disabled={disabled || isRecoveryCodes}
|
||||
spellcheck={false}
|
||||
autocomplete={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{onStrictSignInChange && (
|
||||
<div className="mb-1 flex items-center justify-between">
|
||||
<Checkbox
|
||||
name="use-strict-signin"
|
||||
label="Use strict sign-in"
|
||||
checked={isStrictSignin}
|
||||
name="private-workspace"
|
||||
label="Private username mode"
|
||||
checked={isPrivateUsername}
|
||||
disabled={disabled || isRecoveryCodes}
|
||||
onChange={handleStrictSigninChange}
|
||||
onChange={handleIsPrivateUsernameChange}
|
||||
/>
|
||||
<a
|
||||
href="https://standardnotes.com/help/security"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Learn more"
|
||||
>
|
||||
<a href="https://standardnotes.com/help/80" target="_blank" rel="noopener noreferrer" title="Learn more">
|
||||
<Icon type="info" className="text-neutral" />
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-1 flex items-center justify-between">
|
||||
<Checkbox
|
||||
name="recovery-codes"
|
||||
label="Use recovery code"
|
||||
checked={isRecoveryCodes}
|
||||
disabled={disabled}
|
||||
onChange={handleIsRecoveryCodesChange}
|
||||
/>
|
||||
</div>
|
||||
{isPrivateUsername && (
|
||||
<>
|
||||
<DecoratedInput
|
||||
className={{ container: 'mb-2' }}
|
||||
left={[<Icon type="account-circle" className="text-neutral" />]}
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={privateUsername}
|
||||
onChange={handlePrivateUsernameNameChange}
|
||||
disabled={disabled || isRecoveryCodes}
|
||||
spellcheck={false}
|
||||
autocomplete={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isRecoveryCodes && (
|
||||
<>
|
||||
<DecoratedInput
|
||||
className={{ container: 'mb-2' }}
|
||||
left={[<Icon type="security" className="text-neutral" />]}
|
||||
type="text"
|
||||
placeholder="Recovery code"
|
||||
value={recoveryCodes}
|
||||
onChange={handleRecoveryCodesChange}
|
||||
{onStrictSignInChange && (
|
||||
<div className="mb-1 flex items-center justify-between">
|
||||
<Checkbox
|
||||
name="use-strict-signin"
|
||||
label="Use strict sign-in"
|
||||
checked={isStrictSignin}
|
||||
disabled={disabled || isRecoveryCodes}
|
||||
onChange={handleStrictSigninChange}
|
||||
/>
|
||||
<a
|
||||
href="https://standardnotes.com/help/security"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Learn more"
|
||||
>
|
||||
<Icon type="info" className="text-neutral" />
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-1 flex items-center justify-between">
|
||||
<Checkbox
|
||||
name="recovery-codes"
|
||||
label="Use recovery code"
|
||||
checked={isRecoveryCodes}
|
||||
disabled={disabled}
|
||||
spellcheck={false}
|
||||
autocomplete={false}
|
||||
onChange={handleIsRecoveryCodesChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Checkbox
|
||||
name="custom-sync-server"
|
||||
label="Custom sync server"
|
||||
checked={enableServerOption}
|
||||
onChange={handleServerOptionChange}
|
||||
disabled={disabled || isRecoveryCodes}
|
||||
/>
|
||||
<DecoratedInput
|
||||
type="text"
|
||||
left={[<Icon type="server" className="text-neutral" />]}
|
||||
placeholder="https://api.standardnotes.com"
|
||||
value={server}
|
||||
onChange={handleSyncServerChange}
|
||||
disabled={!enableServerOption && !disabled && !isRecoveryCodes}
|
||||
/>
|
||||
</div>
|
||||
{isRecoveryCodes && (
|
||||
<>
|
||||
<DecoratedInput
|
||||
className={{ container: 'mb-2' }}
|
||||
left={[<Icon type="security" className="text-neutral" />]}
|
||||
type="text"
|
||||
placeholder="Recovery code"
|
||||
value={recoveryCodes}
|
||||
onChange={handleRecoveryCodesChange}
|
||||
disabled={disabled}
|
||||
spellcheck={false}
|
||||
autocomplete={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<ServerPicker customServerAddress={server} handleCustomServerAddressChange={handleSyncServerChange} />
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { ServerType } from './ServerType'
|
||||
import DecoratedInput from '@/Components/Input/DecoratedInput'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { useApplication } from '@/Components/ApplicationProvider'
|
||||
import { isDesktopApplication } from '@/Utils'
|
||||
import RadioButtonGroup from '@/Components/RadioButtonGroup/RadioButtonGroup'
|
||||
import { DefaultHost } from '@standardnotes/snjs'
|
||||
|
||||
type Props = {
|
||||
customServerAddress?: string
|
||||
handleCustomServerAddressChange: (value: string, websocketUrl?: string) => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
const ServerPicker = ({ className, customServerAddress, handleCustomServerAddressChange }: Props) => {
|
||||
const application = useApplication()
|
||||
|
||||
const [currentType, setCurrentType] = useState<ServerType>('standard')
|
||||
|
||||
const selectTab = async (type: ServerType) => {
|
||||
setCurrentType(type)
|
||||
if (type === 'standard') {
|
||||
handleCustomServerAddressChange(DefaultHost.Api, DefaultHost.WebSocket)
|
||||
}
|
||||
if (type === 'home server') {
|
||||
if (!application.homeServer) {
|
||||
application.alerts
|
||||
.alert('Home server is not running. Please open the prefences and home server tab to start it.')
|
||||
.catch(console.error)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const homeServerUrl = await application.homeServer.getHomeServerUrl()
|
||||
if (!homeServerUrl) {
|
||||
application.alerts
|
||||
.alert('Home server is not running. Please open the prefences and home server tab to start it.')
|
||||
.catch(console.error)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
handleCustomServerAddressChange(homeServerUrl)
|
||||
}
|
||||
}
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
[
|
||||
{ label: 'Default', value: 'standard' },
|
||||
{ label: 'Custom', value: 'custom' },
|
||||
].concat(isDesktopApplication() ? [{ label: 'Home Server', value: 'home server' }] : []) as {
|
||||
label: string
|
||||
value: ServerType
|
||||
}[],
|
||||
[],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={`flex h-full flex-grow flex-col px-3 pb-1.5 ${className}`}>
|
||||
<div className="mb-2 flex font-bold">Sync Server</div>
|
||||
<RadioButtonGroup value={currentType} items={options} onChange={selectTab} />
|
||||
{currentType === 'custom' && (
|
||||
<DecoratedInput
|
||||
className={{
|
||||
container: 'mt-1',
|
||||
}}
|
||||
type="text"
|
||||
left={[<Icon type="server" className="text-neutral" />]}
|
||||
placeholder={DefaultHost.Api}
|
||||
value={customServerAddress}
|
||||
onChange={handleCustomServerAddressChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServerPicker
|
||||
@@ -0,0 +1 @@
|
||||
export type ServerType = 'standard' | 'custom' | 'home server'
|
||||
@@ -19,7 +19,6 @@ export class AccountMenuController extends AbstractViewController implements Int
|
||||
signingOut = false
|
||||
otherSessionsSignOut = false
|
||||
server: string | undefined = undefined
|
||||
enableServerOption = false
|
||||
notesAndTags: (SNNote | SNTag)[] = []
|
||||
isEncryptionEnabled = false
|
||||
encryptionStatusString = ''
|
||||
@@ -48,7 +47,6 @@ export class AccountMenuController extends AbstractViewController implements Int
|
||||
signingOut: observable,
|
||||
otherSessionsSignOut: observable,
|
||||
server: observable,
|
||||
enableServerOption: observable,
|
||||
notesAndTags: observable,
|
||||
isEncryptionEnabled: observable,
|
||||
encryptionStatusString: observable,
|
||||
@@ -66,7 +64,6 @@ export class AccountMenuController extends AbstractViewController implements Int
|
||||
setIsBackupEncrypted: action,
|
||||
setOtherSessionsSignOut: action,
|
||||
setCurrentPane: action,
|
||||
setEnableServerOption: action,
|
||||
setServer: action,
|
||||
setDeletingAccount: action,
|
||||
|
||||
@@ -113,10 +110,6 @@ export class AccountMenuController extends AbstractViewController implements Int
|
||||
this.server = server
|
||||
}
|
||||
|
||||
setEnableServerOption = (enableServerOption: boolean): void => {
|
||||
this.enableServerOption = enableServerOption
|
||||
}
|
||||
|
||||
setIsEncryptionEnabled = (isEncryptionEnabled: boolean): void => {
|
||||
this.isEncryptionEnabled = isEncryptionEnabled
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user