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
+53
View File
@@ -0,0 +1,53 @@
/**
* AWS download helper
* @author Nike
* @version 1.0
* @category MyEic
* @subcategory Libraries
*/
class AwsFileDownload {
options = {
pdf : { url: 'https://api.dev.eismea.eu/stable/vod/source?file=',
method: 'GET',
headers: { 'Accept': 'application/json',
'Content-Type': 'application/json',
},
credentials: 'include',
},
}
constructor(options) {
this.mergeOptionsNode(this.options, options);
}
mergeOptionsNode(target, source) {
for (let option in source) {
if(typeof source[option] === 'object' && !Array.isArray(source[option]) && (option in target) && !(target[option] instanceof(Element))) {
this.mergeOptionsNode(target[option], source[option]);
} else {
target[option] = source[option];
}
}
}
async getDownloadUrl(type, filename) {
let response
let uri = this.options[type].url+filename
try {
let foptions = {
method: this.options[type].method,
headers: this.options[type].headers,
}
if(this.options[type].credentials) foptions['credentials'] = this.options[type].credentials
response = await fetch(uri, foptions)
response = await response.json()
if(response && response.payload && response.payload.url) return(response.payload.url)
return(null)
} catch(debugMessage) {
console.warn('Error getting upload signed URL:',debugMessage)
return('')
}
}
}
+55
View File
@@ -0,0 +1,55 @@
/**
* AWS File upload
* @extends FileUpload
* @author Nike
* @version 1.0
* @category MyEic
* @subcategory Libraries
*/
class AwsFileUpload extends FileUpload {
constructor(el, options) {
super(el, options);
this.filename = options.filename
this.pic = options.pic
this.pid = options.pid
}
mergeOptionsNode(target, source) {
for (let option in source) {
if(typeof source[option] === 'object' && !Array.isArray(source[option]) && (option in target) && !(target[option] instanceof(Element))) {
this.mergeOptionsNode(target[option], source[option]);
} else {
target[option] = source[option];
}
}
}
async onBeforeUploadOneStart(file) {
let response
let ext = file.name.substring(file.name.lastIndexOf('.')+1) //Get the extension from the user file name
if((!this.filename) || (this.filename != this.createFileName(this.pic, this.pid)+'.'+ext)) { // Carefull: extension could change !
this.filename = this.createFileName(this.pic, this.pid)+'.'+ext
}
let uri = this.options.getSignedUrl+this.filename
try {
response = await fetch(uri, {
method: this.options.getSignedMethod,
headers: { 'Accept': 'application/json',
'Content-Type': file.type,
},
credentials: 'include',
}).then( response => response.json())
let payload = response.payload
this.options.uploadUrl = payload.url // Signed url is here
this.options.uploadMethod = 'PUT'
} catch(debugMessage) {
console.warn('Error getting upload signed URL:',debugMessage)
return(false)
}
return(true)
}
createFileName(pic, pid) { return(pic+'_'+pid+'_'+crypto.randomUUID()) }
}
+113
View File
@@ -0,0 +1,113 @@
/**
*
* @author Nicolas Stein
* @version 1.0
* @category MyEic
* @subcategory Libraries
*/
class Matomo4Sparc {
/**
* @typedef Matomo4Sparc~options
* @property {string} trackUrl
* @property {string} idsite
* @property {string} method
* @property {object} baseLogObj
* @property {number} baseLogObj.rec
* @property {number} baseLogObj.apiv
* @property {number} baseLogObj.new_visit
*/
/** @type {Matomo4Sparc~options} */
options = {
trackUrl: '',
idsite:'1',
method: 'POST',
baseLogObj : {
rec:1,
apiv:1,
new_visit: 0,
},
}
/**
*
* @param {Matomo4Sparc~options} options
*/
constructor(options) {
this.mergeOptions(this.options, options);
this.options.baseLogObj.idsite = this.options.idsite
//console.log('Matomo4Sparc started...')
}
/**
*
* @param {Object} target
* @param {Object} source
*/
mergeOptions(target, source) {
for (let option in source) {
if(typeof source[option] === 'object' && !Array.isArray(source[option]) && (option in target) && !(target[option] instanceof(Element))) {
this.mergeOptions(target[option], source[option]);
} else {
target[option] = source[option];
}
}
}
/**
* @typedef {Object} Matomo4Sparc~UserInfo
* @property {string} res
* @property {string} ua
* @property {string} uid
* @property {string} _var
*/
/**
*
* @returns {Matomo4Sparc~UserInfo}
*/
userInfos() {
return({
res: window.screen.availWidth+'x'+window.screen.availHeight,
ua: window.navigator.userAgent,
uid: app.User.identity.uuid,
_cvar: JSON.stringify({ userRoles: app.User.roles })
})
}
/**
*
* @param {*} action
* @param {*} callOptions
*/
logAction(action, callOptions) {
let logObj = {
...this.options.baseLogObj,
...this.userInfos(),
...{
rand: crypto.randomUUID(),
action_name: action,
url: document.location.href,
},
...callOptions
}
let fd = new FormData()
for(let key in logObj) fd.set(key, logObj[key])
fetch(
this.options.trackUrl,{
method: this.options.method,
body: fd
})
}
}
app.registerClass('Matomo4Sparc', Matomo4Sparc)
if(app.config.matomo4sparc && app.config.matomo4sparc.enabled) {
app.matomo = new Matomo4Sparc(app.config.matomo4sparc)
app.matomo.logAction('Launched Myeic App', { new_visit: 1 })
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2786
View File
File diff suppressed because one or more lines are too long
+3834
View File
File diff suppressed because it is too large Load Diff
+480
View File
@@ -0,0 +1,480 @@
@font-face {
font-family: 'glyphs';
src: url('fonts/glyphs.eot?q6ldsx9');
src: url('fonts/glyphs.eot?q6ldsx9#iefix') format('embedded-opentype'),
url('fonts/glyphs.ttf?q6ldsx9') format('truetype'),
url('fonts/glyphs.woff?q6ldsx9') format('woff'),
url('fonts/glyphs.svg?q6ldsx9#glyphs') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
}
[class^="icon-"], [class*=" icon-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'glyphs' !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
span[class*="icon-"][primary]:before { color: var(--app-color-primary); }
span[class*="icon-"][secondary] { color: var(--app-color-secondary); }
span[class*="icon-"][success] { color: var(--app-color-success); }
span[class*="icon-"][danger] { color: var(--app-color-danger); }
span[class*="icon-"][warning] { color: var(--app-color-warning); }
span[class*="icon-"][accent] { color: var(--app-color-accent); }
span[class*="icon-"][info] { color: var(--app-color-info); }
[class*="icon-"][xxsmall] { font-size: var(--eicui-base-icon-size-2xs); }
[class*="icon-"][xsmall] { font-size: var(--eicui-base-icon-size-xs) !important; }
[class*="icon-"][small] { font-size: var(--eicui-base-icon-size-s) !important; }
[class*="icon-"][medium] { font-size: var(--eicui-base-icon-size-m) !important; }
[class*="icon-"][large] { font-size: var(--eicui-base-icon-size-l) !important; }
[class*="icon-"][xlarge] { font-size: var(--eicui-base-icon-size-xl) !important; }
[class*="icon-"][xxlarge] { font-size: var(--eicui-base-icon-size-2xl); }
[class*="icon-"][xxxlarge] { font-size: var(--eicui-base-icon-size-3xl); }
[class*="icon-"][xxxxlarge] { font-size: var(--eicui-base-icon-size-4xl); }
@keyframes spin {
0% { transform: rotate(0deg) ; }
100% { transform: rotate(360deg) ; }
}
@keyframes uturn {
0% { transform: rotate(-180deg) ; }
100% { transform: rotate(0deg) ; }
}
@keyframes uturn-ccw {
0% { transform: rotate(180deg) ; }
100% { transform: rotate(0deg) ; }
}
.spin { animation: spin 1s infinite linear; }
.arrow-expand:before {
content: "\f106" !important;
animation: uturn-ccw 0.3s 1 linear;
}
.arrow-collapse:before {
content: "\f107" !important;
animation: uturn 0.3s 1 normal linear;
}
/*
.icon-spinner:before {
animation: spin 1s infinite linear;
}
Updating icons:
! DO NOT MODIFY code above
> Paste ICOMOON styles below (only classes declared as .icon-XXX:before )
*/
.icon-price-tag:before {
content: "\e93c";
}
.icon-align-center:before {
content: "\e933";
}
.icon-align-left:before {
content: "\e934";
}
.icon-align-right:before {
content: "\e935";
}
.icon-format-color:before {
content: "\e93b";
}
.icon-format-bold:before {
content: "\e936";
}
.icon-font-size-down:before {
content: "\e937";
}
.icon-format-underline:before {
content: "\e939";
}
.icon-big-bullet:before {
content: "\e928";
}
.icon-code:before {
content: "\e92b";
}
.icon-check-rounded:before {
content: "\e91f";
}
.icon-dashboard:before {
content: "\e92a";
}
.icon-more:before {
content: "\e923";
}
.icon-locked:before {
content: "\e925";
}
.icon-unlocked:before {
content: "\e926";
}
.icon-menu:before {
content: "\e920";
}
.icon-reply:before {
content: "\e921";
}
.icon-back:before {
content: "\e921";
}
.icon-link-ext:before {
content: "\e922";
}
.icon-share:before {
content: "\e929";
}
.icon-workflow:before {
content: "\e929";
}
.icon-health:before {
content: "\e927";
}
.icon-copy:before {
content: "\e915";
}
.icon-filter:before {
content: "\e91c";
}
.icon-deny:before {
content: "\e906";
}
.icon-home:before {
content: "\e91d";
}
.icon-pause:before {
content: "\e916";
}
.icon-market:before {
content: "\e914";
}
.icon-thumbs-down:before {
content: "\e90b";
}
.icon-thumbs-up:before {
content: "\e90c";
}
.icon-trash:before {
content: "\e91e";
}
.icon-bolt:before {
content: "\e901";
}
.icon-check:before {
content: "\e902";
}
.icon-cancel:before {
content: "\e907";
}
.icon-close:before {
content: "\e908";
}
.icon-download:before {
content: "\e903";
}
.icon-envelope:before {
content: "\e909";
}
.icon-company:before {
content: "\e90a";
}
.icon-servers:before {
content: "\e904";
}
.icon-snapshot:before {
content: "\e92d";
}
.icon-toc:before {
content: "\e92e";
}
.icon-folder:before {
content: "\e92f";
}
.icon-folder-open:before {
content: "\e930";
}
.icon-folder-add:before {
content: "\e931";
}
.icon-folder-remove:before {
content: "\e932";
}
.icon-qrcode:before {
content: "\e938";
}
.icon-chat:before {
content: "\e96c";
}
.icon-bug:before {
content: "\e999";
}
.icon-font-size-up:before {
content: "\ea61";
}
.icon-format-italic:before {
content: "\ea64";
}
.icon-stop:before {
content: "\e92c";
}
.icon-play:before {
content: "\e900";
}
.icon-history:before {
content: "\e94d";
}
.icon-spinner:before {
content: "\e981";
animation: spin 1s infinite linear;
}
.icon-cog:before {
content: "\e994";
}
.icon-star-empty:before {
content: "\e9d7";
}
.icon-star-half:before {
content: "\e9d8";
}
.icon-star-full:before {
content: "\e9d9";
}
.icon-heart:before {
content: "\e9da";
}
.icon-happy:before {
content: "\e9df";
}
.icon-face-smile:before {
content: "\e9e1";
}
.icon-face-tongue:before {
content: "\e9e3";
}
.icon-face-sad:before {
content: "\e9e5";
}
.icon-face-wink:before {
content: "\e9e7";
}
.icon-face-baffled:before {
content: "\e9f3";
}
.icon-face-confused:before {
content: "\e9f5";
}
.icon-youtube:before {
content: "\ea9d";
}
.icon-linkedin:before {
content: "\eac9";
}
.icon-pdf:before {
content: "\eadf";
}
.icon-pen:before {
content: "\e910";
}
.icon-coaching:before {
content: "\e91a";
}
.icon-cart:before {
content: "\e93a";
}
.icon-phone:before {
content: "\e942";
}
.icon-hour-glass:before {
content: "\e90f";
}
.icon-refresh:before {
content: "\e984";
}
.icon-complaint:before {
content: "\e9a8";
}
.icon-evaluation:before {
content: "\e9b8";
}
.icon-link:before {
content: "\e9cb";
}
.icon-loop:before {
content: "\ea2d";
}
.icon-image:before {
content: "\e90d";
}
.icon-new:before {
content: "\e924";
}
.icon-map:before {
content: "\e947";
}
.icon-pin:before {
content: "\e947";
}
.icon-user-check:before {
content: "\e975";
}
.icon-cogs:before {
content: "\e995";
}
.icon-bin:before {
content: "\e9ac";
}
.icon-attachment:before {
content: "\e9cd";
}
.icon-xls:before {
content: "\eae2";
}
.icon-review:before {
content: "\e917";
}
.icon-correction:before {
content: "\e918";
}
.icon-mediation:before {
content: "\e919";
}
.icon-writing:before {
content: "\e91b";
}
.icon-sort-asc:before {
content: "\e911";
}
.icon-sort-desc:before {
content: "\e912";
}
.icon-unsorted:before {
content: "\e913";
}
.icon-help:before {
content: "\e90e";
}
.icon-plus:before {
content: "\f067";
}
.icon-search:before {
content: "\f002";
}
.icon-user:before {
content: "\f007";
}
.icon-logoff:before {
content: "\f011";
}
.icon-edit:before {
content: "\f040";
}
.icon-checked:before {
content: "\f046";
}
.icon-info:before {
content: "\f05a";
}
.icon-expand:before {
content: "\f065";
}
.icon-warning:before {
content: "\f071";
}
.icon-calendar:before {
content: "\f073";
}
.icon-comment:before {
content: "\f075";
}
.icon-twitter:before {
content: "\f081";
}
.icon-facebook:before {
content: "\f082";
}
.icon-square-o:before {
content: "\f096";
}
.icon-website:before {
content: "\f0ac";
}
.icon-users:before {
content: "\f0c0";
}
.icon-list-ul:before {
content: "\f0ca";
}
.icon-table:before {
content: "\f0ce";
}
.icon-exchange:before {
content: "\f0ec";
}
.icon-alert:before {
content: "\f0f3";
}
.icon-chevron-left:before {
content: "\f100";
}
.icon-chevron-right:before {
content: "\f101";
}
.icon-angle-left:before {
content: "\f104";
}
.icon-angle-right:before {
content: "\f105";
}
.icon-angle-up:before {
content: "\f106";
}
.icon-angle-down:before {
content: "\f107";
}
.icon-zip:before {
content: "\f1c6";
}
.icon-send:before {
content: "\f1d8";
}
.icon-preview:before {
content: "\f1e5";
}
.icon-stats:before {
content: "\f200";
}
.icon-toggle-off:before {
content: "\f204";
}
.icon-toggle-on:before {
content: "\f205";
}
.icon-user-add:before {
content: "\f234";
}
.icon-calendar-plus:before {
content: "\f271";
}
.icon-calendar-minus:before {
content: "\f272";
}
.icon-calendar-failed:before {
content: "\f273";
}
.icon-calendar-check:before {
content: "\f274";
}
.icon-graduate:before {
content: "\e905";
}
Binary file not shown.
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
+93
View File
@@ -0,0 +1,93 @@
[eicbarchart] {
position: relative;
}
[eicbarchart] svg .title text {
stroke: var(--card-font-color);
}
[eicbarchart] svg .plot rect {
fill: var(--eicui-base-color-grey-10);
transition: fill 0.5s;
}
[eicbarchart] svg .plot[primary] rect {
fill: var(--eicui-base-color-primary-100);
}
[eicbarchart] svg .plot[secondary] rect {
fill: var(--eicui-base-color-secondary-100);
}
[eicbarchart] svg .plot[success] rect {
fill: var(--eicui-base-color-success-100);
}
[eicbarchart] svg .plot[danger] rect {
fill: var(--eicui-base-color-danger-100);
}
[eicbarchart] svg .plot[warning] rect {
fill: var(--eicui-base-color-warning-100);
}
[eicbarchart] svg .plot[info] rect {
fill: var(--eicui-base-color-info-100);
}
[eiclinechart] svg .plot path {
stroke: var(--eicui-base-color-grey-10);
fill: transparent;
transition: all 0.5s;
stroke-width: 2px;
}
[eiclinechart] svg .plot[primary] path {
stroke: var(--eicui-base-color-primary-100);
}
[eiclinechart] svg .plot[secondary] path {
stroke: var(--eicui-base-color-secondary-100);
}
[eiclinechart] svg .plot[success] path {
stroke: var(--eicui-base-color-success-100);
}
[eiclinechart] svg .plot[danger] path {
stroke: var(--eicui-base-color-danger-100);
}
[eiclinechart] svg .plot[warning] path {
stroke: var(--eicui-base-color-warning-100);
}
[eiclinechart] svg .plot[info] path {
stroke: var(--eicui-base-color-info-100);
}
[eiclinechart] svg .plot .dot {
stroke: var(--eicui-base-color-grey-10);
fill: var(--app-color-white);
transition: all 0.5s;
stroke-width: 2px;
}
[eiclinechart] svg .plot[primary] .dot {
stroke: var(--eicui-base-color-primary-100);
}
[eiclinechart] svg .plot[secondary] .dot {
stroke: var(--eicui-base-color-secondary-100);
}
[eiclinechart] svg .plot[success] .dot {
stroke: var(--eicui-base-color-success-100);
}
[eiclinechart] svg .plot[danger] .dot {
stroke: var(--eicui-base-color-danger-100);
}
[eiclinechart] svg .plot[warning] .dot {
stroke: var(--eicui-base-color-warning-100);
}
[eiclinechart] svg .plot[info] .dot {
stroke: var(--eicui-base-color-info-100);
}
[eicpiechart] svg .back { fill: var(--app-color-white); }
[eicpiechart] svg .slice {
fill: transparent;
animation: slicewheel 0.6s linear;
}
[eicpiechart] svg .plot circle[danger] { stroke: var(--eicui-base-color-danger-100); }
[eicpiechart] svg .plot circle[success] { stroke: var(--eicui-base-color-success-100); }
[eicpiechart] svg .plot circle[info] { stroke: var(--eicui-base-color-info-100); }
[eicpiechart] svg .plot circle[secondary] { stroke: var(--eicui-base-color-grey-50); }
[eicpiechart] svg .plot circle[primary] { stroke: var(--eicui-base-color-primary-100); }
[eicpiechart] svg .plot circle[warning] { stroke: var(--eicui-base-color-warning-100); }
[eicpiechart] svg .plot circle[accent] { stroke: var(--eicui-base-color-accent-100); }
@keyframes slicewheel {
from { stroke-dasharray: 100; }
}
+376
View File
@@ -0,0 +1,376 @@
/**
* @augments EicComponent
* @category EICUI/Components
*/
class Chart extends EicComponent {
constructor(el, options) {
super(el, options);
if(this._constructed) return this;
this.el.innerHTML = ``;
this.plotter = ui.create(`<svg height="${this.options.height == 'auto' ? '100%': this.options.height}" width="100%"></svg>`)
this.el.append(this.plotter);
this.el.setAttribute(this.options.identifier, '')
}
set data(value) {
this.options.data = value;
this.redraw()
}
get data() { return this.options.data }
redraw() {}
clear() {
this.options.data = [];
this.redraw();
}
}
/**
* @augments EicComponent
* @category EICUI/Components
*/
class BarChart extends Chart {
constructor(el, options) {
let defaultOptions = {
title: '',
subtitle: '',
height: 'auto',
showLabels: true,
showValues: true,
maxPlot: 0,
data: [],
identifier: 'eicbarchart'
}
super(el, {...defaultOptions, ...(options || null)});
if(this._constructed) return this;
this.redraw();
}
redraw() {
let series = this.options.data;
let minValue = 0;
let maxValue = 0;
let w = this.position.width;
let h = this.options.height == 'auto' ? this.position.height: this.options.height;
let bh = Math.max(h - 30, 0);
this.plotter.innerHTML = '';
if(this.options.title) {
this.plotter.append(ui.create(`<svg class="title" x="0" y="0"><text x="50%" y="16" text-anchor="middle">${this.options.title}</text></g>`))
}
if(series.length > 0) {
let start = this.options.maxPlot > 0 && this.options.maxPlot < series.length ? series.length - this.options.maxPlot: 0;
for(let item of series) {
if(item.value > maxValue) maxValue = item.value;
if(item.value < minValue) minValue = item.value;
}
let index = 0;
for(let i = start; i < series.length; i++) {
let item = series[i];
let g = ui.create(
`<svg class="plot" ${item.severity || ''} x="${100 * index / (series.length - start)}%" y="0" width="${100 / (series.length - start)}%">
<rect x="10%" y="${bh + 24 - bh * (item.value / maxValue)}" width="80%" height="${bh * (item.value / maxValue)}">
${item.tip ? `<title>${item.tip}</title>`: ''}
</rect>
${this.options.showValue ? `<text x="50%" y="${bh + 12 - bh * (item.value / maxValue)}" text-anchor="middle">${item.value}</text>`: ''}
${this.options.showLabel ? `<text x="50%" y="${h - 6}" text-anchor="middle">${item.label}</text>`: ''}
</svg>`);
this.plotter.append(g);
index++;
}
}
}
add(data) {
this.options.data.push(data);
this.redraw();
}
}
class LineChart extends Chart {
constructor(el, options) {
let defaultOptions = {
title: '',
subtitle: '',
height: 'auto',
showLabels: true,
showValues: true,
maxPlot: 0,
data: [],
identifier: 'eiclinechart'
}
super(el, {...defaultOptions, ...(options || null)});
if(this._constructed) return this;
this.redraw();
}
redraw() {
let series = this.options.data;
let minValue = 0;
let maxValue = 0;
let w = this.position.width;
let h = this.options.height == 'auto' ? this.position.height: this.options.height;
let bh = Math.max(h - 30, 0);
this.plotter.innerHTML = '';
if(this.options.title) {
this.plotter.append(ui.create(`<svg class="title" x="0" y="0"><text x="50%" y="16" text-anchor="middle">${this.options.title}</text></g>`))
}
if(series.length > 0) {
let start = this.options.maxPlot > 0 && this.options.maxPlot < series.length ? series.length - this.options.maxPlot: 0;
for(let serie of series) {
for(let value of serie.data) {
if(value > maxValue) maxValue = value;
if(value < minValue) minValue = value;
}
}
let dots = []
for(let item of series) {
let path = '';
let index = 0;
for(let value of item.data) {
let x = 5 + ((this.position.width - 10) * index / (item.data.length - start - 1));
let y = bh + 24 - (bh * (value / maxValue));
path += `${index == 0 ? 'M': 'L'} ${x} ${y} `
let dot = ui.create(`<svg class="plot" ${item.severity || ''} x="0" y="0" width="100%" height="100%">
<rect x="${x-4}" y="${y-4}" width="8" height="8" class="dot">
<title>${this.options.labels[index]}: ${value}${item.unit ? item.unit: ''}</title>
</rect>
</svg>`)
dots.push(dot)
index++;
}
let line = ui.create(
`<svg class="plot" ${item.severity || ''} x="0" y="0" width="100%" height="100%">
<path d="${path}" />
</svg>`);
this.plotter.append(line);
}
for(let dot of dots) this.plotter.append(dot)
}
}
add(data) {
this.options.data.push(data);
this.redraw();
}
}
class PieChart extends Chart {
constructor(el, options) {
let defaultOptions = {
title: '',
subtitle: '',
height: 'auto',
showLabels: true,
showValues: true,
maxPlot: 0,
data: [],
identifier: 'eicpiechart'
}
super(el, {...defaultOptions, ...(options || null)});
if(this._constructed) return this;
this.redraw();
}
redraw() {
let series = this.options.data;
let r = 40;
let center = ['50%', '50%'];
this.plotter.setAttribute( 'viewBox' , '0 0 100 100')
this.plotter.innerHTML = '';
if(this.options.title) {
this.plotter.append(ui.create(`<svg class="title" x="0" y="0"><text x="50%" y="16" text-anchor="middle">${this.options.title}</text></g>`))
}
series = series.sort((a,b) => a.value < b.value ? -1 : 0)
if(series.length > 0) {
let total = 0;
for(let item of series) { total += item.value }
// build the slices
let o = 0;
let l = r * Math.PI;
for(let item of series) {
if(item.value == 0) continue;
o = o + Math.round(l * item.value / total)
let g = ui.create(`<svg class="plot" x="0" y="0" width="100%" height="100%">
<circle class="slice" ${item.severity} stroke-dasharray="${o + ' ' + l}" stroke-width="${r}" cx="${center[0]}" cy="${center[1]}" r="${r / 2}">
</svg>`);
this.plotter.prepend(g);
}
// add pie background
this.plotter.append( ui.create(`<svg class="plot" x="0" y="0" width="100%" height="100%"><circle class="back" cx="${center[0]}" cy="${center[1]}" r="${r / 1.7}" /></svg>`) );
}
//this.el.html(this.el.html())
}
add(data) {
this.options.data.push(data);
this.redraw();
}
}
class PieTreeChart extends Chart {
constructor(el, options) {
let defaultOptions = {
title: '',
subtitle: '',
height: 'auto',
showLabels: true,
showValues: true,
maxPlot: 0,
data: [],
sort: ((a,b) => a.value < b.value ? -1 : 1), // -1 : 1 => CCW 1 : -1 => CW
identifier: 'eicpiechart',
center:['50%', '50%'],
innerRadius: 35, // in % of the viewbox, thus max 50
outerRadius: 50, // in % of the viewbox, thus max 50
layersGap: 1, // in px
angularGap: 1, // in deg
}
super(el, {...defaultOptions, ...(options || null)});
if(this._constructed) return this;
this.flatSeries = []
if(typeof(this.options.sort)=='function') this.options.data.sort(this.options.sort)
this.serieToFlat(this.options.data)
this.redraw();
}
serieToFlat(serie){
const cleanSerie = serie.map(obj => { const {children, ...cleanObj} = obj; return(cleanObj) }) // Remove children
this.flatSeries.push(cleanSerie)
if(serie.some(item => Array.isArray(item.children))){
let layer = []
for(let item of serie){
if(Array.isArray(item.children)){
if(typeof(this.options.sort)=='function') item.children.sort(this.options.sort)
const ChildrenSum = item.children.reduce((acc, item) => acc + item.value, 0);
const cleanSubSerie = item.children.map(obj => {
obj.value = (obj.value/ChildrenSum)*item.value
obj.parentSeverity = item.severity=='inherit' ? item.parentSeverity : item.severity
return(obj)
})
layer = [...layer, ...cleanSubSerie]
} else { // simulate a single full layer
layer.push( { severity: '', hide:true, value: item.value },)
}
}
this.serieToFlat(layer)
}
}
redraw() {
this.plotter.setAttribute( 'viewBox' , '0 0 100 100')
this.plotter.setAttribute('shape-rendering', 'geometricPrecision')
this.plotter.innerHTML = '';
if(this.options.title) {
this.plotter.append(ui.create(`<svg class="title" x="0" y="0"><text x="50%" y="16" text-anchor="middle">${this.options.title}</text></g>`))
}
const nbLayers = this.flatSeries.length
const bandsWidth = Math.floor((this.options.outerRadius - this.options.innerRadius)/nbLayers)
for(let layer=0; layer<nbLayers; layer++){
this.drawLayer(this.flatSeries[layer], this.options.innerRadius+(layer*bandsWidth), this.options.innerRadius+((layer+1)*bandsWidth)-this.options.layersGap )
}
if(typeof(this.options.click)=='function'){
this.plotter.querySelectorAll('circle.slice').forEach(el => el.addEventListener('click', this.options.click))
}
}
drawLayer(serie, innerRadius, outerRadius){
let sectorWidth = outerRadius>innerRadius ? outerRadius - innerRadius : 1
let radius = innerRadius + (sectorWidth/2)
if(serie.length > 0) {
let total = 0;
for(let item of serie) { total += item.value }
// Ideal dash pattern in our case : [ Space(start angle) Mark(value) Space(complement) ]
// But dasharray must have an even count, thus 4, and it always starts with a mark.
// Therefore: [ Mark(1) Space(start angle) Mark(value) Space(complement) ] then shift left by 1 (thus hide mark(1)) with stroke-dashoffset.
// This is much cleaner than the cumulative & draw-over technique which quickly creates graphical artefacts (dirt at borders) on non-trivial drawings.
const circonf = (2 * radius * Math.PI)
let offset = 0
for(let item of serie) {
if(item.value == 0) continue;
const dashLength = circonf * item.value / total
let markup = `<svg class="plot" x="0" y="0" width="100%" height="100%">`
markup += `<circle class="slice" ${item.hide ? 'stroke="#FFF"' : item.severity=='inherit' ? item.parentSeverity : item.severity}
stroke-dasharray="1 ${offset} ${dashLength} ${circonf-dashLength-offset}"
stroke-dashoffset="1"
stroke-width="${sectorWidth}"
${item.brightness ? 'style="filter: brightness('+item.brightness+');"' : ''}
cx="${this.options.center[0]}"
cy="${this.options.center[1]}"
r="${radius}"
${(typeof(item.dataset)=='object') ? Object.entries(item.dataset).map(([k, v]) => `data-${k}="${v}"`).join(' ') : ''}
>
`
if(item.title) markup += `<title>${item.title}</title>`
markup += '</circle></svg>'
offset += dashLength
this.plotter.prepend(ui.create(markup))
}
if(this.options.angularGap>0){
offset = 0
let markup = `<svg class="plot" x="0" y="0" width="100%" height="100%">`
for(let item of serie) {
if(item.value == 0) continue
const dashLength = circonf * item.value / total
const offsetAngle = offset / radius
const x1 = parseInt(this.options.center[0]) + (innerRadius * Math.cos(offsetAngle))
const y1 = parseInt(this.options.center[1]) + (innerRadius * Math.sin(offsetAngle))
const x2 = parseInt(this.options.center[0]) + (outerRadius * Math.cos(offsetAngle) * 1.1)
const y2 = parseInt(this.options.center[1]) + (outerRadius * Math.sin(offsetAngle) * 1.1)
markup += `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="#FFF" stroke-width="${this.options.angularGap}"/>`
offset += dashLength
}
markup += '</svg>'
this.plotter.append(ui.create(markup))
}
}
}
add(data) {
this.options.data.push(data);
this.redraw();
}
}
+53
View File
@@ -0,0 +1,53 @@
[eicfileupload] div.file-drop-area {
outline: 1px solid var(--eicui-base-color-primary-75);
border-radius: 3px;
margin: var(--eicui-base-spacing-s);
padding: var(--eicui-base-spacing-m);
background-color: transparent;
}
[eicfileupload] div.file-preview-area {
margin: var(--eicui-base-spacing-m) var(--eicui-base-spacing-m);
}
[eicfileupload] div.file-preview-area span.err {
color: var(--eicui-base-color-danger);
}
[eicfileupload] div.buttons-area {
padding: 0 1vw 1vw 1vw;
}
[eicfileupload] div.file-drop-area.dragover {
background-color: #fffae9;
}
[eicfileupload] div.file-drop-area .file-msg {
opacity: 1;
padding: 0 var(--eicui-base-spacing-l);
}
[eicfileupload] div.file-drop-area .file-msg.disabled {
opacity: .5;
}
[eicfileupload] input[type="file"] {
display: none;
}
[eicfileupload] span.fake-btn {
background-color: #fff;
border: 1px solid #9e9ec4;
border-radius: 3px;
margin-right: 8px;
padding: 8px 15px;
cursor: pointer;
}
[eicfileupload] .file-preview-item {
align-items: center;
display: grid;
grid-template-columns: min-content auto 2fr 2fr min-content;
grid-gap: 10px;
}
[eicfileupload] .file-preview-item .file-icon {
font-size: var(--eicui-base-font-size-xl);
}
[eicfileupload] .file-preview-item .file-size,
[eicfileupload] .file-preview-item .file-actions,
[eicfileupload] .file-preview-item .file-type {
text-align: center;
white-space: nowrap;
}
+451
View File
@@ -0,0 +1,451 @@
/**
* File upload
* @augments EicComponent
* @category EICUI/Components
* @author Nike
* @version 1.0
*/
class FileUpload extends EicComponent {
/**
* Triggered just before upload of file(s) is launched
* Return False to prevent theupload to happen
*
* @event FileUpload#onBeforeUploadAllStart
* @type {event}
*/
onBeforeUploadAllStart() { return(true) }
/**
* Triggered when the upload of file(s) is launched
*
* @event FileUpload#onUploadAllStart
* @type {event}
*/
onUploadAllStart() { }
/**
* Triggered when the upload of file(s) is launched
*
* @event FileUpload#onUploadAllEnd
* @type {event}
*/
onUploadAllEnd() { }
/**
* Triggered just before upload of file(s) is launched
* Return False to prevent theupload to happen
*
* @event FileUpload#onBeforeUploadOneStart
* @type {event}
* @param {file} fileInfo The offening file object (https://developer.mozilla.org/en-US/docs/Web/API/File)
*/
onBeforeUploadOneStart(file) { return(true) }
/**
* Triggered when the upload of file(s) is launched
*
* @event FileUpload#onUploadOneStart
* @type {event}
* @param {file} fileInfo The offening file object (https://developer.mozilla.org/en-US/docs/Web/API/File)
*/
onUploadOneStart(file) { }
/**
* Triggered when the upload of file(s) is launched
*
* @event FileUpload#onUploadOneEnd
* @type {event}
* @param {file} fileInfo The offening file object (https://developer.mozilla.org/en-US/docs/Web/API/File)
*/
onUploadOneEnd(file) { }
/**
* Triggered on attempting to add a file with wrong extension/mime to the list
*
* @event FileUpload#onWrongFileType
* @type {event}
* @param {file} fileInfo The offening file object (https://developer.mozilla.org/en-US/docs/Web/API/File)
*/
onWrongFileType(file) { console.warn('Wrong file type !', file) }
/**
* Triggered when a file is added in the list
*
* @event FileUpload#onFileAdded
* @type {event}
* @param {integer} fileIdx The file index in this.filesList
* @param {file} fileInfo The added file object (https://developer.mozilla.org/en-US/docs/Web/API/File)
*/
onFileAdded(fileIdx, file) { }
/**
* Triggered when a file is removed from the list
*
* @event FileUpload#onFileRemoved
* @type {event}
* @param {integer} fileIdx The file index in this.filesList
* @param {file} fileInfo The removed file object (https://developer.mozilla.org/en-US/docs/Web/API/File)
*/
onFileRemoved(fileIdx, file) { }
/**
* @param {element|string} el A DOM element or the selector string for the element which will contain the component
* @param {object} [options] The grid options
* @param {string} [options.uploadUrl] Where to POST the files
* @param {string} [options.browseLabel] Text of the browser button
* @param {string} [options.submitLabel] Text of the upload button
* @param {string} [options.dndLabel] Text of the drop area
* @param {array} [options.allowedMimes] list of mim types accepted
* @param {array} [options.allowedExtensions] list of file extenstions accepted (dot excluded)
* @param {boolean} [options.allowDrop] Enables drag & drop
* @param {Integer} [options.maxFiles] Set to 1 to disable file list
* @param {Integer} [options.minFiles] Upload button will show only if files list greater or equal
* @param {object} [options.mimeTypes] Recognized mime types (unknown types show extension as name, and an attachment icon)
* @param {string} [options.mimetypes[mime].label] type name used in list
* @param {string} [options.mimetypes[mime].icon] icon used in list
* @param {string} [options.noUpload] Use when processiong files in browser : no upload to server, no upload/reset buttons
*/
constructor(el, options) {
super(el, options);
this.options = {
browseLabel: 'Choose file...',
submitLabel : 'upload',
resetLabel: 'clear list',
noUpload : false,
dndLabel: 'Drag and drop file here',
allowedMimes: ['application/pdf'],
allowedExtensions: ['pdf'],
allowDrop: true,
maxSize: 1024*1024*1024,
maxFiles: 1,
minFiles: 1,
uploadMethod: 'POST',
mimeTypes: {
'video/x-msvideo': {label: 'Video' , icon: 'youtube'},
'video/mp4': {label: 'Video' , icon: 'youtube'},
'video/mpeg': {label: 'Video' , icon: 'youtube'},
'video/ogg': {label: 'Video' , icon: 'youtube'},
'application/x-bzip': {label: 'Compressed file' , icon: 'zip'},
'application/x-bzip2': {label: 'Compressed file' , icon: 'zip'},
'application/gzip': {label: 'Compressed file' , icon: 'zip'},
'application/zip': {label: 'Compressed file' , icon: 'zip'},
'application/x-7z-compressed': {label: 'Compressed file' , icon: 'zip'},
'application/msword': {label: 'Text document' , icon: 'writing'},
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': {label: 'Text document' , icon: 'writing'},
'application/vnd.oasis.opendocument.text': {label: 'Text document' , icon: 'writing'},
'application/vnd.ms-excel': {label: 'Spreadsheet' , icon: 'xls'},
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': {label: 'Spreadsheet' , icon: 'xls'},
'application/vnd.oasis.opendocument.spreadsheet': {label: 'Spreadsheet' , icon: 'xls'},
'text/csv': {label: 'Spreadsheet' , icon: 'xls'},
'image/gif': {label: 'Image' , icon: 'image'},
'image/jpeg': {label: 'Image' , icon: 'image'},
'image/png': {label: 'Image' , icon: 'image'},
'image/svg+xml': {label: 'Image' , icon: 'image'},
'application/pdf': {label: 'PDF document' , icon: 'pdf'},
}
};
this.setOptions(options);
this.el.setAttribute('eicfileupload', '');
this.fileItemTpl = `
<div class="file-preview-item">
<div class="file-icon">%{icon}</div>
<div class="file-name">%{name}</div>
<div class="file-size">%{size}</div>
<div class="file-type">%{type}</div>
<div class="file-actions">%{actions}</div>
</div>
`
this.browseBtn = new Button(null, {
icon: null,
severity: 'primary',
disabled: false,
badge: false,
rounded: false,
size: 'small',
hint: null,
label: this.options.browseLabel
})
this.browseBtn.click = (e) => {
e.stopPropagation(); e.preventDefault();
this.fileBtn.click()
}
if(!this.options.noUpload){
this.uploadBtn = new Button(null, {
icon: null,
severity: 'primary',
disabled: false,
badge: false,
rounded: false,
size: null,
hint: null,
label: this.options.submitLabel,
onclick: this.uploadAllFiles.bind(this)
})
this.uploadBtn.el.classList = 'uploadBtn custom-class'
this.resetBtn = new Button(null, {
icon: null,
severity: '',
disabled: false,
badge: false,
rounded: false,
size: null,
hint: null,
label: this.options.resetLabel
})
this.resetBtn.click = this.clearAllFiles.bind(this)
}
this.el.appendChild(ui.create(`
<div class="file-upload">
<div class="file-drop-area">
<span class="file-msg">${options.allowDrop ? this.options.dndLabel : ''}</span>
<input type="file" name="file" class="file-input" aria-label="Choose file ${options.allowDrop ? ' or '+ this.options.dndLabel : ''}">
</div>
<div class="file-preview-area"></div>
<div class="buttons-area cols-2"></div>
</div>
`))
this.fileBtn = this.el.querySelector('.file-input')
this.fileBtn.setAttribute('accept', [...this.options.allowedMimes, ...this.options.allowedExtensions.map(item => '.'+item)].join(', '))
if(this.options.maxFiles>1) this.fileBtn.setAttribute('multiple', '')
this.dropArea = this.el.querySelector('.file-drop-area')
this.dropAreaText = this.el.querySelector('.file-drop-area .file-msg')
this.dropArea.prepend(this.browseBtn.el)
this.previewArea = this.el.querySelector('.file-preview-area')
this.buttonsArea = this.el.querySelector('.buttons-area')
if(!this.options.noUpload){
this.buttonsArea.append(this.uploadBtn.el)
this.buttonsArea.append(this.resetBtn.el)
}
this.filesList = []
this.fileBtn.addEventListener("change", (e) => {
if((!e.target.files) || (e.target.files.length==0)) return
if((this.filesList.length + e.target.files.length) > this.options.maxFiles) return
this.addFiles(e.target.files)
})
if(options.allowDrop) {
this.initDnd()
this.enableDnd()
} else this.disableDnd()
this.refreshFilesList() //List still empty but that hides buttons depending on options
}
initDnd() {
window.addEventListener('drop', (e) => { e.preventDefault(); e.stopPropagation(); })
window.addEventListener('dragover', (e) => { e.preventDefault(); e.stopPropagation(); })
this.dropArea.addEventListener('dragenter', ((e) => {
e.stopPropagation(); e.preventDefault();
if(this.dndDisabled) return
this.dropArea.classList.add('dragover')
}).bind(this));
this.dropArea.addEventListener('dragleave', ((e) =>{
e.stopPropagation(); e.preventDefault();
let elBelow = document.elementFromPoint(e.clientX, e.clientY)
if( (elBelow.classList.contains('file-drop-area'))
|| (elBelow.classList.contains('file-msg'))
|| (elBelow.classList.contains('file-input'))
) return //leaving towards the child, so still over it
if(this.dndDisabled) return
this.dropArea.classList.remove('dragover')
}).bind(this))
this.dropArea.addEventListener('drop', ((e) => {
e.stopPropagation(); e.preventDefault();
if(this.dndDisabled) return
this.dropArea.classList.remove('dragover')
if((!e.dataTransfer) || (!e.dataTransfer.files) || (e.dataTransfer.files.length==0)) return
if((this.filesList.length + e.dataTransfer.files.length) > this.options.maxFiles) return
this.addFiles(e.dataTransfer.files)
}).bind(this));
}
disableDnd() {
this.dndDisabled = true
this.dropAreaText.classList.add('disabled')
}
enableDnd() {
this.dndDisabled = false
this.dropAreaText.classList.remove('disabled')
}
addFiles(files) {
for(let file of files) {
if((file.type!='') && this.checkFileType(file) && (file.size <= this.options.maxSize)) {
this.filesList.push(file)
this.onFileAdded(this.filesList.length-1, file)
this.refreshFilesList()
} else {
this.onWrongFileType(file)
}
}
}
removeFile(e) {
e.stopPropagation(); e.preventDefault();
let el = (e.target.tagName.toLocaleLowerCase()=='button') ? e.target : e.target.parentElement
let fileIdx = el.dataset['id']
let [file, ] = this.filesList.splice(fileIdx,1)
this.refreshFilesList()
this.onFileRemoved(fileIdx, file)
}
checkFileType(file) {
let ext = file.name.substring(file.name.lastIndexOf('.')+1)
return((this.options.allowedMimes.indexOf(file.type)>-1) && (this.options.allowedExtensions.indexOf(ext)>-1))
}
refreshFilesList() {
let html = ''; let itemLine, file, ext, icon, type;
for(let fileIdx in this.filesList) {
file = this.filesList[fileIdx]
ext = file.name.substring(file.name.lastIndexOf('.')+1)
if(this.options.mimeTypes.hasOwnProperty(file.type)) {
icon = this.options.mimeTypes[file.type].icon
type = this.options.mimeTypes[file.type].label
} else {
icon = 'attachment'
type = '".'+ext+'"'
}
icon = (this.options.mimeTypes.hasOwnProperty(file.type)) ? this.options.mimeTypes[file.type].icon : 'attachment'
itemLine = this.fileItemTpl
itemLine = itemLine.replace('%{icon}', `<i class="icon-${icon}"></i>`)
itemLine = itemLine.replace('%{size}', this.formatSize(file.size))
itemLine = itemLine.replace('%{type}', type)
itemLine = itemLine.replace('%{actions}', `
<button eicbutton class="file-remove" data-id="${fileIdx}" basic danger xxsmall title="remove" aria-enabled="true" aria-label="remove">
remove
</button>
`)
itemLine = itemLine.replace('%{name}', file.name) // Do this one last in case some %{xxx} in the name
html += itemLine
}
this.previewArea.innerHTML = html // Caution to myself: Re-creating all avoids multi-click-events
this.previewArea.querySelectorAll('.file-remove').forEach((el) => {
el.addEventListener('click', this.removeFile.bind(this))
})
if(!this.options.noUpload){
if(this.filesList.length >= this.options.minFiles) this.uploadBtn.disabled = false
else this.uploadBtn.disabled = true
}
if(this.filesList.length < this.options.maxFiles) {
this.browseBtn.disabled = false
this.enableDnd()
} else {
this.browseBtn.disabled = true
this.disableDnd()
}
if(!this.options.noUpload){
if(this.filesList.length > 0) this.resetBtn.disabled = false
else this.resetBtn.disabled = true
}
}
formatSize(size) {
if (!+size) return '0 Bytes'
const k = 1024
const units = ['Bytes', 'KB', 'MB', 'GB'] // Over GB, really ?
const i = Math.floor(Math.log(size) / Math.log(k))
return `${parseFloat((size / Math.pow(k, i)).toFixed(2))} ${units[i]}`
}
clearAllFiles(e) {
e.stopPropagation(); e.preventDefault();
this.filesList = []
this.refreshFilesList()
if(this.options.allowDrop) this.enableDnd()
}
uploadAllFiles(e) {
e.stopPropagation(); e.preventDefault();
if(!this.onBeforeUploadAllStart()) return
this.browseBtn.disabled = true
this.disableDnd()
this.uploadBtn.loading = true
this.resetBtn.disabled = true
for(let fileIdx in this.filesList) {
this.filesList[fileIdx].completed = false
this.uploadFile(fileIdx)
}
this.onUploadAllStart()
}
async uploadFile(fileIdx) {
let file = this.filesList[fileIdx]
let actionEl = this.previewArea.querySelector('.file-remove[data-id="'+fileIdx+'"]').parentElement
// Check file type --> if img add extension
let ext = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase();
if (['jpeg', 'png', 'gif', 'jpg', 'svg'].includes(ext)) {
this.options.uploadUrl += '.' + ext; // Add extension
}
let ok = await this.onBeforeUploadOneStart(file)
if((!ok) || (!this.options.uploadUrl)) {
this.filesList[fileIdx].completed = true
actionEl.innerHTML = '<span class="err">Failed !</span>'
this.uploadOneEnd(fileIdx)
return
}
actionEl.innerHTML = '(0 %)'
let xhrRequest = new XMLHttpRequest();
xhrRequest.open(this.options.uploadMethod, this.options.uploadUrl);
// Caution: This fucking AWS shit uses Content-Type as mime-type for the file, contrary to the rest of the web.
// Yeah, I know multi-files...multi-part etc...but AWS signed URL system is one file at a time anyway.
xhrRequest.setRequestHeader('Content-Type', this.filesList[fileIdx].type);
xhrRequest.upload.addEventListener("progress", this.uploadProgress.bind(this, fileIdx, actionEl));
xhrRequest.upload.addEventListener("error", ((err) => {
this.filesList[fileIdx].completed = true
actionEl.innerHTML = '<span class="err">Failed !</span>'
this.uploadOneEnd(fileIdx)
}).bind(this))
xhrRequest.upload.addEventListener("timeout", ((err) => {
this.filesList[fileIdx].completed = true
actionEl.innerHTML = '<span class="err">Timed-out !</span>'
this.uploadOneEnd(fileIdx)
}).bind(this))
xhrRequest.send(file);
this.onUploadOneStart()
}
uploadProgress(fileIdx, actionEl, e) {
if(!e.lengthComputable) return
let percentDone = Math.floor((e.loaded / e.total) * 100);
actionEl.innerHTML = `(${percentDone} %)`
if(e.loaded == e.total) {
this.filesList[fileIdx].completed = true
this.uploadOneEnd(fileIdx)
}
}
uploadOneEnd(fileIdx) {
this.onUploadOneEnd(this.filesList[fileIdx])
let allFinished = this.filesList.reduce((acc, x)=> (x.completed && acc), true)
if(allFinished) {
this.uploadBtn.loading = false
this.uploadBtn.disabled = true
this.resetBtn.disabled = false
this.onUploadAllEnd()
}
}
}
File diff suppressed because it is too large Load Diff
+85
View File
@@ -0,0 +1,85 @@
/* ****************************************************
* NODE MAP
* ****************************************************
*/
[eicnodemap] {
border: 1px solid var(--eicui-base-color-grey-25) !important;
background: var(--eicui-base-color-grey-10);
position: relative;
overflow: hidden;
}
[eicnodemap][resizable] { resize:vertical; }
[eicnodemap] svg .entity {
cursor:pointer;
transition: all 0.7s;
}
[eicnodemap] svg .entity .icon {
font-family: 'glyphs';
font-size: 24px;
}
[eicnodemap] svg .entity .bg {
fill: var(--app-color-white);
transition: all 0.7s;
}
[eicnodemap] svg .entity.selected > .bg {
stroke: var(--eicui-base-color-accessible-focus);
stroke-width: 3px;
}
[eicnodemap] .entity[primary] .bg { fill: var(--eicui-base-color-primary-100); }
[eicnodemap] .entity[secondary] .bg { fill: var(--eicui-base-color-grey-25); }
[eicnodemap] .entity[info] .bg { fill: var(--eicui-base-color-info-100); }
[eicnodemap] .entity[success] .bg { fill: var(--eicui-base-color-success-100); }
[eicnodemap] .entity[warning] .bg { fill: var(--eicui-base-color-warning-100); }
[eicnodemap] .entity[danger] .bg { fill: var(--eicui-base-color-danger-100); }
[eicnodemap] .entity[accent] .bg { fill: var(--eicui-base-color-accent-100); }
[eicnodemap] svg .entity[primary].selected > .bg { fill: var(--eicui-base-color-primary-110); }
[eicnodemap] .entity text {
font-family: Arial, Helvetica, sans-serif;
text-align: center;
fill: var(--app-color-black);
stroke-width: 0.1;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
transition: all 0.7s;
}
[eicnodemap] .entity[primary] text,
[eicnodemap] .entity[info] text,
[eicnodemap] .entity[success] text,
[eicnodemap] .entity[danger] text { fill: var(--app-color-white) }
[eicnodemap] .entity .title { font-size: normal; font-weight: bold; }
[eicnodemap] .entity .subtitle { font-size: smaller; font-weight: normal; }
[eicnodemap] .entity .badge[primary] .bg { fill: var(--eicui-base-color-primary-100); }
[eicnodemap] .entity .badge[success] .bg { fill: var(--eicui-base-color-success-100); }
[eicnodemap] .entity .badge[danger] .bg { fill: var(--eicui-base-color-danger-100); }
[eicnodemap] .entity .badge[warning] .bg { fill: var(--eicui-base-color-warning-100); }
[eicnodemap] .entity .badge[accent] .bg { fill: var(--eicui-base-color-accent-100); }
[eicnodemap] .entity .badge[info] .bg { fill: var(--eicui-base-color-info-100); }
[eicnodemap] .entity .badge text {
fill: var(--app-color-white);
font-size: normal;
}
[eicnodemap] .entity .badge[xxsmall] text { font-size: xx-small; }
[eicnodemap] .entity .badge[xsmall] text { font-size: x-small; }
[eicnodemap] .entity .badge[small] text { font-size: small; }
[eicnodemap] .entity .badge[large] text { font-size: large; }
[eicnodemap] .entity .badge[xlarge] text { font-size:x-large }
[eicnodemap] .entity .badge[xxlarge] text { font-size:xx-large }
[eicnodemap] .entity .type { font-size: 11px; }
[eicnodemap] .relation {
fill: none;
stroke: var(--app-color-info);
stroke-width: 4px;
stroke-dasharray: 10,5;
transition: all 0.7s;
}
[eicnodemap] .entity[disabled] {
pointer-events: none;
}
[eicnodemap] .entity[disabled] .title,
[eicnodemap] .entity[disabled] .subtitle {
opacity: 0.45;
}
+545
View File
@@ -0,0 +1,545 @@
/**
* @augments EicComponent
* @category EICUI/AdvancedComponents
*/
class NodeMap extends EicComponent {
entities = [];
relations = [];
constructor(el, options) {
let defaultOptions = {
resizable: true,
allowDrag: true,
orientation: 'linear',
entity: {
width: 200,
height: 50,
gap: 40
},
map: {
position: { x: 0, y: 0 },
size: { width: 0, height: 0 },
dragging: false,
},
scale: {
current: 1.0,
factor: 0.085,
min: 0.5,
max: 4
},
guides: true
}
super(el, {...defaultOptions, ...(options || {})});
if(this._constructed) return this;
this.el.setAttribute('eicnodemap','');
if(this.options.resizable)
this.el.setAttribute('resizable','');
else
this.el.removeAttribute('resizable');
this.el.innerHTML = '';
this.map = ui.create(`<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"></svg>`)
this.el.append(this.map);
this.viewbox = { x: 0, y: 0, w: this.el.clientWidth, h: this.el.clientHeight };
if(this.options.allowDrag) {
this.map.addEventListener('wheel', this.onMapScroll.bind(this));
this.map.addEventListener('mousedown', this.onMapDragStart.bind(this));
this.map.addEventListener('mousemove', this.onMapDragMove.bind(this));
this.mouseUpHandler = this.onMapDragStop.bind(this);
this.entityUpHandler = this.onEntityDragStop.bind(this);
}
}
set loading(state) {
if(state == true)
this.el.setAttribute('loading','');
else
this.el.removeAttribute('loading');
}
get loading() { return this.el.hasAttribute('loading') }
set data(value) {
this.clear();
if(value.entities) for(let entity of value.entities) this.addEntity(entity);
if(value.relations) for(let relation of value.relations) this.addRelation(relation);
this.reorder();
}
clear() {
this.map.innerHTML = '';
this.entities = [];
}
reorder() {
switch(this.options.orientation) {
case 'linear':
this.reorderLinear();
break;
case 'radial':
default:
this.reorderRadial();
}
}
reorderLinear() {
// 1. prepare geometries and identify starters and enders
for(let entity of this.entities) {
entity.geometry = {
starter: false,
ender: false,
depth: null,
siblings: null,
index: null
};
if(!entity.relations.find(r => r.target.id == entity.id)) {
entity.geometry.starter = true;
entity.geometry.depth = 0
}
entity.geometry.ender = !entity.relations.find(r => r.source.id == entity.id);
}
// 2. dig paths using starters
let starters = this.entities.filter(o => o.geometry.starter == true)
for(let entity of starters) {
this.digLinearPath(entity)
}
// 3. resolve siblings
let maxSiblings = 0;
for(let entity of this.entities) {
entity.geometry.siblings = this.entities.filter(o => o.geometry.depth == entity.geometry.depth).length - 1
maxSiblings = Math.max(maxSiblings, entity.geometry.siblings);
let i = 0;
for(let relation of entity.relations) {
if(relation.target.id != entity.id && !relation.target.geometry.index) {
relation.target.geometry.index = i;
i++;
}
}
}
// 4. reposition
let maxHeight = 3 * maxSiblings * (this.options.entity.height + (this.options.entity.gap))
for(let entity of this.entities) {
entity.x = this.options.entity.gap + (this.options.entity.width + this.options.entity.gap) * entity.geometry.depth;
entity.y = maxHeight * (entity.geometry.index + 1) / (entity.geometry.siblings + 2);
for(let relation of entity.relations) {
relation.redraw();
}
}
this.center();
}
digLinearPath(entity) {
for(let relation of entity.relations) {
if(relation.target.id != entity.id) {
if(!relation.target.geometry.depth) {
relation.target.geometry.depth = entity.geometry.depth + 1;
this.digLinearPath(relation.target)
}
}
}
}
center() {
let box = this.map.getBBox();
this.options.scale.current = Math.max(
(box.height + this.options.entity.gap) / (this.viewbox.h ),
(box.width + this.options.entity.gap) / (this.viewbox.w )
)
this.viewbox.y = box.y - (this.options.entity.gap / 2);
this.viewbox.x = 20;
this.setViewbox();
}
reorderRadial() {
let a = (Math.PI * 2) / this.entities.length;
let r = (this.options.entity.width) * Math.log(this.entities.length);
let i = 0;
let center = { x: this.viewbox.w / 2, y: this.viewbox.h / 2 }
for(let entity of this.entities) {
let pos = {
x: center.x + (Math.cos(a * i - Math.PI * .5) * r) - (this.options.entity.width / 2),
y: center.y + (Math.sin(a * i - Math.PI * .5) * r) - (this.options.entity.height / 2)
}
entity.x = pos.x;
entity.y = pos.y;
for(let relation of entity.relations) relation.redraw();
i++;
}
}
getEntityById(id) { return this.entities.find(entity => entity.id == id); }
addEntity(entity) {
let index = this.entities.length;
let options = { ...entity, ...this.options.entity };
let item = new NodeMapNode(null, options);
item.x = this.options.entity.gap + (this.options.entity.width + this.options.entity.gap) * index;
item.y = 30;
item.el.addEventListener('selected', this.onEntitySelect.bind(this));
this.entities.push(item);
this.map.append(item.el);
}
removeEntity(entity) {
let parents = entity.relations.filter(r => r.target.id == entity.id);
let childs = entity.relations.filter(r => r.source.id == entity.id);
for(let relation of parents) {
let parent = relation.source;
parent.relations = parent.relations.filter(r => r.target.id != entity.id);
for(let relation of childs) {
let child = relation.target;
child.relations = child.relations.filter(r => r.source.id != entity.id);
this.addRelation({source: parent.id, target: child.id});
}
}
entity.relations.forEach(r => r.el.remove())
entity.el.remove();
this.entities = this.entities.filter(e => e.id != entity.id)
this.reorder();
}
addRelation(relation) {
let source = this.entities.find(entity => entity.id == relation.source);
let target = this.entities.find(entity => entity.id == relation.target);
let item = new NodeMapRelation(source, target);
source.relations.push(item);
target.relations.push(item);
this.map.prepend(item.el);
}
onEntityDragStart(event) {
event.stopPropagation();
event.preventDefault();
this.current = event.currentTarget
this.mouseStartPosition = { x: event.pageX, y: event.pageY }
this.entityStartPosition = { x: Number.parseFloat(this.current.getAttribute("x")), y: Number.parseFloat(this.current.getAttribute("y")) }
window.addEventListener("mouseup", this.entityUpHandler);
this.entityDrag = true;
}
onEntityMove(event) {
event.stopPropagation();
event.preventDefault();
this.mousePosition = { x: event.offsetX, y: event.offsetY };
if(this.entityDrag) {
this.current.setAttribute("x", this.entityStartPosition.x - (this.mouseStartPosition.x - event.pageX) * this.options.scale.current)
this.current.setAttribute("y", this.entityStartPosition.y - (this.mouseStartPosition.y - event.pageY) * this.options.scale.current)
let entity = this.entities.find(item => item.data.id == (this.options.entity.current.getAttribute("data-id")))
let pos = { x: Number.parseFloat(this.options.entity.current.getAttribute("x")), y: Number.parseFloat(this.options.entity.current.getAttribute("y")) }
/*
for(let line of entity.lines) {
if(line.getAttribute("data-source") == entity.data.id) {
line.setAttribute("x1", pos.x + (this.options.entity.width / 2));
line.setAttribute("y1", pos.y + (this.options.entity.height / 2));
} else {
line.setAttribute("x2", pos.x + (this.options.entity.width / 2));
line.setAttribute("y2", pos.y + (this.options.entity.height / 2));
}
}
*/
}
}
onEntityDragStop(event) {
event.stopPropagation();
event.preventDefault();
window.removeEventListener("mouseup", this.entityUpHandler);
this.options.entity.dragging = false;
}
onEntitySelect(event) {
let entity = event.detail;
this.entities.forEach(item => {
item.el.classList.remove('selected');
item.el.classList.remove('related');
//item.lines.forEach(line => line.classList.remove('related'))
});
entity.el.classList.add('selected');
/*
entity.lines.forEach(line => {
line.classList.add('related');
let other = this.entities.find(item => (item.data.id == line.getAttribute('data-target') && line.getAttribute('data-source') == selected.data.id) || (item.data.id == line.getAttribute('data-source') && line.getAttribute('data-target') == selected.data.id));
other.el.classList.add('related');
});
*/
this.click(entity);
}
onMapScroll(event) {
event.stopPropagation();
event.preventDefault();
let scale = 1. + (event.deltaY < 0 ? - this.options.scale.factor : this.options.scale.factor);
if((this.options.scale.current * scale < this.options.scale.max) && (this.options.scale.current * scale > this.options.scale.min))
{
let pos = {
x: (this.mousePosition.x * this.options.scale.current) + this.viewbox.x,
y: (this.mousePosition.y * this.options.scale.current) + this.viewbox.y
}
this.viewbox.x = (this.viewbox.x - pos.x) * scale + pos.x;
this.viewbox.y = (this.viewbox.y - pos.y) * scale + pos.y;
this.options.scale.current *= scale;
this.setViewbox();
}
}
onMapDragStart(event) {
this.mouseStartPosition = { x: event.pageX, y: event.pageY }
this.viewboxStartPosition = { x: this.viewbox.x, y: this.viewbox.y }
this.options.map.dragging = true;
window.addEventListener("mouseup", this.mouseUpHandler);
}
onMapDragMove(event) {
this.mousePosition = { x: event.offsetX, y: event.offsetY };
if(this.options.map.dragging) {
this.viewbox.x = this.viewboxStartPosition.x + (this.mouseStartPosition.x - event.pageX) * this.options.scale.current;
this.viewbox.y = this.viewboxStartPosition.y + (this.mouseStartPosition.y - event.pageY) * this.options.scale.current;
this.setViewbox();
}
}
onMapDragStop(event) {
window.removeEventListener("mouseup", this.mouseUpHandler);
this.options.map.dragging = false;
}
onMapResize(event) {
this.viewbox.w = this.find('.results').clientWidth;
//this.viewbox.w = this.map.viewBox.baseVal.width;
this.viewbox.h = this.find('.results').clientHeight;
//this.viewbox.h = this.map.viewBox.baseVal.height;
this.setViewbox();
}
setViewbox() { this.map.setAttribute("viewBox", this.viewbox.x + " " + this.viewbox.y + " " + (this.viewbox.w * this.options.scale.current) + " " + (this.viewbox.h * this.options.scale.current)); }
click() {}
}
/**
* @augments EicComponent
* @category EICUI/AdvancedComponents
*/
class NodeMapNode extends EicComponent {
/**
*
*/
relations = [];
/**
*
*/
id = "";
/**
*
*/
options = {
badge: {
size: 'large',
severity: 'danger'
}
}
constructor(el, options) {
super(el, options);
this.setOptions(options);
if(!el) {
this.el = ui.create(
`<svg class="entity" ${options.severity ? options.severity: '' } x="0" y="0" text-anchor="middle">
<rect class="bg" width="${options.width}" height="${options.height}" x="2" y="5" rx="4"></rect>
<text dominant-baseline="middle" text-anchor="middle" class="title" x="${options.width / 2}" y="${options.height / 3}">${options.title}</text>
<text dominant-baseline="middle" text-anchor="middle" class="subtitle" x="${options.width / 2}" y="${(options.height / 3) * 2}">${options.subtitle}</text>
</svg>`
);
if(options.badge) this.badge = options.badge;
}
if(options.data) {
for(let property in options.data) {
this.el.setAttribute('data-' + property, options.data[property]);
if(property == "id") this.id = options.data[property];
}
}
this.el.addEventListener('click', this.onClick.bind(this));
}
/**
*
*/
set x(value) { this.el.setAttribute('x', value); }
get x() { return parseInt(this.el.getAttribute('x')); }
/**
*
*/
set y(value) { this.el.setAttribute('y', value); }
get y() { return parseInt(this.el.getAttribute('y')); }
/**
*
*/
set disabled(state) { state ? this.el.setAttribute('disabled',''): this.el.removeAttribute('disabled'); }
get disabled() { return this.el.hasAttribute('disabled'); }
/**
*
*/
get width() { return parseInt(this.el.querySelector('rect').getAttribute('width')); }
get height() { return parseInt(this.el.querySelector('rect').getAttribute('height')); }
/**
*
*/
set title(str) { this.el.querySelector('.title').innerHTML = str; }
set subtitle(str) { this.el.querySelector('.subtitle').innerHTML = str; }
/**
*
*/
set badge(value) {
if(!this._badge && value != 0) {
let r = 14;
switch(this.options.badge.size) {
case 'xxsmall': r = 8; break;
case 'xsmall': r = 10; break;
case 'small': r = 12; break;
case 'large': r = 16; break;
case 'xlarge': r = 18; break;
case 'xxlarge': r = 20; break;
case 'xxxlarge': r = 24; break;
}
this._badge = ui.create(
`<svg class="badge" x="${this.options.width - r}" y="0" ${this.options.badge.size} ${this.options.badge.severity} text-anchor="middle">
<circle class="bg" cx="${r}" cy="${r}" r="${r}"></circle>
<text dominant-baseline="middle" text-anchor="middle" x="${r}" y="${r}"></text>
</svg>`
);
this.el.append(this._badge)
}
this._badge.querySelector('text').textContent = value;
}
/**
*
*/
set data(value) { }
onClick(event) {
event.stopPropagation();
event.preventDefault();
this.el.dispatchEvent(new CustomEvent('selected', {detail: this}))
}
}
/**
* @augments EicComponent
* @category EICUI/AdvancedComponents
*/
class NodeMapRelation extends EicComponent {
source = null;
target = null;
constructor(source, target, options) {
super(null, options);
this.setOptions(options);
this.source = source;
this.target = target;
//this.el = ui.create( `<line class="relation" x1="0" y1="0" x2="0" y2="0" data-source="${source.id}" data-target="${target.id}" />` );
this.el = document.createElementNS('http://www.w3.org/2000/svg','path');
this.el.setAttribute('class', 'relation');
this.el.dataset.source = source.id;
this.el.dataset.target = target.id;
this.redraw();
}
redraw() {
let o = {
x: this.source.x + this.source.width / 2,
y: this.source.y + this.source.height / 2
}
let d = {
x: o.x + Math.abs((this.target.y + this.target.height / 2) - o.y),
y: this.target.y + this.target.height / 2
}
let p1 = { x: o.x, y: d.y }
let f = {
x: this.target.x + this.target.width / 2,
y: this.target.y + this.target.height / 2
}
let path = `M ${o.x} ${o.y} C ${p1.x} ${p1.y} ${p1.x} ${p1.y} ${d.x} ${d.y} L ${f.x} ${f.y}`
this.el.setAttribute('d', path)
}
}
View File
+28
View File
@@ -0,0 +1,28 @@
class EicSVGElement {
constructor(options) {
}
}
class SVGBadge extends EicSVGElement {
_value = '';
constructor(options) {
}
get value() { return _value; }
set value(v) {
_value = v;
}
}
class SVGButton extends EicSVGElement {
constructor(options) {
}
}
class SVGChip extends EicSVGElement {}
@@ -0,0 +1,50 @@
class BinaryFileContentSelector extends SelectEditor {
_template = `<div class="eicui-select-editor cols-2">
<input eicinput type="file" data-type="ignore" placeholder="Select a file" />
</div>`;
file = {
label: '',
content: null,
type: ''
}
open() {
super.open();
this.input = this.components.find(c => c.el.nodeName == 'INPUT');
this.input.el.click();
ui.hide(this.el)
}
initEvents() {
this.el.querySelector('input').addEventListener('change', this.onFileChange.bind(this));
}
onFileChange(event) {
let file = this.input.el.files[0];
this.reader = new FileReader();
this.reader.onload = this.onFileLoaded.bind(this);
this.reader.readAsArrayBuffer(file);
this.file.label = file.name;
this.file.value = file.name;
this.file.type = file.type;
}
onFileLoaded() {
this.file.content = this.arrayBufferToBase64(this.reader.result);
this.commit(JSON.parse(JSON.stringify(this.file)));
}
get value() { return this.file }
arrayBufferToBase64( buffer ) {
let bytes = new Uint8Array( buffer );
let size = bytes.byteLength;
let binary = '';
for (let i = 0; i < size; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
}
File diff suppressed because one or more lines are too long
+9
View File
@@ -0,0 +1,9 @@
/*!
Theme: Default
Description: Original highlight.js style
Author: (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
Maintainer: @highlightjs/core-team
Website: https://highlightjs.org/
License: see project LICENSE
Touched: 2021
*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
+1
View File
@@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#272822;color:#ddd}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-selector-tag,.hljs-strong,.hljs-tag{color:#f92672}.hljs-code{color:#66d9ef}.hljs-attribute,.hljs-link,.hljs-regexp,.hljs-symbol{color:#bf79db}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-emphasis,.hljs-section,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-string,.hljs-subst,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type,.hljs-variable{color:#a6e22e}.hljs-class .hljs-title,.hljs-title.class_{color:#fff}.hljs-comment,.hljs-deletion,.hljs-meta,.hljs-quote{color:#75715e}.hljs-doctag,.hljs-keyword,.hljs-literal,.hljs-section,.hljs-selector-id,.hljs-selector-tag,.hljs-title,.hljs-type{font-weight:700}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long