Refactor heatmap to vue component (#5401)
parent
c03a9b3e42
commit
e09fe48773
|
@ -85,8 +85,6 @@ MAX_DISPLAY_FILE_SIZE = 8388608
|
||||||
SHOW_USER_EMAIL = true
|
SHOW_USER_EMAIL = true
|
||||||
; Set the default theme for the Gitea install
|
; Set the default theme for the Gitea install
|
||||||
DEFAULT_THEME = gitea
|
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]
|
[ui.admin]
|
||||||
; Number of users that are displayed on one page
|
; Number of users that are displayed on one page
|
||||||
|
|
|
@ -301,7 +301,6 @@ var (
|
||||||
MaxDisplayFileSize int64
|
MaxDisplayFileSize int64
|
||||||
ShowUserEmail bool
|
ShowUserEmail bool
|
||||||
DefaultTheme string
|
DefaultTheme string
|
||||||
HeatmapColorRange string
|
|
||||||
|
|
||||||
Admin struct {
|
Admin struct {
|
||||||
UserPagingNum int
|
UserPagingNum int
|
||||||
|
@ -328,7 +327,6 @@ var (
|
||||||
ThemeColorMetaTag: `#6cc644`,
|
ThemeColorMetaTag: `#6cc644`,
|
||||||
MaxDisplayFileSize: 8388608,
|
MaxDisplayFileSize: 8388608,
|
||||||
DefaultTheme: `gitea`,
|
DefaultTheme: `gitea`,
|
||||||
HeatmapColorRange: `['#f4f4f4', '#459928']`,
|
|
||||||
Admin: struct {
|
Admin: struct {
|
||||||
UserPagingNum int
|
UserPagingNum int
|
||||||
RepoPagingNum int
|
RepoPagingNum int
|
||||||
|
|
|
@ -193,9 +193,6 @@ func NewFuncMap() []template.FuncMap {
|
||||||
"DefaultTheme": func() string {
|
"DefaultTheme": func() string {
|
||||||
return setting.UI.DefaultTheme
|
return setting.UI.DefaultTheme
|
||||||
},
|
},
|
||||||
"HeatmapColorRange": func() string {
|
|
||||||
return setting.UI.HeatmapColorRange
|
|
||||||
},
|
|
||||||
"dict": func(values ...interface{}) (map[string]interface{}, error) {
|
"dict": func(values ...interface{}) (map[string]interface{}, error) {
|
||||||
if len(values) == 0 {
|
if len(values) == 0 {
|
||||||
return nil, errors.New("invalid dict call")
|
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();
|
$("#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) {
|
function initFilterBranchTagDropdown(selector) {
|
||||||
$(selector).each(function() {
|
$(selector).each(function() {
|
||||||
var $dropdown = $(this);
|
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;
|
color: #9e9e9e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.heatmap-color-0 {
|
||||||
|
background-color: #2d303b;
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="./plugins/d3/">d3</a></td>
|
<td><a href="./plugins/vue-calendar-heatmap">vue-calendar-heatmap</a></td>
|
||||||
<td><a href="https://github.com/d3/d3/blob/master/LICENSE">BSD 3-Clause</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/d3/d3/releases/download/v4.13.0/d3.zip">d3.zip</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/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>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="./plugins/moment/">moment.js</a></td>
|
<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 */
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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>
|
<script src="https://www.google.com/recaptcha/api.js" async></script>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{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}}
|
{{if .RequireTribute}}
|
||||||
<script src="{{AppSubUrl}}/vendor/plugins/tribute/tribute.min.js"></script>
|
<script src="{{AppSubUrl}}/vendor/plugins/tribute/tribute.min.js"></script>
|
||||||
|
|
||||||
|
@ -136,6 +114,13 @@
|
||||||
<!-- JavaScript -->
|
<!-- JavaScript -->
|
||||||
<script src="{{AppSubUrl}}/vendor/plugins/semantic/semantic.min.js"></script>
|
<script src="{{AppSubUrl}}/vendor/plugins/semantic/semantic.min.js"></script>
|
||||||
<script src="{{AppSubUrl}}/js/index.js?v={{MD5 AppVer}}"></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" .}}
|
{{template "custom/footer" .}}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -102,7 +102,7 @@
|
||||||
<link rel="stylesheet" href="{{AppSubUrl}}/vendor/plugins/dropzone/dropzone.css">
|
<link rel="stylesheet" href="{{AppSubUrl}}/vendor/plugins/dropzone/dropzone.css">
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if .EnableHeatmap}}
|
{{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}}
|
{{end}}
|
||||||
<style class="list-search-style"></style>
|
<style class="list-search-style"></style>
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,13 @@
|
||||||
<div class="ui mobile reversed stackable grid">
|
<div class="ui mobile reversed stackable grid">
|
||||||
<div class="ten wide column">
|
<div class="ten wide column">
|
||||||
{{if .EnableHeatmap}}
|
{{if .EnableHeatmap}}
|
||||||
<div class="ui active centered inline indeterminate text loader" id="loading-heatmap">{{.i18n.Tr "user.heatmap.loading"}}</div>
|
<div id="user-heatmap" style="padding-right: 40px">
|
||||||
<div id="user-heatmap"></div>
|
<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>
|
<div class="ui divider"></div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{template "user/dashboard/feeds" .}}
|
{{template "user/dashboard/feeds" .}}
|
||||||
|
|
|
@ -96,8 +96,13 @@
|
||||||
|
|
||||||
{{if eq .TabName "activity"}}
|
{{if eq .TabName "activity"}}
|
||||||
{{if .EnableHeatmap}}
|
{{if .EnableHeatmap}}
|
||||||
<div class="ui active centered inline indeterminate text loader" id="loading-heatmap">{{.i18n.Tr "user.heatmap.loading"}}</div>
|
<div id="user-heatmap" style="padding-right: 40px">
|
||||||
<div id="user-heatmap"></div>
|
<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>
|
<div class="ui divider"></div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="feeds">
|
<div class="feeds">
|
||||||
|
|
Loading…
Reference in New Issue