From 3a72015161ded7b257253e998ae96f09e1263183 Mon Sep 17 00:00:00 2001 From: Noklef Date: Fri, 22 May 2026 03:16:01 +1000 Subject: [PATCH] feat(web-ui): Added Filtering & Searching to the Apps page (#5158) Co-authored-by: Noklef <281545466+Noklef@users.noreply.github.com> --- src_assets/common/assets/web/apps.html | 110 ++++++++++++++++-- .../assets/web/public/assets/css/sunshine.css | 14 +++ .../assets/web/public/assets/locale/en.json | 7 ++ 3 files changed, 124 insertions(+), 7 deletions(-) diff --git a/src_assets/common/assets/web/apps.html b/src_assets/common/assets/web/apps.html index dd294427..13a53fd5 100644 --- a/src_assets/common/assets/web/apps.html +++ b/src_assets/common/assets/web/apps.html @@ -9,18 +9,43 @@
-

{{ $t('apps.applications_title') }}

+

{{ $t('apps.applications_title') }} ({{ appCountLabel }})

{{ $t('apps.applications_desc') }}

+ +
+ + + +
+ + + + + +
+
+ -
-
+
+
- -
@@ -55,6 +80,13 @@
+ +
+
+

{{ $t('apps.no_search_results') }}

+
+
+
@@ -484,7 +516,10 @@ import { apiFetch } from './fetch_utils' import { Modal } from 'bootstrap/dist/js/bootstrap' import { + ArrowDown, ArrowRight, + ArrowUp, + ArrowUpDown, Check, Edit, FileText, @@ -508,7 +543,10 @@ components: { Navbar, Checkbox, + ArrowDown, ArrowRight, + ArrowUp, + ArrowUpDown, Check, Edit, FileText, @@ -548,8 +586,54 @@ fileBrowserError: "", fileBrowserSelectedPath: "", fileBrowserTypedPath: "", + searchQuery: "", + sortMode: "default", }; }, + computed: { + displayedApps() { + let list = this.apps.map((app, index) => ({ app, index })); + + const query = this.searchQuery.trim().toLowerCase(); + if (query) { + list = list.filter(({ app }) => + (app.name || "").toLowerCase().includes(query) + ); + } + + if (this.sortMode !== "default") { + // Apps can be created without a name, so we compare name || "" + list.sort((a, b) => { + const result = (a.app.name || "").localeCompare( + b.app.name || "", undefined, { sensitivity: "base" } + ); + // localeCompare returns 0 if the strings are equal, a negative value if a < b, and a positive value if a > b + return this.sortMode === "asc" + ? result + : -result; + }); + } + + return list; + }, + sortModeLabel() { + switch (this.sortMode) { + case "asc": + return this.$t("apps.sort_ascending"); + case "desc": + return this.$t("apps.sort_descending"); + default: + return this.$t("apps.sort_default"); + } + }, + appCountLabel() { + const total = this.apps.length; + const shown = this.displayedApps.length; + return shown === total + ? `${total}` + : `${shown} / ${total}`; + }, + }, created() { fetch("./api/apps") .then((r) => r.json()) @@ -826,7 +910,19 @@ if (placeholder && placeholder.classList.contains('app-poster-placeholder')) { placeholder.style.display = 'flex'; } - } + }, + resetSearchQuery() { + this.searchQuery = ""; + }, + toggleSort() { + // Sorting goes default -> ascending -> descending -> default + if (this.sortMode === "default") + this.sortMode = "asc"; + else if (this.sortMode === "asc") + this.sortMode = "desc"; + else + this.sortMode = "default"; + }, }, }); diff --git a/src_assets/common/assets/web/public/assets/css/sunshine.css b/src_assets/common/assets/web/public/assets/css/sunshine.css index 6dbde5df..a8efdffd 100644 --- a/src_assets/common/assets/web/public/assets/css/sunshine.css +++ b/src_assets/common/assets/web/public/assets/css/sunshine.css @@ -1712,6 +1712,20 @@ p { align-items: center; } +.apps-toolbar .input-group { + max-width: 18rem; +} + +.apps-toolbar .btn-outline-secondary { + border: 1px solid var(--color-border); +} + +.apps-toolbar .btn-outline-secondary.active { + background-color: var(--color-bg-muted); + border-color: var(--color-border-strong); + color: var(--color-text-base); +} + /* ========================================================================== Featured Apps Page Styles ========================================================================== */ diff --git a/src_assets/common/assets/web/public/assets/locale/en.json b/src_assets/common/assets/web/public/assets/locale/en.json index 5c3996ca..a64a76da 100644 --- a/src_assets/common/assets/web/public/assets/locale/en.json +++ b/src_assets/common/assets/web/public/assets/locale/en.json @@ -81,11 +81,18 @@ "image_desc": "Application icon/picture/image path that will be sent to client. Image must be a PNG file. If not set, Sunshine will send default box image.", "loading": "Loading...", "name": "Name", + "no_applications": "No applications have been added yet", "no_covers_found": "No covers found", + "no_search_results": "No applications match your search", "output_desc": "The file where the output of the command is stored, if it is not specified, the output is ignored", "output_name": "Output", "run_as_desc": "This can be necessary for some applications that require administrator permissions to run properly.", + "search_placeholder": "Search applications...", "searching_covers": "Searching for covers...", + "sort_ascending": "Ascending", + "sort_by_name": "Sort by Name", + "sort_default": "Default order", + "sort_descending": "Descending", "wait_all": "Continue streaming until all app processes exit", "wait_all_desc": "This will continue streaming until all processes started by the app have terminated. When unchecked, streaming will stop when the initial app process exits, even if other app processes are still running.", "working_dir": "Working Directory",