Merge branch 'master' into release

This commit is contained in:
Vishnu 2021-10-08 23:21:49 +05:30
commit b497d242df
10 changed files with 205 additions and 205 deletions

View file

@ -18,7 +18,6 @@ import { VariableSizeList as List } from 'react-window';
import PhotoSwipe from 'components/PhotoSwipe/PhotoSwipe';
import { isInsideBox, isSameDay as isSameDayAnyYear } from 'utils/search';
import { SetDialogMessage } from './MessageDialog';
import { CustomError } from 'utils/common/errorUtil';
import {
GAP_BTW_TILES,
DATE_CONTAINER_HEIGHT,
@ -30,10 +29,10 @@ import {
import { fileIsArchived } from 'utils/file';
import { ALL_SECTION, ARCHIVE_SECTION } from './pages/gallery/Collections';
import { isSharedFile } from 'utils/file';
import { isPlaybackPossible } from 'utils/photoFrame';
const NO_OF_PAGES = 2;
const A_DAY = 24 * 60 * 60 * 1000;
const WAIT_FOR_VIDEO_PLAYBACK = 1 * 1000;
interface TimeStampListItem {
itemType: ITEM_TYPE;
@ -146,7 +145,7 @@ interface Props {
isFirstLoad;
openFileUploader;
loadingBar;
searchMode: boolean;
isInSearchMode: boolean;
search: Search;
setSearchStats: setSearchStats;
deleted?: number[];
@ -165,11 +164,10 @@ const PhotoFrame = ({
isFirstLoad,
openFileUploader,
loadingBar,
searchMode,
isInSearchMode,
search,
setSearchStats,
deleted,
setDialogMessage,
activeCollection,
isSharedCollection,
}: Props) => {
@ -181,7 +179,7 @@ const PhotoFrame = ({
const listRef = useRef(null);
useEffect(() => {
if (searchMode) {
if (isInSearchMode) {
setSearchStats({
resultCount: filteredData.length,
timeTaken: (Date.now() - startTime) / 1000,
@ -225,21 +223,33 @@ const PhotoFrame = ({
setFiles(files);
};
const updateSrcUrl = (index: number, url: string) => {
const updateSrcUrl = async (index: number, url: string) => {
files[index] = {
...files[index],
src: url,
w: window.innerWidth,
h: window.innerHeight,
};
if (files[index].metadata.fileType === FILE_TYPE.VIDEO) {
files[index].html = `
if (await isPlaybackPossible(url)) {
files[index].html = `
<video controls>
<source src="${url}" />
Your browser does not support the video tag.
</video>
`;
delete files[index].src;
} else {
files[index].html = `
<div class="video-loading">
<img src="${files[index].msrc}" />
<div class="download-message" >
${constants.VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD}
<a class="btn btn-outline-success" href=${url} download="${files[index].metadata.title}"">Download</button>
</div>
</div>
`;
}
} else {
files[index].src = url;
}
setFiles(files);
};
@ -313,66 +323,11 @@ const PhotoFrame = ({
url = await DownloadManager.getFile(item, true);
galleryContext.files.set(item.id, url);
}
updateSrcUrl(item.dataIndex, url);
if (item.metadata.fileType === FILE_TYPE.VIDEO) {
try {
await new Promise((resolve, reject) => {
const video = document.createElement('video');
video.addEventListener('timeupdate', function () {
clearTimeout(t);
resolve(null);
});
video.preload = 'metadata';
video.src = url;
video.currentTime = 3;
const t = setTimeout(() => {
reject(
Error(
`${CustomError.VIDEO_PLAYBACK_FAILED} err: wait time exceeded`
)
);
}, WAIT_FOR_VIDEO_PLAYBACK);
});
item.html = `
<video width="320" height="240" controls>
<source src="${url}" />
Your browser does not support the video tag.
</video>
`;
delete item.src;
} catch (e) {
const downloadFile = async () => {
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = item.metadata.title;
document.body.appendChild(a);
a.click();
a.remove();
setOpen(false);
};
setDialogMessage({
title: constants.VIDEO_PLAYBACK_FAILED,
content:
constants.VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD,
staticBackdrop: true,
proceed: {
text: constants.DOWNLOAD,
action: downloadFile,
variant: 'success',
},
close: {
text: constants.CLOSE,
action: () => setOpen(false),
},
});
return;
}
} else {
item.src = url;
}
item.w = window.innerWidth;
item.h = window.innerHeight;
await updateSrcUrl(item.dataIndex, url);
item.html = files[item.dataIndex].html;
item.src = files[item.dataIndex].src;
item.w = files[item.dataIndex].w;
item.h = files[item.dataIndex].h;
try {
instance.invalidateCurrItems();
instance.updateSize(true);
@ -515,7 +470,7 @@ const PhotoFrame = ({
return (
<>
{!isFirstLoad && files.length === 0 && !searchMode ? (
{!isFirstLoad && files.length === 0 && !isInSearchMode ? (
<EmptyScreen>
<img height={150} src="/images/gallery.png" />
<div style={{ color: '#a6a6a6', marginTop: '16px' }}>
@ -635,7 +590,7 @@ const PhotoFrame = ({
return sum;
})();
files.length < 30 &&
!searchMode &&
!isInSearchMode &&
timeStampList.push({
itemType: ITEM_TYPE.BANNER,
banner: (

View file

@ -7,16 +7,15 @@ import {
addToFavorites,
removeFromFavorites,
} from 'services/collectionService';
import { File, FILE_TYPE } from 'services/fileService';
import { File } from 'services/fileService';
import constants from 'utils/strings/constants';
import DownloadManger from 'services/downloadManager';
import exifr from 'exifr';
import Modal from 'react-bootstrap/Modal';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import styled from 'styled-components';
import events from './events';
import { fileNameWithoutExtension, formatDateTime } from 'utils/file';
import { downloadFile, formatDateTime } from 'utils/file';
import { FormCheck } from 'react-bootstrap';
import { prettyPrintExif } from 'utils/exif';
@ -296,21 +295,11 @@ function PhotoSwipe(props: Iprops) {
setShowInfo(true);
};
const downloadFile = async (file) => {
const downloadFileHelper = async (file) => {
const { loadingBar } = props;
const a = document.createElement('a');
a.style.display = 'none';
loadingBar.current.continuousStart();
a.href = await DownloadManger.getFile(file);
await downloadFile(file);
loadingBar.current.complete();
if (file.metadata.fileType === FILE_TYPE.LIVE_PHOTO) {
a.download = fileNameWithoutExtension(file.metadata.title) + '.zip';
} else {
a.download = file.metadata.title;
}
document.body.appendChild(a);
a.click();
a.remove();
};
const { id } = props;
let { className } = props;
@ -344,7 +333,7 @@ function PhotoSwipe(props: Iprops) {
className="pswp-custom download-btn"
title={constants.DOWNLOAD}
onClick={() =>
downloadFile(photoSwipe.currItem)
downloadFileHelper(photoSwipe.currItem)
}
/>

View file

@ -177,12 +177,12 @@ export default function SearchBar(props: Props) {
break;
case SuggestionType.COLLECTION:
props.setActiveCollection(selectedOption.value as number);
resetSearch(true);
setValue(null);
break;
}
};
const resetSearch = async (force?: boolean) => {
if (props.isOpen || force) {
const resetSearch = () => {
if (props.isOpen) {
props.loadingBar.current?.continuousStart();
props.setSearch({});
setTimeout(() => {

View file

@ -35,7 +35,7 @@ interface CollectionProps {
syncWithRemote: () => Promise<void>;
setCollectionNamerAttributes: SetCollectionNamerAttributes;
startLoadingBar: () => void;
searchMode: boolean;
isInSearchMode: boolean;
collectionFilesCount: Map<number, number>;
}
@ -87,6 +87,11 @@ const Chip = styled.button<{ active: boolean }>`
}
`;
const Hider = styled.div<{ hide: boolean }>`
opacity: ${(props) => (props.hide ? '0' : '100')};
height: ${(props) => (props.hide ? '0' : 'auto')};
`;
export default function Collections(props: CollectionProps) {
const { activeCollection, collections, setActiveCollection } = props;
const [selectedCollectionID, setSelectedCollectionID] =
@ -119,7 +124,7 @@ export default function Collections(props: CollectionProps) {
useEffect(() => {
updateScrollObj();
}, [collectionWrapperRef.current]);
}, [collectionWrapperRef.current, props.isInSearchMode]);
useEffect(() => {
if (!collectionWrapperRef?.current) {
@ -184,111 +189,104 @@ export default function Collections(props: CollectionProps) {
};
return (
!props.searchMode && (
<>
<CollectionShare
show={collectionShareModalView}
onHide={() => setCollectionShareModalView(false)}
collection={getSelectedCollection(
selectedCollectionID,
props.collections
<Hider hide={props.isInSearchMode}>
<CollectionShare
show={collectionShareModalView}
onHide={() => setCollectionShareModalView(false)}
collection={getSelectedCollection(
selectedCollectionID,
props.collections
)}
syncWithRemote={props.syncWithRemote}
/>
<CollectionBar>
<CollectionContainer>
{scrollObj.scrollLeft > 0 && (
<NavigationButton
scrollDirection={SCROLL_DIRECTION.LEFT}
onClick={scrollCollection(SCROLL_DIRECTION.LEFT)}
/>
)}
syncWithRemote={props.syncWithRemote}
/>
<CollectionBar>
<CollectionContainer>
{scrollObj.scrollLeft > 0 && (
<NavigationButton
scrollDirection={SCROLL_DIRECTION.LEFT}
onClick={scrollCollection(
SCROLL_DIRECTION.LEFT
)}
<Wrapper
ref={collectionWrapperRef}
onScroll={updateScrollObj}>
<Chip
active={activeCollection === ALL_SECTION}
onClick={clickHandler(ALL_SECTION)}>
{constants.ALL}
<div
style={{
display: 'inline-block',
width: '24px',
}}
/>
)}
<Wrapper
ref={collectionWrapperRef}
onScroll={updateScrollObj}>
<Chip
active={activeCollection === ALL_SECTION}
onClick={clickHandler(ALL_SECTION)}>
{constants.ALL}
<div
style={{
display: 'inline-block',
width: '24px',
}}
/>
</Chip>
{sortCollections(
collections,
props.collectionAndTheirLatestFile,
collectionSortBy
).map((item) => (
<OverlayTrigger
key={item.id}
placement="top"
delay={{ show: 250, hide: 400 }}
overlay={renderTooltip(item.id)}>
<Chip
ref={collectionChipsRef[item.id]}
active={activeCollection === item.id}
onClick={clickHandler(item.id)}>
{item.name}
{item.type !==
CollectionType.favorites &&
item.owner.id === user?.id ? (
<OverlayTrigger
rootClose
trigger="click"
placement="bottom"
overlay={collectionOptions}>
<OptionIcon
onClick={() =>
setSelectedCollectionID(
item.id
)
}
/>
</OverlayTrigger>
) : (
<div
style={{
display: 'inline-block',
width: '24px',
}}
</Chip>
{sortCollections(
collections,
props.collectionAndTheirLatestFile,
collectionSortBy
).map((item) => (
<OverlayTrigger
key={item.id}
placement="top"
delay={{ show: 250, hide: 400 }}
overlay={renderTooltip(item.id)}>
<Chip
ref={collectionChipsRef[item.id]}
active={activeCollection === item.id}
onClick={clickHandler(item.id)}>
{item.name}
{item.type !== CollectionType.favorites &&
item.owner.id === user?.id ? (
<OverlayTrigger
rootClose
trigger="click"
placement="bottom"
overlay={collectionOptions}>
<OptionIcon
onClick={() =>
setSelectedCollectionID(
item.id
)
}
/>
)}
</Chip>
</OverlayTrigger>
))}
<Chip
active={activeCollection === ARCHIVE_SECTION}
onClick={clickHandler(ARCHIVE_SECTION)}>
{constants.ARCHIVE}
<div
style={{
display: 'inline-block',
width: '24px',
}}
/>
</Chip>
</Wrapper>
{scrollObj.scrollLeft <
scrollObj.scrollWidth - scrollObj.clientWidth && (
<NavigationButton
scrollDirection={SCROLL_DIRECTION.RIGHT}
onClick={scrollCollection(
SCROLL_DIRECTION.RIGHT
)}
</OverlayTrigger>
) : (
<div
style={{
display: 'inline-block',
width: '24px',
}}
/>
)}
</Chip>
</OverlayTrigger>
))}
<Chip
active={activeCollection === ARCHIVE_SECTION}
onClick={clickHandler(ARCHIVE_SECTION)}>
{constants.ARCHIVE}
<div
style={{
display: 'inline-block',
width: '24px',
}}
/>
)}
</CollectionContainer>
<CollectionSort
setCollectionSortBy={setCollectionSortBy}
activeSortBy={collectionSortBy}
/>
</CollectionBar>
</>
)
</Chip>
</Wrapper>
{scrollObj.scrollLeft <
scrollObj.scrollWidth - scrollObj.clientWidth && (
<NavigationButton
scrollDirection={SCROLL_DIRECTION.RIGHT}
onClick={scrollCollection(SCROLL_DIRECTION.RIGHT)}
/>
)}
</CollectionContainer>
<CollectionSort
setCollectionSortBy={setCollectionSortBy}
activeSortBy={collectionSortBy}
/>
</CollectionBar>
</Hider>
);
}

View file

@ -79,12 +79,33 @@ const GlobalStyles = createGlobalStyle`
height: 100%;
}
.video-loading > div {
.video-loading > div.spinner-border {
position: relative;
top: -50vh;
left: 50vw;
}
.video-loading > div.download-message {
position: relative;
top: -60vh;
left: 0;
height: 16vh;
padding:2vh 0;
background-color: #151414;
color:#ddd;
display: flex;
flex-direction:column;
align-items: center;
justify-content: space-around;
opacity: 0.8;
font-size:20px;
}
.download-message > a{
width: 130px;
}
:root {
--primary: #e26f99,
};

View file

@ -174,7 +174,7 @@ export default function Gallery() {
});
const loadingBar = useRef(null);
const [searchMode, setSearchMode] = useState(false);
const [isInSearchMode, setIsInSearchMode] = useState(false);
const [searchStats, setSearchStats] = useState(null);
const syncInProgress = useRef(true);
const resync = useRef(false);
@ -491,8 +491,9 @@ export default function Gallery() {
}
};
const updateSearch = (search: Search) => {
setSearch(search);
const updateSearch = (newSearch: Search) => {
setActiveCollection(ALL_SECTION);
setSearch(newSearch);
setSearchStats(null);
};
@ -538,8 +539,8 @@ export default function Gallery() {
attributes={dialogMessage}
/>
<SearchBar
isOpen={searchMode}
setOpen={setSearchMode}
isOpen={isInSearchMode}
setOpen={setIsInSearchMode}
loadingBar={loadingBar}
isFirstFetch={isFirstFetch}
collections={collections}
@ -550,7 +551,7 @@ export default function Gallery() {
<Collections
collections={collections}
collectionAndTheirLatestFile={collectionsAndTheirLatestFile}
searchMode={searchMode}
isInSearchMode={isInSearchMode}
activeCollection={activeCollection}
setActiveCollection={setActiveCollection}
syncWithRemote={syncWithRemote}
@ -617,7 +618,7 @@ export default function Gallery() {
isFirstLoad={isFirstLoad}
openFileUploader={openFileUploader}
loadingBar={loadingBar}
searchMode={searchMode}
isInSearchMode={isInSearchMode}
search={search}
setSearchStats={setSearchStats}
deleted={deleted}

View file

@ -45,7 +45,7 @@ class FFmpegService {
async function generateThumbnailHelper(ffmpeg: FFmpeg, file: File) {
try {
const inputFileName = `${Date.now().toString}-${file.name}`;
const thumbFileName = `${Date.now().toString}-thumb.png`;
const thumbFileName = `${Date.now().toString}-thumb.jpeg`;
ffmpeg.FS(
'writeFile',
inputFileName,
@ -62,6 +62,7 @@ async function generateThumbnailHelper(ffmpeg: FFmpeg, file: File) {
`00:00:0${seekTime.toFixed(3)}`,
'-vframes',
'1',
'-vf scale=512:512',
thumbFileName
);
thumb = ffmpeg.FS('readFile', thumbFileName);

View file

@ -26,7 +26,12 @@ export async function generateThumbnail(
} else {
try {
const thumb = await FFmpegService.generateThumbnail(file);
return { thumbnail: thumb, hasStaticThumbnail: false };
const dummyImageFile = new File([thumb], file.name);
canvas = await generateImageThumbnail(
worker,
dummyImageFile,
isHEIC
);
} catch (e) {
canvas = await generateVideoThumbnail(file);
}

View file

@ -11,6 +11,7 @@ import {
import { decodeMotionPhoto } from 'services/motionPhotoService';
import { getMimeTypeFromBlob } from 'services/upload/readFileService';
import { EncryptionResult } from 'services/upload/uploadService';
import DownloadManger from 'services/downloadManager';
import { logError } from 'utils/sentry';
import { User } from 'services/userService';
import CryptoWorker from 'utils/crypto';
@ -36,6 +37,20 @@ export function downloadAsFile(filename: string, content: string) {
a.remove();
}
export async function downloadFile(file) {
const a = document.createElement('a');
a.style.display = 'none';
a.href = await DownloadManger.getFile(file);
if (file.metadata.fileType === FILE_TYPE.LIVE_PHOTO) {
a.download = fileNameWithoutExtension(file.metadata.title) + '.zip';
} else {
a.download = file.metadata.title;
}
document.body.appendChild(a);
a.click();
a.remove();
}
export function fileIsHEIC(mimeType: string) {
return (
mimeType.toLowerCase().endsWith(TYPE_HEIC) ||

View file

@ -0,0 +1,15 @@
const WAIT_FOR_VIDEO_PLAYBACK = 1 * 1000;
export async function isPlaybackPossible(url: string): Promise<boolean> {
return await new Promise((resolve) => {
const t = setTimeout(() => {
resolve(false);
}, WAIT_FOR_VIDEO_PLAYBACK);
const video = document.createElement('video');
video.addEventListener('canplay', function () {
clearTimeout(t);
resolve(true);
});
video.src = url;
});
}