[web] Introduce selection context

- For handling collection / people split
- This can be done better (much!), need to revisit
This commit is contained in:
Manav Rathi
2024-09-24 18:36:09 +05:30
parent 06bd58edce
commit bad6fd9fae
8 changed files with 155 additions and 7 deletions

View File

@@ -1,5 +1,6 @@
import log from "@/base/log";
import { FileType } from "@/media/file-type";
import type { GalleryBarMode } from "@/new/photos/components/Gallery/BarImpl";
import DownloadManager from "@/new/photos/services/download";
import type { LivePhotoSourceURL, SourceURLs } from "@/new/photos/types/file";
import { EnteFile } from "@/new/photos/types/file";
@@ -48,6 +49,7 @@ interface Props {
| PHOTOS_PAGES.GALLERY
| PHOTOS_PAGES.DEDUPLICATE
| PHOTOS_PAGES.SHARED_ALBUMS;
mode?: GalleryBarMode;
files: EnteFile[];
duplicates?: Duplicate[];
syncWithRemote: () => Promise<void>;
@@ -58,7 +60,10 @@ interface Props {
selected: SelectedState;
tempDeletedFileIds?: Set<number>;
setTempDeletedFileIds?: (value: Set<number>) => void;
/** This will be set if mode is not "people". */
activeCollectionID: number;
/** This will be set if mode is "people". */
activePersonID?: string | undefined;
enableDownload?: boolean;
fileToCollectionsMap: Map<number, number[]>;
collectionNameMap: Map<number, string>;
@@ -72,6 +77,7 @@ interface Props {
const PhotoFrame = ({
page,
duplicates,
mode,
files,
syncWithRemote,
favItemIds,
@@ -80,6 +86,7 @@ const PhotoFrame = ({
tempDeletedFileIds,
setTempDeletedFileIds,
activeCollectionID,
activePersonID,
enableDownload,
fileToCollectionsMap,
collectionNameMap,
@@ -232,7 +239,9 @@ const PhotoFrame = ({
const handleSelect = handleSelectCreator(
setSelected,
mode,
activeCollectionID,
activePersonID,
setRangeStart,
);
@@ -287,8 +296,13 @@ const PhotoFrame = ({
index,
)}
selected={
selected.collectionID === activeCollectionID &&
selected[item.id]
(!mode
? selected.collectionID === activeCollectionID
: mode == selected.context?.mode &&
(selected.context.mode == "people"
? selected.context.personID == activePersonID
: selected.context.collectionID ==
activeCollectionID)) && selected[item.id]
}
selectOnClick={selected.count > 0}
onHover={onHoverOver(index)}
@@ -539,8 +553,10 @@ const PhotoFrame = ({
width={width}
height={height}
getThumbnail={getThumbnail}
mode={mode}
displayFiles={displayFiles}
activeCollectionID={activeCollectionID}
activePersonID={activePersonID}
showAppDownloadBanner={showAppDownloadBanner}
/>
)

View File

@@ -1,3 +1,4 @@
import type { GalleryBarMode } from "@/new/photos/components/Gallery/BarImpl";
import { EnteFile } from "@/new/photos/types/file";
import { formattedByteSize } from "@/new/photos/utils/units";
import { FlexWrapper } from "@ente/shared/components/Container";
@@ -189,6 +190,7 @@ const NothingContainer = styled(ListItemContainer)`
interface Props {
height: number;
width: number;
mode?: GalleryBarMode;
displayFiles: EnteFile[];
showAppDownloadBanner: boolean;
getThumbnail: (
@@ -197,6 +199,7 @@ interface Props {
isScrolling?: boolean,
) => JSX.Element;
activeCollectionID: number;
activePersonID?: string;
}
interface ItemData {
@@ -253,10 +256,12 @@ const PhotoListRow = React.memo(
export function PhotoList({
height,
width,
mode,
displayFiles,
showAppDownloadBanner,
getThumbnail,
activeCollectionID,
activePersonID,
}: Props) {
const galleryContext = useContext(GalleryContext);
const publicCollectionGalleryContext = useContext(
@@ -768,7 +773,9 @@ export function PhotoList({
const handleSelect = handleSelectCreator(
galleryContext.setSelectedFiles,
mode,
activeCollectionID,
activePersonID,
);
const onChangeSelectAllCheckBox = (date: string) => {

View File

@@ -49,6 +49,7 @@ export default function Deduplicate() {
count: 0,
collectionID: 0,
ownCount: 0,
context: undefined,
});
const closeDeduplication = function () {
Router.push(PAGES.GALLERY);
@@ -97,6 +98,7 @@ export default function Deduplicate() {
count: count,
ownCount: count,
collectionID: ALL_SECTION,
context: undefined,
};
for (const fileID of toSelectFileIDs) {
selectedFiles[fileID] = true;
@@ -157,7 +159,12 @@ export default function Deduplicate() {
};
const clearSelection = function () {
setSelected({ count: 0, collectionID: 0, ownCount: 0 });
setSelected({
count: 0,
collectionID: 0,
ownCount: 0,
context: undefined,
});
};
if (!duplicates) {

View File

@@ -202,6 +202,7 @@ export default function Gallery() {
ownCount: 0,
count: 0,
collectionID: 0,
context: { mode: "albums", collectionID: ALL_SECTION },
});
const [planModalView, setPlanModalView] = useState(false);
const [blockingLoad, setBlockingLoad] = useState(false);
@@ -661,6 +662,13 @@ export default function Gallery() {
ownCount: 0,
count: 0,
collectionID: activeCollectionID,
context:
barMode == "people" && activePerson
? { mode: "people" as const, personID: activePerson.id }
: {
mode: "albums" as const,
collectionID: ensure(activeCollectionID),
},
};
filteredData.forEach((item) => {
@@ -677,7 +685,12 @@ export default function Gallery() {
if (!selected?.count) {
return;
}
setSelected({ ownCount: 0, count: 0, collectionID: 0 });
setSelected({
ownCount: 0,
count: 0,
collectionID: 0,
context: undefined,
});
};
const keyboardShortcutHandlerRef = useRef({
@@ -1207,6 +1220,7 @@ export default function Gallery() {
) : (
<PhotoFrame
page={PAGES.GALLERY}
mode={barMode}
files={filteredData}
syncWithRemote={syncWithRemote}
favItemIds={favItemIds}
@@ -1216,6 +1230,7 @@ export default function Gallery() {
setTempDeletedFileIds={setTempDeletedFileIds}
setIsPhotoSwipeOpen={setIsPhotoSwipeOpen}
activeCollectionID={activeCollectionID}
activePersonID={activePerson?.id}
enableDownload={true}
fileToCollectionsMap={fileToCollectionsMap}
collectionNameMap={collectionNameMap}

View File

@@ -109,6 +109,7 @@ export default function PublicCollectionGallery() {
ownCount: 0,
count: 0,
collectionID: 0,
context: undefined,
});
const {
@@ -472,7 +473,12 @@ export default function PublicCollectionGallery() {
if (!selected?.count) {
return;
}
setSelected({ ownCount: 0, count: 0, collectionID: 0 });
setSelected({
ownCount: 0,
count: 0,
collectionID: 0,
context: undefined,
});
};
const downloadFilesHelper = async () => {

View File

@@ -1,4 +1,5 @@
import type { Collection } from "@/media/collection";
import { type SelectionContext } from "@/new/photos/components/Gallery";
import { EnteFile } from "@/new/photos/types/file";
import type { User } from "@ente/shared/user/types";
import { CollectionSelectorAttributes } from "components/Collections/CollectionSelector";
@@ -10,6 +11,12 @@ export type SelectedState = {
ownCount: number;
count: number;
collectionID: number;
/**
* The context in which the selection was made. Only set by newer code if
* there is an active selection (older code continues to rely on the
* {@link collectionID} logic).
*/
context: SelectionContext | undefined;
};
export type SetSelectedState = React.Dispatch<
React.SetStateAction<SelectedState>

View File

@@ -1,7 +1,10 @@
import log from "@/base/log";
import { FileType } from "@/media/file-type";
import type { SelectionContext } from "@/new/photos/components/Gallery";
import type { GalleryBarMode } from "@/new/photos/components/Gallery/BarImpl";
import type { LivePhotoSourceURL, SourceURLs } from "@/new/photos/types/file";
import { EnteFile } from "@/new/photos/types/file";
import { ensure } from "@/utils/ensure";
import { SetSelectedState } from "types/gallery";
export async function playVideo(livePhotoVideo, livePhotoImage) {
@@ -102,7 +105,9 @@ export async function updateFileSrcProps(
export const handleSelectCreator =
(
setSelected: SetSelectedState,
mode: GalleryBarMode | undefined,
activeCollectionID: number,
activePersonID: string | undefined,
setRangeStart?,
) =>
(id: number, isOwnFile: boolean, index?: number) =>
@@ -115,10 +120,84 @@ export const handleSelectCreator =
}
}
setSelected((selected) => {
if (selected.collectionID !== activeCollectionID) {
selected = { ownCount: 0, count: 0, collectionID: 0 };
if (!mode) {
// Retain older behavior for non-gallery call sites.
if (selected.collectionID !== activeCollectionID) {
selected = {
ownCount: 0,
count: 0,
collectionID: 0,
context: undefined,
};
}
} else if (!selected.context) {
// Gallery will specify a mode, but a fresh selection starts off
// without a context, so fill it in with the current context.
selected = {
...selected,
context:
mode == "people"
? { mode, personID: ensure(activePersonID) }
: {
mode,
collectionID: ensure(activeCollectionID),
},
};
} else {
// Both mode and context are defined.
if (selected.context.mode != mode) {
// Clear selection if mode has changed.
selected = {
ownCount: 0,
count: 0,
collectionID: 0,
context:
mode == "people"
? { mode, personID: ensure(activePersonID) }
: {
mode,
collectionID: ensure(activeCollectionID),
},
};
} else {
if (selected.context?.mode == "people") {
if (selected.context.personID != activePersonID) {
// Clear selection if person has changed.
selected = {
ownCount: 0,
count: 0,
collectionID: 0,
context: {
mode: selected.context?.mode,
personID: ensure(activePersonID),
},
};
}
} else {
if (
selected.context.collectionID != activeCollectionID
) {
// Clear selection if collection has changed.
selected = {
ownCount: 0,
count: 0,
collectionID: 0,
context: {
mode: selected.context?.mode,
collectionID: ensure(activeCollectionID),
},
};
}
}
}
}
const newContext: SelectionContext | undefined = !mode
? undefined
: mode == "people"
? { mode, personID: ensure(activePersonID) }
: { mode, collectionID: ensure(activeCollectionID) };
const handleCounterChange = (count: number) => {
if (selected[id] === checked) {
return count;
@@ -146,6 +225,7 @@ export const handleSelectCreator =
...selected,
[id]: checked,
collectionID: activeCollectionID,
context: newContext,
...handleAllCounterChange(),
};
});

View File

@@ -19,6 +19,16 @@ import React from "react";
import { SpaceBetweenFlex } from "../mui-custom";
import { GalleryItemsHeaderAdapter, GalleryItemsSummary } from "./ListHeader";
/**
* The context in which a selection was made.
*
* This allows us to reset the selection if user moves to a different context
* and starts a new selection.
* */
export type SelectionContext =
| { mode: "albums" | "hidden-albums"; collectionID: number }
| { mode: "people"; personID: string };
interface SearchResultsHeaderProps {
selectedOption: SearchOption;
}