use repeat.svg icon; clean up generate_manifests.py

Hunter
Thursday, March 26th, 2026 at 4:41 PM

generate_manifests.py+25-41
resources/repeat.svgnew
resources/script.js+14-2

generate_manifests.py
  8  import re
  9  from pathlib import Path
 10  
    -def get_configuration(localhost=False):
    -	"""Prompt user for configuration values
    -
    -	Args:
    -		localhost: If True, assumes root path and only asks for app name.
    -		           This enables PWA installation on localhost (iOS 18+).
    -	"""
 11 +def get_configuration():
 12 +	"""Prompt user for configuration values"""
 13  	print("=" * 60)
 14  	print("PWA Configuration")
 15  	print("=" * 60)
 16  	print()
 17  
 18  	# Get app name
    -	if localhost:
    -		app_name = input("Enter a name for your mixapp (or press Return/Enter for 'my mixapp'): ").strip()
    -		if not app_name:
    -			app_name = "my mixapp"
    -			print(f"Using default: {app_name}")
    -	else:
    -		app_name = input("Enter a name for your mixapp: ").strip()
    -		if not app_name:
    -			print("Error: App name is required")
    -			exit(1)
    -
    -	# For localhost mode, use root path
    -	if localhost:
    -		base_path = "/"
    -		print()
    -		print("Localhost mode: Using root path for PWA installation")
 19 +	app_name = input("Enter a name for your mixapp: ").strip()
 20 +	if not app_name:
 21 +		print("Error: App name is required")
 22 +		exit(1)
 23 +
 24 +	# Get base path with smart default
 25 +	default_path = app_name.lower().replace(" ", "_")
 26 +	print()
 27 +	print(f"Enter the deployment path (or press Return/Enter for default)")
 28 +	print(f"Default: /{default_path}/")
 29 +	base_path_input = input("Path: ").strip()
 30 +
 31 +	if base_path_input:
 32 +		# User provided a path - ensure it has leading/trailing slashes
 33 +		base_path = base_path_input
 34 +		if not base_path.startswith("/"):
 35 +			base_path = "/" + base_path
 36 +		if not base_path.endswith("/"):
 37 +			base_path = base_path + "/"
 38  	else:
    -		# Get base path with smart default
    -		default_path = app_name.lower().replace(" ", "_")
    -		print()
    -		print(f"Enter the deployment path (or press Return/Enter for default)")
    -		print(f"Default: /{default_path}/")
    -		base_path_input = input("Path: ").strip()
    -
    -		if base_path_input:
    -			# User provided a path - ensure it has leading/trailing slashes
    -			base_path = base_path_input
    -			if not base_path.startswith("/"):
    -				base_path = "/" + base_path
    -			if not base_path.endswith("/"):
    -				base_path = base_path + "/"
    -		else:
    -			# Use default
    -			base_path = f"/{default_path}/"
    -			print(f"Using default path: {base_path}")
 39 +		# Use default
 40 +		base_path = f"/{default_path}/"
 41 +		print(f"Using default path: {base_path}")
 42  
 43  	print()
 44  	print(f"Configuration:")

172  		"resources/pause.png",
173  		"resources/prev.png",
174  		"resources/next.png",
175 +		"resources/repeat.svg",
176  	]
177  
178  	# Conditionally include optional files if they exist
resources/repeat.svg
 1 +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 2 +<!-- Created with Inkscape (http://www.inkscape.org/) -->
 3 +
 4 +<svg
 5 +   width="65.407951mm"
 6 +   height="56.782715mm"
 7 +   viewBox="0 0 65.407951 56.782715"
 8 +   version="1.1"
 9 +   id="svg5"
10 +   xml:space="preserve"
11 +   xmlns="http://www.w3.org/2000/svg"
12 +   xmlns:svg="http://www.w3.org/2000/svg"><defs
13 +     id="defs2" /><g
14 +     id="layer1"
15 +     transform="translate(-468.51962,-125.48088)"><g
16 +       id="g9251"><path
17 +         id="path2689-9"
18 +         style="fill:currentColor;fill-opacity:1;stroke-width:5.39981;stroke-linecap:round;stroke-linejoin:round"
19 +         d="m 558.39505,447.02975 a 2.7001749,2.7001749 0 0 0 -0.9715,0.25878 2.7001749,2.7001749 0 0 0 -1.53172,2.43391 v 19.6921 h -68.73634 c -48.82418,-10e-6 -78.6236,51.9014 -57.88312,93.24693 l 2.36317,4.71165 25.58799,-20.65363 c -9.52258,-21.20085 3.26483,-45.131 29.93196,-45.13099 h 68.73634 v 19.69211 a 2.7001749,2.7001749 0 0 0 4.39509,2.1015 l 44.4911,-35.91071 a 2.7001749,2.7001749 0 0 0 -0.009,-4.20747 l -44.48888,-35.64969 a 2.7001749,2.7001749 0 0 0 -1.8852,-0.58449 z"
20 +         transform="matrix(0.23245107,0,0,0.23163997,379.32931,30.229918)" /><path
21 +         id="path2689-7-7"
22 +         style="fill:currentColor;fill-opacity:1;stroke-width:5.39981;stroke-linecap:round;stroke-linejoin:round"
23 +         d="m 558.39505,447.02975 a 2.7001749,2.7001749 0 0 0 -0.9715,0.25878 2.7001749,2.7001749 0 0 0 -1.53172,2.43391 v 19.6921 h -68.73634 c -48.82418,-10e-6 -78.6236,51.9014 -57.88312,93.24693 l 2.36317,4.71165 25.58799,-20.65363 c -9.52258,-21.20085 3.26483,-45.131 29.93196,-45.13099 h 68.73634 v 19.69211 a 2.7001749,2.7001749 0 0 0 4.39509,2.1015 l 44.4911,-35.91071 a 2.7001749,2.7001749 0 0 0 -0.009,-4.20747 l -44.48888,-35.64969 a 2.7001749,2.7001749 0 0 0 -1.8852,-0.58449 z"
24 +         transform="matrix(-0.23245107,0,0,-0.23163997,623.1179,277.51456)" /></g></g></svg>
resources/script.js
226  		contentDiv.appendChild(artistDiv);
227  
228  		const loopIcon = document.createElement('span');
    -		loopIcon.textContent = '🔁';
    -		loopIcon.style.display = (song.looping || false) ? 'inline' : 'none';
229 +		loopIcon.style.width = '1.18em';
230 +		loopIcon.style.height = '1.18em';
231 +		loopIcon.style.display = (song.looping || false) ? 'inline-block' : 'none';
232 +		loopIcon.style.color = 'var(--text)';
233 +		fetch('resources/repeat.svg')
234 +			.then(r => r.text())
235 +			.then(svgText => {
236 +				loopIcon.innerHTML = svgText;
237 +				const svg = loopIcon.querySelector('svg');
238 +				if (svg) {
239 +					svg.style.width = '1.18em';
240 +					svg.style.height = '1.18em';
241 +				}
242 +			});
243  
244  		item.appendChild(contentDiv);
245  		item.appendChild(loopIcon);