├── .nvmrc ├── .github ├── FUNDING.yml ├── config.yml ├── ISSUE_TEMPLATE │ ├── 3-Feature_request.md │ ├── 2-Question.md │ └── 1-Bug_report.md ├── stale.yml └── workflows │ ├── test.yml │ ├── publish.yml │ ├── build-docs.yml │ └── codeql-analysis.yml ├── .erb ├── mocks │ └── fileMock.js ├── scripts │ ├── setupTests.ts │ ├── .eslintrc │ ├── clean.js │ ├── check-node-env.js │ ├── check-port-in-use.js │ ├── link-modules.ts │ ├── delete-source-maps.js │ ├── electron-rebuild.js │ ├── check-build-exists.ts │ ├── notarize.js │ └── check-native-dep.js ├── img │ └── erb-logo.png └── configs │ ├── .eslintrc │ ├── webpack.config.eslint.ts │ ├── webpack.paths.ts │ ├── webpack.config.base.ts │ ├── webpack.config.main.dev.ts │ ├── webpack.config.renderer.dev.dll.ts │ ├── webpack.config.preload.dev.ts │ ├── webpack.config.main.prod.ts │ └── webpack.config.renderer.prod.ts ├── .npmrc ├── assets ├── icon.icns ├── icon.ico ├── icon.png ├── icons │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ ├── 96x96.png │ └── 1024x1024.png ├── menuTemplate.png ├── entitlements.mac.plist └── assets.d.ts ├── docs ├── public │ └── favicon.png ├── src │ ├── env.d.ts │ ├── assets │ │ ├── legato-screenshot-dark.jpg │ │ └── legato-screenshot-light.jpg │ ├── content │ │ ├── docs │ │ │ ├── changelog.mdx │ │ │ ├── license.mdx │ │ │ ├── contributing.mdx │ │ │ ├── application-behavior.mdx │ │ │ ├── search-filters.mdx │ │ │ ├── getting-started.mdx │ │ │ ├── managing-projects.mdx │ │ │ └── scanning-projects.mdx │ │ └── config.ts │ ├── components │ │ ├── Features.tsx │ │ ├── Footer.astro │ │ └── Hero.tsx │ ├── index.css │ ├── layouts │ │ └── Layout.astro │ └── pages │ │ └── index.astro ├── .vscode │ ├── extensions.json │ └── launch.json ├── tsconfig.json ├── .gitignore ├── package.json ├── astro.config.mjs └── README.md ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── src ├── types │ ├── Track.ts │ └── Progress.ts ├── db │ ├── entity │ │ ├── index.ts │ │ ├── Tag.ts │ │ ├── Setting.ts │ │ └── Project.ts │ ├── migrations │ │ ├── 1744048077069-AutoStart.ts │ │ ├── 1743608773462-BackgroundScan.ts │ │ ├── 1743684245136-MinimizeToTray.ts │ │ ├── 1743785672638-StartMinimized.ts │ │ ├── 1729347610722-TableState.ts │ │ ├── 1709996613942-SeedSettings.ts │ │ └── 1709996613941-CreateDatabase.ts │ └── data-source.ts ├── renderer │ ├── preload.d.ts │ ├── index.ejs │ ├── index.tsx │ ├── components │ │ ├── ui │ │ │ ├── skeleton.tsx │ │ │ ├── spinner.tsx │ │ │ ├── textarea.tsx │ │ │ ├── label.tsx │ │ │ ├── input.tsx │ │ │ ├── sonner.tsx │ │ │ ├── slider.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── switch.tsx │ │ │ ├── badge.tsx │ │ │ ├── button.tsx │ │ │ ├── accordion.tsx │ │ │ ├── card.tsx │ │ │ ├── dropzone.tsx │ │ │ ├── table.tsx │ │ │ ├── dialog.tsx │ │ │ ├── audio-player.tsx │ │ │ ├── sheet.tsx │ │ │ ├── command.tsx │ │ │ └── select.tsx │ │ ├── EditableTagCell.tsx │ │ ├── EditableCell.tsx │ │ ├── EditableSelectCell.tsx │ │ ├── DebounceInput.tsx │ │ ├── datatable │ │ │ ├── data-table-filter-command.tsx │ │ │ ├── data-table-column-toggle.tsx │ │ │ ├── data-table-column-header.tsx │ │ │ └── data-table-pagination.tsx │ │ ├── TableSkeleton.tsx │ │ └── TagInput.tsx │ ├── hooks │ │ ├── useLogger.ts │ │ ├── handlers.ts │ │ └── useOutsideClick.ts │ ├── utils.ts │ ├── store │ │ ├── store.ts │ │ └── Slices │ │ │ ├── appStateSlice.ts │ │ │ ├── settingsSlice.ts │ │ │ └── projectsSlice.ts │ └── App.css ├── __tests__ │ └── App.test.tsx └── main │ ├── util.ts │ ├── preload.ts │ ├── logger.ts │ └── lib │ └── abletonParser.ts ├── postcss.config.js ├── .editorconfig ├── .gitattributes ├── components.json ├── .env ├── .gitignore ├── release └── app │ └── package.json ├── .eslintignore ├── tsconfig.json ├── .eslintrc.js ├── tailwind.config.js ├── README.md ├── CHANGELOG.md └── CODE_OF_CONDUCT.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: pruizlezcano 2 | -------------------------------------------------------------------------------- /.erb/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /.erb/scripts/setupTests.ts: -------------------------------------------------------------------------------- 1 | global.TextEncoder = require('util').TextEncoder; 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | node-linker=hoisted 2 | public-hoist-pattern=* 3 | shamefully-hoist=true 4 | -------------------------------------------------------------------------------- /assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/assets/icon.icns -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/assets/icon.ico -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/assets/icon.png -------------------------------------------------------------------------------- /.erb/img/erb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/.erb/img/erb-logo.png -------------------------------------------------------------------------------- /assets/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/assets/icons/128x128.png -------------------------------------------------------------------------------- /assets/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/assets/icons/16x16.png -------------------------------------------------------------------------------- /assets/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/assets/icons/24x24.png -------------------------------------------------------------------------------- /assets/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/assets/icons/256x256.png -------------------------------------------------------------------------------- /assets/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/assets/icons/32x32.png -------------------------------------------------------------------------------- /assets/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/assets/icons/48x48.png -------------------------------------------------------------------------------- /assets/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/assets/icons/512x512.png -------------------------------------------------------------------------------- /assets/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/assets/icons/64x64.png -------------------------------------------------------------------------------- /assets/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/assets/icons/96x96.png -------------------------------------------------------------------------------- /assets/menuTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/assets/menuTemplate.png -------------------------------------------------------------------------------- /docs/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/docs/public/favicon.png -------------------------------------------------------------------------------- /assets/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/assets/icons/1024x1024.png -------------------------------------------------------------------------------- /docs/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig"] 3 | } 4 | -------------------------------------------------------------------------------- /docs/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /docs/src/assets/legato-screenshot-dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/docs/src/assets/legato-screenshot-dark.jpg -------------------------------------------------------------------------------- /docs/src/assets/legato-screenshot-light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruizlezcano/legato/HEAD/docs/src/assets/legato-screenshot-light.jpg -------------------------------------------------------------------------------- /src/types/Track.ts: -------------------------------------------------------------------------------- 1 | export interface Track { 2 | name: string; 3 | pluginNames: string[]; 4 | type: 'midi' | 'audio' | 'return'; 5 | } 6 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | requiredHeaders: 2 | - Prerequisites 3 | - Expected Behavior 4 | - Current Behavior 5 | - Possible Solution 6 | - Your Environment 7 | -------------------------------------------------------------------------------- /.erb/configs/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "jsxImportSource": "react" 6 | } 7 | } -------------------------------------------------------------------------------- /src/db/entity/index.ts: -------------------------------------------------------------------------------- 1 | import Project from './Project'; 2 | import Setting from './Setting'; 3 | import Tag from './Tag'; 4 | 5 | export { Project, Setting, Tag }; 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /docs/src/content/docs/changelog.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Changelog 3 | tableOfContents: false 4 | --- 5 | import {Content as Changelog} from '../../../../CHANGELOG.md' 6 | 7 | 8 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | const autoprefixer = require('autoprefixer'); 3 | 4 | module.exports = { 5 | plugins: [tailwindcss, autoprefixer], 6 | }; 7 | -------------------------------------------------------------------------------- /docs/src/content/docs/license.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: License 3 | tableOfContents: false 4 | --- 5 | 6 | import License from '../../../../LICENSE?raw' 7 | 8 |
 9 | {License}
10 | 
11 | 12 | 13 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /docs/src/content/docs/contributing.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Contributing 3 | tableOfContents: false 4 | --- 5 | 6 | import {Content as Contributing} from '../../../../CONTRIBUTING.md' 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.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/preload.d.ts: -------------------------------------------------------------------------------- 1 | import { ElectronHandler } from '../main/preload'; 2 | 3 | declare global { 4 | // eslint-disable-next-line no-unused-vars 5 | interface Window { 6 | electron: ElectronHandler; 7 | } 8 | } 9 | 10 | export {}; 11 | -------------------------------------------------------------------------------- /src/db/entity/Tag.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; 2 | 3 | @Entity({ name: 'tag' }) 4 | export default class Tag { 5 | @PrimaryGeneratedColumn() 6 | id!: number; 7 | 8 | @Column() 9 | name!: string; 10 | } 11 | -------------------------------------------------------------------------------- /docs/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/content/config.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection } from 'astro:content'; 2 | import { docsSchema } from '@astrojs/starlight/schema'; 3 | 4 | // eslint-disable-next-line import/prefer-default-export 5 | export const collections = { 6 | docs: defineCollection({ schema: docsSchema() }), 7 | }; 8 | -------------------------------------------------------------------------------- /src/db/entity/Setting.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; 2 | 3 | @Entity({ name: 'setting' }) 4 | export default class Setting { 5 | @PrimaryGeneratedColumn() 6 | id!: number; 7 | 8 | @Column() 9 | key!: string; 10 | 11 | @Column({ nullable: true }) 12 | value?: string; 13 | } 14 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /src/renderer/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | Legato 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /.erb/scripts/clean.js: -------------------------------------------------------------------------------- 1 | import rimraf from 'rimraf'; 2 | import fs from 'fs'; 3 | import webpackPaths from '../configs/webpack.paths'; 4 | 5 | const foldersToRemove = [ 6 | webpackPaths.distPath, 7 | webpackPaths.buildPath, 8 | webpackPaths.dllPath, 9 | ]; 10 | 11 | foldersToRemove.forEach((folder) => { 12 | if (fs.existsSync(folder)) rimraf.sync(folder); 13 | }); 14 | -------------------------------------------------------------------------------- /src/renderer/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | import { Provider } from 'react-redux'; 3 | import App from './App'; 4 | import { store } from './store/store'; 5 | 6 | const container = document.getElementById('root') as HTMLElement; 7 | const root = createRoot(container); 8 | root.render( 9 | 10 | 11 | , 12 | ); 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3-Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: You want something added to the app. 🎉 4 | labels: 'enhancement' 5 | --- 6 | 7 | 16 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/renderer/App.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/utils" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-Question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question.❓ 4 | labels: 'question' 5 | --- 6 | 7 | ## Summary 8 | 9 | 10 | 11 | 20 | -------------------------------------------------------------------------------- /src/renderer/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import * as React from 'react'; 3 | import { cn } from '@/utils'; 4 | 5 | function Skeleton({ 6 | className, 7 | ...props 8 | }: React.HTMLAttributes) { 9 | return ( 10 |
14 | ); 15 | } 16 | 17 | export { Skeleton }; 18 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # Environment variables declared in this file are automatically made available to Prisma. 2 | # See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema 3 | 4 | # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. 5 | # See the documentation for all the connection string options: https://pris.ly/d/connection-strings 6 | 7 | DATABASE_URL="file:./dev.db" -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 pnpm start`, 11 | ), 12 | ); 13 | } else { 14 | process.exit(0); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /.erb/scripts/link-modules.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const { srcNodeModulesPath, appNodeModulesPath, erbNodeModulesPath } = 5 | webpackPaths; 6 | 7 | if (fs.existsSync(appNodeModulesPath)) { 8 | if (!fs.existsSync(srcNodeModulesPath)) { 9 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction'); 10 | } 11 | if (!fs.existsSync(erbNodeModulesPath)) { 12 | fs.symlinkSync(appNodeModulesPath, erbNodeModulesPath, 'junction'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.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 | 31 | # Dev 32 | src/db/dev.db 33 | -------------------------------------------------------------------------------- /src/db/migrations/1744048077069-AutoStart.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AutoStart1744048077069 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.query( 6 | `INSERT INTO "setting" ("key", "value") VALUES ('autoStart', 'false')`, 7 | ); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`DELETE FROM "setting" WHERE "key" = 'autoStart'`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/hooks/useLogger.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable class-methods-use-this */ 2 | class Logger { 3 | public info(message: string): void { 4 | window.electron.ipcRenderer.sendMessage('log-info', message); 5 | } 6 | 7 | public warn(message: string): void { 8 | window.electron.ipcRenderer.sendMessage('log-warn', message); 9 | } 10 | 11 | public error(message: string): void { 12 | window.electron.ipcRenderer.sendMessage('log-error', message); 13 | } 14 | } 15 | 16 | const logger = new Logger(); 17 | 18 | export default logger; 19 | -------------------------------------------------------------------------------- /.erb/scripts/delete-source-maps.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import rimraf from 'rimraf'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | export default function deleteSourceMaps() { 7 | if (fs.existsSync(webpackPaths.distMainPath)) 8 | rimraf.sync(path.join(webpackPaths.distMainPath, '*.js.map'), { 9 | glob: true, 10 | }); 11 | if (fs.existsSync(webpackPaths.distRendererPath)) 12 | rimraf.sync(path.join(webpackPaths.distRendererPath, '*.js.map'), { 13 | glob: true, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /src/renderer/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | // eslint-disable-next-line import/prefer-default-export 5 | export function cn(...inputs: ClassValue[]) { 6 | return twMerge(clsx(inputs)); 7 | } 8 | 9 | export function isMacOs() { 10 | if (typeof window === 'undefined') return false; 11 | 12 | return window.navigator.userAgent.includes('Mac'); 13 | } 14 | 15 | export function capitalize(string: string) { 16 | return string.charAt(0).toUpperCase() + string.slice(1); 17 | } 18 | -------------------------------------------------------------------------------- /src/db/migrations/1743608773462-BackgroundScan.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class BackgroundScan1743608773462 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.query( 6 | `INSERT INTO "setting" ("key", "value") VALUES ('scanSchedule', '0 * * * *')`, 7 | ); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query( 12 | `DELETE FROM "setting" WHERE "key" = 'scanSchedule'`, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/db/migrations/1743684245136-MinimizeToTray.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class MinimizeToTray1743684245136 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.query( 6 | `INSERT INTO "setting" ("key", "value") VALUES ('minimizeToTray', 'false')`, 7 | ); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query( 12 | `DELETE FROM "setting" WHERE "key" = 'minimizeToTray'`, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/db/migrations/1743785672638-StartMinimized.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class StartMinimized1743785672638 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.query( 6 | `INSERT INTO "setting" ("key", "value") VALUES ('startMinimized', 'false')`, 7 | ); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query( 12 | `DELETE FROM "setting" WHERE "key" = 'startMinimized'`, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/renderer/hooks/handlers.ts: -------------------------------------------------------------------------------- 1 | import { Project } from '../../db/entity'; 2 | 3 | export const handleOpenInAbleton = (projectId: number) => 4 | window.electron.ipcRenderer.sendMessage('open-project', projectId); 5 | 6 | export const handleOpenInFinder = (projectId: number) => 7 | window.electron.ipcRenderer.sendMessage('open-project-folder', projectId); 8 | 9 | export const handleProjectUpdate = (project: Project) => { 10 | window.electron.ipcRenderer.sendMessage('update-project', project); 11 | }; 12 | 13 | export const handleList = () => 14 | window.electron.ipcRenderer.sendMessage('list-projects'); 15 | -------------------------------------------------------------------------------- /release/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "legato", 3 | "version": "0.4.0", 4 | "description": "A manager for your music projects", 5 | "license": "GNU GPLv3", 6 | "author": { 7 | "name": "Pablo Ruiz", 8 | "url": "https://github.com/pruizlezcano" 9 | }, 10 | "main": "./dist/main/main.js", 11 | "scripts": { 12 | "rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js", 13 | "postinstall": "pnpm run rebuild && pnpm run link-modules", 14 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts" 15 | }, 16 | "dependencies": { 17 | "glob": "^10.4.5", 18 | "sqlite3": "5.1.6" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/renderer/hooks/useOutsideClick.ts: -------------------------------------------------------------------------------- 1 | const { useEffect, useRef } = require('react'); 2 | 3 | const useOutsideClick = (callback: () => void) => { 4 | const ref = useRef(null); 5 | 6 | useEffect(() => { 7 | const handleClickOutside = (event: MouseEvent) => { 8 | if (ref.current && !ref.current.contains(event.target as Node)) { 9 | callback(); 10 | } 11 | }; 12 | 13 | document.addEventListener('mousedown', handleClickOutside); 14 | 15 | return () => { 16 | document.removeEventListener('mousedown', handleClickOutside); 17 | }; 18 | }, [callback]); 19 | 20 | return ref; 21 | }; 22 | 23 | export default useOutsideClick; 24 | -------------------------------------------------------------------------------- /.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 | 35 | # migration files for typeorm 36 | src/db/migrations/*.ts 37 | -------------------------------------------------------------------------------- /.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/store/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import logger from 'redux-logger'; 3 | import projectReducer from './Slices/projectsSlice'; 4 | import settingsReducer from './Slices/settingsSlice'; 5 | import appStateReducer from './Slices/appStateSlice'; 6 | 7 | export const store = configureStore({ 8 | middleware: (getDefaultMiddleware: any) => 9 | getDefaultMiddleware({ 10 | serializableCheck: false, 11 | }).concat(logger), 12 | 13 | reducer: { 14 | projects: projectReducer, 15 | settings: settingsReducer, 16 | appState: appStateReducer, 17 | }, 18 | }); 19 | 20 | export type RootState = ReturnType; 21 | export type AppDispatch = typeof store.dispatch; 22 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - discussion 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /src/renderer/components/ui/spinner.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/utils'; 2 | import React from 'react'; 3 | 4 | export interface ISVGProps extends React.SVGProps { 5 | size?: number; 6 | className?: string; 7 | } 8 | 9 | export function LoadingSpinner({ size = 24, className, ...props }: ISVGProps) { 10 | return ( 11 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /assets/assets.d.ts: -------------------------------------------------------------------------------- 1 | type Styles = Record; 2 | 3 | declare module '*.svg' { 4 | import React = require('react'); 5 | 6 | export const ReactComponent: React.FC>; 7 | 8 | const content: string; 9 | export default content; 10 | } 11 | 12 | declare module '*.png' { 13 | const content: string; 14 | export default content; 15 | } 16 | 17 | declare module '*.jpg' { 18 | const content: string; 19 | export default content; 20 | } 21 | 22 | declare module '*.scss' { 23 | const content: Styles; 24 | export default content; 25 | } 26 | 27 | declare module '*.sass' { 28 | const content: Styles; 29 | export default content; 30 | } 31 | 32 | declare module '*.css' { 33 | const content: Styles; 34 | export default content; 35 | } 36 | -------------------------------------------------------------------------------- /.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": "pnpm", 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 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "es2022", 5 | "experimentalDecorators": true, 6 | "emitDecoratorMetadata": true, 7 | "module": "commonjs", 8 | "lib": ["dom", "es2022"], 9 | "jsx": "react-jsx", 10 | "strict": true, 11 | "sourceMap": true, 12 | "moduleResolution": "node", 13 | "esModuleInterop": true, 14 | "allowSyntheticDefaultImports": true, 15 | "resolveJsonModule": true, 16 | "allowJs": true, 17 | "outDir": ".erb/dll", 18 | "baseUrl": ".", 19 | "skipLibCheck": true, 20 | "paths": { 21 | "@/*": ["./src/renderer/*"], 22 | } 23 | }, 24 | "exclude": ["test", "release/build", "release/app/dist", ".erb/dll", "docs"], 25 | "experimentalDecorators": true, 26 | "emitDecoratorMetadata": true, 27 | } 28 | -------------------------------------------------------------------------------- /.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 "pnpm 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 "pnpm run build:renderer"', 22 | ), 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/renderer/components/EditableTagCell.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import TagInput from './TagInput'; 3 | 4 | function EditableTagCell({ 5 | value: initialValue, 6 | row: { index }, 7 | column: { id }, 8 | table, 9 | ...props 10 | }: { 11 | value: string[]; 12 | row: { index: number }; 13 | column: { id: string }; 14 | table: any; 15 | type?: string; 16 | placeholder?: string; 17 | }) { 18 | const [value, setValue] = useState(initialValue); 19 | 20 | const onChange = (tags: string[]) => { 21 | setValue(tags); 22 | table.options.meta?.updateData(index, id, tags); 23 | }; 24 | 25 | return ( 26 | 31 | ); 32 | } 33 | 34 | export default EditableTagCell; 35 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | matrix: 17 | os: [macos-latest, windows-latest, ubuntu-latest] 18 | 19 | steps: 20 | - name: Check out Git repository 21 | uses: actions/checkout@v3 22 | 23 | - name: Install PNPM 24 | uses: pnpm/action-setup@v2 25 | with: 26 | version: 8 27 | 28 | - name: pnpm install 29 | run: | 30 | pnpm install 31 | 32 | - name: pnpm test 33 | env: 34 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | run: | 36 | pnpm run package 37 | pnpm run lint 38 | pnpm exec tsc 39 | pnpm test 40 | -------------------------------------------------------------------------------- /src/renderer/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from '@/utils'; 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |