mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Use frontend fetch for branch dropdown component (#25719)
- Send request to get branch/tag list, use loading icon when waiting for response. - Only fetch when the first time branch/tag list shows. - For backend, removed assignment to `ctx.Data["Branches"]` and `ctx.Data["Tags"]` from `context/repo.go` and passed these data wherever needed. - Changed some `v-if` to `v-show` and used native `svg` as mentioned in https://github.com/go-gitea/gitea/pull/25719#issuecomment-1631712757 to improve perfomance when there are a lot of branches. - Places Used the dropdown component: Repo Home Page <img width="1429" alt="Screen Shot 2023-07-06 at 12 17 51" src="https://github.com/go-gitea/gitea/assets/17645053/6accc7b6-8d37-4e88-ae1a-bd2b3b927ea0"> Commits Page <img width="1431" alt="Screen Shot 2023-07-06 at 12 18 34" src="https://github.com/go-gitea/gitea/assets/17645053/2d0bf306-d1e2-45a8-a784-bc424879f537"> Specific commit -> operations -> cherry-pick <img width="758" alt="Screen Shot 2023-07-06 at 12 23 28" src="https://github.com/go-gitea/gitea/assets/17645053/1e557948-3881-4e45-a625-8ef36d45ae2d"> Release Page <img width="1433" alt="Screen Shot 2023-07-06 at 12 25 05" src="https://github.com/go-gitea/gitea/assets/17645053/3ec82af1-15a4-4162-a50b-04a9502161bb"> - Demo https://github.com/go-gitea/gitea/assets/17645053/d45d266b-3eb0-465a-82f9-57f78dc5f9f3 - Note: UI of dropdown menu could be improved in another PR as it should apply to more dropdown menus. Fix #14180 --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		@@ -11,7 +11,7 @@
 | 
			
		||||
      </span>
 | 
			
		||||
      <svg-icon name="octicon-triangle-down" :size="14" class-name="dropdown icon"/>
 | 
			
		||||
    </button>
 | 
			
		||||
    <div class="menu transition" :class="{visible: menuVisible}" v-if="menuVisible" v-cloak>
 | 
			
		||||
    <div class="menu transition" :class="{visible: menuVisible}" v-show="menuVisible" v-cloak>
 | 
			
		||||
      <div class="ui icon search input">
 | 
			
		||||
        <i class="icon"><svg-icon name="octicon-filter" :size="16"/></i>
 | 
			
		||||
        <input name="search" ref="searchField" autocomplete="off" v-model="searchTerm" @keydown="keydown($event)" :placeholder="searchFieldPlaceholder">
 | 
			
		||||
@@ -20,13 +20,13 @@
 | 
			
		||||
        <div class="header branch-tag-choice">
 | 
			
		||||
          <div class="ui grid">
 | 
			
		||||
            <div class="two column row">
 | 
			
		||||
              <a class="reference column" href="#" @click="createTag = false; mode = 'branches'; focusSearchField()">
 | 
			
		||||
              <a class="reference column" href="#" @click="handleTabSwitch('branches')">
 | 
			
		||||
                <span class="text" :class="{black: mode === 'branches'}">
 | 
			
		||||
                  <svg-icon name="octicon-git-branch" :size="16" class-name="gt-mr-2"/>{{ textBranches }}
 | 
			
		||||
                </span>
 | 
			
		||||
              </a>
 | 
			
		||||
              <template v-if="!noTag">
 | 
			
		||||
                <a class="reference column" href="#" @click="createTag = true; mode = 'tags'; focusSearchField()">
 | 
			
		||||
                <a class="reference column" href="#" @click="handleTabSwitch('tags')">
 | 
			
		||||
                  <span class="text" :class="{black: mode === 'tags'}">
 | 
			
		||||
                    <svg-icon name="octicon-tag" :size="16" class-name="gt-mr-2"/>{{ textTags }}
 | 
			
		||||
                  </span>
 | 
			
		||||
@@ -37,20 +37,23 @@
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
      <div class="scrolling menu" ref="scrollContainer">
 | 
			
		||||
        <svg-icon name="octicon-rss" symbol-id="svg-symbol-octicon-rss"/>
 | 
			
		||||
        <div class="loading-indicator is-loading" v-if="isLoading"/>
 | 
			
		||||
        <div v-for="(item, index) in filteredItems" :key="item.name" class="item" :class="{selected: item.selected, active: active === index}" @click="selectItem(item)" :ref="'listItem' + index">
 | 
			
		||||
          {{ item.name }}
 | 
			
		||||
          <a v-if="enableFeed && mode === 'branches'" role="button" class="rss-icon ui compact right" :href="rssURLPrefix + item.url" target="_blank" @click.stop>
 | 
			
		||||
            <svg-icon name="octicon-rss" :size="14"/>
 | 
			
		||||
          <a v-show="enableFeed && mode === 'branches'" role="button" class="rss-icon ui compact right" :href="rssURLPrefix + item.url" target="_blank" @click.stop>
 | 
			
		||||
            <!-- creating a lot of Vue component is pretty slow, so we use a static SVG here -->
 | 
			
		||||
            <svg width="14" height="14" class="svg octicon-rss"><use href="#svg-symbol-octicon-rss"/></svg>
 | 
			
		||||
          </a>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="item" v-if="showCreateNewBranch" :class="{active: active === filteredItems.length}" :ref="'listItem' + filteredItems.length">
 | 
			
		||||
          <a href="#" @click="createNewBranch()">
 | 
			
		||||
            <div v-show="createTag">
 | 
			
		||||
            <div v-show="shouldCreateTag">
 | 
			
		||||
              <i class="reference tags icon"/>
 | 
			
		||||
              <!-- eslint-disable-next-line vue/no-v-html -->
 | 
			
		||||
              <span v-html="textCreateTag.replace('%s', searchTerm)"/>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div v-show="!createTag">
 | 
			
		||||
            <div v-show="!shouldCreateTag">
 | 
			
		||||
              <svg-icon name="octicon-git-branch"/>
 | 
			
		||||
              <!-- eslint-disable-next-line vue/no-v-html -->
 | 
			
		||||
              <span v-html="textCreateBranch.replace('%s', searchTerm)"/>
 | 
			
		||||
@@ -64,12 +67,12 @@
 | 
			
		||||
          <form ref="newBranchForm" :action="formActionUrl" method="post">
 | 
			
		||||
            <input type="hidden" name="_csrf" :value="csrfToken">
 | 
			
		||||
            <input type="hidden" name="new_branch_name" v-model="searchTerm">
 | 
			
		||||
            <input type="hidden" name="create_tag" v-model="createTag">
 | 
			
		||||
            <input type="hidden" name="create_tag" v-model="shouldCreateTag">
 | 
			
		||||
            <input type="hidden" name="current_path" v-model="treePath" v-if="treePath">
 | 
			
		||||
          </form>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="message" v-if="showNoResults">
 | 
			
		||||
      <div class="message" v-if="showNoResults && !isLoading">
 | 
			
		||||
        {{ noResults }}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -81,6 +84,7 @@ import {createApp, nextTick} from 'vue';
 | 
			
		||||
import $ from 'jquery';
 | 
			
		||||
import {SvgIcon} from '../svg.js';
 | 
			
		||||
import {pathEscapeSegments} from '../utils/url.js';
 | 
			
		||||
import {showErrorToast} from '../modules/toast.js';
 | 
			
		||||
 | 
			
		||||
const sfc = {
 | 
			
		||||
  components: {SvgIcon},
 | 
			
		||||
@@ -110,12 +114,16 @@ const sfc = {
 | 
			
		||||
    formActionUrl() {
 | 
			
		||||
      return `${this.repoLink}/branches/_new/${this.branchNameSubURL}`;
 | 
			
		||||
    },
 | 
			
		||||
    shouldCreateTag() {
 | 
			
		||||
      return this.mode === 'tags';
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  watch: {
 | 
			
		||||
    menuVisible(visible) {
 | 
			
		||||
      if (visible) {
 | 
			
		||||
        this.focusSearchField();
 | 
			
		||||
        this.fetchBranchesOrTags();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
@@ -139,7 +147,6 @@ const sfc = {
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  methods: {
 | 
			
		||||
    selectItem(item) {
 | 
			
		||||
      const prev = this.getSelected();
 | 
			
		||||
@@ -246,7 +253,44 @@ const sfc = {
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        this.menuVisible = false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    },
 | 
			
		||||
    handleTabSwitch(mode) {
 | 
			
		||||
      if (this.isLoading) return;
 | 
			
		||||
      this.mode = mode;
 | 
			
		||||
      this.focusSearchField();
 | 
			
		||||
      this.fetchBranchesOrTags();
 | 
			
		||||
    },
 | 
			
		||||
    async fetchBranchesOrTags() {
 | 
			
		||||
      if (!['branches', 'tags'].includes(this.mode) || this.isLoading) return;
 | 
			
		||||
      // only fetch when branch/tag list has not been initialized
 | 
			
		||||
      if (this.hasListInitialized[this.mode] ||
 | 
			
		||||
        (this.mode === 'branches' && !this.showBranchesInDropdown) ||
 | 
			
		||||
        (this.mode === 'tags' && this.noTag)
 | 
			
		||||
      ) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      this.isLoading = true;
 | 
			
		||||
      try {
 | 
			
		||||
        // the "data.defaultBranch" is ambiguous, it could be "branch name" or "tag name"
 | 
			
		||||
        const reqUrl = `${this.repoLink}/${this.mode}/list`;
 | 
			
		||||
        const resp = await fetch(reqUrl);
 | 
			
		||||
        const {results} = await resp.json();
 | 
			
		||||
        for (const result of results) {
 | 
			
		||||
          let selected = false;
 | 
			
		||||
          if (this.mode === 'branches') {
 | 
			
		||||
            selected = result === this.defaultBranch;
 | 
			
		||||
          } else {
 | 
			
		||||
            selected = result === (this.release ? this.release.tagName : this.defaultBranch);
 | 
			
		||||
          }
 | 
			
		||||
          this.items.push({name: result, url: pathEscapeSegments(result), branch: this.mode === 'branches', tag: this.mode === 'tags', selected});
 | 
			
		||||
        }
 | 
			
		||||
        this.hasListInitialized[this.mode] = true;
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        showErrorToast(`Network error when fetching ${this.mode}, error: ${e}`);
 | 
			
		||||
      } finally {
 | 
			
		||||
        this.isLoading = false;
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -258,7 +302,6 @@ export function initRepoBranchTagSelector(selector) {
 | 
			
		||||
      searchTerm: '',
 | 
			
		||||
      refNameText: '',
 | 
			
		||||
      menuVisible: false,
 | 
			
		||||
      createTag: false,
 | 
			
		||||
      release: null,
 | 
			
		||||
 | 
			
		||||
      isViewTag: false,
 | 
			
		||||
@@ -266,27 +309,15 @@ export function initRepoBranchTagSelector(selector) {
 | 
			
		||||
      isViewTree: false,
 | 
			
		||||
 | 
			
		||||
      active: 0,
 | 
			
		||||
 | 
			
		||||
      isLoading: false,
 | 
			
		||||
      // This means whether branch list/tag list has initialized
 | 
			
		||||
      hasListInitialized: {
 | 
			
		||||
        'branches': false,
 | 
			
		||||
        'tags': false,
 | 
			
		||||
      },
 | 
			
		||||
      ...window.config.pageData.branchDropdownDataList[elIndex],
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // the "data.defaultBranch" is ambiguous, it could be "branch name" or "tag name"
 | 
			
		||||
 | 
			
		||||
    if (data.showBranchesInDropdown && data.branches) {
 | 
			
		||||
      for (const branch of data.branches) {
 | 
			
		||||
        data.items.push({name: branch, url: pathEscapeSegments(branch), branch: true, tag: false, selected: branch === data.defaultBranch});
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (!data.noTag && data.tags) {
 | 
			
		||||
      for (const tag of data.tags) {
 | 
			
		||||
        if (data.release) {
 | 
			
		||||
          data.items.push({name: tag, url: pathEscapeSegments(tag), branch: false, tag: true, selected: tag === data.release.tagName});
 | 
			
		||||
        } else {
 | 
			
		||||
          data.items.push({name: tag, url: pathEscapeSegments(tag), branch: false, tag: true, selected: tag === data.defaultBranch});
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const comp = {...sfc, data() { return data }};
 | 
			
		||||
    createApp(comp).mount(elRoot);
 | 
			
		||||
  }
 | 
			
		||||
@@ -302,4 +333,8 @@ export default sfc; // activate IDE's Vue plugin
 | 
			
		||||
.menu .item:hover .rss-icon {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.scrolling.menu .loading-indicator {
 | 
			
		||||
  height: 4em;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user