├── src ├── renderer │ ├── pages │ │ ├── Hello.css │ │ ├── Hello.tsx │ │ ├── Settings.tsx │ │ └── Songs.tsx │ ├── index.tsx │ ├── index.ejs │ ├── App.css │ ├── components │ │ ├── MonacoEditor.tsx │ │ └── AppLayout.tsx │ ├── store.ts │ ├── App.tsx │ └── preload.d.ts ├── main │ ├── utils │ │ ├── ipcResponse.ts │ │ ├── songDeps.ts │ │ └── assets.ts │ ├── util.ts │ ├── preload.ts │ ├── menuHandler.ts │ ├── main.ts │ └── menu.ts ├── globalStore.ts ├── stateSlices │ └── assets.ts └── type.ts ├── .erb ├── mocks │ └── fileMock.js ├── img │ ├── erb-logo.png │ ├── erb-banner.svg │ └── palette-sponsor-banner.svg ├── configs │ ├── .eslintrc │ ├── webpack.config.eslint.ts │ ├── webpack.paths.ts │ ├── webpack.config.base.ts │ ├── webpack.config.renderer.dev.dll.ts │ ├── webpack.config.preload.dev.ts │ ├── webpack.config.main.prod.ts │ ├── webpack.config.renderer.prod.ts │ └── webpack.config.renderer.dev.ts └── scripts │ ├── .eslintrc │ ├── clean.js │ ├── delete-source-maps.js │ ├── link-modules.ts │ ├── check-node-env.js │ ├── check-port-in-use.js │ ├── electron-rebuild.js │ ├── check-build-exists.ts │ ├── notarize.js │ └── check-native-dep.js ├── assets ├── aam.psd ├── icon.icns ├── icon.ico ├── icon.png ├── aam_mac.psd ├── icons │ ├── 128x128.png │ ├── 16x16.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ └── 1024x1024.png ├── entitlements.mac.plist └── assets.d.ts ├── .husky └── pre-commit ├── .vscode ├── extensions.json ├── settings.json └── launch.json ├── release └── app │ ├── yarn.lock │ ├── package-lock.json │ └── package.json ├── .editorconfig ├── .gitattributes ├── .gitignore ├── README.md ├── tsconfig.json ├── .eslintignore ├── LICENSE ├── .eslintrc.js ├── CODE_OF_CONDUCT.md └── package.json /src/renderer/pages/Hello.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.erb/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /assets/aam.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feightwywx/aam/HEAD/assets/aam.psd -------------------------------------------------------------------------------- /assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feightwywx/aam/HEAD/assets/icon.icns -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feightwywx/aam/HEAD/assets/icon.ico -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feightwywx/aam/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/aam_mac.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feightwywx/aam/HEAD/assets/aam_mac.psd -------------------------------------------------------------------------------- /.erb/img/erb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feightwywx/aam/HEAD/.erb/img/erb-logo.png -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /assets/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feightwywx/aam/HEAD/assets/icons/128x128.png -------------------------------------------------------------------------------- /assets/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feightwywx/aam/HEAD/assets/icons/16x16.png -------------------------------------------------------------------------------- /assets/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feightwywx/aam/HEAD/assets/icons/256x256.png -------------------------------------------------------------------------------- /assets/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feightwywx/aam/HEAD/assets/icons/32x32.png -------------------------------------------------------------------------------- /assets/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feightwywx/aam/HEAD/assets/icons/48x48.png -------------------------------------------------------------------------------- /assets/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feightwywx/aam/HEAD/assets/icons/512x512.png -------------------------------------------------------------------------------- /assets/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feightwywx/aam/HEAD/assets/icons/64x64.png -------------------------------------------------------------------------------- /assets/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feightwywx/aam/HEAD/assets/icons/1024x1024.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig"] 3 | } 4 | -------------------------------------------------------------------------------- /release/app/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /.erb/configs/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.eslint.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/no-unresolved: off, import/no-self-import: off */ 2 | 3 | module.exports = require('./webpack.config.renderer.dev').default; 4 | -------------------------------------------------------------------------------- /.erb/scripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off", 6 | "import/no-extraneous-dependencies": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.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 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.exe binary 3 | *.png binary 4 | *.jpg binary 5 | *.jpeg binary 6 | *.ico binary 7 | *.icns binary 8 | *.eot binary 9 | *.otf binary 10 | *.ttf binary 11 | *.woff binary 12 | *.woff2 binary 13 | -------------------------------------------------------------------------------- /src/renderer/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | import App from './App'; 3 | import store from './store'; 4 | 5 | const container = document.getElementById('root')!; 6 | const root = createRoot(container); 7 | root.render(); 8 | 9 | -------------------------------------------------------------------------------- /release/app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aam", 3 | "version": "0.0.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "aam", 9 | "version": "0.0.1", 10 | "hasInstallScript": true, 11 | "license": "MIT" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.erb/scripts/clean.js: -------------------------------------------------------------------------------- 1 | import rimraf from 'rimraf'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const foldersToRemove = [ 5 | webpackPaths.distPath, 6 | webpackPaths.buildPath, 7 | webpackPaths.dllPath, 8 | ]; 9 | 10 | foldersToRemove.forEach((folder) => { 11 | rimraf.sync(folder); 12 | }); 13 | -------------------------------------------------------------------------------- /.erb/scripts/delete-source-maps.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import rimraf from 'rimraf'; 3 | import webpackPaths from '../configs/webpack.paths'; 4 | 5 | export default function deleteSourceMaps() { 6 | rimraf.sync(path.join(webpackPaths.distMainPath, '*.js.map')); 7 | rimraf.sync(path.join(webpackPaths.distRendererPath, '*.js.map')); 8 | } 9 | -------------------------------------------------------------------------------- /src/renderer/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | Arcaea Assets Manager 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /.erb/scripts/link-modules.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const { srcNodeModulesPath } = webpackPaths; 5 | const { appNodeModulesPath } = webpackPaths; 6 | 7 | if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) { 8 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction'); 9 | } 10 | -------------------------------------------------------------------------------- /assets/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/utils/ipcResponse.ts: -------------------------------------------------------------------------------- 1 | import { IPCResponse } from 'type'; 2 | 3 | export function makeSuccessResp(data: T): IPCResponse { 4 | return { 5 | code: 0, 6 | message: 'success', 7 | data, 8 | }; 9 | } 10 | 11 | export function makeFailResp(message: string, code = -1): IPCResponse { 12 | return { 13 | code, 14 | message, 15 | data: null, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /.erb/scripts/check-node-env.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | export default function checkNodeEnv(expectedEnv) { 4 | if (!expectedEnv) { 5 | throw new Error('"expectedEnv" not set'); 6 | } 7 | 8 | if (process.env.NODE_ENV !== expectedEnv) { 9 | console.log( 10 | chalk.whiteBright.bgRed.bold( 11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config` 12 | ) 13 | ); 14 | process.exit(2); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/util.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/prefer-default-export: off */ 2 | import { URL } from 'url'; 3 | import path from 'path'; 4 | 5 | export function resolveHtmlPath(htmlFileName: string) { 6 | if (process.env.NODE_ENV === 'development') { 7 | const port = process.env.PORT || 1212; 8 | const url = new URL(`http://localhost:${port}`); 9 | url.pathname = htmlFileName; 10 | return url.href; 11 | } 12 | return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`; 13 | } 14 | -------------------------------------------------------------------------------- /.erb/scripts/check-port-in-use.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import detectPort from 'detect-port'; 3 | 4 | const port = process.env.PORT || '1212'; 5 | 6 | detectPort(port, (err, availablePort) => { 7 | if (port !== String(availablePort)) { 8 | throw new Error( 9 | chalk.whiteBright.bgRed.bold( 10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start` 11 | ) 12 | ); 13 | } else { 14 | process.exit(0); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arcaea Assets Manager 2 | 3 | 一个[Arcaea](https://arcaea.lowiro.com/)资产管理器。 4 | 5 | ## 功能 6 | 7 | - 资产导入 8 | - 曲目导入,支持每次打包时自动更新曲目资源 9 | - 背景导入 10 | - 打包 11 | - ipa和apk打包 12 | - 依赖验证,检查可能被漏打包的文件 13 | 14 | ## 使用方法 15 | 16 | [看这里](https://www.direcore.xyz/archives/43/) 17 | 18 | ## Troubleshooting 19 | 20 | ### Linux 21 | 22 | #### 提示找不到libz.so 23 | 24 | ARM架构的AppImage打包可能存在这样的问题。参见:[这个Issue](https://github.com/AppImage/AppImageKit/issues/964) 25 | 26 | 你可以安装`zlib1g-dev`来解决这个问题,把`libz.so.1`链接到`libz.so`似乎也是可行的。 27 | 28 | 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "es2021", 5 | "module": "commonjs", 6 | "lib": ["dom", "es2021"], 7 | "jsx": "react-jsx", 8 | "strict": true, 9 | "sourceMap": true, 10 | "baseUrl": "./src", 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "resolveJsonModule": true, 15 | "allowJs": true, 16 | "outDir": ".erb/dll" 17 | }, 18 | "exclude": ["test", "release/build", "release/app/dist", ".erb/dll"] 19 | } 20 | -------------------------------------------------------------------------------- /src/renderer/App.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @NOTE: Prepend a `~` to css file paths that are in your node_modules 3 | * See https://github.com/webpack-contrib/sass-loader#imports 4 | */ 5 | 6 | html { 7 | height: 100%; 8 | width: 100%; 9 | } 10 | 11 | * { 12 | transition: all 0.02s; 13 | } 14 | 15 | body { 16 | margin: 0; 17 | background-color: #fff; 18 | height: 100%; 19 | } 20 | 21 | @media (prefers-color-scheme: dark) { 22 | body { 23 | background-color: #141414; 24 | } 25 | } 26 | 27 | input:focus { 28 | outline: none; 29 | box-shadow: none; 30 | } 31 | -------------------------------------------------------------------------------- /src/globalStore.ts: -------------------------------------------------------------------------------- 1 | import Store from 'electron-store'; 2 | import { StoreType } from 'type'; 3 | 4 | export const globalStore = new Store({ 5 | defaults: { 6 | assets: { 7 | path: '', 8 | }, 9 | settings: { 10 | logLevel: 'info', 11 | minimalRating: 0, 12 | ignoredSong: 13 | 'arcahv,tempestissimo,defection,infinitestrife,worldender,pentiment,arcanaeden,testify,lovelessdress,last,lasteternity,callimakarma,ignotusafterburn,redandblueandgreen,singularityvvvip,overdead,mismal', 14 | }, 15 | }, 16 | }); 17 | 18 | export default { globalStore }; 19 | -------------------------------------------------------------------------------- /release/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aam", 3 | "version": "0.0.2", 4 | "description": "一个Arcaea资产管理工具", 5 | "license": "MIT", 6 | "author": { 7 | "name": ".direwolf", 8 | "email": "canis@direcore.xyz", 9 | "url": "https://github.com/feightwywx/aam" 10 | }, 11 | "main": "./dist/main/main.js", 12 | "scripts": { 13 | "rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js", 14 | "postinstall": "npm run rebuild && npm run link-modules", 15 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts" 16 | }, 17 | "dependencies": {} 18 | } 19 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | 31 | # eslint ignores hidden directories by default: 32 | # https://github.com/eslint/eslint/issues/8429 33 | !.erb 34 | -------------------------------------------------------------------------------- /.erb/scripts/electron-rebuild.js: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import fs from 'fs'; 3 | import { dependencies } from '../../release/app/package.json'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | if ( 7 | Object.keys(dependencies || {}).length > 0 && 8 | fs.existsSync(webpackPaths.appNodeModulesPath) 9 | ) { 10 | const electronRebuildCmd = 11 | '../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .'; 12 | const cmd = 13 | process.platform === 'win32' 14 | ? electronRebuildCmd.replace(/\//g, '\\') 15 | : electronRebuildCmd; 16 | execSync(cmd, { 17 | cwd: webpackPaths.appPath, 18 | stdio: 'inherit', 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /src/renderer/components/MonacoEditor.tsx: -------------------------------------------------------------------------------- 1 | import * as monaco from 'monaco-editor'; 2 | import OriginalMonaco, { EditorProps, loader } from '@monaco-editor/react'; 3 | import React, { useEffect } from 'react'; 4 | import { Spin } from 'antd'; 5 | 6 | loader.config({ monaco }); 7 | 8 | export const MonacoEditor: React.FC = (props) => { 9 | return ( 10 | } 17 | // eslint-disable-next-line react/jsx-props-no-spreading 18 | {...props} 19 | /> 20 | ); 21 | }; 22 | 23 | export default MonacoEditor; 24 | -------------------------------------------------------------------------------- /src/stateSlices/assets.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | import { Song } from 'type'; 3 | 4 | interface assetsState { 5 | path: string; 6 | songs?: Song[]; 7 | } 8 | 9 | const initialState: assetsState = { 10 | path: '', 11 | }; 12 | 13 | export const assetsSlice = createSlice({ 14 | name: 'assets', 15 | initialState, 16 | reducers: { 17 | setPath: (state, action: PayloadAction) => { 18 | state.path = action.payload; 19 | }, 20 | setSongs: (state, action: PayloadAction) => { 21 | state.songs = action.payload; 22 | } 23 | }, 24 | }); 25 | 26 | export const { setPath, setSongs } = assetsSlice.actions; 27 | export default assetsSlice.reducer; 28 | -------------------------------------------------------------------------------- /assets/assets.d.ts: -------------------------------------------------------------------------------- 1 | type Styles = Record; 2 | 3 | declare module '*.svg' { 4 | export const ReactComponent: React.FC>; 5 | 6 | const content: string; 7 | export default content; 8 | } 9 | 10 | declare module '*.png' { 11 | const content: string; 12 | export default content; 13 | } 14 | 15 | declare module '*.jpg' { 16 | const content: string; 17 | export default content; 18 | } 19 | 20 | declare module '*.scss' { 21 | const content: Styles; 22 | export default content; 23 | } 24 | 25 | declare module '*.sass' { 26 | const content: Styles; 27 | export default content; 28 | } 29 | 30 | declare module '*.css' { 31 | const content: Styles; 32 | export default content; 33 | } 34 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | ".eslintrc": "jsonc", 4 | ".prettierrc": "jsonc", 5 | ".eslintignore": "ignore" 6 | }, 7 | 8 | "eslint.validate": [ 9 | "javascript", 10 | "javascriptreact", 11 | "html", 12 | "typescriptreact" 13 | ], 14 | 15 | "javascript.validate.enable": false, 16 | "javascript.format.enable": false, 17 | "typescript.format.enable": false, 18 | 19 | "search.exclude": { 20 | ".git": true, 21 | ".eslintcache": true, 22 | ".erb/dll": true, 23 | "release/{build,app/dist}": true, 24 | "node_modules": true, 25 | "npm-debug.log.*": true, 26 | "test/**/__snapshots__": true, 27 | "package-lock.json": true, 28 | "*.{css,sass,scss}.d.ts": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Electron: Main", 6 | "type": "node", 7 | "request": "launch", 8 | "protocol": "inspector", 9 | "runtimeExecutable": "npm", 10 | "runtimeArgs": ["run", "start"], 11 | "env": { 12 | "MAIN_ARGS": "--inspect=5858 --remote-debugging-port=9223" 13 | } 14 | }, 15 | { 16 | "name": "Electron: Renderer", 17 | "type": "chrome", 18 | "request": "attach", 19 | "port": 9223, 20 | "webRoot": "${workspaceFolder}", 21 | "timeout": 15000 22 | } 23 | ], 24 | "compounds": [ 25 | { 26 | "name": "Electron: All", 27 | "configurations": ["Electron: Main", "Electron: Renderer"] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.erb/scripts/check-build-exists.ts: -------------------------------------------------------------------------------- 1 | // Check if the renderer and main bundles are built 2 | import path from 'path'; 3 | import chalk from 'chalk'; 4 | import fs from 'fs'; 5 | import webpackPaths from '../configs/webpack.paths'; 6 | 7 | const mainPath = path.join(webpackPaths.distMainPath, 'main.js'); 8 | const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js'); 9 | 10 | if (!fs.existsSync(mainPath)) { 11 | throw new Error( 12 | chalk.whiteBright.bgRed.bold( 13 | 'The main process is not built yet. Build it by running "npm run build:main"' 14 | ) 15 | ); 16 | } 17 | 18 | if (!fs.existsSync(rendererPath)) { 19 | throw new Error( 20 | chalk.whiteBright.bgRed.bold( 21 | 'The renderer process is not built yet. Build it by running "npm run build:renderer"' 22 | ) 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/renderer/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; 3 | import assetsReducer from '../stateSlices/assets'; 4 | 5 | const store = configureStore({ 6 | reducer: { assets: assetsReducer }, 7 | }); 8 | 9 | export default store; 10 | 11 | // Infer the `RootState` and `AppDispatch` types from the store itself 12 | export type RootState = ReturnType 13 | // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} 14 | export type AppDispatch = typeof store.dispatch 15 | 16 | // Use throughout your app instead of plain `useDispatch` and `useSelector` 17 | export const useAppDispatch: () => AppDispatch = useDispatch 18 | export const useAppSelector: TypedUseSelectorHook = useSelector 19 | -------------------------------------------------------------------------------- /.erb/scripts/notarize.js: -------------------------------------------------------------------------------- 1 | const { notarize } = require('electron-notarize'); 2 | const { build } = require('../../package.json'); 3 | 4 | exports.default = async function notarizeMacos(context) { 5 | const { electronPlatformName, appOutDir } = context; 6 | if (electronPlatformName !== 'darwin') { 7 | return; 8 | } 9 | 10 | if (process.env.CI !== 'true') { 11 | console.warn('Skipping notarizing step. Packaging is not running in CI'); 12 | return; 13 | } 14 | 15 | if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) { 16 | console.warn( 17 | 'Skipping notarizing step. APPLE_ID and APPLE_ID_PASS env variables must be set' 18 | ); 19 | return; 20 | } 21 | 22 | const appName = context.packager.appInfo.productFilename; 23 | 24 | await notarize({ 25 | appBundleId: build.appId, 26 | appPath: `${appOutDir}/${appName}.app`, 27 | appleId: process.env.APPLE_ID, 28 | appleIdPassword: process.env.APPLE_ID_PASS, 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /src/renderer/pages/Hello.tsx: -------------------------------------------------------------------------------- 1 | import './Hello.css'; 2 | import { setPath, setSongs } from 'stateSlices/assets'; 3 | import { Button } from 'antd'; 4 | import { useNavigate } from 'react-router-dom'; 5 | 6 | import { useAppDispatch, useAppSelector } from '../store'; 7 | 8 | const Hello = () => { 9 | const assets = useAppSelector((state) => state.assets); 10 | const dispatch = useAppDispatch(); 11 | const navigate = useNavigate(); 12 | 13 | return ( 14 | <> 15 | 30 |

{assets.path}

31 | 32 | ); 33 | }; 34 | 35 | export default Hello; 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 .direwolf 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 | -------------------------------------------------------------------------------- /.erb/configs/webpack.paths.ts: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const rootPath = path.join(__dirname, '../..'); 4 | 5 | const dllPath = path.join(__dirname, '../dll'); 6 | 7 | const srcPath = path.join(rootPath, 'src'); 8 | const srcMainPath = path.join(srcPath, 'main'); 9 | const srcRendererPath = path.join(srcPath, 'renderer'); 10 | 11 | const releasePath = path.join(rootPath, 'release'); 12 | const appPath = path.join(releasePath, 'app'); 13 | const appPackagePath = path.join(appPath, 'package.json'); 14 | const appNodeModulesPath = path.join(appPath, 'node_modules'); 15 | const srcNodeModulesPath = path.join(srcPath, 'node_modules'); 16 | 17 | const distPath = path.join(appPath, 'dist'); 18 | const distMainPath = path.join(distPath, 'main'); 19 | const distRendererPath = path.join(distPath, 'renderer'); 20 | 21 | const buildPath = path.join(releasePath, 'build'); 22 | 23 | export default { 24 | rootPath, 25 | dllPath, 26 | srcPath, 27 | srcMainPath, 28 | srcRendererPath, 29 | releasePath, 30 | appPath, 31 | appPackagePath, 32 | appNodeModulesPath, 33 | srcNodeModulesPath, 34 | distPath, 35 | distMainPath, 36 | distRendererPath, 37 | buildPath, 38 | }; 39 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'erb', 3 | rules: { 4 | // A temporary hack related to IDE not resolving correct package.json 5 | 'import/no-extraneous-dependencies': 'off', 6 | 'import/no-unresolved': 'error', 7 | // Since React 17 and typescript 4.1 you can safely disable the rule 8 | 'react/react-in-jsx-scope': 'off', 9 | 'prettier/prettier': 'warn', 10 | // some custom rules 11 | 'no-underscore-dangle': 'off', 12 | 'no-restricted-syntax': 'off', 13 | 'no-await-in-loop': 'warn', 14 | }, 15 | parserOptions: { 16 | ecmaVersion: 2020, 17 | sourceType: 'module', 18 | project: './tsconfig.json', 19 | tsconfigRootDir: __dirname, 20 | createDefaultProgram: true, 21 | }, 22 | settings: { 23 | 'import/resolver': { 24 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below 25 | node: {}, 26 | webpack: { 27 | config: require.resolve('./.erb/configs/webpack.config.eslint.ts'), 28 | }, 29 | typescript: {}, 30 | }, 31 | 'import/parsers': { 32 | '@typescript-eslint/parser': ['.ts', '.tsx'], 33 | }, 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /src/renderer/App.tsx: -------------------------------------------------------------------------------- 1 | import { HashRouter, Routes, Route } from 'react-router-dom'; 2 | import { Provider as ReduxProvider } from 'react-redux'; 3 | import { ConfigProvider, theme } from 'antd'; 4 | import zhCN from 'antd/locale/zh_CN'; 5 | import { useMediaQuery } from 'usehooks-ts'; 6 | 7 | import './App.css'; 8 | import { AppLayout } from './components/AppLayout'; 9 | import Hello from './pages/Hello'; 10 | import Songs from './pages/Songs'; 11 | import store from './store'; 12 | import Settings from './pages/Settings'; 13 | 14 | export default function App() { 15 | const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); 16 | 17 | return ( 18 | 19 | 28 | 29 | 30 | 31 | } /> 32 | } /> 33 | } /> 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/type.ts: -------------------------------------------------------------------------------- 1 | import { LogLevel } from 'electron-log'; 2 | 3 | export interface IPCResponse { 4 | code: number; 5 | message: string; 6 | data: T; 7 | } 8 | 9 | export interface SongBase { 10 | idx: number; 11 | id: string; 12 | title_localized: { 13 | en: string; 14 | ja?: string; 15 | }; 16 | artist: string; 17 | bpm: string; 18 | bpm_base: number; 19 | set: string; 20 | purchase: string; 21 | audioPreview: number; 22 | audioPreviewEnd: number; 23 | side: 0 | 1 | 2; 24 | bg: string; 25 | remote_dl: boolean; 26 | date: number; 27 | version: string; 28 | difficulties: SongDifficulty[]; 29 | } 30 | 31 | export interface Song extends SongBase { 32 | _external?: string; 33 | } 34 | 35 | export interface SongDifficulty { 36 | ratingClass: 0 | 1 | 2 | 3; 37 | chartDesigner: string; 38 | jacketDesigner: string; 39 | rating: number; 40 | ratingPlus?: boolean; 41 | jacketOverride?: boolean; 42 | audioOverride?: boolean; 43 | hidden_until_unlocked?: true; 44 | hidden_until?: 'none' | 'always' | 'difficulty' | 'song'; 45 | } 46 | 47 | export interface Songlist { 48 | songs: Song[]; 49 | } 50 | 51 | export interface AssetDependence { 52 | dep: string; 53 | sourceID: string; 54 | } 55 | 56 | export interface StoreType { 57 | assets: { 58 | path: string; 59 | songs?: Song[]; 60 | }; 61 | settings: SettingsType; 62 | } 63 | 64 | export interface SettingsType { 65 | logLevel: LogLevel; 66 | minimalRating: number; 67 | ignoredSong: string; 68 | } 69 | 70 | export interface AppInfo { 71 | version: string; 72 | isDebug: boolean; 73 | } 74 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base webpack config used across other specific configs 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import CopyWebpackPlugin from 'copy-webpack-plugin'; 7 | import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin'; 8 | import webpackPaths from './webpack.paths'; 9 | import { dependencies as externals } from '../../release/app/package.json'; 10 | 11 | const configuration: webpack.Configuration = { 12 | externals: [...Object.keys(externals || {})], 13 | 14 | stats: 'errors-only', 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.[jt]sx?$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: 'ts-loader', 23 | options: { 24 | // Remove this line to enable type checking in webpack builds 25 | transpileOnly: true, 26 | compilerOptions: { 27 | module: 'esnext', 28 | }, 29 | }, 30 | }, 31 | }, 32 | ], 33 | }, 34 | 35 | output: { 36 | path: webpackPaths.srcPath, 37 | // https://github.com/webpack/webpack/issues/1114 38 | library: { 39 | type: 'commonjs2', 40 | }, 41 | }, 42 | 43 | /** 44 | * Determine the array of extensions that should be used to resolve modules. 45 | */ 46 | resolve: { 47 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], 48 | modules: [webpackPaths.srcPath, 'node_modules'], 49 | fallback: { path: require.resolve('path-browserify') }, 50 | }, 51 | 52 | plugins: [ 53 | new webpack.EnvironmentPlugin({ 54 | NODE_ENV: 'production', 55 | }), 56 | new MonacoWebpackPlugin(), 57 | ], 58 | }; 59 | 60 | export default configuration; 61 | -------------------------------------------------------------------------------- /src/renderer/preload.d.ts: -------------------------------------------------------------------------------- 1 | import { Channels } from 'main/preload'; 2 | import { AppInfo, IPCResponse, Song, Songlist } from '../type'; 3 | 4 | declare global { 5 | interface Window { 6 | electron: { 7 | ipcRenderer: { 8 | sendMessage(channel: Channels, args: unknown[]): void; 9 | on( 10 | channel: Channels, 11 | func: (...args: unknown[]) => void 12 | ): (() => void) | undefined; 13 | once(channel: Channels, func: (...args: unknown[]) => void): void; 14 | openDirectory: () => Promise; 15 | showLogFile: () => Promise; 16 | reset: () => Promise; 17 | getAppInfo: () => Promise; 18 | 19 | store: { 20 | get: (key: string) => unknown; 21 | set: (key: string, val: unknown) => void; 22 | // any other methods you've defined... 23 | }; 24 | }; 25 | }; 26 | 27 | aam: { 28 | ipcRenderer: { 29 | loadSongs: (path: string) => Promise>; 30 | saveSonglist: (songlist: Songlist) => Promise; 31 | deleteSongs: (ids: string[]) => Promise; 32 | 33 | onCloseFolder: ( 34 | callback: (event: Event, args: unknown) => void 35 | ) => void; 36 | onPushSongs: ( 37 | callback: ( 38 | event: Event, 39 | args: { path: string; songs: Song[] } 40 | ) => void 41 | ) => void; 42 | onStartGeneratePackage: ( 43 | callback: (event: Event, args: unknown) => void 44 | ) => void; 45 | onStopGeneratePackage: ( 46 | callback: (event: Event, args: unknown) => void 47 | ) => void; 48 | onLog: (callback: (event: Event, args: string) => void) => void; 49 | }; 50 | }; 51 | } 52 | } 53 | 54 | export {}; 55 | -------------------------------------------------------------------------------- /src/main/preload.ts: -------------------------------------------------------------------------------- 1 | import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'; 2 | import { Song, Songlist } from 'type'; 3 | 4 | export type Channels = 'ipc-example'; 5 | 6 | contextBridge.exposeInMainWorld('electron', { 7 | ipcRenderer: { 8 | store: { 9 | get(key: string) { 10 | return ipcRenderer.sendSync('electron-store-get', key); 11 | }, 12 | set(property: string, val: unknown) { 13 | ipcRenderer.send('electron-store-set', property, val); 14 | }, 15 | // Other method you want to add like has(), reset(), etc. 16 | }, 17 | openDirectory: () => ipcRenderer.invoke('dialog:openDirectory'), 18 | showLogFile: () => ipcRenderer.invoke('showLogFile'), 19 | reset: () => ipcRenderer.invoke('reset'), 20 | getAppInfo: () => ipcRenderer.invoke('getAppInfo'), 21 | }, 22 | }); 23 | 24 | contextBridge.exposeInMainWorld('aam', { 25 | ipcRenderer: { 26 | loadSongs: (path: string) => ipcRenderer.invoke('aam:loadSongs', path), 27 | saveSonglist: (songlist: Songlist) => 28 | ipcRenderer.invoke('aam:saveSonglist', songlist), 29 | deleteSongs: (ids: string[]) => ipcRenderer.invoke('aam:deleteSongs', ids), 30 | 31 | onCloseFolder: (callback: (event: Event, args: unknown) => void) => 32 | ipcRenderer.on('aam:closeFolder', callback), 33 | onPushSongs: (callback: (event: Event, args: unknown) => void) => { 34 | ipcRenderer.on('aam:pushSongs', callback); 35 | }, 36 | onStartGeneratePackage: ( 37 | callback: (event: Event, args: unknown) => void 38 | ) => { 39 | ipcRenderer.on('aam:startGeneratePackage', callback); 40 | }, 41 | onStopGeneratePackage: ( 42 | callback: (event: Event, args: unknown) => void 43 | ) => { 44 | ipcRenderer.on('aam:stopGeneratePackage', callback); 45 | }, 46 | onLog: (callback: (event: Event, args: string) => void) => { 47 | ipcRenderer.on('aam:log', callback); 48 | }, 49 | }, 50 | }); 51 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.dev.dll.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds the DLL for development electron renderer process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import path from 'path'; 7 | import { merge } from 'webpack-merge'; 8 | import baseConfig from './webpack.config.base'; 9 | import webpackPaths from './webpack.paths'; 10 | import { dependencies } from '../../package.json'; 11 | import checkNodeEnv from '../scripts/check-node-env'; 12 | 13 | checkNodeEnv('development'); 14 | 15 | const dist = webpackPaths.dllPath; 16 | 17 | const configuration: webpack.Configuration = { 18 | context: webpackPaths.rootPath, 19 | 20 | devtool: 'eval', 21 | 22 | mode: 'development', 23 | 24 | target: 'electron-renderer', 25 | 26 | externals: ['fsevents', 'crypto-browserify'], 27 | 28 | /** 29 | * Use `module` from `webpack.config.renderer.dev.js` 30 | */ 31 | module: require('./webpack.config.renderer.dev').default.module, 32 | 33 | entry: { 34 | renderer: Object.keys(dependencies || {}), 35 | }, 36 | 37 | output: { 38 | path: dist, 39 | filename: '[name].dev.dll.js', 40 | library: { 41 | name: 'renderer', 42 | type: 'var', 43 | }, 44 | }, 45 | 46 | plugins: [ 47 | new webpack.DllPlugin({ 48 | path: path.join(dist, '[name].json'), 49 | name: '[name]', 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'development', 63 | }), 64 | 65 | new webpack.LoaderOptionsPlugin({ 66 | debug: true, 67 | options: { 68 | context: webpackPaths.srcPath, 69 | output: { 70 | path: webpackPaths.dllPath, 71 | }, 72 | }, 73 | }), 74 | ], 75 | }; 76 | 77 | export default merge(baseConfig, configuration); 78 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.preload.dev.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import { merge } from 'webpack-merge'; 4 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 5 | import baseConfig from './webpack.config.base'; 6 | import webpackPaths from './webpack.paths'; 7 | import checkNodeEnv from '../scripts/check-node-env'; 8 | 9 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's 10 | // at the dev webpack config is not accidentally run in a production environment 11 | if (process.env.NODE_ENV === 'production') { 12 | checkNodeEnv('development'); 13 | } 14 | 15 | const configuration: webpack.Configuration = { 16 | devtool: 'inline-source-map', 17 | 18 | mode: 'development', 19 | 20 | target: 'electron-preload', 21 | 22 | entry: path.join(webpackPaths.srcMainPath, 'preload.ts'), 23 | 24 | output: { 25 | path: webpackPaths.dllPath, 26 | filename: 'preload.js', 27 | library: { 28 | type: 'umd', 29 | }, 30 | }, 31 | 32 | plugins: [ 33 | new BundleAnalyzerPlugin({ 34 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 35 | }), 36 | 37 | /** 38 | * Create global constants which can be configured at compile time. 39 | * 40 | * Useful for allowing different behaviour between development builds and 41 | * release builds 42 | * 43 | * NODE_ENV should be production so that modules do not perform certain 44 | * development checks 45 | * 46 | * By default, use 'development' as NODE_ENV. This can be overriden with 47 | * 'staging', for example, by changing the ENV variables in the npm scripts 48 | */ 49 | new webpack.EnvironmentPlugin({ 50 | NODE_ENV: 'development', 51 | }), 52 | 53 | new webpack.LoaderOptionsPlugin({ 54 | debug: true, 55 | }), 56 | ], 57 | 58 | /** 59 | * Disables webpack processing of __dirname and __filename. 60 | * If you run the bundle in node.js it falls back to these values of node.js. 61 | * https://github.com/webpack/webpack/issues/2010 62 | */ 63 | node: { 64 | __dirname: false, 65 | __filename: false, 66 | }, 67 | 68 | watch: true, 69 | }; 70 | 71 | export default merge(baseConfig, configuration); 72 | -------------------------------------------------------------------------------- /.erb/scripts/check-native-dep.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import chalk from 'chalk'; 3 | import { execSync } from 'child_process'; 4 | import { dependencies } from '../../package.json'; 5 | 6 | if (dependencies) { 7 | const dependenciesKeys = Object.keys(dependencies); 8 | const nativeDeps = fs 9 | .readdirSync('node_modules') 10 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`)); 11 | if (nativeDeps.length === 0) { 12 | process.exit(0); 13 | } 14 | try { 15 | // Find the reason for why the dependency is installed. If it is installed 16 | // because of a devDependency then that is okay. Warn when it is installed 17 | // because of a dependency 18 | const { dependencies: dependenciesObject } = JSON.parse( 19 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString() 20 | ); 21 | const rootDependencies = Object.keys(dependenciesObject); 22 | const filteredRootDependencies = rootDependencies.filter((rootDependency) => 23 | dependenciesKeys.includes(rootDependency) 24 | ); 25 | if (filteredRootDependencies.length > 0) { 26 | const plural = filteredRootDependencies.length > 1; 27 | console.log(` 28 | ${chalk.whiteBright.bgYellow.bold( 29 | 'Webpack does not work with native dependencies.' 30 | )} 31 | ${chalk.bold(filteredRootDependencies.join(', '))} ${ 32 | plural ? 'are native dependencies' : 'is a native dependency' 33 | } and should be installed inside of the "./release/app" folder. 34 | First, uninstall the packages from "./package.json": 35 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')} 36 | ${chalk.bold( 37 | 'Then, instead of installing the package to the root "./package.json":' 38 | )} 39 | ${chalk.whiteBright.bgRed.bold('npm install your-package')} 40 | ${chalk.bold('Install the package to "./release/app/package.json"')} 41 | ${chalk.whiteBright.bgGreen.bold( 42 | 'cd ./release/app && npm install your-package' 43 | )} 44 | Read more about native dependencies at: 45 | ${chalk.bold( 46 | 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure' 47 | )} 48 | `); 49 | process.exit(1); 50 | } 51 | } catch (e) { 52 | console.log('Native dependencies could not be checked'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.main.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for production electron main process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { merge } from 'webpack-merge'; 8 | import TerserPlugin from 'terser-webpack-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import baseConfig from './webpack.config.base'; 11 | import webpackPaths from './webpack.paths'; 12 | import checkNodeEnv from '../scripts/check-node-env'; 13 | import deleteSourceMaps from '../scripts/delete-source-maps'; 14 | 15 | checkNodeEnv('production'); 16 | deleteSourceMaps(); 17 | 18 | const configuration: webpack.Configuration = { 19 | devtool: 'source-map', 20 | 21 | mode: 'production', 22 | 23 | target: 'electron-main', 24 | 25 | entry: { 26 | main: path.join(webpackPaths.srcMainPath, 'main.ts'), 27 | preload: path.join(webpackPaths.srcMainPath, 'preload.ts'), 28 | }, 29 | 30 | output: { 31 | path: webpackPaths.distMainPath, 32 | filename: '[name].js', 33 | library: { 34 | type: 'umd', 35 | }, 36 | }, 37 | 38 | optimization: { 39 | minimizer: [ 40 | new TerserPlugin({ 41 | parallel: true, 42 | }), 43 | ], 44 | }, 45 | 46 | plugins: [ 47 | new BundleAnalyzerPlugin({ 48 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 49 | analyzerPort: 8888, 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'production', 63 | DEBUG_PROD: false, 64 | START_MINIMIZED: false, 65 | }), 66 | 67 | new webpack.DefinePlugin({ 68 | 'process.type': '"main"', 69 | }), 70 | ], 71 | 72 | /** 73 | * Disables webpack processing of __dirname and __filename. 74 | * If you run the bundle in node.js it falls back to these values of node.js. 75 | * https://github.com/webpack/webpack/issues/2010 76 | */ 77 | node: { 78 | __dirname: false, 79 | __filename: false, 80 | }, 81 | }; 82 | 83 | export default merge(baseConfig, configuration); 84 | -------------------------------------------------------------------------------- /src/main/utils/songDeps.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import log from 'electron-log'; 3 | 4 | import type { AssetDependence, Song } from 'type'; 5 | import { globalStore } from '../../globalStore'; 6 | 7 | export async function buildSrcSongDepList(song: Song): Promise { 8 | log.info(`buildSrcSongDepList(): build for ${song.id}`); 9 | const deps = []; 10 | 11 | deps.push('base.ogg'); 12 | deps.push('base.jpg'); 13 | deps.push('base_256.jpg'); 14 | 15 | song.difficulties.forEach((diff) => { 16 | if (diff.rating >= globalStore.store.settings.minimalRating) { 17 | deps.push(`${diff.ratingClass}.aff`); 18 | } 19 | 20 | if (diff.jacketOverride) { 21 | deps.push(`${diff.ratingClass}.jpg`); 22 | deps.push(`${diff.ratingClass}_256.jpg`); 23 | } 24 | 25 | if (diff.audioOverride) { 26 | deps.push(`${diff.ratingClass}.ogg`); 27 | } 28 | }); 29 | 30 | return deps; 31 | } 32 | 33 | export async function buildAssetsSongDepList( 34 | song: Song 35 | ): Promise { 36 | log.info(`buildAssetsSongDepList(): build for ${song.id}`); 37 | if (globalStore.store.settings.ignoredSong.split(',').includes(song.id)) { 38 | log.info(`skipped, song in ignoredSong`); 39 | return []; 40 | } 41 | 42 | const deps: AssetDependence[] = []; 43 | 44 | function pushSongDeps(dep: string) { 45 | deps.push({ 46 | dep: path.join('songs', song.remote_dl ? `dl_${song.id}` : song.id, dep), 47 | sourceID: song.id, 48 | }); 49 | } 50 | 51 | function pushBgDeps(dep: string) { 52 | deps.push({ dep: path.join('img', 'bg', dep), sourceID: song.id }); 53 | } 54 | 55 | if (song.bg) { 56 | pushBgDeps(`${song.bg}.jpg`); 57 | } else { 58 | switch (song.side) { 59 | case 0: 60 | pushBgDeps('base_light.jpg'); 61 | break; 62 | case 1: 63 | pushBgDeps('base_conflict.jpg'); 64 | break; 65 | default: 66 | break; 67 | } 68 | } 69 | 70 | pushSongDeps('base.jpg'); 71 | pushSongDeps('base_256.jpg'); 72 | song.difficulties.forEach((diff) => { 73 | if (diff.jacketOverride) { 74 | pushSongDeps(`${diff.ratingClass}.jpg`); 75 | pushSongDeps(`${diff.ratingClass}_256.jpg`); 76 | } 77 | }); 78 | 79 | if (song.remote_dl) { 80 | pushSongDeps('preview.ogg'); 81 | 82 | song.difficulties.forEach((diff) => { 83 | if (diff.audioOverride) { 84 | pushSongDeps(`${diff.ratingClass}_preview.ogg`); 85 | } 86 | }); 87 | } else { 88 | pushSongDeps('base.ogg'); 89 | 90 | song.difficulties.forEach((diff) => { 91 | if (diff.rating >= globalStore.store.settings.minimalRating) { 92 | pushSongDeps(`${diff.ratingClass}.aff`); 93 | } 94 | 95 | if (diff.audioOverride) { 96 | pushSongDeps(`${diff.ratingClass}.ogg`); 97 | } 98 | }); 99 | } 100 | 101 | return deps; 102 | } 103 | 104 | export default { buildSrcSongDepList }; 105 | -------------------------------------------------------------------------------- /src/renderer/components/AppLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Layout, Menu, message, theme } from 'antd'; 2 | import type { MenuProps } from 'antd'; 3 | import PropTypes from 'prop-types'; 4 | import { useAppDispatch, useAppSelector } from 'renderer/store'; 5 | import { useLocation, useNavigate, useRoutes } from 'react-router-dom'; 6 | import { setPath, setSongs } from 'stateSlices/assets'; 7 | import { useEffect } from 'react'; 8 | import { 9 | AlignLeftOutlined, 10 | PictureOutlined, 11 | SettingOutlined, 12 | } from '@ant-design/icons'; 13 | 14 | export const AppLayout: React.FC<{ 15 | children: React.ReactNode; 16 | siderHidden?: boolean; 17 | }> = ({ children, siderHidden }) => { 18 | const { 19 | token: { colorBgContainer }, 20 | } = theme.useToken(); 21 | const { path } = useAppSelector((state) => state.assets); 22 | 23 | const assets = useAppSelector((state) => state.assets); 24 | const dispatch = useAppDispatch(); 25 | const navigate = useNavigate(); 26 | const location = useLocation(); 27 | 28 | const [messageApi, contextHolder] = message.useMessage(); 29 | console.log(location.pathname); 30 | 31 | const MainMenu: MenuProps['items'] = [ 32 | { 33 | key: 'songs', 34 | label: '曲目', 35 | icon: , 36 | onClick: () => navigate('/songs'), 37 | }, 38 | { 39 | key: 'settings', 40 | label: '设置', 41 | icon: , 42 | onClick: () => navigate('/settings'), 43 | }, 44 | ]; 45 | 46 | useEffect(() => { 47 | window.electron.ipcRenderer.store.set('assets', assets); 48 | if (!assets.path) { 49 | navigate('/'); 50 | } 51 | }, [assets, navigate]); 52 | 53 | // 注册ipc listener 54 | useEffect(() => { 55 | window.aam.ipcRenderer.onCloseFolder(() => { 56 | dispatch(setPath('')); 57 | navigate('/'); 58 | }); 59 | 60 | window.aam.ipcRenderer.onPushSongs((_, args) => { 61 | dispatch(setPath(args.path)); 62 | dispatch(setSongs(args.songs)); 63 | navigate('/songs'); 64 | }); 65 | }, [dispatch, navigate]); 66 | 67 | return ( 68 | 69 | {contextHolder} 70 |