app/assets/javascripts/repository/components/header_area.vue (420 lines of code) (raw):
<script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import Shortcuts from '~/behaviors/shortcuts/shortcuts';
import { shouldDisableShortcuts } from '~/behaviors/shortcuts/shortcuts_toggle';
import { keysFor, START_SEARCH_PROJECT_FILE } from '~/behaviors/shortcuts/keybindings';
import { sanitize } from '~/lib/dompurify';
import { InternalEvents } from '~/tracking';
import { FIND_FILE_BUTTON_CLICK, REF_SELECTOR_CLICK } from '~/tracking/constants';
import { visitUrl, joinPaths, webIDEUrl } from '~/lib/utils/url_utility';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils';
import RefSelector from '~/ref/components/ref_selector.vue';
import Breadcrumbs from '~/repository/components/header_area/breadcrumbs.vue';
import BlobControls from '~/repository/components/header_area/blob_controls.vue';
import RepositoryOverflowMenu from '~/repository/components/header_area/repository_overflow_menu.vue';
import CodeDropdown from '~/vue_shared/components/code_dropdown/code_dropdown.vue';
import SourceCodeDownloadDropdown from '~/vue_shared/components/download_dropdown/download_dropdown.vue';
import CloneCodeDropdown from '~/vue_shared/components/code_dropdown/clone_code_dropdown.vue';
import AddToTree from '~/repository/components/header_area/add_to_tree.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
export default {
name: 'HeaderArea',
i18n: {
compare: __('Compare'),
findFile: __('Find file'),
},
components: {
GlButton,
FileIcon,
RefSelector,
Breadcrumbs,
RepositoryOverflowMenu,
BlobControls,
CodeDropdown,
CompactCodeDropdown: () =>
import('ee_else_ce/repository/components/code_dropdown/compact_code_dropdown.vue'),
SourceCodeDownloadDropdown,
CloneCodeDropdown,
AddToTree,
WebIdeLink: () => import('ee_else_ce/vue_shared/components/web_ide_link.vue'),
LockDirectoryButton: () =>
import('ee_component/repository/components/lock_directory_button.vue'),
HeaderLockIcon: () =>
import('ee_component/repository/components/header_area/header_lock_icon.vue'),
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [glFeatureFlagsMixin()],
inject: [
'canCollaborate',
'canEditTree',
'canPushCode',
'canPushToBranch',
'originalBranch',
'selectedBranch',
'newBranchPath',
'newTagPath',
'newBlobPath',
'forkNewBlobPath',
'forkNewDirectoryPath',
'forkUploadBlobPath',
'uploadPath',
'newDirPath',
'projectRootPath',
'comparePath',
'isReadmeView',
'isFork',
'needsToFork',
'isGitpodEnabledForUser',
'isBlob',
'showEditButton',
'showWebIdeButton',
'isGitpodEnabledForInstance',
'showPipelineEditorUrl',
'webIdeUrl',
'editUrl',
'pipelineEditorUrl',
'gitpodUrl',
'userPreferencesGitpodPath',
'userProfileEnableGitpodPath',
'httpUrl',
'xcodeUrl',
'sshUrl',
'kerberosUrl',
'downloadLinks',
'downloadArtifacts',
'isBinary',
'rootRef',
],
provide() {
return {
currentRef: this.currentRef,
};
},
props: {
projectPath: {
type: String,
required: true,
},
refType: {
type: String,
required: false,
default: null,
},
currentRef: {
type: String,
required: false,
default: null,
},
projectId: {
type: String,
required: true,
},
},
data() {
return {
directoryLocked: false,
fileLocked: false,
lockAuthor: undefined,
};
},
computed: {
isTreeView() {
return this.$route.name !== 'blobPathDecoded';
},
isProjectOverview() {
return this.$route.name === 'projectRoot';
},
isRoot() {
return !this.currentPath || this.currentPath === '/';
},
directoryName() {
return this.currentPath
? this.currentPath.split('/').pop()
: this.projectPath.split('/').pop();
},
fileIconName() {
return this.isTreeView ? 'folder-open' : this.directoryName;
},
isLocked() {
return this.isTreeView ? this.directoryLocked : this.fileLocked;
},
getRefType() {
return this.$route.query.ref_type;
},
currentPath() {
return this.$route.params.path;
},
refSelectorQueryParams() {
return {
sort: 'updated_desc',
};
},
refSelectorValue() {
return this.refType ? joinPaths('refs', this.refType, this.currentRef) : this.currentRef;
},
webIDEUrl() {
return this.isBlob
? this.webIdeUrl
: webIDEUrl(
joinPaths(
'/',
this.projectPath,
'edit',
this.currentRef,
'-',
this.$route?.params.path || '',
'/',
),
);
},
projectIdAsNumber() {
return getIdFromGraphQLId(this.projectId);
},
findFileTooltip() {
const { description } = START_SEARCH_PROJECT_FILE;
const key = this.findFileShortcutKey;
return shouldDisableShortcuts()
? null
: sanitize(`${description} <kbd class="flat gl-ml-1" aria-hidden=true>${key}</kbd>`);
},
findFileShortcutKey() {
return keysFor(START_SEARCH_PROJECT_FILE)[0];
},
showCompactCodeDropdown() {
return this.glFeatures.directoryCodeDropdownUpdates;
},
showBlobControls() {
return this.$route.params.path && this.$route.name === 'blobPathDecoded';
},
},
methods: {
onInput(selectedRef) {
InternalEvents.trackEvent(REF_SELECTOR_CLICK);
visitUrl(generateRefDestinationPath(this.projectRootPath, this.originalBranch, selectedRef));
},
handleFindFile() {
InternalEvents.trackEvent(FIND_FILE_BUTTON_CLICK);
Shortcuts.focusSearchFile();
},
onLockedDirectory({ isLocked, lockAuthor }) {
this.directoryLocked = isLocked;
this.lockAuthor = lockAuthor;
},
onLockedFile({ isLocked, lockAuthor }) {
this.fileLocked = isLocked;
this.lockAuthor = lockAuthor;
},
},
};
</script>
<template>
<section :class="{ 'gl-items-center gl-justify-between sm:gl-flex': isProjectOverview }">
<div class="tree-ref-container mb-2 mb-md-0 gl-flex gl-flex-wrap gl-gap-5">
<ref-selector
v-if="!isReadmeView"
class="tree-ref-holder gl-max-w-26"
data-testid="ref-dropdown-container"
:project-id="projectId"
:value="refSelectorValue"
use-symbolic-ref-names
:query-params="refSelectorQueryParams"
@input="onInput"
/>
<breadcrumbs
v-if="!isReadmeView"
class="js-repo-breadcrumbs"
:current-path="currentPath"
:ref-type="getRefType"
:can-collaborate="canCollaborate"
:can-edit-tree="canEditTree"
:can-push-code="canPushCode"
:can-push-to-branch="canPushToBranch"
:original-branch="originalBranch"
:selected-branch="selectedBranch"
:new-branch-path="newBranchPath"
:new-tag-path="newTagPath"
:new-blob-path="newBlobPath"
:fork-new-blob-path="forkNewBlobPath"
:fork-new-directory-path="forkNewDirectoryPath"
:fork-upload-blob-path="forkUploadBlobPath"
:upload-path="uploadPath"
:new-dir-path="newDirPath"
/>
</div>
<div
class="gl-flex gl-flex-col gl-items-stretch gl-justify-end sm:gl-flex-row sm:gl-items-center sm:gl-gap-5"
:class="{ 'gl-my-5': !isProjectOverview }"
>
<h1
v-if="!isReadmeView && !isProjectOverview"
class="gl-mt-0 gl-inline-flex gl-flex-1 gl-items-center gl-gap-3 gl-break-words gl-text-size-h1 sm:gl-my-0"
data-testid="repository-heading"
>
<file-icon
:file-name="fileIconName"
:folder="isTreeView"
opened
aria-hidden="true"
class="gl-inline-flex"
:class="{ 'gl-text-subtle': isTreeView }"
/>{{ directoryName }}
<header-lock-icon
v-if="!isRoot"
:is-tree-view="isTreeView"
:is-locked="isLocked"
:lock-author="lockAuthor"
/>
</h1>
<!-- Tree controls -->
<div
v-if="!showBlobControls"
class="tree-controls gl-mb-3 gl-flex gl-flex-wrap gl-gap-3 sm:gl-mb-0"
data-testid="tree-controls-container"
>
<add-to-tree
v-if="!isReadmeView && showCompactCodeDropdown"
class="gl-hidden sm:gl-block"
:current-path="currentPath"
:can-collaborate="canCollaborate"
:can-edit-tree="canEditTree"
:can-push-code="canPushCode"
:can-push-to-branch="canPushToBranch"
:original-branch="originalBranch"
:selected-branch="selectedBranch"
:new-branch-path="newBranchPath"
:new-tag-path="newTagPath"
:new-blob-path="newBlobPath"
:fork-new-blob-path="forkNewBlobPath"
:fork-new-directory-path="forkNewDirectoryPath"
:fork-upload-blob-path="forkUploadBlobPath"
:upload-path="uploadPath"
:new-dir-path="newDirPath"
/>
<!-- EE: = render_if_exists 'projects/tree/lock_link' -->
<lock-directory-button
v-if="!isRoot"
:project-path="projectPath"
:path="currentPath"
@lockedDirectory="onLockedDirectory"
/>
<gl-button
v-gl-tooltip.html="findFileTooltip"
:aria-keyshortcuts="findFileShortcutKey"
data-testid="tree-find-file-control"
class="gl-w-full sm:gl-w-auto"
@click="handleFindFile"
>
{{ $options.i18n.findFile }}
</gl-button>
<!-- web ide -->
<web-ide-link
class="gl-w-full sm:!gl-ml-0 sm:gl-w-auto"
data-testid="js-tree-web-ide-link"
:project-id="projectIdAsNumber"
:project-path="projectPath"
:is-fork="isFork"
:needs-to-fork="needsToFork"
:is-gitpod-enabled-for-user="isGitpodEnabledForUser"
:is-blob="isBlob"
:show-edit-button="showEditButton"
:show-web-ide-button="showWebIdeButton"
:is-gitpod-enabled-for-instance="isGitpodEnabledForInstance"
:show-pipeline-editor-url="showPipelineEditorUrl"
:web-ide-url="webIDEUrl"
:edit-url="editUrl"
:pipeline-editor-url="pipelineEditorUrl"
:gitpod-url="gitpodUrl"
:user-preferences-gitpod-path="userPreferencesGitpodPath"
:user-profile-enable-gitpod-path="userProfileEnableGitpodPath"
:git-ref="currentRef"
disable-fork-modal
v-on="$listeners"
/>
<!-- code + mobile panel -->
<div class="project-code-holder gl-w-full sm:gl-w-auto">
<div v-if="showCompactCodeDropdown" class="gl-flex gl-justify-end gl-gap-3">
<add-to-tree
v-if="!isReadmeView"
class="sm:gl-hidden"
:current-path="currentPath"
:can-collaborate="canCollaborate"
:can-edit-tree="canEditTree"
:can-push-code="canPushCode"
:can-push-to-branch="canPushToBranch"
:original-branch="originalBranch"
:selected-branch="selectedBranch"
:new-branch-path="newBranchPath"
:new-tag-path="newTagPath"
:new-blob-path="newBlobPath"
:fork-new-blob-path="forkNewBlobPath"
:fork-new-directory-path="forkNewDirectoryPath"
:fork-upload-blob-path="forkUploadBlobPath"
:upload-path="uploadPath"
:new-dir-path="newDirPath"
/>
<compact-code-dropdown
class="gl-ml-auto"
:ssh-url="sshUrl"
:http-url="httpUrl"
:kerberos-url="kerberosUrl"
:xcode-url="xcodeUrl"
:web-ide-url="webIDEUrl"
:gitpod-url="gitpodUrl"
:current-path="currentPath"
:directory-download-links="downloadLinks"
:project-id="projectId"
:project-path="projectPath"
:show-web-ide-button="showWebIdeButton"
:is-gitpod-enabled-for-instance="isGitpodEnabledForInstance"
:is-gitpod-enabled-for-user="isGitpodEnabledForUser"
/>
<repository-overflow-menu
:full-path="projectPath"
:path="currentPath"
:current-ref="currentRef"
/>
</div>
<template v-else-if="!isReadmeView">
<code-dropdown
class="git-clone-holder js-git-clone-holder gl-hidden sm:gl-inline-block"
:ssh-url="sshUrl"
:http-url="httpUrl"
:kerberos-url="kerberosUrl"
:xcode-url="xcodeUrl"
:current-path="currentPath"
:directory-download-links="downloadLinks"
/>
<div class="gl-flex gl-w-full gl-gap-3 sm:gl-inline-block sm:gl-w-auto">
<div class="gl-flex gl-w-full gl-items-stretch gl-gap-3 sm:gl-hidden">
<source-code-download-dropdown
:download-links="downloadLinks"
:download-artifacts="downloadArtifacts"
/>
<clone-code-dropdown
class="mobile-git-clone js-git-clone-holder !gl-w-full"
:ssh-url="sshUrl"
:http-url="httpUrl"
:kerberos-url="kerberosUrl"
/>
</div>
<repository-overflow-menu
:full-path="projectPath"
:path="currentPath"
:current-ref="currentRef"
/>
</div>
</template>
</div>
</div>
<!-- Blob controls -->
<blob-controls
v-if="showBlobControls"
:project-path="projectPath"
:project-id-as-number="projectIdAsNumber"
:ref-type="getRefType"
:is-binary="isBinary"
@lockedFile="onLockedFile"
/>
</div>
</section>
</template>