refactor: make features state consistent with codebase (#988)

This commit is contained in:
Aman Harwara
2022-04-20 20:11:32 +05:30
committed by GitHub
parent 7dae489617
commit 24d3aac3c7
7 changed files with 59 additions and 77 deletions

View File

@@ -1,7 +1,8 @@
import { Icon } from '@/Components/Icon' import { Icon } from '@/Components/Icon'
import { TAG_FOLDERS_FEATURE_NAME } from '@/Constants'
import { usePremiumModal } from '@/Hooks/usePremiumModal' import { usePremiumModal } from '@/Hooks/usePremiumModal'
import { KeyboardKey } from '@/Services/IOService' import { KeyboardKey } from '@/Services/IOService'
import { FeaturesState, TAG_FOLDERS_FEATURE_NAME } from '@/UIModels/AppState/FeaturesState' import { FeaturesState } from '@/UIModels/AppState/FeaturesState'
import { TagsState } from '@/UIModels/AppState/TagsState' import { TagsState } from '@/UIModels/AppState/TagsState'
import '@reach/tooltip/styles.css' import '@reach/tooltip/styles.css'
import { SNTag } from '@standardnotes/snjs' import { SNTag } from '@standardnotes/snjs'

View File

@@ -1,9 +1,6 @@
import { TAG_FOLDERS_FEATURE_NAME, TAG_FOLDERS_FEATURE_TOOLTIP } from '@/Constants'
import { usePremiumModal } from '@/Hooks/usePremiumModal' import { usePremiumModal } from '@/Hooks/usePremiumModal'
import { import { FeaturesState } from '@/UIModels/AppState/FeaturesState'
FeaturesState,
TAG_FOLDERS_FEATURE_NAME,
TAG_FOLDERS_FEATURE_TOOLTIP,
} from '@/UIModels/AppState/FeaturesState'
import { Tooltip } from '@reach/tooltip' import { Tooltip } from '@reach/tooltip'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact' import { FunctionComponent } from 'preact'

View File

@@ -16,3 +16,7 @@ export const DAYS_IN_A_YEAR = 365
export const BYTES_IN_ONE_KILOBYTE = 1_000 export const BYTES_IN_ONE_KILOBYTE = 1_000
export const BYTES_IN_ONE_MEGABYTE = 1_000_000 export const BYTES_IN_ONE_MEGABYTE = 1_000_000
export const TAG_FOLDERS_FEATURE_NAME = 'Tag folders'
export const TAG_FOLDERS_FEATURE_TOOLTIP = 'A Plus or Pro plan is required to enable Tag folders.'
export const SMART_TAGS_FEATURE_NAME = 'Smart Tags'

View File

@@ -31,7 +31,7 @@ interface Props {
export const PremiumModalProvider: FunctionalComponent<Props> = observer( export const PremiumModalProvider: FunctionalComponent<Props> = observer(
({ application, appState, children }) => { ({ application, appState, children }) => {
const featureName = appState.features._premiumAlertFeatureName const featureName = appState.features.premiumAlertFeatureName
const activate = appState.features.showPremiumAlert const activate = appState.features.showPremiumAlert
const close = appState.features.closePremiumAlert const close = appState.features.closePremiumAlert

View File

@@ -102,7 +102,7 @@ export class AppState {
this.appEventObserverRemovers, this.appEventObserverRemovers,
) )
this.noteTags = new NoteTagsState(application, this, this.appEventObserverRemovers) this.noteTags = new NoteTagsState(application, this, this.appEventObserverRemovers)
this.features = new FeaturesState(application) this.features = new FeaturesState(application, this.appEventObserverRemovers)
this.tags = new TagsState(application, this.appEventObserverRemovers, this.features) this.tags = new TagsState(application, this.appEventObserverRemovers, this.features)
this.noAccountWarning = new NoAccountWarningState(application, this.appEventObserverRemovers) this.noAccountWarning = new NoAccountWarningState(application, this.appEventObserverRemovers)
this.accountMenu = new AccountMenuState(application, this.appEventObserverRemovers) this.accountMenu = new AccountMenuState(application, this.appEventObserverRemovers)
@@ -155,8 +155,6 @@ export class AppState {
this.appEventObserverRemovers.forEach((remover) => remover()) this.appEventObserverRemovers.forEach((remover) => remover())
this.appEventObserverRemovers.length = 0 this.appEventObserverRemovers.length = 0
this.features.deinit()
;(this.features as unknown) = undefined ;(this.features as unknown) = undefined
this.webAppEventDisposer?.() this.webAppEventDisposer?.()

View File

@@ -1,38 +1,29 @@
import { WebApplication } from '@/UIModels/Application'
import { isDev } from '@/Utils' import { isDev } from '@/Utils'
import { ApplicationEvent, FeatureIdentifier, FeatureStatus } from '@standardnotes/snjs' import { ApplicationEvent, FeatureIdentifier, FeatureStatus } from '@standardnotes/snjs'
import { action, computed, makeObservable, observable, runInAction, when } from 'mobx' import { action, computed, makeObservable, observable, runInAction, when } from 'mobx'
import { WebApplication } from '../Application'
export const TAG_FOLDERS_FEATURE_NAME = 'Tag folders'
export const TAG_FOLDERS_FEATURE_TOOLTIP = 'A Plus or Pro plan is required to enable Tag folders.'
export const SMART_TAGS_FEATURE_NAME = 'Smart Tags'
/**
* Holds state for premium/non premium features for the current user features,
* and eventually for in-development features (feature flags).
*/
export class FeaturesState { export class FeaturesState {
readonly enableUnfinishedFeatures: boolean = window?.enabledUnfinishedFeatures hasFolders: boolean
hasSmartViews: boolean
hasFilesBeta: boolean
premiumAlertFeatureName: string | undefined
_hasFolders = false constructor(private application: WebApplication, appObservers: (() => void)[]) {
_hasSmartViews = false this.hasFolders = this.hasNativeFolders()
_hasFilesBeta = false this.hasSmartViews = this.hasNativeSmartViews()
_premiumAlertFeatureName: string | undefined this.hasFilesBeta = this.isEntitledToFilesBeta()
this.premiumAlertFeatureName = undefined
private unsub: () => void
constructor(private application: WebApplication) {
this._hasFolders = this.hasNativeFolders()
this._hasSmartViews = this.hasNativeSmartViews()
this._hasFilesBeta = this.isEntitledToFilesBeta()
this._premiumAlertFeatureName = undefined
makeObservable(this, { makeObservable(this, {
_hasFolders: observable, hasFolders: observable,
_hasSmartViews: observable, hasSmartViews: observable,
hasFolders: computed,
_premiumAlertFeatureName: observable, hasFilesBeta: observable,
isFilesEnabled: computed,
isEntitledToFiles: computed,
premiumAlertFeatureName: observable,
showPremiumAlert: action, showPremiumAlert: action,
closePremiumAlert: action, closePremiumAlert: action,
}) })
@@ -40,49 +31,36 @@ export class FeaturesState {
this.showPremiumAlert = this.showPremiumAlert.bind(this) this.showPremiumAlert = this.showPremiumAlert.bind(this)
this.closePremiumAlert = this.closePremiumAlert.bind(this) this.closePremiumAlert = this.closePremiumAlert.bind(this)
this.unsub = this.application.addEventObserver(async (eventName) => { appObservers.push(
switch (eventName) { application.addEventObserver(async (event) => {
case ApplicationEvent.FeaturesUpdated: switch (event) {
case ApplicationEvent.Launched: case ApplicationEvent.FeaturesUpdated:
runInAction(() => { case ApplicationEvent.Launched:
this._hasFolders = this.hasNativeFolders() runInAction(() => {
this._hasSmartViews = this.hasNativeSmartViews() this.hasFolders = this.hasNativeFolders()
this._hasFilesBeta = this.isEntitledToFilesBeta() this.hasSmartViews = this.hasNativeSmartViews()
}) this.hasFilesBeta = this.isEntitledToFilesBeta()
break })
default: }
break }),
} )
})
}
public deinit() {
this.unsub()
}
public get hasFolders(): boolean {
return this._hasFolders
}
public get hasSmartViews(): boolean {
return this._hasSmartViews
}
public get isFilesEnabled(): boolean {
return this._hasFilesBeta || window.enabledUnfinishedFeatures || isDev
}
public get isEntitledToFiles(): boolean {
return this._hasFilesBeta
} }
public async showPremiumAlert(featureName: string): Promise<void> { public async showPremiumAlert(featureName: string): Promise<void> {
this._premiumAlertFeatureName = featureName this.premiumAlertFeatureName = featureName
return when(() => this._premiumAlertFeatureName === undefined) return when(() => this.premiumAlertFeatureName === undefined)
} }
public async closePremiumAlert(): Promise<void> { public closePremiumAlert() {
this._premiumAlertFeatureName = undefined this.premiumAlertFeatureName = undefined
}
get isFilesEnabled(): boolean {
return this.hasFilesBeta || window.enabledUnfinishedFeatures || isDev
}
get isEntitledToFiles(): boolean {
return this.hasFilesBeta
} }
private hasNativeFolders(): boolean { private hasNativeFolders(): boolean {

View File

@@ -1,6 +1,10 @@
import { confirmDialog } from '@/Services/AlertService' import { confirmDialog } from '@/Services/AlertService'
import { STRING_DELETE_TAG } from '@/Strings' import { STRING_DELETE_TAG } from '@/Strings'
import { MAX_MENU_SIZE_MULTIPLIER, MENU_MARGIN_FROM_APP_BORDER } from '@/Constants' import {
MAX_MENU_SIZE_MULTIPLIER,
MENU_MARGIN_FROM_APP_BORDER,
SMART_TAGS_FEATURE_NAME,
} from '@/Constants'
import { import {
ComponentAction, ComponentAction,
ContentType, ContentType,
@@ -15,7 +19,7 @@ import {
} from '@standardnotes/snjs' } from '@standardnotes/snjs'
import { action, computed, makeAutoObservable, makeObservable, observable, runInAction } from 'mobx' import { action, computed, makeAutoObservable, makeObservable, observable, runInAction } from 'mobx'
import { WebApplication } from '../Application' import { WebApplication } from '../Application'
import { FeaturesState, SMART_TAGS_FEATURE_NAME } from './FeaturesState' import { FeaturesState } from './FeaturesState'
type AnyTag = SNTag | SmartView type AnyTag = SNTag | SmartView