+
+
+
diff --git a/src/components/activity/ActivityDirectiveBuilder.svelte b/src/components/activity/ActivityDirectiveBuilder.svelte
new file mode 100644
index 0000000000..73f87567f2
--- /dev/null
+++ b/src/components/activity/ActivityDirectiveBuilder.svelte
@@ -0,0 +1,438 @@
+
+
+
+
+
+
+ {#if shown}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
+ const { name, value } = event.detail;
+ dirtyDirective.arguments[name] = value;
+ getArgumentValidation();
+ }}
+ />
+ {#if !currentActivityTypeFormParams || currentActivityTypeFormParams.length === 0}
+ No Parameters Found
+ {/if}
+
+
+
+
+
+
+
+
+
+ {/if}
+
+
+
diff --git a/src/components/activity/ActivityDirectiveForm.svelte b/src/components/activity/ActivityDirectiveForm.svelte
index 6ee1c38d3a..392fdef980 100644
--- a/src/components/activity/ActivityDirectiveForm.svelte
+++ b/src/components/activity/ActivityDirectiveForm.svelte
@@ -31,7 +31,7 @@
import type { FieldStore } from '../../types/form';
import type { Argument, ArgumentsMap, FormParameter, ParameterName } from '../../types/parameter';
import type { ActivityDirectiveTagsInsertInput, Tag, TagsChangeEvent } from '../../types/tags';
- import { getActivityMetadata } from '../../utilities/activities';
+ import { getActivityMetadata, validateArguments } from '../../utilities/activities';
import effects from '../../utilities/effects';
import { isInstantiationError } from '../../utilities/errors';
import { classNames, keyByBoolean } from '../../utilities/generic';
@@ -119,7 +119,7 @@
$activityArgumentDefaultsMap[activityType?.name || ''] ?? {},
);
}
- $: validateArguments(revision ? revision.arguments : activityDirective.arguments);
+ $: getArgumentValidation(revision ? revision.arguments : activityDirective.arguments);
$: numOfUserChanges = formParameters.reduce((previousHasChanges: number, formParameter) => {
return /user/.test(formParameter.valueSource) ? previousHasChanges + 1 : previousHasChanges;
}, 0);
@@ -402,6 +402,7 @@
}
const activityDirectiveTags: ActivityDirectiveTagsInsertInput[] = tagsToAdd.map(({ id: tag_id }) => ({
directive_id: activityDirective.id,
+
plan_id: activityDirective.plan_id,
tag_id,
}));
@@ -418,30 +419,15 @@
}
}
- async function validateArguments(newArguments: ArgumentsMap | null): Promise
{
- if (newArguments) {
- const { type } = activityDirective;
- const { errors, success } = await effects.validateActivityArguments(
- type,
+ async function getArgumentValidation(newArguments: ArgumentsMap | null): Promise {
+ if (newArguments && user) {
+ parameterErrorMap = await validateArguments(
activityDirective.id,
- modelId,
+ activityDirective.type,
newArguments,
+ modelId,
user,
);
-
- if (!success && errors) {
- parameterErrorMap = errors.reduce((map: Record, error) => {
- error.subjects?.forEach(subject => {
- if (!map[subject]) {
- map[subject] = [];
- }
- map[subject].push(error.message);
- });
- return map;
- }, {});
- } else {
- parameterErrorMap = {};
- }
}
}
diff --git a/src/components/timeline/Row.svelte b/src/components/timeline/Row.svelte
index 4c8ab3eac7..d394601878 100644
--- a/src/components/timeline/Row.svelte
+++ b/src/components/timeline/Row.svelte
@@ -141,6 +141,7 @@
export let user: User | null;
const dispatch = createEventDispatcher<{
+ buildDirective: { startTime: string; type: string };
discreteTreeExpansionChange: DiscreteTreeExpansionMap;
mouseDown: MouseDown;
mouseOver: MouseOver;
@@ -760,7 +761,7 @@
const createActivities = () => {
items.forEach(item => {
- effects.createActivityDirective({}, start_time, item.name, item.name, {}, plan, user);
+ dispatch('buildDirective', { startTime: start_time, type: item.name });
});
};
diff --git a/src/components/timeline/Timeline.svelte b/src/components/timeline/Timeline.svelte
index 73f75aaf3b..2e2f8d7a93 100644
--- a/src/components/timeline/Timeline.svelte
+++ b/src/components/timeline/Timeline.svelte
@@ -10,7 +10,7 @@
import { planDerivationGroupLinks } from '../../stores/external-source';
import { plugins } from '../../stores/plugins';
import { viewAddTimelineRow, viewUpdateTimeline } from '../../stores/views';
- import type { ActivityDirectiveId, ActivityDirectivesMap } from '../../types/activity';
+ import type { ActivityDirectiveId, ActivityDirectiveInsertInput, ActivityDirectivesMap } from '../../types/activity';
import type { User } from '../../types/app';
import type { ConstraintResultWithName } from '../../types/constraint';
import type { ExternalEvent, ExternalEventId } from '../../types/external-event';
@@ -33,9 +33,11 @@
Timeline,
XAxisTick,
} from '../../types/timeline';
+ import effects from '../../utilities/effects';
import { clamp } from '../../utilities/generic';
import { formatDate } from '../../utilities/time';
import { MAX_CANVAS_SIZE, TimelineInteractionMode, TimelineLockStatus, getXScale } from '../../utilities/timeline';
+ import ActivityDirectiveBuilder from '../activity/ActivityDirectiveBuilder.svelte';
import TimelineRow from './Row.svelte';
import RowHeaderDragHandleWidth from './RowHeaderDragHandleWidth.svelte';
import TimelineContextMenu from './TimelineContextMenu.svelte';
@@ -115,6 +117,8 @@
let xAxisDrawHeight: number = 64;
let xTicksView: XAxisTick[] = [];
let derivationGroups: string[] = [];
+ let directiveBuilder: ActivityDirectiveBuilder;
+ let activeDirectiveName = '';
let throttledZoom = throttle(onZoom, 16, {
leading: true,
@@ -382,10 +386,38 @@
tooltip.hide();
}
}
+
+ function onBuildActivityDirective(startTime: string, activityType: string) {
+ directiveBuilder.setCurrentActivityType(activityType);
+ directiveBuilder.setCurrentActivityStart(startTime);
+ directiveBuilder.show();
+ }
+
+ function onCreateActivityDirective(directive: ActivityDirectiveInsertInput) {
+ if (plan !== null && plan.model) {
+ effects.createActivityDirectivePredefined(directive, plan, user);
+ directiveBuilder.toggle();
+ }
+ }
+ {
+ if (!visibility.detail.isShown) {
+ activeDirectiveName = '';
+ }
+ }}
+ on:createActivityDirective={directive => {
+ onCreateActivityDirective(directive.detail.directive);
+ }}
+ {user}
+/>
+
{#if plan}
@@ -513,6 +545,7 @@
yAxes={row.yAxes}
{timelineZoomTransform}
on:contextMenu={e => onContextMenu(e, row)}
+ on:buildDirective={e => onBuildActivityDirective(e.detail.startTime, e.detail.type)}
on:dblClick
on:deleteActivityDirective
on:mouseDown={onMouseDown}
diff --git a/src/utilities/activities.ts b/src/utilities/activities.ts
index 9a3f3fa7be..6d79d6c18b 100644
--- a/src/utilities/activities.ts
+++ b/src/utilities/activities.ts
@@ -6,6 +6,8 @@ import type {
ActivityDirectivesMap,
} from '../types/activity';
import type { ActivityMetadata, ActivityMetadataKey, ActivityMetadataValue } from '../types/activity-metadata';
+import type { User } from '../types/app';
+import type { ArgumentsMap } from '../types/parameter';
import type {
Plan,
PlanMergeActivityDirectiveDB,
@@ -17,6 +19,7 @@ import type {
import type { Span, SpanId, SpanUtilityMaps, SpansMap } from '../types/simulation';
import type { ActivityTransformDirection } from '../types/time';
import { getClipboardContent, setClipboardContent } from './clipboard';
+import effects from './effects';
import { compare, isEmpty } from './generic';
import { pluralize } from './text';
import {
@@ -564,3 +567,35 @@ export function packActivityDirectivesInPlan(
return result;
}
+
+export async function validateArguments(
+ activityId: number | undefined,
+ activityType: string,
+ activityArguments: ArgumentsMap,
+ modelId: number,
+ user: User,
+): Promise
> {
+ let errorsMap: Record = {};
+ const { errors, success } = await effects.validateActivityArguments(
+ activityType,
+ activityId,
+ modelId,
+ activityArguments,
+ user,
+ );
+
+ if (!success && errors) {
+ errorsMap = errors.reduce((map: Record, error) => {
+ error.subjects?.forEach(subject => {
+ if (!map[subject]) {
+ map[subject] = [];
+ }
+ map[subject].push(error.message);
+ });
+ return map;
+ }, {});
+ } else {
+ errorsMap = {};
+ }
+ return errorsMap;
+}
diff --git a/src/utilities/effects.ts b/src/utilities/effects.ts
index 879bb63059..efc7e4b383 100644
--- a/src/utilities/effects.ts
+++ b/src/utilities/effects.ts
@@ -1095,6 +1095,52 @@ const effects = {
}
},
+ async createActivityDirectivePredefined(
+ activityDirective: ActivityDirectiveInsertInput,
+ plan: Plan | null,
+ user: User | null,
+ ): Promise {
+ try {
+ if ((plan && !queryPermissions.CREATE_ACTIVITY_DIRECTIVE(user, plan)) || !plan) {
+ throwPermissionError('add a directive to the plan');
+ }
+
+ if (plan !== null) {
+ const data = await reqHasura(
+ gql.CREATE_ACTIVITY_DIRECTIVE,
+ {
+ activityDirectiveInsertInput: activityDirective,
+ },
+ user,
+ );
+ const { insert_activity_directive_one: newActivityDirective } = data;
+ if (newActivityDirective != null) {
+ const { id } = newActivityDirective;
+ activityDirectivesDBStore.updateValue(directives => {
+ return (directives || []).map(directive => {
+ if (directive.id === id) {
+ return newActivityDirective;
+ }
+ return directive;
+ });
+ });
+ selectedActivityDirectiveIdStore.set(id);
+ selectedSpanIdStore.set(null);
+
+ showSuccessToast('Activity Directive Created Successfully');
+ logMessage(`Created activity directive "${name}" (ID=${id}).`);
+ } else {
+ throw Error(`Unable to create activity directive "${activityDirective.name}" on plan with ID ${plan.id}`);
+ }
+ } else {
+ throw Error('Plan is not defined.');
+ }
+ } catch (e) {
+ catchError('Activity Directive Create Failed', e as Error);
+ showFailureToast('Activity Directive Create Failed');
+ }
+ },
+
async createActivityDirectiveTags(
tags: ActivityDirectiveTagsInsertInput[],
user: User | null,
@@ -8758,7 +8804,7 @@ const effects = {
async validateActivityArguments(
activityTypeName: string,
- activityId: number,
+ activityId: number | undefined,
modelId: number,
argumentsMap: ArgumentsMap,
user: User | null,
@@ -8776,7 +8822,11 @@ const effects = {
const { validateActivityArguments } = data;
if (validateActivityArguments != null) {
- logMessage(`Validated activity arguments for "${activityTypeName}" (ID=${activityId}).`);
+ if (activityId) {
+ logMessage(`Validated activity arguments for "${activityTypeName}" (ID=${activityId}).`);
+ } else {
+ logMessage(`Validated activity arguments for pending directive of "${activityTypeName}"`);
+ }
return validateActivityArguments;
} else {
throw Error('Unable to validate activity arguments');