diff --git a/apps/web/__e2e__/models/base-view.model.ts b/apps/web/__e2e__/models/base-view.model.ts index 65cffb08d..63d0638a2 100644 --- a/apps/web/__e2e__/models/base-view.model.ts +++ b/apps/web/__e2e__/models/base-view.model.ts @@ -47,7 +47,7 @@ export class BaseViewModel { async findGroup(groupName: string) { const locator = this.list - .locator(getTestId(`virtualized-list`)) + .locator(getTestId(`virtuoso-item-list`)) .locator(getTestId("group-header")); for await (const item of iterateList(locator)) { @@ -95,7 +95,7 @@ export class BaseViewModel { // } async press(key: string) { - const itemList = this.list.locator(getTestId(`virtualized-list`)); + const itemList = this.list.locator(getTestId(`virtuoso-item-list`)); await itemList.press(key); await this.page.waitForTimeout(300); } diff --git a/apps/web/src/components/list-container/index.tsx b/apps/web/src/components/list-container/index.tsx index 4985e43d4..b6f409302 100644 --- a/apps/web/src/components/list-container/index.tsx +++ b/apps/web/src/components/list-container/index.tsx @@ -98,28 +98,42 @@ function ListContainer(props: ListContainerProps) { }, []); const { onFocus, onMouseDown, onKeyDown } = useKeyboardListNavigation({ - length: items.ids.length, + length: items.length, reset: () => toggleSelection(false), - deselect: (index) => deselectItem(items.ids[index]), - select: (index, toggleable) => - toggleable && isSelected(items.ids[index]) - ? deselectItem(items.ids[index]) - : selectItem(items.ids[index]), - bulkSelect: (indices) => setSelectedItems(indices.map((i) => items.ids[i])), + deselect: (index) => { + const id = items.cacheItem(index)?.item.id; + if (!id) return; + deselectItem(id); + }, + select: (index, toggleable) => { + const id = items.cacheItem(index)?.item.id; + if (!id) return; + if (toggleable && isSelected(id)) deselectItem(id); + else selectItem(id); + }, + bulkSelect: async (indices) => { + const ids = + indices.length === items.length + ? await items.ids() + : (indices + .map((i) => items.cacheItem(i)?.item.id) + .filter(Boolean) as string[]); + setSelectedItems(ids); + }, focusItemAt: (index) => { - const item = items.ids[index]; - if (!item || !listRef.current) return; + const id = items.cacheItem(index)?.item.id; + if (!id || !listRef.current) return; - waitForElement(listRef.current, index, `id_${item}`, (element) => + waitForElement(listRef.current, index, `id_${id}`, (element) => element.focus() ); }, - skip: (index) => !items.ids[index] || isGroupHeader(items.ids[index]), + skip: () => false, open: (index) => { - const item = items.ids[index]; - if (!item || !listRef.current) return; + const id = items.cacheItem(index)?.item.id; + if (!id || !listRef.current) return; - waitForElement(listRef.current, index, `id_${item}`, (element) => + waitForElement(listRef.current, index, `id_${id}`, (element) => element.click() ); } @@ -127,7 +141,7 @@ function ListContainer(props: ListContainerProps) { return ( - {!props.items.ids.length && props.placeholder ? ( + {!props.items.length && props.placeholder ? ( <> {header} {props.isLoading ? ( @@ -149,7 +163,7 @@ function ListContainer(props: ListContainerProps) { ref={listRef} computeItemKey={(index) => items.key(index)} defaultItemHeight={DEFAULT_ITEM_HEIGHT} - totalCount={items.ids.length} + totalCount={items.length} onBlur={() => setFocusedGroupIndex(-1)} onKeyDown={(e) => onKeyDown(e.nativeEvent)} components={{ @@ -210,7 +224,7 @@ type ListContext = { group: GroupingKey | undefined; refresh: () => void; focusedGroupIndex: number; - selectItems: (items: any) => void; + selectItems: (items: string[]) => void; scrollToIndex?: ( index: number, options?: ScrollToOptions | undefined @@ -282,17 +296,21 @@ function ItemRenderer({ title={resolvedItem.group.title} isFocused={index === focusedGroupIndex} index={index} - onSelectGroup={() => { - let endIndex; - for (let i = index + 1; i < items.ids.length; ++i) { - if (typeof items.ids[i] === "object") { - endIndex = i; - break; - } - } + onSelectGroup={async () => { + if (!items.groups) return; + + const groups = await items.groups(); + const groupIndex = groups.findIndex((g) => g.index === index); + if (groupIndex < 0) return; + + const nextGroupIndex = + groups[groupIndex + 1]?.index || items.length; + + const ids = await items.ids(); + selectItems([ ...selectionStore.get().selectedItems, - ...items.ids.slice(index, endIndex || items.ids.length) + ...ids.slice(index, nextGroupIndex) ]); }} groups={async () => (items.groups ? items.groups() : [])} diff --git a/apps/web/src/components/list-item/index.tsx b/apps/web/src/components/list-item/index.tsx index e070b4faf..6b8401a35 100644 --- a/apps/web/src/components/list-item/index.tsx +++ b/apps/web/src/components/list-item/index.tsx @@ -79,8 +79,7 @@ function ListItem( const isMenuTarget = target && target === listItemRef.current; const isSelected = useSelectionStore((store) => { - const isInSelection = - store.selectedItems.findIndex((item) => item === props.item.id) > -1; + const isInSelection = store.selectedItems.includes(props.item.id); return isFocused ? store.selectedItems.length > 1 && isInSelection : isInSelection; diff --git a/apps/web/src/stores/selection-store.js b/apps/web/src/stores/selection-store.ts similarity index 68% rename from apps/web/src/stores/selection-store.js rename to apps/web/src/stores/selection-store.ts index a101e14f3..d15ad78bc 100644 --- a/apps/web/src/stores/selection-store.js +++ b/apps/web/src/stores/selection-store.ts @@ -20,15 +20,12 @@ along with this program. If not, see . import createStore from "../common/store"; import BaseStore from "./index"; -/** - * @extends {BaseStore} - */ -class SelectionStore extends BaseStore { - selectedItems = []; +class SelectionStore extends BaseStore { + selectedItems: string[] = []; shouldSelectAll = false; isSelectionMode = false; - toggleSelectionMode = (toggleState) => { + toggleSelectionMode = (toggleState?: boolean) => { this.set((state) => { const isSelectionMode = toggleState !== undefined ? toggleState : !state.isSelectionMode; @@ -38,21 +35,19 @@ class SelectionStore extends BaseStore { }); }; - selectItem = (item) => { - console.log(this.get().selectedItems, item); - const index = this.get().selectedItems.findIndex((v) => item === v); + selectItem = (id: string) => { this.set((state) => { - if (index <= -1) { - state.selectedItems.push(item); + if (!state.selectedItems.includes(id)) { + state.selectedItems.push(id); } }); }; - deselectItem = (item) => { + deselectItem = (id: string) => { this.set((state) => { - const index = state.selectedItems.findIndex((v) => item === v); - if (index >= 0) { - state.selectedItems.splice(index, 1); + const itemAt = state.selectedItems.indexOf(id); + if (itemAt >= 0) { + state.selectedItems.splice(itemAt, 1); } }); @@ -61,12 +56,12 @@ class SelectionStore extends BaseStore { } }; - isSelected = (item) => { - return this.get().selectedItems.indexOf(item) > -1; + isSelected = (id: string) => { + return this.get().selectedItems.indexOf(id) > -1; }; - setSelectedItems = (items) => { - this.set((state) => (state.selectedItems = items)); + setSelectedItems = (ids: string[]) => { + this.set((state) => (state.selectedItems = ids)); }; selectAll = () => { diff --git a/packages/core/src/api/lookup.ts b/packages/core/src/api/lookup.ts index 2a614eda9..5dd3fbfa9 100644 --- a/packages/core/src/api/lookup.ts +++ b/packages/core/src/api/lookup.ts @@ -106,6 +106,7 @@ export default class Lookup { return new VirtualizedGrouping( ids.length, this.db.options.batchSize, + () => Promise.resolve(ids), async (start, end) => { return { ids: ids.slice(start, end), @@ -209,6 +210,7 @@ export default class Lookup { return new VirtualizedGrouping( ids.length, this.db.options.batchSize, + () => Promise.resolve(ids), async (start, end) => { const items = await selector.items(ids); return { diff --git a/packages/core/src/collections/trash.ts b/packages/core/src/collections/trash.ts index 82bda297d..94e8815c4 100644 --- a/packages/core/src/collections/trash.ts +++ b/packages/core/src/collections/trash.ts @@ -221,6 +221,7 @@ export default class Trash { return new VirtualizedGrouping( this.cache.notebooks.length + this.cache.notes.length, this.db.options.batchSize, + () => Promise.resolve([...this.cache.notebooks, ...this.cache.notes]), async (start, end) => { // const notesRange = end < this.cache.notes.length ? [start, end] : [start, this.cache.notes.length - 1]; // const notebooksRange = start >= this.cache.notes.length ?[start, end] : [ diff --git a/packages/core/src/database/sql-collection.ts b/packages/core/src/database/sql-collection.ts index 3270fadd8..ac19d2c8e 100644 --- a/packages/core/src/database/sql-collection.ts +++ b/packages/core/src/database/sql-collection.ts @@ -378,6 +378,7 @@ export class FilteredSelector { return new VirtualizedGrouping( count, this.batchSize, + () => this.ids(options), async (start, end) => { const items = (await this.filter .$call(this.buildSortExpression(options)) @@ -431,6 +432,7 @@ export class FilteredSelector { return new VirtualizedGrouping( count, this.batchSize, + () => this.ids(options), async (start, end) => { const items = (await this.filter .$call(this.buildSortExpression(options)) diff --git a/packages/core/src/utils/virtualized-grouping.ts b/packages/core/src/utils/virtualized-grouping.ts index 77fa1ed39..d3c847263 100644 --- a/packages/core/src/utils/virtualized-grouping.ts +++ b/packages/core/src/utils/virtualized-grouping.ts @@ -28,11 +28,11 @@ type Batch = { export class VirtualizedGrouping { private cache: Map> = new Map(); private pending: Map>> = new Map(); - public ids: boolean[]; constructor( - count: number, + readonly length: number, private readonly batchSize: number, + readonly ids: () => Promise, private readonly fetchItems: ( start: number, end: number @@ -41,9 +41,7 @@ export class VirtualizedGrouping { items: T[] ) => Map, readonly groups?: () => Promise<{ index: number; group: GroupHeader }[]> - ) { - this.ids = new Array(count).fill(false); - } + ) {} key(index: number) { return `${index}`; @@ -60,6 +58,7 @@ export class VirtualizedGrouping { ? "header-item" : "item"; } + cacheItem(index: number) { const batchIndex = Math.floor(index / this.batchSize); const batch = this.cache.get(batchIndex);