mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2025-12-31 20:02:27 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d11735b04c | ||
|
|
d33b700477 | ||
|
|
97d75c2cb9 | ||
|
|
287115f4c3 | ||
|
|
63a221212b | ||
|
|
6a916fd0e1 | ||
|
|
220691d61d | ||
|
|
5cafc4bcbd | ||
|
|
b3a78dc2e6 | ||
|
|
bebc6eabe5 | ||
|
|
26dc6a4ac2 | ||
|
|
ff2626723a | ||
|
|
72bb897269 | ||
|
|
f46387a226 | ||
|
|
a4cbbccb2a | ||
|
|
3e1a008399 | ||
|
|
7e70a8c1de | ||
|
|
8efee04a10 | ||
|
|
a35099f949 |
@@ -10,7 +10,7 @@ MVN_HTTP=0
|
||||
DEFAULT_SDK_VERSION=$(grep sdkVersion ${THIS_DIR}/../gradle.properties | cut -d"=" -f2)
|
||||
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
|
||||
RN_VERSION=$(jq -r '.dependencies."react-native"' ${THIS_DIR}/../../package.json)
|
||||
JSC_VERSION="r"$(jq -r '.dependencies."jsc-android"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -d . -f 1)
|
||||
JSC_VERSION="r"$(jq -r '.dependencies."jsc-android"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -d . -f 1 | cut -c 2-)
|
||||
DO_GIT_TAG=${GIT_TAG:-0}
|
||||
|
||||
if [[ $THE_MVN_REPO == http* ]]; then
|
||||
|
||||
12
config.js
12
config.js
@@ -419,9 +419,15 @@ var config = {
|
||||
// the menu has option to flip the locally seen video for local presentations
|
||||
// disableLocalVideoFlip: false
|
||||
|
||||
// If specified a 'Help' button will be displayed in the overflow menu with a link to the specified URL for
|
||||
// user documentation.
|
||||
// userDocumentationURL: 'https://docs.example.com/video-meetings.html'
|
||||
// Deployment specific URLs.
|
||||
// deploymentUrls: {
|
||||
// // If specified a 'Help' button will be displayed in the overflow menu with a link to the specified URL for
|
||||
// // user documentation.
|
||||
// userDocumentationURL: 'https://docs.example.com/video-meetings.html',
|
||||
// // If specified a 'Download our apps' button will be displayed in the overflow menu with a link
|
||||
// // to the specified URL for an app download page.
|
||||
// downloadAppsUrl: 'https://docs.example.com/our-apps.html'
|
||||
// }
|
||||
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
|
||||
66
css/_mini_toolbox.scss
Normal file
66
css/_mini_toolbox.scss
Normal file
@@ -0,0 +1,66 @@
|
||||
.filmstrip-toolbox,
|
||||
.always-on-top-toolbox {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
z-index: $toolbarZ;
|
||||
|
||||
.toolbox-icon {
|
||||
cursor: pointer;
|
||||
padding: 7px;
|
||||
|
||||
&.toggled {
|
||||
background: $AOTToolbarButtonToggleColor;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.always-on-top-toolbox {
|
||||
flex-direction: row;
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
transform: translateX(-50%);
|
||||
|
||||
.toolbox-button {
|
||||
&:first-child {
|
||||
.toolbox-icon {
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
svg {
|
||||
fill: $hangupColor;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
.toolbox-icon {
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filmstrip-toolbox {
|
||||
flex-direction: column;
|
||||
|
||||
.toolbox-button {
|
||||
&:nth-child(1) {
|
||||
svg {
|
||||
fill: $hangupColor;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbox-icon {
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,89 +270,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.always-on-top-toolbox,
|
||||
.filmstrip-toolbox {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: $toolbarZ;
|
||||
|
||||
i {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
background-color: $AOTToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
i.toggled {
|
||||
background: $AOTToolbarButtonToggleColor;
|
||||
}
|
||||
|
||||
i.toggled:hover:not(.disabled) {
|
||||
background-color: $AOTToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
.toolbox-button {
|
||||
color: $toolbarButtonColor;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.always-on-top-toolbox {
|
||||
flex-direction: row;
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
transform: translateX(-50%);
|
||||
z-index: $toolbarZ;
|
||||
|
||||
i {
|
||||
font-size: $newToolbarFontSize;
|
||||
height: $newToolbarSize;
|
||||
line-height: $newToolbarSize;
|
||||
width: $newToolbarSize;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
.toolbox-button:first-child i {
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
|
||||
.toolbox-button:last-child i {
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.filmstrip-toolbox {
|
||||
i {
|
||||
font-size: 1.9em;
|
||||
height: 37px;
|
||||
line-height: 37px;
|
||||
width: 37px;
|
||||
}
|
||||
|
||||
.toolbox-button:first-child i {
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
|
||||
.toolbox-button:last-child i {
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* START of fade in animation for main toolbar
|
||||
*/
|
||||
|
||||
@@ -32,6 +32,7 @@ $flagsImagePath: "../images/";
|
||||
@import 'overlay/overlay';
|
||||
@import 'inlay';
|
||||
@import 'reload_overlay/reload_overlay';
|
||||
@import 'mini_toolbox';
|
||||
@import 'modals/desktop-picker/desktop-picker';
|
||||
@import 'modals/device-selection/device-selection';
|
||||
@import 'modals/dialog';
|
||||
|
||||
@@ -63,6 +63,8 @@
|
||||
width: -webkit-max-content;
|
||||
word-break: break-all;
|
||||
max-width: 400px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-dialog-dial-in {
|
||||
@@ -86,6 +88,15 @@
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.info-dialog-url-icon {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
|
||||
svg {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.info-dialog-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
@@ -214,4 +225,10 @@
|
||||
-moz-user-select: text;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
|
||||
.info-dialog-url-text-unselectable {
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,29 +19,37 @@ server {
|
||||
ssl_certificate_key /etc/jitsi/meet/jitsi-meet.example.com.key;
|
||||
|
||||
root /usr/share/jitsi-meet;
|
||||
ssi on;
|
||||
index index.html index.htm;
|
||||
error_page 404 /static/404.html;
|
||||
|
||||
location /config.js {
|
||||
location = /config.js {
|
||||
alias /etc/jitsi/meet/jitsi-meet.example.com-config.js;
|
||||
}
|
||||
|
||||
location /external_api.js {
|
||||
location = /external_api.js {
|
||||
alias /usr/share/jitsi-meet/libs/external_api.min.js;
|
||||
}
|
||||
|
||||
location ~ ^/([a-zA-Z0-9=\?]+)$ {
|
||||
rewrite ^/(.*)$ / break;
|
||||
}
|
||||
|
||||
location / {
|
||||
ssi on;
|
||||
#ensure all static content can always be found first
|
||||
location ~ ^/(libs|css|static|images|fonts|lang|sounds|connection_optimization|.well-known)/(.*)$
|
||||
{
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
alias /usr/share/jitsi-meet/$1/$2;
|
||||
}
|
||||
|
||||
# BOSH
|
||||
location /http-bind {
|
||||
location = /http-bind {
|
||||
proxy_pass http://localhost:5280/http-bind;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header Host $http_host;
|
||||
}
|
||||
|
||||
location ~ ^/([^?&:’“]+)$ {
|
||||
try_files $uri @root_path;
|
||||
}
|
||||
|
||||
location @root_path {
|
||||
rewrite ^/(.*)$ / break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ var interfaceConfig = {
|
||||
'fodeviceselection', 'hangup', 'profile', 'info', 'chat', 'recording',
|
||||
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
|
||||
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
|
||||
'tileview', 'videobackgroundblur'
|
||||
'tileview', 'videobackgroundblur', 'download', 'help'
|
||||
],
|
||||
|
||||
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ],
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#import "ReactUtils.h"
|
||||
|
||||
#import <RNGoogleSignin/RNGoogleSignin.h>
|
||||
#import <WebRTC/RTCLogging.h>
|
||||
|
||||
|
||||
@implementation JitsiMeet {
|
||||
@@ -54,6 +55,11 @@
|
||||
|
||||
// Register a log handler for React.
|
||||
registerReactLogHandler();
|
||||
|
||||
#if 0
|
||||
// Enable WebRTC logs
|
||||
RTCSetMinDebugLogLevel(RTCLoggingSeverityInfo);
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
|
||||
@@ -570,6 +570,7 @@
|
||||
"cc": "Toggle subtitles",
|
||||
"chat": "Toggle chat window",
|
||||
"document": "Toggle shared document",
|
||||
"download": "Download our apps",
|
||||
"feedback": "Leave feedback",
|
||||
"fullScreen": "Toggle full screen",
|
||||
"hangup": "Leave the call",
|
||||
@@ -609,6 +610,7 @@
|
||||
"closeChat": "Close chat",
|
||||
"documentClose": "Close shared document",
|
||||
"documentOpen": "Open shared document",
|
||||
"download": "Download our apps",
|
||||
"enterFullScreen": "View full screen",
|
||||
"enterTileView": "Enter tile view",
|
||||
"exitFullScreen": "Exit full screen",
|
||||
@@ -725,7 +727,7 @@
|
||||
"connectCalendarButton": "Connect your calendar",
|
||||
"connectCalendarText": "Connect your calendar to view all your meetings in {{app}}. Plus, add {{provider}} meetings to your calendar and start them with one click.",
|
||||
"enterRoomTitle": "Start a new meeting",
|
||||
"onlyAsciiAllowed": "Meeting name should only contain latin characters and numbers.",
|
||||
"roomNameAllowedChars": "Meeting name should not contain any of these characters: ?, &, :, ', \", %, #.",
|
||||
"go": "GO",
|
||||
"join": "JOIN",
|
||||
"info": "Info",
|
||||
|
||||
3042
package-lock.json
generated
3042
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -49,7 +49,7 @@
|
||||
"i18next-browser-languagedetector": "3.0.1",
|
||||
"i18next-xhr-backend": "3.0.0",
|
||||
"jQuery-Impromptu": "github:trentrichardson/jQuery-Impromptu#v6.0.0",
|
||||
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#a885cc98688ef2c3972284bda901596a26ffee52",
|
||||
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#5ec92357570dc8f0b7ffc1528820721c84c6af8b",
|
||||
"jquery": "3.4.0",
|
||||
"jquery-contextmenu": "2.4.5",
|
||||
"jquery-i18next": "1.2.1",
|
||||
@@ -57,7 +57,7 @@
|
||||
"js-utils": "github:jitsi/js-utils#192b1c996e8c05530eb1f19e82a31069c3021e31",
|
||||
"jsrsasign": "8.0.12",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#f9808adb8eb523bae3318f9f8ef49b544651485f",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#cdd2029b4b6b3626658f9834c324e1ffb7474795",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.13",
|
||||
"moment": "2.19.4",
|
||||
@@ -80,7 +80,7 @@
|
||||
"react-native-svg-transformer": "0.13.0",
|
||||
"react-native-swipeout": "2.3.6",
|
||||
"react-native-watch-connectivity": "0.2.0",
|
||||
"react-native-webrtc": "github:jitsi/react-native-webrtc#047b019a7ce1ec93ab4a2f6796e997d7a02e8e5d",
|
||||
"react-native-webrtc": "github:react-native-webrtc/react-native-webrtc#a12a6cdfdefe53d03b388394e4cf10966bd99fca",
|
||||
"react-native-webview": "7.4.1",
|
||||
"react-redux": "7.1.0",
|
||||
"react-textarea-autosize": "7.1.0",
|
||||
@@ -122,14 +122,14 @@
|
||||
"imports-loader": "0.7.1",
|
||||
"jetifier": "1.6.4",
|
||||
"metro-react-native-babel-preset": "0.56.0",
|
||||
"node-sass": "4.10.0",
|
||||
"node-sass": "4.12.0",
|
||||
"precommit-hook": "3.0.0",
|
||||
"string-replace-loader": "2.1.1",
|
||||
"style-loader": "0.19.0",
|
||||
"webpack": "4.26.1",
|
||||
"webpack": "4.27.1",
|
||||
"webpack-bundle-analyzer": "3.4.1",
|
||||
"webpack-cli": "3.1.2",
|
||||
"webpack-dev-server": "3.1.14"
|
||||
"webpack-dev-server": "3.8.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0",
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { JitsiConnectionErrors } from '../../base/lib-jitsi-meet';
|
||||
import { StyleType } from '../../base/styles';
|
||||
import type { StyleType } from '../../base/styles';
|
||||
|
||||
import { authenticateAndUpgradeRole, cancelLogin } from '../actions';
|
||||
import styles from './styles';
|
||||
|
||||
@@ -153,6 +153,8 @@ class ColorSchemeRegistry {
|
||||
const colorScheme = toState(stateful)['features/base/color-scheme'];
|
||||
|
||||
return {
|
||||
...defaultScheme._defaultTheme,
|
||||
...colorScheme._defaultTheme,
|
||||
...defaultScheme[componentName],
|
||||
...colorScheme[componentName]
|
||||
}[colorDefinition];
|
||||
|
||||
@@ -6,18 +6,26 @@ import { ColorPalette, getRGBAFormat } from '../styles';
|
||||
* The default color scheme of the application.
|
||||
*/
|
||||
export default {
|
||||
'BottomSheet': {
|
||||
'_defaultTheme': {
|
||||
// Generic app theme colors that are used accross the entire app.
|
||||
// All scheme definitions below inherit these values.
|
||||
background: 'rgb(255, 255, 255)',
|
||||
icon: '#1c2025',
|
||||
label: '#1c2025'
|
||||
icon: 'rgb(28, 32, 37)',
|
||||
text: 'rgb(28, 32, 37)'
|
||||
},
|
||||
'Chat': {
|
||||
displayName: 'rgb(94, 109, 121)',
|
||||
localMsgBackground: 'rgb(215, 230, 249)',
|
||||
privateMsgBackground: 'rgb(250, 219, 219)',
|
||||
privateMsgNotice: 'rgb(186, 39, 58)',
|
||||
remoteMsgBackground: 'rgb(241, 242, 246)',
|
||||
replyBorder: 'rgb(219, 197, 200)',
|
||||
replyIcon: 'rgb(94, 109, 121)'
|
||||
},
|
||||
'Dialog': {
|
||||
background: 'rgb(255, 255, 255)',
|
||||
border: 'rgba(0, 3, 6, 0.6)',
|
||||
buttonBackground: ColorPalette.blue,
|
||||
buttonLabel: ColorPalette.white,
|
||||
icon: '#1c2025',
|
||||
text: '#1c2025'
|
||||
buttonLabel: ColorPalette.white
|
||||
},
|
||||
'Header': {
|
||||
background: ColorPalette.blue,
|
||||
@@ -30,8 +38,7 @@ export default {
|
||||
background: 'rgb(42, 58, 75)'
|
||||
},
|
||||
'LoadConfigOverlay': {
|
||||
background: 'rgb(249, 249, 249)',
|
||||
text: 'rgb(28, 32, 37)'
|
||||
background: 'rgb(249, 249, 249)'
|
||||
},
|
||||
'Thumbnail': {
|
||||
activeParticipantHighlight: 'rgb(81, 214, 170)',
|
||||
|
||||
@@ -147,7 +147,7 @@ ColorSchemeRegistry.register('BottomSheet', {
|
||||
* Style for the label in a generic item rendered in the menu.
|
||||
*/
|
||||
labelStyle: {
|
||||
color: schemeColor('label'),
|
||||
color: schemeColor('text'),
|
||||
flexShrink: 1,
|
||||
fontSize: MD_FONT_SIZE,
|
||||
marginLeft: 32,
|
||||
|
||||
@@ -23,6 +23,11 @@ type Props = {
|
||||
*/
|
||||
id?: string,
|
||||
|
||||
/**
|
||||
* Function to invoke on click.
|
||||
*/
|
||||
onClick?: Function,
|
||||
|
||||
/**
|
||||
* The size of the icon (if not provided by the style object).
|
||||
*/
|
||||
@@ -53,6 +58,7 @@ export default function Icon(props: Props) {
|
||||
className,
|
||||
color,
|
||||
id,
|
||||
onClick,
|
||||
size,
|
||||
src: IconComponent,
|
||||
style
|
||||
@@ -69,6 +75,7 @@ export default function Icon(props: Props) {
|
||||
return (
|
||||
<Container
|
||||
className = { `jitsi-icon ${className}` }
|
||||
onClick = { onClick }
|
||||
style = { restStyle }>
|
||||
<IconComponent
|
||||
fill = { calculatedColor }
|
||||
|
||||
3
react/features/base/icons/svg/copy.svg
Normal file
3
react/features/base/icons/svg/copy.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 4C4 2.89543 4.89543 2 6 2H14C15.1046 2 16 2.89543 16 4H6V18C4.89543 18 4 17.1046 4 16V4ZM10 8V20H18V8H10ZM10 6H18C19.1046 6 20 6.89543 20 8V20C20 21.1046 19.1046 22 18 22H10C8.89543 22 8 21.1046 8 20V8C8 6.89543 8.89543 6 10 6Z" fill="#5E6D7A"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 401 B |
3
react/features/base/icons/svg/download.svg
Executable file
3
react/features/base/icons/svg/download.svg
Executable file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 13.3705V5C11 4.44772 11.4477 4 12 4C12.5523 4 13 4.44772 13 5V13.4667L15.631 10.5433C16.0005 10.1328 16.6328 10.0995 17.0433 10.469C17.4538 10.8384 17.4871 11.4707 17.1176 11.8812L12.1064 17.4492C12.0727 17.4867 12.0139 17.4867 11.9802 17.4492L6.96897 11.8812C6.59951 11.4707 6.63278 10.8384 7.04329 10.469C7.4538 10.0995 8.08609 10.1328 8.45555 10.5433L11 13.3705ZM20 15C20 14.4477 20.4477 14 21 14C21.5523 14 22 14.4477 22 15V21C22 21.5523 21.5523 22 21 22H3C2.96548 22 2.93137 21.9983 2.89776 21.9948C2.3935 21.9436 2 21.5178 2 21V15C2 14.4477 2.44772 14 3 14C3.55228 14 4 14.4477 4 15V20H20V15Z" fill="#A4B8D1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 773 B |
@@ -18,11 +18,13 @@ export { default as IconClose } from './close.svg';
|
||||
export { default as IconClosedCaption } from './closed_caption.svg';
|
||||
export { default as IconConnectionActive } from './gsm-bars.svg';
|
||||
export { default as IconConnectionInactive } from './ninja.svg';
|
||||
export { default as IconCopy } from './copy.svg';
|
||||
export { default as IconDeviceBluetooth } from './bluetooth.svg';
|
||||
export { default as IconDeviceEarpiece } from './phone-talk.svg';
|
||||
export { default as IconDeviceHeadphone } from './headset.svg';
|
||||
export { default as IconDeviceSpeaker } from './volume.svg';
|
||||
export { default as IconDominantSpeaker } from './dominant-speaker.svg';
|
||||
export { default as IconDownload } from './download.svg';
|
||||
export { default as IconEventNote } from './event_note.svg';
|
||||
export { default as IconExitFullScreen } from './exit-full-screen.svg';
|
||||
export { default as IconFeedback } from './feedback.svg';
|
||||
|
||||
@@ -63,13 +63,13 @@ export default class JitsiMeetLogStorage {
|
||||
for (let i = 0, len = logEntries.length; i < len; i++) {
|
||||
const logEntry = logEntries[i];
|
||||
|
||||
if (typeof logEntry === 'object') {
|
||||
// Aggregated message
|
||||
logMessage += `(${logEntry.count}) ${logEntry.text}\n`;
|
||||
} else {
|
||||
// Regular message
|
||||
logMessage += `${logEntry}\n`;
|
||||
if (logEntry.timestamp) {
|
||||
logMessage += `${logEntry.timestamp} `;
|
||||
}
|
||||
if (logEntry.count > 1) {
|
||||
logMessage += `(${logEntry.count}) `;
|
||||
}
|
||||
logMessage += `${logEntry.text}\n`;
|
||||
}
|
||||
logMessage += '"}';
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ function buildTransport() {
|
||||
'warn',
|
||||
'error'
|
||||
].reduce((logger, logName) => {
|
||||
logger[logName] = (...args: Array<string>) => {
|
||||
logger[logName] = (timestamp: string, ...args: Array<string>) => {
|
||||
// It ignores the timestamp argument, because LogBridge will add it on the native side anyway
|
||||
const nargs = args.map(arg => {
|
||||
if (arg instanceof Error) {
|
||||
const errorBody = {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from '../../media';
|
||||
import { Container, TintedView } from '../../react';
|
||||
import { connect } from '../../redux';
|
||||
import { StyleType } from '../../styles';
|
||||
import type { StyleType } from '../../styles';
|
||||
import { TestHint } from '../../testing/components';
|
||||
import { getTrackByMediaTypeAndParticipant } from '../../tracks';
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ export default class Linkify extends Component<Props> {
|
||||
return (
|
||||
<ReactLinkify
|
||||
componentDecorator = { this._componentDecorator }>
|
||||
<Text>
|
||||
<Text selectable = { true }>
|
||||
{ this.props.children }
|
||||
</Text>
|
||||
</ReactLinkify>
|
||||
|
||||
@@ -4,6 +4,8 @@ import { PureComponent } from 'react';
|
||||
|
||||
import { getLocalizedDateFormatter } from '../../base/i18n';
|
||||
|
||||
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../constants';
|
||||
|
||||
/**
|
||||
* Formatter string to display the message timestamp.
|
||||
*/
|
||||
@@ -65,7 +67,7 @@ export default class AbstractChatMessage<P: Props> extends PureComponent<P> {
|
||||
_getMessageText() {
|
||||
const { message } = this.props;
|
||||
|
||||
return message.messageType === 'error'
|
||||
return message.messageType === MESSAGE_TYPE_ERROR
|
||||
? this.props.t('chat.error', {
|
||||
error: message.message
|
||||
})
|
||||
@@ -81,7 +83,7 @@ export default class AbstractChatMessage<P: Props> extends PureComponent<P> {
|
||||
const { message, t } = this.props;
|
||||
|
||||
return t('chat.privateNotice', {
|
||||
recipient: message.messageType === 'local' ? message.recipient : t('chat.you')
|
||||
recipient: message.messageType === MESSAGE_TYPE_LOCAL ? message.recipient : t('chat.you')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getParticipantDisplayName } from '../../base/participants';
|
||||
|
||||
import { setPrivateMessageRecipient } from '../actions';
|
||||
|
||||
type Props = {
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* Function used to translate i18n labels.
|
||||
@@ -27,7 +27,7 @@ type Props = {
|
||||
/**
|
||||
* Abstract class for the {@code MessageRecipient} component.
|
||||
*/
|
||||
export default class AbstractMessageRecipient extends PureComponent<Props> {
|
||||
export default class AbstractMessageRecipient<P: Props> extends PureComponent<P> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
import React from 'react';
|
||||
import { KeyboardAvoidingView, SafeAreaView } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
import { HeaderWithNavigation, SlidingView } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { StyleType } from '../../../base/styles';
|
||||
|
||||
import AbstractChat, {
|
||||
_mapDispatchToProps,
|
||||
_mapStateToProps,
|
||||
type Props
|
||||
_mapStateToProps as _abstractMapStateToProps,
|
||||
type Props as AbstractProps
|
||||
} from '../AbstractChat';
|
||||
|
||||
import ChatInputBar from './ChatInputBar';
|
||||
@@ -19,6 +20,14 @@ import MessageContainer from './MessageContainer';
|
||||
import MessageRecipient from './MessageRecipient';
|
||||
import styles from './styles';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: StyleType
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React native component that renders the chat window (modal) of
|
||||
* the mobile client.
|
||||
@@ -41,6 +50,8 @@ class Chat extends AbstractChat<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _styles } = this.props;
|
||||
|
||||
return (
|
||||
<SlidingView
|
||||
onHide = { this._onClose }
|
||||
@@ -52,7 +63,7 @@ class Chat extends AbstractChat<Props> {
|
||||
<HeaderWithNavigation
|
||||
headerLabelKey = 'chat.title'
|
||||
onPressBack = { this._onClose } />
|
||||
<SafeAreaView style = { styles.backdrop }>
|
||||
<SafeAreaView style = { _styles.backdrop }>
|
||||
<MessageContainer messages = { this.props._messages } />
|
||||
<MessageRecipient />
|
||||
<ChatInputBar onSend = { this.props._onSendMessage } />
|
||||
@@ -80,4 +91,17 @@ class Chat extends AbstractChat<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
..._abstractMapStateToProps(state),
|
||||
_styles: ColorSchemeRegistry.get(state, 'Chat')
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat));
|
||||
|
||||
@@ -4,16 +4,28 @@ import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Linkify } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { type StyleType } from '../../../base/styles';
|
||||
|
||||
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
|
||||
import { replaceNonUnicodeEmojis } from '../../functions';
|
||||
|
||||
import AbstractChatMessage, { type Props } from '../AbstractChatMessage';
|
||||
import AbstractChatMessage, { type Props as AbstractProps } from '../AbstractChatMessage';
|
||||
import PrivateMessageButton from '../PrivateMessageButton';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: StyleType
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
*/
|
||||
@@ -24,55 +36,58 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { message } = this.props;
|
||||
const localMessage = message.messageType === 'local';
|
||||
const { _styles, message } = this.props;
|
||||
const localMessage = message.messageType === MESSAGE_TYPE_LOCAL;
|
||||
const { privateMessage } = message;
|
||||
|
||||
// Style arrays that need to be updated in various scenarios, such as
|
||||
// error messages or others.
|
||||
const detailsWrapperStyle = [
|
||||
styles.detailsWrapper
|
||||
];
|
||||
const textWrapperStyle = [
|
||||
styles.textWrapper
|
||||
const messageBubbleStyle = [
|
||||
styles.messageBubble
|
||||
];
|
||||
|
||||
if (localMessage) {
|
||||
// This is a message sent by the local participant.
|
||||
|
||||
// The wrapper needs to be aligned to the right.
|
||||
detailsWrapperStyle.push(styles.ownMessageDetailsWrapper);
|
||||
|
||||
// The bubble needs to be differently styled.
|
||||
textWrapperStyle.push(styles.ownTextWrapper);
|
||||
} else if (message.messageType === 'error') {
|
||||
// The bubble needs to be differently styled.
|
||||
textWrapperStyle.push(styles.systemTextWrapper);
|
||||
// The bubble needs some additional styling
|
||||
messageBubbleStyle.push(_styles.localMessageBubble);
|
||||
} else if (message.messageType === MESSAGE_TYPE_ERROR) {
|
||||
// This is a system message.
|
||||
|
||||
// The bubble needs some additional styling
|
||||
messageBubbleStyle.push(styles.systemMessageBubble);
|
||||
} else {
|
||||
// This is a remote message sent by a remote participant.
|
||||
|
||||
// The bubble needs some additional styling
|
||||
messageBubbleStyle.push(_styles.remoteMessageBubble);
|
||||
}
|
||||
|
||||
if (privateMessage) {
|
||||
messageBubbleStyle.push(_styles.privateMessageBubble);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { styles.messageWrapper } >
|
||||
{ this._renderAvatar() }
|
||||
<View style = { detailsWrapperStyle }>
|
||||
<View style = { styles.replyWrapper }>
|
||||
<View style = { textWrapperStyle } >
|
||||
{
|
||||
this.props.showDisplayName
|
||||
&& this._renderDisplayName()
|
||||
}
|
||||
<View style = { messageBubbleStyle }>
|
||||
<View style = { styles.textWrapper } >
|
||||
{ this._renderDisplayName() }
|
||||
<Linkify linkStyle = { styles.chatLink }>
|
||||
{ replaceNonUnicodeEmojis(this._getMessageText()) }
|
||||
</Linkify>
|
||||
{
|
||||
message.privateMessage
|
||||
&& this._renderPrivateNotice()
|
||||
}
|
||||
{ this._renderPrivateNotice() }
|
||||
</View>
|
||||
{ message.privateMessage && !localMessage
|
||||
&& <PrivateMessageButton
|
||||
participantID = { message.id }
|
||||
reply = { true }
|
||||
showLabel = { false }
|
||||
toggledStyles = { styles.replyStyles } /> }
|
||||
{ this._renderPrivateReplyButton() }
|
||||
</View>
|
||||
{ this.props.showTimestamp && this._renderTimestamp() }
|
||||
{ this._renderTimestamp() }
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@@ -104,37 +119,77 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the display name of the sender.
|
||||
* Renders the display name of the sender if necessary.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
* @returns {React$Element<*> | null}
|
||||
*/
|
||||
_renderDisplayName() {
|
||||
const { _styles, message, showDisplayName } = this.props;
|
||||
|
||||
if (!showDisplayName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style = { styles.displayName }>
|
||||
{ this.props.message.displayName }
|
||||
<Text style = { _styles.displayName }>
|
||||
{ message.displayName }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the message privacy notice.
|
||||
* Renders the message privacy notice, if necessary.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
* @returns {React$Element<*> | null}
|
||||
*/
|
||||
_renderPrivateNotice() {
|
||||
const { _styles, message } = this.props;
|
||||
|
||||
if (!message.privateMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style = { styles.privateNotice }>
|
||||
<Text style = { _styles.privateNotice }>
|
||||
{ this._getPrivateNoticeMessage() }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the time at which the message was sent.
|
||||
* Renders the private reply button, if necessary.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
* @returns {React$Element<*> | null}
|
||||
*/
|
||||
_renderPrivateReplyButton() {
|
||||
const { _styles, message } = this.props;
|
||||
const { messageType, privateMessage } = message;
|
||||
|
||||
if (!privateMessage || messageType === MESSAGE_TYPE_LOCAL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { _styles.replyContainer }>
|
||||
<PrivateMessageButton
|
||||
participantID = { message.id }
|
||||
reply = { true }
|
||||
showLabel = { false }
|
||||
toggledStyles = { _styles.replyStyles } />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the time at which the message was sent, if necessary.
|
||||
*
|
||||
* @returns {React$Element<*> | null}
|
||||
*/
|
||||
_renderTimestamp() {
|
||||
if (!this.props.showTimestamp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style = { styles.timeText }>
|
||||
{ this._getFormattedTimestamp() }
|
||||
@@ -143,4 +198,16 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(ChatMessage);
|
||||
/**
|
||||
* Maps part of the redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_styles: ColorSchemeRegistry.get(state, 'Chat')
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(ChatMessage));
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import React, { Component } from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
|
||||
import { MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from '../../constants';
|
||||
|
||||
import ChatMessage from './ChatMessage';
|
||||
import styles from './styles';
|
||||
|
||||
@@ -73,11 +75,11 @@ export default class ChatMessageGroup extends Component<Props> {
|
||||
<ChatMessage
|
||||
message = { message }
|
||||
showAvatar = {
|
||||
this.props.messages[0].messageType !== 'local'
|
||||
this.props.messages[0].messageType !== MESSAGE_TYPE_LOCAL
|
||||
&& index === this.props.messages.length - 1
|
||||
}
|
||||
showDisplayName = {
|
||||
this.props.messages[0].messageType === 'remote'
|
||||
this.props.messages[0].messageType === MESSAGE_TYPE_REMOTE
|
||||
&& index === this.props.messages.length - 1
|
||||
}
|
||||
showTimestamp = { index === 0 } />
|
||||
|
||||
@@ -3,28 +3,37 @@
|
||||
import React from 'react';
|
||||
import { Text, TouchableHighlight, View } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconCancelSelection } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { type StyleType } from '../../../base/styles';
|
||||
|
||||
import AbstractMessageRecipient, {
|
||||
_mapDispatchToProps,
|
||||
_mapStateToProps
|
||||
_mapStateToProps as _abstractMapStateToProps,
|
||||
type Props as AbstractProps
|
||||
} from '../AbstractMessageRecipient';
|
||||
|
||||
import styles from './styles';
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: StyleType
|
||||
};
|
||||
|
||||
/**
|
||||
* Class to implement the displaying of the recipient of the next message.
|
||||
*/
|
||||
class MessageRecipient extends AbstractMessageRecipient {
|
||||
class MessageRecipient extends AbstractMessageRecipient<Props> {
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _privateMessageRecipient } = this.props;
|
||||
const { _privateMessageRecipient, _styles } = this.props;
|
||||
|
||||
if (!_privateMessageRecipient) {
|
||||
return null;
|
||||
@@ -33,8 +42,8 @@ class MessageRecipient extends AbstractMessageRecipient {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { styles.messageRecipientContainer }>
|
||||
<Text style = { styles.messageRecipientText }>
|
||||
<View style = { _styles.messageRecipientContainer }>
|
||||
<Text style = { _styles.messageRecipientText }>
|
||||
{ t('chat.messageTo', {
|
||||
recipient: _privateMessageRecipient
|
||||
}) }
|
||||
@@ -42,11 +51,24 @@ class MessageRecipient extends AbstractMessageRecipient {
|
||||
<TouchableHighlight onPress = { this.props._onRemovePrivateMessageRecipient }>
|
||||
<Icon
|
||||
src = { IconCancelSelection }
|
||||
style = { styles.messageRecipientCancelIcon } />
|
||||
style = { _styles.messageRecipientCancelIcon } />
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
..._abstractMapStateToProps(state),
|
||||
_styles: ColorSchemeRegistry.get(state, 'Chat')
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(MessageRecipient));
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
|
||||
import { BoxModel, ColorPalette } from '../../../base/styles';
|
||||
|
||||
const BUBBLE_RADIUS = 8;
|
||||
|
||||
/**
|
||||
* The styles of the feature chat.
|
||||
*
|
||||
@@ -20,14 +23,6 @@ export default {
|
||||
width: 32
|
||||
},
|
||||
|
||||
/**
|
||||
* Background of the chat screen.
|
||||
*/
|
||||
backdrop: {
|
||||
backgroundColor: ColorPalette.white,
|
||||
flex: 1
|
||||
},
|
||||
|
||||
chatContainer: {
|
||||
alignItems: 'stretch',
|
||||
flex: 1,
|
||||
@@ -47,14 +42,6 @@ export default {
|
||||
flexDirection: 'column'
|
||||
},
|
||||
|
||||
/**
|
||||
* The text node for the display name.
|
||||
*/
|
||||
displayName: {
|
||||
color: 'rgb(118, 136, 152)',
|
||||
fontSize: 13
|
||||
},
|
||||
|
||||
/**
|
||||
* A special padding to avoid issues on some devices (such as Android devices with custom suggestions bar).
|
||||
*/
|
||||
@@ -76,35 +63,16 @@ export default {
|
||||
height: 48
|
||||
},
|
||||
|
||||
messageBubble: {
|
||||
alignItems: 'center',
|
||||
borderRadius: BUBBLE_RADIUS,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
messageContainer: {
|
||||
flex: 1
|
||||
},
|
||||
|
||||
messageRecipientCancelIcon: {
|
||||
color: ColorPalette.white,
|
||||
fontSize: 18
|
||||
},
|
||||
|
||||
messageRecipientContainer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: ColorPalette.warning,
|
||||
flexDirection: 'row',
|
||||
padding: BoxModel.padding
|
||||
},
|
||||
|
||||
messageRecipientText: {
|
||||
color: ColorPalette.white,
|
||||
flex: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* The message text itself.
|
||||
*/
|
||||
messageText: {
|
||||
color: 'rgb(28, 32, 37)',
|
||||
fontSize: 15
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrapper View for the entire block.
|
||||
*/
|
||||
@@ -123,34 +91,11 @@ export default {
|
||||
alignItems: 'flex-end'
|
||||
},
|
||||
|
||||
/**
|
||||
* Style modifier for the {@code textWrapper} for own messages.
|
||||
*/
|
||||
ownTextWrapper: {
|
||||
backgroundColor: 'rgb(210, 231, 249)',
|
||||
borderTopLeftRadius: 8,
|
||||
borderTopRightRadius: 0
|
||||
},
|
||||
|
||||
replyWrapper: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
replyStyles: {
|
||||
iconStyle: {
|
||||
color: 'rgb(118, 136, 152)',
|
||||
fontSize: 22,
|
||||
margin: BoxModel.margin / 2
|
||||
}
|
||||
},
|
||||
|
||||
privateNotice: {
|
||||
color: ColorPalette.warning,
|
||||
fontSize: 13,
|
||||
fontStyle: 'italic'
|
||||
},
|
||||
|
||||
sendButtonIcon: {
|
||||
color: ColorPalette.darkGrey,
|
||||
fontSize: 22
|
||||
@@ -159,7 +104,7 @@ export default {
|
||||
/**
|
||||
* Style modifier for system (error) messages.
|
||||
*/
|
||||
systemTextWrapper: {
|
||||
systemMessageBubble: {
|
||||
backgroundColor: 'rgb(247, 215, 215)'
|
||||
},
|
||||
|
||||
@@ -168,9 +113,6 @@ export default {
|
||||
*/
|
||||
textWrapper: {
|
||||
alignItems: 'flex-start',
|
||||
backgroundColor: 'rgb(240, 243, 247)',
|
||||
borderRadius: 8,
|
||||
borderTopLeftRadius: 0,
|
||||
flexDirection: 'column',
|
||||
padding: 9
|
||||
},
|
||||
@@ -183,3 +125,73 @@ export default {
|
||||
fontSize: 13
|
||||
}
|
||||
};
|
||||
|
||||
ColorSchemeRegistry.register('Chat', {
|
||||
/**
|
||||
* Background of the chat screen.
|
||||
*/
|
||||
backdrop: {
|
||||
backgroundColor: schemeColor('background'),
|
||||
flex: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* The text node for the display name.
|
||||
*/
|
||||
displayName: {
|
||||
color: schemeColor('displayName'),
|
||||
fontSize: 13
|
||||
},
|
||||
|
||||
localMessageBubble: {
|
||||
backgroundColor: schemeColor('localMsgBackground'),
|
||||
borderTopRightRadius: 0
|
||||
},
|
||||
|
||||
messageRecipientCancelIcon: {
|
||||
color: schemeColor('icon'),
|
||||
fontSize: 18
|
||||
},
|
||||
|
||||
messageRecipientContainer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: schemeColor('privateMsgBackground'),
|
||||
flexDirection: 'row',
|
||||
padding: BoxModel.padding
|
||||
},
|
||||
|
||||
messageRecipientText: {
|
||||
color: schemeColor('text'),
|
||||
flex: 1
|
||||
},
|
||||
|
||||
privateNotice: {
|
||||
color: schemeColor('privateMsgNotice'),
|
||||
fontSize: 11,
|
||||
marginTop: 6
|
||||
},
|
||||
|
||||
privateMessageBubble: {
|
||||
backgroundColor: schemeColor('privateMsgBackground')
|
||||
},
|
||||
|
||||
remoteMessageBubble: {
|
||||
backgroundColor: schemeColor('remoteMsgBackground'),
|
||||
borderTopLeftRadius: 0
|
||||
},
|
||||
|
||||
replyContainer: {
|
||||
alignSelf: 'stretch',
|
||||
borderLeftColor: schemeColor('replyBorder'),
|
||||
borderLeftWidth: 1,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
replyStyles: {
|
||||
iconStyle: {
|
||||
color: schemeColor('replyIcon'),
|
||||
fontSize: 22,
|
||||
padding: 8
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,6 +7,8 @@ import { toArray } from 'react-emoji-render';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Linkify } from '../../../base/react';
|
||||
|
||||
import { MESSAGE_TYPE_LOCAL } from '../../constants';
|
||||
|
||||
import AbstractChatMessage, {
|
||||
type Props
|
||||
} from '../AbstractChatMessage';
|
||||
@@ -47,7 +49,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
</div>
|
||||
{ message.privateMessage && this._renderPrivateNotice() }
|
||||
</div>
|
||||
{ message.privateMessage && message.messageType !== 'local'
|
||||
{ message.privateMessage && message.messageType !== MESSAGE_TYPE_LOCAL
|
||||
&& <PrivateMessageButton
|
||||
participantID = { message.id }
|
||||
reply = { true }
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { MESSAGE_TYPE_REMOTE } from '../../constants';
|
||||
|
||||
import AbstractMessageContainer, { type Props }
|
||||
from '../AbstractMessageContainer';
|
||||
|
||||
@@ -61,7 +63,7 @@ export default class MessageContainer extends AbstractMessageContainer {
|
||||
|
||||
return (
|
||||
<ChatMessageGroup
|
||||
className = { messageType || 'remote' }
|
||||
className = { messageType || MESSAGE_TYPE_REMOTE }
|
||||
key = { index }
|
||||
messages = { group } />
|
||||
);
|
||||
|
||||
@@ -8,13 +8,14 @@ import { connect } from '../../../base/redux';
|
||||
|
||||
import AbstractMessageRecipient, {
|
||||
_mapDispatchToProps,
|
||||
_mapStateToProps
|
||||
_mapStateToProps,
|
||||
type Props
|
||||
} from '../AbstractMessageRecipient';
|
||||
|
||||
/**
|
||||
* Class to implement the displaying of the recipient of the next message.
|
||||
*/
|
||||
class MessageRecipient extends AbstractMessageRecipient {
|
||||
class MessageRecipient extends AbstractMessageRecipient<Props> {
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* The audio ID of the audio element for which the {@link playAudio} action is
|
||||
* triggered when new chat message is received.
|
||||
@@ -5,3 +7,18 @@
|
||||
* @type {string}
|
||||
*/
|
||||
export const INCOMING_MSG_SOUND_ID = 'INCOMING_MSG_SOUND';
|
||||
|
||||
/**
|
||||
* The {@code messageType} of error (system) messages.
|
||||
*/
|
||||
export const MESSAGE_TYPE_ERROR = 'error';
|
||||
|
||||
/**
|
||||
* The {@code messageType} of local messages.
|
||||
*/
|
||||
export const MESSAGE_TYPE_LOCAL = 'local';
|
||||
|
||||
/**
|
||||
* The {@code messageType} of remote messages.
|
||||
*/
|
||||
export const MESSAGE_TYPE_REMOTE = 'remote';
|
||||
|
||||
@@ -22,7 +22,7 @@ import { isButtonEnabled, showToolbox } from '../toolbox';
|
||||
import { SEND_MESSAGE, SET_PRIVATE_MESSAGE_RECIPIENT } from './actionTypes';
|
||||
import { addMessage, clearMessages, toggleChat } from './actions';
|
||||
import { ChatPrivacyDialog } from './components';
|
||||
import { INCOMING_MSG_SOUND_ID } from './constants';
|
||||
import { INCOMING_MSG_SOUND_ID, MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from './constants';
|
||||
import { INCOMING_MSG_SOUND_FILE } from './sounds';
|
||||
|
||||
declare var APP: Object;
|
||||
@@ -194,7 +194,7 @@ function _addChatMsgListener(conference, store) {
|
||||
function _handleChatError({ dispatch }, error) {
|
||||
dispatch(addMessage({
|
||||
hasRead: true,
|
||||
messageType: 'error',
|
||||
messageType: MESSAGE_TYPE_ERROR,
|
||||
message: error,
|
||||
privateMessage: false,
|
||||
timestamp: Date.now()
|
||||
@@ -231,7 +231,7 @@ function _handleReceivedMessage({ dispatch, getState }, { id, message, nick, pri
|
||||
displayName,
|
||||
hasRead,
|
||||
id,
|
||||
messageType: participant.local ? 'local' : 'remote',
|
||||
messageType: participant.local ? MESSAGE_TYPE_LOCAL : MESSAGE_TYPE_REMOTE,
|
||||
message,
|
||||
privateMessage,
|
||||
recipient: getParticipantDisplayName(state, localParticipant.id),
|
||||
@@ -285,7 +285,7 @@ function _persistSentPrivateMessage({ dispatch, getState }, recipientID, message
|
||||
displayName,
|
||||
hasRead: true,
|
||||
id: localParticipant.id,
|
||||
messageType: 'local',
|
||||
messageType: MESSAGE_TYPE_LOCAL,
|
||||
message,
|
||||
privateMessage: true,
|
||||
recipient: getParticipantDisplayName(getState, recipientID),
|
||||
@@ -323,7 +323,7 @@ function _shouldSendPrivateMessageTo(state, action): ?string {
|
||||
const lastMessage = navigator.product === 'ReactNative'
|
||||
? messages[0] : messages[messages.length - 1];
|
||||
|
||||
if (lastMessage.messageType === 'local') {
|
||||
if (lastMessage.messageType === MESSAGE_TYPE_LOCAL) {
|
||||
// The sender is probably aware of any private messages as already sent
|
||||
// a message since then. Doesn't make sense to display the notice now.
|
||||
return undefined;
|
||||
@@ -339,7 +339,7 @@ function _shouldSendPrivateMessageTo(state, action): ?string {
|
||||
const now = Date.now();
|
||||
const recentPrivateMessages = messages.filter(
|
||||
message =>
|
||||
message.messageType !== 'local'
|
||||
message.messageType !== MESSAGE_TYPE_LOCAL
|
||||
&& message.privateMessage
|
||||
&& message.timestamp + PRIVACY_NOTICE_TIMEOUT > now);
|
||||
const recentPrivateMessage = navigator.product === 'ReactNative'
|
||||
|
||||
@@ -40,12 +40,12 @@ class Toolbar extends Component<Props> {
|
||||
<div
|
||||
className = 'filmstrip-toolbox'
|
||||
id = 'new-toolbox'>
|
||||
<AudioMuteButton
|
||||
tooltipPosition = 'left'
|
||||
visible = { this._shouldShowButton('microphone') } />
|
||||
<HangupButton
|
||||
tooltipPosition = 'left'
|
||||
visible = { this._shouldShowButton('hangup') } />
|
||||
<AudioMuteButton
|
||||
tooltipPosition = 'left'
|
||||
visible = { this._shouldShowButton('microphone') } />
|
||||
<VideoMuteButton
|
||||
tooltipPosition = 'left'
|
||||
visible = { this._shouldShowButton('camera') } />
|
||||
|
||||
@@ -139,7 +139,6 @@ function _onFollowMeCommand(attributes = {}, id, store) {
|
||||
// For now gate etherpad checks behind a web-app check to be extra safe
|
||||
// against calling a web-app global.
|
||||
if (typeof APP !== 'undefined'
|
||||
&& state['features/etherpad'].initialized
|
||||
&& oldState.sharedDocumentVisible !== attributes.sharedDocumentVisible) {
|
||||
const isEtherpadVisible = attributes.sharedDocumentVisible === 'true';
|
||||
const documentManager = APP.UI.getSharedDocumentManager();
|
||||
|
||||
@@ -7,7 +7,7 @@ import { setPassword } from '../../../../base/conference';
|
||||
import { getInviteURL } from '../../../../base/connection';
|
||||
import { Dialog } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { Icon, IconInfo } from '../../../../base/icons';
|
||||
import { Icon, IconInfo, IconCopy } from '../../../../base/icons';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import {
|
||||
isLocalParticipantModerator,
|
||||
@@ -136,6 +136,7 @@ type State = {
|
||||
*/
|
||||
class InfoDialog extends Component<Props, State> {
|
||||
_copyElement: ?Object;
|
||||
_copyUrlElement: ?Object;
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#getDerivedStateFromProps()}.
|
||||
@@ -197,12 +198,14 @@ class InfoDialog extends Component<Props, State> {
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onClickURLText = this._onClickURLText.bind(this);
|
||||
this._onCopyInviteURL = this._onCopyInviteURL.bind(this);
|
||||
this._onCopyInviteInfo = this._onCopyInviteInfo.bind(this);
|
||||
this._onCopyInviteUrl = this._onCopyInviteUrl.bind(this);
|
||||
this._onPasswordRemove = this._onPasswordRemove.bind(this);
|
||||
this._onPasswordSubmit = this._onPasswordSubmit.bind(this);
|
||||
this._onTogglePasswordEditState
|
||||
= this._onTogglePasswordEditState.bind(this);
|
||||
this._setCopyElement = this._setCopyElement.bind(this);
|
||||
this._setCopyUrlElement = this._setCopyUrlElement.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,12 +242,18 @@ class InfoDialog extends Component<Props, State> {
|
||||
<span className = 'spacer'> </span>
|
||||
<span className = 'info-value'>
|
||||
<a
|
||||
className = 'info-dialog-url-text'
|
||||
className = 'info-dialog-url-text info-dialog-url-text-unselectable'
|
||||
href = { this.props._inviteURL }
|
||||
onClick = { this._onClickURLText } >
|
||||
{ decodeURI(this._getURLToDisplay()) }
|
||||
</a>
|
||||
</span>
|
||||
<span className = 'info-dialog-url-icon'>
|
||||
<Icon
|
||||
onClick = { this._onCopyInviteUrl }
|
||||
size = { 18 }
|
||||
src = { IconCopy } />
|
||||
</span>
|
||||
</div>
|
||||
<div className = 'info-dialog-dial-in'>
|
||||
{ this._renderDialInDisplay() }
|
||||
@@ -262,7 +271,7 @@ class InfoDialog extends Component<Props, State> {
|
||||
<div className = 'info-dialog-action-link'>
|
||||
<a
|
||||
className = 'info-copy'
|
||||
onClick = { this._onCopyInviteURL }>
|
||||
onClick = { this._onCopyInviteInfo }>
|
||||
{ t('dialog.copy') }
|
||||
</a>
|
||||
</div>
|
||||
@@ -275,6 +284,12 @@ class InfoDialog extends Component<Props, State> {
|
||||
ref = { this._setCopyElement }
|
||||
tabIndex = '-1'
|
||||
value = { this._getTextToCopy() } />
|
||||
<textarea
|
||||
className = 'info-dialog-copy-element'
|
||||
readOnly = { true }
|
||||
ref = { this._setCopyUrlElement }
|
||||
tabIndex = '-1'
|
||||
value = { this.props._inviteURL } />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -365,7 +380,7 @@ class InfoDialog extends Component<Props, State> {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
_onCopyInviteURL: () => void;
|
||||
_onCopyInviteInfo: () => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to copy the contents of {@code this._copyElement} to the
|
||||
@@ -374,7 +389,7 @@ class InfoDialog extends Component<Props, State> {
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCopyInviteURL() {
|
||||
_onCopyInviteInfo() {
|
||||
try {
|
||||
if (!this._copyElement) {
|
||||
throw new Error('No element to copy from.');
|
||||
@@ -388,6 +403,28 @@ class InfoDialog extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
_onCopyInviteUrl: () => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to copy the contents of {@code this._copyUrlElement} to the clipboard.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCopyInviteUrl() {
|
||||
try {
|
||||
if (!this._copyUrlElement) {
|
||||
throw new Error('No element to copy from.');
|
||||
}
|
||||
|
||||
this._copyUrlElement && this._copyUrlElement.select();
|
||||
document.execCommand('copy');
|
||||
this._copyUrlElement && this._copyUrlElement.blur();
|
||||
} catch (err) {
|
||||
logger.error('error when copying the text', err);
|
||||
}
|
||||
}
|
||||
|
||||
_onPasswordRemove: () => void;
|
||||
|
||||
/**
|
||||
@@ -565,6 +602,21 @@ class InfoDialog extends Component<Props, State> {
|
||||
_setCopyElement(element: Object) {
|
||||
this._copyElement = element;
|
||||
}
|
||||
|
||||
_setCopyUrlElement: () => void;
|
||||
|
||||
/**
|
||||
* Sets the internal reference to the DOM/HTML element backing the React
|
||||
* {@code Component} input.
|
||||
*
|
||||
* @param {HTMLInputElement} element - The DOM/HTML element for this
|
||||
* {@code Component}'s input.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setCopyUrlElement(element: Object) {
|
||||
this._copyUrlElement = element;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -577,5 +577,12 @@ export function _decodeRoomURI(url: string) {
|
||||
roomUrl = decodeURI(roomUrl);
|
||||
}
|
||||
|
||||
// Handles a special case where the room name has % encoded, the decoded will have
|
||||
// % followed by a char (non-digit) which is not a valid URL and room name ... so we do not
|
||||
// want to show this decoded
|
||||
if (roomUrl.match(/.*%[^\d].*/)) {
|
||||
return url;
|
||||
}
|
||||
|
||||
return roomUrl;
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* The type of redux action to add a network request to the redux store/state.
|
||||
*
|
||||
* {
|
||||
* type: _ADD_NETWORK_REQUEST,
|
||||
* request: Object
|
||||
* }
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
export const _ADD_NETWORK_REQUEST = '_ADD_NETWORK_REQUEST';
|
||||
|
||||
/**
|
||||
* The type of redux action to remove all network requests from the redux
|
||||
* store/state.
|
||||
*
|
||||
* {
|
||||
* type: _REMOVE_ALL_NETWORK_REQUESTS,
|
||||
* }
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
export const _REMOVE_ALL_NETWORK_REQUESTS
|
||||
= '_REMOVE_ALL_NETWORK_REQUESTS';
|
||||
|
||||
/**
|
||||
* The type of redux action to remove a network request from the redux
|
||||
* store/state.
|
||||
*
|
||||
* {
|
||||
* type: _REMOVE_NETWORK_REQUEST,
|
||||
* request: Object
|
||||
* }
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
export const _REMOVE_NETWORK_REQUEST = '_REMOVE_NETWORK_REQUEST';
|
||||
@@ -1,55 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { LoadingIndicator } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link NetworkActivityIndicator}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Indicates whether there is network activity i.e. ongoing network
|
||||
* requests.
|
||||
*/
|
||||
_networkActivity: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* The React {@code Component} which renders a progress indicator when there
|
||||
* are ongoing network requests.
|
||||
*/
|
||||
class NetworkActivityIndicator extends Component<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return this.props._networkActivity ? <LoadingIndicator /> : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the React {@code Component} props of
|
||||
* {@code NetworkActivityIndicator}.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _networkActivity: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { requests } = state['features/network-activity'];
|
||||
|
||||
return {
|
||||
_networkActivity: Boolean(requests && requests.size)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(NetworkActivityIndicator);
|
||||
@@ -1,3 +0,0 @@
|
||||
export {
|
||||
default as NetworkActivityIndicator
|
||||
} from './NetworkActivityIndicator';
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from './components';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
@@ -1,81 +0,0 @@
|
||||
/* @flow */
|
||||
|
||||
import XHRInterceptor from 'react-native/Libraries/Network/XHRInterceptor';
|
||||
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app';
|
||||
import { MiddlewareRegistry } from '../../base/redux';
|
||||
|
||||
import {
|
||||
_ADD_NETWORK_REQUEST,
|
||||
_REMOVE_ALL_NETWORK_REQUESTS,
|
||||
_REMOVE_NETWORK_REQUEST
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Middleware which captures app startup and conference actions in order to
|
||||
* clear the image cache.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
case APP_WILL_MOUNT:
|
||||
_startNetInterception(store);
|
||||
break;
|
||||
|
||||
case APP_WILL_UNMOUNT:
|
||||
_stopNetInterception(store);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* Starts intercepting network requests. Only XHR requests are supported at the
|
||||
* moment.
|
||||
*
|
||||
* Ongoing request information is kept in redux, and it's removed as soon as a
|
||||
* given request is complete.
|
||||
*
|
||||
* @param {Object} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _startNetInterception({ dispatch }) {
|
||||
XHRInterceptor.setOpenCallback((method, url, xhr) => dispatch({
|
||||
type: _ADD_NETWORK_REQUEST,
|
||||
request: xhr,
|
||||
|
||||
// The following are not really necessary read anywhere at the time of
|
||||
// this writing but are provided anyway if the reducer chooses to
|
||||
// remember them:
|
||||
method,
|
||||
url
|
||||
}));
|
||||
XHRInterceptor.setResponseCallback((...args) => dispatch({
|
||||
type: _REMOVE_NETWORK_REQUEST,
|
||||
|
||||
// XXX The XHR is the last argument of the responseCallback.
|
||||
request: args[args.length - 1]
|
||||
}));
|
||||
|
||||
XHRInterceptor.enableInterception();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops intercepting network requests.
|
||||
*
|
||||
* @param {Object} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _stopNetInterception({ dispatch }) {
|
||||
XHRInterceptor.disableInterception();
|
||||
|
||||
dispatch({
|
||||
type: _REMOVE_ALL_NETWORK_REQUESTS
|
||||
});
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { ReducerRegistry, set } from '../../base/redux';
|
||||
|
||||
import {
|
||||
_ADD_NETWORK_REQUEST,
|
||||
_REMOVE_ALL_NETWORK_REQUESTS,
|
||||
_REMOVE_NETWORK_REQUEST
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* The default/initial redux state of the feature network-activity.
|
||||
*
|
||||
* @type {{
|
||||
* requests: Map
|
||||
* }}
|
||||
*/
|
||||
const DEFAULT_STATE = {
|
||||
/**
|
||||
* The ongoing network requests i.e. the network request which have been
|
||||
* added to the redux store/state and have not been removed.
|
||||
*
|
||||
* @type {Map}
|
||||
*/
|
||||
requests: new Map()
|
||||
};
|
||||
|
||||
ReducerRegistry.register(
|
||||
'features/network-activity',
|
||||
(state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case _ADD_NETWORK_REQUEST: {
|
||||
const {
|
||||
type, // eslint-disable-line no-unused-vars
|
||||
|
||||
request: key,
|
||||
...value
|
||||
} = action;
|
||||
const requests = new Map(state.requests);
|
||||
|
||||
requests.set(key, value);
|
||||
|
||||
return set(state, 'requests', requests);
|
||||
}
|
||||
|
||||
case _REMOVE_ALL_NETWORK_REQUESTS:
|
||||
return set(state, 'requests', DEFAULT_STATE.requests);
|
||||
|
||||
case _REMOVE_NETWORK_REQUEST: {
|
||||
const { request: key } = action;
|
||||
const requests = new Map(state.requests);
|
||||
|
||||
requests.delete(key);
|
||||
|
||||
return set(state, 'requests', requests);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
@@ -18,7 +18,7 @@ export function toDisplayableList(recentList) {
|
||||
date: item.date,
|
||||
duration: item.duration,
|
||||
time: [ item.date ],
|
||||
title: decodeURIComponent(parseURIString(item.conference).room),
|
||||
title: parseURIString(item.conference).room,
|
||||
url: item.conference
|
||||
};
|
||||
}));
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
* The URL that is the main landing page for YouTube live streaming and should
|
||||
* have a user's live stream key.
|
||||
*/
|
||||
export const YOUTUBE_LIVE_DASHBOARD_URL = 'https://www.youtube.com/live_dashboard';
|
||||
export const YOUTUBE_LIVE_DASHBOARD_URL
|
||||
= 'https://www.youtube.com/live_dashboard';
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||
import { Linking, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
import { _abstractMapStateToProps } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { StyleType } from '../../../../base/styles';
|
||||
import { openURLInBrowser } from '../../../../base/util';
|
||||
|
||||
import AbstractStreamKeyForm, {
|
||||
type Props as AbstractProps
|
||||
@@ -121,7 +120,7 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
|
||||
const { helpURL } = this;
|
||||
|
||||
if (typeof helpURL === 'string') {
|
||||
openURLInBrowser(helpURL);
|
||||
Linking.openURL(helpURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Linking,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
@@ -12,7 +13,6 @@ import { _abstractMapStateToProps } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { StyleType } from '../../../../base/styles';
|
||||
import { openURLInBrowser } from '../../../../base/util';
|
||||
|
||||
import { YOUTUBE_LIVE_DASHBOARD_URL } from '../constants';
|
||||
|
||||
@@ -153,7 +153,7 @@ class StreamKeyPicker extends Component<Props, State> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onOpenYoutubeDashboard() {
|
||||
openURLInBrowser(YOUTUBE_LIVE_DASHBOARD_URL);
|
||||
Linking.openURL(YOUTUBE_LIVE_DASHBOARD_URL);
|
||||
}
|
||||
|
||||
_onStreamPick: string => Function
|
||||
|
||||
56
react/features/toolbox/components/DownloadButton.js
Normal file
56
react/features/toolbox/components/DownloadButton.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// @flow
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../analytics';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { IconDownload } from '../../base/icons';
|
||||
import { connect } from '../../base/redux';
|
||||
import { openURLInBrowser } from '../../base/util';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox';
|
||||
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* The URL to the applications page.
|
||||
*/
|
||||
_downloadAppsUrl: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements an {@link AbstractButton} to open the applications page in a new window.
|
||||
*/
|
||||
class DownloadButton extends AbstractButton<Props, *> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.download';
|
||||
icon = IconDownload;
|
||||
label = 'toolbar.download';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens a new window with the user documentation.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
sendAnalytics(createToolbarEvent('download.pressed'));
|
||||
openURLInBrowser(this.props._downloadAppsUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maps part of the redux state to the component's props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
const { downloadAppsUrl } = state['features/base/config'].deploymentUrls || {};
|
||||
const visible = typeof downloadAppsUrl === 'string';
|
||||
|
||||
return {
|
||||
_downloadAppsUrl: downloadAppsUrl,
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(DownloadButton));
|
||||
@@ -44,7 +44,7 @@ class HelpButton extends AbstractButton<Props, *> {
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
const { userDocumentationURL } = state['features/base/config'];
|
||||
const { userDocumentationURL } = state['features/base/config'].deploymentUrls || {};
|
||||
const visible = typeof userDocumentationURL === 'string';
|
||||
|
||||
return {
|
||||
|
||||
@@ -69,6 +69,7 @@ import {
|
||||
setToolbarHovered
|
||||
} from '../../actions';
|
||||
import AudioMuteButton from '../AudioMuteButton';
|
||||
import DownloadButton from '../DownloadButton';
|
||||
import { isToolboxVisible } from '../../functions';
|
||||
import HangupButton from '../HangupButton';
|
||||
import HelpButton from '../HelpButton';
|
||||
@@ -975,6 +976,10 @@ class Toolbox extends Component<Props, State> {
|
||||
key = 'shortcuts'
|
||||
onClick = { this._onToolbarOpenKeyboardShortcuts }
|
||||
text = { t('toolbar.shortcuts') } />,
|
||||
this._shouldShowButton('download')
|
||||
&& <DownloadButton
|
||||
key = 'download'
|
||||
showLabel = { true } />,
|
||||
this._shouldShowButton('help')
|
||||
&& <HelpButton
|
||||
key = 'help'
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../base/color-scheme';
|
||||
import { LoadingIndicator } from '../../base/react';
|
||||
import { connect } from '../../base/redux';
|
||||
import { StyleType } from '../../base/styles';
|
||||
import { destroyLocalTracks } from '../../base/tracks';
|
||||
import { NetworkActivityIndicator } from '../../mobile/network-activity';
|
||||
|
||||
import LocalVideoTrackUnderlay from './LocalVideoTrackUnderlay';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* The type of React {@code Component} props of {@link BlankPage}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The color schemed style of the component.
|
||||
*/
|
||||
_styles: StyleType,
|
||||
|
||||
dispatch: Dispatch<any>
|
||||
};
|
||||
|
||||
@@ -40,12 +49,32 @@ class BlankPage extends Component<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _styles } = this.props;
|
||||
|
||||
return (
|
||||
<LocalVideoTrackUnderlay>
|
||||
<NetworkActivityIndicator />
|
||||
</LocalVideoTrackUnderlay>
|
||||
<View
|
||||
style = { [
|
||||
styles.blankPageWrapper,
|
||||
_styles.loadingOverlayWrapper
|
||||
] }>
|
||||
<LoadingIndicator
|
||||
color = { _styles.indicatorColor }
|
||||
size = 'large' />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(BlankPage);
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_styles: ColorSchemeRegistry.get(state, 'LoadConfigOverlay')
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(BlankPage);
|
||||
|
||||
@@ -12,6 +12,12 @@ import { SettingsButton, SETTINGS_TABS } from '../../settings';
|
||||
import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
|
||||
import Tabs from './Tabs';
|
||||
|
||||
/**
|
||||
* The pattern used to validate room name.
|
||||
* @type {string}
|
||||
*/
|
||||
export const ROOM_NAME_VALIDATE_PATTERN_STR = '^[^?&:\u0022\u0027%#]+$';
|
||||
|
||||
/**
|
||||
* The Web container rendering the welcome page.
|
||||
*
|
||||
@@ -53,6 +59,8 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
*/
|
||||
this._additionalContentRef = null;
|
||||
|
||||
this._roomInputRef = null;
|
||||
|
||||
/**
|
||||
* The HTML Element used as the container for additional toolbar content. Used
|
||||
* for directly appending the additional content template to the dom.
|
||||
@@ -88,6 +96,7 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
this._onRoomChange = this._onRoomChange.bind(this);
|
||||
this._setAdditionalContentRef
|
||||
= this._setAdditionalContentRef.bind(this);
|
||||
this._setRoomInputRef = this._setRoomInputRef.bind(this);
|
||||
this._setAdditionalToolbarContentRef
|
||||
= this._setAdditionalToolbarContentRef.bind(this);
|
||||
this._onTabSelected = this._onTabSelected.bind(this);
|
||||
@@ -184,9 +193,10 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
className = 'enter-room-input'
|
||||
id = 'enter_room_field'
|
||||
onChange = { this._onRoomChange }
|
||||
pattern = '^[a-zA-Z0-9=\?]+$'
|
||||
pattern = { ROOM_NAME_VALIDATE_PATTERN_STR }
|
||||
placeholder = { this.state.roomPlaceholder }
|
||||
title = { t('welcomepage.onlyAsciiAllowed') }
|
||||
ref = { this._setRoomInputRef }
|
||||
title = { t('welcomepage.roomNameAllowedChars') }
|
||||
type = 'text'
|
||||
value = { this.state.room } />
|
||||
</form>
|
||||
@@ -194,7 +204,7 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
<div
|
||||
className = 'welcome-page-button'
|
||||
id = 'enter_room_button'
|
||||
onClick = { this._onJoin }>
|
||||
onClick = { this._onFormSubmit }>
|
||||
{ t('welcomepage.go') }
|
||||
</div>
|
||||
</div>
|
||||
@@ -219,7 +229,9 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
_onFormSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this._onJoin();
|
||||
if (!this._roomInputRef || this._roomInputRef.reportValidity()) {
|
||||
this._onJoin();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -311,6 +323,18 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
this._additionalToolbarContentRef = el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal reference to the HTMLInputElement used to hold the
|
||||
* welcome page input room element.
|
||||
*
|
||||
* @param {HTMLInputElement} el - The HTMLElement for the input of the room name on the welcome page.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setRoomInputRef(el) {
|
||||
this._roomInputRef = el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not additional content should be displayed below
|
||||
* the welcome page's header for entering a room name.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import { BoxModel, ColorPalette } from '../../base/styles';
|
||||
|
||||
export const PLACEHOLDER_TEXT_COLOR = 'rgba(255, 255, 255, 0.3)';
|
||||
@@ -38,6 +40,17 @@ export default {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
/**
|
||||
* View that is rendered when there is no welcome page.
|
||||
*/
|
||||
blankPageWrapper: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
/**
|
||||
* Join button style.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user