mainNgApp.directive('colorpicker', function($compile) {
    return {
    restrict: "E",
    require: 'ngModel',
    replace: true,
    template: '<button class="colorPicker btn"></button>',
    link: function(scope, element, attrs, ngModel) {
        var changeCss = function(color){
            var normalizedColor = drawingOptionHelpers.normalizeColor(color)
            var colorCode = colorsys.rgb2Hex(normalizedColor.r, normalizedColor.g, normalizedColor.b);
            var changes = {
                backgroundColor: colorCode,
            }
            element.css(changes)
            //$compile(element.contents())(scope.$new());
        }
        element.bind('click', function(){
            var normalizedColor = colorLogic.normalizeColor(ngModel.$modelValue);
            (preferences.useSystemColorPicker?AEcolorPickerPromise:pickColor)(normalizedColor)
            .then(function(color){
                scope.$apply(function() {
                    changeCss(color);
                    ngModel.$setViewValue(color);
                });
            })
            .catch(function(reason){
                //no color picked
            })

        });
        // update the color picker whenever the value on the scope changes
        ngModel.$render = function() {
            changeCss(ngModel.$modelValue);
            element.val(ngModel.$modelValue);
            element.change();
        };
    }
  }
})

mainNgApp.directive('folderpicker', function($compile) {
    return {
    restrict: "E",
    require: 'ngModel',
    replace: true,
    template: '<button class="btn btn-secondary btn-sm">...</button>',
    link: function(scope, element, attrs, ngModel) {
        element.bind('click', function(){
            var prompt = attrs.prompt || "";
            AEfolderPickerPromise(prompt, attrs.pathid, ngModel.$modelValue)
            .then(function(path){
                scope.$apply(function() {
                    ngModel.$setViewValue(path);
                });
            })
            .catch(function(reason){})
        });
    }
  }
})
mainNgApp.directive('filepicker', function($compile) {
    return {
    restrict: "E",
    require: 'ngModel',
    replace: true,
    template: '<button class="btn btn-secondary btn-sm">...</button>',
    link: function(scope, element, attrs, ngModel) {
        element.bind('click', function(){
            var prompt = attrs.prompt || "";
            var filter = attrs.filter && attrs.filter.split(",") || [];
            AEfilePickerPromise(prompt, filter, false, attrs.pathid, ngModel.$modelValue)
            .then(function(path){
                if(attrs.urinotation != undefined){
                    path = pathToUri(path)
                }
                scope.$apply(function() {
                    ngModel.$setViewValue(path);
                });
            })
            .catch(function(reason){
                //no file picked
            })
        });
    }
  }
})
mainNgApp.directive('numberrange', function($compile) {
    return {
    restrict: "E",
    require: 'ngModel',
    replace: true,
    template: '<input style="padding-right:2px;padding-left:2px;vertical-align:center;" class="form-control" type="text" ng-model-options="{ updateOn: ' + "'blur'" + '}" ng-keyup="keyup($event);">',
    link: function(scope, element, attrs, ngModel) {
        scope.keyup = function(e) {
            if (e.key === "Enter") {
                e.target.blur()
            }
            cancelBubble(e)
        };
        element.bind('change', function(){
            var input = parseFloat(ngModel.$viewValue.replace(",",".")) || 0;
            if(attrs.step && attrs.step >= 1){input = Math.round(input);}
            var min = attrs.min;
            var max = attrs.max;
            var validValue = input;
            if(min !== undefined){
                validValue = Math.max(min, validValue);
                if(isNaN(validValue)){validValue = input}
            }
            if(max !== undefined){
                validValue = Math.min(max, validValue);
                if(isNaN(validValue)){validValue = input}
            }

            scope.$apply(function() {
                ngModel.$setViewValue(validValue);
                ngModel.$render()
            });
        });
        element.bind('focus', function(){
            element.select()
        })
    }
  }
})
mainNgApp.directive('changevalbyclick', function($compile) {
    return {
    restrict: "E",
    replace: true,
    template: '<div class="activecolor clickable input-group-sm posrel flexcontainer"></div>',
    link: function(scope, element, attrs) {
        var update = function(){
            var onchangeHtmlStr = "";
            if(attrs.ngChange){
                onchangeHtmlStr = ' ng-change="' + attrs.ngChange + '"';
            }
            if(attrs.onenterkey){
                onchangeHtmlStr = onchangeHtmlStr + ' onenterkey="' + attrs.onenterkey + '"';
            }
            element.removeAttr("ng-change")
            element.removeAttr("ng-model")
            var htmlStr = '<span style="vertical-align:middle;min-width:20px;" class="nowrapellipsis">{{' + attrs.ngModel + '}}' + attrs.suffix.replace("_", " ") + '</span>'+
                '<numberrange class="overlay visibleonfocus onfocusinput" ng-model="' + attrs.ngModel + '" min="{{' + (attrs.min !== undefined ? attrs.min : -Number.MAX_VALUE) + '}}" step="{{' + (attrs.step || 0) + '}}" max="{{' + (attrs.max !== undefined ? attrs.max : Number.MAX_VALUE) + '}}"' + onchangeHtmlStr +'></numberrange>'
            element.empty();
            element.prepend(htmlStr);
            $compile(element.contents())(scope);
        }
        scope.$watchGroup([attrs.min, attrs.max, attrs.step], update)
    }
  }
})
mainNgApp.directive('changetextbyclick', function($compile) {
    return {
    restrict: "E",
    replace: true,
    template: '<div class="activecolor clickable input-group-sm posrel flexcontainer"></div>',
    link: function(scope, element, attrs) {
        scope.keyupUnique = function(e) {
            if (e.key === "Enter") {
                e.target.blur()
            }
            cancelBubble(e)
        };
        var onchangeHtmlStr = "";
        if(attrs.ngChange){
            onchangeHtmlStr = ' ng-blur="' + attrs.ngChange + '"';
        }
        if(attrs.onenterkey){
            onchangeHtmlStr = onchangeHtmlStr + ' onenterkey="' + attrs.onenterkey + '"';
        }
        var htmlStr = '<span style="vertical-align:center;min-width:20px;" class="nowrapellipsis">{{' + attrs.ngModel + '.length > 0 && ' + attrs.ngModel + ' || "..."}}' + '</span>'+
            '<input type="text" style="padding-right:2px;padding-left:2px;vertical-align:middle;" ng-keyup="keyupUnique($event);" class="overlay visibleonfocus onfocusinput form-control" ng-model="' + attrs.ngModel + '" ' + onchangeHtmlStr +'>'
        element.prepend(htmlStr);
        var fistInput = element.find("input")
        fistInput.bind("focus", function(){
            fistInput.select()
        })
        $compile(element.contents())(scope.$new());
    }
  }
})
mainNgApp.directive('onenterkey', function () {
    return function (scope, element, attrs) {
        element.bind("keyup", function (event) {
            if(event.key == 'Enter') {
                scope.$apply(function (){
                    scope.$eval(attrs.onenterkey, {$event:event});
                });
                event.preventDefault();
            }
        });
    };
});
mainNgApp.directive('onremovekey', function () {
    return function (scope, element, attrs) {
        element.bind("keyup", function (event) {
            if(event.key == 'Backspace' || event.key == 'Delete') {
                scope.$apply(function (){
                    scope.$eval(attrs.onremovekey, {$event:event});
                });
                event.preventDefault();
            }
        });
    };
});
mainNgApp.directive('mapcompthumbnail', function($compile) {
    return {
        restrict: "E",
        replace: true,
        require: 'ngModel',
        link: function(scope, element, attrs, ngModel) {
           scope.$watch(function () {
              if(mapcompHelpers.isMapcompWrapper(ngModel.$modelValue)){
                  return angular.toJson({
                    a:ngModel.$modelValue.view,
                    b:ngModel.$modelValue.styleInterface && ngModel.$modelValue.styleInterface.id,
                    c:ngModel.$modelValue.data && ngModel.$modelValue.data.mapcompLabelInterface && ngModel.$modelValue.data.mapcompLabelInterface.id || 0,
                    d:ngModel.$modelValue.data && ngModel.$modelValue.data.mapcompLabels || 0,
                    e:ngModel.$modelValue.data && ngModel.$modelValue.data.imagery || 0,
                    f:licensingOptions.advancedData
                  });
              }else{
                  return undefined
              }
           }, function(newValue) {
               var mapcompWrapper = ngModel.$modelValue
                if(mapcompWrapper){
                    if(!mapcompHelpers.isControllerMapcomp(mapcompWrapper)){
                        if(scope.currentId != mapcompWrapper.styleInterface.id){
                            var htmlStr = '<div class="pendingicon ' + iconClasses["spinner"] + '" style="height:' + attrs.size + 'px;width:' + attrs.size + 'px;font-size:' + parseInt(attrs.size*(25/56)) + 'px;padding:' + parseInt(attrs.size*(15/56)) + 'px;"></div>';
                            element.html(htmlStr);
                            $compile(element.contents())(scope.$new());
                        }
                        scope.currentId = mapcompWrapper.styleInterface.id
                        mapcompHelpers.getPreviewDataUrl(mapcompWrapper,Math.max(128,parseInt(attrs.size*1.5))).then(function(dataUrl){
                            if(!mapcompWrapper.styleInterface.errorMsg){
                                var htmlStr = '<div class="posrel"><img class="opactitybg" src="' + dataUrl + '" height="' + attrs.size + '" width="' + attrs.size + '">' + (mapcompWrapper.data.mapcompLabels?mapcompHelpers.getMapcompLabelThumbnailHtmlStr(mapcompWrapper.data.mapcompLabelInterface, attrs.size, false, true):"") + '</div>'
                            }else{
                                if(mapcompWrapper.styleInterface.errorLink){
                                    var htmlStr = '<div class="erroricon ' + iconClasses["error"] + '" style="height:' + attrs.size + 'px;width:' + attrs.size + 'px;font-size:' + parseInt(attrs.size*(25/56)) + 'px;padding:' + parseInt(attrs.size*(15/56)) + 'px;"></div>';
                                }else{
                                    var htmlStr = '<img class="opactitybg" src="' + dataUrl + '" height="' + attrs.size + '" width="' + attrs.size + '">' + addHtml;
                                }
                            }
                            element.html(htmlStr);
                            $compile(element.contents())(scope.$new());
                        }).catch(function(err){
                            if(!mapcompWrapper.styleInterface.errorMsg){
                                var htmlStr = '<div class="' + iconClasses["error"] + '" style="height:' + attrs.size + 'px;width:' + attrs.size + 'px;font-size:' + parseInt(attrs.size*(25/56)) + 'px;padding:' + parseInt(attrs.size*(15/56)) + 'px;"></div>';
                            }else{
                                if(mapcompWrapper.styleInterface.errorLink){
                                    var htmlStr = '<div class="erroricon ' + iconClasses["error"] + '" style="height:' + attrs.size + 'px;width:' + attrs.size + 'px;font-size:' + parseInt(attrs.size*(25/56)) + 'px;padding:' + parseInt(attrs.size*(15/56)) + 'px;"></div>';
                                }else{
                                    var htmlStr = '<div class="' + iconClasses["error"] + '" style="height:' + attrs.size + 'px;width:' + attrs.size + 'px;font-size:' + parseInt(attrs.size*(25/56)) + 'px;padding:' + parseInt(attrs.size*(15/56)) + 'px;"></div>';
                                }
                            }
                            element.html(htmlStr);
                            $compile(element.contents())(scope.$new());
                        })
                    }else{
                        var htmlStr = '<div class="' + iconClasses["controllerMapcomp"] + '" style="height:' + attrs.size + 'px;width:' + attrs.size + 'px;font-size:' + parseInt(attrs.size*(25/56)) + 'px;padding:' + parseInt(attrs.size*(15/56)) + 'px;"></div>';
                        element.html(htmlStr);
                        $compile(element.contents())(scope.$new());
                    }
                }
           });
        }
    }
})
mainNgApp.directive('styleinterfacethumbnail', function($compile) {
    return {
        restrict: "E",
        replace: true,
        require: 'ngModel',
        link: function(scope, element, attrs, ngModel) {
            if(scope[attrs.clickfunc]){
                element.bind('click', function(){
                    if(attrs.clickfuncactive){
                        if(element.hasClass("active")){
                            scope[attrs.clickfuncactive](ngModel.$modelValue).then(scope[attrs.clickfunc]).catch(function(){})
                        }else{
                            scope[attrs.clickfunc](ngModel.$modelValue)
                        }
                    }else{
                        scope[attrs.clickfunc](ngModel.$modelValue)
                    }
                })
            }
           scope.$watch(function () {
              return ngModel.$modelValue.id;
           }, function(newValue) {
               var styleInterface = ngModel.$modelValue
                if(styleInterface){
                    var addHtml = ""
                    if(attrs.activeicon){
                        addHtml = '<span class="overlay activeicon ' + iconClasses[attrs.activeicon] + '"></span>'
                    }
                    var isGEOlayersMapdataHtml = ""
                    if(mapStyler.isStyleInterfaceUsingGEOlayersMapdata(styleInterface)){
                        //isGEOlayersMapdataHtml = '<span class="overlay iconwhite ' + iconClasses.geolayersMapdataSmall + '" style="text-align:right;padding:2px;font-size:' + Math.max(8,parseInt(attrs.size*(10/56))) + 'px;"></span>'
                        var size = Math.max(8,parseInt(attrs.size*(12/56)));
                        isGEOlayersMapdataHtml = '<span class="overlay" style="text-align:right;padding:1px;line-height:' + size + 'px;"><img src="./support_files/maptiler-logo-square.svg" alt="mt" width="' + size +'px" height="' + size +'px"></span>'
                    }
                    var refMap
                    if(gl.styleInterfaceThumbnailCache[styleInterface.id]){
                        var dataUrl = gl.styleInterfaceThumbnailCache[styleInterface.id]
                        var htmlStr = '<img class="opactitybg" src="' + dataUrl + '" height="' + attrs.size + '" width="' + attrs.size + '">' + addHtml + isGEOlayersMapdataHtml;
                        element.html(htmlStr);
                        $compile(element.contents())(scope.$new());
                    }else{
                        var htmlStr = '<div class="pendingicon ' + iconClasses["spinner"] + '" style="height:' + attrs.size + 'px;width:' + attrs.size + 'px;font-size:' + parseInt(attrs.size*(25/56)) + 'px;padding:' + parseInt(attrs.size*(15/56)) + 'px;"></div>';
                        element.html(htmlStr);
                        $compile(element.contents())(scope.$new());
                        mapboxGlTileRenderer.renderStyleInterfaceToDataUrl(styleInterface,Math.max(128,parseInt(attrs.size*1.5)), undefined, refMap || undefined).then(function(dataUrl){
                            if(!styleInterface.errorMsg){
                                gl.styleInterfaceThumbnailCache[styleInterface.id] = dataUrl
                                var htmlStr = '<img class="opactitybg" src="' + dataUrl + '" height="' + attrs.size + '" width="' + attrs.size + '">' + addHtml + isGEOlayersMapdataHtml;
                            }else{
                                if(styleInterface.errorLink){
                                    var htmlStr = '<div class="erroricon ' + iconClasses["error"] + '" style="height:' + attrs.size + 'px;width:' + attrs.size + 'px;font-size:' + parseInt(attrs.size*(25/56)) + 'px;padding:' + parseInt(attrs.size*(15/56)) + 'px;"></div>';
                                }else{
                                    var htmlStr = '<img class="opactitybg" src="' + dataUrl + '" height="' + attrs.size + '" width="' + attrs.size + '">' + addHtml + isGEOlayersMapdataHtml;
                                }
                            }
                            element.html(htmlStr);
                            $compile(element.contents())(scope.$new());
                        }).catch(function(err){
                            var htmlStr = '<div class="' + iconClasses["error"] + '" style="height:' + attrs.size + 'px;width:' + attrs.size + 'px;font-size:' + parseInt(attrs.size*(25/56)) + 'px;padding:' + parseInt(attrs.size*(15/56)) + 'px;"></div>';
                            element.html(htmlStr);
                            $compile(element.contents())(scope.$new());
                        })
                    }
                }
           });
        }
    }
})
mainNgApp.directive('mapcomplabelinterfacethumbnail', function($compile) {
    return {
        restrict: "E",
        replace: true,
        require: 'ngModel',
        link: function(scope, element, attrs, ngModel) {
           scope.$watch(function () {
              return ngModel.$modelValue.id
           }, function(newValue) {
               var addHtml = ""
               if(attrs.activeicon){
                   addHtml = '<span class="overlay activeicon ' + iconClasses[attrs.activeicon] + '"></span>'
               }
               var mapcompLabelInterface = ngModel.$modelValue
               if(mapcompLabelInterface){
                   var htmlStr = mapcompHelpers.getMapcompLabelThumbnailHtmlStr(mapcompLabelInterface, attrs.size, true, false)+addHtml
               }else{
                   var htmlStr = '<div class="erroricon ' + iconClasses["error"] + '" style="height:' + attrs.size + 'px;width:' + attrs.size + 'px;font-size:' + parseInt(attrs.size*(25/56)) + 'px;padding:' + parseInt(attrs.size*(15/56)) + 'px;"></div>';
               }
               element.html(htmlStr);
               $compile(element.contents())(scope.$new());
           });
        }
    }
})
mainNgApp.directive('colorpresetthumbnail', function($compile) {
    return {
        restrict: "E",
        replace: true,
        require: 'ngModel',
        link: function(scope, element, attrs, ngModel) {
           scope.$watch(function () {
              return ngModel.$modelValue
           }, function(newValue) {
               var baseColorPreset = ngModel.$modelValue
               if(baseColorPreset){
                   var htmlStr = ""
                   var colorCSSStrings = (baseColorPreset || []).forEach(function(color){
                       color = colorLogic.normalizeColor(color)
                       htmlStr += "<span class='flexplus1' style='background-color:rgb(" + String(color.r) + "," + String(color.g) + "," + String(color.b) + ")'></span>"
                   })

                    element.html(htmlStr);
                    $compile(element.contents())(scope.$new());
                }
           });
        }
    }
})

mainNgApp.directive('datatransformgradient', function($compile) {
    return {
        restrict: "E",
        replace: true,
        require: 'ngModel',
        link: function(scope, element, attrs, ngModel) {
           scope.$watch(function () {
              return ngModel.$modelValue.stops
           }, function(newValue) {
               var dataTransformer = ngModel.$modelValue;
               if(dataTransformer){
                    var htmlStr = '<div style="background:' + drawingOptionHelpers.getCssGradientFromDataTransformer(dataTransformer) + ';height:8px;"></div>'
                    if(htmlStr){
                        element.html(htmlStr);
                        $compile(element.contents())(scope.$new());
                    }   
                }
           }, true);
        }
    }
})

mainNgApp.directive('drawingoptionthumbnail', function($compile) {
    return {
        restrict: "E",
        replace: true,
        link: function(scope, element, attrs) {
           scope.$watch(function () {
              return (attrs.drawingoption && angular.toJson(scope.$eval(attrs.drawingoption)) || "") + (attrs.bordervalues && angular.toJson(scope.$eval(attrs.bordervalues)) || "");
           }, function(newValue) {
               var shapeStyle = scope.$eval(attrs.drawingoption)
               var borderValues = attrs.bordervalues && scope.$eval(attrs.bordervalues) || {}
               var defaultBorderValues = {
                   strokeWidthMin: 1,
                   strokeWidthMax: 30
               }
               borderValues = extendObj(defaultBorderValues, borderValues)
               var getTumbnailStrokeWidth = function(drawingOption){
                   if(drawingOptionHelpers.isDrawingOptionsDataTransformingType(drawingOption, "strokeWidth")){
                       return Math.round(transformLinearClip(drawingOption.stroke.strokeWidth, defaultBorderValues.strokeWidthMin, defaultBorderValues.strokeWidthMax, 1, 4) || 2)
                   }else{
                       return Math.round(transformLinearClip(drawingOption.stroke.strokeWidth, borderValues.strokeWidthMin, borderValues.strokeWidthMax, 1, 4) || 2)
                   }
               }
               var getTumbnailStrokeOutlineWidth = function(drawingOption){
                   var baseWidth = getTumbnailStrokeWidth(drawingOption)
                   return ((baseWidth < 2) ? baseWidth+1 : baseWidth+2)
               }

               if(shapeStyle){
                    var normalizeToRGBAry = function(hex) {
                        var normalizedCOlor = drawingOptionHelpers.normalizeColor(hex)
                        return [normalizedCOlor.r/256, normalizedCOlor.g/256, normalizedCOlor.b/256];
                    };
                    var getCSSborderClass = function(dashVal){
                        if(dashVal === 0){return "styleThumbnailBorderSolid"}
                        if(dashVal < 0.99){return "styleThumbnailBorderDashed"}
                        return "styleThumbnailBorderDotted"
                    }
                    var getBorderCss = function(options){
                        options = options || {};
                        var dashVal = Math.max(0.1, options.dashVal||1);
                        var color = options.color || "black";
                        var gapColor = options.dashVal ? options.gapColor || (options.outlineSize && options.outlineColor) || "transparent" : options.color;
                        var outlineColor = options.outlineColor;
                        var outlineSize = options.outlineSize || 0;
                        var getLength = function(a,b){//divideables for clean loop animation
                            var d, dOld, outV
                            for(var i = 1; i < 20; i++){
                                outV = a/i
                                d = Math.abs(outV-b)
                                if(outV == b){
                                    break;
                                }else if(dOld && d >= dOld){
                                    outV = a/(i-1)
                                    break;
                                }
                                dOld = d
                            }
                            return outV
                        }
                        var sizeLength = getLength(20, options.width*2/dashVal)
                        var sizeWidth = options.width
                        var dur = Math.min(10,Math.max(0.5,60/Math.abs(options.speed)))
                        var css = {
                            "background":replaceAll(replaceAll('linear-gradient(90deg, {color} 30%, {gapColor} 30%, {gapColor} 70%, {color} 70%),linear-gradient(0deg, {color} 30%, {gapColor} 30%, {gapColor} 70%, {color} 70%),linear-gradient(90deg, {color} 30%, {gapColor} 30%, {gapColor} 70%, {color} 70%),linear-gradient(0deg, {color} 30%, {gapColor} 30%, {gapColor} 70%, {color} 70%)', "{color}", color), "{gapColor}", gapColor),
                            "background-repeat": 'repeat-x, repeat-y, repeat-x, repeat-y',
                            "background-size": replaceAll(replaceAll('{length}px {width}px, {width}px {length}px, {length}px {width}px, {width}px {length}px', "{width}", sizeWidth), "{length}", sizeLength),
                            "background-position": 'left top, right top, left bottom, left top',
                            "box-sizing": "border-box",
                            "width": "20px",
                            "height": "20px"
                        };
                        if(options.speed && options.dashVal) {
                            css["animation"] = ('border-dance' + (options.speed < 0 ? '-ccw' : '') + ' {dur}s infinite linear').replace("{dur}", dur)
                        }
                        if(outlineSize && outlineColor){
                            css["border"] = ("{outlineColor} {outlineSize}px solid").replace("{outlineColor}", outlineColor).replace("{outlineSize}", outlineSize)
                        }
                        return css 
                    }
                    var getBorderElementHtml = function(options){
                        var css = getBorderCss(options);
                        var styleStr = ""
                        for(var p in css){
                            if(css.hasOwnProperty(p)){
                                styleStr += (p + ":" + css[p] + ";")
                            }
                        }
                        return '<div class="overlay" style="' + styleStr + '"></div>'
                    }
                    var getHtmlStr = function(drawingOptions){
                        if(!drawingOptions){return "<div class='styleThumbnail'></div>"}

                        var classStr, styleStr, rgb, r, g, b, a, rgba_bg, rgba_st, opactitybg, extrusionOverlay, dataTransformOverlay, strokeOverlay, strokeGapsOverlay, opacityOverlay
                        classStr = styleStr = extrusionOverlay = dataTransformOverlay = strokeOverlay = strokeGapsOverlay = strokeOutlineOverlay = opacityOverlay = "";


                        rgba_bg = normalizeToRGBAry(drawingOptions.fill.fillColor).map(function(val){return Math.floor(val*255)});
                        if(drawingOptions.fill.addFill){
                            a=drawingOptions.fill.opacity/100;
                        }else{
                            a=0;
                        };
                        if(a === 0){
                            opacityOverlay = "";
                        }else{
                            opacityOverlay = "<div class='opactitybg overlay' style='opacity:" + String(1-a) + ";'></div>";
                        }
                        rgba_bg.push(a);
                        if(drawingOptions.fill.randomizeColors && a > 0){
                            classStr += " randomizeColorBG"

                        }else{
                            styleStr += "background-color:rgba(" + String(rgba_bg[0]) + "," + String(rgba_bg[1]) + "," + String(rgba_bg[2]) + "," + String(rgba_bg[3]) + ");"

                        }

                        if(drawingOptions.stroke.sameColorAsFill){
                            rgba_st = rgba_bg;

                        }else{
                            rgba_st = normalizeToRGBAry(drawingOptions.stroke.strokeColor).map(function(val){return Math.floor(val*255)});

                        }
                        if(drawingOptions.stroke.addStroke){
                            a=drawingOptions.stroke.opacity/100;
                        }else{
                            a=0;
                        };
                        if(a > 0 && a < 1 && !opacityOverlay.length){
                            opacityOverlay = "<div class='opactitybg overlay' style='opacity:" + String(1) + ";'></div>";
                        }
                        rgba_st[3]=a;
                        if(a>0){
                            //strokeOverlay = "<div class='overlay " + String(getCSSborderClass(drawingOptions.stroke.dashVal)) + "' style='border-color:rgba(" + String(rgba_st[0]) + "," + String(rgba_st[1]) + "," + String(rgba_st[2]) + "," + String(rgba_st[3]) + ");border-width:" + String(getTumbnailStrokeWidth(drawingOptions)) + "px;'></div>"
                            strokeOverlay = getBorderElementHtml({
                                color: "rgba(" + rgba_st[0] + "," + rgba_st[1] + "," + rgba_st[2] + "," + rgba_st[3] + ")",
                                //gapColor: "black",
                                //outlineColor: "pink",
                                //outlineSize: 0,
                                width: getTumbnailStrokeWidth(drawingOptions),
                                speed: drawingOptions.stroke.dashSpeed || 0,
                                dashVal:drawingOptions.stroke.dashVal || 0
                            })
                            if(drawingOptions.stroke.fillGaps){
                                rgba_st = normalizeToRGBAry(drawingOptions.stroke.strokeGapColor).map(function(val){return Math.floor(val*255)});
                                rgba_st.push(drawingOptions.stroke.opacity/100)
                                strokeGapsOverlay = "<div class='overlay " + String(getCSSborderClass(0)) + "' style='border-color:rgba(" + String(rgba_st[0]) + "," + String(rgba_st[1]) + "," + String(rgba_st[2]) + "," + String(rgba_st[3]) + ");border-width:" + String(getTumbnailStrokeWidth(drawingOptions)) + "px;'></div>"
                            }else{
                                strokeGapsOverlay = ""
                            }
                            if(drawingOptions.stroke.strokeOutlineWidth){
                                rgba_st = normalizeToRGBAry(drawingOptions.stroke.strokeOutlineColor).map(function(val){return Math.floor(val*255)});
                                rgba_st.push(drawingOptions.stroke.opacity/100)
                                strokeOutlineOverlay = "<div class='overlay " + String(getCSSborderClass(0)) + "' style='border-color:rgba(" + String(rgba_st[0]) + "," + String(rgba_st[1]) + "," + String(rgba_st[2]) + "," + String(rgba_st[3]) + ");border-width:" + String(getTumbnailStrokeOutlineWidth(drawingOptions)) + "px;'></div>"
                            }else{
                                strokeOutlineOverlay = ""
                            }
                        }else{
                            strokeOverlay = ""
                        }
                        if(drawingOptions.extrusion){
                            extrusionOverlay = "<div class='styleThumbnailExtrusionOverlay overlay' style='border-width:" + Math.max(2,Math.floor(drawingOptions.extrusion/150)) + "px;'></div>"
                        }else{
                            extrusionOverlay = "";
                        }
                        if(drawingOptionHelpers.isDrawingOptionsDataTransforming(drawingOptions)){
                            if(drawingOptions.dataDrivenAnimated){
                                dataTransformOverlay = "<div class='styleThumbnailDataTransformOverlay overlay'><span ng-class='iconClasses.keyframe'></span><span style='margin-left:-3px;' ng-class='iconClasses.dataTransformerSmall'></span></div>"
                            }else if(drawingOptions.dataDrivenProperty === false){
                                dataTransformOverlay = "<div class='styleThumbnailDataTransformOverlay overlay'><span ng-class='iconClasses.dataTransformerSmallNoProperty'></span></div>"
                            }else{
                                dataTransformOverlay = "<div class='styleThumbnailDataTransformOverlay overlay'><span ng-class='iconClasses.dataTransformerSmall'></span></div>"
                            }
                            
                        }else{
                            dataTransformOverlay = "";
                        }

                        return "<div class='styleThumbnail posrel' style='display:inline-block;'><div class='styleThumbnail" + classStr +"' style='"+styleStr+"'></div>"+ opacityOverlay + strokeOutlineOverlay + strokeGapsOverlay + strokeOverlay + extrusionOverlay + dataTransformOverlay + "</div>"
                    }
                    var htmlStr = getHtmlStr(shapeStyle)
                    element.html(htmlStr);
                    $compile(element.contents())(scope.$new());
                }
           });
        }
    }
})
mainNgApp.directive('recentproject', function($compile) {
    return {
    restrict: "E",
    replace: true,
    //require: "ngClick",
    scope:{},
    template: '<div class="sampleproject" ng-class="{clickable : !isBusy}" style="min-height:100px;min-width:150px;position:relative">'+
        '<div ng-show="isBusy" class="overlay hostElt isbusy"></div>' +
        '<div ng-show="isBusy" class="progress active overlay" style="border-radius:0px;background-color:rgba(0,0,0,0);">' +
            '<div ng-class="{' + "'" + 'progress-bar-striped' + "'" + ' : progressWidth === 100' + '}" class="progress-bar progress-bar-animated" style="height: 3px; width:{{progressWidth}}%;"></div>' +
        '</div>' +
        '<div class="overlay bggradient"></div>' +
        '<div class="overlay flexcontainervertical">' +
            '<div class="flexfixed flexcontainer">' +
                '<div ng-click="openProject()" class="flexplus1"></div>' +
                '<div class="flexfixed">' +
                    '<button ng-click="removeFromList()" style="margin:6px;" type="button" class="btn btn-sm btn-secondary"><span class="' + iconClasses.x + '"></span></button>' +
                '</div>' +
            '</div>' +
            '<div ng-click="openProject()" class="flexplus1"></div>' +
        '</div>' +
        '<div style="position:absolute;bottom:4px;left:5px;"><h6 id="name" style="margin-bottom:2px;text-shadow: 1px 1px 1px #000000;">{{decodeURI(project.name)}}</h6><h6 id="description" style="opacity:0.8;margin-bottom:2px;text-shadow: 1px 1px 1px #000000;"><small style="margin-bottom:0px;text-shadow: 1px 1px 1px #000000;color:white;">{{shortenPath(project.projectPath, 100)}}</small></h6></div>' +
        '</div>',
    controller: function($scope){
        $scope.isBusy = false;
        $scope.project;
        $scope.decodeURI = decodeURI;
        $scope.shortenPath = shortenPath;
        $scope.removeFromList = function(){
            preferences.recentProjects.forEach(function(recentProject, index){
                if($scope.project.projectPath == recentProject.projectPath){
                    preferences.recentProjects.splice(index,1)
                }
            })
        };
        $scope.openProject = function(){
            if($scope.isBusy == false){
                $scope.isBusy = true
                $scope.safeApply(function(){
                    $scope.isBusy = true
                })
                openProjectAndShowMapcompWrappers($scope.project.projectPath).then(function(fileName){
                    $scope.safeApply(function(){
                        $scope.isBusy = false
                    })
                    // init project
                }).catch(function(err){
                    $scope.safeApply(function(){
                        $scope.isBusy = false
                    })
                    simpleAlert("Error opening Project", err && err.message)
                    glLogger.log("Error opening Project", err)
                })
            }
        };
        $scope.progressWidth = 100;
        $scope.safeApply = function(fn) {
          var phase = this.$root.$$phase;
          if(phase == '$apply' || phase == '$digest') {
            if(fn && (typeof(fn) === 'function')) {
              fn();
            }
          } else {
            this.$apply(fn);
          }
        };
    },
    link: function($scope, element, attrs) {
        var initializeProject = function(project){
            if(!project){try{project = JSON.parse(attrs.project)}catch(e){}}
            if(!project){return null}
            $scope.safeApply(function(){
                $scope.project = project
            })
            var imageSrcUrl = replaceAll(project.snapshotPath, "\\", "/")
            //var projectUrl = project.projectPath
            var name = decodeURI(project.name)
            if(fs.existsSync(project.projectPath)){
                var changes = {
                    backgroundImage: 'url("file:///' + imageSrcUrl + '")',
                    backgroundSize: "cover",
                    backgroundPosition: "center"
                }
            }else{
                var changes = {
                    display: 'none',
                }
            }
            element.css(changes)
            //$compile(element.contents())($scope.$new());
        }
        attrs.$observe("project",function(project){
            if (project){
                try{project = JSON.parse(project)}catch(e){}
                initializeProject(project)
            }
        });
        initializeProject()
    }
  }
})

mainNgApp.directive('onlinesampleproject', function($compile) {
    if(parseFloat(csInterface.hostEnvironment.appVersion) >= 15){
        return {
        restrict: "E",
        replace: true,
        scope:{},
        template: '<div class="sampleproject" ng-class="{clickable : !isBusy}" style="min-height:100px;min-width:150px;position:relative">'+
            '<div ng-show="isBusy" class="overlay hostElt isbusy"></div>' +
            '<div id="gradient" class="overlay bggradient"></div>' +
            '<div style="position:absolute;bottom:4px;left:5px;">' +
                '<h6 id="name" style="margin-bottom:2px;text-shadow: 1px 1px 1px #000000;">{{project.name}}</h6>' +
                '<h6 ng-show="statusDescription" style="margin:0px;margin-bottom:1px"><small style="opacity:0.8;margin-bottom:0px;text-shadow: 1px 1px 1px #000000;color:white;">{{statusDescription}}</small></h6>' +
                '<h6 ng-if="!statusDescription" id="description" style="margin:0px;margin-bottom:1px"><small style="opacity:0.8;margin-bottom:0px;text-shadow: 1px 1px 1px #000000;color:white;">{{project.description}}</small></h6>' +
            '</div>' +
            '<div class="overlay flexcontainervertical">' +
                '<div class="flexfixed flexcontainer">' +
                    '<div ng-click="openProject()" class="flexplus1">' +
                        '<div ng-show="project.type === ' + "'labelTemplates'" + '" class="flexfixed projecttype"><span class="' + iconClasses.labels + '"></span><span class="projecttypetext"> Label Template Project</span></div>' +
                    '</div>' +
                    '<div class="flexfixed">' +
                        '<button ng-click="browserDownload()" style="margin:6px;" type="button" class="btn btn-sm btn-secondary"><span class="' + iconClasses.download + '"></span></button>' +
                    '</div>' +
                    '<div ng-show="project.videoUrl"class="flexfixed">' +
                        '<button ng-click="openVideo()" style="margin:6px;" type="button" class="btn btn-sm btn-secondary"><span class="' + iconClasses.movie + '"></span></button>' +
                    '</div>' +
                '</div>' +
                '<div ng-click="openProject()" class="flexplus1"></div>' +
            '</div>' +
            '<div ng-show="isBusy" class="progress active overlay" style="border-radius:0px;background-color:rgba(0,0,0,0);">' +
                '<div ng-class="{' + "'" + 'progress-bar-striped' + "'" + ' : progressWidth === 100}" class="progress-bar progress-bar-animated" style="height: 4px; width:{{progressWidth}}%;"></div>' +
            '</div>' +
            '</div>',
        controller: function($scope){
            $scope.isBusy = false;
            $scope.statusDescription = null
            $scope.project = {}
            $scope.openVideo = function(){
                if($scope.project.videoUrl){window.cep.util.openURLInDefaultBrowser($scope.project.videoUrl)}
            }
            $scope.browserDownload = function(){
                if($scope.project.uri){window.cep.util.openURLInDefaultBrowser($scope.project.uri)}
            }
            $scope.openProject = function(){

                if($scope.isBusy == false){
                    $scope.isBusy = true;
                    $scope.safeApply(function(){
                        $scope.isBusy = true;
                        $scope.statusDescription = "downloading and opening Project...";
                    })
                    openOnlineProjectAndShowMapcompWrappers($scope.project.uri, $scope.project.id + "_V" + String($scope.project.version || 0), $scope.project.type, function(progress){
                        if(progress.percentage < $scope.progressWidth || progress.percentage > $scope.progressWidth + 2 || progress.percentage === 100){
                            $scope.safeApply(function(){
                                $scope.progressWidth = Math.max(progress.percentage,2)
                            })
                        }

                    },false,$scope.project.mapcompSortType).then(function(fileName){
                        $scope.safeApply(function(){
                            $scope.isBusy = false
                            $scope.progressWidth = 100
                            $scope.statusDescription = null
                        })
                    }).catch(function(err){
                        $scope.safeApply(function(){
                            $scope.isBusy = false
                            $scope.progressWidth = 100
                            $scope.statusDescription = null
                        })
                        if(simpleAlert){
                            simpleAlert("Error opening Project", err && err.message)
                        }

                    })
                }
            }
            $scope.progressWidth = 100;
            $scope.safeApply = function(fn) {
              var phase = this.$root.$$phase;
              if(phase == '$apply' || phase == '$digest') {
                if(fn && (typeof(fn) === 'function')) {
                  fn();
                }
              } else {
                this.$apply(fn);
              }
            };
        },
        link: function($scope, element, attrs) {
            var initializeProject = function(){
                if(attrs.project){
                    $scope.project = JSON.parse(attrs.project)
                }else{
                    $scope.project = attrs.projectid && getItemById(sampleProjects, attrs.projectid);
                }
                var imageSrcUrl = $scope.project.previewImageUri
                $scope.curElem = element
                var changes = {
                    backgroundImage: 'url(' + imageSrcUrl + ')',
                    backgroundSize: "cover",
                    backgroundPosition: "center"
                }
                element.css(changes)
                //$compile(element.contents())($scope.$new());
            }
            attrs.$observe("project",function(project){
                if (project){
                    try{project = JSON.parse(project)}catch(e){}
                    initializeProject(project)
                }
            });
            attrs.$observe("projectid",function(projectid){
                var project = getItemById(sampleProjects, projectid)
                if (project){
                    initializeProject(project)
                }
            });
            initializeProject()
        }
      }
    }else{
        return {
            restrict: "E",
            replace: true,
            link: function(scope, element, attrs) {
                var initializeProject = function(project) {
                    if(!project){
                        if(attrs.project){
                            project = JSON.parse(attrs.project)
                        }else{
                            project = attrs.projectid && getItemById(sampleProjects, attrs.projectid);
                        }
                    }
                    var htmlStr = '<button style="height:100px;width:100%;display:block;" type="button" onclick="window.cep.util.openURLInDefaultBrowser(' + "'" + String(project.uri) + "'" + ')" class="btn btn-outline-primary"><span class="{{iconClasses.download}}"></span> Download "' + project.name + '" with Browser</button>'
                    element.html(htmlStr);
                    $compile(element.contents())(scope.$new());
                }
                attrs.$observe("project",function(project){
                    if (project){
                        try{project = JSON.parse(project)}catch(e){project = null}
                        project && initializeProject(project)
                    }
                });
                attrs.$observe("projectid",function(projectid){
                    var project = getItemById(sampleProjects, projectid)
                    if (project){
                        initializeProject(project)
                    }
                });
                initializeProject()
            }
        }
    }
})

mainNgApp.directive('vimeo', function($compile) {
    return {
        restrict: "E",
        replace: true,
        link: function(scope, element, attrs) {
           scope.$watch(function () {
              return attrs.vimeoid
           }, function(newValue) {
               var htmlStr = ""
               if(parseFloat(csInterface.hostEnvironment.appVersion) >= 15){
                   htmlStr = '<div style="padding:56.25% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/' +
                   String(attrs.vimeoid) +
                   '?background=' + (attrs.background!=undefined?"1":"0") +
                   '&autoplay=' + (attrs.background!=undefined||attrs.autoplay!=undefined?"1":"0") +
                   '&loop=' + (attrs.background!=undefined||attrs.loop!=undefined?"1":"0") +
                   '&title=0&byline=0&portrait=0" style="position:absolute;top:0;left:0;width:100%;height:100%;" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div><script src="https://player.vimeo.com/api/player.js"></script>'
               }else if(attrs.alt!=undefined){
                   htmlStr = '<button style="height:100px;width:100%;display:block;" type="button" onclick="window.cep.util.openURLInDefaultBrowser(' + "'https://vimeo.com/" + String(attrs.vimeoid) + "'" + ')" class="btn btn-outline-primary"><span class="{{iconClasses.movie}}"></span> Watch Video in Browser</button>'
               }
               element.html(htmlStr);
               $compile(element.contents())(scope.$new());
           });
        }
    }
})

mainNgApp.directive('lottie', function($compile) {
    return {
        restrict: "E",
        replace: false,
        link: function(scope, element, attrs) {
            scope.$watch(function () {
                return attrs.path
            }, function(newValue) {
                    var nextLottie = function(){
                        var paths = newValue.split("|")
                        var loop = (attrs.loop !== undefined)?attrs.loop.length?Number(attrs.loop):true:false
                        if(scope.lottieAnim){
                            scope.lottieAnim.destroy()
                        }
                        scope.lottieAnim = lottieHelper.loadAnimation({
                            autoplay: (attrs.autoplay !== undefined),
                            loop: loop,
                            container: element[0],
                            path: paths[getRandomInt(paths.length)]
                        })
                        if(loop){
                            scope.lottieAnim.addEventListener("complete", function(e){
                                nextLottie()
                            })
                        }
                    }
                    nextLottie()
            });
            element.on('$destroy', function(){
                if(scope.lottieAnim){
                    scope.lottieAnim.destroy()
                }
            })
        }
    }
})
