@platforma-sdk/ui-vue 1.28.1 → 1.28.2

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/lib.js +8290 -7910
  3. package/dist/lib.js.map +1 -1
  4. package/dist/lib.umd.cjs +18 -18
  5. package/dist/lib.umd.cjs.map +1 -1
  6. package/dist/src/components/BlockLayout.vue.d.ts +1 -1
  7. package/dist/src/components/BlockLayout.vue.d.ts.map +1 -1
  8. package/dist/src/components/PlAgDataTable/PlAgDataTable.vue.d.ts +2 -2
  9. package/dist/src/plugins/Monetization/LimitCard.vue.d.ts +10 -0
  10. package/dist/src/plugins/Monetization/LimitCard.vue.d.ts.map +1 -0
  11. package/dist/src/plugins/Monetization/MonetizationSidebar.vue.d.ts +3 -0
  12. package/dist/src/plugins/Monetization/MonetizationSidebar.vue.d.ts.map +1 -0
  13. package/dist/src/plugins/Monetization/RunStatus.vue.d.ts +6 -0
  14. package/dist/src/plugins/Monetization/RunStatus.vue.d.ts.map +1 -0
  15. package/dist/src/plugins/Monetization/UserCabinetCard.vue.d.ts +6 -0
  16. package/dist/src/plugins/Monetization/UserCabinetCard.vue.d.ts.map +1 -0
  17. package/dist/src/plugins/Monetization/index.d.ts +3 -0
  18. package/dist/src/plugins/Monetization/index.d.ts.map +1 -0
  19. package/dist/src/plugins/Monetization/useButtonTarget.d.ts +2 -0
  20. package/dist/src/plugins/Monetization/useButtonTarget.d.ts.map +1 -0
  21. package/dist/src/plugins/Monetization/useInfo.d.ts +21 -0
  22. package/dist/src/plugins/Monetization/useInfo.d.ts.map +1 -0
  23. package/dist/src/plugins/Monetization/validation.d.ts +224 -0
  24. package/dist/src/plugins/Monetization/validation.d.ts.map +1 -0
  25. package/dist/style.css +1 -1
  26. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  27. package/package.json +2 -2
  28. package/src/components/BlockLayout.vue +3 -0
  29. package/src/plugins/.gitkeep +0 -0
  30. package/src/plugins/Monetization/LimitCard.vue +162 -0
  31. package/src/plugins/Monetization/MonetizationSidebar.vue +241 -0
  32. package/src/plugins/Monetization/RunStatus.vue +79 -0
  33. package/src/plugins/Monetization/UserCabinetCard.vue +195 -0
  34. package/src/plugins/Monetization/index.ts +5 -0
  35. package/src/plugins/Monetization/useButtonTarget.ts +23 -0
  36. package/src/plugins/Monetization/useInfo.ts +42 -0
  37. package/src/plugins/Monetization/validation.ts +53 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/ui-vue",
3
- "version": "1.28.1",
3
+ "version": "1.28.2",
4
4
  "type": "module",
5
5
  "main": "dist/lib.umd.cjs",
6
6
  "module": "dist/lib.js",
@@ -23,7 +23,7 @@
23
23
  "canonicalize": "~2.1.0",
24
24
  "ag-grid-enterprise": "^33.0.4",
25
25
  "ag-grid-vue3": "^33.0.4",
26
- "@milaboratories/uikit": "^2.2.64",
26
+ "@milaboratories/uikit": "^2.2.65",
27
27
  "@platforma-sdk/model": "^1.28.1"
28
28
  },
29
29
  "devDependencies": {
@@ -7,6 +7,7 @@ import NotFound from './NotFound.vue';
7
7
  import LoaderPage from './LoaderPage.vue';
8
8
  import { PlAppErrorNotificationAlert } from './PlAppErrorNotificationAlert';
9
9
  import BlockLoader from './BlockLoader.vue';
10
+ import { MonetizationSidebar } from '../plugins/Monetization';
10
11
 
11
12
  const sdk = useSdkPlugin();
12
13
 
@@ -49,4 +50,6 @@ const progress = computed(() => app.value?.progress?.());
49
50
  <NotFound v-else />
50
51
  <PlAppErrorNotificationAlert v-if="sdk.loaded && showErrorsNotification" :errors="errors" />
51
52
  </div>
53
+ <!-- Plugins -->
54
+ <MonetizationSidebar v-if="CurrentView" />
52
55
  </template>
File without changes
@@ -0,0 +1,162 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+
4
+ const props = defineProps<{
5
+ label: string;
6
+ used: number;
7
+ toSpend: number;
8
+ available: number | null; // If available is null it means Unlimited
9
+ unit?: string;
10
+ }>();
11
+
12
+ const toSpendPercentage = computed(() => {
13
+ if (props.available === null) return 0;
14
+ return (props.toSpend / props.available) * 100;
15
+ });
16
+
17
+ const usedPercentage = computed(() => {
18
+ if (props.available === null) return 0;
19
+ return (props.used / props.available) * 100;
20
+ });
21
+
22
+ const availablePercentage = computed(() => {
23
+ if (props.available === null) return 100;
24
+ return 100 - usedPercentage.value - toSpendPercentage.value;
25
+ });
26
+ </script>
27
+
28
+ <template>
29
+ <div :class="$style.container">
30
+ <span :class="$style.label">{{ label }}</span>
31
+ <div :class="$style.content">
32
+ <div :class="$style.contentAvailable">
33
+ Available:
34
+ <div style="flex: 1" />
35
+ <span v-if="available !== null"><strong>{{ available }}</strong> / {{ available + toSpend + used }}</span>
36
+ <span v-else>Unlimited</span>
37
+ </div>
38
+ <div :class="$style.progressBar">
39
+ <span :class="$style.progressBarAvailable" :style="{ width: `${availablePercentage}%` }" />
40
+ <span :class="$style.progressBarToSpend" :style="{ width: `${toSpendPercentage}%` }" />
41
+ <span :class="$style.progressBarUsed" :style="{ width: `${usedPercentage}%` }" />
42
+ </div>
43
+ <div v-if="available !== null" :class="$style.legends">
44
+ <div :class="$style.usedLegend">
45
+ <i/>
46
+ Used: {{ used }}
47
+ </div>
48
+ <div :class="$style.toSpendLegend">
49
+ <i/>
50
+ To spend: {{ toSpend }}
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </template>
56
+
57
+ <style module>
58
+ .container {
59
+ display: flex;
60
+ flex-direction: column;
61
+ gap: 8px;
62
+ background-color: #F7F8FA;
63
+ border-radius: 6px;
64
+ padding: 10px 12px 16px 12px;
65
+ color: var(--txt-01);
66
+ font-size: 14px;
67
+ font-weight: 500;
68
+ line-height: 20px;
69
+ border: 1px solid var(--border-color-div-grey);
70
+ }
71
+
72
+ .label {
73
+ display: block;
74
+ margin-bottom: 30px;
75
+ color: var(--txt-01);
76
+ font-size: 14px;
77
+ font-weight: 600;
78
+ line-height: 20px; /* 142.857% */
79
+ }
80
+
81
+ .content {
82
+ display: flex;
83
+ flex-direction: column;
84
+ gap: 20px;
85
+ }
86
+
87
+ .contentAvailable {
88
+ display: flex;
89
+ align-items: flex-start;
90
+ gap: 8px;
91
+ strong {
92
+ font-size: 28px;
93
+ font-weight: 500;
94
+ line-height: 36px; /* 128.571% */
95
+ letter-spacing: -0.56px;
96
+ }
97
+ }
98
+
99
+ .progressBar {
100
+ width: 100%;
101
+ height: 8px;
102
+ background-color: #E0E0E0;
103
+ border-radius: 4px;
104
+ display: flex;
105
+ align-items: center;
106
+ border: 1px solid var(--border-color-default);
107
+ > span {
108
+ display: block;
109
+ height: 100%;
110
+ }
111
+ }
112
+
113
+ .progressBarAvailable {
114
+ background-color: #49CC49;
115
+ border-radius: 4px;
116
+ }
117
+
118
+ .progressBarUsed {
119
+ background-color: #FFCECC;
120
+ border-radius: 4px;
121
+ }
122
+
123
+ .progressBarToSpend {
124
+ background-color: #FAF5AA;
125
+ border-radius: 4px;
126
+ }
127
+
128
+ .legends {
129
+ display: flex;
130
+ gap: 8px;
131
+
132
+ > div {
133
+ flex: 1;
134
+ }
135
+
136
+ i {
137
+ display: block;
138
+ border-radius: 1px;
139
+ border: 1px solid var(--border-color-default);
140
+ width: 16px;
141
+ height: 16px;
142
+ }
143
+ }
144
+
145
+ .usedLegend {
146
+ display: flex;
147
+ align-items: center;
148
+ gap: 8px;
149
+ i {
150
+ background: #FFCECC;
151
+ }
152
+ }
153
+
154
+ .toSpendLegend {
155
+ display: flex;
156
+ align-items: center;
157
+ gap: 8px;
158
+ i {
159
+ background: #FAF5AA;
160
+ }
161
+ }
162
+ </style>
@@ -0,0 +1,241 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed } from 'vue';
3
+ import { PlSlideModal, PlBtnGhost, PlDropdown, PlAlert } from '@milaboratories/uikit';
4
+ import { useButtonTarget } from './useButtonTarget';
5
+ import { useInfo } from './useInfo';
6
+ import UserCabinetCard from './UserCabinetCard.vue';
7
+ import RunStatus from './RunStatus.vue';
8
+ import LimitCard from './LimitCard.vue';
9
+ const isOpen = ref(false);
10
+
11
+ const teleportTarget = useButtonTarget();
12
+
13
+ const { result, error, hasMonetization, canRun } = useInfo();
14
+
15
+ const productName = computed(() => result.value?.productName);
16
+
17
+ const userCabinetUrl = computed(() => {
18
+ if (!result.value) return undefined;
19
+
20
+ return `https://scientist.platforma.bio/product/${result.value.productKey}`;
21
+ });
22
+
23
+ const options = computed(() => {
24
+ if (!result.value) return [];
25
+
26
+ return [
27
+ {
28
+ label: result.value.productName,
29
+ value: result.value.productName,
30
+ },
31
+ ];
32
+ });
33
+
34
+ // TODO: update api to return limits as list of objects
35
+
36
+ const used = computed(() => {
37
+ return result.value?.mnz.details.spentRuns ?? 0;
38
+ });
39
+
40
+ const toSpend = computed(() => {
41
+ return result.value?.mnz.details.runsToSpend ?? 0;
42
+ });
43
+
44
+ const available = computed(() => {
45
+ return result.value?.mnz.details.willRemainAfterRun ?? null;
46
+ });
47
+ </script>
48
+
49
+ <template>
50
+ <PlSlideModal v-if="hasMonetization" v-model="isOpen">
51
+ <template #title>
52
+ Monetization
53
+ </template>
54
+ <PlDropdown label="Product" readonly :model-value="productName" :options="options" />
55
+ <RunStatus :can-run="canRun" />
56
+ <PlAlert v-if="error" type="error">
57
+ {{ error }}
58
+ </PlAlert>
59
+ <UserCabinetCard v-if="userCabinetUrl" :user-cabinet-url="userCabinetUrl" />
60
+ <LimitCard label="Runs Limit" :used="used" :to-spend="toSpend" :available="available" />
61
+ </PlSlideModal>
62
+ <!-- Teleport to the title slot -->
63
+ <Teleport v-if="hasMonetization && teleportTarget" :to="teleportTarget">
64
+ <PlBtnGhost icon="monetization" @click.stop="isOpen = true">
65
+ Monetization
66
+ </PlBtnGhost>
67
+ </Teleport>
68
+ </template>
69
+
70
+ <style module>
71
+ .mnzContainer {
72
+ display: flex;
73
+ flex-direction: column;
74
+ gap: 16px;
75
+ border: 1px solid #e0e0e0;
76
+ padding: 20px;
77
+ border-radius: 8px;
78
+ position: relative;
79
+ background-color: #fafafa;
80
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
81
+ max-width: 600px;
82
+ }
83
+
84
+ .info {
85
+ position: absolute;
86
+ top: 10px;
87
+ right: 10px;
88
+ color: #666;
89
+ cursor: pointer;
90
+ }
91
+
92
+ .header {
93
+ display: flex;
94
+ align-items: center;
95
+ gap: 10px;
96
+ margin-bottom: 4px;
97
+ }
98
+
99
+ .statusIndicator {
100
+ width: 12px;
101
+ height: 12px;
102
+ border-radius: 50%;
103
+ background-color: #ccc;
104
+ }
105
+
106
+ .statusActive {
107
+ background-color: #4caf50;
108
+ }
109
+
110
+ .statusSelectTariff {
111
+ background-color: #ff9800;
112
+ }
113
+
114
+ .statusPaymentRequired {
115
+ background-color: #f44336;
116
+ }
117
+
118
+ .title {
119
+ margin: 0;
120
+ font-size: 18px;
121
+ font-weight: 600;
122
+ color: #333;
123
+ }
124
+
125
+ .statusMessage {
126
+ display: flex;
127
+ align-items: center;
128
+ gap: 8px;
129
+ padding: 10px 14px;
130
+ border-radius: 6px;
131
+ background-color: #f5f5f5;
132
+ border-left: 4px solid #ccc;
133
+ }
134
+
135
+ .warningIcon {
136
+ color: #ff9800;
137
+ }
138
+
139
+ .successIcon {
140
+ color: #4caf50;
141
+ }
142
+
143
+ .urlSection {
144
+ display: flex;
145
+ flex-direction: column;
146
+ gap: 8px;
147
+ padding: 12px;
148
+ background-color: #f0f0f0;
149
+ border-radius: 6px;
150
+ }
151
+
152
+ .urlLabel {
153
+ font-weight: 500;
154
+ color: #555;
155
+ }
156
+
157
+ .urlActions {
158
+ display: flex;
159
+ align-items: center;
160
+ gap: 10px;
161
+ flex-wrap: wrap;
162
+ }
163
+
164
+ .urlDisplay {
165
+ font-family: monospace;
166
+ font-size: 14px;
167
+ color: #0066cc;
168
+ background-color: #e6e6e6;
169
+ padding: 6px 10px;
170
+ border-radius: 4px;
171
+ white-space: nowrap;
172
+ overflow: hidden;
173
+ text-overflow: ellipsis;
174
+ max-width: 300px;
175
+ }
176
+
177
+ .copyButton {
178
+ display: flex;
179
+ align-items: center;
180
+ gap: 6px;
181
+ background-color: #2196f3;
182
+ color: white;
183
+ border: none;
184
+ border-radius: 4px;
185
+ padding: 6px 12px;
186
+ cursor: pointer;
187
+ font-size: 14px;
188
+ transition: background-color 0.2s;
189
+ }
190
+
191
+ .copyButton:hover {
192
+ background-color: #0b7dda;
193
+ }
194
+
195
+ .copyButton:active {
196
+ background-color: #0a6bbd;
197
+ }
198
+
199
+ .copiedMessage {
200
+ color: #4caf50;
201
+ font-size: 14px;
202
+ font-weight: 500;
203
+ }
204
+
205
+ .details {
206
+ margin-top: 10px;
207
+ padding: 16px;
208
+ background-color: #f5f5f5;
209
+ border-radius: 6px;
210
+ }
211
+
212
+ .detailsTitle {
213
+ margin: 0 0 12px 0;
214
+ font-size: 16px;
215
+ font-weight: 500;
216
+ color: #444;
217
+ }
218
+
219
+ .detailsGrid {
220
+ display: grid;
221
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
222
+ gap: 16px;
223
+ }
224
+
225
+ .detailItem {
226
+ display: flex;
227
+ flex-direction: column;
228
+ gap: 4px;
229
+ }
230
+
231
+ .detailLabel {
232
+ font-size: 14px;
233
+ color: #666;
234
+ }
235
+
236
+ .detailValue {
237
+ font-size: 16px;
238
+ font-weight: 500;
239
+ color: #333;
240
+ }
241
+ </style>
@@ -0,0 +1,79 @@
1
+ <script setup lang="ts">
2
+ import { PlIcon24, PlTooltip } from '@milaboratories/uikit';
3
+
4
+ defineProps<{
5
+ canRun: boolean | undefined;
6
+ }>();
7
+ </script>
8
+
9
+ <template>
10
+ <div :class="[{ 'can-run': canRun }, $style.container]">
11
+ <div :class="$style.badge">
12
+ <i :class="$style.blob">
13
+ <span>
14
+ <span :class="$style.dot" />
15
+ </span>
16
+ </i>
17
+ <span>Can run</span>
18
+ </div>
19
+ <PlTooltip>
20
+ <template #tooltip>
21
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
22
+ </template>
23
+ <PlIcon24 name="info" />
24
+ </PlTooltip>
25
+ </div>
26
+ </template>
27
+
28
+ <style module>
29
+ .container {
30
+ display: flex;
31
+ align-items: center;
32
+ gap: 8px;
33
+ --blob-color: #FF5C5C;
34
+ --badge-background: rgba(255, 92, 92, 0.12);
35
+ &.can-run {
36
+ --blob-color: #49CC49;
37
+ --badge-background: rgba(99, 224, 36, 0.12);
38
+ }
39
+ }
40
+
41
+ .badge {
42
+ display: flex;
43
+ height: 40px;
44
+ padding: 6px 16px 6px 8px;
45
+ align-items: center;
46
+ border-radius: 6px;
47
+ border: 1px solid var(--badge-background);
48
+ background: var(--badge-background);
49
+ }
50
+
51
+ .blob {
52
+ width: 24px;
53
+ height: 24px;
54
+ display: flex;
55
+ place-items: center;
56
+ place-content: center;
57
+
58
+ >span {
59
+ display: flex;
60
+ width: 16px;
61
+ height: 16px;
62
+ border-radius: 50%;
63
+ place-content: center;
64
+ place-items: center;
65
+ background-color: rgb(from var(--blob-color) r g b / 0.24);
66
+ }
67
+
68
+ .dot {
69
+ border-radius: 50%;
70
+ height: 8px;
71
+ width: 8px;
72
+ transform: scale(1);
73
+
74
+ background: var(--blob-color);
75
+ box-shadow: 0 0 0 0 var(--blob-color);
76
+ animation: pulse-glob 1s infinite;
77
+ }
78
+ }
79
+ </style>
@@ -0,0 +1,195 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue';
3
+ import { PlIcon24 } from '@milaboratories/uikit';
4
+
5
+ const props = defineProps<{
6
+ userCabinetUrl: string;
7
+ }>();
8
+
9
+ const copiedMessage = ref('');
10
+
11
+ const copyToClipboard = () => {
12
+ if (props.userCabinetUrl) {
13
+ navigator.clipboard.writeText(props.userCabinetUrl)
14
+ .then(() => {
15
+ copiedMessage.value = 'URL copied!';
16
+ setTimeout(() => {
17
+ copiedMessage.value = '';
18
+ }, 2000);
19
+ })
20
+ .catch((err) => {
21
+ console.error('Failed to copy text: ', err);
22
+ copiedMessage.value = 'Copy failed';
23
+ setTimeout(() => {
24
+ copiedMessage.value = '';
25
+ }, 2000);
26
+ });
27
+ }
28
+ };
29
+ </script>
30
+
31
+ <template>
32
+ <div v-if="userCabinetUrl" :class="$style.urlSection">
33
+ <div :class="$style.urlLabel">User cabinet URL:</div>
34
+ <div :class="$style.urlActions">
35
+ <div :class="$style.urlDisplay" :title="userCabinetUrl">{{ userCabinetUrl }}</div>
36
+ <button :class="$style.copyButton" @click="copyToClipboard">
37
+ <PlIcon24 name="copy" />
38
+ <span>Copy</span>
39
+ </button>
40
+ <span v-if="copiedMessage" :class="$style.copiedMessage">{{ copiedMessage }}</span>
41
+ </div>
42
+ </div>
43
+ </template>
44
+
45
+ <style module>
46
+ .header {
47
+ display: flex;
48
+ align-items: center;
49
+ gap: 10px;
50
+ margin-bottom: 4px;
51
+ }
52
+
53
+ .statusIndicator {
54
+ width: 12px;
55
+ height: 12px;
56
+ border-radius: 50%;
57
+ background-color: #ccc;
58
+ }
59
+
60
+ .statusActive {
61
+ background-color: #4caf50;
62
+ }
63
+
64
+ .statusSelectTariff {
65
+ background-color: #ff9800;
66
+ }
67
+
68
+ .statusPaymentRequired {
69
+ background-color: #f44336;
70
+ }
71
+
72
+ .title {
73
+ margin: 0;
74
+ font-size: 18px;
75
+ font-weight: 600;
76
+ color: #333;
77
+ }
78
+
79
+ .statusMessage {
80
+ display: flex;
81
+ align-items: center;
82
+ gap: 8px;
83
+ padding: 10px 14px;
84
+ border-radius: 6px;
85
+ background-color: #f5f5f5;
86
+ border-left: 4px solid #ccc;
87
+ }
88
+
89
+ .warningIcon {
90
+ color: #ff9800;
91
+ }
92
+
93
+ .successIcon {
94
+ color: #4caf50;
95
+ }
96
+
97
+ .urlSection {
98
+ display: flex;
99
+ flex-direction: column;
100
+ gap: 8px;
101
+ padding: 12px;
102
+ background-color: #f0f0f0;
103
+ border-radius: 6px;
104
+ }
105
+
106
+ .urlLabel {
107
+ font-weight: 500;
108
+ color: #555;
109
+ }
110
+
111
+ .urlActions {
112
+ display: flex;
113
+ align-items: center;
114
+ gap: 10px;
115
+ flex-wrap: wrap;
116
+ }
117
+
118
+ .urlDisplay {
119
+ font-family: monospace;
120
+ font-size: 14px;
121
+ color: #0066cc;
122
+ background-color: #e6e6e6;
123
+ padding: 6px 10px;
124
+ border-radius: 4px;
125
+ white-space: nowrap;
126
+ overflow: hidden;
127
+ text-overflow: ellipsis;
128
+ max-width: 300px;
129
+ }
130
+
131
+ .copyButton {
132
+ display: flex;
133
+ align-items: center;
134
+ gap: 6px;
135
+ background-color: #2196f3;
136
+ color: white;
137
+ border: none;
138
+ border-radius: 4px;
139
+ padding: 6px 12px;
140
+ cursor: pointer;
141
+ font-size: 14px;
142
+ transition: background-color 0.2s;
143
+ }
144
+
145
+ .copyButton:hover {
146
+ background-color: #0b7dda;
147
+ }
148
+
149
+ .copyButton:active {
150
+ background-color: #0a6bbd;
151
+ }
152
+
153
+ .copiedMessage {
154
+ color: #4caf50;
155
+ font-size: 14px;
156
+ font-weight: 500;
157
+ }
158
+
159
+ .details {
160
+ margin-top: 10px;
161
+ padding: 16px;
162
+ background-color: #f5f5f5;
163
+ border-radius: 6px;
164
+ }
165
+
166
+ .detailsTitle {
167
+ margin: 0 0 12px 0;
168
+ font-size: 16px;
169
+ font-weight: 500;
170
+ color: #444;
171
+ }
172
+
173
+ .detailsGrid {
174
+ display: grid;
175
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
176
+ gap: 16px;
177
+ }
178
+
179
+ .detailItem {
180
+ display: flex;
181
+ flex-direction: column;
182
+ gap: 4px;
183
+ }
184
+
185
+ .detailLabel {
186
+ font-size: 14px;
187
+ color: #666;
188
+ }
189
+
190
+ .detailValue {
191
+ font-size: 16px;
192
+ font-weight: 500;
193
+ color: #333;
194
+ }
195
+ </style>
@@ -0,0 +1,5 @@
1
+ import MonetizationSidebar from './MonetizationSidebar.vue';
2
+
3
+ export {
4
+ MonetizationSidebar,
5
+ };