diff --git a/package-lock.json b/package-lock.json index 5437e8b502..09fb9cd1b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,7 +91,7 @@ "@types/yargs": "^17.0.10", "@typescript-eslint/eslint-plugin": "^5.59.7", "@typescript-eslint/parser": "^5.59.7", - "@vercel/webpack-asset-relocator-loader": "^1.7.2", + "@vercel/webpack-asset-relocator-loader": "^1.7.3", "babel-loader": "^8.2.5", "babel-plugin-i18next-extract": "^0.9.0", "concurrently": "^7.2.1", @@ -5993,10 +5993,13 @@ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "node_modules/@types/node": { - "version": "18.17.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.1.tgz", - "integrity": "sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==", - "dev": true + "version": "20.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.2.tgz", + "integrity": "sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/node-localstorage": { "version": "1.3.0", @@ -6392,10 +6395,13 @@ "dev": true }, "node_modules/@vercel/webpack-asset-relocator-loader": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@vercel/webpack-asset-relocator-loader/-/webpack-asset-relocator-loader-1.7.2.tgz", - "integrity": "sha512-pdMwUawmAtH/LScbjKJq/y2+gZFggFMc2tlJrlPSrgKajvYPEis3L9QKcMyC9RN1Xos4ezAP5AJfRCNN6RMKCQ==", - "dev": true + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@vercel/webpack-asset-relocator-loader/-/webpack-asset-relocator-loader-1.7.3.tgz", + "integrity": "sha512-vizrI18v8Lcb1PmNNUBz7yxPxxXoOeuaVEjTG9MjvDrphjiSxFZrRJ5tIghk+qdLFRCXI5HBCshgobftbmrC5g==", + "dev": true, + "dependencies": { + "resolve": "^1.10.0" + } }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", @@ -10926,6 +10932,15 @@ "node": ">= 4.0.0" } }, + "node_modules/electron/node_modules/@types/node": { + "version": "18.19.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.28.tgz", + "integrity": "sha512-J5cOGD9n4x3YGgVuaND6khm5x07MMdAKkRyXnjVR6KFhLMNh2yONGiP7Z+4+tBOt5mK+GvDTiacTOVGGpqiecw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/elkjs": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz", @@ -24165,6 +24180,12 @@ "node": ">=14.0" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/unified": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", @@ -30004,10 +30025,13 @@ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "@types/node": { - "version": "18.17.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.1.tgz", - "integrity": "sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==", - "dev": true + "version": "20.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.2.tgz", + "integrity": "sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } }, "@types/node-localstorage": { "version": "1.3.0", @@ -30310,10 +30334,13 @@ "dev": true }, "@vercel/webpack-asset-relocator-loader": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@vercel/webpack-asset-relocator-loader/-/webpack-asset-relocator-loader-1.7.2.tgz", - "integrity": "sha512-pdMwUawmAtH/LScbjKJq/y2+gZFggFMc2tlJrlPSrgKajvYPEis3L9QKcMyC9RN1Xos4ezAP5AJfRCNN6RMKCQ==", - "dev": true + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@vercel/webpack-asset-relocator-loader/-/webpack-asset-relocator-loader-1.7.3.tgz", + "integrity": "sha512-vizrI18v8Lcb1PmNNUBz7yxPxxXoOeuaVEjTG9MjvDrphjiSxFZrRJ5tIghk+qdLFRCXI5HBCshgobftbmrC5g==", + "dev": true, + "requires": { + "resolve": "^1.10.0" + } }, "@webassemblyjs/ast": { "version": "1.11.1", @@ -33472,6 +33499,17 @@ "@electron/get": "^2.0.0", "@types/node": "^18.11.18", "extract-zip": "^2.0.1" + }, + "dependencies": { + "@types/node": { + "version": "18.19.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.28.tgz", + "integrity": "sha512-J5cOGD9n4x3YGgVuaND6khm5x07MMdAKkRyXnjVR6KFhLMNh2yONGiP7Z+4+tBOt5mK+GvDTiacTOVGGpqiecw==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + } } }, "electron-installer-common": { @@ -43556,6 +43594,12 @@ "@fastify/busboy": "^2.0.0" } }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "unified": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", diff --git a/package.json b/package.json index 3343f89e91..f65565fc67 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "@types/yargs": "^17.0.10", "@typescript-eslint/eslint-plugin": "^5.59.7", "@typescript-eslint/parser": "^5.59.7", - "@vercel/webpack-asset-relocator-loader": "^1.7.2", + "@vercel/webpack-asset-relocator-loader": "^1.7.3", "babel-loader": "^8.2.5", "babel-plugin-i18next-extract": "^0.9.0", "concurrently": "^7.2.1", diff --git a/src/common/safeIpc.ts b/src/common/safeIpc.ts index eca7c641f5..d5f89d64dd 100644 --- a/src/common/safeIpc.ts +++ b/src/common/safeIpc.ts @@ -1,13 +1,4 @@ -import { - BrowserWindow, - IpcMainEvent, - IpcMainInvokeEvent, - IpcRendererEvent, - MessagePortMain, - WebContents, - ipcMain as unsafeIpcMain, - ipcRenderer as unsafeIpcRenderer, -} from 'electron'; +import { type FileFilter, type OpenDialogReturnValue } from 'electron'; // TODO: replace with electron/common import { MakeDirectoryOptions } from 'fs'; import { Mode, ObjectEncodingOptions, OpenMode, PathLike } from 'original-fs'; import { FileOpenResult, FileSaveResult, PythonInfo, Version } from './common-types'; @@ -26,14 +17,10 @@ export interface InvokeChannels { 'get-backend-url': ChannelInfo; 'refresh-nodes': ChannelInfo; 'get-app-version': ChannelInfo; - 'dir-select': ChannelInfo; + 'dir-select': ChannelInfo; 'file-select': ChannelInfo< - Electron.OpenDialogReturnValue, - [ - filters: Electron.FileFilter[], - allowMultiple: boolean | undefined, - dirPath: string | undefined - ] + OpenDialogReturnValue, + [filters: FileFilter[], allowMultiple: boolean | undefined, dirPath: string | undefined] >; 'file-save-json': ChannelInfo; @@ -91,6 +78,24 @@ export interface InvokeChannels { 'fs-readdir': ChannelInfo; 'fs-unlink': ChannelInfo; 'fs-access': ChannelInfo; + + // Electron + 'shell-showItemInFolder': ChannelInfo; + 'shell-openPath': ChannelInfo; + 'app-quit': ChannelInfo; + 'clipboard-writeText': ChannelInfo; + 'clipboard-readText': ChannelInfo; + 'clipboard-writeBuffer': ChannelInfo< + void, + [format: string, buffer: Buffer, type?: 'selection' | 'clipboard' | undefined] + >; + 'clipboard-readBuffer': ChannelInfo; + 'clipboard-availableFormats': ChannelInfo; + 'clipboard-readHTML': ChannelInfo; + 'clipboard-readRTF': ChannelInfo; + 'clipboard-readImage': ChannelInfo; + 'clipboard-writeImage': ChannelInfo; + 'clipboard-writeImageFromURL': ChannelInfo; } export interface SendChannels { @@ -135,85 +140,3 @@ export type ChannelArgs = (Invo SendChannels)[C]['args']; export type ChannelReturn = (InvokeChannels & SendChannels)[C]['returnType']; - -interface SafeIpcMain extends Electron.IpcMain { - handle( - channel: C, - listener: ( - event: IpcMainInvokeEvent, - ...args: ChannelArgs - ) => Promise> | ChannelReturn - ): void; - handleOnce( - channel: C, - listener: ( - event: IpcMainInvokeEvent, - ...args: ChannelArgs - ) => Promise> | ChannelReturn - ): void; - on( - channel: C, - listener: (event: IpcMainEvent, ...args: ChannelArgs) => void - ): this; - once( - channel: C, - listener: (event: IpcMainEvent, ...args: ChannelArgs) => void - ): this; - removeAllListeners(channel?: keyof SendChannels): this; - removeHandler(channel: keyof InvokeChannels): void; - removeListener( - channel: C, - listener: (event: IpcMainEvent | IpcMainInvokeEvent, ...args: ChannelArgs) => void - ): this; -} - -interface SafeIpcRenderer extends Electron.IpcRenderer { - invoke( - channel: C, - ...args: ChannelArgs - ): Promise>; - on( - channel: C, - listener: (event: IpcRendererEvent, ...args: ChannelArgs) => void - ): this; - once( - channel: C, - listener: (event: IpcRendererEvent, ...args: ChannelArgs) => void - ): this; - postMessage(channel: keyof SendChannels, message: unknown, transfer?: MessagePort[]): void; - removeAllListeners(channel: keyof SendChannels): this; - removeListener( - channel: C, - listener: (event: IpcRendererEvent, ...args: ChannelArgs) => void - ): this; - send(channel: C, ...args: ChannelArgs): void; - sendSync(channel: C, ...args: ChannelArgs): void; - sendTo( - webContentsId: number, - channel: C, - ...args: ChannelArgs - ): void; - sendToHost(channel: C, ...args: ChannelArgs): void; -} - -interface WebContentsWithSafeIcp extends WebContents { - invoke( - channel: C, - ...args: ChannelArgs - ): Promise>; - postMessage(channel: keyof SendChannels, message: unknown, transfer?: MessagePortMain[]): void; - send(channel: C, ...args: ChannelArgs): void; - sendSync(channel: C, ...args: ChannelArgs): ChannelReturn; - sendTo( - webContentsId: number, - channel: C, - ...args: ChannelArgs - ): void; - sendToHost(channel: C, ...args: ChannelArgs): void; -} -export interface BrowserWindowWithSafeIpc extends BrowserWindow { - webContents: WebContentsWithSafeIcp; -} - -export const ipcMain = unsafeIpcMain as SafeIpcMain; -export const ipcRenderer = unsafeIpcRenderer as SafeIpcRenderer; diff --git a/src/main/backend/process.ts b/src/main/backend/process.ts index eb38ba41cf..e65a8e4544 100644 --- a/src/main/backend/process.ts +++ b/src/main/backend/process.ts @@ -1,5 +1,5 @@ import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; -import { app } from 'electron'; +import { app } from 'electron/main'; import { existsSync } from 'fs'; import path from 'path'; import { getBackend } from '../../common/Backend'; diff --git a/src/main/cli/create.ts b/src/main/cli/create.ts index d5bf579f8d..35cc74de39 100644 --- a/src/main/cli/create.ts +++ b/src/main/cli/create.ts @@ -1,5 +1,5 @@ -import { app } from 'electron'; import electronLog from 'electron-log'; +import { app } from 'electron/main'; import { log } from '../../common/log'; import { Exit } from './exit'; diff --git a/src/main/cli/run.ts b/src/main/cli/run.ts index 136d2a9871..0af48561c8 100644 --- a/src/main/cli/run.ts +++ b/src/main/cli/run.ts @@ -1,4 +1,4 @@ -import { app } from 'electron'; +import { app } from 'electron/main'; import EventSource from 'eventsource'; import { Backend, BackendEventMap, getBackend } from '../../common/Backend'; import { EdgeData, NodeData, NodeSchema, SchemaId } from '../../common/common-types'; diff --git a/src/main/gui/create.ts b/src/main/gui/create.ts index c7e8a589fb..9ded237c4f 100644 --- a/src/main/gui/create.ts +++ b/src/main/gui/create.ts @@ -1,5 +1,5 @@ -import { app, dialog } from 'electron'; import electronLog from 'electron-log'; +import { app, dialog } from 'electron/main'; import { log } from '../../common/log'; import { lazy } from '../../common/util'; import { OpenArguments } from '../arguments'; diff --git a/src/main/gui/main-window.ts b/src/main/gui/main-window.ts index 728904b491..9a0add4732 100644 --- a/src/main/gui/main-window.ts +++ b/src/main/gui/main-window.ts @@ -1,4 +1,5 @@ -import { BrowserWindow, app, dialog, nativeTheme, powerSaveBlocker, shell } from 'electron'; +import { clipboard, nativeImage, shell } from 'electron/common'; +import { BrowserWindow, app, dialog, nativeTheme, powerSaveBlocker } from 'electron/main'; import EventSource from 'eventsource'; import fs, { constants } from 'fs/promises'; import { t } from 'i18next'; @@ -6,7 +7,6 @@ import { BackendEventMap } from '../../common/Backend'; import { Version } from '../../common/common-types'; import { isMac } from '../../common/env'; import { log } from '../../common/log'; -import { BrowserWindowWithSafeIpc, ipcMain } from '../../common/safeIpc'; import { SaveFile, openSaveFile } from '../../common/SaveFile'; import { ChainnerSettings } from '../../common/settings/settings'; import { CriticalError } from '../../common/ui/error'; @@ -15,6 +15,7 @@ import { OpenArguments, parseArgs } from '../arguments'; import { BackendProcess } from '../backend/process'; import { setupBackend } from '../backend/setup'; import { getRootDirSync } from '../platform'; +import { BrowserWindowWithSafeIpc, ipcMain } from '../safeIpc'; import { writeSettings } from '../setting-storage'; import { MenuData, setMainMenu } from './menu'; import { addSplashScreen } from './splash'; @@ -214,6 +215,26 @@ const registerEventHandlerPreSetup = ( ipcMain.handle('fs-readdir', async (event, path) => fs.readdir(path)); ipcMain.handle('fs-unlink', async (event, path) => fs.unlink(path)); ipcMain.handle('fs-access', async (event, path) => fs.access(path)); + + // Handle electron + ipcMain.handle('shell-showItemInFolder', (event, fullPath) => shell.showItemInFolder(fullPath)); + ipcMain.handle('shell-openPath', (event, fullPath) => shell.openPath(fullPath)); + ipcMain.handle('app-quit', () => app.quit()); + ipcMain.handle('clipboard-writeText', (event, text) => clipboard.writeText(text)); + ipcMain.handle('clipboard-readText', () => clipboard.readText()); + ipcMain.handle('clipboard-writeBuffer', (event, format, buffer, type) => + clipboard.writeBuffer(format, buffer, type) + ); + ipcMain.handle('clipboard-readBuffer', (event, format) => clipboard.readBuffer(format)); + ipcMain.handle('clipboard-availableFormats', () => clipboard.availableFormats()); + ipcMain.handle('clipboard-readHTML', () => clipboard.readHTML()); + ipcMain.handle('clipboard-readRTF', () => clipboard.readRTF()); + ipcMain.handle('clipboard-readImage', () => clipboard.readImage()); + ipcMain.handle('clipboard-writeImage', (event, image) => clipboard.writeImage(image)); + ipcMain.handle('clipboard-writeImageFromURL', (event, url) => { + const image = nativeImage.createFromDataURL(url); + clipboard.writeImage(image); + }); }; const registerEventHandlerPostSetup = ( diff --git a/src/main/gui/menu.ts b/src/main/gui/menu.ts index 931b18cb07..3fad9b9d71 100644 --- a/src/main/gui/menu.ts +++ b/src/main/gui/menu.ts @@ -1,12 +1,13 @@ /* eslint-disable @typescript-eslint/no-misused-promises */ -import { Menu, MenuItemConstructorOptions, app, dialog, shell } from 'electron'; +import { shell } from 'electron/common'; +import { Menu, MenuItemConstructorOptions, app, dialog } from 'electron/main'; import os from 'os'; import path from 'path'; import { isMac } from '../../common/env'; import { links } from '../../common/links'; -import { BrowserWindowWithSafeIpc } from '../../common/safeIpc'; import { openSaveFile } from '../../common/SaveFile'; import { getLogsFolder } from '../platform'; +import { BrowserWindowWithSafeIpc } from '../safeIpc'; import { getCpuInfo, getGpuInfo } from '../systemInfo'; export interface MenuData { diff --git a/src/main/gui/splash.ts b/src/main/gui/splash.ts index 26b36bd6c5..8a43c49083 100644 --- a/src/main/gui/splash.ts +++ b/src/main/gui/splash.ts @@ -1,8 +1,9 @@ -import { BrowserWindow, MessageBoxOptions, app, dialog, shell } from 'electron'; +import { shell } from 'electron/common'; +import { BrowserWindow, MessageBoxOptions, app, dialog } from 'electron/main'; import { log } from '../../common/log'; -import { BrowserWindowWithSafeIpc } from '../../common/safeIpc'; import { Progress, ProgressMonitor } from '../../common/ui/progress'; import { assertNever } from '../../common/util'; +import { BrowserWindowWithSafeIpc } from '../safeIpc'; export type SplashStage = 'init' | 'done'; diff --git a/src/main/platform.ts b/src/main/platform.ts index 72ba3bd1f6..5fd77327b6 100644 --- a/src/main/platform.ts +++ b/src/main/platform.ts @@ -1,4 +1,4 @@ -import { app } from 'electron'; +import { app } from 'electron/main'; import { existsSync } from 'fs'; import os from 'os'; import path from 'path'; diff --git a/src/main/safeIpc.ts b/src/main/safeIpc.ts new file mode 100644 index 0000000000..e3cfadea33 --- /dev/null +++ b/src/main/safeIpc.ts @@ -0,0 +1,62 @@ +import { + BrowserWindow, + IpcMainEvent, + IpcMainInvokeEvent, + MessagePortMain, + WebContents, + ipcMain as unsafeIpcMain, +} from 'electron/main'; +import { ChannelArgs, ChannelReturn, InvokeChannels, SendChannels } from '../common/safeIpc'; + +interface SafeIpcMain extends Electron.IpcMain { + handle( + channel: C, + listener: ( + event: IpcMainInvokeEvent, + ...args: ChannelArgs + ) => Promise> | ChannelReturn + ): void; + handleOnce( + channel: C, + listener: ( + event: IpcMainInvokeEvent, + ...args: ChannelArgs + ) => Promise> | ChannelReturn + ): void; + on( + channel: C, + listener: (event: IpcMainEvent, ...args: ChannelArgs) => void + ): this; + once( + channel: C, + listener: (event: IpcMainEvent, ...args: ChannelArgs) => void + ): this; + removeAllListeners(channel?: keyof SendChannels): this; + removeHandler(channel: keyof InvokeChannels): void; + removeListener( + channel: C, + listener: (event: IpcMainEvent | IpcMainInvokeEvent, ...args: ChannelArgs) => void + ): this; +} + +interface WebContentsWithSafeIcp extends WebContents { + invoke( + channel: C, + ...args: ChannelArgs + ): Promise>; + postMessage(channel: keyof SendChannels, message: unknown, transfer?: MessagePortMain[]): void; + send(channel: C, ...args: ChannelArgs): void; + sendSync(channel: C, ...args: ChannelArgs): ChannelReturn; + sendTo( + webContentsId: number, + channel: C, + ...args: ChannelArgs + ): void; + sendToHost(channel: C, ...args: ChannelArgs): void; +} + +export interface BrowserWindowWithSafeIpc extends BrowserWindow { + webContents: WebContentsWithSafeIcp; +} + +export const ipcMain = unsafeIpcMain as SafeIpcMain; diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx index 4d484c5ef8..388128110e 100644 --- a/src/renderer/app.tsx +++ b/src/renderer/app.tsx @@ -3,7 +3,6 @@ import TimeAgo from 'javascript-time-ago'; import en from 'javascript-time-ago/locale/en.json'; import { memo, useState } from 'react'; import { PythonInfo } from '../common/common-types'; -import { ipcRenderer } from '../common/safeIpc'; import { ChainnerSettings } from '../common/settings/settings'; import { AlertBoxProvider } from './contexts/AlertBoxContext'; import { BackendProvider } from './contexts/BackendContext'; @@ -12,6 +11,7 @@ import { HotkeysProvider } from './contexts/HotKeyContext'; import { SettingsProvider } from './contexts/SettingsContext'; import { useAsyncEffect } from './hooks/useAsyncEffect'; import { Main } from './main'; +import { ipcRenderer } from './safeIpc'; import { darktheme } from './theme'; import './i18n'; diff --git a/src/renderer/components/Header/AppInfo.tsx b/src/renderer/components/Header/AppInfo.tsx index 3f0f01e53a..f87db5641e 100644 --- a/src/renderer/components/Header/AppInfo.tsx +++ b/src/renderer/components/Header/AppInfo.tsx @@ -26,11 +26,12 @@ import { import { memo, useEffect, useRef, useState } from 'react'; import semver from 'semver'; import { GitHubRelease, getLatestVersionIfUpdateAvailable } from '../../../common/api/github'; -import { ipcRenderer } from '../../../common/safeIpc'; + import logo from '../../../public/icons/png/256x256.png'; import { useSettings } from '../../contexts/SettingsContext'; import { useAsyncEffect } from '../../hooks/useAsyncEffect'; import { useStored } from '../../hooks/useStored'; +import { ipcRenderer } from '../../safeIpc'; import { Markdown } from '../Markdown'; export const AppInfo = memo(() => { diff --git a/src/renderer/components/Header/KoFiButton.tsx b/src/renderer/components/Header/KoFiButton.tsx index 86418db426..8f1af68d47 100644 --- a/src/renderer/components/Header/KoFiButton.tsx +++ b/src/renderer/components/Header/KoFiButton.tsx @@ -3,7 +3,7 @@ import { memo } from 'react'; import { SiKofi } from 'react-icons/si'; import { links } from '../../../common/links'; import { log } from '../../../common/log'; -import { ipcRenderer } from '../../../common/safeIpc'; +import { ipcRenderer } from '../../safeIpc'; export const KoFiButton = memo(() => { return ( diff --git a/src/renderer/components/SettingsModal.tsx b/src/renderer/components/SettingsModal.tsx index 4347876cf4..89d67c9375 100644 --- a/src/renderer/components/SettingsModal.tsx +++ b/src/renderer/components/SettingsModal.tsx @@ -31,9 +31,9 @@ import { BsFillPencilFill, BsPaletteFill } from 'react-icons/bs'; import { FaPython, FaTools } from 'react-icons/fa'; import { useContext } from 'use-context-selector'; import { isMac } from '../../common/env'; -import { ipcRenderer } from '../../common/safeIpc'; import { BackendContext } from '../contexts/BackendContext'; import { useMutSetting } from '../contexts/SettingsContext'; +import { ipcRenderer } from '../safeIpc'; import { IconFactory } from './CustomIcons'; import { DropdownSetting, NumberSetting, ToggleSetting } from './settings/components'; import { SettingContainer } from './settings/SettingContainer'; diff --git a/src/renderer/components/groups/NcnnFileInputsGroup.tsx b/src/renderer/components/groups/NcnnFileInputsGroup.tsx index 5dd9854200..943fddaa26 100644 --- a/src/renderer/components/groups/NcnnFileInputsGroup.tsx +++ b/src/renderer/components/groups/NcnnFileInputsGroup.tsx @@ -1,7 +1,7 @@ import { memo, useEffect } from 'react'; import { log } from '../../../common/log'; -import { ipcRenderer } from '../../../common/safeIpc'; import { getInputValue } from '../../../common/util'; +import { ipcRenderer } from '../../safeIpc'; import { SchemaInput } from '../inputs/SchemaInput'; import { GroupProps } from './props'; diff --git a/src/renderer/components/inputs/DirectoryInput.tsx b/src/renderer/components/inputs/DirectoryInput.tsx index f6d0f76869..83da970452 100644 --- a/src/renderer/components/inputs/DirectoryInput.tsx +++ b/src/renderer/components/inputs/DirectoryInput.tsx @@ -9,16 +9,17 @@ import { MenuList, Tooltip, } from '@chakra-ui/react'; -import { clipboard, shell } from 'electron'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { BsFolderPlus } from 'react-icons/bs'; import { MdContentCopy, MdFolder } from 'react-icons/md'; -import { ipcRenderer } from '../../../common/safeIpc'; +import { log } from '../../../common/log'; + import { getFields, isDirectory } from '../../../common/types/util'; import { useContextMenu } from '../../hooks/useContextMenu'; import { useInputRefactor } from '../../hooks/useInputRefactor'; import { useLastDirectory } from '../../hooks/useLastDirectory'; +import { ipcRenderer } from '../../safeIpc'; import { AutoLabel } from './InputContainer'; import { InputProps } from './props'; @@ -79,7 +80,9 @@ export const DirectoryInput = memo( isDisabled={!displayDirectory} onClick={() => { if (displayDirectory) { - shell.showItemInFolder(displayDirectory); + ipcRenderer + .invoke('shell-showItemInFolder', displayDirectory) + .catch(log.error); } }} > @@ -90,7 +93,7 @@ export const DirectoryInput = memo( isDisabled={!displayDirectory} onClick={() => { if (displayDirectory) { - clipboard.writeText(displayDirectory); + navigator.clipboard.writeText(displayDirectory).catch(log.error); } }} > diff --git a/src/renderer/components/inputs/FileInput.tsx b/src/renderer/components/inputs/FileInput.tsx index 982ab1a50b..9cccf2a8f5 100644 --- a/src/renderer/components/inputs/FileInput.tsx +++ b/src/renderer/components/inputs/FileInput.tsx @@ -9,19 +9,20 @@ import { Tooltip, VStack, } from '@chakra-ui/react'; -import { clipboard, shell } from 'electron'; import path from 'path'; import { DragEvent, memo } from 'react'; import { useTranslation } from 'react-i18next'; import { BsFileEarmarkPlus } from 'react-icons/bs'; import { MdContentCopy, MdFolder } from 'react-icons/md'; import { useContext } from 'use-context-selector'; -import { ipcRenderer } from '../../../common/safeIpc'; +import { log } from '../../../common/log'; + import { AlertBoxContext } from '../../contexts/AlertBoxContext'; import { getSingleFileWithExtension } from '../../helpers/dataTransfer'; import { useContextMenu } from '../../hooks/useContextMenu'; import { useInputRefactor } from '../../hooks/useInputRefactor'; import { useLastDirectory } from '../../hooks/useLastDirectory'; +import { ipcRenderer } from '../../safeIpc'; import { WithLabel } from './InputContainer'; import { InputProps } from './props'; @@ -120,7 +121,7 @@ export const FileInput = memo( isDisabled={!filePath} onClick={() => { if (filePath) { - shell.showItemInFolder(filePath); + ipcRenderer.invoke('shell-showItemInFolder', filePath).catch(log.error); } }} > @@ -131,7 +132,7 @@ export const FileInput = memo( isDisabled={!filePath} onClick={() => { if (filePath) { - clipboard.writeText(path.parse(filePath).name); + navigator.clipboard.writeText(filePath).catch(log.error); } }} > @@ -142,7 +143,7 @@ export const FileInput = memo( isDisabled={!filePath} onClick={() => { if (filePath) { - clipboard.writeText(filePath); + navigator.clipboard.writeText(filePath).catch(log.error); } }} > diff --git a/src/renderer/components/inputs/NumberInput.tsx b/src/renderer/components/inputs/NumberInput.tsx index a69641f40c..aade255150 100644 --- a/src/renderer/components/inputs/NumberInput.tsx +++ b/src/renderer/components/inputs/NumberInput.tsx @@ -1,9 +1,9 @@ import { isNumericLiteral } from '@chainner/navi'; import { HStack, MenuItem, MenuList } from '@chakra-ui/react'; -import { clipboard } from 'electron'; import { memo, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { MdContentCopy, MdContentPaste } from 'react-icons/md'; +import { log } from '../../../common/log'; import { areApproximatelyEqual } from '../../../common/util'; import { useContextMenu } from '../../hooks/useContextMenu'; import { useInputRefactor } from '../../hooks/useInputRefactor'; @@ -58,7 +58,7 @@ export const NumberInput = memo( icon={} isDisabled={!displayString} onClick={() => { - clipboard.writeText(displayString); + navigator.clipboard.writeText(displayString).catch(log.error); }} > {t('inputs.number.copyText', 'Copy Number')} @@ -66,14 +66,19 @@ export const NumberInput = memo( } onClick={() => { - const n = Number(clipboard.readText()); - if ( - !Number.isNaN(n) && - (min == null || min <= n) && - (max == null || max >= n) - ) { - setValue(n); - } + navigator.clipboard + .readText() + .then((clipboardValue) => { + const n = Number(clipboardValue); + if ( + !Number.isNaN(n) && + (min == null || min <= n) && + (max == null || max >= n) + ) { + setValue(n); + } + }) + .catch(log.error); }} > {t('inputs.number.paste', 'Paste')} diff --git a/src/renderer/components/inputs/SliderInput.tsx b/src/renderer/components/inputs/SliderInput.tsx index cab155e9ca..c21743eb30 100644 --- a/src/renderer/components/inputs/SliderInput.tsx +++ b/src/renderer/components/inputs/SliderInput.tsx @@ -1,11 +1,12 @@ import { isNumericLiteral } from '@chainner/navi'; import { HStack, MenuItem, MenuList, Text, VStack } from '@chakra-ui/react'; -import { clipboard } from 'electron'; import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { MdContentCopy, MdContentPaste } from 'react-icons/md'; import { useContext, useContextSelector } from 'use-context-selector'; import { Input, OfKind } from '../../../common/common-types'; +import { log } from '../../../common/log'; + import { assertNever } from '../../../common/util'; import { BackendContext } from '../../contexts/BackendContext'; import { InputContext } from '../../contexts/InputContext'; @@ -152,7 +153,7 @@ export const SliderInput = memo( } onClick={() => { - clipboard.writeText(String(displaySliderValue)); + navigator.clipboard.writeText(String(displaySliderValue)).catch(log.error); }} > {t('inputs.number.copyText', 'Copy Number')} @@ -160,10 +161,15 @@ export const SliderInput = memo( } onClick={() => { - const n = Number(clipboard.readText()); - if (!Number.isNaN(n) && min <= n && max >= n) { - setValue(n); - } + navigator.clipboard + .readText() + .then((clipboardValue) => { + const n = Number(clipboardValue); + if (!Number.isNaN(n) && min <= n && max >= n) { + setValue(n); + } + }) + .catch(log.error); }} > {t('inputs.number.paste', 'Paste')} diff --git a/src/renderer/components/inputs/TextInput.tsx b/src/renderer/components/inputs/TextInput.tsx index b7d017a2ec..c0505df9d2 100644 --- a/src/renderer/components/inputs/TextInput.tsx +++ b/src/renderer/components/inputs/TextInput.tsx @@ -1,11 +1,11 @@ import { Center, Input, MenuItem, MenuList, Textarea } from '@chakra-ui/react'; -import { clipboard } from 'electron'; import { Resizable } from 're-resizable'; import { ChangeEvent, memo, useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { MdContentCopy, MdContentPaste } from 'react-icons/md'; import { useContextSelector } from 'use-context-selector'; import { useDebouncedCallback } from 'use-debounce'; +import { log } from '../../../common/log'; import { GlobalVolatileContext } from '../../contexts/GlobalNodeState'; import { typeToString } from '../../helpers/naviHelpers'; import { useContextMenu } from '../../hooks/useContextMenu'; @@ -90,7 +90,7 @@ export const TextInput = memo( isDisabled={!displayText} onClick={() => { if (displayText !== undefined) { - clipboard.writeText(displayText); + navigator.clipboard.writeText(displayText).catch(log.error); } }} > @@ -100,12 +100,19 @@ export const TextInput = memo( icon={} isDisabled={isConnected} onClick={() => { - let text = clipboard.readText(); - // replace new lines - text = text.replace(/\r?\n|\r/g, multiline ? '\n' : ' '); - if (text) { - inputValue(text, false); - } + navigator.clipboard + .readText() + .then((clipboardValue) => { + // replace new lines + const text = clipboardValue.replace( + /\r?\n|\r/g, + multiline ? '\n' : ' ' + ); + if (text) { + inputValue(text, false); + } + }) + .catch(log.error); }} > {t('inputs.text.paste', 'Paste')} diff --git a/src/renderer/components/node/special/NoteNode.tsx b/src/renderer/components/node/special/NoteNode.tsx index 3594d2e77e..385b00011a 100644 --- a/src/renderer/components/node/special/NoteNode.tsx +++ b/src/renderer/components/node/special/NoteNode.tsx @@ -14,7 +14,6 @@ import { Tooltip, VStack, } from '@chakra-ui/react'; -import { clipboard } from 'electron'; import { Resizable } from 're-resizable'; import { ChangeEvent, memo, useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -23,6 +22,7 @@ import { MdContentCopy, MdContentPaste } from 'react-icons/md'; import { useContextSelector } from 'use-context-selector'; import { useDebouncedCallback } from 'use-debounce'; import { InputId, NodeData, Size } from '../../../../common/common-types'; +import { log } from '../../../../common/log'; import { GlobalVolatileContext } from '../../../contexts/GlobalNodeState'; import { useNodeStateFromData } from '../../../helpers/nodeState'; import { useContextMenu } from '../../../hooks/useContextMenu'; @@ -128,7 +128,7 @@ const NoteNodeInner = memo(({ data, selected }: NodeProps) => { icon={} onClick={() => { if (value !== undefined) { - clipboard.writeText(value.toString()); + navigator.clipboard.writeText(value.toString()).catch(log.error); } }} > @@ -137,12 +137,16 @@ const NoteNodeInner = memo(({ data, selected }: NodeProps) => { } onClick={() => { - let text = clipboard.readText(); - // replace new lines - text = text.replace(/\r?\n|\r/g, '\n'); - if (text) { - setInputValue(textInputId, text); - } + navigator.clipboard + .readText() + .then((clipboardValue) => { + // replace new lines + const text = clipboardValue.replace(/\r?\n|\r/g, '\n'); + if (text) { + setInputValue(textInputId, text); + } + }) + .catch(log.error); }} > {t('inputs.text.paste', 'Paste')} diff --git a/src/renderer/components/settings/components.tsx b/src/renderer/components/settings/components.tsx index 9a38627ea9..eb9fd1e5b4 100644 --- a/src/renderer/components/settings/components.tsx +++ b/src/renderer/components/settings/components.tsx @@ -14,7 +14,7 @@ import { memo, useEffect, useMemo } from 'react'; import { Setting } from '../../../common/common-types'; import { getCacheLocation } from '../../../common/env'; import { log } from '../../../common/log'; -import { ipcRenderer } from '../../../common/safeIpc'; +import { ipcRenderer } from '../../safeIpc'; import { SettingsProps } from './props'; import { SettingContainer } from './SettingContainer'; diff --git a/src/renderer/contexts/AlertBoxContext.tsx b/src/renderer/contexts/AlertBoxContext.tsx index e62f8dd7f1..ed751253cd 100644 --- a/src/renderer/contexts/AlertBoxContext.tsx +++ b/src/renderer/contexts/AlertBoxContext.tsx @@ -16,14 +16,13 @@ import { useDisclosure, useToast, } from '@chakra-ui/react'; -import { app, clipboard, shell } from 'electron'; import path from 'path'; import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { createContext, useContext, useContextSelector } from 'use-context-selector'; import { log } from '../../common/log'; -import { ipcRenderer } from '../../common/safeIpc'; import { assertNever, noop } from '../../common/util'; import { useMemoObject } from '../hooks/useMemo'; +import { ipcRenderer } from '../safeIpc'; import { ContextMenuContext } from './ContextMenuContext'; import { HotkeysContext } from './HotKeyContext'; @@ -182,9 +181,11 @@ const getButtons = ( ipcRenderer .invoke('get-appdata') .then((appDataPath) => { - shell.openPath(path.join(appDataPath, 'logs')).catch(() => { - log.error('Failed to open logs folder'); - }); + ipcRenderer + .invoke('shell-openPath', path.join(appDataPath, 'logs')) + .catch(() => { + log.error('Failed to open logs folder'); + }); }) .catch(() => { log.error('Failed to get appdata path'); @@ -203,7 +204,9 @@ const getButtons = ( ref={cancelRef} onClick={() => { window.close(); - app.quit(); + ipcRenderer.invoke('app-quit').catch(() => { + log.error('Failed to quit application'); + }); }} > Exit Application @@ -315,7 +318,11 @@ const AlertBoxDialog = memo( background="transparent" icon={} title="Copy to Clipboard" - onClick={() => clipboard.writeText(copyText)} + onClick={() => { + navigator.clipboard + .writeText(copyText.toString()) + .catch(log.error); + }} /> { - clipboard.writeText(command); - onPopoverToggle(); - setTimeout(() => { - onPopoverClose(); - }, 5000); + navigator.clipboard + .writeText(command) + .then(() => { + onPopoverToggle(); + setTimeout(() => { + onPopoverClose(); + }, 5000); + }) + .catch(log.error); }; const installPackage = (pkg: Package) => { diff --git a/src/renderer/contexts/ExecutionContext.tsx b/src/renderer/contexts/ExecutionContext.tsx index bfb1152fe2..591e3863a3 100644 --- a/src/renderer/contexts/ExecutionContext.tsx +++ b/src/renderer/contexts/ExecutionContext.tsx @@ -11,7 +11,6 @@ import { checkNodeValidity } from '../../common/nodes/checkNodeValidity'; import { getConnectedInputs } from '../../common/nodes/connectedInputs'; import { optimizeChain } from '../../common/nodes/optimize'; import { toBackendJson } from '../../common/nodes/toBackendJson'; -import { ipcRenderer } from '../../common/safeIpc'; import { getChainnerScope } from '../../common/types/chainner-scope'; import { fromJson } from '../../common/types/json'; import { EMPTY_MAP, EMPTY_SET, assertNever, groupBy } from '../../common/util'; @@ -29,6 +28,7 @@ import { } from '../hooks/useBackendEventSource'; import { EventBacklog, useEventBacklog } from '../hooks/useEventBacklog'; import { useMemoObject } from '../hooks/useMemo'; +import { ipcRenderer } from '../safeIpc'; import { AlertBoxContext, AlertType } from './AlertBoxContext'; import { BackendContext } from './BackendContext'; import { GlobalContext, GlobalVolatileContext } from './GlobalNodeState'; diff --git a/src/renderer/contexts/GlobalNodeState.tsx b/src/renderer/contexts/GlobalNodeState.tsx index d04b3a2d8b..caee8744ae 100644 --- a/src/renderer/contexts/GlobalNodeState.tsx +++ b/src/renderer/contexts/GlobalNodeState.tsx @@ -28,7 +28,6 @@ import { log } from '../../common/log'; import { getEffectivelyDisabledNodes } from '../../common/nodes/disabled'; import { ChainLineage } from '../../common/nodes/lineage'; import { TypeState } from '../../common/nodes/TypeState'; -import { ipcRenderer } from '../../common/safeIpc'; import { ParsedSaveData, SaveData } from '../../common/SaveFile'; import { @@ -84,6 +83,7 @@ import { useOutputDataStore, } from '../hooks/useOutputDataStore'; import { getSessionStorageOrDefault, useSessionStorage } from '../hooks/useSessionStorage'; +import { ipcRenderer } from '../safeIpc'; import { AlertBoxContext, AlertType } from './AlertBoxContext'; import { BackendContext } from './BackendContext'; import { useSettings } from './SettingsContext'; @@ -1191,7 +1191,7 @@ export const GlobalProvider = memo( createNode, screenToFlowPosition, reactFlowWrapper - ); + ).catch(log.error); }, [changeNodes, changeEdges, createNode, screenToFlowPosition, reactFlowWrapper]); const selectAllFn = useCallback(() => { changeNodes((nodes) => nodes.map((n) => ({ ...n, selected: true }))); diff --git a/src/renderer/contexts/HotKeyContext.tsx b/src/renderer/contexts/HotKeyContext.tsx index 8834d5a760..abdfab2d6f 100644 --- a/src/renderer/contexts/HotKeyContext.tsx +++ b/src/renderer/contexts/HotKeyContext.tsx @@ -1,8 +1,8 @@ import React, { memo, useCallback, useState } from 'react'; import { createContext } from 'use-context-selector'; -import { ipcRenderer } from '../../common/safeIpc'; import { noop } from '../../common/util'; import { useMemoObject } from '../hooks/useMemo'; +import { ipcRenderer } from '../safeIpc'; interface HotkeysContextState { hotkeysEnabled: boolean; diff --git a/src/renderer/contexts/SettingsContext.tsx b/src/renderer/contexts/SettingsContext.tsx index 1be077bbf5..ca0ce9a472 100644 --- a/src/renderer/contexts/SettingsContext.tsx +++ b/src/renderer/contexts/SettingsContext.tsx @@ -2,10 +2,10 @@ import { useColorMode } from '@chakra-ui/react'; import React, { SetStateAction, memo, useCallback, useEffect, useState } from 'react'; import { createContext, useContext } from 'use-context-selector'; import { log } from '../../common/log'; -import { ipcRenderer } from '../../common/safeIpc'; import { ChainnerSettings, defaultSettings } from '../../common/settings/settings'; import { noop } from '../../common/util'; import { useMemoObject } from '../hooks/useMemo'; +import { ipcRenderer } from '../safeIpc'; interface SettingsContextValue { settings: Readonly; diff --git a/src/renderer/helpers/copyAndPaste.ts b/src/renderer/helpers/copyAndPaste.ts index b42e0407d0..574f53276c 100644 --- a/src/renderer/helpers/copyAndPaste.ts +++ b/src/renderer/helpers/copyAndPaste.ts @@ -1,12 +1,11 @@ -import { clipboard } from 'electron'; import os from 'os'; import path from 'path'; import { Edge, Node, Project } from 'reactflow'; import { v4 as uuid4 } from 'uuid'; import { EdgeData, InputId, NodeData, SchemaId } from '../../common/common-types'; import { log } from '../../common/log'; -import { ipcRenderer } from '../../common/safeIpc'; import { createUniqueId, deriveUniqueId } from '../../common/util'; +import { ipcRenderer } from '../safeIpc'; import { NodeProto, copyEdges, copyNodes, setSelected } from './reactFlowUtil'; import { SetState } from './types'; @@ -30,7 +29,9 @@ export const copyToClipboard = ( edges: edges.filter((e) => copyIds.has(e.source) && copyIds.has(e.target)), }; const copyData = Buffer.from(JSON.stringify(data)); - clipboard.writeBuffer('application/chainner.chain', copyData, 'clipboard'); + ipcRenderer + .invoke('clipboard-writeBuffer', 'application/chainner.chain', copyData, 'clipboard') + .catch(log.error); }; export const cutAndCopyToClipboard = ( @@ -52,19 +53,21 @@ export const cutAndCopyToClipboard = ( ); }; -export const pasteFromClipboard = ( +export const pasteFromClipboard = async ( setNodes: SetState[]>, setEdges: SetState[]>, createNode: (proto: NodeProto, parentId?: string) => void, screenToFlowPosition: Project, reactFlowWrapper: React.RefObject ) => { - const availableFormats = clipboard.availableFormats(); + const availableFormats = await ipcRenderer.invoke('clipboard-availableFormats'); if (availableFormats.length === 0) { try { - const chain = JSON.parse( - clipboard.readBuffer('application/chainner.chain').toString() - ) as ClipboardChain; + const clipboardData = await ipcRenderer.invoke( + 'clipboard-readBuffer', + 'application/chainner.chain' + ); + const chain = JSON.parse(Buffer.from(clipboardData).toString()) as ClipboardChain; const duplicationId = createUniqueId(); const deriveId = (oldId: string) => deriveUniqueId(duplicationId + oldId); @@ -93,50 +96,63 @@ export const pasteFromClipboard = ( log.debug('Clipboard format', format); switch (format) { case 'text/plain': - log.debug('Clipboard text', clipboard.readText()); + log.debug('Clipboard text', navigator.clipboard.readText()); break; case 'text/html': - log.debug('Clipboard html', clipboard.readHTML()); + log.debug('Clipboard html', ipcRenderer.invoke('clipboard-readHTML')); break; case 'text/rtf': - log.debug('Clipboard rtf', clipboard.readRTF()); + log.debug('Clipboard rtf', ipcRenderer.invoke('clipboard-readRTF')); break; case 'image/jpeg': case 'image/gif': case 'image/bmp': case 'image/tiff': case 'image/png': { - const imgData = clipboard.readImage().toPNG(); - const imgPath = path.join(os.tmpdir(), `chaiNNer-clipboard-${uuid4()}.png`); ipcRenderer - .invoke('fs-write-file', imgPath, imgData) - .then(() => { - log.debug('Clipboard image', imgPath); - let positionX = 0; - let positionY = 0; - if (reactFlowWrapper.current) { - const { height, width, x, y } = - reactFlowWrapper.current.getBoundingClientRect(); - positionX = (width + x) / 2; - positionY = (height + y) / 2; - } - createNode({ - position: screenToFlowPosition({ x: positionX, y: positionY }), - data: { - schemaId: 'chainner:image:load' as SchemaId, - inputData: { - [0 as InputId]: imgPath, - }, - }, - }); + .invoke('clipboard-readImage') + .then((clipboardData) => { + const imgData = clipboardData.toPNG(); + const imgPath = path.join( + os.tmpdir(), + `chaiNNer-clipboard-${uuid4()}.png` + ); + ipcRenderer + .invoke('fs-write-file', imgPath, imgData) + .then(() => { + log.debug('Clipboard image', imgPath); + let positionX = 0; + let positionY = 0; + if (reactFlowWrapper.current) { + const { height, width, x, y } = + reactFlowWrapper.current.getBoundingClientRect(); + positionX = (width + x) / 2; + positionY = (height + y) / 2; + } + createNode({ + position: screenToFlowPosition({ + x: positionX, + y: positionY, + }), + data: { + schemaId: 'chainner:image:load' as SchemaId, + inputData: { + [0 as InputId]: imgPath, + }, + }, + }); + }) + .catch((e) => { + log.error('Failed to write clipboard image', e); + }); }) .catch((e) => { - log.error('Failed to write clipboard image', e); + log.error('Failed to read clipboard image', e); }); break; } default: - log.debug('Clipboard data', clipboard.readBuffer(format)); + log.debug('Clipboard data', ipcRenderer.invoke('clipboard-readBuffer', format)); } }); } diff --git a/src/renderer/helpers/dataTransfer.ts b/src/renderer/helpers/dataTransfer.ts index 72b9931c1b..96052c5a7f 100644 --- a/src/renderer/helpers/dataTransfer.ts +++ b/src/renderer/helpers/dataTransfer.ts @@ -2,10 +2,10 @@ import { extname } from 'path'; import { Edge, Node, XYPosition } from 'reactflow'; import { EdgeData, NodeData, SchemaId } from '../../common/common-types'; import { log } from '../../common/log'; -import { ipcRenderer } from '../../common/safeIpc'; import { ParsedSaveData, openSaveFile } from '../../common/SaveFile'; import { SchemaMap } from '../../common/SchemaMap'; import { createUniqueId, deriveUniqueId } from '../../common/util'; +import { ipcRenderer } from '../safeIpc'; import { NodeProto, copyEdges, copyNodes, setSelected } from './reactFlowUtil'; import { SetState } from './types'; diff --git a/src/renderer/helpers/nodeScreenshot.ts b/src/renderer/helpers/nodeScreenshot.ts index 5c16eccdfa..f95694f5fd 100644 --- a/src/renderer/helpers/nodeScreenshot.ts +++ b/src/renderer/helpers/nodeScreenshot.ts @@ -1,8 +1,9 @@ -import { clipboard, nativeImage } from 'electron'; import { toPng } from 'html-to-image'; import { Node, ReactFlowInstance } from 'reactflow'; import { EdgeData, NodeData } from '../../common/common-types'; +import { log } from '../../common/log'; import { delay } from '../../common/util'; +import { ipcRenderer } from '../safeIpc'; interface Rect { x: number; @@ -109,6 +110,5 @@ export const saveDataUrlAsFile = (dataUrl: PngDataUrl, fileName: string) => { }; export const writeDataUrlToClipboard = (dataUrl: PngDataUrl) => { - const image = nativeImage.createFromDataURL(dataUrl); - clipboard.writeImage(image); + ipcRenderer.invoke('clipboard-writeImageFromURL', dataUrl).catch(log.error); }; diff --git a/src/renderer/hooks/useInputRefactor.tsx b/src/renderer/hooks/useInputRefactor.tsx index 03b61cae6d..1480f2296c 100644 --- a/src/renderer/hooks/useInputRefactor.tsx +++ b/src/renderer/hooks/useInputRefactor.tsx @@ -1,5 +1,4 @@ import { MenuDivider, MenuItem } from '@chakra-ui/react'; -import { clipboard } from 'electron'; import { useTranslation } from 'react-i18next'; import { CgArrowsExpandUpLeft } from 'react-icons/cg'; import { MdContentCopy } from 'react-icons/md'; @@ -17,6 +16,7 @@ import { SchemaId, } from '../../common/common-types'; import { createInputOverrideId } from '../../common/input-override-common'; +import { log } from '../../common/log'; import { createUniqueId } from '../../common/util'; import { BackendContext } from '../contexts/BackendContext'; import { FakeNodeContext } from '../contexts/FakeExampleContext'; @@ -108,7 +108,9 @@ export const useInputRefactor = ( icon={} key="copy override" onClick={() => { - clipboard.writeText(createInputOverrideId(nodeId, inputId)); + navigator.clipboard + .writeText(createInputOverrideId(nodeId, inputId)) + .catch(log.error); }} > {t('inputs.copyInputOverrideId', 'Copy Input Override Id')} diff --git a/src/renderer/hooks/useIpcRendererListener.ts b/src/renderer/hooks/useIpcRendererListener.ts index dcbb0c0a25..524e94c153 100644 --- a/src/renderer/hooks/useIpcRendererListener.ts +++ b/src/renderer/hooks/useIpcRendererListener.ts @@ -1,6 +1,7 @@ -import { IpcRendererEvent } from 'electron'; +import { IpcRendererEvent } from 'electron'; // TODO: replace with electron/renderer import { useEffect } from 'react'; -import { ChannelArgs, SendChannels, ipcRenderer } from '../../common/safeIpc'; +import { ChannelArgs, SendChannels } from '../../common/safeIpc'; +import { ipcRenderer } from '../safeIpc'; export const useIpcRendererListener = ( channel: C, diff --git a/src/renderer/hooks/useOpenRecent.ts b/src/renderer/hooks/useOpenRecent.ts index 0d3b0e241c..ff049e04be 100644 --- a/src/renderer/hooks/useOpenRecent.ts +++ b/src/renderer/hooks/useOpenRecent.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect } from 'react'; -import { ipcRenderer } from '../../common/safeIpc'; import { EMPTY_ARRAY } from '../../common/util'; +import { ipcRenderer } from '../safeIpc'; import { useIpcRendererListener } from './useIpcRendererListener'; import { useStored } from './useStored'; diff --git a/src/renderer/index.tsx b/src/renderer/index.tsx index 7556a0e1fc..3a86ae9165 100644 --- a/src/renderer/index.tsx +++ b/src/renderer/index.tsx @@ -3,8 +3,8 @@ import path from 'path'; import { createRoot } from 'react-dom/client'; import { QueryClient, QueryClientProvider } from 'react-query'; import { LEVEL_NAME, log } from '../common/log'; -import { ipcRenderer } from '../common/safeIpc'; import { App } from './app'; +import { ipcRenderer } from './safeIpc'; ipcRenderer .invoke('get-appdata') diff --git a/src/renderer/safeIpc.ts b/src/renderer/safeIpc.ts new file mode 100644 index 0000000000..262fbb6073 --- /dev/null +++ b/src/renderer/safeIpc.ts @@ -0,0 +1,33 @@ +import { IpcRendererEvent, ipcRenderer as unsafeIpcRenderer } from 'electron'; // TODO: replace with electron/renderer +import { ChannelArgs, ChannelReturn, InvokeChannels, SendChannels } from '../common/safeIpc'; + +interface SafeIpcRenderer extends Electron.IpcRenderer { + invoke( + channel: C, + ...args: ChannelArgs + ): Promise>; + on( + channel: C, + listener: (event: IpcRendererEvent, ...args: ChannelArgs) => void + ): this; + once( + channel: C, + listener: (event: IpcRendererEvent, ...args: ChannelArgs) => void + ): this; + postMessage(channel: keyof SendChannels, message: unknown, transfer?: MessagePort[]): void; + removeAllListeners(channel: keyof SendChannels): this; + removeListener( + channel: C, + listener: (event: IpcRendererEvent, ...args: ChannelArgs) => void + ): this; + send(channel: C, ...args: ChannelArgs): void; + sendSync(channel: C, ...args: ChannelArgs): void; + sendTo( + webContentsId: number, + channel: C, + ...args: ChannelArgs + ): void; + sendToHost(channel: C, ...args: ChannelArgs): void; +} + +export const ipcRenderer = unsafeIpcRenderer as SafeIpcRenderer; diff --git a/src/renderer/splash.tsx b/src/renderer/splash.tsx index 7eb6926908..3841a9bf67 100644 --- a/src/renderer/splash.tsx +++ b/src/renderer/splash.tsx @@ -3,8 +3,8 @@ import { memo, useEffect, useState } from 'react'; import { createRoot } from 'react-dom/client'; import './i18n'; import { useTranslation } from 'react-i18next'; -import { ipcRenderer } from '../common/safeIpc'; import { ChaiNNerLogo } from './components/chaiNNerLogo'; +import { ipcRenderer } from './safeIpc'; import { theme } from './splashTheme'; const Splash = memo(() => { diff --git a/webpack.main.config.js b/webpack.main.config.js index bb586038ee..af1272f49a 100644 --- a/webpack.main.config.js +++ b/webpack.main.config.js @@ -1,8 +1,11 @@ // eslint-disable-next-line import/no-extraneous-dependencies const CopyPlugin = require('copy-webpack-plugin'); +const isDevelopment = process.env.NODE_ENV !== 'production'; + /** @type {import("webpack").Configuration} */ module.exports = { + target: 'electron-main', /** * This is the main entry point for your application, it's the first file * that runs in the main process. @@ -10,6 +13,7 @@ module.exports = { entry: { main: './src/main/main.ts', }, + mode: isDevelopment ? 'development' : 'production', // Put your normal webpack config below here module: { // eslint-disable-next-line global-require @@ -21,6 +25,7 @@ module.exports = { }), ], resolve: { - extensions: ['.js', '.jsx', '.ts', '.tsx'], + extensions: ['.js', '.ts'], }, + externals: ['electron/common', 'electron/main'], }; diff --git a/webpack.renderer.config.js b/webpack.renderer.config.js index 39fdf31436..d6f0faef94 100644 --- a/webpack.renderer.config.js +++ b/webpack.renderer.config.js @@ -20,6 +20,7 @@ rules.push( /** @type {import("webpack").Configuration} */ module.exports = { + target: 'electron-renderer', // Put your normal webpack config below here module: { rules, @@ -36,9 +37,7 @@ module.exports = { resourceRegExp: /^fsevents$/, }), ].filter(Boolean), - externals: { - fsevents: "require('fsevents')", - }, + externals: ['electron/common', 'electron/renderer', 'fsevents'], resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'], },