Compare commits

..

3 Commits

Author SHA1 Message Date
jeffvli ade1b8f69c Cleanup 2022-10-06 21:28:57 -07:00
Jeffrey Li 06914b3af4 Add preliminary prisma support 2022-10-06 21:17:40 -07:00
Jeffrey Li 95c52d8a11 Initial commit 2022-10-06 15:52:01 -07:00
781 changed files with 20014 additions and 91431 deletions
-3
View File
@@ -1,3 +0,0 @@
node_modules
Dockerfile
docker-compose.*
+6 -3
View File
@@ -1,9 +1,12 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
indent_size = 2
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
+82
View File
@@ -0,0 +1,82 @@
{
"env": {
"browser": true,
"es2021": true
},
"ignorePatterns": [
"node_modules/*",
"dist/*",
"electron/preload/*",
"vite.config.ts",
"post-install.js"
],
"extends": [
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:typescript-sort-keys/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint",
"import",
"sort-keys-fix",
"promise"
],
"rules": {
"react-hooks/exhaustive-deps": [
"warn",
{ "enableDangerousAutofixThisMayCauseInfiniteLoops": true }
],
"react/jsx-sort-props": [
"error",
{
"callbacksLast": true,
"ignoreCase": false,
"noSortAlphabetically": false,
"reservedFirst": true,
"shorthandFirst": true,
"shorthandLast": false
}
],
"import/order": [
"error",
{
"groups": ["builtin", "external", "internal", ["parent", "sibling"]],
"pathGroups": [
{
"pattern": "react",
"group": "external",
"position": "before"
}
],
"pathGroupsExcludedImportTypes": ["react"],
"newlines-between": "never",
"alphabetize": {
"order": "asc",
"caseInsensitive": true
}
}
],
"sort-keys-fix/sort-keys-fix": "warn",
"@typescript-eslint/no-explicit-any": "off",
"consistent-return": "off",
"object-curly-newline": "off",
"indent": "off",
"no-tabs": "off",
"react/jsx-indent": "off",
"react/jsx-indent-props": "off",
"react/react-in-jsx-scope": "off"
}
}
-12
View File
@@ -1,12 +0,0 @@
* text eol=lf
*.exe binary
*.png binary
*.jpg binary
*.jpeg binary
*.ico binary
*.icns binary
*.eot binary
*.otf binary
*.ttf binary
*.woff binary
*.woff2 binary
-3
View File
@@ -1,3 +0,0 @@
# These are supported funding model platforms
ko_fi: jeffvli
-63
View File
@@ -1,63 +0,0 @@
name: Bug report
description: You're having technical issues. 🐞
labels: ['bug']
body:
- type: textarea
attributes:
label: Expected Behavior
description: What should have happened?
validations:
required: true
- type: textarea
attributes:
label: Current Behavior
description: What went wrong? Add screenshots to help explain your problem. (Open the browser dev tools in the menu or using CTRL + SHIFT + I)
validations:
required: true
- type: textarea
attributes:
label: Steps to Reproduce
placeholder: |
<!-- Add relevant code and/or a live example -->
<!-- Add stack traces -->
1.
2.
3.
4.
validations:
required: true
- type: textarea
attributes:
label: Possible Solution
description: Suggest a reason for the bug or how to fix it.
validations:
required: false
- type: textarea
attributes:
label: Context
description: How has this issue affected you? What are you trying to accomplish?
validations:
required: false
- type: input
attributes:
label: Application version
placeholder: (e.g. v0.1.0)
validations:
required: true
- type: input
attributes:
label: Operating System and version
placeholder: (e.g. Windows 11 desktop, Webapp in Firefox)
validations:
required: true
- type: input
attributes:
label: Server and Version
placeholder: (e.g. Navidrome v0.48.0)
validations:
required: true
- type: input
attributes:
label: Node Version (if developing locally)
validations:
required: false
-5
View File
@@ -1,5 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: Question
url: https://github.com/jeffvli/feishin/discussions
about: Please ask and answer questions here.
@@ -1,20 +0,0 @@
name: Feature request - NOT ACCEPTING NEW FEATURE REQUESTS
description: Feature requests are currently closed. The application is actively being rewritten https://github.com/audioling/audioling.
labels: ['enhancement']
body:
- type: textarea
attributes:
label: What do you want to be added?
validations:
required: true
- type: textarea
attributes:
label: Additional context
validations:
required: false
- type: checkboxes
attributes:
label: Is this a server-specific feature? (e.g. Jellyfin only)
options:
- label: 'Yes'
required: false
-6
View File
@@ -1,6 +0,0 @@
requiredHeaders:
- Prerequisites
- Expected Behavior
- Current Behavior
- Possible Solution
- Your Environment
-17
View File
@@ -1,17 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- discussion
- security
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
-55
View File
@@ -1,55 +0,0 @@
# Referenced from: https://docs.github.com/en/actions/publishing-packages/publishing-docker-images#introduction
name: Publish Docker to GHCR
permissions: write-all
on:
workflow_dispatch:
push:
tags:
- 'v*.*.*'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: |
linux/amd64
linux/arm/v7
linux/arm64/v8
-46
View File
@@ -1,46 +0,0 @@
# Referenced from: https://docs.github.com/en/actions/publishing-packages/publishing-docker-images#introduction
name: Publish Docker to GHCR (Manual)
on: workflow_dispatch
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: |
linux/amd64
linux/arm/v7
linux/arm64/v8
-49
View File
@@ -1,49 +0,0 @@
name: Publish Linux (Manual)
on: workflow_dispatch
jobs:
publish:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
steps:
- name: Checkout git repo
uses: actions/checkout@v1
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
version: 9
- name: Install dependencies
run: pnpm install
- name: Build and Publish releases
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run package:linux
pnpm run publish:linux
on_retry_command: pnpm cache delete
- name: Build and Publish releases (arm64)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run package:linux-arm64
pnpm run publish:linux-arm64
on_retry_command: pnpm cache delete
-36
View File
@@ -1,36 +0,0 @@
name: Publish macOS (Manual)
on: workflow_dispatch
jobs:
publish:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest]
steps:
- name: Checkout git repo
uses: actions/checkout@v1
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
version: 9
- name: Install dependencies
run: pnpm install
- name: Build and Publish releases
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run package:mac
pnpm run publish:mac
on_retry_command: pnpm cache delete
-54
View File
@@ -1,54 +0,0 @@
name: Comment on pull request
on:
workflow_run:
workflows: ['Publish (PR)']
types: [completed]
jobs:
pr_comment:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6
with:
# This snippet is public-domain, taken from
# https://github.com/oprypin/nightly.link/blob/master/.github/workflows/pr-comment.yml
script: |
async function upsertComment(owner, repo, issue_number, purpose, body) {
const {data: comments} = await github.rest.issues.listComments(
{owner, repo, issue_number});
const marker = `<!-- bot: ${purpose} -->`;
body = marker + "\n" + body;
const existing = comments.filter((c) => c.body.includes(marker));
if (existing.length > 0) {
const last = existing[existing.length - 1];
core.info(`Updating comment ${last.id}`);
await github.rest.issues.updateComment({
owner, repo,
body,
comment_id: last.id,
});
} else {
core.info(`Creating a comment in issue / PR #${issue_number}`);
await github.rest.issues.createComment({issue_number, body, owner, repo});
}
}
const {owner, repo} = context.repo;
const run_id = ${{github.event.workflow_run.id}};
const pull_requests = ${{ toJSON(github.event.workflow_run.pull_requests) }};
if (!pull_requests.length) {
return core.error("This workflow doesn't match any pull requests!");
}
const artifacts = await github.paginate(
github.rest.actions.listWorkflowRunArtifacts, {owner, repo, run_id});
if (!artifacts.length) {
return core.error(`No artifacts found`);
}
let body = `Download the artifacts for this pull request:\n`;
for (const art of artifacts) {
body += `\n* [${art.name}.zip](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
}
core.info("Review thread message body:", body);
for (const pr of pull_requests) {
await upsertComment(owner, repo, pr.number,
"nightly-link", body);
}
-93
View File
@@ -1,93 +0,0 @@
name: Publish (PR)
on:
pull_request:
branches:
- development
jobs:
publish:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
version: 9
- name: Install dependencies
run: pnpm install
- name: Build for Windows
if: ${{ matrix.os == 'windows-latest' }}
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run package:win:pr
- name: Build for Linux
if: ${{ matrix.os == 'ubuntu-latest' }}
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run package:linux:pr
- name: Build for MacOS
if: ${{ matrix.os == 'macos-latest' }}
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run package:mac:pr
- name: Zip Windows Binaries
if: ${{ matrix.os == 'windows-latest' }}
shell: pwsh
run: |
Compress-Archive -Path "dist/*.exe" -DestinationPath "dist/windows-binaries.zip" -Force
- name: Zip Linux Binaries
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
zip -r dist/linux-binaries.zip dist/*.{AppImage,deb,rpm}
- name: Zip MacOS Binaries
if: ${{ matrix.os == 'macos-latest' }}
run: |
zip -r dist/macos-binaries.zip dist/*.dmg
- name: Upload Windows Binaries
if: ${{ matrix.os == 'windows-latest' }}
uses: actions/upload-artifact@v4
with:
name: windows-binaries
path: dist/windows-binaries.zip
- name: Upload Linux Binaries
if: ${{ matrix.os == 'ubuntu-latest' }}
uses: actions/upload-artifact@v4
with:
name: linux-binaries
path: dist/linux-binaries.zip
- name: Upload MacOS Binaries
if: ${{ matrix.os == 'macos-latest' }}
uses: actions/upload-artifact@v4
with:
name: macos-binaries
path: dist/macos-binaries.zip
-36
View File
@@ -1,36 +0,0 @@
name: Publish Windows (Manual)
on: workflow_dispatch
jobs:
publish:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest]
steps:
- name: Checkout git repo
uses: actions/checkout@v1
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
version: 9
- name: Install dependencies
run: pnpm install
- name: Build and Publish releases
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run package:win
pnpm run publish:win
on_retry_command: pnpm cache delete
-26
View File
@@ -1,26 +0,0 @@
name: Test
on: [push, pull_request]
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v1
- name: Install Node.js and PNPM
uses: pnpm/action-setup@v4
with:
version: 9
- name: Install dependencies
run: pnpm install
- name: Lint Files
run: pnpm run lint
+26 -4
View File
@@ -1,7 +1,29 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
out
dist-ssr
*.local
# Editor directories and files
.idea
.DS_Store
.eslintcache
*.log*
release
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
release/app/dist
release/build
.vscode/.debug.env
./package-lock.json
pnpm-lock.yaml
yarn.lock
-2
View File
@@ -1,2 +0,0 @@
legacy-peer-deps=true
only-built-dependencies=electron,esbuild
-6
View File
@@ -1,6 +0,0 @@
out
dist
pnpm-lock.yaml
LICENSE.md
tsconfig.json
tsconfig.*.json
+12
View File
@@ -0,0 +1,12 @@
{
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always",
"proseWrap": "preserve"
}
-14
View File
@@ -1,14 +0,0 @@
singleQuote: true
semi: true
printWidth: 100
tabWidth: 4
trailingComma: all
useTabs: false
arrowParens: always
proseWrap: never
htmlWhitespaceSensitivity: strict
endOfLine: lf
singleAttributePerLine: true
bracketSpacing: true
plugins:
- prettier-plugin-packagejson
-19
View File
@@ -1,19 +0,0 @@
{
"extends": [
"stylelint-config-standard",
"stylelint-config-css-modules",
"stylelint-config-recess-order"
],
"rules": {
"block-no-empty": null,
"selector-type-case": ["lower", { "ignoreTypes": ["/^\\$\\w+/"] }],
"selector-type-no-unknown": [true, { "ignoreTypes": ["/-styled-mixin/", "/^\\$\\w+/"] }],
"declaration-block-no-shorthand-property-overrides": null,
"declaration-block-no-redundant-longhand-properties": null,
"at-rule-no-unknown": [true, { "ignoreAtRules": ["mixin"] }],
"function-no-unknown": [true, { "ignoreFunctions": ["darken", "alpha", "lighten"] }],
"declaration-property-value-no-unknown": null,
"no-descending-specificity": null,
"no-empty-source": null
}
}
+24
View File
@@ -0,0 +1,24 @@
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
import { createRequire } from 'module'
import { spawn } from 'child_process'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const require = createRequire(import.meta.url)
const pkg = require('../package.json')
// write .debug.env
const envContent = Object.entries(pkg.debug.env).map(([key, val]) => `${key}=${val}`)
fs.writeFileSync(path.join(__dirname, '.debug.env'), envContent.join('\n'))
// bootstrap
spawn(
// TODO: terminate `npm run dev` when Debug exits.
process.platform === 'win32' ? 'npm.cmd' : 'npm',
['run', 'dev'],
{
stdio: 'inherit',
env: Object.assign(process.env, { VSCODE_DEBUG: 'true' }),
},
)
+8 -1
View File
@@ -1,3 +1,10 @@
{
"recommendations": ["dbaeumer.vscode-eslint"]
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"editorconfig.editorconfig",
"mrmlnc.vscode-json5",
"rbbit.typescript-hero",
"syler.sass-indented",
]
}
+33 -25
View File
@@ -1,39 +1,47 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"compounds": [
{
"name": "Debug App",
"preLaunchTask": "start .debug.script.mjs",
"configurations": [
"Debug Main Process",
"Debug Renderer Process"
],
"presentation": {
"hidden": false,
"group": "",
"order": 1
},
"stopAll": true
}
],
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"type": "pwa-node",
"request": "launch",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
},
"runtimeArgs": ["--sourcemap"],
"env": {
"REMOTE_DEBUGGING_PORT": "9222"
}
"runtimeArgs": [
"--no-sandbox",
"--remote-debugging-port=9229",
"."
],
"envFile": "${workspaceFolder}/.vscode/.debug.env",
"console": "integratedTerminal"
},
{
"name": "Debug Renderer Process",
"port": 9222,
"port": 9229,
"request": "attach",
"type": "chrome",
"webRoot": "${workspaceFolder}/src/renderer",
"timeout": 60000,
"presentation": {
"hidden": true
}
}
],
"compounds": [
{
"name": "Debug All",
"configurations": ["Debug Main Process", "Debug Renderer Process"],
"presentation": {
"order": 1
}
}
"type": "pwa-chrome",
"timeout": 60000
},
]
}
-84
View File
@@ -1,84 +0,0 @@
{
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"files.associations": {
".eslintrc": "jsonc",
".prettierrc": "jsonc",
".eslintignore": "ignore"
},
"eslint.validate": ["typescript"],
"eslint.workingDirectories": [
{ "directory": "./", "changeProcessCWD": true },
{ "directory": "./server", "changeProcessCWD": true }
],
"typescript.tsserver.experimental.enableProjectDiagnostics": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit",
"source.organizeImports": "never",
"source.formatDocument": "explicit"
},
"css.validate": true,
"javascript.validate.enable": false,
"javascript.format.enable": false,
"typescript.format.enable": false,
"search.exclude": {
".git": true,
".eslintcache": true,
".erb/dll": true,
"release/{build,app/dist}": true,
"node_modules": true,
"npm-debug.log.*": true,
"test/**/__snapshots__": true,
"package-lock.json": true,
"*.{css,sass,scss}.d.ts": true,
"out/**/*": true,
"dist/**/*": true
},
"i18n-ally.localesPaths": ["src/i18n", "src/i18n/locales"],
"typescript.tsdk": "node_modules\\typescript\\lib",
"typescript.preferences.importModuleSpecifier": "non-relative",
"stylelint.config": null,
"stylelint.validate": ["css", "postcss"],
"typescript.updateImportsOnFileMove.enabled": "always",
"typescript.preferences.autoImportFileExcludePatterns": [
"@mantine/core",
"@mantine/modals",
"@mantine/dates"
],
"[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": true,
"folderTemplates.structures": [
{
"name": "TypeScript Feature Component With CSS Modules",
"omitParentDirectory": true,
"structure": [
{
"fileName": "<FTName | kebabcase>.tsx",
"template": "Functional Component with CSS Modules"
},
{
"fileName": "<FTName | kebabcase>.module.css"
}
]
}
],
"folderTemplates.fileTemplates": {
"Functional Component with CSS Modules": [
"import styles from './<FTName | kebabcase>.module.css';",
"",
"interface <FTName | pascalcase>Props {}",
"",
"export const <FTName | pascalcase> = ({}: <FTName | pascalcase>Props) => {",
" return <div></div>;",
"};"
]
}
}
+34
View File
@@ -0,0 +1,34 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "start .debug.script.mjs",
"type": "shell",
"command": "node .vscode/.debug.script.mjs",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"fileLocation": "relative",
"pattern": {
// TODO: correct "regexp"
"regexp": "^([a-zA-Z]\\:\/?([\\w\\-]\/?)+\\.\\w+):(\\d+):(\\d+): (ERROR|WARNING)\\: (.*)$",
"file": 1,
"line": 3,
"column": 4,
"code": 5,
"message": 6
},
"background": {
"activeOnStart": true,
"endsPattern": "^.*[startup] Electron App.*$",
}
}
}
]
}
// https://code.visualstudio.com/docs/editor/tasks#_operating-system-specific-properties
// https://code.visualstudio.com/docs/editor/tasks#_background-watching-tasks
// https://code.visualstudio.com/docs/editor/tasks#_can-a-background-task-be-used-as-a-prelaunchtask-in-launchjson
-5
View File
@@ -1,5 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-25
View File
@@ -1,25 +0,0 @@
# --- Builder stage
FROM node:23-alpine as builder
WORKDIR /app
# Copy package.json first to cache node_modules
COPY package.json pnpm-lock.yaml .
RUN npm install -g pnpm
RUN pnpm install
# Copy code and build with cached modules
COPY . .
RUN pnpm run build:web
# --- Production stage
FROM nginx:alpine-slim
COPY --chown=nginx:nginx --from=builder /app/out/web /usr/share/nginx/html
COPY ./settings.js.template /etc/nginx/templates/settings.js.template
COPY ng.conf.template /etc/nginx/templates/default.conf.template
ENV PUBLIC_PATH="/"
EXPOSE 9180
CMD ["nginx", "-g", "daemon off;"]
+6 -171
View File
@@ -1,180 +1,15 @@
<img src="assets/icons/icon.png" alt="logo" title="feishin" align="right" height="60px" />
# Sonixd (rewrite)
# Feishin
<p align="center">
<a href="https://github.com/jeffvli/feishin/blob/main/LICENSE">
<img src="https://img.shields.io/github/license/jeffvli/feishin?style=flat-square&color=brightgreen"
alt="License">
</a>
<a href="https://github.com/jeffvli/feishin/releases">
<img src="https://img.shields.io/github/v/release/jeffvli/feishin?style=flat-square&color=blue"
alt="Release">
</a>
<a href="https://github.com/jeffvli/feishin/releases">
<img src="https://img.shields.io/github/downloads/jeffvli/feishin/total?style=flat-square&color=orange"
alt="Downloads">
</a>
</p>
<p align="center">
<a href="https://discord.gg/FVKpcMDy5f">
<img src="https://img.shields.io/discord/922656312888811530?color=black&label=discord&logo=discord&logoColor=white"
alt="Discord">
</a>
<a href="https://matrix.to/#/#sonixd:matrix.org">
<img src="https://img.shields.io/matrix/sonixd:matrix.org?color=black&label=matrix&logo=matrix&logoColor=white"
alt="Matrix">
</a>
</p>
---
Rewrite of [Sonixd](https://github.com/jeffvli/sonixd).
## Features
- [x] MPV player backend
- [x] Web player backend
- [x] Modern UI
- [x] Scrobble playback to your server
- [x] Smart playlist editor (Navidrome)
- [x] Synchronized and unsynchronized lyrics support
- [ ] [Request a feature](https://github.com/jeffvli/feishin/issues) or [view taskboard](https://github.com/users/jeffvli/projects/5/views/1)
## Screenshots
<a href="https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_full_screen_player.png"><img src="https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_full_screen_player.png" width="49.5%"/></a> <a href="https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_album_artist_detail.png"><img src="https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_album_artist_detail.png" width="49.5%"/></a> <a href="https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_album_detail.png"><img src="https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_album_detail.png" width="49.5%"/></a> <a href="https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_smart_playlist.png"><img src="https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_smart_playlist.png" width="49.5%"/></a>
## Getting Started
### Desktop (recommended)
Download the [latest desktop client](https://github.com/jeffvli/feishin/releases). The desktop client is the recommended way to use Feishin. It supports both the MPV and web player backends, as well as includes built-in fetching for lyrics.
#### macOS Notes
If you're using a device running macOS 12 (Monterey) or higher, [check here](https://github.com/jeffvli/feishin/issues/104#issuecomment-1553914730) for instructions on how to remove the app from quarantine.
For media keys to work, you will be prompted to allow Feishin to be a Trusted Accessibility Client. After allowing, you will need to restart Feishin for the privacy settings to take effect.
### Web and Docker
Visit [https://feishin.vercel.app](https://feishin.vercel.app) to use the hosted web version of Feishin. The web client only supports the web player backend.
Feishin is also available as a Docker image. The images are hosted via `ghcr.io` and are available to view [here](https://github.com/jeffvli/feishin/pkgs/container/feishin). You can run the container using the following commands:
```bash
# Run the latest version
docker run --name feishin -p 9180:9180 ghcr.io/jeffvli/feishin:latest
# Build the image locally
docker build -t feishin .
docker run --name feishin -p 9180:9180 feishin
```
#### Docker Compose
To install via Docker Compose use the following snippit. This also works on Portainer.
```yaml
services:
feishin:
container_name: feishin
image: 'ghcr.io/jeffvli/feishin:latest'
environment:
- SERVER_NAME=jellyfin # pre defined server name
- SERVER_LOCK=true # When true AND name/type/url are set, only username/password can be toggled
- SERVER_TYPE=jellyfin # navidrome also works
- SERVER_URL= # http://address:port
- PUID=1000
- PGID=1000
- UMASK=002
- TZ=America/Los_Angeles
ports:
- 9180:9180
restart: unless-stopped
```
### Configuration
1. Upon startup you will be greeted with a prompt to select the path to your MPV binary. If you do not have MPV installed, you can download it [here](https://mpv.io/installation/) or install it using any package manager supported by your OS. After inputting the path, restart the app.
2. After restarting the app, you will be prompted to select a server. Click the `Open menu` button and select `Manage servers`. Click the `Add server` button in the popup and fill out all applicable details. You will need to enter the full URL to your server, including the protocol and port if applicable (e.g. `https://navidrome.my-server.com` or `http://192.168.0.1:4533`).
- **Navidrome** - For the best experience, select "Save password" when creating the server and configure the `SessionTimeout` setting in your Navidrome config to a larger value (e.g. 72h).
- **Linux users** - The default password store uses `libsecret`. `kwallet4/5/6` are also supported, but must be explicitly set in Settings > Window > Passwords/secret score.
3. _Optional_ - If you want to host Feishin on a subpath (not `/`), then pass in the following environment variable: `PUBLIC_PATH=PATH`. For example, to host on `/feishin`, pass in `PUBLIC_PATH=/feishin`.
4. _Optional_ - To hard code the server url, pass the following environment variables: `SERVER_NAME`, `SERVER_TYPE` (one of `jellyfin` or `navidrome`), `SERVER_URL`. To prevent users from changing these settings, pass `SERVER_LOCK=true`. This can only be set if all three of the previous values are set.
## FAQ
### MPV is either not working or is rapidly switching between pause/play states
First thing to do is check that your MPV binary path is correct. Navigate to the settings page and re-set the path and restart the app. If your issue still isn't resolved, try reinstalling MPV. Known working versions include `v0.35.x` and `v0.36.x`. `v0.34.x` is a known broken version.
### What music servers does Feishin support?
Feishin supports any music server that implements a [Navidrome](https://www.navidrome.org/), [Jellyfin](https://jellyfin.org/), or [OpenSubsonic compatible](https://opensubsonic.netlify.app/) API.
- [Navidrome](https://github.com/navidrome/navidrome)
- [Jellyfin](https://github.com/jellyfin/jellyfin)
- [OpenSubsonic](https://opensubsonic.netlify.app/) compatible servers, such as...
- [Airsonic-Advanced](https://github.com/airsonic-advanced/airsonic-advanced)
- [Ampache](https://ampache.org)
- [Astiga](https://asti.ga/)
- [Funkwhale](https://www.funkwhale.audio/)
- [Gonic](https://github.com/sentriz/gonic)
- [LMS](https://github.com/epoupon/lms)
- [Nextcloud Music](https://apps.nextcloud.com/apps/music)
- [Supysonic](https://github.com/spl0k/supysonic)
- More (?)
### I have the issue "The SUID sandbox helper binary was found, but is not configured correctly" on Linux
This happens when you have user (unprivileged) namespaces disabled (`sysctl kernel.unprivileged_userns_clone` returns 0). You can fix this by either enabling unprivileged namespaces, or by making the `chrome-sandbox` Setuid.
```bash
chmod 4755 chrome-sandbox
sudo chown root:root chrome-sandbox
```
Ubunutu 24.04 specifically introduced breaking changes that affect how namespaces work. Please see https://discourse.ubuntu.com/t/ubuntu-24-04-lts-noble-numbat-release-notes/39890#:~:text=security%20improvements%20 for possible fixes.
Repository for the rewrite of [Sonixd](https://github.com/jeffvli/sonixd).
## Development
Built and tested using Node `v23.11.0`.
TBD
This project is built off of [electron-vite](https://github.com/alex8088/electron-vite)
### Developing with Docker Compose
- `pnpm run dev` - Start the development server
- `pnpm run dev:watch` - Start the development server in watch mode (for main / preload HMR)
- `pnpm run start` - Starts the app in production preview mode
- `pnpm run build` - Builds the app for desktop
- `pnpm run build:electron` - Build the electron app (main, preload, and renderer)
- `pnpm run build:remote` - Build the remote app (remote)
- `pnpm run build:web` - Build the standalone web app (renderer)
- `pnpm run package` - Package the project
- `pnpm run package:dev` - Package the project for development
- `pnpm run package:linux` - Package the project for Linux
- `pnpm run package:mac` - Package the project for Mac
- `pnpm run package:win` - Package the project for Windows
- `pnpm run publish:linux` - Publish the project for Linux
- `pnpm run publish:linux-arm64` - Publish the project for Linux ARM64
- `pnpm run publish:mac` - Publish the project for Mac
- `pnpm run publish:win` - Publish the project for Windows
- `pnpm run typecheck` - Type check the project
- `pnpm run typecheck:node` - Type check the project with tsconfig.node.json
- `pnpm run typecheck:web` - Type check the project with tsconfig.web.json
- `pnpm run lint` - Lint the project
- `pnpm run lint:fix` - Lint the project and fix linting errors
- `pnpm run i18next` - Generate i18n files
## Translation
This project uses [Weblate](https://hosted.weblate.org/projects/feishin/) for translations. If you would like to contribute, please visit the link and submit a translation.
TBD
## License
[GNU General Public License v3.0 ©](https://github.com/jeffvli/feishin/blob/dev/LICENSE)
[GNU General Public License v3.0 ©](https://github.com/jeffvli/sonixd-rewrite/blob/dev/LICENSE)
-31
View File
@@ -1,31 +0,0 @@
type Styles = Record<string, string>;
declare module '*.svg' {
const content: string;
export default content;
}
declare module '*.png' {
const content: string;
export default content;
}
declare module '*.jpg' {
const content: string;
export default content;
}
declare module '*.scss' {
const content: Styles;
export default content;
}
declare module '*.sass' {
const content: Styles;
export default content;
}
declare module '*.css' {
const content: Styles;
export default content;
}
-12
View File
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 B

-3
View File
@@ -1,3 +0,0 @@
provider: generic
url: https://example.com/auto-updates
updaterCacheDirName: feishin-updater
-13
View File
@@ -1,13 +0,0 @@
version: '3.5'
services:
feishin:
container_name: feishin
image: ghcr.io/jeffvli/feishin:latest
restart: unless-stopped
ports:
- 9180:9180
environment:
- SERVER_NAME=jellyfin # pre defined server name
- SERVER_LOCK=true # When true AND name/type/url are set, only username/password can be toggled
- SERVER_TYPE=jellyfin # navidrome also works
- SERVER_URL= # http://address:port
+59
View File
@@ -0,0 +1,59 @@
{
"appId": "TEST",
"productName": "TEST",
"copyright": "Copyright © 2022 ${author}",
"directories": {
"app": "release/app",
"output": "release/build",
"buildResources": "electron/resources"
},
"extends": null,
"asar": true,
"asarUnpack": ["**\\*.{node,dll}", "prisma"],
"files": [
"dist",
"node_modules",
"package.json",
"prisma/**/*",
"resources/**/*",
"!**/node_modules/@prisma/engines/introspection-engine*",
"!**/node_modules/@prisma/engines/migration-engine*",
"!**/node_modules/@prisma/engines/prisma-fmt*",
"!**/node_modules/@prisma/engines/query_engine-*",
"!**/node_modules/@prisma/engines/libquery_engine*",
"!**/node_modules/prisma/query_engine*",
"!**/node_modules/prisma/libquery_engine*",
"!**/node_modules/prisma/**/*.mjs"
],
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64"]
}
],
"artifactName": "${productName}-Windows-${version}-Setup.${ext}"
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": false
},
"mac": {
"target": ["dmg"],
"artifactName": "${productName}-Mac-${version}-Installer.${ext}"
},
"linux": {
"icon": "electron/resources/iconset",
"target": ["AppImage", "deb"],
"artifactName": "${productName}-Linux-${version}.${ext}"
},
"extraResources": [
"./assets/**",
"prisma/**/*",
"node_modules/@prisma/engines/migration-engine*",
"node_modules/@prisma/engines/query*",
"node_modules/@prisma/engines/libquery*"
]
}
-75
View File
@@ -1,75 +0,0 @@
appId: org.jeffvli.feishin
productName: Feishin
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
electronVersion: 35.1.5
directories:
buildResources: assets
files:
- 'out/**/*'
- 'package.json'
extraResources:
- assets/**
asarUnpack:
- resources/**
win:
target:
- zip
- nsis
icon: assets/icons/icon.png
nsis:
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
mac:
target:
target: default
arch:
- arm64
- x64
icon: assets/icons/icon.icns
type: distribution
hardenedRuntime: true
entitlements: assets/entitlements.mac.plist
entitlementsInherit: assets/entitlements.mac.plist
gatekeeperAssess: false
notarize: false
dmg:
contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }]
deb:
depends:
- libgssapi_krb5.so.2
- libavahi-common.so.3
- libavahi-client.so.3
- libkrb5.so.3
- libkrb5support.so.0
- libkeyutils.so.1
- libcups.so.2
rpm:
depends:
- libgssapi_krb5.so.2
- libavahi-common.so.3
- libavahi-client.so.3
- libkrb5.so.3
- libkrb5support.so.0
- libkeyutils.so.1
- libcups.so.2
freebsd:
depends:
- libgssapi_krb5.so.2
- libavahi-common.so.3
- libavahi-client.so.3
- libkrb5.so.3
- libkrb5support.so.0
- libkeyutils.so.1
- libcups.so.2
linux:
target:
- AppImage
- tar.xz
category: AudioVideo;Audio;Player
icon: assets/icons/icon.png
npmRebuild: false
publish:
provider: github
owner: jeffvli
repo: feishin
-66
View File
@@ -1,66 +0,0 @@
import react from '@vitejs/plugin-react';
import { externalizeDepsPlugin, UserConfig } from 'electron-vite';
import { resolve } from 'path';
import conditionalImportPlugin from 'vite-plugin-conditional-import';
import dynamicImportPlugin from 'vite-plugin-dynamic-import';
import { ViteEjsPlugin } from 'vite-plugin-ejs';
const currentOSEnv = process.platform;
const config: UserConfig = {
main: {
build: {
rollupOptions: {
external: ['source-map-support'],
},
sourcemap: true,
},
define: {
'import.meta.env.IS_LINUX': JSON.stringify(currentOSEnv === 'linux'),
'import.meta.env.IS_MACOS': JSON.stringify(currentOSEnv === 'darwin'),
'import.meta.env.IS_WIN': JSON.stringify(currentOSEnv === 'win32'),
},
plugins: [
externalizeDepsPlugin(),
dynamicImportPlugin(),
conditionalImportPlugin({
currentEnv: currentOSEnv,
envs: ['win32', 'linux', 'darwin'],
}),
],
resolve: {
alias: {
'/@/main': resolve('src/main'),
'/@/shared': resolve('src/shared'),
},
},
},
preload: {
plugins: [externalizeDepsPlugin()],
resolve: {
alias: {
'/@/preload': resolve('src/preload'),
'/@/shared': resolve('src/shared'),
},
},
},
renderer: {
css: {
modules: {
generateScopedName: 'fs-[name]-[local]',
localsConvention: 'camelCase',
},
},
plugins: [react(), ViteEjsPlugin({ web: false })],
resolve: {
alias: {
'/@/i18n': resolve('src/i18n'),
'/@/remote': resolve('src/remote'),
'/@/renderer': resolve('src/renderer'),
'/@/shared': resolve('src/shared'),
},
},
},
};
export default config;
+11
View File
@@ -0,0 +1,11 @@
/// <reference types="vite-electron-plugin/electron-env" />
declare namespace NodeJS {
interface ProcessEnv {
DIST: string;
DIST_ELECTRON: string;
/** /dist/ or /public/ */
PUBLIC: string;
VSCODE_DEBUG?: 'true';
}
}
+126
View File
@@ -0,0 +1,126 @@
import fs from 'fs';
import path from 'path';
import { PrismaClient } from '@prisma/client';
import { app, ipcMain } from 'electron';
import isDev from 'electron-is-dev';
import './server';
const dbPath = isDev
? path.join(__dirname, '../../../../prisma/dev.db')
: path.join(app.getPath('userData'), 'database.db');
if (!isDev) {
try {
// database file does not exist, need to create
fs.copyFileSync(path.join(process.resourcesPath, 'prisma/dev.db'), dbPath, fs.constants.COPYFILE_EXCL);
console.log(`DB does not exist. Create new DB from ${path.join(process.resourcesPath, 'prisma/dev.db')}`);
} catch (err) {
if (err && 'code' in (err as { code: string }) && (err as { code: string }).code !== 'EEXIST') {
console.error(`DB creation faild. Reason:`, err);
} else {
throw err;
}
}
}
function getPlatformName(): string {
const isDarwin = process.platform === 'darwin';
if (isDarwin && process.arch === 'arm64') {
return `${process.platform}Arm64`;
}
return process.platform;
}
const platformToExecutables: Record<string, any> = {
darwin: {
migrationEngine: 'node_modules/@prisma/engines/migration-engine-darwin',
queryEngine: 'node_modules/@prisma/engines/libquery_engine-darwin.dylib.node',
},
darwinArm64: {
migrationEngine: 'node_modules/@prisma/engines/migration-engine-darwin-arm64',
queryEngine: 'node_modules/@prisma/engines/libquery_engine-darwin-arm64.dylib.node',
},
linux: {
migrationEngine: 'node_modules/@prisma/engines/migration-engine-debian-openssl-1.1.x',
queryEngine: 'node_modules/@prisma/engines/libquery_engine-debian-openssl-1.1.x.so.node',
},
win32: {
migrationEngine: 'node_modules/@prisma/engines/migration-engine-windows.exe',
queryEngine: 'node_modules/@prisma/engines/query_engine-windows.dll.node',
},
};
const extraResourcesPath = app.getAppPath().replace('app.asar', ''); // impacted by extraResources setting in electron-builder.yml
const platformName = getPlatformName();
const mePath = path.join(extraResourcesPath, platformToExecutables[platformName].migrationEngine);
const qePath = path.join(extraResourcesPath, platformToExecutables[platformName].queryEngine);
ipcMain.on('config:get-app-path', (event) => {
event.returnValue = app.getAppPath();
});
ipcMain.on('config:get-platform-name', (event) => {
const isDarwin = process.platform === 'darwin';
event.returnValue =
isDarwin && process.arch === 'arm64' ? `${process.platform}Arm64` : (event.returnValue = process.platform);
});
ipcMain.on('config:get-prisma-db-path', (event) => {
event.returnValue = dbPath;
});
ipcMain.on('config:get-prisma-me-path', (event) => {
event.returnValue = mePath;
});
ipcMain.on('config:get-prisma-qe-path', (event) => {
event.returnValue = qePath;
});
export const prisma = new PrismaClient({
datasources: {
db: {
url: `file:${dbPath}`,
},
},
errorFormat: 'minimal',
// see https://github.com/prisma/prisma/discussions/5200
// __internal: {
// engine: {
// binaryPath: qePath,
// },
// },
});
prisma.server.findMany({
where: {},
});
export const exclude = <T, Key extends keyof T>(resultSet: T, ...keys: Key[]): Omit<T, Key> => {
// eslint-disable-next-line no-restricted-syntax
for (const key of keys) {
delete resultSet[key];
}
return resultSet;
};
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
prisma.$use(async (params, next) => {
const maxRetries = 5;
let retries = 0;
do {
try {
const result = await next(params);
return result;
} catch (err) {
retries += 1;
return sleep(500);
}
} while (retries < maxRetries);
});
@@ -0,0 +1,12 @@
import { ipcMain } from 'electron';
import { prisma } from '..';
export enum ServerApi {
GET_SERVER = 'api:server:get-server',
GET_SERVERS = 'api:server:get-servers',
}
ipcMain.handle(ServerApi.GET_SERVERS, async () => {
const result = await prisma.server.findMany();
return result;
});
+2
View File
@@ -0,0 +1,2 @@
import './mpv-player';
import './api';
@@ -0,0 +1,134 @@
import { ipcMain } from 'electron';
import MpvAPI from 'node-mpv';
import { getWindow } from '../../..';
const mpv = new MpvAPI(
{
audio_only: true,
auto_restart: true,
binary: 'C:/ProgramData/chocolatey/lib/mpv.install/tools/mpv.exe',
time_update: 1,
},
['--gapless-audio=yes', '--prefetch-playlist']
);
mpv.start().catch((error: any) => {
console.log('error', error);
});
mpv.on('status', (status: any) => {
if (status.property === 'playlist-pos') {
if (status.value !== 0) {
getWindow()?.webContents.send('renderer-player-auto-next');
}
}
});
// Automatically updates the play button when the player is playing
mpv.on('started', () => {
getWindow()?.webContents.send('renderer-player-play');
});
// Automatically updates the play button when the player is stopped
mpv.on('stopped', () => {
getWindow()?.webContents.send('renderer-player-stop');
});
// Automatically updates the play button when the player is paused
mpv.on('paused', () => {
getWindow()?.webContents.send('renderer-player-pause');
});
mpv.on('quit', () => {
console.log('mpv quit');
});
// Event output every interval set by time_update, used to update the current time
mpv.on('timeposition', (time: number) => {
getWindow()?.webContents.send('renderer-player-current-time', time);
});
mpv.on('seek', () => {
console.log('mpv seek');
});
// Starts the player
ipcMain.on('player-play', async () => {
await mpv.play();
});
// Pauses the player
ipcMain.on('player-pause', async () => {
await mpv.pause();
});
// Stops the player
ipcMain.on('player-stop', async () => {
await mpv.stop();
});
// Stops the player
ipcMain.on('player-next', async () => {
await mpv.next();
});
// Stops the player
ipcMain.on('player-previous', async () => {
await mpv.prev();
});
// Seeks forward or backward by the given amount of seconds
ipcMain.on('player-seek', async (_event, time: number) => {
await mpv.seek(time);
});
// Seeks to the given time in seconds
ipcMain.on('player-seek-to', async (_event, time: number) => {
await mpv.goToPosition(time);
});
// Sets the queue in position 0 and 1 to the given data. Used when manually starting a song or using the next/prev buttons
ipcMain.on('player-set-queue', async (_event, data: any) => {
if (data.queue.current) {
await mpv.load(data.queue.current.streamUrl, 'replace');
}
if (data.queue.next) {
await mpv.load(data.queue.next.streamUrl, 'append');
}
});
// Replaces the queue in position 1 to the given data
ipcMain.on('player-set-queue-next', async (_event, data: any) => {
const size = await mpv.getPlaylistSize();
if (size > 1) {
await mpv.playlistRemove(1);
}
if (data.queue.next) {
await mpv.load(data.queue.next.streamUrl, 'append');
}
});
// Sets the next song in the queue when reaching the end of the queue
ipcMain.on('player-auto-next', async (_event, data: any) => {
// Always keep the current song as position 0 in the mpv queue
// This allows us to easily set update the next song in the queue without
// disturbing the currently playing song
await mpv.playlistRemove(0);
if (data.queue.next) {
await mpv.load(data.queue.next.streamUrl, 'append');
}
});
// Sets the volume to the given value (0-100)
ipcMain.on('player-volume', async (_event, value: number) => {
mpv.volume(value);
});
// Toggles the mute status
ipcMain.on('player-mute', async () => {
mpv.mute();
});
+3
View File
@@ -0,0 +1,3 @@
import './core';
require(`./${process.platform}`);
+109
View File
@@ -0,0 +1,109 @@
// The built directory structure
//
// ├─┬ dist-electron
// │ ├─┬ main
// │ │ └── index.js > Electron-Main
// │ └─┬ preload
// │ └── index.js > Preload-Scripts
// ├─┬ dist
// │ └── index.html > Electron-Renderer
//
process.env.DIST_ELECTRON = join(__dirname, '..');
process.env.DIST = join(process.env.DIST_ELECTRON, '../dist');
process.env.PUBLIC = app.isPackaged ? process.env.DIST : join(process.env.DIST_ELECTRON, '../public');
import { release } from 'os';
import { join } from 'path';
import { app, BrowserWindow, shell, ipcMain } from 'electron';
import './features';
// Disable GPU Acceleration for Windows 7
if (release().startsWith('6.1')) app.disableHardwareAcceleration();
// Set application name for Windows 10+ notifications
if (process.platform === 'win32') app.setAppUserModelId(app.getName());
if (!app.requestSingleInstanceLock()) {
app.quit();
process.exit(0);
}
let win: BrowserWindow | null = null;
// Here, you can also use other preload
const preload = join(__dirname, '../preload/index.js');
const url = process.env.VITE_DEV_SERVER_URL as string;
const indexHtml = join(process.env.DIST, 'index.html');
async function createWindow() {
win = new BrowserWindow({
icon: join(process.env.PUBLIC as string, 'favicon.svg'),
title: 'Main window',
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
preload,
},
});
if (app.isPackaged) {
win.loadFile(indexHtml);
} else {
win.loadURL(url);
// win.webContents.openDevTools()
}
// Test actively push message to the Electron-Renderer
win.webContents.on('did-finish-load', () => {
win?.webContents.send('main-process-message', new Date().toLocaleString());
});
// Make all links open with the browser, not with the application
win.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith('https:')) shell.openExternal(url);
return { action: 'deny' };
});
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
win = null;
if (process.platform !== 'darwin') app.quit();
});
app.on('second-instance', () => {
if (win) {
// Focus on the main window if the user tried to open another
if (win.isMinimized()) win.restore();
win.focus();
}
});
app.on('activate', () => {
const allWindows = BrowserWindow.getAllWindows();
if (allWindows.length) {
allWindows[0].focus();
} else {
createWindow();
}
});
// new window example arg: new windows url
ipcMain.handle('open-win', (event, arg) => {
const childWindow = new BrowserWindow({
webPreferences: {
preload,
},
});
if (app.isPackaged) {
childWindow.loadFile(indexHtml, { hash: arg });
} else {
childWindow.loadURL(`${url}/#${arg}`);
// childWindow.webContents.openDevTools({ mode: "undocked", activate: true })
}
});
export const getWindow = () => {
return win;
};
+107
View File
@@ -0,0 +1,107 @@
import { contextBridge, ipcRenderer } from 'electron';
function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) {
return new Promise((resolve) => {
if (condition.includes(document.readyState)) {
resolve(true);
} else {
document.addEventListener('readystatechange', () => {
if (condition.includes(document.readyState)) {
resolve(true);
}
});
}
});
}
const safeDOM = {
append(parent: HTMLElement, child: HTMLElement) {
if (!Array.from(parent.children).find((e) => e === child)) {
return parent.appendChild(child);
}
},
remove(parent: HTMLElement, child: HTMLElement) {
if (Array.from(parent.children).find((e) => e === child)) {
return parent.removeChild(child);
}
},
};
/**
* https://tobiasahlin.com/spinkit
* https://connoratherton.com/loaders
* https://projects.lukehaas.me/css-loaders
* https://matejkustec.github.io/SpinThatShit
*/
function useLoading() {
const className = `loaders-css__square-spin`;
const styleContent = `
@keyframes square-spin {
25% { transform: perspective(100px) rotateX(180deg) rotateY(0); }
50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); }
75% { transform: perspective(100px) rotateX(0) rotateY(180deg); }
100% { transform: perspective(100px) rotateX(0) rotateY(0); }
}
.${className} > div {
animation-fill-mode: both;
width: 50px;
height: 50px;
background: #fff;
animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite;
}
.app-loading-wrap {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #282c34;
z-index: 9;
}
`;
const oStyle = document.createElement('style');
const oDiv = document.createElement('div');
oStyle.id = 'app-loading-style';
oStyle.innerHTML = styleContent;
oDiv.className = 'app-loading-wrap';
oDiv.innerHTML = `<div class="${className}"><div></div></div>`;
return {
appendLoading() {
safeDOM.append(document.head, oStyle);
safeDOM.append(document.body, oDiv);
},
removeLoading() {
safeDOM.remove(document.head, oStyle);
safeDOM.remove(document.body, oDiv);
},
};
}
// ----------------------------------------------------------------------
const { appendLoading, removeLoading } = useLoading();
domReady().then(appendLoading);
window.onmessage = (ev) => {
ev.data.payload === 'removeLoading' && removeLoading();
};
setTimeout(removeLoading, 4999);
const serverApi = {
getServer: () => ipcRenderer.invoke('api:server:get-server'), // ServerApi.GET_SERVER
getServers: () => ipcRenderer.invoke('api:server:get-servers'), // ServerApi.GET_SERVERS
};
const api = {
prisma: {
server: serverApi,
},
};
contextBridge.exposeInMainWorld('electron', api);
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

-53
View File
@@ -1,53 +0,0 @@
import eslintConfigPrettier from '@electron-toolkit/eslint-config-prettier';
import tseslint from '@electron-toolkit/eslint-config-ts';
import perfectionist from 'eslint-plugin-perfectionist';
import eslintPluginReact from 'eslint-plugin-react';
import eslintPluginReactHooks from 'eslint-plugin-react-hooks';
import eslintPluginReactRefresh from 'eslint-plugin-react-refresh';
export default tseslint.config(
{ ignores: ['**/node_modules', '**/dist', '**/out'] },
tseslint.configs.recommended,
perfectionist.configs['recommended-natural'],
eslintPluginReact.configs.flat.recommended,
eslintPluginReact.configs.flat['jsx-runtime'],
{
settings: {
react: {
version: 'detect',
},
},
},
{
files: ['**/*.{ts,tsx}'],
plugins: {
'react-hooks': eslintPluginReactHooks,
'react-refresh': eslintPluginReactRefresh,
},
rules: {
...eslintPluginReactHooks.configs.recommended.rules,
...eslintPluginReactRefresh.configs.vite.rules,
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-duplicate-enum-values': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'warn',
curly: ['error', 'all'],
indent: [
'error',
'tab',
{
offsetTernaryExpressions: true,
SwitchCase: 1,
},
],
'no-unused-vars': 'off',
'no-use-before-define': 'off',
quotes: ['error', 'single'],
'react-refresh/only-export-components': 'off',
'react/display-name': 'off',
semi: ['error', 'always'],
'single-attribute-per-line': 'off',
},
},
eslintConfigPrettier,
);
+14
View File
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/assets/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
<title>Vite App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 644 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 465 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 887 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 KiB

-27
View File
@@ -1,27 +0,0 @@
server {
listen 9180;
sendfile on;
default_type application/octet-stream;
gzip on;
gzip_http_version 1.1;
gzip_disable "MSIE [1-6]\.";
gzip_min_length 256;
gzip_vary on;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_comp_level 9;
location ${PUBLIC_PATH} {
alias /usr/share/nginx/html/;
try_files $uri $uri/ /index.html =404;
}
location ${PUBLIC_PATH}settings.js {
alias /etc/nginx/conf.d/settings.js;
}
location ${PUBLIC_PATH}/settings.js {
alias /etc/nginx/conf.d/settings.js;
}
}
+18234
View File
File diff suppressed because it is too large Load Diff
+77 -171
View File
@@ -1,180 +1,86 @@
{
"name": "feishin",
"version": "0.15.0",
"description": "A modern self-hosted music player.",
"keywords": [
"subsonic",
"navidrome",
"jellyfin",
"react",
"electron"
],
"homepage": "https://github.com/jeffvli/feishin",
"bugs": {
"url": "https://github.com/jeffvli/feishin/issues"
},
"name": "sonixd-rewrite",
"productName": "sonixd-rewrite",
"private": true,
"version": "0.0.0",
"description": "",
"author": "jeffvli",
"license": "GPL-3.0",
"author": {
"name": "jeffvli",
"url": "https://github.com/jeffvli/"
},
"main": "./out/main/index.js",
"main": "release/app/dist/main/index.js",
"scripts": {
"build": "pnpm run typecheck && pnpm run build:electron && pnpm run build:remote",
"build:electron": "electron-vite build",
"build:remote": "vite build --config remote.vite.config.ts",
"build:web": "vite build --config web.vite.config.ts",
"dev": "electron-vite dev",
"dev:remote": "vite dev --config remote.vite.config.ts",
"dev:watch": "electron-vite dev --watch",
"i18next": "i18next -c src/i18n/i18next-parser.config.js",
"postinstall": "electron-builder install-app-deps",
"lint": "pnpm run lint-code && pnpm run lint-styles",
"lint-code": "eslint --cache .",
"lint-code:fix": "eslint --cache --fix .",
"lint-styles": "stylelint 'src/**/*.{css,scss}'",
"lint-styles:fix": "stylelint 'src/**/*.{css,scss}' --fix",
"lint:fix": "pnpm run lint-code:fix && pnpm run lint-styles:fix",
"package": "pnpm run build && electron-builder",
"package:dev": "pnpm run build && electron-builder --dir",
"package:linux": "pnpm run build && electron-builder --linux",
"package:linux-arm64:pr": "pnpm run build && electron-builder --linux --arm64 --publish never",
"package:linux:pr": "pnpm run build && electron-builder --linux --publish never",
"package:mac": "pnpm run build && electron-builder --mac",
"package:mac:pr": "pnpm run build && electron-builder --mac --publish never",
"package:win": "pnpm run build && electron-builder --win",
"package:win:pr": "pnpm run build && electron-builder --win --publish never",
"publish:linux": "electron-builder --publish always --linux",
"publish:linux-arm64": "electron-builder --publish always --linux --arm64",
"publish:mac": "electron-builder --publish always --mac",
"publish:win": "electron-builder --publish always --win",
"start": "electron-vite preview",
"typecheck": "pnpm run typecheck:node && pnpm run typecheck:web",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false"
"dev": "vite",
"build": "tsc && vite build && electron-builder",
"postinstall": "node post-install.js && electron-builder install-app-deps",
"prisma:init": "npx prisma migrate dev",
"prisma:dev": "npx prisma db push",
"prisma:migrate": ""
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"dependencies": {
"@ag-grid-community/client-side-row-model": "^28.2.1",
"@ag-grid-community/core": "^28.2.1",
"@ag-grid-community/infinite-row-model": "^28.2.1",
"@ag-grid-community/react": "^28.2.1",
"@ag-grid-community/styles": "^28.2.1",
"@atlaskit/pragmatic-drag-and-drop": "1.4.0",
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.0",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
"@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^4.0.0",
"@mantine/colors-generator": "^8.1.1",
"@mantine/core": "^8.1.1",
"@mantine/dates": "^8.1.1",
"@mantine/form": "^8.1.1",
"@mantine/hooks": "^8.1.1",
"@mantine/modals": "^8.1.1",
"@mantine/notifications": "^8.1.1",
"@tanstack/react-query": "^4.32.1",
"@tanstack/react-query-devtools": "^4.32.1",
"@tanstack/react-query-persist-client": "^4.32.1",
"@ts-rest/core": "^3.23.0",
"@xhayper/discord-rpc": "^1.0.24",
"audiomotion-analyzer": "^4.5.0",
"auto-text-size": "^0.2.3",
"axios": "^1.6.0",
"cheerio": "^1.0.0",
"clsx": "^2.0.0",
"cmdk": "^0.2.0",
"dayjs": "^1.11.6",
"dompurify": "^3.1.6",
"electron-debug": "^3.2.0",
"electron-localshortcut": "^3.2.1",
"electron-log": "^5.1.1",
"electron-store": "^8.1.0",
"electron-updater": "^6.3.9",
"fast-average-color": "^9.3.0",
"format-duration": "^2.0.0",
"fuse.js": "^6.6.2",
"i18next": "^21.10.0",
"idb-keyval": "^6.2.1",
"immer": "^9.0.21",
"is-electron": "^2.2.2",
"lodash": "^4.17.21",
"md5": "^2.3.0",
"memoize-one": "^6.0.0",
"motion": "^12.18.1",
"mpris-service": "^2.1.2",
"nanoid": "^3.3.3",
"node-mpv": "github:jeffvli/Node-MPV#32b4d64395289ad710c41d481d2707a7acfc228f",
"overlayscrollbars": "^2.11.1",
"overlayscrollbars-react": "^0.5.6",
"qs": "^6.14.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-error-boundary": "^3.1.4",
"react-i18next": "^11.18.6",
"react-icons": "^5.5.0",
"react-image": "^4.1.0",
"react-loading-skeleton": "^3.5.0",
"react-player": "^2.11.0",
"react-router": "^6.16.0",
"react-router-dom": "^6.16.0",
"react-virtualized-auto-sizer": "^1.0.17",
"react-window": "^1.8.9",
"react-window-infinite-loader": "^1.0.9",
"semver": "^7.5.4",
"swiper": "^9.3.1",
"use-sync-external-store": "^1.5.0",
"ws": "^8.18.2",
"zod": "^3.22.3",
"zustand": "^5.0.5"
"@prisma/client": "^4.4.0",
"node-mpv": "^2.0.0-beta.2"
},
"devDependencies": {
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
"@electron-toolkit/eslint-config-ts": "^3.0.0",
"@electron-toolkit/tsconfig": "^1.0.1",
"@types/electron-localshortcut": "^3.1.0",
"@types/lodash": "^4.17.18",
"@types/md5": "^2.3.5",
"@types/node": "^22.15.32",
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"@types/react-window": "^1.8.5",
"@types/react-window-infinite-loader": "^1.0.6",
"@types/source-map-support": "^0.5.10",
"@types/ws": "^8.18.1",
"@vitejs/plugin-react": "^4.3.4",
"concurrently": "^7.1.0",
"cross-env": "^7.0.3",
"electron": "^35.1.5",
"electron-builder": "^26.0.12",
"electron-devtools-installer": "^3.2.0",
"electron-vite": "^3.1.0",
"eslint": "^9.24.0",
"eslint-plugin-perfectionist": "^4.13.0",
"eslint-plugin-prettier": "^5.4.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"i18next-parser": "^9.0.2",
"postcss-preset-mantine": "^1.17.0",
"prettier": "^3.5.3",
"prettier-plugin-packagejson": "^2.5.14",
"sass-embedded": "^1.89.0",
"stylelint": "^16.14.1",
"stylelint-config-css-modules": "^4.4.0",
"stylelint-config-recess-order": "^7.1.0",
"stylelint-config-standard": "^38.0.0",
"typescript": "^5.8.3",
"vite": "^6.3.5",
"vite-plugin-conditional-import": "^0.1.7",
"vite-plugin-dynamic-import": "^1.6.0",
"vite-plugin-ejs": "^1.7.0"
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"@vitejs/plugin-react": "^2.1.0",
"electron": "^21.1.0",
"electron-builder": "^23.3.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sass": "^1.55.0",
"typescript": "^4.8.4",
"vite": "^3.1.4",
"vite-electron-plugin": "^0.4.4",
"@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4",
"@mantine/carousel": "^5.5.4",
"@mantine/core": "^5.5.4",
"@mantine/dates": "^5.5.4",
"@mantine/form": "^5.5.4",
"@mantine/hooks": "^5.5.4",
"@mantine/modals": "^5.5.4",
"@mantine/notifications": "^5.5.4",
"@mantine/spotlight": "^5.5.4",
"@tanstack/react-query": "^4.10.1",
"@typescript-eslint/eslint-plugin": "^5.39.0",
"@typescript-eslint/parser": "^5.39.0",
"ag-grid-community": "^28.2.0",
"ag-grid-react": "^28.2.0",
"axios": "^1.0.0",
"dayjs": "^1.11.5",
"electron-is-dev": "^2.0.0",
"electron-rebuild": "^3.2.9",
"embla-carousel-react": "^7.0.3",
"eslint": "^8.24.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-typescript": "^3.5.1",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.0.1",
"eslint-plugin-react": "^7.31.8",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-sort-keys-fix": "^1.1.2",
"eslint-plugin-typescript-sort-keys": "^2.1.0",
"immer": "^9.0.15",
"nanoid": "^4.0.0",
"prettier": "^2.7.1",
"prisma": "^4.4.0",
"react-virtualized-auto-sizer": "^1.0.7",
"react-window": "^1.8.7",
"react-window-infinite-loader": "^1.0.8",
"replace-in-file": "^6.3.5",
"ts-node": "^10.9.1",
"vite-plugin-electron": "^0.9.2",
"vite-plugin-electron-renderer": "^0.9.3",
"zustand": "^4.1.1"
},
"pnpm": {
"onlyBuiltDependencies": [
"abstract-socket",
"electron",
"electron-winstaller",
"esbuild"
]
},
"productName": "feishin"
"debug": {
"env": {
"VITE_DEV_SERVER_URL": "http://127.0.0.1:7777"
}
}
}
-9656
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -0,0 +1,14 @@
const path = require('path');
const replace = require('replace-in-file');
// fix long prisma loading times caused by scanning from process.cwd(), which returns "/" when run in electron
// (thus it scans all files on the computer.) See https://github.com/prisma/prisma/issues/8484
// solution: we get the app path from main process via sync IPC
const options = {
files: path.join(__dirname, '../release/app/node_modules/', '@prisma', 'client', 'index.js'),
from: 'findSync(process.cwd()',
to: `findSync(require("electron").ipcRenderer.sendSync('config:get-app-path')`,
};
const results = replace.sync(options);
console.log('build script: prisma fix', results);
-5
View File
@@ -1,5 +0,0 @@
module.exports = {
plugins: {
'postcss-preset-mantine': {},
},
};
+272
View File
@@ -0,0 +1,272 @@
generator client {
provider = "prisma-client-js"
engineType = "library"
binaryTargets = ["native", "windows"]
// output = "../release/app/node_modules/.prisma/client"
}
datasource db {
provider = "sqlite"
url = "file:../release/app/prisma/dev.db"
}
model User {
id Int @id @default(autoincrement())
name String
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
favorites Favorite[]
albumArtistRatings AlbumArtistRating[]
artistRatings ArtistRating[]
albumRatings AlbumRating[]
songRatings SongRating[]
}
model Server {
id Int @id @default(autoincrement())
nickname String @unique
url String @unique
remoteId String @map("remote_id")
authUsername String @map("auth_username")
authCredential String @map("auth_credential")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
serverType ServerType @relation(fields: [serverTypeId], references: [id])
serverTypeId Int
serverFolders ServerFolder[]
songs Song[]
albums Album[]
artists Artist[]
albumArtists AlbumArtist[]
// @@map("server")
}
model ServerType {
id Int @id @default(autoincrement())
name String @unique
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
Server Server[]
// @@map("server_type")
}
model ServerFolder {
id Int @id @default(autoincrement())
name String
remoteId String @map("remote_id")
enabled Boolean @default(true)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
server Server @relation(fields: [serverId], references: [id])
serverId Int
// @@map("server_folder")
}
model Genre {
id Int @id @default(autoincrement())
name String @unique
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
artists Artist[]
albumArtists AlbumArtist[]
albums Album[]
songs Song[]
// @@map("genre")
}
model Favorite {
id Int @id @default(autoincrement())
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
albumArtists AlbumArtist[]
artists Artist[]
albums Album[]
songs Song[]
User User? @relation(fields: [userId], references: [id])
userId Int?
// @@map("favorite")
}
model AlbumArtistRating {
id Int @id @default(autoincrement())
value Float
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id])
userId Int
albumArtist AlbumArtist? @relation(fields: [albumArtistId], references: [id])
albumArtistId Int
// @@map("album_artist_rating")
@@unique(fields: [albumArtistId, userId], name: "uniqueAlbumArtistRating", map: "unique_album_artist_rating")
}
model ArtistRating {
id Int @id @default(autoincrement())
value Float
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id])
userId Int
artist Artist? @relation(fields: [artistId], references: [id])
artistId Int
// @@map("artist_rating")
@@unique(fields: [artistId, userId], name: "uniqueArtistRating", map: "unique_artist_rating")
}
model AlbumRating {
id Int @id @default(autoincrement())
value Float
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id])
userId Int
album Album? @relation(fields: [albumId], references: [id])
albumId Int
// @@map("album_rating")
@@unique(fields: [albumId, userId], name: "uniqueAlbumRating", map: "unique_album_rating")
}
model SongRating {
id Int @id @default(autoincrement())
value Float
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id])
userId Int
song Song? @relation(fields: [songId], references: [id])
songId Int
// @@map("song_rating")
@@unique(fields: [songId, userId], name: "uniqueSongRating", map: "unique_song_rating")
}
model AlbumArtist {
id Int @id @default(autoincrement())
name String
image String?
image_remote String? @map("image_remote")
sortName String @map("sort_name")
biography String?
remoteId String @map("remote_id")
remoteCreatedAt DateTime? @map("remote_created_at")
deleted Boolean @default(false)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
genres Genre[]
albums Album[]
songs Song[]
favorites Favorite[]
ratings AlbumArtistRating[]
server Server @relation(fields: [serverId], references: [id])
serverId Int
// @@map("album_artist")
@@unique(fields: [serverId, remoteId], name: "uniqueAlbumArtistId", map: "unique_album_artist_id")
}
model Artist {
id Int @id @default(autoincrement())
name String
image String?
image_remote String? @map("image_remote")
biography String?
remoteId String @map("remote_id")
remoteCreatedAt DateTime? @map("remote_created_at")
deleted Boolean @default(false)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
genres Genre[]
favorites Favorite[]
ratings ArtistRating[]
server Server @relation(fields: [serverId], references: [id])
serverId Int
// @@map("artist")
@@unique(fields: [serverId, remoteId], name: "uniqueArtistId", map: "unique_artist_id")
}
model Album {
id Int @id @default(autoincrement())
name String
image String?
image_remote String? @map("image_remote")
releaseDate DateTime? @map("release_date")
releaseYear Int? @map("release_year")
remoteId String
remoteCreatedAt DateTime?
deleted Boolean @default(false)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
genres Genre[]
albumArtists AlbumArtist[]
favorites Favorite[]
ratings AlbumRating[]
server Server @relation(fields: [serverId], references: [id])
serverId Int @map("server_id")
// @@map("album")
@@unique(fields: [serverId, remoteId], name: "uniqueAlbumId", map: "unique_album_id")
}
model Song {
id Int @id @default(autoincrement())
name String
image String?
remote_image String? @map("remote_image")
releaseDate DateTime? @map("release_date")
releaseYear Int? @map("release_year")
duration Float?
lyric String?
bitRate Int @map("bit_rate")
container String
size String?
channels Int?
discIndex Int @default(1) @map("disc_index")
trackIndex Int? @map("track_index")
artistName String? @map("artist_name")
remoteId String @map("remote_id")
remoteCreatedAt DateTime? @map("remote_created_at")
deleted Boolean @default(false)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
genres Genre[]
albumArtists AlbumArtist[]
favorites Favorite[]
ratings SongRating[]
server Server @relation(fields: [serverId], references: [id])
serverId Int @map("server_id")
// @@map("song")
@@unique(fields: [serverId, remoteId], name: "uniqueSongId", map: "unique_song_id")
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

+15
View File
@@ -0,0 +1,15 @@
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#41D1FF"/>
<stop offset="1" stop-color="#BD34FE"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEA83"/>
<stop offset="0.0833333" stop-color="#FFDD35"/>
<stop offset="1" stop-color="#FFA800"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+15
View File
@@ -0,0 +1,15 @@
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#41D1FF"/>
<stop offset="1" stop-color="#BD34FE"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEA83"/>
<stop offset="0.0833333" stop-color="#FFDD35"/>
<stop offset="1" stop-color="#FFA800"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+98
View File
@@ -0,0 +1,98 @@
{
"name": "sonixd-rewrite",
"version": "0.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "sonixd-rewrite",
"version": "0.0.0",
"license": "MIT",
"dependencies": {
"@prisma/client": "4.4.0"
},
"devDependencies": {
"prisma": "4.4.0"
}
},
"node_modules/@prisma/client": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.4.0.tgz",
"integrity": "sha512-ciKOP246x1xwr04G9ajHlJ4pkmtu9Q6esVyqVBO0QJihaKQIUvbPjClp17IsRJyxqNpFm4ScbOc/s9DUzKHINQ==",
"hasInstallScript": true,
"dependencies": {
"@prisma/engines-version": "4.4.0-66.f352a33b70356f46311da8b00d83386dd9f145d6"
},
"engines": {
"node": ">=14.17"
},
"peerDependencies": {
"prisma": "*"
},
"peerDependenciesMeta": {
"prisma": {
"optional": true
}
}
},
"node_modules/@prisma/engines": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.4.0.tgz",
"integrity": "sha512-Fpykccxlt9MHrAs/QpPGpI2nOiRxuLA+LiApgA59ibbf24YICZIMWd3SI2YD+q0IAIso0jCGiHhirAIbxK3RyQ==",
"devOptional": true,
"hasInstallScript": true
},
"node_modules/@prisma/engines-version": {
"version": "4.4.0-66.f352a33b70356f46311da8b00d83386dd9f145d6",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.4.0-66.f352a33b70356f46311da8b00d83386dd9f145d6.tgz",
"integrity": "sha512-P5v/PuEIJLYXZUZBvOLPqoyCW+m6StNqHdiR6te++gYVODpPdLakks5HVx3JaZIY+LwR02juJWFlwpc9Eog/ug=="
},
"node_modules/prisma": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.4.0.tgz",
"integrity": "sha512-l/QKLmLcKJQFuc+X02LyICo0NWTUVaNNZ00jKJBqwDyhwMAhboD1FWwYV50rkH4Wls0RviAJSFzkC2ZrfawpfA==",
"devOptional": true,
"hasInstallScript": true,
"dependencies": {
"@prisma/engines": "4.4.0"
},
"bin": {
"prisma": "build/index.js",
"prisma2": "build/index.js"
},
"engines": {
"node": ">=14.17"
}
}
},
"dependencies": {
"@prisma/client": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.4.0.tgz",
"integrity": "sha512-ciKOP246x1xwr04G9ajHlJ4pkmtu9Q6esVyqVBO0QJihaKQIUvbPjClp17IsRJyxqNpFm4ScbOc/s9DUzKHINQ==",
"requires": {
"@prisma/engines-version": "4.4.0-66.f352a33b70356f46311da8b00d83386dd9f145d6"
}
},
"@prisma/engines": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.4.0.tgz",
"integrity": "sha512-Fpykccxlt9MHrAs/QpPGpI2nOiRxuLA+LiApgA59ibbf24YICZIMWd3SI2YD+q0IAIso0jCGiHhirAIbxK3RyQ==",
"devOptional": true
},
"@prisma/engines-version": {
"version": "4.4.0-66.f352a33b70356f46311da8b00d83386dd9f145d6",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.4.0-66.f352a33b70356f46311da8b00d83386dd9f145d6.tgz",
"integrity": "sha512-P5v/PuEIJLYXZUZBvOLPqoyCW+m6StNqHdiR6te++gYVODpPdLakks5HVx3JaZIY+LwR02juJWFlwpc9Eog/ug=="
},
"prisma": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.4.0.tgz",
"integrity": "sha512-l/QKLmLcKJQFuc+X02LyICo0NWTUVaNNZ00jKJBqwDyhwMAhboD1FWwYV50rkH4Wls0RviAJSFzkC2ZrfawpfA==",
"devOptional": true,
"requires": {
"@prisma/engines": "4.4.0"
}
}
}
}
+19
View File
@@ -0,0 +1,19 @@
{
"name": "sonixd-rewrite",
"version": "0.0.0",
"description": "A foundation for scalable desktop apps",
"main": "./dist/main/main.js",
"author": {
"name": "Electron React Boilerplate Maintainers",
"email": "electronreactboilerplate@gmail.com",
"url": "https://github.com/electron-react-boilerplate"
},
"scripts": {},
"dependencies": {
"@prisma/client": "4.4.0"
},
"devDependencies": {
"prisma": "4.4.0"
},
"license": "MIT"
}
Binary file not shown.
-51
View File
@@ -1,51 +0,0 @@
import react from '@vitejs/plugin-react';
import path from 'path';
import { defineConfig, normalizePath } from 'vite';
import { ViteEjsPlugin } from 'vite-plugin-ejs';
import { version } from './package.json';
export default defineConfig({
build: {
emptyOutDir: true,
outDir: path.resolve(__dirname, './out/remote'),
rollupOptions: {
input: {
favicon: normalizePath(path.resolve(__dirname, './assets/icons/favicon.ico')),
index: normalizePath(path.resolve(__dirname, './src/remote/index.html')),
manifest: normalizePath(path.resolve(__dirname, './src/remote/manifest.json')),
remote: normalizePath(path.resolve(__dirname, './src/remote/index.tsx')),
worker: normalizePath(path.resolve(__dirname, './src/remote/service-worker.ts')),
},
output: {
assetFileNames: '[name].[ext]',
chunkFileNames: '[name].js',
entryFileNames: '[name].js',
},
},
sourcemap: true,
},
css: {
modules: {
generateScopedName: 'fs-[name]-[local]',
localsConvention: 'camelCase',
},
},
plugins: [
react(),
ViteEjsPlugin({
prod: process.env.NODE_ENV === 'production',
root: normalizePath(path.resolve(__dirname, './src/remote')),
version,
}),
],
resolve: {
alias: {
'/@/i18n': path.resolve(__dirname, './src/i18n'),
'/@/remote': path.resolve(__dirname, './src/remote'),
'/@/renderer': path.resolve(__dirname, './src/renderer'),
'/@/shared': path.resolve(__dirname, './src/shared'),
},
},
root: path.resolve(__dirname, './src/remote'),
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

-1
View File
@@ -1 +0,0 @@
"use strict";window.SERVER_URL="${SERVER_URL}";window.SERVER_NAME="${SERVER_NAME}";window.SERVER_TYPE="${SERVER_TYPE}";window.SERVER_LOCK=${SERVER_LOCK};

Some files were not shown because too many files have changed in this diff Show More