289 lines
8 KiB
JavaScript
289 lines
8 KiB
JavaScript
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
|
|
import $ from 'jquery';
|
|
import Activities from '~/activities';
|
|
import AjaxCache from '~/lib/utils/ajax_cache';
|
|
import axios from '~/lib/utils/axios_utils';
|
|
import { localTimeAgo } from '~/lib/utils/datetime_utility';
|
|
import { __ } from '~/locale';
|
|
import ActivityCalendar from './activity_calendar';
|
|
import UserOverviewBlock from './user_overview_block';
|
|
|
|
/**
|
|
* UserTabs
|
|
*
|
|
* Handles persisting and restoring the current tab selection and lazily-loading
|
|
* content on the Users#show page.
|
|
*
|
|
* ### Example Markup
|
|
*
|
|
* <ul class="nav-links">
|
|
* <li class="activity-tab active">
|
|
* <a data-action="activity" data-target="#activity" data-toggle="tab" href="/username">
|
|
* Activity
|
|
* </a>
|
|
* </li>
|
|
* <li class="groups-tab">
|
|
* <a data-action="groups" data-target="#groups" data-toggle="tab" href="/users/username/groups">
|
|
* Groups
|
|
* </a>
|
|
* </li>
|
|
* <li class="contributed-tab">
|
|
* ...
|
|
* </li>
|
|
* <li class="projects-tab">
|
|
* ...
|
|
* </li>
|
|
* <li class="snippets-tab">
|
|
* ...
|
|
* </li>
|
|
* </ul>
|
|
*
|
|
* <div class="tab-content">
|
|
* <div class="tab-pane" id="activity">
|
|
* Activity Content
|
|
* </div>
|
|
* <div class="tab-pane" id="groups">
|
|
* Groups Content
|
|
* </div>
|
|
* <div class="tab-pane" id="contributed">
|
|
* Contributed projects content
|
|
* </div>
|
|
* <div class="tab-pane" id="projects">
|
|
* Projects content
|
|
* </div>
|
|
* <div class="tab-pane" id="snippets">
|
|
* Snippets content
|
|
* </div>
|
|
* </div>
|
|
*
|
|
* <div class="loading">
|
|
* Loading Animation
|
|
* </div>
|
|
*/
|
|
|
|
const CALENDAR_TEMPLATE = `
|
|
<div class="calendar">
|
|
<div class="js-contrib-calendar"></div>
|
|
<div class="calendar-hint"></div>
|
|
</div>
|
|
`;
|
|
|
|
const CALENDAR_PERIOD_6_MONTHS = 6;
|
|
const CALENDAR_PERIOD_12_MONTHS = 12;
|
|
/* computation based on
|
|
* width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group);
|
|
* (see activity_calendar.js)
|
|
*/
|
|
const OVERVIEW_CALENDAR_BREAKPOINT = 918;
|
|
|
|
export default class UserTabs {
|
|
constructor({ defaultAction, action, parentEl }) {
|
|
this.loaded = {};
|
|
this.defaultAction = defaultAction || 'overview';
|
|
this.action = action || this.defaultAction;
|
|
this.$parentEl = $(parentEl) || $(document);
|
|
this.windowLocation = window.location;
|
|
this.$parentEl.find('.nav-links a').each((i, navLink) => {
|
|
this.loaded[$(navLink).attr('data-action')] = false;
|
|
});
|
|
this.actions = Object.keys(this.loaded);
|
|
this.bindEvents();
|
|
|
|
// TODO: refactor to make this configurable via constructor params with a default value of 'show'
|
|
if (this.action === 'show') {
|
|
this.action = this.defaultAction;
|
|
}
|
|
|
|
this.activateTab(this.action);
|
|
}
|
|
|
|
bindEvents() {
|
|
this.$parentEl
|
|
.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]')
|
|
.on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', (event) => this.tabShown(event))
|
|
.on('click', '.gl-pagination a', (event) => this.changeProjectsPage(event));
|
|
|
|
window.addEventListener('resize', () => this.onResize());
|
|
}
|
|
|
|
onResize() {
|
|
this.loadActivityCalendar();
|
|
}
|
|
|
|
changeProjectsPage(e) {
|
|
e.preventDefault();
|
|
|
|
$('.tab-pane.active').empty();
|
|
const endpoint = $(e.target).attr('href');
|
|
this.loadTab(this.getCurrentAction(), endpoint);
|
|
}
|
|
|
|
tabShown(event) {
|
|
const $target = $(event.target);
|
|
const action = $target.data('action');
|
|
const source = $target.attr('href');
|
|
const endpoint = $target.data('endpoint');
|
|
this.setTab(action, endpoint);
|
|
return this.setCurrentAction(source);
|
|
}
|
|
|
|
activateTab(action) {
|
|
return this.$parentEl.find(`.nav-links .js-${action}-tab a`).tab('show');
|
|
}
|
|
|
|
setTab(action, endpoint) {
|
|
if (this.loaded[action]) {
|
|
return;
|
|
}
|
|
if (action === 'activity') {
|
|
this.loadActivities();
|
|
} else if (action === 'overview') {
|
|
this.loadOverviewTab();
|
|
}
|
|
|
|
const loadableActions = [
|
|
'groups',
|
|
'contributed',
|
|
'projects',
|
|
'starred',
|
|
'snippets',
|
|
'followers',
|
|
'following',
|
|
];
|
|
if (loadableActions.indexOf(action) > -1) {
|
|
this.loadTab(action, endpoint);
|
|
}
|
|
}
|
|
|
|
loadTab(action, endpoint) {
|
|
this.toggleLoading(true);
|
|
|
|
const params = action === 'projects' ? { skip_namespace: true } : {};
|
|
|
|
return axios
|
|
.get(endpoint, { params })
|
|
.then(({ data }) => {
|
|
const tabSelector = `div#${action}`;
|
|
this.$parentEl.find(tabSelector).html(data.html);
|
|
this.loaded[action] = true;
|
|
localTimeAgo(document.querySelectorAll(`${tabSelector} .js-timeago`));
|
|
|
|
this.toggleLoading(false);
|
|
})
|
|
.catch(() => {
|
|
this.toggleLoading(false);
|
|
});
|
|
}
|
|
|
|
loadActivities() {
|
|
if (this.loaded.activity) {
|
|
return;
|
|
}
|
|
|
|
// eslint-disable-next-line no-new
|
|
new Activities('#activity');
|
|
|
|
this.loaded.activity = true;
|
|
}
|
|
|
|
loadOverviewTab() {
|
|
if (this.loaded.overview) {
|
|
return;
|
|
}
|
|
|
|
this.loadActivityCalendar();
|
|
|
|
UserTabs.renderMostRecentBlocks('#js-overview .activities-block', {
|
|
requestParams: { limit: 10 },
|
|
});
|
|
UserTabs.renderMostRecentBlocks('#js-overview .projects-block', {
|
|
requestParams: { limit: 10, skip_pagination: true, skip_namespace: true, compact_mode: true },
|
|
});
|
|
|
|
this.loaded.overview = true;
|
|
}
|
|
|
|
static renderMostRecentBlocks(container, options) {
|
|
// eslint-disable-next-line no-new
|
|
new UserOverviewBlock({
|
|
container,
|
|
url: $(`${container} .overview-content-list`).data('href'),
|
|
...options,
|
|
postRenderCallback: () => localTimeAgo(document.querySelectorAll(`${container} .js-timeago`)),
|
|
});
|
|
}
|
|
|
|
loadActivityCalendar() {
|
|
const $calendarWrap = this.$parentEl.find('.tab-pane.active .user-calendar');
|
|
if (!$calendarWrap.length || bp.getBreakpointSize() === 'xs') return;
|
|
|
|
const calendarPath = $calendarWrap.data('calendarPath');
|
|
|
|
AjaxCache.retrieve(calendarPath)
|
|
.then((data) => UserTabs.renderActivityCalendar(data, $calendarWrap))
|
|
.catch(() => {
|
|
const cWrap = $calendarWrap[0];
|
|
cWrap.querySelector('.gl-spinner').classList.add('invisible');
|
|
cWrap.querySelector('.user-calendar-error').classList.remove('invisible');
|
|
cWrap
|
|
.querySelector('.user-calendar-error .js-retry-load')
|
|
.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
cWrap.querySelector('.user-calendar-error').classList.add('invisible');
|
|
cWrap.querySelector('.gl-spinner').classList.remove('invisible');
|
|
this.loadActivityCalendar();
|
|
});
|
|
});
|
|
}
|
|
|
|
static renderActivityCalendar(data, $calendarWrap) {
|
|
const monthsAgo = UserTabs.getVisibleCalendarPeriod($calendarWrap);
|
|
const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath');
|
|
const utcOffset = $calendarWrap.data('utcOffset');
|
|
const calendarHint = __('Issues, merge requests, pushes, and comments.');
|
|
|
|
$calendarWrap.html(CALENDAR_TEMPLATE);
|
|
|
|
$calendarWrap.find('.calendar-hint').text(calendarHint);
|
|
|
|
// eslint-disable-next-line no-new
|
|
new ActivityCalendar({
|
|
container: '.tab-pane.active .js-contrib-calendar',
|
|
activitiesContainer: '.tab-pane.active .user-calendar-activities',
|
|
timestamps: data,
|
|
calendarActivitiesPath,
|
|
utcOffset,
|
|
firstDayOfWeek: gon.first_day_of_week,
|
|
monthsAgo,
|
|
});
|
|
}
|
|
|
|
toggleLoading(status) {
|
|
return this.$parentEl.find('.loading').toggleClass('hide', !status);
|
|
}
|
|
|
|
setCurrentAction(source) {
|
|
let newState = source;
|
|
newState = newState.replace(/\/+$/, '');
|
|
newState += this.windowLocation.search + this.windowLocation.hash;
|
|
window.history.replaceState(
|
|
{
|
|
url: newState,
|
|
},
|
|
document.title,
|
|
newState,
|
|
);
|
|
return newState;
|
|
}
|
|
|
|
getCurrentAction() {
|
|
return this.$parentEl.find('.nav-links a.active').data('action');
|
|
}
|
|
|
|
static getVisibleCalendarPeriod($calendarWrap) {
|
|
const width = $calendarWrap.width();
|
|
return width < OVERVIEW_CALENDAR_BREAKPOINT
|
|
? CALENDAR_PERIOD_6_MONTHS
|
|
: CALENDAR_PERIOD_12_MONTHS;
|
|
}
|
|
}
|