mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
feat(core): add download app button to web (#5023)
This commit is contained in:
@@ -10,7 +10,6 @@ import type { ReactNode } from 'react';
|
||||
import { forwardRef, useRef } from 'react';
|
||||
|
||||
import * as style from './style.css';
|
||||
import { TopTip } from './top-tip';
|
||||
import { WindowsAppControls } from './windows-app-controls';
|
||||
|
||||
interface HeaderPros {
|
||||
@@ -49,61 +48,58 @@ export const Header = forwardRef<HTMLDivElement, HeaderPros>(function Header(
|
||||
const open = useAtomValue(appSidebarOpenAtom);
|
||||
const appSidebarFloating = useAtomValue(appSidebarFloatingAtom);
|
||||
return (
|
||||
<>
|
||||
<TopTip />
|
||||
<div
|
||||
className={clsx(style.header, bottomBorder && style.bottomBorder)}
|
||||
// data-has-warning={showWarning}
|
||||
data-open={open}
|
||||
data-sidebar-floating={appSidebarFloating}
|
||||
data-testid="header"
|
||||
ref={ref}
|
||||
>
|
||||
<div
|
||||
className={clsx(style.header, bottomBorder && style.bottomBorder)}
|
||||
// data-has-warning={showWarning}
|
||||
data-open={open}
|
||||
data-sidebar-floating={appSidebarFloating}
|
||||
data-testid="header"
|
||||
ref={ref}
|
||||
className={clsx(style.headerSideContainer, {
|
||||
block: isTinyScreen,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={clsx(style.headerSideContainer, {
|
||||
block: isTinyScreen,
|
||||
})}
|
||||
className={clsx(
|
||||
style.headerItem,
|
||||
'top-item',
|
||||
!open ? 'top-item-visible' : ''
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
style.headerItem,
|
||||
'top-item',
|
||||
!open ? 'top-item-visible' : ''
|
||||
)}
|
||||
>
|
||||
<div ref={sidebarSwitchRef}>
|
||||
<SidebarSwitch show={!open} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={clsx(style.headerItem, 'left')}>
|
||||
<div ref={leftSlotRef}>{left}</div>
|
||||
<div ref={sidebarSwitchRef}>
|
||||
<SidebarSwitch show={!open} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={clsx({
|
||||
[style.headerCenter]: center,
|
||||
'is-window': isWindowsDesktop,
|
||||
})}
|
||||
ref={centerSlotRef}
|
||||
>
|
||||
{center}
|
||||
</div>
|
||||
<div
|
||||
className={clsx(style.headerSideContainer, 'right', {
|
||||
block: isTinyScreen,
|
||||
})}
|
||||
>
|
||||
<div className={clsx(style.headerItem, 'top-item')}>
|
||||
<div ref={windowControlsRef}>
|
||||
{isWindowsDesktop ? <WindowsAppControls /> : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className={clsx(style.headerItem, 'right')}>
|
||||
<div ref={rightSlotRef}>{right}</div>
|
||||
</div>
|
||||
<div className={clsx(style.headerItem, 'left')}>
|
||||
<div ref={leftSlotRef}>{left}</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<div
|
||||
className={clsx({
|
||||
[style.headerCenter]: center,
|
||||
'is-window': isWindowsDesktop,
|
||||
})}
|
||||
ref={centerSlotRef}
|
||||
>
|
||||
{center}
|
||||
</div>
|
||||
<div
|
||||
className={clsx(style.headerSideContainer, 'right', {
|
||||
block: isTinyScreen,
|
||||
})}
|
||||
>
|
||||
<div className={clsx(style.headerItem, 'top-item')}>
|
||||
<div ref={windowControlsRef}>
|
||||
{isWindowsDesktop ? <WindowsAppControls /> : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className={clsx(style.headerItem, 'right')}>
|
||||
<div ref={rightSlotRef}>{right}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import { BrowserWarning } from '@affine/component/affine-banner';
|
||||
import { DownloadTips } from '@affine/component/affine-banner';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { guideDownloadClientTipAtom } from '../../../atoms/guide';
|
||||
|
||||
const minimumChromeVersion = 102;
|
||||
|
||||
const shouldShowWarning = () => {
|
||||
if (environment.isDesktop) {
|
||||
// even though desktop has compatibility issues,
|
||||
// we don't want to show the warning
|
||||
return false;
|
||||
}
|
||||
if (!environment.isBrowser) {
|
||||
// disable in SSR
|
||||
return false;
|
||||
}
|
||||
if (environment.isChrome) {
|
||||
return environment.chromeVersion < minimumChromeVersion;
|
||||
} else {
|
||||
return !environment.isMobile;
|
||||
}
|
||||
};
|
||||
|
||||
const OSWarningMessage = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [notChrome, setNotChrome] = useState(false);
|
||||
const [notGoodVersion, setNotGoodVersion] = useState(false);
|
||||
useEffect(() => {
|
||||
setNotChrome(environment.isBrowser && !environment.isChrome);
|
||||
setNotGoodVersion(
|
||||
environment.isBrowser &&
|
||||
environment.isChrome &&
|
||||
environment.chromeVersion < minimumChromeVersion
|
||||
);
|
||||
}, []);
|
||||
|
||||
if (notChrome) {
|
||||
return (
|
||||
<span>
|
||||
<Trans i18nKey="recommendBrowser">
|
||||
We recommend the <strong>Chrome</strong> browser for an optimal
|
||||
experience.
|
||||
</Trans>
|
||||
</span>
|
||||
);
|
||||
} else if (notGoodVersion) {
|
||||
return <span>{t['upgradeBrowser']()}</span>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
export const TopTip = () => {
|
||||
const [showWarning, setShowWarning] = useState(false);
|
||||
const [showDownloadTip, setShowDownloadTip] = useAtom(
|
||||
guideDownloadClientTipAtom
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setShowWarning(shouldShowWarning());
|
||||
}, []);
|
||||
|
||||
if (showDownloadTip && environment.isDesktop) {
|
||||
return (
|
||||
<DownloadTips
|
||||
onClose={() => {
|
||||
setShowDownloadTip(false);
|
||||
localStorage.setItem('affine-is-dt-hide', '1');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<BrowserWarning
|
||||
show={showWarning}
|
||||
message={<OSWarningMessage />}
|
||||
onClose={() => {
|
||||
setShowWarning(false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from '@affine/workspace/providers';
|
||||
import {
|
||||
CloudWorkspaceIcon,
|
||||
InformationFillDuotoneIcon,
|
||||
LocalWorkspaceIcon,
|
||||
NoNetworkIcon,
|
||||
UnsyncIcon,
|
||||
@@ -67,7 +68,11 @@ const UnSyncWorkspaceStatus = () => {
|
||||
const LocalWorkspaceStatus = () => {
|
||||
return (
|
||||
<>
|
||||
<LocalWorkspaceIcon />
|
||||
{!environment.isDesktop ? (
|
||||
<InformationFillDuotoneIcon data-warning-color="true" />
|
||||
) : (
|
||||
<LocalWorkspaceIcon />
|
||||
)}
|
||||
Local
|
||||
</>
|
||||
);
|
||||
@@ -109,6 +114,9 @@ const WorkspaceStatus = ({
|
||||
const content = useMemo(() => {
|
||||
// TODO: add i18n
|
||||
if (currentWorkspace.flavour === WorkspaceFlavour.LOCAL) {
|
||||
if (!environment.isDesktop) {
|
||||
return 'This is a local demo workspace.';
|
||||
}
|
||||
return 'Saved locally';
|
||||
}
|
||||
if (!isOnline) {
|
||||
|
||||
@@ -44,6 +44,9 @@ export const StyledWorkspaceStatus = styled('div')(() => {
|
||||
svg: {
|
||||
color: 'var(--affine-icon-color)',
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
'&[data-warning-color="true"]': {
|
||||
color: 'var(--affine-error-color)',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AnimatedDeleteIcon } from '@affine/component';
|
||||
import {
|
||||
AddPageButton,
|
||||
AppDownloadButton,
|
||||
AppSidebar,
|
||||
appSidebarOpenAtom,
|
||||
AppUpdaterButton,
|
||||
@@ -266,7 +267,7 @@ export const RootAppSidebar = ({
|
||||
)}
|
||||
</SidebarScrollableContainer>
|
||||
<SidebarContainer>
|
||||
{environment.isDesktop && <AppUpdaterButton />}
|
||||
{environment.isDesktop ? <AppUpdaterButton /> : <AppDownloadButton />}
|
||||
<div style={{ height: '4px' }} />
|
||||
<AddPageButton onClick={onClickNewPage} />
|
||||
</SidebarContainer>
|
||||
|
||||
122
packages/frontend/core/src/components/top-tip.tsx
Normal file
122
packages/frontend/core/src/components/top-tip.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import { BrowserWarning } from '@affine/component/affine-banner';
|
||||
import { LocalDemoTips } from '@affine/component/affine-banner';
|
||||
import {
|
||||
type AffineOfficialWorkspace,
|
||||
WorkspaceFlavour,
|
||||
} from '@affine/env/workspace';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { authAtom } from '../atoms';
|
||||
import { useCurrentLoginStatus } from '../hooks/affine/use-current-login-status';
|
||||
import { useOnTransformWorkspace } from '../hooks/root/use-on-transform-workspace';
|
||||
import { EnableAffineCloudModal } from './affine/enable-affine-cloud-modal';
|
||||
|
||||
const minimumChromeVersion = 106;
|
||||
|
||||
const shouldShowWarning = (() => {
|
||||
if (environment.isDesktop) {
|
||||
// even though desktop has compatibility issues,
|
||||
// we don't want to show the warning
|
||||
return false;
|
||||
}
|
||||
if (!environment.isBrowser) {
|
||||
// disable in SSR
|
||||
return false;
|
||||
}
|
||||
if (environment.isChrome) {
|
||||
return environment.chromeVersion < minimumChromeVersion;
|
||||
} else {
|
||||
return !environment.isMobile;
|
||||
}
|
||||
})();
|
||||
|
||||
const OSWarningMessage = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const notChrome = environment.isBrowser && !environment.isChrome;
|
||||
const notGoodVersion =
|
||||
environment.isBrowser &&
|
||||
environment.isChrome &&
|
||||
environment.chromeVersion < minimumChromeVersion;
|
||||
|
||||
if (notChrome) {
|
||||
return (
|
||||
<span>
|
||||
<Trans i18nKey="recommendBrowser">
|
||||
We recommend the <strong>Chrome</strong> browser for an optimal
|
||||
experience.
|
||||
</Trans>
|
||||
</span>
|
||||
);
|
||||
} else if (notGoodVersion) {
|
||||
return <span>{t['upgradeBrowser']()}</span>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const TopTip = ({
|
||||
workspace,
|
||||
}: {
|
||||
workspace: AffineOfficialWorkspace;
|
||||
}) => {
|
||||
const loginStatus = useCurrentLoginStatus();
|
||||
const isLoggedIn = loginStatus === 'authenticated';
|
||||
|
||||
const [showWarning, setShowWarning] = useState(shouldShowWarning);
|
||||
const [showLocalDemoTips, setShowLocalDemoTips] = useState(true);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const setAuthModal = useSetAtom(authAtom);
|
||||
const onLogin = useCallback(() => {
|
||||
setAuthModal({ openModal: true, state: 'signIn' });
|
||||
}, [setAuthModal]);
|
||||
|
||||
const onTransformWorkspace = useOnTransformWorkspace();
|
||||
const handleConfirm = useCallback(() => {
|
||||
if (workspace.flavour !== WorkspaceFlavour.LOCAL) {
|
||||
return;
|
||||
}
|
||||
onTransformWorkspace(
|
||||
WorkspaceFlavour.LOCAL,
|
||||
WorkspaceFlavour.AFFINE_CLOUD,
|
||||
workspace
|
||||
);
|
||||
setOpen(false);
|
||||
}, [onTransformWorkspace, workspace]);
|
||||
|
||||
if (
|
||||
showLocalDemoTips &&
|
||||
!environment.isDesktop &&
|
||||
workspace.flavour === WorkspaceFlavour.LOCAL
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
<LocalDemoTips
|
||||
isLoggedIn={isLoggedIn}
|
||||
onLogin={onLogin}
|
||||
onEnableCloud={() => setOpen(true)}
|
||||
onClose={() => {
|
||||
setShowLocalDemoTips(false);
|
||||
}}
|
||||
/>
|
||||
<EnableAffineCloudModal
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
onConfirm={handleConfirm}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<BrowserWarning
|
||||
show={showWarning}
|
||||
message={<OSWarningMessage />}
|
||||
onClose={() => {
|
||||
setShowWarning(false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -29,6 +29,7 @@ import { filterContainerStyle } from './filter-container.css';
|
||||
import { Header } from './pure/header';
|
||||
import { PluginHeader } from './pure/plugin-header';
|
||||
import { WorkspaceModeFilterTab } from './pure/workspace-mode-filter-tab';
|
||||
import { TopTip } from './top-tip';
|
||||
import * as styles from './workspace-header.css';
|
||||
|
||||
const FilterContainer = ({ workspaceId }: { workspaceId: string }) => {
|
||||
@@ -161,23 +162,26 @@ export function WorkspaceHeader({
|
||||
<SharePageModal workspace={currentWorkspace} page={currentPage} />
|
||||
) : null;
|
||||
return (
|
||||
<Header
|
||||
mainContainerAtom={mainContainerAtom}
|
||||
ref={setAppHeader}
|
||||
center={
|
||||
<BlockSuiteHeaderTitle
|
||||
workspace={currentWorkspace}
|
||||
pageId={currentEntry.pageId}
|
||||
/>
|
||||
}
|
||||
right={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
{sharePageModal}
|
||||
<PluginHeader />
|
||||
</div>
|
||||
}
|
||||
bottomBorder
|
||||
/>
|
||||
<>
|
||||
<Header
|
||||
mainContainerAtom={mainContainerAtom}
|
||||
ref={setAppHeader}
|
||||
center={
|
||||
<BlockSuiteHeaderTitle
|
||||
workspace={currentWorkspace}
|
||||
pageId={currentEntry.pageId}
|
||||
/>
|
||||
}
|
||||
right={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
{sharePageModal}
|
||||
<PluginHeader />
|
||||
</div>
|
||||
}
|
||||
bottomBorder
|
||||
/>
|
||||
<TopTip workspace={currentWorkspace} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user