@itfin/components 1.3.21 → 1.3.22
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.
- package/package.json +3 -1
- package/src/assets/scss/_dark-theme.scss +1 -0
- package/src/assets/scss/main.scss +1 -0
- package/src/components/editor/Editor.vue +6 -1
- package/src/components/editor/tools/bpmn/bpmn.js +187 -0
- package/src/components/editor/tools/bpmn/bpmn.scss +63 -0
- package/src/components/editor/tools/drawing/drawing.js +1 -1
- package/src/components/filter/FilterBadge.vue +61 -0
- package/src/components/filter/Rule.vue +27 -5
- package/src/components/filter/RuleGroup.vue +24 -14
- package/src/components/filter/constants.js +19 -0
- package/src/components/filter/index.stories.js +7 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itfin/components",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.22",
|
|
4
4
|
"author": "Vitalii Savchuk <esvit666@gmail.com>",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"serve": "vue-cli-service serve",
|
|
@@ -41,12 +41,14 @@
|
|
|
41
41
|
"@vue/composition-api": "^1.7.1",
|
|
42
42
|
"air-datepicker": "^3.3.5",
|
|
43
43
|
"bootstrap": "^5.3.x",
|
|
44
|
+
"bpmn-js": "^17.0.2",
|
|
44
45
|
"core-js": "^3.7.0",
|
|
45
46
|
"debug": "^4.2.0",
|
|
46
47
|
"intersection-observer": "^0.12.2",
|
|
47
48
|
"lodash": "^4.17.20",
|
|
48
49
|
"luxon": "^3.3.0",
|
|
49
50
|
"pdfjs-dist": "^2.10.377",
|
|
51
|
+
"storybook-dark-mode": "^3.0.3",
|
|
50
52
|
"tippy.js": "^6.3.2",
|
|
51
53
|
"vue-imask": "^6.6.3",
|
|
52
54
|
"vue-property-decorator": "^9.1.2",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
}
|
|
9
9
|
@font-face {
|
|
10
10
|
font-family: "Cascadia";
|
|
11
|
-
src: url("https://unpkg.com/@excalidraw/excalidraw@
|
|
11
|
+
src: url("https://unpkg.com/@excalidraw/excalidraw@0.16.1/dist/excalidraw-assets/Cascadia.woff2");
|
|
12
12
|
}
|
|
13
13
|
.editor {
|
|
14
14
|
&.readonly {
|
|
@@ -67,6 +67,7 @@ import AttachesTool from '@editorjs/attaches';
|
|
|
67
67
|
import NestedList from '@editorjs/nested-list';
|
|
68
68
|
import editorjsCodecup from './tools/highlightcode/codecup';
|
|
69
69
|
import Drawing from './tools/drawing/drawing';
|
|
70
|
+
import Bpmn from './tools/bpmn/bpmn';
|
|
70
71
|
import cloneDeep from 'lodash/cloneDeep';
|
|
71
72
|
|
|
72
73
|
export default @Component({
|
|
@@ -133,6 +134,10 @@ class itfEditor extends Vue {
|
|
|
133
134
|
inlineToolbar: true,
|
|
134
135
|
class: Drawing
|
|
135
136
|
},
|
|
137
|
+
bpmn: {
|
|
138
|
+
inlineToolbar: true,
|
|
139
|
+
class: Bpmn
|
|
140
|
+
},
|
|
136
141
|
// warning: Warning,
|
|
137
142
|
table: {
|
|
138
143
|
inlineToolbar: true,
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import './bpmn.scss';
|
|
2
|
+
import cloneDeep from 'lodash/cloneDeep';
|
|
3
|
+
import 'bpmn-js/dist/assets/bpmn-js.css';
|
|
4
|
+
import 'bpmn-js/dist/assets/diagram-js.css';
|
|
5
|
+
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
|
|
6
|
+
|
|
7
|
+
const icon = '<svg height="24" width="24" viewBox="0 0 2048 2048" xmlns="http://www.w3.org/2000/svg"><path d="m96-593.63783v1280h1856v-1280zm1756 97.69925v1091.54297h-1372v-1091.54103l1372-.002zm-1660 .002 192-.00021v1091.54108l-192 .00021z" transform="translate(0 995.63783)"/></svg>';
|
|
8
|
+
const xml = '<?xml version="1.0" encoding="UTF-8"?>\n' +
|
|
9
|
+
'<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd" id="sample-diagram" targetNamespace="http://bpmn.io/schema/bpmn">\n' +
|
|
10
|
+
' <bpmn2:process id="Process_1" isExecutable="false">\n' +
|
|
11
|
+
' <bpmn2:startEvent id="StartEvent_1"/>\n' +
|
|
12
|
+
' </bpmn2:process>\n' +
|
|
13
|
+
' <bpmndi:BPMNDiagram id="BPMNDiagram_1">\n' +
|
|
14
|
+
' <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">\n' +
|
|
15
|
+
' <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">\n' +
|
|
16
|
+
' <dc:Bounds height="36.0" width="36.0" x="412.0" y="240.0"/>\n' +
|
|
17
|
+
' </bpmndi:BPMNShape>\n' +
|
|
18
|
+
' </bpmndi:BPMNPlane>\n' +
|
|
19
|
+
' </bpmndi:BPMNDiagram>\n' +
|
|
20
|
+
'</bpmn2:definitions>';
|
|
21
|
+
|
|
22
|
+
export default class Bpmn {
|
|
23
|
+
data = null;
|
|
24
|
+
readOnly = false;
|
|
25
|
+
|
|
26
|
+
static get sanitize(){
|
|
27
|
+
return {
|
|
28
|
+
svg: true,
|
|
29
|
+
files: true,
|
|
30
|
+
caption: {} // only tags from Inline Toolbar
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Allow to use this tool in read only mode
|
|
36
|
+
*/
|
|
37
|
+
static get isReadOnlySupported() {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
constructor({ data, readOnly }){
|
|
42
|
+
this.data = data;
|
|
43
|
+
this.readOnly = readOnly;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static get toolbox() {
|
|
47
|
+
return {
|
|
48
|
+
title: 'BPMN',
|
|
49
|
+
icon
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
render(){
|
|
54
|
+
const wrapper = createWrapper(this.data, this.readOnly);
|
|
55
|
+
if (!this.readOnly) {
|
|
56
|
+
wrapper.classList.remove('readonly');
|
|
57
|
+
wrapper.addEventListener('dblclick', async () => {
|
|
58
|
+
const modal = document.createElement('div');
|
|
59
|
+
modal.classList.add('itf-modal');
|
|
60
|
+
modal.classList.add('modal');
|
|
61
|
+
modal.classList.add('fade');
|
|
62
|
+
modal.classList.add('editorjs-bpmn-modal');
|
|
63
|
+
const modalDialog = document.createElement('div');
|
|
64
|
+
modalDialog.classList.add('modal-dialog');
|
|
65
|
+
modalDialog.classList.add('modal-fullscreen');
|
|
66
|
+
const modalContent = document.createElement('div');
|
|
67
|
+
modalContent.classList.add('modal-content');
|
|
68
|
+
const modalHeader = document.createElement('div');
|
|
69
|
+
modalHeader.classList.add('modal-header');
|
|
70
|
+
const modalTitle = document.createElement('h5');
|
|
71
|
+
modalTitle.classList.add('modal-title');
|
|
72
|
+
modalTitle.textContent = 'BPMN Editor';
|
|
73
|
+
modalHeader.appendChild(modalTitle);
|
|
74
|
+
const btnHolder = document.createElement('div');
|
|
75
|
+
btnHolder.classList.add('d-flex');
|
|
76
|
+
const modalSaveBtn = document.createElement('button');
|
|
77
|
+
modalSaveBtn.classList.add('btn');
|
|
78
|
+
modalSaveBtn.classList.add('btn-primary');
|
|
79
|
+
modalSaveBtn.classList.add('itf-button');
|
|
80
|
+
modalSaveBtn.textContent = 'Save & Close';
|
|
81
|
+
const modalCancelBtn = document.createElement('button');
|
|
82
|
+
modalCancelBtn.classList.add('btn');
|
|
83
|
+
modalCancelBtn.classList.add('btn-secondary');
|
|
84
|
+
modalCancelBtn.classList.add('itf-button');
|
|
85
|
+
modalCancelBtn.classList.add('me-2');
|
|
86
|
+
modalCancelBtn.textContent = 'Cancel';
|
|
87
|
+
btnHolder.appendChild(modalCancelBtn);
|
|
88
|
+
btnHolder.appendChild(modalSaveBtn);
|
|
89
|
+
modalHeader.appendChild(modalTitle);
|
|
90
|
+
modalHeader.appendChild(btnHolder);
|
|
91
|
+
modalContent.appendChild(modalHeader);
|
|
92
|
+
const modalBody = document.createElement('div');
|
|
93
|
+
modalBody.classList.add('modal-body');
|
|
94
|
+
const frame = document.createElement('div');
|
|
95
|
+
modalBody.appendChild(frame);
|
|
96
|
+
modalContent.appendChild(modalBody);
|
|
97
|
+
modalDialog.appendChild(modalContent);
|
|
98
|
+
modal.appendChild(modalDialog);
|
|
99
|
+
document.body.appendChild(modal);
|
|
100
|
+
const {default: Modal} = await import('../../../modal/modalSrc');
|
|
101
|
+
const modalEl = new Modal(modal, {context: document.body, backdrop: 'static'});
|
|
102
|
+
modalEl.show();
|
|
103
|
+
modal.addEventListener('hidden.bs.modal', () => {
|
|
104
|
+
document.body.removeChild(modal);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
frame.classList.add('modal-body-content');
|
|
108
|
+
|
|
109
|
+
let modeler;
|
|
110
|
+
setTimeout(async () => {
|
|
111
|
+
const { default: BpmnModeler } = await import('bpmn-js/dist/bpmn-modeler.production.min');
|
|
112
|
+
modeler = new BpmnModeler({
|
|
113
|
+
container: frame,
|
|
114
|
+
keyboard: {
|
|
115
|
+
bindTo: window
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
await modeler.importXML(this.data?.xml || xml);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.error('error loading BPMN 2.0 XML', err);
|
|
123
|
+
}
|
|
124
|
+
}, 500)
|
|
125
|
+
|
|
126
|
+
modalSaveBtn.addEventListener('click', async () => {
|
|
127
|
+
this.data = {
|
|
128
|
+
svg: (await modeler.saveSVG())?.svg,
|
|
129
|
+
xml: (await modeler.saveXML({ format: true }))?.xml
|
|
130
|
+
};
|
|
131
|
+
if (this.data.svg) {
|
|
132
|
+
createSvg(wrapper, this.data, this.readOnly);
|
|
133
|
+
}
|
|
134
|
+
modalEl.hide();
|
|
135
|
+
});
|
|
136
|
+
modalCancelBtn.addEventListener('click', () => {
|
|
137
|
+
modalEl.hide();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
} else {
|
|
141
|
+
wrapper.classList.add('readonly');
|
|
142
|
+
}
|
|
143
|
+
return wrapper;
|
|
144
|
+
|
|
145
|
+
function createWrapper(data, readOnly) {
|
|
146
|
+
const wrapper = document.createElement('div');
|
|
147
|
+
wrapper.classList.add('editorjs-bpmn');
|
|
148
|
+
if (data.svg) {
|
|
149
|
+
wrapper.classList.remove('empty');
|
|
150
|
+
createSvg(wrapper, data, readOnly);
|
|
151
|
+
} else if (!readOnly) {
|
|
152
|
+
wrapper.classList.add('empty');
|
|
153
|
+
wrapper.innerHTML = `<div class="text-muted icon me-2">${icon}</div><div class="text-muted text">Double click to start diagramming your business processes.</div>`;
|
|
154
|
+
}
|
|
155
|
+
return wrapper;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function createSvg(wrapper, { svg, caption }, readOnly) {
|
|
159
|
+
const svgContainer = document.createElement('div');
|
|
160
|
+
svgContainer.innerHTML = svg;
|
|
161
|
+
|
|
162
|
+
const divSvg = document.createElement('div');
|
|
163
|
+
wrapper.innerHTML = '';
|
|
164
|
+
wrapper.classList.remove('empty');
|
|
165
|
+
wrapper.appendChild(divSvg);
|
|
166
|
+
divSvg.appendChild(svgContainer);
|
|
167
|
+
|
|
168
|
+
const captionEl = document.createElement('div');
|
|
169
|
+
captionEl.contentEditable = !readOnly;
|
|
170
|
+
captionEl.classList.add('editorjs-bpmn-caption');
|
|
171
|
+
captionEl.textContent = caption;
|
|
172
|
+
if (!readOnly) {
|
|
173
|
+
captionEl.setAttribute('data-text', 'Caption');
|
|
174
|
+
captionEl.addEventListener('dblclick', (e) => {
|
|
175
|
+
e.stopPropagation();
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
wrapper.appendChild(captionEl);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
save(blockContent){
|
|
182
|
+
const caption = blockContent.querySelector('[contenteditable]');
|
|
183
|
+
const data = cloneDeep(this.data);
|
|
184
|
+
data.caption = caption ? caption.innerHTML : '';
|
|
185
|
+
return data;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
.editorjs-bpmn {
|
|
2
|
+
--editorjs-drawing-border: #dcdfe6;
|
|
3
|
+
|
|
4
|
+
&.empty {
|
|
5
|
+
border: 1px dashed var(--editorjs-drawing-border);
|
|
6
|
+
min-height: 150px;
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
}
|
|
11
|
+
position: relative;
|
|
12
|
+
z-index: 0;
|
|
13
|
+
margin-bottom: 10px;
|
|
14
|
+
border-radius: 5px;
|
|
15
|
+
height: max-content !important;
|
|
16
|
+
|
|
17
|
+
&:not(.readonly) {
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
|
|
20
|
+
&:hover {
|
|
21
|
+
outline: 3px solid var(--editorjs-drawing-border);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.editorjs-bpmn-caption {
|
|
26
|
+
margin-top: 1rem;
|
|
27
|
+
text-align: center;
|
|
28
|
+
&:empty:not(:focus):before {
|
|
29
|
+
content: attr(data-text);
|
|
30
|
+
opacity: .5;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
svg {
|
|
34
|
+
width: 100%;
|
|
35
|
+
height: auto;
|
|
36
|
+
user-select: none;
|
|
37
|
+
pointer-events: none;
|
|
38
|
+
}
|
|
39
|
+
.icon {
|
|
40
|
+
width: 48px;
|
|
41
|
+
height: 48px;
|
|
42
|
+
|
|
43
|
+
path {
|
|
44
|
+
fill: currentColor;
|
|
45
|
+
stroke: none;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
.text {
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.editorjs-bpmn-modal .modal-body {
|
|
54
|
+
padding: 0;
|
|
55
|
+
display: flex;
|
|
56
|
+
}
|
|
57
|
+
.editorjs-bpmn-modal .modal-body-content {
|
|
58
|
+
flex: 1;
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
justify-content: center;
|
|
62
|
+
align-items: center;
|
|
63
|
+
}
|
|
@@ -130,7 +130,7 @@ export default class Drawing {
|
|
|
130
130
|
|
|
131
131
|
function createSvg(wrapper, { svg, caption }, readOnly) {
|
|
132
132
|
const svgContainer = document.createElement('div');
|
|
133
|
-
svgContainer.innerHTML = svg.replace(
|
|
133
|
+
svgContainer.innerHTML = svg.replace(/@undefined/g, '@0.16.1'); // глючить версія в excalidraw
|
|
134
134
|
|
|
135
135
|
const divSvg = document.createElement('div');
|
|
136
136
|
wrapper.innerHTML = '';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
<span class="border rounded d-inline-flex ps-3 pe-1 gap-1 align-items-center">
|
|
5
|
+
Status
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
<select class="form-control input-sm filter-operation" v-model="operator">
|
|
9
|
+
<option v-for="option in operators" :value="option.id">
|
|
10
|
+
{{option.title}}
|
|
11
|
+
</option>
|
|
12
|
+
</select>
|
|
13
|
+
|
|
14
|
+
<div>
|
|
15
|
+
asda
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<itf-button icon>
|
|
19
|
+
<itf-icon name="close" />
|
|
20
|
+
</itf-button>
|
|
21
|
+
</span>
|
|
22
|
+
|
|
23
|
+
</template>
|
|
24
|
+
<style>
|
|
25
|
+
.form-control.filter-operation {
|
|
26
|
+
background: transparent;
|
|
27
|
+
border: none;
|
|
28
|
+
box-shadow: none;
|
|
29
|
+
border-radius: 0;
|
|
30
|
+
|
|
31
|
+
&:focus, &:active {
|
|
32
|
+
box-shadow: none;
|
|
33
|
+
outline: none;
|
|
34
|
+
}
|
|
35
|
+
&:hover {
|
|
36
|
+
background-color: var(--bs-tertiary-bg);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
</style>
|
|
40
|
+
<script>
|
|
41
|
+
import { Component, Prop, Watch, Vue } from 'vue-property-decorator';
|
|
42
|
+
import {getOperatorsByType} from "./constants";
|
|
43
|
+
import itfButton from '../button/Button.vue';
|
|
44
|
+
import itfIcon from '../icon/Icon.vue';
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
export default @Component({
|
|
48
|
+
name: 'itfFilterBadge',
|
|
49
|
+
components: {
|
|
50
|
+
itfButton,
|
|
51
|
+
itfIcon
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
class itfFilterBadge extends Vue {
|
|
55
|
+
@Prop({ type: String, required: true }) type;
|
|
56
|
+
|
|
57
|
+
get operators() {
|
|
58
|
+
return getOperatorsByType(this.type);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="form-group and-or-rule
|
|
2
|
+
<div class="form-group and-or-rule d-flex">
|
|
3
3
|
<div class="col-3">
|
|
4
4
|
<select class="form-control input-sm" v-model="key">
|
|
5
5
|
<option v-for="option in options.keys" :value="option.id">
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
</select>
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
|
-
<div class="
|
|
12
|
-
<select class="form-control input-sm" v-model="operator">
|
|
13
|
-
<option v-for="option in
|
|
14
|
-
{{option.
|
|
11
|
+
<div style="width: 150px" class="px-2">
|
|
12
|
+
<select class="form-control input-sm input-filter" v-model="operator">
|
|
13
|
+
<option v-for="option in operators" :value="option.id">
|
|
14
|
+
{{option.title}}
|
|
15
15
|
</option>
|
|
16
16
|
</select>
|
|
17
17
|
</div>
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
</div>
|
|
28
28
|
</template>
|
|
29
29
|
<style>
|
|
30
|
+
.input-filter {
|
|
31
|
+
background: var(--bs-primary-bg-subtle);
|
|
32
|
+
}
|
|
30
33
|
.and-or-rule {
|
|
31
34
|
position: relative;
|
|
32
35
|
margin-left: 15px !important;
|
|
@@ -63,12 +66,27 @@ import { Component, Prop, Watch, Vue } from "vue-property-decorator";
|
|
|
63
66
|
import itfButton from '../button/Button.vue';
|
|
64
67
|
import itfIcon from '../icon/Icon.vue';
|
|
65
68
|
|
|
69
|
+
const operations = [
|
|
70
|
+
{
|
|
71
|
+
type: 'string',
|
|
72
|
+
operators: [
|
|
73
|
+
{ id: 'eq', title: 'equal' },
|
|
74
|
+
{ id: 'notEq', title: 'not equal' },
|
|
75
|
+
{ id: 'contains', title: 'contains' },
|
|
76
|
+
{ id: 'noContains', title: 'not contains' },
|
|
77
|
+
{ id: 'startsWith', title: 'starts with' },
|
|
78
|
+
{ id: 'endsWith', title: 'ends with' }
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
|
|
66
83
|
export default @Component({
|
|
67
84
|
name: 'itfRule',
|
|
68
85
|
components: { itfButton, itfIcon }
|
|
69
86
|
})
|
|
70
87
|
class itfRule extends Vue {
|
|
71
88
|
@Prop() options;
|
|
89
|
+
@Prop() type;
|
|
72
90
|
|
|
73
91
|
key = -99;
|
|
74
92
|
|
|
@@ -76,6 +94,10 @@ class itfRule extends Vue {
|
|
|
76
94
|
|
|
77
95
|
value = '';
|
|
78
96
|
|
|
97
|
+
get operators() {
|
|
98
|
+
return operations.find(op => op.type === this.type)?.operators || [];
|
|
99
|
+
}
|
|
100
|
+
|
|
79
101
|
@Watch('options.keys.options')
|
|
80
102
|
onOptionUpdated() {
|
|
81
103
|
this.key = -99;
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div>
|
|
3
|
+
<itf-dropdown>
|
|
4
|
+
<template #button>
|
|
5
|
+
<itf-icon name="filter" />
|
|
6
|
+
Filter
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<div class="dropdown-item" @click="addGroup">Add group</div>
|
|
10
|
+
<div class="dropdown-item" @click="addRule">Add condition</div>
|
|
11
|
+
</itf-dropdown>
|
|
3
12
|
<div class="form-group and-or-top col-12">
|
|
4
13
|
<div class="col-5" style="padding: 0">
|
|
5
14
|
<button class="btn btn-xs btn-purple-outline btn-radius"
|
|
@@ -16,14 +25,16 @@
|
|
|
16
25
|
<itf-button v-if="!first" class="btn btn-xs btn-purple pull-right" @click.prevent="deleteSelf()">
|
|
17
26
|
<i class="fa fa-fw fa-close"></i>
|
|
18
27
|
</itf-button>
|
|
19
|
-
<button class="btn btn-xs btn-purple pull-right" @click.prevent="addGroup"> + ( group ) </button>
|
|
20
|
-
<button class="btn btn-xs btn-purple add-rule pull-right" @click.prevent="addRule"> + add </button>
|
|
21
28
|
</div>
|
|
22
29
|
</div>
|
|
23
30
|
|
|
24
31
|
<rule
|
|
25
|
-
v-for="(rule, index) in rules"
|
|
26
|
-
|
|
32
|
+
v-for="(rule, index) in rules"
|
|
33
|
+
ref="rules"
|
|
34
|
+
:options="options"
|
|
35
|
+
:key="rule"
|
|
36
|
+
type="string"
|
|
37
|
+
@delete-rule="deleteRule(index)">
|
|
27
38
|
</rule>
|
|
28
39
|
|
|
29
40
|
<itf-rule-group
|
|
@@ -36,16 +47,8 @@
|
|
|
36
47
|
</template>
|
|
37
48
|
<style>
|
|
38
49
|
.and-or-template {
|
|
39
|
-
padding: 8px;
|
|
40
50
|
position: relative;
|
|
41
|
-
border-radius: 3px;
|
|
42
|
-
border: 1px solid #6d77b8;
|
|
43
|
-
border-top: 3px solid #d2d6de;
|
|
44
51
|
margin-bottom: 20px;
|
|
45
|
-
/* width: 100%; */
|
|
46
|
-
box-shadow: 0 1px 1px rgba(0,0,0,0.1);
|
|
47
|
-
border-top-color: #6d77b8;
|
|
48
|
-
background-color: rgba(255, 255, 255, 0.9);
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
.and-or-template:before,
|
|
@@ -91,11 +94,18 @@
|
|
|
91
94
|
<script>
|
|
92
95
|
import { Component, Prop, Watch, Vue } from 'vue-property-decorator';
|
|
93
96
|
import itfButton from '../button/Button.vue';
|
|
97
|
+
import itfIcon from '../icon/Icon.vue';
|
|
98
|
+
import itfDropdown from '../dropdown/Dropdown.vue';
|
|
94
99
|
import Rule from './Rule'
|
|
95
100
|
|
|
96
101
|
export default @Component({
|
|
97
102
|
name: 'itfRuleGroup',
|
|
98
|
-
components: {
|
|
103
|
+
components: {
|
|
104
|
+
itfIcon,
|
|
105
|
+
itfButton,
|
|
106
|
+
itfDropdown,
|
|
107
|
+
Rule
|
|
108
|
+
}
|
|
99
109
|
})
|
|
100
110
|
class itfRule extends Vue {
|
|
101
111
|
@Prop({ type: Object, required: true }) options;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export function getOperatorsByType(type) {
|
|
4
|
+
const operations = [
|
|
5
|
+
{
|
|
6
|
+
type: 'string',
|
|
7
|
+
operators: [
|
|
8
|
+
{ id: 'eq', title: 'equal' },
|
|
9
|
+
{ id: 'notEq', title: 'not equal' },
|
|
10
|
+
{ id: 'contains', title: 'contains' },
|
|
11
|
+
{ id: 'noContains', title: 'not contains' },
|
|
12
|
+
{ id: 'startsWith', title: 'starts with' },
|
|
13
|
+
{ id: 'endsWith', title: 'ends with' }
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
return operations.find((operation) => operation.type === type).operators;
|
|
19
|
+
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { storiesOf } from '@storybook/vue';
|
|
2
2
|
import itfRule from './Rule.vue';
|
|
3
3
|
import itfRuleGroup from './RuleGroup.vue';
|
|
4
|
+
import itfFilterBadge from './FilterBadge.vue';
|
|
4
5
|
|
|
5
6
|
storiesOf('Common', module)
|
|
6
7
|
.add('Filter', () => ({
|
|
7
8
|
components: {
|
|
8
9
|
itfRule,
|
|
9
|
-
itfRuleGroup
|
|
10
|
+
itfRuleGroup,
|
|
11
|
+
itfFilterBadge
|
|
10
12
|
},
|
|
11
13
|
data() {
|
|
12
14
|
return {
|
|
@@ -62,6 +64,10 @@ storiesOf('Common', module)
|
|
|
62
64
|
|
|
63
65
|
</pre>
|
|
64
66
|
|
|
67
|
+
|
|
68
|
+
<itf-filter-badge type="string" />
|
|
69
|
+
|
|
70
|
+
|
|
65
71
|
<h2>Example</h2>
|
|
66
72
|
|
|
67
73
|
<itf-rule-group :options="options" first />
|