diff --git a/LICENSE.txt b/LICENSE.txt index 32ce21fd..bba9db52 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Sandakan Nipunajith +Copyright (c) 2023 Sandakan Nipunajith Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 63cfbf6f..17cbd49e 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,10 @@ It packs a horizon of features including,

+
+ +![Latest Version Artwork](/assets/other/release%20artworks/whats-new-v2.0.0-stable.webp) + Visit the [release notes](/changelog.md) to see what's new on the latest release.


diff --git a/assets/installer_assets/sidebar.bmp b/assets/installer_assets/sidebar.bmp index 13185840..c0cf808f 100644 Binary files a/assets/installer_assets/sidebar.bmp and b/assets/installer_assets/sidebar.bmp differ diff --git a/assets/styles/styles.css b/assets/styles/styles.css index 99d575df..5a2ef398 100644 --- a/assets/styles/styles.css +++ b/assets/styles/styles.css @@ -133,6 +133,7 @@ html { -moz-osx-font-smoothing: grayscale; font-feature-settings: 'liga'; user-select: none; + transition: font-variation-settings 0.15s ease-in-out; } .material-icons-round { diff --git a/changelog.md b/changelog.md index 24245e8e..40ce02ab 100644 --- a/changelog.md +++ b/changelog.md @@ -8,7 +8,7 @@
-- ### **v2.0.0-stable - ( 20th of April 2023 )** +- ### **v2.0.0-stable - ( 23th of April 2023 )** - ### 🎉 New Features and Features @@ -32,6 +32,7 @@ - Added a new button to the right side of the app's footer for advanced playback options. - Added a new smooth scrolling feature to pages that directs users to specific parts of the page. - Added a new title next to the artwork in the queue to show the queue type. + - Add a new transition effect for some icons. - Added support for responsive song cards in the Home. - Added support to toggle between predictive search and normal search. - Added support for highlighting more than one lyrics line at a time. Fixes #135. @@ -104,6 +105,8 @@ - Updated some styles in the Release Notes prompt and open_source_licenses prompt. - Reordered buttons in the About section of Settings. - Fixed some security vulnerabilities in the app. + - Fixed a bug where Img component try to fetch 404 requests repeatedly. + - Fixed a bug where users can download lyrics in the lyrics editor even though the app is not connected to the internet. diff --git a/open_source_licenses.txt b/open_source_licenses.txt index 360d892f..b298f50f 100644 --- a/open_source_licenses.txt +++ b/open_source_licenses.txt @@ -3,7 +3,7 @@ NORA MIT License -Copyright (c) 2022 Sandakan Nipunajith +Copyright (c) 2023 Sandakan Nipunajith Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/package-lock.json b/package-lock.json index c70bc81f..840c9f81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nora", - "version": "2.0.0-stable.20230420", + "version": "2.0.0-stable", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "nora", - "version": "2.0.0-stable.20230420", + "version": "2.0.0-stable", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -30,8 +30,8 @@ "@types/auto-launch": "^5.0.2", "@types/dotenv-webpack": "^7.0.3", "@types/jest": "^29.5.1", - "@types/node": "18.15.12", - "@types/react": "^18.0.37", + "@types/node": "18.16.0", + "@types/react": "^18.0.38", "@types/react-beautiful-dnd": "^13.1.4", "@types/react-dom": "^18.0.11", "@types/react-test-renderer": "^18.0.0", @@ -56,7 +56,7 @@ "electron-debug": "^3.2.0", "electron-devtools-installer": "^3.2.0", "electronmon": "^2.0.2", - "eslint": "^8.38.0", + "eslint": "^8.39.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-erb": "^4.0.6", "eslint-import-resolver-typescript": "^3.5.5", @@ -92,7 +92,7 @@ "url-loader": "^4.1.1", "webpack": "^5.80.0", "webpack-bundle-analyzer": "^4.8.0", - "webpack-cli": "^5.0.1", + "webpack-cli": "^5.0.2", "webpack-dev-server": "^4.13.3", "webpack-merge": "^5.8.0" } @@ -974,9 +974,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz", - "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", + "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2436,9 +2436,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.15.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.12.tgz", - "integrity": "sha512-Wha1UwsB3CYdqUm2PPzh/1gujGCNtWVUYF0mB00fJFoR4gTyWTDPjSm+zBF787Ahw8vSGgBja90MkgFwvB86Dg==", + "version": "18.16.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.0.tgz", + "integrity": "sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==", "dev": true }, "node_modules/@types/plist": { @@ -2476,9 +2476,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.0.37", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.37.tgz", - "integrity": "sha512-4yaZZtkRN3ZIQD3KSEwkfcik8s0SWV+82dlJot1AbGYHCzJkWP3ENBY6wYeDRmKZ6HkrgoGAmR2HqdwYGp6OEw==", + "version": "18.0.38", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.38.tgz", + "integrity": "sha512-ExsidLLSzYj4cvaQjGnQCk4HFfVT9+EZ9XZsQ8Hsrcn8QNgXtpZ3m9vSIC2MWtx7jHictK6wYhQgGh6ic58oOw==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -3127,9 +3127,9 @@ } }, "node_modules/@webpack-cli/serve": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz", - "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.2.tgz", + "integrity": "sha512-S9h3GmOmzUseyeFW3tYNnWS7gNUuwxZ3mmMq0JyW78Vx1SGKPSkt5bT4pB0rUnVfHjP0EL9gW2bOzmtiTfQt0A==", "dev": true, "engines": { "node": ">=14.15.0" @@ -6608,15 +6608,15 @@ } }, "node_modules/eslint": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", - "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.38.0", + "@eslint/js": "8.39.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -6626,7 +6626,7 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", + "eslint-scope": "^7.2.0", "eslint-visitor-keys": "^3.4.0", "espree": "^9.5.1", "esquery": "^1.4.2", @@ -15787,17 +15787,17 @@ } }, "node_modules/webpack-cli": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz", - "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.2.tgz", + "integrity": "sha512-4y3W5Dawri5+8dXm3+diW6Mn1Ya+Dei6eEVAdIduAmYNLzv1koKVAqsfgrrc9P2mhrYHQphx5htnGkcNwtubyQ==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.0.1", "@webpack-cli/info": "^2.0.1", - "@webpack-cli/serve": "^2.0.1", + "@webpack-cli/serve": "^2.0.2", "colorette": "^2.0.14", - "commander": "^9.4.1", + "commander": "^10.0.1", "cross-spawn": "^7.0.3", "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", @@ -15832,12 +15832,12 @@ } }, "node_modules/webpack-cli/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, "engines": { - "node": "^12.20.0 || >=14" + "node": ">=14" } }, "node_modules/webpack-cli/node_modules/interpret": { diff --git a/package.json b/package.json index 94d9b209..57361e60 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "nora", "productName": "Nora", "description": "An elegant music player built using Electron and React. Inspired by Oto Music for Android by Piyush Mamidwar.", - "version": "2.0.0-stable.20230421", + "version": "2.0.0-stable", "license": "MIT", "appPreferences": { "removeReactStrictMode": false, @@ -135,8 +135,8 @@ "@types/auto-launch": "^5.0.2", "@types/dotenv-webpack": "^7.0.3", "@types/jest": "^29.5.1", - "@types/node": "18.15.12", - "@types/react": "^18.0.37", + "@types/node": "18.16.0", + "@types/react": "^18.0.38", "@types/react-beautiful-dnd": "^13.1.4", "@types/react-dom": "^18.0.11", "@types/react-test-renderer": "^18.0.0", @@ -161,7 +161,7 @@ "electron-debug": "^3.2.0", "electron-devtools-installer": "^3.2.0", "electronmon": "^2.0.2", - "eslint": "^8.38.0", + "eslint": "^8.39.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-erb": "^4.0.6", "eslint-import-resolver-typescript": "^3.5.5", @@ -197,7 +197,7 @@ "url-loader": "^4.1.1", "webpack": "^5.80.0", "webpack-bundle-analyzer": "^4.8.0", - "webpack-cli": "^5.0.1", + "webpack-cli": "^5.0.2", "webpack-dev-server": "^4.13.3", "webpack-merge": "^5.8.0" }, @@ -221,7 +221,7 @@ }, "build": { "productName": "Nora", - "copyright": "Copyright © 2022 Sandakan Nipunajith", + "copyright": "Copyright © 2023 Sandakan Nipunajith", "appId": "com.SandakanNipunajith.Nora", "asar": true, "asarUnpack": "**\\*.{node,dll}", diff --git a/release-notes.json b/release-notes.json index 885f71dd..6134e413 100644 --- a/release-notes.json +++ b/release-notes.json @@ -1,14 +1,259 @@ { "latestVersion": { - "version": "1.2.0-stable", + "version": "2.0.0-stable", "phase": "stable", - "releaseDate": "2023 March 09", - "artwork": "/assets/other/release artworks/whats-new-v1.2.0-stable.webp", + "releaseDate": "2023 April 23", + "artwork": "/assets/other/release artworks/whats-new-v2.0.0-stable.webp", "importantNotes": [ - "This release provides accessibility improvements throughout Nora." + "Installing this update would RESET the app to provide support for new features." ] }, "versions": [ + { + "version": "2.0.0-stable", + "artwork": "/assets/other/release artworks/whats-new-v2.0.0-stable.webp", + "releaseDate": "2023 April 23", + "importantNotes": [ + "Installing this update would RESET the app to provide support for new features." + ], + "notes": { + "new": [ + { + "note": "Added playback-only experimental support for audio formats like FLAC, AAC, and M4R." + }, + { + "note": "Added support for viewing storage usage by the app." + }, + { + "note": "Added experimental support for an improved folder structure." + }, + { + "note": "Added experimental support for suggestions for duplicate artists and artists identified as single artists." + }, + { + "note": "Added a new context option for the currently playing song artwork to the currently playing song's album." + }, + { + "note": "Added a new banner to the SongTagsEditingPage when trying to edit song formats that are supported for playback only." + }, + { + "note": "Added experimental support to generate a playlist cover automatically from the songs inside with the support to randomize the artworks as an additional feature." + }, + { + "note": "Added new options to configure the automatically generated playlist cover in the Preferences section of Settings." + }, + { + "note": "Added experimental support for an Audio Equalizer to the app." + }, + { + "note": "Added a new prompt for the user to customize chosen folders before parsing them." + }, + { + "note": "Added a new feature to reduce animations when the system is on battery power." + }, + { + "note": "Added the 'Generate Palettes' button to the About section of the Settings to generate palettes on demand." + }, + { + "note": "Added a new feature to change the playback speed of the player." + }, + { + "note": "Added a new button to the right side of the app's footer for advanced playback options." + }, + { + "note": "Added a new smooth scrolling feature to pages that directs users to specific parts of the page." + }, + { + "note": "Added a new title next to the artwork in the queue to show the queue type." + }, + { + "note": "Added support for responsive song cards in the Home." + }, + { + "note": "Add a new transition effect for some icons." + }, + { + "note": "Added support to toggle between predictive search and normal search." + }, + { + "note": "Added support for highlighting more than one lyrics line at a time." + }, + { + "note": "Added a new artwork filter for Deezer artist artworks to prevent showing artwork placeholders." + }, + { + "note": "Added a new auto-scrolling feature for the Queue page to scroll to the currently playing song on song skip." + }, + { + "note": "Added a new feature to show some info about the song to be played next in the currently playing song info container periodically." + } + ], + "fixed": [ + { + "note": "Reduced the parsing time of a newly created library by around 30%." + }, + { + "note": "Fixed a bug where the app theme will change when changing the system's theme even though the user didn't select to use the system theme in the app." + }, + { + "note": "Fixed a bug where the theme of the taskbar playback control buttons changed with the app theme instead of the system theme." + }, + { + "note": "Fixed a bug where users can't go to the same page with different data. For example, the user can't go to another artist's info page while staying on another artist's info page." + }, + { + "note": "Migrated the Music Folders section from Settings to the Music Folders page in the sidebar." + }, + { + "note": "Fixed a bug where Sidebar becomes cluttered in smaller resolutions." + }, + { + "note": "Updated some texts in the SongTagsEditingPage." + }, + { + "note": "Fixed a bug where users can save the same song tags again and again in the SongTagsEditingPage." + }, + { + "note": "Improved directory handling by the app." + }, + { + "note": "Reduced the brightness of background artwork." + }, + { + "note": "Fixed a bug where error messages and stack traces aren't being added to the log file." + }, + { + "note": "Fixed a bug where updating song id3 tags doesn't update album metadata." + }, + { + "note": "Fixed a bug where the app doesn't check for updates right after the connection was established." + }, + { + "note": "Fixed a bug where the focus state of a button persists even after the button is clicked." + }, + { + "note": "Fixed a bug where SongTagsEditingPage allows checking for song online results even though the app isn't connected to the internet." + }, + { + "note": "Fixed a bug where disabled buttons show a loading animation." + }, + { + "note": "Fixed a bug where clicking the recent search results and clicking another element to go to another page and come back doesn't persist the clicked recent search result in the search page." + }, + { + "note": "Fixed a bug where the user can't update the artwork of a playlist after creating it." + }, + { + "note": "Fixed a bug where images aren't being shown on the app after they were updated due to caching." + }, + { + "note": "Fixed a bug where the app could go into an infinite error loop if there were any playback errors that the app couldn't handle." + }, + { + "note": "Fixed a bug where removing a song from an album with only one song doesn't remove the album." + }, + { + "note": "Fixed some bugs related to sorting folders." + }, + { + "note": "Fixed flickering issues on some components when they were being updated." + }, + { + "note": "Fixed a bug where images show the alt text when hovered." + }, + { + "note": "Increased the font weight of the text in the sidebar." + }, + { + "note": "Fixed a bug where artist images shown next to the currently playing song cover aren't positioned correctly." + }, + { + "note": "Fixed a bug in Metadata Editing Page where the album cover is always the current song cover." + }, + { + "note": "Fixed a bug where selecting 'Add Selected' or 'Add All' when choosing song metadata results from the internet doesn't update the artists, albums, and genres I the editing page." + }, + { + "note": "Improved accessibility in the Song Metadata Editor." + }, + { "note": "Improved transitions in prompt menus." }, + { + "note": "Fixed a bug where closing the prompt menu will flicker the menu." + }, + { + "note": "Fixed a bug where clicking the Most Relevant Album doesn't open the relevant Album page." + }, + { + "note": "Fixed a bug where artist names on the Song Info page aren't positioned properly." + }, + { + "note": "Fixed some bugs related to customizing selected metadata prompt." + }, + { + "note": "Fixed a bug where hovering over seek bars show an incorrect value." + }, + { + "note": "Fixed a bug where the message 'No Synced Lyrics Found' persists after disabling the lyrics." + }, + { + "note": "Added support for selecting multiple items by Shift + Click and select all by clicking Ctrl + A." + }, + { + "note": "Fixed a bug where the loading element in a button isn't positioned correctly." + }, + { + "note": "Improved accessibility in SongTagsEditingPage." + }, + { + "note": "Fixed a bug where the F5 shortcut to reload doesn't work for other programs when Nora is opened." + }, + { + "note": "Fixed a bug where resetting the app doesn't clear local storage data." + }, + { + "note": "Reduced the space required to save listening data information by around 90%." + }, + { + "note": "Fixed a bug where resetting the app doesn't remove the data related to blacklists." + }, + { + "note": "Fixed a bug where Mini-player doesn't follow reduced motion." + }, + { + "note": "Fixed a bug where adding a song to play next to the last song of the queue will not be played." + }, + { + "note": "Fixed a bug where users can go to the same page repeatedly." + }, + { + "note": "Fixed a bug where clicking a button to go to a specific page twice would direct users to Home." + }, + { + "note": "Fixed a bug where folder modifications are not recognized in folder structures." + }, + { "note": "Updated some styles in prompts." }, + { + "note": "Updated some styles in the Release Notes prompt and open_source_licenses prompt." + }, + { + "note": "Reordered buttons in the About section of Settings." + }, + { + "note": "Fixed some security vulnerabilities in the app." + }, + { + "note": "Added no of songs in a folder when selecting folders to be added to the library." + }, + { + "note": "Fixed a bug where Img component try to fetch 404 requests repeatedly." + }, + { + "note": "Fixed a bug where users can download lyrics in the lyrics editor even though the app is not connected to the internet." + } + ], + "knownIssues": [] + } + }, { "version": "1.2.0-stable", "artwork": "/assets/other/release artworks/whats-new-v1.2.0-stable.webp", diff --git a/release/app/package-lock.json b/release/app/package-lock.json index 60ca2b29..c9bf0730 100644 --- a/release/app/package-lock.json +++ b/release/app/package-lock.json @@ -1,12 +1,12 @@ { "name": "nora", - "version": "2.0.0-stable.20230421", + "version": "2.0.0-stable", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "nora", - "version": "2.0.0-stable.20230421", + "version": "2.0.0-stable", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/release/app/package.json b/release/app/package.json index 26590066..cede679d 100644 --- a/release/app/package.json +++ b/release/app/package.json @@ -1,7 +1,7 @@ { "name": "nora", "productName": "Nora", - "version": "2.0.0-stable.20230421", + "version": "2.0.0-stable", "description": "An elegant music player built using Electron and React. Inspired by Oto Music for Android by Piyush Mamidwar.", "main": "./dist/main/main.js", "author": { diff --git a/release/package-lock.json b/release/package-lock.json index e5ed2746..ca8653d9 100644 --- a/release/package-lock.json +++ b/release/package-lock.json @@ -1,6 +1,6 @@ { "name": "nora", - "version": "2.0.0-stable.20230421", + "version": "2.0.0-stable", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/release/package.json b/release/package.json index 5feddb62..73ee0d87 100644 --- a/release/package.json +++ b/release/package.json @@ -1,7 +1,7 @@ { "name": "nora", "productName": "Nora", - "version": "2.0.0-stable.20230421", + "version": "2.0.0-stable", "description": "An elegant music player built using Electron and React. Inspired by Oto Music for Android by Piyush Mamidwar.", "main": "./dist/main/main.js", "author": { diff --git a/src/@types/app.d.ts b/src/@types/app.d.ts index 6807438f..ad3e04f2 100644 --- a/src/@types/app.d.ts +++ b/src/@types/app.d.ts @@ -1,5 +1,3 @@ -/* eslint-disable no-use-before-define */ -/* eslint-disable no-unused-vars */ import NodeID3 from 'node-id3'; import { ReactElement, ReactNode } from 'react'; import { ButtonProps } from 'renderer/components/Button'; @@ -402,6 +400,7 @@ declare global { interface FolderStructure extends MusicFolderData { subFolders: FolderStructure[]; + noOfSongs?: number; } interface MusicFolder extends FolderStructure { diff --git a/src/@types/apple_itunes_music_api.d.ts b/src/@types/apple_itunes_music_api.d.ts index 815e7eec..67e64aa1 100644 --- a/src/@types/apple_itunes_music_api.d.ts +++ b/src/@types/apple_itunes_music_api.d.ts @@ -1,5 +1,4 @@ /* eslint-disable no-shadow */ -/* eslint-disable no-unused-vars */ // Generated by https://quicktype.io export enum Explicitness { diff --git a/src/main/core/getFolderStructures.ts b/src/main/core/getFolderStructures.ts index 9a52c3a9..cd33144c 100644 --- a/src/main/core/getFolderStructures.ts +++ b/src/main/core/getFolderStructures.ts @@ -1,13 +1,28 @@ import fs from 'fs/promises'; +import path from 'path'; + import log from '../log'; -import { getDirectories } from '../filesystem'; +import { getDirectories, supportedMusicExtensions } from '../filesystem'; import { showOpenDialog } from '../main'; import getAllSettledPromises from '../utils/getAllSettledPromises'; +import { getAllFilePathsFromFolder } from '../fs/parseFolderStructuresForSongPaths'; + +const getSongPathsInAFolder = (folderPath: string) => { + const allFiles = getAllFilePathsFromFolder(folderPath); + + const allSongPaths = allFiles.filter((filePath) => { + const fileExtension = path.extname(filePath); + return supportedMusicExtensions.includes(fileExtension); + }); + + return allSongPaths; +}; export const generateFolderStructure = async (dir: string) => { try { const stats = await fs.stat(dir); - const props: FolderStructure = { + + const structure: FolderStructure = { path: dir, stats: { lastModifiedDate: stats.mtime, @@ -16,6 +31,7 @@ export const generateFolderStructure = async (dir: string) => { lastParsedDate: new Date(), }, subFolders: [], + noOfSongs: getSongPathsInAFolder(dir).length, }; const subDirs = await getDirectories(dir); @@ -27,9 +43,16 @@ export const generateFolderStructure = async (dir: string) => { subDirsStructurePromise ); - props.subFolders.push(...subDirsStructures); + structure.subFolders.push(...subDirsStructures); + + const subDirNoOfSongs = subDirsStructures + .map((x) => x.noOfSongs || 0) + .reduce((prevValue, currValue) => prevValue + currValue, 0); + + if (structure.noOfSongs) structure.noOfSongs += subDirNoOfSongs; + else structure.noOfSongs = subDirNoOfSongs; } - return props; + return structure; } catch (error) { log('Error occurred when analysing folder structure.', { error }, 'ERROR'); throw error; @@ -42,5 +65,6 @@ export const getFolderStructures = async () => { const { fulfilled: folderStructures } = await getAllSettledPromises( musicFolderPaths.map((folderPath) => generateFolderStructure(folderPath)) ); + return folderStructures; }; diff --git a/src/main/core/getMusicFolderData.ts b/src/main/core/getMusicFolderData.ts index ce07652a..ed640e2d 100644 --- a/src/main/core/getMusicFolderData.ts +++ b/src/main/core/getMusicFolderData.ts @@ -1,5 +1,6 @@ import sortFolders from '../utils/sortFolders'; -import { getBlacklistData, getSongsData, getUserData } from '../filesystem'; +import { getSongsData, getUserData } from '../filesystem'; +import { isFolderBlacklisted } from '../utils/isBlacklisted'; const getRelevantSongsofFolders = (folderPath: string) => { const songs = getSongsData(); @@ -19,8 +20,6 @@ const getRelevantSongsofFolders = (folderPath: string) => { const createFolderData = ( folderStructures: FolderStructure[] ): MusicFolder[] => { - const { folderBlacklist } = getBlacklistData(); - const foldersData: MusicFolder[] = []; for (const structure of folderStructures) { @@ -29,7 +28,7 @@ const createFolderData = ( ...structure, subFolders: createFolderData(structure.subFolders), songIds, - isBlacklisted: folderBlacklist.includes(structure.path), + isBlacklisted: isFolderBlacklisted(structure.path), }; if (structure.subFolders.length > 0) { diff --git a/src/main/core/getSongLyrics.ts b/src/main/core/getSongLyrics.ts index 024d3bdc..b19a91dc 100644 --- a/src/main/core/getSongLyrics.ts +++ b/src/main/core/getSongLyrics.ts @@ -1,5 +1,3 @@ -/* eslint-disable no-unused-vars */ -/* eslint-disable default-param-last */ import path from 'path'; import NodeID3 from 'node-id3'; import songlyrics from 'songlyrics'; diff --git a/src/main/core/restoreBlacklistedFolder.ts b/src/main/core/restoreBlacklistedFolder.ts index bd07d7dc..26fc5b79 100644 --- a/src/main/core/restoreBlacklistedFolder.ts +++ b/src/main/core/restoreBlacklistedFolder.ts @@ -1,10 +1,31 @@ -import { dataUpdateEvent } from '../main'; +import path from 'path'; + +import { dataUpdateEvent, sendMessageToRenderer } from '../main'; import { getBlacklistData, setBlacklist } from '../filesystem'; import log from '../log'; +import { isParentFolderBlacklisted } from '../utils/isBlacklisted'; const restoreBlacklistedFolders = async (blacklistedFolderPaths: string[]) => { const blacklist = getBlacklistData(); + for (const blacklistedFolderPath of blacklistedFolderPaths) { + const isParentBlacklisted = isParentFolderBlacklisted( + blacklistedFolderPath + ); + const isParentNotInFolderPaths = !blacklistedFolderPaths.includes( + path.dirname(blacklistedFolderPath) + ); + + if (isParentBlacklisted && isParentNotInFolderPaths) + sendMessageToRenderer( + `Couldn't whitelist '${path.basename( + blacklistedFolderPath + )}' folder because its parent folder '${path.basename( + path.dirname(blacklistedFolderPath) + )}' is also blacklisted. Whitelist the parent folder to whitelist this folder.` + ); + } + blacklist.folderBlacklist = blacklist.folderBlacklist.filter( (blacklistedFolderPath) => !blacklistedFolderPaths.includes(blacklistedFolderPath) diff --git a/src/main/fs/parseFolderStructuresForSongPaths.ts b/src/main/fs/parseFolderStructuresForSongPaths.ts index 017a0958..23b586c1 100644 --- a/src/main/fs/parseFolderStructuresForSongPaths.ts +++ b/src/main/fs/parseFolderStructuresForSongPaths.ts @@ -31,18 +31,32 @@ export const getAllFoldersFromFolderStructures = ( return folderData; }; -const getAllFilesFromFolderStructures = ( +export const getAllFilePathsFromFolder = (folderPath: string) => { + try { + const baseNames = fsSync.readdirSync(folderPath); + const filePaths = baseNames + .filter((baseName) => path.extname(baseName)) + .map((baseName) => path.join(folderPath, baseName)); + + return filePaths; + } catch (error) { + log( + `Error occurred when getting file paths from '${path.basename( + folderPath + )}' folder`, + { error }, + 'ERROR' + ); + return []; + } +}; + +export const getAllFilesFromFolderStructures = ( folderStructures: FolderStructure[] ) => { const allFolders = getAllFoldersFromFolderStructures(folderStructures); const allFiles = allFolders - .map((folder) => { - const baseNames = fsSync.readdirSync(folder.path); - const filePaths = baseNames - .filter((baseName) => path.extname(baseName)) - .map((baseName) => path.join(folder.path, baseName)); - return filePaths; - }) + .map((folder) => getAllFilePathsFromFolder(folder.path)) .flat(); return allFiles; diff --git a/src/main/main.ts b/src/main/main.ts index e55a4e14..04cf81f9 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -839,7 +839,6 @@ function addEventsToCache( function registerFileProtocol( request: { url: string }, - // eslint-disable-next-line no-unused-vars callback: (arg: string) => void ) { const urlWithQueries = decodeURI(request.url).replace( diff --git a/src/main/preload.ts b/src/main/preload.ts index 26015100..75b3792d 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -1,6 +1,3 @@ -/* eslint-disable no-unused-vars */ -/* eslint-disable import/no-cycle */ -/* eslint-disable import/prefer-default-export */ import { contextBridge, ipcRenderer } from 'electron'; import { LastFMTrackInfoApi } from '../@types/last_fm_api'; diff --git a/src/main/utils/isBlacklisted.ts b/src/main/utils/isBlacklisted.ts index c07fc74a..653101a5 100644 --- a/src/main/utils/isBlacklisted.ts +++ b/src/main/utils/isBlacklisted.ts @@ -1,10 +1,24 @@ import path from 'path'; import { getBlacklistData } from '../filesystem'; +export const isParentFolderBlacklisted = (folderPath: string) => { + const { folderBlacklist } = getBlacklistData(); + + const isParentBlacklisted = folderBlacklist.some( + (blacklistedFolderPath) => + path.dirname(folderPath) === blacklistedFolderPath + ); + + return isParentBlacklisted; +}; + export const isFolderBlacklisted = (folderPath: string) => { const { folderBlacklist } = getBlacklistData(); - return folderBlacklist.includes(path.normalize(folderPath)); + const isBlacklsited = folderBlacklist.includes(path.normalize(folderPath)); + const isParentBlacklisted = isParentFolderBlacklisted(folderPath); + + return isBlacklsited || isParentBlacklisted; }; export const isSongBlacklisted = (songId: string, songPath: string) => { diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 9941daa5..70ce5336 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -634,7 +634,6 @@ export default function App() { const fadeOutIntervalId = React.useRef(undefined as NodeJS.Timer | undefined); const fadeInIntervalId = React.useRef(undefined as NodeJS.Timer | undefined); const fadeOutAudio = React.useCallback(() => { - // console.log('volume on fade out', contentRef.current.volume); if (fadeInIntervalId.current) clearInterval(fadeInIntervalId.current); if (fadeOutIntervalId.current) clearInterval(fadeOutIntervalId.current); fadeOutIntervalId.current = setInterval(() => { @@ -652,7 +651,6 @@ export default function App() { }, []); const fadeInAudio = React.useCallback(() => { - // console.log('volume on fade in', contentRef.current.volume); if (fadeInIntervalId.current) clearInterval(fadeInIntervalId.current); if (fadeOutIntervalId.current) clearInterval(fadeOutIntervalId.current); fadeInIntervalId.current = setInterval(() => { @@ -887,7 +885,6 @@ export default function App() { const handleSkipForwardClickWithParams = () => handleSkipForwardClick('PLAYER_SKIP'); - // player.addEventListener('seeking', managePlayerNotStalledStatus); player.addEventListener('canplay', managePlayerNotStalledStatus); player.addEventListener('canplaythrough', managePlayerNotStalledStatus); player.addEventListener('loadeddata', managePlayerNotStalledStatus); @@ -926,7 +923,6 @@ export default function App() { return () => { toggleSongPlayback(false); clearInterval(intervalId); - // player.removeEventListener('seeking', managePlayerNotStalledStatus); player.removeEventListener('canplay', managePlayerNotStalledStatus); player.removeEventListener( 'canplaythrough', @@ -1107,7 +1103,6 @@ export default function App() { const updateNotifications = React.useCallback( ( - // eslint-disable-next-line no-unused-vars callback: (currentNotifications: AppNotification[]) => AppNotification[] ) => { const currentNotifications = content.notificationPanelData.notifications; @@ -1251,7 +1246,6 @@ export default function App() { ); - // info.delay = 60000; } if ( (messageCode === 'SONG_REMOVE_PROCESS_UPDATE' || @@ -1857,7 +1851,6 @@ export default function App() { pageY?: number, contextMenuData?: ContextMenuAdditionalData ) => { - // console.log('pageX', pageX, 'pageY', pageY); dispatch({ type: 'CONTEXT_MENU_DATA_CHANGE', data: { diff --git a/src/renderer/components/Button.tsx b/src/renderer/components/Button.tsx index 9c36b87a..4bdb0da9 100644 --- a/src/renderer/components/Button.tsx +++ b/src/renderer/components/Button.tsx @@ -1,4 +1,3 @@ -/* eslint-disable no-unused-vars */ import React from 'react'; export interface ButtonProps { diff --git a/src/renderer/components/Hyperlink.tsx b/src/renderer/components/Hyperlink.tsx index 893fd7e7..96e77ad6 100644 --- a/src/renderer/components/Hyperlink.tsx +++ b/src/renderer/components/Hyperlink.tsx @@ -37,7 +37,6 @@ const Hyperlink = (props: HyperlinkProp) => { ]); return ( - // eslint-disable-next-line jsx-a11y/click-events-have-key-events { } = props; const imgPropsRef = React.useRef(); + const errorCountRef = React.useRef(0); return ( { alt={alt} className={`outline-1 outline-offset-4 focus-visible:!outline ${className}`} onError={(e) => { - if (!noFallbacks && e.currentTarget.src !== fallbackSrc) - e.currentTarget.src = fallbackSrc; - else e.currentTarget.src = DefaultImage; + if (errorCountRef.current < 3) { + errorCountRef.current += 1; + + if (!noFallbacks && e.currentTarget.src !== fallbackSrc) + e.currentTarget.src = fallbackSrc; + else e.currentTarget.src = DefaultImage; + } else + window.api.sendLogs('maximum img fetch error count reached.', 'warn'); }} onClick={onClick} title={ diff --git a/src/renderer/components/LyricsPage/LyricsPage.tsx b/src/renderer/components/LyricsPage/LyricsPage.tsx index f789374b..bd229d71 100644 --- a/src/renderer/components/LyricsPage/LyricsPage.tsx +++ b/src/renderer/components/LyricsPage/LyricsPage.tsx @@ -1,7 +1,5 @@ -/* eslint-disable no-unused-vars */ /* eslint-disable react/jsx-no-useless-fragment */ /* eslint-disable react/no-array-index-key */ -/* eslint-disable promise/catch-or-return */ import React, { useContext } from 'react'; import debounce from 'renderer/utils/debounce'; import { AppUpdateContext } from 'renderer/contexts/AppUpdateContext'; @@ -71,7 +69,8 @@ export const LyricsPage = () => { songPath: currentSongData.path, duration: currentSongData.duration, }) - .then((res) => setLyrics(res)); + .then((res) => setLyrics(res)) + .catch((err) => console.error(err)); }, [ addNewNotifications, currentSongData.artists, @@ -140,7 +139,8 @@ export const LyricsPage = () => { .finally(() => { setIsDisabled(false); setIsPending(false); - }); + }) + .catch((err) => console.error(err)); }, [ currentSongData.artists, @@ -172,7 +172,8 @@ export const LyricsPage = () => { 'OFFLINE_ONLY' ) .then((res) => setLyrics(res)) - .finally(() => setIsDisabled(false)); + .finally(() => setIsDisabled(false)) + .catch((err) => console.error(err)); }, [ currentSongData.artists, @@ -221,7 +222,8 @@ export const LyricsPage = () => { .finally(() => { setIsPending(false); setIsDisabled(false); - }); + }) + .catch((err) => console.error(err)); } }, [addNewNotifications, currentSongData.path, lyrics] @@ -244,7 +246,8 @@ export const LyricsPage = () => { 'ONLINE_ONLY' ) .then((res) => setLyrics(res)) - .finally(() => setIsDisabled(false)); + .finally(() => setIsDisabled(false)) + .catch((err) => console.error(err)); }, [ currentSongData.artists, diff --git a/src/renderer/components/MiniPlayer/MiniPlayer.tsx b/src/renderer/components/MiniPlayer/MiniPlayer.tsx index 264bd598..838ffdc4 100644 --- a/src/renderer/components/MiniPlayer/MiniPlayer.tsx +++ b/src/renderer/components/MiniPlayer/MiniPlayer.tsx @@ -254,7 +254,7 @@ export default function MiniPlayer(props: MiniPlayerProps) { >