@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itfin/components",
3
- "version": "1.3.21",
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",
@@ -18,6 +18,7 @@ body[data-theme="dark"] {
18
18
  background-color: $dark-input-bg;
19
19
  border-color: $dark-input-bg;
20
20
  color: $dark-body-color;
21
+ box-shadow: 0 2px 10px rgba(255,255,255,.05);
21
22
 
22
23
  &:focus, &:active {
23
24
  background-color: #3d3d3d;
@@ -28,6 +28,7 @@
28
28
 
29
29
  .form-control {
30
30
  min-height: 2.5rem;
31
+ box-shadow: 0 2px 10px rgba(0,0,0,.05);
31
32
  }
32
33
 
33
34
  .color-project-tnm {
@@ -8,7 +8,7 @@
8
8
  }
9
9
  @font-face {
10
10
  font-family: "Cascadia";
11
- src: url("https://unpkg.com/@excalidraw/excalidraw@undefined/dist/excalidraw-assets/Cascadia.woff2");
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('@undefined', '@0.16.1'); // глючить версія в excalidraw
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 row">
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="col-3">
12
- <select class="form-control input-sm" v-model="operator">
13
- <option v-for="option in options.operators" :value="option.id">
14
- {{option.name}}
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 class="and-or-template col-12" :class="first ? 'and-or-first' : '' ">
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" ref="rules"
26
- :options="options" :key="rule" @delete-rule="deleteRule(index)">
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: { itfButton, Rule }
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 />