Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy & Paste Activity Directives #1565

Merged
merged 1 commit into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
ivydeliz marked this conversation as resolved.
Show resolved Hide resolved
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
Loading