@processmaker/screen-builder 2.83.10 → 2.84.0

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.
@@ -0,0 +1,354 @@
1
+ <template>
2
+ <b-tabs
3
+ ref="tabs"
4
+ v-model="activeTab"
5
+ class="h-100 w-100 flat-tabs"
6
+ content-class="h-tab"
7
+ lazy
8
+ @changed="tabsUpdated"
9
+ @input="tabOpened"
10
+ >
11
+ <template #tabs-start>
12
+ <div class="tabs-sticky d-flex flex-row tabs-start">
13
+ <div
14
+ v-show="tabsListOverflow"
15
+ class="position-relative overflow-visible"
16
+ >
17
+ <div
18
+ role="link"
19
+ class="nav-scroll nav-scroll-left"
20
+ data-test="scroll-left"
21
+ @click="scrollTabsLeft"
22
+ >
23
+ <i class="fas fa-chevron-left" />
24
+ </div>
25
+ </div>
26
+ </div>
27
+
28
+ <div :class="{'dd-ml': tabsListOverflow}">
29
+ <slot name="tabs-start" />
30
+ </div>
31
+ </template>
32
+ <b-tab
33
+ v-for="(index, n) in validLocalOpenedPages"
34
+ :key="`tab-${n}`"
35
+ class="h-100 w-100"
36
+ >
37
+ <template #title>
38
+ <b-badge variant="primary" class="mr-1">
39
+ {{ pageNumber(index) }}
40
+ </b-badge>
41
+ <span :data-test="`tab-${n}`">
42
+ {{ pages[index]?.name }}
43
+ </span>
44
+ <span
45
+ :data-test="`close-tab-${n}`"
46
+ class="close-tab"
47
+ role="link"
48
+ @click.stop="closeTab(n)"
49
+ >
50
+ <i class="fas fa-times" />
51
+ </span>
52
+ </template>
53
+ <template #default>
54
+ <div class="h-100 w-100" data-test="tab-content">
55
+ <slot :current-page="index" />
56
+ </div>
57
+ </template>
58
+ </b-tab>
59
+ <template #tabs-end>
60
+ <div
61
+ v-if="tabsListOverflow"
62
+ class="tabs-sticky overflow-visible"
63
+ >
64
+ <div
65
+ role="link"
66
+ class="nav-scroll nav-scroll-right"
67
+ data-test="scroll-right"
68
+ @click="scrollTabsRight"
69
+ >
70
+ <i class="fas fa-chevron-right" />
71
+ </div>
72
+ </div>
73
+ </template>
74
+ <template #empty>
75
+ <p class="text-center m-5 text-secondary" data-test="tab-content">
76
+ {{ $t("There are no open pages.") }}<br />
77
+ {{ $t("Open a new page above using the button") }}
78
+ <i :class="buttonIcon" />
79
+ </p>
80
+ </template>
81
+ </b-tabs>
82
+ </template>
83
+
84
+ <script>
85
+ const SCROLL_STEP = 200;
86
+
87
+ export default {
88
+ props: {
89
+ /**
90
+ * The configuration of all the pages
91
+ */
92
+ pages: {
93
+ type: Array,
94
+ required: true
95
+ },
96
+ /**
97
+ * The array of initial opened pages indexes
98
+ */
99
+ initialOpenedPages: {
100
+ type: Array,
101
+ default: () => [0]
102
+ },
103
+ /**
104
+ * Icon to open a new tab, displayed when there are no pages opened.
105
+ */
106
+ buttonIcon: {
107
+ type: String,
108
+ default: () => "fa fa-file"
109
+ }
110
+ },
111
+ data() {
112
+ return {
113
+ tabsListOverflow: false,
114
+ showLeftScroll: true,
115
+ showRightScroll: true,
116
+ updates: 0,
117
+ activeTab: 0,
118
+ localOpenedPages: this.initialOpenedPages
119
+ };
120
+ },
121
+ computed: {
122
+ validLocalOpenedPages() {
123
+ return this.localOpenedPages.filter((page) => this.pages[page]);
124
+ }
125
+ },
126
+ watch: {
127
+ openedPages: {
128
+ handler(newVal) {
129
+ this.localOpenedPages = newVal;
130
+ },
131
+ deep: true
132
+ },
133
+ pages: {
134
+ handler() {
135
+ this.localOpenedPages = this.localOpenedPages.filter(
136
+ (page) => this.pages[page]
137
+ );
138
+ },
139
+ deep: true
140
+ }
141
+ },
142
+ mounted() {
143
+ this.$nextTick(() => {
144
+ // check resize of tabs list
145
+ window.addEventListener("resize", this.checkTabsOverflow);
146
+ // listen to scroll event
147
+ const tablist = this.$refs.tabs.$el.querySelector(".nav-tabs");
148
+ tablist.addEventListener("scroll", this.checkScrollPosition);
149
+
150
+ Promise.resolve().then(() => {
151
+ this.checkTabsOverflow();
152
+ this.checkScrollPosition();
153
+ });
154
+ });
155
+
156
+ },
157
+ beforeDestroy() {
158
+ window.removeEventListener("resize", this.checkTabsOverflow);
159
+ },
160
+ updated() {
161
+ this.checkTabsOverflow();
162
+ },
163
+ methods: {
164
+ tabOpened() {
165
+ const pageIndex = this.localOpenedPages[this.activeTab];
166
+ this.$emit("tab-opened", pageIndex);
167
+ },
168
+ pageNumber(index) {
169
+ return index + 1;
170
+ },
171
+ checkScrollPosition() {
172
+ const tablist = this.$refs.tabs.$el.querySelector(".nav-tabs");
173
+ this.showLeftScroll = tablist.scrollLeft > 0;
174
+ this.showRightScroll =
175
+ tablist.scrollWidth - tablist.clientWidth > tablist.scrollLeft;
176
+ },
177
+ scrollTabsLeft() {
178
+ const tablist = this.$refs.tabs.$el.querySelector(".nav-tabs");
179
+ tablist.scrollLeft -= SCROLL_STEP;
180
+ },
181
+ scrollTabsRight() {
182
+ const tablist = this.$refs.tabs.$el.querySelector(".nav-tabs");
183
+ tablist.scrollLeft += SCROLL_STEP;
184
+ },
185
+ tabsUpdated() {
186
+ this.updates++;
187
+ },
188
+ waitUpdates(n, timeout, visualThreshold = 80) {
189
+ return new Promise((resolve) => {
190
+ const start = Date.now();
191
+ const interval = setInterval(() => {
192
+ if (this.updates >= n || Date.now() - start > timeout) {
193
+ clearInterval(interval);
194
+ resolve();
195
+ }
196
+ }, visualThreshold);
197
+ });
198
+ },
199
+ closeTab(pageId) {
200
+ this.localOpenedPages.splice(this.localOpenedPages.indexOf(pageId), 1);
201
+ this.$emit("tab-closed", this.pages[pageId], this.localOpenedPages);
202
+ },
203
+ updateTabsReferences(pageDelete) {
204
+ this.localOpenedPages = this.localOpenedPages.map((page) =>
205
+ page > pageDelete ? page - 1 : page
206
+ );
207
+ },
208
+ async openPageByIndex(index) {
209
+ if (index === -1) {
210
+ return;
211
+ }
212
+ const n = this.localOpenedPages.indexOf(index * 1);
213
+ if (n === -1) {
214
+ this.localOpenedPages.push(index);
215
+ await this.waitUpdates(this.updates + 2, 1000);
216
+ this.activeTab = this.localOpenedPages.length - 1;
217
+ } else {
218
+ this.activeTab = n;
219
+ }
220
+ },
221
+ closePageByIndex(index) {
222
+ const n = this.localOpenedPages.indexOf(index);
223
+ if (n !== -1) {
224
+ this.localOpenedPages.splice(n, 1);
225
+ }
226
+ },
227
+ checkTabsOverflow() {
228
+ const tablist = this.$refs.tabs.$el.querySelector(".nav-tabs");
229
+ this.tabsListOverflow = tablist.scrollWidth > tablist.clientWidth;
230
+ }
231
+ }
232
+ };
233
+ </script>
234
+
235
+ <style>
236
+ /* Setup some colors */
237
+ :root {
238
+ --tabs-blue: #1572c2;
239
+ --tabs-light: #c3c9cf;
240
+ --tabs-grey: #6a7888;
241
+ --tabs-border: #cdddee;
242
+ --tabs-scroll-bg: #ebeef2;
243
+ --tabs-white: #ffffff;
244
+ }
245
+ /* Override Bootstrap default tab styles */
246
+ .nav-tabs {
247
+ border-bottom: 1px solid var(--tabs-border) !important;
248
+ flex-wrap: nowrap !important;
249
+ overflow: hidden !important;
250
+ }
251
+
252
+ .nav-tabs .nav-item .nav-link {
253
+ display: flex;
254
+ align-items: center;
255
+ }
256
+
257
+ /* Style for individual tabs */
258
+ .nav-tabs .nav-item.show .nav-link,
259
+ .nav-tabs .nav-link.active {
260
+ border: none;
261
+ box-shadow: inset 0 -3px 0 0 var(--tabs-blue) !important; /* This sets the blue bottom border for the active tab */
262
+ background-color: var(
263
+ --tabs-white
264
+ ); /* This sets the background color for the active tab */
265
+ border-radius: 0 !important; /* This removes the border-radius */
266
+ color: var(--tabs-blue) !important;
267
+ position: relative;
268
+ }
269
+
270
+ /* Style for non-active tabs */
271
+ .nav-tabs .nav-link {
272
+ border: 1px solid var(--tabs-border) !important;
273
+ color: var(--tabs-grey); /* This sets the text color for the inactive tabs */
274
+ border-radius: 0 !important; /* This ensures no border radius for a flat design */
275
+ padding: 0.5rem 1rem;
276
+ margin-right: -1px;
277
+ text-wrap: nowrap !important;
278
+ flex-wrap: nowrap !important;
279
+ }
280
+
281
+ .nav-tabs .nav-item .nav-link:not(.active) {
282
+ .badge {
283
+ background-color: #6A7888 !important;
284
+ }
285
+
286
+ span:not(.badge, .close-tab) {
287
+ color: #556271 !important;
288
+ }
289
+ }
290
+
291
+ /* Style for the hover effect */
292
+ .nav-tabs .nav-link:hover {
293
+ border: none;
294
+ background-color: var(
295
+ --tabs-white
296
+ ); /* This changes the background color when hovering */
297
+ }
298
+
299
+ /* Adding the 'x' button to the tab */
300
+ .nav-tabs .nav-link .close-tab {
301
+ font-size: 0.8rem;
302
+ color: var(--tabs-light);
303
+ margin-left: 0.5rem;
304
+ display: inline !important;
305
+ }
306
+
307
+ /* Style adjustments for the 'x' button */
308
+ .nav-tabs .nav-link .close-tab:hover {
309
+ color: var(--tabs-grey);
310
+ cursor: pointer;
311
+ }
312
+ .nav-tabs .nav-scroll {
313
+ position: absolute;
314
+ top: calc(50% - 0.75rem);
315
+ width: 1.5rem;
316
+ height: 1.5rem;
317
+ font-size: 0.75rem;
318
+ display: flex;
319
+ align-items: center;
320
+ justify-content: center;
321
+ background-color: var(--tabs-scroll-bg);
322
+ color: var(--tabs-grey);
323
+ cursor: pointer;
324
+ border: 1px solid var(--tabs-border);
325
+ border-radius: 100%;
326
+ z-index: 1;
327
+ }
328
+ .nav-tabs .nav-scroll-right {
329
+ right: 5px;
330
+ }
331
+ .nav-tabs .nav-scroll-left {
332
+ margin-left: 5px;
333
+ }
334
+ .nav-tabs .tabs-sticky {
335
+ position: sticky;
336
+ -webkit-position: sticky;
337
+ left: 0;
338
+ right: 0;
339
+ z-index: 2;
340
+ }
341
+ .nav-tabs .tabs-start {
342
+ background-color: var(--tabs-white);
343
+ }
344
+ /* add margin right to the last li element to hide safely the right scroll button */
345
+ .nav-tabs .nav-item:last-of-type {
346
+ margin-right: 2rem;
347
+ }
348
+ .flat-tabs .h-tab {
349
+ height: calc(100% - 42px) !important;
350
+ }
351
+ .dd-ml {
352
+ margin-left: 2rem;
353
+ }
354
+ </style>
@@ -0,0 +1,125 @@
1
+ <template>
2
+ <!--
3
+ Dropdown component to display options related to pages.
4
+ Provides options to add a new page, see all pages, and select individual pages.
5
+ -->
6
+ <b-dropdown
7
+ ref="pageDropdown"
8
+ data-test="page-dropdown"
9
+ variant="platform"
10
+ :boundary="boundary"
11
+ menu-class="page-dropdown-menu"
12
+ >
13
+ <!-- Dropdown button content -->
14
+ <template #button-content>
15
+ <!-- Icon representing a file -->
16
+ <i class="fa fa-file"></i>
17
+ </template>
18
+
19
+ <!-- Option to add a new page -->
20
+ <b-dropdown-item data-test="add-page" @click="onAddPage">
21
+ <!-- Icon for adding a new page -->
22
+ <i class="fa fa-plus platform-dropdown-item-icon"></i>
23
+ <!-- Text for adding a new page -->
24
+ {{ $t("Create Page") }}
25
+ </b-dropdown-item>
26
+
27
+ <!-- Option to see all pages -->
28
+ <b-dropdown-item data-test="see-all-pages" @click="onSeeAllPages">
29
+ <!-- Icon for seeing all pages -->
30
+ <i class="fa fa-eye platform-dropdown-item-icon"></i>
31
+ <!-- Text for seeing all pages -->
32
+ {{ $t("See all pages") }}
33
+ </b-dropdown-item>
34
+
35
+ <!-- Divider between adding and viewing options -->
36
+ <b-dropdown-divider></b-dropdown-divider>
37
+
38
+ <!-- Dropdown items for selecting individual pages -->
39
+ <b-dropdown-item
40
+ v-for="(item, page) in data"
41
+ :key="page"
42
+ :data-test="'page-' + item.name"
43
+ :data-cy="'page-' + page"
44
+ @click="onClickPage(page)"
45
+ >
46
+ <!-- Display the name of the page -->
47
+ {{ item.name }}
48
+ </b-dropdown-item>
49
+ </b-dropdown>
50
+ </template>
51
+
52
+ <script>
53
+ /**
54
+ * Vue component for managing pages through a dropdown menu.
55
+ * @component
56
+ * @prop {Props} props - The component's props object.
57
+ */
58
+
59
+ export default {
60
+ /**
61
+ * The name of the component.
62
+ * @type {string}
63
+ */
64
+ name: "PagesDropdown",
65
+
66
+ /**
67
+ * The props that the component accepts.
68
+ * @type {Object}
69
+ * @property {PageItem[]} data - The array of page items to be displayed in the dropdown.
70
+ * Defaults to null.
71
+ */
72
+ props: {
73
+ data: {
74
+ type: Array,
75
+ default: null
76
+ },
77
+ boundary: {
78
+ type: String,
79
+ default: "viewport"
80
+ }
81
+ },
82
+
83
+ /**
84
+ * The methods available within the component.
85
+ */
86
+ methods: {
87
+ /**
88
+ * Handler for when the "Add Page" option is clicked.
89
+ * Emits the "addPage" event.
90
+ */
91
+ onAddPage() {
92
+ this.$emit("addPage");
93
+ },
94
+
95
+ /**
96
+ * Handler for when the "See All Pages" option is clicked.
97
+ * Emits the "seeAllPages" event.
98
+ */
99
+ onSeeAllPages() {
100
+ this.$emit("seeAllPages");
101
+ },
102
+
103
+ /**
104
+ * Handler for when a specific page is clicked.
105
+ * Emits the "clickPage" event with the selected page.
106
+ * @param {PageItem} page - The selected page item.
107
+ */
108
+ onClickPage(page) {
109
+ this.$emit("clickPage", page);
110
+ }
111
+ }
112
+ };
113
+ </script>
114
+
115
+ <style lang="scss" scoped>
116
+ // Platform btn style
117
+ .btn-platform {
118
+ background-color: #ffff;
119
+ color: #6a7888;
120
+ }
121
+ .platform-dropdown-item-icon {
122
+ // Style for the icons in dropdown items.
123
+ color: #1572c2;
124
+ }
125
+ </style>
@@ -36,7 +36,24 @@
36
36
  <script>
37
37
  export default {
38
38
  components: {},
39
- props: ["label", "value", "helper", "options"],
39
+ props: {
40
+ /**
41
+ * The label for the color select
42
+ */
43
+ label: {},
44
+ /**
45
+ * The value of the color select. eg. `alert alert-success`
46
+ */
47
+ value: {},
48
+ /**
49
+ * The helper text for the color select (not visible yet)
50
+ */
51
+ helper: {},
52
+ /**
53
+ * The options for the color select
54
+ */
55
+ options: {},
56
+ },
40
57
  data() {
41
58
  return {
42
59
  newColor: ""
@@ -0,0 +1,80 @@
1
+ <template>
2
+ <div class="container sortable-box">
3
+ <div class="row">
4
+ <div class="col-sm border rounded-lg p-0 mr-3 sortable-search-box">
5
+ <i class="fa fa-search sortable-search-icon"></i>
6
+ <input
7
+ id="search"
8
+ v-model="search"
9
+ class="form-control border-0 shadow-none px-0"
10
+ :placeholder="$t('Search here')"
11
+ data-test="search"
12
+ />
13
+ </div>
14
+ <div>
15
+ <button
16
+ type="button"
17
+ class="btn sortable-btn-new"
18
+ @click="$emit('add-page', $event)"
19
+ >
20
+ <i class="fa fa-plus"></i>
21
+ </button>
22
+ </div>
23
+ </div>
24
+
25
+ <SortableList
26
+ :items="items"
27
+ :filtered-items="filteredItems"
28
+ @ordered="$emit('ordered', $event)"
29
+ @item-edit="$emit('item-edit', $event)"
30
+ @item-delete="$emit('item-delete', $event)"
31
+ />
32
+ </div>
33
+ </template>
34
+
35
+ <script>
36
+ import SortableList from './sortableList/SortableList.vue'
37
+
38
+ export default {
39
+ name: 'Sortable',
40
+ components: {
41
+ SortableList
42
+ },
43
+ props: {
44
+ items: { type: Array, required: true },
45
+ filterKey: { type: String, required: true },
46
+ },
47
+ data() {
48
+ return {
49
+ search: "",
50
+ filteredItems: [...this.items],
51
+ };
52
+ },
53
+ watch: {
54
+ search(value) {
55
+ this.filteredItems = this.filterItems(value, this.items);
56
+ },
57
+ items: {
58
+ handler(newItems) {
59
+ this.filteredItems = [...newItems];
60
+
61
+ if (this.search.length > 0) {
62
+ this.filteredItems = this.filterItems(this.search, newItems);
63
+ }
64
+ },
65
+ deep: true,
66
+ },
67
+ },
68
+ methods: {
69
+ clearSearch(value) {
70
+ return value.trim().toLowerCase();
71
+ },
72
+ filterItems(searchValue, items) {
73
+ const cleanSearch = this.clearSearch(searchValue);
74
+ return items.filter((item) => item[this.filterKey].toLowerCase().includes(cleanSearch));
75
+ },
76
+ },
77
+ }
78
+ </script>
79
+
80
+ <style lang="scss" scoped src="./sortable.scss"></style>
@@ -0,0 +1,25 @@
1
+ .sortable {
2
+ &-box {
3
+ font-family: "Open Sans", sans-serif !important;
4
+ }
5
+
6
+ &-search-box {
7
+ display: flex;
8
+ align-items: center;
9
+ border-color: #cdddee !important;
10
+ }
11
+
12
+ &-search-icon {
13
+ margin: {
14
+ left: 16px;
15
+ right: 8px;
16
+ }
17
+ color: #6A7888;
18
+ }
19
+
20
+ &-btn-new {
21
+ background: #1572C2;
22
+ color: #ffffff;
23
+ }
24
+ }
25
+