├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── pnpm-workspace.yaml ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── fix_typo.yml │ ├── feature_request.yml │ └── bug_report.yml ├── pull_request_template.md └── workflows │ └── app-test.yml ├── src ├── renderer │ ├── public │ │ ├── images │ │ │ └── retron-logo.webp │ │ └── locales │ │ │ ├── ko │ │ │ └── common.json │ │ │ ├── ja │ │ │ └── common.json │ │ │ ├── en │ │ │ └── common.json │ │ │ ├── de │ │ │ └── common.json │ │ │ └── fr │ │ │ └── common.json │ ├── screens │ │ ├── NotFoundScreen.tsx │ │ └── MainScreen.tsx │ ├── index.tsx │ ├── store │ │ ├── index.ts │ │ └── slices │ │ │ └── appScreenSlice.ts │ ├── assets │ │ └── css │ │ │ └── global.ts │ ├── index.html │ ├── i18n.ts │ ├── App.tsx │ └── components │ │ └── base │ │ └── ThemeProvider.tsx ├── main │ ├── index.dev.ts │ ├── IPCs.ts │ └── index.ts └── preload │ └── index.ts ├── .prettierignore ├── .editorconfig ├── playwright.config.ts ├── .prettierrc ├── tsconfig.node.json ├── tsconfig.json ├── SECURITY.md ├── .gitignore ├── tests ├── testUtil.mts ├── specs │ └── app.spec.ts └── fixtures.mts ├── LICENSE ├── vite.config.ts ├── eslint.config.ts ├── package.json ├── CONTRIBUTING.md ├── README.md └── CODE_OF_CONDUCT.md /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | onlyBuiltDependencies: 2 | - '@swc/core' 3 | - electron 4 | - electron-winstaller 5 | - esbuild 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: jooy2 4 | custom: ["https://cdget.com/donate"] 5 | -------------------------------------------------------------------------------- /src/renderer/public/images/retron-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jooy2/retron/HEAD/src/renderer/public/images/retron-logo.webp -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Lock files 2 | *-lock.json 3 | *-lock.yaml 4 | 5 | # IDEs 6 | .idea/ 7 | .vscode/ 8 | 9 | # Project files 10 | .github/ 11 | buildAssets/ 12 | dist/ 13 | release/ 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = space 8 | indent_size = 2 9 | insert_final_newline = true 10 | max_line_length = 100 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /src/renderer/screens/NotFoundScreen.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | 3 | export default function NotFoundScreen() { 4 | return ( 5 |
6 |

Screen Not Found

7 | Go to main page 8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/renderer/public/locales/ko/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello-title": "안녕, Retron! 이제 개발을 시작할 수 있습니다.", 3 | "hello-desc": "이제 사전 구성된 환경에서 당신만의 웹 애플리케이션을 제작할 수 있습니다. 도움이 필요한 경우 프로젝트 페이지의 이슈 트래커를 이용해주세요.", 4 | "using-version": "애플리케이션 버전: ", 5 | "count-value": "카운트: ", 6 | "source-code": "프로젝트 소스코드" 7 | } 8 | -------------------------------------------------------------------------------- /src/renderer/public/locales/ja/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello-title": "こんにちは、Retron! これで開発を始めることができます。", 3 | "hello-desc": "これで、事前設定された環境で独自のWebアプリケーションを作成できます。 支援が必要な場合は、プロジェクトページの問題トラッカーを使用してください。", 4 | "using-version": "アプリケーションバージョン: ", 5 | "count-value": "カウント: ", 6 | "source-code": "プロジェクトソースコード" 7 | } 8 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@playwright/test'; 2 | 3 | export default defineConfig({ 4 | outputDir: 'tests/results', 5 | retries: process.env.CI ? 2 : 0, 6 | workers: process.env.CI ? 1 : undefined, 7 | timeout: 60000, 8 | expect: { 9 | timeout: 10000, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /src/renderer/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | import App from '@/renderer/App'; 3 | import '@/renderer/i18n'; 4 | 5 | // Add API key defined in contextBridge to window object type 6 | declare global { 7 | interface Window { 8 | mainApi?: any; 9 | } 10 | } 11 | 12 | createRoot(document.getElementById('app')!).render(); 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | #contact_links: 3 | # - name: GitHub Community Support 4 | # url: https://github.com/orgs/community/discussions 5 | # about: Please ask and answer questions here. 6 | # - name: GitHub Security Bug Bounty 7 | # url: https://bounty.github.com/ 8 | # about: Please report security vulnerabilities here. 9 | -------------------------------------------------------------------------------- /src/renderer/public/locales/en/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello-title": "Hello, Retron! Now you can start developing.", 3 | "hello-desc": "You can now build your own web applications in a pre-configured environment. If you need help, please use the issue tracker on the project page.", 4 | "using-version": "Application Version: ", 5 | "count-value": "Count: ", 6 | "source-code": "Project Source Code" 7 | } 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "singleQuote": true, 4 | "quoteProps": "as-needed", 5 | "trailingComma": "all", 6 | "bracketSpacing": true, 7 | "bracketSameLine": false, 8 | "jsxSingleQuote": false, 9 | "arrowParens": "always", 10 | "insertPragma": false, 11 | "requirePragma": false, 12 | "proseWrap": "never", 13 | "htmlWhitespaceSensitivity": "strict", 14 | "endOfLine": "lf" 15 | } 16 | -------------------------------------------------------------------------------- /src/renderer/store/index.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import appScreenSlice from '@/renderer/store/slices/appScreenSlice'; 3 | 4 | export const store = configureStore({ 5 | reducer: { 6 | appScreen: appScreenSlice, 7 | }, 8 | }); 9 | 10 | export type RootState = ReturnType; 11 | // export type AppDispatch = typeof store.dispatch; 12 | 13 | export default store; 14 | -------------------------------------------------------------------------------- /src/renderer/public/locales/de/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello-title": "Hallo Retron! Jetzt können wir mit der Entwicklung beginnen.", 3 | "hello-desc": "Sie können jetzt Ihre eigenen Webanwendungen in einer vorkonfigurierten Umgebung erstellen. Wenn Sie Hilfe benötigen, nutzen Sie bitte den Issue-Tracker auf der Projektseite.", 4 | "using-version": "Anwendungsversion: ", 5 | "count-value": "zählen: ", 6 | "source-code": "Quellcode des Projekts" 7 | } 8 | -------------------------------------------------------------------------------- /src/renderer/public/locales/fr/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello-title": "Salut Retron ! Maintenant, nous pouvons commencer à développer.", 3 | "hello-desc": "Vous pouvez désormais créer vos propres applications Web dans un environnement préconfiguré. Si vous avez besoin d'aide, veuillez utiliser l'outil de suivi des problèmes sur la page du projet.", 4 | "using-version": "Version de l'application: ", 5 | "count-value": "compter: ", 6 | "source-code": "code source du projet" 7 | } 8 | -------------------------------------------------------------------------------- /src/renderer/assets/css/global.ts: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react'; 2 | 3 | export const bodyRoot = css` 4 | user-select: none; 5 | img { 6 | max-width: 100%; 7 | height: auto; 8 | } 9 | `; 10 | 11 | export const jumbo = css` 12 | padding: 5% 10%; 13 | z-index: 2; 14 | position: relative; 15 | h1 { 16 | font-size: 2rem; 17 | } 18 | p { 19 | font-size: 1.2rem; 20 | } 21 | strong { 22 | color: green; 23 | } 24 | `; 25 | 26 | export default { 27 | bodyRoot, 28 | jumbo, 29 | }; 30 | -------------------------------------------------------------------------------- /src/renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Retron Template 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/index.dev.ts: -------------------------------------------------------------------------------- 1 | // Warning: This file is only used in the development environment 2 | // and is removed at build time. 3 | // Do not edit the file unless necessary. 4 | import installExtension, { 5 | REACT_DEVELOPER_TOOLS, 6 | REDUX_DEVTOOLS, 7 | } from 'electron-extension-installer'; 8 | 9 | const commonExtensionOptions = { 10 | loadExtensionOptions: { 11 | allowFileAccess: true, 12 | }, 13 | }; 14 | 15 | installExtension(REACT_DEVELOPER_TOOLS, commonExtensionOptions); 16 | installExtension(REDUX_DEVTOOLS, commonExtensionOptions); 17 | -------------------------------------------------------------------------------- /src/main/IPCs.ts: -------------------------------------------------------------------------------- 1 | import { IpcMainEvent, ipcMain, shell } from 'electron'; 2 | import { version } from '../../package.json'; 3 | 4 | /* 5 | * IPC Communications 6 | * */ 7 | export default class IPCs { 8 | static initialize(): void { 9 | // Get application version 10 | ipcMain.on('msgRequestGetVersion', (event: IpcMainEvent) => { 11 | event.returnValue = version; 12 | }); 13 | 14 | // Open url via web browser 15 | ipcMain.on('msgOpenExternalLink', async (event: IpcMainEvent, url: string) => { 16 | await shell.openExternal(url); 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "Preserve", 5 | "moduleDetection": "force", 6 | "jsx": "preserve", 7 | "composite": true, 8 | "resolveJsonModule": true, 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true 11 | }, 12 | "include": [ 13 | "src/main", 14 | "src/preload", 15 | "package.json", 16 | "eslint.config.ts", 17 | "vite.config.ts", 18 | "buildAssets/builder", 19 | "tests/**/*.ts", 20 | "tests/**/*.mts", 21 | "tests/**/*.spec.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { initReactI18next } from 'react-i18next'; 3 | import LanguageDetector from 'i18next-browser-languagedetector'; 4 | import Backend from 'i18next-http-backend'; 5 | 6 | i18n 7 | .use(Backend) 8 | .use(LanguageDetector) 9 | .use(initReactI18next) 10 | .init({ 11 | ns: ['common'], 12 | load: 'languageOnly', 13 | defaultNS: 'common', 14 | fallbackNS: 'common', 15 | fallbackLng: 'en', 16 | interpolation: { 17 | escapeValue: false, 18 | }, 19 | react: { 20 | useSuspense: false, 21 | }, 22 | backend: { 23 | loadPath: 'locales/{{lng}}/{{ns}}.json', 24 | }, 25 | }); 26 | 27 | export default i18n; 28 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "compounds": [ 4 | { 5 | "name": "Debug Run", 6 | "configurations": [ 7 | "Debug App" 8 | ], 9 | "presentation": { 10 | "hidden": false, 11 | "group": "", 12 | "order": 1 13 | }, 14 | "stopAll": true 15 | } 16 | ], 17 | "configurations": [ 18 | { 19 | "name": "Debug App", 20 | "request": "launch", 21 | "type": "node", 22 | "timeout": 60000, 23 | "runtimeArgs": [ 24 | "run-script", 25 | "dev" 26 | ], 27 | "cwd": "${workspaceRoot}", 28 | "runtimeExecutable": "npm", 29 | "console": "integratedTerminal" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "outDir": "./dist", 5 | "target": "esnext", 6 | "module": "Preserve", 7 | "moduleDetection": "force", 8 | "jsx": "preserve", 9 | "noImplicitAny": false, 10 | "allowSyntheticDefaultImports": true, 11 | "declaration": true, 12 | "resolveJsonModule": true, 13 | "esModuleInterop": true, 14 | "sourceMap": true, 15 | "strict": true, 16 | "skipLibCheck": true, 17 | "paths": { 18 | "@/*": ["./src/*"] 19 | }, 20 | "lib": ["esnext", "dom"] 21 | }, 22 | "include": ["src/*.ts", "src/*.d.ts", "src/renderer"], 23 | "references": [ 24 | { 25 | "path": "./tsconfig.node.json" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.settings.useSplitJSON": true, 3 | "eslint.format.enable": true, 4 | "eslint.lintTask.enable": false, 5 | "eslint.codeActionsOnSave.rules": null, 6 | "editor.codeActionsOnSave": { 7 | "source.fixAll.eslint": "explicit" 8 | }, 9 | "eslint.validate": ["javascript"], 10 | "files.autoSave": "afterDelay", 11 | "editor.formatOnSave": true, 12 | "editor.wordWrap": "on", 13 | "editor.defaultFormatter": "esbenp.prettier-vscode", 14 | "[javascript]": { 15 | "editor.defaultFormatter": "esbenp.prettier-vscode" 16 | }, 17 | "editor.tabSize": 2, 18 | "prettier.embeddedLanguageFormatting": "off", 19 | "prettier.enable": true, 20 | "files.associations": { 21 | "*.mjs": "javascript", 22 | "*.cjs": "javascript", 23 | "*.mts": "typescript" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/renderer/App.tsx: -------------------------------------------------------------------------------- 1 | import { HashRouter, Route, Routes } from 'react-router-dom'; 2 | import { Provider } from 'react-redux'; 3 | import { store } from '@/renderer/store'; 4 | import ThemeProvider from '@/renderer/components/base/ThemeProvider'; 5 | 6 | import NotFoundScreen from '@/renderer/screens/NotFoundScreen'; 7 | import MainScreen from '@/renderer/screens/MainScreen'; 8 | 9 | export default function App() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | } /> 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/fix_typo.yml: -------------------------------------------------------------------------------- 1 | name: 'Fix typo request' 2 | description: Request to fix a typo or bad translation in this project 3 | labels: ['typo'] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Before creating an issue, please read the following: 9 | 10 | - Search to see if the same issue already exists, and keep the title concise and accurate so that it's easy for others to understand and search for. 11 | - Please create a separate issue for each type of issue. 12 | - Please be as detailed as possible and write in English so that we can handle your issue quickly. 13 | - type: textarea 14 | attributes: 15 | label: Describe the issue 16 | description: Please describe where the typo occurs and a list of text that needs to be corrected. 17 | validations: 18 | required: true 19 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting Security Issues 4 | 5 | To report a security vulnerability, create an issue on GitHub on the "Open a draft security advisory " page on GitHub: https://github.com/jooy2/retron/security/advisories/new 6 | 7 | Also, send private instructions in advance via maintainer email. Do not submit vulnerability-related content as a general issue. 8 | 9 | ## Security compliance 10 | 11 | Project maintainers are quickly addressing reported security vulnerabilities in the project and providing relevant patches. 12 | 13 | We report these to the relevant users and handle the correspondence to prevent the issue from recurring. 14 | 15 | ## Security recommendations 16 | 17 | We recommend that users of project sources use the latest version, which addresses possible security vulnerabilities. 18 | 19 | ## Contact 20 | 21 | - Administrator: jooy2.contact@gmail.com 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 'Feature request' 2 | description: Report a feature request in this project. 3 | labels: ['enhancement'] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Before creating an issue, please read the following: 9 | 10 | - Search to see if the same issue already exists, and keep the title concise and accurate so that it's easy for others to understand and search for. 11 | - Please create a separate issue for each type of issue. 12 | - Please be as detailed as possible and write in English so that we can handle your issue quickly. 13 | - type: textarea 14 | attributes: 15 | label: Describe the feature 16 | description: Feel free to describe any features or improvements you would like to see. You can attach text or images of examples, behavior, etc. from other projects to elaborate. 17 | validations: 18 | required: true 19 | -------------------------------------------------------------------------------- /src/renderer/store/slices/appScreenSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import type { PayloadAction } from '@reduxjs/toolkit'; 3 | 4 | export interface AppScreenState { 5 | version: string; 6 | darkTheme: boolean; 7 | counterValue: number; 8 | } 9 | 10 | const initialState: AppScreenState = { 11 | version: 'Unknown', 12 | darkTheme: false, 13 | counterValue: 0, 14 | }; 15 | 16 | export const appScreenSlice = createSlice({ 17 | name: 'appScreen', 18 | initialState, 19 | reducers: { 20 | setVersion: (state, action: PayloadAction) => { 21 | state.version = action.payload; 22 | }, 23 | setDarkTheme: (state, action: PayloadAction) => { 24 | state.darkTheme = action.payload; 25 | }, 26 | increaseCount: (state) => { 27 | state.counterValue += 1; 28 | }, 29 | }, 30 | }); 31 | 32 | export const { setVersion, setDarkTheme, increaseCount } = appScreenSlice.actions; 33 | 34 | export default appScreenSlice.reducer; 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore for Node.js Projects 2 | # ---------- Start of common ignore files 3 | 4 | # Node artifact files 5 | node_modules/ 6 | 7 | # Log files 8 | *.log 9 | 10 | # dotenv environment variables file 11 | .env 12 | 13 | # JetBrains IDEs 14 | .idea/ 15 | *.iml 16 | 17 | # Visual Studio Code IDE 18 | .vscode/* 19 | !.vscode/settings.json 20 | !.vscode/tasks.json 21 | !.vscode/launch.json 22 | !.vscode/extensions.json 23 | !.vscode/*.code-snippets 24 | 25 | # Local History for Visual Studio Code 26 | .history/ 27 | 28 | # Built Visual Studio Code Extensions 29 | *.vsix 30 | 31 | # Generated by MacOS 32 | .DS_Store 33 | .AppleDouble 34 | .LSOverride 35 | 36 | # Generated by Windows 37 | Thumbs.db 38 | [Dd]esktop.ini 39 | $RECYCLE.BIN/ 40 | 41 | # Applications 42 | *.app 43 | *.pkg 44 | *.dmg 45 | *.exe 46 | *.war 47 | *.deb 48 | 49 | # Large media files 50 | *.mp4 51 | *.tiff 52 | *.avi 53 | *.flv 54 | *.mov 55 | *.wmv 56 | 57 | # ---------- End of common ignore files 58 | 59 | # Project files 60 | dist/ 61 | release/ 62 | tests/results/ 63 | .eslintcache 64 | vite-plugin-electron.log 65 | -------------------------------------------------------------------------------- /src/renderer/components/base/ThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource @emotion/react */ 2 | import { ReactNode, useMemo } from 'react'; 3 | import { ThemeProvider as MuiThemeProvider, createTheme } from '@mui/material/styles'; 4 | import { ThemeProvider as EmotionThemeProvider } from '@emotion/react'; 5 | import { useSelector } from 'react-redux'; 6 | import CssBaseline from '@mui/material/CssBaseline'; 7 | import { RootState } from '@/renderer/store'; 8 | 9 | export default function ThemeProvider({ children }: { children: ReactNode }) { 10 | const darkTheme = useSelector((state: RootState) => state.appScreen.darkTheme); 11 | const muiTheme = useMemo( 12 | () => 13 | createTheme({ 14 | palette: { 15 | mode: darkTheme ? 'dark' : 'light', 16 | background: { 17 | default: darkTheme ? '#111111' : '#ffffff', 18 | }, 19 | }, 20 | }), 21 | [darkTheme], 22 | ); 23 | 24 | return ( 25 | 26 | 27 | {children} 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /tests/testUtil.mts: -------------------------------------------------------------------------------- 1 | import { Page } from 'playwright'; 2 | import { TestInfo } from 'playwright/test'; 3 | 4 | export default class TestUtil { 5 | _page: Page; 6 | 7 | _testInfo: TestInfo; 8 | 9 | _testScreenshotPath: string; 10 | 11 | constructor(page: Page, testInfo: TestInfo, testScreenshotPath: string) { 12 | this._page = page; 13 | this._testInfo = testInfo; 14 | this._testScreenshotPath = testScreenshotPath; 15 | } 16 | 17 | async captureScreenshot(pageInstance: Page, screenshotName: string) { 18 | if (!pageInstance) { 19 | return; 20 | } 21 | 22 | try { 23 | const screenshotPath = `${this._testScreenshotPath}/${screenshotName || `unknown_${Date.now()}`}.png`; 24 | 25 | await pageInstance.screenshot({ path: screenshotPath }); 26 | } catch { 27 | // Do nothing 28 | } 29 | } 30 | 31 | async onTestError(error: Error) { 32 | const titleLists = [...this._testInfo.titlePath]; 33 | titleLists.shift(); 34 | const title = titleLists.join('-'); 35 | 36 | await this.captureScreenshot(this._page, `${title}_${Date.now()}`); 37 | 38 | return new Error(error.message); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2026 CDGet (https://cdget.com). 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 | -------------------------------------------------------------------------------- /tests/specs/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, beforeAll, afterAll } from '../fixtures.mjs'; 2 | 3 | test.beforeAll(beforeAll); 4 | test.afterAll(afterAll); 5 | 6 | // @ts-expect-error ignore 7 | test('Document element check', async ({ page, util }) => { 8 | try { 9 | await expect( 10 | page.getByTestId('main-logo').first(), 11 | 'Confirm main logo is visible', 12 | ).toBeVisible(); 13 | await expect( 14 | page.getByTestId('btn-change-theme').first(), 15 | 'Confirm change theme is visible', 16 | ).toBeVisible(); 17 | 18 | await util.captureScreenshot(page, 'result'); 19 | } catch (error) { 20 | throw await util.onTestError(error); 21 | } 22 | }); 23 | 24 | // @ts-expect-error ignore 25 | test('Counter button click check', async ({ page, util }) => { 26 | try { 27 | await page.getByTestId('btn-counter').click({ clickCount: 10, delay: 50 }); 28 | 29 | const counterValueElement = await page 30 | .getByTestId('counter-value') 31 | .getByRole('status') 32 | .innerHTML(); 33 | 34 | expect(counterValueElement, 'Confirm counter value is same').toBe('10'); 35 | } catch (error) { 36 | throw await util.onTestError(error); 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ## Pull request checklist 8 | 9 | You should familiarize yourself with the files `README.md`, `CONTRIBUTING.md`, and `CODE_OF_CONDUCT.md` in the root of your project. 10 | 11 | - If an issue has been created for this, add `(fixes #{ISSUE_NUMBER})` to the end of the commit description. In `{ISSUE_NUMBER}`, please include the relevant issue number. 12 | - If you need to update or add to the article, please update the relevant content. If a multilingual article exists, you should update all relevant content in your own language, except for translations. 13 | - Add or update test code if it exists and is needed. Also, verify that the tests pass. 14 | - If this PR is not yet complete, keep the PR in draft status. If it's no longer valid, close the PR with an explanation. 15 | 16 | 19 | 20 | ### What did you change? 21 | 22 | ### Why did you make the change? 23 | 24 | ### How does this work? 25 | -------------------------------------------------------------------------------- /.github/workflows/app-test.yml: -------------------------------------------------------------------------------- 1 | name: app-test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths: 7 | - '**' 8 | - '!*.md' 9 | - '!LICENSE' 10 | - '!.github/**' 11 | - '.github/workflows/app-test.yml' 12 | pull_request: 13 | branches: [main] 14 | workflow_dispatch: 15 | 16 | jobs: 17 | app-test: 18 | runs-on: ${{ matrix.os }} 19 | name: Test Node.js ${{ matrix.node_version }} on ${{ matrix.os }} 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | node_version: ['20', '22', '24'] 24 | os: [windows-latest, macos-latest, ubuntu-latest] 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | with: 29 | fetch-depth: 0 30 | 31 | - name: Install xvfb 32 | if: runner.os == 'Linux' 33 | run: | 34 | sudo apt install -y -q --no-install-recommends xvfb 35 | 36 | - name: Setup Node.js ${{ matrix.node_version }} 37 | uses: actions/setup-node@v4 38 | with: 39 | node-version: ${{ matrix.node_version }} 40 | cache: npm 41 | cache-dependency-path: '**/package-lock.json' 42 | 43 | - name: Cache dependencies 44 | uses: actions/cache@v4 45 | id: npm-cache 46 | with: 47 | path: | 48 | **/node_modules 49 | key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} 50 | restore-keys: | 51 | ${{ runner.os }}-npm- 52 | 53 | - name: Install dependencies 54 | if: steps.npm-cache.outputs.cache-hit != 'true' 55 | run: npm i 56 | 57 | - name: Test module script (Windows or macOS) 58 | if: runner.os != 'Linux' 59 | run: | 60 | npm run test 61 | 62 | - name: Test module script (Linux) 63 | if: runner.os == 'Linux' 64 | run: | 65 | npm run test:linux 66 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 'Bug report' 2 | description: Report a bug in this project. 3 | labels: ['bug'] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Before creating an issue, please read the following: 9 | 10 | - Read the `README.md` file on the project page or the documentation file and compare your code to the intent of the project. 11 | - Search to see if the same issue already exists, and keep the title concise and accurate so that it's easy for others to understand and search for. 12 | - Please create a separate issue for each type of issue. 13 | - For modular projects, make sure you're using the latest version of the module. 14 | - Please be as detailed as possible and write in English so that we can handle your issue quickly. 15 | - type: textarea 16 | attributes: 17 | label: Describe the bug 18 | description: | 19 | For the issue you are experiencing, please describe in detail what you are seeing, the error message, and the impact of the issue. If you are able to reproduce the issue, please list the steps in order. You can attach an image or video if necessary. 20 | validations: 21 | required: true 22 | - type: textarea 23 | attributes: 24 | label: Expected behavior 25 | description: Describe how it should be handled when it's normal behavior or what needs to be fixed. 26 | validations: 27 | - type: input 28 | attributes: 29 | label: Your environment - System OS 30 | description: Please describe the full range of OSes you are experiencing the issue with, preferably including the version. 31 | placeholder: Windows 11, macOS 15.x, Linux Ubuntu 24.04, Android 15, iOS 16... 32 | validations: 33 | - type: input 34 | attributes: 35 | label: Your environment - Web Browser 36 | description: If relevant, please describe the web browser you are currently using. 37 | placeholder: Google Chrome, Microsoft Edge, Mozilla Firefox... 38 | validations: 39 | -------------------------------------------------------------------------------- /src/preload/index.ts: -------------------------------------------------------------------------------- 1 | import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'; 2 | 3 | // Whitelist of valid channels used for IPC communication (Send message from Renderer to Main) 4 | const mainAvailChannels: string[] = ['msgRequestGetVersion', 'msgOpenExternalLink']; 5 | const rendererAvailChannels: string[] = []; 6 | 7 | contextBridge.exposeInMainWorld('mainApi', { 8 | send: (channel: string, ...data: any[]): void => { 9 | if (mainAvailChannels.includes(channel)) { 10 | ipcRenderer.send.apply(null, [channel, ...data]); 11 | } else { 12 | throw new Error(`Unknown ipc channel name: ${channel}`); 13 | } 14 | }, 15 | sendSync: (channel: string, ...data: any[]): any => { 16 | if (mainAvailChannels.includes(channel)) { 17 | return ipcRenderer.sendSync.apply(null, [channel, ...data]); 18 | } 19 | 20 | throw new Error(`Unknown ipc channel name: ${channel}`); 21 | }, 22 | on: (channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): void => { 23 | if (rendererAvailChannels.includes(channel)) { 24 | ipcRenderer.on(channel, listener); 25 | } else { 26 | throw new Error(`Unknown ipc channel name: ${channel}`); 27 | } 28 | }, 29 | once: (channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): void => { 30 | if (rendererAvailChannels.includes(channel)) { 31 | ipcRenderer.once(channel, listener); 32 | } else { 33 | throw new Error(`Unknown ipc channel name: ${channel}`); 34 | } 35 | }, 36 | off: (channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): void => { 37 | if (rendererAvailChannels.includes(channel)) { 38 | ipcRenderer.off(channel, listener); 39 | } else { 40 | throw new Error(`Unknown ipc channel name: ${channel}`); 41 | } 42 | }, 43 | invoke: async (channel: string, ...data: any[]): Promise => { 44 | if (mainAvailChannels.includes(channel)) { 45 | const result = await ipcRenderer.invoke.apply(null, [channel, ...data]); 46 | return result; 47 | } 48 | throw new Error(`Unknown ipc channel name: ${channel}`); 49 | }, 50 | }); 51 | -------------------------------------------------------------------------------- /src/main/index.ts: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow } from 'electron'; 2 | 3 | import { dirname, join } from 'path'; 4 | import { fileURLToPath } from 'url'; 5 | import IPCs from './IPCs'; 6 | import { debug } from '../../package.json'; 7 | 8 | const isDevEnv = process.env.NODE_ENV === 'development'; 9 | 10 | let mainWindow; 11 | const currentDirName = dirname(fileURLToPath(import.meta.url)); 12 | 13 | const exitApp = (): void => { 14 | if (mainWindow && !mainWindow.isDestroyed()) { 15 | mainWindow.hide(); 16 | } 17 | mainWindow.destroy(); 18 | app.exit(); 19 | }; 20 | 21 | const createWindow = async () => { 22 | mainWindow = new BrowserWindow({ 23 | width: 720, 24 | height: 540, 25 | webPreferences: { 26 | nodeIntegration: false, 27 | contextIsolation: true, 28 | devTools: isDevEnv, 29 | preload: join(currentDirName, '../preload/index.js'), 30 | }, 31 | }); 32 | 33 | mainWindow.setMenu(null); 34 | 35 | mainWindow.on('close', (event: Event): void => { 36 | event.preventDefault(); 37 | exitApp(); 38 | }); 39 | 40 | mainWindow.webContents.on('did-frame-finish-load', (): void => { 41 | if (isDevEnv) { 42 | mainWindow.webContents.openDevTools(); 43 | } 44 | }); 45 | 46 | mainWindow.once('ready-to-show', (): void => { 47 | mainWindow.setAlwaysOnTop(true); 48 | mainWindow.show(); 49 | mainWindow.focus(); 50 | mainWindow.setAlwaysOnTop(false); 51 | }); 52 | 53 | if (isDevEnv) { 54 | await mainWindow.loadURL(debug.env.VITE_DEV_SERVER_URL); 55 | } else { 56 | await mainWindow.loadFile(join(currentDirName, '../index.html')); 57 | } 58 | 59 | // Initialize IPC Communication 60 | IPCs.initialize(); 61 | }; 62 | 63 | app.whenReady().then(async () => { 64 | // Disable special menus on macOS by uncommenting the following, if necessary 65 | /* 66 | if (process.platform === 'darwin') { 67 | systemPreferences.setUserDefault('NSDisabledDictationMenuItem', 'boolean', true); 68 | systemPreferences.setUserDefault('NSDisabledCharacterPaletteMenuItem', 'boolean', true); 69 | } 70 | */ 71 | 72 | if (isDevEnv) { 73 | import('./index.dev'); 74 | } 75 | 76 | await createWindow(); 77 | }); 78 | 79 | app.on('window-all-closed', () => { 80 | if (process.platform !== 'darwin') { 81 | app.quit(); 82 | } 83 | }); 84 | 85 | app.on('activate', () => { 86 | if (BrowserWindow.getAllWindows().length === 0) { 87 | createWindow(); 88 | } 89 | }); 90 | -------------------------------------------------------------------------------- /tests/fixtures.mts: -------------------------------------------------------------------------------- 1 | import * as base from '@playwright/test'; 2 | import { _electron as electron, Page, ElectronApplication } from 'playwright'; 3 | import { join } from 'path'; 4 | import { main } from '../package.json'; 5 | import TestUtil from './testUtil.mjs'; 6 | 7 | let appElectron: ElectronApplication; 8 | let page: Page; 9 | 10 | const __cwd = process.cwd(); 11 | const __isCiProcess = process.env.CI === 'true'; 12 | const __testPath = join(__cwd, 'tests'); 13 | const __testResultPath = join(__testPath, 'results'); 14 | const __testScreenshotPath = join(__testResultPath, 'screenshots'); 15 | 16 | export const beforeAll = async () => { 17 | // Open Electron app from build directory 18 | appElectron = await electron.launch({ 19 | args: [ 20 | main, 21 | ...(__isCiProcess ? ['--no-sandbox'] : []), 22 | '--enable-logging', 23 | '--ignore-certificate-errors', 24 | '--ignore-ssl-errors', 25 | '--ignore-blocklist', 26 | '--ignore-gpu-blocklist', 27 | ], 28 | locale: 'en-US', 29 | colorScheme: 'light', 30 | env: { 31 | ...process.env, 32 | NODE_ENV: 'production', 33 | }, 34 | }); 35 | page = await appElectron.firstWindow(); 36 | 37 | await page.waitForEvent('load'); 38 | 39 | page.on('console', console.log); 40 | page.on('pageerror', console.log); 41 | 42 | const evaluateResult = await appElectron.evaluate(async ({ app, BrowserWindow }) => { 43 | const currentWindow = BrowserWindow.getFocusedWindow(); 44 | 45 | // Fix window position for testing 46 | currentWindow.setPosition(50, 50); 47 | currentWindow.setSize(1080, 560); 48 | 49 | return { 50 | packaged: app.isPackaged, 51 | dataPath: app.getPath('userData'), 52 | }; 53 | }); 54 | 55 | base.expect(evaluateResult.packaged, 'app is not packaged').toBe(false); 56 | }; 57 | 58 | export const afterAll = async () => { 59 | await appElectron.close(); 60 | }; 61 | 62 | export const test = base.test.extend({ 63 | // eslint-disable-next-line no-empty-pattern 64 | page: async ({}, use) => { 65 | // eslint-disable-next-line react-hooks/rules-of-hooks 66 | await use(page); 67 | }, 68 | // @ts-expect-error ignore 69 | util: async ({ page }, use, testInfo) => { 70 | // eslint-disable-next-line react-hooks/rules-of-hooks 71 | await use(new TestUtil(page, testInfo, __testScreenshotPath)); 72 | }, 73 | }); 74 | 75 | export const { expect } = base; 76 | 77 | export default { 78 | test, 79 | expect, 80 | beforeAll, 81 | afterAll, 82 | }; 83 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'url'; 2 | import { defineConfig, loadEnv } from 'vite'; 3 | import ElectronPlugin, { ElectronOptions } from 'vite-plugin-electron'; 4 | import RendererPlugin from 'vite-plugin-electron-renderer'; 5 | import EslintPlugin from 'vite-plugin-eslint'; 6 | import ReactPlugin from '@vitejs/plugin-react-swc'; 7 | import { resolve, dirname } from 'path'; 8 | import { rmSync } from 'fs'; 9 | import { builtinModules } from 'module'; 10 | 11 | const isDEV = process.env.NODE_ENV === 'development'; 12 | 13 | export default defineConfig(({ mode }) => { 14 | process.env = { 15 | ...(isDEV 16 | ? { 17 | ELECTRON_ENABLE_LOGGING: 'true', 18 | } 19 | : {}), 20 | ...process.env, 21 | ...loadEnv(mode, process.cwd()), 22 | }; 23 | 24 | rmSync('dist', { recursive: true, force: true }); 25 | 26 | const electronPluginConfigs: ElectronOptions[] = [ 27 | { 28 | entry: 'src/main/index.ts', 29 | onstart: ({ startup }) => { 30 | startup(); 31 | }, 32 | vite: { 33 | root: resolve('.'), 34 | build: { 35 | assetsDir: '.', 36 | outDir: 'dist/main', 37 | rollupOptions: { 38 | external: ['electron', ...builtinModules], 39 | }, 40 | }, 41 | }, 42 | }, 43 | { 44 | entry: 'src/preload/index.ts', 45 | onstart: ({ reload }) => { 46 | reload(); 47 | }, 48 | vite: { 49 | root: resolve('.'), 50 | build: { 51 | outDir: 'dist/preload', 52 | }, 53 | }, 54 | }, 55 | ]; 56 | 57 | if (isDEV) { 58 | electronPluginConfigs.push({ 59 | entry: 'src/main/index.dev.ts', 60 | vite: { 61 | root: resolve('.'), 62 | build: { 63 | outDir: 'dist/main', 64 | }, 65 | }, 66 | }); 67 | } 68 | 69 | return { 70 | resolve: { 71 | extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.mts', '.json', '.scss'], 72 | alias: { 73 | '@': resolve(dirname(fileURLToPath(import.meta.url)), 'src'), 74 | }, 75 | }, 76 | base: './', 77 | root: resolve('./src/renderer'), 78 | publicDir: resolve('./src/renderer/public'), 79 | build: { 80 | sourcemap: isDEV, 81 | minify: !isDEV, 82 | outDir: resolve('./dist'), 83 | }, 84 | plugins: [ 85 | ReactPlugin(), 86 | // Docs: https://github.com/gxmari007/vite-plugin-eslint 87 | EslintPlugin(), 88 | // Docs: https://github.com/electron-vite/vite-plugin-electron 89 | ElectronPlugin(electronPluginConfigs), 90 | RendererPlugin(), 91 | ], 92 | }; 93 | }); 94 | -------------------------------------------------------------------------------- /src/renderer/screens/MainScreen.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource @emotion/react */ 2 | import { useEffect } from 'react'; 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | import { useTranslation } from 'react-i18next'; 5 | 6 | import ButtonGroup from '@mui/material/ButtonGroup'; 7 | import Button from '@mui/material/Button'; 8 | import Grid from '@mui/material/Grid'; 9 | import { increaseCount, setDarkTheme, setVersion } from '@/renderer/store/slices/appScreenSlice'; 10 | import { bodyRoot, jumbo } from '@/renderer/assets/css/global'; 11 | import type { RootState } from '@/renderer/store'; 12 | 13 | export default function MainScreen() { 14 | const darkTheme = useSelector((state: RootState) => state.appScreen.darkTheme); 15 | const appVersion = useSelector((state: RootState) => state.appScreen.version); 16 | const counterValue = useSelector((state: RootState) => state.appScreen.counterValue); 17 | const [t] = useTranslation(['common']); 18 | const dispatch = useDispatch(); 19 | 20 | const handleGithubLink = async (): Promise => { 21 | await window.mainApi.send('msgOpenExternalLink', 'https://github.com/jooy2/retron'); 22 | }; 23 | 24 | const handleChangeTheme = (): void => { 25 | dispatch(setDarkTheme(!darkTheme)); 26 | }; 27 | 28 | const handleIncreaseCount = (): void => { 29 | dispatch(increaseCount()); 30 | }; 31 | 32 | useEffect(() => { 33 | // Get application version from package.json version string (Using IPC communication) 34 | dispatch(setVersion(window.mainApi.sendSync('msgRequestGetVersion'))); 35 | }, []); 36 | 37 | return ( 38 |
39 |
40 | 41 | 42 | logo 48 | 49 | 50 |

{t('hello-title')}

51 |

{t('hello-desc')}

52 |

53 | {t('using-version')} {appVersion} 54 |

55 |

56 | {t('count-value')}{' '} 57 | 58 | {counterValue} 59 | 60 |

61 | 62 | 63 | 66 | 69 | 70 |
71 |
72 |
73 |
74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /eslint.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, globalIgnores } from 'eslint/config'; 2 | import pluginJs from '@eslint/js'; 3 | import pluginTypeScriptESLint from 'typescript-eslint'; 4 | import pluginReact from 'eslint-plugin-react'; 5 | import pluginReactHooks from 'eslint-plugin-react-hooks'; 6 | import pluginNode from 'eslint-plugin-n'; 7 | import pluginJsxA11y from 'eslint-plugin-jsx-a11y'; 8 | import parserTypeScript from '@typescript-eslint/parser'; 9 | import pluginImport from 'eslint-plugin-import'; 10 | import pluginPrettier from 'eslint-plugin-prettier/recommended'; 11 | 12 | import globals from 'globals'; 13 | 14 | export default defineConfig( 15 | pluginPrettier, 16 | pluginReact.configs.flat.recommended, 17 | pluginJs.configs.recommended, 18 | pluginReactHooks.configs.flat.recommended, 19 | pluginTypeScriptESLint.configs.recommended, 20 | pluginImport.flatConfigs.electron, 21 | pluginJsxA11y.flatConfigs.recommended, 22 | pluginNode.configs['flat/recommended-script'], 23 | globalIgnores([ 24 | '**/node_modules', 25 | '**/dist', 26 | '**/release', 27 | '**/.idea', 28 | '**/.vscode', 29 | '**/.github', 30 | '**/buildAssets/builder', 31 | '**/tests/results', 32 | '**/package-lock.json', 33 | ]), 34 | { 35 | files: ['**/*.{js,mjs,cjs,jsx,tsx,ts}'], 36 | settings: { 37 | react: { 38 | version: 'detect', 39 | }, 40 | }, 41 | languageOptions: { 42 | ecmaVersion: 'latest', 43 | sourceType: 'module', 44 | globals: { 45 | ...globals.browser, 46 | ...globals.node, 47 | }, 48 | parserOptions: { 49 | parser: parserTypeScript, 50 | ecmaVersion: 2022, 51 | ecmaFeatures: { 52 | jsx: true, 53 | }, 54 | requireConfigFile: false, 55 | }, 56 | }, 57 | rules: { 58 | eqeqeq: 'error', 59 | 'no-unused-vars': 'off', 60 | 'no-underscore-dangle': 'warn', 61 | 'no-case-declarations': 'off', 62 | 'no-trailing-spaces': 'error', 63 | 'no-unsafe-optional-chaining': 'off', 64 | 'no-control-regex': 'off', 65 | 'n/no-missing-import': 'off', 66 | 'n/no-unsupported-features/node-builtins': 'off', 67 | 'react/require-default-props': [ 68 | 'error', 69 | { 70 | forbidDefaultForRequired: true, 71 | functions: 'defaultArguments', 72 | }, 73 | ], 74 | 'react-hooks/exhaustive-deps': 'off', 75 | 'react/react-in-jsx-scope': 'off', 76 | 'react/jsx-props-no-spreading': 'off', 77 | 'react/no-unknown-property': ['error', { ignore: ['css'] }], 78 | 'react/jsx-filename-extension': [ 79 | 2, 80 | { 81 | extensions: ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.mts'], 82 | }, 83 | ], 84 | 'jsx-a11y/anchor-is-valid': 0, 85 | 'jsx-a11y/label-has-associated-control': 1, 86 | 'jsx-a11y/no-noninteractive-element-interactions': 0, 87 | 'jsx-a11y/click-events-have-key-events': 0, 88 | 'jsx-a11y/no-static-element-interactions': 0, 89 | '@typescript-eslint/no-unused-vars': [ 90 | 'error', 91 | { 92 | argsIgnorePattern: '^_', 93 | varsIgnorePattern: '^_', 94 | caughtErrorsIgnorePattern: '^_', 95 | }, 96 | ], 97 | '@typescript-eslint/no-explicit-any': 'off', 98 | }, 99 | }, 100 | ); 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "retron", 3 | "appId": "com.retron.retron", 4 | "version": "1.0.0", 5 | "description": "ViteJS + Electron + React + Material-UI Template", 6 | "homepage": "https://github.com/jooy2/retron", 7 | "author": "CDGet ", 8 | "license": "MIT", 9 | "main": "dist/main/index.js", 10 | "private": true, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/jooy2/retron.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/jooy2/retron/issues" 17 | }, 18 | "debug": { 19 | "env": { 20 | "VITE_DEV_SERVER_URL": "http://localhost:5173" 21 | } 22 | }, 23 | "engines": { 24 | "node": ">=18.0.0" 25 | }, 26 | "scripts": { 27 | "dev": "vite", 28 | "dev:debug": "vite -d", 29 | "dev:debug:force": "vite -d --force", 30 | "build": "npm run build:pre && electron-builder --config=buildAssets/builder/config.js", 31 | "build:pre": "tsc && vite build", 32 | "build:all": "npm run build:pre && electron-builder -wml --config=buildAssets/builder/config.js", 33 | "build:dir": "npm run build:pre && electron-builder --dir --config=buildAssets/builder/config.js", 34 | "build:win": "npm run build:pre && electron-builder --windows --config=buildAssets/builder/config.js", 35 | "build:win:portable": "npm run build:pre && electron-builder --windows nsis:ia32 portable --config=buildAssets/builder/config.js", 36 | "build:mac": "npm run build:pre && electron-builder --mac --config=buildAssets/builder/config.js", 37 | "build:linux": "npm run build:pre && electron-builder --linux --config=buildAssets/builder/config.js", 38 | "lint": "eslint . --ext .js,.ts,.tsx,.jsx .", 39 | "lint:fix": "eslint . --ext .js,.ts,.tsx,.jsx --fix .", 40 | "format": "prettier .", 41 | "format:fix": "prettier . --write", 42 | "test": "npm run build:pre && playwright test", 43 | "test:linux": "npm run build:pre && xvfb-run --auto-servernum --server-args='-screen 0, 1280x960x24' -- playwright test" 44 | }, 45 | "dependencies": { 46 | "@emotion/react": "^11.14.0", 47 | "@emotion/styled": "^11.14.1", 48 | "@mui/material": "^7.3.6", 49 | "@reduxjs/toolkit": "^2.11.2", 50 | "i18next": "^25.7.3", 51 | "i18next-browser-languagedetector": "^8.2.0", 52 | "i18next-http-backend": "^3.0.2", 53 | "react": "^19.2.3", 54 | "react-dom": "^19.2.3", 55 | "react-i18next": "^16.5.0", 56 | "react-redux": "^9.2.0", 57 | "react-router-dom": "^7.10.1" 58 | }, 59 | "devDependencies": { 60 | "@eslint/js": "^9.39.2", 61 | "@playwright/test": "^1.57.0", 62 | "@types/react": "^19.2.7", 63 | "@types/react-dom": "^19.2.3", 64 | "@typescript-eslint/parser": "^8.50.0", 65 | "@vitejs/plugin-react-swc": "^4.2.2", 66 | "dotenv": "^17.2.3", 67 | "electron": "^39.2.7", 68 | "electron-builder": "^26.0.14", 69 | "electron-extension-installer": "^2.0.1", 70 | "eslint": "^9.39.2", 71 | "eslint-config-prettier": "^10.1.8", 72 | "eslint-plugin-import": "^2.32.0", 73 | "eslint-plugin-jsx-a11y": "^6.10.2", 74 | "eslint-plugin-n": "^17.23.1", 75 | "eslint-plugin-prettier": "^5.5.4", 76 | "eslint-plugin-react": "^7.37.5", 77 | "eslint-plugin-react-hooks": "^7.0.1", 78 | "globals": "^16.5.0", 79 | "jiti": "^2.6.1", 80 | "playwright": "^1.57.0", 81 | "prettier": "^3.7.4", 82 | "typescript": "^5.9.3", 83 | "typescript-eslint": "^8.50.0", 84 | "vite": "^7.3.0", 85 | "vite-plugin-electron": "^0.29.0", 86 | "vite-plugin-electron-renderer": "^0.14.6", 87 | "vite-plugin-eslint": "^1.8.1" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Project 2 | 3 | Thank you for contributing to the project. Your contributions will help us take the project to the next level. 4 | 5 | This project adheres to the Contributor Covenant code of conduct. Your contribution implies that you have read and agree to this policy. Any behavior that undermines the quality of the project community, including this policy, will be warned or restricted by the maintainers. 6 | 7 | ## Issues 8 | 9 | Issues can be created on the following page: https://github.com/jooy2/retron/issues 10 | 11 | Alternatively, you can email the package maintainer. However, we prefer to track progress via GitHub Issues. 12 | 13 | When creating an issue, keep the following in mind: 14 | 15 | - Please specify the correct category selection based on the format of the issue (e.g., bug report, feature request). 16 | - Check to see if there are duplicate issues. 17 | - Describe in detail what is happening and what needs to be fixed. You may need additional materials such as images or video. 18 | - Use appropriate keyword titles to make it easy for others to search and understand. 19 | - Please use English in all content. 20 | - You may need to describe the environment in which the issue occurs. 21 | 22 | ## How to contribute (Pull Requests) 23 | 24 | ### Write the code you want to change 25 | 26 | Here's the process for contributing to the project: 27 | 28 | 1. Clone the project (or rebase to the latest commit in the main branch) 29 | 2. Install the package (if the package manager exists) 30 | 3. Setting up lint or code formatter in the IDE (if your project includes a linter) and installing the relevant plugins. Some projects may use specific commands to check rules and perform formatting after module installation and before committing. 31 | 4. Write the code that needs to be fixed 32 | 5. Update the documentation (if it exists) or create a new one. If your project supports multilingual documentation, update the documentation for all languages. You can fill in the content in your own language and not translate it. 33 | 6. Add or modify tests as needed (if test code exists). You should also verify that existing tests pass. 34 | 35 | ### Write a commit message 36 | 37 | While we don't have strict restrictions on commit messages, we recommend that you follow the recommendations below whenever possible: 38 | 39 | - Write in English. 40 | - Use the ` symbol to name functions, variables, or folders and files. 41 | - Use a format like `xxx: message (fixes #1)`. The content in parentheses is optional. 42 | - The message includes a summary of what was modified. 43 | - It's a good idea to separate multiple modifications into their own commit messages. 44 | 45 | It is recommended that you include a tag at the beginning of the commit message. Between the tag and the message, use `: ` between the tag and the message. 46 | 47 | tags conform to the ["Udacity Git Commit Message Style Guide"](https://udacity.github.io/git-styleguide). However, you are welcome to use tags not listed here for additional situations. 48 | 49 | - `feat`: A new feature 50 | - `fix`: A bug fix 51 | - `docs`: Changes to documentation 52 | - `style`: Formatting, missing semicolons, etc.; no code change 53 | - `refactor`: Refactoring production code 54 | - `test`: Adding tests, refactoring test; no production code change 55 | - `chore`: Updating build tasks, package manager configs, etc.; no production code change 56 | 57 | Informal tags: 58 | 59 | - `package`: Modifications to package settings, modules, or GitHub projects 60 | - `typo`: Fix typos 61 | 62 | ### Create a pull request 63 | 64 | When creating a pull request, keep the following in mind: 65 | 66 | - Include a specific description of what the modification is, why it needs to be made, and how it works. 67 | - Check to see if there are duplicate pull requests. 68 | - Please use English in all content. 69 | 70 | Typically, a project maintainer will review and test your code before merging it into the project. This process can take some time, and they may ask you for further edits or clarifications in the comments. 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![Retron-logo](src/renderer/public/images/retron-logo.webp) 4 | 5 | ## Vite + Electron + React + Material-UI Template 6 | 7 | > [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jooy2/retron/blob/main/LICENSE) ![Programming Language Usage](https://img.shields.io/github/languages/top/jooy2/retron) ![Languages](https://img.shields.io/github/languages/count/jooy2/retron) ![Commit Count](https://img.shields.io/github/commit-activity/y/jooy2/retron) ![github repo size](https://img.shields.io/github/repo-size/jooy2/retron) [![Followers](https://img.shields.io/github/followers/jooy2?style=social)](https://github.com/jooy2) ![Stars](https://img.shields.io/github/stars/jooy2/retron?style=social) 8 | 9 | This is a skeleton template for easily creating React-based Electron projects. 10 | 11 | It is configured to experience fast development and build speed using **[Vite](https://vitejs.dev)** bundler. As a bonus, it includes several React utilities and layout configurations. 12 | 13 |
14 | 15 | ## Advantages of use 16 | 17 | - ✅ You can build immediately without any presets, so you can develop quickly. 18 | - ✅ It is being maintained quickly to be compatible with the latest `React` and `Electron`, as well as many modules. 19 | - ✅ There is no need to worry about layout and data management by using various additional templates. 20 | 21 | ## Features 22 | 23 | - ⚡️ Rapid development through hot-reload 24 | - ⚡️ Cross-platform development and build support 25 | - ⚡️ Support for automated application testing 26 | - ⚡️ TypeScript support 27 | - ⚡️ Multilingual support 28 | - ⚡️ Support for themes (dark & light) 29 | - ⚡️ Basic layout manager 30 | - ⚡️ Global state management through the Redux store 31 | - ⚡️ Quick support through the GitHub community 32 | 33 | ## Components 34 | 35 | - **For compile & build** 36 | 37 | - `vite` 38 | - `electron` 39 | - `electron-builder` (Package builder) 40 | 41 | - **For web development framework** 42 | 43 | - `react` 44 | - `react-dom` 45 | - `react-router-dom` 46 | - `@redux/toolkit` & `react-redux` (Global state management) 47 | - `typescript` 48 | 49 | - **For CSS Design** 50 | 51 | - `@mui/material` (Material Design CSS Framework) 52 | - `@emotion/react` 53 | 54 | - **For Multilingual language support** 55 | 56 | - `i18next` (Multilingual translation) 57 | 58 | - **For development utils** 59 | 60 | - `eslint` (Code syntax checking) 61 | - `eslint-plugin-react-hooks` 62 | - `prettier` 63 | 64 | - **For testing** 65 | 66 | - `playwright` 67 | 68 | ## Installation 69 | 70 | You can easily clone a repository with just the npm command. (Recommend) 71 | 72 | ```shell 73 | $ npm init retron 74 | ``` 75 | 76 | OR, Click **[Use this template](https://github.com/jooy2/retron/generate)** to instantly create your own project. 77 | 78 | OR, Clone this repo using below command. 79 | 80 | ```shell 81 | $ git clone https://github.com/jooy2/retron 82 | ``` 83 | 84 | Then, install the dependency module. 85 | 86 | ```shell 87 | # via npm 88 | $ npm i 89 | 90 | # via yarn (https://yarnpkg.com) 91 | $ yarn install 92 | 93 | # via pnpm (https://pnpm.io) 94 | $ pnpm i 95 | ``` 96 | 97 | You can test your project in the development environment using the following command: 98 | 99 | ```shell 100 | $ npm run dev 101 | ``` 102 | 103 | ## Build 104 | 105 | **Retron** can build targeting Windows 10 or later, macOS 14.x or later, and major Linux distributions. 106 | 107 | ```shell 108 | # For Windows (.exe, .appx) 109 | $ npm run build:win 110 | 111 | # For macOS (.dmg) 112 | $ npm run build:mac 113 | 114 | # For Linux (.rpm, .deb, .snap) 115 | $ npm run build:linux 116 | ``` 117 | 118 | The built packages can be found in `release/{version}` location. 119 | 120 | ### Build settings for projects that use Native Node modules 121 | 122 | For projects that use the **Native Node Module**, add the following script to your `package.json`: When installing dependencies, `electron-builder` will take care of any modules that require rebuilding. 123 | 124 | ```json 125 | { 126 | "scripts": { 127 | "postinstall": "electron-builder install-app-deps" 128 | } 129 | } 130 | ``` 131 | 132 | ### What do I need to do for a multi-platform build? 133 | 134 | **macOS** is recommended if you want to build multiple platforms simultaneously on one platform. Because it can be configured with just a few very simple settings. 135 | 136 | You can perform multi-platform builds at once with the following command. Alternatively, you can just do it for the OS you want via the individual build commands above. 137 | 138 | ```shell 139 | $ npm run build 140 | ``` 141 | 142 | ## Looking for Electron templates made with Vue? 143 | 144 | Also check out the `Vutron` project, which consists of Vite + Vue 3 + Vuetify + Electron. 145 | 146 | https://github.com/jooy2/vutron 147 | 148 | ## Contributing 149 | 150 | Anyone can contribute to the project by reporting new issues or submitting a pull request. For more information, please see [CONTRIBUTING.md](CONTRIBUTING.md). 151 | 152 | ## License 153 | 154 | Please see the [LICENSE](LICENSE) file for more information about project owners, usage rights, and more. 155 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 8 | 9 | ## Our Standards 10 | 11 | Examples of behavior that contributes to a positive environment for our community include: 12 | 13 | - Demonstrating empathy and kindness toward other people 14 | - Being respectful of differing opinions, viewpoints, and experiences 15 | - Giving and gracefully accepting constructive feedback 16 | - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 17 | - Focusing on what is best not just for us as individuals, but for the overall community 18 | 19 | Examples of unacceptable behavior include: 20 | 21 | - The use of sexualized language or imagery, and sexual attention or advances of any kind 22 | - Trolling, insulting or derogatory comments, and personal or political attacks 23 | - Public or private harassment 24 | - Publishing others' private information, such as a physical or email address, without their explicit permission 25 | - Other conduct which could reasonably be considered inappropriate in a professional setting 26 | 27 | ## Enforcement Responsibilities 28 | 29 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 32 | 33 | ## Scope 34 | 35 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 36 | 37 | ## Enforcement 38 | 39 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at https://cdget.com/contact. All complaints will be reviewed and investigated promptly and fairly. 40 | 41 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 42 | 43 | ## Enforcement Guidelines 44 | 45 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 46 | 47 | ### 1. Correction 48 | 49 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 50 | 51 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 52 | 53 | ### 2. Warning 54 | 55 | **Community Impact**: A violation through a single incident or series of actions. 56 | 57 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 58 | 59 | ### 3. Temporary Ban 60 | 61 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 62 | 63 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 64 | 65 | ### 4. Permanent Ban 66 | 67 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 68 | 69 | **Consequence**: A permanent ban from any sort of public interaction within the community. 70 | 71 | ## Attribution 72 | 73 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 74 | 75 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 76 | 77 | [homepage]: https://www.contributor-covenant.org 78 | 79 | For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. 80 | --------------------------------------------------------------------------------