mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-11-15 18:13:01 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63665ded9b | ||
|
|
7fd81171f0 | ||
|
|
0e3dca5915 | ||
|
|
9276502e4f |
@@ -57,6 +57,17 @@ done
|
||||
granted=0
|
||||
failed=0
|
||||
|
||||
reachable() {
|
||||
local node="$1" user
|
||||
local access=(-r "$node" -a -w "$node")
|
||||
if [[ -d "$node" ]]; then
|
||||
access=(-r "$node" -a -x "$node")
|
||||
fi
|
||||
for user in frigate go2rtc; do
|
||||
s6-setuidgid "$user" test "${access[@]}" || return 1
|
||||
done
|
||||
}
|
||||
|
||||
grant() {
|
||||
local node="$1"
|
||||
# nullglob only drops patterns that hold a metacharacter, so a literal
|
||||
@@ -72,6 +83,11 @@ grant() {
|
||||
fi
|
||||
if setfacl -m "$spec" "$node" 2>/dev/null; then
|
||||
granted=$((granted + 1))
|
||||
|
||||
# an unprivileged LXC can't set ACLs on its idmapped nodes, which are often
|
||||
# already world-rw
|
||||
elif reachable "$node"; then
|
||||
return 0
|
||||
else
|
||||
failed=$((failed + 1))
|
||||
echo "[WARN] could not grant device access on ${node}; see EXTRA_GROUPS in the non-root docs for the fallback"
|
||||
|
||||
@@ -98,7 +98,7 @@ Additionally, when creating and editing camera groups in the UI, you can choose
|
||||
|
||||
:::note
|
||||
|
||||
Frigate's default dashboard ("All Cameras") will always use the first entry you've defined in streams when playing live streams from your cameras.
|
||||
Frigate's default dashboard ("All Cameras") always uses the first entry in `streams`. That should be your highest quality stream, because Auto starts on the first stream and only steps down from there. To show a lower quality stream on a dashboard, create a camera group and choose the stream in each camera's [streaming settings](#streaming-options-on-camera-group-dashboards) for that group.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
@@ -139,7 +139,8 @@ def collect(ctx: ReportContext) -> FeaturesSection:
|
||||
restream=config.birdseye.restream,
|
||||
),
|
||||
mqtt=config.mqtt.enabled,
|
||||
notifications=config.notifications.enabled,
|
||||
notifications=config.notifications.enabled
|
||||
or any(camera.notifications.enabled for camera in cameras),
|
||||
auth=config.auth.enabled,
|
||||
proxy_auth=config.proxy.header_map.user is not None,
|
||||
tls=config.tls.enabled,
|
||||
|
||||
+17
-1
@@ -45,7 +45,7 @@ from frigate.config.camera.updater import (
|
||||
CameraConfigUpdateEnum,
|
||||
CameraConfigUpdateTopic,
|
||||
)
|
||||
from frigate.const import REDACTED_CREDENTIAL_SENTINEL
|
||||
from frigate.const import REDACTED_CREDENTIAL_SENTINEL, REPLAY_CAMERA_PREFIX
|
||||
from frigate.ffmpeg_presets import FFMPEG_HWACCEL_VAAPI, _gpu_selector
|
||||
from frigate.genai import PROVIDERS, load_providers
|
||||
from frigate.jobs.media_sync import (
|
||||
@@ -979,6 +979,17 @@ def _config_set_in_memory(request: Request, body: AppConfigSetBody) -> JSONRespo
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
# an unresolvable scene would crash the threads that look up the model
|
||||
detect_update = sections.get("detect")
|
||||
if isinstance(detect_update, dict) and "scene" in detect_update:
|
||||
try:
|
||||
config._resolve_camera_model(cam_name, detect_update["scene"])
|
||||
except (TypeError, ValueError):
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": "Invalid detect scene"},
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
for section_name, update in sections.items():
|
||||
err = apply_section_update(camera_config, section_name, update)
|
||||
if err is not None:
|
||||
@@ -987,6 +998,11 @@ def _config_set_in_memory(request: Request, body: AppConfigSetBody) -> JSONRespo
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
# the model is resolved again from the scene when the replay
|
||||
# camera is recycled by the refresh below
|
||||
if "detect" in sections and cam_name.startswith(REPLAY_CAMERA_PREFIX):
|
||||
config.drop_camera_model(cam_name)
|
||||
|
||||
# Publish ZMQ updates so processing threads pick up changes
|
||||
if body.update_topic and body.update_topic.startswith("config/cameras/"):
|
||||
_, _, camera, field = body.update_topic.split("/")
|
||||
|
||||
+11
-4
@@ -82,12 +82,19 @@ async def review(
|
||||
reviewed = params.reviewed
|
||||
limit = params.limit
|
||||
severity = params.severity
|
||||
before = params.before or datetime.datetime.now().timestamp()
|
||||
after = (
|
||||
params.after
|
||||
or (datetime.datetime.now() - datetime.timedelta(hours=24)).timestamp()
|
||||
before = (
|
||||
params.before
|
||||
if params.before is not None
|
||||
else datetime.datetime.now().timestamp()
|
||||
)
|
||||
|
||||
if params.after is not None:
|
||||
after = params.after
|
||||
elif params.before is not None:
|
||||
after = 0
|
||||
else:
|
||||
after = (datetime.datetime.now() - datetime.timedelta(hours=24)).timestamp()
|
||||
|
||||
clauses = [
|
||||
(ReviewSegment.start_time < before)
|
||||
& ((ReviewSegment.end_time.is_null(True)) | (ReviewSegment.end_time > after))
|
||||
|
||||
+2
-9
@@ -76,7 +76,7 @@ from frigate.models import (
|
||||
from frigate.notices import install_registry
|
||||
from frigate.notices.registry import NoticeRegistry
|
||||
from frigate.object_detection.base import ObjectDetectProcess
|
||||
from frigate.object_detection.util import detection_frame_size
|
||||
from frigate.object_detection.util import create_detection_input_shm
|
||||
from frigate.output.output import OutputProcess
|
||||
from frigate.ptz.autotrack import PtzAutoTracker
|
||||
from frigate.ptz.onvif import OnvifController
|
||||
@@ -399,14 +399,7 @@ class FrigateApp:
|
||||
model = self.config.model_for_camera(name)
|
||||
model_cameras[model.scene].append(name)
|
||||
|
||||
try:
|
||||
shm_in = UntrackedSharedMemory(
|
||||
name=name,
|
||||
create=True,
|
||||
size=detection_frame_size(model),
|
||||
)
|
||||
except FileExistsError:
|
||||
shm_in = UntrackedSharedMemory(name=name)
|
||||
shm_in = create_detection_input_shm(name, self.config.models)
|
||||
|
||||
try:
|
||||
shm_out = UntrackedSharedMemory(
|
||||
|
||||
@@ -16,7 +16,7 @@ from frigate.config.camera.updater import (
|
||||
)
|
||||
from frigate.const import REPLAY_CAMERA_PREFIX
|
||||
from frigate.models import Regions
|
||||
from frigate.object_detection.util import detection_frame_size
|
||||
from frigate.object_detection.util import create_detection_input_shm
|
||||
from frigate.util.builtin import empty_and_close_queue
|
||||
from frigate.util.image import SharedMemoryFrameManager, UntrackedSharedMemory
|
||||
from frigate.util.object import get_camera_regions_grid
|
||||
@@ -127,13 +127,10 @@ class CameraMaintainer(threading.Thread):
|
||||
max(model.width, model.height),
|
||||
)
|
||||
|
||||
create_detection_input_shm(name, self.config.models)
|
||||
|
||||
try:
|
||||
UntrackedSharedMemory(name=f"out-{name}", create=True, size=20 * 6 * 4)
|
||||
UntrackedSharedMemory(
|
||||
name=name,
|
||||
create=True,
|
||||
size=detection_frame_size(model),
|
||||
)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -296,7 +296,8 @@ class CameraState:
|
||||
return frame_copy
|
||||
|
||||
def finished(self, obj_id: str) -> None:
|
||||
del self.tracked_objects[obj_id]
|
||||
# state rebuilt after a resolution or scene change no longer has the object
|
||||
self.tracked_objects.pop(obj_id, None)
|
||||
|
||||
def on(self, event_type: str, callback: Callable[..., Any]) -> None:
|
||||
self.callbacks[event_type].append(callback)
|
||||
|
||||
@@ -113,6 +113,23 @@ class AsyncLocalObjectDetector(BaseLocalDetector):
|
||||
return self.detect_api.receive_output()
|
||||
|
||||
|
||||
def map_input_frame(
|
||||
frame_manager: SharedMemoryFrameManager, camera: str, model: ModelConfig
|
||||
) -> np.ndarray | None:
|
||||
"""Map a camera's detection input segment as this model's input tensor.
|
||||
|
||||
The segment is sized for the largest configured model, so it can be larger
|
||||
than this model's input.
|
||||
"""
|
||||
buf = frame_manager.write(camera)
|
||||
shape = (1, model.height, model.width, 3)
|
||||
|
||||
if buf is None or len(buf) < model.height * model.width * 3:
|
||||
return None
|
||||
|
||||
return np.ndarray(shape, dtype=np.uint8, buffer=buf)
|
||||
|
||||
|
||||
class DetectorRunner(FrigateProcess):
|
||||
def __init__(
|
||||
self,
|
||||
@@ -154,14 +171,10 @@ class DetectorRunner(FrigateProcess):
|
||||
connection_id = self.detection_queue.get(timeout=1)
|
||||
except queue.Empty:
|
||||
continue
|
||||
input_frame = frame_manager.get(
|
||||
input_frame = map_input_frame(
|
||||
frame_manager,
|
||||
connection_id,
|
||||
(
|
||||
1,
|
||||
self.detector_config.model.height, # type: ignore[union-attr]
|
||||
self.detector_config.model.width, # type: ignore[union-attr]
|
||||
3,
|
||||
),
|
||||
self.detector_config.model, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
if input_frame is None:
|
||||
@@ -227,14 +240,10 @@ class AsyncDetectorRunner(FrigateProcess):
|
||||
continue
|
||||
|
||||
assert self._frame_manager is not None
|
||||
input_frame = self._frame_manager.get(
|
||||
input_frame = map_input_frame(
|
||||
self._frame_manager,
|
||||
connection_id,
|
||||
(
|
||||
1,
|
||||
self.detector_config.model.height, # type: ignore[union-attr]
|
||||
self.detector_config.model.width, # type: ignore[union-attr]
|
||||
3,
|
||||
),
|
||||
self.detector_config.model, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
if input_frame is None:
|
||||
|
||||
@@ -6,18 +6,39 @@ import threading
|
||||
from numpy import ndarray
|
||||
|
||||
from frigate.detectors.detector_config import InputTensorEnum, ModelConfig
|
||||
from frigate.util.image import UntrackedSharedMemory
|
||||
|
||||
|
||||
def detection_frame_size(model: ModelConfig) -> int:
|
||||
"""Get the shared memory size a camera needs to hand frames to a model.
|
||||
def create_detection_input_shm(
|
||||
camera: str, models: list[ModelConfig]
|
||||
) -> UntrackedSharedMemory:
|
||||
"""Create the shared memory a camera hands detection frames through.
|
||||
|
||||
It is sized for the largest model so a camera re-added under the same name
|
||||
with another scene can reuse it. Detectors keep mappings of it, so it is
|
||||
never unlinked while they run.
|
||||
|
||||
Args:
|
||||
model: The model the camera runs on
|
||||
camera: Name of the camera
|
||||
models: Every configured model
|
||||
|
||||
Returns:
|
||||
Size in bytes of one model input frame
|
||||
The camera's detection input segment
|
||||
"""
|
||||
return model.height * model.width * 3
|
||||
size = max(model.height * model.width * 3 for model in models)
|
||||
|
||||
try:
|
||||
return UntrackedSharedMemory(name=camera, create=True, size=size)
|
||||
except FileExistsError:
|
||||
shm = UntrackedSharedMemory(name=camera)
|
||||
|
||||
if shm.size >= size:
|
||||
return shm
|
||||
|
||||
# left by an earlier run with smaller models, so no detector maps it yet
|
||||
shm.close()
|
||||
shm.unlink()
|
||||
return UntrackedSharedMemory(name=camera, create=True, size=size)
|
||||
|
||||
|
||||
class RequestStore:
|
||||
|
||||
@@ -318,7 +318,13 @@ class PtzAutoTracker(threading.Thread):
|
||||
) = map(float, camera_config.onvif.autotracking.movement_weights)
|
||||
|
||||
if startup and camera_config.onvif.autotracking.calibrate_on_startup:
|
||||
await self._calibrate_camera(camera)
|
||||
try:
|
||||
await self._calibrate_camera(camera)
|
||||
except Exception:
|
||||
logger.exception(f"{camera}: Autotracker calibration failed")
|
||||
self.calibrating[camera] = False
|
||||
self._disable(camera, "calibration failed")
|
||||
return
|
||||
|
||||
self.dispatcher.publish(f"{camera}/ptz_autotracker/active", "OFF", retain=False)
|
||||
self.autotracker_init[camera] = True
|
||||
|
||||
@@ -98,6 +98,46 @@ class TestHttpApp(BaseTestHttp):
|
||||
assert response.status_code == 200
|
||||
assert app.frigate_config.cameras["front_door"].objects.track == ["person"]
|
||||
|
||||
@patch("frigate.detectors.detector_config.load_labels")
|
||||
def test_config_set_in_memory_rebinds_replay_model_on_scene_change(
|
||||
self, mock_labels
|
||||
):
|
||||
mock_labels.return_value = {}
|
||||
replay = "_replay_front_door"
|
||||
self.minimal_config["models"] = [
|
||||
{"devices": ["cpu"]},
|
||||
{"scene": "thermal", "devices": ["openvino:CPU"], "width": 300},
|
||||
]
|
||||
self.minimal_config["cameras"][replay] = self.minimal_config["cameras"].pop(
|
||||
"front_door"
|
||||
)
|
||||
app = super().create_app()
|
||||
app.config_publisher = Mock()
|
||||
assert app.frigate_config.model_for_camera(replay).scene == "default"
|
||||
|
||||
with AuthTestClient(app) as client:
|
||||
response = client.put(
|
||||
"/config/set",
|
||||
json={
|
||||
"requires_restart": 0,
|
||||
"skip_save": True,
|
||||
"update_topic": f"config/cameras/{replay}/detect",
|
||||
"config_data": {
|
||||
"cameras": {replay: {"detect": {"scene": "thermal"}}}
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert app.frigate_config.cameras[replay].detect.scene == "thermal"
|
||||
assert app.frigate_config.model_for_camera(replay).scene == "thermal"
|
||||
topics = [
|
||||
call.args[0].update_type.name
|
||||
for call in app.config_publisher.publish_update.call_args_list
|
||||
]
|
||||
assert topics[0] == "detect"
|
||||
assert topics[-1] == "refresh"
|
||||
|
||||
####################################################################################################################
|
||||
################################### Credential redaction sentinel ################################################
|
||||
####################################################################################################################
|
||||
|
||||
@@ -89,6 +89,37 @@ class TestHttpReview(BaseTestHttp):
|
||||
assert response_json[0]["id"] == id
|
||||
assert response_json[0]["has_been_reviewed"] == False
|
||||
|
||||
def test_get_review_before_only_has_no_lower_bound(self):
|
||||
now = datetime.now().timestamp()
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
id = "123456.random"
|
||||
super().insert_mock_review_segment(id, now - 72 * 3600, now - 71 * 3600)
|
||||
response = client.get("/review", params={"before": now - 48 * 3600})
|
||||
assert response.status_code == 200
|
||||
assert [r["id"] for r in response.json()] == [id]
|
||||
|
||||
def test_get_review_after_zero_is_not_defaulted(self):
|
||||
now = datetime.now().timestamp()
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
id = "123456.random"
|
||||
super().insert_mock_review_segment(id, now - 72 * 3600, now - 71 * 3600)
|
||||
response = client.get("/review", params={"after": 0})
|
||||
assert response.status_code == 200
|
||||
assert [r["id"] for r in response.json()] == [id]
|
||||
|
||||
def test_get_review_no_bounds_defaults_to_last_24_hours(self):
|
||||
now = datetime.now().timestamp()
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
super().insert_mock_review_segment(
|
||||
"123456.random", now - 72 * 3600, now - 71 * 3600
|
||||
)
|
||||
response = client.get("/review", params={"limit": 10})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == []
|
||||
|
||||
def test_get_review_with_time_filter_no_matches(self):
|
||||
"""Test that review items outside the range are not returned."""
|
||||
now = datetime.now().timestamp()
|
||||
|
||||
@@ -100,6 +100,17 @@ class TestFeaturesCollector(unittest.TestCase):
|
||||
self.assertEqual(section.users, {"admin": 1, "viewer": 1, "custom": 1})
|
||||
self.assertEqual((section.camera_groups, section.profiles), (1, 0))
|
||||
self.assertFalse(section.plus_api_key)
|
||||
self.assertFalse(section.notifications)
|
||||
|
||||
def test_camera_notifications_count_as_on(self):
|
||||
config = {
|
||||
**CONFIG,
|
||||
"cameras": {"front": {**FRONT_CAMERA, "notifications": {"enabled": True}}},
|
||||
}
|
||||
|
||||
section = features.collect(make_context(make_config(config), {}))
|
||||
|
||||
self.assertTrue(section.notifications)
|
||||
|
||||
|
||||
class TestFeatureMappings(unittest.TestCase):
|
||||
|
||||
@@ -193,6 +193,35 @@ class TestAutotrackerPrediction(unittest.TestCase):
|
||||
self.assertEqual(self._target_box([-1, -2, 1, 2], 0), 0.2)
|
||||
|
||||
|
||||
class TestAutotrackerCalibrationFailure(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_calibration_failure_disables_autotracking(self) -> None:
|
||||
tracker = _make_tracker()
|
||||
camera_config = tracker.config.cameras[CAMERA]
|
||||
camera_config.onvif.autotracking.calibrate_on_startup = True
|
||||
tracker.tracked_object_history = {}
|
||||
tracker.tracked_object_metrics = {}
|
||||
tracker.move_metrics = {}
|
||||
tracker.intercept = {}
|
||||
tracker.move_coefficients = {}
|
||||
tracker.zoom_time = {}
|
||||
tracker.move_queues = {}
|
||||
tracker.move_queue_locks = {}
|
||||
tracker.onvif.loop = asyncio.get_running_loop()
|
||||
tracker.onvif.cams = {CAMERA: {"init": True, "features": ["pt-r-fov"]}}
|
||||
tracker.onvif.get_service_capabilities = AsyncMock(return_value=True)
|
||||
tracker.onvif.get_camera_status = AsyncMock()
|
||||
tracker._process_move_queue = AsyncMock()
|
||||
tracker._calibrate_camera = AsyncMock(side_effect=RuntimeError("disconnected"))
|
||||
tracker.autotracker_init[CAMERA] = False
|
||||
|
||||
with self.assertLogs("frigate.ptz.autotrack", level="ERROR"):
|
||||
await tracker._autotracker_setup(camera_config, CAMERA, startup=True)
|
||||
|
||||
self.assertFalse(camera_config.onvif.autotracking.enabled)
|
||||
self.assertFalse(tracker.autotracker_init[CAMERA])
|
||||
self.assertFalse(tracker.calibrating[CAMERA])
|
||||
|
||||
|
||||
class TestAutotrackerMoveQueue(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_absolute_zoom_does_not_overwrite_the_pan_tilt_sample(self) -> None:
|
||||
tracker = _make_tracker()
|
||||
|
||||
@@ -91,6 +91,7 @@ class TrackedObjectProcessor(threading.Thread):
|
||||
CameraConfigUpdateEnum.enabled,
|
||||
CameraConfigUpdateEnum.motion,
|
||||
CameraConfigUpdateEnum.objects,
|
||||
CameraConfigUpdateEnum.refresh,
|
||||
CameraConfigUpdateEnum.remove,
|
||||
CameraConfigUpdateEnum.timestamp_style,
|
||||
CameraConfigUpdateEnum.zones,
|
||||
@@ -784,6 +785,21 @@ class TrackedObjectProcessor(threading.Thread):
|
||||
|
||||
self.requestor.send_data(UPDATE_CAMERA_ACTIVITY, self.camera_activity)
|
||||
|
||||
# a replay camera recycled onto another scene's model needs new state
|
||||
for camera in updated_topics.get("refresh", []):
|
||||
camera_state = self.camera_states.get(camera)
|
||||
|
||||
if (
|
||||
camera_state is None
|
||||
or camera_state.model is self.config.model_for_camera(camera)
|
||||
):
|
||||
continue
|
||||
|
||||
self.force_end_all_events(camera, camera_state)
|
||||
camera_state.shutdown()
|
||||
self.frame_manager.close_camera(camera)
|
||||
self.create_camera_state(camera)
|
||||
|
||||
# manage camera disabled state
|
||||
for camera, config in self.config.cameras.items():
|
||||
if not config.enabled_in_config:
|
||||
|
||||
Generated
+275
-184
@@ -10,7 +10,7 @@
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@cycjimmy/jsmpeg-player": "^7.0.0",
|
||||
"@hookform/resolvers": "^7.0.3",
|
||||
"@hookform/resolvers": "^7.0.6",
|
||||
"@radix-ui/react-alert-dialog": "^1.2.1",
|
||||
"@radix-ui/react-aspect-ratio": "^1.2.0",
|
||||
"@radix-ui/react-checkbox": "^1.4.1",
|
||||
@@ -37,15 +37,15 @@
|
||||
"@rjsf/shadcn": "^6.11.1",
|
||||
"@rjsf/utils": "^6.10.1",
|
||||
"@rjsf/validator-ajv8": "^6.11.1",
|
||||
"apexcharts": "^8.7.0",
|
||||
"axios": "^1.21.0",
|
||||
"apexcharts": "^8.8.1",
|
||||
"axios": "^1.21.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"copy-to-clipboard": "^4.0.2",
|
||||
"date-fns": "^4.4.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"framer-motion": "^14.10.0",
|
||||
"framer-motion": "^14.12.0",
|
||||
"hls.js": "^1.7.4",
|
||||
"i18next": "^26.7.2",
|
||||
"i18next-http-backend": "^4.0.10",
|
||||
@@ -53,7 +53,7 @@
|
||||
"js-yaml": "^5.5.0",
|
||||
"konva": "^10.9.1",
|
||||
"lodash": "^4.18.1",
|
||||
"lucide-react": "^1.70.0",
|
||||
"lucide-react": "^1.72.0",
|
||||
"monaco-yaml": "^5.5.1",
|
||||
"next-themes": "^0.4.6",
|
||||
"nosleep.js": "^0.12.0",
|
||||
@@ -62,9 +62,9 @@
|
||||
"react-day-picker": "^10.0.2",
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-dom": "^19.3.1",
|
||||
"react-dropzone": "^20.1.4",
|
||||
"react-dropzone": "^20.2.0",
|
||||
"react-grid-layout": "^2.3.0",
|
||||
"react-hook-form": "^7.91.0",
|
||||
"react-hook-form": "^7.92.0",
|
||||
"react-i18next": "^17.1.13",
|
||||
"react-icons": "^5.7.0",
|
||||
"react-konva": "^19.3.2",
|
||||
@@ -73,7 +73,7 @@
|
||||
"react-swipeable": "^7.0.2",
|
||||
"react-zoom-pan-pinch": "3.4.4",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"scroll-into-view-if-needed": "^3.1.0",
|
||||
"scroll-into-view-if-needed": "^3.2.0",
|
||||
"sonner": "^2.0.8",
|
||||
"swr": "^2.5.1",
|
||||
"tailwind-merge": "^2.6.1",
|
||||
@@ -93,7 +93,7 @@
|
||||
"@types/node": "^26.6.6",
|
||||
"@types/react": "^19.3.0",
|
||||
"@types/react-dom": "^19.3.0",
|
||||
"@vitejs/plugin-react": "^6.2.1",
|
||||
"@vitejs/plugin-react": "^6.3.0",
|
||||
"autoprefixer": "^10.6.2",
|
||||
"esbuild": "^0.28.2",
|
||||
"eslint": "^10.14.0",
|
||||
@@ -106,12 +106,12 @@
|
||||
"monaco-editor": "^0.53.0",
|
||||
"patch-package": "^8.0.1",
|
||||
"postcss": "^8.5.32",
|
||||
"prettier": "^3.9.10",
|
||||
"prettier": "^3.10.1",
|
||||
"prettier-plugin-tailwindcss": "^0.8.1",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.72.1",
|
||||
"vite": "^8.3.6"
|
||||
"typescript-eslint": "^8.72.2",
|
||||
"vite": "^8.3.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@aashutoshrathi/word-wrap": {
|
||||
@@ -450,6 +450,40 @@
|
||||
"url": "https://github.com/sponsors/JounQin"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "2.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-beta.2.tgz",
|
||||
"integrity": "sha512-MzCVoWtcg4Pm2B3je5mMvYMvj3MmBKw+fU7vbsJc9/iT8ip6/eYIHo/Dsm/mr+wsHB6SEyJDuxB09Yy6r/8+4A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "2.2.0",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "2.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-beta.2.tgz",
|
||||
"integrity": "sha512-JcrSPzPvZuNZvJqPTF9Fw25LbOJ8avzFVlaf2grmH4Cku0oGP47tRgqPZadcfj3YoZMHkFVnCYFbQM8I+i5QEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.2.0.tgz",
|
||||
"integrity": "sha512-JP7OYH38Q7+pyTAe14raZHLQIK7BDKbdwji+Xi3pcd/6QkJYQH3SX4wu6EplGrGJWRepoNzedfm0G3B9Z4e9Rw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
|
||||
@@ -1059,9 +1093,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@hookform/resolvers": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-7.0.3.tgz",
|
||||
"integrity": "sha512-beGY1JY6K5cyWlswD4BVf7UTb5MxSjKkjljrRyLPT3KIQzsTU0jOibBKqtdcKqIZYQUUyURed7roLWZtSdAtiQ==",
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-7.0.6.tgz",
|
||||
"integrity": "sha512-LSTMwW4tmRWtM7pS5HkSqahi71USbhrmjc9pNHNa7UyYTbKIK/e0G1EAw6xsEnp9GahTjXhunxRJp0Bd8E6NZA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/utils": "^0.3.0"
|
||||
@@ -1071,7 +1105,7 @@
|
||||
"@vinejs/vine": "^2.0.0 || ^3.0.0 || ^4.0.0",
|
||||
"ajv": "^8.12.0",
|
||||
"ajv-errors": "^3.0.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"ajv-formats": "^2.1.1 || ^3.0.0",
|
||||
"arktype": "^2.0.0",
|
||||
"ata-validator": "^1.23.0",
|
||||
"class-transformer": ">=0.4.0",
|
||||
@@ -1658,6 +1692,28 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.6.tgz",
|
||||
"integrity": "sha512-mRyDmEw9/fCDXCGfL1sbgIw/GUK0PVxRW4YwgPN61F3LQ+fSyoBFarJVzgGyhF6kRzEQZ0FejDqm4WLXSeqQmw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tybys/wasm-util": "^0.10.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=23.5.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emnapi/core": "^1.7.1 || 2.0.0-beta.2",
|
||||
"@emnapi/runtime": "^1.7.1 || 2.0.0-beta.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -3018,9 +3074,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm-eabi": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz",
|
||||
"integrity": "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==",
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.19.tgz",
|
||||
"integrity": "sha512-e+Ebe0WT+9tbXmNKPIiC8YrWAHYwug3m6i4p3rZGGulfZri1jtI4HslT+OZnFf/OIHowWF71DTlHwXBP2SiA7A==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -3035,9 +3091,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz",
|
||||
"integrity": "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==",
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.19.tgz",
|
||||
"integrity": "sha512-hOombrTJ+szt2ciEzS7oCA78lMPBgF5UMQWCJIyNfnrI7GLl8HYoFqCnpL2gcPOaXMqryGCU5ExdQKyDDcC+yA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3052,9 +3108,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz",
|
||||
"integrity": "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==",
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.19.tgz",
|
||||
"integrity": "sha512-XoMA2pwCKGTQzUjPgtRwkedH4WmXhdIPKT6l6oOxd1PUbNodGI6DcBekgN7EC//+x3pfBYX/qL1gNmZAqivw5g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3069,9 +3125,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz",
|
||||
"integrity": "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==",
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.19.tgz",
|
||||
"integrity": "sha512-zo8EsSSwMZCY/e+fRnGaX02YGgvJD0yvhNi/9hpGPA2MJ9ZF8dPCw75UGfpI6OpQ/myR/dIYmogRvqiZD0TrXg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3086,9 +3142,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz",
|
||||
"integrity": "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==",
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.19.tgz",
|
||||
"integrity": "sha512-O8ISAOfI1nj7AuBPd30FSF47z7f7lzlk5poQVGgiikvQuGEBFYvW8ki+UEnT+YJPnNGjQzn304klt4O9FUE7mA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3103,9 +3159,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz",
|
||||
"integrity": "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==",
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.19.tgz",
|
||||
"integrity": "sha512-/lYCba5NO0yq4S0jGMH8QTBtkvQxoXjoQWzF5BcWp9MjB9jxVRD1GQDNofkVjcOj30DWyhLIN/4Oe60JKzaONw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -3120,9 +3176,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz",
|
||||
"integrity": "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==",
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.19.tgz",
|
||||
"integrity": "sha512-pm/apFMBnSbFb3O6nG5pHO1iqHhZrjGDFEjSm+ot9J17XXPvH9L90y5h+jgfHCE+Qdlsvy8qKEcU4VKxgjD+vA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3140,9 +3196,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz",
|
||||
"integrity": "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==",
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.19.tgz",
|
||||
"integrity": "sha512-K1SmQp3FFichn8oqrNIwvFoZ5wcvjC7goBzpaMNvLf9FZc+QB/QvLmnpJZgAAGHiD980tP8FZZnXk2K94rvlxg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3160,9 +3216,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz",
|
||||
"integrity": "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==",
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.19.tgz",
|
||||
"integrity": "sha512-I0xwD57jP/3A7+5UPZin2w8Y7XHEmEM5zqpYZOcQ45MlNW6tUJUziymV3yRr7rL4vwyfEcRsRksC4a7Kc08p5Q==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -3180,9 +3236,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz",
|
||||
"integrity": "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==",
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.19.tgz",
|
||||
"integrity": "sha512-BrtEHT24SNXj9HiXqy+s/kXiSiQGt6EKCPUlRirAoIioVxKGnbXBUEkbZASAei7iZJB0/Ynm+0JP00Ipx7AnSw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -3200,9 +3256,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz",
|
||||
"integrity": "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==",
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.19.tgz",
|
||||
"integrity": "sha512-rm75/9DkHKeU2WnA8w4X94WGYgLlDCFCfkQC+4bguVJmC/jvDRv2B+2wLOYhyE0fVuv7//Ckk39OoOufyCu76Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3220,9 +3276,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz",
|
||||
"integrity": "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==",
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.19.tgz",
|
||||
"integrity": "sha512-irh/4+iYFM4GAzpluDErgjz89bj6NDtqqMAG/6O1LG38Ed3NpyebzCpcVNkFW8uhy+/3/BocUvnXDR33CXWe9A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3240,9 +3296,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz",
|
||||
"integrity": "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==",
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.19.tgz",
|
||||
"integrity": "sha512-+KWTWNoPt+CfSq94CiceWu3yPE+QzkaG+phPyz9G60tmZLwfQkcA4AeksafzvJAw4pvg1mcd5a1TkUqVv0s4JA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3256,10 +3312,29 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.19.tgz",
|
||||
"integrity": "sha512-7EH+skOsl5qpWnK0vzamgJFhOv1cwxQpsxm8tI883K9fur5c27p8q87fzEO6BbBPabA2/+s/zR63/pLc137iOg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "2.0.0-beta.2",
|
||||
"@emnapi/runtime": "2.0.0-beta.2",
|
||||
"@napi-rs/wasm-runtime": "1.2.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=23.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz",
|
||||
"integrity": "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==",
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.19.tgz",
|
||||
"integrity": "sha512-3Q1Yg1Q7tEvBWoZf5rFvG/GKZQPgwfru+p4wBajDfJFBs0SHCpL++dfTjW9gyAKd1T7ZpIYvdaUax7Pa/IhFVQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3274,9 +3349,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz",
|
||||
"integrity": "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==",
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.19.tgz",
|
||||
"integrity": "sha512-y/sqRRc6d7hECzOFc5n3LSeqctmIp9mMjY1Kq4s14uLPd/LZ4z7i1zrW6fMtp2mPeuQIC4e6ULMcwQzMomqQag==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3549,6 +3624,17 @@
|
||||
"@swc/counter": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz",
|
||||
"integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/debug": {
|
||||
"version": "4.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
||||
@@ -3675,17 +3761,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.72.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.72.1.tgz",
|
||||
"integrity": "sha512-5CokTWXpmbylfFabsbvHC0CAw3XLaGnoa+nFJX5FR592h/LoZsENUE6p/rg5xvkKrFEXz6pYmgI29AgXc2N3BQ==",
|
||||
"version": "8.72.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.72.2.tgz",
|
||||
"integrity": "sha512-222PKFXKihmFpi/pS5njoBgdAqsPhj2/GBmFbrNw8Tq8gEG9Sv+kSlKFy4u2q+mJv9ZzJgM2Gkv/bUzRZwZw5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.72.1",
|
||||
"@typescript-eslint/type-utils": "8.72.1",
|
||||
"@typescript-eslint/utils": "8.72.1",
|
||||
"@typescript-eslint/visitor-keys": "8.72.1",
|
||||
"@typescript-eslint/scope-manager": "8.72.2",
|
||||
"@typescript-eslint/type-utils": "8.72.2",
|
||||
"@typescript-eslint/utils": "8.72.2",
|
||||
"@typescript-eslint/visitor-keys": "8.72.2",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
@@ -3698,7 +3784,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.72.1",
|
||||
"@typescript-eslint/parser": "^8.72.2",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
@@ -3714,16 +3800,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.72.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.72.1.tgz",
|
||||
"integrity": "sha512-ZGwZP4OCfyA0AQTpEAMWz+rtX01DRhQWW5hB0ucKDEIX5U7jdmPSaMx94rQfaPAQzIYz2BimOgKOJtjfpRFQ9g==",
|
||||
"version": "8.72.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.72.2.tgz",
|
||||
"integrity": "sha512-llP+T1YuJOLlxEciP6OyQGtz5SMxyn+9LA2BvZw5RmToGVa1y4beEDVQL6Gje7T5WFYh19WREjDCN9Ev4gm5AA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.72.1",
|
||||
"@typescript-eslint/types": "8.72.1",
|
||||
"@typescript-eslint/typescript-estree": "8.72.1",
|
||||
"@typescript-eslint/visitor-keys": "8.72.1",
|
||||
"@typescript-eslint/scope-manager": "8.72.2",
|
||||
"@typescript-eslint/types": "8.72.2",
|
||||
"@typescript-eslint/typescript-estree": "8.72.2",
|
||||
"@typescript-eslint/visitor-keys": "8.72.2",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3739,14 +3825,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.72.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.72.1.tgz",
|
||||
"integrity": "sha512-fAuVMkwdO/huI8nyo/WKnNKf6Gag93S56Rh3Ts5mGcavwSdaN8tRg1gCeIGX8PF+nzp1BjGfO4gHhGzW1J6jTg==",
|
||||
"version": "8.72.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.72.2.tgz",
|
||||
"integrity": "sha512-6y5dLTZdHhI8Eh3yIQ/bfQhRKwn5e+TpJj64o18Cngc0iW/BCrVvrr6zGYqXPnYH31u6YXAYiFh0+qxX5mybJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.72.1",
|
||||
"@typescript-eslint/types": "^8.72.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.72.2",
|
||||
"@typescript-eslint/types": "^8.72.2",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3761,14 +3847,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.72.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.72.1.tgz",
|
||||
"integrity": "sha512-lC/t6eMCZZQAAKZEYGL6RDVSuFehpdmMT6h+nJIjIa6oylN/PwDgDVtEIbB7O9xFfZYtZAYLoLlDNWKJhtF+uQ==",
|
||||
"version": "8.72.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.72.2.tgz",
|
||||
"integrity": "sha512-iIoMJ6/8c1/QpFNyFjU0d1YzPco4MsZjQw0k3V+vfzNWAFmUmvhlyMoYEoYYDQZXc+J6Gi1X5XcxlzW2taIIGQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.72.1",
|
||||
"@typescript-eslint/visitor-keys": "8.72.1"
|
||||
"@typescript-eslint/types": "8.72.2",
|
||||
"@typescript-eslint/visitor-keys": "8.72.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -3779,9 +3865,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.72.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.1.tgz",
|
||||
"integrity": "sha512-e4D5R5vpeAEPBxT2BAzsFQL3sbwyeeeo5+aczgNSRK1V734jE7pTqaGFU+fXapWEQwnZbpFaTn32+Z8DwS6OTg==",
|
||||
"version": "8.72.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.2.tgz",
|
||||
"integrity": "sha512-+h3iCgZXuTNtoLXcnnjP12ZUHLMqoUcIHPfQrFEXTUqvb9dF8QK9b5nv5VtSo5fLkdHuks8ehbTzZj3rImAm9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -3796,15 +3882,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.72.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.72.1.tgz",
|
||||
"integrity": "sha512-iY2wlmyrXJmx4yxUGP3aYCFF3cA/6ShddOKmLU2dx7LnEykbAM8WThlxZEfd1NddqceF6eJmZUayxx9oKIYNlg==",
|
||||
"version": "8.72.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.72.2.tgz",
|
||||
"integrity": "sha512-STfkR38qIdhfkLpgtXnUjAzsNy4xouqiBO0lrVc/Wpk9Kem5/aHXUx1J8dGC16zpdYN1+yc2ILQ28er1kXX7ag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.72.1",
|
||||
"@typescript-eslint/typescript-estree": "8.72.1",
|
||||
"@typescript-eslint/utils": "8.72.1",
|
||||
"@typescript-eslint/types": "8.72.2",
|
||||
"@typescript-eslint/typescript-estree": "8.72.2",
|
||||
"@typescript-eslint/utils": "8.72.2",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
@@ -3821,9 +3907,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.72.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.72.1.tgz",
|
||||
"integrity": "sha512-LmE5IhQet1BWOWEXqBrVtBPsMAOQ6Nzk35d+WLh3mk4EUF33sqlfxjio1MdYeSvKSG5skactKKuixBIKXhL4Rw==",
|
||||
"version": "8.72.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.72.2.tgz",
|
||||
"integrity": "sha512-Xfn37OLZx03+PCU7qWx9DTv2FCRh1E6fk2+wONeNY15HXIwtcwuveaSwVpGAUptkNmPQegRxW5JyRel9T7wrog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -3835,16 +3921,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.72.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.1.tgz",
|
||||
"integrity": "sha512-DAlUoUV93hIvrtIQLwCbmZQYVZwOMhiHJrOVUUIWicFZaIYjTRqlY3I/NZevfkLmSqkNHRu2qYDcq9lrn16oOQ==",
|
||||
"version": "8.72.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.2.tgz",
|
||||
"integrity": "sha512-pQEGqIK/cqEvKivVWaFLYpe7XYejFxfuAOi4mu1oAWFvIo36aRHI9esRFTUxcIy/z8N482RJyH+LpZB0ZIU7Ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.72.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.72.1",
|
||||
"@typescript-eslint/types": "8.72.1",
|
||||
"@typescript-eslint/visitor-keys": "8.72.1",
|
||||
"@typescript-eslint/project-service": "8.72.2",
|
||||
"@typescript-eslint/tsconfig-utils": "8.72.2",
|
||||
"@typescript-eslint/types": "8.72.2",
|
||||
"@typescript-eslint/visitor-keys": "8.72.2",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -3863,16 +3949,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.72.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.72.1.tgz",
|
||||
"integrity": "sha512-5v5liypurJBFHCBNqh/RjsymAD+VWcE0djuGY/BUqp5fC3Qn+luWDEksjuTtWjPn6i5zQlUtkZCRG/CYG0yCkA==",
|
||||
"version": "8.72.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.72.2.tgz",
|
||||
"integrity": "sha512-hv3eRYbpPbbpezuax1EfGrzpa3C4ySgNZkPUkLOWOAjU8HsOKowEDAr8C7nsNl1C+SBSKDpvno6VKHnlhvp9iA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.72.1",
|
||||
"@typescript-eslint/types": "8.72.1",
|
||||
"@typescript-eslint/typescript-estree": "8.72.1"
|
||||
"@typescript-eslint/scope-manager": "8.72.2",
|
||||
"@typescript-eslint/types": "8.72.2",
|
||||
"@typescript-eslint/typescript-estree": "8.72.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -3887,13 +3973,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.72.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.1.tgz",
|
||||
"integrity": "sha512-SB8xqipSzZHUePCCLNeYxqnxq83X61/qzNhjQGbTfjxGZIeOEP7tBgWYX05Tcd6CgE02WdTJnh5b8kMZJ/+bnQ==",
|
||||
"version": "8.72.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.2.tgz",
|
||||
"integrity": "sha512-NAGrjn0Pitjqzq/GkgMC4kBYm233PQ8GffyQ9+kTA3AlNQYEzOf25EIM02BAHZv5FRJSpHpJk+OGUxVkOB93GQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.72.1",
|
||||
"@typescript-eslint/types": "8.72.2",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3911,9 +3997,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.2.1.tgz",
|
||||
"integrity": "sha512-9KyWCij3r0+qLG3Q62vBmv0VyevmYibAtxaCIVK9o0k6CXgaajZRIe8tpfchQcdZArJ5b/7OrIz4W3b2uxVRDA==",
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.3.0.tgz",
|
||||
"integrity": "sha512-H9E955aVvD2F4zNHCw4eIZbIfF6xKldlh7MNu+hSU7xwQzh65lI/N+CJWqmfhi0PEjDXIt/H7T7J/4wU63KJiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4075,18 +4161,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/apex-commons": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/apex-commons/-/apex-commons-0.8.3.tgz",
|
||||
"integrity": "sha512-WdU5lblyL1ByI/nkFOJkSgJYenCsHmay0P3UIcnSeUiCyF1b30SvPSZLbyxOCUOry8IlL7M0/3GvsGbZTMm8FA==",
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/apex-commons/-/apex-commons-0.10.0.tgz",
|
||||
"integrity": "sha512-mBXqK6wykMnWV4TzT9636k4aaeEvE8PmBIfa2bHx91eQoit8RjJrOlBpGxNGz1Bmt6gq2U3CoqPAUZftBbYdLQ==",
|
||||
"license": "SEE LICENSE IN LICENSE.md"
|
||||
},
|
||||
"node_modules/apexcharts": {
|
||||
"version": "8.7.0",
|
||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-8.7.0.tgz",
|
||||
"integrity": "sha512-HHpMlM4QvLXFLL6hyn1iivF+3IyoCrBEXYcV9elJuT+Dc4At9P5+RAaSYA1onOrcXg59+VTAsnyUDdm4GRhUfA==",
|
||||
"version": "8.8.1",
|
||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-8.8.1.tgz",
|
||||
"integrity": "sha512-Aeha39Wdrg4pTdJTpM+QsQemLoCRpwC5tEgwHYzbvtRxo4E9EmQ6zdTZbhcYia24HyPhqx3IATd1evgS0lRtzw==",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"dependencies": {
|
||||
"apex-commons": "^0.8.3"
|
||||
"apex-commons": "^0.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
@@ -4131,9 +4217,9 @@
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/attr-accept": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-4.0.0.tgz",
|
||||
"integrity": "sha512-hmCnJClmeKNKlsBHgbM8yLZRiQZ4/20UXbLJb6OUT16eWcM5/xNZerr80a/zCYob768KIGq++aLrQNTuwPsIOQ==",
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-4.0.1.tgz",
|
||||
"integrity": "sha512-rOKAGOwbnTUvwTL8rNA0hWnU3uX9tOI20di4KjR5I1ez/RJmnA37W3zz4EMxGC2YnI289yPjRy63CdAZeQFZ4Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 22"
|
||||
@@ -4177,12 +4263,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.21.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.21.0.tgz",
|
||||
"integrity": "sha512-YCzXMB53Q9PZBsh7yN7wtapit4WepwcHS2fP8QhGsBxSz5YtgYobV7GGlWlAJi+RxfU77GQTQi2DSLUma42EYA==",
|
||||
"version": "1.21.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.21.1.tgz",
|
||||
"integrity": "sha512-6eI5Q/Y4u/Um9dDlsRoq5pY2rxnUTv+L4PpLitnbk/i4jb3MxLZ5CsN5Ih3IysbeRYK4pR+P7qfMsOQl9fwwFQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.16.0",
|
||||
"follow-redirects": "^1.16.2",
|
||||
"form-data": "^4.0.6",
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
"proxy-from-env": "^2.1.0"
|
||||
@@ -5596,12 +5682,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "14.10.0",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-14.10.0.tgz",
|
||||
"integrity": "sha512-e8MjIet+IJKohYxBj8TAk9Mg5cD0HiOX87qde1YTPL67gI2ao074O6yBtQnIcWCk+bTX7XX4litepufLiYPrLg==",
|
||||
"version": "14.12.0",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-14.12.0.tgz",
|
||||
"integrity": "sha512-4m+10xYan0ic8nFI8zyB5hJkW5F8F4Rer617UecOs8esw0he9unDgXaEZ0rUX7oztz1w4iuvyPWhk4jltMX5iw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-dom": "14.10.0",
|
||||
"motion-dom": "14.12.0",
|
||||
"motion-utils": "14.7.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
@@ -6996,9 +7082,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "1.70.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.70.0.tgz",
|
||||
"integrity": "sha512-gCTPPN1MlQUBtUgdsUv+0CAUj+X8hifRUiRBw4E5v7lvDIOIpx0EZezdGQMSimS8pKA6esvullJub4GYr0mCHA==",
|
||||
"version": "1.72.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.72.0.tgz",
|
||||
"integrity": "sha512-fbF5i1GU8GODETa3Q7rXqrJrcx3iPYrQtxthom103zGx+Z0BGwKYAW9s+9435ohYJ7hQm0av0PoZcqI1CRwNrg==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -8073,9 +8159,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/motion-dom": {
|
||||
"version": "14.10.0",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-14.10.0.tgz",
|
||||
"integrity": "sha512-k0LwJZ1R5xCvqrScszO4ywuZP52lRcT3QO0IUovgczom9ZHb0BaRLOX6M2O40q7TrSLvzX/fIHHmDm8TUMt3fg==",
|
||||
"version": "14.12.0",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-14.12.0.tgz",
|
||||
"integrity": "sha512-YnAY17g8mXRVoXGZbH+v/O2SkNBJqz2TMuqL9yek9Omfhsdjw7gJwNgI5nsGZFpbbm3vINyOo+VtGPryIslatw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-utils": "14.7.0"
|
||||
@@ -8714,9 +8800,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.9.10",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz",
|
||||
"integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==",
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.10.1.tgz",
|
||||
"integrity": "sha512-70vp3ZUYoPlFbF9uiTdXHUUCGEIp7d4+6p+8DYbkm2jP7x/3i/paXVF6dopSI3zd/CEitrZN1rc4i85xWmmXlg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
@@ -9015,12 +9101,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-dropzone": {
|
||||
"version": "20.1.4",
|
||||
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-20.1.4.tgz",
|
||||
"integrity": "sha512-LyjT9iQySNkJGC6yL+XTlmfTRxHhPx0eSoTh+WlR0Ge+W35rLcRH/oH7w5bzijfPHeCFcLnO9G7i+hX+LINAAA==",
|
||||
"version": "20.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-20.2.0.tgz",
|
||||
"integrity": "sha512-FRlcSp6UFT9Etg57U4+g6O6qF/jH7sUh2Hz67lQlrZJa/3FjfAFvw7DwDSItiG2vBUXTdh2wroEwXPPg7o/jkg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"attr-accept": "^4.0.0",
|
||||
"attr-accept": "^4.0.1",
|
||||
"file-selector": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -9055,9 +9141,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-hook-form": {
|
||||
"version": "7.91.0",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.91.0.tgz",
|
||||
"integrity": "sha512-5BEBUnCBwa28AFXYTYmOaB/+CSpJ/+CWVASHEvIGUzFdHyUiPHeRhG7r5hOZ8nZi0yP7pbHdFNXO/p31EKTJFQ==",
|
||||
"version": "7.92.0",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.92.0.tgz",
|
||||
"integrity": "sha512-O5ILyY5fYHN7N13Pfxpe55KboyNFsJdM03BHpshQkClJd8raugiAUGY7VjifITrfhVxyx81ko25azXCoayhf1w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
@@ -9495,9 +9581,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.16.tgz",
|
||||
"integrity": "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==",
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.19.tgz",
|
||||
"integrity": "sha512-QIiV1J9d0pzN4jeeTH4sjPy0AZ6cAy2cG0ExeoE1+U8Rvj+hR3gYkokx0YbeUNGlZqiQJJ2utcETe0oKXwzsuA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -9511,21 +9597,22 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm-eabi": "1.2.16",
|
||||
"@rolldown/binding-android-arm64": "1.2.16",
|
||||
"@rolldown/binding-darwin-arm64": "1.2.16",
|
||||
"@rolldown/binding-darwin-x64": "1.2.16",
|
||||
"@rolldown/binding-freebsd-x64": "1.2.16",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.2.16",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.2.16",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.2.16",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.2.16",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.2.16",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.2.16",
|
||||
"@rolldown/binding-linux-x64-musl": "1.2.16",
|
||||
"@rolldown/binding-openharmony-arm64": "1.2.16",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.2.16",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.2.16"
|
||||
"@rolldown/binding-android-arm-eabi": "1.2.19",
|
||||
"@rolldown/binding-android-arm64": "1.2.19",
|
||||
"@rolldown/binding-darwin-arm64": "1.2.19",
|
||||
"@rolldown/binding-darwin-x64": "1.2.19",
|
||||
"@rolldown/binding-freebsd-x64": "1.2.19",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.2.19",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.2.19",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.2.19",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.2.19",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.2.19",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.2.19",
|
||||
"@rolldown/binding-linux-x64-musl": "1.2.19",
|
||||
"@rolldown/binding-openharmony-arm64": "1.2.19",
|
||||
"@rolldown/binding-wasm32-wasi": "1.2.19",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.2.19",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.2.19"
|
||||
}
|
||||
},
|
||||
"node_modules/run-async": {
|
||||
@@ -9583,11 +9670,15 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/scroll-into-view-if-needed": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz",
|
||||
"integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.2.0.tgz",
|
||||
"integrity": "sha512-mnlrulRcw9gQR/HI0SlT56kmyCS/Z1tH/By43m8v8kHBjD8XecscweRcjKAk3kt4WLeyJQaSSQ8efy5f+r/dqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"compute-scroll-into-view": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=26"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
@@ -10080,16 +10171,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.72.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.72.1.tgz",
|
||||
"integrity": "sha512-3Li9FfPmQp45nxEtFD1ZCrYFI3cYK5kpyNtmm1H7lMagm8u9sy6PTjy3ytwQZs7KNukOyKFHAZ6z6GjYFeaeXw==",
|
||||
"version": "8.72.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.72.2.tgz",
|
||||
"integrity": "sha512-c/+AKMW1KB81r28Sp0AAdcyXj5dzNmAsZFcsrxKbZTKmcBK2xIwBR31dcSYSDu3bKmzXe8GKrSxO+eDQ401Uzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.72.1",
|
||||
"@typescript-eslint/parser": "8.72.1",
|
||||
"@typescript-eslint/typescript-estree": "8.72.1",
|
||||
"@typescript-eslint/utils": "8.72.1"
|
||||
"@typescript-eslint/eslint-plugin": "8.72.2",
|
||||
"@typescript-eslint/parser": "8.72.2",
|
||||
"@typescript-eslint/typescript-estree": "8.72.2",
|
||||
"@typescript-eslint/utils": "8.72.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -10420,16 +10511,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.3.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.6.tgz",
|
||||
"integrity": "sha512-UI7dK29NnS54Fk+mmo7o+jI6U8Fp7hDl+X5Q1k45WH+d4dSlbLVUETGQ+hfkNgvLQGyoElPj837nZyv4MBc0VA==",
|
||||
"version": "8.3.8",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.8.tgz",
|
||||
"integrity": "sha512-LzOn3h9rG/0vhLdXrV4yJ4QFA9eEoqCl+Yx+kGJd7aB9w6nfNgVHKSYwLPZUHrz4ZGa+t+kXjV9itvcHDLQouA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.33.0",
|
||||
"picomatch": "^4.0.7",
|
||||
"postcss": "^8.5.32",
|
||||
"rolldown": "~1.2.16",
|
||||
"rolldown": "~1.2.18",
|
||||
"tinyglobby": "^0.2.17"
|
||||
},
|
||||
"bin": {
|
||||
|
||||
+12
-12
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@cycjimmy/jsmpeg-player": "^7.0.0",
|
||||
"@hookform/resolvers": "^7.0.3",
|
||||
"@hookform/resolvers": "^7.0.6",
|
||||
"@radix-ui/react-alert-dialog": "^1.2.1",
|
||||
"@radix-ui/react-aspect-ratio": "^1.2.0",
|
||||
"@radix-ui/react-checkbox": "^1.4.1",
|
||||
@@ -49,15 +49,15 @@
|
||||
"@rjsf/shadcn": "^6.11.1",
|
||||
"@rjsf/utils": "^6.10.1",
|
||||
"@rjsf/validator-ajv8": "^6.11.1",
|
||||
"apexcharts": "^8.7.0",
|
||||
"axios": "^1.21.0",
|
||||
"apexcharts": "^8.8.1",
|
||||
"axios": "^1.21.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"copy-to-clipboard": "^4.0.2",
|
||||
"date-fns": "^4.4.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"framer-motion": "^14.10.0",
|
||||
"framer-motion": "^14.12.0",
|
||||
"hls.js": "^1.7.4",
|
||||
"i18next": "^26.7.2",
|
||||
"i18next-http-backend": "^4.0.10",
|
||||
@@ -65,7 +65,7 @@
|
||||
"js-yaml": "^5.5.0",
|
||||
"konva": "^10.9.1",
|
||||
"lodash": "^4.18.1",
|
||||
"lucide-react": "^1.70.0",
|
||||
"lucide-react": "^1.72.0",
|
||||
"monaco-yaml": "^5.5.1",
|
||||
"next-themes": "^0.4.6",
|
||||
"nosleep.js": "^0.12.0",
|
||||
@@ -74,9 +74,9 @@
|
||||
"react-day-picker": "^10.0.2",
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-dom": "^19.3.1",
|
||||
"react-dropzone": "^20.1.4",
|
||||
"react-dropzone": "^20.2.0",
|
||||
"react-grid-layout": "^2.3.0",
|
||||
"react-hook-form": "^7.91.0",
|
||||
"react-hook-form": "^7.92.0",
|
||||
"react-i18next": "^17.1.13",
|
||||
"react-icons": "^5.7.0",
|
||||
"react-konva": "^19.3.2",
|
||||
@@ -85,7 +85,7 @@
|
||||
"react-swipeable": "^7.0.2",
|
||||
"react-zoom-pan-pinch": "3.4.4",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"scroll-into-view-if-needed": "^3.1.0",
|
||||
"scroll-into-view-if-needed": "^3.2.0",
|
||||
"sonner": "^2.0.8",
|
||||
"swr": "^2.5.1",
|
||||
"tailwind-merge": "^2.6.1",
|
||||
@@ -105,7 +105,7 @@
|
||||
"@types/node": "^26.6.6",
|
||||
"@types/react": "^19.3.0",
|
||||
"@types/react-dom": "^19.3.0",
|
||||
"@vitejs/plugin-react": "^6.2.1",
|
||||
"@vitejs/plugin-react": "^6.3.0",
|
||||
"autoprefixer": "^10.6.2",
|
||||
"esbuild": "^0.28.2",
|
||||
"eslint": "^10.14.0",
|
||||
@@ -118,12 +118,12 @@
|
||||
"monaco-editor": "^0.53.0",
|
||||
"patch-package": "^8.0.1",
|
||||
"postcss": "^8.5.32",
|
||||
"prettier": "^3.9.10",
|
||||
"prettier": "^3.10.1",
|
||||
"prettier-plugin-tailwindcss": "^0.8.1",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.72.1",
|
||||
"vite": "^8.3.6"
|
||||
"typescript-eslint": "^8.72.2",
|
||||
"vite": "^8.3.8"
|
||||
},
|
||||
"overrides": {
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
|
||||
@@ -173,6 +173,9 @@
|
||||
"label": "Play in background",
|
||||
"tips": "Enable this option to continue streaming when the player is hidden."
|
||||
},
|
||||
"metadata": {
|
||||
"loading": "Fetching stream metadata…"
|
||||
},
|
||||
"mode": "Streaming Technology",
|
||||
"technology": {
|
||||
"description": "Choose your preferred streaming technology. Frigate may still fall back to low bandwidth mode on playback or network errors.",
|
||||
|
||||
@@ -240,14 +240,14 @@ const detect: SectionConfigOverrides = {
|
||||
},
|
||||
replay: {
|
||||
restartRequired: [],
|
||||
fieldOrder: ["width", "height", "fps"],
|
||||
fieldOrder: ["scene", "width", "height", "fps"],
|
||||
fieldGroups: {
|
||||
resolution: ["width", "height", "fps"],
|
||||
},
|
||||
hiddenFields: [
|
||||
"enabled",
|
||||
"enabled_in_config",
|
||||
"scene",
|
||||
hideSceneWithoutChoice,
|
||||
"min_initialized",
|
||||
"max_disappeared",
|
||||
"annotation_offset",
|
||||
|
||||
@@ -248,10 +248,21 @@ export default function LivePlayer({
|
||||
// remounts them in the same render and the new player is hidden until it
|
||||
// plays. jsmpeg plays the camera, not the stream, and keeps playing
|
||||
const [renderedStream, setRenderedStream] = useState(streamName);
|
||||
const [switchFrame, setSwitchFrame] = useState<HTMLCanvasElement>();
|
||||
if (renderedStream !== streamName) {
|
||||
setRenderedStream(streamName);
|
||||
if (preferredLiveMode !== "jsmpeg") {
|
||||
setLiveReady(false);
|
||||
|
||||
// the old video is still in the DOM until this render commits, so its
|
||||
// last frame can cover the gap until the new stream plays. Tiles that
|
||||
// show the camera still already cover it
|
||||
if (!showStillWithoutActivity) {
|
||||
const frame = captureVideoFrame(overlayRef.current);
|
||||
if (frame) {
|
||||
setSwitchFrame(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,6 +274,7 @@ export default function LivePlayer({
|
||||
|
||||
const playerIsPlaying = useCallback(() => {
|
||||
setLiveReady(true);
|
||||
setSwitchFrame(undefined);
|
||||
}, []);
|
||||
|
||||
// enabled states
|
||||
@@ -416,6 +428,12 @@ export default function LivePlayer({
|
||||
</div>
|
||||
)}
|
||||
{player}
|
||||
{switchFrame && !liveReady && (
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
ref={(node) => node?.replaceChildren(switchFrame)}
|
||||
/>
|
||||
)}
|
||||
{cameraEnabled &&
|
||||
!offline &&
|
||||
(!showStillWithoutActivity || isReEnabling) &&
|
||||
@@ -582,3 +600,19 @@ export default function LivePlayer({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function captureVideoFrame(
|
||||
container: HTMLElement | null,
|
||||
): HTMLCanvasElement | undefined {
|
||||
const video = container?.querySelector("video");
|
||||
if (!video || video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = video.videoWidth;
|
||||
canvas.height = video.videoHeight;
|
||||
canvas.className = "size-full object-contain";
|
||||
canvas.getContext("2d")?.drawImage(video, 0, 0);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
@@ -12,11 +12,15 @@ import { useTranslation } from "react-i18next";
|
||||
import { LuInfo } from "react-icons/lu";
|
||||
import { MdOutlineRestartAlt } from "react-icons/md";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||
|
||||
// Radix Select needs a non-empty value. Auto itself is stored as the
|
||||
// absence of a pinned stream
|
||||
const AUTO_VALUE = "__frigate_auto__";
|
||||
|
||||
const GRID_ITEM =
|
||||
"h-auto min-h-10 min-w-[calc((100%-1rem)/3)] grow whitespace-normal bg-secondary [overflow-wrap:anywhere] px-2 py-2 text-secondary-foreground hover:bg-secondary hover:text-secondary-foreground data-[state=on]:bg-selected data-[state=on]:text-selected-foreground data-[state=on]:hover:bg-selected data-[state=on]:hover:text-selected-foreground";
|
||||
|
||||
type LiveStreamSelectProps = {
|
||||
// friendly name to go2rtc stream name, in ladder order
|
||||
streams: Record<string, string>;
|
||||
@@ -29,6 +33,7 @@ type LiveStreamSelectProps = {
|
||||
// sends auto back to the top of the ladder
|
||||
onRetry: () => void;
|
||||
disabled: boolean;
|
||||
variant?: "select" | "grid";
|
||||
};
|
||||
|
||||
export default function LiveStreamSelect({
|
||||
@@ -40,6 +45,7 @@ export default function LiveStreamSelect({
|
||||
onSelect,
|
||||
onRetry,
|
||||
disabled,
|
||||
variant = "select",
|
||||
}: LiveStreamSelectProps) {
|
||||
const { t } = useTranslation(["views/live"]);
|
||||
|
||||
@@ -52,48 +58,78 @@ export default function LiveStreamSelect({
|
||||
const auto = autoAvailable && pinnedStream == undefined;
|
||||
const value = auto ? AUTO_VALUE : (pinnedStream ?? playingStream);
|
||||
|
||||
const autoLabel = auto
|
||||
? t("stream.auto.selected", {
|
||||
stream:
|
||||
autoReason === "floor"
|
||||
? t("stream.auto.floorLabel")
|
||||
: labelFor(playingStream),
|
||||
})
|
||||
: t("stream.auto.label");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onValueChange={(next) =>
|
||||
onSelect(next === AUTO_VALUE ? undefined : next)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue>
|
||||
{auto
|
||||
? t("stream.auto.selected", {
|
||||
stream:
|
||||
autoReason === "floor"
|
||||
? t("stream.auto.floorLabel")
|
||||
: labelFor(playingStream),
|
||||
})
|
||||
: labelFor(value)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
{variant === "grid" &&
|
||||
!autoAvailable &&
|
||||
Object.keys(streams).length <= 1 ? (
|
||||
<div className="text-sm">{labelFor(playingStream)}</div>
|
||||
) : variant === "grid" ? (
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
className="flex flex-wrap items-stretch gap-2"
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onValueChange={(next) => {
|
||||
// radix sends an empty value when the active item is tapped again
|
||||
if (next) {
|
||||
onSelect(next === AUTO_VALUE ? undefined : next);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{autoAvailable && (
|
||||
<ToggleGroupItem value={AUTO_VALUE} className={GRID_ITEM}>
|
||||
{autoLabel}
|
||||
</ToggleGroupItem>
|
||||
)}
|
||||
{Object.entries(streams).map(([label, name]) => (
|
||||
<ToggleGroupItem key={label} value={name} className={GRID_ITEM}>
|
||||
{label}
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
) : (
|
||||
<Select
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onValueChange={(next) =>
|
||||
onSelect(next === AUTO_VALUE ? undefined : next)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue>{auto ? autoLabel : labelFor(value)}</SelectValue>
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{autoAvailable && (
|
||||
<SelectItem className="cursor-pointer" value={AUTO_VALUE}>
|
||||
<span className="flex flex-col gap-0.5 whitespace-normal">
|
||||
<span>{t("stream.auto.label")}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t("stream.auto.tips")}
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{autoAvailable && (
|
||||
<SelectItem className="cursor-pointer" value={AUTO_VALUE}>
|
||||
<span className="flex flex-col gap-0.5 whitespace-normal">
|
||||
<span>{t("stream.auto.label")}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t("stream.auto.tips")}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</SelectItem>
|
||||
)}
|
||||
{Object.entries(streams).map(([label, name]) => (
|
||||
<SelectItem key={label} className="cursor-pointer" value={name}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</SelectItem>
|
||||
)}
|
||||
{Object.entries(streams).map(([label, name]) => (
|
||||
<SelectItem key={label} className="cursor-pointer" value={name}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
|
||||
{auto && autoReason && !disabled && (
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
|
||||
@@ -230,15 +230,16 @@ export default function LiveCameraView({
|
||||
}
|
||||
}, [streamNameLoaded, liveStreams, pinnedStream, clearPinnedStream]);
|
||||
|
||||
const { data: cameraMetadata } = useSWR<LiveStreamMetadata>(
|
||||
isRestreamed ? `go2rtc/streams/${streamName}` : null,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
revalidateIfStale: false,
|
||||
dedupingInterval: 60000,
|
||||
},
|
||||
);
|
||||
const { data: cameraMetadata, isLoading: cameraMetadataLoading } =
|
||||
useSWR<LiveStreamMetadata>(
|
||||
isRestreamed ? `go2rtc/streams/${streamName}` : null,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
revalidateIfStale: false,
|
||||
dedupingInterval: 60000,
|
||||
},
|
||||
);
|
||||
|
||||
const webRTCAvailability = useWebRTCAvailableForStream(
|
||||
cameraMetadata,
|
||||
@@ -847,6 +848,7 @@ export default function LiveCameraView({
|
||||
onResetStream={resetStream}
|
||||
supportsAudioOutput={supportsAudioOutput}
|
||||
supports2WayTalk={supports2WayTalk}
|
||||
streamMetadataLoading={cameraMetadataLoading}
|
||||
cameraEnabled={cameraEnabled}
|
||||
debug={debug}
|
||||
setDebug={setDebug}
|
||||
@@ -873,9 +875,12 @@ export default function LiveCameraView({
|
||||
growClassName,
|
||||
)}
|
||||
ref={clickOverlayRef}
|
||||
style={{
|
||||
aspectRatio: constrainedAspectRatio,
|
||||
}}
|
||||
style={
|
||||
{
|
||||
aspectRatio: constrainedAspectRatio,
|
||||
"--camera-aspect": cameraAspectRatio,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{clickOverlay && overlaySize.width > 0 && (
|
||||
<div
|
||||
@@ -914,7 +919,10 @@ export default function LiveCameraView({
|
||||
)}
|
||||
<LivePlayer
|
||||
key={camera.name}
|
||||
className={fullscreen ? "rounded-none" : ""}
|
||||
className={cn(
|
||||
"aspect-[var(--camera-aspect)]",
|
||||
fullscreen && "rounded-none",
|
||||
)}
|
||||
windowVisible
|
||||
showStillWithoutActivity={false}
|
||||
alwaysShowCameraName={false}
|
||||
@@ -1007,6 +1015,7 @@ type FrigateCameraFeaturesProps = {
|
||||
onResetStream: () => void;
|
||||
supportsAudioOutput: boolean;
|
||||
supports2WayTalk: boolean;
|
||||
streamMetadataLoading: boolean;
|
||||
cameraEnabled: boolean;
|
||||
debug: boolean;
|
||||
setDebug: (debug: boolean) => void;
|
||||
@@ -1038,6 +1047,7 @@ function FrigateCameraFeatures({
|
||||
onResetStream,
|
||||
supportsAudioOutput,
|
||||
supports2WayTalk,
|
||||
streamMetadataLoading,
|
||||
cameraEnabled,
|
||||
debug,
|
||||
setDebug,
|
||||
@@ -1434,7 +1444,12 @@ function FrigateCameraFeatures({
|
||||
!debug &&
|
||||
isRestreamed && (
|
||||
<div className="flex flex-row items-center gap-1 text-sm text-muted-foreground">
|
||||
{supportsAudioOutput ? (
|
||||
{streamMetadataLoading ? (
|
||||
<>
|
||||
<ActivityIndicator className="size-4" />
|
||||
<div>{t("stream.metadata.loading")}</div>
|
||||
</>
|
||||
) : supportsAudioOutput ? (
|
||||
<>
|
||||
<LuCheck className="size-4 text-success" />
|
||||
<div>{t("stream.audio.available")}</div>
|
||||
@@ -1693,72 +1708,7 @@ function FrigateCameraFeatures({
|
||||
<DrawerContent className="max-h-[75dvh] overflow-hidden rounded-2xl">
|
||||
<div className="scrollbar-container mt-2 flex h-auto flex-col gap-2 overflow-y-auto px-2 py-4">
|
||||
<>
|
||||
{isAdmin && (
|
||||
<>
|
||||
<FilterSwitch
|
||||
label={t("cameraSettings.camera")}
|
||||
isChecked={enabledState == "ON"}
|
||||
onCheckedChange={() =>
|
||||
sendEnabled(enabledState == "ON" ? "OFF" : "ON")
|
||||
}
|
||||
/>
|
||||
<FilterSwitch
|
||||
label={t("cameraSettings.objectDetection")}
|
||||
isChecked={detectState == "ON"}
|
||||
onCheckedChange={() =>
|
||||
sendDetect(detectState == "ON" ? "OFF" : "ON")
|
||||
}
|
||||
/>
|
||||
{recordingEnabled && (
|
||||
<FilterSwitch
|
||||
label={t("cameraSettings.recording")}
|
||||
isChecked={recordState == "ON"}
|
||||
onCheckedChange={() =>
|
||||
sendRecord(recordState == "ON" ? "OFF" : "ON")
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<FilterSwitch
|
||||
label={t("cameraSettings.snapshots")}
|
||||
isChecked={snapshotState == "ON"}
|
||||
onCheckedChange={() =>
|
||||
sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")
|
||||
}
|
||||
/>
|
||||
{audioDetectEnabled && (
|
||||
<FilterSwitch
|
||||
label={t("cameraSettings.audioDetection")}
|
||||
isChecked={audioState == "ON"}
|
||||
onCheckedChange={() =>
|
||||
sendAudio(audioState == "ON" ? "OFF" : "ON")
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{audioDetectEnabled && transcriptionEnabled && (
|
||||
<FilterSwitch
|
||||
label={t("cameraSettings.transcription")}
|
||||
disabled={audioState == "OFF"}
|
||||
isChecked={transcriptionState == "ON"}
|
||||
onCheckedChange={() =>
|
||||
sendTranscription(
|
||||
transcriptionState == "ON" ? "OFF" : "ON",
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{autotrackingEnabled && (
|
||||
<FilterSwitch
|
||||
label={t("cameraSettings.autotracking")}
|
||||
isChecked={autotrackingState == "ON"}
|
||||
onCheckedChange={() =>
|
||||
sendAutotracking(autotrackingState == "ON" ? "OFF" : "ON")
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="mt-3 flex flex-col gap-5">
|
||||
<div className="mb-3 flex flex-col gap-5">
|
||||
{!isRestreamed && (
|
||||
<div className="flex flex-col gap-2 p-2">
|
||||
<Label>{t("stream.title")}</Label>
|
||||
@@ -1811,6 +1761,7 @@ function FrigateCameraFeatures({
|
||||
onSelect={onSelectStream}
|
||||
onRetry={onResetStream}
|
||||
disabled={debug || forceLowBandwidth}
|
||||
variant="grid"
|
||||
/>
|
||||
|
||||
{debug && (
|
||||
@@ -1826,7 +1777,12 @@ function FrigateCameraFeatures({
|
||||
!debug &&
|
||||
isRestreamed && (
|
||||
<div className="mt-1 flex flex-row items-center gap-1 text-sm text-muted-foreground">
|
||||
{supportsAudioOutput ? (
|
||||
{streamMetadataLoading ? (
|
||||
<>
|
||||
<ActivityIndicator className="size-4" size={14} />
|
||||
<div>{t("stream.metadata.loading")}</div>
|
||||
</>
|
||||
) : supportsAudioOutput ? (
|
||||
<>
|
||||
<LuCheck className="size-4 text-success" />
|
||||
<div>{t("stream.audio.available")}</div>
|
||||
@@ -1955,6 +1911,73 @@ function FrigateCameraFeatures({
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isAdmin && (
|
||||
<>
|
||||
<FilterSwitch
|
||||
label={t("cameraSettings.camera")}
|
||||
isChecked={enabledState == "ON"}
|
||||
onCheckedChange={() =>
|
||||
sendEnabled(enabledState == "ON" ? "OFF" : "ON")
|
||||
}
|
||||
/>
|
||||
<FilterSwitch
|
||||
label={t("cameraSettings.objectDetection")}
|
||||
isChecked={detectState == "ON"}
|
||||
onCheckedChange={() =>
|
||||
sendDetect(detectState == "ON" ? "OFF" : "ON")
|
||||
}
|
||||
/>
|
||||
{recordingEnabled && (
|
||||
<FilterSwitch
|
||||
label={t("cameraSettings.recording")}
|
||||
isChecked={recordState == "ON"}
|
||||
onCheckedChange={() =>
|
||||
sendRecord(recordState == "ON" ? "OFF" : "ON")
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<FilterSwitch
|
||||
label={t("cameraSettings.snapshots")}
|
||||
isChecked={snapshotState == "ON"}
|
||||
onCheckedChange={() =>
|
||||
sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")
|
||||
}
|
||||
/>
|
||||
{audioDetectEnabled && (
|
||||
<FilterSwitch
|
||||
label={t("cameraSettings.audioDetection")}
|
||||
isChecked={audioState == "ON"}
|
||||
onCheckedChange={() =>
|
||||
sendAudio(audioState == "ON" ? "OFF" : "ON")
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{audioDetectEnabled && transcriptionEnabled && (
|
||||
<FilterSwitch
|
||||
label={t("cameraSettings.transcription")}
|
||||
disabled={audioState == "OFF"}
|
||||
isChecked={transcriptionState == "ON"}
|
||||
onCheckedChange={() =>
|
||||
sendTranscription(
|
||||
transcriptionState == "ON" ? "OFF" : "ON",
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{autotrackingEnabled && (
|
||||
<FilterSwitch
|
||||
label={t("cameraSettings.autotracking")}
|
||||
isChecked={autotrackingState == "ON"}
|
||||
onCheckedChange={() =>
|
||||
sendAutotracking(autotrackingState == "ON" ? "OFF" : "ON")
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="mt-3 flex flex-col gap-5">
|
||||
{isRestreamed &&
|
||||
Object.values(camera.live.streams).length > 0 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
|
||||
Reference in New Issue
Block a user