UNPKG

@blocktion/json-to-table

Version:

A powerful, modular React component for converting JSON data to navigable tables with advanced features like automatic column detection, theming, and sub-table navigation. Part of the Blocktion SaaS project ecosystem.

195 lines (194 loc) 7.11 kB
import { ArrayAnalyzer } from "../utils/arrayUtils"; export class NavigationManager { constructor() { this.stack = []; this.listeners = new Set(); } navigateToSubTable(path, value, title, rootDocumentIndex) { try { const analysis = this.analyzeNavigationTarget(value); const newItem = this.createNavigationItem(path, value, title, analysis, rootDocumentIndex); this.stack.push(newItem); this.notifyListeners(); return { success: true, newData: analysis.processedData, newTitle: analysis.displayTitle, breadcrumb: this.generateBreadcrumb(), }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown navigation error", }; } } navigateBack(targetLevel) { if (targetLevel < 0 || targetLevel >= this.stack.length) { return { success: false, error: "Invalid navigation target level", }; } this.stack = this.stack.slice(0, targetLevel); this.notifyListeners(); const currentItem = this.stack[this.stack.length - 1]; return { success: true, newData: currentItem?.data || [], newTitle: currentItem?.title || "Root", breadcrumb: this.generateBreadcrumb(), }; } navigateToRoot(rootData) { this.stack = []; this.notifyListeners(); return { success: true, newData: rootData, newTitle: "Root Table", breadcrumb: ["Root"], }; } getCurrentStack() { return [...this.stack]; } getBreadcrumb() { return this.generateBreadcrumb(); } canNavigate(value) { if (value === null || value === undefined) return false; if (Array.isArray(value)) return value.length > 0; if (typeof value === "object") return true; // Check if it's a JSON string that can be parsed if (typeof value === "string") { try { const parsed = JSON.parse(value); return ((typeof parsed === "object" && parsed !== null) || Array.isArray(parsed)); } catch { return false; } } return false; } addListener(listener) { this.listeners.add(listener); } removeListener(listener) { this.listeners.delete(listener); } analyzeNavigationTarget(value) { // Handle JSON strings by parsing them first let parsedValue = value; if (typeof value === "string") { try { parsedValue = JSON.parse(value); } catch { // If it's not valid JSON, treat as primitive return { type: "primitive", processedData: [value], displayTitle: "Value", analysis: null, }; } } if (Array.isArray(parsedValue)) { const analysis = ArrayAnalyzer.analyzeContent(parsedValue); let processedData; let displayTitle; switch (analysis.type) { case "objects": // Arrays of objects - use the array as-is for column detection processedData = parsedValue; displayTitle = ArrayAnalyzer.getNavigationTitle(parsedValue, "objects"); break; case "primitives": // Arrays of primitives - create array table format processedData = ArrayAnalyzer.createArrayTableData(parsedValue); displayTitle = ArrayAnalyzer.getNavigationTitle(parsedValue, "primitives"); break; case "mixed": // Mixed arrays - create specialized mixed array format with recursive processing processedData = ArrayAnalyzer.createMixedArrayData(parsedValue); displayTitle = ArrayAnalyzer.getNavigationTitle(parsedValue, "mixed"); break; case "empty": processedData = []; displayTitle = ArrayAnalyzer.getNavigationTitle(parsedValue, "empty"); break; case "nulls": processedData = ArrayAnalyzer.createArrayTableData(parsedValue); displayTitle = ArrayAnalyzer.getNavigationTitle(parsedValue, "nulls"); break; default: processedData = parsedValue; displayTitle = ArrayAnalyzer.getArrayDisplayText(parsedValue); } return { type: "array", processedData, displayTitle, analysis, }; } else if (typeof parsedValue === "object" && parsedValue !== null) { return { type: "object", processedData: [parsedValue], displayTitle: "Object", analysis: null, }; } else { return { type: "primitive", processedData: [value], // Use original value for display displayTitle: "Value", analysis: null, }; } } createNavigationItem(path, value, title, analysis, rootDocumentIndex) { const parentTitle = this.stack.length > 0 ? this.stack[this.stack.length - 1].title : "Root"; const breadcrumbPath = this.stack.length > 0 ? this.stack[this.stack.length - 1].breadcrumbPath : []; // Use the original field name (path) instead of the display title for breadcrumb // This ensures the update path uses the correct field names const breadcrumbItem = path || analysis.displayTitle; return { path, title: analysis.displayTitle, data: analysis.processedData, parentTitle, breadcrumbPath: [...breadcrumbPath, breadcrumbItem], rootDocumentIndex, }; } generateBreadcrumb() { if (this.stack.length === 0) return ["Root"]; return ["Root", ...this.stack[this.stack.length - 1].breadcrumbPath]; } notifyListeners() { const result = { success: true, newData: this.stack.length > 0 ? this.stack[this.stack.length - 1].data : [], newTitle: this.stack.length > 0 ? this.stack[this.stack.length - 1].title : "Root", breadcrumb: this.generateBreadcrumb(), }; this.listeners.forEach((listener) => { listener.onNavigationChange(result); }); } }