From ee86de6b306601b7214157b0439d8a4e242a35e8 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 13 Jun 2024 13:28:01 +0530 Subject: [PATCH 1/7] Move to gallery Doesn't make much sense to show it to logged out users --- web/apps/photos/src/pages/_app.tsx | 10 ---------- web/apps/photos/src/pages/gallery/index.tsx | 9 +++++++++ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index 9654f0f6d6..1cdcb6b3b8 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -1,4 +1,3 @@ -import { WhatsNew } from "@/new/photos/components/WhatsNew"; import { CustomHead } from "@/next/components/Head"; import { setAppNameForAuthenticatedRequests } from "@/next/http"; import { setupI18n } from "@/next/i18n"; @@ -130,10 +129,6 @@ export default function App({ Component, pageProps }: AppProps) { >(); useState(null); const [messageDialogView, setMessageDialogView] = useState(false); - // TODO(MR): This is never true currently, this is the WIP ability to show - // what's new dialog on desktop app updates. The UI is done, need to hook - // this up to logic to trigger it. - const [openWhatsNew, setOpenWhatsNew] = useState(false); const [dialogBoxV2View, setDialogBoxV2View] = useState(false); const [watchFolderView, setWatchFolderView] = useState(false); const [watchFolderFiles, setWatchFolderFiles] = useState(null); @@ -407,11 +402,6 @@ export default function App({ Component, pageProps }: AppProps) { attributes={dialogBoxAttributeV2} /> - setOpenWhatsNew(false)} - /> - (null); const [shouldDisableDropzone, setShouldDisableDropzone] = useState(false); const [isPhotoSwipeOpen, setIsPhotoSwipeOpen] = useState(false); + // TODO(MR): This is never true currently, this is the WIP ability to show + // what's new dialog on desktop app updates. The UI is done, need to hook + // this up to logic to trigger it. + const [openWhatsNew, setOpenWhatsNew] = useState(false); const { // A function to call to get the props we should apply to the container, @@ -1154,6 +1159,10 @@ export default function Gallery() { sidebarView={sidebarView} closeSidebar={closeSidebar} /> + setOpenWhatsNew(false)} + /> {!isInSearchMode && !isFirstLoad && !files?.length && From 6f4b20ad6ffa66c5e2dea7ca17552053ea9f97c3 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 13 Jun 2024 13:44:26 +0530 Subject: [PATCH 2/7] Link --- web/apps/photos/src/pages/gallery/index.tsx | 5 +++ web/packages/next/types/ipc.ts | 34 +++++++++++++++++---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/web/apps/photos/src/pages/gallery/index.tsx b/web/apps/photos/src/pages/gallery/index.tsx index 24f90ee2e3..140a203e4e 100644 --- a/web/apps/photos/src/pages/gallery/index.tsx +++ b/web/apps/photos/src/pages/gallery/index.tsx @@ -391,12 +391,17 @@ export default function Gallery() { if (electron) { // void clipService.setupOnFileUploadListener(); electron.onMainWindowFocus(() => syncWithRemote(false, true)); + electron.onShowWhatsNew(() => { + setOpenWhatsNew(true); + return true; + }); } }; main(); return () => { clearInterval(syncInterval.current); if (electron) { + electron.onShowWhatsNew(undefined); electron.onMainWindowFocus(undefined); clipService.removeOnFileUploadListener(); } diff --git a/web/packages/next/types/ipc.ts b/web/packages/next/types/ipc.ts index 23da7221e9..2bbfa19bf0 100644 --- a/web/packages/next/types/ipc.ts +++ b/web/packages/next/types/ipc.ts @@ -87,12 +87,12 @@ export interface Electron { * into the foreground. More precisely, the callback gets invoked when the * main window gets focus. * - * Note: Setting a callback clears any previous callbacks. + * Setting a callback clears any previous callbacks. * * @param cb The function to call when the main window gets focus. Pass * `undefined` to clear the callback. */ - onMainWindowFocus: (cb?: () => void) => void; + onMainWindowFocus: (cb: (() => void) | undefined) => void; /** * Set or clear the callback {@link cb} to invoke whenever the app gets @@ -103,13 +103,13 @@ export interface Electron { * In particular, this is necessary for handling passkey authentication. * See: [Note: Passkey verification in the desktop app] * - * Note: Setting a callback clears any previous callbacks. + * Setting a callback clears any previous callbacks. * * @param cb The function to call when the app gets asked to open a * "ente://" URL. The URL string (a.k.a. "deeplink") we were asked to open * is passed to the function verbatim. */ - onOpenURL: (cb?: (url: string) => void) => void; + onOpenURL: (cb: ((url: string) => void) | undefined) => void; // - App update @@ -118,10 +118,10 @@ export interface Electron { * (actionable) app update is available. This allows the Node.js layer to * ask the renderer to show an "Update available" dialog to the user. * - * Note: Setting a callback clears any previous callbacks. + * Setting a callback clears any previous callbacks. */ onAppUpdateAvailable: ( - cb?: ((update: AppUpdate) => void) | undefined, + cb: ((update: AppUpdate) => void) | undefined, ) => void; /** @@ -148,6 +148,28 @@ 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. + * + * 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. + */ + onShowWhatsNew: (cb?: (() => boolean) | undefined) => void; + // - FS /** From 4e9ff3024cae9e7ca416cff67970915681ca42f9 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 13 Jun 2024 14:26:53 +0530 Subject: [PATCH 3/7] Hook it up --- desktop/src/main.ts | 2 ++ desktop/src/main/ipc.ts | 19 +++++++++++++++- desktop/src/main/services/app-update.ts | 24 +++++++++++++++++++++ desktop/src/main/stores/user-preferences.ts | 5 +++++ desktop/src/preload.ts | 19 +++++++++++++--- web/packages/next/types/ipc.ts | 2 +- 6 files changed, 66 insertions(+), 5 deletions(-) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 50f69759da..768e1a1df4 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -21,6 +21,7 @@ import { attachFSWatchIPCHandlers, attachIPCHandlers, attachLogoutIPCHandler, + attachWindowIPCHandlers, } from "./main/ipc"; import log, { initLogging } from "./main/log"; import { createApplicationMenu, createTrayContextMenu } from "./main/menu"; @@ -116,6 +117,7 @@ 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 6e7df7cdea..303ddb6dbd 100644 --- a/desktop/src/main/ipc.ts +++ b/desktop/src/main/ipc.ts @@ -9,7 +9,7 @@ */ import type { FSWatcher } from "chokidar"; -import { ipcMain } from "electron/main"; +import { ipcMain, type BrowserWindow } from "electron/main"; import type { CollectionMapping, FolderWatch, @@ -19,6 +19,8 @@ import type { import { logToDisk } from "./log"; import { appVersion, + canShowWhatsNew, + didShowWhatsNew, skipAppUpdate, updateAndRestart, updateOnNextRestart, @@ -117,6 +119,8 @@ export const attachIPCHandlers = () => { ipcMain.on("skipAppUpdate", (_, version: string) => skipAppUpdate(version)); + ipcMain.on("didShowWhatsNew", () => didShowWhatsNew()); + // - FS ipcMain.handle("fsExists", (_, path: string) => fsExists(path)); @@ -216,6 +220,19 @@ 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 8b2d07a49c..c903ea9d0d 100644 --- a/desktop/src/main/services/app-update.ts +++ b/desktop/src/main/services/app-update.ts @@ -180,3 +180,27 @@ 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) { + if (compareVersions(version, app.getVersion()) <= 0) { + log.debug(() => `Downgrade from ${version}, skipping what's new`); + 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/stores/user-preferences.ts b/desktop/src/main/stores/user-preferences.ts index b337b342ac..4b23310ab2 100644 --- a/desktop/src/main/stores/user-preferences.ts +++ b/desktop/src/main/stores/user-preferences.ts @@ -9,6 +9,10 @@ interface UserPreferences { hideDockIcon?: boolean; skipAppVersion?: string; muteUpdateNotificationVersion?: string; + /** + * The app version for which we last showed the What's New screen. + */ + whatsNewShownVersion?: string; /** * The last position and size of our app's window. * @@ -33,6 +37,7 @@ const userPreferencesSchema: Schema = { hideDockIcon: { type: "boolean" }, skipAppVersion: { type: "string" }, muteUpdateNotificationVersion: { type: "string" }, + whatsNewShownVersion: { type: "string" }, windowBounds: { properties: { x: { type: "number" }, diff --git a/desktop/src/preload.ts b/desktop/src/preload.ts index fe449bad38..9eb2597c0b 100644 --- a/desktop/src/preload.ts +++ b/desktop/src/preload.ts @@ -72,12 +72,12 @@ const encryptionKey = () => ipcRenderer.invoke("encryptionKey"); const saveEncryptionKey = (encryptionKey: string) => ipcRenderer.invoke("saveEncryptionKey", encryptionKey); -const onMainWindowFocus = (cb?: () => void) => { +const onMainWindowFocus = (cb: (() => void) | undefined) => { ipcRenderer.removeAllListeners("mainWindowFocus"); if (cb) ipcRenderer.on("mainWindowFocus", cb); }; -const onOpenURL = (cb?: (url: string) => void) => { +const onOpenURL = (cb: ((url: string) => void) | undefined) => { ipcRenderer.removeAllListeners("openURL"); if (cb) ipcRenderer.on("openURL", (_, url: string) => cb(url)); }; @@ -85,7 +85,7 @@ const onOpenURL = (cb?: (url: string) => void) => { // - App update const onAppUpdateAvailable = ( - cb?: ((update: AppUpdate) => void) | undefined, + cb: ((update: AppUpdate) => void) | undefined, ) => { ipcRenderer.removeAllListeners("appUpdateAvailable"); if (cb) { @@ -104,6 +104,18 @@ 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); @@ -320,6 +332,7 @@ contextBridge.exposeInMainWorld("electron", { updateAndRestart, updateOnNextRestart, skipAppUpdate, + onShowWhatsNew, // - FS diff --git a/web/packages/next/types/ipc.ts b/web/packages/next/types/ipc.ts index 2bbfa19bf0..6aa7133d7a 100644 --- a/web/packages/next/types/ipc.ts +++ b/web/packages/next/types/ipc.ts @@ -168,7 +168,7 @@ export interface Electron { * that the Node.js layer can update the persisted state to avoid showing it * again. */ - onShowWhatsNew: (cb?: (() => boolean) | undefined) => void; + onShowWhatsNew: (cb: (() => boolean) | undefined) => void; // - FS From e9f10f77d9296084c47737d1fb9b9330746751bd Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 13 Jun 2024 14:39:36 +0530 Subject: [PATCH 4/7] Fix condition --- desktop/src/main/services/app-update.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/desktop/src/main/services/app-update.ts b/desktop/src/main/services/app-update.ts index c903ea9d0d..60104a01c9 100644 --- a/desktop/src/main/services/app-update.ts +++ b/desktop/src/main/services/app-update.ts @@ -190,11 +190,10 @@ export const skipAppUpdate = (version: string) => */ export const canShowWhatsNew = (mainWindow: BrowserWindow) => { const version = userPreferences.get("whatsNewShownVersion"); - if (version) { - if (compareVersions(version, app.getVersion()) <= 0) { - log.debug(() => `Downgrade from ${version}, skipping what's new`); - return; - } + 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"); }; From b2154429f325c55631781525b2dad854d6af46f5 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 13 Jun 2024 14:49:16 +0530 Subject: [PATCH 5/7] Mention pk --- .../new/photos/components/WhatsNew.tsx | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/web/packages/new/photos/components/WhatsNew.tsx b/web/packages/new/photos/components/WhatsNew.tsx index f6128b860a..268b142519 100644 --- a/web/packages/new/photos/components/WhatsNew.tsx +++ b/web/packages/new/photos/components/WhatsNew.tsx @@ -6,6 +6,7 @@ import { DialogContent, DialogContentText, DialogTitle, + Typography, styled, useMediaQuery, } from "@mui/material"; @@ -38,8 +39,21 @@ export const WhatsNew: React.FC = ({ open, onClose }) => {
  • - The app will remember its position and size when it - is closed, and will reopen the same way. + + + Support for Passkeys + + Passkeys can now be used as a second factor + authentication mechanism. + +
  • +
  • + Window size + + { + "The app's window will remember its size and position." + } +
  • @@ -70,9 +84,10 @@ const SlideTransition = React.forwardRef(function Transition( const StyledUL = styled("ul")` padding-inline: 1rem; - list-style-type: circle; - margin-block-end: 20px; + li { + margin-block: 2rem; + } `; const StyledButton = styled(Button)` From 2bbf33287bfa73f4e57af8350fdb9375f9cba570 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 13 Jun 2024 16:00:57 +0530 Subject: [PATCH 6/7] 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 From 67028a072cf73da1f3b808c88e2931188ff1414e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 13 Jun 2024 16:05:53 +0530 Subject: [PATCH 7/7] Fix --- .../new/photos/components/WhatsNew.tsx | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/web/packages/new/photos/components/WhatsNew.tsx b/web/packages/new/photos/components/WhatsNew.tsx index 2d455aef22..9a98ee5e48 100644 --- a/web/packages/new/photos/components/WhatsNew.tsx +++ b/web/packages/new/photos/components/WhatsNew.tsx @@ -30,8 +30,8 @@ export const WhatsNew: React.FC = ({ open, onClose }) => { const fullScreen = useMediaQuery("(max-width: 428px)"); useEffect(() => { - void didShowWhatsNew(); - }, []); + if (open) void didShowWhatsNew(); + }, [open]); return ( = ({ open, onClose }) => { {"What's new"} - -
  • - - - Support for Passkeys - - Passkeys can now be used as a second factor - authentication mechanism. - -
  • -
  • - Window size - - { - "The app's window will remember its size and position." - } - -
  • -
    +
    @@ -87,6 +69,31 @@ const SlideTransition = React.forwardRef(function Transition( return ; }); +const ChangelogContent: React.FC = () => { + // NOTE: Remember to update changelogVersion when changing the content + // below. + + return ( + +
  • + + + Support for Passkeys + + Passkeys can now be used as a second factor authentication + mechanism. + +
  • +
  • + Window size + + {"The app's window will remember its size and position."} + +
  • +
    + ); +}; + const StyledUL = styled("ul")` padding-inline: 1rem;