@nethserver/ns8-ui-lib 0.1.2 → 0.1.5

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": "@nethserver/ns8-ui-lib",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "description": "Vue.js library for NethServer 8 UI",
5
5
  "keywords": [
6
6
  "nethserver",
@@ -0,0 +1,478 @@
1
+ <template>
2
+ <div :class="`ns-byte-slider cv-slider ${carbonPrefix}--form-item`">
3
+ <div class="wrapper" :data-invalid="isInvalid">
4
+ <label
5
+ v-show="label"
6
+ :for="uid"
7
+ :class="[
8
+ `${carbonPrefix}--label`,
9
+ { [`${carbonPrefix}--label--disabled`]: disabled },
10
+ ]"
11
+ :id="labelId"
12
+ >
13
+ {{ label }}
14
+ </label>
15
+ <!-- unlimited checkbox -->
16
+ <NsCheckbox
17
+ v-if="showUnlimited"
18
+ :label="unlimitedLabel"
19
+ v-model="internalUnlimited"
20
+ :disabled="disabled"
21
+ value="checkUnlimited"
22
+ class="is-unlimited"
23
+ />
24
+ <div :class="`${carbonPrefix}--slider-container`">
25
+ <span
26
+ v-if="internalMinLabel"
27
+ :class="`${carbonPrefix}--slider__range-label`"
28
+ >{{ internalMinLabel }}</span
29
+ >
30
+ <div
31
+ :class="[
32
+ `${carbonPrefix}--slider`,
33
+ {
34
+ [`${carbonPrefix}--slider--disabled`]:
35
+ disabled || internalUnlimited,
36
+ },
37
+ ]"
38
+ data-slider
39
+ data-slider-input-box="#slider-input-box"
40
+ >
41
+ <div
42
+ :class="`${carbonPrefix}--slider__track`"
43
+ @click="onTrackClick"
44
+ ref="track"
45
+ ></div>
46
+ <div
47
+ :class="`${carbonPrefix}--slider__filled-track`"
48
+ :style="`width: ${percentage};`"
49
+ ></div>
50
+ <div
51
+ :class="[
52
+ `${carbonPrefix}--slider__thumb`,
53
+ {
54
+ [`${carbonPrefix}--slider__thumb--clicked`]: animateClick,
55
+ },
56
+ ]"
57
+ tabindex="0"
58
+ :aria-labelledby="labelId"
59
+ :style="`left: ${percentage};`"
60
+ ref="thumb"
61
+ @keydown.up.right.prevent="onUp"
62
+ @keydown.down.left.prevent="onDown"
63
+ @mousedown="onStartDrag"
64
+ ></div>
65
+ <input
66
+ :id="uid"
67
+ :class="`${carbonPrefix}--slider__input`"
68
+ type="range"
69
+ :step="step"
70
+ :min="min"
71
+ :max="max"
72
+ ref="range"
73
+ />
74
+ </div>
75
+ <span
76
+ v-if="internalMaxLabel"
77
+ :class="`${carbonPrefix}--slider__range-label`"
78
+ >{{ internalMaxLabel }}</span
79
+ >
80
+ <input
81
+ type="number"
82
+ :class="[
83
+ `${carbonPrefix}--text-input ${carbonPrefix}--slider-text-input`,
84
+ {
85
+ [`${carbonPrefix}--text-input--light`]: isLight,
86
+ [`${carbonPrefix}--text-input--invalid`]: isInvalid,
87
+ [`${carbonPrefix}--text-input--warning`]: isWarn,
88
+ },
89
+ ,
90
+ 'range-input',
91
+ ]"
92
+ :placeholder="min"
93
+ v-model="internalValue"
94
+ @change="onChange"
95
+ ref="inputBox"
96
+ @keydown.up.prevent="onUp"
97
+ @keydown.down.prevent="onDown"
98
+ :disabled="disabled || internalUnlimited"
99
+ />
100
+ <!-- MiB/GiB toggle -->
101
+ <cv-radio-group v-if="showMibGibToggle" class="mib-gib">
102
+ <cv-radio-button
103
+ v-model="internalByteUnit"
104
+ name="group-mib-gib"
105
+ label="MiB"
106
+ value="mib"
107
+ :disabled="disabled || internalUnlimited"
108
+ />
109
+ <cv-radio-button
110
+ v-model="internalByteUnit"
111
+ name="group-mib-gib"
112
+ label="GiB"
113
+ value="gib"
114
+ :disabled="disabled || internalUnlimited"
115
+ />
116
+ </cv-radio-group>
117
+ </div>
118
+ <!-- human readable label -->
119
+ <NsTag
120
+ v-if="showHumanReadableLabel && !internalUnlimited"
121
+ :label="humanReadableLabel"
122
+ :kind="tagKind"
123
+ :disabled="disabled || internalUnlimited"
124
+ class="human-label"
125
+ >
126
+ </NsTag>
127
+ </div>
128
+ <div :class="`${carbonPrefix}--form-requirement`" v-if="isInvalid">
129
+ <slot name="invalid-message">{{ invalidMessage }}</slot>
130
+ </div>
131
+ </div>
132
+ </template>
133
+
134
+ <script>
135
+ import {
136
+ uidMixin,
137
+ themeMixin,
138
+ carbonPrefixMixin,
139
+ methodsMixin,
140
+ } from "@carbon/vue/src/mixins";
141
+
142
+ export default {
143
+ name: "NsByteSlider",
144
+ mixins: [
145
+ uidMixin,
146
+ themeMixin,
147
+ carbonPrefixMixin,
148
+ methodsMixin({ thumb: ["blur", "focus"] }),
149
+ ],
150
+ props: {
151
+ disabled: Boolean,
152
+ label: String,
153
+ // NOTE: It is not safe to rely on Numbers for non-integer steps
154
+ min: { type: String, default: "0" },
155
+ max: { type: String, default: "100" },
156
+ step: { type: String, default: "1" },
157
+ stepMultiplier: {
158
+ type: String,
159
+ default: "4",
160
+ validator(val) {
161
+ if (val.length) {
162
+ let intMultiplier = parseInt(val);
163
+ if (isNaN(intMultiplier) || intMultiplier < 1) {
164
+ console.warn("ns-byte-slider: multiplier must be >= 1");
165
+ return false;
166
+ }
167
+ }
168
+ return true;
169
+ },
170
+ },
171
+ value: String,
172
+ minLabel: String,
173
+ maxLabel: String,
174
+ showUnlimited: { type: Boolean, default: false },
175
+ isUnlimited: { type: Boolean, default: false },
176
+ byteUnit: { type: String, default: "gib" },
177
+ unlimitedLabel: { type: String, default: "Unlimited" },
178
+ showHumanReadableLabel: { type: Boolean, default: false },
179
+ showMibGibToggle: { type: Boolean, default: false },
180
+ tagKind: { type: String, default: "high-contrast" },
181
+ invalidMessage: { type: String, default: "" },
182
+ },
183
+ model: {
184
+ prop: "value",
185
+ event: "modelEvent",
186
+ },
187
+ data() {
188
+ return {
189
+ internalValue: "",
190
+ animateClick: false,
191
+ isDragging: false,
192
+ dragStartX: 0,
193
+ dragStartValue: 0,
194
+ percentage: "0%",
195
+ internalUnlimited: false,
196
+ internalByteUnit: "gib",
197
+ isInvalid: false,
198
+ };
199
+ },
200
+ computed: {
201
+ labelId() {
202
+ return `${this.uid}-label`;
203
+ },
204
+ internalMinLabel() {
205
+ return this.minLabel !== undefined ? this.minLabel : this.getMin();
206
+ },
207
+ internalMaxLabel() {
208
+ return this.maxLabel !== undefined ? this.maxLabel : this.getMax();
209
+ },
210
+ internalMultiplier() {
211
+ let intMultiplier = parseInt(this.stepMultiplier);
212
+ // default to 4 fro multiplier
213
+ return isNaN(intMultiplier) ? 4 : Math.max(intMultiplier, 1);
214
+ },
215
+ humanReadableLabel() {
216
+ if (this.internalByteUnit === "gib") {
217
+ return this.gibFormat(this.internalValue);
218
+ } else {
219
+ // MiB
220
+ return this.mibFormat(this.internalValue);
221
+ }
222
+ },
223
+ },
224
+ created() {
225
+ this.internalUnlimited = this.isUnlimited;
226
+ this.internalByteUnit = this.byteUnit;
227
+ },
228
+ mounted() {
229
+ this.$refs.range.value = this.value;
230
+ this.internalValue = this.$refs.range.value;
231
+ this.percentage = `${
232
+ ((this.internalValue - this.getMin()) * 100) /
233
+ (this.getMax() - this.getMin())
234
+ }%`;
235
+ this.checkSlots();
236
+ },
237
+ updated() {
238
+ this.checkSlots();
239
+ },
240
+ watch: {
241
+ value(val) {
242
+ this.setValue(val);
243
+ },
244
+ min() {
245
+ this.$nextTick(() => {
246
+ this.setValue(this.internalValue);
247
+ });
248
+ },
249
+ max() {
250
+ this.$nextTick(() => {
251
+ this.setValue(this.internalValue);
252
+ });
253
+ },
254
+ step() {
255
+ this.$nextTick(() => {
256
+ this.setValue(this.internalValue);
257
+ });
258
+ },
259
+ isUnlimited() {
260
+ this.internalUnlimited = this.isUnlimited;
261
+ },
262
+ internalUnlimited() {
263
+ if (this.internalUnlimited) {
264
+ this.$emit("unlimited", true);
265
+ } else {
266
+ this.$emit("unlimited", false);
267
+ }
268
+ },
269
+ byteUnit() {
270
+ this.internalByteUnit = this.byteUnit;
271
+ },
272
+ internalByteUnit() {
273
+ this.$emit("byteUnit", this.internalByteUnit);
274
+ },
275
+ },
276
+ methods: {
277
+ // NOTE: It is not safe to rely on Numbers for non-integer steps
278
+ getMin() {
279
+ if (this.$refs.range) {
280
+ const val = parseFloat(this.$refs.range.min);
281
+ return isNaN(val) ? 0 : val;
282
+ }
283
+ return 0;
284
+ },
285
+ getMax() {
286
+ if (this.$refs.range) {
287
+ const val = parseFloat(this.$refs.range.max);
288
+ return isNaN(val) ? 100 : val;
289
+ }
290
+ return 100;
291
+ },
292
+ getValue() {
293
+ if (this.$refs.range) {
294
+ const val = parseFloat(this.$refs.range.value);
295
+ return isNaN(val) ? 0 : val;
296
+ }
297
+ return (this.getMax() + this.getMin()) / 2;
298
+ },
299
+ getStep() {
300
+ if (this.$refs.range) {
301
+ const val = parseFloat(this.$refs.range.step);
302
+ return isNaN(val) ? 0 : val;
303
+ }
304
+ return 1;
305
+ },
306
+
307
+ setValue(newValue) {
308
+ if (this.disabled) return;
309
+
310
+ this.$refs.range.value = newValue;
311
+ this.internalValue = this.$refs.range.value;
312
+
313
+ this.percentage = `${
314
+ ((this.internalValue - this.getMin()) * 100) /
315
+ (this.getMax() - this.getMin())
316
+ }%`;
317
+
318
+ this.$emit("modelEvent", this.$refs.range.value);
319
+ this.$emit("change", this.$refs.range.value);
320
+ },
321
+ onChange() {
322
+ let newValue = this.internalValue.length
323
+ ? parseFloat(this.internalValue)
324
+ : this.getMin();
325
+ this.setValue(newValue);
326
+ },
327
+ onStartDrag(ev) {
328
+ document.body.addEventListener("mousemove", this.onDrag);
329
+ document.body.addEventListener("mouseup", this.onStopDrag);
330
+
331
+ this.dragStartX = ev.clientX;
332
+ this.dragStartValue = this.getValue();
333
+ this.isDragging = true;
334
+ },
335
+ onDrag(ev) {
336
+ if (this.isDragging) {
337
+ // percentage change
338
+ let newValue =
339
+ (ev.clientX - this.dragStartX) / this.$refs.track.offsetWidth;
340
+ // uncapped new value
341
+ newValue =
342
+ this.dragStartValue + (this.getMax() - this.getMin()) * newValue;
343
+
344
+ this.setValue(newValue, ev);
345
+ }
346
+ },
347
+ onStopDrag() {
348
+ this.isDragging = false;
349
+ document.body.removeEventListener("mousemove", this.onDrag);
350
+ document.body.removeEventListener("mouseup", this.onStopDrag);
351
+ },
352
+ onTrackClick(ev) {
353
+ const afterAnimate = (ev) => {
354
+ if (ev.propertyName === "left") {
355
+ this.animateClick = false;
356
+ this.$refs.thumb.removeEventListener("transitionend", afterAnimate);
357
+ }
358
+ };
359
+
360
+ let newValue = ev.offsetX / this.$refs.track.offsetWidth;
361
+ newValue = (this.getMax() - this.getMin()) * newValue + this.getMin();
362
+ this.$refs.thumb.addEventListener("transitionend", afterAnimate);
363
+ this.animateClick = true;
364
+
365
+ this.setValue(newValue, ev);
366
+ },
367
+ onUp(ev) {
368
+ let curValue =
369
+ ev.target.type === "number"
370
+ ? parseFloat(ev.target.value)
371
+ : this.getValue();
372
+ let newValue =
373
+ curValue +
374
+ (ev.shiftKey
375
+ ? this.internalMultiplier * this.getStep()
376
+ : this.getStep());
377
+ this.setValue(newValue, ev);
378
+ },
379
+ onDown(ev) {
380
+ let curValue =
381
+ ev.target.type === "number"
382
+ ? parseFloat(ev.target.value)
383
+ : this.getValue();
384
+ let newValue =
385
+ curValue -
386
+ (ev.shiftKey
387
+ ? this.internalMultiplier * this.getStep()
388
+ : this.getStep());
389
+ this.setValue(newValue, ev);
390
+ },
391
+ mibFormat(size) {
392
+ let result;
393
+
394
+ switch (true) {
395
+ case size === null || size === "" || isNaN(size):
396
+ result = "-";
397
+ break;
398
+
399
+ case size >= 0 && size < 1024:
400
+ result = size + " MiB";
401
+ break;
402
+
403
+ case size >= 1024 && size < Math.pow(1024, 2):
404
+ result = Math.round((size / 1024) * 100) / 100 + " GiB";
405
+ break;
406
+
407
+ default:
408
+ result = Math.round((size / Math.pow(1024, 2)) * 100) / 100 + " TiB";
409
+ }
410
+ return result;
411
+ },
412
+ gibFormat(size) {
413
+ let result;
414
+
415
+ switch (true) {
416
+ case size === null || size === "" || isNaN(size):
417
+ result = "-";
418
+ break;
419
+
420
+ case size >= 0 && size < 1024:
421
+ result = size + " GiB";
422
+ break;
423
+
424
+ default:
425
+ result = Math.round((size / 1024) * 100) / 100 + " TiB";
426
+ }
427
+ return result;
428
+ },
429
+ checkSlots() {
430
+ // NOTE: this.$slots is not reactive so needs to be managed on updated
431
+ this.isInvalid = !!(
432
+ this.$slots["invalid-message"] ||
433
+ (this.invalidMessage && this.invalidMessage.length)
434
+ );
435
+ },
436
+ },
437
+ };
438
+ </script>
439
+
440
+ <style scoped lang="scss">
441
+ .is-unlimited {
442
+ margin-top: 0.5rem !important;
443
+ margin-bottom: 0.5rem !important;
444
+ }
445
+
446
+ .range-input {
447
+ margin-top: 0.5rem !important;
448
+ }
449
+
450
+ .mib-gib {
451
+ margin-left: 1rem;
452
+ margin-top: 0.5rem !important;
453
+ }
454
+
455
+ .human-label {
456
+ margin-top: 1rem;
457
+ }
458
+ </style>
459
+
460
+ <style lang="scss">
461
+ // global styles
462
+
463
+ .ns-byte-slider .bx--slider-container {
464
+ flex-wrap: wrap;
465
+ }
466
+
467
+ .ns-byte-slider .bx--slider-text-input {
468
+ width: 5rem;
469
+ }
470
+
471
+ .ns-byte-slider .wrapper[data-invalid] ~ .bx--form-requirement {
472
+ display: block;
473
+ overflow: visible;
474
+ max-height: 12.5rem;
475
+ font-weight: 400;
476
+ color: #da1e28;
477
+ }
478
+ </style>
@@ -32,14 +32,6 @@
32
32
  :data-contained-checkbox-disabled="$attrs.disabled"
33
33
  :for="uid"
34
34
  >
35
- <!-- <span ////
36
- :class="[
37
- `${carbonPrefix}--checkbox-label-text`,
38
- { [`${carbonPrefix}--visually-hidden`]: hideLabel },
39
- ]"
40
- >
41
- {{ label }}
42
- </span> -->
43
35
  <div
44
36
  :class="[
45
37
  `${carbonPrefix}--checkbox-label-text`,
@@ -47,9 +39,7 @@
47
39
  ]"
48
40
  >
49
41
  <div class="label-with-tooltip">
50
- <span>
51
- {{ label }}
52
- </span>
42
+ <span v-html="label"></span>
53
43
  <!-- tooltip -->
54
44
  <cv-interactive-tooltip
55
45
  v-if="hasTooltipSlot"
@@ -380,7 +380,7 @@ export default {
380
380
  backwardText: { type: String, default: undefined },
381
381
  forwardText: { type: String, default: undefined },
382
382
  pageNumberLabel: { type: String, default: undefined },
383
- filterRowsCallback: { type: Function }
383
+ filterRowsCallback: { type: Function },
384
384
  },
385
385
  data() {
386
386
  return {
@@ -463,6 +463,29 @@ export default {
463
463
  this.filteredRows = this.defaultFilterRows();
464
464
  }
465
465
  },
466
+ searchMatch(target, cleanRegex, queryText) {
467
+ if (Array.isArray(target)) {
468
+ // search field is an array (e.g. groups)
469
+ return target.some((elem) => {
470
+ return this.searchMatch(elem, cleanRegex, queryText);
471
+ });
472
+ } else if (typeof target === "string") {
473
+ // search field is a simple string
474
+ return new RegExp(queryText, "i").test(target.replace(cleanRegex, ""));
475
+ } else if (typeof target === "number") {
476
+ // search field is a number
477
+ return new RegExp(queryText, "i").test(
478
+ target.toString().replace(cleanRegex, "")
479
+ );
480
+ } else if (typeof target === "object") {
481
+ // search field is an object, search in its attributes
482
+
483
+ return Object.keys(target).some((key) => {
484
+ // recursion
485
+ return this.searchMatch(target[key], cleanRegex, queryText);
486
+ });
487
+ }
488
+ },
466
489
  defaultFilterRows() {
467
490
  if (!this.searchFilter) {
468
491
  return this.allRows;
@@ -474,22 +497,10 @@ export default {
474
497
  const searchResults = this.allRows.filter((option) => {
475
498
  // compare query text with attributes option
476
499
  return this.rawColumns.some((searchField) => {
477
- const searchValue = option[searchField];
500
+ const target = option[searchField];
478
501
 
479
- if (searchValue) {
480
- if (Array.isArray(searchValue)) {
481
- // search field is an array (e.g. groups)
482
- return searchValue.some((elem) => {
483
- return new RegExp(queryText, "i").test(
484
- elem.replace(cleanRegex, "")
485
- );
486
- });
487
- } else {
488
- // search field is a simple string
489
- return new RegExp(queryText, "i").test(
490
- searchValue.replace(cleanRegex, "")
491
- );
492
- }
502
+ if (target) {
503
+ return this.searchMatch(target, cleanRegex, queryText);
493
504
  } else {
494
505
  return false;
495
506
  }
@@ -55,6 +55,7 @@ export default {
55
55
  warningThreshold: { type: Number, default: 70 },
56
56
  dangerThreshold: { type: Number, default: 90 },
57
57
  useStatusColors: { type: Boolean, default: false },
58
+ useHealthyColor: { type: Boolean, default: true },
58
59
  },
59
60
  data() {
60
61
  return {
@@ -77,7 +78,11 @@ export default {
77
78
  return Number(this.value) || 0;
78
79
  },
79
80
  healthyStatus() {
80
- return this.useStatusColors && this.numericValue < this.warningThreshold;
81
+ return (
82
+ this.useStatusColors &&
83
+ this.useHealthyColor &&
84
+ this.numericValue < this.warningThreshold
85
+ );
81
86
  },
82
87
  warningStatus() {
83
88
  return (