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>