supervisely 6.73.438__py3-none-any.whl → 6.73.513__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/__init__.py +137 -1
- supervisely/_utils.py +81 -0
- supervisely/annotation/annotation.py +8 -2
- supervisely/annotation/json_geometries_map.py +14 -11
- supervisely/annotation/label.py +80 -3
- supervisely/api/annotation_api.py +14 -11
- supervisely/api/api.py +59 -38
- supervisely/api/app_api.py +11 -2
- supervisely/api/dataset_api.py +74 -12
- supervisely/api/entities_collection_api.py +10 -0
- supervisely/api/entity_annotation/figure_api.py +52 -4
- supervisely/api/entity_annotation/object_api.py +3 -3
- supervisely/api/entity_annotation/tag_api.py +63 -12
- supervisely/api/guides_api.py +210 -0
- supervisely/api/image_api.py +72 -1
- supervisely/api/labeling_job_api.py +83 -1
- supervisely/api/labeling_queue_api.py +33 -7
- supervisely/api/module_api.py +9 -0
- supervisely/api/project_api.py +71 -26
- supervisely/api/storage_api.py +3 -1
- supervisely/api/task_api.py +13 -2
- supervisely/api/team_api.py +4 -3
- supervisely/api/video/video_annotation_api.py +119 -3
- supervisely/api/video/video_api.py +65 -14
- supervisely/api/video/video_figure_api.py +24 -11
- supervisely/app/__init__.py +1 -1
- supervisely/app/content.py +23 -7
- supervisely/app/development/development.py +18 -2
- supervisely/app/fastapi/__init__.py +1 -0
- supervisely/app/fastapi/custom_static_files.py +1 -1
- supervisely/app/fastapi/multi_user.py +105 -0
- supervisely/app/fastapi/subapp.py +88 -42
- supervisely/app/fastapi/websocket.py +77 -9
- supervisely/app/singleton.py +21 -0
- supervisely/app/v1/app_service.py +18 -2
- supervisely/app/v1/constants.py +7 -1
- supervisely/app/widgets/__init__.py +6 -0
- supervisely/app/widgets/activity_feed/__init__.py +0 -0
- supervisely/app/widgets/activity_feed/activity_feed.py +239 -0
- supervisely/app/widgets/activity_feed/style.css +78 -0
- supervisely/app/widgets/activity_feed/template.html +22 -0
- supervisely/app/widgets/card/card.py +20 -0
- supervisely/app/widgets/classes_list_selector/classes_list_selector.py +121 -9
- supervisely/app/widgets/classes_list_selector/template.html +60 -93
- supervisely/app/widgets/classes_mapping/classes_mapping.py +13 -12
- supervisely/app/widgets/classes_table/classes_table.py +1 -0
- supervisely/app/widgets/deploy_model/deploy_model.py +56 -35
- supervisely/app/widgets/dialog/dialog.py +12 -0
- supervisely/app/widgets/dialog/template.html +2 -1
- supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +1 -1
- supervisely/app/widgets/experiment_selector/experiment_selector.py +8 -0
- supervisely/app/widgets/fast_table/fast_table.py +184 -60
- supervisely/app/widgets/fast_table/template.html +1 -1
- supervisely/app/widgets/heatmap/__init__.py +0 -0
- supervisely/app/widgets/heatmap/heatmap.py +564 -0
- supervisely/app/widgets/heatmap/script.js +533 -0
- supervisely/app/widgets/heatmap/style.css +233 -0
- supervisely/app/widgets/heatmap/template.html +21 -0
- supervisely/app/widgets/modal/__init__.py +0 -0
- supervisely/app/widgets/modal/modal.py +198 -0
- supervisely/app/widgets/modal/template.html +10 -0
- supervisely/app/widgets/object_class_view/object_class_view.py +3 -0
- supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
- supervisely/app/widgets/radio_tabs/template.html +1 -0
- supervisely/app/widgets/select/select.py +6 -3
- supervisely/app/widgets/select_class/__init__.py +0 -0
- supervisely/app/widgets/select_class/select_class.py +363 -0
- supervisely/app/widgets/select_class/template.html +50 -0
- supervisely/app/widgets/select_cuda/select_cuda.py +22 -0
- supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +65 -7
- supervisely/app/widgets/select_tag/__init__.py +0 -0
- supervisely/app/widgets/select_tag/select_tag.py +352 -0
- supervisely/app/widgets/select_tag/template.html +64 -0
- supervisely/app/widgets/select_team/select_team.py +37 -4
- supervisely/app/widgets/select_team/template.html +4 -5
- supervisely/app/widgets/select_user/__init__.py +0 -0
- supervisely/app/widgets/select_user/select_user.py +270 -0
- supervisely/app/widgets/select_user/template.html +13 -0
- supervisely/app/widgets/select_workspace/select_workspace.py +59 -10
- supervisely/app/widgets/select_workspace/template.html +9 -12
- supervisely/app/widgets/table/table.py +68 -13
- supervisely/app/widgets/tree_select/tree_select.py +2 -0
- supervisely/aug/aug.py +6 -2
- supervisely/convert/base_converter.py +1 -0
- supervisely/convert/converter.py +2 -2
- supervisely/convert/image/csv/csv_converter.py +24 -15
- supervisely/convert/image/image_converter.py +3 -1
- supervisely/convert/image/image_helper.py +48 -4
- supervisely/convert/image/label_studio/label_studio_converter.py +2 -0
- supervisely/convert/image/medical2d/medical2d_helper.py +2 -24
- supervisely/convert/image/multispectral/multispectral_converter.py +6 -0
- supervisely/convert/image/pascal_voc/pascal_voc_converter.py +8 -5
- supervisely/convert/image/pascal_voc/pascal_voc_helper.py +7 -0
- supervisely/convert/pointcloud/kitti_3d/kitti_3d_converter.py +33 -3
- supervisely/convert/pointcloud/kitti_3d/kitti_3d_helper.py +12 -5
- supervisely/convert/pointcloud/las/las_converter.py +13 -1
- supervisely/convert/pointcloud/las/las_helper.py +110 -11
- supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +27 -16
- supervisely/convert/pointcloud/pointcloud_converter.py +91 -3
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +58 -22
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +21 -47
- supervisely/convert/video/__init__.py +1 -0
- supervisely/convert/video/multi_view/__init__.py +0 -0
- supervisely/convert/video/multi_view/multi_view.py +543 -0
- supervisely/convert/video/sly/sly_video_converter.py +359 -3
- supervisely/convert/video/video_converter.py +24 -4
- supervisely/convert/volume/dicom/dicom_converter.py +13 -5
- supervisely/convert/volume/dicom/dicom_helper.py +30 -18
- supervisely/geometry/constants.py +1 -0
- supervisely/geometry/geometry.py +4 -0
- supervisely/geometry/helpers.py +5 -1
- supervisely/geometry/oriented_bbox.py +676 -0
- supervisely/geometry/polyline_3d.py +110 -0
- supervisely/geometry/rectangle.py +2 -1
- supervisely/io/env.py +76 -1
- supervisely/io/fs.py +21 -0
- supervisely/nn/benchmark/base_evaluator.py +104 -11
- supervisely/nn/benchmark/instance_segmentation/evaluator.py +1 -8
- supervisely/nn/benchmark/object_detection/evaluator.py +20 -4
- supervisely/nn/benchmark/object_detection/vis_metrics/pr_curve.py +10 -5
- supervisely/nn/benchmark/semantic_segmentation/evaluator.py +34 -16
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/confusion_matrix.py +1 -1
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/frequently_confused.py +1 -1
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/overview.py +1 -1
- supervisely/nn/benchmark/visualization/evaluation_result.py +66 -4
- supervisely/nn/inference/cache.py +43 -18
- supervisely/nn/inference/gui/serving_gui_template.py +5 -2
- supervisely/nn/inference/inference.py +916 -222
- supervisely/nn/inference/inference_request.py +55 -10
- supervisely/nn/inference/predict_app/gui/classes_selector.py +83 -12
- supervisely/nn/inference/predict_app/gui/gui.py +676 -488
- supervisely/nn/inference/predict_app/gui/input_selector.py +205 -26
- supervisely/nn/inference/predict_app/gui/model_selector.py +2 -4
- supervisely/nn/inference/predict_app/gui/output_selector.py +46 -6
- supervisely/nn/inference/predict_app/gui/settings_selector.py +756 -59
- supervisely/nn/inference/predict_app/gui/tags_selector.py +1 -1
- supervisely/nn/inference/predict_app/gui/utils.py +236 -119
- supervisely/nn/inference/predict_app/predict_app.py +2 -2
- supervisely/nn/inference/session.py +43 -35
- supervisely/nn/inference/tracking/bbox_tracking.py +118 -35
- supervisely/nn/inference/tracking/point_tracking.py +5 -1
- supervisely/nn/inference/tracking/tracker_interface.py +10 -1
- supervisely/nn/inference/uploader.py +139 -12
- supervisely/nn/live_training/__init__.py +7 -0
- supervisely/nn/live_training/api_server.py +111 -0
- supervisely/nn/live_training/artifacts_utils.py +243 -0
- supervisely/nn/live_training/checkpoint_utils.py +229 -0
- supervisely/nn/live_training/dynamic_sampler.py +44 -0
- supervisely/nn/live_training/helpers.py +14 -0
- supervisely/nn/live_training/incremental_dataset.py +146 -0
- supervisely/nn/live_training/live_training.py +497 -0
- supervisely/nn/live_training/loss_plateau_detector.py +111 -0
- supervisely/nn/live_training/request_queue.py +52 -0
- supervisely/nn/model/model_api.py +9 -0
- supervisely/nn/model/prediction.py +2 -1
- supervisely/nn/model/prediction_session.py +26 -14
- supervisely/nn/prediction_dto.py +19 -1
- supervisely/nn/tracker/base_tracker.py +11 -1
- supervisely/nn/tracker/botsort/botsort_config.yaml +0 -1
- supervisely/nn/tracker/botsort/tracker/mc_bot_sort.py +7 -4
- supervisely/nn/tracker/botsort_tracker.py +94 -65
- supervisely/nn/tracker/utils.py +4 -5
- supervisely/nn/tracker/visualize.py +93 -93
- supervisely/nn/training/gui/classes_selector.py +16 -1
- supervisely/nn/training/gui/train_val_splits_selector.py +52 -31
- supervisely/nn/training/train_app.py +46 -31
- supervisely/project/data_version.py +115 -51
- supervisely/project/download.py +1 -1
- supervisely/project/pointcloud_episode_project.py +37 -8
- supervisely/project/pointcloud_project.py +30 -2
- supervisely/project/project.py +14 -2
- supervisely/project/project_meta.py +27 -1
- supervisely/project/project_settings.py +32 -18
- supervisely/project/versioning/__init__.py +1 -0
- supervisely/project/versioning/common.py +20 -0
- supervisely/project/versioning/schema_fields.py +35 -0
- supervisely/project/versioning/video_schema.py +221 -0
- supervisely/project/versioning/volume_schema.py +87 -0
- supervisely/project/video_project.py +717 -15
- supervisely/project/volume_project.py +623 -5
- supervisely/template/experiment/experiment.html.jinja +4 -4
- supervisely/template/experiment/experiment_generator.py +14 -21
- supervisely/template/live_training/__init__.py +0 -0
- supervisely/template/live_training/header.html.jinja +96 -0
- supervisely/template/live_training/live_training.html.jinja +51 -0
- supervisely/template/live_training/live_training_generator.py +464 -0
- supervisely/template/live_training/sly-style.css +402 -0
- supervisely/template/live_training/template.html.jinja +18 -0
- supervisely/versions.json +28 -26
- supervisely/video/sampling.py +39 -20
- supervisely/video/video.py +41 -12
- supervisely/video_annotation/video_figure.py +38 -4
- supervisely/video_annotation/video_object.py +29 -4
- supervisely/volume/stl_converter.py +2 -0
- supervisely/worker_api/agent_rpc.py +24 -1
- supervisely/worker_api/rpc_servicer.py +31 -7
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/METADATA +58 -40
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/RECORD +203 -155
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/WHEEL +1 -1
- supervisely_lib/__init__.py +6 -1
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info/licenses}/LICENSE +0 -0
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/top_level.txt +0 -0
supervisely/app/singleton.py
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
|
+
import supervisely.io.env as sly_env
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
class Singleton(type):
|
|
2
5
|
_instances = {}
|
|
6
|
+
_nested_instances = {}
|
|
3
7
|
|
|
4
8
|
def __call__(cls, *args, **kwargs):
|
|
5
9
|
local = kwargs.pop("__local__", False)
|
|
6
10
|
if local is False:
|
|
7
11
|
if cls not in cls._instances:
|
|
8
12
|
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
|
|
13
|
+
|
|
14
|
+
if sly_env.is_multiuser_mode_enabled():
|
|
15
|
+
from supervisely.app.content import DataJson, StateJson
|
|
16
|
+
from copy import deepcopy
|
|
17
|
+
|
|
18
|
+
# Initialize nested instances dict once
|
|
19
|
+
nested_instances = cls._nested_instances.setdefault(cls, {})
|
|
20
|
+
|
|
21
|
+
user_id = sly_env.user_from_multiuser_app()
|
|
22
|
+
if user_id is not None and cls in (StateJson, DataJson):
|
|
23
|
+
if user_id not in nested_instances:
|
|
24
|
+
# Create new instance and copy data
|
|
25
|
+
instance = super(Singleton, cls).__call__(*args, **kwargs)
|
|
26
|
+
instance.update(deepcopy(dict(cls._instances[cls])))
|
|
27
|
+
nested_instances[user_id] = instance
|
|
28
|
+
|
|
29
|
+
return nested_instances[user_id]
|
|
9
30
|
return cls._instances[cls]
|
|
10
31
|
else:
|
|
11
32
|
return super(Singleton, cls).__call__(*args, **kwargs)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# isort: skip_file
|
|
2
|
+
|
|
1
3
|
import json
|
|
2
4
|
import os
|
|
3
5
|
import time
|
|
@@ -12,7 +14,8 @@ import queue
|
|
|
12
14
|
import re
|
|
13
15
|
|
|
14
16
|
from supervisely.worker_api.agent_api import AgentAPI
|
|
15
|
-
|
|
17
|
+
|
|
18
|
+
# from supervisely.worker_proto import worker_api_pb2 as api_proto # Import moved to methods where needed
|
|
16
19
|
from supervisely.function_wrapper import function_wrapper
|
|
17
20
|
from supervisely._utils import take_with_default
|
|
18
21
|
from supervisely.sly_logger import logger as default_logger
|
|
@@ -30,7 +33,6 @@ from supervisely._utils import _remove_sensitive_information
|
|
|
30
33
|
from supervisely.worker_api.agent_rpc import send_from_memory_generator
|
|
31
34
|
from supervisely.io.fs_cache import FileCache
|
|
32
35
|
|
|
33
|
-
|
|
34
36
|
# https://www.roguelynn.com/words/asyncio-we-did-it-wrong/
|
|
35
37
|
|
|
36
38
|
|
|
@@ -390,6 +392,13 @@ class AppService:
|
|
|
390
392
|
)
|
|
391
393
|
|
|
392
394
|
def publish_sync(self, initial_events=None):
|
|
395
|
+
try:
|
|
396
|
+
from supervisely.worker_proto import worker_api_pb2 as api_proto
|
|
397
|
+
except Exception as e:
|
|
398
|
+
from supervisely.app.v1.constants import PROTOBUF_REQUIRED_ERROR
|
|
399
|
+
|
|
400
|
+
raise ImportError(PROTOBUF_REQUIRED_ERROR) from e
|
|
401
|
+
|
|
393
402
|
if initial_events is not None:
|
|
394
403
|
for event_obj in initial_events:
|
|
395
404
|
event_obj["api_token"] = os.environ[API_TOKEN]
|
|
@@ -507,6 +516,13 @@ class AppService:
|
|
|
507
516
|
self._error = error
|
|
508
517
|
|
|
509
518
|
def send_response(self, request_id, data):
|
|
519
|
+
try:
|
|
520
|
+
from supervisely.worker_proto import worker_api_pb2 as api_proto
|
|
521
|
+
except Exception as e:
|
|
522
|
+
from supervisely.app.v1.constants import PROTOBUF_REQUIRED_ERROR
|
|
523
|
+
|
|
524
|
+
raise ImportError(PROTOBUF_REQUIRED_ERROR) from e
|
|
525
|
+
|
|
510
526
|
out_bytes = json.dumps(data).encode("utf-8")
|
|
511
527
|
self.api.put_stream_with_data(
|
|
512
528
|
"SendGeneralEventData",
|
supervisely/app/v1/constants.py
CHANGED
|
@@ -7,4 +7,10 @@ SHARED_DATA = '/sessions'
|
|
|
7
7
|
|
|
8
8
|
STOP_COMMAND = "stop"
|
|
9
9
|
|
|
10
|
-
IMAGE_ANNOTATION_EVENTS = ["manual_selected_figure_changed"]
|
|
10
|
+
IMAGE_ANNOTATION_EVENTS = ["manual_selected_figure_changed"]
|
|
11
|
+
|
|
12
|
+
# Error message for missing or incompatible protobuf dependencies
|
|
13
|
+
PROTOBUF_REQUIRED_ERROR = (
|
|
14
|
+
"protobuf is required for agent/worker/app_v1 functionality. "
|
|
15
|
+
"Please install supervisely with agent extras: pip install 'supervisely[agent]'"
|
|
16
|
+
)
|
|
@@ -104,6 +104,8 @@ from supervisely.app.widgets.pie_chart.pie_chart import PieChart
|
|
|
104
104
|
from supervisely.app.widgets.timeline.timeline import Timeline
|
|
105
105
|
from supervisely.app.widgets.nodes_flow.nodes_flow import NodesFlow
|
|
106
106
|
from supervisely.app.widgets.dialog.dialog import Dialog
|
|
107
|
+
from supervisely.app.widgets.modal.modal import Modal
|
|
108
|
+
from supervisely.app.widgets.activity_feed.activity_feed import ActivityFeed
|
|
107
109
|
from supervisely.app.widgets.draggable.draggable import Draggable
|
|
108
110
|
from supervisely.app.widgets.tooltip.tooltip import Tooltip
|
|
109
111
|
from supervisely.app.widgets.image_annotation_preview.image_annotation_preview import (
|
|
@@ -129,6 +131,9 @@ from supervisely.app.widgets.classes_mapping_preview.classes_mapping_preview imp
|
|
|
129
131
|
)
|
|
130
132
|
from supervisely.app.widgets.classes_list_selector.classes_list_selector import ClassesListSelector
|
|
131
133
|
from supervisely.app.widgets.classes_list_preview.classes_list_preview import ClassesListPreview
|
|
134
|
+
from supervisely.app.widgets.select_class.select_class import SelectClass
|
|
135
|
+
from supervisely.app.widgets.select_tag.select_tag import SelectTag
|
|
136
|
+
from supervisely.app.widgets.select_user.select_user import SelectUser
|
|
132
137
|
from supervisely.app.widgets.tags_list_selector.tags_list_selector import TagsListSelector
|
|
133
138
|
from supervisely.app.widgets.tags_list_preview.tags_list_preview import TagsListPreview
|
|
134
139
|
from supervisely.app.widgets.members_list_selector.members_list_selector import MembersListSelector
|
|
@@ -161,3 +166,4 @@ from supervisely.app.widgets.dropdown_checkbox_selector.dropdown_checkbox_select
|
|
|
161
166
|
from supervisely.app.widgets.ecosystem_model_selector.ecosystem_model_selector import (
|
|
162
167
|
EcosystemModelSelector,
|
|
163
168
|
)
|
|
169
|
+
from supervisely.app.widgets.heatmap.heatmap import Heatmap
|
|
File without changes
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from supervisely.app import DataJson
|
|
6
|
+
from supervisely.app.widgets import Widget
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from typing import Literal
|
|
10
|
+
except ImportError:
|
|
11
|
+
from typing_extensions import Literal
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ActivityFeed(Widget):
|
|
15
|
+
"""ActivityFeed is a widget that displays a vertical list of activity items with status indicators.
|
|
16
|
+
Similar to a timeline or activity log showing sequential events with their current status.
|
|
17
|
+
|
|
18
|
+
Each item can contain a custom widget as content and displays a status indicator (pending, in process, completed, failed).
|
|
19
|
+
Items are automatically numbered if no number is provided.
|
|
20
|
+
|
|
21
|
+
Read about it in `Developer Portal <https://developer.supervisely.com/app-development/widgets/layouts-and-containers/activity-feed>`_
|
|
22
|
+
(including screenshots and examples).
|
|
23
|
+
|
|
24
|
+
:param items: List of ActivityFeed.Item objects to display
|
|
25
|
+
:type items: Optional[List[ActivityFeed.Item]]
|
|
26
|
+
:param widget_id: An identifier of the widget.
|
|
27
|
+
:type widget_id: str, optional
|
|
28
|
+
|
|
29
|
+
:Usage example:
|
|
30
|
+
.. code-block:: python
|
|
31
|
+
|
|
32
|
+
from supervisely.app.widgets import ActivityFeed, Text
|
|
33
|
+
|
|
34
|
+
# Create items with custom content
|
|
35
|
+
item1 = ActivityFeed.Item(
|
|
36
|
+
content=Text("Processing dataset"),
|
|
37
|
+
status="completed"
|
|
38
|
+
)
|
|
39
|
+
item2 = ActivityFeed.Item(
|
|
40
|
+
content=Text("Training model"),
|
|
41
|
+
status="in_progress",
|
|
42
|
+
number=2
|
|
43
|
+
)
|
|
44
|
+
item3 = ActivityFeed.Item(
|
|
45
|
+
content=Text("Generating report"),
|
|
46
|
+
status="pending"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Create activity feed
|
|
50
|
+
feed = ActivityFeed(items=[item1, item2, item3])
|
|
51
|
+
|
|
52
|
+
# Add item during runtime
|
|
53
|
+
new_item = ActivityFeed.Item(
|
|
54
|
+
content=Text("Deploy model"),
|
|
55
|
+
status="pending"
|
|
56
|
+
)
|
|
57
|
+
feed.add_item(new_item)
|
|
58
|
+
|
|
59
|
+
# Update status by item number
|
|
60
|
+
feed.set_status(2, "completed")
|
|
61
|
+
|
|
62
|
+
# Get item status
|
|
63
|
+
status = feed.get_status(2)
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
class Item:
|
|
67
|
+
"""Represents a single item in the ActivityFeed.
|
|
68
|
+
|
|
69
|
+
:param content: Widget to display as the item content
|
|
70
|
+
:type content: Widget
|
|
71
|
+
:param status: Status of the item (pending, in_progress, completed, failed)
|
|
72
|
+
:type status: Literal["pending", "in_progress", "completed", "failed"]
|
|
73
|
+
:param number: Position number in the feed (auto-assigned if not provided)
|
|
74
|
+
:type number: Optional[int]
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
content: Widget,
|
|
80
|
+
status: Literal["pending", "in_progress", "completed", "failed"] = "pending",
|
|
81
|
+
number: Optional[int] = None,
|
|
82
|
+
) -> ActivityFeed.Item:
|
|
83
|
+
self.content = content
|
|
84
|
+
self.status = status
|
|
85
|
+
self.number = number
|
|
86
|
+
self._validate_status()
|
|
87
|
+
|
|
88
|
+
def _validate_status(self):
|
|
89
|
+
valid_statuses = ["pending", "in_progress", "completed", "failed"]
|
|
90
|
+
if self.status not in valid_statuses:
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f"Invalid status '{self.status}'. Must be one of: {', '.join(valid_statuses)}"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def to_json(self):
|
|
96
|
+
return {
|
|
97
|
+
"number": self.number,
|
|
98
|
+
"status": self.status,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
items: Optional[List[ActivityFeed.Item]] = None,
|
|
104
|
+
widget_id: Optional[str] = None,
|
|
105
|
+
):
|
|
106
|
+
self._items = items if items is not None else []
|
|
107
|
+
self._auto_assign_numbers()
|
|
108
|
+
super().__init__(widget_id=widget_id, file_path=__file__)
|
|
109
|
+
|
|
110
|
+
def _auto_assign_numbers(self):
|
|
111
|
+
"""Automatically assign numbers to items that don't have them."""
|
|
112
|
+
next_number = 1
|
|
113
|
+
for item in self._items:
|
|
114
|
+
if item.number is None:
|
|
115
|
+
item.number = next_number
|
|
116
|
+
next_number = max(next_number, item.number) + 1
|
|
117
|
+
|
|
118
|
+
def get_json_data(self) -> Dict:
|
|
119
|
+
"""Returns dictionary with widget data.
|
|
120
|
+
|
|
121
|
+
:return: Dictionary with items data
|
|
122
|
+
:rtype: Dict
|
|
123
|
+
"""
|
|
124
|
+
return {
|
|
125
|
+
"items": [item.to_json() for item in self._items],
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
def get_json_state(self) -> Dict:
|
|
129
|
+
"""Returns dictionary with widget state (empty for this widget).
|
|
130
|
+
|
|
131
|
+
:return: Empty dictionary
|
|
132
|
+
:rtype: Dict
|
|
133
|
+
"""
|
|
134
|
+
return {}
|
|
135
|
+
|
|
136
|
+
def add_item(
|
|
137
|
+
self,
|
|
138
|
+
item: Optional[ActivityFeed.Item] = None,
|
|
139
|
+
content: Optional[Widget] = None,
|
|
140
|
+
status: Literal["pending", "in_progress", "completed", "failed"] = "pending",
|
|
141
|
+
number: Optional[int] = None,
|
|
142
|
+
) -> None:
|
|
143
|
+
"""Add a new item to the activity feed.
|
|
144
|
+
|
|
145
|
+
You can either pass an ActivityFeed.Item object or provide content and status separately.
|
|
146
|
+
|
|
147
|
+
:param item: ActivityFeed.Item to add
|
|
148
|
+
:type item: Optional[ActivityFeed.Item]
|
|
149
|
+
:param content: Widget content (used if item is not provided)
|
|
150
|
+
:type content: Optional[Widget]
|
|
151
|
+
:param status: Status of the item (used if item is not provided)
|
|
152
|
+
:type status: Literal["pending", "in_progress", "completed", "failed"]
|
|
153
|
+
:param number: Position number (auto-assigned if not provided)
|
|
154
|
+
:type number: Optional[int]
|
|
155
|
+
"""
|
|
156
|
+
if item is None:
|
|
157
|
+
if content is None:
|
|
158
|
+
raise ValueError("Either 'item' or 'content' must be provided")
|
|
159
|
+
item = ActivityFeed.Item(content=content, status=status, number=number)
|
|
160
|
+
|
|
161
|
+
if item.number is None:
|
|
162
|
+
# Auto-assign number
|
|
163
|
+
if self._items:
|
|
164
|
+
item.number = max(i.number for i in self._items) + 1
|
|
165
|
+
else:
|
|
166
|
+
item.number = 1
|
|
167
|
+
|
|
168
|
+
self._items.append(item)
|
|
169
|
+
self.update_data()
|
|
170
|
+
DataJson().send_changes()
|
|
171
|
+
|
|
172
|
+
def remove_item(self, number: int) -> None:
|
|
173
|
+
"""Remove an item from the activity feed by its number.
|
|
174
|
+
|
|
175
|
+
:param number: Number of the item to remove. Starts from 1.
|
|
176
|
+
:type number: int
|
|
177
|
+
"""
|
|
178
|
+
self._items = [item for item in self._items if item.number != number]
|
|
179
|
+
self.update_data()
|
|
180
|
+
DataJson().send_changes()
|
|
181
|
+
|
|
182
|
+
def set_status(
|
|
183
|
+
self,
|
|
184
|
+
number: int,
|
|
185
|
+
status: Literal["pending", "in_progress", "completed", "failed"],
|
|
186
|
+
) -> None:
|
|
187
|
+
"""Update the status of an item by its number.
|
|
188
|
+
|
|
189
|
+
:param number: Number of the item to update. Starts from 1.
|
|
190
|
+
:type number: int
|
|
191
|
+
:param status: New status for the item
|
|
192
|
+
:type status: Literal["pending", "in_progress", "completed", "failed"]
|
|
193
|
+
"""
|
|
194
|
+
for i, item in enumerate(self._items):
|
|
195
|
+
if item.number == number:
|
|
196
|
+
item.status = status
|
|
197
|
+
item._validate_status()
|
|
198
|
+
DataJson()[self.widget_id]["items"][i]["status"] = status
|
|
199
|
+
DataJson().send_changes()
|
|
200
|
+
return
|
|
201
|
+
raise ValueError(f"Item with number {number} not found")
|
|
202
|
+
|
|
203
|
+
def get_status(self, number: int) -> str:
|
|
204
|
+
"""Get the status of an item by its number.
|
|
205
|
+
|
|
206
|
+
:param number: Number of the item. Starts from 1.
|
|
207
|
+
:type number: int
|
|
208
|
+
:return: Status of the item
|
|
209
|
+
:rtype: str
|
|
210
|
+
"""
|
|
211
|
+
for item in self._items:
|
|
212
|
+
if item.number == number:
|
|
213
|
+
return item.status
|
|
214
|
+
raise ValueError(f"Item with number {number} not found")
|
|
215
|
+
|
|
216
|
+
def get_items(self) -> List[ActivityFeed.Item]:
|
|
217
|
+
"""Get all items in the activity feed.
|
|
218
|
+
|
|
219
|
+
:return: List of all items
|
|
220
|
+
:rtype: List[ActivityFeed.Item]
|
|
221
|
+
"""
|
|
222
|
+
return self._items
|
|
223
|
+
|
|
224
|
+
def clear(self) -> None:
|
|
225
|
+
"""Remove all items from the activity feed."""
|
|
226
|
+
self._items = []
|
|
227
|
+
self.update_data()
|
|
228
|
+
DataJson().send_changes()
|
|
229
|
+
|
|
230
|
+
def set_items(self, items: List[ActivityFeed.Item]) -> None:
|
|
231
|
+
"""Replace all items in the activity feed.
|
|
232
|
+
|
|
233
|
+
:param items: New list of items
|
|
234
|
+
:type items: List[ActivityFeed.Item]
|
|
235
|
+
"""
|
|
236
|
+
self._items = items
|
|
237
|
+
self._auto_assign_numbers()
|
|
238
|
+
self.update_data()
|
|
239
|
+
DataJson().send_changes()
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
.sly-activity-feed {
|
|
2
|
+
padding: 10px;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.sly-activity-feed-item {
|
|
6
|
+
display: flex;
|
|
7
|
+
position: relative;
|
|
8
|
+
padding-bottom: 20px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.sly-activity-feed-item:last-child {
|
|
12
|
+
padding-bottom: 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.sly-activity-feed-item:not(:last-child) .sly-activity-feed-line {
|
|
16
|
+
display: block;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.sly-activity-feed-marker {
|
|
20
|
+
flex-shrink: 0;
|
|
21
|
+
width: 40px;
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
align-items: center;
|
|
25
|
+
position: relative;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.sly-activity-feed-circle {
|
|
29
|
+
width: 16px;
|
|
30
|
+
height: 16px;
|
|
31
|
+
border-radius: 50%;
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
flex-shrink: 0;
|
|
36
|
+
font-size: 10px;
|
|
37
|
+
color: white;
|
|
38
|
+
z-index: 1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.sly-activity-feed-circle.completed {
|
|
42
|
+
background-color: #67C23A;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.sly-activity-feed-circle.failed {
|
|
46
|
+
background-color: #F56C6C;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.sly-activity-feed-circle.in_progress {
|
|
50
|
+
background-color: #409EFF;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.sly-activity-feed-circle.pending {
|
|
54
|
+
background-color: #909399;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.sly-activity-feed-line {
|
|
58
|
+
position: absolute;
|
|
59
|
+
top: 16px;
|
|
60
|
+
left: 50%;
|
|
61
|
+
transform: translateX(-50%);
|
|
62
|
+
width: 2px;
|
|
63
|
+
height: calc(100% + 4px);
|
|
64
|
+
background-color: #E4E7ED;
|
|
65
|
+
display: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.sly-activity-feed-content {
|
|
69
|
+
flex: 1;
|
|
70
|
+
padding-left: 10px;
|
|
71
|
+
padding-top: 0px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.sly-activity-feed-number {
|
|
75
|
+
font-size: 11px;
|
|
76
|
+
color: #909399;
|
|
77
|
+
margin-top: 4px;
|
|
78
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<link rel="stylesheet" href="./sly/css/app/widgets/activity_feed/style.css" />
|
|
2
|
+
|
|
3
|
+
<div class="sly-activity-feed">
|
|
4
|
+
{% for item in widget._items %}
|
|
5
|
+
<div class="sly-activity-feed-item" v-if="data.{{{widget.widget_id}}}.items[{{{loop.index0}}}]">
|
|
6
|
+
<div class="sly-activity-feed-marker">
|
|
7
|
+
<div class="sly-activity-feed-circle" :class="data.{{{widget.widget_id}}}.items[{{{loop.index0}}}].status">
|
|
8
|
+
<i v-if="data.{{{widget.widget_id}}}.items[{{{loop.index0}}}].status === 'completed'" class="el-icon-check"></i>
|
|
9
|
+
<i v-else-if="data.{{{widget.widget_id}}}.items[{{{loop.index0}}}].status === 'failed'"
|
|
10
|
+
class="el-icon-close"></i>
|
|
11
|
+
<i v-else-if="data.{{{widget.widget_id}}}.items[{{{loop.index0}}}].status === 'in_progress'"
|
|
12
|
+
class="el-icon-loading"></i>
|
|
13
|
+
<i v-else class="el-icon-time"></i>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="sly-activity-feed-line"></div>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="sly-activity-feed-content">
|
|
18
|
+
{{{item.content}}}
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
{% endfor %}
|
|
22
|
+
</div>
|
|
@@ -157,3 +157,23 @@ class Card(Widget):
|
|
|
157
157
|
:rtype: bool
|
|
158
158
|
"""
|
|
159
159
|
return self._disabled["disabled"]
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def description(self) -> Optional[str]:
|
|
163
|
+
"""Description of the card.
|
|
164
|
+
|
|
165
|
+
:return: Description of the card.
|
|
166
|
+
:rtype: Optional[str]
|
|
167
|
+
"""
|
|
168
|
+
return self._description
|
|
169
|
+
|
|
170
|
+
@description.setter
|
|
171
|
+
def description(self, value: str) -> None:
|
|
172
|
+
"""Sets the description of the card.
|
|
173
|
+
|
|
174
|
+
:param value: Description of the card.
|
|
175
|
+
:type value: str
|
|
176
|
+
"""
|
|
177
|
+
self._description = value
|
|
178
|
+
StateJson()[self.widget_id]["description"] = self._description
|
|
179
|
+
StateJson().send_changes()
|