unclean SPARC
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user