Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31b13401a2 | ||
|
|
338e0f6023 | ||
|
|
07d32dbfd8 | ||
|
|
0d887b73a0 | ||
|
|
685a1cd2a4 | ||
|
|
8f9f41efe5 | ||
|
|
dde4e565c8 | ||
|
|
94575b5a07 | ||
|
|
3bf822170c | ||
|
|
deca04ed3e | ||
|
|
97908ec84b | ||
|
|
20dcb48ec5 | ||
|
|
7bad07f717 | ||
|
|
2f85f01a1a | ||
|
|
a86d07f854 | ||
|
|
414129d968 | ||
|
|
5d664b86cb | ||
|
|
d6c904d38a | ||
|
|
ab04a872e2 | ||
|
|
7f0a16d701 | ||
|
|
43e89e2119 | ||
|
|
baf211ff82 | ||
|
|
c5c55fd9fd | ||
|
|
8403e3e9b4 | ||
|
|
fd08d19939 | ||
|
|
b14717bb48 | ||
|
|
f969c04c47 | ||
|
|
f7120d824d | ||
|
|
981ef684a7 | ||
|
|
55fc4356da | ||
|
|
0425bd4348 |
+106
-4
@@ -309,6 +309,11 @@ ul.menu > li.separator:hover {
|
|||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul.menu > li.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
/* The down arrow head - trigger for context menu */
|
/* The down arrow head - trigger for context menu */
|
||||||
.menuTrigger {
|
.menuTrigger {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -517,8 +522,6 @@ div.trusted-badge {
|
|||||||
color: #999;
|
color: #999;
|
||||||
font-size: 1.8rem;
|
font-size: 1.8rem;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
right: 1rem;
|
|
||||||
bottom: 1rem;
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 3.25rem;
|
width: 3.25rem;
|
||||||
height: 3.25rem;
|
height: 3.25rem;
|
||||||
@@ -1744,11 +1747,15 @@ video {
|
|||||||
|
|
||||||
/* Badge with "You" for current user or peer's name. */
|
/* Badge with "You" for current user or peer's name. */
|
||||||
.call-party .caller-name {
|
.call-party .caller-name {
|
||||||
|
max-width: 12rem;
|
||||||
padding: 0.15rem 0.5rem;
|
padding: 0.15rem 0.5rem;
|
||||||
border-radius: 0.35rem;
|
border-radius: 0.35rem;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
text-shadow: 0 0 1px white;
|
text-shadow: 0 0 1px white;
|
||||||
background-color: rgba(255, 255, 255, 0.7);
|
background-color: rgba(255, 255, 255, 0.7);
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow-x: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.call-party.self .caller-name.inactive {
|
.call-party.self .caller-name.inactive {
|
||||||
@@ -1815,6 +1822,10 @@ video {
|
|||||||
font-weight: lighter;
|
font-weight: lighter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#go-to-latest {
|
||||||
|
right: 1.5rem;
|
||||||
|
bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes active-call {
|
@keyframes active-call {
|
||||||
0% {
|
0% {
|
||||||
@@ -2570,7 +2581,7 @@ ul.tabbar li.active > a {
|
|||||||
.material-icons.big {
|
.material-icons.big {
|
||||||
font-size: 2.2rem;
|
font-size: 2.2rem;
|
||||||
}
|
}
|
||||||
.material-icons.bigger {
|
.material-icons.x-big {
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
}
|
}
|
||||||
.material-icons.huge {
|
.material-icons.huge {
|
||||||
@@ -2580,13 +2591,26 @@ ul.tabbar li.active > a {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.composed-material i:nth-child(2) {
|
.composed-material .second {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
top: 7%;
|
top: 7%;
|
||||||
left: 39%;
|
left: 39%;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
.composed-material .second-small {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 62%;
|
||||||
|
top: 14%;
|
||||||
|
left: 30%;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* White outline */
|
||||||
|
.material-icons.outline {
|
||||||
|
position: relative;
|
||||||
|
text-shadow: -1px 1px #FFF, 1px 1px #FFF, 1px -1px #FFF, -1px -1px #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
#self-avatar .material-icons {
|
#self-avatar .material-icons {
|
||||||
font-size: 2.4rem;
|
font-size: 2.4rem;
|
||||||
@@ -3178,6 +3202,80 @@ ul.phone-country-selector span.country {
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Video conferencing. */
|
||||||
|
.call-party-speaking {
|
||||||
|
border: 2.5px solid #2196f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item .caller-name.inactive {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0.25rem;
|
||||||
|
right: 0.25rem;
|
||||||
|
max-width: 6.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item .muted {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.25rem;
|
||||||
|
right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item .avatar-box {
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
/* Center the avatar in parent view. */
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#carousel-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 54rem;
|
||||||
|
position: relative;
|
||||||
|
align-self: center;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
#carousel {
|
||||||
|
overflow-x: scroll;
|
||||||
|
overflow-y: hidden;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
scrollbar-width: none; /* Hide scrollbar in Firefox */
|
||||||
|
}
|
||||||
|
#carousel::-webkit-scrollbar {
|
||||||
|
display: none; /* Hide scrollbar in Safari and Chrome */
|
||||||
|
}
|
||||||
|
|
||||||
|
#carousel-content {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 1rem;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#carousel-prev {
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
transform: translate(50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#carousel-next {
|
||||||
|
top: 50%;
|
||||||
|
right: 0;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item {
|
||||||
|
position: relative;
|
||||||
|
width: 8rem;
|
||||||
|
height: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Very wide monitor.
|
Very wide monitor.
|
||||||
*/
|
*/
|
||||||
@@ -3241,4 +3339,8 @@ ul.phone-country-selector span.country {
|
|||||||
.pinned {
|
.pinned {
|
||||||
width: unset;
|
width: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.call-party.self {
|
||||||
|
bottom: 8.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -34,6 +34,8 @@
|
|||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.development.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.development.js"></script>
|
||||||
<!-- React-Intl for translations -->
|
<!-- React-Intl for translations -->
|
||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-intl@6/react-intl.iife.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-intl@6/react-intl.iife.js"></script>
|
||||||
|
<!-- LiveKit for group video calls -->
|
||||||
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/livekit-client@1/dist/livekit-client.umd.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.production.min.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.production.min.js"></script>
|
||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-dom@18.2.0/umd/react-dom.production.min.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-dom@18.2.0/umd/react-dom.production.min.js"></script>
|
||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-intl@6.3.2/react-intl.iife.min.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-intl@6.3.2/react-intl.iife.min.js"></script>
|
||||||
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/livekit-client@1.10.0/dist/livekit-client.umd.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript><div id="noscript-message"><div id="noscript-title">Javascript support is required.</div>
|
<noscript><div id="noscript-message"><div id="noscript-title">Javascript support is required.</div>
|
||||||
|
|||||||
Generated
+2444
-1655
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "tinode-webapp",
|
"name": "tinode-webapp",
|
||||||
"description": "Tinode messenger for the web",
|
"description": "Tinode messenger for the web",
|
||||||
"version": "0.23.0-rc1",
|
"version": "0.23.0-rc2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/tinode/webapp.git"
|
"url": "https://github.com/tinode/webapp.git"
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"firebase": "^9.17.2",
|
"firebase": "^9.17.2",
|
||||||
"libphonenumber-js": "^1.10.24",
|
"libphonenumber-js": "^1.10.24",
|
||||||
"npm-check-updates": "^16.7.12",
|
"livekit-client": "^1.10.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-intl": "^6.3.0",
|
"react-intl": "^6.3.0",
|
||||||
@@ -83,8 +83,8 @@
|
|||||||
"babel-plugin-formatjs": "^10.4.0",
|
"babel-plugin-formatjs": "^10.4.0",
|
||||||
"browserslist": "^4.21.5",
|
"browserslist": "^4.21.5",
|
||||||
"copy-webpack-plugin": "^11.0.0",
|
"copy-webpack-plugin": "^11.0.0",
|
||||||
"cssnano": "^5.1.15",
|
"cssnano": "^6.0.1",
|
||||||
"glob": "^9.3.0",
|
"glob": "^10.2.7",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
"postcss-cli": "^10.1.0",
|
"postcss-cli": "^10.1.0",
|
||||||
"webpack": "^5.76.1",
|
"webpack": "^5.76.1",
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ const OUTPUT_PATTERN = './src/i18n.min/%s.json';
|
|||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
function getLocaleFromFileName(filename) {
|
function getLocaleFromFileName(filename) {
|
||||||
const parts = filename.split('/');
|
const parts = filename.split(path.sep);
|
||||||
return parts[parts.length - 1].split('.json')[0];
|
return parts[parts.length - 1].split('.json')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -96,7 +96,7 @@ export const MAX_TITLE_LENGTH = 60;
|
|||||||
// Maximum length of topic description.
|
// Maximum length of topic description.
|
||||||
export const MAX_TOPIC_DESCRIPTION_LENGTH = 360;
|
export const MAX_TOPIC_DESCRIPTION_LENGTH = 360;
|
||||||
// Maximum length of user name when displaying a video call.
|
// Maximum length of user name when displaying a video call.
|
||||||
export const MAX_PEER_TITLE_LENGTH = 20;
|
export const MAX_PEER_TITLE_LENGTH = 24;
|
||||||
|
|
||||||
// Length of message previews in chat list, in characters.
|
// Length of message previews in chat list, in characters.
|
||||||
export const MESSAGE_PREVIEW_LENGTH = 80;
|
export const MESSAGE_PREVIEW_LENGTH = 80;
|
||||||
|
|||||||
@@ -6,3 +6,6 @@ export const CALL_STATE_IN_PROGRESS = 3;
|
|||||||
|
|
||||||
// Video call message headers representing call state.
|
// Video call message headers representing call state.
|
||||||
export const CALL_HEAD_STARTED = 'started';
|
export const CALL_HEAD_STARTED = 'started';
|
||||||
|
|
||||||
|
// Gap between video conference carousel items.
|
||||||
|
export const VC_CAROUSEL_ITEM_GAP = 16;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+26
-5
@@ -1420,7 +1420,7 @@
|
|||||||
"cred_confirmed_successfully": {
|
"cred_confirmed_successfully": {
|
||||||
"translation": "Erfolgreich bestätigt",
|
"translation": "Erfolgreich bestätigt",
|
||||||
"defaultMessage": "Confirmed successfully",
|
"defaultMessage": "Confirmed successfully",
|
||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Notification message that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
@@ -1635,17 +1635,38 @@
|
|||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"pin_message": {
|
"pin_message": {
|
||||||
"translation": "",
|
"translation": "Anpinnen",
|
||||||
"defaultMessage": "Pin",
|
"defaultMessage": "Pin",
|
||||||
"description": "Menu item [Pin] for pinning message to the top.",
|
"description": "Menu item [Pin] for pinning message to the top.",
|
||||||
"missing": true,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"unpin_message": {
|
"unpin_message": {
|
||||||
"translation": "",
|
"translation": "Lösen",
|
||||||
"defaultMessage": "Unpin",
|
"defaultMessage": "Unpin",
|
||||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||||
"missing": true,
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"calls_conference": {
|
||||||
|
"translation": "Telefonkonferenz",
|
||||||
|
"defaultMessage": "Conference call",
|
||||||
|
"description": "Conference call label",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"password_reset_success": {
|
||||||
|
"translation": "Passwort erfolgreich zurückgesetzt",
|
||||||
|
"defaultMessage": "Password reset successfully",
|
||||||
|
"description": "Notification message that the password was successfully reset.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"join_conference": {
|
||||||
|
"translation": "+ Mitmachen",
|
||||||
|
"defaultMessage": "+ Join",
|
||||||
|
"description": "Call to action to join a conference call",
|
||||||
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-1
@@ -1420,7 +1420,7 @@
|
|||||||
"cred_confirmed_successfully": {
|
"cred_confirmed_successfully": {
|
||||||
"translation": "Confirmed successfully",
|
"translation": "Confirmed successfully",
|
||||||
"defaultMessage": "Confirmed successfully",
|
"defaultMessage": "Confirmed successfully",
|
||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Notification message that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
@@ -1647,5 +1647,26 @@
|
|||||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"calls_conference": {
|
||||||
|
"translation": "Conference call",
|
||||||
|
"defaultMessage": "Conference call",
|
||||||
|
"description": "Conference call label",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"password_reset_success": {
|
||||||
|
"translation": "Password reset successfully",
|
||||||
|
"defaultMessage": "Password reset successfully",
|
||||||
|
"description": "Notification message that the password was successfully reset.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"join_conference": {
|
||||||
|
"translation": "+ Join",
|
||||||
|
"defaultMessage": "+ Join",
|
||||||
|
"description": "Call to action to join a conference call",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-5
@@ -1420,7 +1420,7 @@
|
|||||||
"cred_confirmed_successfully": {
|
"cred_confirmed_successfully": {
|
||||||
"translation": "Confirmado con éxito",
|
"translation": "Confirmado con éxito",
|
||||||
"defaultMessage": "Confirmed successfully",
|
"defaultMessage": "Confirmed successfully",
|
||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Notification message that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
@@ -1628,17 +1628,17 @@
|
|||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"pin_message": {
|
"pin_message": {
|
||||||
"translation": "",
|
"translation": "Fijar",
|
||||||
"defaultMessage": "Pin",
|
"defaultMessage": "Pin",
|
||||||
"description": "Menu item [Pin] for pinning message to the top.",
|
"description": "Menu item [Pin] for pinning message to the top.",
|
||||||
"missing": true,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"unpin_message": {
|
"unpin_message": {
|
||||||
"translation": "",
|
"translation": "Desanclar",
|
||||||
"defaultMessage": "Unpin",
|
"defaultMessage": "Unpin",
|
||||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||||
"missing": true,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"failed_to_init_audio": {
|
"failed_to_init_audio": {
|
||||||
@@ -1647,5 +1647,26 @@
|
|||||||
"description": "Error message when audio is not available",
|
"description": "Error message when audio is not available",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"calls_conference": {
|
||||||
|
"translation": "Conferencia",
|
||||||
|
"defaultMessage": "Conference call",
|
||||||
|
"description": "Conference call label",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"password_reset_success": {
|
||||||
|
"translation": "Restablecimiento de contraseña con éxito",
|
||||||
|
"defaultMessage": "Password reset successfully",
|
||||||
|
"description": "Notification message that the password was successfully reset.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"join_conference": {
|
||||||
|
"translation": "+ Unirse",
|
||||||
|
"defaultMessage": "+ Join",
|
||||||
|
"description": "Call to action to join a conference call",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,6 +143,10 @@
|
|||||||
"defaultMessage": "missed",
|
"defaultMessage": "missed",
|
||||||
"description": "Label for missed incoming call"
|
"description": "Label for missed incoming call"
|
||||||
},
|
},
|
||||||
|
"calls_conference": {
|
||||||
|
"defaultMessage": "Conference call",
|
||||||
|
"description": "Conference call label"
|
||||||
|
},
|
||||||
"calls_incoming": {
|
"calls_incoming": {
|
||||||
"defaultMessage": "Incoming call",
|
"defaultMessage": "Incoming call",
|
||||||
"description": "Incoming call label"
|
"description": "Incoming call label"
|
||||||
@@ -209,7 +213,7 @@
|
|||||||
},
|
},
|
||||||
"cred_confirmed_successfully": {
|
"cred_confirmed_successfully": {
|
||||||
"defaultMessage": "Confirmed successfully",
|
"defaultMessage": "Confirmed successfully",
|
||||||
"description": "Message explaining that the credential was successfully validated."
|
"description": "Notification message that the credential was successfully validated."
|
||||||
},
|
},
|
||||||
"credential_email_prompt": {
|
"credential_email_prompt": {
|
||||||
"defaultMessage": "Your registration email",
|
"defaultMessage": "Your registration email",
|
||||||
@@ -370,6 +374,10 @@
|
|||||||
"defaultMessage": "Invalid security token",
|
"defaultMessage": "Invalid security token",
|
||||||
"description": "Error message when resetting password."
|
"description": "Error message when resetting password."
|
||||||
},
|
},
|
||||||
|
"join_conference": {
|
||||||
|
"defaultMessage": "+ Join",
|
||||||
|
"description": "Call to action to join a conference call"
|
||||||
|
},
|
||||||
"label_client": {
|
"label_client": {
|
||||||
"defaultMessage": "Client:",
|
"defaultMessage": "Client:",
|
||||||
"description": "Label for a client version"
|
"description": "Label for a client version"
|
||||||
@@ -704,6 +712,10 @@
|
|||||||
"defaultMessage": "A text message with security code has been sent.",
|
"defaultMessage": "A text message with security code has been sent.",
|
||||||
"description": "Notification that the SMS with password reset instructions has been sent"
|
"description": "Notification that the SMS with password reset instructions has been sent"
|
||||||
},
|
},
|
||||||
|
"password_reset_success": {
|
||||||
|
"defaultMessage": "Password reset successfully",
|
||||||
|
"description": "Notification message that the password was successfully reset."
|
||||||
|
},
|
||||||
"password_unchanged_prompt": {
|
"password_unchanged_prompt": {
|
||||||
"defaultMessage": "Unchanged",
|
"defaultMessage": "Unchanged",
|
||||||
"description": "Message in editor while password is unchanged"
|
"description": "Message in editor while password is unchanged"
|
||||||
|
|||||||
+26
-5
@@ -1420,7 +1420,7 @@
|
|||||||
"cred_confirmed_successfully": {
|
"cred_confirmed_successfully": {
|
||||||
"translation": "Confirmé avec succès",
|
"translation": "Confirmé avec succès",
|
||||||
"defaultMessage": "Confirmed successfully",
|
"defaultMessage": "Confirmed successfully",
|
||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Notification message that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
@@ -1628,17 +1628,17 @@
|
|||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"pin_message": {
|
"pin_message": {
|
||||||
"translation": "",
|
"translation": "Épingler",
|
||||||
"defaultMessage": "Pin",
|
"defaultMessage": "Pin",
|
||||||
"description": "Menu item [Pin] for pinning message to the top.",
|
"description": "Menu item [Pin] for pinning message to the top.",
|
||||||
"missing": true,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"unpin_message": {
|
"unpin_message": {
|
||||||
"translation": "",
|
"translation": "Détacher",
|
||||||
"defaultMessage": "Unpin",
|
"defaultMessage": "Unpin",
|
||||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||||
"missing": true,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"failed_to_init_audio": {
|
"failed_to_init_audio": {
|
||||||
@@ -1647,5 +1647,26 @@
|
|||||||
"description": "Error message when audio is not available",
|
"description": "Error message when audio is not available",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"calls_conference": {
|
||||||
|
"translation": "Conférence téléphonique",
|
||||||
|
"defaultMessage": "Conference call",
|
||||||
|
"description": "Conference call label",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"password_reset_success": {
|
||||||
|
"translation": "Mot de passe réinitialisé avec succès",
|
||||||
|
"defaultMessage": "Password reset successfully",
|
||||||
|
"description": "Notification message that the password was successfully reset.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"join_conference": {
|
||||||
|
"translation": "+ Rejoindre",
|
||||||
|
"defaultMessage": "+ Join",
|
||||||
|
"description": "Call to action to join a conference call",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-5
@@ -1420,7 +1420,7 @@
|
|||||||
"cred_confirmed_successfully": {
|
"cred_confirmed_successfully": {
|
||||||
"translation": "성공적으로 확인됨",
|
"translation": "성공적으로 확인됨",
|
||||||
"defaultMessage": "Confirmed successfully",
|
"defaultMessage": "Confirmed successfully",
|
||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Notification message that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
@@ -1628,17 +1628,17 @@
|
|||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"pin_message": {
|
"pin_message": {
|
||||||
"translation": "",
|
"translation": "고정",
|
||||||
"defaultMessage": "Pin",
|
"defaultMessage": "Pin",
|
||||||
"description": "Menu item [Pin] for pinning message to the top.",
|
"description": "Menu item [Pin] for pinning message to the top.",
|
||||||
"missing": true,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"unpin_message": {
|
"unpin_message": {
|
||||||
"translation": "",
|
"translation": "고정 해제",
|
||||||
"defaultMessage": "Unpin",
|
"defaultMessage": "Unpin",
|
||||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||||
"missing": true,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"failed_to_init_audio": {
|
"failed_to_init_audio": {
|
||||||
@@ -1647,5 +1647,26 @@
|
|||||||
"description": "Error message when audio is not available",
|
"description": "Error message when audio is not available",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"calls_conference": {
|
||||||
|
"translation": "전화 회의",
|
||||||
|
"defaultMessage": "Conference call",
|
||||||
|
"description": "Conference call label",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"password_reset_success": {
|
||||||
|
"translation": "비밀번호 재설정 성공",
|
||||||
|
"defaultMessage": "Password reset successfully",
|
||||||
|
"description": "Notification message that the password was successfully reset.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"join_conference": {
|
||||||
|
"translation": "+ 통화에 참여",
|
||||||
|
"defaultMessage": "+ Join",
|
||||||
|
"description": "Call to action to join a conference call",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-5
@@ -1420,7 +1420,7 @@
|
|||||||
"cred_confirmed_successfully": {
|
"cred_confirmed_successfully": {
|
||||||
"translation": "Confirmat cu succes",
|
"translation": "Confirmat cu succes",
|
||||||
"defaultMessage": "Confirmed successfully",
|
"defaultMessage": "Confirmed successfully",
|
||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Notification message that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
@@ -1628,17 +1628,17 @@
|
|||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"pin_message": {
|
"pin_message": {
|
||||||
"translation": "",
|
"translation": "Fixați",
|
||||||
"defaultMessage": "Pin",
|
"defaultMessage": "Pin",
|
||||||
"description": "Menu item [Pin] for pinning message to the top.",
|
"description": "Menu item [Pin] for pinning message to the top.",
|
||||||
"missing": true,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"unpin_message": {
|
"unpin_message": {
|
||||||
"translation": "",
|
"translation": "Anulați fixarea",
|
||||||
"defaultMessage": "Unpin",
|
"defaultMessage": "Unpin",
|
||||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||||
"missing": true,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"failed_to_init_audio": {
|
"failed_to_init_audio": {
|
||||||
@@ -1647,5 +1647,26 @@
|
|||||||
"description": "Error message when audio is not available",
|
"description": "Error message when audio is not available",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"calls_conference": {
|
||||||
|
"translation": "Apel de conferinta",
|
||||||
|
"defaultMessage": "Conference call",
|
||||||
|
"description": "Conference call label",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"password_reset_success": {
|
||||||
|
"translation": "Resetarea parolei cu succes",
|
||||||
|
"defaultMessage": "Password reset successfully",
|
||||||
|
"description": "Notification message that the password was successfully reset.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"join_conference": {
|
||||||
|
"translation": "+ Participa",
|
||||||
|
"defaultMessage": "+ Join",
|
||||||
|
"description": "Call to action to join a conference call",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-5
@@ -1420,7 +1420,7 @@
|
|||||||
"cred_confirmed_successfully": {
|
"cred_confirmed_successfully": {
|
||||||
"translation": "Подтверждено успешно",
|
"translation": "Подтверждено успешно",
|
||||||
"defaultMessage": "Confirmed successfully",
|
"defaultMessage": "Confirmed successfully",
|
||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Notification message that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
@@ -1628,17 +1628,17 @@
|
|||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"pin_message": {
|
"pin_message": {
|
||||||
"translation": "",
|
"translation": "Закрепить",
|
||||||
"defaultMessage": "Pin",
|
"defaultMessage": "Pin",
|
||||||
"description": "Menu item [Pin] for pinning message to the top.",
|
"description": "Menu item [Pin] for pinning message to the top.",
|
||||||
"missing": true,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"unpin_message": {
|
"unpin_message": {
|
||||||
"translation": "",
|
"translation": "Открепить",
|
||||||
"defaultMessage": "Unpin",
|
"defaultMessage": "Unpin",
|
||||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||||
"missing": true,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"failed_to_init_audio": {
|
"failed_to_init_audio": {
|
||||||
@@ -1647,5 +1647,26 @@
|
|||||||
"description": "Error message when audio is not available",
|
"description": "Error message when audio is not available",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"calls_conference": {
|
||||||
|
"translation": "Групповой звонок",
|
||||||
|
"defaultMessage": "Conference call",
|
||||||
|
"description": "Conference call label",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"password_reset_success": {
|
||||||
|
"translation": "Пароль успешно изменен",
|
||||||
|
"defaultMessage": "Password reset successfully",
|
||||||
|
"description": "Notification message that the password was successfully reset.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"join_conference": {
|
||||||
|
"translation": "+ Присоединиться",
|
||||||
|
"defaultMessage": "+ Join",
|
||||||
|
"description": "Call to action to join a conference call",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-5
@@ -1420,7 +1420,7 @@
|
|||||||
"cred_confirmed_successfully": {
|
"cred_confirmed_successfully": {
|
||||||
"translation": "確認成功",
|
"translation": "確認成功",
|
||||||
"defaultMessage": "Confirmed successfully",
|
"defaultMessage": "Confirmed successfully",
|
||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Notification message that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
@@ -1628,17 +1628,17 @@
|
|||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"pin_message": {
|
"pin_message": {
|
||||||
"translation": "",
|
"translation": "置頂消息",
|
||||||
"defaultMessage": "Pin",
|
"defaultMessage": "Pin",
|
||||||
"description": "Menu item [Pin] for pinning message to the top.",
|
"description": "Menu item [Pin] for pinning message to the top.",
|
||||||
"missing": true,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"unpin_message": {
|
"unpin_message": {
|
||||||
"translation": "",
|
"translation": "取消置頂消息",
|
||||||
"defaultMessage": "Unpin",
|
"defaultMessage": "Unpin",
|
||||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||||
"missing": true,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"failed_to_init_audio": {
|
"failed_to_init_audio": {
|
||||||
@@ -1647,5 +1647,26 @@
|
|||||||
"description": "Error message when audio is not available",
|
"description": "Error message when audio is not available",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"calls_conference": {
|
||||||
|
"translation": "電話會議",
|
||||||
|
"defaultMessage": "Conference call",
|
||||||
|
"description": "Conference call label",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"password_reset_success": {
|
||||||
|
"translation": "密碼重置成功",
|
||||||
|
"defaultMessage": "Password reset successfully",
|
||||||
|
"description": "Notification message that the password was successfully reset.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"join_conference": {
|
||||||
|
"translation": "+ 加入通話",
|
||||||
|
"defaultMessage": "+ Join",
|
||||||
|
"description": "Call to action to join a conference call",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-5
@@ -1420,7 +1420,7 @@
|
|||||||
"cred_confirmed_successfully": {
|
"cred_confirmed_successfully": {
|
||||||
"translation": "确认成功",
|
"translation": "确认成功",
|
||||||
"defaultMessage": "Confirmed successfully",
|
"defaultMessage": "Confirmed successfully",
|
||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Notification message that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
@@ -1628,17 +1628,17 @@
|
|||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"pin_message": {
|
"pin_message": {
|
||||||
"translation": "",
|
"translation": "置顶消息",
|
||||||
"defaultMessage": "Pin",
|
"defaultMessage": "Pin",
|
||||||
"description": "Menu item [Pin] for pinning message to the top.",
|
"description": "Menu item [Pin] for pinning message to the top.",
|
||||||
"missing": true,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"unpin_message": {
|
"unpin_message": {
|
||||||
"translation": "",
|
"translation": "取消置顶消息",
|
||||||
"defaultMessage": "Unpin",
|
"defaultMessage": "Unpin",
|
||||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||||
"missing": true,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"failed_to_init_audio": {
|
"failed_to_init_audio": {
|
||||||
@@ -1647,5 +1647,26 @@
|
|||||||
"description": "Error message when audio is not available",
|
"description": "Error message when audio is not available",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"calls_conference": {
|
||||||
|
"translation": "电话会议",
|
||||||
|
"defaultMessage": "Conference call",
|
||||||
|
"description": "Conference call label",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"password_reset_success": {
|
||||||
|
"translation": "密码重置成功",
|
||||||
|
"defaultMessage": "Password reset successfully",
|
||||||
|
"description": "Notification message that the password was successfully reset.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"join_conference": {
|
||||||
|
"translation": "+ 加入通话",
|
||||||
|
"defaultMessage": "+ Join",
|
||||||
|
"description": "Call to action to join a conference call",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,8 @@ export function fullFormatter(style, data, values, key, stack) {
|
|||||||
attr.callState = data.state;
|
attr.callState = data.state;
|
||||||
attr.incoming = data.incoming;
|
attr.incoming = data.incoming;
|
||||||
attr.duration = data.duration;
|
attr.duration = data.duration;
|
||||||
|
attr.vc = data.vc;
|
||||||
|
attr.onCallJoin = this.onCallJoin;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'VD':
|
case 'VD':
|
||||||
@@ -208,7 +210,7 @@ function handleImageData(el, data, attr) {
|
|||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional processing of image data.
|
// Additional processing of attached video data.
|
||||||
function handleVideoData(el, data, attr) {
|
function handleVideoData(el, data, attr) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
attr.src = 'img/broken_video.png';
|
attr.src = 'img/broken_video.png';
|
||||||
@@ -239,7 +241,7 @@ function handleVideoData(el, data, attr) {
|
|||||||
}
|
}
|
||||||
el = InlineVideo;
|
el = InlineVideo;
|
||||||
} else {
|
} else {
|
||||||
// Use custom element instead of <img>.
|
// Use custom element instead of <img> or <video>.
|
||||||
el = UploadingImage;
|
el = UploadingImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export default class HashNavigation {
|
|||||||
// Parse hash as in http://www.example.com/path#hash as if it were
|
// Parse hash as in http://www.example.com/path#hash as if it were
|
||||||
// path and arguments.
|
// path and arguments.
|
||||||
static parseUrlHash(hash) {
|
static parseUrlHash(hash) {
|
||||||
// Split path from args, path -> parts[0], args->path[1]
|
// Split 'path/?args' into 'path/' and 'args', path -> parts[0], args->path[1].
|
||||||
const parts = hash.split('?', 2);
|
const parts = hash.split('?', 2);
|
||||||
const params = {};
|
const params = {};
|
||||||
let path = [];
|
let path = [];
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
// This is a generated file. Don't edit.
|
// This is a generated file. Don't edit.
|
||||||
|
|
||||||
export const PACKAGE_VERSION = "0.23.0-rc1";
|
export const PACKAGE_VERSION = "0.23.0-rc2";
|
||||||
|
|||||||
+57
-22
@@ -19,6 +19,7 @@ import LogoView from './logo-view.jsx';
|
|||||||
import MetaMessage from '../widgets/meta-message.jsx';
|
import MetaMessage from '../widgets/meta-message.jsx';
|
||||||
import PinnedMessages from '../widgets/pinned-messages.jsx';
|
import PinnedMessages from '../widgets/pinned-messages.jsx';
|
||||||
import SendMessage from '../widgets/send-message.jsx';
|
import SendMessage from '../widgets/send-message.jsx';
|
||||||
|
import VCPanel from '../widgets/vc-panel.jsx';
|
||||||
import VideoPreview from '../widgets/video-preview.jsx';
|
import VideoPreview from '../widgets/video-preview.jsx';
|
||||||
|
|
||||||
import { DEFAULT_P2P_ACCESS_MODE, EDIT_PREVIEW_LENGTH, IMAGE_PREVIEW_DIM, IMMEDIATE_P2P_SUBSCRIPTION,
|
import { DEFAULT_P2P_ACCESS_MODE, EDIT_PREVIEW_LENGTH, IMAGE_PREVIEW_DIM, IMMEDIATE_P2P_SUBSCRIPTION,
|
||||||
@@ -185,16 +186,23 @@ class MessagesView extends React.Component {
|
|||||||
|
|
||||||
getVisibleMessageRange(holderRect) {
|
getVisibleMessageRange(holderRect) {
|
||||||
let min = Number.MAX_SAFE_INTEGER, max = -1;
|
let min = Number.MAX_SAFE_INTEGER, max = -1;
|
||||||
this.chatMessageRefs.forEach((ref, seq) => {
|
let visibilityStatus = false;
|
||||||
|
this.chatMessageRefs.every((ref, seq) => {
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
const { top, bottom, height } = ref.current.getBoundingClientRect();
|
const { top, bottom, height } = ref.current.getBoundingClientRect();
|
||||||
const visible = top <= holderRect.top ? holderRect.top - top <= height : bottom - holderRect.bottom <= height;
|
const visible = top <= holderRect.top ? holderRect.top - top <= height : bottom - holderRect.bottom <= height;
|
||||||
if (visible) {
|
if (visible) {
|
||||||
|
visibilityStatus = true;
|
||||||
min = Math.min(min, seq);
|
min = Math.min(min, seq);
|
||||||
max = Math.max(max, seq);
|
max = Math.max(max, seq);
|
||||||
|
} else if (visibilityStatus) {
|
||||||
|
// The remaining elements are no longer visible, no need to iterate them.
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
return max >= min ? {min: min, max: max} : {min: 0, max: 0};
|
return max >= min ? {min: min, max: max} : {min: 0, max: 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +328,8 @@ class MessagesView extends React.Component {
|
|||||||
showGoToLastButton: false,
|
showGoToLastButton: false,
|
||||||
dragging: false,
|
dragging: false,
|
||||||
pins: [],
|
pins: [],
|
||||||
selectedPin: 0
|
selectedPin: 0,
|
||||||
|
subsVersion: 0
|
||||||
};
|
};
|
||||||
} else if (nextProps.topic != prevState.topic) {
|
} else if (nextProps.topic != prevState.topic) {
|
||||||
const topic = nextProps.tinode.getTopic(nextProps.topic);
|
const topic = nextProps.tinode.getTopic(nextProps.topic);
|
||||||
@@ -577,13 +586,14 @@ class MessagesView extends React.Component {
|
|||||||
showGoToLastButton: (pos > SHOW_GO_TO_LAST_DIST) && (pos < this.state.scrollPosition),
|
showGoToLastButton: (pos > SHOW_GO_TO_LAST_DIST) && (pos < this.state.scrollPosition),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.state.fetchingMessages) {
|
if (this.state.fetchingMessages || this.processingScrollEvent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.target.scrollTop <= FETCH_PAGE_TRIGGER) {
|
if (event.target.scrollTop <= FETCH_PAGE_TRIGGER) {
|
||||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||||
if (topic && topic.isSubscribed()) {
|
if (topic && topic.isSubscribed()) {
|
||||||
|
this.processingScrollEvent = true;
|
||||||
const {min, max} = this.getVisibleMessageRange(event.target.getBoundingClientRect());
|
const {min, max} = this.getVisibleMessageRange(event.target.getBoundingClientRect());
|
||||||
const gaps = topic.msgHasMoreMessages(min, max, false);
|
const gaps = topic.msgHasMoreMessages(min, max, false);
|
||||||
if (gaps.length > 0) {
|
if (gaps.length > 0) {
|
||||||
@@ -595,6 +605,7 @@ class MessagesView extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.processingScrollEvent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mount drag and drop events */
|
/* Mount drag and drop events */
|
||||||
@@ -706,12 +717,12 @@ class MessagesView extends React.Component {
|
|||||||
if (this.state.topic) {
|
if (this.state.topic) {
|
||||||
const subs = [];
|
const subs = [];
|
||||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||||
topic.subscribers((sub) => {
|
topic.subscribers(sub => {
|
||||||
if (sub.online && sub.user != this.props.myUserId) {
|
if (sub.online && sub.user != this.props.myUserId) {
|
||||||
subs.push(sub);
|
subs.push(sub);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const newState = {onlineSubs: subs};
|
const newState = {onlineSubs: subs, subsVersion: this.state.subsVersion + 1};
|
||||||
const peer = topic.p2pPeerDesc();
|
const peer = topic.p2pPeerDesc();
|
||||||
if (peer) {
|
if (peer) {
|
||||||
Object.assign(newState, {
|
Object.assign(newState, {
|
||||||
@@ -1442,23 +1453,45 @@ class MessagesView extends React.Component {
|
|||||||
onSendMessage={this.sendFileAttachment} />
|
onSendMessage={this.sendFileAttachment} />
|
||||||
);
|
);
|
||||||
} else if (this.state.rtcPanel) {
|
} else if (this.state.rtcPanel) {
|
||||||
component2 = (
|
// P2P call.
|
||||||
<CallPanel
|
if (Tinode.isP2PTopicName(this.state.rtcPanel)) {
|
||||||
topic={this.state.topic}
|
component2 = (
|
||||||
seq={this.props.callSeq}
|
<CallPanel
|
||||||
callState={this.props.callState}
|
topic={this.state.topic}
|
||||||
callAudioOnly={this.props.callAudioOnly}
|
seq={this.props.callSeq}
|
||||||
tinode={this.props.tinode}
|
callState={this.props.callState}
|
||||||
title={this.state.title}
|
callAudioOnly={this.props.callAudioOnly}
|
||||||
avatar={this.state.avatar || true}
|
tinode={this.props.tinode}
|
||||||
|
title={this.state.title}
|
||||||
|
avatar={this.state.avatar || true}
|
||||||
|
|
||||||
onError={this.props.onError}
|
onError={this.props.onError}
|
||||||
onHangup={this.handleCallHangup}
|
onHangup={this.handleCallHangup}
|
||||||
onInvite={this.props.onCallInvite}
|
onInvite={this.props.onCallInvite}
|
||||||
onSendOffer={this.props.onCallSendOffer}
|
onSendOffer={this.props.onCallSendOffer}
|
||||||
onIceCandidate={this.props.onCallIceCandidate}
|
onIceCandidate={this.props.onCallIceCandidate}
|
||||||
onSendAnswer={this.props.onCallSendAnswer} />
|
onSendAnswer={this.props.onCallSendAnswer} />
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
// Video conference or stream.
|
||||||
|
component2 = (
|
||||||
|
<VCPanel
|
||||||
|
topic={this.state.topic}
|
||||||
|
seq={this.props.callSeq}
|
||||||
|
callState={this.props.callState}
|
||||||
|
callAudioOnly={this.props.callAudioOnly}
|
||||||
|
tinode={this.props.tinode}
|
||||||
|
title={this.state.title}
|
||||||
|
avatar={this.state.avatar || true}
|
||||||
|
myUid={this.props.myUserId}
|
||||||
|
viewportWidth={this.props.viewportWidth}
|
||||||
|
|
||||||
|
onError={this.props.onError}
|
||||||
|
onHangup={this.handleCallHangup}
|
||||||
|
onCreateCall={this.props.onCallInvite}
|
||||||
|
onJoin={this.props.onVCJoin} />
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||||
const isChannel = topic.isChannelType();
|
const isChannel = topic.isChannelType();
|
||||||
@@ -1539,6 +1572,7 @@ class MessagesView extends React.Component {
|
|||||||
messageNodes.push(
|
messageNodes.push(
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
tinode={this.props.tinode}
|
tinode={this.props.tinode}
|
||||||
|
topic={this.state.topic}
|
||||||
content={msg.content}
|
content={msg.content}
|
||||||
mimeType={msg.head && msg.head.mime}
|
mimeType={msg.head && msg.head.mime}
|
||||||
replyToSeq={replyToSeq}
|
replyToSeq={replyToSeq}
|
||||||
@@ -1565,6 +1599,7 @@ class MessagesView extends React.Component {
|
|||||||
pickReply={this.handlePickReply}
|
pickReply={this.handlePickReply}
|
||||||
editMessage={this.handleEditMessage}
|
editMessage={this.handleEditMessage}
|
||||||
onQuoteClick={this.handleQuoteClick}
|
onQuoteClick={this.handleQuoteClick}
|
||||||
|
onAcceptCall={this.props.onAcceptCall}
|
||||||
onError={this.props.onError}
|
onError={this.props.onError}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
key={msg.seq} />
|
key={msg.seq} />
|
||||||
@@ -1596,7 +1631,7 @@ class MessagesView extends React.Component {
|
|||||||
let messagesComponent = (
|
let messagesComponent = (
|
||||||
<>
|
<>
|
||||||
<div id="messages-container">
|
<div id="messages-container">
|
||||||
<button className={'action-button' + (this.state.showGoToLastButton ? '' : ' hidden')}
|
<button id="go-to-latest" className={'action-button' + (this.state.showGoToLastButton ? '' : ' hidden')}
|
||||||
onClick={this.goToLatestMessage}>
|
onClick={this.goToLatestMessage}>
|
||||||
<i className="material-icons">arrow_downward</i>
|
<i className="material-icons">arrow_downward</i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -48,7 +48,23 @@ class PasswordResetView extends React.PureComponent {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const parsed = HashNavigation.parseUrlHash(window.location.hash);
|
const parsed = HashNavigation.parseUrlHash(window.location.hash);
|
||||||
this.setState({token: parsed.params.token, scheme: parsed.params.scheme});
|
const newState = {
|
||||||
|
token: parsed.params.token,
|
||||||
|
scheme: parsed.params.scheme,
|
||||||
|
code: parsed.params.code || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
this.savedCred = parsed.params.cred;
|
||||||
|
if (this.props.reqCredMethod && parsed.params.cred) {
|
||||||
|
newState[this.props.reqCredMethod] = this.savedCred;
|
||||||
|
}
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
if (prevProps.reqCredMethod != this.props.reqCredMethod && this.props.reqCredMethod) {
|
||||||
|
this.setState({[this.props.reqCredMethod]: this.savedCred || ''});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit(e) {
|
handleSubmit(e) {
|
||||||
@@ -62,7 +78,7 @@ class PasswordResetView extends React.PureComponent {
|
|||||||
} else if (this.state.code && this.props.reqCredMethod) {
|
} else if (this.state.code && this.props.reqCredMethod) {
|
||||||
// Reset using security code.
|
// Reset using security code.
|
||||||
// The secret is structured as <code>:<cred_method>:<cred_value>, "123456:email:alice@example.com".
|
// The secret is structured as <code>:<cred_method>:<cred_value>, "123456:email:alice@example.com".
|
||||||
const cred = this.state.email.trim() || this.state.tel.trim();
|
const cred = this.props.reqCredMethod == 'email' ? this.state.email.trim() : this.state.tel.trim();
|
||||||
this.props.onReset(this.state.password.trim(), {
|
this.props.onReset(this.state.password.trim(), {
|
||||||
scheme: 'code',
|
scheme: 'code',
|
||||||
secret: btoa(`${this.state.code}:${this.props.reqCredMethod}:${cred}`)
|
secret: btoa(`${this.state.code}:${this.props.reqCredMethod}:${cred}`)
|
||||||
@@ -102,7 +118,7 @@ class PasswordResetView extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const showCredentialInput = !(this.state.token && this.state.scheme);
|
const showCredentialInput = !((this.state.token || (this.state.code && this.props.reqCredMethod)) && this.state.scheme);
|
||||||
const showPasswordInput = !showCredentialInput || this.state.haveCode || this.state.sent;
|
const showPasswordInput = !showCredentialInput || this.state.haveCode || this.state.sent;
|
||||||
|
|
||||||
const passwordInput = (<>
|
const passwordInput = (<>
|
||||||
@@ -193,7 +209,7 @@ class PasswordResetView extends React.PureComponent {
|
|||||||
{this.state.haveCode || this.state.sent ? codeInput : null}
|
{this.state.haveCode || this.state.sent ? codeInput : null}
|
||||||
{showPasswordInput ? passwordInput : null}
|
{showPasswordInput ? passwordInput : null}
|
||||||
<div className="dialog-buttons">
|
<div className="dialog-buttons">
|
||||||
{this.state.haveCode || this.state.sent || this.state.token ? null :
|
{this.state.haveCode || this.state.sent || this.state.token || this.state.code ? null :
|
||||||
<a href="#" onClick={this.handleShowCodeField} style={{marginRight: 'auto'}}>
|
<a href="#" onClick={this.handleShowCodeField} style={{marginRight: 'auto'}}>
|
||||||
<FormattedMessage id="password_i_have_code" defaultMessage="I have code"
|
<FormattedMessage id="password_i_have_code" defaultMessage="I have code"
|
||||||
description="Call to open field to enter password reset code" />
|
description="Call to open field to enter password reset code" />
|
||||||
|
|||||||
+46
-13
@@ -83,7 +83,12 @@ const messages = defineMessages({
|
|||||||
cred_confirmed_successfully: {
|
cred_confirmed_successfully: {
|
||||||
id: 'cred_confirmed_successfully',
|
id: 'cred_confirmed_successfully',
|
||||||
defaultMessage: 'Confirmed successfully',
|
defaultMessage: 'Confirmed successfully',
|
||||||
description: 'Message explaining that the credential was successfully validated.'
|
description: 'Notification message that the credential was successfully validated.'
|
||||||
|
},
|
||||||
|
password_reset_success: {
|
||||||
|
id: 'password_reset_success',
|
||||||
|
defaultMessage: 'Password reset successfully',
|
||||||
|
description: 'Notification message that the password was successfully reset.'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -176,6 +181,7 @@ class TinodeWeb extends React.Component {
|
|||||||
this.handleCallSendOffer = this.handleCallSendOffer.bind(this);
|
this.handleCallSendOffer = this.handleCallSendOffer.bind(this);
|
||||||
this.handleCallIceCandidate = this.handleCallIceCandidate.bind(this);
|
this.handleCallIceCandidate = this.handleCallIceCandidate.bind(this);
|
||||||
this.handleCallSendAnswer = this.handleCallSendAnswer.bind(this);
|
this.handleCallSendAnswer = this.handleCallSendAnswer.bind(this);
|
||||||
|
this.handleVCCallJoin = this.handleVCCallJoin.bind(this);
|
||||||
|
|
||||||
this.handleCallAccept = this.handleCallAccept.bind(this);
|
this.handleCallAccept = this.handleCallAccept.bind(this);
|
||||||
|
|
||||||
@@ -1593,7 +1599,7 @@ class TinodeWeb extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let muted = false, blocked = false, self_blocked = false, subscribed = false, deleter = false,
|
let muted = false, blocked = false, self_blocked = false, subscribed = false, deleter = false,
|
||||||
archived = false, webrtc = false;
|
archived = false, webrtc = false, writer = false;
|
||||||
if (topic) {
|
if (topic) {
|
||||||
subscribed = topic.isSubscribed();
|
subscribed = topic.isSubscribed();
|
||||||
archived = topic.isArchived();
|
archived = topic.isArchived();
|
||||||
@@ -1604,10 +1610,12 @@ class TinodeWeb extends React.Component {
|
|||||||
blocked = !acs.isJoiner();
|
blocked = !acs.isJoiner();
|
||||||
self_blocked = !acs.isJoiner('want');
|
self_blocked = !acs.isJoiner('want');
|
||||||
deleter = acs.isDeleter();
|
deleter = acs.isDeleter();
|
||||||
|
writer = acs.isWriter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
webrtc = !!this.tinode.getServerParam('iceServers');
|
webrtc = writer && !!this.tinode.getServerParam('iceServers');
|
||||||
|
const mediaServerAvailable = !!this.tinode.getServerParam('vcEndpoint');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
subscribed ? {
|
subscribed ? {
|
||||||
@@ -1618,8 +1626,9 @@ class TinodeWeb extends React.Component {
|
|||||||
title: this.props.intl.formatMessage(messages.menu_item_audio_call),
|
title: this.props.intl.formatMessage(messages.menu_item_audio_call),
|
||||||
handler: this.handleStartAudioCall
|
handler: this.handleStartAudioCall
|
||||||
} : null,
|
} : null,
|
||||||
subscribed && Tinode.isP2PTopicName(topicName) && webrtc ? {
|
subscribed && webrtc ? {
|
||||||
title: this.props.intl.formatMessage(messages.menu_item_video_call),
|
title: this.props.intl.formatMessage(messages.menu_item_video_call),
|
||||||
|
disabled: Tinode.isGroupTopicName(topicName) && !mediaServerAvailable,
|
||||||
handler: this.handleStartVideoCall
|
handler: this.handleStartVideoCall
|
||||||
} : null,
|
} : null,
|
||||||
subscribed ? 'messages_clear' : null,
|
subscribed ? 'messages_clear' : null,
|
||||||
@@ -1733,9 +1742,11 @@ class TinodeWeb extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
this.tinode.connect()
|
this.tinode.connect()
|
||||||
.then(_ => this.tinode.updateAccountBasic(null, null, newPassword, {scheme: tempAuth.scheme, secret: secret}))
|
.then(_ => this.tinode.updateAccountBasic(null, null, newPassword, {scheme: tempAuth.scheme, secret: secret}))
|
||||||
.then(_ => HashNavigation.navigateTo(''))
|
.then(_ => {
|
||||||
|
this.handleError(this.props.intl.formatMessage(messages.password_reset_success), 'info');
|
||||||
|
HashNavigation.navigateTo('');
|
||||||
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
// Socket error
|
|
||||||
this.handleError(err.message, 'err');
|
this.handleError(err.message, 'err');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1775,13 +1786,19 @@ class TinodeWeb extends React.Component {
|
|||||||
switch (callState) {
|
switch (callState) {
|
||||||
case CALL_STATE_OUTGOING_INITATED:
|
case CALL_STATE_OUTGOING_INITATED:
|
||||||
const head = { webrtc: CALL_HEAD_STARTED, aonly: !!audioOnly };
|
const head = { webrtc: CALL_HEAD_STARTED, aonly: !!audioOnly };
|
||||||
this.handleSendMessage(Drafty.videoCall(audioOnly), undefined, undefined, head)
|
const msg = Drafty.videoCall(audioOnly);
|
||||||
|
if (Tinode.isGroupTopicName(callTopic)) {
|
||||||
|
// Video conference.
|
||||||
|
head.vc = true;
|
||||||
|
}
|
||||||
|
return this.handleSendMessage(msg, undefined, undefined, head)
|
||||||
.then(ctrl => {
|
.then(ctrl => {
|
||||||
if (ctrl.code < 200 || ctrl.code >= 300 || !ctrl.params || !ctrl.params.seq) {
|
if (ctrl.code < 200 || ctrl.code >= 300 || !ctrl.params || !ctrl.params.seq) {
|
||||||
this.handleCallClose();
|
this.handleCallClose();
|
||||||
return;
|
return ctrl;
|
||||||
}
|
}
|
||||||
this.setState({callSeq: ctrl.params['seq']});
|
this.setState({callSeq: ctrl.params['seq']});
|
||||||
|
return ctrl
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case CALL_STATE_IN_PROGRESS:
|
case CALL_STATE_IN_PROGRESS:
|
||||||
@@ -1839,6 +1856,15 @@ class TinodeWeb extends React.Component {
|
|||||||
topic.videoCall('answer', callSeq, sdp);
|
topic.videoCall('answer', callSeq, sdp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleVCCallJoin(callTopic, callSeq) {
|
||||||
|
const topic = this.tinode.getTopic(callTopic);
|
||||||
|
if (!topic) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
topic.videoCall('vc-join', callSeq);
|
||||||
|
}
|
||||||
|
|
||||||
handleCallClose() {
|
handleCallClose() {
|
||||||
if (this.callTimeoutTimer) {
|
if (this.callTimeoutTimer) {
|
||||||
clearTimeout(this.callTimeoutTimer);
|
clearTimeout(this.callTimeoutTimer);
|
||||||
@@ -1850,16 +1876,21 @@ class TinodeWeb extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCallAccept(topicName) {
|
handleCallAccept(topicName, setCallTopic, callSeq) {
|
||||||
const topic = this.tinode.getTopic(topicName);
|
const topic = this.tinode.getTopic(topicName);
|
||||||
if (!topic) {
|
if (!topic) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (topic.isSubscribed()) {
|
if (topic.isSubscribed()) {
|
||||||
this.handleTopicSelected(this.state.callTopic);
|
this.handleTopicSelected(setCallTopic ? topicName : this.state.callTopic);
|
||||||
this.setState({
|
const upd = {
|
||||||
callState: CALL_STATE_IN_PROGRESS
|
callState: CALL_STATE_IN_PROGRESS
|
||||||
});
|
}
|
||||||
|
if (setCallTopic) {
|
||||||
|
upd.callTopic = topicName;
|
||||||
|
upd.callSeq = callSeq;
|
||||||
|
}
|
||||||
|
this.setState(upd);
|
||||||
} else {
|
} else {
|
||||||
// We need to switch and subscribe to callTopic first.
|
// We need to switch and subscribe to callTopic first.
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -1897,7 +1928,7 @@ class TinodeWeb extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleDataMessage(data) {
|
handleDataMessage(data) {
|
||||||
if (data.head && data.head.webrtc && data.head.webrtc == CALL_HEAD_STARTED) {
|
if (data.head && data.head.webrtc && data.head.webrtc == CALL_HEAD_STARTED && Tinode.isP2PTopicName(data.topic)) {
|
||||||
// If it's a video call invite message.
|
// If it's a video call invite message.
|
||||||
// See if we need to display incoming call UI.
|
// See if we need to display incoming call UI.
|
||||||
const topic = this.tinode.getTopic(data.topic);
|
const topic = this.tinode.getTopic(data.topic);
|
||||||
@@ -2092,11 +2123,13 @@ class TinodeWeb extends React.Component {
|
|||||||
callState={this.state.callState}
|
callState={this.state.callState}
|
||||||
callAudioOnly={this.state.callAudioOnly}
|
callAudioOnly={this.state.callAudioOnly}
|
||||||
onCallHangup={this.handleCallHangup}
|
onCallHangup={this.handleCallHangup}
|
||||||
|
onAcceptCall={this.handleCallAccept}
|
||||||
|
|
||||||
onCallInvite={this.handleCallInvite}
|
onCallInvite={this.handleCallInvite}
|
||||||
onCallSendOffer={this.handleCallSendOffer}
|
onCallSendOffer={this.handleCallSendOffer}
|
||||||
onCallIceCandidate={this.handleCallIceCandidate}
|
onCallIceCandidate={this.handleCallIceCandidate}
|
||||||
onCallSendAnswer={this.handleCallSendAnswer}
|
onCallSendAnswer={this.handleCallSendAnswer}
|
||||||
|
onVCJoin={this.handleVCCallJoin}
|
||||||
|
|
||||||
errorText={this.state.errorText}
|
errorText={this.state.errorText}
|
||||||
errorLevel={this.state.errorLevel}
|
errorLevel={this.state.errorLevel}
|
||||||
|
|||||||
@@ -7,11 +7,23 @@ import { secondsToTime } from '../lib/strformat.js';
|
|||||||
|
|
||||||
export default class CallMessage extends React.PureComponent {
|
export default class CallMessage extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
|
const bigIcon = this.props.vc ?
|
||||||
|
<div className="composed-material">
|
||||||
|
<i className="material-icons gray second-small">group</i>
|
||||||
|
<i className="material-icons gray big outline">call</i>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<div><i className="material-icons big gray">call</i></div>;
|
||||||
const isCallDropped = ['busy', 'declined', 'disconnected', 'missed'].includes(this.props.callState);
|
const isCallDropped = ['busy', 'declined', 'disconnected', 'missed'].includes(this.props.callState);
|
||||||
const successClass = 'material-icons medium ' + (isCallDropped ? 'red' : 'green');
|
const successClass = 'material-icons medium ' + (isCallDropped ? 'red' : 'green');
|
||||||
const callIcon = this.props.incoming ?
|
const callIcon = this.props.incoming ?
|
||||||
(isCallDropped ? 'call_missed' : 'call_received') :
|
(isCallDropped ? 'call_missed' : 'call_received') :
|
||||||
(isCallDropped ? 'call_missed_outgoing' : 'call_made');
|
(isCallDropped ? 'call_missed_outgoing' : 'call_made');
|
||||||
|
const title = this.props.vc ?
|
||||||
|
<FormattedMessage id="calls_conference" defaultMessage="Conference call" description="Conference call label" /> :
|
||||||
|
this.props.incoming ?
|
||||||
|
<FormattedMessage id="calls_incoming" defaultMessage="Incoming call" description="Incoming call label" /> :
|
||||||
|
<FormattedMessage id="calls_outgoing" defaultMessage="Outgoing call" description="Outgoing call label" />;
|
||||||
let duration;
|
let duration;
|
||||||
if (isCallDropped) {
|
if (isCallDropped) {
|
||||||
switch (this.props.callState) {
|
switch (this.props.callState) {
|
||||||
@@ -33,14 +45,17 @@ export default class CallMessage extends React.PureComponent {
|
|||||||
} else {
|
} else {
|
||||||
duration = <span>{secondsToTime(this.props.duration / 1000)}</span>;
|
duration = <span>{secondsToTime(this.props.duration / 1000)}</span>;
|
||||||
}
|
}
|
||||||
|
const allowJoin = this.props.vc && this.props.callState == 'started';
|
||||||
return <div className="call-message">
|
return <div className="call-message">
|
||||||
<div><i className="material-icons big gray">call</i></div>
|
<div><i className="material-icons big gray">{bigIcon}</i></div>
|
||||||
<div className="flex-column narrow">
|
<div className="flex-column narrow">
|
||||||
<div>{this.props.incoming ?
|
<div>{title}</div>
|
||||||
<FormattedMessage id="calls_incoming" defaultMessage="Incoming call" description="Incoming call label" /> :
|
|
||||||
<FormattedMessage id="calls_outgoing" defaultMessage="Outgoing call" description="Outgoing call label" />
|
|
||||||
}</div>
|
|
||||||
<div className="duration"><i className={successClass}>{callIcon}</i> {duration}</div>
|
<div className="duration"><i className={successClass}>{callIcon}</i> {duration}</div>
|
||||||
|
{allowJoin ?
|
||||||
|
<a onClick={this.props.onCallJoin}>
|
||||||
|
<FormattedMessage id="join_conference" defaultMessage="+ Join" description="Call to action to join a conference call" />
|
||||||
|
</a>
|
||||||
|
: null}
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import { secondsToTime } from '../lib/strformat.js';
|
|||||||
|
|
||||||
// Props:
|
// Props:
|
||||||
// incoming: true | false.
|
// incoming: true | false.
|
||||||
// success: true | false.
|
// callState: 'busy' | 'declined' | 'disconnected' | 'missed'.
|
||||||
|
// duration: <number> (call duration in milliseconds).
|
||||||
export default class CallStatus extends React.PureComponent {
|
export default class CallStatus extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const isCallDropped = ['busy', 'declined', 'disconnected', 'missed'].includes(this.props.callState);
|
const isCallDropped = ['busy', 'declined', 'disconnected', 'missed'].includes(this.props.callState);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
this.handleContextClick = this.handleContextClick.bind(this);
|
this.handleContextClick = this.handleContextClick.bind(this);
|
||||||
this.handleCancelUpload = this.handleCancelUpload.bind(this);
|
this.handleCancelUpload = this.handleCancelUpload.bind(this);
|
||||||
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
||||||
|
this.handleCallJoin = this.handleCallJoin.bind(this);
|
||||||
|
|
||||||
this.formatterContext = {
|
this.formatterContext = {
|
||||||
formatMessage: props.intl.formatMessage.bind(props.intl),
|
formatMessage: props.intl.formatMessage.bind(props.intl),
|
||||||
@@ -38,7 +39,8 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
onImagePreview: this.handleExpandImage,
|
onImagePreview: this.handleExpandImage,
|
||||||
onVideoPreview: this.handlePlayVideo,
|
onVideoPreview: this.handlePlayVideo,
|
||||||
onFormButtonClick: this.handleFormButtonClick,
|
onFormButtonClick: this.handleFormButtonClick,
|
||||||
onQuoteClick: this.handleQuoteClick
|
onQuoteClick: this.handleQuoteClick,
|
||||||
|
onCallJoin: this.handleCallJoin
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +151,10 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleCallJoin() {
|
||||||
|
this.props.onAcceptCall(this.props.topic, true, this.props.seq);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const sideClass = this.props.sequence + ' ' + (this.props.response ? 'left' : 'right');
|
const sideClass = this.props.sequence + ' ' + (this.props.response ? 'left' : 'right');
|
||||||
const bubbleClass = (this.props.sequence == 'single' || this.props.sequence == 'last') ? 'bubble tip' : 'bubble';
|
const bubbleClass = (this.props.sequence == 'single' || this.props.sequence == 'last') ? 'bubble tip' : 'bubble';
|
||||||
|
|||||||
@@ -493,11 +493,12 @@ class ContextMenu extends React.Component {
|
|||||||
item = this.MenuItems[item];
|
item = this.MenuItems[item];
|
||||||
}
|
}
|
||||||
if (item && item.title) {
|
if (item && item.title) {
|
||||||
|
const className = item.disabled ? 'disabled' : undefined;
|
||||||
menu.push(
|
menu.push(
|
||||||
item.title == '-' ?
|
item.title == '-' ?
|
||||||
<li className="separator" key={count} />
|
<li className="separator" key={count} />
|
||||||
:
|
:
|
||||||
<li onClick={this.handleClick} data-id={count} key={count}>{item.title}</li>
|
<li className={className} onClick={this.handleClick} data-id={count} key={count}>{item.title}</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
count++;
|
count++;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default class InlineVideo extends React.PureComponent {
|
|||||||
{React.createElement('img', this.props)}
|
{React.createElement('img', this.props)}
|
||||||
<div className="play-control">
|
<div className="play-control">
|
||||||
{this.props.onClick ?
|
{this.props.onClick ?
|
||||||
<i className="material-icons white bigger">play_arrow</i>
|
<i className="material-icons white x-big">play_arrow</i>
|
||||||
:
|
:
|
||||||
<img src="img/broken_video.png" style={{filter: 'invert(100%)'}} width="36" height="36" />}
|
<img src="img/broken_video.png" style={{filter: 'invert(100%)'}} width="36" height="36" />}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
// Video conference carousel item.
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import LetterTile from './letter-tile.jsx';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props:
|
||||||
|
* - cameraPub
|
||||||
|
* - micPub
|
||||||
|
* - isSpeaking
|
||||||
|
* - tinode
|
||||||
|
* - photo
|
||||||
|
* - userId
|
||||||
|
* - name
|
||||||
|
*/
|
||||||
|
export default class VCCarouselItem extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleVideoRefChange = this.handleVideoRefChange.bind(this);
|
||||||
|
this.handleAudioRefChange = this.handleAudioRefChange.bind(this);
|
||||||
|
|
||||||
|
this.videoRef = null;
|
||||||
|
this.audioRef = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.videoRef && !this.videoRef.srcObject && this.props.cameraPub && this.props.cameraPub.videoTrack) {
|
||||||
|
this.props.cameraPub.videoTrack.attach(this.videoRef);
|
||||||
|
}
|
||||||
|
if (this.audioRef && !this.audioRef.srcObject && this.props.micPub && this.props.micPub.audioTrack) {
|
||||||
|
this.props.micPub.audioTrack.attach(this.audioRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleVideoRefChange(node) {
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.videoRef = node;
|
||||||
|
|
||||||
|
// Attach video track to video element.
|
||||||
|
const cameraPub = this.props.cameraPub;
|
||||||
|
const cameraEnabled = cameraPub && cameraPub.isSubscribed && !cameraPub.isMuted;
|
||||||
|
if (cameraEnabled && cameraPub.videoTrack) {
|
||||||
|
cameraPub.videoTrack.attach(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAudioRefChange(node) {
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.audioRef = node;
|
||||||
|
|
||||||
|
// Attach audio track to audio element.
|
||||||
|
const micPub = this.props.micPub;
|
||||||
|
const micEnabled = micPub && micPub.isSubscribed && !micPub.isMuted;
|
||||||
|
if (micEnabled && micPub.audioTrack) {
|
||||||
|
micPub.audioTrack.attach(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let classes = 'call-party carousel-item';
|
||||||
|
if (this.props.isSpeaking) {
|
||||||
|
classes += ' call-party-speaking';
|
||||||
|
}
|
||||||
|
|
||||||
|
const micPub = this.props.micPub;
|
||||||
|
const cameraPub = this.props.cameraPub;
|
||||||
|
return (
|
||||||
|
<div className={classes}>
|
||||||
|
<video ref={this.handleVideoRefChange}></video>
|
||||||
|
<audio ref={this.handleAudioRefChange}></audio>
|
||||||
|
{micPub && micPub.isMuted ?
|
||||||
|
<i className="material-icons muted">mic_off</i>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
{cameraPub && cameraPub.isMuted ?
|
||||||
|
<div className="avatar-box">
|
||||||
|
<LetterTile
|
||||||
|
tinode={this.props.tinode}
|
||||||
|
avatar={this.props.photo || true}
|
||||||
|
topic={this.props.userId}
|
||||||
|
title={this.props.name} />
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
|
<div className="caller-name inactive">
|
||||||
|
{this.props.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Track } from 'livekit-client';
|
||||||
|
|
||||||
|
import VCCarouselItem from './vc-carousel-item.jsx';
|
||||||
|
|
||||||
|
import { VC_CAROUSEL_ITEM_GAP } from '../constants.js';
|
||||||
|
|
||||||
|
export default class VCCarousel extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
prevVisible: false,
|
||||||
|
nextVisible: false
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleScrollReference = this.handleScrollReference.bind(this);
|
||||||
|
this.handleScrollEvent = this.handleScrollEvent.bind(this);
|
||||||
|
this.handleScrollClick = this.handleScrollClick.bind(this);
|
||||||
|
|
||||||
|
this.carouselRef = null;
|
||||||
|
this.contentRef = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.carouselRef && this.props.viewportWidth != prevProps.viewportWidth) {
|
||||||
|
this.handleScrollEvent({target: this.carouselRef});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.carouselRef) {
|
||||||
|
this.carouselRef.removeEventListener('scroll', this.handleScrollEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't use React.createRef as the ref.current is not available in componentDidMount in this component.
|
||||||
|
handleScrollReference(node) {
|
||||||
|
if (node) {
|
||||||
|
this.carouselRef = node;
|
||||||
|
this.carouselRef.addEventListener('scroll', this.handleScrollEvent);
|
||||||
|
this.handleScrollEvent({target: this.carouselRef});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get older messages and show/hide [go to latest message] button.
|
||||||
|
handleScrollEvent(event) {
|
||||||
|
this.setState({
|
||||||
|
// Show [prev] / [next] as appropriate.
|
||||||
|
prevVisible: event.target.scrollLeft > VC_CAROUSEL_ITEM_GAP,
|
||||||
|
nextVisible: event.target.scrollWidth - event.target.scrollLeft - event.target.offsetWidth > VC_CAROUSEL_ITEM_GAP
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScrollClick(next) {
|
||||||
|
// this.carouselRef.offsetWidth - is the width of the visible window.
|
||||||
|
const val = this.carouselRef.offsetWidth + VC_CAROUSEL_ITEM_GAP;
|
||||||
|
this.carouselRef.scrollBy(next ? val : -val, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let peers = [];
|
||||||
|
if (this.props.participants) {
|
||||||
|
Object.entries(this.props.participants).forEach(([identity, party], i) => {
|
||||||
|
const participant = party.participant;
|
||||||
|
const cameraPub = participant.getTrack(Track.Source.Camera);
|
||||||
|
const micPub = participant.getTrack(Track.Source.Microphone);
|
||||||
|
|
||||||
|
peers.push(<VCCarouselItem
|
||||||
|
tinode={this.props.tinode}
|
||||||
|
isSpeaking={participant.isSpeaking}
|
||||||
|
cameraPub={cameraPub ? cameraPub : null}
|
||||||
|
micPub={micPub ? micPub : null}
|
||||||
|
name={party.name}
|
||||||
|
userId={identity}
|
||||||
|
photo={party.photo}
|
||||||
|
key={i}
|
||||||
|
/>);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div id="carousel-wrapper">
|
||||||
|
<div id="carousel" ref={this.handleScrollReference}>
|
||||||
|
<div id="carousel-content" ref={this.contentRef}>
|
||||||
|
{peers}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{this.state.prevVisible ?
|
||||||
|
<button id="carousel-prev" className="action-button" onClick={_ => this.handleScrollClick(false)}>
|
||||||
|
<i className="material-icons">navigate_before</i>
|
||||||
|
</button>
|
||||||
|
:
|
||||||
|
null}
|
||||||
|
{this.state.nextVisible ?
|
||||||
|
<button id="carousel-next" className="action-button" onClick={_ => this.handleScrollClick(true)}>
|
||||||
|
<i className="material-icons">navigate_next</i>
|
||||||
|
</button>
|
||||||
|
:
|
||||||
|
null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,412 @@
|
|||||||
|
// Video conference or broadcast: local and/or peer viewports and controls.
|
||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import {
|
||||||
|
LocalVideoTrack,
|
||||||
|
MediaDeviceFailure,
|
||||||
|
ParticipantEvent,
|
||||||
|
RemoteParticipant,
|
||||||
|
Room,
|
||||||
|
RoomEvent,
|
||||||
|
Track,
|
||||||
|
VideoPresets
|
||||||
|
} from 'livekit-client';
|
||||||
|
|
||||||
|
import LetterTile from './letter-tile.jsx';
|
||||||
|
import VCCarousel from './vc-carousel.jsx';
|
||||||
|
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||||
|
|
||||||
|
import { MAX_PEER_TITLE_LENGTH } from '../config.js';
|
||||||
|
import { CALL_STATE_OUTGOING_INITATED } from '../constants.js';
|
||||||
|
|
||||||
|
import { clipStr } from '../lib/utils.js'
|
||||||
|
|
||||||
|
class VCPanel extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
room: undefined,
|
||||||
|
|
||||||
|
participants: {},
|
||||||
|
activeSpeaker: undefined,
|
||||||
|
|
||||||
|
previousOnInfo: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isOutgoingCall = props.callState == CALL_STATE_OUTGOING_INITATED;
|
||||||
|
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||||
|
this.isBroadcast = topic && topic.chan;
|
||||||
|
|
||||||
|
this.localRef = React.createRef();
|
||||||
|
this.remoteRef = React.createRef();
|
||||||
|
|
||||||
|
this.onInfo = this.onInfo.bind(this);
|
||||||
|
this.start = this.start.bind(this);
|
||||||
|
this.stop = this.stop.bind(this);
|
||||||
|
|
||||||
|
this.reportError = this.reportError.bind(this);
|
||||||
|
|
||||||
|
this.handleCloseClick = this.handleCloseClick.bind(this);
|
||||||
|
this.handleToggleCameraClick = this.handleToggleCameraClick.bind(this);
|
||||||
|
this.handleToggleMicClick = this.handleToggleMicClick.bind(this);
|
||||||
|
|
||||||
|
this.handleRemoteHangup = this.handleRemoteHangup.bind(this);
|
||||||
|
|
||||||
|
this.handleTrackSubscribed = this.handleTrackSubscribed.bind(this);
|
||||||
|
this.handleTrackUnsubscribed = this.handleTrackUnsubscribed.bind(this);
|
||||||
|
this.handleActiveSpeakerChange = this.handleActiveSpeakerChange.bind(this);
|
||||||
|
this.handleDisconnect = this.handleDisconnect.bind(this);
|
||||||
|
this.handleLocalTrackUnpublished = this.handleLocalTrackUnpublished.bind(this);
|
||||||
|
this.handleLocalTrackPublished = this.handleLocalTrackPublished.bind(this);
|
||||||
|
this.handleParticipantConnected = this.handleParticipantConnected.bind(this);
|
||||||
|
this.handleParticipantDisconnected = this.handleParticipantDisconnected.bind(this);
|
||||||
|
this.handleData = this.handleData.bind(this);
|
||||||
|
this.handleDevicesChanged = this.handleDevicesChanged.bind(this);
|
||||||
|
this.startRoom = this.startRoom.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||||
|
this.previousOnInfo = topic.onInfo;
|
||||||
|
topic.onInfo = this.onInfo;
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||||
|
if (topic) {
|
||||||
|
topic.onInfo = this.previousOnInfo;
|
||||||
|
}
|
||||||
|
this.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
onInfo(info) {
|
||||||
|
if (info.what != 'call') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (info.event) {
|
||||||
|
case 'vc-token':
|
||||||
|
const ts = info.payload;
|
||||||
|
this.setState({token: ts.token});
|
||||||
|
this.startRoom(ts.token);
|
||||||
|
break;
|
||||||
|
case 'hang-up':
|
||||||
|
this.handleRemoteHangup(info);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn("Unknown call event", info.event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
handleParticipantConnected(participant) {
|
||||||
|
if (!participant) return;
|
||||||
|
if (participant instanceof RemoteParticipant) {
|
||||||
|
participant
|
||||||
|
.on(ParticipantEvent.TrackMuted, pub => {
|
||||||
|
console.log('track was muted', pub.trackSid, participant.identity);
|
||||||
|
this.forceUpdate(); // Redraw carousel.
|
||||||
|
})
|
||||||
|
.on(ParticipantEvent.TrackUnmuted, pub => {
|
||||||
|
console.log('track was unmuted', pub.trackSid, participant.identity);
|
||||||
|
this.forceUpdate(); // Redraw carousel.
|
||||||
|
})
|
||||||
|
.on(ParticipantEvent.IsSpeakingChanged, _ => {
|
||||||
|
this.forceUpdate(); // Redraw carousel.
|
||||||
|
})
|
||||||
|
.on(ParticipantEvent.ConnectionQualityChanged, _ => {
|
||||||
|
this.forceUpdate(); // Redraw carousel.
|
||||||
|
});
|
||||||
|
|
||||||
|
const me = this.props.tinode.getMeTopic();
|
||||||
|
const c = me.getContact(participant.identity);
|
||||||
|
|
||||||
|
const participants = Object.assign({}, this.state.participants);
|
||||||
|
participants[participant.identity] = {
|
||||||
|
name: c && c.public && c.public.fn ? c.public.fn : participant.identity,
|
||||||
|
photo: makeImageUrl(c && c.public ? c.public.photo : null),
|
||||||
|
participant: participant
|
||||||
|
};
|
||||||
|
this.setState({participants: participants});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleParticipantDisconnected(participant) {
|
||||||
|
const participants = Object.assign({}, this.state.participants);
|
||||||
|
delete participants[participant.identity];
|
||||||
|
this.setState({participants: participants});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleData(msg, participant) {
|
||||||
|
console.log('Data message ', msg, participant);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTrackSubscribed(track, publication, participant) {
|
||||||
|
if (participant instanceof RemoteParticipant) {
|
||||||
|
// Check if we're presenting a remote video stream.
|
||||||
|
if (this.remoteRef.current && !(this.remoteRef.current.src != '' || this.remoteRef.srcObject)) {
|
||||||
|
track.attach(this.remoteRef.current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Redraw the carousel and remote view.
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTrackUnsubscribed(track, publication, participant) {
|
||||||
|
console.log('handleTrackUnsubscribed', track);
|
||||||
|
// remove tracks from all attached elements
|
||||||
|
track.detach();
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLocalTrackPublished(pub) {
|
||||||
|
console.log('Local track published', pub);
|
||||||
|
const track = pub.track;
|
||||||
|
if ((track instanceof LocalVideoTrack) && this.localRef.current) {
|
||||||
|
const elem = this.localRef.current;
|
||||||
|
track.attach(elem);
|
||||||
|
// flip.
|
||||||
|
elem.style.transform = 'scale(-1, 1)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLocalTrackUnpublished(pub) {
|
||||||
|
const track = pub.track;
|
||||||
|
console.log('handleLocalTrackUnpublished', track);
|
||||||
|
track.detach();
|
||||||
|
// Redraw carousel.
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleActiveSpeakerChange(speakers) {
|
||||||
|
console.log('handleActiveSpeakerChange', speakers);
|
||||||
|
speakers.forEach(s => {
|
||||||
|
if (s.isSpeaking) {
|
||||||
|
console.log('Speaking participant ', s);
|
||||||
|
|
||||||
|
// Display speaker in remote peer view.
|
||||||
|
const elem = this.remoteRef.current;
|
||||||
|
const me = this.props.tinode.getMeTopic();
|
||||||
|
const c = me.getContact(s.identity);
|
||||||
|
const name = c && c.public && c.public.fn ? c.public.fn : undefined;
|
||||||
|
this.setState({activeSpeaker: name});
|
||||||
|
|
||||||
|
const cameraPub = s.getTrack(Track.Source.Camera);
|
||||||
|
const cameraEnabled = cameraPub && cameraPub.isSubscribed && !cameraPub.isMuted;
|
||||||
|
if (cameraEnabled && cameraPub.videoTrack) {
|
||||||
|
cameraPub.videoTrack.attach(elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDevicesChanged() {
|
||||||
|
console.log('devices changed');
|
||||||
|
// TODO: implement.
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDisconnect() {
|
||||||
|
console.log('disconnected from room');
|
||||||
|
this.handleCloseClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
startRoom(token) {
|
||||||
|
const roomOpts = {
|
||||||
|
adaptiveStream: true,
|
||||||
|
dynacast: true,
|
||||||
|
publishDefaults: {
|
||||||
|
simulcast: true,
|
||||||
|
videoSimulcastLayers: [VideoPresets.h90, VideoPresets.h216],
|
||||||
|
videoCodec: 'vp8'
|
||||||
|
},
|
||||||
|
videoCaptureDefaults: {
|
||||||
|
resolution: VideoPresets.h720.resolution
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const connectOpts = {
|
||||||
|
autoSubscribe: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// creates a new room with options
|
||||||
|
const room = new Room(roomOpts);
|
||||||
|
|
||||||
|
this.setState({room: room});
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||||
|
const shouldPublish = this.isOutgoingCall || !this.isBroadcast;
|
||||||
|
|
||||||
|
room
|
||||||
|
.on(RoomEvent.ParticipantConnected, this.handleParticipantConnected)
|
||||||
|
.on(RoomEvent.ParticipantDisconnected, this.handleParticipantDisconnected)
|
||||||
|
.on(RoomEvent.DataReceived, this.handleData)
|
||||||
|
.on(RoomEvent.TrackSubscribed, this.handleTrackSubscribed)
|
||||||
|
.on(RoomEvent.TrackUnsubscribed, this.handleTrackUnsubscribed)
|
||||||
|
.on(RoomEvent.ActiveSpeakersChanged, this.handleActiveSpeakerChange)
|
||||||
|
.on(RoomEvent.Disconnected, this.handleDisconnect)
|
||||||
|
.on(RoomEvent.LocalTrackUnpublished, this.handleLocalTrackUnpublished)
|
||||||
|
.on(RoomEvent.LocalTrackPublished, this.handleLocalTrackPublished)
|
||||||
|
|
||||||
|
.on(RoomEvent.RoomMetadataChanged, metadata => {
|
||||||
|
console.log('VC room new metadata ', metadata);
|
||||||
|
})
|
||||||
|
.on(RoomEvent.MediaDevicesChanged, this.handleDevicesChanged)
|
||||||
|
.on(RoomEvent.AudioPlaybackStatusChanged, e => {
|
||||||
|
// TODO: handle it.
|
||||||
|
console.log('Audio playback status change: ', e);
|
||||||
|
})
|
||||||
|
.on(RoomEvent.MediaDevicesError, e => {
|
||||||
|
const failure = MediaDeviceFailure.getFailure(e);
|
||||||
|
this.reportError(failure);
|
||||||
|
})
|
||||||
|
.on(RoomEvent.ConnectionQualityChanged, (quality, participant) => {
|
||||||
|
const id = participant ? participant.identity : 'nil';
|
||||||
|
console.log('Connection quality changed', id, ' -> ', quality);
|
||||||
|
})
|
||||||
|
.on(RoomEvent.SignalConnected, _ => {
|
||||||
|
const signalConnectionTime = Date.now() - startTime;
|
||||||
|
console.log(`signal connection established in ${signalConnectionTime}ms`);
|
||||||
|
// speed up publishing by starting to publish before it's fully connected
|
||||||
|
// publishing is accepted as soon as signal connection has established
|
||||||
|
if (shouldPublish) {
|
||||||
|
room.localParticipant.enableCameraAndMicrophone();
|
||||||
|
console.log(`tracks published in ${Date.now() - startTime}ms`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const vcEndpoint = this.props.tinode.getServerParam('vcEndpoint', null);
|
||||||
|
room.connect(vcEndpoint, token, connectOpts).then(_ => {
|
||||||
|
console.log('Connected to VC room', room.name, '. Access token', token);
|
||||||
|
|
||||||
|
room.participants.forEach((participant) => {
|
||||||
|
this.handleParticipantConnected(participant);
|
||||||
|
});
|
||||||
|
this.handleParticipantConnected(room.localParticipant);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
if (this.state.localStream) {
|
||||||
|
this.props.onError(this.props.intl.formatMessage(messages.already_in_call), 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.isOutgoingCall) {
|
||||||
|
this.props.onCreateCall(this.props.topic, this.props.seq, this.props.callState, this.props.callAudioOnly)
|
||||||
|
.then(ctrl => {
|
||||||
|
const token = ctrl.params.token;
|
||||||
|
const seq = ctrl.params.seq;
|
||||||
|
this.setState({callSeq: seq, token: token});
|
||||||
|
this.startRoom(token);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.props.onJoin(this.props.topic, this.props.seq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if (this.state.room) {
|
||||||
|
this.state.room.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reportError(err) {
|
||||||
|
this.props.onError(err.message, 'err');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call disconnected by remote.
|
||||||
|
handleRemoteHangup() {
|
||||||
|
this.handleCloseClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCloseClick() {
|
||||||
|
this.stop();
|
||||||
|
this.props.onHangup(this.props.topic, this.props.seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleToggleCameraClick() {
|
||||||
|
if (!this.state.room) return;
|
||||||
|
const enabled = this.state.room.localParticipant.isCameraEnabled;
|
||||||
|
this.state.room.localParticipant.setCameraEnabled(!enabled)
|
||||||
|
.then(_ => this.forceUpdate());
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleToggleMicClick() {
|
||||||
|
if (!this.state.room) return;
|
||||||
|
const enabled = this.state.room.localParticipant.isMicrophoneEnabled;
|
||||||
|
this.state.room.localParticipant.setMicrophoneEnabled(!enabled)
|
||||||
|
.then(_ => this.forceUpdate());
|
||||||
|
|
||||||
|
// Make sure we redraw the mute/unmute icons (e.g. mic -> mic_off).
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const isPublishing = this.isOutgoingCall || !this.isBroadcast;
|
||||||
|
const disabled = !isPublishing || !this.state.room || !this.state.room.localParticipant;
|
||||||
|
const audioIcon = !disabled && this.state.room.localParticipant.isMicrophoneEnabled ? 'mic' : 'mic_off';
|
||||||
|
const videoIcon = !disabled && this.state.room.localParticipant.isCameraEnabled ? 'videocam' : 'videocam_off';
|
||||||
|
|
||||||
|
const peerTitle = this.state.activeSpeaker ? this.state.activeSpeaker : clipStr(this.props.title, MAX_PEER_TITLE_LENGTH);
|
||||||
|
|
||||||
|
const remoteActive = this.remoteRef.current && (this.remoteRef.current.src || this.remoteRef.current.srcObject)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="video-container">
|
||||||
|
{!this.isBroadcast && Object.keys(this.state.participants).length > 0 ?
|
||||||
|
<VCCarousel
|
||||||
|
viewportWidth={this.props.viewportWidth}
|
||||||
|
tinode={this.props.tinode}
|
||||||
|
participants={this.state.participants} /> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
<div id="video-container-panel">
|
||||||
|
{isPublishing ?
|
||||||
|
<div className="call-party self" disabled={this.state.audioOnly}>
|
||||||
|
<video ref={this.localRef} autoPlay muted playsInline />
|
||||||
|
<div className="caller-name inactive">
|
||||||
|
<FormattedMessage id="calls_you_label"
|
||||||
|
defaultMessage="You" description="Shown over the local video screen" />
|
||||||
|
</div>
|
||||||
|
</div> :
|
||||||
|
null }
|
||||||
|
<div className="call-party peer" disabled={!remoteActive}>
|
||||||
|
<video ref={this.remoteRef} autoPlay playsInline />
|
||||||
|
{remoteActive ?
|
||||||
|
<div className="caller-name inactive">{peerTitle}</div>
|
||||||
|
:
|
||||||
|
<div className="caller-card">
|
||||||
|
<div className="avatar-box">
|
||||||
|
<LetterTile
|
||||||
|
tinode={this.props.tinode}
|
||||||
|
avatar={this.props.avatar}
|
||||||
|
topic={this.props.topic}
|
||||||
|
title={this.props.title} />
|
||||||
|
</div>
|
||||||
|
<div className="caller-name">{peerTitle}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="controls">
|
||||||
|
<button className="danger" onClick={this.handleCloseClick}>
|
||||||
|
<i className="material-icons">call_end</i>
|
||||||
|
</button>
|
||||||
|
<button className="secondary" onClick={this.handleToggleCameraClick} disabled={disabled}>
|
||||||
|
<i className="material-icons">{videoIcon}</i>
|
||||||
|
</button>
|
||||||
|
<button className="secondary" onClick={this.handleToggleMicClick} disabled={disabled}>
|
||||||
|
<i className="material-icons">{audioIcon}</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default injectIntl(VCPanel);
|
||||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+762
-93
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -61,10 +61,23 @@ class PasswordResetView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pu
|
|||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const parsed = _lib_navigation_js__WEBPACK_IMPORTED_MODULE_4__["default"].parseUrlHash(window.location.hash);
|
const parsed = _lib_navigation_js__WEBPACK_IMPORTED_MODULE_4__["default"].parseUrlHash(window.location.hash);
|
||||||
this.setState({
|
const newState = {
|
||||||
token: parsed.params.token,
|
token: parsed.params.token,
|
||||||
scheme: parsed.params.scheme
|
scheme: parsed.params.scheme,
|
||||||
});
|
code: parsed.params.code || ''
|
||||||
|
};
|
||||||
|
this.savedCred = parsed.params.cred;
|
||||||
|
if (this.props.reqCredMethod && parsed.params.cred) {
|
||||||
|
newState[this.props.reqCredMethod] = this.savedCred;
|
||||||
|
}
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
if (prevProps.reqCredMethod != this.props.reqCredMethod && this.props.reqCredMethod) {
|
||||||
|
this.setState({
|
||||||
|
[this.props.reqCredMethod]: this.savedCred || ''
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
handleSubmit(e) {
|
handleSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -74,7 +87,7 @@ class PasswordResetView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pu
|
|||||||
secret: this.state.token
|
secret: this.state.token
|
||||||
});
|
});
|
||||||
} else if (this.state.code && this.props.reqCredMethod) {
|
} else if (this.state.code && this.props.reqCredMethod) {
|
||||||
const cred = this.state.email.trim() || this.state.tel.trim();
|
const cred = this.props.reqCredMethod == 'email' ? this.state.email.trim() : this.state.tel.trim();
|
||||||
this.props.onReset(this.state.password.trim(), {
|
this.props.onReset(this.state.password.trim(), {
|
||||||
scheme: 'code',
|
scheme: 'code',
|
||||||
secret: btoa(`${this.state.code}:${this.props.reqCredMethod}:${cred}`)
|
secret: btoa(`${this.state.code}:${this.props.reqCredMethod}:${cred}`)
|
||||||
@@ -119,7 +132,7 @@ class PasswordResetView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pu
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const showCredentialInput = !(this.state.token && this.state.scheme);
|
const showCredentialInput = !((this.state.token || this.state.code && this.props.reqCredMethod) && this.state.scheme);
|
||||||
const showPasswordInput = !showCredentialInput || this.state.haveCode || this.state.sent;
|
const showPasswordInput = !showCredentialInput || this.state.haveCode || this.state.sent;
|
||||||
const passwordInput = react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", {
|
const passwordInput = react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", {
|
||||||
className: "small gray",
|
className: "small gray",
|
||||||
@@ -222,7 +235,7 @@ class PasswordResetView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pu
|
|||||||
onSubmit: this.handleSubmit
|
onSubmit: this.handleSubmit
|
||||||
}, !this.state.sent && showCredentialInput ? credentialInput : null, this.state.haveCode || this.state.sent ? codeInput : null, showPasswordInput ? passwordInput : null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
}, !this.state.sent && showCredentialInput ? credentialInput : null, this.state.haveCode || this.state.sent ? codeInput : null, showPasswordInput ? passwordInput : null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
className: "dialog-buttons"
|
className: "dialog-buttons"
|
||||||
}, this.state.haveCode || this.state.sent || this.state.token ? null : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
}, this.state.haveCode || this.state.sent || this.state.token || this.state.code ? null : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||||
href: "#",
|
href: "#",
|
||||||
onClick: this.handleShowCodeField,
|
onClick: this.handleShowCodeField,
|
||||||
style: {
|
style: {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+10
-16
@@ -906,7 +906,7 @@ class DB {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
markTopicAsDeleted(name, deleted) {
|
markTopicAsDeleted(name) {
|
||||||
if (!this.isReady()) {
|
if (!this.isReady()) {
|
||||||
return this.disabled ? Promise.resolve() : Promise.reject(new Error("not initialized"));
|
return this.disabled ? Promise.resolve() : Promise.reject(new Error("not initialized"));
|
||||||
}
|
}
|
||||||
@@ -922,10 +922,8 @@ class DB {
|
|||||||
const req = trx.objectStore('topic').get(name);
|
const req = trx.objectStore('topic').get(name);
|
||||||
req.onsuccess = event => {
|
req.onsuccess = event => {
|
||||||
const topic = event.target.result;
|
const topic = event.target.result;
|
||||||
if (topic && topic._deleted != deleted) {
|
topic._deleted = true;
|
||||||
topic._deleted = deleted;
|
trx.objectStore('topic').put(topic);
|
||||||
trx.objectStore('topic').put(topic);
|
|
||||||
}
|
|
||||||
trx.commit();
|
trx.commit();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -3517,11 +3515,10 @@ class TopicMe extends _topic_js__WEBPACK_IMPORTED_MODULE_2__["default"] {
|
|||||||
cont.unread = cont.seq - cont.read;
|
cont.unread = cont.seq - cont.read;
|
||||||
break;
|
break;
|
||||||
case 'gone':
|
case 'gone':
|
||||||
this._tinode.cacheRemTopic(pres.src);
|
|
||||||
if (!cont._deleted) {
|
if (!cont._deleted) {
|
||||||
cont._deleted = true;
|
cont._deleted = true;
|
||||||
cont._attached = false;
|
cont._attached = false;
|
||||||
this._tinode._db.markTopicAsDeleted(pres.src, true);
|
this._tinode._db.markTopicAsDeleted(pres.src);
|
||||||
} else {
|
} else {
|
||||||
this._tinode._db.remTopic(pres.src);
|
this._tinode._db.remTopic(pres.src);
|
||||||
}
|
}
|
||||||
@@ -3551,13 +3548,7 @@ class TopicMe extends _topic_js__WEBPACK_IMPORTED_MODULE_2__["default"] {
|
|||||||
}
|
}
|
||||||
} else if (pres.what == 'tags') {
|
} else if (pres.what == 'tags') {
|
||||||
this.getMeta(this.startMetaQuery().withTags().build());
|
this.getMeta(this.startMetaQuery().withTags().build());
|
||||||
} else if (pres.what == 'msg') {
|
|
||||||
this.getMeta(this.startMetaQuery().withOneSub(undefined, pres.src).build());
|
|
||||||
const dummy = this._tinode.getTopic(pres.src);
|
|
||||||
dummy._deleted = false;
|
|
||||||
this._tinode._db.updTopic(dummy);
|
|
||||||
}
|
}
|
||||||
this._refreshContact(pres.what, cont);
|
|
||||||
}
|
}
|
||||||
if (this.onPres) {
|
if (this.onPres) {
|
||||||
this.onPres(pres);
|
this.onPres(pres);
|
||||||
@@ -3636,13 +3627,13 @@ class MetaGetBuilder {
|
|||||||
this.what = {};
|
this.what = {};
|
||||||
}
|
}
|
||||||
#get_desc_ims() {
|
#get_desc_ims() {
|
||||||
return this.topic._deleted ? undefined : this.topic.updated;
|
return this.topic.updated;
|
||||||
}
|
}
|
||||||
#get_subs_ims() {
|
#get_subs_ims() {
|
||||||
if (this.topic.isP2PType()) {
|
if (this.topic.isP2PType()) {
|
||||||
return this.#get_desc_ims();
|
return this.#get_desc_ims();
|
||||||
}
|
}
|
||||||
return this.topic._deleted ? undefined : this.topic._lastSubsUpdate;
|
return this.topic._lastSubsUpdate;
|
||||||
}
|
}
|
||||||
withData(since, before, limit) {
|
withData(since, before, limit) {
|
||||||
this.what['data'] = {
|
this.what['data'] = {
|
||||||
@@ -3867,6 +3858,9 @@ class Topic {
|
|||||||
if (this._attached) {
|
if (this._attached) {
|
||||||
return Promise.resolve(this);
|
return Promise.resolve(this);
|
||||||
}
|
}
|
||||||
|
if (this._deleted) {
|
||||||
|
return Promise.reject(new Error("Conversation deleted"));
|
||||||
|
}
|
||||||
return this._tinode.subscribe(this.name || _config_js__WEBPACK_IMPORTED_MODULE_3__.TOPIC_NEW, getParams, setParams).then(ctrl => {
|
return this._tinode.subscribe(this.name || _config_js__WEBPACK_IMPORTED_MODULE_3__.TOPIC_NEW, getParams, setParams).then(ctrl => {
|
||||||
if (ctrl.code >= 300) {
|
if (ctrl.code >= 300) {
|
||||||
return ctrl;
|
return ctrl;
|
||||||
@@ -4718,7 +4712,7 @@ class Topic {
|
|||||||
break;
|
break;
|
||||||
case 'upd':
|
case 'upd':
|
||||||
if (pres.src && !this._tinode.isTopicCached(pres.src)) {
|
if (pres.src && !this._tinode.isTopicCached(pres.src)) {
|
||||||
this.getMeta(this.startMetaQuery().withOneSub(pres.src).build());
|
this.getMeta(this.startMetaQuery().withLaterOneSub(pres.src).build());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'aux':
|
case 'aux':
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+13
-255
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
// This is a generated file. Don't edit.
|
// This is a generated file. Don't edit.
|
||||||
|
|
||||||
const PACKAGE_VERSION = "0.23.0-rc1";
|
const PACKAGE_VERSION = "0.23.0-rc2";
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ module.exports = (env, argv) => {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
externals: {
|
externals: {
|
||||||
|
'livekit-client': 'LivekitClient',
|
||||||
'react': 'React',
|
'react': 'React',
|
||||||
'react-dom': 'ReactDOM',
|
'react-dom': 'ReactDOM',
|
||||||
'react-intl': 'ReactIntl',
|
'react-intl': 'ReactIntl',
|
||||||
|
|||||||
Reference in New Issue
Block a user