Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 84500401e7 | |||
| 28293c6340 | |||
| 39b3aed5f3 | |||
| ba90dffa18 | |||
| 69df9b6b95 | |||
| 760c3df90c | |||
| 60f287bb40 | |||
| f52af51a20 | |||
| f2d569a1b0 | |||
| db777fa64b |
+3
-9
@@ -1,11 +1,7 @@
|
|||||||
## v0.1.2 (2026-03-29)
|
## v0.1.3 (2026-04-07)
|
||||||
|
|
||||||
### Features
|
|
||||||
- Redesign media browser UI ([cad6e8a](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/cad6e8a))
|
|
||||||
- Add media folder management from WebUI ([c9ee41a](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/c9ee41a))
|
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
- Make folder status visible with dot + text label ([c50a8f4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/c50a8f4))
|
- Prevent dialog `showModal` from auto-focusing first input ([db777fa](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/db777fa))
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -14,8 +10,6 @@
|
|||||||
|
|
||||||
| Hash | Message | Author |
|
| Hash | Message | Author |
|
||||||
|------|---------|--------|
|
|------|---------|--------|
|
||||||
| [c50a8f4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/c50a8f4) | fix: make folder status visible with dot + text label | alexei.dolgolyov |
|
| [db777fa](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/db777fa) | fix: prevent dialog showModal from auto-focusing first input | alexei.dolgolyov |
|
||||||
| [cad6e8a](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/cad6e8a) | feat: redesign media browser UI | alexei.dolgolyov |
|
|
||||||
| [c9ee41a](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/c9ee41a) | feat: add media folder management from WebUI | alexei.dolgolyov |
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -23,6 +23,17 @@ detect_version() {
|
|||||||
|
|
||||||
VERSION_CLEAN="${VERSION#v}"
|
VERSION_CLEAN="${VERSION#v}"
|
||||||
|
|
||||||
|
# Normalize non-PEP440 labels (e.g. "dev", "nightly", "snapshot") to a
|
||||||
|
# valid PEP440 dev release. Without this, pip/setuptools rejects
|
||||||
|
# pyproject.toml with: `project.version` must be pep440.
|
||||||
|
#
|
||||||
|
# Valid forms: 1.2.3, 1.2.3a1, 1.2.3rc2, 1.2.3.dev0, 1.2.3.post1, +local
|
||||||
|
# Invalid forms: dev, vdev, nightly, snapshot-2024
|
||||||
|
if ! [[ "$VERSION_CLEAN" =~ ^[0-9]+(\.[0-9]+)*((a|b|rc|\.dev|\.post)[0-9]+)*(\+[a-zA-Z0-9.]+)?$ ]]; then
|
||||||
|
echo " Warning: '$VERSION_CLEAN' is not PEP440-compliant, using 0.0.0.dev0"
|
||||||
|
VERSION_CLEAN="0.0.0.dev0"
|
||||||
|
fi
|
||||||
|
|
||||||
# Stamp version into pyproject.toml (single source of truth)
|
# Stamp version into pyproject.toml (single source of truth)
|
||||||
sed -i "s/^version = .*/version = \"${VERSION_CLEAN}\"/" pyproject.toml
|
sed -i "s/^version = .*/version = \"${VERSION_CLEAN}\"/" pyproject.toml
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-9
@@ -19,10 +19,15 @@ BUILD_OUTPUT="build/MediaServer-v${VERSION_CLEAN}-win-x64"
|
|||||||
|
|
||||||
clean_dist "${DIST_DIR}" "${WHEEL_DIR}" "${SITE_PACKAGES}"
|
clean_dist "${DIST_DIR}" "${WHEEL_DIR}" "${SITE_PACKAGES}"
|
||||||
|
|
||||||
# --- Download embedded Python ---
|
# --- Download embedded Python (cache-friendly) ---
|
||||||
echo "Downloading embedded Python ${PYTHON_VERSION}..."
|
mkdir -p build
|
||||||
curl -sL "https://www.python.org/ftp/python/${PYTHON_VERSION}/python-${PYTHON_VERSION}-embed-amd64.zip" \
|
if [ ! -f build/python-embed.zip ]; then
|
||||||
|
echo "Downloading embedded Python ${PYTHON_VERSION}..."
|
||||||
|
curl -sL "https://www.python.org/ftp/python/${PYTHON_VERSION}/python-${PYTHON_VERSION}-embed-amd64.zip" \
|
||||||
-o build/python-embed.zip
|
-o build/python-embed.zip
|
||||||
|
else
|
||||||
|
echo "Using cached embedded Python ${PYTHON_VERSION}"
|
||||||
|
fi
|
||||||
unzip -qo build/python-embed.zip -d "${DIST_DIR}/python"
|
unzip -qo build/python-embed.zip -d "${DIST_DIR}/python"
|
||||||
|
|
||||||
# Patch ._pth to enable site-packages and app source
|
# Patch ._pth to enable site-packages and app source
|
||||||
@@ -35,9 +40,18 @@ echo '..\app' >> "$PTH_FILE"
|
|||||||
echo "Downloading Windows wheels..."
|
echo "Downloading Windows wheels..."
|
||||||
|
|
||||||
# Core dependencies
|
# Core dependencies
|
||||||
|
# NOTE: uvicorn[standard] pulls in uvloop via `sys_platform != 'win32'` marker.
|
||||||
|
# pip evaluates env markers against the HOST (Linux in CI), so uvloop is
|
||||||
|
# requested, but `--platform win_amd64 --only-binary :all:` cannot find a
|
||||||
|
# Windows wheel for uvloop (none exist). Result: pip backtracks across every
|
||||||
|
# uvicorn[standard] version and ResolutionImpossible. Fix: use plain uvicorn
|
||||||
|
# and list only the Windows-compatible standard extras we actually need.
|
||||||
CORE_DEPS=(
|
CORE_DEPS=(
|
||||||
"fastapi>=0.109.0"
|
"fastapi>=0.109.0"
|
||||||
"uvicorn[standard]>=0.27.0"
|
"uvicorn>=0.27.0"
|
||||||
|
"httptools>=0.5.0"
|
||||||
|
"websockets>=10.4"
|
||||||
|
"python-dotenv>=0.13"
|
||||||
"pydantic>=2.0"
|
"pydantic>=2.0"
|
||||||
"pydantic-settings>=2.0"
|
"pydantic-settings>=2.0"
|
||||||
"pyyaml>=6.0"
|
"pyyaml>=6.0"
|
||||||
@@ -53,23 +67,37 @@ WIN_DEPS=(
|
|||||||
"pycaw>=20230407"
|
"pycaw>=20230407"
|
||||||
"screen-brightness-control>=0.20.0"
|
"screen-brightness-control>=0.20.0"
|
||||||
"monitorcontrol>=3.0.0"
|
"monitorcontrol>=3.0.0"
|
||||||
"pystray>=0.19.0"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Visualizer dependencies
|
# Visualizer dependencies
|
||||||
VIS_DEPS=(
|
VIS_DEPS=(
|
||||||
"soundcard>=0.4.0"
|
"soundcard>=0.4.0"
|
||||||
"numpy>=1.24.0,<2.0"
|
"numpy>=1.24.0,<2.0"
|
||||||
|
# pystray lives here (not WIN_DEPS) so its transitive Pillow resolves in the
|
||||||
|
# same pass as CORE_DEPS. Keeping it in the per-dep WIN_DEPS loop downloaded
|
||||||
|
# a second Pillow version that clobbered the core one on unzip, producing
|
||||||
|
# "_imaging extension was built for another version of Pillow" at runtime.
|
||||||
|
"pystray>=0.19.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
ALL_DEPS=("${CORE_DEPS[@]}" "${WIN_DEPS[@]}" "${VIS_DEPS[@]}")
|
# Resolve core + visualizer deps in a SINGLE call so pip picks compatible
|
||||||
|
# transitive versions (notably pydantic/pydantic-core must match).
|
||||||
|
# Per-dep loops resolve each dep independently and can leave mismatched
|
||||||
|
# transitive versions that overwrite each other in the site-packages unzip.
|
||||||
|
CROSS_DEPS=("${CORE_DEPS[@]}" "${VIS_DEPS[@]}")
|
||||||
|
pip download --quiet --no-cache-dir --dest "$WHEEL_DIR" \
|
||||||
|
--platform win_amd64 --python-version "${PYTHON_SHORT}" \
|
||||||
|
--implementation cp --only-binary :all: \
|
||||||
|
"${CROSS_DEPS[@]}"
|
||||||
|
|
||||||
for dep in "${ALL_DEPS[@]}"; do
|
# Windows-only deps in a loop with --pre: winsdk only ships beta wheels
|
||||||
pip download --quiet --no-cache-dir --dest "$WHEEL_DIR" \
|
# (1.0.0bNN) and each dep needs its own platform/non-platform fallback.
|
||||||
|
for dep in "${WIN_DEPS[@]}"; do
|
||||||
|
pip download --pre --quiet --no-cache-dir --dest "$WHEEL_DIR" \
|
||||||
--platform win_amd64 --python-version "${PYTHON_SHORT}" \
|
--platform win_amd64 --python-version "${PYTHON_SHORT}" \
|
||||||
--implementation cp --only-binary :all: \
|
--implementation cp --only-binary :all: \
|
||||||
"$dep" 2>/dev/null || \
|
"$dep" 2>/dev/null || \
|
||||||
pip download --quiet --no-cache-dir --dest "$WHEEL_DIR" \
|
pip download --pre --quiet --no-cache-dir --dest "$WHEEL_DIR" \
|
||||||
--only-binary :all: \
|
--only-binary :all: \
|
||||||
"$dep"
|
"$dep"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -133,6 +133,23 @@ Object.assign(window, {
|
|||||||
// Initialization (DOMContentLoaded)
|
// Initialization (DOMContentLoaded)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
// Prevent <dialog>.showModal() from auto-focusing the first input field.
|
||||||
|
// On touch devices this pops up the on-screen keyboard, which is confusing
|
||||||
|
// when the user just opened a dialog. Force focus onto the dialog itself.
|
||||||
|
const _origShowModal = HTMLDialogElement.prototype.showModal;
|
||||||
|
HTMLDialogElement.prototype.showModal = function (...args) {
|
||||||
|
if (!this.hasAttribute('tabindex')) {
|
||||||
|
this.setAttribute('tabindex', '-1');
|
||||||
|
}
|
||||||
|
const result = _origShowModal.apply(this, args);
|
||||||
|
const active = document.activeElement;
|
||||||
|
if (active && active !== this && this.contains(active)) {
|
||||||
|
active.blur();
|
||||||
|
this.focus({ preventScroll: true });
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', async () => {
|
window.addEventListener('DOMContentLoaded', async () => {
|
||||||
// Cache DOM references
|
// Cache DOM references
|
||||||
cacheDom();
|
cacheDom();
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "media-server-frontend",
|
"name": "media-server-frontend",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Frontend build tooling for media server WebUI",
|
"description": "Frontend build tooling for media server WebUI",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "media-server"
|
name = "media-server"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
description = "REST API server for controlling system-wide media playback"
|
description = "REST API server for controlling system-wide media playback"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { text = "MIT" }
|
license = { text = "MIT" }
|
||||||
|
|||||||
Reference in New Issue
Block a user