@schneideress/dashboardframework 20.0.26 → 20.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -4976,12 +4976,313 @@ class RADashboardArea {
|
|
|
4976
4976
|
this.isElementLoaded(this.widgetList[this.currentLoadingCount]?.widgetElement) ?
|
|
4977
4977
|
this.initialWidgetCount++ : null;
|
|
4978
4978
|
}
|
|
4979
|
+
/**
|
|
4980
|
+
* Whether expanded view is active. Uses bulk action as source of truth (works in both normal browser and kiosk).
|
|
4981
|
+
* Handles bulkActionData as string (from API) or object (from event). Falls back to URL param for edge cases (e.g. shared link).
|
|
4982
|
+
*/
|
|
4983
|
+
isExpandedView() {
|
|
4984
|
+
if (this.bulkActionData) {
|
|
4985
|
+
try {
|
|
4986
|
+
const data = typeof this.bulkActionData === 'string'
|
|
4987
|
+
? JSON.parse(this.bulkActionData)
|
|
4988
|
+
: this.bulkActionData;
|
|
4989
|
+
if (data?.allSelections?.expandedView === true)
|
|
4990
|
+
return true;
|
|
4991
|
+
}
|
|
4992
|
+
catch (_) {
|
|
4993
|
+
// ignore parse errors
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
return typeof location !== 'undefined' && new URLSearchParams(window.location.search).get('expanded') === 'true';
|
|
4997
|
+
}
|
|
4979
4998
|
dataLoaded(widgetInfo) {
|
|
4980
4999
|
setTimeout(() => {
|
|
4981
5000
|
this.currentLoadingCount--;
|
|
4982
5001
|
}, 100);
|
|
5002
|
+
// For expanded view, override widget dimensions after data loads (from bulk action or URL)
|
|
5003
|
+
if (this.isExpandedView() && widgetInfo && widgetInfo.widgetInstanceId) {
|
|
5004
|
+
this.overrideWidgetDimensions(widgetInfo);
|
|
5005
|
+
}
|
|
4983
5006
|
// this.loadData()
|
|
4984
5007
|
}
|
|
5008
|
+
/**
|
|
5009
|
+
* Override widget height and width for expanded view based on actual data content
|
|
5010
|
+
* Reinitializes gridster after updating dimensions
|
|
5011
|
+
*/
|
|
5012
|
+
overrideWidgetDimensions(widgetInfo) {
|
|
5013
|
+
// Find the gridster item for this widget
|
|
5014
|
+
const gridsterItem = this.widgetList.find((item) => item.widgetInfo && item.widgetInfo.widgetInstanceId === widgetInfo.widgetInstanceId);
|
|
5015
|
+
if (gridsterItem && gridsterItem.widgetElement) {
|
|
5016
|
+
// Wait for DOM to update after data load - use multiple attempts to catch async rendering
|
|
5017
|
+
this.measureAndUpdateWidgetDimensions(gridsterItem, 0);
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
/**
|
|
5021
|
+
* Measure widget content and update dimensions, with retry logic for async data loading
|
|
5022
|
+
*/
|
|
5023
|
+
measureAndUpdateWidgetDimensions(gridsterItem, attempt = 0, maxAttempts = 8) {
|
|
5024
|
+
setTimeout(() => {
|
|
5025
|
+
// Get the actual rendered height of the widget content
|
|
5026
|
+
const widgetElement = gridsterItem.widgetElement;
|
|
5027
|
+
let element = null;
|
|
5028
|
+
// widgetElement is the ElementRef from widgetLoaded event (wcWrapper.nativeElement)
|
|
5029
|
+
if (widgetElement) {
|
|
5030
|
+
if (widgetElement.nativeElement) {
|
|
5031
|
+
element = widgetElement.nativeElement;
|
|
5032
|
+
}
|
|
5033
|
+
else if (widgetElement instanceof HTMLElement) {
|
|
5034
|
+
element = widgetElement;
|
|
5035
|
+
}
|
|
5036
|
+
}
|
|
5037
|
+
if (!element || this.gridcellHeight <= 0) {
|
|
5038
|
+
if (attempt < maxAttempts) {
|
|
5039
|
+
this.measureAndUpdateWidgetDimensions(gridsterItem, attempt + 1, maxAttempts);
|
|
5040
|
+
}
|
|
5041
|
+
return;
|
|
5042
|
+
}
|
|
5043
|
+
// Find the table body or grid content that contains the actual data rows
|
|
5044
|
+
const tbody = element.querySelector('tbody');
|
|
5045
|
+
const table = element.querySelector('table');
|
|
5046
|
+
const wcBody = element.querySelector('.wcBody');
|
|
5047
|
+
const wc = element.querySelector('.wc');
|
|
5048
|
+
const kendoGridContent = element.querySelector('.k-grid-content');
|
|
5049
|
+
const kendoGridTable = element.querySelector('.k-grid-table');
|
|
5050
|
+
// Measure the actual content height - try multiple strategies to get accurate measurement
|
|
5051
|
+
let contentHeight = 0;
|
|
5052
|
+
let measuredElement = null;
|
|
5053
|
+
// Strategy 1: Measure table body (most accurate for row count)
|
|
5054
|
+
if (tbody) {
|
|
5055
|
+
measuredElement = tbody;
|
|
5056
|
+
// Count actual rows and calculate height
|
|
5057
|
+
const rows = tbody.querySelectorAll('tr');
|
|
5058
|
+
if (rows.length > 0) {
|
|
5059
|
+
// Get height of first row to estimate
|
|
5060
|
+
const firstRow = rows[0];
|
|
5061
|
+
const rowHeight = firstRow.offsetHeight || 30; // Default row height if not available
|
|
5062
|
+
// Calculate total height based on row count
|
|
5063
|
+
const estimatedHeight = rows.length * rowHeight;
|
|
5064
|
+
// Use scrollHeight if available, otherwise use estimated
|
|
5065
|
+
contentHeight = Math.max(tbody.scrollHeight, estimatedHeight);
|
|
5066
|
+
}
|
|
5067
|
+
else {
|
|
5068
|
+
contentHeight = tbody.scrollHeight;
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
// Strategy 2: Measure Kendo grid content
|
|
5072
|
+
else if (kendoGridContent) {
|
|
5073
|
+
measuredElement = kendoGridContent;
|
|
5074
|
+
contentHeight = kendoGridContent.scrollHeight;
|
|
5075
|
+
// Also check the table inside
|
|
5076
|
+
if (kendoGridTable) {
|
|
5077
|
+
const tableHeight = kendoGridTable.scrollHeight;
|
|
5078
|
+
contentHeight = Math.max(contentHeight, tableHeight);
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
// Strategy 3: Measure table
|
|
5082
|
+
else if (table) {
|
|
5083
|
+
measuredElement = table;
|
|
5084
|
+
contentHeight = table.scrollHeight;
|
|
5085
|
+
}
|
|
5086
|
+
// Strategy 4: Measure widget content
|
|
5087
|
+
else if (wc) {
|
|
5088
|
+
measuredElement = wc;
|
|
5089
|
+
contentHeight = wc.scrollHeight;
|
|
5090
|
+
}
|
|
5091
|
+
// Strategy 5: Measure widget body
|
|
5092
|
+
else if (wcBody) {
|
|
5093
|
+
measuredElement = wcBody;
|
|
5094
|
+
contentHeight = wcBody.scrollHeight;
|
|
5095
|
+
}
|
|
5096
|
+
// Fallback: measure the wrapper
|
|
5097
|
+
else {
|
|
5098
|
+
measuredElement = element;
|
|
5099
|
+
contentHeight = element.scrollHeight;
|
|
5100
|
+
}
|
|
5101
|
+
// If scrollHeight seems constrained, try alternative measurements
|
|
5102
|
+
if (measuredElement) {
|
|
5103
|
+
// Check if scrollHeight is accurate (should be > clientHeight if content overflows)
|
|
5104
|
+
const scrollH = measuredElement.scrollHeight;
|
|
5105
|
+
const clientH = measuredElement.clientHeight;
|
|
5106
|
+
const offsetH = measuredElement.offsetHeight;
|
|
5107
|
+
// If scrollHeight equals clientHeight and seems too small, content might be constrained
|
|
5108
|
+
if (scrollH === clientH && scrollH < 200) {
|
|
5109
|
+
// Try offsetHeight
|
|
5110
|
+
contentHeight = Math.max(contentHeight, offsetH);
|
|
5111
|
+
}
|
|
5112
|
+
// For tables, also try measuring all rows directly
|
|
5113
|
+
if (tbody || table) {
|
|
5114
|
+
const allRows = (tbody || table)?.querySelectorAll('tr') || [];
|
|
5115
|
+
if (allRows.length > 0) {
|
|
5116
|
+
let totalRowHeight = 0;
|
|
5117
|
+
allRows.forEach((row) => {
|
|
5118
|
+
totalRowHeight += row.offsetHeight || 30;
|
|
5119
|
+
});
|
|
5120
|
+
// Use the maximum of scrollHeight and calculated row heights
|
|
5121
|
+
contentHeight = Math.max(contentHeight, totalRowHeight);
|
|
5122
|
+
}
|
|
5123
|
+
}
|
|
5124
|
+
}
|
|
5125
|
+
// Get header height
|
|
5126
|
+
const header = element.querySelector('.wcheader');
|
|
5127
|
+
const headerHeight = header ? header.offsetHeight : 46;
|
|
5128
|
+
// Calculate total height needed (content + header + padding)
|
|
5129
|
+
// Add sufficient padding to ensure no scrolling
|
|
5130
|
+
const padding = 40; // Padding for margins and spacing
|
|
5131
|
+
const totalHeight = contentHeight + headerHeight + padding;
|
|
5132
|
+
// Check if content is loaded (reasonable height)
|
|
5133
|
+
// For expanded view, we expect significant content
|
|
5134
|
+
const hasValidContent = contentHeight > 100 || (attempt >= 3 && contentHeight > 50);
|
|
5135
|
+
if (!hasValidContent && attempt < maxAttempts) {
|
|
5136
|
+
// Content not fully loaded, retry with longer delay
|
|
5137
|
+
this.measureAndUpdateWidgetDimensions(gridsterItem, attempt + 1, maxAttempts);
|
|
5138
|
+
return;
|
|
5139
|
+
}
|
|
5140
|
+
// Calculate rows needed based on grid cell height
|
|
5141
|
+
// Use Math.ceil to ensure we have enough space (round up)
|
|
5142
|
+
const calculatedRows = Math.ceil(totalHeight / this.gridcellHeight);
|
|
5143
|
+
// Add a small buffer (0.5 rows) to ensure no scrolling, then round up
|
|
5144
|
+
// This accounts for any rounding errors or minor spacing issues
|
|
5145
|
+
const bufferedRows = calculatedRows + 0.5;
|
|
5146
|
+
const newRows = Math.max(Math.ceil(bufferedRows), 4);
|
|
5147
|
+
// For expanded view, use full available width (no spacing needed since widgets stack vertically)
|
|
5148
|
+
const maxCols = this.responsiveService.maxColsDesktop || 12;
|
|
5149
|
+
const newCols = maxCols; // Full width
|
|
5150
|
+
// Update gridster item dimensions if changed
|
|
5151
|
+
const needsUpdate = gridsterItem.rows !== newRows || gridsterItem.cols !== newCols || gridsterItem.x !== 0;
|
|
5152
|
+
if (needsUpdate) {
|
|
5153
|
+
gridsterItem.rows = newRows;
|
|
5154
|
+
gridsterItem.cols = newCols;
|
|
5155
|
+
gridsterItem.x = 0; // Always start at the beginning of the row (full width)
|
|
5156
|
+
// Update widget info
|
|
5157
|
+
if (gridsterItem.widgetInfo) {
|
|
5158
|
+
gridsterItem.widgetInfo.height = newRows;
|
|
5159
|
+
gridsterItem.widgetInfo.width = newCols;
|
|
5160
|
+
gridsterItem.widgetInfo.position_x = 0; // Start at position 0 for full width
|
|
5161
|
+
}
|
|
5162
|
+
// Reposition widgets to prevent overlapping after dimension change
|
|
5163
|
+
this.repositionWidgetsAfterDimensionChange();
|
|
5164
|
+
// Force gridster to recalculate layout
|
|
5165
|
+
this.ngZone.run(() => {
|
|
5166
|
+
// Trigger change detection by updating the array reference
|
|
5167
|
+
this.widgetList = [...this.widgetList];
|
|
5168
|
+
// Call optionsChanged if API is available
|
|
5169
|
+
if (this.options && this.options.api) {
|
|
5170
|
+
this.options.api?.optionsChanged();
|
|
5171
|
+
}
|
|
5172
|
+
// Update area height after dimensions change
|
|
5173
|
+
setTimeout(() => {
|
|
5174
|
+
this.setAreaHeight();
|
|
5175
|
+
// Re-verify height after layout update to ensure accuracy
|
|
5176
|
+
// Check if there's still scrolling and adjust if needed
|
|
5177
|
+
if (attempt < maxAttempts - 1) {
|
|
5178
|
+
setTimeout(() => {
|
|
5179
|
+
// Re-measure to verify the height is correct
|
|
5180
|
+
const widgetEl = gridsterItem.widgetElement;
|
|
5181
|
+
let el = null;
|
|
5182
|
+
if (widgetEl) {
|
|
5183
|
+
el = widgetEl.nativeElement || widgetEl;
|
|
5184
|
+
}
|
|
5185
|
+
if (el) {
|
|
5186
|
+
const wcBody = el.querySelector('.wcBody');
|
|
5187
|
+
const tbody = el.querySelector('tbody');
|
|
5188
|
+
const contentEl = tbody || wcBody || el;
|
|
5189
|
+
// Check if there's scrolling (scrollHeight > clientHeight)
|
|
5190
|
+
if (contentEl && contentEl.scrollHeight > contentEl.clientHeight + 10) {
|
|
5191
|
+
// Still has scrolling, need more height
|
|
5192
|
+
const additionalHeight = contentEl.scrollHeight - contentEl.clientHeight;
|
|
5193
|
+
const additionalRows = Math.ceil(additionalHeight / this.gridcellHeight);
|
|
5194
|
+
if (additionalRows > 0) {
|
|
5195
|
+
gridsterItem.rows = gridsterItem.rows + additionalRows;
|
|
5196
|
+
if (gridsterItem.widgetInfo) {
|
|
5197
|
+
gridsterItem.widgetInfo.height = gridsterItem.rows;
|
|
5198
|
+
}
|
|
5199
|
+
this.widgetList = [...this.widgetList];
|
|
5200
|
+
if (this.options && this.options.api) {
|
|
5201
|
+
this.options.api?.optionsChanged();
|
|
5202
|
+
}
|
|
5203
|
+
setTimeout(() => this.setAreaHeight(), 100);
|
|
5204
|
+
}
|
|
5205
|
+
}
|
|
5206
|
+
}
|
|
5207
|
+
// Continue with normal retry logic
|
|
5208
|
+
this.measureAndUpdateWidgetDimensions(gridsterItem, attempt + 1, maxAttempts);
|
|
5209
|
+
}, 400);
|
|
5210
|
+
}
|
|
5211
|
+
}, 200);
|
|
5212
|
+
});
|
|
5213
|
+
}
|
|
5214
|
+
else if (attempt < maxAttempts - 1) {
|
|
5215
|
+
// Dimensions didn't change, but verify one more time that content is fully loaded
|
|
5216
|
+
this.measureAndUpdateWidgetDimensions(gridsterItem, attempt + 1, maxAttempts);
|
|
5217
|
+
}
|
|
5218
|
+
}, attempt === 0 ? 1500 : attempt < 3 ? 800 : 500); // Longer initial wait for data to fully render
|
|
5219
|
+
}
|
|
5220
|
+
/**
|
|
5221
|
+
* Reposition widgets to prevent overlapping after dimension changes in expanded view
|
|
5222
|
+
* When 2 widgets are in the same row, the 2nd widget moves to next row after the 1st widget's height
|
|
5223
|
+
*/
|
|
5224
|
+
repositionWidgetsAfterDimensionChange() {
|
|
5225
|
+
if (this.widgetList.length === 0)
|
|
5226
|
+
return;
|
|
5227
|
+
// Sort widgets by their original Y position (row), then by X position (left to right)
|
|
5228
|
+
const sortedWidgets = [...this.widgetList].sort((a, b) => {
|
|
5229
|
+
const aY = a.y || 0;
|
|
5230
|
+
const bY = b.y || 0;
|
|
5231
|
+
if (aY !== bY) {
|
|
5232
|
+
return aY - bY;
|
|
5233
|
+
}
|
|
5234
|
+
// If same row, sort by X position (left to right)
|
|
5235
|
+
return (a.x || 0) - (b.x || 0);
|
|
5236
|
+
});
|
|
5237
|
+
// Group widgets by their original row (Y position) before repositioning
|
|
5238
|
+
const rowGroups = new Map();
|
|
5239
|
+
sortedWidgets.forEach(widget => {
|
|
5240
|
+
const originalY = widget.y || 0;
|
|
5241
|
+
if (!rowGroups.has(originalY)) {
|
|
5242
|
+
rowGroups.set(originalY, []);
|
|
5243
|
+
}
|
|
5244
|
+
rowGroups.get(originalY).push(widget);
|
|
5245
|
+
});
|
|
5246
|
+
// Sort rows by Y position
|
|
5247
|
+
const sortedRows = Array.from(rowGroups.entries()).sort((a, b) => a[0] - b[0]);
|
|
5248
|
+
let currentY = 0;
|
|
5249
|
+
// Process each original row
|
|
5250
|
+
sortedRows.forEach(([originalRowY, widgetsInRow]) => {
|
|
5251
|
+
// Sort widgets in this row by X position (left to right)
|
|
5252
|
+
widgetsInRow.sort((a, b) => (a.x || 0) - (b.x || 0));
|
|
5253
|
+
// Process widgets sequentially: first widget at currentY, subsequent widgets after previous widget's height
|
|
5254
|
+
// All widgets get x=0 since they take full width and stack vertically
|
|
5255
|
+
widgetsInRow.forEach((widget, index) => {
|
|
5256
|
+
if (index === 0) {
|
|
5257
|
+
// First widget in row: place at currentY
|
|
5258
|
+
widget.y = currentY;
|
|
5259
|
+
}
|
|
5260
|
+
else {
|
|
5261
|
+
// Subsequent widgets: place after the previous widget's bottom edge
|
|
5262
|
+
const previousWidget = widgetsInRow[index - 1];
|
|
5263
|
+
const previousWidgetHeight = previousWidget.rows || 0;
|
|
5264
|
+
const previousWidgetY = previousWidget.y || 0;
|
|
5265
|
+
widget.y = previousWidgetY + previousWidgetHeight;
|
|
5266
|
+
}
|
|
5267
|
+
// Set x to 0 for all widgets (full width, no horizontal spacing)
|
|
5268
|
+
widget.x = 0;
|
|
5269
|
+
// Update widget info
|
|
5270
|
+
if (widget.widgetInfo) {
|
|
5271
|
+
widget.widgetInfo.position_y = widget.y;
|
|
5272
|
+
widget.widgetInfo.position_x = 0; // Full width, start at position 0
|
|
5273
|
+
}
|
|
5274
|
+
});
|
|
5275
|
+
// Calculate where the next row should start
|
|
5276
|
+
// If multiple widgets in row, use the last widget's bottom edge
|
|
5277
|
+
// Otherwise, use the first widget's bottom edge
|
|
5278
|
+
if (widgetsInRow.length > 0) {
|
|
5279
|
+
const lastWidget = widgetsInRow[widgetsInRow.length - 1];
|
|
5280
|
+
const lastWidgetY = lastWidget.y || 0;
|
|
5281
|
+
const lastWidgetHeight = lastWidget.rows || 0;
|
|
5282
|
+
currentY = lastWidgetY + lastWidgetHeight;
|
|
5283
|
+
}
|
|
5284
|
+
});
|
|
5285
|
+
}
|
|
4985
5286
|
isElementLoaded(el) {
|
|
4986
5287
|
if (el) {
|
|
4987
5288
|
let rect = el.getBoundingClientRect();
|
|
@@ -5122,6 +5423,8 @@ class RADashboardArea {
|
|
|
5122
5423
|
};
|
|
5123
5424
|
let _the = this;
|
|
5124
5425
|
this.isSiteDetailDashboard = this.dashboardInfo?.templateName === 'Site Detail';
|
|
5426
|
+
// Check if expanded view is requested
|
|
5427
|
+
//const isExpandedView = new URLSearchParams(window.location.search).get('expanded') === 'true';
|
|
5125
5428
|
this.dashboardService.getWidgetsByDashboardArea(widgetInfo, this.appConfig).subscribe(widgets => {
|
|
5126
5429
|
if (this.isSiteDetailDashboard) {
|
|
5127
5430
|
widgets = this.arrangeWidgetsInTwoColumnGrid(widgets);
|