Merge branch 'main' into fix-dev-server-crash

This commit is contained in:
Abhinav 2022-11-29 21:36:35 +05:30
commit b433b37e08
12 changed files with 219 additions and 124 deletions

View file

@ -72,8 +72,10 @@ interface Props {
}
type SourceURL = {
imageURL?: string;
videoURL?: string;
originalImageURL?: string;
originalVideoURL?: string;
convertedImageURL?: string;
convertedVideoURL?: string;
};
const PhotoFrame = ({
@ -199,9 +201,16 @@ const PhotoFrame = ({
}, [files]);
const collectionNameMap = useMemo(() => {
return new Map<number, string>(
collections.map((collection) => [collection.id, collection.name])
);
if (collections) {
return new Map<number, string>(
collections.map((collection) => [
collection.id,
collection.name,
])
);
} else {
return new Map();
}
}, [collections]);
useEffect(() => {
@ -320,17 +329,25 @@ const PhotoFrame = ({
};
const updateSrcURL = async (id: number, srcURL: SourceURL) => {
const { videoURL, imageURL } = srcURL;
const isPlayable = videoURL && (await isPlaybackPossible(videoURL));
const {
originalImageURL,
convertedImageURL,
originalVideoURL,
convertedVideoURL,
} = srcURL;
const isPlayable =
convertedVideoURL && (await isPlaybackPossible(convertedVideoURL));
const updateFile = (file: EnteFile) => {
file.w = window.innerWidth;
file.h = window.innerHeight;
file.isSourceLoaded = true;
file.originalImageURL = originalImageURL;
file.originalVideoURL = originalVideoURL;
if (file.metadata.fileType === FILE_TYPE.VIDEO) {
if (isPlayable) {
file.html = `
<video controls onContextMenu="return false;">
<source src="${videoURL}" />
<source src="${convertedVideoURL}" />
Your browser does not support the video tag.
</video>
`;
@ -340,7 +357,7 @@ const PhotoFrame = ({
<img src="${file.msrc}" onContextMenu="return false;"/>
<div class="download-banner" >
${constants.VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD}
<a class="btn btn-outline-success" href=${videoURL} download="${file.metadata.title}"">Download</a>
<a class="btn btn-outline-success" href=${convertedVideoURL} download="${file.metadata.title}"">Download</a>
</div>
</div>
`;
@ -349,9 +366,9 @@ const PhotoFrame = ({
if (isPlayable) {
file.html = `
<div class = 'pswp-item-container'>
<img id = "live-photo-image-${file.id}" src="${imageURL}" onContextMenu="return false;"/>
<img id = "live-photo-image-${file.id}" src="${convertedImageURL}" onContextMenu="return false;"/>
<video id = "live-photo-video-${file.id}" loop muted onContextMenu="return false;">
<source src="${videoURL}" />
<source src="${convertedVideoURL}" />
Your browser does not support the video tag.
</video>
</div>
@ -368,7 +385,7 @@ const PhotoFrame = ({
`;
}
} else {
file.src = imageURL;
file.src = convertedImageURL;
}
return file;
};
@ -500,6 +517,9 @@ const PhotoFrame = ({
item.msrc = newFile.msrc;
item.html = newFile.html;
item.src = newFile.src;
item.isSourceLoaded = newFile.isSourceLoaded;
item.originalImageURL = newFile.originalImageURL;
item.originalVideoURL = newFile.originalVideoURL;
item.w = newFile.w;
item.h = newFile.h;
@ -522,10 +542,13 @@ const PhotoFrame = ({
if (!fetching[item.id]) {
try {
fetching[item.id] = true;
let urls: string[];
let urls: { original: string[]; converted: string[] };
if (galleryContext.files.has(item.id)) {
const mergedURL = galleryContext.files.get(item.id);
urls = mergedURL.split(',');
urls = {
original: mergedURL.original.split(','),
converted: mergedURL.converted.split(','),
};
} else {
appContext.startLoading();
if (
@ -541,26 +564,39 @@ const PhotoFrame = ({
urls = await DownloadManager.getFile(item, true);
}
appContext.finishLoading();
const mergedURL = urls.join(',');
const mergedURL = {
original: urls.original.join(','),
converted: urls.converted.join(','),
};
galleryContext.files.set(item.id, mergedURL);
}
let imageURL;
let videoURL;
let originalImageURL;
let originalVideoURL;
let convertedImageURL;
let convertedVideoURL;
if (item.metadata.fileType === FILE_TYPE.LIVE_PHOTO) {
[imageURL, videoURL] = urls;
[originalImageURL, originalVideoURL] = urls.converted;
} else if (item.metadata.fileType === FILE_TYPE.VIDEO) {
[videoURL] = urls;
[originalVideoURL] = urls.original;
[convertedVideoURL] = urls.converted;
} else {
[imageURL] = urls;
[originalImageURL] = urls.original;
[convertedImageURL] = urls.converted;
}
setIsSourceLoaded(false);
const newFile = await updateSrcURL(item.id, {
imageURL,
videoURL,
originalImageURL,
originalVideoURL,
convertedImageURL,
convertedVideoURL,
});
item.msrc = newFile.msrc;
item.html = newFile.html;
item.src = newFile.src;
item.isSourceLoaded = newFile.isSourceLoaded;
item.originalImageURL = newFile.originalImageURL;
item.originalVideoURL = newFile.originalVideoURL;
item.w = newFile.w;
item.h = newFile.h;
try {

View file

@ -44,6 +44,7 @@ interface Iprops {
refreshPhotoswipe: () => void;
fileToCollectionsMap: Map<number, number[]>;
collectionNameMap: Map<number, string>;
isTrashCollection: boolean;
}
function BasicDeviceCamera({
@ -77,6 +78,7 @@ export function FileInfo({
refreshPhotoswipe,
fileToCollectionsMap,
collectionNameMap,
isTrashCollection,
}: Iprops) {
const [location, setLocation] = useState<Location>(null);
const [parsedExifData, setParsedExifData] = useState<Record<string, any>>();
@ -107,6 +109,7 @@ export function FileInfo({
useEffect(() => {
if (!exif) {
setParsedExifData({});
return;
}
const parsedExifData = {};
@ -188,33 +191,33 @@ export function FileInfo({
/>
)}
{/* {location && ( */}
<InfoItem
icon={<LocationOnOutlined />}
title={constants.LOCATION}
caption={
<Link
href={getOpenStreetMapLink({
latitude: file.metadata.latitude,
longitude: file.metadata.longitude,
})}
target="_blank"
sx={{ fontWeight: 'bold' }}>
{constants.SHOW_ON_MAP}
</Link>
}
customEndButton={
<CopyButton
code={getOpenStreetMapLink({
latitude: file.metadata.latitude,
longitude: file.metadata.longitude,
})}
color="secondary"
size="medium"
/>
}
/>
{/* )} */}
{location && (
<InfoItem
icon={<LocationOnOutlined />}
title={constants.LOCATION}
caption={
<Link
href={getOpenStreetMapLink({
latitude: file.metadata.latitude,
longitude: file.metadata.longitude,
})}
target="_blank"
sx={{ fontWeight: 'bold' }}>
{constants.SHOW_ON_MAP}
</Link>
}
customEndButton={
<CopyButton
code={getOpenStreetMapLink({
latitude: file.metadata.latitude,
longitude: file.metadata.longitude,
})}
color="secondary"
size="medium"
/>
}
/>
)}
<InfoItem
icon={<TextSnippetOutlined />}
title={constants.DETAILS}
@ -243,23 +246,27 @@ export function FileInfo({
caption={formatTime(file.metadata.modificationTime / 1000)}
hideEditOption
/>
<InfoItem icon={<FolderOutlined />} hideEditOption>
<Box
display={'flex'}
gap={1}
flexWrap="wrap"
justifyContent={'flex-start'}
alignItems={'flex-start'}>
{fileToCollectionsMap
.get(file.id)
?.map((collectionID) => (
<Chip key={collectionID}>
{collectionNameMap.get(collectionID)}
</Chip>
))}
</Box>
</InfoItem>
{!isTrashCollection && (
<InfoItem icon={<FolderOutlined />} hideEditOption>
<Box
display={'flex'}
gap={1}
flexWrap="wrap"
justifyContent={'flex-start'}
alignItems={'flex-start'}>
{fileToCollectionsMap
.get(file.id)
?.filter((collectionID) =>
collectionNameMap.has(collectionID)
)
?.map((collectionID) => (
<Chip key={collectionID}>
{collectionNameMap.get(collectionID)}
</Chip>
))}
</Box>
</InfoItem>
)}
</Stack>
<ExifData
exif={exif}

View file

@ -14,7 +14,7 @@ import { livePhotoBtnHTML } from 'components/LivePhotoBtn';
import { logError } from 'utils/sentry';
import { FILE_TYPE } from 'constants/file';
import { isClipboardItemPresent, sleep } from 'utils/common';
import { isClipboardItemPresent } from 'utils/common';
import { playVideo, pauseVideo } from 'utils/photoFrame';
import { PublicCollectionGalleryContext } from 'utils/publicCollectionGallery';
import { AppContext } from 'pages/_app';
@ -79,7 +79,9 @@ function PhotoViewer(props: Iprops) {
const { isOpen, items, isSourceLoaded } = props;
const [isFav, setIsFav] = useState(false);
const [showInfo, setShowInfo] = useState(false);
const [exif, setExif] = useState<any>(null);
const [exif, setExif] =
useState<{ key: string; value: Record<string, any> }>();
const exifCopy = useRef(null);
const [livePhotoBtnOptions, setLivePhotoBtnOptions] = useState(
defaultLivePhotoDefaultOptions
);
@ -89,6 +91,7 @@ function PhotoViewer(props: Iprops) {
);
const appContext = useContext(AppContext);
const exifExtractionInProgress = useRef<string>(null);
const [shouldShowCopyOption] = useState(isClipboardItemPresent());
useEffect(() => {
@ -235,8 +238,12 @@ function PhotoViewer(props: Iprops) {
}
}, [photoSwipe?.currItem, isOpen, isSourceLoaded]);
function updateFavButton() {
setIsFav(isInFav(this?.currItem));
useEffect(() => {
exifCopy.current = exif;
}, [exif]);
function updateFavButton(file: EnteFile) {
setIsFav(isInFav(file));
}
const openPhotoSwipe = () => {
@ -298,11 +305,31 @@ function PhotoViewer(props: Iprops) {
});
}
});
photoSwipe.listen('beforeChange', function () {
updateInfo.call(this);
updateFavButton.call(this);
photoSwipe.listen('beforeChange', () => {
const currItem = photoSwipe?.currItem as EnteFile;
if (
!currItem ||
!exifCopy?.current?.value === null ||
exifCopy?.current?.key === currItem.src
) {
return;
}
setExif({ key: currItem.src, value: undefined });
checkExifAvailable(currItem);
updateFavButton(currItem);
});
photoSwipe.listen('resize', () => {
const currItem = photoSwipe?.currItem as EnteFile;
if (
!currItem ||
!exifCopy?.current?.value === null ||
exifCopy?.current?.key === currItem.src
) {
return;
}
setExif({ key: currItem.src, value: undefined });
checkExifAvailable(currItem);
});
photoSwipe.listen('resize', checkExifAvailable);
photoSwipe.init();
needUpdate.current = false;
setPhotoSwipe(photoSwipe);
@ -323,7 +350,7 @@ function PhotoViewer(props: Iprops) {
}
handleCloseInfo();
};
const isInFav = (file) => {
const isInFav = (file: EnteFile) => {
const { favItemIds } = props;
if (favItemIds && file) {
return favItemIds.has(file.id);
@ -331,7 +358,7 @@ function PhotoViewer(props: Iprops) {
return false;
};
const onFavClick = async (file) => {
const onFavClick = async (file: EnteFile) => {
const { favItemIds } = props;
if (!isInFav(file)) {
favItemIds.add(file.id);
@ -386,33 +413,37 @@ function PhotoViewer(props: Iprops) {
}
};
const checkExifAvailable = async () => {
await sleep(100);
const checkExifAvailable = async (file: EnteFile) => {
try {
const img: HTMLImageElement = document.querySelector(
'.pswp__img:not(.pswp__img--placeholder)'
);
if (img) {
const exifData = await exifr.parse(img);
if (exifData) {
setExif(exifData);
} else {
setExif(null);
if (exifExtractionInProgress.current === file.src) {
return;
}
try {
if (file.isSourceLoaded) {
exifExtractionInProgress.current = file.src;
const imageBlob = await (
await fetch(file.originalImageURL)
).blob();
const exifData = (await exifr.parse(imageBlob)) as Record<
string,
any
>;
if (exifExtractionInProgress.current === file.src) {
if (exifData) {
setExif({ key: file.src, value: exifData });
} else {
setExif({ key: file.src, value: null });
}
}
}
} finally {
exifExtractionInProgress.current = null;
}
} catch (e) {
logError(e, 'exifr parsing failed');
}
};
function updateInfo() {
const file: EnteFile = this?.currItem;
if (file) {
setExif(undefined);
checkExifAvailable();
}
}
const handleCloseInfo = () => {
setShowInfo(false);
};
@ -553,7 +584,9 @@ function PhotoViewer(props: Iprops) {
}
className="pswp__button pswp__button--custom"
onClick={() => {
onFavClick(photoSwipe?.currItem);
onFavClick(
photoSwipe?.currItem as EnteFile
);
}}>
{isFav ? (
<FavoriteIcon fontSize="small" />
@ -591,11 +624,12 @@ function PhotoViewer(props: Iprops) {
</div>
</div>
<FileInfo
isTrashCollection={props.isTrashCollection}
shouldDisableEdits={props.isSharedCollection}
showInfo={showInfo}
handleCloseInfo={handleCloseInfo}
file={photoSwipe?.currItem as EnteFile}
exif={exif}
exif={exif?.value}
scheduleUpdate={scheduleUpdate}
refreshPhotoswipe={refreshPhotoswipe}
fileToCollectionsMap={props.fileToCollectionsMap}

View file

@ -26,6 +26,7 @@ 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';
export const DeduplicateContext = createContext<DeduplicateContextType>(
DefaultDeduplicateContext
@ -44,6 +45,7 @@ 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(
@ -74,6 +76,7 @@ export default function Deduplicate() {
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);
@ -174,6 +177,7 @@ export default function Deduplicate() {
)}
<PhotoFrame
files={duplicateFiles}
collections={collections}
syncWithRemote={syncWithRemote}
setSelected={setSelected}
selected={selected}

View file

@ -18,7 +18,10 @@ import QueueProcessor, { PROCESSING_STRATEGY } from './queueProcessor';
const MAX_PARALLEL_DOWNLOADS = 10;
class DownloadManager {
private fileObjectURLPromise = new Map<string, Promise<string[]>>();
private fileObjectURLPromise = new Map<
string,
Promise<{ original: string[]; converted: string[] }>
>();
private thumbnailObjectURLPromise = new Map<number, Promise<string>>();
private thumbnailDownloadRequestsProcessor = new QueueProcessor<any>(
@ -95,12 +98,11 @@ class DownloadManager {
if (forPreview) {
return await getRenderableFileURL(file, fileBlob);
} else {
return [
await createTypedObjectURL(
fileBlob,
file.metadata.title
),
];
const fileURL = await createTypedObjectURL(
fileBlob,
file.metadata.title
);
return { converted: [fileURL], original: [fileURL] };
}
};
if (!this.fileObjectURLPromise.get(fileKey)) {

View file

@ -244,10 +244,7 @@ class ExportService {
file,
RecordType.FAILED
);
console.log(
`export failed for fileID:${file.id}, reason:`,
e
);
logError(
e,
'download and save failed for file during export'
@ -624,7 +621,6 @@ class ExportService {
oldFileSavePath,
newFileSavePath
);
console.log(oldFileMetadataSavePath, newFileMetadataSavePath);
await this.electronAPIs.checkExistsAndRename(
oldFileMetadataSavePath,
newFileMetadataSavePath

View file

@ -17,7 +17,10 @@ import { CustomError } from 'utils/error';
import QueueProcessor from './queueProcessor';
class PublicCollectionDownloadManager {
private fileObjectURLPromise = new Map<string, Promise<string[]>>();
private fileObjectURLPromise = new Map<
string,
Promise<{ original: string[]; converted: string[] }>
>();
private thumbnailObjectURLPromise = new Map<number, Promise<string>>();
private thumbnailDownloadRequestsProcessor = new QueueProcessor<any>(5);
@ -121,12 +124,11 @@ class PublicCollectionDownloadManager {
if (forPreview) {
return await getRenderableFileURL(file, fileBlob);
} else {
return [
await createTypedObjectURL(
fileBlob,
file.metadata.title
),
];
const fileURL = await createTypedObjectURL(
fileBlob,
file.metadata.title
);
return { converted: [fileURL], original: [fileURL] };
}
};
if (!this.fileObjectURLPromise.get(fileKey)) {

View file

@ -200,7 +200,6 @@ export const syncPublicFiles = async (
const parsedError = parseSharingErrorCodes(e);
logError(e, 'failed to sync shared collection files');
if (parsedError.message === CustomError.TOKEN_EXPIRED) {
console.log('invalid token or password');
throw e;
}
}

View file

@ -36,7 +36,8 @@ export async function updateCreationTimeWithExif(
if (file.metadata.fileType !== FILE_TYPE.IMAGE) {
continue;
}
const fileURL = await downloadManager.getFile(file)[0];
const fileURL = (await downloadManager.getFile(file))
.original[0];
const fileObject = await getFileFromURL(fileURL);
const fileTypeInfo = await getFileType(fileObject);
const exifData = await getRawExif(fileObject, fileTypeInfo);

View file

@ -54,6 +54,9 @@ export interface EnteFile {
isDeleted: boolean;
isTrashed?: boolean;
deleteBy?: number;
isSourceLoaded?: boolean;
originalVideoURL?: string;
originalImageURL?: string;
dataIndex: number;
updationTime: number;
}

View file

@ -17,7 +17,7 @@ export type SetCollectionSelectorAttributes = React.Dispatch<
export type GalleryContextType = {
thumbs: Map<number, string>;
files: Map<number, string>;
files: Map<number, { original: string; converted: string }>;
showPlanSelectorModal: () => void;
setActiveCollection: (collection: number) => void;
syncWithRemote: (force?: boolean, silent?: boolean) => Promise<void>;

View file

@ -287,14 +287,25 @@ export async function getRenderableFileURL(file: EnteFile, fileBlob: Blob) {
file.metadata.title,
fileBlob
);
return [URL.createObjectURL(convertedBlob)];
return {
converted: [URL.createObjectURL(convertedBlob)],
original: [URL.createObjectURL(fileBlob)],
};
}
case FILE_TYPE.LIVE_PHOTO: {
const livePhoto = await getRenderableLivePhoto(file, fileBlob);
return livePhoto.map((asset) => URL.createObjectURL(asset));
return {
converted: livePhoto.map((asset) => URL.createObjectURL(asset)),
original: [URL.createObjectURL(fileBlob)],
};
}
default: {
const previewURL = URL.createObjectURL(fileBlob);
return {
converted: [previewURL],
original: [previewURL],
};
}
default:
return [URL.createObjectURL(fileBlob)];
}
}