diff --git a/css/_font.scss b/css/_font.scss index 99972b853..a6164657d 100644 --- a/css/_font.scss +++ b/css/_font.scss @@ -69,6 +69,9 @@ .icon-exit-full-screen:before { content: "\e90c"; } +.icon-star:before { + content: "\e916"; +} .icon-star-full:before { content: "\e90a"; } @@ -105,9 +108,6 @@ .icon-settings:before { content: "\e915"; } -.icon-star:before { - content: "\e916"; -} .icon-share-desktop:before { content: "\e917"; } diff --git a/css/_variables.scss b/css/_variables.scss index 74895928b..1f1302d43 100644 --- a/css/_variables.scss +++ b/css/_variables.scss @@ -48,7 +48,7 @@ $dominantSpeakerBg: #165ecc; $raiseHandBg: #D6D61E; $rateStarDefault: #ccc; -$rateStarActivity: #165ecc; +$rateStarActivity: #f6c342; $rateStarLabelColor: #333; /** @@ -61,4 +61,16 @@ $defaultWatermarkLink: '../images/watermark.png'; * Z-indexes. TODO: Replace this by a function. */ $toolbarZ: 900; -$overlayZ: 800; \ No newline at end of file +$overlayZ: 800; + +/** + * Font Colors TODO: change according the design + */ +$defaultFontColor: #777; +$defaultLightFontColor: #F1F1F1; +$defaultDarkFontColor: #000; +$buttonFontColor: #777; +$buttonHoverFontColor: #287ade; +$linkFontColor: #489afe; +$linkHoverFontColor: #287ade; + diff --git a/css/main.scss b/css/main.scss index 76b68d929..214cd599f 100644 --- a/css/main.scss +++ b/css/main.scss @@ -22,6 +22,8 @@ @import 'toastr'; @import 'base'; @import 'overlay/overlay'; +@import 'modals/dialog'; +@import 'modals/feedback/feedback'; @import 'videolayout_default'; @import 'jquery-impromptu'; @import 'modaldialog'; @@ -38,7 +40,6 @@ @import 'toolbars'; @import 'side_toolbar_container'; @import 'device_settings_dialog'; -@import 'feedback'; @import 'jquery.contextMenu'; @import 'keyboard-shortcuts'; diff --git a/css/modals/_dialog.scss b/css/modals/_dialog.scss new file mode 100644 index 000000000..c3cea92ca --- /dev/null +++ b/css/modals/_dialog.scss @@ -0,0 +1,37 @@ +.dialog{ + visibility: visible; + height: auto; + + p { + color: $defaultDarkFontColor; + } + .aui-dialog2-content:last-child { + border-bottom-right-radius: 5px; + border-bottom-left-radius: 5px; + } + .aui-dialog2-content:first-child { + border-top-right-radius: 5px; + border-top-left-radius: 5px; + } + .aui-dialog2-footer{ + padding-top: 0; + } + .aui-button { + height: 36px; + padding-top: 12px; + border: none; + background-color: transparent!important; + border-left: solid 1px #e4e4e4; + font-weight: 700; + + &_close { + color: $defaultFontColor; + } + &_submit { + color: $linkFontColor; + &:hover { + color: $linkHoverFontColor; + } + } + } +} \ No newline at end of file diff --git a/css/_feedback.scss b/css/modals/feedback/_feedback.scss similarity index 55% rename from css/_feedback.scss rename to css/modals/feedback/_feedback.scss index e065e5b18..da4400d09 100644 --- a/css/_feedback.scss +++ b/css/modals/feedback/_feedback.scss @@ -33,6 +33,8 @@ } .shake-rotate { + display: inline-block; + -webkit-animation-duration: .4s; animation-duration: .4s; -webkit-animation-iteration-count: infinite; @@ -43,65 +45,65 @@ animation-timing-function: ease-in-out } -.text-center { - text-align: center; -} - -.feedbackDetails textarea { - resize: vertical; - min-height: 100px; -} - -.feedback-rating { - line-height: 1.2; - padding: 20px 0; - +.feedback { h2 { font-weight: 400; font-size: 24px; line-height: 1.2; - padding: auto; - margin: auto; - border: none; } - p { - margin-top: 10px; - margin-left: 0px; - margin-bottom: 0px; - margin-right: 0px; + font-weight: 400; + font-size: 14px; } - .star-label { - font-size: 16px; - color: $rateStarLabelColor; + &__content { + text-align: center; } + &__footer { - .star-btn { - color: $rateStarDefault; - font-size: 36px; - position: relative; - cursor: pointer; - outline: none; - text-decoration: none; - @include transition(all .2s ease); - - &.starHover, - &.active, &:hover { - color: $rateStarActivity; + color: #287ade; + outline: 0; + } + } + &__rating { + line-height: 1.2; + padding: 20px 0; - .fa { - top: -6px; - } - }; - - &.rated:hover .fa { - top: 0; + p { + margin: 10px 0 0; } - .fa { + .star-label { + font-size: 16px; + color: $rateStarLabelColor; + } + + .star-btn { + color: $rateStarDefault; + font-size: 36px; position: relative; + cursor: pointer; + outline: none; + text-decoration: none; + @include transition(all .2s ease); + + &.starHover, + &.active, + &:hover { + color: $rateStarActivity; + > i:before { + content: "\e90a"; + } + }; + + } + } + &__details { + text-align: left; + textarea { + resize: vertical; + min-height: 100px; } } } diff --git a/index.html b/index.html index e2023c0af..f98cc785f 100644 --- a/index.html +++ b/index.html @@ -262,5 +262,6 @@ + diff --git a/modules/UI/Feedback.js b/modules/UI/Feedback.js deleted file mode 100644 index a7351593d..000000000 --- a/modules/UI/Feedback.js +++ /dev/null @@ -1,326 +0,0 @@ -/* global $, APP, config, interfaceConfig, JitsiMeetJS */ -import UIEvents from "../../service/UI/UIEvents"; -import UIUtil from "./util/UIUtil"; - -/** - * Constructs the html for the overall feedback window. - * - * @returns {string} the constructed html string - */ -var constructOverallFeedbackHtml = function() { - var feedbackQuestion = (Feedback.feedbackScore < 0) - ? '

' + APP.translation - .translateString("dialog.feedbackQuestion") - : ''; - - var message = '
' + - '
' + - APP.translation.translateString("dialog.thankYou", - {appName:interfaceConfig.APP_NAME}) + - '
' + - feedbackQuestion + - '


' + - '
' + - '' + - '' + - '' + - '' + - '' + - '
'; - - return message; -}; - -/** - * Constructs the html for the detailed feedback window. - * - * @returns {string} the contructed html string - */ -var constructDetailedFeedbackHtml = function() { - // Construct the html, which will be served as a dialog message. - var message = '
' + - '
' + - APP.translation.translateString("dialog.sorryFeedback") + - '


' + - '
' + - '' + - '
'; - - return message; -}; - -var createRateFeedbackHTML = function () { - var rate = APP.translation.translateString('dialog.rateExperience'), - help = APP.translation.translateString('dialog.feedbackHelp'); - - return ` -
-

${ rate }

-

 

-
- - - - - - - - - - - - - - - -
-

 

-

${ help }

-
- `; -}; - -/** - * The callback function corresponding to the openFeedbackWindow parameter. - * - * @type {function} - */ -var feedbackWindowCallback = null; - -/** - * Shows / hides the feedback button. - * @private - */ -function _toggleFeedbackIcon() { - $('#feedbackButtonDiv').toggleClass("hidden"); -} - -/** - * Shows / hides the feedback button. - * @param {show} set to {true} to show the feedback button or to {false} - * to hide it - * @private - */ -function _showFeedbackButton (show) { - var feedbackButton = $("#feedbackButtonDiv"); - - if (show) - feedbackButton.css("display", "block"); - else - feedbackButton.css("display", "none"); -} - -/** - * Defines all methods in connection to the Feedback window. - * - * @type {{feedbackScore: number, openFeedbackWindow: Function, - * toggleStars: Function, hoverStars: Function, unhoverStars: Function}} - */ -var Feedback = { - /** - * The feedback score. -1 indicates no score has been given for now. - */ - feedbackScore: -1, - - /** - * Initialise the Feedback functionality. - * @param emitter the EventEmitter to associate with the Feedback. - */ - init: function (emitter) { - // CallStats is the way we send feedback, so we don't have to initialise - // if callstats isn't enabled. - if (!APP.conference.isCallstatsEnabled()) - return; - - // If enabled property is still undefined, i.e. it hasn't been set from - // some other module already, we set it to true by default. - if (typeof this.enabled == "undefined") - this.enabled = true; - - _showFeedbackButton(this.enabled); - - let $feedbackButton = $("#feedbackButton"); - - $feedbackButton.click(function (event) { - Feedback.openFeedbackWindow(); - }); - - UIUtil.setTooltip($feedbackButton.get(0), 'feedback', 'right'); - - // Show / hide the feedback button whenever the film strip is - // shown / hidden. - emitter.addListener(UIEvents.TOGGLE_FILM_STRIP, function () { - _toggleFeedbackIcon(); - }); - }, - /** - * Enables/ disabled the feedback feature. - */ - enableFeedback: function (enable) { - if (this.enabled !== enable) - _showFeedbackButton(enable); - this.enabled = enable; - }, - - /** - * Indicates if the feedback functionality is enabled. - * - * @return true if the feedback functionality is enabled, false otherwise. - */ - isEnabled: function() { - return this.enabled && APP.conference.isCallstatsEnabled(); - }, - - /** - * Returns true if the feedback window is currently visible and false - * otherwise. - * @return {boolean} true if the feedback window is visible, false - * otherwise - */ - isVisible: function() { - return $(".feedback").is(":visible"); - }, - - /** - * Opens the feedback window. - */ - openFeedbackWindow: function (callback) { - feedbackWindowCallback = callback; - // Add all mouse and click listeners. - var onLoadFunction = function (event) { - $('#stars >a').each(function(index) { - // On star mouse over. - $(this).get(0).onmouseover = function(){ - Feedback.hoverStars(index); - }; - // On star mouse leave. - $(this).get(0).onmouseleave = function(){ - Feedback.unhoverStars(index); - }; - // On star click. - $(this).get(0).onclick = function(){ - Feedback.toggleStars(index); - Feedback.feedbackScore = index+1; - - // If the feedback is less than 3 stars we're going to - // ask the user for more information. - if (Feedback.feedbackScore > 3) { - APP.conference.sendFeedback(Feedback.feedbackScore, ""); - if (feedbackWindowCallback) - feedbackWindowCallback(); - else - APP.UI.messageHandler.closeDialog(); - } - else { - feedbackDialog.goToState('detailed_feedback'); - } - }; - // Init stars to correspond to previously entered feedback. - if (Feedback.feedbackScore > 0 - && index < Feedback.feedbackScore) { - Feedback.hoverStars(index); - Feedback.toggleStars(index); - } - }); - }; - - // Defines the different states of the feedback window. - var states = { - overall_feedback: { - html: createRateFeedbackHTML(), - persistent: false, - buttons: {}, - closeText: '', - focus: "div[id='stars']", - position: {width: 500} - }, - detailed_feedback: { - html: constructDetailedFeedbackHtml(), - buttons: {"Submit": true, "Cancel": false}, - closeText: '', - focus: "textarea[id='feedbackTextArea']", - position: {width: 500}, - submit: function(e,v,m,f) { - e.preventDefault(); - if (v) { - var feedbackDetails - = document.getElementById("feedbackTextArea").value; - - if (feedbackDetails && feedbackDetails.length > 0) { - APP.conference.sendFeedback( Feedback.feedbackScore, - feedbackDetails); - } - - if (feedbackWindowCallback) - feedbackWindowCallback(); - else - APP.UI.messageHandler.closeDialog(); - } else { - // User cancelled - if (feedbackWindowCallback) - feedbackWindowCallback(); - else - APP.UI.messageHandler.closeDialog(); - } - } - } - }; - - // Create the feedback dialog. - var feedbackDialog - = APP.UI.messageHandler.openDialogWithStates( - states, - { persistent: false, - buttons: {}, - closeText: '', - loaded: onLoadFunction, - position: {width: 500}}, null); - JitsiMeetJS.analytics.sendEvent('feedback.open'); - }, - /** - * Toggles the appropriate css class for the given number of stars, to - * indicate that those stars have been clicked/selected. - * - * @param starCount the number of stars, for which to toggle the css class - */ - toggleStars: function (starCount) - { - $('#stars >a >i').each(function(index) { - if (index <= starCount) { - $(this).removeClass("icon-star"); - } - else - $(this).addClass("icon-star"); - }); - }, - /** - * Toggles the appropriate css class for the given number of stars, to - * indicate that those stars have been hovered. - * - * @param starCount the number of stars, for which to toggle the css class - */ - hoverStars: function (starCount) - { - $('#stars >a >i').each(function(index) { - if (index <= starCount) - $(this).addClass("starHover"); - }); - }, - /** - * Toggles the appropriate css class for the given number of stars, to - * indicate that those stars have been un-hovered. - * - * @param starCount the number of stars, for which to toggle the css class - */ - unhoverStars: function (starCount) - { - $('#stars >a >i').each(function(index) { - if (index <= starCount && $(this).hasClass("icon-star")) - $(this).removeClass("starHover"); - }); - } -}; - -// Exports the Feedback class. -module.exports = Feedback; diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 79eb79df4..d09e98323 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -29,7 +29,7 @@ var EventEmitter = require("events"); UI.messageHandler = require("./util/MessageHandler"); var messageHandler = UI.messageHandler; var JitsiPopover = require("./util/JitsiPopover"); -var Feedback = require("./Feedback"); +var Feedback = require("./feedback/Feedback"); import FollowMe from "../FollowMe"; @@ -1126,7 +1126,7 @@ UI.requestFeedback = function () { if (Feedback.isEnabled()) { // If the user has already entered feedback, we'll show the // window and immidiately start the conference dispose timeout. - if (Feedback.feedbackScore > 0) { + if (Feedback.getFeedbackScore() > 0) { Feedback.openFeedbackWindow(); resolve(); diff --git a/modules/UI/feedback/Feedback.js b/modules/UI/feedback/Feedback.js new file mode 100644 index 000000000..eb51dd166 --- /dev/null +++ b/modules/UI/feedback/Feedback.js @@ -0,0 +1,105 @@ +/* global $, APP, config, interfaceConfig, JitsiMeetJS */ +import UIEvents from "../../../service/UI/UIEvents"; +import FeedabckWindow from "./FeedbackWindow"; + +/** + * Shows / hides the feedback button. + * @private + */ +function _toggleFeedbackIcon() { + $('#feedbackButtonDiv').toggleClass("hidden"); +} + +/** + * Shows / hides the feedback button. + * @param {show} set to {true} to show the feedback button or to {false} + * to hide it + * @private + */ +function _showFeedbackButton (show) { + var feedbackButton = $("#feedbackButtonDiv"); + + if (show) + feedbackButton.css("display", "block"); + else + feedbackButton.css("display", "none"); +} + +/** + * Defines all methods in connection to the Feedback window. + * + * @type {{openFeedbackWindow: Function}} + */ +var Feedback = { + + /** + * Initialise the Feedback functionality. + * @param emitter the EventEmitter to associate with the Feedback. + */ + init: function (emitter) { + // CallStats is the way we send feedback, so we don't have to initialise + // if callstats isn't enabled. + if (!APP.conference.isCallstatsEnabled()) + return; + + // If enabled property is still undefined, i.e. it hasn't been set from + // some other module already, we set it to true by default. + if (typeof this.enabled == "undefined") + this.enabled = true; + + _showFeedbackButton(this.enabled); + + this.window = new FeedabckWindow({}); + + $("#feedbackButton").click(Feedback.openFeedbackWindow); + + // Show / hide the feedback button whenever the film strip is + // shown / hidden. + emitter.addListener(UIEvents.TOGGLE_FILM_STRIP, function () { + _toggleFeedbackIcon(); + }); + }, + /** + * Enables/ disabled the feedback feature. + */ + enableFeedback: function (enable) { + if (this.enabled !== enable) + _showFeedbackButton(enable); + this.enabled = enable; + }, + + /** + * Indicates if the feedback functionality is enabled. + * + * @return true if the feedback functionality is enabled, false otherwise. + */ + isEnabled: function() { + return this.enabled && APP.conference.isCallstatsEnabled(); + }, + + /** + * Returns true if the feedback window is currently visible and false + * otherwise. + * @return {boolean} true if the feedback window is visible, false + * otherwise + */ + isVisible: function() { + return $(".feedback").is(":visible"); + }, + + /** + * Opens the feedback window. + */ + openFeedbackWindow: function (callback) { + Feedback.window.show(callback); + + JitsiMeetJS.analytics.sendEvent('feedback.open'); + }, + + getFeedbackScore: function() { + return Feedback.window.feedbackScore; + } + +}; + +module.exports = Feedback; \ No newline at end of file diff --git a/modules/UI/feedback/FeedbackWindow.js b/modules/UI/feedback/FeedbackWindow.js new file mode 100644 index 000000000..b7f64544e --- /dev/null +++ b/modules/UI/feedback/FeedbackWindow.js @@ -0,0 +1,216 @@ +/* global $, APP, interfaceConfig, AJS */ +/* jshint -W101 */ + +const selector = '#aui-feedback-dialog'; + +/** + * Toggles the appropriate css class for the given number of stars, to + * indicate that those stars have been clicked/selected. + * + * @param starCount the number of stars, for which to toggle the css class + */ +let toggleStars = function(starCount) { + $('#stars > a').each(function(index, el) { + if (index <= starCount) { + el.classList.add("starHover"); + } else + el.classList.remove("starHover"); + }); +}; + +/** + * Constructs the html for the detailed feedback window. + * + * @returns {string} the contructed html string + */ +let constructDetailedFeedbackHtml = function() { + + return ` +
+
+

${APP.translation.translateString("dialog.sorryFeedback")}

+

+ +
+
+ + `; +}; + +/** + * Constructs the html for the rated feedback window. + * + * @returns {string} the contructed html string + */ +let createRateFeedbackHTML = function (Feedback) { + var rateExperience = APP.translation.translateString('dialog.rateExperience'), + feedbackHelp = APP.translation.translateString('dialog.feedbackHelp'), + feedbackQuestion = (Feedback.feedbackScore < 0) + ? `


${APP.translation.translateString('dialog.feedbackQuestion')}

` + : ''; + + return ` +
+ ${feedbackQuestion} +
+ +
+
+`; +}; + +/** + * Callback for Rate Feedback + * + * @param Feedback + */ +let onLoadRateFunction = function (Feedback) { + $('#stars > a').each((index, el) => { + el.onmouseover = function(){ + toggleStars(index); + }; + el.onmouseleave = function(){ + toggleStars(Feedback.feedbackScore - 1); + }; + el.onclick = function(){ + Feedback.feedbackScore = index + 1; + + // If the feedback is less than 3 stars we're going to + // ask the user for more information. + if (Feedback.feedbackScore > 3) { + APP.conference.sendFeedback(Feedback.feedbackScore, ""); + Feedback.hide(); + } else { + Feedback.setState('detailed_feedback'); + } + }; + }); + + // Init stars to correspond to previously entered feedback. + if (Feedback.feedbackScore > 0) { + toggleStars(Feedback.feedbackScore - 1); + } +}; + +/** + * Callback for Detailed Feedback + * + * @param Feedback + */ +let onLoadDetailedFunction = function(Feedback) { + let submitBtn = Feedback.$el.find('#dialog-submit-button'); + let closeBtn = Feedback.$el.find('#dialog-close-button'); + + if (submitBtn && submitBtn.length) { + submitBtn.on('click', (e) => { + e.preventDefault(); + Feedback.onFeedbackSubmitted(); + }); + } + if (closeBtn && closeBtn.length) { + closeBtn.on('click', (e) => { + e.preventDefault(); + Feedback.hide(); + }); + } +}; + +/** + * @class Dialog + * + */ +export default class Dialog { + + constructor(options) { + this.feedbackScore = -1; + this.onCloseCallback = null; + + this.states = { + rate_feedback: { + getHtml: createRateFeedbackHTML, + onLoad: onLoadRateFunction + }, + detailed_feedback: { + getHtml: constructDetailedFeedbackHtml, + onLoad: onLoadDetailedFunction + } + }; + this.state = options.state || 'rate_feedback'; + + this.window = AJS.dialog2(selector, { + closeOnOutsideClick: true + }); + this.$el = this.window.$el; + + this.setState(); + } + + setState(state) { + let newState = state || this.state; + + let htmlStr = this.states[newState].getHtml(this); + + this.$el.html(htmlStr); + + this.states[newState].onLoad(this); + } + + show(cb) { + this.setState('rate_feedback'); + if (typeof cb == 'function') { + this.onCloseCallback = cb; + } + + this.window.show(); + + } + + hide() { + this.window.hide(); + + if (this.onCloseCallback) { + this.onCloseCallback(); + this.onCloseCallback = null; + } + } + + onFeedbackSubmitted() { + let message = this.$el.find('textarea').val(); + let self = this; + + if (message && message.length > 0) { + APP.conference.sendFeedback( + self.feedbackScore, + message); + } + this.hide(); + } + +} diff --git a/modules/UI/recording/Recording.js b/modules/UI/recording/Recording.js index cf3041adb..c37a39eb0 100644 --- a/modules/UI/recording/Recording.js +++ b/modules/UI/recording/Recording.js @@ -17,7 +17,7 @@ import UIEvents from "../../../service/UI/UIEvents"; import UIUtil from '../util/UIUtil'; import VideoLayout from '../videolayout/VideoLayout'; -import Feedback from '../Feedback.js'; +import Feedback from '../feedback/Feedback.js'; import Toolbar from '../toolbars/Toolbar'; /** diff --git a/modules/UI/videolayout/FilmStrip.js b/modules/UI/videolayout/FilmStrip.js index 125d7c7fa..4dc12dcc6 100644 --- a/modules/UI/videolayout/FilmStrip.js +++ b/modules/UI/videolayout/FilmStrip.js @@ -24,7 +24,7 @@ const FilmStrip = { */ toggleFilmStrip (visible) { if (typeof visible === 'boolean' - && this.isFilmStripVisible() == visible) { + && this.isFilmStripVisible() == visible) { return; } @@ -34,8 +34,8 @@ const FilmStrip = { var eventEmitter = this.eventEmitter; if (eventEmitter) { eventEmitter.emit( - UIEvents.TOGGLED_FILM_STRIP, - this.isFilmStripVisible()); + UIEvents.TOGGLED_FILM_STRIP, + this.isFilmStripVisible()); } }, @@ -75,7 +75,7 @@ const FilmStrip = { /** * Normalizes local and remote thumbnail ratios */ - normalizeThumbnailRatio () { + normalizeThumbnailRatio () { let remoteHeightRatio = interfaceConfig.REMOTE_THUMBNAIL_RATIO_HEIGHT; let remoteWidthRatio = interfaceConfig.REMOTE_THUMBNAIL_RATIO_WIDTH; @@ -120,11 +120,11 @@ const FilmStrip = { */ let videoAreaAvailableWidth = UIUtil.getAvailableVideoWidth() - - UIUtil.parseCssInt(this.filmStrip.css('right'), 10) - - UIUtil.parseCssInt(this.filmStrip.css('paddingLeft'), 10) - - UIUtil.parseCssInt(this.filmStrip.css('paddingRight'), 10) - - UIUtil.parseCssInt(this.filmStrip.css('borderLeftWidth'), 10) - - UIUtil.parseCssInt(this.filmStrip.css('borderRightWidth'), 10) + - UIUtil.parseCssInt(this.filmStrip.css('right'), 10) + - UIUtil.parseCssInt(this.filmStrip.css('paddingLeft'), 10) + - UIUtil.parseCssInt(this.filmStrip.css('paddingRight'), 10) + - UIUtil.parseCssInt(this.filmStrip.css('borderLeftWidth'), 10) + - UIUtil.parseCssInt(this.filmStrip.css('borderRightWidth'), 10) - 5; let availableWidth = videoAreaAvailableWidth; @@ -270,4 +270,4 @@ const FilmStrip = { }; -export default FilmStrip; +export default FilmStrip; \ No newline at end of file