feat: add "Files Navigation" experimental feature to Labs (#1125)
This commit is contained in:
@@ -26,6 +26,7 @@ import { makeObservable, observable } from 'mobx'
|
|||||||
import { PanelResizedData } from '@/Types/PanelResizedData'
|
import { PanelResizedData } from '@/Types/PanelResizedData'
|
||||||
import { WebAppEvent } from './WebAppEvent'
|
import { WebAppEvent } from './WebAppEvent'
|
||||||
import { isDesktopApplication } from '@/Utils'
|
import { isDesktopApplication } from '@/Utils'
|
||||||
|
import { storage, StorageKey } from '@/Services/LocalStorage'
|
||||||
|
|
||||||
type WebServices = {
|
type WebServices = {
|
||||||
viewControllerManager: ViewControllerManager
|
viewControllerManager: ViewControllerManager
|
||||||
@@ -62,7 +63,7 @@ export class WebApplication extends SNApplication {
|
|||||||
defaultHost: defaultSyncServerHost,
|
defaultHost: defaultSyncServerHost,
|
||||||
appVersion: deviceInterface.appVersion,
|
appVersion: deviceInterface.appVersion,
|
||||||
webSocketUrl: webSocketUrl,
|
webSocketUrl: webSocketUrl,
|
||||||
supportsFileNavigation: window.enabledUnfinishedFeatures || false,
|
supportsFileNavigation: storage.get(StorageKey.FilesNavigationEnabled) || false,
|
||||||
})
|
})
|
||||||
|
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { ExtensionsLatestVersions } from '@/Components/Preferences/Panes/Extensi
|
|||||||
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'
|
import LabsPane from './Labs/Labs'
|
||||||
import Advanced from '@/Components/Preferences/Panes/Account/Advanced'
|
import Advanced from '@/Components/Preferences/Panes/Account/Advanced'
|
||||||
import PreferencesPane from '../../PreferencesComponents/PreferencesPane'
|
import PreferencesPane from '../../PreferencesComponents/PreferencesPane'
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import Switch from '@/Components/Switch/Switch'
|
import { Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||||
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
|
||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { FeatureIdentifier, FeatureStatus, FindNativeFeature } from '@standardnotes/snjs'
|
import { FeatureIdentifier, FeatureStatus, FindNativeFeature } from '@standardnotes/snjs'
|
||||||
import { Fragment, FunctionComponent, useCallback, useEffect, useState } from 'react'
|
import { Fragment, FunctionComponent, useCallback, useEffect, useState } from 'react'
|
||||||
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
||||||
|
import PreferencesGroup from '../../../PreferencesComponents/PreferencesGroup'
|
||||||
|
import PreferencesSegment from '../../../PreferencesComponents/PreferencesSegment'
|
||||||
|
import LabsFeature from './LabsFeature'
|
||||||
|
import { StorageKey, useLocalStorageItem } from '@/Services/LocalStorage'
|
||||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
|
||||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
|
||||||
|
|
||||||
type ExperimentalFeatureItem = {
|
type ExperimentalFeatureItem = {
|
||||||
identifier: FeatureIdentifier
|
identifier: FeatureIdentifier
|
||||||
@@ -17,11 +18,14 @@ type ExperimentalFeatureItem = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: {
|
||||||
|
features: WebApplication['features']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const LabsPane: FunctionComponent<Props> = ({ application }) => {
|
const LabsPane: FunctionComponent<Props> = ({ application }) => {
|
||||||
const [experimentalFeatures, setExperimentalFeatures] = useState<ExperimentalFeatureItem[]>([])
|
const [experimentalFeatures, setExperimentalFeatures] = useState<ExperimentalFeatureItem[]>([])
|
||||||
|
const [isFilesNavigationEnabled, setFilesNavigation] = useLocalStorageItem(StorageKey.FilesNavigationEnabled)
|
||||||
|
|
||||||
const reloadExperimentalFeatures = useCallback(() => {
|
const reloadExperimentalFeatures = useCallback(() => {
|
||||||
const experimentalFeatures = application.features.getExperimentalFeatures().map((featureIdentifier) => {
|
const experimentalFeatures = application.features.getExperimentalFeatures().map((featureIdentifier) => {
|
||||||
@@ -43,12 +47,23 @@ const LabsPane: FunctionComponent<Props> = ({ application }) => {
|
|||||||
|
|
||||||
const premiumModal = usePremiumModal()
|
const premiumModal = usePremiumModal()
|
||||||
|
|
||||||
|
const toggleFilesNavigation = useCallback(() => {
|
||||||
|
const isEntitled = application.features.getFeatureStatus(FeatureIdentifier.Files) === FeatureStatus.Entitled
|
||||||
|
|
||||||
|
if (!isEntitled) {
|
||||||
|
premiumModal.activate('Files navigation')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilesNavigation(!isFilesNavigationEnabled)
|
||||||
|
}, [application.features, isFilesNavigationEnabled, premiumModal, setFilesNavigation])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PreferencesGroup>
|
<PreferencesGroup>
|
||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<Title>Labs</Title>
|
<Title>Labs</Title>
|
||||||
<div>
|
<div>
|
||||||
{experimentalFeatures.map(({ identifier, name, description, isEnabled, isEntitled }, index: number) => {
|
{experimentalFeatures.map(({ identifier, name, description, isEnabled, isEntitled }, index) => {
|
||||||
const toggleFeature = () => {
|
const toggleFeature = () => {
|
||||||
if (!isEntitled) {
|
if (!isEntitled) {
|
||||||
premiumModal.activate(name)
|
premiumModal.activate(name)
|
||||||
@@ -63,17 +78,23 @@ const LabsPane: FunctionComponent<Props> = ({ application }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment key={identifier}>
|
<Fragment key={identifier}>
|
||||||
<div className="flex items-center justify-between">
|
<LabsFeature
|
||||||
<div className="flex flex-col">
|
name={name}
|
||||||
<Subtitle>{name}</Subtitle>
|
description={description}
|
||||||
<Text>{description}</Text>
|
toggleFeature={toggleFeature}
|
||||||
</div>
|
isEnabled={isEnabled}
|
||||||
<Switch onChange={toggleFeature} checked={isEnabled} />
|
/>
|
||||||
</div>
|
|
||||||
{showHorizontalSeparator && <HorizontalSeparator classes="mt-2.5 mb-3" />}
|
{showHorizontalSeparator && <HorizontalSeparator classes="mt-2.5 mb-3" />}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
<HorizontalSeparator classes="mt-2.5 mb-3" />
|
||||||
|
<LabsFeature
|
||||||
|
name="Files navigation"
|
||||||
|
description={'Enables a "Files" view which allows for better files navigation. Requires reload.'}
|
||||||
|
toggleFeature={toggleFilesNavigation}
|
||||||
|
isEnabled={!!isFilesNavigationEnabled}
|
||||||
|
/>
|
||||||
{experimentalFeatures.length === 0 && (
|
{experimentalFeatures.length === 0 && (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Subtitle, Text } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||||
|
import Switch from '@/Components/Switch/Switch'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
toggleFeature: () => void
|
||||||
|
isEnabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const LabsFeature = ({ name, description, toggleFeature, isEnabled }: Props) => {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<Subtitle>{name}</Subtitle>
|
||||||
|
<Text>{description}</Text>
|
||||||
|
</div>
|
||||||
|
<Switch onChange={toggleFeature} checked={isEnabled} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LabsFeature
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
|
import { useCallback, useState } from 'react'
|
||||||
|
|
||||||
export enum StorageKey {
|
export enum StorageKey {
|
||||||
AnonymousUserId = 'AnonymousUserId',
|
AnonymousUserId = 'AnonymousUserId',
|
||||||
ShowBetaWarning = 'ShowBetaWarning',
|
ShowBetaWarning = 'ShowBetaWarning',
|
||||||
ShowNoAccountWarning = 'ShowNoAccountWarning',
|
ShowNoAccountWarning = 'ShowNoAccountWarning',
|
||||||
|
FilesNavigationEnabled = 'FilesNavigationEnabled',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StorageValue = {
|
export type StorageValue = {
|
||||||
[StorageKey.AnonymousUserId]: string
|
[StorageKey.AnonymousUserId]: string
|
||||||
[StorageKey.ShowBetaWarning]: boolean
|
[StorageKey.ShowBetaWarning]: boolean
|
||||||
[StorageKey.ShowNoAccountWarning]: boolean
|
[StorageKey.ShowNoAccountWarning]: boolean
|
||||||
|
[StorageKey.FilesNavigationEnabled]: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const storage = {
|
export const storage = {
|
||||||
@@ -22,3 +26,19 @@ export const storage = {
|
|||||||
localStorage.removeItem(key)
|
localStorage.removeItem(key)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LocalStorageHookReturnType<Key extends StorageKey> = [StorageValue[Key] | null, (value: StorageValue[Key]) => void]
|
||||||
|
|
||||||
|
export const useLocalStorageItem = <Key extends StorageKey>(key: Key): LocalStorageHookReturnType<Key> => {
|
||||||
|
const [value, setValue] = useState(() => storage.get(key))
|
||||||
|
|
||||||
|
const set = useCallback(
|
||||||
|
(value: StorageValue[Key]) => {
|
||||||
|
storage.set(key, value)
|
||||||
|
setValue(value)
|
||||||
|
},
|
||||||
|
[key],
|
||||||
|
)
|
||||||
|
|
||||||
|
return [value, set]
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user