unclean SPARC

This commit is contained in:
STEINNI
2025-08-27 07:03:09 +00:00
commit f308460931
430 changed files with 54426 additions and 0 deletions
@@ -0,0 +1,60 @@
<style>
.path-browser section{
height: 35vh !important;
width: 400px;
}
.path-browser .ffsDialog-folders-list .cell i.ffs.icon-folder{
color: var(--eicui-base-color-warning);
margin-right: .5em;
}
.path-browser .ffsDialog-folders-list footer{ display:none; }
.path-browser div.current-path{ height: calc(var(--eicui-base-font-size-2xs)*2); }
.ffsDialog-hidden {
display: none !important;
}
.ffsDialog-folders-manager .ffsDialog-folder-controls {
display: flex;
align-items: center;
gap: 0.5em;
margin-bottom: 1em;
}
.ffsDialog-folder-controls input {
flex: 1;
}
.ffsConfirmBox {
display: flex;
gap: 0.5em;
align-items: center;
font-size: 0.85em;
color: var(--eicui-base-color-danger);
}
</style>
<article eiccard class="path-browser">
<header>
<label>Current path:</label>
<div class="current-path"></div>
</header>
<section class="ffsDialog-folders-browser">
<div class="ffsDialog-folders-list"></div>
</section>
<section class="ffsDialog-folders-manager ffsDialog-hidden">
<div class="ffsDialog-folder-controls">
<input type="text" class="ffsDialog-folder-name eicui-input" placeholder="New folder name" />
<button eicbutton xsmall rounded primary class="create-folder">Create</button>
</div>
<ul class="ffsDialog-folder-list" style="list-style:none;padding:0;"></ul>
</section>
<footer style="margin-top:1em;">
<button eicbutton xsmall rounded primary class="ffsDialog-btn-manage">Manage Folders</button>
<button eicbutton xsmall rounded primary btn-back class="ffsDialog-btn-back ffsDialog-hidden" icon="icon-back"><i class="icon-back">Back</i></button>
</footer>
</article>
@@ -0,0 +1,292 @@
class FileBrowserDialog extends EICDialogContent {
actions = [
{ label: 'Cancel', class: 'FBDCancelBtn', onclick: this.cancel.bind(this), severity: 'secondary', role: 'cancel' },
{ label: 'Select path', onclick: this.add.bind(this), severity: 'primary', role: 'add', class: 'ffs-selectPath', disabled: false }
]
constructor(...args) {
super(...args)
Object.assign(this, app.helpers.validators)
this.dirSimpleClickerTO = null // Timeout for simple directory click
}
DOMContentLoaded(options) {
this.ffs = options.ffs
this.editable = options.editable
this.canManage = options.canManage
this.currentPath = options.startPath
this.templates = options.templates
this.mailings = options.mailings
// Checks if a path has any subfolders or files
this.ffs.hasChildren = (path) => {
const hasFolder = this.ffs.getFolders().some(f => f.path.startsWith(path + '/') && f.path !== path)
const hasFile = this.ffs.getFiles().some(file => file.fullPath.startsWith(path + '/'))
return hasFolder || hasFile
}
// Cache the select path button after DOM has been updated
setTimeout(() => {
this.selectPathButton = this.find('.ffs-selectPath')
}, 0)
this._cacheDOMElements()
this._attachEventListeners()
// Initialize with the starting directory
this.ffs.changeDir(this.currentPath)
this.foldersGrid = new DataGrid(this.find('.ffsDialog-folders-list'), {
headers: [{ label: 'Folders', type: 'markup', filter: 'text', sortable: true }],
actions: []
})
this.fillFoldersGrid()
this.validateInput()
if (!this.canManage) {
this.find('.ffsDialog-btn-manage').style.display = 'none'
}
}
_cacheDOMElements() {
this.browserSection = this.find('.ffsDialog-folders-browser')
this.managerSection = this.find('.ffsDialog-folders-manager')
this.folderInput = this.find('.ffsDialog-folder-name')
this.folderList = this.find('.ffsDialog-folder-list')
this.createBtn = this.find('.create-folder')
}
_attachEventListeners() {
this.find('.ffsDialog-btn-manage').addEventListener('click', () => this.showManager())
this.find('.ffsDialog-btn-back').addEventListener('click', () => this.showBrowser())
this.createBtn.addEventListener('click', () => this.createFolder())
this.folderInput.addEventListener('input', () => this.validateInput())
}
// Fill the folder grid with accessible folders
fillFoldersGrid() {
this.foldersGrid.clear()
this.showPath(this.ffs.currentPath)
const authorized = this.ffs.authorizedPaths || []
const rights = this.ffs.rights || []
// Search for rights related to the current path
const currentRights = rights.find(r => r.path === this.ffs.currentPath)
const canManageCurrent = currentRights && (currentRights.create || currentRights.delete)
const manageBtn = this.find('.ffsDialog-btn-manage')
if (manageBtn) {
manageBtn.style.display = canManageCurrent ? '' : 'none'
}
for (let folder of this.ffs.getFolders()) {
let fullPath = folder.path
if (!fullPath.startsWith('/')) {
fullPath = '/' + fullPath
}
const isAuthorized = authorized.includes(fullPath)
const isParentOfAuthorized = authorized.some(authPath => authPath.startsWith(fullPath + '/'))
if (folder.name === '..') {
const parentPath = fullPath
const parentRights = rights.find(r => r.path === parentPath)
if (parentPath === '/') {
if (!parentRights || !parentRights.enter) continue
}
}
if (
folder.name === '..' ||
isAuthorized ||
isParentOfAuthorized
) {
const displayName = folder.name === '..' ? ' <span style="font-style: italic;">go back up</span>' : folder.name
const icon = folder.name === '..' ? 'icon-back' : 'icon-folder'
const row = this.foldersGrid.addRow(folder.name, [`<i class="ffs ${icon}"></i>${displayName}`])
row.dataset.type = 'folder'
row.addEventListener('click', () => this.onEnterDir(folder))
}
}
}
// Display the current path as a breadcrumb
showPath(path) {
this.currentPath = path
const dirs = path.split('/').filter(Boolean)
const chips = dirs.map((dir, idx) => {
const subPath = '/' + dirs.slice(0, idx + 1).join('/')
return `<span eicchip xxsmall info data-path="${subPath}">${dir}</span>`
}).join('')
this.find('.current-path').innerHTML = chips
}
onEnterDir(folder) {
clearTimeout(this.dirSimpleClickerTO)
this.ffs.changeDir(folder.path)
this.fillFoldersGrid()
}
add() {
this.commit(this.currentPath)
}
// Show the folder management section (create/delete)
showManager() {
this.browserSection.classList.add('ffsDialog-hidden')
this.managerSection.classList.remove('ffsDialog-hidden')
this.find('.ffsDialog-btn-manage').classList.add('ffsDialog-hidden')
this.find('.ffsDialog-btn-back').classList.remove('ffsDialog-hidden')
if (this.selectPathButton) this.selectPathButton.style.display = 'none'
this.manageFolders()
}
showBrowser() {
this.browserSection.classList.remove('ffsDialog-hidden')
this.managerSection.classList.add('ffsDialog-hidden')
this.find('.ffsDialog-btn-manage').classList.remove('ffsDialog-hidden')
this.find('.ffsDialog-btn-back').classList.add('ffsDialog-hidden')
if (this.selectPathButton) this.selectPathButton.style.display = ''
this.fillFoldersGrid()
}
// Validate the folder name input
validateInput() {
const value = this.folderInput.value.trim()
const invalidChars = /[\/\\:\*\?"<>|]/
const errorText = invalidChars.test(value) ? 'Folder name contains invalid characters.' : ''
this.createBtn.disabled = !!errorText
let errorElem = this.find('.folder-error')
if (!errorElem) {
errorElem = document.createElement('div')
errorElem.className = 'folder-error'
errorElem.style.color = 'red'
errorElem.style.fontSize = '0.8em'
errorElem.style.marginTop = '0.3em'
this.folderInput.insertAdjacentElement('afterend', errorElem)
}
errorElem.textContent = errorText
}
// Create a folder in the current path
async createFolder() {
const dirName = this.folderInput.value.trim()
if (!dirName) return
const originalHTML = this.createBtn.innerHTML
this.createBtn.innerHTML = `<i class="icon-spinner spin"></i>`
this.createBtn.disabled = true
try {
if (this.templates) {
await this.templates.makeDir(this.currentPath, dirName)
}
if (this.mailings) {
await this.mailings.makeDir(this.currentPath, dirName)
}
ui.growl.append(`Folder "${dirName}" created successfully!`, 'success')
this.folderInput.value = ''
this.validateInput()
this.createBtn.innerHTML = originalHTML
this.createBtn.disabled = false
const cancelBtn = Array.from(document.querySelectorAll('button')).find(btn => btn.textContent.trim() === 'Cancel')
cancelBtn?.click()
} catch (e) {
console.error(e)
ui.growl.append(`Error creating folder "${dirName}"`, 'error')
}
}
async deleteFolder(path, name, yesBtn, noBtn) {
const originalText = yesBtn.innerHTML
yesBtn.innerHTML = `<i class="icon-spinner spin"></i>`
yesBtn.disabled = true
noBtn.disabled = true
try {
if (this.templates) {
await this.templates.removeDir(1, path)
}
if (this.mailings) {
await this.mailings.removeDir(2, path)
}
ui.growl.append(`Folder "${name}" deleted!`, 'success')
const cancelBtn = Array.from(document.querySelectorAll('button')).find(btn => btn.textContent.trim() === 'Cancel')
cancelBtn?.click()
} catch (err) {
console.error(err)
yesBtn.innerHTML = originalText
yesBtn.disabled = false
noBtn.disabled = false
ui.growl.append(`Error deleting folder "${name}"`, 'error')
}
}
// Display the list of folders in the management section, with delete buttons if allowed
manageFolders() {
this.folderList.innerHTML = ''
const folders = this.ffs.getFolders().filter(f => f.name !== '..')
const authorizedPaths = this.ffs.authorizedPaths || []
const pathKeys = Object.keys(this.ffs.path2dir)
const foldersWithFiles = this.ffs.foldersWithFiles || []
for (const f of folders) {
const hasFolderChild = pathKeys.some(path => path !== f.path && path.startsWith(f.path + '/'))
const hasFileChild = foldersWithFiles.some(path => path === f.path || path.startsWith(f.path + '/'))
const isAuthorized = authorizedPaths.includes(f.path)
const canDelete = !hasFolderChild && !hasFileChild && isAuthorized
const li = document.createElement('li')
li.innerHTML = `
<i class="ffs icon-folder" style="margin-right: 0.5em"></i>
<span>${f.name}</span>
${canDelete ? `
<button class="ffsDelBtn" eicbutton xsmall rounded danger style="margin-left:1em">
<i class="icon-bin"></i>
</button>
<div class="ffsConfirmBox ffsDialog-hidden" style="margin-top: 0.5em; display: flex; gap: 0.5em; align-items: center;">
<span>Confirm deletion?</span>
<button class="ffsConfirmYes" eicbutton xsmall rounded danger data-path="${f.path}" data-name="${f.name}">Yes</button>
<button class="ffsConfirmNo" eicbutton xsmall rounded secondary>No</button>
</div>` : ''
}
`
this.folderList.appendChild(li)
if (canDelete) {
const delBtn = li.querySelector('.ffsDelBtn')
const confirmBox = li.querySelector('.ffsConfirmBox')
const yesBtn = li.querySelector('.ffsConfirmYes')
const noBtn = li.querySelector('.ffsConfirmNo')
delBtn.addEventListener('click', () => {
delBtn.style.display = 'none'
confirmBox.classList.remove('ffsDialog-hidden')
})
noBtn.addEventListener('click', () => {
confirmBox.classList.add('ffsDialog-hidden')
delBtn.style.display = ''
})
yesBtn.addEventListener('click', () => {
const path = yesBtn.getAttribute('data-path')
const name = yesBtn.getAttribute('data-name')
this.deleteFolder(path, name, yesBtn, noBtn)
})
}
}
}
}
app.registerClass('FileBrowserDialog', FileBrowserDialog)