web: fix selection logic in list

This commit is contained in:
Abdullah Atta
2023-12-26 09:34:29 +05:00
parent 882403bbe3
commit c2b3e35ba1
8 changed files with 70 additions and 54 deletions

View File

@@ -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);
}

View File

@@ -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 (
<Flex variant="columnFill" sx={{ overflow: "hidden" }}>
{!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() : [])}

View File

@@ -79,8 +79,7 @@ function ListItem<TItem extends Item, TContext>(
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;

View File

@@ -20,15 +20,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import createStore from "../common/store";
import BaseStore from "./index";
/**
* @extends {BaseStore<SelectionStore>}
*/
class SelectionStore extends BaseStore {
selectedItems = [];
class SelectionStore extends BaseStore<SelectionStore> {
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 = () => {

View File

@@ -106,6 +106,7 @@ export default class Lookup {
return new VirtualizedGrouping<TrashItem>(
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<T>(
ids.length,
this.db.options.batchSize,
() => Promise.resolve(ids),
async (start, end) => {
const items = await selector.items(ids);
return {

View File

@@ -221,6 +221,7 @@ export default class Trash {
return new VirtualizedGrouping<TrashItem>(
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] : [

View File

@@ -378,6 +378,7 @@ export class FilteredSelector<T extends Item> {
return new VirtualizedGrouping<T>(
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<T extends Item> {
return new VirtualizedGrouping<T>(
count,
this.batchSize,
() => this.ids(options),
async (start, end) => {
const items = (await this.filter
.$call(this.buildSortExpression(options))

View File

@@ -28,11 +28,11 @@ type Batch<T> = {
export class VirtualizedGrouping<T> {
private cache: Map<number, Batch<T>> = new Map();
private pending: Map<number, Promise<Batch<T>>> = new Map();
public ids: boolean[];
constructor(
count: number,
readonly length: number,
private readonly batchSize: number,
readonly ids: () => Promise<string[]>,
private readonly fetchItems: (
start: number,
end: number
@@ -41,9 +41,7 @@ export class VirtualizedGrouping<T> {
items: T[]
) => Map<number, { index: number; hidden?: boolean; group: GroupHeader }>,
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<T> {
? "header-item"
: "item";
}
cacheItem(index: number) {
const batchIndex = Math.floor(index / this.batchSize);
const batch = this.cache.get(batchIndex);