Skip to content

Commit

Permalink
Copy & Paste Activity Directives (#1565)
Browse files Browse the repository at this point in the history
  • Loading branch information
ivydeliz committed Jan 21, 2025
1 parent 9275b0b commit 00193a6
Show file tree
Hide file tree
Showing 9 changed files with 348 additions and 14 deletions.
39 changes: 39 additions & 0 deletions src/components/activity/ActivityDirectivesTable.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
import ContextMenuItem from '../context-menu/ContextMenuItem.svelte';
import ContextMenuSeparator from '../context-menu/ContextMenuSeparator.svelte';
import { createEventDispatcher } from 'svelte';
import {
canPasteActivityDirectivesFromClipboard,
copyActivityDirectivesToClipboard,
getPasteActivityDirectivesText,
getActivityDirectivesToPaste,
} from '../../utilities/activities';
export let activityDirectives: ActivityDirective[] = [];
export let activityDirectiveErrorRollupsMap: Record<ActivityDirectiveId, ActivityErrorRollup> | undefined = undefined;
Expand All @@ -31,6 +37,7 @@
export let filterExpression: string = '';
const dispatch = createEventDispatcher<{
createActivityDirectives: ActivityDirective[];
scrollTimelineToTime: number;
}>();
Expand All @@ -44,15 +51,22 @@
let activityErrorColumnDef: DataGridColumnDef | null = null;
let activityDirectivesWithErrorCounts: ActivityDirectiveWithErrorCounts[] = [];
let completeColumnDefs: ColDef[] = columnDefs;
let hasCreatePermission: boolean = false;
let hasDeletePermission: boolean = false;
let isDeletingDirective: boolean = false;
let showCopyMenu: boolean = true;
$: hasDeletePermission =
plan !== null ? featurePermissions.activityDirective.canDelete(user, plan) && !planReadOnly : false;
$: hasCreatePermission =
plan !== null ? featurePermissions.activityDirective.canCreate(user, plan) && !planReadOnly : false;
$: activityDirectivesWithErrorCounts = activityDirectives.map(activityDirective => ({
...activityDirective,
errorCounts: activityDirectiveErrorRollupsMap?.[activityDirective.id]?.errorCounts,
}));
$: {
activityActionColumnDef = {
cellClass: 'action-cell-container',
Expand Down Expand Up @@ -145,6 +159,25 @@
dispatch('scrollTimelineToTime', directive.start_time_ms);
}
}
function copyActivityDirectives({ detail: activities }: CustomEvent<ActivityDirective[]>) {
if (plan !== null) {
copyActivityDirectivesToClipboard(plan, activities);
}
}
function canPasteActivityDirectives(): boolean {
return plan !== null && hasCreatePermission && canPasteActivityDirectivesFromClipboard(plan);
}
function pasteActivityDirectives() {
if (plan !== null && canPasteActivityDirectives()) {
const directives = getActivityDirectivesToPaste(plan);
if (directives !== undefined) {
dispatch(`createActivityDirectives`, directives);
}
}
}
</script>

<BulkActionDataGrid
Expand All @@ -161,10 +194,12 @@
pluralItemDisplayText="Activity Directives"
scrollToSelection={true}
singleItemDisplayText="Activity Directive"
{showCopyMenu}
suppressDragLeaveHidesColumns={false}
{user}
{filterExpression}
on:bulkDeleteItems={deleteActivityDirectives}
on:bulkCopyItems={copyActivityDirectives}
on:columnMoved
on:columnPinned
on:columnResized
Expand All @@ -178,5 +213,9 @@
<ContextMenuItem on:click={scrollTimelineToActivityDirective}>Scroll to Activity</ContextMenuItem>
<ContextMenuSeparator></ContextMenuSeparator>
{/if}
{#if canPasteActivityDirectives()}
<ContextMenuItem on:click={pasteActivityDirectives}>{getPasteActivityDirectivesText()}</ContextMenuItem>
<ContextMenuSeparator></ContextMenuSeparator>
{/if}
</svelte:fragment>
</BulkActionDataGrid>
9 changes: 9 additions & 0 deletions src/components/activity/ActivityDirectivesTablePanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import ActivityTableMenu from './ActivityTableMenu.svelte';
import { get } from 'svelte/store';
import { getTimeRangeAroundTime } from '../../utilities/timeline';
import effects from '../../utilities/effects';
export let gridSection: ViewGridSection;
export let user: User | null;
Expand Down Expand Up @@ -246,6 +247,13 @@
dataGrid?.sizeColumnsToFit();
}
function createActivityDirectives({ detail }: CustomEvent<ActivityDirective[]>) {
const p = get(plan);
if (p !== null) {
effects.cloneActivityDirectives(detail, p, user);
}
}
function onGridSizeChanged() {
if (activityDirectivesTable?.autoSizeColumns === 'fill') {
autoSizeSpace();
Expand Down Expand Up @@ -426,6 +434,7 @@
on:columnPinned={onColumnPinned}
on:columnResized={onColumnResized}
on:columnVisible={onColumnVisible}
on:createActivityDirectives={createActivityDirectives}
on:gridSizeChanged={onGridSizeChangedDebounced}
on:rowDoubleClicked={onRowDoubleClicked}
on:selectionChanged={onSelectionChanged}
Expand Down
50 changes: 48 additions & 2 deletions src/components/timeline/TimelineContextMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@
TimeRange,
VerticalGuide,
} from '../../types/timeline';
import { getAllSpansForActivityDirective, getSpanRootParent } from '../../utilities/activities';
import {
canPasteActivityDirectivesFromClipboard,
copyActivityDirectivesToClipboard,
getAllSpansForActivityDirective,
getSpanRootParent,
getPasteActivityDirectivesText,
getActivityDirectivesToPaste,
} from '../../utilities/activities';
import effects from '../../utilities/effects';
import { getTarget } from '../../utilities/generic';
import { permissionHandler } from '../../utilities/permissionHandler';
Expand All @@ -31,6 +38,7 @@
import ContextMenuItem from '../context-menu/ContextMenuItem.svelte';
import ContextMenuSeparator from '../context-menu/ContextMenuSeparator.svelte';
import ContextSubMenuItem from '../context-menu/ContextSubMenuItem.svelte';
import { featurePermissions } from '../../utilities/permissions';
export let activityDirectivesMap: ActivityDirectivesMap;
export let contextMenu: MouseOver | null;
Expand All @@ -49,6 +57,7 @@
const dispatch = createEventDispatcher<{
collapseDiscreteTree: Row;
createActivityDirectives: ActivityDirective[];
deleteActivityDirective: number;
deleteRow: Row;
duplicateRow: Row;
Expand All @@ -57,6 +66,7 @@
jumpToActivityDirective: number;
jumpToSpan: number;
moveRow: { direction: 'up' | 'down'; row: Row };
pasteActivityDirectivesAtTime: Date | null;
toggleActivityComposition: { composition: ActivityOptions['composition']; row: Row };
updateVerticalGuides: VerticalGuide[];
viewTimeRangeChanged: TimeRange;
Expand Down Expand Up @@ -241,6 +251,27 @@
export function isShown() {
return contextMenuComponent.isShown();
}
function copyActivityDirective(activity: ActivityDirective) {
plan !== null && copyActivityDirectivesToClipboard(plan, [activity]);
}
function canPasteActivityDirectives(): boolean {
return (
plan !== null &&
featurePermissions.activityDirective.canCreate(user, plan) &&
canPasteActivityDirectivesFromClipboard(plan)
);
}
function pasteActivityDirectivesAtTime(time: Date | false | null) {
if (plan !== null && featurePermissions.activityDirective.canCreate(user, plan) && time instanceof Date) {
const directives = getActivityDirectivesToPaste(plan, time.getTime());
if (directives !== undefined) {
effects.cloneActivityDirectives(directives, plan, user);
}
}
}
</script>

<ContextMenu hideAfterClick on:hide bind:this={contextMenuComponent}>
Expand Down Expand Up @@ -311,6 +342,9 @@
Set Simulation End at Directive Start
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem on:click={() => activityDirective !== null && copyActivityDirective(activityDirective)}>
Copy Activity Directive
</ContextMenuItem>
<ContextMenuItem
on:click={() => {
if (activityDirective !== null) {
Expand All @@ -329,7 +363,7 @@
],
]}
>
Delete Directive
Delete Activity Directive
</ContextMenuItem>
{:else if span}
<ContextMenuItem on:click={jumpToActivityDirective}>Jump to Activity Directive</ContextMenuItem>
Expand Down Expand Up @@ -398,6 +432,18 @@
>
Set Simulation End
</ContextMenuItem>
{#if canPasteActivityDirectives()}
<ContextMenuSeparator />
<ContextMenuItem
on:click={() => {
if (xScaleView && offsetX !== undefined) {
pasteActivityDirectivesAtTime(xScaleView.invert(offsetX));
}
}}
>
{getPasteActivityDirectivesText()} at Time
</ContextMenuItem>
{/if}
{/if}
<ContextMenuSeparator />
{#if span}
Expand Down
42 changes: 31 additions & 11 deletions src/components/ui/DataGrid/BulkActionDataGrid.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
// eslint-disable-next-line
interface $$Events extends ComponentEvents<DataGrid<RowData>> {
bulkCopyItems: CustomEvent<RowData[]>;
bulkDeleteItems: CustomEvent<RowData[]>;
}
import { browser } from '$app/environment';
import type { ColDef, ColumnState, IRowNode, RedrawRowsParams } from 'ag-grid-community';
import { keyBy } from 'lodash-es';
import { createEventDispatcher, onDestroy, type ComponentEvents } from 'svelte';
import { type ComponentEvents, createEventDispatcher, onDestroy } from 'svelte';
import type { User } from '../../../types/app';
import type { Dispatcher } from '../../../types/component';
import type { RowId, TRowData } from '../../../types/data-grid';
Expand All @@ -35,6 +37,7 @@
export let selectedItemId: RowId | null = null;
export let selectedItemIds: RowId[] = [];
export let showContextMenu: boolean = true;
export let showCopyMenu: boolean = false;
export let singleItemDisplayText: string = '';
export let suppressDragLeaveHidesColumns: boolean = true;
export let suppressRowClickSelection: boolean = false;
Expand Down Expand Up @@ -89,23 +92,33 @@
onDestroy(() => onBlur());
function bulkCopyItems() {
const selectedRows = getRowDataFromSelectedItems();
if (selectedRows.length) {
dispatch('bulkCopyItems', selectedRows);
}
}
function bulkDeleteItems() {
if (deletePermission) {
const selectedItemIdsMap = keyBy(selectedItemIds);
const selectedRows: RowData[] = items.reduce((selectedRows: RowData[], row: RowData) => {
const id = getRowId(row);
if (selectedItemIdsMap[id] !== undefined) {
selectedRows.push(row);
}
return selectedRows;
}, []);
const selectedRows = getRowDataFromSelectedItems();
if (selectedRows.length) {
dispatch('bulkDeleteItems', selectedRows);
}
}
}
function getRowDataFromSelectedItems(): RowData[] {
const selectedItemIdsMap = keyBy(selectedItemIds);
return items.reduce((selectedRows: RowData[], row: RowData) => {
const id = getRowId(row);
if (selectedItemIdsMap[id] !== undefined) {
selectedRows.push(row);
}
return selectedRows;
}, []);
}
function onBlur() {
if (browser) {
document.removeEventListener('keydown', onKeyDown);
Expand Down Expand Up @@ -172,13 +185,20 @@
>
<svelte:fragment slot="context-menu">
{#if showContextMenu}
<!-- to further extend context menu -->
<slot name="context-menu" />
<ContextMenuHeader>Bulk Actions</ContextMenuHeader>
<ContextMenuItem on:click={selectAllItems}>
Select All {isFiltered ? 'Visible ' : ''}{pluralItemDisplayText}
</ContextMenuItem>

{#if selectedItemIds.length}
{#if showCopyMenu}
<ContextMenuItem on:click={bulkCopyItems}>
Copy {selectedItemIds.length}
{selectedItemIds.length > 1 ? pluralItemDisplayText : singleItemDisplayText}
</ContextMenuItem>
{/if}

<ContextMenuItem
use={[
[
Expand Down
Loading

0 comments on commit 00193a6

Please sign in to comment.