user_tabs.js 5.4 KB
Newer Older
1
import $ from 'jquery';
2 3 4 5 6
import axios from '~/lib/utils/axios_utils';
import Activities from '~/activities';
import { localTimeAgo } from '~/lib/utils/datetime_utility';
import { __ } from '~/locale';
import flash from '~/flash';
7 8
import ActivityCalendar from './activity_calendar';

9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
/**
 * 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="/u/username">
 *       Activity
 *     </a>
 *   </li>
 *   <li class="groups-tab">
 *     <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/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-status">
 *   <div class="loading">
 *     Loading Animation
 *   </div>
 * </div>
 */
Bryce Johnson's avatar
Bryce Johnson committed
63

64 65 66 67 68 69 70 71 72
const CALENDAR_TEMPLATE = `
  <div class="clearfix calendar">
    <div class="js-contrib-calendar"></div>
    <div class="calendar-hint">
      Summary of issues, merge requests, push events, and comments
    </div>
  </div>
`;

73
export default class UserTabs {
74
  constructor({ defaultAction, action, parentEl }) {
75 76 77 78
    this.loaded = {};
    this.defaultAction = defaultAction || 'activity';
    this.action = action || this.defaultAction;
    this.$parentEl = $(parentEl) || $(document);
79
    this.windowLocation = window.location;
80 81 82
    this.$parentEl.find('.nav-links a').each((i, navLink) => {
      this.loaded[$(navLink).attr('data-action')] = false;
    });
83 84 85 86 87
    this.actions = Object.keys(this.loaded);
    this.bindEvents();

    if (this.action === 'show') {
      this.action = this.defaultAction;
Bryce Johnson's avatar
Bryce Johnson committed
88 89
    }

90 91
    this.activateTab(this.action);
  }
92

93
  bindEvents() {
94 95 96 97
    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));
98
  }
99

100 101
  changeProjectsPage(e) {
    e.preventDefault();
Bryce Johnson's avatar
Bryce Johnson committed
102

103 104 105 106
    $('.tab-pane.active').empty();
    const endpoint = $(e.target).attr('href');
    this.loadTab(this.getCurrentAction(), endpoint);
  }
Bryce Johnson's avatar
Bryce Johnson committed
107

108 109 110 111 112 113 114 115
  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);
  }
Bryce Johnson's avatar
Bryce Johnson committed
116

117
  activateTab(action) {
118
    return this.$parentEl.find(`.nav-links .js-${action}-tab a`).tab('show');
119
  }
120

121 122 123 124 125 126
  setTab(action, endpoint) {
    if (this.loaded[action]) {
      return;
    }
    if (action === 'activity') {
      this.loadActivities();
Bryce Johnson's avatar
Bryce Johnson committed
127 128
    }

129 130
    const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
    if (loadableActions.indexOf(action) > -1) {
131
      this.loadTab(action, endpoint);
Bryce Johnson's avatar
Bryce Johnson committed
132
    }
133
  }
Bryce Johnson's avatar
Bryce Johnson committed
134

135
  loadTab(action, endpoint) {
136 137
    this.toggleLoading(true);

138 139
    return axios
      .get(endpoint)
140
      .then(({ data }) => {
141 142 143
        const tabSelector = `div#${action}`;
        this.$parentEl.find(tabSelector).html(data.html);
        this.loaded[action] = true;
144
        localTimeAgo($('.js-timeago', tabSelector));
145 146 147 148 149 150

        this.toggleLoading(false);
      })
      .catch(() => {
        this.toggleLoading(false);
      });
151
  }
Bryce Johnson's avatar
Bryce Johnson committed
152

153
  loadActivities() {
154
    if (this.loaded.activity) {
155
      return;
Bryce Johnson's avatar
Bryce Johnson committed
156
    }
157
    const $calendarWrap = this.$parentEl.find('.user-calendar');
158 159
    const calendarPath = $calendarWrap.data('calendarPath');
    const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath');
160
    const utcOffset = $calendarWrap.data('utcOffset');
161 162
    let utcFormatted = 'UTC';
    if (utcOffset !== 0) {
163
      utcFormatted = `UTC${utcOffset > 0 ? '+' : ''}${utcOffset / 3600}`;
164
    }
165

166 167
    axios
      .get(calendarPath)
168
      .then(({ data }) => {
169
        $calendarWrap.html(CALENDAR_TEMPLATE);
170
        $calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`);
171 172

        // eslint-disable-next-line no-new
173 174
        new ActivityCalendar('.js-contrib-calendar', data, calendarActivitiesPath, utcOffset);
      })
Phil Hughes's avatar
Phil Hughes committed
175
      .catch(() => flash(__('There was an error loading users activity calendar.')));
176

177
    // eslint-disable-next-line no-new
178
    new Activities();
179
    this.loaded.activity = true;
180
  }
Bryce Johnson's avatar
Bryce Johnson committed
181

182
  toggleLoading(status) {
183
    return this.$parentEl.find('.loading-status .loading').toggleClass('hide', !status);
184
  }
185

186
  setCurrentAction(source) {
187 188 189
    let newState = source;
    newState = newState.replace(/\/+$/, '');
    newState += this.windowLocation.search + this.windowLocation.hash;
190
    window.history.replaceState(
191 192 193 194 195 196
      {
        url: newState,
      },
      document.title,
      newState,
    );
197
    return newState;
Bryce Johnson's avatar
Bryce Johnson committed
198
  }
199 200

  getCurrentAction() {
Clement Ho's avatar
Clement Ho committed
201
    return this.$parentEl.find('.nav-links a.active').data('action');
202 203
  }
}