From aa80f86a7a9ea2aa96589d36519e62a583b5ece8 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 14:31:29 +0530 Subject: [PATCH] More lints --- web/apps/photos/eslint.config.mjs | 1 - .../Collections/AlbumCastDialog.tsx | 39 ++++++++-------- .../Collections/CollectionShare.tsx | 46 ++++++++++--------- .../DownloadStatusNotifications.tsx | 2 +- .../src/components/FileListWithViewer.tsx | 2 +- web/apps/photos/src/components/Sidebar.tsx | 35 ++++++++------ web/apps/photos/src/components/Upload.tsx | 1 + .../photos/src/components/WatchFolder.tsx | 6 +-- web/apps/photos/src/pages/_app.tsx | 7 ++- web/apps/photos/src/pages/gallery.tsx | 3 ++ web/apps/photos/src/pages/shared-albums.tsx | 3 +- web/apps/photos/src/services/watch.ts | 10 ++-- 12 files changed, 90 insertions(+), 65 deletions(-) diff --git a/web/apps/photos/eslint.config.mjs b/web/apps/photos/eslint.config.mjs index d17d12eed0..3da791ba73 100644 --- a/web/apps/photos/eslint.config.mjs +++ b/web/apps/photos/eslint.config.mjs @@ -11,7 +11,6 @@ export default [ */ "@typescript-eslint/no-unnecessary-condition": "off", /** TODO: Disabled as we migrate, try to prune these again */ - "@typescript-eslint/no-floating-promises": "off", "react-hooks/exhaustive-deps": "off", }, }, diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index f1e300335f..d81658cac5 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -20,6 +20,7 @@ import { loadCast } from "ente-new/photos/utils/chromecast-sender"; import { t } from "i18next"; import React, { useCallback, useEffect, useState } from "react"; import { Trans } from "react-i18next"; +import { z } from "zod/v4"; type AlbumCastDialogProps = ModalVisibilityProps & { /** The collection that we want to cast. */ @@ -114,7 +115,7 @@ export const AlbumCastDialogContents: React.FC = ({ useEffect(() => { if (view == "auto") { - loadCast().then(async (cast) => { + void loadCast().then(async (cast) => { const instance = cast.framework.CastContext.getInstance(); try { await instance.requestSession(); @@ -127,29 +128,24 @@ export const AlbumCastDialogContents: React.FC = ({ session.addMessageListener( "urn:x-cast:pair-request", (_, message) => { - const data = message; - // TODO: - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const obj = JSON.parse(data); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - const code = obj.code; + const { code } = CastPairRequest.parse( + JSON.parse(message), + ); - if (code) { - publishCastPayload(`${code}`, collection) - .then(() => { - setView("choose"); - onClose(); - }) - .catch((e: unknown) => { - log.error("Error casting to TV", e); - setView("auto-cast-error"); - }); - } + void publishCastPayload(code, collection) + .then(() => { + setView("choose"); + onClose(); + }) + .catch((e: unknown) => { + log.error("Error casting to TV", e); + setView("auto-cast-error"); + }); }, ); const collectionID = collection.id; - session + void session .sendMessage("urn:x-cast:pair-request", { collectionID }) .then(() => { log.debug(() => "urn:x-cast:pair-request sent"); @@ -252,3 +248,8 @@ export const AlbumCastDialogContents: React.FC = ({ ); }; + +/** + * Zod schema for the "x-cast:pair-request" payload sent by the cast app. + */ +const CastPairRequest = z.object({ code: z.string() }); diff --git a/web/apps/photos/src/components/Collections/CollectionShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare.tsx index e09fe29fb4..8e3a05ae8c 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare.tsx @@ -627,7 +627,19 @@ const AddParticipantForm: React.FC = ({ }); const resetExistingSelection = () => - formik.setFieldValue("selectedEmails", []); + void formik.setFieldValue("selectedEmails", []); + + const createToggleEmail = (email: string) => { + return () => { + const emails = formik.values.selectedEmails; + void formik.setFieldValue( + "selectedEmails", + emails.includes(email) + ? emails.filter((e) => e != email) + : emails.concat(email), + ); + }; + }; return (
@@ -661,18 +673,7 @@ const AddParticipantForm: React.FC = ({ { - const emails = - formik.values.selectedEmails; - formik.setFieldValue( - "selectedEmails", - emails.includes(email) - ? emails.filter( - (e) => e != email, - ) - : emails.concat(email), - ); - }} + onClick={createToggleEmail(email)} label={email} startIcon={ = ({ const selectAndManageParticipant = useCallback( (email: string) => { setSelectedParticipant( - collection.sharees.find((sharee) => sharee.email === email), + collection.sharees.find((sharee) => sharee.email == email), ); showManageParticipant(); }, - [showManageParticipant], + [collection, showManageParticipant], ); const handleRootClose = () => { @@ -1120,16 +1121,17 @@ const PublicShare: React.FC = ({ useEffect(() => { if (publicURL?.url) { - appendCollectionKeyToShareURL(publicURL.url, collection.key).then( - (url) => setResolvedURL(url), - ); + void appendCollectionKeyToShareURL( + publicURL.url, + collection.key, + ).then((url) => setResolvedURL(url)); } else { setResolvedURL(undefined); } }, [publicURL]); const handleCopyLink = () => { - if (resolvedURL) navigator.clipboard.writeText(resolvedURL); + if (resolvedURL) void navigator.clipboard.writeText(resolvedURL); }; return ( @@ -1475,7 +1477,7 @@ const ManagePublicCollect: React.FC = ({ onUpdate, }) => { const handleFileDownloadSetting = () => { - onUpdate({ enableCollect: !publicURL.enableCollect }); + void onUpdate({ enableCollect: !publicURL.enableCollect }); }; return ( @@ -1666,7 +1668,9 @@ const ManageDownloadAccess: React.FC = ({ }, }); } else { - onUpdate({ enableDownload: true }); + // TODO: Various calls to onUpdate return promises. The UI should + // handle the in-progress states where needed. + void onUpdate({ enableDownload: true }); } }; diff --git a/web/apps/photos/src/components/DownloadStatusNotifications.tsx b/web/apps/photos/src/components/DownloadStatusNotifications.tsx index f7a667463e..40d8f7a28d 100644 --- a/web/apps/photos/src/components/DownloadStatusNotifications.tsx +++ b/web/apps/photos/src/components/DownloadStatusNotifications.tsx @@ -75,7 +75,7 @@ export const DownloadStatusNotifications: React.FC< const createOnClick = (group: SaveGroup) => () => { const electron = globalThis.electron; if (electron && group.downloadDirPath) { - electron.openDirectory(group.downloadDirPath); + void electron.openDirectory(group.downloadDirPath); } else if (onShowCollectionSummary) { onShowCollectionSummary( group.collectionSummaryID, diff --git a/web/apps/photos/src/components/FileListWithViewer.tsx b/web/apps/photos/src/components/FileListWithViewer.tsx index 92c81df25f..13ebc7c5c9 100644 --- a/web/apps/photos/src/components/FileListWithViewer.tsx +++ b/web/apps/photos/src/components/FileListWithViewer.tsx @@ -167,7 +167,7 @@ export const FileListWithViewer: React.FC = ({ (editedFile: File, collection: Collection, enteFile: EnteFile) => { uploadManager.prepareForNewUpload(); uploadManager.showUploadProgressDialog(); - uploadManager.uploadFile(editedFile, collection, enteFile); + void uploadManager.uploadFile(editedFile, collection, enteFile); }, [], ); diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index eeb8a1f2ca..308334a4ac 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -265,6 +265,10 @@ const UserDetailsSection: React.FC = ({ isSubscriptionStripe(userDetails.subscription) && isSubscriptionPastDue(userDetails.subscription) ) { + // TODO: This makes an API request, so the UI should indicate + // the await. + // + // eslint-disable-next-line @typescript-eslint/no-floating-promises redirectToCustomerPortal(); } else { onShowPlanSelector(); @@ -337,6 +341,7 @@ const SubscriptionStatus: React.FC = ({ isSubscriptionStripe(userDetails.subscription) && isSubscriptionPastDue(userDetails.subscription) ) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises redirectToCustomerPortal(); } else { onShowPlanSelector(); @@ -851,16 +856,17 @@ const LanguageSelector = () => { const locale = getLocaleInUse(); const updateCurrentLocale = (newLocale: SupportedLocale) => { - setLocaleInUse(newLocale); - // [Note: Changing locale causes a full reload] - // - // A full reload is needed because we use the global `t` instance - // instead of the useTranslation hook. - // - // We also rely on this behaviour by caching various formatters in - // module static variables that not get updated if the i18n.language - // changes unless there is a full reload. - window.location.reload(); + void setLocaleInUse(newLocale).then(() => { + // [Note: Changing locale causes a full reload] + // + // A full reload is needed because we use the global `t` instance + // instead of the useTranslation hook. + // + // We also rely on this behaviour by caching various formatters in + // module static variables that not get updated if the i18n.language + // changes unless there is a full reload. + window.location.reload(); + }); }; const options = supportedLocales.map((locale) => ({ @@ -1088,11 +1094,14 @@ const Help: React.FC = ({ continue: { text: t("view_logs"), action: viewLogs }, }); - const viewLogs = () => { + const viewLogs = async () => { log.info("Viewing logs"); const electron = globalThis.electron; - if (electron) electron.openLogDirectory(); - else saveStringAsFile(savedLogs(), `ente-web-logs-${Date.now()}.txt`); + if (electron) { + await electron.openLogDirectory(); + } else { + saveStringAsFile(savedLogs(), `ente-web-logs-${Date.now()}.txt`); + } }; return ( diff --git a/web/apps/photos/src/components/Upload.tsx b/web/apps/photos/src/components/Upload.tsx index 610df422da..30b31cc8d3 100644 --- a/web/apps/photos/src/components/Upload.tsx +++ b/web/apps/photos/src/components/Upload.tsx @@ -1,6 +1,7 @@ // TODO: Too many null assertions in this file. The types need reworking. // TODO: Audit this file /* eslint-disable @typescript-eslint/no-misused-promises */ +/* eslint-disable @typescript-eslint/no-floating-promises */ import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import DiscFullIcon from "@mui/icons-material/DiscFull"; import GoogleIcon from "@mui/icons-material/Google"; diff --git a/web/apps/photos/src/components/WatchFolder.tsx b/web/apps/photos/src/components/WatchFolder.tsx index f7c46085be..164db56c89 100644 --- a/web/apps/photos/src/components/WatchFolder.tsx +++ b/web/apps/photos/src/components/WatchFolder.tsx @@ -53,7 +53,7 @@ export const WatchFolder: React.FC = ({ useModalVisibility(); useEffect(() => { - watcher.getWatches().then((ws) => setWatches(ws)); + void watcher.getWatches().then((ws) => setWatches(ws)); }, []); useEffect(() => { @@ -87,7 +87,7 @@ export const WatchFolder: React.FC = ({ const selectCollectionMappingAndAddWatch = async (path: string) => { const filePaths = await ensureElectron().fs.findFiles(path); if (areAllInSameDirectory(filePaths)) { - addWatch(path, "root"); + await addWatch(path, "root"); } else { setSavedFolderPath(path); showMappingChoice(); @@ -109,7 +109,7 @@ export const WatchFolder: React.FC = ({ const handleCollectionMappingSelect = (mapping: CollectionMapping) => { setSavedFolderPath(undefined); - addWatch(savedFolderPath!, mapping); + void addWatch(savedFolderPath!, mapping); }; return ( diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index 3ae9eb6abe..9310e89365 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -89,8 +89,11 @@ const App: React.FC = ({ Component, pageProps }) => { // the user is logged in. const handleOpenEnteURL = (url: string) => { - if (url.startsWith("ente://app")) router.push(url); - else log.info(`Ignoring unhandled open request for URL ${url}`); + if (url.startsWith("ente://app")) { + void router.push(url); + } else { + log.info(`Ignoring unhandled open request for URL ${url}`); + } }; const showUpdateDialog = (update: AppUpdate) => { diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 9a47329755..b471f67846 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -1,3 +1,6 @@ +// TODO: Audit this file (the code here is mostly fine, but needs revisiting +// the file it depends on have been audited and their interfaces fixed). +/* eslint-disable @typescript-eslint/no-floating-promises */ import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import FileUploadOutlinedIcon from "@mui/icons-material/FileUploadOutlined"; import MenuIcon from "@mui/icons-material/Menu"; diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 5ae52db264..cd6b842e7f 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -1,4 +1,5 @@ -// TODO: Audit this file (too many null assertions) +// TODO: Audit this file (too many null assertions + other issues) +/* eslint-disable @typescript-eslint/no-floating-promises */ import AddPhotoAlternateOutlinedIcon from "@mui/icons-material/AddPhotoAlternateOutlined"; import CloseIcon from "@mui/icons-material/Close"; import DownloadIcon from "@mui/icons-material/Download"; diff --git a/web/apps/photos/src/services/watch.ts b/web/apps/photos/src/services/watch.ts index 536805347b..f2fec3a5dd 100644 --- a/web/apps/photos/src/services/watch.ts +++ b/web/apps/photos/src/services/watch.ts @@ -96,7 +96,7 @@ class FolderWatcher { this.upload = upload; this.onTriggerRemotePull = onTriggerRemotePull; this.registerListeners(); - this.syncWithDisk(); + this.triggerSyncWithDisk(); } /** Return `true` if we are currently using the uploader. */ @@ -126,7 +126,7 @@ class FolderWatcher { */ resumePausedSync() { this.isPaused = false; - this.syncWithDisk(); + this.triggerSyncWithDisk(); } /** Return the list of folders we are watching for changes. */ @@ -152,7 +152,7 @@ class FolderWatcher { */ async addWatch(folderPath: string, mapping: CollectionMapping) { const watches = await ensureElectron().watch.add(folderPath, mapping); - this.syncWithDisk(); + this.triggerSyncWithDisk(); return watches; } @@ -165,6 +165,10 @@ class FolderWatcher { return await ensureElectron().watch.remove(folderPath); } + private triggerSyncWithDisk() { + void this.syncWithDisk(); + } + private async syncWithDisk() { try { const watches = await this.getWatches();