@uniai-fe/uds-templates 0.4.29 → 0.4.31
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.
- package/dist/styles.css +4 -1
- package/package.json +6 -6
- package/src/cctv/components/video/Container.tsx +8 -1
- package/src/cctv/components/video/Template.tsx +1 -1
- package/src/cctv/hooks/useRtcStream.ts +15 -1
- package/src/cctv/styles/cam-list.scss +4 -1
- package/src/cctv/styles/video.scss +7 -0
- package/src/cctv/utils/video-state.ts +2 -1
package/dist/styles.css
CHANGED
|
@@ -1519,6 +1519,9 @@
|
|
|
1519
1519
|
border-radius: var(--cctv-video-radius);
|
|
1520
1520
|
background: var(--cctv-video-bg);
|
|
1521
1521
|
}
|
|
1522
|
+
.cctv-video-container[data-error=true] .cctv-video-box {
|
|
1523
|
+
visibility: hidden;
|
|
1524
|
+
}
|
|
1522
1525
|
|
|
1523
1526
|
.cctv-video-box {
|
|
1524
1527
|
width: 100%;
|
|
@@ -1681,7 +1684,7 @@
|
|
|
1681
1684
|
.cctv-cam-list-track {
|
|
1682
1685
|
display: grid;
|
|
1683
1686
|
width: 100%;
|
|
1684
|
-
grid-template-columns: repeat(auto-fit, minmax(
|
|
1687
|
+
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
|
1685
1688
|
gap: var(--cctv-list-gap);
|
|
1686
1689
|
padding: 0;
|
|
1687
1690
|
margin: 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniai-fe/uds-templates",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.31",
|
|
4
4
|
"description": "UNIAI Design System; UI Templates Package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@svgr/webpack": "^8.1.0",
|
|
73
|
-
"@tanstack/react-query": "^5.
|
|
73
|
+
"@tanstack/react-query": "^5.97.0",
|
|
74
74
|
"@types/node": "^24.10.2",
|
|
75
75
|
"@types/react": "^19.2.14",
|
|
76
76
|
"@types/react-dom": "^19.2.3",
|
|
@@ -86,11 +86,11 @@
|
|
|
86
86
|
"@uniai-fe/util-next": "workspace:*",
|
|
87
87
|
"@uniai-fe/util-rtc": "workspace:*",
|
|
88
88
|
"eslint": "^9.39.2",
|
|
89
|
-
"jotai": "^2.19.
|
|
89
|
+
"jotai": "^2.19.1",
|
|
90
90
|
"next": "^15.5.11",
|
|
91
|
-
"prettier": "^3.8.
|
|
92
|
-
"react-hook-form": "^7.72.
|
|
93
|
-
"sass": "^1.
|
|
91
|
+
"prettier": "^3.8.2",
|
|
92
|
+
"react-hook-form": "^7.72.1",
|
|
93
|
+
"sass": "^1.99.0",
|
|
94
94
|
"typescript": "~5.9.3"
|
|
95
95
|
}
|
|
96
96
|
}
|
|
@@ -3,11 +3,18 @@ import clsx from "clsx";
|
|
|
3
3
|
export default function CCTVVideoContainer({
|
|
4
4
|
className,
|
|
5
5
|
children,
|
|
6
|
+
isError,
|
|
6
7
|
}: {
|
|
7
8
|
className?: string;
|
|
8
9
|
children: React.ReactNode;
|
|
10
|
+
isError?: boolean;
|
|
9
11
|
}) {
|
|
10
12
|
return (
|
|
11
|
-
<div
|
|
13
|
+
<div
|
|
14
|
+
className={clsx("cctv-video-container", className)}
|
|
15
|
+
data-error={isError ? "true" : undefined}
|
|
16
|
+
>
|
|
17
|
+
{children}
|
|
18
|
+
</div>
|
|
12
19
|
);
|
|
13
20
|
}
|
|
@@ -32,7 +32,7 @@ const CCTVVideoTemplate = forwardRef<HTMLVideoElement, CctvVideoTemplateProps>(
|
|
|
32
32
|
ref,
|
|
33
33
|
) => {
|
|
34
34
|
return (
|
|
35
|
-
<CCTVVideoContainer className={className}>
|
|
35
|
+
<CCTVVideoContainer className={className} isError={isError}>
|
|
36
36
|
<CCTVVideoContents ref={ref} muted />
|
|
37
37
|
<CCTVVideoOverlayContainer>
|
|
38
38
|
<CCTVVideoOverlayHeader {...headerOptions} />
|
|
@@ -84,6 +84,12 @@ export function useCctvRtcStream({
|
|
|
84
84
|
useEffect(() => {
|
|
85
85
|
const currentVideo = videoRef.current;
|
|
86
86
|
|
|
87
|
+
// 토큰/카메라 에러로 전환되면 직전 MediaStream이 화면에 남지 않도록 비운다.
|
|
88
|
+
if (tokenQuery.isError || !cam?.cam_online) {
|
|
89
|
+
if (currentVideo) currentVideo.srcObject = null;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
87
93
|
// 필수 값이 없으면 스트림을 시작하지 않는다.
|
|
88
94
|
if (!tokenQuery.data?.token || !endpoint || !currentVideo) return;
|
|
89
95
|
|
|
@@ -110,6 +116,8 @@ export function useCctvRtcStream({
|
|
|
110
116
|
handle = streamHandle;
|
|
111
117
|
})
|
|
112
118
|
.catch(error => {
|
|
119
|
+
// 스트림 실패 시에도 이전 프레임이 에러 오버레이 뒤에 남지 않도록 정리한다.
|
|
120
|
+
currentVideo.srcObject = null;
|
|
113
121
|
// Error 객체 여부에 따라 메시지를 정규화한다.
|
|
114
122
|
setStreamError(
|
|
115
123
|
error instanceof Error
|
|
@@ -129,7 +137,13 @@ export function useCctvRtcStream({
|
|
|
129
137
|
handle = null;
|
|
130
138
|
currentVideo.srcObject = null;
|
|
131
139
|
};
|
|
132
|
-
}, [
|
|
140
|
+
}, [
|
|
141
|
+
endpoint,
|
|
142
|
+
tokenQuery.data?.token,
|
|
143
|
+
tokenQuery.isError,
|
|
144
|
+
cam?.cam_id,
|
|
145
|
+
cam?.cam_online,
|
|
146
|
+
]);
|
|
133
147
|
|
|
134
148
|
const liveState = useMemo(
|
|
135
149
|
() =>
|
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
.cctv-cam-list-track {
|
|
18
18
|
display: grid;
|
|
19
19
|
width: 100%;
|
|
20
|
-
grid-template-columns: repeat(
|
|
20
|
+
grid-template-columns: repeat(
|
|
21
|
+
auto-fit,
|
|
22
|
+
minmax(320px, 1fr)
|
|
23
|
+
); // 최소 너비 비율을 4:3으로 맞춤
|
|
21
24
|
gap: var(--cctv-list-gap);
|
|
22
25
|
padding: 0;
|
|
23
26
|
margin: 0;
|
|
@@ -40,9 +40,10 @@ export function getOverlayMessage({
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
if (!cam.cam_online) return CCTV_MESSAGE.offline;
|
|
43
|
-
|
|
43
|
+
// 에러 상태가 준비 상태와 겹칠 때는 에러 문구가 최종 표시 계약을 우선한다.
|
|
44
44
|
if (isTokenError) return CCTV_MESSAGE.tokenError;
|
|
45
45
|
if (streamError) return streamError;
|
|
46
|
+
if (isTokenLoading || isStreaming) return CCTV_MESSAGE.preparing;
|
|
46
47
|
return null;
|
|
47
48
|
}
|
|
48
49
|
|