fix: left sidebar style fixes (#3950)

Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
Peng Xiao 2023-08-29 00:04:22 +08:00 committed by GitHub
parent e92d27549a
commit d62935935f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 235 additions and 99 deletions

View File

@ -27,7 +27,6 @@ export const StyledTextContent = styled('div')(() => {
return {
margin: 'auto',
width: '425px',
fontFamily: 'Avenir Next',
fontStyle: 'normal',
fontWeight: '400',
fontSize: '18px',

View File

@ -26,7 +26,6 @@ export const StyledTextContent = styled('div')(() => {
return {
margin: 'auto',
width: '425px',
fontFamily: 'Avenir Next',
fontStyle: 'normal',
fontWeight: '400',
fontSize: '18px',

View File

@ -29,6 +29,7 @@ const useDBFileSecondaryPath = (workspaceId: string) => {
}
});
}
return;
}, [workspaceId]);
return path;
};

View File

@ -111,6 +111,7 @@ export const RootAppSidebar = ({
if (isDesktop) {
return window.events?.applicationMenu.onNewPageAction(onClickNewPage);
}
return;
}, [onClickNewPage]);
const [sidebarOpen, setSidebarOpen] = useAtom(appSidebarOpenAtom);

View File

@ -15,7 +15,7 @@ export const updaterHandlers = {
if (res) {
const { updateInfo } = res;
return {
updateInfo,
version: updateInfo.version,
};
}
return null;

View File

@ -24,7 +24,7 @@ import { type PropsWithChildren, useState } from 'react';
import { MemoryRouter } from 'react-router-dom';
export default {
title: 'Components/AppSidebar',
title: 'AFFiNE/AppSidebar',
component: AppSidebar,
} satisfies Meta;

View File

@ -0,0 +1,60 @@
import {
type AddPageButtonPureProps,
AppUpdaterButtonPure,
} from '@affine/component/app-sidebar';
import type { Meta, StoryFn } from '@storybook/react';
import type { PropsWithChildren } from 'react';
export default {
title: 'AFFiNE/AppUpdaterButton',
component: AppUpdaterButtonPure,
parameters: {
chromatic: { disableSnapshot: true },
},
} satisfies Meta<typeof AppUpdaterButtonPure>;
const Container = ({ children }: PropsWithChildren) => (
<main
style={{
position: 'relative',
width: '320px',
display: 'flex',
flexDirection: 'row',
backgroundColor: '#eee',
padding: '16px',
}}
>
{children}
</main>
);
export const Default: StoryFn<AddPageButtonPureProps> = props => {
return (
<Container>
<AppUpdaterButtonPure {...props} />
</Container>
);
};
Default.args = {
appQuitting: false,
updateReady: true,
updateAvailable: {
version: '1.0.0-beta.1',
allowAutoUpdate: true,
},
downloadProgress: 42,
currentChangelogUnread: true,
};
export const Updated: StoryFn<AddPageButtonPureProps> = props => {
return (
<Container>
<AppUpdaterButtonPure {...props} updateAvailable={null} />
</Container>
);
};
Updated.args = {
currentChangelogUnread: true,
};

View File

@ -3,6 +3,7 @@ import type {
DBHandlerManager,
DebugHandlerManager,
DialogHandlerManager,
EventMap,
ExportHandlerManager,
UIHandlerManager,
UnwrapManagerHandlerToClientSide,
@ -24,7 +25,7 @@ declare global {
updater: UnwrapManagerHandlerToClientSide<UpdaterHandlerManager>;
workspace: UnwrapManagerHandlerToClientSide<WorkspaceHandlerManager>;
};
events: any;
events: EventMap;
}
interface WindowEventMap {

View File

@ -18,21 +18,23 @@ export const root = style({
'&:hover': {
background: 'var(--affine-white-60)',
},
'&[data-has-update="true"]:before': {
'&[data-disabled="true"]': {
pointerEvents: 'none',
},
'&:after': {
content: "''",
position: 'absolute',
top: '-3px',
right: '-3px',
top: '-2px',
right: '-2px',
width: '8px',
height: '8px',
backgroundColor: 'var(--affine-primary-color)',
borderRadius: '50%',
zIndex: 1,
opacity: 1,
transition: '0.3s ease',
transition: 'opacity 0.3s',
},
'&[data-disabled="true"]': {
pointerEvents: 'none',
'&:hover:after': {
opacity: 0,
},
},
vars: {
@ -42,7 +44,7 @@ export const root = style({
export const icon = style({
marginRight: '18px',
color: 'var(--affine-primary-color)',
color: 'var(--affine-icon-color)',
fontSize: '24px',
});
@ -90,7 +92,7 @@ export const installLabelNormal = style([
{
justifyContent: 'space-between',
selectors: {
[`${root}:hover &`]: {
[`${root}:hover &, ${root}[data-updating=true] &`]: {
display: 'none',
},
},
@ -102,7 +104,7 @@ export const installLabelHover = style([
{
display: 'none',
selectors: {
[`${root}:hover &`]: {
[`${root}:hover &, ${root}[data-updating=true] &`]: {
display: 'flex',
},
},
@ -175,7 +177,7 @@ export const particles = style({
pointerEvents: 'none',
display: 'none',
selectors: {
[`${root}:hover &`]: {
[`${root}:hover &, ${root}[data-updating=true] &`]: {
display: 'block',
},
'&:before': {
@ -224,8 +226,10 @@ export const halo = style({
'radial-gradient(ellipse 30% 45% at bottom, rgba(30, 150, 235, 0.6), transparent)',
},
selectors: {
'&:hover:before, &:hover:after': {
transform: 'translateY(0) scale(1)',
},
[`${root}:hover &:before, ${root}:hover &:after,
${root}[data-updating=true] &:before, ${root}[data-updating=true] &:after`]:
{
transform: 'translateY(0) scale(1)',
},
},
});

View File

@ -1,4 +1,5 @@
import { isBrowser } from '@affine/env/constant';
import type { UpdateMeta } from '@toeverything/infra';
import { atomWithObservable, atomWithStorage } from 'jotai/utils';
import { Observable } from 'rxjs';
@ -8,7 +9,7 @@ function rpcToObservable<
H extends () => Promise<T>,
E extends (callback: (t: T) => void) => () => void,
>(
initialValue: T,
initialValue: T | null,
{
event,
handler,
@ -18,8 +19,8 @@ function rpcToObservable<
handler?: H;
onSubscribe?: () => void;
}
) {
return new Observable<T>(subscriber => {
): Observable<T | null> {
return new Observable<T | null>(subscriber => {
subscriber.next(initialValue);
onSubscribe?.();
if (!isBrowser || !environment.isDesktop || !event) {
@ -40,13 +41,13 @@ function rpcToObservable<
}
export const updateReadyAtom = atomWithObservable(() => {
return rpcToObservable(null as any | null, {
return rpcToObservable(null as UpdateMeta | null, {
event: window.events?.updater.onUpdateReady,
});
});
export const updateAvailableAtom = atomWithObservable(() => {
return rpcToObservable(null as any | null, {
return rpcToObservable(null as UpdateMeta | null, {
event: window.events?.updater.onUpdateAvailable,
onSubscribe: () => {
window.apis?.updater.checkForUpdatesAndNotify().catch(err => {
@ -56,8 +57,8 @@ export const updateAvailableAtom = atomWithObservable(() => {
});
});
export const downloadProgressAtom = atomWithObservable<number>(() => {
return rpcToObservable(0, {
export const downloadProgressAtom = atomWithObservable(() => {
return rpcToObservable(null as number | null, {
event: window.events?.updater.onDownloadProgress,
});
});

View File

@ -14,7 +14,17 @@ import {
updateReadyAtom,
} from './index.jotai';
interface AddPageButtonProps {
export interface AddPageButtonPureProps {
onClickUpdate: () => void;
onDismissCurrentChangelog: () => void;
currentChangelogUnread: boolean;
updateReady: boolean;
updateAvailable: {
version: string;
allowAutoUpdate: boolean;
} | null;
downloadProgress: number | null;
appQuitting: boolean;
className?: string;
style?: React.CSSProperties;
}
@ -39,61 +49,19 @@ const currentChangelogUnreadAtom = atom(async get => {
return false;
});
// Although it is called an input, it is actually a button.
export function AppUpdaterButton({ className, style }: AddPageButtonProps) {
export function AppUpdaterButtonPure({
updateReady,
onClickUpdate,
onDismissCurrentChangelog,
currentChangelogUnread,
updateAvailable,
downloadProgress,
appQuitting,
className,
style,
}: AddPageButtonPureProps) {
const t = useAFFiNEI18N();
const currentChangelogUnread = useAtomValue(currentChangelogUnreadAtom);
const updateReady = useAtomValue(updateReadyAtom);
const updateAvailable = useAtomValue(updateAvailableAtom);
const currentVersion = useAtomValue(currentVersionAtom);
const downloadProgress = useAtomValue(downloadProgressAtom);
const setChangelogCheckAtom = useSetAtom(changelogCheckedAtom);
const [appQuitting, setAppQuitting] = useState(false);
const onDismissCurrentChangelog = useCallback(() => {
if (!currentVersion) {
return;
}
startTransition(() =>
setChangelogCheckAtom(mapping => {
return {
...mapping,
[currentVersion]: true,
};
})
);
}, [currentVersion, setChangelogCheckAtom]);
const onClickUpdate = useCallback(() => {
if (updateReady) {
setAppQuitting(true);
window.apis?.updater.quitAndInstall().catch(err => {
// TODO: add error toast here
console.error(err);
});
} else if (updateAvailable) {
if (updateAvailable.allowAutoUpdate) {
// wait for download to finish
} else {
window.open(
`https://github.com/toeverything/AFFiNE/releases/tag/v${currentVersion}`,
'_blank'
);
}
} else if (currentChangelogUnread) {
window.open(runtimeConfig.changelogUrl, '_blank');
onDismissCurrentChangelog();
} else {
throw new Unreachable();
}
}, [
currentChangelogUnread,
currentVersion,
onDismissCurrentChangelog,
updateAvailable,
updateReady,
]);
if (!updateAvailable && !currentChangelogUnread) {
return null;
}
@ -125,7 +93,8 @@ export function AppUpdaterButton({ className, style }: AddPageButtonProps) {
<button
style={style}
className={clsx([styles.root, className])}
data-has-update={updateAvailable ? 'true' : 'false'}
data-has-update={!!updateAvailable}
data-updating={appQuitting}
data-disabled={
(updateAvailable?.allowAutoUpdate && !updateReady) || appQuitting
}
@ -157,7 +126,9 @@ export function AppUpdaterButton({ className, style }: AddPageButtonProps) {
<div className={clsx([styles.installLabelHover])}>
<ResetIcon className={styles.icon} />
<span className={styles.ellipsisTextOverflow}>
{t['com.affine.updater.restart-to-update']()}
{t[
appQuitting ? 'Loading' : 'com.affine.updater.restart-to-update'
]()}
</span>
</div>
) : (
@ -215,3 +186,76 @@ export function AppUpdaterButton({ className, style }: AddPageButtonProps) {
);
}
}
// Although it is called an input, it is actually a button.
export function AppUpdaterButton({
className,
style,
}: {
className?: string;
style?: React.CSSProperties;
}) {
const currentChangelogUnread = useAtomValue(currentChangelogUnreadAtom);
const updateReady = useAtomValue(updateReadyAtom);
const updateAvailable = useAtomValue(updateAvailableAtom);
const currentVersion = useAtomValue(currentVersionAtom);
const downloadProgress = useAtomValue(downloadProgressAtom);
const setChangelogCheckAtom = useSetAtom(changelogCheckedAtom);
const [appQuitting, setAppQuitting] = useState(false);
const onDismissCurrentChangelog = useCallback(() => {
if (!currentVersion) {
return;
}
startTransition(() =>
setChangelogCheckAtom(mapping => {
return {
...mapping,
[currentVersion]: true,
};
})
);
}, [currentVersion, setChangelogCheckAtom]);
const onClickUpdate = useCallback(() => {
if (updateReady) {
setAppQuitting(true);
window.apis?.updater.quitAndInstall().catch(err => {
// TODO: add error toast here
console.error(err);
});
} else if (updateAvailable) {
if (updateAvailable.allowAutoUpdate) {
// wait for download to finish
} else {
window.open(
`https://github.com/toeverything/AFFiNE/releases/tag/v${currentVersion}`,
'_blank'
);
}
} else if (currentChangelogUnread) {
window.open(runtimeConfig.changelogUrl, '_blank');
onDismissCurrentChangelog();
} else {
throw new Unreachable();
}
}, [
currentChangelogUnread,
currentVersion,
onDismissCurrentChangelog,
updateAvailable,
updateReady,
]);
return (
<AppUpdaterButtonPure
appQuitting={appQuitting}
updateReady={!!updateReady}
onClickUpdate={onClickUpdate}
onDismissCurrentChangelog={onDismissCurrentChangelog}
currentChangelogUnread={currentChangelogUnread}
updateAvailable={updateAvailable}
downloadProgress={downloadProgress}
className={className}
style={style}
/>
);
}

View File

@ -70,7 +70,7 @@ export const mainContainerStyle = style({
backgroundColor: 'var(--affine-background-primary-color)',
selectors: {
'&[data-show-padding="true"]': {
margin: '8px',
margin: '8px 8px 8px 0',
borderRadius: '5px',
overflow: 'hidden',
boxShadow: 'var(--affine-shadow-1)',

View File

@ -184,10 +184,15 @@ export type ExportHandlers = {
savePDFFileAs: (title: string) => Promise<any>;
};
export interface UpdateMeta {
version: string;
allowAutoUpdate: boolean;
}
export type UpdaterHandlers = {
currentVersion: () => Promise<any>;
quitAndInstall: () => Promise<any>;
checkForUpdatesAndNotify: () => Promise<any>;
currentVersion: () => Promise<string>;
quitAndInstall: () => Promise<void>;
checkForUpdatesAndNotify: () => Promise<{ version: string } | null>;
};
export type WorkspaceHandlers = {
@ -196,15 +201,6 @@ export type WorkspaceHandlers = {
getMeta: (id: string) => Promise<WorkspaceMeta>;
};
export type EventMap = DBHandlers &
DebugHandlers &
DialogHandlers &
UIHandlers &
ClipboardHandlers &
ExportHandlers &
UpdaterHandlers &
WorkspaceHandlers;
export type UnwrapManagerHandlerToServerSide<
ElectronEvent extends {
frameId: number;
@ -242,3 +238,36 @@ export type App<
> = TypedEventEmitter<{
[K in keyof Handlers as `${Namespace}:${K & string}`]: Handlers[K];
}>;
export interface UpdaterEvents {
onUpdateAvailable: (fn: (versionMeta: UpdateMeta) => void) => () => void;
onUpdateReady: (fn: (versionMeta: UpdateMeta) => void) => () => void;
onDownloadProgress: (fn: (progress: number) => void) => () => void;
}
export interface ApplicationMenuEvents {
onNewPageAction: (fn: () => void) => () => void;
}
export interface DBEvents {
onExternalUpdate: (
fn: (update: {
workspaceId: string;
update: Uint8Array;
docId?: string;
}) => void
) => () => void;
}
export interface WorkspaceEvents {
onMetaChange: (
fn: (workspaceId: string, meta: WorkspaceMeta) => void
) => () => void;
}
export interface EventMap {
updater: UpdaterEvents;
applicationMenu: ApplicationMenuEvents;
db: DBEvents;
workspace: WorkspaceEvents;
}

View File

@ -55,7 +55,6 @@ vi.stubGlobal('window', {
},
events: {
db: {
// @ts-expect-error
onExternalUpdate: fn => {
triggerDBUpdate = fn;
return () => {
@ -119,7 +118,6 @@ describe('SQLite download provider', () => {
offlineYdoc.getText('text').insert(0, 'sqlite-world');
// @ts-expect-error
triggerDBUpdate?.({
workspaceId: id + '-another-id',
update: Y.encodeStateAsUpdate(offlineYdoc),
@ -128,7 +126,6 @@ describe('SQLite download provider', () => {
// not yet updated (because the workspace id is different)
expect(workspace.doc.getText('text').toString()).toBe('');
// @ts-expect-error
triggerDBUpdate?.({
workspaceId: id,
update: Y.encodeStateAsUpdate(offlineYdoc),

View File

@ -85,7 +85,7 @@ const ImagePreviewModalImpl = (
};
}
return () => {};
return;
}, [isOpen, props, setIsOpen]);
const nextImageHandler = useCallback(