summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/merge_requests/components/sticky_header.vue
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/merge_requests/components/sticky_header.vue')
-rw-r--r--app/assets/javascripts/merge_requests/components/sticky_header.vue180
1 files changed, 180 insertions, 0 deletions
diff --git a/app/assets/javascripts/merge_requests/components/sticky_header.vue b/app/assets/javascripts/merge_requests/components/sticky_header.vue
new file mode 100644
index 00000000000..f067982fce1
--- /dev/null
+++ b/app/assets/javascripts/merge_requests/components/sticky_header.vue
@@ -0,0 +1,180 @@
+<script>
+import {
+ GlIntersectionObserver,
+ GlLink,
+ GlSprintf,
+ GlBadge,
+ GlSafeHtmlDirective,
+} from '@gitlab/ui';
+import { mapGetters, mapState } from 'vuex';
+import { TYPE_MERGE_REQUEST } from '~/graphql_shared/constants';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { isLoggedIn } from '~/lib/utils/common_utils';
+import StatusBox from '~/issuable/components/status_box.vue';
+import DiscussionCounter from '~/notes/components/discussion_counter.vue';
+import TodoWidget from '~/sidebar/components/todo_toggle/sidebar_todo_widget.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+
+export default {
+ components: {
+ GlIntersectionObserver,
+ GlLink,
+ GlSprintf,
+ GlBadge,
+ StatusBox,
+ DiscussionCounter,
+ TodoWidget,
+ ClipboardButton,
+ },
+ directives: {
+ SafeHtml: GlSafeHtmlDirective,
+ },
+ mixins: [glFeatureFlagsMixin()],
+ inject: {
+ projectPath: { default: null },
+ title: { default: '' },
+ tabs: { default: () => [] },
+ isFluidLayout: { default: false },
+ },
+ data() {
+ return {
+ isStickyHeaderVisible: false,
+ discussionCounter: 0,
+ };
+ },
+ computed: {
+ ...mapGetters(['getNoteableData', 'discussionTabCounter']),
+ ...mapState({
+ activeTab: (state) => state.page.activeTab,
+ doneFetchingBatchDiscussions: (state) => state.notes.doneFetchingBatchDiscussions,
+ }),
+ issuableId() {
+ return convertToGraphQLId(TYPE_MERGE_REQUEST, this.getNoteableData.id);
+ },
+ issuableIid() {
+ return `${this.getNoteableData.iid}`;
+ },
+ isSignedIn() {
+ return isLoggedIn();
+ },
+ },
+ watch: {
+ discussionTabCounter(val) {
+ if (this.glFeatures.paginatedMrDiscussions) {
+ if (this.doneFetchingBatchDiscussions) {
+ this.discussionCounter = val;
+ }
+ } else {
+ this.discussionCounter = val;
+ }
+ },
+ },
+ methods: {
+ setStickyHeaderVisible(val) {
+ this.isStickyHeaderVisible = val;
+ },
+ visitTab(e) {
+ window.mrTabs?.clickTab(e);
+ },
+ },
+ safeHtmlConfig: {
+ ADD_TAGS: ['gl-emoji'],
+ },
+};
+</script>
+
+<template>
+ <gl-intersection-observer
+ @appear="setStickyHeaderVisible(false)"
+ @disappear="setStickyHeaderVisible(true)"
+ >
+ <div
+ v-if="isStickyHeaderVisible"
+ class="issue-sticky-header merge-request-sticky-header gl-fixed gl-bg-white gl-border-1 gl-border-b-solid gl-border-b-gray-100 gl-pt-3 gl-display-none gl-md-display-block"
+ >
+ <div
+ class="issue-sticky-header-text gl-display-flex gl-flex-direction-column gl-align-items-center gl-mx-auto gl-px-5"
+ :class="{ 'gl-max-w-container-xl': !isFluidLayout }"
+ >
+ <div class="gl-w-full gl-display-flex gl-align-items-center">
+ <status-box :initial-state="getNoteableData.state" issuable-type="merge_request" />
+ <p
+ v-safe-html:[$options.safeHtmlConfig]="title"
+ class="gl-display-none gl-lg-display-block gl-font-weight-bold gl-overflow-hidden gl-white-space-nowrap gl-text-overflow-ellipsis gl-my-0 gl-mr-4"
+ ></p>
+ <div class="gl-display-flex gl-align-items-center">
+ <gl-sprintf :message="__('%{source} %{copyButton} into %{target}')">
+ <template #copyButton>
+ <clipboard-button
+ :text="getNoteableData.source_branch"
+ :title="__('Copy branch name')"
+ size="small"
+ category="tertiary"
+ tooltip-placement="bottom"
+ class="gl-m-0! gl-mx-1! js-source-branch-copy"
+ />
+ </template>
+ <template #source>
+ <gl-link
+ :title="getNoteableData.source_branch"
+ :href="getNoteableData.source_branch_path"
+ class="gl-text-blue-500! gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-px-2 gl-text-truncate gl-max-w-26"
+ >
+ {{ getNoteableData.source_branch }}
+ </gl-link>
+ </template>
+ <template #target>
+ <gl-link
+ :title="getNoteableData.target_branch"
+ :href="getNoteableData.target_branch_path"
+ class="gl-text-blue-500! gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-px-2 gl-text-truncate gl-max-w-26 gl-ml-2"
+ >
+ {{ getNoteableData.target_branch }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+ </div>
+ <div class="gl-w-full gl-display-flex">
+ <ul
+ class="merge-request-tabs nav-tabs nav nav-links gl-display-flex gl-flex-nowrap gl-m-0 gl-p-0 gl-border-b-0"
+ >
+ <li
+ v-for="(tab, index) in tabs"
+ :key="tab[0]"
+ :class="{ active: activeTab === tab[0] }"
+ >
+ <gl-link
+ :href="tab[2]"
+ :data-action="tab[0]"
+ class="gl-outline-0! gl-py-4!"
+ @click="visitTab"
+ >
+ {{ tab[1] }}
+ <gl-badge variant="muted" size="sm">
+ <template v-if="index === 0 && discussionCounter !== 0">
+ {{ discussionCounter }}
+ </template>
+ <template v-else>
+ {{ tab[3] }}
+ </template>
+ </gl-badge>
+ </gl-link>
+ </li>
+ </ul>
+ <div class="gl-display-none gl-lg-display-flex gl-align-items-center gl-ml-auto">
+ <discussion-counter blocks-merge hide-options />
+ <todo-widget
+ v-if="isSignedIn"
+ :issuable-id="issuableId"
+ :issuable-iid="issuableIid"
+ :full-path="projectPath"
+ issuable-type="merge_request"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ </gl-intersection-observer>
+</template>