Skip to content

Commit 1b26719

Browse files
committed
feat: implement workflow panel and enhance workspace layout
- Introduced a new WorkflowPanel component to streamline the workflow process with structured steps for user interaction. - Updated App.vue to replace the InvestigationPane with the WorkflowPanel, improving the workspace layout and usability. - Adjusted resizing logic for the workspace columns to enhance responsiveness and user experience. - Enhanced FileLoader component with visual feedback for loading actions and improved styling. - Refactored StructureExplorer to support automatic scrolling to the first matched structure, improving navigation efficiency. - Updated WorkspaceHeader to include an export button, enabling users to export pipeline steps.
1 parent d6d85d7 commit 1b26719

7 files changed

Lines changed: 454 additions & 77 deletions

File tree

src/App.vue

Lines changed: 23 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,54 +5,39 @@ import InputCodeEditor from './components/InputCodeEditor.vue';
55
import ToasterView from './components/ToasterView.vue';
66
import WorkspaceHeader from './components/WorkspaceHeader.vue';
77
import ExportPanel from './components/ExportPanel.vue';
8-
import InvestigationPane from './components/InvestigationPane.vue';
9-
import ContextPanel from './components/ContextPanel.vue';
8+
import WorkflowPanel from './components/WorkflowPanel.vue';
109
1110
const workspaceGrid = ref(null);
12-
const leftWidth = ref(352);
13-
const rightWidth = ref(384);
11+
const leftWidth = ref(0);
1412
1513
const MIN_LEFT_WIDTH = 288;
16-
const MIN_CENTER_WIDTH = 544;
17-
const MIN_RIGHT_WIDTH = 320;
14+
const MIN_RIGHT_WIDTH = 288;
1815
const HANDLE_WIDTH = 10;
19-
const GRID_GAP = 16;
2016
2117
let activeResize = null;
2218
2319
const workspaceGridStyle = computed(() => ({
24-
'--left-column-width': `${leftWidth.value}px`,
25-
'--right-column-width': `${rightWidth.value}px`,
20+
'--left-column-width': leftWidth.value > 0 ? `${leftWidth.value}px` : '1fr',
21+
'--right-column-width': leftWidth.value > 0 ? 'minmax(18rem, 1fr)' : '1fr',
2622
}));
2723
2824
function getAvailableContentWidth() {
2925
const containerWidth = workspaceGrid.value?.clientWidth ?? 0;
30-
return containerWidth - (HANDLE_WIDTH * 2) - (GRID_GAP * 4);
26+
return containerWidth - HANDLE_WIDTH - 8;
3127
}
3228
3329
function clampLeftWidth(width) {
34-
const maxWidth = getAvailableContentWidth() - MIN_CENTER_WIDTH - rightWidth.value;
30+
const maxWidth = getAvailableContentWidth() - MIN_RIGHT_WIDTH;
3531
return Math.min(Math.max(width, MIN_LEFT_WIDTH), Math.max(MIN_LEFT_WIDTH, maxWidth));
3632
}
3733
38-
function clampRightWidth(width) {
39-
const maxWidth = getAvailableContentWidth() - MIN_CENTER_WIDTH - leftWidth.value;
40-
return Math.min(Math.max(width, MIN_RIGHT_WIDTH), Math.max(MIN_RIGHT_WIDTH, maxWidth));
41-
}
42-
4334
function updateResize(event) {
4435
if (!activeResize) {
4536
return;
4637
}
4738
4839
const delta = event.clientX - activeResize.startX;
49-
50-
if (activeResize.side === 'left') {
51-
leftWidth.value = clampLeftWidth(activeResize.startWidth + delta);
52-
return;
53-
}
54-
55-
rightWidth.value = clampRightWidth(activeResize.startWidth - delta);
40+
leftWidth.value = clampLeftWidth(activeResize.startWidth + delta);
5641
}
5742
5843
function stopResize() {
@@ -69,7 +54,7 @@ function startResize(side, event) {
6954
activeResize = {
7055
side,
7156
startX: event.clientX,
72-
startWidth: side === 'left' ? leftWidth.value : rightWidth.value,
57+
startWidth: leftWidth.value,
7358
};
7459
7560
window.addEventListener('pointermove', updateResize);
@@ -89,30 +74,20 @@ onBeforeUnmount(() => {
8974
</section>
9075
9176
<aside class="workspace-column left-column">
92-
<investigation-pane />
77+
<workflow-panel />
9378
</aside>
9479
9580
<div
9681
class="resize-handle resize-left"
97-
title="Drag to make the code editor narrower or wider"
82+
title="Drag to resize the workflow and code panels"
9883
@pointerdown.prevent="startResize('left', $event)"
9984
></div>
10085
101-
<section class="workspace-column center-column">
86+
<section class="workspace-column right-column">
10287
<section class="workspace-panel code-panel">
10388
<input-code-editor />
10489
</section>
10590
</section>
106-
107-
<div
108-
class="resize-handle resize-right"
109-
title="Drag to make the code editor narrower or wider"
110-
@pointerdown.prevent="startResize('right', $event)"
111-
></div>
112-
113-
<aside class="workspace-column right-column">
114-
<context-panel />
115-
</aside>
11691
</div>
11792
</main>
11893
<export-panel v-if="store.exportPanelOpen" />
@@ -132,13 +107,11 @@ onBeforeUnmount(() => {
132107
grid-template-columns:
133108
minmax(18rem, var(--left-column-width))
134109
10px
135-
minmax(34rem, 1fr)
136-
10px
137-
minmax(20rem, var(--right-column-width));
110+
var(--right-column-width);
138111
grid-template-rows: auto minmax(0, 1fr);
139112
grid-template-areas:
140-
'header header header header right'
141-
'left left-handle center right-handle right';
113+
'header header header'
114+
'left left-handle right';
142115
gap: .5rem;
143116
align-items: stretch;
144117
flex: 1;
@@ -156,10 +129,6 @@ onBeforeUnmount(() => {
156129
grid-area: left;
157130
}
158131
159-
.center-column {
160-
grid-area: center;
161-
}
162-
163132
.right-column {
164133
grid-area: right;
165134
}
@@ -195,10 +164,6 @@ onBeforeUnmount(() => {
195164
grid-area: left-handle;
196165
}
197166
198-
.resize-right {
199-
grid-area: right-handle;
200-
}
201-
202167
.workspace-panel {
203168
border: 1px solid var(--panel-border);
204169
border-radius: 14px;
@@ -207,7 +172,7 @@ onBeforeUnmount(() => {
207172
box-shadow: 0 8px 22px rgba(0, 0, 0, 0.14);
208173
}
209174
210-
.center-column {
175+
.right-column {
211176
min-width: 0;
212177
overflow: auto;
213178
padding-right: 0.1rem;
@@ -222,11 +187,10 @@ onBeforeUnmount(() => {
222187
223188
@media (max-width: 1280px) {
224189
.workspace-grid {
225-
grid-template-columns: minmax(18rem, 22rem) minmax(0, 1fr);
190+
grid-template-columns: minmax(18rem, 1fr) minmax(18rem, 1fr);
226191
grid-template-areas:
227192
'header header'
228-
'left center'
229-
'right right';
193+
'left right';
230194
}
231195
232196
.resize-handle {
@@ -242,10 +206,14 @@ onBeforeUnmount(() => {
242206
243207
.workspace-grid {
244208
grid-template-columns: 1fr;
209+
grid-template-areas:
210+
'header'
211+
'left'
212+
'right';
245213
}
246214
247215
.workspace-column,
248-
.center-column {
216+
.right-column {
249217
overflow: visible;
250218
}
251219
}

src/components/FileLoader.vue

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ const inputCodeEditorId = store.editorIds.inputCodeEditor;
1111
const fileInput = ref(null);
1212
const isOpen = ref(false);
1313
const showSamples = ref(false);
14+
const shouldHighlightLoad = computed(() =>
15+
store.currentScriptKind === 'custom' &&
16+
!store.isCurrentScriptModified,
17+
);
1418
const canClearEditor = computed(() => !!(
1519
store.getCurrentScriptContent().length ||
1620
store.isCurrentInputParsed() ||
@@ -101,6 +105,7 @@ function loadSample(sampleId) {
101105
<div class="file-loader">
102106
<button
103107
class="toolbar-btn icon-btn"
108+
:class="{highlighted: shouldHighlightLoad}"
104109
type="button"
105110
title="Open script actions for loading, sampling, or clearing the current script"
106111
aria-label="Open script actions"
@@ -158,6 +163,7 @@ function loadSample(sampleId) {
158163
<style scoped>
159164
.file-loader {
160165
position: relative;
166+
isolation: isolate;
161167
}
162168
163169
.toolbar-btn {
@@ -170,11 +176,42 @@ function loadSample(sampleId) {
170176
cursor: pointer;
171177
}
172178
179+
.toolbar-btn.highlighted {
180+
border-color: rgba(0, 204, 255, 0.95);
181+
background: rgba(0, 204, 255, 0.22);
182+
box-shadow: 0 0 0 0 rgba(0, 204, 255, 0.7);
183+
animation: pulse-glow 2s infinite;
184+
position: relative;
185+
z-index: 1;
186+
}
187+
188+
.toolbar-btn.highlighted:hover,
189+
.toolbar-btn.highlighted:focus-visible {
190+
border-color: rgba(0, 204, 255, 1);
191+
background: rgba(0, 204, 255, 0.28);
192+
outline: none;
193+
}
194+
173195
.toolbar-icon {
174196
width: 1.2rem;
175197
height: 1.2rem;
176198
}
177199
200+
@keyframes pulse-glow {
201+
0%,
202+
100% {
203+
box-shadow:
204+
0 0 0 0 rgba(0, 204, 255, 0.7),
205+
0 0 18px rgba(0, 204, 255, 0.32);
206+
}
207+
208+
70% {
209+
box-shadow:
210+
0 0 0 15px rgba(0, 204, 255, 0),
211+
0 0 24px rgba(0, 204, 255, 0.14);
212+
}
213+
}
214+
178215
.load-menu {
179216
position: absolute;
180217
top: calc(100% + 0.45rem);

src/components/StructureExplorer.vue

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup>
2-
import {computed, onBeforeUnmount, onMounted, reactive, ref, watch} from 'vue';
2+
import {computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch} from 'vue';
33
import store from '../store';
44
import IconSearch from './icons/IconSearch.vue';
55
import IconTrash from './icons/IconTrash.vue';
@@ -24,6 +24,7 @@ const currentPage = ref(0);
2424
const exampleStructureId = ref('');
2525
const showMatchesOnly = ref(false);
2626
const showDefineStructure = ref(false);
27+
const structureList = ref(null);
2728
2829
const categories = computed(() => [...new Set(
2930
store.availableKnownStructures.map((structure) => structure.category),
@@ -74,6 +75,8 @@ const activePreview = computed(() => store.getKnownStructureTransformPreview(act
7475
const exampleStructure = computed(() => store.getKnownStructureById(exampleStructureId.value));
7576
const canFindMatches = computed(() => store.hasPendingKnownStructureScan());
7677
const canClearResults = computed(() => store.hasKnownStructureResultsToClear());
78+
const firstMatchedStructureId = computed(() =>
79+
visibleStructures.value.find((structure) => hasStructureMatches(structure))?.id ?? null);
7780
7881
function toggleSelection(structureId) {
7982
const nextIds = store.selectedKnownStructureIds.includes(structureId)
@@ -204,6 +207,46 @@ function prevPage() {
204207
currentPage.value = currentPage.value <= 0 ? totalPages.value - 1 : currentPage.value - 1;
205208
}
206209
210+
async function scrollFirstMatchedStructureIntoView() {
211+
const targetStructureId = firstMatchedStructureId.value;
212+
213+
if (!targetStructureId) {
214+
return;
215+
}
216+
217+
const targetIndex = visibleStructures.value.findIndex((structure) => structure.id === targetStructureId);
218+
219+
if (targetIndex === -1) {
220+
return;
221+
}
222+
223+
const targetPage = Math.floor(targetIndex / PAGE_SIZE);
224+
225+
if (currentPage.value !== targetPage) {
226+
currentPage.value = targetPage;
227+
}
228+
229+
await nextTick();
230+
231+
const container = structureList.value;
232+
const targetCard = container?.querySelector(`[data-structure-id="${targetStructureId}"]`);
233+
234+
if (!container || !targetCard) {
235+
return;
236+
}
237+
238+
const maxScrollTop = Math.max(0, container.scrollHeight - container.clientHeight);
239+
const targetScrollTop = Math.min(
240+
Math.max(0, targetCard.offsetTop - container.offsetTop),
241+
maxScrollTop,
242+
);
243+
244+
container.scrollTo({
245+
top: targetScrollTop,
246+
behavior: 'smooth',
247+
});
248+
}
249+
207250
watch(
208251
[
209252
() => filters.search,
@@ -224,6 +267,17 @@ watch(totalPages, (nextTotalPages) => {
224267
}
225268
});
226269
270+
watch(
271+
() => store.knownStructureExecutionStatus.lastRunAt,
272+
async (lastRunAt, previousLastRunAt) => {
273+
if (!lastRunAt || lastRunAt === previousLastRunAt) {
274+
return;
275+
}
276+
277+
await scrollFirstMatchedStructureIntoView();
278+
},
279+
);
280+
227281
function handleWindowKeydown(event) {
228282
if (event.key === 'Escape' && exampleStructure.value) {
229283
closeExample();
@@ -336,10 +390,11 @@ onBeforeUnmount(() => {
336390
@complete="handleStructureCreated"
337391
/>
338392
339-
<div v-else class="structure-list">
393+
<div v-else ref="structureList" class="structure-list">
340394
<article
341395
v-for="structure in pagedStructures"
342396
:key="structure.id"
397+
:data-structure-id="structure.id"
343398
class="structure-card"
344399
:class="{
345400
active: structure.id === store.activeKnownStructureId,

0 commit comments

Comments
 (0)