Skip to content

Commit 816e27f

Browse files
committed
WIP launch view
1 parent d4f96d8 commit 816e27f

File tree

3 files changed

+84
-26
lines changed

3 files changed

+84
-26
lines changed

src/components/launch/DependencyGraph.vue

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ const props = defineProps<{ model: NodeLinkGraph }>()
1515
1616
export interface GraphNodeDatum extends SimulationNodeDatum {
1717
id: string
18-
name?: string
1918
group: string
20-
focus?: boolean
21-
condition: string
19+
name?: string
20+
dark?: boolean
21+
condition?: string
22+
level?: number
2223
}
2324
2425
export interface GraphLinkDatum extends SimulationLinkDatum<GraphNodeDatum> {
@@ -59,9 +60,9 @@ function onSelectNode(node: GraphNodeDatum | null): void {
5960
}
6061
6162
function groupColors(group: string): string {
62-
if (group === LaunchActionType.ARG) return 'forestgreen'
63+
if (group === LaunchActionType.ARG) return 'limegreen'
6364
if (group === LaunchActionType.NODE) return 'dodgerblue'
64-
if (group === LaunchActionType.INCLUDE) return 'orange'
65+
if (group === LaunchActionType.INCLUDE) return 'chocolate'
6566
return 'gray'
6667
}
6768
@@ -101,7 +102,26 @@ function buildModelElement(): SVGElement {
101102
svgHeight.value, // height
102103
])
103104
104-
// Add a line for each link, and a circle for each node.
105+
// Add arrowheads for each link
106+
svg
107+
.append('defs')
108+
.selectAll('marker')
109+
.data(['link-arrowhead'])
110+
.enter()
111+
.append('marker')
112+
.attr('id', 'link-arrowhead')
113+
.attr('viewBox', '0 -3 6 6')
114+
.attr('refX', 22) // about half of the link distance
115+
.attr('refY', 0)
116+
.attr('markerWidth', 6)
117+
.attr('markerHeight', 6)
118+
.attr('orient', 'auto')
119+
.append('path')
120+
.attr('d', 'M0,-3L6,0L0,3')
121+
.style('stroke', '#999')
122+
.style('fill', '#999')
123+
124+
// Add a line for each link
105125
const link = svg
106126
.append('g')
107127
.attr('stroke', '#999')
@@ -110,15 +130,17 @@ function buildModelElement(): SVGElement {
110130
.data(links)
111131
.join('line')
112132
.attr('stroke-width', (d) => Math.sqrt(d.value))
133+
.style('marker-end', 'url(#link-arrowhead)')
113134
135+
// Add a circle for each node
114136
const node = svg
115137
.append('g')
116-
.attr('stroke', '#fff')
117138
.attr('stroke-width', 1.5)
118139
.selectAll<SVGCircleElement, GraphNodeDatum>('circle')
119140
.data(nodes)
120141
.join('circle')
121-
.attr('r', (d) => (d.focus ? 20 : 10))
142+
.attr('r', (d) => (d.level || 0) * 5 + 10)
143+
.attr('stroke', (d) => (d.dark ? '#999' : '#fff'))
122144
.attr('stroke-dasharray', (d) => (!d.condition ? '' : '2 1'))
123145
.attr('fill', (d) => color(d.group))
124146

src/components/launch/LaunchActionList.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ import type { LaunchAction, LaunchActionId } from '@/types/launch'
1010
1111
defineProps<{
1212
actions: LaunchAction[]
13-
selectedAction: LaunchActionId
13+
selectedAction?: LaunchActionId
1414
currentDependencies: Set<LaunchActionId>
1515
}>()
1616
1717
const emit = defineEmits<{
18-
(e: 'select', id: LaunchActionId): void
18+
(e: 'select', action: LaunchAction): void
1919
}>()
2020
2121
// Event Handlers --------------------------------------------------------------
2222
2323
function onSelectAction(action: LaunchAction): void {
24-
emit('select', action.id)
24+
emit('select', action)
2525
}
2626
</script>
2727

@@ -31,7 +31,7 @@ function onSelectAction(action: LaunchAction): void {
3131
v-for="action in actions"
3232
:key="action.id"
3333
:class="{
34-
selected: action.id === selectedAction,
34+
selected: selectedAction && action.id === selectedAction,
3535
dependency: currentDependencies.has(action.id),
3636
}"
3737
@click="onSelectAction(action)"

src/views/LaunchView.vue

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
<!-- Copyright © 2025 André Santos -->
33

44
<script setup lang="ts">
5+
// Arc diagram might be a better view for this type of information
6+
// https://observablehq.com/@d3/arc-diagram
7+
58
// Imports ---------------------------------------------------------------------
69
710
import { computed, ref, watch } from 'vue'
@@ -27,7 +30,7 @@ const loading = ref<boolean>(false)
2730
const launchFile = ref<LaunchFile | null>(null)
2831
const error = ref<string | null>(null)
2932
30-
const selectedAction = ref<LaunchActionId>('')
33+
const selectedAction = ref<LaunchAction | null>(null)
3134
const currentDependencies = computed<Set<LaunchActionId>>(getDependencies)
3235
const dependencyGraph = ref<NodeLinkGraph>(getDependencyGraph())
3336
@@ -44,7 +47,7 @@ async function fetchData(id: LaunchId | LaunchId[]): Promise<void> {
4447
id = id[0]
4548
}
4649
error.value = launchFile.value = null
47-
selectedAction.value = ''
50+
selectedAction.value = null
4851
loading.value = true
4952
5053
try {
@@ -59,11 +62,11 @@ async function fetchData(id: LaunchId | LaunchId[]): Promise<void> {
5962
}
6063
}
6164
62-
function onSelectAction(id: LaunchActionId): void {
63-
if (selectedAction.value === id) {
64-
selectedAction.value = ''
65+
function onSelectAction(action: LaunchAction): void {
66+
if (selectedAction.value != null && selectedAction.value.id === action.id) {
67+
selectedAction.value = null
6568
} else {
66-
selectedAction.value = id
69+
selectedAction.value = action
6770
}
6871
dependencyGraph.value = getDependencyGraph()
6972
}
@@ -74,17 +77,18 @@ function getDependencyGraph(): NodeLinkGraph {
7477
const nodes: GraphNodeDatum[] = []
7578
const links: GraphLinkDatum[] = []
7679
if (launchFile.value != null) {
77-
if (selectedAction.value !== '') {
80+
if (selectedAction.value != null) {
7881
const actions: Record<LaunchActionId, LaunchAction> = {}
7982
for (const action of launchFile.value.actions) {
8083
actions[action.id] = action
8184
}
82-
let action = actions[selectedAction.value]
85+
let action = selectedAction.value
8386
nodes.push({
8487
id: action.id,
8588
name: action.name,
8689
group: action.type,
87-
focus: true,
90+
level: 2,
91+
dark: action.dependencies.length > 0,
8892
condition: '',
8993
})
9094
for (const target of action.dependencies) {
@@ -94,15 +98,27 @@ function getDependencyGraph(): NodeLinkGraph {
9498
const deps: LaunchActionId[] = action.dependencies.slice()
9599
while (deps.length > 0) {
96100
action = actions[deps.pop()!]
97-
nodes.push({ id: action.id, name: action.name, group: action.type, condition: '' })
101+
nodes.push({
102+
id: action.id,
103+
name: action.name,
104+
group: action.type,
105+
dark: action.dependencies.length > 0,
106+
condition: '',
107+
})
98108
for (const target of action.dependencies) {
99109
links.push({ source: action.id, target, value: 2 })
100110
deps.push(target)
101111
}
102112
}
103113
} else {
104114
for (const action of launchFile.value.actions) {
105-
nodes.push({ id: action.id, name: action.name, group: action.type, condition: '' })
115+
nodes.push({
116+
id: action.id,
117+
name: action.name,
118+
group: action.type,
119+
dark: action.dependencies.length > 0,
120+
condition: '',
121+
})
106122
for (const target of action.dependencies) {
107123
links.push({ source: action.id, target, value: 2 })
108124
}
@@ -113,8 +129,8 @@ function getDependencyGraph(): NodeLinkGraph {
113129
}
114130
115131
function getDependencies(): Set<LaunchActionId> {
116-
const id = selectedAction.value
117-
if (id !== '' && launchFile.value != null) {
132+
if (launchFile.value != null && selectedAction.value != null) {
133+
const id = selectedAction.value.id
118134
for (const action of launchFile.value.actions) {
119135
if (action.id === id) {
120136
return new Set(action.dependencies)
@@ -141,11 +157,25 @@ function getDependencies(): Set<LaunchActionId> {
141157
<LaunchActionList
142158
v-if="launchFile.actions.length > 0"
143159
:actions="launchFile.actions"
144-
:selected-action="selectedAction"
160+
:selected-action="selectedAction?.id"
145161
:current-dependencies="currentDependencies"
146162
@select="onSelectAction"
147163
/>
148164
<p v-else>There are no launch actions.</p>
165+
166+
<div v-if="selectedAction != null" class="panel details">
167+
<h4>
168+
<code class="tag">{{ selectedAction.type }}</code>
169+
{{ selectedAction.name }}
170+
</h4>
171+
<p v-if="selectedAction.dependencies.length > 0">
172+
Depends on:
173+
<template v-for="(dep, i) in selectedAction.dependencies" :key="dep">
174+
<template v-if="i > 0">, </template>
175+
<code>{{ dep }}</code>
176+
</template>
177+
</p>
178+
</div>
149179
</div>
150180

151181
<div class="right-side">
@@ -178,6 +208,12 @@ function getDependencies(): Set<LaunchActionId> {
178208
vertical-align: middle;
179209
}
180210
211+
.launch-view > .left-side > .details {
212+
flex: 0 0 auto;
213+
padding: 0 0.5rem;
214+
max-height: 4rem;
215+
}
216+
181217
.launch-view > .right-side {
182218
height: 100%;
183219
flex: 2 0 0;

0 commit comments

Comments
 (0)