diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..e74f2f7 --- /dev/null +++ b/.drone.yml @@ -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 diff --git a/.gitignore b/.gitignore index 2e56407..6121081 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,6 @@ build/linux/arch/* .idea ts-dist/** ts-dist +themes +!src/themes .sass-cache diff --git a/CHANGELOG.md b/CHANGELOG.md index 24397c2..6f2b6e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 5.2.0 - moved from Javascript to Typescript for all files + - use `npm run watch` to watch for changes & recompile typescript and sass files +- Added support for theming the application +- Added drone build file use `drone exec` or drone.ci to build it + ## 5.1.0 ### New features diff --git a/README.md b/README.md index f187f6a..6fc5c6a 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,80 @@ # Tidal-hifi -![GitHub release](https://img.shields.io/github/release/Mastermindzh/tidal-hifi.svg) +![GitHub release](https://img.shields.io/github/release/Mastermindzh/tidal-hifi.svg) [![github builds](https://github.com/mastermindzh/tidal-hifi/actions/workflows/build.yml/badge.svg)](https://github.com/Mastermindzh/tidal-hifi/actions) [![Build Status](https://ci.mastermindzh.tech/api/badges/Mastermindzh/tidal-hifi/status.svg)](https://ci.mastermindzh.tech/Mastermindzh/tidal-hifi) [![Discord logo](./docs/images/discord.png)](https://discord.gg/yhNwf4v4He) The web version of [listen.tidal.com](https://listen.tidal.com) running in electron with hifi support thanks to widevine. -![tidal-hifi preview](./docs/preview.png) +![tidal-hifi preview](./docs/images/preview.png) ## Table of Contents -- [Installation](#installation) - - [Dependencies](#dependencies) - - [Using releases](#using-releases) - - [Snap](#snap) - - [Arch Linux](#arch-linux) - - [Flatpak](#flatpak) - - [Nix](#nix) - - [Using source](#using-source) -- [Features](#features) -- [Integrations](#integrations) - - [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) -- [Why](#why) -- [Why not extend existing projects?](#why-not-extend-existing-projects) -- [Special thanks to](#special-thanks-to) -- [Buy me a coffee? Please don't](#buy-me-a-coffee-please-dont) -- [Images](#images) - - [Settings window](#settings-window) - - [User setups](#user-setups) +- [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) + - [Using releases](#using-releases) + - [Snap](#snap) + - [Arch Linux](#arch-linux) + - [Flatpak](#flatpak) + - [Nix](#nix) + - [Using source](#using-source) + - [Integrations](#integrations) + - [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) + - [Special thanks to](#special-thanks-to) + - [Donations](#donations) + - [Images](#images) + - [Settings window](#settings-window) + - [User setups](#user-setups) +## 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. + +- ![Discord logo](./docs/images/discord.png) [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 ### 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 @@ -48,15 +86,15 @@ To install with `snap` you need to download the pre-packaged snap-package from t 1. Download -```sh -wget #for instance: https://github.com/Mastermindzh/tidal-hifi/releases/download/1.0/tidal-hifi_1.0.0_amd64.snap -``` + ```sh + wget #for instance: https://github.com/Mastermindzh/tidal-hifi/releases/download/1.0/tidal-hifi_1.0.0_amd64.snap + ``` 2. Install -```sh -snap install --dangerous #for instance: tidal-hifi_1.0.0_amd64.snap -``` + ```sh + snap install --dangerous #for instance: tidal-hifi_1.0.0_amd64.snap + ``` ### Arch Linux @@ -91,23 +129,12 @@ To install and work with the code on this project follow these steps: - npm install - 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 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. -![integrations menu, showing a list of integrations](./docs/integrations.png) +![integrations menu, showing a list of integrations](./docs/images/integrations.png) 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). 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 - [Castlabs](https://castlabs.com/) 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). -Inspired by [haydenjames' issue](https://github.com/Mastermindzh/tidal-hifi/issues/27#issuecomment-704198429) +You can find my Github sponsorship page at: [https://github.com/sponsors/Mastermindzh](https://github.com/sponsors/Mastermindzh) ## Images ### Settings window -![settings window](./docs/settings-preview.png) +![settings window](./docs/images/settings-preview.png) ### User setups diff --git a/build/electron-builder.base.yml b/build/electron-builder.base.yml index b35a36c..27f2319 100644 --- a/build/electron-builder.base.yml +++ b/build/electron-builder.base.yml @@ -7,6 +7,8 @@ snap: plugs: - default - screen-inhibit-control +extraResources: + - "themes/**" linux: category: AudioVideo icon: assets/icons diff --git a/docs/images/customcss-config.png b/docs/images/customcss-config.png new file mode 100644 index 0000000..4104a2a Binary files /dev/null and b/docs/images/customcss-config.png differ diff --git a/docs/images/customcss.png b/docs/images/customcss.png new file mode 100644 index 0000000..8cff474 Binary files /dev/null and b/docs/images/customcss.png differ diff --git a/docs/images/discord.png b/docs/images/discord.png new file mode 100644 index 0000000..c4ee7fa Binary files /dev/null and b/docs/images/discord.png differ diff --git a/docs/integrations.png b/docs/images/integrations.png similarity index 100% rename from docs/integrations.png rename to docs/images/integrations.png diff --git a/docs/preview.png b/docs/images/preview.png similarity index 100% rename from docs/preview.png rename to docs/images/preview.png diff --git a/docs/settings-preview.png b/docs/images/settings-preview.png similarity index 100% rename from docs/settings-preview.png rename to docs/images/settings-preview.png diff --git a/docs/settings.png b/docs/images/settings.png similarity index 100% rename from docs/settings.png rename to docs/images/settings.png diff --git a/docs/images/theming.png b/docs/images/theming.png new file mode 100644 index 0000000..5792a55 Binary files /dev/null and b/docs/images/theming.png differ diff --git a/docs/theming.md b/docs/theming.md new file mode 100644 index 0000000..2599ca4 --- /dev/null +++ b/docs/theming.md @@ -0,0 +1,38 @@ +# Theming tidal-hifi + +## Table of contents + + + +- [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) + + + +By default tidal-hifi comes with a few themes. +You can select these in the settings window under the theming tab as shown below. + +![Settings window with the theming tab opened](./images/theming.png) + +## 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`. + +![settings window on the theming tab with a custom CSS override](./images/customcss.png) + +## config + +The theme selector and customCSS are stored in the config file. +The custom CSS is stored as a list of lines. + +![settings window on the theming tab next to the config file](./images/customcss-config.png) + +## 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). diff --git a/package-lock.json b/package-lock.json index fc82f3b..e38be89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8846,4 +8846,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 12518c3..11d102b 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "compile": "tsc && npm run sass-and-copy", "watch": "tsc-watch --onSuccess \"npm run sass-and-copy\"", "copy-files": "copyfiles -u 1 --exclude './src/**/*.ts' --exclude './src/**/*.scss' \"./src/**/*\" ts-dist", - "sass-and-copy": "npm run sass && npm run copy-files", + "copy-themes-dev": "copyfiles -u 1 \"./themes/*\" node_modules/electron/dist/resources", + "sass-and-copy": "npm run sass && npm run copy-files && npm run copy-themes-dev", "build": "npm run builder -- -c ./build/electron-builder.yml", "build-deb": "npm run builder -- -c ./build/electron-builder.deb.yml", "build-unpacked": "npm run builder -- -c ./build/electron-builder.unpacked.yml", @@ -20,7 +21,7 @@ "build-base": "npm run builder -- -c ./build/electron-builder.base.yml", "prebuilder": "npm run compile", "builder": "electron-builder --publish=never", - "sass": "sass ./src/pages/settings/settings.scss ./src/pages/settings/settings.css", + "sass": "sass ./src/pages/settings/settings.scss ./src/pages/settings/settings.css && sass --no-source-map src/themes:themes", "style-lint": "npx stylelint **/*.scss", "style-lint-fix": "npx stylelint --fix **/*.scss" }, diff --git a/src/constants/settings.ts b/src/constants/settings.ts index 04444e6..df2c39f 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -33,6 +33,7 @@ export const settings = { singleInstance: "singleInstance", skipArtists: "skipArtists", skippedArtists: "skippedArtists", + theme: "theme", trayIcon: "trayIcon", updateFrequency: "updateFrequency", windowBounds: { diff --git a/src/pages/settings/preload.ts b/src/pages/settings/preload.ts index fb64769..965ccf8 100644 --- a/src/pages/settings/preload.ts +++ b/src/pages/settings/preload.ts @@ -1,8 +1,10 @@ -import remote from "@electron/remote"; +import remote, { app } from "@electron/remote"; import { ipcRenderer, shell } from "electron"; +import fs from "fs"; import { globalEvents } from "../../constants/globalEvents"; import { settings } from "../../constants/settings"; import { settingsStore } from "./../../scripts/settings"; +import { getOptions, getOptionsHeader, getThemeListFromDirectory } from "./theming"; let adBlock: HTMLInputElement, api: HTMLInputElement, @@ -21,8 +23,45 @@ let adBlock: HTMLInputElement, singleInstance: HTMLInputElement, skipArtists: HTMLInputElement, skippedArtists: HTMLInputElement, + theme: HTMLSelectElement, trayIcon: HTMLInputElement, updateFrequency: HTMLInputElement; +function getThemeFiles() { + const selectElement = document.getElementById("themesList") as HTMLSelectElement; + const builtInThemes = getThemeListFromDirectory(process.resourcesPath); + const userThemes = getThemeListFromDirectory(`${app.getPath("userData")}/themes`); + + let allThemes = [ + 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 + const oldOptions = document.querySelectorAll("#themesList option"); + oldOptions.forEach((o) => o.remove()); + + allThemes.forEach((option) => { + selectElement.add(option, null); + }); +} + +function handleFileUploads() { + const fileMessage = document.getElementById("file-message"); + fileMessage.innerText = "or drag and drop files here"; + + document.getElementById("theme-files").addEventListener("change", function (e: any) { + Array.from(e.target.files).forEach((file: File) => { + const destination = `${app.getPath("userData")}/themes/${file.name}`; + fs.copyFileSync(file.path, destination, null); + }); + fileMessage.innerText = `${e.target.files.length} files successfully uploaded`; + getThemeFiles(); + }); +} /** * Sync the UI forms with the current settings @@ -30,7 +69,7 @@ let adBlock: HTMLInputElement, function refreshSettings() { adBlock.checked = settingsStore.get(settings.adBlock); api.checked = settingsStore.get(settings.api); - customCSS.value = settingsStore.get(settings.customCSS); + customCSS.value = settingsStore.get(settings.customCSS).join("\n"); disableBackgroundThrottle.checked = settingsStore.get(settings.disableBackgroundThrottle); disableHardwareMediaKeys.checked = settingsStore.get(settings.flags.disableHardwareMediaKeys); enableCustomHotkeys.checked = settingsStore.get(settings.enableCustomHotkeys); @@ -44,6 +83,7 @@ function refreshSettings() { port.value = settingsStore.get(settings.apiSettings.port); singleInstance.checked = settingsStore.get(settings.singleInstance); skipArtists.checked = settingsStore.get(settings.skipArtists); + theme.value = settingsStore.get(settings.theme); skippedArtists.value = settingsStore.get(settings.skippedArtists).join("\n"); trayIcon.checked = settingsStore.get(settings.trayIcon); updateFrequency.value = settingsStore.get(settings.updateFrequency); @@ -75,10 +115,13 @@ function restart() { * Bind UI components to functions after DOMContentLoaded */ window.addEventListener("DOMContentLoaded", () => { - function get(id: string): HTMLInputElement { - return document.getElementById(id) as HTMLInputElement; + function get(id: string): T { + return document.getElementById(id) as T; } + getThemeFiles(); + handleFileUploads(); + document.getElementById("close").addEventListener("click", hide); document.getElementById("restart").addEventListener("click", restart); document.querySelectorAll(".external-link").forEach((elem) => @@ -105,6 +148,13 @@ window.addEventListener("DOMContentLoaded", () => { }); } + function addSelectListener(source: HTMLSelectElement, key: string) { + source.addEventListener("change", () => { + settingsStore.set(key, source.value); + ipcRenderer.send(globalEvents.storeChanged); + }); + } + ipcRenderer.on("refreshData", () => { refreshSettings(); }); @@ -127,6 +177,7 @@ window.addEventListener("DOMContentLoaded", () => { notifications = get("notifications"); playBackControl = get("playBackControl"); port = get("port"); + theme = get("themesList"); trayIcon = get("trayIcon"); skipArtists = get("skipArtists"); skippedArtists = get("skippedArtists"); @@ -152,6 +203,7 @@ window.addEventListener("DOMContentLoaded", () => { addInputListener(skipArtists, settings.skipArtists); addTextAreaListener(skippedArtists, settings.skippedArtists); addInputListener(singleInstance, settings.singleInstance); + addSelectListener(theme, settings.theme); addInputListener(trayIcon, settings.trayIcon); addInputListener(updateFrequency, settings.updateFrequency); }); diff --git a/src/pages/settings/settings.html b/src/pages/settings/settings.html index b784a02..583b24b 100644 --- a/src/pages/settings/settings.html +++ b/src/pages/settings/settings.html @@ -35,6 +35,9 @@ + + + @@ -226,6 +229,49 @@ +
+

Flags

+
+
+

Disable hardware built-in media keys

+

+ Also prevents certain desktop environments from recognizing the chrome MPRIS + client separately from the custom MPRIS client. +

+
+ +
+
+
+

Enable GPU rasterization

+

Move a part of the rendering to the GPU for increased performance.

+
+ +
+
+
+

Disable Background Throttling

+

+ Makes app more responsive while in the background, at the cost of performance. +

+
+ +
+
+ + +
+
+

Theming

Custom CSS

@@ -238,41 +284,34 @@
-

Flags

+

Theme files

-

Disable hardware built-in media keys

+

Current theme

- Also prevents certain desktop environments from recognizing the chrome MPRIS - client separately from the custom MPRIS client. + Select a theme below or "Tidal - Default" to return to the original Tidal look.

+
-
+
-

Enable GPU rasterization

-

Move a part of the rendering to the GPU for increased performance.

-
- -
-
-
-

Disable Background Throttling

+

Upload new themes

- Makes app more responsive while in the background, at the cost of performance. + Click the button and select the css files to import. They will be added to the theme list + automatically.

+
+
+ Choose files + or drag and drop files here + +
+
-
diff --git a/src/pages/settings/settings.scss b/src/pages/settings/settings.scss index 537dbd5..8e7318a 100644 --- a/src/pages/settings/settings.scss +++ b/src/pages/settings/settings.scss @@ -156,7 +156,7 @@ html { display: none; } - @for $i from 1 to 6 { + @for $i from 1 to 7 { .settings > input:nth-child(#{$i * 2 - 1}):checked ~ & > .tabs__section:nth-child(#{$i}) { display: block; } @@ -230,8 +230,6 @@ html { border-color: $tidal-blue; color: $white; } - - // --- Switch slider component --- } } } @@ -361,3 +359,87 @@ html { } } } + +// file upload + +.file-drop-area { + position: relative; + display: flex; + align-items: center; + width: 100%; + max-width: 100%; + padding: 25px 0 25px 0px; + border: 1px dashed $tidal-grey; + border-radius: 3px; + transition: 0.2s; + &.is-active { + background-color: $black; + } + + div { + padding-left: 25px; + } +} + +.file-btn { + flex-shrink: 0; + background-color: $black; + border: 1px solid $tidal-grey; + border-radius: 3px; + padding: 8px 15px; + margin-right: 10px; + font-size: 12px; + text-transform: uppercase; +} + +.file-msg { + font-size: small; + font-weight: 300; + line-height: 1.4; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.file-input { + position: absolute; + left: 0; + top: 0; + height: 100%; + width: 100%; + cursor: pointer; + opacity: 0; + &:focus { + outline: none; + } +} + +.select-input { + display: block; + width: 100%; + margin-bottom: 10px; + padding: 5px 0; + transition: 0.2s; + border: 0; + border-bottom: solid 1px $grey-333; + outline: none; + background: transparent; + color: $tidal-grey; + font-size: 14px; + + &:focus { + border-color: $tidal-blue; + color: $white; + } + + option { + background-color: $tidal-grey-darkest; + + &:disabled { + font-size: 1.2em; + line-height: 1.5em; + text-align: center; + color: $white; + } + } +} diff --git a/src/pages/settings/theming.ts b/src/pages/settings/theming.ts new file mode 100644 index 0000000..b35cfcc --- /dev/null +++ b/src/pages/settings/theming.ts @@ -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); + } +}; diff --git a/src/preload.ts b/src/preload.ts index 862414e..91144dd 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -1,5 +1,6 @@ import { Notification, app, dialog } from "@electron/remote"; import { ipcRenderer } from "electron"; +import fs from "fs"; import Player from "mpris-service"; import { globalEvents } from "./constants/globalEvents"; import { settings } from "./constants/settings"; @@ -39,7 +40,7 @@ const elements = { bar: '*[data-test="progress-bar"]', footer: "#footerPlayer", 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"]', tracklist_row: '[data-test="tracklist-row"]', volume: '*[data-test="volume"]', @@ -147,8 +148,24 @@ const elements = { function addCustomCss() { window.addEventListener("DOMContentLoaded", () => { + const selectedTheme = settingsStore.get(settings.theme); + if (selectedTheme !== "none") { + const themeFile = `${process.resourcesPath}/${selectedTheme}`; + fs.readFile(themeFile, "utf-8", (err, data) => { + if (err) { + alert("An error ocurred reading the theme file."); + return; + } + + const themeStyle = document.createElement("style"); + themeStyle.innerHTML = data; + document.head.appendChild(themeStyle); + }); + } + + // read customCSS (it will override the theme) const style = document.createElement("style"); - style.innerHTML = settingsStore.get(settings.customCSS); + style.innerHTML = settingsStore.get(settings.customCSS).join("\n"); document.head.appendChild(style); }); } diff --git a/src/scripts/settings.ts b/src/scripts/settings.ts index b54465d..614f8d1 100644 --- a/src/scripts/settings.ts +++ b/src/scripts/settings.ts @@ -13,7 +13,7 @@ export const settingsStore = new Store({ apiSettings: { port: 47836, }, - customCSS: "", + customCSS: [], disableBackgroundThrottle: true, disableHardwareMediaKeys: false, enableCustomHotkeys: false, @@ -30,6 +30,7 @@ export const settingsStore = new Store({ singleInstance: true, skipArtists: false, skippedArtists: [""], + theme: "none", trayIcon: true, updateFrequency: 500, windowBounds: { width: 800, height: 600 }, @@ -54,7 +55,7 @@ export const createSettingsWindow = function () { settingsWindow = new BrowserWindow({ width: 700, height: 600, - resizable: false, + resizable: true, show: false, transparent: true, frame: false, diff --git a/src/themes/Blood.scss b/src/themes/Blood.scss new file mode 100644 index 0000000..b7dfd6e --- /dev/null +++ b/src/themes/Blood.scss @@ -0,0 +1,10 @@ +$foreground: red; +$background: black; + +span { + color: $foreground; +} + +.sidebar--WvRg_ { + background-color: $background; +} diff --git a/src/themes/Tokyo Night.scss b/src/themes/Tokyo Night.scss new file mode 100644 index 0000000..b9f6a73 --- /dev/null +++ b/src/themes/Tokyo Night.scss @@ -0,0 +1,82 @@ +:root { + --footer-player-background: #1a1b26; + --sidebar-background: #1a1b26; + --sidebar-hover-background: #414868; + --sidebar-menu-top-text: #565f89; + --sidebar-menu-playlist-text: #565f89; + --search-background: #1a1b26; + --main-background: #16161e; + --main-navigation-control-background: #1a1b26; + --main-feed-button-background: #1a1b26; + --player-control-background: #24283b; + --player-control-active-button: #ff9e64; + --player-progress-bar: #ff9e64; + --indicator-hifi-background: #9ece6a; + --indicator-hifi-span: #1a1b26; + --player-control-favorite: #f7768e; + --search-dialog-background: #24283b; + --right-queue-background: #24283b; +} +.player--fNPGt.notFullscreen--ugyc2 { + background-color: var(--footer-player-background); +} +.sidebar--WvRg_ { + background-color: var(--sidebar-background); + contain: strict; + flex-grow: 1; + overflow-y: auto; +} +.item--VTpWS:hover { + background-color: var(--sidebar-hover-background); +} +.main--LUnJp { + background-color: var(--main-background); +} +button.button--ncJwL { + background-color: var(--main-navigation-control-background); +} +.player--fNPGt.lossLess--g5Jss button.withBackground[aria-checked="true"] path { + fill: var(--player-control-active-button); +} +.player--fNPGt.lossLess--g5Jss button.withBackground[aria-checked="true"] { + background-color: var(--player-control-background); +} +.activeItem--qV6eL .activeItem--qV6eL .playlistItem--YARJh .section--FI41E.playingItem--eWkYS { + color: #565f89; +} +.progressBarWrapper--WZfox { + color: var(--player-progress-bar); +} +.playbackControls--FLeZA button .tidal-ui__icon { + transform: scale(1); +} +.css-11m9iw3 { + background-color: var(--indicator-hifi-background); +} +.css-11m9iw3 span { + color: var(--indicator-hifi-span); +} +.activeItem--qV6eL { + color: var(--sidebar-menu-top-text); +} +.activeItem--qV6eL .playlistItem--YARJh { + color: var(--sidebar-menu-playlist-text); +} +button.feedBell--B8anb { + background-color: var(--main-feed-button-background); +} +.baseContainer--cbf17 { + background-color: var(--search-dialog-background); +} +.favoriteButton--TtBlM.is-favorite path { + fill: var(--player-control-favorite); +} +.container--mkEWd { + background-color: var(--right-queue-background); +} +.container--vJVjO { + background-color: var(--search-background); +} +.searchFieldHighlighted--Fitvs { + color: var(--snow-white); +}