SYNC: widgetbar refactor

This commit is contained in:
SPRINX0\prochazka
2025-12-11 12:29:08 +01:00
committed by Diflow
parent 3d2ad1cb9b
commit efefec3c20
3 changed files with 287 additions and 149 deletions

View File

@@ -3,149 +3,59 @@
import { writable } from 'svelte/store';
import _ from 'lodash';
import { getLocalStorage, setLocalStorage } from '../utility/storageCache';
import {
computeInitialWidgetBarProps,
handleResizeWidgetBar,
toggleCollapseWidgetBar,
WidgetBarItemDefinition,
} from '../utility/widgetBarResizing';
export let hidden = false;
export let storageName = null;
let definitions = {};
let definitions: WidgetBarItemDefinition[] = [];
let clientHeight;
let definitionCount = 0;
// const widgetColumnBarHeight = writable(0);
const widgetColumnBarComputed = writable({});
const fromStorage = getLocalStorage(storageName);
let resizedHeights = fromStorage?.resizedHeights || {};
let storedProps = getLocalStorage(storageName) || {};
$: setLocalStorage(storageName, { resizedHeights });
$: setLocalStorage(storageName, storedProps);
$: containerProps = {
clientHeight,
titleHeight: 30,
splitterHeight: 3,
};
// setContext('widgetColumnBarHeight', widgetColumnBarHeight);
setContext('pushWidgetItemDefinition', (name, item) => {
definitions = {
...definitions,
[name]: {
...item,
name,
index: definitionCount,
},
};
definitionCount += 1;
setContext('pushWidgetItemDefinition', item => {
definitions = [...definitions, item];
});
setContext('updateWidgetItemDefinition', (name, item) => {
// console.log('WidgetColumnBar updateWidgetItemDefinition', name, item);
definitions = {
...definitions,
[name]: { ...definitions[name], ...item },
};
});
setContext('widgetResizeItem', (name, newHeight) => {
resizedHeights = {
...resizedHeights,
[name]: newHeight,
};
recompute(definitions);
definitions = definitions.map(def => (def.name === name ? { ...def, ...item } : def));
});
setContext('widgetColumnBarComputed', widgetColumnBarComputed);
setContext('widgetResizeItem', (name, deltaY) => {
$widgetColumnBarComputed = handleResizeWidgetBar(
containerProps,
definitions,
$widgetColumnBarComputed,
name,
deltaY
);
});
setContext('toggleWidgetCollapse', name => {
$widgetColumnBarComputed = toggleCollapseWidgetBar(containerProps, definitions, $widgetColumnBarComputed, name);
});
// $: $widgetColumnBarHeight = clientHeight;
$: recompute(definitions);
function recompute(defs: any) {
const visibleItems = _.orderBy(_.values(defs), ['index'])
.filter(x => !x.collapsed && !x.skip && x.positiveCondition)
.map(x => x.name);
const visibleItemsCount = visibleItems.length;
const computed = {};
// First pass: calculate base heights
let totalFixedHeight = 0;
let totalFlexibleItems = 0;
const itemHeights = {};
const isResized = {};
for (const key of visibleItems) {
const def = defs[key];
const minHeight = def.minimalHeight || 100;
// Check if this item has a user-resized height
if (key in resizedHeights) {
itemHeights[key] = Math.max(resizedHeights[key], minHeight);
isResized[key] = true;
totalFixedHeight += itemHeights[key];
} else if (def.height != null) {
let height = 0;
if (_.isString(def.height) && def.height.endsWith('px')) height = parseInt(def.height.slice(0, -2));
else if (_.isString(def.height) && def.height.endsWith('%'))
height = (clientHeight * parseFloat(def.height.slice(0, -1))) / 100;
else height = parseInt(def.height);
height = Math.max(height, minHeight);
itemHeights[key] = height;
isResized[key] = false;
totalFixedHeight += height;
} else {
isResized[key] = false;
totalFlexibleItems++;
}
}
// Second pass: distribute remaining space to flexible items
const availableHeightForFlexible = clientHeight - totalFixedHeight;
for (const key of visibleItems) {
if (!(key in itemHeights)) {
const def = defs[key];
const minHeight = def.minimalHeight || 100;
let height = totalFlexibleItems > 0 ? availableHeightForFlexible / totalFlexibleItems : minHeight;
height = Math.max(height, minHeight);
itemHeights[key] = height;
}
}
// Third pass: scale all non-resized items proportionally to fill clientHeight exactly
const totalHeight = _.sum(Object.values(itemHeights));
const resizedKeys = Object.keys(isResized).filter(k => isResized[k]);
const nonResizedKeys = visibleItems.filter(k => !isResized[k]);
if (totalHeight !== clientHeight && nonResizedKeys.length > 0) {
const totalResizedHeight = _.sum(resizedKeys.map(k => itemHeights[k]));
const totalNonResizedHeight = _.sum(nonResizedKeys.map(k => itemHeights[k]));
const availableForNonResized = clientHeight - totalResizedHeight;
if (totalNonResizedHeight > 0 && availableForNonResized > 0) {
const ratio = availableForNonResized / totalNonResizedHeight;
for (const key of nonResizedKeys) {
itemHeights[key] = itemHeights[key] * ratio;
}
}
} else if (totalHeight !== clientHeight && nonResizedKeys.length === 0) {
// All items are resized, scale proportionally
const ratio = clientHeight / totalHeight;
for (const key of visibleItems) {
itemHeights[key] = itemHeights[key] * ratio;
}
}
// Build computed result
let visibleIndex = 0;
for (const key of visibleItems) {
computed[key] = {
size: itemHeights[key],
splitterVisible: visibleItemsCount > 1 && visibleIndex < visibleItemsCount - 1,
visibleItemsCount,
};
visibleIndex++;
}
// Clean up resizedHeights - remove entries for items that no longer exist
resizedHeights = _.pickBy(
_.mapValues(resizedHeights, (v, k) => {
if (k in itemHeights) return v;
return undefined;
}),
v => v != null
);
$widgetColumnBarComputed = computed;
function recompute(defs: WidgetBarItemDefinition[]) {
$widgetColumnBarComputed = computeInitialWidgetBarProps(containerProps, defs, storedProps);
}
onMount(() => {

View File

@@ -2,10 +2,19 @@
import _ from 'lodash';
import { getContext } from 'svelte';
import type { Readable } from 'svelte/store';
import WidgetTitle from './WidgetTitle.svelte';
import splitterDrag from '../utility/splitterDrag';
import { getLocalStorage, setLocalStorage } from '../utility/storageCache';
import {
PushWidgetBarItemDefinitionFunction,
UpdateWidgetBarItemDefinitionFunction,
WidgetBarComputedResult,
WidgetBarComputedProps,
ToggleCollapseWidgetItemFunction,
ResizeWidgetItemFunction,
} from '../utility/widgetBarResizing';
// import { getLocalStorage, setLocalStorage } from '../utility/storageCache';
export let title;
export let skip = false;
@@ -13,9 +22,9 @@
export let height = null;
export let collapsed = null;
export let storageName = null;
// export let storageName = null;
export let onClose = null;
export let minimalHeight = 100;
export let minimalHeight = 50;
export let name;
// let size = 0;
@@ -25,20 +34,24 @@
// visibleItemsCount: 0,
// });
const pushWidgetItemDefinition = getContext('pushWidgetItemDefinition') as any;
const updateWidgetItemDefinition = getContext('updateWidgetItemDefinition') as any;
const pushWidgetItemDefinition = getContext('pushWidgetItemDefinition') as PushWidgetBarItemDefinitionFunction;
const updateWidgetItemDefinition = getContext('updateWidgetItemDefinition') as UpdateWidgetBarItemDefinitionFunction;
// const widgetColumnBarHeight = getContext('widgetColumnBarHeight') as any;
const widgetResizeItem = getContext('widgetResizeItem') as any;
const widgetColumnBarComputed = getContext('widgetColumnBarComputed') as any;
pushWidgetItemDefinition(name, {
const widgetResizeItem = getContext('widgetResizeItem') as ResizeWidgetItemFunction;
const widgetColumnBarComputed = getContext('widgetColumnBarComputed') as Readable<WidgetBarComputedResult>;
const toggleWidgetCollapse = getContext('toggleWidgetCollapse') as ToggleCollapseWidgetItemFunction;
pushWidgetItemDefinition({
name,
collapsed,
height,
skip,
positiveCondition,
minimalHeight,
skip: skip || !positiveCondition,
minimalContentHeight: minimalHeight,
});
$: updateWidgetItemDefinition(name, { collapsed: !visible, height, skip, positiveCondition });
$: updateWidgetItemDefinition(name, { collapsed, height, skip: skip || !positiveCondition });
// $: setInitialSize(height, $widgetColumnBarHeight);
@@ -46,39 +59,31 @@
// setLocalStorage(storageName, { relativeHeight: size / $widgetColumnBarHeight, visible });
// }
let visible =
storageName && getLocalStorage(storageName) && getLocalStorage(storageName).visible != null
? getLocalStorage(storageName).visible
: !collapsed;
$: computed = $widgetColumnBarComputed[name] || {};
$: collapsible = computed.visibleItemsCount != 1 || !visible;
$: size = computed.size ?? 100;
$: splitterVisible = computed.splitterVisible;
$: computed = $widgetColumnBarComputed[name] || ({} as WidgetBarComputedProps);
</script>
{#if !skip && positiveCondition}
<WidgetTitle
clickable={collapsible}
on:click={collapsible ? () => (visible = !visible) : null}
clickable={computed.clickableTitle}
on:click={computed.clickableTitle ? () => toggleWidgetCollapse(name) : null}
data-testid={$$props['data-testid']}
{onClose}>{title}</WidgetTitle
>
{#if visible}
{#if !computed.collapsed}
<div
class="wrapper"
style={splitterVisible ? `height:${size}px` : 'flex: 1 1 0'}
style={computed.splitterVisible ? `height:${computed.contentHeight}px` : 'flex: 1 1 0'}
data-testid={$$props['data-testid'] ? `${$$props['data-testid']}_content` : undefined}
>
<slot />
</div>
{#if splitterVisible}
{#if computed.splitterVisible}
<div
class="vertical-split-handle"
use:splitterDrag={'clientY'}
on:resizeSplitter={e => widgetResizeItem(name, size + e.detail)}
on:resizeSplitter={e => widgetResizeItem(name, e.detail)}
/>
{/if}
{/if}