refactor: replace 'preact' with 'react' (#1048)
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import { FunctionComponent } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { PreferencesMenu } from './PreferencesMenu'
|
||||
import Backups from '@/Components/Preferences/Panes/Backups/Backups'
|
||||
import Appearance from './Panes/Appearance'
|
||||
import General from './Panes/General/General'
|
||||
import AccountPreferences from './Panes/Account/AccountPreferences'
|
||||
import Security from './Panes/Security/Security'
|
||||
import Listed from './Panes/Listed/Listed'
|
||||
import HelpAndFeedback from './Panes/HelpFeedback'
|
||||
import { PreferencesProps } from './PreferencesProps'
|
||||
|
||||
const PaneSelector: FunctionComponent<PreferencesProps & { menu: PreferencesMenu }> = ({
|
||||
menu,
|
||||
appState,
|
||||
application,
|
||||
mfaProvider,
|
||||
userProvider,
|
||||
}) => {
|
||||
switch (menu.selectedPaneId) {
|
||||
case 'general':
|
||||
return (
|
||||
<General
|
||||
appState={appState}
|
||||
application={application}
|
||||
extensionsLatestVersions={menu.extensionsLatestVersions}
|
||||
/>
|
||||
)
|
||||
case 'account':
|
||||
return <AccountPreferences application={application} appState={appState} />
|
||||
case 'appearance':
|
||||
return <Appearance application={application} />
|
||||
case 'security':
|
||||
return (
|
||||
<Security mfaProvider={mfaProvider} userProvider={userProvider} appState={appState} application={application} />
|
||||
)
|
||||
case 'backups':
|
||||
return <Backups application={application} appState={appState} />
|
||||
case 'listed':
|
||||
return <Listed application={application} />
|
||||
case 'shortcuts':
|
||||
return null
|
||||
case 'accessibility':
|
||||
return null
|
||||
case 'get-free-month':
|
||||
return null
|
||||
case 'help-feedback':
|
||||
return <HelpAndFeedback />
|
||||
default:
|
||||
return (
|
||||
<General
|
||||
appState={appState}
|
||||
application={application}
|
||||
extensionsLatestVersions={menu.extensionsLatestVersions}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default observer(PaneSelector)
|
||||
@@ -1,20 +1,20 @@
|
||||
import { PreferencesPane } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { Authentication } from './Authentication'
|
||||
import { Credentials } from './Credentials'
|
||||
import { Sync } from './Sync'
|
||||
import { Subscription } from './Subscription/Subscription'
|
||||
import { SignOutWrapper } from './SignOutView'
|
||||
import { FilesSection } from './Files'
|
||||
import Authentication from './Authentication'
|
||||
import Credentials from './Credentials'
|
||||
import Sync from './Sync'
|
||||
import Subscription from './Subscription/Subscription'
|
||||
import SignOutWrapper from './SignOutView'
|
||||
import FilesSection from './Files'
|
||||
import PreferencesPane from '../../PreferencesComponents/PreferencesPane'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
appState: AppState
|
||||
}
|
||||
|
||||
export const AccountPreferences = observer(({ application, appState }: Props) => (
|
||||
const AccountPreferences = ({ application, appState }: Props) => (
|
||||
<PreferencesPane>
|
||||
{!application.hasAccount() ? (
|
||||
<Authentication application={application} appState={appState} />
|
||||
@@ -28,4 +28,6 @@ export const AccountPreferences = observer(({ application, appState }: Props) =>
|
||||
{application.hasAccount() && appState.features.hasFiles && <FilesSection application={application} />}
|
||||
<SignOutWrapper application={application} appState={appState} />
|
||||
</PreferencesPane>
|
||||
))
|
||||
)
|
||||
|
||||
export default observer(AccountPreferences)
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { FunctionalComponent } from 'preact'
|
||||
import { PreferencesGroup, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { OfflineSubscription } from '@/Components/Preferences/Panes/Account/OfflineSubscription'
|
||||
import { FunctionComponent } from 'react'
|
||||
import OfflineSubscription from '@/Components/Preferences/Panes/Account/OfflineSubscription'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { Extensions } from '@/Components/Preferences/Panes/Extensions/Extensions'
|
||||
import Extensions from '@/Components/Preferences/Panes/Extensions/Extensions'
|
||||
import { ExtensionsLatestVersions } from '@/Components/Preferences/Panes/Extensions/ExtensionsLatestVersions'
|
||||
import { AccordionItem } from '@/Components/Shared/AccordionItem'
|
||||
import AccordionItem from '@/Components/Shared/AccordionItem'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
interface IProps {
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
appState: AppState
|
||||
extensionsLatestVersions: ExtensionsLatestVersions
|
||||
}
|
||||
|
||||
export const Advanced: FunctionalComponent<IProps> = observer(({ application, appState, extensionsLatestVersions }) => {
|
||||
const Advanced: FunctionComponent<Props> = ({ application, appState, extensionsLatestVersions }) => {
|
||||
return (
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
@@ -33,4 +34,6 @@ export const Advanced: FunctionalComponent<IProps> = observer(({ application, ap
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(Advanced)
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/Components/Preferences/PreferencesComponents'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { AccountIllustration } from '@standardnotes/icons'
|
||||
import { AccountMenuPane } from '@/Components/AccountMenu/AccountMenuPane'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
export const Authentication: FunctionComponent<{
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
appState: AppState
|
||||
}> = observer(({ appState }) => {
|
||||
}
|
||||
|
||||
const Authentication: FunctionComponent<Props> = ({ appState }) => {
|
||||
const clickSignIn = () => {
|
||||
appState.preferences.closePreferences()
|
||||
appState.accountMenu.setCurrentPane(AccountMenuPane.SignIn)
|
||||
@@ -43,4 +47,6 @@ export const Authentication: FunctionComponent<{
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(Authentication)
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { useState } from '@node_modules/preact/hooks'
|
||||
import {
|
||||
ModalDialog,
|
||||
ModalDialogButtons,
|
||||
ModalDialogDescription,
|
||||
ModalDialogLabel,
|
||||
} from '@/Components/Shared/ModalDialog'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { FunctionalComponent } from 'preact'
|
||||
import ModalDialog from '@/Components/Shared/ModalDialog'
|
||||
import ModalDialogButtons from '@/Components/Shared/ModalDialogButtons'
|
||||
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
|
||||
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { FunctionComponent, useState } from 'react'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { useBeforeUnload } from '@/Hooks/useBeforeUnload'
|
||||
import { ChangeEmailForm } from './ChangeEmailForm'
|
||||
import { ChangeEmailSuccess } from './ChangeEmailSuccess'
|
||||
import ChangeEmailForm from './ChangeEmailForm'
|
||||
import ChangeEmailSuccess from './ChangeEmailSuccess'
|
||||
import { isEmailValid } from '@/Utils'
|
||||
|
||||
enum SubmitButtonTitles {
|
||||
@@ -29,7 +26,7 @@ type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const ChangeEmail: FunctionalComponent<Props> = ({ onCloseDialog, application }) => {
|
||||
const ChangeEmail: FunctionComponent<Props> = ({ onCloseDialog, application }) => {
|
||||
const [currentPassword, setCurrentPassword] = useState('')
|
||||
const [newEmail, setNewEmail] = useState('')
|
||||
const [isContinuing, setIsContinuing] = useState(false)
|
||||
@@ -158,3 +155,5 @@ export const ChangeEmail: FunctionalComponent<Props> = ({ onCloseDialog, applica
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChangeEmail
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { StateUpdater } from 'preact/hooks'
|
||||
import { FunctionalComponent } from 'preact'
|
||||
import { Dispatch, SetStateAction, FunctionComponent } from 'react'
|
||||
|
||||
type Props = {
|
||||
setNewEmail: StateUpdater<string>
|
||||
setCurrentPassword: StateUpdater<string>
|
||||
setNewEmail: Dispatch<SetStateAction<string>>
|
||||
setCurrentPassword: Dispatch<SetStateAction<string>>
|
||||
}
|
||||
|
||||
const labelClassName = 'block mb-1'
|
||||
|
||||
const inputClassName = 'sk-input contrast'
|
||||
|
||||
export const ChangeEmailForm: FunctionalComponent<Props> = ({ setNewEmail, setCurrentPassword }) => {
|
||||
const ChangeEmailForm: FunctionComponent<Props> = ({ setNewEmail, setCurrentPassword }) => {
|
||||
return (
|
||||
<div className="w-full flex flex-col">
|
||||
<div className="mt-2 mb-3">
|
||||
@@ -42,3 +41,5 @@ export const ChangeEmailForm: FunctionalComponent<Props> = ({ setNewEmail, setCu
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChangeEmailForm
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FunctionalComponent } from 'preact'
|
||||
import { FunctionComponent } from 'react'
|
||||
|
||||
export const ChangeEmailSuccess: FunctionalComponent = () => {
|
||||
const ChangeEmailSuccess: FunctionComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className={'sk-label sk-bold info mt-2'}>Your email has been successfully changed.</div>
|
||||
@@ -11,3 +11,5 @@ export const ChangeEmailSuccess: FunctionalComponent = () => {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChangeEmailSuccess
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { Title, Text } from '../../PreferencesComponents/Content'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
const ClearSessionDataView: FunctionComponent<{
|
||||
appState: AppState
|
||||
}> = ({ appState }) => {
|
||||
return (
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Clear workspace</Title>
|
||||
<Text>Remove all data related to the current workspace from the application.</Text>
|
||||
<div className="min-h-3" />
|
||||
<Button
|
||||
dangerStyle={true}
|
||||
label="Clear workspace"
|
||||
onClick={() => {
|
||||
appState.accountMenu.setSigningOut(true)
|
||||
}}
|
||||
/>
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)
|
||||
}
|
||||
|
||||
export default observer(ClearSessionDataView)
|
||||
@@ -1,28 +1,24 @@
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Subtitle,
|
||||
Text,
|
||||
Title,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { observer } from '@node_modules/mobx-react-lite'
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import { dateToLocalizedString } from '@standardnotes/snjs'
|
||||
import { useCallback, useState } from 'preact/hooks'
|
||||
import { ChangeEmail } from '@/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmail'
|
||||
import { FunctionComponent, render } from 'preact'
|
||||
import { useCallback, useState, FunctionComponent } from 'react'
|
||||
import ChangeEmail from '@/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmail'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { PasswordWizard } from '@/Components/PasswordWizard/PasswordWizard'
|
||||
import PasswordWizard from '@/Components/PasswordWizard/PasswordWizard'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
appState: AppState
|
||||
}
|
||||
|
||||
export const Credentials: FunctionComponent<Props> = observer(({ application }: Props) => {
|
||||
const Credentials: FunctionComponent<Props> = ({ application }: Props) => {
|
||||
const [isChangeEmailDialogOpen, setIsChangeEmailDialogOpen] = useState(false)
|
||||
const [shouldShowPasswordWizard, setShouldShowPasswordWizard] = useState(false)
|
||||
|
||||
const user = application.getUser()
|
||||
|
||||
@@ -30,35 +26,46 @@ export const Credentials: FunctionComponent<Props> = observer(({ application }:
|
||||
const passwordCreatedOn = dateToLocalizedString(passwordCreatedAtTimestamp)
|
||||
|
||||
const presentPasswordWizard = useCallback(() => {
|
||||
render(<PasswordWizard application={application} />, document.body.appendChild(document.createElement('div')))
|
||||
}, [application])
|
||||
setShouldShowPasswordWizard(true)
|
||||
}, [])
|
||||
|
||||
const dismissPasswordWizard = useCallback(() => {
|
||||
setShouldShowPasswordWizard(false)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Credentials</Title>
|
||||
<Subtitle>Email</Subtitle>
|
||||
<Text>
|
||||
You're signed in as <span className="font-bold wrap">{user?.email}</span>
|
||||
</Text>
|
||||
<Button
|
||||
className="min-w-20 mt-3"
|
||||
variant="normal"
|
||||
label="Change email"
|
||||
onClick={() => {
|
||||
setIsChangeEmailDialogOpen(true)
|
||||
}}
|
||||
/>
|
||||
<HorizontalSeparator classes="mt-5 mb-3" />
|
||||
<Subtitle>Password</Subtitle>
|
||||
<Text>
|
||||
Current password was set on <span className="font-bold">{passwordCreatedOn}</span>
|
||||
</Text>
|
||||
<Button className="min-w-20 mt-3" variant="normal" label="Change password" onClick={presentPasswordWizard} />
|
||||
{isChangeEmailDialogOpen && (
|
||||
<ChangeEmail onCloseDialog={() => setIsChangeEmailDialogOpen(false)} application={application} />
|
||||
)}
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
<>
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Credentials</Title>
|
||||
<Subtitle>Email</Subtitle>
|
||||
<Text>
|
||||
You're signed in as <span className="font-bold wrap">{user?.email}</span>
|
||||
</Text>
|
||||
<Button
|
||||
className="min-w-20 mt-3"
|
||||
variant="normal"
|
||||
label="Change email"
|
||||
onClick={() => {
|
||||
setIsChangeEmailDialogOpen(true)
|
||||
}}
|
||||
/>
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
<Subtitle>Password</Subtitle>
|
||||
<Text>
|
||||
Current password was set on <span className="font-bold">{passwordCreatedOn}</span>
|
||||
</Text>
|
||||
<Button className="min-w-20 mt-3" variant="normal" label="Change password" onClick={presentPasswordWizard} />
|
||||
{isChangeEmailDialogOpen && (
|
||||
<ChangeEmail onCloseDialog={() => setIsChangeEmailDialogOpen(false)} application={application} />
|
||||
)}
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
{shouldShowPasswordWizard ? (
|
||||
<PasswordWizard application={application} dismissModal={dismissPasswordWizard} />
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(Credentials)
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { formatSizeToReadableString } from '@standardnotes/filepicker'
|
||||
import { SubscriptionSettingName } from '@standardnotes/snjs'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import { PreferencesGroup, PreferencesSegment, Subtitle, Title } from '../../PreferencesComponents'
|
||||
import { FunctionComponent, useEffect, useState } from 'react'
|
||||
import { Subtitle, Title } from '../../PreferencesComponents/Content'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const FilesSection: FunctionComponent<Props> = ({ application }) => {
|
||||
const FilesSection: FunctionComponent<Props> = ({ application }) => {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [filesQuotaUsed, setFilesQuotaUsed] = useState<number>(0)
|
||||
const [filesQuotaTotal, setFilesQuotaTotal] = useState<number>(0)
|
||||
@@ -63,3 +64,5 @@ export const FilesSection: FunctionComponent<Props> = ({ application }) => {
|
||||
</PreferencesGroup>
|
||||
)
|
||||
}
|
||||
|
||||
export default FilesSection
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
import { FunctionalComponent } from 'preact'
|
||||
import { Subtitle } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { DecoratedInput } from '@/Components/Input/DecoratedInput'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import React, { FunctionComponent, useEffect, useState } from 'react'
|
||||
import { Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import DecoratedInput from '@/Components/Input/DecoratedInput'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { STRING_REMOVE_OFFLINE_KEY_CONFIRMATION } from '@/Strings'
|
||||
import { ButtonType, ClientDisplayableError } from '@standardnotes/snjs'
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
|
||||
interface IProps {
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
appState: AppState
|
||||
}
|
||||
|
||||
export const OfflineSubscription: FunctionalComponent<IProps> = observer(({ application }) => {
|
||||
const OfflineSubscription: FunctionComponent<Props> = ({ application }) => {
|
||||
const [activationCode, setActivationCode] = useState('')
|
||||
const [isSuccessfullyActivated, setIsSuccessfullyActivated] = useState(false)
|
||||
const [isSuccessfullyRemoved, setIsSuccessfullyRemoved] = useState(false)
|
||||
@@ -31,7 +30,7 @@ export const OfflineSubscription: FunctionalComponent<IProps> = observer(({ appl
|
||||
return !application.hasAccount() || application.isThirdPartyHostUsed() || hasUserPreviouslyStoredCode
|
||||
}
|
||||
|
||||
const handleSubscriptionCodeSubmit = async (event: Event) => {
|
||||
const handleSubscriptionCodeSubmit = async (event: React.FormEvent) => {
|
||||
event.preventDefault()
|
||||
|
||||
const result = await application.features.setOfflineFeaturesCode(activationCode)
|
||||
@@ -123,4 +122,6 @@ export const OfflineSubscription: FunctionalComponent<IProps> = observer(({ appl
|
||||
<HorizontalSeparator classes="mt-8 mb-5" />
|
||||
</>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(OfflineSubscription)
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { OtherSessionsSignOutContainer } from '@/Components/OtherSessionsSignOut/OtherSessionsSignOut'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Subtitle,
|
||||
Text,
|
||||
Title,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import OtherSessionsSignOutContainer from '@/Components/OtherSessionsSignOut/OtherSessionsSignOut'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { Subtitle, Title, Text } from '../../PreferencesComponents/Content'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
import ClearSessionDataView from './ClearSessionDataView'
|
||||
|
||||
const SignOutView: FunctionComponent<{
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
appState: AppState
|
||||
}> = observer(({ application, appState }) => {
|
||||
}
|
||||
|
||||
const SignOutView: FunctionComponent<Props> = observer(({ application, appState }) => {
|
||||
return (
|
||||
<>
|
||||
<PreferencesGroup>
|
||||
@@ -36,6 +36,7 @@ const SignOutView: FunctionComponent<{
|
||||
<Button variant="normal" label="Manage sessions" onClick={() => appState.openSessionsModal()} />
|
||||
</div>
|
||||
</PreferencesSegment>
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
<PreferencesSegment>
|
||||
<Subtitle>This workspace</Subtitle>
|
||||
<Text>Remove all data related to the current workspace from the application.</Text>
|
||||
@@ -54,33 +55,13 @@ const SignOutView: FunctionComponent<{
|
||||
)
|
||||
})
|
||||
|
||||
const ClearSessionDataView: FunctionComponent<{
|
||||
appState: AppState
|
||||
}> = observer(({ appState }) => {
|
||||
return (
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Clear workspace</Title>
|
||||
<Text>Remove all data related to the current workspace from the application.</Text>
|
||||
<div className="min-h-3" />
|
||||
<Button
|
||||
dangerStyle={true}
|
||||
label="Clear workspace"
|
||||
onClick={() => {
|
||||
appState.accountMenu.setSigningOut(true)
|
||||
}}
|
||||
/>
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)
|
||||
})
|
||||
SignOutView.displayName = 'SignOutView'
|
||||
|
||||
export const SignOutWrapper: FunctionComponent<{
|
||||
application: WebApplication
|
||||
appState: AppState
|
||||
}> = observer(({ application, appState }) => {
|
||||
const SignOutWrapper: FunctionComponent<Props> = ({ application, appState }) => {
|
||||
if (!application.hasAccount()) {
|
||||
return <ClearSessionDataView appState={appState} />
|
||||
}
|
||||
return <SignOutView appState={appState} application={application} />
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(SignOutWrapper)
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { FunctionalComponent } from 'preact'
|
||||
import { LinkButton, Text } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { FunctionComponent, useState } from 'react'
|
||||
import { LinkButton, Text } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { useState } from 'preact/hooks'
|
||||
import { loadPurchaseFlowUrl } from '@/Components/PurchaseFlow/PurchaseFlowFunctions'
|
||||
|
||||
export const NoSubscription: FunctionalComponent<{
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
}> = ({ application }) => {
|
||||
}
|
||||
|
||||
const NoSubscription: FunctionComponent<Props> = ({ application }) => {
|
||||
const [isLoadingPurchaseFlow, setIsLoadingPurchaseFlow] = useState(false)
|
||||
const [purchaseFlowError, setPurchaseFlowError] = useState<string | undefined>(undefined)
|
||||
|
||||
@@ -39,3 +40,5 @@ export const NoSubscription: FunctionalComponent<{
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default NoSubscription
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { SubscriptionState } from '@/UIModels/AppState/SubscriptionState'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Text } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
|
||||
type Props = { subscriptionState: SubscriptionState }
|
||||
|
||||
const StatusText = ({ subscriptionState }: Props) => {
|
||||
const {
|
||||
userSubscriptionName,
|
||||
userSubscriptionExpirationDate,
|
||||
isUserSubscriptionExpired,
|
||||
isUserSubscriptionCanceled,
|
||||
} = subscriptionState
|
||||
const expirationDateString = userSubscriptionExpirationDate?.toLocaleString()
|
||||
|
||||
if (isUserSubscriptionCanceled) {
|
||||
return (
|
||||
<Text className="mt-1">
|
||||
Your{' '}
|
||||
<span className="font-bold">
|
||||
Standard Notes{userSubscriptionName ? ' ' : ''}
|
||||
{userSubscriptionName}
|
||||
</span>{' '}
|
||||
subscription has been canceled{' '}
|
||||
{isUserSubscriptionExpired ? (
|
||||
<span className="font-bold">and expired on {expirationDateString}</span>
|
||||
) : (
|
||||
<span className="font-bold">but will remain valid until {expirationDateString}</span>
|
||||
)}
|
||||
. You may resubscribe below if you wish.
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
if (isUserSubscriptionExpired) {
|
||||
return (
|
||||
<Text className="mt-1">
|
||||
Your{' '}
|
||||
<span className="font-bold">
|
||||
Standard Notes{userSubscriptionName ? ' ' : ''}
|
||||
{userSubscriptionName}
|
||||
</span>{' '}
|
||||
subscription <span className="font-bold">expired on {expirationDateString}</span>. You may resubscribe below if
|
||||
you wish.
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Text className="mt-1">
|
||||
Your{' '}
|
||||
<span className="font-bold">
|
||||
Standard Notes{userSubscriptionName ? ' ' : ''}
|
||||
{userSubscriptionName}
|
||||
</span>{' '}
|
||||
subscription will be <span className="font-bold">renewed on {expirationDateString}</span>.
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
export default observer(StatusText)
|
||||
@@ -1,17 +1,19 @@
|
||||
import { PreferencesGroup, PreferencesSegment, Title } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { SubscriptionInformation } from './SubscriptionInformation'
|
||||
import { NoSubscription } from './NoSubscription'
|
||||
import SubscriptionInformation from './SubscriptionInformation'
|
||||
import NoSubscription from './NoSubscription'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
appState: AppState
|
||||
}
|
||||
|
||||
export const Subscription: FunctionComponent<Props> = observer(({ application, appState }: Props) => {
|
||||
const Subscription: FunctionComponent<Props> = ({ application, appState }: Props) => {
|
||||
const subscriptionState = appState.subscription
|
||||
const { userSubscription } = subscriptionState
|
||||
|
||||
@@ -33,4 +35,6 @@ export const Subscription: FunctionComponent<Props> = observer(({ application, a
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(Subscription)
|
||||
|
||||
@@ -1,70 +1,16 @@
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { SubscriptionState } from '@/UIModels/AppState/SubscriptionState'
|
||||
import { Text } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { openSubscriptionDashboard } from '@/Utils/ManageSubscription'
|
||||
import StatusText from './StatusText'
|
||||
|
||||
type Props = {
|
||||
subscriptionState: SubscriptionState
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
const StatusText = observer(({ subscriptionState }: { subscriptionState: Props['subscriptionState'] }) => {
|
||||
const {
|
||||
userSubscriptionName,
|
||||
userSubscriptionExpirationDate,
|
||||
isUserSubscriptionExpired,
|
||||
isUserSubscriptionCanceled,
|
||||
} = subscriptionState
|
||||
const expirationDateString = userSubscriptionExpirationDate?.toLocaleString()
|
||||
|
||||
if (isUserSubscriptionCanceled) {
|
||||
return (
|
||||
<Text className="mt-1">
|
||||
Your{' '}
|
||||
<span className="font-bold">
|
||||
Standard Notes{userSubscriptionName ? ' ' : ''}
|
||||
{userSubscriptionName}
|
||||
</span>{' '}
|
||||
subscription has been canceled{' '}
|
||||
{isUserSubscriptionExpired ? (
|
||||
<span className="font-bold">and expired on {expirationDateString}</span>
|
||||
) : (
|
||||
<span className="font-bold">but will remain valid until {expirationDateString}</span>
|
||||
)}
|
||||
. You may resubscribe below if you wish.
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
if (isUserSubscriptionExpired) {
|
||||
return (
|
||||
<Text className="mt-1">
|
||||
Your{' '}
|
||||
<span className="font-bold">
|
||||
Standard Notes{userSubscriptionName ? ' ' : ''}
|
||||
{userSubscriptionName}
|
||||
</span>{' '}
|
||||
subscription <span className="font-bold">expired on {expirationDateString}</span>. You may resubscribe below if
|
||||
you wish.
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Text className="mt-1">
|
||||
Your{' '}
|
||||
<span className="font-bold">
|
||||
Standard Notes{userSubscriptionName ? ' ' : ''}
|
||||
{userSubscriptionName}
|
||||
</span>{' '}
|
||||
subscription will be <span className="font-bold">renewed on {expirationDateString}</span>.
|
||||
</Text>
|
||||
)
|
||||
})
|
||||
|
||||
export const SubscriptionInformation = observer(({ subscriptionState, application }: Props) => {
|
||||
const SubscriptionInformation = ({ subscriptionState, application }: Props) => {
|
||||
const manageSubscription = async () => {
|
||||
openSubscriptionDashboard(application)
|
||||
}
|
||||
@@ -80,4 +26,6 @@ export const SubscriptionInformation = observer(({ subscriptionState, applicatio
|
||||
/>
|
||||
</>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(SubscriptionInformation)
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { SyncQueueStrategy, dateToLocalizedString } from '@standardnotes/snjs'
|
||||
import { Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { SyncQueueStrategy } from '@standardnotes/snjs'
|
||||
import { STRING_GENERIC_SYNC_ERROR } from '@/Strings'
|
||||
import { useState } from '@node_modules/preact/hooks'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { FunctionComponent, useState } from 'react'
|
||||
import { formatLastSyncDate } from '@/Utils/FormatLastSyncDate'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const formatLastSyncDate = (lastUpdatedDate: Date) => {
|
||||
return dateToLocalizedString(lastUpdatedDate)
|
||||
}
|
||||
|
||||
export const Sync: FunctionComponent<Props> = observer(({ application }: Props) => {
|
||||
const Sync: FunctionComponent<Props> = ({ application }: Props) => {
|
||||
const [isSyncingInProgress, setIsSyncingInProgress] = useState(false)
|
||||
const [lastSyncDate, setLastSyncDate] = useState(formatLastSyncDate(application.sync.getLastSyncDate() as Date))
|
||||
|
||||
@@ -55,4 +53,6 @@ export const Sync: FunctionComponent<Props> = observer(({ application }: Props)
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(Sync)
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
import { Dropdown, DropdownItem } from '@/Components/Dropdown/Dropdown'
|
||||
import Dropdown from '@/Components/Dropdown/Dropdown'
|
||||
import { DropdownItem } from '@/Components/Dropdown/DropdownItem'
|
||||
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
import { Switch } from '@/Components/Switch/Switch'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import Switch from '@/Components/Switch/Switch'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { ContentType, FeatureIdentifier, FeatureStatus, PrefKey, GetFeatures, SNTheme } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesPane,
|
||||
PreferencesSegment,
|
||||
Subtitle,
|
||||
Title,
|
||||
Text,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { FunctionComponent, useEffect, useState } from 'react'
|
||||
import { Subtitle, Title, Text } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { sortThemes } from '@/Utils/SortThemes'
|
||||
import PreferencesPane from '../PreferencesComponents/PreferencesPane'
|
||||
import PreferencesGroup from '../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const Appearance: FunctionComponent<Props> = observer(({ application }) => {
|
||||
const Appearance: FunctionComponent<Props> = ({ application }) => {
|
||||
const premiumModal = usePremiumModal()
|
||||
const isEntitledToMidnightTheme =
|
||||
application.features.getFeatureStatus(FeatureIdentifier.MidnightTheme) === FeatureStatus.Entitled
|
||||
@@ -120,7 +116,7 @@ export const Appearance: FunctionComponent<Props> = observer(({ application }) =
|
||||
</div>
|
||||
<Switch onChange={toggleUseDeviceSettings} checked={useDeviceSettings} />
|
||||
</div>
|
||||
<HorizontalSeparator classes="mt-5 mb-3" />
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
<div>
|
||||
<Subtitle>Automatic Light Theme</Subtitle>
|
||||
<Text>Theme to be used for system light mode:</Text>
|
||||
@@ -135,7 +131,7 @@ export const Appearance: FunctionComponent<Props> = observer(({ application }) =
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<HorizontalSeparator classes="mt-5 mb-3" />
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
<div>
|
||||
<Subtitle>Automatic Dark Theme</Subtitle>
|
||||
<Text>Theme to be used for system dark mode:</Text>
|
||||
@@ -155,4 +151,6 @@ export const Appearance: FunctionComponent<Props> = observer(({ application }) =
|
||||
</PreferencesGroup>
|
||||
</PreferencesPane>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(Appearance)
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { PreferencesPane } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { CloudLink } from './CloudBackups/CloudBackups'
|
||||
import { DataBackups } from './DataBackups'
|
||||
import { EmailBackups } from './EmailBackups'
|
||||
import { FileBackups } from './Files/FileBackups'
|
||||
import { FunctionComponent } from 'react'
|
||||
import PreferencesPane from '@/Components/Preferences/PreferencesComponents/PreferencesPane'
|
||||
import CloudLink from './CloudBackups/CloudBackups'
|
||||
import DataBackups from './DataBackups'
|
||||
import EmailBackups from './EmailBackups'
|
||||
import FileBackupsCrossPlatform from './Files/FileBackupsCrossPlatform'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
|
||||
interface Props {
|
||||
type Props = {
|
||||
appState: AppState
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const Backups: FunctionComponent<Props> = ({ application, appState }) => {
|
||||
const Backups: FunctionComponent<Props> = ({ application, appState }) => {
|
||||
return (
|
||||
<PreferencesPane>
|
||||
<DataBackups application={application} appState={appState} />
|
||||
<FileBackups application={application} />
|
||||
<FileBackupsCrossPlatform application={application} />
|
||||
<EmailBackups application={application} />
|
||||
<CloudLink application={application} />
|
||||
</PreferencesPane>
|
||||
)
|
||||
}
|
||||
|
||||
export default observer(Backups)
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { useCallback, useEffect, useState } from 'preact/hooks'
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
FunctionComponent,
|
||||
KeyboardEventHandler,
|
||||
ChangeEventHandler,
|
||||
MouseEventHandler,
|
||||
} from 'react'
|
||||
import {
|
||||
ButtonType,
|
||||
SettingName,
|
||||
@@ -8,11 +16,10 @@ import {
|
||||
OneDriveBackupFrequency,
|
||||
} from '@standardnotes/snjs'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { isDev, openInNewTab } from '@/Utils'
|
||||
import { Subtitle } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { KeyboardKey } from '@/Services/IOService'
|
||||
import { FunctionComponent } from 'preact'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -20,17 +27,13 @@ type Props = {
|
||||
isEntitledToCloudBackups: boolean
|
||||
}
|
||||
|
||||
export const CloudBackupProvider: FunctionComponent<Props> = ({
|
||||
application,
|
||||
providerName,
|
||||
isEntitledToCloudBackups,
|
||||
}) => {
|
||||
const CloudBackupProvider: FunctionComponent<Props> = ({ application, providerName, isEntitledToCloudBackups }) => {
|
||||
const [authBegan, setAuthBegan] = useState(false)
|
||||
const [successfullyInstalled, setSuccessfullyInstalled] = useState(false)
|
||||
const [backupFrequency, setBackupFrequency] = useState<string | undefined>(undefined)
|
||||
const [confirmation, setConfirmation] = useState('')
|
||||
|
||||
const disable = async (event: Event) => {
|
||||
const disable: MouseEventHandler = async (event) => {
|
||||
event.stopPropagation()
|
||||
|
||||
try {
|
||||
@@ -52,7 +55,7 @@ export const CloudBackupProvider: FunctionComponent<Props> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const installIntegration = (event: Event) => {
|
||||
const installIntegration: MouseEventHandler = (event) => {
|
||||
if (!isEntitledToCloudBackups) {
|
||||
return
|
||||
}
|
||||
@@ -117,7 +120,7 @@ export const CloudBackupProvider: FunctionComponent<Props> = ({
|
||||
return urlSearchParams.get(integrationTokenKeyInUrl)
|
||||
}
|
||||
|
||||
const handleKeyPress = async (event: KeyboardEvent) => {
|
||||
const handleKeyPress: KeyboardEventHandler = async (event) => {
|
||||
if (event.key === KeyboardKey.Enter) {
|
||||
try {
|
||||
const decryptedCode = atob(confirmation)
|
||||
@@ -145,8 +148,8 @@ export const CloudBackupProvider: FunctionComponent<Props> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const handleChange = (event: Event) => {
|
||||
setConfirmation((event.target as HTMLInputElement).value)
|
||||
const handleChange: ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||
setConfirmation(event.target.value)
|
||||
}
|
||||
|
||||
const getIntegrationStatus = useCallback(async () => {
|
||||
@@ -219,3 +222,5 @@ export const CloudBackupProvider: FunctionComponent<Props> = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CloudBackupProvider
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import { CloudBackupProvider } from './CloudBackupProvider'
|
||||
import { useCallback, useEffect, useState } from 'preact/hooks'
|
||||
import CloudBackupProvider from './CloudBackupProvider'
|
||||
import { useCallback, useEffect, useState, FunctionComponent, Fragment } from 'react'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Subtitle,
|
||||
Text,
|
||||
Title,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import {
|
||||
FeatureStatus,
|
||||
FeatureIdentifier,
|
||||
@@ -16,11 +10,12 @@ import {
|
||||
MuteFailedCloudBackupsEmailsOption,
|
||||
SettingName,
|
||||
} from '@standardnotes/snjs'
|
||||
import { FunctionComponent } from 'preact'
|
||||
|
||||
import { Switch } from '@/Components/Switch/Switch'
|
||||
import Switch from '@/Components/Switch/Switch'
|
||||
import { convertStringifiedBooleanToBoolean } from '@/Utils'
|
||||
import { STRING_FAILED_TO_UPDATE_USER_SETTING } from '@/Strings'
|
||||
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
|
||||
|
||||
const providerData = [{ name: CloudProvider.Dropbox }, { name: CloudProvider.Google }, { name: CloudProvider.OneDrive }]
|
||||
|
||||
@@ -28,7 +23,7 @@ type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const CloudLink: FunctionComponent<Props> = ({ application }) => {
|
||||
const CloudLink: FunctionComponent<Props> = ({ application }) => {
|
||||
const [isEntitledToCloudBackups, setIsEntitledToCloudBackups] = useState(false)
|
||||
const [isFailedCloudBackupEmailMuted, setIsFailedCloudBackupEmailMuted] = useState(true)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
@@ -121,14 +116,14 @@ export const CloudLink: FunctionComponent<Props> = ({ application }) => {
|
||||
<HorizontalSeparator classes={`mt-3 mb-3 ${additionalClass}`} />
|
||||
<div>
|
||||
{providerData.map(({ name }) => (
|
||||
<>
|
||||
<Fragment key={name}>
|
||||
<CloudBackupProvider
|
||||
application={application}
|
||||
providerName={name}
|
||||
isEntitledToCloudBackups={isEntitledToCloudBackups}
|
||||
/>
|
||||
<HorizontalSeparator classes={`mt-3 mb-3 ${additionalClass}`} />
|
||||
</>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -155,3 +150,5 @@ export const CloudLink: FunctionComponent<Props> = ({ application }) => {
|
||||
</PreferencesGroup>
|
||||
)
|
||||
}
|
||||
|
||||
export default CloudLink
|
||||
|
||||
@@ -11,27 +11,22 @@ import {
|
||||
STRING_ENC_NOT_ENABLED,
|
||||
} from '@/Strings'
|
||||
import { BackupFile } from '@standardnotes/snjs'
|
||||
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
||||
import { ChangeEventHandler, MouseEventHandler, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { JSXInternal } from 'preact/src/jsx'
|
||||
import TargetedEvent = JSXInternal.TargetedEvent
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Title,
|
||||
Text,
|
||||
Subtitle,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
appState: AppState
|
||||
}
|
||||
|
||||
export const DataBackups = observer(({ application, appState }: Props) => {
|
||||
const DataBackups = ({ application, appState }: Props) => {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const [isImportDataLoading, setIsImportDataLoading] = useState(false)
|
||||
const {
|
||||
@@ -109,8 +104,8 @@ export const DataBackups = observer(({ application, appState }: Props) => {
|
||||
})
|
||||
}
|
||||
|
||||
const importFileSelected = async (event: TargetedEvent<HTMLInputElement, Event>) => {
|
||||
const { files } = event.target as HTMLInputElement
|
||||
const importFileSelected: ChangeEventHandler<HTMLInputElement> = async (event) => {
|
||||
const { files } = event.target
|
||||
|
||||
if (!files) {
|
||||
return
|
||||
@@ -136,7 +131,7 @@ export const DataBackups = observer(({ application, appState }: Props) => {
|
||||
}
|
||||
|
||||
// Whenever "Import Backup" is either clicked or key-pressed, proceed the import
|
||||
const handleImportFile = (event: TargetedEvent<HTMLSpanElement, Event> | KeyboardEvent) => {
|
||||
const handleImportFile: MouseEventHandler = (event) => {
|
||||
if (event instanceof KeyboardEvent) {
|
||||
const { code } = event
|
||||
|
||||
@@ -158,7 +153,7 @@ export const DataBackups = observer(({ application, appState }: Props) => {
|
||||
<PreferencesSegment>
|
||||
<Title>Data Backups</Title>
|
||||
|
||||
{!isDesktopApplication() && (
|
||||
{isDesktopApplication() && (
|
||||
<Text className="mb-3">
|
||||
Backups are automatically created on desktop and can be managed via the "Backups" top-level menu.
|
||||
</Text>
|
||||
@@ -183,10 +178,11 @@ export const DataBackups = observer(({ application, appState }: Props) => {
|
||||
|
||||
<Button variant="normal" onClick={downloadDataArchive} label="Download backup" className="mt-2" />
|
||||
</PreferencesSegment>
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
<PreferencesSegment>
|
||||
<Subtitle>Import a previously saved backup file</Subtitle>
|
||||
|
||||
<div class="flex flex-row items-center mt-3">
|
||||
<div className="flex flex-row items-center mt-3">
|
||||
<Button variant="normal" label="Import backup" onClick={handleImportFile} />
|
||||
<input type="file" ref={fileInputRef} onChange={importFileSelected} className="hidden" />
|
||||
{isImportDataLoading && <div className="sk-spinner normal info ml-4" />}
|
||||
@@ -195,4 +191,6 @@ export const DataBackups = observer(({ application, appState }: Props) => {
|
||||
</PreferencesGroup>
|
||||
</>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(DataBackups)
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import { convertStringifiedBooleanToBoolean, isDesktopApplication } from '@/Utils'
|
||||
import { STRING_FAILED_TO_UPDATE_USER_SETTING } from '@/Strings'
|
||||
import { useCallback, useEffect, useState } from 'preact/hooks'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Subtitle,
|
||||
Text,
|
||||
Title,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Dropdown, DropdownItem } from '@/Components/Dropdown/Dropdown'
|
||||
import { Switch } from '@/Components/Switch/Switch'
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import Dropdown from '@/Components/Dropdown/Dropdown'
|
||||
import { DropdownItem } from '@/Components/Dropdown/DropdownItem'
|
||||
import Switch from '@/Components/Switch/Switch'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import {
|
||||
FeatureStatus,
|
||||
FeatureIdentifier,
|
||||
@@ -20,12 +15,14 @@ import {
|
||||
MuteFailedBackupsEmailsOption,
|
||||
SettingName,
|
||||
} from '@standardnotes/snjs'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const EmailBackups = observer(({ application }: Props) => {
|
||||
const EmailBackups = ({ application }: Props) => {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [emailFrequency, setEmailFrequency] = useState<EmailBackupFrequency>(EmailBackupFrequency.Disabled)
|
||||
const [emailFrequencyOptions, setEmailFrequencyOptions] = useState<DropdownItem[]>([])
|
||||
@@ -132,7 +129,7 @@ export const EmailBackups = observer(({ application }: Props) => {
|
||||
</a>
|
||||
.
|
||||
</Text>
|
||||
<HorizontalSeparator classes="mt-3 mb-3" />
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
</>
|
||||
)}
|
||||
<div className={isEntitledToEmailBackups ? '' : 'faded cursor-default pointer-events-none'}>
|
||||
@@ -157,7 +154,7 @@ export const EmailBackups = observer(({ application }: Props) => {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<HorizontalSeparator classes="mt-5 mb-4" />
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
<Subtitle>Email preferences</Subtitle>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
@@ -177,4 +174,6 @@ export const EmailBackups = observer(({ application }: Props) => {
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(EmailBackups)
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { PreferencesSegment, Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { useCallback, useEffect, useMemo, useState, FunctionComponent } from 'react'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { FileBackupMetadataFile, FileBackupsConstantsV1, FileItem, FileHandleRead } from '@standardnotes/snjs'
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
import { EncryptionStatusItem } from '../../Security/Encryption'
|
||||
import { Icon } from '@/Components/Icon/Icon'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { StreamingFileApi } from '@standardnotes/filepicker'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { isHandlingBackupDrag } from '@/Utils/DragTypeCheck'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import EncryptionStatusItem from '../../Security/EncryptionStatusItem'
|
||||
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const BackupsDropZone: FunctionComponent<Props> = ({ application }) => {
|
||||
const BackupsDropZone: FunctionComponent<Props> = ({ application }) => {
|
||||
const [droppedFile, setDroppedFile] = useState<FileBackupMetadataFile | undefined>(undefined)
|
||||
const [decryptedFileItem, setDecryptedFileItem] = useState<FileItem | undefined>(undefined)
|
||||
const [binaryFile, setBinaryFile] = useState<FileHandleRead | undefined>(undefined)
|
||||
@@ -225,3 +225,5 @@ export const BackupsDropZone: FunctionComponent<Props> = ({ application }) => {
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default BackupsDropZone
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Subtitle, Title, Text } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { useMemo } from 'react'
|
||||
import BackupsDropZone from './BackupsDropZone'
|
||||
import FileBackupsDesktop from './FileBackupsDesktop'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
const FileBackupsCrossPlatform = ({ application }: Props) => {
|
||||
const fileBackupsService = useMemo(() => application.fileBackups, [application])
|
||||
|
||||
return fileBackupsService ? (
|
||||
<FileBackupsDesktop application={application} backupsService={fileBackupsService} />
|
||||
) : (
|
||||
<>
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>File Backups</Title>
|
||||
<Subtitle>Automatically save encrypted backups of files uploaded to any device to this computer.</Subtitle>
|
||||
<Text className="mt-3">To enable file backups, use the Standard Notes desktop application.</Text>
|
||||
</PreferencesSegment>
|
||||
<PreferencesSegment>
|
||||
<BackupsDropZone application={application} />
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FileBackupsCrossPlatform
|
||||
@@ -1,45 +1,24 @@
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Title,
|
||||
Text,
|
||||
Subtitle,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { Switch } from '@/Components/Switch/Switch'
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
import { EncryptionStatusItem } from '../../Security/Encryption'
|
||||
import { Icon } from '@/Components/Icon/Icon'
|
||||
import { BackupsDropZone } from './BackupsDropZone'
|
||||
import { Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import Switch from '@/Components/Switch/Switch'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import BackupsDropZone from './BackupsDropZone'
|
||||
import EncryptionStatusItem from '../../Security/EncryptionStatusItem'
|
||||
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
backupsService: NonNullable<WebApplication['fileBackups']>
|
||||
}
|
||||
|
||||
export const FileBackups = observer(({ application }: Props) => {
|
||||
const FileBackupsDesktop = ({ application, backupsService }: Props) => {
|
||||
const [backupsEnabled, setBackupsEnabled] = useState(false)
|
||||
const [backupsLocation, setBackupsLocation] = useState('')
|
||||
const backupsService = useMemo(() => application.fileBackups, [application])
|
||||
|
||||
if (!backupsService) {
|
||||
return (
|
||||
<>
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>File Backups</Title>
|
||||
<Subtitle>Automatically save encrypted backups of files uploaded to any device to this computer.</Subtitle>
|
||||
<Text className="mt-3">To enable file backups, use the Standard Notes desktop application.</Text>
|
||||
</PreferencesSegment>
|
||||
<PreferencesSegment>
|
||||
<BackupsDropZone application={application} />
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
void backupsService.isFilesBackupsEnabled().then(setBackupsEnabled)
|
||||
@@ -108,6 +87,7 @@ export const FileBackups = observer(({ application }: Props) => {
|
||||
icon={[<Icon type="attachment-file" className="min-w-5 min-h-5" />]}
|
||||
checkmark={false}
|
||||
/>
|
||||
|
||||
<div className="flex flex-row mt-5">
|
||||
<Button
|
||||
variant="normal"
|
||||
@@ -133,4 +113,6 @@ export const FileBackups = observer(({ application }: Props) => {
|
||||
</PreferencesGroup>
|
||||
</>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(FileBackupsDesktop)
|
||||
@@ -1,15 +1,10 @@
|
||||
import { FunctionComponent } from 'preact'
|
||||
import {
|
||||
Title,
|
||||
Subtitle,
|
||||
Text,
|
||||
LinkButton,
|
||||
PreferencesGroup,
|
||||
PreferencesPane,
|
||||
PreferencesSegment,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { Title, Subtitle, Text, LinkButton } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import PreferencesPane from '../PreferencesComponents/PreferencesPane'
|
||||
import PreferencesGroup from '../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
export const CloudLink: FunctionComponent = () => (
|
||||
const CloudLink: FunctionComponent = () => (
|
||||
<PreferencesPane>
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
@@ -83,3 +78,5 @@ export const CloudLink: FunctionComponent = () => (
|
||||
</PreferencesGroup>
|
||||
</PreferencesPane>
|
||||
)
|
||||
|
||||
export default CloudLink
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { DisplayStringForContentType } from '@standardnotes/snjs'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { Title, Text, Subtitle, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { Fragment, FunctionComponent } from 'react'
|
||||
import { Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { AnyExtension } from './AnyExtension'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
export const ConfirmCustomExtension: FunctionComponent<{
|
||||
const ConfirmCustomExtension: FunctionComponent<{
|
||||
component: AnyExtension
|
||||
callback: (confirmed: boolean) => void
|
||||
}> = ({ component, callback }) => {
|
||||
@@ -44,11 +45,11 @@ export const ConfirmCustomExtension: FunctionComponent<{
|
||||
return undefined
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Fragment key={field.value}>
|
||||
<Subtitle>{field.label}</Subtitle>
|
||||
<Text className={'wrap'}>{field.value}</Text>
|
||||
<div className="min-h-2" />
|
||||
</>
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -64,3 +65,5 @@ export const ConfirmCustomExtension: FunctionComponent<{
|
||||
</PreferencesSegment>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfirmCustomExtension
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { useState, useRef, useEffect } from 'preact/hooks'
|
||||
import { FunctionComponent, useState, useRef, useEffect } from 'react'
|
||||
|
||||
export const ExtensionInfoCell: FunctionComponent<{
|
||||
type Props = {
|
||||
extensionName: string
|
||||
changeName: (newName: string) => void
|
||||
isThirdParty: boolean
|
||||
}> = ({ extensionName, changeName, isThirdParty }) => {
|
||||
}
|
||||
|
||||
const ExtensionInfoCell: FunctionComponent<Props> = ({ extensionName, changeName, isThirdParty }) => {
|
||||
const [isRenaming, setIsRenaming] = useState(false)
|
||||
const [newExtensionName, setNewExtensionName] = useState<string>(extensionName)
|
||||
|
||||
@@ -42,7 +43,7 @@ export const ExtensionInfoCell: FunctionComponent<{
|
||||
<input
|
||||
ref={inputRef}
|
||||
disabled={!isRenaming || !renameable}
|
||||
autocomplete="off"
|
||||
autoComplete="off"
|
||||
className="flex-grow text-base font-bold no-border bg-default px-0 color-text"
|
||||
type="text"
|
||||
value={newExtensionName}
|
||||
@@ -71,3 +72,5 @@ export const ExtensionInfoCell: FunctionComponent<{
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ExtensionInfoCell
|
||||
|
||||
@@ -1,45 +1,32 @@
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { SNComponent } from '@standardnotes/snjs'
|
||||
import { PreferencesSegment, SubtitleLight } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Switch } from '@/Components/Switch/Switch'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { useState } from 'preact/hooks'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { ExtensionInfoCell } from './ExtensionInfoCell'
|
||||
import { AnyExtension } from './AnyExtension'
|
||||
import { FunctionComponent, useState } from 'react'
|
||||
import { ComponentMutator, SNComponent } from '@standardnotes/snjs'
|
||||
import { SubtitleLight } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import Switch from '@/Components/Switch/Switch'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import ExtensionInfoCell from './ExtensionInfoCell'
|
||||
import { ExtensionItemProps } from './ExtensionItemProps'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
const UseHosted: FunctionComponent<{
|
||||
offlineOnly: boolean
|
||||
toggleOfllineOnly: () => void
|
||||
}> = ({ offlineOnly, toggleOfllineOnly }) => (
|
||||
toggleOfflineOnly: () => void
|
||||
}> = ({ offlineOnly, toggleOfflineOnly }) => (
|
||||
<div className="flex flex-row">
|
||||
<SubtitleLight className="flex-grow">Use hosted when local is unavailable</SubtitleLight>
|
||||
<Switch onChange={toggleOfllineOnly} checked={!offlineOnly} />
|
||||
<Switch onChange={toggleOfflineOnly} checked={!offlineOnly} />
|
||||
</div>
|
||||
)
|
||||
|
||||
export interface ExtensionItemProps {
|
||||
application: WebApplication
|
||||
extension: AnyExtension
|
||||
first: boolean
|
||||
latestVersion: string | undefined
|
||||
uninstall: (extension: AnyExtension) => void
|
||||
toggleActivate?: (extension: AnyExtension) => void
|
||||
}
|
||||
|
||||
export const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({ application, extension, uninstall }) => {
|
||||
const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({ application, extension, uninstall }) => {
|
||||
const [offlineOnly, setOfflineOnly] = useState(extension instanceof SNComponent ? extension.offlineOnly : false)
|
||||
const [extensionName, setExtensionName] = useState(extension.displayName)
|
||||
|
||||
const toggleOffllineOnly = () => {
|
||||
const toggleOfflineOnly = () => {
|
||||
const newOfflineOnly = !offlineOnly
|
||||
setOfflineOnly(newOfflineOnly)
|
||||
application.mutator
|
||||
.changeAndSaveItem(extension, (m: any) => {
|
||||
if (m.content == undefined) {
|
||||
m.content = {}
|
||||
}
|
||||
m.content.offlineOnly = newOfflineOnly
|
||||
.changeAndSaveItem<ComponentMutator>(extension, (mutator) => {
|
||||
mutator.offlineOnly = newOfflineOnly
|
||||
})
|
||||
.then((item) => {
|
||||
const component = item as SNComponent
|
||||
@@ -53,11 +40,8 @@ export const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({ applicati
|
||||
const changeExtensionName = (newName: string) => {
|
||||
setExtensionName(newName)
|
||||
application.mutator
|
||||
.changeAndSaveItem(extension, (m: any) => {
|
||||
if (m.content == undefined) {
|
||||
m.content = {}
|
||||
}
|
||||
m.content.name = newName
|
||||
.changeAndSaveItem<ComponentMutator>(extension, (mutator) => {
|
||||
mutator.name = newName
|
||||
})
|
||||
.then((item) => {
|
||||
const component = item as SNComponent
|
||||
@@ -76,9 +60,7 @@ export const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({ applicati
|
||||
|
||||
<div className="min-h-2" />
|
||||
|
||||
{isThirParty && localInstallable && (
|
||||
<UseHosted offlineOnly={offlineOnly} toggleOfllineOnly={toggleOffllineOnly} />
|
||||
)}
|
||||
{isThirParty && localInstallable && <UseHosted offlineOnly={offlineOnly} toggleOfflineOnly={toggleOfflineOnly} />}
|
||||
|
||||
<>
|
||||
<div className="min-h-2" />
|
||||
@@ -94,3 +76,5 @@ export const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({ applicati
|
||||
</PreferencesSegment>
|
||||
)
|
||||
}
|
||||
|
||||
export default ExtensionItem
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { AnyExtension } from './AnyExtension'
|
||||
|
||||
export interface ExtensionItemProps {
|
||||
application: WebApplication
|
||||
extension: AnyExtension
|
||||
first: boolean
|
||||
latestVersion: string | undefined
|
||||
uninstall: (extension: AnyExtension) => void
|
||||
toggleActivate?: (extension: AnyExtension) => void
|
||||
}
|
||||
@@ -1,24 +1,26 @@
|
||||
import { ButtonType, ContentType, SNComponent } from '@standardnotes/snjs'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { DecoratedInput } from '@/Components/Input/DecoratedInput'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import DecoratedInput from '@/Components/Input/DecoratedInput'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { Title, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { useEffect, useRef, useState } from 'preact/hooks'
|
||||
import { FunctionComponent, useEffect, useRef, useState } from 'react'
|
||||
import { Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ExtensionsLatestVersions } from './ExtensionsLatestVersions'
|
||||
import { ExtensionItem } from './ExtensionItem'
|
||||
import { ConfirmCustomExtension } from './ConfirmCustomExtension'
|
||||
import ExtensionItem from './ExtensionItem'
|
||||
import ConfirmCustomExtension from './ConfirmCustomExtension'
|
||||
import { AnyExtension } from './AnyExtension'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
const loadExtensions = (application: WebApplication) =>
|
||||
application.items.getItems([ContentType.ActionsExtension, ContentType.Component, ContentType.Theme]) as AnyExtension[]
|
||||
|
||||
export const Extensions: FunctionComponent<{
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
extensionsLatestVersions: ExtensionsLatestVersions
|
||||
className?: string
|
||||
}> = observer(({ application, extensionsLatestVersions, className = '' }) => {
|
||||
}
|
||||
|
||||
const Extensions: FunctionComponent<Props> = ({ application, extensionsLatestVersions, className = '' }) => {
|
||||
const [customUrl, setCustomUrl] = useState('')
|
||||
const [confirmableExtension, setConfirmableExtension] = useState<AnyExtension | undefined>(undefined)
|
||||
const [extensions, setExtensions] = useState(loadExtensions(application))
|
||||
@@ -134,4 +136,6 @@ export const Extensions: FunctionComponent<{
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(Extensions)
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import { Dropdown, DropdownItem } from '@/Components/Dropdown/Dropdown'
|
||||
import Dropdown from '@/Components/Dropdown/Dropdown'
|
||||
import { DropdownItem } from '@/Components/Dropdown/DropdownItem'
|
||||
import { FeatureIdentifier, PrefKey, ComponentArea, ComponentMutator, SNComponent } from '@standardnotes/snjs'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Subtitle,
|
||||
Text,
|
||||
Title,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
import { Switch } from '@/Components/Switch/Switch'
|
||||
import { FunctionComponent, useEffect, useState } from 'react'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import Switch from '@/Components/Switch/Switch'
|
||||
import { PLAIN_EDITOR_NAME } from '@/Constants'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -47,7 +43,7 @@ const getDefaultEditor = (application: WebApplication) => {
|
||||
return application.componentManager.componentsForArea(ComponentArea.Editor).filter((e) => e.isDefaultEditor())[0]
|
||||
}
|
||||
|
||||
export const Defaults: FunctionComponent<Props> = ({ application }) => {
|
||||
const Defaults: FunctionComponent<Props> = ({ application }) => {
|
||||
const [editorItems, setEditorItems] = useState<DropdownItem[]>([])
|
||||
const [defaultEditorValue, setDefaultEditorValue] = useState(
|
||||
() => getDefaultEditor(application)?.package_info?.identifier || 'plain-editor',
|
||||
@@ -123,7 +119,7 @@ export const Defaults: FunctionComponent<Props> = ({ application }) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<HorizontalSeparator classes="mt-5 mb-3" />
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<Subtitle>Spellcheck</Subtitle>
|
||||
@@ -134,7 +130,7 @@ export const Defaults: FunctionComponent<Props> = ({ application }) => {
|
||||
</div>
|
||||
<Switch onChange={toggleSpellcheck} checked={spellcheck} />
|
||||
</div>
|
||||
<HorizontalSeparator classes="mt-5 mb-3" />
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<Subtitle>Add all parent tags when adding a nested tag to a note</Subtitle>
|
||||
@@ -152,3 +148,5 @@ export const Defaults: FunctionComponent<Props> = ({ application }) => {
|
||||
</PreferencesGroup>
|
||||
)
|
||||
}
|
||||
|
||||
export default Defaults
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { PreferencesPane } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { ExtensionsLatestVersions } from '@/Components/Preferences/Panes/Extensions/ExtensionsLatestVersions'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Tools } from './Tools'
|
||||
import { Defaults } from './Defaults'
|
||||
import { LabsPane } from './Labs'
|
||||
import { Advanced } from '@/Components/Preferences/Panes/Account/Advanced'
|
||||
import Tools from './Tools'
|
||||
import Defaults from './Defaults'
|
||||
import LabsPane from './Labs'
|
||||
import Advanced from '@/Components/Preferences/Panes/Account/Advanced'
|
||||
import PreferencesPane from '../../PreferencesComponents/PreferencesPane'
|
||||
|
||||
interface GeneralProps {
|
||||
type Props = {
|
||||
appState: AppState
|
||||
application: WebApplication
|
||||
extensionsLatestVersions: ExtensionsLatestVersions
|
||||
}
|
||||
|
||||
export const General: FunctionComponent<GeneralProps> = observer(
|
||||
({ appState, application, extensionsLatestVersions }) => (
|
||||
<PreferencesPane>
|
||||
<Tools application={application} />
|
||||
<Defaults application={application} />
|
||||
<LabsPane application={application} />
|
||||
<Advanced application={application} appState={appState} extensionsLatestVersions={extensionsLatestVersions} />
|
||||
</PreferencesPane>
|
||||
),
|
||||
const General: FunctionComponent<Props> = ({ appState, application, extensionsLatestVersions }) => (
|
||||
<PreferencesPane>
|
||||
<Tools application={application} />
|
||||
<Defaults application={application} />
|
||||
<LabsPane application={application} />
|
||||
<Advanced application={application} appState={appState} extensionsLatestVersions={extensionsLatestVersions} />
|
||||
</PreferencesPane>
|
||||
)
|
||||
|
||||
export default observer(General)
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import { Switch } from '@/Components/Switch/Switch'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Subtitle,
|
||||
Text,
|
||||
Title,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import Switch from '@/Components/Switch/Switch'
|
||||
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { FeatureIdentifier, FeatureStatus, FindNativeFeature } from '@standardnotes/snjs'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { useCallback, useEffect, useState } from 'preact/hooks'
|
||||
import { Fragment, FunctionComponent, useCallback, useEffect, useState } from 'react'
|
||||
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
type ExperimentalFeatureItem = {
|
||||
identifier: FeatureIdentifier
|
||||
@@ -25,7 +20,7 @@ type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const LabsPane: FunctionComponent<Props> = ({ application }) => {
|
||||
const LabsPane: FunctionComponent<Props> = ({ application }) => {
|
||||
const [experimentalFeatures, setExperimentalFeatures] = useState<ExperimentalFeatureItem[]>([])
|
||||
|
||||
const reloadExperimentalFeatures = useCallback(() => {
|
||||
@@ -67,7 +62,7 @@ export const LabsPane: FunctionComponent<Props> = ({ application }) => {
|
||||
const showHorizontalSeparator = experimentalFeatures.length > 1 && index !== experimentalFeatures.length - 1
|
||||
|
||||
return (
|
||||
<>
|
||||
<Fragment key={identifier}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<Subtitle>{name}</Subtitle>
|
||||
@@ -76,7 +71,7 @@ export const LabsPane: FunctionComponent<Props> = ({ application }) => {
|
||||
<Switch onChange={toggleFeature} checked={isEnabled} />
|
||||
</div>
|
||||
{showHorizontalSeparator && <HorizontalSeparator classes="mt-5 mb-3" />}
|
||||
</>
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
{experimentalFeatures.length === 0 && (
|
||||
@@ -91,3 +86,5 @@ export const LabsPane: FunctionComponent<Props> = ({ application }) => {
|
||||
</PreferencesGroup>
|
||||
)
|
||||
}
|
||||
|
||||
export default LabsPane
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
import { Switch } from '@/Components/Switch/Switch'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Subtitle,
|
||||
Text,
|
||||
Title,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import Switch from '@/Components/Switch/Switch'
|
||||
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { PrefKey } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionalComponent } from 'preact'
|
||||
import { useState } from 'preact/hooks'
|
||||
import { FunctionComponent, useState } from 'react'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const Tools: FunctionalComponent<Props> = observer(({ application }: Props) => {
|
||||
const Tools: FunctionComponent<Props> = ({ application }: Props) => {
|
||||
const [monospaceFont, setMonospaceFont] = useState(() =>
|
||||
application.getPreference(PrefKey.EditorMonospaceEnabled, true),
|
||||
)
|
||||
@@ -47,7 +42,7 @@ export const Tools: FunctionalComponent<Props> = observer(({ application }: Prop
|
||||
</div>
|
||||
<Switch onChange={toggleMonospaceFont} checked={monospaceFont} />
|
||||
</div>
|
||||
<HorizontalSeparator classes="mt-5 mb-3" />
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<Subtitle>Margin Resizers</Subtitle>
|
||||
@@ -59,4 +54,6 @@ export const Tools: FunctionalComponent<Props> = observer(({ application }: Prop
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(Tools)
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { FunctionComponent } from 'preact'
|
||||
import {
|
||||
Title,
|
||||
Subtitle,
|
||||
Text,
|
||||
LinkButton,
|
||||
PreferencesGroup,
|
||||
PreferencesPane,
|
||||
PreferencesSegment,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { Title, Subtitle, Text, LinkButton } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import PreferencesPane from '../PreferencesComponents/PreferencesPane'
|
||||
import PreferencesGroup from '../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
export const HelpAndFeedback: FunctionComponent = () => (
|
||||
const HelpAndFeedback: FunctionComponent = () => (
|
||||
<PreferencesPane>
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
@@ -26,6 +22,7 @@ export const HelpAndFeedback: FunctionComponent = () => (
|
||||
</a>
|
||||
</Text>
|
||||
</PreferencesSegment>
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
<PreferencesSegment>
|
||||
<Subtitle>Can I collaborate with others on a note?</Subtitle>
|
||||
<Text>
|
||||
@@ -34,6 +31,7 @@ export const HelpAndFeedback: FunctionComponent = () => (
|
||||
conflicts, which may result in the duplication of notes.
|
||||
</Text>
|
||||
</PreferencesSegment>
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
<PreferencesSegment>
|
||||
<Subtitle>Can I use Standard Notes totally offline?</Subtitle>
|
||||
<Text>
|
||||
@@ -44,6 +42,7 @@ export const HelpAndFeedback: FunctionComponent = () => (
|
||||
</a>
|
||||
</Text>
|
||||
</PreferencesSegment>
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
<PreferencesSegment>
|
||||
<Subtitle>Can’t find your question here?</Subtitle>
|
||||
<LinkButton className="mt-3" label="Open FAQ" link="https://standardnotes.com/help" />
|
||||
@@ -83,3 +82,5 @@ export const HelpAndFeedback: FunctionComponent = () => (
|
||||
</PreferencesGroup>
|
||||
</PreferencesPane>
|
||||
)
|
||||
|
||||
export default HelpAndFeedback
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesPane,
|
||||
PreferencesSegment,
|
||||
Title,
|
||||
Subtitle,
|
||||
Text,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Title, Subtitle, Text } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { ButtonType, ListedAccount } from '@standardnotes/snjs'
|
||||
import { useCallback, useEffect, useState } from 'preact/hooks'
|
||||
import { ListedAccountItem } from './BlogItem'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import ListedAccountItem from './ListedAccountItem'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import PreferencesPane from '../../PreferencesComponents/PreferencesPane'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const Listed = observer(({ application }: Props) => {
|
||||
const Listed = ({ application }: Props) => {
|
||||
const [accounts, setAccounts] = useState<ListedAccount[]>([])
|
||||
const [requestingAccount, setRequestingAccount] = useState<boolean>()
|
||||
|
||||
@@ -92,19 +89,24 @@ export const Listed = observer(({ application }: Props) => {
|
||||
</Text>
|
||||
</PreferencesSegment>
|
||||
{application.getUser() && (
|
||||
<PreferencesSegment>
|
||||
<Subtitle>Get Started</Subtitle>
|
||||
<Text>Create a free Listed author account to get started.</Text>
|
||||
<Button
|
||||
className="mt-3"
|
||||
variant="normal"
|
||||
disabled={requestingAccount}
|
||||
label={requestingAccount ? 'Creating account...' : 'Create new author'}
|
||||
onClick={registerNewAccount}
|
||||
/>
|
||||
</PreferencesSegment>
|
||||
<>
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
<PreferencesSegment>
|
||||
<Subtitle>Get Started</Subtitle>
|
||||
<Text>Create a free Listed author account to get started.</Text>
|
||||
<Button
|
||||
className="mt-3"
|
||||
variant="normal"
|
||||
disabled={requestingAccount}
|
||||
label={requestingAccount ? 'Creating account...' : 'Create new author'}
|
||||
onClick={registerNewAccount}
|
||||
/>
|
||||
</PreferencesSegment>
|
||||
</>
|
||||
)}
|
||||
</PreferencesGroup>
|
||||
</PreferencesPane>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(Listed)
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
import { LinkButton, Subtitle } from '@/Components/Preferences/PreferencesComponents'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import { LinkButton, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { ListedAccount, ListedAccountInfo } from '@standardnotes/snjs'
|
||||
import { FunctionalComponent } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import { FunctionComponent, useEffect, useState } from 'react'
|
||||
|
||||
type Props = {
|
||||
account: ListedAccount
|
||||
@@ -11,7 +10,7 @@ type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const ListedAccountItem: FunctionalComponent<Props> = ({ account, showSeparator, application }) => {
|
||||
const ListedAccountItem: FunctionComponent<Props> = ({ account, showSeparator, application }) => {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [accountInfo, setAccountInfo] = useState<ListedAccountInfo>()
|
||||
|
||||
@@ -42,3 +41,5 @@ export const ListedAccountItem: FunctionalComponent<Props> = ({ account, showSep
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ListedAccountItem
|
||||
@@ -1,54 +1,15 @@
|
||||
import { Icon } from '@/Components/Icon/Icon'
|
||||
import { STRING_E2E_ENABLED, STRING_ENC_NOT_ENABLED, STRING_LOCAL_ENC_ENABLED } from '@/Strings'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ComponentChild, FunctionComponent } from 'preact'
|
||||
import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { Title, Text } from '../../PreferencesComponents/Content'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
import EncryptionEnabled from './EncryptionEnabled'
|
||||
|
||||
const formatCount = (count: number, itemType: string) => `${count} / ${count} ${itemType}`
|
||||
type Props = { appState: AppState }
|
||||
|
||||
export const EncryptionStatusItem: FunctionComponent<{
|
||||
icon: ComponentChild
|
||||
status: string
|
||||
checkmark?: boolean
|
||||
}> = ({ icon, status, checkmark = true }) => (
|
||||
<div className="w-full rounded py-1.5 px-3 text-input my-1 min-h-8 flex flex-row items-center bg-contrast no-border focus-within:ring-info">
|
||||
{icon}
|
||||
<div className="min-w-3 min-h-1" />
|
||||
<div className="flex-grow color-text text-sm">{status}</div>
|
||||
<div className="min-w-3 min-h-1" />
|
||||
{checkmark && <Icon className="success min-w-4 min-h-4" type="check-bold" />}
|
||||
</div>
|
||||
)
|
||||
|
||||
const EncryptionEnabled: FunctionComponent<{ appState: AppState }> = observer(({ appState }) => {
|
||||
const count = appState.accountMenu.structuredNotesAndTagsCount
|
||||
const notes = formatCount(count.notes, 'notes')
|
||||
const tags = formatCount(count.tags, 'tags')
|
||||
const archived = formatCount(count.archived, 'archived notes')
|
||||
const deleted = formatCount(count.deleted, 'trashed notes')
|
||||
|
||||
const noteIcon = <Icon type="rich-text" className="min-w-5 min-h-5" />
|
||||
const tagIcon = <Icon type="hashtag" className="min-w-5 min-h-5" />
|
||||
const archiveIcon = <Icon type="archive" className="min-w-5 min-h-5" />
|
||||
const trashIcon = <Icon type="trash" className="min-w-5 min-h-5" />
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row items-start pb-1 pt-1.5">
|
||||
<EncryptionStatusItem status={notes} icon={[noteIcon]} />
|
||||
<div className="min-w-3" />
|
||||
<EncryptionStatusItem status={tags} icon={[tagIcon]} />
|
||||
</div>
|
||||
<div className="flex flex-row items-start">
|
||||
<EncryptionStatusItem status={archived} icon={[archiveIcon]} />
|
||||
<div className="min-w-3" />
|
||||
<EncryptionStatusItem status={deleted} icon={[trashIcon]} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
export const Encryption: FunctionComponent<{ appState: AppState }> = observer(({ appState }) => {
|
||||
const Encryption: FunctionComponent<Props> = ({ appState }) => {
|
||||
const app = appState.application
|
||||
const hasUser = app.hasAccount()
|
||||
const hasPasscode = app.hasPasscode()
|
||||
@@ -70,4 +31,6 @@ export const Encryption: FunctionComponent<{ appState: AppState }> = observer(({
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(Encryption)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'react'
|
||||
import EncryptionStatusItem from './EncryptionStatusItem'
|
||||
import { formatCount } from './formatCount'
|
||||
|
||||
type Props = {
|
||||
appState: AppState
|
||||
}
|
||||
|
||||
const EncryptionEnabled: FunctionComponent<Props> = ({ appState }) => {
|
||||
const count = appState.accountMenu.structuredNotesAndTagsCount
|
||||
const notes = formatCount(count.notes, 'notes')
|
||||
const tags = formatCount(count.tags, 'tags')
|
||||
const archived = formatCount(count.archived, 'archived notes')
|
||||
const deleted = formatCount(count.deleted, 'trashed notes')
|
||||
|
||||
const noteIcon = <Icon type="rich-text" className="min-w-5 min-h-5" />
|
||||
const tagIcon = <Icon type="hashtag" className="min-w-5 min-h-5" />
|
||||
const archiveIcon = <Icon type="archive" className="min-w-5 min-h-5" />
|
||||
const trashIcon = <Icon type="trash" className="min-w-5 min-h-5" />
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row items-start pb-1 pt-1.5">
|
||||
<EncryptionStatusItem status={notes} icon={noteIcon} />
|
||||
<div className="min-w-3" />
|
||||
<EncryptionStatusItem status={tags} icon={tagIcon} />
|
||||
</div>
|
||||
<div className="flex flex-row items-start">
|
||||
<EncryptionStatusItem status={archived} icon={archiveIcon} />
|
||||
<div className="min-w-3" />
|
||||
<EncryptionStatusItem status={deleted} icon={trashIcon} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default observer(EncryptionEnabled)
|
||||
@@ -0,0 +1,20 @@
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { FunctionComponent, ReactNode } from 'react'
|
||||
|
||||
type Props = {
|
||||
icon: ReactNode
|
||||
status: string
|
||||
checkmark?: boolean
|
||||
}
|
||||
|
||||
const EncryptionStatusItem: FunctionComponent<Props> = ({ icon, status, checkmark = true }) => (
|
||||
<div className="w-full rounded py-1.5 px-3 text-input my-1 min-h-8 flex flex-row items-center bg-contrast no-border focus-within:ring-info">
|
||||
{icon}
|
||||
<div className="min-w-3 min-h-1" />
|
||||
<div className="flex-grow color-text text-sm">{status}</div>
|
||||
<div className="min-w-3 min-h-1" />
|
||||
{checkmark && <Icon className="success min-w-4 min-h-4" type="check-bold" />}
|
||||
</div>
|
||||
)
|
||||
|
||||
export default EncryptionStatusItem
|
||||
@@ -1,26 +1,21 @@
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Text,
|
||||
Title,
|
||||
Subtitle,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Fragment, FunctionComponent, useState } from 'react'
|
||||
import { Text, Title, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import {
|
||||
ButtonType,
|
||||
ClientDisplayableError,
|
||||
DisplayStringForContentType,
|
||||
EncryptedItemInterface,
|
||||
} from '@standardnotes/snjs'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
import { useState } from 'preact/hooks'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
|
||||
type Props = { appState: AppState }
|
||||
|
||||
export const ErroredItems: FunctionComponent<Props> = observer(({ appState }: Props) => {
|
||||
const ErroredItems: FunctionComponent<Props> = ({ appState }: Props) => {
|
||||
const app = appState.application
|
||||
|
||||
const [erroredItems, setErroredItems] = useState(app.items.invalidItems)
|
||||
@@ -96,7 +91,7 @@ export const ErroredItems: FunctionComponent<Props> = observer(({ appState }: Pr
|
||||
|
||||
{erroredItems.map((item, index) => {
|
||||
return (
|
||||
<>
|
||||
<Fragment key={item.uuid}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<Subtitle>{`${getContentTypeDisplay(item)} created on ${item.createdAtString}`}</Subtitle>
|
||||
@@ -134,10 +129,12 @@ export const ErroredItems: FunctionComponent<Props> = observer(({ appState }: Pr
|
||||
</div>
|
||||
</div>
|
||||
{index < erroredItems.length - 1 && <HorizontalSeparator classes="mt-5 mb-3" />}
|
||||
</>
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(ErroredItems)
|
||||
|
||||
@@ -10,23 +10,22 @@ import {
|
||||
} from '@/Strings'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { preventRefreshing } from '@/Utils'
|
||||
import { JSXInternal } from 'preact/src/jsx'
|
||||
import TargetedEvent = JSXInternal.TargetedEvent
|
||||
import TargetedMouseEvent = JSXInternal.TargetedMouseEvent
|
||||
import { alertDialog } from '@/Services/AlertService'
|
||||
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
||||
import { ChangeEventHandler, FormEvent, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { ApplicationEvent } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { PreferencesSegment, Title, Text, PreferencesGroup } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { Title, Text } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
appState: AppState
|
||||
}
|
||||
|
||||
export const PasscodeLock = observer(({ application, appState }: Props) => {
|
||||
const PasscodeLock = ({ application, appState }: Props) => {
|
||||
const keyStorageInfo = StringUtils.keyStorageInfo(application)
|
||||
const passcodeAutoLockOptions = application.getAutolockService().getAutoLockIntervalOptions()
|
||||
|
||||
@@ -34,8 +33,8 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
|
||||
|
||||
const passcodeInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const [passcode, setPasscode] = useState<string | undefined>(undefined)
|
||||
const [passcodeConfirmation, setPasscodeConfirmation] = useState<string | undefined>(undefined)
|
||||
const [passcode, setPasscode] = useState<string>()
|
||||
const [passcodeConfirmation, setPasscodeConfirmation] = useState<string>()
|
||||
const [selectedAutoLockInterval, setSelectedAutoLockInterval] = useState<unknown>(null)
|
||||
const [isPasscodeFocused, setIsPasscodeFocused] = useState(false)
|
||||
const [showPasscodeForm, setShowPasscodeForm] = useState(false)
|
||||
@@ -93,17 +92,17 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
|
||||
})
|
||||
}
|
||||
|
||||
const handlePasscodeChange = (event: TargetedEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target as HTMLInputElement
|
||||
const handlePasscodeChange: ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||
const { value } = event.target
|
||||
setPasscode(value)
|
||||
}
|
||||
|
||||
const handleConfirmPasscodeChange = (event: TargetedEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target as HTMLInputElement
|
||||
const handleConfirmPasscodeChange: ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||
const { value } = event.target
|
||||
setPasscodeConfirmation(value)
|
||||
}
|
||||
|
||||
const submitPasscodeForm = async (event: TargetedEvent<HTMLFormElement> | TargetedMouseEvent<HTMLButtonElement>) => {
|
||||
const submitPasscodeForm = async (event: MouseEvent | FormEvent) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (!passcode || passcode.length === 0) {
|
||||
@@ -167,6 +166,12 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
|
||||
}
|
||||
}, [application])
|
||||
|
||||
const cancelPasscodeForm = () => {
|
||||
setShowPasscodeForm(false)
|
||||
setPasscode(undefined)
|
||||
setPasscodeConfirmation(undefined)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PreferencesGroup>
|
||||
@@ -197,20 +202,20 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
|
||||
className="sk-input contrast"
|
||||
type="password"
|
||||
ref={passcodeInputRef}
|
||||
value={passcode}
|
||||
value={passcode ? passcode : ''}
|
||||
onChange={handlePasscodeChange}
|
||||
placeholder="Passcode"
|
||||
/>
|
||||
<input
|
||||
className="sk-input contrast"
|
||||
type="password"
|
||||
value={passcodeConfirmation}
|
||||
value={passcodeConfirmation ? passcodeConfirmation : ''}
|
||||
onChange={handleConfirmPasscodeChange}
|
||||
placeholder="Confirm Passcode"
|
||||
/>
|
||||
<div className="min-h-2" />
|
||||
<Button variant="primary" onClick={submitPasscodeForm} label="Set Passcode" className="mr-3" />
|
||||
<Button variant="normal" onClick={() => setShowPasscodeForm(false)} label="Cancel" />
|
||||
<Button variant="normal" onClick={cancelPasscodeForm} label="Cancel" />
|
||||
</form>
|
||||
)}
|
||||
|
||||
@@ -237,6 +242,7 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
|
||||
{passcodeAutoLockOptions.map((option) => {
|
||||
return (
|
||||
<a
|
||||
key={option.value}
|
||||
className={`sk-a info mr-3 ${option.value === selectedAutoLockInterval ? 'boxed' : ''}`}
|
||||
onClick={() => selectAutoLockInterval(option.value)}
|
||||
>
|
||||
@@ -251,4 +257,6 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(PasscodeLock)
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
import { Switch } from '@/Components/Switch/Switch'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Subtitle,
|
||||
Text,
|
||||
Title,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import Switch from '@/Components/Switch/Switch'
|
||||
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { MuteSignInEmailsOption, LogSessionUserAgentOption, SettingName } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionalComponent } from 'preact'
|
||||
import { useCallback, useEffect, useState } from 'preact/hooks'
|
||||
import { FunctionComponent, useCallback, useEffect, useState } from 'react'
|
||||
import { STRING_FAILED_TO_UPDATE_USER_SETTING } from '@/Strings'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const Privacy: FunctionalComponent<Props> = observer(({ application }: Props) => {
|
||||
const Privacy: FunctionComponent<Props> = ({ application }: Props) => {
|
||||
const [signInEmailsMutedValue, setSignInEmailsMutedValue] = useState<MuteSignInEmailsOption>(
|
||||
MuteSignInEmailsOption.NotMuted,
|
||||
)
|
||||
const [sessionUaLoggingValue, setSessionUaLoggingValue] = useState<LogSessionUserAgentOption>(
|
||||
LogSessionUserAgentOption.Enabled,
|
||||
)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
const updateSetting = async (settingName: SettingName, payload: string): Promise<boolean> => {
|
||||
try {
|
||||
@@ -110,7 +105,7 @@ export const Privacy: FunctionalComponent<Props> = observer(({ application }: Pr
|
||||
</Text>
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<div className={'sk-spinner info small'} />
|
||||
<div className={'sk-spinner info small flex-shrink-0 ml-2'} />
|
||||
) : (
|
||||
<Switch
|
||||
onChange={toggleMuteSignInEmails}
|
||||
@@ -118,7 +113,7 @@ export const Privacy: FunctionalComponent<Props> = observer(({ application }: Pr
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<HorizontalSeparator classes="mt-5 mb-3" />
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<Subtitle>Session user agent logging</Subtitle>
|
||||
@@ -129,7 +124,7 @@ export const Privacy: FunctionalComponent<Props> = observer(({ application }: Pr
|
||||
</Text>
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<div className={'sk-spinner info small'} />
|
||||
<div className={'sk-spinner info small flex-shrink-0 ml-2'} />
|
||||
) : (
|
||||
<Switch
|
||||
onChange={toggleSessionLogging}
|
||||
@@ -141,4 +136,6 @@ export const Privacy: FunctionalComponent<Props> = observer(({ application }: Pr
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(Privacy)
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { FunctionalComponent } from 'preact'
|
||||
import { useCallback, useState, useEffect } from 'preact/hooks'
|
||||
import { FunctionComponent, useCallback, useState, useEffect } from 'react'
|
||||
import { ApplicationEvent } from '@standardnotes/snjs'
|
||||
import { isSameDay } from '@/Utils'
|
||||
import { PreferencesGroup, PreferencesSegment, Title, Text } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
import { Title, Text } from '../../PreferencesComponents/Content'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const Protections: FunctionalComponent<Props> = ({ application }) => {
|
||||
const Protections: FunctionComponent<Props> = ({ application }) => {
|
||||
const enableProtections = () => {
|
||||
application.clearProtectionSession().catch(console.error)
|
||||
}
|
||||
@@ -88,3 +89,5 @@ export const Protections: FunctionalComponent<Props> = ({ application }) => {
|
||||
</PreferencesGroup>
|
||||
)
|
||||
}
|
||||
|
||||
export default Protections
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { PreferencesPane } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { TwoFactorAuthWrapper } from '../TwoFactorAuth/TwoFactorAuthWrapper'
|
||||
import { FunctionComponent } from 'react'
|
||||
import TwoFactorAuthWrapper from '../TwoFactorAuth/TwoFactorAuthWrapper'
|
||||
import { MfaProps } from '../TwoFactorAuth/MfaProps'
|
||||
import { Encryption } from './Encryption'
|
||||
import { PasscodeLock } from './PasscodeLock'
|
||||
import { Privacy } from './Privacy'
|
||||
import { Protections } from './Protections'
|
||||
import { ErroredItems } from './ErroredItems'
|
||||
import Encryption from './Encryption'
|
||||
import PasscodeLock from './PasscodeLock'
|
||||
import Privacy from './Privacy'
|
||||
import Protections from './Protections'
|
||||
import ErroredItems from './ErroredItems'
|
||||
import PreferencesPane from '@/Components/Preferences/PreferencesComponents/PreferencesPane'
|
||||
|
||||
interface SecurityProps extends MfaProps {
|
||||
appState: AppState
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const securityPrefsHasBubble = (application: WebApplication): boolean => {
|
||||
return application.items.invalidItems.length > 0
|
||||
}
|
||||
|
||||
export const Security: FunctionComponent<SecurityProps> = (props) => (
|
||||
const Security: FunctionComponent<SecurityProps> = (props) => (
|
||||
<PreferencesPane>
|
||||
<Encryption appState={props.appState} />
|
||||
{props.application.items.invalidItems.length > 0 && <ErroredItems appState={props.appState} />}
|
||||
@@ -29,3 +25,5 @@ export const Security: FunctionComponent<SecurityProps> = (props) => (
|
||||
{props.application.getUser() && <Privacy application={props.application} />}
|
||||
</PreferencesPane>
|
||||
)
|
||||
|
||||
export default Security
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const formatCount = (count: number, itemType: string) => `${count} / ${count} ${itemType}`
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
|
||||
export const securityPrefsHasBubble = (application: WebApplication): boolean => {
|
||||
return application.items.invalidItems.length > 0
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
import { Icon } from '@/Components/Icon/Icon'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { useState, useRef, useEffect } from 'preact/hooks'
|
||||
import { FunctionComponent, useState, useRef, useEffect, MouseEventHandler } from 'react'
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
|
||||
const DisclosureIconButton: FunctionComponent<{
|
||||
type Props = {
|
||||
className?: string
|
||||
icon: IconType
|
||||
onMouseEnter?: any
|
||||
onMouseLeave?: any
|
||||
}> = ({ className = '', icon, onMouseEnter, onMouseLeave }) => (
|
||||
onMouseEnter?: MouseEventHandler<HTMLButtonElement>
|
||||
onMouseLeave?: MouseEventHandler<HTMLButtonElement>
|
||||
}
|
||||
|
||||
const DisclosureIconButton: FunctionComponent<Props> = ({ className = '', icon, onMouseEnter, onMouseLeave }) => (
|
||||
<DisclosureButton
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
@@ -26,7 +27,7 @@ const DisclosureIconButton: FunctionComponent<{
|
||||
* Note: it can be generalized but more use cases are required
|
||||
* @returns
|
||||
*/
|
||||
export const AuthAppInfoTooltip: FunctionComponent = () => {
|
||||
const AuthAppInfoTooltip: FunctionComponent = () => {
|
||||
const [isClicked, setClicked] = useState(false)
|
||||
const [isHover, setHover] = useState(false)
|
||||
const ref = useRef(null)
|
||||
@@ -61,3 +62,5 @@ py-1.5 px-2 absolute w-103 -top-10 -left-51`}
|
||||
</Disclosure>
|
||||
)
|
||||
}
|
||||
|
||||
export default AuthAppInfoTooltip
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { FunctionComponent } from 'react'
|
||||
|
||||
export const Bullet: FunctionComponent<{ className?: string }> = ({ className = '' }) => (
|
||||
type Props = {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const Bullet: FunctionComponent<Props> = ({ className = '' }) => (
|
||||
<div className={`min-w-1 min-h-1 rounded-full bg-inverted-default ${className} mr-2`} />
|
||||
)
|
||||
|
||||
export default Bullet
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { FunctionComponent, useState } from 'react'
|
||||
import IconButton from '@/Components/Button/IconButton'
|
||||
|
||||
import { IconButton } from '@/Components/Button/IconButton'
|
||||
type Props = {
|
||||
copyValue: string
|
||||
}
|
||||
|
||||
import { useState } from 'preact/hooks'
|
||||
|
||||
export const CopyButton: FunctionComponent<{ copyValue: string }> = ({ copyValue: secretKey }) => {
|
||||
const CopyButton: FunctionComponent<Props> = ({ copyValue: secretKey }) => {
|
||||
const [isCopied, setCopied] = useState(false)
|
||||
return (
|
||||
<IconButton
|
||||
@@ -19,3 +20,5 @@ export const CopyButton: FunctionComponent<{ copyValue: string }> = ({ copyValue
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default CopyButton
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { DecoratedInput } from '@/Components/Input/DecoratedInput'
|
||||
import { IconButton } from '@/Components/Button/IconButton'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import DecoratedInput from '@/Components/Input/DecoratedInput'
|
||||
import IconButton from '@/Components/Button/IconButton'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { CopyButton } from './CopyButton'
|
||||
import { Bullet } from './Bullet'
|
||||
import { FunctionComponent } from 'react'
|
||||
import CopyButton from './CopyButton'
|
||||
import Bullet from './Bullet'
|
||||
import { downloadSecretKey } from './download-secret-key'
|
||||
import { TwoFactorActivation } from './TwoFactorActivation'
|
||||
import {
|
||||
ModalDialog,
|
||||
ModalDialogButtons,
|
||||
ModalDialogDescription,
|
||||
ModalDialogLabel,
|
||||
} from '@/Components/Shared/ModalDialog'
|
||||
import ModalDialog from '@/Components/Shared/ModalDialog'
|
||||
import ModalDialogButtons from '@/Components/Shared/ModalDialogButtons'
|
||||
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
|
||||
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
|
||||
|
||||
export const SaveSecretKey: FunctionComponent<{
|
||||
type Props = {
|
||||
activation: TwoFactorActivation
|
||||
}> = observer(({ activation: act }) => {
|
||||
}
|
||||
|
||||
const SaveSecretKey: FunctionComponent<Props> = ({ activation: act }) => {
|
||||
const download = (
|
||||
<IconButton
|
||||
focusable={false}
|
||||
@@ -81,4 +81,6 @@ export const SaveSecretKey: FunctionComponent<{
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(SaveSecretKey)
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import QRCode from 'qrcode.react'
|
||||
import { DecoratedInput } from '@/Components/Input/DecoratedInput'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import DecoratedInput from '@/Components/Input/DecoratedInput'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { TwoFactorActivation } from './TwoFactorActivation'
|
||||
import { AuthAppInfoTooltip } from './AuthAppInfoPopup'
|
||||
import {
|
||||
ModalDialog,
|
||||
ModalDialogButtons,
|
||||
ModalDialogDescription,
|
||||
ModalDialogLabel,
|
||||
} from '@/Components/Shared/ModalDialog'
|
||||
import { CopyButton } from './CopyButton'
|
||||
import { Bullet } from './Bullet'
|
||||
import AuthAppInfoTooltip from './AuthAppInfoPopup'
|
||||
import ModalDialog from '@/Components/Shared/ModalDialog'
|
||||
import ModalDialogButtons from '@/Components/Shared/ModalDialogButtons'
|
||||
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
|
||||
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
|
||||
import CopyButton from './CopyButton'
|
||||
import Bullet from './Bullet'
|
||||
|
||||
export const ScanQRCode: FunctionComponent<{
|
||||
type Props = {
|
||||
activation: TwoFactorActivation
|
||||
}> = observer(({ activation: act }) => {
|
||||
}
|
||||
|
||||
const ScanQRCode: FunctionComponent<Props> = ({ activation: act }) => {
|
||||
return (
|
||||
<ModalDialog>
|
||||
<ModalDialogLabel closeDialog={act.cancelActivation}>Step 1 of 3 - Scan QR code</ModalDialogLabel>
|
||||
@@ -58,4 +58,6 @@ export const ScanQRCode: FunctionComponent<{
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(ScanQRCode)
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { TwoFactorActivation } from './TwoFactorActivation'
|
||||
import { SaveSecretKey } from './SaveSecretKey'
|
||||
import { ScanQRCode } from './ScanQRCode'
|
||||
import { Verification } from './Verification'
|
||||
import { TwoFactorSuccess } from './TwoFactorSuccess'
|
||||
import SaveSecretKey from './SaveSecretKey'
|
||||
import ScanQRCode from './ScanQRCode'
|
||||
import Verification from './Verification'
|
||||
import TwoFactorSuccess from './TwoFactorSuccess'
|
||||
|
||||
export const TwoFactorActivationView: FunctionComponent<{
|
||||
type Props = {
|
||||
activation: TwoFactorActivation
|
||||
}> = observer(({ activation: act }) => {
|
||||
}
|
||||
|
||||
const TwoFactorActivationView: FunctionComponent<Props> = ({ activation: act }) => {
|
||||
switch (act.activationStep) {
|
||||
case 'scan-qr-code':
|
||||
return <ScanQRCode activation={act} />
|
||||
@@ -19,4 +21,6 @@ export const TwoFactorActivationView: FunctionComponent<{
|
||||
case 'success':
|
||||
return <TwoFactorSuccess activation={act} />
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(TwoFactorActivationView)
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { Title, Text, PreferencesGroup, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Switch } from '@/Components/Switch/Switch'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { is2FAActivation, is2FADisabled, TwoFactorAuth } from './TwoFactorAuth'
|
||||
import { TwoFactorActivationView } from './TwoFactorActivationView'
|
||||
|
||||
const TwoFactorTitle: FunctionComponent<{ auth: TwoFactorAuth }> = observer(({ auth }) => {
|
||||
if (!auth.isLoggedIn()) {
|
||||
return <Title>Two-factor authentication not available</Title>
|
||||
}
|
||||
if (!auth.isMfaFeatureAvailable()) {
|
||||
return <Title>Two-factor authentication not available</Title>
|
||||
}
|
||||
return <Title>Two-factor authentication</Title>
|
||||
})
|
||||
|
||||
const TwoFactorDescription: FunctionComponent<{ auth: TwoFactorAuth }> = observer(({ auth }) => {
|
||||
if (!auth.isLoggedIn()) {
|
||||
return <Text>Sign in or register for an account to configure 2FA.</Text>
|
||||
}
|
||||
if (!auth.isMfaFeatureAvailable()) {
|
||||
return (
|
||||
<Text>
|
||||
A paid subscription plan is required to enable 2FA.{' '}
|
||||
<a target="_blank" href="https://standardnotes.com/features">
|
||||
Learn more
|
||||
</a>
|
||||
.
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
return <Text>An extra layer of security when logging in to your account.</Text>
|
||||
})
|
||||
|
||||
const TwoFactorSwitch: FunctionComponent<{ auth: TwoFactorAuth }> = observer(({ auth }) => {
|
||||
if (!(auth.isLoggedIn() && auth.isMfaFeatureAvailable())) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (auth.status === 'fetching') {
|
||||
return <div class="sk-spinner normal info" />
|
||||
}
|
||||
|
||||
return <Switch checked={!is2FADisabled(auth.status)} onChange={auth.toggle2FA} />
|
||||
})
|
||||
|
||||
export const TwoFactorAuthView: FunctionComponent<{
|
||||
auth: TwoFactorAuth
|
||||
}> = observer(({ auth }) => {
|
||||
return (
|
||||
<>
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex-grow flex flex-col">
|
||||
<TwoFactorTitle auth={auth} />
|
||||
<TwoFactorDescription auth={auth} />
|
||||
</div>
|
||||
<div className="flex flex-col justify-center items-center min-w-15">
|
||||
<TwoFactorSwitch auth={auth} />
|
||||
</div>
|
||||
</div>
|
||||
</PreferencesSegment>
|
||||
|
||||
{auth.errorMessage != null && (
|
||||
<PreferencesSegment>
|
||||
<Text className="color-danger">{auth.errorMessage}</Text>
|
||||
</PreferencesSegment>
|
||||
)}
|
||||
</PreferencesGroup>
|
||||
{auth.status !== 'fetching' && is2FAActivation(auth.status) && (
|
||||
<TwoFactorActivationView activation={auth.status} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,45 @@
|
||||
import { FunctionComponent } from 'react'
|
||||
import { Text } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { is2FAActivation, TwoFactorAuth } from '../TwoFactorAuth'
|
||||
import TwoFactorActivationView from '../TwoFactorActivationView'
|
||||
import TwoFactorTitle from './TwoFactorTitle'
|
||||
import TwoFactorDescription from './TwoFactorDescription'
|
||||
import TwoFactorSwitch from './TwoFactorSwitch'
|
||||
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
|
||||
|
||||
type Props = {
|
||||
auth: TwoFactorAuth
|
||||
}
|
||||
|
||||
const TwoFactorAuthView: FunctionComponent<Props> = ({ auth }) => {
|
||||
return (
|
||||
<>
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex-grow flex flex-col">
|
||||
<TwoFactorTitle auth={auth} />
|
||||
<TwoFactorDescription auth={auth} />
|
||||
</div>
|
||||
<div className="flex flex-col justify-center items-center min-w-15">
|
||||
<TwoFactorSwitch auth={auth} />
|
||||
</div>
|
||||
</div>
|
||||
</PreferencesSegment>
|
||||
|
||||
{auth.errorMessage != null && (
|
||||
<PreferencesSegment>
|
||||
<Text className="color-danger">{auth.errorMessage}</Text>
|
||||
</PreferencesSegment>
|
||||
)}
|
||||
</PreferencesGroup>
|
||||
{auth.status !== 'fetching' && is2FAActivation(auth.status) && (
|
||||
<TwoFactorActivationView activation={auth.status} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default observer(TwoFactorAuthView)
|
||||
@@ -0,0 +1,28 @@
|
||||
import { FunctionComponent } from 'react'
|
||||
import { Text } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { TwoFactorAuth } from '../TwoFactorAuth'
|
||||
|
||||
type Props = {
|
||||
auth: TwoFactorAuth
|
||||
}
|
||||
|
||||
const TwoFactorDescription: FunctionComponent<Props> = ({ auth }) => {
|
||||
if (!auth.isLoggedIn()) {
|
||||
return <Text>Sign in or register for an account to configure 2FA.</Text>
|
||||
}
|
||||
if (!auth.isMfaFeatureAvailable()) {
|
||||
return (
|
||||
<Text>
|
||||
A paid subscription plan is required to enable 2FA.{' '}
|
||||
<a target="_blank" href="https://standardnotes.com/features">
|
||||
Learn more
|
||||
</a>
|
||||
.
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
return <Text>An extra layer of security when logging in to your account.</Text>
|
||||
}
|
||||
|
||||
export default observer(TwoFactorDescription)
|
||||
@@ -0,0 +1,22 @@
|
||||
import { FunctionComponent } from 'react'
|
||||
import Switch from '@/Components/Switch/Switch'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { is2FADisabled, TwoFactorAuth } from '../TwoFactorAuth'
|
||||
|
||||
type Props = {
|
||||
auth: TwoFactorAuth
|
||||
}
|
||||
|
||||
const TwoFactorSwitch: FunctionComponent<Props> = ({ auth }) => {
|
||||
if (!(auth.isLoggedIn() && auth.isMfaFeatureAvailable())) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (auth.status === 'fetching') {
|
||||
return <div className="sk-spinner normal info" />
|
||||
}
|
||||
|
||||
return <Switch checked={!is2FADisabled(auth.status)} onChange={auth.toggle2FA} />
|
||||
}
|
||||
|
||||
export default observer(TwoFactorSwitch)
|
||||
@@ -0,0 +1,20 @@
|
||||
import { FunctionComponent } from 'react'
|
||||
import { Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { TwoFactorAuth } from '../TwoFactorAuth'
|
||||
|
||||
type Props = {
|
||||
auth: TwoFactorAuth
|
||||
}
|
||||
|
||||
const TwoFactorTitle: FunctionComponent<Props> = ({ auth }) => {
|
||||
if (!auth.isLoggedIn()) {
|
||||
return <Title>Two-factor authentication not available</Title>
|
||||
}
|
||||
if (!auth.isMfaFeatureAvailable()) {
|
||||
return <Title>Two-factor authentication not available</Title>
|
||||
}
|
||||
return <Title>Two-factor authentication</Title>
|
||||
}
|
||||
|
||||
export default observer(TwoFactorTitle)
|
||||
@@ -1,11 +1,12 @@
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { useState } from 'preact/hooks'
|
||||
import { FunctionComponent, useState } from 'react'
|
||||
import { MfaProps } from './MfaProps'
|
||||
import { TwoFactorAuth } from './TwoFactorAuth'
|
||||
import { TwoFactorAuthView } from './TwoFactorAuthView'
|
||||
import TwoFactorAuthView from './TwoFactorAuthView/TwoFactorAuthView'
|
||||
|
||||
export const TwoFactorAuthWrapper: FunctionComponent<MfaProps> = (props) => {
|
||||
const TwoFactorAuthWrapper: FunctionComponent<MfaProps> = (props) => {
|
||||
const [auth] = useState(() => new TwoFactorAuth(props.mfaProvider, props.userProvider))
|
||||
auth.fetchStatus()
|
||||
return <TwoFactorAuthView auth={auth} />
|
||||
}
|
||||
|
||||
export default TwoFactorAuthWrapper
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import ModalDialog, {
|
||||
ModalDialogButtons,
|
||||
ModalDialogDescription,
|
||||
ModalDialogLabel,
|
||||
} from '@/Components/Shared/ModalDialog'
|
||||
import { Subtitle } from '@/Components/Preferences/PreferencesComponents'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import ModalDialog from '@/Components/Shared/ModalDialog'
|
||||
import ModalDialogButtons from '@/Components/Shared/ModalDialogButtons'
|
||||
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
|
||||
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
|
||||
import { Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { TwoFactorActivation } from './TwoFactorActivation'
|
||||
|
||||
export const TwoFactorSuccess: FunctionComponent<{
|
||||
type Props = {
|
||||
activation: TwoFactorActivation
|
||||
}> = observer(({ activation: act }) => (
|
||||
}
|
||||
|
||||
const TwoFactorSuccess: FunctionComponent<Props> = ({ activation: act }) => (
|
||||
<ModalDialog>
|
||||
<ModalDialogLabel closeDialog={act.finishActivation}>Successfully Enabled</ModalDialogLabel>
|
||||
<ModalDialogDescription>
|
||||
@@ -23,4 +24,6 @@ export const TwoFactorSuccess: FunctionComponent<{
|
||||
<Button className="min-w-20" variant="primary" label="Finish" onClick={act.finishActivation} />
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
))
|
||||
)
|
||||
|
||||
export default observer(TwoFactorSuccess)
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { DecoratedInput } from '@/Components/Input/DecoratedInput'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import DecoratedInput from '@/Components/Input/DecoratedInput'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { Bullet } from './Bullet'
|
||||
import { FunctionComponent } from 'react'
|
||||
import Bullet from './Bullet'
|
||||
import { TwoFactorActivation } from './TwoFactorActivation'
|
||||
import {
|
||||
ModalDialog,
|
||||
ModalDialogButtons,
|
||||
ModalDialogDescription,
|
||||
ModalDialogLabel,
|
||||
} from '@/Components/Shared/ModalDialog'
|
||||
import ModalDialog from '@/Components/Shared/ModalDialog'
|
||||
import ModalDialogButtons from '@/Components/Shared/ModalDialogButtons'
|
||||
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
|
||||
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
|
||||
|
||||
export const Verification: FunctionComponent<{
|
||||
type Props = {
|
||||
activation: TwoFactorActivation
|
||||
}> = observer(({ activation: act }) => {
|
||||
}
|
||||
|
||||
const Verification: FunctionComponent<Props> = ({ activation: act }) => {
|
||||
const secretKeyClass = act.verificationStatus === 'invalid-secret' ? 'border-danger' : ''
|
||||
const authTokenClass = act.verificationStatus === 'invalid-auth-code' ? 'border-danger' : ''
|
||||
return (
|
||||
@@ -53,4 +53,6 @@ export const Verification: FunctionComponent<{
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(Verification)
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export * from './HelpFeedback'
|
||||
export * from './Security/Security'
|
||||
export * from './Account/AccountPreferences'
|
||||
export * from './Listed/Listed'
|
||||
export * from './General/General'
|
||||
@@ -0,0 +1,15 @@
|
||||
import { FunctionComponent } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { PreferencesMenu } from './PreferencesMenu'
|
||||
import PreferencesMenuView from './PreferencesMenuView'
|
||||
import PaneSelector from './PaneSelector'
|
||||
import { PreferencesProps } from './PreferencesProps'
|
||||
|
||||
const PreferencesCanvas: FunctionComponent<PreferencesProps & { menu: PreferencesMenu }> = (props) => (
|
||||
<div className="flex flex-row flex-grow min-h-0 justify-between">
|
||||
<PreferencesMenuView menu={props.menu} />
|
||||
<PaneSelector {...props} />
|
||||
</div>
|
||||
)
|
||||
|
||||
export default observer(PreferencesCanvas)
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { FunctionComponent } from 'react'
|
||||
|
||||
export const Title: FunctionComponent = ({ children }) => (
|
||||
<>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Icon } from '@/Components/Icon/Icon'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
|
||||
interface Props {
|
||||
@@ -10,7 +10,7 @@ interface Props {
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
export const MenuItem: FunctionComponent<Props> = ({ iconType, label, selected, onClick, hasBubble }) => (
|
||||
const PreferencesMenuItem: FunctionComponent<Props> = ({ iconType, label, selected, onClick, hasBubble }) => (
|
||||
<div
|
||||
className={`preferences-menu-item select-none ${selected ? 'selected' : ''}`}
|
||||
onClick={(e) => {
|
||||
@@ -24,3 +24,5 @@ export const MenuItem: FunctionComponent<Props> = ({ iconType, label, selected,
|
||||
{hasBubble && <span className="ml-1 color-warning">⚠️</span>}
|
||||
</div>
|
||||
)
|
||||
|
||||
export default PreferencesMenuItem
|
||||
|
||||
@@ -1,21 +1,7 @@
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
import { FunctionComponent } from 'react'
|
||||
|
||||
const HorizontalLine: FunctionComponent<{ index: number; length: number }> = ({ index, length }) => {
|
||||
return index < length - 1 ? <HorizontalSeparator classes="my-4" /> : null
|
||||
}
|
||||
|
||||
export const PreferencesGroup: FunctionComponent = ({ children }) => (
|
||||
<div className="bg-default border-1 border-solid rounded border-main px-6 py-6 flex flex-col mb-3">
|
||||
{Array.isArray(children)
|
||||
? children
|
||||
.filter((child) => child != undefined && child !== '' && child !== false)
|
||||
.map((child, i, arr) => (
|
||||
<>
|
||||
{child}
|
||||
<HorizontalLine index={i} length={arr.length} />
|
||||
</>
|
||||
))
|
||||
: children}
|
||||
</div>
|
||||
const PreferencesGroup: FunctionComponent = ({ children }) => (
|
||||
<div className="bg-default border-1 border-solid rounded border-main px-6 py-6 flex flex-col mb-3">{children}</div>
|
||||
)
|
||||
|
||||
export default PreferencesGroup
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { FunctionComponent } from 'react'
|
||||
|
||||
export const PreferencesPane: FunctionComponent = ({ children }) => (
|
||||
const PreferencesPane: FunctionComponent = ({ children }) => (
|
||||
<div className="color-foreground flex-grow flex flex-row overflow-y-auto min-h-0">
|
||||
<div className="flex-grow flex flex-col py-6 items-center">
|
||||
<div className="w-125 max-w-125 flex flex-col">
|
||||
@@ -10,3 +10,5 @@ export const PreferencesPane: FunctionComponent = ({ children }) => (
|
||||
<div className="flex-basis-55 flex-shrink" />
|
||||
</div>
|
||||
)
|
||||
|
||||
export default PreferencesPane
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { FunctionComponent } from 'react'
|
||||
|
||||
type Props = {
|
||||
classes?: string
|
||||
}
|
||||
export const PreferencesSegment: FunctionComponent<Props> = ({ children, classes = '' }) => (
|
||||
const PreferencesSegment: FunctionComponent<Props> = ({ children, classes = '' }) => (
|
||||
<div className={`flex flex-col ${classes}`}>{children}</div>
|
||||
)
|
||||
|
||||
export default PreferencesSegment
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export * from './Content'
|
||||
export * from './MenuItem'
|
||||
export * from './PreferencesPane'
|
||||
export * from './PreferencesGroup'
|
||||
export * from './PreferencesSegment'
|
||||
@@ -2,7 +2,7 @@ import { action, makeAutoObservable, observable } from 'mobx'
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { ExtensionsLatestVersions } from './Panes/Extensions/ExtensionsLatestVersions'
|
||||
import { securityPrefsHasBubble } from './Panes'
|
||||
import { securityPrefsHasBubble } from './Panes/Security/securityPrefsHasBubble'
|
||||
|
||||
const PREFERENCE_IDS = [
|
||||
'general',
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { MenuItem } from './PreferencesComponents'
|
||||
import { FunctionComponent } from 'react'
|
||||
import PreferencesMenuItem from './PreferencesComponents/MenuItem'
|
||||
import { PreferencesMenu } from './PreferencesMenu'
|
||||
|
||||
export const PreferencesMenuView: FunctionComponent<{
|
||||
type Props = {
|
||||
menu: PreferencesMenu
|
||||
}> = observer(({ menu }) => (
|
||||
}
|
||||
|
||||
const PreferencesMenuView: FunctionComponent<Props> = ({ menu }) => (
|
||||
<div className="min-w-55 overflow-y-auto flex flex-col px-3 py-6">
|
||||
{menu.menuItems.map((pref) => (
|
||||
<MenuItem
|
||||
<PreferencesMenuItem
|
||||
key={pref.id}
|
||||
iconType={pref.icon}
|
||||
label={pref.label}
|
||||
@@ -18,4 +20,6 @@ export const PreferencesMenuView: FunctionComponent<{
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
))
|
||||
)
|
||||
|
||||
export default observer(PreferencesMenuView)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { MfaProps } from './Panes/TwoFactorAuth/MfaProps'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
|
||||
export interface PreferencesProps extends MfaProps {
|
||||
application: WebApplication
|
||||
appState: AppState
|
||||
closePreferences: () => void
|
||||
}
|
||||
@@ -1,80 +1,13 @@
|
||||
import { RoundIconButton } from '@/Components/Button/RoundIconButton'
|
||||
import { TitleBar } from '@/Components/TitleBar/TitleBar'
|
||||
import { Title } from '@/Components/TitleBar/Title'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import RoundIconButton from '@/Components/Button/RoundIconButton'
|
||||
import TitleBar from '@/Components/TitleBar/TitleBar'
|
||||
import Title from '@/Components/TitleBar/Title'
|
||||
import { FunctionComponent, useEffect, useMemo } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { AccountPreferences, HelpAndFeedback, Listed, General, Security } from './Panes'
|
||||
import { PreferencesMenu } from './PreferencesMenu'
|
||||
import { PreferencesMenuView } from './PreferencesMenuView'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { MfaProps } from './Panes/TwoFactorAuth/MfaProps'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { useEffect, useMemo } from 'preact/hooks'
|
||||
import { Backups } from '@/Components/Preferences/Panes/Backups/Backups'
|
||||
import { Appearance } from './Panes/Appearance'
|
||||
import PreferencesCanvas from './PreferencesCanvas'
|
||||
import { PreferencesProps } from './PreferencesProps'
|
||||
|
||||
interface PreferencesProps extends MfaProps {
|
||||
application: WebApplication
|
||||
appState: AppState
|
||||
closePreferences: () => void
|
||||
}
|
||||
|
||||
const PaneSelector: FunctionComponent<PreferencesProps & { menu: PreferencesMenu }> = observer(
|
||||
({ menu, appState, application, mfaProvider, userProvider }) => {
|
||||
switch (menu.selectedPaneId) {
|
||||
case 'general':
|
||||
return (
|
||||
<General
|
||||
appState={appState}
|
||||
application={application}
|
||||
extensionsLatestVersions={menu.extensionsLatestVersions}
|
||||
/>
|
||||
)
|
||||
case 'account':
|
||||
return <AccountPreferences application={application} appState={appState} />
|
||||
case 'appearance':
|
||||
return <Appearance application={application} />
|
||||
case 'security':
|
||||
return (
|
||||
<Security
|
||||
mfaProvider={mfaProvider}
|
||||
userProvider={userProvider}
|
||||
appState={appState}
|
||||
application={application}
|
||||
/>
|
||||
)
|
||||
case 'backups':
|
||||
return <Backups application={application} appState={appState} />
|
||||
case 'listed':
|
||||
return <Listed application={application} />
|
||||
case 'shortcuts':
|
||||
return null
|
||||
case 'accessibility':
|
||||
return null
|
||||
case 'get-free-month':
|
||||
return null
|
||||
case 'help-feedback':
|
||||
return <HelpAndFeedback />
|
||||
default:
|
||||
return (
|
||||
<General
|
||||
appState={appState}
|
||||
application={application}
|
||||
extensionsLatestVersions={menu.extensionsLatestVersions}
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const PreferencesCanvas: FunctionComponent<PreferencesProps & { menu: PreferencesMenu }> = observer((props) => (
|
||||
<div className="flex flex-row flex-grow min-h-0 justify-between">
|
||||
<PreferencesMenuView menu={props.menu} />
|
||||
<PaneSelector {...props} />
|
||||
</div>
|
||||
))
|
||||
|
||||
export const PreferencesView: FunctionComponent<PreferencesProps> = observer((props) => {
|
||||
const PreferencesView: FunctionComponent<PreferencesProps> = (props) => {
|
||||
const menu = useMemo(
|
||||
() => new PreferencesMenu(props.application, props.appState.enableUnfinishedFeatures),
|
||||
[props.appState.enableUnfinishedFeatures, props.application],
|
||||
@@ -97,7 +30,6 @@ export const PreferencesView: FunctionComponent<PreferencesProps> = observer((pr
|
||||
return (
|
||||
<div className="h-full w-full absolute top-left-0 flex flex-col bg-contrast z-index-preferences">
|
||||
<TitleBar className="items-center justify-between">
|
||||
{/* div is added so flex justify-between can center the title */}
|
||||
<div className="h-8 w-8" />
|
||||
<Title className="text-lg">Your preferences for Standard Notes</Title>
|
||||
<RoundIconButton
|
||||
@@ -111,4 +43,6 @@ export const PreferencesView: FunctionComponent<PreferencesProps> = observer((pr
|
||||
<PreferencesCanvas {...props} menu={menu} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default observer(PreferencesView)
|
||||
|
||||
@@ -1,28 +1,22 @@
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { PreferencesView } from './PreferencesView'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import PreferencesView from './PreferencesView'
|
||||
import { PreferencesViewWrapperProps } from './PreferencesViewWrapperProps'
|
||||
|
||||
export interface PreferencesViewWrapperProps {
|
||||
appState: AppState
|
||||
application: WebApplication
|
||||
const PreferencesViewWrapper: FunctionComponent<PreferencesViewWrapperProps> = ({ appState, application }) => {
|
||||
if (!appState.preferences?.isOpen) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<PreferencesView
|
||||
closePreferences={() => appState.preferences.closePreferences()}
|
||||
application={application}
|
||||
appState={appState}
|
||||
mfaProvider={application}
|
||||
userProvider={application}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const PreferencesViewWrapper: FunctionComponent<PreferencesViewWrapperProps> = observer(
|
||||
({ appState, application }) => {
|
||||
if (!appState.preferences?.isOpen) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<PreferencesView
|
||||
closePreferences={() => appState.preferences.closePreferences()}
|
||||
application={application}
|
||||
appState={appState}
|
||||
mfaProvider={application}
|
||||
userProvider={application}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
||||
export default observer(PreferencesViewWrapper)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
|
||||
export interface PreferencesViewWrapperProps {
|
||||
appState: AppState
|
||||
application: WebApplication
|
||||
}
|
||||
Reference in New Issue
Block a user