Merge remote-tracking branch 'upstream/develop' into styled-components-8

This commit is contained in:
Ramón Souza 2021-11-03 14:37:07 +00:00
commit f1281a77e3
78 changed files with 1790 additions and 1251 deletions

View File

@ -11,7 +11,7 @@ stages:
# define which docker image to use for builds
default:
image: gitlab.senfcall.de:5050/senfcall-public/docker-bbb-build:v2021-10-25
image: gitlab.senfcall.de:5050/senfcall-public/docker-bbb-build:v2021-11-01
# This stage uses git to find out since when each package has been unmodified.
# it then checks an API endpoint on the package server to find out for which of

View File

@ -60,6 +60,7 @@ public class ApiParams {
public static final String WEBCAMS_ONLY_FOR_MODERATOR = "webcamsOnlyForModerator";
public static final String WELCOME = "welcome";
public static final String HTML5_INSTANCE_ID = "html5InstanceId";
public static final String ROLE = "role";
public static final String BREAKOUT_ROOMS_ENABLED = "breakoutRoomsEnabled";
public static final String BREAKOUT_ROOMS_RECORD = "breakoutRoomsRecord";

View File

@ -74,6 +74,10 @@ import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;
public class MeetingService implements MessageListener {
private static Logger log = LoggerFactory.getLogger(MeetingService.class);
@ -1189,6 +1193,13 @@ public class MeetingService implements MessageListener {
log.info("Starting Meeting Service.");
try {
processMessage = true;
Process p = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", "cat /etc/bigbluebutton/bigbluebutton-release | cut -d '=' -f2"});
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String apiVersionFromFile = reader.readLine();
paramsProcessorUtil.setBbbVersion(apiVersionFromFile);
Runnable messageReceiver = new Runnable() {
public void run() {
while (processMessage) {

View File

@ -108,31 +108,34 @@ public class ParamsProcessorUtil {
private Long maxPresentationFileUpload = 30000000L; // 30MB
private Integer clientLogoutTimerInMinutes = 0;
private Integer meetingExpireIfNoUserJoinedInMinutes = 5;
private Integer meetingExpireWhenLastUserLeftInMinutes = 1;
private Integer userInactivityInspectTimerInMinutes = 120;
private Integer userInactivityThresholdInMinutes = 30;
private Integer meetingExpireIfNoUserJoinedInMinutes = 5;
private Integer meetingExpireWhenLastUserLeftInMinutes = 1;
private Integer userInactivityInspectTimerInMinutes = 120;
private Integer userInactivityThresholdInMinutes = 30;
private Integer userActivitySignResponseDelayInMinutes = 5;
private Boolean defaultAllowDuplicateExtUserid = true;
private Boolean defaultEndWhenNoModerator = false;
private Integer defaultEndWhenNoModeratorDelayInMinutes = 1;
private Integer defaultHtml5InstanceId = 1;
private Boolean defaultEndWhenNoModerator = false;
private Integer defaultEndWhenNoModeratorDelayInMinutes = 1;
private Integer defaultHtml5InstanceId = 1;
private String formatConfNum(String s) {
if (s.length() > 5) {
/* Reverse conference number.
* Put a whitespace every third char.
* Reverse it again to display it correctly.
* Trim leading whitespaces.
* */
String confNumReversed = new StringBuilder(s).reverse().toString();
String confNumSplit = confNumReversed.replaceAll("(.{3})", "$1 ");
String confNumL = new StringBuilder(confNumSplit).reverse().toString().trim();
return confNumL;
}
private String bbbVersion = "";
private Boolean allowRevealOfBBBVersion = false;
return s;
}
private String formatConfNum(String s) {
if (s.length() > 5) {
/* Reverse conference number.
* Put a whitespace every third char.
* Reverse it again to display it correctly.
* Trim leading whitespaces.
* */
String confNumReversed = new StringBuilder(s).reverse().toString();
String confNumSplit = confNumReversed.replaceAll("(.{3})", "$1 ");
String confNumL = new StringBuilder(confNumSplit).reverse().toString().trim();
return confNumL;
}
return s;
}
private String substituteKeywords(String message, String dialNumber, String telVoice, String meetingName) {
String welcomeMessage = message;
@ -659,6 +662,14 @@ public class ParamsProcessorUtil {
}
}
public String getBbbVersion() {
return bbbVersion;
}
public Boolean getAllowRevealOfBBBVersion() {
return allowRevealOfBBBVersion;
}
public String processWelcomeMessage(String message, Boolean isBreakout) {
String welcomeMessage = message;
if (StringUtils.isEmpty(message)) {
@ -1174,8 +1185,16 @@ public class ParamsProcessorUtil {
this.defaultEndWhenNoModerator = val;
}
public void setEndWhenNoModeratorDelayInMinutes(Integer value) {
this.defaultEndWhenNoModeratorDelayInMinutes = value;
}
public void setEndWhenNoModeratorDelayInMinutes(Integer value) {
this.defaultEndWhenNoModeratorDelayInMinutes = value;
}
public void setBbbVersion(String version) {
this.bbbVersion = this.allowRevealOfBBBVersion ? version : "";
}
public void setAllowRevealOfBBBVersion(Boolean allowVersion) {
this.allowRevealOfBBBVersion = allowVersion;
}
}

View File

@ -14,7 +14,8 @@ public class JoinMeeting extends RequestWithChecksum<JoinMeeting.Params> {
PASSWORD("password"),
GUEST("guest"),
AUTH("auth"),
CREATE_TIME("createTime");
CREATE_TIME("createTime"),
ROLE("role");
private final String value;
@ -49,6 +50,8 @@ public class JoinMeeting extends RequestWithChecksum<JoinMeeting.Params> {
private String createTimeString;
private Long createTime;
private String role;
public JoinMeeting(Checksum checksum) {
super(checksum);
}
@ -115,6 +118,14 @@ public class JoinMeeting extends RequestWithChecksum<JoinMeeting.Params> {
this.createTime = createTime;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
@Override
public void populateFromParamsMap(Map<String, String[]> params) {
if(params.containsKey(Params.MEETING_ID.getValue())) {
@ -127,6 +138,7 @@ public class JoinMeeting extends RequestWithChecksum<JoinMeeting.Params> {
if(params.containsKey(Params.GUEST.getValue())) setGuestString(params.get(Params.GUEST.getValue())[0]);
if(params.containsKey(Params.AUTH.getValue())) setAuthString(params.get(Params.AUTH.getValue())[0]);
if(params.containsKey(Params.CREATE_TIME.getValue())) setCreateTimeString(params.get(Params.CREATE_TIME.getValue())[0]);
if(params.containsKey(Params.ROLE.getValue())) setRole(params.get(Params.ROLE.getValue())[0]);
}
@Override

View File

@ -52,12 +52,13 @@ public class ResponseBuilder {
return new Date(timestamp).toString();
}
public String buildMeetingVersion(String version, String returnCode) {
public String buildMeetingVersion(String apiVersion, String bbbVersion, String returnCode) {
StringWriter xmlText = new StringWriter();
Map<String, Object> data = new HashMap<String, Object>();
data.put("returnCode", returnCode);
data.put("version", version);
data.put("apiVersion", apiVersion);
data.put("bbbVersion", bbbVersion);
processData(getTemplate("api-version.ftlx"), data, xmlText);

View File

@ -5,16 +5,16 @@
meteor-base@1.5.1
mobile-experience@1.1.0
mongo@1.12.0
mongo@1.13.0
reactive-var@1.0.11
standard-minifier-css@1.7.3
standard-minifier-js@2.6.1
standard-minifier-css@1.7.4
standard-minifier-js@2.7.1
es5-shim@4.8.0
ecmascript@0.15.3
ecmascript@0.16.0
shell-server@0.5.0
static-html
static-html@1.3.2
react-meteor-data
http@1.4.2
session@1.2.0

View File

@ -1 +1 @@
METEOR@2.3.6
METEOR@2.5

View File

@ -1,5 +1,5 @@
allow-deny@1.1.0
autoupdate@1.7.0
autoupdate@1.8.0
babel-compiler@7.7.0
babel-runtime@1.5.0
base64@1.0.12
@ -8,20 +8,20 @@ blaze-tools@1.1.2
boilerplate-generator@1.7.1
caching-compiler@1.2.2
caching-html-compiler@1.2.1
callback-hook@1.3.1
callback-hook@1.4.0
cfs:reactive-list@0.0.9
check@1.3.1
ddp@1.4.0
ddp-client@2.5.0
ddp-common@1.4.0
ddp-server@2.4.1
ddp-server@2.5.0
deps@1.0.12
diff-sequence@1.1.1
dynamic-import@0.7.1
ecmascript@0.15.3
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.11.1
ecmascript-runtime-server@0.10.1
dynamic-import@0.7.2
ecmascript@0.16.0
ecmascript-runtime@0.8.0
ecmascript-runtime-client@0.12.1
ecmascript-runtime-server@0.11.0
ejson@1.1.1
es5-shim@4.8.0
fetch@0.1.1
@ -34,21 +34,21 @@ id-map@1.1.1
inter-process-messaging@0.1.1
launch-screen@1.3.0
lmieulet:meteor-coverage@3.2.0
logging@1.2.0
meteor@1.9.3
logging@1.3.1
meteor@1.10.0
meteor-base@1.5.1
meteortesting:browser-tests@1.3.4
meteortesting:mocha@2.0.2
meteortesting:mocha-core@8.0.1
minifier-css@1.5.4
minifier-js@2.6.1
minifier-css@1.6.0
minifier-js@2.7.1
minimongo@1.7.0
mobile-experience@1.1.0
mobile-status-bar@1.1.0
modern-browsers@0.1.5
modules@0.16.0
modern-browsers@0.1.7
modules@0.17.0
modules-runtime@0.12.0
mongo@1.12.0
mongo@1.13.0
mongo-decimal@0.1.2
mongo-dev-server@1.1.0
mongo-id@1.0.8
@ -57,7 +57,7 @@ npm-mongo@3.9.1
ordered-dict@1.1.0
promise@0.12.0
random@1.2.0
react-fast-refresh@0.1.1
react-fast-refresh@0.2.0
react-meteor-data@0.2.16
reactive-dict@1.3.0
reactive-var@1.0.11
@ -69,13 +69,13 @@ session@1.2.0
shell-server@0.5.0
socket-stream-client@0.4.0
spacebars-compiler@1.3.0
standard-minifier-css@1.7.3
standard-minifier-js@2.6.1
standard-minifier-css@1.7.4
standard-minifier-js@2.7.1
static-html@1.3.2
templating-tools@1.2.1
tmeasday:check-npm-versions@0.3.2
tracker@1.2.0
underscore@1.0.10
url@1.3.2
webapp@1.11.1
webapp@1.13.0
webapp-hashing@1.1.0

View File

@ -1,4 +1,4 @@
#!/bin/sh -e
#!/bin/sh -ex
# Please check bigbluebutton/bigbluebutton-html5/dev_local_deployment/README.md
@ -22,21 +22,18 @@ if [ -d "node_modules" ]; then
rm -r node_modules/
fi
meteor reset
meteor npm install --production
meteor npm ci --production
sudo chmod 777 /usr/share/meteor
METEOR_DISABLE_OPTIMISTIC_CACHING=1 meteor build $UPPER_DESTINATION_DIR --architecture os.linux.x86_64 --allow-superuser
METEOR_DISABLE_OPTIMISTIC_CACHING=1 meteor build $UPPER_DESTINATION_DIR --architecture os.linux.x86_64 --allow-superuser --directory
sudo chown -R meteor:meteor "$UPPER_DESTINATION_DIR"/
echo 'stage3'
tar -xzf $UPPER_DESTINATION_DIR/bigbluebutton-html5.tar.gz -C $UPPER_DESTINATION_DIR
cd "$DESTINATION_DIR"/programs/server/ || exit
sudo npm i --production
sudo npm i
echo "deployed to $DESTINATION_DIR/programs/server\n\n\n"
echo "writing $DESTINATION_DIR/mongod_start_pre.sh"

View File

@ -50,6 +50,7 @@ const PresentationOptionsContainer = ({
return (
<Styled.RestorePresentationButton
icon={`${buttonType}${isLayoutSwapped ? '_off' : ''}`}
data-test="restorePresentationButton"
label={intl.formatMessage(isLayoutSwapped ? intlMessages.restorePresentationLabel : intlMessages.minimizePresentationLabel)}
aria-label={intl.formatMessage(isLayoutSwapped ? intlMessages.restorePresentationLabel : intlMessages.minimizePresentationLabel)}
aria-describedby={intl.formatMessage(isLayoutSwapped ? intlMessages.restorePresentationDesc : intlMessages.minimizePresentationDesc)}

View File

@ -6,7 +6,7 @@ import _ from 'lodash';
import AudioService from '/imports/ui/components/audio/service';
import ChatPushAlert from './push-alert/component';
import Service from '../service';
import { styles } from '../styles';
import Styled from './styles';
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_CHAT_CLEAR = CHAT_CONFIG.chat_clear;
@ -148,15 +148,15 @@ const ChatAlert = (props) => {
};
const createMessage = (name, message) => (
<div className={styles.pushMessageContent}>
<h3 className={styles.userNameMessage}>{name}</h3>
<div className={styles.contentMessage}>
<Styled.PushMessageContent>
<Styled.UserNameMessage>{name}</Styled.UserNameMessage>
<Styled.ContentMessage>
{
mapContentText(message)
.reduce((acc, text) => [...acc, (<br key={_.uniqueId('br_')} />), text], [])
}
</div>
</div>
</Styled.ContentMessage>
</Styled.PushMessageContent>
);
return pushAlertEnabled

View File

@ -0,0 +1,45 @@
import styled from 'styled-components';
import { colorGrayDark } from '/imports/ui/stylesheets/styled-components/palette';
import { borderRadius } from '/imports/ui/stylesheets/styled-components/general';
import { fontSizeSmall } from '/imports/ui/stylesheets/styled-components/typography';
const PushMessageContent = styled.div`
margin-top: 1.4rem;
margin-bottom: .4rem;
margin-left: .4rem;
margin-right: .4rem;
background-color: inherit;
width: 98%;
`;
const UserNameMessage = styled.h3`
margin: 0;
font-size: 80%;
color: ${colorGrayDark};
font-weight: bold;
background-color: inherit;
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1em;
max-height: 1em;
`;
const ContentMessage = styled.div`
margin-top: ${borderRadius};
font-size: 80%;
background-color: inherit;
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: ${fontSizeSmall};
max-height: calc(${fontSizeSmall} * 10);
`;
export default {
PushMessageContent,
UserNameMessage,
ContentMessage,
};

View File

@ -1,30 +0,0 @@
.btn {
--icon-offset: -.4em;
--square-side-length: 1.56rem;
z-index: 3;
flex: 0 0;
margin-top: auto;
cursor: pointer;
span:first-child {
width: var(--square-side-length);
height: var(--square-side-length);
}
i {
color: var(--color-gray-dark) !important;
top: var(--icon-offset);
left: var(--icon-offset);
}
&:hover,
&:focus {
> span:first-child {
background-color: transparent !important;
}
}
}

View File

@ -6,13 +6,14 @@ import Button from '/imports/ui/components/button/component';
import withShortcutHelper from '/imports/ui/components/shortcut-help/service';
import { Meteor } from 'meteor/meteor';
import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger';
import { styles } from './styles.scss';
import Styled from './styles';
import MessageFormContainer from './message-form/container';
import TimeWindowList from './time-window-list/container';
import ChatDropdownContainer from './chat-dropdown/container';
import { PANELS, ACTIONS } from '../layout/enums';
import { UserSentMessageCollection } from './service';
import Auth from '/imports/ui/services/auth';
import browserInfo from '/imports/utils/browserInfo';
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id;
@ -55,21 +56,19 @@ const Chat = (props) => {
} = props;
const userSentMessage = UserSentMessageCollection.findOne({ userId: Auth.userID, sent: true });
const { isChrome } = browserInfo;
const HIDE_CHAT_AK = shortcuts.hideprivatechat;
const CLOSE_CHAT_AK = shortcuts.closeprivatechat;
ChatLogger.debug('ChatComponent::render', props);
return (
<div
<Styled.Chat
isChrome={isChrome}
data-test={chatID !== PUBLIC_CHAT_ID ? 'privateChat' : 'publicChat'}
className={styles.chat}
>
<header className={styles.header}>
<div
data-test="chatTitle"
className={styles.title}
>
<Button
<Styled.Header>
<Styled.Title data-test="chatTitle">
<Styled.HideChatButton
onClick={() => {
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
@ -88,9 +87,8 @@ const Chat = (props) => {
accessKey={chatID !== 'public' ? HIDE_CHAT_AK : null}
label={title}
icon="left_arrow"
className={styles.hideBtn}
/>
</div>
</Styled.Title>
{
chatID !== PUBLIC_CHAT_ID
? (
@ -127,7 +125,7 @@ const Chat = (props) => {
/>
)
}
</header>
</Styled.Header>
<TimeWindowList
id={ELEMENT_ID}
chatId={chatID}
@ -160,7 +158,7 @@ const Chat = (props) => {
locked={isChatLocked}
partnerIsLoggedOut={partnerIsLoggedOut}
/>
</div>
</Styled.Chat>
);
};

View File

@ -1,13 +1,10 @@
import React, { PureComponent } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import cx from 'classnames';
import TextareaAutosize from 'react-autosize-textarea';
import deviceInfo from '/imports/utils/deviceInfo';
import PropTypes from 'prop-types';
import _ from 'lodash';
import TypingIndicatorContainer from './typing-indicator/container';
import { styles } from './styles.scss';
import Button from '../../button/component';
import Styled from './styles';
const propTypes = {
intl: PropTypes.object.isRequired,
@ -16,7 +13,6 @@ const propTypes = {
minMessageLength: PropTypes.number.isRequired,
maxMessageLength: PropTypes.number.isRequired,
chatTitle: PropTypes.string.isRequired,
className: PropTypes.string,
chatAreaId: PropTypes.string.isRequired,
handleSendMessage: PropTypes.func.isRequired,
UnsentMessagesCollection: PropTypes.objectOf(Object).isRequired,
@ -27,10 +23,6 @@ const propTypes = {
startUserTyping: PropTypes.func.isRequired,
};
const defaultProps = {
className: '',
};
const messages = defineMessages({
submitLabel: {
id: 'app.chat.submitLabel',
@ -261,7 +253,6 @@ class MessageForm extends PureComponent {
chatTitle,
title,
disabled,
className,
idChatOpen,
partnerIsLoggedOut,
} = this.props;
@ -269,14 +260,12 @@ class MessageForm extends PureComponent {
const { hasErrors, error, message } = this.state;
return CHAT_ENABLED ? (
<form
<Styled.Form
ref={(ref) => { this.form = ref; }}
className={cx(className, styles.form)}
onSubmit={this.handleSubmit}
>
<div className={styles.wrapper}>
<TextareaAutosize
className={styles.input}
<Styled.Wrapper>
<Styled.Input
id="message-input"
innerRef={(ref) => { this.textarea = ref; return this.textarea; }}
placeholder={intl.formatMessage(messages.inputPlaceholder, { 0: title })}
@ -291,10 +280,9 @@ class MessageForm extends PureComponent {
onKeyDown={this.handleMessageKeyDown}
async
/>
<Button
<Styled.SendButton
hideLabel
circle
className={styles.sendButton}
aria-label={intl.formatMessage(messages.submitLabel)}
type="submit"
disabled={disabled || partnerIsLoggedOut}
@ -304,14 +292,13 @@ class MessageForm extends PureComponent {
onClick={() => { }}
data-test="sendMessageButton"
/>
</div>
</Styled.Wrapper>
<TypingIndicatorContainer {...{ idChatOpen, error }} />
</form>
</Styled.Form>
) : null;
}
}
MessageForm.propTypes = propTypes;
MessageForm.defaultProps = defaultProps;
export default injectIntl(MessageForm);

View File

@ -0,0 +1,92 @@
import styled from 'styled-components';
import {
colorBlueLight,
colorText,
colorGrayLighter,
colorPrimary,
} from '/imports/ui/stylesheets/styled-components/palette';
import {
smPaddingX,
smPaddingY,
borderRadius,
borderSize,
} from '/imports/ui/stylesheets/styled-components/general';
import { fontSizeBase } from '/imports/ui/stylesheets/styled-components/typography';
import TextareaAutosize from 'react-autosize-textarea';
import Button from '/imports/ui/components/button/component';
const Form = styled.form`
flex-grow: 0;
flex-shrink: 0;
align-self: flex-end;
width: 100%;
position: relative;
margin-bottom: calc(-1 * ${smPaddingX});
margin-top: .2rem;
`;
const Wrapper = styled.div`
display: flex;
flex-direction: row;
`;
const Input = styled(TextareaAutosize)`
flex: 1;
background: #fff;
background-clip: padding-box;
margin: 0;
color: ${colorText};
-webkit-appearance: none;
padding: calc(${smPaddingY} * 2.5) calc(${smPaddingX} * 1.25);
resize: none;
transition: none;
border-radius: ${borderRadius};
font-size: ${fontSizeBase};
line-height: 1;
min-height: 2.5rem;
max-height: 10rem;
border: 1px solid ${colorGrayLighter};
box-shadow: 0 0 0 1px ${colorGrayLighter};
&:disabled,
&[disabled] {
cursor: not-allowed;
opacity: .75;
background-color: rgba(167,179,189,0.25);
}
&:focus {
border-radius: ${borderSize};
box-shadow: 0 0 0 ${borderSize} ${colorBlueLight}, inset 0 0 0 1px ${colorPrimary};
}
&:hover,
&:active,
&:focus {
outline: transparent;
outline-style: dotted;
outline-width: ${borderSize};
}
`;
const SendButton = styled(Button)`
margin:0 0 0 ${smPaddingX};
align-self: center;
font-size: 0.9rem;
[dir="rtl"] & {
margin: 0 ${smPaddingX} 0 0;
-webkit-transform: scale(-1, 1);
-moz-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
-o-transform: scale(-1, 1);
transform: scale(-1, 1);
}
`;
export default {
Form,
Wrapper,
Input,
SendButton,
};

View File

@ -1,182 +0,0 @@
@import "/imports/ui/stylesheets/mixins/focus";
@import "/imports/ui/stylesheets/mixins/_indicators";
@import "/imports/ui/stylesheets/variables/placeholders";
:root {
--max-chat-input-msg-height: .93rem;
}
.form {
flex-grow: 0;
flex-shrink: 0;
align-self: flex-end;
width: 100%;
position: relative;
margin-bottom: calc(-1 * var(--sm-padding-x));
margin-top: .2rem;
}
.wrapper {
display: flex;
flex-direction: row;
}
.actions {
display: flex;
align-items: center;
justify-content: center;
flex-grow: 0;
flex-shrink: 0;
border: var(--border-size) solid var(--color-gray-lighter);
background-color: #fff;
border-radius: var(--border-radius) 0 0 var(--border-radius);
color: var(--color-gray-light);
padding: var(--sm-padding-y) var(--sm-padding-x);
cursor: pointer;
:global(.animationsEnabled) & {
transition: all .3s;
}
--bg-faded: rgba(167,179,189,0.25);
&:hover,
&:focus {
background-color: var(--bg-faded);
}
&:disabled,
&[disabled] {
cursor: not-allowed;
opacity: .75;
background-color: var(--bg-faded);
}
}
.input {
@include inputFocus(var(--color-blue-light));
flex: 1;
background: #fff;
background-clip: padding-box;
margin: 0;
color: var(--color-text);
-webkit-appearance: none;
padding: calc(var(--sm-padding-y) * 2.5) calc(var(--sm-padding-x) * 1.25);
resize: none;
transition: none;
border-radius: var(--border-radius);
font-size: var(--font-size-base);
line-height: 1;
min-height: 2.5rem;
max-height: 10rem;
border: 1px solid var(--color-gray-lighter);
box-shadow: 0 0 0 1px var(--color-gray-lighter);
&:disabled,
&[disabled] {
cursor: not-allowed;
opacity: .75;
background-color: rgba(167,179,189,0.25);
}
&:hover,
&:active,
&:focus {
@extend %highContrastOutline;
}
}
.sendButton {
margin:0 0 0 var(--sm-padding-x);
align-self: center;
font-size: 0.9rem;
[dir="rtl"] & {
margin: 0 var(--sm-padding-x) 0 0;
-webkit-transform: scale(-1, 1);
-moz-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
-o-transform: scale(-1, 1);
transform: scale(-1, 1);
}
}
.error,
.info {
font-size: calc(var(--font-size-base) * .75);
color: var(--color-gray-dark);
text-align: left;
padding: var(--border-size) 0;
position: relative;
}
.error,
.info,
.spacer {
height: var(--max-chat-input-msg-height);
max-height: var(--max-chat-input-msg-height);
}
.coupleTyper,
.singleTyper {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: bold;
font-size: var(--font-size-smaller);
}
.singleTyper {
max-width: 70%;
}
.coupleTyper {
max-width: 25%;
}
.typingIndicator {
display: flex;
flex-direction: row;
> span {
display: block;
margin-right: 0.05rem;
margin-left: 0.05rem;
line-height: var(--font-size-md);
}
text-align: left;
[dir="rtl"] & {
text-align: right;
}
}
.error {
color: var(--color-danger);
}
.connectingAnimation {
margin: auto;
display: inline-block;
width: 1.5em;
&:after {
overflow: hidden;
display: inline-block;
vertical-align: bottom;
content: "\2026"; /* ascii code for the ellipsis character */
width: 0;
margin-left: 0.25em;
:global(.animationsEnabled) & {
animation: ellipsis steps(4, end) 900ms infinite;
}
}
}
@keyframes ellipsis {
to {
width: 1.5em;
}
}

View File

@ -3,8 +3,7 @@ import {
defineMessages, injectIntl, FormattedMessage,
} from 'react-intl';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { styles } from '../styles.scss';
import Styled from './styles';
const propTypes = {
intl: PropTypes.object.isRequired,
@ -46,10 +45,10 @@ class TypingIndicator extends PureComponent {
id="app.chat.one.typing"
description="label used when one user is typing"
values={{
0: <span className={styles.singleTyper}>
0: <Styled.SingleTyper>
{`${name}`}
&nbsp;
</span>,
</Styled.SingleTyper>,
}}
/>
);
@ -63,15 +62,15 @@ class TypingIndicator extends PureComponent {
id="app.chat.two.typing"
description="label used when two users are typing"
values={{
0: <span className={styles.coupleTyper}>
0: <Styled.CoupleTyper>
{`${name}`}
&nbsp;
</span>,
1: <span className={styles.coupleTyper}>
</Styled.CoupleTyper>,
1: <Styled.CoupleTyper>
&nbsp;
{`${name2}`}
&nbsp;
</span>,
</Styled.CoupleTyper>,
}}
/>
);
@ -96,15 +95,14 @@ class TypingIndicator extends PureComponent {
const typingElement = indicatorEnabled ? this.renderTypingElement() : null;
const style = {};
style[styles.error] = !!error;
style[styles.info] = !error;
style[styles.spacer] = !!typingElement;
return (
<div className={cx(style)}>
<span className={styles.typingIndicator}>{error || typingElement}</span>
</div>
<Styled.TypingIndicatorWrapper
error={!!error}
info={!error}
spacer={!!typingElement}
>
<Styled.TypingIndicator>{error || typingElement}</Styled.TypingIndicator>
</Styled.TypingIndicatorWrapper>
);
}
}

View File

@ -0,0 +1,74 @@
import styled from 'styled-components';
import { colorDanger, colorGrayDark } from '/imports/ui/stylesheets/styled-components/palette';
import { borderSize } from '/imports/ui/stylesheets/styled-components/general';
import { fontSizeSmaller, fontSizeMD, fontSizeBase } from '/imports/ui/stylesheets/styled-components/typography';
const SingleTyper = styled.span`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: bold;
font-size: ${fontSizeSmaller};
max-width: 70%;
`;
const CoupleTyper = styled.span`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: bold;
font-size: ${fontSizeSmaller};
max-width: 25%;
`;
const TypingIndicator = styled.span`
display: flex;
flex-direction: row;
> span {
display: block;
margin-right: 0.05rem;
margin-left: 0.05rem;
line-height: ${fontSizeMD};
}
text-align: left;
[dir="rtl"] & {
text-align: right;
}
`;
const TypingIndicatorWrapper = styled.div`
${({ error }) => error && `
color: ${colorDanger};
font-size: calc(${fontSizeBase} * .75);
color: ${colorGrayDark};
text-align: left;
padding: ${borderSize} 0;
position: relative;
height: .93rem;
max-height: .93rem;
`}
${({ info }) => info && `
font-size: calc(${fontSizeBase} * .75);
color: ${colorGrayDark};
text-align: left;
padding: ${borderSize} 0;
position: relative;
height: .93rem;
max-height: .93rem;
`}
${({ spacer }) => spacer && `
height: .93rem;
max-height: .93rem;
`}
`;
export default {
SingleTyper,
CoupleTyper,
TypingIndicator,
TypingIndicatorWrapper,
};

View File

@ -0,0 +1,92 @@
import styled from 'styled-components';
import {
colorWhite,
colorGrayDark,
} from '/imports/ui/stylesheets/styled-components/palette';
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
import Button from '/imports/ui/components/button/component';
import {
mdPaddingX,
mdPaddingY,
pollHeaderOffset,
borderSizeLarge,
borderSize,
} from '/imports/ui/stylesheets/styled-components/general';
import { DivElipsis } from '/imports/ui/stylesheets/styled-components/placeholders';
const Chat = styled.div`
background-color: ${colorWhite};
padding: ${mdPaddingX} ${mdPaddingY} ${mdPaddingX} ${mdPaddingX};
display: flex;
flex-grow: 1;
flex-direction: column;
justify-content: space-around;
overflow: hidden;
height: 100%;
${({ isChrome }) => isChrome && `
transform: translateZ(0);
`}
@media ${smallOnly} {
transform: none !important;
}
`;
const Header = styled.header`
position: relative;
top: ${pollHeaderOffset};
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
`;
const Title = styled(DivElipsis)`
flex: 1;
& > button, button:hover {
max-width: 98%;
}
`;
const HideChatButton = styled(Button)`
position: relative;
background-color: ${colorWhite};
display: block;
margin: ${borderSizeLarge};
margin-bottom: ${borderSize};
padding-left: 0;
padding-right: inherit;
z-index: 3;
[dir="rtl"] & {
padding-left: inherit;
padding-right: 0;
}
& > i {
color: ${colorGrayDark};
font-size: smaller;
[dir="rtl"] & {
-webkit-transform: scale(-1, 1);
-moz-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
-o-transform: scale(-1, 1);
transform: scale(-1, 1);
}
}
&:hover {
background-color: ${colorWhite};
}
`;
export default {
Chat,
Header,
Title,
HideChatButton,
};

View File

@ -1,124 +0,0 @@
@import "/imports/ui/stylesheets/mixins/focus";
@import "/imports/ui/stylesheets/variables/breakpoints";
@import "/imports/ui/stylesheets/variables/placeholders";
:root {
--toast-content-width: 98%;
--toast-font-size: 80%;
--toast-content-margin-sm: .4rem;
--toast-content-margin-md: 1.4rem;
}
@mixin lineClamp($lineHeight: 1em, $lineCount: 1) {
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: $lineHeight;
max-height: calc(#{"$lineHeight * $lineCount"});
}
.chat {
background-color: var(--color-white);
padding:
var(--md-padding-x)
var(--md-padding-y)
var(--md-padding-x)
var(--md-padding-x);
display: flex;
flex-grow: 1;
flex-direction: column;
justify-content: space-around;
overflow: hidden;
height: 100%;
:global(.browser-chrome) & {
transform: translateZ(0);
}
@include mq($small-only) {
transform: none !important;
}
}
.header {
position: relative;
top: var(--poll-header-offset);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.title {
@extend %text-elipsis;
flex: 1;
& > button, button:hover {
max-width: var(--toast-content-width);
}
}
.hideBtn {
position: relative;
background-color: var(--color-white);
display: block;
margin: var(--border-size-large);
margin-bottom: var(--border-size);
padding-left: 0;
padding-right: inherit;
z-index: 3;
[dir="rtl"] & {
padding-left: inherit;
padding-right: 0;
}
> i {
color: var(--color-gray-dark);
font-size: smaller;
[dir="rtl"] & {
-webkit-transform: scale(-1, 1);
-moz-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
-o-transform: scale(-1, 1);
transform: scale(-1, 1);
}
}
&:hover {
background-color: var(--color-white);
}
}
.link {
text-decoration: none;
background-color: inherit;
}
.pushMessageContent {
margin-top: var(--toast-content-margin-md);
margin-bottom: var(--toast-content-margin-sm);
margin-left: var(--toast-content-margin-sm);
margin-right: var(--toast-content-margin-sm);
background-color: inherit;
width: var(--toast-content-width);
}
.userNameMessage {
margin: 0;
font-size: var(--toast-font-size);
color: var(--color-gray-dark);
font-weight: bold;
background-color: inherit;
@include lineClamp(1em, 1);
}
.contentMessage {
margin-top: var(--border-radius);
font-size: var(--toast-font-size);
background-color: inherit;
@include lineClamp(var(--font-size-small), 10);
}

View File

@ -3,9 +3,8 @@ import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import _ from 'lodash';
import Button from '/imports/ui/components/button/component';
import { List, AutoSizer,CellMeasurer, CellMeasurerCache } from 'react-virtualized';
import { styles } from './styles';
import { AutoSizer,CellMeasurer, CellMeasurerCache } from 'react-virtualized';
import Styled from './styles';
import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger';
import TimeWindowChatItem from './time-window-chat-item/container';
@ -236,9 +235,8 @@ class TimeWindowList extends PureComponent {
if (count && userScrolledBack) {
return (
<Button
<Styled.UnreadButton
aria-hidden="true"
className={styles.unreadButton}
color="primary"
size="sm"
key="unread-messages"
@ -278,7 +276,7 @@ class TimeWindowList extends PureComponent {
return (
[
<div
<Styled.MessageListWrapper
onMouseDown={() => {
this.setState({
userScrolledBack: true,
@ -292,7 +290,6 @@ class TimeWindowList extends PureComponent {
this.userScrolledBack = true
}
}}
className={styles.messageListWrapper}
key="chat-list"
data-test="chatMessages"
aria-live="polite"
@ -305,7 +302,7 @@ class TimeWindowList extends PureComponent {
this.cache.clearAll();
}
return (
<List
<Styled.MessageList
ref={(ref) => {
if (ref !== null) {
this.listRef = ref;
@ -317,7 +314,6 @@ class TimeWindowList extends PureComponent {
}}
isScrolling
rowHeight={this.cache.rowHeight}
className={styles.messageList}
rowRenderer={this.rowRender}
rowCount={timeWindowsValues.length}
height={height}
@ -340,7 +336,7 @@ class TimeWindowList extends PureComponent {
);
}}
</AutoSizer>
</div>,
</Styled.MessageListWrapper>,
this.renderUnreadNotification(),
]
);

View File

@ -0,0 +1,68 @@
import styled from 'styled-components';
import {
smPaddingX,
smPaddingY,
mdPaddingX,
mdPaddingY,
} from '/imports/ui/stylesheets/styled-components/general';
import { ButtonElipsis } from '/imports/ui/stylesheets/styled-components/placeholders';
import { VirtualizedScrollboxVertical } from '/imports/ui/stylesheets/styled-components/scrollable';
const UnreadButton = styled(ButtonElipsis)`
flex-shrink: 0;
width: 100%;
text-transform: uppercase;
margin-bottom: .25rem;
z-index: 3;
`;
const MessageListWrapper = styled.div`
display: flex;
flex-flow: column;
flex-grow: 1;
flex-shrink: 1;
position: relative;
overflow-x: hidden;
overflow-y: auto;
padding-left: ${smPaddingX};
margin-left: calc(-1 * ${mdPaddingX});
padding-right: ${smPaddingY};
margin-right: calc(-1 * ${mdPaddingY});
padding-bottom: ${mdPaddingX};
margin-bottom: calc(-1 * ${mdPaddingX});
z-index: 2;
[dir="rtl"] & {
padding-right: ${mdPaddingX};
margin-right: calc(-1 * ${mdPaddingX});
padding-left: ${mdPaddingY};
margin-left: calc(-1 * ${mdPaddingX});
}
`;
const MessageList = styled(VirtualizedScrollboxVertical)`
flex-flow: column;
flex-grow: 1;
flex-shrink: 1;
margin: 0 auto 0 0;
right: 0 ${mdPaddingX} 0 0;
padding-top: 0;
width: 100%;
outline-style: none;
[dir="rtl"] & {
margin: 0 0 0 auto;
padding: 0 0 0 ${mdPaddingX};
}
&:after {
content: "";
display: block;
height: ${mdPaddingX};
}
`;
export default {
UnreadButton,
MessageListWrapper,
MessageList,
};

View File

@ -1,57 +0,0 @@
@import "/imports/ui/stylesheets/mixins/_scrollable";
@import "/imports/ui/stylesheets/variables/placeholders";
.messageListWrapper {
display: flex;
flex-flow: column;
flex-grow: 1;
flex-shrink: 1;
position: relative;
overflow-x: hidden;
overflow-y: auto;
padding-left: var(--sm-padding-x);
margin-left: calc(-1 * var(--md-padding-x));
padding-right: var(--sm-padding-y);
margin-right: calc(-1 * var(--md-padding-y));
padding-bottom: var(--md-padding-x);
margin-bottom: calc(-1 * var(--md-padding-x));
z-index: 2;
[dir="rtl"] & {
padding-right: var(--md-padding-x);
margin-right: calc(-1 * var(--md-padding-x));
padding-left: var(--md-padding-y);
margin-left: calc(-1 * var(--md-padding-x));
}
}
.messageList {
@include scrollbox-vertical();
flex-flow: column;
flex-grow: 1;
flex-shrink: 1;
margin: 0 auto 0 0;
right: 0 var(--md-padding-x) 0 0;
padding-top: 0;
width: 100%;
outline-style: none;
[dir="rtl"] & {
margin: 0 0 0 auto;
padding: 0 0 0 var(--md-padding-x);
}
&:after {
content: "";
display: block;
height: var(--md-padding-x);
}
}
.unreadButton {
flex-shrink: 0;
width: 100%;
text-transform: uppercase;
margin-bottom: .25rem;
z-index: 3;
@extend %text-elipsis;
}

View File

@ -3,12 +3,9 @@ import PropTypes from 'prop-types';
import { FormattedTime, defineMessages, injectIntl } from 'react-intl';
import _ from 'lodash';
import UserAvatar from '/imports/ui/components/user-avatar/component';
import cx from 'classnames';
import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger';
import MessageChatItem from './message-chat-item/component';
import PollService from '/imports/ui/components/poll/service';
import Icon from '/imports/ui/components/icon/component';
import { styles } from './styles';
import Styled from './styles';
const CHAT_CONFIG = Meteor.settings.public.chat;
const CHAT_CLEAR_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_clear;
@ -86,13 +83,13 @@ class TimeWindowChatItem extends PureComponent {
}
return (
<div className={styles.item} key={`time-window-chat-item-${messageKey}`}>
<div className={styles.messages}>
<Styled.Item key={`time-window-chat-item-${messageKey}`}>
<Styled.Messages>
{messages.map(message => (
message.text !== ''
? (
<MessageChatItem
className={(message.id ? styles.systemMessage : styles.systemMessageNoBorder)}
<Styled.SystemMessageChatItem
border={message.id}
key={message.id ? message.id : _.uniqueId('id-')}
text={intlMessages[message.text] ? intl.formatMessage(intlMessages[message.text]) : message.text }
time={message.time}
@ -103,8 +100,8 @@ class TimeWindowChatItem extends PureComponent {
/>
) : null
))}
</div>
</div>
</Styled.Messages>
</Styled.Item>
);
}
@ -130,13 +127,12 @@ class TimeWindowChatItem extends PureComponent {
const regEx = /<a[^>]+>/i;
ChatLogger.debug('TimeWindowChatItem::renderMessageItem', this.props);
const defaultAvatarString = name?.toLowerCase().slice(0, 2) || " ";
const emphasizedTextClass = isModerator && CHAT_EMPHASIZE_TEXT && chatId === CHAT_PUBLIC_ID ?
styles.emphasizedMessage : null;
const emphasizedText = isModerator && CHAT_EMPHASIZE_TEXT && chatId === CHAT_PUBLIC_ID;
return (
<div className={styles.item} key={`time-window-${messageKey}`}>
<div className={styles.wrapper}>
<div className={styles.avatarWrapper}>
<Styled.Item key={`time-window-${messageKey}`}>
<Styled.Wrapper>
<Styled.AvatarWrapper>
<UserAvatar
color={color}
moderator={isModerator}
@ -144,29 +140,28 @@ class TimeWindowChatItem extends PureComponent {
>
{defaultAvatarString}
</UserAvatar>
</div>
<div className={styles.content}>
<div className={styles.meta}>
<div className={isOnline ? styles.name : styles.logout}>
</Styled.AvatarWrapper>
<Styled.Content>
<Styled.Meta>
<Styled.Name isOnline={isOnline}>
<span>{name}</span>
{isOnline
? null
: (
<span className={styles.offline}>
<Styled.Offline>
{`(${intl.formatMessage(intlMessages.offline)})`}
</span>
</Styled.Offline>
)}
</div>
<time className={styles.time} dateTime={dateTime}>
</Styled.Name>
<Styled.Time dateTime={dateTime}>
<FormattedTime value={dateTime} />
</time>
</div>
<div className={styles.messages}>
</Styled.Time>
</Styled.Meta>
<Styled.Messages>
{messages.map(message => (
<MessageChatItem
className={regEx.test(message.text) ?
cx(styles.hyperlink, emphasizedTextClass) :
cx(styles.message, emphasizedTextClass)}
<Styled.ChatItem
hasLink={regEx.test(message.text)}
emphasizedMessage={emphasizedText}
key={message.id}
text={message.text}
time={message.time}
@ -188,10 +183,10 @@ class TimeWindowChatItem extends PureComponent {
scrollArea={scrollArea}
/>
))}
</div>
</div>
</div>
</div>
</Styled.Messages>
</Styled.Content>
</Styled.Wrapper>
</Styled.Item>
);
}
@ -212,28 +207,27 @@ class TimeWindowChatItem extends PureComponent {
const dateTime = new Date(timestamp);
return messages ? (
<div className={styles.item} key={_.uniqueId('message-poll-item-')}>
<div className={styles.wrapper} ref={(ref) => { this.item = ref; }}>
<div className={styles.avatarWrapper}>
<Styled.Item key={_.uniqueId('message-poll-item-')}>
<Styled.Wrapper ref={(ref) => { this.item = ref; }}>
<Styled.AvatarWrapper>
<UserAvatar
color={PollService.POLL_AVATAR_COLOR}
moderator={true}
>
{<Icon className={styles.isPoll} iconName="polling" />}
{<Styled.PollIcon iconName="polling" />}
</UserAvatar>
</div>
<div className={styles.content}>
<div className={styles.meta}>
<div className={styles.name}>
</Styled.AvatarWrapper>
<Styled.Content>
<Styled.Meta>
<Styled.Name>
<span>{intl.formatMessage(intlMessages.pollResult)}</span>
</div>
<time className={styles.time} dateTime={dateTime}>
</Styled.Name>
<Styled.Time dateTime={dateTime}>
<FormattedTime value={dateTime} />
</time>
</div>
<MessageChatItem
</Styled.Time>
</Styled.Meta>
<Styled.PollMessageChatItem
type="poll"
className={cx(styles.message, styles.pollWrapper)}
key={messages[0].id}
text={getPollResultString(extra.pollResultData, intl)}
time={messages[0].time}
@ -243,9 +237,9 @@ class TimeWindowChatItem extends PureComponent {
scrollArea={scrollArea}
color={color}
/>
</div>
</div>
</div>
</Styled.Content>
</Styled.Wrapper>
</Styled.Item>
) : null;
}
@ -259,9 +253,9 @@ class TimeWindowChatItem extends PureComponent {
}
return (
<div className={styles.item}>
<Styled.Item>
{this.renderMessageItem()}
</div>
</Styled.Item>
);
}
}

View File

@ -0,0 +1,210 @@
import styled from 'styled-components';
import {
borderRadius,
borderSize,
chatPollMarginSm,
} from '/imports/ui/stylesheets/styled-components/general';
import { lineHeightComputed, fontSizeBase, btnFontWeight } from '/imports/ui/stylesheets/styled-components/typography';
import {
systemMessageBackgroundColor,
systemMessageBorderColor,
systemMessageFontColor,
colorHeading,
colorGrayLight,
palettePlaceholderText,
colorGrayLighter,
colorPrimary,
colorText,
} from '/imports/ui/stylesheets/styled-components/palette';
import MessageChatItem from './message-chat-item/component';
import Icon from '/imports/ui/components/icon/component';
const Item = styled.div`
padding: calc(${lineHeightComputed} / 4) 0 calc(${lineHeightComputed} / 2) 0;
font-size: ${fontSizeBase};
pointer-events: auto;
[dir="rtl"] & {
direction: rtl;
}
`;
const Messages = styled.div`
> * {
&:first-child {
margin-top: calc(${lineHeightComputed} / 4);
}
}
`;
const SystemMessageChatItem = styled(MessageChatItem)`
${({ border }) => border && `
background: ${systemMessageBackgroundColor};
border: 1px solid ${systemMessageBorderColor};
border-radius: ${borderRadius};
font-weight: ${btnFontWeight};
padding: ${fontSizeBase};
color: ${systemMessageFontColor};
margin-top: 0px;
margin-bottom: 0px;
overflow-wrap: break-word;
`}
${({ border }) => !border && `
color: ${systemMessageFontColor};
margin-top: 0px;
margin-bottom: 0px;
`}
`;
const Wrapper = styled.div`
display: flex;
flex-flow: row;
flex: 1;
position: relative;
margin: ${borderSize} 0 0 ${borderSize};
[dir="rtl"] & {
margin: ${borderSize} ${borderSize} 0 0;
}
`;
const AvatarWrapper = styled.div`
flex-basis: 2.25rem;
flex-shrink: 0;
flex-grow: 0;
margin: 0 calc(${lineHeightComputed} / 2) 0 0;
[dir="rtl"] & {
margin: 0 0 0 calc(${lineHeightComputed} / 2);
}
`;
const Content = styled.div`
flex: 1;
display: flex;
flex-flow: column;
overflow-x: hidden;
width: calc(100% - 1.7rem);
`;
const Meta = styled.div`
display: flex;
flex: 1;
flex-flow: row;
line-height: 1.35;
`;
const Name = styled.div`
display: flex;
min-width: 0;
font-weight: 600;
position: relative;
&:first-child {
min-width: 0;
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
${({ isOnline }) => isOnline && `
color: ${colorHeading};
`}
${({ isOnline }) => !isOnline && `
text-transform: capitalize;
font-style: italic;
& > span {
text-align: right;
padding: 0 .1rem 0 0;
[dir="rtl"] & {
text-align: left;
padding: 0 0 0 .1rem;
}
}
`}
`;
const Offline = styled.span`
color: ${colorGrayLight};
font-weight: 100;
text-transform: lowercase;
font-style: italic;
font-size: 90%;
line-height: 1;
align-self: center;
`;
const Time = styled.time`
flex-shrink: 0;
flex-grow: 0;
flex-basis: 3.5rem;
color: ${palettePlaceholderText};
text-transform: uppercase;
font-size: 75%;
margin: 0 0 0 calc(${lineHeightComputed} / 2);
[dir="rtl"] & {
margin: 0 calc(${lineHeightComputed} / 2) 0 0;
}
& > span {
vertical-align: sub;
}
`;
const ChatItem = styled(MessageChatItem)`
flex: 1;
margin-top: calc(${lineHeightComputed} / 3);
margin-bottom: 0;
color: ${colorText};
word-wrap: break-word;
${({ hasLink }) => hasLink && `
& > a {
color: ${colorPrimary};
}
`}
${({ emphasizedMessage }) => emphasizedMessage && `
font-weight: bold;
`}
`;
const PollIcon = styled(Icon)`
bottom: 1px;
`;
const PollMessageChatItem = styled(MessageChatItem)`
flex: 1;
margin-top: calc(${lineHeightComputed} / 3);
margin-bottom: 0;
color: ${colorText};
word-wrap: break-word;
background: ${systemMessageBackgroundColor};
border: solid 1px ${colorGrayLighter};
border-radius: ${borderRadius};
padding: ${chatPollMarginSm};
padding-left: 1rem;
margin-top: ${chatPollMarginSm} !important;
`;
export default {
Item,
Messages,
SystemMessageChatItem,
Wrapper,
AvatarWrapper,
Content,
Meta,
Name,
Offline,
Time,
ChatItem,
PollIcon,
PollMessageChatItem,
};

View File

@ -1,173 +0,0 @@
@import "/imports/ui/stylesheets/variables/placeholders";
:root {
--systemMessage-background-color: #F9FBFC;
--systemMessage-border-color: #C5CDD4;
--systemMessage-font-color: var(--color-dark-grey);
--chat-poll-margin-sm: .5rem;
}
.item {
padding: calc(var(--line-height-computed) / 4) 0 calc(var(--line-height-computed) / 2) 0;
font-size: var(--font-size-base);
pointer-events: auto;
[dir="rtl"] & {
direction: rtl;
}
}
.wrapper {
display: flex;
flex-flow: row;
flex: 1;
position: relative;
margin:var(--border-size) 0 0 var(--border-size);
[dir="rtl"] & {
margin: var(--border-size) var(--border-size) 0 0;
}
}
.systemMessage {
background: var(--systemMessage-background-color);
border: 1px solid var(--systemMessage-border-color);
border-radius: var(--border-radius);
font-weight: var(--btn-font-weight);
padding: var(--font-size-base);
color: var(--systemMessage-font-color);
margin-top: 0px;
margin-bottom: 0px;
overflow-wrap: break-word;
}
.systemMessageNoBorder {
color: var(--systemMessage-font-color);
margin-top: 0px;
margin-bottom: 0px;
}
.avatarWrapper {
flex-basis: 2.25rem;
flex-shrink: 0;
flex-grow: 0;
margin: 0 calc(var(--line-height-computed) / 2) 0 0;
[dir="rtl"] & {
margin: 0 0 0 calc(var(--line-height-computed) / 2);
}
}
.content {
flex: 1;
display: flex;
flex-flow: column;
overflow-x: hidden;
width: calc(100% - 1.7rem);
}
.meta {
display: flex;
flex: 1;
flex-flow: row;
line-height: 1.35;
& + .message {
margin-top: 0;
}
}
.name,
.logout {
display: flex;
min-width: 0;
font-weight: 600;
position: relative;
:first-child {
@extend %text-elipsis;
}
}
.name {
color: var(--color-heading);
}
.logout {
text-transform: capitalize;
font-style: italic;
& > span {
text-align: right;
padding: 0 .1rem 0 0;
[dir="rtl"] & {
text-align: left;
padding: 0 0 0 .1rem;
}
}
}
.offline {
color: var(--color-gray-light);
font-weight: 100;
text-transform: lowercase;
font-style: italic;
font-size: 90%;
line-height: 1;
align-self: center;
}
.time {
flex-shrink: 0;
flex-grow: 0;
flex-basis: 3.5rem;
color: var(--palette-placeholder-text);
text-transform: uppercase;
font-size: 75%;
margin: 0 0 0 calc(var(--line-height-computed) / 2);
[dir="rtl"] & {
margin: 0 calc(var(--line-height-computed) / 2) 0 0;
}
> span {
vertical-align: sub;
}
}
.messages {
> .message:first-child {
margin-top: calc(var(--line-height-computed) / 4);
}
}
.message, .hyperlink {
flex: 1;
margin-top: calc(var(--line-height-computed) / 3);
margin-bottom: 0;
color: var(--color-text);
word-wrap: break-word;
}
.hyperlink {
a {
color: var(--color-primary);
}
}
.isPoll {
bottom: 1px;
}
.pollWrapper {
background: var(--systemMessage-background-color);
border: solid 1px var(--color-gray-lighter);
border-radius: var(--border-radius);
padding: var(--chat-poll-margin-sm);
padding-left: 1rem;
margin-top: var(--chat-poll-margin-sm) !important;
}
.emphasizedMessage{
font-weight: bold;
}

View File

@ -103,7 +103,7 @@ class ExternalVideoModal extends Component {
contentLabel={intl.formatMessage(intlMessages.title)}
hideBorder
>
<header data-test="videoModealHeader" className={styles.header}>
<header data-test="videoModalHeader" className={styles.header}>
<h3 className={styles.title}>{intl.formatMessage(intlMessages.title)}</h3>
</header>

View File

@ -4,8 +4,7 @@ import humanizeSeconds from '/imports/utils/humanizeSeconds';
import Tooltip from '/imports/ui/components/tooltip/component';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import cx from 'classnames';
import { styles } from './styles';
import Styled from './styles';
const intlMessages = defineMessages({
notificationRecordingStart: {
@ -138,7 +137,7 @@ class RecordingIndicator extends PureComponent {
};
const recordingIndicatorIcon = (
<span data-test="mainWhiteboard" className={cx(styles.recordingIndicatorIcon, (!isPhone || recording) && styles.presentationTitleMargin)}>
<Styled.RecordingIndicatorIcon titleMargin={!isPhone || recording} data-test="mainWhiteboard">
<svg xmlns="http://www.w3.org/2000/svg" height="100%" version="1" viewBox="0 0 20 20">
<g stroke="#FFF" fill="#FFF" strokeLinecap="square">
<circle
@ -157,16 +156,16 @@ class RecordingIndicator extends PureComponent {
/>
</g>
</svg>
</span>
</Styled.RecordingIndicatorIcon>
);
const showButton = amIModerator && allowStartStopRecording;
const recordMeetingButton = (
<div
<Styled.RecordingControl
aria-label={recordTitle}
aria-describedby={"recording-description"}
className={recording ? styles.recordingControlON : styles.recordingControlOFF}
recording={recording}
role="button"
tabIndex={0}
key="recording-toggle"
@ -174,14 +173,14 @@ class RecordingIndicator extends PureComponent {
onKeyPress={recordingToggle}
>
{recordingIndicatorIcon}
<div className={styles.presentationTitle}>
<span id={"recording-description"} className={styles.visuallyHidden}>
<Styled.PresentationTitle>
<Styled.VisuallyHidden id={"recording-description"}>
{`${title} ${recording ? humanizeSeconds(time) : ''}`}
</span>
</Styled.VisuallyHidden>
{recording
? <span aria-hidden>{humanizeSeconds(time)}</span> : <span>{recordTitle}</span>}
</div>
</div>
</Styled.PresentationTitle>
</Styled.RecordingControl>
);
const recordMeetingButtonWithTooltip = (
@ -195,9 +194,9 @@ class RecordingIndicator extends PureComponent {
return (
<Fragment>
{record
? <span className={styles.presentationTitleSeparator} aria-hidden>|</span>
? <Styled.PresentationTitleSeparator aria-hidden>|</Styled.PresentationTitleSeparator>
: null}
<div className={styles.recordingIndicator}>
<Styled.RecordingIndicator>
{showButton
? recordingButton
: null}
@ -208,20 +207,19 @@ class RecordingIndicator extends PureComponent {
? intlMessages.notificationRecordingStart
: intlMessages.notificationRecordingStop)}`}
>
<div
<Styled.RecordingStatusViewOnly
aria-label={`${intl.formatMessage(recording
? intlMessages.notificationRecordingStart
: intlMessages.notificationRecordingStop)}`}
className={styles.recordingStatusViewOnly}
>
{recordingIndicatorIcon}
{recording
? <div className={styles.presentationTitle}>{humanizeSeconds(time)}</div> : null}
</div>
? <Styled.PresentationTitle>{humanizeSeconds(time)}</Styled.PresentationTitle> : null}
</Styled.RecordingStatusViewOnly>
</Tooltip>
)}
</div>
</Styled.RecordingIndicator>
</Fragment>
);
}

View File

@ -0,0 +1,138 @@
import styled from 'styled-components';
import { fontSizeLarge, fontSizeBase } from '/imports/ui/stylesheets/styled-components/typography';
import {
smPaddingX,
borderSize,
borderSizeLarge,
borderSizeSmall,
} from '/imports/ui/stylesheets/styled-components/general';
import { colorWhite, colorPrimary, colorGray } from '/imports/ui/stylesheets/styled-components/palette';
const RecordingIndicatorIcon = styled.span`
width: ${fontSizeLarge};
height: ${fontSizeLarge};
font-size: ${fontSizeBase};
${({ titleMargin }) => titleMargin && `
[dir="ltr"] & {
margin-right: ${smPaddingX};
}
`}
`;
const RecordingControl = styled.div`
display: flex;
border-radius: 2em 2em;
align-items: center;
span {
border: none;
box-shadow: none;
background-color: transparent !important;
color: ${colorWhite} !important;
}
&:hover {
color: ${colorWhite} !important;
cursor: pointer;
}
&:focus {
outline: none;
box-shadow: 0 0 0 ${borderSize} ${colorPrimary};
}
${({ recording }) => recording && `
padding: 5px;
background-color: ${colorPrimary};
border: ${borderSizeLarge} solid ${colorPrimary};
&:focus {
background-clip: padding-box;
border: ${borderSizeLarge} solid transparent;
}
`}
${({ recording }) => !recording && `
padding: 7px;
border: ${borderSizeSmall} solid ${colorWhite};
&:focus {
padding: 5px;
border: ${borderSizeLarge} solid ${colorWhite};
box-shadow: none;
}
`}
`;
const PresentationTitle = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
font-weight: 200;
color: ${colorWhite};
font-size: ${fontSizeBase};
padding: 0;
margin-right: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 30vw;
[dir="rtl"] & {
margin-left: 0;
margin-right: ${smPaddingX};
}
& > [class^="icon-bbb-"] {
font-size: 75%;
}
span {
vertical-align: middle;
}
`;
const VisuallyHidden = styled.span`
position: absolute;
overflow: hidden;
clip: rect(0 0 0 0);
height: 1px; width: 1px;
margin: -1px; padding: 0; border: 0;
`;
const PresentationTitleSeparator = styled.span`
color: ${colorGray};
font-size: ${fontSizeBase};
margin: 0 1rem;
`;
const RecordingIndicator = styled.div`
&:hover {
outline: transparent;
outline-style: dotted;
outline-width: ${borderSize};
}
&:active,
&:focus,
&:focus-within {
outline: transparent;
outline-width: ${borderSize};
outline-style: solid;
}
`;
const RecordingStatusViewOnly = styled.div`
display: flex;
`;
export default {
RecordingIndicatorIcon,
RecordingControl,
PresentationTitle,
VisuallyHidden,
PresentationTitleSeparator,
RecordingIndicator,
RecordingStatusViewOnly,
};

View File

@ -1,122 +0,0 @@
@import '/imports/ui/stylesheets/mixins/_indicators';
@import "/imports/ui/stylesheets/variables/placeholders";
.visuallyHidden {
position: absolute;
overflow: hidden;
clip: rect(0 0 0 0);
height: 1px; width: 1px;
margin: -1px; padding: 0; border: 0;
}
%recordingControl {
display: flex;
border-radius: 2em 2em;
align-items: center;
span {
border: none;
box-shadow: none;
background-color: transparent !important;
color: var(--color-white) !important;
}
&:hover {
color: var(--color-white) !important;
cursor: pointer;
}
&:focus {
outline: none;
box-shadow: 0 0 0 var(--border-size) var(--color-primary);
}
}
.recordingControlON {
@extend %recordingControl;
padding: 5px;
background-color: var(--color-primary);
border: var(--border-size-large) solid var(--color-primary);
&:focus {
background-clip: padding-box;
border: var(--border-size-large) solid transparent;
}
}
.recordingControlOFF {
@extend %recordingControl;
padding: 7px;
border: var(--border-size-small) solid var(--color-white);
&:focus {
padding: 5px;
border: var(--border-size-large) solid var(--color-white);
box-shadow: none;
}
}
.presentationTitle {
display: flex;
flex-direction: column;
justify-content: center;
font-weight: 200;
color: var(--color-white);
font-size: var(--font-size-base);
padding: 0;
margin-right: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 30vw;
[dir="rtl"] & {
margin-left: 0;
margin-right: var(--sm-padding-x);
}
> [class^="icon-bbb-"] {
font-size: 75%;
}
span {
vertical-align: middle;
}
}
.presentationTitleMargin {
[dir="ltr"] & {
margin-right: var(--sm-padding-x);
}
}
.recordingStatusViewOnly {
display: flex;
}
.recordingIndicatorIcon {
width: var(--font-size-large);
height: var(--font-size-large);
font-size: var(--font-size-base);
}
.recordingIndicator {
&:hover {
@extend %highContrastOutline;
}
&:active,
&:focus,
&:focus-within {
@extend %highContrastOutline;
outline-style: solid;
}
}
.presentationTitleSeparator {
color: var(--color-gray);
font-size: var(--font-size-base);
margin: 0 1rem;
}

View File

@ -1,9 +1,7 @@
import React, { PureComponent } from 'react';
import cx from 'classnames';
import _ from 'lodash';
import { defineMessages, injectIntl } from 'react-intl';
import Button from '/imports/ui/components/button/component';
import { styles } from './styles';
import Styled from './styles';
import Service from './service';
const intlMessages = defineMessages({
@ -60,15 +58,6 @@ class TalkingIndicator extends PureComponent {
callerName,
} = talkers[`${id}`];
const style = {
[styles.talker]: true,
[styles.spoke]: !talking,
[styles.muted]: muted,
[styles.mobileHide]: sidebarNavigationIsOpen
&& sidebarContentIsOpen,
[styles.isViewer]: !amIModerator,
};
const ariaLabel = intl.formatMessage(talking
? intlMessages.isTalking : intlMessages.wasTalking, {
0: callerName,
@ -78,9 +67,12 @@ class TalkingIndicator extends PureComponent {
icon = muted ? 'mute' : icon;
return (
<Button
<Styled.TalkingIndicatorButton
spoke={!talking}
muted={muted}
mobileHide={sidebarNavigationIsOpen && sidebarContentIsOpen}
isViewer={!amIModerator}
key={_.uniqueId(`${callerName}-`)}
className={cx(style)}
onClick={() => this.handleMuteUser(id)}
label={callerName}
tooltipLabel={!muted && amIModerator
@ -98,11 +90,11 @@ class TalkingIndicator extends PureComponent {
}}
>
{talking ? (
<div id="description" className={styles.hidden}>
<Styled.Hidden id="description">
{`${intl.formatMessage(intlMessages.ariaMuteDesc)}`}
</div>
</Styled.Hidden>
) : null}
</Button>
</Styled.TalkingIndicatorButton>
);
});
@ -111,14 +103,6 @@ class TalkingIndicator extends PureComponent {
const nobodyTalking = Service.nobodyTalking(talkers);
const style = {
[styles.talker]: true,
[styles.spoke]: nobodyTalking,
// [styles.muted]: false,
[styles.mobileHide]: sidebarNavigationIsOpen
&& sidebarContentIsOpen,
};
const { moreThanMaxIndicatorsTalking, moreThanMaxIndicatorsWereTalking } = intlMessages;
const ariaLabel = intl.formatMessage(nobodyTalking
@ -127,9 +111,12 @@ class TalkingIndicator extends PureComponent {
});
return (
<Button
<Styled.TalkingIndicatorButton
spoke={nobodyTalking}
muted={false}
mobileHide={sidebarNavigationIsOpen && sidebarContentIsOpen}
isViewer={false}
key={_.uniqueId('_has__More_')}
className={cx(style)}
onClick={() => {}} // maybe add a dropdown to show the rest of the users
label="..."
tooltipLabel={ariaLabel}
@ -146,12 +133,12 @@ class TalkingIndicator extends PureComponent {
};
return (
<div className={styles.isTalkingWrapper}>
<div className={styles.speaking}>
<Styled.IsTalkingWrapper>
<Styled.Speaking>
{talkingUserElements}
{maxIndicator()}
</div>
</div>
</Styled.Speaking>
</Styled.IsTalkingWrapper>
);
}
}

View File

@ -0,0 +1,156 @@
import styled from 'styled-components';
import {
borderSize,
borderRadius,
talkerBorderRadius,
talkerPaddingXsm,
talkerPaddingLg,
talkerMaxWidth,
talkerMarginSm,
spokeOpacity,
talkerPaddingXl,
} from '/imports/ui/stylesheets/styled-components/general';
import {
colorWhite,
colorSuccess,
colorDanger,
} from '/imports/ui/stylesheets/styled-components/palette';
import {
fontSizeBase,
talkerFontWeight,
fontSizeXS,
fontSizeSmaller,
} from '/imports/ui/stylesheets/styled-components/typography';
import { phoneLandscape, smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
import Button from '/imports/ui/components/button/component';
const TalkingIndicatorButton = styled(Button)`
display: flex;
flex-direction: row;
outline: transparent;
outline-style: dotted;
outline-width: ${borderSize};
flex: 0 0 auto;
color: ${colorWhite};
font-weight: ${talkerFontWeight};
border-radius: ${talkerBorderRadius} ${talkerBorderRadius};
font-size: ${fontSizeBase};
padding: ${talkerPaddingXsm} ${talkerPaddingLg} ${talkerPaddingXsm} ${talkerPaddingLg};
margin-left: ${borderRadius};
margin-right: ${borderRadius};
box-shadow: none !important;
@media ${phoneLandscape} {
height: 1rem;
}
i,
span {
position: relative;
}
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin: 0 0 0 0 !important;
max-width: ${talkerMaxWidth};
@media ${phoneLandscape} {
font-size: ${fontSizeXS};
}
[dir="rtl"] & {
margin-left: ${talkerMarginSm};
}
}
i {
font-size: ${fontSizeSmaller};
width: 1rem;
height: 1rem;
line-height: 1rem;
background-color: ${colorSuccess};
border-radius: 50%;
position: relative;
right: ${talkerMarginSm};
@media ${phoneLandscape} {
height: ${talkerMarginSm};
width: ${talkerMarginSm};
font-size: ${fontSizeXS};
}
[dir="rtl"] & {
right: calc(${talkerMarginSm} * -1);
}
}
span:hover {
opacity: 1;
}
${({ spoke }) => spoke && `
opacity: ${spokeOpacity};
[dir="rtl"] & {
padding-right: ${talkerPaddingLg}
}
`}
${({ muted }) => muted && `
cursor: default;
i {
background-color: ${colorDanger};
}
`}
${({ mobileHide }) => mobileHide && `
@media ${smallOnly} {
visibility: hidden;
}
`}
${({ isViewer }) => isViewer && `
cursor: default;
`}
`;
const Hidden = styled.div`
display: none;
`;
const IsTalkingWrapper = styled.div`
display: flex;
flex-direction: row;
position: relative;
margin-top: ${talkerMarginSm};
overflow: hidden;
`;
const Speaking = styled.div`
display: flex;
flex-direction: row;
flex-wrap: nowrap;
overflow-x: auto;
overflow-y: hidden;
max-height: ${talkerPaddingXl};
scrollbar-width: 0; // firefox
scrollbar-color: transparent;
&::-webkit-scrollbar {
width: 0px;
height: 0px;
background: transparent;
}
`;
export default {
TalkingIndicatorButton,
Hidden,
IsTalkingWrapper,
Speaking,
};

View File

@ -1,139 +0,0 @@
@import "/imports/ui/stylesheets/variables/breakpoints";
@import "/imports/ui/stylesheets/mixins/_indicators";
@import "/imports/ui/stylesheets/variables/placeholders";
:root {
--spoke-opacity: .5;
--talker-margin-sm: .5rem;
--talker-padding-lg: .75rem;
--talker-padding-xl: 1.62rem;
--talker-padding-xsm: .13rem;
--talker-max-width: 10rem;
--talker-font-weight: 400;
--talker-border-radius: 2rem;
}
.hidden {
display: none;
}
.isTalkingWrapper,
.speaking,
.talker,
.spoke {
display: flex;
flex-direction: row;
}
.isTalkingWrapper {
position: relative;
margin-top: var(--talker-margin-sm);
overflow: hidden;
}
.speaking {
flex-wrap: nowrap;
overflow-x: auto;
overflow-y: hidden;
max-height: var(--talker-padding-xl);
scrollbar-width: 0; // firefox
scrollbar-color: transparent;
}
.speaking::-webkit-scrollbar {
width: 0px;
height: 0px;
background: transparent;
}
.talker {
@extend %highContrastOutline;
flex: 0 0 auto;
color: var(--color-white);
font-weight: var(--talker-font-weight);
border-radius: var(--talker-border-radius) var(--talker-border-radius);
font-size: var(--font-size-base);
padding: var(--talker-padding-xsm) var(--talker-padding-lg) var(--talker-padding-xsm) var(--talker-padding-lg);
margin-left: var(--border-radius);
margin-right: var(--border-radius);
height: var(--talker-height);
box-shadow: none !important;
@include mq($phone-landscape) {
height: 1rem;
}
i,
span {
position: relative;
}
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin: 0 0 0 0 !important;
max-width: var(--talker-max-width);
bottom: var(--bottom-offset);
@include mq($phone-landscape) {
font-size: var(--font-size-xs);
}
[dir="rtl"] & {
margin-left: var(--talker-margin-sm);
}
}
i {
font-size: var(--font-size-smaller);
width: 1rem;
height: 1rem;
line-height: 1rem;
background-color: var(--color-success);
border-radius: 50%;
position: relative;
right: var(--talker-margin-sm);
@include mq($phone-landscape) {
height: var(--talker-margin-sm);
width: var(--talker-margin-sm);
font-size: var(--font-size-xs);
}
[dir="rtl"] & {
right: calc(var(--talker-margin-sm) * -1);
}
}
span:hover {
opacity: 1;
}
}
.spoke {
opacity: var(--spoke-opacity);
[dir="rtl"] & {
padding-right: var(--talker-padding-lg)
}
}
.muted {
cursor: default;
i {
background-color: var(--color-danger);
}
}
.mobileHide {
@include mq($small-only) {
visibility: hidden;
}
}
.isViewer {
cursor: default;
}

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import { styles } from '/imports/ui/components/user-list/user-list-content/styles';
import Styled from './styles';
const intlMessages = defineMessages({
unreadPlural: {
@ -49,14 +49,11 @@ const ChatUnreadCounter = (props) => {
: intl.formatMessage(intlMessages.unreadSingular)}`;
return (
<div
className={styles.unreadMessages}
aria-label={arialabel}
>
<div className={styles.unreadMessagesText} aria-hidden="true">
<Styled.UnreadMessages aria-label={arialabel}>
<Styled.UnreadMessagesText aria-hidden="true">
{counter}
</div>
</div>
</Styled.UnreadMessagesText>
</Styled.UnreadMessages>
);
};

View File

@ -0,0 +1,10 @@
import styled from 'styled-components';
import Styled from '/imports/ui/components/user-list/user-list-content/styles';
const UnreadMessages = styled(Styled.UnreadMessages)``;
const UnreadMessagesText = styled(Styled.UnreadMessagesText)``;
export default {
UnreadMessages,
UnreadMessagesText,
};

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import PollService from '/imports/ui/components/poll/service';
import caseInsensitiveReducer from '/imports/utils/caseInsensitiveReducer';
import { injectIntl, defineMessages } from 'react-intl';
import styles from './styles';
import Styled from './styles';
import {
getSwapLayout,
shouldEnableSwapLayout,
@ -475,7 +475,7 @@ class PollDrawComponent extends Component {
strokeWidth={thickness}
/>
{extendedTextArray.map((line) => (
<text
<Styled.OutlineText
x={line.keyColumn.xLeft}
y={line.keyColumn.yLeft}
dy={maxLineHeight / 2}
@ -484,10 +484,9 @@ class PollDrawComponent extends Component {
fontFamily="Arial"
fontSize={calcFontSize}
textAnchor={isRTL ? 'end' : 'start'}
className={styles.outline}
>
{line.keyColumn.keyString}
</text>
</Styled.OutlineText>
))}
{extendedTextArray.map((line) => (
<rect
@ -510,15 +509,14 @@ class PollDrawComponent extends Component {
textAnchor={isRTL ? 'start' : 'end'}
>
{extendedTextArray.map((line) => (
<tspan
<Styled.OutlineTSpan
x={line.percentColumn.xRight}
y={line.percentColumn.yRight}
dy={maxLineHeight / 2}
key={`${line.key}_percent`}
className={styles.outline}
>
{line.percentColumn.percentString}
</tspan>
</Styled.OutlineTSpan>
))}
</text>
<text
@ -530,16 +528,15 @@ class PollDrawComponent extends Component {
textAnchor={isRTL ? 'end' : 'start'}
>
{extendedTextArray.map((line) => (
<tspan
<Styled.OutlineTSpan
x={line.barColumn.xNumVotes + (line.barColumn.barWidth / 2)}
y={line.barColumn.yNumVotes + (line.barColumn.barHeight / 2)}
dy={maxLineHeight / 2}
key={`${line.key}_numVotes`}
fill={line.barColumn.color}
className={styles.outline}
>
{line.barColumn.numVotes}
</tspan>
</Styled.OutlineTSpan>
))}
</text>
</g>

View File

@ -0,0 +1,18 @@
import styled from 'styled-components';
import { pollAnnotationGray } from '/imports/ui/stylesheets/styled-components/palette';
const OutlineText = styled.text`
stroke: ${pollAnnotationGray};
stroke-width: .5;
`;
const OutlineTSpan = styled.tspan`
stroke: ${pollAnnotationGray};
stroke-width: .5;
`;
export default {
OutlineText,
OutlineTSpan,
};

View File

@ -1,8 +0,0 @@
:root {
--poll-annotation-gray: #333333;
}
.outline {
stroke: var(--poll-annotation-gray);
stroke-width: .5;
}

View File

@ -1,11 +1,11 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { HEXToINTColor, INTToHEXColor } from '/imports/utils/hexInt';
import { defineMessages, injectIntl } from 'react-intl';
import KEY_CODES from '/imports/utils/keyCodes';
import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
import { styles } from './styles.scss';
import Styled from './styles';
import ToolbarMenuItem from './toolbar-menu-item/component';
import ToolbarSubmenu from './toolbar-submenu/component';
@ -505,7 +505,6 @@ class WhiteboardToolbar extends Component {
icon="hand"
label={intl.formatMessage(intlMessages.toolbarItemPan)}
onItemClick={() => { }}
className={styles.toolbarButton}
/>
) : (
<ToolbarMenuItem
@ -517,7 +516,6 @@ class WhiteboardToolbar extends Component {
onItemClick={this.displaySubMenu}
objectToReturn="annotationList"
onBlur={this.closeSubMenu}
className={cx(styles.toolbarButton, currentSubmenuOpen === 'annotationList' ? styles.toolbarActive : null)}
showCornerTriangle
>
{currentSubmenuOpen === 'annotationList' && annotations.length > 1
@ -552,7 +550,6 @@ class WhiteboardToolbar extends Component {
onItemClick={this.displaySubMenu}
objectToReturn="fontSizeList"
onBlur={this.closeSubMenu}
className={cx(styles.toolbarButton, currentSubmenuOpen === 'fontSizeList' ? styles.toolbarActive : null)}
showCornerTriangle
>
{currentSubmenuOpen === 'fontSizeList'
@ -577,8 +574,7 @@ class WhiteboardToolbar extends Component {
renderFontItemIcon() {
const { fontSizeSelected, colorSelected } = this.state;
return (
<p
className={styles.textThickness}
<Styled.TextThickness
style={{
fontSize: fontSizeSelected.value <= 32 ? fontSizeSelected.value : 32,
color: colorSelected.value,
@ -587,7 +583,7 @@ class WhiteboardToolbar extends Component {
}}
>
Aa
</p>
</Styled.TextThickness>
);
}
@ -616,7 +612,6 @@ class WhiteboardToolbar extends Component {
onItemClick={this.displaySubMenu}
objectToReturn="thicknessList"
onBlur={this.closeSubMenu}
className={cx(styles.toolbarButton, currentSubmenuOpen === 'thicknessList' ? styles.toolbarActive : null)}
customIcon={this.renderThicknessItemIcon()}
showCornerTriangle
>
@ -648,7 +643,7 @@ class WhiteboardToolbar extends Component {
} = this.state;
return (
<svg className={styles.customSvgIcon} shapeRendering="geometricPrecision">
<Styled.CustomSvgIcon shapeRendering="geometricPrecision">
<circle
shapeRendering="geometricPrecision"
cx="50%"
@ -681,7 +676,7 @@ class WhiteboardToolbar extends Component {
fill="freeze"
/>
</circle>
</svg>
</Styled.CustomSvgIcon>
);
}
@ -710,7 +705,6 @@ class WhiteboardToolbar extends Component {
onItemClick={this.displaySubMenu}
objectToReturn="colorList"
onBlur={this.closeSubMenu}
className={cx(styles.toolbarButton, currentSubmenuOpen === 'colorList' ? styles.toolbarActive : null)}
customIcon={this.renderColorItemIcon()}
showCornerTriangle
>
@ -740,7 +734,7 @@ class WhiteboardToolbar extends Component {
} = this.state;
return (
<svg className={styles.customSvgIcon}>
<Styled.CustomSvgIcon>
<rect x="25%" y="25%" width="50%" height="50%" stroke="black" strokeWidth="1" fill={colorSelected.value}>
<animate
ref={(ref) => { this.colorListIconColor = ref; }}
@ -754,7 +748,7 @@ class WhiteboardToolbar extends Component {
fill="freeze"
/>
</rect>
</svg>
</Styled.CustomSvgIcon>
);
}
@ -767,7 +761,6 @@ class WhiteboardToolbar extends Component {
label={intl.formatMessage(intlMessages.toolbarUndoAnnotation)}
icon="undo"
onItemClick={this.handleUndo}
className={styles.toolbarButton}
/>
);
}
@ -781,7 +774,6 @@ class WhiteboardToolbar extends Component {
label={intl.formatMessage(intlMessages.toolbarClearAnnotations)}
icon="delete"
onItemClick={this.handleClearAll}
className={styles.toolbarButton}
/>
);
}
@ -795,8 +787,8 @@ class WhiteboardToolbar extends Component {
} = this.props;
return (
<span className={styles.multiUserToolItem} data-test={multiUser ? 'multiWhiteboardTool' : 'whiteboardTool'}>
{multiUser && <span className={styles.multiUserTool}>{multiUserSize}</span>}
<span data-test={multiUser ? 'multiWhiteboardTool' : 'whiteboardTool'}>
{multiUser && <Styled.MultiUserTool>{multiUserSize}</Styled.MultiUserTool>}
<ToolbarMenuItem
disabled={!isMeteorConnected}
label={multiUser
@ -805,7 +797,6 @@ class WhiteboardToolbar extends Component {
}
icon={multiUser ? 'multi_whiteboard' : 'whiteboard'}
onItemClick={this.handleSwitchWhiteboardMode}
className={styles.toolbarButton}
/>
</span>
);
@ -825,7 +816,6 @@ class WhiteboardToolbar extends Component {
}
icon={palmRejection ? 'palm_rejection' : 'no_palm_rejection'}
onItemClick={this.handleSwitchPalmRejectionMode}
className={styles.toolbarButton}
/>
);
}
@ -834,7 +824,7 @@ class WhiteboardToolbar extends Component {
const { annotationSelected } = this.state;
const { isPresenter, intl } = this.props;
return (
<div className={styles.toolbarContainer} role="region" aria-label={intl.formatMessage(intlMessages.toolbarAriaLabel)}>
<Styled.ToolbarContainer role="region" aria-label={intl.formatMessage(intlMessages.toolbarAriaLabel)}>
<div className={styles.toolbarWrapper}>
{this.renderToolItem()}
{annotationSelected.value === 'text' ? this.renderFontItem() : this.renderThicknessItem()}
@ -844,7 +834,7 @@ class WhiteboardToolbar extends Component {
{window.PointerEvent ? this.renderPalmRejectionItem() : null}
{isPresenter ? this.renderMultiUserItem() : null}
</div>
</div>
</Styled.ToolbarContainer>
);
}
}

View File

@ -0,0 +1,82 @@
import styled from 'styled-components';
import {
toolbarListColor,
toolbarListBgFocus,
colorDanger,
colorWhite,
colorGrayDark,
} from '/imports/ui/stylesheets/styled-components/palette';
import {
toolbarButtonWidth,
toolbarButtonHeight,
lgPaddingX,
borderSizeLarge,
smPaddingX,
toolbarMargin,
} from '/imports/ui/stylesheets/styled-components/general';
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
const TextThickness = styled.p`
font-family: Arial, sans-serif;
font-weight: normal;
text-shadow: -1px 0 ${toolbarListBgFocus}, 0 1px ${toolbarListBgFocus}, 1px 0 ${toolbarListBgFocus}, 0 -1px ${toolbarListBgFocus};
margin: auto;
color: ${toolbarListColor};
`;
const CustomSvgIcon = styled.svg`
position: absolute;
width: ${toolbarButtonWidth};
height: ${toolbarButtonHeight};
left: 0;
top: 0;
`;
const MultiUserTool = styled.span`
background-color: ${colorDanger};
border-radius: 50%;
width: ${lgPaddingX};
height: ${lgPaddingX};
position: absolute;
z-index: 2;
right: 0px;
color: ${colorWhite};
display: flex;
justify-content: center;
align-items: center;
box-shadow: 1px 1px ${borderSizeLarge} ${colorGrayDark};
font-size: ${smPaddingX};
`;
const ToolbarContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 ${toolbarMargin} 0 0;
position: absolute;
top: 0;
right: 0;
left: auto;
bottom: 0;
pointer-events: none;
z-index: 3;
[dir="rtl"] & {
margin: 0 0 0 ${toolbarMargin};
right: auto;
left: 0;
}
@media ${smallOnly} {
transform: scale(.75);
transform-origin: right;
}
`;
export default {
TextThickness,
CustomSvgIcon,
MultiUserTool,
ToolbarContainer,
};

View File

@ -1,9 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Button from '/imports/ui/components/button/component';
import _ from 'lodash';
import cx from 'classnames';
import { styles } from '../styles';
import Styled from './styles';
export default class ToolbarMenuItem extends Component {
constructor() {
@ -73,7 +71,6 @@ export default class ToolbarMenuItem extends Component {
icon,
customIcon,
onBlur,
className,
children,
showCornerTriangle,
expanded,
@ -81,11 +78,12 @@ export default class ToolbarMenuItem extends Component {
} = this.props;
return (
<div
className={cx(styles.buttonWrapper, !showCornerTriangle || styles.cornerTriangle)}
<Styled.ButtonWrapper
showCornerTriangle={showCornerTriangle}
hidden={disabled}
>
<Button
<Styled.ToolbarButton
state={expanded ? 'active' : 'inactive'}
aria-expanded={expanded}
aria-haspopup={haspopup}
hideLabel
@ -100,12 +98,11 @@ export default class ToolbarMenuItem extends Component {
onKeyPress={this.handleOnMouseDown}
onKeyUp={this.handleOnMouseUp}
onBlur={onBlur}
className={className}
setRef={this.setRef}
disabled={disabled}
/>
{children}
</div>
</Styled.ButtonWrapper>
);
}
}
@ -126,7 +123,7 @@ ToolbarMenuItem.propTypes = {
icon: PropTypes.string,
customIcon: PropTypes.node,
label: PropTypes.string.isRequired,
className: PropTypes.string.isRequired,
toolbarActive: PropTypes.bool,
disabled: PropTypes.bool,
showCornerTriangle: PropTypes.bool,
};
@ -139,4 +136,5 @@ ToolbarMenuItem.defaultProps = {
children: null,
disabled: false,
showCornerTriangle: false,
toolbarActive: false,
};

View File

@ -0,0 +1,116 @@
import styled from 'styled-components';
import {
toolbarButtonWidth,
toolbarButtonHeight,
toolbarItemOutlineOffset,
toolbarButtonBorder,
toolbarButtonBorderRadius,
toolbarItemTrianglePadding,
} from '/imports/ui/stylesheets/styled-components/general';
import {
toolbarButtonBorderColor,
toolbarListColor,
toolbarButtonColor,
toolbarButtonBg,
toolbarListBg,
} from '/imports/ui/stylesheets/styled-components/palette';
import { toolbarButtonFontSize } from '/imports/ui/stylesheets/styled-components/typography';
import Button from '/imports/ui/components/button/component';
const ButtonWrapper = styled.div`
width: ${toolbarButtonWidth};
min-width: ${toolbarButtonWidth};
height: ${toolbarButtonHeight};
min-height: ${toolbarButtonHeight};
position: relative;
& > button {
outline-offset: ${toolbarItemOutlineOffset};
border-bottom: ${toolbarButtonBorder} solid ${toolbarButtonBorderColor};
}
&:first-child > button {
border-top-left-radius: ${toolbarButtonBorderRadius};
border-top-right-radius: ${toolbarButtonBorderRadius};
}
&:last-child > button {
border-bottom: 0;
border-bottom-left-radius: ${toolbarButtonBorderRadius};
border-bottom-right-radius: ${toolbarButtonBorderRadius};
}
${({ showCornerTriangle }) => showCornerTriangle && `
&::before {
content: '';
position: absolute;
border-color: transparent;
border-style: solid;
z-index: 2;
border-radius: 0;
border-width: 0.35em;
bottom: ${toolbarItemTrianglePadding};
left: ${toolbarItemTrianglePadding};
border-left-color: ${toolbarListColor};
border-bottom-color: ${toolbarListColor};
[dir="rtl"] & {
left: auto;
right: ${toolbarItemTrianglePadding};
border-left-color: transparent;
border-right-color: ${toolbarListColor};
}
}
`}
`;
const ToolbarButton = styled(Button)`
padding: 0;
border: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
align-items: center !important;
justify-content: center !important;
position: relative;
border-radius: 0;
box-shadow: none !important;
z-index: 1;
font-size: ${toolbarButtonFontSize};
color: ${toolbarButtonColor};
background-color: ${toolbarButtonBg};
border-color: ${toolbarButtonBorderColor};
&:focus,
&:hover {
border: 0;
}
& > i {
color: ${toolbarButtonColor};
}
${({ state }) => state === 'active' && `
background-color: ${toolbarListBg};
& > i {
color: ${toolbarListColor};
}
border-top-left-radius: 0;
border-top-right-radius: ${toolbarButtonBorderRadius};
[dir="rtl"] & {
border-top-left-radius: ${toolbarButtonBorderRadius};
border-top-right-radius: 0;
}
`}
`;
export default {
ButtonWrapper,
ToolbarButton,
};

View File

@ -1,8 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Button from '/imports/ui/components/button/component';
import _ from 'lodash';
import { styles } from '../styles';
import Styled from './styles';
export default class ToolbarSubmenuItem extends Component {
constructor() {
@ -48,15 +47,16 @@ export default class ToolbarSubmenuItem extends Component {
render() {
const {
className,
customIcon,
icon,
label,
toolbarActive,
} = this.props;
return (
<div className={styles.buttonWrapper}>
<Button
<Styled.ButtonWrapper>
<Styled.SubmenuButton
state={toolbarActive ? 'selected' : 'unselected'}
hideLabel
role="button"
color="default"
@ -67,10 +67,9 @@ export default class ToolbarSubmenuItem extends Component {
customIcon={customIcon}
onMouseUp={this.handleOnMouseUp}
onKeyPress={this.handleOnMouseUp}
className={className}
setRef={this.setRef}
/>
</div>
</Styled.ButtonWrapper>
);
}
}
@ -86,10 +85,11 @@ ToolbarSubmenuItem.propTypes = {
PropTypes.object,
PropTypes.number,
]).isRequired,
className: PropTypes.string.isRequired,
selected: PropTypes.bool,
};
ToolbarSubmenuItem.defaultProps = {
icon: null,
customIcon: null,
selected: false,
};

View File

@ -0,0 +1,82 @@
import styled from 'styled-components';
import {
toolbarButtonWidth,
toolbarButtonHeight,
toolbarItemOutlineOffset,
toolbarButtonBorder,
toolbarButtonBorderRadius,
} from '/imports/ui/stylesheets/styled-components/general';
import {
toolbarButtonBorderColor,
toolbarListColor,
toolbarButtonColor,
toolbarListBg,
toolbarListBgFocus,
} from '/imports/ui/stylesheets/styled-components/palette';
import { toolbarButtonFontSize } from '/imports/ui/stylesheets/styled-components/typography';
import Button from '/imports/ui/components/button/component';
const ButtonWrapper = styled.div`
width: ${toolbarButtonWidth};
min-width: ${toolbarButtonWidth};
height: ${toolbarButtonHeight};
min-height: ${toolbarButtonHeight};
position: relative;
& > button {
outline-offset: ${toolbarItemOutlineOffset};
border-bottom: ${toolbarButtonBorder} solid ${toolbarButtonBorderColor};
}
&:first-child > button {
border-top-left-radius: ${toolbarButtonBorderRadius};
border-bottom-left-radius: ${toolbarButtonBorderRadius};
}
`;
const SubmenuButton = styled(Button)`
padding: 0;
border: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
align-items: center !important;
justify-content: center !important;
position: relative;
border-radius: 0;
box-shadow: none !important;
z-index: 1;
font-size: ${toolbarButtonFontSize};
color: ${toolbarButtonColor};
border-color: ${toolbarButtonBorderColor};
background-color: ${toolbarListBg};
&:focus,
&:hover {
border: 0;
}
& > i {
color: ${toolbarListColor};
}
${({ state }) => state === 'selected' && `
background-color: ${toolbarListColor} !important;
background:red;
& > i {
color: ${toolbarListBgFocus} !important;
}
& > svg {
fill: ${toolbarListBgFocus};
}
`}
`;
export default {
ButtonWrapper,
SubmenuButton,
};

View File

@ -1,9 +1,8 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { defineMessages, injectIntl } from 'react-intl';
import _ from 'lodash';
import { styles } from '../styles';
import Styled from './styles';
import ToolbarSubmenuItem from '../toolbar-submenu-item/component';
const intlMessages = defineMessages({
@ -89,41 +88,27 @@ class ToolbarSubmenu extends Component {
static getCustomIcon(type, obj) {
if (type === 'color') {
return (
<svg className={styles.customSvgIcon}>
<Styled.CustomSvgIcon>
<rect x="20%" y="20%" width="60%" height="60%" fill={obj.value} />
</svg>
</Styled.CustomSvgIcon>
);
} if (type === 'thickness') {
return (
<svg className={styles.customSvgIcon}>
<Styled.CustomSvgIcon>
<circle cx="50%" cy="50%" r={obj.value} />
</svg>
</Styled.CustomSvgIcon>
);
} if (type === 'font-size') {
return (
<p className={styles.textThickness} style={{ fontSize: obj.value <= 32 ? obj.value : 32 }}>
<Styled.TextThickness style={{ fontSize: obj.value <= 32 ? obj.value : 32 }}>
Aa
</p>
</Styled.TextThickness>
);
}
return null;
}
static getWrapperClassNames(type) {
if (type === 'font-size') {
return cx(styles.fontSizeList, styles.toolbarList);
} if (
type === 'annotations'
|| type === 'thickness'
|| type === 'color'
) {
return styles.toolbarList;
}
return null;
}
constructor() {
super();
@ -253,10 +238,10 @@ class ToolbarSubmenu extends Component {
} = this.props;
return (
<div
<Styled.Wrapper
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
className={ToolbarSubmenu.getWrapperClassNames(type)}
type={type}
ref={(node) => { this.submenuItems = node; }}
>
{objectsToRender ? objectsToRender.map(obj => (
@ -266,14 +251,11 @@ class ToolbarSubmenu extends Component {
customIcon={customIcon ? ToolbarSubmenu.getCustomIcon(type, obj) : null}
onItemClick={this.onItemClick}
objectToReturn={obj}
className={cx(
styles.toolbarListButton,
objectSelected.value === obj.value ? styles.selectedListButton : '',
)}
toolbarActive={objectSelected.value === obj.value}
key={obj.value}
/>
)) : null}
</div>
</Styled.Wrapper>
);
}
}

View File

@ -0,0 +1,49 @@
import styled from 'styled-components';
import { toolbarButtonHeight, toolbarButtonWidth } from '/imports/ui/stylesheets/styled-components/general';
import { toolbarListColor, toolbarListBgFocus } from '/imports/ui/stylesheets/styled-components/palette';
const CustomSvgIcon = styled.svg`
position: absolute;
width: ${toolbarButtonWidth};
height: ${toolbarButtonHeight};
left: 0;
top: 0;
`;
const TextThickness = styled.p`
font-family: Arial, sans-serif;
font-weight: normal;
text-shadow: -1px 0 ${toolbarListBgFocus}, 0 1px ${toolbarListBgFocus}, 1px 0 ${toolbarListBgFocus}, 0 -1px ${toolbarListBgFocus};
margin: auto;
color: ${toolbarListColor};
`;
const Wrapper = styled.div`
${({ type }) => (
type === 'font-size'
|| type === 'annotations'
|| type === 'thickness'
|| type === 'color' ) && `
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: ${toolbarButtonHeight};
position: absolute;
right: ${toolbarButtonHeight};
left: auto;
top: 0;
box-shadow: 0 0 10px -2px rgba(0, 0, 0, .25);
[dir="rtl"] & {
right: auto;
left: ${toolbarButtonHeight};
}
`}
`;
export default {
CustomSvgIcon,
TextThickness,
Wrapper,
};

View File

@ -2,10 +2,12 @@ const smallOnly = 'only screen and (max-width: 40em)';
const mediumOnly = 'only screen and (min-width: 40.063em) and (max-width: 64em)';
const mediumUp = 'only screen and (min-width: 40.063em)';
const landscape = "only screen and (orientation: landscape)";
const phoneLandscape = 'only screen and (max-width: 480px) and (orientation: landscape)';
export {
smallOnly,
mediumOnly,
mediumUp,
landscape,
phoneLandscape,
};

View File

@ -24,6 +24,23 @@ const indicatorPadding = '.45rem'; // used to center presenter indicator icon in
const actionsBarHeight = '75px'; // TODO: Change to ActionsBar real height
const audioIndicatorWidth = '1.12rem';
const audioIndicatorFs = '75%';
const chatPollMarginSm = '.5rem';
const talkerBorderRadius = '2rem';
const talkerPaddingXsm = '.13rem';
const talkerPaddingLg = '.75rem';
const talkerPaddingXl = '1.62rem';
const talkerMaxWidth = '10rem';
const talkerMarginSm = '.5rem';
const spokeOpacity = '.5';
const toolbarButtonWidth = '3rem';
const toolbarButtonHeight = '3rem';
const toolbarItemOutlineOffset = '-.19rem';
const toolbarButtonBorder = '1px';
const toolbarButtonBorderRadius = '5px';
const toolbarItemTrianglePadding = '2px';
const toolbarMargin = '.8rem';
export {
borderSizeSmall,
@ -51,4 +68,19 @@ export {
actionsBarHeight,
audioIndicatorWidth,
audioIndicatorFs,
chatPollMarginSm,
talkerBorderRadius,
talkerPaddingXsm,
talkerPaddingLg,
talkerMaxWidth,
talkerMarginSm,
spokeOpacity,
talkerPaddingXl,
toolbarButtonWidth,
toolbarButtonHeight,
toolbarItemOutlineOffset,
toolbarButtonBorder,
toolbarButtonBorderRadius,
toolbarItemTrianglePadding,
toolbarMargin,
};

View File

@ -32,12 +32,26 @@ const itemFocusBorder = colorBlueLighter;
const btnDefaultColor = colorGray;
const btnPrimaryBorder = colorPrimary;
const btnDefaultBg = colorWhite;
const toolbarButtonColor = btnDefaultColor;
const userThumbnailBorder = colorGrayLight;
const loaderBg = colorGrayDark;
const loaderBullet = colorWhite;
const systemMessageBackgroundColor = '#F9FBFC';
const systemMessageBorderColor = '#C5CDD4';
const systemMessageFontColor = colorGrayDark;
const colorHeading = colorGrayDark;
const palettePlaceholderText = '#787675';
const pollAnnotationGray = '#333333';
const toolbarButtonBorderColor = colorGrayLighter;
const toolbarListColor = colorGray;
const toolbarButtonBg = btnDefaultBg;
const toolbarListBg = '#DDD';
const toolbarListBgFocus = '#C6C6C6';
export {
colorWhite,
colorOffWhite,
@ -69,4 +83,15 @@ export {
userThumbnailBorder,
loaderBg,
loaderBullet,
systemMessageBackgroundColor,
systemMessageBorderColor,
systemMessageFontColor,
colorHeading,
palettePlaceholderText,
pollAnnotationGray,
toolbarButtonBorderColor,
toolbarListColor,
toolbarButtonBg,
toolbarListBg,
toolbarListBgFocus,
};

View File

@ -1,4 +1,5 @@
import styled from 'styled-components';
import Button from '/imports/ui/components/button/component';
const FlexColumn = styled.div`
display: flex;
@ -41,6 +42,14 @@ const HeaderElipsis = styled.h3`
text-overflow: ellipsis;
`;
const ButtonElipsis = styled(Button)`
min-width: 0;
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;
export {
FlexColumn,
FlexRow,
@ -48,4 +57,5 @@ export {
TextElipsis,
TitleElipsis,
HeaderElipsis,
ButtonElipsis,
};

View File

@ -9,6 +9,9 @@ const fontSizeXL = '1.75rem';
const fontSizeMD = '0.95rem';
const headingsFontWeight = '500';
const btnFontWeight = '600';
const talkerFontWeight = '400';
const toolbarButtonFontSize = '1.75rem';
export {
lineHeightComputed,
@ -21,4 +24,7 @@ export {
fontSizeXL,
fontSizeMD,
headingsFontWeight,
btnFontWeight,
talkerFontWeight,
toolbarButtonFontSize,
};

View File

@ -11,7 +11,6 @@ const stressTest = require('./stress/stress.obj');
const userTest = require('./user/user.obj');
const virtualizedListTest = require('./virtualizedlist/virtualizedlist.obj');
const webcamTest = require('./webcam/webcam.obj');
const webcamLayout = require('./webcam/webcamlayout.obj');
const whiteboardTest = require('./whiteboard/whiteboard.obj');
process.setMaxListeners(Infinity);
@ -28,7 +27,6 @@ describe('Shared Notes ', sharedNotesTest);
describe('User', userTest);
describe('Virtualized List', virtualizedListTest);
describe('Webcam', webcamTest);
describe('Webcam Layout', webcamLayout);
describe('Whiteboard', whiteboardTest);
if (process.env.STRESS_TEST === 'true') {
describe('Stress', stressTest);

View File

@ -156,6 +156,7 @@ exports.stopScreenSharing = 'button[data-test="stopScreenShare"]';
exports.presentationToolbarWrapper = '#presentationToolbarWrapper';
exports.presentationTitle = '[class^="presentationTitle--"]';
exports.hidePresentation = 'button[data-test="hidePresentationButton"]';
exports.minimizePresentation = 'button[aria-label="Minimize presentation"]';
exports.restorePresentation = 'button[data-test="restorePresentationButton"]';
exports.nextSlide = '[data-test="nextSlide"]';
exports.prevSlide = '[data-test="prevSlide"]';
@ -173,6 +174,16 @@ exports.confirmManagePresentation = 'button[data-test="confirmManagePresentation
exports.allowPresentationDownload = 'button[data-test="allowPresentationDownload"]';
exports.disallowPresentationDownload = 'button[data-test="disallowPresentationDownload"]';
exports.uploadPresentationFileName = 'uploadTest.png';
exports.presentationContainer = 'div[class^="presentationContainer--"]';
exports.externalVideoBtn = 'li[data-test="external-video"]';
exports.externalVideoModalHeader = 'header[data-test="videoModalHeader"]';
exports.videoModalInput = 'input[id="video-modal-input"]';
exports.startShareVideoBtn = 'button[aria-label="Share a new video"]';
exports.videoPlayer = 'div[data-test="videoPlayer"]';
// YouTube frame
exports.youtubeLink = 'https://www.youtube.com/watch?v=Hso8yLzkqj8&ab_channel=BigBlueButton';
exports.youtubeFrame = 'iframe[title^="YouTube"]';
exports.ytFrameTitle = 'a[class^="ytp-title-link"]';
// User
exports.firstUser = '[data-test="userListItemCurrent"]';

View File

@ -202,6 +202,7 @@ class Page {
'--window-size=1150,980',
'--allow-file-access',
'--lang=en-US',
'--disable-features=IsolateOrigins,site-per-process',
];
return {
headless: false,

View File

@ -574,7 +574,7 @@ class CustomParameters {
await this.page2.startRecording(testName);
await this.page1.screenshot(`${testName}`, `01-page1-${testName}`);
await this.page2.screenshot(`${testName}`, `01-page2-${testName}`);
await this.page2.waitAndClick(e.hidePresentation);
await this.page2.waitAndClick(e.minimizePresentation);
await this.page2.screenshot(`${testName}`, `02-page2-${testName}`);
const zoomInCase = await util.zoomIn(this.page1);
await this.page1.screenshot(`${testName}`, `02-page1-${testName}`);
@ -619,7 +619,7 @@ class CustomParameters {
await this.page2.startRecording(testName);
await this.page1.screenshot(`${testName}`, `01-page1-${testName}`);
await this.page2.screenshot(`${testName}`, `01-page2-${testName}`);
await this.page2.waitAndClick(e.hidePresentation);
await this.page2.waitAndClick(e.minimizePresentation);
await this.page2.screenshot(`${testName}`, `02-page2-${testName}`);
const pollCase = await util.poll(this.page1, this.page2) === true;
await this.page2.waitForSelector(e.smallToastMsg);

View File

@ -793,9 +793,9 @@
}
},
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
"ansi-styles": {
"version": "4.3.0",
@ -866,11 +866,11 @@
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
},
"axios": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"requires": {
"follow-redirects": "^1.10.0"
"follow-redirects": "^1.14.0"
}
},
"babel-jest": {
@ -1996,9 +1996,9 @@
}
},
"follow-redirects": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz",
"integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA=="
"version": "1.14.5",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz",
"integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA=="
},
"for-in": {
"version": "1.0.2",
@ -4972,9 +4972,9 @@
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
"tmpl": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
"integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE="
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="
},
"to-fast-properties": {
"version": "2.0.0",

View File

@ -6,9 +6,9 @@
"verbose": false
},
"dependencies": {
"axios": "^0.21.1",
"child_process": "^1.0.2",
"axios": "^0.21.4",
"babel-jest": "^27.0.6",
"child_process": "^1.0.2",
"dotenv": "^6.0.0",
"fs-extra": "^9.1.0",
"jest": "^26.6.3",

View File

@ -100,9 +100,9 @@ class Polling {
e.receivedAnswer, e.answerMessage
);
await this.modPage.screenshot(testName, '04-success-to-receive-answer');
} catch (e) {
} catch (er) {
await this.modPage.screenshot(testName, '04-failed-to-receive-answer');
await this.modPage.logger(e);
await this.modPage.logger(er);
return false;
}

View File

@ -1,8 +1,8 @@
const Page = require('../core/page');
const e = require('../core/elements');
const util = require('./util');
const { ELEMENT_WAIT_LONGER_TIME, ELEMENT_WAIT_TIME } = require('../core/constants');
const { checkElement, checkElementTextIncludes, checkElementText } = require('../core/util');
const { ELEMENT_WAIT_LONGER_TIME } = require('../core/constants');
const { checkElement, checkElementText } = require('../core/util');
class Presentation {
constructor() {
@ -129,6 +129,59 @@ class Presentation {
return false;
}
}
async hideAndRestorePresentation(testName) {
try {
await this.modPage.waitForSelector(e.whiteboard);
await this.modPage.screenshot(testName, '01-after-close-audio-modal');
await this.modPage.waitAndClick(e.minimizePresentation);
const presentationWasRemoved = await this.modPage.wasRemoved(e.presentationContainer);
await this.modPage.screenshot(testName, '02-minimize-presentation');
await this.modPage.waitAndClick(e.restorePresentation);
const presentationWasRestored = await this.modPage.hasElement(e.presentationContainer);
await this.modPage.screenshot(testName, '03-restore-presentation');
return presentationWasRemoved && presentationWasRestored;
} catch (err) {
await this.modPage.logger(err);
return false;
}
}
async startExternalVideo(testName) {
try {
await this.modPage.waitForSelector(e.whiteboard);
await this.modPage.screenshot(testName, '01-after-close-audio-modal');
await this.modPage.waitAndClick(e.actions);
await this.modPage.waitAndClick(e.externalVideoBtn);
await this.modPage.waitForSelector(e.externalVideoModalHeader);
await this.modPage.type(e.videoModalInput, e.youtubeLink);
await this.modPage.screenshot(testName, '02-before-start-sharing-video');
await this.modPage.waitAndClick(e.startShareVideoBtn);
const modFrame = await this.getFrame(this.modPage, e.youtubeFrame);
await this.modPage.screenshot(testName, '03-modPage-after-rendering-frame');
const userFrame = await this.getFrame(this.userPage, e.youtubeFrame);
await this.userPage.screenshot(testName, '03-userPage-after-rendering-frame');
const resp = (await modFrame.hasElement('video')) && (await userFrame.hasElement('video'));
return resp === true;
} catch (err) {
await this.modPage.logger(err);
return false;
}
}
async getFrame(page, frameSelector) {
await page.waitForSelector(frameSelector);
const handleFrame = await page.page.$(frameSelector);
const contentFrame = await handleFrame.contentFrame();
const frame = new Page(contentFrame);
await frame.waitForSelector(e.ytFrameTitle);
return frame;
}
}
module.exports = exports = Presentation;

View File

@ -98,5 +98,50 @@ const presentationTest = () => {
expect(response).toBe(true);
Page.checkRegression(24.62, screenshot);
});
test('Hide/Restore presentation', async () => {
const test = new Presentation();
let response;
let screenshot;
try {
const testName = 'hideAndRestorePresentation';
await test.modPage.logger('begin of ', testName);
await test.initModPage(testName);
await test.modPage.startRecording(testName);
response = await test.hideAndRestorePresentation(testName);
await test.modPage.stopRecording();
screenshot = await test.modPage.page.screenshot();
await test.modPage.logger('end of ', testName);
} catch (e) {
await test.modPage.logger(e);
} finally {
await test.modPage.close();
}
expect(response).toBe(true);
Page.checkRegression(24.62, screenshot);
});
test('Start external video', async () => {
const test = new Presentation();
let response;
let screenshot;
try {
const testName = 'startExternalVideo';
await test.modPage.logger('begin of ', testName);
await test.initPages(testName);
await test.modPage.startRecording(testName);
response = await test.startExternalVideo(testName);
await test.modPage.stopRecording();
screenshot = await test.modPage.page.screenshot();
await test.modPage.logger('end of ', testName);
} catch (e) {
await test.modPage.logger(e);
} finally {
await closePages(test.modPage, test.userPage);
}
expect(response).toBe(true);
Page.checkRegression(24.62, screenshot);
});
};
module.exports = exports = presentationTest;

View File

@ -6,7 +6,7 @@ usage() {
BBB Health Check
OPTIONS:
-t <test name: webcamlayout/whiteboard/webcam/virtualizedlist/user/trigger/sharednotes/screenshare/presentation/polling/notifications/customparameters/chat/breakout/audio/all>
-t <test name: whiteboard/webcam/virtualizedlist/user/trigger/sharednotes/screenshare/presentation/polling/notifications/customparameters/chat/breakout/audio/all>
-u Print usage
HERE

View File

@ -8,14 +8,12 @@ class ShareScreen extends Page {
super();
}
async test() {
async startSharing() {
try {
await util.startScreenshare(this);
await this.waitForSelector(e.screenshareConnecting);
await this.waitForSelector(e.screenShareVideo, VIDEO_LOADING_WAIT_TIME);
const response = await this.hasElement(e.isSharingScreen, true);
const resp = await this.hasElement(e.isSharingScreen);
return response === true;
return resp === true;
} catch (err) {
await this.logger(err);
return false;

View File

@ -22,7 +22,7 @@ const screenShareTest = () => {
await test.logger('begin of ', testName);
await test.init(true, true, testName)
await test.startRecording(testName);
response = await test.test();
response = await test.startSharing();
await test.logger('end of ', testName);
await test.stopRecording();
screenshot = await test.page.screenshot();

View File

@ -3,9 +3,6 @@ const { VIDEO_LOADING_WAIT_TIME } = require('../core/constants');
async function startScreenshare(test) {
await test.waitAndClick(e.startScreenSharing);
}
async function waitForScreenshareContainer(test) {
await test.waitForSelector(e.screenshareConnecting);
await test.waitForSelector(e.screenShareVideo, VIDEO_LOADING_WAIT_TIME);
}
@ -16,5 +13,4 @@ async function getScreenShareBreakoutContainer(test) {
}
exports.getScreenShareBreakoutContainer = getScreenShareBreakoutContainer;
exports.startScreenshare = startScreenshare;
exports.waitForScreenshareContainer = waitForScreenshareContainer;
exports.startScreenshare = startScreenshare;

View File

@ -246,9 +246,9 @@ class MultiUsers {
);
await this.userPage.screenshot(testName, '04-connection-network-success');
return true;
} catch (e) {
} catch (er) {
await this.userPage.screenshot(testName, '04-connection-network-failed');
this.userPage.logger(e);
this.userPage.logger(er);
return false;
}
} catch (err) {
@ -280,8 +280,8 @@ class MultiUsers {
await this.page1.waitAndClick(e.userListButton);
await this.page2.waitAndClick(e.userListButton);
await this.page2.waitAndClick(e.chatButtonKey);
const onUserListPanel = await this.page1.wasRemoved(e.hidePresentation);
const onChatPanel = await this.page2.wasRemoved(e.hidePresentation);
const onUserListPanel = await this.page1.wasRemoved(e.minimizePresentation);
const onChatPanel = await this.page2.wasRemoved(e.minimizePresentation);
return onUserListPanel && onChatPanel;
} catch (err) {

View File

@ -53,7 +53,6 @@ class Status extends Page {
async disableScreenshareFromConnectionStatus() {
try {
await utilScreenshare.startScreenshare(this);
await utilScreenshare.waitForScreenshareContainer(this);
await util.connectionStatus(this);
await this.waitAndClickElement(e.dataSavingScreenshare);
await this.waitAndClickElement(e.closeConnectionStatusModal);
@ -72,7 +71,6 @@ class Status extends Page {
await this.joinMicrophone();
await this.shareWebcam(true, ELEMENT_WAIT_LONGER_TIME);
await utilScreenshare.startScreenshare(this);
await utilScreenshare.waitForScreenshareContainer(this);
await util.connectionStatus(this);
await sleep(5000);
const connectionStatusItemEmpty = await this.page.evaluate(checkElementLengthEqualTo, e.connectionStatusItemEmpty, 0);

View File

@ -54,5 +54,28 @@ const webcamTest = () => {
expect(response).toBe(true);
Page.checkRegression(7.5, screenshot);
});
test('Checks webcam talking indicator', async () => {
const test = new Share();
let response;
let screenshot;
try {
const testName = 'joinWebcamAndMicrophone';
await test.logger('begin of ', testName);
await test.init(true, false, testName);
await test.startRecording(testName);
await test.webcamLayoutStart();
response = await test.webcamLayoutTest(testName);
await test.logger('end of ', testName);
await test.stopRecording();
screenshot = await test.page.screenshot();
} catch (err) {
await test.logger(err);
} finally {
await test.close();
}
expect(response).toBe(true);
Page.checkRegression(10.83, screenshot);
});
};
module.exports = exports = webcamTest;

View File

@ -1,36 +0,0 @@
const Page = require('../core/page');
const Share = require('./share');
const { toMatchImageSnapshot } = require('jest-image-snapshot');
const { MAX_WEBCAM_LAYOUT_TEST_TIMEOUT } = require('../core/constants');
expect.extend({ toMatchImageSnapshot });
const webcamLayoutTest = () => {
beforeEach(() => {
jest.setTimeout(MAX_WEBCAM_LAYOUT_TEST_TIMEOUT);
});
test('Join Webcam and microphone', async () => {
const test = new Share();
let response;
let screenshot;
try {
const testName = 'joinWebcamAndMicrophone';
await test.logger('begin of ', testName);
await test.init(true, false, testName);
await test.startRecording(testName);
await test.webcamLayoutStart();
response = await test.webcamLayoutTest(testName);
await test.logger('end of ', testName);
await test.stopRecording();
screenshot = await test.page.screenshot();
} catch (err) {
await test.logger(err);
} finally {
await test.close();
}
expect(response).toBe(true);
Page.checkRegression(10.83, screenshot);
});
};
module.exports = exports = webcamLayoutTest;

View File

@ -1,3 +0,0 @@
const webcamLayoutTest = require('./webcamlayout.obj');
describe('Webcams Layout', webcamLayoutTest);

View File

@ -407,3 +407,6 @@ endWhenNoModerator=false
# Number of minutes to wait for moderator rejoin before end meeting (if `endWhenNoModerator` enabled)
endWhenNoModeratorDelayInMinutes=1
# Allow endpoint with current BigBlueButton version
allowRevealOfBBBVersion=false

View File

@ -186,6 +186,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<property name="endWhenNoModerator" value="${endWhenNoModerator}"/>
<property name="endWhenNoModeratorDelayInMinutes" value="${endWhenNoModeratorDelayInMinutes}"/>
<property name="defaultKeepEvents" value="${defaultKeepEvents}"/>
<property name="allowRevealOfBBBVersion" value="${allowRevealOfBBBVersion}"/>
</bean>
<import resource="doc-conversion.xml"/>

View File

@ -83,7 +83,7 @@ class ApiController {
withFormat {
xml {
render(text: responseBuilder.buildMeetingVersion(paramsProcessorUtil.getApiVersion(), RESP_CODE_SUCCESS), contentType: "text/xml")
render(text: responseBuilder.buildMeetingVersion(paramsProcessorUtil.getApiVersion(), paramsProcessorUtil.getBbbVersion(), RESP_CODE_SUCCESS), contentType: "text/xml")
}
}
}
@ -126,6 +126,8 @@ class ApiController {
Meeting newMeeting = paramsProcessorUtil.processCreateParams(params)
ApiErrors errors = new ApiErrors()
if (meetingService.createMeeting(newMeeting)) {
// See if the request came with pre-uploading of presentation.
uploadDocuments(newMeeting); //
@ -183,6 +185,11 @@ class ApiController {
request.getQueryString()
)
HashMap<String, String> roles = new HashMap<String, String>();
roles.put("moderator", ROLE_MODERATOR);
roles.put("viewer", ROLE_ATTENDEE);
if(!(validationResponse == null)) {
invalid(validationResponse.getKey(), validationResponse.getValue(), REDIRECT_RESPONSE)
return
@ -247,6 +254,10 @@ class ApiController {
role = Meeting.ROLE_ATTENDEE
}
if (!StringUtils.isEmpty(params.role) && roles.containsKey(params.role.toLowerCase())) {
role = roles.get(params.role.toLowerCase());
}
// We preprend "w_" to our internal meeting Id to indicate that this is a web user.
// For users joining using the phone, we will prepend "v_" so it will be easier
// to distinguish users who doesn't have a web client. (ralam june 12, 2017)
@ -358,13 +369,10 @@ class ApiController {
session[sessionToken] = sessionToken
meetingService.addUserSession(sessionToken, us)
logSessionInfo()
//Identify which of these to logs should be used. sessionToken or user-token
log.info("Session sessionToken for " + us.fullname + " [" + session[sessionToken] + "]")
log.info("Session user-token for " + us.fullname + " [" + session['user-token'] + "]")
logSession()
log.info("Session token: ${sessionToken}")
// Process if we send the user directly to the client or
@ -792,8 +800,6 @@ class ApiController {
String API_CALL = 'enter'
log.debug CONTROLLER_NAME + "#${API_CALL}"
logSessionInfo()
String respMessage = "Session not found."
boolean reject = false;
@ -835,7 +841,6 @@ class ApiController {
logoutUrl = us.logoutUrl
}
logSession()
log.info("Session token: ${sessionToken}")
response.addHeader("Cache-Control", "no-cache")
@ -1412,8 +1417,6 @@ class ApiController {
return false
}
logSession()
if (!session[token]) {
log.info("Session for token ${token} not found")

View File

@ -3,6 +3,7 @@
<response>
<#-- Where code is a 'SUCCESS' String -->
<returncode>${returnCode}</returncode>
<version>${version}</version>
<apiVersion>${apiVersion}</apiVersion>
<bbbVersion>${bbbVersion}</bbbVersion>
</response>
</#compress>

View File

@ -45,7 +45,7 @@ export ROOT_URL=http://127.0.0.1/html5client
export MONGO_OPLOG_URL=mongodb://127.0.1.1/local
export MONGO_URL=mongodb://127.0.1.1/meteor
export NODE_ENV=production
export NODE_VERSION=node-v14.17.6-linux-x64
export NODE_VERSION=node-v14.18.1-linux-x64
export SERVER_WEBSOCKET_COMPRESSION=0
export BIND_IP=127.0.0.1
PORT=$PORT /usr/share/$NODE_VERSION/bin/node --max-old-space-size=2048 --max_semi_space_size=128 main.js NODEJS_BACKEND_INSTANCE_ID=$INSTANCE_ID

View File

@ -45,7 +45,7 @@ export ROOT_URL=http://127.0.0.1/html5client
export MONGO_OPLOG_URL=mongodb://127.0.1.1/local
export MONGO_URL=mongodb://127.0.1.1/meteor
export NODE_ENV=production
export NODE_VERSION=node-v14.17.6-linux-x64
export NODE_VERSION=node-v14.18.1-linux-x64
export SERVER_WEBSOCKET_COMPRESSION=0
export BIND_IP=127.0.0.1
PORT=$PORT /usr/share/$NODE_VERSION/bin/node --max-old-space-size=2048 --max_semi_space_size=128 main.js

View File

@ -35,6 +35,7 @@ mkdir -p staging/usr/share/meteor
rm -rf /tmp/html5-build
mkdir -p /tmp/html5-build
npm -v
meteor npm -v
meteor node -v
cat .meteor/release
@ -42,19 +43,29 @@ cat .meteor/release
# meteor version control was moved to the Dockerfile of the image used in .gitlab-ci.yml
# meteor update --allow-superuser --release 2.3.6
# build the HTML5 client
# Install the npm dependencies needed for the HTML5 client.
# Argument 'c' means package-lock.json will be respected
# --production means we won't be installing devDependencies
meteor npm ci --production
METEOR_DISABLE_OPTIMISTIC_CACHING=1 meteor build /tmp/html5-build --architecture os.linux.x86_64 --allow-superuser
# Build the HTML5 client https://guide.meteor.com/deployment.html#custom-deployment
# https://docs.meteor.com/environment-variables.html#METEOR-DISABLE-OPTIMISTIC-CACHING - disable caching because we're only building once
# --allow-superuser
# --directory - instead of creating tar.gz and then extracting (which is the default option)
METEOR_DISABLE_OPTIMISTIC_CACHING=1 meteor build /tmp/html5-build --architecture os.linux.x86_64 --allow-superuser --directory
# extract, install the npm dependencies, then copy to staging
tar xfz /tmp/html5-build/bbb-html5_${VERSION}_${DISTRO}.tar.gz -C /tmp/html5-build/
# Install the npm dependencies, then copy to staging
cd /tmp/html5-build/bundle/programs/server/
npm i --production
# Install Meteor related dependencies
# Note that we don't use "c" argument as there is no package-lock.json here
# only package.json. The dependencies for bbb-html5 are already installed in
# /usr/share/meteor/bundle/programs/server/npm/node_modules/ and not in
# /usr/share/meteor/bundle/programs/server/node_modules
npm i
cd -
cp -r /tmp/html5-build/bundle staging/usr/share/meteor
cp $DISTRO/systemd_start.sh staging/usr/share/meteor/bundle
chmod +x staging/usr/share/meteor/bundle/systemd_start.sh
@ -81,11 +92,11 @@ cp $DISTRO/bbb-html5-frontend@.service staging/usr/lib/systemd/system
mkdir -p staging/usr/share
if [ ! -f node-v14.17.6-linux-x64.tar.gz ]; then
wget https://nodejs.org/dist/v14.17.6/node-v14.17.6-linux-x64.tar.gz
if [ ! -f node-v14.18.1-linux-x64.tar.gz ]; then
wget https://nodejs.org/dist/v14.18.1/node-v14.18.1-linux-x64.tar.gz
fi
cp node-v14.17.6-linux-x64.tar.gz staging/usr/share
cp node-v14.18.1-linux-x64.tar.gz staging/usr/share
if [ -f staging/usr/share/meteor/bundle/programs/web.browser/head.html ]; then
sed -i "s/VERSION/$(($BUILD))/" staging/usr/share/meteor/bundle/programs/web.browser/head.html