5
.github/workflows/docker-build.yml
vendored
5
.github/workflows/docker-build.yml
vendored
@@ -40,6 +40,11 @@ jobs:
|
|||||||
# Keep dev tag for main/master branch
|
# Keep dev tag for main/master branch
|
||||||
type=raw,value=dev,enable=${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' }}
|
type=raw,value=dev,enable=${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' }}
|
||||||
|
|
||||||
|
- name: Set version in config.html
|
||||||
|
run: |
|
||||||
|
VERSION=$(echo "${{ steps.meta.outputs.version }}" | sed 's/^v//')
|
||||||
|
sed -i "s|Set on build|Version: $VERSION|g" static/html/config.html
|
||||||
|
|
||||||
# Build and push Docker image with multiarch support
|
# Build and push Docker image with multiarch support
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v4
|
||||||
|
|||||||
@@ -1,61 +1,5 @@
|
|||||||
amqp==5.3.1
|
|
||||||
annotated-types==0.7.0
|
|
||||||
anyio==4.9.0
|
|
||||||
billiard==4.2.1
|
|
||||||
blinker==1.9.0
|
|
||||||
celery==5.5.2
|
|
||||||
certifi==2025.4.26
|
|
||||||
charset-normalizer==3.4.2
|
|
||||||
click==8.2.1
|
|
||||||
click-didyoumean==0.3.1
|
|
||||||
click-plugins==1.1.1
|
|
||||||
click-repl==0.3.0
|
|
||||||
deezspot @ git+https://github.com/Xoconoch/deezspot-fork-again
|
|
||||||
defusedxml==0.7.1
|
|
||||||
fastapi==0.115.12
|
|
||||||
Flask==3.1.1
|
|
||||||
Flask-Celery-Helper==1.1.0
|
|
||||||
flask-cors==6.0.0
|
|
||||||
h11==0.16.0
|
|
||||||
httptools==0.6.4
|
|
||||||
idna==3.10
|
|
||||||
ifaddr==0.2.0
|
|
||||||
itsdangerous==2.2.0
|
|
||||||
Jinja2==3.1.6
|
|
||||||
kombu==5.5.3
|
|
||||||
librespot==0.0.9
|
|
||||||
MarkupSafe==3.0.2
|
|
||||||
mutagen==1.47.0
|
|
||||||
prompt_toolkit==3.0.51
|
|
||||||
protobuf==3.20.1
|
|
||||||
pycryptodome==3.23.0
|
|
||||||
pycryptodomex==3.17
|
|
||||||
pydantic==2.11.5
|
|
||||||
pydantic_core==2.33.2
|
|
||||||
PyOgg==0.6.14a1
|
|
||||||
python-dateutil==2.9.0.post0
|
|
||||||
python-dotenv==1.1.0
|
|
||||||
PyYAML==6.0.2
|
|
||||||
redis==6.2.0
|
|
||||||
requests==2.30.0
|
|
||||||
six==1.17.0
|
|
||||||
sniffio==1.3.1
|
|
||||||
spotipy==2.25.1
|
|
||||||
spotipy_anon==1.4
|
|
||||||
sse-starlette==2.3.5
|
|
||||||
starlette==0.46.2
|
|
||||||
tqdm==4.67.1
|
|
||||||
typing-inspection==0.4.1
|
|
||||||
typing_extensions==4.13.2
|
|
||||||
tzdata==2025.2
|
|
||||||
urllib3==2.4.0
|
|
||||||
uvicorn==0.34.2
|
|
||||||
uvloop==0.21.0
|
|
||||||
vine==5.1.0
|
|
||||||
waitress==3.0.2
|
waitress==3.0.2
|
||||||
watchfiles==1.0.5
|
celery==5.5.3
|
||||||
wcwidth==0.2.13
|
Flask==3.1.1
|
||||||
websocket-client==1.5.1
|
flask_cors==6.0.0
|
||||||
websockets==15.0.1
|
deezspot-spotizerr==1.4.0
|
||||||
Werkzeug==3.1.3
|
|
||||||
zeroconf==0.62.0
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from routes.utils.watch.db import (
|
|||||||
remove_specific_albums_from_artist_table,
|
remove_specific_albums_from_artist_table,
|
||||||
is_album_in_artist_db
|
is_album_in_artist_db
|
||||||
)
|
)
|
||||||
from routes.utils.watch.manager import check_watched_artists
|
from routes.utils.watch.manager import check_watched_artists, get_watch_config
|
||||||
from routes.utils.get_info import get_spotify_info
|
from routes.utils.get_info import get_spotify_info
|
||||||
|
|
||||||
artist_bp = Blueprint('artist', __name__, url_prefix='/api/artist')
|
artist_bp = Blueprint('artist', __name__, url_prefix='/api/artist')
|
||||||
@@ -159,6 +159,10 @@ def get_artist_info():
|
|||||||
@artist_bp.route('/watch/<string:artist_spotify_id>', methods=['PUT'])
|
@artist_bp.route('/watch/<string:artist_spotify_id>', methods=['PUT'])
|
||||||
def add_artist_to_watchlist(artist_spotify_id):
|
def add_artist_to_watchlist(artist_spotify_id):
|
||||||
"""Adds an artist to the watchlist."""
|
"""Adds an artist to the watchlist."""
|
||||||
|
watch_config = get_watch_config()
|
||||||
|
if not watch_config.get("enabled", False):
|
||||||
|
return jsonify({"error": "Watch feature is currently disabled globally."}), 403
|
||||||
|
|
||||||
logger.info(f"Attempting to add artist {artist_spotify_id} to watchlist.")
|
logger.info(f"Attempting to add artist {artist_spotify_id} to watchlist.")
|
||||||
try:
|
try:
|
||||||
if get_watched_artist(artist_spotify_id):
|
if get_watched_artist(artist_spotify_id):
|
||||||
@@ -224,6 +228,10 @@ def get_artist_watch_status(artist_spotify_id):
|
|||||||
@artist_bp.route('/watch/<string:artist_spotify_id>', methods=['DELETE'])
|
@artist_bp.route('/watch/<string:artist_spotify_id>', methods=['DELETE'])
|
||||||
def remove_artist_from_watchlist(artist_spotify_id):
|
def remove_artist_from_watchlist(artist_spotify_id):
|
||||||
"""Removes an artist from the watchlist."""
|
"""Removes an artist from the watchlist."""
|
||||||
|
watch_config = get_watch_config()
|
||||||
|
if not watch_config.get("enabled", False):
|
||||||
|
return jsonify({"error": "Watch feature is currently disabled globally."}), 403
|
||||||
|
|
||||||
logger.info(f"Attempting to remove artist {artist_spotify_id} from watchlist.")
|
logger.info(f"Attempting to remove artist {artist_spotify_id} from watchlist.")
|
||||||
try:
|
try:
|
||||||
if not get_watched_artist(artist_spotify_id):
|
if not get_watched_artist(artist_spotify_id):
|
||||||
@@ -249,6 +257,10 @@ def list_watched_artists_endpoint():
|
|||||||
@artist_bp.route('/watch/trigger_check', methods=['POST'])
|
@artist_bp.route('/watch/trigger_check', methods=['POST'])
|
||||||
def trigger_artist_check_endpoint():
|
def trigger_artist_check_endpoint():
|
||||||
"""Manually triggers the artist checking mechanism for all watched artists."""
|
"""Manually triggers the artist checking mechanism for all watched artists."""
|
||||||
|
watch_config = get_watch_config()
|
||||||
|
if not watch_config.get("enabled", False):
|
||||||
|
return jsonify({"error": "Watch feature is currently disabled globally. Cannot trigger check."}), 403
|
||||||
|
|
||||||
logger.info("Manual trigger for artist check received for all artists.")
|
logger.info("Manual trigger for artist check received for all artists.")
|
||||||
try:
|
try:
|
||||||
thread = threading.Thread(target=check_watched_artists, args=(None,))
|
thread = threading.Thread(target=check_watched_artists, args=(None,))
|
||||||
@@ -261,6 +273,10 @@ def trigger_artist_check_endpoint():
|
|||||||
@artist_bp.route('/watch/trigger_check/<string:artist_spotify_id>', methods=['POST'])
|
@artist_bp.route('/watch/trigger_check/<string:artist_spotify_id>', methods=['POST'])
|
||||||
def trigger_specific_artist_check_endpoint(artist_spotify_id: str):
|
def trigger_specific_artist_check_endpoint(artist_spotify_id: str):
|
||||||
"""Manually triggers the artist checking mechanism for a specific artist."""
|
"""Manually triggers the artist checking mechanism for a specific artist."""
|
||||||
|
watch_config = get_watch_config()
|
||||||
|
if not watch_config.get("enabled", False):
|
||||||
|
return jsonify({"error": "Watch feature is currently disabled globally. Cannot trigger check."}), 403
|
||||||
|
|
||||||
logger.info(f"Manual trigger for specific artist check received for ID: {artist_spotify_id}")
|
logger.info(f"Manual trigger for specific artist check received for ID: {artist_spotify_id}")
|
||||||
try:
|
try:
|
||||||
watched_artist = get_watched_artist(artist_spotify_id)
|
watched_artist = get_watched_artist(artist_spotify_id)
|
||||||
@@ -279,6 +295,10 @@ def trigger_specific_artist_check_endpoint(artist_spotify_id: str):
|
|||||||
@artist_bp.route('/watch/<string:artist_spotify_id>/albums', methods=['POST'])
|
@artist_bp.route('/watch/<string:artist_spotify_id>/albums', methods=['POST'])
|
||||||
def mark_albums_as_known_for_artist(artist_spotify_id):
|
def mark_albums_as_known_for_artist(artist_spotify_id):
|
||||||
"""Fetches details for given album IDs and adds/updates them in the artist's local DB table."""
|
"""Fetches details for given album IDs and adds/updates them in the artist's local DB table."""
|
||||||
|
watch_config = get_watch_config()
|
||||||
|
if not watch_config.get("enabled", False):
|
||||||
|
return jsonify({"error": "Watch feature is currently disabled globally. Cannot mark albums."}), 403
|
||||||
|
|
||||||
logger.info(f"Attempting to mark albums as known for artist {artist_spotify_id}.")
|
logger.info(f"Attempting to mark albums as known for artist {artist_spotify_id}.")
|
||||||
try:
|
try:
|
||||||
album_ids = request.json
|
album_ids = request.json
|
||||||
@@ -313,6 +333,10 @@ def mark_albums_as_known_for_artist(artist_spotify_id):
|
|||||||
@artist_bp.route('/watch/<string:artist_spotify_id>/albums', methods=['DELETE'])
|
@artist_bp.route('/watch/<string:artist_spotify_id>/albums', methods=['DELETE'])
|
||||||
def mark_albums_as_missing_locally_for_artist(artist_spotify_id):
|
def mark_albums_as_missing_locally_for_artist(artist_spotify_id):
|
||||||
"""Removes specified albums from the artist's local DB table."""
|
"""Removes specified albums from the artist's local DB table."""
|
||||||
|
watch_config = get_watch_config()
|
||||||
|
if not watch_config.get("enabled", False):
|
||||||
|
return jsonify({"error": "Watch feature is currently disabled globally. Cannot mark albums."}), 403
|
||||||
|
|
||||||
logger.info(f"Attempting to mark albums as missing (delete locally) for artist {artist_spotify_id}.")
|
logger.info(f"Attempting to mark albums as missing (delete locally) for artist {artist_spotify_id}.")
|
||||||
try:
|
try:
|
||||||
album_ids = request.json
|
album_ids = request.json
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from routes.utils.watch.db import (
|
|||||||
is_track_in_playlist_db # Added import
|
is_track_in_playlist_db # Added import
|
||||||
)
|
)
|
||||||
from routes.utils.get_info import get_spotify_info # Already used, but ensure it's here
|
from routes.utils.get_info import get_spotify_info # Already used, but ensure it's here
|
||||||
from routes.utils.watch.manager import check_watched_playlists # For manual trigger
|
from routes.utils.watch.manager import check_watched_playlists, get_watch_config # For manual trigger & config
|
||||||
|
|
||||||
logger = logging.getLogger(__name__) # Added logger initialization
|
logger = logging.getLogger(__name__) # Added logger initialization
|
||||||
playlist_bp = Blueprint('playlist', __name__, url_prefix='/api/playlist')
|
playlist_bp = Blueprint('playlist', __name__, url_prefix='/api/playlist')
|
||||||
@@ -180,6 +180,10 @@ def get_playlist_info():
|
|||||||
@playlist_bp.route('/watch/<string:playlist_spotify_id>', methods=['PUT'])
|
@playlist_bp.route('/watch/<string:playlist_spotify_id>', methods=['PUT'])
|
||||||
def add_to_watchlist(playlist_spotify_id):
|
def add_to_watchlist(playlist_spotify_id):
|
||||||
"""Adds a playlist to the watchlist."""
|
"""Adds a playlist to the watchlist."""
|
||||||
|
watch_config = get_watch_config()
|
||||||
|
if not watch_config.get("enabled", False):
|
||||||
|
return jsonify({"error": "Watch feature is currently disabled globally."}), 403
|
||||||
|
|
||||||
logger.info(f"Attempting to add playlist {playlist_spotify_id} to watchlist.")
|
logger.info(f"Attempting to add playlist {playlist_spotify_id} to watchlist.")
|
||||||
try:
|
try:
|
||||||
# Check if already watched
|
# Check if already watched
|
||||||
@@ -227,6 +231,10 @@ def get_playlist_watch_status(playlist_spotify_id):
|
|||||||
@playlist_bp.route('/watch/<string:playlist_spotify_id>', methods=['DELETE'])
|
@playlist_bp.route('/watch/<string:playlist_spotify_id>', methods=['DELETE'])
|
||||||
def remove_from_watchlist(playlist_spotify_id):
|
def remove_from_watchlist(playlist_spotify_id):
|
||||||
"""Removes a playlist from the watchlist."""
|
"""Removes a playlist from the watchlist."""
|
||||||
|
watch_config = get_watch_config()
|
||||||
|
if not watch_config.get("enabled", False):
|
||||||
|
return jsonify({"error": "Watch feature is currently disabled globally."}), 403
|
||||||
|
|
||||||
logger.info(f"Attempting to remove playlist {playlist_spotify_id} from watchlist.")
|
logger.info(f"Attempting to remove playlist {playlist_spotify_id} from watchlist.")
|
||||||
try:
|
try:
|
||||||
if not get_watched_playlist(playlist_spotify_id):
|
if not get_watched_playlist(playlist_spotify_id):
|
||||||
@@ -242,6 +250,10 @@ def remove_from_watchlist(playlist_spotify_id):
|
|||||||
@playlist_bp.route('/watch/<string:playlist_spotify_id>/tracks', methods=['POST'])
|
@playlist_bp.route('/watch/<string:playlist_spotify_id>/tracks', methods=['POST'])
|
||||||
def mark_tracks_as_known(playlist_spotify_id):
|
def mark_tracks_as_known(playlist_spotify_id):
|
||||||
"""Fetches details for given track IDs and adds/updates them in the playlist's local DB table."""
|
"""Fetches details for given track IDs and adds/updates them in the playlist's local DB table."""
|
||||||
|
watch_config = get_watch_config()
|
||||||
|
if not watch_config.get("enabled", False):
|
||||||
|
return jsonify({"error": "Watch feature is currently disabled globally. Cannot mark tracks."}), 403
|
||||||
|
|
||||||
logger.info(f"Attempting to mark tracks as known for playlist {playlist_spotify_id}.")
|
logger.info(f"Attempting to mark tracks as known for playlist {playlist_spotify_id}.")
|
||||||
try:
|
try:
|
||||||
track_ids = request.json
|
track_ids = request.json
|
||||||
@@ -275,7 +287,11 @@ def mark_tracks_as_known(playlist_spotify_id):
|
|||||||
@playlist_bp.route('/watch/<string:playlist_spotify_id>/tracks', methods=['DELETE'])
|
@playlist_bp.route('/watch/<string:playlist_spotify_id>/tracks', methods=['DELETE'])
|
||||||
def mark_tracks_as_missing_locally(playlist_spotify_id):
|
def mark_tracks_as_missing_locally(playlist_spotify_id):
|
||||||
"""Removes specified tracks from the playlist's local DB table."""
|
"""Removes specified tracks from the playlist's local DB table."""
|
||||||
logger.info(f"Attempting to mark tracks as missing (delete locally) for playlist {playlist_spotify_id}.")
|
watch_config = get_watch_config()
|
||||||
|
if not watch_config.get("enabled", False):
|
||||||
|
return jsonify({"error": "Watch feature is currently disabled globally. Cannot mark tracks."}), 403
|
||||||
|
|
||||||
|
logger.info(f"Attempting to mark tracks as missing (remove locally) for playlist {playlist_spotify_id}.")
|
||||||
try:
|
try:
|
||||||
track_ids = request.json
|
track_ids = request.json
|
||||||
if not isinstance(track_ids, list) or not all(isinstance(tid, str) for tid in track_ids):
|
if not isinstance(track_ids, list) or not all(isinstance(tid, str) for tid in track_ids):
|
||||||
@@ -304,6 +320,10 @@ def list_watched_playlists_endpoint():
|
|||||||
@playlist_bp.route('/watch/trigger_check', methods=['POST'])
|
@playlist_bp.route('/watch/trigger_check', methods=['POST'])
|
||||||
def trigger_playlist_check_endpoint():
|
def trigger_playlist_check_endpoint():
|
||||||
"""Manually triggers the playlist checking mechanism for all watched playlists."""
|
"""Manually triggers the playlist checking mechanism for all watched playlists."""
|
||||||
|
watch_config = get_watch_config()
|
||||||
|
if not watch_config.get("enabled", False):
|
||||||
|
return jsonify({"error": "Watch feature is currently disabled globally. Cannot trigger check."}), 403
|
||||||
|
|
||||||
logger.info("Manual trigger for playlist check received for all playlists.")
|
logger.info("Manual trigger for playlist check received for all playlists.")
|
||||||
try:
|
try:
|
||||||
# Run check_watched_playlists without an ID to check all
|
# Run check_watched_playlists without an ID to check all
|
||||||
@@ -317,6 +337,10 @@ def trigger_playlist_check_endpoint():
|
|||||||
@playlist_bp.route('/watch/trigger_check/<string:playlist_spotify_id>', methods=['POST'])
|
@playlist_bp.route('/watch/trigger_check/<string:playlist_spotify_id>', methods=['POST'])
|
||||||
def trigger_specific_playlist_check_endpoint(playlist_spotify_id: str):
|
def trigger_specific_playlist_check_endpoint(playlist_spotify_id: str):
|
||||||
"""Manually triggers the playlist checking mechanism for a specific playlist."""
|
"""Manually triggers the playlist checking mechanism for a specific playlist."""
|
||||||
|
watch_config = get_watch_config()
|
||||||
|
if not watch_config.get("enabled", False):
|
||||||
|
return jsonify({"error": "Watch feature is currently disabled globally. Cannot trigger check."}), 403
|
||||||
|
|
||||||
logger.info(f"Manual trigger for specific playlist check received for ID: {playlist_spotify_id}")
|
logger.info(f"Manual trigger for specific playlist check received for ID: {playlist_spotify_id}")
|
||||||
try:
|
try:
|
||||||
# Check if the playlist is actually in the watchlist first
|
# Check if the playlist is actually in the watchlist first
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ def download_album(
|
|||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
not_interface=False,
|
not_interface=False,
|
||||||
make_zip=False,
|
make_zip=False,
|
||||||
method_save=1,
|
|
||||||
custom_dir_format=custom_dir_format,
|
custom_dir_format=custom_dir_format,
|
||||||
custom_track_format=custom_track_format,
|
custom_track_format=custom_track_format,
|
||||||
pad_tracks=pad_tracks,
|
pad_tracks=pad_tracks,
|
||||||
@@ -151,7 +150,6 @@ def download_album(
|
|||||||
recursive_quality=True,
|
recursive_quality=True,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
not_interface=False,
|
not_interface=False,
|
||||||
method_save=1,
|
|
||||||
make_zip=False,
|
make_zip=False,
|
||||||
real_time_dl=real_time,
|
real_time_dl=real_time,
|
||||||
custom_dir_format=custom_dir_format,
|
custom_dir_format=custom_dir_format,
|
||||||
@@ -192,7 +190,6 @@ def download_album(
|
|||||||
recursive_quality=True,
|
recursive_quality=True,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
not_interface=False,
|
not_interface=False,
|
||||||
method_save=1,
|
|
||||||
make_zip=False,
|
make_zip=False,
|
||||||
real_time_dl=real_time,
|
real_time_dl=real_time,
|
||||||
custom_dir_format=custom_dir_format,
|
custom_dir_format=custom_dir_format,
|
||||||
@@ -228,7 +225,6 @@ def download_album(
|
|||||||
quality_download=quality,
|
quality_download=quality,
|
||||||
recursive_quality=True,
|
recursive_quality=True,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
method_save=1,
|
|
||||||
make_zip=False,
|
make_zip=False,
|
||||||
custom_dir_format=custom_dir_format,
|
custom_dir_format=custom_dir_format,
|
||||||
custom_track_format=custom_track_format,
|
custom_track_format=custom_track_format,
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ def download_artist_albums(url, album_type="album,single,compilation", request_a
|
|||||||
raise ValueError(error_msg)
|
raise ValueError(error_msg)
|
||||||
|
|
||||||
# Get artist info with albums
|
# Get artist info with albums
|
||||||
artist_data = get_spotify_info(artist_id, "artist")
|
artist_data = get_spotify_info(artist_id, "artist_discography")
|
||||||
|
|
||||||
# Debug logging to inspect the structure of artist_data
|
# Debug logging to inspect the structure of artist_data
|
||||||
logger.debug(f"Artist data structure has keys: {list(artist_data.keys() if isinstance(artist_data, dict) else [])}")
|
logger.debug(f"Artist data structure has keys: {list(artist_data.keys() if isinstance(artist_data, dict) else [])}")
|
||||||
@@ -153,11 +153,13 @@ def download_artist_albums(url, album_type="album,single,compilation", request_a
|
|||||||
album_name = album.get('name', 'Unknown Album')
|
album_name = album.get('name', 'Unknown Album')
|
||||||
album_artists = album.get('artists', [])
|
album_artists = album.get('artists', [])
|
||||||
album_artist = album_artists[0].get('name', 'Unknown Artist') if album_artists else 'Unknown Artist'
|
album_artist = album_artists[0].get('name', 'Unknown Artist') if album_artists else 'Unknown Artist'
|
||||||
|
album_id = album.get('id')
|
||||||
|
|
||||||
logger.debug(f"Extracted album URL: {album_url}")
|
logger.debug(f"Extracted album URL: {album_url}")
|
||||||
|
logger.debug(f"Extracted album ID: {album_id}")
|
||||||
if not album_url:
|
|
||||||
logger.warning(f"Skipping album without URL: {album_name}")
|
if not album_url or not album_id:
|
||||||
|
logger.warning(f"Skipping album without URL or ID: {album_name}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Create album-specific request args instead of using original artist request
|
# Create album-specific request args instead of using original artist request
|
||||||
@@ -172,7 +174,7 @@ def download_artist_albums(url, album_type="album,single,compilation", request_a
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Include original download URL for this album task
|
# Include original download URL for this album task
|
||||||
album_request_args["original_url"] = url_for('album.handle_download', url=album_url, _external=True)
|
album_request_args["original_url"] = url_for('album.handle_download', album_id=album_id, _external=True)
|
||||||
|
|
||||||
# Create task for this album
|
# Create task for this album
|
||||||
task_data = {
|
task_data = {
|
||||||
|
|||||||
@@ -106,7 +106,6 @@ def download_playlist(
|
|||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
not_interface=False,
|
not_interface=False,
|
||||||
make_zip=False,
|
make_zip=False,
|
||||||
method_save=1,
|
|
||||||
custom_dir_format=custom_dir_format,
|
custom_dir_format=custom_dir_format,
|
||||||
custom_track_format=custom_track_format,
|
custom_track_format=custom_track_format,
|
||||||
pad_tracks=pad_tracks,
|
pad_tracks=pad_tracks,
|
||||||
@@ -146,7 +145,6 @@ def download_playlist(
|
|||||||
recursive_quality=True,
|
recursive_quality=True,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
not_interface=False,
|
not_interface=False,
|
||||||
method_save=1,
|
|
||||||
make_zip=False,
|
make_zip=False,
|
||||||
real_time_dl=real_time,
|
real_time_dl=real_time,
|
||||||
custom_dir_format=custom_dir_format,
|
custom_dir_format=custom_dir_format,
|
||||||
@@ -187,7 +185,6 @@ def download_playlist(
|
|||||||
recursive_quality=True,
|
recursive_quality=True,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
not_interface=False,
|
not_interface=False,
|
||||||
method_save=1,
|
|
||||||
make_zip=False,
|
make_zip=False,
|
||||||
real_time_dl=real_time,
|
real_time_dl=real_time,
|
||||||
custom_dir_format=custom_dir_format,
|
custom_dir_format=custom_dir_format,
|
||||||
@@ -223,7 +220,6 @@ def download_playlist(
|
|||||||
quality_download=quality,
|
quality_download=quality,
|
||||||
recursive_quality=False,
|
recursive_quality=False,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
method_save=1,
|
|
||||||
make_zip=False,
|
make_zip=False,
|
||||||
custom_dir_format=custom_dir_format,
|
custom_dir_format=custom_dir_format,
|
||||||
custom_track_format=custom_track_format,
|
custom_track_format=custom_track_format,
|
||||||
|
|||||||
@@ -106,7 +106,6 @@ def download_track(
|
|||||||
recursive_quality=False,
|
recursive_quality=False,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
not_interface=False,
|
not_interface=False,
|
||||||
method_save=1,
|
|
||||||
custom_dir_format=custom_dir_format,
|
custom_dir_format=custom_dir_format,
|
||||||
custom_track_format=custom_track_format,
|
custom_track_format=custom_track_format,
|
||||||
initial_retry_delay=initial_retry_delay,
|
initial_retry_delay=initial_retry_delay,
|
||||||
@@ -140,7 +139,6 @@ def download_track(
|
|||||||
recursive_quality=False,
|
recursive_quality=False,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
not_interface=False,
|
not_interface=False,
|
||||||
method_save=1,
|
|
||||||
real_time_dl=real_time,
|
real_time_dl=real_time,
|
||||||
custom_dir_format=custom_dir_format,
|
custom_dir_format=custom_dir_format,
|
||||||
custom_track_format=custom_track_format,
|
custom_track_format=custom_track_format,
|
||||||
@@ -174,7 +172,6 @@ def download_track(
|
|||||||
recursive_quality=False,
|
recursive_quality=False,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
not_interface=False,
|
not_interface=False,
|
||||||
method_save=1,
|
|
||||||
real_time_dl=real_time,
|
real_time_dl=real_time,
|
||||||
custom_dir_format=custom_dir_format,
|
custom_dir_format=custom_dir_format,
|
||||||
custom_track_format=custom_track_format,
|
custom_track_format=custom_track_format,
|
||||||
@@ -204,7 +201,6 @@ def download_track(
|
|||||||
quality_download=quality,
|
quality_download=quality,
|
||||||
recursive_quality=False,
|
recursive_quality=False,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
method_save=1,
|
|
||||||
custom_dir_format=custom_dir_format,
|
custom_dir_format=custom_dir_format,
|
||||||
custom_track_format=custom_track_format,
|
custom_track_format=custom_track_format,
|
||||||
pad_tracks=pad_tracks,
|
pad_tracks=pad_tracks,
|
||||||
|
|||||||
@@ -73,6 +73,42 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
downloadQueue.toggleVisibility();
|
downloadQueue.toggleVisibility();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attempt to set initial watchlist button visibility from cache
|
||||||
|
const watchlistButton = document.getElementById('watchlistButton') as HTMLAnchorElement | null;
|
||||||
|
if (watchlistButton) {
|
||||||
|
const cachedWatchEnabled = localStorage.getItem('spotizerr_watch_enabled_cached');
|
||||||
|
if (cachedWatchEnabled === 'true') {
|
||||||
|
watchlistButton.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch watch config to determine if watchlist button should be visible
|
||||||
|
async function updateWatchlistButtonVisibility() {
|
||||||
|
if (watchlistButton) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/config/watch');
|
||||||
|
if (response.ok) {
|
||||||
|
const watchConfig = await response.json();
|
||||||
|
localStorage.setItem('spotizerr_watch_enabled_cached', watchConfig.enabled ? 'true' : 'false');
|
||||||
|
if (watchConfig && watchConfig.enabled === false) {
|
||||||
|
watchlistButton.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
watchlistButton.classList.remove('hidden'); // Ensure it's shown if enabled
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Failed to fetch watch config, defaulting to hidden');
|
||||||
|
// Don't update cache on error
|
||||||
|
watchlistButton.classList.add('hidden'); // Hide if config fetch fails
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching watch config:', error);
|
||||||
|
// Don't update cache on error
|
||||||
|
watchlistButton.classList.add('hidden'); // Hide on error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateWatchlistButtonVisibility();
|
||||||
});
|
});
|
||||||
|
|
||||||
function renderAlbum(album: Album) {
|
function renderAlbum(album: Album) {
|
||||||
|
|||||||
123
src/js/artist.ts
123
src/js/artist.ts
@@ -46,7 +46,28 @@ interface WatchStatusResponse {
|
|||||||
artist_data?: any; // The artist data from DB if watched
|
artist_data?: any; // The artist data from DB if watched
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
// Added: Interface for global watch config
|
||||||
|
interface GlobalWatchConfig {
|
||||||
|
enabled: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Added: Helper function to fetch global watch config
|
||||||
|
async function getGlobalWatchConfig(): Promise<GlobalWatchConfig> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/config/watch');
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error('Failed to fetch global watch config, assuming disabled.');
|
||||||
|
return { enabled: false }; // Default to disabled on error
|
||||||
|
}
|
||||||
|
return await response.json() as GlobalWatchConfig;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching global watch config:', error);
|
||||||
|
return { enabled: false }; // Default to disabled on error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const pathSegments = window.location.pathname.split('/');
|
const pathSegments = window.location.pathname.split('/');
|
||||||
const artistId = pathSegments[pathSegments.indexOf('artist') + 1];
|
const artistId = pathSegments[pathSegments.indexOf('artist') + 1];
|
||||||
|
|
||||||
@@ -55,13 +76,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const globalWatchConfig = await getGlobalWatchConfig(); // Fetch global config
|
||||||
|
const isGlobalWatchActuallyEnabled = globalWatchConfig.enabled;
|
||||||
|
|
||||||
// Fetch artist info directly
|
// Fetch artist info directly
|
||||||
fetch(`/api/artist/info?id=${encodeURIComponent(artistId)}`)
|
fetch(`/api/artist/info?id=${encodeURIComponent(artistId)}`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!response.ok) throw new Error('Network response was not ok');
|
if (!response.ok) throw new Error('Network response was not ok');
|
||||||
return response.json() as Promise<ArtistData>;
|
return response.json() as Promise<ArtistData>;
|
||||||
})
|
})
|
||||||
.then(data => renderArtist(data, artistId))
|
.then(data => renderArtist(data, artistId, isGlobalWatchActuallyEnabled))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
showError('Failed to load artist info.');
|
showError('Failed to load artist info.');
|
||||||
@@ -72,11 +96,47 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
queueIcon.addEventListener('click', () => downloadQueue.toggleVisibility());
|
queueIcon.addEventListener('click', () => downloadQueue.toggleVisibility());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attempt to set initial watchlist button visibility from cache
|
||||||
|
const watchlistButton = document.getElementById('watchlistButton') as HTMLAnchorElement | null;
|
||||||
|
if (watchlistButton) {
|
||||||
|
const cachedWatchEnabled = localStorage.getItem('spotizerr_watch_enabled_cached');
|
||||||
|
if (cachedWatchEnabled === 'true') {
|
||||||
|
watchlistButton.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch watch config to determine if watchlist button should be visible
|
||||||
|
async function updateWatchlistButtonVisibility() {
|
||||||
|
if (watchlistButton) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/config/watch');
|
||||||
|
if (response.ok) {
|
||||||
|
const watchConfig = await response.json();
|
||||||
|
localStorage.setItem('spotizerr_watch_enabled_cached', watchConfig.enabled ? 'true' : 'false');
|
||||||
|
if (watchConfig && watchConfig.enabled === false) {
|
||||||
|
watchlistButton.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
watchlistButton.classList.remove('hidden'); // Ensure it's shown if enabled
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Failed to fetch watch config for artist page, defaulting to hidden');
|
||||||
|
// Don't update cache on error
|
||||||
|
watchlistButton.classList.add('hidden'); // Hide if config fetch fails
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching watch config for artist page:', error);
|
||||||
|
// Don't update cache on error
|
||||||
|
watchlistButton.classList.add('hidden'); // Hide on error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateWatchlistButtonVisibility();
|
||||||
|
|
||||||
// Initialize the watch button after main artist rendering
|
// Initialize the watch button after main artist rendering
|
||||||
// This is done inside renderArtist after button element is potentially created.
|
// This is done inside renderArtist after button element is potentially created.
|
||||||
});
|
});
|
||||||
|
|
||||||
async function renderArtist(artistData: ArtistData, artistId: string) {
|
async function renderArtist(artistData: ArtistData, artistId: string, isGlobalWatchEnabled: boolean) {
|
||||||
const loadingEl = document.getElementById('loading');
|
const loadingEl = document.getElementById('loading');
|
||||||
if (loadingEl) loadingEl.classList.add('hidden');
|
if (loadingEl) loadingEl.classList.add('hidden');
|
||||||
|
|
||||||
@@ -84,7 +144,10 @@ async function renderArtist(artistData: ArtistData, artistId: string) {
|
|||||||
if (errorEl) errorEl.classList.add('hidden');
|
if (errorEl) errorEl.classList.add('hidden');
|
||||||
|
|
||||||
// Fetch watch status upfront to avoid race conditions for album button rendering
|
// Fetch watch status upfront to avoid race conditions for album button rendering
|
||||||
const isArtistActuallyWatched = await getArtistWatchStatus(artistId);
|
let isArtistActuallyWatched = false; // Default
|
||||||
|
if (isGlobalWatchEnabled) { // Only fetch if globally enabled
|
||||||
|
isArtistActuallyWatched = await getArtistWatchStatus(artistId);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if explicit filter is enabled
|
// Check if explicit filter is enabled
|
||||||
const isExplicitFilterEnabled = downloadQueue.isExplicitFilterEnabled();
|
const isExplicitFilterEnabled = downloadQueue.isExplicitFilterEnabled();
|
||||||
@@ -107,12 +170,25 @@ async function renderArtist(artistData: ArtistData, artistId: string) {
|
|||||||
artistImageEl.src = artistImageSrc;
|
artistImageEl.src = artistImageSrc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize Watch Button after other elements are rendered
|
|
||||||
const watchArtistBtn = document.getElementById('watchArtistBtn') as HTMLButtonElement | null;
|
const watchArtistBtn = document.getElementById('watchArtistBtn') as HTMLButtonElement | null;
|
||||||
if (watchArtistBtn) {
|
const syncArtistBtn = document.getElementById('syncArtistBtn') as HTMLButtonElement | null;
|
||||||
initializeWatchButton(artistId, isArtistActuallyWatched);
|
|
||||||
|
if (!isGlobalWatchEnabled) {
|
||||||
|
if (watchArtistBtn) {
|
||||||
|
watchArtistBtn.classList.add('hidden');
|
||||||
|
watchArtistBtn.disabled = true;
|
||||||
|
}
|
||||||
|
if (syncArtistBtn) {
|
||||||
|
syncArtistBtn.classList.add('hidden');
|
||||||
|
syncArtistBtn.disabled = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn("Watch artist button not found in HTML.");
|
if (watchArtistBtn) {
|
||||||
|
initializeWatchButton(artistId, isArtistActuallyWatched);
|
||||||
|
} else {
|
||||||
|
console.warn("Watch artist button not found in HTML.");
|
||||||
|
}
|
||||||
|
// Sync button visibility is managed by initializeWatchButton
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define the artist URL (used by both full-discography and group downloads)
|
// Define the artist URL (used by both full-discography and group downloads)
|
||||||
@@ -207,8 +283,8 @@ async function renderArtist(artistData: ArtistData, artistId: string) {
|
|||||||
|
|
||||||
// Use the definitively fetched watch status for rendering album buttons
|
// Use the definitively fetched watch status for rendering album buttons
|
||||||
// const isArtistWatched = watchArtistBtn && watchArtistBtn.dataset.watching === 'true'; // Old way
|
// const isArtistWatched = watchArtistBtn && watchArtistBtn.dataset.watching === 'true'; // Old way
|
||||||
const useThisWatchStatusForAlbums = isArtistActuallyWatched; // New way
|
// const useThisWatchStatusForAlbums = isArtistActuallyWatched; // Old way, now combination of global and individual
|
||||||
|
|
||||||
for (const [groupType, albums] of Object.entries(albumGroups)) {
|
for (const [groupType, albums] of Object.entries(albumGroups)) {
|
||||||
const groupSection = document.createElement('section');
|
const groupSection = document.createElement('section');
|
||||||
groupSection.className = 'album-group';
|
groupSection.className = 'album-group';
|
||||||
@@ -253,7 +329,7 @@ async function renderArtist(artistData: ArtistData, artistId: string) {
|
|||||||
albumCardActions.className = 'album-card-actions';
|
albumCardActions.className = 'album-card-actions';
|
||||||
|
|
||||||
// Persistent Mark as Known/Missing button (if artist is watched) - Appears first (left)
|
// Persistent Mark as Known/Missing button (if artist is watched) - Appears first (left)
|
||||||
if (useThisWatchStatusForAlbums && album.id) {
|
if (isGlobalWatchEnabled && isArtistActuallyWatched && album.id) {
|
||||||
const toggleKnownBtn = document.createElement('button');
|
const toggleKnownBtn = document.createElement('button');
|
||||||
toggleKnownBtn.className = 'toggle-known-status-btn persistent-album-action-btn';
|
toggleKnownBtn.className = 'toggle-known-status-btn persistent-album-action-btn';
|
||||||
toggleKnownBtn.dataset.albumId = album.id;
|
toggleKnownBtn.dataset.albumId = album.id;
|
||||||
@@ -351,7 +427,7 @@ async function renderArtist(artistData: ArtistData, artistId: string) {
|
|||||||
albumCardActions_AppearsOn.className = 'album-card-actions';
|
albumCardActions_AppearsOn.className = 'album-card-actions';
|
||||||
|
|
||||||
// Persistent Mark as Known/Missing button for appearing_on albums (if artist is watched) - Appears first (left)
|
// Persistent Mark as Known/Missing button for appearing_on albums (if artist is watched) - Appears first (left)
|
||||||
if (useThisWatchStatusForAlbums && album.id) {
|
if (isGlobalWatchEnabled && isArtistActuallyWatched && album.id) {
|
||||||
const toggleKnownBtn = document.createElement('button');
|
const toggleKnownBtn = document.createElement('button');
|
||||||
toggleKnownBtn.className = 'toggle-known-status-btn persistent-album-action-btn';
|
toggleKnownBtn.className = 'toggle-known-status-btn persistent-album-action-btn';
|
||||||
toggleKnownBtn.dataset.albumId = album.id;
|
toggleKnownBtn.dataset.albumId = album.id;
|
||||||
@@ -413,7 +489,7 @@ async function renderArtist(artistData: ArtistData, artistId: string) {
|
|||||||
if (albumsContainerEl) albumsContainerEl.classList.remove('hidden');
|
if (albumsContainerEl) albumsContainerEl.classList.remove('hidden');
|
||||||
|
|
||||||
if (!isExplicitFilterEnabled) {
|
if (!isExplicitFilterEnabled) {
|
||||||
attachAlbumActionListeners(artistId);
|
attachAlbumActionListeners(artistId, isGlobalWatchEnabled);
|
||||||
attachGroupDownloadListeners(artistId, artistName);
|
attachGroupDownloadListeners(artistId, artistName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -448,7 +524,7 @@ function attachGroupDownloadListeners(artistId: string, artistName: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachAlbumActionListeners(artistIdForContext: string) {
|
function attachAlbumActionListeners(artistIdForContext: string, isGlobalWatchEnabled: boolean) {
|
||||||
const groupsContainer = document.getElementById('album-groups');
|
const groupsContainer = document.getElementById('album-groups');
|
||||||
if (!groupsContainer) return;
|
if (!groupsContainer) return;
|
||||||
|
|
||||||
@@ -457,6 +533,10 @@ function attachAlbumActionListeners(artistIdForContext: string) {
|
|||||||
const button = target.closest('.toggle-known-status-btn') as HTMLButtonElement | null;
|
const button = target.closest('.toggle-known-status-btn') as HTMLButtonElement | null;
|
||||||
|
|
||||||
if (button && button.dataset.albumId) {
|
if (button && button.dataset.albumId) {
|
||||||
|
if (!isGlobalWatchEnabled) {
|
||||||
|
showNotification("Watch feature is currently disabled globally.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const albumId = button.dataset.albumId;
|
const albumId = button.dataset.albumId;
|
||||||
const currentStatus = button.dataset.status;
|
const currentStatus = button.dataset.status;
|
||||||
|
|
||||||
@@ -685,15 +765,24 @@ async function initializeWatchButton(artistId: string, initialIsWatching: boolea
|
|||||||
if (currentlyWatching) {
|
if (currentlyWatching) {
|
||||||
await unwatchArtist(artistId);
|
await unwatchArtist(artistId);
|
||||||
updateWatchButton(artistId, false);
|
updateWatchButton(artistId, false);
|
||||||
// Re-fetch and re-render artist data
|
// Re-fetch and re-render artist data, passing the global watch status again
|
||||||
const newArtistData = await (await fetch(`/api/artist/info?id=${encodeURIComponent(artistId)}`)).json() as ArtistData;
|
const newArtistData = await (await fetch(`/api/artist/info?id=${encodeURIComponent(artistId)}`)).json() as ArtistData;
|
||||||
renderArtist(newArtistData, artistId);
|
// Assuming renderArtist needs the global status, which it does. We need to get it or have it available.
|
||||||
|
// Since initializeWatchButton is called from renderArtist, we can assume isGlobalWatchEnabled is in that scope.
|
||||||
|
// This part is tricky as initializeWatchButton doesn't have isGlobalWatchEnabled.
|
||||||
|
// Let's re-fetch global config or rely on the fact that if this button is clickable, global is on.
|
||||||
|
// For simplicity, the re-render will pick up the global status from its own scope if called from top level.
|
||||||
|
// The click handler itself does not need to pass isGlobalWatchEnabled to renderArtist, renderArtist's caller does.
|
||||||
|
// Let's ensure renderArtist is called correctly after watch/unwatch.
|
||||||
|
const globalWatchConfig = await getGlobalWatchConfig(); // Re-fetch for re-render
|
||||||
|
renderArtist(newArtistData, artistId, globalWatchConfig.enabled);
|
||||||
} else {
|
} else {
|
||||||
await watchArtist(artistId);
|
await watchArtist(artistId);
|
||||||
updateWatchButton(artistId, true);
|
updateWatchButton(artistId, true);
|
||||||
// Re-fetch and re-render artist data
|
// Re-fetch and re-render artist data
|
||||||
const newArtistData = await (await fetch(`/api/artist/info?id=${encodeURIComponent(artistId)}`)).json() as ArtistData;
|
const newArtistData = await (await fetch(`/api/artist/info?id=${encodeURIComponent(artistId)}`)).json() as ArtistData;
|
||||||
renderArtist(newArtistData, artistId);
|
const globalWatchConfig = await getGlobalWatchConfig(); // Re-fetch for re-render
|
||||||
|
renderArtist(newArtistData, artistId, globalWatchConfig.enabled);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// On error, revert button to its state before the click attempt
|
// On error, revert button to its state before the click attempt
|
||||||
|
|||||||
@@ -168,6 +168,43 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
downloadQueue.toggleVisibility();
|
downloadQueue.toggleVisibility();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attempt to set initial watchlist button visibility from cache
|
||||||
|
const watchlistButton = document.getElementById('watchlistButton') as HTMLAnchorElement | null;
|
||||||
|
if (watchlistButton) {
|
||||||
|
const cachedWatchEnabled = localStorage.getItem('spotizerr_watch_enabled_cached');
|
||||||
|
if (cachedWatchEnabled === 'true') {
|
||||||
|
watchlistButton.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch watch config to determine if watchlist button should be visible
|
||||||
|
async function updateWatchlistButtonVisibility() {
|
||||||
|
if (watchlistButton) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/config/watch');
|
||||||
|
if (response.ok) {
|
||||||
|
const watchConfig = await response.json();
|
||||||
|
localStorage.setItem('spotizerr_watch_enabled_cached', watchConfig.enabled ? 'true' : 'false');
|
||||||
|
if (watchConfig && watchConfig.enabled === false) {
|
||||||
|
watchlistButton.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
watchlistButton.classList.remove('hidden'); // Ensure it's shown if enabled
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Failed to fetch watch config for config page, defaulting to hidden');
|
||||||
|
// Don't update cache on error
|
||||||
|
watchlistButton.classList.add('hidden'); // Hide if config fetch fails
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching watch config for config page:', error);
|
||||||
|
// Don't update cache on error
|
||||||
|
watchlistButton.classList.add('hidden'); // Hide on error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateWatchlistButtonVisibility();
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showConfigError(error.message);
|
showConfigError(error.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const queueIcon = document.getElementById('queueIcon');
|
const queueIcon = document.getElementById('queueIcon');
|
||||||
const emptyState = document.getElementById('emptyState');
|
const emptyState = document.getElementById('emptyState');
|
||||||
const loadingResults = document.getElementById('loadingResults');
|
const loadingResults = document.getElementById('loadingResults');
|
||||||
|
const watchlistButton = document.getElementById('watchlistButton') as HTMLAnchorElement | null;
|
||||||
|
|
||||||
// Initialize the queue
|
// Initialize the queue
|
||||||
if (queueIcon) {
|
if (queueIcon) {
|
||||||
@@ -124,6 +125,41 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attempt to set initial watchlist button visibility from cache
|
||||||
|
if (watchlistButton) {
|
||||||
|
const cachedWatchEnabled = localStorage.getItem('spotizerr_watch_enabled_cached');
|
||||||
|
if (cachedWatchEnabled === 'true') {
|
||||||
|
watchlistButton.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch watch config to determine if watchlist button should be visible
|
||||||
|
async function updateWatchlistButtonVisibility() {
|
||||||
|
if (watchlistButton) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/config/watch');
|
||||||
|
if (response.ok) {
|
||||||
|
const watchConfig = await response.json();
|
||||||
|
localStorage.setItem('spotizerr_watch_enabled_cached', watchConfig.enabled ? 'true' : 'false');
|
||||||
|
if (watchConfig && watchConfig.enabled === false) {
|
||||||
|
watchlistButton.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
watchlistButton.classList.remove('hidden'); // Ensure it's shown if enabled
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Failed to fetch watch config, defaulting to hidden');
|
||||||
|
// Don't update cache on error, rely on default hidden or previous cache state until success
|
||||||
|
watchlistButton.classList.add('hidden'); // Hide if config fetch fails
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching watch config:', error);
|
||||||
|
// Don't update cache on error
|
||||||
|
watchlistButton.classList.add('hidden'); // Hide on error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateWatchlistButtonVisibility();
|
||||||
|
|
||||||
// Check for URL parameters
|
// Check for URL parameters
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const query = urlParams.get('q');
|
const query = urlParams.get('q');
|
||||||
|
|||||||
@@ -61,6 +61,12 @@ interface WatchedPlaylistStatus {
|
|||||||
playlist_data?: Playlist; // Optional, present if watched
|
playlist_data?: Playlist; // Optional, present if watched
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Added: Interface for global watch config
|
||||||
|
interface GlobalWatchConfig {
|
||||||
|
enabled: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
interface DownloadQueueItem {
|
interface DownloadQueueItem {
|
||||||
name: string;
|
name: string;
|
||||||
artist?: string; // Can be a simple string for the queue
|
artist?: string; // Can be a simple string for the queue
|
||||||
@@ -69,7 +75,22 @@ interface DownloadQueueItem {
|
|||||||
// Add any other properties your item might have, compatible with QueueItem
|
// Add any other properties your item might have, compatible with QueueItem
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
// Added: Helper function to fetch global watch config
|
||||||
|
async function getGlobalWatchConfig(): Promise<GlobalWatchConfig> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/config/watch');
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error('Failed to fetch global watch config, assuming disabled.');
|
||||||
|
return { enabled: false }; // Default to disabled on error
|
||||||
|
}
|
||||||
|
return await response.json() as GlobalWatchConfig;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching global watch config:', error);
|
||||||
|
return { enabled: false }; // Default to disabled on error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
// Parse playlist ID from URL
|
// Parse playlist ID from URL
|
||||||
const pathSegments = window.location.pathname.split('/');
|
const pathSegments = window.location.pathname.split('/');
|
||||||
const playlistId = pathSegments[pathSegments.indexOf('playlist') + 1];
|
const playlistId = pathSegments[pathSegments.indexOf('playlist') + 1];
|
||||||
@@ -79,20 +100,40 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const globalWatchConfig = await getGlobalWatchConfig(); // Fetch global config
|
||||||
|
const isGlobalWatchActuallyEnabled = globalWatchConfig.enabled;
|
||||||
|
|
||||||
// Fetch playlist info directly
|
// Fetch playlist info directly
|
||||||
fetch(`/api/playlist/info?id=${encodeURIComponent(playlistId)}`)
|
fetch(`/api/playlist/info?id=${encodeURIComponent(playlistId)}`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!response.ok) throw new Error('Network response was not ok');
|
if (!response.ok) throw new Error('Network response was not ok');
|
||||||
return response.json() as Promise<Playlist>;
|
return response.json() as Promise<Playlist>;
|
||||||
})
|
})
|
||||||
.then(data => renderPlaylist(data))
|
.then(data => renderPlaylist(data, isGlobalWatchActuallyEnabled))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
showError('Failed to load playlist.');
|
showError('Failed to load playlist.');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch initial watch status
|
// Fetch initial watch status for the specific playlist
|
||||||
fetchWatchStatus(playlistId);
|
if (isGlobalWatchActuallyEnabled) {
|
||||||
|
fetchWatchStatus(playlistId); // This function then calls updateWatchButtons
|
||||||
|
} else {
|
||||||
|
// If global watch is disabled, ensure watch-related buttons are hidden/disabled
|
||||||
|
const watchBtn = document.getElementById('watchPlaylistBtn') as HTMLButtonElement;
|
||||||
|
const syncBtn = document.getElementById('syncPlaylistBtn') as HTMLButtonElement;
|
||||||
|
if (watchBtn) {
|
||||||
|
watchBtn.classList.add('hidden');
|
||||||
|
watchBtn.disabled = true;
|
||||||
|
// Remove any existing event listener to prevent actions
|
||||||
|
watchBtn.onclick = null;
|
||||||
|
}
|
||||||
|
if (syncBtn) {
|
||||||
|
syncBtn.classList.add('hidden');
|
||||||
|
syncBtn.disabled = true;
|
||||||
|
syncBtn.onclick = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const queueIcon = document.getElementById('queueIcon');
|
const queueIcon = document.getElementById('queueIcon');
|
||||||
if (queueIcon) {
|
if (queueIcon) {
|
||||||
@@ -100,12 +141,48 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
downloadQueue.toggleVisibility();
|
downloadQueue.toggleVisibility();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attempt to set initial watchlist button visibility from cache
|
||||||
|
const watchlistButton = document.getElementById('watchlistButton') as HTMLAnchorElement | null;
|
||||||
|
if (watchlistButton) {
|
||||||
|
const cachedWatchEnabled = localStorage.getItem('spotizerr_watch_enabled_cached');
|
||||||
|
if (cachedWatchEnabled === 'true') {
|
||||||
|
watchlistButton.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch watch config to determine if watchlist button should be visible
|
||||||
|
async function updateWatchlistButtonVisibility() {
|
||||||
|
if (watchlistButton) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/config/watch');
|
||||||
|
if (response.ok) {
|
||||||
|
const watchConfig = await response.json();
|
||||||
|
localStorage.setItem('spotizerr_watch_enabled_cached', watchConfig.enabled ? 'true' : 'false');
|
||||||
|
if (watchConfig && watchConfig.enabled === false) {
|
||||||
|
watchlistButton.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
watchlistButton.classList.remove('hidden'); // Ensure it's shown if enabled
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Failed to fetch watch config for playlist page, defaulting to hidden');
|
||||||
|
// Don't update cache on error
|
||||||
|
watchlistButton.classList.add('hidden'); // Hide if config fetch fails
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching watch config for playlist page:', error);
|
||||||
|
// Don't update cache on error
|
||||||
|
watchlistButton.classList.add('hidden'); // Hide on error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateWatchlistButtonVisibility();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders playlist header and tracks.
|
* Renders playlist header and tracks.
|
||||||
*/
|
*/
|
||||||
function renderPlaylist(playlist: Playlist) {
|
function renderPlaylist(playlist: Playlist, isGlobalWatchEnabled: boolean) {
|
||||||
// Hide loading and error messages
|
// Hide loading and error messages
|
||||||
const loadingEl = document.getElementById('loading');
|
const loadingEl = document.getElementById('loading');
|
||||||
if (loadingEl) loadingEl.classList.add('hidden');
|
if (loadingEl) loadingEl.classList.add('hidden');
|
||||||
@@ -250,7 +327,11 @@ function renderPlaylist(playlist: Playlist) {
|
|||||||
|
|
||||||
// Determine if the playlist is being watched to show/hide management buttons
|
// Determine if the playlist is being watched to show/hide management buttons
|
||||||
const watchPlaylistButton = document.getElementById('watchPlaylistBtn') as HTMLButtonElement;
|
const watchPlaylistButton = document.getElementById('watchPlaylistBtn') as HTMLButtonElement;
|
||||||
const isPlaylistWatched = watchPlaylistButton && watchPlaylistButton.classList.contains('watching');
|
// isIndividuallyWatched checks if the button is visible and has the 'watching' class.
|
||||||
|
// This implies global watch is enabled if the button is even interactable for individual status.
|
||||||
|
const isIndividuallyWatched = watchPlaylistButton &&
|
||||||
|
watchPlaylistButton.classList.contains('watching') &&
|
||||||
|
!watchPlaylistButton.classList.contains('hidden');
|
||||||
|
|
||||||
if (playlist.tracks?.items) {
|
if (playlist.tracks?.items) {
|
||||||
playlist.tracks.items.forEach((item: PlaylistItem, index: number) => {
|
playlist.tracks.items.forEach((item: PlaylistItem, index: number) => {
|
||||||
@@ -314,7 +395,7 @@ function renderPlaylist(playlist: Playlist) {
|
|||||||
actionsContainer.innerHTML += downloadBtnHTML;
|
actionsContainer.innerHTML += downloadBtnHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPlaylistWatched) {
|
if (isGlobalWatchEnabled && isIndividuallyWatched) { // Check global and individual watch status
|
||||||
// Initial state is set based on track.is_locally_known
|
// Initial state is set based on track.is_locally_known
|
||||||
const isKnown = track.is_locally_known === true; // Ensure boolean check, default to false if undefined
|
const isKnown = track.is_locally_known === true; // Ensure boolean check, default to false if undefined
|
||||||
const initialStatus = isKnown ? "known" : "missing";
|
const initialStatus = isKnown ? "known" : "missing";
|
||||||
@@ -346,7 +427,7 @@ function renderPlaylist(playlist: Playlist) {
|
|||||||
if (tracksContainerEl) tracksContainerEl.classList.remove('hidden');
|
if (tracksContainerEl) tracksContainerEl.classList.remove('hidden');
|
||||||
|
|
||||||
// Attach download listeners to newly rendered download buttons
|
// Attach download listeners to newly rendered download buttons
|
||||||
attachTrackActionListeners();
|
attachTrackActionListeners(isGlobalWatchEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -374,7 +455,7 @@ function showError(message: string) {
|
|||||||
/**
|
/**
|
||||||
* Attaches event listeners to all individual track action buttons (download, mark known, mark missing).
|
* Attaches event listeners to all individual track action buttons (download, mark known, mark missing).
|
||||||
*/
|
*/
|
||||||
function attachTrackActionListeners() {
|
function attachTrackActionListeners(isGlobalWatchEnabled: boolean) {
|
||||||
document.querySelectorAll('.track-download-btn').forEach((btn) => {
|
document.querySelectorAll('.track-download-btn').forEach((btn) => {
|
||||||
btn.addEventListener('click', (e: Event) => {
|
btn.addEventListener('click', (e: Event) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -405,6 +486,11 @@ function attachTrackActionListeners() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isGlobalWatchEnabled) { // Added check
|
||||||
|
showNotification("Watch feature is currently disabled globally. Cannot change track status.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
try {
|
try {
|
||||||
if (currentStatus === 'missing') {
|
if (currentStatus === 'missing') {
|
||||||
@@ -656,6 +742,18 @@ function updateWatchButtons(isWatched: boolean, playlistId: string) {
|
|||||||
async function watchPlaylist(playlistId: string) {
|
async function watchPlaylist(playlistId: string) {
|
||||||
const watchBtn = document.getElementById('watchPlaylistBtn') as HTMLButtonElement;
|
const watchBtn = document.getElementById('watchPlaylistBtn') as HTMLButtonElement;
|
||||||
if (watchBtn) watchBtn.disabled = true;
|
if (watchBtn) watchBtn.disabled = true;
|
||||||
|
// This function should only be callable if global watch is enabled.
|
||||||
|
// We can add a check here or rely on the UI not presenting the button.
|
||||||
|
// For safety, let's check global config again before proceeding.
|
||||||
|
const globalConfig = await getGlobalWatchConfig();
|
||||||
|
if (!globalConfig.enabled) {
|
||||||
|
showError("Cannot watch playlist, feature is disabled globally.");
|
||||||
|
if (watchBtn) {
|
||||||
|
watchBtn.disabled = false; // Re-enable if it was somehow clicked
|
||||||
|
updateWatchButtons(false, playlistId); // Reset button to non-watching state
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/playlist/watch/${playlistId}`, { method: 'PUT' });
|
const response = await fetch(`/api/playlist/watch/${playlistId}`, { method: 'PUT' });
|
||||||
@@ -668,7 +766,7 @@ async function watchPlaylist(playlistId: string) {
|
|||||||
const newPlaylistInfoResponse = await fetch(`/api/playlist/info?id=${encodeURIComponent(playlistId)}`);
|
const newPlaylistInfoResponse = await fetch(`/api/playlist/info?id=${encodeURIComponent(playlistId)}`);
|
||||||
if (!newPlaylistInfoResponse.ok) throw new Error('Failed to re-fetch playlist info after watch.');
|
if (!newPlaylistInfoResponse.ok) throw new Error('Failed to re-fetch playlist info after watch.');
|
||||||
const newPlaylistData = await newPlaylistInfoResponse.json() as Playlist;
|
const newPlaylistData = await newPlaylistInfoResponse.json() as Playlist;
|
||||||
renderPlaylist(newPlaylistData);
|
renderPlaylist(newPlaylistData, globalConfig.enabled); // Pass current global enabled state
|
||||||
|
|
||||||
showNotification(`Playlist added to watchlist. Tracks are being updated.`);
|
showNotification(`Playlist added to watchlist. Tracks are being updated.`);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -683,6 +781,17 @@ async function watchPlaylist(playlistId: string) {
|
|||||||
async function unwatchPlaylist(playlistId: string) {
|
async function unwatchPlaylist(playlistId: string) {
|
||||||
const watchBtn = document.getElementById('watchPlaylistBtn') as HTMLButtonElement;
|
const watchBtn = document.getElementById('watchPlaylistBtn') as HTMLButtonElement;
|
||||||
if (watchBtn) watchBtn.disabled = true;
|
if (watchBtn) watchBtn.disabled = true;
|
||||||
|
// Similarly, check global config
|
||||||
|
const globalConfig = await getGlobalWatchConfig();
|
||||||
|
if (!globalConfig.enabled) {
|
||||||
|
// This case should be rare if UI behaves, but good for robustness
|
||||||
|
showError("Cannot unwatch playlist, feature is disabled globally.");
|
||||||
|
if (watchBtn) {
|
||||||
|
watchBtn.disabled = false;
|
||||||
|
// updateWatchButtons(true, playlistId); // Or keep as is if it was 'watching'
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/playlist/watch/${playlistId}`, { method: 'DELETE' });
|
const response = await fetch(`/api/playlist/watch/${playlistId}`, { method: 'DELETE' });
|
||||||
@@ -695,7 +804,7 @@ async function unwatchPlaylist(playlistId: string) {
|
|||||||
const newPlaylistInfoResponse = await fetch(`/api/playlist/info?id=${encodeURIComponent(playlistId)}`);
|
const newPlaylistInfoResponse = await fetch(`/api/playlist/info?id=${encodeURIComponent(playlistId)}`);
|
||||||
if (!newPlaylistInfoResponse.ok) throw new Error('Failed to re-fetch playlist info after unwatch.');
|
if (!newPlaylistInfoResponse.ok) throw new Error('Failed to re-fetch playlist info after unwatch.');
|
||||||
const newPlaylistData = await newPlaylistInfoResponse.json() as Playlist;
|
const newPlaylistData = await newPlaylistInfoResponse.json() as Playlist;
|
||||||
renderPlaylist(newPlaylistData);
|
renderPlaylist(newPlaylistData, globalConfig.enabled); // Pass current global enabled state
|
||||||
|
|
||||||
showNotification('Playlist removed from watchlist. Track statuses updated.');
|
showNotification('Playlist removed from watchlist. Track statuses updated.');
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -710,6 +819,12 @@ async function unwatchPlaylist(playlistId: string) {
|
|||||||
async function syncPlaylist(playlistId: string) {
|
async function syncPlaylist(playlistId: string) {
|
||||||
const syncBtn = document.getElementById('syncPlaylistBtn') as HTMLButtonElement;
|
const syncBtn = document.getElementById('syncPlaylistBtn') as HTMLButtonElement;
|
||||||
let originalButtonContent = ''; // Define outside
|
let originalButtonContent = ''; // Define outside
|
||||||
|
// Check global config
|
||||||
|
const globalConfig = await getGlobalWatchConfig();
|
||||||
|
if (!globalConfig.enabled) {
|
||||||
|
showError("Cannot sync playlist, feature is disabled globally.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (syncBtn) {
|
if (syncBtn) {
|
||||||
syncBtn.disabled = true;
|
syncBtn.disabled = true;
|
||||||
|
|||||||
@@ -30,6 +30,42 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
downloadQueue.toggleVisibility();
|
downloadQueue.toggleVisibility();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attempt to set initial watchlist button visibility from cache
|
||||||
|
const watchlistButton = document.getElementById('watchlistButton') as HTMLAnchorElement | null;
|
||||||
|
if (watchlistButton) {
|
||||||
|
const cachedWatchEnabled = localStorage.getItem('spotizerr_watch_enabled_cached');
|
||||||
|
if (cachedWatchEnabled === 'true') {
|
||||||
|
watchlistButton.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch watch config to determine if watchlist button should be visible
|
||||||
|
async function updateWatchlistButtonVisibility() {
|
||||||
|
if (watchlistButton) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/config/watch');
|
||||||
|
if (response.ok) {
|
||||||
|
const watchConfig = await response.json();
|
||||||
|
localStorage.setItem('spotizerr_watch_enabled_cached', watchConfig.enabled ? 'true' : 'false');
|
||||||
|
if (watchConfig && watchConfig.enabled === false) {
|
||||||
|
watchlistButton.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
watchlistButton.classList.remove('hidden'); // Ensure it's shown if enabled
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Failed to fetch watch config for track page, defaulting to hidden');
|
||||||
|
// Don't update cache on error
|
||||||
|
watchlistButton.classList.add('hidden'); // Hide if config fetch fails
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching watch config for track page:', error);
|
||||||
|
// Don't update cache on error
|
||||||
|
watchlistButton.classList.add('hidden'); // Hide on error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateWatchlistButtonVisibility();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -133,13 +133,37 @@ interface WatchedPlaylistOriginal {
|
|||||||
|
|
||||||
type WatchedItem = (WatchedArtistOriginal & { itemType: 'artist' }) | (WatchedPlaylistOriginal & { itemType: 'playlist' });
|
type WatchedItem = (WatchedArtistOriginal & { itemType: 'artist' }) | (WatchedPlaylistOriginal & { itemType: 'playlist' });
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
// Added: Interface for global watch config
|
||||||
|
interface GlobalWatchConfig {
|
||||||
|
enabled: boolean;
|
||||||
|
[key: string]: any; // Allow other properties
|
||||||
|
}
|
||||||
|
|
||||||
|
// Added: Helper function to fetch global watch config
|
||||||
|
async function getGlobalWatchConfig(): Promise<GlobalWatchConfig> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/config/watch');
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error('Failed to fetch global watch config, assuming disabled.');
|
||||||
|
return { enabled: false }; // Default to disabled on error
|
||||||
|
}
|
||||||
|
return await response.json() as GlobalWatchConfig;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching global watch config:', error);
|
||||||
|
return { enabled: false }; // Default to disabled on error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async function() {
|
||||||
const watchedItemsContainer = document.getElementById('watchedItemsContainer');
|
const watchedItemsContainer = document.getElementById('watchedItemsContainer');
|
||||||
const loadingIndicator = document.getElementById('loadingWatchedItems');
|
const loadingIndicator = document.getElementById('loadingWatchedItems');
|
||||||
const emptyStateIndicator = document.getElementById('emptyWatchedItems');
|
const emptyStateIndicator = document.getElementById('emptyWatchedItems');
|
||||||
const queueIcon = document.getElementById('queueIcon');
|
const queueIcon = document.getElementById('queueIcon');
|
||||||
const checkAllWatchedBtn = document.getElementById('checkAllWatchedBtn') as HTMLButtonElement | null;
|
const checkAllWatchedBtn = document.getElementById('checkAllWatchedBtn') as HTMLButtonElement | null;
|
||||||
|
|
||||||
|
// Fetch global watch config first
|
||||||
|
const globalWatchConfig = await getGlobalWatchConfig();
|
||||||
|
|
||||||
if (queueIcon) {
|
if (queueIcon) {
|
||||||
queueIcon.addEventListener('click', () => {
|
queueIcon.addEventListener('click', () => {
|
||||||
downloadQueue.toggleVisibility();
|
downloadQueue.toggleVisibility();
|
||||||
@@ -214,8 +238,28 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initial load
|
// Initial load is now conditional
|
||||||
loadWatchedItems();
|
if (globalWatchConfig.enabled) {
|
||||||
|
if (checkAllWatchedBtn) checkAllWatchedBtn.classList.remove('hidden');
|
||||||
|
loadWatchedItems();
|
||||||
|
} else {
|
||||||
|
// Watch feature is disabled globally
|
||||||
|
showLoading(false);
|
||||||
|
showEmptyState(false);
|
||||||
|
if (checkAllWatchedBtn) checkAllWatchedBtn.classList.add('hidden'); // Hide the button
|
||||||
|
|
||||||
|
if (watchedItemsContainer) {
|
||||||
|
watchedItemsContainer.innerHTML = `
|
||||||
|
<div class="empty-state-container">
|
||||||
|
<img src="/static/images/eye-crossed.svg" alt="Watch Disabled" class="empty-state-icon">
|
||||||
|
<p class="empty-state-message">The Watchlist feature is currently disabled in the application settings.</p>
|
||||||
|
<p class="empty-state-submessage">Please enable it in <a href="/settings" class="settings-link">Settings</a> to use this page.</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
// Ensure the main loading indicator is also hidden if it was shown by default
|
||||||
|
if (loadingIndicator) loadingIndicator.classList.add('hidden');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const MAX_NOTIFICATIONS = 3;
|
const MAX_NOTIFICATIONS = 3;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
<img src="{{ url_for('static', filename='images/home.svg') }}" alt="Home">
|
<img src="{{ url_for('static', filename='images/home.svg') }}" alt="Home">
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<a href="/watchlist" class="btn-icon watchlist-icon floating-icon" aria-label="Watchlist" title="Go to Watchlist">
|
<a id="watchlistButton" href="/watchlist" class="btn-icon watchlist-icon floating-icon hidden" aria-label="Watchlist" title="Go to Watchlist">
|
||||||
<img src="{{ url_for('static', filename='images/binoculars.svg') }}" alt="Watchlist" onerror="handleImageError(this)"/>
|
<img src="{{ url_for('static', filename='images/binoculars.svg') }}" alt="Watchlist" onerror="handleImageError(this)"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
<img src="{{ url_for('static', filename='images/home.svg') }}" alt="Home">
|
<img src="{{ url_for('static', filename='images/home.svg') }}" alt="Home">
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<a href="/watchlist" class="btn-icon watchlist-icon floating-icon" aria-label="Watchlist" title="Go to Watchlist">
|
<a id="watchlistButton" href="/watchlist" class="btn-icon watchlist-icon floating-icon hidden" aria-label="Watchlist" title="Go to Watchlist">
|
||||||
<img src="{{ url_for('static', filename='images/binoculars.svg') }}" alt="Watchlist" onerror="handleImageError(this)"/>
|
<img src="{{ url_for('static', filename='images/binoculars.svg') }}" alt="Watchlist" onerror="handleImageError(this)"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<div class="config-container">
|
<div class="config-container">
|
||||||
<header class="config-header">
|
<header class="config-header">
|
||||||
<h1 class="header-title">Configuration</h1>
|
<h1 class="header-title">Configuration</h1>
|
||||||
<span class="version-text">2.0.1</span>
|
<span class="version-text">Set on build</span>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="account-config card">
|
<div class="account-config card">
|
||||||
@@ -118,9 +118,9 @@
|
|||||||
<option value="">-- Select placeholder --</option>
|
<option value="">-- Select placeholder --</option>
|
||||||
<optgroup label="Common">
|
<optgroup label="Common">
|
||||||
<option value="%music%">%music% - Track title</option>
|
<option value="%music%">%music% - Track title</option>
|
||||||
<option value="%artist%">%artist% - Track artist</option>
|
<option value="%artist%">%artist% - Track artist. If multiple artists, use %artist_1%, %artist_2%, etc. to select a specific one.</option>
|
||||||
<option value="%album%">%album% - Album name</option>
|
<option value="%album%">%album% - Album name</option>
|
||||||
<option value="%ar_album%">%ar_album% - Album artist</option>
|
<option value="%ar_album%">%ar_album% - Album artist. If multiple album artists, use %ar_album_1%, %ar_album_2%, etc. to select a specific one.</option>
|
||||||
<option value="%tracknum%">%tracknum% - Track number</option>
|
<option value="%tracknum%">%tracknum% - Track number</option>
|
||||||
<option value="%year%">%year% - Year of release</option>
|
<option value="%year%">%year% - Year of release</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
@@ -171,9 +171,9 @@
|
|||||||
<option value="">-- Select placeholder --</option>
|
<option value="">-- Select placeholder --</option>
|
||||||
<optgroup label="Common">
|
<optgroup label="Common">
|
||||||
<option value="%music%">%music% - Track title</option>
|
<option value="%music%">%music% - Track title</option>
|
||||||
<option value="%artist%">%artist% - Track artist</option>
|
<option value="%artist%">%artist% - Track artist. If multiple artists, use %artist_1%, %artist_2%, etc. to select a specific one.</option>
|
||||||
<option value="%album%">%album% - Album name</option>
|
<option value="%album%">%album% - Album name</option>
|
||||||
<option value="%ar_album%">%ar_album% - Album artist</option>
|
<option value="%ar_album%">%ar_album% - Album artist. If multiple album artists, use %ar_album_1%, %ar_album_2%, etc. to select a specific one.</option>
|
||||||
<option value="%tracknum%">%tracknum% - Track number</option>
|
<option value="%tracknum%">%tracknum% - Track number</option>
|
||||||
<option value="%year%">%year% - Year of release</option>
|
<option value="%year%">%year% - Year of release</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
@@ -324,7 +324,7 @@
|
|||||||
<img src="{{ url_for('static', filename='images/arrow-left.svg') }}" alt="Back" />
|
<img src="{{ url_for('static', filename='images/arrow-left.svg') }}" alt="Back" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="/watchlist" class="btn-icon watchlist-icon floating-icon" aria-label="Watchlist" title="Go to Watchlist">
|
<a id="watchlistButton" href="/watchlist" class="btn-icon watchlist-icon floating-icon hidden" aria-label="Watchlist" title="Go to Watchlist">
|
||||||
<img src="{{ url_for('static', filename='images/binoculars.svg') }}" alt="Watchlist" onerror="handleImageError(this)"/>
|
<img src="{{ url_for('static', filename='images/binoculars.svg') }}" alt="Watchlist" onerror="handleImageError(this)"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
<img src="{{ url_for('static', filename='images/settings.svg') }}" alt="Settings" onerror="handleImageError(this)"/>
|
<img src="{{ url_for('static', filename='images/settings.svg') }}" alt="Settings" onerror="handleImageError(this)"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="/watchlist" class="btn-icon watchlist-icon floating-icon" aria-label="Watchlist" title="Go to Watchlist">
|
<a id="watchlistButton" href="/watchlist" class="btn-icon watchlist-icon floating-icon hidden" aria-label="Watchlist" title="Go to Watchlist">
|
||||||
<img src="{{ url_for('static', filename='images/binoculars.svg') }}" alt="Watchlist" onerror="handleImageError(this)"/>
|
<img src="{{ url_for('static', filename='images/binoculars.svg') }}" alt="Watchlist" onerror="handleImageError(this)"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
<img src="{{ url_for('static', filename='images/home.svg') }}" alt="Home">
|
<img src="{{ url_for('static', filename='images/home.svg') }}" alt="Home">
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<a href="/watchlist" class="btn-icon watchlist-icon floating-icon" aria-label="Watchlist" title="Go to Watchlist">
|
<a id="watchlistButton" href="/watchlist" class="btn-icon watchlist-icon floating-icon hidden" aria-label="Watchlist" title="Go to Watchlist">
|
||||||
<img src="{{ url_for('static', filename='images/binoculars.svg') }}" alt="Watchlist" onerror="handleImageError(this)"/>
|
<img src="{{ url_for('static', filename='images/binoculars.svg') }}" alt="Watchlist" onerror="handleImageError(this)"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
<img src="{{ url_for('static', filename='images/home.svg') }}" alt="Home">
|
<img src="{{ url_for('static', filename='images/home.svg') }}" alt="Home">
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<a href="/watchlist" class="btn-icon watchlist-icon floating-icon" aria-label="Watchlist" title="Go to Watchlist">
|
<a id="watchlistButton" href="/watchlist" class="btn-icon watchlist-icon floating-icon hidden" aria-label="Watchlist" title="Go to Watchlist">
|
||||||
<img src="{{ url_for('static', filename='images/binoculars.svg') }}" alt="Watchlist" onerror="handleImageError(this)"/>
|
<img src="{{ url_for('static', filename='images/binoculars.svg') }}" alt="Watchlist" onerror="handleImageError(this)"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
@@ -49,10 +49,6 @@
|
|||||||
<a href="/" class="btn-icon home-btn floating-icon settings-icon" aria-label="Return to Home" title="Return to Home">
|
<a href="/" class="btn-icon home-btn floating-icon settings-icon" aria-label="Return to Home" title="Return to Home">
|
||||||
<img src="{{ url_for('static', filename='images/home.svg') }}" alt="Home" onerror="handleImageError(this)"/>
|
<img src="{{ url_for('static', filename='images/home.svg') }}" alt="Home" onerror="handleImageError(this)"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="/watchlist" class="btn-icon watchlist-icon floating-icon" aria-label="Watchlist" title="Go to Watchlist">
|
|
||||||
<img src="{{ url_for('static', filename='images/binoculars.svg') }}" alt="Watchlist" onerror="handleImageError(this)"/>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
id="queueIcon"
|
id="queueIcon"
|
||||||
|
|||||||
Reference in New Issue
Block a user