martinsonesson.se JsDOC

/*
    IMPORTS
*/

import {getColorAsHex} from './../../helpers/colorGenerator';
import {removeDuplicates} from './../../helpers/helpers';

/**
 * @constructor ProjectsController
 * @memberof controllers
 * @description Controller for the projects page
 * @param {$scope} $scope - See {@link https://code.angularjs.org/1.3.15/docs/api/ng/type/$rootScope.Scope}
 * @param {$log} $log - See {@link https://code.angularjs.org/1.3.15/docs/api/ng/service/$log}
 * @param {$timeout} $timeout - See {@link https://code.angularjs.org/1.3.15/docs/api/ng/service/$timeout}
 * @param {$routeParams} $routeParams - See {@link https://code.angularjs.org/1.3.15/docs/api/ngRoute/service/$routeParams}
 * @param {$location} $location - See {@link https://code.angularjs.org/1.3.15/docs/api/ng/service/$location}
 * @param {$window} $window - See {@link https://code.angularjs.org/1.3.15/docs/api/ng/service/$timeout}
 * @param {services.GithubService} githubService - Service for fetch data from Github API
 */
export default class ProjectsController {
    constructor($scope, $log, $timeout, $routeParams, $location, $window, githubService) {
        this.vm = this;

        this.$log = $log;
        this.$timeout = $timeout;
        this.$routeParams = $routeParams;
        this.githubService = githubService;
        this.$window = $window;
        this.$location = $location;

        this.vm.loading = false;

        this.vm.openProject = this.openProject;
        this.vm.closeProject = this.closeProject;
        this.vm.getClassForDep = this.getClassForDep;

        this.gitHubRepoNames = {
            risk: 'ECMA6Risk',
            starapp: 'StarApp',
            portfolio: 'martinsweb',
            snake: 'GyroSnake',
            instaanalytics: 'InstagramAnalytics',
            wcc: 'WorldCup2014Simulator',
            wh40k: 'Warhammer-40k-Unit-Simulator',
            flappyDoge: 'FlappyDoge'
        };
        this.gitHubUserName = 'ToWelie89';
        this.dependenciesToLookForInPackage = [
            'grunt',
            'angular',
            'webpack', //missing icon
            'babel',
            'karma',
            'react',
            'jquery',
            'bootstrap'
        ];

        this.reset();
        this.setEvents();

        this.$timeout(() => {
            this.setEvents();
            if (this.$routeParams.projectName) {
                this.initProject(this.$routeParams.projectName);
            }
        }, 100);
    }

    /**
     * @function controllers.MenuController#reset
     * @description Reset data used for repository stats
     */
    reset() {
        this.db = {
            js: 0,
            html: 0,
            php: 0,
            css: 0,
            less: 0,
            ts: 0,
            xml: 0,
            json: 0,
            scss: 0,
            cs: 0,
            java: 0,
            svg: 0
        };
        this.vm.usedDependencies = [];
        this.currentOpenProject = '';
    }

    /**
     * @function controllers.MenuController#initProject
     * @param {Obj} project The project to initialize
     * @description Function for initializing a chosen project which includes gathering data of the project repo
     */
    initProject(project) {
        this.vm.loading = true;
        this.reset();
        this.currentOpenProject = project;
        $('#' + this.currentOpenProject + 'Modal').modal('toggle');

        $('.carousel-control.left').click(function() {
            $('.projectCarousel').carousel('prev');
        });
        $('.carousel-control.right').click(function() {
            $('.projectCarousel').carousel('next');
        });

        $('.carousel-indicators li').on('click', function() {
            $('.projectCarousel').carousel($(this).index());
        });

        const localStorageData = JSON.parse(localStorage.getItem(`${this.currentOpenProject}StorageData`));

        if (localStorageData && !this.localStorageDataIsOlderThanOneDay(localStorageData)) {
            this.db = localStorageData.db;
            this.vm.usedDependencies = localStorageData.usedDependencies;
            this.vm.usedDependencies = removeDuplicates(this.vm.usedDependencies);
            this.initializeStatistics();
            this.vm.loading = false;
        } else {
            this.getDataForRepoFromGithub();
        }
    }

    localStorageDataIsOlderThanOneDay(localStorageData) {
        const timeStamp = localStorageData.timeStamp;
        const diff = Date.now() - timeStamp;
        return (diff > (1000 * 60 * 60 * 24));
    }

    getDataForRepoFromGithub() {
        if (this.gitHubRepoNames[this.currentOpenProject]) {
            var url = 'https://api.github.com/repos/' + this.gitHubUserName + '/' + this.gitHubRepoNames[this.currentOpenProject] + '/contents';
            this.githubService.getGithubApiResponseByURL(url)
            .then(response => {
                console.log(response.data);
                this.depth = 0;
                // If one of these files exists in root folder then we can assume project is versioned with Git
                if (response.data.find(f => f.name === '.gitignore' || f.name === '.gitAttributes' || f.name === '.gitModules')) {
                    this.vm.usedDependencies.push('git');
                }
                this.getDependenciesFromPackage(response);
                this.handleContents(response.data);
            });
        }
    }

    /**
     * @function controllers.MenuController#getDependenciesFromPackage
     * @param {String} response The response
     * @description Function for determining which dependencies are used for the project
     */
    getDependenciesFromPackage(response) {
        const packageJson = response.data.find(f => f.name === 'package.json');
        if (packageJson) {
            // Package.json exists, therefore project uses npm
            this.vm.usedDependencies.push('npm');
            this.githubService.getGithubApiResponseByURL(packageJson.download_url)
            .then(resp => {
                const dependencies = Object.keys(resp.data.devDependencies ? resp.data.devDependencies : {})
                                     .concat(Object.keys(resp.data.dependencies ? resp.data.dependencies : {}));

                this.dependenciesToLookForInPackage.forEach(d => {
                    const foundDep = dependencies.find(dep => {
                        return dep.includes(d);
                    });

                    if (foundDep) {
                        this.vm.usedDependencies.push(d);
                    }
                });
            });
        }
    }

    /**
     * @function controllers.MenuController#folderIsBlacklisted
     * @param {String} dirName Name of the folder
     * @description Function for determining if the specified folder is in the blacklist of folders to ignore
     */
    folderIsBlacklisted(dirName) {
        const folderBlackList = [
            'cssLibs',
            'node_modules',
            'bower_components',
            'bin',
            'obj',
            'packages',
            'fonts'
        ];

        return (folderBlackList.includes(dirName) || dirName.includes('ReSharper_'));
    }

    /**
     * @function controllers.MenuController#handleContents
     * @param {Obj} data The content data to process
     * @description Recursive function used to traverse all folders of a repo and get the contents and build up the stats used for graphs
     */
    handleContents(data) {
        data.forEach(d => {
            if (d.type === 'file') {
                var fileParts = d.name.split('.');
                var fileExtension = fileParts[fileParts.length - 1];
                if (this.db[fileExtension] !== undefined) {
                    this.db[fileExtension] = this.db[fileExtension] + d.size;
                }
            } else if (d.type === 'dir' && !this.folderIsBlacklisted(d.name)) {
                this.depth++;
                this.githubService.getGithubApiResponseByURL(d.url)
                    .then(resp => {
                        this.handleContents(resp.data);
                    });
            }
        });
        this.depth--;
        if (this.depth === 0) {
            // Recursion is done
            this.initializeStatistics();
            // Save data to localstorage

            if (this.newDb['js']) { this.vm.usedDependencies.push('javascript'); }
            if (this.newDb['java']) { this.vm.usedDependencies.push('java'); }
            if (this.newDb['cs']) { this.vm.usedDependencies.push('csharp'); }
            if (this.newDb['css']) { this.vm.usedDependencies.push('css'); }
            if (this.newDb['html']) { this.vm.usedDependencies.push('html'); }
            if (this.newDb['scss']) { this.vm.usedDependencies.push('sass'); }
            if (this.newDb['less']) { this.vm.usedDependencies.push('less'); } // Missing icon
            if (this.newDb['sh']) { this.vm.usedDependencies.push('shell'); }
            if (this.newDb['py']) { this.vm.usedDependencies.push('python'); }
            if (this.newDb['php']) { this.vm.usedDependencies.push('php'); }
            if (this.newDb['svg']) { this.vm.usedDependencies.push('svg'); }

            this.vm.usedDependencies = removeDuplicates(this.vm.usedDependencies);

            const data = {
                db: this.newDb,
                usedDependencies: this.vm.usedDependencies,
                timeStamp: Date.now()
            }

            localStorage.setItem(`${this.currentOpenProject}StorageData`, JSON.stringify(data));

            this.vm.loading = false;
        }
    }

    initializeStatistics() {
        const totalSizeSum = Object.values(this.db).reduce((a, b) => a + b, 0);
        this.newDb = {};
        Object.keys(this.db).forEach(key => {
            if (this.db[key] > 0) {
                this.newDb[key] = Math.round((this.db[key] / totalSizeSum) * 100);
            }
        });

        console.log(this.newDb);
        console.log(this.vm.usedDependencies);

        // Convert to array of objects
        this.vm.dbArray = Object.keys(this.newDb).map(x => {
            return {
                key: x,
                value: this.newDb[x]
            };
        });
        // Sort to show most frequent first
        this.vm.dbArray.sort((a, b) => {
            return b.value - a.value;
        });
        // Fix to account for rounding errors where total % would sometime be 99% or 101%
        const totalPercentageSum = this.vm.dbArray.map(x => x.value).reduce((a, b) => a + b, 0);
        if (totalPercentageSum !== 100 && this.vm.dbArray[0]) {
            this.vm.dbArray[0].value += (100 - totalPercentageSum);
        }
    }

    /**
     * @function controllers.MenuController#setEvents
     * @description Set listener for the hide event of the modals
     */
    setEvents() {
        $('.modal').each(function() {
            $(this).on('hide.bs.modal', e => {
                window.location.href = '/#/projects';
            });
        });
    }

    /**
     * @function controllers.ProjectsController#openProject
     * @param {String} The project name
     * @description Opens a modal for the project
     */
    openProject(projectName) {
        this.$location.path('projects/' + projectName);
    }

    /**
     * @function controllers.ProjectsController#closeProject
     * @param {String} The project name
     * @description Closes a modal for the project
     */
    closeProject(projectName) {
        $('#' + projectName + 'Modal').modal('toggle');
        $('.modal-backdrop').css('opacity', 0);
        $('body').removeClass('modal-open');
        $('.modal-backdrop').remove();

        this.$location.path('projects/');
    }

    getClassForDep(dep) {
        switch (dep) {
        case 'angular':
            return 'icon-angular';
        case 'bootstrap':
            return 'icon-bootstrap';
        case 'grunt':
            return 'icon-grunt';
        case 'jquery':
            return 'icon-jquery';
        case 'react':
            return 'icon-react';
        case 'npm':
            return 'icon-npm';
        case 'git':
            return 'icon-git';
        case 'javascript':
            return 'icon-javascript-alt';
        case 'java':
            return 'icon-java-bold';
        case 'csharp':
            return 'icon-csharp';
        case 'css':
            return 'icon-css3-alt';
        case 'html':
            return 'icon-html5-alt';
        case 'sass':
            return 'icon-sass';
        case 'shell':
            return 'icon-shell';
        case 'python':
            return 'icon-python';
        case 'php':
            return 'icon-php';
        case 'svg':
            return 'icon-svg';
        case 'less':
            return 'fab fa-less';
        }
    }

    /**
     * @function controllers.ProjectsController#shuffle
     * @param {Array} The array to shuffle
     * @description Randomly shuffles an array
     */
    shuffle(o) {
        for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
        return o;
    }
}