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
+8
View File
@@ -0,0 +1,8 @@
<header eicapptoolbar>
<button eicbutton icon="icon-menu" xxxlarge rounded class="app-menu" role="button" aria-label="Button" aria-enabled="true"></button>
<a class="eu-logo" href="/" title="EIC Home"></a>
<h1>European Innovation Council</h1>
</header>
<menu eicmenu lookup class="app-menu"></menu>
<div class="app-workspace"></div>
<div class="app-content-thesaurus"></div>
+122
View File
@@ -0,0 +1,122 @@
/**
* Application main view. Acts as a container and manager for regular views
*
* @category MyEic
* @subcategory Views
*
* @extends EICDialogContent
*/
class EICAppTemplate extends EICDomContent {
DOMContentLoaded() {
ui.init({ariaEnabled: true});
ui.eicfy(this.el);
window.addEventListener('resize', this.onGlobalResize.bind(this), true);
this.menu = new Menu(this.find('menu.app-menu'));
// todo : replace with something more dynamic
this.loadAppMenu('global/app-menu-map.json');
}
onGlobalResize() { EICController.resize(); }
loadAppMenu(source) {
app.Assets.loadJson({ name: source})
.then(this.setupAppMenu.bind(this));
}
setupAppMenu(data) {
let menu = [];
for(let section of data) {
let apps = section.items.filter(item => {
if(item.access.includes("*")) return(true)
let intersect = app.User.roles.filter(r => (item.access.includes(r)) || (item.access.includes("*")) )
return(intersect.length > 0)
});
if(apps.length > 0) {
let entry = Object.assign({}, section);
entry.items = apps;
menu.push(entry)
}
}
this.menu.clear();
this.menu.parse(menu);
const button = new Button(this.find('button.app-menu'));
button.click = this.toggleAppMenu.bind(this);
this.menu.collapsed = app.User.getPreference('global.menu.collapsed');
}
/**
* Enables/disables collapsable app menu
* @param {*} event
*/
toggleAppMenu() {
this.menu.collapsed = ! this.menu.collapsed ;
app.User.setPreference('global.menu.collapsed', this.menu.collapsed);
}
initSession() {
this.session = ui.create(`<div class="eic-session connected">
<div eicdropdown>
<button eicuser><span></span></button>
<menu eicmenu></menu>
</div>
</div>`);
this.userMenu = new DropDown(this.session.querySelector('[eicdropdown]'));
this.userMenu.menu.parse([
{
label: "My profile",
icon: "icon-user",
severity: 'success',
onclick: this.onProfile.bind(this)
},
{
label: "Log out",
icon: "icon-logoff",
severity: 'danger',
onclick: this.onLogout.bind(this)
}
]);
this.find('header').appendChild(this.session);
this.userIcon = new UserIcon(this.session.querySelector('[eicuser]'));
this.userIcon.showStatus = true;
this.userIcon.uuid = app.User.identity.uuid;
this.userIcon.fullname = app.User.identity.firstname + ' ' + app.User.identity.lastname
this.userIcon.online = app.MessageBus.connected;
app.events.channel.addEventListener('MessageBus.Connected', this.onBusConnected.bind(this))
app.events.channel.addEventListener('MessageBus.Closed', this.onBusClosed.bind(this))
return this.session;
}
async onLogout(event) {
event.stopPropagation(); event.preventDefault();
this.userMenu.collapse();
app.User.logout();
}
onProfile(event) {
event.stopPropagation();
event.preventDefault();
this.userMenu.collapse();
app.Router.route('/common/myprofile');
}
onBusConnected() { this.userIcon.online = true; }
onBusClosed() { this.userIcon.online = false; }
}
app.registerClass('EICAppTemplate', EICAppTemplate);
@@ -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)
@@ -0,0 +1,8 @@
<div class="confirm-dialog">
<section>
<alert eicalert ${severity} ${muted}>
${message}
</alert>
</section>
</div>
@@ -0,0 +1,69 @@
/**
* @category MyEic
* @subcategory Views
*
* @extends EICDialogContent
*/
class ConfirmDialog extends EICDialogContent {
actions = [
{
label: 'Cancel',
onclick: this.cancel.bind(this),
severity: 'secondary',
role: 'cancel'
}
]
constructor(options) {
super(options);
this.options = options;
if(this.options.cancelLabel) this.actions[0].label = this.options.cancelLabel
if(this.options.cancelSeverity) this.actions[0].severity = this.options.cancelSeverity
if(this.options.okLabel != '') {
this.actions.push({
label: this.options.okLabel || 'OK',
onclick: this.comply.bind(this),
severity: options.okSeverity||options.severity,
role: 'comply'
});
}
}
DOMContentLoaded() {
ui.eicfy(this.el);
if(typeof(this.options.contentLoaded)=='function') this.options.contentLoaded(this.el)
if(this.options.promptsClass) this.form = new Form(this.find('.'+this.options.promptsClass))
}
comply() {
let data = true
if(this.options.promptsClass){
if(!this.form.validate()) return
data = this.form.value
}
this.actions.find(o => o.role == 'comply').button.loading = true;
this.actions.find(o => o.role == 'cancel').button.disabled = true;
if(this.options.okPromise){
this.options.okPromise(data).then( this.onSuccess.bind(this,data), this.onFailed.bind(this) )
} else {
this.onSuccess(data)
}
}
onSuccess(data) {
this.actions.find(o => o.role == 'comply').button.loading = false;
this.actions.find(o => o.role == 'cancel').button.disabled = false;
this.commit(data);
}
onFailed() {
this.actions.find(o => o.role == 'comply').button.loading = false;
this.actions.find(o => o.role == 'cancel').button.disabled = false;
this.abort();
}
}
app.registerClass('ConfirmDialog',ConfirmDialog);