Skip to content

Commit 0fa0233

Browse files
authored
Merge pull request #2381 from OpenC3/cmd_editor
Edit calendar activities via CommandEditor
2 parents 7387f0b + 0c6ca31 commit 0fa0233

File tree

4 files changed

+276
-10
lines changed

4 files changed

+276
-10
lines changed

openc3-cosmos-init/plugins/packages/openc3-vue-common/src/components/CommandEditor.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ export default {
313313
getCmdString() {
314314
let cmd = `${this.targetName} ${this.commandName}`
315315
if (this.computedRows.length === 0) return cmd
316-
cmd += ' with '
316+
cmd += ' with'
317317
for (const row of this.computedRows) {
318318
// null value indicates required parameter not set, see updateCmdParams
319319
if (row.val === null) {

openc3-cosmos-init/plugins/packages/openc3-vue-common/src/tools/calendar/ActivityCreateDialog.vue

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
</v-btn>
5454
</v-row>
5555
</template>
56-
56+
5757
<template v-if="dialogStep === 2" #actions>
5858
<v-row class="ma-0 px-6 pb-4">
5959
<v-btn variant="text" @click="() => (dialogStep -= 1)">
@@ -235,15 +235,14 @@
235235
>
236236
</v-select>
237237
<div v-if="kind === 'COMMAND'">
238-
<v-text-field
238+
<v-textarea
239239
v-model="activityData"
240-
type="text"
241240
label="Command Input"
242-
placeholder="INST COLLECT with TYPE 0, DURATION 1, OPCODE 171, TEMP 0"
243-
prefix="cmd('"
244-
suffix="')"
245-
hint="Timeline runs commands with cmd_no_hazardous_check"
241+
rows="1"
242+
readonly
243+
auto-grow
246244
data-test="activity-cmd"
245+
@click:control="editItem()"
247246
/>
248247
</div>
249248
<div v-else-if="kind === 'SCRIPT'" class="ma-3">
@@ -262,19 +261,67 @@
262261
</v-stepper>
263262
</v-card>
264263
</v-dialog>
264+
265+
<!-- Command Editor Dialog -->
266+
<v-dialog
267+
v-model="showCommandDialog"
268+
max-width="1200"
269+
persistent
270+
scrollable
271+
>
272+
<v-card>
273+
<v-card-title class="d-flex align-center">
274+
<span>{{ dialogTitle }}</span>
275+
<v-spacer />
276+
<v-btn icon="mdi-close" variant="text" @click="closeCommandDialog" />
277+
</v-card-title>
278+
<v-card-text class="pa-0">
279+
<div v-if="dialogError" class="error-message">
280+
<v-icon class="mr-2" color="error">mdi-alert-circle</v-icon>
281+
<span class="flex-grow-1">{{ dialogError }}</span>
282+
<v-btn
283+
icon="mdi-close"
284+
size="small"
285+
variant="text"
286+
color="error"
287+
class="ml-2"
288+
@click="clearDialogError"
289+
/>
290+
</div>
291+
<command-editor
292+
ref="commandEditor"
293+
:send-disabled="false"
294+
:states-in-hex="statesInHex"
295+
:show-ignored-params="showIgnoredParams"
296+
:cmd-raw="cmdRaw"
297+
:cmd-string="dialogCmdString"
298+
:show-command-button="false"
299+
@build-cmd="updateCommand($event)"
300+
/>
301+
</v-card-text>
302+
<v-card-actions>
303+
<v-spacer />
304+
<v-btn variant="outlined" @click="closeCommandDialog"> Cancel </v-btn>
305+
<v-btn color="primary" variant="flat" @click="updateCommand()">
306+
Update Command
307+
</v-btn>
308+
</v-card-actions>
309+
</v-card>
310+
</v-dialog>
265311
</div>
266312
</template>
267313

268314
<script>
269315
import { Api } from '@openc3/js-common/services'
270-
import { EnvironmentChooser, ScriptChooser } from '@/components'
316+
import { EnvironmentChooser, ScriptChooser, CommandEditor } from '@/components'
271317
import { TimeFilters } from '@/util'
272318
import CreateDialog from './CreateDialog'
273319
274320
export default {
275321
components: {
276322
EnvironmentChooser,
277323
ScriptChooser,
324+
CommandEditor,
278325
},
279326
mixins: [CreateDialog, TimeFilters],
280327
props: {
@@ -307,6 +354,10 @@ export default {
307354
frequency: 90,
308355
timeSpan: 'minutes',
309356
timeSpans: ['minutes', 'hours', 'days'],
357+
showCommandDialog: false,
358+
dialogTitle: 'Add Command',
359+
dialogError: '',
360+
dialogCmdString: null,
310361
}
311362
},
312363
computed: {
@@ -431,7 +482,7 @@ export default {
431482
let data = {
432483
environment: this.activityEnvironment,
433484
customTitle: this.customTitle,
434-
notes: this.notes
485+
notes: this.notes,
435486
}
436487
data[kind] = this.activityData
437488
let recurring = {}
@@ -485,6 +536,31 @@ export default {
485536
// We don't do the $emit or set show here because it has to be in the callback
486537
this.clearHandler()
487538
},
539+
editItem: function () {
540+
// Set up dialog for editing
541+
this.dialogTitle = 'Edit Command'
542+
this.dialogCmdString = this.activityData
543+
this.showCommandDialog = true
544+
},
545+
closeCommandDialog: function () {
546+
this.showCommandDialog = false
547+
this.dialogCmdString = null
548+
this.dialogError = ''
549+
},
550+
clearDialogError: function () {
551+
this.dialogError = ''
552+
},
553+
updateCommand: function () {
554+
let commandString = ''
555+
try {
556+
commandString = this.$refs.commandEditor.getCmdString()
557+
} catch (error) {
558+
this.dialogError = error.message || 'Please fix command parameters'
559+
return
560+
}
561+
this.activityData = commandString
562+
this.showCommandDialog = false
563+
},
488564
},
489565
}
490566
</script>

openc3-cosmos-init/plugins/packages/openc3-vue-common/src/tools/scriptrunner/ScriptRunner.vue

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,11 @@
278278
></pre>
279279
<v-menu v-model="executeSelectionMenu" :target="[menuX, menuY]">
280280
<v-list>
281+
<v-list-item
282+
:title="currentLineHasCommand ? 'Edit Command' : 'Insert Command'"
283+
@click="openCommandEditor"
284+
/>
285+
<v-divider />
281286
<v-list-item title="Execute Selection" @click="executeSelection" />
282287
<v-list-item
283288
v-if="scriptId"
@@ -520,6 +525,46 @@
520525
:persistent="true"
521526
@status="promptDialogCallback"
522527
/>
528+
<!-- Command Editor Dialog -->
529+
<v-dialog v-model="commandEditor.show" max-width="1200" persistent scrollable>
530+
<v-card>
531+
<v-card-title class="d-flex align-center">
532+
<span>Insert Command</span>
533+
<v-spacer />
534+
<v-btn icon="mdi-close" variant="text" @click="closeCommandDialog" />
535+
</v-card-title>
536+
<v-card-text class="pa-0">
537+
<div v-if="commandEditor.dialogError" class="error-message">
538+
<v-icon class="mr-2" color="error">mdi-alert-circle</v-icon>
539+
<span class="flex-grow-1">{{ commandEditor.dialogError }}</span>
540+
<v-btn
541+
icon="mdi-close"
542+
size="small"
543+
variant="text"
544+
color="error"
545+
@click="commandEditor.dialogError = null"
546+
class="ml-2"
547+
/>
548+
</div>
549+
<command-editor
550+
ref="commandEditor"
551+
:initial-target-name="commandEditor.targetName"
552+
:initial-packet-name="commandEditor.packetName"
553+
:cmd-string="commandEditor.cmdString"
554+
:send-disabled="false"
555+
:show-command-button="false"
556+
@build-cmd="insertCommand($event)"
557+
/>
558+
</v-card-text>
559+
<v-card-actions>
560+
<v-spacer />
561+
<v-btn variant="outlined" @click="closeCommandDialog"> Cancel </v-btn>
562+
<v-btn color="primary" variant="flat" @click="insertCommand()">
563+
Insert Command
564+
</v-btn>
565+
</v-card-actions>
566+
</v-card>
567+
</v-dialog>
523568
<v-bottom-sheet v-model="showScripts">
524569
<v-sheet class="pb-11 pt-5 px-5">
525570
<running-scripts
@@ -564,6 +609,7 @@ import OverridesDialog from '@/tools/scriptrunner/Dialogs/OverridesDialog.vue'
564609
import PromptDialog from '@/tools/scriptrunner/Dialogs/PromptDialog.vue'
565610
import ResultsDialog from '@/tools/scriptrunner/Dialogs/ResultsDialog.vue'
566611
import ScriptEnvironmentDialog from '@/tools/scriptrunner/Dialogs/ScriptEnvironmentDialog.vue'
612+
import CommandEditor from '@/components/CommandEditor.vue'
567613
import SuiteRunner from '@/tools/scriptrunner/SuiteRunner.vue'
568614
import ScriptLogMessages from '@/tools/scriptrunner/ScriptLogMessages.vue'
569615
import {
@@ -603,6 +649,7 @@ export default {
603649
RunningScripts,
604650
ScriptLogMessages,
605651
CriticalCmdDialog,
652+
CommandEditor,
606653
},
607654
mixins: [AceEditorModes, ClassificationBanners],
608655
beforeRouteUpdate: function (to, from, next) {
@@ -740,6 +787,16 @@ export default {
740787
mnemonicChecker: new MnemonicChecker(),
741788
showScripts: false,
742789
showOverrides: false,
790+
commandEditor: {
791+
show: false,
792+
targetName: null,
793+
commandName: null,
794+
dialogError: null,
795+
cmdString: null,
796+
isEditing: false,
797+
editLine: null,
798+
},
799+
currentLineHasCommand: false,
743800
activePromptId: '',
744801
api: null,
745802
timeZone: 'local',
@@ -1249,6 +1306,72 @@ export default {
12491306
toggleVimMode() {
12501307
AceEditorUtils.toggleVimMode(this.editor)
12511308
},
1309+
openCommandEditor() {
1310+
this.executeSelectionMenu = false
1311+
const position = this.editor.getCursorPosition()
1312+
const line = this.editor.session.getLine(position.row)
1313+
1314+
if (this.currentLineHasCommand) {
1315+
// Extract and parse the command from the line
1316+
const cmdString = this.parseCommandFromLine(line)
1317+
this.commandEditor.cmdString = cmdString
1318+
this.commandEditor.isEditing = true
1319+
this.commandEditor.editLine = position.row
1320+
} else {
1321+
// Inserting a new command
1322+
this.commandEditor.cmdString = null
1323+
this.commandEditor.isEditing = false
1324+
this.commandEditor.editLine = null
1325+
}
1326+
this.commandEditor.show = true
1327+
this.commandEditor.dialogError = null
1328+
},
1329+
insertCommand(event) {
1330+
let commandString = ''
1331+
try {
1332+
commandString = this.$refs.commandEditor.getCmdString()
1333+
let parts = commandString.split(' ')
1334+
this.commandEditor.targetName = parts[0]
1335+
this.commandEditor.commandName = parts[1]
1336+
} catch (error) {
1337+
this.commandEditor.dialogError =
1338+
error.message || 'Please fix command parameters'
1339+
return
1340+
}
1341+
1342+
if (
1343+
this.commandEditor.isEditing &&
1344+
this.commandEditor.editLine !== null
1345+
) {
1346+
// Replace the existing line
1347+
const line = this.editor.session.getLine(this.commandEditor.editLine)
1348+
const indent = line.match(/^\s*/)[0] // Preserve indentation
1349+
// Extract trailing comment if present
1350+
const commentMatch = line.match(/\s+#.*$/)
1351+
const trailingComment = commentMatch ? commentMatch[0] : ''
1352+
const newLine = `${indent}cmd("${commandString}")${trailingComment}`
1353+
const Range = this.Range
1354+
this.editor.session.replace(
1355+
new Range(
1356+
this.commandEditor.editLine,
1357+
0,
1358+
this.commandEditor.editLine,
1359+
line.length,
1360+
),
1361+
newLine,
1362+
)
1363+
} else {
1364+
// Insert a new command at the cursor position
1365+
const position = this.editor.getCursorPosition()
1366+
this.editor.session.insert(position, `cmd("${commandString}")\n`)
1367+
}
1368+
1369+
this.fileModified = true
1370+
this.commandEditor.show = false
1371+
},
1372+
closeCommandDialog: function () {
1373+
this.commandEditor.show = false
1374+
},
12521375
doResize() {
12531376
this.editor.resize()
12541377
// nextTick allows the resize to work correctly
@@ -1383,8 +1506,26 @@ export default {
13831506
showExecuteSelectionMenu: function ($event) {
13841507
this.menuX = $event.pageX
13851508
this.menuY = $event.pageY
1509+
// Check if the current line contains a command
1510+
const position = this.editor.getCursorPosition()
1511+
const line = this.editor.session.getLine(position.row)
1512+
this.currentLineHasCommand = this.isCommandLine(line)
13861513
this.executeSelectionMenu = true
13871514
},
1515+
isCommandLine: function (line) {
1516+
// Check if line contains cmd() or cmd_no_hazardous_check() or similar command patterns
1517+
const trimmedLine = line.trim()
1518+
// Match patterns like: cmd("...", cmd_no_hazardous_check("...", cmd_raw("...", etc.
1519+
return /^\s*cmd(_\w+)?\s*\(/.test(trimmedLine)
1520+
},
1521+
parseCommandFromLine: function (line) {
1522+
// Extract the command string from patterns like: cmd("TARGET COMMAND with PARAM value")
1523+
const match = line.match(/cmd(_\w+)?\s*\(\s*["'](.+?)["']\s*\)/)
1524+
if (match) {
1525+
return match[2] // Return the command string
1526+
}
1527+
return null
1528+
},
13881529
runFromCursor: function () {
13891530
const start_row = this.editor.getCursorPosition().row + 1
13901531
if (!this.scriptId) {
@@ -2739,6 +2880,25 @@ class TestSuite(Suite):
27392880
</script>
27402881
27412882
<style scoped>
2883+
hr {
2884+
color: white;
2885+
height: 3px;
2886+
}
2887+
2888+
.error-message {
2889+
border: 2px solid #f44336;
2890+
border-radius: 8px;
2891+
background-color: rgba(244, 67, 54, 0.1);
2892+
color: #d32f2f;
2893+
padding-left: 8px;
2894+
padding-right: 8px;
2895+
margin: 16px;
2896+
display: flex;
2897+
align-items: center;
2898+
font-weight: 500;
2899+
box-shadow: 0 2px 4px rgba(244, 67, 54, 0.2);
2900+
}
2901+
27422902
#sr-controls {
27432903
padding: 0px;
27442904
}

0 commit comments

Comments
 (0)