Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 103 additions & 47 deletions src_assets/common/assets/web/apps.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,37 @@ <h1>{{ $t('apps.applications_title') }}<span v-if="apps.length"> ({{ appCountLab
</div>

<!-- Actions toolbar -->
<div class="apps-toolbar d-flex flex-wrap justify-content-end align-items-center gap-2 mb-3"
v-if="apps && apps.length > 0">
<!-- Sort by name toggle -->
<button class="btn btn-outline-secondary" type="button" @click="toggleSort"
:class="{ active: sortMode !== 'default' }"
:aria-pressed="sortMode !== 'default'"
:title="$t('apps.sort_by_name') + ': ' + sortModeLabel">
<arrow-up-down v-if="sortMode === 'default'" :size="16" class="icon me-1"></arrow-up-down>
<arrow-up v-else-if="sortMode === 'asc'" :size="16" class="icon me-1"></arrow-up>
<arrow-down v-else :size="16" class="icon me-1"></arrow-down>
{{ $t('apps.sort_by_name') }}
</button>
<!-- Search box -->
<div class="input-group">
<span class="input-group-text">
<search :size="16" class="icon"></search>
</span>
<input type="text" class="form-control" v-model="searchQuery" :placeholder="$t('apps.search_placeholder')" />
<button v-if="searchQuery" class="btn btn-outline-secondary" type="button" @click="resetSearchQuery" :aria-label="$t('_common.close')">
<x :size="16" class="icon"></x>
<div class="apps-toolbar d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
<!-- Left side actions -->
<div class="d-flex align-items-center gap-2">
<!-- Add new application -->
<button class="btn btn-primary" @click="newApp">
<layers-plus :size="18" class="icon"></layers-plus>
{{ $t('apps.add_new') }}
</button>
</div>
<!-- Right side actions -->
<div class="d-flex align-items-center gap-2" v-if="apps && apps.length > 0">
<!-- Sort by name toggle -->
<button class="btn btn-outline-secondary text-nowrap" type="button" @click="toggleSort"
:class="{ active: sortMode !== 'default' }"
:aria-pressed="sortMode !== 'default'"
:title="$t('apps.sort_by_name') + ': ' + sortModeLabel">
<arrow-up-down v-if="sortMode === 'default'" :size="16" class="icon me-1"></arrow-up-down>
<arrow-up v-else-if="sortMode === 'asc'" :size="16" class="icon me-1"></arrow-up>
<arrow-down v-else :size="16" class="icon me-1"></arrow-down>
{{ $t('apps.sort_by_name') }}
</button>
<!-- Search box -->
<div class="input-group">
<input type="text" class="form-control" v-model="searchQuery" :placeholder="$t('apps.search_placeholder')" />
<button v-if="searchQuery" class="btn btn-outline-secondary" type="button" @click="resetSearchQuery" :aria-label="$t('_common.close')">
<x :size="16" class="icon"></x>
</button>
<span v-else class="input-group-text">
<search :size="16" class="icon"></search>
</span>
</div>
</div>
</div>

Expand Down Expand Up @@ -71,7 +81,7 @@ <h5 class="card-title mb-2">{{ app.name }}</h5>
<edit :size="16" class="icon"></edit>
{{ $t('apps.edit') }}
</button>
<button class="btn btn-sm btn-danger" @click="showDeleteForm(index)">
<button class="btn btn-sm btn-danger" @click="showDeleteModal(index)">
<trash-2 :size="16" class="icon"></trash-2>
</button>
</div>
Expand Down Expand Up @@ -416,7 +426,7 @@ <h4>{{ $t('apps.env_vars_about') }}</h4>
<pre>sh -c "displayplacer "id:&lt;screenId&gt; res:${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT} hz:${SUNSHINE_CLIENT_FPS} scaling:on origin:(0,0) degree:0""</pre>
</div>
<div class="form-text"><a
:href="`${documentationBaseUrl}/md_docs_2app__examples.html`"
href="https://docs.lizardbyte.dev/projects/sunshine/latest/md_docs_2app__examples.html"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be conflicted. Can you fix conflicts and rebase?

target="_blank">{{ $t('_common.see_more') }}</a></div>
</div>
<!-- Save buttons -->
Expand Down Expand Up @@ -569,7 +579,6 @@ <h5 class="modal-title">{{ fileBrowserTitle || $t('file_browser.title') }}</h5>
data() {
return {
apps: [],
showEditForm: false,
editForm: null,
detachedCmd: "",
coverSearching: false,
Expand All @@ -589,6 +598,7 @@ <h5 class="modal-title">{{ fileBrowserTitle || $t('file_browser.title') }}</h5>
fileBrowserTypedPath: "",
searchQuery: "",
sortMode: "default",
deleteTarget: null,
version: null,
githubVersion: null,
};
Expand Down Expand Up @@ -646,14 +656,21 @@ <h5 class="modal-title">{{ fileBrowserTitle || $t('file_browser.title') }}</h5>
? `${total}`
: `${shown} / ${total}`;
},
editModalTitle() {
if (!this.editForm) {
return "";
}
const action = this.editForm.index === -1
? this.$t("apps.add_new")
: this.$t("apps.edit");

return this.editForm.name
? `${action}: ${this.editForm.name}`
: action;
},
},
created() {
fetch("./api/apps")
.then((r) => r.json())
.then((r) => {
console.log(r);
this.apps = r.apps;
});
this.loadApps();

fetch("./api/config")
.then(r => r.json())
Expand Down Expand Up @@ -683,8 +700,7 @@ <h5 class="modal-title">{{ fileBrowserTitle || $t('file_browser.title') }}</h5>
detached: [],
"image-path": ""
};
this.editForm.index = -1;
this.showEditForm = true;
this.openEditModal();
},
editApp(id) {
this.editForm = JSON.parse(JSON.stringify(this.apps[id]));
Expand All @@ -707,22 +723,34 @@ <h5 class="modal-title">{{ fileBrowserTitle || $t('file_browser.title') }}</h5>
if (this.editForm["exit-timeout"] === undefined) {
this.editForm["exit-timeout"] = 5;
}
this.showEditForm = true;
},
showDeleteForm(id) {
let resp = confirm(
"Are you sure to delete " + this.apps[id].name + "?"
);
if (resp) {
apiFetch("./api/apps/" + id, {
method: "DELETE",
headers: {
"Content-Type": "application/json"
},
}).then((r) => {
if (r.status === 200) document.location.reload();
this.openEditModal();
},
showDeleteModal(id) {
this.deleteTarget = { index: id, name: this.apps[id].name };
this.$nextTick(() => {
Modal.getOrCreateInstance(this.$refs.deleteModal).show();
});
},
closeDeleteModal() {
const modal = Modal.getInstance(this.$refs.deleteModal);
if (modal) modal.hide();
},
loadApps() {
return fetch("./api/apps")
.then((r) => r.json())
.then((r) => {
this.apps = r.apps;
});
}
},
confirmDelete() {
apiFetch("./api/apps/" + this.deleteTarget.index, {
method: "DELETE",
headers: {
"Content-Type": "application/json"
},
}).then((r) => {
if (r.status === 200) document.location.reload();
});
},
addPrepCmd() {
let template = {
Expand All @@ -747,6 +775,8 @@ <h5 class="modal-title">{{ fileBrowserTitle || $t('file_browser.title') }}</h5>
this.coverCandidates = [];
this.coverSearchQuery = "";

this.showStacked(this.$refs.coverFinderModal);

// Perform initial search with app name
this.performCoverSearch();
},
Expand Down Expand Up @@ -844,7 +874,7 @@ <h5 class="modal-title">{{ fileBrowserTitle || $t('file_browser.title') }}</h5>
this.fileBrowserTypedPath = startPath || '';
this.fileBrowserError = '';
this.fileBrowserNavigate(startPath || '');
Modal.getOrCreateInstance(this.$refs.fileBrowserModal).show();
this.showStacked(this.$refs.fileBrowserModal);
},
fileBrowserClose() {
const modal = Modal.getInstance(this.$refs.fileBrowserModal);
Expand Down Expand Up @@ -944,6 +974,32 @@ <h5 class="modal-title">{{ fileBrowserTitle || $t('file_browser.title') }}</h5>
else
this.sortMode = "default";
},
openEditModal() {
// Use $nextTick because if the edit version of the modal is created too early, it breaks...
this.$nextTick(() => {
Modal.getOrCreateInstance(this.$refs.editModal).show();
});
},
closeEditModal() {
const modal = Modal.getInstance(this.$refs.editModal);
if (modal) modal.hide();
},
// We need to update the z-index since bootstrap gives all modals the same z-index
// Fixes the stacking issue of modals (cover finder / file browser)
showStacked(modalEl) {
modalEl.addEventListener('show.bs.modal', () => {
const openCount = document.querySelectorAll('.modal.show').length;
const z = 1055 + (openCount + 1) * 20;
modalEl.style.zIndex = z;

requestAnimationFrame(() => {
const backdrops = document.querySelectorAll('.modal-backdrop');
const backdrop = backdrops[backdrops.length - 1];
if (backdrop) backdrop.style.zIndex = z - 10;
});
}, { once: true });
Modal.getOrCreateInstance(modalEl).show();
},
},
});

Expand Down
3 changes: 2 additions & 1 deletion src_assets/common/assets/web/public/assets/css/sunshine.css
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,7 @@ body {
.modal-header,
.modal-footer {
border-color: var(--color-border) !important;
background-color: var(--color-surface-raised) !important;
}

.list-group-item {
Expand Down Expand Up @@ -1713,7 +1714,7 @@ p {
}

.apps-toolbar .input-group {
max-width: 18rem;
width: 14rem;
}

.apps-toolbar .btn-outline-secondary {
Expand Down
2 changes: 2 additions & 0 deletions src_assets/common/assets/web/public/assets/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
"covers_found": "Covers Found",
"cover_search_hint": "Search names should match IGDB naming conventions.",
"delete": "Delete",
"delete_confirm": "Are you sure you want to delete {name}?",
"delete_title": "Delete Application",
"detached_cmds": "Detached Commands",
"detached_cmds_add": "Add Detached Command",
"detached_cmds_desc": "A list of commands to be run in the background.",
Expand Down