@burdenoff/microfe-bigconsole 2026.613.1 → 2026.613.3
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.
- package/dist/bigconsole/components/dashboard/DashboardCanvas.js +142 -135
- package/dist/bigconsole/components/dashboard/DashboardCanvas.js.map +1 -1
- package/dist/bigconsole/components/dashboard/WidgetPalette.js +124 -120
- package/dist/bigconsole/components/dashboard/WidgetPalette.js.map +1 -1
- package/dist/bigconsole/components/widgets/PropertiesPanel.js +9 -9
- package/dist/bigconsole/components/widgets/WidgetRegistry.js +10 -7
- package/dist/bigconsole/components/widgets/WidgetRegistry.js.map +1 -1
- package/dist/bigconsole/components/widgets/WidgetTypeSelector.js +32 -27
- package/dist/bigconsole/components/widgets/WidgetTypeSelector.js.map +1 -1
- package/dist/bigconsole/components/widgets/WidgetWrapper.js +9 -9
- package/dist/bigconsole/components/widgets/WidgetWrapper.js.map +1 -1
- package/dist/bigconsole/hooks/index.js +1 -0
- package/dist/bigconsole/hooks/useWidgetCatalog.js +82 -0
- package/dist/bigconsole/hooks/useWidgetCatalog.js.map +1 -0
- package/dist/bigconsole/hooks/useWidgetOperations.js +2 -1
- package/dist/bigconsole/hooks/useWidgetOperations.js.map +1 -1
- package/dist/bigconsole/pages/DashboardBuilderPage.js +172 -163
- package/dist/bigconsole/pages/DashboardBuilderPage.js.map +1 -1
- package/dist/bigconsole/utils/widgetPositioning.js.map +1 -1
- package/dist/generated/wspace-operations.js +17 -17
- package/dist/generated/wspace-operations.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardBuilderPage.js","names":[],"sources":["../../../src/bigconsole/pages/DashboardBuilderPage.tsx"],"sourcesContent":["/**\n * DashboardBuilderPage\n *\n * Full dashboard builder experience with side panels.\n */\n\nimport { type FC, memo, useState, useEffect, useCallback } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useBigConsoleNavigate } from '../context';\nimport { DashboardCanvas, ExportDashboardDialog } from '../components/dashboard';\nimport { WidgetTypeSelector } from '../components/widgets/WidgetTypeSelector';\nimport { useDashboardStore, useWidgetStore } from '../store';\nimport { useDashboardOperations, useWidgetOperations, usePageOperations } from '../hooks';\nimport type { WidgetType, Dashboard, DashboardPage } from '../types';\nimport { findNextAvailablePosition } from '../utils/widgetPositioning';\nimport { getWidgetDefinition } from '../components/widgets/WidgetRegistry';\n\n// Breadcrumb name registration helpers (get from shell at call time to avoid timing issues)\nfunction getBreadcrumbRegistry() {\n try {\n return (\n window as unknown as {\n __breadcrumbRegistry?: { register: (id: string, name: string) => void; unregister: (id: string) => void };\n }\n ).__breadcrumbRegistry;\n } catch {\n return undefined;\n }\n}\n\nfunction registerBreadcrumbName(id: string, name: string) {\n const registry = getBreadcrumbRegistry();\n if (registry?.register) {\n registry.register(id, name);\n }\n}\n\nfunction unregisterBreadcrumbName(id: string) {\n const registry = getBreadcrumbRegistry();\n if (registry?.unregister) {\n registry.unregister(id);\n }\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardBuilderPage: FC = memo(function DashboardBuilderPage() {\n const { dashboardId } = useParams<{ dashboardId: string }>();\n const navigate = useBigConsoleNavigate();\n const [isWidgetPanelOpen, setIsWidgetPanelOpen] = useState(false);\n const [isSaving, setIsSaving] = useState(false);\n const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);\n const [activePageId, setActivePageId] = useState<string | null>(null);\n\n // Dashboard operations from GraphQL\n const {\n fetchDashboard,\n updateDashboard,\n loading: dashboardLoading,\n error: dashboardError,\n } = useDashboardOperations();\n\n // Widget operations from GraphQL\n const {\n createWidget: createWidgetMutation,\n updateWidget: updateWidgetMutation,\n deleteWidget: deleteWidgetMutation,\n duplicateWidget: duplicateWidgetMutation,\n batchUpdatePositions,\n } = useWidgetOperations(undefined, dashboardId);\n\n // Page operations from GraphQL\n const {\n createPage: createPageMutation,\n updatePage: updatePageMutation,\n deletePage: deletePageMutation,\n reorderPages: reorderPagesMutation,\n } = usePageOperations();\n\n // Store actions\n const setCurrentDashboard = useDashboardStore((state) => state.setCurrentDashboard);\n const currentDashboard = useDashboardStore((state) => state.currentDashboard);\n const setViewMode = useDashboardStore((state) => state.setViewMode);\n const selectWidget = useWidgetStore((state) => state.selectWidget);\n const widgets = useWidgetStore((state) => state.widgets);\n const clipboardWidgetId = useWidgetStore((state) => state.clipboardWidgetId);\n const clipboardAction = useWidgetStore((state) => state.clipboardAction);\n const clearClipboard = useWidgetStore((state) => state.clearClipboard);\n\n // Set edit mode on mount\n useEffect(() => {\n setViewMode('edit');\n return () => setViewMode('view');\n }, [setViewMode]);\n\n // Load dashboard data from API\n useEffect(() => {\n if (!dashboardId) {\n navigate('/dashboards');\n return;\n }\n\n // Fetch dashboard from API\n fetchDashboard(dashboardId).then((dashboard) => {\n if (dashboard) {\n setCurrentDashboard({\n id: dashboard.id,\n title: dashboard.name,\n description: dashboard.description || '',\n pages: dashboard.pages.map((page, index) => ({\n id: page.id,\n title: page.name || '',\n index,\n layoutConfig: page.layoutConfig || {},\n })) as any,\n createdAt: dashboard.createdAt,\n updatedAt: dashboard.updatedAt,\n } as unknown as Dashboard);\n // Set active page to first page\n if (dashboard.pages.length > 0 && !activePageId) {\n setActivePageId(dashboard.pages[0].id);\n }\n\n // Register dashboard name for breadcrumb display\n if (dashboard.name) {\n registerBreadcrumbName(dashboardId, dashboard.name);\n }\n } else {\n // Dashboard not found, redirect\n navigate('/dashboards');\n }\n });\n\n // Cleanup: unregister breadcrumb name on unmount\n return () => {\n if (dashboardId) {\n unregisterBreadcrumbName(dashboardId);\n }\n };\n }, [dashboardId, navigate, setCurrentDashboard, fetchDashboard, activePageId]);\n\n // Handle paste widget from clipboard\n const handlePaste = useCallback(async () => {\n if (!clipboardWidgetId || !dashboardId || !currentDashboard?.pages?.[0]?.id) {\n return;\n }\n\n const sourceWidget = widgets.get(clipboardWidgetId);\n if (!sourceWidget) {\n clearClipboard();\n return;\n }\n\n const pageId = currentDashboard.pages[0].id;\n\n // Use smart placement algorithm - horizontal first, then next row\n const widgetsArray = Array.from(widgets.values());\n const widgetSize = {\n width: sourceWidget.positionWidth ?? 4,\n height: sourceWidget.positionHeight ?? 4,\n };\n\n const { x, y } = findNextAvailablePosition(widgetsArray, widgetSize, { cols: 12 });\n\n const newPosition = {\n x,\n y,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n\n const newTitle = clipboardAction === 'copy' ? `${sourceWidget.title} (Copy)` : sourceWidget.title;\n\n let newWidget = null;\n\n // Try duplicateWidget API first\n try {\n newWidget = await duplicateWidgetMutation(clipboardWidgetId, newPosition, newTitle);\n } catch (err) {}\n\n // Fallback: Create new widget with source widget's data\n if (!newWidget) {\n newWidget = await createWidgetMutation({\n pageId,\n dashboardId,\n type: sourceWidget.type,\n title: newTitle,\n description: sourceWidget.description,\n // v2.0: Use dataSinkId and parserId instead of dataSourceId/dataPath\n dataSinkId: sourceWidget.dataSinkId ?? undefined,\n datasetId: sourceWidget.datasetId ?? undefined,\n parserId: sourceWidget.parserId ?? undefined,\n parserRules: sourceWidget.parserRules ?? undefined,\n config: sourceWidget.config || {},\n position: newPosition,\n refreshInterval: sourceWidget.refreshInterval ?? undefined,\n metadata: sourceWidget.metadata,\n });\n }\n\n if (newWidget) {\n selectWidget(newWidget.id);\n\n // If cut action, delete the original widget from backend\n if (clipboardAction === 'cut') {\n await deleteWidgetMutation(clipboardWidgetId);\n }\n }\n\n clearClipboard();\n }, [\n clipboardWidgetId,\n clipboardAction,\n dashboardId,\n currentDashboard,\n widgets,\n duplicateWidgetMutation,\n createWidgetMutation,\n deleteWidgetMutation,\n selectWidget,\n clearClipboard,\n ]);\n\n // Handle quick add widget from panel\n const handleQuickAddWidget = useCallback(\n async (type: WidgetType) => {\n if (!dashboardId || !currentDashboard?.pages?.[0]?.id) return;\n\n const pageId = currentDashboard.pages[0].id;\n const title = type\n .replace(/_/g, ' ')\n .toLowerCase()\n .replace(/^\\w/, (c) => c.toUpperCase());\n\n // Use smart placement algorithm - horizontal first, then next row\n const widgetsArray = Array.from(widgets.values());\n const definition = getWidgetDefinition(type);\n const widgetSize = {\n width: definition?.defaultSize?.width ?? 4,\n height: definition?.defaultSize?.height ?? 4,\n };\n\n const { x, y } = findNextAvailablePosition(widgetsArray, widgetSize, { cols: 12 });\n\n // Create widget via API\n const newWidget = await createWidgetMutation({\n pageId,\n dashboardId,\n type,\n title,\n config: {},\n position: {\n x,\n y,\n width: widgetSize.width,\n height: widgetSize.height,\n },\n });\n\n if (newWidget) {\n selectWidget(newWidget.id);\n setIsWidgetPanelOpen(false);\n }\n },\n [dashboardId, currentDashboard, createWidgetMutation, selectWidget, widgets]\n );\n\n // Navigate back to the dashboards list\n const handleBack = useCallback(() => {\n navigate('/dashboards');\n }, [navigate]);\n\n // Handle save - save dirty widgets and batch update positions\n const handleSave = useCallback(async () => {\n if (!dashboardId) return;\n\n setIsSaving(true);\n try {\n // Get the LATEST state directly from store (avoid stale closure)\n const storeState = useWidgetStore.getState();\n const currentWidgets = Array.from(storeState.widgets.values());\n const currentDirtyIds = storeState.dirtyWidgetIds;\n\n // Debug: Log dirty widget IDs\n\n // 1. Save dirty widgets (property changes like title, config, etc.)\n const dirtyWidgets = currentWidgets.filter((w) => currentDirtyIds.has(w.id));\n\n if (dirtyWidgets.length > 0) {\n // Save each dirty widget\n const updatePromises = dirtyWidgets.map(async (widget) => {\n const updateInput = {\n id: widget.id,\n title: widget.title,\n config: widget.config,\n refreshInterval: widget.refreshInterval ?? undefined,\n // v2.0: Use dataSinkId and parserId instead of dataSourceId/dataPath\n dataSinkId: widget.dataSinkId ?? undefined,\n datasetId: widget.datasetId ?? undefined,\n parserId: widget.parserId ?? undefined,\n parserRules: widget.parserRules ?? undefined,\n };\n\n try {\n const result = await updateWidgetMutation(updateInput);\n return result;\n } catch (err) {\n throw err;\n }\n });\n\n const results = await Promise.all(updatePromises);\n\n // Clear dirty flags after successful save\n storeState.clearAllDirty();\n } else {\n }\n\n // 2. Batch update widget positions\n if (currentWidgets.length > 0) {\n const positionUpdates = currentWidgets.map((widget) => ({\n id: widget.id,\n position: widget.position || {\n x: widget.positionX ?? 0,\n y: widget.positionY ?? 0,\n width: widget.positionWidth ?? 4,\n height: widget.positionHeight ?? 4,\n },\n }));\n\n await batchUpdatePositions(positionUpdates);\n }\n\n // 3. Update dashboard metadata if needed\n if (currentDashboard) {\n await updateDashboard({\n id: dashboardId,\n name: currentDashboard.title,\n description: currentDashboard.description,\n });\n }\n } catch (error) {\n } finally {\n setIsSaving(false);\n }\n }, [dashboardId, currentDashboard, updateWidgetMutation, batchUpdatePositions, updateDashboard]);\n\n // Handle export\n const handleExport = useCallback(() => {\n setIsExportDialogOpen(true);\n }, []);\n\n const handleExportClose = useCallback(() => {\n setIsExportDialogOpen(false);\n }, []);\n\n // ============================================================================\n // Page Management Callbacks\n // ============================================================================\n\n // Handle page selection\n const handlePageChange = useCallback((pageId: string) => {\n setActivePageId(pageId);\n }, []);\n\n // Handle adding a new page\n const handlePageAdd = useCallback(async () => {\n if (!currentDashboard || !dashboardId) return;\n\n const newPageCount = currentDashboard.pages.length + 1;\n const newPageName = `Page ${newPageCount}`;\n\n // Call GraphQL mutation to create page\n // Note: 'order' is not available for create - backend auto-assigns order\n const newPage = await createPageMutation({\n dashboardId,\n name: newPageName,\n });\n\n if (newPage) {\n // Update local state with the created page\n setCurrentDashboard({\n ...currentDashboard,\n pages: [\n ...currentDashboard.pages,\n {\n id: newPage.id,\n title: newPage.name,\n index: newPage.order,\n layoutConfig: newPage.layoutConfig || {},\n },\n ],\n });\n\n // Switch to the new page\n setActivePageId(newPage.id);\n }\n }, [currentDashboard, dashboardId, setCurrentDashboard, createPageMutation]);\n\n // Handle renaming a page\n const handlePageRename = useCallback(\n async (pageId: string, newName: string) => {\n if (!currentDashboard) return;\n\n // Call GraphQL mutation to update page\n const updatedPage = await updatePageMutation({\n id: pageId,\n name: newName,\n });\n\n if (updatedPage) {\n setCurrentDashboard({\n ...currentDashboard,\n pages: currentDashboard.pages.map((page) => (page.id === pageId ? { ...page, title: newName } : page)),\n });\n }\n },\n [currentDashboard, setCurrentDashboard, updatePageMutation]\n );\n\n // Handle duplicating a page\n const handlePageDuplicate = useCallback(\n async (pageId: string) => {\n if (!currentDashboard || !dashboardId) return;\n\n const sourcePage = currentDashboard.pages.find((p) => p.id === pageId);\n if (!sourcePage) return;\n\n // Call GraphQL mutation to create a copy of the page\n // Note: 'order' is not available for create - backend auto-assigns order\n // Note: 'layout' should be PageLayout enum ('GRID', 'FLEX', 'FREEFORM'), not layoutConfig object\n const newPage = await createPageMutation({\n dashboardId,\n name: `${sourcePage.title} (Copy)`,\n });\n\n if (newPage) {\n setCurrentDashboard({\n ...currentDashboard,\n pages: [\n ...currentDashboard.pages,\n {\n id: newPage.id,\n title: newPage.name,\n index: newPage.order,\n layoutConfig: newPage.layoutConfig || {},\n },\n ],\n });\n\n setActivePageId(newPage.id);\n }\n },\n [currentDashboard, dashboardId, setCurrentDashboard, createPageMutation]\n );\n\n // Handle deleting a page\n const handlePageDelete = useCallback(\n async (pageId: string) => {\n if (!currentDashboard || currentDashboard.pages.length <= 1) return;\n\n // Call GraphQL mutation to delete page\n const success = await deletePageMutation(pageId);\n\n if (success) {\n const updatedPages = currentDashboard.pages.filter((p) => p.id !== pageId).map((p, index) => ({ ...p, index }));\n\n setCurrentDashboard({\n ...currentDashboard,\n pages: updatedPages,\n });\n\n // Switch to first page if current page was deleted\n if (activePageId === pageId) {\n setActivePageId(updatedPages[0]?.id || null);\n }\n }\n },\n [currentDashboard, setCurrentDashboard, activePageId, deletePageMutation]\n );\n\n // Handle reordering pages\n const handlePagesReorder = useCallback(\n async (reorderedPages: DashboardPage[]) => {\n if (!currentDashboard || !dashboardId) return;\n\n // Update local state optimistically\n setCurrentDashboard({\n ...currentDashboard,\n pages: reorderedPages.map((page, index) => ({\n ...page,\n index,\n })),\n });\n\n // Call GraphQL mutation to persist the reorder\n const pageIds = reorderedPages.map((p) => p.id);\n await reorderPagesMutation(dashboardId, pageIds);\n },\n [currentDashboard, dashboardId, setCurrentDashboard, reorderPagesMutation]\n );\n\n // Show loading state\n if (dashboardLoading || !currentDashboard) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"animate-spin w-8 h-8 border-4 border-action-primary-bg border-t-transparent rounded-full\" />\n </div>\n );\n }\n\n // Show error state\n if (dashboardError) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full p-6\">\n <div className=\"bg-status-error-bg border border-status-error-border rounded-lg p-4 max-w-md\">\n <h3 className=\"text-sm font-medium text-status-error-text\">Failed to load dashboard</h3>\n <p className=\"text-sm text-text-secondary mt-1\">{dashboardError.message}</p>\n <button\n type=\"button\"\n onClick={() => navigate('/dashboards')}\n className=\"mt-3 px-3 py-1.5 text-sm font-medium text-action-primary-fg bg-action-primary-bg hover:bg-action-primary-bgHover rounded-md\"\n >\n Back to Dashboards\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Header / Toolbar */}\n <div className=\"flex items-center gap-3 px-3 py-2 border-b border-border-default bg-bg-surface flex-shrink-0\">\n <Button\n onClick={handleBack}\n variant=\"ghost\"\n size=\"sm\"\n title=\"Back to Dashboards\"\n aria-label=\"Back to Dashboards\"\n >\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Back\n </Button>\n <h2 className=\"text-sm font-semibold text-text-primary truncate\">{currentDashboard.title}</h2>\n </div>\n\n {/* Builder Body */}\n <div className=\"flex flex-1 min-h-0 relative\">\n {/* Widget Panel (Left) */}\n <div\n className={`\n ${isWidgetPanelOpen ? 'w-72' : 'w-0'}\n flex-shrink-0\n bg-bg-surface\n border-r border-border-default\n transition-all duration-300\n overflow-hidden\n `}\n >\n {isWidgetPanelOpen && (\n <div className=\"h-full flex flex-col\">\n <div className=\"flex items-center justify-between p-3 border-b border-border-default\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Widgets</h3>\n <button\n type=\"button\"\n onClick={() => setIsWidgetPanelOpen(false)}\n className=\"\n p-1 rounded-md\n text-text-secondary\n hover:text-text-primary\n hover:bg-bg-muted\n \"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <div className=\"flex-1 overflow-auto\">\n <WidgetTypeSelector onSelect={handleQuickAddWidget} />\n </div>\n </div>\n )}\n </div>\n\n {/* Toggle Widget Panel Button */}\n {!isWidgetPanelOpen && (\n <button\n type=\"button\"\n onClick={() => setIsWidgetPanelOpen(true)}\n className=\"\n absolute left-0 top-1/2 -translate-y-1/2 z-20\n w-8 h-24\n bg-bg-surface\n border border-l-0 border-border-default\n rounded-r-lg\n flex items-center justify-center\n text-text-secondary\n hover:text-text-primary\n hover:bg-bg-muted\n transition-colors\n \"\n title=\"Open widget panel\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n )}\n\n {/* Main Canvas */}\n <div className=\"flex-1 relative\">\n <DashboardCanvas\n dashboardId={currentDashboard.id}\n pageId={activePageId || currentDashboard.pages?.[0]?.id}\n title={currentDashboard.title}\n onPaste={handlePaste}\n onSave={handleSave}\n isSaving={isSaving}\n onExport={handleExport}\n // Page tabs props\n pages={currentDashboard.pages.map((page) => ({\n id: page.id,\n name: page.title || `Page ${(page.index ?? 0) + 1}`,\n order: page.index ?? 0,\n }))}\n activePageId={activePageId || currentDashboard.pages?.[0]?.id}\n onPageChange={handlePageChange}\n onPageAdd={handlePageAdd}\n onPageRename={handlePageRename}\n onPageDuplicate={handlePageDuplicate}\n onPageDelete={handlePageDelete}\n onPagesReorder={handlePagesReorder}\n showPageTabs={true}\n />\n </div>\n\n {/* Export Dashboard Dialog */}\n <ExportDashboardDialog\n isOpen={isExportDialogOpen}\n dashboardId={currentDashboard.id}\n dashboardName={currentDashboard.title}\n onClose={handleExportClose}\n />\n </div>\n </div>\n );\n});\n\nexport default DashboardBuilderPage;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DashboardBuilderPage.js","names":[],"sources":["../../../src/bigconsole/pages/DashboardBuilderPage.tsx"],"sourcesContent":["/**\n * DashboardBuilderPage\n *\n * Full dashboard builder experience with side panels.\n */\n\nimport { type FC, memo, useState, useEffect, useCallback } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useBigConsoleNavigate, useBigConsole } from '../context';\nimport { DashboardCanvas, ExportDashboardDialog } from '../components/dashboard';\nimport { WidgetTypeSelector } from '../components/widgets/WidgetTypeSelector';\nimport { useDashboardStore, useWidgetStore } from '../store';\nimport { useDashboardOperations, useWidgetOperations, usePageOperations, useWidgetCatalog } from '../hooks';\nimport type { WidgetType, Dashboard, DashboardPage } from '../types';\nimport { findNextAvailablePosition } from '../utils/widgetPositioning';\nimport { getWidgetDefinition } from '../components/widgets/WidgetRegistry';\n\n// Breadcrumb name registration helpers (get from shell at call time to avoid timing issues)\nfunction getBreadcrumbRegistry() {\n try {\n return (\n window as unknown as {\n __breadcrumbRegistry?: { register: (id: string, name: string) => void; unregister: (id: string) => void };\n }\n ).__breadcrumbRegistry;\n } catch {\n return undefined;\n }\n}\n\nfunction registerBreadcrumbName(id: string, name: string) {\n const registry = getBreadcrumbRegistry();\n if (registry?.register) {\n registry.register(id, name);\n }\n}\n\nfunction unregisterBreadcrumbName(id: string) {\n const registry = getBreadcrumbRegistry();\n if (registry?.unregister) {\n registry.unregister(id);\n }\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardBuilderPage: FC = memo(function DashboardBuilderPage() {\n const { dashboardId } = useParams<{ dashboardId: string }>();\n const navigate = useBigConsoleNavigate();\n const { workspaceId, apiGatewayUrl, authToken } = useBigConsole();\n // Built-in widget catalog from the backend (getWidgetTypes); falls back to the\n // bundled registry while loading or if the supergraph hasn't adopted it yet.\n const { definitions: widgetCatalog } = useWidgetCatalog({ workspaceId, apiGatewayUrl, authToken });\n const [isWidgetPanelOpen, setIsWidgetPanelOpen] = useState(false);\n const [isSaving, setIsSaving] = useState(false);\n const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);\n const [activePageId, setActivePageId] = useState<string | null>(null);\n\n // Dashboard operations from GraphQL\n const {\n fetchDashboard,\n updateDashboard,\n loading: dashboardLoading,\n error: dashboardError,\n } = useDashboardOperations();\n\n // Widget operations from GraphQL\n const {\n createWidget: createWidgetMutation,\n updateWidget: updateWidgetMutation,\n deleteWidget: deleteWidgetMutation,\n duplicateWidget: duplicateWidgetMutation,\n batchUpdatePositions,\n } = useWidgetOperations(undefined, dashboardId);\n\n // Page operations from GraphQL\n const {\n createPage: createPageMutation,\n updatePage: updatePageMutation,\n deletePage: deletePageMutation,\n reorderPages: reorderPagesMutation,\n } = usePageOperations();\n\n // Store actions\n const setCurrentDashboard = useDashboardStore((state) => state.setCurrentDashboard);\n const currentDashboard = useDashboardStore((state) => state.currentDashboard);\n const setViewMode = useDashboardStore((state) => state.setViewMode);\n const selectWidget = useWidgetStore((state) => state.selectWidget);\n const widgets = useWidgetStore((state) => state.widgets);\n const clipboardWidgetId = useWidgetStore((state) => state.clipboardWidgetId);\n const clipboardAction = useWidgetStore((state) => state.clipboardAction);\n const clearClipboard = useWidgetStore((state) => state.clearClipboard);\n\n // Set edit mode on mount\n useEffect(() => {\n setViewMode('edit');\n return () => setViewMode('view');\n }, [setViewMode]);\n\n // Load dashboard data from API\n useEffect(() => {\n if (!dashboardId) {\n navigate('/dashboards');\n return;\n }\n\n // Fetch dashboard from API\n fetchDashboard(dashboardId).then((dashboard) => {\n if (dashboard) {\n setCurrentDashboard({\n id: dashboard.id,\n title: dashboard.name,\n description: dashboard.description || '',\n pages: dashboard.pages.map((page, index) => ({\n id: page.id,\n title: page.name || '',\n index,\n layoutConfig: page.layoutConfig || {},\n })) as any,\n createdAt: dashboard.createdAt,\n updatedAt: dashboard.updatedAt,\n } as unknown as Dashboard);\n // Set active page to first page\n if (dashboard.pages.length > 0 && !activePageId) {\n setActivePageId(dashboard.pages[0].id);\n }\n\n // Register dashboard name for breadcrumb display\n if (dashboard.name) {\n registerBreadcrumbName(dashboardId, dashboard.name);\n }\n } else {\n // Dashboard not found, redirect\n navigate('/dashboards');\n }\n });\n\n // Cleanup: unregister breadcrumb name on unmount\n return () => {\n if (dashboardId) {\n unregisterBreadcrumbName(dashboardId);\n }\n };\n }, [dashboardId, navigate, setCurrentDashboard, fetchDashboard, activePageId]);\n\n // Handle paste widget from clipboard\n const handlePaste = useCallback(async () => {\n if (!clipboardWidgetId || !dashboardId || !currentDashboard?.pages?.[0]?.id) {\n return;\n }\n\n const sourceWidget = widgets.get(clipboardWidgetId);\n if (!sourceWidget) {\n clearClipboard();\n return;\n }\n\n const pageId = currentDashboard.pages[0].id;\n\n // Use smart placement algorithm - horizontal first, then next row\n const widgetsArray = Array.from(widgets.values());\n const widgetSize = {\n width: sourceWidget.positionWidth ?? 4,\n height: sourceWidget.positionHeight ?? 4,\n };\n\n const { x, y } = findNextAvailablePosition(widgetsArray, widgetSize, { cols: 12 });\n\n const newPosition = {\n x,\n y,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n\n const newTitle = clipboardAction === 'copy' ? `${sourceWidget.title} (Copy)` : sourceWidget.title;\n\n let newWidget = null;\n\n // Try duplicateWidget API first\n try {\n newWidget = await duplicateWidgetMutation(clipboardWidgetId, newPosition, newTitle);\n } catch (err) {}\n\n // Fallback: Create new widget with source widget's data\n if (!newWidget) {\n newWidget = await createWidgetMutation({\n pageId,\n dashboardId,\n type: sourceWidget.type,\n title: newTitle,\n description: sourceWidget.description,\n // v2.0: Use dataSinkId and parserId instead of dataSourceId/dataPath\n dataSinkId: sourceWidget.dataSinkId ?? undefined,\n datasetId: sourceWidget.datasetId ?? undefined,\n parserId: sourceWidget.parserId ?? undefined,\n parserRules: sourceWidget.parserRules ?? undefined,\n config: sourceWidget.config || {},\n position: newPosition,\n refreshInterval: sourceWidget.refreshInterval ?? undefined,\n metadata: sourceWidget.metadata,\n });\n }\n\n if (newWidget) {\n selectWidget(newWidget.id);\n\n // If cut action, delete the original widget from backend\n if (clipboardAction === 'cut') {\n await deleteWidgetMutation(clipboardWidgetId);\n }\n }\n\n clearClipboard();\n }, [\n clipboardWidgetId,\n clipboardAction,\n dashboardId,\n currentDashboard,\n widgets,\n duplicateWidgetMutation,\n createWidgetMutation,\n deleteWidgetMutation,\n selectWidget,\n clearClipboard,\n ]);\n\n // Handle quick add widget from panel\n const handleQuickAddWidget = useCallback(\n async (type: WidgetType) => {\n if (!dashboardId || !currentDashboard?.pages?.[0]?.id) return;\n\n const pageId = currentDashboard.pages[0].id;\n const title = type\n .replace(/_/g, ' ')\n .toLowerCase()\n .replace(/^\\w/, (c) => c.toUpperCase());\n\n // Use smart placement algorithm - horizontal first, then next row.\n // Prefer the backend-sourced catalog entry; fall back to the static registry.\n const widgetsArray = Array.from(widgets.values());\n const definition = widgetCatalog.find((d) => d.type === type) ?? getWidgetDefinition(type);\n const widgetSize = {\n width: definition?.defaultSize?.width ?? 4,\n height: definition?.defaultSize?.height ?? 4,\n };\n\n const { x, y } = findNextAvailablePosition(widgetsArray, widgetSize, { cols: 12 });\n\n // Create widget via API\n const newWidget = await createWidgetMutation({\n pageId,\n dashboardId,\n type,\n title,\n config: {},\n position: {\n x,\n y,\n width: widgetSize.width,\n height: widgetSize.height,\n },\n });\n\n if (newWidget) {\n selectWidget(newWidget.id);\n setIsWidgetPanelOpen(false);\n }\n },\n [dashboardId, currentDashboard, createWidgetMutation, selectWidget, widgets, widgetCatalog]\n );\n\n // Navigate back to the dashboards list\n const handleBack = useCallback(() => {\n navigate('/dashboards');\n }, [navigate]);\n\n // Handle save - save dirty widgets and batch update positions\n const handleSave = useCallback(async () => {\n if (!dashboardId) return;\n\n setIsSaving(true);\n try {\n // Get the LATEST state directly from store (avoid stale closure)\n const storeState = useWidgetStore.getState();\n const currentWidgets = Array.from(storeState.widgets.values());\n const currentDirtyIds = storeState.dirtyWidgetIds;\n\n // Debug: Log dirty widget IDs\n\n // 1. Save dirty widgets (property changes like title, config, etc.)\n const dirtyWidgets = currentWidgets.filter((w) => currentDirtyIds.has(w.id));\n\n if (dirtyWidgets.length > 0) {\n // Save each dirty widget\n const updatePromises = dirtyWidgets.map(async (widget) => {\n const updateInput = {\n id: widget.id,\n title: widget.title,\n config: widget.config,\n refreshInterval: widget.refreshInterval ?? undefined,\n // v2.0: Use dataSinkId and parserId instead of dataSourceId/dataPath\n dataSinkId: widget.dataSinkId ?? undefined,\n datasetId: widget.datasetId ?? undefined,\n parserId: widget.parserId ?? undefined,\n parserRules: widget.parserRules ?? undefined,\n };\n\n try {\n const result = await updateWidgetMutation(updateInput);\n return result;\n } catch (err) {\n throw err;\n }\n });\n\n const results = await Promise.all(updatePromises);\n\n // Clear dirty flags after successful save\n storeState.clearAllDirty();\n } else {\n }\n\n // 2. Batch update widget positions\n if (currentWidgets.length > 0) {\n const positionUpdates = currentWidgets.map((widget) => ({\n id: widget.id,\n position: widget.position || {\n x: widget.positionX ?? 0,\n y: widget.positionY ?? 0,\n width: widget.positionWidth ?? 4,\n height: widget.positionHeight ?? 4,\n },\n }));\n\n await batchUpdatePositions(positionUpdates);\n }\n\n // 3. Update dashboard metadata if needed\n if (currentDashboard) {\n await updateDashboard({\n id: dashboardId,\n name: currentDashboard.title,\n description: currentDashboard.description,\n });\n }\n } catch (error) {\n } finally {\n setIsSaving(false);\n }\n }, [dashboardId, currentDashboard, updateWidgetMutation, batchUpdatePositions, updateDashboard]);\n\n // Handle export\n const handleExport = useCallback(() => {\n setIsExportDialogOpen(true);\n }, []);\n\n const handleExportClose = useCallback(() => {\n setIsExportDialogOpen(false);\n }, []);\n\n // ============================================================================\n // Page Management Callbacks\n // ============================================================================\n\n // Handle page selection\n const handlePageChange = useCallback((pageId: string) => {\n setActivePageId(pageId);\n }, []);\n\n // Handle adding a new page\n const handlePageAdd = useCallback(async () => {\n if (!currentDashboard || !dashboardId) return;\n\n const newPageCount = currentDashboard.pages.length + 1;\n const newPageName = `Page ${newPageCount}`;\n\n // Call GraphQL mutation to create page\n // Note: 'order' is not available for create - backend auto-assigns order\n const newPage = await createPageMutation({\n dashboardId,\n name: newPageName,\n });\n\n if (newPage) {\n // Update local state with the created page\n setCurrentDashboard({\n ...currentDashboard,\n pages: [\n ...currentDashboard.pages,\n {\n id: newPage.id,\n title: newPage.name,\n index: newPage.order,\n layoutConfig: newPage.layoutConfig || {},\n },\n ],\n });\n\n // Switch to the new page\n setActivePageId(newPage.id);\n }\n }, [currentDashboard, dashboardId, setCurrentDashboard, createPageMutation]);\n\n // Handle renaming a page\n const handlePageRename = useCallback(\n async (pageId: string, newName: string) => {\n if (!currentDashboard) return;\n\n // Call GraphQL mutation to update page\n const updatedPage = await updatePageMutation({\n id: pageId,\n name: newName,\n });\n\n if (updatedPage) {\n setCurrentDashboard({\n ...currentDashboard,\n pages: currentDashboard.pages.map((page) => (page.id === pageId ? { ...page, title: newName } : page)),\n });\n }\n },\n [currentDashboard, setCurrentDashboard, updatePageMutation]\n );\n\n // Handle duplicating a page\n const handlePageDuplicate = useCallback(\n async (pageId: string) => {\n if (!currentDashboard || !dashboardId) return;\n\n const sourcePage = currentDashboard.pages.find((p) => p.id === pageId);\n if (!sourcePage) return;\n\n // Call GraphQL mutation to create a copy of the page\n // Note: 'order' is not available for create - backend auto-assigns order\n // Note: 'layout' should be PageLayout enum ('GRID', 'FLEX', 'FREEFORM'), not layoutConfig object\n const newPage = await createPageMutation({\n dashboardId,\n name: `${sourcePage.title} (Copy)`,\n });\n\n if (newPage) {\n setCurrentDashboard({\n ...currentDashboard,\n pages: [\n ...currentDashboard.pages,\n {\n id: newPage.id,\n title: newPage.name,\n index: newPage.order,\n layoutConfig: newPage.layoutConfig || {},\n },\n ],\n });\n\n setActivePageId(newPage.id);\n }\n },\n [currentDashboard, dashboardId, setCurrentDashboard, createPageMutation]\n );\n\n // Handle deleting a page\n const handlePageDelete = useCallback(\n async (pageId: string) => {\n if (!currentDashboard || currentDashboard.pages.length <= 1) return;\n\n // Call GraphQL mutation to delete page\n const success = await deletePageMutation(pageId);\n\n if (success) {\n const updatedPages = currentDashboard.pages.filter((p) => p.id !== pageId).map((p, index) => ({ ...p, index }));\n\n setCurrentDashboard({\n ...currentDashboard,\n pages: updatedPages,\n });\n\n // Switch to first page if current page was deleted\n if (activePageId === pageId) {\n setActivePageId(updatedPages[0]?.id || null);\n }\n }\n },\n [currentDashboard, setCurrentDashboard, activePageId, deletePageMutation]\n );\n\n // Handle reordering pages\n const handlePagesReorder = useCallback(\n async (reorderedPages: DashboardPage[]) => {\n if (!currentDashboard || !dashboardId) return;\n\n // Update local state optimistically\n setCurrentDashboard({\n ...currentDashboard,\n pages: reorderedPages.map((page, index) => ({\n ...page,\n index,\n })),\n });\n\n // Call GraphQL mutation to persist the reorder\n const pageIds = reorderedPages.map((p) => p.id);\n await reorderPagesMutation(dashboardId, pageIds);\n },\n [currentDashboard, dashboardId, setCurrentDashboard, reorderPagesMutation]\n );\n\n // Show loading state\n if (dashboardLoading || !currentDashboard) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"animate-spin w-8 h-8 border-4 border-action-primary-bg border-t-transparent rounded-full\" />\n </div>\n );\n }\n\n // Show error state\n if (dashboardError) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full p-6\">\n <div className=\"bg-status-error-bg border border-status-error-border rounded-lg p-4 max-w-md\">\n <h3 className=\"text-sm font-medium text-status-error-text\">Failed to load dashboard</h3>\n <p className=\"text-sm text-text-secondary mt-1\">{dashboardError.message}</p>\n <button\n type=\"button\"\n onClick={() => navigate('/dashboards')}\n className=\"mt-3 px-3 py-1.5 text-sm font-medium text-action-primary-fg bg-action-primary-bg hover:bg-action-primary-bgHover rounded-md\"\n >\n Back to Dashboards\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Header / Toolbar */}\n <div className=\"flex items-center gap-3 px-3 py-2 border-b border-border-default bg-bg-surface flex-shrink-0\">\n <Button\n onClick={handleBack}\n variant=\"ghost\"\n size=\"sm\"\n title=\"Back to Dashboards\"\n aria-label=\"Back to Dashboards\"\n >\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Back\n </Button>\n <h2 className=\"text-sm font-semibold text-text-primary truncate\">{currentDashboard.title}</h2>\n </div>\n\n {/* Builder Body */}\n <div className=\"flex flex-1 min-h-0 relative\">\n {/* Widget Panel (Left) */}\n <div\n className={`\n ${isWidgetPanelOpen ? 'w-72' : 'w-0'}\n flex-shrink-0\n bg-bg-surface\n border-r border-border-default\n transition-all duration-300\n overflow-hidden\n `}\n >\n {isWidgetPanelOpen && (\n <div className=\"h-full flex flex-col\">\n <div className=\"flex items-center justify-between p-3 border-b border-border-default\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Widgets</h3>\n <button\n type=\"button\"\n onClick={() => setIsWidgetPanelOpen(false)}\n className=\"\n p-1 rounded-md\n text-text-secondary\n hover:text-text-primary\n hover:bg-bg-muted\n \"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <div className=\"flex-1 overflow-auto\">\n <WidgetTypeSelector onSelect={handleQuickAddWidget} definitions={widgetCatalog} />\n </div>\n </div>\n )}\n </div>\n\n {/* Toggle Widget Panel Button */}\n {!isWidgetPanelOpen && (\n <button\n type=\"button\"\n onClick={() => setIsWidgetPanelOpen(true)}\n className=\"\n absolute left-0 top-1/2 -translate-y-1/2 z-20\n w-8 h-24\n bg-bg-surface\n border border-l-0 border-border-default\n rounded-r-lg\n flex items-center justify-center\n text-text-secondary\n hover:text-text-primary\n hover:bg-bg-muted\n transition-colors\n \"\n title=\"Open widget panel\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n )}\n\n {/* Main Canvas */}\n <div className=\"flex-1 relative\">\n <DashboardCanvas\n dashboardId={currentDashboard.id}\n pageId={activePageId || currentDashboard.pages?.[0]?.id}\n title={currentDashboard.title}\n onPaste={handlePaste}\n onSave={handleSave}\n isSaving={isSaving}\n onExport={handleExport}\n // Page tabs props\n pages={currentDashboard.pages.map((page) => ({\n id: page.id,\n name: page.title || `Page ${(page.index ?? 0) + 1}`,\n order: page.index ?? 0,\n }))}\n activePageId={activePageId || currentDashboard.pages?.[0]?.id}\n onPageChange={handlePageChange}\n onPageAdd={handlePageAdd}\n onPageRename={handlePageRename}\n onPageDuplicate={handlePageDuplicate}\n onPageDelete={handlePageDelete}\n onPagesReorder={handlePagesReorder}\n showPageTabs={true}\n />\n </div>\n\n {/* Export Dashboard Dialog */}\n <ExportDashboardDialog\n isOpen={isExportDialogOpen}\n dashboardId={currentDashboard.id}\n dashboardName={currentDashboard.title}\n onClose={handleExportClose}\n />\n </div>\n </div>\n );\n});\n\nexport default DashboardBuilderPage;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widgetPositioning.js","names":[],"sources":["../../../src/bigconsole/utils/widgetPositioning.ts"],"sourcesContent":["/**\n * Widget Positioning Utilities\n *\n * Smart placement algorithm for dashboard widgets.\n * Places widgets horizontally first (left-to-right), then moves to next row.\n * Uses a grid occupancy map to find the first available slot.\n */\n\nimport type { Widget } from '../types';\n\nexport interface WidgetSize {\n width: number;\n height: number;\n}\n\nexport interface WidgetPosition {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface GridConfig {\n cols: number;\n maxRows?: number;\n}\n\n/**\n * Default grid configuration\n */\nexport const DEFAULT_GRID_CONFIG: GridConfig = {\n cols: 12,\n maxRows: 100,\n};\n\n/**\n * Responsive breakpoints configuration\n */\nexport const RESPONSIVE_BREAKPOINTS = {\n lg: 1200,\n md: 996,\n sm: 768,\n xs: 480,\n} as const;\n\n/**\n * Columns per breakpoint for responsive layout\n */\nexport const RESPONSIVE_COLS = {\n lg: 12,\n md: 10,\n sm: 6,\n xs: 4,\n} as const;\n\n/**\n * Creates an occupancy map from existing widgets\n * Each occupied cell is marked with the widget ID\n */\nfunction createOccupancyMap(widgets: Widget[]): Map<string, string> {\n const occupancyMap = new Map<string, string>();\n\n widgets.forEach((widget) => {\n const x = widget.positionX ?? 0;\n const y = widget.positionY ?? 0;\n const w = widget.positionWidth ?? 4;\n const h = widget.positionHeight ?? 4;\n\n for (let dx = 0; dx < w; dx++) {\n for (let dy = 0; dy < h; dy++) {\n occupancyMap.set(`${x + dx},${y + dy}`, widget.id);\n }\n }\n });\n\n return occupancyMap;\n}\n\n/**\n * Checks if a widget can be placed at the given position\n */\nfunction canPlaceWidget(\n occupancyMap: Map<string, string>,\n x: number,\n y: number,\n width: number,\n height: number,\n cols: number\n): boolean {\n // Check if widget fits within grid columns\n if (x + width > cols) {\n return false;\n }\n\n // Check if all required cells are available\n for (let dx = 0; dx < width; dx++) {\n for (let dy = 0; dy < height; dy++) {\n if (occupancyMap.has(`${x + dx},${y + dy}`)) {\n return false;\n }\n }\n }\n\n return true;\n}\n\n/**\n * Finds the next available position for a widget using smart placement.\n * Scans row-by-row, left-to-right to find the first slot that fits.\n *\n * @param existingWidgets - Array of existing widgets on the dashboard\n * @param newWidgetSize - Size of the new widget to place\n * @param config - Grid configuration (columns, max rows)\n * @returns Position where the new widget should be placed\n */\nexport function findNextAvailablePosition(\n existingWidgets: Widget[],\n newWidgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols, maxRows = 100 } = config;\n const { width, height } = newWidgetSize;\n\n // Handle empty dashboard - place at origin\n if (existingWidgets.length === 0) {\n return { x: 0, y: 0 };\n }\n\n // Create occupancy map\n const occupancyMap = createOccupancyMap(existingWidgets);\n\n // Find the maximum Y to limit our search\n const maxExistingY = Math.max(...existingWidgets.map((w) => (w.positionY ?? 0) + (w.positionHeight ?? 4)));\n\n // Scan row by row, left to right, to find first available slot\n for (let y = 0; y <= maxExistingY + height && y < maxRows; y++) {\n for (let x = 0; x <= cols - width; x++) {\n if (canPlaceWidget(occupancyMap, x, y, width, height, cols)) {\n return { x, y };\n }\n }\n }\n\n // Fallback: place at the bottom of existing widgets\n return { x: 0, y: maxExistingY };\n}\n\n/**\n * Finds position to the right of a specific widget\n * Falls back to smart placement if no space to the right\n */\nexport function findPositionNextToWidget(\n existingWidgets: Widget[],\n referenceWidgetId: string,\n newWidgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols } = config;\n const referenceWidget = existingWidgets.find((w) => w.id === referenceWidgetId);\n\n if (!referenceWidget) {\n return findNextAvailablePosition(existingWidgets, newWidgetSize, config);\n }\n\n const refX = referenceWidget.positionX ?? 0;\n const refY = referenceWidget.positionY ?? 0;\n const refW = referenceWidget.positionWidth ?? 4;\n\n // Try to place to the right of reference widget\n const newX = refX + refW;\n const newY = refY;\n\n // Check if it fits\n if (newX + newWidgetSize.width <= cols) {\n const occupancyMap = createOccupancyMap(existingWidgets.filter((w) => w.id !== referenceWidgetId));\n\n if (canPlaceWidget(occupancyMap, newX, newY, newWidgetSize.width, newWidgetSize.height, cols)) {\n return { x: newX, y: newY };\n }\n }\n\n // Fall back to smart placement\n return findNextAvailablePosition(existingWidgets, newWidgetSize, config);\n}\n\n/**\n * Calculates position from drop coordinates (for drag-and-drop)\n */\nexport function calculatePositionFromDrop(\n dropX: number,\n dropY: number,\n canvasRect: DOMRect,\n containerWidth: number,\n widgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols } = config;\n const columnWidth = containerWidth / cols;\n const rowHeight = 60; // Match DashboardGrid rowHeight\n\n const relativeX = dropX - canvasRect.left;\n const relativeY = dropY - canvasRect.top;\n\n const gridX = Math.max(0, Math.min(cols - widgetSize.width, Math.floor(relativeX / columnWidth)));\n const gridY = Math.max(0, Math.floor(relativeY / rowHeight));\n\n return { x: gridX, y: gridY };\n}\n\n/**\n * Gets responsive columns for a given container width\n */\nexport function getResponsiveCols(containerWidth: number): number {\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.lg) {\n return RESPONSIVE_COLS.lg;\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.md) {\n return RESPONSIVE_COLS.md;\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.sm) {\n return RESPONSIVE_COLS.sm;\n }\n return RESPONSIVE_COLS.xs;\n}\n\n/**\n * Adjusts widget positions for a new column count (responsive)\n * Ensures widgets don't overflow the grid\n */\nexport function adjustPositionsForCols(\n widgets: Widget[],\n newCols: number\n): Map<string, { x: number; y: number; width: number; height: number }> {\n const adjustedPositions = new Map<string, { x: number; y: number; width: number; height: number }>();\n\n // Sort widgets by their original position (top-left first)\n const sortedWidgets = [...widgets].sort((a, b) => {\n const aY = a.positionY ?? 0;\n const bY = b.positionY ?? 0;\n const aX = a.positionX ?? 0;\n const bX = b.positionX ?? 0;\n return aY !== bY ? aY - bY : aX - bX;\n });\n\n // Recalculate positions for the new column count\n const occupancyMap = new Map<string, string>();\n\n sortedWidgets.forEach((widget) => {\n const width = Math.min(widget.positionWidth ?? 4, newCols);\n const height = widget.positionHeight ?? 4;\n\n // Find next available position\n let placed = false;\n for (let y = 0; y < 100 && !placed; y++) {\n for (let x = 0; x <= newCols - width && !placed; x++) {\n let canPlace = true;\n for (let dx = 0; dx < width && canPlace; dx++) {\n for (let dy = 0; dy < height && canPlace; dy++) {\n if (occupancyMap.has(`${x + dx},${y + dy}`)) {\n canPlace = false;\n }\n }\n }\n\n if (canPlace) {\n // Mark cells as occupied\n for (let dx = 0; dx < width; dx++) {\n for (let dy = 0; dy < height; dy++) {\n occupancyMap.set(`${x + dx},${y + dy}`, widget.id);\n }\n }\n\n adjustedPositions.set(widget.id, { x, y, width, height });\n placed = true;\n }\n }\n }\n });\n\n return adjustedPositions;\n}\n"],"mappings":";AA8BA,IAAa,IAAkC;CAC7C,MAAM;CACN,SAAS;CACV;AA0BD,SAAS,EAAmB,GAAwC;CAClE,IAAM,oBAAe,IAAI,KAAqB;AAe9C,QAbA,EAAQ,SAAS,MAAW;EAC1B,IAAM,IAAI,EAAO,aAAa,GACxB,IAAI,EAAO,aAAa,GACxB,IAAI,EAAO,iBAAiB,GAC5B,IAAI,EAAO,kBAAkB;AAEnC,OAAK,IAAI,IAAK,GAAG,IAAK,GAAG,IACvB,MAAK,IAAI,IAAK,GAAG,IAAK,GAAG,IACvB,GAAa,IAAI,GAAG,IAAI,EAAG,GAAG,IAAI,KAAM,EAAO,GAAG;GAGtD,EAEK;;AAMT,SAAS,EACP,GACA,GACA,GACA,GACA,GACA,GACS;AAET,KAAI,IAAI,IAAQ,EACd,QAAO;AAIT,MAAK,IAAI,IAAK,GAAG,IAAK,GAAO,IAC3B,MAAK,IAAI,IAAK,GAAG,IAAK,GAAQ,IAC5B,KAAI,EAAa,IAAI,GAAG,IAAI,EAAG,GAAG,IAAI,IAAK,CACzC,QAAO;AAKb,QAAO;;AAYT,SAAgB,EACd,GACA,GACA,IAAqB,GACK;CAC1B,IAAM,EAAE,SAAM,aAAU,QAAQ,GAC1B,EAAE,UAAO,cAAW;AAG1B,KAAI,EAAgB,WAAW,EAC7B,QAAO;EAAE,GAAG;EAAG,GAAG;EAAG;CAIvB,IAAM,IAAe,EAAmB,EAAgB,EAGlD,IAAe,KAAK,IAAI,GAAG,EAAgB,KAAK,OAAO,EAAE,aAAa,MAAM,EAAE,kBAAkB,GAAG,CAAC;AAG1G,MAAK,IAAI,IAAI,GAAG,KAAK,IAAe,KAAU,IAAI,GAAS,IACzD,MAAK,IAAI,IAAI,GAAG,KAAK,IAAO,GAAO,IACjC,KAAI,EAAe,GAAc,GAAG,GAAG,GAAO,GAAQ,EAAK,CACzD,QAAO;EAAE;EAAG;EAAG;AAMrB,QAAO;EAAE,GAAG;EAAG,GAAG;EAAc;;AA4ClC,SAAgB,EACd,GACA,GACA,GACA,GACA,GACA,IAAqB,GACK;CAC1B,IAAM,EAAE,YAAS,GACX,IAAc,IAAiB,GAG/B,IAAY,IAAQ,EAAW,MAC/B,IAAY,IAAQ,EAAW;AAKrC,QAAO;EAAE,GAHK,KAAK,IAAI,GAAG,KAAK,IAAI,IAAO,EAAW,OAAO,KAAK,MAAM,IAAY,EAAY,CAAC,CAAC;EAG9E,GAFL,KAAK,IAAI,GAAG,KAAK,MAAM,IAAY,GAAU,CAAC;EAE/B"}
|
|
1
|
+
{"version":3,"file":"widgetPositioning.js","names":[],"sources":["../../../src/bigconsole/utils/widgetPositioning.ts"],"sourcesContent":["/**\n * Widget Positioning Utilities\n *\n * Smart placement algorithm for dashboard widgets.\n * Places widgets horizontally first (left-to-right), then moves to next row.\n * Uses a grid occupancy map to find the first available slot.\n */\n\nimport type { ResponsivePosition, Widget } from '../types';\n\nexport interface WidgetSize {\n width: number;\n height: number;\n}\n\nexport interface WidgetPosition {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface GridConfig {\n cols: number;\n maxRows?: number;\n}\n\n/**\n * Default grid configuration\n */\nexport const DEFAULT_GRID_CONFIG: GridConfig = {\n cols: 12,\n maxRows: 100,\n};\n\n/**\n * Responsive breakpoints configuration\n */\nexport const RESPONSIVE_BREAKPOINTS = {\n xl: 1536,\n lg: 1200,\n md: 996,\n sm: 768,\n xs: 480,\n} as const;\n\n/**\n * Columns per breakpoint for responsive layout.\n * xl keeps the same 12-column grid as lg so existing widget spans remain\n * valid; the extra width simply yields wider columns on very large screens.\n */\nexport const RESPONSIVE_COLS = {\n xl: 12,\n lg: 12,\n md: 10,\n sm: 6,\n xs: 4,\n} as const;\n\n/**\n * Creates an occupancy map from existing widgets\n * Each occupied cell is marked with the widget ID\n */\nfunction createOccupancyMap(widgets: Widget[]): Map<string, string> {\n const occupancyMap = new Map<string, string>();\n\n widgets.forEach((widget) => {\n const x = widget.positionX ?? 0;\n const y = widget.positionY ?? 0;\n const w = widget.positionWidth ?? 4;\n const h = widget.positionHeight ?? 4;\n\n for (let dx = 0; dx < w; dx++) {\n for (let dy = 0; dy < h; dy++) {\n occupancyMap.set(`${x + dx},${y + dy}`, widget.id);\n }\n }\n });\n\n return occupancyMap;\n}\n\n/**\n * Checks if a widget can be placed at the given position\n */\nfunction canPlaceWidget(\n occupancyMap: Map<string, string>,\n x: number,\n y: number,\n width: number,\n height: number,\n cols: number\n): boolean {\n // Check if widget fits within grid columns\n if (x + width > cols) {\n return false;\n }\n\n // Check if all required cells are available\n for (let dx = 0; dx < width; dx++) {\n for (let dy = 0; dy < height; dy++) {\n if (occupancyMap.has(`${x + dx},${y + dy}`)) {\n return false;\n }\n }\n }\n\n return true;\n}\n\n/**\n * Finds the next available position for a widget using smart placement.\n * Scans row-by-row, left-to-right to find the first slot that fits.\n *\n * @param existingWidgets - Array of existing widgets on the dashboard\n * @param newWidgetSize - Size of the new widget to place\n * @param config - Grid configuration (columns, max rows)\n * @returns Position where the new widget should be placed\n */\nexport function findNextAvailablePosition(\n existingWidgets: Widget[],\n newWidgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols, maxRows = 100 } = config;\n const { width, height } = newWidgetSize;\n\n // Handle empty dashboard - place at origin\n if (existingWidgets.length === 0) {\n return { x: 0, y: 0 };\n }\n\n // Create occupancy map\n const occupancyMap = createOccupancyMap(existingWidgets);\n\n // Find the maximum Y to limit our search\n const maxExistingY = Math.max(...existingWidgets.map((w) => (w.positionY ?? 0) + (w.positionHeight ?? 4)));\n\n // Scan row by row, left to right, to find first available slot\n for (let y = 0; y <= maxExistingY + height && y < maxRows; y++) {\n for (let x = 0; x <= cols - width; x++) {\n if (canPlaceWidget(occupancyMap, x, y, width, height, cols)) {\n return { x, y };\n }\n }\n }\n\n // Fallback: place at the bottom of existing widgets\n return { x: 0, y: maxExistingY };\n}\n\n/**\n * Finds position to the right of a specific widget\n * Falls back to smart placement if no space to the right\n */\nexport function findPositionNextToWidget(\n existingWidgets: Widget[],\n referenceWidgetId: string,\n newWidgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols } = config;\n const referenceWidget = existingWidgets.find((w) => w.id === referenceWidgetId);\n\n if (!referenceWidget) {\n return findNextAvailablePosition(existingWidgets, newWidgetSize, config);\n }\n\n const refX = referenceWidget.positionX ?? 0;\n const refY = referenceWidget.positionY ?? 0;\n const refW = referenceWidget.positionWidth ?? 4;\n\n // Try to place to the right of reference widget\n const newX = refX + refW;\n const newY = refY;\n\n // Check if it fits\n if (newX + newWidgetSize.width <= cols) {\n const occupancyMap = createOccupancyMap(existingWidgets.filter((w) => w.id !== referenceWidgetId));\n\n if (canPlaceWidget(occupancyMap, newX, newY, newWidgetSize.width, newWidgetSize.height, cols)) {\n return { x: newX, y: newY };\n }\n }\n\n // Fall back to smart placement\n return findNextAvailablePosition(existingWidgets, newWidgetSize, config);\n}\n\n/**\n * Calculates position from drop coordinates (for drag-and-drop)\n */\nexport function calculatePositionFromDrop(\n dropX: number,\n dropY: number,\n canvasRect: DOMRect,\n containerWidth: number,\n widgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols } = config;\n const columnWidth = containerWidth / cols;\n const rowHeight = 60; // Match DashboardGrid rowHeight\n\n const relativeX = dropX - canvasRect.left;\n const relativeY = dropY - canvasRect.top;\n\n const gridX = Math.max(0, Math.min(cols - widgetSize.width, Math.floor(relativeX / columnWidth)));\n const gridY = Math.max(0, Math.floor(relativeY / rowHeight));\n\n return { x: gridX, y: gridY };\n}\n\n/**\n * Gets responsive columns for a given container width\n */\nexport function getResponsiveCols(containerWidth: number): number {\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.xl) {\n return RESPONSIVE_COLS.xl;\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.lg) {\n return RESPONSIVE_COLS.lg;\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.md) {\n return RESPONSIVE_COLS.md;\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.sm) {\n return RESPONSIVE_COLS.sm;\n }\n return RESPONSIVE_COLS.xs;\n}\n\n/** Responsive breakpoint tier identifiers, ordered smallest → largest. */\nexport type ResponsiveTier = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\n/**\n * Resolves the column span for a responsive tier, applying the documented\n * fallback chain. `xl` is optional and falls back to `lg` when not set, so\n * existing dashboards (xs/sm/md/lg only) are unaffected on xl screens.\n */\nexport function getResponsiveSpan(position: ResponsivePosition, tier: ResponsiveTier): number {\n switch (tier) {\n case 'xl':\n return position.xl ?? position.lg;\n case 'lg':\n return position.lg;\n case 'md':\n return position.md;\n case 'sm':\n return position.sm;\n case 'xs':\n default:\n return position.xs;\n }\n}\n\n/**\n * Maps a container width to its responsive tier.\n */\nexport function getResponsiveTier(containerWidth: number): ResponsiveTier {\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.xl) {\n return 'xl';\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.lg) {\n return 'lg';\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.md) {\n return 'md';\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.sm) {\n return 'sm';\n }\n return 'xs';\n}\n\n/**\n * Adjusts widget positions for a new column count (responsive)\n * Ensures widgets don't overflow the grid\n */\nexport function adjustPositionsForCols(\n widgets: Widget[],\n newCols: number\n): Map<string, { x: number; y: number; width: number; height: number }> {\n const adjustedPositions = new Map<string, { x: number; y: number; width: number; height: number }>();\n\n // Sort widgets by their original position (top-left first)\n const sortedWidgets = [...widgets].sort((a, b) => {\n const aY = a.positionY ?? 0;\n const bY = b.positionY ?? 0;\n const aX = a.positionX ?? 0;\n const bX = b.positionX ?? 0;\n return aY !== bY ? aY - bY : aX - bX;\n });\n\n // Recalculate positions for the new column count\n const occupancyMap = new Map<string, string>();\n\n sortedWidgets.forEach((widget) => {\n const width = Math.min(widget.positionWidth ?? 4, newCols);\n const height = widget.positionHeight ?? 4;\n\n // Find next available position\n let placed = false;\n for (let y = 0; y < 100 && !placed; y++) {\n for (let x = 0; x <= newCols - width && !placed; x++) {\n let canPlace = true;\n for (let dx = 0; dx < width && canPlace; dx++) {\n for (let dy = 0; dy < height && canPlace; dy++) {\n if (occupancyMap.has(`${x + dx},${y + dy}`)) {\n canPlace = false;\n }\n }\n }\n\n if (canPlace) {\n // Mark cells as occupied\n for (let dx = 0; dx < width; dx++) {\n for (let dy = 0; dy < height; dy++) {\n occupancyMap.set(`${x + dx},${y + dy}`, widget.id);\n }\n }\n\n adjustedPositions.set(widget.id, { x, y, width, height });\n placed = true;\n }\n }\n }\n });\n\n return adjustedPositions;\n}\n"],"mappings":";AA8BA,IAAa,IAAkC;CAC7C,MAAM;CACN,SAAS;CACV;AA8BD,SAAS,EAAmB,GAAwC;CAClE,IAAM,oBAAe,IAAI,KAAqB;AAe9C,QAbA,EAAQ,SAAS,MAAW;EAC1B,IAAM,IAAI,EAAO,aAAa,GACxB,IAAI,EAAO,aAAa,GACxB,IAAI,EAAO,iBAAiB,GAC5B,IAAI,EAAO,kBAAkB;AAEnC,OAAK,IAAI,IAAK,GAAG,IAAK,GAAG,IACvB,MAAK,IAAI,IAAK,GAAG,IAAK,GAAG,IACvB,GAAa,IAAI,GAAG,IAAI,EAAG,GAAG,IAAI,KAAM,EAAO,GAAG;GAGtD,EAEK;;AAMT,SAAS,EACP,GACA,GACA,GACA,GACA,GACA,GACS;AAET,KAAI,IAAI,IAAQ,EACd,QAAO;AAIT,MAAK,IAAI,IAAK,GAAG,IAAK,GAAO,IAC3B,MAAK,IAAI,IAAK,GAAG,IAAK,GAAQ,IAC5B,KAAI,EAAa,IAAI,GAAG,IAAI,EAAG,GAAG,IAAI,IAAK,CACzC,QAAO;AAKb,QAAO;;AAYT,SAAgB,EACd,GACA,GACA,IAAqB,GACK;CAC1B,IAAM,EAAE,SAAM,aAAU,QAAQ,GAC1B,EAAE,UAAO,cAAW;AAG1B,KAAI,EAAgB,WAAW,EAC7B,QAAO;EAAE,GAAG;EAAG,GAAG;EAAG;CAIvB,IAAM,IAAe,EAAmB,EAAgB,EAGlD,IAAe,KAAK,IAAI,GAAG,EAAgB,KAAK,OAAO,EAAE,aAAa,MAAM,EAAE,kBAAkB,GAAG,CAAC;AAG1G,MAAK,IAAI,IAAI,GAAG,KAAK,IAAe,KAAU,IAAI,GAAS,IACzD,MAAK,IAAI,IAAI,GAAG,KAAK,IAAO,GAAO,IACjC,KAAI,EAAe,GAAc,GAAG,GAAG,GAAO,GAAQ,EAAK,CACzD,QAAO;EAAE;EAAG;EAAG;AAMrB,QAAO;EAAE,GAAG;EAAG,GAAG;EAAc;;AA4ClC,SAAgB,EACd,GACA,GACA,GACA,GACA,GACA,IAAqB,GACK;CAC1B,IAAM,EAAE,YAAS,GACX,IAAc,IAAiB,GAG/B,IAAY,IAAQ,EAAW,MAC/B,IAAY,IAAQ,EAAW;AAKrC,QAAO;EAAE,GAHK,KAAK,IAAI,GAAG,KAAK,IAAI,IAAO,EAAW,OAAO,KAAK,MAAM,IAAY,EAAY,CAAC,CAAC;EAG9E,GAFL,KAAK,IAAI,GAAG,KAAK,MAAM,IAAY,GAAU,CAAC;EAE/B"}
|
|
@@ -1813,14 +1813,14 @@ var wt = e`
|
|
|
1813
1813
|
...WidgetActionFields
|
|
1814
1814
|
}
|
|
1815
1815
|
}
|
|
1816
|
-
${k}
|
|
1817
|
-
e`
|
|
1816
|
+
${k}`, Et = e`
|
|
1818
1817
|
query GetWidgetTypes {
|
|
1819
1818
|
getWidgetTypes {
|
|
1820
1819
|
...WidgetTypeInfoFields
|
|
1821
1820
|
}
|
|
1822
1821
|
}
|
|
1823
|
-
${N}
|
|
1822
|
+
${N}`;
|
|
1823
|
+
e`
|
|
1824
1824
|
query GetWidgetWithData($id: ID!) {
|
|
1825
1825
|
getWidget(id: $id) {
|
|
1826
1826
|
...WidgetCoreFields
|
|
@@ -1839,7 +1839,7 @@ e`
|
|
|
1839
1839
|
${E}
|
|
1840
1840
|
${D}
|
|
1841
1841
|
${O}`;
|
|
1842
|
-
var
|
|
1842
|
+
var $ = e`
|
|
1843
1843
|
query ListEventLogs($dashboardId: ID, $widgetId: ID, $eventName: String, $status: EventStatus, $first: Int, $after: String) {
|
|
1844
1844
|
listEventLogs(
|
|
1845
1845
|
dashboardId: $dashboardId
|
|
@@ -1862,7 +1862,7 @@ var Et = e`
|
|
|
1862
1862
|
}
|
|
1863
1863
|
}
|
|
1864
1864
|
${S}
|
|
1865
|
-
${w}`,
|
|
1865
|
+
${w}`, Dt = e`
|
|
1866
1866
|
query ListEventWebhookMappings($dashboardId: ID, $widgetId: ID, $isActive: Boolean) {
|
|
1867
1867
|
listEventWebhookMappings(
|
|
1868
1868
|
dashboardId: $dashboardId
|
|
@@ -1890,13 +1890,13 @@ e`
|
|
|
1890
1890
|
}
|
|
1891
1891
|
${M}
|
|
1892
1892
|
${w}`;
|
|
1893
|
-
var
|
|
1893
|
+
var Ot = e`
|
|
1894
1894
|
query ListWidgetsByDashboard($dashboardId: ID!, $type: WidgetType) {
|
|
1895
1895
|
listWidgetsByDashboard(dashboardId: $dashboardId, type: $type) {
|
|
1896
1896
|
...WidgetListFields
|
|
1897
1897
|
}
|
|
1898
1898
|
}
|
|
1899
|
-
${j}`,
|
|
1899
|
+
${j}`, kt = e`
|
|
1900
1900
|
query ListWidgetsForPage($pageId: ID!, $type: WidgetType) {
|
|
1901
1901
|
listWidgets(pageId: $pageId, type: $type, first: 100) {
|
|
1902
1902
|
edges {
|
|
@@ -1907,29 +1907,29 @@ var Dt = e`
|
|
|
1907
1907
|
totalCount
|
|
1908
1908
|
}
|
|
1909
1909
|
}
|
|
1910
|
-
${j}`,
|
|
1910
|
+
${j}`, At = e`
|
|
1911
1911
|
mutation CreateWorkflow($input: CreateWorkflowInput!) {
|
|
1912
1912
|
createWorkflow(input: $input) {
|
|
1913
1913
|
...WorkflowFullFields
|
|
1914
1914
|
}
|
|
1915
1915
|
}
|
|
1916
|
-
${I}`,
|
|
1916
|
+
${I}`, jt = e`
|
|
1917
1917
|
mutation DeleteWorkflow($id: ID!) {
|
|
1918
1918
|
deleteWorkflow(id: $id)
|
|
1919
1919
|
}
|
|
1920
|
-
`,
|
|
1920
|
+
`, Mt = e`
|
|
1921
1921
|
mutation ExecuteWorkflow($id: ID!, $triggerData: JSON) {
|
|
1922
1922
|
executeWorkflow(id: $id, triggerData: $triggerData) {
|
|
1923
1923
|
...WorkflowFullFields
|
|
1924
1924
|
}
|
|
1925
1925
|
}
|
|
1926
|
-
${I}`,
|
|
1926
|
+
${I}`, Nt = e`
|
|
1927
1927
|
mutation LinkDataSinkToWorkflow($workflowId: ID!, $input: LinkDataSinkInput!) {
|
|
1928
1928
|
linkDataSinkToWorkflow(workflowId: $workflowId, input: $input) {
|
|
1929
1929
|
...DataSinkWorkflowLinkFields
|
|
1930
1930
|
}
|
|
1931
1931
|
}
|
|
1932
|
-
${F}`,
|
|
1932
|
+
${F}`, Pt = e`
|
|
1933
1933
|
mutation UnlinkDataSinkFromWorkflow($workflowId: ID!, $dataSinkId: ID!) {
|
|
1934
1934
|
unlinkDataSinkFromWorkflow(workflowId: $workflowId, dataSinkId: $dataSinkId)
|
|
1935
1935
|
}
|
|
@@ -1941,25 +1941,25 @@ e`
|
|
|
1941
1941
|
}
|
|
1942
1942
|
}
|
|
1943
1943
|
${F}`;
|
|
1944
|
-
var
|
|
1944
|
+
var Ft = e`
|
|
1945
1945
|
mutation UpdateWorkflow($id: ID!, $input: UpdateWorkflowInput!, $version: Int!) {
|
|
1946
1946
|
updateWorkflow(id: $id, input: $input, version: $version) {
|
|
1947
1947
|
...WorkflowFullFields
|
|
1948
1948
|
}
|
|
1949
1949
|
}
|
|
1950
|
-
${I}`,
|
|
1950
|
+
${I}`, It = e`
|
|
1951
1951
|
query GetWorkflow($id: ID!) {
|
|
1952
1952
|
workflow(id: $id) {
|
|
1953
1953
|
...WorkflowFullFields
|
|
1954
1954
|
}
|
|
1955
1955
|
}
|
|
1956
|
-
${I}`,
|
|
1956
|
+
${I}`, Lt = e`
|
|
1957
1957
|
query GetWorkflowByKey($key: String!) {
|
|
1958
1958
|
workflowByKey(key: $key) {
|
|
1959
1959
|
...WorkflowFullFields
|
|
1960
1960
|
}
|
|
1961
1961
|
}
|
|
1962
|
-
${I}`,
|
|
1962
|
+
${I}`, Rt = e`
|
|
1963
1963
|
query ListWorkflows($filter: FluidGridsWorkflowFilterInput) {
|
|
1964
1964
|
workflows(filter: $filter) {
|
|
1965
1965
|
...WorkflowListItem
|
|
@@ -1967,6 +1967,6 @@ var Pt = e`
|
|
|
1967
1967
|
}
|
|
1968
1968
|
${L}`;
|
|
1969
1969
|
//#endregion
|
|
1970
|
-
export { z as ArchiveTemplateDocument, Ae as BatchCreateDataSinksDocument, je as BatchDeleteDataSinksDocument, it as BatchUpdateWidgetPositionsDocument, B as CloneDashboardDocument, V as CreateDashboardDocument, H as CreateDashboardPageDocument, U as CreateDashboardViewDocument, Me as CreateDataSinkDocument, at as CreateEventWebhookMappingDocument, W as CreateGlobalFilterDocument, He as CreateParserDocument, Ye as CreatePipelineDocument, G as CreateTemplateDocument, st as CreateWidgetActionDocument, ot as CreateWidgetDocument, ct as CreateWidgetDrilldownDocument,
|
|
1970
|
+
export { z as ArchiveTemplateDocument, Ae as BatchCreateDataSinksDocument, je as BatchDeleteDataSinksDocument, it as BatchUpdateWidgetPositionsDocument, B as CloneDashboardDocument, V as CreateDashboardDocument, H as CreateDashboardPageDocument, U as CreateDashboardViewDocument, Me as CreateDataSinkDocument, at as CreateEventWebhookMappingDocument, W as CreateGlobalFilterDocument, He as CreateParserDocument, Ye as CreatePipelineDocument, G as CreateTemplateDocument, st as CreateWidgetActionDocument, ot as CreateWidgetDocument, ct as CreateWidgetDrilldownDocument, At as CreateWorkflowDocument, Be as DataSinkDataUpdatedDocument, Ve as DataSinkUpdatedDocument, K as DeleteDashboardDocument, q as DeleteDashboardPageDocument, J as DeleteDashboardViewDocument, Ne as DeleteDataSinkDocument, lt as DeleteEventWebhookMappingDocument, Y as DeleteGlobalFilterDocument, Ue as DeleteParserDocument, Xe as DeletePipelineDocument, X as DeleteTemplateDocument, dt as DeleteWidgetActionDocument, ut as DeleteWidgetDocument, ft as DeleteWidgetDrilldownDocument, jt as DeleteWorkflowDocument, Z as DeprecateTemplateDocument, pt as DuplicateWidgetDocument, mt as EmitWidgetEventDocument, We as ExecuteParserDocument, Ze as ExecutePipelineDocument, Mt as ExecuteWorkflowDocument, _e as ExportDashboardDocument, ve as GetDashboardDocument, ye as GetDashboardViewDocument, Re as GetDataSinkDataDocument, Le as GetDataSinkDocument, be as GetDefaultDashboardViewDocument, Ke as GetParserDocument, et as GetPipelineDocument, tt as GetPipelineExecutionDocument, xe as GetTemplateDocument, Tt as GetWidgetActionsDocument, wt as GetWidgetDocument, Et as GetWidgetTypesDocument, Lt as GetWorkflowByKeyDocument, It as GetWorkflowDocument, Q as ImportDashboardDocument, ne as InstantiateTemplateDocument, Pe as InvalidateDataSinkCacheDocument, Nt as LinkDataSinkToWorkflowDocument, Se as ListDashboardSharesDocument, Ce as ListDashboardViewsDocument, we as ListDashboardsDocument, ze as ListDataSinksDocument, $ as ListEventLogsDocument, Dt as ListEventWebhookMappingsDocument, Te as ListFeaturedTemplatesDocument, qe as ListParsersDocument, nt as ListPipelineExecutionsDocument, rt as ListPipelinesDocument, Ee as ListSharedWithMeDocument, De as ListTemplatesDocument, Ot as ListWidgetsByDashboardDocument, kt as ListWidgetsForPageDocument, Rt as ListWorkflowsDocument, re as PublishTemplateDocument, ie as RateTemplateDocument, Fe as RefreshDataSinkDocument, ht as RefreshWidgetDataDocument, ae as ReorderDashboardPagesDocument, oe as ReorderGlobalFiltersDocument, gt as ReorderWidgetActionsDocument, _t as RetryEventDeliveryDocument, se as RevokeAllSharesDocument, ce as RevokeDashboardShareDocument, Oe as SearchTemplatesDocument, le as SetDefaultViewDocument, ue as ShareDashboardDocument, Je as TestParserExpressionDocument, Qe as TestPipelineDocument, vt as TestWebhookMappingDocument, yt as ToggleEventWebhookMappingDocument, Pt as UnlinkDataSinkFromWorkflowDocument, de as UpdateDashboardDocument, fe as UpdateDashboardPageDocument, pe as UpdateDashboardShareDocument, me as UpdateDashboardViewDocument, Ie as UpdateDataSinkDocument, bt as UpdateEventWebhookMappingDocument, he as UpdateGlobalFilterDocument, Ge as UpdateParserDocument, $e as UpdatePipelineDocument, ge as UpdateTemplateDocument, St as UpdateWidgetActionDocument, xt as UpdateWidgetDocument, Ct as UpdateWidgetDrilldownDocument, Ft as UpdateWorkflowDocument, ke as ValidateDashboardImportDocument, R as WorkspaceInstalledAppsDocument };
|
|
1971
1971
|
|
|
1972
1972
|
//# sourceMappingURL=wspace-operations.js.map
|