supervisely 6.73.420__py3-none-any.whl → 6.73.422__py3-none-any.whl

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 (76) hide show
  1. supervisely/api/api.py +10 -5
  2. supervisely/api/app_api.py +71 -4
  3. supervisely/api/module_api.py +4 -0
  4. supervisely/api/nn/deploy_api.py +15 -9
  5. supervisely/api/nn/ecosystem_models_api.py +201 -0
  6. supervisely/api/nn/neural_network_api.py +12 -3
  7. supervisely/api/project_api.py +35 -6
  8. supervisely/api/task_api.py +5 -1
  9. supervisely/app/widgets/__init__.py +8 -1
  10. supervisely/app/widgets/agent_selector/template.html +1 -0
  11. supervisely/app/widgets/deploy_model/__init__.py +0 -0
  12. supervisely/app/widgets/deploy_model/deploy_model.py +729 -0
  13. supervisely/app/widgets/dropdown_checkbox_selector/__init__.py +0 -0
  14. supervisely/app/widgets/dropdown_checkbox_selector/dropdown_checkbox_selector.py +87 -0
  15. supervisely/app/widgets/dropdown_checkbox_selector/template.html +12 -0
  16. supervisely/app/widgets/ecosystem_model_selector/__init__.py +0 -0
  17. supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +190 -0
  18. supervisely/app/widgets/experiment_selector/experiment_selector.py +447 -264
  19. supervisely/app/widgets/fast_table/fast_table.py +402 -74
  20. supervisely/app/widgets/fast_table/script.js +364 -96
  21. supervisely/app/widgets/fast_table/style.css +24 -0
  22. supervisely/app/widgets/fast_table/template.html +43 -3
  23. supervisely/app/widgets/radio_table/radio_table.py +10 -2
  24. supervisely/app/widgets/select/select.py +6 -4
  25. supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +18 -0
  26. supervisely/app/widgets/tabs/tabs.py +22 -6
  27. supervisely/app/widgets/tabs/template.html +5 -1
  28. supervisely/nn/artifacts/__init__.py +1 -1
  29. supervisely/nn/artifacts/artifacts.py +10 -2
  30. supervisely/nn/artifacts/detectron2.py +1 -0
  31. supervisely/nn/artifacts/hrda.py +1 -0
  32. supervisely/nn/artifacts/mmclassification.py +20 -0
  33. supervisely/nn/artifacts/mmdetection.py +5 -3
  34. supervisely/nn/artifacts/mmsegmentation.py +1 -0
  35. supervisely/nn/artifacts/ritm.py +1 -0
  36. supervisely/nn/artifacts/rtdetr.py +1 -0
  37. supervisely/nn/artifacts/unet.py +1 -0
  38. supervisely/nn/artifacts/utils.py +3 -0
  39. supervisely/nn/artifacts/yolov5.py +2 -0
  40. supervisely/nn/artifacts/yolov8.py +1 -0
  41. supervisely/nn/benchmark/semantic_segmentation/metric_provider.py +18 -18
  42. supervisely/nn/experiments.py +9 -0
  43. supervisely/nn/inference/gui/serving_gui_template.py +39 -13
  44. supervisely/nn/inference/inference.py +160 -94
  45. supervisely/nn/inference/predict_app/__init__.py +0 -0
  46. supervisely/nn/inference/predict_app/gui/__init__.py +0 -0
  47. supervisely/nn/inference/predict_app/gui/classes_selector.py +91 -0
  48. supervisely/nn/inference/predict_app/gui/gui.py +710 -0
  49. supervisely/nn/inference/predict_app/gui/input_selector.py +165 -0
  50. supervisely/nn/inference/predict_app/gui/model_selector.py +79 -0
  51. supervisely/nn/inference/predict_app/gui/output_selector.py +139 -0
  52. supervisely/nn/inference/predict_app/gui/preview.py +93 -0
  53. supervisely/nn/inference/predict_app/gui/settings_selector.py +184 -0
  54. supervisely/nn/inference/predict_app/gui/tags_selector.py +110 -0
  55. supervisely/nn/inference/predict_app/gui/utils.py +282 -0
  56. supervisely/nn/inference/predict_app/predict_app.py +184 -0
  57. supervisely/nn/inference/uploader.py +9 -5
  58. supervisely/nn/model/prediction.py +2 -0
  59. supervisely/nn/model/prediction_session.py +20 -3
  60. supervisely/nn/training/gui/gui.py +131 -44
  61. supervisely/nn/training/gui/model_selector.py +8 -6
  62. supervisely/nn/training/gui/train_val_splits_selector.py +122 -70
  63. supervisely/nn/training/gui/training_artifacts.py +0 -5
  64. supervisely/nn/training/train_app.py +161 -44
  65. supervisely/template/experiment/experiment.html.jinja +74 -17
  66. supervisely/template/experiment/experiment_generator.py +258 -112
  67. supervisely/template/experiment/header.html.jinja +31 -13
  68. supervisely/template/experiment/sly-style.css +7 -2
  69. {supervisely-6.73.420.dist-info → supervisely-6.73.422.dist-info}/METADATA +3 -1
  70. {supervisely-6.73.420.dist-info → supervisely-6.73.422.dist-info}/RECORD +74 -56
  71. supervisely/app/widgets/experiment_selector/style.css +0 -27
  72. supervisely/app/widgets/experiment_selector/template.html +0 -61
  73. {supervisely-6.73.420.dist-info → supervisely-6.73.422.dist-info}/LICENSE +0 -0
  74. {supervisely-6.73.420.dist-info → supervisely-6.73.422.dist-info}/WHEEL +0 -0
  75. {supervisely-6.73.420.dist-info → supervisely-6.73.422.dist-info}/entry_points.txt +0 -0
  76. {supervisely-6.73.420.dist-info → supervisely-6.73.422.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,262 @@
1
1
  Vue.component('fast-table', {
2
+ template: `
3
+ <!-- eslint-disable vue/no-v-html -->
4
+ <div class="sly-ninja-table" :disabled="disabled" style="position: relative;">
5
+ <div v-if="disabled" class="sly-fast-table-disable-overlay"></div>
6
+ <div class=" tailwind fast-table">
7
+ <div
8
+ ref="wrapper"
9
+ class="rounded-lg border border-slate-200 bg-white overflow-hidden"
10
+ >
11
+ <div
12
+ v-if="settings.showHeaderControls"
13
+ class="py-2 px-2 md:px-5 md:py-4 flex flex-col md:flex-row gap-2 justify-between items-center"
14
+ >
15
+ <div
16
+ class="fflex"
17
+ style="flex-grow: 1"
18
+ >
19
+ <slot name="header-left-side-start" />
20
+ <div class="relative w-full md:max-w-[14rem]">
21
+ <i class="zmdi zmdi-search h-4 absolute top-2 left-2.5 opacity-50" />
22
+ <i
23
+ v-if="search"
24
+ class="zmdi zmdi-close h-4 absolute top-2.5 right-3 opacity-50 cursor-pointer"
25
+ @click="searchChanged('')"
26
+ />
27
+ <input
28
+ :value="search"
29
+ type="text"
30
+ :placeholder="\`\${name ? \`Search for \${name}...\` : 'Search'}\`"
31
+ class="text-sm rounded-md px-3 py-1.5 text-gray-900 shadow-sm placeholder:text-gray-400 border border-slate-200 w-full pl-8 bg-slate-50"
32
+ @input="searchChanged($event.target.value)"
33
+ @keydown.esc="searchChanged('')"
34
+ >
35
+ </div>
36
+ <slot name="header-left-side-end" />
37
+ </div>
38
+ <div
39
+ v-if="data && data.length"
40
+ class="text-[.9rem] text-slate-500 flex items-center gap-0 w-full md:w-auto whitespace-nowrap"
41
+ >
42
+ <button
43
+ aria-label="Go back"
44
+ class="hover:text-secondary-500 text-[1.3rem] md:ml-0 -ml-2"
45
+ :class="{ '!text-slate-200 cursor-default': !canGoBack }"
46
+ @click="go(-1)"
47
+ >
48
+ <i class="zmdi zmdi-chevron-left" />
49
+ </button>
50
+ <span>Rows {{ Math.min((page - 1) * LIMIT + 1, total) }}-{{ Math.min(total, page * LIMIT) }} of {{ total }}</span>
51
+ <button
52
+ aria-label="Go forward"
53
+ class="hover:text-secondary-500 text-[1.3rem]"
54
+ :class="{ '!text-slate-200 cursor-default': !canGoForward }"
55
+ @click="go(+1)"
56
+ >
57
+ <i class="zmdi zmdi-chevron-right" />
58
+ </button>
59
+ <div class="md:hidden w-full flex-1" />
60
+ </div>
61
+ </div>
62
+ <div
63
+ ref="scrollBox"
64
+ class="overflow-x-auto border-slate-200 relative"
65
+ :class="{ 'border-t': settings.showHeaderControls}"
66
+ >
67
+ <div
68
+ :class="{ 'opacity-100 !flex': needsRightGradient }"
69
+ class="bg-gradient-to-r from-transparent to-white absolute right-0 top-0 bottom-0 z-10 w-56 opacity-0 transition-opacity hidden"
70
+ >
71
+ <i class="zmdi zmdi-chevron-right absolute top-1/2 right-2 text-[1.5rem] text-slate-400 -translate-y-1/2" />
72
+ </div>
73
+ <table
74
+ ref="tableBox"
75
+ class="w-full text-[.8rem] md:text-[.9rem] mb-1"
76
+ >
77
+ <thead>
78
+ <tr>
79
+ <th
80
+ v-if="settings.isRadio"
81
+ class="px-2 md:px-3 py-2.5 whitespace-nowrap first:pl-3 last:pr-3 md:last:pr-6 first:text-left cursor-pointer sticky top-0 bg-slate-50 box-content shadow-[inset_0_-2px_0_#dfe6ec] group"
82
+ :class="{ 'first:sticky first:left-0 first:z-20 first:shadow-[inset_-2px_-2px_0_#dfe6ec]': fixColumns }"
83
+ style="width: 20px;"
84
+ >
85
+ </th>
86
+ <th
87
+ v-else-if="settings.isRowSelectable"
88
+ class="px-2 md:px-3 py-2.5 whitespace-nowrap first:pl-3 last:pr-3 md:last:pr-6 first:text-left cursor-pointer sticky top-0 bg-slate-50 box-content shadow-[inset_0_-2px_0_#dfe6ec] group"
89
+ :class="{ 'first:sticky first:left-0 first:z-20 first:shadow-[inset_-2px_-2px_0_#dfe6ec]': fixColumns }"
90
+ >
91
+ <el-checkbox
92
+ v-if="settings.isRowSelectable"
93
+ size="small"
94
+ :value="isAllRowsSelected"
95
+ @input="selectAllRows"
96
+ />
97
+ </th>
98
+ <th
99
+ v-for="(c,idx) in columns.slice(0, columnNumberLimit)"
100
+ class="px-2 md:px-3 py-2.5 whitespace-nowrap first:pl-3 last:pr-3 md:first:pl-6 md:last:pr-6 first:text-left cursor-pointer sticky top-0 bg-slate-50 box-content shadow-[inset_0_-2px_0_#dfe6ec] group"
101
+ :class="{ 'first:sticky first:left-0 first:z-20 first:shadow-[inset_-2px_-2px_0_#dfe6ec]': fixColumns }"
102
+ @click="onHeaderCellClick(idx)"
103
+ >
104
+ <div class="flex items-center">
105
+ <span>{{ c }}</span>
106
+ <el-tooltip v-if="columnsSettings[idx] && columnsSettings[idx].tooltip">
107
+ <div
108
+ slot="content"
109
+ v-html="columnsSettings[idx].tooltip"
110
+ />
111
+ <i
112
+ class="zmdi zmdi-info-outline ml5 text-slate-500 text-[12px]"
113
+ style="margin-top: -1px;"
114
+ />
115
+ </el-tooltip>
116
+ <span
117
+ v-if="!columnsSettings[idx] || !columnsSettings[idx].disableSort"
118
+ class="w-[16px] text-[12px] text-secondary-500 ml-1"
119
+ >
120
+ <i
121
+ v-if="sort.column !== idx"
122
+ class="zmdi zmdi-sort-amount-asc ml5 opacity-0 group-hover:opacity-100 text-slate-400 transition-opacity"
123
+ />
124
+ <i
125
+ v-if="sort.column === idx && sort.order === 'asc'"
126
+ class="zmdi zmdi-sort-amount-asc ml5"
127
+ />
128
+ <i
129
+ v-if="sort.column === idx && sort.order === 'desc'"
130
+ class="zmdi zmdi-sort-amount-desc ml5"
131
+ />
132
+ </span>
133
+ </div>
134
+ <div
135
+ v-if="oneOfRowsHasSubtitle"
136
+ class="text-[.7rem] text-slate-500 font-normal text-left"
137
+ >
138
+ {{ columnsSettings[idx] && columnsSettings[idx].subtitle ? columnsSettings[idx].subtitle : 'ㅤ' }}
139
+ </div>
140
+ </th>
141
+ </tr>
142
+ </thead>
143
+ <tbody>
144
+ <tr
145
+ v-for="row in data"
146
+ class="border-b border-gray-200 last:border-0 group"
147
+ :class="{ 'cursor-pointer': settings.isRowClickable }"
148
+ @click="settings.isRowClickable && $emit('row-click', { idx: row.idx, row: row.items, columnsSettings })"
149
+ >
150
+ <td
151
+ v-if="settings.isRadio"
152
+ class="px-2 md:px-3 py-2 bg-white first:pl-3 last:pr-3 md:last:pr-6 first:text-left cursor-pointer group-hover:bg-slate-50 group"
153
+ :class="{ 'first:sticky first:left-0 first:z-10 first:shadow-[inset_-2px_0_0_#dfe6ec]': fixColumns, 'cursor-pointer': settings.isCellClickable }"
154
+ >
155
+ <el-radio
156
+ v-model="selectedRadioIdx"
157
+ :label="row.idx"
158
+ @input="updateSelectedRadio(row)"
159
+ style="width: 20px;"
160
+ class="row-radio"
161
+ >
162
+ <span style="margin-left: -20px; visibility: hidden;">{{ row.idx }}</span>
163
+ </el-radio>
164
+ </td>
165
+ <td
166
+ v-else-if="settings.isRowSelectable"
167
+ class="row-select-cell px-2 md:px-3 py-2 bg-white first:pl-3 last:pr-3 md:last:pr-6 group-hover:bg-slate-50 group"
168
+ :class="{ 'first:sticky first:left-0 first:z-10 first:shadow-[inset_-2px_0_0_#dfe6ec]': fixColumns, 'cursor-pointer': settings.isCellClickable }"
169
+ >
170
+ <el-checkbox
171
+ size="small"
172
+ :value="!!selectedRowsById[row.id]"
173
+ @input="updateSelectedRows(row)"
174
+ />
175
+ </td>
176
+
177
+ <td
178
+ v-for="(col,idx) in row.items.slice(0, columnNumberLimit)"
179
+ class="px-2 md:px-3 py-2 bg-white first:pl-3 last:pr-3 md:first:pl-6 md:last:pr-6 group-hover:bg-slate-50 group"
180
+ :class="{ 'first:sticky first:left-0 first:z-10 first:shadow-[inset_-2px_0_0_#dfe6ec]': fixColumns, 'cursor-pointer': settings.isCellClickable }"
181
+ @click="settings.isCellClickable && $emit('cell-click', { idx: row.idx, row: row.items , column: idx})"
182
+ >
183
+ <div
184
+ class="flex items-center whitespace-nowrap"
185
+ :class="{ 'justify-end': columnsSettings[idx] && columnsSettings[idx].align === 'right' }"
186
+ >
187
+ <span
188
+ v-if="columnsSettings[idx] && columnsSettings[idx].type === 'class'"
189
+ class="w-3 h-3 rounded-sm flex mr-1.5 flex-none"
190
+ :style="{ backgroundColor: (classesMap[col] || { color: '#00ff00' }).color }"
191
+ />
192
+ <span v-if="columnsSettings[idx] && columnsSettings[idx].customCell">
193
+ <slot
194
+ name="cell-content"
195
+ :row="row"
196
+ :cell-value="col"
197
+ :column="columns[idx]"
198
+ :idx="idx"
199
+ />
200
+ </span>
201
+ <el-tooltip
202
+ v-else-if="columnsSettings[idx] && columnsSettings[idx].maxWidth"
203
+ :enterable="false"
204
+ :open-delay="300"
205
+ >
206
+ <span
207
+ slot="content"
208
+ v-html="col"
209
+ />
210
+ <span
211
+ class="ellipsis"
212
+ :style="{ 'max-width': columnsSettings[idx].maxWidth }"
213
+ v-html="highlight(col)"
214
+ />
215
+ </el-tooltip>
216
+ <span
217
+ v-else
218
+ v-html="highlight(col)"
219
+ />
220
+ <span
221
+ v-if="col != '' && columnsSettings[idx] && columnsSettings[idx].postfix"
222
+ class="text-slate-400 ml-0.5 text-[.7rem]"
223
+ >{{ columnsSettings[idx].postfix }}</span>
224
+ <span
225
+ v-if="idx === 0 && settings.isRowClickable"
226
+ class="opacity-0 transition-all duration-300 text-secondary-500 text-xs group-hover:translate-x-2 group-hover:opacity-100"
227
+ >➔</span>
228
+ </div>
229
+ <div
230
+ v-if="columnsSettings[idx] && columnsSettings[idx].type === 'class'"
231
+ class="text-[.6rem] text-slate-500 pl-[1.2rem]"
232
+ >
233
+ {{ (classesMap[col] && classesMap[col].shape ? classesMap[col].shape : 'Unknown').replace('bitmap', 'mask') }}
234
+ </div>
235
+ <div
236
+ v-if="columnsSettings[idx] && columnsSettings[idx].maxValue"
237
+ class="h-[2px] bg-slate-200 mt-0.5 w-[50px]"
238
+ >
239
+ <div
240
+ class="h-[2px] bg-secondary-500"
241
+ :style="{ width: col * 100 / columnsSettings[idx].maxValue + '%' }"
242
+ />
243
+ </div>
244
+ </td>
245
+ </tr>
246
+ </tbody>
247
+ </table>
248
+ <div
249
+ v-if="!data || !data.length"
250
+ class="text-[.9rem] text-center text-slate-500 mt-5 mb-5"
251
+ >
252
+ 😭 Nothing is found
253
+ </div>
254
+ </div>
255
+ </div>
256
+ </div>
257
+ </div>
258
+ `,
259
+
2
260
  props: {
3
261
  data: {
4
262
  type: Array,
@@ -40,62 +298,97 @@ Vue.component('fast-table', {
40
298
  type: String,
41
299
  default: '',
42
300
  },
43
- showHeader: {
301
+ selectedRows: {
302
+ type: Array,
303
+ default: () => [],
304
+ },
305
+ selectedRadioIdx: {
306
+ type: Number,
307
+ default: 0,
308
+ },
309
+ name: {
310
+ type: String,
311
+ default: '',
312
+ },
313
+ disabled: {
44
314
  type: Boolean,
45
- default: true,
315
+ default: false,
46
316
  },
47
317
  },
318
+
48
319
  data() {
49
320
  return {
50
- columnNumberLimit: 15,
321
+ columnNumberLimit: 50,
51
322
  };
52
323
  },
324
+
53
325
  computed: {
54
326
  canGoBack() {
55
327
  return this.page - 1 > 0;
56
328
  },
329
+
57
330
  canGoForward() {
58
331
  return this.page * this.LIMIT < this.total;
59
332
  },
333
+
334
+ fixColumns() {
335
+ if (!this.settings?.fixColumns) return 0;
336
+
337
+ if (this.settings?.isRowSelectable) return this.settings.fixColumns + 1;
338
+
339
+ return this.settings.fixColumns;
340
+ },
341
+
60
342
  settings() {
343
+ const defaultSettings = {
344
+ showHeaderControls: true,
345
+ };
61
346
  return {
347
+ ...defaultSettings,
62
348
  ...(this.options || {}),
63
349
  };
64
350
  },
351
+
65
352
  LIMIT() {
66
353
  return this.pageSize || 50;
67
354
  },
355
+
356
+ selectedRowsById() {
357
+ return _.keyBy(this.selectedRows, 'id');
358
+ },
359
+
68
360
  classesMap() {
69
361
  return this.projectMeta ? _.keyBy(this.projectMeta.classes, 'title') : {};
70
362
  },
363
+
71
364
  needsRightGradient() {
72
365
  return false;
73
366
  },
367
+
74
368
  columnsSettings() {
75
369
  return this.columnsOptions || Array(this.columns.length || 0).fill({});
76
370
  },
371
+
77
372
  oneOfRowsHasSubtitle() {
78
373
  return this.columnsSettings.find(i => i?.subtitle);
79
374
  },
375
+
376
+ isAllRowsSelected() {
377
+ if (!this.selectedRows?.length) return false;
378
+ return this.data.every(r => this.selectedRowsById[r.id]);
379
+ },
80
380
  },
81
- watch: {
82
- // scrollX() {
83
- // if (this.scrollX.value > 0 && columnNumberLimit.value !== 99999) columnNumberLimit.value = 99999;
84
- // },
85
- },
86
- mounted() {
87
- },
88
- created() {
89
- this.updateData = _.throttle(this._updateData, 200);
90
- },
381
+
91
382
  methods: {
92
383
  _updateData() {
93
384
  this.$emit('filters-changed');
94
385
  },
386
+
95
387
  searchChanged(val) {
96
388
  this.$emit('update:search', val);
97
389
  this.updateData();
98
390
  },
391
+
99
392
  go(dir) {
100
393
  if (dir === -1 && !this.canGoBack) return;
101
394
  if (dir === +1 && !this.canGoForward) return;
@@ -103,7 +396,10 @@ Vue.component('fast-table', {
103
396
  this.$emit('update:page', this.page + dir);
104
397
  this.updateData();
105
398
  },
399
+
106
400
  onHeaderCellClick(idx) {
401
+ if (this.columnsSettings?.[idx]?.disableSort) return;
402
+
107
403
  let order = this.sort.order;
108
404
 
109
405
  if (this.sort.column !== idx) order = 'asc';
@@ -112,90 +408,62 @@ Vue.component('fast-table', {
112
408
  this.$emit('update:sort', { order, column: idx });
113
409
  this.updateData();
114
410
  },
411
+
412
+ selectAllRows() {
413
+ if (this.isAllRowsSelected) {
414
+ const curRowsSet = new Set(this.data.map(r => r.id));
415
+ this.$emit('update:selected-rows', [...this.selectedRows.filter(r => !curRowsSet.has(r.id))]);
416
+ } else {
417
+ const selected = [...this.selectedRows];
418
+
419
+ this.data.forEach((row) => {
420
+ if (this.selectedRowsById[row.id]) return;
421
+
422
+ selected.push(row);
423
+ });
424
+
425
+ this.$emit('update:selected-rows', selected);
426
+ }
427
+ },
428
+
429
+ updateSelectedRows(row) {
430
+ const isRowSelected = this.selectedRowsById[row.id];
431
+
432
+ if (!isRowSelected) {
433
+ this.$emit('update:selected-rows', [...this.selectedRows, row]);
434
+ } else {
435
+ this.$emit('update:selected-rows', [...this.selectedRows.filter(p => p.id !== row.id)]);
436
+ }
437
+ },
438
+
439
+ updateSelectedRadio(row) {
440
+ console.log('updateSelectedRadio', row);
441
+ row = _.cloneDeep(row)
442
+ this.$emit('update:selected-rows', [row])
443
+ },
444
+
445
+ handleScroll() {
446
+ const scrollX = this.$refs.scrollBox.scrollLeft;
447
+ if (scrollX > 0 && this.columnNumberLimit !== 99999) {
448
+ this.columnNumberLimit = 99999;
449
+ }
450
+ },
451
+
115
452
  highlight(text) {
116
453
  if (!this.search) return text;
117
- return text.toString().replace(new RegExp(this.search, 'gi'), match => `<span class="bg-yellow-400">${match}</span>`);
454
+ return text.toString().replace(new RegExp(this.search, 'gi'), match => '<span class="bg-yellow-400">'+match+'</span>');
118
455
  },
119
456
  },
120
- template: `
121
- <div class="tailwind fast-table">
122
- <div class="rounded-lg border border-slate-200 shadow bg-white" ref="wrapper">
123
- <div v-if ="showHeader === true" class="py-2 px-2 md:px-5 md:py-4 flex flex-col md:flex-row gap-2 justify-between items-center">
124
- <div class="relative w-full md:max-w-[18rem]">
125
- <i class="zmdi zmdi-search h-4 absolute top-2 left-2.5 opacity-50"></i>
126
- <!--<img alt="Search" src="~assets/icons/search.png" class="h-4 absolute top-2 left-2.5 opacity-50" />
127
- <img alt="Close" src="~assets/icons/close-light.svg" class="h-4 absolute top-2.5 right-3 opacity-50 cursor-pointer" v-if="search" @click="searchChanged('')" />
128
- -->
129
- <i class="zmdi zmdi-close h-4 absolute top-2.5 right-3 opacity-50 cursor-pointer" v-if="search" @click="searchChanged('')"></i>
130
- <input :value="search" @input="searchChanged($event.target.value)" type="text" placeholder="Search" class="text-sm rounded-md px-3 py-1.5 text-gray-900 shadow-sm placeholder:text-gray-400 border border-slate-200 w-full pl-8 bg-slate-50" @keydown.esc="searchChanged('')" />
131
- </div>
132
- <div class="text-[.9rem] text-slate-500 flex items-center gap-0 w-full md:w-auto whitespace-nowrap" v-if="(data?.length)">
133
- <button aria-label="Go back" @click="go(-1)" class="hover:text-secondary-500 text-[1.3rem] md:ml-0 -ml-2" :class="{ '!text-slate-200 cursor-default': !canGoBack }"><i class="zmdi zmdi-chevron-left" /></button>
134
- <span>Rows {{ Math.min((page - 1) * LIMIT + 1, total) }}-{{ Math.min(total, page * LIMIT) }} of {{ total }}</span>
135
- <button aria-label="Go forward" @click="go(+1)" class="hover:text-secondary-500 text-[1.3rem]" :class="{ '!text-slate-200 cursor-default': !canGoForward }"><i class="zmdi zmdi-chevron-right" /></button>
136
- <div class="md:hidden w-full flex-1"></div>
137
- </div>
138
- </div>
139
- <div class="overflow-x-auto border-t border-slate-200 relative" ref="scrollBox">
140
- <div :class="{ 'opacity-100 !flex': needsRightGradient }" class="bg-gradient-to-r from-transparent to-white absolute right-0 top-0 bottom-0 z-10 w-56 opacity-0 transition-opacity hidden">
141
- <i class="zmdi zmdi-chevron-right absolute top-1/2 right-2 text-[1.5rem] text-slate-400 -translate-y-1/2" />
142
- </div>
143
- <table class="w-full text-[.8rem] md:text-[.9rem] mb-1" ref="tableBox">
144
- <thead>
145
- <tr>
146
- <th
147
- v-for="(c,idx) in columns.slice(0, columnNumberLimit)"
148
- class="px-2 md:px-3 py-2.5 whitespace-nowrap first:pl-3 last:pr-3 md:first:pl-6 md:last:pr-6 first:text-left cursor-pointer sticky top-0 bg-slate-50 box-content shadow-[inset_0_-2px_0_#dfe6ec] group"
149
- :class="{ 'first:sticky first:left-0 first:z-20 first:shadow-[inset_-2px_-2px_0_#dfe6ec]': settings.fixColumns }"
150
- @click="onHeaderCellClick(idx)"
151
- >
152
- <div class="flex items-center">
153
- <span>{{ c }}</span>
154
- <el-tooltip v-if="columnsSettings[idx]?.tooltip">
155
- <div slot="content" v-html="columnsSettings[idx].tooltip" />
156
- <i class="zmdi zmdi-info-outline ml5 text-slate-500 text-[12px]" />
157
- </el-tooltip>
158
- <span class="w-[16px] text-[12px] text-secondary-500 ml-1">
159
- <i class="zmdi zmdi-sort-amount-asc ml5 opacity-0 group-hover:opacity-100 text-slate-400 transition-opacity" v-if="sort.column !== idx" />
160
- <i class="zmdi zmdi-sort-amount-asc ml5" v-if="sort.column === idx && sort.order === 'asc'" />
161
- <i class="zmdi zmdi-sort-amount-desc ml5" v-if="sort.column === idx && sort.order === 'desc'" />
162
- </span>
163
- </div>
164
- <div class="text-[.7rem] text-slate-500 font-normal text-left" v-if="oneOfRowsHasSubtitle">
165
- {{ columnsSettings[idx]?.subtitle || 'ㅤ' }}
166
- </div>
167
- </th>
168
- </tr>
169
- </thead>
170
- <tbody>
171
- <tr v-for="row in data" class="border-b border-gray-200 last:border-0 group" :class="{ 'cursor-pointer': settings.isRowClickable }" @click="settings.isRowClickable && $emit('row-click', { idx: row.idx, row: row.items })">
172
- <td
173
- v-for="(col,idx) in row.items.slice(0, columnNumberLimit)"
174
- class="px-2 md:px-3 py-2 bg-white first:pl-3 last:pr-3 md:first:pl-6 md:last:pr-6 group-hover:bg-slate-50 group"
175
- :class="{ 'first:sticky first:left-0 first:z-10 first:shadow-[inset_-2px_0_0_#dfe6ec]': settings.fixColumns, 'cursor-pointer': settings.isCellClickable }"
176
- @click="settings.isCellClickable && $emit('cell-click', { idx: row.idx, row: row.items , column: idx})"
177
- >
178
- <div class="flex items-center whitespace-nowrap">
179
- <span v-if="columnsSettings[idx]?.type === 'class'" class="w-3 h-3 rounded-sm flex mr-1.5 flex-none" :style="{ backgroundColor: (classesMap[col] || { color: '#00ff00' }).color }"></span>
180
- <span v-html="highlight(col)"></span>
181
- <span v-if="idx === 0 && settings.isRowClickable" class="opacity-0 transition-all duration-300 text-secondary-500 text-xs group-hover:translate-x-2 group-hover:opacity-100">➔</span>
182
- <span class="text-slate-400 ml-0.5 text-[.7rem]" v-if="col != '' && columnsSettings[idx]?.postfix">{{ columnsSettings[idx].postfix }}</span>
183
- </div>
184
- <div v-if="columnsSettings[idx]?.type === 'class'" class="text-[.6rem] text-slate-500 pl-[1.2rem]">
185
- {{ (classesMap[col]?.shape || 'Unknown').replace('bitmap', 'mask') }}
186
- </div>
187
- <div v-if="columnsSettings[idx]?.maxValue" class="h-[2px] bg-slate-200 mt-0.5 w-[50px]">
188
- <div class="h-[2px] bg-secondary-500" :style="{ width: col * 100 / columnsSettings[idx].maxValue + '%' }" />
189
- </div>
190
- </td>
191
- </tr>
192
- </tbody>
193
- </table>
194
- <div v-if="!(data?.length)" class="text-[.9rem] text-center text-slate-500 mt-5 mb-5">
195
- 😭 Nothing is found
196
- </div>
197
- </div>
198
- </div>
199
- </div>
200
- `,
201
- });
457
+
458
+ mounted() {
459
+ this.$refs.scrollBox.addEventListener('scroll', this.handleScroll);
460
+ },
461
+
462
+ beforeDestroy() {
463
+ this.$refs.scrollBox.addEventListener('scroll', this.handleScroll);
464
+ },
465
+
466
+ created() {
467
+ this.updateData = _.debounce(this._updateData, 300);
468
+ },
469
+ });
@@ -693,4 +693,28 @@
693
693
  .tailwind .md\:last\:pr-6:last-child {
694
694
  padding-right: 1.5rem;
695
695
  }
696
+ }
697
+
698
+ .ellipsis {
699
+ overflow: hidden;
700
+ text-overflow: ellipsis;
701
+ }
702
+
703
+ .row-radio .el-radio__inner {
704
+ border-width: 1px;
705
+ }
706
+
707
+ .header-left-side-start-cls {
708
+ color: black;
709
+ }
710
+
711
+ .sly-fast-table-disable-overlay {
712
+ position: absolute;
713
+ top: 0px;
714
+ left: 0px;
715
+ right: 0px;
716
+ bottom: 0px;
717
+ z-index: 2000;
718
+ background-color: rgba(227, 230, 236, 0.5);
719
+ border-radius: 0.5rem;
696
720
  }
@@ -1,6 +1,6 @@
1
1
  <link rel="stylesheet" href="./sly/css/app/widgets/fast_table/style.css" />
2
2
 
3
- <fast-table
3
+ <fast-table
4
4
  style="width: {{{widget._width}}}"
5
5
  :page.sync="state.{{{widget.widget_id}}}.page"
6
6
  :total="data.{{{widget.widget_id}}}.total"
@@ -13,6 +13,13 @@
13
13
  :search.sync="state.{{{widget.widget_id}}}.search"
14
14
  :data="data.{{{widget.widget_id}}}.data"
15
15
  :show-header="data.{{{widget.widget_id}}}.showHeader"
16
+ :selected-rows="state.{{{widget.widget_id}}}.selectedRows"
17
+ :selected-radio-idx="state.{{{widget.widget_id}}}.selectedRows && state.{{{widget.widget_id}}}.selectedRows.length > 0 ? state.{{{widget.widget_id}}}.selectedRows[0].idx : null"
18
+ :disabled="data.{{{widget.widget_id}}}.disabled"
19
+ @update:selected-rows="(rows) => {
20
+ state.{{{widget.widget_id}}}.selectedRows = rows;
21
+ data.{{{widget.widget_id}}}.selectionChangedHandled && post('/{{{widget.widget_id}}}/selection_changed_cb');
22
+ }"
16
23
  {%
17
24
  if
18
25
  widget._row_click_handled
@@ -29,5 +36,38 @@
29
36
  {%
30
37
  endif
31
38
  %}
32
- @filters-changed="post('/{{{widget.widget_id}}}/update_data_cb')"
33
- />
39
+ @filters-changed="state.reactToChanges = false; post('/{{{widget.widget_id}}}/update_data_cb');"
40
+ >
41
+ {% if widget._header_left_content %}
42
+ <span slot="header-left-side-start" class="header-left-side-start-cls">
43
+ <div style="padding-right: 10px">
44
+ {{{ widget._header_left_content }}}
45
+ </div>
46
+ </span>
47
+ {% endif %}
48
+ {% if widget._header_right_content %}
49
+ <span slot="header-left-side-end" class="header-right-side-end-cls">
50
+ <div style="padding-left: 10px">
51
+ {{{ widget._header_right_content }}}
52
+ </div>
53
+ </span>
54
+ {% endif %}
55
+ <span slot="cell-content" slot-scope="{ row, column, cellValue, idx }">
56
+ {% for column_data in widget._columns_data %}
57
+ {% if column_data.is_widget %}
58
+ <div
59
+ v-if="column === '{{{ column_data.name }}}'"
60
+ :class="data.{{{widget.widget_id}}}.columnsOptions?.[{{{ loop.index0 }}}]?.classes || ''" JINJA TEMPLATE
61
+ :style="data.{{{widget.widget_id}}}.columnsOptions?.[{{{ loop.index0 }}}]?.style || ''"
62
+ @click.native.stop
63
+ >
64
+ {{{ column_data.widget_html }}}
65
+ </div>
66
+ {% else %}
67
+ <div v-if="column === '{{{ column_data.name }}}'">
68
+ <span v-html="highlight(col)"></span>
69
+ </div>
70
+ {% endif %}
71
+ {% endfor %}
72
+ </span>
73
+ </fast-table>
@@ -1,6 +1,7 @@
1
- from typing import List, Dict, Union
2
- from supervisely.app.jinja2 import create_env
1
+ from typing import Dict, List, Union
2
+
3
3
  from supervisely.app.content import DataJson, StateJson
4
+ from supervisely.app.jinja2 import create_env
4
5
  from supervisely.app.widgets import Widget
5
6
 
6
7
 
@@ -154,3 +155,10 @@ class RadioTable(Widget):
154
155
  raise ValueError(f'Row with index "{row_index}" does not exist')
155
156
  StateJson()[self.widget_id]["selectedRow"] = row_index
156
157
  StateJson().send_changes()
158
+
159
+ def select_row_by_value(self, column, value):
160
+ for idx, row in enumerate(self._rows):
161
+ if row[self._columns.index(column)] == value:
162
+ self.select_row(idx)
163
+ return
164
+ raise ValueError(f'Value "{value}" not found in column "{column}"')
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
- from supervisely.app import StateJson, DataJson
3
- from supervisely.app.widgets import Widget, ConditionalWidget
4
- from typing import List, Dict, Optional
2
+
3
+ from typing import Dict, List, Optional
4
+
5
+ from supervisely.app import DataJson, StateJson
6
+ from supervisely.app.widgets import ConditionalWidget, Widget
5
7
 
6
8
  try:
7
9
  from typing import Literal
@@ -183,7 +185,7 @@ class Select(ConditionalWidget):
183
185
  self._changes_handled = True
184
186
 
185
187
  @server.post(route_path)
186
- async def _click():
188
+ def _click():
187
189
  res = self.get_value()
188
190
  func(res)
189
191