diff options
Diffstat (limited to 'web_src/js/components/DashboardRepoList.vue')
-rw-r--r-- | web_src/js/components/DashboardRepoList.vue | 103 |
1 files changed, 67 insertions, 36 deletions
diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue index 40ecbba5e3..e938814ec6 100644 --- a/web_src/js/components/DashboardRepoList.vue +++ b/web_src/js/components/DashboardRepoList.vue @@ -6,7 +6,7 @@ import {fomanticQuery} from '../modules/fomantic/base.ts'; const {appSubUrl, assetUrlPrefix, pageData} = window.config; -type CommitStatus = 'pending' | 'success' | 'error' | 'failure' | 'warning'; +type CommitStatus = 'pending' | 'success' | 'error' | 'failure' | 'warning' | 'skipped'; type CommitStatusMap = { [status in CommitStatus]: { @@ -22,6 +22,7 @@ const commitStatus: CommitStatusMap = { error: {name: 'gitea-exclamation', color: 'red'}, failure: {name: 'octicon-x', color: 'red'}, warning: {name: 'gitea-exclamation', color: 'yellow'}, + skipped: {name: 'octicon-skip', color: 'grey'}, }; export default defineComponent({ @@ -38,7 +39,7 @@ export default defineComponent({ return { tab, repos: [], - reposTotalCount: 0, + reposTotalCount: null, reposFilter, archivedFilter, privateFilter, @@ -112,9 +113,6 @@ export default defineComponent({ const el = document.querySelector('#dashboard-repo-list'); this.changeReposFilter(this.reposFilter); fomanticQuery(el.querySelector('.ui.dropdown')).dropdown(); - nextTick(() => { - this.$refs.search.focus(); - }); this.textArchivedFilterTitles = { 'archived': this.textShowOnlyArchived, @@ -130,12 +128,12 @@ export default defineComponent({ }, methods: { - changeTab(t) { - this.tab = t; + changeTab(tab: string) { + this.tab = tab; this.updateHistory(); }, - changeReposFilter(filter) { + changeReposFilter(filter: string) { this.reposFilter = filter; this.repos = []; this.page = 1; @@ -218,7 +216,9 @@ export default defineComponent({ this.searchRepos(); }, - changePage(page) { + async changePage(page: number) { + if (this.isLoading) return; + this.page = page; if (this.page > this.finalPage) { this.page = this.finalPage; @@ -228,7 +228,7 @@ export default defineComponent({ } this.repos = []; this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0; - this.searchRepos(); + await this.searchRepos(); }, async searchRepos() { @@ -240,12 +240,20 @@ export default defineComponent({ let response, json; try { + const firstLoad = this.reposTotalCount === null; if (!this.reposTotalCount) { const totalCountSearchURL = `${this.subUrl}/repo/search?count_only=1&uid=${this.uid}&team_id=${this.teamId}&q=&page=1&mode=`; response = await GET(totalCountSearchURL); - this.reposTotalCount = response.headers.get('X-Total-Count') ?? '?'; + this.reposTotalCount = parseInt(response.headers.get('X-Total-Count') ?? '0'); + } + if (firstLoad && this.reposTotalCount) { + nextTick(() => { + // MDN: If there's no focused element, this is the Document.body or Document.documentElement. + if ((document.activeElement === document.body || document.activeElement === document.documentElement)) { + this.$refs.search.focus({preventScroll: true}); + } + }); } - response = await GET(searchedURL); json = await response.json(); } catch { @@ -256,7 +264,7 @@ export default defineComponent({ } if (searchedURL === this.searchURL) { - this.repos = json.data.map((webSearchRepo) => { + this.repos = json.data.map((webSearchRepo: any) => { return { ...webSearchRepo.repository, latest_commit_status_state: webSearchRepo.latest_commit_status?.State, // if latest_commit_status is null, it means there is no commit status @@ -264,7 +272,7 @@ export default defineComponent({ locale_latest_commit_status_state: webSearchRepo.locale_latest_commit_status, }; }); - const count = response.headers.get('X-Total-Count'); + const count = Number(response.headers.get('X-Total-Count')); if (searchedQuery === '' && searchedMode === '' && this.archivedFilter === 'both') { this.reposTotalCount = count; } @@ -275,7 +283,7 @@ export default defineComponent({ } }, - repoIcon(repo) { + repoIcon(repo: any) { if (repo.fork) { return 'octicon-repo-forked'; } else if (repo.mirror) { @@ -298,7 +306,7 @@ export default defineComponent({ return commitStatus[status].color; }, - reposFilterKeyControl(e) { + async reposFilterKeyControl(e: KeyboardEvent) { switch (e.key) { case 'Enter': document.querySelector<HTMLAnchorElement>('.repo-owner-name-list li.active a')?.click(); @@ -307,7 +315,7 @@ export default defineComponent({ if (this.activeIndex > 0) { this.activeIndex--; } else if (this.page > 1) { - this.changePage(this.page - 1); + await this.changePage(this.page - 1); this.activeIndex = this.searchLimit - 1; } break; @@ -316,17 +324,17 @@ export default defineComponent({ this.activeIndex++; } else if (this.page < this.finalPage) { this.activeIndex = 0; - this.changePage(this.page + 1); + await this.changePage(this.page + 1); } break; case 'ArrowRight': if (this.page < this.finalPage) { - this.changePage(this.page + 1); + await this.changePage(this.page + 1); } break; case 'ArrowLeft': if (this.page > 1) { - this.changePage(this.page - 1); + await this.changePage(this.page - 1); } break; } @@ -336,7 +344,6 @@ export default defineComponent({ }, }, }); - </script> <template> <div> @@ -348,13 +355,21 @@ export default defineComponent({ <h4 class="ui top attached header tw-flex tw-items-center"> <div class="tw-flex-1 tw-flex tw-items-center"> {{ textMyRepos }} - <span class="ui grey label tw-ml-2">{{ reposTotalCount }}</span> + <span v-if="reposTotalCount" class="ui grey label tw-ml-2">{{ reposTotalCount }}</span> </div> <a class="tw-flex tw-items-center muted" :href="subUrl + '/repo/create' + (isOrganization ? '?org=' + organizationId : '')" :data-tooltip-content="textNewRepo"> <svg-icon name="octicon-plus"/> </a> </h4> - <div class="ui attached segment repos-search"> + <div v-if="!reposTotalCount" class="ui attached segment"> + <div v-if="!isLoading" class="empty-repo-or-org"> + <svg-icon name="octicon-git-branch" :size="24"/> + <p>{{ textNoRepo }}</p> + </div> + <!-- using the loading indicator here will cause more (unnecessary) page flickers, so at the moment, not use the loading indicator --> + <!-- <div v-else class="is-loading loading-icon-2px tw-min-h-16"/> --> + </div> + <div v-else class="ui attached segment repos-search"> <div class="ui small fluid action left icon input"> <input type="search" spellcheck="false" maxlength="255" @input="changeReposFilter(reposFilter)" v-model="searchQuery" ref="search" @keydown="reposFilterKeyControl" :placeholder="textSearchRepos"> <i class="icon loading-icon-3px" :class="{'is-loading': isLoading}"><svg-icon name="octicon-search" :size="16"/></i> @@ -367,7 +382,7 @@ export default defineComponent({ otherwise if the "input" handles click event for intermediate status, it breaks the internal state--> <input type="checkbox" class="tw-pointer-events-none" v-bind.prop="checkboxArchivedFilterProps"> <label> - <svg-icon name="octicon-archive" :size="16" class-name="tw-mr-1"/> + <svg-icon name="octicon-archive" :size="16" class="tw-mr-1"/> {{ textShowArchived }} </label> </div> @@ -376,7 +391,7 @@ export default defineComponent({ <div class="ui checkbox" ref="checkboxPrivateFilter" :title="checkboxPrivateFilterTitle"> <input type="checkbox" class="tw-pointer-events-none" v-bind.prop="checkboxPrivateFilterProps"> <label> - <svg-icon name="octicon-lock" :size="16" class-name="tw-mr-1"/> + <svg-icon name="octicon-lock" :size="16" class="tw-mr-1"/> {{ textShowPrivate }} </label> </div> @@ -411,17 +426,17 @@ export default defineComponent({ </div> <div v-if="repos.length" class="ui attached table segment tw-rounded-b"> <ul class="repo-owner-name-list"> - <li class="tw-flex tw-items-center tw-py-2" v-for="repo, index in repos" :class="{'active': index === activeIndex}" :key="repo.id"> + <li class="tw-flex tw-items-center tw-py-2" v-for="(repo, index) in repos" :class="{'active': index === activeIndex}" :key="repo.id"> <a class="repo-list-link muted" :href="repo.link"> - <svg-icon :name="repoIcon(repo)" :size="16" class-name="repo-list-icon"/> + <svg-icon :name="repoIcon(repo)" :size="16" class="repo-list-icon"/> <div class="text truncate">{{ repo.full_name }}</div> <div v-if="repo.archived"> <svg-icon name="octicon-archive" :size="16"/> </div> </a> - <a class="tw-flex tw-items-center" v-if="repo.latest_commit_status_state" :href="repo.latest_commit_status_state_link" :data-tooltip-content="repo.locale_latest_commit_status_state"> + <a class="tw-flex tw-items-center" v-if="repo.latest_commit_status_state" :href="repo.latest_commit_status_state_link || null" :data-tooltip-content="repo.locale_latest_commit_status_state"> <!-- the commit status icon logic is taken from templates/repo/commit_status.tmpl --> - <svg-icon :name="statusIcon(repo.latest_commit_status_state)" :class-name="'tw-ml-2 commit-status icon text ' + statusColor(repo.latest_commit_status_state)" :size="16"/> + <svg-icon :name="statusIcon(repo.latest_commit_status_state)" :class="'tw-ml-2 commit-status icon text ' + statusColor(repo.latest_commit_status_state)" :size="16"/> </a> </li> </ul> @@ -432,26 +447,26 @@ export default defineComponent({ class="item navigation tw-py-1" :class="{'disabled': page === 1}" @click="changePage(1)" :title="textFirstPage" > - <svg-icon name="gitea-double-chevron-left" :size="16" class-name="tw-mr-1"/> + <svg-icon name="gitea-double-chevron-left" :size="16" class="tw-mr-1"/> </a> <a class="item navigation tw-py-1" :class="{'disabled': page === 1}" @click="changePage(page - 1)" :title="textPreviousPage" > - <svg-icon name="octicon-chevron-left" :size="16" clsas-name="tw-mr-1"/> + <svg-icon name="octicon-chevron-left" :size="16" class="tw-mr-1"/> </a> <a class="active item tw-py-1">{{ page }}</a> <a class="item navigation" :class="{'disabled': page === finalPage}" @click="changePage(page + 1)" :title="textNextPage" > - <svg-icon name="octicon-chevron-right" :size="16" class-name="tw-ml-1"/> + <svg-icon name="octicon-chevron-right" :size="16" class="tw-ml-1"/> </a> <a class="item navigation tw-py-1" :class="{'disabled': page === finalPage}" @click="changePage(finalPage)" :title="textLastPage" > - <svg-icon name="gitea-double-chevron-right" :size="16" class-name="tw-ml-1"/> + <svg-icon name="gitea-double-chevron-right" :size="16" class="tw-ml-1"/> </a> </div> </div> @@ -467,11 +482,17 @@ export default defineComponent({ <svg-icon name="octicon-plus"/> </a> </h4> - <div v-if="organizations.length" class="ui attached table segment tw-rounded-b"> + <div v-if="!organizations.length" class="ui attached segment"> + <div class="empty-repo-or-org"> + <svg-icon name="octicon-organization" :size="24"/> + <p>{{ textNoOrg }}</p> + </div> + </div> + <div v-else class="ui attached table segment tw-rounded-b"> <ul class="repo-owner-name-list"> <li class="tw-flex tw-items-center tw-py-2" v-for="org in organizations" :key="org.name"> <a class="repo-list-link muted" :href="subUrl + '/' + encodeURIComponent(org.name)"> - <svg-icon name="octicon-organization" :size="16" class-name="repo-list-icon"/> + <svg-icon name="octicon-organization" :size="16" class="repo-list-icon"/> <div class="text truncate">{{ org.full_name ? `${org.full_name} (${org.name})` : org.name }}</div> <div><!-- div to prevent underline of label on hover --> <span class="ui tiny basic label" v-if="org.org_visibility !== 'public'"> @@ -481,7 +502,7 @@ export default defineComponent({ </a> <div class="text light grey tw-flex tw-items-center tw-ml-2"> {{ org.num_repos }} - <svg-icon name="octicon-repo" :size="16" class-name="tw-ml-1 tw-mt-0.5"/> + <svg-icon name="octicon-repo" :size="16" class="tw-ml-1 tw-mt-0.5"/> </div> </li> </ul> @@ -546,4 +567,14 @@ ul li:not(:last-child) { .repo-owner-name-list li.active { background: var(--color-hover); } + +.empty-repo-or-org { + margin-top: 1em; + text-align: center; + color: var(--color-placeholder-text); +} + +.empty-repo-or-org p { + margin: 1em auto; +} </style> |