Skip to content

Commit

Permalink
Show variables and globals in form view (#1536)
Browse files Browse the repository at this point in the history
* support for seqN variables in form view

* scope vml variables and parameters pulled in

* return globals even if parse tree is unavailable

* pull isVariable check into reactive block

* reuse isDefined function

* toggle between literal and reference option

* change term reference to symbol

* readability of enable repeat args check

* don't use symbol for boolean test

* pr feedback reactive statement not triggered

* pr feedback - combine template literals where possible

* pr feedback variable name

* use filterEmpty instead of new duplicate function

* move CSS attribute to parent, playwrigth selector update

* ensure functions called in reactive statements only depend on parameters

* remove extraneous optional chain

* more descriptive test names

* pr feedback, delete useQuotes option and format in ArgEditor
  • Loading branch information
joswig authored Nov 9, 2024
1 parent f7862d2 commit 0f606b6
Show file tree
Hide file tree
Showing 16 changed files with 384 additions and 89 deletions.
2 changes: 1 addition & 1 deletion e2e-tests/fixtures/Sequence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export class Sequence {
await expect(this.command).toHaveText('C FSW_CMD_0 "ON" false 1');
await this.page
.locator('fieldset')
.filter({ hasText: 'enum_arg_0 ONOFF' })
.filter({ hasText: 'enum_arg_0 Value Type Literal' })
.getByRole('combobox')
.selectOption('OFF');

Expand Down
7 changes: 5 additions & 2 deletions src/components/sequencing/SequenceEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { lintGutter } from '@codemirror/lint';
import { Compartment, EditorState } from '@codemirror/state';
import { type ViewUpdate } from '@codemirror/view';
import type { SyntaxNode } from '@lezer/common';
import type { SyntaxNode, Tree } from '@lezer/common';
import type { ChannelDictionary, CommandDictionary, ParameterDictionary } from '@nasa-jpl/aerie-ampcs';
import ChevronDownIcon from '@nasa-jpl/stellar/icons/chevron_down.svg?component';
import CollapseIcon from 'bootstrap-icons/icons/arrow-bar-down.svg?component';
Expand Down Expand Up @@ -102,6 +102,7 @@
let menu: Menu;
let outputFormats: IOutputFormat[];
let selectedNode: SyntaxNode | null;
let currentTree: Tree;
let commandInfoMapper: CommandInfoMapper = new SeqNCommandInfoMapper();
let selectedOutputFormat: IOutputFormat | undefined;
let toggleSeqJsonPreview: boolean = false;
Expand Down Expand Up @@ -226,7 +227,7 @@
}
}
$: showOutputs = !isInVmlMode && !!outputFormats.length;
$: showOutputs = !isInVmlMode && outputFormats.length > 0;
$: {
if (showOutputs) {
editorHeights = toggleSeqJsonPreview ? '1fr 3px 1fr' : '1.88fr 3px 80px';
Expand Down Expand Up @@ -361,6 +362,7 @@
commandInfoMapper = new SeqNCommandInfoMapper();
}
selectedNode = updatedSelectionNode;
currentTree = tree;
}
}
Expand Down Expand Up @@ -539,6 +541,7 @@
{#if !!commandDictionary && !!selectedNode}
<SelectedCommand
node={selectedNode}
tree={currentTree}
{channelDictionary}
{commandDictionary}
{commandInfoMapper}
Expand Down
101 changes: 77 additions & 24 deletions src/components/sequencing/form/ArgEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
import {
getMissingArgDefs,
isFswCommandArgumentBoolean,
isFswCommandArgumentEnum,
isFswCommandArgumentRepeat,
isFswCommandArgumentVarString,
isNumberArg,
quoteEscape,
unquoteUnescape,
type ArgTextDef,
} from './../../../utilities/codemirror/codemirror-utils';
import AddMissingArgsButton from './AddMissingArgsButton.svelte';
Expand All @@ -26,24 +28,46 @@
export let setInEditor: (token: SyntaxNode, val: string) => void;
export let addDefaultArgs: (commandNode: SyntaxNode, argDefs: FswCommandArgument[]) => void;
export let commandInfoMapper: CommandInfoMapper;
export let variablesInScope: string[];
let argDef: FswCommandArgument | undefined = undefined;
let enableRepeatAdd: boolean = false;
let isSymbol: boolean = false;
$: argDef = argInfo.argDef;
$: {
isSymbol = commandInfoMapper.isArgumentNodeOfVariableType(argInfo.node ?? null);
if (!!argDef && isSymbol) {
argDef = {
arg_type: 'enum',
bit_length: null,
default_value: null,
description: argDef.description,
enum_name: 'variables',
name: argDef.name,
range: variablesInScope,
};
}
}
$: enableRepeatAdd =
argInfo.argDef &&
isFswCommandArgumentRepeat(argInfo.argDef) &&
argInfo.children &&
argInfo.argDef.repeat &&
argInfo.children.length < argInfo.argDef.repeat.arguments.length * (argInfo.argDef.repeat.max ?? Infinity);
argDef !== undefined &&
isFswCommandArgumentRepeat(argDef) &&
argInfo.children !== undefined &&
argDef.repeat !== null &&
argInfo.children.length < argDef.repeat.arguments.length * (argDef.repeat.max ?? Infinity);
function addRepeatTuple() {
const repeatArgs = argInfo.argDef && isFswCommandArgumentRepeat(argInfo.argDef) && argInfo.argDef.repeat?.arguments;
const repeatArgs = argDef && isFswCommandArgumentRepeat(argDef) && argDef.repeat?.arguments;
if (argInfo.node && repeatArgs) {
addDefaultArgs(argInfo.node, repeatArgs);
}
}
</script>

<fieldset>
{#if !argInfo.argDef}
{#if !argDef}
{#if argInfo.text}
<div class="st-typography-medium" title="Unknown Argument">Unknown Argument</div>
<ExtraArgumentEditor
Expand All @@ -56,16 +80,38 @@
/>
{/if}
{:else}
<ArgTitle argDef={argInfo.argDef} />
{#if argInfo.argDef.arg_type === 'enum' && argInfo.node}
{#if argInfo.argDef}
<ArgTitle
argDef={argInfo.argDef}
{commandInfoMapper}
argumentValueCategory={isSymbol ? 'Symbol' : 'Literal'}
setInEditor={val => {
if (argInfo.node) {
setInEditor(argInfo.node, val);
}
}}
/>
{/if}
{#if isSymbol && isFswCommandArgumentEnum(argDef)}
<div class="st-typography-small-caps">Reference</div>
<EnumEditor
{argDef}
initVal={argInfo.text ?? ''}
setInEditor={val => {
if (argInfo.node) {
setInEditor(argInfo.node, val);
}
}}
/>
{:else if isFswCommandArgumentEnum(argDef) && argInfo.node}
{#if commandInfoMapper.nodeTypeEnumCompatible(argInfo.node)}
<EnumEditor
{commandDictionary}
argDef={argInfo.argDef}
initVal={argInfo.text ?? ''}
{argDef}
initVal={unquoteUnescape(argInfo.text ?? '')}
setInEditor={val => {
if (argInfo.node) {
setInEditor(argInfo.node, val);
setInEditor(argInfo.node, quoteEscape(val));
}
}}
/>
Expand All @@ -81,40 +127,47 @@
Convert to enum type
</button>
{/if}
{:else if isNumberArg(argInfo.argDef) && commandInfoMapper.nodeTypeNumberCompatible(argInfo.node ?? null)}
{:else if isNumberArg(argDef) && commandInfoMapper.nodeTypeNumberCompatible(argInfo.node ?? null)}
<NumEditor
argDef={argInfo.argDef}
initVal={Number(argInfo.text) ?? argInfo.argDef.default_value ?? 0}
{argDef}
initVal={Number(argInfo.text) ?? argDef.default_value ?? 0}
setInEditor={val => {
if (argInfo.node) {
setInEditor(argInfo.node, val.toString());
}
}}
/>
{:else if isFswCommandArgumentVarString(argInfo.argDef)}
{:else if isFswCommandArgumentVarString(argDef)}
<StringEditor
argDef={argInfo.argDef}
{argDef}
initVal={argInfo.text ?? ''}
setInEditor={val => {
if (argInfo.node) {
setInEditor(argInfo.node, val);
}
}}
/>
{:else if isFswCommandArgumentBoolean(argInfo.argDef)}
{:else if isFswCommandArgumentBoolean(argDef)}
<BooleanEditor
argDef={argInfo.argDef}
{argDef}
initVal={argInfo.text ?? ''}
setInEditor={val => {
if (argInfo.node) {
setInEditor(argInfo.node, val);
}
}}
/>
{:else if isFswCommandArgumentRepeat(argInfo.argDef) && !!argInfo.children}
{:else if isFswCommandArgumentRepeat(argDef) && !!argInfo.children}
{#each argInfo.children as childArgInfo}
{#if childArgInfo.node}
<svelte:self argInfo={childArgInfo} {commandInfoMapper} {commandDictionary} {setInEditor} {addDefaultArgs} />
<svelte:self
argInfo={childArgInfo}
{commandInfoMapper}
{commandDictionary}
{setInEditor}
{addDefaultArgs}
{variablesInScope}
/>
{/if}
{/each}
{#if argInfo.children.find(childArgInfo => !childArgInfo.node)}
Expand All @@ -125,15 +178,15 @@
}
}}
/>
{:else if !!argInfo.argDef.repeat}
{:else if !!argDef.repeat}
<div>
<button
class="st-button secondary"
disabled={!enableRepeatAdd}
on:click={addRepeatTuple}
title={`Add additional set of argument values to ${argInfo.argDef.name} repeat array`}
title={`Add additional set of argument values to ${argDef.name} repeat array`}
>
Add {argInfo.argDef.name} tuple
Add {argDef.name} tuple
</button>
</div>
{/if}
Expand Down
88 changes: 71 additions & 17 deletions src/components/sequencing/form/ArgTitle.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<script lang="ts">
import type { FswCommandArgument } from '@nasa-jpl/aerie-ampcs';
import { isArray } from 'lodash-es';
import type { CommandInfoMapper } from '../../../utilities/codemirror/commandInfoMapper';
import { getTarget } from '../../../utilities/generic';
import Collapse from '../../Collapse.svelte';
import {
isFswCommandArgumentFloat,
Expand All @@ -13,10 +15,19 @@
} from './../../../utilities/codemirror/codemirror-utils';
export let argDef: FswCommandArgument;
export let commandInfoMapper: CommandInfoMapper;
export let setInEditor: (val: string) => void;
export let argumentValueCategory: 'Literal' | 'Symbol';
$: title = getArgTitle(argDef);
let title: string = '';
let typeInfo: string = '';
let formattedRange: string = '';
function compactType(argDef: FswCommandArgument) {
$: typeInfo = compactType(argDef);
$: title = getArgTitle(argDef, typeInfo);
$: formattedRange = formatRange(argDef);
function compactType(argDef: FswCommandArgument): string {
if (isFswCommandArgumentUnsigned(argDef)) {
return `U${argDef.bit_length}`;
} else if (isFswCommandArgumentInteger(argDef)) {
Expand All @@ -30,7 +41,14 @@
return '';
}
function getArgTitle(argDef: FswCommandArgument) {
function formatRange(argDef: FswCommandArgument): string {
if ('range' in argDef && argDef.range !== null && !isArray(argDef.range)) {
return `[${argDef.range.min} – ${argDef.range.max}]`;
}
return '';
}
function getArgTitle(argDef: FswCommandArgument, typeInfo: string): string {
if (
isFswCommandArgumentRepeat(argDef) &&
typeof argDef.repeat?.max === 'number' &&
Expand All @@ -39,29 +57,65 @@
return `${argDef.name} - [${argDef.repeat?.min}, ${argDef.repeat?.max}] sets`;
}
let compactTypeInfo = compactType(argDef);
if (compactTypeInfo) {
compactTypeInfo = ` [${compactTypeInfo}]`;
}
let base = `${argDef.name}${compactTypeInfo}`;
if ('range' in argDef && argDef.range) {
if (isArray(argDef.range)) {
base += ` [${argDef.range.join(', ')}]`;
} else {
base += ` [${argDef.range.min} – ${argDef.range.max}]`;
}
}
const bracketedTypeInfo = typeInfo && ` [${typeInfo}]`;
const base = `${argDef.name}${bracketedTypeInfo} ${formatRange(argDef)}`;
if ('units' in argDef) {
return `${base} – (${argDef.units})`;
}
return base;
}
function onValueTypeChange(event: Event) {
const { value } = getTarget(event);
if (value === 'Literal') {
setInEditor(commandInfoMapper.getDefaultValueForArgumentDef(argDef, {}));
} else {
setInEditor('VARIABLE_OR_CONSTANT_NAME');
}
}
</script>

<Collapse headerHeight={24} padContent={false} {title} defaultExpanded={false}>
<div style="padding-bottom: 4px">
{argDef.description}
<div class="w-100 labeled-values" style="padding-bottom: 4px">
{#if formattedRange}
<div>Range</div>
<div>{formattedRange}</div>
{/if}

{#if typeInfo}
<div>Type</div>
<div>{typeInfo}</div>
{/if}

{#if argDef.description}
<div>Description</div>
<div>
{argDef.description}
</div>
{/if}

<div>Value Type</div>

<select class="st-select" required bind:value={argumentValueCategory} on:change={onValueTypeChange}>
<option value="Literal"> Literal </option>
<option value="Symbol"> Symbol </option>
</select>
</div>
</Collapse>

<style>
.labeled-values {
align-content: center;
align-items: top;
column-gap: 3px;
display: grid;
grid-template-columns: max-content 1fr;
row-gap: 2px;
}
.labeled-values > div:nth-child(odd) {
font-weight: bold;
}
</style>
12 changes: 5 additions & 7 deletions src/components/sequencing/form/EnumEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,25 @@
<script lang="ts">
import type { CommandDictionary, FswCommandArgumentEnum } from '@nasa-jpl/aerie-ampcs';
import type { SelectedDropdownOptionValue } from '../../../types/dropdown';
import { quoteEscape, unquoteUnescape } from '../../../utilities/codemirror/codemirror-utils';
import { quoteEscape } from '../../../utilities/codemirror/codemirror-utils';
import SearchableDropdown from '../../ui/SearchableDropdown.svelte';
const SEARCH_THRESHOLD = 100;
const MAX_SEARCH_ITEMS = 1_000;
export let argDef: FswCommandArgumentEnum;
export let commandDictionary: CommandDictionary;
export let commandDictionary: CommandDictionary | null = null;
export let initVal: string;
export let setInEditor: (val: string) => void;
let enumValues: string[];
let isValueInEnum: boolean = false;
let value: string;
$: value = unquoteUnescape(initVal);
$: enumValues = commandDictionary.enumMap[argDef.enum_name]?.values?.map(v => v.symbol) ?? argDef.range ?? [];
$: value = initVal;
$: enumValues = commandDictionary?.enumMap[argDef.enum_name]?.values?.map(v => v.symbol) ?? argDef.range ?? [];
$: isValueInEnum = !!enumValues.find(ev => ev === value);
$: {
setInEditor(quoteEscape(value));
}
$: setInEditor(value);
$: options = enumValues.map(ev => ({
display: ev,
value: ev,
Expand Down
Loading

0 comments on commit 0f606b6

Please sign in to comment.