Skip to content

Commit c1837ec

Browse files
bybashmadeindjs
andauthored
feat: UX for double click on nodes [AB-879] (#1245)
* feat: UX for double click on nodes [AB-879] * feat: change selection code [AB-879] * feat: tracking dbl clck and button [AB-879] * feat: review changes [AB-879] * feat: review changes * Apply suggestion from @madeindjs Co-authored-by: Alexandre Rousseau <alexandre@rsseau.fr> --------- Co-authored-by: Alexandre Rousseau <alexandre@rsseau.fr>
1 parent 7a9e941 commit c1837ec

File tree

10 files changed

+210
-26
lines changed

10 files changed

+210
-26
lines changed

src/ui/src/builder/BuilderApp.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ const notes = computed(() =>
270270
async function handleKeydown(ev: KeyboardEvent) {
271271
if (ev.key === "Escape") {
272272
ssbm.setSelection(null);
273+
ssbm.expandedEditorForComponent.value = null;
273274
return;
274275
}
275276

src/ui/src/builder/BuilderEmbeddedCodeEditor.vue

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,19 @@ onMounted(() => {
115115
emit("update:modelValue", newCode);
116116
});
117117
resizeObserver.observe(rootEl.value);
118+
119+
// when in modal, focus the editor and set the cursor to the last line
120+
if (props.variant === "half-screen") {
121+
editor.focus();
122+
editor.setPosition({
123+
lineNumber: editor.getModel().getLineCount(),
124+
column: editor
125+
.getModel()
126+
.getLineLastNonWhitespaceColumn(
127+
editor.getModel().getLineCount(),
128+
),
129+
});
130+
}
118131
});
119132
120133
function updateDimensions() {

src/ui/src/builder/builderManager.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -414,18 +414,18 @@ export function generateBuilderManager() {
414414

415415
const activeBlueprintRunId = computed(() => {
416416
const logEntries = getLogEntries();
417-
const runId =
418-
logEntries.find((entry) => {
419-
return !!entry.blueprintExecution;
420-
})?.blueprintExecution?.runId;
417+
const runId = logEntries.find((entry) => {
418+
return !!entry.blueprintExecution;
419+
})?.blueprintExecution?.runId;
421420
if (!runId) return null;
422421
const isActive = Boolean(
423-
logEntries
424-
.filter((entry) => {
425-
return entry?.blueprintExecution?.runId === runId
426-
&& !entry?.blueprintExecution?.exit;
427-
})?.[0]
428-
)
422+
logEntries.filter((entry) => {
423+
return (
424+
entry?.blueprintExecution?.runId === runId &&
425+
!entry?.blueprintExecution?.exit
426+
);
427+
})?.[0],
428+
);
429429
return isActive ? runId : null;
430430
});
431431

@@ -435,6 +435,7 @@ export function generateBuilderManager() {
435435
mode,
436436
activeRootId,
437437
openPanels: ref(new Set<"code" | "log">()),
438+
expandedEditorForComponent: ref<string | null>(null),
438439
isSettingsBarCollapsed: ref(false),
439440
isComponentIdSelected,
440441
selectionStatus,

src/ui/src/builder/settings/BuilderSettingsProperties.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
:unit="fieldValue.type"
9191
:error="errorsByFields[fieldKey]"
9292
:is-expansible="isExpansible(fieldValue)"
93+
:should-expand="isExpansible(fieldValue) && isFieldExpanded"
9394
@expand="handleExpand(fieldKey)"
9495
@shrink="handleShrink(fieldKey)"
9596
>
@@ -288,6 +289,12 @@ const secretsManager = inject(injectionKeys.secretsManager);
288289
289290
const expandedFields = ref(new Set());
290291
292+
const isFieldExpanded = computed(() => {
293+
return (
294+
ssbm.expandedEditorForComponent.value === selectedComponent.value?.id
295+
);
296+
});
297+
291298
const selectedInstancePath = computed<InstancePath>(() =>
292299
parseInstancePathString(ssbm.firstSelectedItem?.value?.instancePath),
293300
);
@@ -392,10 +399,12 @@ function sortByOrder(fieldEntries: FieldEntry[]): FieldEntry[] {
392399
}
393400
394401
function handleExpand(fieldKey: string) {
402+
ssbm.expandedEditorForComponent.value = selectedComponent.value?.id ?? null;
395403
expandedFields.value.add(fieldKey);
396404
}
397405
398406
function handleShrink(fieldKey: string) {
407+
ssbm.expandedEditorForComponent.value = null;
399408
expandedFields.value.delete(fieldKey);
400409
}
401410
</script>

src/ui/src/builder/sidebar/BuilderSidebarComponentTree.vue

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,16 +155,41 @@ async function addPage() {
155155
}
156156
157157
async function addBlueprint() {
158+
const key = getNewBlueprintKey();
159+
158160
const pageId = createAndInsertComponent(
159161
"blueprints_blueprint",
160162
"blueprints_root",
163+
undefined,
164+
key
165+
? {
166+
content: {
167+
key,
168+
},
169+
}
170+
: undefined,
161171
);
162-
wf.setActivePageId(pageId);
172+
163173
await nextTick();
174+
wf.setActivePageId(pageId);
164175
wfbm.setSelection(pageId);
165176
tracking.track("blueprints_new_added");
166177
}
167178
179+
function getNewBlueprintKey() {
180+
const existingKeys = allBlueprints.value
181+
.map((bp) => bp.content?.key)
182+
.filter((key) => key?.startsWith("BLUEPRINT_"))
183+
.map((key) => Number.parseInt(key.replace("BLUEPRINT_", ""), 10))
184+
.filter((num) => !Number.isNaN(num));
185+
186+
const maxIndex =
187+
existingKeys.length > 0
188+
? Math.max(...existingKeys)
189+
: allBlueprints.value.length + 10;
190+
return `BLUEPRINT_${maxIndex + 1}`;
191+
}
192+
168193
async function addSharedBlueprint() {
169194
const pageId = createAndInsertComponent(
170195
"blueprints_blueprint",

src/ui/src/components/blueprints/BlueprintsRoot.vue

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
<template>
22
<div class="BlueprintsRoot" data-writer-container>
3+
<div
4+
v-if="displayedBlueprintId || displayedBlueprintKey"
5+
class="BlueprintsRoot_titleContainer"
6+
>
7+
<span class="BlueprintsRoot_title_prefix">Blueprint:</span>
8+
<div>
9+
{{
10+
displayedBlueprintKey
11+
? displayedBlueprintKey
12+
: displayedBlueprintId
13+
}}
14+
</div>
15+
<div v-if="selectedItemKey">> {{ selectedItemKey }}</div>
16+
</div>
317
<template v-for="vnode in getChildrenVNodes()" :key="vnode.key">
418
<component
519
:is="vnode"
@@ -28,8 +42,11 @@ export default {
2842
<script setup lang="ts">
2943
import { computed, inject, onMounted } from "vue";
3044
import injectionKeys from "@/injectionKeys";
45+
import { getSourceBlueprintName } from "@/builder/useComponentDescription";
46+
import { useComponentInformation } from "@/composables/useComponentInformation";
3147
3248
const wf = inject(injectionKeys.core);
49+
const wfbm = inject(injectionKeys.builderManager);
3350
const getChildrenVNodes = inject(injectionKeys.getChildrenVNodes);
3451
3552
const displayedBlueprintId = computed(() => {
@@ -48,13 +65,21 @@ const displayedBlueprintId = computed(() => {
4865
return pageComponents[0].id;
4966
});
5067
51-
onMounted(() => {
52-
if (
53-
displayedBlueprintId.value &&
54-
wf.activePageId.value !== displayedBlueprintId.value
55-
) {
56-
wf.setActivePageId(displayedBlueprintId.value);
68+
const displayedBlueprintKey = computed(() => {
69+
const displayedBlueprint = wf.getComponentById(displayedBlueprintId.value);
70+
return displayedBlueprint?.content?.key;
71+
});
72+
73+
const selectedItemKey = computed(() => {
74+
const { component, definition: def } = useComponentInformation(
75+
wf,
76+
wfbm.firstSelectedId,
77+
);
78+
if (!component.value || component.value.type === "blueprints_blueprint") {
79+
return null;
5780
}
81+
82+
return component.value?.content?.alias || def.value?.name || "Unknown";
5883
});
5984
</script>
6085

@@ -68,6 +93,34 @@ onMounted(() => {
6893
width: 100%;
6994
}
7095
96+
.BlueprintsRoot_titleContainer {
97+
position: absolute;
98+
top: 24px;
99+
left: 24px;
100+
font-family: Poppins, "Helvetica Neue", "Lucida Grande", sans-serif;
101+
font-size: 10px;
102+
font-weight: 500;
103+
line-height: 10px; /* 100% */
104+
letter-spacing: 0.5px;
105+
text-transform: none;
106+
z-index: 1;
107+
pointer-events: none;
108+
user-select: none;
109+
110+
background: var(--builderSubtleSeparatorColor);
111+
padding: 8px;
112+
border-radius: 8px;
113+
border: 1px solid var(--builderSeparatorColor);
114+
display: flex;
115+
align-items: baseline;
116+
gap: 4px;
117+
}
118+
119+
.BlueprintsRoot_title_prefix {
120+
text-transform: none;
121+
color: var(--builderSecondaryTextColor);
122+
}
123+
71124
.BlueprintsRoot.selected {
72125
background-color: var(--emptinessColor);
73126
}

src/ui/src/components/blueprints/abstract/BlueprintsNode.vue

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
'BlueprintsNode--stopped': completionStyle == 'stopped',
1212
'BlueprintsNode--error': completionStyle == 'error',
1313
}"
14+
@dblclick="handleDoubleClick"
1415
>
1516
<div
1617
v-if="isIntelligent && completionStyle === null"
@@ -110,6 +111,7 @@
110111
/>
111112
<BlueprintsNodeActions
112113
:show-display-error-option="canDisplayErrorOut"
114+
:show-open-editor-option="isCodeComponent"
113115
@show-error="forceDisplayErrorOut = true"
114116
/>
115117
</div>
@@ -137,7 +139,7 @@ export default {
137139
</script>
138140

139141
<script setup lang="ts">
140-
import { computed, inject, ref, watch } from "vue";
142+
import { computed, inject, nextTick, ref, watch } from "vue";
141143
import injectionKeys from "@/injectionKeys";
142144
import { FieldType, WriterComponentDefinition } from "@/writerTypes";
143145
import BlueprintsNodeNamer from "../base/BlueprintsNodeNamer.vue";
@@ -151,11 +153,15 @@ import BlueprintsNodeLogs from "./BlueprintsNodeLogs.vue";
151153
import BlueprintsNodeTools from "./BlueprintsNodeTools.vue";
152154
import { useBlueprintNodeTools } from "@/composables/useBlueprintNodeTools";
153155
import { getSourceBlueprintName } from "@/builder/useComponentDescription";
156+
import { useToasts } from "@/builder/useToast";
157+
import { useWriterTracking } from "@/composables/useWriterTracking";
154158
155159
const emit = defineEmits(["outMousedown", "engaged"]);
156160
const wf = inject(injectionKeys.core);
157161
const wfbm = inject(injectionKeys.builderManager);
158-
const { removeOut } = useComponentActions(wf, wfbm);
162+
const { removeOut, goToComponentParentPage } = useComponentActions(wf, wfbm);
163+
const { pushToast } = useToasts();
164+
const tracking = useWriterTracking(wf);
159165
const componentId = inject(injectionKeys.componentId);
160166
const fields = inject(injectionKeys.evaluatedFields);
161167
@@ -173,6 +179,10 @@ const isDeprecated = computed(() => {
173179
174180
const { component, definition: def } = useComponentInformation(wf, componentId);
175181
182+
const isCodeComponent = computed(() => {
183+
return component.value?.type === "blueprints_code";
184+
});
185+
176186
const displayName = computed(() => {
177187
if (!component.value) return "Unknown";
178188
return (
@@ -411,6 +421,44 @@ const possibleImageUrls = computed(() => {
411421
return paths.map((p) => convertAbsolutePathtoFullURL(p));
412422
});
413423
424+
async function handleDoubleClick(ev: MouseEvent) {
425+
if (
426+
component.value.type === "blueprints_code" ||
427+
component.value.type === "blueprints_setstate" ||
428+
component.value.type === "blueprints_returnvalue" ||
429+
component.value.type === "blueprints_logmessage"
430+
) {
431+
const isSelected = wfbm.isComponentIdSelected(componentId);
432+
if (isSelected) {
433+
// Expand the code editor
434+
wfbm.expandedEditorForComponent.value = componentId;
435+
tracking.track(
436+
isCodeComponent.value
437+
? "dbl_click_for_code_editor_opened"
438+
: "dbl_click_for_value_opened",
439+
);
440+
}
441+
} else if (component.value.type === "blueprints_runblueprint") {
442+
const bpKey = component.value.content?.blueprintKey ?? "";
443+
if (!bpKey) return;
444+
445+
const blueprint = wf
446+
.getComponents("blueprints_root")
447+
.find((page) => page.content.key === bpKey);
448+
449+
if (!blueprint) return;
450+
goToComponentParentPage(blueprint.id);
451+
await nextTick();
452+
wfbm.handleSelectionFromEvent(ev, blueprint.id, undefined, "tree");
453+
454+
pushToast({
455+
type: "success",
456+
message: `Navigated to ${blueprint.content.key} blueprint`,
457+
});
458+
tracking.track("dbl_click_for_blueprint_navigated");
459+
}
460+
}
461+
414462
function openLogs() {
415463
const item = latestKnownOutcomes.value.at(-1);
416464
if (!item) return;

src/ui/src/components/blueprints/abstract/BlueprintsNodeActions.vue

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { computed, inject } from "vue";
2+
import { computed, inject, nextTick } from "vue";
33
import injectionKeys from "@/injectionKeys";
44
import { useComponentInformation } from "@/composables/useComponentInformation";
55
import { useBlueprintComponentResultId } from "@/composables/useBlueprintComponentResultId";
@@ -22,6 +22,7 @@ const instancePath = inject(injectionKeys.instancePath);
2222
2323
const props = defineProps({
2424
showDisplayErrorOption: { type: Boolean },
25+
showOpenEditorOption: { type: Boolean },
2526
});
2627
2728
const emits = defineEmits({
@@ -39,6 +40,13 @@ function copyComponentId() {
3940
copy(resultId.value);
4041
}
4142
43+
async function openEditorForComponent() {
44+
wfbm.setSelection(componentId, undefined, "click");
45+
await nextTick();
46+
wfbm.expandedEditorForComponent.value = componentId;
47+
tracking.track("button_click_for_code_editor_opened");
48+
}
49+
4250
const settingsActions = useBuilderSettingsActions(
4351
wf,
4452
wfbm,
@@ -96,6 +104,17 @@ const dropdownOptions = computed(() => {
96104

97105
<template>
98106
<div class="BlueprintsNodeActions">
107+
<WdsButton
108+
v-if="props.showOpenEditorOption"
109+
size="smallIcon"
110+
variant="neutral"
111+
data-writer-tooltip-placement="bottom"
112+
data-writer-unselectable
113+
data-writer-tooltip="Open editor"
114+
@click.prevent="openEditorForComponent"
115+
>
116+
<WdsIcon name="code" />
117+
</WdsButton>
99118
<WdsButton
100119
v-if="resultId"
101120
size="smallIcon"

src/ui/src/composables/useWriterTracking.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ type WriterTrackingEventName =
4141
| "blueprints_block_output_copied"
4242
| "blueprints_shared_added"
4343
| "blueprints_shared_published"
44-
| "blueprints_shared_installed";
44+
| "blueprints_shared_installed"
45+
| "dbl_click_for_code_editor_opened"
46+
| "dbl_click_for_value_opened"
47+
| "button_click_for_code_editor_opened"
48+
| "dbl_click_for_blueprint_navigated";
4549

4650
type EventProperties = {
4751
[key: string]: unknown;

0 commit comments

Comments
 (0)