@page "/editor" @using Microsoft.AspNetCore.Components.QuickGrid @using SchemaEditor.Services @using ExcelKv.Core @inject ExcelService ExcelService @inject GarnetClientService GarnetClient @inject IJSRuntime JSRuntime @rendermode InteractiveServer

Standard Schema Editor

@if (ExcelService.LoadedData.Any()) {
@ExcelService.LoadedData.Count keys
}
@if (!string.IsNullOrEmpty(errorMessage)) { } @if (!string.IsNullOrEmpty(successMessage)) { }
@if (rawData != null && rawData.Any()) {
Top Header
Start Row
End Row
Left Axis
Start Col
End Col
Limit
End Row
End Col
RAW PREVIEW
@for (int c = 0; c < (rawData.FirstOrDefault()?.Length ?? 0); c++) { bool isSearchMatch = SearchMatch(c, -1); bool isLeftAxis = c >= config.LeftHeaderStartCol && c < config.LeftHeaderStartCol + config.LeftHeaderWidth; } @for (int r = 0; r < rawData.Count; r++) { bool isHeaderRow = r >= config.TopHeaderStartRow && r < config.TopHeaderStartRow + config.TopHeaderDepth; bool isDataStartRow = r == config.TopHeaderStartRow + config.TopHeaderDepth; bool isLimit = (config.DataEndRow.HasValue && r == config.DataEndRow.Value) || (!config.DataEndRow.HasValue && r == rawData.Count - 1); @{ int dataStartCol = config.LeftHeaderStartCol + config.LeftHeaderWidth; bool isDataRowRange = r >= (config.TopHeaderStartRow + config.TopHeaderDepth) && (!config.DataEndRow.HasValue || r <= config.DataEndRow.Value); } @for (int c = 0; c < rawData[r].Length; c++) { var val = rawData[r][c]; bool isHighlighted = (r == highlightedRow && c == highlightedCol); bool isLimitCol = (config.DataEndCol.HasValue && c == config.DataEndCol.Value); bool isLeftAxisCol = c >= config.LeftHeaderStartCol && c < config.LeftHeaderStartCol + config.LeftHeaderWidth; bool isDataStartColumn = (c == dataStartCol); // Determine cell style based on region string cellClass = ""; if (isHighlighted) cellClass = "table-active border border-3 border-danger fw-bold"; else if (isLimitCol) cellClass = "border-end border-3 border-danger"; else if (isLeftAxisCol) cellClass = "table-light text-primary"; else if (isHeaderRow && !isLeftAxisCol) cellClass = "table-primary fw-bold text-center"; // Feature: Yellow Data Region Start Line if (isDataStartColumn && isDataRowRange) { cellClass += " border-start border-4 border-warning"; } } }
# @c @if(isLeftAxis) {
AXIS
}
@if(isLimit) { END } @{ // Determine if this is the "End" cell // Rule: It is the End Row. // And it is either the explicit End Col OR the last column in the row if End Col is null. bool isVisualEndCol = false; if (isLimit) { if (config.DataEndCol.HasValue) isVisualEndCol = (c == config.DataEndCol.Value); else isVisualEndCol = (c == rawData[r].Length - 1); } } @if(isDataStartRow && isDataStartColumn) { START } @if(isLimit && isVisualEndCol) { END } @val
@if (ExcelService.LoadedData.Any()) {
PARSED RESULT (@FilteredItems.Count() items)
@foreach (var item in FilteredItems.Take(100)) { } @if(FilteredItems.Count() > 100) { }
Key Value
@item.Key @item.Value
... showing first 100 of @FilteredItems.Count() (Refine Search) ...
}
}
@code { private string filePath = ""; private List sampleFiles = new(); private List sheets = new(); protected override void OnInitialized() { // Robust discovery: Check multiple possible locations var currentDir = System.IO.Directory.GetCurrentDirectory(); // Candidates: // 1. Current Dir (if running from repo root) // 2. Parent Dir (if running from SchemaEditor project root) // 3. Absolute path fallback (safeguard) var candidates = new[] { System.IO.Path.Combine(currentDir, "sample_data"), System.IO.Path.Combine(currentDir, "..", "sample_data"), "/home/lectom/repos/design-bim-dogma/sample_data" }; bool found = false; foreach(var path in candidates) { if(System.IO.Directory.Exists(path)) { sampleFiles = System.IO.Directory.GetFiles(path, "*.xlsx").ToList(); if(sampleFiles.Any()) { filePath = sampleFiles.First(); found = true; // Debug info (console or temp ui) Console.WriteLine($"[Editor] Found sample_data at: {path}"); break; } } } if(!found) { // Fallback for debugging - show where we looked errorMessage = $"Warning: sample_data not found. Checked: {string.Join(", ", candidates)} (CWD: {currentDir})"; } } private void OnSampleFileSelected(ChangeEventArgs e) { var val = e.Value?.ToString(); if(!string.IsNullOrEmpty(val)) filePath = val; } private string selectedSheet = ""; private List rawData = new(); private string? errorMessage; private string? successMessage; // Traceability State private int highlightedRow = -1; private int highlightedCol = -1; // Search State private string searchText = ""; // IQueryable Source for Filter private IEnumerable FilteredItems => string.IsNullOrWhiteSpace(searchText) ? ExcelService.LoadedData : ExcelService.LoadedData.Where(x => { var terms = searchText.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); string k = x.Key ?? ""; string v = x.Value ?? ""; return terms.All(t => k.Contains(t, StringComparison.OrdinalIgnoreCase) || v.Contains(t, StringComparison.OrdinalIgnoreCase)); }); private RegionConfig config = new RegionConfig { TopHeaderStartRow = 0, TopHeaderDepth = 3, LeftHeaderStartCol = 0, LeftHeaderWidth = 4 }; private async Task HighlightCell(int r, int c) { highlightedRow = r; highlightedCol = c; StateHasChanged(); try { await JSRuntime.InvokeVoidAsync("scrollToElement", $"cell-{r}-{c}"); } catch { /* Ignore JS errors */ } } private async Task OnPreviewCellDoubleClick(int r, int c) { highlightedRow = r; highlightedCol = c; // Reverse Traceability: Find item in LoadedData if (ExcelService.LoadedData.Any()) { var match = ExcelService.LoadedData.FirstOrDefault(x => x.Row == r && x.Col == c); if (match != null) { // To ensure the item is visible in the virtualized/limited list, we filter by its Key searchText = match.Key; StateHasChanged(); // Apply filter // Wait for render then scroll await Task.Delay(50); try { await JSRuntime.InvokeVoidAsync("scrollToElement", $"result-item-{r}-{c}"); } catch { /* Ignore */ } } } } private bool SearchMatch(int c, int r) { // Placeholder for future advanced matrix search highlighting if needed return false; } private async Task ConnectFile() { errorMessage = null; successMessage = null; try { sheets = await ExcelService.GetSheetsAsync(filePath); if(sheets.Any()) selectedSheet = sheets[0]; } catch(Exception ex) { errorMessage = $"Connection Failed: {ex.Message}"; } } private async Task LoadPreview() { if (string.IsNullOrEmpty(selectedSheet)) return; errorMessage = null; successMessage = null; highlightedRow = -1; try { rawData = await ExcelService.GetPreviewAsync(filePath, selectedSheet); ExcelService.LoadedData.Clear(); } catch (Exception ex) { errorMessage = $"Preview Failed: {ex.Message}"; } } private void SetTopRow(int r) { if (r >= config.TopHeaderStartRow) config.TopHeaderDepth = r - config.TopHeaderStartRow + 1; else { int oldEnd = config.TopHeaderStartRow + config.TopHeaderDepth - 1; config.TopHeaderStartRow = r; config.TopHeaderDepth = oldEnd - r + 1; } } private void SetLeftCol(int c) { if (c >= config.LeftHeaderStartCol) config.LeftHeaderWidth = c - config.LeftHeaderStartCol + 1; else { int oldEnd = config.LeftHeaderStartCol + config.LeftHeaderWidth - 1; config.LeftHeaderStartCol = c; config.LeftHeaderWidth = oldEnd - c + 1; } } private async Task ParseData() { errorMessage = null; successMessage = null; highlightedRow = -1; try { await ExcelService.LoadFileAsync(filePath, selectedSheet, config); successMessage = $"Parsing Complete! {ExcelService.LoadedData.Count} items generated."; } catch (Exception ex) { errorMessage = $"Parse Failed: {ex.Message}"; } } private async Task SaveToGarnet() { errorMessage = null; successMessage = null; if(!ExcelService.LoadedData.Any()) return; try { await ExcelService.SaveToStorageAsync(GarnetClient); successMessage = $"Saved {ExcelService.LoadedData.Count} items to Garnet."; } catch(Exception ex) { errorMessage = $"Save Failed: {ex.Message}"; } } }