From 216f303315fb5346a84b397f0a9c823752e232db Mon Sep 17 00:00:00 2001 From: Moe Date: Thu, 1 Dec 2022 14:09:51 +0000 Subject: [PATCH] Helping Hand : Active Tutorial --- definitions/base.js | 223 ++++++++++++- languages/en_CA.json | 4 + web/assets/css/bs5.helpinghand.css | 29 ++ web/assets/js/bs5.help.js | 50 ++- web/assets/js/bs5.helpinghand.js | 75 +++++ web/assets/js/bs5.helpinghand.shows.js | 417 +++++++++++++++++++++++++ web/pages/blocks/footer.ejs | 2 + web/pages/blocks/header.ejs | 1 + web/pages/blocks/home/help.ejs | 270 +--------------- 9 files changed, 810 insertions(+), 261 deletions(-) create mode 100644 web/assets/css/bs5.helpinghand.css create mode 100644 web/assets/js/bs5.helpinghand.js create mode 100644 web/assets/js/bs5.helpinghand.shows.js diff --git a/definitions/base.js b/definitions/base.js index 0ea408c1..31b64ad3 100644 --- a/definitions/base.js +++ b/definitions/base.js @@ -6680,8 +6680,8 @@ module.exports = function(s,config,lang){ } ] }, - } - }, + } + }, "Log Viewer": { "section": "Log Viewer", "blocks": { @@ -8773,5 +8773,224 @@ module.exports = function(s,config,lang){ } } }, + "Help Window": { + "section": "Help Window", + "blocks": { + "Column1": { + "name": lang["Helping Hand"], + "color": "navy", + "blockquote": lang.helpFinderDescription, + "section-pre-class": "col-md-4", + "info": [ + { + "id": "helpinghand-options", + "field": lang["Active Tutorial"], + "fieldType": "select", + "possible": [ + // { + // "name": lang['Date Updated'], + // "value": "dateUpdated" + // }, + ] + }, + { + "field": lang["Monitor"], + "form-group-class": "helping-hand-target-monitor", + "fieldType": "select", + "class": "monitors_list", + }, + { + "fieldType": "btn", + "class": `btn-primary fill watch-helping-hand mb-1`, + "btnContent": lang.Run, + }, + { + "id": "helpinghand-results", + "fieldType": "div", + }, + ] + }, + "Column2": { + noHeader: true, + "color": "blue", + "section-pre-class": "col-md-8", + "noDefaultSectionClasses": true, + "box-wrapper-class": "row", + "info": [ + { + title: "New to Shinobi?", + info: `Try reading over some of these links to get yourself started.`, + buttons: [ + { + icon: 'newspaper-o', + color: 'default', + text: 'After Installation Guides', + href: 'https://shinobi.video/docs/configure', + class: '' + }, + { + icon: 'plus', + color: 'default', + text: 'Adding an H.264 Camera', + href: 'https://shinobi.video/docs/configure#content-adding-an-h264h265-camera', + class: '' + }, + { + icon: 'plus', + color: 'default', + text: 'Adding an MJPEG Camera', + href: 'https://shinobi.video/articles/2018-09-19-how-to-add-an-mjpeg-camera', + class: '' + }, + { + icon: 'gears', + color: 'default', + text: 'RTSP Camera Optimization', + href: 'https://shinobi.video/articles/2017-07-29-how-i-optimized-my-rtsp-camera', + class: '' + }, + { + icon: 'comments-o', + color: 'info', + text: 'Community Chat', + href: 'https://discord.gg/ehRd8Zz', + class: '' + }, + { + icon: 'reddit', + color: 'info', + text: 'Forum on Reddit', + href: 'https://www.reddit.com/r/ShinobiCCTV', + class: '' + }, + { + icon: 'file-o', + color: 'primary', + text: 'Documentation', + href: 'http://shinobi.video/docs', + class: '' + } + ] + }, + { + bigIcon: "smile-o", + title: "It's a proven fact", + info: `Generosity makes you a happier person, please consider supporting the development.`, + buttons: [ + { + icon: 'share-square-o', + color: 'default', + text: 'ShinobiShop Subscriptions', + href: 'https://licenses.shinobi.video/subscribe', + class: '' + }, + { + icon: 'paypal', + color: 'default', + text: 'Donate by PayPal', + href: 'https://www.paypal.me/ShinobiCCTV', + class: '' + }, + { + icon: 'bank', + color: 'default', + text: 'University of Zurich (UZH)', + href: 'https://www.zora.uzh.ch/id/eprint/139275/', + class: '' + }, + ] + }, + { + title: "Shinobi Mobile", + info: `Your subscription key can unlock features for Shinobi Mobile running on iOS and Android today!`, + buttons: [ + { + icon: 'star', + color: 'success', + text: 'Join Public Beta', + href: 'https://shinobi.video/mobile', + class: '' + }, + { + icon: 'comments-o', + color: 'primary', + text: '#mobile-client Chat', + href: 'https://discord.gg/ehRd8Zz', + class: '' + }, + ] + }, + { + title: "Support the Development", + info: `Subscribe to any of the following to boost development! Once subscribed put your Subscription ID in at the Super user panel, then restart Shinobi to Activate your installation, thanks! `, + buttons: [ + { + icon: 'share-square-o', + color: 'default', + text: 'Shinobi Mobile License ($5/m)', + href: 'https://licenses.shinobi.video/subscribe?planSubscribe=plan_G31AZ9mknNCa6z', + class: '' + }, + { + icon: 'share-square-o', + color: 'default', + text: 'Tiny Support Subscription ($10/m)', + href: 'https://licenses.shinobi.video/subscribe?planSubscribe=plan_G42jNgIqXaWmIC', + class: '' + }, + { + icon: 'share-square-o', + color: 'default', + text: 'Shinobi Pro License ($75/m)', + href: 'https://licenses.shinobi.video/subscribe?planSubscribe=plan_G3LGdNwA8lSmQy', + class: '' + }, + ] + }, + { + title: "Donations, One-Time Boost", + info: `Sometimes a subscription isn't practical for people. In which case you may show support through a PayPal donation. And as a thank you for doing so your PayPal Transaction ID can be used as a subscriptionId in your Shinobi configuration file.

Each 5 USD/EUR or 7 CAD will provide one month of activated usage. Meaning, a $20 USD donation today makes this popup go away (or activates the mobile app) for 4 months.`, + width: 12, + buttons: [ + { + icon: 'paypal', + color: 'default', + text: 'Donate by PayPal', + href: 'https://www.paypal.me/ShinobiCCTV', + class: '' + }, + ] + }, + ].map((block) => { + var parsedButtons = block.buttons.map((btn) => { + return { + "fieldType": "btn", + "class": `btn-${btn.color} fill mb-1`, + "icon": btn.icon, + "attribute": `href="${btn.href}" target="_blank"`, + "btnContent": btn.text, + } + }); + return { + noHeader: true, + isFormGroupGroup: true, + "section-pre-class": `col-md-${block.width || '6'} mb-3`, + "info": [ + { + "fieldType": "div", + divContent: `

${block.title}

` + }, + { + "fieldType": "div", + class: 'mb-3', + divContent: block.info + }, + ...parsedButtons + ] + } + }) + }, + } + }, }) } diff --git a/languages/en_CA.json b/languages/en_CA.json index 6b46085d..11803dab 100644 --- a/languages/en_CA.json +++ b/languages/en_CA.json @@ -618,6 +618,7 @@ "accountSettingsDescription": "Manage your Profile and set options like Max Storage Amount and Max Number of Days to keep videos.", "eventFiltersDescription": "Setup filters for when Events occur.", "monitorConfigFinderDescription": "This tool will help you search for configurations for cameras posted by the community. All hosted on ShinobiHub. You can post yours too, it would really help the community :)", + "helpFinderDescription": "This tool will help you learn to use Shinobi or just do certain things for you.", "License Key": "License Key", "License Activation": "License Activation", "License Activation Failed": "License Activation Failed", @@ -628,6 +629,9 @@ "Optional": "Optional", "Prefix": "Prefix", "Saved": "Saved", + "Run": "Run", + "Helping Hand": "Helping Hand", + "Active Tutorial": "Active Tutorial", "Not Saved": "Not Saved", "Not Connected": "Not Connected", "License Plate Detector": "License Plate Detector", diff --git a/web/assets/css/bs5.helpinghand.css b/web/assets/css/bs5.helpinghand.css new file mode 100644 index 00000000..2c08476e --- /dev/null +++ b/web/assets/css/bs5.helpinghand.css @@ -0,0 +1,29 @@ +#helping-hand { + z-index: 111; + position: absolute; + top: 0; + left: 0; + transition: 1.5s; + color: #fff; + animation: blinkingText 2s infinite; +} +@keyframes blinkingText{ + 0% { color: red;} + 50% { color: yellow;} + 100% { color: red;} +} +#helping-hand-player { + position: fixed; + border: 2px solid yellow; + width: 300px; + right: 10px; + bottom: 70px; + z-index: 112; + padding: 1rem; + border-radius: 10px; + background: rgba(0,0,0,0.8); +} +#helping-hand-annotation { + color: #fff; + padding-right: 2rem; +} diff --git a/web/assets/js/bs5.help.js b/web/assets/js/bs5.help.js index b79223eb..c9e8c3f2 100644 --- a/web/assets/js/bs5.help.js +++ b/web/assets/js/bs5.help.js @@ -1,5 +1,4 @@ -$(document).ready(function(){ - var helpWindow = $('#help_window') +function initHelpNotice(){ var openMessage = null function lessThanOneWeekAgo(date){ const WEEK = 1000 * 60 * 60 * 24 * 7; @@ -77,4 +76,51 @@ $(document).ready(function(){ }) console.log('Please support the Shinobi development.') console.log('https://licenses.shinobi.video/subscribe') +} +$(document).ready(function(){ + var theEnclosure = $('#tab-helpWindow') + var helpingHandResults = $('#helpinghand-results') + var helpingHandSelector = $('#helpinghand-options') + var monitorList = theEnclosure.find('.monitors_list') + function loadHelpingHandsOptions(){ + var html = `` + $.each(helpingHandShows,function(showId,show){ + html += createOptionHtml({ + label: `${show.name}`, + value: showId + }) + }) + helpingHandSelector.html(html) + } + window.getSelectedHelpingHandMonitorTarget = function(){ + return monitorList.val() + } + function loadHelpingHandSelectors(){ + loadHelpingHandsOptions() + drawMonitorListToSelector(monitorList) + } + $('.watch-helping-hand').click(function(){ + var selectedShowId = helpingHandSelector.val() + playHelpingHandShow(selectedShowId) + }) + helpingHandSelector.change(function(){ + var selectedShowId = helpingHandSelector.val() + var theShow = helpingHandShows[selectedShowId] + var monitorTargetSpecific = theEnclosure.find('.helping-hand-target-monitor') + if(theShow.targetMonitor){ + monitorTargetSpecific.show() + }else{ + monitorTargetSpecific.hide() + } + }) + initHelpNotice() + addOnTabOpen('helpWindow', function () { + loadHelpingHandSelectors() + }) + addOnTabReopen('helpWindow', function () { + loadHelpingHandSelectors() + }) + $('body').on('click','.helping-hand-stop',function(){ + stopHelpingHandShow() + }) }) diff --git a/web/assets/js/bs5.helpinghand.js b/web/assets/js/bs5.helpinghand.js new file mode 100644 index 00000000..a608db31 --- /dev/null +++ b/web/assets/js/bs5.helpinghand.js @@ -0,0 +1,75 @@ +var helpingHand = null +var isWatching = false +var helpingHandAnnotationBox = null +function drawHelpingHand(){ + if(!helpingHand){ + var html = ` +
+
+
+
+
+ ${lang.Stop} +
+
+
+ ` + $('body').append(html) + helpingHand = $('#helping-hand') + helpingHandAnnotationBox = $('#helping-hand-annotation') + } +} +function removeHelpingHand(){ + if(helpingHand){ + helpingHand.fadeOut(2000) + helpingHand.remove() + $('#helping-hand-player').remove() + helpingHand = null + } +} +async function typeWriteInField(txt,fieldTarget){ + var speed = 100; + var element = $(fieldTarget).focus().val('')[0] + for (let i = 0; i < txt.length; i++) { + element.value += txt.charAt(i); + await setPromiseTimeout(speed); + } +} +async function stopHelpingHandShow(showId){ + isWatching = false +} +async function playHelpingHandShow(showId){ + drawHelpingHand() + isWatching = true + var selectedShow = helpingHandShows[showId] + var playlist = selectedShow.playlist + for (let i = 0; i < playlist.length; i++) { + if(isWatching){ + var movement = playlist[i]; + var waitTime = movement.time * 1000 + await setPromiseTimeout(waitTime); + var cmd = movement.cmd + var text = movement.text + var handPos = movement.handPos + var handPosCss = handPos + if(handPos.el){ + var handElOffset = $(handPos.el).offset() + handElOffset.top += 30 + handPosCss = handElOffset + } + helpingHand.css(handPosCss) + if(text)helpingHandAnnotationBox[0].innerHTML = text + if(cmd){ + await setPromiseTimeout(1500); + try{ + if(isWatching)await cmd(); + }catch(err){ + console.error(err) + } + } + } + } + if(isWatching)await setPromiseTimeout(3000); + removeHelpingHand() + isWatching = false +} diff --git a/web/assets/js/bs5.helpinghand.shows.js b/web/assets/js/bs5.helpinghand.shows.js new file mode 100644 index 00000000..ba6aec13 --- /dev/null +++ b/web/assets/js/bs5.helpinghand.shows.js @@ -0,0 +1,417 @@ +var helpingHandShows = { + "motion-preset-pair": { + name: 'Add a Motion Detection On/Off Preset Pair', + targetMonitor: true, + playlist: [ + { + text: 'Select the Monitor States tab in the Main Menu.', + time: 0, + handPos: { + el: `[page-open="monitorStates"]` + }, + cmd: () => { + var sideMenu = $('#menu-side') + var theButton = sideMenu.find('[page-open="monitorStates"]') + sideMenu.animate({scrollTop: sideMenu.position().top},1000); + } + }, + { + time: 1, + handPos: { + el: `[page-open="monitorStates"]` + }, + cmd: () => { + openTab('monitorStates',{}) + } + }, + { + text: 'Select Add New under Monitor States.', + time: 1, + handPos: { + el: `#monitorStatesSelector` + }, + cmd: () => { + $('#monitorStatesSelector option').prop('selected',false); + $('#monitorStatesSelector').val('').change(); + } + }, + { + text: `Set the name for the new Preset.`, + time: 1, + handPos: { + el: `#tab-monitorStates [name="name"]` + }, + cmd: async () => { + await typeWriteInField('Motion Detection On','#tab-monitorStates [name="name"]'); + } + }, + { + text: `Add a Monitor to this new Preset.`, + time: 1, + handPos: { + el: `#tab-monitorStates .add-monitor` + }, + cmd: () => { + $('#tab-monitorStates .add-monitor').click(); + } + }, + { + text: `Select the target Monitor.`, + time: 1, + handPos: { + el: `#tab-monitorStates .state-monitor-row .state-monitor-row-select` + }, + cmd: () => { + var monitorId = getSelectedHelpingHandMonitorTarget(); + $(`#tab-monitorStates .state-monitor-row .state-monitor-row-select`).val(monitorId) + } + }, + { + text: `Select the options to activate for when this Preset becomes active.`, + time: 0.2, + handPos: { + el: `#tab-monitorStates .state-monitor-row-fields-container` + }, + cmd: () => { + var optionsSelected = $('#tab-monitorStates .state-monitor-row-fields-container') + var scrollBodyHeight = optionsSelected.height() + var detectorOption = optionsSelected.find('[data-name="detail=detector"]'); + optionsSelected.animate({scrollTop: detectorOption.position().top - scrollBodyHeight},1000); + } + }, + { + time: 1, + handPos: { + el: `#tab-monitorStates .state-monitor-row-fields-container [data-name="detail=detector"]` + }, + cmd: () => { + var optionsSelected = $('#tab-monitorStates .state-monitor-row-fields-container') + var detectorOption = optionsSelected.find('[data-name="detail=detector"]').click(); + } + }, + { + time: 1, + handPos: { + el: `#tab-monitorStates .state-monitor-row-fields-container [data-name="detail=detector"] select` + }, + cmd: () => { + $('#tab-monitorStates .state-monitor-row-fields-container [data-name="detail=detector"] select').val('1'); + } + }, + { + text: `Save the Preset.`, + time: 1, + handPos: { + el: `#tab-monitorStates .sticky-bar [type="submit"]` + }, + cmd: () => { + $('#tab-monitorStates form').submit(); + } + }, + /// Motion Off + { + text: `For this tutorial we're going to add another one for Motion Detection Off.`, + time: 1, + handPos: { + el: `#monitorStatesSelector` + }, + cmd: () => { + $('#monitorStatesSelector option').prop('selected',false); + $('#monitorStatesSelector').val('').change(); + } + }, + { + time: 1, + handPos: { + el: `#tab-monitorStates [name="name"]` + }, + cmd: async () => { + await typeWriteInField('Motion Detection Off','#tab-monitorStates [name="name"]'); + } + }, + { + time: 1, + handPos: { + el: `#tab-monitorStates .add-monitor` + }, + cmd: () => { + $('#tab-monitorStates .add-monitor').click(); + } + }, + { + time: 1, + handPos: { + el: `#tab-monitorStates .state-monitor-row .state-monitor-row-select` + }, + cmd: () => { + var monitorId = getSelectedHelpingHandMonitorTarget(); + $(`#tab-monitorStates .state-monitor-row .state-monitor-row-select`).val(monitorId) + } + }, + { + time: 0.2, + handPos: { + el: `#tab-monitorStates .state-monitor-row-fields-container` + }, + cmd: () => { + var optionsSelected = $('#tab-monitorStates .state-monitor-row-fields-container') + var scrollBodyHeight = optionsSelected.height() + var detectorOption = optionsSelected.find('[data-name="detail=detector"]'); + optionsSelected.animate({scrollTop: detectorOption.position().top - scrollBodyHeight},1000); + } + }, + { + time: 1, + handPos: { + el: `#tab-monitorStates .state-monitor-row-fields-container [data-name="detail=detector"]` + }, + cmd: () => { + var optionsSelected = $('#tab-monitorStates .state-monitor-row-fields-container') + var detectorOption = optionsSelected.find('[data-name="detail=detector"]').click(); + } + }, + { + time: 1, + handPos: { + el: `#tab-monitorStates .state-monitor-row-fields-container [data-name="detail=detector"] select` + }, + cmd: () => { + $('#tab-monitorStates .state-monitor-row-fields-container [data-name="detail=detector"] select').val('0'); + } + }, + { + time: 1, + handPos: { + el: `#tab-monitorStates .sticky-bar [type="submit"]` + }, + cmd: () => { + $('#tab-monitorStates form').submit(); + } + }, + /// set schedule, motion ON + { + text: 'Now we set them to activate automatically based on a time frame.', + time: 1, + handPos: { + el: `[page-open="schedules"]` + }, + cmd: () => { + openTab('schedules',{}) + } + }, + { + time: 1, + handPos: { + el: `#schedulesSelector` + }, + cmd: () => { + $('#schedulesSelector option').prop('selected',false); + $('#schedulesSelector').val('').change(); + } + }, + { + time: 1, + handPos: { + el: `#tab-schedules [name="name"]` + }, + cmd: async () => { + await typeWriteInField('Motion Detection On','#tab-schedules [name="name"]'); + } + }, + { + time: 1, + handPos: { + el: `#tab-schedules [name="enabled"]` + } + }, + { + time: 1, + handPos: { + el: `#tab-schedules [name="start"]` + }, + cmd: async () => { + await typeWriteInField('00:00','#tab-schedules [name="start"]'); + } + }, + { + time: 1, + handPos: { + el: `#tab-schedules [name="end"]` + }, + cmd: async () => { + await typeWriteInField('23:00','#tab-schedules [name="end"]'); + } + }, + { + time: 1, + handPos: { + el: `#tab-schedules [name="days"]` + }, + cmd: async () => { + var daysSelector = $('#tab-schedules [name="days"]'); + daysSelector.find('option').prop('selected',false) + daysSelector.find('[value="0"],[value="2"],[value="4"],[value="6"]').prop('selected',true) + } + }, + { + time: 2, + handPos: { + el: `#tab-schedules [name="monitorStates"]` + }, + cmd: async () => { + var presetSelector = $('#tab-schedules [name="monitorStates"]'); + presetSelector.find('option').prop('selected',false) + presetSelector.find('[value="Motion Detection On"]').prop('selected',true) + } + }, + { + time: 1, + handPos: { + el: `#tab-schedules .sticky-bar [type="submit"]` + }, + cmd: () => { + $('#tab-schedules form').submit(); + } + }, + /// set schedule, motion OFF + { + time: 3, + handPos: { + el: `#schedulesSelector` + }, + cmd: () => { + $('#schedulesSelector option').prop('selected',false); + $('#schedulesSelector').val('').change(); + } + }, + { + time: 1, + handPos: { + el: `#tab-schedules [name="name"]` + }, + cmd: async () => { + await typeWriteInField('Motion Detection Off','#tab-schedules [name="name"]'); + } + }, + { + time: 1, + handPos: { + el: `#tab-schedules [name="enabled"]` + } + }, + { + time: 1, + handPos: { + el: `#tab-schedules [name="start"]` + }, + cmd: async () => { + await typeWriteInField('00:00','#tab-schedules [name="start"]'); + } + }, + { + time: 1, + handPos: { + el: `#tab-schedules [name="end"]` + }, + cmd: async () => { + await typeWriteInField('23:00','#tab-schedules [name="end"]'); + } + }, + { + time: 1, + handPos: { + el: `#tab-schedules [name="days"]` + }, + cmd: async () => { + var daysSelector = $('#tab-schedules [name="days"]'); + daysSelector.find('option').prop('selected',false) + daysSelector.find('[value="1"],[value="3"],[value="5"]').prop('selected',true) + } + }, + { + time: 2, + handPos: { + el: `#tab-schedules [name="monitorStates"]` + }, + cmd: async () => { + var presetSelector = $('#tab-schedules [name="monitorStates"]'); + presetSelector.find('option').prop('selected',false) + presetSelector.find('[value="Motion Detection Off"]').prop('selected',true) + } + }, + { + time: 1, + handPos: { + el: `#tab-schedules .sticky-bar [type="submit"]` + }, + cmd: () => { + $('#tab-schedules form').submit(); + } + }, + ] + }, + "create-api-key": { + name: 'Create API Key', + targetMonitor: false, + playlist: [ + { + text: 'Select the API Keys tab in the Main Menu.', + time: 0, + handPos: { + el: `[page-open="apiKeys"]` + }, + cmd: () => { + var sideMenu = $('#menu-side') + var theButton = sideMenu.find('[page-open="apiKeys"]') + sideMenu.animate({scrollTop: sideMenu.position().top},1000); + } + }, + { + time: 1, + handPos: { + el: `[page-open="apiKeys"]` + }, + cmd: () => { + openTab('apiKeys',{}) + } + }, + { + text: `Set the name for the new Preset.`, + time: 1, + handPos: { + el: `#tab-apiKeys [name="ip"]` + }, + cmd: async () => { + await typeWriteInField('0.0.0.0','#tab-apiKeys [name="ip"]'); + } + }, + { + time: 1, + handPos: { + el: `#tab-apiKeys [detail="permissions"]` + }, + cmd: async () => { + var theSelector = $('#tab-apiKeys [detail="permissions"]'); + theSelector.find('option').prop('selected',true) + } + }, + { + text: `Generate the Key.`, + time: 1, + handPos: { + el: `#apiKeySectionAddNew [type="submit"]` + }, + cmd: () => { + $('#apiKeySectionAddNew').submit(); + } + }, + { + text: `Generate the Key.`, + time: 1, + handPos: { + el: `#api_list [api_key]:nth-child(1) .copy` + }, + }, + ] + } +} diff --git a/web/pages/blocks/footer.ejs b/web/pages/blocks/footer.ejs index 3f2f3739..7f0fed12 100644 --- a/web/pages/blocks/footer.ejs +++ b/web/pages/blocks/footer.ejs @@ -37,6 +37,8 @@ + + diff --git a/web/pages/blocks/header.ejs b/web/pages/blocks/header.ejs index 2de3c891..3e9c3b96 100644 --- a/web/pages/blocks/header.ejs +++ b/web/pages/blocks/header.ejs @@ -38,6 +38,7 @@ +