trying to port web app starter from PHP

This commit is contained in:
udo
2026-04-19 09:38:23 +00:00
parent 46d98a092f
commit be514d63d6
546 changed files with 76910 additions and 2807 deletions
@@ -0,0 +1,105 @@
<script>
window.ArcgaugeComponents = window.ArcgaugeComponents || {
start_listen : function(prop) {
$.events.on('value-broadcast', function(data) {
if(!prop.items || !prop.items[data.name]) return;
const item = Object.assign({}, prop.scale || {}, prop.items[data.name]);
GaugeComponents.updateArcGauge({
arcId: prop.id + '-' + data.name + '-arc',
textId: prop.id + '-' + data.name + '-text',
metaId: prop.id + '-' + data.name + '-meta',
value: Number(data.value),
max: Number(item.max || 100),
suffix: item.unit || '',
precision: item.precision,
watermarkPrefix: item.watermark_prefix || data.name,
color: item.color,
meta: data.meta != null ? data.meta : null,
});
});
}
};
</script><?php
include_js('components/gauges/common.js');
include_css('themes/common/css/gauges.css');
return [
'render' => function($prop) {
$prop['id'] = !empty($prop['id']) ? $prop['id'] : 'arcgauge-'.uniqid();
$prop['scale'] = $prop['scale'] ?? array();
$prop['items'] = $prop['items'] ?? array();
?>
<div class="arcgauge-set" id="<?= asafe($prop['id']) ?>" style="<?= asafe((string)($prop['style'] ?? '')) ?>">
<?php if(!empty($prop['title'])) { ?>
<div class="arcgauge-set-header">
<h3><?= safe((string)$prop['title']) ?></h3>
<?php if(!empty($prop['subtitle'])) { ?><p><?= safe((string)$prop['subtitle']) ?></p><?php } ?>
</div>
<?php } ?>
<div class="arcgauge-grid">
<?php foreach($prop['items'] as $item_id => $item) {
$item = array_merge($prop['scale'], $item);
$value = (float)first($item['value'], 0);
$max = (float)first($item['max'], 100);
$precision = isset($item['precision']) ? (int)$item['precision'] : 1;
$pct = $max > 0 ? clamp(($value / $max) * 100, 0, 100) : 0;
$arc_length = round(($pct / 100) * 157.08, 1);
$display_value = number_format($value, $precision).safe((string)($item['unit'] ?? ''));
$resolved_color = '#10b981';
if(isset($item['color']))
{
if(is_array($item['color']))
{
$color_entry = pick_entry_from_range($item['color'], $value);
$resolved_color = first($color_entry['color'] ?? false, $resolved_color);
}
else
{
$resolved_color = (string)$item['color'];
}
}
else if($pct >= 85)
{
$resolved_color = 'var(--error, #ef4444)';
}
else if($pct >= 60)
{
$resolved_color = 'var(--warning, #f59e0b)';
}
else
{
$resolved_color = 'var(--success, #10b981)';
}
?>
<section class="arcgauge-card">
<div class="arcgauge-label"><?= safe((string)first($item['label'], ucfirst((string)$item_id))) ?></div>
<svg class="arcgauge-svg" viewBox="0 0 120 68" aria-hidden="true">
<path class="arcgauge-track" d="M 10 60 A 50 50 0 0 1 110 60" fill="none" stroke-width="5" stroke-linecap="round"/>
<path id="<?= asafe($prop['id']) ?>-<?= asafe($item_id) ?>-arc" class="arcgauge-arc-dyn" d="M 10 60 A 50 50 0 0 1 110 60" fill="none" stroke="<?= asafe($resolved_color) ?>" stroke-width="5" stroke-linecap="round" stroke-dasharray="<?= asafe((string)$arc_length) ?> 157.08"/>
<?php if(!empty($item['watermark_prefix'])) { ?>
<line id="<?= asafe($item['watermark_prefix']) ?>WmLo" class="arcgauge-watermark arcgauge-watermark-lo" x1="0" y1="0" x2="0" y2="0" opacity="0"/>
<line id="<?= asafe($item['watermark_prefix']) ?>WmHi" class="arcgauge-watermark arcgauge-watermark-hi" x1="0" y1="0" x2="0" y2="0" opacity="0"/>
<?php } ?>
<text id="<?= asafe($prop['id']) ?>-<?= asafe($item_id) ?>-text" class="arcgauge-value" x="60" y="47" text-anchor="middle"><?= safe($display_value) ?></text>
<text class="arcgauge-caption" x="60" y="62" text-anchor="middle"><?= safe((string)first($item['caption'], '')) ?></text>
</svg>
<div class="arcgauge-meta" id="<?= asafe($prop['id']) ?>-<?= asafe($item_id) ?>-meta"><?= safe((string)first($item['meta'], '--')) ?></div>
</section>
<?php } ?>
</div>
</div>
<?php
if(!empty($prop['listen']))
{
?><script>
ArcgaugeComponents.start_listen(<?= jsafe($prop) ?>);
</script><?php
}
}
];
+95
View File
@@ -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
View File
@@ -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);
}
}
});
@@ -0,0 +1,173 @@
<script>
window.NeedlegaugeComponents = window.NeedlegaugeComponents || {
start_listen : function(prop) {
$.events.on('value-broadcast', function(data) {
if(prop.items[data.name]) {
// update value text
let item = Object.assign({}, prop.scale, prop.items[data.name]);
$('#' + prop.id + '-' + data.name + '-value').text(data.value + (item.unit || ''));
let vrange = (item.max || 100) - (item.min || 0);
let pct_value = GaugeComponents.clampValue((data.value - (item.min || 0)) / vrange, 0, 1);
let angle = -Math.PI + item.angle_start + (pct_value * (item.angle_end - item.angle_start));
$('#' + prop.id + '-' + data.name + '-needle').css('transform', 'rotate(' + angle + 'rad)');
}
});
},
}
</script><?php
include_js('components/gauges/common.js');
include_css('themes/common/css/gauges.css');
return [
'render' => function($prop) {
$prop['id'] = !empty($prop['id']) ? $prop['id'] : 'needlegauge-' . uniqid();
$prop['style'] = (string)($prop['style'] ?? '');
$prop['items'] = (array)($prop['items'] ?? array());
$prop['scale'] = (array)($prop['scale'] ?? array());
$prop['size'] = first($prop['size'] ?? false, 200);
$prop['subtitle'] = (string)($prop['subtitle'] ?? '');
$prop['scale']['angle_start'] = first($prop['scale']['angle_start'], -pi());
$prop['scale']['angle_end'] = first($prop['scale']['angle_end'], 0);
$prop['img_height'] = first($prop['img_height'] ?? false,
$prop['size'] * max(cos(first($prop['scale']['angle_start'], -pi())), cos(first($prop['scale']['angle_end'], 0))));
?>
<section class="gauge-set needlegauge-set" id="<?= asafe($prop['id']) ?>" style="<?= asafe($prop['style']) ?>">
<?php if(isset($prop['title'])) { ?>
<div class="gauge-set-header">
<h3><?= safe($prop['title']) ?></h3>
<?php if($prop['subtitle'] !== '') { ?><p><?= safe($prop['subtitle']) ?></p><?php } ?>
</div>
<?php } ?>
<div class="needlegauge-grid">
<?php
foreach($prop['items'] as $item_id => $item)
{
$item = array_merge($prop['scale'], $item);
$item['min'] = first($item['min'] ?? false, 0);
$item['max'] = first($item['max'] ?? false, 100);
$item['tooltip'] = (string)($item['tooltip'] ?? '');
$item['unit'] = (string)($item['unit'] ?? '');
$item['color'] = $item['color'] ?? false;
$item['label'] = (string)first($item['label'] ?? false, ucfirst((string)$item_id));
$vrange = ($item['max'] - $item['min']);
$pct_value = clamp(($item['value'] - $item['min']) / $vrange, 0, 1);
$needle_angle = -pi() + $item['angle_start'] + ($pct_value * ($item['angle_end'] - $item['angle_start']));
$needle_color = first($item['color'] ?? false, '#888888');
if(is_array($item['color'])) {
$color_entry = pick_entry_from_range($item['color'], $item['value']);
$needle_color = first($color_entry['color'] ?? false, '#888888');
}
$tick_interval = first($item['ticks-every'], $vrange / 20);
$label_interval = first($item['value-labels-every'], $vrange / 4);
$tick_number = 0;
$ticks_html = '';
$min_angle = first($item['angle_start']);
$max_angle = first($item['angle_end']);
$tick_color = first($item['tick-color'], '#888888');
for($v = $item['min']; $v <= $item['max']; $v += $tick_interval)
{
$tick_number++;
$angle = $min_angle + (($v-$item['min'])/$vrange) * ($max_angle - $min_angle);
$x1 = $prop['size']/2 + cos($angle) * $prop['size']*0.38;
$y1 = $prop['size']/2 + sin($angle) * $prop['size']*0.38;
$x2 = $prop['size']/2 + cos($angle) * $prop['size']*0.41;
$y2 = $prop['size']/2 + sin($angle) * $prop['size']*0.41;
$ticks_html .= '<line x1="'.($x1).'" y1="'.($y1).'" x2="'.($x2).'" y2="'.($y2).'"
stroke="'. $tick_color .'" stroke-width="1"/>';
}
for($v = $item['min']; $v <= $item['max']; $v += $label_interval)
{
$tick_number++;
$angle = $min_angle + (($v-$item['min'])/$vrange) * ($max_angle - $min_angle);
$x1 = $prop['size']/2 + cos($angle) * $prop['size']*0.35;
$y1 = $prop['size']/2 + sin($angle) * $prop['size']*0.35;
$x2 = $prop['size']/2 + cos($angle) * $prop['size']*0.41;
$y2 = $prop['size']/2 + sin($angle) * $prop['size']*0.41;
$ticks_html .= '<line x1="'.($x1).'" y1="'.($y1).'" x2="'.($x2).'" y2="'.($y2).'"
stroke="'. $tick_color .'" stroke-width="3"/>';
$lx = $prop['size']/2 + cos($angle) * $prop['size']*0.45;
$ly = $prop['size']/2 + sin($angle) * $prop['size']*0.45;
$ticks_html .= '<text x="'.($lx).'" y="'.($ly).'" text-anchor="middle" dominant-baseline="central"
font-size="10" fill="'. $tick_color .'">'.($v == 0 ? '0' : $v).'</text>';
}
?>
<section class="gauge-card needlegauge-card">
<div class="gauge-metric-label needlegauge-label-head"><?= safe($item['label']) ?></div>
<div class="needlegauge-visual">
<svg id="<?= $prop['id'] ?>-<?= $item_id ?>-svg" width="<?= $prop['size'] ?>" height="<?= $prop['img_height'] ?>" class="needlegauge-svg"
viewBox="0 0 <?= $prop['size'] ?> <?= $prop['img_height'] ?>">
<?php
if(is_array($item['color']))
{
foreach($item['color'] as $range)
{
$angle_from = $min_angle + ((max($range['from'], $item['min']) - $item['min']) / $vrange) * ($max_angle - $min_angle);
$angle_to = $min_angle + ((min($range['to'], $item['max']) - $item['min']) / $vrange) * ($max_angle - $min_angle);
$color = $range['color'] ?? 'rgba(120,120,120,0.5)';
SVG::circle_segment($prop['size']/2, $prop['size']/2, $prop['size']*0.4,
$angle_from, $angle_to, $color, 8, 'rgba(0,0,0,0)', 'opacity:0.25');
}
}
else
{
SVG::circle_segment($prop['size']/2, $prop['size']/2, $prop['size']*0.4,
$min_angle, $max_angle, $tick_color, 8, 'rgba(0,0,0,0)', 'opacity:0.25');
}
?>
<g class="ticks">
<?= $ticks_html ?>
</g>
<line id="<?= $prop['id'] ?>-<?= $item_id ?>-needle" class="needle"
x1="<?= $prop['size']*0.55 ?>" y1="<?= $prop['size']*0.5 ?>" x2="<?= $prop['size']*0.1 ?>" y2="<?= $prop['size']*0.5 ?>"
stroke="<?= ($needle_color) ?>" stroke-width="3" stroke-linecap="round"
style="transform-origin: <?= $prop['size']/2 ?>px <?= $prop['size']/2 ?>px;
transform: rotate(<?= ($needle_angle) ?>rad); transition: transform 0.3s ease;"/>
<circle cx="<?= $prop['size']/2 ?>" cy="<?= $prop['size']/2 ?>" r="6" fill="<?= $needle_color ?>"/>
</svg>
</div>
<div class="needlegauge-info">
<div class="gauge-metric-value needlegauge-value" id="<?= $prop['id'] ?>-<?= $item_id ?>-value"
title="<?= asafe($item['tooltip']) ?>">
<?= safe($item['value']) ?><?= safe($item['unit']) ?>
</div>
<?php if($item['tooltip'] !== '') { ?><div class="gauge-metric-meta needlegauge-meta"><?= safe($item['tooltip']) ?></div><?php } ?>
</div>
</section>
<?php
}
?>
</div>
</section>
<?php
if(!empty($prop['listen']))
{
?><script>
NeedlegaugeComponents.start_listen(<?= jsafe($prop) ?>);
</script><?php
}
}
];
@@ -0,0 +1,203 @@
<script>
window.ProgressbarComponents = window.ProgressbarComponents || {
start_listen : function(prop) {
$.events.on('value-broadcast', function(data) {
if(prop.items[data.name]) {
// update value text
let bar = Object.assign({}, prop.scale, prop.items[data.name]);
$('#' + prop.id + '-' + data.name + '-value').text(data.value + (bar.unit || ''));
// update bar width/height
let vrange = (bar.max || 100) - (bar.min || 0);
let pct_value = GaugeComponents.clampValue((data.value - (bar.min || 0)) / vrange * 100, 0, 100);
if(Array.isArray(bar.color)) {
let colorMatch = GaugeComponents.pickEntryFromRange(bar.color, Number(data.value));
if(colorMatch && colorMatch.color)
$('#' + prop.id + '-' + data.name + '-bar').css('background', colorMatch.color);
}
if(prop.layout === 'horizontal') {
$('#' + prop.id + '-' + data.name + '-bar').css('width', pct_value + '%');
} else {
$('#' + prop.id + '-' + data.name + '-bar').css('height', pct_value + '%');
}
}
});
},
}
</script><?php
include_js('components/gauges/common.js');
include_css('themes/common/css/gauges.css');
return [
'render' => function($prop) {
$prop['id'] = !empty($prop['id']) ? $prop['id'] : 'progressbar-'.uniqid();
$prop['style'] = (string)($prop['style'] ?? '');
$prop['item-style'] = (string)($prop['item-style'] ?? '');
$prop['label-style'] = (string)($prop['label-style'] ?? '');
$prop['value-style'] = (string)($prop['value-style'] ?? '');
$prop['bar-style'] = (string)($prop['bar-style'] ?? '');
$prop['items'] = (array)($prop['items'] ?? array());
$default_palette = [
'var(--success, #10b981)',
'var(--primary, #60a5fa)',
'var(--accent, #22d3ee)',
'var(--warning, #f59e0b)',
];
$auto_color_counter = 0;
if(empty($prop['scale'])) $prop['scale'] = [];
$layout = first($prop['layout'] ?? false, 'horizontal');
?>
<section class="gauge-set progressbar-set progressbar-set-<?= asafe($layout) ?>" id="<?= $prop['id'] ?>" style="<?= $prop['style'] ?>">
<?php if(isset($prop['title'])) { ?>
<div class="gauge-set-header">
<h3><?= safe($prop['title']) ?></h3>
<?php if(!empty($prop['subtitle'])) { ?><p><?= safe((string)$prop['subtitle']) ?></p><?php } ?>
</div>
<?php } ?>
<?php if($layout == 'horizontal') { ?>
<div class="progressbar-grid progressbar-grid-horizontal">
<?php foreach($prop['items'] as $bar_id => $bar)
{
$bar = array_merge($prop['scale'], $bar);
$bar['style'] = (string)($bar['style'] ?? '');
$bar['tooltip'] = (string)($bar['tooltip'] ?? '');
$bar['before'] = $bar['before'] ?? '';
$bar['after'] = $bar['after'] ?? '';
$bar['unit'] = (string)($bar['unit'] ?? '');
$bar['color'] = $bar['color'] ?? false;
$vrange = (first($bar['max'], 100) - first($bar['min'], 0));
$bar['pct-value'] = clamp(($bar['value'] - first($bar['min'], 0)) / $vrange * 100, 0, 100);
if(is_array($bar['color']))
{
$color_entry = pick_entry_from_range($bar['color'], $bar['value']);
$bar['color'] = $color_entry['color'] ?? false;
}
if(!$bar['color'])
$bar['color'] = first($prop['bar-color'] ?? false, $default_palette[$auto_color_counter++ % sizeof($default_palette)]);
?>
<section class="gauge-card progressbar-card progressbar-card-horizontal" id="<?= $prop['id'] ?>-<?= safe($bar_id) ?>"
title="<?= asafe($bar['tooltip']) ?>"
style="<?= $prop['item-style'] ?>;<?= $bar['style'] ?>">
<?= $bar['before'] ?>
<div class="progressbar-card-head">
<div class="gauge-metric-label progressbar-label" style="<?= $prop['label-style'] ?>">
<?= safe($bar['label']) ?>
</div>
<div class="gauge-metric-value progressbar-value" style="<?= $prop['value-style'] ?>"
id="<?= $prop['id'] ?>-<?= safe($bar_id) ?>-value">
<?= safe($bar['value']) ?><?= safe($bar['unit']) ?>
</div>
</div>
<div class="progressbar-background progressbar-background-horizontal">
<div class="progressbar-bar" id="<?= $prop['id'] ?>-<?= safe($bar_id) ?>-bar"
style="background-color: <?= safe($bar['color']) ?>;
<?= $prop['bar-style'] ?>
opacity: <?= isset($bar['opacity']) ? safe($bar['opacity']) : '0.75' ?>;
width: <?= safe($bar['pct-value']) ?>%;">
</div>
<?php
if(isset($prop['markers'])) {
foreach($prop['markers'] as $marker_id => $marker) {
$marker_pct = clamp(($marker['value'] - first($bar['min'], 0)) / $vrange * 100, 0, 100);
?>
<div class="progressbar-marker"
title="<?= asafe($marker['label']) ?>"
style="left: <?= ($marker_pct) ?>%;
background: <?= first($marker['color'] ?? false, 'var(--primary-light)') ?>;">
</div>
<?php
}
}
?>
</div>
<?php if($bar['tooltip'] !== '') { ?><div class="gauge-metric-meta progressbar-meta"><?= safe($bar['tooltip']) ?></div><?php } ?>
<?= $bar['after'] ?>
</section>
<?php } ?>
</div>
<?php } else { ?>
<div class="progressbar-grid progressbar-grid-vertical" style="--progressbar-height: <?= safe((string)first($prop['height'] ?? false, 240)) ?>px;">
<?php
if(sizeof($prop['items']) > 0)
{
foreach($prop['items'] as $bar_id => $bar)
{
$bar = array_merge($prop['scale'], $bar);
$bar['style'] = (string)($bar['style'] ?? '');
$bar['tooltip'] = (string)($bar['tooltip'] ?? '');
$bar['before'] = $bar['before'] ?? '';
$bar['after'] = $bar['after'] ?? '';
$bar['unit'] = (string)($bar['unit'] ?? '');
$bar['color'] = $bar['color'] ?? false;
$vrange = (first($bar['max'], 100) - first($bar['min'], 0));
$bar['pct-value'] = clamp(($bar['value'] - first($bar['min'], 0)) / $vrange * 100, 0, 100);
if(is_array($bar['color']))
{
$color_entry = pick_entry_from_range($bar['color'], $bar['value']);
$bar['color'] = $color_entry['color'] ?? false;
}
if(!$bar['color'])
$bar['color'] = first($prop['bar-color'] ?? false, $default_palette[$auto_color_counter++ % sizeof($default_palette)]);
?>
<section class="gauge-card progressbar-card progressbar-card-vertical" id="<?= $prop['id'] ?>-<?= safe($bar_id) ?>"
title="<?= asafe($bar['tooltip']) ?>"
style="<?= $prop['item-style'] ?>;<?= $bar['style'] ?>">
<?= $bar['before'] ?>
<div class="gauge-metric-label progressbar-label" style="<?= $prop['label-style'] ?>">
<?= safe($bar['label']) ?>
</div>
<div class="progressbar-background progressbar-background-vertical">
<div class="progressbar-bar" id="<?= $prop['id'] ?>-<?= safe($bar_id) ?>-bar"
style="background-color: <?= safe($bar['color']) ?>;
<?= $prop['bar-style'] ?>
opacity: <?= isset($bar['opacity']) ? safe($bar['opacity']) : '0.75' ?>;
height: <?= safe($bar['pct-value']) ?>%;">
</div>
<?php
// Render markers for vertical layout
if(isset($prop['markers'])) {
foreach($prop['markers'] as $marker_id => $marker) {
$marker_pct = clamp(($marker['value'] - first($bar['min'], 0)) / $vrange * 100, 0, 100);
?>
<div class="progressbar-marker"
title="<?= asafe($marker['label']) ?>"
style="bottom: <?= ($marker_pct) ?>%;
background: <?= first($marker['color'] ?? false, 'var(--primary-light)') ?>;">
</div>
<?php
}
}
?>
</div>
<div class="gauge-metric-value progressbar-value" style="<?= $prop['value-style'] ?>"
id="<?= $prop['id'] ?>-<?= safe($bar_id) ?>-value">
<?= safe($bar['value']) ?><?= safe($bar['unit']) ?>
</div>
<?php if($bar['tooltip'] !== '') { ?><div class="gauge-metric-meta progressbar-meta"><?= safe($bar['tooltip']) ?></div><?php } ?>
<?= $bar['after'] ?>
</section>
<?php
}
}
?>
</div>
<?php } ?>
</section>
<?php
if(!empty($prop['listen']))
{
?><script>
ProgressbarComponents.start_listen(<?= jsafe($prop) ?>);
</script><?php
}
}
];