From 24d3aac3c7688e62d8755b29cf623f1697149d4d Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Wed, 20 Apr 2022 20:11:32 +0530 Subject: [PATCH] refactor: make features state consistent with codebase (#988) --- .../Components/Tags/TagsListItem.tsx | 3 +- .../Components/Tags/TagsSectionTitle.tsx | 7 +- app/assets/javascripts/Constants.ts | 4 + .../javascripts/Hooks/usePremiumModal.tsx | 2 +- .../javascripts/UIModels/AppState/AppState.ts | 4 +- .../UIModels/AppState/FeaturesState.ts | 108 +++++++----------- .../UIModels/AppState/TagsState.ts | 8 +- 7 files changed, 59 insertions(+), 77 deletions(-) diff --git a/app/assets/javascripts/Components/Tags/TagsListItem.tsx b/app/assets/javascripts/Components/Tags/TagsListItem.tsx index 1e116d342..f50859215 100644 --- a/app/assets/javascripts/Components/Tags/TagsListItem.tsx +++ b/app/assets/javascripts/Components/Tags/TagsListItem.tsx @@ -1,7 +1,8 @@ import { Icon } from '@/Components/Icon' +import { TAG_FOLDERS_FEATURE_NAME } from '@/Constants' import { usePremiumModal } from '@/Hooks/usePremiumModal' 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 '@reach/tooltip/styles.css' import { SNTag } from '@standardnotes/snjs' diff --git a/app/assets/javascripts/Components/Tags/TagsSectionTitle.tsx b/app/assets/javascripts/Components/Tags/TagsSectionTitle.tsx index 1250fc115..965d9471d 100644 --- a/app/assets/javascripts/Components/Tags/TagsSectionTitle.tsx +++ b/app/assets/javascripts/Components/Tags/TagsSectionTitle.tsx @@ -1,9 +1,6 @@ +import { TAG_FOLDERS_FEATURE_NAME, TAG_FOLDERS_FEATURE_TOOLTIP } from '@/Constants' import { usePremiumModal } from '@/Hooks/usePremiumModal' -import { - FeaturesState, - TAG_FOLDERS_FEATURE_NAME, - TAG_FOLDERS_FEATURE_TOOLTIP, -} from '@/UIModels/AppState/FeaturesState' +import { FeaturesState } from '@/UIModels/AppState/FeaturesState' import { Tooltip } from '@reach/tooltip' import { observer } from 'mobx-react-lite' import { FunctionComponent } from 'preact' diff --git a/app/assets/javascripts/Constants.ts b/app/assets/javascripts/Constants.ts index e0975d24c..72048b38d 100644 --- a/app/assets/javascripts/Constants.ts +++ b/app/assets/javascripts/Constants.ts @@ -16,3 +16,7 @@ export const DAYS_IN_A_YEAR = 365 export const BYTES_IN_ONE_KILOBYTE = 1_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' diff --git a/app/assets/javascripts/Hooks/usePremiumModal.tsx b/app/assets/javascripts/Hooks/usePremiumModal.tsx index cdd067c8d..d58755289 100644 --- a/app/assets/javascripts/Hooks/usePremiumModal.tsx +++ b/app/assets/javascripts/Hooks/usePremiumModal.tsx @@ -31,7 +31,7 @@ interface Props { export const PremiumModalProvider: FunctionalComponent = observer( ({ application, appState, children }) => { - const featureName = appState.features._premiumAlertFeatureName + const featureName = appState.features.premiumAlertFeatureName const activate = appState.features.showPremiumAlert const close = appState.features.closePremiumAlert diff --git a/app/assets/javascripts/UIModels/AppState/AppState.ts b/app/assets/javascripts/UIModels/AppState/AppState.ts index 280be7570..2c616e1ed 100644 --- a/app/assets/javascripts/UIModels/AppState/AppState.ts +++ b/app/assets/javascripts/UIModels/AppState/AppState.ts @@ -102,7 +102,7 @@ export class AppState { 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.noAccountWarning = new NoAccountWarningState(application, this.appEventObserverRemovers) this.accountMenu = new AccountMenuState(application, this.appEventObserverRemovers) @@ -155,8 +155,6 @@ export class AppState { this.appEventObserverRemovers.forEach((remover) => remover()) this.appEventObserverRemovers.length = 0 - - this.features.deinit() ;(this.features as unknown) = undefined this.webAppEventDisposer?.() diff --git a/app/assets/javascripts/UIModels/AppState/FeaturesState.ts b/app/assets/javascripts/UIModels/AppState/FeaturesState.ts index f5dcd8cea..3e9391c46 100644 --- a/app/assets/javascripts/UIModels/AppState/FeaturesState.ts +++ b/app/assets/javascripts/UIModels/AppState/FeaturesState.ts @@ -1,38 +1,29 @@ +import { WebApplication } from '@/UIModels/Application' import { isDev } from '@/Utils' import { ApplicationEvent, FeatureIdentifier, FeatureStatus } from '@standardnotes/snjs' 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 { - readonly enableUnfinishedFeatures: boolean = window?.enabledUnfinishedFeatures + hasFolders: boolean + hasSmartViews: boolean + hasFilesBeta: boolean + premiumAlertFeatureName: string | undefined - _hasFolders = false - _hasSmartViews = false - _hasFilesBeta = false - _premiumAlertFeatureName: string | undefined - - private unsub: () => void - - constructor(private application: WebApplication) { - this._hasFolders = this.hasNativeFolders() - this._hasSmartViews = this.hasNativeSmartViews() - this._hasFilesBeta = this.isEntitledToFilesBeta() - this._premiumAlertFeatureName = undefined + constructor(private application: WebApplication, appObservers: (() => void)[]) { + this.hasFolders = this.hasNativeFolders() + this.hasSmartViews = this.hasNativeSmartViews() + this.hasFilesBeta = this.isEntitledToFilesBeta() + this.premiumAlertFeatureName = undefined makeObservable(this, { - _hasFolders: observable, - _hasSmartViews: observable, - hasFolders: computed, - _premiumAlertFeatureName: observable, + hasFolders: observable, + hasSmartViews: observable, + + hasFilesBeta: observable, + isFilesEnabled: computed, + isEntitledToFiles: computed, + + premiumAlertFeatureName: observable, showPremiumAlert: action, closePremiumAlert: action, }) @@ -40,49 +31,36 @@ export class FeaturesState { this.showPremiumAlert = this.showPremiumAlert.bind(this) this.closePremiumAlert = this.closePremiumAlert.bind(this) - this.unsub = this.application.addEventObserver(async (eventName) => { - switch (eventName) { - case ApplicationEvent.FeaturesUpdated: - case ApplicationEvent.Launched: - runInAction(() => { - this._hasFolders = this.hasNativeFolders() - 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 + appObservers.push( + application.addEventObserver(async (event) => { + switch (event) { + case ApplicationEvent.FeaturesUpdated: + case ApplicationEvent.Launched: + runInAction(() => { + this.hasFolders = this.hasNativeFolders() + this.hasSmartViews = this.hasNativeSmartViews() + this.hasFilesBeta = this.isEntitledToFilesBeta() + }) + } + }), + ) } public async showPremiumAlert(featureName: string): Promise { - this._premiumAlertFeatureName = featureName - return when(() => this._premiumAlertFeatureName === undefined) + this.premiumAlertFeatureName = featureName + return when(() => this.premiumAlertFeatureName === undefined) } - public async closePremiumAlert(): Promise { - this._premiumAlertFeatureName = undefined + public closePremiumAlert() { + this.premiumAlertFeatureName = undefined + } + + get isFilesEnabled(): boolean { + return this.hasFilesBeta || window.enabledUnfinishedFeatures || isDev + } + + get isEntitledToFiles(): boolean { + return this.hasFilesBeta } private hasNativeFolders(): boolean { diff --git a/app/assets/javascripts/UIModels/AppState/TagsState.ts b/app/assets/javascripts/UIModels/AppState/TagsState.ts index 79c1e9f5c..960bb5f25 100644 --- a/app/assets/javascripts/UIModels/AppState/TagsState.ts +++ b/app/assets/javascripts/UIModels/AppState/TagsState.ts @@ -1,6 +1,10 @@ import { confirmDialog } from '@/Services/AlertService' 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 { ComponentAction, ContentType, @@ -15,7 +19,7 @@ import { } from '@standardnotes/snjs' import { action, computed, makeAutoObservable, makeObservable, observable, runInAction } from 'mobx' import { WebApplication } from '../Application' -import { FeaturesState, SMART_TAGS_FEATURE_NAME } from './FeaturesState' +import { FeaturesState } from './FeaturesState' type AnyTag = SNTag | SmartView