decided in favor of dedicated COMPONENT() macro, updates to documentation
This commit is contained in:
+95
@@ -0,0 +1,95 @@
|
||||
.horizontal .progressbar-item {
|
||||
display: flex;
|
||||
min-width: 100%;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.progressbar-container.vertical {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.vertical .progressbar-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.progressbar-label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.horizontal .progressbar-label, .horizontal .progressbar-value {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.horizontal .progressbar-label {
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.vertical .progressbar-label {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.horizontal .progressbar-value {
|
||||
min-width: 50px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.progressbar-background {
|
||||
flex: 1;
|
||||
background: var(--bg-color);
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.vertical .progressbar-background {
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.progressbar-bar {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.progressbar-marker {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
opacity: 0.5;
|
||||
background: var(--text-color, #333);
|
||||
}
|
||||
|
||||
.progressbar-marker:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.horizontal .progressbar-marker {
|
||||
border-width: 0 2px 0 2px;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
min-width: 4px;
|
||||
}
|
||||
|
||||
.vertical .progressbar-marker {
|
||||
border-width: 2px 0 2px 0;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
min-height: 4px;
|
||||
}
|
||||
|
||||
.progressbar-background {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.needlegauge-svg .needle {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.needlegauge-item {
|
||||
display: inline-block;
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
// common code for gauges components
|
||||
|
||||
window.GaugeComponents = window.GaugeComponents || {};
|
||||
|
||||
Object.assign(window.GaugeComponents, { // as a namespace
|
||||
|
||||
// utility functions
|
||||
clampValue: function(value, min, max) {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
},
|
||||
|
||||
pickEntryFromRange: function(ranges, value) {
|
||||
if (!Array.isArray(ranges)) return null;
|
||||
for (const entry of ranges) {
|
||||
if (value >= entry.from && value <= entry.to) return entry;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates an SVG element with namespace
|
||||
*/
|
||||
createSVGElement: function(tagName, attributes = {}) {
|
||||
const element = document.createElementNS('http://www.w3.org/2000/svg', tagName);
|
||||
Object.entries(attributes).forEach(([key, value]) => {
|
||||
element.setAttribute(key, value);
|
||||
});
|
||||
return element;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets CSS custom property value from computed styles
|
||||
*/
|
||||
getCSSVar: function(varName) {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
|
||||
},
|
||||
|
||||
resolveColor: function(colorSpec, value, pct) {
|
||||
if (Array.isArray(colorSpec)) {
|
||||
const match = this.pickEntryFromRange(colorSpec, value);
|
||||
if (match && match.color) return match.color;
|
||||
}
|
||||
if (typeof colorSpec === 'string' && colorSpec !== '') return colorSpec;
|
||||
if (pct < 60) return this.getCSSVar('--success') || '#10b981';
|
||||
if (pct < 85) return this.getCSSVar('--warning') || '#f59e0b';
|
||||
return this.getCSSVar('--error') || '#ef4444';
|
||||
},
|
||||
|
||||
formatValue: function(value, precision, suffix) {
|
||||
const numericValue = Number(value);
|
||||
const normalizedPrecision = Number.isFinite(precision) ? precision : 1;
|
||||
if (!Number.isFinite(numericValue)) return '--';
|
||||
return numericValue.toFixed(normalizedPrecision) + (suffix || '');
|
||||
},
|
||||
|
||||
gaugeArcPoint: function(pct, radius = 50) {
|
||||
const angle = Math.PI - (pct / 100) * Math.PI;
|
||||
return {
|
||||
x: 60 + radius * Math.cos(angle),
|
||||
y: 60 - radius * Math.sin(angle),
|
||||
};
|
||||
},
|
||||
|
||||
updateWatermark: function(prefix, pct) {
|
||||
const now = Date.now();
|
||||
this._watermarks = this._watermarks || {};
|
||||
let watermark = this._watermarks[prefix];
|
||||
const resetWindow = 10 * 60 * 1000;
|
||||
if (!watermark || (now - watermark.resetTs) > resetWindow) {
|
||||
watermark = { lo: pct, hi: pct, resetTs: now };
|
||||
this._watermarks[prefix] = watermark;
|
||||
} else {
|
||||
if (pct < watermark.lo) watermark.lo = pct;
|
||||
if (pct > watermark.hi) watermark.hi = pct;
|
||||
}
|
||||
return watermark;
|
||||
},
|
||||
|
||||
renderWatermarkTick: function(lineId, pct) {
|
||||
const line = document.getElementById(lineId);
|
||||
if (!line) return;
|
||||
if (pct == null) {
|
||||
line.setAttribute('opacity', '0');
|
||||
return;
|
||||
}
|
||||
const outer = this.gaugeArcPoint(pct, 53);
|
||||
const inner = this.gaugeArcPoint(pct, 43);
|
||||
line.setAttribute('x1', outer.x.toFixed(1));
|
||||
line.setAttribute('y1', outer.y.toFixed(1));
|
||||
line.setAttribute('x2', inner.x.toFixed(1));
|
||||
line.setAttribute('y2', inner.y.toFixed(1));
|
||||
line.setAttribute('opacity', '0.7');
|
||||
},
|
||||
|
||||
updateArcGauge: function(options) {
|
||||
const value = Number(options.value);
|
||||
const max = Number(options.max || 100);
|
||||
const normalizedValue = Number.isFinite(value) ? value : 0;
|
||||
const pct = this.clampValue(max === 0 ? 0 : (normalizedValue / max) * 100, 0, 100);
|
||||
const arcLength = (pct / 100) * 157.08;
|
||||
const arc = document.getElementById(options.arcId);
|
||||
const text = document.getElementById(options.textId);
|
||||
const meta = options.metaId ? document.getElementById(options.metaId) : null;
|
||||
if (arc) {
|
||||
arc.setAttribute('stroke-dasharray', arcLength.toFixed(1) + ' 157.08');
|
||||
arc.setAttribute('stroke', this.resolveColor(options.color, normalizedValue, pct));
|
||||
}
|
||||
if (text) {
|
||||
text.textContent = this.formatValue(normalizedValue, options.precision, options.suffix);
|
||||
}
|
||||
if (meta && options.meta != null) {
|
||||
meta.textContent = options.meta;
|
||||
}
|
||||
if (options.watermarkPrefix) {
|
||||
const watermark = this.updateWatermark(options.watermarkPrefix, pct);
|
||||
this.renderWatermarkTick(options.watermarkPrefix + 'WmLo', watermark.lo);
|
||||
this.renderWatermarkTick(options.watermarkPrefix + 'WmHi', watermark.hi);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
#load "../../lib/app.uce"
|
||||
|
||||
RENDER:PROGRESSBAR(Request& context)
|
||||
COMPONENT:PROGRESSBAR(Request& context)
|
||||
{
|
||||
starter_register_js("components/gauges/common.js", context);
|
||||
starter_register_css("themes/common/css/gauges.css", context);
|
||||
@@ -96,13 +96,13 @@ RENDER:PROGRESSBAR(Request& context)
|
||||
});
|
||||
}
|
||||
};
|
||||
ProgressbarComponents.start_listen(<?= json_encode(context.call) ?>);
|
||||
ProgressbarComponents.start_listen(<?: json_encode(context.call) ?>);
|
||||
</script>
|
||||
<? } ?>
|
||||
</>
|
||||
}
|
||||
|
||||
RENDER:NEEDLEGAUGE(Request& context)
|
||||
COMPONENT:NEEDLEGAUGE(Request& context)
|
||||
{
|
||||
starter_register_css("themes/common/css/gauges.css", context);
|
||||
String gauge_id = first(context.call["id"].to_string(), "needlegauge-" + std::to_string((u64)time()));
|
||||
@@ -167,7 +167,7 @@ RENDER:NEEDLEGAUGE(Request& context)
|
||||
</>
|
||||
}
|
||||
|
||||
RENDER:ARCGAUGE(Request& context)
|
||||
COMPONENT:ARCGAUGE(Request& context)
|
||||
{
|
||||
starter_register_js("components/gauges/common.js", context);
|
||||
starter_register_css("themes/common/css/gauges.css", context);
|
||||
|
||||
Reference in New Issue
Block a user