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:
@@ -34,6 +34,8 @@ export type EditorMenuItem = {
|
|||||||
name: string;
|
name: string;
|
||||||
component?: SNComponent;
|
component?: SNComponent;
|
||||||
isEntitled: boolean;
|
isEntitled: boolean;
|
||||||
|
isExperimental: boolean;
|
||||||
|
isExperimentalEnabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EditorMenuGroup = AccordionMenuGroup<EditorMenuItem>;
|
export type EditorMenuGroup = AccordionMenuGroup<EditorMenuItem>;
|
||||||
|
|||||||
@@ -223,6 +223,10 @@ export const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
|||||||
selectEditor(item);
|
selectEditor(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (item.isExperimental && !item.isExperimentalEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
type={MenuItemType.RadioButton}
|
type={MenuItemType.RadioButton}
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ export const createEditorMenuGroups = (
|
|||||||
{
|
{
|
||||||
name: PLAIN_EDITOR_NAME,
|
name: PLAIN_EDITOR_NAME,
|
||||||
isEntitled: true,
|
isEntitled: true,
|
||||||
|
isExperimental: false,
|
||||||
|
isExperimentalEnabled: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'rich-text': [],
|
'rich-text': [],
|
||||||
@@ -67,6 +69,13 @@ export const createEditorMenuGroups = (
|
|||||||
editorItems[getEditorGroup(editorFeature)].push({
|
editorItems[getEditorGroup(editorFeature)].push({
|
||||||
name: editorFeature.name as string,
|
name: editorFeature.name as string,
|
||||||
isEntitled: false,
|
isEntitled: false,
|
||||||
|
isExperimental: application.features.isExperimentalFeature(
|
||||||
|
editorFeature.identifier
|
||||||
|
),
|
||||||
|
isExperimentalEnabled:
|
||||||
|
application.features.isExperimentalFeatureEnabled(
|
||||||
|
editorFeature.identifier
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -78,6 +87,12 @@ export const createEditorMenuGroups = (
|
|||||||
isEntitled:
|
isEntitled:
|
||||||
application.features.getFeatureStatus(editor.identifier) ===
|
application.features.getFeatureStatus(editor.identifier) ===
|
||||||
FeatureStatus.Entitled,
|
FeatureStatus.Entitled,
|
||||||
|
isExperimental: application.features.isExperimentalFeature(
|
||||||
|
editor.identifier
|
||||||
|
),
|
||||||
|
isExperimentalEnabled: application.features.isExperimentalFeatureEnabled(
|
||||||
|
editor.identifier
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
editorItems[getEditorGroup(editor.package_info)].push(editorItem);
|
editorItems[getEditorGroup(editor.package_info)].push(editorItem);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { WebApplication } from '@/ui_models/application';
|
|||||||
import { AppState } from '@/ui_models/app_state';
|
import { AppState } from '@/ui_models/app_state';
|
||||||
import { FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
import { PreferencesPane } from '../components';
|
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 { ExtensionsLatestVersions } from '@/preferences/panes/extensions-segments';
|
||||||
import { Advanced } from '@/preferences/panes/account';
|
import { Advanced } from '@/preferences/panes/account';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
@@ -19,6 +19,7 @@ export const General: FunctionComponent<GeneralProps> = observer(
|
|||||||
<Tools application={application} />
|
<Tools application={application} />
|
||||||
<Defaults application={application} />
|
<Defaults application={application} />
|
||||||
<ErrorReporting appState={appState} />
|
<ErrorReporting appState={appState} />
|
||||||
|
<LabsPane application={application} />
|
||||||
<Advanced
|
<Advanced
|
||||||
application={application}
|
application={application}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './ErrorReporting';
|
export * from './ErrorReporting';
|
||||||
export * from './Tools';
|
export * from './Tools';
|
||||||
export * from './Defaults';
|
export * from './Defaults';
|
||||||
|
export * from './Labs';
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -27,8 +27,8 @@
|
|||||||
"@babel/preset-typescript": "^7.16.7",
|
"@babel/preset-typescript": "^7.16.7",
|
||||||
"@reach/disclosure": "^0.16.2",
|
"@reach/disclosure": "^0.16.2",
|
||||||
"@reach/visually-hidden": "^0.16.0",
|
"@reach/visually-hidden": "^0.16.0",
|
||||||
"@standardnotes/responses": "^1.3.0",
|
"@standardnotes/responses": "1.3.1",
|
||||||
"@standardnotes/services": "^1.5.2",
|
"@standardnotes/services": "1.5.3",
|
||||||
"@standardnotes/stylekit": "5.15.0",
|
"@standardnotes/stylekit": "5.15.0",
|
||||||
"@svgr/webpack": "^6.2.1",
|
"@svgr/webpack": "^6.2.1",
|
||||||
"@types/jest": "^27.4.1",
|
"@types/jest": "^27.4.1",
|
||||||
@@ -79,10 +79,10 @@
|
|||||||
"@reach/listbox": "^0.16.2",
|
"@reach/listbox": "^0.16.2",
|
||||||
"@reach/tooltip": "^0.16.2",
|
"@reach/tooltip": "^0.16.2",
|
||||||
"@standardnotes/components": "1.7.9",
|
"@standardnotes/components": "1.7.9",
|
||||||
"@standardnotes/features": "1.34.2",
|
"@standardnotes/features": "1.34.3",
|
||||||
"@standardnotes/settings": "^1.12.0",
|
"@standardnotes/settings": "1.12.0",
|
||||||
"@standardnotes/sncrypto-web": "1.7.3",
|
"@standardnotes/sncrypto-web": "1.7.3",
|
||||||
"@standardnotes/snjs": "2.77.0",
|
"@standardnotes/snjs": "2.77.1",
|
||||||
"mobx": "^6.4.2",
|
"mobx": "^6.4.2",
|
||||||
"mobx-react-lite": "^3.3.0",
|
"mobx-react-lite": "^3.3.0",
|
||||||
"preact": "^10.6.6",
|
"preact": "^10.6.6",
|
||||||
|
|||||||
70
yarn.lock
70
yarn.lock
@@ -2331,53 +2331,53 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@standardnotes/components/-/components-1.7.9.tgz#41e5fdbcee250b9b3c18045dad8998c6f668307b"
|
resolved "https://registry.yarnpkg.com/@standardnotes/components/-/components-1.7.9.tgz#41e5fdbcee250b9b3c18045dad8998c6f668307b"
|
||||||
integrity sha512-/+Paw6ry/IS9ldYUM/lgC4O6qwl1fukWvNw65IMKyB9LMY3+xTh/I2BfnWynP117pVPxtu3/2+FBEnx04KvQwg==
|
integrity sha512-/+Paw6ry/IS9ldYUM/lgC4O6qwl1fukWvNw65IMKyB9LMY3+xTh/I2BfnWynP117pVPxtu3/2+FBEnx04KvQwg==
|
||||||
|
|
||||||
"@standardnotes/domain-events@^2.23.24":
|
"@standardnotes/domain-events@^2.23.25":
|
||||||
version "2.23.24"
|
version "2.23.25"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/domain-events/-/domain-events-2.23.24.tgz#7872c178491cffb8ad9efa20eca3610a30ca669e"
|
resolved "https://registry.yarnpkg.com/@standardnotes/domain-events/-/domain-events-2.23.25.tgz#bab5773fd1355a94fe35faba995b53ae414e325a"
|
||||||
integrity sha512-R8n1J4YHnY0qxUJcZt91eth+87Dy//GncsC+mpkNwEgtrzAAkS6lXO4xFwYVw7UHp6EmLtsKCnbXk+72WPzd9A==
|
integrity sha512-2nhsCRAbAowtBvzXgRVBo+o4blz1VfmGLM32TabW1EEXHI1Y5K/qQ7+OrZ9TPkd6B0JNC4od/OCY7rLCma8BEg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.17.3"
|
"@standardnotes/auth" "^3.17.3"
|
||||||
"@standardnotes/features" "^1.34.2"
|
"@standardnotes/features" "^1.34.3"
|
||||||
|
|
||||||
"@standardnotes/features@1.34.2", "@standardnotes/features@^1.34.2":
|
"@standardnotes/features@1.34.3", "@standardnotes/features@^1.34.3":
|
||||||
version "1.34.2"
|
version "1.34.3"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.34.2.tgz#a3f2480e88a3873ae7dd34e6b57b9595a9f15c87"
|
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.34.3.tgz#4de3259e43cfd2c2d50a0a113439566a6bed8fca"
|
||||||
integrity sha512-77/DyFMsL+gW4ElVl4n1huNyeYhFA1PyTPbITWkFhQib02aPhhOmjSecxJknFppSNZnBRRqd2hbBa0vMhxUWIA==
|
integrity sha512-WL+nyJsm/mBq2zuR3ZlJ/mJhaVPrm3j7gvBdDr4kxdvMM5y9E4DiYHp98SeUvblIGXWmhZxVc7rdDmVaoYt99g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.17.3"
|
"@standardnotes/auth" "^3.17.3"
|
||||||
"@standardnotes/common" "^1.15.3"
|
"@standardnotes/common" "^1.15.3"
|
||||||
|
|
||||||
"@standardnotes/payloads@^1.4.1":
|
"@standardnotes/payloads@^1.4.2":
|
||||||
version "1.4.1"
|
version "1.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/payloads/-/payloads-1.4.1.tgz#5da68ecc55920080223518c5d5d186380d1a43ca"
|
resolved "https://registry.yarnpkg.com/@standardnotes/payloads/-/payloads-1.4.2.tgz#6f9995a4f585fa814f88bfffdb8d973f078d21d6"
|
||||||
integrity sha512-NXc6Iv2AHIOIzURCuPiHpSgLQwfBFpg8ecozwa+zRXMe1ggEljJGWLutds3ehbqp7C0eCaZr+3pGJhtVdoW06w==
|
integrity sha512-iw2Fhr7oBQgtOtpLnDyCARbP8VWUpT9bhRdgKg47I8Ky5EmyYBcYOf2U8XuXkzypot4+zZNYc5Bgzgs4YXyDzQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/applications" "^1.1.3"
|
"@standardnotes/applications" "^1.1.3"
|
||||||
"@standardnotes/common" "^1.15.3"
|
"@standardnotes/common" "^1.15.3"
|
||||||
"@standardnotes/features" "^1.34.2"
|
"@standardnotes/features" "^1.34.3"
|
||||||
"@standardnotes/utils" "^1.2.3"
|
"@standardnotes/utils" "^1.2.3"
|
||||||
|
|
||||||
"@standardnotes/responses@^1.3.0":
|
"@standardnotes/responses@1.3.1", "@standardnotes/responses@^1.3.1":
|
||||||
version "1.3.0"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/responses/-/responses-1.3.0.tgz#5c4a901e4ee3fec010e83fe6a773b988e601db07"
|
resolved "https://registry.yarnpkg.com/@standardnotes/responses/-/responses-1.3.1.tgz#2890e679b6e635b14e223a9eb5a5d8c44accd530"
|
||||||
integrity sha512-6/g+Hg9yOHvbydw69HNVeblbPH1vzDKSZGYLqWkhKpZjBIHn5dv29PnLfK4OUDJD3C5xFmweYKPBkGuZMuKXrQ==
|
integrity sha512-h9C/DJike0MVQjDkhZhQ8lPCreZKsAuqtEvXpwVl8vcFAOEfeS1BXacvxSlefHx90og6ti9rfF0D/FqzcI+b6Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.17.3"
|
"@standardnotes/auth" "^3.17.3"
|
||||||
"@standardnotes/common" "^1.15.3"
|
"@standardnotes/common" "^1.15.3"
|
||||||
"@standardnotes/features" "^1.34.2"
|
"@standardnotes/features" "^1.34.3"
|
||||||
"@standardnotes/payloads" "^1.4.1"
|
"@standardnotes/payloads" "^1.4.2"
|
||||||
|
|
||||||
"@standardnotes/services@^1.5.2":
|
"@standardnotes/services@1.5.3", "@standardnotes/services@^1.5.3":
|
||||||
version "1.5.2"
|
version "1.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/services/-/services-1.5.2.tgz#7c559785b659694801db391814217a2f8acaac87"
|
resolved "https://registry.yarnpkg.com/@standardnotes/services/-/services-1.5.3.tgz#0cfa7c0336b31c0084e2be976641fb2c36739e5b"
|
||||||
integrity sha512-MSfZmV8+eJp5SMYs9MV1I/WygBTQL0gVK+utfxB8DctC0y1HeEpF4OtooZHmariiQt38tC7vi1/kEpxqgCRL+Q==
|
integrity sha512-NBsF5A4hJZPFOASTSJd60dTuVTiyop1IVZGlmvjs2K8UfHG9/ET59BkVwzPsCsSfnoYSJasVJkxnFunkaeJ/OA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/applications" "^1.1.3"
|
"@standardnotes/applications" "^1.1.3"
|
||||||
"@standardnotes/common" "^1.15.3"
|
"@standardnotes/common" "^1.15.3"
|
||||||
"@standardnotes/responses" "^1.3.0"
|
"@standardnotes/responses" "^1.3.1"
|
||||||
"@standardnotes/utils" "^1.2.3"
|
"@standardnotes/utils" "^1.2.3"
|
||||||
|
|
||||||
"@standardnotes/settings@^1.12.0":
|
"@standardnotes/settings@1.12.0", "@standardnotes/settings@^1.12.0":
|
||||||
version "1.12.0"
|
version "1.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/settings/-/settings-1.12.0.tgz#43f3dd7f015f726b1ed88a48fcc3737899116cd5"
|
resolved "https://registry.yarnpkg.com/@standardnotes/settings/-/settings-1.12.0.tgz#43f3dd7f015f726b1ed88a48fcc3737899116cd5"
|
||||||
integrity sha512-w6S5TT7KRpvUb+JsXZ7ucWPjlWRtpKQdsyT7cLs66ynKRXxUn40hf4kA8T9FhuLAKbG+wIYDrAZl3FRk+HvDWQ==
|
integrity sha512-w6S5TT7KRpvUb+JsXZ7ucWPjlWRtpKQdsyT7cLs66ynKRXxUn40hf4kA8T9FhuLAKbG+wIYDrAZl3FRk+HvDWQ==
|
||||||
@@ -2396,19 +2396,19 @@
|
|||||||
buffer "^6.0.3"
|
buffer "^6.0.3"
|
||||||
libsodium-wrappers "^0.7.9"
|
libsodium-wrappers "^0.7.9"
|
||||||
|
|
||||||
"@standardnotes/snjs@2.77.0":
|
"@standardnotes/snjs@2.77.1":
|
||||||
version "2.77.0"
|
version "2.77.1"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.77.0.tgz#8606cc0c372e27fb099b92c418301f3d0e1add77"
|
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.77.1.tgz#2af90c0837edfc0508579a8f71686e946a81afb6"
|
||||||
integrity sha512-/JAX65BgJstGIvmQRhtm1QUeS9jRzqbyb5KtRR4WgWn9SVuGRZo2BE0d5ywlQ/y/CW64Xmbp9LDFd137O48uFA==
|
integrity sha512-kbCh63YxQCaKKx2AHonsBgtTlHeBR6SFtmchBIXiSKA4O7USroGJilTUaK2DVyK5L89hwafkZfot7R+nvXD9zQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/applications" "^1.1.3"
|
"@standardnotes/applications" "^1.1.3"
|
||||||
"@standardnotes/auth" "^3.17.3"
|
"@standardnotes/auth" "^3.17.3"
|
||||||
"@standardnotes/common" "^1.15.3"
|
"@standardnotes/common" "^1.15.3"
|
||||||
"@standardnotes/domain-events" "^2.23.24"
|
"@standardnotes/domain-events" "^2.23.25"
|
||||||
"@standardnotes/features" "^1.34.2"
|
"@standardnotes/features" "^1.34.3"
|
||||||
"@standardnotes/payloads" "^1.4.1"
|
"@standardnotes/payloads" "^1.4.2"
|
||||||
"@standardnotes/responses" "^1.3.0"
|
"@standardnotes/responses" "^1.3.1"
|
||||||
"@standardnotes/services" "^1.5.2"
|
"@standardnotes/services" "^1.5.3"
|
||||||
"@standardnotes/settings" "^1.12.0"
|
"@standardnotes/settings" "^1.12.0"
|
||||||
"@standardnotes/sncrypto-common" "^1.7.3"
|
"@standardnotes/sncrypto-common" "^1.7.3"
|
||||||
"@standardnotes/utils" "^1.2.3"
|
"@standardnotes/utils" "^1.2.3"
|
||||||
|
|||||||
Reference in New Issue
Block a user