Merge remote-tracking branch 'upstream/develop' into styled-components-8
This commit is contained in:
commit
f1281a77e3
@ -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
|
||||
|
@ -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";
|
||||
|
@ -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) {
|
||||
|
@ -118,6 +118,9 @@ public class ParamsProcessorUtil {
|
||||
private Integer defaultEndWhenNoModeratorDelayInMinutes = 1;
|
||||
private Integer defaultHtml5InstanceId = 1;
|
||||
|
||||
private String bbbVersion = "";
|
||||
private Boolean allowRevealOfBBBVersion = false;
|
||||
|
||||
private String formatConfNum(String s) {
|
||||
if (s.length() > 5) {
|
||||
/* Reverse conference number.
|
||||
@ -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)) {
|
||||
@ -1178,4 +1189,12 @@ public class ParamsProcessorUtil {
|
||||
this.defaultEndWhenNoModeratorDelayInMinutes = value;
|
||||
}
|
||||
|
||||
public void setBbbVersion(String version) {
|
||||
this.bbbVersion = this.allowRevealOfBBBVersion ? version : "";
|
||||
}
|
||||
|
||||
public void setAllowRevealOfBBBVersion(Boolean allowVersion) {
|
||||
this.allowRevealOfBBBVersion = allowVersion;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -1 +1 @@
|
||||
METEOR@2.3.6
|
||||
METEOR@2.5
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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)}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
};
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
};
|
@ -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;
|
||||
}
|
||||
}
|
@ -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}`}
|
||||
|
||||
</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}`}
|
||||
|
||||
</span>,
|
||||
1: <span className={styles.coupleTyper}>
|
||||
</Styled.CoupleTyper>,
|
||||
1: <Styled.CoupleTyper>
|
||||
|
||||
{`${name2}`}
|
||||
|
||||
</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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
92
bigbluebutton-html5/imports/ui/components/chat/styles.js
Normal file
92
bigbluebutton-html5/imports/ui/components/chat/styles.js
Normal 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,
|
||||
};
|
@ -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);
|
||||
}
|
@ -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(),
|
||||
]
|
||||
);
|
||||
|
@ -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,
|
||||
};
|
@ -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;
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
@ -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;
|
||||
}
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
@ -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;
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
};
|
@ -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>
|
||||
|
@ -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,
|
||||
};
|
@ -1,8 +0,0 @@
|
||||
:root {
|
||||
--poll-annotation-gray: #333333;
|
||||
}
|
||||
|
||||
.outline {
|
||||
stroke: var(--poll-annotation-gray);
|
||||
stroke-width: .5;
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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"]';
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
26
bigbluebutton-tests/puppeteer/package-lock.json
generated
26
bigbluebutton-tests/puppeteer/package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
@ -17,4 +14,3 @@ async function getScreenShareBreakoutContainer(test) {
|
||||
|
||||
exports.getScreenShareBreakoutContainer = getScreenShareBreakoutContainer;
|
||||
exports.startScreenshare = startScreenshare;
|
||||
exports.waitForScreenshareContainer = waitForScreenshareContainer;
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
@ -1,3 +0,0 @@
|
||||
const webcamLayoutTest = require('./webcamlayout.obj');
|
||||
|
||||
describe('Webcams Layout', webcamLayoutTest);
|
@ -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
|
||||
|
@ -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"/>
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user