Skip to content

Commit

Permalink
Merge pull request #626 from Hikari-Fox/main
Browse files Browse the repository at this point in the history
Add extra web app grid view tab
  • Loading branch information
filips123 authored Feb 3, 2025
2 parents 797387f + 0db6b38 commit f522eba
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 13 deletions.
1 change: 1 addition & 0 deletions extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"cloud-download",
"download",
"gear-fill",
"grid-3x3-gap-fill",
"pencil-square",
"plus-lg",
"trash"
Expand Down
4 changes: 4 additions & 0 deletions extension/src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,10 @@
"message": "Manage web apps and profiles",
"description": "The title of the manage page"
},
"managePageTabGrid": {
"message": "Grid",
"description": "The grid tab on the manage page"
},
"managePageTabApps": {
"message": "Apps",
"description": "The apps tab on the manage page"
Expand Down
4 changes: 4 additions & 0 deletions extension/src/_locales/sl/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@
"message": "Upravljaj aplikacije in profile",
"description": "The title of the manage page"
},
"managePageTabGrid": {
"message": "Mreža",
"description": "The grid tab on the manage page"
},
"managePageTabApps": {
"message": "Aplikacije",
"description": "The apps tab on the manage page"
Expand Down
40 changes: 36 additions & 4 deletions extension/src/sites/manage.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
<div class="card-header sticky-top">
<nav>
<div class="nav nav-tabs card-header-tabs" id="card-navigation" role="tablist">
<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>
<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>
<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>
<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>
<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>
</div>
Expand All @@ -22,7 +23,38 @@

<div class="card-body">
<div class="tab-content">
<div class="tab-pane fade show active" id="sites-pane" role="tabpanel" aria-labelledby="sites-tab">
<div class="tab-pane fade show active" id="grid-pane" role="tabpanel" aria-labelledby="grid-tab">
<div class="list-group list-group-flush list-group-border-last" id="grid-list">
<template id="grid-list-template">
<div class="grid-item">
<div class="icon-container">
<div class="site-icon me-2 my-auto letter-icon" id="grid-list-template-letter"></div>
<img class="site-icon me-2 my-auto" id="grid-list-template-icon" />
</div>
<div class="me-2 text-overflow-hide">
<div class="list-group-item-name" id="grid-list-template-title"></div>
</div>
<div class="grid-item-buttons">
<button type="button" class="btn btn-outline-primary bi-box-arrow-up-right" id="grid-list-template-launch"></button>
<button type="button" class="btn btn-outline-primary bi-pencil-square" id="grid-list-template-edit"></button>
<button type="button" class="btn btn-outline-primary bi-trash" id="grid-list-template-remove"></button>
</div>
</div>
</template>
<div class="list-group-item justify-content-between align-items-start d-flex" id="grid-list-loading">
<div>
<div><em data-i18n="commonLoading"></em></div>
</div>
</div>
<div class="list-group-item justify-content-between align-items-start d-flex d-none" id="grid-list-empty">
<div>
<div><em data-i18n="managePageAppListEmpty"></em></div>
</div>
</div>
</div>
</div>

<div class="tab-pane fade" id="sites-pane" role="tabpanel" aria-labelledby="sites-tab">
<div class="list-group list-group-flush list-group-border-last" id="sites-list">
<button type="button" class="list-group-item list-group-item-action fst-italic" id="site-install-button">
<span class="bi bi-plus-lg pe-1"></span>
Expand Down Expand Up @@ -367,8 +399,8 @@ <h5 class="offcanvas-title" id="profile-edit-label" data-i18n="managePageProfile
</div>
<div class="mb-3" id="profile-template-editing-div">
<label for="profile-template" class="form-label no-validation mb-1">
<input class="form-check-input" type="checkbox" value="" id="profile-template-editing-apply" />
<label class="form-check-label" for="profile-template-editing-apply" data-i18n="managePageProfileEditApplyProfileLabel"></label>
<input class="form-check-input" type="checkbox" value="" id="profile-template-editing-apply" />
<label class="form-check-label" for="profile-template-editing-apply" data-i18n="managePageProfileEditApplyProfileLabel"></label>
</label>
<label for="profile-template-editing" class="form-label visually-hidden" data-i18n="profileTemplate"></label>
<input type="text" class="form-control form-control-sm" id="profile-template-editing" data-i18n data-i18n-placeholder="profileTemplatePlaceholder" />
Expand Down
143 changes: 134 additions & 9 deletions extension/src/sites/manage.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,20 +75,35 @@ async function createSiteList () {

// Get the list elements
const listElement = document.getElementById('sites-list')
const gridContainer = document.getElementById('grid-list')
const templateElement = document.getElementById('sites-list-template')
const gridTemplateElement = document.getElementById('grid-list-template')
const loadingElement = document.getElementById('sites-list-loading')
const gridLoadingElement = document.getElementById('grid-list-loading')
const emptyElement = document.getElementById('sites-list-empty')
const gridEmptyElement = document.getElementById('grid-list-empty')

loadingElement.classList.add('d-none')
if (!sites.length) emptyElement.classList.remove('d-none')
gridLoadingElement.classList.add('d-none')

if (!sites.length) {
emptyElement.classList.remove('d-none')
gridEmptyElement.classList.remove('d-none')
}

// Create a list element for every instance with handlers for launching and editing
for (const site of sites) {
// Create list view item
const siteElement = templateElement.content.firstElementChild.cloneNode(true)

// Create grid view item
const gridItem = gridTemplateElement.content.firstElementChild.cloneNode(true)

const siteName = sanitizeString(site.config.name || site.manifest.name || site.manifest.short_name) || new URL(site.manifest.scope).host
const siteDescription = sanitizeString(site.config.description || site.manifest.description) || ''
const siteIcon = site.config.icon_url || getIcon(buildIconList(site.manifest.icons), 64)

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

// Setup grid view item
const gridLetterElement = gridItem.querySelector('#grid-list-template-letter')
if (siteIcon) gridLetterElement.classList.add('d-none')
gridLetterElement.setAttribute('data-letter', siteName[0])
gridLetterElement.removeAttribute('id')

const gridIconElement = gridItem.querySelector('#grid-list-template-icon')
if (!siteIcon) gridIconElement.classList.add('d-none')
gridIconElement.src = siteIcon
gridIconElement.setAttribute('alt', await getMessage('managePageAppListIcon'))
gridIconElement.removeAttribute('id')
gridIconElement.onerror = () => {
gridLetterElement.classList.remove('d-none')
gridIconElement.classList.add('d-none')
}

// Handle grid item clicks to show/hide buttons
const buttonsPopup = gridItem.querySelector('.grid-item-buttons')
gridItem.addEventListener('click', (event) => {
// Don't show popup if clicking on a button
if (event.target.closest('.grid-item-buttons')) {
return
}

// Remove active class from all other items
document.querySelectorAll('.grid-item').forEach(item => {
if (item !== gridItem) {
item.classList.remove('active')
}
})

// Toggle active class on clicked item
gridItem.classList.toggle('active')

if (gridItem.classList.contains('active')) {
// Position the popup at click coordinates
buttonsPopup.style.top = `${event.clientY}px`
buttonsPopup.style.left = `${event.clientX}px`

// Adjust position if popup would go off screen
const rect = buttonsPopup.getBoundingClientRect()
const viewportWidth = document.documentElement.clientWidth
const viewportHeight = document.documentElement.clientHeight

if (rect.right > viewportWidth) {
buttonsPopup.style.left = `${event.clientX - rect.width}px`
}
if (rect.bottom > viewportHeight) {
buttonsPopup.style.top = `${event.clientY - rect.height}px`
}
}

event.stopPropagation()
})

// Close popup when clicking outside
document.addEventListener('click', (event) => {
if (!event.target.closest('.grid-item')) {
document.querySelectorAll('.grid-item').forEach(item => {
item.classList.remove('active')
})
}
})

// Set titles and descriptions
const titleElement = siteElement.querySelector('#sites-list-template-title')
titleElement.innerText = siteName
titleElement.removeAttribute('id')
Expand All @@ -112,16 +192,32 @@ async function createSiteList () {
descriptionElement.innerText = siteDescription
descriptionElement.removeAttribute('id')

const gridTitleElement = gridItem.querySelector('#grid-list-template-title')
gridTitleElement.innerText = siteName
gridTitleElement.removeAttribute('id')

// Setup launch buttons
const launchElement = siteElement.querySelector('#sites-list-template-launch')
const gridLaunchElement = gridItem.querySelector('#grid-list-template-launch')
const launchElementTooltip = await getMessage('managePageAppListLaunch')

launchElement.addEventListener('click', () => { launchSite(site) })
gridLaunchElement.addEventListener('click', () => { launchSite(site) })

launchElement.setAttribute('title', launchElementTooltip)
launchElement.setAttribute('aria-label', launchElementTooltip)
launchElement.removeAttribute('id')

gridLaunchElement.setAttribute('title', launchElementTooltip)
gridLaunchElement.setAttribute('aria-label', launchElementTooltip)
gridLaunchElement.removeAttribute('id')

// Setup edit buttons
const editElement = siteElement.querySelector('#sites-list-template-edit')
const gridEditElement = gridItem.querySelector('#grid-list-template-edit')
const editElementTooltip = await getMessage('managePageAppListEdit')
editElement.addEventListener('click', async (event) => {

const editHandler = async (event) => {
const form = document.getElementById('web-app-form')
const submit = document.getElementById('web-app-submit')

Expand Down Expand Up @@ -379,14 +475,25 @@ async function createSiteList () {
// Show offcanvas element
Offcanvas.getOrCreateInstance(document.getElementById('site-edit-offcanvas')).show()
event.preventDefault()
})
}

editElement.addEventListener('click', editHandler)
gridEditElement.addEventListener('click', editHandler)

editElement.setAttribute('title', editElementTooltip)
editElement.setAttribute('aria-label', editElementTooltip)
editElement.removeAttribute('id')

gridEditElement.setAttribute('title', editElementTooltip)
gridEditElement.setAttribute('aria-label', editElementTooltip)
gridEditElement.removeAttribute('id')

// Setup remove buttons
const removeElement = siteElement.querySelector('#sites-list-template-remove')
const gridRemoveElement = gridItem.querySelector('#grid-list-template-remove')
const removeElementTooltip = await getMessage('managePageAppListRemove')
removeElement.addEventListener('click', () => {

const removeHandler = () => {
const lastSiteInProfile = profiles[site.profile].sites.length <= 1

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

Modal.getOrCreateInstance(document.getElementById('site-remove-modal')).show()
})
}

removeElement.addEventListener('click', removeHandler)
gridRemoveElement.addEventListener('click', removeHandler)

removeElement.setAttribute('title', removeElementTooltip)
removeElement.setAttribute('aria-label', removeElementTooltip)
removeElement.removeAttribute('id')

gridRemoveElement.setAttribute('title', removeElementTooltip)
gridRemoveElement.setAttribute('aria-label', removeElementTooltip)
gridRemoveElement.removeAttribute('id')

listElement.insertBefore(siteElement, templateElement)
gridContainer.insertBefore(gridItem, gridTemplateElement)
}
}

Expand Down Expand Up @@ -689,29 +805,38 @@ async function createProfileList () {

// Handle site and profile search
async function handleSearch () {
const searchHandler = function (listElement) {
const searchHandler = function (listElement, gridElement) {
document.getElementById('search-box').classList.remove('invisible')

document.getElementById('search-input').oninput = function () {
const searchQuery = sanitizeString(this.value.toLowerCase())

for (const item of document.getElementById(listElement).children) {
const itemName = sanitizeString(item.querySelector('.list-group-item-name')?.innerText.toLowerCase())
const searchQuery = sanitizeString(this.value.toLowerCase())

if (!itemName) continue
item.classList.toggle('d-none', itemName.indexOf(searchQuery) === -1)
}

if (gridElement) {
for (const item of document.getElementById(gridElement).children) {
const itemName = sanitizeString(item.querySelector('.list-group-item-name')?.innerText.toLowerCase())
if (!itemName) continue
item.classList.toggle('d-none', itemName.indexOf(searchQuery) === -1)
}
}
}
}

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

document.getElementById('grid-tab').addEventListener('click', () => searchHandler('grid-list'))
document.getElementById('sites-tab').addEventListener('click', () => searchHandler('sites-list'))
document.getElementById('profiles-tab').addEventListener('click', () => searchHandler('profiles-list'))
document.getElementById('settings-tab').addEventListener('click', () => searchHide())

searchHandler('sites-list')
searchHandler('grid-list')
}

// Handle extension settings
Expand Down
Loading

0 comments on commit f522eba

Please sign in to comment.