diff --git a/e2e-tests/fixtures/Sequence.ts b/e2e-tests/fixtures/Sequence.ts index 9b0af47d30..3b96fe95c7 100644 --- a/e2e-tests/fixtures/Sequence.ts +++ b/e2e-tests/fixtures/Sequence.ts @@ -167,7 +167,7 @@ export class Sequence { this.editor = page.locator('.cm-activeLine').first(); this.command = this.editor.getByText(/C\s+FSW_CMD_0.*/); this.jsonEditor = page.getByText(`{ "id": "${this.sequenceName}`); - this.jsonImport = page.locator('input[name="seqJsonFile"]'); + this.jsonImport = page.locator('input[name="outputFile"]'); this.linter = page.locator('.cm-lint-marker'); this.page = page; this.parcel = page.locator('select[name="parcel"]'); diff --git a/src/app.d.ts b/src/app.d.ts index cfcca1c2d8..fd8267d3ad 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,9 +1,5 @@ /* eslint-disable no-var */ /* eslint @typescript-eslint/no-unused-vars: 0 */ -import type { ParameterDictionary } from '@nasa-jpl/aerie-ampcs'; -import type { SeqJson } from '@nasa-jpl/seq-json-schema/types'; -import type { GlobalType } from './types/global-type'; -import type { ArgDelegator } from './utilities/new-sequence-editor/extension-points'; declare global { namespace App { @@ -49,28 +45,43 @@ declare global { export default content; } - var CONDITIONAL_KEYWORDS: { ELSE: string; ELSE_IF?: string[]; END_IF: string; IF: string[] } | undefined; - var LOOP_KEYWORDS: + var SequenceAdaptation: | { - BREAK: string; - CONTINUE: string; - END_WHILE_LOOP: string; - WHILE_LOOP: string[]; + ARG_DELEGATOR?: ArgDelegator; + CONDITIONAL_KEYWORDS?: { ELSE?: string; ELSE_IF?: string[]; END_IF?: string; IF: string[] }; + GLOBALS?: GlobalType[]; + INPUT_FORMAT?: { + NAME: string; + TO_INPUT_FORMAT?: (input: string) => Promise; + }; + LINT?: (commandDictionary, view, node) => any; + LOOP_KEYWORDS?: { + BREAK: string; + CONTINUE: string; + END_WHILE_LOOP: string; + WHILE_LOOP: string[]; + }; + MODFIY_OUTPUT?: ( + output: SeqJson | any, + parameterDictionaries: ParameterDictionary[], + channelDictionary: ChannelDictionary | null, + ) => any; + MODIFY_OUTPUT_PARSE?: ( + output: SeqJson | any, + parameterDictionaries: ParameterDictionary[], + channelDictionary: ChannelDictionary | null, + ) => any; + OUTPUT_FORMAT?: { + NAME: string; + TO_OUTPUT_FORMAT?: ( + tree: Tree | any, + sequence: string, + commandDictionary: CommandDictionary | null, + sequenceName: string, + ) => Promise; + }; } | undefined; - var GLOBALS: GlobalType[] | undefined; - var ARG_DELEGATOR: ArgDelegator | undefined; - function LINT(commandDictionary, view, node); - function TO_SEQ_JSON( - seqJson: SeqJson, - parameterDictionaries: ParameterDictionary[], - channelDictionary: ChannelDictionary | null, - ); - function FROM_SEQ_JSON( - seqJson: SeqJson, - parameterDictionaries: ParameterDictionary[], - channelDictionary: ChannelDictionary | null, - ); } export {}; diff --git a/src/assets/aerie-phoenix-wordmark.svg b/src/assets/aerie-phoenix-wordmark.svg deleted file mode 100644 index cf0dc22abd..0000000000 --- a/src/assets/aerie-phoenix-wordmark.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/components/expansion/ExpansionRuns.svelte b/src/components/expansion/ExpansionRuns.svelte index e0f5f2dabe..21352ddff9 100644 --- a/src/components/expansion/ExpansionRuns.svelte +++ b/src/components/expansion/ExpansionRuns.svelte @@ -4,11 +4,12 @@ import { base } from '$app/paths'; import type { ICellRendererParams } from 'ag-grid-community'; import { expansionRunsColumns } from '../../stores/expansion'; - import { parcel, parcelId } from '../../stores/sequencing'; + import { parcels } from '../../stores/sequencing'; import type { User } from '../../types/app'; import type { DataGridColumnDef, DataGridRowSelection } from '../../types/data-grid'; import type { ActivityInstanceJoin, ExpandedSequence, ExpansionRun } from '../../types/expansion'; - import { seqJsonToSequence } from '../../utilities/new-sequence-editor/from-seq-json'; + import type { Parcel } from '../../types/sequencing'; + import { seqJsonToSequence } from '../../utilities/sequence-editor/from-seq-json'; import SequenceEditor from '../sequencing/SequenceEditor.svelte'; import CssGrid from '../ui/CssGrid.svelte'; import CssGridGutter from '../ui/CssGridGutter.svelte'; @@ -20,6 +21,18 @@ export let expansionRuns: ExpansionRun[] = []; export let user: User | null; + let parcel: Parcel | null; + let selectedSequence: ExpandedSequence | null = null; + let selectedSequenceIds: number[] = []; + let selectedExpansionRun: ExpansionRun | null = null; + let sequenceDefinition: string; + + $: convertOutputToSequence(selectedSequence); + + async function convertOutputToSequence(sequence: ExpandedSequence | null): Promise { + sequenceDefinition = (await seqJsonToSequence(sequence?.expanded_sequence)) ?? 'No Sequence Selected'; + } + const columnDefs: DataGridColumnDef[] = [ { field: 'id', @@ -91,10 +104,7 @@ }, ]; - let selectedSequence: ExpandedSequence | null = null; - let selectedSequenceIds: number[] = []; - let selectedExpansionRun: ExpansionRun | null = null; - + $: parcel = $parcels.find(p => p.id === selectedExpansionRun?.expansion_set.parcel_id) ?? null; $: selectedSequenceIds = selectedSequence ? [selectedSequence.id] : []; function toggleRun(event: CustomEvent>) { @@ -106,11 +116,8 @@ if (isSelected) { selectedExpansionRun = clickedRun; - - $parcelId = selectedExpansionRun.expansion_set.parcel_id; } else if (selectedExpansionRun?.id === clickedRun.id) { selectedExpansionRun = null; - $parcelId = null; } } @@ -177,10 +184,10 @@ = {}; + export let selectedDictionaryIds: Record = {}; + export let isEditingDictionaries: boolean = false; export let isEditingParcel: boolean = false; export let isMultiselect: boolean = false; + export let hasDeletePermission: boolean = false; export let hasEditPermission: boolean = false; export let type: string; export let user: User | null; + let columnDefs: DataGridColumnDef[]; let dictionaryDataGrid: SingleActionDataGrid | undefined = undefined; let dictionaryColumnDefs: DataGridColumnDef[]; let displayText: string = ''; let displayTextPlural: string = ''; - let editingColumnDefs: DataGridColumnDef[]; - let hasDeletePermission: boolean = false; + let editingDictionariesColumnDefs: DataGridColumnDef[]; + let editingParcelColumnDefs: DataGridColumnDef[]; let isSequenceAdaptation: boolean = false; let sequenceAdaptationColumDefs: DataGridColumnDef[]; const dispatch = createEventDispatcher<{ delete: { id: number }; - multiSelect: { ids: Record }; - select: { id: number | null }; + select: { id: number; value: boolean }; }>(); type CellRendererParams = { @@ -46,13 +44,46 @@ $: isSequenceAdaptation = type === 'Sequence'; $: displayText = isSequenceAdaptation ? `${type} Adaptation` : `${type} Dictionary`; $: displayTextPlural = isSequenceAdaptation ? `${type} Adaptations` : `${type} Dictionaries`; - $: hasDeletePermission = featurePermissions.commandDictionary.canDelete(user); - $: if ((multiSelectDictionaryIds || dictionaryId) && dictionaryDataGrid?.redrawRows !== undefined) { + $: if (selectedDictionaryIds && dictionaryDataGrid?.redrawRows !== undefined) { dictionaryDataGrid.redrawRows(); } - $: editingColumnDefs = [ + $: editingDictionariesColumnDefs = [ + { + cellClass: 'action-cell-container', + cellRenderer: (params: DictionaryCellRendererParams) => { + const actionsDiv = document.createElement('div'); + actionsDiv.className = 'actions-cell'; + new DataGridActions({ + props: { + deleteCallback: params.deleteDictionary, + deleteTooltip: { + content: `Delete ${displayText}`, + placement: 'bottom', + }, + hasDeletePermission, + rowData: params.data, + }, + target: actionsDiv, + }); + + return actionsDiv; + }, + cellRendererParams: { + deleteDictionary, + } as CellRendererParams, + field: 'actions', + headerName: '', + resizable: false, + sortable: false, + suppressAutoSize: true, + suppressSizeToFit: true, + width: 25, + }, + ]; + + $: editingParcelColumnDefs = [ { cellDataType: 'boolean', editable: hasEditPermission, @@ -61,13 +92,20 @@ suppressSizeToFit: true, valueGetter: (params: ValueGetterParams) => { const { data } = params; + if (data) { if (isMultiselect) { - return !!multiSelectDictionaryIds[data.id]; + return !!selectedDictionaryIds[data.id]; } - return dictionaryId === data.id; + // We have a single id if we're not in multiselect mode. + const idList = Object.keys(selectedDictionaryIds); + + if (idList.length > 0) { + return Number(idList[0]) === data.id; + } } + return false; }, width: 35, @@ -75,7 +113,7 @@ ]; $: dictionaryColumnDefs = [ - ...(isEditingParcel ? editingColumnDefs : []), + ...(isEditingParcel ? editingParcelColumnDefs : []), { field: 'id', filter: 'number', @@ -92,8 +130,7 @@ ]; $: sequenceAdaptationColumDefs = [ - ...(isEditingParcel ? editingColumnDefs : []), - { field: 'name', filter: 'text', headerName: 'Name', sortable: true, width: 100 }, + ...(isEditingParcel ? editingParcelColumnDefs : []), { field: 'id', filter: 'number', @@ -104,42 +141,13 @@ suppressSizeToFit: true, width: 60, }, + { field: 'name', filter: 'text', headerName: 'Name', sortable: true, width: 100 }, { field: 'created_at', filter: 'text', headerName: 'Created At', resizable: true, sortable: true }, ]; $: columnDefs = [ ...(isSequenceAdaptation ? sequenceAdaptationColumDefs : dictionaryColumnDefs), - { - cellClass: 'action-cell-container', - cellRenderer: (params: DictionaryCellRendererParams) => { - const actionsDiv = document.createElement('div'); - actionsDiv.className = 'actions-cell'; - new DataGridActions({ - props: { - deleteCallback: params.deleteDictionary, - deleteTooltip: { - content: `Delete ${displayText}`, - placement: 'bottom', - }, - hasDeletePermission, - rowData: params.data, - }, - target: actionsDiv, - }); - - return actionsDiv; - }, - cellRendererParams: { - deleteDictionary, - } as CellRendererParams, - field: 'actions', - headerName: '', - resizable: false, - sortable: false, - suppressAutoSize: true, - suppressSizeToFit: true, - width: 25, - }, + ...(isEditingDictionaries ? editingDictionariesColumnDefs : []), ]; function deleteDictionary({ id }: Pick) { @@ -150,10 +158,20 @@ deleteDictionary({ id: event.detail[0] as number }); } + /** + * Called when a row is clicked on. + * @param event + */ function onRowClicked(event: CustomEvent>) { - selectRow(event.detail.data.id, true); + const currentValue = selectedDictionaryIds[event.detail.data.id]; + + selectRow(event.detail.data.id, currentValue === undefined ? true : !currentValue); } + /** + * Called when a checkbox is selected. + * @param event + */ function onToggle(event: CustomEvent>) { const { detail: { data, newValue }, @@ -164,17 +182,13 @@ } } - function selectRow(id: number, newValue: boolean) { - if (isMultiselect && typeof newValue === 'boolean') { - multiSelectDictionaryIds = { - ...multiSelectDictionaryIds, - [id]: newValue, - }; - dispatch('multiSelect', { ids: multiSelectDictionaryIds }); - } else { - dictionaryId = newValue ? id : null; - dispatch('select', { id: dictionaryId }); - } + function selectRow(id: number, value: boolean) { + selectedDictionaryIds = { + ...selectedDictionaryIds, + [id]: value, + }; + + dispatch('select', { id, value }); } diff --git a/src/components/parcels/ParcelForm.svelte b/src/components/parcels/ParcelForm.svelte index 115c6e892e..45452baea8 100644 --- a/src/components/parcels/ParcelForm.svelte +++ b/src/components/parcels/ParcelForm.svelte @@ -3,14 +3,13 @@ @@ -242,7 +231,7 @@ }} on:click={saveParcel} > - {savingParcel ? 'Saving...' : saveButtonText} + {saveButtonText} @@ -281,7 +270,7 @@
import type { CommandDictionary, FswCommandArgument } from '@nasa-jpl/aerie-ampcs'; - import { getAllEnumSymbols } from '../../utilities/new-sequence-editor/sequence-linter'; + import { getAllEnumSymbols } from '../../utilities/sequence-editor/sequence-linter'; export let arg: FswCommandArgument; export let commandDictionary: CommandDictionary; @@ -15,7 +15,7 @@ } -
+
Name: {arg.name}
diff --git a/src/components/sequencing/CommandTooltip.svelte b/src/components/sequencing/CommandTooltip.svelte index 3a2fb01d12..36006ff979 100644 --- a/src/components/sequencing/CommandTooltip.svelte +++ b/src/components/sequencing/CommandTooltip.svelte @@ -21,19 +21,21 @@ } -
-
+
+
{commandExample}
-
-
{command.description}
diff --git a/src/components/sequencing/form/AddMissingArgsButton.svelte b/src/components/sequencing/form/AddMissingArgsButton.svelte index da3f23ef1e..5d7b1c6072 100644 --- a/src/components/sequencing/form/AddMissingArgsButton.svelte +++ b/src/components/sequencing/form/AddMissingArgsButton.svelte @@ -5,5 +5,5 @@
- +
diff --git a/src/components/sequencing/form/ArgEditor.svelte b/src/components/sequencing/form/ArgEditor.svelte index 960c427d83..b1d997fffb 100644 --- a/src/components/sequencing/form/ArgEditor.svelte +++ b/src/components/sequencing/form/ArgEditor.svelte @@ -10,7 +10,7 @@ isNumberArg, quoteEscape, type ArgTextDef, - } from '../../../utilities/codemirror/codemirror-utils'; + } from './../../../utilities/codemirror/codemirror-utils'; import AddMissingArgsButton from './AddMissingArgsButton.svelte'; import ArgTitle from './ArgTitle.svelte'; import EnumEditor from './EnumEditor.svelte'; @@ -38,86 +38,99 @@ } -{#if !argInfo.argDef} - {#if argInfo.text} -
Unknown Argument
- { - if (argInfo.node) { - setInEditor(argInfo.node, ''); - } - }} - /> - {/if} -{:else} - - {#if argInfo.argDef.arg_type === 'enum' && argInfo.node} - {#if argInfo.node?.name === 'String'} - { +
+ {#if !argInfo.argDef} + {#if argInfo.text} +
Unknown Argument
+ { if (argInfo.node) { - setInEditor(argInfo.node, val); + setInEditor(argInfo.node, ''); } }} /> - {:else} - {/if} - {:else if isNumberArg(argInfo.argDef) && argInfo.node?.name === 'Number'} - { - if (argInfo.node) { - setInEditor(argInfo.node, val); - } - }} - /> - {:else if isFswCommandArgumentVarString(argInfo.argDef)} - { - if (argInfo.node) { - setInEditor(argInfo.node, val); - } - }} - /> - {:else if isFswCommandArgumentRepeat(argInfo.argDef) && !!argInfo.children} - {#each argInfo.children as childArgInfo} - {#if childArgInfo.node} - + {:else} + + {#if argInfo.argDef.arg_type === 'enum' && argInfo.node} + {#if argInfo.node?.name === 'String'} + { + if (argInfo.node) { + setInEditor(argInfo.node, val); + } + }} + /> + {:else} + {/if} - {/each} - {#if argInfo.children.find(childArgInfo => !childArgInfo.node)} - { - if (argInfo.node && argInfo.children) { - addDefaultArgs(argInfo.node, getMissingArgDefs(argInfo.children)); + {:else if isNumberArg(argInfo.argDef) && argInfo.node?.name === 'Number'} + { + if (argInfo.node) { + setInEditor(argInfo.node, val.toString()); } }} /> - {:else if !!argInfo.argDef.repeat} -
- -
+ {:else if isFswCommandArgumentVarString(argInfo.argDef)} + { + if (argInfo.node) { + setInEditor(argInfo.node, val); + } + }} + /> + {:else if isFswCommandArgumentRepeat(argInfo.argDef) && !!argInfo.children} + {#each argInfo.children as childArgInfo} + {#if childArgInfo.node} + + {/if} + {/each} + {#if argInfo.children.find(childArgInfo => !childArgInfo.node)} + { + if (argInfo.node && argInfo.children) { + addDefaultArgs(argInfo.node, getMissingArgDefs(argInfo.children)); + } + }} + /> + {:else if !!argInfo.argDef.repeat} +
+ +
+ {/if} + {:else} +
Unexpected value for definition
{/if} - {:else} -
Unexpected value for definition
{/if} -{/if} +
+ + diff --git a/src/components/sequencing/form/ArgTitle.svelte b/src/components/sequencing/form/ArgTitle.svelte index 0f2f020504..1bf4f41264 100644 --- a/src/components/sequencing/form/ArgTitle.svelte +++ b/src/components/sequencing/form/ArgTitle.svelte @@ -2,13 +2,15 @@ -
- {title} - {argDef.description} -
+ +
+ {argDef.description} +
+
diff --git a/src/components/sequencing/form/EnumEditor.svelte b/src/components/sequencing/form/EnumEditor.svelte index 6acf459189..0557b43b67 100644 --- a/src/components/sequencing/form/EnumEditor.svelte +++ b/src/components/sequencing/form/EnumEditor.svelte @@ -49,7 +49,7 @@ searchPlaceholder="Filter values" /> {:else} - {#if !isValueInEnum} {/if} @@ -59,9 +59,3 @@ {/if}
- - diff --git a/src/components/sequencing/form/ExtraArgumentEditor.svelte b/src/components/sequencing/form/ExtraArgumentEditor.svelte index 4e31d8a183..5ecf83b081 100644 --- a/src/components/sequencing/form/ExtraArgumentEditor.svelte +++ b/src/components/sequencing/form/ExtraArgumentEditor.svelte @@ -6,7 +6,14 @@ export let setInEditor: () => void; -
- - {initVal} +
+ +
+ + diff --git a/src/components/sequencing/form/NumEditor.svelte b/src/components/sequencing/form/NumEditor.svelte index 8b72e9d6af..2d1a3fada2 100644 --- a/src/components/sequencing/form/NumEditor.svelte +++ b/src/components/sequencing/form/NumEditor.svelte @@ -7,59 +7,37 @@ // (("e" | "E") ("+" | "-")? ("_" | @digit)+)? | // @digit ("_" | @digit)* "n" | - import { isFswCommandArgumentUnsigned, type NumberArg } from '../../../utilities/codemirror/codemirror-utils'; - - // const PAT_INT = "^[-+]?\\d+$"; - // const PAT_FLOAT = "^[-+]?\\d+\.?\\d*$"; + import { isFswCommandArgumentUnsigned, type NumberArg } from './../../../utilities/codemirror/codemirror-utils'; export let argDef: NumberArg; - export let initVal: string; - export let setInEditor: (val: string) => void; + export let initVal: number; + export let setInEditor: (val: number) => void; let max: number = Infinity; let min: number = -Infinity; - let value: string; - - // $: pattern = isFswCommandArgumentUnsigned(argDef) ? PAT_INT : PAT_FLOAT; + let value: number; $: max = argDef.range?.max ?? Infinity; $: min = argDef.range?.min ?? (isFswCommandArgumentUnsigned(argDef) ? 0 : -Infinity); $: value = initVal; $: valFloat = Number(value); $: { - if (value && !isNaN(valFloat)) { + if (typeof value === 'number' && !isNaN(valFloat)) { setInEditor(value); } } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - function customValidate(e: Event & { currentTarget: EventTarget & HTMLInputElement }) { - // const val = (e.target as HTMLInputElement).value; - const numVal = Number(value); - if (numVal < min) { - (e.target as HTMLInputElement).setCustomValidity('value is too low'); - } else if (numVal > max) { - (e.target as HTMLInputElement).setCustomValidity('value is too low'); - } else { - (e.target as HTMLInputElement).setCustomValidity(''); - } - }
- - - - {#if typeof min === 'number' && typeof max === 'number' && min === max && valFloat !== max} - + + {#if typeof min === 'number' && typeof max === 'number' && (valFloat < min || valFloat > max)} + {/if}
diff --git a/src/components/sequencing/form/StringEditor.svelte b/src/components/sequencing/form/StringEditor.svelte index e610e44ed9..f3fa05869e 100644 --- a/src/components/sequencing/form/StringEditor.svelte +++ b/src/components/sequencing/form/StringEditor.svelte @@ -2,7 +2,7 @@ -
- -
- - + diff --git a/src/css/tooltip.css b/src/css/tooltip.css index 1e032ae8fb..5ce6aa3890 100644 --- a/src/css/tooltip.css +++ b/src/css/tooltip.css @@ -1,3 +1,7 @@ +.sequence-tooltip { + max-width: 50rem; +} + .tooltip { background-color: rgba(0, 0, 0, 0.8); border-radius: 4px; diff --git a/src/routes/dictionaries/+page.svelte b/src/routes/dictionaries/+page.svelte index 9670687157..a52ac2c73c 100644 --- a/src/routes/dictionaries/+page.svelte +++ b/src/routes/dictionaries/+page.svelte @@ -10,12 +10,8 @@ import Panel from '../../components/ui/Panel.svelte'; import SectionTitle from '../../components/ui/SectionTitle.svelte'; import { DictionaryTypes } from '../../enums/dictionaryTypes'; - import { - channelDictionaries, - commandDictionaries, - parameterDictionaries, - sequenceAdaptations, - } from '../../stores/sequencing'; + import { sequenceAdaptations } from '../../stores/sequence-adaptation'; + import { channelDictionaries, commandDictionaries, parameterDictionaries } from '../../stores/sequencing'; import effects from '../../utilities/effects'; import { permissionHandler } from '../../utilities/permissionHandler'; import { featurePermissions } from '../../utilities/permissions'; @@ -29,13 +25,17 @@ let createButtonDisabled: boolean = false; let createDictionaryError: string | null = null; let creatingDictionary: boolean = false; - let files: FileList; + let files: FileList | undefined; let file: File; let fileInput: HTMLInputElement; let isSequenceAdaptation: boolean = false; let sequenceAdaptationName: string; - $: hasCreatePermission = featurePermissions.commandDictionary.canCreate(data.user); + $: hasCreatePermission = + featurePermissions.channelDictionary.canCreate(data.user) && + featurePermissions.commandDictionary.canCreate(data.user) && + featurePermissions.parameterDictionary.canCreate(data.user) && + featurePermissions.sequenceAdaptation.canCreate(data.user); $: createButtonDisabled = !files || files?.length === 0 || (isSequenceAdaptation && sequenceAdaptationName === ''); $: { if (files && files.length > 0) { @@ -80,6 +80,8 @@ } } + // Set files to undefined to reset the input form and set the value to empty string to clear the uploaded file. + files = undefined; fileInput.value = ''; isSequenceAdaptation = false; sequenceAdaptationName = ''; @@ -158,7 +160,7 @@