Closes #236: onboarding wizard

This commit is contained in:
Benjamin Gamard 2019-02-05 17:32:47 +01:00
parent d4d1c35264
commit 940b365447
8 changed files with 462 additions and 6 deletions

View File

@ -7,7 +7,7 @@ angular.module('docs',
// Dependencies
['ui.router', 'ui.bootstrap', 'dialog', 'ngProgress', 'monospaced.qrcode', 'yaru22.angular-timeago', 'ui.validate',
'ui.sortable', 'restangular', 'ngSanitize', 'ngTouch', 'colorpicker.module', 'ngFileUpload', 'pascalprecht.translate',
'tmh.dynamicLocale']
'tmh.dynamicLocale', 'ngOnboarding']
)
/**
@ -514,7 +514,7 @@ angular.module('docs',
/**
* Initialize ngProgress.
*/
.run(function($rootScope, ngProgressFactory, $http) {
.run (function ($rootScope, ngProgressFactory, $http) {
$rootScope.ngProgress = ngProgressFactory.createInstance();
// Watch for the number of XHR running
@ -527,6 +527,12 @@ angular.module('docs',
$rootScope.ngProgress.start();
}
});
})
/**
* Initialize ngOnboarding.
*/
.run (function ($rootScope) {
$rootScope.onboardingEnabled = false;
});
if (location.search.indexOf("protractor") > -1) {

View File

@ -142,4 +142,47 @@ angular.module('docs').controller('DocumentDefault', function ($scope, $rootScop
}).then(function (data) {
$scope.documentsWorkflow = data.documents;
});
// Onboarding
$translate('onboarding.step1.title').then(function () {
if (localStorage.onboardingDisplayed || $(window).width() < 1000) {
return;
}
localStorage.onboardingDisplayed = true;
$rootScope.onboardingEnabled = true;
$rootScope.onboardingSteps = [
{
title: $translate.instant('onboarding.step1.title'),
description: $translate.instant('onboarding.step1.description'),
position: "centered",
width: 300
},
{
title: $translate.instant('onboarding.step2.title'),
description: $translate.instant('onboarding.step2.description'),
attachTo: "#document-add-btn",
position: "right"
},
{
title: $translate.instant('onboarding.step3.title'),
description: $translate.instant('onboarding.step3.description'),
attachTo: "#quick-upload-zone",
position: "left"
},
{
title: $translate.instant('onboarding.step4.title'),
description: $translate.instant('onboarding.step4.description'),
attachTo: "#search-box",
position: "right"
},
{
title: $translate.instant('onboarding.step5.title'),
description: $translate.instant('onboarding.step5.description'),
attachTo: "#navigation-tag",
position: "right"
}
];
});
});

View File

@ -13,6 +13,7 @@
<link rel="stylesheet" href="style/fontawesome.css" type="text/css" />
<link rel="stylesheet" href="style/colorpicker.css" type="text/css" />
<link rel="stylesheet" href="style/pell.css" type="text/css" />
<link rel="stylesheet" href="style/ng-onboarding.css" type="text/css" />
<link rel="stylesheet/less" href="style/main.less" type="text/css" />
<!-- endref -->
<link rel="stylesheet" href="../api/theme/stylesheet" type="text/css" id="theme-stylesheet" />
@ -49,6 +50,7 @@
<script src="lib/angular.ngprogress.js" type="text/javascript"></script>
<script src="lib/angular.qrcode.js" type="text/javascript"></script>
<script src="lib/angular.timeago.js" type="text/javascript"></script>
<script src="lib/angular.ng-onboarding.js" type="text/javascript"></script>
<script src="app/docs/app.js" type="text/javascript"></script>
<script src="app/docs/controller/Login.js" type="text/javascript"></script>
<script src="app/docs/controller/Main.js" type="text/javascript"></script>
@ -106,6 +108,8 @@
<!-- endref -->
</head>
<body translate-cloak ng-cloak>
<onboarding-popover ng-if="onboardingEnabled" enabled="onboardingEnabled" steps="onboardingSteps"></onboarding-popover>
<nav class="navbar navbar-default" role="navigation" ng-controller="Navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle"
@ -134,7 +138,7 @@
<li ui-sref-active="{ active: 'document.**' }">
<a href="#/document"><span class="fas fa-book"></span> {{ 'index.nav_documents' | translate }}</a>
</li>
<li ui-sref-active="{ active: 'tag.**' }">
<li ui-sref-active="{ active: 'tag.**' }" id="navigation-tag">
<a href="#/tag"><span class="fas fa-tags"></span> {{ 'index.nav_tags' | translate }}</a>
</li>
<li ui-sref-active="{ active: 'user.**', active2: 'group.**' }">

View File

@ -0,0 +1,185 @@
(function() {
var app;
app = angular.module("ngOnboarding", []);
app.provider("ngOnboardingDefaults", function() {
return {
options: {
overlay: true,
overlayOpacity: 0.4,
overlayClass: 'onboarding-overlay',
popoverClass: 'onboarding-popover',
titleClass: 'onboarding-popover-title',
contentClass: 'onboarding-popover-content',
arrowClass: 'onboarding-arrow',
buttonContainerClass: 'onboarding-button-container',
buttonClass: "btn",
showButtons: true,
nextButtonText: 'Next &rarr;',
previousButtonText: '&larr; Previous',
showDoneButton: true,
doneButtonText: 'Done',
closeButtonClass: 'onboarding-close-button',
closeButtonText: 'X',
stepClass: 'onboarding-step-info',
showStepInfo: true
},
$get: function() {
return this.options;
},
set: function(keyOrHash, value) {
var k, v, _results;
if (typeof keyOrHash === 'object') {
_results = [];
for (k in keyOrHash) {
v = keyOrHash[k];
_results.push(this.options[k] = v);
}
return _results;
} else {
return this.options[keyOrHash] = value;
}
}
};
});
app.directive('onboardingPopover', [
'ngOnboardingDefaults', '$sce', '$timeout', function(ngOnboardingDefaults, $sce, $timeout) {
return {
restrict: 'E',
scope: {
enabled: '=',
steps: '=',
onFinishCallback: '&onFinishCallback',
index: '=?stepIndex'
},
replace: true,
link: function(scope, element, attrs) {
var attributesToClear, curStep, setupOverlay, setupPositioning;
curStep = null;
attributesToClear = ['title', 'top', 'right', 'bottom', 'left', 'width', 'height', 'position'];
scope.stepCount = scope.steps.length;
scope.next = function() {
return scope.index = scope.index + 1;
};
scope.previous = function() {
return scope.index = scope.index - 1;
};
scope.close = function() {
scope.enabled = false;
setupOverlay(false);
if (scope.onFinishCallback) {
return scope.onFinishCallback();
}
};
scope.$watch('index', function(newVal, oldVal) {
var attr, k, v, _i, _len;
if (newVal === null) {
scope.enabled = false;
setupOverlay(false);
return;
}
curStep = scope.steps[scope.index];
scope.lastStep = scope.index + 1 === scope.steps.length;
scope.showNextButton = scope.index + 1 < scope.steps.length;
scope.showPreviousButton = scope.index > 0;
for (_i = 0, _len = attributesToClear.length; _i < _len; _i++) {
attr = attributesToClear[_i];
scope[attr] = null;
}
for (k in ngOnboardingDefaults) {
v = ngOnboardingDefaults[k];
if (curStep[k] === void 0) {
scope[k] = v;
}
}
for (k in curStep) {
v = curStep[k];
scope[k] = v;
}
scope.description = $sce.trustAsHtml(scope.description);
scope.nextButtonText = $sce.trustAsHtml(scope.nextButtonText);
scope.previousButtonText = $sce.trustAsHtml(scope.previousButtonText);
scope.doneButtonText = $sce.trustAsHtml(scope.doneButtonText);
scope.closeButtonText = $sce.trustAsHtml(scope.closeButtonText);
setupOverlay();
return setupPositioning();
});
setupOverlay = function(showOverlay) {
if (showOverlay == null) {
showOverlay = true;
}
$('.onboarding-focus').removeClass('onboarding-focus');
if (showOverlay) {
if (curStep['attachTo'] && scope.overlay) {
return $(curStep['attachTo']).addClass('onboarding-focus');
}
}
};
setupPositioning = function() {
var attachTo, bottom, left, right, top, xMargin, yMargin;
attachTo = curStep['attachTo'];
scope.position = curStep['position'];
xMargin = 15;
yMargin = 15;
if (attachTo) {
if (!(scope.left || scope.right)) {
left = null;
right = null;
if (scope.position === 'right') {
left = $(attachTo).offset().left + $(attachTo).outerWidth() + xMargin;
} else if (scope.position === 'left') {
right = $(window).width() - $(attachTo).offset().left + xMargin;
} else if (scope.position === 'top' || scope.position === 'bottom') {
left = $(attachTo).offset().left;
}
if (curStep['xOffset']) {
if (left !== null) {
left = left + curStep['xOffset'];
}
if (right !== null) {
right = right - curStep['xOffset'];
}
}
scope.left = left;
scope.right = right;
}
if (!(scope.top || scope.bottom)) {
top = null;
bottom = null;
if (scope.position === 'left' || scope.position === 'right') {
top = $(attachTo).offset().top + $(attachTo).outerHeight() / 2 - 14;
} else if (scope.position === 'bottom') {
top = $(attachTo).offset().top + $(attachTo).outerHeight() + yMargin;
} else if (scope.position === 'top') {
bottom = $(window).height() - $(attachTo).offset().top + yMargin;
}
if (curStep['yOffset']) {
if (top !== null) {
top = top + curStep['yOffset'];
}
if (bottom !== null) {
bottom = bottom - curStep['yOffset'];
}
}
scope.top = top;
scope.bottom = bottom;
}
}
if (scope.position && scope.position.length) {
return scope.positionClass = "onboarding-" + scope.position;
} else {
return scope.positionClass = null;
}
};
if (scope.steps.length && !scope.index) {
return scope.index = 0;
}
},
template: "<div class='onboarding-container' ng-show='enabled'>\n <div class='{{overlayClass}}' ng-style='{opacity: overlayOpacity}', ng-show='overlay'></div>\n <div class='{{popoverClass}} {{positionClass}}' ng-style=\"{width: width, height: height, left: left, top: top, right: right, bottom: bottom}\">\n <div class='{{arrowClass}}'></div>\n <h3 class='{{titleClass}}' ng-show='title' ng-bind='title'></h3>\n <a href='' ng-click='close()' class='{{closeButtonClass}}' ng-bind-html='closeButtonText'></a>\n <div class='{{contentClass}}'>\n <p ng-bind-html='description'></p>\n </div>\n <div class='{{buttonContainerClass}}' ng-show='showButtons'>\n <span ng-show='showStepInfo' class='{{stepClass}}'>{{index + 1}}/{{stepCount}}</span>\n <a href='' ng-click='previous()' ng-show='showPreviousButton' class='{{buttonClass}}' ng-bind-html='previousButtonText'></a>\n <a href='' ng-click='next()' ng-show='showNextButton' class='{{buttonClass}}' ng-bind-html='nextButtonText'></a>\n <a href='' ng-click='close()' ng-show='showDoneButton && lastStep' class='{{buttonClass}}' ng-bind-html='doneButtonText'></a>\n </div>\n </div>\n</div>"
};
}
]);
}).call(this);

View File

@ -564,6 +564,28 @@
"first": "First",
"last": "Last"
},
"onboarding": {
"step1": {
"title": "First time?",
"description": "If it's your first time on Sismics Docs, click the Next button, otherwise feel free to close me."
},
"step2": {
"title": "Documents",
"description": "Sismics Docs is organized in documents and each document contains multiple files."
},
"step3": {
"title": "Files",
"description": "You can add files after creating a document or before using this Quick upload area."
},
"step4": {
"title": "Search",
"description": "This is the main way to find your documents back. There is also an advanced search with the magnifier button."
},
"step5": {
"title": "Tags",
"description": "Documents can be organized in tags (which are like super-folders). Create them here."
}
},
"ok": "OK",
"cancel": "Cancel",
"share": "Share",

View File

@ -1,4 +1,4 @@
<div class="well well-3d" style="background-color: #f6f9fc">
<div class="well well-3d" style="background-color: #f6f9fc" id="quick-upload-zone">
<div class="pull-right" ng-show="files.length > 0">
<a href class="btn btn-default" ng-init="selectAll = true" ng-click="changeChecked(selectAll); selectAll = !selectAll">
<span class="far" ng-class="{ 'fa-check-square': selectAll, 'fa-square': !selectAll }"></span>

View File

@ -3,7 +3,7 @@
<div class="well well-3d">
<!-- Main new document button -->
<div class="text-center mb-19">
<div class="btn-group" uib-dropdown>
<div class="btn-group" uib-dropdown id="document-add-btn">
<a href="#/document/add" class="btn btn-primary">
<span class="fas fa-plus"></span> {{ 'document.add_document' | translate }}
</a>
@ -18,7 +18,7 @@
<!-- Search (simple and advanced) -->
<div class="row search-dropdown-anchor">
<div class="col-xs-12 input-group">
<div class="col-xs-12 input-group" id="search-box">
<input type="search" class="form-control"
uib-typeahead="suggestion for suggestion in suggestions"
typeahead-focus-first="false"

View File

@ -0,0 +1,196 @@
.onboarding-popover {
position: absolute;
z-index: 100001;
padding: 1px;
text-align: left;
white-space: normal;
background-color: #ffffff;
border: 1px solid #cccccc;
border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
background-clip: padding-box;
max-width: 800px;
max-height: 800px;
min-width: 100px;
min-height: 50px;
}
.onboarding-popover.onboarding-top {
margin-top: -10px;
}
.onboarding-popover.onboarding-right {
margin-left: 10px;
}
.onboarding-popover.onboarding-bottom {
margin-top: 10px;
}
.onboarding-popover.onboarding-left {
margin-left: -10px;
}
.onboarding-popover.onboarding-centered {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
-webkit-transform: translate(-50%, -50%);
-moz-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
-o-transform: translate(-50%, -50%);
}
.onboarding-arrow,
.onboarding-arrow:after {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
.onboarding-arrow {
border-width: 11px;
}
.onboarding-arrow:after {
border-width: 10px;
content: "";
}
.onboarding-popover.onboarding-bottom .onboarding-arrow {
top: -11px;
left: 50%;
margin-left: -11px;
border-bottom-color: #cccccc;
border-top-width: 0;
}
.onboarding-popover.onboarding-bottom .onboarding-arrow:after {
content: " ";
top: 1px;
margin-left: -10px;
border-top-width: 0;
border-bottom-color: #ffffff;
}
.onboarding-popover.onboarding-top .onboarding-arrow {
bottom: -11px;
left: 50%;
margin-left: -11px;
border-top-color: #cccccc;
border-bottom-width: 0;
}
.onboarding-popover.onboarding-top .onboarding-arrow:after {
content: " ";
bottom: 1px;
margin-left: -10px;
border-bottom-width: 0;
border-top-color: #ffffff;
}
.onboarding-popover.onboarding-left .onboarding-arrow {
top: 14px;
right: -11px;
margin-top: -11px;
border-left-color: #cccccc;
border-right-width: 0;
}
.onboarding-popover.onboarding-left .onboarding-arrow:after {
content: " ";
right: 1px;
border-right-width: 0;
border-left-color: #ffffff;
bottom: -10px;
}
.onboarding-popover.onboarding-right .onboarding-arrow {
top: 14px;
left: -11px;
margin-top: -11px;
border-right-color: #cccccc;
border-left-width: 0;
}
.onboarding-popover.onboarding-right .onboarding-arrow:after {
content: " ";
left: 1px;
bottom: -10px;
border-left-width: 0;
border-right-color: #ffffff;
}
.onboarding-popover-title {
position: relative;
padding: 8px 14px;
margin: 0;
font-size: 14px;
font-weight: bold;
line-height: 18px;
background-color: #ffffff;
border-bottom: 1px solid #ebebeb;
border-radius: 5px 5px 0 0;
}
.onboarding-close-button {
position: absolute;
top: 2px;
right: 5px;
display: inline-block;
padding: 5px;
font-size: 110%;
font-family: Arial, sans-serif;
}
.onboarding-close-button,
.onboarding-close-button:hover,
.onboarding-close-button:visited {
color: #333;
text-decoration: none;
}
.onboarding-popover-content {
padding: 9px 14px;
}
.onboarding-button-container {
text-align: right;
padding: 7px;
display: block;
background-color: #f4f4f4;
}
.onboarding-button {
padding: 5px 10px;
margin: 0;
font-size: 12px;
font-weight: bold;
line-height: 1.5;
text-align: center;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
background-image: none;
border: 1px solid #ccc;
border-radius: 3px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
color: #333;
background-color: #fff;
display: inline-block;
text-decoration: none;
}
.onboarding-button:hover,
.onboarding-button:active {
color: #333;
background-color: #ebebeb;
border-color: #adadad;
outline: 0;
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
.onboarding-overlay {
position: absolute;
z-index: 99999;
top: 0;
left: 0;
width: 100%;
height: 1000%;
background-color: #000000;
}
.onboarding-focus {
/*z-index: 100000 !important;*/
}
.onboarding-step-info {
float: left;
text-align: left;
display: inline-block;
margin-top: 7px;
font-size: 80%;
font-style: italic;
}