Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKushal Pandya <kushalspandya@gmail.com>2018-12-08 12:58:45 +0300
committerPhil Hughes <me@iamphill.com>2018-12-08 12:58:45 +0300
commit295e8b9bed2e83a878c2713f69d6be6b3b83c7d0 (patch)
tree1a1a0abbdf275e8b9a30dd6ce572ac46a19f98b7 /app/assets
parent6018259f737060444fc968b3deeaa9081722b886 (diff)
CE Backport: Epic issue list and related issue list re-design
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/boards/components/issue_due_date.vue20
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue94
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue90
3 files changed, 198 insertions, 6 deletions
diff --git a/app/assets/javascripts/boards/components/issue_due_date.vue b/app/assets/javascripts/boards/components/issue_due_date.vue
index 15937b1091a..e038198e6f0 100644
--- a/app/assets/javascripts/boards/components/issue_due_date.vue
+++ b/app/assets/javascripts/boards/components/issue_due_date.vue
@@ -15,6 +15,16 @@ export default {
type: String,
required: true,
},
+ cssClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ tooltipPlacement: {
+ type: String,
+ required: false,
+ default: 'bottom',
+ },
},
computed: {
title() {
@@ -66,15 +76,13 @@ export default {
<template>
<span>
- <span ref="issueDueDate" class="board-card-info card-number">
- <icon
- :class="{ 'text-danger': isPastDue, 'board-card-info-icon': true }"
- name="calendar"
- /><time :class="{ 'text-danger': isPastDue }" datetime="date" class="board-card-info-text">{{
+ <span ref="issueDueDate" :class="cssClass" class="board-card-info card-number">
+ <icon :class="{ 'text-danger': isPastDue, 'board-card-info-icon': true }" name="calendar" />
+ <time :class="{ 'text-danger': isPastDue }" datetime="date" class="board-card-info-text">{{
body
}}</time>
</span>
- <gl-tooltip :target="() => $refs.issueDueDate" placement="bottom">
+ <gl-tooltip :target="() => $refs.issueDueDate" :placement="tooltipPlacement">
<span class="bold">{{ __('Due date') }}</span> <br />
<span :class="{ 'text-danger-muted': isPastDue }">{{ title }}</span>
</gl-tooltip>
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue b/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
new file mode 100644
index 00000000000..7e79e63aa1e
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
@@ -0,0 +1,94 @@
+<script>
+import { GlTooltipDirective } from '@gitlab/ui';
+import { __, sprintf } from '~/locale';
+
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+
+export default {
+ components: {
+ UserAvatarLink,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ assignees: {
+ type: Array,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ maxVisibleAssignees: 2,
+ maxAssigneeAvatars: 3,
+ maxAssignees: 99,
+ };
+ },
+ computed: {
+ countOverLimit() {
+ return this.assignees.length - this.maxVisibleAssignees;
+ },
+ assigneesToShow() {
+ if (this.assignees.length > this.maxAssigneeAvatars) {
+ return this.assignees.slice(0, this.maxVisibleAssignees);
+ }
+ return this.assignees;
+ },
+ assigneesCounterTooltip() {
+ const { countOverLimit, maxAssignees } = this;
+ const count = countOverLimit > maxAssignees ? maxAssignees : countOverLimit;
+
+ return sprintf(__('%{count} more assignees'), { count });
+ },
+ shouldRenderAssigneesCounter() {
+ const assigneesCount = this.assignees.length;
+ if (assigneesCount <= this.maxAssigneeAvatars) {
+ return false;
+ }
+
+ return assigneesCount > this.countOverLimit;
+ },
+ assigneeCounterLabel() {
+ if (this.countOverLimit > this.maxAssignees) {
+ return `${this.maxAssignees}+`;
+ }
+
+ return `+${this.countOverLimit}`;
+ },
+ },
+ methods: {
+ avatarUrlTitle(assignee) {
+ return sprintf(__('Avatar for %{assigneeName}'), {
+ assigneeName: assignee.name,
+ });
+ },
+ },
+};
+</script>
+<template>
+ <div class="issue-assignees">
+ <user-avatar-link
+ v-for="assignee in assigneesToShow"
+ :key="assignee.id"
+ :link-href="assignee.web_url"
+ :img-alt="avatarUrlTitle(assignee)"
+ :img-src="assignee.avatar_url"
+ :img-size="24"
+ class="js-no-trigger"
+ tooltip-placement="bottom"
+ >
+ <span class="js-assignee-tooltip">
+ <span class="bold d-block">{{ __('Assignee') }}</span> {{ assignee.name }}
+ <span class="text-white-50">@{{ assignee.username }}</span>
+ </span>
+ </user-avatar-link>
+ <span
+ v-if="shouldRenderAssigneesCounter"
+ v-gl-tooltip
+ :title="assigneesCounterTooltip"
+ class="avatar-counter"
+ data-placement="bottom"
+ >{{ assigneeCounterLabel }}</span
+ >
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue b/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue
new file mode 100644
index 00000000000..d5d967e25bf
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue
@@ -0,0 +1,90 @@
+<script>
+import { GlTooltip } from '@gitlab/ui';
+import { __, sprintf } from '~/locale';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import { timeFor, parsePikadayDate, dateInWords } from '~/lib/utils/datetime_utility';
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ Icon,
+ GlTooltip,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ milestone: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ milestoneDue: this.milestone.due_date ? parsePikadayDate(this.milestone.due_date) : null,
+ milestoneStart: this.milestone.start_date
+ ? parsePikadayDate(this.milestone.start_date)
+ : null,
+ };
+ },
+ computed: {
+ isMilestoneStarted() {
+ if (!this.milestoneStart) {
+ return false;
+ }
+ return Date.now() > this.milestoneStart;
+ },
+ isMilestonePastDue() {
+ if (!this.milestoneDue) {
+ return false;
+ }
+ return Date.now() > this.milestoneDue;
+ },
+ milestoneDatesAbsolute() {
+ if (this.milestoneDue) {
+ return `(${dateInWords(this.milestoneDue)})`;
+ } else if (this.milestoneStart) {
+ return `(${dateInWords(this.milestoneStart)})`;
+ }
+ return '';
+ },
+ milestoneDatesHuman() {
+ if (this.milestoneStart || this.milestoneDue) {
+ if (this.milestoneDue) {
+ return timeFor(
+ this.milestoneDue,
+ sprintf(__('Expired %{expiredOn}'), {
+ expiredOn: this.timeFormated(this.milestoneDue),
+ }),
+ );
+ }
+
+ return sprintf(
+ this.isMilestoneStarted ? __('Started %{startsIn}') : __('Starts %{startsIn}'),
+ {
+ startsIn: this.timeFormated(this.milestoneStart),
+ },
+ );
+ }
+ return '';
+ },
+ },
+};
+</script>
+<template>
+ <div ref="milestoneDetails" class="issue-milestone-details">
+ <icon :size="16" class="inline icon" name="clock" />
+ <span class="milestone-title">{{ milestone.title }}</span>
+ <gl-tooltip :target="() => $refs.milestoneDetails" placement="bottom" class="js-item-milestone">
+ <span class="bold">{{ __('Milestone') }}</span> <br />
+ <span>{{ milestone.title }}</span> <br />
+ <span
+ v-if="milestoneStart || milestoneDue"
+ :class="{
+ 'text-danger-muted': isMilestonePastDue,
+ 'text-tertiary': !isMilestonePastDue,
+ }"
+ ><span>{{ milestoneDatesHuman }}</span
+ ><br /><span>{{ milestoneDatesAbsolute }}</span>
+ </span>
+ </gl-tooltip>
+ </div>
+</template>