[web] Miscellaneous tweaks (#1363)

- Clean up environment detection code
- Remove l11n unsafe direct string manipulation
- Inline
- Remove isCanvasBlocked checker
- Remove unused stuff
This commit is contained in:
Manav Rathi 2024-04-08 09:38:59 +05:30 committed by GitHub
commit 873b158718
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 103 additions and 252 deletions

View file

@ -1,4 +1,3 @@
import { FACE_SEARCH_PRIVACY_POLICY_LINK } from "@ente/shared/constants/urls";
import {
Button,
Checkbox,
@ -60,8 +59,8 @@ export default function EnableFaceSearch({
components={{
a: (
<Link
target={"_blank"}
href={FACE_SEARCH_PRIVACY_POLICY_LINK}
target="_blank"
href="https://ente.io/privacy#8-biometric-information-privacy-policy"
underline="always"
sx={{
color: "inherit",

View file

@ -1,4 +1,3 @@
import { ML_BLOG_LINK } from "@ente/shared/constants/urls";
import { Box, Button, Stack, Typography } from "@mui/material";
import Titlebar from "components/Titlebar";
import { t } from "i18next";
@ -10,6 +9,9 @@ export default function EnableMLSearch({
enableMlSearch,
onRootClose,
}) {
const showDetails = () =>
openLink("https://ente.io/blog/desktop-ml-beta", true);
return (
<Stack spacing={"4px"} py={"12px"}>
<Titlebar
@ -33,9 +35,9 @@ export default function EnableMLSearch({
{t("ENABLE")}
</Button>
<Button
color={"secondary"}
color="secondary"
size="large"
onClick={() => openLink(ML_BLOG_LINK, true)}
onClick={showDetails}
>
{t("ML_MORE_DETAILS")}
</Button>

View file

@ -1,13 +1,13 @@
import { haveWindow } from "@/next/env";
import { styled } from "@mui/material";
import { useEffect, useRef } from "react";
import { runningInBrowser } from "utils/common";
import { MapButton } from "./MapButton";
import { t } from "i18next";
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css"; // Re-uses images from ~leaflet package
import "leaflet/dist/leaflet.css";
runningInBrowser() && require("leaflet-defaulticon-compatibility");
const L = runningInBrowser()
haveWindow() && require("leaflet-defaulticon-compatibility");
const L = haveWindow()
? (require("leaflet") as typeof import("leaflet"))
: null;

View file

@ -2,10 +2,6 @@ import { t } from "i18next";
import { useContext } from "react";
import EnteSpinner from "@ente/shared/components/EnteSpinner";
import {
DESKTOP_ROADMAP_URL,
WEB_ROADMAP_URL,
} from "@ente/shared/constants/urls";
import { Typography } from "@mui/material";
import { EnteMenuItem } from "components/Menu/EnteMenuItem";
import { NoStyleAnchor } from "components/pages/sharedAlbum/GoToEnte";
@ -20,17 +16,12 @@ export default function HelpSection() {
const { setDialogMessage } = useContext(AppContext);
const { openExportModal } = useContext(GalleryContext);
async function openRoadmap() {
let roadmapURL: string;
if (isElectron()) {
roadmapURL = DESKTOP_ROADMAP_URL;
} else {
roadmapURL = WEB_ROADMAP_URL;
}
openLink(roadmapURL, true);
}
const openRoadmap = () =>
openLink("https://github.com/ente-io/ente/discussions", true);
function handleExportOpen() {
const contactSupport = () => openLink("mailto:support@ente.io", true);
function openExport() {
if (isElectron()) {
openExportModal();
} else {
@ -46,7 +37,7 @@ export default function HelpSection() {
variant="secondary"
/>
<EnteMenuItem
onClick={() => openLink("mailto:support@ente.io", true)}
onClick={contactSupport}
labelComponent={
<NoStyleAnchor href="mailto:support@ente.io">
<Typography fontWeight={"bold"}>
@ -57,7 +48,7 @@ export default function HelpSection() {
variant="secondary"
/>
<EnteMenuItem
onClick={handleExportOpen}
onClick={openExport}
label={t("EXPORT")}
endIcon={
exportService.isExportInProgress() && (

View file

@ -2,7 +2,6 @@ import { Dialog, DialogContent, Link } from "@mui/material";
import { t } from "i18next";
import { dialogCloseHandler } from "@ente/shared/components/DialogBox/TitleWithCloseButton";
import { APP_DOWNLOAD_URL } from "@ente/shared/constants/urls";
import { UPLOAD_RESULT, UPLOAD_STAGES } from "constants/upload";
import UploadProgressContext from "contexts/uploadProgress";
import { useContext, useEffect, useState } from "react";
@ -84,7 +83,7 @@ export function UploadProgressDialog() {
components={{
a: (
<Link
href={APP_DOWNLOAD_URL}
href="https://ente.io/download/desktop"
target="_blank"
/>
),

View file

@ -1,15 +1,8 @@
import { useContext, useEffect, useRef, useState } from "react";
import { t } from "i18next";
import { Trans } from "react-i18next";
import { getLatestCollections } from "services/collectionService";
import UploadProgress from "./UploadProgress";
import ElectronAPIs from "@ente/shared/electron";
import { CustomError } from "@ente/shared/error";
import { addLogLine } from "@ente/shared/logging";
import { logError } from "@ente/shared/sentry";
import { isPromise } from "@ente/shared/utils";
import DiscFullIcon from "@mui/icons-material/DiscFull";
import UserNameInputDialog from "components/UserNameInputDialog";
import {
@ -18,10 +11,13 @@ import {
UPLOAD_STAGES,
UPLOAD_STRATEGY,
} from "constants/upload";
import { t } from "i18next";
import isElectron from "is-electron";
import { AppContext } from "pages/_app";
import { GalleryContext } from "pages/gallery";
import { useContext, useEffect, useRef, useState } from "react";
import billingService from "services/billingService";
import { getLatestCollections } from "services/collectionService";
import ImportService from "services/importService";
import {
getPublicCollectionUID,
@ -52,7 +48,6 @@ import {
UploadFileNames,
} from "types/upload/ui";
import { getOrCreateAlbum } from "utils/collection";
import { downloadApp, waitAndRun } from "utils/common";
import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery";
import {
getDownloadAppMessage,
@ -63,8 +58,8 @@ import {
getImportSuggestion,
groupFilesBasedOnParentFolder,
} from "utils/upload";
import { isCanvasBlocked } from "utils/upload/isCanvasBlocked";
import { SetCollectionNamerAttributes } from "../Collections/CollectionNamer";
import UploadProgress from "./UploadProgress";
import UploadStrategyChoiceModal from "./UploadStrategyChoiceModal";
import UploadTypeSelector from "./UploadTypeSelector";
@ -312,21 +307,6 @@ export default function Uploader(props: Props) {
return;
}
}
if (isCanvasBlocked()) {
addLogLine("canvas blocked, blocking upload");
appContext.setDialogMessage({
title: t("CANVAS_BLOCKED_TITLE"),
content: <Trans i18nKey="CANVAS_BLOCKED_MESSAGE" />,
close: { text: t("CLOSE") },
proceed: {
text: t("DOWNLOAD"),
action: downloadApp,
variant: "accent",
},
});
return;
}
uploadRunning.current = true;
props.closeUploadTypeSelector();
props.setLoading(true);
@ -844,3 +824,13 @@ export default function Uploader(props: Props) {
</>
);
}
async function waitAndRun(
waitPromise: Promise<void>,
task: () => Promise<void>,
) {
if (waitPromise && isPromise(waitPromise)) {
await waitPromise;
}
await task();
}

View file

@ -24,7 +24,6 @@ import {
planForSubscription,
updateSubscription,
} from "utils/billing";
import { reverseString } from "utils/common";
import { getLocalUserDetails } from "utils/user";
import { getTotalFamilyUsage, isPartOfFamily } from "utils/user/family";
import FreeSubscriptionPlanSelectorCard from "./free";
@ -130,9 +129,7 @@ function PlanSelectorCard(props: Props) {
}
} else if (hasStripeSubscription(subscription)) {
appContext.setDialogMessage({
title: `${t("CONFIRM")} ${reverseString(
t("UPDATE_SUBSCRIPTION"),
)}`,
title: t("update_subscription_title"),
content: t("UPDATE_SUBSCRIPTION_MESSAGE"),
proceed: {
text: t("UPDATE_SUBSCRIPTION"),

View file

@ -49,7 +49,7 @@ import {
syncMapEnabled,
validateKey,
} from "services/userService";
import { mergeMaps, preloadImage } from "utils/common";
import { preloadImage } from "utils/common";
import {
FILE_OPS_TYPE,
constructFileToCollectionMap,
@ -135,7 +135,6 @@ import {
import { Search, SearchResultSummary, UpdateSearch } from "types/search";
import { FamilyData } from "types/user";
import ComlinkSearchWorker from "utils/comlink/ComlinkSearchWorker";
import { checkConnectivity } from "utils/common";
import { isArchivedFile } from "utils/magicMetadata";
import { getSessionExpiredMessage } from "utils/ui";
import { getLocalFamilyData } from "utils/user/family";
@ -680,13 +679,13 @@ export default function Gallery() {
};
const syncWithRemote = async (force = false, silent = false) => {
if (!navigator.onLine) return;
if (syncInProgress.current && !force) {
resync.current = { force, silent };
return;
}
syncInProgress.current = true;
try {
checkConnectivity();
const token = getToken();
if (!token) {
return;
@ -719,8 +718,6 @@ export default function Gallery() {
clearKeys();
router.push(PAGES.CREDENTIALS);
break;
case CustomError.NO_INTERNET_CONNECTION:
break;
default:
logError(e, "syncWithRemote failed");
}
@ -1252,3 +1249,11 @@ function useEffectSingleThreaded(
main(deps);
}, deps);
}
const mergeMaps = <K, V>(map1: Map<K, V>, map2: Map<K, V>) => {
const mergedMap = new Map<K, V>(map1);
map2.forEach((value, key) => {
mergedMap.set(key, value);
});
return mergedMap;
};

View file

@ -1,4 +1,3 @@
import { CITIES_URL } from "@ente/shared/constants/urls";
import { logError } from "@ente/shared/sentry";
import { LocationTagData } from "types/entity";
import { Location } from "types/upload";
@ -22,7 +21,9 @@ class LocationSearchService {
if (this.citiesPromise) {
return;
}
this.citiesPromise = fetch(CITIES_URL).then((response) => {
this.citiesPromise = fetch(
"https://static.ente.io/world_cities.json",
).then((response) => {
return response.json().then((data) => {
this.cities = data["data"];
});

View file

@ -1,3 +1,4 @@
import { haveWindow } from "@/next/env";
import { getDedicatedCryptoWorker } from "@ente/shared/crypto";
import { DedicatedCryptoWorker } from "@ente/shared/crypto/internal/crypto.worker";
import { addLogLine } from "@ente/shared/logging";
@ -24,7 +25,6 @@ import {
SceneDetectionMethod,
SceneDetectionService,
} from "types/machineLearning";
import { getConcurrency } from "utils/common/concurrency";
import { logQueueStats } from "utils/machineLearning";
import arcfaceAlignmentService from "./arcfaceAlignmentService";
import arcfaceCropService from "./arcfaceCropService";
@ -232,3 +232,6 @@ export class LocalMLSyncContext implements MLSyncContext {
}
}
}
export const getConcurrency = () =>
haveWindow() && Math.max(2, Math.ceil(navigator.hardwareConcurrency / 2));

View file

@ -1,6 +1,6 @@
import { haveWindow } from "@/next/env";
import { ComlinkWorker } from "@ente/shared/worker/comlinkWorker";
import { Remote } from "comlink";
import { runningInBrowser } from "utils/common";
import { DedicatedConvertWorker } from "worker/convert.worker";
class ComlinkConvertWorker {
@ -16,7 +16,7 @@ class ComlinkConvertWorker {
}
export const getDedicatedConvertWorker = () => {
if (runningInBrowser()) {
if (haveWindow()) {
const cryptoComlinkWorker = new ComlinkWorker<
typeof DedicatedConvertWorker
>(

View file

@ -1,9 +1,9 @@
import { haveWindow } from "@/next/env";
import { ComlinkWorker } from "@ente/shared/worker/comlinkWorker";
import { runningInBrowser } from "utils/common";
import { DedicatedMLWorker } from "worker/ml.worker";
export const getDedicatedMLWorker = (name: string) => {
if (runningInBrowser()) {
if (haveWindow()) {
const cryptoComlinkWorker = new ComlinkWorker<typeof DedicatedMLWorker>(
name ?? "ente-ml-worker",
new Worker(new URL("worker/ml.worker.ts", import.meta.url)),

View file

@ -1,6 +1,6 @@
import { haveWindow } from "@/next/env";
import { ComlinkWorker } from "@ente/shared/worker/comlinkWorker";
import { Remote } from "comlink";
import { runningInBrowser } from "utils/common";
import { DedicatedSearchWorker } from "worker/search.worker";
class ComlinkSearchWorker {
@ -16,7 +16,7 @@ class ComlinkSearchWorker {
}
export const getDedicatedSearchWorker = () => {
if (runningInBrowser()) {
if (haveWindow()) {
const cryptoComlinkWorker = new ComlinkWorker<
typeof DedicatedSearchWorker
>(

View file

@ -1,5 +0,0 @@
import { runningInBrowser } from ".";
export const getConcurrency = () =>
runningInBrowser() &&
Math.max(2, Math.ceil(navigator.hardwareConcurrency / 2));

View file

@ -1,65 +1,3 @@
import { APP_DOWNLOAD_URL } from "@ente/shared/constants/urls";
import { CustomError } from "@ente/shared/error";
import { isPromise } from "@ente/shared/utils";
import isElectron from "is-electron";
export function checkConnectivity() {
if (navigator.onLine) {
return true;
}
throw new Error(CustomError.NO_INTERNET_CONNECTION);
}
export function runningInBrowser() {
return typeof window !== "undefined";
}
export function runningInWorker() {
return typeof importScripts === "function";
}
export function runningInElectron() {
return isElectron();
}
export function runningInChrome(includeMobile: boolean) {
try {
const userAgentData = navigator["userAgentData"];
const chromeBrand = userAgentData?.brands?.filter(
(b) => b.brand === "Google Chrome" || b.brand === "Chromium",
)?.[0];
return chromeBrand && (includeMobile || userAgentData.mobile === false);
} catch (error) {
console.error("Error in runningInChrome: ", error);
return false;
}
}
export function offscreenCanvasSupported() {
return !(typeof OffscreenCanvas === "undefined");
}
export function webglSupported() {
try {
const canvas = document.createElement("canvas");
const gl = canvas.getContext("webgl");
return gl && gl instanceof WebGLRenderingContext;
} catch (error) {
console.error("Error in webglSupported: ", error);
return false;
}
}
export function downloadApp() {
openLink(APP_DOWNLOAD_URL, true);
}
export function reverseString(title: string) {
return title
?.split(" ")
.reduce((reversedString, currWord) => `${currWord} ${reversedString}`);
}
export function initiateEmail(email: string) {
const a = document.createElement("a");
a.href = "mailto:" + email;
@ -74,6 +12,7 @@ export const preloadImage = (imgBasePath: string) => {
}
new Image().srcset = srcSet.join(",");
};
export function openLink(href: string, newTab?: boolean) {
const a = document.createElement("a");
a.href = href;
@ -84,16 +23,6 @@ export function openLink(href: string, newTab?: boolean) {
a.click();
}
export async function waitAndRun(
waitPromise: Promise<void>,
task: () => Promise<void>,
) {
if (waitPromise && isPromise(waitPromise)) {
await waitPromise;
}
await task();
}
export function isClipboardItemPresent() {
return typeof ClipboardItem !== "undefined";
}
@ -105,11 +34,3 @@ export function batch<T>(arr: T[], batchSize: number): T[][] {
}
return batches;
}
export const mergeMaps = <K, V>(map1: Map<K, V>, map2: Map<K, V>) => {
const mergedMap = new Map<K, V>(map1);
map2.forEach((value, key) => {
mergedMap.set(key, value);
});
return mergedMap;
};

View file

@ -1,3 +1,4 @@
import { haveWindow } from "@/next/env";
import { addLogLine } from "@ente/shared/logging";
import { logError } from "@ente/shared/sentry";
import {
@ -14,6 +15,7 @@ import {
deleteDB,
openDB,
} from "idb";
import isElectron from "is-electron";
import {
Face,
MLLibraryData,
@ -23,7 +25,6 @@ import {
Thing,
} from "types/machineLearning";
import { IndexStatus } from "types/machineLearning/ui";
import { runningInBrowser, runningInElectron } from "utils/common";
interface Config {}
@ -64,7 +65,7 @@ class MLIDbStorage {
public _db: Promise<IDBPDatabase<MLDb>>;
constructor() {
if (!runningInBrowser() || !runningInElectron()) {
if (!haveWindow() || !isElectron()) {
return;
}

View file

@ -1,16 +1,16 @@
import { DialogBoxAttributes } from "@ente/shared/components/DialogBox/types";
import AutoAwesomeOutlinedIcon from "@mui/icons-material/AutoAwesomeOutlined";
import { t } from "i18next";
import { downloadApp } from "utils/common";
import { logoutUser } from "@ente/accounts/services/user";
import { DialogBoxAttributes } from "@ente/shared/components/DialogBox/types";
import ElectronAPIs from "@ente/shared/electron";
import { AppUpdateInfo } from "@ente/shared/electron/types";
import AutoAwesomeOutlinedIcon from "@mui/icons-material/AutoAwesomeOutlined";
import InfoOutlined from "@mui/icons-material/InfoRounded";
import { Link } from "@mui/material";
import { OPEN_STREET_MAP_LINK } from "components/Sidebar/EnableMap";
import { t } from "i18next";
import { Trans } from "react-i18next";
import { Subscription } from "types/billing";
import { openLink } from "utils/common";
export const getDownloadAppMessage = (): DialogBoxAttributes => {
return {
title: t("DOWNLOAD_APP"),
@ -27,6 +27,8 @@ export const getDownloadAppMessage = (): DialogBoxAttributes => {
};
};
const downloadApp = () => openLink("https://ente.io/download/desktop", true);
export const getTrashFilesMessage = (
deleteFileHelper,
): DialogBoxAttributes => ({

View file

@ -1,53 +0,0 @@
//
// Canvas Blocker &
// Firefox privacy.resistFingerprinting Detector.
// (c) 2018 // JOHN OZBAY // CRYPT.EE
// MIT License
// Credits: https://github.com/johnozbay/canvas-block-detector/blob/master/isCanvasBlocked.js
//
export function isCanvasBlocked() {
// create a 1px image data
let blocked = false;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
// some blockers just return an undefined ctx. So let's check that first.
if (ctx) {
const imageData = ctx.createImageData(1, 1);
const originalImageData = imageData.data;
// set pixels to RGB 128
originalImageData[0] = 128;
originalImageData[1] = 128;
originalImageData[2] = 128;
originalImageData[3] = 255;
// set this to canvas
ctx.putImageData(imageData, 1, 1);
try {
// now get the data back from canvas.
const checkData = ctx.getImageData(1, 1, 1, 1).data;
// If this is firefox, and privacy.resistFingerprinting is enabled,
// OR a browser extension blocking the canvas,
// This will return RGB all white (255,255,255) instead of the (128,128,128) we put.
// so let's check the R and G to see if they're 255 or 128 (matching what we've initially set)
if (
originalImageData[0] !== checkData[0] &&
originalImageData[1] !== checkData[1]
) {
blocked = true;
}
} catch (error) {
// some extensions will return getImageData null. this is to account for that.
blocked = true;
}
} else {
blocked = true;
}
return blocked;
}

View file

@ -10,3 +10,28 @@
* all other commands.
*/
export const isDevBuild = process.env.NODE_ENV === "development";
/**
* `true` if we're running in the default global context (aka the main thread)
* of a web browser.
*
* In particular, this is `false` when we're running in a Web Worker,
* irrespecitve of whether the worker is running in a Node.js context or a web
* browser context.
*
* > We can be running in a browser context either if the user has the page open
* in a web browser, or if we're the renderer process of an Electron app.
*
* Note that this cannot be a constant, otherwise it'll get inlined during SSR
* with the wrong value.
*/
export const haveWindow = () => typeof window !== "undefined";
/**
* Return true if we are running in a [Web
* Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API)
*
* Note that this cannot be a constant, otherwise it'll get inlined during SSR
* with the wrong value.
*/
export const inWorker = () => typeof importScripts === "function";

View file

@ -168,6 +168,7 @@
"UPDATE_PAYMENT_METHOD": "Update payment method",
"MONTHLY": "Monthly",
"YEARLY": "Yearly",
"update_subscription_title": "Confirm plan change",
"UPDATE_SUBSCRIPTION_MESSAGE": "Are you sure you want to change your plan?",
"UPDATE_SUBSCRIPTION": "Change plan",
"CANCEL_SUBSCRIPTION": "Cancel subscription",
@ -421,8 +422,6 @@
"ENTER_TWO_FACTOR_OTP": "Enter the 6-digit code from your authenticator app.",
"CREATE_ACCOUNT": "Create account",
"COPIED": "Copied",
"CANVAS_BLOCKED_TITLE": "Unable to generate thumbnail",
"CANVAS_BLOCKED_MESSAGE": "<p>It looks like your browser has disabled access to canvas, which is necessary to generate thumbnails for your photos </p> <p> Please enable access to your browser's canvas, or check out our desktop app</p>",
"WATCH_FOLDERS": "Watch folders",
"UPGRADE_NOW": "Upgrade now",
"RENEW_NOW": "Renew now",

View file

@ -10,6 +10,7 @@
"get-user-locale": "^2.3",
"i18next": "^23.10",
"i18next-resources-to-backend": "^1.2.0",
"is-electron": "^2.2",
"next": "^14.1",
"react": "^18",
"react-dom": "^18",

View file

@ -1,5 +1,9 @@
{
"extends": "@/build-config/tsconfig-typecheck.json",
"compilerOptions": {
/* Also indicate expectation of a WebWorker runtime */
"lib": ["ESnext", "DOM", "DOM.Iterable", "WebWorker"]
},
/* Typecheck all files with the given extensions (here or in subfolders) */
"include": ["**/*.ts", "**/*.tsx"]
}

View file

@ -1,21 +1,5 @@
export const ENTE_WEBSITE_LINK = "https://ente.io";
export const ML_BLOG_LINK = "https://ente.io/blog/desktop-ml-beta";
export const FACE_SEARCH_PRIVACY_POLICY_LINK =
"https://ente.io/privacy#8-biometric-information-privacy-policy";
export const SUPPORT_EMAIL = "support@ente.io";
export const APP_DOWNLOAD_URL = "https://ente.io/download/desktop";
export const FEEDBACK_EMAIL = "feedback@ente.io";
export const DELETE_ACCOUNT_EMAIL = "account-deletion@ente.io";
export const WEB_ROADMAP_URL = "https://github.com/ente-io/ente/discussions";
export const DESKTOP_ROADMAP_URL =
"https://github.com/ente-io/ente/discussions";
export const CITIES_URL = "https://static.ente.io/world_cities.json";

View file

@ -1,4 +1,4 @@
import { runningInWorker } from "@ente/shared/platform";
import { inWorker } from "@/next/env";
import * as Comlink from "comlink";
import { wrap } from "comlink";
import { ElectronAPIsType } from "./types";
@ -17,7 +17,7 @@ class WorkerSafeElectronServiceImpl implements LimitedElectronAPIs {
this.ready = this.init();
}
private async init() {
if (runningInWorker()) {
if (inWorker()) {
const workerSafeElectronClient =
wrap<typeof WorkerSafeElectronClient>(self);

View file

@ -70,7 +70,6 @@ export const CustomError = {
EXPORT_STOPPED: "export stopped",
NO_EXPORT_FOLDER_SELECTED: "no export folder selected",
EXPORT_FOLDER_DOES_NOT_EXIST: "export folder does not exist",
NO_INTERNET_CONNECTION: "no internet connection",
AUTH_KEY_NOT_FOUND: "auth key not found",
EXIF_DATA_NOT_FOUND: "exif data not found",
SELECT_FOLDER_ABORTED: "select folder aborted",

View file

@ -1,13 +0,0 @@
import isElectron from "is-electron";
export function runningInBrowser() {
return typeof window !== "undefined";
}
export function runningInWorker() {
return typeof importScripts === "function";
}
export function runningInElectron() {
return isElectron();
}

View file

@ -1,8 +1,8 @@
import { runningInBrowser } from "../../platform";
import { haveWindow } from "@/next/env";
import localForage from "localforage";
if (runningInBrowser()) {
if (haveWindow()) {
localForage.config({
name: "ente-files",
version: 1.0,

View file

@ -3,7 +3,6 @@
"version": "0.0.0",
"private": true,
"dependencies": {
"is-electron": "^2.2",
"libsodium-wrappers": "0.7.9",
"yup": "^1.4"
},