├── template ├── .nvmrc ├── index.d.ts ├── .npmrc ├── src │ ├── resources │ │ ├── build │ │ │ └── icons │ │ │ │ ├── icon.ico │ │ │ │ └── icon.icns │ │ └── public │ │ │ └── illustration.svg │ ├── lib │ │ ├── electron-app │ │ │ ├── utils │ │ │ │ ├── index.ts │ │ │ │ ├── ignore-console-warnings.ts │ │ │ │ └── react-devtools.ts │ │ │ ├── extensions │ │ │ │ └── react-developer-tools │ │ │ │ │ ├── icons │ │ │ │ │ ├── 128-deadcode.png │ │ │ │ │ ├── 128-disabled.png │ │ │ │ │ ├── 128-outdated.png │ │ │ │ │ ├── 16-deadcode.png │ │ │ │ │ ├── 16-disabled.png │ │ │ │ │ ├── 16-outdated.png │ │ │ │ │ ├── 32-deadcode.png │ │ │ │ │ ├── 32-disabled.png │ │ │ │ │ ├── 32-outdated.png │ │ │ │ │ ├── 48-deadcode.png │ │ │ │ │ ├── 48-disabled.png │ │ │ │ │ ├── 48-outdated.png │ │ │ │ │ ├── 128-development.png │ │ │ │ │ ├── 128-production.png │ │ │ │ │ ├── 128-restricted.png │ │ │ │ │ ├── 128-unminified.png │ │ │ │ │ ├── 16-development.png │ │ │ │ │ ├── 16-production.png │ │ │ │ │ ├── 16-restricted.png │ │ │ │ │ ├── 16-unminified.png │ │ │ │ │ ├── 32-development.png │ │ │ │ │ ├── 32-production.png │ │ │ │ │ ├── 32-restricted.png │ │ │ │ │ ├── 32-unminified.png │ │ │ │ │ ├── 48-development.png │ │ │ │ │ ├── 48-production.png │ │ │ │ │ ├── 48-restricted.png │ │ │ │ │ ├── 48-unminified.png │ │ │ │ │ ├── outdated.svg │ │ │ │ │ ├── disabled.svg │ │ │ │ │ ├── restricted.svg │ │ │ │ │ ├── production.svg │ │ │ │ │ ├── deadcode.svg │ │ │ │ │ └── development.svg │ │ │ │ │ ├── build │ │ │ │ │ ├── panel.js │ │ │ │ │ ├── prepareInjection.js │ │ │ │ │ ├── fileFetcher.js │ │ │ │ │ ├── hookSettingsInjector.js │ │ │ │ │ ├── proxy.js │ │ │ │ │ ├── background.js │ │ │ │ │ ├── backendManager.js │ │ │ │ │ └── react_devtools_backend_compact.js │ │ │ │ │ ├── main.html │ │ │ │ │ ├── popups │ │ │ │ │ ├── restricted.html │ │ │ │ │ ├── shared.css │ │ │ │ │ ├── production.html │ │ │ │ │ ├── disabled.html │ │ │ │ │ ├── outdated.html │ │ │ │ │ ├── development.html │ │ │ │ │ ├── shared.js │ │ │ │ │ ├── unminified.html │ │ │ │ │ └── deadcode.html │ │ │ │ │ ├── manifest.json │ │ │ │ │ └── panel.html │ │ │ ├── factories │ │ │ │ ├── app │ │ │ │ │ ├── instance.ts │ │ │ │ │ └── setup.ts │ │ │ │ ├── windows │ │ │ │ │ └── create.ts │ │ │ │ └── ipcs │ │ │ │ │ └── register-window-creation.ts │ │ │ └── release │ │ │ │ ├── utils │ │ │ │ ├── extractors.ts │ │ │ │ ├── path.ts │ │ │ │ ├── question.ts │ │ │ │ ├── exec.ts │ │ │ │ └── validations.ts │ │ │ │ ├── constants │ │ │ │ └── colors.ts │ │ │ │ └── modules │ │ │ │ ├── prebuild.ts │ │ │ │ └── release.ts │ │ └── electron-router-dom.ts │ ├── renderer │ │ ├── lib │ │ │ └── utils.ts │ │ ├── routes.tsx │ │ ├── index.tsx │ │ ├── index.html │ │ ├── screens │ │ │ └── main.tsx │ │ ├── components │ │ │ └── ui │ │ │ │ └── alert.tsx │ │ └── globals.css │ ├── shared │ │ ├── constants.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── preload │ │ └── index.ts │ └── main │ │ ├── index.ts │ │ └── windows │ │ └── main.ts ├── trusted-dependencies-scripts.json ├── .vscode │ ├── extensions.json │ └── settings.json ├── .editorconfig ├── .gitignore ├── components.json ├── tsconfig.json ├── electron-builder.ts ├── .github │ └── workflows │ │ └── release.yml ├── biome.json ├── electron.vite.config.ts └── package.json ├── .github └── FUNDING.yml ├── docs ├── images │ ├── preview.png │ └── bullet.svg ├── THEMING.md ├── STRUCTURE.md ├── SOURCE_CODE_PROTECTION.md ├── FAQ.md ├── RELEASING.md └── UNSIGNED_APPS.md ├── .gitignore ├── LICENSE └── README.md /template/.nvmrc: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /template/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: daltonmenezes 2 | patreon: daltonmenezes 3 | 4 | -------------------------------------------------------------------------------- /docs/images/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/docs/images/preview.png -------------------------------------------------------------------------------- /template/.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers=true 2 | shamefully-hoist=true 3 | strict-peer-dependencies=false 4 | -------------------------------------------------------------------------------- /template/src/resources/build/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/resources/build/icons/icon.ico -------------------------------------------------------------------------------- /template/src/resources/build/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/resources/build/icons/icon.icns -------------------------------------------------------------------------------- /template/src/lib/electron-app/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { ignoreConsoleWarnings } from './ignore-console-warnings' 2 | export { loadReactDevtools } from './react-devtools' 3 | -------------------------------------------------------------------------------- /template/trusted-dependencies-scripts.json: -------------------------------------------------------------------------------- 1 | [ 2 | "esbuild", 3 | "electron", 4 | "electron-vite", 5 | "@biomejs/biome", 6 | "@tailwindcss/oxide", 7 | "electron-winstaller" 8 | ] 9 | -------------------------------------------------------------------------------- /template/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["biomejs.biome", "bradlc.vscode-tailwindcss"], 3 | "unwantedRecommendations": [ 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /template/src/renderer/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from 'clsx' 2 | import { twMerge } from 'tailwind-merge' 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/128-deadcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/128-deadcode.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/128-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/128-disabled.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/128-outdated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/128-outdated.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/16-deadcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/16-deadcode.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/16-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/16-disabled.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/16-outdated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/16-outdated.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/32-deadcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/32-deadcode.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/32-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/32-disabled.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/32-outdated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/32-outdated.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/48-deadcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/48-deadcode.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/48-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/48-disabled.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/48-outdated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/48-outdated.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/128-development.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/128-development.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/128-production.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/128-production.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/128-restricted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/128-restricted.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/128-unminified.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/128-unminified.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/16-development.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/16-development.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/16-production.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/16-production.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/16-restricted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/16-restricted.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/16-unminified.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/16-unminified.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/32-development.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/32-development.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/32-production.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/32-production.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/32-restricted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/32-restricted.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/32-unminified.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/32-unminified.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/48-development.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/48-development.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/48-production.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/48-production.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/48-restricted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/48-restricted.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/48-unminified.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonmenezes/electron-app/HEAD/template/src/lib/electron-app/extensions/react-developer-tools/icons/48-unminified.png -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/build/panel.js: -------------------------------------------------------------------------------- 1 | (()=>{window.container=document.getElementById("container");let n=!1;window.injectStyles=e=>{if(!n){n=!0;const t=e();for(const n of t)document.head.appendChild(n)}}})(); -------------------------------------------------------------------------------- /template/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/factories/app/instance.ts: -------------------------------------------------------------------------------- 1 | import { app } from 'electron' 2 | 3 | export function makeAppWithSingleInstanceLock(fn: () => void) { 4 | const isPrimaryInstance = app.requestSingleInstanceLock() 5 | 6 | !isPrimaryInstance ? app.quit() : fn() 7 | } 8 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/release/utils/extractors.ts: -------------------------------------------------------------------------------- 1 | export function extractOwnerAndRepoFromGitRemoteURL(url: string) { 2 | return url 3 | ?.replace(/^git@github.com:|.git$/gims, '') 4 | ?.replace(/^https:\/\/github.com\/|.git$/gims, '') 5 | ?.trim() 6 | } 7 | -------------------------------------------------------------------------------- /template/src/lib/electron-router-dom.ts: -------------------------------------------------------------------------------- 1 | import { createElectronRouter } from 'electron-router-dom' 2 | 3 | export const { Router, registerRoute, settings } = createElectronRouter({ 4 | port: 4927, 5 | 6 | types: { 7 | ids: ['main', 'about'], 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/release/utils/path.ts: -------------------------------------------------------------------------------- 1 | import { normalize, dirname } from 'node:path' 2 | 3 | export function getDevFolder(path: string) { 4 | const [nodeModules, devFolder] = normalize(dirname(path)).split(/\/|\\/g) 5 | 6 | return [nodeModules, devFolder].join('/') 7 | } 8 | -------------------------------------------------------------------------------- /template/src/renderer/routes.tsx: -------------------------------------------------------------------------------- 1 | import { Route } from 'react-router-dom' 2 | 3 | import { Router } from 'lib/electron-router-dom' 4 | 5 | import { MainScreen } from './screens/main' 6 | 7 | export function AppRoutes() { 8 | return } path="/" />} /> 9 | } 10 | -------------------------------------------------------------------------------- /template/src/shared/constants.ts: -------------------------------------------------------------------------------- 1 | export const ENVIRONMENT = { 2 | IS_DEV: process.env.NODE_ENV === 'development', 3 | } 4 | 5 | export const PLATFORM = { 6 | IS_MAC: process.platform === 'darwin', 7 | IS_WINDOWS: process.platform === 'win32', 8 | IS_LINUX: process.platform === 'linux', 9 | } 10 | -------------------------------------------------------------------------------- /template/src/renderer/index.tsx: -------------------------------------------------------------------------------- 1 | import ReactDom from 'react-dom/client' 2 | import React from 'react' 3 | 4 | import { AppRoutes } from './routes' 5 | 6 | import './globals.css' 7 | 8 | ReactDom.createRoot(document.querySelector('app') as HTMLElement).render( 9 | 10 | 11 | 12 | ) 13 | -------------------------------------------------------------------------------- /template/src/preload/index.ts: -------------------------------------------------------------------------------- 1 | import { contextBridge } from 'electron' 2 | 3 | declare global { 4 | interface Window { 5 | App: typeof API 6 | } 7 | } 8 | 9 | const API = { 10 | sayHelloFromBridge: () => console.log('\nHello from bridgeAPI! 👋\n\n'), 11 | username: process.env.USER, 12 | } 13 | 14 | contextBridge.exposeInMainWorld('App', API) 15 | -------------------------------------------------------------------------------- /template/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | .env 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .eslintcache 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/release/constants/colors.ts: -------------------------------------------------------------------------------- 1 | export const COLORS = { 2 | RED: '\x1b[31m', 3 | RESET: '\x1b[0m', 4 | GRAY: '\x1b[90m', 5 | BLUE: '\x1b[34m', 6 | CYAN: '\x1b[36m', 7 | GREEN: '\x1b[32m', 8 | WHITE: '\x1b[37m', 9 | YELLOW: '\x1b[33m', 10 | MAGENTA: '\x1b[35m', 11 | LIGHT_GRAY: '\x1b[37m', 12 | SOFT_GRAY: '\x1b[38;5;244m', 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | pnpm-lock.yaml 6 | 7 | # testing 8 | /coverage 9 | 10 | # production 11 | /build 12 | /dist 13 | 14 | # misc 15 | .DS_Store 16 | .env 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | .eslintcache 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/build/prepareInjection.js: -------------------------------------------------------------------------------- 1 | (()=>{let e;window.addEventListener("message",(function({data:o,source:n}){if(n===window&&o&&"react-devtools-hook"===o.source){const{source:n,payload:s}=o,t={source:n,payload:s};e=t,chrome.runtime.sendMessage(t)}})),window.addEventListener("pageshow",(function({target:o}){e&&o===window.document&&chrome.runtime.sendMessage(e)}))})(); -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/popups/restricted.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 |

11 | This is a restricted browser page. 12 |
13 | React devtools cannot access this page. 14 |

15 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/popups/shared.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-size: 14px; 3 | } 4 | 5 | body { 6 | margin: 8px; 7 | } 8 | 9 | @media (prefers-color-scheme: dark) { 10 | :root { 11 | color-scheme: dark; 12 | } 13 | 14 | @supports (-moz-appearance:none) { 15 | :root { 16 | background: black; 17 | } 18 | 19 | body { 20 | color: white; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/release/utils/question.ts: -------------------------------------------------------------------------------- 1 | import Readline from 'node:readline' 2 | 3 | export function question(question: string): Promise { 4 | const readline = Readline.createInterface({ 5 | input: process.stdin, 6 | output: process.stdout, 7 | }) 8 | 9 | return new Promise(resolve => { 10 | readline.question(question, answer => { 11 | readline.close() 12 | resolve(answer) 13 | }) 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/popups/production.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 |

14 | This page is using the production build of React. ✅ 15 |
16 | Open the developer tools, and "Components" and "Profiler" tabs will appear to the right. 17 |

18 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/utils/ignore-console-warnings.ts: -------------------------------------------------------------------------------- 1 | export function ignoreConsoleWarnings(warningsToIgnore: string[]) { 2 | const originalEmitWarning = process.emitWarning 3 | 4 | process.emitWarning = (warning, ...args) => { 5 | if ( 6 | typeof warning === 'string' && 7 | warningsToIgnore.length > 0 && 8 | warningsToIgnore.some(ignoredWarning => warning.includes(ignoredWarning)) 9 | ) { 10 | return 11 | } 12 | 13 | originalEmitWarning(warning, ...(args as string[])) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /template/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/renderer/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "renderer/components", 15 | "utils": "renderer/utils", 16 | "ui": "renderer/components/ui", 17 | "lib": "renderer/lib", 18 | "hooks": "renderer/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/popups/disabled.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 |

14 | This page doesn’t appear to be using React. 15 |
16 | If this seems wrong, follow the troubleshooting instructions. 17 |

18 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/factories/windows/create.ts: -------------------------------------------------------------------------------- 1 | import { BrowserWindow } from 'electron' 2 | import { join } from 'node:path' 3 | 4 | import type { WindowProps } from 'shared/types' 5 | 6 | import { registerRoute } from 'lib/electron-router-dom' 7 | 8 | export function createWindow({ id, ...settings }: WindowProps) { 9 | const window = new BrowserWindow(settings) 10 | 11 | registerRoute({ 12 | id, 13 | browserWindow: window, 14 | htmlFile: join(__dirname, '../renderer/index.html'), 15 | }) 16 | 17 | window.on('closed', window.destroy) 18 | 19 | return window 20 | } 21 | -------------------------------------------------------------------------------- /template/src/shared/types.ts: -------------------------------------------------------------------------------- 1 | import type { BrowserWindow, IpcMainInvokeEvent } from 'electron' 2 | 3 | import type { registerRoute } from 'lib/electron-router-dom' 4 | 5 | export type BrowserWindowOrNull = Electron.BrowserWindow | null 6 | 7 | type Route = Parameters[0] 8 | 9 | export interface WindowProps extends Electron.BrowserWindowConstructorOptions { 10 | id: Route['id'] 11 | query?: Route['query'] 12 | } 13 | 14 | export interface WindowCreationByIPC { 15 | channel: string 16 | window(): BrowserWindowOrNull 17 | callback(window: BrowserWindow, event: IpcMainInvokeEvent): void 18 | } 19 | -------------------------------------------------------------------------------- /docs/THEMING.md: -------------------------------------------------------------------------------- 1 |

Theming

2 | 3 | As the app uses [shadcn/ui](https://ui.shadcn.com/) components, theming is done following their guidelines. Check their [Theming documentation](https://ui.shadcn.com/docs/theming) for more details. 4 | 5 | ## Changing the default theme 6 | By default, the app uses the `dark` theme, you can change it by modifying the `class` attribute in the `html` tag in the `src/renderer/index.html` file: 7 | 8 | ```html 9 | 10 | 11 | ``` -------------------------------------------------------------------------------- /template/src/lib/electron-app/utils/react-devtools.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'node:path' 2 | import { session } from 'electron' 3 | 4 | export async function loadReactDevtools() { 5 | const reactDevToolsPath = resolve( 6 | 'src', 7 | 'lib', 8 | 'electron-app', 9 | 'extensions', 10 | 'react-developer-tools' 11 | ) 12 | 13 | try { 14 | await session.defaultSession.extensions.loadExtension(reactDevToolsPath, { 15 | allowFileAccess: true, 16 | }) 17 | 18 | console.log('\nReact Developer Tools loaded!\n') 19 | } catch (err) { 20 | console.error('Error loading React Developer Tools:', err) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/factories/ipcs/register-window-creation.ts: -------------------------------------------------------------------------------- 1 | import { ipcMain } from 'electron' 2 | 3 | import type { WindowCreationByIPC, BrowserWindowOrNull } from 'shared/types' 4 | 5 | export function registerWindowCreationByIPC({ 6 | channel, 7 | callback, 8 | window: createWindow, 9 | }: WindowCreationByIPC) { 10 | let window: BrowserWindowOrNull 11 | 12 | ipcMain.handle(channel, event => { 13 | if (!createWindow || window) return 14 | 15 | window = createWindow() as NonNullable 16 | 17 | window.on('closed', () => { 18 | window = null 19 | }) 20 | 21 | callback?.(window, event) 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /template/src/renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/build/fileFetcher.js: -------------------------------------------------------------------------------- 1 | (()=>{function fetchResource(e){const reject=t=>{chrome.runtime.sendMessage({source:"react-devtools-fetch-resource-content-script",payload:{type:"fetch-file-with-cache-error",url:e,value:t}})};fetch(e,{cache:"force-cache",signal:AbortSignal.timeout(6e4)}).then((t=>{t.ok?t.text().then((t=>{return c=t,void chrome.runtime.sendMessage({source:"react-devtools-fetch-resource-content-script",payload:{type:"fetch-file-with-cache-complete",url:e,value:c}});var c})).catch((e=>reject(null))):reject(null)}),(e=>reject(null)))}chrome.runtime.onMessage.addListener((e=>{"devtools-page"===e?.source&&"fetch-file-with-cache"===e?.payload?.type&&fetchResource(e.payload.url)}))})(); -------------------------------------------------------------------------------- /template/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[javascript]": { 3 | "editor.formatOnSave": true 4 | }, 5 | 6 | "[json]": { 7 | "editor.formatOnSave": true 8 | }, 9 | 10 | "[typescript]": { 11 | "editor.formatOnSave": true 12 | }, 13 | 14 | "[typescriptreact]": { 15 | "editor.formatOnSave": true 16 | }, 17 | 18 | "biome.enabled": true, 19 | 20 | "editor.codeActionsOnSave": { 21 | "source.fixAll.biome": "explicit" 22 | }, 23 | 24 | "editor.defaultFormatter": "biomejs.biome", 25 | 26 | "editor.quickSuggestions": { 27 | "strings": "on" 28 | }, 29 | 30 | "files.associations": { 31 | "*.css": "tailwindcss" 32 | }, 33 | 34 | "tailwindCSS.experimental.configFile": "src/renderer/globals.css" 35 | } 36 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/popups/outdated.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 |

14 | This page is using an outdated version of React. ⌛ 15 |

16 |

17 | We recommend updating React to ensure that you receive important bugfixes and performance improvements. 18 |
19 |
20 | You can find the upgrade instructions on the React blog. 21 |

22 |
23 |

24 | Open the developer tools, and "Components" and "Profiler" tabs will appear to the right. 25 |

26 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/popups/development.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 |

14 | This page is using the development build of React. 🚧 15 |

16 |

17 | Note that the development build is not suitable for production. 18 |
19 | Make sure to use the production build before deployment. 20 |

21 |
22 |

23 | Open the developer tools, and "Components" and "Profiler" tabs will appear to the right. 24 |

25 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/release/utils/exec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | execSync, 3 | type ExecSyncOptionsWithStringEncoding, 4 | } from 'node:child_process' 5 | 6 | import { resolve } from 'node:path' 7 | 8 | interface Options { 9 | inherit?: boolean 10 | } 11 | 12 | function makeOptions( 13 | options?: Options 14 | ): Partial { 15 | return { 16 | stdio: options?.inherit ? 'inherit' : 'pipe', 17 | cwd: resolve(), 18 | encoding: 'utf8', 19 | } 20 | } 21 | 22 | export function exec(commands: string[], options?: Options) { 23 | const outputs = [] 24 | 25 | for (const command of commands) { 26 | const output = execSync(command, makeOptions(options)) as string 27 | outputs.push(output) 28 | } 29 | 30 | return outputs 31 | } 32 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/build/hookSettingsInjector.js: -------------------------------------------------------------------------------- 1 | window.addEventListener("message",(async function messageListener(e){if(e.source===window&&"react-devtools-hook-installer"===e.data.source&&e.data.payload.handshake){const e=await chrome.storage.local.get();"boolean"!=typeof e.appendComponentStack&&(e.appendComponentStack=!0),"boolean"!=typeof e.breakOnConsoleErrors&&(e.breakOnConsoleErrors=!1),"boolean"!=typeof e.showInlineWarningsAndErrors&&(e.showInlineWarningsAndErrors=!0),"boolean"!=typeof e.hideConsoleLogsInStrictMode&&(e.hideConsoleLogsInStrictMode=!1),window.postMessage({source:"react-devtools-hook-settings-injector",payload:{settings:e}}),window.removeEventListener("message",messageListener)}})),window.postMessage({source:"react-devtools-hook-settings-injector",payload:{handshake:!0}}); -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/popups/shared.js: -------------------------------------------------------------------------------- 1 | /* globals chrome */ 2 | 3 | 'use strict'; 4 | 5 | document.addEventListener('DOMContentLoaded', function () { 6 | // Make links work 7 | const links = document.getElementsByTagName('a'); 8 | for (let i = 0; i < links.length; i++) { 9 | (function () { 10 | const ln = links[i]; 11 | const location = ln.href; 12 | ln.onclick = function () { 13 | chrome.tabs.create({active: true, url: location}); 14 | return false; 15 | }; 16 | })(); 17 | } 18 | 19 | // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=428044 20 | document.body.style.opacity = 0; 21 | document.body.style.transition = 'opacity ease-out .4s'; 22 | requestAnimationFrame(function () { 23 | document.body.style.opacity = 1; 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /template/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "esnext", 5 | "lib": ["esnext", "dom", "dom.iterable"], 6 | "jsx": "react-jsx", 7 | "importHelpers": true, 8 | "moduleResolution": "bundler", 9 | "module": "ESNext", 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "sourceMap": true, 13 | "isolatedModules": true, 14 | "allowJs": true, 15 | "allowSyntheticDefaultImports": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "noEmit": true, 19 | "baseUrl": ".", 20 | "paths": { 21 | "*": ["src/*"], 22 | "~/*": ["./*"] 23 | } 24 | }, 25 | "include": [ 26 | "./src", 27 | "electron.vite.config.*", 28 | "electron-builder.ts", 29 | "index.d.ts" 30 | ], 31 | "exclude": ["node_modules"] 32 | } 33 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/popups/unminified.html: -------------------------------------------------------------------------------- 1 | 2 | 17 |

18 | This page is using an unminified build of React. 🚧 19 |

20 |

21 | The React build on this page appears to be unminified. 22 |
23 | This makes its size larger, and causes React to run slower. 24 |
25 |
26 | Make sure to set up minification before deployment. 27 |

28 |
29 |

30 | Open the developer tools, and "Components" and "Profiler" tabs will appear to the right. 31 |

32 | -------------------------------------------------------------------------------- /template/src/main/index.ts: -------------------------------------------------------------------------------- 1 | import { app } from 'electron' 2 | 3 | import { makeAppWithSingleInstanceLock } from 'lib/electron-app/factories/app/instance' 4 | import { makeAppSetup } from 'lib/electron-app/factories/app/setup' 5 | import { loadReactDevtools } from 'lib/electron-app/utils' 6 | import { ENVIRONMENT } from 'shared/constants' 7 | import { MainWindow } from './windows/main' 8 | import { waitFor } from 'shared/utils' 9 | 10 | makeAppWithSingleInstanceLock(async () => { 11 | await app.whenReady() 12 | const window = await makeAppSetup(MainWindow) 13 | 14 | if (ENVIRONMENT.IS_DEV) { 15 | await loadReactDevtools() 16 | /* This trick is necessary to get the new 17 | React Developer Tools working at app initial load. 18 | Otherwise, it only works on manual reload. 19 | */ 20 | window.webContents.once('devtools-opened', async () => { 21 | await waitFor(1000) 22 | window.webContents.reload() 23 | }) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/popups/deadcode.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 |

14 | This page includes an extra development build of React. 🚧 15 |

16 |

17 | The React build on this page includes both development and production versions because dead code elimination has not been applied correctly. 18 |
19 |
20 | This makes its size larger, and causes React to run slower. 21 |
22 |
23 | Make sure to set up dead code elimination before deployment. 24 |

25 |
26 |

27 | Open the developer tools, and "Components" and "Profiler" tabs will appear to the right. 28 |

29 | -------------------------------------------------------------------------------- /template/src/shared/utils.ts: -------------------------------------------------------------------------------- 1 | import { author as _author, name } from '~/package.json' 2 | 3 | const author = _author.name ?? _author 4 | const authorInKebabCase = author.replace(/\s+/g, '-') 5 | const appId = `com.${authorInKebabCase}.${name}`.toLowerCase() 6 | 7 | /** 8 | * @param {string} id 9 | * @description Create the app id using the name and author from package.json transformed to kebab case if the id is not provided. 10 | * @default 'com.{author}.{app}' - the author and app comes from package.json 11 | * @example 12 | * makeAppId('com.example.app') 13 | * // => 'com.example.app' 14 | */ 15 | export function makeAppId(id: string = appId): string { 16 | return id 17 | } 18 | 19 | /** 20 | * 21 | * @param {number} ms 22 | * @description Wait for a given number of milliseconds. 23 | * @example 24 | * await waitFor(1000) // Waits for 1 second 25 | */ 26 | export function waitFor(ms: number) { 27 | return new Promise(resolve => setTimeout(resolve, ms)) 28 | } 29 | -------------------------------------------------------------------------------- /docs/STRUCTURE.md: -------------------------------------------------------------------------------- 1 |

Structure Overview

2 | 3 |
4 | 5 | ## src/lib 6 | 7 | A folder containing lib configurations/instances. 8 | 9 | ## src/main 10 | 11 | A folder containing the main process files and folders. 12 | 13 | ## src/renderer 14 | 15 | A folder containing the renderer process files and folders. ReactJS lives here! 16 | 17 | ## src/preload 18 | A folder containing the preload script that expose the API connection between main and renderer world by IPC in the context bridge. 19 | 20 | ## src/resources 21 | 22 | A folder containing public assets and assets for the build process like icons. 23 | 24 | > **Note**: all the content inside the **public** folder will be copied to the builded version as its. 25 | 26 | ## src/shared 27 | 28 | A folder containing data shared between one or more processes, such as constants, utilities, types, etc. 29 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/release/utils/validations.ts: -------------------------------------------------------------------------------- 1 | import semver from 'semver' 2 | 3 | import { COLORS } from '../constants/colors' 4 | 5 | export function checkValidations({ 6 | version, 7 | newVersion, 8 | }: { 9 | version: string 10 | newVersion: string 11 | }) { 12 | if (!newVersion) { 13 | console.log(`${COLORS.RED}No version entered${COLORS.RESET}`) 14 | 15 | return true 16 | } 17 | 18 | if (!semver.valid(newVersion)) { 19 | console.log( 20 | `${COLORS.RED}Version must have a semver format (${COLORS.SOFT_GRAY}x.x.x${COLORS.RESET} example: ${COLORS.GREEN}1.0.1${COLORS.RESET}${COLORS.RED})${COLORS.RESET}` 21 | ) 22 | 23 | return true 24 | } 25 | 26 | if (semver.ltr(newVersion, version)) { 27 | console.log( 28 | `${COLORS.RED}New version is lower than current version${COLORS.RESET}` 29 | ) 30 | 31 | return true 32 | } 33 | 34 | if (semver.eq(newVersion, version)) { 35 | console.log( 36 | `${COLORS.RED}New version is equal to current version${COLORS.RESET}` 37 | ) 38 | 39 | return true 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /template/src/main/windows/main.ts: -------------------------------------------------------------------------------- 1 | import { BrowserWindow } from 'electron' 2 | import { join } from 'node:path' 3 | 4 | import { createWindow } from 'lib/electron-app/factories/windows/create' 5 | import { ENVIRONMENT } from 'shared/constants' 6 | import { displayName } from '~/package.json' 7 | 8 | export async function MainWindow() { 9 | const window = createWindow({ 10 | id: 'main', 11 | title: displayName, 12 | width: 700, 13 | height: 473, 14 | show: false, 15 | center: true, 16 | movable: true, 17 | resizable: false, 18 | alwaysOnTop: true, 19 | autoHideMenuBar: true, 20 | 21 | webPreferences: { 22 | preload: join(__dirname, '../preload/index.js'), 23 | }, 24 | }) 25 | 26 | window.webContents.on('did-finish-load', () => { 27 | if (ENVIRONMENT.IS_DEV) { 28 | window.webContents.openDevTools({ mode: 'detach' }) 29 | } 30 | 31 | window.show() 32 | }) 33 | 34 | window.on('close', () => { 35 | for (const window of BrowserWindow.getAllWindows()) { 36 | window.destroy() 37 | } 38 | }) 39 | 40 | return window 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 - * Dalton Menezes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /template/src/renderer/screens/main.tsx: -------------------------------------------------------------------------------- 1 | import { Terminal } from 'lucide-react' 2 | import { useEffect } from 'react' 3 | 4 | import { 5 | Alert, 6 | AlertTitle, 7 | AlertDescription, 8 | } from 'renderer/components/ui/alert' 9 | 10 | // The "App" comes from the context bridge in preload/index.ts 11 | const { App } = window 12 | 13 | export function MainScreen() { 14 | useEffect(() => { 15 | // check the console on dev tools 16 | App.sayHelloFromBridge() 17 | }, []) 18 | 19 | const userName = App.username || 'there' 20 | 21 | return ( 22 |
23 | 24 | 25 | Hi, {userName}! 26 | 27 | 28 | 29 | 30 | 31 | 32 | It's time to build something awesome! 33 | 34 | 35 | 36 |
37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/factories/app/setup.ts: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow } from 'electron' 2 | 3 | import { PLATFORM, ENVIRONMENT } from 'shared/constants' 4 | import { ignoreConsoleWarnings } from '../../utils' 5 | import { makeAppId } from 'shared/utils' 6 | 7 | ignoreConsoleWarnings(['Manifest version 2 is deprecated']) 8 | 9 | export async function makeAppSetup(createWindow: () => Promise) { 10 | let window = await createWindow() 11 | 12 | app.on('activate', async () => { 13 | const windows = BrowserWindow.getAllWindows() 14 | 15 | if (!windows.length) { 16 | window = await createWindow() 17 | } else { 18 | for (window of windows.reverse()) { 19 | window.restore() 20 | } 21 | } 22 | }) 23 | 24 | app.on('web-contents-created', (_, contents) => 25 | contents.on( 26 | 'will-navigate', 27 | (event, _) => !ENVIRONMENT.IS_DEV && event.preventDefault() 28 | ) 29 | ) 30 | 31 | app.on('window-all-closed', () => !PLATFORM.IS_MAC && app.quit()) 32 | 33 | return window 34 | } 35 | 36 | PLATFORM.IS_LINUX && app.disableHardwareAcceleration() 37 | 38 | PLATFORM.IS_WINDOWS && 39 | app.setAppUserModelId(ENVIRONMENT.IS_DEV ? process.execPath : makeAppId()) 40 | 41 | app.commandLine.appendSwitch('force-color-profile', 'srgb') 42 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/release/modules/prebuild.ts: -------------------------------------------------------------------------------- 1 | /** biome-ignore-all lint/correctness/noUnusedVariables: <> */ 2 | import { resolve, basename } from 'node:path' 3 | import { writeFile } from 'node:fs/promises' 4 | 5 | import trustedDependencies from '../../../../../trusted-dependencies-scripts.json' 6 | import packageJSON from '../../../../../package.json' 7 | import { getDevFolder } from '../utils/path' 8 | 9 | async function createPackageJSONDistVersion() { 10 | const { main, scripts, resources, devDependencies, ...rest } = packageJSON 11 | 12 | const packageJSONDistVersion = { 13 | main: `./main/${basename(main || 'index.mjs')}`, 14 | ...rest, 15 | } 16 | 17 | try { 18 | await Promise.all([ 19 | writeFile( 20 | resolve(getDevFolder(main), 'package.json'), 21 | JSON.stringify(packageJSONDistVersion, null, 2) 22 | ), 23 | 24 | writeFile( 25 | resolve(getDevFolder(main), packageJSON.pnpm.onlyBuiltDependenciesFile), 26 | JSON.stringify(trustedDependencies, null, 2) 27 | ), 28 | ]) 29 | } catch ({ message }: any) { 30 | console.log(` 31 | 🛑 Something went wrong!\n 32 | 🧐 There was a problem creating the package.json dist version...\n 33 | 👀 Error: ${message} 34 | `) 35 | } 36 | } 37 | 38 | createPackageJSONDistVersion() 39 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/build/proxy.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";function injectProxy({target:e}){if(!window.__REACT_DEVTOOLS_PROXY_INJECTED__){window.__REACT_DEVTOOLS_PROXY_INJECTED__=!0,connectPort(),sayHelloToBackendManager();const e=setInterval((()=>{n?clearInterval(e):sayHelloToBackendManager()}),500)}}window.addEventListener("pagereveal",injectProxy),window.addEventListener("pageshow",injectProxy),window.addEventListener("pagehide",(function({target:e}){e===window.document&&delete window.__REACT_DEVTOOLS_PROXY_INJECTED__}));let e=null,n=!1;function sayHelloToBackendManager(){window.postMessage({source:"react-devtools-content-script",hello:!0},"*")}function handleMessageFromDevtools(e){window.postMessage({source:"react-devtools-content-script",payload:e},"*")}function handleMessageFromPage(o){if(o.source===window&&o.data)switch(o.data.source){case"react-devtools-bridge":n=!0,e.postMessage(o.data.payload);break;case"react-devtools-backend-manager":{const{source:e,payload:n}=o.data;chrome.runtime.sendMessage({source:e,payload:n});break}}}function handleDisconnect(){window.removeEventListener("message",handleMessageFromPage),e=null,connectPort()}function connectPort(){e=chrome.runtime.connect({name:"proxy"}),window.addEventListener("message",handleMessageFromPage),e.onMessage.addListener(handleMessageFromDevtools),e.onDisconnect.addListener(handleDisconnect)}})(); -------------------------------------------------------------------------------- /template/electron-builder.ts: -------------------------------------------------------------------------------- 1 | /** biome-ignore-all lint/suspicious/noTemplateCurlyInString: <> */ 2 | import type { Configuration } from 'electron-builder' 3 | 4 | import { 5 | main, 6 | name, 7 | version, 8 | resources, 9 | description, 10 | displayName, 11 | author as _author, 12 | } from './package.json' 13 | 14 | import { getDevFolder } from './src/lib/electron-app/release/utils/path' 15 | 16 | const author = _author?.name ?? _author 17 | const currentYear = new Date().getFullYear() 18 | const authorInKebabCase = author.replace(/\s+/g, '-') 19 | const appId = `com.${authorInKebabCase}.${name}`.toLowerCase() 20 | 21 | const artifactName = [`${name}-v${version}`, '-${os}.${ext}'].join('') 22 | 23 | export default { 24 | appId, 25 | productName: displayName, 26 | copyright: `Copyright © ${currentYear} — ${author}`, 27 | 28 | directories: { 29 | app: getDevFolder(main), 30 | output: `dist/v${version}`, 31 | }, 32 | 33 | mac: { 34 | artifactName, 35 | icon: `${resources}/build/icons/icon.icns`, 36 | category: 'public.app-category.utilities', 37 | target: ['zip', 'dmg', 'dir'], 38 | }, 39 | 40 | linux: { 41 | artifactName, 42 | category: 'Utilities', 43 | synopsis: description, 44 | target: ['AppImage', 'deb', 'pacman', 'freebsd', 'rpm'], 45 | }, 46 | 47 | win: { 48 | artifactName, 49 | icon: `${resources}/build/icons/icon.ico`, 50 | target: ['zip', 'portable'], 51 | }, 52 | } satisfies Configuration 53 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/outdated.svg: -------------------------------------------------------------------------------- 1 | outdated -------------------------------------------------------------------------------- /template/.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: ["*"] 4 | 5 | jobs: 6 | release: 7 | if: startsWith(github.ref, 'refs/tags/v') 8 | runs-on: ${{ matrix.os }} 9 | 10 | strategy: 11 | matrix: 12 | os: [macos-latest, ubuntu-latest, windows-latest] 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | - run: npm i -g --force corepack && corepack enable 18 | 19 | - name: Install Node.js, NPM and PNPM 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 20 23 | cache: "pnpm" 24 | - run: pnpm install 25 | 26 | - name: apt-update 27 | if: startsWith(matrix.os, 'ubuntu-latest') 28 | run: sudo apt-get update 29 | 30 | - name: autoremove 31 | if: startsWith(matrix.os, 'ubuntu-latest') 32 | run: sudo apt autoremove 33 | 34 | - name: Install libarchive rpm on Linux 35 | if: startsWith(matrix.os, 'ubuntu-latest') 36 | run: sudo apt-get install libarchive-tools rpm 37 | 38 | - name: Release Electron app 39 | uses: daltonmenezes/action-electron-builder@v1.0.1 40 | with: 41 | package_manager: "pnpm" 42 | # GitHub token, automatically provided to the action 43 | # (No need to define this secret in the repo settings) 44 | github_token: ${{ secrets.github_token }} 45 | 46 | # If the commit is tagged with a version (e.g. "v1.0.0"), 47 | # release the app after building 48 | release: true 49 | -------------------------------------------------------------------------------- /docs/SOURCE_CODE_PROTECTION.md: -------------------------------------------------------------------------------- 1 |

Source Code Protection

2 | 3 | > This process is done via [v8 bytecode compilation](https://nodejs.org/api/vm.html#vm_script_createcacheddata), to get more knowledge about it, please, [check the Electron Vite docs](https://evite.netlify.app/guide/source-code-protection.html). 4 | 5 | Use the `bytecodePlugin` from `electron-vite` to enable it in the **electron.vite.config.ts**: 6 | 7 | ```ts 8 | import { defineConfig, bytecodePlugin } from 'electron-vite' 9 | 10 | export default defineConfig({ 11 | main: { 12 | plugins: [tsconfigPaths, bytecodePlugin({ transformArrowFunctions: false })] 13 | }, 14 | 15 | preload: { 16 | // Note: you will get the following warning using bytecodePlugin in the preload script in production build: "The vm module of Node.js is deprecated in the renderer process and will be removed", is up to you to keep bytecodePlugin here. Also, keep following the Electron Vite docs for more updates about this plugin! 17 | plugins: [tsconfigPaths, bytecodePlugin({ transformArrowFunctions: false })] 18 | }, 19 | 20 | renderer: { 21 | // ... 22 | } 23 | }) 24 | ``` 25 | Also, `sandbox` should be `false` in `webPreferences` for the windows you are using a preload script like: 26 | ```ts 27 | const window = createWindow({ 28 | id: 'main', 29 | 30 | webPreferences: { 31 | preload: join(__dirname, '../preload/index.js'), 32 | sandbox: false, 33 | }, 34 | }) 35 | ``` 36 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/disabled.svg: -------------------------------------------------------------------------------- 1 | disabled -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/restricted.svg: -------------------------------------------------------------------------------- 1 | disabled -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/production.svg: -------------------------------------------------------------------------------- 1 | production -------------------------------------------------------------------------------- /docs/FAQ.md: -------------------------------------------------------------------------------- 1 |

FAQ - Frequently Asked Questions

2 | 3 | ## How can I fix the `electron-builder install-app-deps Windows Script Host` error? 4 | If you are trying to use this boilerplate with npm instead pnpm on Windows, you will get that error related to `electron-builder install-app-deps`, so: 5 | - Take a look at [this comment](https://github.com/ficonsulting/RInno/issues/44#issuecomment-992299431) to fix it! 6 | 7 | ## What should I do if the release action fails and the re-run failed jobs keep failing? 8 | First, make sure you have the necessary permissions to run the action, see [Releasing](./RELEASING.md) section. 9 | 10 | Then, go to the releases page in your repository and delete the draft release, then execute the action again but in the `re-run all jobs` mode. 11 | 12 | ## What are Autofill errors in the terminal? 13 | If you see the following errors in the terminal, you can ignore them: 14 | ```bash 15 | [97794:0301/202707.879855:ERROR:CONSOLE(1)] "Request Autofill.enable failed. {"code":-32601,"message":"'Autofill.enable' wasn't found"}", source: devtools://devtools/bundled/core/protocol_client/protocol_client.js (1) 16 | [97794:0301/202707.879884:ERROR:CONSOLE(1)] "Request Autofill.setAddresses failed. {"code":-32601,"message":"'Autofill.setAddresses' wasn't found"}", source: devtools://devtools/bundled/core/protocol_client/protocol_client.js (1) 17 | ``` 18 | It only happens when devtools panel is open and it's not an issue with your app. 19 | 20 | For more information, take a look at [this issue](https://github.com/electron/electron/issues/41614). 21 | 22 | -------------------------------------------------------------------------------- /docs/images/bullet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/RELEASING.md: -------------------------------------------------------------------------------- 1 |

Releasing

2 | 3 | > [!NOTE] 4 | > to be able to perform `auto-updates` you will need a `code signed app`, for this purpose you will need to configure it by yourself, so check the [electron-builder](https://www.electron.build/code-signing) and [action-electron-builder](https://github.com/daltonmenezes/action-electron-builder#code-signing) docs please to get know how to do this. 5 | 6 | > [!WARNING] 7 | > to be able to perform releases from GitHub Actions, your repo **must have the necessary permissions**, so: 8 | > - Go to `/settings/actions` in your repo and: 9 | > - in the `Actions permissions` section, select `Allow all actions and reusable workflows` option 10 | > - in the `Workflow permissions` section, select `Read and write permissions` option 11 | > - Save the changes 12 | 13 | To release your app on a GitHub release with `Windows`, `Mac` and `Linux` binaries, you can perform the following commands: 14 | 15 | ```bash 16 | git pull 17 | pnpm make:release 18 | ``` 19 | 20 | Then, enter the new version of your app, so it will produce the following binaries in a `draft release` from the action: 21 | - `Windows` → `zip (portable)`, `.exe` 22 | - `Mac` → `.zip (app)`, `.dmg` 23 | - `Linux` → `AppImage`, `freebsd`, `pacman`, `rpm`, `deb` 24 | 25 | In this process, the action will be triggered and the previous command will open the `releases` and `actions` page in your browser. When the action is finished, you can click in the `releases` page and refresh it to see the draft release with the binaries, so you can edit it and release it for your users. 26 | 27 | https://user-images.githubusercontent.com/1149845/156939675-5ea0c510-ddd3-4de7-b293-87d3697bd1a8.mp4 28 | 29 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "update_url": "https://clients2.google.com/service/update2/crx", 3 | 4 | "manifest_version": 3, 5 | "name": "React Developer Tools", 6 | "description": "Adds React debugging tools to the Chrome Developer Tools.\n\nCreated from revision 3cde211b0c on 10/20/2025.", 7 | "version": "7.0.1", 8 | "version_name": "7.0.1 (10/20/2025)", 9 | "minimum_chrome_version": "114", 10 | "icons": { 11 | "16": "icons/16-production.png", 12 | "32": "icons/32-production.png", 13 | "48": "icons/48-production.png", 14 | "128": "icons/128-production.png" 15 | }, 16 | "action": { 17 | "default_icon": { 18 | "16": "icons/16-disabled.png", 19 | "32": "icons/32-disabled.png", 20 | "48": "icons/48-disabled.png", 21 | "128": "icons/128-disabled.png" 22 | }, 23 | "default_popup": "popups/disabled.html" 24 | }, 25 | "devtools_page": "main.html", 26 | "content_security_policy": { 27 | "extension_pages": "script-src 'self'; object-src 'self'" 28 | }, 29 | "web_accessible_resources": [ 30 | { 31 | "resources": [ 32 | "main.html", 33 | "panel.html", 34 | "build/*.js" 35 | ], 36 | "matches": [ 37 | "" 38 | ], 39 | "extension_ids": [] 40 | } 41 | ], 42 | "background": { 43 | "service_worker": "build/background.js" 44 | }, 45 | "permissions": [ 46 | "scripting", 47 | "storage", 48 | "tabs" 49 | ], 50 | "optional_permissions": [ 51 | "clipboardWrite" 52 | ], 53 | "host_permissions": [ 54 | "" 55 | ], 56 | "content_scripts": [ 57 | { 58 | "matches": [ 59 | "" 60 | ], 61 | "js": [ 62 | "build/prepareInjection.js" 63 | ], 64 | "run_at": "document_start" 65 | } 66 | ] 67 | } -------------------------------------------------------------------------------- /docs/UNSIGNED_APPS.md: -------------------------------------------------------------------------------- 1 |

Running released unsigend apps

2 | 3 | ## Introduction 4 | 5 | This document explains how to run your distributed unsigned apps, which is a common scenario when you are developing and can't afford to sign your apps yet. 6 | 7 | ## macOS 8 | By default, macOS blocks the execution of unsigned apps. To bypass this: 9 | 10 | 1. **Download the app** and move it to the `Applications` folder (optional). 11 | 2. **When trying to open it, you will see a warning that the app cannot be opened.** 12 | 3. **Go to** `System Preferences` > `Security & Privacy` > `General`. 13 | 4. **In the "Allow apps downloaded from" section**, click `Open Anyway` for the blocked app. 14 | 5. If needed, run this command in the Terminal: 15 | ```sh 16 | xattr -c '/Applications/Your App.app' 17 | ``` 18 | or 19 | ```sh 20 | xattr -d com.apple.quarantine '/Applications/Your App.app' 21 | ``` 22 | or 23 | ```sh 24 | codesign --force --deep --sign - '/Applications/Your App.app' 25 | ``` 26 | 6. The app should now open normally. 27 | 28 | ## Linux 29 | On Linux, execution permission may be disabled by default. To run the app: 30 | 31 | 1. **Download the app** and move it to the desired folder. 32 | 2. **Open the Terminal** and navigate to the app's folder. 33 | 3. **Run the following command**: 34 | ```sh 35 | chmod +x YourApp 36 | ``` 37 | 4. **Now you can run the app** by double-clicking it or running it from the Terminal. 38 | 39 | ## Windows 40 | On Windows, SmartScreen may block the app. To run it: 41 | 42 | 1. **When trying to open the app, a Windows Defender SmartScreen warning will appear.** 43 | 2. Click `More info`. 44 | 3. Click `Run anyway`. 45 | 46 | Now you can run your Electron app without issues! 47 | 48 | -------------------------------------------------------------------------------- /template/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", 3 | "assist": { 4 | "actions": { 5 | "source": { 6 | "organizeImports": "off", 7 | "useSortedAttributes": "on", 8 | "useSortedKeys": "off", 9 | "useSortedProperties": "on" 10 | } 11 | }, 12 | "enabled": true 13 | }, 14 | "formatter": { 15 | "enabled": true, 16 | "indentStyle": "space", 17 | "indentWidth": 2, 18 | "lineEnding": "lf", 19 | "lineWidth": 80 20 | }, 21 | "javascript": { 22 | "formatter": { 23 | "arrowParentheses": "asNeeded", 24 | "bracketSpacing": true, 25 | "indentStyle": "space", 26 | "jsxQuoteStyle": "double", 27 | "quoteStyle": "single", 28 | "semicolons": "asNeeded", 29 | "trailingCommas": "es5" 30 | } 31 | }, 32 | "linter": { 33 | "enabled": true, 34 | "rules": { 35 | "a11y": { 36 | "noSvgWithoutTitle": "off", 37 | "useButtonType": "off" 38 | }, 39 | "complexity": { 40 | "noBannedTypes": "warn" 41 | }, 42 | "correctness": { 43 | "noUnusedImports": "warn", 44 | "useExhaustiveDependencies": "off" 45 | }, 46 | "recommended": true, 47 | "style": { 48 | "noUselessElse": "warn" 49 | }, 50 | "suspicious": { 51 | "noArrayIndexKey": "info", 52 | "noDuplicateObjectKeys": "warn", 53 | "noDuplicateProperties": "warn", 54 | "noEmptyInterface": "off", 55 | "noExplicitAny": "off", 56 | "noUnknownAtRules": "off" 57 | } 58 | } 59 | }, 60 | "css": { 61 | "parser": { 62 | "tailwindDirectives": true 63 | } 64 | }, 65 | "files": { 66 | "includes": [ 67 | ".", 68 | "src/**/*", 69 | "!src/lib/electron-app/extensions/**/*", 70 | "!node_modules/**/*", 71 | "!dist/**/*" 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /template/src/renderer/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import type * as React from 'react' 2 | import { cva, type VariantProps } from 'class-variance-authority' 3 | 4 | import { cn } from 'renderer/lib/utils' 5 | 6 | const alertVariants = cva( 7 | 'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current', 8 | { 9 | variants: { 10 | variant: { 11 | default: 'bg-background text-foreground', 12 | destructive: 13 | 'text-destructive-foreground [&>svg]:text-current *:data-[slot=alert-description]:text-destructive-foreground/80', 14 | }, 15 | }, 16 | defaultVariants: { 17 | variant: 'default', 18 | }, 19 | } 20 | ) 21 | 22 | function Alert({ 23 | className, 24 | variant, 25 | ...props 26 | }: React.ComponentProps<'div'> & VariantProps) { 27 | return ( 28 |
34 | ) 35 | } 36 | 37 | function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) { 38 | return ( 39 |
47 | ) 48 | } 49 | 50 | function AlertDescription({ 51 | className, 52 | ...props 53 | }: React.ComponentProps<'div'>) { 54 | return ( 55 |
63 | ) 64 | } 65 | 66 | export { Alert, AlertTitle, AlertDescription } 67 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 57 | 58 | 59 | 60 |
61 |

Looks like this page doesn't have React, or it hasn't been loaded yet.

62 |
63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/release/modules/release.ts: -------------------------------------------------------------------------------- 1 | import { writeFile } from 'node:fs/promises' 2 | import { resolve } from 'node:path' 3 | import open from 'open' 4 | 5 | import { extractOwnerAndRepoFromGitRemoteURL } from '../utils/extractors' 6 | import { checkValidations } from '../utils/validations' 7 | import packageJSON from '../../../../../package.json' 8 | import { question } from '../utils/question' 9 | import { COLORS } from '../constants/colors' 10 | import { exec } from '../utils/exec' 11 | 12 | async function makeRelease() { 13 | console.clear() 14 | 15 | const { version } = packageJSON 16 | 17 | const newVersion = await question( 18 | `Enter a new version: ${COLORS.SOFT_GRAY}(current is ${version})${COLORS.RESET} ` 19 | ) 20 | 21 | if (checkValidations({ version, newVersion })) { 22 | return 23 | } 24 | 25 | packageJSON.version = newVersion 26 | 27 | try { 28 | console.log( 29 | `${COLORS.CYAN}> Updating package.json version...${COLORS.RESET}` 30 | ) 31 | 32 | await writeFile( 33 | resolve('package.json'), 34 | JSON.stringify(packageJSON, null, 2) 35 | ) 36 | 37 | console.log(`\n${COLORS.GREEN}Done!${COLORS.RESET}\n`) 38 | console.log(`${COLORS.CYAN}> Trying to release it...${COLORS.RESET}`) 39 | 40 | exec( 41 | [ 42 | `git commit -am v${newVersion}`, 43 | `git tag v${newVersion}`, 44 | 'git push', 45 | 'git push --tags', 46 | ], 47 | { 48 | inherit: true, 49 | } 50 | ) 51 | 52 | const [repository] = exec(['git remote get-url --push origin']) 53 | const ownerAndRepo = extractOwnerAndRepoFromGitRemoteURL(repository) 54 | 55 | console.log( 56 | `${COLORS.CYAN}> Opening the repository releases page...${COLORS.RESET}` 57 | ) 58 | 59 | await open(`https://github.com/${ownerAndRepo}/releases`) 60 | 61 | console.log( 62 | `${COLORS.CYAN}> Opening the repository actions page...${COLORS.RESET}` 63 | ) 64 | 65 | await open(`https://github.com/${ownerAndRepo}/actions`) 66 | 67 | console.log(`\n${COLORS.GREEN}Done!${COLORS.RESET}\n`) 68 | } catch ({ message }: any) { 69 | console.log(` 70 | 🛑 Something went wrong!\n 71 | 👀 Error: ${message} 72 | `) 73 | } 74 | } 75 | 76 | makeRelease() 77 | -------------------------------------------------------------------------------- /template/electron.vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, externalizeDepsPlugin } from 'electron-vite' 2 | import { codeInspectorPlugin } from 'code-inspector-plugin' 3 | import { resolve, normalize, dirname } from 'node:path' 4 | import tailwindcss from '@tailwindcss/vite' 5 | 6 | import injectProcessEnvPlugin from 'rollup-plugin-inject-process-env' 7 | import tsconfigPathsPlugin from 'vite-tsconfig-paths' 8 | import reactPlugin from '@vitejs/plugin-react' 9 | 10 | import { settings } from './src/lib/electron-router-dom' 11 | import { main, resources } from './package.json' 12 | 13 | const [nodeModules, devFolder] = normalize(dirname(main)).split(/\/|\\/g) 14 | const devPath = [nodeModules, devFolder].join('/') 15 | 16 | const tsconfigPaths = tsconfigPathsPlugin({ 17 | projects: [resolve('tsconfig.json')], 18 | }) 19 | 20 | export default defineConfig({ 21 | main: { 22 | mode: 'es2022', 23 | plugins: [tsconfigPaths, externalizeDepsPlugin()], 24 | 25 | build: { 26 | rollupOptions: { 27 | input: { 28 | index: resolve('src/main/index.ts'), 29 | }, 30 | 31 | output: { 32 | dir: resolve(devPath, 'main'), 33 | format: 'es', 34 | }, 35 | }, 36 | }, 37 | }, 38 | 39 | preload: { 40 | mode: 'es2022', 41 | plugins: [tsconfigPaths, externalizeDepsPlugin()], 42 | 43 | build: { 44 | rollupOptions: { 45 | output: { 46 | dir: resolve(devPath, 'preload'), 47 | }, 48 | }, 49 | }, 50 | }, 51 | 52 | renderer: { 53 | define: { 54 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), 55 | 'process.platform': JSON.stringify(process.platform), 56 | }, 57 | 58 | server: { 59 | port: settings.port, 60 | }, 61 | 62 | plugins: [ 63 | tsconfigPaths, 64 | tailwindcss(), 65 | codeInspectorPlugin({ 66 | bundler: 'vite', 67 | hotKeys: ['altKey'], 68 | hideConsole: true, 69 | }), 70 | reactPlugin(), 71 | ], 72 | 73 | publicDir: resolve(resources, 'public'), 74 | 75 | build: { 76 | outDir: resolve(devPath, 'renderer'), 77 | 78 | rollupOptions: { 79 | plugins: [ 80 | injectProcessEnvPlugin({ 81 | NODE_ENV: 'production', 82 | platform: process.platform, 83 | }), 84 | ], 85 | 86 | input: { 87 | index: resolve('src/renderer/index.html'), 88 | }, 89 | 90 | output: { 91 | dir: resolve(devPath, 'renderer'), 92 | }, 93 | }, 94 | }, 95 | }, 96 | }) 97 | -------------------------------------------------------------------------------- /template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "My Electron App", 3 | "name": "my-electron-app", 4 | "description": "Your awesome app description", 5 | "version": "0.0.0", 6 | "main": "./node_modules/.dev/main/index.mjs", 7 | "resources": "src/resources", 8 | "author": { 9 | "name": "Dalton Menezes", 10 | "email": "daltonmenezes@outlook.com" 11 | }, 12 | "license": "MIT", 13 | "packageManager": "pnpm@10.0.0", 14 | "pnpm": { 15 | "onlyBuiltDependenciesFile": "./trusted-dependencies-scripts.json" 16 | }, 17 | "scripts": { 18 | "start": "electron-vite preview", 19 | "predev": "run-s clean:dev", 20 | "dev": "cross-env NODE_ENV=development electron-vite dev --watch", 21 | "compile:app": "electron-vite build", 22 | "compile:packageJSON": "tsx ./src/lib/electron-app/release/modules/prebuild.ts", 23 | "prebuild": "run-s clean:dev compile:app compile:packageJSON", 24 | "build": "pnpm electron-builder", 25 | "postinstall": "run-s prebuild install:deps", 26 | "install:deps": "electron-builder install-app-deps", 27 | "make:release": "tsx ./src/lib/electron-app/release/modules/release.ts", 28 | "release": "electron-builder --publish always", 29 | "clean:dev": "rimraf ./node_modules/.dev", 30 | "lint": "biome check --no-errors-on-unmatched", 31 | "lint:fix": "biome check --write --no-errors-on-unmatched --assist-enabled=true", 32 | "typecheck": "tsc --noEmit" 33 | }, 34 | "dependencies": { 35 | "class-variance-authority": "^0.7.1", 36 | "clsx": "^2.1.1", 37 | "electron-router-dom": "^2.1.0", 38 | "lucide-react": "^0.556.0", 39 | "react": "^19.2.1", 40 | "react-dom": "^19.2.1", 41 | "react-router-dom": "^7.10.1", 42 | "tailwind-merge": "^3.4.0", 43 | "tailwindcss-animate": "^1.0.7" 44 | }, 45 | "devDependencies": { 46 | "@biomejs/biome": "^2.3.8", 47 | "@tailwindcss/vite": "^4.1.17", 48 | "@types/node": "^24.10.1", 49 | "@types/react": "^19.2.7", 50 | "@types/react-dom": "^19.2.3", 51 | "@types/semver": "^7.7.1", 52 | "@vitejs/plugin-react": "^5.1.1", 53 | "code-inspector-plugin": "^1.3.0", 54 | "cross-env": "^10.1.0", 55 | "electron": "^39.2.6", 56 | "electron-builder": "^26.0.12", 57 | "electron-vite": "^4.0.1", 58 | "npm-run-all": "^4.1.5", 59 | "open": "^11.0.0", 60 | "rimraf": "^6.1.2", 61 | "rollup-plugin-inject-process-env": "^1.3.1", 62 | "semver": "^7.7.3", 63 | "tailwindcss": "^4.1.17", 64 | "tsx": "^4.21.0", 65 | "typescript": "^5.9.3", 66 | "vite": "^7.2.6", 67 | "vite-tsconfig-paths": "^5.1.4" 68 | } 69 | } -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/deadcode.svg: -------------------------------------------------------------------------------- 1 | development780780 -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/icons/development.svg: -------------------------------------------------------------------------------- 1 | development780780 -------------------------------------------------------------------------------- /template/src/renderer/globals.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | @plugin "tailwindcss-animate"; 4 | 5 | @custom-variant dark (&:is(.dark *)); 6 | 7 | :root { 8 | --background: oklch(1 0 0); 9 | --foreground: oklch(0.145 0 0); 10 | --card: oklch(1 0 0); 11 | --card-foreground: oklch(0.145 0 0); 12 | --popover: oklch(1 0 0); 13 | --popover-foreground: oklch(0.145 0 0); 14 | --primary: oklch(0.205 0 0); 15 | --primary-foreground: oklch(0.985 0 0); 16 | --secondary: oklch(0.97 0 0); 17 | --secondary-foreground: oklch(0.205 0 0); 18 | --muted: oklch(0.97 0 0); 19 | --muted-foreground: oklch(0.556 0 0); 20 | --accent: oklch(0.97 0 0); 21 | --accent-foreground: oklch(0.205 0 0); 22 | --destructive: oklch(0.577 0.245 27.325); 23 | --destructive-foreground: oklch(0.577 0.245 27.325); 24 | --border: oklch(0.922 0 0); 25 | --input: oklch(0.922 0 0); 26 | --ring: oklch(0.708 0 0); 27 | --chart-1: oklch(0.646 0.222 41.116); 28 | --chart-2: oklch(0.6 0.118 184.704); 29 | --chart-3: oklch(0.398 0.07 227.392); 30 | --chart-4: oklch(0.828 0.189 84.429); 31 | --chart-5: oklch(0.769 0.188 70.08); 32 | --radius: 0.625rem; 33 | --sidebar: oklch(0.985 0 0); 34 | --sidebar-foreground: oklch(0.145 0 0); 35 | --sidebar-primary: oklch(0.205 0 0); 36 | --sidebar-primary-foreground: oklch(0.985 0 0); 37 | --sidebar-accent: oklch(0.97 0 0); 38 | --sidebar-accent-foreground: oklch(0.205 0 0); 39 | --sidebar-border: oklch(0.922 0 0); 40 | --sidebar-ring: oklch(0.708 0 0); 41 | } 42 | 43 | .dark { 44 | --background: oklch(0.145 0 0); 45 | --foreground: oklch(0.985 0 0); 46 | --card: oklch(0.145 0 0); 47 | --card-foreground: oklch(0.985 0 0); 48 | --popover: oklch(0.145 0 0); 49 | --popover-foreground: oklch(0.985 0 0); 50 | --primary: oklch(0.985 0 0); 51 | --primary-foreground: oklch(0.205 0 0); 52 | --secondary: oklch(0.269 0 0); 53 | --secondary-foreground: oklch(0.985 0 0); 54 | --muted: oklch(0.269 0 0); 55 | --muted-foreground: oklch(0.708 0 0); 56 | --accent: oklch(0.269 0 0); 57 | --accent-foreground: oklch(0.985 0 0); 58 | --destructive: oklch(0.396 0.141 25.723); 59 | --destructive-foreground: oklch(0.637 0.237 25.331); 60 | --border: oklch(0.269 0 0); 61 | --input: oklch(0.269 0 0); 62 | --ring: oklch(0.439 0 0); 63 | --chart-1: oklch(0.488 0.243 264.376); 64 | --chart-2: oklch(0.696 0.17 162.48); 65 | --chart-3: oklch(0.769 0.188 70.08); 66 | --chart-4: oklch(0.627 0.265 303.9); 67 | --chart-5: oklch(0.645 0.246 16.439); 68 | --sidebar: oklch(0.205 0 0); 69 | --sidebar-foreground: oklch(0.985 0 0); 70 | --sidebar-primary: oklch(0.488 0.243 264.376); 71 | --sidebar-primary-foreground: oklch(0.985 0 0); 72 | --sidebar-accent: oklch(0.269 0 0); 73 | --sidebar-accent-foreground: oklch(0.985 0 0); 74 | --sidebar-border: oklch(0.269 0 0); 75 | --sidebar-ring: oklch(0.439 0 0); 76 | } 77 | 78 | @theme inline { 79 | --color-background: var(--background); 80 | --color-foreground: var(--foreground); 81 | --color-card: var(--card); 82 | --color-card-foreground: var(--card-foreground); 83 | --color-popover: var(--popover); 84 | --color-popover-foreground: var(--popover-foreground); 85 | --color-primary: var(--primary); 86 | --color-primary-foreground: var(--primary-foreground); 87 | --color-secondary: var(--secondary); 88 | --color-secondary-foreground: var(--secondary-foreground); 89 | --color-muted: var(--muted); 90 | --color-muted-foreground: var(--muted-foreground); 91 | --color-accent: var(--accent); 92 | --color-accent-foreground: var(--accent-foreground); 93 | --color-destructive: var(--destructive); 94 | --color-destructive-foreground: var(--destructive-foreground); 95 | --color-border: var(--border); 96 | --color-input: var(--input); 97 | --color-ring: var(--ring); 98 | --color-chart-1: var(--chart-1); 99 | --color-chart-2: var(--chart-2); 100 | --color-chart-3: var(--chart-3); 101 | --color-chart-4: var(--chart-4); 102 | --color-chart-5: var(--chart-5); 103 | --radius-sm: calc(var(--radius) - 4px); 104 | --radius-md: calc(var(--radius) - 2px); 105 | --radius-lg: var(--radius); 106 | --radius-xl: calc(var(--radius) + 4px); 107 | --color-sidebar: var(--sidebar); 108 | --color-sidebar-foreground: var(--sidebar-foreground); 109 | --color-sidebar-primary: var(--sidebar-primary); 110 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 111 | --color-sidebar-accent: var(--sidebar-accent); 112 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 113 | --color-sidebar-border: var(--sidebar-border); 114 | --color-sidebar-ring: var(--sidebar-ring); 115 | } 116 | 117 | @layer base { 118 | * { 119 | @apply border-border outline-ring/50; 120 | @apply antialiased; 121 | } 122 | 123 | body { 124 | @apply bg-background text-foreground; 125 | } 126 | 127 | html, 128 | body { 129 | user-select: none; 130 | scroll-behavior: smooth; 131 | -webkit-app-region: no-drag; 132 | -webkit-font-smoothing: antialiased; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/build/background.js: -------------------------------------------------------------------------------- 1 | (()=>{var e={5603:()=>{const e=[{id:"@react-devtools/proxy",js:["build/proxy.js"],matches:[""],persistAcrossSessions:!0,runAt:"document_start",world:chrome.scripting.ExecutionWorld.ISOLATED},{id:"@react-devtools/file-fetcher",js:["build/fileFetcher.js"],matches:[""],persistAcrossSessions:!0,runAt:"document_end",world:chrome.scripting.ExecutionWorld.ISOLATED},{id:"@react-devtools/hook",js:["build/installHook.js"],matches:[""],persistAcrossSessions:!0,runAt:"document_start",world:chrome.scripting.ExecutionWorld.MAIN},{id:"@react-devtools/hook-settings-injector",js:["build/hookSettingsInjector.js"],matches:[""],persistAcrossSessions:!0,runAt:"document_start"}];!async function(){try{await chrome.scripting.unregisterContentScripts(),await chrome.scripting.registerContentScripts(e)}catch(e){console.error(e)}}()}},t={};function __webpack_require__(n){var o=t[n];if(void 0!==o)return o.exports;var r=t[n]={exports:{}};return e[n](r,r.exports,__webpack_require__),r.exports}(()=>{"use strict";__webpack_require__(5603);const background_setExtensionIconAndPopup=function(e,t){chrome.action.setIcon({tabId:t,path:{16:chrome.runtime.getURL(`icons/16-${e}.png`),32:chrome.runtime.getURL(`icons/32-${e}.png`),48:chrome.runtime.getURL(`icons/48-${e}.png`),128:chrome.runtime.getURL(`icons/128-${e}.png`)}}),chrome.action.setPopup({tabId:t,popup:chrome.runtime.getURL(`popups/${e}.html`)})};function isRestrictedBrowserPage(e){if(!e)return!0;const t=new URL(e).protocol;return"chrome:"===t||"about:"===t}function checkAndHandleRestrictedPageIfSo(e){e&&isRestrictedBrowserPage(e.url)&&background_setExtensionIconAndPopup("restricted",e.id)}chrome.tabs.query({},(e=>e.forEach(checkAndHandleRestrictedPageIfSo))),chrome.tabs.onCreated.addListener((e=>checkAndHandleRestrictedPageIfSo(e))),chrome.tabs.onUpdated.addListener(((e,t,n)=>{t.url&&isRestrictedBrowserPage(t.url)&&background_setExtensionIconAndPopup("restricted",e)}));function executeScriptInMainWorld({target:e,files:t,injectImmediately:n}){return chrome.scripting.executeScript({target:e,files:t,injectImmediately:n,world:chrome.scripting.ExecutionWorld.MAIN})}const e=["compact"];const t={};function registerTab(e){t[e]||(t[e]={extension:null,proxy:null,disconnectPipe:null})}function connectExtensionAndProxyPorts(e,n,o){if(!e)throw new Error("Attempted to connect ports, when extension port is not present");if(!n)throw new Error("Attempted to connect ports, when proxy port is not present");if(t[o].disconnectPipe)throw new Error(`Attempted to connect already connected ports for tab with id ${o}`);function extensionPortMessageListener(e){try{n.postMessage(e)}catch(e){0,disconnectListener()}}function proxyPortMessageListener(t){try{e.postMessage(t)}catch(e){0,disconnectListener()}}function disconnectListener(){e.onMessage.removeListener(extensionPortMessageListener),n.onMessage.removeListener(proxyPortMessageListener),delete t[o].disconnectPipe}t[o].disconnectPipe=disconnectListener,e.onMessage.addListener(extensionPortMessageListener),n.onMessage.addListener(proxyPortMessageListener),e.onDisconnect.addListener(disconnectListener),n.onDisconnect.addListener(disconnectListener)}chrome.runtime.onConnect.addListener((e=>{if("proxy"===e.name){if(null==e.sender?.tab?.id)return;const n=e.sender.tab.id;return t[n]?.proxy&&(t[n].disconnectPipe?.(),t[n].proxy.disconnect()),registerTab(n),function(e,n){t[n].proxy=e,e.onDisconnect.addListener((()=>{t[n].disconnectPipe?.(),delete t[n].proxy}))}(e,n),void(t[n].extension&&connectExtensionAndProxyPorts(t[n].extension,t[n].proxy,n))}if(+(n=e.name)+""===n){const n=+e.name;return registerTab(n),function(e,n){t[n].extension=e,e.onDisconnect.addListener((()=>{t[n].disconnectPipe?.(),delete t[n].extension}))}(e,n),void(t[n].proxy&&connectExtensionAndProxyPorts(t[n].extension,t[n].proxy,n))}var n;console.warn(`Unknown port ${e.name} connected`)})),chrome.runtime.onMessage.addListener(((t,n)=>{switch(t?.source){case"devtools-page":!function(e){const{payload:t}=e;switch(t?.type){case"fetch-file-with-cache":{const{payload:{tabId:t,url:n}}=e;t&&n?chrome.tabs.sendMessage(t,{source:"devtools-page",payload:{type:"fetch-file-with-cache",url:n}}):chrome.runtime.sendMessage({source:"react-devtools-background",payload:{type:"fetch-file-with-cache-error",url:n,value:null}});break}case"inject-backend-manager":{const{payload:{tabId:t}}=e;if(!t)throw new Error("Couldn't inject backend manager: tabId not specified");executeScriptInMainWorld({injectImmediately:!0,target:{tabId:t},files:["/build/backendManager.js"]}).then((()=>{}),(e=>{console.error("Failed to inject backend manager:",e)}));break}}}(t);break;case"react-devtools-fetch-resource-content-script":!function(e){const{payload:t}=e;switch(t?.type){case"fetch-file-with-cache-complete":case"fetch-file-with-cache-error":chrome.runtime.sendMessage({source:"react-devtools-background",payload:t})}}(t);break;case"react-devtools-backend-manager":!function(t,n){const{payload:o}=t;"require-backends"===o?.type&&o.versions.forEach((t=>{e.includes(t)&&executeScriptInMainWorld({injectImmediately:!0,target:{tabId:n.tab.id},files:[`/build/react_devtools_backend_${t}.js`]})}))}(t,n);break;case"react-devtools-hook":!function(e,t){const{payload:n}=e;"react-renderer-attached"===n?.type&&background_setExtensionIconAndPopup(n.reactBuildType,t.tab.id)}(t,n)}})),chrome.tabs.onActivated.addListener((({tabId:e})=>{for(const n in t)if(null!=t[n].proxy&&null!=t[n].extension){const o=e===+n?"resumeElementPolling":"pauseElementPolling";t[n].extension.postMessage({event:o})}}))})()})(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Electron App

2 | 3 |

💅 An Electron app boilerplate with React v19, TypeScript v5, Tailwind v4, shadcn/ui, Electron Vite, Biome, GitHub Action releases and more. 4 |

5 | 6 | 7 | github url 8 | 9 | 10 | 11 | patreon url 12 | 13 | 14 | 15 | releases url 16 | 17 | 18 | 19 | license url 20 | 21 |

22 | 23 |

24 | 25 | preview 26 | 27 |

28 | 29 | # Features 30 | - **Stands out** 31 | - 🔥 Fast and Ready-to-go with a well-thought-out structure 32 | - 🚀 Auto reload for main and **Fast Refresh** for renderer process 33 | - 🎉 Window/Screen routing included 34 | - 😎 Preload (context bridge) already configured 35 | - 🔮 GitHub Action releases with `Windows`, `Mac` and `Linux` binaries 36 | - 🔒 Source Code Protection support 37 | - 🍪 Absolute paths support 38 | - **Technologies**: 39 | - 🔋 Electron 40 | - 🔥 ReactJS v19 41 | - 🌎 React Router DOM v7 and Electron Router DOM v2 42 | - 🧐 React Developer Tools 43 | - 🔍 Code inspector (holding `Alt` or `Option` key on DOM element and clicking on it) 44 | - 💙 TypeScript v5 45 | - 📦 Electron Vite 46 | - ✨ TailwindCSS v4 47 | - 🎨 shadcn/ui 48 | - 🍦 lucide-icons 49 | - 💫 Biome / EditorConfig 50 | - 📦 Electron Builder 51 | - 🔮 action-electron-builder 52 | 53 |
54 | 55 | > :warning: If **Windows 7** and **8** support is important for your project, you should know that Electron in a version greater than 22x no longer supports it. You can read more about it [here](https://www.electronjs.org/docs/latest/breaking-changes#removed-windows-7--8--81-support). Therefore, you must downgrade Electron to 22x version if it's important for you! 56 | 57 | # Requirements 58 | - [Node.js 22](https://nodejs.org/en/download/) 59 | - [pnpm 10](https://pnpm.io/installation) 60 | 61 | # Installation 62 | ```bash 63 | npx degit daltonmenezes/electron-app/template project_name 64 | ``` 65 | ```bash 66 | cd project_name 67 | pnpm install 68 | pnpm dev 69 | ``` 70 | 71 | Now, look at the **package.json** file in the root directory, you should update some of that settings with your project branding. 72 | 73 | # Adding new dependencies 74 | For security reasons, **pnpm** has the [onlyBuiltDependenciesFile](https://pnpm.io/package_json#pnpmonlybuiltdependenciesfile) property where only 75 | dependencies listed in the [trusted-dependencies-scripts.json](./template/trusted-dependencies-scripts.json) file can perform the postscripts execution. So, if you want to add a new dependency that needs to run a postscript, you should add it to the [trusted-dependencies-scripts.json](./template/trusted-dependencies-scripts.json) file list. 76 | 77 | # Distribution 78 | 79 | > [!NOTE] 80 | > this section refers to local distribution, to release your app from GitHub Actions, see [Releasing](./docs/RELEASING.md) section. 81 | 82 | ### For all platforms 83 | 84 | > **Note**: Check [Electron Builder docs](https://www.electron.build/cli) for more knowledge 85 | 86 | ``` 87 | pnpm build 88 | ``` 89 | 90 | ### For a specific one 91 | 92 | ```bash 93 | pnpm build --mac 94 | # OR 95 | pnpm build --win 96 | # OR 97 | pnpm build --linux 98 | ``` 99 | 100 | The builded apps will be available in the `dist` folder. 101 | 102 | # Documents 103 | 104 | 105 | 110 | 115 | 120 | 121 | 122 | 127 | 132 | 137 | 138 | 139 | 140 | 145 | 146 |
106 |

107 | Routing 108 |

109 |
111 |

112 | Structure Overview 113 |

114 |
116 |

117 | Source Code Protection 118 |

119 |
123 |

124 | Releasing 125 |

126 |
128 |

129 | Running released unsigend apps 130 |

131 |
133 |

134 | FAQ - Frequently Asked Questions 135 |

136 |
141 |

142 | Theming 143 |

144 |
147 | 148 | # Contributing 149 | > **Note**: contributions are always welcome, but always **ask first**, — please — before work on a PR. 150 | 151 | That said, there's a bunch of ways you can contribute to this project, like by: 152 | 153 | - :beetle: Reporting a bug 154 | - :page_facing_up: Improving this documentation 155 | - :rotating_light: Sharing this project and recommending it to your friends 156 | - :dollar: Supporting this project on GitHub Sponsors or Patreon 157 | - :star2: Giving a star on this repository 158 | 159 | # License 160 | 161 | [MIT © Dalton Menezes](https://github.com/daltonmenezes/electron-app/blob/main/LICENSE) 162 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/build/backendManager.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";var e={3018:(e,t,r)=>{const n=r(5986),i=Symbol("max"),s=Symbol("length"),o=Symbol("lengthCalculator"),l=Symbol("allowStale"),a=Symbol("maxAge"),h=Symbol("dispose"),u=Symbol("noDisposeOnSet"),c=Symbol("lruList"),d=Symbol("cache"),f=Symbol("updateAgeOnGet"),naiveLength=()=>1;const get=(e,t,r)=>{const n=e[d].get(t);if(n){const t=n.value;if(isStale(e,t)){if(del(e,n),!e[l])return}else r&&(e[f]&&(n.value.now=Date.now()),e[c].unshiftNode(n));return t.value}},isStale=(e,t)=>{if(!t||!t.maxAge&&!e[a])return!1;const r=Date.now()-t.now;return t.maxAge?r>t.maxAge:e[a]&&r>e[a]},trim=e=>{if(e[s]>e[i])for(let t=e[c].tail;e[s]>e[i]&&null!==t;){const r=t.prev;del(e,t),t=r}},del=(e,t)=>{if(t){const r=t.value;e[h]&&e[h](r.key,r.value),e[s]-=r.length,e[d].delete(r.key),e[c].removeNode(t)}};class p{constructor(e,t,r,n,i){this.key=e,this.value=t,this.length=r,this.now=n,this.maxAge=i||0}}const forEachStep=(e,t,r,n)=>{let i=r.value;isStale(e,i)&&(del(e,r),e[l]||(i=void 0)),i&&t.call(n,i.value,i.key,e)};e.exports=class{constructor(e){if("number"==typeof e&&(e={max:e}),e||(e={}),e.max&&("number"!=typeof e.max||e.max<0))throw new TypeError("max must be a non-negative number");this[i]=e.max||1/0;const t=e.length||naiveLength;if(this[o]="function"!=typeof t?naiveLength:t,this[l]=e.stale||!1,e.maxAge&&"number"!=typeof e.maxAge)throw new TypeError("maxAge must be a number");this[a]=e.maxAge||0,this[h]=e.dispose,this[u]=e.noDisposeOnSet||!1,this[f]=e.updateAgeOnGet||!1,this.reset()}set max(e){if("number"!=typeof e||e<0)throw new TypeError("max must be a non-negative number");this[i]=e||1/0,trim(this)}get max(){return this[i]}set allowStale(e){this[l]=!!e}get allowStale(){return this[l]}set maxAge(e){if("number"!=typeof e)throw new TypeError("maxAge must be a non-negative number");this[a]=e,trim(this)}get maxAge(){return this[a]}set lengthCalculator(e){"function"!=typeof e&&(e=naiveLength),e!==this[o]&&(this[o]=e,this[s]=0,this[c].forEach((e=>{e.length=this[o](e.value,e.key),this[s]+=e.length}))),trim(this)}get lengthCalculator(){return this[o]}get length(){return this[s]}get itemCount(){return this[c].length}rforEach(e,t){t=t||this;for(let r=this[c].tail;null!==r;){const n=r.prev;forEachStep(this,e,r,t),r=n}}forEach(e,t){t=t||this;for(let r=this[c].head;null!==r;){const n=r.next;forEachStep(this,e,r,t),r=n}}keys(){return this[c].toArray().map((e=>e.key))}values(){return this[c].toArray().map((e=>e.value))}reset(){this[h]&&this[c]&&this[c].length&&this[c].forEach((e=>this[h](e.key,e.value))),this[d]=new Map,this[c]=new n,this[s]=0}dump(){return this[c].map((e=>!isStale(this,e)&&{k:e.key,v:e.value,e:e.now+(e.maxAge||0)})).toArray().filter((e=>e))}dumpLru(){return this[c]}set(e,t,r){if((r=r||this[a])&&"number"!=typeof r)throw new TypeError("maxAge must be a number");const n=r?Date.now():0,l=this[o](t,e);if(this[d].has(e)){if(l>this[i])return del(this,this[d].get(e)),!1;const o=this[d].get(e).value;return this[h]&&(this[u]||this[h](e,o.value)),o.now=n,o.maxAge=r,o.value=t,this[s]+=l-o.length,o.length=l,this.get(e),trim(this),!0}const f=new p(e,t,l,n,r);return f.length>this[i]?(this[h]&&this[h](e,t),!1):(this[s]+=f.length,this[c].unshift(f),this[d].set(e,this[c].head),trim(this),!0)}has(e){if(!this[d].has(e))return!1;const t=this[d].get(e).value;return!isStale(this,t)}get(e){return get(this,e,!0)}peek(e){return get(this,e,!1)}pop(){const e=this[c].tail;return e?(del(this,e),e.value):null}del(e){del(this,this[d].get(e))}load(e){this.reset();const t=Date.now();for(let r=e.length-1;r>=0;r--){const n=e[r],i=n.e||0;if(0===i)this.set(n.k,n.v);else{const e=i-t;e>0&&this.set(n.k,n.v,e)}}}prune(){this[d].forEach(((e,t)=>get(this,t,!1)))}}},7533:e=>{e.exports=function(e){e.prototype[Symbol.iterator]=function*(){for(let e=this.head;e;e=e.next)yield e.value}}},5986:(e,t,r)=>{function Yallist(e){var t=this;if(t instanceof Yallist||(t=new Yallist),t.tail=null,t.head=null,t.length=0,e&&"function"==typeof e.forEach)e.forEach((function(e){t.push(e)}));else if(arguments.length>0)for(var r=0,n=arguments.length;r1)r=t;else{if(!this.head)throw new TypeError("Reduce of empty list with no initial value");n=this.head.next,r=this.head.value}for(var i=0;null!==n;i++)r=e(r,n.value,i),n=n.next;return r},Yallist.prototype.reduceReverse=function(e,t){var r,n=this.tail;if(arguments.length>1)r=t;else{if(!this.tail)throw new TypeError("Reduce of empty list with no initial value");n=this.tail.prev,r=this.tail.value}for(var i=this.length-1;null!==n;i--)r=e(r,n.value,i),n=n.prev;return r},Yallist.prototype.toArray=function(){for(var e=new Array(this.length),t=0,r=this.head;null!==r;t++)e[t]=r.value,r=r.next;return e},Yallist.prototype.toArrayReverse=function(){for(var e=new Array(this.length),t=0,r=this.tail;null!==r;t++)e[t]=r.value,r=r.prev;return e},Yallist.prototype.slice=function(e,t){(t=t||this.length)<0&&(t+=this.length),(e=e||0)<0&&(e+=this.length);var r=new Yallist;if(tthis.length&&(t=this.length);for(var n=0,i=this.head;null!==i&&nthis.length&&(t=this.length);for(var n=this.length,i=this.tail;null!==i&&n>t;n--)i=i.prev;for(;null!==i&&n>e;n--,i=i.prev)r.push(i.value);return r},Yallist.prototype.splice=function(e,t){e>this.length&&(e=this.length-1),e<0&&(e=this.length+e);for(var r=0,n=this.head;null!==n&&r{var t=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(t,{a:t}),t},__webpack_require__.d=(e,t)=>{for(var r in t)__webpack_require__.o(t,r)&&!__webpack_require__.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{const esm_compareVersions=(e,t)=>{const r=validateAndParse(e),n=validateAndParse(t),i=r.pop(),s=n.pop(),o=compareSegments(r,n);return 0!==o?o:i&&s?compareSegments(i.split("."),s.split(".")):i||s?i?-1:1:0},e=/^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i,validateAndParse=t=>{if("string"!=typeof t)throw new TypeError("Invalid argument expected string");const r=t.match(e);if(!r)throw new Error(`Invalid argument not valid semver ('${t}' received)`);return r.shift(),r},isWildcard=e=>"*"===e||"x"===e||"X"===e,tryParse=e=>{const t=parseInt(e,10);return isNaN(t)?e:t},compareStrings=(e,t)=>{if(isWildcard(e)||isWildcard(t))return 0;const[r,n]=((e,t)=>typeof e!=typeof t?[String(e),String(t)]:[e,t])(tryParse(e),tryParse(t));return r>n?1:r{for(let r=0;r":[1],">=":[0,1],"=":[0],"<=":[-1,0],"<":[-1]};Object.keys(t);var r=__webpack_require__(3018),n=__webpack_require__.n(r);Symbol.for("react.element"),Symbol.for("react.transitional.element"),Symbol.for("react.portal"),Symbol.for("react.fragment"),Symbol.for("react.strict_mode"),Symbol.for("react.profiler"),Symbol.for("react.consumer"),Symbol.for("react.context"),Symbol.for("react.forward_ref"),Symbol.for("react.suspense"),Symbol.for("react.suspense_list"),Symbol.for("react.memo"),Symbol.for("react.lazy"),Symbol.for("react.scope"),Symbol.for("react.activity"),Symbol.for("react.legacy_hidden"),Symbol.for("react.tracing_marker"),Symbol.for("react.memo_cache_sentinel"),Symbol.for("react.postpone"),Symbol.for("react.view_transition"),Symbol.iterator;Symbol.asyncIterator;const i="React::DevTools::recordChangeDescriptions",s="React::DevTools::recordTimeline",o="React::DevTools::reloadAndProfile";function sessionStorageRemoveItem(e){try{sessionStorage.removeItem(e)}catch(e){}}function sessionStorageSetItem(e,t){try{return sessionStorage.setItem(e,t)}catch(e){}}Array.isArray,Object.prototype.hasOwnProperty,new WeakMap,new(n())({max:1e3}),Symbol.for("react.provider");function getIsReloadAndProfileSupported(){let e=!1;try{localStorage.getItem("test"),e=!0}catch(e){}return e&&!!(window.document&&window.document.featurePolicy&&window.document.featurePolicy.allowsFeature("sync-xhr"))}function getIfReloadedAndProfiling(){return"true"===function(e){try{return sessionStorage.getItem(e)}catch(e){return null}}(o)}function onReloadAndProfile(e,t){sessionStorageSetItem(o,"true"),sessionStorageSetItem(i,e?"true":"false"),sessionStorageSetItem(s,t?"true":"false")}Symbol("inspectable"),Symbol("inspected"),Symbol("name"),Symbol("preview_long"),Symbol("preview_short"),Symbol("readonly"),Symbol("size"),Symbol("type"),Symbol("unserializable");Array.isArray;const l="999.9.9";function hasAssignedBackend(e){return null!=e&&""!==e&&function(e="",t=""){return esm_compareVersions(e,t)>-1}(e,l)}const a="compact";let h=!1;const u=new Set;function registerRenderer(e,t){let r=e.reconcilerVersion||e.version;hasAssignedBackend(r)||(r=a),t.backends.has(r)||u.add(r)}function activateBackend(e,t){const r=t.backends.get(e);if(!r)throw new Error(`Could not find backend for version "${e}"`);const{Agent:n,Bridge:l,initBackend:a,setupNativeStyleEditor:h}=r,c=new l({listen(e){const listener=t=>{t.source===window&&t.data&&"react-devtools-content-script"===t.data.source&&t.data.payload&&e(t.data.payload)};return window.addEventListener("message",listener),()=>{window.removeEventListener("message",listener)}},send(e,t,r){window.postMessage({source:"react-devtools-bridge",payload:{event:e,payload:t}},"*",r)}}),d=new n(c,getIfReloadedAndProfiling(),onReloadAndProfile);sessionStorageRemoveItem(o),sessionStorageRemoveItem(i),sessionStorageRemoveItem(s),d.addListener("shutdown",(()=>{t.emit("shutdown"),delete window.__REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__})),a(t,d,window,getIsReloadAndProfileSupported()),"function"==typeof h&&t.resolveRNStyle&&h(c,d,t.resolveRNStyle,t.nativeStyleEditorValidAttributes),c.send("extensionBackendInitialized"),u.delete(e)}function updateRequiredBackends(){0!==u.size&&window.postMessage({source:"react-devtools-backend-manager",payload:{type:"require-backends",versions:Array.from(u)}},"*")}window.__REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__||(window.__REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__=!0,window.addEventListener("message",(function welcome(e){e.source===window&&"react-devtools-content-script"===e.data.source&&(h?console.warn('React DevTools detected duplicate welcome "message" events from the content script.'):(h=!0,window.removeEventListener("message",welcome),function(e){if(null==e)return;e.renderers.forEach((t=>{registerRenderer(t,e)})),e.backends.forEach(((t,r)=>{u.delete(r),activateBackend(r,e)})),updateRequiredBackends();const t=e.sub("renderer",(({renderer:t})=>{registerRenderer(t,e),updateRequiredBackends()})),r=e.sub("devtools-backend-installed",(t=>{activateBackend(t,e),updateRequiredBackends()})),n=e.sub("shutdown",(()=>{t(),r(),n()}))}(window.__REACT_DEVTOOLS_GLOBAL_HOOK__)))})))})()})(); -------------------------------------------------------------------------------- /template/src/resources/public/illustration.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /template/src/lib/electron-app/extensions/react-developer-tools/build/react_devtools_backend_compact.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";var e={3018:(e,t,n)=>{const r=n(5986),i=Symbol("max"),o=Symbol("length"),s=Symbol("lengthCalculator"),l=Symbol("allowStale"),a=Symbol("maxAge"),d=Symbol("dispose"),h=Symbol("noDisposeOnSet"),c=Symbol("lruList"),u=Symbol("cache"),p=Symbol("updateAgeOnGet"),naiveLength=()=>1;const get=(e,t,n)=>{const r=e[u].get(t);if(r){const t=r.value;if(isStale(e,t)){if(del(e,r),!e[l])return}else n&&(e[p]&&(r.value.now=Date.now()),e[c].unshiftNode(r));return t.value}},isStale=(e,t)=>{if(!t||!t.maxAge&&!e[a])return!1;const n=Date.now()-t.now;return t.maxAge?n>t.maxAge:e[a]&&n>e[a]},trim=e=>{if(e[o]>e[i])for(let t=e[c].tail;e[o]>e[i]&&null!==t;){const n=t.prev;del(e,t),t=n}},del=(e,t)=>{if(t){const n=t.value;e[d]&&e[d](n.key,n.value),e[o]-=n.length,e[u].delete(n.key),e[c].removeNode(t)}};class f{constructor(e,t,n,r,i){this.key=e,this.value=t,this.length=n,this.now=r,this.maxAge=i||0}}const forEachStep=(e,t,n,r)=>{let i=n.value;isStale(e,i)&&(del(e,n),e[l]||(i=void 0)),i&&t.call(r,i.value,i.key,e)};e.exports=class{constructor(e){if("number"==typeof e&&(e={max:e}),e||(e={}),e.max&&("number"!=typeof e.max||e.max<0))throw new TypeError("max must be a non-negative number");this[i]=e.max||1/0;const t=e.length||naiveLength;if(this[s]="function"!=typeof t?naiveLength:t,this[l]=e.stale||!1,e.maxAge&&"number"!=typeof e.maxAge)throw new TypeError("maxAge must be a number");this[a]=e.maxAge||0,this[d]=e.dispose,this[h]=e.noDisposeOnSet||!1,this[p]=e.updateAgeOnGet||!1,this.reset()}set max(e){if("number"!=typeof e||e<0)throw new TypeError("max must be a non-negative number");this[i]=e||1/0,trim(this)}get max(){return this[i]}set allowStale(e){this[l]=!!e}get allowStale(){return this[l]}set maxAge(e){if("number"!=typeof e)throw new TypeError("maxAge must be a non-negative number");this[a]=e,trim(this)}get maxAge(){return this[a]}set lengthCalculator(e){"function"!=typeof e&&(e=naiveLength),e!==this[s]&&(this[s]=e,this[o]=0,this[c].forEach((e=>{e.length=this[s](e.value,e.key),this[o]+=e.length}))),trim(this)}get lengthCalculator(){return this[s]}get length(){return this[o]}get itemCount(){return this[c].length}rforEach(e,t){t=t||this;for(let n=this[c].tail;null!==n;){const r=n.prev;forEachStep(this,e,n,t),n=r}}forEach(e,t){t=t||this;for(let n=this[c].head;null!==n;){const r=n.next;forEachStep(this,e,n,t),n=r}}keys(){return this[c].toArray().map((e=>e.key))}values(){return this[c].toArray().map((e=>e.value))}reset(){this[d]&&this[c]&&this[c].length&&this[c].forEach((e=>this[d](e.key,e.value))),this[u]=new Map,this[c]=new r,this[o]=0}dump(){return this[c].map((e=>!isStale(this,e)&&{k:e.key,v:e.value,e:e.now+(e.maxAge||0)})).toArray().filter((e=>e))}dumpLru(){return this[c]}set(e,t,n){if((n=n||this[a])&&"number"!=typeof n)throw new TypeError("maxAge must be a number");const r=n?Date.now():0,l=this[s](t,e);if(this[u].has(e)){if(l>this[i])return del(this,this[u].get(e)),!1;const s=this[u].get(e).value;return this[d]&&(this[h]||this[d](e,s.value)),s.now=r,s.maxAge=n,s.value=t,this[o]+=l-s.length,s.length=l,this.get(e),trim(this),!0}const p=new f(e,t,l,r,n);return p.length>this[i]?(this[d]&&this[d](e,t),!1):(this[o]+=p.length,this[c].unshift(p),this[u].set(e,this[c].head),trim(this),!0)}has(e){if(!this[u].has(e))return!1;const t=this[u].get(e).value;return!isStale(this,t)}get(e){return get(this,e,!0)}peek(e){return get(this,e,!1)}pop(){const e=this[c].tail;return e?(del(this,e),e.value):null}del(e){del(this,this[u].get(e))}load(e){this.reset();const t=Date.now();for(let n=e.length-1;n>=0;n--){const r=e[n],i=r.e||0;if(0===i)this.set(r.k,r.v);else{const e=i-t;e>0&&this.set(r.k,r.v,e)}}}prune(){this[u].forEach(((e,t)=>get(this,t,!1)))}}},7533:e=>{e.exports=function(e){e.prototype[Symbol.iterator]=function*(){for(let e=this.head;e;e=e.next)yield e.value}}},5986:(e,t,n)=>{function Yallist(e){var t=this;if(t instanceof Yallist||(t=new Yallist),t.tail=null,t.head=null,t.length=0,e&&"function"==typeof e.forEach)e.forEach((function(e){t.push(e)}));else if(arguments.length>0)for(var n=0,r=arguments.length;n1)n=t;else{if(!this.head)throw new TypeError("Reduce of empty list with no initial value");r=this.head.next,n=this.head.value}for(var i=0;null!==r;i++)n=e(n,r.value,i),r=r.next;return n},Yallist.prototype.reduceReverse=function(e,t){var n,r=this.tail;if(arguments.length>1)n=t;else{if(!this.tail)throw new TypeError("Reduce of empty list with no initial value");r=this.tail.prev,n=this.tail.value}for(var i=this.length-1;null!==r;i--)n=e(n,r.value,i),r=r.prev;return n},Yallist.prototype.toArray=function(){for(var e=new Array(this.length),t=0,n=this.head;null!==n;t++)e[t]=n.value,n=n.next;return e},Yallist.prototype.toArrayReverse=function(){for(var e=new Array(this.length),t=0,n=this.tail;null!==n;t++)e[t]=n.value,n=n.prev;return e},Yallist.prototype.slice=function(e,t){(t=t||this.length)<0&&(t+=this.length),(e=e||0)<0&&(e+=this.length);var n=new Yallist;if(tthis.length&&(t=this.length);for(var r=0,i=this.head;null!==i&&rthis.length&&(t=this.length);for(var r=this.length,i=this.tail;null!==i&&r>t;r--)i=i.prev;for(;null!==i&&r>e;r--,i=i.prev)n.push(i.value);return n},Yallist.prototype.splice=function(e,t){e>this.length&&(e=this.length-1),e<0&&(e=this.length+e);for(var n=0,r=this.head;null!==r&&n{var t=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(t,{a:t}),t},__webpack_require__.d=(e,t)=>{for(var n in t)__webpack_require__.o(t,n)&&!__webpack_require__.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{function _defineProperty(e,t,n){var r;return(t="symbol"==typeof(r=function(e,t){if("object"!=typeof e||!e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var r=n.call(e,t||"default");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(t,"string"))?r:r+"")in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}class e{constructor(){_defineProperty(this,"listenersMap",new Map)}addListener(e,t){const n=this.listenersMap.get(e);if(void 0===n)this.listenersMap.set(e,[t]);else{n.indexOf(t)<0&&n.push(t)}}emit(e,...t){const n=this.listenersMap.get(e);if(void 0!==n)if(1===n.length){n[0].apply(null,t)}else{let e=!1,r=null;const i=Array.from(n);for(let n=0;n=0&&n.splice(e,1)}}}const t="React::DevTools::lastSelection",esm_compareVersions=(e,t)=>{const n=validateAndParse(e),r=validateAndParse(t),i=n.pop(),o=r.pop(),s=compareSegments(n,r);return 0!==s?s:i&&o?compareSegments(i.split("."),o.split(".")):i||o?i?-1:1:0},n=/^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i,validateAndParse=e=>{if("string"!=typeof e)throw new TypeError("Invalid argument expected string");const t=e.match(n);if(!t)throw new Error(`Invalid argument not valid semver ('${e}' received)`);return t.shift(),t},isWildcard=e=>"*"===e||"x"===e||"X"===e,tryParse=e=>{const t=parseInt(e,10);return isNaN(t)?e:t},compareStrings=(e,t)=>{if(isWildcard(e)||isWildcard(t))return 0;const[n,r]=((e,t)=>typeof e!=typeof t?[String(e),String(t)]:[e,t])(tryParse(e),tryParse(t));return n>r?1:n{for(let n=0;n":[1],">=":[0,1],"=":[0],"<=":[-1,0],"<":[-1]};Object.keys(r);var i=__webpack_require__(3018),o=__webpack_require__.n(i);Symbol.for("react.element"),Symbol.for("react.transitional.element"),Symbol.for("react.portal"),Symbol.for("react.fragment"),Symbol.for("react.strict_mode"),Symbol.for("react.profiler"),Symbol.for("react.consumer"),Symbol.for("react.context"),Symbol.for("react.forward_ref"),Symbol.for("react.suspense"),Symbol.for("react.suspense_list"),Symbol.for("react.memo"),Symbol.for("react.lazy"),Symbol.for("react.scope"),Symbol.for("react.activity"),Symbol.for("react.legacy_hidden"),Symbol.for("react.tracing_marker"),Symbol.for("react.memo_cache_sentinel"),Symbol.for("react.postpone"),Symbol.for("react.view_transition"),Symbol.iterator;Symbol.asyncIterator;const s=Array.isArray;Object.prototype.hasOwnProperty,new WeakMap,new(o())({max:1e3}),Symbol.for("react.provider");Symbol("inspectable"),Symbol("inspected"),Symbol("name"),Symbol("preview_long"),Symbol("preview_short"),Symbol("readonly"),Symbol("size"),Symbol("type"),Symbol("unserializable");Array.isArray;function gte(e="",t=""){return esm_compareVersions(e,t)>-1}const isReactNativeEnvironment=()=>null==window.document;function getOwnerWindow(e){return e.ownerDocument?e.ownerDocument.defaultView:null}function getOwnerIframe(e){const t=getOwnerWindow(e);return t?t.frameElement:null}function getBoundingClientRectWithBorderOffset(e){const t=getElementDimensions(e);return mergeRectOffsets([e.getBoundingClientRect(),{top:t.borderTop,left:t.borderLeft,bottom:t.borderBottom,right:t.borderRight,width:0,height:0}])}function mergeRectOffsets(e){return e.reduce(((e,t)=>null==e?t:{top:e.top+t.top,left:e.left+t.left,width:e.width,height:e.height,bottom:e.bottom+t.bottom,right:e.right+t.right}))}function getNestedBoundingClientRect(e,t){const n=getOwnerIframe(e);if(n&&n!==t){const r=[e.getBoundingClientRect()];let i=n,o=!1;for(;i;){const e=getBoundingClientRectWithBorderOffset(i);if(r.push(e),i=getOwnerIframe(i),o)break;i&&getOwnerWindow(i)===t&&(o=!0)}return mergeRectOffsets(r)}return e.getBoundingClientRect()}function getElementDimensions(e){const t=window.getComputedStyle(e);return{borderLeft:parseInt(t.borderLeftWidth,10),borderRight:parseInt(t.borderRightWidth,10),borderTop:parseInt(t.borderTopWidth,10),borderBottom:parseInt(t.borderBottomWidth,10),marginLeft:parseInt(t.marginLeft,10),marginRight:parseInt(t.marginRight,10),marginTop:parseInt(t.marginTop,10),marginBottom:parseInt(t.marginBottom,10),paddingLeft:parseInt(t.paddingLeft,10),paddingRight:parseInt(t.paddingRight,10),paddingTop:parseInt(t.paddingTop,10),paddingBottom:parseInt(t.paddingBottom,10)}}const l=Object.assign;class a{constructor(e,t){this.node=e.createElement("div"),this.border=e.createElement("div"),this.padding=e.createElement("div"),this.content=e.createElement("div"),this.border.style.borderColor=c.border,this.padding.style.borderColor=c.padding,this.content.style.backgroundColor=c.background,l(this.node.style,{borderColor:c.margin,pointerEvents:"none",position:"fixed"}),this.node.style.zIndex="10000000",this.node.appendChild(this.border),this.border.appendChild(this.padding),this.padding.appendChild(this.content),t.appendChild(this.node)}remove(){this.node.parentNode&&this.node.parentNode.removeChild(this.node)}update(e,t){boxWrap(t,"margin",this.node),boxWrap(t,"border",this.border),boxWrap(t,"padding",this.padding),l(this.content.style,{height:e.height-t.borderTop-t.borderBottom-t.paddingTop-t.paddingBottom+"px",width:e.width-t.borderLeft-t.borderRight-t.paddingLeft-t.paddingRight+"px"}),l(this.node.style,{top:e.top-t.marginTop+"px",left:e.left-t.marginLeft+"px"})}}class d{constructor(e,t){this.tip=e.createElement("div"),l(this.tip.style,{display:"flex",flexFlow:"row nowrap",backgroundColor:"#333740",borderRadius:"2px",fontFamily:'"SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace',fontWeight:"bold",padding:"3px 5px",pointerEvents:"none",position:"fixed",fontSize:"12px",whiteSpace:"nowrap"}),this.nameSpan=e.createElement("span"),this.tip.appendChild(this.nameSpan),l(this.nameSpan.style,{color:"#ee78e6",borderRight:"1px solid #aaaaaa",paddingRight:"0.5rem",marginRight:"0.5rem"}),this.dimSpan=e.createElement("span"),this.tip.appendChild(this.dimSpan),l(this.dimSpan.style,{color:"#d7d7d7"}),this.tip.style.zIndex="10000000",t.appendChild(this.tip)}remove(){this.tip.parentNode&&this.tip.parentNode.removeChild(this.tip)}updateText(e,t,n){this.nameSpan.textContent=e,this.dimSpan.textContent=Math.round(t)+"px × "+Math.round(n)+"px"}updatePosition(e,t){const n=this.tip.getBoundingClientRect(),r=function(e,t,n){const r=Math.max(n.height,20),i=Math.max(n.width,60),o=5;let s;s=e.top+e.height+r<=t.top+t.height?e.top+e.heightt.left+t.width&&(l=t.left+t.width-i-o);return s+="px",l+="px",{style:{top:s,left:l}}}(e,t,{width:n.width,height:n.height});l(this.tip.style,r.style)}}class h{constructor(e){const t=window.__REACT_DEVTOOLS_TARGET_WINDOW__||window;this.window=t;const n=window.__REACT_DEVTOOLS_TARGET_WINDOW__||window;this.tipBoundsWindow=n;const r=t.document;this.container=r.createElement("div"),this.container.style.zIndex="10000000",this.tip=new d(r,this.container),this.rects=[],this.agent=e,r.body.appendChild(this.container)}remove(){this.tip.remove(),this.rects.forEach((e=>{e.remove()})),this.rects.length=0,this.container.parentNode&&this.container.parentNode.removeChild(this.container)}inspect(e,t){const n=e.filter((e=>e.nodeType===Node.ELEMENT_NODE));for(;this.rects.length>n.length;){this.rects.pop().remove()}if(0===n.length)return;for(;this.rects.length{const n=getNestedBoundingClientRect(e,this.window),i=getElementDimensions(e);r.top=Math.min(r.top,n.top-i.marginTop),r.right=Math.max(r.right,n.left+n.width+i.marginRight),r.bottom=Math.max(r.bottom,n.top+n.height+i.marginBottom),r.left=Math.min(r.left,n.left-i.marginLeft);this.rects[t].update(n,i)})),!t){t=n[0].nodeName.toLowerCase();const e=n[0],r=this.agent.getComponentNameForHostInstance(e);r&&(t+=" (in "+r+")")}this.tip.updateText(t,r.right-r.left,r.bottom-r.top);const i=getNestedBoundingClientRect(this.tipBoundsWindow.document.documentElement,this.window);this.tip.updatePosition({top:r.top,left:r.left,height:r.bottom-r.top,width:r.right-r.left},{top:i.top+this.tipBoundsWindow.scrollY,left:i.left+this.tipBoundsWindow.scrollX,height:this.tipBoundsWindow.innerHeight,width:this.tipBoundsWindow.innerWidth})}}function boxWrap(e,t,n){l(n.style,{borderTopWidth:e[t+"Top"]+"px",borderLeftWidth:e[t+"Left"]+"px",borderRightWidth:e[t+"Right"]+"px",borderBottomWidth:e[t+"Bottom"]+"px",borderStyle:"solid"})}const c={background:"rgba(120, 170, 210, 0.7)",padding:"rgba(77, 200, 0, 0.3)",margin:"rgba(255, 155, 0, 0.3)",border:"rgba(255, 200, 50, 0.3)"},u=2e3;let p=null,f=null;function hideOverlay(e){return isReactNativeEnvironment()?function(e){e.emit("hideNativeHighlight")}(e):(p=null,void(null!==f&&(f.remove(),f=null)))}function showOverlay(e,t,n,r){return isReactNativeEnvironment()?function(e,t){t.emit("showNativeHighlight",e)}(e,n):function(e,t,n,r){null!==p&&clearTimeout(p),null===f&&(f=new h(n)),f.inspect(e,t),r&&(p=setTimeout((()=>hideOverlay(n)),u))}(e,t,n,r)}let g=new Set,m=!1;const v=["#37afa9","#63b19e","#80b393","#97b488","#abb67d","#beb771","#cfb965","#dfba57","#efbb49","#febc38"];let y=null;function drawWeb(e){null===y&&function(){y=window.document.createElement("canvas"),y.setAttribute("popover","manual"),y.style.cssText="\n xx-background-color: red;\n xx-opacity: 0.5;\n bottom: 0;\n left: 0;\n pointer-events: none;\n position: fixed;\n right: 0;\n top: 0;\n background-color: transparent;\n outline: none;\n box-shadow: none;\n border: none;\n ";const e=window.document.documentElement;e.insertBefore(y,e.firstChild)}();const t=window.devicePixelRatio||1,n=y;n.width=window.innerWidth*t,n.height=window.innerHeight*t,n.style.width=`${window.innerWidth}px`,n.style.height=`${window.innerHeight}px`;const r=n.getContext("2d");r.scale(t,t),r.clearRect(0,0,n.width/t,n.height/t);if(groupAndSortNodes(e).forEach((e=>{!function(e,t){t.forEach((({color:t,rect:n})=>{e.beginPath(),e.strokeStyle=t,e.rect(n.left,n.top,n.width-1,n.height-1),e.stroke()}))}(r,e),function(e,t){const n=t.map((({displayName:e,count:t})=>e?`${e}${t>1?` x${t}`:""}`:"")).filter(Boolean).join(", ");n&&function(e,t,n,r){const{left:i,top:o}=t;e.font="10px monospace",e.textBaseline="middle",e.textAlign="center";const s=2,l=14,a=e.measureText(n),d=a.width+2*s,h=l,c=i,u=o-h;e.fillStyle=r,e.fillRect(c,u,d,h),e.fillStyle="#000000",e.fillText(n,c+d/2,u+h/2)}(e,t[0].rect,n,t[0].color)}(r,e)})),null!==y){if(0===e.size&&y.matches(":popover-open"))return void y.hidePopover();y.matches(":popover-open")&&y.hidePopover(),y.showPopover()}}function groupAndSortNodes(e){const t=new Map;return iterateNodes(e,(({rect:e,color:n,displayName:r,count:i})=>{if(!e)return;const o=`${e.left},${e.top}`;t.has(o)||t.set(o,[]),t.get(o)?.push({rect:e,color:n,displayName:r,count:i})})),Array.from(t.values()).sort(((e,t)=>Math.max(...e.map((e=>e.count)))-Math.max(...t.map((e=>e.count)))))}function draw(e,t){return isReactNativeEnvironment()?function(e,t){const n=[];iterateNodes(e,(({color:e,node:t})=>{n.push({node:t,color:e})})),t.emit("drawTraceUpdates",n);const r=groupAndSortNodes(e);t.emit("drawGroupedTraceUpdatesWithNames",r)}(e,t):drawWeb(e)}function iterateNodes(e,t){e.forEach(((e,n)=>{const r=Math.min(v.length-1,e.count-1),i=v[r];t({color:i,node:n,count:e.count,displayName:e.displayName,expirationTime:e.expirationTime,lastMeasuredAt:e.lastMeasuredAt,rect:e.rect})}))}function destroy(e){return isReactNativeEnvironment()?function(e){e.emit("disableTraceUpdates")}(e):void(null!==y&&(y.matches(":popover-open")&&y.hidePopover(),null!=y.parentNode&&y.parentNode.removeChild(y),y=null))}const w=250,_=3e3,b=250,I=new Map([["Forget","✨"],["Memo","🧠"]]),S="object"==typeof performance&&"function"==typeof performance.now?()=>performance.now():()=>Date.now(),E=new Map;let P=null,x=null,D=!1,N=null;function traceUpdates(e){D&&(e.forEach((e=>{const t=E.get(e),n=S();let r=null!=t?t.lastMeasuredAt:0,i=null!=t?t.rect:null;(null===i||r+bI.get(e)||"")).join("");o=n?`${n}${e}`:e}E.set(e,{count:null!=t?t.count+1:1,expirationTime:null!=t?Math.min(n+_,t.expirationTime+w):n+w,lastMeasuredAt:r,rect:i,displayName:o})})),null!==N&&(clearTimeout(N),N=null),null===x&&(x=requestAnimationFrame(prepareToDraw)))}function prepareToDraw(){x=null,N=null;const e=S();let t=Number.MAX_VALUE;E.forEach(((n,r)=>{n.expirationTime{try{if(this._messageQueue.length){for(let e=0;e{switch(r){case"context":this.send("overrideContext",{id:e,path:t,rendererID:n,wasForwarded:!0,value:i});break;case"hooks":this.send("overrideHookState",{id:e,path:t,rendererID:n,wasForwarded:!0,value:i});break;case"props":this.send("overrideProps",{id:e,path:t,rendererID:n,wasForwarded:!0,value:i});break;case"state":this.send("overrideState",{id:e,path:t,rendererID:n,wasForwarded:!0,value:i})}})),this._wall=e,this._wallUnlisten=e.listen((e=>{e&&e.event&&this.emit(e.event,e.payload)}))||null,this.addListener("overrideValueAtPath",this.overrideValueAtPath)}get wall(){return this._wall}send(e,...t){this._isShutdown?console.warn(`Cannot send message "${e}" through a Bridge that has been shutdown.`):(this._messageQueue.push(e,t),this._scheduledFlush||(this._scheduledFlush=!0,"function"==typeof devtoolsJestTestScheduler?devtoolsJestTestScheduler(this._flush):queueMicrotask(this._flush)))}shutdown(){if(this._isShutdown)return void console.warn("Bridge was already shutdown.");this.emit("shutdown"),this.send("shutdown"),this._isShutdown=!0,this.addListener=function(){},this.emit=function(){},this.removeAllListeners();const e=this._wallUnlisten;e&&e();do{this._flush()}while(this._messageQueue.length)}};function agent_defineProperty(e,t,n){var r;return(t="symbol"==typeof(r=function(e,t){if("object"!=typeof e||!e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var r=n.call(e,t||"default");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(t,"string"))?r:r+"")in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function mergeRoots(e,t,n){const r=e.suspendedByRange,i=t.suspendedByRange;t.isErrored&&(e.isErrored=!0);for(let n=0;nr[1]&&(r[1]=i[1])))}class k extends e{constructor(e,n=!1,r){super(),agent_defineProperty(this,"_isProfiling",!1),agent_defineProperty(this,"_rendererInterfaces",{}),agent_defineProperty(this,"_persistedSelection",null),agent_defineProperty(this,"_persistedSelectionMatch",null),agent_defineProperty(this,"_traceUpdatesEnabled",!1),agent_defineProperty(this,"clearErrorsAndWarnings",(({rendererID:e})=>{const t=this._rendererInterfaces[e];null==t?console.warn(`Invalid renderer id "${e}"`):t.clearErrorsAndWarnings()})),agent_defineProperty(this,"clearErrorsForElementID",(({id:e,rendererID:t})=>{const n=this._rendererInterfaces[t];null==n?console.warn(`Invalid renderer id "${t}"`):n.clearErrorsForElementID(e)})),agent_defineProperty(this,"clearWarningsForElementID",(({id:e,rendererID:t})=>{const n=this._rendererInterfaces[t];null==n?console.warn(`Invalid renderer id "${t}"`):n.clearWarningsForElementID(e)})),agent_defineProperty(this,"copyElementPath",(({id:e,path:t,rendererID:n})=>{const r=this._rendererInterfaces[n];if(null==r)console.warn(`Invalid renderer id "${n}" for element "${e}"`);else{const n=r.getSerializedElementValueByPath(e,t);null!=n?this._bridge.send("saveToClipboard",n):console.warn(`Unable to obtain serialized value for element "${e}"`)}})),agent_defineProperty(this,"deletePath",(({hookID:e,id:t,path:n,rendererID:r,type:i})=>{const o=this._rendererInterfaces[r];null==o?console.warn(`Invalid renderer id "${r}" for element "${t}"`):o.deletePath(i,t,e,n)})),agent_defineProperty(this,"getBackendVersion",(()=>{const e="7.0.1-3cde211b0c";e&&this._bridge.send("backendVersion",e)})),agent_defineProperty(this,"getBridgeProtocol",(()=>{this._bridge.send("bridgeProtocol",A)})),agent_defineProperty(this,"getProfilingData",(({rendererID:e})=>{const t=this._rendererInterfaces[e];null==t&&console.warn(`Invalid renderer id "${e}"`),this._bridge.send("profilingData",t.getProfilingData())})),agent_defineProperty(this,"getProfilingStatus",(()=>{this._bridge.send("profilingStatus",this._isProfiling)})),agent_defineProperty(this,"getOwnersList",(({id:e,rendererID:t})=>{const n=this._rendererInterfaces[t];if(null==n)console.warn(`Invalid renderer id "${t}" for element "${e}"`);else{const t=n.getOwnersList(e);this._bridge.send("ownersList",{id:e,owners:t})}})),agent_defineProperty(this,"inspectElement",(({forceFullData:e,id:t,path:n,rendererID:r,requestID:i})=>{const o=this._rendererInterfaces[r];null==o?console.warn(`Invalid renderer id "${r}" for element "${t}"`):(this._bridge.send("inspectedElement",o.inspectElement(i,t,n,e)),null!==this._persistedSelectionMatch&&this._persistedSelectionMatch.id===t||(this._persistedSelection=null,this._persistedSelectionMatch=null,o.setTrackedPath(null),this._lastSelectedElementID=t,this._lastSelectedRendererID=r,this._persistSelectionTimerScheduled||(this._persistSelectionTimerScheduled=!0,setTimeout(this._persistSelection,1e3))))})),agent_defineProperty(this,"inspectScreen",(({requestID:e,id:t,forceFullData:n,path:r})=>{let i=null,o=!1,s=0,l=null,a=null;if(null!==r&&r.length>1){if("suspendedBy"!==r[0])throw new Error("Only hydrating suspendedBy paths is supported. This is a bug.");if("number"!=typeof r[1])throw new Error(`Expected suspendedBy index to be a number. Received '${r[1]}' instead. This is a bug.`);l=r[1],a=r.slice(2)}for(const r in this._rendererInterfaces){const c=this._rendererInterfaces[r];let u=null;if(null!==l&&null!==a){const e=l-s;void 0!==c.getElementAttributeByPath(t,["suspendedBy",e])&&(u=["suspendedBy",e].concat(a))}const p=c.inspectElement(e,t,u,n);switch(p.type){case"hydrated-path":if(p.path[1]+=s,null!==p.value)for(let e=0;e{const n=this._rendererInterfaces[t];null==n?console.warn(`Invalid renderer id "${t}" for element "${e}"`):n.logElementToConsole(e)})),agent_defineProperty(this,"overrideError",(({id:e,rendererID:t,forceError:n})=>{const r=this._rendererInterfaces[t];null==r?console.warn(`Invalid renderer id "${t}" for element "${e}"`):r.overrideError(e,n)})),agent_defineProperty(this,"overrideSuspense",(({id:e,rendererID:t,forceFallback:n})=>{const r=this._rendererInterfaces[t];null==r?console.warn(`Invalid renderer id "${t}" for element "${e}"`):r.overrideSuspense(e,n)})),agent_defineProperty(this,"overrideSuspenseMilestone",(({suspendedSet:e})=>{for(const t in this._rendererInterfaces){const n=this._rendererInterfaces[t];n.supportsTogglingSuspense&&n.overrideSuspenseMilestone(e)}})),agent_defineProperty(this,"overrideValueAtPath",(({hookID:e,id:t,path:n,rendererID:r,type:i,value:o})=>{const s=this._rendererInterfaces[r];null==s?console.warn(`Invalid renderer id "${r}" for element "${t}"`):s.overrideValueAtPath(i,t,e,n,o)})),agent_defineProperty(this,"overrideContext",(({id:e,path:t,rendererID:n,wasForwarded:r,value:i})=>{r||this.overrideValueAtPath({id:e,path:t,rendererID:n,type:"context",value:i})})),agent_defineProperty(this,"overrideHookState",(({id:e,hookID:t,path:n,rendererID:r,wasForwarded:i,value:o})=>{i||this.overrideValueAtPath({id:e,path:n,rendererID:r,type:"hooks",value:o})})),agent_defineProperty(this,"overrideProps",(({id:e,path:t,rendererID:n,wasForwarded:r,value:i})=>{r||this.overrideValueAtPath({id:e,path:t,rendererID:n,type:"props",value:i})})),agent_defineProperty(this,"overrideState",(({id:e,path:t,rendererID:n,wasForwarded:r,value:i})=>{r||this.overrideValueAtPath({id:e,path:t,rendererID:n,type:"state",value:i})})),agent_defineProperty(this,"onReloadAndProfileSupportedByHost",(()=>{this._bridge.send("isReloadAndProfileSupportedByBackend",!0)})),agent_defineProperty(this,"reloadAndProfile",(({recordChangeDescriptions:e,recordTimeline:t})=>{"function"==typeof this._onReloadAndProfile&&this._onReloadAndProfile(e,t),this._bridge.send("reloadAppForProfiling")})),agent_defineProperty(this,"renamePath",(({hookID:e,id:t,newPath:n,oldPath:r,rendererID:i,type:o})=>{const s=this._rendererInterfaces[i];null==s?console.warn(`Invalid renderer id "${i}" for element "${t}"`):s.renamePath(o,t,e,r,n)})),agent_defineProperty(this,"setTraceUpdatesEnabled",(e=>{this._traceUpdatesEnabled=e,D=e,D||(E.clear(),null!==x&&(cancelAnimationFrame(x),x=null),null!==N&&(clearTimeout(N),N=null),destroy(P));for(const t in this._rendererInterfaces){this._rendererInterfaces[t].setTraceUpdatesEnabled(e)}})),agent_defineProperty(this,"syncSelectionFromBuiltinElementsPanel",(()=>{const e=window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0;null!=e&&this.selectNode(e)})),agent_defineProperty(this,"shutdown",(()=>{this.emit("shutdown"),this._bridge.removeAllListeners(),this.removeAllListeners()})),agent_defineProperty(this,"startProfiling",(({recordChangeDescriptions:e,recordTimeline:t})=>{this._isProfiling=!0;for(const n in this._rendererInterfaces){this._rendererInterfaces[n].startProfiling(e,t)}this._bridge.send("profilingStatus",this._isProfiling)})),agent_defineProperty(this,"stopProfiling",(()=>{this._isProfiling=!1;for(const e in this._rendererInterfaces){this._rendererInterfaces[e].stopProfiling()}this._bridge.send("profilingStatus",this._isProfiling)})),agent_defineProperty(this,"stopInspectingNative",(e=>{this._bridge.send("stopInspectingHost",e)})),agent_defineProperty(this,"storeAsGlobal",(({count:e,id:t,path:n,rendererID:r})=>{const i=this._rendererInterfaces[r];null==i?console.warn(`Invalid renderer id "${r}" for element "${t}"`):i.storeAsGlobal(t,n,e)})),agent_defineProperty(this,"updateHookSettings",(e=>{this.emit("updateHookSettings",e)})),agent_defineProperty(this,"getHookSettings",(()=>{this.emit("getHookSettings")})),agent_defineProperty(this,"onHookSettings",(e=>{this._bridge.send("hookSettings",e)})),agent_defineProperty(this,"updateComponentFilters",(e=>{for(const t in this._rendererInterfaces){const n=+t,r=this._rendererInterfaces[n];if(this._lastSelectedRendererID===n){const e=r.getPathForElement(this._lastSelectedElementID);null!==e&&(r.setTrackedPath(e),this._persistedSelection={rendererID:n,path:e})}r.updateComponentFilters(e)}})),agent_defineProperty(this,"getEnvironmentNames",(()=>{let e=null;for(const t in this._rendererInterfaces){const n=this._rendererInterfaces[+t].getEnvironmentNames();if(null===e)e=n;else for(let t=0;t{this.emit("traceUpdates",e)})),agent_defineProperty(this,"onFastRefreshScheduled",(()=>{this._bridge.send("fastRefreshScheduled")})),agent_defineProperty(this,"onHookOperations",(e=>{if(this._bridge.send("operations",e),null!==this._persistedSelection){const t=e[0];if(this._persistedSelection.rendererID===t){const e=this._rendererInterfaces[t];if(null==e)console.warn(`Invalid renderer id "${t}"`);else{const t=this._persistedSelectionMatch,n=e.getBestMatchForTrackedPath();this._persistedSelectionMatch=n;const r=null!==t?t.id:null,i=null!==n?n.id:null;r!==i&&null!==i&&this._bridge.send("selectElement",i),null!==n&&n.isFullMatch&&(this._persistedSelection=null,this._persistedSelectionMatch=null,e.setTrackedPath(null))}}}})),agent_defineProperty(this,"getIfHasUnsupportedRendererVersion",(()=>{this.emit("getIfHasUnsupportedRendererVersion")})),agent_defineProperty(this,"_persistSelectionTimerScheduled",!1),agent_defineProperty(this,"_lastSelectedRendererID",-1),agent_defineProperty(this,"_lastSelectedElementID",-1),agent_defineProperty(this,"_persistSelection",(()=>{this._persistSelectionTimerScheduled=!1;const e=this._lastSelectedRendererID,n=this._lastSelectedElementID,r=this._rendererInterfaces[e],i=null!=r?r.getPathForElement(n):null;null!==i?function(e,t){try{return sessionStorage.setItem(e,t)}catch(e){}}(t,JSON.stringify({rendererID:e,path:i})):function(e){try{sessionStorage.removeItem(e)}catch(e){}}(t)})),this._isProfiling=n,this._onReloadAndProfile=r;const i=function(e){try{return sessionStorage.getItem(e)}catch(e){return null}}(t);null!=i&&(this._persistedSelection=JSON.parse(i)),this._bridge=e,e.addListener("clearErrorsAndWarnings",this.clearErrorsAndWarnings),e.addListener("clearErrorsForElementID",this.clearErrorsForElementID),e.addListener("clearWarningsForElementID",this.clearWarningsForElementID),e.addListener("copyElementPath",this.copyElementPath),e.addListener("deletePath",this.deletePath),e.addListener("getBackendVersion",this.getBackendVersion),e.addListener("getBridgeProtocol",this.getBridgeProtocol),e.addListener("getProfilingData",this.getProfilingData),e.addListener("getProfilingStatus",this.getProfilingStatus),e.addListener("getOwnersList",this.getOwnersList),e.addListener("inspectElement",this.inspectElement),e.addListener("inspectScreen",this.inspectScreen),e.addListener("logElementToConsole",this.logElementToConsole),e.addListener("overrideError",this.overrideError),e.addListener("overrideSuspense",this.overrideSuspense),e.addListener("overrideSuspenseMilestone",this.overrideSuspenseMilestone),e.addListener("overrideValueAtPath",this.overrideValueAtPath),e.addListener("reloadAndProfile",this.reloadAndProfile),e.addListener("renamePath",this.renamePath),e.addListener("setTraceUpdatesEnabled",this.setTraceUpdatesEnabled),e.addListener("startProfiling",this.startProfiling),e.addListener("stopProfiling",this.stopProfiling),e.addListener("storeAsGlobal",this.storeAsGlobal),e.addListener("syncSelectionFromBuiltinElementsPanel",this.syncSelectionFromBuiltinElementsPanel),e.addListener("shutdown",this.shutdown),e.addListener("updateHookSettings",this.updateHookSettings),e.addListener("getHookSettings",this.getHookSettings),e.addListener("updateComponentFilters",this.updateComponentFilters),e.addListener("getEnvironmentNames",this.getEnvironmentNames),e.addListener("getIfHasUnsupportedRendererVersion",this.getIfHasUnsupportedRendererVersion),e.addListener("overrideContext",this.overrideContext),e.addListener("overrideHookState",this.overrideHookState),e.addListener("overrideProps",this.overrideProps),e.addListener("overrideState",this.overrideState),function(e,t){function registerListenersOnWindow(e){e&&"function"==typeof e.addEventListener?(e.addEventListener("click",onClick,!0),e.addEventListener("mousedown",onMouseEvent,!0),e.addEventListener("mouseover",onMouseEvent,!0),e.addEventListener("mouseup",onMouseEvent,!0),e.addEventListener("pointerdown",onPointerDown,!0),e.addEventListener("pointermove",onPointerMove,!0),e.addEventListener("pointerup",onPointerUp,!0)):t.emit("startInspectingNative")}function stopInspectingHost(){hideOverlay(t),removeListenersOnWindow(window),g.forEach((function(e){try{removeListenersOnWindow(e.contentWindow)}catch(e){}})),g=new Set}function removeListenersOnWindow(e){e&&"function"==typeof e.removeEventListener?(e.removeEventListener("click",onClick,!0),e.removeEventListener("mousedown",onMouseEvent,!0),e.removeEventListener("mouseover",onMouseEvent,!0),e.removeEventListener("mouseup",onMouseEvent,!0),e.removeEventListener("pointerdown",onPointerDown,!0),e.removeEventListener("pointermove",onPointerMove,!0),e.removeEventListener("pointerup",onPointerUp,!0)):t.emit("stopInspectingNative")}function highlightHostInstance({displayName:r,hideAfterTimeout:i,id:o,openBuiltinElementsPanel:s,rendererID:l,scrollIntoView:a}){const d=t.rendererInterfaces[l];if(null==d)return console.warn(`Invalid renderer id "${l}" for element "${o}"`),void hideOverlay(t);if(!d.hasElementWithId(o))return void hideOverlay(t);const h=d.findHostInstancesForElementID(o);if(null!=h)for(let o=0;o0&&(d.length>2||d[0].width>0||d[0].height>0))return a&&"function"==typeof l.scrollIntoView&&(n&&(clearTimeout(n),n=null),l.scrollIntoView({block:"nearest",inline:"nearest"})),showOverlay(h,r,t,i),void(s&&(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0=l,e.send("syncSelectionToBuiltinElementsPanel")))}hideOverlay(t)}function attemptScrollToHostInstance(e,t){const n=e.findHostInstancesForElementID(t);if(null!=n)for(let e=0;e0&&(r.length>2||r[0].width>0||r[0].height>0)&&"function"==typeof t.scrollIntoView)return t.scrollIntoView({block:"nearest",inline:"nearest",behavior:"smooth"}),!0}return!1}e.addListener("clearHostInstanceHighlight",(function(){hideOverlay(t)})),e.addListener("highlightHostInstance",highlightHostInstance),e.addListener("highlightHostInstances",(function({displayName:e,hideAfterTimeout:n,elements:r,scrollIntoView:i}){const o=[];for(let e=0;e0){const e=o[0];i&&"function"==typeof e.scrollIntoView&&e.scrollIntoView({block:"nearest",inline:"nearest"})}showOverlay(o,e,t,n)})),e.addListener("scrollToHostInstance",(function({id:e,rendererID:r}){hideOverlay(t),n&&(clearTimeout(n),n=null);const i=t.rendererInterfaces[r];if(null==i)return void console.warn(`Invalid renderer id "${r}" for element "${e}"`);if(!i.hasElementWithId(e))return;if(attemptScrollToHostInstance(i,e))return;const o=i.findLastKnownRectsForID(e);if(null!==o&&o.length>0){let t=1/0,r=1/0;for(let e=0;ewindow.scrollX+s.clientWidth||r>window.scrollY+s.clientHeight)&&window.scrollTo({top:r,left:t,behavior:"smooth"}),n=setTimeout((()=>{attemptScrollToHostInstance(i,e)}),100)}})),e.addListener("shutdown",stopInspectingHost),e.addListener("startInspectingHost",(function(e){m=e,registerListenersOnWindow(window)})),e.addListener("stopInspectingHost",stopInspectingHost);let n=null;function onClick(t){t.preventDefault(),t.stopPropagation(),stopInspectingHost(),e.send("stopInspectingHost",!0)}function onMouseEvent(e){e.preventDefault(),e.stopPropagation()}function onPointerDown(e){e.preventDefault(),e.stopPropagation(),selectElementForNode(getEventTarget(e))}let r=null;function onPointerMove(e){e.preventDefault(),e.stopPropagation();const n=getEventTarget(e);if(r!==n){if(r=n,"IFRAME"===n.tagName){const e=n;try{g.has(e)||(registerListenersOnWindow(e.contentWindow),g.add(e))}catch(e){}}if(m){const e=t.getIDForHostInstance(n,m);if(null!==e){const n=t.rendererInterfaces[e.rendererID];if(null==n)return void console.warn(`Invalid renderer id "${e.rendererID}" for element "${e.id}"`);highlightHostInstance({displayName:n.getDisplayNameForElementID(e.id),hideAfterTimeout:!1,id:e.id,openBuiltinElementsPanel:!1,rendererID:e.rendererID,scrollIntoView:!1})}}else showOverlay([n],null,t,!1)}}function onPointerUp(e){e.preventDefault(),e.stopPropagation()}const selectElementForNode=n=>{const r=t.getIDForHostInstance(n,m);null!==r&&e.send("selectElement",r.id)};function getEventTarget(e){return e.composed?e.composedPath()[0]:e.target}}(e,this),P=this,P.addListener("traceUpdates",traceUpdates),e.send("backendInitialized"),this._isProfiling&&e.send("profilingStatus",!0)}get rendererInterfaces(){return this._rendererInterfaces}getInstanceAndStyle({id:e,rendererID:t}){const n=this._rendererInterfaces[t];return null==n?(console.warn(`Invalid renderer id "${t}"`),null):n.getInstanceAndStyle(e)}getIDForHostInstance(e,t){if(isReactNativeEnvironment()||"number"!=typeof e.nodeType){for(const n in this._rendererInterfaces){const r=this._rendererInterfaces[n];try{const i=t?r.getSuspenseNodeIDForHostInstance(e):r.getElementIDForHostInstance(e);if(null!==i)return{id:i,rendererID:+n}}catch(e){}}return null}{let n=null,r=null,i=0;for(const t in this._rendererInterfaces){const o=this._rendererInterfaces[t],s=o.getNearestMountedDOMNode(e);if(null!==s){if(s===e){n=s,r=o,i=+t;break}(null===n||n.contains(s))&&(n=s,r=o,i=+t)}}if(null!=r&&null!=n)try{const e=t?r.getSuspenseNodeIDForHostInstance(n):r.getElementIDForHostInstance(n);if(null!==e)return{id:e,rendererID:i}}catch(e){}return null}}getComponentNameForHostInstance(e){const t=this.getIDForHostInstance(e);if(null!==t){return this._rendererInterfaces[t.rendererID].getDisplayNameForElementID(t.id)}return null}selectNode(e){const t=this.getIDForHostInstance(e);null!==t&&this._bridge.send("selectElement",t.id)}registerRendererInterface(e,t){this._rendererInterfaces[e]=t,t.setTraceUpdatesEnabled(this._traceUpdatesEnabled);const n=t.renderer;if(null!==n){1===n.bundleType&>e(n.version,"19.3.0-canary")&&this._bridge.send("enableSuspenseTab")}const r=this._persistedSelection;null!==r&&r.rendererID===e&&t.setTrackedPath(r.path)}onUnsupportedRenderer(){this._bridge.send("unsupportedRendererVersion")}}function initBackend(e,t,n,r){if(null==e)return()=>{};function registerRendererInterface(e,n){t.registerRendererInterface(e,n),n.flushInitialOperations()}const i=[e.sub("renderer-attached",(({id:e,rendererInterface:t})=>{registerRendererInterface(e,t)})),e.sub("unsupported-renderer-version",(()=>{t.onUnsupportedRenderer()})),e.sub("fastRefreshScheduled",t.onFastRefreshScheduled),e.sub("operations",t.onHookOperations),e.sub("traceUpdates",t.onTraceUpdates),e.sub("settingsInitialized",t.onHookSettings)];t.addListener("getIfHasUnsupportedRendererVersion",(()=>{e.hasUnsupportedRendererAttached&&t.onUnsupportedRenderer()})),e.rendererInterfaces.forEach(((e,t)=>{registerRendererInterface(t,e)})),e.emit("react-devtools",t),e.reactDevtoolsAgent=t;return t.addListener("shutdown",(()=>{i.forEach((e=>e())),e.rendererInterfaces.forEach((e=>{e.cleanup()})),e.reactDevtoolsAgent=null})),t.addListener("updateHookSettings",(t=>{e.settings=t})),t.addListener("getHookSettings",(()=>{null!=e.settings&&t.onHookSettings(e.settings)})),r&&t.onReloadAndProfileSupportedByHost(),()=>{i.forEach((e=>e()))}}function resolveBoxStyle(e,t){let n=!1;const r={bottom:0,left:0,right:0,top:0},i=t[e];if(null!=i){for(const e of Object.keys(r))r[e]=i;n=!0}const o=t[e+"Horizontal"];if(null!=o)r.left=o,r.right=o,n=!0;else{const i=t[e+"Left"];null!=i&&(r.left=i,n=!0);const o=t[e+"Right"];null!=o&&(r.right=o,n=!0);const s=t[e+"End"];null!=s&&(r.right=s,n=!0);const l=t[e+"Start"];null!=l&&(r.left=l,n=!0)}const s=t[e+"Vertical"];if(null!=s)r.bottom=s,r.top=s,n=!0;else{const i=t[e+"Bottom"];null!=i&&(r.bottom=i,n=!0);const o=t[e+"Top"];null!=o&&(r.top=o,n=!0)}return n?r:null}function setupNativeStyleEditor(e,t,n,r){e.addListener("NativeStyleEditor_measure",(({id:r,rendererID:i})=>{measureStyle(t,e,n,r,i)})),e.addListener("NativeStyleEditor_renameAttribute",(({id:r,rendererID:i,oldName:o,newName:l,value:a})=>{!function(e,t,n,r,i,o){const l=e.getInstanceAndStyle({id:t,rendererID:n});if(!l||!l.style)return;const{instance:a,style:d}=l,h=i?{[r]:void 0,[i]:o}:{[r]:void 0};let c;if(null!==a&&"function"==typeof a.setNativeProps){const e=O.get(t);e?Object.assign(e,h):O.set(t,h),a.setNativeProps({style:h})}else if(s(d)){const l=d.length-1;"object"!=typeof d[l]||s(d[l])?e.overrideValueAtPath({type:"props",id:t,rendererID:n,path:["style"],value:d.concat([h])}):(c=shallowClone(d[l]),delete c[r],i?c[i]=o:c[r]=void 0,e.overrideValueAtPath({type:"props",id:t,rendererID:n,path:["style",l],value:c}))}else"object"==typeof d?(c=shallowClone(d),delete c[r],i?c[i]=o:c[r]=void 0,e.overrideValueAtPath({type:"props",id:t,rendererID:n,path:["style"],value:c})):e.overrideValueAtPath({type:"props",id:t,rendererID:n,path:["style"],value:[d,h]});e.emit("hideNativeHighlight")}(t,r,i,o,l,a),setTimeout((()=>measureStyle(t,e,n,r,i)))})),e.addListener("NativeStyleEditor_setValue",(({id:r,rendererID:i,name:o,value:l})=>{!function(e,t,n,r,i){const o=e.getInstanceAndStyle({id:t,rendererID:n});if(!o||!o.style)return;const{instance:l,style:a}=o,d={[r]:i};if(null!==l&&"function"==typeof l.setNativeProps){const e=O.get(t);e?Object.assign(e,d):O.set(t,d),l.setNativeProps({style:d})}else if(s(a)){const o=a.length-1;"object"!=typeof a[o]||s(a[o])?e.overrideValueAtPath({type:"props",id:t,rendererID:n,path:["style"],value:a.concat([d])}):e.overrideValueAtPath({type:"props",id:t,rendererID:n,path:["style",o,r],value:i})}else e.overrideValueAtPath({type:"props",id:t,rendererID:n,path:["style"],value:[a,d]});e.emit("hideNativeHighlight")}(t,r,i,o,l),setTimeout((()=>measureStyle(t,e,n,r,i)))})),e.send("isNativeStyleEditorSupported",{isSupported:!0,validAttributes:r})}const R={top:0,left:0,right:0,bottom:0},O=new Map;function measureStyle(e,t,n,r,i){const o=e.getInstanceAndStyle({id:r,rendererID:i});if(!o||!o.style)return void t.send("NativeStyleEditor_styleAndLayout",{id:r,layout:null,style:null});const{instance:s,style:l}=o;let a=n(l);const d=O.get(r);null!=d&&(a=Object.assign({},a,d)),s&&"function"==typeof s.measure?s.measure(((e,n,i,o,s,l)=>{if("number"!=typeof e)return void t.send("NativeStyleEditor_styleAndLayout",{id:r,layout:null,style:a||null});const d=null!=a&&resolveBoxStyle("margin",a)||R,h=null!=a&&resolveBoxStyle("padding",a)||R;t.send("NativeStyleEditor_styleAndLayout",{id:r,layout:{x:e,y:n,width:i,height:o,left:s,top:l,margin:d,padding:h},style:a||null})})):t.send("NativeStyleEditor_styleAndLayout",{id:r,layout:null,style:a||null})}function shallowClone(e){const t={};for(const n in e)t[n]=e[n];return t}const B="compact";!function(e){if(null==e)return;e.backends.set(B,{Agent:k,Bridge:L,initBackend,setupNativeStyleEditor}),e.emit("devtools-backend-installed",B)}(window.__REACT_DEVTOOLS_GLOBAL_HOOK__)})()})(); 2 | //# sourceMappingURL=react_devtools_backend_compact.js.map --------------------------------------------------------------------------------