Skip to content

Commit f522eba

Browse files
authored
Merge pull request #626 from Hikari-Fox/main
Add extra web app grid view tab
2 parents 797387f + 0db6b38 commit f522eba

File tree

6 files changed

+282
-13
lines changed

6 files changed

+282
-13
lines changed

extension/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"cloud-download",
7272
"download",
7373
"gear-fill",
74+
"grid-3x3-gap-fill",
7475
"pencil-square",
7576
"plus-lg",
7677
"trash"

extension/src/_locales/en/messages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,10 @@
745745
"message": "Manage web apps and profiles",
746746
"description": "The title of the manage page"
747747
},
748+
"managePageTabGrid": {
749+
"message": "Grid",
750+
"description": "The grid tab on the manage page"
751+
},
748752
"managePageTabApps": {
749753
"message": "Apps",
750754
"description": "The apps tab on the manage page"

extension/src/_locales/sl/messages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@
183183
"message": "Upravljaj aplikacije in profile",
184184
"description": "The title of the manage page"
185185
},
186+
"managePageTabGrid": {
187+
"message": "Mreža",
188+
"description": "The grid tab on the manage page"
189+
},
186190
"managePageTabApps": {
187191
"message": "Aplikacije",
188192
"description": "The apps tab on the manage page"

extension/src/sites/manage.html

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
<div class="card-header sticky-top">
1414
<nav>
1515
<div class="nav nav-tabs card-header-tabs" id="card-navigation" role="tablist">
16-
<button class="nav-link active" id="sites-tab" data-bs-toggle="tab" data-bs-target="#sites-pane" type="button" role="tab" aria-controls="sites-pane" aria-selected="true" data-i18n="managePageTabApps"></button>
16+
<button class="nav-link active icon-link py-0 bi-grid-3x3-gap-fill" id="grid-tab" data-bs-toggle="tab" data-bs-target="#grid-pane" type="button" role="tab" aria-controls="grid-pane" aria-selected="true" data-i18n data-i18n-title="managePageTabGrid" data-i18n-aria-label="managePageTabGrid"></button>
17+
<button class="nav-link" id="sites-tab" data-bs-toggle="tab" data-bs-target="#sites-pane" type="button" role="tab" aria-controls="sites-pane" aria-selected="false" data-i18n="managePageTabApps"></button>
1718
<button class="nav-link" id="profiles-tab" data-bs-toggle="tab" data-bs-target="#profiles-pane" type="button" role="tab" aria-controls="profiles-pane" aria-selected="false" data-i18n="managePageTabProfiles"></button>
1819
<button class="nav-link icon-link ms-auto py-0 bi-gear-fill lead" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-pane" type="button" role="tab" aria-controls="settings-pane" aria-selected="false" data-i18n data-i18n-title="managePageTabSettings" data-i18n-aria-label="managePageTabSettings"></button>
1920
</div>
@@ -22,7 +23,38 @@
2223

2324
<div class="card-body">
2425
<div class="tab-content">
25-
<div class="tab-pane fade show active" id="sites-pane" role="tabpanel" aria-labelledby="sites-tab">
26+
<div class="tab-pane fade show active" id="grid-pane" role="tabpanel" aria-labelledby="grid-tab">
27+
<div class="list-group list-group-flush list-group-border-last" id="grid-list">
28+
<template id="grid-list-template">
29+
<div class="grid-item">
30+
<div class="icon-container">
31+
<div class="site-icon me-2 my-auto letter-icon" id="grid-list-template-letter"></div>
32+
<img class="site-icon me-2 my-auto" id="grid-list-template-icon" />
33+
</div>
34+
<div class="me-2 text-overflow-hide">
35+
<div class="list-group-item-name" id="grid-list-template-title"></div>
36+
</div>
37+
<div class="grid-item-buttons">
38+
<button type="button" class="btn btn-outline-primary bi-box-arrow-up-right" id="grid-list-template-launch"></button>
39+
<button type="button" class="btn btn-outline-primary bi-pencil-square" id="grid-list-template-edit"></button>
40+
<button type="button" class="btn btn-outline-primary bi-trash" id="grid-list-template-remove"></button>
41+
</div>
42+
</div>
43+
</template>
44+
<div class="list-group-item justify-content-between align-items-start d-flex" id="grid-list-loading">
45+
<div>
46+
<div><em data-i18n="commonLoading"></em></div>
47+
</div>
48+
</div>
49+
<div class="list-group-item justify-content-between align-items-start d-flex d-none" id="grid-list-empty">
50+
<div>
51+
<div><em data-i18n="managePageAppListEmpty"></em></div>
52+
</div>
53+
</div>
54+
</div>
55+
</div>
56+
57+
<div class="tab-pane fade" id="sites-pane" role="tabpanel" aria-labelledby="sites-tab">
2658
<div class="list-group list-group-flush list-group-border-last" id="sites-list">
2759
<button type="button" class="list-group-item list-group-item-action fst-italic" id="site-install-button">
2860
<span class="bi bi-plus-lg pe-1"></span>
@@ -367,8 +399,8 @@ <h5 class="offcanvas-title" id="profile-edit-label" data-i18n="managePageProfile
367399
</div>
368400
<div class="mb-3" id="profile-template-editing-div">
369401
<label for="profile-template" class="form-label no-validation mb-1">
370-
<input class="form-check-input" type="checkbox" value="" id="profile-template-editing-apply" />
371-
<label class="form-check-label" for="profile-template-editing-apply" data-i18n="managePageProfileEditApplyProfileLabel"></label>
402+
<input class="form-check-input" type="checkbox" value="" id="profile-template-editing-apply" />
403+
<label class="form-check-label" for="profile-template-editing-apply" data-i18n="managePageProfileEditApplyProfileLabel"></label>
372404
</label>
373405
<label for="profile-template-editing" class="form-label visually-hidden" data-i18n="profileTemplate"></label>
374406
<input type="text" class="form-control form-control-sm" id="profile-template-editing" data-i18n data-i18n-placeholder="profileTemplatePlaceholder" />

extension/src/sites/manage.js

Lines changed: 134 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,35 @@ async function createSiteList () {
7575

7676
// Get the list elements
7777
const listElement = document.getElementById('sites-list')
78+
const gridContainer = document.getElementById('grid-list')
7879
const templateElement = document.getElementById('sites-list-template')
80+
const gridTemplateElement = document.getElementById('grid-list-template')
7981
const loadingElement = document.getElementById('sites-list-loading')
82+
const gridLoadingElement = document.getElementById('grid-list-loading')
8083
const emptyElement = document.getElementById('sites-list-empty')
84+
const gridEmptyElement = document.getElementById('grid-list-empty')
8185

8286
loadingElement.classList.add('d-none')
83-
if (!sites.length) emptyElement.classList.remove('d-none')
87+
gridLoadingElement.classList.add('d-none')
88+
89+
if (!sites.length) {
90+
emptyElement.classList.remove('d-none')
91+
gridEmptyElement.classList.remove('d-none')
92+
}
8493

8594
// Create a list element for every instance with handlers for launching and editing
8695
for (const site of sites) {
96+
// Create list view item
8797
const siteElement = templateElement.content.firstElementChild.cloneNode(true)
98+
99+
// Create grid view item
100+
const gridItem = gridTemplateElement.content.firstElementChild.cloneNode(true)
101+
88102
const siteName = sanitizeString(site.config.name || site.manifest.name || site.manifest.short_name) || new URL(site.manifest.scope).host
89103
const siteDescription = sanitizeString(site.config.description || site.manifest.description) || ''
90104
const siteIcon = site.config.icon_url || getIcon(buildIconList(site.manifest.icons), 64)
91105

106+
// Setup list view item
92107
const letterElement = siteElement.querySelector('#sites-list-template-letter')
93108
if (siteIcon) letterElement.classList.add('d-none')
94109
letterElement.setAttribute('data-letter', siteName[0])
@@ -104,6 +119,71 @@ async function createSiteList () {
104119
iconElement.classList.add('d-none')
105120
}
106121

122+
// Setup grid view item
123+
const gridLetterElement = gridItem.querySelector('#grid-list-template-letter')
124+
if (siteIcon) gridLetterElement.classList.add('d-none')
125+
gridLetterElement.setAttribute('data-letter', siteName[0])
126+
gridLetterElement.removeAttribute('id')
127+
128+
const gridIconElement = gridItem.querySelector('#grid-list-template-icon')
129+
if (!siteIcon) gridIconElement.classList.add('d-none')
130+
gridIconElement.src = siteIcon
131+
gridIconElement.setAttribute('alt', await getMessage('managePageAppListIcon'))
132+
gridIconElement.removeAttribute('id')
133+
gridIconElement.onerror = () => {
134+
gridLetterElement.classList.remove('d-none')
135+
gridIconElement.classList.add('d-none')
136+
}
137+
138+
// Handle grid item clicks to show/hide buttons
139+
const buttonsPopup = gridItem.querySelector('.grid-item-buttons')
140+
gridItem.addEventListener('click', (event) => {
141+
// Don't show popup if clicking on a button
142+
if (event.target.closest('.grid-item-buttons')) {
143+
return
144+
}
145+
146+
// Remove active class from all other items
147+
document.querySelectorAll('.grid-item').forEach(item => {
148+
if (item !== gridItem) {
149+
item.classList.remove('active')
150+
}
151+
})
152+
153+
// Toggle active class on clicked item
154+
gridItem.classList.toggle('active')
155+
156+
if (gridItem.classList.contains('active')) {
157+
// Position the popup at click coordinates
158+
buttonsPopup.style.top = `${event.clientY}px`
159+
buttonsPopup.style.left = `${event.clientX}px`
160+
161+
// Adjust position if popup would go off screen
162+
const rect = buttonsPopup.getBoundingClientRect()
163+
const viewportWidth = document.documentElement.clientWidth
164+
const viewportHeight = document.documentElement.clientHeight
165+
166+
if (rect.right > viewportWidth) {
167+
buttonsPopup.style.left = `${event.clientX - rect.width}px`
168+
}
169+
if (rect.bottom > viewportHeight) {
170+
buttonsPopup.style.top = `${event.clientY - rect.height}px`
171+
}
172+
}
173+
174+
event.stopPropagation()
175+
})
176+
177+
// Close popup when clicking outside
178+
document.addEventListener('click', (event) => {
179+
if (!event.target.closest('.grid-item')) {
180+
document.querySelectorAll('.grid-item').forEach(item => {
181+
item.classList.remove('active')
182+
})
183+
}
184+
})
185+
186+
// Set titles and descriptions
107187
const titleElement = siteElement.querySelector('#sites-list-template-title')
108188
titleElement.innerText = siteName
109189
titleElement.removeAttribute('id')
@@ -112,16 +192,32 @@ async function createSiteList () {
112192
descriptionElement.innerText = siteDescription
113193
descriptionElement.removeAttribute('id')
114194

195+
const gridTitleElement = gridItem.querySelector('#grid-list-template-title')
196+
gridTitleElement.innerText = siteName
197+
gridTitleElement.removeAttribute('id')
198+
199+
// Setup launch buttons
115200
const launchElement = siteElement.querySelector('#sites-list-template-launch')
201+
const gridLaunchElement = gridItem.querySelector('#grid-list-template-launch')
116202
const launchElementTooltip = await getMessage('managePageAppListLaunch')
203+
117204
launchElement.addEventListener('click', () => { launchSite(site) })
205+
gridLaunchElement.addEventListener('click', () => { launchSite(site) })
206+
118207
launchElement.setAttribute('title', launchElementTooltip)
119208
launchElement.setAttribute('aria-label', launchElementTooltip)
120209
launchElement.removeAttribute('id')
121210

211+
gridLaunchElement.setAttribute('title', launchElementTooltip)
212+
gridLaunchElement.setAttribute('aria-label', launchElementTooltip)
213+
gridLaunchElement.removeAttribute('id')
214+
215+
// Setup edit buttons
122216
const editElement = siteElement.querySelector('#sites-list-template-edit')
217+
const gridEditElement = gridItem.querySelector('#grid-list-template-edit')
123218
const editElementTooltip = await getMessage('managePageAppListEdit')
124-
editElement.addEventListener('click', async (event) => {
219+
220+
const editHandler = async (event) => {
125221
const form = document.getElementById('web-app-form')
126222
const submit = document.getElementById('web-app-submit')
127223

@@ -379,14 +475,25 @@ async function createSiteList () {
379475
// Show offcanvas element
380476
Offcanvas.getOrCreateInstance(document.getElementById('site-edit-offcanvas')).show()
381477
event.preventDefault()
382-
})
478+
}
479+
480+
editElement.addEventListener('click', editHandler)
481+
gridEditElement.addEventListener('click', editHandler)
482+
383483
editElement.setAttribute('title', editElementTooltip)
384484
editElement.setAttribute('aria-label', editElementTooltip)
385485
editElement.removeAttribute('id')
386486

487+
gridEditElement.setAttribute('title', editElementTooltip)
488+
gridEditElement.setAttribute('aria-label', editElementTooltip)
489+
gridEditElement.removeAttribute('id')
490+
491+
// Setup remove buttons
387492
const removeElement = siteElement.querySelector('#sites-list-template-remove')
493+
const gridRemoveElement = gridItem.querySelector('#grid-list-template-remove')
388494
const removeElementTooltip = await getMessage('managePageAppListRemove')
389-
removeElement.addEventListener('click', () => {
495+
496+
const removeHandler = () => {
390497
const lastSiteInProfile = profiles[site.profile].sites.length <= 1
391498

392499
document.getElementById('site-remove-button').onclick = async function () {
@@ -433,12 +540,21 @@ async function createSiteList () {
433540
}
434541

435542
Modal.getOrCreateInstance(document.getElementById('site-remove-modal')).show()
436-
})
543+
}
544+
545+
removeElement.addEventListener('click', removeHandler)
546+
gridRemoveElement.addEventListener('click', removeHandler)
547+
437548
removeElement.setAttribute('title', removeElementTooltip)
438549
removeElement.setAttribute('aria-label', removeElementTooltip)
439550
removeElement.removeAttribute('id')
440551

552+
gridRemoveElement.setAttribute('title', removeElementTooltip)
553+
gridRemoveElement.setAttribute('aria-label', removeElementTooltip)
554+
gridRemoveElement.removeAttribute('id')
555+
441556
listElement.insertBefore(siteElement, templateElement)
557+
gridContainer.insertBefore(gridItem, gridTemplateElement)
442558
}
443559
}
444560

@@ -689,29 +805,38 @@ async function createProfileList () {
689805

690806
// Handle site and profile search
691807
async function handleSearch () {
692-
const searchHandler = function (listElement) {
808+
const searchHandler = function (listElement, gridElement) {
693809
document.getElementById('search-box').classList.remove('invisible')
694810

695811
document.getElementById('search-input').oninput = function () {
812+
const searchQuery = sanitizeString(this.value.toLowerCase())
813+
696814
for (const item of document.getElementById(listElement).children) {
697815
const itemName = sanitizeString(item.querySelector('.list-group-item-name')?.innerText.toLowerCase())
698-
const searchQuery = sanitizeString(this.value.toLowerCase())
699-
700816
if (!itemName) continue
701817
item.classList.toggle('d-none', itemName.indexOf(searchQuery) === -1)
702818
}
819+
820+
if (gridElement) {
821+
for (const item of document.getElementById(gridElement).children) {
822+
const itemName = sanitizeString(item.querySelector('.list-group-item-name')?.innerText.toLowerCase())
823+
if (!itemName) continue
824+
item.classList.toggle('d-none', itemName.indexOf(searchQuery) === -1)
825+
}
826+
}
703827
}
704828
}
705829

706830
const searchHide = function () {
707831
document.getElementById('search-box').classList.add('invisible')
708832
}
709833

834+
document.getElementById('grid-tab').addEventListener('click', () => searchHandler('grid-list'))
710835
document.getElementById('sites-tab').addEventListener('click', () => searchHandler('sites-list'))
711836
document.getElementById('profiles-tab').addEventListener('click', () => searchHandler('profiles-list'))
712837
document.getElementById('settings-tab').addEventListener('click', () => searchHide())
713838

714-
searchHandler('sites-list')
839+
searchHandler('grid-list')
715840
}
716841

717842
// Handle extension settings

0 commit comments

Comments
 (0)