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
+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);
}
}