feat: add Labs pane to preferences (#892)

* feat: add Labs pane to preferences

* feat: use lab_features value for account switcher

* feat: labs pane with experimental features

* fix: use toggleExperimentalFeature from features service

* fix: show premium modal if not entitled for experimental feature

* fix: add isExperimental && isExperimentalEnabled to EditorMenuItem type

* fix: hide experimental editor if not enabled

* chore(deps): update features and snjs

* fix: remove comment

* fix: remove filtering from reloadExperimentalFeatures

* fix: revert Footer.tsx

* chore(deps): bump @standardnotes packages

* fix: change experimental features layout

Co-authored-by: Johnny Almonte <johnny243@users.noreply.github.com>
Co-authored-by: Mo <mo@standardnotes.com>
This commit is contained in:
Aman Harwara
2022-03-08 22:09:37 +05:30
committed by GitHub
parent 6d64889aad
commit ab6e5ac367
8 changed files with 166 additions and 41 deletions

View File

@@ -34,6 +34,8 @@ export type EditorMenuItem = {
name: string;
component?: SNComponent;
isEntitled: boolean;
isExperimental: boolean;
isExperimentalEnabled: boolean;
};
export type EditorMenuGroup = AccordionMenuGroup<EditorMenuItem>;

View File

@@ -223,6 +223,10 @@ export const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
selectEditor(item);
};
if (item.isExperimental && !item.isExperimentalEnabled) {
return;
}
return (
<MenuItem
type={MenuItemType.RadioButton}

View File

@@ -41,6 +41,8 @@ export const createEditorMenuGroups = (
{
name: PLAIN_EDITOR_NAME,
isEntitled: true,
isExperimental: false,
isExperimentalEnabled: false,
},
],
'rich-text': [],
@@ -67,6 +69,13 @@ export const createEditorMenuGroups = (
editorItems[getEditorGroup(editorFeature)].push({
name: editorFeature.name as string,
isEntitled: false,
isExperimental: application.features.isExperimentalFeature(
editorFeature.identifier
),
isExperimentalEnabled:
application.features.isExperimentalFeatureEnabled(
editorFeature.identifier
),
});
}
});
@@ -78,6 +87,12 @@ export const createEditorMenuGroups = (
isEntitled:
application.features.getFeatureStatus(editor.identifier) ===
FeatureStatus.Entitled,
isExperimental: application.features.isExperimentalFeature(
editor.identifier
),
isExperimentalEnabled: application.features.isExperimentalFeatureEnabled(
editor.identifier
),
};
editorItems[getEditorGroup(editor.package_info)].push(editorItem);

View File

@@ -2,7 +2,7 @@ import { WebApplication } from '@/ui_models/application';
import { AppState } from '@/ui_models/app_state';
import { FunctionComponent } from 'preact';
import { PreferencesPane } from '../components';
import { ErrorReporting, Tools, Defaults } from './general-segments';
import { ErrorReporting, Tools, Defaults, LabsPane } from './general-segments';
import { ExtensionsLatestVersions } from '@/preferences/panes/extensions-segments';
import { Advanced } from '@/preferences/panes/account';
import { observer } from 'mobx-react-lite';
@@ -19,6 +19,7 @@ export const General: FunctionComponent<GeneralProps> = observer(
<Tools application={application} />
<Defaults application={application} />
<ErrorReporting appState={appState} />
<LabsPane application={application} />
<Advanced
application={application}
appState={appState}

View File

@@ -0,0 +1,102 @@
import { FindNativeFeature } from '@standardnotes/features';
import { Switch } from '@/components/Switch';
import {
PreferencesGroup,
PreferencesSegment,
Subtitle,
Text,
Title,
} from '@/preferences/components';
import { WebApplication } from '@/ui_models/application';
import { FeatureIdentifier, FeatureStatus } from '@standardnotes/snjs';
import { FunctionComponent } from 'preact';
import { useCallback, useEffect, useState } from 'preact/hooks';
import { usePremiumModal } from '@/components/Premium';
import { HorizontalSeparator } from '@/components/shared/HorizontalSeparator';
type Props = {
application: WebApplication;
};
export const LabsPane: FunctionComponent<Props> = ({ application }) => {
const [experimentalFeatures, setExperimentalFeatures] =
useState<FeatureIdentifier[]>();
const reloadExperimentalFeatures = useCallback(() => {
const experimentalFeatures = application.features.getExperimentalFeatures();
setExperimentalFeatures(experimentalFeatures);
}, [application.features]);
useEffect(() => {
reloadExperimentalFeatures();
}, [reloadExperimentalFeatures]);
const premiumModal = usePremiumModal();
if (!experimentalFeatures) {
return (
<div className="flex items-center justify-between">
No experimental features available.
</div>
);
}
return (
<PreferencesGroup>
<PreferencesSegment>
<Title>Labs</Title>
<div>
{experimentalFeatures?.map(
(featureIdentifier: FeatureIdentifier, index: number) => {
const feature = FindNativeFeature(featureIdentifier);
const featureName = feature?.name ?? featureIdentifier;
const featureDescription = feature?.description ?? '';
const isFeatureEnabled =
application.features.isExperimentalFeatureEnabled(
featureIdentifier
);
const toggleFeature = () => {
const isEntitled =
application.features.getFeatureStatus(featureIdentifier) ===
FeatureStatus.Entitled;
if (!isEntitled) {
premiumModal.activate(featureName);
return;
}
application.features.toggleExperimentalFeature(
featureIdentifier
);
reloadExperimentalFeatures();
};
const showHorizontalSeparator =
experimentalFeatures.length > 1 &&
index !== experimentalFeatures.length - 1;
return (
<>
<div className="flex items-center justify-between">
<div className="flex flex-col">
<Subtitle>{featureName}</Subtitle>
<Text>{featureDescription}</Text>
</div>
<Switch
onChange={toggleFeature}
checked={isFeatureEnabled}
/>
</div>
{showHorizontalSeparator && (
<HorizontalSeparator classes="mt-5 mb-3" />
)}
</>
);
}
)}
</div>
</PreferencesSegment>
</PreferencesGroup>
);
};

View File

@@ -1,3 +1,4 @@
export * from './ErrorReporting';
export * from './Tools';
export * from './Defaults';
export * from './Labs';