Compare commits
22 Commits
1761c8dd40
...
5.3.0
Author | SHA1 | Date | |
---|---|---|---|
63d123f96a | |||
f038412c50 | |||
ff02287df7 | |||
|
f221ded108 | ||
1440f70100 | |||
439333e15a | |||
b9854e0595 | |||
8b56c28d75 | |||
700a14fe88 | |||
3c835077d5 | |||
194de286c8 | |||
a7dee5c2c9 | |||
8036cbb919 | |||
90cf231c76 | |||
42a70534f2 | |||
b07865d98b | |||
cc26bfa080 | |||
822bdf401e | |||
a169c57a52 | |||
60eb1bbef9 | |||
62244f432a | |||
|
0120391418 |
16
.drone.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: install
|
||||||
|
image: node:19.4.0
|
||||||
|
commands:
|
||||||
|
- npm install
|
||||||
|
|
||||||
|
- name: build_with_linux
|
||||||
|
image: node:19.4.0
|
||||||
|
commands:
|
||||||
|
- apt-get update && apt-get upgrade -y
|
||||||
|
- apt-get install -y libarchive-tools rpm
|
||||||
|
- npm run build
|
@@ -10,6 +10,10 @@ insert_final_newline = true
|
|||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
[**.ts]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
[**.json]
|
[**.json]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
4
.github/workflows/build.yml
vendored
@@ -5,6 +5,10 @@ on:
|
|||||||
branches-ignore:
|
branches-ignore:
|
||||||
- master
|
- master
|
||||||
- develop
|
- develop
|
||||||
|
pull_request:
|
||||||
|
branches-ignore:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
jobs:
|
jobs:
|
||||||
build_on_linux:
|
build_on_linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
12
.github/workflows/release.yml
vendored
@@ -5,6 +5,10 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- develop
|
- develop
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_on_linux:
|
build_on_linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -16,7 +20,7 @@ jobs:
|
|||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
- uses: actions/setup-node@master
|
- uses: actions/setup-node@master
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 19
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- uses: actions/upload-artifact@master
|
- uses: actions/upload-artifact@master
|
||||||
@@ -25,12 +29,12 @@ jobs:
|
|||||||
path: dist/
|
path: dist/
|
||||||
|
|
||||||
build_on_mac:
|
build_on_mac:
|
||||||
runs-on: macOS-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
- uses: actions/setup-node@master
|
- uses: actions/setup-node@master
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 19
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- uses: actions/upload-artifact@master
|
- uses: actions/upload-artifact@master
|
||||||
@@ -44,7 +48,7 @@ jobs:
|
|||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
- uses: actions/setup-node@master
|
- uses: actions/setup-node@master
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 19
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- uses: actions/upload-artifact@master
|
- uses: actions/upload-artifact@master
|
||||||
|
1
.gitignore
vendored
@@ -16,3 +16,4 @@ ts-dist/**
|
|||||||
ts-dist
|
ts-dist
|
||||||
themes
|
themes
|
||||||
!src/themes
|
!src/themes
|
||||||
|
.sass-cache
|
||||||
|
@@ -4,12 +4,20 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## 5.3.0
|
||||||
|
|
||||||
|
- SPKChaosPhoenix updated the beautiful Tokyo Night theme:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## 5.2.0
|
## 5.2.0
|
||||||
|
|
||||||
- moved from Javascript to Typescript for all files
|
- moved from Javascript to Typescript for all files
|
||||||
|
|
||||||
- use `npm run watch` to watch for changes & recompile typescript and sass files
|
- use `npm run watch` to watch for changes & recompile typescript and sass files
|
||||||
|
|
||||||
- Added support for theming the application
|
- Added support for theming the application
|
||||||
|
- Added drone build file use `drone exec` or drone.ci to build it
|
||||||
|
|
||||||
## 5.1.0
|
## 5.1.0
|
||||||
|
|
||||||
|
109
README.md
@@ -1,16 +1,22 @@
|
|||||||
# Tidal-hifi<img src = "./build/icon.png" height="40" align="right"/>
|
# Tidal-hifi<img src = "./build/icon.png" height="40" align="right"/>
|
||||||
|
|
||||||

|
 [](https://github.com/Mastermindzh/tidal-hifi/actions) [](https://ci.mastermindzh.tech/Mastermindzh/tidal-hifi) [](https://discord.gg/yhNwf4v4He)
|
||||||
|
|
||||||
The web version of [listen.tidal.com](https://listen.tidal.com) running in electron with hifi support thanks to widevine.
|
The web version of [listen.tidal.com](https://listen.tidal.com) running in electron with hifi support thanks to widevine.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
<!-- toc -->
|
<!-- toc -->
|
||||||
|
|
||||||
- [Installation](#installation)
|
- [Tidal-hifi](#tidal-hifi)
|
||||||
|
- [Table of Contents](#table-of-contents)
|
||||||
|
- [Features](#features)
|
||||||
|
- [Contributions](#contributions)
|
||||||
|
- [Why did I create tidal-hifi?](#why-did-i-create-tidal-hifi)
|
||||||
|
- [Why not extend existing projects?](#why-not-extend-existing-projects)
|
||||||
|
- [Installation](#installation)
|
||||||
- [Dependencies](#dependencies)
|
- [Dependencies](#dependencies)
|
||||||
- [Using releases](#using-releases)
|
- [Using releases](#using-releases)
|
||||||
- [Snap](#snap)
|
- [Snap](#snap)
|
||||||
@@ -18,25 +24,57 @@ The web version of [listen.tidal.com](https://listen.tidal.com) running in elect
|
|||||||
- [Flatpak](#flatpak)
|
- [Flatpak](#flatpak)
|
||||||
- [Nix](#nix)
|
- [Nix](#nix)
|
||||||
- [Using source](#using-source)
|
- [Using source](#using-source)
|
||||||
- [Features](#features)
|
- [Integrations](#integrations)
|
||||||
- [Integrations](#integrations)
|
|
||||||
- [Known bugs](#known-bugs)
|
- [Known bugs](#known-bugs)
|
||||||
- [last.fm doesn't work out of the box. Use rescrobbler as a workaround](#lastfm-doesnt-work-out-of-the-box-use-rescrobbler-as-a-workaround)
|
- [last.fm doesn't work out of the box. Use rescrobbler as a workaround](#lastfm-doesnt-work-out-of-the-box-use-rescrobbler-as-a-workaround)
|
||||||
- [Why](#why)
|
- [Special thanks to](#special-thanks-to)
|
||||||
- [Why not extend existing projects?](#why-not-extend-existing-projects)
|
- [Donations](#donations)
|
||||||
- [Special thanks to](#special-thanks-to)
|
- [Images](#images)
|
||||||
- [Buy me a coffee? Please don't](#buy-me-a-coffee-please-dont)
|
|
||||||
- [Images](#images)
|
|
||||||
- [Settings window](#settings-window)
|
- [Settings window](#settings-window)
|
||||||
- [User setups](#user-setups)
|
- [User setups](#user-setups)
|
||||||
|
|
||||||
<!-- tocstop -->
|
<!-- tocstop -->
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- HiFi playback
|
||||||
|
- Notifications
|
||||||
|
- Custom [theming](./docs/theming.md)
|
||||||
|
- Custom hotkeys ([source](https://defkey.com/tidal-desktop-shortcuts))
|
||||||
|
- API for status and playback
|
||||||
|
- Disabled audio & visual ads, unlocked lyrics, suggested track, track info, and unlimited skips thanks to uBlockOrigin custom filters ([source](https://github.com/uBlockOrigin/uAssets/issues/17495))
|
||||||
|
- Custom [integrations](#integrations)
|
||||||
|
- [Settings feature](./docs/images/settings.png) to disable certain functionality. (`ctrl+=` or `ctrl+0`)
|
||||||
|
- AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847))
|
||||||
|
|
||||||
|
## Contributions
|
||||||
|
|
||||||
|
To contribute you can use the standard GitHub features (issues, prs, etc) or join the discord server to talk with like-minded individuals.
|
||||||
|
|
||||||
|
-  [Join the Discord server](https://discord.gg/yhNwf4v4He)
|
||||||
|
|
||||||
|
## Why did I create tidal-hifi?
|
||||||
|
|
||||||
|
I moved from Spotify over to Tidal and found Linux support to be lacking.
|
||||||
|
|
||||||
|
When I started this project there weren't any Linux apps that offered Tidal's "hifi" options nor any scripts to control it.
|
||||||
|
|
||||||
|
### Why not extend existing projects?
|
||||||
|
|
||||||
|
Whilst there are a handful of projects attempting to run Tidal on Electron they are all unappealing to me because of various reasons:
|
||||||
|
|
||||||
|
- Lack of maintainers/developers. (no hotfixes, no issues being handled etc)
|
||||||
|
- Most are simple web wrappers, not my cup of tea.
|
||||||
|
- Some are DE oriented. I want this to work on WM's too.
|
||||||
|
- None have widevine working at the moment
|
||||||
|
|
||||||
|
Sometimes it's just easier to start over, cover my own needs and then making it available to the public :)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
Note that you **need** a notification library such as [libnotify](https://github.com/GNOME/libnotify) or [dunst](https://github.com/dunst-project/dunst) in order for the software to work properly.
|
Note that you **need** a notification library such as [libnotify](https://github.com/GNOME/libnotify) or [dunst](https://github.com/dunst-project/dunst) for the software to work properly.
|
||||||
|
|
||||||
### Using releases
|
### Using releases
|
||||||
|
|
||||||
@@ -48,15 +86,15 @@ To install with `snap` you need to download the pre-packaged snap-package from t
|
|||||||
|
|
||||||
1. Download
|
1. Download
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
wget <URI> #for instance: https://github.com/Mastermindzh/tidal-hifi/releases/download/1.0/tidal-hifi_1.0.0_amd64.snap
|
wget <URI> #for instance: https://github.com/Mastermindzh/tidal-hifi/releases/download/1.0/tidal-hifi_1.0.0_amd64.snap
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install
|
2. Install
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
snap install --dangerous <path> #for instance: tidal-hifi_1.0.0_amd64.snap
|
snap install --dangerous <path> #for instance: tidal-hifi_1.0.0_amd64.snap
|
||||||
```
|
```
|
||||||
|
|
||||||
### Arch Linux
|
### Arch Linux
|
||||||
|
|
||||||
@@ -91,23 +129,12 @@ To install and work with the code on this project follow these steps:
|
|||||||
- npm install
|
- npm install
|
||||||
- npm start
|
- npm start
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- HiFi playback
|
|
||||||
- Notifications
|
|
||||||
- Custom hotkeys ([source](https://defkey.com/tidal-desktop-shortcuts))
|
|
||||||
- API for status and playback
|
|
||||||
- Disabled audio & visual ads, unlocked lyrics, suggested track, track info, and unlimited skips thanks to uBlockOrigin custom filters ([source](https://github.com/uBlockOrigin/uAssets/issues/17495))
|
|
||||||
- Custom [integrations](#integrations)
|
|
||||||
- [Settings feature](./docs/settings.png) to disable certain functionality. (`ctrl+=` or `ctrl+0`)
|
|
||||||
- AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847))
|
|
||||||
|
|
||||||
## Integrations
|
## Integrations
|
||||||
|
|
||||||
Tidal-hifi comes with several integrations out of the box.
|
Tidal-hifi comes with several integrations out of the box.
|
||||||
You can find these in the settings menu (`ctrl + =` by default) under the "integrations" tab.
|
You can find these in the settings menu (`ctrl + =` by default) under the "integrations" tab.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
It currently includes:
|
It currently includes:
|
||||||
|
|
||||||
@@ -126,38 +153,20 @@ The last.fm login doesn't work, as is evident from the following issue: [Last.fm
|
|||||||
However, in that same issue you can read about a workaround using [rescrobbler](https://github.com/InputUsername/rescrobbled).
|
However, in that same issue you can read about a workaround using [rescrobbler](https://github.com/InputUsername/rescrobbled).
|
||||||
For now that will be the default workaround.
|
For now that will be the default workaround.
|
||||||
|
|
||||||
## Why
|
|
||||||
|
|
||||||
I moved from Spotify over to Tidal and found Linux support to be lacking.
|
|
||||||
|
|
||||||
When I started this project there weren't any Linux apps that offered Tidal's "hifi" options nor any scripts to control it.
|
|
||||||
|
|
||||||
## Why not extend existing projects?
|
|
||||||
|
|
||||||
Whilst there are a handful of projects attempting to run Tidal on Electron they are all unappealing to me because of various reasons:
|
|
||||||
|
|
||||||
- Lack of a maintainers/developers. (no hotfixes, no issues being handled etc)
|
|
||||||
- Most are simple web wrappers, not my cup of tea.
|
|
||||||
- Some are DE oriented. I want this to work on WM's too.
|
|
||||||
- None have widevine working at the moment
|
|
||||||
|
|
||||||
Sometimes it's just easier to start over, cover my own needs and then making it available to the public :)
|
|
||||||
|
|
||||||
## Special thanks to
|
## Special thanks to
|
||||||
|
|
||||||
- [Castlabs](https://castlabs.com/)
|
- [Castlabs](https://castlabs.com/)
|
||||||
For maintaining Electron with Widevine CDM installation, Verified Media Path (VMP), and persistent licenses (StorageID)
|
For maintaining Electron with Widevine CDM installation, Verified Media Path (VMP), and persistent licenses (StorageID)
|
||||||
|
|
||||||
## Buy me a coffee? Please don't
|
## Donations
|
||||||
|
|
||||||
Instead spend some money on a charity I care for: [kwf.nl](https://www.kwf.nl/donatie/donation).
|
You can find my Github sponsorship page at: [https://github.com/sponsors/Mastermindzh](https://github.com/sponsors/Mastermindzh)
|
||||||
Inspired by [haydenjames' issue](https://github.com/Mastermindzh/tidal-hifi/issues/27#issuecomment-704198429)
|
|
||||||
|
|
||||||
## Images
|
## Images
|
||||||
|
|
||||||
### Settings window
|
### Settings window
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### User setups
|
### User setups
|
||||||
|
|
||||||
|
BIN
docs/images/customcss-config.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
docs/images/customcss.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
docs/images/discord.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 800 KiB After Width: | Height: | Size: 800 KiB |
Before Width: | Height: | Size: 726 KiB After Width: | Height: | Size: 726 KiB |
Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 317 KiB |
BIN
docs/images/theming.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
docs/images/tokyo-night.png
Normal file
After Width: | Height: | Size: 2.7 MiB |
38
docs/theming.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Theming tidal-hifi
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
<!-- toc -->
|
||||||
|
|
||||||
|
- [Theming tidal-hifi](#theming-tidal-hifi)
|
||||||
|
- [Table of contents](#table-of-contents)
|
||||||
|
- [Custom CSS](#custom-css)
|
||||||
|
- [config](#config)
|
||||||
|
- [Warning! Themes might break](#warning-themes-might-break)
|
||||||
|
|
||||||
|
<!-- tocstop -->
|
||||||
|
|
||||||
|
By default tidal-hifi comes with a few themes.
|
||||||
|
You can select these in the settings window under the theming tab as shown below.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Custom CSS
|
||||||
|
|
||||||
|
The custom CSS will be added to the HTML document last.
|
||||||
|
This means that it will overwrite any existing CSS, even that of themes, unless the original has an access modifier such as `$important`.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## config
|
||||||
|
|
||||||
|
The theme selector and customCSS are stored in the config file.
|
||||||
|
The custom CSS is stored as a list of lines.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Warning! Themes might break
|
||||||
|
|
||||||
|
Themes might break at any point. Tidal changes their webpage structure a ton (they probably generate classNames and don't provide roles/ids/attributes.)
|
||||||
|
|
||||||
|
If one breaks you can create an Issue on GitHub or ask for assistance in the [Discord channel](https://discord.gg/yhNwf4v4He).
|
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "tidal-hifi",
|
"name": "tidal-hifi",
|
||||||
"version": "5.2.0",
|
"version": "5.3.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "tidal-hifi",
|
"name": "tidal-hifi",
|
||||||
"version": "5.2.0",
|
"version": "5.3.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/remote": "^2.0.9",
|
"@electron/remote": "^2.0.9",
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tidal-hifi",
|
"name": "tidal-hifi",
|
||||||
"version": "5.2.0",
|
"version": "5.3.0",
|
||||||
"description": "Tidal on Electron with widevine(hifi) support",
|
"description": "Tidal on Electron with widevine(hifi) support",
|
||||||
"main": "ts-dist/main.js",
|
"main": "ts-dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
export const flags: { [key: string]: { flag: string; value?: any }[] } = {
|
export const flags: { [key: string]: { flag: string; value?: string }[] } = {
|
||||||
gpuRasterization: [{ flag: "enable-gpu-rasterization", value: undefined }],
|
gpuRasterization: [{ flag: "enable-gpu-rasterization", value: undefined }],
|
||||||
disableHardwareMediaKeys: [{ flag: "disable-features", value: "HardwareMediaKeyHandling" }],
|
disableHardwareMediaKeys: [{ flag: "disable-features", value: "HardwareMediaKeyHandling" }],
|
||||||
};
|
};
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
import remote from "@electron/remote";
|
import remote, { app } from "@electron/remote";
|
||||||
import { ipcRenderer, shell } from "electron";
|
import { ipcRenderer, shell } from "electron";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { globalEvents } from "../../constants/globalEvents";
|
import { globalEvents } from "../../constants/globalEvents";
|
||||||
import { settings } from "../../constants/settings";
|
import { settings } from "../../constants/settings";
|
||||||
import { settingsStore } from "./../../scripts/settings";
|
import { settingsStore } from "./../../scripts/settings";
|
||||||
|
import { getOptions, getOptionsHeader, getThemeListFromDirectory } from "./theming";
|
||||||
|
|
||||||
let adBlock: HTMLInputElement,
|
let adBlock: HTMLInputElement,
|
||||||
api: HTMLInputElement,
|
api: HTMLInputElement,
|
||||||
@@ -25,23 +26,25 @@ let adBlock: HTMLInputElement,
|
|||||||
theme: HTMLSelectElement,
|
theme: HTMLSelectElement,
|
||||||
trayIcon: HTMLInputElement,
|
trayIcon: HTMLInputElement,
|
||||||
updateFrequency: HTMLInputElement;
|
updateFrequency: HTMLInputElement;
|
||||||
|
|
||||||
function getThemeFiles() {
|
function getThemeFiles() {
|
||||||
const selectElement = document.getElementById("themesList") as HTMLSelectElement;
|
const selectElement = document.getElementById("themesList") as HTMLSelectElement;
|
||||||
const fileNames = fs
|
const builtInThemes = getThemeListFromDirectory(process.resourcesPath);
|
||||||
.readdirSync(process.resourcesPath)
|
const userThemes = getThemeListFromDirectory(`${app.getPath("userData")}/themes`);
|
||||||
.filter((file) => file.endsWith(".css"))
|
|
||||||
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
||||||
|
|
||||||
const options = fileNames.map((name) => {
|
let allThemes = [
|
||||||
return new Option(name.replace(".css", ""), name);
|
getOptionsHeader("Built-in Themes"),
|
||||||
});
|
new Option("Tidal - Default", "none"),
|
||||||
|
].concat(getOptions(builtInThemes));
|
||||||
|
|
||||||
|
if (userThemes.length >= 1) {
|
||||||
|
allThemes = allThemes.concat([getOptionsHeader("User Themes")]).concat(getOptions(userThemes));
|
||||||
|
}
|
||||||
|
|
||||||
// empty old options
|
// empty old options
|
||||||
const oldOptions = document.querySelectorAll("#themesList option");
|
const oldOptions = document.querySelectorAll("#themesList option");
|
||||||
oldOptions.forEach((o) => o.remove());
|
oldOptions.forEach((o) => o.remove());
|
||||||
|
|
||||||
[new Option("Tidal - Default", "none")].concat(options).forEach((option) => {
|
allThemes.forEach((option) => {
|
||||||
selectElement.add(option, null);
|
selectElement.add(option, null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -52,7 +55,7 @@ function handleFileUploads() {
|
|||||||
|
|
||||||
document.getElementById("theme-files").addEventListener("change", function (e: any) {
|
document.getElementById("theme-files").addEventListener("change", function (e: any) {
|
||||||
Array.from(e.target.files).forEach((file: File) => {
|
Array.from(e.target.files).forEach((file: File) => {
|
||||||
const destination = `${process.resourcesPath}/${file.name}`;
|
const destination = `${app.getPath("userData")}/themes/${file.name}`;
|
||||||
fs.copyFileSync(file.path, destination, null);
|
fs.copyFileSync(file.path, destination, null);
|
||||||
});
|
});
|
||||||
fileMessage.innerText = `${e.target.files.length} files successfully uploaded`;
|
fileMessage.innerText = `${e.target.files.length} files successfully uploaded`;
|
||||||
|
@@ -434,5 +434,12 @@ html {
|
|||||||
|
|
||||||
option {
|
option {
|
||||||
background-color: $tidal-grey-darkest;
|
background-color: $tidal-grey-darkest;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
font-size: 1.2em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
text-align: center;
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
54
src/pages/settings/theming.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
const cssFilter = (file: string) => file.endsWith(".css");
|
||||||
|
const sort = (a: string, b: string) => a.toLowerCase().localeCompare(b.toLowerCase());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an "options header" (disabled option) based on a bit of text
|
||||||
|
* @param text of the header
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getOptionsHeader = (text: string): HTMLOptionElement => {
|
||||||
|
const opt = new Option(text, undefined, false, false);
|
||||||
|
opt.disabled = true;
|
||||||
|
return opt;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a list of filenames to a list of HTMLOptionElements
|
||||||
|
* Will strip ".css" from the name but keeps it in the value
|
||||||
|
* @param array array of filenames
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getOptions = (array: string[]) => {
|
||||||
|
return array.map((name) => {
|
||||||
|
return new Option(name.replace(".css", ""), name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read .css files from a directory and return them in a sorted array.
|
||||||
|
* @param directory to read from. Will be created if it doesn't exist
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getThemeListFromDirectory = (directory: string): string[] => {
|
||||||
|
try {
|
||||||
|
makeUserThemesDirectory(directory);
|
||||||
|
return fs.readdirSync(directory).filter(cssFilter).sort(sort);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the directory to store user themes in
|
||||||
|
* @param directory directory to create
|
||||||
|
*/
|
||||||
|
export const makeUserThemesDirectory = (directory: string) => {
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(directory, { recursive: true });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
};
|
@@ -14,7 +14,7 @@ import { setTitle } from "./scripts/window-functions";
|
|||||||
const notificationPath = `${app.getPath("userData")}/notification.jpg`;
|
const notificationPath = `${app.getPath("userData")}/notification.jpg`;
|
||||||
const appName = "Tidal Hifi";
|
const appName = "Tidal Hifi";
|
||||||
let currentSong = "";
|
let currentSong = "";
|
||||||
let player: any;
|
let player: Player;
|
||||||
let currentPlayStatus = statuses.paused;
|
let currentPlayStatus = statuses.paused;
|
||||||
|
|
||||||
const elements = {
|
const elements = {
|
||||||
@@ -40,7 +40,7 @@ const elements = {
|
|||||||
bar: '*[data-test="progress-bar"]',
|
bar: '*[data-test="progress-bar"]',
|
||||||
footer: "#footerPlayer",
|
footer: "#footerPlayer",
|
||||||
album_header_title: '.header-details [data-test="title"]',
|
album_header_title: '.header-details [data-test="title"]',
|
||||||
playing_title: 'span[data-test="table-cell-title"].css-geqnfr',
|
playing_title: 'span[data-test="table-cell-title"].css-1vjc1xk',
|
||||||
album_name_cell: '[data-test="table-cell-album"]',
|
album_name_cell: '[data-test="table-cell-album"]',
|
||||||
tracklist_row: '[data-test="tracklist-row"]',
|
tracklist_row: '[data-test="tracklist-row"]',
|
||||||
volume: '*[data-test="volume"]',
|
volume: '*[data-test="volume"]',
|
||||||
|
@@ -18,7 +18,7 @@ export const startExpress = (mainWindow: BrowserWindow) => {
|
|||||||
* @param {*} res
|
* @param {*} res
|
||||||
* @param {*} action
|
* @param {*} action
|
||||||
*/
|
*/
|
||||||
function handleGlobalEvent(res: Response, action: any) {
|
function handleGlobalEvent(res: Response, action: string) {
|
||||||
mainWindow.webContents.send("globalEvent", action);
|
mainWindow.webContents.send("globalEvent", action);
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@ export const settingsStore = new Store({
|
|||||||
apiSettings: {
|
apiSettings: {
|
||||||
port: 47836,
|
port: 47836,
|
||||||
},
|
},
|
||||||
customCSS: "",
|
customCSS: [],
|
||||||
disableBackgroundThrottle: true,
|
disableBackgroundThrottle: true,
|
||||||
disableHardwareMediaKeys: false,
|
disableHardwareMediaKeys: false,
|
||||||
enableCustomHotkeys: false,
|
enableCustomHotkeys: false,
|
||||||
@@ -55,7 +55,7 @@ export const createSettingsWindow = function () {
|
|||||||
settingsWindow = new BrowserWindow({
|
settingsWindow = new BrowserWindow({
|
||||||
width: 700,
|
width: 700,
|
||||||
height: 600,
|
height: 600,
|
||||||
resizable: false,
|
resizable: true,
|
||||||
show: false,
|
show: false,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
frame: false,
|
frame: false,
|
||||||
@@ -67,7 +67,7 @@ export const createSettingsWindow = function () {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
settingsWindow.on("close", (event: any) => {
|
settingsWindow.on("close", (event: Event) => {
|
||||||
if (settingsWindow != null) {
|
if (settingsWindow != null) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
settingsWindow.hide();
|
settingsWindow.hide();
|
||||||
|
@@ -17,37 +17,37 @@
|
|||||||
--search-dialog-background: #24283b;
|
--search-dialog-background: #24283b;
|
||||||
--right-queue-background: #24283b;
|
--right-queue-background: #24283b;
|
||||||
}
|
}
|
||||||
.player--fNPGt.notFullscreen--ugyc2 {
|
.player--gAOQG.notFullscreen--xbpBL {
|
||||||
background-color: var(--footer-player-background);
|
background-color: var(--footer-player-background);
|
||||||
}
|
}
|
||||||
.sidebar--WvRg_ {
|
.sidebar--jVJai {
|
||||||
background-color: var(--sidebar-background);
|
background-color: var(--sidebar-background);
|
||||||
contain: strict;
|
contain: strict;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.item--VTpWS:hover {
|
.item--buEQw:hover {
|
||||||
background-color: var(--sidebar-hover-background);
|
background-color: var(--sidebar-hover-background);
|
||||||
}
|
}
|
||||||
.main--LUnJp {
|
.main--jxfcQ {
|
||||||
background-color: var(--main-background);
|
background-color: var(--main-background);
|
||||||
}
|
}
|
||||||
button.button--ncJwL {
|
button.button--yO9Cd {
|
||||||
background-color: var(--main-navigation-control-background);
|
background-color: var(--main-navigation-control-background);
|
||||||
}
|
}
|
||||||
.player--fNPGt.lossLess--g5Jss button.withBackground[aria-checked="true"] path {
|
.player--gAOQG.lossLess--ON3FI button.withBackground[aria-checked="true"] path {
|
||||||
fill: var(--player-control-active-button);
|
fill: var(--player-control-active-button);
|
||||||
}
|
}
|
||||||
.player--fNPGt.lossLess--g5Jss button.withBackground[aria-checked="true"] {
|
.player--gAOQG.lossLess--ON3FI button.withBackground[aria-checked="true"] {
|
||||||
background-color: var(--player-control-background);
|
background-color: var(--player-control-background);
|
||||||
}
|
}
|
||||||
.activeItem--qV6eL .activeItem--qV6eL .playlistItem--YARJh .section--FI41E.playingItem--eWkYS {
|
.activeItem--kFIk0 .activeItem--kFIk0 .playlistItem--mQrxp .section--PSIay.playingItem--eWkYS {
|
||||||
color: #565f89;
|
color: #565f89;
|
||||||
}
|
}
|
||||||
.progressBarWrapper--WZfox {
|
.progressBarWrapper--IBBI9 {
|
||||||
color: var(--player-progress-bar);
|
color: var(--player-progress-bar);
|
||||||
}
|
}
|
||||||
.playbackControls--FLeZA button .tidal-ui__icon {
|
.playbackControls--FhKVf button .tidal-ui__icon {
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
.css-11m9iw3 {
|
.css-11m9iw3 {
|
||||||
@@ -56,27 +56,30 @@ button.button--ncJwL {
|
|||||||
.css-11m9iw3 span {
|
.css-11m9iw3 span {
|
||||||
color: var(--indicator-hifi-span);
|
color: var(--indicator-hifi-span);
|
||||||
}
|
}
|
||||||
.activeItem--qV6eL {
|
.activeItem--kFIk0 {
|
||||||
color: var(--sidebar-menu-top-text);
|
color: var(--sidebar-menu-top-text);
|
||||||
}
|
}
|
||||||
.activeItem--qV6eL .playlistItem--YARJh {
|
.activeItem--kFIk0 .playlistItem--mQrxp {
|
||||||
color: var(--sidebar-menu-playlist-text);
|
color: var(--sidebar-menu-playlist-text);
|
||||||
}
|
}
|
||||||
button.feedBell--B8anb {
|
button.feedBell--kvAbD {
|
||||||
background-color: var(--main-feed-button-background);
|
background-color: var(--main-feed-button-background);
|
||||||
}
|
}
|
||||||
.baseContainer--cbf17 {
|
.baseContainer--jxCbW {
|
||||||
background-color: var(--search-dialog-background);
|
background-color: var(--search-dialog-background);
|
||||||
}
|
}
|
||||||
.favoriteButton--TtBlM.is-favorite path {
|
.favoriteButton--Qladw.is-favorite path {
|
||||||
fill: var(--player-control-favorite);
|
fill: var(--player-control-favorite);
|
||||||
}
|
}
|
||||||
.container--mkEWd {
|
.container--PFTHk {
|
||||||
background-color: var(--right-queue-background);
|
background-color: var(--right-queue-background);
|
||||||
}
|
}
|
||||||
.container--vJVjO {
|
.container--cl4MJ{
|
||||||
background-color: var(--search-background);
|
background-color: var(--search-background);
|
||||||
}
|
}
|
||||||
.searchFieldHighlighted--Fitvs {
|
.searchFieldHighlighted--Fitvs {
|
||||||
color: var(--snow-white);
|
color: var(--snow-white);
|
||||||
}
|
}
|
||||||
|
.searchField--EGBSq {
|
||||||
|
background-color: var(--search-background);
|
||||||
|
}
|
||||||
|
60
src/types/mpris-service.d.ts
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
declare class InitOptions {
|
||||||
|
name: string;
|
||||||
|
identity: string;
|
||||||
|
supportedUriSchemes: string[];
|
||||||
|
supportedMimeTypes: string[];
|
||||||
|
supportedInterfaces: string[];
|
||||||
|
desktopEntry: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare class Player {
|
||||||
|
metadata: {
|
||||||
|
"xesam:title": string;
|
||||||
|
"xesam:artist": string[];
|
||||||
|
"xesam:album": string;
|
||||||
|
"mpris:artUrl": string;
|
||||||
|
"mpris:length": number;
|
||||||
|
"mpris:trackid": string;
|
||||||
|
// other options
|
||||||
|
[key: string]: string | number | string[] | object;
|
||||||
|
};
|
||||||
|
playbackStatus: string;
|
||||||
|
identity: string;
|
||||||
|
fullscreen: boolean;
|
||||||
|
supportedUriSchemes: string[];
|
||||||
|
supportedMimeTypes: string[];
|
||||||
|
canQuit: boolean;
|
||||||
|
canRaise: boolean;
|
||||||
|
canSetFullscreen: boolean;
|
||||||
|
hasTrackList: boolean;
|
||||||
|
desktopEntry: string;
|
||||||
|
loopStatus: string;
|
||||||
|
shuffle: boolean;
|
||||||
|
volume: number;
|
||||||
|
canControl: boolean;
|
||||||
|
canPause: boolean;
|
||||||
|
canPlay: boolean;
|
||||||
|
canSeek: boolean;
|
||||||
|
canGoNext: boolean;
|
||||||
|
canGoPrevious: boolean;
|
||||||
|
rate: number;
|
||||||
|
minimumRate: number;
|
||||||
|
maximumRate: number;
|
||||||
|
playlists: string[];
|
||||||
|
activePlaylist: string;
|
||||||
|
|
||||||
|
constructor(opts: { name: string; supportedInterfaces?: string[] });
|
||||||
|
constructor(opts: InitOptions);
|
||||||
|
|
||||||
|
getPosition(): number;
|
||||||
|
seeked(): void;
|
||||||
|
getTrackIndex(trackId: number): number;
|
||||||
|
getTrack(trackId: number): string;
|
||||||
|
addTrack(track: object): void;
|
||||||
|
removeTrack(trackId: number): number;
|
||||||
|
getPlaylistIndex(playlistId: number): number;
|
||||||
|
setPlaylists(playlists: object): void;
|
||||||
|
setActivePlaylist(playlistId: number): void;
|
||||||
|
|
||||||
|
on(event: string | symbol, listener: (...args: object[]) => void): this;
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"typeRoots": ["src/types"],
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
|