Skip to content

Commit

Permalink
feat: new search
Browse files Browse the repository at this point in the history
  • Loading branch information
righ committed Nov 16, 2024
1 parent ef0d77d commit 58f7575
Show file tree
Hide file tree
Showing 17 changed files with 288 additions and 149 deletions.
2 changes: 2 additions & 0 deletions .storybook/examples/basic/resize.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export const ResizeSheets = () => {
sheetResize: "horizontal",
sheetHeight: 500,
sheetWidth: 500,
showFormulaBar: false,
}}
/>
</td>
Expand All @@ -95,6 +96,7 @@ export const ResizeSheets = () => {
sheetResize: "none",
sheetHeight: 500,
sheetWidth: 500,
showFormulaBar: false,
}}
/>
</td>
Expand Down
8 changes: 4 additions & 4 deletions e2e/search.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { test, expect } from '@playwright/test';
test('search and next', async ({ page }) => {
await page.goto('http://localhost:5233/iframe.html?id=basic--large&viewMode=story');

const searchBox = page.locator('.gs-searchbox');
const searchBar = page.locator('.gs-search-bar');
const progress = page.locator('.gs-search-progress');
expect(await searchBox.count()).toBe(0);
expect(await searchBar.count()).toBe(0);

await page.keyboard.down('Control');
await page.keyboard.press('f');

expect(await searchBox.count()).toBe(1);
expect(await searchBar.count()).toBe(1);
expect(await progress.textContent()).toBe('0 / 0');

await page.keyboard.up('Control');
Expand Down Expand Up @@ -44,5 +44,5 @@ test('search and next', async ({ page }) => {
expect(await a1000.getAttribute('class')).toContain('gs-choosing');

await page.keyboard.press('Escape');
expect(await searchBox.count()).toBe(0);
expect(await searchBar.count()).toBe(0);
});
8 changes: 4 additions & 4 deletions src/components/GridSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { functions } from '../formula/mapping';
import { Context } from '../store';
import { reducer as defaultReducer } from '../store/actions';

import { SearchBox } from './SearchBox';
import { Editor } from './Editor';
import { StoreInitializer } from './StoreInitializer';
import { Resizer } from './Resizer';
Expand All @@ -28,6 +27,7 @@ import { x2c, y2r } from '../lib/converters';
import { embedStyle } from '../styles/embedder';
import { useSheetContext } from './SheetProvider';
import { FormulaBar } from './FormulaBar';
import { SearchBar } from './SearchBar';

export function GridSheet({
initialCells: initialData,
Expand All @@ -42,7 +42,7 @@ export function GridSheet({
const [prevSheetName, setPrevSheetName] = React.useState(sheetName);
const rootRef = React.useRef<HTMLDivElement | null>(null);
const mainRef = React.useRef<HTMLDivElement | null>(null);
const searchInputRef = React.useRef<HTMLInputElement | null>(null);
const searchInputRef = React.useRef<HTMLTextAreaElement | null>(null);
const editorRef = React.useRef<HTMLTextAreaElement | null>(null);
const largeEditorRef = React.useRef<HTMLTextAreaElement | null>(null);
const tabularRef = React.useRef<HTMLDivElement | null>(null);
Expand Down Expand Up @@ -114,6 +114,7 @@ export function GridSheet({
entering: false,
matchingCells: [],
matchingCellIndex: 0,
searchCaseSensitive: false,
editingOnEnter: true,
showAddress: true,
contextMenuPosition: { y: -1, x: -1 },
Expand Down Expand Up @@ -176,8 +177,7 @@ export function GridSheet({
return (
<Context.Provider value={{ store, dispatch }}>
<div className={`gs-root1`} ref={rootRef} data-sheet-name={sheetName} data-mode={mode}>
{showFormulaBar && <FormulaBar />}
<SearchBox />
{typeof store.searchQuery === 'undefined' ? showFormulaBar && <FormulaBar /> : <SearchBar />}
<div
className={`gs-main ${className || ''}`}
ref={mainRef}
Expand Down
99 changes: 99 additions & 0 deletions src/components/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { a2p } from '../lib/converters';
import React from 'react';

import { Context } from '../store';
import { setSearchQuery, search, setSearchCaseSensitive } from '../store/actions';
import { smartScroll } from '../lib/virtualization';
import { SearchIcon } from './svg/SearchIcon';
import { CloseIcon } from './svg/CloseIcon';

export const SearchBar: React.FC = () => {
const { store, dispatch } = React.useContext(Context);
const {
rootRef,
editorRef,
searchInputRef,
tabularRef,
searchQuery,
searchCaseSensitive,
matchingCellIndex,
matchingCells,
table,
} = store;

const matchingCell = matchingCells[matchingCellIndex];
React.useEffect(() => {
if (!matchingCell) {
return;
}
const point = a2p(matchingCell);
if (typeof point === 'undefined') {
return;
}
smartScroll(table, tabularRef.current, point);
}, [searchQuery, matchingCellIndex, searchCaseSensitive]);

if (typeof searchQuery === 'undefined') {
return null;
}
if (rootRef.current === null) {
return null;
}
return (
<label className={`gs-search-bar ${matchingCells.length > 0 ? 'gs-search-found' : ''}`}>
<div
className="gs-search-progress"
onClick={(e) => {
const input = e.currentTarget.previousSibling as HTMLInputElement;
input?.nodeName === 'INPUT' && input.focus();
}}
>
{matchingCells.length === 0 ? 0 : matchingCellIndex + 1} / {matchingCells.length}
</div>
<div className="gs-search-bar-icon" onClick={() => dispatch(search(1))}>
<SearchIcon style={{ verticalAlign: 'middle', marginLeft: '5px' }} />
</div>
<textarea
ref={searchInputRef}
value={searchQuery}
onChange={(e) => dispatch(setSearchQuery(e.target.value))}
onKeyDown={(e) => {
if (e.key === 'Escape') {
const el = editorRef?.current;
if (el) {
el.focus();
}
dispatch(setSearchQuery(undefined));
}
if (e.key === 'f' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
return false;
}
if (e.key === 'Enter') {
dispatch(search(e.shiftKey ? -1 : 1));
e.preventDefault();
return false;
}
return true;
}}
></textarea>
<div className={`gs-search-casesensitive`}>
<span
className={`${searchCaseSensitive ? 'gs-search-casesensitive-on' : ''}`}
onClick={() => dispatch(setSearchCaseSensitive(!searchCaseSensitive))}
>
Aa
</span>
</div>
<a
className="gs-search-close"
onClick={() => {
dispatch(setSearchQuery(undefined));
editorRef.current?.focus();
}}
>
<CloseIcon style={{ verticalAlign: 'middle' }} />
</a>
</label>
);
};
92 changes: 0 additions & 92 deletions src/components/SearchBox.tsx

This file was deleted.

33 changes: 33 additions & 0 deletions src/components/svg/Base.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';

export interface IconProps {
style?: React.CSSProperties;
color?: string;
size?: number;
}

interface BaseProps extends IconProps {
children?: React.ReactNode;
}

// https://tabler.io/icons

export const Base = ({ style, size = 24, children }: BaseProps) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox={`0 0 24 24`}
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
style={style}
className="icon-tabler"
>
{children}
</svg>
);
};
14 changes: 14 additions & 0 deletions src/components/svg/CloseIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import { type IconProps, Base } from './Base';

// https://tabler.io/icons

export const CloseIcon = ({ style, color = 'none', size = 24 }: IconProps) => {
return (
<Base style={style} size={size}>
<path stroke="none" d="M0 0h24v24H0z" fill={color} />
<path d="M18 6l-12 12" fill={color} />
<path d="M6 6l12 12" fill={color} />
</Base>
);
};
14 changes: 14 additions & 0 deletions src/components/svg/SearchIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import { type IconProps, Base } from './Base';

// https://tabler.io/icons

export const SearchIcon = ({ style, color = 'none', size = 24 }: IconProps) => {
return (
<Base style={style} size={size}>
<path stroke="none" d="M0 0h24v24H0z" fill={color} />
<path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" fill={color} />
<path d="M21 21l-6 -6" fill={color} />
</Base>
);
};
13 changes: 13 additions & 0 deletions src/store/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ class SetSearchQueryAction<T extends string | undefined> extends CoreAction<T> {
}
export const setSearchQuery = new SetSearchQueryAction().bind();

class SetSearchCaseSensitiveAction<T extends boolean> extends CoreAction<T> {
reduce(store: StoreType, payload: T): StoreType {
const searchCaseSensitive = payload;
const { table } = store;
return {
...store,
...initSearchStatement(table, { ...store, searchCaseSensitive }),
searchCaseSensitive,
};
}
}
export const setSearchCaseSensitive = new SetSearchCaseSensitiveAction().bind();

class SetEditingCellAction<T extends string> extends CoreAction<T> {
reduce(store: StoreType, payload: T): StoreType {
return {
Expand Down
8 changes: 5 additions & 3 deletions src/store/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,18 @@ export const shouldTracking = (operation: string) => {
};

export const initSearchStatement = (table: Table, store: StoreType) => {
const { searchQuery } = store;
const { searchQuery, searchCaseSensitive } = store;
let { choosing } = store;
if (!searchQuery) {
return { matchingCells: [] };
}
const matchingCells: Address[] = [];
for (let y = 1; y <= table.bottom; y++) {
for (let x = 1; x <= table.right; x++) {
const s = table.stringify({ y, x }, undefined, true);
if (s.indexOf(searchQuery) !== -1) {
const v = table.stringify({ y, x }, undefined, true);
const s = searchCaseSensitive ? v : v.toLowerCase();
const q = searchCaseSensitive ? searchQuery : searchQuery.toLowerCase();
if (s.indexOf(q) !== -1) {
matchingCells.push(`${x2c(x)}${y2r(y)}`);
}
}
Expand Down
Loading

0 comments on commit 58f7575

Please sign in to comment.