summaryrefslogtreecommitdiff
path: root/app/ui
diff options
context:
space:
mode:
authorRaindropsSys <raindrops@equestria.dev>2023-10-27 22:29:56 +0200
committerRaindropsSys <raindrops@equestria.dev>2023-10-27 22:29:56 +0200
commit4d4308c46d4f7801c657cc79d2243e1a81831334 (patch)
treea2e392e0af92b9a3ca3d1b5afb841640276e2294 /app/ui
parent9f9d66afebc59c6c265c4424f7b8fb36d8876541 (diff)
downloadmist-4d4308c46d4f7801c657cc79d2243e1a81831334.tar.gz
mist-4d4308c46d4f7801c657cc79d2243e1a81831334.tar.bz2
mist-4d4308c46d4f7801c657cc79d2243e1a81831334.zip
Updated 32 files, added 279 files, deleted 3 files and renamed 14 files (automated)
Diffstat (limited to 'app/ui')
-rw-r--r--app/ui/albums.php104
-rw-r--r--app/ui/download.php74
-rw-r--r--app/ui/explore.php44
-rw-r--r--app/ui/favorites.php16
-rw-r--r--app/ui/info.php187
-rw-r--r--app/ui/listing.php189
-rw-r--r--app/ui/lyrics.php164
-rw-r--r--app/ui/modal.php49
-rw-r--r--app/ui/navigation.php69
-rw-r--r--app/ui/player-mobile.php104
-rw-r--r--app/ui/player.php76
-rw-r--r--app/ui/queue.php78
-rw-r--r--app/ui/search.php91
-rw-r--r--app/ui/settings.php90
-rw-r--r--app/ui/songs.php5
-rw-r--r--app/ui/update.php67
-rw-r--r--app/ui/welcome.php49
17 files changed, 1456 insertions, 0 deletions
diff --git a/app/ui/albums.php b/app/ui/albums.php
new file mode 100644
index 0000000..e383f81
--- /dev/null
+++ b/app/ui/albums.php
@@ -0,0 +1,104 @@
+<?php header("X-Frame-Options: SAMEORIGIN"); require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; ?>
+<!doctype html>
+<html lang="en">
+<head>
+ <script>
+ if (typeof window.parent.openModal === "undefined") {
+ location.href = "/app/#/albums";
+ }
+ </script>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>albums</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <link href="/assets/dark.css" rel="stylesheet">
+ <link href="/assets/styles.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+ <script src="/assets/localforage.min.js"></script>
+ <script src="/assets/fuse.min.js"></script>
+ <script src="/assets/js/shortcuts.js"></script>
+ <link id="native-css" href="/assets/native.css" rel="stylesheet" disabled>
+</head>
+<body class="crossplatform">
+ <script src="/assets/js/common.js"></script>
+ <div class="container">
+ <br>
+ <h2 style="margin-top: 10px; margin-bottom: 20px; margin-left: 10px;">Albums<input placeholder="Filter" id="filter" class="form-control" style="width: 256px; float: right;" onchange="updateFilter();" onkeyup="updateFilter();"></h2>
+ <div id="album-grid" style="display: grid; grid-template-columns: repeat(5, 1fr);">
+ <?php global $albums;
+
+ $albums = array_filter($albums, function ($i) {
+ global $library;
+ return in_array($i, $library);
+ }, ARRAY_FILTER_USE_KEY);
+
+ uasort($albums, function ($a, $b) {
+ return strcmp($a["title"], $b["title"]);
+ });
+
+ uasort($albums, function ($a, $b) {
+ return strcmp($a["artist"], $b["artist"]);
+ });
+
+ foreach ($albums as $id => $album): ?>
+ <a id="album-<?= $id ?>" data-item-track="<?= $album["title"] ?>" data-item-artist="<?= $album["artist"] ?>" href="listing.php?a=<?= $id ?>" style="padding: 10px; color: inherit; text-decoration: inherit;" class="album">
+ <img class="album-list-art" alt="" src="/assets/content/<?= $id ?>.jpg" style="width: 100%; aspect-ratio: 1; border-radius: 5px; margin-bottom: 5px;">
+ <div class="album-list-item" style="max-width: calc(80vw / 5); white-space: nowrap; overflow: hidden !important; text-overflow: ellipsis;"><?= $album["title"] ?></div>
+ <div class="album-list-item" style="max-width: calc(80vw / 5); opacity: .5; white-space: nowrap; overflow: hidden !important; text-overflow: ellipsis;"><?= $album["artist"] ?></div>
+ </a>
+ <?php endforeach; ?>
+ </div>
+ <?php if (count($albums) === 0): ?>
+ <div class="text-muted" style="position: fixed; display: flex; inset: 0; align-items: center; justify-content: center;">
+ <div style="text-align: center;">
+ <img class="icon" src="/assets/logo-transparent.svg" style="filter: grayscale(1) invert(1); width: 96px; height: 96px;" alt="">
+ <h4 style="opacity: .75;">Add music to your library</h4>
+ <p style="max-width: 300px; margin-left: auto; margin-right: auto;">Browse millions of songs and collect your favorites here.</p>
+ <div class="btn btn-primary" onclick="window.parent.openUI('explore');">Browse Mist</div>
+ </div>
+ </div>
+ <?php endif; ?>
+ <div id="search-results" style="display: none; grid-template-columns: repeat(5, 1fr);"></div>
+ </div>
+
+ <script>
+ let items = Array.from(document.getElementsByClassName("album")).map(i => { return { title: i.getAttribute("data-item-track"), artist: i.getAttribute("data-item-artist"), id: i.id } });
+
+ const fuse = new Fuse(items, {
+ keys: [
+ {
+ name: 'title',
+ weight: 1
+ },
+ {
+ name: 'artist',
+ weight: .5
+ }
+ ]
+ });
+
+ function updateFilter() {
+ let query = document.getElementById("filter").value.trim();
+
+ if (query !== "") {
+ document.getElementById("search-results").style.display = "grid";
+ document.getElementById("album-grid").style.display = "none";
+
+ let results = fuse.search(query);
+ document.getElementById("search-results").innerHTML = "";
+
+ for (let result of results) {
+ document.getElementById("search-results").innerHTML += document.getElementById(result.item.id).outerHTML;
+ }
+ } else {
+ document.getElementById("search-results").style.display = "none";
+ document.getElementById("album-grid").style.display = "grid";
+ }
+ }
+ </script>
+
+ <br><br>
+</body>
+</html> \ No newline at end of file
diff --git a/app/ui/download.php b/app/ui/download.php
new file mode 100644
index 0000000..13f76b0
--- /dev/null
+++ b/app/ui/download.php
@@ -0,0 +1,74 @@
+<?php header("X-Frame-Options: SAMEORIGIN"); require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $songs;
+
+if (!isset($_GET["i"]) || !isset($songs[$_GET["i"]])) {
+ die();
+}
+
+$song = $songs[$_GET["i"]];
+$fileName = str_replace(["/", "<", ">", ":", "\"", "\\", "|", "?", "*"], "-", $song["artist"] . " - " . $song["title"]);
+
+function getSize($bytes) {
+ if ($bytes < 1024) {
+ return $bytes;
+ }
+
+ if ($bytes < 1024**2) {
+ return round($bytes / 1024, 1) . " KB";
+ }
+
+ if ($bytes < 1024**3) {
+ return round($bytes / 1024**2, 1) . " MB";
+ }
+
+ return round($bytes / 1024**3, 1) . " GB";
+}
+
+?>
+<!doctype html>
+<html lang="en">
+<head>
+ <script>
+ if (typeof window.parent.parent.openModal === "undefined") {
+ location.href = "/app/";
+ }
+ </script>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>download</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <link href="/assets/dark.css" rel="stylesheet">
+ <link href="/assets/styles.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+ <script src="/assets/localforage.min.js"></script>
+ <script src="/assets/fuse.min.js"></script>
+ <script src="/assets/js/shortcuts.js"></script>
+ <link id="native-css" href="/assets/native.css" rel="stylesheet" disabled>
+</head>
+<body class="crossplatform" style="background-color: transparent !important;">
+ <script src="/assets/js/common.js"></script>
+ <div style="padding: 1rem;">
+ <p>
+ <?= $song["artist"] ?> — <?= $song["title"] ?><br>
+ <?= $song["album"] ?>
+ </p>
+
+ <p>Select the version of the song you would like to download:</p>
+ <ul class="list-group">
+ <li class="list-group-item">Lossless (FLAC, <?= getSize(filesize($_SERVER['DOCUMENT_ROOT'] . "/assets/content/" . $_GET["i"] . ".flac")) ?>)<a style="float: right;" download="<?= $fileName ?>.flac" href="/assets/content/<?= $_GET["i"] ?>.flac">Download</a></li>
+ <li class="list-group-item">AAC-LC (MP4, <?= getSize(filesize($_SERVER['DOCUMENT_ROOT'] . "/assets/content/" . $_GET["i"] . ".m4a")) ?>)<a style="float: right;" download="<?= $fileName ?>.m4a" href="/assets/content/<?= $_GET["i"] ?>.m4a">Download</a></li>
+ <li class="list-group-item">Album art (JPEG, <?= getSize(filesize($_SERVER['DOCUMENT_ROOT'] . "/assets/content/" . $_GET["i"] . ".jpg")) ?>)<a style="float: right;" download="<?= $fileName ?>.jpg" href="/assets/content/<?= $_GET["i"] ?>.jpg">Download</a></li>
+ </ul>
+ </div>
+
+ <script>
+ window.sizeInterval = setInterval(() => {
+ if (document.body.clientHeight > 0) {
+ clearInterval(sizeInterval);
+ window.parent.document.getElementById("modal-frame").style.height = document.body.clientHeight + "px";
+ }
+ });
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/app/ui/explore.php b/app/ui/explore.php
new file mode 100644
index 0000000..7edfdf1
--- /dev/null
+++ b/app/ui/explore.php
@@ -0,0 +1,44 @@
+<?php header("X-Frame-Options: SAMEORIGIN"); require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $songs; global $albums; ?>
+<!doctype html>
+<html lang="en">
+<head>
+ <script>
+ if (typeof window.parent.openModal === "undefined") {
+ location.href = "/app/#/explore";
+ }
+ </script>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>explore</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <link href="/assets/dark.css" rel="stylesheet">
+ <link href="/assets/styles.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+ <script src="/assets/localforage.min.js"></script>
+ <script src="/assets/fuse.min.js"></script>
+ <script src="/assets/js/shortcuts.js"></script>
+ <link id="native-css" href="/assets/native.css" rel="stylesheet" disabled>
+</head>
+<body class="crossplatform">
+ <script src="/assets/js/common.js"></script>
+ <div class="container">
+ <br>
+ <h2 style="margin-top: 10px; margin-bottom: 20px; margin-left: 10px;">Explore</h2>
+
+ <form action="search.php">
+ <div style="width: calc(100% - 20px); margin-left: 10px;" class="input-group mb-3">
+ <input name="q" type="text" id="search" class="form-control" placeholder="Search on Mist">
+ <button class="btn btn-outline-secondary" type="submit" id="btn-search">Search</button>
+ </div>
+ </form>
+
+ <hr style="width: calc(100% - 20px); margin-left: 10px;">
+
+ <p style="margin-left: 10px;">To search for content on Mist, start by typing the name of a song, artist or album. Any corresponding results will show up. Should anything you need be missing, you can contact your administrator to ask for more content to be added.</p>
+ </div>
+
+ <br><br>
+</body>
+</html> \ No newline at end of file
diff --git a/app/ui/favorites.php b/app/ui/favorites.php
new file mode 100644
index 0000000..f1ebe30
--- /dev/null
+++ b/app/ui/favorites.php
@@ -0,0 +1,16 @@
+<?php
+
+header("X-Frame-Options: SAMEORIGIN");
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php";
+global $songs; global $favorites;
+
+$hasAlbum = false;
+$favoritesList = true;
+$list = [];
+
+foreach ($favorites as $id) {
+ if (isset($songs[$id])) $list[$id] = $songs[$id];
+}
+
+unset($_GET["a"]);
+require_once "listing.php"; \ No newline at end of file
diff --git a/app/ui/info.php b/app/ui/info.php
new file mode 100644
index 0000000..fd4fad5
--- /dev/null
+++ b/app/ui/info.php
@@ -0,0 +1,187 @@
+<?php header("X-Frame-Options: SAMEORIGIN"); require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $songs;
+
+if (!isset($_GET["i"]) || !isset($songs[$_GET["i"]])) {
+ die();
+}
+
+$song = $songs[$_GET["i"]];
+$fileName = str_replace(["/", "<", ">", ":", "\"", "\\", "|", "?", "*"], "-", $song["artist"] . " - " . $song["title"]);
+
+function getSize($bytes) {
+ if ($bytes < 1024) {
+ return $bytes;
+ }
+
+ if ($bytes < 1024**2) {
+ return round($bytes / 1024, 1) . " KB";
+ }
+
+ if ($bytes < 1024**3) {
+ return round($bytes / 1024**2, 1) . " MB";
+ }
+
+ return round($bytes / 1024**3, 1) . " GB";
+}
+
+function timeToDuration($seconds) {
+ $hours = floor($seconds / 3600);
+ $minutes = floor($seconds / 60) - ($hours * 60);
+ $seconds = floor($seconds) - ($hours * 3600) - ($minutes * 60);
+ $parts = [];
+
+ if ($hours > 0) $parts[] = $hours . " hour" . ($hours > 1 ? "s" : "");
+ if ($minutes > 0) $parts[] = $minutes . " minute" . ($minutes > 1 ? "s" : "");
+ if ($seconds > 0) $parts[] = $seconds . " second" . ($seconds > 1 ? "s" : "");
+
+ return implode(", ", $parts);
+}
+
+function getBitRate($bits) {
+ $bitsValue = $bits . " bps";
+
+ if ($bits > 1000) {
+ if ($bits > 1000000) {
+ if ($bits > 1000000000) {
+ $bitsValue = round($bits / 1000000000, 2) . " Gbps";
+ } else {
+ $bitsValue = round($bits / 1000000, 2) . " Mbps";
+ }
+ } else {
+ $bitsValue = round($bits / 1000, 2) . " kbps";
+ }
+ }
+
+ $bytesValue = ($bits / 8) . " B/s";
+
+ if (($bits / 8) > 1000) {
+ if (($bits / 8) > 1000000) {
+ if (($bits / 8) > 1000000000) {
+ $bytesValue = round(($bits / 8) / 1000000000, 2) . " GB/s";
+ } else {
+ $bytesValue = round(($bits / 8) / 1000000, 2) . " MB/s";
+ }
+ } else {
+ $bytesValue = round(($bits / 8) / 1000, 2) . " kB/s";
+ }
+ }
+
+ return $bitsValue . " (" . $bytesValue . ")";
+}
+
+function getChannelConfiguration($c) {
+ if ($c === 1) return " (Mono)";
+ if ($c === 2) return " (Stereo)";
+ if ($c === 3) return " (Stereo+1)";
+ if ($c === 4) return " (3.1)";
+ if ($c === 5) return " (4.1 or 5.0 Surround)";
+ if ($c === 6) return " (5.1 Surround)";
+ if ($c === 7) return " (6.1 or 7.0 Surround)";
+ if ($c === 8) return " (7.1 Surround)";
+ if ($c === 9) return " (9.0 or 7.2 Spatial Audio)";
+ if ($c === 10) return " (9.1 Spatial Audio)";
+ if ($c >= 11) return " (Dolby Atmos)";
+ return "";
+}
+
+?>
+<!doctype html>
+<html lang="en">
+<head>
+ <script src="/assets/js/common.js"></script>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>info</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <link href="/assets/dark.css" rel="stylesheet">
+ <link href="/assets/styles.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+ <script src="/assets/localforage.min.js"></script>
+ <script src="/assets/fuse.min.js"></script>
+ <script src="/assets/js/shortcuts.js"></script>
+ <link id="native-css" href="/assets/native.css" rel="stylesheet" disabled>
+</head>
+<body class="crossplatform" style="background-color: transparent !important;">
+ <script>
+ if (navigator.userAgent.includes("MistNative/darwin") || navigator.userAgent.includes("MistNative/win32")) {
+ document.getElementById("native-css").disabled = false;
+ document.body.classList.remove("crossplatform");
+ }
+ </script>
+ <div style="padding: 1rem;">
+ <div style="display: grid; grid-template-columns: 96px 1fr; grid-gap: 15px;">
+ <img alt="" src="/assets/content/<?= $_GET["i"] ?>.jpg" style="aspect-ratio: 1; width: 96px;">
+ <div style="display: flex; align-items: center; width: 100%;">
+ <div>
+ <div style="white-space: nowrap; overflow: hidden !important; text-overflow: ellipsis; max-width: 100%;"><b><?= $song["title"] ?></b></div>
+ <div style="white-space: nowrap; overflow: hidden !important; text-overflow: ellipsis; max-width: 100%;"><?= $song["artist"] ?></div>
+ <div style="white-space: nowrap; overflow: hidden !important; text-overflow: ellipsis; max-width: 100%;"><?= $song["album"] ?></div>
+ </div>
+ </div>
+ </div>
+
+ <hr>
+
+ <table style="width: 100%;">
+ <tr>
+ <td style="width: calc(100% / 3); text-align: right; padding-right: 10px; opacity: .5;">Track</td>
+ <td><?php if (isset($song["disc"])): ?>Disc <?= $song["disc"] ?>, track <?php endif; ?><?= $song["track"] > 0 ? $song["track"] : "-" ?></td>
+ </tr>
+ <tr>
+ <td style="width: calc(100% / 3); text-align: right; padding-right: 10px; opacity: .5;">Duration</td>
+ <td><?= timeToDuration($song["length"]) ?></td>
+ </tr>
+ <tr>
+ <td style="width: calc(100% / 3); text-align: right; padding-right: 10px; opacity: .5;">Bit rate</td>
+ <td><?= getBitRate($song["bitRate"]) ?></td>
+ </tr>
+ <tr>
+ <td style="width: calc(100% / 3); text-align: right; padding-right: 10px; opacity: .5;">Sample rate</td>
+ <td><?= $song["sampleRate"] ?> Hz</td>
+ </tr>
+ <tr>
+ <td style="width: calc(100% / 3); text-align: right; padding-right: 10px; opacity: .5;">Bits per sample</td>
+ <td><?= $song["bitDepth"] ?> bits</td>
+ </tr>
+ <tr>
+ <td style="width: calc(100% / 3); text-align: right; padding-right: 10px; opacity: .5;">Channels</td>
+ <td><?= $song["channels"] ?><?= getChannelConfiguration($song["channels"]) ?></td>
+ </tr>
+ <tr>
+ <td style="width: calc(100% / 3); text-align: right; padding-right: 10px; opacity: .5;">Year</td>
+ <td><?= $song["date"] ?></td>
+ </tr>
+ <tr>
+ <td style="width: calc(100% / 3); text-align: right; padding-right: 10px; opacity: .5;">High-resolution</td>
+ <td><?= $song["hiRes"] ? "Yes" : "No" ?></td>
+ </tr>
+ <tr>
+ <td style="width: calc(100% / 3); text-align: right; padding-right: 10px; opacity: .5;">Mist Stella</td>
+ <td>No</td>
+ </tr>
+ <tr>
+ <td style="width: calc(100% / 3); text-align: right; padding-right: 10px; opacity: .5;">Copyright</td>
+ <td><?= trim($song["copyright"] ?? "") !== "" ? $song["copyright"] : "-" ?></td>
+ </tr>
+ <tr>
+ <td style="width: calc(100% / 3); text-align: right; padding-right: 10px; opacity: .5;">File size (lossless)</td>
+ <td><?= getSize(filesize($_SERVER['DOCUMENT_ROOT'] . "/assets/content/" . $_GET["i"] . ".flac")) ?></td>
+ </tr>
+ <tr>
+ <td style="width: calc(100% / 3); text-align: right; padding-right: 10px; opacity: .5;">File size (AAC-LC)</td>
+ <td><?= getSize(filesize($_SERVER['DOCUMENT_ROOT'] . "/assets/content/" . $_GET["i"] . ".m4a")) ?></td>
+ </tr>
+ </table>
+ </div>
+
+ <script>
+ window.sizeInterval = setInterval(() => {
+ if (document.body.clientHeight > 0) {
+ clearInterval(sizeInterval);
+ window.parent.document.getElementById("modal-frame").style.height = document.body.clientHeight + "px";
+ }
+ });
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/app/ui/listing.php b/app/ui/listing.php
new file mode 100644
index 0000000..9ad227f
--- /dev/null
+++ b/app/ui/listing.php
@@ -0,0 +1,189 @@
+<?php header("X-Frame-Options: SAMEORIGIN"); require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $_PROFILE; global $library;
+
+if (isset($list)) {
+ $presetList = true;
+} else {
+ $presetList = false;
+}
+
+if (!$presetList) {
+ global $albums; global $songs; global $favorites;
+
+ if (isset($_GET["a"]) && isset($albums[$_GET["a"]])) {
+ $hasAlbum = true;
+ $list = [];
+
+ foreach ($albums[$_GET["a"]]["tracks"] as $id) {
+ $list[$id] = $songs[$id];
+ }
+ } else {
+ $hasAlbum = false;
+ $list = $songs;
+
+ foreach ($albums as $id => $album) {
+ foreach ($album["tracks"] as $track) {
+ $list[$track]["_albumID"] = $id;
+ }
+ }
+
+ uasort($list, function ($a, $b) {
+ return strcmp($a["title"], $b["title"]);
+ });
+
+ $list = array_filter($list, function ($i) {
+ global $library;
+ return in_array($i["_albumID"], $library);
+ });
+ }
+}
+
+?>
+<!doctype html>
+<html lang="en">
+<head>
+ <script>
+ if (typeof window.parent.openModal === "undefined") {
+ location.href = "/app/#/songs";
+ }
+ </script>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>listing</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <link href="/assets/dark.css" rel="stylesheet">
+ <link href="/assets/styles.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+ <script src="/assets/localforage.min.js"></script>
+ <script src="/assets/js/shortcuts.js"></script>
+ <script src="/assets/fuse.min.js"></script>
+ <link id="native-css" href="/assets/native.css" rel="stylesheet" disabled>
+</head>
+<body class="crossplatform">
+ <script src="/assets/js/common.js"></script>
+ <?php if ($hasAlbum): ?>
+ <script>
+ window.parent.location.hash = "#/albums/<?= $_GET["a"] ?>";
+ </script>
+ <?php endif; ?>
+ <div id="content" style="position: fixed; inset: 0; z-index: 10; overflow: auto; backdrop-filter: blur(50vw); -webkit-backdrop-filter: blur(50vw);">
+ <div class="container">
+ <br>
+ <?php if (!$hasAlbum && !isset($favoritesList)): ?>
+ <h2 style="margin-top: 10px; margin-bottom: 20px; margin-left: 10px;">Songs<input placeholder="Filter" id="filter" class="form-control" style="width: 256px; float: right;" onchange="updateFilter();" onkeyup="updateFilter();"></h2>
+ <?php elseif (isset($favoritesList)): ?>
+ <div id="album-info" style="display: grid; grid-template-columns: 20vw 1fr; margin-top: 10px; margin-left: 10px; grid-gap: 30px;">
+ <img id="album-info-art" alt="" src="/assets/favorites.svg" style="height: 20vw; width: 20vw; border-radius: .75vw;">
+ <div id="album-info-text" style="padding: 30px 0; display: grid; grid-template-rows: 1fr max-content;">
+ <div><h2>Favorites</h2>
+ <h2 style="opacity: .5;"><?= $_PROFILE["name"] ?></h2>
+ <div style="opacity: .5;">
+ Click on the heart icon near a song to add it to this list.
+ </div>
+ </div>
+ <div id="album-info-buttons">
+ <a class="btn btn-primary <?= count(array_keys($list)) <= 0 ? "disabled" : "" ?>" onclick="window.parent.playSong('<?= array_keys($list)[0] ?? '' ?>', 'favorites');" style="width: 100px;">Play</a>
+ <a class="btn btn-outline-primary <?= count(array_keys($list)) <= 0 ? "disabled" : "" ?>" style="width: 100px;" onclick="window.parent.shuffleList('favorites');">Shuffle</a>
+ <input placeholder="Filter" id="filter" class="form-control" style="width: 256px; float: right;" onchange="updateFilter();" onkeyup="updateFilter();">
+ </div>
+ </div>
+ </div>
+ <?php else: ?>
+ <div id="album-info" style="display: grid; grid-template-columns: 20vw 1fr; margin-top: 10px; margin-left: 10px; grid-gap: 30px;">
+ <img id="album-info-art" alt="" src="/assets/content/<?= $_GET["a"] ?>.jpg" style="height: 20vw; width: 20vw; border-radius: .75vw;">
+ <div id="album-info-text" style="padding: 30px 0; display: grid; grid-template-rows: 1fr max-content;">
+ <div><h2><?= $albums[$_GET["a"]]["title"] ?></h2>
+ <h2 style="opacity: .5;"><?= $albums[$_GET["a"]]["artist"] ?></h2>
+ <div style="opacity: .5;">
+ <?= $albums[$_GET["a"]]["date"] ?>
+ <?php if ($albums[$_GET["a"]]["hiRes"]): ?>
+ · <img src='/assets/icons/lossless.svg' alt='' class='icon player-badge-icon'>Hi-Res Lossless
+ <?php endif; ?>
+ </div>
+ </div>
+ <div id="album-info-buttons" <?php if (!in_array($_GET["a"], $library)): ?>class="nolibrary"<?php endif; ?>>
+ <?php if (in_array($_GET["a"], $library)): ?>
+ <a class="btn btn-primary" onclick="window.parent.playSong('<?= array_keys($list)[0] ?>', 'album:<?= $_GET["a"] ?>');" style="width: 100px;">Play</a>
+ <a class="btn btn-outline-primary" style="width: 100px;" onclick="window.parent.shuffleList('album:<?= $_GET["a"] ?>');">Shuffle</a>
+ <?php else: ?>
+ <a class="btn btn-primary" onclick="window.addToLibrary();" id="library-button" style="width: 200px;">Add to library</a>
+ <?php endif ?>
+ </div>
+ </div>
+ </div>
+ <?php endif; ?>
+ <?php displayList($list, $hasAlbum); ?>
+ <div class="list-group" style="margin-left: 10px; margin-top: 20px; display: none;" id="search-results"></div>
+ <?php if (count($list) === 0): ?>
+ <div class="text-muted" style="position: fixed; display: flex; inset: 0; align-items: center; justify-content: center;">
+ <div style="text-align: center;">
+ <img class="icon" src="/assets/logo-transparent.svg" style="filter: grayscale(1) invert(1); width: 96px; height: 96px;" alt="">
+ <h4 style="opacity: .75;">Add music to your library</h4>
+ <p style="max-width: 300px; margin-left: auto; margin-right: auto;">Browse millions of songs and collect your favorites here.</p>
+ <div class="btn btn-primary" onclick="window.parent.openUI('explore');">Browse Mist</div>
+ </div>
+ </div>
+ <?php endif; ?>
+ <?php if ($hasAlbum && in_array($_GET["a"], $library)): ?>
+ <br>
+ <div><a class="link" id="library-button" href="#" onclick="removeFromLibrary();">Remove from library</a></div>
+ <?php endif; ?>
+ </div>
+
+ <script>
+ <?php if ($hasAlbum): ?>
+ async function addToLibrary() {
+ document.getElementById("library-button").classList.add("disabled");
+ await fetch("/api/addLibrary.php?i=<?= $_GET["a"] ?>");
+ window.parent.redownloadLibrary();
+ location.reload();
+ }
+
+ async function removeFromLibrary() {
+ document.getElementById("library-button").classList.add("disabled");
+ await fetch("/api/removeLibrary.php?i=<?= $_GET["a"] ?>");
+ window.parent.redownloadLibrary();
+ location.reload();
+ }
+ <?php endif; ?>
+
+ let items = Array.from(document.getElementsByClassName("track")).map(i => { return { title: i.getAttribute("data-item-track"), artist: i.getAttribute("data-item-artist"), id: i.id } });
+
+ const fuse = new Fuse(items, {
+ keys: [
+ {
+ name: 'title',
+ weight: 1
+ },
+ {
+ name: 'artist',
+ weight: .5
+ }
+ ]
+ });
+
+ function updateFilter() {
+ let query = document.getElementById("filter").value.trim();
+
+ if (query !== "") {
+ document.getElementById("search-results").style.display = "flex";
+ document.getElementById("main-list").style.display = "none";
+
+ let results = fuse.search(query);
+ document.getElementById("search-results").innerHTML = "";
+
+ for (let result of results) {
+ document.getElementById("search-results").innerHTML += document.getElementById(result.item.id).outerHTML;
+ }
+ } else {
+ document.getElementById("search-results").style.display = "none";
+ document.getElementById("main-list").style.display = "flex";
+ }
+ }
+ </script>
+
+ <br><br>
+ </div>
+</body>
+</html> \ No newline at end of file
diff --git a/app/ui/lyrics.php b/app/ui/lyrics.php
new file mode 100644
index 0000000..bd24832
--- /dev/null
+++ b/app/ui/lyrics.php
@@ -0,0 +1,164 @@
+<?php header("X-Frame-Options: SAMEORIGIN"); require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; ?>
+<!doctype html>
+<html lang="en">
+<head>
+ <script>
+ if (typeof window.parent.openModal === "undefined") {
+ location.href = "/app/#/lyrics";
+ }
+ </script>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>lyrics</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <link href="/assets/dark.css" rel="stylesheet">
+ <link href="/assets/styles.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+ <script src="/assets/localforage.min.js"></script>
+ <script src="/assets/js/shortcuts.js"></script>
+ <link id="native-css" href="/assets/native.css" rel="stylesheet" disabled>
+ <style>
+ .synced-lyrics-item {
+ opacity: .25;
+ font-size: 4vw;
+ min-font-size: 22px;
+ margin-bottom: 10px;
+ transition: opacity 200ms;
+ }
+
+ .synced-lyrics-item.active {
+ opacity: 1;
+ }
+
+ ::-webkit-scrollbar {
+ display: none;
+ }
+
+ #lyrics-synced {
+ transition-property: top;
+ transition-duration: 500ms;
+ transition-timing-function: cubic-bezier(0.54, 0, 0.23, 0.79);
+ }
+ </style>
+</head>
+<body class="crossplatform" style="background-color: transparent !important;">
+ <script src="/assets/js/common.js"></script>
+ <div id="lyrics-outer">
+ <div id="not-playing" style="position: fixed; inset: 16px; display: flex; align-items: center; justify-content: center; opacity: .5; text-align: center;">
+ Lyrics will appear here when you start playing a song. If supported, they will also scroll as the song plays.
+ </div>
+
+ <div id="not-available" style="display: none; position: fixed; inset: 16px; align-items: center; justify-content: center; opacity: .5; text-align: center;">
+ No lyrics are available for this song. Try again later.
+ </div>
+
+ <div id="loading" style="display: none; position: fixed; inset: 16px; align-items: center; justify-content: center; opacity: .5; text-align: center;">
+ Loading lyrics...
+ </div>
+
+ <div id="lyrics-unsynced" style="display: none; position: fixed; inset: 16px; overflow: auto;"></div>
+ <div id="lyrics-synced" style="text-align: center; display: none; position: fixed; left: 16px; right: 16px; top: 0; bottom: 0; z-index: 5;"></div>
+ <div id="lyrics-synced-fade" style="display: none; position: fixed; inset: 0; z-index: 10; background-image: linear-gradient(180deg, rgba(255,0,0,0) 25%, rgba(255,255,255,1) 100%);"></div>
+ </div>
+
+ <script>
+ let lastID = null;
+ let lastTimestamp = null;
+ window.lyrics = {};
+
+ setInterval(async () => {
+ if (window.parent.currentSongID !== lastID) {
+ lastID = window.parent.currentSongID;
+
+ if (lastID === null) {
+ document.getElementById("lyrics-synced").style.display = "none";
+ document.getElementById("lyrics-synced-fade").style.display = "none";
+ document.getElementById("lyrics-unsynced").style.display = "none";
+ document.getElementById("not-available").style.display = "none";
+ document.getElementById("loading").style.display = "none";
+ document.getElementById("not-playing").style.display = "flex";
+ } else {
+ document.getElementById("lyrics-synced").style.display = "none";
+ document.getElementById("lyrics-synced-fade").style.display = "none";
+ document.getElementById("lyrics-unsynced").style.display = "none";
+ document.getElementById("not-available").style.display = "none";
+ document.getElementById("loading").style.display = "flex";
+ document.getElementById("not-playing").style.display = "none";
+
+ if (!window.lyrics[lastID]) {
+ window.lyricsLoadTimeout = setTimeout(() => {
+ location.reload();
+ }, 10000);
+
+ try {
+ window.lyrics[lastID] = await (await fetch("/api/lyrics.php?id=" + lastID)).json()
+ } catch (e) {
+ window.lyrics[lastID] = {
+ synced: false,
+ payload: null
+ }
+ }
+
+ clearTimeout(window.lyricsLoadTimeout);
+
+ if (window.lyrics[lastID] && window.lyrics[lastID].payload) {
+ if (window.lyrics[lastID].synced) {
+ document.getElementById("lyrics-synced").style.display = "";
+ document.getElementById("lyrics-synced-fade").style.display = "";
+ document.getElementById("lyrics-unsynced").style.display = "none";
+ document.getElementById("not-available").style.display = "none";
+ document.getElementById("loading").style.display = "none";
+ document.getElementById("not-playing").style.display = "none";
+
+ document.getElementById("lyrics-synced").innerHTML = "<div style='height: 16px;'></div>" + window.lyrics[lastID].payload.map(i => `
+ <div class="synced-lyrics-item" id="synced-lyrics-${i.startTimeMs}">${i.words}</div>
+ `).join("") + "<div style='height: 16px;'></div>";
+ } else {
+ document.getElementById("lyrics-synced").style.display = "none";
+ document.getElementById("lyrics-synced-fade").style.display = "none";
+ document.getElementById("lyrics-unsynced").style.display = "";
+ document.getElementById("not-available").style.display = "none";
+ document.getElementById("loading").style.display = "none";
+ document.getElementById("not-playing").style.display = "none";
+
+ document.getElementById("lyrics-unsynced").innerText = window.lyrics[lastID].payload.replaceAll("\n\n\n", "\n");
+ }
+ } else {
+ document.getElementById("lyrics-synced").style.display = "none";
+ document.getElementById("lyrics-synced-fade").style.display = "none";
+ document.getElementById("lyrics-unsynced").style.display = "none";
+ document.getElementById("not-available").style.display = "flex";
+ document.getElementById("loading").style.display = "none";
+ document.getElementById("not-playing").style.display = "none";
+ }
+ }
+ }
+ }
+
+ if (window.lyrics[lastID] && window.lyrics[lastID].synced) {
+ document.getElementById("lyrics-synced").style.display = "";
+ document.getElementById("lyrics-synced-fade").style.display = "";
+ document.getElementById("lyrics-unsynced").style.display = "none";
+ document.getElementById("not-available").style.display = "none";
+ document.getElementById("loading").style.display = "none";
+ document.getElementById("not-playing").style.display = "none";
+
+ for (let item of [...window.lyrics[lastID].payload].reverse()) {
+ if (parseInt(item.startTimeMs) / 1000 <= window.parent.document.getElementById("player").contentDocument.getElementById("player-audio").currentTime) {
+ if (item.startTimeMs !== lastTimestamp) {
+ Array.from(document.getElementsByClassName("synced-lyrics-item")).map(i => i.classList.remove("active"));
+ document.getElementById("lyrics-synced").style.top = "-" + (document.getElementById("synced-lyrics-" + item.startTimeMs).offsetTop - 33) + "px";
+ lastTimestamp = item.startTimeMs;
+ document.getElementById("synced-lyrics-" + item.startTimeMs).classList.add("active");
+ }
+
+ break;
+ }
+ }
+ }
+ }, 100);
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/app/ui/modal.php b/app/ui/modal.php
new file mode 100644
index 0000000..b8f1cfb
--- /dev/null
+++ b/app/ui/modal.php
@@ -0,0 +1,49 @@
+<?php header("X-Frame-Options: SAMEORIGIN"); require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; ?>
+<!doctype html>
+<html lang="en">
+<head>
+ <script>
+ if (typeof window.parent.openModal === "undefined") {
+ location.href = "/app/";
+ }
+ </script>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>modal</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <link href="/assets/dark.css" rel="stylesheet">
+ <link href="/assets/styles.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+ <script src="/assets/localforage.min.js"></script>
+ <script src="/assets/fuse.min.js"></script>
+ <script src="/assets/js/shortcuts.js"></script>
+ <link id="native-css" href="/assets/native.css" rel="stylesheet" disabled>
+</head>
+<body class="crossplatform" style="background-color: transparent !important;">
+ <script src="/assets/js/common.js"></script>
+ <div class="modal fade" id="modal">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header" id="modal-header">
+ <h4 class="modal-title" id="modal-title">Title</h4>
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
+ </div>
+
+ <div class="modal-body" style="padding: 0;">
+ <iframe id="modal-frame" style="width: 100%; border-bottom-right-radius: 0.5rem; margin-bottom: -6px; border-bottom-left-radius: 0.5rem; height: calc(100vh - 130px);"></iframe>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <script>
+ window._modal = new bootstrap.Modal(document.getElementById("modal"));
+
+ document.getElementById("modal").addEventListener("hidden.bs.modal", () => {
+ window.parent.document.getElementById("modal").style.display = "none";
+ });
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/app/ui/navigation.php b/app/ui/navigation.php
new file mode 100644
index 0000000..9344791
--- /dev/null
+++ b/app/ui/navigation.php
@@ -0,0 +1,69 @@
+<?php header("X-Frame-Options: SAMEORIGIN"); require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $_PROFILE ?>
+<!doctype html>
+<html lang="en">
+<head>
+ <script>
+ if (typeof window.parent.openModal === "undefined") {
+ location.href = "/app/";
+ }
+ </script>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>navigation</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <link href="/assets/dark.css" rel="stylesheet">
+ <link href="/assets/styles.css" rel="stylesheet">
+ <link id="native-css" href="/assets/native.css" rel="stylesheet" disabled>
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+ <script src="/assets/localforage.min.js"></script>
+ <script src="/assets/js/shortcuts.js"></script>
+ <link id="native-css" href="/assets/native.css" rel="stylesheet" disabled>
+</head>
+<body class="crossplatform navigation-body" style="overflow: hidden;">
+ <script src="/assets/js/common.js"></script>
+ <div class="navigation-container-inner container" style="margin-left: 20px; overflow: hidden;">
+ <div id="navigation-left">
+ <div id="navigation-gradient" style="background-image: radial-gradient(circle, rgba(122,195,245,.25) 0%, rgba(255,255,255,0) 75%); top: -256px; left: 0; right: 0; height: calc(512px + var(--android-status-bar)); position: fixed; z-index: -1;"></div>
+ <div id="navigation-top" style="-webkit-app-region: drag; position: fixed; top: 0; right: 0; left: 0; height: calc(128px + var(--android-status-bar));"></div>
+ <br><br><br>
+ <h3 style="margin-top: var(--android-status-bar); margin-left: 10px;">
+ <img src="/assets/logo-display.png" alt="" style="width: 32px; height: 32px; vertical-align: middle;">
+ <span style="vertical-align: middle;" class="navigation-desktop">&nbsp;Mist</span>
+ </h3>
+ <br>
+ </div>
+
+ <div id="navigation-container">
+ <div id="explore" class="navigation-item" onclick="window.parent.openUI('explore');">
+ <img class="icon" alt="" src="/assets/icons/explore.svg" style="vertical-align: middle; width: 32px;"><span style="vertical-align: middle; margin-left: 5px;" class="navigation-desktop">Explore</span>
+ </div>
+ <div id="albums" class="navigation-item active" onclick="window.parent.openUI('albums');">
+ <img class="icon" alt="" src="/assets/icons/album.svg" style="vertical-align: middle; width: 32px;"><span style="vertical-align: middle; margin-left: 5px;" class="navigation-desktop">Albums</span>
+ </div>
+ <div id="songs" class="navigation-item" onclick="window.parent.openUI('songs');">
+ <img class="icon" alt="" src="/assets/icons/song.svg" style="vertical-align: middle; width: 32px;"><span style="vertical-align: middle; margin-left: 5px;" class="navigation-desktop">Songs</span>
+ </div>
+ <div id="favorites" class="navigation-item" onclick="window.parent.openUI('favorites');">
+ <img class="icon" alt="" src="/assets/icons/favorites.svg" style="vertical-align: middle; width: 32px;"><span style="vertical-align: middle; margin-left: 5px;" class="navigation-desktop">Favorites</span>
+ </div>
+ <div id="lyrics" class="navigation-item" onclick="window.parent.showLyrics();">
+ <img class="icon" alt="" src="/assets/icons/lyrics.svg" style="vertical-align: middle; width: 32px;"><span style="vertical-align: middle; margin-left: 5px;" class="navigation-desktop">Lyrics</span>
+ </div>
+ <div id="queue" class="navigation-item" onclick="window.parent.openUI('queue');">
+ <img class="icon" alt="" src="/assets/icons/playlist.svg" style="vertical-align: middle; width: 32px;"><span style="vertical-align: middle; margin-left: 5px;" class="navigation-desktop">Queue</span>
+ </div>
+ <div id="settings" class="navigation-item" onclick="window.parent.openUI('settings');">
+ <img class="icon" alt="" src="/assets/icons/settings.svg" style="vertical-align: middle; width: 32px;"><span style="vertical-align: middle; margin-left: 5px;" class="navigation-desktop">Settings</span>
+ </div>
+ </div>
+
+ <div id="navigation-version">
+ <a style="position: fixed; width: 100%; bottom: 60px; color: inherit; text-decoration: inherit; display: block;" id="account" class="navigation-item" href="https://account.equestria.dev/hub/users/<?= $_PROFILE["id"] ?>" target="_blank">
+ <img alt="" src="https://account.equestria.dev/hub/api/rest/avatar/<?= $_PROFILE["id"] ?>?dpr=2&size=32" style="filter: none !important; border-radius: 999px; vertical-align: middle; width: 32px;"><span style="vertical-align: middle; margin-left: 5px;" class="navigation-desktop"><?= $_PROFILE["name"] ?></span>
+ </a>
+ </div>
+ </div>
+</body>
+</html> \ No newline at end of file
diff --git a/app/ui/player-mobile.php b/app/ui/player-mobile.php
new file mode 100644
index 0000000..5266961
--- /dev/null
+++ b/app/ui/player-mobile.php
@@ -0,0 +1,104 @@
+<?php header("X-Frame-Options: SAMEORIGIN"); require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; ?>
+<!doctype html>
+<html lang="en">
+<head>
+ <script>
+ if (typeof window.parent.openModal === "undefined") {
+ location.href = "/app/";
+ }
+ </script>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>player-mobile</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+ <link href="/assets/dark.css" rel="stylesheet">
+ <link href="/assets/styles.css" rel="stylesheet">
+ <script src="/assets/js/shortcuts.js"></script>
+ <style>
+ .player-btn {
+ filter: invert(1);
+ opacity: .75;
+ }
+ </style>
+</head>
+<body>
+ <script>
+ let style = document.createElement("style");
+
+ if (window.MistAndroid) {
+ style.innerHTML =
+ `:root {
+ --android-navigation-bar: ${window.MistAndroid.getNavigationBarHeight()}px;
+ --android-status-bar: ${window.MistAndroid.getStatusBarHeight()}px;
+ }`;
+ } else {
+ style.innerHTML =
+ `:root {
+ --android-navigation-bar: 0px;
+ --android-status-bar: 0px;
+ }`;
+ }
+
+ document.head.append(style);
+ </script>
+ <div id="act-1" onclick="window.parent.hideMobilePlayer();" style="position: fixed; z-index: 9999999; top: 0; left: 0; right: 0; height: calc(20px + var(--android-status-bar));"></div>
+ <div id="act-2" onclick="window.parent.hideMobilePlayer();" style="position: fixed; z-index: 9999999; bottom: 0; left: 0; right: 0; height: calc(20px + var(--android-navigation-bar))"></div>
+ <div id="act-3" onclick="window.parent.hideMobilePlayer();" style="position: fixed; z-index: 9999999; bottom: 0; left: 0; top: 0; width: 20px;"></div>
+ <div id="act-4" onclick="window.parent.hideMobilePlayer();" style="position: fixed; z-index: 9999999; bottom: 0; right: 0; top: 0; width: 20px;"></div>
+ <div id="android" style="position: fixed; z-index: 9999999; bottom: 0; right: 0; left: 0; height: var(--android-navigation-bar); border-top: 1px solid rgba(255, 255, 255, .1);"></div>
+
+ <div id="album-art-bg" style="position: fixed;inset: 0;background-position: center;background-size: cover; z-index: 5;"></div>
+ <div id="album-art-bg2" style="background-color: rgba(0, 0, 0, .1); z-index: 10; position: fixed;inset: 0; backdrop-filter: blur(100px);"></div>
+ <div id="player" class="bg-white mobile-player" style="background-color: transparent !important; color: white;position: fixed; bottom: var(--android-navigation-bar); left: 0; right: 0; height: 64px; z-index: 9999;">
+ <div class="container" style="display: grid; grid-template-columns: 1fr 1.5fr 1fr;">
+ <div id="buttons" style="height: 48px;position: fixed;width: max-content;left: 0;right: 0;margin: 8px auto;bottom: calc(30px + var(--android-navigation-bar));">
+ <span onclick="window.parent.toggleShuffle();" class="player-btn" style="border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; height: 48px; width: 48px;" id="btn-shuffle">
+ <img alt="" src="/assets/icons/shuffle-off.svg" style="pointer-events: none; width: 32px; height: 32px;" id="btn-shuffle-icon">
+ </span>
+ <span onclick="window.parent.previous();" class="player-btn disabled" style="border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; height: 48px; width: 48px;" id="btn-previous">
+ <img alt="" src="/assets/icons/previous.svg" style="pointer-events: none; width: 32px; height: 32px;" id="btn-previous-icon">
+ </span>
+ <span onclick="window.parent.playPause();" class="player-btn" style="border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; height: 48px; width: 48px;" id="btn-play">
+ <img alt="" src="/assets/icons/play.svg" style="pointer-events: none; width: 32px; height: 32px;" id="btn-play-icon">
+ </span>
+ <span onclick="window.parent.next();" class="player-btn disabled" style="border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; height: 48px; width: 48px;" id="btn-next">
+ <img alt="" src="/assets/icons/next.svg" style="pointer-events: none; width: 32px; height: 32px;" id="btn-next-icon">
+ </span>
+ <span onclick="window.parent.toggleRepeat();" class="player-btn" style="border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; height: 48px; width: 48px;" id="btn-repeat">
+ <img alt="" src="/assets/icons/repeat-off.svg" style="pointer-events: none; width: 32px; height: 32px;" id="btn-repeat-icon">
+ </span>
+ </div>
+ <div style="position: fixed;top: calc(30px + var(--android-status-bar));left: 30px;right: 30px;">
+ <img alt="" id="album-art" style="border-radius: 5px; background-color: rgba(0, 0, 0, .1); height: 48px !important; width: 48px !important;">
+ <div id="info-grid" style="display: grid;grid-template-rows: 22px 22px;position: fixed;top: calc(30px + var(--android-status-bar));left: 88px;">
+ <div id="info-grid-title" style="white-space: nowrap;overflow: hidden !important;text-overflow: ellipsis;display: flex;font-size: 0.91rem;align-items: end;text-align: left;justify-content: left; opacity: .75;"><span id="title">Title</span></div>
+ <div id="info-grid-info" style="white-space: nowrap;overflow: hidden !important;text-overflow: ellipsis;display: flex;font-size: 0.91rem;align-items: start;text-align: left;justify-content: left;opacity: .35;"><span id="artist">Artist</span><span style="display: none;">&nbsp;—&nbsp;<span id="album">Album</span></span></div>
+ <div id="info-grid-time" style="font-size: 9px;opacity: .5;margin-left: 2px;margin-right: 2px;position: fixed;bottom: calc(125px + var(--android-navigation-bar));left: 30px;right: 30px;">
+ <span id="elapsed-time">0:00</span>
+ <span id="remaining-time" style="float: right;">-0:00</span>
+ </div>
+ <div style="background-color: rgba(255, 255, 255, .05);position: fixed;bottom: calc(150px + var(--android-navigation-bar));left: 30px;right: 30px;border-radius: 999px;" id="seekbar-container">
+ <div id="seekbar" style="pointer-events: none;background-color: rgba(255, 255, 255, .25); width: 0; height: 8px;border-radius: 999px;"></div>
+ </div>
+ </div>
+ </div>
+ <div style="text-align: right; display: flex; align-items: center; justify-content: right;" id="badges">
+ <span id="badge-lossy" style="display: none;"></span>
+ <span id="badge-cd" style="display: block;border: 1px solid transparent;color: rgba(255, 255, 255, .75);background-color: rgba(255, 255, 255, .25);padding: 2px 5px;border-radius: 5px;font-size: 12px;position: fixed;margin-left: auto;margin-right: auto;width: max-content;left: 0;right: 0;bottom: calc(120px + var(--android-navigation-bar));">
+ <span style="display: grid; grid-template-columns: max-content max-content">
+ <span><img src="/assets/icons/lossless.svg" alt="" class="player-badge-icon" style="filter: invert(1); opacity: .75;">Lossless</span>
+ </span>
+ </span>
+ <span id="badge-hires" style="display: block;border: 1px solid transparent;color: rgba(255, 255, 255, .75);background-color: rgba(255, 255, 255, .25);padding: 2px 5px;border-radius: 5px;font-size: 12px;position: fixed;margin-left: auto;margin-right: auto;width: max-content;left: 0;right: 0;bottom: calc(120px + var(--android-navigation-bar));">
+ <span style="display: grid; grid-template-columns: max-content max-content">
+ <span><img src="/assets/icons/lossless.svg" alt="" class="player-badge-icon" style="filter: invert(1); opacity: .75;">Hi-Res Lossless</span>
+ </span>
+ </span>
+ </div>
+ </div>
+ </div>
+</body>
+</html> \ No newline at end of file
diff --git a/app/ui/player.php b/app/ui/player.php
new file mode 100644
index 0000000..ea60889
--- /dev/null
+++ b/app/ui/player.php
@@ -0,0 +1,76 @@
+<?php header("X-Frame-Options: SAMEORIGIN"); require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; ?>
+<!doctype html>
+<html lang="en">
+<head>
+ <script>
+ if (typeof window.parent.openModal === "undefined") {
+ location.href = "/app/";
+ }
+ </script>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>player</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+ <link href="/assets/dark.css" rel="stylesheet">
+ <link href="/assets/styles.css" rel="stylesheet">
+ <script src="/assets/js/shortcuts.js"></script>
+ <link id="native-css" href="/assets/native.css" rel="stylesheet" disabled>
+</head>
+<body class="crossplatform">
+ <script src="/assets/js/common.js"></script>
+ <div id="player" class="bg-white desktop-player" style="position: fixed; bottom: 0; left: 0; right: 0; height: 64px; z-index: 9999;">
+ <div id="desktop-player-action" onclick="window.parent.showMobilePlayer();" style="display: none;"></div>
+ <audio id="player-audio"></audio>
+ <div class="container" style="display: grid; grid-template-columns: 1fr 1.5fr 1fr;">
+ <div id="buttons" style="height: 48px; margin-top: 8px; margin-bottom: 8px;">
+ <span onclick="window.parent.toggleShuffle();" class="player-btn" style="border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; height: 48px; width: 48px;" id="btn-shuffle">
+ <img class="icon" alt="" src="/assets/icons/shuffle-off.svg" style="pointer-events: none; width: 32px; height: 32px;" id="btn-shuffle-icon">
+ </span>
+ <span onclick="window.parent.previous();" class="player-btn disabled" style="border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; height: 48px; width: 48px;" id="btn-previous">
+ <img class="icon" alt="" src="/assets/icons/previous.svg" style="pointer-events: none; width: 32px; height: 32px;" id="btn-previous-icon">
+ </span>
+ <span onclick="window.parent.playPause();" class="player-btn" style="border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; height: 48px; width: 48px;" id="btn-play">
+ <img class="icon" alt="" src="/assets/icons/play.svg" style="pointer-events: none; width: 32px; height: 32px;" id="btn-play-icon">
+ </span>
+ <span onclick="window.parent.next();" class="player-btn disabled" style="border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; height: 48px; width: 48px;" id="btn-next">
+ <img class="icon" alt="" src="/assets/icons/next.svg" style="pointer-events: none; width: 32px; height: 32px;" id="btn-next-icon">
+ </span>
+ <span onclick="window.parent.toggleRepeat();" class="player-btn" style="border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; height: 48px; width: 48px;" id="btn-repeat">
+ <img class="icon" alt="" src="/assets/icons/repeat-off.svg" style="pointer-events: none; width: 32px; height: 32px;" id="btn-repeat-icon">
+ </span>
+ </div>
+ <div>
+ <div id="info" style="display: none; grid-template-columns: 64px 1fr; height: 64px; border-left: 1px solid rgba(0, 0, 0, .25); border-right: 1px solid rgba(0, 0, 0, .25);">
+ <img alt="" id="album-art" style="background-color: rgba(0, 0, 0, .1); height: 64px; width: 64px;">
+ <div id="info-grid" style="display: grid; grid-template-rows: 2px 22px 22px 12px 6px;">
+ <div id="info-grid-sep"></div>
+ <div id="info-grid-title" style="white-space: nowrap; overflow: hidden !important; text-overflow: ellipsis; display: flex; font-size: 0.91rem; align-items: end; text-align: center; justify-content: center;"><span id="title">Title</span></div>
+ <div id="info-grid-info" style="white-space: nowrap; overflow: hidden !important; text-overflow: ellipsis; display: flex; font-size: 0.91rem; align-items: start; text-align: center; justify-content: center; opacity: .5;"><span id="artist">Artist</span><span class="player-badge-desktop">&nbsp;—&nbsp;<span id="album">Album</span></span></div>
+ <div id="info-grid-time" style="font-size: 9px; opacity: .5; margin-left: 2px; margin-right: 2px;">
+ <span id="elapsed-time">0:00</span>
+ <span id="remaining-time" style="float: right;">-0:00</span>
+ </div>
+ <div style="background-color: rgba(0, 0, 0, .05);" id="seekbar-container">
+ <div id="seekbar" style="pointer-events: none; background-color: rgba(0, 0, 0, .25); width: 0; height: 8px;"></div>
+ </div>
+ </div>
+ </div>
+ <div id="cover" style="display: grid; grid-template-columns: 64px 1fr; height: 64px; border-left: 1px solid rgba(0, 0, 0, .25); border-right: 1px solid rgba(0, 0, 0, .25);">
+ <img alt="" src="/assets/nothing.svg" id="album-art" style="background-color: rgba(0, 0, 0, .1); height: 64px; width: 64px;">
+ <div style="display: flex; align-items: center; justify-content: center;">
+ <img class="icon" src="/assets/logo-transparent.svg" style="filter: grayscale(1) invert(1); width: 32px; height: 32px;" alt="">
+ </div>
+ </div>
+ </div>
+ <div style="text-align: right; display: flex; align-items: center; justify-content: right;" id="badges">
+ <span id="badge-lossy" style="display: none; border: 1px solid #a402b6; color: white; background-color: #a402b6; padding: 2px 5px; border-radius: 5px; font-size: 12px;"><span style="display: grid; grid-template-columns: max-content max-content"><span>AAC-LC</span><span class="player-badge-desktop">256 kbps</span></span></span>
+ <span id="badge-cd" style="display: none; border: 1px solid #02b6a7; color: white; background-color: #02b6a7; padding: 2px 5px; border-radius: 5px; font-size: 12px;"></span>
+ <span id="badge-hires" style="display: none; border: 1px solid #b66e02; color: white; background-color: #b66e02; padding: 2px 5px; border-radius: 5px; font-size: 12px;"></span>
+ </div>
+ </div>
+ </div>
+</body>
+</html> \ No newline at end of file
diff --git a/app/ui/queue.php b/app/ui/queue.php
new file mode 100644
index 0000000..c4403a4
--- /dev/null
+++ b/app/ui/queue.php
@@ -0,0 +1,78 @@
+<?php header("X-Frame-Options: SAMEORIGIN"); require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; ?>
+<!doctype html>
+<html lang="en">
+<head>
+ <script>
+ if (typeof window.parent.openModal === "undefined") {
+ location.href = "/app/#/queue";
+ }
+ </script>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>queue</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <link href="/assets/dark.css" rel="stylesheet">
+ <link href="/assets/styles.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+ <script src="/assets/localforage.min.js"></script>
+ <script src="/assets/fuse.min.js"></script>
+ <script src="/assets/js/shortcuts.js"></script>
+ <link id="native-css" href="/assets/native.css" rel="stylesheet" disabled>
+</head>
+<body class="crossplatform">
+ <script src="/assets/js/common.js"></script>
+ <div class="container">
+ <br>
+ <h2 style="margin-top: 10px; margin-bottom: 20px; margin-left: 10px;">Queue</h2>
+ <div class="list-group" style="margin-left: 10px; margin-top: 20px;" id="main-list"></div>
+ <div class="text-muted" style="margin-left: 10px; margin-top: 20px; display: none;" id="empty">
+ There are no songs playing next. To add songs to the queue, browse your library and select Add to queue.
+ </div>
+ </div>
+
+ <script>
+ function refreshQueue() {
+ let list = window.parent.playlist.slice(window.parent.currentPlaylistPosition + 1);
+
+ if (list.length === 0) {
+ document.getElementById('main-list').style.display = "none";
+ document.getElementById('empty').style.display = "";
+ } else {
+ document.getElementById('main-list').style.display = "";
+ document.getElementById('empty').style.display = "none";
+
+ document.getElementById('main-list').innerHTML = list.map(i => [window.parent.songs[i], i]).map((i, j) => `
+ <div data-item-track="${i[0].title}" data-item-artist="${i[0].artist}" id="item-${i[1]}" class="list-group-item track" style="display: grid; grid-template-columns: 32px 1fr max-content;">
+ <div style="opacity: .5; display: flex; align-items: center; justify-content: left;"></div>
+ <div style="display: grid; grid-template-columns: 48px 1fr; grid-gap: 10px;">
+ <img src="/assets/content/${i[1]}.jpg" style="width: 48px; height: 48px;">
+ <div style="width: 50vw; height: 3em; display: flex; align-items: center; justify-content: left;">
+ <div style="max-width: 100%;"><div style="max-width: 100%; white-space: nowrap; overflow: hidden !important; text-overflow: ellipsis;">${i[0].title}</div><div style="max-width: 100%; opacity: .5; white-space: nowrap; overflow: hidden !important; text-overflow: ellipsis;">${i[0].artist}</div></div>
+ </div>
+ </div>
+ <div class="list-actions">
+ <span onclick="removeSong(${j + window.parent.currentPlaylistPosition + 1});" class="player-btn" style="border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; height: 48px; width: 48px;" id="btn-remove-${i[1]}">
+ <img class="icon" alt="" src="/assets/icons/remove.svg" style="pointer-events: none; width: 32px; height: 32px;" id="btn-remove-${i[1]}-icon">
+ </span>
+ <span onclick="window.parent.playSong('${i[1]}', 'keep');" class="player-btn" style="border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; height: 48px; width: 48px;" id="btn-play-${i[1]}">
+ <img class="icon" alt="" src="/assets/icons/play.svg" style="pointer-events: none; width: 32px; height: 32px;" id="btn-play-${i[1]}-icon">
+ </span>
+ </div>
+ </div>
+ `).join("");
+ }
+ }
+
+ function removeSong(index) {
+ window.parent.playlist.splice(index, 1);
+ refreshQueue();
+ }
+
+ refreshQueue();
+ </script>
+
+ <br><br>
+</body>
+</html> \ No newline at end of file
diff --git a/app/ui/search.php b/app/ui/search.php
new file mode 100644
index 0000000..7d9fbba
--- /dev/null
+++ b/app/ui/search.php
@@ -0,0 +1,91 @@
+<?php header("X-Frame-Options: SAMEORIGIN"); require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php";
+
+if (!isset($_GET["q"]) || trim($_GET["q"]) === "" || trim(preg_replace("/ +/m", " ", preg_replace("/[^a-z\d ]/m", " ", strtolower($_GET["q"])))) === "") {
+ header("Location: explore.php");
+ die();
+}
+
+?>
+<!doctype html>
+<html lang="en">
+<head>
+ <script>
+ if (typeof window.parent.openModal === "undefined") {
+ location.href = "/app/#/explore";
+ }
+ </script>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>search</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <link href="/assets/dark.css" rel="stylesheet">
+ <link href="/assets/styles.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+ <script src="/assets/localforage.min.js"></script>
+ <script src="/assets/fuse.min.js"></script>
+ <script src="/assets/js/shortcuts.js"></script>
+ <link id="native-css" href="/assets/native.css" rel="stylesheet" disabled>
+</head>
+<body class="crossplatform">
+ <script src="/assets/js/common.js"></script>
+ <div class="container">
+ <br>
+ <h2 style="margin-top: 10px; margin-bottom: 20px; margin-left: 10px;">Search results for "<?= strip_tags($_GET["q"]) ?>"</h2>
+ <?php
+
+ global $albums; global $songs;
+
+ function getMatches($item) {
+ $n = 0;
+ $query = preg_replace("/ +/m", " ", preg_replace("/[^a-z\d ]/m", " ", strtolower($_GET["q"])));
+
+ if (isset($item["title"]) && str_contains(preg_replace("/ +/m", " ", preg_replace("/[^a-z\d ]/m", " ", strtolower($item["title"]))), $query)) $n++;
+ if (isset($item["artist"]) && str_contains(preg_replace("/ +/m", " ", preg_replace("/[^a-z\d ]/m", " ", strtolower($item["artist"]))), $query)) $n++;
+ if (isset($item["album"]) && str_contains(preg_replace("/ +/m", " ", preg_replace("/[^a-z\d ]/m", " ", strtolower($item["album"]))), $query)) $n++;
+
+ return $n;
+ }
+
+ $albums = array_filter($albums, function ($i) {
+ return getMatches($i) > 0;
+ });
+
+ uasort($albums, function ($a, $b) {
+ return strcmp($a["title"], $b["title"]);
+ });
+
+ uasort($albums, function ($a, $b) {
+ return strcmp($a["artist"], $b["artist"]);
+ });
+
+ uasort($albums, function ($a, $b) {
+ return getMatches($b) - getMatches($a);
+ });
+
+ $songs = array_filter($songs, function ($i) {
+ return getMatches($i) > 0;
+ });
+
+ uasort($songs, function ($a, $b) {
+ return getMatches($b) - getMatches($a);
+ });
+
+ ?>
+
+ <div id="album-grid" style="display: grid; grid-template-columns: repeat(5, 1fr);">
+ <?php foreach ($albums as $id => $album): ?>
+ <a id="album-<?= $id ?>" data-item-track="<?= $album["title"] ?>" data-item-artist="<?= $album["artist"] ?>" href="listing.php?a=<?= $id ?>" style="padding: 10px; color: inherit; text-decoration: inherit;" class="album">
+ <img class="album-list-art" alt="" src="/assets/content/<?= $id ?>.jpg" style="width: 100%; aspect-ratio: 1; border-radius: 5px; margin-bottom: 5px;">
+ <div class="album-list-item" style="max-width: calc(80vw / 5); white-space: nowrap; overflow: hidden !important; text-overflow: ellipsis;"><?= $album["title"] ?></div>
+ <div class="album-list-item" style="max-width: calc(80vw / 5); opacity: .5; white-space: nowrap; overflow: hidden !important; text-overflow: ellipsis;"><?= $album["artist"] ?></div>
+ </a>
+ <?php endforeach; ?>
+ </div>
+ <?php global $songs; displayList($songs); ?>
+ </div>
+
+ <br><br>
+</body>
+</html> \ No newline at end of file
diff --git a/app/ui/settings.php b/app/ui/settings.php
new file mode 100644
index 0000000..e6dc9ba
--- /dev/null
+++ b/app/ui/settings.php
@@ -0,0 +1,90 @@
+<?php header("X-Frame-Options: SAMEORIGIN"); require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; ?>
+<!doctype html>
+<html lang="en">
+<head>
+ <script>
+ if (typeof window.parent.openModal === "undefined") {
+ location.href = "/app/#/settings";
+ }
+ </script>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>settings</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <link href="/assets/dark.css" rel="stylesheet">
+ <link href="/assets/styles.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+ <script src="/assets/localforage.min.js"></script>
+ <script src="/assets/fuse.min.js"></script>
+ <script src="/assets/js/shortcuts.js"></script>
+ <link id="native-css" href="/assets/native.css" rel="stylesheet" disabled>
+</head>
+<body class="crossplatform">
+ <script src="/assets/js/common.js"></script>
+ <div class="container">
+ <br>
+ <h2 style="margin-top: 10px; margin-bottom: 20px; margin-left: 10px;">Settings</h2>
+ <div style="margin-left: 10px;">
+ <div class="form-check form-switch">
+ <input onchange="saveDS();" class="form-check-input" type="checkbox" role="switch" id="data-saving">
+ <label class="form-check-label" for="data-saving">Enable data saving</label>
+ <div class="text-muted small">Data saving disables playing lossless and high-resolution audio. Instead, you will get 256 kbps AAC-encoded audio, which is high efficient. If you use Bluetooth headphones, the difference should be unnoticeable.</div>
+ </div>
+ <script>
+ if (localStorage.getItem("data-saving") === "true") document.getElementById("data-saving").checked = true;
+ function saveDS() {
+ localStorage.setItem("data-saving", document.getElementById("data-saving").checked ? "true" : "false");
+ window.parent.location.reload();
+ }
+ </script>
+
+ <?php if (str_contains($_SERVER['HTTP_USER_AGENT'], "MistNative/")): ?>
+ <div class="form-check form-switch" style="margin-top: 10px;">
+ <input onchange="saveDN();" class="form-check-input" type="checkbox" role="switch" id="desktop-notification">
+ <label class="form-check-label" for="desktop-notification">Display notification when song changes</label>
+ <div class="text-muted small">If this is enabled, a desktop notification will be shown when the song being played changes, containing information about the new song. This requires having notifications enabled in your system settings.</div>
+ </div>
+ <script>
+ if (localStorage.getItem("desktop-notification") === "true") document.getElementById("desktop-notification").checked = true;
+ function saveDN() {
+ localStorage.setItem("desktop-notification", document.getElementById("desktop-notification").checked ? "true" : "false");
+ }
+ </script>
+ <?php endif; ?>
+
+ <?php if (str_contains($_SERVER['HTTP_USER_AGENT'], "MistNative/")): ?>
+ <div class="form-check form-switch" style="margin-top: 10px;">
+ <input onchange="saveRP();" class="form-check-input" type="checkbox" role="switch" id="rich-presence">
+ <label class="form-check-label" for="desktop-notification">Show the song you are listening to on Discord</label>
+ <div class="text-muted small">Using Discord Rich Presence, Mist can display on Discord the song you are currently listening to. You need to have the Discord desktop app installed and running on your computer for this to work.</div>
+ </div>
+ <script>
+ if (localStorage.getItem("rich-presence") === "true") document.getElementById("rich-presence").checked = true;
+ function saveRP() {
+ localStorage.setItem("rich-presence", document.getElementById("rich-presence").checked ? "true" : "false");
+
+ if (localStorage.getItem("rich-presence") === "false") {
+ window.parent.discordRichPresenceData = null;
+ } else {
+ window.parent.discordRichPresenceData = {
+ largeImageKey: "logo"
+ };
+ }
+ }
+ </script>
+ <?php endif; ?>
+
+ <hr>
+ <?php if (str_contains($_SERVER['HTTP_USER_AGENT'], "MistNative/")): ?>
+ <a onclick="window.parent.MistNative.about();" href="#">About Mist</a>
+ <?php else: ?>
+ <div class="text-muted">
+ <img class="icon" src="/assets/logo-transparent.svg" style="vertical-align: middle; filter: grayscale(1) invert(1); width: 32px; height: 32px;" alt="">
+ <span style="vertical-align: middle;">Mist version <?= str_replace("|", " ", file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/version")) ?> (build <?= trim(file_exists("/opt/spotify/build.txt") ? file_get_contents("/opt/spotify/build.txt") : "trunk") ?>) · © <?= date('Y') ?> Equestria.dev</span>
+ </div>
+ <?php endif; ?>
+ </div>
+</body>
+</html> \ No newline at end of file
diff --git a/app/ui/songs.php b/app/ui/songs.php
new file mode 100644
index 0000000..cc81f3b
--- /dev/null
+++ b/app/ui/songs.php
@@ -0,0 +1,5 @@
+<?php
+
+header("X-Frame-Options: SAMEORIGIN");
+unset($_GET["a"]);
+require_once "./listing.php"; \ No newline at end of file
diff --git a/app/ui/update.php b/app/ui/update.php
new file mode 100644
index 0000000..1117e95
--- /dev/null
+++ b/app/ui/update.php
@@ -0,0 +1,67 @@
+<?php header("X-Frame-Options: SAMEORIGIN"); require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; ?>
+<!doctype html>
+<html lang="en">
+<head>
+ <script>
+ if (typeof window.parent.parent.openModal === "undefined") {
+ location.href = "/app/";
+ }
+ </script>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>update</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <link href="/assets/dark.css" rel="stylesheet">
+ <link href="/assets/styles.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+ <script src="/assets/localforage.min.js"></script>
+ <script src="/assets/fuse.min.js"></script>
+ <script src="/assets/js/shortcuts.js"></script>
+ <link id="native-css" href="/assets/native.css" rel="stylesheet" disabled>
+</head>
+<body class="crossplatform" style="background-color: transparent !important;">
+ <script src="/assets/js/common.js"></script>
+ <div style="padding: 1rem;">
+ <div style="text-align: center;">
+ <h2 style="margin-top: 30px;">What's new in Mist?</h2>
+
+ <div style="text-align: left; margin-top: 50px;">
+ <div style="display: grid; grid-template-columns: 48px 1fr; grid-gap: 20px; margin-bottom: 20px;">
+ <img src="/assets/icons/notes-security.svg" style="width: 48px;" class="icon" alt="">
+ <div>
+ <div><b>Enhanced security</b></div>
+ <div>Listening to your favorite songs shouldn't come at the expense of your security and your privacy. Mist now includes protections against the most common forms of attacks.</div>
+ </div>
+ </div>
+ <div style="display: grid; grid-template-columns: 48px 1fr; grid-gap: 20px; margin-bottom: 20px;">
+ <img src="/assets/icons/notes-connect.svg" style="width: 48px;" class="icon" alt="">
+ <div>
+ <div><b>Mist is now on Discord</b></div>
+ <div>If you are using the desktop app, Mist can now use Discord Rich Presence to show the music you are playing to all of your friends. You will also need the Discord desktop app.</div>
+ </div>
+ </div>
+ <div style="display: grid; grid-template-columns: 48px 1fr; grid-gap: 20px; margin-bottom: 20px;">
+ <img src="/assets/icons/notes-android.svg" style="width: 48px;" class="icon" alt="">
+ <div>
+ <div><b>Native Android application</b></div>
+ <div>You can now quit using the Progressive Web App, and instead use the official Mist Android app. Lower battery and resource usage, along with better OS integrations.</div>
+ </div>
+ </div>
+ </div>
+
+ <a style="margin-top: 50px; margin-bottom: 30px; display: block;" class="btn btn-primary" onclick="localStorage.setItem('lastUpdate', '<?= trim(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/version")) ?>|<?= trim(file_exists("/opt/spotify/build.txt") ? file_get_contents("/opt/spotify/build.txt") : "trunk") ?>'); window.parent._modal.hide();">Continue</a>
+ </div>
+ </div>
+
+ <script>
+ window.sizeInterval = setInterval(() => {
+ if (document.body.clientHeight > 0) {
+ clearInterval(sizeInterval);
+ window.parent.document.getElementById("modal-frame").style.height = document.body.clientHeight + "px";
+ }
+ });
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/app/ui/welcome.php b/app/ui/welcome.php
new file mode 100644
index 0000000..800da74
--- /dev/null
+++ b/app/ui/welcome.php
@@ -0,0 +1,49 @@
+<?php header("X-Frame-Options: SAMEORIGIN"); require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; ?>
+<!doctype html>
+<html lang="en">
+<head>
+ <script>
+ if (typeof window.parent.parent.openModal === "undefined") {
+ location.href = "/app/";
+ }
+ </script>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>welcome</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <link href="/assets/dark.css" rel="stylesheet">
+ <link href="/assets/styles.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+ <script src="/assets/localforage.min.js"></script>
+ <script src="/assets/fuse.min.js"></script>
+ <script src="/assets/js/shortcuts.js"></script>
+ <link id="native-css" href="/assets/native.css" rel="stylesheet" disabled>
+</head>
+<body class="crossplatform" style="background-color: transparent !important;">
+ <script src="/assets/js/common.js"></script>
+ <div style="padding: 1rem;">
+ <div style="text-align: center;">
+ <img src="/assets/logo-display.png" alt="Logo" style="width: 96px; margin-bottom: 15px; margin-top: 30px;">
+ <h2>Welcome to<br>Mist</h2>
+
+ <h4 style="margin-top: 30px; font-weight: normal;">The best way to discover and rediscover your favorite music.</h4>
+
+ <p style="margin-top: 100px;" class="small text-muted">Your searches, favorites and library help improve the service. The administrators can provide you with more information about how your data is managed.</p>
+ <p class="small text-muted">With your Equestria.dev Account, you will be able to sign in to available services. Equestria.dev records certain data for security, support and reporting purposes. <a target="_blank" href="https://equestria.dev/legal/privacy">See how this data is managed.</a></p>
+
+ <a style="margin-bottom: 30px; display: block;" class="btn btn-primary" onclick="localStorage.setItem('welcomed', 'true'); localStorage.setItem('lastUpdate', '<?= trim(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/version")) ?>|<?= trim(file_exists("/opt/spotify/build.txt") ? file_get_contents("/opt/spotify/build.txt") : "trunk") ?>'); window.parent._modal.hide();">Continue</a>
+ </div>
+ </div>
+
+ <script>
+ window.sizeInterval = setInterval(() => {
+ if (document.body.clientHeight > 0) {
+ clearInterval(sizeInterval);
+ window.parent.document.getElementById("modal-frame").style.height = document.body.clientHeight + "px";
+ }
+ });
+ </script>
+</body>
+</html> \ No newline at end of file