Merge pull request #314 from ente-io/master

release
This commit is contained in:
Abhinav Kumar 2022-01-27 15:53:20 +05:30 committed by GitHub
commit aa305cc8d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 229 additions and 168 deletions

View file

@ -26,8 +26,6 @@ module.exports = {
'base-uri ': "'self'", 'base-uri ': "'self'",
'frame-ancestors': " 'none'", 'frame-ancestors': " 'none'",
'form-action': "'none'", 'form-action': "'none'",
'report-uri': 'https://csp-reporter.ente.io',
'report-to': 'https://csp-reporter.ente.io',
}, },
WORKBOX_CONFIG: { WORKBOX_CONFIG: {

View file

@ -1,5 +1,5 @@
import * as Sentry from '@sentry/nextjs'; import * as Sentry from '@sentry/nextjs';
import { getSentryTunnelUrl } from 'utils/common/apiUtil'; import { getSentryTunnelURL } from 'utils/common/apiUtil';
import { getUserAnonymizedID } from 'utils/user'; import { getUserAnonymizedID } from 'utils/user';
import { import {
getSentryDSN, getSentryDSN,
@ -21,7 +21,7 @@ Sentry.init({
release: SENTRY_RELEASE, release: SENTRY_RELEASE,
attachStacktrace: true, attachStacktrace: true,
autoSessionTracking: false, autoSessionTracking: false,
tunnel: getSentryTunnelUrl(), tunnel: getSentryTunnelURL(),
// ... // ...
// Note: if you want to override the automatic release value, do not set a // Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so // `release` value here - use the environment variable `SENTRY_RELEASE`, so

View file

@ -209,7 +209,7 @@ const PhotoFrame = ({
}); });
}, [files, deleted, search, activeCollection]); }, [files, deleted, search, activeCollection]);
const updateUrl = (index: number) => (url: string) => { const updateURL = (index: number) => (url: string) => {
files[index] = { files[index] = {
...files[index], ...files[index],
msrc: url, msrc: url,
@ -240,7 +240,7 @@ const PhotoFrame = ({
setFiles(files); setFiles(files);
}; };
const updateSrcUrl = async (index: number, url: string) => { const updateSrcURL = async (index: number, url: string) => {
files[index] = { files[index] = {
...files[index], ...files[index],
w: window.innerWidth, w: window.innerWidth,
@ -337,7 +337,7 @@ const PhotoFrame = ({
selected[file[index].id] ?? false selected[file[index].id] ?? false
}`} }`}
file={file[index]} file={file[index]}
updateUrl={updateUrl(file[index].dataIndex)} updateURL={updateURL(file[index].dataIndex)}
onClick={onThumbnailClick(index)} onClick={onThumbnailClick(index)}
selectable={!isSharedCollection} selectable={!isSharedCollection}
onSelect={handleSelect(file[index].id, index)} onSelect={handleSelect(file[index].id, index)}
@ -370,7 +370,7 @@ const PhotoFrame = ({
url = await DownloadManager.getThumbnail(item); url = await DownloadManager.getThumbnail(item);
galleryContext.thumbs.set(item.id, url); galleryContext.thumbs.set(item.id, url);
} }
updateUrl(item.dataIndex)(url); updateURL(item.dataIndex)(url);
item.msrc = url; item.msrc = url;
if (!item.src) { if (!item.src) {
item.src = url; item.src = url;
@ -397,7 +397,7 @@ const PhotoFrame = ({
url = await DownloadManager.getFile(item, true); url = await DownloadManager.getFile(item, true);
galleryContext.files.set(item.id, url); galleryContext.files.set(item.id, url);
} }
await updateSrcUrl(item.dataIndex, url); await updateSrcURL(item.dataIndex, url);
item.html = files[item.dataIndex].html; item.html = files[item.dataIndex].html;
item.src = files[item.dataIndex].src; item.src = files[item.dataIndex].src;
item.w = files[item.dataIndex].w; item.w = files[item.dataIndex].w;

View file

@ -91,9 +91,11 @@ const renderInfoItem = (label: string, value: string | JSX.Element) => (
); );
function RenderCreationTime({ function RenderCreationTime({
shouldDisableEdits,
file, file,
scheduleUpdate, scheduleUpdate,
}: { }: {
shouldDisableEdits: boolean;
file: EnteFile; file: EnteFile;
scheduleUpdate: () => void; scheduleUpdate: () => void;
}) { }) {
@ -160,24 +162,25 @@ function RenderCreationTime({
<Value <Value
width={isInEditMode ? '20%' : '10%'} width={isInEditMode ? '20%' : '10%'}
style={{ cursor: 'pointer', marginLeft: '10px' }}> style={{ cursor: 'pointer', marginLeft: '10px' }}>
{!isInEditMode ? ( {!shouldDisableEdits &&
<IconButton onClick={openEditMode}> (!isInEditMode ? (
<EditIcon /> <IconButton onClick={openEditMode}>
</IconButton> <EditIcon />
) : (
<>
<IconButton onClick={saveEdits}>
{loading ? (
<SmallLoadingSpinner />
) : (
<TickIcon />
)}
</IconButton> </IconButton>
<IconButton onClick={discardEdits}> ) : (
<CloseIcon /> <>
</IconButton> <IconButton onClick={saveEdits}>
</> {loading ? (
)} <SmallLoadingSpinner />
) : (
<TickIcon />
)}
</IconButton>
<IconButton onClick={discardEdits}>
<CloseIcon />
</IconButton>
</>
))}
</Value> </Value>
</Row> </Row>
</> </>
@ -275,9 +278,11 @@ const FileNameEditForm = ({ filename, saveEdits, discardEdits, extension }) => {
}; };
function RenderFileName({ function RenderFileName({
shouldDisableEdits,
file, file,
scheduleUpdate, scheduleUpdate,
}: { }: {
shouldDisableEdits: boolean;
file: EnteFile; file: EnteFile;
scheduleUpdate: () => void; scheduleUpdate: () => void;
}) { }) {
@ -322,13 +327,18 @@ function RenderFileName({
{getFileTitle(filename, extension)} {getFileTitle(filename, extension)}
</FreeFlowText> </FreeFlowText>
</Value> </Value>
<Value {!shouldDisableEdits && (
width="10%" <Value
style={{ cursor: 'pointer', marginLeft: '10px' }}> width="10%"
<IconButton onClick={openEditMode}> style={{
<EditIcon /> cursor: 'pointer',
</IconButton> marginLeft: '10px',
</Value> }}>
<IconButton onClick={openEditMode}>
<EditIcon />
</IconButton>
</Value>
)}
</> </>
) : ( ) : (
<FileNameEditForm <FileNameEditForm
@ -396,6 +406,7 @@ function ExifData(props: { exif: any }) {
} }
function InfoModal({ function InfoModal({
shouldDisableEdits,
showInfo, showInfo,
handleCloseInfo, handleCloseInfo,
items, items,
@ -419,12 +430,14 @@ function InfoModal({
)} )}
{metadata?.title && ( {metadata?.title && (
<RenderFileName <RenderFileName
shouldDisableEdits={shouldDisableEdits}
file={items[photoSwipe?.getCurrentIndex()]} file={items[photoSwipe?.getCurrentIndex()]}
scheduleUpdate={scheduleUpdate} scheduleUpdate={scheduleUpdate}
/> />
)} )}
{metadata?.creationTime && ( {metadata?.creationTime && (
<RenderCreationTime <RenderCreationTime
shouldDisableEdits={shouldDisableEdits}
file={items[photoSwipe?.getCurrentIndex()]} file={items[photoSwipe?.getCurrentIndex()]}
scheduleUpdate={scheduleUpdate} scheduleUpdate={scheduleUpdate}
/> />
@ -744,6 +757,7 @@ function PhotoSwipe(props: Iprops) {
</div> </div>
</div> </div>
<InfoModal <InfoModal
shouldDisableEdits={props.isSharedCollection}
showInfo={showInfo} showInfo={showInfo}
handleCloseInfo={handleCloseInfo} handleCloseInfo={handleCloseInfo}
items={items} items={items}

View file

@ -39,12 +39,13 @@ function RecoveryKeyModal({ somethingWentWrong, ...props }: Props) {
return; return;
} }
const main = async () => { const main = async () => {
const recoveryKey = await getRecoveryKey(); try {
if (!recoveryKey) { const recoveryKey = await getRecoveryKey();
setRecoveryKey(bip39.entropyToMnemonic(recoveryKey));
} catch (e) {
somethingWentWrong(); somethingWentWrong();
props.onHide(); props.onHide();
} }
setRecoveryKey(bip39.entropyToMnemonic(recoveryKey));
}; };
main(); main();
}, [props.show]); }, [props.show]);

View file

@ -238,7 +238,9 @@ export default function Sidebar(props: Props) {
onHide={() => setRecoveryModalView(false)} onHide={() => setRecoveryModalView(false)}
somethingWentWrong={() => somethingWentWrong={() =>
props.setDialogMessage({ props.setDialogMessage({
title: constants.RECOVER_KEY_GENERATION_FAILED, title: constants.ERROR,
content:
constants.RECOVER_KEY_GENERATION_FAILED,
close: { variant: 'danger' }, close: { variant: 'danger' },
}) })
} }

View file

@ -82,7 +82,7 @@ function CollectionSelector({
<CollectionCard> <CollectionCard>
<PreviewCard <PreviewCard
file={item.file} file={item.file}
updateUrl={() => {}} updateURL={() => {}}
onSelect={() => {}} onSelect={() => {}}
forcedEnable forcedEnable
/> />

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { Form, Modal, Button } from 'react-bootstrap'; import { Form, Modal, Button } from 'react-bootstrap';
import constants from 'utils/strings/constants'; import constants from 'utils/strings/constants';
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
@ -21,9 +21,10 @@ import { reverseString } from 'utils/common';
import { SetDialogMessage } from 'components/MessageDialog'; import { SetDialogMessage } from 'components/MessageDialog';
import ArrowEast from 'components/icons/ArrowEast'; import ArrowEast from 'components/icons/ArrowEast';
import LinkButton from './LinkButton'; import LinkButton from './LinkButton';
import { DeadCenter } from 'pages/gallery'; import { DeadCenter, GalleryContext } from 'pages/gallery';
import billingService from 'services/billingService'; import billingService from 'services/billingService';
import { SetLoading } from 'types/gallery'; import { SetLoading } from 'types/gallery';
import { logError } from 'utils/sentry';
export const PlanIcon = styled.div<{ currentlySubscribed: boolean }>` export const PlanIcon = styled.div<{ currentlySubscribed: boolean }>`
border-radius: 20px; border-radius: 20px;
@ -86,6 +87,8 @@ function PlanSelector(props: Props) {
const subscription: Subscription = getUserSubscription(); const subscription: Subscription = getUserSubscription();
const [plans, setPlans] = useState<Plan[]>(null); const [plans, setPlans] = useState<Plan[]>(null);
const [planPeriod, setPlanPeriod] = useState<PLAN_PERIOD>(PLAN_PERIOD.YEAR); const [planPeriod, setPlanPeriod] = useState<PLAN_PERIOD>(PLAN_PERIOD.YEAR);
const galleryContext = useContext(GalleryContext);
const togglePeriod = () => { const togglePeriod = () => {
setPlanPeriod((prevPeriod) => setPlanPeriod((prevPeriod) =>
prevPeriod === PLAN_PERIOD.MONTH prevPeriod === PLAN_PERIOD.MONTH
@ -93,9 +96,16 @@ function PlanSelector(props: Props) {
: PLAN_PERIOD.MONTH : PLAN_PERIOD.MONTH
); );
}; };
function onReopenClick() {
galleryContext.closeMessageDialog();
galleryContext.showPlanSelectorModal();
}
useEffect(() => { useEffect(() => {
if (props.modalView) { if (!props.modalView) {
const main = async () => { return;
}
const main = async () => {
try {
props.setLoading(true); props.setLoading(true);
let plans = await billingService.getPlans(); let plans = await billingService.getPlans();
@ -111,10 +121,24 @@ function PlanSelector(props: Props) {
plans = [planForSubscription(subscription), ...plans]; plans = [planForSubscription(subscription), ...plans];
} }
setPlans(plans); setPlans(plans);
} catch (e) {
logError(e, 'plan selector modal open failed');
props.closeModal();
props.setDialogMessage({
title: constants.OPEN_PLAN_SELECTOR_MODAL_FAILED,
content: constants.UNKNOWN_ERROR,
close: { text: 'close', variant: 'danger' },
proceed: {
text: constants.REOPEN_PLAN_SELECTOR_MODAL,
variant: 'success',
action: onReopenClick,
},
});
} finally {
props.setLoading(false); props.setLoading(false);
}; }
main(); };
} main();
}, [props.modalView]); }, [props.modalView]);
async function onPlanSelect(plan: Plan) { async function onPlanSelect(plan: Plan) {

View file

@ -9,7 +9,7 @@ import { GAP_BTW_TILES } from 'constants/gallery';
interface IProps { interface IProps {
file: EnteFile; file: EnteFile;
updateUrl: (url: string) => void; updateURL: (url: string) => void;
onClick?: () => void; onClick?: () => void;
forcedEnable?: boolean; forcedEnable?: boolean;
selectable?: boolean; selectable?: boolean;
@ -161,7 +161,7 @@ export default function PreviewCard(props: IProps) {
const { const {
file, file,
onClick, onClick,
updateUrl, updateURL,
forcedEnable, forcedEnable,
selectable, selectable,
selected, selected,
@ -185,7 +185,7 @@ export default function PreviewCard(props: IProps) {
if (!file.src) { if (!file.src) {
file.src = url; file.src = url;
} }
updateUrl(url); updateURL(url);
} }
} catch (e) { } catch (e) {
// no-op // no-op

View file

@ -129,10 +129,7 @@ const InProgressSection = (props: InProgressProps) => {
<FileList> <FileList>
{fileList.map(({ fileName, progress }) => ( {fileList.map(({ fileName, progress }) => (
<li key={fileName}> <li key={fileName}>
{constants.FILE_UPLOAD_PROGRESS( {`${fileName} - ${progress}%`}
fileName,
progress
)}
</li> </li>
))} ))}
</FileList> </FileList>
@ -235,10 +232,20 @@ export default function UploadProgress(props: Props) {
/> />
<ResultSection <ResultSection
fileUploadResultMap={fileUploadResultMap} fileUploadResultMap={fileUploadResultMap}
fileUploadResult={FileUploadResults.SKIPPED} fileUploadResult={FileUploadResults.ALREADY_UPLOADED}
sectionTitle={constants.SKIPPED_FILES} sectionTitle={constants.SKIPPED_FILES}
sectionInfo={constants.SKIPPED_INFO} sectionInfo={constants.SKIPPED_INFO}
/> />
<ResultSection
fileUploadResultMap={fileUploadResultMap}
fileUploadResult={
FileUploadResults.LARGER_THAN_AVAILABLE_STORAGE
}
sectionTitle={
constants.LARGER_THAN_AVAILABLE_STORAGE_UPLOADS
}
sectionInfo={constants.LARGER_THAN_AVAILABLE_STORAGE_INFO}
/>
<ResultSection <ResultSection
fileUploadResultMap={fileUploadResultMap} fileUploadResultMap={fileUploadResultMap}
fileUploadResult={FileUploadResults.UNSUPPORTED} fileUploadResult={FileUploadResults.UNSUPPORTED}

View file

@ -30,10 +30,11 @@ export enum UPLOAD_STAGES {
} }
export enum FileUploadResults { export enum FileUploadResults {
FAILED = -1, FAILED,
SKIPPED = -2, ALREADY_UPLOADED,
UNSUPPORTED = -3, UNSUPPORTED,
BLOCKED = -4, BLOCKED,
TOO_LARGE = -5, TOO_LARGE,
UPLOADED = 100, LARGER_THAN_AVAILABLE_STORAGE,
UPLOADED,
} }

22
src/pages/404.tsx Normal file
View file

@ -0,0 +1,22 @@
import Container from 'components/Container';
import React, { useContext, useEffect, useState } from 'react';
import constants from 'utils/strings/constants';
import { AppContext } from './_app';
export default function NotFound() {
const appContext = useContext(AppContext);
const [loading, setLoading] = useState(true);
useEffect(() => {
appContext.showNavBar(true);
setLoading(false);
}, []);
return (
<Container>
{loading ? (
<span className="sr-only">Loading...</span>
) : (
constants.NOT_FOUND
)}
</Container>
);
}

View file

@ -479,8 +479,8 @@ type AppContextType = {
sharedFiles: File[]; sharedFiles: File[];
resetSharedFiles: () => void; resetSharedFiles: () => void;
setDisappearingFlashMessage: (message: FlashMessage) => void; setDisappearingFlashMessage: (message: FlashMessage) => void;
redirectUrl: string; redirectURL: string;
setRedirectUrl: (url: string) => void; setRedirectURL: (url: string) => void;
}; };
export enum FLASH_MESSAGE_TYPE { export enum FLASH_MESSAGE_TYPE {
@ -510,7 +510,7 @@ export default function App({ Component, err }) {
const [sharedFiles, setSharedFiles] = useState<File[]>(null); const [sharedFiles, setSharedFiles] = useState<File[]>(null);
const [redirectName, setRedirectName] = useState<string>(null); const [redirectName, setRedirectName] = useState<string>(null);
const [flashMessage, setFlashMessage] = useState<FlashMessage>(null); const [flashMessage, setFlashMessage] = useState<FlashMessage>(null);
const [redirectUrl, setRedirectUrl] = useState(null); const [redirectURL, setRedirectURL] = useState(null);
useEffect(() => { useEffect(() => {
if ( if (
!('serviceWorker' in navigator) || !('serviceWorker' in navigator) ||
@ -644,8 +644,8 @@ export default function App({ Component, err }) {
sharedFiles, sharedFiles,
resetSharedFiles, resetSharedFiles,
setDisappearingFlashMessage, setDisappearingFlashMessage,
redirectUrl, redirectURL,
setRedirectUrl, setRedirectURL,
}}> }}>
{loading ? ( {loading ? (
<Container> <Container>

View file

@ -76,9 +76,9 @@ export default function Credentials() {
} }
await SaveKeyInSessionStore(SESSION_KEYS.ENCRYPTION_KEY, key); await SaveKeyInSessionStore(SESSION_KEYS.ENCRYPTION_KEY, key);
await decryptAndStoreToken(key); await decryptAndStoreToken(key);
const redirectUrl = appContext.redirectUrl; const redirectURL = appContext.redirectURL;
appContext.setRedirectUrl(null); appContext.setRedirectURL(null);
router.push(redirectUrl ?? PAGES.GALLERY); router.push(redirectURL ?? PAGES.GALLERY);
} catch (e) { } catch (e) {
logError(e, 'user entered a wrong password'); logError(e, 'user entered a wrong password');
setFieldError('passphrase', constants.INCORRECT_PASSPHRASE); setFieldError('passphrase', constants.INCORRECT_PASSPHRASE);

View file

@ -119,6 +119,7 @@ const defaultGalleryContext: GalleryContextType = {
thumbs: new Map(), thumbs: new Map(),
files: new Map(), files: new Map(),
showPlanSelectorModal: () => null, showPlanSelectorModal: () => null,
closeMessageDialog: () => null,
setActiveCollection: () => null, setActiveCollection: () => null,
syncWithRemote: () => null, syncWithRemote: () => null,
}; };
@ -144,7 +145,7 @@ export default function Gallery() {
collectionID: 0, collectionID: 0,
}); });
const [dialogMessage, setDialogMessage] = useState<MessageAttributes>(); const [dialogMessage, setDialogMessage] = useState<MessageAttributes>();
const [dialogView, setDialogView] = useState(false); const [messageDialogView, setMessageDialogView] = useState(false);
const [planModalView, setPlanModalView] = useState(false); const [planModalView, setPlanModalView] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [collectionSelectorAttributes, setCollectionSelectorAttributes] = const [collectionSelectorAttributes, setCollectionSelectorAttributes] =
@ -186,10 +187,13 @@ export default function Gallery() {
const [fixCreationTimeAttributes, setFixCreationTimeAttributes] = const [fixCreationTimeAttributes, setFixCreationTimeAttributes] =
useState<FixCreationTimeAttributes>(null); useState<FixCreationTimeAttributes>(null);
const showPlanSelectorModal = () => setPlanModalView(true);
const closeMessageDialog = () => setMessageDialogView(false);
useEffect(() => { useEffect(() => {
const key = getKey(SESSION_KEYS.ENCRYPTION_KEY); const key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
if (!key) { if (!key) {
appContext.setRedirectUrl(router.asPath); appContext.setRedirectURL(router.asPath);
router.push(PAGES.ROOT); router.push(PAGES.ROOT);
return; return;
} }
@ -218,7 +222,7 @@ export default function Gallery() {
appContext.showNavBar(true); appContext.showNavBar(true);
}, []); }, []);
useEffect(() => setDialogView(true), [dialogMessage]); useEffect(() => setMessageDialogView(true), [dialogMessage]);
useEffect( useEffect(
() => collectionSelectorAttributes && setCollectionSelectorView(true), () => collectionSelectorAttributes && setCollectionSelectorView(true),
@ -536,7 +540,8 @@ export default function Gallery() {
<GalleryContext.Provider <GalleryContext.Provider
value={{ value={{
...defaultGalleryContext, ...defaultGalleryContext,
showPlanSelectorModal: () => setPlanModalView(true), showPlanSelectorModal,
closeMessageDialog,
setActiveCollection, setActiveCollection,
syncWithRemote, syncWithRemote,
}}> }}>
@ -563,8 +568,8 @@ export default function Gallery() {
<AlertBanner bannerMessage={bannerMessage} /> <AlertBanner bannerMessage={bannerMessage} />
<MessageDialog <MessageDialog
size="lg" size="lg"
show={dialogView} show={messageDialogView}
onHide={() => setDialogView(false)} onHide={closeMessageDialog}
attributes={dialogMessage} attributes={dialogMessage}
/> />
<SearchBar <SearchBar

View file

@ -1,4 +1,4 @@
import { getEndpoint, getPaymentsUrl } from 'utils/common/apiUtil'; import { getEndpoint, getPaymentsURL } from 'utils/common/apiUtil';
import { getToken } from 'utils/common/key'; import { getToken } from 'utils/common/key';
import { setData, LS_KEYS } from 'utils/storage/localStorage'; import { setData, LS_KEYS } from 'utils/storage/localStorage';
import { convertToHumanReadable } from 'utils/billing'; import { convertToHumanReadable } from 'utils/billing';
@ -154,7 +154,7 @@ class billingService {
action: string action: string
) { ) {
try { try {
window.location.href = `${getPaymentsUrl()}?productID=${productID}&paymentToken=${paymentToken}&action=${action}&redirectURL=${ window.location.href = `${getPaymentsURL()}?productID=${productID}&paymentToken=${paymentToken}&action=${action}&redirectURL=${
window.location.origin window.location.origin
}/gallery`; }/gallery`;
} catch (e) { } catch (e) {

View file

@ -1,5 +1,5 @@
import { getToken } from 'utils/common/key'; import { getToken } from 'utils/common/key';
import { getFileUrl, getThumbnailUrl } from 'utils/common/apiUtil'; import { getFileURL, getThumbnailURL } from 'utils/common/apiUtil';
import CryptoWorker from 'utils/crypto'; import CryptoWorker from 'utils/crypto';
import { import {
generateStreamFromArrayBuffer, generateStreamFromArrayBuffer,
@ -13,8 +13,8 @@ import { logError } from 'utils/sentry';
import { FILE_TYPE } from 'constants/file'; import { FILE_TYPE } from 'constants/file';
class DownloadManager { class DownloadManager {
private fileObjectUrlPromise = new Map<string, Promise<string>>(); private fileObjectURLPromise = new Map<string, Promise<string>>();
private thumbnailObjectUrlPromise = new Map<number, Promise<string>>(); private thumbnailObjectURLPromise = new Map<number, Promise<string>>();
public async getThumbnail(file: EnteFile) { public async getThumbnail(file: EnteFile) {
try { try {
@ -22,10 +22,18 @@ class DownloadManager {
if (!token) { if (!token) {
return null; return null;
} }
if (!this.thumbnailObjectUrlPromise.get(file.id)) { if (!this.thumbnailObjectURLPromise.get(file.id)) {
const downloadPromise = async () => { const downloadPromise = async () => {
const thumbnailCache = await caches.open('thumbs'); const thumbnailCache = await (async () => {
const cacheResp: Response = await thumbnailCache.match( try {
return await caches.open('thumbs');
} catch (e) {
return null;
// ignore
}
})();
const cacheResp: Response = await thumbnailCache?.match(
file.id.toString() file.id.toString()
); );
if (cacheResp) { if (cacheResp) {
@ -34,7 +42,7 @@ class DownloadManager {
const thumb = await this.downloadThumb(token, file); const thumb = await this.downloadThumb(token, file);
const thumbBlob = new Blob([thumb]); const thumbBlob = new Blob([thumb]);
try { try {
await thumbnailCache.put( await thumbnailCache?.put(
file.id.toString(), file.id.toString(),
new Response(thumbBlob) new Response(thumbBlob)
); );
@ -43,12 +51,12 @@ class DownloadManager {
} }
return URL.createObjectURL(thumbBlob); return URL.createObjectURL(thumbBlob);
}; };
this.thumbnailObjectUrlPromise.set(file.id, downloadPromise()); this.thumbnailObjectURLPromise.set(file.id, downloadPromise());
} }
return await this.thumbnailObjectUrlPromise.get(file.id); return await this.thumbnailObjectURLPromise.get(file.id);
} catch (e) { } catch (e) {
this.thumbnailObjectUrlPromise.delete(file.id); this.thumbnailObjectURLPromise.delete(file.id);
logError(e, 'get preview Failed'); logError(e, 'get preview Failed');
throw e; throw e;
} }
@ -56,7 +64,7 @@ class DownloadManager {
downloadThumb = async (token: string, file: EnteFile) => { downloadThumb = async (token: string, file: EnteFile) => {
const resp = await HTTPService.get( const resp = await HTTPService.get(
getThumbnailUrl(file.id), getThumbnailURL(file.id),
null, null,
{ 'X-Auth-Token': token }, { 'X-Auth-Token': token },
{ responseType: 'arraybuffer' } { responseType: 'arraybuffer' }
@ -84,23 +92,23 @@ class DownloadManager {
} }
return URL.createObjectURL(fileBlob); return URL.createObjectURL(fileBlob);
}; };
if (!this.fileObjectUrlPromise.get(fileKey)) { if (!this.fileObjectURLPromise.get(fileKey)) {
this.fileObjectUrlPromise.set( this.fileObjectURLPromise.set(
fileKey, fileKey,
getFilePromise(shouldBeConverted) getFilePromise(shouldBeConverted)
); );
} }
const fileURL = await this.fileObjectUrlPromise.get(fileKey); const fileURL = await this.fileObjectURLPromise.get(fileKey);
return fileURL; return fileURL;
} catch (e) { } catch (e) {
this.fileObjectUrlPromise.delete(fileKey); this.fileObjectURLPromise.delete(fileKey);
logError(e, 'Failed to get File'); logError(e, 'Failed to get File');
throw e; throw e;
} }
}; };
public async getCachedOriginalFile(file: EnteFile) { public async getCachedOriginalFile(file: EnteFile) {
return await this.fileObjectUrlPromise.get(file.id.toString()); return await this.fileObjectURLPromise.get(file.id.toString());
} }
async downloadFile(file: EnteFile) { async downloadFile(file: EnteFile) {
@ -114,7 +122,7 @@ class DownloadManager {
file.metadata.fileType === FILE_TYPE.LIVE_PHOTO file.metadata.fileType === FILE_TYPE.LIVE_PHOTO
) { ) {
const resp = await HTTPService.get( const resp = await HTTPService.get(
getFileUrl(file.id), getFileURL(file.id),
null, null,
{ 'X-Auth-Token': token }, { 'X-Auth-Token': token },
{ responseType: 'arraybuffer' } { responseType: 'arraybuffer' }
@ -126,7 +134,7 @@ class DownloadManager {
); );
return generateStreamFromArrayBuffer(decrypted); return generateStreamFromArrayBuffer(decrypted);
} }
const resp = await fetch(getFileUrl(file.id), { const resp = await fetch(getFileURL(file.id), {
headers: { headers: {
'X-Auth-Token': token, 'X-Auth-Token': token,
}, },

View file

@ -129,6 +129,7 @@ export const updateTrash = async (
} catch (e) { } catch (e) {
logError(e, 'Get trash files failed'); logError(e, 'Get trash files failed');
} }
return currentTrash;
}; };
function removeDuplicates(trash: Trash) { function removeDuplicates(trash: Trash) {

View file

@ -85,11 +85,11 @@ export async function updateFileCreationDateInEXIF(
export async function convertImageToDataURL(reader: FileReader, url: string) { export async function convertImageToDataURL(reader: FileReader, url: string) {
const blob = await fetch(url).then((r) => r.blob()); const blob = await fetch(url).then((r) => r.blob());
const dataUrl = await new Promise<string>((resolve) => { const dataURL = await new Promise<string>((resolve) => {
reader.onload = () => resolve(reader.result as string); reader.onload = () => resolve(reader.result as string);
reader.readAsDataURL(blob); reader.readAsDataURL(blob);
}); });
return dataUrl; return dataURL;
} }
function dataURIToBlob(dataURI) { function dataURIToBlob(dataURI) {

View file

@ -1,4 +1,5 @@
import { import {
FileUploadResults,
RANDOM_PERCENTAGE_PROGRESS_FOR_PUT, RANDOM_PERCENTAGE_PROGRESS_FOR_PUT,
UPLOAD_STAGES, UPLOAD_STAGES,
} from 'constants/upload'; } from 'constants/upload';
@ -9,7 +10,7 @@ class UIService {
private filesUploaded: number; private filesUploaded: number;
private totalFileCount: number; private totalFileCount: number;
private fileProgress: Map<string, number>; private fileProgress: Map<string, number>;
private uploadResult: Map<string, number>; private uploadResult: Map<string, FileUploadResults>;
private progressUpdater: ProgressUpdater; private progressUpdater: ProgressUpdater;
init(progressUpdater: ProgressUpdater) { init(progressUpdater: ProgressUpdater) {
@ -47,8 +48,8 @@ class UIService {
this.updateProgressBarUI(); this.updateProgressBarUI();
} }
moveFileToResultList(filename: string) { moveFileToResultList(filename: string, uploadResult: FileUploadResults) {
this.uploadResult.set(filename, this.fileProgress.get(filename)); this.uploadResult.set(filename, uploadResult);
this.fileProgress.delete(filename); this.fileProgress.delete(filename);
this.updateProgressBarUI(); this.updateProgressBarUI();
} }

View file

@ -185,7 +185,11 @@ class UploadManager {
this.failedFiles.push(fileWithCollection); this.failedFiles.push(fileWithCollection);
} }
UIService.moveFileToResultList(fileWithCollection.file.name); UIService.moveFileToResultList(
fileWithCollection.file.name,
fileUploadResult
);
UploadService.reducePendingUploadCount();
} }
} }

View file

@ -36,6 +36,10 @@ class UploadService {
await this.preFetchUploadURLs(); await this.preFetchUploadURLs();
} }
reducePendingUploadCount() {
this.pendingUploadCount--;
}
async readFile( async readFile(
worker: any, worker: any,
reader: FileReader, reader: FileReader,

View file

@ -1,5 +1,4 @@
import { EnteFile } from 'types/file'; import { EnteFile } from 'types/file';
import { sleep } from 'utils/common';
import { handleUploadError, CustomError } from 'utils/error'; import { handleUploadError, CustomError } from 'utils/error';
import { decryptFile } from 'utils/file'; import { decryptFile } from 'utils/file';
import { logError } from 'utils/sentry'; import { logError } from 'utils/sentry';
@ -22,7 +21,6 @@ import {
import { FILE_TYPE } from 'constants/file'; import { FILE_TYPE } from 'constants/file';
import { FileUploadResults } from 'constants/upload'; import { FileUploadResults } from 'constants/upload';
const TwoSecondInMillSeconds = 2000;
const FIVE_GB_IN_BYTES = 5 * 1024 * 1024 * 1024; const FIVE_GB_IN_BYTES = 5 * 1024 * 1024 * 1024;
interface UploadResponse { interface UploadResponse {
fileUploadResult: FileUploadResults; fileUploadResult: FileUploadResults;
@ -46,12 +44,6 @@ export default async function uploader(
try { try {
if (rawFile.size >= FIVE_GB_IN_BYTES) { if (rawFile.size >= FIVE_GB_IN_BYTES) {
UIService.setFileProgress(
rawFile.name,
FileUploadResults.TOO_LARGE
);
// wait two second before removing the file from the progress in file section
await sleep(TwoSecondInMillSeconds);
return { fileUploadResult: FileUploadResults.TOO_LARGE }; return { fileUploadResult: FileUploadResults.TOO_LARGE };
} }
fileTypeInfo = await getFileType(reader, rawFile); fileTypeInfo = await getFileType(reader, rawFile);
@ -65,10 +57,7 @@ export default async function uploader(
); );
if (fileAlreadyInCollection(existingFilesInCollection, metadata)) { if (fileAlreadyInCollection(existingFilesInCollection, metadata)) {
UIService.setFileProgress(rawFile.name, FileUploadResults.SKIPPED); return { fileUploadResult: FileUploadResults.ALREADY_UPLOADED };
// wait two second before removing the file from the progress in file section
await sleep(TwoSecondInMillSeconds);
return { fileUploadResult: FileUploadResults.SKIPPED };
} }
file = await UploadService.readFile( file = await UploadService.readFile(
@ -105,7 +94,6 @@ export default async function uploader(
const uploadedFile = await UploadHttpClient.uploadFile(uploadFile); const uploadedFile = await UploadHttpClient.uploadFile(uploadFile);
const decryptedFile = await decryptFile(uploadedFile, collection); const decryptedFile = await decryptFile(uploadedFile, collection);
UIService.setFileProgress(rawFile.name, FileUploadResults.UPLOADED);
UIService.increaseFileUploaded(); UIService.increaseFileUploaded();
return { return {
fileUploadResult: FileUploadResults.UPLOADED, fileUploadResult: FileUploadResults.UPLOADED,
@ -118,29 +106,15 @@ export default async function uploader(
const error = handleUploadError(e); const error = handleUploadError(e);
switch (error.message) { switch (error.message) {
case CustomError.ETAG_MISSING: case CustomError.ETAG_MISSING:
UIService.setFileProgress(
rawFile.name,
FileUploadResults.BLOCKED
);
return { fileUploadResult: FileUploadResults.BLOCKED }; return { fileUploadResult: FileUploadResults.BLOCKED };
case CustomError.UNSUPPORTED_FILE_FORMAT: case CustomError.UNSUPPORTED_FILE_FORMAT:
UIService.setFileProgress(
rawFile.name,
FileUploadResults.UNSUPPORTED
);
return { fileUploadResult: FileUploadResults.UNSUPPORTED }; return { fileUploadResult: FileUploadResults.UNSUPPORTED };
case CustomError.FILE_TOO_LARGE: case CustomError.FILE_TOO_LARGE:
UIService.setFileProgress( return {
rawFile.name, fileUploadResult:
FileUploadResults.TOO_LARGE FileUploadResults.LARGER_THAN_AVAILABLE_STORAGE,
); };
return { fileUploadResult: FileUploadResults.TOO_LARGE };
default: default:
UIService.setFileProgress(
rawFile.name,
FileUploadResults.FAILED
);
return { fileUploadResult: FileUploadResults.FAILED }; return { fileUploadResult: FileUploadResults.FAILED };
} }
} finally { } finally {

View file

@ -72,13 +72,21 @@ export const setRecoveryKey = (token: string, recoveryKey: RecoveryKey) =>
}); });
export const logoutUser = async () => { export const logoutUser = async () => {
// ignore server logout result as logoutUser can be triggered before sign up or on token expiry try {
await _logout(); // ignore server logout result as logoutUser can be triggered before sign up or on token expiry
clearKeys(); await _logout();
clearData(); clearKeys();
await caches.delete('thumbs'); clearData();
await clearFiles(); try {
router.push(PAGES.ROOT); await caches.delete('thumbs');
} catch (e) {
// ignore
}
await clearFiles();
router.push(PAGES.ROOT);
} catch (e) {
logError(e, 'logoutUser failed');
}
}; };
export const clearFiles = async () => { export const clearFiles = async () => {

View file

@ -26,6 +26,7 @@ export type GalleryContextType = {
thumbs: Map<number, string>; thumbs: Map<number, string>;
files: Map<number, string>; files: Map<number, string>;
showPlanSelectorModal: () => void; showPlanSelectorModal: () => void;
closeMessageDialog: () => void;
setActiveCollection: (collection: number) => void; setActiveCollection: (collection: number) => void;
syncWithRemote: (force?: boolean, silent?: boolean) => Promise<void>; syncWithRemote: (force?: boolean, silent?: boolean) => Promise<void>;
}; };

View file

@ -4,7 +4,7 @@ export const getEndpoint = () => {
return endPoint; return endPoint;
}; };
export const getFileUrl = (id: number) => { export const getFileURL = (id: number) => {
if (process.env.NEXT_PUBLIC_ENTE_ENDPOINT !== undefined) { if (process.env.NEXT_PUBLIC_ENTE_ENDPOINT !== undefined) {
return ( return (
`${process.env.NEXT_PUBLIC_ENTE_ENDPOINT}/files/download/${id}` ?? `${process.env.NEXT_PUBLIC_ENTE_ENDPOINT}/files/download/${id}` ??
@ -14,7 +14,7 @@ export const getFileUrl = (id: number) => {
return `https://files.ente.io/?fileID=${id}`; return `https://files.ente.io/?fileID=${id}`;
}; };
export const getThumbnailUrl = (id: number) => { export const getThumbnailURL = (id: number) => {
if (process.env.NEXT_PUBLIC_ENTE_ENDPOINT !== undefined) { if (process.env.NEXT_PUBLIC_ENTE_ENDPOINT !== undefined) {
return ( return (
`${process.env.NEXT_PUBLIC_ENTE_ENDPOINT}/files/preview/${id}` ?? `${process.env.NEXT_PUBLIC_ENTE_ENDPOINT}/files/preview/${id}` ??
@ -24,11 +24,11 @@ export const getThumbnailUrl = (id: number) => {
return `https://thumbnails.ente.io/?fileID=${id}`; return `https://thumbnails.ente.io/?fileID=${id}`;
}; };
export const getSentryTunnelUrl = () => { export const getSentryTunnelURL = () => {
return `https://sentry-reporter.ente.io`; return `https://sentry-reporter.ente.io`;
}; };
export const getPaymentsUrl = () => { export const getPaymentsURL = () => {
if (process.env.NEXT_PUBLIC_ENTE_ENDPOINT !== undefined) { if (process.env.NEXT_PUBLIC_ENTE_ENDPOINT !== undefined) {
return process.env.NEXT_PUBLIC_ENTE_PAYMENT_ENDPOINT; return process.env.NEXT_PUBLIC_ENTE_PAYMENT_ENDPOINT;
} }

View file

@ -106,7 +106,7 @@ export const SaveKeyInSessionStore = async (
}; };
export const getRecoveryKey = async () => { export const getRecoveryKey = async () => {
let recoveryKey = null; let recoveryKey: string = null;
try { try {
const cryptoWorker = await new CryptoWorker(); const cryptoWorker = await new CryptoWorker();
@ -129,6 +129,7 @@ export const getRecoveryKey = async () => {
return recoveryKey; return recoveryKey;
} catch (e) { } catch (e) {
logError(e, 'getRecoveryKey failed'); logError(e, 'getRecoveryKey failed');
throw e;
} }
}; };
@ -138,7 +139,7 @@ async function createNewRecoveryKey() {
const cryptoWorker = await new CryptoWorker(); const cryptoWorker = await new CryptoWorker();
const recoveryKey = await cryptoWorker.generateEncryptionKey(); const recoveryKey: string = await cryptoWorker.generateEncryptionKey();
const encryptedMasterKey: B64EncryptionResult = const encryptedMasterKey: B64EncryptionResult =
await cryptoWorker.encryptToB64(masterKey, recoveryKey); await cryptoWorker.encryptToB64(masterKey, recoveryKey);
const encryptedRecoveryKey: B64EncryptionResult = const encryptedRecoveryKey: B64EncryptionResult =

View file

@ -4,7 +4,6 @@ import localForage from 'localforage';
if (runningInBrowser()) { if (runningInBrowser()) {
localForage.config({ localForage.config({
driver: localForage.INDEXEDDB,
name: 'ente-files', name: 'ente-files',
version: 1.0, version: 1.0,
storeName: 'files', storeName: 'files',

View file

@ -109,26 +109,6 @@ const englishConstants = {
}, },
UPLOADING_FILES: 'file upload', UPLOADING_FILES: 'file upload',
FILE_NOT_UPLOADED_LIST: 'the following files were not uploaded', FILE_NOT_UPLOADED_LIST: 'the following files were not uploaded',
FILE_UPLOAD_PROGRESS: (name: string, progress: number) => (
<div id={name}>
{name}
{' - '}
<span style={{ color: '#eee' }}>
{(() => {
switch (progress) {
case -1:
return 'failed';
case -2:
return 'already uploaded, skipping...';
case -3:
return 'unsupported file format, skipping....';
default:
return `${progress}%`;
}
})()}
</span>
</div>
),
SUBSCRIPTION_EXPIRED: (action) => ( SUBSCRIPTION_EXPIRED: (action) => (
<> <>
your subscription has expired, please a{' '} your subscription has expired, please a{' '}
@ -546,8 +526,11 @@ const englishConstants = {
BLOCKED_UPLOADS: 'blocked uploads', BLOCKED_UPLOADS: 'blocked uploads',
INPROGRESS_UPLOADS: 'uploads in progress', INPROGRESS_UPLOADS: 'uploads in progress',
TOO_LARGE_UPLOADS: 'large files', TOO_LARGE_UPLOADS: 'large files',
TOO_LARGE_INFO: LARGER_THAN_AVAILABLE_STORAGE_UPLOADS: 'insufficient storage',
LARGER_THAN_AVAILABLE_STORAGE_INFO:
'these files were not uploaded as they exceed the maximum size limit for your storage plan', 'these files were not uploaded as they exceed the maximum size limit for your storage plan',
TOO_LARGE_INFO:
'these files were not uploaded as they exceed our maximum file size limit',
UPLOAD_TO_COLLECTION: 'upload to album', UPLOAD_TO_COLLECTION: 'upload to album',
ARCHIVE: 'archive', ARCHIVE: 'archive',
ALL: 'all', ALL: 'all',
@ -621,6 +604,9 @@ const englishConstants = {
DATE_TIME_ORIGINAL: 'EXIF:DateTimeOriginal', DATE_TIME_ORIGINAL: 'EXIF:DateTimeOriginal',
DATE_TIME_DIGITIZED: 'EXIF:DateTimeDigitized', DATE_TIME_DIGITIZED: 'EXIF:DateTimeDigitized',
CUSTOM_TIME: 'custom time', CUSTOM_TIME: 'custom time',
REOPEN_PLAN_SELECTOR_MODAL: 're-open plans',
OPEN_PLAN_SELECTOR_MODAL_FAILED: 'failed to open plans',
NOT_FOUND: '404 not found',
}; };
export default englishConstants; export default englishConstants;

View file

@ -4592,9 +4592,9 @@ ms@2.1.2, ms@^2.1.1:
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
nanoid@^3.1.23: nanoid@^3.1.23:
version "3.1.25" version "3.2.0"
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c"
integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q== integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==
native-url@0.3.4: native-url@0.3.4:
version "0.3.4" version "0.3.4"