Skip to content

Commit ebcb0bd

Browse files
feat(ui): add plugin icons to auto-completions + make autocompletion work upon writing full package
1 parent 2a26c41 commit ebcb0bd

File tree

1 file changed

+101
-15
lines changed

1 file changed

+101
-15
lines changed

ui/src/components/inputs/MonacoEditor.vue

Lines changed: 101 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
</template>
44

55
<script lang="ts">
6-
import {defineComponent} from "vue";
6+
import {defineComponent, h, render} from "vue";
77
import {mapMutations, mapState} from "vuex";
88
99
import "monaco-editor/esm/vs/editor/editor.all.js";
@@ -21,10 +21,11 @@
2121
import {configureMonacoYaml} from "monaco-yaml";
2222
import {yamlSchemas} from "override/utils/yamlSchemas";
2323
import Utils from "../../utils/utils";
24-
import {YamlUtils as YAML_UTILS} from "@kestra-io/ui-libs";
24+
import {TaskIcon, YamlUtils as YAML_UTILS} from "@kestra-io/ui-libs";
2525
import {QUOTE, YamlNoAutoCompletion} from "../../services/autoCompletionProvider.js"
2626
import {FlowAutoCompletion} from "override/services/flowAutoCompletionProvider.js";
2727
import RegexProvider from "../../utils/regex";
28+
import uniqBy from "lodash/uniqBy";
2829
import IModel = editor.IModel;
2930
import CompletionList = languages.CompletionList;
3031
import ProviderResult = languages.ProviderResult;
@@ -76,18 +77,20 @@
7677
flowsInputsCache: {},
7778
autoCompletionProviders: [] as monaco.IDisposable[],
7879
monaco: null as typeof monaco | null,
79-
suggestWidgetObserver: undefined as MutationObserver | undefined
80+
suggestWidgetResizeObserver: undefined as MutationObserver | undefined,
81+
suggestWidgetIconsObserver: undefined as MutationObserver | undefined,
82+
suggestWidget: undefined as HTMLElement | undefined,
8083
}
8184
},
8285
computed: {
8386
...mapState("namespace", ["datatypeNamespaces"]),
8487
...mapState("core", ["monacoYamlConfigured"]),
8588
...mapState("editor", ["current"]),
89+
...mapState("plugin", ["icons"]),
8690
prefix() {
8791
return this.schemaType ? `${this.schemaType}-` : "";
8892
},
8993
},
90-
9194
props: {
9295
path: {
9396
type: String,
@@ -176,6 +179,77 @@
176179
if (this.$options.editor) {
177180
monaco.editor.setTheme(newVal);
178181
}
182+
},
183+
suggestWidget(newVal: HTMLElement | undefined) {
184+
const replaceRowsIcons = (nodes: HTMLElement[]) => {
185+
nodes = uniqBy(nodes, node => node.id);
186+
for (let node of nodes) {
187+
const maybeTaskName = node?.getAttribute("aria-label");
188+
if (!maybeTaskName || node.getAttribute("data-index") === null) {
189+
continue;
190+
}
191+
192+
const vsCodeIcon = node.querySelector(".suggest-icon") as HTMLElement;
193+
const taskIcon = node.querySelector(".wrapper:has(.icon)") as HTMLElement | null;
194+
195+
if (maybeTaskName.includes(".")) {
196+
if (this.icons[maybeTaskName] !== undefined) {
197+
vsCodeIcon.style.display = "none";
198+
199+
const tempContainer = document.createElement("div");
200+
render(h(TaskIcon, {
201+
cls: maybeTaskName,
202+
class: "w-auto h-auto me-1",
203+
"only-icon": true,
204+
icons: this.icons
205+
}), tempContainer);
206+
207+
if (taskIcon !== null) {
208+
taskIcon.replaceWith(tempContainer.firstElementChild!);
209+
} else {
210+
vsCodeIcon.after(tempContainer.firstElementChild!);
211+
}
212+
tempContainer.remove();
213+
}
214+
} else {
215+
vsCodeIcon.style.display = "revert";
216+
taskIcon?.remove();
217+
}
218+
}
219+
}
220+
221+
if (newVal !== undefined) {
222+
if (newVal.querySelector(".monaco-list-row") !== null) {
223+
replaceRowsIcons([...newVal.getElementsByClassName("monaco-list-row")] as HTMLElement[]);
224+
}
225+
226+
this.suggestWidgetIconsObserver?.disconnect();
227+
this.suggestWidgetIconsObserver = undefined;
228+
229+
this.suggestWidgetIconsObserver = new MutationObserver(mutations => {
230+
replaceRowsIcons(
231+
mutations.flatMap(({addedNodes}) => {
232+
const nodes = [...addedNodes] as (Node | HTMLElement)[];
233+
const maybeRows: HTMLElement[] = nodes.filter(n => (<HTMLElement>n).classList?.contains("monaco-list-row")) as HTMLElement[];
234+
235+
for(let node of nodes) {
236+
let maybeRow: HTMLElement | null = null;
237+
if (node instanceof Text) {
238+
maybeRow = node.parentElement?.closest(".monaco-list-row") as HTMLElement | null;
239+
}
240+
241+
if (maybeRow !== null) {
242+
return [...maybeRows, maybeRow];
243+
}
244+
}
245+
246+
return maybeRows;
247+
})
248+
);
249+
})
250+
251+
this.suggestWidgetIconsObserver.observe(newVal, {childList: true, subtree: true})
252+
}
179253
}
180254
},
181255
mounted: async function () {
@@ -222,7 +296,8 @@
222296
223297
if (suggestion.label.includes(".")) {
224298
const dotSplit = suggestion.label.split(/\.(?=\w)/);
225-
suggestion.filterText = [dotSplit.pop(), ...dotSplit].join(".");
299+
const taskName = dotSplit.pop();
300+
suggestion.filterText = [taskName, ...dotSplit, taskName].join(".");
226301
}
227302
});
228303
@@ -247,6 +322,17 @@
247322
},
248323
methods: {
249324
...mapMutations("editor", ["setTabDirty"]),
325+
disposeObservers() {
326+
if (this.suggestWidgetResizeObserver !== undefined) {
327+
this.suggestWidgetResizeObserver!.disconnect();
328+
this.suggestWidgetResizeObserver = undefined;
329+
}
330+
if (this.suggestWidgetIconsObserver !== undefined) {
331+
this.suggestWidgetIconsObserver!.disconnect();
332+
this.suggestWidgetIconsObserver = undefined;
333+
}
334+
this.suggestWidget = undefined;
335+
},
250336
async addKestraAutoCompletions() {
251337
const NO_SUGGESTIONS = {suggestions: []};
252338
@@ -440,17 +526,17 @@
440526
* Once the resize has been done, the observer is disconnected and put back to undefined so that new instances of Monaco repeats the process to target the proper DOM element.
441527
*/
442528
observeAndResizeSuggestWidget() {
443-
if (this.suggestWidgetObserver !== undefined) {
529+
if (this.suggestWidgetResizeObserver !== undefined) {
444530
return;
445531
}
446532
447-
this.suggestWidgetObserver = new MutationObserver(([{
533+
this.suggestWidgetResizeObserver = new MutationObserver(([{
448534
target,
449535
addedNodes
450536
}]) => {
451537
const simulateResizeOnSashAndDisconnect = (resizer: HTMLElement) => {
452-
this.suggestWidgetObserver?.disconnect();
453-
this.suggestWidgetObserver = undefined;
538+
this.suggestWidgetResizeObserver?.disconnect();
539+
this.suggestWidgetResizeObserver = undefined;
454540
455541
const resizerInitialCoordinates = {
456542
x: resizer.getBoundingClientRect().left,
@@ -505,19 +591,19 @@
505591
506592
const maybeSuggestWidgetHtmlElement = addedNodes?.[0] as HTMLElement;
507593
if (maybeSuggestWidgetHtmlElement?.classList.contains("suggest-widget")) {
508-
const resizer = ([...maybeSuggestWidgetHtmlElement.querySelectorAll(".monaco-sash.vertical")] as HTMLElement[])
509-
.sort((a, b) => parseInt(b.style.left) - parseInt(a.style.left))[0];
594+
this.suggestWidget = maybeSuggestWidgetHtmlElement;
595+
const resizer = maybeSuggestWidgetHtmlElement.querySelector(".monaco-sash.vertical") as HTMLElement;
510596
511597
if (resizer.classList.contains("disabled")) {
512-
this.suggestWidgetObserver!.disconnect();
513-
this.suggestWidgetObserver?.observe(resizer, {attributeFilter: ["class"]})
598+
this.suggestWidgetResizeObserver!.disconnect();
599+
this.suggestWidgetResizeObserver?.observe(resizer, {attributeFilter: ["class"]})
514600
} else {
515601
simulateResizeOnSashAndDisconnect(resizer);
516602
}
517603
}
518604
});
519605
520-
this.suggestWidgetObserver.observe(this.$el.querySelector(".overflowingContentWidgets"), {childList: true})
606+
this.suggestWidgetResizeObserver.observe(this.$el.querySelector(".overflowingContentWidgets"), {childList: true})
521607
},
522608
initMonaco: async function () {
523609
let self = this;
@@ -640,7 +726,7 @@
640726
this.$options.editor.focus();
641727
},
642728
destroy: function () {
643-
this.suggestWidgetObserver?.disconnect();
729+
this.disposeObservers();
644730
this.autoCompletionProviders.forEach(provider => provider.dispose());
645731
this.$options.editor?.getModel()?.dispose?.();
646732
this.$options.editor?.dispose?.();

0 commit comments

Comments
 (0)