chore: improve preferences folder hierarchies (#1186)

This commit is contained in:
Mo
2022-06-30 13:49:03 -05:00
committed by GitHub
parent eac62fef1a
commit 7c2b8fe545
31 changed files with 88 additions and 72 deletions

View File

@@ -15,6 +15,7 @@
"prebuild": "yarn clean", "prebuild": "yarn clean",
"build": "webpack --config web.webpack.prod.js && yarn tsc", "build": "webpack --config web.webpack.prod.js && yarn tsc",
"lint": "eslint src/javascripts", "lint": "eslint src/javascripts",
"format": "prettier --write src/javascripts",
"tsc": "tsc --project tsconfig.json", "tsc": "tsc --project tsconfig.json",
"test": "jest --config jest.config.js --coverage", "test": "jest --config jest.config.js --coverage",
"upgrade:snjs": "ncu -u '@standardnotes/*'" "upgrade:snjs": "ncu -u '@standardnotes/*'"

View File

@@ -1,11 +0,0 @@
import { WebApplication } from '@/Application/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
}

View File

@@ -1,29 +1,29 @@
import { FunctionComponent } from 'react' import { FunctionComponent } from 'react'
import OfflineSubscription from '@/Components/Preferences/Panes/Account/OfflineSubscription' import OfflineSubscription from '@/Components/Preferences/Panes/General/Advanced/OfflineSubscription'
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import { ViewControllerManager } from '@/Services/ViewControllerManager' import { ViewControllerManager } from '@/Services/ViewControllerManager'
import Extensions from '@/Components/Preferences/Panes/Extensions/Extensions' import PackagesPreferencesSection from '@/Components/Preferences/Panes/General/Advanced/Packages/Section'
import { ExtensionsLatestVersions } from '@/Components/Preferences/Panes/Extensions/ExtensionsLatestVersions' import { PackageProvider } from '@/Components/Preferences/Panes/General/Advanced/Packages/Provider/PackageProvider'
import AccordionItem from '@/Components/Shared/AccordionItem' import AccordionItem from '@/Components/Shared/AccordionItem'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup' import PreferencesGroup from '../../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment' import PreferencesSegment from '../../../PreferencesComponents/PreferencesSegment'
type Props = { type Props = {
application: WebApplication application: WebApplication
viewControllerManager: ViewControllerManager viewControllerManager: ViewControllerManager
extensionsLatestVersions: ExtensionsLatestVersions extensionsLatestVersions: PackageProvider
} }
const Advanced: FunctionComponent<Props> = ({ application, viewControllerManager, extensionsLatestVersions }) => { const Advanced: FunctionComponent<Props> = ({ application, viewControllerManager, extensionsLatestVersions }) => {
return ( return (
<PreferencesGroup> <PreferencesGroup>
<PreferencesSegment> <PreferencesSegment>
<AccordionItem title={'Advanced Settings'}> <AccordionItem title={'Advanced Options'}>
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
<div className="flex flex-grow flex-col"> <div className="flex flex-grow flex-col">
<OfflineSubscription application={application} viewControllerManager={viewControllerManager} /> <OfflineSubscription application={application} viewControllerManager={viewControllerManager} />
<Extensions <PackagesPreferencesSection
className={'mt-3'} className={'mt-3'}
application={application} application={application}
extensionsLatestVersions={extensionsLatestVersions} extensionsLatestVersions={extensionsLatestVersions}

View File

@@ -2,11 +2,11 @@ import { DisplayStringForContentType } from '@standardnotes/snjs'
import Button from '@/Components/Button/Button' import Button from '@/Components/Button/Button'
import { Fragment, FunctionComponent } from 'react' import { Fragment, FunctionComponent } from 'react'
import { Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content' import { Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import { AnyExtension } from './AnyExtension' import { AnyPackageType } from './Types/AnyPackageType'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment' import PreferencesSegment from '../../../../PreferencesComponents/PreferencesSegment'
const ConfirmCustomExtension: FunctionComponent<{ const ConfirmCustomPackage: FunctionComponent<{
component: AnyExtension component: AnyPackageType
callback: (confirmed: boolean) => void callback: (confirmed: boolean) => void
}> = ({ component, callback }) => { }> = ({ component, callback }) => {
const fields = [ const fields = [
@@ -66,4 +66,4 @@ const ConfirmCustomExtension: FunctionComponent<{
) )
} }
export default ConfirmCustomExtension export default ConfirmCustomPackage

View File

@@ -3,9 +3,10 @@ import { ComponentMutator, SNComponent } from '@standardnotes/snjs'
import { SubtitleLight } from '@/Components/Preferences/PreferencesComponents/Content' import { SubtitleLight } from '@/Components/Preferences/PreferencesComponents/Content'
import Switch from '@/Components/Switch/Switch' import Switch from '@/Components/Switch/Switch'
import Button from '@/Components/Button/Button' import Button from '@/Components/Button/Button'
import ExtensionInfoCell from './ExtensionInfoCell' import PackageEntrySubInfo from './PackageEntrySubInfo'
import { ExtensionItemProps } from './ExtensionItemProps' import PreferencesSegment from '../../../../PreferencesComponents/PreferencesSegment'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment' import { WebApplication } from '@/Application/Application'
import { AnyPackageType } from './Types/AnyPackageType'
const UseHosted: FunctionComponent<{ const UseHosted: FunctionComponent<{
offlineOnly: boolean offlineOnly: boolean
@@ -17,7 +18,16 @@ const UseHosted: FunctionComponent<{
</div> </div>
) )
const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({ application, extension, uninstall }) => { interface PackageEntryProps {
application: WebApplication
extension: AnyPackageType
first: boolean
latestVersion: string | undefined
uninstall: (extension: AnyPackageType) => void
toggleActivate?: (extension: AnyPackageType) => void
}
const PackageEntry: FunctionComponent<PackageEntryProps> = ({ application, extension, uninstall }) => {
const [offlineOnly, setOfflineOnly] = useState(extension instanceof SNComponent ? extension.offlineOnly : false) const [offlineOnly, setOfflineOnly] = useState(extension instanceof SNComponent ? extension.offlineOnly : false)
const [extensionName, setExtensionName] = useState(extension.displayName) const [extensionName, setExtensionName] = useState(extension.displayName)
@@ -56,7 +66,7 @@ const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({ application, ext
return ( return (
<PreferencesSegment classes={'mb-5'}> <PreferencesSegment classes={'mb-5'}>
<ExtensionInfoCell isThirdParty={isThirdParty} extensionName={extensionName} changeName={changeExtensionName} /> <PackageEntrySubInfo isThirdParty={isThirdParty} extensionName={extensionName} changeName={changeExtensionName} />
<div className="my-1" /> <div className="my-1" />
@@ -71,4 +81,4 @@ const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({ application, ext
) )
} }
export default ExtensionItem export default PackageEntry

View File

@@ -6,7 +6,7 @@ type Props = {
isThirdParty: boolean isThirdParty: boolean
} }
const ExtensionInfoCell: FunctionComponent<Props> = ({ extensionName, changeName, isThirdParty }) => { const PackageEntrySubInfo: FunctionComponent<Props> = ({ extensionName, changeName, isThirdParty }) => {
const [isRenaming, setIsRenaming] = useState(false) const [isRenaming, setIsRenaming] = useState(false)
const [newExtensionName, setNewExtensionName] = useState<string>(extensionName) const [newExtensionName, setNewExtensionName] = useState<string>(extensionName)
@@ -73,4 +73,4 @@ const ExtensionInfoCell: FunctionComponent<Props> = ({ extensionName, changeName
) )
} }
export default ExtensionInfoCell export default PackageEntrySubInfo

View File

@@ -1,13 +1,13 @@
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { ClientDisplayableError, FeatureDescription } from '@standardnotes/snjs' import { ClientDisplayableError, FeatureDescription } from '@standardnotes/snjs'
import { makeAutoObservable, observable } from 'mobx' import { makeAutoObservable, observable } from 'mobx'
import { AnyExtension } from './AnyExtension' import { AnyPackageType } from '../Types/AnyPackageType'
import { ComponentChecksumsType } from '@standardnotes/components-meta' import { ComponentChecksumsType } from '@standardnotes/components-meta'
import RawComponentChecksumsFile from '@standardnotes/components-meta/dist/zips/checksums.json' import RawComponentChecksumsFile from '@standardnotes/components-meta/dist/zips/checksums.json'
const ComponentChecksums = RawComponentChecksumsFile as ComponentChecksumsType const ComponentChecksums = RawComponentChecksumsFile as ComponentChecksumsType
export class ExtensionsLatestVersions { export class PackageProvider {
static async load(application: WebApplication): Promise<ExtensionsLatestVersions | undefined> { static async load(application: WebApplication): Promise<PackageProvider | undefined> {
const response = await application.getAvailableSubscriptions() const response = await application.getAvailableSubscriptions()
if (response instanceof ClientDisplayableError) { if (response instanceof ClientDisplayableError) {
@@ -18,16 +18,16 @@ export class ExtensionsLatestVersions {
collectFeatures(response.PLUS_PLAN?.features as FeatureDescription[], versionMap) collectFeatures(response.PLUS_PLAN?.features as FeatureDescription[], versionMap)
collectFeatures(response.PRO_PLAN?.features as FeatureDescription[], versionMap) collectFeatures(response.PRO_PLAN?.features as FeatureDescription[], versionMap)
return new ExtensionsLatestVersions(versionMap) return new PackageProvider(versionMap)
} }
constructor(private readonly latestVersionsMap: Map<string, string>) { constructor(private readonly latestVersionsMap: Map<string, string>) {
makeAutoObservable<ExtensionsLatestVersions, 'latestVersionsMap'>(this, { makeAutoObservable<PackageProvider, 'latestVersionsMap'>(this, {
latestVersionsMap: observable.ref, latestVersionsMap: observable.ref,
}) })
} }
getVersion(extension: AnyExtension): string | undefined { getVersion(extension: AnyPackageType): string | undefined {
return this.latestVersionsMap.get(extension.package_info.identifier) return this.latestVersionsMap.get(extension.package_info.identifier)
} }
} }

View File

@@ -3,26 +3,34 @@ import Button from '@/Components/Button/Button'
import DecoratedInput from '@/Components/Input/DecoratedInput' import DecoratedInput from '@/Components/Input/DecoratedInput'
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { FunctionComponent, useEffect, useRef, useState } from 'react' import { FunctionComponent, useEffect, useRef, useState } from 'react'
import { Title } from '@/Components/Preferences/PreferencesComponents/Content' import { Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import { ExtensionsLatestVersions } from './ExtensionsLatestVersions' import { PackageProvider } from './Provider/PackageProvider'
import ExtensionItem from './ExtensionItem' import PackageEntry from './PackageEntry'
import ConfirmCustomExtension from './ConfirmCustomExtension' import ConfirmCustomPackage from './ConfirmCustomPackage'
import { AnyExtension } from './AnyExtension' import { AnyPackageType } from './Types/AnyPackageType'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment' import PreferencesSegment from '../../../../PreferencesComponents/PreferencesSegment'
const loadExtensions = (application: WebApplication) => const loadExtensions = (application: WebApplication) =>
application.items.getItems([ContentType.ActionsExtension, ContentType.Component, ContentType.Theme]) as AnyExtension[] application.items.getItems([
ContentType.ActionsExtension,
ContentType.Component,
ContentType.Theme,
]) as AnyPackageType[]
type Props = { type Props = {
application: WebApplication application: WebApplication
extensionsLatestVersions: ExtensionsLatestVersions extensionsLatestVersions: PackageProvider
className?: string className?: string
} }
const Extensions: FunctionComponent<Props> = ({ application, extensionsLatestVersions, className = '' }) => { const PackagesPreferencesSection: FunctionComponent<Props> = ({
application,
extensionsLatestVersions,
className = '',
}) => {
const [customUrl, setCustomUrl] = useState('') const [customUrl, setCustomUrl] = useState('')
const [confirmableExtension, setConfirmableExtension] = useState<AnyExtension | undefined>(undefined) const [confirmableExtension, setConfirmableExtension] = useState<AnyPackageType | undefined>(undefined)
const [extensions, setExtensions] = useState(loadExtensions(application)) const [extensions, setExtensions] = useState(loadExtensions(application))
const confirmableEnd = useRef<HTMLDivElement>(null) const confirmableEnd = useRef<HTMLDivElement>(null)
@@ -33,7 +41,7 @@ const Extensions: FunctionComponent<Props> = ({ application, extensionsLatestVer
} }
}, [confirmableExtension, confirmableEnd]) }, [confirmableExtension, confirmableEnd])
const uninstallExtension = async (extension: AnyExtension) => { const uninstallExtension = async (extension: AnyPackageType) => {
application.alertService application.alertService
.confirm( .confirm(
'Are you sure you want to uninstall this extension? Note that extensions managed by your subscription will automatically be re-installed on application restart.', 'Are you sure you want to uninstall this extension? Note that extensions managed by your subscription will automatically be re-installed on application restart.',
@@ -69,7 +77,7 @@ const Extensions: FunctionComponent<Props> = ({ application, extensionsLatestVer
} }
const confirmExtension = async () => { const confirmExtension = async () => {
await application.mutator.insertItem(confirmableExtension as AnyExtension) await application.mutator.insertItem(confirmableExtension as AnyPackageType)
application.sync.sync().catch(console.error) application.sync.sync().catch(console.error)
setExtensions(loadExtensions(application)) setExtensions(loadExtensions(application))
} }
@@ -95,7 +103,7 @@ const Extensions: FunctionComponent<Props> = ({ application, extensionsLatestVer
{visibleExtensions {visibleExtensions
.sort((e1, e2) => e1.displayName?.toLowerCase().localeCompare(e2.displayName?.toLowerCase())) .sort((e1, e2) => e1.displayName?.toLowerCase().localeCompare(e2.displayName?.toLowerCase()))
.map((extension, i) => ( .map((extension, i) => (
<ExtensionItem <PackageEntry
key={extension.uuid} key={extension.uuid}
application={application} application={application}
extension={extension} extension={extension}
@@ -110,20 +118,28 @@ const Extensions: FunctionComponent<Props> = ({ application, extensionsLatestVer
<div> <div>
{!confirmableExtension && ( {!confirmableExtension && (
<PreferencesSegment> <PreferencesSegment>
<Title>Install Custom Extension</Title> <Subtitle>Install External Package</Subtitle>
<DecoratedInput <div className={'mt-2'}>
placeholder={'Enter Extension URL'} <DecoratedInput
value={customUrl} placeholder={'Enter Package URL'}
onChange={(value) => { value={customUrl}
setCustomUrl(value) onChange={(value) => {
}} setCustomUrl(value)
}}
/>
</div>
<Button
disabled={customUrl.length === 0}
className="mt-3 min-w-20"
primary
label="Install"
onClick={() => submitExtensionUrl(customUrl)}
/> />
<Button className="mt-3 min-w-20" label="Install" onClick={() => submitExtensionUrl(customUrl)} />
</PreferencesSegment> </PreferencesSegment>
)} )}
{confirmableExtension && ( {confirmableExtension && (
<PreferencesSegment> <PreferencesSegment>
<ConfirmCustomExtension component={confirmableExtension} callback={handleConfirmExtensionSubmit} /> <ConfirmCustomPackage component={confirmableExtension} callback={handleConfirmExtensionSubmit} />
<div ref={confirmableEnd} /> <div ref={confirmableEnd} />
</PreferencesSegment> </PreferencesSegment>
)} )}
@@ -132,4 +148,4 @@ const Extensions: FunctionComponent<Props> = ({ application, extensionsLatestVer
) )
} }
export default observer(Extensions) export default observer(PackagesPreferencesSection)

View File

@@ -1,3 +1,3 @@
import { SNActionsExtension, SNComponent, SNTheme } from '@standardnotes/snjs' import { SNActionsExtension, SNComponent, SNTheme } from '@standardnotes/snjs'
export type AnyExtension = SNComponent | SNTheme | SNActionsExtension export type AnyPackageType = SNComponent | SNTheme | SNActionsExtension

View File

@@ -1,18 +1,18 @@
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager' import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { FunctionComponent } from 'react' import { FunctionComponent } from 'react'
import { ExtensionsLatestVersions } from '@/Components/Preferences/Panes/Extensions/ExtensionsLatestVersions' import { PackageProvider } from '@/Components/Preferences/Panes/General/Advanced/Packages/Provider/PackageProvider'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import Tools from './Tools' import Tools from './Tools'
import Defaults from './Defaults' import Defaults from './Defaults'
import LabsPane from './Labs/Labs' import LabsPane from './Labs/Labs'
import Advanced from '@/Components/Preferences/Panes/Account/Advanced' import Advanced from '@/Components/Preferences/Panes/General/Advanced/AdvancedSection'
import PreferencesPane from '../../PreferencesComponents/PreferencesPane' import PreferencesPane from '../../PreferencesComponents/PreferencesPane'
type Props = { type Props = {
viewControllerManager: ViewControllerManager viewControllerManager: ViewControllerManager
application: WebApplication application: WebApplication
extensionsLatestVersions: ExtensionsLatestVersions extensionsLatestVersions: PackageProvider
} }
const General: FunctionComponent<Props> = ({ viewControllerManager, application, extensionsLatestVersions }) => ( const General: FunctionComponent<Props> = ({ viewControllerManager, application, extensionsLatestVersions }) => (

View File

@@ -1,8 +1,8 @@
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager' import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { FunctionComponent } from 'react' import { FunctionComponent } from 'react'
import TwoFactorAuthWrapper from '../TwoFactorAuth/TwoFactorAuthWrapper' import TwoFactorAuthWrapper from './TwoFactorAuth/TwoFactorAuthWrapper'
import { MfaProps } from '../TwoFactorAuth/MfaProps' import { MfaProps } from './TwoFactorAuth/MfaProps'
import Encryption from './Encryption' import Encryption from './Encryption'
import PasscodeLock from './PasscodeLock' import PasscodeLock from './PasscodeLock'
import Privacy from './Privacy' import Privacy from './Privacy'

View File

@@ -1,7 +1,7 @@
import { action, makeAutoObservable, observable } from 'mobx' import { action, makeAutoObservable, observable } from 'mobx'
import { IconType } from '@standardnotes/snjs' import { IconType } from '@standardnotes/snjs'
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { ExtensionsLatestVersions } from './Panes/Extensions/ExtensionsLatestVersions' import { PackageProvider } from './Panes/General/Advanced/Packages/Provider/PackageProvider'
import { securityPrefsHasBubble } from './Panes/Security/securityPrefsHasBubble' import { securityPrefsHasBubble } from './Panes/Security/securityPrefsHasBubble'
const PREFERENCE_IDS = [ const PREFERENCE_IDS = [
@@ -59,7 +59,7 @@ const READY_PREFERENCES_MENU_ITEMS: PreferencesMenuItem[] = [
export class PreferencesMenu { export class PreferencesMenu {
private _selectedPane: PreferenceId = 'account' private _selectedPane: PreferenceId = 'account'
private _menu: PreferencesMenuItem[] private _menu: PreferencesMenuItem[]
private _extensionLatestVersions: ExtensionsLatestVersions = new ExtensionsLatestVersions(new Map()) private _extensionLatestVersions: PackageProvider = new PackageProvider(new Map())
constructor(private application: WebApplication, private readonly _enableUnfinishedFeatures: boolean) { constructor(private application: WebApplication, private readonly _enableUnfinishedFeatures: boolean) {
this._menu = this._enableUnfinishedFeatures ? PREFERENCES_MENU_ITEMS : READY_PREFERENCES_MENU_ITEMS this._menu = this._enableUnfinishedFeatures ? PREFERENCES_MENU_ITEMS : READY_PREFERENCES_MENU_ITEMS
@@ -79,7 +79,7 @@ export class PreferencesMenu {
} }
private loadLatestVersions(): void { private loadLatestVersions(): void {
ExtensionsLatestVersions.load(this.application) PackageProvider.load(this.application)
.then((versions) => { .then((versions) => {
if (versions) { if (versions) {
this._extensionLatestVersions = versions this._extensionLatestVersions = versions
@@ -88,7 +88,7 @@ export class PreferencesMenu {
.catch(console.error) .catch(console.error)
} }
get extensionsLatestVersions(): ExtensionsLatestVersions { get extensionsLatestVersions(): PackageProvider {
return this._extensionLatestVersions return this._extensionLatestVersions
} }

View File

@@ -1,5 +1,5 @@
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { MfaProps } from './Panes/TwoFactorAuth/MfaProps' import { MfaProps } from './Panes/Security/TwoFactorAuth/MfaProps'
import { ViewControllerManager } from '@/Services/ViewControllerManager' import { ViewControllerManager } from '@/Services/ViewControllerManager'
export interface PreferencesProps extends MfaProps { export interface PreferencesProps extends MfaProps {