supervisely 6.73.419__py3-none-any.whl → 6.73.421__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.
- supervisely/api/api.py +10 -5
- supervisely/api/app_api.py +71 -4
- supervisely/api/module_api.py +4 -0
- supervisely/api/nn/deploy_api.py +15 -9
- supervisely/api/nn/ecosystem_models_api.py +201 -0
- supervisely/api/nn/neural_network_api.py +12 -3
- supervisely/api/project_api.py +35 -6
- supervisely/api/task_api.py +5 -1
- supervisely/app/widgets/__init__.py +8 -1
- supervisely/app/widgets/agent_selector/template.html +1 -0
- supervisely/app/widgets/deploy_model/__init__.py +0 -0
- supervisely/app/widgets/deploy_model/deploy_model.py +729 -0
- supervisely/app/widgets/dropdown_checkbox_selector/__init__.py +0 -0
- supervisely/app/widgets/dropdown_checkbox_selector/dropdown_checkbox_selector.py +87 -0
- supervisely/app/widgets/dropdown_checkbox_selector/template.html +12 -0
- supervisely/app/widgets/ecosystem_model_selector/__init__.py +0 -0
- supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +190 -0
- supervisely/app/widgets/experiment_selector/experiment_selector.py +447 -264
- supervisely/app/widgets/fast_table/fast_table.py +402 -74
- supervisely/app/widgets/fast_table/script.js +364 -96
- supervisely/app/widgets/fast_table/style.css +24 -0
- supervisely/app/widgets/fast_table/template.html +43 -3
- supervisely/app/widgets/radio_table/radio_table.py +10 -2
- supervisely/app/widgets/select/select.py +6 -4
- supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +18 -0
- supervisely/app/widgets/tabs/tabs.py +22 -6
- supervisely/app/widgets/tabs/template.html +5 -1
- supervisely/nn/artifacts/__init__.py +1 -1
- supervisely/nn/artifacts/artifacts.py +10 -2
- supervisely/nn/artifacts/detectron2.py +1 -0
- supervisely/nn/artifacts/hrda.py +1 -0
- supervisely/nn/artifacts/mmclassification.py +20 -0
- supervisely/nn/artifacts/mmdetection.py +5 -3
- supervisely/nn/artifacts/mmsegmentation.py +1 -0
- supervisely/nn/artifacts/ritm.py +1 -0
- supervisely/nn/artifacts/rtdetr.py +1 -0
- supervisely/nn/artifacts/unet.py +1 -0
- supervisely/nn/artifacts/utils.py +3 -0
- supervisely/nn/artifacts/yolov5.py +2 -0
- supervisely/nn/artifacts/yolov8.py +1 -0
- supervisely/nn/benchmark/semantic_segmentation/metric_provider.py +18 -18
- supervisely/nn/experiments.py +9 -0
- supervisely/nn/inference/gui/serving_gui_template.py +39 -13
- supervisely/nn/inference/inference.py +160 -94
- supervisely/nn/inference/predict_app/__init__.py +0 -0
- supervisely/nn/inference/predict_app/gui/__init__.py +0 -0
- supervisely/nn/inference/predict_app/gui/classes_selector.py +91 -0
- supervisely/nn/inference/predict_app/gui/gui.py +710 -0
- supervisely/nn/inference/predict_app/gui/input_selector.py +165 -0
- supervisely/nn/inference/predict_app/gui/model_selector.py +79 -0
- supervisely/nn/inference/predict_app/gui/output_selector.py +139 -0
- supervisely/nn/inference/predict_app/gui/preview.py +93 -0
- supervisely/nn/inference/predict_app/gui/settings_selector.py +184 -0
- supervisely/nn/inference/predict_app/gui/tags_selector.py +110 -0
- supervisely/nn/inference/predict_app/gui/utils.py +282 -0
- supervisely/nn/inference/predict_app/predict_app.py +184 -0
- supervisely/nn/inference/uploader.py +9 -5
- supervisely/nn/model/prediction.py +2 -0
- supervisely/nn/model/prediction_session.py +20 -3
- supervisely/nn/training/gui/gui.py +131 -44
- supervisely/nn/training/gui/model_selector.py +8 -6
- supervisely/nn/training/gui/train_val_splits_selector.py +122 -70
- supervisely/nn/training/gui/training_artifacts.py +0 -5
- supervisely/nn/training/train_app.py +161 -44
- supervisely/project/project.py +211 -73
- supervisely/template/experiment/experiment.html.jinja +74 -17
- supervisely/template/experiment/experiment_generator.py +258 -112
- supervisely/template/experiment/header.html.jinja +31 -13
- supervisely/template/experiment/sly-style.css +7 -2
- {supervisely-6.73.419.dist-info → supervisely-6.73.421.dist-info}/METADATA +3 -1
- {supervisely-6.73.419.dist-info → supervisely-6.73.421.dist-info}/RECORD +75 -57
- supervisely/app/widgets/experiment_selector/style.css +0 -27
- supervisely/app/widgets/experiment_selector/template.html +0 -61
- {supervisely-6.73.419.dist-info → supervisely-6.73.421.dist-info}/LICENSE +0 -0
- {supervisely-6.73.419.dist-info → supervisely-6.73.421.dist-info}/WHEEL +0 -0
- {supervisely-6.73.419.dist-info → supervisely-6.73.421.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.419.dist-info → supervisely-6.73.421.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
|
-
|
|
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:
|
|
315
|
+
default: false,
|
|
46
316
|
},
|
|
47
317
|
},
|
|
318
|
+
|
|
48
319
|
data() {
|
|
49
320
|
return {
|
|
50
|
-
columnNumberLimit:
|
|
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
|
-
|
|
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 =>
|
|
454
|
+
return text.toString().replace(new RegExp(this.search, 'gi'), match => '<span class="bg-yellow-400">'+match+'</span>');
|
|
118
455
|
},
|
|
119
456
|
},
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
2
|
-
|
|
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
|
-
|
|
3
|
-
from
|
|
4
|
-
|
|
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
|
-
|
|
188
|
+
def _click():
|
|
187
189
|
res = self.get_value()
|
|
188
190
|
func(res)
|
|
189
191
|
|