mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-23 08:53:27 +03:00
feat(core): add configuration for experimental features (#7699)
close AF-1218 AF-1219 Added configuration for experimental features Example: ``` const blocksuiteFeatureFlags = { ... enable_expand_database_block: { displayName: 'Enable Expand Database Block', description: 'Allows expanding database blocks for better view and management.', feedbackType: 'discord', displayChannel: ['stable', 'beta', 'canary', 'internal'], restrictedPlatform: 'client' }, enable_ai_onboarding: { displayName: 'AI Onboarding', description: 'Enables AI onboarding.', displayChannel: [], defaultState: true, }, ... } ``` ![CleanShot 2024-08-02 at 12 26 36@2x](https://github.com/user-attachments/assets/98b1e8e7-cd8b-4309-8063-323b2f3b5a94)
This commit is contained in:
parent
6228b27271
commit
9037e6695e
@ -23,7 +23,7 @@
|
||||
|
||||
<div align="center">
|
||||
<a href="https://affine.pro">Home Page</a> |
|
||||
<a href="https://discord.com/invite/yz6tGVsf5p">Discord</a> |
|
||||
<a href="https://discord.gg/whd5mjYqVw">Discord</a> |
|
||||
<a href="https://app.affine.pro">Live Demo</a> |
|
||||
<a href="https://affine.pro/blog/">Blog</a> |
|
||||
<a href="https://docs.affine.pro/docs/">Documentation</a>
|
||||
|
@ -148,7 +148,7 @@ export const emailTemplate = ({
|
||||
</a>
|
||||
</td>
|
||||
<td style="padding: 0 10px">
|
||||
<a href="https://discord.gg/Arn7TqJBvG" target="_blank"
|
||||
<a href="https://discord.gg/whd5mjYqVw" target="_blank"
|
||||
><img
|
||||
src="https://cdn.affine.pro/mail/2023-8-9/Discord.png"
|
||||
alt="AFFiNE discord link"
|
||||
|
@ -91,11 +91,14 @@ export function setupEditorFlags(docCollection: DocCollection) {
|
||||
|
||||
// override this flag in app settings
|
||||
// TODO(@eyhn): need a better way to manage block suite flags
|
||||
docCollection.awarenessStore.setFlag('enable_synced_doc_block', true);
|
||||
docCollection.awarenessStore.setFlag('enable_edgeless_text', true);
|
||||
docCollection.awarenessStore.setFlag('enable_color_picker', true);
|
||||
docCollection.awarenessStore.setFlag('enable_ai_chat_block', true);
|
||||
docCollection.awarenessStore.setFlag('enable_ai_onboarding', true);
|
||||
Object.entries(blocksuiteFeatureFlags).forEach(([key, value]) => {
|
||||
if (value.defaultState !== undefined) {
|
||||
docCollection.awarenessStore.setFlag(
|
||||
key as keyof BlockSuiteFlags,
|
||||
value.defaultState
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('syncEditorFlags', err);
|
||||
}
|
||||
@ -140,3 +143,89 @@ export const appSettingAtom = atom<
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export type BuildChannel = 'stable' | 'beta' | 'canary' | 'internal';
|
||||
|
||||
export type FeedbackType = 'discord' | 'email' | 'github';
|
||||
|
||||
export type PreconditionType = () => boolean | undefined;
|
||||
|
||||
export type Flag<K extends string> = Partial<{
|
||||
[key in K]: {
|
||||
displayName: string;
|
||||
description?: string;
|
||||
precondition?: PreconditionType;
|
||||
defaultState?: boolean; // default to open and not controlled by user
|
||||
feedbackType?: FeedbackType;
|
||||
};
|
||||
}>;
|
||||
|
||||
const isNotStableBuild: PreconditionType = () => {
|
||||
return runtimeConfig.appBuildType !== 'stable';
|
||||
};
|
||||
const isDesktopEnvironment: PreconditionType = () => environment.isDesktop;
|
||||
const neverShow: PreconditionType = () => false;
|
||||
|
||||
export const blocksuiteFeatureFlags: Flag<keyof BlockSuiteFlags> = {
|
||||
enable_database_attachment_note: {
|
||||
displayName: 'Database Attachment Note',
|
||||
description: 'Allows adding notes to database attachments.',
|
||||
precondition: isNotStableBuild,
|
||||
},
|
||||
enable_database_statistics: {
|
||||
displayName: 'Database Block Statistics',
|
||||
description: 'Shows statistics for database blocks.',
|
||||
precondition: isNotStableBuild,
|
||||
},
|
||||
enable_block_query: {
|
||||
displayName: 'Todo Block Query',
|
||||
description: 'Enables querying of todo blocks.',
|
||||
precondition: isNotStableBuild,
|
||||
},
|
||||
enable_synced_doc_block: {
|
||||
displayName: 'Synced Doc Block',
|
||||
description: 'Enables syncing of doc blocks.',
|
||||
precondition: neverShow,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_edgeless_text: {
|
||||
displayName: 'Edgeless Text',
|
||||
description: 'Enables edgeless text blocks.',
|
||||
precondition: neverShow,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_color_picker: {
|
||||
displayName: 'Color Picker',
|
||||
description: 'Enables color picker blocks.',
|
||||
precondition: neverShow,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_ai_chat_block: {
|
||||
displayName: 'AI Chat Block',
|
||||
description: 'Enables AI chat blocks.',
|
||||
precondition: neverShow,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_ai_onboarding: {
|
||||
displayName: 'AI Onboarding',
|
||||
description: 'Enables AI onboarding.',
|
||||
precondition: neverShow,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_expand_database_block: {
|
||||
displayName: 'Expand Database Block',
|
||||
description: 'Enables expanding of database blocks.',
|
||||
precondition: neverShow,
|
||||
defaultState: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const affineFeatureFlags: Flag<keyof AppSetting> = {
|
||||
enableMultiView: {
|
||||
displayName: 'Split View',
|
||||
description:
|
||||
'The Split View feature in AFFiNE allows users to divide their workspace into multiple sections, enabling simultaneous viewing and editing of different documents.The Split View feature in AFFiNE allows users to divide their workspace into multiple sections, enabling simultaneous viewing and editing of different documents.',
|
||||
feedbackType: 'discord',
|
||||
precondition: isDesktopEnvironment,
|
||||
},
|
||||
};
|
||||
|
@ -21,7 +21,7 @@ export const relatedLinks = [
|
||||
{
|
||||
icon: <DiscordIcon />,
|
||||
title: 'Discord',
|
||||
link: 'https://discord.gg/Arn7TqJBvG',
|
||||
link: 'https://discord.gg/whd5mjYqVw',
|
||||
},
|
||||
{
|
||||
icon: <YouTubeIcon />,
|
||||
|
@ -54,6 +54,7 @@ export const switchRow = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
});
|
||||
export const switchDisabled = style({
|
||||
opacity: 0.5,
|
||||
@ -64,3 +65,34 @@ export const subHeader = style({
|
||||
color: cssVar('textSecondaryColor'),
|
||||
marginBottom: 8,
|
||||
});
|
||||
|
||||
export const rowContainer = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
});
|
||||
export const description = style({
|
||||
color: cssVar('textSecondaryColor'),
|
||||
fontSize: cssVar('fontXs'),
|
||||
// 2 lines
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
width: '100%',
|
||||
});
|
||||
export const feedback = style({
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
fontSize: cssVar('fontXs'),
|
||||
color: cssVar('textSecondaryColor'),
|
||||
gap: 8,
|
||||
});
|
||||
|
||||
export const arrowRightIcon = style({
|
||||
marginLeft: 'auto',
|
||||
marginRight: 0,
|
||||
});
|
||||
|
@ -1,8 +1,19 @@
|
||||
import { Button, Checkbox, Loading, Switch } from '@affine/component';
|
||||
import { Button, Checkbox, Loading, Switch, Tooltip } from '@affine/component';
|
||||
import { SettingHeader } from '@affine/component/setting-components';
|
||||
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
|
||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import {
|
||||
ArrowRightSmallIcon,
|
||||
DiscordIcon,
|
||||
EmailIcon,
|
||||
GithubIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
import {
|
||||
affineFeatureFlags,
|
||||
blocksuiteFeatureFlags,
|
||||
type FeedbackType,
|
||||
} from '@toeverything/infra';
|
||||
import { useAtom } from 'jotai';
|
||||
import { atomWithStorage } from 'jotai/utils';
|
||||
import { Suspense, useCallback, useState } from 'react';
|
||||
@ -75,28 +86,75 @@ const ExperimentalFeaturesPrompt = ({
|
||||
);
|
||||
};
|
||||
|
||||
const FeedbackIcon = ({ type }: { type: FeedbackType }) => {
|
||||
switch (type) {
|
||||
case 'discord':
|
||||
return <DiscordIcon fontSize={16} />;
|
||||
case 'email':
|
||||
return <EmailIcon fontSize={16} />;
|
||||
case 'github':
|
||||
return <GithubIcon fontSize={16} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const feedbackLink: Record<FeedbackType, string> = {
|
||||
discord: 'https://discord.gg/whd5mjYqVw',
|
||||
email: 'mailto:support@toeverything.info',
|
||||
github: 'https://github.com/toeverything/AFFiNE/issues',
|
||||
};
|
||||
|
||||
const ExperimentalFeaturesItem = ({
|
||||
title,
|
||||
description,
|
||||
feedbackType,
|
||||
isMutating,
|
||||
checked,
|
||||
onChange,
|
||||
testId,
|
||||
}: {
|
||||
title: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
feedbackType?: FeedbackType;
|
||||
isMutating?: boolean;
|
||||
checked: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
testId?: string;
|
||||
}) => {
|
||||
const link = feedbackType ? feedbackLink[feedbackType] : undefined;
|
||||
|
||||
return (
|
||||
<div className={styles.switchRow}>
|
||||
{title}
|
||||
<Switch
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
className={isMutating ? styles.switchDisabled : ''}
|
||||
data-testid={testId}
|
||||
/>
|
||||
<div className={styles.rowContainer}>
|
||||
<div className={styles.switchRow}>
|
||||
{title}
|
||||
<Switch
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
className={isMutating ? styles.switchDisabled : ''}
|
||||
data-testid={testId}
|
||||
/>
|
||||
</div>
|
||||
{!!description && (
|
||||
<Tooltip content={description}>
|
||||
<div className={styles.description}>{description}</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!!feedbackType && (
|
||||
<a
|
||||
className={styles.feedback}
|
||||
href={link}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<FeedbackIcon type={feedbackType} />
|
||||
<span>Discussion about this feature</span>
|
||||
<ArrowRightSmallIcon
|
||||
fontSize={20}
|
||||
className={styles.arrowRightIcon}
|
||||
/>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -110,28 +168,24 @@ const SplitViewSettingRow = () => {
|
||||
},
|
||||
[updateSettings]
|
||||
);
|
||||
const multiViewFlagConfig = affineFeatureFlags['enableMultiView'];
|
||||
const shouldShow = multiViewFlagConfig?.precondition?.();
|
||||
|
||||
if (!environment.isDesktop) {
|
||||
return null; // only enable on desktop
|
||||
if (!multiViewFlagConfig || !shouldShow) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ExperimentalFeaturesItem
|
||||
title="Split View"
|
||||
title={multiViewFlagConfig.displayName}
|
||||
description={multiViewFlagConfig.description}
|
||||
feedbackType={multiViewFlagConfig.feedbackType}
|
||||
checked={appSettings.enableMultiView}
|
||||
onChange={onToggle}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// feature flag -> display name
|
||||
const blocksuiteFeatureFlags: Partial<Record<keyof BlockSuiteFlags, string>> = {
|
||||
enable_expand_database_block: 'Enable Expand Database Block',
|
||||
enable_database_attachment_note: 'Enable Database Attachment Note',
|
||||
enable_database_statistics: 'Enable Database Block Statistics',
|
||||
enable_block_query: 'Enable Todo Block Query',
|
||||
};
|
||||
|
||||
const BlocksuiteFeatureFlagSettings = () => {
|
||||
const { appSettings, updateSettings } = useAppSettingHelper();
|
||||
const toggleSetting = useCallback(
|
||||
@ -148,16 +202,25 @@ const BlocksuiteFeatureFlagSettings = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.entries(blocksuiteFeatureFlags).map(([flag, displayName]) => (
|
||||
<ExperimentalFeaturesItem
|
||||
key={flag}
|
||||
title={'Block Suite: ' + displayName}
|
||||
checked={!!appSettings.editorFlags?.[flag as EditorFlag]}
|
||||
onChange={checked =>
|
||||
toggleSetting(flag as keyof BlockSuiteFlags, checked)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{Object.entries(blocksuiteFeatureFlags).map(([key, value]) => {
|
||||
const hidden = value.precondition && !value.precondition();
|
||||
|
||||
if (hidden) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ExperimentalFeaturesItem
|
||||
key={key}
|
||||
title={'Block Suite: ' + value.displayName}
|
||||
description={value.description}
|
||||
feedbackType={value.feedbackType}
|
||||
checked={!!appSettings.editorFlags?.[key as EditorFlag]}
|
||||
onChange={checked =>
|
||||
toggleSetting(key as keyof BlockSuiteFlags, checked)
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -171,6 +234,9 @@ const ExperimentalFeaturesMain = () => {
|
||||
title={t[
|
||||
'com.affine.settings.workspace.experimental-features.header.plugins'
|
||||
]()}
|
||||
subtitle={t[
|
||||
'com.affine.settings.workspace.experimental-features.header.subtitle'
|
||||
]()}
|
||||
/>
|
||||
<div
|
||||
className={styles.settingsContainer}
|
||||
|
@ -40,9 +40,6 @@ export const useGeneralSettingList = (): GeneralSettingList => {
|
||||
const hasPaymentFeature = useLiveData(
|
||||
serverConfigService.serverConfig.features$.map(f => f?.payment)
|
||||
);
|
||||
const isEarlyAccess = useLiveData(
|
||||
userFeatureService.userFeature.isEarlyAccess$
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
userFeatureService.userFeature.revalidate();
|
||||
@ -86,7 +83,7 @@ export const useGeneralSettingList = (): GeneralSettingList => {
|
||||
}
|
||||
}
|
||||
|
||||
if (isEarlyAccess || runtimeConfig.enableExperimentalFeature) {
|
||||
if (runtimeConfig.enableExperimentalFeature) {
|
||||
settings.push({
|
||||
key: 'experimental-features',
|
||||
title: t['com.affine.settings.workspace.experimental-features'](),
|
||||
|
@ -1247,6 +1247,7 @@
|
||||
"com.affine.settings.workspace.experimental-features": "Experimental features",
|
||||
"com.affine.settings.workspace.experimental-features.get-started": "Get started",
|
||||
"com.affine.settings.workspace.experimental-features.header.plugins": "Experimental features",
|
||||
"com.affine.settings.workspace.experimental-features.header.subtitle": "You can customize your workspace here.",
|
||||
"com.affine.settings.workspace.experimental-features.prompt-disclaimer": "I am aware of the risks, and I am willing to continue to use it.",
|
||||
"com.affine.settings.workspace.experimental-features.prompt-header": "Do you want to use the plugin system that is in an experimental stage?",
|
||||
"com.affine.settings.workspace.experimental-features.prompt-warning": "You are about to enable an experimental feature. This feature is still in development and may contain errors or behave unpredictably. Please proceed with caution and at your own risk.",
|
||||
|
Loading…
Reference in New Issue
Block a user