Compare commits
11 Commits
react-thum
...
prosody-mo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b022ce60ac | ||
|
|
20ce38bd4c | ||
|
|
c4ba97e87c | ||
|
|
4b8aae90e0 | ||
|
|
c2539bf615 | ||
|
|
4fdd4b66f7 | ||
|
|
9fa29d7353 | ||
|
|
c14f639639 | ||
|
|
c007477ee9 | ||
|
|
50997ae6ac | ||
|
|
f8a41aea9c |
@@ -359,17 +359,12 @@ var config = {
|
||||
// Default language for the user interface.
|
||||
// defaultLanguage: 'en',
|
||||
|
||||
// If true all users without a token will be considered guests and all users
|
||||
// with token will be considered non-guests. Only guests will be allowed to
|
||||
// edit their profile.
|
||||
enableUserRolesBasedOnToken: false,
|
||||
// Disables profile and the edit of all fields from the profile settings (display name and email)
|
||||
// disableProfile: false,
|
||||
|
||||
// Whether or not some features are checked based on token.
|
||||
// enableFeaturesBasedOnToken: false,
|
||||
|
||||
// Enable lock room for all moderators, even when userRolesBasedOnToken is enabled and participants are guests.
|
||||
// lockRoomGuestEnabled: false,
|
||||
|
||||
// When enabled the password used for locking a room is restricted to up to the number of digits specified
|
||||
// roomPasswordNumberOfDigits: 10,
|
||||
// default: roomPasswordNumberOfDigits: false,
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
|
||||
.meetings-list-empty {
|
||||
text-align: center;
|
||||
@@ -20,11 +19,34 @@
|
||||
flex-direction: column;
|
||||
|
||||
.description {
|
||||
font-size: 16px;
|
||||
padding: 20px;
|
||||
color: #2f3237;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
margin-bottom: 16px;
|
||||
max-width: 436px;
|
||||
}
|
||||
}
|
||||
|
||||
.meetings-list-empty-image {
|
||||
text-align: center;
|
||||
margin: 24px 0 20px 0;
|
||||
}
|
||||
|
||||
.meetings-list-empty-button {
|
||||
align-items: center;
|
||||
color: #0163FF;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
margin: 24px 0 32px 0;
|
||||
}
|
||||
|
||||
.meetings-list-empty-icon {
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.button {
|
||||
background: #0074E0;
|
||||
border-radius: 4px;
|
||||
@@ -32,7 +54,7 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 5px 10px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -43,12 +65,13 @@
|
||||
}
|
||||
|
||||
.item {
|
||||
background: rgba(255,255,255,0.50);
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
display: inline-flex;
|
||||
margin-top: 5px;
|
||||
min-height: 92px;
|
||||
width: 100%;
|
||||
margin: 4px 4px 0 4px;
|
||||
min-height: 60px;
|
||||
width: calc(100% - 8px);
|
||||
word-break: break-word;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -61,37 +84,41 @@
|
||||
.left-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 140px;
|
||||
flex-grow: 0;
|
||||
padding-left: 30px;
|
||||
padding-top: 25px;
|
||||
|
||||
.date {
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
padding-left: 16px;
|
||||
padding-top: 13px;
|
||||
}
|
||||
|
||||
.right-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
padding-left: 30px;
|
||||
padding-top: 25px;
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
padding-left: 16px;
|
||||
padding-top: 13px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #5E6D7A;
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-grow: 0;
|
||||
padding-right: 30px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
&.with-click-handler {
|
||||
@@ -99,7 +126,7 @@
|
||||
}
|
||||
|
||||
&.with-click-handler:hover {
|
||||
background-color: #75A7E7;
|
||||
background-color: #c7ddff;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
@@ -120,4 +147,20 @@
|
||||
display: block
|
||||
}
|
||||
}
|
||||
|
||||
.delete-meeting {
|
||||
display: none;
|
||||
margin-right: 16px;
|
||||
position: absolute;
|
||||
|
||||
&> svg {
|
||||
fill: #0074e0;
|
||||
}
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
.delete-meeting {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,67 @@
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $verySmallScreen) {
|
||||
.welcome {
|
||||
#enter_room {
|
||||
position: relative;
|
||||
height: 42px;
|
||||
|
||||
.welcome-page-button {
|
||||
font-size: 16px;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 68px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #06345E;
|
||||
background-image: linear-gradient(180deg, rgba(8, 110, 202, 0.8) 0%, rgba(8, 110, 202, 0) 100%);
|
||||
|
||||
#enter_room {
|
||||
.enter-room-input-container {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.warning-without-link,
|
||||
.warning-with-link {
|
||||
top: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-tabs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header-text-title {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcome-cards-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.without-content {
|
||||
.header {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#moderated-meetings {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.welcome-footer-row-block {
|
||||
display: block;
|
||||
}
|
||||
.welcome-badge {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
#videoResolutionLabel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -161,71 +161,47 @@ $unsupportedDesktopBrowserTextFontSize: 21px;
|
||||
/**
|
||||
* The size of the default watermark.
|
||||
*/
|
||||
$watermarkWidth: 186px;
|
||||
$watermarkHeight: 74px;
|
||||
$watermarkWidth: 71px;
|
||||
$watermarkHeight: 32px;
|
||||
|
||||
$welcomePageWatermarkWidth: 186px;
|
||||
$welcomePageWatermarkHeight: 74px;
|
||||
$welcomePageWatermarkWidth: 71px;
|
||||
$welcomePageWatermarkHeight: 32px;
|
||||
|
||||
/**
|
||||
* Welcome page variables.
|
||||
*/
|
||||
$welcomePageDescriptionColor: #fff;
|
||||
$welcomePageFontFamily: inherit;
|
||||
$welcomePageBackground: linear-gradient(-90deg, #1251AE 0%, #0074FF 50%, #1251AE 100%);
|
||||
$welcomePageBackground: none;
|
||||
$welcomePageTitleColor: #fff;
|
||||
|
||||
$welcomePageHeaderBackground: none;
|
||||
$welcomePageHeaderBackgroundSmall: none;
|
||||
$welcomePageHeaderBackground: linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), url('/images/welcome-background.png');
|
||||
$welcomePageHeaderBackgroundPosition: none;
|
||||
$welcomePageHeaderBackgroundRepeat: none;
|
||||
$welcomePageHeaderBackgroundSize: none;
|
||||
$welcomePageHeaderBackgroundSize: cover;
|
||||
$welcomePageHeaderPaddingBottom: 0px;
|
||||
$welcomePageHeaderMinHeight: fit-content;
|
||||
$welcomePageHeaderTitleMaxWidth: initial;
|
||||
$welcomePageHeaderTextAlign: center;
|
||||
|
||||
$welcomePageHeaderTextMarginTop: 35px;
|
||||
$welcomePageHeaderTextMarginBottom: 35px;
|
||||
$welcomePageHeaderTextDisplay: flex;
|
||||
$welcomePageHeaderTextWidth: 650px;
|
||||
$welcomePageHeaderContainerDisplay: flex;
|
||||
$welcomePageHeaderContainerMargin: 146px 32px 0 32px;
|
||||
|
||||
$welcomePageHeaderTextTitleMarginBottom: 16px;
|
||||
$welcomePageHeaderTextTitleFontSize: 2.5rem;
|
||||
$welcomePageHeaderTextTitleFontWeight: 500;
|
||||
$welcomePageHeaderTextTitleLineHeight: 1.18;
|
||||
$welcomePageHeaderTextTitleMarginBottom: 0;
|
||||
$welcomePageHeaderTextTitleFontSize: 42px;
|
||||
$welcomePageHeaderTextTitleFontWeight: normal;
|
||||
$welcomePageHeaderTextTitleLineHeight: 50px;
|
||||
$welcomePageHeaderTextTitleOpacity: 1;
|
||||
|
||||
$welcomePageHeaderTextDescriptionDisplay: inherit;
|
||||
$welcomePageHeaderTextDescriptionFontSize: 1rem;
|
||||
$welcomePageHeaderTextDescriptionFontWeight: 400;
|
||||
$welcomePageHeaderTextDescriptionLineHeight: 24px;
|
||||
$welcomePageHeaderTextDescriptionMarginBottom: 20px;
|
||||
$welcomePageHeaderTextDescriptionAlignSelf: inherit;
|
||||
|
||||
$welcomePageEnterRoomDisplay: flex;
|
||||
$welcomePageEnterRoomWidth: 680px;
|
||||
$welcomePageEnterRoomPadding: 25px 30px;
|
||||
$welcomePageEnterRoomBorderRadius: 0px;
|
||||
|
||||
$welcomePageEnterRoomInputContainerPadding: 0 8px 5px 0px;
|
||||
$welcomePageEnterRoomInputContainerBorderWidth: 0px 0px 2px 0px;
|
||||
$welcomePageEnterRoomInputContainerBorderStyle: solid;
|
||||
$welcomePageEnterRoomInputContainerBorderImage: linear-gradient(to right, #dee1e6, #fff) 1;
|
||||
|
||||
$welcomePageEnterRoomTitleDisplay: inherit;
|
||||
$welcomePageEnterRoomWidth: calc(100% - 32px);
|
||||
$welcomePageEnterRoomPadding: 4px;
|
||||
$welcomePageEnterRoomMargin: 0 auto;
|
||||
|
||||
$welcomePageTabContainerDisplay: flex;
|
||||
$welcomePageTabContentDisplay: inherit;
|
||||
$welcomePageTabButtonsDisplay: flex;
|
||||
$welcomePageTabDisplay: block;
|
||||
|
||||
$welcomePageButtonWidth: 51px;
|
||||
$welcomePageButtonMinWidth: inherit;
|
||||
$welcomePageButtonFontSize: 14px;
|
||||
$welcomePageButtonHeight: 35px;
|
||||
$welcomePageButtonFontWeight: inherit;
|
||||
$welcomePageButtonBorderRadius: 4px;
|
||||
$welcomePageButtonLineHeight: 35px;
|
||||
|
||||
/**
|
||||
* Deep-linking page variables.
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,7 @@ body.welcome-page {
|
||||
|
||||
.welcome {
|
||||
background-image: $welcomePageBackground;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: $welcomePageFontFamily;
|
||||
@@ -18,21 +19,15 @@ body.welcome-page {
|
||||
background-repeat: $welcomePageHeaderBackgroundRepeat;
|
||||
background-size: $welcomePageHeaderBackgroundSize;
|
||||
padding-bottom: $welcomePageHeaderPaddingBottom;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: $welcomePageHeaderMinHeight;
|
||||
background-color: #002637;
|
||||
height: 480px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
.header-text {
|
||||
display: $welcomePageHeaderTextDisplay;
|
||||
.header-container {
|
||||
display: $welcomePageHeaderContainerDisplay;
|
||||
flex-direction: column;
|
||||
margin-top: $watermarkHeight + $welcomePageHeaderTextMarginTop;
|
||||
margin-bottom: $welcomePageHeaderTextMarginBottom;
|
||||
max-width: calc(100% - 40px);
|
||||
width: $welcomePageHeaderTextWidth;
|
||||
margin: $welcomePageHeaderContainerMargin;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
@@ -42,50 +37,52 @@ body.welcome-page {
|
||||
font-weight: $welcomePageHeaderTextTitleFontWeight;
|
||||
line-height: $welcomePageHeaderTextTitleLineHeight;
|
||||
margin-bottom: $welcomePageHeaderTextTitleMarginBottom;
|
||||
max-width: $welcomePageHeaderTitleMaxWidth;
|
||||
opacity: $welcomePageHeaderTextTitleOpacity;
|
||||
text-align: $welcomePageHeaderTextAlign;
|
||||
}
|
||||
|
||||
.header-text-description {
|
||||
display: $welcomePageHeaderTextDescriptionDisplay;
|
||||
color: $welcomePageDescriptionColor;
|
||||
font-size: $welcomePageHeaderTextDescriptionFontSize;
|
||||
font-weight: $welcomePageHeaderTextDescriptionFontWeight;
|
||||
line-height: $welcomePageHeaderTextDescriptionLineHeight;
|
||||
margin-bottom: $welcomePageHeaderTextDescriptionMarginBottom;
|
||||
align-self: $welcomePageHeaderTextDescriptionAlignSelf;
|
||||
.header-text-subtitle {
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 26px;
|
||||
margin: 16px 0 32px 0;
|
||||
text-align: $welcomePageHeaderTextAlign;
|
||||
|
||||
}
|
||||
|
||||
#enter_room {
|
||||
display: $welcomePageEnterRoomDisplay;
|
||||
align-items: center;
|
||||
max-width: calc(100% - 40px);
|
||||
max-width: 480px;
|
||||
width: $welcomePageEnterRoomWidth;
|
||||
z-index: $zindex2;
|
||||
background-color: #fff;
|
||||
padding: $welcomePageEnterRoomPadding;
|
||||
border-radius: $welcomePageEnterRoomBorderRadius;
|
||||
border-radius: 4px;
|
||||
margin: $welcomePageEnterRoomMargin;
|
||||
|
||||
.enter-room-input-container {
|
||||
width: 100%;
|
||||
padding: $welcomePageEnterRoomInputContainerPadding;
|
||||
text-align: left;
|
||||
color: #253858;
|
||||
flex-grow: 1;
|
||||
height: fit-content;
|
||||
|
||||
.enter-room-title {
|
||||
display: $welcomePageEnterRoomTitleDisplay;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
padding-right: 4px;
|
||||
position: relative;
|
||||
|
||||
.enter-room-input {
|
||||
border-width: $welcomePageEnterRoomInputContainerBorderWidth;
|
||||
border-style: $welcomePageEnterRoomInputContainerBorderStyle;
|
||||
border-image: $welcomePageEnterRoomInputContainerBorderImage;
|
||||
border: 0;
|
||||
background: #fff;
|
||||
display: inline-block;
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
padding-left: 10px;
|
||||
|
||||
&:focus {
|
||||
outline: auto 2px #005fcc;
|
||||
}
|
||||
}
|
||||
|
||||
.insecure-room-name-warning {
|
||||
@@ -109,16 +106,28 @@ body.welcome-page {
|
||||
}
|
||||
}
|
||||
|
||||
.warning-without-link {
|
||||
position: absolute;
|
||||
top: 44px;
|
||||
left: -10px;
|
||||
}
|
||||
|
||||
.warning-with-link {
|
||||
position: absolute;
|
||||
top: 84px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#moderated-meetings {
|
||||
max-width: calc(100% - 40px);
|
||||
padding: 16px 0 39px 0;
|
||||
margin: $welcomePageEnterRoomMargin;
|
||||
width: $welcomePageEnterRoomWidth;
|
||||
|
||||
p {
|
||||
color: $welcomePageDescriptionColor;
|
||||
text-align: left;
|
||||
text-align: $welcomePageHeaderTextAlign;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
@@ -126,76 +135,70 @@ body.welcome-page {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
font-size: 16px;
|
||||
.tab-container {
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
display: $welcomePageTabContainerDisplay;
|
||||
flex-direction: column;
|
||||
|
||||
.tab-content{
|
||||
display: $welcomePageTabContentDisplay;
|
||||
height: 250px;
|
||||
margin: 5px 0px;
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
min-height: 354px;
|
||||
width: 710px;
|
||||
background: #75A7E7;
|
||||
display: $welcomePageTabContainerDisplay;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tab-content{
|
||||
display: $welcomePageTabContentDisplay;
|
||||
margin: 5px 0px;
|
||||
overflow: hidden;
|
||||
.tab-buttons {
|
||||
background-color: #c7ddff;
|
||||
border-radius: 6px;
|
||||
color: #0163FF;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
margin: 4px;
|
||||
display: $welcomePageTabButtonsDisplay;
|
||||
|
||||
.tab {
|
||||
background-color: #c7ddff;
|
||||
border-radius: 7px;
|
||||
cursor: pointer;
|
||||
display: $welcomePageTabDisplay;
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
margin: 2px;
|
||||
padding: 7px 0;
|
||||
text-align: center;
|
||||
|
||||
> * {
|
||||
position: absolute;
|
||||
&.selected {
|
||||
background-color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-buttons {
|
||||
font-size: 18px;
|
||||
color: #FFFFFF;
|
||||
display: $welcomePageTabButtonsDisplay;
|
||||
flex-grow: 0;
|
||||
flex-direction: row;
|
||||
min-height: 54px;
|
||||
width: 100%;
|
||||
|
||||
.tab {
|
||||
display: $welcomePageTabDisplay;
|
||||
text-align: center;
|
||||
background: rgba(9,30,66,0.37);
|
||||
height: 55px;
|
||||
line-height: 54px;
|
||||
flex-grow: 1;
|
||||
cursor: pointer;
|
||||
|
||||
&.selected, &:hover {
|
||||
background: rgba(9,30,66,0.71);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-left: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-page-button {
|
||||
width: $welcomePageButtonWidth;
|
||||
min-width: $welcomePageButtonMinWidth;
|
||||
height: $welcomePageButtonHeight;
|
||||
font-size: $welcomePageButtonFontSize;
|
||||
font-weight: $welcomePageButtonFontWeight;
|
||||
border: 0;
|
||||
font-size: 14px;
|
||||
background: #0074E0;
|
||||
border-radius: $welcomePageButtonBorderRadius;
|
||||
border-radius: 3px;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: $welcomePageButtonLineHeight;
|
||||
cursor: pointer;
|
||||
padding: 16px 20px;
|
||||
|
||||
&:focus-within {
|
||||
outline: auto 2px #022e61;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-page-settings {
|
||||
background: rgba(255, 255, 255, 0.38);
|
||||
border-radius: 3px;
|
||||
color: $welcomePageDescriptionColor;
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
right: 32px;
|
||||
@@ -217,4 +220,83 @@ body.welcome-page {
|
||||
height: $welcomePageWatermarkHeight;
|
||||
}
|
||||
}
|
||||
|
||||
&.without-content {
|
||||
.welcome-card {
|
||||
min-width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-cards-container {
|
||||
color:#131519;
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
.welcome-card-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0 32px;
|
||||
}
|
||||
|
||||
.welcome-card-text {
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.welcome-card {
|
||||
width: 49%;
|
||||
border-radius: 8px;
|
||||
|
||||
&--dark {
|
||||
background: #444447;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&--blue {
|
||||
background: #D5E5FF;
|
||||
}
|
||||
|
||||
&--grey {
|
||||
background: #F2F3F4;
|
||||
}
|
||||
|
||||
&--shadow {
|
||||
box-shadow: 0px 4px 30px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-footer {
|
||||
background: #131519;
|
||||
color: #fff;
|
||||
margin-top: 40px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.welcome-footer-centered {
|
||||
max-width: 688px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.welcome-footer-padded {
|
||||
padding: 0px 16px;
|
||||
}
|
||||
|
||||
.welcome-footer-row-block {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #424447;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-footer--row-1 {
|
||||
padding: 40px 0 24px 0;
|
||||
}
|
||||
|
||||
.welcome-footer-row-1-text {
|
||||
max-width: 200px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
BIN
images/app-store-badge.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
21
images/calendar.svg
Normal file
@@ -0,0 +1,21 @@
|
||||
<svg width="68" height="72" viewBox="0 0 68 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1" y="5.64514" width="65.3548" height="65.3548" rx="7" stroke="#A4B8D1" stroke-width="2"/>
|
||||
<rect y="23.2258" width="67.3548" height="2.0213" fill="#A4B8D1"/>
|
||||
<rect x="14.5161" width="2.32258" height="14.5161" fill="#A4B8D1"/>
|
||||
<rect x="11.6129" y="12.1935" width="8.12903" height="2.32258" fill="#A4B8D1"/>
|
||||
<rect x="50.5161" width="2.32258" height="14.5161" fill="#A4B8D1"/>
|
||||
<rect x="47.6129" y="12.1935" width="8.12903" height="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="24.387" cy="37.7419" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="33.6774" cy="37.7419" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="42.9677" cy="37.7419" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="52.258" cy="37.7419" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="24.387" cy="47.0322" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="15.0968" cy="47.0322" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="33.6774" cy="47.0322" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="42.9677" cy="47.0322" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="24.387" cy="56.3226" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="15.0968" cy="56.3226" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="33.6774" cy="56.3226" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="42.9677" cy="56.3226" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="52.258" cy="47.0322" r="2.32258" fill="#A4B8D1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
BIN
images/f-droid-badge.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
images/google-play-badge.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 33 KiB |
8
images/watermark.svg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
images/welcome-background.png
Normal file
|
After Width: | Height: | Size: 290 KiB |
@@ -182,6 +182,7 @@
|
||||
<!--#include virtual="title.html" -->
|
||||
<!--#include virtual="plugin.head.html" -->
|
||||
<!--#include virtual="static/welcomePageAdditionalContent.html" -->
|
||||
<!--#include virtual="static/welcomePageAdditionalCard.html" -->
|
||||
<!--#include virtual="static/settingsToolbarAdditionalContent.html" -->
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -46,9 +46,9 @@ var interfaceConfig = {
|
||||
|
||||
DEFAULT_BACKGROUND: '#474747',
|
||||
DEFAULT_LOCAL_DISPLAY_NAME: 'me',
|
||||
DEFAULT_LOGO_URL: 'images/watermark.png',
|
||||
DEFAULT_LOGO_URL: 'images/watermark.svg',
|
||||
DEFAULT_REMOTE_DISPLAY_NAME: 'Fellow Jitster',
|
||||
DEFAULT_WELCOME_PAGE_LOGO_URL: 'images/watermark.png',
|
||||
DEFAULT_WELCOME_PAGE_LOGO_URL: 'images/watermark.svg',
|
||||
|
||||
DISABLE_DOMINANT_SPEAKER_INDICATOR: false,
|
||||
|
||||
@@ -86,7 +86,9 @@ var interfaceConfig = {
|
||||
*/
|
||||
DISABLE_VIDEO_BACKGROUND: false,
|
||||
|
||||
DISPLAY_WELCOME_PAGE_CONTENT: true,
|
||||
DISPLAY_WELCOME_FOOTER: true,
|
||||
DISPLAY_WELCOME_PAGE_ADDITIONAL_CARD: false,
|
||||
DISPLAY_WELCOME_PAGE_CONTENT: false,
|
||||
DISPLAY_WELCOME_PAGE_TOOLBAR_ADDITIONAL_CONTENT: false,
|
||||
|
||||
ENABLE_DIAL_OUT: true,
|
||||
@@ -136,6 +138,21 @@ var interfaceConfig = {
|
||||
*/
|
||||
MOBILE_APP_PROMO: true,
|
||||
|
||||
/**
|
||||
* Specify custom URL for downloading android mobile app.
|
||||
*/
|
||||
MOBILE_DOWNLOAD_LINK_ANDROID: 'https://play.google.com/store/apps/details?id=org.jitsi.meet',
|
||||
|
||||
/**
|
||||
* Specify custom URL for downloading f droid app.
|
||||
*/
|
||||
MOBILE_DOWNLOAD_LINK_F_DROID: 'https://f-droid.org/en/packages/org.jitsi.meet/',
|
||||
|
||||
/**
|
||||
* Specify URL for downloading ios mobile app.
|
||||
*/
|
||||
MOBILE_DOWNLOAD_LINK_IOS: 'https://itunes.apple.com/us/app/jitsi-meet/id1165103905',
|
||||
|
||||
NATIVE_APP_NAME: 'Jitsi Meet',
|
||||
|
||||
// Names of browsers which should show a warning stating the current browser
|
||||
@@ -169,7 +186,6 @@ var interfaceConfig = {
|
||||
SHOW_JITSI_WATERMARK: true,
|
||||
SHOW_POWERED_BY: false,
|
||||
SHOW_PROMOTIONAL_CLOSE_PAGE: false,
|
||||
SHOW_WATERMARK_FOR_GUESTS: true, // if watermark is disabled by default, it can be shown only for guests
|
||||
|
||||
/*
|
||||
* If indicated some of the error dialogs may point to the support URL for
|
||||
@@ -223,27 +239,12 @@ var interfaceConfig = {
|
||||
*/
|
||||
VIDEO_QUALITY_LABEL_DISABLED: false,
|
||||
|
||||
/**
|
||||
* When enabled, the kick participant button will not be presented for users without a JWT
|
||||
*/
|
||||
// HIDE_KICK_BUTTON_FOR_GUESTS: false,
|
||||
|
||||
/**
|
||||
* How many columns the tile view can expand to. The respected range is
|
||||
* between 1 and 5.
|
||||
*/
|
||||
// TILE_VIEW_MAX_COLUMNS: 5,
|
||||
|
||||
/**
|
||||
* Specify custom URL for downloading android mobile app.
|
||||
*/
|
||||
// MOBILE_DOWNLOAD_LINK_ANDROID: 'https://play.google.com/store/apps/details?id=org.jitsi.meet',
|
||||
|
||||
/**
|
||||
* Specify URL for downloading ios mobile app.
|
||||
*/
|
||||
// MOBILE_DOWNLOAD_LINK_IOS: 'https://itunes.apple.com/us/app/jitsi-meet/id1165103905',
|
||||
|
||||
/**
|
||||
* Specify Firebase dynamic link properties for the mobile apps.
|
||||
*/
|
||||
|
||||
@@ -220,7 +220,6 @@
|
||||
"kickTitle": "Ouch! {{participantDisplayName}} kicked you out of the meeting",
|
||||
"liveStreaming": "Live Streaming",
|
||||
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Not possible while recording is active",
|
||||
"liveStreamingDisabledForGuestTooltip": "Guests can't start live streaming.",
|
||||
"liveStreamingDisabledTooltip": "Start live stream disabled.",
|
||||
"lockMessage": "Failed to lock the conference.",
|
||||
"lockRoom": "Add meeting $t(lockRoomPasswordUppercase)",
|
||||
@@ -255,7 +254,6 @@
|
||||
"readMore": "more",
|
||||
"recording": "Recording",
|
||||
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Not possible while a live stream is active",
|
||||
"recordingDisabledForGuestTooltip": "Guests can't start recordings.",
|
||||
"recordingDisabledTooltip": "Start recording disabled.",
|
||||
"rejoinNow": "Rejoin now",
|
||||
"remoteControlAllowedMessage": "{{user}} accepted your remote control request!",
|
||||
@@ -287,7 +285,6 @@
|
||||
"shareVideoTitle": "Share a video",
|
||||
"shareYourScreen": "Share your screen",
|
||||
"shareYourScreenDisabled": "Screen sharing disabled.",
|
||||
"shareYourScreenDisabledForGuest": "Guests can't screen share.",
|
||||
"startLiveStreaming": "Start live stream",
|
||||
"startRecording": "Start recording",
|
||||
"startRemoteControlErrorMessage": "An error occurred while trying to start the remote control session!",
|
||||
@@ -878,6 +875,8 @@
|
||||
"goSmall": "GO",
|
||||
"info": "Dial-in info",
|
||||
"join": "CREATE / JOIN",
|
||||
"jitsiMeet": "Jitsi Meet",
|
||||
"jitsiOnMobile": "Jitsi on mobile – download our apps and start a meeting from anywhere",
|
||||
"moderatedMessage": "Or <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">book a meeting URL</a> in advance where you are the only moderator.",
|
||||
"privacy": "Privacy",
|
||||
"recentList": "Recent",
|
||||
@@ -888,6 +887,8 @@
|
||||
"roomname": "Enter room name",
|
||||
"roomnameHint": "Enter the name or URL of the room you want to join. You may make a name up, just let the people you are meeting know it so that they enter the same name.",
|
||||
"sendFeedback": "Send feedback",
|
||||
"secureMeetings": "Secure and high quality meetings",
|
||||
"startMeeting": "Start meeting",
|
||||
"terms": "Terms",
|
||||
"title": "Secure, fully featured, and completely free video conferencing"
|
||||
},
|
||||
|
||||
@@ -104,7 +104,7 @@ export default class LocalVideo extends SmallVideo {
|
||||
}
|
||||
|
||||
this._renderDisplayName({
|
||||
allowEditing: APP.store.getState()['features/base/jwt'].isGuest,
|
||||
allowEditing: !config.disableProfile,
|
||||
displayNameSuffix: interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME,
|
||||
elementID: 'localDisplayName',
|
||||
participantID: this.id
|
||||
|
||||
@@ -298,13 +298,13 @@ export function maybeRedirectToWelcomePage(options: Object = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { isGuest, jwt } = getState()['features/base/jwt'];
|
||||
const { jwt } = getState()['features/base/jwt'];
|
||||
|
||||
let hashParam;
|
||||
|
||||
// save whether current user is guest or not, and pass auth token,
|
||||
// before navigating to close page
|
||||
window.sessionStorage.setItem('guest', isGuest);
|
||||
window.sessionStorage.setItem('guest', !jwt);
|
||||
window.sessionStorage.setItem('jwt', jwt);
|
||||
|
||||
let path = 'close.html';
|
||||
|
||||
@@ -85,6 +85,7 @@ export default [
|
||||
'disableInviteFunctions',
|
||||
'disableLocalVideoFlip',
|
||||
'disableNS',
|
||||
'disableProfile',
|
||||
'disableRemoteControl',
|
||||
'disableRemoteMute',
|
||||
'disableRtx',
|
||||
|
||||
3
react/features/base/icons/svg/calendar-plus.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.99996 2.50002C4.99996 2.03978 5.37306 1.66669 5.83329 1.66669C6.29353 1.66669 6.66663 2.03978 6.66663 2.50002V3.33335H13.3333V2.50002C13.3333 2.03978 13.7064 1.66669 14.1666 1.66669C14.6269 1.66669 15 2.03978 15 2.50002V3.33335H16.6666C17.5871 3.33335 18.3333 4.07955 18.3333 5.00002V16.6667C18.3333 17.5872 17.5871 18.3334 16.6666 18.3334H3.33329C2.41282 18.3334 1.66663 17.5872 1.66663 16.6667V5.00002C1.66663 4.07955 2.41282 3.33335 3.33329 3.33335H4.99996V2.50002ZM3.33329 16.6667V5.00002H16.6666V16.6667H3.33329ZM9.99996 6.66669C9.53972 6.66669 9.16663 7.03978 9.16663 7.50002V10H6.66662C6.20639 10 5.83329 10.3731 5.83329 10.8334C5.83329 11.2936 6.20639 11.6667 6.66662 11.6667H9.16663V14.1667C9.16663 14.6269 9.53972 15 9.99996 15C10.4602 15 10.8333 14.6269 10.8333 14.1667V11.6667H13.3333C13.7935 11.6667 14.1666 11.2936 14.1666 10.8334C14.1666 10.3731 13.7935 10 13.3333 10H10.8333V7.50002C10.8333 7.03978 10.4602 6.66669 9.99996 6.66669Z" fill="#0163FF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -11,6 +11,7 @@ export { default as IconAudioOnly } from './visibility.svg';
|
||||
export { default as IconAudioOnlyOff } from './visibility-off.svg';
|
||||
export { default as IconAudioRoute } from './volume.svg';
|
||||
export { default as IconBlurBackground } from './blur-background.svg';
|
||||
export { default as IconPlusCalendar } from './calendar-plus.svg';
|
||||
export { default as IconCamera } from './camera.svg';
|
||||
export { default as IconCameraDisabled } from './camera-disabled.svg';
|
||||
export { default as IconCancelSelection } from './cancel.svg';
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
/* @flow */
|
||||
|
||||
import jwtDecode from 'jwt-decode';
|
||||
|
||||
import { parseURLParams } from '../util';
|
||||
|
||||
/**
|
||||
@@ -24,7 +22,7 @@ export function parseJWTFromURLParams(url: URL = window.location) {
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getJwtName(state: Object) {
|
||||
const jwtData = jwtDecode(state['features/base/jwt'].jwt);
|
||||
const { user } = state['features/base/jwt'];
|
||||
|
||||
return jwtData?.context?.user?.name || '';
|
||||
return user?.name || '';
|
||||
}
|
||||
|
||||
@@ -29,10 +29,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case SET_CONFIG:
|
||||
case SET_LOCATION_URL:
|
||||
// XXX The JSON Web Token (JWT) is not the only piece of state that we
|
||||
// have decided to store in the feature jwt, there is isGuest as well
|
||||
// which depends on the states of the features base/config and jwt. So
|
||||
// the JSON Web Token comes from the conference/room's URL and isGuest
|
||||
// needs a recalculation upon SET_CONFIG as well.
|
||||
// have decided to store in the feature jwt
|
||||
return _setConfigOrLocationURL(store, next, action);
|
||||
|
||||
case SET_JWT:
|
||||
@@ -128,12 +125,6 @@ function _setJWT(store, next, action) {
|
||||
|
||||
if (!Object.keys(actionPayload).length) {
|
||||
if (jwt) {
|
||||
const {
|
||||
enableUserRolesBasedOnToken
|
||||
} = store.getState()['features/base/config'];
|
||||
|
||||
action.isGuest = !enableUserRolesBasedOnToken;
|
||||
|
||||
let jwtPayload;
|
||||
|
||||
try {
|
||||
|
||||
@@ -4,24 +4,6 @@ import { equals, ReducerRegistry } from '../redux';
|
||||
|
||||
import { SET_JWT } from './actionTypes';
|
||||
|
||||
/**
|
||||
* The default/initial redux state of the feature jwt.
|
||||
*
|
||||
* @private
|
||||
* @type {{
|
||||
* isGuest: boolean
|
||||
* }}
|
||||
*/
|
||||
const DEFAULT_STATE = {
|
||||
/**
|
||||
* The indicator which determines whether the local participant is a guest
|
||||
* in the conference.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
isGuest: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Reduces redux actions which affect the JSON Web Token (JWT) stored in the
|
||||
* redux store.
|
||||
@@ -33,13 +15,12 @@ const DEFAULT_STATE = {
|
||||
*/
|
||||
ReducerRegistry.register(
|
||||
'features/base/jwt',
|
||||
(state = DEFAULT_STATE, action) => {
|
||||
(state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case SET_JWT: {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { type, ...payload } = action;
|
||||
const nextState = {
|
||||
...DEFAULT_STATE,
|
||||
...payload
|
||||
};
|
||||
|
||||
|
||||
@@ -298,12 +298,9 @@ export function isIconUrl(icon: ?string | ?Object) {
|
||||
*
|
||||
* @param {Object|Function} stateful - Object or function that can be resolved
|
||||
* to the Redux state.
|
||||
* @param {?boolean} ignoreToken - When true we ignore the token check.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalParticipantModerator(
|
||||
stateful: Object | Function,
|
||||
ignoreToken: ?boolean = false) {
|
||||
export function isLocalParticipantModerator(stateful: Object | Function) {
|
||||
const state = toState(stateful);
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
@@ -311,11 +308,7 @@ export function isLocalParticipantModerator(
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
localParticipant.role === PARTICIPANT_ROLE.MODERATOR
|
||||
&& (ignoreToken
|
||||
|| !state['features/base/config'].enableUserRolesBasedOnToken
|
||||
|| !state['features/base/jwt'].isGuest));
|
||||
return localParticipant.role === PARTICIPANT_ROLE.MODERATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
getLocalizedDateFormatter,
|
||||
getLocalizedDurationFormatter
|
||||
} from '../../../i18n';
|
||||
import { Icon, IconTrash } from '../../../icons';
|
||||
|
||||
import Container from './Container';
|
||||
import Text from './Text';
|
||||
@@ -38,9 +39,9 @@ type Props = {
|
||||
meetings: Array<Object>,
|
||||
|
||||
/**
|
||||
* Defines what happens when an item in the section list is clicked
|
||||
* Handler for deleting an item.
|
||||
*/
|
||||
onItemClick: Function
|
||||
onItemDelete?: Function
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -138,6 +139,25 @@ export default class MeetingsList extends Component<Props> {
|
||||
return null;
|
||||
}
|
||||
|
||||
_onDelete: Object => Function;
|
||||
|
||||
/**
|
||||
* Returns a function that is used on the onDelete callback.
|
||||
*
|
||||
* @param {Object} item - The item to be deleted.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onDelete(item) {
|
||||
const { onItemDelete } = this.props;
|
||||
|
||||
return evt => {
|
||||
evt.stopPropagation();
|
||||
|
||||
onItemDelete && onItemDelete(item);
|
||||
};
|
||||
}
|
||||
|
||||
_renderItem: (Object, number) => React$Node;
|
||||
|
||||
/**
|
||||
@@ -156,7 +176,7 @@ export default class MeetingsList extends Component<Props> {
|
||||
title,
|
||||
url
|
||||
} = meeting;
|
||||
const { hideURL = false } = this.props;
|
||||
const { hideURL = false, onItemDelete } = this.props;
|
||||
const onPress = this._onPress(url);
|
||||
const rootClassName
|
||||
= `item ${
|
||||
@@ -168,10 +188,10 @@ export default class MeetingsList extends Component<Props> {
|
||||
key = { index }
|
||||
onClick = { onPress }>
|
||||
<Container className = 'left-column'>
|
||||
<Text className = 'date'>
|
||||
<Text className = 'title'>
|
||||
{ _toDateString(date) }
|
||||
</Text>
|
||||
<Text>
|
||||
<Text className = 'subtitle'>
|
||||
{ _toTimeString(time) }
|
||||
</Text>
|
||||
</Container>
|
||||
@@ -187,13 +207,18 @@ export default class MeetingsList extends Component<Props> {
|
||||
}
|
||||
{
|
||||
typeof duration === 'number' ? (
|
||||
<Text>
|
||||
<Text className = 'subtitle'>
|
||||
{ getLocalizedDurationFormatter(duration) }
|
||||
</Text>) : null
|
||||
}
|
||||
</Container>
|
||||
<Container className = 'actions'>
|
||||
{ elementAfter || null }
|
||||
|
||||
{ onItemDelete && <Icon
|
||||
className = 'delete-meeting'
|
||||
onClick = { this._onDelete(meeting) }
|
||||
src = { IconTrash } />}
|
||||
</Container>
|
||||
</Container>
|
||||
);
|
||||
|
||||
@@ -225,7 +225,6 @@ class Watermarks extends Component<Props, State> {
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
const { isGuest } = state['features/base/jwt'];
|
||||
const {
|
||||
customizationReady,
|
||||
customizationFailed,
|
||||
@@ -239,12 +238,11 @@ function _mapStateToProps(state, ownProps) {
|
||||
DEFAULT_LOGO_URL,
|
||||
JITSI_WATERMARK_LINK,
|
||||
SHOW_JITSI_WATERMARK,
|
||||
SHOW_JITSI_WATERMARK_FOR_GUESTS,
|
||||
filmStripOnly
|
||||
} = interfaceConfig;
|
||||
let _showJitsiWatermark = (!filmStripOnly
|
||||
&& (customizationReady && !customizationFailed)
|
||||
&& (SHOW_JITSI_WATERMARK || (isGuest && SHOW_JITSI_WATERMARK_FOR_GUESTS)))
|
||||
&& SHOW_JITSI_WATERMARK)
|
||||
|| !isValidRoom;
|
||||
let _logoUrl = logoImageUrl;
|
||||
let _logoLink = logoClickUrl;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Icon, IconPlusCalendar } from '../../base/icons';
|
||||
import { AbstractPage } from '../../base/react';
|
||||
import { connect } from '../../base/redux';
|
||||
import { openSettingsDialog, SETTINGS_TABS } from '../../settings';
|
||||
@@ -185,16 +186,22 @@ class CalendarList extends AbstractPage<Props> {
|
||||
|
||||
return (
|
||||
<div className = 'meetings-list-empty'>
|
||||
<p className = 'description'>
|
||||
<div className = 'meetings-list-empty-image'>
|
||||
<img src = '/images/calendar.svg' />
|
||||
</div>
|
||||
<div className = 'description'>
|
||||
{ t('welcomepage.connectCalendarText', {
|
||||
app: interfaceConfig.APP_NAME,
|
||||
provider: interfaceConfig.PROVIDER_NAME
|
||||
}) }
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className = 'button'
|
||||
className = 'meetings-list-empty-button'
|
||||
onClick = { this._onOpenSettings }>
|
||||
{ t('welcomepage.connectCalendarButton') }
|
||||
<Icon
|
||||
className = 'meetings-list-empty-icon'
|
||||
src = { IconPlusCalendar } />
|
||||
<span>{ t('welcomepage.connectCalendarButton') }</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import Tooltip from '@atlaskit/tooltip';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Icon, IconAdd } from '../../base/icons';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link JoinButton}.
|
||||
@@ -60,7 +61,9 @@ class JoinButton extends Component<Props> {
|
||||
<div
|
||||
className = 'button join-button'
|
||||
onClick = { this._onClick }>
|
||||
{ t('calendarSync.join') }
|
||||
<Icon
|
||||
size = '14'
|
||||
src = { IconAdd } />
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -352,7 +352,7 @@ export function invitePeopleAndChatRooms( // eslint-disable-line max-params
|
||||
export function isAddPeopleEnabled(state: Object): boolean {
|
||||
const { peopleSearchUrl } = state['features/base/config'];
|
||||
|
||||
return !isGuest(state) && Boolean(peopleSearchUrl);
|
||||
return state['features/base/jwt'].jwt && Boolean(peopleSearchUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,16 +368,6 @@ export function isDialOutEnabled(state: Object): boolean {
|
||||
&& conference && conference.isSIPCallingSupported();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user is guest or not.
|
||||
*
|
||||
* @param {Object} state - Current state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isGuest(state: Object): boolean {
|
||||
return state['features/base/jwt'].isGuest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a string looks like it could be for a phone number.
|
||||
*
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { Dispatch } from 'redux';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { MeetingsList } from '../../base/react';
|
||||
import { connect } from '../../base/redux';
|
||||
import { deleteRecentListEntry } from '../actions';
|
||||
import { isRecentListEnabled, toDisplayableList } from '../functions';
|
||||
|
||||
import AbstractRecentList from './AbstractRecentList';
|
||||
@@ -55,6 +56,19 @@ class RecentList extends AbstractRecentList<Props> {
|
||||
this._getRenderListEmptyComponent
|
||||
= this._getRenderListEmptyComponent.bind(this);
|
||||
this._onPress = this._onPress.bind(this);
|
||||
this._onItemDelete = this._onItemDelete.bind(this);
|
||||
}
|
||||
|
||||
_onItemDelete: Object => void;
|
||||
|
||||
/**
|
||||
* Deletes a recent entry.
|
||||
*
|
||||
* @param {Object} entry - The entry to be deleted.
|
||||
* @inheritdoc
|
||||
*/
|
||||
_onItemDelete(entry) {
|
||||
this.props.dispatch(deleteRecentListEntry(entry));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,6 +92,7 @@ class RecentList extends AbstractRecentList<Props> {
|
||||
hideURL = { true }
|
||||
listEmptyComponent = { this._getRenderListEmptyComponent() }
|
||||
meetings = { recentList }
|
||||
onItemDelete = { this._onItemDelete }
|
||||
onPress = { this._onPress } />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { parseURIString, safeDecodeURIComponent } from '../base/util';
|
||||
*/
|
||||
export function toDisplayableList(recentList) {
|
||||
return (
|
||||
recentList.slice(-3).reverse()
|
||||
recentList.reverse()
|
||||
.map(item => {
|
||||
return {
|
||||
date: item.date,
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
import { openDialog } from '../../../base/dialog';
|
||||
import { IconLiveStreaming } from '../../../base/icons';
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import { getLocalParticipant } from '../../../base/participants';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
isLocalParticipantModerator
|
||||
} from '../../../base/participants';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
import { getActiveSession } from '../../functions';
|
||||
|
||||
@@ -125,13 +128,14 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
// If the containing component provides the visible prop, that is one
|
||||
// above all, but if not, the button should be autonomus and decide on
|
||||
// its own to be visible or not.
|
||||
const isModerator = isLocalParticipantModerator(state);
|
||||
const {
|
||||
enableFeaturesBasedOnToken,
|
||||
liveStreamingEnabled
|
||||
} = state['features/base/config'];
|
||||
const { features = {} } = getLocalParticipant(state);
|
||||
|
||||
visible = liveStreamingEnabled;
|
||||
visible = isModerator && liveStreamingEnabled;
|
||||
|
||||
if (enableFeaturesBasedOnToken) {
|
||||
visible = visible && String(features.livestreaming) === 'true';
|
||||
@@ -140,13 +144,7 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
if (!visible && !_disabled) {
|
||||
_disabled = true;
|
||||
visible = true;
|
||||
|
||||
// button and tooltip
|
||||
if (state['features/base/jwt'].isGuest) {
|
||||
_tooltip = 'dialog.liveStreamingDisabledForGuestTooltip';
|
||||
} else {
|
||||
_tooltip = 'dialog.liveStreamingDisabledTooltip';
|
||||
}
|
||||
_tooltip = 'dialog.liveStreamingDisabledTooltip';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,13 +153,7 @@ export function _mapStateToProps(state: Object, ownProps: Props): Object {
|
||||
if (!visible && !_disabled) {
|
||||
_disabled = true;
|
||||
visible = true;
|
||||
|
||||
// button and tooltip
|
||||
if (state['features/base/jwt'].isGuest) {
|
||||
_tooltip = 'dialog.recordingDisabledForGuestTooltip';
|
||||
} else {
|
||||
_tooltip = 'dialog.recordingDisabledTooltip';
|
||||
}
|
||||
_tooltip = 'dialog.recordingDisabledTooltip';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconKick } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import AbstractKickButton, {
|
||||
type Props
|
||||
} from '../AbstractKickButton';
|
||||
@@ -42,11 +41,7 @@ class KickButton extends AbstractKickButton {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { participantID, t, visible } = this.props;
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
const { participantID, t } = this.props;
|
||||
|
||||
return (
|
||||
<RemoteVideoMenuButton
|
||||
@@ -61,22 +56,4 @@ class KickButton extends AbstractKickButton {
|
||||
|
||||
_handleClick: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to {@link KickButton}'s React {@code Component}
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
const shouldHide = interfaceConfig.HIDE_KICK_BUTTON_FOR_GUESTS && state['features/base/jwt'].isGuest;
|
||||
|
||||
return {
|
||||
visible: !shouldHide
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(KickButton));
|
||||
|
||||
export default translate(KickButton);
|
||||
|
||||
@@ -154,9 +154,15 @@ function PasswordSection({
|
||||
<a
|
||||
className = 'remove-password'
|
||||
onClick = { onPasswordRemove }>{ t('dialog.Remove') }</a>
|
||||
<a
|
||||
className = 'copy-password'
|
||||
onClick = { onPasswordCopy }>{ t('dialog.copy') }</a>
|
||||
{
|
||||
|
||||
// There are cases like lobby and grant moderator when password is not available
|
||||
password ? <>
|
||||
<a
|
||||
className = 'copy-password'
|
||||
onClick = { onPasswordCopy }>{ t('dialog.copy') }</a>
|
||||
</> : null
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -125,13 +125,10 @@ function mapStateToProps(state) {
|
||||
locked,
|
||||
password
|
||||
} = state['features/base/conference'];
|
||||
const {
|
||||
lockRoomGuestEnabled,
|
||||
roomPasswordNumberOfDigits
|
||||
} = state['features/base/config'];
|
||||
const { roomPasswordNumberOfDigits } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_canEditPassword: isLocalParticipantModerator(state, lockRoomGuestEnabled),
|
||||
_canEditPassword: isLocalParticipantModerator(state),
|
||||
_conference: conference,
|
||||
_dialIn: state['features/invite'],
|
||||
_locked: locked,
|
||||
|
||||
@@ -126,14 +126,13 @@ class SettingsDialog extends Component<Props> {
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const configuredTabs = interfaceConfig.SETTINGS_SECTIONS || [];
|
||||
const jwt = state['features/base/jwt'];
|
||||
|
||||
// The settings sections to display.
|
||||
const showDeviceSettings = configuredTabs.includes('devices');
|
||||
const moreTabProps = getMoreTabProps(state);
|
||||
const { showModeratorSettings, showLanguageSettings, showPrejoinSettings } = moreTabProps;
|
||||
const showProfileSettings
|
||||
= configuredTabs.includes('profile') && jwt.isGuest;
|
||||
= configuredTabs.includes('profile') && !state['features/base/config'].disableProfile;
|
||||
const showCalendarSettings
|
||||
= configuredTabs.includes('calendar') && isCalendarEnabled(state);
|
||||
const tabs = [];
|
||||
|
||||
@@ -85,7 +85,12 @@ export class AbstractClosedCaptionButton
|
||||
export function _abstractMapStateToProps(state: Object, ownProps: Object) {
|
||||
const { _requestingSubtitles } = state['features/subtitles'];
|
||||
const { transcribingEnabled } = state['features/base/config'];
|
||||
const { visible = Boolean(transcribingEnabled && isLocalParticipantModerator(state)) } = ownProps;
|
||||
const { isTranscribing } = state['features/transcribing'];
|
||||
|
||||
// if the participant is moderator, it can enable transcriptions and if
|
||||
// transcriptions are already started for the meeting, guests can just show them
|
||||
const { visible = Boolean(transcribingEnabled
|
||||
&& (isLocalParticipantModerator(state) || isTranscribing)) } = ownProps;
|
||||
|
||||
return {
|
||||
_requestingSubtitles,
|
||||
|
||||
@@ -31,14 +31,12 @@ class ClosedCaptionButton
|
||||
* @returns {Props}
|
||||
*/
|
||||
export function mapStateToProps(state: Object, ownProps: Object) {
|
||||
const { transcribingEnabled } = state['features/base/config'];
|
||||
const { isGuest = true } = state['features/base/jwt'];
|
||||
const enabled = getFeatureFlag(state, CLOSE_CAPTIONS_ENABLED, true) && transcribingEnabled && !isGuest;
|
||||
const { visible = enabled } = ownProps;
|
||||
const enabled = getFeatureFlag(state, CLOSE_CAPTIONS_ENABLED, true);
|
||||
const abstractProps = _abstractMapStateToProps(state, ownProps);
|
||||
|
||||
return {
|
||||
..._abstractMapStateToProps(state, ownProps),
|
||||
visible
|
||||
...abstractProps,
|
||||
visible: abstractProps.visible && enabled
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ class OverflowMenuProfileItem extends Component<Props> {
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_localParticipant: getLocalParticipant(state),
|
||||
_unclickable: !state['features/base/jwt'].isGuest
|
||||
_unclickable: state['features/base/config'].disableProfile
|
||||
|| !interfaceConfig.SETTINGS_SECTIONS.includes('profile')
|
||||
};
|
||||
}
|
||||
|
||||
@@ -128,16 +128,16 @@ type Props = {
|
||||
*/
|
||||
_fullScreen: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the profile is disabled.
|
||||
*/
|
||||
_isProfileDisabled: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the tile view is enabled.
|
||||
*/
|
||||
_tileViewEnabled: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the current user is logged in through a JWT.
|
||||
*/
|
||||
_isGuest: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the current meeting belongs to a JaaS user.
|
||||
*/
|
||||
@@ -993,7 +993,7 @@ class Toolbox extends Component<Props, State> {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isProfileVisible() {
|
||||
return this.props._isGuest && this._shouldShowButton('profile');
|
||||
return !this.props._isProfileDisabled && this._shouldShowButton('profile');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1426,15 +1426,7 @@ function _mapStateToProps(state) {
|
||||
desktopSharingEnabled = getParticipants(state)
|
||||
.find(({ features = {} }) =>
|
||||
String(features['screen-sharing']) === 'true') !== undefined;
|
||||
|
||||
// we want to show button and tooltip
|
||||
if (state['features/base/jwt'].isGuest) {
|
||||
desktopSharingDisabledTooltipKey
|
||||
= 'dialog.shareYourScreenDisabledForGuest';
|
||||
} else {
|
||||
desktopSharingDisabledTooltipKey
|
||||
= 'dialog.shareYourScreenDisabled';
|
||||
}
|
||||
desktopSharingDisabledTooltipKey = 'dialog.shareYourScreenDisabled';
|
||||
}
|
||||
|
||||
// NB: We compute the buttons again here because if URL parameters were used to
|
||||
@@ -1448,7 +1440,7 @@ function _mapStateToProps(state) {
|
||||
_desktopSharingDisabledTooltipKey: desktopSharingDisabledTooltipKey,
|
||||
_dialog: Boolean(state['features/base/dialog'].component),
|
||||
_feedbackConfigured: Boolean(callStatsID),
|
||||
_isGuest: state['features/base/jwt'].isGuest,
|
||||
_isProfileDisabled: Boolean(state['features/base/config'].disableProfile),
|
||||
_isVpaasMeeting: isVpaasMeeting(state),
|
||||
_fullScreen: fullScreen,
|
||||
_tileViewEnabled: shouldDisplayTileView(state),
|
||||
|
||||
@@ -47,9 +47,6 @@ export default class Tabs extends Component<Props> {
|
||||
|
||||
return (
|
||||
<div className = 'tab-container'>
|
||||
<div className = 'tab-content'>
|
||||
{ content }
|
||||
</div>
|
||||
{ tabs.length > 1 ? (
|
||||
<div className = 'tab-buttons'>
|
||||
{
|
||||
@@ -64,6 +61,9 @@ export default class Tabs extends Component<Props> {
|
||||
}
|
||||
</div>) : null
|
||||
}
|
||||
<div className = 'tab-content'>
|
||||
{ content }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,12 +20,6 @@ import Tabs from './Tabs';
|
||||
*/
|
||||
export const ROOM_NAME_VALIDATE_PATTERN_STR = '^[^?&:\u0022\u0027%#]+$';
|
||||
|
||||
/**
|
||||
* Maximum number of pixels corresponding to a mobile layout.
|
||||
* @type {number}
|
||||
*/
|
||||
const WINDOW_WIDTH_THRESHOLD = 425;
|
||||
|
||||
/**
|
||||
* The Web container rendering the welcome page.
|
||||
*
|
||||
@@ -78,6 +72,17 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
*/
|
||||
this._additionalToolbarContentRef = null;
|
||||
|
||||
this._additionalCardRef = null;
|
||||
|
||||
/**
|
||||
* The template to use as the additional card displayed near the main one.
|
||||
*
|
||||
* @private
|
||||
* @type {HTMLTemplateElement|null}
|
||||
*/
|
||||
this._additionalCardTemplate = document.getElementById(
|
||||
'welcome-page-additional-card-template');
|
||||
|
||||
/**
|
||||
* The template to use as the main content for the welcome page. If
|
||||
* not found then only the welcome page head will display.
|
||||
@@ -102,12 +107,14 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onFormSubmit = this._onFormSubmit.bind(this);
|
||||
this._onRoomChange = this._onRoomChange.bind(this);
|
||||
this._setAdditionalCardRef = this._setAdditionalCardRef.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);
|
||||
this._renderFooter = this._renderFooter.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,6 +144,12 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
this._additionalToolbarContentTemplate.content.cloneNode(true)
|
||||
);
|
||||
}
|
||||
|
||||
if (this._shouldShowAdditionalCard()) {
|
||||
this._additionalCardRef.appendChild(
|
||||
this._additionalCardTemplate.content.cloneNode(true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,10 +172,10 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
*/
|
||||
render() {
|
||||
const { _moderatedRoomServiceUrl, t } = this.props;
|
||||
const { APP_NAME, DEFAULT_WELCOME_PAGE_LOGO_URL } = interfaceConfig;
|
||||
const { DEFAULT_WELCOME_PAGE_LOGO_URL, DISPLAY_WELCOME_FOOTER } = interfaceConfig;
|
||||
const showAdditionalCard = this._shouldShowAdditionalCard();
|
||||
const showAdditionalContent = this._shouldShowAdditionalContent();
|
||||
const showAdditionalToolbarContent = this._shouldShowAdditionalToolbarContent();
|
||||
const showResponsiveText = this._shouldShowResponsiveText();
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -172,6 +185,7 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
<div className = 'welcome-watermark'>
|
||||
<Watermarks defaultJitsiLogoURL = { DEFAULT_WELCOME_PAGE_LOGO_URL } />
|
||||
</div>
|
||||
|
||||
<div className = 'header'>
|
||||
<div className = 'welcome-page-settings'>
|
||||
<SettingsButton
|
||||
@@ -184,64 +198,82 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
}
|
||||
</div>
|
||||
<div className = 'header-image' />
|
||||
<div className = 'header-text'>
|
||||
<div className = 'header-container'>
|
||||
<h1 className = 'header-text-title'>
|
||||
{ t('welcomepage.title') }
|
||||
{ t('welcomepage.jitsiMeet') }
|
||||
</h1>
|
||||
<p className = 'header-text-description'>
|
||||
{ t('welcomepage.appDescription',
|
||||
{ app: APP_NAME }) }
|
||||
</p>
|
||||
</div>
|
||||
<div id = 'enter_room'>
|
||||
<div className = 'enter-room-input-container'>
|
||||
<div className = 'enter-room-title'>
|
||||
{ t('welcomepage.enterRoomTitle') }
|
||||
<span className = 'header-text-subtitle'>
|
||||
{ t('welcomepage.secureMeetings')}
|
||||
</span>
|
||||
<div id = 'enter_room'>
|
||||
<div className = 'enter-room-input-container'>
|
||||
<form onSubmit = { this._onFormSubmit }>
|
||||
<input
|
||||
aria-disabled = 'false'
|
||||
aria-label = 'Meeting name input'
|
||||
autoFocus = { true }
|
||||
className = 'enter-room-input'
|
||||
id = 'enter_room_field'
|
||||
onChange = { this._onRoomChange }
|
||||
pattern = { ROOM_NAME_VALIDATE_PATTERN_STR }
|
||||
placeholder = { this.state.roomPlaceholder }
|
||||
ref = { this._setRoomInputRef }
|
||||
title = { t('welcomepage.roomNameAllowedChars') }
|
||||
type = 'text'
|
||||
value = { this.state.room } />
|
||||
<div
|
||||
className = { _moderatedRoomServiceUrl
|
||||
? 'warning-with-link'
|
||||
: 'warning-without-link' }>
|
||||
{ this._renderInsecureRoomNameWarning() }
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<form onSubmit = { this._onFormSubmit }>
|
||||
<input
|
||||
autoFocus = { true }
|
||||
className = 'enter-room-input'
|
||||
id = 'enter_room_field'
|
||||
onChange = { this._onRoomChange }
|
||||
pattern = { ROOM_NAME_VALIDATE_PATTERN_STR }
|
||||
placeholder = { this.state.roomPlaceholder }
|
||||
ref = { this._setRoomInputRef }
|
||||
title = { t('welcomepage.roomNameAllowedChars') }
|
||||
type = 'text'
|
||||
value = { this.state.room } />
|
||||
{ this._renderInsecureRoomNameWarning() }
|
||||
</form>
|
||||
<button
|
||||
aria-disabled = 'false'
|
||||
aria-label = 'Start meeting'
|
||||
className = 'welcome-page-button'
|
||||
id = 'enter_room_button'
|
||||
onClick = { this._onFormSubmit }
|
||||
tabIndex = '0'
|
||||
type = 'button'>
|
||||
{ t('welcomepage.startMeeting') }
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className = 'welcome-page-button'
|
||||
id = 'enter_room_button'
|
||||
onClick = { this._onFormSubmit }>
|
||||
{
|
||||
showResponsiveText
|
||||
? t('welcomepage.goSmall')
|
||||
: t('welcomepage.go')
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{ _moderatedRoomServiceUrl && (
|
||||
<div id = 'moderated-meetings'>
|
||||
<p>
|
||||
{
|
||||
translateToHTML(
|
||||
|
||||
{ _moderatedRoomServiceUrl && (
|
||||
<div id = 'moderated-meetings'>
|
||||
<p>
|
||||
{
|
||||
translateToHTML(
|
||||
t, 'welcomepage.moderatedMessage', { url: _moderatedRoomServiceUrl })
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
) }
|
||||
{ this._renderTabs() }
|
||||
}
|
||||
</p>
|
||||
</div>)}
|
||||
</div>
|
||||
</div>
|
||||
{ showAdditionalContent
|
||||
? <div
|
||||
className = 'welcome-page-content'
|
||||
ref = { this._setAdditionalContentRef } />
|
||||
: null }
|
||||
|
||||
<div className = 'welcome-cards-container'>
|
||||
<div className = 'welcome-card-row'>
|
||||
<div className = 'welcome-tabs welcome-card welcome-card--blue'>
|
||||
{ this._renderTabs() }
|
||||
</div>
|
||||
{ showAdditionalCard
|
||||
? <div
|
||||
className = 'welcome-card welcome-card--dark'
|
||||
ref = { this._setAdditionalCardRef } />
|
||||
: null }
|
||||
</div>
|
||||
|
||||
{ showAdditionalContent
|
||||
? <div
|
||||
className = 'welcome-page-content'
|
||||
ref = { this._setAdditionalContentRef } />
|
||||
: null }
|
||||
</div>
|
||||
{ DISPLAY_WELCOME_FOOTER && this._renderFooter()}
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
@@ -302,6 +334,45 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
this.setState({ selectedTab: tabIndex });
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the footer.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderFooter() {
|
||||
const { t } = this.props;
|
||||
const {
|
||||
MOBILE_DOWNLOAD_LINK_ANDROID,
|
||||
MOBILE_DOWNLOAD_LINK_F_DROID,
|
||||
MOBILE_DOWNLOAD_LINK_IOS
|
||||
} = interfaceConfig;
|
||||
|
||||
return (<footer className = 'welcome-footer'>
|
||||
<div className = 'welcome-footer-centered'>
|
||||
<div className = 'welcome-footer-padded'>
|
||||
<div className = 'welcome-footer-row-block welcome-footer--row-1'>
|
||||
<div className = 'welcome-footer-row-1-text'>{t('welcomepage.jitsiOnMobile')}</div>
|
||||
<a
|
||||
className = 'welcome-badge'
|
||||
href = { MOBILE_DOWNLOAD_LINK_IOS }>
|
||||
<img src = './images/app-store-badge.png' />
|
||||
</a>
|
||||
<a
|
||||
className = 'welcome-badge'
|
||||
href = { MOBILE_DOWNLOAD_LINK_ANDROID }>
|
||||
<img src = './images/google-play-badge.png' />
|
||||
</a>
|
||||
<a
|
||||
className = 'welcome-badge'
|
||||
href = { MOBILE_DOWNLOAD_LINK_F_DROID }>
|
||||
<img src = './images/f-droid-badge.png' />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders tabs to show previous meetings and upcoming calendar events. The
|
||||
* tabs are purposefully hidden on mobile browsers.
|
||||
@@ -342,6 +413,19 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
tabs = { tabs } />);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal reference to the HTMLDivElement used to hold the
|
||||
* additional card shown near the tabs card.
|
||||
*
|
||||
* @param {HTMLDivElement} el - The HTMLElement for the div that is the root
|
||||
* of the welcome page content.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setAdditionalCardRef(el) {
|
||||
this._additionalCardRef = el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal reference to the HTMLDivElement used to hold the
|
||||
* welcome page content.
|
||||
@@ -380,6 +464,19 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
this._roomInputRef = el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not an additional card should be displayed near the tabs.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_shouldShowAdditionalCard() {
|
||||
return interfaceConfig.DISPLAY_WELCOME_PAGE_ADDITIONAL_CARD
|
||||
&& this._additionalCardTemplate
|
||||
&& this._additionalCardTemplate.content
|
||||
&& this._additionalCardTemplate.innerHTML.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not additional content should be displayed below
|
||||
* the welcome page's header for entering a room name.
|
||||
@@ -407,20 +504,6 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
&& this._additionalToolbarContentTemplate.content
|
||||
&& this._additionalToolbarContentTemplate.innerHTML.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the screen has a size smaller than a custom margin
|
||||
* and therefore display different text in the go button.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_shouldShowResponsiveText() {
|
||||
const { innerWidth } = window;
|
||||
|
||||
return innerWidth <= WINDOW_WIDTH_THRESHOLD;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(WelcomePage));
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
local jid = require "util.jid";
|
||||
local um_is_admin = require "core.usermanager".is_admin;
|
||||
local is_healthcheck_room = module:require "util".is_healthcheck_room;
|
||||
local util = module:require "util";
|
||||
local is_healthcheck_room = util.is_healthcheck_room;
|
||||
local extract_subdomain = util.extract_subdomain;
|
||||
|
||||
local moderated_subdomains;
|
||||
local moderated_rooms;
|
||||
@@ -22,11 +24,14 @@ end
|
||||
-- -> true, room_name, subdomain
|
||||
-- -> true, room_name, nil (if no subdomain is used for the room)
|
||||
local function is_moderated(room_jid)
|
||||
if #moderated_subdomains == 0 and #moderated_rooms == 0 then
|
||||
return false;
|
||||
end
|
||||
|
||||
local room_node = jid.node(room_jid);
|
||||
-- parses bare room address, for multidomain expected format is:
|
||||
-- [subdomain]roomName@conference.domain
|
||||
local target_subdomain, target_room_name = room_node:match("^%[([^%]]+)%](.+)$");
|
||||
|
||||
local target_subdomain, target_room_name = extract_subdomain(room_node);
|
||||
if target_subdomain then
|
||||
if moderated_subdomains:contains(target_subdomain) then
|
||||
return true, target_room_name, target_subdomain;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
local ext_events = module:require "ext_events"
|
||||
local jid = require "util.jid"
|
||||
local extract_subdomain = module:require "util".extract_subdomain;
|
||||
|
||||
-- Options and configuration
|
||||
local poltergeist_component = module:get_option_string(
|
||||
@@ -33,7 +34,7 @@ local function url_from_room_jid(room_jid)
|
||||
local node, _, _ = jid.split(room_jid)
|
||||
if not node then return nil end
|
||||
|
||||
local target_subdomain, target_node = node:match("^%[([^%]]+)%](.+)$")
|
||||
local target_subdomain, target_node = extract_subdomain(node);
|
||||
|
||||
if not(target_node or target_subdomain) then
|
||||
return "https://"..muc_domain_base.."/"..node
|
||||
|
||||
@@ -9,7 +9,9 @@ local jid = require "util.jid";
|
||||
local json_safe = require "cjson.safe";
|
||||
local path = require "util.paths";
|
||||
local sha256 = require "util.hashes".sha256;
|
||||
local http_get_with_retry = module:require "util".http_get_with_retry;
|
||||
local main_util = module:require "util";
|
||||
local http_get_with_retry = main_util.http_get_with_retry;
|
||||
local extract_subdomain = main_util.extract_subdomain;
|
||||
|
||||
local nr_retries = 3;
|
||||
|
||||
@@ -350,7 +352,7 @@ function Util:verify_room(session, room_address)
|
||||
local room_node = jid.node(room_address);
|
||||
-- parses bare room address, for multidomain expected format is:
|
||||
-- [subdomain]roomName@conference.domain
|
||||
local target_subdomain, target_room = room_node:match("^%[([^%]]+)%](.+)$");
|
||||
local target_subdomain, target_room = extract_subdomain(room_node);
|
||||
|
||||
-- if we have '*' as room name in token, this means all rooms are allowed
|
||||
-- so we will use the actual name of the room when constructing strings
|
||||
|
||||
@@ -33,6 +33,12 @@ local roomless_iqs = {};
|
||||
-- (e.g. from room1@conference.foo.example.com/res returns (room1, example.com, res, foo))
|
||||
local function room_jid_split_subdomain(room_jid)
|
||||
local node, host, resource = jid.split(room_jid);
|
||||
|
||||
-- optimization, skip matching if there is no subdomain or it is not the muc component address at all
|
||||
if host == muc_domain or not starts_with(host, muc_domain_prefix) then
|
||||
return node, host, resource;
|
||||
end
|
||||
|
||||
local target_subdomain = host and host:match(target_subdomain_pattern);
|
||||
return node, host, resource, target_subdomain
|
||||
end
|
||||
@@ -80,11 +86,13 @@ local function internal_room_jid_match_rewrite(room_jid, stanza)
|
||||
|
||||
return room_jid;
|
||||
end
|
||||
local target_subdomain, target_node = node:match("^%[([^%]]+)%](.+)$");
|
||||
|
||||
local target_subdomain, target_node = extract_subdomain(node);
|
||||
if not (target_node and target_subdomain) then
|
||||
-- module:log("debug", "Not rewriting... unexpected node format: %s", node);
|
||||
return room_jid;
|
||||
end
|
||||
|
||||
-- Ok, rewrite room_jid address to pretty format
|
||||
local new_node, new_host, new_resource = target_node, muc_domain_prefix..".".. target_subdomain.."."..muc_domain_base, resource;
|
||||
room_jid = jid.join(new_node, new_host, new_resource);
|
||||
@@ -183,6 +191,7 @@ function update_presence_identity(
|
||||
|
||||
stanza:tag("identity"):tag("user");
|
||||
for k, v in pairs(user) do
|
||||
v = tostring(v)
|
||||
stanza:tag(k):text(v):up();
|
||||
end
|
||||
stanza:up();
|
||||
@@ -225,6 +234,17 @@ function is_feature_allowed(session, feature)
|
||||
end
|
||||
end
|
||||
|
||||
--- Extracts the subdomain and room name from internal jid node [foo]room1
|
||||
-- @return subdomain(optional, if extracted or nil), the room name
|
||||
function extract_subdomain(room_node)
|
||||
-- optimization, skip matching if there is no subdomain, no [subdomain] part in the beginning of the node
|
||||
if not starts_with(room_node, '[') then
|
||||
return room_node;
|
||||
end
|
||||
|
||||
return room_node:match("^%[([^%]]+)%](.+)$");
|
||||
end
|
||||
|
||||
function starts_with(str, start)
|
||||
return str:sub(1, #start) == start
|
||||
end
|
||||
@@ -305,6 +325,7 @@ function http_get_with_retry(url, retry)
|
||||
end
|
||||
|
||||
return {
|
||||
extract_subdomain = extract_subdomain;
|
||||
is_feature_allowed = is_feature_allowed;
|
||||
is_healthcheck_room = is_healthcheck_room;
|
||||
get_room_from_jid = get_room_from_jid;
|
||||
|
||||
1
static/welcomePageAdditionalCard.html
Normal file
@@ -0,0 +1 @@
|
||||
<template id = "welcome-page-additional-card-template"></template>
|
||||