feedback dialog changes

This commit is contained in:
Kostiantyn Pashura 2016-09-14 18:11:53 +03:00 committed by yanas
parent f8b200f32c
commit 4572e1d344
12 changed files with 437 additions and 389 deletions

View File

@ -69,6 +69,9 @@
.icon-exit-full-screen:before { .icon-exit-full-screen:before {
content: "\e90c"; content: "\e90c";
} }
.icon-star:before {
content: "\e916";
}
.icon-star-full:before { .icon-star-full:before {
content: "\e90a"; content: "\e90a";
} }
@ -105,9 +108,6 @@
.icon-settings:before { .icon-settings:before {
content: "\e915"; content: "\e915";
} }
.icon-star:before {
content: "\e916";
}
.icon-share-desktop:before { .icon-share-desktop:before {
content: "\e917"; content: "\e917";
} }

View File

@ -48,7 +48,7 @@ $dominantSpeakerBg: #165ecc;
$raiseHandBg: #D6D61E; $raiseHandBg: #D6D61E;
$rateStarDefault: #ccc; $rateStarDefault: #ccc;
$rateStarActivity: #165ecc; $rateStarActivity: #f6c342;
$rateStarLabelColor: #333; $rateStarLabelColor: #333;
/** /**
@ -62,3 +62,15 @@ $defaultWatermarkLink: '../images/watermark.png';
*/ */
$toolbarZ: 900; $toolbarZ: 900;
$overlayZ: 800; $overlayZ: 800;
/**
* Font Colors TODO: change according the design
*/
$defaultFontColor: #777;
$defaultLightFontColor: #F1F1F1;
$defaultDarkFontColor: #000;
$buttonFontColor: #777;
$buttonHoverFontColor: #287ade;
$linkFontColor: #489afe;
$linkHoverFontColor: #287ade;

View File

@ -22,6 +22,8 @@
@import 'toastr'; @import 'toastr';
@import 'base'; @import 'base';
@import 'overlay/overlay'; @import 'overlay/overlay';
@import 'modals/dialog';
@import 'modals/feedback/feedback';
@import 'videolayout_default'; @import 'videolayout_default';
@import 'jquery-impromptu'; @import 'jquery-impromptu';
@import 'modaldialog'; @import 'modaldialog';
@ -38,7 +40,6 @@
@import 'toolbars'; @import 'toolbars';
@import 'side_toolbar_container'; @import 'side_toolbar_container';
@import 'device_settings_dialog'; @import 'device_settings_dialog';
@import 'feedback';
@import 'jquery.contextMenu'; @import 'jquery.contextMenu';
@import 'keyboard-shortcuts'; @import 'keyboard-shortcuts';

37
css/modals/_dialog.scss Normal file
View File

@ -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;
}
}
}
}

View File

@ -33,6 +33,8 @@
} }
.shake-rotate { .shake-rotate {
display: inline-block;
-webkit-animation-duration: .4s; -webkit-animation-duration: .4s;
animation-duration: .4s; animation-duration: .4s;
-webkit-animation-iteration-count: infinite; -webkit-animation-iteration-count: infinite;
@ -43,33 +45,33 @@
animation-timing-function: ease-in-out animation-timing-function: ease-in-out
} }
.text-center { .feedback {
text-align: center;
}
.feedbackDetails textarea {
resize: vertical;
min-height: 100px;
}
.feedback-rating {
line-height: 1.2;
padding: 20px 0;
h2 { h2 {
font-weight: 400; font-weight: 400;
font-size: 24px; font-size: 24px;
line-height: 1.2; line-height: 1.2;
padding: auto; }
margin: auto; p {
border: none; font-weight: 400;
font-size: 14px;
} }
&__content {
text-align: center;
}
&__footer {
&:hover {
color: #287ade;
outline: 0;
}
}
&__rating {
line-height: 1.2;
padding: 20px 0;
p { p {
margin-top: 10px; margin: 10px 0 0;
margin-left: 0px;
margin-bottom: 0px;
margin-right: 0px;
} }
.star-label { .star-label {
@ -90,18 +92,18 @@
&.active, &.active,
&:hover { &:hover {
color: $rateStarActivity; color: $rateStarActivity;
> i:before {
.fa { content: "\e90a";
top: -6px;
} }
}; };
&.rated:hover .fa {
top: 0;
} }
}
.fa { &__details {
position: relative; text-align: left;
textarea {
resize: vertical;
min-height: 100px;
} }
} }
} }

View File

@ -262,5 +262,6 @@
</ul> </ul>
</div> </div>
</div> </div>
<div id="aui-feedback-dialog" class="dialog feedback aui-layer aui-dialog2 aui-dialog2-medium" style="display: none;"></div>
</body> </body>
</html> </html>

View File

@ -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)
? '<br/><br/>' + APP.translation
.translateString("dialog.feedbackQuestion")
: '';
var message = '<div class="feedback"><div>' +
'<div class="feedbackTitle">' +
APP.translation.translateString("dialog.thankYou",
{appName:interfaceConfig.APP_NAME}) +
'</div>' +
feedbackQuestion +
'</div><br/><br/>' +
'<div id="stars">' +
'<a><i class="icon-star icon-star-full"></i></a>' +
'<a><i class="icon-star icon-star-full"></i></a>' +
'<a><i class="icon-star icon-star-full"></i></a>' +
'<a><i class="icon-star icon-star-full"></i></a>' +
'<a><i class="icon-star icon-star-full"></i></a>' +
'</div></div>';
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 = '<div class="feedback">' +
'<div class="feedbackTitle">' +
APP.translation.translateString("dialog.sorryFeedback") +
'</div><br/><br/>' +
'<div class="feedbackDetails">' +
'<textarea id="feedbackTextArea" rows="10" cols="50" autofocus>' +
'</textarea>' +
'</div></div>';
return message;
};
var createRateFeedbackHTML = function () {
var rate = APP.translation.translateString('dialog.rateExperience'),
help = APP.translation.translateString('dialog.feedbackHelp');
return `
<div class="feedback-rating text-center">
<h2>${ rate }</h2>
<p class="star-label">&nbsp;</p>
<div id="stars" class="feedback-stars">
<a class="star-btn">
<i class="fa fa-star shake-rotate"></i>
</a>
<a class="star-btn">
<i class="fa fa-star shake-rotate"></i>
</a>
<a class="star-btn">
<i class="fa fa-star shake-rotate"></i>
</a>
<a class="star-btn">
<i class="fa fa-star shake-rotate"></i>
</a>
<a class="star-btn">
<i class="fa fa-star shake-rotate"></i>
</a>
</div>
<p>&nbsp;</p>
<p>${ help }</p>
</div>
`;
};
/**
* 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;

View File

@ -29,7 +29,7 @@ var EventEmitter = require("events");
UI.messageHandler = require("./util/MessageHandler"); UI.messageHandler = require("./util/MessageHandler");
var messageHandler = UI.messageHandler; var messageHandler = UI.messageHandler;
var JitsiPopover = require("./util/JitsiPopover"); var JitsiPopover = require("./util/JitsiPopover");
var Feedback = require("./Feedback"); var Feedback = require("./feedback/Feedback");
import FollowMe from "../FollowMe"; import FollowMe from "../FollowMe";
@ -1126,7 +1126,7 @@ UI.requestFeedback = function () {
if (Feedback.isEnabled()) { if (Feedback.isEnabled()) {
// If the user has already entered feedback, we'll show the // If the user has already entered feedback, we'll show the
// window and immidiately start the conference dispose timeout. // window and immidiately start the conference dispose timeout.
if (Feedback.feedbackScore > 0) { if (Feedback.getFeedbackScore() > 0) {
Feedback.openFeedbackWindow(); Feedback.openFeedbackWindow();
resolve(); resolve();

View File

@ -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;

View File

@ -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 `
<div class="aui-dialog2-content feedback__content">
<div class="feedback__details">
<p>${APP.translation.translateString("dialog.sorryFeedback")}</p>
<br/><br/>
<textarea id="feedbackTextArea" rows="10" cols="50" autofocus></textarea>
</div>
</div>
<footer class="aui-dialog2-footer feedback__footer">
<div class="aui-dialog2-footer-actions">
<button id="dialog-close-button" class="aui-button aui-button_close">Close</button>
<button id="dialog-submit-button" class="aui-button aui-button_submit">Submit</button>
</div>
</footer>
`;
};
/**
* 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)
? `<p><br/>${APP.translation.translateString('dialog.feedbackQuestion')}</p>`
: '';
return `
<div class="aui-dialog2-content feedback__content">
${feedbackQuestion}
<form action="javascript:false;" onsubmit="return false;">
<div class="feedback__rating">
<h2>${ rateExperience }</h2>
<p class="star-label">&nbsp;</p>
<div id="stars" class="feedback-stars">
<a class="star-btn">
<i class="icon-star shake-rotate"></i>
</a>
<a class="star-btn">
<i class="icon-star shake-rotate"></i>
</a>
<a class="star-btn">
<i class="icon-star shake-rotate"></i>
</a>
<a class="star-btn">
<i class="icon-star shake-rotate"></i>
</a>
<a class="star-btn">
<i class="icon-star shake-rotate"></i>
</a>
</div>
<p>&nbsp;</p>
<p>${ feedbackHelp }</p>
</div>
</form>
</div>
`;
};
/**
* 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();
}
}

View File

@ -17,7 +17,7 @@
import UIEvents from "../../../service/UI/UIEvents"; import UIEvents from "../../../service/UI/UIEvents";
import UIUtil from '../util/UIUtil'; import UIUtil from '../util/UIUtil';
import VideoLayout from '../videolayout/VideoLayout'; import VideoLayout from '../videolayout/VideoLayout';
import Feedback from '../Feedback.js'; import Feedback from '../feedback/Feedback.js';
import Toolbar from '../toolbars/Toolbar'; import Toolbar from '../toolbars/Toolbar';
/** /**