<commit_before>var setEditorialVoteEventListeners = function() {
    $(document).on('click', '.love-editorial', function() {
        var $this = $(this),
            $loveCount = $($this.siblings()[0]),
            $heartIcon = $this.children()[0];
        if ($heartIcon.classList.contains('red-text')) {
            $.web2py.flash('Already voted the editorial');
        } else {
            if (isLoggedIn === false) {
                $.web2py.flash('Please login to cast your vote');
            } else {
                $.ajax({
                    url: loveEditorialURL + '/' + $(this).data()['id'],
                    method: 'POST',
                    success: function(response) {
                        $loveCount.html(response['love_count']);
                        $heartIcon.classList.add('red-text');
                        $.web2py.flash('Vote submitted');
                    },
                    error: function(response) {
                        if (response.status === 401) {
                            $.web2py.flash('Please login to cast your vote');
                        } else {
                            $.web2py.flash('Error submitting your vote');
                        }
                    }
                });
            }
        }
    });
};

var initTooltips = function() {
    $('.tooltipped').tooltip({
        delay: 50
    });
};

(function($) {
    "use strict";

    /* Handle the case of single digit in a time stamp */
    var formatTimeStamp = function(num) {
        var strnum = num.toString();

        return strnum.length == 1 ? '0' + strnum : strnum;
    };

    /* Transform the timestamps from IST to client browser timestamp */
    var transformTimestamps = function() {
        var clientOffset = new Date().getTimezoneOffset() * 60 * 1000;
        var ISTOffset = -330 * 60 * 1000;
        var convertedTimestamp, ISTTimestamp, d;

        $('.stopstalk-timestamp').each(function(i, td) {
            ISTTimestamp = new Date(td.textContent.replace(/-/g, '/')).getTime();
            d = new Date(ISTTimestamp + ISTOffset - clientOffset);
            convertedTimestamp = [d.getFullYear(),
                d.getMonth() + 1,
                d.getDate()
            ].map(formatTimeStamp).join('-') + " " + [d.getHours(),
                d.getMinutes(),
                d.getSeconds()
            ].map(formatTimeStamp).join(':');
            $(this).html(convertedTimestamp);
        });
    };

    /* Download a submission as a file */
    var downloadSubmission = function(fileName, text) {
        var element = document.createElement('a');

        element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
        element.setAttribute('download', fileName);
        element.style.display = 'none';
        document.body.appendChild(element);
        element.click();
        document.body.removeChild(element);
    };

    /* Copy the code to the clipboard */
    var copyToClipBoard = function(copyText) {
        var textField = document.createElement('textarea');

        textField.innerHTML = copyText;
        document.body.appendChild(textField);
        textField.select();
        document.execCommand("copy");
        textField.remove();

        $.web2py.flash("Copied to Clipboard");
    };

    /* Escape characters in html to be representable as string */
    var htmlifySubmission = function(solutionCode) {
        return solutionCode.replace(/&/g, "&")
            .replace(/>/g, ">")
            .replace(/</g, "<")
            .replace(/"/g, """).trim(' ');
    };

    /* Get a url parameter by the key from a string url */
    var getParameterByName = function (name, url) {
        if (!url) {
          url = window.location.href;
        }
        name = name.replace(/[\[\]]/g, "\\$&");
        var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
            results = regex.exec(url);
        if (!results) return null;
        if (!results[2]) return '';
        return decodeURIComponent(results[2].replace(/\+/g, " "));
    };

    var markPulseButtonClick = function(elementID, key) {
        if (isLoggedIn && !clickedButton[key]) {
            $.ajax({
                url: markReadURL,
                data: {key: key},
                success: function() {
                    clickedButton[key] = true;
                    $('#' + elementID).removeClass('pulse');
                }
            });
        }
    };

    var openProblemDifficultyModal = function() {
        if (window.localStorage["lastShowedProblemDifficulty"] &&
            Date.now() - window.localStorage["lastShowedProblemDifficulty"] < 24 * 60 * 60 * 100) {
            // Modal showed less than 24 hours before;
            return;
        }

        $.ajax({
            url: getNextProblemURL,
            method: 'GET',
            success: function(response) {
                $('#problem-difficulty-title').html("How difficult is " + response["pname"] + "?");
                $('#problem-difficulty-modal').modal('open');
                problemDifficultyModalOpen = true;
            },
            error: function(e) {
                console.log("Failed to get the next problem", e);
            }
        });

    };

    var initProblemDifficultySubmitHandler = function() {
        $('#problem-difficulty-modal-form').on('submit', function(e) {
            var $thisForm = $('#problem-difficulty-modal-form'),
                $thisTitle = $('#problem-difficulty-title'),
                $thisProblemLink = $("#problem-details-link");

            $.ajax({
                method: 'GET',
                url: problemDifficultySubmitURL + '.json',
                data: {
                    "score":  $("input[name='problem_difficulty_value']:checked").val(),
                    "problem_id": $("#problem-difficulty-modal-form").attr("data-problem")
                },
                success: function(response) {
                    setTimeout(function() {
                        $thisForm.attr('data-problem', response["new_problem_id"]);
                        $('#problem-difficulty-modal-submit-button').val('Submit');
                        $('#problem-difficulty-modal-submit-button').removeClass('disabled');

                        $thisForm.trigger('reset');

                        $thisForm.fadeOut(function() {
                            $thisForm.fadeIn();
                        });

                        $thisTitle.fadeOut(function() {
                            // $thisTitle.html(problemTitles[Math.floor(Math.random() * problemTitles.length)].replace("___", "" + response["pname"] + ""));
                            $thisTitle.html("How difficult is " + response["pname"] + "?");
                            $thisTitle.fadeIn();
                        });

                        // $thisProblemLink.fadeOut(function() {
                        //     $thisProblemLink.html();
                        //     $thisProblemLink.href = response["plink"];
                        //     $thisProblemLink.fadeIn();
                        // });
                    }, 100);
                },
                error: function(err) {
                    console.log(err);
                }
            });
            e.preventDefault();
        });
    };

    $(document).ready(function() {

        var $viewDownloadButton = $('#final-view-download-button'),
            $downloadButton = $('#final-download-button'),
            $downloadModal = $('#download-submission-modal'),
            $viewFileName = $('#view_download_file_name'),
            $viewPreLoader = $('#view-submission-preloader'),
            $viewPre = $('#view-submission-pre'),
            $viewModal = $('#view-submission-modal'),
            $linkToSite = $('#link-to-site');

        transformTimestamps();

        $("#welcome a").on('touchend', function(){
            window.location.href = $(this).attr('href');
        });

        $('.collapsible').collapsible({
            accordion: false // A setting that changes the collapsible behavior to expandable instead of the default accordion style
        });

        $('select').material_select();

        $('#problem-difficulty-modal').modal({
            dismissible: false,
            complete: function() {
                window.localStorage["lastShowedProblemDifficulty"] = Date.now();
                problemDifficultyModalOpen = false;
            }
        });

        initTooltips();

        openProblemDifficultyModal();

        initProblemDifficultySubmitHandler();

        $('#open-side-nav').click(function() {
            if (typeof ga !== 'undefined') {
                ga('send', {
                    hitType: 'event',
                    eventCategory: 'button',
                    eventAction: 'click',
                    eventLabel: 'Open Side Navbar'
                });
            }
        });

        $('#open-side-nav').sideNav();

        // Remove focus from any focused element
        if (document.activeElement) {
            document.activeElement.blur();
        }

        $(document).on('mouseenter', '.submissions-table tr, .user-editorials-table tr', function() {
            var todoIcon = $(this).find('.add-to-todo-list');
            todoIcon.show();
        });

        $(document).on('mouseleave', '.submissions-table tr, .user-editorials-table tr', function() {
            var todoIcon = $(this).find('.add-to-todo-list');
            todoIcon.hide();
        });

        $(document).on('click', '.add-to-todo-list', function() {
            var stopstalkLink = this.parentElement.firstChild["href"],
                problemLink = getParameterByName("plink", stopstalkLink),
                thisElement = this,
                $thisElement = $(this),
                problemId = $thisElement.data()["pid"];
            $.ajax({
                url: addTodoURL,
                method: 'POST',
                data: {pid: problemId},
                success: function(response) {
                    var tooltipID = thisElement.getAttribute("data-tooltip-id");
                    $thisElement.remove();
                    $('#' + tooltipID).remove();
                    $.web2py.flash(response);
                },
                error: function(e) {
                    $.web2py.flash("Some error occurred");
                }
            });
        });

        $(document).on('click', '.delete-editorial', function() {
            var result = confirm("Are you sure you want to DELETE the editorial ?")
            if(!result) return;
            var $this = $(this);
            $.ajax({
                url: deleteEditorialURL + '/' + $this.data('id'),
                method: 'POST',
                success: function(response) {
                    if (response === "SUCCESS") {
                        $this.parent().parent().fadeOut();
                        $.web2py.flash("Editorial deleted successfully!");
                    } else {
                        $.web2py.flash("Can't delete accepted Editorial!");
                    }
                },
                error: function(err) {
                    $.web2py.flash("Something went wrong!");
                }
            });
        });

        $(document).on('click', '.view-submission-button', function() {
            var site = $(this).data('site');
            var viewLink = $(this).data('view-link');

            var onViewModalClose = function() {
                $viewDownloadButton.addClass('disabled');
                $viewFileName.val('');
                $viewPreLoader.removeClass('hide');
                $viewPre.addClass('hide');
                $viewPre.removeClass('prettyprint');
                $viewPre.removeClass('prettyprinted');
                $viewPre.html('');
                $('#copy-to-clipboard').addClass('hide');
                $linkToSite.html('');
                $linkToSite.attr('href', '');
            };

            $linkToSite.html('View on ' + site);
            $linkToSite.attr('href', viewLink);

            $viewModal.modal({
                complete: onViewModalClose
            });

            $viewModal.modal('open');

            $.ajax({
                method: 'GET',
                url: '/default/download_submission',
                data: {
                    site: site,
                    viewLink: viewLink
                }
            }).done(function(response) {
                var solutionText;

                $viewPreLoader.addClass('hide');
                $viewPre.removeClass('hide');

                $('#copy-to-clipboard').removeClass('hide');

                if (response != -1) {
                    solutionText = htmlifySubmission(response);
                } else {
                    solutionText = "// Unable to retrieve submission from " + site + "\n" + "/* Possible reasons :-\n\t * " + site + " is down\n\t * The submission is from an ongoing contest\n\t * There was some error from our side. \n\t   Please write to us (https://www.stopstalk.com/contact_us)\n */";
                }

                $viewPre.html(solutionText);
                $viewModal.one('click', '#copy-to-clipboard', function() {
                    if (response != -1) {
                        copyToClipBoard(response);
                    } else {
                        copyToClipBoard(solutionText);
                    }
                });

                $viewPre.addClass('prettyprint');
                PR.prettyPrint();
                if (response != -1) {
                    $viewDownloadButton.removeClass('disabled');
                    $viewFileName.on('keydown', function() {
                        if (event.which == 13) {
                            $('#final-view-download-button').trigger('click');
                        }
                    });

                    $(document).one('click', '#final-view-download-button', function() {
                        var fileName = $viewFileName.val();
                        if (fileName) {
                            downloadSubmission(fileName, response);
                            $viewModal.modal('close');
                            onViewModalClose();
                        } else {
                            $.web2py.flash("Please enter a file name");
                        }
                    });
                } else {
                    $.web2py.flash("Unable to Download");
                }
            });
        });

        $(document).on('click', '#heart-button', function() {
            markPulseButtonClick('heart-button', 'heart_button');
        });

        $(document).on('click', '#onboarding-button', function() {
            markPulseButtonClick('onboarding-button', 'onboarding_button');
        });

        $(document).on('click', '.download-submission-button', function() {
            var site = $(this).data('site');
            var viewLink = $(this).data('view-link');

            var onDownloadModalClose = function() {
                /* Callback function on Modal close */
                $downloadButton.addClass('disabled');
                $('#file_name').val('');
            };

            $downloadModal.modal({
                complete: onDownloadModalClose
            });

            $downloadModal.modal('open');

            $.ajax({
                method: 'GET',
                url: '/default/download_submission',
                data: {
                    site: site,
                    viewLink: viewLink
                }
            }).done(function(response) {
                if (response != -1) {
                    $downloadButton.removeClass('disabled');
                    $('#file_name').on('keydown', function() {
                        if (event.which == 13) {
                            $('#final-download-button').trigger('click');
                        }
                    });

                    $(document).one('click', '#final-download-button', function() {
                        var fileName = $('#file_name').val();
                        if (fileName) {
                            downloadSubmission(fileName, response);
                            $downloadModal.modal('close');
                            onDownloadModalClose();
                        } else {
                            $.web2py.flash("Please enter a file name");
                        }
                    });
                } else {
                    $.web2py.flash("Unable to Download");
                }
            });
        });
    });
})(jQuery);
<commit_msg>add layout.js changes<commit_after>var setEditorialVoteEventListeners = function() {
    $(document).on('click', '.love-editorial', function() {
        var $this = $(this),
            $loveCount = $($this.siblings()[0]),
            $heartIcon = $this.children()[0];
        if ($heartIcon.classList.contains('red-text')) {
            $.web2py.flash('Already voted the editorial');
        } else {
            if (isLoggedIn === false) {
                $.web2py.flash('Please login to cast your vote');
            } else {
                $.ajax({
                    url: loveEditorialURL + '/' + $(this).data()['id'],
                    method: 'POST',
                    success: function(response) {
                        $loveCount.html(response['love_count']);
                        $heartIcon.classList.add('red-text');
                        $.web2py.flash('Vote submitted');
                    },
                    error: function(response) {
                        if (response.status === 401) {
                            $.web2py.flash('Please login to cast your vote');
                        } else {
                            $.web2py.flash('Error submitting your vote');
                        }
                    }
                });
            }
        }
    });
};

var initTooltips = function() {
    $('.tooltipped').tooltip({
        delay: 50
    });
};

(function($) {
    "use strict";

    /* Handle the case of single digit in a time stamp */
    var formatTimeStamp = function(num) {
        var strnum = num.toString();

        return strnum.length == 1 ? '0' + strnum : strnum;
    };

    /* Transform the timestamps from IST to client browser timestamp */
    var transformTimestamps = function() {
        var clientOffset = new Date().getTimezoneOffset() * 60 * 1000;
        var ISTOffset = -330 * 60 * 1000;
        var convertedTimestamp, ISTTimestamp, d;

        $('.stopstalk-timestamp').each(function(i, td) {
            ISTTimestamp = new Date(td.textContent.replace(/-/g, '/')).getTime();
            d = new Date(ISTTimestamp + ISTOffset - clientOffset);
            convertedTimestamp = [d.getFullYear(),
                d.getMonth() + 1,
                d.getDate()
            ].map(formatTimeStamp).join('-') + " " + [d.getHours(),
                d.getMinutes(),
                d.getSeconds()
            ].map(formatTimeStamp).join(':');
            $(this).html(convertedTimestamp);
        });
    };

    /* Download a submission as a file */
    var downloadSubmission = function(fileName, text) {
        var element = document.createElement('a');

        element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
        element.setAttribute('download', fileName);
        element.style.display = 'none';
        document.body.appendChild(element);
        element.click();
        document.body.removeChild(element);
    };

    /* Copy the code to the clipboard */
    var copyToClipBoard = function(copyText) {
        var textField = document.createElement('textarea');

        textField.innerHTML = copyText;
        document.body.appendChild(textField);
        textField.select();
        document.execCommand("copy");
        textField.remove();

        $.web2py.flash("Copied to Clipboard");
    };

    /* Escape characters in html to be representable as string */
    var htmlifySubmission = function(solutionCode) {
        return solutionCode.replace(/&/g, "&")
            .replace(/>/g, ">")
            .replace(/</g, "<")
            .replace(/"/g, """).trim(' ');
    };

    /* Get a url parameter by the key from a string url */
    var getParameterByName = function (name, url) {
        if (!url) {
          url = window.location.href;
        }
        name = name.replace(/[\[\]]/g, "\\$&");
        var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
            results = regex.exec(url);
        if (!results) return null;
        if (!results[2]) return '';
        return decodeURIComponent(results[2].replace(/\+/g, " "));
    };

    var markPulseButtonClick = function(elementID, key) {
        if (isLoggedIn && !clickedButton[key]) {
            $.ajax({
                url: markReadURL,
                data: {key: key},
                success: function() {
                    clickedButton[key] = true;
                    $('#' + elementID).removeClass('pulse');
                }
            });
        }
    };

    var problemDifficultyResponseHandler = function(response) {
        if(response["score"]) $(".difficulty-list-item input[value='" + response["score"] + "']").prop("checked", true);
        $("#problem-difficulty-modal-form").attr("data-problem", response["problem_id"]);
    };

    var openProblemDifficultyModal = function(explicitClick, problemId) {
        explicitClick = explicitClick || false;
        problemId = problemId || null;
        if (!isLoggedIn ||
            (window.localStorage["lastShowedProblemDifficulty"] &&
             (Date.now() - window.localStorage["lastShowedProblemDifficulty"] < 24 * 60 * 60 * 100) && !explicitClick)) {
            // Modal showed less than 24 hours before;
            return;
        }

        $.ajax({
            url: getNextProblemURL,
            method: 'GET',
            data: {problem_id: problemId},
            success: function(response) {
                problemDifficultyResponseHandler(response);
                $('#problem-difficulty-title').html("How difficult is " + response["pname"] + "?");
                $('#problem-difficulty-modal').modal('open');
                problemDifficultyModalOpen = true;
            },
            error: function(e) {
                console.log("Failed to get the next problem", e);
            }
        });

    };

    var initProblemDifficultySubmitHandler = function() {
        $('#problem-difficulty-modal-form').on('submit', function(e) {
            var $thisForm = $('#problem-difficulty-modal-form'),
                $thisTitle = $('#problem-difficulty-title'),
                $thisProblemLink = $("#problem-details-link");

            $.ajax({
                method: 'GET',
                url: problemDifficultySubmitURL + '.json',
                data: {
                    "score":  $("input[name='problem_difficulty_value']:checked").val(),
                    "problem_id": $("#problem-difficulty-modal-form").attr("data-problem")
                },
                success: function(response) {
                    setTimeout(function() {
                        problemDifficultyResponseHandler(response);
                        $('#problem-difficulty-modal-submit-button').val('Submit');
                        $('#problem-difficulty-modal-submit-button').removeClass('disabled');

                        $thisForm.trigger('reset');

                        $thisForm.fadeOut(function() {
                            $thisForm.fadeIn();
                        });

                        $thisTitle.fadeOut(function() {
                            // $thisTitle.html(problemTitles[Math.floor(Math.random() * problemTitles.length)].replace("___", "" + response["pname"] + ""));
                            $thisTitle.html("How difficult is " + response["pname"] + "?");
                            $thisTitle.fadeIn();
                        });

                        $thisProblemLink.fadeOut(function() {
                            $thisProblemLink.html();
                            $thisProblemLink.href = response["plink"];
                            $thisProblemLink.fadeIn();
                        });
                    }, 100);
                },
                error: function(err) {
                    console.log(err);
                }
            });
            e.preventDefault();
        });
    };

    $(document).ready(function() {

        var $viewDownloadButton = $('#final-view-download-button'),
            $downloadButton = $('#final-download-button'),
            $downloadModal = $('#download-submission-modal'),
            $viewFileName = $('#view_download_file_name'),
            $viewPreLoader = $('#view-submission-preloader'),
            $viewPre = $('#view-submission-pre'),
            $viewModal = $('#view-submission-modal'),
            $linkToSite = $('#link-to-site');

        transformTimestamps();

        $("#welcome a").on('touchend', function(){
            window.location.href = $(this).attr('href');
        });

        $('.collapsible').collapsible({
            accordion: false // A setting that changes the collapsible behavior to expandable instead of the default accordion style
        });

        $('select').material_select();

        $('#problem-difficulty-modal').modal({
            dismissible: true,
            complete: function() {
                window.localStorage["lastShowedProblemDifficulty"] = Date.now();
                problemDifficultyModalOpen = false;
            }
        });

        initTooltips();

        openProblemDifficultyModal();

        initProblemDifficultySubmitHandler();

        if (showProblemDifficultyOnboarding === "True" &&
            window.localStorage["lastShowedProblemDifficulty"]) {
            $('.tap-target').tapTarget('open');
        };

        $('#explain-problem-difficulty').click(function() {
            if (!isLoggedIn) {
                $.web2py.flash("Login to suggest problem difficulty!");
                return;
            } else {
                showProblemDifficultyOnboarding = "False";
                $('.tap-target').tapTarget('close');
                openProblemDifficultyModal(true);
                $.ajax({
                    url: markReadURL,
                    data: {key: "problem_difficulty"}
                });
                $('.tap-target').tapTarget('close');
            }
        });

        $('#problem-page-difficulty-button').click(function() {
            openProblemDifficultyModal(true, problemId);
            $("#problem-difficulty-modal-form").attr("data-problem", problemId);
        });

        $('#open-side-nav').click(function() {
            if (typeof ga !== 'undefined') {
                ga('send', {
                    hitType: 'event',
                    eventCategory: 'button',
                    eventAction: 'click',
                    eventLabel: 'Open Side Navbar'
                });
            }
        });

        $('#open-side-nav').sideNav();

        // Remove focus from any focused element
        if (document.activeElement) {
            document.activeElement.blur();
        }

        $(document).on('mouseenter', '.submissions-table tr, .user-editorials-table tr', function() {
            var todoIcon = $(this).find('.add-to-todo-list');
            todoIcon.show();
        });

        $(document).on('mouseleave', '.submissions-table tr, .user-editorials-table tr', function() {
            var todoIcon = $(this).find('.add-to-todo-list');
            todoIcon.hide();
        });

        $(document).on('click', '.add-to-todo-list', function() {
            var stopstalkLink = this.parentElement.firstChild["href"],
                problemLink = getParameterByName("plink", stopstalkLink),
                thisElement = this,
                $thisElement = $(this),
                problemId = $thisElement.data()["pid"];
            $.ajax({
                url: addTodoURL,
                method: 'POST',
                data: {pid: problemId},
                success: function(response) {
                    var tooltipID = thisElement.getAttribute("data-tooltip-id");
                    $thisElement.remove();
                    $('#' + tooltipID).remove();
                    $.web2py.flash(response);
                },
                error: function(e) {
                    $.web2py.flash("Some error occurred");
                }
            });
        });

        $(document).on('click', '.delete-editorial', function() {
            var result = confirm("Are you sure you want to DELETE the editorial ?")
            if(!result) return;
            var $this = $(this);
            $.ajax({
                url: deleteEditorialURL + '/' + $this.data('id'),
                method: 'POST',
                success: function(response) {
                    if (response === "SUCCESS") {
                        $this.parent().parent().fadeOut();
                        $.web2py.flash("Editorial deleted successfully!");
                    } else {
                        $.web2py.flash("Can't delete accepted Editorial!");
                    }
                },
                error: function(err) {
                    $.web2py.flash("Something went wrong!");
                }
            });
        });

        $(document).on('click', '.view-submission-button', function() {
            var site = $(this).data('site');
            var viewLink = $(this).data('view-link');

            var onViewModalClose = function() {
                $viewDownloadButton.addClass('disabled');
                $viewFileName.val('');
                $viewPreLoader.removeClass('hide');
                $viewPre.addClass('hide');
                $viewPre.removeClass('prettyprint');
                $viewPre.removeClass('prettyprinted');
                $viewPre.html('');
                $('#copy-to-clipboard').addClass('hide');
                $linkToSite.html('');
                $linkToSite.attr('href', '');
            };

            $linkToSite.html('View on ' + site);
            $linkToSite.attr('href', viewLink);

            $viewModal.modal({
                complete: onViewModalClose
            });

            $viewModal.modal('open');

            $.ajax({
                method: 'GET',
                url: '/default/download_submission',
                data: {
                    site: site,
                    viewLink: viewLink
                }
            }).done(function(response) {
                var solutionText;

                $viewPreLoader.addClass('hide');
                $viewPre.removeClass('hide');

                $('#copy-to-clipboard').removeClass('hide');

                if (response != -1) {
                    solutionText = htmlifySubmission(response);
                } else {
                    solutionText = "// Unable to retrieve submission from " + site + "\n" + "/* Possible reasons :-\n\t * " + site + " is down\n\t * The submission is from an ongoing contest\n\t * There was some error from our side. \n\t   Please write to us (https://www.stopstalk.com/contact_us)\n */";
                }

                $viewPre.html(solutionText);
                $viewModal.one('click', '#copy-to-clipboard', function() {
                    if (response != -1) {
                        copyToClipBoard(response);
                    } else {
                        copyToClipBoard(solutionText);
                    }
                });

                $viewPre.addClass('prettyprint');
                PR.prettyPrint();
                if (response != -1) {
                    $viewDownloadButton.removeClass('disabled');
                    $viewFileName.on('keydown', function() {
                        if (event.which == 13) {
                            $('#final-view-download-button').trigger('click');
                        }
                    });

                    $(document).one('click', '#final-view-download-button', function() {
                        var fileName = $viewFileName.val();
                        if (fileName) {
                            downloadSubmission(fileName, response);
                            $viewModal.modal('close');
                            onViewModalClose();
                        } else {
                            $.web2py.flash("Please enter a file name");
                        }
                    });
                } else {
                    $.web2py.flash("Unable to Download");
                }
            });
        });

        $(document).on('click', '#heart-button', function() {
            markPulseButtonClick('heart-button', 'heart_button');
        });

        $(document).on('click', '#onboarding-button', function() {
            markPulseButtonClick('onboarding-button', 'onboarding_button');
        });

        $(document).on('click', '.download-submission-button', function() {
            var site = $(this).data('site');
            var viewLink = $(this).data('view-link');

            var onDownloadModalClose = function() {
                /* Callback function on Modal close */
                $downloadButton.addClass('disabled');
                $('#file_name').val('');
            };

            $downloadModal.modal({
                complete: onDownloadModalClose
            });

            $downloadModal.modal('open');

            $.ajax({
                method: 'GET',
                url: '/default/download_submission',
                data: {
                    site: site,
                    viewLink: viewLink
                }
            }).done(function(response) {
                if (response != -1) {
                    $downloadButton.removeClass('disabled');
                    $('#file_name').on('keydown', function() {
                        if (event.which == 13) {
                            $('#final-download-button').trigger('click');
                        }
                    });

                    $(document).one('click', '#final-download-button', function() {
                        var fileName = $('#file_name').val();
                        if (fileName) {
                            downloadSubmission(fileName, response);
                            $downloadModal.modal('close');
                            onDownloadModalClose();
                        } else {
                            $.web2py.flash("Please enter a file name");
                        }
                    });
                } else {
                    $.web2py.flash("Unable to Download");
                }
            });
        });
    });
})(jQuery);
<|endoftext|>


<commit_before>$(document).ready(function() {
  var i18nOpts = {
    lng: 'en',
    fallbackLng: 'en',
    resGetPath: 'static/locales/__lng__.json'
  };

  var translate = function() {
    $('.i18container').i18n();
    $('#i18_navbar').i18n();
    $('#i18_about_me').i18n();
    $('#i18_skills').i18n();
    $('#i18_projects').i18n();
    $('#i18_career').i18n();
    $('#i18_blog').i18n();
    $('#i18_link').i18n();
  };

  $("[id^=set_lang]").each(function() {
    var $this = $(this);
    $this.on('click', function() {
      i18n.setLng($this.data('locale'), translate);
    });
  });

  $.i18n.init(i18nOpts, translate);
});
<commit_msg>Updated default language dor portugues<commit_after>$(document).ready(function() {
  var i18nOpts = {
    lng: 'br',
    fallbackLng: 'br',
    resGetPath: 'static/locales/__lng__.json'
  };

  var translate = function() {
    $('.i18container').i18n();
    $('#i18_navbar').i18n();
    $('#i18_about_me').i18n();
    $('#i18_skills').i18n();
    $('#i18_projects').i18n();
    $('#i18_career').i18n();
    $('#i18_blog').i18n();
    $('#i18_link').i18n();
  };

  $("[id^=set_lang]").each(function() {
    var $this = $(this);
    $this.on('click', function() {
      i18n.setLng($this.data('locale'), translate);
    });
  });

  $.i18n.init(i18nOpts, translate);
});
<|endoftext|>


<commit_before>import $ from 'jquery'
import Dropdown from '../../com/dropdown'
import NavTree from '../../com/nav-tree'
import './api.scss'

function getVersion(element) {
  let version = $(element).attr('data-kotlin-version');
  if (version.startsWith("Kotlin ")) {
    version = version.substring("Kotlin ".length)
  }
  return version
}

function updateState(state) {
  localStorage.setItem("apiState", JSON.stringify(state));
  const $platformDependentElements = $('[data-platform]');
  const $versionDependentElements = $('[data-kotlin-version]');
  $versionDependentElements.removeClass('hidden');
  $platformDependentElements.removeClass('hidden');

  if (state.platform.toLowerCase() != 'all') {
    $platformDependentElements.each((ind, element) => {
      const $element = $(element);
      if ($element.attr('data-platform').toLowerCase() == state.platform.toLowerCase()) return;
      $element.addClass('hidden')
    })
  }

  $versionDependentElements.each((ind, element) => {
    const $element = $(element);
    const version = getVersion(element);
    if (version <= state.version) return;
    $element.addClass('hidden')
  })
}

function addSelectToPanel(panelElement, title, config) {
  const selectElement = $(`<div class="api-panel__select"><span class="api-panel__dropdown-title">${title}</span></div>`);
  $(panelElement).append(selectElement);
  new Dropdown(selectElement, config);
}

function initializeSelects() {
  const $breadcrumbs = $('.api-docs-breadcrumbs');
  if ($breadcrumbs.length > 0) {
    $breadcrumbs
      .wrap('

')
      .after('

');
  } else {
    $('.page-content').prepend('

');
  }

  const switchersPanel = $('.api-panel__switchers')[0];

  const state = localStorage.getItem("apiState") ?
    JSON.parse(localStorage.getItem("apiState")) :
    {
      platform: 'all',
      version: '1.1'
    };
  updateState(state);

  addSelectToPanel(switchersPanel, "Platform", {
    items: {
      'all': 'All',
      'jvm': 'JVM',
      'js': 'JS'
    },
    selected: state.platform,
    onSelect: (platform) => {
      state.platform = platform;
      updateState(state)
    }
  });

  addSelectToPanel(switchersPanel, "Version", {
    items: {
      '1.0': '1.0',
      '1.1': '1.1',
      '1.2': '1.2'
    },
    selected: state.version,
    onSelect: (version) => {
      state.version = version;
      updateState(state)
    }
  })
}

function addTag(rowElement, tag) {
  let $tagsElement = $(rowElement).find('.tags');
  if ($tagsElement.length == 0) {
    $tagsElement = $('

');
    $(rowElement).find('td:first').append($tagsElement);
  }
  $tagsElement.append(`<div class="tags__tag">${tag}</div>`)
}

function addTags() {
  $('[data-platform]').each((ind, element) => {
    const platform = element.getAttribute('data-platform');
    addTag(element, platform)
  });

  $('[data-kotlin-version]').each((ind, element) => addTag(element, getVersion(element)));

  $('[data-jre-version]').each((ind, element) => {
    const platform = element.getAttribute('data-jre-version');
    addTag(element, platform)
  });
}

$(document).ready(() => {
  initializeSelects();
  addTags();
  new NavTree(document.querySelector('.js-side-tree-nav'));
});
<commit_msg>Set default lv to 1.2<commit_after>import $ from 'jquery'
import Dropdown from '../../com/dropdown'
import NavTree from '../../com/nav-tree'
import './api.scss'

function getVersion(element) {
  let version = $(element).attr('data-kotlin-version');
  if (version.startsWith("Kotlin ")) {
    version = version.substring("Kotlin ".length)
  }
  return version
}

function updateState(state) {
  localStorage.setItem("apiState", JSON.stringify(state));
  const $platformDependentElements = $('[data-platform]');
  const $versionDependentElements = $('[data-kotlin-version]');
  $versionDependentElements.removeClass('hidden');
  $platformDependentElements.removeClass('hidden');

  if (state.platform.toLowerCase() != 'all') {
    $platformDependentElements.each((ind, element) => {
      const $element = $(element);
      if ($element.attr('data-platform').toLowerCase() == state.platform.toLowerCase()) return;
      $element.addClass('hidden')
    })
  }

  $versionDependentElements.each((ind, element) => {
    const $element = $(element);
    const version = getVersion(element);
    if (version <= state.version) return;
    $element.addClass('hidden')
  })
}

function addSelectToPanel(panelElement, title, config) {
  const selectElement = $(`<div class="api-panel__select"><span class="api-panel__dropdown-title">${title}</span></div>`);
  $(panelElement).append(selectElement);
  new Dropdown(selectElement, config);
}

function initializeSelects() {
  const $breadcrumbs = $('.api-docs-breadcrumbs');
  if ($breadcrumbs.length > 0) {
    $breadcrumbs
      .wrap('

')
      .after('

');
  } else {
    $('.page-content').prepend('

');
  }

  const switchersPanel = $('.api-panel__switchers')[0];

  const state = localStorage.getItem("apiState") ?
    JSON.parse(localStorage.getItem("apiState")) :
    {
      platform: 'all',
      version: '1.2'
    };
  updateState(state);

  addSelectToPanel(switchersPanel, "Platform", {
    items: {
      'all': 'All',
      'jvm': 'JVM',
      'js': 'JS'
    },
    selected: state.platform,
    onSelect: (platform) => {
      state.platform = platform;
      updateState(state)
    }
  });

  addSelectToPanel(switchersPanel, "Version", {
    items: {
      '1.0': '1.0',
      '1.1': '1.1',
      '1.2': '1.2'
    },
    selected: state.version,
    onSelect: (version) => {
      state.version = version;
      updateState(state)
    }
  })
}

function addTag(rowElement, tag) {
  let $tagsElement = $(rowElement).find('.tags');
  if ($tagsElement.length == 0) {
    $tagsElement = $('

');
    $(rowElement).find('td:first').append($tagsElement);
  }
  $tagsElement.append(`<div class="tags__tag">${tag}</div>`)
}

function addTags() {
  $('[data-platform]').each((ind, element) => {
    const platform = element.getAttribute('data-platform');
    addTag(element, platform)
  });

  $('[data-kotlin-version]').each((ind, element) => addTag(element, getVersion(element)));

  $('[data-jre-version]').each((ind, element) => {
    const platform = element.getAttribute('data-jre-version');
    addTag(element, platform)
  });
}

$(document).ready(() => {
  initializeSelects();
  addTags();
  new NavTree(document.querySelector('.js-side-tree-nav'));
});
<|endoftext|>


<commit_before>module.exports = app => {
  app.event('app_mention', async ({ event, context }) => {
    await app.client.chat.postMessage({
        token: context.botToken,
        channel: event.channel,
        text: `<@${event.user.id}> はーい`
      });
  });
};
<commit_msg>I accept my ignorance of async/await<commit_after>module.exports = app => {
  app.event('app_mention', async ({ event, context }) => {
    console.log(event);
    await app.client.chat.postMessage({
        token: context.botToken,
        channel: event.channel,
        text: `<@${event.user.id}> はーい`
      });
  });
};
<|endoftext|>


<commit_before>export { ConcatPointwiseConv };
export { ConcatPointwiseConvPool };

import * as Pool from "../../util/Pool.js";
import { ShuffleInfo, ShuffleInfoPool } from "./ChannelShuffler_ShuffleInfo.js";
import { ConcatGather, ConcatGatherPool } from "./ChannelShuffler_ConcatGather.js";

/**
 * Implement the channel shuffler by 1x1 tf.Conv2D() (i.e. pointwise convolution).
 *
 * Interestingly, although it looks like the most computing intensively (because many multiplications),
 * it is usually the fastest method (faster than concat-reshape-transpose-reshape-split, concat-gather,
 * split-concat) no matter in WebGL backend or CPU backend.
 *
 * Only when output group is one (i.e. no group; all one group), this pointwise-convolution method
 * may become second fastest.
 *
 * In both WebGL and CPU backend, the less the output group count is, the faster the shuffling is. That is,
 * one output group is faster than two (and four, eight, ...) output group. This behavior is the same as the
 * other shuffling method.
 *
 *
 * Concat-PointwiseConv-Split
 *
 * Another style of this implementation is PointwiseConv-Split (i.e. pointwise convolution by only one 1x1
 * filter and then split). Its performance, however, is slower than pointwise convolution of multiple 1x1
 * filters. The reason seems that the tf.split() is a slow operation (especially in mobile).
 *
 *
 *
 * @member {number[]} concatenatedShape
 *   (Please see ShuffleInfo explanation.)
 *
 * @member {number} outputGroupCount
 *   (Please see ShuffleInfo explanation.)
 *
 * @member {ShuffleInfo} shuffleInfo
 *   The information calculated from concatenatedShape and outputGroupCount.
 *
 * @member {tf.tensor4d[]} filtersTensor4dArray
 *   The pointwise convolution filters. They are used to achieve shuffle-split, and will be released by calling disposeResources().
 *
 * @member {number} tensorWeightCountExtracted
 *   The wieght count extracted from inputFloat32Array and used in tensors. Not including Params, because they are not used in
 * tensors. Not including inferenced weights (even if they are used in tensors), because they are not extracted from inputFloat32Array.
 *
 * @member {number} tensorWeightCountTotal
 *   The total wieght count used in tensors. Not including Params, because they are not used in tensors. Including inferenced
 * weights, if they are used in tensors.
 *
 * @member {function} gather
 *   Permute and split the input tensor by gather. It is a function pointer to one of this.gather_XXX().
 *
 * @member {function} concatGather
 *   Concatenate, permute and split the input tensor by concat-gather. It is a function pointer to one of
 * this.concatGather_XXX().
 */
class ConcatPointwiseConv {

  /**
   *
   * @param {number[]} concatenatedShape
   *   Used to calculate shuffleInfo.
   *
   * @param {number} outputGroupCount
   *   Used to calculate shuffleInfo.
   *
   * @exception {Object} If failed (e.g. out of GPU memory).
   *
   * @see ConcatGather
   */
  constructor( concatenatedShape, outputGroupCount ) {
    ConcatPointwiseConv.setAsConstructor.call( this, concatenatedShape, outputGroupCount );
  }

  /**
   *
   * @param {ShuffleInfo} this
   *   The object to be initialized.
   *
   * @param {number[]} concatenatedShape
   *   Used to calculate shuffleInfo.
   *
   * @param {number} outputGroupCount
   *   Used to calculate shuffleInfo.
   *
   * @exception {Object} If failed (e.g. out of GPU memory).
   *
   * @return {ConcatPointwiseConv}
   *   Return the this object.
   *
   * @see ConcatGather
   */
  static setAsConstructor( concatenatedShape, outputGroupCount ) {

    this.tensorWeightCountExtracted = 0;
    this.tensorWeightCountTotal = 0;

    let concatGather = ConcatGatherPool.Singleton.get_or_create_by( concatenatedShape, outputGroupCount );

    const filterHeight = 1; // Pointwise convolution is convolution 2d with 1 x 1 filter.
    const filterWidth = 1;
    const inDepth = concatGather.shuffleInfo.totalChannelCount;
    const outDepth = concatGather.shuffleInfo.channelCountPerGroup;

    // Every filter is a tensor3d [ filterHeight, filterWidth, inDepth ].
    // All filters composes a tensor4d [ filterHeight, filterWidth, inDepth, outDepth ];
    //
    let filtersShape = Pool.Array.Singleton.get_or_create_by( 4 );
    filtersShape[ 0 ] = [ filterHeight ];
    filtersShape[ 1 ] = [ filterWidth ];
    filtersShape[ 2 ] = [ inDepth ];
    filtersShape[ 3 ] = [ outDepth ];

    // Build 1x1 convolution filters for channel shuffling. (as an array of tf.tensor4d).
    try {

//!!! (2022/06/24 Remarked) Old Codes. Use Pool.Array instead.
//       this.filtersTensor4dArray = tf.tidy( "ChannelShuffler.PointwiseConv.init.filtersTensor4dArray", () => {
//         return concatGather.shuffledChannelIndicesTensor1dArray.map( ( shuffledChannelIndicesTensor1d ) => {
//           return tf.tidy( "ChannelShuffler.PointwiseConv.init.filtersTensor4dArray.shuffledChannelIndicesTensor1d", () => {
//
//             // Generate oneHotIndices (tensor2d, int32) by shuffledChannelIndices (tensor1d).
//             let filtersOfOneGroupTensor2d_int32 = tf.oneHot( shuffledChannelIndicesTensor1d, inDepth );
//
//             // Generate oneHotIndices (tensor2d, float32).
//             //
//             // The tf.oneHot() genetates tensor with ( dtype == "int32" ). However, in backend WASM, if tf.conv2d()
//             // input tensor ( dtype == "float32" ) and filter tensor ( dtype == "int32" ), the result will be wrong.
//             // This issue does not exist in backend CPU and WEBGL. For avoiding this problem, convert the filter
//             // tensor from ( dtype == "int32" ) into ( dtype == "float32" ).
//             //
//             let filtersOfOneGroupTensor2d = tf.cast( filtersOfOneGroupTensor2d_int32, "float32" );
//
//             // Transpose it so that the last axis is the outDepth (not inDepth) which conforms to the requirement
//             // of tf.conv2d()'s filters.
//             let filtersOfOneGroupTensor2d_transposed = filtersOfOneGroupTensor2d.transpose();
//
//             // Reinterpret the tensor2d to tensor4d so that it can be used as tf.conv2d()'s filters.
//             let filtersOfOneGroupTensor4d = filtersOfOneGroupTensor2d_transposed.reshape( filtersShape );
//             return filtersOfOneGroupTensor4d;
//           });
//         });
//       });
//
//       if ( this.filtersTensor4dArray ) {
//         for ( let i = 0; i < this.filtersTensor4dArray.length; ++i ) {
//           let filtersTensor4d = this.filtersTensor4dArray[ i ];
//           if ( filtersTensor4d ) {
//             this.tensorWeightCountTotal += filtersTensor4d.size;
//           }
//         }
//       }

      this.filtersTensor4dArray = Pool.Array.Singleton.get_or_create_by( concatGather.shuffledChannelIndicesTensor1dArray.length );
      for ( let i = 0; i < concatGather.shuffledChannelIndicesTensor1dArray.length; ++i ) {
        let shuffledChannelIndicesTensor1d = concatGather.shuffledChannelIndicesTensor1dArray[ i ];
        let filtersTensor4d = tf.tidy( "ChannelShuffler.PointwiseConv.init.filtersTensor4dArray.shuffledChannelIndicesTensor1d", () => {

          // Generate oneHotIndices (tensor2d, int32) by shuffledChannelIndices (tensor1d).
          let filtersOfOneGroupTensor2d_int32 = tf.oneHot( shuffledChannelIndicesTensor1d, inDepth );

          // Generate oneHotIndices (tensor2d, float32).
          //
          // The tf.oneHot() genetates tensor with ( dtype == "int32" ). However, in backend WASM, if tf.conv2d()
          // input tensor ( dtype == "float32" ) and filter tensor ( dtype == "int32" ), the result will be wrong.
          // This issue does not exist in backend CPU and WEBGL. For avoiding this problem, convert the filter
          // tensor from ( dtype == "int32" ) into ( dtype == "float32" ).
          //
          let filtersOfOneGroupTensor2d = tf.cast( filtersOfOneGroupTensor2d_int32, "float32" );

          // Transpose it so that the last axis is the outDepth (not inDepth) which conforms to the requirement
          // of tf.conv2d()'s filters.
          let filtersOfOneGroupTensor2d_transposed = filtersOfOneGroupTensor2d.transpose();

          // Reinterpret the tensor2d to tensor4d so that it can be used as tf.conv2d()'s filters.
          let filtersOfOneGroupTensor4d = filtersOfOneGroupTensor2d_transposed.reshape( filtersShape );
          return filtersOfOneGroupTensor4d;
        });

        this.filtersTensor4dArray[ i ] = filtersTensor4d;

        if ( filtersTensor4d )
          this.tensorWeightCountTotal += filtersTensor4d.size;
      }

      this.shuffleInfo = concatGather.shuffleInfo; // Need the shuffle info.
      concatGather.shuffleInfo = null; // (Because ownership has been transferred.)

    // Exception if failed (e.g. out of (GPU) memory).
    } catch ( e ) {
      throw e;

    } finally {
      Pool.Array.Singleton.recycle( filtersShape );
      filtersShape = null;

      concatGather.disposeResources_and_recycleToPool(); // Always release the look up table (by tensor1d).
      concatGather = null;
    }

    this.gather = this.gather_loop;
    this.concatGather = this.concatGather_dispose_finally_call_loop;

    return this;
  }

  /**
   * Release tf.tensor.
   *
   * Sub-class should override this method (and call super.disposeResources() before return).
   */
  disposeResources() {

    this.concatGather = null;
    this.gather = null;

    this.tensorWeightCountTotal = 0;
    this.tensorWeightCountExtracted = 0;

    if ( this.shuffleInfo ) {
      this.shuffleInfo.disposeResources_and_recycleToPool();
      this.shuffleInfo = null;
    }

    if ( this.filtersTensor4dArray ) {
      for ( let i = 0; i < this.filtersTensor4dArray.length; ++i ) {
        tf.dispose( this.filtersTensor4dArray[ i ] );
        this.filtersTensor4dArray[ i ] = null;
      }
      Pool.Array.Singleton.recycle( this.filtersTensor4dArray );
      this.filtersTensor4dArray = null;
    }

    //super.disposeResources();
  }

  /**
   * After calling this method, this object should be viewed as disposed and should not be operated again.
   */
  disposeResources_and_recycleToPool() {
    this.disposeResources();
    ConcatPointwiseConvPool.Singleton.recycle( this );
  }

  get concatenatedShape() {
    return this.shuffleInfo.concatenatedShape;
  }

  get outputGroupCount() {
    return this.shuffleInfo.outputGroupCount;
  }

  /**
   * Permute and split the input tensor by gather.
   *
   * @param {tf.tensor} concatenatedTensor
   *   An single tensor (not array) to be processed. It should conform to this.shuffleInfo.concatenatedShape.
   *
   * @return {tf.tensor[]}
   *   An array of shuffled tensors. Their total channel count is the same as concatenated tensorArray, but their
   * last dimensions are shuffled.
   */
  gather_loop( concatenatedTensor ) {
    let shuffledSplitedTensorArray = new Array( this.filtersTensor4dArray.length );
    for ( let i = 0; i < shuffledSplitedTensorArray.length; ++i ) {
      // shuffle and split by pointwise convolution (one operation achieves two operations).
      shuffledSplitedTensorArray[ i ] = concatenatedTensor.conv2d( this.filtersTensor4dArray[ i ], 1, "valid" );
    }
    return shuffledSplitedTensorArray;
  }

  /**
   * Concatenate, permute and split the input tensor by concat-gather.
   *
   * @param {tf.tensor[]} tensorArray
   *   An array of tensors to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor[]}
   *   An array of shuffled tensors. Their total channel count is the same as concatenated tensorArray, but their
   * last dimensions are shuffled.
   */
  concatGather_dispose_finally_call_loop( tensorArray ) {
    let concatenatedTensor = tf.concat( tensorArray, this.shuffleInfo.lastAxisId );

    try {
      return this.gather_loop( concatenatedTensor );

    } finally {
      concatenatedTensor.dispose();
    }
  }

}

/**
 * Providing ChannelShuffler.ConcatPointwiseConv
 *
 */
class ConcatPointwiseConvPool extends Pool.Root {

  constructor() {
    super( "ChannelShuffler.ConcatPointwiseConvPool", ConcatPointwiseConv, ConcatPointwiseConv.setAsConstructor );
  }

}

/**
 * Used as default ChannelShuffler.ConcatPointwiseConv provider.
 */
ConcatPointwiseConvPool.Singleton = new ConcatPointwiseConvPool();

<commit_msg>Update ChannelShuffler_ConcatPointwiseConv.js<commit_after>export { ConcatPointwiseConv };
export { ConcatPointwiseConvPool };

import * as Pool from "../../util/Pool.js";
import { ShuffleInfo, ShuffleInfoPool } from "./ChannelShuffler_ShuffleInfo.js";
import { ConcatGather, ConcatGatherPool } from "./ChannelShuffler_ConcatGather.js";

/**
 * Implement the channel shuffler by 1x1 tf.Conv2D() (i.e. pointwise convolution).
 *
 * Interestingly, although it looks like the most computing intensively (because many multiplications),
 * it is usually the fastest method (faster than concat-reshape-transpose-reshape-split, concat-gather,
 * split-concat) no matter in WebGL backend or CPU backend.
 *
 * Only when output group is one (i.e. no group; all one group), this pointwise-convolution method
 * may become second fastest.
 *
 * In both WebGL and CPU backend, the less the output group count is, the faster the shuffling is. That is,
 * one output group is faster than two (and four, eight, ...) output group. This behavior is the same as the
 * other shuffling method.
 *
 *
 * Concat-PointwiseConv-Split
 *
 * Another style of this implementation is PointwiseConv-Split (i.e. pointwise convolution by only one 1x1
 * filter and then split). Its performance, however, is slower than pointwise convolution of multiple 1x1
 * filters. The reason seems that the tf.split() is a slow operation (especially in mobile).
 *
 *
 *
 * @member {number[]} concatenatedShape
 *   (Please see ShuffleInfo explanation.)
 *
 * @member {number} outputGroupCount
 *   (Please see ShuffleInfo explanation.)
 *
 * @member {ShuffleInfo} shuffleInfo
 *   The information calculated from concatenatedShape and outputGroupCount.
 *
 * @member {tf.tensor4d[]} filtersTensor4dArray
 *   The pointwise convolution filters. They are used to achieve shuffle-split, and will be released by calling disposeResources().
 *
 * @member {number} tensorWeightCountExtracted
 *   The wieght count extracted from inputFloat32Array and used in tensors. Not including Params, because they are not used in
 * tensors. Not including inferenced weights (even if they are used in tensors), because they are not extracted from inputFloat32Array.
 *
 * @member {number} tensorWeightCountTotal
 *   The total wieght count used in tensors. Not including Params, because they are not used in tensors. Including inferenced
 * weights, if they are used in tensors.
 *
 * @member {function} gather
 *   Permute and split the input tensor by gather. It is a function pointer to one of this.gather_XXX().
 *
 * @member {function} concatGather
 *   Concatenate, permute and split the input tensor by concat-gather. It is a function pointer to one of
 * this.concatGather_XXX().
 */
class ConcatPointwiseConv {

  /**
   *
   * @param {number[]} concatenatedShape
   *   Used to calculate shuffleInfo.
   *
   * @param {number} outputGroupCount
   *   Used to calculate shuffleInfo.
   *
   * @exception {Object} If failed (e.g. out of GPU memory).
   *
   * @see ConcatGather
   */
  constructor( concatenatedShape, outputGroupCount ) {
    ConcatPointwiseConv.setAsConstructor.call( this, concatenatedShape, outputGroupCount );
  }

  /**
   *
   * @param {ShuffleInfo} this
   *   The object to be initialized.
   *
   * @param {number[]} concatenatedShape
   *   Used to calculate shuffleInfo.
   *
   * @param {number} outputGroupCount
   *   Used to calculate shuffleInfo.
   *
   * @exception {Object} If failed (e.g. out of GPU memory).
   *
   * @return {ConcatPointwiseConv}
   *   Return the this object.
   *
   * @see ConcatGather
   */
  static setAsConstructor( concatenatedShape, outputGroupCount ) {

    this.tensorWeightCountExtracted = 0;
    this.tensorWeightCountTotal = 0;

    let concatGather = ConcatGatherPool.Singleton.get_or_create_by( concatenatedShape, outputGroupCount );

    const filterHeight = 1; // Pointwise convolution is convolution 2d with 1 x 1 filter.
    const filterWidth = 1;
    const inDepth = concatGather.shuffleInfo.totalChannelCount;
    const outDepth = concatGather.shuffleInfo.channelCountPerGroup;

    // Every filter is a tensor3d [ filterHeight, filterWidth, inDepth ].
    // All filters composes a tensor4d [ filterHeight, filterWidth, inDepth, outDepth ];
    //
    let filtersShape = Pool.Array.Singleton.get_or_create_by( 4 );
    filtersShape[ 0 ] = [ filterHeight ];
    filtersShape[ 1 ] = [ filterWidth ];
    filtersShape[ 2 ] = [ inDepth ];
    filtersShape[ 3 ] = [ outDepth ];

    // Build 1x1 convolution filters for channel shuffling. (as an array of tf.tensor4d).
    try {

//!!! (2022/06/24 Remarked) Old Codes. Use Pool.Array instead.
//       this.filtersTensor4dArray = tf.tidy( "ChannelShuffler.PointwiseConv.init.filtersTensor4dArray", () => {
//         return concatGather.shuffledChannelIndicesTensor1dArray.map( ( shuffledChannelIndicesTensor1d ) => {
//           return tf.tidy( "ChannelShuffler.PointwiseConv.init.filtersTensor4dArray.shuffledChannelIndicesTensor1d", () => {
//
//             // Generate oneHotIndices (tensor2d, int32) by shuffledChannelIndices (tensor1d).
//             let filtersOfOneGroupTensor2d_int32 = tf.oneHot( shuffledChannelIndicesTensor1d, inDepth );
//
//             // Generate oneHotIndices (tensor2d, float32).
//             //
//             // The tf.oneHot() genetates tensor with ( dtype == "int32" ). However, in backend WASM, if tf.conv2d()
//             // input tensor ( dtype == "float32" ) and filter tensor ( dtype == "int32" ), the result will be wrong.
//             // This issue does not exist in backend CPU and WEBGL. For avoiding this problem, convert the filter
//             // tensor from ( dtype == "int32" ) into ( dtype == "float32" ).
//             //
//             let filtersOfOneGroupTensor2d = tf.cast( filtersOfOneGroupTensor2d_int32, "float32" );
//
//             // Transpose it so that the last axis is the outDepth (not inDepth) which conforms to the requirement
//             // of tf.conv2d()'s filters.
//             let filtersOfOneGroupTensor2d_transposed = filtersOfOneGroupTensor2d.transpose();
//
//             // Reinterpret the tensor2d to tensor4d so that it can be used as tf.conv2d()'s filters.
//             let filtersOfOneGroupTensor4d = filtersOfOneGroupTensor2d_transposed.reshape( filtersShape );
//             return filtersOfOneGroupTensor4d;
//           });
//         });
//       });
//
//       if ( this.filtersTensor4dArray ) {
//         for ( let i = 0; i < this.filtersTensor4dArray.length; ++i ) {
//           let filtersTensor4d = this.filtersTensor4dArray[ i ];
//           if ( filtersTensor4d ) {
//             this.tensorWeightCountTotal += filtersTensor4d.size;
//           }
//         }
//       }

      this.filtersTensor4dArray = Pool.Array.Singleton.get_or_create_by( concatGather.shuffledChannelIndicesTensor1dArray.length );
      for ( let i = 0; i < concatGather.shuffledChannelIndicesTensor1dArray.length; ++i ) {
        let shuffledChannelIndicesTensor1d = concatGather.shuffledChannelIndicesTensor1dArray[ i ];
        let filtersTensor4d = tf.tidy( "ChannelShuffler.PointwiseConv.init.filtersTensor4dArray.shuffledChannelIndicesTensor1d", () => {

          // Generate oneHotIndices (tensor2d, int32) by shuffledChannelIndices (tensor1d).
          let filtersOfOneGroupTensor2d_int32 = tf.oneHot( shuffledChannelIndicesTensor1d, inDepth );

          // Generate oneHotIndices (tensor2d, float32).
          //
          // The tf.oneHot() genetates tensor with ( dtype == "int32" ). However, in backend WASM, if tf.conv2d()
          // input tensor ( dtype == "float32" ) and filter tensor ( dtype == "int32" ), the result will be wrong.
          // This issue does not exist in backend CPU and WEBGL. For avoiding this problem, convert the filter
          // tensor from ( dtype == "int32" ) into ( dtype == "float32" ).
          //
          let filtersOfOneGroupTensor2d = tf.cast( filtersOfOneGroupTensor2d_int32, "float32" );

          // Transpose it so that the last axis is the outDepth (not inDepth) which conforms to the requirement
          // of tf.conv2d()'s filters.
          let filtersOfOneGroupTensor2d_transposed = filtersOfOneGroupTensor2d.transpose();

          // Reinterpret the tensor2d to tensor4d so that it can be used as tf.conv2d()'s filters.
          let filtersOfOneGroupTensor4d = filtersOfOneGroupTensor2d_transposed.reshape( filtersShape );
          return filtersOfOneGroupTensor4d;
        });

        this.filtersTensor4dArray[ i ] = filtersTensor4d;

        if ( filtersTensor4d )
          this.tensorWeightCountTotal += filtersTensor4d.size;
      }

      this.shuffleInfo = concatGather.shuffleInfo; // Need the shuffle info.
      concatGather.shuffleInfo = null; // (Because ownership has been transferred.)

    // Exception if failed (e.g. out of (GPU) memory).
    } catch ( e ) {
      throw e;

    } finally {
      Pool.Array.Singleton.recycle( filtersShape );
      filtersShape = null;

      concatGather.disposeResources_and_recycleToPool(); // Always release the look up table (by tensor1d).
      concatGather = null;
    }

    this.gather = this.gather_loop;
    this.concatGather = this.concatGather_dispose_finally_call_loop;

    return this;
  }

  /**
   * Release tf.tensor.
   *
   * Sub-class should override this method (and call super.disposeResources() before return).
   */
  disposeResources() {

    this.concatGather = null;
    this.gather = null;

    this.tensorWeightCountTotal = 0;
    this.tensorWeightCountExtracted = 0;

    if ( this.shuffleInfo ) {
      this.shuffleInfo.disposeResources_and_recycleToPool();
      this.shuffleInfo = null;
    }

    if ( this.filtersTensor4dArray ) {
      for ( let i = 0; i < this.filtersTensor4dArray.length; ++i ) {
        tf.dispose( this.filtersTensor4dArray[ i ] );
        this.filtersTensor4dArray[ i ] = null;
      }
      Pool.Array.Singleton.recycle( this.filtersTensor4dArray );
      this.filtersTensor4dArray = null;
    }

    //super.disposeResources();
  }

  /**
   * After calling this method, this object should be viewed as disposed and should not be operated again.
   *
   * Sub-class should override this method for recycling to its pool (and NEVER call super.disposeResources_and_recycleToPool()).
   */
  disposeResources_and_recycleToPool() {
    this.disposeResources();
    ConcatPointwiseConvPool.Singleton.recycle( this );
  }

  get concatenatedShape() {
    return this.shuffleInfo.concatenatedShape;
  }

  get outputGroupCount() {
    return this.shuffleInfo.outputGroupCount;
  }

  /**
   * Permute and split the input tensor by gather.
   *
   * @param {tf.tensor} concatenatedTensor
   *   An single tensor (not array) to be processed. It should conform to this.shuffleInfo.concatenatedShape.
   *
   * @return {tf.tensor[]}
   *   An array of shuffled tensors. Their total channel count is the same as concatenated tensorArray, but their
   * last dimensions are shuffled.
   */
  gather_loop( concatenatedTensor ) {
    let shuffledSplitedTensorArray = new Array( this.filtersTensor4dArray.length );
    for ( let i = 0; i < shuffledSplitedTensorArray.length; ++i ) {
      // shuffle and split by pointwise convolution (one operation achieves two operations).
      shuffledSplitedTensorArray[ i ] = concatenatedTensor.conv2d( this.filtersTensor4dArray[ i ], 1, "valid" );
    }
    return shuffledSplitedTensorArray;
  }

  /**
   * Concatenate, permute and split the input tensor by concat-gather.
   *
   * @param {tf.tensor[]} tensorArray
   *   An array of tensors to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor[]}
   *   An array of shuffled tensors. Their total channel count is the same as concatenated tensorArray, but their
   * last dimensions are shuffled.
   */
  concatGather_dispose_finally_call_loop( tensorArray ) {
    let concatenatedTensor = tf.concat( tensorArray, this.shuffleInfo.lastAxisId );

    try {
      return this.gather_loop( concatenatedTensor );

    } finally {
      concatenatedTensor.dispose();
    }
  }

}

/**
 * Providing ChannelShuffler.ConcatPointwiseConv
 *
 */
class ConcatPointwiseConvPool extends Pool.Root {

  constructor() {
    super( "ChannelShuffler.ConcatPointwiseConvPool", ConcatPointwiseConv, ConcatPointwiseConv.setAsConstructor );
  }

}

/**
 * Used as default ChannelShuffler.ConcatPointwiseConv provider.
 */
ConcatPointwiseConvPool.Singleton = new ConcatPointwiseConvPool();

<|endoftext|>


<commit_before>odoo.define('wysiwyg.widgets.CropImageDialog', function (require) {
'use strict';

var core = require('web.core');
var Dialog = require('wysiwyg.widgets.Dialog');

var _t = core._t;

/**
 * CropImageDialog widget. Let users crop an image.
 */
var CropImageDialog = Dialog.extend({
    template: 'wysiwyg.widgets.crop_image',
    xmlDependencies: Dialog.prototype.xmlDependencies.concat(
        ['/web_editor/static/src/xml/wysiwyg.xml']
    ),
    jsLibs: [
        '/web_editor/static/lib/cropper/js/cropper.js',
    ],
    cssLibs: [
        '/web_editor/static/lib/cropper/css/cropper.css',
    ],
    events: _.extend({}, Dialog.prototype.events, {
        'click .o_crop_options [data-event]': '_onCropOptionClick',
    }),

    /**
     * @constructor
     */
    init: function (parent, options, media) {
        var self = this;
        this.media = media;
        this.$media = $(this.media);
        var src = this.$media.attr('src').split('?')[0];
        this.aspectRatioList = [
            [_t("Free"), '0/0', 0],
            ["16:9", '16/9', 16 / 9],
            ["4:3", '4/3', 4 / 3],
            ["1:1", '1/1', 1],
            ["2:3", '2/3', 2 / 3],
        ];
        this.imageData = {
            imageSrc: src,
            originalSrc: this.$media.data('crop:originalSrc') || src, // the original src for cropped DB images will be fetched later
            mimetype: this.$media.data('crop:mimetype') || (_.str.endsWith(src, '.png') ? 'image/png' : 'image/jpeg'), // the mimetype for DB images will be fetched later
            aspectRatio: this.$media.data('aspectRatio') || this.aspectRatioList[0][1],
            isExternalImage: src.substr(0, 5) !== 'data:' && src[0] !== '/' && src.indexOf(window.location.host) < 0,
        };
        this.options = _.extend({
            title: _t("Crop Image"),
            buttons: this.imageData.isExternalImage ? [{
                text: _t("Close"),
                close: true,
            }] : [{
                text: _t("Save"),
                classes: 'btn-primary',
                click: this.save,
            }, {
                text: _t("Discard"),
                close: true,
            }],
        }, options || {});
        this._super(parent, this.options);
        this.trigger_up('getRecordInfo', _.extend(this.options, {
            callback: function (recordInfo) {
                _.defaults(self.options, recordInfo);
            },
        }));
    },
    /**
     * @override
     */
    willStart: function () {
        var self = this;
        var def = this._super.apply(this, arguments);
        if (this.imageData.isExternalImage) {
            return def;
        }

        var defs = [def];
        var params = {};
        var isDBImage = false;
        var matchImageID = this.imageData.imageSrc.match(/^\/web\/image\/(\d+)/);
        if (matchImageID) {
            params.image_id = parseInt(matchImageID[1]);
            isDBImage = true;
        } else {
            var matchXmlID = this.imageData.imageSrc.match(/^\/web\/image\/([^/?]+)/);
            if (matchXmlID) {
                params.xml_id = matchXmlID[1];
                isDBImage = true;
            }
        }
        if (isDBImage) {
            defs.push(this._rpc({
                route: '/web_editor/get_image_info',
                params: params,
            }).then(function (res) {
                _.extend(self.imageData, res);
            }));
        }
        return Promise.all(defs);
    },
    /**
     * @override
     */
    start: function () {
        this.$cropperImage = this.$('.o_cropper_image');
        if (this.$cropperImage.length) {
            var data = this.$media.data();
            var ratio = 0;
            for (var i = 0; i < this.aspectRatioList.length; i++) {
                if (this.aspectRatioList[i][1] === data.aspectRatio) {
                    ratio = this.aspectRatioList[i][2];
                    break;
                }
            }
            this.$cropperImage.cropper({
                viewMode: 1,
                autoCropArea: 1,
                aspectRatio: ratio,
                data: _.pick(data, 'x', 'y', 'width', 'height', 'rotate', 'scaleX', 'scaleY')
            });
        }
        return this._super.apply(this, arguments);
    },
    /**
     * @override
     */
    destroy: function () {
        if (this.$cropperImage.length) {
            this.$cropperImage.cropper('destroy');
        }
        this._super.apply(this, arguments);
    },
    /**
     * Updates the DOM image with cropped data and associates required
     * information for a potential future save (where required cropped data
     * attachments will be created).
     *
     * @override
     */
    save: function () {
        var self = this;
        var cropperData = this.$cropperImage.cropper('getData');

        // Mark the media for later creation of required cropped attachments...
        this.$media.addClass('o_cropped_img_to_save');

        // ... and attach required data
        this.$media.data('crop:resModel', this.options.res_model);
        this.$media.data('crop:resID', this.options.res_id);
        this.$media.data('crop:id', this.imageData.id);
        this.$media.data('crop:mimetype', this.imageData.mimetype);
        this.$media.data('crop:originalSrc', this.imageData.originalSrc);

        // Mark the media with the cropping information which is required for
        // a future crop edition
        this.$media
            .attr('data-aspect-ratio', this.imageData.aspectRatio)
            .data('aspectRatio', this.imageData.aspectRatio);
        _.each(cropperData, function (value, key) {
            key = _.str.dasherize(key);
            self.$media.attr('data-' + key, value);
            self.$media.data(key, value);
        });

        // Update the media with base64 source for preview before saving
        var canvas = this.$cropperImage.cropper('getCroppedCanvas', {
            width: cropperData.width,
            height: cropperData.height,
        });
        this.$media.attr('src', canvas.toDataURL(this.imageData.mimetype));

        this.final_data = this.media;
        return this._super.apply(this, arguments);
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * Called when a crop option is clicked -> change the crop area accordingly.
     *
     * @private
     * @param {MouseEvent} ev
     */
    _onCropOptionClick: function (ev) {
        var $option = $(ev.currentTarget);
        var opt = $option.data('event');
        var value = $option.data('value');
        switch (opt) {
            case 'ratio':
                this.$cropperImage.cropper('reset');
                this.imageData.aspectRatio = $option.data('label');
                this.$cropperImage.cropper('setAspectRatio', value);
                break;
            case 'zoom':
            case 'rotate':
            case 'reset':
                this.$cropperImage.cropper(opt, value);
                break;
            case 'flip':
                var direction = value === 'horizontal' ? 'x' : 'y';
                var scaleAngle = -$option.data(direction);
                $option.data(direction, scaleAngle);
                this.$cropperImage.cropper('scale' + direction.toUpperCase(), scaleAngle);
                break;
        }
    },
});

return CropImageDialog;
});
<commit_msg>[FIX] web_editor: allow better usability to crop all image zones<commit_after>odoo.define('wysiwyg.widgets.CropImageDialog', function (require) {
'use strict';

var core = require('web.core');
var Dialog = require('wysiwyg.widgets.Dialog');

var _t = core._t;

/**
 * CropImageDialog widget. Let users crop an image.
 */
var CropImageDialog = Dialog.extend({
    template: 'wysiwyg.widgets.crop_image',
    xmlDependencies: Dialog.prototype.xmlDependencies.concat(
        ['/web_editor/static/src/xml/wysiwyg.xml']
    ),
    jsLibs: [
        '/web_editor/static/lib/cropper/js/cropper.js',
    ],
    cssLibs: [
        '/web_editor/static/lib/cropper/css/cropper.css',
    ],
    events: _.extend({}, Dialog.prototype.events, {
        'click .o_crop_options [data-event]': '_onCropOptionClick',
    }),

    /**
     * @constructor
     */
    init: function (parent, options, media) {
        var self = this;
        this.media = media;
        this.$media = $(this.media);
        var src = this.$media.attr('src').split('?')[0];
        this.aspectRatioList = [
            [_t("Free"), '0/0', 0],
            ["16:9", '16/9', 16 / 9],
            ["4:3", '4/3', 4 / 3],
            ["1:1", '1/1', 1],
            ["2:3", '2/3', 2 / 3],
        ];
        this.imageData = {
            imageSrc: src,
            originalSrc: this.$media.data('crop:originalSrc') || src, // the original src for cropped DB images will be fetched later
            mimetype: this.$media.data('crop:mimetype') || (_.str.endsWith(src, '.png') ? 'image/png' : 'image/jpeg'), // the mimetype for DB images will be fetched later
            aspectRatio: this.$media.data('aspectRatio') || this.aspectRatioList[0][1],
            isExternalImage: src.substr(0, 5) !== 'data:' && src[0] !== '/' && src.indexOf(window.location.host) < 0,
        };
        this.options = _.extend({
            title: _t("Crop Image"),
            buttons: this.imageData.isExternalImage ? [{
                text: _t("Close"),
                close: true,
            }] : [{
                text: _t("Save"),
                classes: 'btn-primary',
                click: this.save,
            }, {
                text: _t("Discard"),
                close: true,
            }],
        }, options || {});
        this._super(parent, this.options);
        this.trigger_up('getRecordInfo', _.extend(this.options, {
            callback: function (recordInfo) {
                _.defaults(self.options, recordInfo);
            },
        }));
    },
    /**
     * @override
     */
    willStart: function () {
        var self = this;
        var def = this._super.apply(this, arguments);
        if (this.imageData.isExternalImage) {
            return def;
        }

        var defs = [def];
        var params = {};
        var isDBImage = false;
        var matchImageID = this.imageData.imageSrc.match(/^\/web\/image\/(\d+)/);
        if (matchImageID) {
            params.image_id = parseInt(matchImageID[1]);
            isDBImage = true;
        } else {
            var matchXmlID = this.imageData.imageSrc.match(/^\/web\/image\/([^/?]+)/);
            if (matchXmlID) {
                params.xml_id = matchXmlID[1];
                isDBImage = true;
            }
        }
        if (isDBImage) {
            defs.push(this._rpc({
                route: '/web_editor/get_image_info',
                params: params,
            }).then(function (res) {
                _.extend(self.imageData, res);
            }));
        }
        return Promise.all(defs);
    },
    /**
     * @override
     */
    start: function () {
        this.$cropperImage = this.$('.o_cropper_image');
        if (this.$cropperImage.length) {
            var data = this.$media.data();
            var ratio = 0;
            for (var i = 0; i < this.aspectRatioList.length; i++) {
                if (this.aspectRatioList[i][1] === data.aspectRatio) {
                    ratio = this.aspectRatioList[i][2];
                    break;
                }
            }
            this.$cropperImage.cropper({
                viewMode: 2,
                dragMode: 'move',
                autoCropArea: 1.0,
                aspectRatio: ratio,
                data: _.pick(data, 'x', 'y', 'width', 'height', 'rotate', 'scaleX', 'scaleY')
            });
        }
        return this._super.apply(this, arguments);
    },
    /**
     * @override
     */
    destroy: function () {
        if (this.$cropperImage.length) {
            this.$cropperImage.cropper('destroy');
        }
        this._super.apply(this, arguments);
    },
    /**
     * Updates the DOM image with cropped data and associates required
     * information for a potential future save (where required cropped data
     * attachments will be created).
     *
     * @override
     */
    save: function () {
        var self = this;
        var cropperData = this.$cropperImage.cropper('getData');

        // Mark the media for later creation of required cropped attachments...
        this.$media.addClass('o_cropped_img_to_save');

        // ... and attach required data
        this.$media.data('crop:resModel', this.options.res_model);
        this.$media.data('crop:resID', this.options.res_id);
        this.$media.data('crop:id', this.imageData.id);
        this.$media.data('crop:mimetype', this.imageData.mimetype);
        this.$media.data('crop:originalSrc', this.imageData.originalSrc);

        // Mark the media with the cropping information which is required for
        // a future crop edition
        this.$media
            .attr('data-aspect-ratio', this.imageData.aspectRatio)
            .data('aspectRatio', this.imageData.aspectRatio);
        _.each(cropperData, function (value, key) {
            key = _.str.dasherize(key);
            self.$media.attr('data-' + key, value);
            self.$media.data(key, value);
        });

        // Update the media with base64 source for preview before saving
        var canvas = this.$cropperImage.cropper('getCroppedCanvas', {
            width: cropperData.width,
            height: cropperData.height,
        });
        this.$media.attr('src', canvas.toDataURL(this.imageData.mimetype));

        this.final_data = this.media;
        return this._super.apply(this, arguments);
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * Called when a crop option is clicked -> change the crop area accordingly.
     *
     * @private
     * @param {MouseEvent} ev
     */
    _onCropOptionClick: function (ev) {
        var $option = $(ev.currentTarget);
        var opt = $option.data('event');
        var value = $option.data('value');
        switch (opt) {
            case 'ratio':
                this.$cropperImage.cropper('reset');
                this.imageData.aspectRatio = $option.data('label');
                this.$cropperImage.cropper('setAspectRatio', value);
                break;
            case 'zoom':
            case 'rotate':
            case 'reset':
                this.$cropperImage.cropper(opt, value);
                break;
            case 'flip':
                var direction = value === 'horizontal' ? 'x' : 'y';
                var scaleAngle = -$option.data(direction);
                $option.data(direction, scaleAngle);
                this.$cropperImage.cropper('scale' + direction.toUpperCase(), scaleAngle);
                break;
        }
    },
});

return CropImageDialog;
});
<|endoftext|>