Added guided tour on first start

This commit is contained in:
Joel Collins 2020-04-06 12:20:08 +01:00
parent 50b70a3ea3
commit 72c30f7186
8 changed files with 210 additions and 26 deletions

24
package-lock.json generated
View file

@ -10514,6 +10514,12 @@
"verror": "1.10.0" "verror": "1.10.0"
} }
}, },
"jump.js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/jump.js/-/jump.js-1.0.2.tgz",
"integrity": "sha1-4GQbR/QKOPITnCX9oFAL8o5DAVo=",
"dev": true
},
"kew": { "kew": {
"version": "0.7.0", "version": "0.7.0",
"resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz",
@ -12314,6 +12320,12 @@
"ts-pnp": "^1.1.6" "ts-pnp": "^1.1.6"
} }
}, },
"popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"dev": true
},
"portfinder": { "portfinder": {
"version": "1.0.25", "version": "1.0.25",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz",
@ -16064,6 +16076,18 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true "dev": true
}, },
"vue-tour": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/vue-tour/-/vue-tour-1.3.0.tgz",
"integrity": "sha512-hLXA8vCCWNNjvXwEbfOagcZfJMz1a9Xi7LTN5nb/Nqpuv8RKT25dS5/HJBvHEEh1EoYQNOwohx1YA6x+2KTQbQ==",
"dev": true,
"requires": {
"hash-sum": "^2.0.0",
"jump.js": "^1.0.2",
"popper.js": "^1.16.0",
"vue": "^2.6.10"
}
},
"vuex": { "vuex": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.2.tgz", "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.2.tgz",

View file

@ -55,6 +55,7 @@
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-plugin-load-script": "^1.2.0", "vue-plugin-load-script": "^1.2.0",
"vue-template-compiler": "^2.6.11", "vue-template-compiler": "^2.6.11",
"vue-tour": "^1.3.0",
"vuex": "^3.1.2" "vuex": "^3.1.2"
}, },
"optionalDependencies": { "optionalDependencies": {

View file

@ -4,7 +4,14 @@
class="uk-height-1-1 uk-margin-remove uk-padding-remove" class="uk-height-1-1 uk-margin-remove uk-padding-remove"
:class="handleTheme" :class="handleTheme"
> >
<div id="tour-header"></div>
<appContent /> <appContent />
<v-tour
name="guidedTour"
:steps="tourSteps"
:callbacks="tourCallbacks"
:options="{ highlight: true }"
></v-tour>
</div> </div>
</template> </template>
@ -37,7 +44,80 @@ export default {
return { return {
keysDown: {}, keysDown: {},
systemDark: undefined, systemDark: undefined,
themeObserver: undefined themeObserver: undefined,
tourSteps: [
{
target: "#tour-header", // We're using document.querySelector() under the hood
header: {
title: "Welcome to OpenFlexure eV"
},
content: `Click Next to learn how to use OpenFlexure eV`,
params: {
placement: "bottom"
}
},
{
target: "#saved-connections-grid",
header: {
title: "Saved microscopes"
},
content: `Connect to your saved microscopes for faster access`
},
{
target: "#nearby-connections-grid",
header: {
title: "Nearby microscopes"
},
content: `Connect to microscopes found on your network`
},
{
target: "#new-connection-card",
header: {
title: "New connection"
},
content: `Connect locally if you're running on a microscope, \nor open a new remote connection to a microscope`
},
{
target: "#gallery-tab-icon",
header: {
title: "Capture gallery"
},
content: `View and download your microscope images from the gallery tab`
},
{
target: "#navigate-tab-icon",
header: {
title: "Navigate around your sample"
},
content: `Move your microscope stage and perform autofocus from the navigate tab`
},
{
target: "#capture-tab-icon",
header: {
title: "Capture microscope images"
},
content: `Take images and simple tile scans from the capture tab`
},
{
target: "#settings-tab-icon",
header: {
title: "Change settings"
},
content: `Change app and microscope settings, including microscope calibration, from the settings tab`
},
{
target: "#extension-tab-divider",
header: {
title: "Microscope extensions"
},
content: `Extensions installed on your microscope will appear below this line`
}
],
tourCallbacks: {
onStop: () => {
this.setLocalStorageObj("completedTour", true);
}
}
}; };
}, },
@ -86,6 +166,12 @@ export default {
this.systemDark = false; this.systemDark = false;
} }
}); });
// Handle guided tour
// If the user has already completed or skipped the guided tour
var completedTour = this.getLocalStorageObj("completedTour") || false;
if (!completedTour) {
this.$tours["guidedTour"].start();
}
}, },
created: function() { created: function() {
@ -252,4 +338,56 @@ html {
height: 100%; height: 100%;
padding: 0; padding: 0;
} }
// Style tour
.v-tour__target--highlighted {
box-shadow: 0px 0px 180px 40px rgba(0, 0, 0, 0.4) !important;
border-radius: 5px;
opacity: 100%;
}
.v-step {
background: @global-primary-background !important;
}
.v-step__header {
background-color: darken(@global-primary-background, 15%) !important;
}
.v-step__button {
font-size: 0.9rem !important;
}
// Change step arrow colour
// This is awful and hacky and makes me sad, but needs must
.v-step .v-step__arrow {
border-color: darken(@global-primary-background, 15%) !important;
&--dark {
border-color: darken(@global-primary-background, 15%) !important;
}
}
.v-step[x-placement^="top"] .v-step__arrow {
border-left-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
}
.v-step[x-placement^="bottom"] .v-step__arrow {
border-left-color: transparent !important;
border-right-color: transparent !important;
border-top-color: transparent !important;
}
.v-step[x-placement^="right"] .v-step__arrow {
border-left-color: transparent !important;
border-top-color: transparent !important;
border-bottom-color: transparent !important;
}
.v-step[x-placement^="left"] .v-step__arrow {
border-top-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
}
</style> </style>

View file

@ -1,6 +1,6 @@
<template> <template>
<div <div
id="panel-left" id="app-content"
class="uk-margin-remove uk-padding-remove uk-height-1-1" class="uk-margin-remove uk-padding-remove uk-height-1-1"
uk-grid uk-grid
> >
@ -11,7 +11,8 @@
> >
<tabIcon <tabIcon
v-show="!liteMode" v-show="!liteMode"
id="connect" id="connect-tab-icon"
tab-i-d="connect"
:require-connection="false" :require-connection="false"
:current-tab="currentTab" :current-tab="currentTab"
@set-tab="setTab" @set-tab="setTab"
@ -19,7 +20,8 @@
<i class="material-icons">bug_report</i> <i class="material-icons">bug_report</i>
</tabIcon> </tabIcon>
<tabIcon <tabIcon
id="gallery" id="gallery-tab-icon"
tab-i-d="gallery"
:require-connection="true" :require-connection="true"
:current-tab="currentTab" :current-tab="currentTab"
@set-tab="setTab" @set-tab="setTab"
@ -30,7 +32,8 @@
<hr /> <hr />
<tabIcon <tabIcon
id="navigate" id="navigate-tab-icon"
tab-i-d="navigate"
:require-connection="true" :require-connection="true"
:current-tab="currentTab" :current-tab="currentTab"
@set-tab="setTab" @set-tab="setTab"
@ -38,7 +41,8 @@
<i class="material-icons">gamepad</i> <i class="material-icons">gamepad</i>
</tabIcon> </tabIcon>
<tabIcon <tabIcon
id="capture" id="capture-tab-icon"
tab-i-d="capture"
:require-connection="true" :require-connection="true"
:current-tab="currentTab" :current-tab="currentTab"
@set-tab="setTab" @set-tab="setTab"
@ -46,7 +50,8 @@
<i class="material-icons">camera_alt</i> <i class="material-icons">camera_alt</i>
</tabIcon> </tabIcon>
<tabIcon <tabIcon
id="settings" id="settings-tab-icon"
tab-i-d="settings"
:require-connection="false" :require-connection="false"
:current-tab="currentTab" :current-tab="currentTab"
@set-tab="setTab" @set-tab="setTab"
@ -54,12 +59,12 @@
<i class="material-icons">settings</i> <i class="material-icons">settings</i>
</tabIcon> </tabIcon>
<hr /> <hr id="extension-tab-divider" />
<tabIcon <tabIcon
v-for="plugin in pluginsGuiList" v-for="plugin in pluginsGuiList"
:id="plugin.id"
:key="plugin.id" :key="plugin.id"
:tab-i-d="plugin.id"
:title="plugin.title" :title="plugin.title"
:require-connection="plugin.requiresConnection" :require-connection="plugin.requiresConnection"
:current-tab="currentTab" :current-tab="currentTab"
@ -76,35 +81,35 @@
class="uk-padding-remove uk-height-1-1 uk-width-expand" class="uk-padding-remove uk-height-1-1 uk-width-expand"
> >
<tabContent <tabContent
id="connect" tab-i-d="connect"
:require-connection="false" :require-connection="false"
:current-tab="currentTab" :current-tab="currentTab"
> >
<connectContent /> <connectContent />
</tabContent> </tabContent>
<tabContent <tabContent
id="gallery" tab-i-d="gallery"
:require-connection="false" :require-connection="false"
:current-tab="currentTab" :current-tab="currentTab"
> >
<galleryContent /> <galleryContent />
</tabContent> </tabContent>
<tabContent <tabContent
id="navigate" tab-i-d="navigate"
:require-connection="true" :require-connection="true"
:current-tab="currentTab" :current-tab="currentTab"
> >
<navigateContent /> <navigateContent />
</tabContent> </tabContent>
<tabContent <tabContent
id="capture" tab-i-d="capture"
:require-connection="true" :require-connection="true"
:current-tab="currentTab" :current-tab="currentTab"
> >
<captureContent /> <captureContent />
</tabContent> </tabContent>
<tabContent <tabContent
id="settings" tab-i-d="settings"
:require-connection="false" :require-connection="false"
:current-tab="currentTab" :current-tab="currentTab"
> >
@ -113,8 +118,8 @@
<tabContent <tabContent
v-for="plugin in pluginsGuiList" v-for="plugin in pluginsGuiList"
:id="plugin.id"
:key="plugin.id" :key="plugin.id"
:tab-i-d="plugin.id"
:require-connection="plugin.requiresConnection" :require-connection="plugin.requiresConnection"
:current-tab="currentTab" :current-tab="currentTab"
> >
@ -204,6 +209,8 @@ export default {
); );
}, },
mounted: function() {},
beforeDestroy() { beforeDestroy() {
// Then we call that function here to unwatch // Then we call that function here to unwatch
if (this.unwatchStoreFunction) { if (this.unwatchStoreFunction) {

View file

@ -1,7 +1,7 @@
<template> <template>
<div <div
v-if="!(requireConnection && !$store.getters.ready)" v-if="!(requireConnection && !$store.getters.ready)"
:hidden="currentTab != id" :hidden="currentTab != tabID"
class="uk-width-expand uk-height-1-1" class="uk-width-expand uk-height-1-1"
> >
<div class="section-content"><slot></slot></div> <div class="section-content"><slot></slot></div>
@ -13,7 +13,7 @@ export default {
name: "TabContent", name: "TabContent",
props: { props: {
id: { tabID: {
type: String, type: String,
required: true required: true
}, },

View file

@ -18,7 +18,7 @@ export default {
name: "TabIcon", name: "TabIcon",
props: { props: {
id: { tabID: {
type: String, type: String,
required: true required: true
}, },
@ -45,7 +45,7 @@ export default {
return this.title; return this.title;
} else { } else {
// Get the last section of a fully qualified name // Get the last section of a fully qualified name
var topName = this.id.split(".").pop(); var topName = this.tabID.split(".").pop();
// Make first character uppercase, then add the rest of the string // Make first character uppercase, then add the rest of the string
return topName.charAt(0).toUpperCase() + topName.slice(1); return topName.charAt(0).toUpperCase() + topName.slice(1);
} }
@ -57,7 +57,7 @@ export default {
classObject: function() { classObject: function() {
return { return {
"tabicon-active": this.currentTab == this.id, "tabicon-active": this.currentTab == this.tabID,
"uk-disabled": this.requireConnection && !this.$store.getters.ready "uk-disabled": this.requireConnection && !this.$store.getters.ready
}; };
} }
@ -65,7 +65,7 @@ export default {
methods: { methods: {
setThisTab(event) { setThisTab(event) {
this.$emit("set-tab", event, this.id); this.$emit("set-tab", event, this.tabID);
if (this.clickCallback) { if (this.clickCallback) {
this.clickCallback(); this.clickCallback();
} }

View file

@ -7,6 +7,7 @@
> >
<div class="uk-width-auto"> <div class="uk-width-auto">
<div <div
id="new-connection-card"
class="uk-card uk-card-default uk-padding-remove uk-width-medium connect-card-align-top" class="uk-card uk-card-default uk-padding-remove uk-width-medium connect-card-align-top"
> >
<div class="uk-card-body uk-padding-small"> <div class="uk-card-body uk-padding-small">
@ -97,7 +98,11 @@
<li class="uk-open"> <li class="uk-open">
<a class="uk-accordion-title" href="#">Saved devices</a> <a class="uk-accordion-title" href="#">Saved devices</a>
<div class="uk-accordion-content"> <div class="uk-accordion-content">
<div class="uk-grid-medium uk-grid-match uk-margin-top" uk-grid> <div
id="saved-connections-grid"
class="uk-grid-medium uk-grid-match uk-margin-top"
uk-grid
>
<div v-for="host in savedHosts" :key="host.name"> <div v-for="host in savedHosts" :key="host.name">
<hostCard <hostCard
:name="host.name" :name="host.name"
@ -114,7 +119,11 @@
<li class="uk-open"> <li class="uk-open">
<a class="uk-accordion-title" href="#">Nearby devices</a> <a class="uk-accordion-title" href="#">Nearby devices</a>
<div class="uk-accordion-content"> <div class="uk-accordion-content">
<div class="uk-grid-medium uk-grid-match uk-margin-top" uk-grid> <div
id="nearby-connections-grid"
class="uk-grid-medium uk-grid-match uk-margin-top"
uk-grid
>
<div v-for="host in foundHostArray" :key="host.name"> <div v-for="host in foundHostArray" :key="host.name">
<hostCard <hostCard
:name="host.name" :name="host.name"

View file

@ -1,16 +1,21 @@
import Vue from "vue"; import Vue from "vue";
import App from "./App.vue"; import App from "./App.vue";
import store from "./store"; import store from "./store";
import UIkit from "uikit"; import UIkit from "uikit";
import VueTour from "vue-tour";
import LoadScript from "vue-plugin-load-script";
require("vue-tour/dist/vue-tour.css");
// Import MD icons // Import MD icons
import "material-design-icons/iconfont/material-icons.css"; import "material-design-icons/iconfont/material-icons.css";
// Import load-script module // Use load-script module
import LoadScript from "vue-plugin-load-script";
Vue.use(LoadScript); Vue.use(LoadScript);
// Use vue-tour module
Vue.use(VueTour);
Vue.config.productionTip = false; Vue.config.productionTip = false;
Vue.mixin({ Vue.mixin({