Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/web/src/javascripts/Application/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { makeObservable, observable } from 'mobx'
import { PanelResizedData } from '@/Types/PanelResizedData'
import { WebAppEvent } from './WebAppEvent'
import { isDesktopApplication } from '@/Utils'
import { storage, StorageKey } from '@/Services/LocalStorage'

type WebServices = {
viewControllerManager: ViewControllerManager
Expand Down Expand Up @@ -62,7 +63,7 @@ export class WebApplication extends SNApplication {
defaultHost: defaultSyncServerHost,
appVersion: deviceInterface.appVersion,
webSocketUrl: webSocketUrl,
supportsFileNavigation: window.enabledUnfinishedFeatures || false,
supportsFileNavigation: storage.get(StorageKey.FilesNavigationEnabled) || false,
})

makeObservable(this, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ExtensionsLatestVersions } from '@/Components/Preferences/Panes/Extensi
import { observer } from 'mobx-react-lite'
import Tools from './Tools'
import Defaults from './Defaults'
import LabsPane from './Labs'
import LabsPane from './Labs/Labs'
import Advanced from '@/Components/Preferences/Panes/Account/Advanced'
import PreferencesPane from '../../PreferencesComponents/PreferencesPane'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import Switch from '@/Components/Switch/Switch'
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
import { Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
import { WebApplication } from '@/Application/Application'
import { FeatureIdentifier, FeatureStatus, FindNativeFeature } from '@standardnotes/snjs'
import { Fragment, FunctionComponent, useCallback, useEffect, useState } from 'react'
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 PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'

type ExperimentalFeatureItem = {
identifier: FeatureIdentifier
Expand All @@ -17,11 +18,14 @@ type ExperimentalFeatureItem = {
}

type Props = {
application: WebApplication
application: {
features: WebApplication['features']
}
}

const LabsPane: FunctionComponent<Props> = ({ application }) => {
const [experimentalFeatures, setExperimentalFeatures] = useState<ExperimentalFeatureItem[]>([])
const [isFilesNavigationEnabled, setFilesNavigation] = useLocalStorageItem(StorageKey.FilesNavigationEnabled)

const reloadExperimentalFeatures = useCallback(() => {
const experimentalFeatures = application.features.getExperimentalFeatures().map((featureIdentifier) => {
Expand All @@ -43,12 +47,23 @@ const LabsPane: FunctionComponent<Props> = ({ application }) => {

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 (
<PreferencesGroup>
<PreferencesSegment>
<Title>Labs</Title>
<div>
{experimentalFeatures.map(({ identifier, name, description, isEnabled, isEntitled }, index: number) => {
{experimentalFeatures.map(({ identifier, name, description, isEnabled, isEntitled }, index) => {
const toggleFeature = () => {
if (!isEntitled) {
premiumModal.activate(name)
Expand All @@ -63,17 +78,23 @@ const LabsPane: FunctionComponent<Props> = ({ application }) => {

return (
<Fragment key={identifier}>
<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>
<LabsFeature
name={name}
description={description}
toggleFeature={toggleFeature}
isEnabled={isEnabled}
/>
{showHorizontalSeparator && <HorizontalSeparator classes="mt-2.5 mb-3" />}
</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 && (
<div className="flex items-center justify-between">
<div className="flex flex-col">
Expand Down
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions packages/web/src/javascripts/Services/LocalStorage.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { useCallback, useState } from 'react'

export enum StorageKey {
AnonymousUserId = 'AnonymousUserId',
ShowBetaWarning = 'ShowBetaWarning',
ShowNoAccountWarning = 'ShowNoAccountWarning',
FilesNavigationEnabled = 'FilesNavigationEnabled',
}

export type StorageValue = {
[StorageKey.AnonymousUserId]: string
[StorageKey.ShowBetaWarning]: boolean
[StorageKey.ShowNoAccountWarning]: boolean
[StorageKey.FilesNavigationEnabled]: boolean
}

export const storage = {
Expand All @@ -22,3 +26,19 @@ export const storage = {
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]
}