From 2bbf33287bfa73f4e57af8350fdb9375f9cba570 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 13 Jun 2024 16:00:57 +0530 Subject: [PATCH] Handle nightly builds (or versions without changelogs) --- desktop/src/main.ts | 2 - desktop/src/main/ipc.ts | 36 ++++++++--------- desktop/src/main/services/app-update.ts | 23 ----------- desktop/src/main/services/store.ts | 12 +++++- desktop/src/main/stores/user-preferences.ts | 8 ++-- desktop/src/preload.ts | 21 ++++------ web/apps/photos/src/pages/gallery/index.tsx | 7 +--- .../new/photos/components/WhatsNew.tsx | 11 ++++-- web/packages/new/photos/services/changelog.ts | 39 +++++++++++++++++++ web/packages/next/types/ipc.ts | 31 +++++++-------- 10 files changed, 102 insertions(+), 88 deletions(-) create mode 100644 web/packages/new/photos/services/changelog.ts diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 768e1a1df4..50f69759da 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -21,7 +21,6 @@ import { attachFSWatchIPCHandlers, attachIPCHandlers, attachLogoutIPCHandler, - attachWindowIPCHandlers, } from "./main/ipc"; import log, { initLogging } from "./main/log"; import { createApplicationMenu, createTrayContextMenu } from "./main/menu"; @@ -117,7 +116,6 @@ const main = () => { // Setup IPC and streams. const watcher = createWatcher(mainWindow); attachIPCHandlers(); - attachWindowIPCHandlers(mainWindow); attachFSWatchIPCHandlers(watcher); attachLogoutIPCHandler(watcher); registerStreamProtocol(); diff --git a/desktop/src/main/ipc.ts b/desktop/src/main/ipc.ts index 303ddb6dbd..55f5f8530e 100644 --- a/desktop/src/main/ipc.ts +++ b/desktop/src/main/ipc.ts @@ -9,7 +9,7 @@ */ import type { FSWatcher } from "chokidar"; -import { ipcMain, type BrowserWindow } from "electron/main"; +import { ipcMain } from "electron/main"; import type { CollectionMapping, FolderWatch, @@ -19,8 +19,6 @@ import type { import { logToDisk } from "./log"; import { appVersion, - canShowWhatsNew, - didShowWhatsNew, skipAppUpdate, updateAndRestart, updateOnNextRestart, @@ -48,7 +46,12 @@ import { computeCLIPTextEmbeddingIfAvailable, } from "./services/ml-clip"; import { computeFaceEmbeddings, detectFaces } from "./services/ml-face"; -import { encryptionKey, saveEncryptionKey } from "./services/store"; +import { + encryptionKey, + lastShownChangelogVersion, + saveEncryptionKey, + setLastShownChangelogVersion, +} from "./services/store"; import { clearPendingUploads, listZipItems, @@ -103,11 +106,19 @@ export const attachIPCHandlers = () => { ipcMain.handle("selectDirectory", () => selectDirectory()); + ipcMain.handle("encryptionKey", () => encryptionKey()); + ipcMain.handle("saveEncryptionKey", (_, encryptionKey: string) => saveEncryptionKey(encryptionKey), ); - ipcMain.handle("encryptionKey", () => encryptionKey()); + ipcMain.handle("lastShownChangelogVersion", () => + lastShownChangelogVersion(), + ); + + ipcMain.handle("setLastShownChangelogVersion", (_, version: number) => + setLastShownChangelogVersion(version), + ); // - App update @@ -119,8 +130,6 @@ export const attachIPCHandlers = () => { ipcMain.on("skipAppUpdate", (_, version: string) => skipAppUpdate(version)); - ipcMain.on("didShowWhatsNew", () => didShowWhatsNew()); - // - FS ipcMain.handle("fsExists", (_, path: string) => fsExists(path)); @@ -220,19 +229,6 @@ export const attachIPCHandlers = () => { ipcMain.handle("clearPendingUploads", () => clearPendingUploads()); }; -/** - * Sibling of {@link attachIPCHandlers} that attaches handlers that need access - * to the main window for their functioning. - * - * @param mainWindow Our app's main {@link BrowserWindow}. It is usually needed - * by these handler to send messages back to the main window's `webContents`. - */ -export const attachWindowIPCHandlers = (mainWindow: BrowserWindow) => { - // - App update - - ipcMain.on("canShowWhatsNew", () => canShowWhatsNew(mainWindow)); -}; - /** * Sibling of {@link attachIPCHandlers} that attaches handlers specific to the * watch folder functionality. diff --git a/desktop/src/main/services/app-update.ts b/desktop/src/main/services/app-update.ts index 60104a01c9..8b2d07a49c 100644 --- a/desktop/src/main/services/app-update.ts +++ b/desktop/src/main/services/app-update.ts @@ -180,26 +180,3 @@ export const updateOnNextRestart = (version: string) => export const skipAppUpdate = (version: string) => userPreferences.set("skipAppVersion", version); - -/** - * Invoked when the renderer attaches a callback for {@link onShowWhatsNew}. - * This can be taken as a signal that the UI is ready and will be able to show - * the What's New screen if we need to tell it to. - * - * See: [Note: Conditions for showing the "What's new" screen] - */ -export const canShowWhatsNew = (mainWindow: BrowserWindow) => { - const version = userPreferences.get("whatsNewShownVersion"); - if (version && compareVersions(app.getVersion(), version) <= 0) { - // We have shown What's New earlier, but it was for a version same as or - // newer than the current version. Nothing to do now. - return; - } - mainWindow.webContents.send("showWhatsNew"); -}; - -/** - * End the sequence started by {@link canShowWhatsNew}. - */ -export const didShowWhatsNew = () => - userPreferences.set("whatsNewShownVersion", app.getVersion()); diff --git a/desktop/src/main/services/store.ts b/desktop/src/main/services/store.ts index 253c2cbf0c..4663c2525f 100644 --- a/desktop/src/main/services/store.ts +++ b/desktop/src/main/services/store.ts @@ -1,12 +1,16 @@ import { safeStorage } from "electron/main"; import { safeStorageStore } from "../stores/safe-storage"; import { uploadStatusStore } from "../stores/upload-status"; +import { userPreferences } from "../stores/user-preferences"; import { watchStore } from "../stores/watch"; /** * Clear all stores except user preferences. * - * This is useful to reset state when the user logs out. + * This function is useful to reset state when the user logs out. User + * preferences are preserved since they contain things tied to the person using + * the app or other machine specific state not tied to the account they were + * using inside the app. */ export const clearStores = () => { safeStorageStore.clear(); @@ -32,3 +36,9 @@ export const encryptionKey = (): string | undefined => { const keyBuffer = Buffer.from(b64EncryptedKey, "base64"); return safeStorage.decryptString(keyBuffer); }; + +export const lastShownChangelogVersion = (): number | undefined => + userPreferences.get("lastShownChangelogVersion"); + +export const setLastShownChangelogVersion = (version: number) => + userPreferences.set("lastShownChangelogVersion", version); diff --git a/desktop/src/main/stores/user-preferences.ts b/desktop/src/main/stores/user-preferences.ts index 4b23310ab2..400e8f6833 100644 --- a/desktop/src/main/stores/user-preferences.ts +++ b/desktop/src/main/stores/user-preferences.ts @@ -10,9 +10,11 @@ interface UserPreferences { skipAppVersion?: string; muteUpdateNotificationVersion?: string; /** - * The app version for which we last showed the What's New screen. + * The changelog version for which we last showed the "What's new" screen. + * + * See: [Note: Conditions for showing "What's new"] */ - whatsNewShownVersion?: string; + lastShownChangelogVersion?: number; /** * The last position and size of our app's window. * @@ -37,7 +39,7 @@ const userPreferencesSchema: Schema = { hideDockIcon: { type: "boolean" }, skipAppVersion: { type: "string" }, muteUpdateNotificationVersion: { type: "string" }, - whatsNewShownVersion: { type: "string" }, + lastShownChangelogVersion: { type: "number" }, windowBounds: { properties: { x: { type: "number" }, diff --git a/desktop/src/preload.ts b/desktop/src/preload.ts index 9eb2597c0b..50fb8b15c7 100644 --- a/desktop/src/preload.ts +++ b/desktop/src/preload.ts @@ -72,6 +72,12 @@ const encryptionKey = () => ipcRenderer.invoke("encryptionKey"); const saveEncryptionKey = (encryptionKey: string) => ipcRenderer.invoke("saveEncryptionKey", encryptionKey); +const lastShownChangelogVersion = () => + ipcRenderer.invoke("lastShownChangelogVersion"); + +const setLastShownChangelogVersion = (version: number) => + ipcRenderer.invoke("setLastShownChangelogVersion", version); + const onMainWindowFocus = (cb: (() => void) | undefined) => { ipcRenderer.removeAllListeners("mainWindowFocus"); if (cb) ipcRenderer.on("mainWindowFocus", cb); @@ -104,18 +110,6 @@ const skipAppUpdate = (version: string) => { ipcRenderer.send("skipAppUpdate", version); }; -const onShowWhatsNew = (cb: (() => boolean) | undefined) => { - ipcRenderer.removeAllListeners("showWhatsNew"); - if (cb) { - ipcRenderer.on("showWhatsNew", () => { - if (cb()) { - ipcRenderer.send("didShowWhatsNew"); - } - }); - ipcRenderer.send("canShowWhatsNew"); - } -}; - // - FS const fsExists = (path: string) => ipcRenderer.invoke("fsExists", path); @@ -323,6 +317,8 @@ contextBridge.exposeInMainWorld("electron", { logout, encryptionKey, saveEncryptionKey, + lastShownChangelogVersion, + setLastShownChangelogVersion, onMainWindowFocus, onOpenURL, @@ -332,7 +328,6 @@ contextBridge.exposeInMainWorld("electron", { updateAndRestart, updateOnNextRestart, skipAppUpdate, - onShowWhatsNew, // - FS diff --git a/web/apps/photos/src/pages/gallery/index.tsx b/web/apps/photos/src/pages/gallery/index.tsx index 140a203e4e..8d0dc2b372 100644 --- a/web/apps/photos/src/pages/gallery/index.tsx +++ b/web/apps/photos/src/pages/gallery/index.tsx @@ -1,4 +1,5 @@ import { WhatsNew } from "@/new/photos/components/WhatsNew"; +import { shouldShowWhatsNew } from "@/new/photos/services/changelog"; import { fetchAndSaveFeatureFlagsIfNeeded } from "@/new/photos/services/feature-flags"; import log from "@/next/log"; import { CenteredFlex } from "@ente/shared/components/Container"; @@ -391,17 +392,13 @@ export default function Gallery() { if (electron) { // void clipService.setupOnFileUploadListener(); electron.onMainWindowFocus(() => syncWithRemote(false, true)); - electron.onShowWhatsNew(() => { - setOpenWhatsNew(true); - return true; - }); + if (await shouldShowWhatsNew()) setOpenWhatsNew(true); } }; main(); return () => { clearInterval(syncInterval.current); if (electron) { - electron.onShowWhatsNew(undefined); electron.onMainWindowFocus(undefined); clipService.removeOnFileUploadListener(); } diff --git a/web/packages/new/photos/components/WhatsNew.tsx b/web/packages/new/photos/components/WhatsNew.tsx index 268b142519..2d455aef22 100644 --- a/web/packages/new/photos/components/WhatsNew.tsx +++ b/web/packages/new/photos/components/WhatsNew.tsx @@ -12,7 +12,8 @@ import { } from "@mui/material"; import Slide from "@mui/material/Slide"; import type { TransitionProps } from "@mui/material/transitions"; -import React from "react"; +import React, { useEffect } from "react"; +import { didShowWhatsNew } from "../services/changelog"; interface WhatsNewProps { /** If `true`, then the dialog is shown. */ @@ -22,12 +23,16 @@ interface WhatsNewProps { } /** - * Show a dialog showing a short summary of interesting-for-the-user things in - * this release of the desktop app. + * Show a dialog showing a short summary of interesting-for-the-user things + * since the last time this dialog was shown. */ export const WhatsNew: React.FC = ({ open, onClose }) => { const fullScreen = useMediaQuery("(max-width: 428px)"); + useEffect(() => { + void didShowWhatsNew(); + }, []); + return ( { + const electron = globalThis.electron; + if (!electron) return false; + const lastShownVersion = (await electron.lastShownChangelogVersion()) ?? 0; + return lastShownVersion < changelogVersion; +}; + +export const didShowWhatsNew = async () => + // We should only have been called if we're in electron. + ensureElectron().setLastShownChangelogVersion(changelogVersion); diff --git a/web/packages/next/types/ipc.ts b/web/packages/next/types/ipc.ts index 6aa7133d7a..646ec79127 100644 --- a/web/packages/next/types/ipc.ts +++ b/web/packages/next/types/ipc.ts @@ -149,26 +149,21 @@ export interface Electron { skipAppUpdate: (version: string) => void; /** - * Set or clear the callback {@link cb} to invoke when the app should show - * the "What's new" screen for the current version. + * Get the persisted version for the last shown changelog. * - * Setting a callback clears any previous callbacks. - * - * [Note: Conditions for showing the "What's new" screen] - * - * This screen is shown only once per update, and only when the current - * version is (sem-versionally) greater than the previous version for which - * this dialog was shown (if any). The state about whether or not this - * dialog has already been shown is persisted on the Node.js, in the user - * preferences store (which is not cleared on logout). - * - * If the Node.js layer notices an attached callback (and the above - * conditions are satisfied), then it invokes the callback. The callback - * should return `true` to indicate that the whats new screen was shown so - * that the Node.js layer can update the persisted state to avoid showing it - * again. + * See: [Note: Conditions for showing "What's new"] */ - onShowWhatsNew: (cb: (() => boolean) | undefined) => void; + lastShownChangelogVersion: () => Promise; + + /** + * Save the given {@link version} to disk as the version of the last shown + * changelog. + * + * The value is saved to a store which is not cleared during logout. + * + * @see {@link lastShownChangelogVersion} + */ + setLastShownChangelogVersion: (version: number) => Promise; // - FS