@oneuptime/common 12.0.4 → 12.0.5
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/Models/DatabaseModels/AIRun.ts +38 -1
- package/Models/DatabaseModels/AlertFeed.ts +30 -0
- package/Models/DatabaseModels/Dashboard.ts +28 -0
- package/Models/DatabaseModels/Incident.ts +2 -0
- package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
- package/Models/DatabaseModels/IncidentFeed.ts +30 -0
- package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
- package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
- package/Models/DatabaseModels/LogSavedView.ts +33 -0
- package/Models/DatabaseModels/Project.ts +231 -9
- package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.ts +4 -4
- package/Models/DatabaseModels/StatusPage.ts +28 -0
- package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
- package/Server/API/AIAgentDataAPI.ts +44 -55
- package/Server/API/AIInvestigationAPI.ts +175 -17
- package/Server/API/DashboardAPI.ts +10 -10
- package/Server/API/StatusPageAPI.ts +38 -19
- package/Server/EnvironmentConfig.ts +16 -0
- package/Server/Infrastructure/ClickhouseConfig.ts +15 -1
- package/Server/Infrastructure/ClickhouseDatabase.ts +1 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.ts +27 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.ts +89 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.ts +27 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.ts +26 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Services/AIRunService.ts +78 -10
- package/Server/Services/AIService.ts +94 -22
- package/Server/Services/AlertFeedService.ts +5 -0
- package/Server/Services/AnalyticsDatabaseService.ts +29 -0
- package/Server/Services/DatabaseService.ts +43 -2
- package/Server/Services/IncidentFeedService.ts +5 -0
- package/Server/Services/LlmLogService.ts +46 -2
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +6 -0
- package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +4 -0
- package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +8 -5
- package/Server/Utils/AI/CodeFix/FixRunBudget.ts +90 -16
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +2 -0
- package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +2 -0
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +223 -22
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +25 -24
- package/Server/Utils/AI/SRE/ConfidenceSignal.ts +182 -71
- package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +252 -43
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +25 -24
- package/Server/Utils/AI/SRE/InstrumentationTaskTrigger.ts +47 -13
- package/Server/Utils/AI/SRE/InvestigationGrader.ts +9 -5
- package/Server/Utils/AI/SRE/InvestigationQueue.ts +141 -24
- package/Server/Utils/AI/SRE/InvestigationSubjectLock.ts +66 -0
- package/Server/Utils/AI/SRE/PostedRootCause.ts +94 -3
- package/Server/Utils/AI/SRE/README.md +1 -1
- package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +132 -10
- package/Server/Utils/AnalyticsDatabase/ClusterConfig.ts +24 -0
- package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +29 -0
- package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +56 -0
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +46 -0
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +1 -0
- package/Server/Utils/PasswordHash.ts +28 -4
- package/Tests/App/Dashboard/AIInvestigationHeaderStatus.test.tsx +251 -0
- package/Tests/App/Dashboard/EventStatusPanel.test.tsx +747 -0
- package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +480 -0
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1184 -0
- package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +500 -0
- package/Tests/App/Dashboard/PortMonitorCriteriaFilter.test.ts +93 -0
- package/Tests/App/StatusPage/PublicStatusPageAPIErrorHandling.test.ts +117 -0
- package/Tests/App/StatusPage/StatusPageModelAPIPolymorphism.test.ts +223 -0
- package/Tests/Models/DatabaseModels/PermissionCatalogueCoverage.test.ts +243 -0
- package/Tests/Server/API/AIAgentDataFixFromIncidentContext.test.ts +266 -0
- package/Tests/Server/API/AIInvestigationAPI.test.ts +594 -0
- package/Tests/Server/API/AIInvestigationCreateFixTask.test.ts +80 -0
- package/Tests/Server/API/DashboardMasterPasswordAPI.test.ts +172 -1
- package/Tests/Server/API/StatusPageMasterPasswordAPI.test.ts +335 -0
- package/Tests/Server/Services/AIRunCodeFixClaim.test.ts +59 -5
- package/Tests/Server/Services/AIRunHumanVerdict.test.ts +127 -58
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +396 -20
- package/Tests/Server/Services/AddMasterPasswordSaltMigration.test.ts +283 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +25 -1
- package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +45 -35
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsWithoutHooks.test.ts +54 -0
- package/Tests/Server/Services/FeedAIRunAssociation.test.ts +125 -0
- package/Tests/Server/Services/FixFromIncidentTaskTrigger.test.ts +418 -57
- package/Tests/Server/Services/InstrumentationTaskTrigger.test.ts +173 -14
- package/Tests/Server/Services/MasterPasswordScrypt.test.ts +567 -0
- package/Tests/Server/Services/SeparateIncidentAlertAiSettingsMigration.test.ts +194 -0
- package/Tests/Server/Utils/AI/AIAlertGating.test.ts +37 -4
- package/Tests/Server/Utils/AI/AIConfidenceSignal.test.ts +330 -29
- package/Tests/Server/Utils/AI/AIIncidentGating.test.ts +25 -0
- package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +293 -5
- package/Tests/Server/Utils/AI/CodeFixAgentCompletion.test.ts +59 -1
- package/Tests/Server/Utils/AI/FixFromIncidentAutoTrigger.test.ts +342 -19
- package/Tests/Server/Utils/AI/FixRunBudget.test.ts +214 -30
- package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +2 -0
- package/Tests/Server/Utils/AI/InvestigationGrader.test.ts +65 -17
- package/Tests/Server/Utils/AI/InvestigationQueueRemediationExecution.test.ts +3 -0
- package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +313 -20
- package/Tests/Server/Utils/AI/PostedRootCause.test.ts +254 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +4 -0
- package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +2 -0
- package/Tests/Server/Utils/AI/SubjectCodeFixRunDedupe.test.ts +44 -0
- package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +212 -0
- package/Tests/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.test.ts +401 -0
- package/Tests/Server/Utils/Monitor/MonitorMetricUtilPortTimings.test.ts +243 -0
- package/Tests/Server/Utils/PasswordHash.test.ts +30 -0
- package/Tests/Types/Dashboard/DashboardTemplates.test.ts +111 -0
- package/Tests/Types/Monitor/CephMetricCatalog.test.ts +104 -0
- package/Tests/Types/Monitor/CriteriaFilter.test.ts +4 -0
- package/Tests/Types/Monitor/DockerMetricCatalog.test.ts +109 -0
- package/Tests/Types/Monitor/DockerSwarmMetricCatalog.test.ts +118 -0
- package/Tests/Types/Monitor/HostMetricCatalog.test.ts +104 -0
- package/Tests/Types/Monitor/IotMetricCatalog.test.ts +104 -0
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +118 -0
- package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +42 -0
- package/Tests/Types/Monitor/MonitorStep.test.ts +38 -0
- package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +75 -0
- package/Tests/Types/Monitor/PodmanMetricCatalog.test.ts +109 -0
- package/Tests/Types/Monitor/ProxmoxMetricCatalog.test.ts +107 -0
- package/Tests/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.test.ts +151 -0
- package/Tests/Types/Permission.test.ts +137 -0
- package/Tests/Types/Rum/SessionReplayMaskingMode.test.ts +101 -0
- package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +5 -5
- package/Tests/UI/Components/CardModelDetailEdit.test.tsx +3 -5
- package/Tests/UI/Components/ErrorBoundary.test.tsx +158 -0
- package/Tests/UI/Components/FeedItemSafeMode.test.tsx +74 -0
- package/Tests/UI/Components/MoreMenu.test.tsx +756 -0
- package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +62 -0
- package/Tests/UI/Monitor/PortMonitorView.test.tsx +237 -0
- package/Tests/UI/Telemetry/TelemetrySnapshotWindowAlert.test.tsx +91 -0
- package/Tests/UI/Utils/DownloadFile.test.ts +103 -0
- package/Tests/UI/Utils/ErrorSupportBundle.test.ts +582 -0
- package/Tests/UI/Utils/Project.test.ts +31 -0
- package/Tests/UI/Utils/StatusPageModelAPIInjection.test.ts +245 -0
- package/Tests/UI/Utils/UseDashboardGridDnd.test.tsx +820 -0
- package/Tests/Utils/Dashboard/DashboardViewConfig.test.ts +329 -0
- package/Tests/Utils/Dashboard/GridLayout.test.ts +951 -0
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +117 -0
- package/Tests/Utils/RecordingRuleExpression.test.ts +327 -0
- package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +186 -0
- package/Tests/Utils/StatusPage/GroupTree.test.ts +456 -0
- package/Tests/Utils/StatusPage/OverviewGroupHierarchyVisibility.test.ts +588 -0
- package/Tests/Utils/Telemetry/SavedViewTimeRange.test.ts +244 -0
- package/Tests/Utils/Telemetry/TelemetryQueryTimeRange.test.ts +825 -0
- package/Types/AI/AIRunCodeFixRecommendation.ts +17 -0
- package/Types/AI/CodeFixTaskContext.ts +63 -3
- package/Types/AI/CodeFixTaskType.ts +3 -3
- package/Types/HashedString.ts +4 -3
- package/Types/Monitor/CriteriaFilter.ts +4 -0
- package/Types/Monitor/DomainMonitor/DomainLookupMethod.ts +23 -0
- package/Types/Monitor/DomainMonitor/DomainMonitorResponse.ts +7 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +24 -7
- package/Types/Monitor/MonitorMetricType.ts +8 -0
- package/Types/Monitor/MonitorStep.ts +10 -1
- package/Types/Monitor/MonitorStepDomainMonitor.ts +21 -0
- package/Types/Monitor/PortMonitor/PortMonitorTimings.ts +10 -0
- package/Types/Permission.ts +23 -175
- package/Types/Probe/ProbeMonitorResponse.ts +6 -0
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +2 -3
- package/UI/Components/ErrorBoundary.tsx +138 -11
- package/UI/Components/Feed/FeedItem.tsx +9 -2
- package/UI/Components/ModelDetail/CardModelDetail.tsx +0 -12
- package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +12 -2
- package/UI/Components/MoreMenu/MoreMenu.tsx +285 -97
- package/UI/Components/MoreMenu/MoreMenuItem.tsx +8 -5
- package/UI/Utils/DownloadFile.ts +16 -8
- package/UI/Utils/ErrorSupportBundle.ts +805 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +19 -8
- package/UI/Utils/Project.ts +4 -0
- package/UI/Utils/StatusPage.ts +8 -2
- package/UI/Utils/UseDashboardGridDnd.ts +917 -0
- package/Utils/Dashboard/DashboardViewConfig.ts +80 -36
- package/Utils/Dashboard/GridLayout.ts +523 -0
- package/Utils/Metrics/RecordingRuleExpression.ts +19 -17
- package/Utils/Monitor/MonitorMetricType.ts +44 -2
- package/Utils/StatusPage/GroupNestingLayout.ts +69 -0
- package/Utils/StatusPage/GroupTree.ts +157 -66
- package/Utils/StatusPage/ResourceUptime.ts +37 -2
- package/Utils/Telemetry/SavedViewTimeRange.ts +114 -0
- package/Utils/Telemetry/TelemetryQueryTimeRange.ts +334 -0
- package/build/dist/Models/DatabaseModels/AIRun.js +39 -1
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertFeed.js +32 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Dashboard.js +29 -0
- package/build/dist/Models/DatabaseModels/Dashboard.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +2 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentFeed.js +32 -0
- package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LogSavedView.js +33 -0
- package/build/dist/Models/DatabaseModels/LogSavedView.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +241 -11
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.js +4 -4
- package/build/dist/Models/DatabaseModels/StatusPage.js +29 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +28 -46
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +111 -11
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +8 -5
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +29 -11
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +14 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js +12 -1
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseDatabase.js +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseDatabase.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js +36 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AIRunService.js +58 -12
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +74 -21
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertFeedService.js +3 -0
- package/build/dist/Server/Services/AlertFeedService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +29 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +21 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentFeedService.js +3 -0
- package/build/dist/Server/Services/IncidentFeedService.js.map +1 -1
- package/build/dist/Server/Services/LlmLogService.js +22 -1
- package/build/dist/Server/Services/LlmLogService.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +3 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +4 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +8 -5
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +63 -24
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +2 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +2 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +156 -28
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +21 -19
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js +128 -49
- package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js +170 -31
- package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +21 -19
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js +33 -9
- package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +9 -5
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +121 -41
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js +46 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js +61 -14
- package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +79 -1
- package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js +21 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +20 -3
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +43 -0
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +33 -0
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Server/Utils/PasswordHash.js +25 -4
- package/build/dist/Server/Utils/PasswordHash.js.map +1 -1
- package/build/dist/Types/AI/AIRunCodeFixRecommendation.js +18 -0
- package/build/dist/Types/AI/AIRunCodeFixRecommendation.js.map +1 -0
- package/build/dist/Types/AI/CodeFixTaskContext.js +23 -3
- package/build/dist/Types/AI/CodeFixTaskContext.js.map +1 -1
- package/build/dist/Types/AI/CodeFixTaskType.js +3 -3
- package/build/dist/Types/HashedString.js +4 -3
- package/build/dist/Types/HashedString.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +4 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js +24 -0
- package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +24 -7
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorMetricType.js +7 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +6 -1
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js +15 -0
- package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js.map +1 -1
- package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js +2 -0
- package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js.map +1 -0
- package/build/dist/Types/Permission.js +23 -155
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +2 -5
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
- package/build/dist/UI/Components/ErrorBoundary.js +67 -8
- package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedItem.js +2 -2
- package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +0 -12
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +12 -2
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js +193 -65
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
- package/build/dist/UI/Utils/DownloadFile.js +15 -8
- package/build/dist/UI/Utils/DownloadFile.js.map +1 -1
- package/build/dist/UI/Utils/ErrorSupportBundle.js +476 -0
- package/build/dist/UI/Utils/ErrorSupportBundle.js.map +1 -0
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -8
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/Project.js +3 -0
- package/build/dist/UI/Utils/Project.js.map +1 -1
- package/build/dist/UI/Utils/StatusPage.js +4 -4
- package/build/dist/UI/Utils/StatusPage.js.map +1 -1
- package/build/dist/UI/Utils/UseDashboardGridDnd.js +551 -0
- package/build/dist/UI/Utils/UseDashboardGridDnd.js.map +1 -0
- package/build/dist/Utils/Dashboard/DashboardViewConfig.js +42 -26
- package/build/dist/Utils/Dashboard/DashboardViewConfig.js.map +1 -1
- package/build/dist/Utils/Dashboard/GridLayout.js +338 -0
- package/build/dist/Utils/Dashboard/GridLayout.js.map +1 -0
- package/build/dist/Utils/Metrics/RecordingRuleExpression.js +19 -17
- package/build/dist/Utils/Metrics/RecordingRuleExpression.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +38 -6
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/StatusPage/GroupNestingLayout.js +46 -0
- package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -1
- package/build/dist/Utils/StatusPage/GroupTree.js +79 -34
- package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -1
- package/build/dist/Utils/StatusPage/ResourceUptime.js +22 -1
- package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
- package/build/dist/Utils/Telemetry/SavedViewTimeRange.js +79 -0
- package/build/dist/Utils/Telemetry/SavedViewTimeRange.js.map +1 -0
- package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js +236 -0
- package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,917 @@
|
|
|
1
|
+
import GridLayoutUtil, { GridRect } from "../../Utils/Dashboard/GridLayout";
|
|
2
|
+
import React, {
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from "react";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Drag-and-drop + resize controller for the dashboard grid.
|
|
12
|
+
*
|
|
13
|
+
* Design notes, because the split of responsibilities is what makes this
|
|
14
|
+
* feel smooth:
|
|
15
|
+
*
|
|
16
|
+
* - The ACTIVE widget's DOM element is driven imperatively (transform /
|
|
17
|
+
* width / height updated in a requestAnimationFrame loop). React never
|
|
18
|
+
* repositions it mid-gesture, so charts and tables inside it are not
|
|
19
|
+
* re-rendered 60 times a second.
|
|
20
|
+
* - Everything else — the snapped drop placeholder and neighbours being
|
|
21
|
+
* pushed out of the way — renders through React state, but that state
|
|
22
|
+
* only changes when the SNAPPED grid cell changes (a few times per
|
|
23
|
+
* gesture), never per pointer event.
|
|
24
|
+
* - The preview layout is always recomputed from the layout as it was when
|
|
25
|
+
* the gesture started (`moveRect`/`resizeRect` are pure), so wiggling the
|
|
26
|
+
* pointer back and forth can never accumulate stray pushes.
|
|
27
|
+
* - On drop or cancel the element is "settled" imperatively to its final
|
|
28
|
+
* rect with transitions enabled, giving a snap-into-place animation, and
|
|
29
|
+
* React then renders the exact same values so there is nothing to fight
|
|
30
|
+
* over.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
export type ResizeDirection = "n" | "s" | "e" | "w" | "ne" | "nw" | "se" | "sw";
|
|
34
|
+
|
|
35
|
+
export type GridDndMode = "move" | "resize";
|
|
36
|
+
|
|
37
|
+
export interface GridDndMetrics {
|
|
38
|
+
/** Width (= height) of one dashboard unit, in px. */
|
|
39
|
+
unitSizeInPx: number;
|
|
40
|
+
/** Gap between units, in px. */
|
|
41
|
+
gapInPx: number;
|
|
42
|
+
columns: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface DashboardGridDndProps {
|
|
46
|
+
/** Committed layout, one rect per widget. */
|
|
47
|
+
items: Array<GridRect>;
|
|
48
|
+
metrics: GridDndMetrics;
|
|
49
|
+
isEnabled: boolean;
|
|
50
|
+
/** The positioning container widgets are absolutely placed in. */
|
|
51
|
+
canvasRef: React.RefObject<HTMLElement>;
|
|
52
|
+
onCommit: (rects: Array<GridRect>) => void;
|
|
53
|
+
/** Pointer went down and came up without dragging — treat as a click. */
|
|
54
|
+
onItemClick?: ((id: string) => void) | undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface DashboardGridDndResult {
|
|
58
|
+
/**
|
|
59
|
+
* Rects to render each widget at. While a gesture is active the active
|
|
60
|
+
* widget stays pinned at its origin (its DOM node is moved imperatively)
|
|
61
|
+
* and neighbours reflect the live push-preview.
|
|
62
|
+
*/
|
|
63
|
+
rects: Array<GridRect>;
|
|
64
|
+
/** Snapped destination of the active widget — render the drop ghost here. */
|
|
65
|
+
placeholderRect: GridRect | null;
|
|
66
|
+
/** Rows needed to contain the live layout (including the placeholder). */
|
|
67
|
+
totalRows: number;
|
|
68
|
+
activeId: string | null;
|
|
69
|
+
activeMode: GridDndMode | null;
|
|
70
|
+
/** True once the pointer has actually moved past the drag threshold. */
|
|
71
|
+
isGestureActive: boolean;
|
|
72
|
+
registerItemElement: (id: string, element: HTMLElement | null) => void;
|
|
73
|
+
startMove: (id: string, event: React.PointerEvent) => void;
|
|
74
|
+
startResize: (
|
|
75
|
+
id: string,
|
|
76
|
+
direction: ResizeDirection,
|
|
77
|
+
event: React.PointerEvent,
|
|
78
|
+
) => void;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Pointer must travel this many px before a press becomes a drag. */
|
|
82
|
+
const DRAG_THRESHOLD_IN_PX: number = 5;
|
|
83
|
+
|
|
84
|
+
/** Distance from the scroll-viewport edge where auto-scroll kicks in. */
|
|
85
|
+
const AUTO_SCROLL_EDGE_IN_PX: number = 72;
|
|
86
|
+
|
|
87
|
+
/** Fastest auto-scroll speed, px per frame. */
|
|
88
|
+
const AUTO_SCROLL_MAX_SPEED_IN_PX: number = 28;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* How many rows past the current board bottom a single gesture may reach.
|
|
92
|
+
* Without a cap, dragging against the bottom edge auto-scrolls forever:
|
|
93
|
+
* the container grows to fit the candidate, which mints fresh scroll room,
|
|
94
|
+
* which moves the candidate further down. Committing near the cap raises
|
|
95
|
+
* the board bottom, so taller boards remain reachable across gestures.
|
|
96
|
+
*/
|
|
97
|
+
const AUTO_GROW_SLACK_ROWS: number = 6;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Transition the canvas should put on every idle widget wrapper. The hook
|
|
101
|
+
* writes the exact same string when settling a dropped widget, so React's
|
|
102
|
+
* subsequent render is guaranteed to be a no-op style-wise.
|
|
103
|
+
*/
|
|
104
|
+
export const GRID_ITEM_TRANSITION: string =
|
|
105
|
+
"transform 0.22s cubic-bezier(0.2, 0, 0, 1), width 0.22s cubic-bezier(0.2, 0, 0, 1), height 0.22s cubic-bezier(0.2, 0, 0, 1)";
|
|
106
|
+
|
|
107
|
+
interface PxRect {
|
|
108
|
+
x: number;
|
|
109
|
+
y: number;
|
|
110
|
+
width: number;
|
|
111
|
+
height: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface GestureSession {
|
|
115
|
+
id: string;
|
|
116
|
+
mode: GridDndMode;
|
|
117
|
+
direction: ResizeDirection | null;
|
|
118
|
+
pointerId: number;
|
|
119
|
+
/** Normalized snapshot of the layout when the gesture began. */
|
|
120
|
+
originRects: Array<GridRect>;
|
|
121
|
+
originRect: GridRect;
|
|
122
|
+
/** Pointer position, canvas-relative, at gesture start. */
|
|
123
|
+
startCanvasX: number;
|
|
124
|
+
startCanvasY: number;
|
|
125
|
+
/** Pointer position, client coords, at gesture start (for threshold). */
|
|
126
|
+
startClientX: number;
|
|
127
|
+
startClientY: number;
|
|
128
|
+
latestClientX: number;
|
|
129
|
+
latestClientY: number;
|
|
130
|
+
/**
|
|
131
|
+
* Where inside the widget the pointer grabbed it, as a FRACTION of the
|
|
132
|
+
* widget's size — fractions survive mid-drag zoom/resize of the canvas,
|
|
133
|
+
* frozen pixel offsets do not.
|
|
134
|
+
*/
|
|
135
|
+
grabFracX: number;
|
|
136
|
+
grabFracY: number;
|
|
137
|
+
/** Lowest row edge this gesture is allowed to reach. */
|
|
138
|
+
maxBottomUnits: number;
|
|
139
|
+
started: boolean;
|
|
140
|
+
/** Snapped candidate rect currently previewed. */
|
|
141
|
+
candidate: GridRect;
|
|
142
|
+
rafId: number | null;
|
|
143
|
+
/**
|
|
144
|
+
* Removes exactly the window listeners this session added. Stored on the
|
|
145
|
+
* session because later renders create new handler instances — any
|
|
146
|
+
* endGesture closure must detach the ORIGINAL ones.
|
|
147
|
+
*/
|
|
148
|
+
detach: (() => void) | null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
interface PreviewState {
|
|
152
|
+
rects: Array<GridRect>;
|
|
153
|
+
activeRect: GridRect;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
interface GestureState {
|
|
157
|
+
id: string;
|
|
158
|
+
mode: GridDndMode;
|
|
159
|
+
started: boolean;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
type UseDashboardGridDndFunction = (
|
|
163
|
+
props: DashboardGridDndProps,
|
|
164
|
+
) => DashboardGridDndResult;
|
|
165
|
+
|
|
166
|
+
const useDashboardGridDnd: UseDashboardGridDndFunction = (
|
|
167
|
+
props: DashboardGridDndProps,
|
|
168
|
+
): DashboardGridDndResult => {
|
|
169
|
+
const [preview, setPreview] = useState<PreviewState | null>(null);
|
|
170
|
+
const [gesture, setGesture] = useState<GestureState | null>(null);
|
|
171
|
+
|
|
172
|
+
const sessionRef: React.MutableRefObject<GestureSession | null> =
|
|
173
|
+
useRef<GestureSession | null>(null);
|
|
174
|
+
const elementsRef: React.MutableRefObject<Map<string, HTMLElement>> = useRef<
|
|
175
|
+
Map<string, HTMLElement>
|
|
176
|
+
>(new Map<string, HTMLElement>());
|
|
177
|
+
|
|
178
|
+
// Live mirrors so the imperative handlers never see stale props.
|
|
179
|
+
const propsRef: React.MutableRefObject<DashboardGridDndProps> =
|
|
180
|
+
useRef<DashboardGridDndProps>(props);
|
|
181
|
+
propsRef.current = props;
|
|
182
|
+
|
|
183
|
+
const registerItemElement: (id: string, element: HTMLElement | null) => void =
|
|
184
|
+
useCallback((id: string, element: HTMLElement | null): void => {
|
|
185
|
+
if (element) {
|
|
186
|
+
elementsRef.current.set(id, element);
|
|
187
|
+
} else {
|
|
188
|
+
elementsRef.current.delete(id);
|
|
189
|
+
}
|
|
190
|
+
}, []);
|
|
191
|
+
|
|
192
|
+
// ── px helpers ────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
type CellSizeFunction = () => number;
|
|
195
|
+
const getCellSize: CellSizeFunction = (): number => {
|
|
196
|
+
const m: GridDndMetrics = propsRef.current.metrics;
|
|
197
|
+
return m.unitSizeInPx + m.gapInPx;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
type UnitsToPxFunction = (units: number) => number;
|
|
201
|
+
const unitsToPositionPx: UnitsToPxFunction = (units: number): number => {
|
|
202
|
+
return units * getCellSize();
|
|
203
|
+
};
|
|
204
|
+
const unitsToSizePx: UnitsToPxFunction = (units: number): number => {
|
|
205
|
+
const m: GridDndMetrics = propsRef.current.metrics;
|
|
206
|
+
return units * m.unitSizeInPx + (units - 1) * m.gapInPx;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
type RectToPxFunction = (rect: GridRect) => PxRect;
|
|
210
|
+
const rectToPx: RectToPxFunction = (rect: GridRect): PxRect => {
|
|
211
|
+
return {
|
|
212
|
+
x: unitsToPositionPx(rect.left),
|
|
213
|
+
y: unitsToPositionPx(rect.top),
|
|
214
|
+
width: unitsToSizePx(rect.width),
|
|
215
|
+
height: unitsToSizePx(rect.height),
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// ── element styling ───────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
type ApplyPxFunction = (element: HTMLElement, px: PxRect) => void;
|
|
222
|
+
const applyPxRect: ApplyPxFunction = (
|
|
223
|
+
element: HTMLElement,
|
|
224
|
+
px: PxRect,
|
|
225
|
+
): void => {
|
|
226
|
+
element.style.transform = `translate(${px.x}px, ${px.y}px)`;
|
|
227
|
+
element.style.width = `${px.width}px`;
|
|
228
|
+
element.style.height = `${px.height}px`;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
type SettleFunction = (id: string, rect: GridRect) => void;
|
|
232
|
+
const settleElement: SettleFunction = (id: string, rect: GridRect): void => {
|
|
233
|
+
const element: HTMLElement | undefined = elementsRef.current.get(id);
|
|
234
|
+
if (!element) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
element.style.transition = GRID_ITEM_TRANSITION;
|
|
238
|
+
element.style.willChange = "";
|
|
239
|
+
applyPxRect(element, rectToPx(rect));
|
|
240
|
+
/*
|
|
241
|
+
* Keep the card elevated while it glides into place; dropping the
|
|
242
|
+
* z-index immediately would paint it underneath the neighbours it is
|
|
243
|
+
* still crossing. Clear only after the settle transition, and only if
|
|
244
|
+
* no new gesture grabbed the same card in the meantime.
|
|
245
|
+
*/
|
|
246
|
+
window.setTimeout(() => {
|
|
247
|
+
const settled: HTMLElement | undefined = elementsRef.current.get(id);
|
|
248
|
+
if (settled && sessionRef.current?.id !== id) {
|
|
249
|
+
settled.style.zIndex = "";
|
|
250
|
+
}
|
|
251
|
+
}, 260);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// ── auto-scroll ───────────────────────────────────────────────────────
|
|
255
|
+
|
|
256
|
+
type FindScrollParentFunction = (
|
|
257
|
+
element: HTMLElement | null,
|
|
258
|
+
) => HTMLElement | null;
|
|
259
|
+
const findScrollParent: FindScrollParentFunction = (
|
|
260
|
+
element: HTMLElement | null,
|
|
261
|
+
): HTMLElement | null => {
|
|
262
|
+
let current: HTMLElement | null = element?.parentElement || null;
|
|
263
|
+
while (current) {
|
|
264
|
+
const style: CSSStyleDeclaration = window.getComputedStyle(current);
|
|
265
|
+
const overflowY: string = style.overflowY;
|
|
266
|
+
const isScrollable: boolean =
|
|
267
|
+
(overflowY === "auto" || overflowY === "scroll") &&
|
|
268
|
+
current.scrollHeight > current.clientHeight;
|
|
269
|
+
if (isScrollable) {
|
|
270
|
+
return current;
|
|
271
|
+
}
|
|
272
|
+
current = current.parentElement;
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
type AutoScrollFunction = (clientY: number) => void;
|
|
278
|
+
const autoScrollIfNearEdge: AutoScrollFunction = (clientY: number): void => {
|
|
279
|
+
const canvas: HTMLElement | null = propsRef.current.canvasRef.current;
|
|
280
|
+
const scrollParent: HTMLElement | null = findScrollParent(canvas);
|
|
281
|
+
|
|
282
|
+
let viewportTop: number = 0;
|
|
283
|
+
let viewportBottom: number = window.innerHeight;
|
|
284
|
+
|
|
285
|
+
if (scrollParent) {
|
|
286
|
+
const rect: DOMRect = scrollParent.getBoundingClientRect();
|
|
287
|
+
viewportTop = Math.max(rect.top, 0);
|
|
288
|
+
viewportBottom = Math.min(rect.bottom, window.innerHeight);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
let speed: number = 0;
|
|
292
|
+
|
|
293
|
+
const distanceFromTop: number = clientY - viewportTop;
|
|
294
|
+
const distanceFromBottom: number = viewportBottom - clientY;
|
|
295
|
+
|
|
296
|
+
if (distanceFromTop < AUTO_SCROLL_EDGE_IN_PX) {
|
|
297
|
+
const intensity: number =
|
|
298
|
+
1 - Math.max(distanceFromTop, 0) / AUTO_SCROLL_EDGE_IN_PX;
|
|
299
|
+
speed = -Math.ceil(intensity * AUTO_SCROLL_MAX_SPEED_IN_PX);
|
|
300
|
+
} else if (distanceFromBottom < AUTO_SCROLL_EDGE_IN_PX) {
|
|
301
|
+
const intensity: number =
|
|
302
|
+
1 - Math.max(distanceFromBottom, 0) / AUTO_SCROLL_EDGE_IN_PX;
|
|
303
|
+
speed = Math.ceil(intensity * AUTO_SCROLL_MAX_SPEED_IN_PX);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (speed === 0) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (scrollParent) {
|
|
311
|
+
scrollParent.scrollTop += speed;
|
|
312
|
+
} else {
|
|
313
|
+
window.scrollBy(0, speed);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// ── candidate computation ─────────────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Raw (unsnapped) px rect for the active widget, following the pointer.
|
|
321
|
+
* This is what the element visually tracks; the snapped candidate is
|
|
322
|
+
* derived from it.
|
|
323
|
+
*/
|
|
324
|
+
type ComputeRawRectFunction = (session: GestureSession) => PxRect;
|
|
325
|
+
const computeRawRect: ComputeRawRectFunction = (
|
|
326
|
+
session: GestureSession,
|
|
327
|
+
): PxRect => {
|
|
328
|
+
const canvas: HTMLElement | null = propsRef.current.canvasRef.current;
|
|
329
|
+
const metrics: GridDndMetrics = propsRef.current.metrics;
|
|
330
|
+
const canvasRect: DOMRect | null = canvas
|
|
331
|
+
? canvas.getBoundingClientRect()
|
|
332
|
+
: null;
|
|
333
|
+
|
|
334
|
+
const pointerCanvasX: number = canvasRect
|
|
335
|
+
? session.latestClientX - canvasRect.left
|
|
336
|
+
: session.latestClientX;
|
|
337
|
+
const pointerCanvasY: number = canvasRect
|
|
338
|
+
? session.latestClientY - canvasRect.top
|
|
339
|
+
: session.latestClientY;
|
|
340
|
+
|
|
341
|
+
const origin: PxRect = rectToPx(session.originRect);
|
|
342
|
+
const boardWidth: number = unitsToSizePx(metrics.columns);
|
|
343
|
+
|
|
344
|
+
if (session.mode === "move") {
|
|
345
|
+
const grabOffsetX: number = session.grabFracX * origin.width;
|
|
346
|
+
const grabOffsetY: number = session.grabFracY * origin.height;
|
|
347
|
+
const maxTopUnits: number = Math.max(
|
|
348
|
+
session.maxBottomUnits - session.originRect.height,
|
|
349
|
+
0,
|
|
350
|
+
);
|
|
351
|
+
let x: number = pointerCanvasX - grabOffsetX;
|
|
352
|
+
let y: number = pointerCanvasY - grabOffsetY;
|
|
353
|
+
x = Math.min(Math.max(x, 0), Math.max(boardWidth - origin.width, 0));
|
|
354
|
+
y = Math.min(Math.max(y, 0), unitsToPositionPx(maxTopUnits));
|
|
355
|
+
return { x, y, width: origin.width, height: origin.height };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Resize: apply the pointer delta to the grabbed edge(s).
|
|
359
|
+
const dx: number = pointerCanvasX - session.startCanvasX;
|
|
360
|
+
const dy: number = pointerCanvasY - session.startCanvasY;
|
|
361
|
+
|
|
362
|
+
const direction: ResizeDirection = session.direction || "se";
|
|
363
|
+
|
|
364
|
+
const minWidthPx: number = unitsToSizePx(
|
|
365
|
+
Math.min(session.originRect.minWidth, metrics.columns),
|
|
366
|
+
);
|
|
367
|
+
const minHeightPx: number = unitsToSizePx(session.originRect.minHeight);
|
|
368
|
+
|
|
369
|
+
let x: number = origin.x;
|
|
370
|
+
let y: number = origin.y;
|
|
371
|
+
let width: number = origin.width;
|
|
372
|
+
let height: number = origin.height;
|
|
373
|
+
|
|
374
|
+
const right: number = origin.x + origin.width;
|
|
375
|
+
const bottom: number = origin.y + origin.height;
|
|
376
|
+
|
|
377
|
+
if (direction.includes("e")) {
|
|
378
|
+
width = Math.min(Math.max(origin.width + dx, minWidthPx), boardWidth - x);
|
|
379
|
+
}
|
|
380
|
+
if (direction.includes("s")) {
|
|
381
|
+
const maxHeightPx: number = unitsToSizePx(
|
|
382
|
+
Math.max(
|
|
383
|
+
session.maxBottomUnits - session.originRect.top,
|
|
384
|
+
session.originRect.minHeight,
|
|
385
|
+
),
|
|
386
|
+
);
|
|
387
|
+
height = Math.min(Math.max(origin.height + dy, minHeightPx), maxHeightPx);
|
|
388
|
+
}
|
|
389
|
+
if (direction.includes("w")) {
|
|
390
|
+
x = Math.min(Math.max(origin.x + dx, 0), right - minWidthPx);
|
|
391
|
+
width = right - x;
|
|
392
|
+
}
|
|
393
|
+
if (direction.includes("n")) {
|
|
394
|
+
y = Math.min(Math.max(origin.y + dy, 0), bottom - minHeightPx);
|
|
395
|
+
height = bottom - y;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return { x, y, width, height };
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
/** Snap a raw px rect onto the grid, clamped into bounds. */
|
|
402
|
+
type SnapFunction = (session: GestureSession, raw: PxRect) => GridRect;
|
|
403
|
+
const snapToGrid: SnapFunction = (
|
|
404
|
+
session: GestureSession,
|
|
405
|
+
raw: PxRect,
|
|
406
|
+
): GridRect => {
|
|
407
|
+
const metrics: GridDndMetrics = propsRef.current.metrics;
|
|
408
|
+
const cell: number = getCellSize();
|
|
409
|
+
|
|
410
|
+
const sizeToUnits: (px: number) => number = (px: number): number => {
|
|
411
|
+
return Math.max(1, Math.round((px + metrics.gapInPx) / cell));
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
if (session.mode === "move") {
|
|
415
|
+
const maxTopUnits: number = Math.max(
|
|
416
|
+
session.maxBottomUnits - session.originRect.height,
|
|
417
|
+
0,
|
|
418
|
+
);
|
|
419
|
+
return GridLayoutUtil.clampRect(
|
|
420
|
+
{
|
|
421
|
+
...session.originRect,
|
|
422
|
+
left: Math.round(raw.x / cell),
|
|
423
|
+
top: Math.min(Math.round(raw.y / cell), maxTopUnits),
|
|
424
|
+
},
|
|
425
|
+
metrics.columns,
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const direction: ResizeDirection = session.direction || "se";
|
|
430
|
+
|
|
431
|
+
let left: number = session.originRect.left;
|
|
432
|
+
let top: number = session.originRect.top;
|
|
433
|
+
let width: number = session.originRect.width;
|
|
434
|
+
let height: number = session.originRect.height;
|
|
435
|
+
|
|
436
|
+
const rightEdge: number =
|
|
437
|
+
session.originRect.left + session.originRect.width;
|
|
438
|
+
const bottomEdge: number =
|
|
439
|
+
session.originRect.top + session.originRect.height;
|
|
440
|
+
|
|
441
|
+
if (direction.includes("e")) {
|
|
442
|
+
width = sizeToUnits(raw.width);
|
|
443
|
+
}
|
|
444
|
+
if (direction.includes("s")) {
|
|
445
|
+
height = sizeToUnits(raw.height);
|
|
446
|
+
}
|
|
447
|
+
if (direction.includes("w")) {
|
|
448
|
+
width = sizeToUnits(raw.width);
|
|
449
|
+
left = rightEdge - width;
|
|
450
|
+
}
|
|
451
|
+
if (direction.includes("n")) {
|
|
452
|
+
height = sizeToUnits(raw.height);
|
|
453
|
+
top = bottomEdge - height;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return GridLayoutUtil.clampRect(
|
|
457
|
+
{ ...session.originRect, left, top, width, height },
|
|
458
|
+
metrics.columns,
|
|
459
|
+
);
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
// ── preview recompute (only when the snapped cell changes) ────────────
|
|
463
|
+
|
|
464
|
+
type RefreshPreviewFunction = (session: GestureSession) => void;
|
|
465
|
+
const refreshPreview: RefreshPreviewFunction = (
|
|
466
|
+
session: GestureSession,
|
|
467
|
+
): void => {
|
|
468
|
+
const raw: PxRect = computeRawRect(session);
|
|
469
|
+
const candidate: GridRect = snapToGrid(session, raw);
|
|
470
|
+
|
|
471
|
+
if (GridLayoutUtil.areRectsAtSamePlace(candidate, session.candidate)) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
session.candidate = candidate;
|
|
476
|
+
|
|
477
|
+
let nextRects: Array<GridRect>;
|
|
478
|
+
|
|
479
|
+
if (session.mode === "move") {
|
|
480
|
+
nextRects = GridLayoutUtil.moveRect({
|
|
481
|
+
rects: session.originRects,
|
|
482
|
+
id: session.id,
|
|
483
|
+
left: candidate.left,
|
|
484
|
+
top: candidate.top,
|
|
485
|
+
columns: propsRef.current.metrics.columns,
|
|
486
|
+
});
|
|
487
|
+
} else {
|
|
488
|
+
nextRects = GridLayoutUtil.resizeRect({
|
|
489
|
+
rects: session.originRects,
|
|
490
|
+
id: session.id,
|
|
491
|
+
left: candidate.left,
|
|
492
|
+
top: candidate.top,
|
|
493
|
+
width: candidate.width,
|
|
494
|
+
height: candidate.height,
|
|
495
|
+
columns: propsRef.current.metrics.columns,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
setPreview({ rects: nextRects, activeRect: candidate });
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
// ── frame loop ────────────────────────────────────────────────────────
|
|
503
|
+
|
|
504
|
+
type FrameFunction = () => void;
|
|
505
|
+
const onFrame: FrameFunction = (): void => {
|
|
506
|
+
const session: GestureSession | null = sessionRef.current;
|
|
507
|
+
if (!session) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (session.started) {
|
|
512
|
+
autoScrollIfNearEdge(session.latestClientY);
|
|
513
|
+
|
|
514
|
+
const raw: PxRect = computeRawRect(session);
|
|
515
|
+
const element: HTMLElement | undefined = elementsRef.current.get(
|
|
516
|
+
session.id,
|
|
517
|
+
);
|
|
518
|
+
if (element) {
|
|
519
|
+
applyPxRect(element, raw);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
refreshPreview(session);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
session.rafId = window.requestAnimationFrame(onFrame);
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
// ── gesture lifecycle ─────────────────────────────────────────────────
|
|
529
|
+
|
|
530
|
+
type ActivateFunction = (session: GestureSession) => void;
|
|
531
|
+
const activateGesture: ActivateFunction = (session: GestureSession): void => {
|
|
532
|
+
session.started = true;
|
|
533
|
+
|
|
534
|
+
const element: HTMLElement | undefined = elementsRef.current.get(
|
|
535
|
+
session.id,
|
|
536
|
+
);
|
|
537
|
+
if (element) {
|
|
538
|
+
element.style.transition = "none";
|
|
539
|
+
element.style.willChange = "transform, width, height";
|
|
540
|
+
element.style.zIndex = "60";
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
document.body.style.userSelect = "none";
|
|
544
|
+
document.body.style.cursor =
|
|
545
|
+
session.mode === "move"
|
|
546
|
+
? "grabbing"
|
|
547
|
+
: getResizeCursor(session.direction || "se");
|
|
548
|
+
|
|
549
|
+
setGesture({ id: session.id, mode: session.mode, started: true });
|
|
550
|
+
// Show the placeholder at the origin right away.
|
|
551
|
+
setPreview({
|
|
552
|
+
rects: session.originRects,
|
|
553
|
+
activeRect: session.candidate,
|
|
554
|
+
});
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
type EndFunction = (commit: boolean) => void;
|
|
558
|
+
const endGesture: EndFunction = (commit: boolean): void => {
|
|
559
|
+
const session: GestureSession | null = sessionRef.current;
|
|
560
|
+
if (!session) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (session.rafId !== null) {
|
|
565
|
+
window.cancelAnimationFrame(session.rafId);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (session.detach) {
|
|
569
|
+
session.detach();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
document.body.style.userSelect = "";
|
|
573
|
+
document.body.style.cursor = "";
|
|
574
|
+
|
|
575
|
+
sessionRef.current = null;
|
|
576
|
+
|
|
577
|
+
const wasStarted: boolean = session.started;
|
|
578
|
+
|
|
579
|
+
if (!wasStarted) {
|
|
580
|
+
// Press-and-release without movement: a click.
|
|
581
|
+
setGesture(null);
|
|
582
|
+
setPreview(null);
|
|
583
|
+
/*
|
|
584
|
+
* Only a press on the widget body counts as a click. A press on a
|
|
585
|
+
* resize handle that never moved must not open the settings modal —
|
|
586
|
+
* otherwise the same press behaves differently depending on whether
|
|
587
|
+
* the pointer jittered a single pixel.
|
|
588
|
+
*/
|
|
589
|
+
if (commit && session.mode === "move" && propsRef.current.onItemClick) {
|
|
590
|
+
propsRef.current.onItemClick(session.id);
|
|
591
|
+
}
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (commit) {
|
|
596
|
+
const finalRects: Array<GridRect> =
|
|
597
|
+
session.mode === "move"
|
|
598
|
+
? GridLayoutUtil.moveRect({
|
|
599
|
+
rects: session.originRects,
|
|
600
|
+
id: session.id,
|
|
601
|
+
left: session.candidate.left,
|
|
602
|
+
top: session.candidate.top,
|
|
603
|
+
columns: propsRef.current.metrics.columns,
|
|
604
|
+
})
|
|
605
|
+
: GridLayoutUtil.resizeRect({
|
|
606
|
+
rects: session.originRects,
|
|
607
|
+
id: session.id,
|
|
608
|
+
left: session.candidate.left,
|
|
609
|
+
top: session.candidate.top,
|
|
610
|
+
width: session.candidate.width,
|
|
611
|
+
height: session.candidate.height,
|
|
612
|
+
columns: propsRef.current.metrics.columns,
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
const finalRect: GridRect =
|
|
616
|
+
finalRects.find((r: GridRect) => {
|
|
617
|
+
return r.id === session.id;
|
|
618
|
+
}) || session.originRect;
|
|
619
|
+
|
|
620
|
+
settleElement(session.id, finalRect);
|
|
621
|
+
setGesture(null);
|
|
622
|
+
setPreview(null);
|
|
623
|
+
|
|
624
|
+
if (!GridLayoutUtil.areLayoutsEqual(finalRects, propsRef.current.items)) {
|
|
625
|
+
propsRef.current.onCommit(finalRects);
|
|
626
|
+
}
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Cancelled: glide back to where the widget started.
|
|
631
|
+
settleElement(session.id, session.originRect);
|
|
632
|
+
setGesture(null);
|
|
633
|
+
setPreview(null);
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
type PointerMoveHandler = (event: PointerEvent) => void;
|
|
637
|
+
const onPointerMove: PointerMoveHandler = (event: PointerEvent): void => {
|
|
638
|
+
const session: GestureSession | null = sessionRef.current;
|
|
639
|
+
if (!session || event.pointerId !== session.pointerId) {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
session.latestClientX = event.clientX;
|
|
644
|
+
session.latestClientY = event.clientY;
|
|
645
|
+
|
|
646
|
+
if (!session.started) {
|
|
647
|
+
const distance: number = Math.hypot(
|
|
648
|
+
event.clientX - session.startClientX,
|
|
649
|
+
event.clientY - session.startClientY,
|
|
650
|
+
);
|
|
651
|
+
// Resize gestures are always intentional — no threshold.
|
|
652
|
+
if (session.mode === "resize" || distance >= DRAG_THRESHOLD_IN_PX) {
|
|
653
|
+
activateGesture(session);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (session.started) {
|
|
658
|
+
event.preventDefault();
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
type PointerUpHandler = (event: PointerEvent) => void;
|
|
663
|
+
const onPointerUp: PointerUpHandler = (event: PointerEvent): void => {
|
|
664
|
+
const session: GestureSession | null = sessionRef.current;
|
|
665
|
+
if (!session || event.pointerId !== session.pointerId) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
// Take one final reading so fast flicks land where they were released.
|
|
669
|
+
session.latestClientX = event.clientX;
|
|
670
|
+
session.latestClientY = event.clientY;
|
|
671
|
+
if (session.started) {
|
|
672
|
+
const raw: PxRect = computeRawRect(session);
|
|
673
|
+
session.candidate = snapToGrid(session, raw);
|
|
674
|
+
}
|
|
675
|
+
endGesture(true);
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
type PointerCancelHandler = (event: PointerEvent) => void;
|
|
679
|
+
const onPointerCancel: PointerCancelHandler = (event: PointerEvent): void => {
|
|
680
|
+
const session: GestureSession | null = sessionRef.current;
|
|
681
|
+
if (!session || event.pointerId !== session.pointerId) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
endGesture(false);
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
type KeyDownHandler = (event: KeyboardEvent) => void;
|
|
688
|
+
const onKeyDown: KeyDownHandler = (event: KeyboardEvent): void => {
|
|
689
|
+
if (event.key === "Escape" && sessionRef.current) {
|
|
690
|
+
event.preventDefault();
|
|
691
|
+
event.stopPropagation();
|
|
692
|
+
endGesture(false);
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
type WindowBlurHandler = () => void;
|
|
697
|
+
const onWindowBlur: WindowBlurHandler = (): void => {
|
|
698
|
+
endGesture(false);
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
type StartSessionFunction = (
|
|
702
|
+
id: string,
|
|
703
|
+
mode: GridDndMode,
|
|
704
|
+
direction: ResizeDirection | null,
|
|
705
|
+
event: React.PointerEvent,
|
|
706
|
+
) => void;
|
|
707
|
+
const startSession: StartSessionFunction = (
|
|
708
|
+
id: string,
|
|
709
|
+
mode: GridDndMode,
|
|
710
|
+
direction: ResizeDirection | null,
|
|
711
|
+
event: React.PointerEvent,
|
|
712
|
+
): void => {
|
|
713
|
+
if (!propsRef.current.isEnabled) {
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
if (sessionRef.current) {
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
// Primary button / primary touch only.
|
|
720
|
+
if (event.button !== 0 || !event.isPrimary) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
const items: Array<GridRect> = propsRef.current.items;
|
|
725
|
+
const originRects: Array<GridRect> = GridLayoutUtil.normalize(
|
|
726
|
+
items,
|
|
727
|
+
propsRef.current.metrics.columns,
|
|
728
|
+
);
|
|
729
|
+
const originRect: GridRect | undefined = originRects.find((r: GridRect) => {
|
|
730
|
+
return r.id === id;
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
if (!originRect) {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
event.preventDefault();
|
|
738
|
+
event.stopPropagation();
|
|
739
|
+
|
|
740
|
+
try {
|
|
741
|
+
(event.currentTarget as HTMLElement).setPointerCapture?.(event.pointerId);
|
|
742
|
+
} catch {
|
|
743
|
+
// jsdom / older browsers: window listeners still cover us.
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const canvas: HTMLElement | null = propsRef.current.canvasRef.current;
|
|
747
|
+
const canvasRect: DOMRect | null = canvas
|
|
748
|
+
? canvas.getBoundingClientRect()
|
|
749
|
+
: null;
|
|
750
|
+
|
|
751
|
+
const startCanvasX: number = canvasRect
|
|
752
|
+
? event.clientX - canvasRect.left
|
|
753
|
+
: event.clientX;
|
|
754
|
+
const startCanvasY: number = canvasRect
|
|
755
|
+
? event.clientY - canvasRect.top
|
|
756
|
+
: event.clientY;
|
|
757
|
+
|
|
758
|
+
const originPx: PxRect = rectToPx(originRect);
|
|
759
|
+
|
|
760
|
+
const session: GestureSession = {
|
|
761
|
+
id,
|
|
762
|
+
mode,
|
|
763
|
+
direction,
|
|
764
|
+
pointerId: event.pointerId,
|
|
765
|
+
originRects,
|
|
766
|
+
originRect,
|
|
767
|
+
startCanvasX,
|
|
768
|
+
startCanvasY,
|
|
769
|
+
startClientX: event.clientX,
|
|
770
|
+
startClientY: event.clientY,
|
|
771
|
+
latestClientX: event.clientX,
|
|
772
|
+
latestClientY: event.clientY,
|
|
773
|
+
grabFracX: Math.min(
|
|
774
|
+
Math.max((startCanvasX - originPx.x) / Math.max(originPx.width, 1), 0),
|
|
775
|
+
1,
|
|
776
|
+
),
|
|
777
|
+
grabFracY: Math.min(
|
|
778
|
+
Math.max((startCanvasY - originPx.y) / Math.max(originPx.height, 1), 0),
|
|
779
|
+
1,
|
|
780
|
+
),
|
|
781
|
+
maxBottomUnits:
|
|
782
|
+
GridLayoutUtil.requiredRows(originRects) + AUTO_GROW_SLACK_ROWS,
|
|
783
|
+
started: false,
|
|
784
|
+
candidate: originRect,
|
|
785
|
+
rafId: null,
|
|
786
|
+
detach: null,
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
sessionRef.current = session;
|
|
790
|
+
setGesture({ id, mode, started: false });
|
|
791
|
+
|
|
792
|
+
window.addEventListener("pointermove", onPointerMove);
|
|
793
|
+
window.addEventListener("pointerup", onPointerUp);
|
|
794
|
+
window.addEventListener("pointercancel", onPointerCancel);
|
|
795
|
+
window.addEventListener("keydown", onKeyDown, true);
|
|
796
|
+
window.addEventListener("blur", onWindowBlur);
|
|
797
|
+
|
|
798
|
+
session.detach = (): void => {
|
|
799
|
+
window.removeEventListener("pointermove", onPointerMove);
|
|
800
|
+
window.removeEventListener("pointerup", onPointerUp);
|
|
801
|
+
window.removeEventListener("pointercancel", onPointerCancel);
|
|
802
|
+
window.removeEventListener("keydown", onKeyDown, true);
|
|
803
|
+
window.removeEventListener("blur", onWindowBlur);
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
session.rafId = window.requestAnimationFrame(onFrame);
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
const startMove: (id: string, event: React.PointerEvent) => void =
|
|
810
|
+
useCallback((id: string, event: React.PointerEvent): void => {
|
|
811
|
+
startSession(id, "move", null, event);
|
|
812
|
+
}, []);
|
|
813
|
+
|
|
814
|
+
const startResize: (
|
|
815
|
+
id: string,
|
|
816
|
+
direction: ResizeDirection,
|
|
817
|
+
event: React.PointerEvent,
|
|
818
|
+
) => void = useCallback(
|
|
819
|
+
(id: string, direction: ResizeDirection, event: React.PointerEvent) => {
|
|
820
|
+
startSession(id, "resize", direction, event);
|
|
821
|
+
},
|
|
822
|
+
[],
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
/*
|
|
826
|
+
* Keep a live reference to endGesture so unmount/disable cleanup always
|
|
827
|
+
* runs the freshest closure (the session's own detach handles listener
|
|
828
|
+
* identity).
|
|
829
|
+
*/
|
|
830
|
+
const endGestureRef: React.MutableRefObject<EndFunction> =
|
|
831
|
+
useRef<EndFunction>(endGesture);
|
|
832
|
+
endGestureRef.current = endGesture;
|
|
833
|
+
|
|
834
|
+
// Cancel any in-flight gesture when unmounting or when editing turns off.
|
|
835
|
+
useEffect(() => {
|
|
836
|
+
return () => {
|
|
837
|
+
endGestureRef.current(false);
|
|
838
|
+
};
|
|
839
|
+
}, []);
|
|
840
|
+
|
|
841
|
+
useEffect(() => {
|
|
842
|
+
if (!props.isEnabled && sessionRef.current) {
|
|
843
|
+
endGestureRef.current(false);
|
|
844
|
+
}
|
|
845
|
+
}, [props.isEnabled]);
|
|
846
|
+
|
|
847
|
+
// ── derived render data ───────────────────────────────────────────────
|
|
848
|
+
|
|
849
|
+
const activeId: string | null = gesture?.id || null;
|
|
850
|
+
const isGestureActive: boolean = Boolean(gesture?.started);
|
|
851
|
+
|
|
852
|
+
const rects: Array<GridRect> = useMemo(() => {
|
|
853
|
+
if (!preview || !activeId) {
|
|
854
|
+
return props.items;
|
|
855
|
+
}
|
|
856
|
+
/*
|
|
857
|
+
* Neighbours come from the preview; the active widget stays pinned at
|
|
858
|
+
* its origin because its DOM node is being moved imperatively.
|
|
859
|
+
*/
|
|
860
|
+
const originRect: GridRect | undefined =
|
|
861
|
+
sessionRef.current?.originRect ||
|
|
862
|
+
props.items.find((r: GridRect) => {
|
|
863
|
+
return r.id === activeId;
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
return preview.rects.map((rect: GridRect) => {
|
|
867
|
+
return rect.id === activeId && originRect ? originRect : rect;
|
|
868
|
+
});
|
|
869
|
+
}, [preview, activeId, props.items]);
|
|
870
|
+
|
|
871
|
+
const placeholderRect: GridRect | null =
|
|
872
|
+
isGestureActive && preview ? preview.activeRect : null;
|
|
873
|
+
|
|
874
|
+
const totalRows: number = useMemo(() => {
|
|
875
|
+
const layoutRows: number = GridLayoutUtil.requiredRows(
|
|
876
|
+
preview ? preview.rects : props.items,
|
|
877
|
+
);
|
|
878
|
+
return layoutRows;
|
|
879
|
+
}, [preview, props.items]);
|
|
880
|
+
|
|
881
|
+
return {
|
|
882
|
+
rects,
|
|
883
|
+
placeholderRect,
|
|
884
|
+
totalRows,
|
|
885
|
+
activeId,
|
|
886
|
+
activeMode: gesture?.mode || null,
|
|
887
|
+
isGestureActive,
|
|
888
|
+
registerItemElement,
|
|
889
|
+
startMove,
|
|
890
|
+
startResize,
|
|
891
|
+
};
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
type GetResizeCursorFunction = (direction: ResizeDirection) => string;
|
|
895
|
+
|
|
896
|
+
export const getResizeCursor: GetResizeCursorFunction = (
|
|
897
|
+
direction: ResizeDirection,
|
|
898
|
+
): string => {
|
|
899
|
+
switch (direction) {
|
|
900
|
+
case "n":
|
|
901
|
+
case "s":
|
|
902
|
+
return "ns-resize";
|
|
903
|
+
case "e":
|
|
904
|
+
case "w":
|
|
905
|
+
return "ew-resize";
|
|
906
|
+
case "ne":
|
|
907
|
+
case "sw":
|
|
908
|
+
return "nesw-resize";
|
|
909
|
+
case "nw":
|
|
910
|
+
case "se":
|
|
911
|
+
return "nwse-resize";
|
|
912
|
+
default:
|
|
913
|
+
return "default";
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
export default useDashboardGridDnd;
|