Merge branch 'gallery-refactor' into hidden-support

This commit is contained in:
Abhinav 2023-05-16 10:35:32 +05:30
commit 477848fb16
23 changed files with 340 additions and 357 deletions

View file

@ -1,15 +0,0 @@
const path = require('path');
const buildEslintCommand = (filenames) =>
`yarn lint --fix --file ${filenames
.map((f) => path.relative(process.cwd(), f))
.join(' --file ')} --`;
const buildPrettierCommand = (filenames) =>
`yarn prettier --write --ignore-unknown ${filenames.join(' ')}`;
module.exports = {
'apps/**/*.{js,jsx,ts,tsx}': [buildEslintCommand, buildPrettierCommand],
'packages/**/*.{js,jsx,ts,tsx}': [buildEslintCommand, buildPrettierCommand],
'**/*.{json,css,scss,md,html,yml,yaml}': [buildPrettierCommand],
};

View file

@ -84,7 +84,7 @@
"PREVIOUS": "Previous (←)",
"NEXT": "Next (→)",
"TITLE_PHOTOS": "ente Photos",
"TITLE_ALBUMS": "ente Albums",
"TITLE_ALBUMS": "ente Photos",
"TITLE_AUTH": "ente Auth",
"UPLOAD_FIRST_PHOTO": "Upload your first photo",
"IMPORT_YOUR_FOLDERS": "Import your folders",

View file

@ -83,7 +83,7 @@
"PREVIOUS": "Anterior (←)",
"NEXT": "Siguiente (→)",
"TITLE_PHOTOS": "ente Fotos",
"TITLE_ALBUMS": "ente Álbumes",
"TITLE_ALBUMS": "ente Fotos",
"TITLE_AUTH": "ente Auth",
"UPLOAD_FIRST_PHOTO": "Carga tu primer archivo",
"IMPORT_YOUR_FOLDERS": "Importar tus carpetas",

View file

@ -83,7 +83,7 @@
"PREVIOUS": "Précédent (←)",
"NEXT": "Suivant (→)",
"TITLE_PHOTOS": "ente Photos",
"TITLE_ALBUMS": "ente Albums",
"TITLE_ALBUMS": "ente Photos",
"TITLE_AUTH": "ente Auth",
"UPLOAD_FIRST_PHOTO": "Chargez votre 1ere photo",
"IMPORT_YOUR_FOLDERS": "Importez vos dossiers",

View file

@ -83,7 +83,7 @@
"PREVIOUS": "Precedente (←)",
"NEXT": "Successivo (→)",
"TITLE_PHOTOS": "ente Photos",
"TITLE_ALBUMS": "ente Album",
"TITLE_ALBUMS": "ente Photos",
"TITLE_AUTH": "ente Auth",
"UPLOAD_FIRST_PHOTO": "Carica la tua prima foto",
"IMPORT_YOUR_FOLDERS": "Importa una cartella",

View file

@ -83,7 +83,7 @@
"PREVIOUS": "Vorige (←)",
"NEXT": "Volgende (→)",
"TITLE_PHOTOS": "ente Photos",
"TITLE_ALBUMS": "ente Albums",
"TITLE_ALBUMS": "ente Photos",
"TITLE_AUTH": "ente Auth",
"UPLOAD_FIRST_PHOTO": "Je eerste foto uploaden",
"IMPORT_YOUR_FOLDERS": "Importeer uw mappen",

View file

@ -83,7 +83,7 @@
"PREVIOUS": "上一个 (←)",
"NEXT": "下一个 (→)",
"TITLE_PHOTOS": "ente 照片",
"TITLE_ALBUMS": "ente 相册",
"TITLE_ALBUMS": "ente 照片",
"TITLE_AUTH": "ente 验证器",
"UPLOAD_FIRST_PHOTO": "上传您的第一张照片",
"IMPORT_YOUR_FOLDERS": "导入您的文件夹",

View file

@ -1,18 +1,12 @@
import { GalleryContext } from 'pages/gallery';
import PreviewCard from './pages/gallery/PreviewCard';
import React, { useContext, useEffect, useRef, useState } from 'react';
import { useContext, useEffect, useRef, useState } from 'react';
import { EnteFile } from 'types/file';
import { styled } from '@mui/material';
import DownloadManager from 'services/downloadManager';
import AutoSizer from 'react-virtualized-auto-sizer';
import PhotoViewer from 'components/PhotoViewer';
import {
ALL_SECTION,
ARCHIVE_SECTION,
CollectionType,
TRASH_SECTION,
} from 'constants/collection';
import { isSharedFile } from 'utils/file';
import { TRASH_SECTION } from 'constants/collection';
import { updateFileMsrcProps, updateFileSrcProps } from 'utils/photoFrame';
import { PhotoList } from './PhotoList';
import { MergedSourceURL, SelectedState } from 'types/gallery';
@ -20,19 +14,11 @@ import PublicCollectionDownloadManager from 'services/publicCollectionDownloadMa
import { PublicCollectionGalleryContext } from 'utils/publicCollectionGallery';
import { useRouter } from 'next/router';
import { AppContext } from 'pages/_app';
import { DeduplicateContext } from 'pages/deduplicate';
import { IsArchived } from 'utils/magicMetadata';
import { isSameDayAnyYear, isInsideBox } from 'utils/search';
import { Search } from 'types/search';
import { logError } from 'utils/sentry';
import { User } from 'types/user';
import { getData, LS_KEYS } from 'utils/storage/localStorage';
import { useMemo } from 'react';
import { Collection } from 'types/collection';
import { addLogLine } from 'utils/logging';
import PhotoSwipe from 'photoswipe';
import { isHiddenCollection } from 'utils/collection';
import { t } from 'i18next';
const Container = styled('div')`
display: block;
@ -51,41 +37,36 @@ const PHOTOSWIPE_HASH_SUFFIX = '&opened';
interface Props {
files: EnteFile[];
collections?: Collection[];
syncWithRemote: () => Promise<void>;
favItemIds?: Set<number>;
archivedCollections?: Set<number>;
setSelected: (
selected: SelectedState | ((selected: SelectedState) => SelectedState)
) => void;
selected: SelectedState;
isInSearchMode?: boolean;
search?: Search;
deletedFileIds?: Set<number>;
setDeletedFileIds?: (value: Set<number>) => void;
activeCollection: number;
isIncomingSharedCollection?: boolean;
enableDownload?: boolean;
isDeduplicating?: boolean;
resetSearch?: () => void;
fileToCollectionsMap: Map<number, number[]>;
collectionNameMap: Map<number, string>;
showAppDownloadBanner?: boolean;
}
const PhotoFrame = ({
files,
collections,
syncWithRemote,
favItemIds,
archivedCollections,
setSelected,
selected,
isInSearchMode,
search,
deletedFileIds,
setDeletedFileIds,
activeCollection,
isIncomingSharedCollection,
enableDownload,
isDeduplicating,
fileToCollectionsMap,
collectionNameMap,
showAppDownloadBanner,
}: Props) => {
const [user, setUser] = useState<User>(null);
const [open, setOpen] = useState(false);
@ -93,7 +74,6 @@ const PhotoFrame = ({
const [fetching, setFetching] = useState<{ [k: number]: boolean }>({});
const galleryContext = useContext(GalleryContext);
const appContext = useContext(AppContext);
const deduplicateContext = useContext(DeduplicateContext);
const publicCollectionGalleryContext = useContext(
PublicCollectionGalleryContext
);
@ -106,7 +86,8 @@ const PhotoFrame = ({
const updateInProgress = useRef(false);
const updateRequired = useRef(false);
const [filteredData, setFilteredData] = useState<EnteFile[]>([]);
const [displayFiles, setDisplayFiles] = useState<EnteFile[]>([]);
useEffect(() => {
const user: User = getData(LS_KEYS.USER);
setUser(user);
@ -119,142 +100,33 @@ const PhotoFrame = ({
return;
}
updateInProgress.current = true;
const idSet = new Set();
const user: User = getData(LS_KEYS.USER);
const filteredData = files
.filter((item) => {
// only show single copy of a file
if (idSet.has(item.id)) {
return false;
const displayFiles = files.map((item) => {
const filteredItem = {
...item,
w: window.innerWidth,
h: window.innerHeight,
title: item.pubMagicMetadata?.data.caption,
};
try {
if (galleryContext.thumbs.has(item.id)) {
updateFileMsrcProps(
filteredItem,
galleryContext.thumbs.get(item.id)
);
}
if (
activeCollection === TRASH_SECTION ||
isDeduplicating ||
activeCollection === HIDDEN_SECTION ||
isIncomingSharedCollection
) {
idSet.add(item.id);
return true;
if (galleryContext.files.has(item.id)) {
updateFileSrcProps(
filteredItem,
galleryContext.files.get(item.id)
);
}
// SEARCH MODE
if (isInSearchMode) {
if (
search?.date &&
!isSameDayAnyYear(search.date)(
new Date(item.metadata.creationTime / 1000)
)
) {
return false;
}
if (
search?.location &&
!isInsideBox(
{
latitude: item.metadata.latitude,
longitude: item.metadata.longitude,
},
search.location
)
) {
return false;
}
if (
search?.person &&
search.person.files.indexOf(item.id) === -1
) {
return false;
}
if (
search?.thing &&
search.thing.files.indexOf(item.id) === -1
) {
return false;
}
if (
search?.text &&
search.text.files.indexOf(item.id) === -1
) {
return false;
}
if (
search?.files &&
search.files.indexOf(item.id) === -1
) {
return false;
}
idSet.add(item.id);
return true;
}
// shared files can only be seen in their respective shared collection
if (isSharedFile(user, item)) {
if (activeCollection === item.collectionID) {
idSet.add(item.id);
return true;
} else {
return false;
}
}
// Archived files/collection files can only be seen in archive section or their respective collection
if (
IsArchived(item) ||
archivedCollections.has(item.collectionID)
) {
if (
activeCollection === ARCHIVE_SECTION ||
activeCollection === item.collectionID
) {
idSet.add(item.id);
return true;
} else {
return false;
}
}
// ALL SECTION - show all files
if (activeCollection === ALL_SECTION) {
idSet.add(item.id);
return true;
}
// COLLECTION SECTION - show files in the active collection
if (activeCollection === item.collectionID) {
idSet.add(item.id);
return true;
} else {
return false;
}
})
.map((item) => {
const filteredItem = {
...item,
w: window.innerWidth,
h: window.innerHeight,
title: item.pubMagicMetadata?.data.caption,
};
try {
if (galleryContext.thumbs.has(item.id)) {
updateFileMsrcProps(
filteredItem,
galleryContext.thumbs.get(item.id)
);
}
if (galleryContext.files.has(item.id)) {
updateFileSrcProps(
filteredItem,
galleryContext.files.get(item.id)
);
}
} catch (e) {
logError(e, 'PhotoFrame url prefill failed');
}
return filteredItem;
});
setFilteredData(filteredData);
} catch (e) {
logError(e, 'PhotoFrame url prefill failed');
}
return filteredItem;
});
setDisplayFiles(displayFiles);
updateInProgress.current = false;
if (updateRequired.current) {
updateRequired.current = false;
@ -264,54 +136,11 @@ const PhotoFrame = ({
}
};
main();
}, [
files,
deletedFileIds,
search?.date,
search?.files,
search?.location,
search?.person,
search?.thing,
search?.text,
activeCollection,
]);
}, [files]);
useEffect(() => {
setFetching({});
}, [filteredData]);
const fileToCollectionsMap = useMemo(() => {
const fileToCollectionsMap = new Map<number, number[]>();
files.forEach((file) => {
if (!fileToCollectionsMap.get(file.id)) {
fileToCollectionsMap.set(file.id, []);
}
fileToCollectionsMap.get(file.id).push(file.collectionID);
});
return fileToCollectionsMap;
}, [files]);
const collectionNameMap = useMemo(() => {
if (collections) {
return new Map<number, string>(
collections.map((collection) => {
if (isHiddenCollection(collection)) {
return [collection.id, t('HIDDEN')];
} else if (collection.type === CollectionType.favorites) {
return [collection.id, t('FAVORITES')];
} else if (
collection.type === CollectionType.uncategorized
) {
return [collection.id, t('UNCATEGORIZED')];
} else {
return [collection.id, collection.name];
}
})
);
} else {
return new Map();
}
}, [collections]);
}, [displayFiles]);
useEffect(() => {
const currentURL = new URL(window.location.href);
@ -368,7 +197,7 @@ const PhotoFrame = ({
}, [selected]);
const updateURL = (index: number) => (id: number, url: string) => {
const file = filteredData[index];
const file = displayFiles[index];
// this is to prevent outdated updateURL call from updating the wrong file
if (file.id !== id) {
addLogLine(
@ -394,7 +223,7 @@ const PhotoFrame = ({
id: number,
mergedSrcURL: MergedSourceURL
) => {
const file = filteredData[index];
const file = displayFiles[index];
// this is to prevent outdate updateSrcURL call from updating the wrong file
if (file.id !== id) {
addLogLine(
@ -488,7 +317,7 @@ const PhotoFrame = ({
(index - i) * direction >= 0;
i += direction
) {
checked = checked && !!selected[filteredData[i].id];
checked = checked && !!selected[displayFiles[i].id];
}
for (
let i = rangeStart;
@ -496,13 +325,13 @@ const PhotoFrame = ({
i += direction
) {
handleSelect(
filteredData[i].id,
filteredData[i].ownerID === user?.id
displayFiles[i].id,
displayFiles[i].ownerID === user?.id
)(!checked);
}
handleSelect(
filteredData[index].id,
filteredData[index].ownerID === user?.id,
displayFiles[index].id,
displayFiles[index].ownerID === user?.id,
index
)(!checked);
}
@ -670,19 +499,15 @@ const PhotoFrame = ({
width={width}
height={height}
getThumbnail={getThumbnail}
filteredData={filteredData}
displayFiles={displayFiles}
activeCollection={activeCollection}
showAppDownloadBanner={
files.length < 30 &&
!isInSearchMode &&
!deduplicateContext.isOnDeduplicatePage
}
showAppDownloadBanner={showAppDownloadBanner}
/>
)}
</AutoSizer>
<PhotoViewer
isOpen={open}
items={filteredData}
items={displayFiles}
currentIndex={currentIndex}
onClose={handleClose}
gettingData={getSlideData}

View file

@ -163,7 +163,7 @@ const NothingContainer = styled(ListItemContainer)`
interface Props {
height: number;
width: number;
filteredData: EnteFile[];
displayFiles: EnteFile[];
showAppDownloadBanner: boolean;
getThumbnail: (
file: EnteFile,
@ -176,7 +176,7 @@ interface Props {
export function PhotoList({
height,
width,
filteredData,
displayFiles,
showAppDownloadBanner,
getThumbnail,
activeCollection,
@ -266,7 +266,7 @@ export function PhotoList({
}, [
width,
height,
filteredData,
displayFiles,
deduplicateContext.isOnDeduplicatePage,
deduplicateContext.fileSizeMap,
]);
@ -340,19 +340,19 @@ export function PhotoList({
const groupByFileSize = (timeStampList: TimeStampListItem[]) => {
let index = 0;
while (index < filteredData.length) {
const file = filteredData[index];
while (index < displayFiles.length) {
const file = displayFiles[index];
const currentFileSize = deduplicateContext.fileSizeMap.get(file.id);
const currentCreationTime = file.metadata.creationTime;
let lastFileIndex = index;
while (lastFileIndex < filteredData.length) {
while (lastFileIndex < displayFiles.length) {
if (
deduplicateContext.fileSizeMap.get(
filteredData[lastFileIndex].id
displayFiles[lastFileIndex].id
) !== currentFileSize ||
(deduplicateContext.clubSameTimeFilesOnly &&
filteredData[lastFileIndex].metadata.creationTime !==
displayFiles[lastFileIndex].metadata.creationTime !==
currentCreationTime)
) {
break;
@ -370,7 +370,7 @@ export function PhotoList({
const tileSize = Math.min(columns, lastFileIndex - index + 1);
timeStampList.push({
itemType: ITEM_TYPE.FILE,
items: filteredData.slice(index, index + tileSize),
items: displayFiles.slice(index, index + tileSize),
itemStartIndex: index,
});
index += tileSize;
@ -381,7 +381,7 @@ export function PhotoList({
const groupByTime = (t, timeStampList: TimeStampListItem[]) => {
let listItemIndex = 0;
let currentDate;
filteredData.forEach((item, index) => {
displayFiles.forEach((item, index) => {
if (
!currentDate ||
!isSameDay(

View file

@ -43,7 +43,7 @@ export const FileInfoSidebar = styled((props: DialogProps) => (
});
interface Iprops {
shouldDisableEdits: boolean;
shouldDisableEdits?: boolean;
showInfo: boolean;
handleCloseInfo: () => void;
file: EnteFile;

View file

@ -133,7 +133,7 @@ function PhotoViewer(props: Iprops) {
switch (event.key) {
case 'i':
case 'I':
setShowInfo(true);
!props.isIncomingSharedCollection && setShowInfo(true);
break;
case 'Backspace':
case 'Delete':
@ -663,18 +663,19 @@ function PhotoViewer(props: Iprops) {
</div>
</div>
</div>
<FileInfo
isTrashCollection={props.isTrashCollection}
shouldDisableEdits={props.isIncomingSharedCollection}
showInfo={showInfo}
handleCloseInfo={handleCloseInfo}
file={photoSwipe?.currItem as EnteFile}
exif={exif?.value}
scheduleUpdate={scheduleUpdate}
refreshPhotoswipe={refreshPhotoswipe}
fileToCollectionsMap={props.fileToCollectionsMap}
collectionNameMap={props.collectionNameMap}
/>
{!props.isIncomingSharedCollection && (
<FileInfo
isTrashCollection={props.isTrashCollection}
showInfo={showInfo}
handleCloseInfo={handleCloseInfo}
file={photoSwipe?.currItem as EnteFile}
exif={exif?.value}
scheduleUpdate={scheduleUpdate}
refreshPhotoswipe={refreshPhotoswipe}
fileToCollectionsMap={props.fileToCollectionsMap}
collectionNameMap={props.collectionNameMap}
/>
)}
</>
);
}

View file

@ -42,7 +42,7 @@ export const getAppNameAndTitle = () => {
const albumsURL = new URL(getAlbumsURL());
const authURL = new URL(getAuthURL());
if (currentURL.origin === albumsURL.origin) {
return { name: APPS.ALBUMS, title: 'ente Albums' };
return { name: APPS.ALBUMS, title: 'ente Photos' };
} else if (currentURL.origin === authURL.origin) {
return { name: APPS.AUTH, title: 'ente Auth' };
} else {

View file

@ -4,8 +4,7 @@ export enum REDIRECTS {
}
export const getRedirectURL = (redirect: REDIRECTS) => {
// open current app with query param of redirect = roadmap
const url = new URL(window.location.href);
const url = new URL('https://web.ente.io');
url.searchParams.set('redirect', redirect);
return url.href;
};

View file

@ -0,0 +1,38 @@
import { useEffect, useRef, useState } from 'react';
export default function useMemoSingleThreaded<T>(
fn: () => T | Promise<T>,
deps: any[]
): T {
const [result, setResult] = useState<T>(null);
const updateInProgress = useRef(false);
const updateRequired = useRef(false);
useEffect(() => {
const main = async () => {
if (updateInProgress.current) {
updateRequired.current = true;
return;
}
updateInProgress.current = true;
const result = fn();
if (isPromise(result)) {
const resultValue = await result;
setResult(resultValue);
} else {
setResult(result);
}
updateInProgress.current = false;
if (updateRequired.current) {
updateRequired.current = false;
setTimeout(main, 0);
}
};
main();
}, deps);
return result;
}
function isPromise<T>(obj: T | Promise<T>): obj is Promise<T> {
return obj && typeof (obj as any).then === 'function';
}

View file

@ -3,7 +3,7 @@ import { t } from 'i18next';
import PhotoFrame from 'components/PhotoFrame';
import { ALL_SECTION } from 'constants/collection';
import { AppContext } from 'pages/_app';
import React, { createContext, useContext, useEffect, useState } from 'react';
import { createContext, useContext, useEffect, useState } from 'react';
import {
getDuplicateFiles,
clubDuplicatesByTime,
@ -13,7 +13,7 @@ import { EnteFile } from 'types/file';
import { SelectedState } from 'types/gallery';
import { ServerErrorCodes } from 'utils/error';
import { getSelectedFiles } from 'utils/file';
import { constructFileToCollectionMap, getSelectedFiles } from 'utils/file';
import {
DeduplicateContextType,
DefaultDeduplicateContext,
@ -27,8 +27,8 @@ import { styled } from '@mui/material';
import { syncCollections } from 'services/collectionService';
import EnteSpinner from 'components/EnteSpinner';
import VerticallyCentered from 'components/Container';
import { Collection } from 'types/collection';
import Typography from '@mui/material/Typography';
import useMemoSingleThreaded from 'hooks/useMemoSingleThreaded';
export const DeduplicateContext = createContext<DeduplicateContextType>(
DefaultDeduplicateContext
@ -47,7 +47,6 @@ export default function Deduplicate() {
setRedirectURL,
} = useContext(AppContext);
const [duplicateFiles, setDuplicateFiles] = useState<EnteFile[]>(null);
const [collections, setCollection] = useState<Collection[]>([]);
const [clubSameTimeFilesOnly, setClubSameTimeFilesOnly] = useState(false);
const [fileSizeMap, setFileSizeMap] = useState(new Map<number, number>());
const [collectionNameMap, setCollectionNameMap] = useState(
@ -75,10 +74,13 @@ export default function Deduplicate() {
syncWithRemote();
}, [clubSameTimeFilesOnly]);
const fileToCollectionsMap = useMemoSingleThreaded(() => {
return constructFileToCollectionMap(duplicateFiles);
}, [duplicateFiles]);
const syncWithRemote = async () => {
startLoading();
const collections = await syncCollections();
setCollection(collections);
const collectionNameMap = new Map<number, string>();
for (const collection of collections) {
collectionNameMap.set(collection.id, collection.name);
@ -189,12 +191,12 @@ export default function Deduplicate() {
) : (
<PhotoFrame
files={duplicateFiles}
collections={collections}
syncWithRemote={syncWithRemote}
setSelected={setSelected}
selected={selected}
activeCollection={ALL_SECTION}
isDeduplicating
fileToCollectionsMap={fileToCollectionsMap}
collectionNameMap={collectionNameMap}
/>
)}
<DeduplicateOptions

View file

@ -39,15 +39,17 @@ import {
setIsFirstLogin,
setJustSignedUp,
} from 'utils/storage';
import { isTokenValid, logoutUser, validateKey } from 'services/userService';
import { isTokenValid, validateKey } from 'services/userService';
import { useDropzone } from 'react-dropzone';
import EnteSpinner from 'components/EnteSpinner';
import { LoadingOverlay } from 'components/LoadingOverlay';
import PhotoFrame from 'components/PhotoFrame';
import {
changeFilesVisibility,
constructFileToCollectionMap,
downloadFiles,
getSelectedFiles,
isSharedFile,
mergeMetadata,
sortFiles,
} from 'utils/file';
@ -78,8 +80,8 @@ import {
getSelectedCollection,
getArchivedCollections,
hasNonSystemCollections,
getSearchableCollections,
splitNormalAndHiddenCollections,
constructCollectionNameMap,
} from 'utils/collection';
import { logError } from 'utils/sentry';
import { getLocalTrashedFiles, syncTrash } from 'services/trashService';
@ -113,6 +115,10 @@ import { getToken } from 'utils/common/key';
import ExportModal from 'components/ExportModal';
import GalleryEmptyState from 'components/GalleryEmptyState';
import AuthenticateUserModal from 'components/AuthenticateUserModal';
import useMemoSingleThreaded from 'hooks/useMemoSingleThreaded';
import { IsArchived } from 'utils/magicMetadata';
import { isSameDayAnyYear, isInsideBox } from 'utils/search';
import { getSessionExpiredMessage } from 'utils/ui';
export const DeadCenter = styled('div')`
flex: 1;
@ -242,38 +248,6 @@ export default function Gallery() {
const closeAuthenticateUserModal = () =>
setAuthenticateUserModalView(false);
const showSessionExpiredMessage = () =>
setDialogMessage({
title: t('SESSION_EXPIRED'),
content: t('SESSION_EXPIRED_MESSAGE'),
nonClosable: true,
proceed: {
text: t('LOGIN'),
action: logoutUser,
variant: 'accent',
},
});
const displayFiles = useMemo(() => {
if (!files || !hiddenFiles || !trashedFiles) {
return [];
}
if (activeCollection === HIDDEN_SECTION) {
return hiddenFiles;
} else if (activeCollection === TRASH_SECTION) {
const markedForDeletionFiles = files.filter((file) =>
deletedFileIds.has(file.id)
);
return [...markedForDeletionFiles, ...trashedFiles];
} else {
const nonMarkedForDeletion = files.filter(
(file) => !deletedFileIds.has(file.id)
);
return nonMarkedForDeletion;
}
}, [activeCollection, files, hiddenFiles, trashedFiles]);
useEffect(() => {
appContext.showNavBar(true);
const key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
@ -393,6 +367,146 @@ export default function Gallery() {
}
}, [isInSearchMode, searchResultSummary]);
const displayFiles = useMemoSingleThreaded((): EnteFile[] => {
if (!files || !hiddenFiles || !trashedFiles) {
return [];
}
let displayFiles: EnteFile[] = [];
if (activeCollection === HIDDEN_SECTION) {
displayFiles = hiddenFiles;
} else if (activeCollection === TRASH_SECTION) {
const markedForDeletionFiles = files.filter((file) =>
deletedFileIds.has(file.id)
);
displayFiles = [...markedForDeletionFiles, ...trashedFiles];
} else {
displayFiles = files.filter((item) => {
// SEARCH MODE
if (isInSearchMode) {
if (
search?.date &&
!isSameDayAnyYear(search.date)(
new Date(item.metadata.creationTime / 1000)
)
) {
return false;
}
if (
search?.location &&
!isInsideBox(
{
latitude: item.metadata.latitude,
longitude: item.metadata.longitude,
},
search.location
)
) {
return false;
}
if (
search?.person &&
search.person.files.indexOf(item.id) === -1
) {
return false;
}
if (
search?.thing &&
search.thing.files.indexOf(item.id) === -1
) {
return false;
}
if (
search?.text &&
search.text.files.indexOf(item.id) === -1
) {
return false;
}
if (search?.files && search.files.indexOf(item.id) === -1) {
return false;
}
idSet.add(item.id);
return true;
}
// shared files can only be seen in their respective shared collection
if (isSharedFile(user, item)) {
if (activeCollection === item.collectionID) {
idSet.add(item.id);
return true;
} else {
return false;
}
}
// Archived files/collection files can only be seen in archive section or their respective collection
if (
IsArchived(item) ||
archivedCollections.has(item.collectionID)
) {
if (
activeCollection === ARCHIVE_SECTION ||
activeCollection === item.collectionID
) {
idSet.add(item.id);
return true;
} else {
return false;
}
}
// ALL SECTION - show all files
if (activeCollection === ALL_SECTION) {
idSet.add(item.id);
return true;
}
// COLLECTION SECTION - show files in the active collection
if (activeCollection === item.collectionID) {
idSet.add(item.id);
return true;
} else {
return false;
}
});
}
const idSet = new Set();
const user: User = getData(LS_KEYS.USER);
const uniqueFiles = displayFiles.filter((item) => {
if (idSet.has(item.id)) {
return false;
}
idSet.add(item.id);
return true;
});
return uniqueFiles;
}, [
files,
deletedFileIds,
search?.date,
search?.files,
search?.location,
search?.person,
search?.thing,
search?.text,
activeCollection,
]);
const fileToCollectionsMap = useMemoSingleThreaded(() => {
return constructFileToCollectionMap(files);
}, [files]);
const collectionNameMap = useMemo(() => {
return constructCollectionNameMap(collections);
}, [collections]);
const showSessionExpiredMessage = () => {
setDialogMessage(getSessionExpiredMessage());
};
const syncWithRemote = async (force = false, silent = false) => {
if (syncInProgress.current && !force) {
resync.current = { force, silent };
@ -690,8 +804,6 @@ export default function Gallery() {
setExportModalView(false);
};
console.log('rendering gallery', displayFiles);
return (
<GalleryContext.Provider
value={{
@ -752,7 +864,7 @@ export default function Gallery() {
setIsInSearchMode={setIsInSearchMode}
openUploader={openUploader}
isInSearchMode={isInSearchMode}
collections={getSearchableCollections(collections)}
collections={collections}
files={files}
setActiveCollection={setActiveCollection}
updateSearch={updateSearch}
@ -815,14 +927,10 @@ export default function Gallery() {
) : (
<PhotoFrame
files={displayFiles}
collections={collections}
syncWithRemote={syncWithRemote}
favItemIds={favItemIds}
archivedCollections={archivedCollections}
setSelected={setSelected}
selected={selected}
isInSearchMode={isInSearchMode}
search={search}
deletedFileIds={deletedFileIds}
setDeletedFileIds={setDeletedFileIds}
activeCollection={activeCollection}
@ -831,6 +939,11 @@ export default function Gallery() {
CollectionSummaryType.incomingShare
}
enableDownload={true}
fileToCollectionsMap={fileToCollectionsMap}
collectionNameMap={collectionNameMap}
showAppDownloadBanner={
files.length < 30 && !isInSearchMode
}
/>
)}
{selected.count > 0 &&

View file

@ -406,6 +406,8 @@ export default function PublicCollectionGallery() {
publicCollection?.publicURLs?.[0]?.enableDownload ??
true
}
fileToCollectionsMap={null}
collectionNameMap={null}
/>
{blockingLoad && (
<LoadingOverlay>

View file

@ -59,7 +59,7 @@ import {
getCollectionNameMap,
getNonEmptyPersonalCollections,
} from 'utils/collection';
import { migrateExportJSON } from './migration';
import { migrateExport } from './migration';
const EXPORT_RECORD_FILE_NAME = 'export_status.json';
@ -130,10 +130,7 @@ class ExportService {
async runMigration(exportDir: string, exportRecord: ExportRecord) {
try {
addLogLine('running migration');
this.migrationInProgress = migrateExportJSON(
exportDir,
exportRecord
);
this.migrationInProgress = migrateExport(exportDir, exportRecord);
await this.migrationInProgress;
addLogLine('migration completed');
this.migrationInProgress = null;
@ -292,6 +289,11 @@ class ExportService {
failed: 0,
total: 0,
});
if (this.migrationInProgress) {
addLogLine('migration in progress, waiting for it to complete');
await this.migrationInProgress;
this.migrationInProgress = null;
}
}
async postExport() {
@ -333,11 +335,6 @@ class ExportService {
addLogLine('export not in progress, starting export');
}
this.exportInProgress = true;
if (this.migrationInProgress) {
addLogLine('migration in progress, waiting for it to complete');
await this.migrationInProgress;
this.migrationInProgress = null;
}
try {
const exportFolder = this.getExportSettings()?.folder;
await this.preExport(exportFolder);

View file

@ -39,29 +39,7 @@ import { decodeLivePhoto } from 'services/livePhotoService';
import downloadManager from 'services/downloadManager';
import { retryAsyncFunction } from 'utils/network';
export async function migrateExportJSON(
exportDir: string,
exportRecord: ExportRecord
) {
try {
if (!exportDir) {
return;
}
await migrateExport(exportDir, exportRecord);
} catch (e) {
logError(e, 'migrateExportJSON failed');
throw e;
}
}
/*
this function migrates the exportRecord file to apply any schema changes.
currently we apply only a single migration to update file and collection name to newer format
so there is just a if condition check,
later this will be converted to a loop which applies the migration one by one
till the files reaches the latest version
*/
async function migrateExport(
export async function migrateExport(
exportDir: string,
exportRecord: ExportRecordV1 | ExportRecordV2 | ExportRecord
) {
@ -94,6 +72,7 @@ async function migrateExport(
addLogLine(`Record at latest version`);
} catch (e) {
logError(e, 'export record migration failed');
throw e;
}
}

View file

@ -305,12 +305,6 @@ export function getNonEmptyPersonalCollections(
return personalCollections;
}
export function getSearchableCollections(
collections: Collection[]
): Collection[] {
return collections.filter((collection) => !isHiddenCollection(collection));
}
export function getNonHiddenCollections(
collections: Collection[]
): Collection[] {
@ -334,3 +328,14 @@ export async function splitNormalAndHiddenCollections(
}
return { normalCollections, hiddenCollections };
}
export function constructCollectionNameMap(
collections: Collection[]
): Map<number, string> {
return new Map<number, string>(
(collections ?? []).map((collection) => [
collection.id,
collection.name,
])
);
}

View file

@ -579,3 +579,14 @@ export function getPersonalFiles(files: EnteFile[], user: User) {
export function getIDBasedSortedFiles(files: EnteFile[]) {
return files.sort((a, b) => a.id - b.id);
}
export function constructFileToCollectionMap(files: EnteFile[]) {
const fileToCollectionsMap = new Map<number, number[]>();
(files ?? []).forEach((file) => {
if (!fileToCollectionsMap.get(file.id)) {
fileToCollectionsMap.set(file.id, []);
}
fileToCollectionsMap.get(file.id).push(file.collectionID);
});
return fileToCollectionsMap;
}

View file

@ -8,6 +8,7 @@ import { AppUpdateInfo } from 'types/electron';
import InfoOutlined from '@mui/icons-material/InfoRounded';
import { Trans } from 'react-i18next';
import { Subscription } from 'types/billing';
import { logoutUser } from 'services/userService';
export const getDownloadAppMessage = (): DialogBoxAttributes => {
return {
title: t('DOWNLOAD_APP'),
@ -116,3 +117,15 @@ export const getSubscriptionPurchaseSuccessMessage = (
/>
),
});
export const getSessionExpiredMessage = (): DialogBoxAttributes => ({
title: t('SESSION_EXPIRED'),
content: t('SESSION_EXPIRED_MESSAGE'),
nonClosable: true,
proceed: {
text: t('LOGIN'),
action: logoutUser,
variant: 'accent',
},
});

View file

@ -27,5 +27,18 @@
"workspaces": [
"packages/*",
"apps/*"
]
],
"lint-staged": {
"apps/**/*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write --ignore-unknown"
],
"packages/**/*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write --ignore-unknown"
],
"**/*.{json,css,scss,md,html,yml,yaml}": [
"prettier --write --ignore-unknown"
]
}
}