/* * 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 += '

'; // Check hide counts option var check_counts = $( 'span#hideCounts' ).hasClass( 'active' ) ? 'display: none;' : ''; var check_filters = $( 'span#hideFilters' ).hasClass( 'active' ) ? 'display: none;' : ''; // Hidden divs html += ''; html += ''; html += ''; html += ''; html += ''; html += '

'; html += '
'; html += '' + result.value + ''; html += ''; html += '(0 items)'; html += ''; html += '
'; html += '
'; html += 'settingsContainer Options'; html += 'file_uploadCollapse'; html += 'open_withMove'; // Three dot menu html += ''; html += '
'; html += '

'; // Rule list html += ''; html += '
'; // Append container to page $( 'div#rules_container' ).prepend( html ); // Rebuild array rebuildArray(); // Initialize sort initialize_sort(); initialize_multisort(); } }); }); // Edit container button $( document ).on( 'click', 'div.edit_container_div', function() { var thisEditContainer = $( this ); // Hide list item dropdown content $(thisEditContainer).parent().hide(); // Get container title, bold var getTitle = $( this ).parent().parent().siblings( 'div.container_title_left' ).children( 'span.group_name' ).html(); var getTitleBold = $( this ).parents( 'h4' ).siblings( 'input.title_bold' ).val(); // Get container color option var container_color = $( this ).parents( 'h4' ).siblings( 'input.container_color' ).val(); var container_opacity = $( this ).parents( 'h4' ).siblings( 'input.container_opacity' ).val(); var containerRgba = hexToRGB( container_color, container_opacity ); // Get font color option var font_color = $( this ).parents( 'h4' ).siblings( 'input.title_color' ).val(); var font_opacity = $( this ).parents( 'h4' ).siblings( 'input.title_opacity' ).val(); var fontRgba = hexToRGB( font_color, font_opacity ); // Define overlay html var editCont = ''; editCont += '

Use this panel to adjust the visual appearance of the container.

'; editCont += ""; // Only show title if not original rules container if( getTitle !== "Original Rules" ) { editCont += "" editCont += "" editCont += "" editCont += "" } editCont += ""; editCont += ""; editCont += ""; editCont += ""; editCont += ""; editCont += ""; editCont += ""; editCont += ""; editCont += ""; editCont += ""; editCont += ""; editCont += "
Title"; editCont += "
info Container Title is Required.
"; editCont += ""; editCont += "
Title Bold
Container Color
Font Color
"; 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 + "

Duplicating this rule only makes a duplicate in the context of this app. It does not actually duplicate the rule in the Rule Machine app.", icon: "info", iconColor: "#81bc00", showCancelButton: true, confirmButtonColor: "#81bc00", cancelButtonColor: "#d33", confirmButtonText: "Duplicate Rule", allowOutsideClick: false, allowEscapeKey: false, customClass: { popup: "headerSuccess" } }).then((result) => { if (result.isConfirmed) { // Clone rule and insert after itself var new_item = $( thisCopy ).parents( 'li' ).clone(); $( thisCopy ).parents( 'li' ).after( new_item ); // Adjust copy/delete icon new_item.find( 'span.copy_rule' ).remove(); var text = "delete_outlineRemove Duplicate Rule" $( text ).insertAfter( new_item.find( 'span.ruleViewLogs' ) ); // Adjust new icon color new_item.find( 'i.duplicate_rule' ).css({ 'color': iconColor, 'opacity': iconOpacity }); // Rebuild array rebuildArray(); } }); }); // Delete duplicate rule $( document ).on( 'click', 'span.delete_duplicate', function(e) { // Prevent highlighting active row e.stopPropagation(); // Get variables var thisDuplicate = $( this ); var dupeName = $( this ).parent().siblings( 'div.rule_title_left' ).children( 'span.rule_name' ).text(); Swal.fire({ titleText: "Remove Duplicate Rule?", html: "Removing: " + dupeName + "

Removing this duplicate rule only removes it in the context of this app. It does not actually remove the rule in the Rule Machine app.", icon: "question", iconColor: "#d33", showCancelButton: true, confirmButtonColor: "#81bc00", cancelButtonColor: "#d33", confirmButtonText: "Remove Rule", allowOutsideClick: false, allowEscapeKey: false, customClass: { popup: "headerError" } }).then((result) => { if (result.isConfirmed) { $( thisDuplicate ).parents( 'li' ).remove(); // Rebuild array rebuildArray(); } }); }); // View rule, view rule status, view rule logs $( document ).on( 'click', 'span.ruleView, span.ruleViewStatus, span.ruleViewLogs', function() { window.open( $( this ).attr( 'url' ) ); return false; }); /********************************************************* Sorting *********************************************************/ // Sort ascending $( document ).on( 'click', 'div.sortasc_container', function() { // Get list items var list = $( this ).parents( 'h4' ).siblings( 'ul.rulelist' ); var items = list.children( 'li' ).get(); // Sort items.sort( function( a, b ) { var a_sort = $( a ).find( 'span.rule_name' ).text().toUpperCase(); var b_sort = $( b ).find( 'span.rule_name' ).text().toUpperCase(); // Remove special characters a_sort = a_sort.replace( /[^\w\s]/gi, '' ); b_sort = b_sort.replace( /[^\w\s]/gi, '' ); return a_sort.localeCompare( b_sort ); }); // Append back to list $.each( items, function( idx, itm ) { list.append( itm ); }); // Rebuild array rebuildArray(); }); // Sort descending $( document ).on( 'click', 'div.sortdesc_container', function() { // Get list items var list = $( this ).parents( 'h4' ).siblings( 'ul.rulelist' ); var items = list.children( 'li' ).get(); // Sort items.sort( function( a, b ) { var b_sort = $( b ).find( 'span.rule_name' ).text().toUpperCase(); var a_sort = $( a ).find( 'span.rule_name' ).text().toUpperCase(); // Remove special characters b_sort = b_sort.replace( /[^\w\s]/gi, '' ); a_sort = a_sort.replace( /[^\w\s]/gi, '' ); return b_sort.localeCompare( a_sort ); }); // Append back to list $.each( items, function( idx, itm ) { list.append( itm ); }); // Rebuild array rebuildArray(); }); /********************************************************* Exporting and Importing *********************************************************/ // Export options $( document ).on( 'click', 'span#generate_export', function() { // Get options from hidden input var options = $( 'input#userArray' ).val(); // Place into textarea $( 'textarea#export_textarea' ).val( options ); }); // Copy export to clipboard $( document ).on( 'click', 'span#copy_export', function() { // Get export value var text = $( 'textarea#export_textarea' ).val(); // Hubitat does not always run over https. In order for copy to clipboard on http, need js helper const textArea = document.createElement( 'textarea' ); textArea.value = text; document.body.appendChild( textArea ); textArea.focus(); textArea.select(); // Try to copy to clipboard; alert if unsuccessful try { document.execCommand( 'copy' ); // Change tooltip text $( 'span#exportTooltip' ).text( 'Copied Successfully!' ); } catch (err) { Swal.fire({ titleText: "Clipboard Unavailable", text: "There is a problem with copying data to the clipboard. Try manually selecting the data, right-click and select Copy.", icon: "question", iconColor: "#d33", confirmButtonColor: "#81bc00", allowOutsideClick: false, allowEscapeKey: false, customClass: { popup: "headerError" } }); } // Remove temp textarea document.body.removeChild( textArea ); }); // Copy export change text $( document ).on( 'mouseout', 'span#copy_export', function() { $( 'span#exportTooltip' ).html( 'Copy to clipboard' ); }); // Import options $( document ).on( 'click', 'span#generate_import', function() { // Get import value var import_opts = $( 'textarea#import_textarea' ).val(); if( import_opts == '' ) { // Show import error $( 'div#importEmptyError' ).show(); $( 'textarea#import_textarea' ).css( 'border', '1px solid red' ); return false; } // Check if the import is parsable by json var json_check = true; try { var json = $.parseJSON( import_opts ); } catch( err ) { json_check = false; } if( ! json_check ) { // Show import error $( 'div#importMalformError' ).show(); $( 'textarea#import_textarea' ).css( 'border', '1px solid red' ); return false; } // Copy and paste into hidden input field $( 'input#userArray' ).val( import_opts ); // Click "Done" button $( 'button#btnDone' ).click(); }); // Remove import errors from textarea when focused $( document ).on( 'focus', 'textarea#import_textarea', function() { $( 'div#importEmptyError' ).hide(); $( 'div#importMalformError' ).hide(); $( 'textarea#import_textarea' ).css( 'border', '1px solid black' ); }); /********************************************************* Reset Options *********************************************************/ $( document ).on( 'click', 'span#reset_opts', function() { // Use a quick notice as a confirmation if( $( 'div#confirmReset' ).length == 0 ) { $( '
Clicking the "Reset Options" button once more will permanently delete any customizations.
' ).insertBefore( $(this) ); } else { // Get default setting var get_defaults = $( 'input#load_default_opts' ).val(); // Set default setting $( 'input#userArray' ).val( get_defaults ); // Click "Done" button $( 'button#btnDone' ).click(); } }); /********************************************************* Global and Container: Counts and Filters; Machine Names *********************************************************/ // Global option hide container counts $( 'span#hideCounts' ).click( function() { if( $( this ).hasClass( 'active' ) ) { $( 'span.group_rule_count' ).show(); $( 'span#hideCounts' ).removeClass( 'active' ); $( 'span#hideCounts span.tooltiptext' ).text( 'Hide Counts' ); } else { $( 'span.group_rule_count' ).hide(); $( 'span#hideCounts' ).addClass( 'active' ); $( 'span#hideCounts span.tooltiptext' ).text( 'Show Counts' ); } // Rebuild array rebuildArray(); }); // Global option hide container filters $( 'span#hideFilters' ).click( function() { if( $( this ).hasClass( 'active' ) ) { $( 'span.container_filter' ).show(); $( 'span#global_filter' ).show(); $( 'span#hideFilters' ).removeClass( 'active' ); $( 'span#hideFilters span.tooltiptext' ).text( 'Hide Filters' ); } else { $( 'span.container_filter' ).hide(); $( 'span#global_filter' ).hide(); $( 'span#hideFilters' ).addClass( 'active' ); $( 'span#hideFilters span.tooltiptext' ).text( 'Show Filters' ); } // Rebuild array rebuildArray(); }); // Global show/hide rule machine names $( document ).on( 'click', 'span#hideMachines', function() { if( $( this ).hasClass( 'active' ) ) { $( 'ul.rulelist span.ruleType' ).show(); $( 'span#hideMachines' ).removeClass( 'active' ); $( 'span#hideMachines span.tooltiptext' ).text( 'Hide Machine Names' ); } else { $( 'ul.rulelist span.ruleType' ).hide(); $( 'span#hideMachines' ).addClass( 'active' ); $( 'span#hideMachines span.tooltiptext' ).text( 'Show Machine Names' ); } // Rebuild array rebuildArray(); }); // Filter global rules $( document ).on( 'keyup', 'span#global_filter', function() { // Get filter value var filterVal = $( this ).children('input').val(); // Check if there is a value and add highlight color if( filterVal !== '' ) { $( this ).children( 'input' ).addClass( 'active' ); } else { $( this ).children( 'input' ).removeClass( 'active' ); } // Show or hide rules $( 'ul.rulelist' ).each( function() { $( this ).children().each(function() { var thisName = $(this).find( 'span.rule_name' ).text(); if ( thisName.toLowerCase().indexOf( filterVal ) >= 0 ) { $( this ).show(); } else { $( this ).hide(); } }); // Replace rule counts var ruleCount = $( this ).children( ':visible' ).length; var items = ruleCount == 1 ? 'item' : 'items'; $( this ).siblings( 'h4' ).find( 'span.group_rule_count' ).html( '(' + ruleCount + ' ' + items + ')' ); }); }); // Filter container rules $( document ).on( 'keyup', 'span.container_filter', function() { // Get filter value var filterVal = $( this ).children('input').val(); // Check if there is a value and add highlight color if( filterVal !== '' ) { $( this ).children( 'input' ).addClass( 'active' ); } else { $( this ).children( 'input' ).removeClass( 'active' ); } // Show or hide rules $( this ).parents( 'h4' ).siblings( 'ul.rulelist' ).children().each( function() { var thisName = $(this).find( 'span.rule_name' ).text(); if ( thisName.toLowerCase().indexOf( filterVal ) >= 0 ) { $( this ).show(); } else { $( this ).hide(); } }); // Replace rule counts var ruleCount = $( this ).parents( 'h4' ).siblings( 'ul.rulelist' ).children( ':visible' ).length; var items = ruleCount == 1 ? 'item' : 'items'; $( this ).siblings( 'span.group_rule_count' ).html( '(' + ruleCount + ' ' + items + ')' ); }); /********************************************************* Build User Array: Updates options to hidden input field *********************************************************/ function rebuildArray() { // Define base array var rb_array = {}; // Push global options rb_array.hide_counts = $( 'span#hideCounts' ).hasClass( 'active' ) ? 'true' : 'false'; rb_array.hide_filters = $( 'span#hideFilters' ).hasClass( 'active' ) ? 'true' : 'false'; rb_array.hide_machines = $( 'span#hideMachines' ).hasClass( 'active' ) ? 'true' : 'false'; rb_array.welcome_nag = $( 'input#welcome_nag' ).val(); rb_array.rule_machines = JSON.parse( $( 'input#ruleMachines' ).val() ); rb_array.check_machines = JSON.parse( $( 'input#checkMachines' ).val() ); rb_array.containers = []; // Loop each container $( 'div.rule_container' ).each( function() { // Create container array and populate var title = $( this ).children( 'h4' ).find( 'span.group_name' ).text(); var this_array = {}; this_array.name = title; this_array.slug = string_to_slug( title ); this_array.title_color = $( this ).children( 'input.title_color' ).val(); this_array.title_opacity = $( this ).children( 'input.title_opacity' ).val(); this_array.title_bold = $( this ).children( 'input.title_bold' ).val(); this_array.container_color = $( this ).children( 'input.container_color' ).val(); this_array.container_opacity = $( this ).children( 'input.container_opacity' ).val(); this_array.visible = $( this ).children( 'ul' ).is( ':visible' ); // Count rules in this container and update on page var count = $( this ).find( 'ul.rulelist' ).children().not( '.machineHidden' ).length; var items = count == 1 ? 'item' : 'items'; $( this ).children( 'h4' ).find( 'span.group_rule_count' ).html( '(' + count + ' ' + items + ')' ); // Populate all rules this_array.rules = []; $( this ).children( 'ul' ).children( 'li' ).each( function(i, v) { this_array.rules.push( v.id ); }); // Push this container to base array rb_array.containers.push( this_array ); }); // Populate hidden input with new user array $( 'input#userArray' ).val( JSON.stringify( rb_array ) ); // Adjust list classes on duplicate items $( 'span.delete_duplicate' ).each( function() { $( this ).parents( 'li' ).addClass( 'duplicate' ); }); } /********************************************************* Page Options Overlay *********************************************************/ // Toggle main page options overlay $( document ).on( 'click', 'span#options_panel', function() { var html = ''; html += '
'; html += '

Global Options

'; html += '
'; html += '

Select which rules to display in the Rule Machine Manager main application.

'; html += "
"; // Get all default machine types; and loop for input fields var machineTypes = JSON.parse( $( 'input#load_default_opts' ).val() ).check_machines; $( machineTypes ).each( function( i, v ) { html += "
"; html += ''; html += ''; html += "
"; }); html += "
"; html += '
'; html += '

Export Options

'; html += '
'; 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 += '

Import Options

'; 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 += '
info Import content cannot be empty.
'; html += '
info Import content is malformed.
'; html += ""; html += "import_export Import Options"; html += '
'; html += '

Reset Options

'; html += '
'; 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 += '

'; html += "restart_alt Reset Options" html += '

'; html += '
'; html += '
'; Swal.fire({ width: '50em', titleText: "Options Panel", html: html, allowOutsideClick: false, allowEscapeKey: false, customClass: { popup: "headerSuccess", htmlContainer: "alignLeft" }, confirmButtonColor: "#81bc00", showCancelButton: true, cancelButtonColor: "#d33", cancelButtonText: "Dismiss", didOpen: function() { // Instantiate jquery accordian $( "#overlayAccordian" ).accordion({ heightStyle: 'content' }); // Instantiate jquery checkbox radio $( 'input.mainOptsRadio').checkboxradio(); // Populate rule machines checkboxes var machines = $( 'input#ruleMachines' ).val(); machines = JSON.parse( machines ); // Loop each radio machine type and check if checked $( 'input.mainOptsRadio' ).each( function() { if( $.inArray( $( this ).val(), machines ) > -1 ) { $( this ).prop( "checked", true ).checkboxradio( "refresh" ); } }); } }).then((result) => { if (result.isConfirmed) { // Build array names list var ruleNames = []; // Loop each machine type radio selection $( 'input.mainOptsRadio' ).each( function() { // If this machine type is checked if( $( this ).prop( "checked" ) == true ) { // Add to array names list ruleNames.push( $( this ).val() ); // Show page elements $( 'li.' + $( this ).val() ).removeClass( 'machineHidden' ).show(); } else { // Hide page elements $( 'li.' + $( this ).val() ).addClass( 'machineHidden' ).hide(); } }); // Put array names list into input array in page $( 'input#ruleMachines' ).val( JSON.stringify( ruleNames ) ); // Rebuild Array rebuildArray(); } }); }); /********************************************************* Helper Functions *********************************************************/ // Build slugs from friendly names function string_to_slug( str ) { // Trim string and cast to lowercase str = str.replace(/^\s+|\s+$/g, ''); str = str.toLowerCase(); // Remove accents, swap ñ for n, etc var from = "àáäâèéëêìíïîòóöôùúüûñç·/_,:;"; var to = "aaaaeeeeiiiioooouuuunc------"; for (var i=0, l=from.length ; i