diff --git a/src/components/Sidebar/DebugLogs.tsx b/src/components/Sidebar/DebugLogs.tsx
index 185698285..2f4283d80 100644
--- a/src/components/Sidebar/DebugLogs.tsx
+++ b/src/components/Sidebar/DebugLogs.tsx
@@ -4,6 +4,9 @@ import { downloadAsFile } from 'utils/file';
import constants from 'utils/strings/constants';
import { addLogLine, getDebugLogs } from 'utils/logging';
import SidebarButton from './Button';
+import { getData, LS_KEYS } from 'utils/storage/localStorage';
+import { User } from 'types/user';
+import { getSentryUserID } from 'utils/user';
export default function DebugLogs() {
const appContext = useContext(AppContext);
@@ -22,6 +25,11 @@ export default function DebugLogs() {
});
const downloadDebugLogs = () => {
+ addLogLine(
+ 'latest commit id :' + process.env.NEXT_PUBLIC_LATEST_COMMIT_HASH
+ );
+ addLogLine(`user sentry id ${getSentryUserID()}`);
+ addLogLine(`ente userID ${(getData(LS_KEYS.USER) as User)?.id}`);
addLogLine('exporting logs');
const logs = getDebugLogs();
const logString = logs.join('\n');
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 8fb4f531e..ed219cfd2 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -27,7 +27,6 @@ import {
getRoadmapRedirectURL,
} from 'services/userService';
import { CustomError } from 'utils/error';
-import { getSentryUserID } from 'utils/user';
export const MessageContainer = styled('div')`
background-color: #111;
@@ -208,10 +207,6 @@ export default function App({ Component, err }) {
useEffect(() => {
addLogLine(`app started`);
- addLogLine(
- `latest commit id :${process.env.NEXT_PUBLIC_LATEST_COMMIT_HASH}`
- );
- addLogLine(`user sentry id ${getSentryUserID()}`);
}, []);
useEffect(() => setMessageDialogView(true), [dialogMessage]);
diff --git a/src/pages/credentials/index.tsx b/src/pages/credentials/index.tsx
index c972e0973..2fba50807 100644
--- a/src/pages/credentials/index.tsx
+++ b/src/pages/credentials/index.tsx
@@ -25,7 +25,7 @@ import FormPaperFooter from 'components/Form/FormPaper/Footer';
import LinkButton from 'components/pages/gallery/LinkButton';
import { CustomError } from 'utils/error';
import isElectron from 'is-electron';
-import desktopService from 'services/desktopService';
+import safeStorageService from 'services/electron/safeStorage';
import VerticallyCentered from 'components/Container';
import EnteSpinner from 'components/EnteSpinner';
import { Input } from '@mui/material';
@@ -43,7 +43,7 @@ export default function Credentials() {
const keyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
let key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
if (!key && isElectron()) {
- key = await desktopService.getEncryptionKey();
+ key = await safeStorageService.getEncryptionKey();
if (key) {
await saveKeyInSessionStore(
SESSION_KEYS.ENCRYPTION_KEY,
diff --git a/src/pages/gallery/index.tsx b/src/pages/gallery/index.tsx
index e99203b21..7bf1172d7 100644
--- a/src/pages/gallery/index.tsx
+++ b/src/pages/gallery/index.tsx
@@ -14,7 +14,7 @@ import {
trashFiles,
deleteFromTrash,
} from 'services/fileService';
-import { styled } from '@mui/material';
+import { styled, Typography } from '@mui/material';
import {
syncCollections,
getFavItemIds,
@@ -101,6 +101,7 @@ import UploadInputs from 'components/UploadSelectorInputs';
import useFileInput from 'hooks/useFileInput';
import { User } from 'types/user';
import { getData, LS_KEYS } from 'utils/storage/localStorage';
+import { CenteredFlex } from 'components/Container';
export const DeadCenter = styled('div')`
flex: 1;
@@ -110,12 +111,6 @@ export const DeadCenter = styled('div')`
text-align: center;
flex-direction: column;
`;
-const AlertContainer = styled('div')`
- background-color: #111;
- padding: 5px 0;
- font-size: 14px;
- text-align: center;
-`;
const defaultGalleryContext: GalleryContextType = {
thumbs: new Map(),
@@ -609,9 +604,11 @@ export default function Gallery() {
)}
{isFirstLoad && (
-
- {constants.INITIAL_LOAD_DELAY_WARNING}
-
+
+
+ {constants.INITIAL_LOAD_DELAY_WARNING}
+
+
)}
(
@@ -267,21 +270,23 @@ export default function PublicCollectionGallery() {
}
if (isPasswordProtected && !passwordJWTToken.current) {
return (
-
-
-
-
- {constants.LINK_PASSWORD}
-
-
-
-
-
+
+
+ {constants.PASSWORD}
+
+ {constants.LINK_PASSWORD}
+
+
+
+
);
}
if (!publicFiles) {
diff --git a/src/services/cacheService.ts b/src/services/cacheService.ts
new file mode 100644
index 000000000..e24302a77
--- /dev/null
+++ b/src/services/cacheService.ts
@@ -0,0 +1,31 @@
+import electronService from './electron/common';
+import electronCacheService from './electron/cache';
+import { logError } from 'utils/sentry';
+
+const THUMB_CACHE = 'thumbs';
+
+export function getCacheProvider() {
+ if (electronService.checkIsBundledApp()) {
+ return electronCacheService;
+ } else {
+ return caches;
+ }
+}
+
+export async function openThumbnailCache() {
+ try {
+ return await getCacheProvider().open(THUMB_CACHE);
+ } catch (e) {
+ logError(e, 'openThumbnailCache failed');
+ // log and ignore
+ }
+}
+
+export async function deleteThumbnailCache() {
+ try {
+ return await getCacheProvider().delete(THUMB_CACHE);
+ } catch (e) {
+ logError(e, 'deleteThumbnailCache failed');
+ // dont throw
+ }
+}
diff --git a/src/services/downloadManager.ts b/src/services/downloadManager.ts
index b81253ce5..ca45c22ae 100644
--- a/src/services/downloadManager.ts
+++ b/src/services/downloadManager.ts
@@ -3,8 +3,7 @@ import { getFileURL, getThumbnailURL } from 'utils/common/apiUtil';
import CryptoWorker from 'utils/crypto';
import {
generateStreamFromArrayBuffer,
- convertForPreview,
- needsConversionForPreview,
+ getRenderableFileURL,
createTypedObjectURL,
} from 'utils/file';
import HTTPService from './HTTPService';
@@ -13,6 +12,7 @@ import { EnteFile } from 'types/file';
import { logError } from 'utils/sentry';
import { FILE_TYPE } from 'constants/file';
import { CustomError } from 'utils/error';
+import { openThumbnailCache } from './cacheService';
class DownloadManager {
private fileObjectURLPromise = new Map>();
@@ -26,14 +26,7 @@ class DownloadManager {
}
if (!this.thumbnailObjectURLPromise.get(file.id)) {
const downloadPromise = async () => {
- const thumbnailCache = await (async () => {
- try {
- return await caches.open('thumbs');
- } catch (e) {
- return null;
- // ignore
- }
- })();
+ const thumbnailCache = await openThumbnailCache();
const cacheResp: Response = await thumbnailCache?.match(
file.id.toString()
@@ -43,14 +36,13 @@ class DownloadManager {
}
const thumb = await this.downloadThumb(token, file);
const thumbBlob = new Blob([thumb]);
- try {
- await thumbnailCache?.put(
- file.id.toString(),
- new Response(thumbBlob)
- );
- } catch (e) {
- // TODO: handle storage full exception.
- }
+
+ thumbnailCache
+ ?.put(file.id.toString(), new Response(thumbBlob))
+ .catch((e) => {
+ logError(e, 'cache put failed');
+ // TODO: handle storage full exception.
+ });
return URL.createObjectURL(thumbBlob);
};
this.thumbnailObjectURLPromise.set(file.id, downloadPromise());
@@ -84,38 +76,24 @@ class DownloadManager {
};
getFile = async (file: EnteFile, forPreview = false) => {
- const shouldBeConverted = forPreview && needsConversionForPreview(file);
- const fileKey = shouldBeConverted
- ? `${file.id}_converted`
- : `${file.id}`;
+ const fileKey = forPreview ? `${file.id}_preview` : `${file.id}`;
try {
- const getFilePromise = async (convert: boolean) => {
+ const getFilePromise = async () => {
const fileStream = await this.downloadFile(file);
const fileBlob = await new Response(fileStream).blob();
- if (convert) {
- const convertedBlobs = await convertForPreview(
- file,
- fileBlob
- );
- return await Promise.all(
- convertedBlobs.map(
- async (blob) =>
- await createTypedObjectURL(
- blob,
- file.metadata.title
- )
- )
- );
+ if (forPreview) {
+ return await getRenderableFileURL(file, fileBlob);
+ } else {
+ return [
+ await createTypedObjectURL(
+ fileBlob,
+ file.metadata.title
+ ),
+ ];
}
- return [
- await createTypedObjectURL(fileBlob, file.metadata.title),
- ];
};
if (!this.fileObjectURLPromise.get(fileKey)) {
- this.fileObjectURLPromise.set(
- fileKey,
- getFilePromise(shouldBeConverted)
- );
+ this.fileObjectURLPromise.set(fileKey, getFilePromise());
}
const fileURLs = await this.fileObjectURLPromise.get(fileKey);
return fileURLs;
diff --git a/src/services/electron/cache.ts b/src/services/electron/cache.ts
new file mode 100644
index 000000000..3c7d213a4
--- /dev/null
+++ b/src/services/electron/cache.ts
@@ -0,0 +1,24 @@
+import { runningInBrowser } from 'utils/common';
+
+class ElectronCacheService {
+ private ElectronAPIs: any;
+ private allElectronAPIsExist: boolean = false;
+
+ constructor() {
+ this.ElectronAPIs = runningInBrowser() && window['ElectronAPIs'];
+ this.allElectronAPIsExist = !!this.ElectronAPIs?.openDiskCache;
+ }
+ async open(cacheName: string): Promise {
+ if (this.allElectronAPIsExist) {
+ return await this.ElectronAPIs.openDiskCache(cacheName);
+ }
+ }
+
+ async delete(cacheName: string): Promise {
+ if (this.allElectronAPIsExist) {
+ return await this.ElectronAPIs.deleteDiskCache(cacheName);
+ }
+ }
+}
+
+export default new ElectronCacheService();
diff --git a/src/services/electron/common.ts b/src/services/electron/common.ts
new file mode 100644
index 000000000..8f29837b1
--- /dev/null
+++ b/src/services/electron/common.ts
@@ -0,0 +1,18 @@
+import isElectron from 'is-electron';
+import { runningInBrowser } from 'utils/common';
+
+class ElectronService {
+ private ElectronAPIs: any;
+ private isBundledApp: boolean = false;
+
+ constructor() {
+ this.ElectronAPIs = runningInBrowser() && window['ElectronAPIs'];
+ this.isBundledApp = !!this.ElectronAPIs?.openDiskCache;
+ }
+
+ checkIsBundledApp() {
+ return isElectron() && this.isBundledApp;
+ }
+}
+
+export default new ElectronService();
diff --git a/src/services/desktopService.tsx b/src/services/electron/safeStorage.tsx
similarity index 90%
rename from src/services/desktopService.tsx
rename to src/services/electron/safeStorage.tsx
index a302e8bc0..58c31c180 100644
--- a/src/services/desktopService.tsx
+++ b/src/services/electron/safeStorage.tsx
@@ -1,7 +1,7 @@
import { runningInBrowser } from 'utils/common';
import { logError } from 'utils/sentry';
-class DesktopService {
+class SafeStorageService {
private ElectronAPIs: any;
private allElectronAPIsExist: boolean = false;
constructor() {
@@ -35,8 +35,8 @@ class DesktopService {
return await this.ElectronAPIs.clearElectronStore();
}
} catch (e) {
- logError(e, 'getEncryptionKey failed');
+ logError(e, 'clearElectronStore failed');
}
}
}
-export default new DesktopService();
+export default new SafeStorageService();
diff --git a/src/services/heicConverter/heicConverterService.ts b/src/services/heicConverter/heicConverterService.ts
index e93ec3871..bc478e0b8 100644
--- a/src/services/heicConverter/heicConverterService.ts
+++ b/src/services/heicConverter/heicConverterService.ts
@@ -4,6 +4,7 @@ import { createNewConvertWorker } from 'utils/heicConverter';
import { retryAsyncFunction } from 'utils/network';
import { logError } from 'utils/sentry';
import { addLogLine } from 'utils/logging';
+import { makeHumanReadableStorage } from 'utils/billing';
const WORKER_POOL_SIZE = 2;
const MAX_CONVERSION_IN_PARALLEL = 1;
@@ -40,11 +41,21 @@ class HEICConverter {
const timeout = setTimeout(() => {
reject(Error('wait time exceeded'));
}, WAIT_TIME_IN_MICROSECONDS);
- const convertedHEIC =
+ const startTime = Date.now();
+ const convertedHEIC: Blob =
await comlink.convertHEIC(
fileBlob,
format
);
+ addLogLine(
+ `originalFileSize:${makeHumanReadableStorage(
+ fileBlob?.size
+ )},convertedFileSize:${makeHumanReadableStorage(
+ convertedHEIC?.size
+ )}, heic conversion time: ${
+ Date.now() - startTime
+ }ms `
+ );
clearTimeout(timeout);
resolve(convertedHEIC);
} catch (e) {
@@ -54,6 +65,20 @@ class HEICConverter {
main();
}
);
+ if (!convertedHEIC || convertedHEIC?.size === 0) {
+ logError(
+ Error(`converted heic fileSize is Zero`),
+ 'converted heic fileSize is Zero',
+ {
+ originalFileSize: makeHumanReadableStorage(
+ fileBlob?.size ?? 0
+ ),
+ convertedFileSize: makeHumanReadableStorage(
+ convertedHEIC?.size ?? 0
+ ),
+ }
+ );
+ }
await new Promise((resolve) => {
setTimeout(
() => resolve(null),
diff --git a/src/services/publicCollectionDownloadManager.ts b/src/services/publicCollectionDownloadManager.ts
index 0d62ebf06..9b56b3bbb 100644
--- a/src/services/publicCollectionDownloadManager.ts
+++ b/src/services/publicCollectionDownloadManager.ts
@@ -5,8 +5,8 @@ import {
import CryptoWorker from 'utils/crypto';
import {
generateStreamFromArrayBuffer,
- convertForPreview,
- needsConversionForPreview,
+ getRenderableFileURL,
+ createTypedObjectURL,
} from 'utils/file';
import HTTPService from './HTTPService';
import { EnteFile } from 'types/file';
@@ -107,34 +107,28 @@ class PublicCollectionDownloadManager {
passwordToken: string,
forPreview = false
) => {
- const shouldBeConverted = forPreview && needsConversionForPreview(file);
- const fileKey = shouldBeConverted
- ? `${file.id}_converted`
- : `${file.id}`;
+ const fileKey = forPreview ? `${file.id}_preview` : `${file.id}`;
try {
- const getFilePromise = async (convert: boolean) => {
+ const getFilePromise = async () => {
const fileStream = await this.downloadFile(
token,
passwordToken,
file
);
const fileBlob = await new Response(fileStream).blob();
- if (convert) {
- const convertedBlobs = await convertForPreview(
- file,
- fileBlob
- );
- return convertedBlobs.map((blob) =>
- URL.createObjectURL(blob)
- );
+ if (forPreview) {
+ return await getRenderableFileURL(file, fileBlob);
+ } else {
+ return [
+ await createTypedObjectURL(
+ fileBlob,
+ file.metadata.title
+ ),
+ ];
}
- return [URL.createObjectURL(fileBlob)];
};
if (!this.fileObjectURLPromise.get(fileKey)) {
- this.fileObjectURLPromise.set(
- fileKey,
- getFilePromise(shouldBeConverted)
- );
+ this.fileObjectURLPromise.set(fileKey, getFilePromise());
}
const fileURLs = await this.fileObjectURLPromise.get(fileKey);
return fileURLs;
diff --git a/src/services/upload/thumbnailService.ts b/src/services/upload/thumbnailService.ts
index 6170198e8..d5b13046a 100644
--- a/src/services/upload/thumbnailService.ts
+++ b/src/services/upload/thumbnailService.ts
@@ -4,7 +4,7 @@ import { logError } from 'utils/sentry';
import { BLACK_THUMBNAIL_BASE64 } from 'constants/upload';
import FFmpegService from 'services/ffmpeg/ffmpegService';
import { convertBytesToHumanReadable } from 'utils/file/size';
-import { isFileHEIC } from 'utils/file';
+import { isExactTypeHEIC } from 'utils/file';
import { ElectronFile, FileTypeInfo } from 'types/upload';
import { getUint8ArrayView } from '../readerService';
import HEICConverter from 'services/heicConverter/heicConverterService';
@@ -37,7 +37,7 @@ export async function generateThumbnail(
file = new File([await file.blob()], file.name);
}
if (fileTypeInfo.fileType === FILE_TYPE.IMAGE) {
- const isHEIC = isFileHEIC(fileTypeInfo.exactType);
+ const isHEIC = isExactTypeHEIC(fileTypeInfo.exactType);
canvas = await generateImageThumbnail(file, isHEIC);
} else {
try {
diff --git a/src/services/userService.ts b/src/services/userService.ts
index 152636380..832455d1d 100644
--- a/src/services/userService.ts
+++ b/src/services/userService.ts
@@ -20,7 +20,8 @@ import {
import { getLocalFamilyData, isPartOfFamily } from 'utils/billing';
import { ServerErrorCodes } from 'utils/error';
import isElectron from 'is-electron';
-import desktopService from './desktopService';
+import safeStorageService from './electron/safeStorage';
+import { deleteThumbnailCache } from './cacheService';
const ENDPOINT = getEndpoint();
@@ -118,13 +119,13 @@ export const logoutUser = async () => {
clearKeys();
clearData();
try {
- await caches.delete('thumbs');
+ await deleteThumbnailCache();
} catch (e) {
// ignore
}
await clearFiles();
if (isElectron()) {
- desktopService.clearElectronStore();
+ safeStorageService.clearElectronStore();
}
router.push(PAGES.ROOT);
} catch (e) {
diff --git a/src/utils/crypto/index.ts b/src/utils/crypto/index.ts
index 2c4546c55..38f303b24 100644
--- a/src/utils/crypto/index.ts
+++ b/src/utils/crypto/index.ts
@@ -8,7 +8,7 @@ import { setRecoveryKey } from 'services/userService';
import { logError } from 'utils/sentry';
import { ComlinkWorker } from 'utils/comlink';
import isElectron from 'is-electron';
-import desktopService from 'services/desktopService';
+import safeStorageService from 'services/electron/safeStorage';
export interface B64EncryptionResult {
encryptedData: string;
@@ -103,7 +103,7 @@ export const saveKeyInSessionStore = async (
const sessionKeyAttributes = await cryptoWorker.encryptToB64(key);
setKey(keyType, sessionKeyAttributes);
if (isElectron() && !fromDesktop) {
- desktopService.setEncryptionKey(key);
+ safeStorageService.setEncryptionKey(key);
}
};
diff --git a/src/utils/file/index.ts b/src/utils/file/index.ts
index 98ce23524..bbb0689e8 100644
--- a/src/utils/file/index.ts
+++ b/src/utils/file/index.ts
@@ -26,7 +26,8 @@ import ffmpegService from 'services/ffmpeg/ffmpegService';
import { NEW_FILE_MAGIC_METADATA, VISIBILITY_STATE } from 'types/magicMetadata';
import { IsArchived, updateMagicMetadataProps } from 'utils/magicMetadata';
import { ARCHIVE_SECTION, TRASH_SECTION } from 'constants/collection';
-
+import { addLogLine } from 'utils/logging';
+import { makeHumanReadableStorage } from 'utils/billing';
export function downloadAsFile(filename: string, content: string) {
const file = new Blob([content], {
type: 'text/plain',
@@ -131,14 +132,6 @@ function downloadUsingAnchor(link: string, name: string) {
a.remove();
}
-export function isFileHEIC(mimeType: string) {
- return (
- mimeType &&
- (mimeType.toLowerCase().endsWith(TYPE_HEIC) ||
- mimeType.toLowerCase().endsWith(TYPE_HEIF))
- );
-}
-
export function sortFilesIntoCollections(files: EnteFile[]) {
const collectionWiseFiles = new Map([
[ARCHIVE_SECTION, []],
@@ -327,40 +320,71 @@ export function generateStreamFromArrayBuffer(data: Uint8Array) {
});
}
-export async function convertForPreview(
+export async function getRenderableFileURL(file: EnteFile, fileBlob: Blob) {
+ switch (file.metadata.fileType) {
+ case FILE_TYPE.IMAGE: {
+ const convertedBlob = await getRenderableImage(
+ file.metadata.title,
+ fileBlob
+ );
+ return [URL.createObjectURL(convertedBlob)];
+ }
+ case FILE_TYPE.LIVE_PHOTO: {
+ const livePhoto = await getRenderableLivePhoto(file, fileBlob);
+ return livePhoto.map((asset) => URL.createObjectURL(asset));
+ }
+ default:
+ return [URL.createObjectURL(fileBlob)];
+ }
+}
+
+async function getRenderableLivePhoto(
file: EnteFile,
fileBlob: Blob
): Promise {
- const convertIfHEIC = async (fileName: string, fileBlob: Blob) => {
- const mimeType = (
- await getFileType(new File([fileBlob], file.metadata.title))
- ).exactType;
- if (isFileHEIC(mimeType)) {
- fileBlob = await HEICConverter.convert(fileBlob);
- }
- return fileBlob;
- };
+ const originalName = fileNameWithoutExtension(file.metadata.title);
+ const motionPhoto = await decodeMotionPhoto(fileBlob, originalName);
+ const imageBlob = new Blob([motionPhoto.image]);
+ return await Promise.all([
+ getRenderableImage(motionPhoto.imageNameTitle, imageBlob),
+ getPlayableVideo(motionPhoto.videoNameTitle, motionPhoto.video),
+ ]);
+}
- if (file.metadata.fileType === FILE_TYPE.LIVE_PHOTO) {
- const originalName = fileNameWithoutExtension(file.metadata.title);
- const motionPhoto = await decodeMotionPhoto(fileBlob, originalName);
- let image = new Blob([motionPhoto.image]);
+async function getPlayableVideo(videoNameTitle: string, video: Uint8Array) {
+ const mp4ConvertedVideo = await ffmpegService.convertToMP4(
+ video,
+ videoNameTitle
+ );
+ return new Blob([mp4ConvertedVideo]);
+}
- // can run conversion in parellel as video and image
- // have different processes
- const convertedVideo = ffmpegService.convertToMP4(
- motionPhoto.video,
- motionPhoto.videoNameTitle
+async function getRenderableImage(fileName: string, imageBlob: Blob) {
+ if (await isFileHEIC(imageBlob, fileName)) {
+ addLogLine(
+ `HEICConverter called for ${fileName}-${makeHumanReadableStorage(
+ imageBlob.size
+ )}`
);
-
- image = await convertIfHEIC(motionPhoto.imageNameTitle, image);
- const video = new Blob([await convertedVideo]);
-
- return [image, video];
+ const convertedImageBlob = await HEICConverter.convert(imageBlob);
+ addLogLine(`${fileName} successfully converted`);
+ return convertedImageBlob;
+ } else {
+ return imageBlob;
}
+}
- fileBlob = await convertIfHEIC(file.metadata.title, fileBlob);
- return [fileBlob];
+export async function isFileHEIC(fileBlob: Blob, fileName: string) {
+ const tempFile = new File([fileBlob], fileName);
+ const { exactType } = await getFileType(tempFile);
+ return isExactTypeHEIC(exactType);
+}
+
+export function isExactTypeHEIC(exactType: string) {
+ return (
+ exactType.toLowerCase().endsWith(TYPE_HEIC) ||
+ exactType.toLowerCase().endsWith(TYPE_HEIF)
+ );
}
export async function changeFilesVisibility(
@@ -510,17 +534,15 @@ export async function downloadFiles(files: EnteFile[]) {
}
}
-export function needsConversionForPreview(file: EnteFile) {
- const fileExtension = splitFilenameAndExtension(file.metadata.title)[1];
- if (
+export async function needsConversionForPreview(
+ file: EnteFile,
+ fileBlob: Blob
+) {
+ const isHEIC = await isFileHEIC(fileBlob, file.metadata.title);
+ return (
file.metadata.fileType === FILE_TYPE.LIVE_PHOTO ||
- (file.metadata.fileType === FILE_TYPE.IMAGE &&
- isFileHEIC(fileExtension))
- ) {
- return true;
- } else {
- return false;
- }
+ (file.metadata.fileType === FILE_TYPE.IMAGE && isHEIC)
+ );
}
export const isLivePhoto = (file: EnteFile) =>
diff --git a/src/utils/strings/englishConstants.tsx b/src/utils/strings/englishConstants.tsx
index 37e58b985..91e6fc5a4 100644
--- a/src/utils/strings/englishConstants.tsx
+++ b/src/utils/strings/englishConstants.tsx
@@ -227,7 +227,7 @@ const englishConstants = {
target="_blank"
style={{ color: '#51cd7c' }}
rel="noreferrer">
- android
+ Android
{' '}
or{' '}
- ios app{' '}
+ iOS app{' '}
to automatically backup all your photos
>