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,410 @@
/**
* Short Proposal Form
*
* @version 2023
* @author Michael Fallise
*
* Options:
* * modeAc
*
*/
class SubmissionShortForm2023View extends EICDomContent {
options = {
mode: 'read',
autoSaveInterval: 15 * 1000,
validationInterval: 2 * 1000,
}
chunks = [];
DOMContentLoaded(options) {
this.components = ui.eicfy(this.el)
for(let model in options.models) this[model] = options.models[model];
this.tabs = new Tab();
this.tabs.addTabs(this.findAll('.tabs-extended menu li'), this.findAll('.content'))
this.form = new Form(this.find('.form'));
this.pic = options.profile.pic;
this.number = options.profile.number;
this.members.list(this.pic,this.number).then(this.fillContributors.bind(this))
this.mode = options.profile.mode || this.options.mode;
this.checkConcurrency(options.profile.mode || this.options.mode);
}
async checkConcurrency(mode) {
if(mode == 'edit') {
let diff = (new Date()).getTime() - (new Date(this.submission.data.updated.date)).getTime()
let created = (new Date()).getTime() - (new Date(this.submission.data.created.date)).getTime()
if(diff < this.options.autoSaveInterval && created > this.options.autoSaveInterval) {
// someone is editing right now
let goReadOnly = await this.openDialog(
await this.loadContent(
'projects/submissions/dialogs/SubmissionFormConcurrencyDialog',
{ title: 'Someone is already at work' }, {
contributor: this.submission.data.updated.contributor
}
)
);
mode = 'read';
if(!goReadOnly) {
this.mode = 'edit';
this.unload();
}
}
}
this.fill(mode);
}
DOMContentRemoved() {
if(!this.loaded) return;
if(this.autoSave) {
clearInterval(this.autoSave)
this.autoSave = null;
};
if(this.validationCheck) {
clearInterval(this.validationCheck);
this.validationCheck = null;
}
if((this.mode == 'edit')) this.save();
for(let chunk of this.chunks) { chunk.view.DOMContentRemoved(); }
}
DOMContentBlured() {
if(!this.loaded) return;
if(this.autoSave) {
clearInterval(this.autoSave);
this.autoSave = null;
}
if(this.validationCheck) {
clearInterval(this.validationCheck);
this.validationCheck = null;
}
}
DOMContentFocused(options) {
if(!this.loaded) return;
//By Nike : if options, means rerouted, then new models, then switch to fresh models !
if(options) {
for(let model in options.models) this[model] = options.models[model];
}
//By Nike: propagate DOMContentFocused to tabs. Caution before setupMode which calls fill, which must have fresh model.data
for(let chunk of this.chunks) chunk.view.DOMContentFocused(options)
if(
options &&
options.profile &&
options.profile.mode &&
options.profile.mode != this.mode
) this.setupMode(options.profile.mode);
if(!this.autoSave && this.mode == 'edit') {
this.autoSave = setInterval(this.save.bind(this), this.options.autoSaveInterval);
}
if(!this.validationCheck && this.mode == 'edit') {
this.validationCheck = setInterval(this.validate.bind(this), this.options.validationCheck);
}
}
fill(mode) {
let myTabsPath = 'projects/submissions/2023/tabs/'
this.find('.tools .created .date').innerText = (new Date(this.submission.data.created.date)).toLocaleString("en-DE");
this.find('.tools .created .contributor').innerText = `${this.submission.data.created.contributor.firstname} ${this.submission.data.created.contributor.lastname}`;
this.refreshUpdated(this.submission.data.updated);
let queue = [
this.loadContent( myTabsPath+'SubmissionShortForm2023GeneralView', {}, { model: this.submission } )
.then( view => this.appendTab('general', view)),
this.loadContent( myTabsPath+'SubmissionShortForm2023CompanyView', {}, { model: this.submission } )
.then( view => this.appendTab('company', view)),
this.loadContent( myTabsPath+'SubmissionShortForm2023TeamView', {}, { model: this.submission, team: this.team, pic: this.pic, number: this.number } )
.then( view => this.appendTab('team', view)),
this.loadContent( myTabsPath+'SubmissionShortForm2023ProblemView', {}, { model: this.submission } )
.then( view => this.appendTab('problem', view)),
this.loadContent( myTabsPath+'SubmissionShortForm2023SolutionView', {}, { model: this.submission } )
.then( view => this.appendTab('solution', view)),
this.loadContent( myTabsPath+'SubmissionShortForm2023CompetitionView', {}, { model: this.submission } )
.then( view => this.appendTab('competition', view)),
this.loadContent( myTabsPath+'SubmissionShortForm2023FundingView', {}, { model: this.submission } )
.then( view => this.appendTab('funding', view)),
this.loadContent( myTabsPath+'SubmissionShortForm2023ImpactView', {}, { model: this.submission } )
.then( view => this.appendTab('impacts', view)),
this.loadContent( myTabsPath+'SubmissionShortForm2023DocumentsView', {}, { model: this.submission } )
.then( view => {
this.appendTab('documents', view);
view.el.addEventListener('save', this.save.bind(this) )
}),
this.loadContent( myTabsPath+'SubmissionShortForm2023ConsentView', {}, { model: this.submission, team: this.team, pic: this.pic, number: this.number } )
.then( view => this.appendTab('consent', view))
];
if(['draft', 'frozen'].includes(this.submission.data.status)) {
queue.push(
this.loadContent( myTabsPath+'SubmissionShortForm2023DisclaimerView', {}, { model: this.submission } )
.then( view => {
this.appendTab('status', view);
this.statusTab = view;
view.el.addEventListener('submit', this.submit.bind(this));
})
)
} else {
queue.push(
this.loadContent( myTabsPath+'SubmissionShortForm2023StatusView', {}, { model: this.submission } )
.then( view => {
this.appendTab('status', view);
this.statusTab = view;
view.el.addEventListener('withdraw', this.withdraw.bind(this));
view.el.addEventListener('clone', this.clone.bind(this));
view.el.addEventListener('complain', this.complain.bind(this));
})
)
}
Promise.allSettled(queue)
.then((results) => {
this.setupMode(mode);
this.loaded = true;
});
}
setupMode(mode) {
this.mode = mode;
for(let chunk of this.chunks) {
chunk.view.options.mode = mode;
chunk.view.fill(mode);
}
if(this.mode == 'read' && this.autoSave) {
clearInterval(this.autoSave);
this.autoSave = null
}
if(this.mode == 'read' && this.validationCheck) {
clearInterval(this.validationCheck);
this.validationCheck = null
}
if(this.mode == 'edit' && !this.autoSave) {
this.autoSave = setInterval(this.save.bind(this), this.options.autoSaveInterval);
this.save(); // perform a save on startup to mark editing start
}
if(this.mode == 'edit' && !this.validationCheck) {
this.validationCheck = setInterval(this.validate.bind(this), this.options.validationInterval);
}
if(!['draft', 'frozen'].includes(this.submission.data.status)) {
this.tabs.selectByIndex(this.tabs.triggers.length - 2);
}
}
appendTab(target, view) {
let chunk = {target: target, view: view};
this.chunks.push( chunk );
this.find(`.content[data-part="${target}"]`).append(view.el);
return chunk;
}
fillContributors(data) {
let metrics = { contributors: 0, pending: 0 }
this.find('.contributors').innerHTML = '';
for(let item of data) {
if(item.status == 'active') metrics.contributors++;
if(item.status == 'pending') metrics.pending++;
}
this.find('.contributors').innerHTML = '';
if(metrics.contributors > 0) {
let chip = new Chip(null, {label: 'contributors', severity: 'primary', badge: metrics.contributors});
chip.el.addEventListener('click', this.onContributorsEdit.bind(this))
this.find('.contributors').append(chip.el);
}
if(metrics.pending > 0) {
let chip = new Chip(null, {label: 'pending requests', severity: 'warning', badge: metrics.pending})
chip.el.addEventListener('click', this.onContributorsEdit.bind(this))
this.find('.contributors').append(chip.el);
}
}
async onContributorsEdit(event) {
event.stopPropagation();
event.preventDefault();
await this.openDialog(
await this.loadContent(
'projects/submissions/dialogs/SubmissionFormAccessDialog',
{ title: 'Contributors to this proposal' }, {
pic: this.pic,
number: this.number,
models: {
proposal: this.members,
organisation: new ApplicantMembersModel(['list'])
}
}
)
);
this.members.list(this.pic,this.number)
.then(this.fillContributors.bind(this));
}
validate() {
let valid = true;
for(let chunk of this.chunks) {
let report = chunk.view.validate(true);
let badge = this.components.find(item => item.el.hasAttribute('eicbadge') && item.el.parentElement.dataset.target == chunk.target)
badge.value = report.issues;
valid = valid && report.valid;
}
this.statusTab.switchSubmit(!valid);
return valid;
}
refreshUpdated(data) {
this.find('.tools .updated .date').innerText = (new Date(data.date)).toLocaleString("en-DE");
this.find('.tools .updated .contributor').innerText = `${data.contributor.firstname} ${data.contributor.lastname}`;
}
save() {
this.validate();
this.submission.save(this.pic, this.number, this.form.value)
.then(
async payload => {
this.refreshUpdated(payload.updated);
this.dispatchUpdate();
},
async error => {
if(error.code == 504) {
ui.growl('Auto save encountered some issue.', 'danger');
this.setupMode('read');
} else {
ui.growl.append(error.displayMessage, 'danger');
}
}
);
}
async submit(event) {
if(this.validate()) {
if(this.autoSave) {
clearInterval(this.autoSave)
this.autoSave = null;
};
if(this.validationCheck) {
clearInterval(this.validationCheck);
this.validationCheck = null;
}
this.submission.submit(this.pic, this.number, this.form.value)
.then(async payload => {
this.dispatchUpdate();
this.mode = 'read'; // exiting edit mode, avoiding save on close
ui.growl.append('Your proposal has been successfully submitted', 'success');
this.unload();
},
() => {
if(!this.autoSave && this.mode == 'edit') {
this.autoSave = setInterval(this.save.bind(this), this.options.autoSaveInterval);
}
if(!this.validationCheck && this.mode == 'edit') {
this.validationCheck = setInterval(this.validate.bind(this), this.options.validationCheck);
}
});
}
}
async withdraw(event) {
this.submission.withdraw(this.pic, this.number, this.form.value)
.then(async payload => {
this.dispatchUpdate();
ui.growl.append('Your proposal has been successfully withdrawn', 'success');
this.unload();
});
}
async clone(event) {
this.submission.clone(this.pic, this.number)
.then(
async payload => {
this.dispatchUpdate();
ui.growl.append('A new proposal is available', 'success');
this.unload();
},
async () => {
ui.growl.append('Sorry, you don\'t have the rights to clone this proposal', 'danger')
}
);
}
async complain(event) {
let complaint = await this.openDialog(
await this.loadContent(
'projects/submissions/dialogs/SubmissionFormComplaintDialog',
{ title: 'File a complaint about the evaluation' }, {
pic: this.pic,
number: this.number,
proposal: this.submission
}
)
);
if(complaint) this.unload();
}
DOMContentResized() {
super.DOMContentResized();
let device = this.el.getAttribute('device');
let tabs = this.find('.tabs-extended');
switch(device) {
case 'desktop':
ui.show(tabs);
let current = tabs.querySelector('.tab-selected');
let part = current.getAttribute('data-target');
this.findAll('.content[data-part]').forEach(content => { if(content.getAttribute('data-part') != part) ui.hide(content) });
break;
default:
ui.hide(tabs);
this.findAll('.content[data-part]').forEach(content => { ui.show(content) })
break;
}
}
dispatchUpdate() {
/*
// MFA: if ever needed to perform local updates
let data = {
number: this.submission.data.proposal.number,
acronym: this.submission.data.proposal.acronym,
status: this.submission.data.status,
version: this.submission.data.version
}
*/
let data = null;
app.events.trigger('proposal_updated', data);
}
}
app.registerClass('SubmissionShortForm2023View', SubmissionShortForm2023View);