diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e2e7a5a16a..87ce7078bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -78,6 +78,7 @@ jobs: ENABLE_ALL_PAGE_FILTER: true ENABLE_LEGACY_PROVIDER: true ENABLE_PRELOADING: false + ENABLE_NEW_SETTING_MODAL: false steps: - uses: actions/checkout@v3 @@ -103,6 +104,7 @@ jobs: ENABLE_ALL_PAGE_FILTER: true ENABLE_LEGACY_PROVIDER: false ENABLE_PRELOADING: false + ENABLE_NEW_SETTING_MODAL: false steps: - uses: actions/checkout@v3 diff --git a/apps/web/package.json b/apps/web/package.json index 985eec07df..2426f818ca 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -22,7 +22,7 @@ "@blocksuite/blocks": "0.0.0-20230607055421-9b20fcaf-nightly", "@blocksuite/editor": "0.0.0-20230607055421-9b20fcaf-nightly", "@blocksuite/global": "0.0.0-20230607055421-9b20fcaf-nightly", - "@blocksuite/icons": "^2.1.19", + "@blocksuite/icons": "^2.1.21", "@blocksuite/lit": "0.0.0-20230607055421-9b20fcaf-nightly", "@blocksuite/store": "0.0.0-20230607055421-9b20fcaf-nightly", "@dnd-kit/core": "^6.0.8", diff --git a/apps/web/preset.config.mjs b/apps/web/preset.config.mjs index 1da7f61365..e0edd9cce6 100644 --- a/apps/web/preset.config.mjs +++ b/apps/web/preset.config.mjs @@ -46,4 +46,8 @@ export const buildFlags = { process.env.ENABLE_PRELOADING === undefined ? true : process.env.ENABLE_PRELOADING === 'true', + enableNewSettingModal: + process.env.ENABLE_NEW_SETTING_MODAL === undefined + ? true + : process.env.ENABLE_PRELOADING === 'true', }; diff --git a/apps/web/src/adapters/affine/index.tsx b/apps/web/src/adapters/affine/index.tsx index f2d4aaa141..363c3f4cae 100644 --- a/apps/web/src/adapters/affine/index.tsx +++ b/apps/web/src/adapters/affine/index.tsx @@ -44,6 +44,7 @@ import { BlockSuiteWorkspace } from '../../shared'; import { toast } from '../../utils'; import { BlockSuitePageList, + NewWorkspaceSettingDetail, PageDetailEditor, WorkspaceHeader, WorkspaceSettingDetail, @@ -364,5 +365,18 @@ export const AffineAdapter: WorkspaceAdapter = { /> ); }, + NewSettingsDetail: ({ + currentWorkspace, + onDeleteWorkspace, + onTransformWorkspace, + }) => { + return ( + + ); + }, }, }; diff --git a/apps/web/src/adapters/local/index.tsx b/apps/web/src/adapters/local/index.tsx index cd0011f26e..2678218e85 100644 --- a/apps/web/src/adapters/local/index.tsx +++ b/apps/web/src/adapters/local/index.tsx @@ -21,6 +21,7 @@ import { nanoid } from '@blocksuite/store'; import { BlockSuitePageList, + NewWorkspaceSettingDetail, PageDetailEditor, WorkspaceHeader, WorkspaceSettingDetail, @@ -115,5 +116,18 @@ export const LocalAdapter: WorkspaceAdapter = { /> ); }, + NewSettingsDetail: ({ + currentWorkspace, + onDeleteWorkspace, + onTransformWorkspace, + }) => { + return ( + + ); + }, }, }; diff --git a/apps/web/src/adapters/shared.ts b/apps/web/src/adapters/shared.ts index 040becc2d2..0d3eef79ef 100644 --- a/apps/web/src/adapters/shared.ts +++ b/apps/web/src/adapters/shared.ts @@ -1,5 +1,5 @@ import { lazy } from 'react'; - +// export { WorkspaceSettingDetail as NewWorkspaceSettingDetail } from '../components/affine/new-workspace-setting-detail'; export const WorkspaceSettingDetail = lazy(() => import('../components/affine/workspace-setting-detail').then( ({ WorkspaceSettingDetail }) => ({ @@ -7,6 +7,13 @@ export const WorkspaceSettingDetail = lazy(() => }) ) ); +export const NewWorkspaceSettingDetail = lazy(() => + import('../components/affine/new-workspace-setting-detail').then( + ({ WorkspaceSettingDetail }) => ({ + default: WorkspaceSettingDetail, + }) + ) +); export const BlockSuitePageList = lazy(() => import('../components/blocksuite/block-suite-page-list').then( ({ BlockSuitePageList }) => ({ diff --git a/apps/web/src/adapters/workspace.ts b/apps/web/src/adapters/workspace.ts index b1a9a7151e..88bb5b921a 100644 --- a/apps/web/src/adapters/workspace.ts +++ b/apps/web/src/adapters/workspace.ts @@ -36,6 +36,7 @@ export const WorkspaceAdapters = { PageDetail: unimplemented, PageList: unimplemented, SettingsDetail: unimplemented, + NewSettingsDetail: unimplemented, }, }, [WorkspaceFlavour.PUBLIC]: { @@ -57,6 +58,7 @@ export const WorkspaceAdapters = { PageDetail: unimplemented, PageList: unimplemented, SettingsDetail: unimplemented, + NewSettingsDetail: unimplemented, }, }, } satisfies { diff --git a/apps/web/src/atoms/index.ts b/apps/web/src/atoms/index.ts index ebbdb61b26..7b3cb962b7 100644 --- a/apps/web/src/atoms/index.ts +++ b/apps/web/src/atoms/index.ts @@ -80,6 +80,7 @@ export const openWorkspacesModalAtom = atom(false); export const openCreateWorkspaceModalAtom = atom(false); export const openQuickSearchModalAtom = atom(false); export const openOnboardingModalAtom = atom(false); +export const openSettingModalAtom = atom(false); export const openDisableCloudAlertModalAtom = atom(false); diff --git a/apps/web/src/atoms/settings.ts b/apps/web/src/atoms/settings.ts new file mode 100644 index 0000000000..4c4bd7a85c --- /dev/null +++ b/apps/web/src/atoms/settings.ts @@ -0,0 +1,67 @@ +import { useAtom } from 'jotai'; +import { atomWithStorage } from 'jotai/utils'; +import { useCallback } from 'react'; + +export type DateFormats = + | 'MM/dd/YYYY' + | 'dd/MM/YYYY' + | 'YYYY-MM-dd' + | 'YYYY.MM.dd' + | 'YYYY/MM/dd' + | 'dd-MMM-YYYY' + | 'dd MMMM YYYY'; + +export type AppSetting = { + clientBorder: boolean; + fullWidthLayout: boolean; + windowFrameStyle: 'frameless' | 'NativeTitleBar'; + dateFormat: DateFormats; + startWeekOnMonday: boolean; + disableBlurBackground: boolean; + disableNoisyBackground: boolean; + autoCheckUpdate: boolean; + autoDownloadUpdate: boolean; +}; +export const windowFrameStyleOptions: AppSetting['windowFrameStyle'][] = [ + 'frameless', + 'NativeTitleBar', +]; + +export const dateFormatOptions: DateFormats[] = [ + 'MM/dd/YYYY', + 'dd/MM/YYYY', + 'YYYY-MM-dd', + 'YYYY.MM.dd', + 'YYYY/MM/dd', + 'dd-MMM-YYYY', + 'dd MMMM YYYY', +]; + +export const AppSettingAtom = atomWithStorage('AFFiNE settings', { + clientBorder: false, + fullWidthLayout: false, + windowFrameStyle: 'frameless', + dateFormat: dateFormatOptions[0], + startWeekOnMonday: false, + disableBlurBackground: false, + disableNoisyBackground: false, + autoCheckUpdate: true, + autoDownloadUpdate: true, +}); + +export const useAppSetting = () => { + const [settings, setSettings] = useAtom(AppSettingAtom); + + return [ + settings, + useCallback( + (patch: Partial) => { + setSettings((prev: AppSetting) => ({ + ...prev, + ...patch, + })); + }, + [setSettings] + ), + ] as const; +}; diff --git a/apps/web/src/components/affine/app-container.tsx b/apps/web/src/components/affine/app-container.tsx new file mode 100644 index 0000000000..3bb8f863fc --- /dev/null +++ b/apps/web/src/components/affine/app-container.tsx @@ -0,0 +1,18 @@ +import { + AppContainer as AppContainerWithoutSettings, + type WorkspaceRootProps, +} from '@affine/component/workspace'; + +import { useAppSetting } from '../../atoms/settings'; + +export const AppContainer = (props: WorkspaceRootProps) => { + const [appSettings] = useAppSetting(); + + return ( + + ); +}; diff --git a/apps/web/src/components/affine/language-menu/index.tsx b/apps/web/src/components/affine/language-menu/index.tsx new file mode 100644 index 0000000000..65d406c6d0 --- /dev/null +++ b/apps/web/src/components/affine/language-menu/index.tsx @@ -0,0 +1,70 @@ +import { Menu, MenuItem, MenuTrigger, styled } from '@affine/component'; +import { LOCALES } from '@affine/i18n'; +import { useI18N } from '@affine/i18n'; +import type { FC, ReactElement } from 'react'; +import { useCallback } from 'react'; + +export const StyledListItem = styled(MenuItem)(() => ({ + width: '132px', + height: '38px', + textTransform: 'capitalize', +})); + +const LanguageMenuContent: FC<{ + currentLanguage?: string; +}> = ({ currentLanguage }) => { + const i18n = useI18N(); + const changeLanguage = useCallback( + (event: string) => { + return i18n.changeLanguage(event); + }, + [i18n] + ); + return ( + <> + {LOCALES.map(option => { + return ( + { + changeLanguage(option.tag).catch(err => { + throw new Error('Failed to change language', err); + }); + }} + > + {option.originalName} + + ); + })} + + ); +}; +export const LanguageMenu: FC = () => { + const i18n = useI18N(); + + const currentLanguage = LOCALES.find(item => item.tag === i18n.language); + + return ( + + ) as ReactElement + } + placement="bottom-end" + trigger="click" + disablePortal={true} + > + + {currentLanguage?.originalName} + + + ); +}; diff --git a/apps/web/src/components/affine/new-workspace-setting-detail/index.tsx b/apps/web/src/components/affine/new-workspace-setting-detail/index.tsx new file mode 100644 index 0000000000..4ee8cd1d7a --- /dev/null +++ b/apps/web/src/components/affine/new-workspace-setting-detail/index.tsx @@ -0,0 +1,36 @@ +import type { + WorkspaceFlavour, + WorkspaceRegistry, +} from '@affine/env/workspace'; +import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name'; +import type { FC } from 'react'; + +import type { AffineOfficialWorkspace } from '../../../shared'; + +export type WorkspaceSettingDetailProps = { + workspace: AffineOfficialWorkspace; + onDeleteWorkspace: () => Promise; + onTransferWorkspace: < + From extends WorkspaceFlavour, + To extends WorkspaceFlavour + >( + from: From, + to: To, + workspace: WorkspaceRegistry[From] + ) => void; +}; + +export const WorkspaceSettingDetail: FC = ({ + workspace, +}) => { + const [workspaceName] = useBlockSuiteWorkspaceName( + workspace.blockSuiteWorkspace ?? null + ); + return ( +
+

New Workspace Setting Coming Soon!

+ + {workspaceName} +
+ ); +}; diff --git a/apps/web/src/components/affine/setting-modal/account-setting/index.tsx b/apps/web/src/components/affine/setting-modal/account-setting/index.tsx new file mode 100644 index 0000000000..16b54d24e2 --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/account-setting/index.tsx @@ -0,0 +1,3 @@ +export const AccountSetting = () => { + return
AccountSetting
; +}; diff --git a/apps/web/src/components/affine/setting-modal/common/setting-header.tsx b/apps/web/src/components/affine/setting-modal/common/setting-header.tsx new file mode 100644 index 0000000000..9da8cedbc0 --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/common/setting-header.tsx @@ -0,0 +1,14 @@ +import type { FC } from 'react'; + +import { settingHeader } from './share.css'; +export const SettingHeader: FC<{ title: string; subtitle?: string }> = ({ + title, + subtitle, +}) => { + return ( +
+
{title}
+
{subtitle}
+
+ ); +}; diff --git a/apps/web/src/components/affine/setting-modal/common/setting-row.tsx b/apps/web/src/components/affine/setting-modal/common/setting-row.tsx new file mode 100644 index 0000000000..0bad48cb2d --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/common/setting-row.tsx @@ -0,0 +1,22 @@ +import type { CSSProperties, FC, PropsWithChildren, ReactElement } from 'react'; + +import { settingRow } from './share.css'; + +export const SettingRow: FC< + PropsWithChildren<{ + name: string; + desc: string | ReactElement; + style?: CSSProperties; + onClick?: () => void; + }> +> = ({ name, desc, children, onClick, style }) => { + return ( +
+
+
{name}
+
{desc}
+
+
{children}
+
+ ); +}; diff --git a/apps/web/src/components/affine/setting-modal/common/share.css.ts b/apps/web/src/components/affine/setting-modal/common/share.css.ts new file mode 100644 index 0000000000..9348c7120e --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/common/share.css.ts @@ -0,0 +1,69 @@ +import { globalStyle, style } from '@vanilla-extract/css'; + +export const settingHeader = style({ + height: '68px', + borderBottom: '1px solid var(--affine-border-color)', + marginBottom: '24px', +}); + +globalStyle(`${settingHeader} .title`, { + fontSize: 'var(--affine-font-base)', + fontWeight: 600, + lineHeight: '24px', + marginBottom: '4px', +}); + +globalStyle(`${settingHeader} .subtitle`, { + fontSize: 'var(--affine-font-xs)', + lineHeight: '16px', + color: 'var(--affine-text-secondary-color)', +}); + +export const wrapper = style({ + borderBottom: '1px solid var(--affine-border-color)', + paddingBottom: '24px', + marginBottom: '24px', + selectors: { + '&:last-of-type': { + borderBottom: 'none', + paddingBottom: '0', + marginBottom: '0', + }, + }, +}); + +globalStyle(`${wrapper} .title`, { + fontSize: 'var(--affine-font-sm)', + fontWeight: 600, + lineHeight: '18px', + color: 'var(--affine-text-secondary-color)', + marginBottom: '16px', +}); + +export const settingRow = style({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: '25px', + color: 'var(--affine-text-primary-color)', +}); + +globalStyle(`${settingRow} .left-col`, { + flexShrink: 0, + maxWidth: '80%', +}); +globalStyle(`${settingRow} .name`, { + marginBottom: '2px', + fontSize: 'var(--affine-font-sm)', + fontWeight: 600, +}); +globalStyle(`${settingRow} .desc`, { + fontSize: 'var(--affine-font-xs)', + color: 'var(--affine-text-secondary-color)', +}); +globalStyle(`${settingRow} .right-col`, { + flexGrow: 1, + display: 'flex', + justifyContent: 'flex-end', + paddingLeft: '15px', +}); diff --git a/apps/web/src/components/affine/setting-modal/common/wrapper.tsx b/apps/web/src/components/affine/setting-modal/common/wrapper.tsx new file mode 100644 index 0000000000..249360fbf6 --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/common/wrapper.tsx @@ -0,0 +1,14 @@ +import type { FC, PropsWithChildren } from 'react'; + +import { wrapper } from './share.css'; +export const Wrapper: FC> = ({ + title, + children, +}) => { + return ( +
+ {title ?
{title}
: null} + {children} +
+ ); +}; diff --git a/apps/web/src/components/affine/setting-modal/config.ts b/apps/web/src/components/affine/setting-modal/config.ts new file mode 100644 index 0000000000..2c794cc4dd --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/config.ts @@ -0,0 +1,2 @@ +// Some settings are not implemented yet, but need to show in the setting modal when boss is watching. +export const IS_EXHIBITION = false; diff --git a/apps/web/src/components/affine/setting-modal/general-setting/about/index.tsx b/apps/web/src/components/affine/setting-modal/general-setting/about/index.tsx new file mode 100644 index 0000000000..99383eb2e2 --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/general-setting/about/index.tsx @@ -0,0 +1,110 @@ +import { Switch } from '@affine/component'; +import { relatedLinks } from '@affine/component/contact-modal'; +import { env } from '@affine/env'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { ArrowRightSmallIcon, OpenInNewIcon } from '@blocksuite/icons'; +import { useCallback } from 'react'; + +import { type AppSetting, useAppSetting } from '../../../../../atoms/settings'; +import { SettingHeader } from '../../common/setting-header'; +import { SettingRow } from '../../common/setting-row'; +import { Wrapper } from '../../common/wrapper'; +import { IS_EXHIBITION } from '../../config'; +import { communityItem, communityWrapper, link } from './style.css'; + +export const AboutAffine = () => { + const t = useAFFiNEI18N(); + const [appSettings, setAppSettings] = useAppSetting(); + const changeSwitch = useCallback( + (key: keyof AppSetting, checked: boolean) => { + setAppSettings({ [key]: checked }); + }, + [setAppSettings] + ); + return ( + <> + + {IS_EXHIBITION && env.isDesktop ? ( + + + + changeSwitch('autoCheckUpdate', checked)} + /> + + + changeSwitch('autoCheckUpdate', checked)} + /> + + { + window.open( + 'https://github.com/toeverything/AFFiNE/releases', + '_blank' + ); + }} + > + + + + ) : null} + + + {t['Official Website']()} + + + + {t['AFFiNE Community']()} + + + + +
+ {relatedLinks.map(({ icon, title, link }) => { + return ( +
{ + window.open(link, '_blank'); + }} + key={title} + > + {icon} +

{title}

+
+ ); + })} +
+
+ + + {t['Privacy']()} + + + + {t['Terms of Use']()} + + + + + ); +}; diff --git a/apps/web/src/components/affine/setting-modal/general-setting/about/style.css.ts b/apps/web/src/components/affine/setting-modal/general-setting/about/style.css.ts new file mode 100644 index 0000000000..9f9a44950e --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/general-setting/about/style.css.ts @@ -0,0 +1,47 @@ +import { globalStyle, style } from '@vanilla-extract/css'; + +export const link = style({ + height: '18px', + display: 'flex', + alignItems: 'center', + color: 'var(--affine-text-primary-color)', + fontSize: 'var(--affine-font-sm)', + fontWeight: 600, + marginBottom: '12px', + selectors: { + '&:last-of-type': { + marginBottom: '0', + }, + }, +}); + +globalStyle(`${link} .icon`, { + color: 'var(--affine-icon-color)', + fontSize: 'var(--affine-font-base)', + marginLeft: '5px', +}); + +export const communityWrapper = style({ + display: 'grid', + justifyContent: 'space-between', + gridTemplateColumns: 'repeat(auto-fill, 84px)', + gridGap: '6px', +}); +export const communityItem = style({ + width: '84px', + height: '58px', + borderRadius: '8px', + border: '1px solid var(--affine-border-color)', + color: 'var(--affine-text-primary-color)', + cursor: 'pointer', +}); +globalStyle(`${communityItem} svg`, { + width: '24px', + height: '24px', + display: 'block', + margin: '8px auto 4px', +}); +globalStyle(`${communityItem} p`, { + fontSize: 'var(--affine-font-xs)', + textAlign: 'center', +}); diff --git a/apps/web/src/components/affine/setting-modal/general-setting/appearance/date-format-setting.tsx b/apps/web/src/components/affine/setting-modal/general-setting/appearance/date-format-setting.tsx new file mode 100644 index 0000000000..51bd7e154a --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/general-setting/appearance/date-format-setting.tsx @@ -0,0 +1,58 @@ +import { Menu, MenuItem, MenuTrigger } from '@affine/component'; +import dayjs from 'dayjs'; +import { type FC, useCallback } from 'react'; + +import { + dateFormatOptions, + type DateFormats, + useAppSetting, +} from '../../../../../atoms/settings'; + +const DateFormatMenuContent: FC<{ + currentOption: DateFormats; + onSelect: (option: DateFormats) => void; +}> = ({ onSelect, currentOption }) => { + return ( + <> + {dateFormatOptions.map(option => { + return ( + { + onSelect(option); + }} + > + {dayjs(new Date()).format(option)} + + ); + })} + + ); +}; +export const DateFormatSetting = () => { + const [appearanceSettings, setAppSettings] = useAppSetting(); + const handleSelect = useCallback( + (option: DateFormats) => { + setAppSettings({ dateFormat: option }); + }, + [setAppSettings] + ); + return ( + + } + placement="bottom-end" + trigger="click" + disablePortal={true} + > + + {dayjs(new Date()).format(appearanceSettings.dateFormat)} + + + ); +}; diff --git a/apps/web/src/components/affine/setting-modal/general-setting/appearance/index.tsx b/apps/web/src/components/affine/setting-modal/general-setting/appearance/index.tsx new file mode 100644 index 0000000000..e39da48ec2 --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/general-setting/appearance/index.tsx @@ -0,0 +1,168 @@ +import { RadioButton, RadioButtonGroup, Switch } from '@affine/component'; +import { env } from '@affine/env'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { useTheme } from 'next-themes'; +import { useCallback } from 'react'; + +import { + type AppSetting, + useAppSetting, + windowFrameStyleOptions, +} from '../../../../../atoms/settings'; +import { LanguageMenu } from '../../../language-menu'; +import { SettingHeader } from '../../common/setting-header'; +import { SettingRow } from '../../common/setting-row'; +import { Wrapper } from '../../common/wrapper'; +import { IS_EXHIBITION } from '../../config'; +import { DateFormatSetting } from './date-format-setting'; +import { settingWrapper } from './style.css'; + +export const ThemeSettings = () => { + const t = useAFFiNEI18N(); + const { setTheme, theme } = useTheme(); + + return ( + { + setTheme(value); + }, + [setTheme] + )} + > + {t['system']()} + {t['light']()} + {t['dark']()} + + ); +}; + +export const AppearanceSettings = () => { + const t = useAFFiNEI18N(); + + const [appSettings, setAppSettings] = useAppSetting(); + const changeSwitch = useCallback( + (key: keyof AppSetting, checked: boolean) => { + setAppSettings({ [key]: checked }); + }, + [setAppSettings] + ); + return ( + <> + + + + + + + +
+ +
+
+ {IS_EXHIBITION && env.isDesktop ? ( + + changeSwitch('clientBorder', checked)} + /> + + ) : null} + + + changeSwitch('fullWidthLayout', checked)} + /> + + {IS_EXHIBITION && env.isDesktop ? ( + + { + setAppSettings({ windowFrameStyle: value }); + }} + > + {windowFrameStyleOptions.map(option => { + return ( + + {t[option]()} + + ); + })} + + + ) : null} +
+ {IS_EXHIBITION ? ( + + +
+ +
+
+ + changeSwitch('startWeekOnMonday', checked)} + /> + +
+ ) : null} + + {env.isDesktop ? ( + + + + changeSwitch('disableNoisyBackground', checked) + } + /> + + + + changeSwitch('disableBlurBackground', checked) + } + /> + + + ) : null} + + ); +}; diff --git a/apps/web/src/components/affine/setting-modal/general-setting/appearance/style.css.ts b/apps/web/src/components/affine/setting-modal/general-setting/appearance/style.css.ts new file mode 100644 index 0000000000..a4eba9c4f5 --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/general-setting/appearance/style.css.ts @@ -0,0 +1,9 @@ +import { style } from '@vanilla-extract/css'; + +export const settingWrapper = style({ + flexGrow: 1, + display: 'flex', + width: '50%', + minWidth: '150px', + maxWidth: '250px', +}); diff --git a/apps/web/src/components/affine/setting-modal/general-setting/index.tsx b/apps/web/src/components/affine/setting-modal/general-setting/index.tsx new file mode 100644 index 0000000000..49183db683 --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/general-setting/index.tsx @@ -0,0 +1,53 @@ +import { + AppearanceIcon, + InformationIcon, + KeyboardIcon, +} from '@blocksuite/icons'; +import type { FC, SVGProps } from 'react'; + +import { AboutAffine } from './about'; +import { AppearanceSettings } from './appearance'; +import { Shortcuts } from './shortcuts'; + +export type GeneralSettingKeys = 'shortcuts' | 'appearance' | 'about'; + +export type GeneralSettingList = { + key: GeneralSettingKeys; + title: string; + icon: FC>; +}[]; + +export const generalSettingList: GeneralSettingList = [ + { + key: 'appearance', + title: 'Appearance', + icon: AppearanceIcon, + }, + { + key: 'shortcuts', + title: 'Keyboard Shortcuts', + icon: KeyboardIcon, + }, + { + key: 'about', + title: 'About AFFiNE', + icon: InformationIcon, + }, +]; + +export const GeneralSetting = ({ + generalKey, +}: { + generalKey: GeneralSettingKeys; +}) => { + switch (generalKey) { + case 'shortcuts': + return ; + case 'appearance': + return ; + case 'about': + return ; + default: + return null; + } +}; diff --git a/apps/web/src/components/affine/setting-modal/general-setting/shortcuts/index.tsx b/apps/web/src/components/affine/setting-modal/general-setting/shortcuts/index.tsx new file mode 100644 index 0000000000..76f6656d9c --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/general-setting/shortcuts/index.tsx @@ -0,0 +1,69 @@ +import { useAFFiNEI18N } from '@affine/i18n/hooks'; + +import { + useEdgelessShortcuts, + useGeneralShortcuts, + useMarkdownShortcuts, + usePageShortcuts, +} from '../../../../../hooks/affine/use-shortcuts'; +import { SettingHeader } from '../../common/setting-header'; +import { Wrapper } from '../../common/wrapper'; +import { shortcutRow } from './style.css'; + +export const Shortcuts = () => { + const t = useAFFiNEI18N(); + + const markdownShortcuts = useMarkdownShortcuts(); + const pageShortcuts = usePageShortcuts(); + const edgelessShortcuts = useEdgelessShortcuts(); + const generalShortcuts = useGeneralShortcuts(); + + return ( + <> + + + {Object.entries(generalShortcuts).map(([title, shortcuts]) => { + return ( +
+ {title} + {shortcuts} +
+ ); + })} +
+ + {Object.entries(pageShortcuts).map(([title, shortcuts]) => { + return ( +
+ {title} + {shortcuts} +
+ ); + })} +
+ + {Object.entries(edgelessShortcuts).map(([title, shortcuts]) => { + return ( +
+ {title} + {shortcuts} +
+ ); + })} +
+ + {Object.entries(markdownShortcuts).map(([title, shortcuts]) => { + return ( +
+ {title} + {shortcuts} +
+ ); + })} +
+ + ); +}; diff --git a/apps/web/src/components/affine/setting-modal/general-setting/shortcuts/style.css.ts b/apps/web/src/components/affine/setting-modal/general-setting/shortcuts/style.css.ts new file mode 100644 index 0000000000..cccce6f937 --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/general-setting/shortcuts/style.css.ts @@ -0,0 +1,21 @@ +import { globalStyle, style } from '@vanilla-extract/css'; + +export const shortcutRow = style({ + height: '32px', + marginBottom: '12px', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + fontSize: 'var(--affine-font-base)', + selectors: { + '&:last-of-type': { + marginBottom: '0', + }, + }, +}); + +globalStyle(`${shortcutRow} .shortcut`, { + border: '1px solid var(--affine-border-color)', + borderRadius: '8px', + padding: '4px 18px', +}); diff --git a/apps/web/src/components/affine/setting-modal/index.tsx b/apps/web/src/components/affine/setting-modal/index.tsx new file mode 100644 index 0000000000..8c9b8b2ffc --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/index.tsx @@ -0,0 +1,140 @@ +import { Modal, ModalCloseButton, ModalWrapper } from '@affine/component'; +import type { + AffineLegacyCloudWorkspace, + LocalWorkspace, +} from '@affine/env/workspace'; +import { WorkspaceFlavour } from '@affine/env/workspace'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { ContactWithUsIcon } from '@blocksuite/icons'; +import type { NextRouter } from 'next/router'; +import type React from 'react'; +import { useCallback, useMemo, useState } from 'react'; + +import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace'; +import { useWorkspaces } from '../../../hooks/use-workspaces'; +import type { BlockSuiteWorkspace } from '../../../shared'; +import { AccountSetting } from './account-setting'; +import { + GeneralSetting, + type GeneralSettingKeys, + generalSettingList, +} from './general-setting'; +import { SettingSidebar } from './setting-sidebar'; +import { settingContent } from './style.css'; +import type { Workspace } from './type'; +import { WorkSpaceSetting } from './workspace-setting'; + +export type QuickSearchModalProps = { + currentWorkspace?: BlockSuiteWorkspace; + workspaceList?: BlockSuiteWorkspace[]; + open: boolean; + setOpen: (value: boolean) => void; + router: NextRouter; +}; + +export const SettingModal: React.FC = ({ + open, + setOpen, +}) => { + const t = useAFFiNEI18N(); + const workspaces = useWorkspaces(); + const [currentWorkspace] = useCurrentWorkspace(); + + const workspaceList = useMemo(() => { + return workspaces.filter( + ({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC + ) as Workspace[]; + }, [workspaces]); + + const [currentRef, setCurrentRef] = useState<{ + workspace: Workspace | null; + generalKey: GeneralSettingKeys | null; + isAccount: boolean; + }>({ + workspace: null, + generalKey: generalSettingList[0].key, + isAccount: false, + }); + const handleClose = useCallback(() => { + setOpen(false); + }, [setOpen]); + + const onGeneralSettingClick = useCallback((key: GeneralSettingKeys) => { + setCurrentRef({ + workspace: null, + generalKey: key, + isAccount: false, + }); + }, []); + const onWorkspaceSettingClick = useCallback((workspace: Workspace) => { + setCurrentRef({ + workspace: workspace, + generalKey: null, + isAccount: false, + }); + }, []); + const onAccountSettingClick = useCallback(() => { + setCurrentRef({ + workspace: null, + generalKey: null, + isAccount: true, + }); + }, []); + + return ( + + + + + + +
+
+
+ {currentRef.workspace ? ( + + ) : null} + {currentRef.generalKey ? ( + + ) : null} + {currentRef.isAccount ? : null} +
+ +
+
+
+
+ ); +}; diff --git a/apps/web/src/components/affine/setting-modal/setting-sidebar/index.tsx b/apps/web/src/components/affine/setting-modal/setting-sidebar/index.tsx new file mode 100644 index 0000000000..90a3d42d56 --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/setting-sidebar/index.tsx @@ -0,0 +1,126 @@ +import { WorkspaceAvatar } from '@affine/component/workspace-avatar'; +import type { + AffineLegacyCloudWorkspace, + LocalWorkspace, +} from '@affine/env/workspace'; +import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name'; +import clsx from 'clsx'; + +import type { + GeneralSettingKeys, + GeneralSettingList, +} from '../general-setting'; +import type { Workspace } from '../type'; +import { + accountButton, + settingSlideBar, + sidebarItemsWrapper, + sidebarSelectItem, + sidebarSubtitle, + sidebarTitle, +} from './style.css'; + +export const SettingSidebar = ({ + generalSettingList, + onGeneralSettingClick, + currentWorkspace, + workspaceList, + onWorkspaceSettingClick, + selectedWorkspace, + selectedGeneralKey, + onAccountSettingClick, +}: { + generalSettingList: GeneralSettingList; + onGeneralSettingClick: (key: GeneralSettingKeys) => void; + currentWorkspace: Workspace; + workspaceList: Workspace[]; + onWorkspaceSettingClick: ( + workspace: AffineLegacyCloudWorkspace | LocalWorkspace + ) => void; + + selectedWorkspace: Workspace | null; + selectedGeneralKey: string | null; + onAccountSettingClick: () => void; +}) => { + return ( +
+
Settings
+
General
+
+ {generalSettingList.map(({ title, icon, key }) => { + return ( +
{ + onGeneralSettingClick(key); + }} + > + {icon({ className: 'icon' })} + {title} +
+ ); + })} +
+ +
Workspace
+
+ {workspaceList.map(workspace => { + return ( + { + onWorkspaceSettingClick(workspace); + }} + isCurrent={workspace.id === currentWorkspace.id} + isActive={workspace.id === selectedWorkspace?.id} + /> + ); + })} +
+ +
+
+
+
+ Account NameAccount Name +
+
+ xxxxxxxx@gmail.comxxxxxxxx@gmail.com +
+
+
+
+ ); +}; + +const WorkspaceListItem = ({ + workspace, + onClick, + isCurrent, + isActive, +}: { + workspace: AffineLegacyCloudWorkspace | LocalWorkspace; + onClick: () => void; + isCurrent: boolean; + isActive: boolean; +}) => { + const [workspaceName] = useBlockSuiteWorkspaceName( + workspace.blockSuiteWorkspace ?? null + ); + return ( +
+ + {workspaceName} + {isCurrent ?
Current
: null} +
+ ); +}; diff --git a/apps/web/src/components/affine/setting-modal/setting-sidebar/style.css.ts b/apps/web/src/components/affine/setting-modal/setting-sidebar/style.css.ts new file mode 100644 index 0000000000..04ee3e1d36 --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/setting-sidebar/style.css.ts @@ -0,0 +1,138 @@ +import { globalStyle, style } from '@vanilla-extract/css'; + +export const settingSlideBar = style({ + width: '25%', + maxWidth: '242px', + // TODO: use color variable + // background: 'var(--affine-background-secondary-color)', + backgroundColor: '#F4F4F5', + padding: '20px 16px', + height: '100%', + flexShrink: 0, + display: 'flex', + flexDirection: 'column', +}); + +export const sidebarTitle = style({ + fontSize: 'var(--affine-font-h-6)', + fontWeight: '600', + lineHeight: 'var(--affine-line-height)', + paddingLeft: '8px', +}); + +export const sidebarSubtitle = style({ + fontSize: 'var(--affine-font-sm)', + lineHeight: 'var(--affine-line-height)', + color: 'var(--affine-text-secondary-color)', + paddingLeft: '8px', + marginTop: '20px', + marginBottom: '4px', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', +}); + +export const sidebarItemsWrapper = style({ + selectors: { + '&.scroll': { + flexGrow: 1, + }, + }, +}); + +export const sidebarSelectItem = style({ + display: 'flex', + alignItems: 'center', + padding: '0 8px', + height: '30px', + marginBottom: '4px', + fontSize: 'var(--affine-font-sm)', + borderRadius: '8px', + cursor: 'pointer', + userSelect: 'none', + ':hover': { + background: 'var(--affine-hover-color)', + }, + selectors: { + '&.active': { + background: 'var(--affine-hover-color)', + }, + [`${sidebarItemsWrapper} &:last-of-type`]: { + marginBottom: 0, + }, + }, +}); + +globalStyle(`${settingSlideBar} .icon`, { + width: '16px', + height: '16px', + marginRight: '10px', + flexShrink: 0, +}); +globalStyle(`${settingSlideBar} .setting-name`, { + minWidth: 0, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + flexGrow: 1, +}); +globalStyle(`${settingSlideBar} .current-label`, { + height: '20px', + borderRadius: '8px', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + padding: '0 5px', + // TODO: use color variable + background: '#1E96EB', + fontSize: 'var(--affine-font-xs)', + fontWeight: '600', + color: 'var(--affine-white)', + marginLeft: '10px', + flexShrink: 0, +}); + +export const accountButton = style({ + height: '42px', + padding: '4px 8px', + borderRadius: '8px', + cursor: 'pointer', + userSelect: 'none', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + ':hover': { + background: 'var(--affine-hover-color)', + }, +}); + +globalStyle(`${accountButton} .avatar`, { + width: '28px', + height: '28px', + border: '1px solid', + borderColor: 'var(--affine-white)', + borderRadius: '14px', + flexShrink: '0', + marginRight: '10px', + background: 'red', +}); +globalStyle(`${accountButton} .content`, { + flexGrow: '1', + minWidth: 0, +}); +globalStyle(`${accountButton} .name`, { + fontSize: 'var(--affine-font-sm)', + fontWeight: 600, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + flexGrow: 1, +}); +globalStyle(`${accountButton} .email`, { + fontSize: 'var(--affine-font-xs)', + color: 'var(--affine-text-secondary-color)', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + flexGrow: 1, +}); diff --git a/apps/web/src/components/affine/setting-modal/style.css.ts b/apps/web/src/components/affine/setting-modal/style.css.ts new file mode 100644 index 0000000000..e573eb879d --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/style.css.ts @@ -0,0 +1,38 @@ +import { globalStyle, style } from '@vanilla-extract/css'; + +export const settingContent = style({ + flexGrow: '1', + height: '100%', + padding: '40px 0', +}); + +globalStyle(`${settingContent} .wrapper`, { + width: '66%', + minWidth: '450px', + height: '100%', + maxWidth: '560px', + margin: '0 auto', + overflowY: 'auto', +}); +globalStyle(`${settingContent} .content`, { + minHeight: '100%', + paddingBottom: '80px', +}); +globalStyle(`${settingContent} .footer`, { + cursor: 'pointer', + paddingTop: '40px', + marginTop: '-80px', + fontSize: 'var(--affine-font-sm)', + display: 'flex', +}); + +globalStyle(`${settingContent} .footer a`, { + color: 'var(--affine-text-primary-color)', +}); + +globalStyle(`${settingContent} .footer > svg`, { + fontSize: 'var(--affine-font-base)', + color: 'var(--affine-icon-color)', + marginRight: '12px', + marginTop: '2px', +}); diff --git a/apps/web/src/components/affine/setting-modal/type.ts b/apps/web/src/components/affine/setting-modal/type.ts new file mode 100644 index 0000000000..1f491cb120 --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/type.ts @@ -0,0 +1,6 @@ +import type { + AffineLegacyCloudWorkspace, + LocalWorkspace, +} from '@affine/env/workspace'; + +export type Workspace = AffineLegacyCloudWorkspace | LocalWorkspace; diff --git a/apps/web/src/components/affine/setting-modal/workspace-setting/index.tsx b/apps/web/src/components/affine/setting-modal/workspace-setting/index.tsx new file mode 100644 index 0000000000..b51b496882 --- /dev/null +++ b/apps/web/src/components/affine/setting-modal/workspace-setting/index.tsx @@ -0,0 +1,29 @@ +import { assertExists } from '@blocksuite/store'; +import React, { Suspense, useCallback } from 'react'; + +import { getUIAdapter } from '../../../../adapters/workspace'; +import { useOnTransformWorkspace } from '../../../../hooks/root/use-on-transform-workspace'; +import { useAppHelper } from '../../../../hooks/use-workspaces'; +import type { Workspace } from '../type'; + +export const WorkSpaceSetting = ({ workspace }: { workspace: Workspace }) => { + const helper = useAppHelper(); + const { NewSettingsDetail } = getUIAdapter(workspace.flavour); + + const onDeleteWorkspace = useCallback(async () => { + assertExists(currentWorkspace); + const workspaceId = currentWorkspace.id; + return helper.deleteWorkspace(workspaceId); + }, [helper]); + const onTransformWorkspace = useOnTransformWorkspace(); + + return ( + loading}> + + + ); +}; diff --git a/apps/web/src/components/blocksuite/workspace-header/header-right-items/user-avatar.tsx b/apps/web/src/components/blocksuite/workspace-header/header-right-items/user-avatar.tsx index f324670aa5..c0b90b993c 100644 --- a/apps/web/src/components/blocksuite/workspace-header/header-right-items/user-avatar.tsx +++ b/apps/web/src/components/blocksuite/workspace-header/header-right-items/user-avatar.tsx @@ -1,5 +1,5 @@ import { Menu, MenuItem } from '@affine/component'; -import { AffineIcon, SignOutIcon } from '@blocksuite/icons'; +import { AffineLogoSBlue2_1Icon, SignOutIcon } from '@blocksuite/icons'; import type { CSSProperties } from 'react'; import { forwardRef } from 'react'; @@ -90,7 +90,7 @@ export const WorkspaceAvatar = forwardRef( {props.name ? ( props.name.substring(0, 1) ) : ( - + )} )} diff --git a/apps/web/src/components/page-detail-editor.css.ts b/apps/web/src/components/page-detail-editor.css.ts index d5fb518d07..9c1045cce7 100644 --- a/apps/web/src/components/page-detail-editor.css.ts +++ b/apps/web/src/components/page-detail-editor.css.ts @@ -4,3 +4,16 @@ export const pluginContainer = style({ height: '100%', width: '100%', }); + +export const editor = style({ + height: 'calc(100% - 52px)', + + selectors: { + '&.full-screen': { + padding: '0 5%', + vars: { + '--affine-editor-width': '100%', + }, + }, + }, +}); diff --git a/apps/web/src/components/page-detail-editor.tsx b/apps/web/src/components/page-detail-editor.tsx index b37f761b6c..c953ee1b00 100644 --- a/apps/web/src/components/page-detail-editor.tsx +++ b/apps/web/src/components/page-detail-editor.tsx @@ -18,6 +18,7 @@ import type { PluginUIAdapter, } from '@toeverything/plugin-infra/type'; import type { PluginBlockSuiteAdapter } from '@toeverything/plugin-infra/type'; +import clsx from 'clsx'; import { useAtomValue, useSetAtom } from 'jotai'; import Head from 'next/head'; import type { FC, ReactElement } from 'react'; @@ -32,8 +33,10 @@ import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; import { pageSettingFamily } from '../atoms'; import { contentLayoutAtom } from '../atoms/layout'; +import { useAppSetting } from '../atoms/settings'; import type { AffineOfficialWorkspace } from '../shared'; import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor'; +import { editor } from './page-detail-editor.css'; import { pluginContainer } from './page-detail-editor.css'; export type PageDetailEditorProps = { @@ -71,12 +74,15 @@ const EditorWrapper = memo(function EditorWrapper({ (DEFAULT_HELLO_WORLD_PAGE_ID === pageId ? 'edgeless' : 'page'); const setEditor = useSetAtom(rootCurrentEditorAtom); + const [appSettings] = useAppSetting(); + assertExists(meta); + return ( { - const t = useAFFiNEI18N(); - return { - [t['Undo']()]: '⌘+Z', - [t['Redo']()]: '⌘+⇧+Z', - [t['Bold']()]: '⌘+B', - [t['Italic']()]: '⌘+I', - [t['Underline']()]: '⌘+U', - [t['Strikethrough']()]: '⌘+⇧+S', - [t['Inline code']()]: ' ⌘+E', - [t['Code block']()]: '⌘+⌥+C', - [t['Link']()]: '⌘+K', - [t['Quick search']()]: '⌘+K', - [t['Body text']()]: '⌘+⌥+0', - [t['Heading']({ number: '1' })]: '⌘+⌥+1', - [t['Heading']({ number: '2' })]: '⌘+⌥+2', - [t['Heading']({ number: '3' })]: '⌘+⌥+3', - [t['Heading']({ number: '4' })]: '⌘+⌥+4', - [t['Heading']({ number: '5' })]: '⌘+⌥+5', - [t['Heading']({ number: '6' })]: '⌘+⌥+6', - [t['Increase indent']()]: 'Tab', - [t['Reduce indent']()]: '⇧+Tab', - }; -}; - -export const useMacMarkdownShortcuts = (): ShortcutTip => { - const t = useAFFiNEI18N(); - return { - [t['Bold']()]: '**Text** ', - [t['Italic']()]: '*Text* ', - [t['Underline']()]: '~Text~ ', - [t['Strikethrough']()]: '~~Text~~ ', - [t['Divider']()]: '***', - [t['Inline code']()]: '`Text` ', - [t['Code block']()]: '``` Space', - [t['Heading']({ number: '1' })]: '# Text', - [t['Heading']({ number: '2' })]: '## Text', - [t['Heading']({ number: '3' })]: '### Text', - [t['Heading']({ number: '4' })]: '#### Text', - [t['Heading']({ number: '5' })]: '##### Text', - [t['Heading']({ number: '6' })]: '###### Text', - }; -}; - -export const useWindowsKeyboardShortcuts = (): ShortcutTip => { - const t = useAFFiNEI18N(); - return { - [t['Undo']()]: 'Ctrl+Z', - [t['Redo']()]: 'Ctrl+Y', - [t['Bold']()]: 'Ctrl+B', - [t['Italic']()]: 'Ctrl+I', - [t['Underline']()]: 'Ctrl+U', - [t['Strikethrough']()]: 'Ctrl+Shift+S', - [t['Inline code']()]: ' Ctrl+E', - [t['Code block']()]: 'Ctrl+Alt+C', - [t['Link']()]: 'Ctrl+K', - [t['Quick search']()]: 'Ctrl+K', - [t['Body text']()]: 'Ctrl+Shift+0', - [t['Heading']({ number: '1' })]: 'Ctrl+Shift+1', - [t['Heading']({ number: '2' })]: 'Ctrl+Shift+2', - [t['Heading']({ number: '3' })]: 'Ctrl+Shift+3', - [t['Heading']({ number: '4' })]: 'Ctrl+Shift+4', - [t['Heading']({ number: '5' })]: 'Ctrl+Shift+5', - [t['Heading']({ number: '6' })]: 'Ctrl+Shift+6', - [t['Increase indent']()]: 'Tab', - [t['Reduce indent']()]: 'Shift+Tab', - }; -}; -export const useWinMarkdownShortcuts = (): ShortcutTip => { - const t = useAFFiNEI18N(); - return { - [t['Bold']()]: '**Text** ', - [t['Italic']()]: '*Text* ', - [t['Underline']()]: '~Text~ ', - [t['Strikethrough']()]: '~~Text~~ ', - [t['Divider']()]: '***', - [t['Inline code']()]: '`Text` ', - [t['Code block']()]: '``` Text', - [t['Heading']({ number: '1' })]: '# Text', - [t['Heading']({ number: '2' })]: '## Text', - [t['Heading']({ number: '3' })]: '### Text', - [t['Heading']({ number: '4' })]: '#### Text', - [t['Heading']({ number: '5' })]: '##### Text', - [t['Heading']({ number: '6' })]: '###### Text', - }; -}; diff --git a/apps/web/src/components/pure/shortcuts-modal/index.tsx b/apps/web/src/components/pure/shortcuts-modal/index.tsx index 28c01c253e..00e7622106 100644 --- a/apps/web/src/components/pure/shortcuts-modal/index.tsx +++ b/apps/web/src/components/pure/shortcuts-modal/index.tsx @@ -3,16 +3,14 @@ import { MuiClickAwayListener, MuiSlide, } from '@affine/component'; -import { env } from '@affine/env'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; -import { useEffect, useState } from 'react'; import { - useMacKeyboardShortcuts, - useMacMarkdownShortcuts, - useWindowsKeyboardShortcuts, - useWinMarkdownShortcuts, -} from './config'; + useEdgelessShortcuts, + useGeneralShortcuts, + useMarkdownShortcuts, + usePageShortcuts, +} from '../../../hooks/affine/use-shortcuts'; import { KeyboardIcon } from './icons'; import { StyledListItem, @@ -26,25 +24,13 @@ type ModalProps = { onClose: () => void; }; -const checkIsMac = () => { - return env.isBrowser && env.isMacOs; -}; - export const ShortcutsModal = ({ open, onClose }: ModalProps) => { const t = useAFFiNEI18N(); - const macMarkdownShortcuts = useMacMarkdownShortcuts(); - const winMarkdownShortcuts = useWinMarkdownShortcuts(); - const macKeyboardShortcuts = useMacKeyboardShortcuts(); - const windowsKeyboardShortcuts = useWindowsKeyboardShortcuts(); - const [isMac, setIsMac] = useState(false); - const markdownShortcuts = isMac ? macMarkdownShortcuts : winMarkdownShortcuts; - const keyboardShortcuts = isMac - ? macKeyboardShortcuts - : windowsKeyboardShortcuts; - useEffect(() => { - setIsMac(checkIsMac()); - }, []); + const markdownShortcuts = useMarkdownShortcuts(); + const pageShortcuts = usePageShortcuts(); + const edgelessShortcuts = useEdgelessShortcuts(); + const generalShortcuts = useGeneralShortcuts(); return ( @@ -72,9 +58,27 @@ export const ShortcutsModal = ({ open, onClose }: ModalProps) => { /> - {t['Keyboard Shortcuts']()} + {t['General']()} - {Object.entries(keyboardShortcuts).map(([title, shortcuts]) => { + {Object.entries(generalShortcuts).map(([title, shortcuts]) => { + return ( + + {title} + {shortcuts} + + ); + })} + {t['Page']()} + {Object.entries(pageShortcuts).map(([title, shortcuts]) => { + return ( + + {title} + {shortcuts} + + ); + })} + {t['Edgeless']()} + {Object.entries(edgelessShortcuts).map(([title, shortcuts]) => { return ( {title} diff --git a/apps/web/src/components/root-app-sidebar/index.tsx b/apps/web/src/components/root-app-sidebar/index.tsx index f212eb67d2..d4bdf99ab9 100644 --- a/apps/web/src/components/root-app-sidebar/index.tsx +++ b/apps/web/src/components/root-app-sidebar/index.tsx @@ -4,6 +4,7 @@ import { appSidebarOpenAtom, AppUpdaterButton, CategoryDivider, + MenuItem, MenuLinkItem, QuickSearchInput, SidebarContainer, @@ -25,6 +26,7 @@ import type { ReactElement } from 'react'; import React, { useCallback, useEffect, useMemo } from 'react'; import { useHistoryAtom } from '../../atoms/history'; +import { useAppSetting } from '../../atoms/settings'; import type { AllWorkspace } from '../../shared'; import FavoriteList from '../pure/workspace-slider-bar/favorite/favorite-list'; import { WorkspaceSelector } from '../pure/workspace-slider-bar/WorkspaceSelector'; @@ -32,6 +34,7 @@ import { WorkspaceSelector } from '../pure/workspace-slider-bar/WorkspaceSelecto export type RootAppSidebarProps = { isPublicWorkspace: boolean; onOpenQuickSearchModal: () => void; + onOpenSettingModal: () => void; onOpenWorkspaceListModal: () => void; currentWorkspace: AllWorkspace | null; openPage: (pageId: string) => void; @@ -88,8 +91,11 @@ export const RootAppSidebar = ({ paths, onOpenQuickSearchModal, onOpenWorkspaceListModal, + onOpenSettingModal, }: RootAppSidebarProps): ReactElement => { const currentWorkspaceId = currentWorkspace?.id || null; + const [appSettings] = useAppSetting(); + const blockSuiteWorkspace = currentWorkspace?.blockSuiteWorkspace; const t = useAFFiNEI18N(); const onClickNewPage = useCallback(async () => { @@ -143,7 +149,10 @@ export const RootAppSidebar = ({ return ( <> - + {t['Settings']()} + {config.enableNewSettingModal ? ( + } onClick={onOpenSettingModal}> + + {t['Settings']()} + + NEW + + + + ) : null} diff --git a/apps/web/src/hooks/affine/use-shortcuts.ts b/apps/web/src/hooks/affine/use-shortcuts.ts new file mode 100644 index 0000000000..6088b91d08 --- /dev/null +++ b/apps/web/src/hooks/affine/use-shortcuts.ts @@ -0,0 +1,237 @@ +import { env } from '@affine/env'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { useMemo } from 'react'; + +interface ShortcutTip { + [x: string]: string; +} + +export const useWinGeneralKeyboardShortcuts = (): ShortcutTip => { + const t = useAFFiNEI18N(); + return useMemo( + () => ({ + [t['Cancel']()]: 'ESC', + [t['Quick Search']()]: 'Ctrl + K', + [t['New Page']()]: 'Ctrl + N', + // not implement yet + // [t['Append to Daily Note']()]: 'Ctrl + Alt + A', + [t['Expand/Collapse Sidebar']()]: 'Ctrl + /', + // not implement yet + // [t['Go Back']()]: 'Ctrl + [', + // [t['Go Forward']()]: 'Ctrl + ]', + }), + [t] + ); +}; +export const useMacGeneralKeyboardShortcuts = (): ShortcutTip => { + const t = useAFFiNEI18N(); + return useMemo( + () => ({ + [t['Cancel']()]: 'ESC', + [t['Quick Search']()]: '⌘ + K', + [t['New Page']()]: '⌘ + N', + // not implement yet + // [t['Append to Daily Note']()]: '⌘ + ⌥ + A', + [t['Expand/Collapse Sidebar']()]: '⌘ + /', + // not implement yet + // [t['Go Back']()]: '⌘ + [', + // [t['Go Forward']()]: '⌘ + ]', + }), + [t] + ); +}; + +export const useMacEdgelessKeyboardShortcuts = (): ShortcutTip => { + const t = useAFFiNEI18N(); + return useMemo( + () => ({ + [t['Select All']()]: '⌘ + A', + [t['Undo']()]: '⌘ + Z', + [t['Redo']()]: '⌘ + ⇧ + Z', + [t['Zoom in']()]: '⌘ + +', + [t['Zoom out']()]: '⌘ + -', + [t['Zoom to 100%']()]: '⌘ + 0', + [t['Zoom to fit']()]: '⌘ + 1', + [t['Select']()]: 'V', + [t['Text']()]: 'T', + [t['Shape']()]: 'S', + [t['Image']()]: 'I', + [t['Straight Connector']()]: 'L', + [t['Elbowed Connector']()]: 'X', + // not implement yet + // [t['Curve Connector']()]: 'C', + [t['Pen']()]: 'P', + [t['Hand']()]: 'H', + [t['Note']()]: 'N', + // not implement yet + // [t['Group']()]: '⌘ + G', + // [t['Ungroup']()]: '⌘ + ⇧ + G', + }), + [t] + ); +}; +export const useWinEdgelessKeyboardShortcuts = (): ShortcutTip => { + const t = useAFFiNEI18N(); + return useMemo( + () => ({ + [t['Select All']()]: 'Ctrl + A', + [t['Undo']()]: 'Ctrl + Z', + [t['Redo']()]: 'Ctrl + Y/Ctrl + Shift + Z', + [t['Zoom in']()]: 'Ctrl + +', + [t['Zoom out']()]: 'Ctrl + -', + [t['Zoom to 100%']()]: 'Ctrl + 0', + [t['Zoom to fit']()]: 'Ctrl + 1', + [t['Select']()]: 'V', + [t['Text']()]: 'T', + [t['Shape']()]: 'S', + [t['Image']()]: 'I', + [t['Straight Connector']()]: 'L', + [t['Elbowed Connector']()]: 'X', + // not implement yet + // [t['Curve Connector']()]: 'C', + [t['Pen']()]: 'P', + [t['Hand']()]: 'H', + [t['Note']()]: 'N', + // not implement yet + // [t['Group']()]: 'Ctrl + G', + // [t['Ungroup']()]: 'Ctrl + Shift + G', + }), + [t] + ); +}; +export const useMacPageKeyboardShortcuts = (): ShortcutTip => { + const t = useAFFiNEI18N(); + return useMemo( + () => ({ + [t['Undo']()]: '⌘+Z', + [t['Redo']()]: '⌘+⇧+Z', + [t['Bold']()]: '⌘+B', + [t['Italic']()]: '⌘+I', + [t['Underline']()]: '⌘+U', + [t['Strikethrough']()]: '⌘+⇧+S', + [t['Inline code']()]: ' ⌘+E', + [t['Code block']()]: '⌘+⌥+C', + [t['Link']()]: '⌘+K', + [t['Quick search']()]: '⌘+K', + [t['Body text']()]: '⌘+⌥+0', + [t['Heading']({ number: '1' })]: '⌘+⌥+1', + [t['Heading']({ number: '2' })]: '⌘+⌥+2', + [t['Heading']({ number: '3' })]: '⌘+⌥+3', + [t['Heading']({ number: '4' })]: '⌘+⌥+4', + [t['Heading']({ number: '5' })]: '⌘+⌥+5', + [t['Heading']({ number: '6' })]: '⌘+⌥+6', + [t['Increase indent']()]: 'Tab', + [t['Reduce indent']()]: '⇧+Tab', + [t['Group as Database']()]: '⌘ + G', + // not implement yet + // [t['Move Up']()]: '⌘ + ⌥ + ↑', + // [t['Move Down']()]: '⌘ + ⌥ + ↓', + }), + [t] + ); +}; + +export const useMacMarkdownShortcuts = (): ShortcutTip => { + const t = useAFFiNEI18N(); + return useMemo( + () => ({ + [t['Bold']()]: '**Text** ', + [t['Italic']()]: '*Text* ', + [t['Underline']()]: '~Text~ ', + [t['Strikethrough']()]: '~~Text~~ ', + [t['Divider']()]: '***', + [t['Inline code']()]: '`Text` ', + [t['Code block']()]: '``` Space', + [t['Heading']({ number: '1' })]: '# Text', + [t['Heading']({ number: '2' })]: '## Text', + [t['Heading']({ number: '3' })]: '### Text', + [t['Heading']({ number: '4' })]: '#### Text', + [t['Heading']({ number: '5' })]: '##### Text', + [t['Heading']({ number: '6' })]: '###### Text', + }), + [t] + ); +}; + +export const useWinPageKeyboardShortcuts = (): ShortcutTip => { + const t = useAFFiNEI18N(); + return useMemo( + () => ({ + [t['Undo']()]: 'Ctrl+Z', + [t['Redo']()]: 'Ctrl+Y', + [t['Bold']()]: 'Ctrl+B', + [t['Italic']()]: 'Ctrl+I', + [t['Underline']()]: 'Ctrl+U', + [t['Strikethrough']()]: 'Ctrl+Shift+S', + [t['Inline code']()]: ' Ctrl+E', + [t['Code block']()]: 'Ctrl+Alt+C', + [t['Link']()]: 'Ctrl+K', + [t['Quick search']()]: 'Ctrl+K', + [t['Body text']()]: 'Ctrl+Shift+0', + [t['Heading']({ number: '1' })]: 'Ctrl+Shift+1', + [t['Heading']({ number: '2' })]: 'Ctrl+Shift+2', + [t['Heading']({ number: '3' })]: 'Ctrl+Shift+3', + [t['Heading']({ number: '4' })]: 'Ctrl+Shift+4', + [t['Heading']({ number: '5' })]: 'Ctrl+Shift+5', + [t['Heading']({ number: '6' })]: 'Ctrl+Shift+6', + [t['Increase indent']()]: 'Tab', + [t['Reduce indent']()]: 'Shift+Tab', + [t['Group as Database']()]: 'Ctrl + G', + // not implement yet + // [t['Move Up']()]: 'Ctrl + Alt + ↑', + // [t['Move Down']()]: 'Ctrl + Alt + ↓', + }), + [t] + ); +}; +export const useWinMarkdownShortcuts = (): ShortcutTip => { + const t = useAFFiNEI18N(); + return useMemo( + () => ({ + [t['Bold']()]: '**Text** ', + [t['Italic']()]: '*Text* ', + [t['Underline']()]: '~Text~ ', + [t['Strikethrough']()]: '~~Text~~ ', + [t['Divider']()]: '***', + [t['Inline code']()]: '`Text` ', + [t['Code block']()]: '``` Text', + [t['Heading']({ number: '1' })]: '# Text', + [t['Heading']({ number: '2' })]: '## Text', + [t['Heading']({ number: '3' })]: '### Text', + [t['Heading']({ number: '4' })]: '#### Text', + [t['Heading']({ number: '5' })]: '##### Text', + [t['Heading']({ number: '6' })]: '###### Text', + }), + [t] + ); +}; + +export const useMarkdownShortcuts = (): ShortcutTip => { + const macMarkdownShortcuts = useMacMarkdownShortcuts(); + const winMarkdownShortcuts = useWinMarkdownShortcuts(); + const isMac = env.isBrowser && env.isMacOs; + return isMac ? macMarkdownShortcuts : winMarkdownShortcuts; +}; + +export const usePageShortcuts = (): ShortcutTip => { + const macPageShortcuts = useMacPageKeyboardShortcuts(); + const winPageShortcuts = useWinPageKeyboardShortcuts(); + const isMac = env.isBrowser && env.isMacOs; + return isMac ? macPageShortcuts : winPageShortcuts; +}; + +export const useEdgelessShortcuts = (): ShortcutTip => { + const macEdgelessShortcuts = useMacEdgelessKeyboardShortcuts(); + const winEdgelessShortcuts = useWinEdgelessKeyboardShortcuts(); + const isMac = env.isBrowser && env.isMacOs; + + return isMac ? macEdgelessShortcuts : winEdgelessShortcuts; +}; + +export const useGeneralShortcuts = (): ShortcutTip => { + const macGeneralShortcuts = useMacGeneralKeyboardShortcuts(); + const winGeneralShortcuts = useWinGeneralKeyboardShortcuts(); + const isMac = env.isBrowser && env.isMacOs; + + return isMac ? macGeneralShortcuts : winGeneralShortcuts; +}; diff --git a/apps/web/src/layouts/public-workspace-layout.tsx b/apps/web/src/layouts/public-workspace-layout.tsx index fe3234f1c6..241f018917 100644 --- a/apps/web/src/layouts/public-workspace-layout.tsx +++ b/apps/web/src/layouts/public-workspace-layout.tsx @@ -1,4 +1,4 @@ -import { AppContainer, MainContainer } from '@affine/component/workspace'; +import { MainContainer } from '@affine/component/workspace'; import type { AffinePublicWorkspace } from '@affine/env/workspace'; import { useAtom } from 'jotai'; import Head from 'next/head'; @@ -7,6 +7,7 @@ import type React from 'react'; import { lazy, Suspense } from 'react'; import { openQuickSearchModalAtom } from '../atoms'; +import { AppContainer } from '../components/affine/app-container'; import { useRouterTitle } from '../hooks/use-router-title'; const QuickSearchModal = lazy(() => diff --git a/apps/web/src/layouts/workspace-layout.tsx b/apps/web/src/layouts/workspace-layout.tsx index 8ccee15e97..d7bfca29d6 100644 --- a/apps/web/src/layouts/workspace-layout.tsx +++ b/apps/web/src/layouts/workspace-layout.tsx @@ -4,7 +4,6 @@ import { appSidebarResizingAtom } from '@affine/component/app-sidebar'; import type { DraggableTitleCellData } from '@affine/component/page-list'; import { StyledTitleLink } from '@affine/component/page-list'; import { - AppContainer, MainContainer, ToolContainer, WorkspaceFallback, @@ -42,12 +41,17 @@ import type { FC, PropsWithChildren, ReactElement } from 'react'; import { lazy, Suspense, useCallback, useEffect, useMemo } from 'react'; import { WorkspaceAdapters } from '../adapters/workspace'; -import { openQuickSearchModalAtom, openWorkspacesModalAtom } from '../atoms'; +import { + openQuickSearchModalAtom, + openSettingModalAtom, + openWorkspacesModalAtom, +} from '../atoms'; import { useTrackRouterHistoryEffect } from '../atoms/history'; import { publicWorkspaceAtom, publicWorkspaceIdAtom, } from '../atoms/public-workspace'; +import { AppContainer } from '../components/affine/app-container'; import type { IslandItemNames } from '../components/pure/help-island'; import { HelpIsland } from '../components/pure/help-island'; import { @@ -71,6 +75,11 @@ const QuickSearchModal = lazy(() => default: module.QuickSearchModal, })) ); +const SettingModal = lazy(() => + import('../components/affine/setting-modal').then(module => ({ + default: module.SettingModal, + })) +); export const PublicQuickSearch: FC = () => { const publicWorkspace = useAtomValue(publicWorkspaceAtom); @@ -119,6 +128,25 @@ export const QuickSearch: FC = () => { /> ); }; +export const Setting: FC = () => { + const [currentWorkspace] = useCurrentWorkspace(); + const router = useRouter(); + const [openSettingModal, setOpenSettingModalAtom] = + useAtom(openSettingModalAtom); + const blockSuiteWorkspace = currentWorkspace?.blockSuiteWorkspace; + const isPublicWorkspace = + router.pathname.split('/')[1] === 'public-workspace'; + if (!blockSuiteWorkspace || isPublicWorkspace) { + return null; + } + return ( + + ); +}; const logger = new DebugLogger('workspace-layout'); @@ -390,6 +418,12 @@ export const WorkspaceLayoutInner: FC = ({ children }) => { setOpenQuickSearchModalAtom(true); }, [setOpenQuickSearchModalAtom]); + const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom); + + const handleOpenSettingModal = useCallback(() => { + setOpenSettingModalAtom(true); + }, [setOpenSettingModalAtom]); + const resizing = useAtomValue(appSidebarResizingAtom); const sensors = useSensors( @@ -444,6 +478,7 @@ export const WorkspaceLayoutInner: FC = ({ children }) => { = ({ children }) => { + ); }; diff --git a/apps/web/src/pages/_debug/broadcast.dev.tsx b/apps/web/src/pages/_debug/broadcast.dev.tsx index 75e483700b..cd97a8bc4c 100644 --- a/apps/web/src/pages/_debug/broadcast.dev.tsx +++ b/apps/web/src/pages/_debug/broadcast.dev.tsx @@ -1,5 +1,5 @@ import { Button } from '@affine/component'; -import { AppContainer, MainContainer } from '@affine/component/workspace'; +import { MainContainer } from '@affine/component/workspace'; import { DebugLogger } from '@affine/debug'; import type { BroadCastChannelProvider } from '@affine/env/workspace'; import { WorkspaceFlavour } from '@affine/env/workspace'; @@ -10,6 +10,7 @@ import { Typography } from '@mui/material'; import type React from 'react'; import { useEffect, useMemo, useState } from 'react'; +import { AppContainer } from '../../components/affine/app-container'; import { BlockSuitePageList } from '../../components/blocksuite/block-suite-page-list'; import { toast } from '../../utils'; diff --git a/apps/web/src/pages/_debug/init-page.dev.tsx b/apps/web/src/pages/_debug/init-page.dev.tsx index 22bdbefd02..55a6988be1 100644 --- a/apps/web/src/pages/_debug/init-page.dev.tsx +++ b/apps/web/src/pages/_debug/init-page.dev.tsx @@ -1,7 +1,8 @@ -import { AppContainer, MainContainer } from '@affine/component/workspace'; +import { MainContainer } from '@affine/component/workspace'; import { useRouter } from 'next/router'; import { lazy, Suspense } from 'react'; +import { AppContainer } from '../../components/affine/app-container'; import type { NextPageWithLayout } from '../../shared'; const Editor = lazy(() => diff --git a/apps/web/src/pages/_debug/login.dev.tsx b/apps/web/src/pages/_debug/login.dev.tsx index d8cabb731a..14f8126c25 100644 --- a/apps/web/src/pages/_debug/login.dev.tsx +++ b/apps/web/src/pages/_debug/login.dev.tsx @@ -1,5 +1,5 @@ import { Button } from '@affine/component'; -import { AppContainer, MainContainer } from '@affine/component/workspace'; +import { MainContainer } from '@affine/component/workspace'; import { currentAffineUserAtom } from '@affine/workspace/affine/atom'; import { clearLoginStorage, @@ -14,6 +14,7 @@ import { useAtom } from 'jotai'; import type { NextPage } from 'next'; import { lazy, Suspense, useMemo } from 'react'; +import { AppContainer } from '../../components/affine/app-container'; import { toast } from '../../utils'; const Viewer = lazy(() => diff --git a/apps/web/src/pages/plugins.tsx b/apps/web/src/pages/plugins.tsx index efe498c437..d22e58c1c6 100644 --- a/apps/web/src/pages/plugins.tsx +++ b/apps/web/src/pages/plugins.tsx @@ -1,4 +1,4 @@ -import { AppContainer, MainContainer } from '@affine/component/workspace'; +import { MainContainer } from '@affine/component/workspace'; import { config } from '@affine/env'; import { NoSsr } from '@mui/material'; import { affinePluginsAtom } from '@toeverything/plugin-infra/manager'; @@ -6,6 +6,8 @@ import { useAtomValue } from 'jotai'; import type { ReactElement } from 'react'; import { Suspense } from 'react'; +import { AppContainer } from '../components/affine/app-container'; + const Plugins = () => { const plugins = useAtomValue(affinePluginsAtom); return ( diff --git a/packages/component/package.json b/packages/component/package.json index 361fa31944..bfa98d6f1b 100644 --- a/packages/component/package.json +++ b/packages/component/package.json @@ -54,7 +54,7 @@ "@blocksuite/blocks": "0.0.0-20230607055421-9b20fcaf-nightly", "@blocksuite/editor": "0.0.0-20230607055421-9b20fcaf-nightly", "@blocksuite/global": "0.0.0-20230607055421-9b20fcaf-nightly", - "@blocksuite/icons": "^2.1.19", + "@blocksuite/icons": "^2.1.21", "@blocksuite/lit": "0.0.0-20230607055421-9b20fcaf-nightly", "@blocksuite/store": "0.0.0-20230607055421-9b20fcaf-nightly", "@types/react": "^18.2.12", diff --git a/packages/component/src/components/affine-banner/download-client.tsx b/packages/component/src/components/affine-banner/download-client.tsx index 9397dc9088..46fc4a022c 100644 --- a/packages/component/src/components/affine-banner/download-client.tsx +++ b/packages/component/src/components/affine-banner/download-client.tsx @@ -1,5 +1,5 @@ import { Trans } from '@affine/i18n'; -import { AffineLogoSimCBlue1_1Icon, CloseIcon } from '@blocksuite/icons'; +import { AffineLogoSBlue2_1Icon, CloseIcon } from '@blocksuite/icons'; import { downloadCloseButtonStyle, @@ -17,7 +17,7 @@ export const DownloadTips = ({ onClose }: { onClose: () => void }) => { data-testid="download-client-tip" >
- +
Enjoying the demo? diff --git a/packages/component/src/components/app-sidebar/index.css.ts b/packages/component/src/components/app-sidebar/index.css.ts index 43061998e0..d83ed2548d 100644 --- a/packages/component/src/components/app-sidebar/index.css.ts +++ b/packages/component/src/components/app-sidebar/index.css.ts @@ -16,6 +16,7 @@ export const navWrapperStyle = style({ zIndex: 2, paddingBottom: '8px', backgroundColor: 'transparent', + borderRight: '1px solid var(--affine-border-color)', '@media': { [`(max-width: ${floatingMaxWidth}px)`]: { position: 'absolute', @@ -39,6 +40,9 @@ export const navWrapperStyle = style({ '&[data-enable-animation="true"]': { transition: 'margin-left .3s, width .3s', }, + '&.has-background': { + backgroundColor: 'var(--affine-white-60)', + }, }, }); diff --git a/packages/component/src/components/app-sidebar/index.tsx b/packages/component/src/components/app-sidebar/index.tsx index 52ad754f6c..e5fd5009e6 100644 --- a/packages/component/src/components/app-sidebar/index.tsx +++ b/packages/component/src/components/app-sidebar/index.tsx @@ -1,6 +1,7 @@ import { env } from '@affine/env'; import { Skeleton } from '@mui/material'; import { assignInlineVars } from '@vanilla-extract/dynamic'; +import clsx from 'clsx'; import { useAtom, useAtomValue } from 'jotai'; import type { PropsWithChildren, ReactElement } from 'react'; import { useEffect, useRef, useState } from 'react'; @@ -25,7 +26,11 @@ import { ResizeIndicator } from './resize-indicator'; import type { SidebarHeaderProps } from './sidebar-header'; import { SidebarHeader } from './sidebar-header'; -export type AppSidebarProps = PropsWithChildren; +export type AppSidebarProps = PropsWithChildren< + SidebarHeaderProps & { + hasBackground?: boolean; + } +>; function useEnableAnimation() { const [enable, setEnable] = useState(false); @@ -91,7 +96,9 @@ export function AppSidebar(props: AppSidebarProps): ReactElement { style={assignInlineVars({ [navWidthVar]: `${appSidebarWidth}px`, })} - className={navWrapperStyle} + className={clsx(navWrapperStyle, { + 'has-background': env.isDesktop && props.hasBackground, + })} data-open={open} data-is-macos-electron={isMacosDesktop} data-enable-animation={enableAnimation && !isResizing} diff --git a/packages/component/src/components/block-suite-editor/index.tsx b/packages/component/src/components/block-suite-editor/index.tsx index e8c9b6fde2..e56fb9fd3b 100644 --- a/packages/component/src/components/block-suite-editor/index.tsx +++ b/packages/component/src/components/block-suite-editor/index.tsx @@ -23,6 +23,7 @@ export type EditorProps = { onInit: (page: Page, editor: Readonly) => void; onLoad?: (page: Page, editor: EditorContainer) => () => void; style?: CSSProperties; + className?: string; }; export type ErrorBoundaryProps = { @@ -122,7 +123,9 @@ const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => { }, [editor, page]); // issue: https://github.com/toeverything/AFFiNE/issues/2004 - const className = `editor-wrapper ${editor.mode}-mode`; + const className = `editor-wrapper ${editor.mode}-mode ${ + props.className || '' + }`; return (
, title: 'GitHub', @@ -107,7 +107,7 @@ export const ContactModal = ({ {t['Get in touch! Join our communities']()} - {linkList.map(({ icon, title, link }) => { + {relatedLinks.map(({ icon, title, link }) => { return ( {icon} diff --git a/packages/component/src/components/notification-center/index.tsx b/packages/component/src/components/notification-center/index.tsx index cd36f015ca..1f13d641c6 100644 --- a/packages/component/src/components/notification-center/index.tsx +++ b/packages/component/src/components/notification-center/index.tsx @@ -2,7 +2,7 @@ // License on the MIT // https://github.com/emilkowalski/sonner/blob/5cb703edc108a23fd74979235c2f3c4005edd2a7/src/index.tsx -import { CloseIcon, InformationFillIcon } from '@blocksuite/icons'; +import { CloseIcon, InformationIcon } from '@blocksuite/icons'; import * as Toast from '@radix-ui/react-toast'; import clsx from 'clsx'; import { useAtom, useAtomValue, useSetAtom } from 'jotai'; @@ -303,7 +303,7 @@ function NotificationCard(props: NotificationCardProps): ReactElement { [styles.lightInfoIconStyle]: notification.theme !== 'dark', })} > - +
{notification.title} diff --git a/packages/component/src/components/workspace/index.css.ts b/packages/component/src/components/workspace/index.css.ts index 3143eed84c..42e8076b26 100644 --- a/packages/component/src/components/workspace/index.css.ts +++ b/packages/component/src/components/workspace/index.css.ts @@ -14,7 +14,10 @@ export const appStyle = style({ '&[data-is-resizing="true"]': { cursor: 'col-resize', }, - '&:before': { + '&.blur-background': { + backgroundColor: 'var(--affine-background-primary-color)', + }, + '&.noisy-background::before': { content: '""', position: 'absolute', inset: 0, diff --git a/packages/component/src/components/workspace/index.tsx b/packages/component/src/components/workspace/index.tsx index 8fb485d445..24bbe5a4a7 100644 --- a/packages/component/src/components/workspace/index.tsx +++ b/packages/component/src/components/workspace/index.tsx @@ -1,22 +1,33 @@ import { clsx } from 'clsx'; -import type { PropsWithChildren, ReactElement } from 'react'; +import type { FC, PropsWithChildren, ReactElement } from 'react'; import { AppSidebarFallback } from '../app-sidebar'; import { appStyle, mainContainerStyle, toolStyle } from './index.css'; export type WorkspaceRootProps = PropsWithChildren<{ resizing?: boolean; + useNoisyBackground?: boolean; + useBlurBackground?: boolean; }>; -export const AppContainer = (props: WorkspaceRootProps): ReactElement => { - const noisyBackground = environment.isDesktop && environment.isMacOs; +export const AppContainer: FC = ({ + resizing, + useNoisyBackground, + useBlurBackground, + children, +}) => { + const noisyBackground = + useNoisyBackground && environment.isDesktop && environment.isMacOs; return (
- {props.children} + {children}
); }; diff --git a/packages/component/src/ui/menu/index.ts b/packages/component/src/ui/menu/index.ts index bfbb694286..99df50c588 100644 --- a/packages/component/src/ui/menu/index.ts +++ b/packages/component/src/ui/menu/index.ts @@ -1,4 +1,5 @@ export * from './menu'; // export { StyledMenuItem as MenuItem } from './styles'; export * from './menu-item'; +export * from './menu-trigger'; export * from './pure-menu'; diff --git a/packages/component/src/ui/menu/menu-trigger.tsx b/packages/component/src/ui/menu/menu-trigger.tsx new file mode 100644 index 0000000000..6da3ffb0dd --- /dev/null +++ b/packages/component/src/ui/menu/menu-trigger.tsx @@ -0,0 +1,22 @@ +import { ArrowDownSmallIcon } from '@blocksuite/icons'; +import { forwardRef } from 'react'; + +import type { ButtonProps } from '../button'; +import { StyledButton } from './styles'; + +export const MenuTrigger = forwardRef( + ({ children, ...props }, ref) => { + return ( + } + iconPosition="end" + noBorder={true} + {...props} + > + {children} + + ); + } +); +MenuTrigger.displayName = 'MenuTrigger'; diff --git a/packages/component/src/ui/menu/styles.ts b/packages/component/src/ui/menu/styles.ts index a2bf48ad8f..92e8417ff5 100644 --- a/packages/component/src/ui/menu/styles.ts +++ b/packages/component/src/ui/menu/styles.ts @@ -1,6 +1,7 @@ import type { CSSProperties } from 'react'; import { displayFlex, styled, textEllipsis } from '../../styles'; +import { Button } from '../button'; import StyledPopperContainer from '../shared/container'; export const StyledMenuWrapper = styled(StyledPopperContainer, { @@ -100,3 +101,16 @@ export const StyledMenuItem = styled('button')<{ }; } ); + +export const StyledButton = styled(Button)(() => { + return { + width: '100%', + height: '32px', + borderRadius: '8px', + backgroundColor: 'transparent', + ...displayFlex('space-between', 'center'), + border: `1px solid var(--affine-border-color)`, + padding: '0 10px', + fontSize: 'var(--affine-font-base)', + }; +}); diff --git a/packages/env/src/config.ts b/packages/env/src/config.ts index d4082902e1..b1d38d2ddc 100644 --- a/packages/env/src/config.ts +++ b/packages/env/src/config.ts @@ -48,6 +48,7 @@ export const buildFlagsSchema = z.object({ enableLegacyCloud: z.boolean(), changelogUrl: z.string(), enablePreloading: z.boolean(), + enableNewSettingModal: z.boolean(), }); export const blockSuiteFeatureFlags = z.object({ diff --git a/packages/env/src/workspace.ts b/packages/env/src/workspace.ts index b4663bc982..09f7a624ce 100644 --- a/packages/env/src/workspace.ts +++ b/packages/env/src/workspace.ts @@ -201,6 +201,19 @@ type SettingProps = ) => void; }; +type NewSettingProps = + UIBaseProps & { + onDeleteWorkspace: () => Promise; + onTransformWorkspace: < + From extends keyof WorkspaceRegistry, + To extends keyof WorkspaceRegistry + >( + from: From, + to: To, + workspace: WorkspaceRegistry[From] + ) => void; + }; + type PageDetailProps = UIBaseProps & { currentPageId: string; @@ -218,6 +231,7 @@ export interface WorkspaceUISchema { PageDetail: FC>; PageList: FC>; SettingsDetail: FC>; + NewSettingsDetail: FC>; Provider: FC; } diff --git a/packages/i18n/src/resources/en.json b/packages/i18n/src/resources/en.json index 34650df38b..b0310bcbfc 100644 --- a/packages/i18n/src/resources/en.json +++ b/packages/i18n/src/resources/en.json @@ -297,5 +297,67 @@ "Update Available": "Update available", "dark": "Dark", "system": "System", - "light": "Light" + "light": "Light", + "Need more customization options? You can suggest them to us in the community.": "Need more customization options? You can suggest them to us in the community.", + "Check Keyboard Shortcuts quickly": "Check Keyboard Shortcuts quickly", + "Quick Search": "Quick Search", + "Append to Daily Note": "Append to Daily Note", + "Expand/Collapse Sidebar": "Expand/Collapse Sidebar", + "Go Back": "Go Back", + "Go Forward": "Go Forward", + "Select All": "Select All", + "Zoom in": "Zoom in", + "Zoom out": "Zoom out", + "Zoom to 100%": "Zoom to 100%", + "Zoom to fit": "Zoom to fit", + "Image": "Image", + "Straight Connector": "Straight Connector", + "Elbowed Connector": "Elbowed Connector", + "Curve Connector": "Curve Connector", + "Hand": "Hand", + "Note": "Note", + "Group": "Group", + "Ungroup": "Ungroup", + "Group as Database": "Group as Database", + "Move Up": "Move Up", + "Move Down": "Move Down", + "Appearance Settings": "Appearance Settings", + "Customize your AFFiNE Appearance": "Customize your AFFiNE Appearance", + "Theme": "Theme", + "Date": "Date", + "Sidebar": "Sidebar", + "Color Scheme": "Color Scheme", + "Choose your color scheme": "Choose your color scheme", + "Display Language": "Display Language", + "Select the language for the interface.": "Select the language for the interface.", + "Client Border Style": "Client Border Style", + "Customize the appearance of the client.": "Customize the appearance of the client.", + "Full width Layout": "Full width Layout", + "Maximum display of content within a page.": "Maximum display of content within a page.", + "Window frame style": "Window frame style", + "Customize appearance of Windows Client.": "Customize appearance of Windows Client.", + "Date Format": "Date Format", + "Customize your date style.": "Customize your date style.", + "Start Week On Monday": "Start Week On Monday", + "By default, the week starts on Sunday.": "By default, the week starts on Sunday.", + "Disable the noise background on the sidebar": "Disable the noise background on the sidebar", + "None yet": "None yet", + "Disable the blur sidebar": "Disable the blur sidebar", + "frameless": "Frameless", + "NativeTitleBar": "Native Titlebar", + "About AFFiNE": "About AFFiNE", + "Version": "Version", + "Contact with us": "Contact with us", + "Communities": "Communities", + "Info of legal": "Info of legal", + "Check for updates": "Check for updates", + "New version is ready": "New version is ready", + "Check for updates automatically": "Check for updates automatically", + "If enabled, it will automatically check for new versions at regular intervals.": "If enabled, it will automatically check for new versions at regular intervals.", + "Download updates automatically": "Download updates automatically", + "If enabled, new versions will be automatically downloaded to the current device.": " If enabled, new versions will be automatically downloaded to the current device.", + "Discover what's new": "Discover what's new", + "View the AFFiNE Changelog.": "View the AFFiNE Changelog.", + "Privacy": "Privacy", + "Terms of Use": "Terms of Use" } diff --git a/packages/storybook/package.json b/packages/storybook/package.json index e2c44623f2..f7f3defbf7 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -33,7 +33,7 @@ "@blocksuite/blocks": "0.0.0-20230607055421-9b20fcaf-nightly", "@blocksuite/editor": "0.0.0-20230607055421-9b20fcaf-nightly", "@blocksuite/global": "0.0.0-20230607055421-9b20fcaf-nightly", - "@blocksuite/icons": "^2.1.19", + "@blocksuite/icons": "^2.1.21", "@blocksuite/lit": "0.0.0-20230607055421-9b20fcaf-nightly", "@blocksuite/store": "0.0.0-20230607055421-9b20fcaf-nightly", "react": "18.3.0-canary-16d053d59-20230506", diff --git a/tests/parallels/layout.spec.ts b/tests/parallels/layout.spec.ts index 780073d4db..35388270b0 100644 --- a/tests/parallels/layout.spec.ts +++ b/tests/parallels/layout.spec.ts @@ -47,7 +47,7 @@ test('Drag resizer can resize sidebar', async ({ page }) => { }); await page.mouse.up(); const boundingBox = await page.getByTestId('app-sidebar').boundingBox(); - expect(boundingBox?.width).toBe(400); + expect(boundingBox?.width).toBe(399); }); test('Sidebar in between sm & md breakpoint', async ({ page }) => { diff --git a/tests/parallels/shortcuts.spec.ts b/tests/parallels/shortcuts.spec.ts index 61d6b7dba0..fff5d42ecd 100644 --- a/tests/parallels/shortcuts.spec.ts +++ b/tests/parallels/shortcuts.spec.ts @@ -16,5 +16,5 @@ test('Open shortcuts modal', async ({ page }) => { await shortcutsIcon.click(); await page.waitForTimeout(1000); const shortcutsModal = page.locator('[data-testid=shortcuts-modal]'); - await expect(shortcutsModal).toContainText('Keyboard shortcuts'); + await expect(shortcutsModal).toContainText('Page'); }); diff --git a/yarn.lock b/yarn.lock index 2e5540995e..b8834b84c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -69,7 +69,7 @@ __metadata: "@blocksuite/blocks": 0.0.0-20230607055421-9b20fcaf-nightly "@blocksuite/editor": 0.0.0-20230607055421-9b20fcaf-nightly "@blocksuite/global": 0.0.0-20230607055421-9b20fcaf-nightly - "@blocksuite/icons": ^2.1.19 + "@blocksuite/icons": ^2.1.21 "@blocksuite/lit": 0.0.0-20230607055421-9b20fcaf-nightly "@blocksuite/store": 0.0.0-20230607055421-9b20fcaf-nightly "@dnd-kit/core": ^6.0.8 @@ -398,7 +398,7 @@ __metadata: "@blocksuite/blocks": 0.0.0-20230607055421-9b20fcaf-nightly "@blocksuite/editor": 0.0.0-20230607055421-9b20fcaf-nightly "@blocksuite/global": 0.0.0-20230607055421-9b20fcaf-nightly - "@blocksuite/icons": ^2.1.19 + "@blocksuite/icons": ^2.1.21 "@blocksuite/lit": 0.0.0-20230607055421-9b20fcaf-nightly "@blocksuite/store": 0.0.0-20230607055421-9b20fcaf-nightly "@storybook/addon-actions": ^7.0.21 @@ -455,7 +455,7 @@ __metadata: "@blocksuite/blocks": 0.0.0-20230607055421-9b20fcaf-nightly "@blocksuite/editor": 0.0.0-20230607055421-9b20fcaf-nightly "@blocksuite/global": 0.0.0-20230607055421-9b20fcaf-nightly - "@blocksuite/icons": ^2.1.19 + "@blocksuite/icons": ^2.1.21 "@blocksuite/lit": 0.0.0-20230607055421-9b20fcaf-nightly "@blocksuite/store": 0.0.0-20230607055421-9b20fcaf-nightly "@dnd-kit/core": ^6.0.8 @@ -3984,13 +3984,13 @@ __metadata: languageName: node linkType: hard -"@blocksuite/icons@npm:^2.1.19": - version: 2.1.19 - resolution: "@blocksuite/icons@npm:2.1.19" +"@blocksuite/icons@npm:^2.1.21": + version: 2.1.21 + resolution: "@blocksuite/icons@npm:2.1.21" peerDependencies: "@types/react": ^18.0.25 react: ^18.2.0 - checksum: 6b4abbac571324241a1ada723553f8e7fd2e91c0efd57fda68ea24ffc301e4ebb2691ba59744593109b153039dc94299b139f674fc656be48037d0e29454f71b + checksum: ade86c53243691da1aae2bf2abca88b0d9594590a59cf30ec361cba8cb4268737e7129fc0a61ad87e610d709e3eb3d10c8fea3bb76beeeebb334dd14f1001ea1 languageName: node linkType: hard