From 04bafced517c09b340ada58051f175ac54eddf83 Mon Sep 17 00:00:00 2001 From: duranb Date: Thu, 14 May 2026 14:41:09 -0700 Subject: [PATCH 01/10] Refactor output format controls into separate OutputToolbar component - Extract output format dropdown and action buttons from EditorToolbar into new OutputToolbar component - Remove output format props and dropdown menu from EditorToolbar (outputFormats, outputDisabled, onCopyOutput, onDownloadOutput) - Add OutputToolbar component with output format selection, copy, download, and preview toggle buttons - Update SequenceEditor to use OutputToolbar in the output panel header - Simplify downloadOutput --- .../sequencing/EditorToolbar.svelte | 51 +------------ .../sequencing/OutputToolbar.svelte | 74 +++++++++++++++++++ .../sequencing/SequenceEditor.svelte | 64 +++++++--------- .../workspaces/[workspaceId]/+page.svelte | 11 +-- 4 files changed, 103 insertions(+), 97 deletions(-) create mode 100644 src/components/sequencing/OutputToolbar.svelte diff --git a/src/components/sequencing/EditorToolbar.svelte b/src/components/sequencing/EditorToolbar.svelte index 4481012a19..7aae1d7aec 100644 --- a/src/components/sequencing/EditorToolbar.svelte +++ b/src/components/sequencing/EditorToolbar.svelte @@ -1,8 +1,8 @@ + +
+ {#if outputLanguages.length > 0} + + + {/if} +
+ + + + + + + + + + +
+
diff --git a/src/components/sequencing/SequenceEditor.svelte b/src/components/sequencing/SequenceEditor.svelte index 7c358cfabe..6dc3ed35ef 100644 --- a/src/components/sequencing/SequenceEditor.svelte +++ b/src/components/sequencing/SequenceEditor.svelte @@ -13,10 +13,9 @@ PhoenixAdaptation, PhoenixContext, } from '@nasa-jpl/aerie-sequence-languages'; - import { Button, Label } from '@nasa-jpl/stellar-svelte'; import { basicSetup, EditorView } from 'codemirror'; import { debounce } from 'lodash-es'; - import { FileBracesCorner, PanelBottomClose, PanelBottomOpen } from 'lucide-svelte'; + import { FileBracesCorner } from 'lucide-svelte'; import { createEventDispatcher, onDestroy, onMount } from 'svelte'; import { clearWorkspaceAdaptationMessages } from '../../stores/workspaceErrors'; import type { ActionDefinition } from '../../types/actions'; @@ -31,10 +30,10 @@ import CssGridGutter from '../ui/CssGridGutter.svelte'; import Panel from '../ui/Panel.svelte'; import SectionTitle from '../ui/SectionTitle.svelte'; - import Tooltip from '../ui/Tooltip.svelte'; import FileMetadataBanner from '../workspace/FileMetadataBanner.svelte'; import CommandPanel from './CommandPanel/CommandPanel.svelte'; import EditorToolbar from './EditorToolbar.svelte'; + import OutputToolbar from './OutputToolbar.svelte'; export let availableActions: { action: ActionDefinition; parameter: string }[] = []; export let fileMetadata: WorkspaceFileMetadata | null = null; @@ -58,7 +57,7 @@ const dispatch = createEventDispatcher<{ adaptationError: { error: Error; filePath: string }; downloadInput: { filePath: string }; - downloadOutput: { content: string; filePath: string; filename: string; outputLanguage: OutputLanguage }; + downloadOutput: { content: string; filePath: string; filename: string }; editorViewChange: EditorView | null; lintChange: { diagnostics: LintDiagnostic[]; filePath: string }; runAction: { action: ActionDefinition; parameter: string }; @@ -261,16 +260,22 @@ } }, 300); - function downloadOutputFormat(outputLanguage: OutputLanguage): void { + function downloadOutputFormat(): void { const content = editorOutputView.state.doc.toString(); - const filename = replaceFileExtension( - sequenceName, - sequenceAdaptation.input.fileExtension, - outputLanguage.fileExtension, - ); + if (selectedOutputFormat) { + const filename = replaceFileExtension( + sequenceName, + sequenceAdaptation.input.fileExtension, + selectedOutputFormat.fileExtension, + ); - dispatch('downloadOutput', { content, filePath: sequenceFilePath, filename, outputLanguage }); + dispatch('downloadOutput', { + content, + filePath: sequenceFilePath, + filename, + }); + } } function downloadInputFormat(): void { @@ -428,10 +433,6 @@ downloadDisabled={disableCopyAndExport} downloadTooltip="Download sequence contents" onDownload={downloadInputFormat} - outputFormats={showOutputs ? sequenceAdaptation.outputs : []} - outputDisabled={disableCopyAndExport} - onCopyOutput={copyOutputFormatToClipboard} - onDownloadOutput={downloadOutputFormat} showSaveButton={!(readOnly || previewOnly || isLoading)} saveDisabled={!isSequenceDefinitionUpdated} saveHighlighted={isSequenceDefinitionUpdated} @@ -455,30 +456,15 @@ {selectedOutputFormat?.name} (Read-only)
-
- {#if sequenceAdaptation.outputs.length > 0} - - - {/if} - - - - -
+
diff --git a/src/routes/workspaces/[workspaceId]/+page.svelte b/src/routes/workspaces/[workspaceId]/+page.svelte index 7d78bbe878..1cc7e7a300 100644 --- a/src/routes/workspaces/[workspaceId]/+page.svelte +++ b/src/routes/workspaces/[workspaceId]/+page.svelte @@ -7,12 +7,7 @@ import { page } from '$app/stores'; import { env } from '$env/dynamic/public'; import type { ChannelDictionary, CommandDictionary, ParameterDictionary } from '@nasa-jpl/aerie-ampcs'; - import type { - LibrarySequenceSignature, - OutputLanguage, - PhoenixContext, - UserSequence, - } from '@nasa-jpl/aerie-sequence-languages'; + import type { LibrarySequenceSignature, PhoenixContext, UserSequence } from '@nasa-jpl/aerie-sequence-languages'; import { Button, Checkbox, Resizable, Select } from '@nasa-jpl/stellar-svelte'; import type { EditorView } from 'codemirror'; import { capitalize, startCase } from 'lodash-es'; @@ -1213,9 +1208,7 @@ onDownloadFile(filePath); } - async function onDownloadOutput( - event: CustomEvent<{ content: string; filePath: string; filename: string; outputLanguage: OutputLanguage }>, - ) { + async function onDownloadOutput(event: CustomEvent<{ content: string; filePath: string; filename: string }>) { const { content, filePath, filename } = event.detail; // Check if downloading output for the active file with unsaved changes From 3700d49d73a367af086d39f06095af1621283094 Mon Sep 17 00:00:00 2001 From: duranb Date: Thu, 14 May 2026 15:20:19 -0700 Subject: [PATCH 02/10] Only show output panel when editing a file matching the adaptation's input extension --- src/components/sequencing/SequenceEditor.svelte | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/sequencing/SequenceEditor.svelte b/src/components/sequencing/SequenceEditor.svelte index 6dc3ed35ef..75de2522f3 100644 --- a/src/components/sequencing/SequenceEditor.svelte +++ b/src/components/sequencing/SequenceEditor.svelte @@ -25,7 +25,7 @@ import { blockTheme } from '../../utilities/codemirror/themes/block'; import { phoenixResources } from '../../utilities/sequence-editor/adaptation-resources'; import { showFailureToast, showSuccessToast } from '../../utilities/toast'; - import { replaceFileExtension } from '../../utilities/workspaces'; + import { doesFilenameMatchExtension, replaceFileExtension } from '../../utilities/workspaces'; import CssGrid from '../ui/CssGrid.svelte'; import CssGridGutter from '../ui/CssGridGutter.svelte'; import Panel from '../ui/Panel.svelte'; @@ -151,7 +151,8 @@ $: { previousShowOutputs = showOutputs; - showOutputs = sequenceAdaptation.outputs.length > 0; + const isInputFile = doesFilenameMatchExtension(sequenceAdaptation.input.fileExtension, sequenceName); + showOutputs = isInputFile && sequenceAdaptation.outputs.length > 0; } $: if (showOutputs) { editorHeights = toggleSeqJsonPreview ? '1fr 3px 1fr' : '1.88fr 3px 80px'; From cd235a0a5f0005fd4923a4389343be009605a10e Mon Sep 17 00:00:00 2001 From: duranb Date: Thu, 14 May 2026 15:21:09 -0700 Subject: [PATCH 03/10] Use output language extensions when editing non-input sequence files - Add `isInputFile` prop to SequenceEditor to determine if the current file matches the adaptation's input extension - Apply input editor extensions only when editing an input file; otherwise, find and use the matching output format's extensions - Show output panel only when editing an input file with available output formats - Compute `activeFileIsInputSequence` in workspace page to distinguish input files from output files - Update right --- .../sequencing/SequenceEditor.svelte | 17 +++++++++-- .../workspaces/[workspaceId]/+page.svelte | 29 ++++++++++--------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/components/sequencing/SequenceEditor.svelte b/src/components/sequencing/SequenceEditor.svelte index 75de2522f3..771895fe35 100644 --- a/src/components/sequencing/SequenceEditor.svelte +++ b/src/components/sequencing/SequenceEditor.svelte @@ -41,6 +41,7 @@ export let includeActions: boolean = false; export let preserveAdaptationLog: boolean = false; export let isLoading: boolean = false; + export let isInputFile: boolean = false; export let previewOnly: boolean = false; export let readOnly: boolean = false; export let sequenceAdaptation: PhoenixAdaptation; @@ -92,12 +93,23 @@ $: commandInfoMapper = sequenceAdaptation.input.commandInfoMapper; - $: if (phoenixContext && sequenceAdaptation.input.getEditorExtension) { + // Only use input extensions if the sequence file matches the input file extensions + $: if (phoenixContext && isInputFile && sequenceAdaptation.input.getEditorExtension) { inputEditorExtension = sequenceAdaptation.input.getEditorExtension(phoenixContext, phoenixResources); + } else if (sequenceAdaptation.outputs.length > 0) { + const matchingOutputLanguage = sequenceAdaptation.outputs.find(output => + doesFilenameMatchExtension(output.fileExtension, sequenceName), + ); + inputEditorExtension = matchingOutputLanguage?.getEditorExtension?.(phoenixContext, phoenixResources) ?? []; + } else { + inputEditorExtension = []; } - $: if (sequenceAdaptation.outputs.length > 0) { + // Only use output extensions if the sequence file is an input file + $: if (isInputFile && sequenceAdaptation.outputs.length > 0) { selectedOutputFormat = sequenceAdaptation.outputs[0]; + } else { + selectedOutputFormat = undefined; } $: if (phoenixContext && selectedOutputFormat?.getEditorExtension) { @@ -151,7 +163,6 @@ $: { previousShowOutputs = showOutputs; - const isInputFile = doesFilenameMatchExtension(sequenceAdaptation.input.fileExtension, sequenceName); showOutputs = isInputFile && sequenceAdaptation.outputs.length > 0; } $: if (showOutputs) { diff --git a/src/routes/workspaces/[workspaceId]/+page.svelte b/src/routes/workspaces/[workspaceId]/+page.svelte index 1cc7e7a300..f324c0a285 100644 --- a/src/routes/workspaces/[workspaceId]/+page.svelte +++ b/src/routes/workspaces/[workspaceId]/+page.svelte @@ -109,6 +109,7 @@ import { showFailureToast, showSuccessToast } from '../../../utilities/toast'; import { computeMovedFilePath, + doesFilenameMatchExtension, downloadWorkspaceNodesAsZip, findNodeAffectingPath, flattenWorkspaceTreeWithPaths, @@ -272,17 +273,21 @@ $: activeFileMetadata = ($activeDocumentPath && workspaceTreeMap[$activeDocumentPath]?.metadata) || null; $: activeFileIsSequence = - $activeDocumentPath !== null && - $activeDocument.type !== null && - $activeDocument.type === WorkspaceContentType.Sequence; + $activeDocumentPath === null || + ($activeDocument.type !== null && $activeDocument.type === WorkspaceContentType.Sequence); + $: activeFileIsInputSequence = + activeFileIsSequence && + (!$activeDocument.fileName || + (!!$activeDocument.fileName && + doesFilenameMatchExtension($sequenceAdaptation.input.fileExtension, $activeDocument.fileName))); $: commandInfoMapper = $sequenceAdaptation.input.commandInfoMapper; $: isFileReadOnly = activeFileMetadata?.readOnly ?? false; // Switch right panel tab when file type changes - let previousActiveFileIsSequence: boolean = activeFileIsSequence; - $: if (activeFileIsSequence !== previousActiveFileIsSequence) { - previousActiveFileIsSequence = activeFileIsSequence; - if (!activeFileIsSequence) { + let previousActiveFileIsInputSequence: boolean = activeFileIsInputSequence; + $: if (activeFileIsInputSequence !== previousActiveFileIsInputSequence) { + previousActiveFileIsInputSequence = activeFileIsInputSequence; + if (!activeFileIsInputSequence) { rightPanelActiveTab = 'metadata'; } else { rightPanelActiveTab = 'command'; @@ -1403,9 +1408,6 @@ {:else} {@const isTextOrEmpty = $activeDocumentPath === null || isTextFile(workspaceTreeMap[$activeDocumentPath]?.type)} - {@const isSequenceFile = - $activeDocumentPath === null || - ($activeDocument.type !== null && $activeDocument.type === WorkspaceContentType.Sequence)}
{#if showLoadingSpinner && isTextOrEmpty}
{/if} - {#if isTextOrEmpty && isSequenceFile} + {#if isTextOrEmpty && activeFileIsSequence}
onReadOnlyChange(readOnly)} {preserveAdaptationLog} previewOnly={!hasEditFilePermission} @@ -1536,7 +1539,7 @@ filePath={$activeDocumentPath} fileMetadata={activeFileMetadata} hasEditPermission={hasEditFilePermission} - isSequenceFile={activeFileIsSequence} + isSequenceFile={activeFileIsInputSequence} {phoenixContext} {commandInfoMapper} on:updateUserMetadata={onUpdateUserMetadata} @@ -1551,7 +1554,7 @@ bind:activeTab={rightPanelActiveTab} bind:panelOpen={rightPanelOpen} commandNodeName={rightPanelCommandNodeName} - isSequenceFile={activeFileIsSequence} + isSequenceFile={activeFileIsInputSequence} /> {/if}
From cd8981a5e4b681c0d7e517ec7e002f92fc4bad58 Mon Sep 17 00:00:00 2001 From: duranb Date: Thu, 14 May 2026 15:26:32 -0700 Subject: [PATCH 04/10] add missing declarations for variables --- src/components/sequencing/SequenceEditor.svelte | 1 + src/routes/workspaces/[workspaceId]/+page.svelte | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/components/sequencing/SequenceEditor.svelte b/src/components/sequencing/SequenceEditor.svelte index 771895fe35..bcd7a93a20 100644 --- a/src/components/sequencing/SequenceEditor.svelte +++ b/src/components/sequencing/SequenceEditor.svelte @@ -67,6 +67,7 @@ sequenceOutputUpdate: { filePath: string; output?: string }; }>(); + let commandFormBuilderGrid: string; let compartmentAdaptation: Compartment; let compartmentOutputAdaptation: Compartment; let compartmentReadonly: Compartment; diff --git a/src/routes/workspaces/[workspaceId]/+page.svelte b/src/routes/workspaces/[workspaceId]/+page.svelte index f324c0a285..2060221137 100644 --- a/src/routes/workspaces/[workspaceId]/+page.svelte +++ b/src/routes/workspaces/[workspaceId]/+page.svelte @@ -7,7 +7,12 @@ import { page } from '$app/stores'; import { env } from '$env/dynamic/public'; import type { ChannelDictionary, CommandDictionary, ParameterDictionary } from '@nasa-jpl/aerie-ampcs'; - import type { LibrarySequenceSignature, PhoenixContext, UserSequence } from '@nasa-jpl/aerie-sequence-languages'; + import type { + CommandInfoMapper, + LibrarySequenceSignature, + PhoenixContext, + UserSequence, + } from '@nasa-jpl/aerie-sequence-languages'; import { Button, Checkbox, Resizable, Select } from '@nasa-jpl/stellar-svelte'; import type { EditorView } from 'codemirror'; import { capitalize, startCase } from 'lodash-es'; @@ -92,6 +97,7 @@ WorkspaceNodesEvent, } from '../../../types/workspace'; import type { + WorkspaceFileMetadata, WorkspaceTreeMap, WorkspaceTreeNode, WorkspaceTreeNodeWithFullPath, @@ -138,11 +144,15 @@ const resizableHandleClass = 'w-[3px] hover:after:bg-neutral-300 hover:after:transition-all hover:after:delay-[400ms] data-[active]:after:bg-neutral-300 data-[active]:after:transition-all'; + let activeFileMetadata: WorkspaceFileMetadata | null; + let activeFileIsSequence: boolean = false; + let activeFileIsInputSequence: boolean = false; let availableActionsForActiveFile: ActionParameterPair[] = []; let panelsReady: boolean = false; let allActionsForWorkspace: ActionDefinition[] = []; let channelDictionary: ChannelDictionary | null = null; let commandDictionary: CommandDictionary | null = null; + let commandInfoMapper: CommandInfoMapper | null = null; let consolePaneApi: PaneAPI; let leftPaneApi: PaneAPI; let leftPanelActiveTab: string = @@ -155,6 +165,7 @@ let hasEditWorkspaceCollaboratorsPermission: boolean = false; let hasRunActionPermission: boolean = false; let isConsoleExpanded: boolean = false; + let isFileReadOnly: boolean = false; let parameterDictionaries: ParameterDictionary[] = []; let phoenixContext: PhoenixContext; let isWorkspaceLoading: boolean = false; @@ -170,6 +181,7 @@ let showLoadingSpinner: boolean = false; let librarySequences: LibrarySequenceSignature[] = []; let loadingSpinnerTimeout: ReturnType | null = null; + let logLevelLabel: string = 'Default levels'; let logLevels: LogLevel[] = defaultLogLevels; let preserveAdaptationLog: boolean = false; let workspaceSequences: UserSequence[] = []; From 331a6ec63d15c34e6313639586f03fcbd96ea58c Mon Sep 17 00:00:00 2001 From: duranb Date: Mon, 18 May 2026 16:06:23 -0700 Subject: [PATCH 05/10] Remove unnecessary generic type parameter from OutputToolbar component --- src/components/sequencing/OutputToolbar.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/sequencing/OutputToolbar.svelte b/src/components/sequencing/OutputToolbar.svelte index 4ffc970925..67295bc4a4 100644 --- a/src/components/sequencing/OutputToolbar.svelte +++ b/src/components/sequencing/OutputToolbar.svelte @@ -1,6 +1,6 @@ -