diff --git a/src/components/activity/ActivityDirectivesTable.svelte b/src/components/activity/ActivityDirectivesTable.svelte index 0e9647c9b4..87a27205f4 100644 --- a/src/components/activity/ActivityDirectivesTable.svelte +++ b/src/components/activity/ActivityDirectivesTable.svelte @@ -14,6 +14,9 @@ import BulkActionDataGrid from '../ui/DataGrid/BulkActionDataGrid.svelte'; import type DataGrid from '../ui/DataGrid/DataGrid.svelte'; import DataGridActions from '../ui/DataGrid/DataGridActions.svelte'; + import ContextMenuItem from '../context-menu/ContextMenuItem.svelte'; + import ContextMenuSeparator from '../context-menu/ContextMenuSeparator.svelte'; + import { createEventDispatcher } from 'svelte'; export let activityDirectives: ActivityDirective[] = []; export let activityDirectiveErrorRollupsMap: Record | undefined = undefined; @@ -26,6 +29,10 @@ export let user: User | null; export let filterExpression: string = ''; + const dispatch = createEventDispatcher<{ + scrollToTime: number; + }>(); + type ActivityDirectiveWithErrorCounts = ActivityDirective & { errorCounts?: ActivityErrorCounts }; type CellRendererParams = { deleteActivityDirective: (activity: ActivityDirective) => void; @@ -129,6 +136,13 @@ function getRowId(activityDirective: ActivityDirective): ActivityDirectiveId { return activityDirective.id; } + + function scrollToActivityInTimeline() { + const directive = activityDirectives.find(item => item.id === selectedActivityDirectiveId) ?? null; + if (directive?.start_time_ms) { + dispatch('scrollToTime', directive.start_time_ms); + } + } +> + + Scroll to Activity + + + diff --git a/src/components/activity/ActivityDirectivesTablePanel.svelte b/src/components/activity/ActivityDirectivesTablePanel.svelte index 9dfc2087a3..623e67e4c6 100644 --- a/src/components/activity/ActivityDirectivesTablePanel.svelte +++ b/src/components/activity/ActivityDirectivesTablePanel.svelte @@ -14,7 +14,7 @@ import { InvalidDate } from '../../constants/time'; import { activityDirectivesMap, selectActivity, selectedActivityDirectiveId } from '../../stores/activities'; import { activityErrorRollupsMap } from '../../stores/errors'; - import { plan, planReadOnly } from '../../stores/plan'; + import { plan, planReadOnly, setViewTimeRangeAroundTime } from '../../stores/plan'; import { plugins } from '../../stores/plugins'; import { view, viewTogglePanel, viewUpdateActivityDirectivesTable } from '../../stores/views'; import type { ActivityDirective } from '../../types/activity'; @@ -364,6 +364,10 @@ viewUpdateActivityDirectivesTable({ autoSizeColumns: 'off' }); } } + + function scrollToTime({ detail }: CustomEvent) { + setViewTimeRangeAroundTime(detail); + } @@ -417,6 +421,7 @@ on:gridSizeChanged={onGridSizeChangedDebounced} on:rowDoubleClicked={onRowDoubleClicked} on:selectionChanged={onSelectionChanged} + on:scrollToTime={scrollToTime} /> diff --git a/src/components/timeline/TimelineViewControls.svelte b/src/components/timeline/TimelineViewControls.svelte index 903ff1fb15..3bd0badbdb 100644 --- a/src/components/timeline/TimelineViewControls.svelte +++ b/src/components/timeline/TimelineViewControls.svelte @@ -17,7 +17,7 @@ import { createEventDispatcher } from 'svelte'; import { SearchParameters } from '../../enums/searchParameters'; import { activityDirectivesMap, selectedActivityDirective } from '../../stores/activities'; - import { plan, planReadOnly } from '../../stores/plan'; + import { plan, planReadOnly, setViewTimeRangeAroundTime } from '../../stores/plan'; import { selectedSpan, simulationDataset, @@ -27,12 +27,7 @@ } from '../../stores/simulation'; import { timelineInteractionMode, timelineLockStatus, viewIsModified } from '../../stores/views'; import type { TimeRange } from '../../types/timeline'; - import { - getActivityDirectiveStartTimeMs, - getDoyTimeFromInterval, - getIntervalInMs, - getUnixEpochTime, - } from '../../utilities/time'; + import { getActivityDirectiveStartTimeMs, getDoyTimeFromInterval, getUnixEpochTime } from '../../utilities/time'; import { TimelineLockStatus } from '../../utilities/timeline'; import { showFailureToast, showSuccessToast } from '../../utilities/toast'; import { tooltip } from '../../utilities/tooltip'; @@ -216,15 +211,7 @@ function scrollToSelection() { const time = getSelectionTime(); - if (!isNaN(time) && (time < viewTimeRange.start || time > viewTimeRange.end)) { - const midSpan = time + getIntervalInMs($selectedSpan?.duration) / 2; - const start = Math.max(maxTimeRange.start, midSpan - viewDuration / 2); - const end = Math.min(maxTimeRange.end, midSpan + viewDuration / 2); - dispatch('viewTimeRangeChanged', { - end, - start, - }); - } + setViewTimeRangeAroundTime(time); } function openDoc(event: MouseEvent, url: string) { diff --git a/src/components/ui/DataGrid/BulkActionDataGrid.svelte b/src/components/ui/DataGrid/BulkActionDataGrid.svelte index 1c46d4fa69..56e36613de 100644 --- a/src/components/ui/DataGrid/BulkActionDataGrid.svelte +++ b/src/components/ui/DataGrid/BulkActionDataGrid.svelte @@ -171,6 +171,9 @@ on:selectionChanged > + {#if selectedItemIds.length === 1} + + {/if} {#if showContextMenu} Bulk Actions diff --git a/src/stores/plan.ts b/src/stores/plan.ts index f4e5e2c38a..7666f3fb73 100644 --- a/src/stores/plan.ts +++ b/src/stores/plan.ts @@ -1,4 +1,4 @@ -import { derived, writable, type Readable, type Writable } from 'svelte/store'; +import { derived, writable, type Readable, type Writable, get } from 'svelte/store'; import type { ActivityType } from '../types/activity'; import type { Plan, PlanMergeRequest, PlanMergeRequestSchema, PlanMetadata } from '../types/plan'; import type { PlanDataset } from '../types/simulation'; @@ -114,3 +114,15 @@ export function resetPlanStores() { export function setActivityEditingLocked(locked: boolean) { activityEditingLocked.set(locked); } + +export function setViewTimeRangeAroundTime(timeInMs: number) { + const currentViewTimeRange = get(viewTimeRange); + if (!isNaN(timeInMs) && (timeInMs < currentViewTimeRange.start || timeInMs > currentViewTimeRange.end)) { + const padding = (currentViewTimeRange.end - currentViewTimeRange.start) / 2; + const maxRange = get(maxTimeRange); + viewTimeRange.set({ + end: Math.min(maxRange.end, timeInMs + padding), + start: Math.max(maxRange.start, timeInMs - padding), + }); + } +}