mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
fix(electron): optimize find in page in electron (#9900)
fix AF-2168 When using find in page (cmd+f) in electron, the popup should not prevent the user from interacting with the main content. Also fixed some minor ui issues
This commit is contained in:
@@ -25,32 +25,20 @@ const contentHide = keyframes({
|
||||
},
|
||||
});
|
||||
|
||||
export const modalOverlay = style({
|
||||
export const anchor = style({
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
backgroundColor: 'transparent',
|
||||
zIndex: cssVar('zIndexModal'),
|
||||
});
|
||||
|
||||
export const modalContentWrapper = style({
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'flex-end',
|
||||
zIndex: cssVar('zIndexModal'),
|
||||
right: '28px',
|
||||
top: '80px',
|
||||
zIndex: cssVar('zIndexModal'),
|
||||
});
|
||||
|
||||
export const modalContent = style({
|
||||
export const contentContainer = style({
|
||||
width: 400,
|
||||
height: 48,
|
||||
backgroundColor: cssVar('backgroundOverlayPanelColor'),
|
||||
borderRadius: '8px',
|
||||
boxShadow: cssVar('shadow3'),
|
||||
minHeight: 48,
|
||||
// :focus-visible will set outline
|
||||
outline: 'none',
|
||||
display: 'flex',
|
||||
gap: 8,
|
||||
@@ -116,6 +104,7 @@ export const input = style({
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
color: 'transparent',
|
||||
fontSize: '15px',
|
||||
});
|
||||
|
||||
export const inputHack = style([
|
||||
@@ -134,19 +123,22 @@ export const count = style({
|
||||
userSelect: 'none',
|
||||
});
|
||||
|
||||
export const arrowButtonContainer = style({
|
||||
border: '1px solid',
|
||||
borderColor: cssVarV2('layer/insideBorder/border'),
|
||||
flexShrink: 0,
|
||||
borderRadius: '4px',
|
||||
overflow: 'hidden',
|
||||
});
|
||||
|
||||
export const arrowButton = style({
|
||||
width: 32,
|
||||
height: 32,
|
||||
flexShrink: 0,
|
||||
border: '1px solid',
|
||||
borderColor: cssVarV2('layer/insideBorder/border'),
|
||||
borderRadius: 0,
|
||||
selectors: {
|
||||
'&.backward': {
|
||||
borderRadius: '4px 0 0 4px',
|
||||
},
|
||||
'&.forward': {
|
||||
borderLeft: 'none',
|
||||
borderRadius: '0 4px 4px 0',
|
||||
'&:first-child': {
|
||||
borderRight: '1px solid',
|
||||
borderColor: cssVarV2('layer/insideBorder/border'),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
CloseIcon,
|
||||
SearchIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import * as Popover from '@radix-ui/react-popover';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||
import clsx from 'clsx';
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from 'react';
|
||||
import { useTransition } from 'react-transition-state';
|
||||
|
||||
import * as styles from './find-in-page-modal.css';
|
||||
import * as styles from './find-in-page-popup.css';
|
||||
|
||||
const animationTimeout = 120;
|
||||
|
||||
@@ -36,8 +36,10 @@ const drawText = (
|
||||
}
|
||||
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = canvas.getBoundingClientRect().width * dpr;
|
||||
canvas.height = canvas.getBoundingClientRect().height * dpr;
|
||||
// the container will be animated,
|
||||
// so we need to use clientWidth and clientHeight instead of getBoundingClientRect
|
||||
canvas.width = canvas.clientWidth * dpr;
|
||||
canvas.height = canvas.clientHeight * dpr;
|
||||
|
||||
const rootStyles = getComputedStyle(document.documentElement);
|
||||
const textColor = rootStyles
|
||||
@@ -46,15 +48,13 @@ const drawText = (
|
||||
|
||||
ctx.scale(dpr, dpr);
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
const offsetX = -scrollLeft;
|
||||
ctx.fillStyle = textColor;
|
||||
ctx.font = '15px Inter';
|
||||
|
||||
const offsetX = -scrollLeft; // Offset based on scrollLeft
|
||||
|
||||
ctx.fillText(text, offsetX, 22);
|
||||
|
||||
ctx.letterSpacing = '0.01em';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textBaseline = 'alphabetic';
|
||||
ctx.fillText(text, offsetX, 22);
|
||||
};
|
||||
|
||||
const CanvasText = ({
|
||||
@@ -80,7 +80,7 @@ const CanvasText = ({
|
||||
return <canvas className={className} ref={ref} />;
|
||||
};
|
||||
|
||||
export const FindInPageModal = () => {
|
||||
export const FindInPagePopup = () => {
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
const findInPage = useService(FindInPageService).findInPage;
|
||||
@@ -123,22 +123,6 @@ export const FindInPageModal = () => {
|
||||
setActive(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
setValue(findInPage.searchText$.value || '');
|
||||
const onEsc = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
findInPage.onChangeVisible(false);
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', onEsc);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onEsc);
|
||||
};
|
||||
}
|
||||
return () => {};
|
||||
}, [findInPage, findInPage.searchText$.value, visible]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsub = findInPage.isSearching$.subscribe(() => {
|
||||
inputRef.current?.focus();
|
||||
@@ -169,6 +153,7 @@ export const FindInPageModal = () => {
|
||||
},
|
||||
[findInPage]
|
||||
);
|
||||
|
||||
const handleDone = useCallback(() => {
|
||||
onChangeVisible(false);
|
||||
}, [onChangeVisible]);
|
||||
@@ -207,79 +192,87 @@ export const FindInPageModal = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog.Root open={status !== 'exited'}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className={styles.modalOverlay} />
|
||||
<div className={styles.modalContentWrapper}>
|
||||
<Dialog.Content
|
||||
style={assignInlineVars({
|
||||
[styles.animationTimeout]: `${animationTimeout}ms`,
|
||||
<Popover.Root open={status !== 'exited'} onOpenChange={onChangeVisible}>
|
||||
<Popover.Anchor className={styles.anchor} data-find-in-page-anchor />
|
||||
<Popover.Portal>
|
||||
<Popover.Content
|
||||
style={assignInlineVars({
|
||||
[styles.animationTimeout]: `${animationTimeout}ms`,
|
||||
})}
|
||||
className={styles.contentContainer}
|
||||
data-state={status}
|
||||
sideOffset={5}
|
||||
side="left"
|
||||
onFocusOutside={e => {
|
||||
// do not close the popup when focus outside (like focus in the editor)
|
||||
e.preventDefault();
|
||||
}}
|
||||
onPointerDownOutside={e => {
|
||||
// do not close the popup when clicking outside (like clicking at the sidebar)
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={clsx(styles.inputContainer, {
|
||||
active: active || isSearching,
|
||||
})}
|
||||
className={styles.modalContent}
|
||||
data-state={status}
|
||||
>
|
||||
<div
|
||||
className={clsx(styles.inputContainer, {
|
||||
active: active || isSearching,
|
||||
})}
|
||||
>
|
||||
<SearchIcon className={styles.searchIcon} />
|
||||
<div className={styles.inputMain}>
|
||||
<RowInput
|
||||
type="text"
|
||||
autoFocus
|
||||
value={value}
|
||||
ref={inputRef}
|
||||
style={{
|
||||
visibility: isSearching ? 'hidden' : 'visible',
|
||||
}}
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleFocus}
|
||||
className={styles.input}
|
||||
onKeyDown={handleKeydown}
|
||||
onChange={handleValueChange}
|
||||
onScroll={handleScroll}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={handleCompositionEnd}
|
||||
/>
|
||||
<CanvasText
|
||||
className={styles.inputHack}
|
||||
text={value}
|
||||
scrollLeft={scrollLeft}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.count}>
|
||||
{value.length > 0 && result && result.matches !== 0 ? (
|
||||
<>
|
||||
<span>{result?.activeMatchOrdinal || 0}</span>
|
||||
<span>/</span>
|
||||
<span>{result?.matches || 0}</span>
|
||||
</>
|
||||
) : value.length ? (
|
||||
<span>No matches</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<IconButton
|
||||
size="24"
|
||||
className={clsx(styles.arrowButton, 'backward')}
|
||||
onClick={handleBackWard}
|
||||
icon={<ArrowUpSmallIcon />}
|
||||
<SearchIcon className={styles.searchIcon} />
|
||||
<div className={styles.inputMain}>
|
||||
<RowInput
|
||||
type="text"
|
||||
autoFocus
|
||||
value={value}
|
||||
ref={inputRef}
|
||||
style={{
|
||||
visibility: isSearching ? 'hidden' : 'visible',
|
||||
}}
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleFocus}
|
||||
className={styles.input}
|
||||
onKeyDown={handleKeydown}
|
||||
onChange={handleValueChange}
|
||||
onScroll={handleScroll}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={handleCompositionEnd}
|
||||
/>
|
||||
<IconButton
|
||||
size="24"
|
||||
className={clsx(styles.arrowButton, 'forward')}
|
||||
onClick={handleForward}
|
||||
icon={<ArrowDownSmallIcon />}
|
||||
<CanvasText
|
||||
className={styles.inputHack}
|
||||
text={value}
|
||||
scrollLeft={scrollLeft}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.count}>
|
||||
{value.length > 0 && result && result.matches !== 0 ? (
|
||||
<>
|
||||
<span>{result?.activeMatchOrdinal || 0}</span>
|
||||
<span>/</span>
|
||||
<span>{result?.matches || 0}</span>
|
||||
</>
|
||||
) : value.length ? (
|
||||
<span>No matches</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IconButton onClick={handleDone} icon={<CloseIcon />} />
|
||||
</Dialog.Content>
|
||||
</div>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
<div className={styles.arrowButtonContainer}>
|
||||
<IconButton
|
||||
size="24"
|
||||
className={styles.arrowButton}
|
||||
onClick={handleBackWard}
|
||||
icon={<ArrowUpSmallIcon />}
|
||||
/>
|
||||
<IconButton
|
||||
size="24"
|
||||
className={styles.arrowButton}
|
||||
onClick={handleForward}
|
||||
icon={<ArrowDownSmallIcon />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<IconButton onClick={handleDone} icon={<CloseIcon />} />
|
||||
</Popover.Content>
|
||||
</Popover.Portal>
|
||||
</Popover.Root>
|
||||
);
|
||||
};
|
||||
@@ -6,7 +6,7 @@ import { Outlet } from 'react-router-dom';
|
||||
|
||||
import { GlobalDialogs } from '../../dialogs';
|
||||
import { CustomThemeModifier } from './custom-theme';
|
||||
import { FindInPageModal } from './find-in-page/find-in-page-modal';
|
||||
import { FindInPagePopup } from './find-in-page/find-in-page-popup';
|
||||
|
||||
export const RootWrapper = () => {
|
||||
const defaultServerService = useService(DefaultServerService);
|
||||
@@ -30,7 +30,7 @@ export const RootWrapper = () => {
|
||||
<NotificationCenter />
|
||||
<Outlet />
|
||||
<CustomThemeModifier />
|
||||
{BUILD_CONFIG.isElectron && <FindInPageModal />}
|
||||
{BUILD_CONFIG.isElectron && <FindInPagePopup />}
|
||||
</FrameworkScope>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user