Merge pull request #5528 from KDSBrowne/xx.add.Edge-support-html5

Add support for HTML5 client in Edge
This commit is contained in:
Anton Georgiev 2018-06-21 13:36:04 -04:00 committed by GitHub
commit fb66e9420a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 258 additions and 177 deletions

View File

@ -5,7 +5,7 @@ import { defineMessages, injectIntl, intlShape } from 'react-intl';
import Modal from 'react-modal';
import cx from 'classnames';
import Resizable from 're-resizable';
import browser from 'browser-detect';
import ToastContainer from '../toast/container';
import ModalContainer from '../modal/container';
import NotificationsBarContainer from '../notifications-bar/container';
@ -79,6 +79,11 @@ class App extends Component {
document.getElementsByTagName('html')[0].lang = locale;
document.getElementsByTagName('html')[0].style.fontSize = this.props.fontSize;
const BROWSER_RESULTS = browser();
const body = document.getElementsByTagName('body')[0];
body.classList.add(`browser-${BROWSER_RESULTS.name}`);
body.classList.add(`os-${BROWSER_RESULTS.os.split(' ').shift().toLowerCase()}`);
this.handleWindowResize();
window.addEventListener('resize', this.handleWindowResize, false);
}

View File

@ -2,7 +2,6 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ModalBase from '/imports/ui/components/modal/base/component';
import Button from '/imports/ui/components/button/component';
import deviceInfo from '/imports/utils/deviceInfo';
import { defineMessages, injectIntl, intlShape } from 'react-intl';
import { styles } from './styles';
import PermissionsOverlay from '../permissions-overlay/component';
@ -10,7 +9,6 @@ import AudioSettings from '../audio-settings/component';
import EchoTest from '../echo-test/component';
import Help from '../help/component';
const propTypes = {
intl: intlShape.isRequired,
closeModal: PropTypes.func.isRequired,
@ -307,11 +305,12 @@ class AudioModal extends Component {
const {
isEchoTest,
intl,
isIOSChrome,
} = this.props;
const { content } = this.state;
if (deviceInfo.osType().isIOSChrome) {
if (isIOSChrome) {
return (
<div>
<div className={styles.warning}>!</div>
@ -381,6 +380,7 @@ class AudioModal extends Component {
const {
intl,
showPermissionsOvelay,
isIOSChrome,
} = this.props;
const { content } = this.state;
@ -399,16 +399,13 @@ class AudioModal extends Component {
data-test="audioModalHeader"
className={styles.header}
>{
(!deviceInfo.osType().isIOSChrome ?
isIOSChrome ? null :
<h3 className={styles.title}>
{content ?
this.contents[content].title :
intl.formatMessage(intlMessages.audioChoiceLabel)}
</h3> : <h3 className={styles.title} />
)
</h3>
}
<Button
data-test="modalBaseCloseButton"
className={styles.closeBtn}

View File

@ -1,6 +1,7 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { withModalMounter } from '/imports/ui/components/modal/service';
import browser from 'browser-detect';
import AudioModal from './component';
import Service from '../service';
@ -24,7 +25,7 @@ export default withModalMounter(withTracker(({ mountModal }) =>
}
reject(() => {
Service.exitAudio();
})
});
});
return call.then(() => {
@ -55,4 +56,5 @@ export default withModalMounter(withTracker(({ mountModal }) =>
joinFullAudioImmediately: !listenOnlyMode && skipCheck,
joinFullAudioEchoTest: !listenOnlyMode && !skipCheck,
forceListenOnlyAttendee: listenOnlyMode && forceListenOnly && !Service.isUserModerator(),
isIOSChrome: browser().name === 'crios',
}))(AudioModalContainer));

View File

@ -1,4 +1,4 @@
import React, { Component } from 'react';
import React from 'react';
import { injectIntl, intlShape, defineMessages } from 'react-intl';
import { styles } from './styles';
@ -17,52 +17,16 @@ const intlMessages = defineMessages({
},
});
class PermissionsOverlay extends Component {
constructor(props) {
super(props);
const broswerStyles = {
Chrome: {
top: '145px',
left: '380px',
},
Firefox: {
top: '210px',
left: '605px',
},
Safari: {
top: '100px',
left: '100px',
},
};
const browser = window.bowser.name;
this.state = {
styles: {
top: broswerStyles[browser].top,
left: broswerStyles[browser].left,
},
};
}
render() {
const {
intl,
} = this.props;
return (
<div className={styles.overlay}>
<div style={this.state.styles} className={styles.hint}>
{ intl.formatMessage(intlMessages.title) }
<small>
{ intl.formatMessage(intlMessages.hint) }
</small>
</div>
</div>
);
}
}
const PermissionsOverlay = ({ intl }) => (
<div className={styles.overlay}>
<div className={styles.hint}>
{ intl.formatMessage(intlMessages.title) }
<small>
{ intl.formatMessage(intlMessages.hint) }
</small>
</div>
</div>
);
PermissionsOverlay.propTypes = propTypes;

View File

@ -1,3 +1,44 @@
@mixin arrowIconStyle() {
&:after {
top: -50px;
left: -20px;
font-size: 20px;
-webkit-animation: bounce 2s infinite;
animation: bounce 2s infinite;
display: block;
font-family: 'bbb-icons';
content: "\E906";
position: relative;
}
:global(.browser-edge) &:after {
top: -50px;
left: -15.5em;
font-size: 20px;
-webkit-animation: bounceRotate 2s infinite;
animation: bounceRotate 2s infinite;
}
}
@mixin positionHint() {
:global(.browser-edge) & {
left: 50%;
bottom: 10%;
}
:global(.browser-firefox) & {
top: 210px;
left: 605px;
}
:global(.browser-chrome) & {
top: 145px;
left: 380px;
}
:global(.browser-safari) & {
top: 100px;
left: 100px;
}
}
.overlay {
position: fixed;
z-index: 1002;
@ -10,6 +51,8 @@
}
.hint {
@include positionHint();
position: absolute;
color: #fff;
font-size: 16px;
@ -25,17 +68,7 @@
opacity: .6;
}
&:after {
display: block;
font-family: 'bbb-icons';
content: "\E906";
position: relative;
top: -50px;
left: -20px;
font-size: 20px;
-webkit-animation: bounce 2s infinite;
animation: bounce 2s infinite;
}
@include arrowIconStyle();
}
@-webkit-keyframes bounce {
@ -80,6 +113,21 @@
}
}
@keyframes bounceRotate {
0%, 20%, 50%, 80%, 100% {
-ms-transform: translateY(0) rotate(180deg);
transform: translateY(0) rotate(180deg);
}
40% {
-ms-transform: translateY(10px) rotate(180deg);
transform: translateY(10px) rotate(180deg);
}
60% {
-ms-transform: translateY(5px) rotate(180deg);
transform: translateY(5px) rotate(180deg);
}
}
@keyframes fade-in {
0% {
opacity: 0;

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import browser from 'browser-detect';
import Modal from '/imports/ui/components/modal/simple/component';
import deviceInfo from '/imports/utils/deviceInfo';
import _ from 'lodash';
import { styles } from './styles';
@ -77,23 +77,24 @@ const SHORTCUTS_CONFIG = Meteor.settings.public.app.shortcuts;
class ShortcutHelpComponent extends Component {
render() {
const { intl } = this.props;
const { isWindows, isLinux, isMac } = deviceInfo.osType();
const { isFirefox, isChrome, isIE } = deviceInfo.browserType();
const shortcuts = Object.values(SHORTCUTS_CONFIG);
const { name } = browser();
let accessMod = null;
if (isMac) {
accessMod = 'Control + Alt';
}
if (isWindows) {
accessMod = isIE ? 'Alt' : accessMod;
}
if (isWindows || isLinux) {
accessMod = isFirefox ? 'Alt + Shift' : accessMod;
accessMod = isChrome ? 'Alt' : accessMod;
switch (name) {
case 'chrome':
case 'edge':
accessMod = 'Alt';
break;
case 'firefox':
accessMod = 'Alt + Shift';
break;
case 'safari':
case 'crios':
case 'fxios':
accessMod = 'Control + Alt';
break;
}
return (

View File

@ -1,12 +1,12 @@
@import "/imports/ui/stylesheets/variables/palette";
@import "/imports/ui/stylesheets/variables/general";
@import "/imports/ui/stylesheets/mixins/_indicators";
/* Variables
* ==========
*/
$user-avatar-border: $color-gray-light;
$user-avatar-text: $color-white;
$user-indicators-offset: -5px;
$user-indicator-presenter-bg: $color-primary;
$user-indicator-voice-bg: $color-success;
$user-indicator-muted-bg: $color-danger;
$user-list-bg: $color-off-white;
@ -72,24 +72,14 @@ $user-color: currentColor; //picks the current color reference in the class
.presenter {
&:before {
content: "\00a0\e90b\00a0";
opacity: 1;
top: $user-indicators-offset;
left: $user-indicators-offset;
bottom: auto;
right: auto;
border-radius: 5px;
background-color: $user-indicator-presenter-bg;
padding: .425rem;
}
@include presenterIndicator();
}
.voice {
&:after {
content: "\00a0\e931\00a0";
background-color: $user-indicator-voice-bg;
opacity: 1;
width: 1.375rem;
height: 1.375rem;
top: 1.375rem;
left: 1.375rem;
}
@ -99,18 +89,19 @@ $user-color: currentColor; //picks the current color reference in the class
&:after {
content: "\00a0\e932\00a0";
background-color: $user-indicator-muted-bg;
opacity: 1;
}
}
.listenOnly {
&:after {
content: "\00a0\e90c\00a0";
opacity: 1;
}
}
.listenOnly, .muted, .voice {
@include indicatorStyles();
}
.content {
color: $user-avatar-text;
top: 50%;

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import RenderInBrowser from 'react-render-in-browser';
import AnnotationHelpers from '../helpers';
const DRAW_END = Meteor.settings.public.whiteboard.annotations.status.end;
@ -138,20 +139,19 @@ export default class TextDrawComponent extends Component {
renderViewerTextShape(results) {
const styles = TextDrawComponent.getViewerStyles(results);
const { isChrome, isEdge } = this.props.browserType;
return (
<g>
{ isChrome || isEdge ? null :
<clipPath id={this.props.annotation.id}>
<rect
x={results.x}
y={results.y}
width={results.width}
height={results.height}
/>
</clipPath>
}
<RenderInBrowser only firefox>
<clipPath id={this.props.annotation.id}>
<rect
x={results.x}
y={results.y}
width={results.width}
height={results.height}
/>
</clipPath>
</RenderInBrowser>
<foreignObject
clipPath={`url(#${this.props.annotation.id})`}
x={results.x}

View File

@ -1,6 +1,5 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import deviceInfo from '/imports/utils/deviceInfo';
import TextShapeService from './service';
import TextDrawComponent from './component';
@ -21,6 +20,5 @@ export default withTracker((params) => {
isActive,
setTextShapeValue: TextShapeService.setTextShapeValue,
resetTextShapeActiveId: TextShapeService.resetTextShapeActiveId,
browserType: deviceInfo.browserType(),
};
})(TextDrawContainer);

View File

@ -3,6 +3,9 @@ import PropTypes from 'prop-types';
import cx from 'classnames';
import { HEXToINTColor, INTToHEXColor } from '/imports/utils/hexInt';
import { defineMessages, injectIntl, intlShape } from 'react-intl';
import RenderInBrowser from 'react-render-in-browser';
import browser from 'browser-detect';
import { noop } from 'lodash';
import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
import { styles } from './styles.scss';
import ToolbarMenuItem from './toolbar-menu-item/component';
@ -58,6 +61,8 @@ const intlMessages = defineMessages({
},
});
const runExceptInEdge = fn => (browser().name === 'edge' ? noop : fn);
class WhiteboardToolbar extends Component {
constructor() {
super();
@ -97,6 +102,8 @@ class WhiteboardToolbar extends Component {
this.handleColorChange = this.handleColorChange.bind(this);
this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.componentDidMount = runExceptInEdge(this.componentDidMount);
this.componentDidUpdate = runExceptInEdge(this.componentDidUpdate);
}
componentWillMount() {
@ -174,7 +181,6 @@ class WhiteboardToolbar extends Component {
* 3. Switch from the Text tool to any other - trigger color and radius for thickness
* 4. Trigger initial animation for the icons
*/
// 1st case
if (this.state.colorSelected.value !== prevState.colorSelected.value) {
// 1st case b)
@ -186,13 +192,12 @@ class WhiteboardToolbar extends Component {
// 2nd case
} else if (this.state.thicknessSelected.value !== prevState.thicknessSelected.value) {
this.thicknessListIconRadius.beginElement();
// 3rd case
// 3rd case
} else if (this.state.annotationSelected.value !== 'text' &&
prevState.annotationSelected.value === 'text') {
prevState.annotationSelected.value === 'text') {
this.thicknessListIconRadius.beginElement();
this.thicknessListIconColor.beginElement();
}
// 4th case, initial animation is triggered in componentDidMount
}
@ -406,36 +411,41 @@ class WhiteboardToolbar extends Component {
renderThicknessItemIcon() {
return (
<svg className={styles.customSvgIcon} shapeRendering="geometricPrecision">
<circle
shapeRendering="geometricPrecision"
cx="50%"
cy="50%"
stroke="black"
strokeWidth="1"
>
<animate
ref={(ref) => { this.thicknessListIconColor = ref; }}
attributeName="fill"
attributeType="XML"
from={this.state.prevColorSelected.value}
to={this.state.colorSelected.value}
begin="indefinite"
dur={TRANSITION_DURATION}
repeatCount="0"
fill="freeze"
/>
<animate
ref={(ref) => { this.thicknessListIconRadius = ref; }}
attributeName="r"
attributeType="XML"
from={this.state.prevThicknessSelected.value}
to={this.state.thicknessSelected.value}
begin="indefinite"
dur={TRANSITION_DURATION}
repeatCount="0"
fill="freeze"
/>
</circle>
<RenderInBrowser only edge>
<circle cx="50%" cy="50%" r={this.state.thicknessSelected.value} stroke="black" strokeWidth="1" fill={this.state.colorSelected.value} />
</RenderInBrowser>
<RenderInBrowser except edge>
<circle
shapeRendering="geometricPrecision"
cx="50%"
cy="50%"
stroke="black"
strokeWidth="1"
>
<animate
ref={(ref) => { this.thicknessListIconColor = ref; }}
attributeName="fill"
attributeType="XML"
from={this.state.prevColorSelected.value}
to={this.state.colorSelected.value}
begin="indefinite"
dur={TRANSITION_DURATION}
repeatCount="0"
fill="freeze"
/>
<animate
ref={(ref) => { this.thicknessListIconRadius = ref; }}
attributeName="r"
attributeType="XML"
from={this.state.prevThicknessSelected.value}
to={this.state.thicknessSelected.value}
begin="indefinite"
dur={TRANSITION_DURATION}
repeatCount="0"
fill="freeze"
/>
</circle>
</RenderInBrowser>
</svg>
);
}
@ -474,19 +484,24 @@ class WhiteboardToolbar extends Component {
renderColorItemIcon() {
return (
<svg className={styles.customSvgIcon}>
<rect x="25%" y="25%" width="50%" height="50%" stroke="black" strokeWidth="1">
<animate
ref={(ref) => { this.colorListIconColor = ref; }}
attributeName="fill"
attributeType="XML"
from={this.state.prevColorSelected.value}
to={this.state.colorSelected.value}
begin="indefinite"
dur={TRANSITION_DURATION}
repeatCount="0"
fill="freeze"
/>
</rect>
<RenderInBrowser only edge>
<rect x="25%" y="25%" width="50%" height="50%" stroke="black" strokeWidth="1" fill={this.state.colorSelected.value} />
</RenderInBrowser>
<RenderInBrowser except edge>
<rect x="25%" y="25%" width="50%" height="50%" stroke="black" strokeWidth="1">
<animate
ref={(ref) => { this.colorListIconColor = ref; }}
attributeName="fill"
attributeType="XML"
from={this.state.prevColorSelected.value}
to={this.state.colorSelected.value}
begin="indefinite"
dur={TRANSITION_DURATION}
repeatCount="0"
fill="freeze"
/>
</rect>
</RenderInBrowser>
</svg>
);
}

View File

@ -0,0 +1,41 @@
@import "/imports/ui/stylesheets/variables/palette";
@import "/imports/ui/stylesheets/variables/general";
@mixin presenterIndicator() {
&:before {
opacity: 1;
top: $user-indicators-offset;
left: $user-indicators-offset;
bottom: auto;
right: auto;
border-radius: 5px;
background-color: $color-primary;
}
:global(.browser-chrome) &:before,
:global(.browser-firefox) &:before {
padding: .45rem;
}
:global(.browser-edge) &:before {
padding-top: $indicator-padding-top;
padding-left: $indicator-padding-left;
padding-right: $indicator-padding-right;
padding-bottom: $indicator-padding-bottom;
}
}
@mixin indicatorStyles() {
&:after {
opacity: 1;
width: 1.2rem;
height: 1.2rem;
}
:global(.browser-edge) &:after {
padding-top: $indicator-padding-top;
padding-left: $indicator-padding-left;
padding-right: $indicator-padding-right;
padding-bottom: $indicator-padding-bottom;
}
}

View File

@ -14,3 +14,14 @@ $lg-padding-y: 0.6rem;
$jumbo-padding-x: 3.025rem;
$jumbo-padding-y: 1.5rem;
//used to center presenter indicator icon in Chrome / Firefox
$indicator-padding: .425rem;
//used to center indicator icons in Edge
$indicator-padding-right: 1.2em;
$indicator-padding-left: 0.175em;
$indicator-padding-top: 0.7em;
$indicator-padding-bottom: 0.7em;
$user-indicators-offset: -5px;

View File

@ -12,25 +12,6 @@ const deviceInfo = {
isPhone: smallSide <= MAX_PHONE_SHORT_SIDE,
};
},
browserType() {
return {
// Uses features to determine browser
isChrome: !!window.chrome && !!window.chrome.webstore,
isFirefox: typeof InstallTrigger !== 'undefined',
isIE: 'ActiveXObject' in window,
isEdge: !document.documentMode && window.StyleMedia,
};
},
osType() {
return {
// Uses userAgent to determine operating system
isWindows: window.navigator.userAgent.indexOf('Windows') !== -1,
isMac: window.navigator.userAgent.indexOf('Mac') !== -1,
isLinux: window.navigator.userAgent.indexOf('Linux') !== -1,
isIOSChrome: navigator.userAgent.match('CriOS'),
};
},
};

View File

@ -401,6 +401,21 @@
"concat-map": "0.0.1"
}
},
"browser-detect": {
"version": "0.2.27",
"resolved": "https://registry.npmjs.org/browser-detect/-/browser-detect-0.2.27.tgz",
"integrity": "sha512-qjOSrFROblMbGhFbS1U7DkszptdRxAH7O9I3zZPT6oIbZKjhrudj+ZRuiQkuVtXs1/HEgMv+2zJuxZIsn/bLhQ==",
"requires": {
"core-js": "2.5.7"
},
"dependencies": {
"core-js": {
"version": "2.5.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
"integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw=="
}
}
},
"browserslist": {
"version": "2.11.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz",
@ -4660,6 +4675,16 @@
"prop-types": "15.6.0"
}
},
"react-render-in-browser": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/react-render-in-browser/-/react-render-in-browser-1.0.0.tgz",
"integrity": "sha512-DnOYcGVfjcu13Em8Z/sNbgYSrL26NjCQhZNzOEMV3BJiZ5WfvWFqvI9P/MW2K8guAkuf+hBouQyZysJdqrVhKA==",
"requires": {
"prop-types": "15.6.0",
"react": "16.0.0",
"react-dom": "16.0.0"
}
},
"react-router": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-3.0.5.tgz",

View File

@ -29,6 +29,7 @@
"need to investigate"
],
"babel-runtime": "~6.26.0",
"browser-detect": "^0.2.27",
"classnames": "~2.2.5",
"clipboard": "~1.7.1",
"core-js": "~2.5.3",
@ -52,6 +53,7 @@
"react-dropzone": "~4.2.1",
"react-intl": "~2.4.0",
"react-modal": "~3.0.4",
"react-render-in-browser": "^1.0.0",
"react-router": "~3.0.2",
"react-tabs": "~2.1.0",
"react-toastify": "~2.1.2",