Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Added] extra grid view tab #626

Merged
merged 5 commits into from
Feb 3, 2025
Merged
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
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