Merge pull request #1116 from ogmaresca/createTab-utility-function

Add a utility function to create tabs with lazy loading functionality
This commit is contained in:
cmdr2 2023-04-26 16:21:51 +05:30 committed by GitHub
commit 382dee1fd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 382 additions and 286 deletions

View File

@ -720,9 +720,140 @@ Array.prototype.addEventListener = function(method, callback) {
const originalFunction = this[method] const originalFunction = this[method]
if (originalFunction) { if (originalFunction) {
this[method] = function() { this[method] = function() {
console.log(`Array.${method}()`, arguments)
originalFunction.apply(this, arguments) originalFunction.apply(this, arguments)
callback.apply(this, arguments) callback.apply(this, arguments)
} }
} }
} }
/**
* @typedef {object} TabOpenDetails
* @property {HTMLElement} contentElement
* @property {HTMLElement} labelElement
* @property {number} timesOpened
* @property {boolean} firstOpen
*/
/**
* @typedef {object} CreateTabRequest
* @property {string} id
* @property {string | Node | (() => (string | Node))} label
* Label text or an HTML element
* @property {string} icon
* @property {string | Node | Promise<string | Node> | (() => (string | Node | Promise<string | Node>)) | undefined} content
* HTML string or HTML element
* @property {((TabOpenDetails, Event) => (undefined | string | Node | Promise<string | Node>)) | undefined} onOpen
* If an HTML string or HTML element is returned, then that will replace the tab content
* @property {string | undefined} css
*/
/**
* @param {CreateTabRequest} request
*/
function createTab(request) {
if (!request?.id) {
console.error('createTab() error - id is required', Error().stack)
return
}
if (!request.label) {
console.error('createTab() error - label is required', Error().stack)
return
}
if (!request.icon) {
console.error('createTab() error - icon is required', Error().stack)
return
}
if (!request.content && !request.onOpen) {
console.error('createTab() error - content or onOpen required', Error().stack)
return
}
const tabsContainer = document.querySelector('.tab-container')
if (!tabsContainer) {
return
}
const tabsContentWrapper = document.querySelector('#tab-content-wrapper')
if (!tabsContentWrapper) {
return
}
console.debug('creating tab: ', request)
if (request.css) {
document.querySelector('body').insertAdjacentElement(
'beforeend',
createElement('style', { id: `tab-${request.id}-css` }, undefined, request.css),
)
}
const label = typeof request.label === 'function' ? request.label() : request.label
const labelElement = label instanceof Node ? label : createElement('span', undefined, undefined, label)
const tab = createElement(
'span',
{ id: `tab-${request.id}`, 'data-times-opened': 0 },
['tab'],
createElement(
'span',
undefined,
undefined,
[
createElement(
'i',
{ style: 'margin-right: 0.25em' },
['fa-solid', `${request.icon.startsWith('fa-') ? '' : 'fa-'}${request.icon}`, 'icon'],
),
labelElement,
],
)
)
tabsContainer.insertAdjacentElement('beforeend', tab)
const wrapper = createElement('div', { id: request.id }, ['tab-content-inner'], 'Loading..')
const tabContent = createElement('div', { id: `tab-content-${request.id}` }, ['tab-content'], wrapper)
tabsContentWrapper.insertAdjacentElement('beforeend', tabContent)
linkTabContents(tab)
function replaceContent(resultFactory) {
if (resultFactory === undefined || resultFactory === null) {
return
}
const result = typeof resultFactory === 'function' ? resultFactory() : resultFactory
if (result instanceof Promise) {
result.then(replaceContent)
} else if (result instanceof Node) {
wrapper.replaceChildren(result)
} else {
wrapper.innerHTML = result
}
}
replaceContent(request.content)
tab.addEventListener('click', (e) => {
const timesOpened = +(tab.dataset.timesOpened || 0) + 1
tab.dataset.timesOpened = timesOpened
if (request.onOpen) {
const result = request.onOpen(
{
contentElement: wrapper,
labelElement,
timesOpened,
firstOpen: timesOpened === 1,
},
e,
)
replaceContent(result)
}
})
}

View File

@ -130,34 +130,11 @@
} }
drawDiagram(fn) drawDiagram(fn)
} }
createTab({
/////////////////////// Tab implementation id: 'merge',
document.querySelector('.tab-container')?.insertAdjacentHTML('beforeend', ` icon: 'fa-code-merge',
<span id="tab-merge" class="tab"> label: 'Merge models',
<span><i class="fa fa-code-merge icon"></i> Merge models</span> css: `
</span>
`)
document.querySelector('#tab-content-wrapper')?.insertAdjacentHTML('beforeend', `
<div id="tab-content-merge" class="tab-content">
<div id="merge" class="tab-content-inner">
Loading..
</div>
</div>
`)
const tabMerge = document.querySelector('#tab-merge')
if (tabMerge) {
linkTabContents(tabMerge)
}
const merge = document.querySelector('#merge')
if (!merge) {
// merge tab not found, dont exec plugin code.
return
}
document.querySelector('body').insertAdjacentHTML('beforeend', `
<style>
#tab-content-merge .tab-content-inner { #tab-content-merge .tab-content-inner {
max-width: 100%; max-width: 100%;
padding: 10pt; padding: 10pt;
@ -233,11 +210,8 @@
} }
.merge-container #merge-warning { .merge-container #merge-warning {
color: rgb(153, 153, 153); color: rgb(153, 153, 153);
} }`,
</style> content: `
`)
merge.innerHTML = `
<div class="merge-container panel-box"> <div class="merge-container panel-box">
<div class="merge-input"> <div class="merge-input">
<p><label for="#mergeModelA">Select Model A:</label></p> <p><label for="#mergeModelA">Select Model A:</label></p>
@ -327,7 +301,11 @@
<div class="merge-buttons"> <div class="merge-buttons">
<button id="merge-button" class="primaryButton">Merge models</button> <button id="merge-button" class="primaryButton">Merge models</button>
</div> </div>
</div>` </div>`,
onOpen: ({ firstOpen }) => {
if (!firstOpen) {
return
}
const tabSettingsSingle = document.querySelector('#tab-merge-opts-single') const tabSettingsSingle = document.querySelector('#tab-merge-opts-single')
const tabSettingsBatch = document.querySelector('#tab-merge-opts-batch') const tabSettingsBatch = document.querySelector('#tab-merge-opts-batch')
@ -454,5 +432,7 @@
hypernetworkModelField.innerHTML = '' hypernetworkModelField.innerHTML = ''
await getModels() await getModels()
}) })
},
})
})() })()

View File

@ -9,41 +9,21 @@
} }
} }
document.querySelector('.tab-container')?.insertAdjacentHTML('beforeend', ` createTab({
<span id="tab-news" class="tab"> id: 'news',
<span><i class="fa fa-bolt icon"></i> What's new?</span> icon: 'fa-bolt',
</span> label: "What's new",
`) css: `
document.querySelector('#tab-content-wrapper')?.insertAdjacentHTML('beforeend', `
<div id="tab-content-news" class="tab-content">
<div id="news" class="tab-content-inner">
Loading..
</div>
</div>
`)
const tabNews = document.querySelector('#tab-news')
if (tabNews) {
linkTabContents(tabNews)
}
const news = document.querySelector('#news')
if (!news) {
// news tab not found, dont exec plugin code.
return
}
document.querySelector('body').insertAdjacentHTML('beforeend', `
<style>
#tab-content-news .tab-content-inner { #tab-content-news .tab-content-inner {
max-width: 100%; max-width: 100%;
text-align: left; text-align: left;
padding: 10pt; padding: 10pt;
} }
</style> `,
`) onOpen: async ({ firstOpen }) => {
if (firstOpen) {
const loadMarkedScriptPromise = loadScript('/media/js/marked.min.js')
loadScript('/media/js/marked.min.js').then(async function() {
let appConfig = await fetch('/get/app_config') let appConfig = await fetch('/get/app_config')
if (!appConfig.ok) { if (!appConfig.ok) {
console.error('[release-notes] Failed to get app_config.') console.error('[release-notes] Failed to get app_config.')
@ -59,6 +39,11 @@
return return
} }
releaseNotes = await releaseNotes.text() releaseNotes = await releaseNotes.text()
news.innerHTML = marked.parse(releaseNotes)
await loadMarkedScriptPromise
return marked.parse(releaseNotes)
}
},
}) })
})() })()