diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..82dc408 --- /dev/null +++ b/_config.yml @@ -0,0 +1,18 @@ +theme: jekyll-theme-slate +title: Jeedom - Kimagure +description: Documents plugins Jeedom + +author: + Kimagure + +languages: ["en", "fr"] +defaultLang: fr +languageNames: + fr: Français + en: English + +plugins: + - jemoji + - jekyll-seo-tag + +markdown: CommonMarkGhPages diff --git a/_includes/lightbox.html b/_includes/lightbox.html new file mode 100644 index 0000000..e798c5d --- /dev/null +++ b/_includes/lightbox.html @@ -0,0 +1,3 @@ + + {{ include.title }} + diff --git a/_layouts/default.html b/_layouts/default.html new file mode 100644 index 0000000..cd70143 --- /dev/null +++ b/_layouts/default.html @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + {% seo %} + + + + +
+
+

{{ page.title }}

+

{{ page.description | default: site.github.project_tagline }}

+ + {% if site.show_downloads %} +
+ Download this project as a .zip file + Download this project as a tar.gz file +
+ {% endif %} +
+
+ + +
+
+ {{ content }} +
+
+ + + + + {% if site.google_analytics %} + + {% endif %} + + + + diff --git a/assets/css/lightbox.css b/assets/css/lightbox.css new file mode 100644 index 0000000..cd4d790 --- /dev/null +++ b/assets/css/lightbox.css @@ -0,0 +1,198 @@ +html.lb-disable-scrolling { + overflow: hidden; + /* Position fixed required for iOS. Just putting overflow: hidden; on the body is not enough. */ + position: fixed; + height: 100vh; + width: 100vw; +} + +.lightboxOverlay { + position: absolute; + top: 0; + left: 0; + z-index: 9999; + background-color: black; + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); + opacity: 0.8; + display: none; +} + +.lightbox { + position: absolute; + left: 0; + width: 100%; + z-index: 10000; + text-align: center; + line-height: 0; + font-weight: normal; +} + +.lightbox .lb-image { + display: block; + height: auto; + max-width: inherit; + max-height: none; +} + +.lightbox a img { + border: none; +} + +.lb-outerContainer { + position: relative; + *zoom: 1; + width: 250px; + height: 250px; + margin: 0 auto; +} + +.lb-outerContainer:after { + content: ""; + display: table; + clear: both; +} + +.lb-loader { + position: absolute; + top: 43%; + left: 0; + height: 25%; + width: 100%; + text-align: center; + line-height: 0; +} + +.lb-cancel { + display: block; + width: 32px; + height: 32px; + margin: 0 auto; + background: url(../images/loading.gif) no-repeat; +} + +.lb-nav { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + z-index: 10; +} + +.lb-container > .nav { + left: 0; +} + +.lb-nav a { + outline: none; + background-image: url('data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='); +} + +.lb-prev, .lb-next { + height: 100%; + cursor: pointer; + display: block; +} + +.lb-nav a.lb-prev { + width: 34%; + left: 0; + float: left; + background: url(../images/prev.png) left 48% no-repeat; + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0); + opacity: 0; + -webkit-transition: opacity 0.2s; + -moz-transition: opacity 0.2s; + -o-transition: opacity 0.2s; + transition: opacity 0.2s; +} + +.lb-nav a.lb-prev:hover { + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); + opacity: 1; +} + +.lb-nav a.lb-next { + width: 64%; + right: 0; + float: right; + background: url(../images/next.png) right 48% no-repeat; + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0); + opacity: 0; + -webkit-transition: opacity 0.2s; + -moz-transition: opacity 0.2s; + -o-transition: opacity 0.2s; + transition: opacity 0.2s; +} + +.lb-nav a.lb-next:hover { + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); + opacity: 1; +} + +.lb-dataContainer { + margin: 0 auto; + padding-top: 5px; + *zoom: 1; + width: 100%; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; +} + +.lb-dataContainer:after { + content: ""; + display: table; + clear: both; +} + +.lb-data { + padding: 0 4px; + color: #ccc; +} + +.lb-data .lb-details { + width: 85%; + float: left; + text-align: left; + line-height: 1.1em; +} + +.lb-data .lb-caption { + font-size: 13px; + font-weight: bold; + line-height: 1em; +} + +.lb-data .lb-caption a { + color: #4ae; +} + +.lb-data .lb-number { + display: block; + clear: left; + padding-bottom: 1em; + font-size: 12px; + color: #999999; +} + +.lb-data .lb-close { + display: block; + float: right; + width: 30px; + height: 30px; + background: url(../images/close.png) top right no-repeat; + text-align: right; + outline: none; + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); + opacity: 0.7; + -webkit-transition: opacity 0.2s; + -moz-transition: opacity 0.2s; + -o-transition: opacity 0.2s; + transition: opacity 0.2s; +} + +.lb-data .lb-close:hover { + cursor: pointer; + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); + opacity: 1; +} diff --git a/assets/css/style.scss b/assets/css/style.scss new file mode 100644 index 0000000..ed923d4 --- /dev/null +++ b/assets/css/style.scss @@ -0,0 +1,72 @@ +--- +--- + +@import "{{ site.theme }}"; + +.inner { + max-width:760px !important; +} + +#header_wrap, #footer_wrap { + color:#fff; + background-color:#333333; + background-image:linear-gradient(90deg, #333333, #333333); +} + +p { + text-align: justify; + text-justify: inter-word; +} + +img { + -webkit-box-shadow: none; + -moz-box-shadow: none; + -o-box-shadow: none; + -ms-box-shadow: none; + box-shadow: none; + border: none; +} + +.alert { + color: red; + font-weight: bold; +} + +.emoji { + padding: 0; + margin: 0; +} +li .emoji { + padding-bottom: 4px !important; + vertical-align: bottom +} + +summary:hover { + cursor: pointer; +} +summary { + background-color: rgb(228, 228, 228); + margin-top: 2px !important; + padding-left: 12px !important; +} + +//scroll to top: +.scrollUpButton { + display: none; + opacity: 0.6; + position: fixed; + bottom: 10px; + right: 10px; + display: none; + background: #000; + color: #fff; + font-size: 1.5em; + text-decoration: none; + padding: 5px 10px 5px 10px; +} +.scrollUpButton:hover, .scrollUpButton:focus { + outline: none; + text-decoration: none; + color: #fff; + opacity: 1; +} diff --git a/assets/images/close.png b/assets/images/close.png new file mode 100644 index 0000000..20baa1d Binary files /dev/null and b/assets/images/close.png differ diff --git a/assets/images/loading.gif b/assets/images/loading.gif new file mode 100644 index 0000000..5087c2a Binary files /dev/null and b/assets/images/loading.gif differ diff --git a/assets/images/next.png b/assets/images/next.png new file mode 100644 index 0000000..08365ac Binary files /dev/null and b/assets/images/next.png differ diff --git a/assets/images/prev.png b/assets/images/prev.png new file mode 100644 index 0000000..329fa98 Binary files /dev/null and b/assets/images/prev.png differ diff --git a/assets/js/lightbox.js b/assets/js/lightbox.js new file mode 100644 index 0000000..5b8785a --- /dev/null +++ b/assets/js/lightbox.js @@ -0,0 +1,519 @@ +/*! + * Lightbox v2.10.0 + * by Lokesh Dhakar + * + * More info: + * http://lokeshdhakar.com/projects/lightbox2/ + * + * Copyright 2007, 2018 Lokesh Dhakar + * Released under the MIT license + * https://github.com/lokesh/lightbox2/blob/master/LICENSE + * + * @preserve + */ + +// Uses Node, AMD or browser globals to create a module. +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(require('jquery')); + } else { + // Browser globals (root is window) + root.lightbox = factory(root.jQuery); + } +}(this, function ($) { + + function Lightbox(options) { + this.album = []; + this.currentImageIndex = void 0; + this.init(); + + // options + this.options = $.extend({}, this.constructor.defaults); + this.option(options); + } + + // Descriptions of all options available on the demo site: + // http://lokeshdhakar.com/projects/lightbox2/index.html#options + Lightbox.defaults = { + albumLabel: 'Image %1 of %2', + alwaysShowNavOnTouchDevices: false, + fadeDuration: 600, + fitImagesInViewport: true, + imageFadeDuration: 600, + // maxWidth: 800, + // maxHeight: 600, + positionFromTop: 50, + resizeDuration: 700, + showImageNumberLabel: true, + wrapAround: false, + disableScrolling: false, + /* + Sanitize Title + If the caption data is trusted, for example you are hardcoding it in, then leave this to false. + This will free you to add html tags, such as links, in the caption. + + If the caption data is user submitted or from some other untrusted source, then set this to true + to prevent xss and other injection attacks. + */ + sanitizeTitle: false + }; + + Lightbox.prototype.option = function(options) { + $.extend(this.options, options); + }; + + Lightbox.prototype.imageCountLabel = function(currentImageNum, totalImages) { + return this.options.albumLabel.replace(/%1/g, currentImageNum).replace(/%2/g, totalImages); + }; + + Lightbox.prototype.init = function() { + var self = this; + // Both enable and build methods require the body tag to be in the DOM. + $(document).ready(function() { + self.enable(); + self.build(); + }); + }; + + // Loop through anchors and areamaps looking for either data-lightbox attributes or rel attributes + // that contain 'lightbox'. When these are clicked, start lightbox. + Lightbox.prototype.enable = function() { + var self = this; + $('body').on('click', 'a[rel^=lightbox], area[rel^=lightbox], a[data-lightbox], area[data-lightbox]', function(event) { + self.start($(event.currentTarget)); + return false; + }); + }; + + // Build html for the lightbox and the overlay. + // Attach event handlers to the new DOM elements. click click click + Lightbox.prototype.build = function() { + if ($('#lightbox').length > 0) { + return; + } + + var self = this; + $('
').appendTo($('body')); + + // Cache jQuery objects + this.$lightbox = $('#lightbox'); + this.$overlay = $('#lightboxOverlay'); + this.$outerContainer = this.$lightbox.find('.lb-outerContainer'); + this.$container = this.$lightbox.find('.lb-container'); + this.$image = this.$lightbox.find('.lb-image'); + this.$nav = this.$lightbox.find('.lb-nav'); + + // Store css values for future lookup + this.containerPadding = { + top: parseInt(this.$container.css('padding-top'), 10), + right: parseInt(this.$container.css('padding-right'), 10), + bottom: parseInt(this.$container.css('padding-bottom'), 10), + left: parseInt(this.$container.css('padding-left'), 10) + }; + + this.imageBorderWidth = { + top: parseInt(this.$image.css('border-top-width'), 10), + right: parseInt(this.$image.css('border-right-width'), 10), + bottom: parseInt(this.$image.css('border-bottom-width'), 10), + left: parseInt(this.$image.css('border-left-width'), 10) + }; + + // Attach event handlers to the newly minted DOM elements + this.$overlay.hide().on('click', function() { + self.end(); + return false; + }); + + this.$lightbox.hide().on('click', function(event) { + if ($(event.target).attr('id') === 'lightbox') { + self.end(); + } + return false; + }); + + this.$outerContainer.on('click', function(event) { + if ($(event.target).attr('id') === 'lightbox') { + self.end(); + } + return false; + }); + + this.$lightbox.find('.lb-prev').on('click', function() { + if (self.currentImageIndex === 0) { + self.changeImage(self.album.length - 1); + } else { + self.changeImage(self.currentImageIndex - 1); + } + return false; + }); + + this.$lightbox.find('.lb-next').on('click', function() { + if (self.currentImageIndex === self.album.length - 1) { + self.changeImage(0); + } else { + self.changeImage(self.currentImageIndex + 1); + } + return false; + }); + + /* + Show context menu for image on right-click + + There is a div containing the navigation that spans the entire image and lives above of it. If + you right-click, you are right clicking this div and not the image. This prevents users from + saving the image or using other context menu actions with the image. + + To fix this, when we detect the right mouse button is pressed down, but not yet clicked, we + set pointer-events to none on the nav div. This is so that the upcoming right-click event on + the next mouseup will bubble down to the image. Once the right-click/contextmenu event occurs + we set the pointer events back to auto for the nav div so it can capture hover and left-click + events as usual. + */ + this.$nav.on('mousedown', function(event) { + if (event.which === 3) { + self.$nav.css('pointer-events', 'none'); + + self.$lightbox.one('contextmenu', function() { + setTimeout(function() { + this.$nav.css('pointer-events', 'auto'); + }.bind(self), 0); + }); + } + }); + + + this.$lightbox.find('.lb-loader, .lb-close').on('click', function() { + self.end(); + return false; + }); + }; + + // Show overlay and lightbox. If the image is part of a set, add siblings to album array. + Lightbox.prototype.start = function($link) { + var self = this; + var $window = $(window); + + $window.on('resize', $.proxy(this.sizeOverlay, this)); + + $('select, object, embed').css({ + visibility: 'hidden' + }); + + this.sizeOverlay(); + + this.album = []; + var imageNumber = 0; + + function addToAlbum($link) { + self.album.push({ + alt: $link.attr('data-alt'), + link: $link.attr('href'), + title: $link.attr('data-title') || $link.attr('title') + }); + } + + // Support both data-lightbox attribute and rel attribute implementations + var dataLightboxValue = $link.attr('data-lightbox'); + var $links; + + if (dataLightboxValue) { + $links = $($link.prop('tagName') + '[data-lightbox="' + dataLightboxValue + '"]'); + for (var i = 0; i < $links.length; i = ++i) { + addToAlbum($($links[i])); + if ($links[i] === $link[0]) { + imageNumber = i; + } + } + } else { + if ($link.attr('rel') === 'lightbox') { + // If image is not part of a set + addToAlbum($link); + } else { + // If image is part of a set + $links = $($link.prop('tagName') + '[rel="' + $link.attr('rel') + '"]'); + for (var j = 0; j < $links.length; j = ++j) { + addToAlbum($($links[j])); + if ($links[j] === $link[0]) { + imageNumber = j; + } + } + } + } + + // Position Lightbox + var top = $window.scrollTop() + this.options.positionFromTop; + var left = $window.scrollLeft(); + this.$lightbox.css({ + top: top + 'px', + left: left + 'px' + }).fadeIn(this.options.fadeDuration); + + // Disable scrolling of the page while open + if (this.options.disableScrolling) { + $('html').addClass('lb-disable-scrolling'); + } + + this.changeImage(imageNumber); + }; + + // Hide most UI elements in preparation for the animated resizing of the lightbox. + Lightbox.prototype.changeImage = function(imageNumber) { + var self = this; + + this.disableKeyboardNav(); + var $image = this.$lightbox.find('.lb-image'); + + this.$overlay.fadeIn(this.options.fadeDuration); + + $('.lb-loader').fadeIn('slow'); + this.$lightbox.find('.lb-image, .lb-nav, .lb-prev, .lb-next, .lb-dataContainer, .lb-numbers, .lb-caption').hide(); + + this.$outerContainer.addClass('animating'); + + // When image to show is preloaded, we send the width and height to sizeContainer() + var preloader = new Image(); + preloader.onload = function() { + var $preloader; + var imageHeight; + var imageWidth; + var maxImageHeight; + var maxImageWidth; + var windowHeight; + var windowWidth; + + $image.attr({ + 'alt': self.album[imageNumber].alt, + 'src': self.album[imageNumber].link + }); + + $preloader = $(preloader); + + $image.width(preloader.width); + $image.height(preloader.height); + + if (self.options.fitImagesInViewport) { + // Fit image inside the viewport. + // Take into account the border around the image and an additional 10px gutter on each side. + + windowWidth = $(window).width(); + windowHeight = $(window).height(); + maxImageWidth = windowWidth - self.containerPadding.left - self.containerPadding.right - self.imageBorderWidth.left - self.imageBorderWidth.right - 20; + maxImageHeight = windowHeight - self.containerPadding.top - self.containerPadding.bottom - self.imageBorderWidth.top - self.imageBorderWidth.bottom - 120; + + // Check if image size is larger then maxWidth|maxHeight in settings + if (self.options.maxWidth && self.options.maxWidth < maxImageWidth) { + maxImageWidth = self.options.maxWidth; + } + if (self.options.maxHeight && self.options.maxHeight < maxImageWidth) { + maxImageHeight = self.options.maxHeight; + } + + // Is the current image's width or height is greater than the maxImageWidth or maxImageHeight + // option than we need to size down while maintaining the aspect ratio. + if ((preloader.width > maxImageWidth) || (preloader.height > maxImageHeight)) { + if ((preloader.width / maxImageWidth) > (preloader.height / maxImageHeight)) { + imageWidth = maxImageWidth; + imageHeight = parseInt(preloader.height / (preloader.width / imageWidth), 10); + $image.width(imageWidth); + $image.height(imageHeight); + } else { + imageHeight = maxImageHeight; + imageWidth = parseInt(preloader.width / (preloader.height / imageHeight), 10); + $image.width(imageWidth); + $image.height(imageHeight); + } + } + } + self.sizeContainer($image.width(), $image.height()); + }; + + preloader.src = this.album[imageNumber].link; + this.currentImageIndex = imageNumber; + }; + + // Stretch overlay to fit the viewport + Lightbox.prototype.sizeOverlay = function() { + this.$overlay + .width($(document).width()) + .height($(document).height()); + }; + + // Animate the size of the lightbox to fit the image we are showing + Lightbox.prototype.sizeContainer = function(imageWidth, imageHeight) { + var self = this; + + var oldWidth = this.$outerContainer.outerWidth(); + var oldHeight = this.$outerContainer.outerHeight(); + var newWidth = imageWidth + this.containerPadding.left + this.containerPadding.right + this.imageBorderWidth.left + this.imageBorderWidth.right; + var newHeight = imageHeight + this.containerPadding.top + this.containerPadding.bottom + this.imageBorderWidth.top + this.imageBorderWidth.bottom; + + function postResize() { + self.$lightbox.find('.lb-dataContainer').width(newWidth); + self.$lightbox.find('.lb-prevLink').height(newHeight); + self.$lightbox.find('.lb-nextLink').height(newHeight); + self.showImage(); + } + + if (oldWidth !== newWidth || oldHeight !== newHeight) { + this.$outerContainer.animate({ + width: newWidth, + height: newHeight + }, this.options.resizeDuration, 'swing', function() { + postResize(); + }); + } else { + postResize(); + } + }; + + // Display the image and its details and begin preload neighboring images. + Lightbox.prototype.showImage = function() { + this.$lightbox.find('.lb-loader').stop(true).hide(); + this.$lightbox.find('.lb-image').fadeIn(this.options.imageFadeDuration); + + this.updateNav(); + this.updateDetails(); + this.preloadNeighboringImages(); + this.enableKeyboardNav(); + }; + + // Display previous and next navigation if appropriate. + Lightbox.prototype.updateNav = function() { + // Check to see if the browser supports touch events. If so, we take the conservative approach + // and assume that mouse hover events are not supported and always show prev/next navigation + // arrows in image sets. + var alwaysShowNav = false; + try { + document.createEvent('TouchEvent'); + alwaysShowNav = (this.options.alwaysShowNavOnTouchDevices) ? true : false; + } catch (e) {} + + this.$lightbox.find('.lb-nav').show(); + + if (this.album.length > 1) { + if (this.options.wrapAround) { + if (alwaysShowNav) { + this.$lightbox.find('.lb-prev, .lb-next').css('opacity', '1'); + } + this.$lightbox.find('.lb-prev, .lb-next').show(); + } else { + if (this.currentImageIndex > 0) { + this.$lightbox.find('.lb-prev').show(); + if (alwaysShowNav) { + this.$lightbox.find('.lb-prev').css('opacity', '1'); + } + } + if (this.currentImageIndex < this.album.length - 1) { + this.$lightbox.find('.lb-next').show(); + if (alwaysShowNav) { + this.$lightbox.find('.lb-next').css('opacity', '1'); + } + } + } + } + }; + + // Display caption, image number, and closing button. + Lightbox.prototype.updateDetails = function() { + var self = this; + + // Enable anchor clicks in the injected caption html. + // Thanks Nate Wright for the fix. @https://github.com/NateWr + if (typeof this.album[this.currentImageIndex].title !== 'undefined' && + this.album[this.currentImageIndex].title !== '') { + var $caption = this.$lightbox.find('.lb-caption'); + if (this.options.sanitizeTitle) { + $caption.text(this.album[this.currentImageIndex].title); + } else { + $caption.html(this.album[this.currentImageIndex].title); + } + $caption.fadeIn('fast') + .find('a').on('click', function(event) { + if ($(this).attr('target') !== undefined) { + window.open($(this).attr('href'), $(this).attr('target')); + } else { + location.href = $(this).attr('href'); + } + }); + } + + if (this.album.length > 1 && this.options.showImageNumberLabel) { + var labelText = this.imageCountLabel(this.currentImageIndex + 1, this.album.length); + this.$lightbox.find('.lb-number').text(labelText).fadeIn('fast'); + } else { + this.$lightbox.find('.lb-number').hide(); + } + + this.$outerContainer.removeClass('animating'); + + this.$lightbox.find('.lb-dataContainer').fadeIn(this.options.resizeDuration, function() { + return self.sizeOverlay(); + }); + }; + + // Preload previous and next images in set. + Lightbox.prototype.preloadNeighboringImages = function() { + if (this.album.length > this.currentImageIndex + 1) { + var preloadNext = new Image(); + preloadNext.src = this.album[this.currentImageIndex + 1].link; + } + if (this.currentImageIndex > 0) { + var preloadPrev = new Image(); + preloadPrev.src = this.album[this.currentImageIndex - 1].link; + } + }; + + Lightbox.prototype.enableKeyboardNav = function() { + $(document).on('keyup.keyboard', $.proxy(this.keyboardAction, this)); + }; + + Lightbox.prototype.disableKeyboardNav = function() { + $(document).off('.keyboard'); + }; + + Lightbox.prototype.keyboardAction = function(event) { + var KEYCODE_ESC = 27; + var KEYCODE_LEFTARROW = 37; + var KEYCODE_RIGHTARROW = 39; + + var keycode = event.keyCode; + var key = String.fromCharCode(keycode).toLowerCase(); + if (keycode === KEYCODE_ESC || key.match(/x|o|c/)) { + this.end(); + } else if (key === 'p' || keycode === KEYCODE_LEFTARROW) { + if (this.currentImageIndex !== 0) { + this.changeImage(this.currentImageIndex - 1); + } else if (this.options.wrapAround && this.album.length > 1) { + this.changeImage(this.album.length - 1); + } + } else if (key === 'n' || keycode === KEYCODE_RIGHTARROW) { + if (this.currentImageIndex !== this.album.length - 1) { + this.changeImage(this.currentImageIndex + 1); + } else if (this.options.wrapAround && this.album.length > 1) { + this.changeImage(0); + } + } + }; + + // Closing time. :-( + Lightbox.prototype.end = function() { + this.disableKeyboardNav(); + $(window).off('resize', this.sizeOverlay); + this.$lightbox.fadeOut(this.options.fadeDuration); + this.$overlay.fadeOut(this.options.fadeDuration); + $('select, object, embed').css({ + visibility: 'visible' + }); + if (this.options.disableScrolling) { + $('html').removeClass('lb-disable-scrolling'); + } + }; + + return new Lightbox(); +})); diff --git a/assets/js/scrollTop.js b/assets/js/scrollTop.js new file mode 100644 index 0000000..c8691f0 --- /dev/null +++ b/assets/js/scrollTop.js @@ -0,0 +1,13 @@ +$(document).ready(function(){ + $(window).scroll(function(){ + if ($(this).scrollTop() > 100) { + $('.scrollUpButton').fadeIn(); + } else { + $('.scrollUpButton').fadeOut(); + } + }); + $('.scrollUpButton').click(function(){ + $("html, body").animate({ scrollTop: 0 }, 500); + return false; + }); +}); diff --git a/index.md b/index.md new file mode 100644 index 0000000..8a55f58 --- /dev/null +++ b/index.md @@ -0,0 +1,4 @@ +Documentation des plugins [Jeedom](https://www.jeedom.com) par Kimagure + +- [kTwinkly](kTwinkly/) Gestion des guirlandes connectées Twinkly + diff --git a/kTwinkly/fr_FR/changelog.md b/kTwinkly/fr_FR/changelog.md new file mode 100644 index 0000000..bab4e18 --- /dev/null +++ b/kTwinkly/fr_FR/changelog.md @@ -0,0 +1,34 @@ +# Changelog kTwinkly + +>**IMPORTANT** +> +>Pour rappel s'il n'y a pas d'information sur la mise à jour, c'est que celle-ci concerne uniquement de la mise à jour de documentation, de traduction ou de texte. + + + +## 05 Décembre 2020 + +``BETA`` Publication de la première version beta + +## 06 Décembre 2020 + +``BETA`` Renommage de la classe utilitaire de Twinkly en TwinklyString pour éviter les éventuels conflits avec l'autre plugin Twinkly. + +``BETA`` Utilisation du mode "effect" au lieu du mode "movie" pour les guirlandes GEN2. + +``BETA`` Correction du process de démarrage du proxy mitmdump. + +## 07 Décembre 2020 + +`BETA` Mise à jour du script d'installation des dépendances : + +- Installation de python 3.7.3 en mode “altinstall” pour Debian 9 + +- Nouveau process d’installation de mitmproxy via pip + +`BETA` Ajout d’une nouvelle option pour avoir des logs de mitmproxy (kTwinkly_mitm) + +`BETA` Image d’équipement par défaut si l’image n’est pas trouvée dans la librairie du plugin + + + diff --git a/kTwinkly/fr_FR/index.md b/kTwinkly/fr_FR/index.md new file mode 100644 index 0000000..60b02d4 --- /dev/null +++ b/kTwinkly/fr_FR/index.md @@ -0,0 +1,205 @@ +# Plugin kTwinkly + +Ce plugin pour Jeedom permet le pilotage des guirlandes connectées [Twinkly](https://www.twinkly.com/) + +Listes des fonctionnalités disponibles : + +- [Découverte automatique](#découverte-et-paramétrage-des-équipements) des guirlandes connectées sur le réseau et de leurs caractéristiques +- Pilotage simple on/off +- Contrôle du niveau de luminosité +- Chargement d'une animation sur la guirlande +- [Capture des animations](#capture-des-animations) envoyés vers la guirlande depuis l'application mobile officielle Twinkly pour pouvoir ensuite les charger sur le sapin. + +Certaines fonctionnalités peuvent ne pas être disponibles sur les guirlandes d'anciennes générations ("gen 1") à cause de limitation du contrôleur ou du firmware. + + + +## Information importante + +A cause d'une limitation "by design" du mécanisme d'authentification sur le contrôleur Twinkly, il est n'est possible d'utiliser qu'**un seul outil à la fois** pour piloter une guirlande. Il n'est donc pas possible d'utiliser confortablement et sans erreur le plugin en même temps que l'app mobile. Cette limitation n'est pas spécifique à ce plugin, mais empêche également l'utilisation simultanée de plusieurs smartphones pour piloter une même guirlande : voir la [FAQ](https://www.twinkly.com/knowledge/how-to-manage-twinkly-from-multiple-smartphone/) sur le site de Twinkly. + +Pour contourner ce problème, notamment pendant les phases de capture des animations qui se font au travers de l'app mobile, il est possible de désactiver temporairement le rafraichissement automatique des informations d'une guirlande par le plugin. Plus aucun accès n'est donc fait en arrière plan par le plugin, et l'app mobile est pleinement utilisable. + + + +## Installation du plugin + +Après installation du plugin depuis le market Jeedom et son activation, il est nécessaire d'installer les dépendances pour pouvoir utiliser la fonction de capture des animations (package mitmproxy). + +Il y a 2 paramètres disponibles dans la configuration générale du plugin : + +- la fréquence à laquelle le plugin appelera l'API des différents contrôleurs Twinkly pour récupérer la mise à jour des informations (état, luminosité). Cette fréquence est de 10 secondes par défaut. +- le port HTTP du proxy qui sera lancé sur le serveur Jeedom pour [capturer les animations](#capture-des-animations) depuis l'application mobile. +- une option de debugging permettant d'activer les logs du proxy de capture. Ces logs seront visibles sous le nom kTwinkly_mitm dans la page de logs de Jeedom. La log est écrasée à chaque démarrage du proxy. + +![](https://kimagurefr.github.io/jeedom_docs/kTwinkly/images/config_plugin.png) + + + +## Découverte et paramétrage des équipements + +Depuis la page du plugin (Objets Connectés > Twinkly), il faut ensuite créer ou faire détecter automatiquement les guirlandes. + +**Important** : il est nécessaire que les guirlandes soient configurées préalablement et connectées au réseau wifi en utilisant l'application mobile officielle Twinkly sur iOS ou Android. + + + +La solution la plus simple est d'utiliser le bouton Recherche pour lancer la découverte automatique des équipements. + + + +Si la découverte automatique ne marche pas (équipements sur un réseau différent, ou trafic UDP broadcast bloqué), il est possible de créer les équipements manuellement. Il faut alors fournir les informations suivantes : + +- L'adresse IP de la guirlande +- L'adresse MAC + +Ces 2 informations sont visibles dans l'application mobile Twinkly. + +![Configuration Equipement](https://kimagurefr.github.io/jeedom_docs/kTwinkly/images/config_equipement.png) + +Après sauvegarde de l'équipement, les caractéristiques seront récupérées depuis le contrôleur de la guirlande. + +Dans cet écran, il est également possible de désactiver le rafraîchissement automatique des informations, pour ne pas perturber l'application mobile. + + + +## Commandes des équipements + +Chaque équipement dispose des commandes suivantes + +### Commandes actions + +- `On` Allumage de la guirlande (passage au mode "movie") +- ``Off`` Extinction de la guirlande (passage au mode "off") +- ``Luminosité`` Changement du niveau de luminosité de la guirlande (0-100) +- ``Animation`` Chargement vers la guirlande d'une animation préalablement capturée ou importée +- ``Refresh`` Rafraîchissement manuel des informations de la guirlande (état, luminosité) + +### Commandes infos + +Ces valeurs sont rafraîchies automatiquement à la fréquence choisie sur la page de configuration du plugin, ou manuellement via la commande ``Refresh`` si la mise à jour est désactivée. + +- ``Etat luminosité`` Valeur actuelle de la luminosité de la guirlande (0-100) +- ``Etat`` Mode courant de la guirlande (off/movie) + + + +## Gestion des animations + +Les animations disponibles pour un équipement (guirlandes) sont gérées par la fenêtre accessible en utilisant le bouton + +![Fenêtre de gestion des animations](https://kimagurefr.github.io/jeedom_docs/kTwinkly/images/animations.png) + +Depuis cette fenêtre, il est possible : + +- d'ajouter une animation depuis un fichier zip qui aurait préalablement été capture puis sauvegardé sur disque, en utilisant le bouton **Ajouter** +- de supprimer une animation de la liste, en cochant la case correspondante, puis en cliquant sur le bouton **Supprimer** +- de télécharger une animation préalablement capturée en cliquant sur l'icône à la fin de la ligne correspondante +- de changer le titre affiché dans la liste pour chaque animation. La valeur initiale est récupérée lors de la capture pour les guirlandes "gen 2" (compatibles "playlist"). Pour les guirlandes gen1, le nom n'est pas envoyé par l'application mobile, donc le GUID correspondant au nom du zip est affiché, il suffit de le remplacer par le nom choisi +- de réordonner la liste des animations via drag-and-drop, pour choisir l'ordre d'affichage dans la liste de l'équipement + + + +## Capture des animations + +Les animations (séquence d'allumage/extinction des leds de la guirlande dans les différentes couleurs), appelées "*movies*" dans l'API Twinkly, sont des fichiers binaires composés de multiples séquences de 3 octets indiquant l'intensité de rouge, vert et bleu pour chacune des leds de la guirlande. + +Les animations sont spécifiques à chaque guirlande et sont calculées lors de la phase d'analyse réalisée depuis l'application mobile Twinkly (capture de la disposition des leds via l'appareil photo du smartphone, puis calcul en local ou via le cloud Twinkly). + +C'est pourquoi il est nécessaire de récupérer ces fichiers binaires après chaque nouvelle installation de la guirlande, ou changement de disposition. + +Le principe utilisé par le plugin est de démarrer un proxy HTTP sur le serveur Jeedom, de configurer le smartphone pour qu'il utilise ce proxy, puis d'utiliser l'application mobile normalement pour envoyer l'animation vers une guirlande. Le proxy se chargera alors de capturer l'animation et les informations associées, et de les stocker sur le disque sous la forme d'un fichier zip. + +L'animation sera ensuite directement utilisable par le plugin et pourra être envoyée à volonté vers la guirlande. + +On ne peut capturer les animations que pour une seule guirlande à la fois. Pour capturer les animations pour plusieurs guirlandes, il faut répéter les opérations 2 à 5 ci-dessous pour chaque guirlande. + +Les étapes du processus de capture sont décrites ci-dessous. + +#### Etape 1 - Arrêt du rafraîchissement automatique + +Comme expliqué en [introduction](#information-importante), le contrôleur Twinkly est limité à un seul appareil de commande à la fois. + +Comme on doit utiliser l'application mobile dans la procédure ci-dessous, il est nécessaire d'interrompre temporairement la collecte automatique des données par le plugin, sous peine de déconnecter l'application mobile pendant son utilisation. + +Il suffit de décocher la case **Rafraîchissement auto** de l'équipement et de le sauvegarder avant de lancer le processus de capture, puis de rétablir cette option à la fin du processus. + +#### Etape 2 - Démarrage du proxy + +**Attention** : cette étape nécessite que l'installation des dépendances depuis la page de configuration du proxy ait été réalisée. Le processus de capture s'appuie sur l'outil *mitmdump* du projet [mitmproxy](https://mitmproxy.org/) qui est installé depuis les repos de la distribution Linux du serveur (Debian la plupart du temps). + +Il suffit de cliquer sur le bouton pour démarrer le proxy. + +Un message s'affiche avec les informations à utiliser pour configurer le proxy sur le smartphone (adresse IP et port). L'adresse IP est l'IP interne du serveur Jeedom. Le port est celui choisi dans la [page de configuration générale du plugin]( #installation-du-plugin), ou 14233 par défaut. + +![](http://kimagurefr.github.io/jeedom_kTwinklyDoc/images/proxy_demarre.png) + +#### Etape 3 - Configuration du smartphone + +##### Sur iOS + +- Aller dans Réglages > Wi-Fi > Cliquer sur l'icône ![i](https://kimagurefr.github.io/jeedom_kTwinklyDoc/images/info-circle.png) à côté du nom du réseau. + +![](https://kimagurefr.github.io/jeedom_docs/kTwinkly/images/proxy1.png) + +- En bas de la page, sélectionner "PROXY HTTP / Configurer le proxy" + +![](https://kimagurefr.github.io/jeedom_docs/kTwinkly/images/proxy2.png) + +- Puis dans la page de configuration, entrer les informations du proxy affichées sur l'écran du plugin + + ![](https://kimagurefr.github.io/jeedom_docs/kTwinkly/images/proxy3.png) + +- Enregistrer la configuration + +##### Sur Android + +En fonction versions d'Android et des différentes surcouches constructeur ou opérateurs, les écrans peuvent être différents sur votre propre smartphone, mais le principe devrait rester le même. + +- Accéder à la page des paramètres du réseau Wifi + + Paramètres wifi + +- Accéder aux paramètres avancés + + + +- Sélectionner l'entrée "Proxy" et choisir "Manuel" + + + +- Entrer les paramètres du proxy affichés sur la page du plugin + + + +- Enregistrer la configuration + +#### Etape 4 - Utilisation de l'app mobile Twinkly + +Il faut lancer l'application mobile Twinkly, de choisir les animations que vous souhaitez capturer dans la galerie et de l'envoyer vers le sapin. Attention, afficher l'animation sur l'écran de prévisualisation ne suffit pas, il faut vraiment télécharger l'animation vers la guirlande pour qu'elle puisse être capturée par le proxy. + + + +#### Etape 5 - Arrêt de la capture et récupération des fichiers + +Cliquer sur le bouton **Arrêter la capture** (voir le screenshot plus haut). + +Le plugin va récupérer toutes les animations envoyées vers la guirlande et les ajouter à la liste. + +Pour les guirlandes "gen2", le **titre** indiqué est celui fourni par Twinkly. Pour une guirlande "gen1", l'information n'est pas disponible, donc le titre par défaut est le nom du fichier capturé (un GUID). + +Dans les 2 cas, il est bien sûr possible de changer le titre (ne pas oublier de cliquer sur le bouton **Sauvegarder** pour conserver les modifications) + +Si aucune animation n'a pu être capturée, le message ci-dessous sera affiché. + +![](https://kimagurefr.github.io/jeedom_docs/kTwinkly/images/aucune_animation.png) + +Vérifiez que le proxy a correctement été configuré sur le smartphone et que avez bien envoyé l'animation vers la bonne guirlande. + +#### Etape 6 - Rétablissement du rafraîchissement automatique + +Vous pouvez désormais réactiver le rafraîchissement automatique des informations dans les paramètres de l'équipement. + +#### Etape 7 - Désactiver le proxy sur le smartphone + +N'oubliez pas de repasser le paramétrage du proxy sur "Aucun" ou "Désactivé" pour rétablir le fonctionnement normal de votre smartphone. Un oubli rendra l'accès wifi inopérant jusqu'à désactivation. \ No newline at end of file diff --git a/kTwinkly/images/animations.png b/kTwinkly/images/animations.png new file mode 100644 index 0000000..08e75d7 Binary files /dev/null and b/kTwinkly/images/animations.png differ diff --git a/kTwinkly/images/aucune_animation.png b/kTwinkly/images/aucune_animation.png new file mode 100644 index 0000000..0bca6ce Binary files /dev/null and b/kTwinkly/images/aucune_animation.png differ diff --git a/kTwinkly/images/bouton_animations.png b/kTwinkly/images/bouton_animations.png new file mode 100644 index 0000000..8b50db4 Binary files /dev/null and b/kTwinkly/images/bouton_animations.png differ diff --git a/kTwinkly/images/bouton_capture.png b/kTwinkly/images/bouton_capture.png new file mode 100644 index 0000000..d8f744e Binary files /dev/null and b/kTwinkly/images/bouton_capture.png differ diff --git a/kTwinkly/images/config_equipement.png b/kTwinkly/images/config_equipement.png new file mode 100644 index 0000000..4f54fee Binary files /dev/null and b/kTwinkly/images/config_equipement.png differ diff --git a/kTwinkly/images/config_plugin.png b/kTwinkly/images/config_plugin.png new file mode 100644 index 0000000..10d923a Binary files /dev/null and b/kTwinkly/images/config_plugin.png differ diff --git a/kTwinkly/images/info-circle.png b/kTwinkly/images/info-circle.png new file mode 100644 index 0000000..a23ac4e Binary files /dev/null and b/kTwinkly/images/info-circle.png differ diff --git a/kTwinkly/images/liste_guirlandes.png b/kTwinkly/images/liste_guirlandes.png new file mode 100644 index 0000000..df7882b Binary files /dev/null and b/kTwinkly/images/liste_guirlandes.png differ diff --git a/kTwinkly/images/movie-download.png b/kTwinkly/images/movie-download.png new file mode 100644 index 0000000..874b434 Binary files /dev/null and b/kTwinkly/images/movie-download.png differ diff --git a/kTwinkly/images/proxy-android1.png b/kTwinkly/images/proxy-android1.png new file mode 100644 index 0000000..596e516 Binary files /dev/null and b/kTwinkly/images/proxy-android1.png differ diff --git a/kTwinkly/images/proxy-android2.png b/kTwinkly/images/proxy-android2.png new file mode 100644 index 0000000..8532e83 Binary files /dev/null and b/kTwinkly/images/proxy-android2.png differ diff --git a/kTwinkly/images/proxy-android3.png b/kTwinkly/images/proxy-android3.png new file mode 100644 index 0000000..5331431 Binary files /dev/null and b/kTwinkly/images/proxy-android3.png differ diff --git a/kTwinkly/images/proxy-android4.png b/kTwinkly/images/proxy-android4.png new file mode 100644 index 0000000..b00d708 Binary files /dev/null and b/kTwinkly/images/proxy-android4.png differ diff --git a/kTwinkly/images/proxy1.png b/kTwinkly/images/proxy1.png new file mode 100644 index 0000000..3ad9d00 Binary files /dev/null and b/kTwinkly/images/proxy1.png differ diff --git a/kTwinkly/images/proxy2.png b/kTwinkly/images/proxy2.png new file mode 100644 index 0000000..e226fc9 Binary files /dev/null and b/kTwinkly/images/proxy2.png differ diff --git a/kTwinkly/images/proxy3.png b/kTwinkly/images/proxy3.png new file mode 100644 index 0000000..1c2d8e6 Binary files /dev/null and b/kTwinkly/images/proxy3.png differ diff --git a/kTwinkly/images/proxy_demarre.png b/kTwinkly/images/proxy_demarre.png new file mode 100644 index 0000000..99df2aa Binary files /dev/null and b/kTwinkly/images/proxy_demarre.png differ diff --git a/kTwinkly/images/recherche.png b/kTwinkly/images/recherche.png new file mode 100644 index 0000000..4223263 Binary files /dev/null and b/kTwinkly/images/recherche.png differ diff --git a/kTwinkly/index.html b/kTwinkly/index.html new file mode 100644 index 0000000..7b561f6 --- /dev/null +++ b/kTwinkly/index.html @@ -0,0 +1,3 @@ +