supervisely 6.73.410__py3-none-any.whl → 6.73.470__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.

Potentially problematic release.


This version of supervisely might be problematic. Click here for more details.

Files changed (190) hide show
  1. supervisely/__init__.py +136 -1
  2. supervisely/_utils.py +81 -0
  3. supervisely/annotation/json_geometries_map.py +2 -0
  4. supervisely/annotation/label.py +80 -3
  5. supervisely/api/annotation_api.py +9 -9
  6. supervisely/api/api.py +67 -43
  7. supervisely/api/app_api.py +72 -5
  8. supervisely/api/dataset_api.py +108 -33
  9. supervisely/api/entity_annotation/figure_api.py +113 -49
  10. supervisely/api/image_api.py +82 -0
  11. supervisely/api/module_api.py +10 -0
  12. supervisely/api/nn/deploy_api.py +15 -9
  13. supervisely/api/nn/ecosystem_models_api.py +201 -0
  14. supervisely/api/nn/neural_network_api.py +12 -3
  15. supervisely/api/pointcloud/pointcloud_api.py +38 -0
  16. supervisely/api/pointcloud/pointcloud_episode_annotation_api.py +3 -0
  17. supervisely/api/project_api.py +213 -6
  18. supervisely/api/task_api.py +11 -1
  19. supervisely/api/video/video_annotation_api.py +4 -2
  20. supervisely/api/video/video_api.py +79 -1
  21. supervisely/api/video/video_figure_api.py +24 -11
  22. supervisely/api/volume/volume_api.py +38 -0
  23. supervisely/app/__init__.py +1 -1
  24. supervisely/app/content.py +14 -6
  25. supervisely/app/fastapi/__init__.py +1 -0
  26. supervisely/app/fastapi/custom_static_files.py +1 -1
  27. supervisely/app/fastapi/multi_user.py +88 -0
  28. supervisely/app/fastapi/subapp.py +175 -42
  29. supervisely/app/fastapi/templating.py +1 -1
  30. supervisely/app/fastapi/websocket.py +77 -9
  31. supervisely/app/singleton.py +21 -0
  32. supervisely/app/v1/app_service.py +18 -2
  33. supervisely/app/v1/constants.py +7 -1
  34. supervisely/app/widgets/__init__.py +11 -1
  35. supervisely/app/widgets/agent_selector/template.html +1 -0
  36. supervisely/app/widgets/card/card.py +20 -0
  37. supervisely/app/widgets/dataset_thumbnail/dataset_thumbnail.py +11 -2
  38. supervisely/app/widgets/dataset_thumbnail/template.html +3 -1
  39. supervisely/app/widgets/deploy_model/deploy_model.py +750 -0
  40. supervisely/app/widgets/dialog/dialog.py +12 -0
  41. supervisely/app/widgets/dialog/template.html +2 -1
  42. supervisely/app/widgets/dropdown_checkbox_selector/__init__.py +0 -0
  43. supervisely/app/widgets/dropdown_checkbox_selector/dropdown_checkbox_selector.py +87 -0
  44. supervisely/app/widgets/dropdown_checkbox_selector/template.html +12 -0
  45. supervisely/app/widgets/ecosystem_model_selector/__init__.py +0 -0
  46. supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +195 -0
  47. supervisely/app/widgets/experiment_selector/experiment_selector.py +454 -263
  48. supervisely/app/widgets/fast_table/fast_table.py +713 -126
  49. supervisely/app/widgets/fast_table/script.js +492 -95
  50. supervisely/app/widgets/fast_table/style.css +54 -0
  51. supervisely/app/widgets/fast_table/template.html +45 -5
  52. supervisely/app/widgets/heatmap/__init__.py +0 -0
  53. supervisely/app/widgets/heatmap/heatmap.py +523 -0
  54. supervisely/app/widgets/heatmap/script.js +378 -0
  55. supervisely/app/widgets/heatmap/style.css +227 -0
  56. supervisely/app/widgets/heatmap/template.html +21 -0
  57. supervisely/app/widgets/input_tag/input_tag.py +102 -15
  58. supervisely/app/widgets/input_tag_list/__init__.py +0 -0
  59. supervisely/app/widgets/input_tag_list/input_tag_list.py +274 -0
  60. supervisely/app/widgets/input_tag_list/template.html +70 -0
  61. supervisely/app/widgets/radio_table/radio_table.py +10 -2
  62. supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
  63. supervisely/app/widgets/radio_tabs/template.html +1 -0
  64. supervisely/app/widgets/select/select.py +6 -4
  65. supervisely/app/widgets/select_dataset/select_dataset.py +6 -0
  66. supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +83 -7
  67. supervisely/app/widgets/table/table.py +68 -13
  68. supervisely/app/widgets/tabs/tabs.py +22 -6
  69. supervisely/app/widgets/tabs/template.html +5 -1
  70. supervisely/app/widgets/transfer/style.css +3 -0
  71. supervisely/app/widgets/transfer/template.html +3 -1
  72. supervisely/app/widgets/transfer/transfer.py +48 -45
  73. supervisely/app/widgets/tree_select/tree_select.py +2 -0
  74. supervisely/convert/image/csv/csv_converter.py +24 -15
  75. supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +43 -41
  76. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +75 -51
  77. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +137 -124
  78. supervisely/convert/video/video_converter.py +2 -2
  79. supervisely/geometry/polyline_3d.py +110 -0
  80. supervisely/io/env.py +161 -1
  81. supervisely/nn/artifacts/__init__.py +1 -1
  82. supervisely/nn/artifacts/artifacts.py +10 -2
  83. supervisely/nn/artifacts/detectron2.py +1 -0
  84. supervisely/nn/artifacts/hrda.py +1 -0
  85. supervisely/nn/artifacts/mmclassification.py +20 -0
  86. supervisely/nn/artifacts/mmdetection.py +5 -3
  87. supervisely/nn/artifacts/mmsegmentation.py +1 -0
  88. supervisely/nn/artifacts/ritm.py +1 -0
  89. supervisely/nn/artifacts/rtdetr.py +1 -0
  90. supervisely/nn/artifacts/unet.py +1 -0
  91. supervisely/nn/artifacts/utils.py +3 -0
  92. supervisely/nn/artifacts/yolov5.py +2 -0
  93. supervisely/nn/artifacts/yolov8.py +1 -0
  94. supervisely/nn/benchmark/semantic_segmentation/metric_provider.py +18 -18
  95. supervisely/nn/experiments.py +9 -0
  96. supervisely/nn/inference/cache.py +37 -17
  97. supervisely/nn/inference/gui/serving_gui_template.py +39 -13
  98. supervisely/nn/inference/inference.py +953 -211
  99. supervisely/nn/inference/inference_request.py +15 -8
  100. supervisely/nn/inference/instance_segmentation/instance_segmentation.py +1 -0
  101. supervisely/nn/inference/object_detection/object_detection.py +1 -0
  102. supervisely/nn/inference/predict_app/__init__.py +0 -0
  103. supervisely/nn/inference/predict_app/gui/__init__.py +0 -0
  104. supervisely/nn/inference/predict_app/gui/classes_selector.py +160 -0
  105. supervisely/nn/inference/predict_app/gui/gui.py +915 -0
  106. supervisely/nn/inference/predict_app/gui/input_selector.py +344 -0
  107. supervisely/nn/inference/predict_app/gui/model_selector.py +77 -0
  108. supervisely/nn/inference/predict_app/gui/output_selector.py +179 -0
  109. supervisely/nn/inference/predict_app/gui/preview.py +93 -0
  110. supervisely/nn/inference/predict_app/gui/settings_selector.py +881 -0
  111. supervisely/nn/inference/predict_app/gui/tags_selector.py +110 -0
  112. supervisely/nn/inference/predict_app/gui/utils.py +399 -0
  113. supervisely/nn/inference/predict_app/predict_app.py +176 -0
  114. supervisely/nn/inference/session.py +47 -39
  115. supervisely/nn/inference/tracking/bbox_tracking.py +5 -1
  116. supervisely/nn/inference/tracking/point_tracking.py +5 -1
  117. supervisely/nn/inference/tracking/tracker_interface.py +4 -0
  118. supervisely/nn/inference/uploader.py +9 -5
  119. supervisely/nn/model/model_api.py +44 -22
  120. supervisely/nn/model/prediction.py +15 -1
  121. supervisely/nn/model/prediction_session.py +70 -14
  122. supervisely/nn/prediction_dto.py +7 -0
  123. supervisely/nn/tracker/__init__.py +6 -8
  124. supervisely/nn/tracker/base_tracker.py +54 -0
  125. supervisely/nn/tracker/botsort/__init__.py +1 -0
  126. supervisely/nn/tracker/botsort/botsort_config.yaml +30 -0
  127. supervisely/nn/tracker/botsort/osnet_reid/__init__.py +0 -0
  128. supervisely/nn/tracker/botsort/osnet_reid/osnet.py +566 -0
  129. supervisely/nn/tracker/botsort/osnet_reid/osnet_reid_interface.py +88 -0
  130. supervisely/nn/tracker/botsort/tracker/__init__.py +0 -0
  131. supervisely/nn/tracker/{bot_sort → botsort/tracker}/basetrack.py +1 -2
  132. supervisely/nn/tracker/{utils → botsort/tracker}/gmc.py +51 -59
  133. supervisely/nn/tracker/{deep_sort/deep_sort → botsort/tracker}/kalman_filter.py +71 -33
  134. supervisely/nn/tracker/botsort/tracker/matching.py +202 -0
  135. supervisely/nn/tracker/{bot_sort/bot_sort.py → botsort/tracker/mc_bot_sort.py} +68 -81
  136. supervisely/nn/tracker/botsort_tracker.py +273 -0
  137. supervisely/nn/tracker/calculate_metrics.py +264 -0
  138. supervisely/nn/tracker/utils.py +273 -0
  139. supervisely/nn/tracker/visualize.py +520 -0
  140. supervisely/nn/training/gui/gui.py +152 -49
  141. supervisely/nn/training/gui/hyperparameters_selector.py +1 -1
  142. supervisely/nn/training/gui/model_selector.py +8 -6
  143. supervisely/nn/training/gui/train_val_splits_selector.py +144 -71
  144. supervisely/nn/training/gui/training_artifacts.py +3 -1
  145. supervisely/nn/training/train_app.py +225 -46
  146. supervisely/project/pointcloud_episode_project.py +12 -8
  147. supervisely/project/pointcloud_project.py +12 -8
  148. supervisely/project/project.py +221 -75
  149. supervisely/template/experiment/experiment.html.jinja +105 -55
  150. supervisely/template/experiment/experiment_generator.py +258 -112
  151. supervisely/template/experiment/header.html.jinja +31 -13
  152. supervisely/template/experiment/sly-style.css +7 -2
  153. supervisely/versions.json +3 -1
  154. supervisely/video/sampling.py +42 -20
  155. supervisely/video/video.py +41 -12
  156. supervisely/video_annotation/video_figure.py +38 -4
  157. supervisely/volume/stl_converter.py +2 -0
  158. supervisely/worker_api/agent_rpc.py +24 -1
  159. supervisely/worker_api/rpc_servicer.py +31 -7
  160. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/METADATA +22 -14
  161. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/RECORD +167 -148
  162. supervisely_lib/__init__.py +6 -1
  163. supervisely/app/widgets/experiment_selector/style.css +0 -27
  164. supervisely/app/widgets/experiment_selector/template.html +0 -61
  165. supervisely/nn/tracker/bot_sort/__init__.py +0 -21
  166. supervisely/nn/tracker/bot_sort/fast_reid_interface.py +0 -152
  167. supervisely/nn/tracker/bot_sort/matching.py +0 -127
  168. supervisely/nn/tracker/bot_sort/sly_tracker.py +0 -401
  169. supervisely/nn/tracker/deep_sort/__init__.py +0 -6
  170. supervisely/nn/tracker/deep_sort/deep_sort/__init__.py +0 -1
  171. supervisely/nn/tracker/deep_sort/deep_sort/detection.py +0 -49
  172. supervisely/nn/tracker/deep_sort/deep_sort/iou_matching.py +0 -81
  173. supervisely/nn/tracker/deep_sort/deep_sort/linear_assignment.py +0 -202
  174. supervisely/nn/tracker/deep_sort/deep_sort/nn_matching.py +0 -176
  175. supervisely/nn/tracker/deep_sort/deep_sort/track.py +0 -166
  176. supervisely/nn/tracker/deep_sort/deep_sort/tracker.py +0 -145
  177. supervisely/nn/tracker/deep_sort/deep_sort.py +0 -301
  178. supervisely/nn/tracker/deep_sort/generate_clip_detections.py +0 -90
  179. supervisely/nn/tracker/deep_sort/preprocessing.py +0 -70
  180. supervisely/nn/tracker/deep_sort/sly_tracker.py +0 -273
  181. supervisely/nn/tracker/tracker.py +0 -285
  182. supervisely/nn/tracker/utils/kalman_filter.py +0 -492
  183. supervisely/nn/tracking/__init__.py +0 -1
  184. supervisely/nn/tracking/boxmot.py +0 -114
  185. supervisely/nn/tracking/tracking.py +0 -24
  186. /supervisely/{nn/tracker/utils → app/widgets/deploy_model}/__init__.py +0 -0
  187. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/LICENSE +0 -0
  188. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/WHEEL +0 -0
  189. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/entry_points.txt +0 -0
  190. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,291 @@
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 v-if="settings.searchPosition !== 'right'" 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 v-if="settings.searchPosition === 'right'" class="relative w-full md:max-w-[14rem]">
39
+ <i class="zmdi zmdi-search h-4 absolute top-2 left-2.5 opacity-50" />
40
+ <i
41
+ v-if="search"
42
+ class="zmdi zmdi-close h-4 absolute top-2.5 right-3 opacity-50 cursor-pointer"
43
+ @click="searchChanged('')"
44
+ />
45
+ <input
46
+ :value="search"
47
+ type="text"
48
+ :placeholder="\`\${name ? \`Search for \${name}...\` : 'Search'}\`"
49
+ 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"
50
+ @input="searchChanged($event.target.value)"
51
+ @keydown.esc="searchChanged('')"
52
+ >
53
+ </div>
54
+ <div
55
+ v-if="data && data.length"
56
+ class="text-[.9rem] text-slate-500 flex items-center gap-0 w-full md:w-auto whitespace-nowrap"
57
+ >
58
+ <button
59
+ aria-label="Go back"
60
+ class="hover:text-secondary-500 text-[1.3rem] md:ml-0 -ml-2"
61
+ :class="{ '!text-slate-200 cursor-default': !canGoBack }"
62
+ @click="go(-1)"
63
+ >
64
+ <i class="zmdi zmdi-chevron-left" />
65
+ </button>
66
+ <span>Rows {{ Math.min((page - 1) * LIMIT + 1, total) }}-{{ Math.min(total, page * LIMIT) }} of {{ total }}</span>
67
+ <button
68
+ aria-label="Go forward"
69
+ class="hover:text-secondary-500 text-[1.3rem]"
70
+ :class="{ '!text-slate-200 cursor-default': !canGoForward }"
71
+ @click="go(+1)"
72
+ >
73
+ <i class="zmdi zmdi-chevron-right" />
74
+ </button>
75
+ <div class="md:hidden w-full flex-1" />
76
+ </div>
77
+ </div>
78
+ <div
79
+ ref="scrollBox"
80
+ class="overflow-x-auto border-slate-200 relative"
81
+ :class="{ 'border-t': settings.showHeaderControls}"
82
+ >
83
+ <div
84
+ :class="{ 'opacity-100 !flex': needsRightGradient }"
85
+ 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"
86
+ >
87
+ <i class="zmdi zmdi-chevron-right absolute top-1/2 right-2 text-[1.5rem] text-slate-400 -translate-y-1/2" />
88
+ </div>
89
+ <table
90
+ ref="tableBox"
91
+ class="w-full text-[.8rem] md:text-[.9rem] mb-1"
92
+ :key="'table-' + page"
93
+ >
94
+ <thead>
95
+ <tr>
96
+ <th
97
+ v-if="settings.isRadio"
98
+ 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"
99
+ :class="{ 'first:sticky first:left-0 first:z-20 first:shadow-[inset_-2px_-2px_0_#dfe6ec]': fixColumns }"
100
+ style="width: 20px;"
101
+ >
102
+ </th>
103
+ <th
104
+ v-if="settings.isRowSelectable && (!settings.maxSelectedRows || settings.maxSelectedRows > 1)"
105
+ 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"
106
+ :class="{ 'first:sticky first:left-0 first:z-20 first:shadow-[inset_-2px_-2px_0_#dfe6ec]': fixColumns }"
107
+ >
108
+ <el-checkbox
109
+ v-if="settings.isRowSelectable && (!settings.maxSelectedRows || settings.maxSelectedRows > 1)"
110
+ size="small"
111
+ v-model="headerCheckboxModel"
112
+ :indeterminate="isSomeOnPageSelected && !isAllOnPageSelected"
113
+ :key="'headercb-' + page + '-' + (data && data.map(r => r.idx).join(',')) + '-' + (selectedRows && selectedRows.map(r => r.idx).join(',')) + '-' + (isAllOnPageSelected?1:0) + '-' + (isSomeOnPageSelected?1:0)"
114
+ style="transform: scale(1.2);"
115
+ />
116
+ </th>
117
+ <th
118
+ v-if="settings.isRowSelectable && settings.maxSelectedRows === 1"
119
+ 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"
120
+ :class="{ 'first:sticky first:left-0 first:z-20 first:shadow-[inset_-2px_-2px_0_#dfe6ec]': fixColumns }"
121
+ >
122
+ </th>
123
+ <th
124
+ v-for="(c,idx) in columns.slice(0, columnNumberLimit)"
125
+ 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"
126
+ :class="{ 'first:sticky first:left-0 first:z-20 first:shadow-[inset_-2px_-2px_0_#dfe6ec]': fixColumns }"
127
+ @click="onHeaderCellClick(idx)"
128
+ >
129
+ <div class="flex items-center">
130
+ <span>{{ c }}</span>
131
+ <el-tooltip v-if="columnsSettings[idx] && columnsSettings[idx].tooltip">
132
+ <div
133
+ slot="content"
134
+ v-html="columnsSettings[idx].tooltip"
135
+ />
136
+ <i
137
+ class="zmdi zmdi-info-outline ml5 text-slate-500 text-[12px]"
138
+ style="margin-top: -1px;"
139
+ />
140
+ </el-tooltip>
141
+ <span
142
+ v-if="!columnsSettings[idx] || !columnsSettings[idx].disableSort"
143
+ class="w-[16px] text-[12px] text-secondary-500 ml-1"
144
+ >
145
+ <i
146
+ v-if="sort.column !== idx"
147
+ class="zmdi zmdi-sort-amount-asc ml5 opacity-0 group-hover:opacity-100 text-slate-400 transition-opacity"
148
+ />
149
+ <i
150
+ v-if="sort.column === idx && sort.order === 'asc'"
151
+ class="zmdi zmdi-sort-amount-asc ml5"
152
+ />
153
+ <i
154
+ v-if="sort.column === idx && sort.order === 'desc'"
155
+ class="zmdi zmdi-sort-amount-desc ml5"
156
+ />
157
+ </span>
158
+ </div>
159
+ <div
160
+ v-if="oneOfRowsHasSubtitle"
161
+ class="text-[.7rem] text-slate-500 font-normal text-left"
162
+ >
163
+ {{ columnsSettings[idx] && columnsSettings[idx].subtitle ? columnsSettings[idx].subtitle : 'ㅤ' }}
164
+ </div>
165
+ </th>
166
+ </tr>
167
+ </thead>
168
+ <tbody :key="'page-' + page">
169
+ <tr
170
+ v-for="row in data"
171
+ :key="'row-' + rowKeyValue(row)"
172
+ class="border-b border-gray-200 last:border-0 group"
173
+ :class="{ 'cursor-pointer': settings.isRowClickable }"
174
+ @click="settings.isRowClickable && $emit('row-click', { idx: rowKeyValue(row), row: row.items, columnsSettings })"
175
+ >
176
+ <td
177
+ v-if="settings.isRadio"
178
+ 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"
179
+ :class="{ 'first:sticky first:left-0 first:z-10 first:shadow-[inset_-2px_0_0_#dfe6ec]': fixColumns, 'cursor-pointer': settings.isCellClickable }"
180
+ >
181
+ <el-radio
182
+ v-model="selectedRadioIdx"
183
+ :label="row.idx"
184
+ @input="updateSelectedRadio(row)"
185
+ style="width: 20px;"
186
+ class="row-radio"
187
+ >
188
+ <span style="margin-left: -20px; visibility: hidden;">{{ row.idx }}</span>
189
+ </el-radio>
190
+ </td>
191
+ <td
192
+ v-else-if="settings.isRowSelectable"
193
+ 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"
194
+ :class="{ 'first:sticky first:left-0 first:z-10 first:shadow-[inset_-2px_0_0_#dfe6ec]': fixColumns, 'cursor-pointer': settings.isCellClickable }"
195
+ >
196
+ <el-checkbox
197
+ size="small"
198
+ v-model="rowCheckboxModel[rowKeyValue(row)]"
199
+ @change="onRowCheckboxChange(row, rowCheckboxModel[rowKeyValue(row)])"
200
+ @click.stop
201
+ :key="'rowcb-' + page + '-' + rowKeyValue(row)"
202
+ style="transform: scale(1.2);"
203
+ />
204
+ </td>
205
+
206
+ <td
207
+ v-for="(col,idx) in row.items.slice(0, columnNumberLimit)"
208
+ 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"
209
+ :class="{ 'first:sticky first:left-0 first:z-10 first:shadow-[inset_-2px_0_0_#dfe6ec]': fixColumns, 'cursor-pointer': settings.isCellClickable }"
210
+ @click="settings.isCellClickable && $emit('cell-click', { idx: row.idx, row: row.items , column: idx})"
211
+ >
212
+ <div
213
+ class="flex items-center whitespace-nowrap"
214
+ :class="{ 'justify-end': columnsSettings[idx] && columnsSettings[idx].align === 'right' }"
215
+ >
216
+ <span
217
+ v-if="columnsSettings[idx] && columnsSettings[idx].type === 'class'"
218
+ class="w-3 h-3 rounded-sm flex mr-1.5 flex-none"
219
+ :style="{ backgroundColor: (classesMap[col] || { color: '#00ff00' }).color }"
220
+ />
221
+ <span v-if="columnsSettings[idx] && columnsSettings[idx].customCell">
222
+ <slot
223
+ name="cell-content"
224
+ :row="row"
225
+ :cell-value="col"
226
+ :column="columns[idx]"
227
+ :idx="idx"
228
+ />
229
+ </span>
230
+ <el-tooltip
231
+ v-else-if="columnsSettings[idx] && columnsSettings[idx].maxWidth"
232
+ :enterable="false"
233
+ :open-delay="300"
234
+ >
235
+ <span
236
+ slot="content"
237
+ v-html="col"
238
+ />
239
+ <span
240
+ class="ellipsis"
241
+ :style="{ 'max-width': columnsSettings[idx].maxWidth }"
242
+ v-html="highlight(col)"
243
+ />
244
+ </el-tooltip>
245
+ <span
246
+ v-else
247
+ v-html="highlight(col)"
248
+ />
249
+ <span
250
+ v-if="col != '' && columnsSettings[idx] && columnsSettings[idx].postfix"
251
+ class="text-slate-400 ml-0.5 text-[.7rem]"
252
+ >{{ columnsSettings[idx].postfix }}</span>
253
+ <span
254
+ v-if="idx === 0 && settings.isRowClickable"
255
+ class="opacity-0 transition-all duration-300 text-secondary-500 text-xs group-hover:translate-x-2 group-hover:opacity-100"
256
+ >➔</span>
257
+ </div>
258
+ <div
259
+ v-if="columnsSettings[idx] && columnsSettings[idx].type === 'class'"
260
+ class="text-[.6rem] text-slate-500 pl-[1.2rem]"
261
+ >
262
+ {{ (classesMap[col] && classesMap[col].shape ? classesMap[col].shape : 'Unknown').replace('bitmap', 'mask') }}
263
+ </div>
264
+ <div
265
+ v-if="columnsSettings[idx] && columnsSettings[idx].maxValue"
266
+ class="h-[2px] bg-slate-200 mt-0.5 w-[50px]"
267
+ >
268
+ <div
269
+ class="h-[2px] bg-secondary-500"
270
+ :style="{ width: col * 100 / columnsSettings[idx].maxValue + '%' }"
271
+ />
272
+ </div>
273
+ </td>
274
+ </tr>
275
+ </tbody>
276
+ </table>
277
+ <div
278
+ v-if="!data || !data.length"
279
+ class="text-[.9rem] text-center text-slate-500 mt-5 mb-5"
280
+ >
281
+ 😭 Nothing is found
282
+ </div>
283
+ </div>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ `,
288
+
2
289
  props: {
3
290
  data: {
4
291
  type: Array,
@@ -40,62 +327,137 @@ Vue.component('fast-table', {
40
327
  type: String,
41
328
  default: '',
42
329
  },
43
- showHeader: {
330
+ selectedRows: {
331
+ type: Array,
332
+ default: () => [],
333
+ },
334
+ selectedRadioIdx: {
335
+ type: Number,
336
+ default: 0,
337
+ },
338
+ name: {
339
+ type: String,
340
+ default: '',
341
+ },
342
+ disabled: {
44
343
  type: Boolean,
45
- default: true,
344
+ default: false,
345
+ },
346
+ rowKey: {
347
+ type: String,
348
+ default: 'idx',
46
349
  },
47
350
  },
351
+
48
352
  data() {
49
353
  return {
50
- columnNumberLimit: 15,
354
+ columnNumberLimit: 50,
355
+ rowCheckboxModel: {},
51
356
  };
52
357
  },
358
+
53
359
  computed: {
54
360
  canGoBack() {
55
361
  return this.page - 1 > 0;
56
362
  },
363
+
57
364
  canGoForward() {
58
365
  return this.page * this.LIMIT < this.total;
59
366
  },
367
+
368
+ fixColumns() {
369
+ if (!this.settings?.fixColumns) return 0;
370
+
371
+ if (this.settings?.isRowSelectable) return this.settings.fixColumns + 1;
372
+
373
+ return this.settings.fixColumns;
374
+ },
375
+
60
376
  settings() {
377
+ const defaultSettings = {
378
+ showHeaderControls: true,
379
+ searchPosition: 'left',
380
+ };
61
381
  return {
382
+ ...defaultSettings,
62
383
  ...(this.options || {}),
63
384
  };
64
385
  },
386
+
65
387
  LIMIT() {
66
388
  return this.pageSize || 50;
67
389
  },
390
+
68
391
  classesMap() {
69
392
  return this.projectMeta ? _.keyBy(this.projectMeta.classes, 'title') : {};
70
393
  },
394
+
71
395
  needsRightGradient() {
72
396
  return false;
73
397
  },
398
+
74
399
  columnsSettings() {
75
400
  return this.columnsOptions || Array(this.columns.length || 0).fill({});
76
401
  },
402
+
77
403
  oneOfRowsHasSubtitle() {
78
404
  return this.columnsSettings.find(i => i?.subtitle);
79
405
  },
406
+
407
+ isAllOnPageSelected() {
408
+ const rows = this.data || [];
409
+ if (rows.length === 0) return false;
410
+ const selectedIdx = new Set((this.selectedRows || []).map(r => this.rowKeyValue(r)));
411
+ return rows.every(r => selectedIdx.has(this.rowKeyValue(r)));
412
+ },
413
+
414
+ isSomeOnPageSelected() {
415
+ const rows = this.data || [];
416
+ if (rows.length === 0) return false;
417
+ const selectedIdx = new Set((this.selectedRows || []).map(r => this.rowKeyValue(r)));
418
+ const count = rows.filter(r => selectedIdx.has(this.rowKeyValue(r))).length;
419
+ return count > 0 && count < rows.length;
420
+ },
421
+
422
+ selectedIdxSet() {
423
+ return new Set((this.selectedRows || []).map(r => this.rowKeyValue(r)));
424
+ },
425
+
426
+ headerCheckboxModel: {
427
+ get() {
428
+ return this.isAllOnPageSelected;
429
+ },
430
+ set(val) {
431
+ this.selectAllRows(val);
432
+ }
433
+ },
80
434
  },
435
+
81
436
  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);
437
+ data: {
438
+ immediate: true,
439
+ handler() {
440
+ this.syncRowCheckboxModel();
441
+ }
442
+ },
443
+ selectedRows: {
444
+ immediate: true,
445
+ handler() {
446
+ this.syncRowCheckboxModel();
447
+ }
448
+ }
90
449
  },
450
+
91
451
  methods: {
92
452
  _updateData() {
93
- this.$emit('filters-changed');
453
+ this.$emit('filters-changed')
94
454
  },
455
+
95
456
  searchChanged(val) {
96
457
  this.$emit('update:search', val);
97
458
  this.updateData();
98
459
  },
460
+
99
461
  go(dir) {
100
462
  if (dir === -1 && !this.canGoBack) return;
101
463
  if (dir === +1 && !this.canGoForward) return;
@@ -103,7 +465,10 @@ Vue.component('fast-table', {
103
465
  this.$emit('update:page', this.page + dir);
104
466
  this.updateData();
105
467
  },
468
+
106
469
  onHeaderCellClick(idx) {
470
+ if (this.columnsSettings?.[idx]?.disableSort) return;
471
+
107
472
  let order = this.sort.order;
108
473
 
109
474
  if (this.sort.column !== idx) order = 'asc';
@@ -112,90 +477,122 @@ Vue.component('fast-table', {
112
477
  this.$emit('update:sort', { order, column: idx });
113
478
  this.updateData();
114
479
  },
480
+
481
+ selectAllRows(checked) {
482
+ if (!this.data || this.data.length === 0) return;
483
+
484
+ const current = Array.isArray(this.selectedRows) ? [...this.selectedRows] : [];
485
+ const max = this.settings && this.settings.maxSelectedRows ? this.settings.maxSelectedRows : 0;
486
+
487
+ if (checked) {
488
+ const selectedIdx = new Set(current.map(r => this.rowKeyValue(r)));
489
+ const result = [...current];
490
+
491
+ if (max && max > 0) {
492
+ let quota = Math.max(0, max - result.length);
493
+ for (const r of this.data) {
494
+ const key = this.rowKeyValue(r);
495
+ if (!selectedIdx.has(key)) {
496
+ if (quota <= 0) break;
497
+ result.push(_.cloneDeep(r));
498
+ selectedIdx.add(key);
499
+ quota -= 1;
500
+ }
501
+ }
502
+ this.$emit('update:selected-rows', result);
503
+ } else {
504
+ for (const r of this.data) {
505
+ const key = this.rowKeyValue(r);
506
+ if (!selectedIdx.has(key)) {
507
+ result.push(_.cloneDeep(r));
508
+ selectedIdx.add(key);
509
+ }
510
+ }
511
+ this.$emit('update:selected-rows', result);
512
+ }
513
+ } else {
514
+ const pageIdx = new Set(this.data.map(r => this.rowKeyValue(r)));
515
+ const result = current.filter(r => !pageIdx.has(this.rowKeyValue(r)));
516
+ this.$emit('update:selected-rows', result);
517
+ }
518
+ },
519
+
520
+ syncRowCheckboxModel() {
521
+ const map = {};
522
+ const selected = new Set((this.selectedRows || []).map(r => this.rowKeyValue(r)));
523
+ for (const r of (this.data || [])) {
524
+ const key = this.rowKeyValue(r);
525
+ map[key] = selected.has(key);
526
+ }
527
+ this.rowCheckboxModel = map;
528
+ },
529
+
530
+ onRowCheckboxChange(row, checked) {
531
+ this.updateSelectedRows(row, checked);
532
+ },
533
+
534
+ updateSelectedRows(row, checked) {
535
+ checked = typeof checked === 'boolean' ? checked : !!checked;
536
+ const current = Array.isArray(this.selectedRows) ? [...this.selectedRows] : [];
537
+ const key = this.rowKeyValue(row);
538
+ const exists = current.some(r => this.rowKeyValue(r) === key);
539
+ const max = this.settings && this.settings.maxSelectedRows ? this.settings.maxSelectedRows : 0;
540
+
541
+ let result = current;
542
+ if (checked) {
543
+ if (max && max > 0) {
544
+ if (max === 1) {
545
+ result = [_.cloneDeep(row)];
546
+ this.$emit('update:selected-rows', result);
547
+ return;
548
+ }
549
+ if (!exists && current.length >= max) {
550
+ // revert checkbox state if over the limit
551
+ this.$nextTick(() => { this.$set(this.rowCheckboxModel, key, false); });
552
+ return;
553
+ }
554
+ }
555
+ if (!exists) result = [...current, _.cloneDeep(row)];
556
+ } else {
557
+ if (exists) result = current.filter(r => this.rowKeyValue(r) !== key);
558
+ }
559
+ this.$emit('update:selected-rows', result);
560
+ },
561
+
562
+ updateSelectedRadio(row) {
563
+ console.log('updateSelectedRadio', row);
564
+ row = _.cloneDeep(row)
565
+ this.$emit('update:selected-rows', [row])
566
+ },
567
+
568
+ handleScroll() {
569
+ const scrollX = this.$refs.scrollBox.scrollLeft;
570
+ if (scrollX > 0 && this.columnNumberLimit !== 99999) {
571
+ this.columnNumberLimit = 99999;
572
+ }
573
+ },
574
+
115
575
  highlight(text) {
116
576
  if (!this.search) return text;
117
- return text.toString().replace(new RegExp(this.search, 'gi'), match => `<span class="bg-yellow-400">${match}</span>`);
577
+ return text.toString().replace(new RegExp(this.search, 'gi'), match => '<span class="bg-yellow-400">'+match+'</span>');
578
+ },
579
+
580
+ rowKeyValue(row) {
581
+ if (!row) return undefined;
582
+ const keyName = this.rowKey || 'idx';
583
+ return row[keyName] != null ? row[keyName] : row.idx;
118
584
  },
119
585
  },
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
- });
586
+
587
+ mounted() {
588
+ this.$refs.scrollBox.addEventListener('scroll', this.handleScroll);
589
+ },
590
+
591
+ beforeDestroy() {
592
+ this.$refs.scrollBox.addEventListener('scroll', this.handleScroll);
593
+ },
594
+
595
+ created() {
596
+ this.updateData = _.debounce(this._updateData, 300);
597
+ },
598
+ });