/* * Version 3.2 */ jQuery( document ).ready( function( $ ) { /********************************************************* Initial Page Stuff *********************************************************/ // Initial rulelist sortable initialize_sort(); initialize_multisort(); // Rebuild array on page load (if not updating settings) if( ! $( 'div.preventRebuildArray' ).length ) { rebuildArray(); } /********************************************************* Quick Save Options *********************************************************/ $( 'span#quick_save' ).click( function() { var thisSave = $( this ); // Get variables from page var accessToken = $( 'input#accessToken' ).val(); var appID = $( 'input#appID' ).val(); var appState = $( 'input#appState' ).val(); var userOpts = $( 'input#userArray' ).val(); // Check if app has been installed first if( appState == 'INCOMPLETE' ) { // Alert user to first save the app Swal.fire({ titleText: "App Not Installed", html: "
To use this feature; the app must first be installed. Please close this window, and click \"Done\" to save the app.
", icon: "warning", iconColor: "#81bc00", confirmButtonColor: "#81bc00", allowOutsideClick: false, allowEscapeKey: false, customClass: { popup: "headerSuccess", htmlContainer: "alignLeft" } }); return false; } // If app is installed but an access token was not generated if( appState == 'COMPLETE' && ( ! accessToken || accessToken == 'null' ) ) { // Alert user on how to enable oauth for the app Swal.fire({ titleText: "Authentication Required", html: "To use this feature; oAuth must be enabled for this application.
To enable oAuth:
Finally, reload this page and the feature will become available.
Learn more about oAuth with Hubitat Applications
NOTE: If this message keeps displaying even though oAuth has been enabled; please click the \"Done\" button on the Rule Machine Manager page.
", icon: "warning", iconColor: "#81bc00", confirmButtonColor: "#81bc00", allowOutsideClick: false, allowEscapeKey: false, customClass: { popup: "headerSuccess", htmlContainer: "alignLeft" } }); return false; } // Change icon to loader $( this ).html( 'autorenew' ); // Fire endpoint request with page settings array $.ajax({ url: "/apps/api/" + appID + "/updateSettings?access_token=" + accessToken, method: "POST", headers: { "Access-Control-Allow-Origin": "*", "Authorization": "Bearer " + accessToken, }, data: { 'userOpts': userOpts }, success: function( response ) { // Options were updated successfully if( response.status == 'success' ) { // Remove any page notices $( 'div.page_notice' ).slideToggle(); // Define toast const Toast = Swal.mixin({ toast: true, position: "top-end", showConfirmButton: false, timer: 3000, timerProgressBar: true, didOpen: (toast) => { toast.onmouseenter = Swal.stopTimer; toast.onmouseleave = Swal.resumeTimer; } }); Toast.fire({ icon: "success", iconColor: "#81bc00", title: "Options Saved Successfully" }); } }, error: function( xhr, textStatus, error ) { // Error updating options Swal.fire({ titleText: "GET Request Error", html: "An error was encountered while attempting the GET request.
XHR Status Text: " + xhr.statusText + "
Text Status: " + textStatus + "
Error: " + error,
icon: "warning",
iconColor: "#d33",
confirmButtonColor: "#81bc00",
allowOutsideClick: false,
allowEscapeKey: false,
customClass: { popup: "headerError", htmlContainer: "alignLeft" }
});
},
complete: function() {
// Redisplay icon
$( thisSave ).html( 'rocketQuick Save' );
}
});
});
/*********************************************************
Containers: Create, Edit and Delete
*********************************************************/
// Create container button
$( "span#create_group_button" ).click( function() {
Swal.fire({
titleText: "Create New Container",
html: "Please first enter a valid container name.",
input: "text",
inputPlaceholder: "Container Name...",
icon: "question",
iconColor: "#81bc00",
confirmButtonColor: "#81bc00",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonText: "Create",
allowOutsideClick: false,
allowEscapeKey: false,
customClass: { popup: "headerSuccess" },
willOpen: (value) => {
// Change okay button type from button to submit; otherwise reloads page
$( value ).find( 'button.swal2-confirm' ).removeAttr("type").attr("type", "submit");
},
inputValidator: (value) => {
// Validate input field
if ( ! value ) { return "Please enter a valid container name."; }
}
}).then((result) => {
if (result.isConfirmed) {
var html = '';
html += '
Use this panel to adjust the visual appearance of the container.
'; editCont += "| Title | " editCont += "";
editCont += " info Container Title is Required. ";
editCont += "";
editCont += " | "
editCont += "|
| Title Bold | ||
| Container Color | "; editCont += ""; editCont += " | "; editCont += " |
| Font Color | "; editCont += ""; editCont += " | "; editCont += " |
NOTE: Remember to click "Done" on the main page after making any modifications.
'; // Define overlay hidden inputs editCont += ''; editCont += ''; editCont += ''; editCont += ''; // Open overlay Swal.fire({ titleText: "Edit Container", html: editCont, confirmButtonColor: "#81bc00", showCancelButton: true, cancelButtonColor: "#d33", confirmButtonText: "Apply", allowOutsideClick: false, allowEscapeKey: false, customClass: { popup: "headerSuccess", htmlContainer: "alignLeft" }, willOpen: (value) => { // Change okay button type from button to submit; otherwise reloads page $( value ).find( 'button.swal2-confirm' ).removeAttr("type").attr("type", "submit"); }, preConfirm: () => { // If not original rules container; check for title if( getTitle != "Original Rules" ) { var checkTitle = $( 'input#overlayContainerTitle' ).val(); if( ! checkTitle || checkTitle == "" ) { // Show input validation error $( 'div#overlayTitleError' ).show(); $( 'input#overlayContainerTitle' ).css( 'border', '1px solid red'); // Prevent overlay from closing return false; } } } }).then((result) => { if (result.isConfirmed) { // Get values from overlay form var newContainerTitle = getTitle != "Original Rules" ? $( 'input#overlayContainerTitle' ).val() : getTitle; var newContainerColor = $( 'input#overlayContainerColor' ).val(); var newContainerOpacity = $( 'input#overlayContainerOpacity' ).val(); var newContainerRgba = hexToRGB( newContainerColor, newContainerOpacity ); var newContainerBold = $( 'input#overlayTitleBold' ).prop('checked'); var newFontColor = $( 'input#overlayFontColor' ).val(); var newFontOpacity = $( 'input#overlayFontOpacity' ).val(); var newFontWeight = newContainerBold == true ? 'bold' : 'normal'; // Populate main page hidden input fields for array rebuild $( thisEditContainer ).parents( 'h4' ).siblings( 'input.container_color' ).val( newContainerColor ); $( thisEditContainer ).parents( 'h4' ).siblings( 'input.container_opacity' ).val( newContainerOpacity ); $( thisEditContainer ).parents( 'h4' ).siblings( 'input.title_bold' ).val( newContainerBold == true ? 'true' : 'false' ); $( thisEditContainer ).parents( 'h4' ).siblings( 'input.title_color' ).val( newFontColor ); $( thisEditContainer ).parents( 'h4' ).siblings( 'input.title_opacity' ).val( newFontOpacity ); // Update page container background color $( thisEditContainer ).parents( 'div.rule_container' ).css({ 'background-color': newContainerRgba }); // Update page container name, count and icons $( thisEditContainer ).parent().parent().siblings( 'div.container_title_left' ).children( 'span.group_name' ).html( newContainerTitle ).css({ 'color': newFontColor, 'font-weight': newFontWeight, 'opacity': newFontOpacity }); $( thisEditContainer ).parent().parent().siblings( 'div.container_title_left' ).children( 'span.group_rule_count' ).css({ 'color': newFontColor, 'font-weight': newFontWeight, 'opacity': newFontOpacity }); // Update page container li elements $( thisEditContainer ).parents( 'h4' ).siblings( 'ul.rulelist' ).children( 'li' ).find( 'span.rule_name' ).css({ 'color': newFontColor, 'opacity': newFontOpacity }); $( thisEditContainer ).parents( 'h4' ).siblings( 'ul.rulelist' ).children( 'li' ).find( 'i' ).css({ 'color': newFontColor, 'opacity': newFontOpacity }); $( thisEditContainer ).parent().parent().find( 'i' ).css({ 'color': newFontColor, 'opacity': newFontOpacity }); // Rebuild array rebuildArray(); } }); // Set container background color picker in overlay $(".cont_bg_color_picker").spectrum({ color: containerRgba, showInitial: true, showPaletteOnly: true, togglePaletteOnly: true, togglePaletteMoreText: 'Show More', togglePaletteLessText: 'Show Less', chooseText: 'Choose', cancelText: 'Cancel', hideAfterPaletteSelect: true, showAlpha: true, palette: [ ["#000","#444","#666","#999","#ccc","#eee","#f3f3f3","#fff"], ["#f00","#f90","#ff0","#0f0","#0ff","#00f","#90f","#f0f"], ["#f4cccc","#fce5cd","#fff2cc","#d9ead3","#d0e0e3","#cfe2f3","#d9d2e9","#ead1dc"], ["#ea9999","#f9cb9c","#ffe599","#b6d7a8","#a2c4c9","#9fc5e8","#b4a7d6","#d5a6bd"], ["#e06666","#f6b26b","#ffd966","#93c47d","#76a5af","#6fa8dc","#8e7cc3","#c27ba0"], ["#c00","#e69138","#f1c232","#6aa84f","#45818e","#3d85c6","#674ea7","#a64d79"], ["#900","#b45f06","#bf9000","#38761d","#134f5c","#0b5394","#351c75","#741b47"], ["#600","#783f04","#7f6000","#274e13","#0c343d","#073763","#20124d","#4c1130"] ], change: function( color ) { // Populate overlay hidden input fields for later consumption $( this ).parents( 'table' ).siblings( 'input#overlayContainerColor' ).val( color.toHexString() ); $( this ).parents( 'table' ).siblings( 'input#overlayContainerOpacity' ).val( color.getAlpha() ); } }); // Set font color picker in overlay $(".font_color_picker").spectrum({ color: fontRgba, showInitial: true, showPaletteOnly: true, togglePaletteOnly: true, togglePaletteMoreText: 'Show More', togglePaletteLessText: 'Show Less', chooseText: 'Choose', cancelText: 'Cancel', hideAfterPaletteSelect: true, showAlpha: true, palette: [ ["#000","#444","#666","#999","#ccc","#eee","#f3f3f3","#fff"], ["#f00","#f90","#ff0","#0f0","#0ff","#00f","#90f","#f0f"], ["#f4cccc","#fce5cd","#fff2cc","#d9ead3","#d0e0e3","#cfe2f3","#d9d2e9","#ead1dc"], ["#ea9999","#f9cb9c","#ffe599","#b6d7a8","#a2c4c9","#9fc5e8","#b4a7d6","#d5a6bd"], ["#e06666","#f6b26b","#ffd966","#93c47d","#76a5af","#6fa8dc","#8e7cc3","#c27ba0"], ["#c00","#e69138","#f1c232","#6aa84f","#45818e","#3d85c6","#674ea7","#a64d79"], ["#900","#b45f06","#bf9000","#38761d","#134f5c","#0b5394","#351c75","#741b47"], ["#600","#783f04","#7f6000","#274e13","#0c343d","#073763","#20124d","#4c1130"] ], change: function( color ) { // Populate hidden input field for later consumption $( this ).parents( 'table' ).siblings( 'input#overlayFontColor' ).val( color.toHexString() ); $( this ).parents( 'table' ).siblings( 'input#overlayFontOpacity' ).val( color.getAlpha() ); } }); }); // Delete container button $( document ).on( 'click', 'div.delete_container', function() { var this_delete = $( this ); Swal.fire({ titleText: "Confirm Container Deletion", text: "Deleting this container will move all of this containers rules back to the Original Rules container.", icon: "question", iconColor: "#d33", showCancelButton: true, confirmButtonColor: "#81bc00", cancelButtonColor: "#d33", confirmButtonText: "Delete", allowOutsideClick: false, allowEscapeKey: false, customClass: { popup: "headerError" } }).then((result) => { if (result.isConfirmed) { // Check if any rules exist in container var check_rules = $( this_delete ).parents( 'h4.container_title_h4' ).siblings( 'ul' ).children(); // If rules are found if( check_rules.length !== 0 ) { // Copy rules and append to original rules container var copy_html = $( this_delete ).parents( 'h4.container_title_h4' ).siblings( 'ul' ).html(); $( 'div#original-rules' ).find( 'ul.rulelist' ).append( copy_html ); // Get original container colors var deleteColor = $( 'div#original-rules' ).children( 'input.title_color' ).val(); var deleteOpacity = $( 'div#original-rules' ).children( 'input.title_opacity' ).val(); // Apply original container colors to elements $( 'div#original-rules' ).find( 'ul.rulelist' ).children( 'li' ).find( 'span.rule_name' ).css({ 'color': deleteColor, 'opacity': deleteOpacity }); $( 'div#original-rules' ).find( 'ul.rulelist' ).children( 'li' ).find( 'i' ).css({ 'color': deleteColor, 'opacity': deleteOpacity }); } // Remove container $( this_delete ).parents( 'div.rule_container' ).remove(); // Rebuild array rebuildArray(); // Define toast const Toast = Swal.mixin({ toast: true, position: "top-end", showConfirmButton: false, timer: 3000, timerProgressBar: true, didOpen: (toast) => { toast.onmouseenter = Swal.stopTimer; toast.onmouseleave = Swal.resumeTimer; } }); Toast.fire({ icon: "success", iconColor: "#81bc00", title: "Container Deleted Successfully" }); } }); }); /********************************************************* Container Helpers *********************************************************/ // Toggle container expand/collapse $( document ).on( 'click', 'i.toggleContainer', function() { // Toggle list $( this ).parents( 'h4' ).siblings( 'ul' ).toggle(); // Get color and opacity from sister element icon (hover interferes with original color) var thisColor = $( this ).parent().siblings( 'span.moreOpts' ).children( 'i' ).css( 'color' ); var thisOpacity = $( this ).parent().siblings( 'span.moreOpts' ).children( 'i' ).css( 'opacity' ); // Define additional styles var thisStyle = "color:" + thisColor + ";opacity:" + thisOpacity + ";"; // Determine which icon and text to display if( $( this ).text() == 'file_upload' ) { $( this ).parent().html( 'file_downloadExpand' ); } else if( $( this ).text() == 'file_download' ) { $( this ).parent().html( 'file_uploadCollapse' ); } // Rebuild array rebuildArray(); }); // Toggle container options dropdown $( document ).on( 'click', 'i.submenu', function() { // Close all dropdowns other than this; and toggle this dropdown $( 'div.dropdown-content' ).not( $( this ).parent().siblings( 'div.dropdown-content' ) ).hide(); $( this ).parent().siblings( 'div.dropdown-content' ).toggle(); $( this ).toggleClass( 'active' ); // Rotate menu icon var css = ! $( this ).parent().siblings( 'div.dropdown-content' ).is( ':visible' ) ? '0deg' : '90deg'; $( this ).css({ 'rotate': css, transition : 'rotate 0.3s ease-in-out' }); }); // Close container options dropdown if clicking anywhere outside of container window.onclick = function(event) { if( ! event.target.matches( '.submenu' ) ) { $( 'div.dropdown-content' ).hide(); $( 'i.submenu' ).css( 'rotate', '0deg' ); $( 'i.submenu' ).removeClass( 'active' ); } } // Overlay keyup for title validation $( document ).on( 'focus', 'input#overlayContainerTitle', function() { $( this ).css( 'border', '1px solid #CCC' ); $( this ).siblings( 'div#overlayTitleError' ).hide(); }); // Reset background color $( document ).on ( 'click', 'button#reset_background_color', function() { // Set color picker $(".cont_bg_color_picker").spectrum( "set", "#FFFFFF" ); // Set overlay hidden input fields $( 'input#overlayContainerColor' ).val( '#FFFFFF' ); $( 'input#overlayContainerOpacity' ).val( '1' ); }); // Reset font color $( document ).on( 'click', 'button#reset_font_color', function() { // Set color picker $(".font_color_picker").spectrum( "set", "#000000" ); // Set overlay hidden input fields $( 'input#overlayFontColor' ).val( '#000000' ); $( 'input#overlayFontOpacity' ).val( '1' ); }); /********************************************************* Container Dragging and Dropping *********************************************************/ // Initialize drag/drop on containers function initialize_sort() { // Sort main containers $( "div#rules_container" ).sortable({ handle: '.drag_handle', opacity: 0.5, axis: 'y', tolerance: 'pointer', change: function(event, ui) { ui.placeholder.css({visibility: 'visible', border : '1px solid #1a77c9', height: '80px'}); }, stop: function() { // Rebuild Array rebuildArray(); } }); } // Initialize drag/drop on rule lists function initialize_multisort() { $('ul.rulelist').multipleSortable({ items: 'li', selectedClass: 'selected', connectWith: 'ul.rulelist', container: '#rules_container', stop: function( event, ui, items ) { // Set background color of dropped rules to match container var fontColor = $( items[0] ).parent().siblings( 'h4' ).find( 'span.group_name' ).css( 'color' ); var fontOpacity = $( items[0] ).parent().siblings( 'h4' ).find( 'span.group_name' ).css( 'opacity' ); // Set font color on each dropped list item $( items ).each( function() { $( this).find( 'span.rule_name' ).css({ 'color': fontColor, 'opacity': fontOpacity }); $( this).find( 'i' ).css({ 'color': fontColor, 'opacity': fontOpacity }); }); // Rebuild Array rebuildArray(); } }); } // Set selected classes on rule list items for multi sortable function $( document ).on( 'click', 'ul.rulelist li.rule', function(e) { // If this list item has selected class if( $(this).hasClass( 'selected' ) ) { // Remove selected class $( this ).removeClass( 'selected' ); } // Else this list item does not have selected class else { // If ctrl key is held; add to selected if( e.ctrlKey == true || e.metaKey == true ) { // Toggle selected class (add to items) $( this ).toggleClass( 'selected' ); } // Else ctrl key was not held else { // Remove selected class from all items, and just select this $( 'ul.rulelist li.selected' ).removeClass( 'selected' ); $( this ).toggleClass( 'selected' ); } } }); /********************************************************* Rules: Copy, Delete, View *********************************************************/ // Copy rule $( document ).on( 'click', 'i.copy_rule', function(e) { // Prevent highlighting active row e.stopPropagation(); // Get variables var thisCopy = $( this ); var ruleName = $( this ).parent().parent().siblings( 'div.rule_title_left' ).children( 'span.rule_name' ).text(); // Get icon color from sibling var iconColor = $( this ).parent().siblings( 'span.ruleViewLogs' ).children( 'i' ).css( 'color' ); var iconOpacity = $( this ).parent().siblings( 'span.ruleViewLogs' ).children( 'i' ).css( 'opacity' ); // Fire confirmation alert Swal.fire({ titleText: "Duplicate Rule?", html: "Duplicating: " + ruleName + "Select which rules to display in the Rule Machine Manager main application.
'; html += "'; html += "Click the \"Export Options\" button to generate the app settings into the textarea. "; html += "Copy/paste the text and save in a text document for importing at a later time."; html += '
'; html += '"Copy Options" will copy the text to the browser clipboard which can then be pasted into a document.
'; html += ''; html += ''; html += 'import_export Export Options'; html += ""; html += ""; html += "Copy to clipboard"; html += "content_copy Copy Options"; html += ""; html += '
'; html += '';
html += "Paste the contents from a previous export into the textarea.
"
html += "Click Import Options to populate the settings.
"
html += "NOTE: The page will reload automatically after clicking Import Options to save the settings."
html += '
';
html += "Use this tool to restore the app back to initial default values.
"
html += "This can be useful if something is buggy, or to start a clean slate.
"
html += "Note: Clicking this button will erase any customizations and reload the page."
html += '
'; html += "restart_alt Reset Options" html += '
'; html += 'Welcome to the Rule Machine Manager Application! If this is the first time installing the app; please don\'t forget to press the "Done" button when finished.
'; html += 'Rule Machine Manager (RMM) is a tool used to group and organize the rules created in the Rule Machine app. It allows creation of containers, drag/drop organizing, and many other features.
'; html += 'RMM does not in any way interfere with the rules created, saved, and administered by the Rule Machine app. It only provides a visual interface to help organize and more quickly identify a rule.
'; html += 'Click any of the following icons to learn more about Rule Machine Managers features.
'; html += ''
html += 'Note: This modal can be re-opened anytime by clicking the "Help" icon on the top right area of the page.
';
html += 'Note: Vist the Hubitat Community Thread for more information and to request assistance.'
html += '
Rule Machine Manager provides many options to enhance the user experience.
'; html += '';
html += 'Global Options - Enable/Disable which rule machine types are dislayed in the containers.
';
html += 'Export Options - Creates a text file of the application user settings.
';
html += 'Import Options - Allows importing a previously exported text file.
';
html += 'Reset Options - Restores the application back to original default settings.';
html += '
';
html += 'Edit Title - Edit the title of the container.
';
html += 'Title Bold - Toggles the boldness of the title of the container.
';
html += 'Container Color - Sets the background color of the container.
';
html += 'Font Color - Sets the font color of the container.';
html += '
There are two types of filters which can be used with Rule Machine Manager; filtering rules by typing keywords; or by enabling/disabling a rule machine type.
'; html += 'Global Filter - At the top of the page is the global filter. Typing keywords here will filter all containers at the same time.
'; html += 'Container Filter - Each container has its own filter. Typing keywords here will filter just the associated container.
'; html += 'Rule Machine Manager uses rules from many different apps (Rule Machine, Room Lighting, etc). Each of these machine types are stored as options; and can be enabled/disabled from the "App Options" -> "Global Options" section.
'; html += 'Disabling a machine type will hide all rules associated with that machine type; while enabling one will perform the reverse.
'; html += 'A new quick save feature allows saving the application options without leaving/reloading the page.
'; html += 'This feature requires that oAuth be enabled on this application. oAuth is a security feature which uses a unique token to allow endpoint requests. This endpoint request is then used to send the plugin settings back to itself; allowing saving without reloading the page.
'; html += 'Close this window and click the rocketship icon (rocket) on the page to get started.
'; html += 'Learn more about Enabling oAuth on Hubitat Applications.
'; html += 'This app uses rules from many different rule managers. To enable/disable a particular rule type displaying on the page; visit "App Options" -> "Global Options".
'; html += 'Currently Supported Rule Types:
Containers are where rules are stored and listed. RMM begins with one container already defined; the "Original Rules" container. This container houses all rules which have already been created using the Rule Manager app.
'; html += 'New containers can be created and added to the page. Creating new containers helps with organization. The rules from the "Original Rules" container can then be moved to any other container on the page.
'; html += 'Containers are sortable. Meaning, any container can be moved around on the page. Organize the containers in a way which best suits the viewer. After saving the app, the order of containers is preserved.
'; html += 'Containers can be collapsed and expanded. Collapsing a container hides all the rules inside the container. This may be useful for containers whose rules are seldomly used, freeing up valuable working space. Expanding a container redisplays the hidden rules.
'; html += 'Containers can be deleted; with the exception of the "Original Rules" container. Deleting a container with rules inside will delete the container, and move the rules back into the "Original Rules" container.
'; html += 'All rules appearing in RMM are simply placeholders. There is no chance of corrupting an actual Rule Manager rule. This app calls an endpoint which lists all the rule names; and the rest of the interface is built using that data.
'; html += 'Rules will first appear in the "Original Rules" container (assuming some have been created using the Rule Manager app). Rules are draggable and sortable, and can be moved around into any order.
'; html += 'Rules can be moved into other containers. This allows for greater flexibility in rule organization.
'; html += 'Select multiple rules for moving by holding down the control key while clicking the rules to be moved.
'; html += 'Rules can be duplicated; in the sense a duplicate placeholder is created in the app. Duplicating rules allows a rule to appear in multiple containers. Duplicate rules can be deleted; which only deletes the reference to the rule, not the actual rule itself.
'; html += 'Rules can be automatically sorted inside their respective containers. Use the icons for the container to sort the rules in alphabetical order.
'; html += 'Clicking the "View Rule" icon will open the rule in the Rule Manager app (edit mode) in a new browser window.
'; html += 'RMM allows styling of the container area. Containers can have their appearance altered; helping to visually identify certain containers.
'; html += 'Styling the containers can help facilitate quick visual identification of frequently used rules. Use color combinations which can facilitate visual organization.
'; html += 'All styling modifications are saved in the app settings and will be re-applied each time the app is loaded.
'; html += 'RMM is packed full of fun features to keep any user busy for hours. Here are just a few:
'; html += '