@7365admin1/layer-common 3.2.1-staging.62 → 3.2.1-staging.64
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.
|
@@ -327,6 +327,8 @@ type AccessTab = "resident" | "visitor" | "administrator";
|
|
|
327
327
|
type AccessRow = {
|
|
328
328
|
key: string;
|
|
329
329
|
identityKey: string;
|
|
330
|
+
identityType?: string;
|
|
331
|
+
visitorId?: string;
|
|
330
332
|
name: string;
|
|
331
333
|
facialData: string;
|
|
332
334
|
faceImage?: string;
|
|
@@ -351,6 +353,7 @@ const { getVisitors } = useVisitor();
|
|
|
351
353
|
|
|
352
354
|
const readers = ref<Record<string, any>[]>([]);
|
|
353
355
|
const identities = ref<Record<string, any>[]>([]);
|
|
356
|
+
const allIdentities = ref<Record<string, any>[]>([]);
|
|
354
357
|
const visitorRecords = ref<Record<string, any>>({});
|
|
355
358
|
const logs = ref<Record<string, any>[]>([]);
|
|
356
359
|
const liveAccessLogs = ref<Record<string, any>[]>([]);
|
|
@@ -558,28 +561,41 @@ async function loadDashboard() {
|
|
|
558
561
|
const isAdministratorTab = activeTab.value === "administrator";
|
|
559
562
|
const isVisitorTab = activeTab.value === "visitor";
|
|
560
563
|
const [identityResponse, logResponse, hidAccessLogs, configurationResponse, roleResponse, visitorResponse, readerUserResponse] = await Promise.all([
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
564
|
+
loadDashboardSource(
|
|
565
|
+
getIdentities(selectedReaderId.value, {
|
|
566
|
+
page: 1,
|
|
567
|
+
limit: 500,
|
|
568
|
+
}),
|
|
569
|
+
null,
|
|
570
|
+
"identities",
|
|
571
|
+
),
|
|
572
|
+
loadDashboardSource(
|
|
573
|
+
getLogs(selectedReaderId.value, {
|
|
574
|
+
page: 1,
|
|
575
|
+
limit: 500,
|
|
576
|
+
}),
|
|
577
|
+
null,
|
|
578
|
+
"persisted access logs",
|
|
579
|
+
),
|
|
580
|
+
loadDashboardSource(loadLiveAccessLogs(selectedReaderId.value), [], "live access logs"),
|
|
581
|
+
loadDashboardSource(loadReaderConfiguration(selectedReaderId.value), {}, "reader configuration"),
|
|
582
|
+
isAdministratorTab
|
|
583
|
+
? loadDashboardSource(loadAdministratorRoles(selectedReaderId.value), null, "administrator roles")
|
|
584
|
+
: Promise.resolve(null),
|
|
573
585
|
isVisitorTab && props.org
|
|
574
|
-
?
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
586
|
+
? loadDashboardSource(
|
|
587
|
+
getVisitors({
|
|
588
|
+
org: props.org,
|
|
589
|
+
site: props.site,
|
|
590
|
+
page: 1,
|
|
591
|
+
limit: 500,
|
|
592
|
+
order: "desc",
|
|
593
|
+
}),
|
|
594
|
+
null,
|
|
595
|
+
"visitor records",
|
|
596
|
+
)
|
|
581
597
|
: Promise.resolve(null),
|
|
582
|
-
loadReaderUsers(selectedReaderId.value),
|
|
598
|
+
loadDashboardSource(loadReaderUsers(selectedReaderId.value), [], "reader users"),
|
|
583
599
|
]);
|
|
584
600
|
|
|
585
601
|
if (sequence !== loadSequence.value) return;
|
|
@@ -587,18 +603,19 @@ async function loadDashboard() {
|
|
|
587
603
|
const loadedIdentities = identityResponse?.items ?? identityResponse?.data?.items ?? identityResponse?.data?.identities ?? [];
|
|
588
604
|
const mergedIdentities = mergeReaderUsersWithIdentities(readerUserResponse, loadedIdentities);
|
|
589
605
|
const loadedVisitors = visitorResponse?.items ?? visitorResponse?.data?.items ?? visitorResponse?.data?.visitors ?? [];
|
|
590
|
-
visitorRecords.value =
|
|
591
|
-
|
|
592
|
-
|
|
606
|
+
visitorRecords.value = Object.fromEntries(
|
|
607
|
+
loadedVisitors.map((visitor: Record<string, any>) => [String(visitor._id), visitor]),
|
|
608
|
+
);
|
|
593
609
|
administratorUserIds.value = getAdministratorRoleUserIds(roleResponse);
|
|
610
|
+
allIdentities.value = mergedIdentities;
|
|
594
611
|
identities.value = isAdministratorTab
|
|
595
612
|
? mergedIdentities.filter((identity: Record<string, any>) => {
|
|
596
613
|
const hidUserId = toHidNumericId(identity.hidUserId);
|
|
597
614
|
return Boolean(hidUserId && administratorUserIds.value.has(hidUserId));
|
|
598
615
|
})
|
|
599
|
-
:
|
|
600
|
-
? mergedIdentities.filter(
|
|
601
|
-
|
|
616
|
+
: activeTab.value === "visitor"
|
|
617
|
+
? mergedIdentities.filter(isVisitorIdentity)
|
|
618
|
+
: mergedIdentities.filter((identity: Record<string, any>) => !isVisitorIdentity(identity));
|
|
602
619
|
logs.value = logResponse?.items ?? logResponse?.data?.items ?? logResponse?.data?.logs ?? [];
|
|
603
620
|
liveAccessLogs.value = hidAccessLogs;
|
|
604
621
|
readerConfiguration.value = normalizeReaderConfiguration(configurationResponse);
|
|
@@ -610,6 +627,15 @@ async function loadDashboard() {
|
|
|
610
627
|
}
|
|
611
628
|
}
|
|
612
629
|
|
|
630
|
+
async function loadDashboardSource<T>(request: Promise<T>, fallback: T, label: string): Promise<T> {
|
|
631
|
+
try {
|
|
632
|
+
return await request;
|
|
633
|
+
} catch (error) {
|
|
634
|
+
console.warn(`Unable to load HID ${label}`, error);
|
|
635
|
+
return fallback;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
613
639
|
async function loadUserImages(items: Record<string, any>[]) {
|
|
614
640
|
if (!selectedReaderId.value) return;
|
|
615
641
|
|
|
@@ -775,6 +801,7 @@ function mergeReaderUsersWithIdentities(readerUsers: Record<string, any>[], iden
|
|
|
775
801
|
function resetDashboardData() {
|
|
776
802
|
selectedHistoryRow.value = undefined;
|
|
777
803
|
identities.value = [];
|
|
804
|
+
allIdentities.value = [];
|
|
778
805
|
visitorRecords.value = {};
|
|
779
806
|
logs.value = [];
|
|
780
807
|
liveAccessLogs.value = [];
|
|
@@ -782,15 +809,11 @@ function resetDashboardData() {
|
|
|
782
809
|
administratorUserIds.value = new Set();
|
|
783
810
|
}
|
|
784
811
|
|
|
785
|
-
function getIdentityType(tab: AccessTab) {
|
|
786
|
-
if (tab === "administrator") return "admin";
|
|
787
|
-
return tab;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
812
|
function isVisibleForActiveTab(row: AccessRow) {
|
|
791
813
|
const identity = findIdentityByKey(row.identityKey);
|
|
814
|
+
const isVisitor = row.identityType === "visitor" || Boolean(row.visitorId) || isVisitorIdentity(identity);
|
|
792
815
|
if (activeTab.value === "visitor") {
|
|
793
|
-
return
|
|
816
|
+
return isVisitor;
|
|
794
817
|
}
|
|
795
818
|
|
|
796
819
|
if (activeTab.value === "administrator") {
|
|
@@ -798,7 +821,11 @@ function isVisibleForActiveTab(row: AccessRow) {
|
|
|
798
821
|
return Boolean(hidUserId && administratorUserIds.value.has(hidUserId));
|
|
799
822
|
}
|
|
800
823
|
|
|
801
|
-
return !
|
|
824
|
+
return !isVisitor;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function isVisitorIdentity(identity: Record<string, unknown> | undefined) {
|
|
828
|
+
return Boolean(identity && (identity.type === "visitor" || identity.visitor));
|
|
802
829
|
}
|
|
803
830
|
|
|
804
831
|
function loadAdministratorRoles(readerId: string) {
|
|
@@ -850,7 +877,7 @@ function getIdentityKey(identity: Record<string, any>) {
|
|
|
850
877
|
|
|
851
878
|
function findIdentityByKey(identityKey: string) {
|
|
852
879
|
if (!identityKey) return undefined;
|
|
853
|
-
return
|
|
880
|
+
return allIdentities.value.find((identity) => getIdentityKey(identity) === identityKey);
|
|
854
881
|
}
|
|
855
882
|
|
|
856
883
|
function getLogIdentityKey(log: Record<string, any>) {
|
|
@@ -1154,12 +1181,15 @@ function getRawAccessLogs(event: Record<string, any>) {
|
|
|
1154
1181
|
|
|
1155
1182
|
function mapAccessLogToRow(rawLog: Record<string, any>, event: Record<string, any>, index: string): AccessRow {
|
|
1156
1183
|
const identity = findIdentityForAccessLog(rawLog, event);
|
|
1184
|
+
const eventIdentity = event.payload?.identity || event.payload?.identityLookup || {};
|
|
1157
1185
|
const rawAccessTime = getAccessLogTime(rawLog, event);
|
|
1158
1186
|
const identityKey = identity ? getIdentityKey(identity) : getLogIdentityKey({ ...event, payload: { ...event.payload, rawAccessLog: rawLog } });
|
|
1159
1187
|
|
|
1160
1188
|
return {
|
|
1161
1189
|
key: `${event._id || event.id || "log"}-${rawLog.id || rawLog.time || index}`,
|
|
1162
1190
|
identityKey,
|
|
1191
|
+
identityType: String(identity?.type || eventIdentity.type || "").toLowerCase() || undefined,
|
|
1192
|
+
visitorId: String(identity?.visitor || eventIdentity.visitor || "") || undefined,
|
|
1163
1193
|
name: identity ? getName(identity) : getRawLogName(rawLog, event),
|
|
1164
1194
|
facialData: identity ? getFacialData(identity) : "N/A",
|
|
1165
1195
|
faceImage: identity ? getUserImageSrc(identity) : "",
|
|
@@ -1175,7 +1205,7 @@ function mapAccessLogToRow(rawLog: Record<string, any>, event: Record<string, an
|
|
|
1175
1205
|
function findIdentityForAccessLog(rawLog: Record<string, any>, event: Record<string, any>) {
|
|
1176
1206
|
const eventIdentityId = event.payload?.identity?._id;
|
|
1177
1207
|
if (eventIdentityId) {
|
|
1178
|
-
const byId =
|
|
1208
|
+
const byId = allIdentities.value.find((identity) => String(identity._id) === String(eventIdentityId));
|
|
1179
1209
|
if (byId) return byId;
|
|
1180
1210
|
}
|
|
1181
1211
|
|
|
@@ -1184,7 +1214,7 @@ function findIdentityForAccessLog(rawLog: Record<string, any>, event: Record<str
|
|
|
1184
1214
|
const cardNo = rawLog.card_value || rawLog.cardNo || event.payload?.identity?.cardNo || event.payload?.identityLookup?.cardNo;
|
|
1185
1215
|
const qrCodeValue = rawLog.qrcode_value || rawLog.qrCode || rawLog.qrcode;
|
|
1186
1216
|
|
|
1187
|
-
return
|
|
1217
|
+
return allIdentities.value.find((identity) => {
|
|
1188
1218
|
return (
|
|
1189
1219
|
(hidUserId !== undefined && hidUserId !== null && String(identity.hidUserId) === String(hidUserId)) ||
|
|
1190
1220
|
(registration && identity.registration === registration) ||
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-dialog
|
|
3
|
+
:model-value="modelValue"
|
|
4
|
+
max-width="520"
|
|
5
|
+
persistent
|
|
6
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
7
|
+
>
|
|
8
|
+
<v-card class="camera-dialog" rounded="lg">
|
|
9
|
+
<v-card-title class="d-flex align-center justify-space-between pa-4">
|
|
10
|
+
<span class="text-subtitle-1 font-weight-bold">Take Facial Photo</span>
|
|
11
|
+
<v-btn
|
|
12
|
+
icon="mdi-close"
|
|
13
|
+
variant="text"
|
|
14
|
+
size="small"
|
|
15
|
+
aria-label="Close camera"
|
|
16
|
+
@click="close"
|
|
17
|
+
/>
|
|
18
|
+
</v-card-title>
|
|
19
|
+
|
|
20
|
+
<v-divider />
|
|
21
|
+
|
|
22
|
+
<v-card-text class="pa-4">
|
|
23
|
+
<div class="camera-viewport">
|
|
24
|
+
<video
|
|
25
|
+
ref="videoElement"
|
|
26
|
+
class="camera-preview"
|
|
27
|
+
autoplay
|
|
28
|
+
muted
|
|
29
|
+
playsinline
|
|
30
|
+
/>
|
|
31
|
+
|
|
32
|
+
<div v-if="starting" class="camera-state">
|
|
33
|
+
<v-progress-circular indeterminate color="primary" />
|
|
34
|
+
<span class="mt-3">Starting camera...</span>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div v-else-if="cameraError" class="camera-state px-6 text-center">
|
|
38
|
+
<v-icon icon="mdi-camera-off-outline" size="42" color="error" />
|
|
39
|
+
<span class="mt-3 text-error">{{ cameraError }}</span>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<canvas ref="canvasElement" class="d-none" />
|
|
44
|
+
|
|
45
|
+
<div class="text-caption text-medium-emphasis mt-3 text-center">
|
|
46
|
+
Keep your face centered and make sure the area is well lit.
|
|
47
|
+
</div>
|
|
48
|
+
</v-card-text>
|
|
49
|
+
|
|
50
|
+
<v-divider />
|
|
51
|
+
|
|
52
|
+
<v-card-actions class="pa-4">
|
|
53
|
+
<v-btn variant="text" @click="close">Cancel</v-btn>
|
|
54
|
+
<v-spacer />
|
|
55
|
+
<v-btn
|
|
56
|
+
color="primary"
|
|
57
|
+
variant="flat"
|
|
58
|
+
prepend-icon="mdi-camera"
|
|
59
|
+
:disabled="starting || !!cameraError || !stream"
|
|
60
|
+
@click="capture"
|
|
61
|
+
>
|
|
62
|
+
Take Photo
|
|
63
|
+
</v-btn>
|
|
64
|
+
</v-card-actions>
|
|
65
|
+
</v-card>
|
|
66
|
+
</v-dialog>
|
|
67
|
+
</template>
|
|
68
|
+
|
|
69
|
+
<script setup lang="ts">
|
|
70
|
+
const props = defineProps<{ modelValue: boolean }>();
|
|
71
|
+
const emit = defineEmits<{
|
|
72
|
+
"update:modelValue": [value: boolean];
|
|
73
|
+
capture: [dataUrl: string];
|
|
74
|
+
}>();
|
|
75
|
+
|
|
76
|
+
const videoElement = ref<HTMLVideoElement | null>(null);
|
|
77
|
+
const canvasElement = ref<HTMLCanvasElement | null>(null);
|
|
78
|
+
const stream = shallowRef<MediaStream | null>(null);
|
|
79
|
+
const starting = ref(false);
|
|
80
|
+
const cameraError = ref("");
|
|
81
|
+
|
|
82
|
+
function stopCamera() {
|
|
83
|
+
stream.value?.getTracks().forEach((track) => track.stop());
|
|
84
|
+
stream.value = null;
|
|
85
|
+
if (videoElement.value) videoElement.value.srcObject = null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getCameraErrorMessage(caught: unknown) {
|
|
89
|
+
if (caught instanceof DOMException) {
|
|
90
|
+
if (caught.name === "NotAllowedError") {
|
|
91
|
+
return "Camera permission was denied. Allow camera access and try again.";
|
|
92
|
+
}
|
|
93
|
+
if (caught.name === "NotFoundError") {
|
|
94
|
+
return "No camera was found on this device.";
|
|
95
|
+
}
|
|
96
|
+
if (caught.name === "NotReadableError") {
|
|
97
|
+
return "The camera is currently being used by another application.";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return "Unable to open the camera. Use HTTPS or check the browser camera permission.";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function startCamera() {
|
|
104
|
+
stopCamera();
|
|
105
|
+
cameraError.value = "";
|
|
106
|
+
starting.value = true;
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
110
|
+
throw new Error("Camera API is unavailable");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
stream.value = await navigator.mediaDevices.getUserMedia({
|
|
114
|
+
audio: false,
|
|
115
|
+
video: {
|
|
116
|
+
facingMode: "user",
|
|
117
|
+
width: { ideal: 1280 },
|
|
118
|
+
height: { ideal: 960 },
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await nextTick();
|
|
123
|
+
if (!videoElement.value) throw new Error("Camera preview is unavailable");
|
|
124
|
+
videoElement.value.srcObject = stream.value;
|
|
125
|
+
await videoElement.value.play();
|
|
126
|
+
} catch (caught: unknown) {
|
|
127
|
+
stopCamera();
|
|
128
|
+
cameraError.value = getCameraErrorMessage(caught);
|
|
129
|
+
} finally {
|
|
130
|
+
starting.value = false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function capture() {
|
|
135
|
+
const video = videoElement.value;
|
|
136
|
+
const canvas = canvasElement.value;
|
|
137
|
+
if (!video || !canvas || !video.videoWidth || !video.videoHeight) return;
|
|
138
|
+
|
|
139
|
+
canvas.width = video.videoWidth;
|
|
140
|
+
canvas.height = video.videoHeight;
|
|
141
|
+
const context = canvas.getContext("2d");
|
|
142
|
+
if (!context) return;
|
|
143
|
+
|
|
144
|
+
context.translate(canvas.width, 0);
|
|
145
|
+
context.scale(-1, 1);
|
|
146
|
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
147
|
+
|
|
148
|
+
emit("capture", canvas.toDataURL("image/jpeg", 0.9));
|
|
149
|
+
close();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function close() {
|
|
153
|
+
stopCamera();
|
|
154
|
+
emit("update:modelValue", false);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
watch(
|
|
158
|
+
() => props.modelValue,
|
|
159
|
+
(isOpen) => {
|
|
160
|
+
if (isOpen) void startCamera();
|
|
161
|
+
else stopCamera();
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
onBeforeUnmount(stopCamera);
|
|
166
|
+
</script>
|
|
167
|
+
|
|
168
|
+
<style scoped>
|
|
169
|
+
.camera-dialog {
|
|
170
|
+
overflow: hidden;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.camera-viewport {
|
|
174
|
+
position: relative;
|
|
175
|
+
width: 100%;
|
|
176
|
+
overflow: hidden;
|
|
177
|
+
aspect-ratio: 3 / 4;
|
|
178
|
+
border-radius: 8px;
|
|
179
|
+
background: #111722;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.camera-preview {
|
|
183
|
+
width: 100%;
|
|
184
|
+
height: 100%;
|
|
185
|
+
object-fit: cover;
|
|
186
|
+
transform: scaleX(-1);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.camera-state {
|
|
190
|
+
position: absolute;
|
|
191
|
+
inset: 0;
|
|
192
|
+
display: flex;
|
|
193
|
+
flex-direction: column;
|
|
194
|
+
align-items: center;
|
|
195
|
+
justify-content: center;
|
|
196
|
+
background: #111722;
|
|
197
|
+
color: #ffffff;
|
|
198
|
+
}
|
|
199
|
+
</style>
|
|
@@ -237,7 +237,7 @@
|
|
|
237
237
|
<div class="sip-switch-row sip-section-switch">
|
|
238
238
|
<div>
|
|
239
239
|
<span>Enable video</span>
|
|
240
|
-
<small>Allow this reader to send
|
|
240
|
+
<small>Allow this reader to send its camera feed during SIP intercom calls.</small>
|
|
241
241
|
</div>
|
|
242
242
|
<v-switch v-model="sipForm.videoEnabled" :disabled="!sipForm.enabled" color="success" density="compact" hide-details />
|
|
243
243
|
</div>
|
|
@@ -417,7 +417,10 @@
|
|
|
417
417
|
<span class="sip-account-address">{{ sipAccount.sipAddress }}</span>
|
|
418
418
|
</div>
|
|
419
419
|
<div class="call-toggle-row web-video-toggle">
|
|
420
|
-
<
|
|
420
|
+
<div>
|
|
421
|
+
<span>Receive HID video</span>
|
|
422
|
+
<small>Show the HID camera feed without sharing this device's camera.</small>
|
|
423
|
+
</div>
|
|
421
424
|
<v-switch
|
|
422
425
|
v-model="webPhoneForm.video"
|
|
423
426
|
color="success"
|
|
@@ -457,12 +460,10 @@
|
|
|
457
460
|
class="web-phone-media"
|
|
458
461
|
:class="{
|
|
459
462
|
'has-video': webPhoneForm.video && webPhone.callState.value === 'active',
|
|
460
|
-
'has-local-video': webPhone.localVideoAvailable.value,
|
|
461
463
|
'has-remote-video': webPhone.remoteVideoAvailable.value,
|
|
462
464
|
}"
|
|
463
465
|
>
|
|
464
466
|
<video ref="remoteVideoEl" autoplay playsinline class="remote-video" />
|
|
465
|
-
<video ref="localVideoEl" autoplay muted playsinline class="local-video" />
|
|
466
467
|
<audio ref="remoteAudioEl" autoplay />
|
|
467
468
|
<div
|
|
468
469
|
v-if="!webPhoneForm.video || webPhone.callState.value !== 'active' || !webPhone.remoteVideoAvailable.value"
|
|
@@ -472,7 +473,7 @@
|
|
|
472
473
|
<strong>{{ webPhoneCallLabel }}</strong>
|
|
473
474
|
<span v-if="webPhone.currentTarget.value">{{ webPhone.currentTarget.value }}</span>
|
|
474
475
|
<span v-if="webPhoneForm.video && webPhone.callState.value === 'active' && !webPhone.remoteVideoAvailable.value">
|
|
475
|
-
Waiting for video from the
|
|
476
|
+
Waiting for video from the HID device
|
|
476
477
|
</span>
|
|
477
478
|
</div>
|
|
478
479
|
</div>
|
|
@@ -529,14 +530,6 @@
|
|
|
529
530
|
:title="webPhone.muted.value ? 'Unmute' : 'Mute'"
|
|
530
531
|
@click="toggleWebMute"
|
|
531
532
|
/>
|
|
532
|
-
<v-btn
|
|
533
|
-
v-if="webPhoneForm.video"
|
|
534
|
-
:icon="webPhone.cameraEnabled.value ? 'mdi-video' : 'mdi-video-off'"
|
|
535
|
-
variant="outlined"
|
|
536
|
-
:disabled="webPhone.callState.value !== 'active' || !webPhone.localVideoAvailable.value"
|
|
537
|
-
:title="webPhone.cameraEnabled.value ? 'Turn camera off' : 'Turn camera on'"
|
|
538
|
-
@click="toggleWebCamera"
|
|
539
|
-
/>
|
|
540
533
|
<v-btn
|
|
541
534
|
icon="mdi-phone-hangup"
|
|
542
535
|
class="hangup-web-call"
|
|
@@ -723,7 +716,6 @@ const webPhoneActionLoading = ref(false);
|
|
|
723
716
|
const sipAccount = ref<SipAccount | null>(null);
|
|
724
717
|
const sipAccountLoading = ref(false);
|
|
725
718
|
const sipAccountError = ref("");
|
|
726
|
-
const localVideoEl = ref<HTMLVideoElement | null>(null);
|
|
727
719
|
const remoteVideoEl = ref<HTMLVideoElement | null>(null);
|
|
728
720
|
const remoteAudioEl = ref<HTMLAudioElement | null>(null);
|
|
729
721
|
const webPhone = useSipWebPhone();
|
|
@@ -1249,7 +1241,6 @@ async function connectWebPhone() {
|
|
|
1249
1241
|
displayName: "iService365 Web",
|
|
1250
1242
|
video: webPhoneForm.video,
|
|
1251
1243
|
}, {
|
|
1252
|
-
localVideo: localVideoEl.value,
|
|
1253
1244
|
remoteVideo: remoteVideoEl.value,
|
|
1254
1245
|
remoteAudio: remoteAudioEl.value,
|
|
1255
1246
|
});
|
|
@@ -1315,14 +1306,6 @@ async function hangupWebCall() {
|
|
|
1315
1306
|
}
|
|
1316
1307
|
}
|
|
1317
1308
|
|
|
1318
|
-
function toggleWebCamera() {
|
|
1319
|
-
try {
|
|
1320
|
-
webPhone.toggleCamera();
|
|
1321
|
-
} catch (error: unknown) {
|
|
1322
|
-
showMessage(getErrorMessage(error), "error");
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
1309
|
function toggleWebMute() {
|
|
1327
1310
|
try {
|
|
1328
1311
|
webPhone.toggleMute();
|
|
@@ -2462,23 +2445,6 @@ export default {
|
|
|
2462
2445
|
display: block;
|
|
2463
2446
|
}
|
|
2464
2447
|
|
|
2465
|
-
.local-video {
|
|
2466
|
-
position: absolute;
|
|
2467
|
-
right: 12px;
|
|
2468
|
-
bottom: 12px;
|
|
2469
|
-
display: none;
|
|
2470
|
-
width: 108px;
|
|
2471
|
-
aspect-ratio: 3 / 4;
|
|
2472
|
-
border: 2px solid #fff;
|
|
2473
|
-
border-radius: 4px;
|
|
2474
|
-
background: #263b49;
|
|
2475
|
-
object-fit: cover;
|
|
2476
|
-
}
|
|
2477
|
-
|
|
2478
|
-
.web-phone-media.has-local-video .local-video {
|
|
2479
|
-
display: block;
|
|
2480
|
-
}
|
|
2481
|
-
|
|
2482
2448
|
.web-phone-media.has-remote-video .audio-call-state {
|
|
2483
2449
|
display: none;
|
|
2484
2450
|
}
|