Refactor heatmap to vue component (#5401)
This commit is contained in:
		
							parent
							
								
									c03a9b3e42
								
							
						
					
					
						commit
						e09fe48773
					
				
					 18 changed files with 258 additions and 384 deletions
				
			
		| 
						 | 
				
			
			@ -85,8 +85,6 @@ MAX_DISPLAY_FILE_SIZE = 8388608
 | 
			
		|||
SHOW_USER_EMAIL = true
 | 
			
		||||
; Set the default theme for the Gitea install
 | 
			
		||||
DEFAULT_THEME = gitea
 | 
			
		||||
; Set the color range to use for heatmap (default to `['#f4f4f4', '#459928']` but can use `['#2d303b', '#80bb46']` with the theme `arc-green`)
 | 
			
		||||
HEATMAP_COLOR_RANGE = `['#f4f4f4', '#459928']`
 | 
			
		||||
 | 
			
		||||
[ui.admin]
 | 
			
		||||
; Number of users that are displayed on one page
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -301,7 +301,6 @@ var (
 | 
			
		|||
		MaxDisplayFileSize  int64
 | 
			
		||||
		ShowUserEmail       bool
 | 
			
		||||
		DefaultTheme        string
 | 
			
		||||
		HeatmapColorRange   string
 | 
			
		||||
 | 
			
		||||
		Admin struct {
 | 
			
		||||
			UserPagingNum   int
 | 
			
		||||
| 
						 | 
				
			
			@ -328,7 +327,6 @@ var (
 | 
			
		|||
		ThemeColorMetaTag:   `#6cc644`,
 | 
			
		||||
		MaxDisplayFileSize:  8388608,
 | 
			
		||||
		DefaultTheme:        `gitea`,
 | 
			
		||||
		HeatmapColorRange:   `['#f4f4f4', '#459928']`,
 | 
			
		||||
		Admin: struct {
 | 
			
		||||
			UserPagingNum   int
 | 
			
		||||
			RepoPagingNum   int
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -193,9 +193,6 @@ func NewFuncMap() []template.FuncMap {
 | 
			
		|||
		"DefaultTheme": func() string {
 | 
			
		||||
			return setting.UI.DefaultTheme
 | 
			
		||||
		},
 | 
			
		||||
		"HeatmapColorRange": func() string {
 | 
			
		||||
			return setting.UI.HeatmapColorRange
 | 
			
		||||
		},
 | 
			
		||||
		"dict": func(values ...interface{}) (map[string]interface{}, error) {
 | 
			
		||||
			if len(values) == 0 {
 | 
			
		||||
				return nil, errors.New("invalid dict call")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
						 | 
				
			
			@ -2293,6 +2293,96 @@ function cancelStopwatch() {
 | 
			
		|||
    $("#cancel_stopwatch_form").submit();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initHeatmap(appElementId, heatmapUser, locale) {
 | 
			
		||||
    var el = document.getElementById(appElementId);
 | 
			
		||||
    if (!el) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    locale = locale || {};
 | 
			
		||||
 | 
			
		||||
    locale.contributions = locale.contributions || 'contributions';
 | 
			
		||||
    locale.no_contributions = locale.no_contributions || 'No contributions';
 | 
			
		||||
 | 
			
		||||
    var vueDelimeters = ['${', '}'];
 | 
			
		||||
 | 
			
		||||
    Vue.component('activity-heatmap', {
 | 
			
		||||
        delimiters: vueDelimeters,
 | 
			
		||||
 | 
			
		||||
        props: {
 | 
			
		||||
            user: {
 | 
			
		||||
                type: String,
 | 
			
		||||
                required: true
 | 
			
		||||
            },
 | 
			
		||||
            suburl: {
 | 
			
		||||
                type: String,
 | 
			
		||||
                required: true
 | 
			
		||||
            },
 | 
			
		||||
            locale: {
 | 
			
		||||
                type: Object,
 | 
			
		||||
                required: true
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        data: function () {
 | 
			
		||||
            return {
 | 
			
		||||
                isLoading: true,
 | 
			
		||||
                colorRange: [],
 | 
			
		||||
                endDate: null,
 | 
			
		||||
                values: []
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        mounted: function() {
 | 
			
		||||
            this.colorRange = [
 | 
			
		||||
                this.getColor(0),
 | 
			
		||||
                this.getColor(1),
 | 
			
		||||
                this.getColor(2),
 | 
			
		||||
                this.getColor(3),
 | 
			
		||||
                this.getColor(4),
 | 
			
		||||
                this.getColor(5)
 | 
			
		||||
            ];
 | 
			
		||||
            console.log(this.colorRange);
 | 
			
		||||
            this.endDate = new Date();
 | 
			
		||||
            this.loadHeatmap(this.user);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        methods: {
 | 
			
		||||
            loadHeatmap: function(userName) {
 | 
			
		||||
                var self = this;
 | 
			
		||||
                $.get(this.suburl + '/api/v1/users/' + userName + '/heatmap', function(chartRawData) {
 | 
			
		||||
                    var chartData = [];
 | 
			
		||||
                    for (var i = 0; i < chartRawData.length; i++) {
 | 
			
		||||
                        chartData[i] = { date: new Date(chartRawData[i].timestamp * 1000), count: chartRawData[i].contributions };
 | 
			
		||||
                    }
 | 
			
		||||
                    self.values = chartData;
 | 
			
		||||
                    self.isLoading = false;
 | 
			
		||||
                });
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            getColor: function(idx) {
 | 
			
		||||
                var el = document.createElement('div');
 | 
			
		||||
                el.className = 'heatmap-color-' + idx;
 | 
			
		||||
 | 
			
		||||
                return getComputedStyle(el).backgroundColor;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        template: '<div><div v-show="isLoading"><slot name="loading"></slot></div><calendar-heatmap v-show="!isLoading" :locale="locale" :no-data-text="locale.no_contributions" :tooltip-unit="locale.contributions" :end-date="endDate" :values="values" :range-color="colorRange" />'
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    new Vue({
 | 
			
		||||
        delimiters: vueDelimeters,
 | 
			
		||||
        el: el,
 | 
			
		||||
 | 
			
		||||
        data: {
 | 
			
		||||
            suburl: document.querySelector('meta[name=_suburl]').content,
 | 
			
		||||
            heatmapUser: heatmapUser,
 | 
			
		||||
            locale: locale
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initFilterBranchTagDropdown(selector) {
 | 
			
		||||
    $(selector).each(function() {
 | 
			
		||||
        var $dropdown = $(this);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -605,3 +605,27 @@ footer {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.heatmap-color-0 {
 | 
			
		||||
    background-color: #f4f4f4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.heatmap-color-1 {
 | 
			
		||||
    background-color: #d7e5db;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.heatmap-color-2 {
 | 
			
		||||
    background-color: #adc7ab;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.heatmap-color-3 {
 | 
			
		||||
    background-color: #83a87b;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.heatmap-color-4 {
 | 
			
		||||
    background-color: #598a4b;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.heatmap-color-5 {
 | 
			
		||||
    background-color: #2f6b1b;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -818,3 +818,7 @@
 | 
			
		|||
        color: #9e9e9e;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.heatmap-color-0 {
 | 
			
		||||
    background-color: #2d303b;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										11
									
								
								public/vendor/librejs.html
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								public/vendor/librejs.html
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -136,14 +136,9 @@
 | 
			
		|||
          <td><a href="https://github.com/swagger-api/swagger-ui/archive/v3.0.4.tar.gz">swagger-ui-v3.0.4.tar.gz</a></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
          <td><a href="./plugins/d3/">d3</a></td>
 | 
			
		||||
          <td><a href="https://github.com/d3/d3/blob/master/LICENSE">BSD 3-Clause</a></td>
 | 
			
		||||
          <td><a href="https://github.com/d3/d3/releases/download/v4.13.0/d3.zip">d3.zip</a></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
          <td><a href="./plugins/calendar-heatmap/">calendar-heatmap</a></td>
 | 
			
		||||
          <td><a href="https://github.com/DKirwan/calendar-heatmap/blob/master/LICENSE">MIT</a></td>
 | 
			
		||||
          <td><a href="https://github.com/DKirwan/calendar-heatmap/archive/master.zip">337b431.zip</a></td>
 | 
			
		||||
          <td><a href="./plugins/vue-calendar-heatmap">vue-calendar-heatmap</a></td>
 | 
			
		||||
          <td><a href="https://github.com/WildCodeSchool/vue-calendar-heatmap/blob/master/README.md">MIT</a></td>
 | 
			
		||||
          <td><a href="https://github.com/WildCodeSchool/vue-calendar-heatmap/archive/master.zip">7f48b20.zip</a></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
          <td><a href="./plugins/moment/">moment.js</a></td>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,27 +0,0 @@
 | 
			
		|||
text.month-name,
 | 
			
		||||
text.calendar-heatmap-legend-text,
 | 
			
		||||
text.day-initial {
 | 
			
		||||
  font-size: 10px;
 | 
			
		||||
  fill: inherit;
 | 
			
		||||
  font-family: Helvetica, arial, 'Open Sans', sans-serif;
 | 
			
		||||
}
 | 
			
		||||
rect.day-cell:hover {
 | 
			
		||||
  stroke: #555555;
 | 
			
		||||
  stroke-width: 1px;
 | 
			
		||||
}
 | 
			
		||||
.day-cell-tooltip {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  z-index: 9999;
 | 
			
		||||
  padding: 5px 9px;
 | 
			
		||||
  color: #bbbbbb;
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  background: rgba(0, 0, 0, 0.85);
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
.day-cell-tooltip > span {
 | 
			
		||||
  font-family: Helvetica, arial, 'Open Sans', sans-serif
 | 
			
		||||
}
 | 
			
		||||
.calendar-heatmap {
 | 
			
		||||
  box-sizing: initial;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,311 +0,0 @@
 | 
			
		|||
// https://github.com/DKirwan/calendar-heatmap
 | 
			
		||||
 | 
			
		||||
function calendarHeatmap() {
 | 
			
		||||
  // defaults
 | 
			
		||||
  var width = 750;
 | 
			
		||||
  var height = 110;
 | 
			
		||||
  var legendWidth = 150;
 | 
			
		||||
  var selector = 'body';
 | 
			
		||||
  var SQUARE_LENGTH = 11;
 | 
			
		||||
  var SQUARE_PADDING = 2;
 | 
			
		||||
  var MONTH_LABEL_PADDING = 6;
 | 
			
		||||
  var now = moment().endOf('day').toDate();
 | 
			
		||||
  var yearAgo = moment().startOf('day').subtract(1, 'year').toDate();
 | 
			
		||||
  var startDate = null;
 | 
			
		||||
  var counterMap= {};
 | 
			
		||||
  var data = [];
 | 
			
		||||
  var max = null;
 | 
			
		||||
  var colorRange = ['#D8E6E7', '#218380'];
 | 
			
		||||
  var tooltipEnabled = true;
 | 
			
		||||
  var tooltipUnit = 'contribution';
 | 
			
		||||
  var legendEnabled = true;
 | 
			
		||||
  var onClick = null;
 | 
			
		||||
  var weekStart = 1; //0 for Sunday, 1 for Monday
 | 
			
		||||
  var locale = {
 | 
			
		||||
    months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
 | 
			
		||||
    days: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
 | 
			
		||||
    No: 'No',
 | 
			
		||||
    on: 'on',
 | 
			
		||||
    Less: 'Less',
 | 
			
		||||
    More: 'More'
 | 
			
		||||
  };
 | 
			
		||||
  var v = Number(d3.version.split('.')[0]);
 | 
			
		||||
 | 
			
		||||
  // setters and getters
 | 
			
		||||
  chart.data = function (value) {
 | 
			
		||||
    if (!arguments.length) { return data; }
 | 
			
		||||
    data = value;
 | 
			
		||||
 | 
			
		||||
    counterMap= {};
 | 
			
		||||
 | 
			
		||||
    data.forEach(function (element, index) {
 | 
			
		||||
        var key= moment(element.date).format( 'YYYY-MM-DD' );
 | 
			
		||||
        var counter= counterMap[key] || 0;
 | 
			
		||||
        counterMap[key]= counter + element.count;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return chart;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  chart.max = function (value) {
 | 
			
		||||
    if (!arguments.length) { return max; }
 | 
			
		||||
    max = value;
 | 
			
		||||
    return chart;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  chart.selector = function (value) {
 | 
			
		||||
    if (!arguments.length) { return selector; }
 | 
			
		||||
    selector = value;
 | 
			
		||||
    return chart;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  chart.startDate = function (value) {
 | 
			
		||||
    if (!arguments.length) { return startDate; }
 | 
			
		||||
    yearAgo = value;
 | 
			
		||||
    now = moment(value).endOf('day').add(1, 'year').toDate();
 | 
			
		||||
    return chart;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  chart.colorRange = function (value) {
 | 
			
		||||
    if (!arguments.length) { return colorRange; }
 | 
			
		||||
    colorRange = value;
 | 
			
		||||
    return chart;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  chart.tooltipEnabled = function (value) {
 | 
			
		||||
    if (!arguments.length) { return tooltipEnabled; }
 | 
			
		||||
    tooltipEnabled = value;
 | 
			
		||||
    return chart;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  chart.tooltipUnit = function (value) {
 | 
			
		||||
    if (!arguments.length) { return tooltipUnit; }
 | 
			
		||||
    tooltipUnit = value;
 | 
			
		||||
    return chart;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  chart.legendEnabled = function (value) {
 | 
			
		||||
    if (!arguments.length) { return legendEnabled; }
 | 
			
		||||
    legendEnabled = value;
 | 
			
		||||
    return chart;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  chart.onClick = function (value) {
 | 
			
		||||
    if (!arguments.length) { return onClick(); }
 | 
			
		||||
    onClick = value;
 | 
			
		||||
    return chart;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  chart.locale = function (value) {
 | 
			
		||||
    if (!arguments.length) { return locale; }
 | 
			
		||||
    locale = value;
 | 
			
		||||
    return chart;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  function chart() {
 | 
			
		||||
 | 
			
		||||
    d3.select(chart.selector()).selectAll('svg.calendar-heatmap').remove(); // remove the existing chart, if it exists
 | 
			
		||||
 | 
			
		||||
    var dateRange = ((d3.time && d3.time.days) || d3.timeDays)(yearAgo, now); // generates an array of date objects within the specified range
 | 
			
		||||
    var monthRange = ((d3.time && d3.time.months) || d3.timeMonths)(moment(yearAgo).startOf('month').toDate(), now); // it ignores the first month if the 1st date is after the start of the month
 | 
			
		||||
    var firstDate = moment(dateRange[0]);
 | 
			
		||||
    if (chart.data().length == 0) {
 | 
			
		||||
      max = 0;
 | 
			
		||||
    } else if (max === null) {
 | 
			
		||||
      max = d3.max(chart.data(), function (d) { return d.count; }); // max data value
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // color range
 | 
			
		||||
    var color = ((d3.scale && d3.scale.linear) || d3.scaleLinear)()
 | 
			
		||||
      .range(chart.colorRange())
 | 
			
		||||
      .domain([0, max]);
 | 
			
		||||
 | 
			
		||||
    var tooltip;
 | 
			
		||||
    var dayRects;
 | 
			
		||||
 | 
			
		||||
    drawChart();
 | 
			
		||||
 | 
			
		||||
    function drawChart() {
 | 
			
		||||
      var svg = d3.select(chart.selector())
 | 
			
		||||
        .style('position', 'relative')
 | 
			
		||||
        .append('svg')
 | 
			
		||||
        .attr('width', width)
 | 
			
		||||
        .attr('class', 'calendar-heatmap')
 | 
			
		||||
        .attr('height', height)
 | 
			
		||||
        .style('padding', '36px');
 | 
			
		||||
 | 
			
		||||
      dayRects = svg.selectAll('.day-cell')
 | 
			
		||||
        .data(dateRange);  //  array of days for the last yr
 | 
			
		||||
 | 
			
		||||
      var enterSelection = dayRects.enter().append('rect')
 | 
			
		||||
        .attr('class', 'day-cell')
 | 
			
		||||
        .attr('width', SQUARE_LENGTH)
 | 
			
		||||
        .attr('height', SQUARE_LENGTH)
 | 
			
		||||
        .attr('fill', function(d) { return color(countForDate(d)); })
 | 
			
		||||
        .attr('x', function (d, i) {
 | 
			
		||||
          var cellDate = moment(d);
 | 
			
		||||
          var result = cellDate.week() - firstDate.week() + (firstDate.weeksInYear() * (cellDate.weekYear() - firstDate.weekYear()));
 | 
			
		||||
          return result * (SQUARE_LENGTH + SQUARE_PADDING);
 | 
			
		||||
        })
 | 
			
		||||
        .attr('y', function (d, i) {
 | 
			
		||||
          return MONTH_LABEL_PADDING + formatWeekday(d.getDay()) * (SQUARE_LENGTH + SQUARE_PADDING);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
      if (typeof onClick === 'function') {
 | 
			
		||||
        (v === 3 ? enterSelection : enterSelection.merge(dayRects)).on('click', function(d) {
 | 
			
		||||
          var count = countForDate(d);
 | 
			
		||||
          onClick({ date: d, count: count});
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (chart.tooltipEnabled()) {
 | 
			
		||||
        (v === 3 ? enterSelection : enterSelection.merge(dayRects)).on('mouseover', function(d, i) {
 | 
			
		||||
          tooltip = d3.select(chart.selector())
 | 
			
		||||
            .append('div')
 | 
			
		||||
            .attr('class', 'day-cell-tooltip')
 | 
			
		||||
            .html(tooltipHTMLForDate(d))
 | 
			
		||||
            .style('left', function () { return Math.floor(i / 7) * SQUARE_LENGTH + 'px'; })
 | 
			
		||||
            .style('top', function () {
 | 
			
		||||
              return formatWeekday(d.getDay()) * (SQUARE_LENGTH + SQUARE_PADDING) + MONTH_LABEL_PADDING * 2 + 'px';
 | 
			
		||||
            });
 | 
			
		||||
        })
 | 
			
		||||
        .on('mouseout', function (d, i) {
 | 
			
		||||
          tooltip.remove();
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (chart.legendEnabled()) {
 | 
			
		||||
        var colorRange = [color(0)];
 | 
			
		||||
        for (var i = 3; i > 0; i--) {
 | 
			
		||||
          colorRange.push(color(max / i));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var legendGroup = svg.append('g');
 | 
			
		||||
        legendGroup.selectAll('.calendar-heatmap-legend')
 | 
			
		||||
            .data(colorRange)
 | 
			
		||||
            .enter()
 | 
			
		||||
          .append('rect')
 | 
			
		||||
            .attr('class', 'calendar-heatmap-legend')
 | 
			
		||||
            .attr('width', SQUARE_LENGTH)
 | 
			
		||||
            .attr('height', SQUARE_LENGTH)
 | 
			
		||||
            .attr('x', function (d, i) { return (width - legendWidth) + (i + 1) * 13; })
 | 
			
		||||
            .attr('y', height + SQUARE_PADDING)
 | 
			
		||||
            .attr('fill', function (d) { return d; });
 | 
			
		||||
 | 
			
		||||
        legendGroup.append('text')
 | 
			
		||||
          .attr('class', 'calendar-heatmap-legend-text calendar-heatmap-legend-text-less')
 | 
			
		||||
          .attr('x', width - legendWidth - 13)
 | 
			
		||||
          .attr('y', height + SQUARE_LENGTH)
 | 
			
		||||
          .text(locale.Less);
 | 
			
		||||
 | 
			
		||||
        legendGroup.append('text')
 | 
			
		||||
          .attr('class', 'calendar-heatmap-legend-text calendar-heatmap-legend-text-more')
 | 
			
		||||
          .attr('x', (width - legendWidth + SQUARE_PADDING) + (colorRange.length + 1) * 13)
 | 
			
		||||
          .attr('y', height + SQUARE_LENGTH)
 | 
			
		||||
          .text(locale.More);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      dayRects.exit().remove();
 | 
			
		||||
      var monthLabels = svg.selectAll('.month')
 | 
			
		||||
          .data(monthRange)
 | 
			
		||||
          .enter().append('text')
 | 
			
		||||
          .attr('class', 'month-name')
 | 
			
		||||
          .text(function (d) {
 | 
			
		||||
            return locale.months[d.getMonth()];
 | 
			
		||||
          })
 | 
			
		||||
          .attr('x', function (d, i) {
 | 
			
		||||
            var matchIndex = 0;
 | 
			
		||||
            dateRange.find(function (element, index) {
 | 
			
		||||
              matchIndex = index;
 | 
			
		||||
              return moment(d).isSame(element, 'month') && moment(d).isSame(element, 'year');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return Math.floor(matchIndex / 7) * (SQUARE_LENGTH + SQUARE_PADDING);
 | 
			
		||||
          })
 | 
			
		||||
          .attr('y', 0);  // fix these to the top
 | 
			
		||||
 | 
			
		||||
      locale.days.forEach(function (day, index) {
 | 
			
		||||
        index = formatWeekday(index);
 | 
			
		||||
        if (index % 2) {
 | 
			
		||||
          svg.append('text')
 | 
			
		||||
            .attr('class', 'day-initial')
 | 
			
		||||
            .attr('transform', 'translate(-8,' + (SQUARE_LENGTH + SQUARE_PADDING) * (index + 1) + ')')
 | 
			
		||||
            .style('text-anchor', 'middle')
 | 
			
		||||
            .attr('dy', '2')
 | 
			
		||||
            .text(day);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function pluralizedTooltipUnit (count) {
 | 
			
		||||
      if ('string' === typeof tooltipUnit) {
 | 
			
		||||
        return (tooltipUnit + (count === 1 ? '' : 's'));
 | 
			
		||||
      }
 | 
			
		||||
      for (var i in tooltipUnit) {
 | 
			
		||||
        var _rule = tooltipUnit[i];
 | 
			
		||||
        var _min = _rule.min;
 | 
			
		||||
        var _max = _rule.max || _rule.min;
 | 
			
		||||
        _max = _max === 'Infinity' ? Infinity : _max;
 | 
			
		||||
        if (count >= _min && count <= _max) {
 | 
			
		||||
          return _rule.unit;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function tooltipHTMLForDate(d) {
 | 
			
		||||
      var dateStr = moment(d).format('ddd, MMM Do YYYY');
 | 
			
		||||
      var count = countForDate(d);
 | 
			
		||||
      return '<span><strong>' + (count ? count : locale.No) + ' ' + pluralizedTooltipUnit(count) + '</strong> ' + locale.on + ' ' + dateStr + '</span>';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function countForDate(d) {
 | 
			
		||||
        var key= moment(d).format( 'YYYY-MM-DD' );
 | 
			
		||||
        return counterMap[key] || 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function formatWeekday(weekDay) {
 | 
			
		||||
      if (weekStart === 1) {
 | 
			
		||||
        if (weekDay === 0) {
 | 
			
		||||
          return 6;
 | 
			
		||||
        } else {
 | 
			
		||||
          return weekDay - 1;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return weekDay;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var daysOfChart = chart.data().map(function (day) {
 | 
			
		||||
      return day.date.toDateString();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return chart;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// polyfill for Array.find() method
 | 
			
		||||
/* jshint ignore:start */
 | 
			
		||||
if (!Array.prototype.find) {
 | 
			
		||||
  Array.prototype.find = function (predicate) {
 | 
			
		||||
    if (this === null) {
 | 
			
		||||
      throw new TypeError('Array.prototype.find called on null or undefined');
 | 
			
		||||
    }
 | 
			
		||||
    if (typeof predicate !== 'function') {
 | 
			
		||||
      throw new TypeError('predicate must be a function');
 | 
			
		||||
    }
 | 
			
		||||
    var list = Object(this);
 | 
			
		||||
    var length = list.length >>> 0;
 | 
			
		||||
    var thisArg = arguments[1];
 | 
			
		||||
    var value;
 | 
			
		||||
 | 
			
		||||
    for (var i = 0; i < length; i++) {
 | 
			
		||||
      value = list[i];
 | 
			
		||||
      if (predicate.call(thisArg, value, i, list)) {
 | 
			
		||||
        return value;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return undefined;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
/* jshint ignore:end */
 | 
			
		||||
							
								
								
									
										2
									
								
								public/vendor/plugins/d3/d3.v4.min.js
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								public/vendor/plugins/d3/d3.v4.min.js
									
										
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								public/vendor/plugins/vue-calendar-heatmap/vue-calendar-heatmap.browser.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								public/vendor/plugins/vue-calendar-heatmap/vue-calendar-heatmap.browser.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										112
									
								
								public/vendor/plugins/vue-calendar-heatmap/vue-calendar-heatmap.css
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								public/vendor/plugins/vue-calendar-heatmap/vue-calendar-heatmap.css
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,112 @@
 | 
			
		|||
 | 
			
		||||
svg.vch__wrapper[data-v-a9cfea66] {
 | 
			
		||||
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
 | 
			
		||||
  line-height: 10px;
 | 
			
		||||
}
 | 
			
		||||
svg.vch__wrapper .vch__months__labels__wrapper text.vch__month__label[data-v-a9cfea66] {
 | 
			
		||||
  font-size: 10px;
 | 
			
		||||
}
 | 
			
		||||
svg.vch__wrapper .vch__days__labels__wrapper text.vch__day__label[data-v-a9cfea66],
 | 
			
		||||
svg.vch__wrapper .vch__legend__wrapper text[data-v-a9cfea66] {
 | 
			
		||||
  font-size: 9px;
 | 
			
		||||
}
 | 
			
		||||
svg.vch__wrapper .vch__months__labels__wrapper text.vch__month__label[data-v-a9cfea66],
 | 
			
		||||
svg.vch__wrapper .vch__days__labels__wrapper text.vch__day__label[data-v-a9cfea66],
 | 
			
		||||
svg.vch__wrapper .vch__legend__wrapper text[data-v-a9cfea66] {
 | 
			
		||||
  fill: #767676;
 | 
			
		||||
}
 | 
			
		||||
svg.vch__wrapper rect.vch__day__square[data-v-a9cfea66]:hover {
 | 
			
		||||
  stroke: #555;
 | 
			
		||||
  stroke-width: 1px;
 | 
			
		||||
}
 | 
			
		||||
svg.vch__wrapper rect.vch__day__square[data-v-a9cfea66]:focus {
 | 
			
		||||
  outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.vue-tooltip-theme.tooltip {
 | 
			
		||||
  display: block !important;
 | 
			
		||||
  z-index: 10000;
 | 
			
		||||
}
 | 
			
		||||
.vue-tooltip-theme.tooltip .tooltip-inner {
 | 
			
		||||
  background: rgba(0, 0, 0, .7);
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  color: #ebedf0;
 | 
			
		||||
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  line-height: 16px;
 | 
			
		||||
  padding: 14px 10px;
 | 
			
		||||
}
 | 
			
		||||
.vue-tooltip-theme.tooltip .tooltip-inner b {
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
.vue-tooltip-theme.tooltip .tooltip-arrow {
 | 
			
		||||
  width: 0;
 | 
			
		||||
  height: 0;
 | 
			
		||||
  border-style: solid;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  margin: 5px;
 | 
			
		||||
  border-color: black;
 | 
			
		||||
  z-index: 1;
 | 
			
		||||
}
 | 
			
		||||
.vue-tooltip-theme.tooltip[x-placement^="top"] {
 | 
			
		||||
  margin-bottom: 5px;
 | 
			
		||||
}
 | 
			
		||||
.vue-tooltip-theme.tooltip[x-placement^="top"] .tooltip-arrow {
 | 
			
		||||
  border-width: 5px 5px 0 5px;
 | 
			
		||||
  border-left-color: transparent !important;
 | 
			
		||||
  border-right-color: transparent !important;
 | 
			
		||||
  border-bottom-color: transparent !important;
 | 
			
		||||
  bottom: -5px;
 | 
			
		||||
  left: calc(50% - 5px);
 | 
			
		||||
  margin-top: 0;
 | 
			
		||||
  margin-bottom: 0;
 | 
			
		||||
}
 | 
			
		||||
.vue-tooltip-theme.tooltip[x-placement^="bottom"] {
 | 
			
		||||
  margin-top: 5px;
 | 
			
		||||
}
 | 
			
		||||
.vue-tooltip-theme.tooltip[x-placement^="bottom"] .tooltip-arrow {
 | 
			
		||||
  border-width: 0 5px 5px 5px;
 | 
			
		||||
  border-left-color: transparent !important;
 | 
			
		||||
  border-right-color: transparent !important;
 | 
			
		||||
  border-top-color: transparent !important;
 | 
			
		||||
  top: -5px;
 | 
			
		||||
  left: calc(50% - 5px);
 | 
			
		||||
  margin-top: 0;
 | 
			
		||||
  margin-bottom: 0;
 | 
			
		||||
}
 | 
			
		||||
.vue-tooltip-theme.tooltip[x-placement^="right"] {
 | 
			
		||||
  margin-left: 5px;
 | 
			
		||||
}
 | 
			
		||||
.vue-tooltip-theme.tooltip[x-placement^="right"] .tooltip-arrow {
 | 
			
		||||
  border-width: 5px 5px 5px 0;
 | 
			
		||||
  border-left-color: transparent !important;
 | 
			
		||||
  border-top-color: transparent !important;
 | 
			
		||||
  border-bottom-color: transparent !important;
 | 
			
		||||
  left: -5px;
 | 
			
		||||
  top: calc(50% - 5px);
 | 
			
		||||
  margin-left: 0;
 | 
			
		||||
  margin-right: 0;
 | 
			
		||||
}
 | 
			
		||||
.vue-tooltip-theme.tooltip[x-placement^="left"] {
 | 
			
		||||
  margin-right: 5px;
 | 
			
		||||
}
 | 
			
		||||
.vue-tooltip-theme.tooltip[x-placement^="left"] .tooltip-arrow {
 | 
			
		||||
  border-width: 5px 0 5px 5px;
 | 
			
		||||
  border-top-color: transparent !important;
 | 
			
		||||
  border-right-color: transparent !important;
 | 
			
		||||
  border-bottom-color: transparent !important;
 | 
			
		||||
  right: -5px;
 | 
			
		||||
  top: calc(50% - 5px);
 | 
			
		||||
  margin-left: 0;
 | 
			
		||||
  margin-right: 0;
 | 
			
		||||
}
 | 
			
		||||
.vue-tooltip-theme.tooltip[aria-hidden='true'] {
 | 
			
		||||
  visibility: hidden;
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
  transition: opacity .15s, visibility .15s;
 | 
			
		||||
}
 | 
			
		||||
.vue-tooltip-theme.tooltip[aria-hidden='false'] {
 | 
			
		||||
  visibility: visible;
 | 
			
		||||
  opacity: 1;
 | 
			
		||||
  transition: opacity .15s;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -49,28 +49,6 @@
 | 
			
		|||
		<script src="https://www.google.com/recaptcha/api.js" async></script>
 | 
			
		||||
	{{end}}
 | 
			
		||||
{{end}}
 | 
			
		||||
{{if .EnableHeatmap}}
 | 
			
		||||
	<script src="{{AppSubUrl}}/vendor/plugins/moment/moment.min.js" charset="utf-8"></script>
 | 
			
		||||
	<script src="{{AppSubUrl}}/vendor/plugins/d3/d3.v4.min.js" charset="utf-8"></script>
 | 
			
		||||
	<script src="{{AppSubUrl}}/vendor/plugins/calendar-heatmap/calendar-heatmap.js" charset="utf-8"></script>
 | 
			
		||||
	<script type="text/javascript">
 | 
			
		||||
		$.get( '{{AppSubUrl}}/api/v1/users/{{.HeatmapUser}}/heatmap', function( chartRawData ) {
 | 
			
		||||
			var chartData = [];
 | 
			
		||||
			for (var i = 0; i < chartRawData.length; i++) {
 | 
			
		||||
				chartData[i] = {date: new Date(chartRawData[i].timestamp * 1000), count: chartRawData[i].contributions};
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			$('#loading-heatmap').removeClass('active');
 | 
			
		||||
 | 
			
		||||
			var heatmap = calendarHeatmap()
 | 
			
		||||
				.data(chartData)
 | 
			
		||||
				.selector('#user-heatmap')
 | 
			
		||||
				.colorRange({{SafeJS HeatmapColorRange}})
 | 
			
		||||
				.tooltipEnabled(true);
 | 
			
		||||
			heatmap();
 | 
			
		||||
		});
 | 
			
		||||
	</script>
 | 
			
		||||
{{end}}
 | 
			
		||||
{{if .RequireTribute}}
 | 
			
		||||
	<script src="{{AppSubUrl}}/vendor/plugins/tribute/tribute.min.js"></script>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -136,6 +114,13 @@
 | 
			
		|||
	<!-- JavaScript -->
 | 
			
		||||
	<script src="{{AppSubUrl}}/vendor/plugins/semantic/semantic.min.js"></script>
 | 
			
		||||
	<script src="{{AppSubUrl}}/js/index.js?v={{MD5 AppVer}}"></script>
 | 
			
		||||
{{if .EnableHeatmap}}
 | 
			
		||||
	<script src="{{AppSubUrl}}/vendor/plugins/moment/moment.min.js" charset="utf-8"></script>
 | 
			
		||||
	<script src="{{AppSubUrl}}/vendor/plugins/vue-calendar-heatmap/vue-calendar-heatmap.browser.js" charset="utf-8"></script>
 | 
			
		||||
	<script type="text/javascript">
 | 
			
		||||
		initHeatmap('user-heatmap', '{{.HeatmapUser}}');
 | 
			
		||||
	</script>
 | 
			
		||||
{{end}}
 | 
			
		||||
{{template "custom/footer" .}}
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -102,7 +102,7 @@
 | 
			
		|||
	<link rel="stylesheet" href="{{AppSubUrl}}/vendor/plugins/dropzone/dropzone.css">
 | 
			
		||||
{{end}}
 | 
			
		||||
{{if .EnableHeatmap}}
 | 
			
		||||
	<link rel="stylesheet" href="{{AppSubUrl}}/vendor/plugins/calendar-heatmap/calendar-heatmap.css">
 | 
			
		||||
	<link rel="stylesheet" href="{{AppSubUrl}}/vendor/plugins/vue-calendar-heatmap/vue-calendar-heatmap.css">
 | 
			
		||||
{{end}}
 | 
			
		||||
	<style class="list-search-style"></style>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,8 +6,13 @@
 | 
			
		|||
		<div class="ui mobile reversed stackable grid">
 | 
			
		||||
			<div class="ten wide column">
 | 
			
		||||
				{{if .EnableHeatmap}}
 | 
			
		||||
					<div class="ui active centered inline indeterminate text loader" id="loading-heatmap">{{.i18n.Tr "user.heatmap.loading"}}</div>
 | 
			
		||||
					<div id="user-heatmap"></div>
 | 
			
		||||
					<div id="user-heatmap" style="padding-right: 40px">
 | 
			
		||||
						<activity-heatmap :locale="locale" :suburl="suburl" :user="heatmapUser">
 | 
			
		||||
							<div slot="loading">
 | 
			
		||||
								<div class="ui active centered inline indeterminate text loader" id="loading-heatmap">{{.i18n.Tr "user.heatmap.loading"}}</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</activity-heatmap>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="ui divider"></div>
 | 
			
		||||
				{{end}}
 | 
			
		||||
				{{template "user/dashboard/feeds" .}}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -96,8 +96,13 @@
 | 
			
		|||
 | 
			
		||||
				{{if eq .TabName "activity"}}
 | 
			
		||||
					{{if .EnableHeatmap}}
 | 
			
		||||
						<div class="ui active centered inline indeterminate text loader" id="loading-heatmap">{{.i18n.Tr "user.heatmap.loading"}}</div>
 | 
			
		||||
						<div id="user-heatmap"></div>
 | 
			
		||||
						<div id="user-heatmap" style="padding-right: 40px">
 | 
			
		||||
							<activity-heatmap :locale="locale" :suburl="suburl" :user="heatmapUser">
 | 
			
		||||
								<div slot="loading">
 | 
			
		||||
									<div class="ui active centered inline indeterminate text loader" id="loading-heatmap">{{.i18n.Tr "user.heatmap.loading"}}</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</activity-heatmap>
 | 
			
		||||
						</div>
 | 
			
		||||
						<div class="ui divider"></div>
 | 
			
		||||
					{{end}}
 | 
			
		||||
					<div class="feeds">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue