@kestra-io/ui-libs 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,18 +9,24 @@
9
9
  >
10
10
  <template #badge-button-before>
11
11
  <span
12
- v-if="!data.execution && !data.isReadOnly"
12
+ v-if="!execution && !data.isReadOnly"
13
13
  class="rounded-button"
14
14
  :class="[`bg-${color}`]"
15
15
  @click="$emit(EVENTS.EDIT, {task: data.node.trigger, section: SECTIONS.TRIGGERS})"
16
+ data-bs-toggle="tooltip"
17
+ data-bs-placement="top"
18
+ :title="$t('edit')"
16
19
  >
17
20
  <Pencil class="button-icon" alt="Edit task" />
18
21
  </span>
19
22
  <span
20
- v-if="!data.execution && !data.isReadOnly"
23
+ v-if="!execution && !data.isReadOnly"
21
24
  class="rounded-button"
22
25
  :class="[`bg-${color}`]"
23
26
  @click="$emit(EVENTS.DELETE, {id, section: SECTIONS.TRIGGERS})"
27
+ data-bs-toggle="tooltip"
28
+ data-bs-placement="top"
29
+ :title="$t('delete')"
24
30
  >
25
31
  <Delete class="button-icon" alt="Delete task" />
26
32
  </span>
@@ -28,18 +34,39 @@
28
34
  </basic-node>
29
35
  <Handle type="target" :position="targetPosition" />
30
36
  </template>
31
-
37
+ <script setup>
38
+ import BasicNode from "./BasicNode.vue";
39
+ </script>
32
40
  <script>
33
41
  import BasicNode from "./BasicNode.vue";
34
42
  import {Handle} from "@vue-flow/core";
43
+ import {mapState} from "vuex";
35
44
  import {EVENTS, SECTIONS} from "../../utils/constants.js";
36
45
  import Pencil from "vue-material-design-icons/Pencil.vue";
37
46
  import Delete from "vue-material-design-icons/Delete.vue";
47
+ import {Tooltip} from "bootstrap";
38
48
 
39
49
  export default {
40
50
  name: "Task",
41
51
  inheritAttrs: false,
52
+ mounted(){
53
+ const tooltipTriggerList = [].slice.call(document.querySelectorAll("[data-bs-toggle=\"tooltip\"]"));
54
+ this.tooltips = tooltipTriggerList.map(function (tooltipTriggerEl) {
55
+ return new Tooltip(tooltipTriggerEl, {
56
+ trigger : "hover"
57
+ })
58
+ })
59
+ },
60
+ beforeUnmount() {
61
+ document.querySelectorAll("[data-bs-toggle=\"tooltip\"]").forEach((el) => {
62
+ const tooltip = Tooltip.getInstance(el);
63
+ if (tooltip) {
64
+ tooltip.dispose();
65
+ }
66
+ });
67
+ },
42
68
  computed: {
69
+ ...mapState("execution", ["execution"]),
43
70
  SECTIONS() {
44
71
  return SECTIONS
45
72
  },
@@ -57,7 +84,6 @@
57
84
  components: {
58
85
  Delete, Pencil,
59
86
  Handle,
60
- BasicNode,
61
87
  },
62
88
  props: {
63
89
  data: {
@@ -0,0 +1,355 @@
1
+ <script setup>
2
+ import {
3
+ ref,
4
+ defineProps,
5
+ defineEmits, watch, nextTick,
6
+ onMounted
7
+ } from "vue";
8
+ import {
9
+ ClusterNode,
10
+ DotNode,
11
+ TaskNode,
12
+ TriggerNode,
13
+ CollapsedClusterNode,
14
+ EdgeNode,
15
+ } from "../index.js";
16
+ import {useVueFlow, VueFlow} from "@vue-flow/core";
17
+ import {ControlButton, Controls} from "@vue-flow/controls";
18
+ import SplitCellsVertical from "../../assets/icons/SplitCellsVertical.vue";
19
+ import SplitCellsHorizontal from "../../assets/icons/SplitCellsHorizontal.vue";
20
+ import {cssVariable} from "../../utils/global.js";
21
+ import {VueFlowUtils, YamlUtils} from "../../index.js";
22
+ import Utils from "../../utils/Utils.js";
23
+ import VueflowUtils from "../../utils/VueFlowUtils.js";
24
+ import {CLUSTER_UID_SEPARATOR} from "../../utils/constants.js";
25
+
26
+ const props = defineProps({
27
+ id: {
28
+ type: String,
29
+ required: true
30
+ },
31
+ isHorizontal: {
32
+ type: Boolean,
33
+ default: true,
34
+ },
35
+ isReadOnly: {
36
+ type: Boolean,
37
+ default: true,
38
+ },
39
+ isAllowedEdit: {
40
+ type: Boolean,
41
+ default: false,
42
+ },
43
+ flowables: {
44
+ type: Array,
45
+ default: () => [],
46
+ },
47
+ source: {
48
+ type: String,
49
+ default: undefined,
50
+ required: true,
51
+ },
52
+ toggleOrientationButton: {
53
+ type: Boolean,
54
+ default: false,
55
+ },
56
+ flowGraph: {
57
+ type: Object,
58
+ required: true
59
+ },
60
+ flowId: {
61
+ type: String,
62
+ required: false,
63
+ default: undefined
64
+ },
65
+ namespace: {
66
+ type: String,
67
+ required: false,
68
+ default: undefined
69
+ },
70
+ });
71
+
72
+ const dragging = ref(false);
73
+ const lastPosition = ref(null)
74
+ const {getNodes, onNodeDrag, onNodeDragStart, onNodeDragStop, fitView, setElements} = useVueFlow({id: props.id});
75
+ const edgeReplacer = ref({});
76
+ const hiddenNodes = ref([]);
77
+ const collapsed = ref([]);
78
+ const clusterToNode = ref([])
79
+
80
+ const emit = defineEmits(
81
+ [
82
+ "edit",
83
+ "delete",
84
+ "open-link",
85
+ "show-logs",
86
+ "show-description",
87
+ "on-add-flowable-error",
88
+ "add-task",
89
+ "toggle-orientation",
90
+ "loading",
91
+ "swapped-task",
92
+ "message"
93
+ ]
94
+ )
95
+
96
+ onMounted( () => {
97
+ generateGraph();
98
+ })
99
+
100
+ watch(() => props.flowGraph, () => {
101
+ generateGraph();
102
+ })
103
+
104
+ const generateGraph = () => {
105
+ VueFlowUtils.cleanGraph(props.id);
106
+
107
+ nextTick(() => {
108
+ forwardEvent("loading", true);
109
+ const elements = VueflowUtils.generateGraph(
110
+ props.id,
111
+ props.flowId,
112
+ props.namespace,
113
+ props.flowGraph,
114
+ props.source,
115
+ hiddenNodes.value,
116
+ props.isHorizontal,
117
+ edgeReplacer.value,
118
+ collapsed.value,
119
+ clusterToNode.value,
120
+ props.isReadOnly,
121
+ props.isAllowedEdit,
122
+ flowables()
123
+ );
124
+ setElements(elements);
125
+ fitView();
126
+ forwardEvent("loading", false);
127
+ })
128
+ }
129
+
130
+ const forwardEvent = (type, payload) => {
131
+ emit(type, payload);
132
+ };
133
+
134
+ // Graph interactions functions
135
+ const onMouseOver = (node) => {
136
+ if (!dragging.value) {
137
+ VueFlowUtils.linkedElements(props.id, node.uid).forEach((n) => {
138
+ if (n.type === "task") {
139
+ n.style = {...n.style, outline: "0.5px solid " + cssVariable("--bs-gray-900")}
140
+ n.class = "rounded-3"
141
+ }
142
+ });
143
+ }
144
+
145
+ }
146
+
147
+ const onMouseLeave = () => {
148
+ resetNodesStyle();
149
+ }
150
+
151
+ const resetNodesStyle = () => {
152
+ getNodes.value.filter(n => n.type === "task" || n.type === "trigger")
153
+ .forEach(n => {
154
+ n.style = {...n.style, opacity: "1", outline: "none"}
155
+ n.class = ""
156
+ })
157
+ }
158
+
159
+ onNodeDragStart((e) => {
160
+ dragging.value = true;
161
+ resetNodesStyle();
162
+ e.node.style = {...e.node.style, zIndex: 1976}
163
+ lastPosition.value = e.node.position;
164
+ })
165
+
166
+ onNodeDragStop((e) => {
167
+ dragging.value = false;
168
+ if (checkIntersections(e.intersections, e.node) === null) {
169
+ const taskNode1 = e.node;
170
+ // check multiple intersection with task
171
+ const taskNode2 = e.intersections.find(n => n.type === "task");
172
+ if (taskNode2) {
173
+ try {
174
+ emit("swapped-task", YamlUtils.swapTasks(props.source, taskNode1.id, taskNode2.id))
175
+ } catch (e) {
176
+ emit("message", {
177
+ variant: "error",
178
+ title: "cannot swap tasks",
179
+ message: `${e.message}, ${e.messageOptions}`
180
+ });
181
+ taskNode1.position = lastPosition.value;
182
+ }
183
+ } else {
184
+ taskNode1.position = lastPosition.value;
185
+ }
186
+ } else {
187
+ e.node.position = lastPosition.value;
188
+ }
189
+ resetNodesStyle();
190
+ e.node.style = {...e.node.style, zIndex: 5}
191
+ lastPosition.value = null;
192
+ })
193
+
194
+ onNodeDrag((e) => {
195
+ resetNodesStyle();
196
+ getNodes.value.filter(n => n.id !== e.node.id).forEach(n => {
197
+ if (n.type === "trigger" || (n.type === "task" && YamlUtils.isParentChildrenRelation(props.source, n.id, e.node.id))) {
198
+ n.style = {...n.style, opacity: "0.5"}
199
+ } else {
200
+ n.style = {...n.style, opacity: "1"}
201
+ }
202
+ })
203
+ if (!checkIntersections(e.intersections, e.node) && e.intersections.filter(n => n.type === "task").length === 1) {
204
+ e.intersections.forEach(n => {
205
+ if (n.type === "task") {
206
+ n.style = {...n.style, outline: "0.5px solid " + cssVariable("--bs-primary")}
207
+ n.class = "rounded-3"
208
+ }
209
+ })
210
+ e.node.style = {...e.node.style, outline: "0.5px solid " + cssVariable("--bs-primary")}
211
+ e.node.class = "rounded-3"
212
+ }
213
+ })
214
+
215
+ const checkIntersections = (intersections, node) => {
216
+ const tasksMeet = intersections.filter(n => n.type === "task").map(n => n.id);
217
+ if (tasksMeet.length > 1) {
218
+ return "toomuchtaskerror";
219
+ }
220
+ if (tasksMeet.length === 1 && YamlUtils.isParentChildrenRelation(props.source, tasksMeet[0], node.id)) {
221
+ return "parentchildrenerror";
222
+ }
223
+ if (intersections.filter(n => n.type === "trigger").length > 0) {
224
+ return "triggererror";
225
+ }
226
+ return null;
227
+ }
228
+
229
+ const collapseCluster = (clusterUid, regenerate, recursive) => {
230
+
231
+ const cluster = props.flowGraph.clusters.find(cluster => cluster.cluster.uid === clusterUid)
232
+ const nodeId = clusterUid === "Triggers" ? "Triggers" : Utils.splitFirst(clusterUid, CLUSTER_UID_SEPARATOR);
233
+ collapsed.value = collapsed.value.concat([nodeId])
234
+
235
+ hiddenNodes.value = hiddenNodes.value.concat(cluster.nodes.filter(e => e !== nodeId || recursive));
236
+ if (clusterUid !== "Triggers") {
237
+ hiddenNodes.value = hiddenNodes.value.concat([cluster.cluster.uid])
238
+ edgeReplacer.value = {
239
+ ...edgeReplacer.value,
240
+ [cluster.cluster.uid]: nodeId,
241
+ [cluster.start]: nodeId,
242
+ [cluster.end]: nodeId
243
+ }
244
+
245
+ for (let child of cluster.nodes) {
246
+ if (props.flowGraph.clusters.map(cluster => cluster.cluster.uid).includes(child)) {
247
+ collapseCluster(child, false, true);
248
+ }
249
+ }
250
+ } else {
251
+ edgeReplacer.value = {
252
+ ...edgeReplacer.value,
253
+ [cluster.start]: nodeId,
254
+ [cluster.end]: nodeId
255
+ }
256
+ }
257
+
258
+ if (regenerate) {
259
+ generateGraph();
260
+ }
261
+ }
262
+
263
+ const expand = (taskId) => {
264
+ edgeReplacer.value = {}
265
+ hiddenNodes.value = []
266
+ clusterToNode.value = []
267
+ collapsed.value = collapsed.value.filter(n => n != taskId)
268
+
269
+ collapsed.value.forEach(n => collapseCluster(CLUSTER_UID_SEPARATOR + n, false, false))
270
+
271
+ generateGraph();
272
+ }
273
+
274
+ const flowables = () => {
275
+ return props.flowGraph && props.flowGraph.flowables ? props.flowGraph.flowables : [];
276
+ }
277
+ </script>
278
+ <template>
279
+ <VueFlow
280
+ :id="id"
281
+ :default-marker-color="cssVariable('--bs-cyan')"
282
+ fit-view-on-init
283
+ :nodes-draggable="false"
284
+ :nodes-connectable="false"
285
+ :elevate-nodes-on-select="false"
286
+ :elevate-edges-on-select="false"
287
+ >
288
+ <template #node-cluster="clusterProps">
289
+ <ClusterNode
290
+ v-bind="clusterProps"
291
+ @collapse="collapseCluster($event, true)"
292
+ />
293
+ </template>
294
+
295
+ <template #node-dot="dotProps">
296
+ <DotNode
297
+ v-bind="dotProps"
298
+ />
299
+ </template>
300
+
301
+ <template #node-task="taskProps">
302
+ <TaskNode
303
+ v-bind="taskProps"
304
+ @edit="forwardEvent('edit', $event)"
305
+ @delete="forwardEvent('delete', $event)"
306
+ @expand="expand($event)"
307
+ @open-link="forwardEvent('open-link', $event)"
308
+ @show-logs="forwardEvent('show-logs', $event)"
309
+ @show-description="forwardEvent('show-description', $event)"
310
+ @mouseover="onMouseOver($event)"
311
+ @mouseleave="onMouseLeave()"
312
+ @add-error="forwardEvent('on-add-flowable-error', $event)"
313
+ />
314
+ </template>
315
+
316
+ <template #node-trigger="triggerProps">
317
+ <TriggerNode
318
+ v-bind="triggerProps"
319
+ :is-read-only="isReadOnly"
320
+ :is-allowed-edit="isAllowedEdit"
321
+ @delete="forwardEvent('delete', $event)"
322
+ @edit="forwardEvent('edit', $event)"
323
+ />
324
+ </template>
325
+
326
+ <template #node-collapsedcluster="CollapsedProps">
327
+ <CollapsedClusterNode
328
+ v-bind="CollapsedProps"
329
+ @expand="expand($event)"
330
+ />
331
+ </template>
332
+
333
+ <template #edge-edge="EdgeProps">
334
+ <EdgeNode
335
+ v-bind="EdgeProps"
336
+ :yaml-source="source"
337
+ :flowables-ids="flowables"
338
+ @add-task="forwardEvent('add-task', $event)"
339
+ :is-read-only="isReadOnly"
340
+ :is-allowed-edit="isAllowedEdit"
341
+ />
342
+ </template>
343
+
344
+ <Controls :show-interactive="false">
345
+ <ControlButton @click="forwardEvent('toggle-orientation', $event)" v-if="toggleOrientationButton">
346
+ <SplitCellsVertical :size="48" v-if="!isHorizontal" />
347
+ <SplitCellsHorizontal v-if="isHorizontal" />
348
+ </ControlButton>
349
+ </Controls>
350
+ </VueFlow>
351
+ </template>
352
+
353
+ <style scoped lang="scss">
354
+
355
+ </style>
package/src/index.js CHANGED
@@ -1 +1,7 @@
1
+ import YamlUtils from "./utils/YamlUtils.js";
2
+ import Utils from "./utils/Utils.js";
3
+ import VueFlowUtils from "./utils/VueFlowUtils.js";
4
+
5
+ export {YamlUtils, Utils, VueFlowUtils};
6
+
1
7
  export * from "./components/index.js";
@@ -1,3 +1,8 @@
1
+ // primary color
2
+ $primary: #8405FF !default;
3
+ $secondary: #C182FF !default;
4
+ $tertiary: #2F3342 !default;
5
+
1
6
  // color system
2
7
  $blue: #1761FD !default;
3
8
  $indigo: #8405FF !default;
@@ -11,22 +16,18 @@ $green: #03DABA !default;
11
16
  $teal: #03D87F !default;
12
17
  $cyan: #60C5FE !default;
13
18
 
14
- // primary color
15
- $primary: #8405FF !default;
16
- $secondary: #C182FF !default;
17
- $tertiary: #2F3342 !default;
18
19
 
19
20
  // gray
20
21
  $white: #FFF !default;
21
22
  $gray-100: #F5F5FF !default;
22
- $gray-200: #f1f5fa !default;
23
+ $gray-200: #F0F0FF !default;
23
24
  $gray-300: #E5E4F7 !default;
24
- $gray-400: #b6c2e4 !default;
25
- $gray-500: #8997bd !default;
26
- $gray-600: #7081b9 !default;
27
- $gray-700: #303e67 !default;
28
- $gray-800: #2c3652 !default;
29
- $gray-900: #1d2c48 !default;
25
+ $gray-400: #D1CFE9 !default;
26
+ $gray-500: #B8B6D9 !default;
27
+ $gray-600: #A6A4CA !default;
28
+ $gray-700: #9A8EB4 !default;
29
+ $gray-800: #7E719F !default;
30
+ $gray-900: #564A75 !default;
30
31
  $black: #26282D !default;
31
32
 
32
33
  $light: $gray-200 !default;
@@ -34,10 +35,21 @@ $dark: $gray-900 !default;
34
35
 
35
36
  // fonts
36
37
  $font-size-base: 1rem !default;
38
+ $font-size-xs: $font-size-base * .75;
39
+ $font-size-sm: $font-size-base * .875;
40
+ $font-size-lg: $font-size-base * 1.25;
41
+ $font-size-xl: $font-size-base * 1.375;
37
42
  $font-family-sans-serif: "Public Sans", sans-serif;
38
43
  $font-family-monospace: "Source Code Pro", monospace;
39
44
  $font-size-xs: $font-size-base * 0.75 !default;
40
45
 
46
+ $h1-font-size: $font-size-base * 3.375;
47
+ $h2-font-size: $font-size-base * 2.5;
48
+ $h3-font-size: $font-size-base * 2;
49
+ $h4-font-size: $font-size-base * 1.375;
50
+ $h5-font-size: $font-size-base * 1.25;
51
+ $h6-font-size: $font-size-base * 1.125;
52
+
41
53
  // border radius
42
54
  $border-radius: 0.25rem !default;
43
55
  $border-radius-lg: 0.5rem !default;
@@ -49,12 +61,16 @@ $spacer: 1rem !default;
49
61
 
50
62
  // body
51
63
  $body-color: $gray-800 !default;
64
+ $body-tertiary-color: #FFFFFF;
65
+ $body-tertiary-bg: #785EEA;
52
66
  $border-color: $gray-300 !default;
53
67
  $body-bg: $gray-100 !default;
54
68
  $card-bg: $white !default;
55
69
  $input-bg: $white !default;
56
70
 
71
+ // link
57
72
  $link-color: $primary !default;
73
+ $link-decoration: none;
58
74
 
59
75
  // border radius
60
76
  $border-radius: .25rem !default;
@@ -65,6 +81,45 @@ $box-shadow-sm: 0 .125rem .25rem rgba($black, .075);
65
81
  $box-shadow: 0 .5rem 1rem rgba($black, .15);
66
82
  $box-shadow-lg: 0 1rem 3rem rgba($black, .175);
67
83
 
84
+ // button
85
+ $btn-font-size-lg: 18px;
86
+
87
+ // grid
88
+ $grid-gutter-width: 1.75rem;
89
+
90
+ // nav
91
+ $nav-pills-border-radius: 50rem;
92
+
93
+ // accordion
94
+ $accordion-icon-color: $white;
95
+ $accordion-icon-active-color: $white;
96
+
97
+ // purples
98
+ $purple-1: #382369;
99
+ $purple-2: #FBC7F4;
100
+ $purple-3: #9580EE;
101
+ $purple-4: linear-gradient(160.34deg, rgba(130, 0, 255, 0.12) 5.3%, rgba(130, 0, 255, 0) 75.43%), #201838;
102
+ $purple-5: #362762;
103
+ $purple-6: #6113BC;
104
+ $purple-7: #1A1223;
105
+ $purple-8: #EEEDFF;
106
+ $purple-9: 0px 12.972px 39.7269px rgba(90, 0, 176, 0.1);
107
+ $purple-10: #150E1C;
108
+ $purple-11: #332C3B;
109
+ $purple-12: #432A71;
110
+ $purple-16: #281A35;
111
+ $purple-19: conic-gradient(from 90deg at 50% 50%, #BE79FF 0deg, #7136F6 360deg);
112
+ $purple-20: #291E39;
113
+ $purple-21: #A42DCD;
114
+ $purple-22: #FCF7FE;
115
+ $purple-23: #EDE8F3;
116
+ $purple-24: #15023F;
117
+ $purple-26: #DCCDEB;
118
+ $purple-27: #8200FF;
119
+ $purple-28: #2D313E;
120
+ $purple-29: #F1F5FF;
121
+ $purple-30: rgba(255, 255, 255, 0.1);
122
+
68
123
  // boostrap flags
69
124
  $enable-reduced-motion: false;
70
125
 
@@ -93,6 +148,8 @@ $element-colors: (
93
148
  ),
94
149
  );
95
150
 
151
+ $baseline-max-width: 730px;
152
+
96
153
  // bootstrap
97
154
  @import "bootstrap/scss/functions";
98
155
  @import "bootstrap/scss/mixins";
package/src/scss/app.scss CHANGED
@@ -37,3 +37,5 @@ marker[id*='id=marker-custom&type=arrowclosed'] polyline {
37
37
  .button-icon {
38
38
  font-size: 0.75rem;
39
39
  }
40
+
41
+
@@ -43,6 +43,18 @@ $theme-colors: map-merge($theme-colors, $custom-colors);
43
43
  }
44
44
 
45
45
 
46
+ .tooltip {
47
+ html.dark & {
48
+ color: $white;
49
+ background-color: $card-bg;
50
+ border-radius: $border-radius;
51
+ padding: calc($spacer / 3);
52
+ font-size: $font-size-xs;
53
+ border: 1px solid $border-color;
54
+ }
55
+ }
56
+
57
+
46
58
  // overload
47
59
  .font-monospace {
48
60
  font-size: $font-size-base * 0.7;
@@ -66,6 +66,16 @@ $theme-colors: map-merge($theme-colors, $custom-colors);
66
66
  }
67
67
 
68
68
 
69
+ .tooltip {
70
+ color: $black;
71
+ background-color: $card-bg;
72
+ border-radius: $border-radius-lg;
73
+ padding: calc($spacer/3);
74
+ font-size: $font-size-xs;
75
+ border: 1px solid $border-color;
76
+ }
77
+
78
+
69
79
  // overload
70
80
  .font-monospace {
71
81
  font-size: $font-size-base * 0.7;