Spaces:
Running
Running
Hermes Agent commited on
Commit ·
aae3fe7
1
Parent(s): 13c408a
fix(all): restore spotlight hero banner, fix detail poster overlap, enable reliable Home/Movies/TV navigation, and prevent STRM pruning playback errors
Browse files- apex/repair_jellyfin_libraries.py +72 -5
- apex/tg_streamer.py +4 -47
- apex/ui/abyss.css +116 -8
- apex/ui/spotlight-loader.js +74 -67
apex/repair_jellyfin_libraries.py
CHANGED
|
@@ -531,9 +531,15 @@ def heal_mismatched_media(roots=None, databases=None):
|
|
| 531 |
conn.close()
|
| 532 |
except Exception:
|
| 533 |
pass
|
| 534 |
-
elif f.endswith(".strm")
|
| 535 |
try:
|
| 536 |
-
os.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 537 |
except Exception:
|
| 538 |
pass
|
| 539 |
try:
|
|
@@ -558,8 +564,11 @@ def heal_mismatched_media(roots=None, databases=None):
|
|
| 558 |
new_strm = os.path.join(dirpath, f"{clean_strm_base}{ext}")
|
| 559 |
if not os.path.exists(new_strm):
|
| 560 |
try:
|
| 561 |
-
|
| 562 |
-
|
|
|
|
|
|
|
|
|
|
| 563 |
for db_p in db_list:
|
| 564 |
if os.path.exists(db_p):
|
| 565 |
try:
|
|
@@ -570,7 +579,7 @@ def heal_mismatched_media(roots=None, databases=None):
|
|
| 570 |
except Exception:
|
| 571 |
pass
|
| 572 |
except Exception as strm_err:
|
| 573 |
-
print(f"[REPAIR] Note
|
| 574 |
elif fn.lower() in ("movie.nfo", "tvshow.nfo"):
|
| 575 |
_heal_nfo_file(os.path.join(dirpath, fn))
|
| 576 |
|
|
@@ -784,6 +793,63 @@ def repair_system_xml(configs=None):
|
|
| 784 |
print(f"[REPAIR] Note repairing {cfg}: {e}")
|
| 785 |
|
| 786 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 787 |
def main():
|
| 788 |
env_force = os.environ.get("RESET_JELLYFIN_PASSWORD", "").strip().lower() in ("1", "true", "yes")
|
| 789 |
manual_trigger = any(os.path.exists(p) for p in MANUAL_RESET_TRIGGERS)
|
|
@@ -813,6 +879,7 @@ def main():
|
|
| 813 |
heal_subtitle_files()
|
| 814 |
heal_mismatched_media()
|
| 815 |
heal_channel_and_db_names()
|
|
|
|
| 816 |
repair_user_subtitle_preferences()
|
| 817 |
repair_user_policy_preferences()
|
| 818 |
repair_system_xml()
|
|
|
|
| 531 |
conn.close()
|
| 532 |
except Exception:
|
| 533 |
pass
|
| 534 |
+
elif f.endswith(".strm"):
|
| 535 |
try:
|
| 536 |
+
if not os.path.exists(dst_f):
|
| 537 |
+
shutil.copy2(src_f, dst_f)
|
| 538 |
+
elif not os.path.exists(src_f):
|
| 539 |
+
try:
|
| 540 |
+
os.link(dst_f, src_f)
|
| 541 |
+
except OSError:
|
| 542 |
+
shutil.copy2(dst_f, src_f)
|
| 543 |
except Exception:
|
| 544 |
pass
|
| 545 |
try:
|
|
|
|
| 564 |
new_strm = os.path.join(dirpath, f"{clean_strm_base}{ext}")
|
| 565 |
if not os.path.exists(new_strm):
|
| 566 |
try:
|
| 567 |
+
try:
|
| 568 |
+
os.link(old_strm, new_strm)
|
| 569 |
+
except OSError:
|
| 570 |
+
shutil.copy2(old_strm, new_strm)
|
| 571 |
+
print(f"[REPAIR] Created clean alias strm: '{fn}' -> '{clean_strm_base}{ext}'")
|
| 572 |
for db_p in db_list:
|
| 573 |
if os.path.exists(db_p):
|
| 574 |
try:
|
|
|
|
| 579 |
except Exception:
|
| 580 |
pass
|
| 581 |
except Exception as strm_err:
|
| 582 |
+
print(f"[REPAIR] Note creating strm alias {old_strm}: {strm_err}")
|
| 583 |
elif fn.lower() in ("movie.nfo", "tvshow.nfo"):
|
| 584 |
_heal_nfo_file(os.path.join(dirpath, fn))
|
| 585 |
|
|
|
|
| 793 |
print(f"[REPAIR] Note repairing {cfg}: {e}")
|
| 794 |
|
| 795 |
|
| 796 |
+
|
| 797 |
+
def heal_missing_db_strm_paths(databases=None):
|
| 798 |
+
"""Ensures every single .strm path recorded in Jellyfin BaseItems actually exists on disk.
|
| 799 |
+
If a file is missing due to prior pruning or dot/dash naming differences, reconstructs
|
| 800 |
+
or hardlinks it immediately from matching media on disk."""
|
| 801 |
+
db_list = databases or TARGET_DATABASES
|
| 802 |
+
for db_p in db_list:
|
| 803 |
+
if not os.path.exists(db_p):
|
| 804 |
+
continue
|
| 805 |
+
try:
|
| 806 |
+
conn = sqlite3.connect(db_p, timeout=10)
|
| 807 |
+
cur = conn.cursor()
|
| 808 |
+
cur.execute("SELECT Id, Path, Name FROM BaseItems WHERE Path LIKE '%.strm'")
|
| 809 |
+
rows = cur.fetchall()
|
| 810 |
+
repaired = 0
|
| 811 |
+
for row_id, path, name in rows:
|
| 812 |
+
if not path or os.path.exists(path):
|
| 813 |
+
continue
|
| 814 |
+
parent_dir = os.path.dirname(path)
|
| 815 |
+
file_name = os.path.basename(path)
|
| 816 |
+
found_src = None
|
| 817 |
+
|
| 818 |
+
# 1. Search in the same parent directory for any .strm
|
| 819 |
+
if os.path.isdir(parent_dir):
|
| 820 |
+
candidates = [os.path.join(parent_dir, f) for f in os.listdir(parent_dir) if f.lower().endswith(".strm")]
|
| 821 |
+
if candidates:
|
| 822 |
+
found_src = candidates[0]
|
| 823 |
+
|
| 824 |
+
# 2. If parent_dir doesn't exist, search grandparent directory for a matching folder
|
| 825 |
+
if not found_src:
|
| 826 |
+
grandparent = os.path.dirname(parent_dir)
|
| 827 |
+
parent_name = os.path.basename(parent_dir)
|
| 828 |
+
if os.path.isdir(grandparent):
|
| 829 |
+
norm_parent = re.sub(r"[^a-zA-Z0-9]", "", parent_name).lower()
|
| 830 |
+
for sib in os.listdir(grandparent):
|
| 831 |
+
sib_path = os.path.join(grandparent, sib)
|
| 832 |
+
if os.path.isdir(sib_path):
|
| 833 |
+
if re.sub(r"[^a-zA-Z0-9]", "", sib).lower() == norm_parent:
|
| 834 |
+
cands = [os.path.join(sib_path, f) for f in os.listdir(sib_path) if f.lower().endswith(".strm")]
|
| 835 |
+
if cands:
|
| 836 |
+
found_src = cands[0]
|
| 837 |
+
break
|
| 838 |
+
|
| 839 |
+
if found_src and os.path.exists(found_src):
|
| 840 |
+
os.makedirs(parent_dir, exist_ok=True)
|
| 841 |
+
try:
|
| 842 |
+
os.link(found_src, path)
|
| 843 |
+
except OSError:
|
| 844 |
+
shutil.copy2(found_src, path)
|
| 845 |
+
repaired += 1
|
| 846 |
+
print(f"[REPAIR] Healed missing DB STRM: '{path}' <- '{found_src}'")
|
| 847 |
+
conn.close()
|
| 848 |
+
if repaired:
|
| 849 |
+
print(f"[REPAIR] Successfully healed {repaired} missing STRM file(s) for DB {db_p}")
|
| 850 |
+
except Exception as e:
|
| 851 |
+
print(f"[REPAIR] Note healing missing DB STRMs: {e}")
|
| 852 |
+
|
| 853 |
def main():
|
| 854 |
env_force = os.environ.get("RESET_JELLYFIN_PASSWORD", "").strip().lower() in ("1", "true", "yes")
|
| 855 |
manual_trigger = any(os.path.exists(p) for p in MANUAL_RESET_TRIGGERS)
|
|
|
|
| 879 |
heal_subtitle_files()
|
| 880 |
heal_mismatched_media()
|
| 881 |
heal_channel_and_db_names()
|
| 882 |
+
heal_missing_db_strm_paths()
|
| 883 |
repair_user_subtitle_preferences()
|
| 884 |
repair_user_policy_preferences()
|
| 885 |
repair_system_xml()
|
apex/tg_streamer.py
CHANGED
|
@@ -5373,53 +5373,10 @@ async def subtitles_sync(request):
|
|
| 5373 |
|
| 5374 |
|
| 5375 |
async def _prune_orphan_strms(movies_dir, shows_dir, keep):
|
| 5376 |
-
"""
|
| 5377 |
-
|
| 5378 |
-
|
| 5379 |
-
|
| 5380 |
-
Only files whose contents reference our own streamer are touched, so
|
| 5381 |
-
unrelated .strm files are never removed.
|
| 5382 |
-
"""
|
| 5383 |
-
removed = 0
|
| 5384 |
-
for root_dir in (movies_dir, shows_dir):
|
| 5385 |
-
if not os.path.isdir(root_dir):
|
| 5386 |
-
continue
|
| 5387 |
-
for root, _, files in os.walk(root_dir):
|
| 5388 |
-
for f in files:
|
| 5389 |
-
if not f.lower().endswith(".strm"):
|
| 5390 |
-
continue
|
| 5391 |
-
path = os.path.normpath(os.path.join(root, f))
|
| 5392 |
-
if path in keep:
|
| 5393 |
-
continue
|
| 5394 |
-
try:
|
| 5395 |
-
with open(path, "r", encoding="utf-8", errors="ignore") as fh:
|
| 5396 |
-
content = fh.read(256)
|
| 5397 |
-
except OSError:
|
| 5398 |
-
continue
|
| 5399 |
-
stream_port = os.environ.get(
|
| 5400 |
-
"TG_LISTEN_PORT", os.environ.get("PORT", "8080")
|
| 5401 |
-
)
|
| 5402 |
-
is_our_stream = (
|
| 5403 |
-
"/stream/" in content
|
| 5404 |
-
or f"127.0.0.1:{stream_port}" in content
|
| 5405 |
-
or f":{stream_port}/" in content
|
| 5406 |
-
)
|
| 5407 |
-
if not is_our_stream:
|
| 5408 |
-
continue
|
| 5409 |
-
try:
|
| 5410 |
-
os.remove(path)
|
| 5411 |
-
removed += 1
|
| 5412 |
-
logger.info(f"[RESTORE] Pruned orphaned .strm: {path}")
|
| 5413 |
-
parent_dir = os.path.dirname(path)
|
| 5414 |
-
if os.path.isdir(parent_dir) and not os.listdir(parent_dir):
|
| 5415 |
-
try:
|
| 5416 |
-
os.rmdir(parent_dir)
|
| 5417 |
-
logger.info(f"[RESTORE] Cleaned empty parent folder: {parent_dir}")
|
| 5418 |
-
except OSError:
|
| 5419 |
-
pass
|
| 5420 |
-
except OSError as e:
|
| 5421 |
-
logger.warning(f"[RESTORE] Could not prune {path}: {e}")
|
| 5422 |
-
return removed
|
| 5423 |
|
| 5424 |
|
| 5425 |
async def restore_cached_strm_files():
|
|
|
|
| 5373 |
|
| 5374 |
|
| 5375 |
async def _prune_orphan_strms(movies_dir, shows_dir, keep):
|
| 5376 |
+
"""Safely preserve all .strm files. Deletion is permanently disabled to ensure
|
| 5377 |
+
seamless media playback across all library naming variants."""
|
| 5378 |
+
logger.info("[RESTORE] Automatic .strm pruning is safely disabled to safeguard media playback.")
|
| 5379 |
+
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5380 |
|
| 5381 |
|
| 5382 |
async def restore_cached_strm_files():
|
apex/ui/abyss.css
CHANGED
|
@@ -4306,8 +4306,8 @@ div[data-category="nextup"] .cardScalable,
|
|
| 4306 |
align-items: center !important;
|
| 4307 |
padding-left: 16px !important;
|
| 4308 |
padding-right: 16px !important;
|
| 4309 |
-
z-index:
|
| 4310 |
-
pointer-events:
|
| 4311 |
}
|
| 4312 |
|
| 4313 |
#apex-tv-topbar .apex-tv-tabs {
|
|
@@ -4387,12 +4387,9 @@ div[data-category="nextup"] .cardScalable,
|
|
| 4387 |
}
|
| 4388 |
}
|
| 4389 |
|
| 4390 |
-
/* Hide topbar
|
| 4391 |
-
body
|
| 4392 |
-
body
|
| 4393 |
-
body:has(.nowPlayingPage:not(.hide)) #apex-tv-topbar,
|
| 4394 |
-
body:has(#dashboardPage:not(.hide)) #apex-tv-topbar,
|
| 4395 |
-
body.jellyfin-settings-page #apex-tv-topbar {
|
| 4396 |
display: none !important;
|
| 4397 |
opacity: 0 !important;
|
| 4398 |
pointer-events: none !important;
|
|
@@ -4453,3 +4450,114 @@ body.jellyfin-settings-page #apex-tv-topbar {
|
|
| 4453 |
box-sizing: border-box !important;
|
| 4454 |
}
|
| 4455 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4306 |
align-items: center !important;
|
| 4307 |
padding-left: 16px !important;
|
| 4308 |
padding-right: 16px !important;
|
| 4309 |
+
z-index: 99999 !important;
|
| 4310 |
+
pointer-events: auto !important;
|
| 4311 |
}
|
| 4312 |
|
| 4313 |
#apex-tv-topbar .apex-tv-tabs {
|
|
|
|
| 4387 |
}
|
| 4388 |
}
|
| 4389 |
|
| 4390 |
+
/* Hide topbar only during active fullscreen video playback or settings */
|
| 4391 |
+
body.videoPlayerContainerVisible #apex-tv-topbar,
|
| 4392 |
+
body.hide-apex-topbar #apex-tv-topbar {
|
|
|
|
|
|
|
|
|
|
| 4393 |
display: none !important;
|
| 4394 |
opacity: 0 !important;
|
| 4395 |
pointer-events: none !important;
|
|
|
|
| 4450 |
box-sizing: border-box !important;
|
| 4451 |
}
|
| 4452 |
}
|
| 4453 |
+
|
| 4454 |
+
|
| 4455 |
+
/* ========================================================
|
| 4456 |
+
DETAIL PAGE: POSTER THUMBNAIL & METADATA SPACING FIX
|
| 4457 |
+
Prevents poster from overlapping title/metadata text
|
| 4458 |
+
======================================================== */
|
| 4459 |
+
#itemDetailPage .detailRibbon,
|
| 4460 |
+
.layout-mobile #itemDetailPage .detailRibbon,
|
| 4461 |
+
.layout-desktop #itemDetailPage .detailRibbon,
|
| 4462 |
+
.layout-tv #itemDetailPage .detailRibbon,
|
| 4463 |
+
.detailRibbon {
|
| 4464 |
+
display: flex !important;
|
| 4465 |
+
flex-direction: row !important;
|
| 4466 |
+
align-items: flex-start !important;
|
| 4467 |
+
gap: 18px !important;
|
| 4468 |
+
padding-left: 16px !important;
|
| 4469 |
+
padding-right: 16px !important;
|
| 4470 |
+
margin-top: 0 !important;
|
| 4471 |
+
box-sizing: border-box !important;
|
| 4472 |
+
}
|
| 4473 |
+
|
| 4474 |
+
#itemDetailPage .detailImageContainer,
|
| 4475 |
+
.detailImageContainer {
|
| 4476 |
+
position: relative !important;
|
| 4477 |
+
flex-shrink: 0 !important;
|
| 4478 |
+
width: 140px !important;
|
| 4479 |
+
max-width: 140px !important;
|
| 4480 |
+
margin: 0 !important;
|
| 4481 |
+
z-index: 2 !important;
|
| 4482 |
+
}
|
| 4483 |
+
|
| 4484 |
+
#itemDetailPage .detailImageContainer .card,
|
| 4485 |
+
.layout-mobile #itemDetailPage .detailImageContainer .card,
|
| 4486 |
+
.layout-desktop #itemDetailPage .detailImageContainer .card,
|
| 4487 |
+
.layout-tv #itemDetailPage .detailImageContainer .card,
|
| 4488 |
+
.detailImageContainer .card {
|
| 4489 |
+
position: relative !important;
|
| 4490 |
+
top: auto !important;
|
| 4491 |
+
left: auto !important;
|
| 4492 |
+
margin: 0 !important;
|
| 4493 |
+
width: 140px !important;
|
| 4494 |
+
max-width: 140px !important;
|
| 4495 |
+
border-radius: 12px !important;
|
| 4496 |
+
}
|
| 4497 |
+
|
| 4498 |
+
#itemDetailPage .detailPagePrimaryContent,
|
| 4499 |
+
.layout-mobile #itemDetailPage .detailPagePrimaryContent,
|
| 4500 |
+
.layout-desktop #itemDetailPage .detailPagePrimaryContent,
|
| 4501 |
+
.layout-tv #itemDetailPage .detailPagePrimaryContent,
|
| 4502 |
+
.detailPagePrimaryContent {
|
| 4503 |
+
flex: 1 1 auto !important;
|
| 4504 |
+
min-width: 0 !important;
|
| 4505 |
+
padding-left: 0 !important;
|
| 4506 |
+
margin-left: 0 !important;
|
| 4507 |
+
}
|
| 4508 |
+
|
| 4509 |
+
#itemDetailPage .itemName,
|
| 4510 |
+
.itemName {
|
| 4511 |
+
word-break: break-word !important;
|
| 4512 |
+
overflow-wrap: break-word !important;
|
| 4513 |
+
}
|
| 4514 |
+
|
| 4515 |
+
/* Universal Abyss Spotlight Hero Banner Iframe */
|
| 4516 |
+
iframe.featurediframe {
|
| 4517 |
+
width: 100% !important;
|
| 4518 |
+
display: block !important;
|
| 4519 |
+
border: none !important;
|
| 4520 |
+
margin: 0 !important;
|
| 4521 |
+
padding: 0 !important;
|
| 4522 |
+
min-height: 380px !important;
|
| 4523 |
+
height: 52vh !important;
|
| 4524 |
+
opacity: 1 !important;
|
| 4525 |
+
visibility: visible !important;
|
| 4526 |
+
}
|
| 4527 |
+
|
| 4528 |
+
@media (orientation: landscape) {
|
| 4529 |
+
iframe.featurediframe {
|
| 4530 |
+
height: 70vh !important;
|
| 4531 |
+
min-height: 340px !important;
|
| 4532 |
+
}
|
| 4533 |
+
}
|
| 4534 |
+
|
| 4535 |
+
/* Suppress Cast, SyncPlay, Menu/Hamburger buttons */
|
| 4536 |
+
button[aria-label="Menu"],
|
| 4537 |
+
button[aria-label="menu"],
|
| 4538 |
+
button:has(svg[data-testid="MenuIcon"]),
|
| 4539 |
+
.claude-stepped-menu,
|
| 4540 |
+
.mainDrawerButton,
|
| 4541 |
+
.headerButtonMenu,
|
| 4542 |
+
.headerSyncplayButton,
|
| 4543 |
+
.btnSyncPlay,
|
| 4544 |
+
.headerCastButton,
|
| 4545 |
+
.btnCast,
|
| 4546 |
+
button[data-action="cast"],
|
| 4547 |
+
button[data-action="syncplay"],
|
| 4548 |
+
button[title*="SyncPlay" i],
|
| 4549 |
+
button[aria-label*="SyncPlay" i],
|
| 4550 |
+
button[title*="Cast" i],
|
| 4551 |
+
button[aria-label*="Cast" i],
|
| 4552 |
+
button:has(svg[data-testid="CastIcon"]),
|
| 4553 |
+
button:has(svg[data-testid="GroupIcon"]),
|
| 4554 |
+
button:has(svg[data-testid="PeopleIcon"]),
|
| 4555 |
+
.headerLeft,
|
| 4556 |
+
.headerTabs.sectionTabs {
|
| 4557 |
+
display: none !important;
|
| 4558 |
+
visibility: hidden !important;
|
| 4559 |
+
pointer-events: none !important;
|
| 4560 |
+
width: 0 !important;
|
| 4561 |
+
height: 0 !important;
|
| 4562 |
+
opacity: 0 !important;
|
| 4563 |
+
}
|
apex/ui/spotlight-loader.js
CHANGED
|
@@ -262,12 +262,16 @@
|
|
| 262 |
});
|
| 263 |
|
| 264 |
function isRouteVisible(indexPage, homeTab) {
|
| 265 |
-
if (!
|
| 266 |
if (document.hidden) return false;
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
}
|
| 272 |
|
| 273 |
function connectLifecycle(indexPage, homeTab, favoritesTab, iframe) {
|
|
@@ -328,60 +332,78 @@
|
|
| 328 |
|
| 329 |
var currentSync = null;
|
| 330 |
|
| 331 |
-
function
|
| 332 |
-
var
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
}
|
| 343 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 344 |
}
|
| 345 |
|
| 346 |
function installSpotlight() {
|
| 347 |
var installed = false;
|
| 348 |
safe(function () {
|
| 349 |
-
var
|
| 350 |
-
if (!
|
| 351 |
clearSpotlightLifecycle();
|
| 352 |
return;
|
| 353 |
}
|
| 354 |
-
|
|
|
|
| 355 |
if (!homeTab) {
|
| 356 |
-
clearSpotlightLifecycle();
|
| 357 |
return;
|
| 358 |
}
|
| 359 |
-
|
|
|
|
|
|
|
| 360 |
|
| 361 |
installFrameStyle();
|
| 362 |
installBackdropOverlay();
|
| 363 |
|
| 364 |
var iframe = homeTab.querySelector ? homeTab.querySelector("." + FRAME_CLASS) : null;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
if (!iframe) {
|
| 366 |
iframe = document.createElement("iframe");
|
| 367 |
iframe.className = FRAME_CLASS + " focusable";
|
| 368 |
iframe.setAttribute("tabindex", "0");
|
| 369 |
iframe.src = spotlightUrl;
|
| 370 |
iframe.title = "Abyss Spotlight";
|
|
|
|
|
|
|
|
|
|
| 371 |
iframe.addEventListener("blur", function () { spotlightFocused = false; });
|
| 372 |
-
var sections = homeTab.querySelector ? homeTab.querySelector(".sections") : null;
|
|
|
|
|
|
|
|
|
|
| 373 |
homeTab.insertBefore(iframe, sections || homeTab.firstChild);
|
| 374 |
}
|
| 375 |
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
|
|
|
|
|
|
|
|
|
| 380 |
|
| 381 |
if (
|
| 382 |
indexPage !== currentIndexPage ||
|
| 383 |
homeTab !== currentHomeTab ||
|
| 384 |
-
favoritesTab !== currentFavoritesTab ||
|
| 385 |
iframe !== currentIframe
|
| 386 |
) {
|
| 387 |
currentIndexPage = indexPage;
|
|
@@ -445,7 +467,7 @@
|
|
| 445 |
var logoSvgUrl = (window.location.pathname.indexOf("/web") !== -1) ? "ui/apex-logo.svg" : "/jellyfin/web/ui/apex-logo.svg";
|
| 446 |
|
| 447 |
// 1. Favicon sync
|
| 448 |
-
var links = document.querySelectorAll("
|
| 449 |
for (var i = 0; i < links.length; i++) {
|
| 450 |
if (links[i].getAttribute("data-apex-logo") !== "true") {
|
| 451 |
links[i].setAttribute("data-apex-logo", "true");
|
|
@@ -597,7 +619,7 @@
|
|
| 597 |
}, 400);
|
| 598 |
}
|
| 599 |
|
| 600 |
-
var libIds = { movies:
|
| 601 |
var librariesDiscovered = false;
|
| 602 |
|
| 603 |
function discoverLibraries() {
|
|
@@ -635,53 +657,27 @@
|
|
| 635 |
return;
|
| 636 |
}
|
| 637 |
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
return;
|
| 644 |
-
}
|
| 645 |
-
}
|
| 646 |
-
if (libIds.movies) {
|
| 647 |
-
var url = "/details?id=" + libIds.movies;
|
| 648 |
-
if (window.Emby && window.Emby.Page && typeof window.Emby.Page.show === "function") {
|
| 649 |
-
window.Emby.Page.show(url);
|
| 650 |
-
return;
|
| 651 |
-
}
|
| 652 |
-
window.location.hash = "#" + url;
|
| 653 |
return;
|
| 654 |
}
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
if (name === "TV Shows") {
|
| 660 |
-
var tvCards = document.querySelectorAll('.card[data-type="CollectionFolder"], [data-category="views"] .card');
|
| 661 |
-
for (var t = 0; t < tvCards.length; t++) {
|
| 662 |
-
var text = (tvCards[t].textContent || "").toLowerCase();
|
| 663 |
-
if (text.indexOf("tv") !== -1 || text.indexOf("show") !== -1 || text.indexOf("series") !== -1) {
|
| 664 |
-
tvCards[t].click();
|
| 665 |
-
return;
|
| 666 |
-
}
|
| 667 |
-
}
|
| 668 |
-
if (libIds.tvshows) {
|
| 669 |
-
var url = "/details?id=" + libIds.tvshows;
|
| 670 |
-
if (window.Emby && window.Emby.Page && typeof window.Emby.Page.show === "function") {
|
| 671 |
-
window.Emby.Page.show(url);
|
| 672 |
-
return;
|
| 673 |
-
}
|
| 674 |
-
window.location.hash = "#" + url;
|
| 675 |
return;
|
| 676 |
}
|
| 677 |
-
window.location.hash = "#
|
| 678 |
return;
|
| 679 |
}
|
|
|
|
| 680 |
}
|
| 681 |
|
| 682 |
function updateActiveNavTab() {
|
| 683 |
var hash = window.location.hash || "";
|
| 684 |
-
|
| 685 |
for (var i = 0; i < links.length; i++) {
|
| 686 |
var item = links[i];
|
| 687 |
var navType = item.getAttribute("data-nav");
|
|
@@ -751,6 +747,17 @@
|
|
| 751 |
document.body.appendChild(tvTopbar);
|
| 752 |
}
|
| 753 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 754 |
// 2. Hide Hamburger, Cast, SyncPlay, and stray drawer buttons completely
|
| 755 |
var unwanted = document.querySelectorAll(
|
| 756 |
'button[aria-label="Menu"], button[aria-label="menu"], ' +
|
|
|
|
| 262 |
});
|
| 263 |
|
| 264 |
function isRouteVisible(indexPage, homeTab) {
|
| 265 |
+
if (!homeTab || !homeTab.isConnected) return false;
|
| 266 |
if (document.hidden) return false;
|
| 267 |
+
var hash = window.location.hash || "";
|
| 268 |
+
if (hash && hash !== "#/" && hash !== "#" && hash.indexOf("/home") === -1) {
|
| 269 |
+
if (hash.indexOf("details") !== -1 || hash.indexOf("search") !== -1 || hash.indexOf("settings") !== -1 || hash.indexOf("dashboard") !== -1 || hash.indexOf("video") !== -1) {
|
| 270 |
+
return false;
|
| 271 |
+
}
|
| 272 |
+
}
|
| 273 |
+
if (indexPage && (indexPage.classList.contains("hide") || indexPage.hidden)) return false;
|
| 274 |
+
return true;
|
| 275 |
}
|
| 276 |
|
| 277 |
function connectLifecycle(indexPage, homeTab, favoritesTab, iframe) {
|
|
|
|
| 332 |
|
| 333 |
var currentSync = null;
|
| 334 |
|
| 335 |
+
function findHomeTab() {
|
| 336 |
+
var homeTab = document.querySelector("#homeTab");
|
| 337 |
+
if (homeTab && homeTab.isConnected) return homeTab;
|
| 338 |
+
|
| 339 |
+
var indexPage = document.querySelector("#indexPage");
|
| 340 |
+
if (indexPage && indexPage.isConnected) {
|
| 341 |
+
var tab = indexPage.querySelector("#homeTab, .pageTabContent.is-active, .tabContent.is-active");
|
| 342 |
+
if (tab) return tab;
|
| 343 |
+
var sec = indexPage.querySelector(".sections, .homeSectionsContainer");
|
| 344 |
+
if (sec && sec.parentElement) return sec.parentElement;
|
| 345 |
+
return indexPage;
|
| 346 |
}
|
| 347 |
+
|
| 348 |
+
var activeTab = document.querySelector(".pageTabContent.is-active, div[data-role='page']:not(.hide) .homeSectionsContainer");
|
| 349 |
+
if (activeTab) return activeTab.closest(".pageTabContent, div[data-role='page']") || activeTab;
|
| 350 |
+
|
| 351 |
+
return null;
|
| 352 |
}
|
| 353 |
|
| 354 |
function installSpotlight() {
|
| 355 |
var installed = false;
|
| 356 |
safe(function () {
|
| 357 |
+
var hash = window.location.hash || "";
|
| 358 |
+
if (hash.indexOf("dashboard") !== -1 || hash.indexOf("settings") !== -1 || hash.indexOf("video") !== -1) {
|
| 359 |
clearSpotlightLifecycle();
|
| 360 |
return;
|
| 361 |
}
|
| 362 |
+
|
| 363 |
+
var homeTab = findHomeTab();
|
| 364 |
if (!homeTab) {
|
|
|
|
| 365 |
return;
|
| 366 |
}
|
| 367 |
+
|
| 368 |
+
var indexPage = homeTab.closest("#indexPage, div[data-role='page'], .page") || homeTab;
|
| 369 |
+
var favoritesTab = document.querySelector("#favoritesTab");
|
| 370 |
|
| 371 |
installFrameStyle();
|
| 372 |
installBackdropOverlay();
|
| 373 |
|
| 374 |
var iframe = homeTab.querySelector ? homeTab.querySelector("." + FRAME_CLASS) : null;
|
| 375 |
+
if (!iframe) {
|
| 376 |
+
iframe = document.querySelector("." + FRAME_CLASS);
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
if (!iframe) {
|
| 380 |
iframe = document.createElement("iframe");
|
| 381 |
iframe.className = FRAME_CLASS + " focusable";
|
| 382 |
iframe.setAttribute("tabindex", "0");
|
| 383 |
iframe.src = spotlightUrl;
|
| 384 |
iframe.title = "Abyss Spotlight";
|
| 385 |
+
iframe.style.width = "100%";
|
| 386 |
+
iframe.style.display = "block";
|
| 387 |
+
iframe.style.border = "none";
|
| 388 |
iframe.addEventListener("blur", function () { spotlightFocused = false; });
|
| 389 |
+
var sections = homeTab.querySelector ? homeTab.querySelector(".sections, .homeSectionsContainer") : null;
|
| 390 |
+
homeTab.insertBefore(iframe, sections || homeTab.firstChild);
|
| 391 |
+
} else if (iframe.parentElement !== homeTab) {
|
| 392 |
+
var sections = homeTab.querySelector ? homeTab.querySelector(".sections, .homeSectionsContainer") : null;
|
| 393 |
homeTab.insertBefore(iframe, sections || homeTab.firstChild);
|
| 394 |
}
|
| 395 |
|
| 396 |
+
iframe.style.display = "block";
|
| 397 |
+
iframe.style.visibility = "visible";
|
| 398 |
+
iframe.style.opacity = "1";
|
| 399 |
+
document.documentElement.classList.add("abyss-spotlight-visible");
|
| 400 |
+
|
| 401 |
+
if (indexPage && indexPage.classList) indexPage.classList.add("abyss-spotlight-active");
|
| 402 |
+
if (homeTab.classList) homeTab.classList.add("abyss-spotlight-active");
|
| 403 |
|
| 404 |
if (
|
| 405 |
indexPage !== currentIndexPage ||
|
| 406 |
homeTab !== currentHomeTab ||
|
|
|
|
| 407 |
iframe !== currentIframe
|
| 408 |
) {
|
| 409 |
currentIndexPage = indexPage;
|
|
|
|
| 467 |
var logoSvgUrl = (window.location.pathname.indexOf("/web") !== -1) ? "ui/apex-logo.svg" : "/jellyfin/web/ui/apex-logo.svg";
|
| 468 |
|
| 469 |
// 1. Favicon sync
|
| 470 |
+
var links = document.querySelectorAll("a[href*=\x22" + targetId + "\x22], [data-id=\x22" + targetId + "\x22]");
|
| 471 |
for (var i = 0; i < links.length; i++) {
|
| 472 |
if (links[i].getAttribute("data-apex-logo") !== "true") {
|
| 473 |
links[i].setAttribute("data-apex-logo", "true");
|
|
|
|
| 619 |
}, 400);
|
| 620 |
}
|
| 621 |
|
| 622 |
+
var libIds = { movies: "f137a2dd21bbc1b99aa5c0f6bf02a805", tvshows: "767bffe4f11c93ef34b805451a696a4e" };
|
| 623 |
var librariesDiscovered = false;
|
| 624 |
|
| 625 |
function discoverLibraries() {
|
|
|
|
| 657 |
return;
|
| 658 |
}
|
| 659 |
|
| 660 |
+
var targetId = name === "Movies" ? libIds.movies : libIds.tvshows;
|
| 661 |
+
if (targetId) {
|
| 662 |
+
var links = document.querySelectorAll("a[href*=\x22" + targetId + "\x22], [data-id=\x22" + targetId + "\x22]");
|
| 663 |
+
if (links.length && links[0].click) {
|
| 664 |
+
links[0].click();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 665 |
return;
|
| 666 |
}
|
| 667 |
+
var targetUrl = "/details?id=" + targetId;
|
| 668 |
+
if (window.Emby && window.Emby.Page && typeof window.Emby.Page.show === "function") {
|
| 669 |
+
window.Emby.Page.show(targetUrl);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 670 |
return;
|
| 671 |
}
|
| 672 |
+
window.location.hash = "#" + targetUrl;
|
| 673 |
return;
|
| 674 |
}
|
| 675 |
+
window.location.hash = name === "Movies" ? "#/movies" : "#/shows";
|
| 676 |
}
|
| 677 |
|
| 678 |
function updateActiveNavTab() {
|
| 679 |
var hash = window.location.hash || "";
|
| 680 |
+
var links = document.querySelectorAll("a[href*=\x22" + targetId + "\x22], [data-id=\x22" + targetId + "\x22]");
|
| 681 |
for (var i = 0; i < links.length; i++) {
|
| 682 |
var item = links[i];
|
| 683 |
var navType = item.getAttribute("data-nav");
|
|
|
|
| 747 |
document.body.appendChild(tvTopbar);
|
| 748 |
}
|
| 749 |
|
| 750 |
+
if (tvTopbar) {
|
| 751 |
+
var isVideo = !!document.querySelector(".videoPlayerContainer:not(.hide), #videoOsdPage:not(.hide), video");
|
| 752 |
+
var hash = window.location.hash || "";
|
| 753 |
+
var isHiddenRoute = hash.indexOf("dashboard") !== -1 || hash.indexOf("settings") !== -1;
|
| 754 |
+
if (isVideo || isHiddenRoute) {
|
| 755 |
+
tvTopbar.style.display = "none";
|
| 756 |
+
} else {
|
| 757 |
+
tvTopbar.style.display = "flex";
|
| 758 |
+
}
|
| 759 |
+
}
|
| 760 |
+
|
| 761 |
// 2. Hide Hamburger, Cast, SyncPlay, and stray drawer buttons completely
|
| 762 |
var unwanted = document.querySelectorAll(
|
| 763 |
'button[aria-label="Menu"], button[aria-label="menu"], ' +
|