Merge branch 'vc' of https://github.com/tinode/webapp into vc
This commit is contained in:
+1
-1
@@ -96,7 +96,7 @@ export const MAX_TITLE_LENGTH = 60;
|
||||
// Maximum length of topic description.
|
||||
export const MAX_TOPIC_DESCRIPTION_LENGTH = 360;
|
||||
// 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.
|
||||
export const MESSAGE_PREVIEW_LENGTH = 80;
|
||||
|
||||
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
+21
-7
@@ -1420,7 +1420,7 @@
|
||||
"cred_confirmed_successfully": {
|
||||
"translation": "Erfolgreich bestätigt",
|
||||
"defaultMessage": "Confirmed successfully",
|
||||
"description": "Message explaining that the credential was successfully validated.",
|
||||
"description": "Notification message that the credential was successfully validated.",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1635,24 +1635,38 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"pin_message": {
|
||||
"translation": "",
|
||||
"translation": "Anpinnen",
|
||||
"defaultMessage": "Pin",
|
||||
"description": "Menu item [Pin] for pinning message to the top.",
|
||||
"missing": true,
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unpin_message": {
|
||||
"translation": "",
|
||||
"translation": "Lösen",
|
||||
"defaultMessage": "Unpin",
|
||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||
"missing": true,
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"calls_conference": {
|
||||
"translation": "",
|
||||
"translation": "Telefonkonferenz",
|
||||
"defaultMessage": "Conference call",
|
||||
"description": "Conference call label",
|
||||
"missing": true,
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
+15
-1
@@ -1420,7 +1420,7 @@
|
||||
"cred_confirmed_successfully": {
|
||||
"translation": "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,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1654,5 +1654,19 @@
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
+21
-7
@@ -1420,7 +1420,7 @@
|
||||
"cred_confirmed_successfully": {
|
||||
"translation": "Confirmado con éxito",
|
||||
"defaultMessage": "Confirmed successfully",
|
||||
"description": "Message explaining that the credential was successfully validated.",
|
||||
"description": "Notification message that the credential was successfully validated.",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1628,17 +1628,17 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"pin_message": {
|
||||
"translation": "",
|
||||
"translation": "Fijar",
|
||||
"defaultMessage": "Pin",
|
||||
"description": "Menu item [Pin] for pinning message to the top.",
|
||||
"missing": true,
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unpin_message": {
|
||||
"translation": "",
|
||||
"translation": "Desanclar",
|
||||
"defaultMessage": "Unpin",
|
||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||
"missing": true,
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"failed_to_init_audio": {
|
||||
@@ -1649,10 +1649,24 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"calls_conference": {
|
||||
"translation": "",
|
||||
"translation": "Conferencia",
|
||||
"defaultMessage": "Conference call",
|
||||
"description": "Conference call label",
|
||||
"missing": true,
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@
|
||||
},
|
||||
"cred_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": {
|
||||
"defaultMessage": "Your registration email",
|
||||
@@ -374,6 +374,10 @@
|
||||
"defaultMessage": "Invalid security token",
|
||||
"description": "Error message when resetting password."
|
||||
},
|
||||
"join_conference": {
|
||||
"defaultMessage": "+ Join",
|
||||
"description": "Call to action to join a conference call"
|
||||
},
|
||||
"label_client": {
|
||||
"defaultMessage": "Client:",
|
||||
"description": "Label for a client version"
|
||||
@@ -708,6 +712,10 @@
|
||||
"defaultMessage": "A text message with security code 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": {
|
||||
"defaultMessage": "Unchanged",
|
||||
"description": "Message in editor while password is unchanged"
|
||||
|
||||
+21
-7
@@ -1420,7 +1420,7 @@
|
||||
"cred_confirmed_successfully": {
|
||||
"translation": "Confirmé avec succès",
|
||||
"defaultMessage": "Confirmed successfully",
|
||||
"description": "Message explaining that the credential was successfully validated.",
|
||||
"description": "Notification message that the credential was successfully validated.",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1628,17 +1628,17 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"pin_message": {
|
||||
"translation": "",
|
||||
"translation": "Épingler",
|
||||
"defaultMessage": "Pin",
|
||||
"description": "Menu item [Pin] for pinning message to the top.",
|
||||
"missing": true,
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unpin_message": {
|
||||
"translation": "",
|
||||
"translation": "Détacher",
|
||||
"defaultMessage": "Unpin",
|
||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||
"missing": true,
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"failed_to_init_audio": {
|
||||
@@ -1649,10 +1649,24 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"calls_conference": {
|
||||
"translation": "",
|
||||
"translation": "Conférence téléphonique",
|
||||
"defaultMessage": "Conference call",
|
||||
"description": "Conference call label",
|
||||
"missing": true,
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
+21
-7
@@ -1420,7 +1420,7 @@
|
||||
"cred_confirmed_successfully": {
|
||||
"translation": "성공적으로 확인됨",
|
||||
"defaultMessage": "Confirmed successfully",
|
||||
"description": "Message explaining that the credential was successfully validated.",
|
||||
"description": "Notification message that the credential was successfully validated.",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1628,17 +1628,17 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"pin_message": {
|
||||
"translation": "",
|
||||
"translation": "고정",
|
||||
"defaultMessage": "Pin",
|
||||
"description": "Menu item [Pin] for pinning message to the top.",
|
||||
"missing": true,
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unpin_message": {
|
||||
"translation": "",
|
||||
"translation": "고정 해제",
|
||||
"defaultMessage": "Unpin",
|
||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||
"missing": true,
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"failed_to_init_audio": {
|
||||
@@ -1649,10 +1649,24 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"calls_conference": {
|
||||
"translation": "",
|
||||
"translation": "전화 회의",
|
||||
"defaultMessage": "Conference call",
|
||||
"description": "Conference call label",
|
||||
"missing": true,
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
+21
-7
@@ -1420,7 +1420,7 @@
|
||||
"cred_confirmed_successfully": {
|
||||
"translation": "Confirmat cu succes",
|
||||
"defaultMessage": "Confirmed successfully",
|
||||
"description": "Message explaining that the credential was successfully validated.",
|
||||
"description": "Notification message that the credential was successfully validated.",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1628,17 +1628,17 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"pin_message": {
|
||||
"translation": "",
|
||||
"translation": "Fixați",
|
||||
"defaultMessage": "Pin",
|
||||
"description": "Menu item [Pin] for pinning message to the top.",
|
||||
"missing": true,
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unpin_message": {
|
||||
"translation": "",
|
||||
"translation": "Anulați fixarea",
|
||||
"defaultMessage": "Unpin",
|
||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||
"missing": true,
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"failed_to_init_audio": {
|
||||
@@ -1649,10 +1649,24 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"calls_conference": {
|
||||
"translation": "",
|
||||
"translation": "Apel de conferinta",
|
||||
"defaultMessage": "Conference call",
|
||||
"description": "Conference call label",
|
||||
"missing": true,
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
+21
-7
@@ -1420,7 +1420,7 @@
|
||||
"cred_confirmed_successfully": {
|
||||
"translation": "Подтверждено успешно",
|
||||
"defaultMessage": "Confirmed successfully",
|
||||
"description": "Message explaining that the credential was successfully validated.",
|
||||
"description": "Notification message that the credential was successfully validated.",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1628,17 +1628,17 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"pin_message": {
|
||||
"translation": "",
|
||||
"translation": "Закрепить",
|
||||
"defaultMessage": "Pin",
|
||||
"description": "Menu item [Pin] for pinning message to the top.",
|
||||
"missing": true,
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unpin_message": {
|
||||
"translation": "",
|
||||
"translation": "Открепить",
|
||||
"defaultMessage": "Unpin",
|
||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||
"missing": true,
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"failed_to_init_audio": {
|
||||
@@ -1649,10 +1649,24 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"calls_conference": {
|
||||
"translation": "",
|
||||
"translation": "Групповой звонок",
|
||||
"defaultMessage": "Conference call",
|
||||
"description": "Conference call label",
|
||||
"missing": true,
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
+21
-7
@@ -1420,7 +1420,7 @@
|
||||
"cred_confirmed_successfully": {
|
||||
"translation": "確認成功",
|
||||
"defaultMessage": "Confirmed successfully",
|
||||
"description": "Message explaining that the credential was successfully validated.",
|
||||
"description": "Notification message that the credential was successfully validated.",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1628,17 +1628,17 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"pin_message": {
|
||||
"translation": "",
|
||||
"translation": "置頂消息",
|
||||
"defaultMessage": "Pin",
|
||||
"description": "Menu item [Pin] for pinning message to the top.",
|
||||
"missing": true,
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unpin_message": {
|
||||
"translation": "",
|
||||
"translation": "取消置頂消息",
|
||||
"defaultMessage": "Unpin",
|
||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||
"missing": true,
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"failed_to_init_audio": {
|
||||
@@ -1649,10 +1649,24 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"calls_conference": {
|
||||
"translation": "",
|
||||
"translation": "電話會議",
|
||||
"defaultMessage": "Conference call",
|
||||
"description": "Conference call label",
|
||||
"missing": true,
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
+21
-7
@@ -1420,7 +1420,7 @@
|
||||
"cred_confirmed_successfully": {
|
||||
"translation": "确认成功",
|
||||
"defaultMessage": "Confirmed successfully",
|
||||
"description": "Message explaining that the credential was successfully validated.",
|
||||
"description": "Notification message that the credential was successfully validated.",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1628,17 +1628,17 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"pin_message": {
|
||||
"translation": "",
|
||||
"translation": "置顶消息",
|
||||
"defaultMessage": "Pin",
|
||||
"description": "Menu item [Pin] for pinning message to the top.",
|
||||
"missing": true,
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unpin_message": {
|
||||
"translation": "",
|
||||
"translation": "取消置顶消息",
|
||||
"defaultMessage": "Unpin",
|
||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||
"missing": true,
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"failed_to_init_audio": {
|
||||
@@ -1649,10 +1649,24 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"calls_conference": {
|
||||
"translation": "",
|
||||
"translation": "电话会议",
|
||||
"defaultMessage": "Conference call",
|
||||
"description": "Conference call label",
|
||||
"missing": true,
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ function handleImageData(el, data, attr) {
|
||||
return el;
|
||||
}
|
||||
|
||||
// Additional processing of image data.
|
||||
// Additional processing of attached video data.
|
||||
function handleVideoData(el, data, attr) {
|
||||
if (!data) {
|
||||
attr.src = 'img/broken_video.png';
|
||||
@@ -241,7 +241,7 @@ function handleVideoData(el, data, attr) {
|
||||
}
|
||||
el = InlineVideo;
|
||||
} else {
|
||||
// Use custom element instead of <img>.
|
||||
// Use custom element instead of <img> or <video>.
|
||||
el = UploadingImage;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ export default class HashNavigation {
|
||||
// Parse hash as in http://www.example.com/path#hash as if it were
|
||||
// path and arguments.
|
||||
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 params = {};
|
||||
let path = [];
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
// This is a generated file. Don't edit.
|
||||
|
||||
export const PACKAGE_VERSION = "0.23.0-rc1";
|
||||
export const PACKAGE_VERSION = "0.23.0-rc2";
|
||||
|
||||
@@ -186,16 +186,23 @@ class MessagesView extends React.Component {
|
||||
|
||||
getVisibleMessageRange(holderRect) {
|
||||
let min = Number.MAX_SAFE_INTEGER, max = -1;
|
||||
this.chatMessageRefs.forEach((ref, seq) => {
|
||||
let visibilityStatus = false;
|
||||
this.chatMessageRefs.every((ref, seq) => {
|
||||
if (ref.current) {
|
||||
const { top, bottom, height } = ref.current.getBoundingClientRect();
|
||||
const visible = top <= holderRect.top ? holderRect.top - top <= height : bottom - holderRect.bottom <= height;
|
||||
if (visible) {
|
||||
visibilityStatus = true;
|
||||
min = Math.min(min, 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};
|
||||
}
|
||||
|
||||
@@ -321,7 +328,8 @@ class MessagesView extends React.Component {
|
||||
showGoToLastButton: false,
|
||||
dragging: false,
|
||||
pins: [],
|
||||
selectedPin: 0
|
||||
selectedPin: 0,
|
||||
subsVersion: 0
|
||||
};
|
||||
} else if (nextProps.topic != prevState.topic) {
|
||||
const topic = nextProps.tinode.getTopic(nextProps.topic);
|
||||
@@ -578,13 +586,14 @@ class MessagesView extends React.Component {
|
||||
showGoToLastButton: (pos > SHOW_GO_TO_LAST_DIST) && (pos < this.state.scrollPosition),
|
||||
});
|
||||
|
||||
if (this.state.fetchingMessages) {
|
||||
if (this.state.fetchingMessages || this.processingScrollEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target.scrollTop <= FETCH_PAGE_TRIGGER) {
|
||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||
if (topic && topic.isSubscribed()) {
|
||||
this.processingScrollEvent = true;
|
||||
const {min, max} = this.getVisibleMessageRange(event.target.getBoundingClientRect());
|
||||
const gaps = topic.msgHasMoreMessages(min, max, false);
|
||||
if (gaps.length > 0) {
|
||||
@@ -596,6 +605,7 @@ class MessagesView extends React.Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.processingScrollEvent = false;
|
||||
}
|
||||
|
||||
/* Mount drag and drop events */
|
||||
@@ -707,12 +717,12 @@ class MessagesView extends React.Component {
|
||||
if (this.state.topic) {
|
||||
const subs = [];
|
||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||
topic.subscribers((sub) => {
|
||||
topic.subscribers(sub => {
|
||||
if (sub.online && sub.user != this.props.myUserId) {
|
||||
subs.push(sub);
|
||||
}
|
||||
});
|
||||
const newState = {onlineSubs: subs};
|
||||
const newState = {onlineSubs: subs, subsVersion: this.state.subsVersion + 1};
|
||||
const peer = topic.p2pPeerDesc();
|
||||
if (peer) {
|
||||
Object.assign(newState, {
|
||||
@@ -1474,6 +1484,7 @@ class MessagesView extends React.Component {
|
||||
title={this.state.title}
|
||||
avatar={this.state.avatar || true}
|
||||
myUid={this.props.myUserId}
|
||||
viewportWidth={this.props.viewportWidth}
|
||||
|
||||
onError={this.props.onError}
|
||||
onHangup={this.handleCallHangup}
|
||||
@@ -1620,7 +1631,7 @@ class MessagesView extends React.Component {
|
||||
let messagesComponent = (
|
||||
<>
|
||||
<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}>
|
||||
<i className="material-icons">arrow_downward</i>
|
||||
</button>
|
||||
|
||||
@@ -48,7 +48,23 @@ class PasswordResetView extends React.PureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
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) {
|
||||
@@ -62,7 +78,7 @@ class PasswordResetView extends React.PureComponent {
|
||||
} else if (this.state.code && this.props.reqCredMethod) {
|
||||
// Reset using security code.
|
||||
// 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(), {
|
||||
scheme: 'code',
|
||||
secret: btoa(`${this.state.code}:${this.props.reqCredMethod}:${cred}`)
|
||||
@@ -102,7 +118,7 @@ class PasswordResetView extends React.PureComponent {
|
||||
}
|
||||
|
||||
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 passwordInput = (<>
|
||||
@@ -193,7 +209,7 @@ class PasswordResetView extends React.PureComponent {
|
||||
{this.state.haveCode || this.state.sent ? codeInput : null}
|
||||
{showPasswordInput ? passwordInput : null}
|
||||
<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'}}>
|
||||
<FormattedMessage id="password_i_have_code" defaultMessage="I have code"
|
||||
description="Call to open field to enter password reset code" />
|
||||
|
||||
@@ -83,7 +83,12 @@ const messages = defineMessages({
|
||||
cred_confirmed_successfully: {
|
||||
id: 'cred_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.'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1737,9 +1742,11 @@ class TinodeWeb extends React.Component {
|
||||
} else {
|
||||
this.tinode.connect()
|
||||
.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 => {
|
||||
// Socket error
|
||||
this.handleError(err.message, 'err');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,7 +7,13 @@ import { secondsToTime } from '../lib/strformat.js';
|
||||
|
||||
export default class CallMessage extends React.PureComponent {
|
||||
render() {
|
||||
const bigIcon = this.props.vc ? 'video_call' : 'call';
|
||||
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 successClass = 'material-icons medium ' + (isCallDropped ? 'red' : 'green');
|
||||
const callIcon = this.props.incoming ?
|
||||
@@ -46,7 +52,9 @@ export default class CallMessage extends React.PureComponent {
|
||||
<div>{title}</div>
|
||||
<div className="duration"><i className={successClass}>{callIcon}</i> {duration}</div>
|
||||
{allowJoin ?
|
||||
<a onClick={this.props.onCallJoin}>Join</a>
|
||||
<a onClick={this.props.onCallJoin}>
|
||||
<FormattedMessage id="join_conference" defaultMessage="+ Join" description="Call to action to join a conference call" />
|
||||
</a>
|
||||
: null}
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
@@ -6,7 +6,8 @@ import { secondsToTime } from '../lib/strformat.js';
|
||||
|
||||
// Props:
|
||||
// incoming: true | false.
|
||||
// success: true | false.
|
||||
// callState: 'busy' | 'declined' | 'disconnected' | 'missed'.
|
||||
// duration: <number> (call duration in milliseconds).
|
||||
export default class CallStatus extends React.PureComponent {
|
||||
render() {
|
||||
const isCallDropped = ['busy', 'declined', 'disconnected', 'missed'].includes(this.props.callState);
|
||||
|
||||
@@ -29,7 +29,7 @@ export default class InlineVideo extends React.PureComponent {
|
||||
{React.createElement('img', this.props)}
|
||||
<div className="play-control">
|
||||
{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" />}
|
||||
</div>
|
||||
|
||||
@@ -1,34 +1,44 @@
|
||||
// Video conference carousel item.
|
||||
import React from 'react';
|
||||
import Track from 'livekit-client';
|
||||
|
||||
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.state = {
|
||||
videoRef: undefined,
|
||||
audioRef: undefined
|
||||
};
|
||||
|
||||
this.handleVideoRefChange = this.handleVideoRefChange.bind(this);
|
||||
this.handleAudioRefChange = this.handleAudioRefChange.bind(this);
|
||||
|
||||
this.videoRef = null;
|
||||
this.audioRef = null;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.state.videoRef && !this.state.videoRef.srcObject && this.props.cameraPub && this.props.cameraPub.videoTrack) {
|
||||
this.props.cameraPub.videoTrack.attach(this.state.videoRef);
|
||||
if (this.videoRef && !this.videoRef.srcObject && this.props.cameraPub && this.props.cameraPub.videoTrack) {
|
||||
this.props.cameraPub.videoTrack.attach(this.videoRef);
|
||||
}
|
||||
if (this.state.audioRef && !this.state.audioRef.srcObject && this.props.micPub && this.props.micPub.audioTrack) {
|
||||
this.props.micPub.audioTrack.attach(this.state.audioRef);
|
||||
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.setState({videoRef: node});
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.videoRef = node;
|
||||
|
||||
// Attach video track to video element.
|
||||
const cameraPub = this.props.cameraPub;
|
||||
@@ -39,14 +49,17 @@ export default class VCCarouselItem extends React.PureComponent {
|
||||
}
|
||||
|
||||
handleAudioRefChange(node) {
|
||||
if (!node) { return; }
|
||||
this.setState({audioRef: 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 && this.state.audioTrack) {
|
||||
this.state.audioTrack.attach(node);
|
||||
if (micEnabled && micPub.audioTrack) {
|
||||
micPub.audioTrack.attach(node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,28 +73,24 @@ export default class VCCarouselItem extends React.PureComponent {
|
||||
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 carousel-item">
|
||||
<LetterTile
|
||||
tinode={this.props.tinode}
|
||||
avatar={this.props.photo}
|
||||
topic={this.props.identity}
|
||||
title={this.props.name} />
|
||||
</div>
|
||||
</>
|
||||
: null}
|
||||
<div className="caller-name inactive">
|
||||
{this.props.name}
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
+58
-54
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
|
||||
import { Track } from 'livekit-client';
|
||||
|
||||
import LetterTile from './letter-tile.jsx';
|
||||
import VCCarouselItem from './vc-carousel-item.jsx';
|
||||
|
||||
import { VC_CAROUSEL_ITEM_GAP } from '../constants.js';
|
||||
@@ -10,39 +9,56 @@ import { VC_CAROUSEL_ITEM_GAP } from '../constants.js';
|
||||
export default class VCCarousel extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handlePrevClick = this.handlePrevClick.bind(this);
|
||||
this.handleNextClick = this.handleNextClick.bind(this);
|
||||
|
||||
this.carouselRef = React.createRef();
|
||||
|
||||
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();
|
||||
this.prevRef = React.createRef();
|
||||
this.nextRef = React.createRef();
|
||||
}
|
||||
|
||||
handlePrevClick() {
|
||||
const gap = VC_CAROUSEL_ITEM_GAP;
|
||||
const width = this.carouselRef.current.offsetWidth;
|
||||
this.carouselRef.current.scrollBy(-(width + gap), 0);
|
||||
if (this.carouselRef.current.scrollLeft - width - gap <= 0) {
|
||||
this.prevRef.current.style.display = 'none';
|
||||
}
|
||||
if (!this.contentRef.current.scrollWidth - width - gap <= this.carouselRef.current.scrollLeft + width) {
|
||||
this.nextRef.current.style.display = 'flex';
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.carouselRef && this.props.viewportWidth != prevProps.viewportWidth) {
|
||||
this.handleScrollEvent({target: this.carouselRef});
|
||||
}
|
||||
}
|
||||
|
||||
handleNextClick() {
|
||||
const gap = VC_CAROUSEL_ITEM_GAP;
|
||||
const width = this.carouselRef.current.offsetWidth;
|
||||
this.carouselRef.current.scrollBy(width + gap, 0);
|
||||
if (this.carouselRef.current.scrollWidth !== 0) {
|
||||
this.prevRef.current.style.display = 'flex';
|
||||
componentWillUnmount() {
|
||||
if (this.carouselRef) {
|
||||
this.carouselRef.removeEventListener('scroll', this.handleScrollEvent);
|
||||
}
|
||||
if (this.contentRef.current.scrollWidth - width - gap <= this.carouselRef.current.scrollLeft + width) {
|
||||
this.nextRef.current.style.display = 'none';
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -57,44 +73,32 @@ export default class VCCarousel extends React.Component {
|
||||
cameraPub={cameraPub ? cameraPub : null}
|
||||
micPub={micPub ? micPub : null}
|
||||
name={party.name}
|
||||
identity={identity}
|
||||
userId={identity}
|
||||
photo={party.photo}
|
||||
key={i}
|
||||
/>);
|
||||
});
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div id="carousel-wrapper">
|
||||
<div id="carousel" ref={this.carouselRef}>
|
||||
<div id="carousel-content" ref={this.contentRef}>
|
||||
{peers}
|
||||
</div>
|
||||
<div id="carousel-wrapper">
|
||||
<div id="carousel" ref={this.handleScrollReference}>
|
||||
<div id="carousel-content" ref={this.contentRef}>
|
||||
{peers}
|
||||
</div>
|
||||
<button id="carousel-prev" ref={this.prevRef} onClick={this.handlePrevClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||
<path d="M15.61 7.41L14.2 6l-6 6 6 6 1.41-1.41L11.03 12l4.58-4.59z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button id="carousel-next" ref={this.nextRef} onClick={this.handleNextClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||
<path d="M10.02 6L8.61 7.41 13.19 12l-4.58 4.59L10.02 18l6-6-6-6z" />
|
||||
</svg>
|
||||
</button>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+58
-69
@@ -1,17 +1,12 @@
|
||||
// Video conference or broadcast: local and/or peer viewports and controls.
|
||||
import React from 'react';
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
|
||||
import {
|
||||
connect,
|
||||
LocalParticipant,
|
||||
LocalVideoTrack,
|
||||
MediaDeviceFailure,
|
||||
Participant,
|
||||
ParticipantEvent,
|
||||
RemoteParticipant,
|
||||
RemoteTrack,
|
||||
RemoteTrackPublication,
|
||||
Room,
|
||||
RoomEvent,
|
||||
Track,
|
||||
@@ -70,8 +65,6 @@ class VCPanel extends React.PureComponent {
|
||||
this.handleData = this.handleData.bind(this);
|
||||
this.handleDevicesChanged = this.handleDevicesChanged.bind(this);
|
||||
this.startRoom = this.startRoom.bind(this);
|
||||
|
||||
this.participants = {};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -83,7 +76,9 @@ class VCPanel extends React.PureComponent {
|
||||
|
||||
componentWillUnmount() {
|
||||
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||
topic.onInfo = this.previousOnInfo;
|
||||
if (topic) {
|
||||
topic.onInfo = this.previousOnInfo;
|
||||
}
|
||||
this.stop();
|
||||
}
|
||||
|
||||
@@ -128,23 +123,21 @@ class VCPanel extends React.PureComponent {
|
||||
|
||||
const me = this.props.tinode.getMeTopic();
|
||||
const c = me.getContact(participant.identity);
|
||||
const name = c && c.public && c.public.fn ? c.public.fn : participant.identity;
|
||||
const photo = makeImageUrl(c && c.public ? c.public.photo : null);
|
||||
const p = {
|
||||
name: name,
|
||||
photo: photo,
|
||||
|
||||
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.participants[participant.identity] = p
|
||||
console.log('participant connected', this.participants);
|
||||
this.setState({participants: this.participants}, _ => { this.forceUpdate(); });
|
||||
this.setState({participants: participants});
|
||||
}
|
||||
}
|
||||
|
||||
handleParticipantDisconnected(participant) {
|
||||
console.log('Deleting participant ', participant.identity);
|
||||
delete this.participants[participant.identity];
|
||||
this.setState({participants: this.participants});
|
||||
const participants = Object.assign({}, this.state.participants);
|
||||
delete participants[participant.identity];
|
||||
this.setState({participants: participants});
|
||||
}
|
||||
|
||||
handleData(msg, participant) {
|
||||
@@ -364,58 +357,54 @@ class VCPanel extends React.PureComponent {
|
||||
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
|
||||
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 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> :
|
||||
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 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>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user