Update component header, format, streamline SV5 components

This commit is contained in:
Christoph Kluge
2025-07-02 18:43:25 +02:00
parent dd48f5ab87
commit 60ec7e54f5
76 changed files with 2070 additions and 1988 deletions

View File

@@ -1,259 +1,257 @@
<!--
Copyright (c) 2021 Michael Keller
Originally created by Michael Keller (https://github.com/mhkeller/svelte-double-range-slider)
Changes: remove dependency, text inputs, configurable value ranges, on:change event
Changes #2: Rewritten for Svelte 5, removed bodyHandler
Copyright (c) 2021 Michael Keller
Originally created by Michael Keller (https://github.com/mhkeller/svelte-double-range-slider)
Changes: remove dependency, text inputs, configurable value ranges, on:change event
Changes #2: Rewritten for Svelte 5, removed bodyHandler
-->
<!--
@component Selector component to display range selections via min and max double-sliders
Properties:
- min: Number
- max: Number
- sliderHandleFrom: Number (Starting position of slider #1)
- sliderHandleTo: Number (Starting position of slider #2)
Events:
- `change`: [Number, Number] (Positions of the two sliders)
<!--
@component Selector component to display range selections via min and max double-sliders
Properties:
- `sliderMin Number!`: Minimum possible value for slider
- `sliderMax Number!`: Maximum possible value for slider
- `fromPreset Number?`: Latest "from" value selection [Default: 1]
- `toPreset Number?`: Latest "to" value selection [Default: 100]
- `changeRange Func`: The callback function to apply current range selections
-->
<script>
/* Svelte 5 Props */
let {
sliderMin,
sliderMax,
fromPreset = 1,
toPreset = 100,
changeRange
} = $props();
/* Svelte 5 Props */
let {
sliderMin,
sliderMax,
fromPreset = 1,
toPreset = 100,
changeRange
} = $props();
/* State Init */
let pendingValues = $state([fromPreset, toPreset]);
let sliderFrom = $state(Math.max(((fromPreset == null ? sliderMin : fromPreset) - sliderMin) / (sliderMax - sliderMin), 0.));
let sliderTo = $state(Math.min(((toPreset == null ? sliderMin : toPreset) - sliderMin) / (sliderMax - sliderMin), 1.));
let inputFieldFrom = $state(fromPreset.toString());
let inputFieldTo = $state(toPreset.toString());
let leftHandle = $state();
let sliderMain = $state();
/* State Init */
let pendingValues = $state([fromPreset, toPreset]);
let sliderFrom = $state(Math.max(((fromPreset == null ? sliderMin : fromPreset) - sliderMin) / (sliderMax - sliderMin), 0.));
let sliderTo = $state(Math.min(((toPreset == null ? sliderMin : toPreset) - sliderMin) / (sliderMax - sliderMin), 1.));
let inputFieldFrom = $state(fromPreset.toString());
let inputFieldTo = $state(toPreset.toString());
let leftHandle = $state();
let sliderMain = $state();
/* Var Init */
let timeoutId = null;
/* Var Init */
let timeoutId = null;
/* Functions */
function queueChangeEvent() {
if (timeoutId !== null) {
clearTimeout(timeoutId)
}
timeoutId = setTimeout(() => {
timeoutId = null
changeRange(pendingValues);
}, 100);
}
/* Functions */
function queueChangeEvent() {
if (timeoutId !== null) {
clearTimeout(timeoutId)
}
timeoutId = setTimeout(() => {
timeoutId = null
changeRange(pendingValues);
}, 100);
}
function updateStates(newValue, newPosition, target) {
if (target === 'from') {
pendingValues[0] = isNaN(newValue) ? null : newValue;
inputFieldFrom = isNaN(newValue) ? null : newValue.toString();
sliderFrom = newPosition;
} else if (target === 'to') {
pendingValues[1] = isNaN(newValue) ? null : newValue;
inputFieldTo = isNaN(newValue) ? null : newValue.toString();
sliderTo = newPosition;
}
function updateStates(newValue, newPosition, target) {
if (target === 'from') {
pendingValues[0] = isNaN(newValue) ? null : newValue;
inputFieldFrom = isNaN(newValue) ? null : newValue.toString();
sliderFrom = newPosition;
} else if (target === 'to') {
pendingValues[1] = isNaN(newValue) ? null : newValue;
inputFieldTo = isNaN(newValue) ? null : newValue.toString();
sliderTo = newPosition;
}
queueChangeEvent();
}
queueChangeEvent();
}
function rangeChanged (evt, target) {
evt.preventDefault()
evt.stopPropagation()
const { left, right } = sliderMain.getBoundingClientRect();
const parentWidth = right - left;
const newP = Math.min(Math.max((evt.detail.x - left) / parentWidth, 0), 1);
const newV = Math.floor(sliderMin + newP * (sliderMax - sliderMin));
updateStates(newV, newP, target);
}
function rangeChanged (evt, target) {
evt.preventDefault()
evt.stopPropagation()
const { left, right } = sliderMain.getBoundingClientRect();
const parentWidth = right - left;
const newP = Math.min(Math.max((evt.detail.x - left) / parentWidth, 0), 1);
const newV = Math.floor(sliderMin + newP * (sliderMax - sliderMin));
updateStates(newV, newP, target);
}
function inputChanged(evt, target) {
evt.preventDefault()
evt.stopPropagation()
const newV = Number.parseInt(evt.target.value);
const newP = clamp((newV - sliderMin) / (sliderMax - sliderMin), 0., 1.)
updateStates(newV, newP, target);
}
function inputChanged(evt, target) {
evt.preventDefault()
evt.stopPropagation()
const newV = Number.parseInt(evt.target.value);
const newP = clamp((newV - sliderMin) / (sliderMax - sliderMin), 0., 1.)
updateStates(newV, newP, target);
}
function clamp(x, testMin, testMax) {
return x < testMin
? testMin
: (x > testMax
? testMax
: x
);
}
function clamp(x, testMin, testMax) {
return x < testMin
? testMin
: (x > testMax
? testMax
: x
);
}
function draggable(node) {
let x;
let y;
function draggable(node) {
let x;
let y;
function handleMousedown(event) {
if (event.type === 'touchstart') {
event = event.touches[0];
}
x = event.clientX;
y = event.clientY;
function handleMousedown(event) {
if (event.type === 'touchstart') {
event = event.touches[0];
}
x = event.clientX;
y = event.clientY;
node.dispatchEvent(new CustomEvent('dragstart', {
detail: { x, y }
}));
node.dispatchEvent(new CustomEvent('dragstart', {
detail: { x, y }
}));
window.addEventListener('mousemove', handleMousemove);
window.addEventListener('mouseup', handleMouseup);
window.addEventListener('mousemove', handleMousemove);
window.addEventListener('mouseup', handleMouseup);
window.addEventListener('touchmove', handleMousemove);
window.addEventListener('touchend', handleMouseup);
}
window.addEventListener('touchmove', handleMousemove);
window.addEventListener('touchend', handleMouseup);
}
function handleMousemove(event) {
if (event.type === 'touchmove') {
event = event.changedTouches[0];
}
function handleMousemove(event) {
if (event.type === 'touchmove') {
event = event.changedTouches[0];
}
const dx = event.clientX - x;
const dy = event.clientY - y;
const dx = event.clientX - x;
const dy = event.clientY - y;
x = event.clientX;
y = event.clientY;
x = event.clientX;
y = event.clientY;
node.dispatchEvent(new CustomEvent('dragmove', {
detail: { x, y, dx, dy }
}));
}
node.dispatchEvent(new CustomEvent('dragmove', {
detail: { x, y, dx, dy }
}));
}
function handleMouseup(event) {
x = event.clientX;
y = event.clientY;
function handleMouseup(event) {
x = event.clientX;
y = event.clientY;
node.dispatchEvent(new CustomEvent('dragend', {
detail: { x, y }
}));
node.dispatchEvent(new CustomEvent('dragend', {
detail: { x, y }
}));
window.removeEventListener('mousemove', handleMousemove);
window.removeEventListener('mouseup', handleMouseup);
window.removeEventListener('mousemove', handleMousemove);
window.removeEventListener('mouseup', handleMouseup);
window.removeEventListener('touchmove', handleMousemove);
window.removeEventListener('touchend', handleMouseup);
}
window.removeEventListener('touchmove', handleMousemove);
window.removeEventListener('touchend', handleMouseup);
}
node.addEventListener('mousedown', handleMousedown);
node.addEventListener('touchstart', handleMousedown);
return {
destroy() {
node.removeEventListener('mousedown', handleMousedown);
node.removeEventListener('touchstart', handleMousedown);
}
};
}
node.addEventListener('mousedown', handleMousedown);
node.addEventListener('touchstart', handleMousedown);
return {
destroy() {
node.removeEventListener('mousedown', handleMousedown);
node.removeEventListener('touchstart', handleMousedown);
}
};
}
</script>
<div class="double-range-container">
<div class="header">
<input class="form-control" type="text" placeholder="from..." value={inputFieldFrom}
oninput={(e) => inputChanged(e, 'from')} />
<div class="header">
<input class="form-control" type="text" placeholder="from..." value={inputFieldFrom}
oninput={(e) => inputChanged(e, 'from')} />
<span>Full Range: <b> {sliderMin} </b> - <b> {sliderMax} </b></span>
<span>Full Range: <b> {sliderMin} </b> - <b> {sliderMax} </b></span>
<input class="form-control" type="text" placeholder="to..." value={inputFieldTo}
oninput={(e) => inputChanged(e, 'to')} />
</div>
<input class="form-control" type="text" placeholder="to..." value={inputFieldTo}
oninput={(e) => inputChanged(e, 'to')} />
</div>
<div id="slider-active" class="slider" bind:this={sliderMain}>
<div
class="slider-body"
style="left: {100 * sliderFrom}%;right: {100 * (1 - sliderTo)}%;"
></div>
<div
class="slider-handle"
bind:this={leftHandle}
data-which="from"
use:draggable
ondragmove={(e) => rangeChanged(e, 'from')}
style="left: {100 * sliderFrom}%"
></div>
<div
class="slider-handle"
data-which="to"
use:draggable
ondragmove={(e) => rangeChanged(e, 'to')}
style="left: {100 * sliderTo}%"
></div>
</div>
<div id="slider-active" class="slider" bind:this={sliderMain}>
<div
class="slider-body"
style="left: {100 * sliderFrom}%;right: {100 * (1 - sliderTo)}%;"
></div>
<div
class="slider-handle"
bind:this={leftHandle}
data-which="from"
use:draggable
ondragmove={(e) => rangeChanged(e, 'from')}
style="left: {100 * sliderFrom}%"
></div>
<div
class="slider-handle"
data-which="to"
use:draggable
ondragmove={(e) => rangeChanged(e, 'to')}
style="left: {100 * sliderTo}%"
></div>
</div>
</div>
<style>
.header {
width: 100%;
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 5px;
}
.header :nth-child(2) {
padding-top: 10px;
}
.header input {
height: 25px;
border-radius: 5px;
width: 100px;
}
.header {
width: 100%;
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 5px;
}
.header :nth-child(2) {
padding-top: 10px;
}
.header input {
height: 25px;
border-radius: 5px;
width: 100px;
}
.double-range-container {
width: 100%;
height: 50px;
user-select: none;
box-sizing: border-box;
white-space: nowrap;
margin-top: -4px;
}
.slider {
position: relative;
width: 100%;
height: 6px;
top: 10px;
transform: translate(0, -50%);
background-color: #e2e2e2;
box-shadow: inset 0 7px 10px -5px #4a4a4a, inset 0 -1px 0px 0px #9c9c9c;
border-radius: 6px;
}
.slider-handle {
position: absolute;
top: 50%;
width: 0;
height: 0;
}
.slider-handle:after {
content: ' ';
box-sizing: border-box;
position: absolute;
border-radius: 50%;
width: 16px;
height: 16px;
background-color: #fdfdfd;
border: 1px solid #7b7b7b;
transform: translate(-50%, -50%)
}
/* .handle[data-which="end"]:after{
transform: translate(-100%, -50%);
} */
.slider-handle:active:after {
background-color: #ddd;
z-index: 9;
}
.slider-body {
top: 0;
position: absolute;
background-color: #34a1ff;
bottom: 0;
}
.double-range-container {
width: 100%;
height: 50px;
user-select: none;
box-sizing: border-box;
white-space: nowrap;
margin-top: -4px;
}
.slider {
position: relative;
width: 100%;
height: 6px;
top: 10px;
transform: translate(0, -50%);
background-color: #e2e2e2;
box-shadow: inset 0 7px 10px -5px #4a4a4a, inset 0 -1px 0px 0px #9c9c9c;
border-radius: 6px;
}
.slider-handle {
position: absolute;
top: 50%;
width: 0;
height: 0;
}
.slider-handle:after {
content: ' ';
box-sizing: border-box;
position: absolute;
border-radius: 50%;
width: 16px;
height: 16px;
background-color: #fdfdfd;
border: 1px solid #7b7b7b;
transform: translate(-50%, -50%)
}
/* .handle[data-which="end"]:after{
transform: translate(-100%, -50%);
} */
.slider-handle:active:after {
background-color: #ddd;
z-index: 9;
}
.slider-body {
top: 0;
position: absolute;
background-color: #34a1ff;
bottom: 0;
}
</style>

View File

@@ -1,11 +1,12 @@
<!--
@component Selector component for (footprint) metrics to be displayed as histogram
@component Selector component for (footprint) metrics to be displayed as histogram
Properties:
- `cluster String`: Currently selected cluster
- `selectedHistograms [String]`: The currently selected metrics to display as histogram
- ìsOpen Bool`: Is selection opened
-->
Properties:
- `cluster String`: Currently selected cluster
- `selectedHistograms [String]`: The currently selected metrics to display as histogram
- `ìsOpen Bool`: Is selection opened [Bindable]
- `applyChange Func`: The callback function to apply current selection
-->
<script>
import { getContext } from "svelte";

View File

@@ -1,15 +1,18 @@
<!--
@component Metric selector component; allows reorder via drag and drop
@component Metric selector component; allows reorder via drag and drop
Properties:
- `metrics [String]`: (changes from inside, needs to be initialised, list of selected metrics)
- `isOpen Bool`: (can change from inside and outside)
- `configName String`: The config key for the last saved selection (constant)
- `allMetrics [String]?`: List of all available metrics [Default: null]
- `cluster String?`: The currently selected cluster [Default: null]
- `showFootprint Bool?`: Upstream state of wether to render footpritn card [Default: false]
- `footprintSelect Bool?`: Render checkbox for footprint display in upstream component [Default: false]
-->
Properties:
- `isOpen Bool`: Is selection modal opened [Bindable, Default: false]
- `showFootprint Bool?`: Upstream state of whether to render footprint card [Bindable, Default: false]
- `totalMetrics Number?`: Total available metrics [Bindable, Default: 0]
- `presetMetrics [String]`: Latest selection of metrics [Default: []]
- `cluster String?`: The currently selected cluster [Default: null]
- `subCluster String?`: The currently selected subCluster [Default: null]
- `footprintSelect Bool?`: Render checkbox for footprint display in upstream component [Default: false]
- `preInitialized Bool?`: If the parent component has a dedicated call to init() [Default: false]
- `configName String`: The config key for the last saved selection (constant)
- `applyMetrics Func`: The callback function to apply current selection
-->
<script>
import { getContext } from "svelte";
@@ -64,7 +67,7 @@
/* Reactive Effects */
$effect(() => {
totalMetrics = allMetrics.size;
totalMetrics = allMetrics?.size || 0;
});
$effect(() => {

View File

@@ -1,10 +1,12 @@
<!--
@component Selector for sorting field and direction
@component Selector for sorting field and direction
Properties:
- sorting: { field: String, order: "DESC" | "ASC" } (changes from inside)
- isOpen: Boolean (can change from inside and outside)
-->
Properties:
- `presetSorting Object?`: The latest sort selection state
- Default { field: "startTime", type: "col", order: "DESC" }
- `isOpen Bool?`: Is modal opened [Bindable, Default: false]
- `applySorting Func`: The callback function to apply current selection
-->
<script>
import { getContext, onMount } from "svelte";

View File

@@ -1,15 +1,13 @@
<!--
@component Selector for specified real time ranges for data cutoff; used in systems and nodes view
@component Selector for specified real time ranges for data cutoff; used in systems and nodes view
Properties:
- `from Date`: The datetime to start data display from
- `to Date`: The datetime to end data display at
- `customEnabled Bool?`: Allow custom time window selection [Default: true]
- `options Object? {String:Number}`: The quick time selection options [Default: {..., "Last 24hrs": 24*60*60}]
Events:
- `change, {Date, Date}`: Set 'from, to' values in upstream component
-->
Properties:
- `presetFrom Date`: The latest "from" JS Date Object
- `presetTo Date`: The latest Date "to" JS Date Object
- `customEnabled Bool?`: Allow custom time window selection [Default: true]
- `options Object? {String:Number}`: The quick time selection options [Default: {..., "Last 24hrs": 24*60*60}]
- `applyTime Func`: The callback function to apply current selection
-->
<script>
import {