@processmaker/screen-builder 2.83.11 → 2.84.1

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,233 @@
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>
@@ -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", this.data[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>
@@ -47,6 +47,7 @@ import FormListTable from "./renderer/form-list-table.vue";
47
47
  import FormAnalyticsChart from "./renderer/form-analytics-chart.vue";
48
48
  import accordions from "@/components/accordions";
49
49
  import VariableNameGenerator from "@/components/VariableNameGenerator";
50
+ import "../assets/css/tabs.css";
50
51
 
51
52
  const rendererComponents = {
52
53
  ...renderer,
@@ -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
+
@@ -0,0 +1,141 @@
1
+ <template>
2
+ <div class="row mt-3">
3
+ <div class="col p-0 border rounded-lg sortable-list">
4
+ <div class="sortable-list-header">
5
+ <div class="sortable-item-icon"></div>
6
+ <div class="sortable-list-title">PAGE NAME</div>
7
+ </div>
8
+ <div class="sortable-container" @dragover="dragOver">
9
+ <div
10
+ v-for="(item, index) in sortedItems"
11
+ :key="index"
12
+ :data-order="item.order"
13
+ :data-test="`item-${item.order}`"
14
+ :title="item.name"
15
+ draggable="true"
16
+ @dragstart="(event) => dragStart(event, item.order)"
17
+ @dragenter="(event) => dragEnter(event, item.order)"
18
+ @dragend="dragEnd"
19
+ class="sortable-item sortable-draggable"
20
+ >
21
+ <div class="sortable-item-icon">
22
+ <i class="fas fa-bars"></i>
23
+ </div>
24
+ <div class="rounded sortable-item-name">
25
+ <b-form-input
26
+ v-if="editRowIndex === index"
27
+ v-model="item.name"
28
+ type="text"
29
+ autofocus
30
+ @blur.stop="onBlur()"
31
+ />
32
+ <span v-else>{{ item.name }}</span>
33
+ </div>
34
+ <div class="border rounded-lg sortable-item-action">
35
+ <button class="btn" @click.stop="onClick(item, index)">
36
+ <i class="fas fa-edit"></i>
37
+ </button>
38
+ <div class="sortable-item-vr"></div>
39
+ <button class="btn" @click="$emit('item-delete', item)">
40
+ <i class="fas fa-trash-alt"></i>
41
+ </button>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </template>
48
+
49
+ <script>
50
+ export default {
51
+ name: 'SortableList',
52
+ props: {
53
+ items: { type: Array, required: true },
54
+ filteredItems: { type: Array, required: true },
55
+ },
56
+ data() {
57
+ return {
58
+ draggedItem: 0,
59
+ draggedOverItem: 0,
60
+ editRowIndex: null,
61
+ };
62
+ },
63
+ computed: {
64
+ sortedItems() {
65
+ const sortedItems = [...this.filteredItems].sort(
66
+ (a, b) => a.order - b.order
67
+ );
68
+ return sortedItems;
69
+ }
70
+ },
71
+ methods: {
72
+ onBlur() {
73
+ this.editRowIndex = -1;
74
+ },
75
+ onClick(item, index) {
76
+ if (this.editRowIndex === -1 || this.editRowIndex === index) {
77
+ this.editRowIndex = null;
78
+ return;
79
+ }
80
+ this.editRowIndex = index;
81
+ this.$emit("item-edit", item);
82
+ },
83
+ dragStart(event, order) {
84
+ // disable edit mode
85
+ this.editRowIndex = null;
86
+ this.draggedItem = order;
87
+ // add dragging class to the element
88
+ event.target.classList.add('dragging');
89
+ },
90
+ dragEnter(event, order) {
91
+ this.draggedOverItem = order;
92
+ },
93
+ dragEnd(event) {
94
+ // remove dragging class from the element
95
+ event.target.classList.remove('dragging');
96
+
97
+ // get the index of the dragged item and the dragged over item
98
+ const itemsSortedClone = [...this.items].sort(
99
+ (a, b) => a.order - b.order
100
+ );
101
+ const draggedItemIndex = itemsSortedClone.findIndex(
102
+ (item) => item.order === this.draggedItem
103
+ );
104
+ const draggedOverItemIndex = itemsSortedClone.findIndex(
105
+ (item) => item.order === this.draggedOverItem
106
+ );
107
+
108
+ if (draggedItemIndex !== draggedOverItemIndex) {
109
+ // get the order of the dragged over item
110
+ const tempOrder = itemsSortedClone[draggedOverItemIndex].order;
111
+ // set the increment
112
+ const increment = this.draggedItem > this.draggedOverItem ? 1 : -1;
113
+
114
+ // update the order of the items between the dragged item and the dragged over item
115
+ if (draggedItemIndex < draggedOverItemIndex) {
116
+ for (let i = draggedItemIndex + 1; i <= draggedOverItemIndex; i++) {
117
+ const orderAux = itemsSortedClone[i].order;
118
+ itemsSortedClone[i].order = orderAux + increment;
119
+ }
120
+
121
+ itemsSortedClone[draggedItemIndex].order = tempOrder;
122
+ } else if (draggedItemIndex > draggedOverItemIndex) {
123
+ for (let i = draggedOverItemIndex; i <= draggedItemIndex - 1; i++) {
124
+ const orderAux = itemsSortedClone[i].order;
125
+ itemsSortedClone[i].order = orderAux + increment;
126
+ }
127
+
128
+ itemsSortedClone[draggedItemIndex].order = tempOrder;
129
+ }
130
+ }
131
+
132
+ this.$emit('ordered', itemsSortedClone);
133
+ },
134
+ dragOver(event) {
135
+ event.preventDefault();
136
+ },
137
+ },
138
+ }
139
+ </script>
140
+
141
+ <style lang="scss" scoped src="./sortableList.scss"></style>