mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-01 19:49:54 +02:00
web: fix selection logic in list
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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() : [])}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = () => {
|
||||
@@ -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 {
|
||||
|
||||
@@ -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] : [
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user