Fix incorrect checkbox behaviors in the dashboard repolist's filter (#23147)

Co-author: yp05327 , this PR is based on yp05327's #22813.

The problems of the old DashboardRepoList / repolist.tmpl: 

* It mixes many different frameworks together
* It "just works", bug on bug
* It uses many anti-pattern of Vue

This PR:

* Fix bugs and close #22800
* Decouple the "checkbox" elements from Fomantic UI (only use CSS
styles)
* Simplify the HTML layout
* Simplify JS logic
* Make it easier to refactor the DashboardRepoList into a pure Vue
component in the future.

### Screenshots

#### Default

![image](https://user-images.githubusercontent.com/2114189/221355768-a3eb5b23-85b4-4e3d-b906-844d8b15539d.png)

####  Click "Archived" to make it checked

![image](https://user-images.githubusercontent.com/2114189/221355777-9a104ddf-52a7-4504-869a-43a73827d802.png)

####  Click "Archived" to make it intermediate

![image](https://user-images.githubusercontent.com/2114189/221355802-0f67a073-67ad-4e92-84a6-558c432103a5.png)

####  Click "Archived" to make it unchecked

![image](https://user-images.githubusercontent.com/2114189/221355810-acf1d9d8-ccce-47fe-a02e-70cf4e666331.png)

---------

Co-authored-by: yp05327 <576951401@qq.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
wxiaoguang 2023-03-01 10:22:14 +08:00 committed by GitHub
parent 3e426bba78
commit 7a5af25592
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 60 additions and 113 deletions

View File

@ -46,49 +46,32 @@
<div class="ui dropdown icon button" title="{{.locale.Tr "home.filter"}}"> <div class="ui dropdown icon button" title="{{.locale.Tr "home.filter"}}">
<i class="icon gt-df gt-ac gt-jc gt-m-0">{{svg "octicon-filter" 16}}</i> <i class="icon gt-df gt-ac gt-jc gt-m-0">{{svg "octicon-filter" 16}}</i>
<div class="menu"> <div class="menu">
<div class="item"> <a class="item" @click="toggleArchivedFilter()">
<a @click="toggleArchivedFilter()"> <div class="ui checkbox"
<div class="ui checkbox" id="archivedFilterCheckbox" title="{{.locale.Tr "home.show_both_archived_unarchived"}}" v-if="archivedFilter === 'both'"> ref="checkboxArchivedFilter"
<input type="checkbox"> data-title-both="{{.locale.Tr "home.show_both_archived_unarchived"}}"
<label> data-title-unarchived="{{.locale.Tr "home.show_only_unarchived"}}"
{{svg "octicon-archive" 16 "gt-mr-2"}} data-title-archived="{{.locale.Tr "home.show_only_archived"}}"
{{.locale.Tr "home.show_archived"}} :title="checkboxArchivedFilterTitle"
</label> >
</div> <!--the "hidden" is necessary to make the checkbox work without Fomantic UI js,
<div class="ui checkbox" id="archivedFilterCheckbox" title="{{.locale.Tr "home.show_only_unarchived"}}" v-if="archivedFilter === 'unarchived'"> otherwise if the "input" handles click event for intermediate status, it breaks the internal state-->
<input type="checkbox"> <input type="checkbox" class="hidden" v-bind.prop="checkboxArchivedFilterProps">
<label>
{{svg "octicon-archive" 16 "gt-mr-2"}}
{{.locale.Tr "home.show_archived"}}
</label>
</div>
<div class="ui checkbox" id="archivedFilterCheckbox" title="{{.locale.Tr "home.show_only_archived"}}" v-if="archivedFilter === 'archived'">
<input type="checkbox">
<label> <label>
{{svg "octicon-archive" 16 "gt-mr-2"}} {{svg "octicon-archive" 16 "gt-mr-2"}}
{{.locale.Tr "home.show_archived"}} {{.locale.Tr "home.show_archived"}}
</label> </label>
</div> </div>
</a> </a>
</div> <a class="item" @click="togglePrivateFilter()">
<div class="item"> <div class="ui checkbox"
<a @click="togglePrivateFilter()"> ref="checkboxPrivateFilter"
<div class="ui checkbox" id="privateFilterCheckbox" title="{{.locale.Tr "home.show_both_private_public"}}" v-if="privateFilter === 'both'"> data-title-both="{{.locale.Tr "home.show_both_private_public"}}"
<input type="checkbox"> data-title-public="{{.locale.Tr "home.show_only_public"}}"
<label> data-title-private="{{.locale.Tr "home.show_only_private"}}"
{{svg "octicon-lock" 16 "gt-mr-2"}} :title="checkboxPrivateFilterTitle"
{{.locale.Tr "home.show_private"}} >
</label> <input type="checkbox" class="hidden" v-bind.prop="checkboxPrivateFilterProps">
</div>
<div class="ui checkbox" id="privateFilterCheckbox" title="{{.locale.Tr "home.show_only_public"}}" v-if="privateFilter === 'public'">
<input type="checkbox">
<label>
{{svg "octicon-lock" 16 "gt-mr-2"}}
{{.locale.Tr "home.show_private"}}
</label>
</div>
<div class="ui checkbox" id="privateFilterCheckbox" title="{{.locale.Tr "home.show_only_private"}}" v-if="privateFilter === 'private'">
<input type="checkbox">
<label> <label>
{{svg "octicon-lock" 16 "gt-mr-2"}} {{svg "octicon-lock" 16 "gt-mr-2"}}
{{.locale.Tr "home.show_private"}} {{.locale.Tr "home.show_private"}}
@ -98,7 +81,6 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="ui secondary tiny pointing borderless menu center grid repos-filter"> <div class="ui secondary tiny pointing borderless menu center grid repos-filter">
<a class="item" :class="{active: reposFilter === 'all'}" @click="changeReposFilter('all')"> <a class="item" :class="{active: reposFilter === 'all'}" @click="changeReposFilter('all')">
{{.locale.Tr "all"}} {{.locale.Tr "all"}}

View File

@ -87,6 +87,7 @@ function initVueComponents(app) {
} }
return { return {
hasMounted: false, // accessing $refs in computed() need to wait for mounted
tab, tab,
repos: [], repos: [],
reposTotalCount: 0, reposTotalCount: 0,
@ -134,7 +135,19 @@ function initVueComponents(app) {
}, },
repoTypeCount() { repoTypeCount() {
return this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`]; return this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`];
} },
checkboxArchivedFilterTitle() {
return this.hasMounted && this.$refs.checkboxArchivedFilter?.getAttribute(`data-title-${this.archivedFilter}`);
},
checkboxArchivedFilterProps() {
return {checked: this.archivedFilter === 'archived', indeterminate: this.archivedFilter === 'both'};
},
checkboxPrivateFilterTitle() {
return this.hasMounted && this.$refs.checkboxPrivateFilter?.getAttribute(`data-title-${this.privateFilter}`);
},
checkboxPrivateFilterProps() {
return {checked: this.privateFilter === 'private', indeterminate: this.privateFilter === 'both'};
},
}, },
mounted() { mounted() {
@ -144,10 +157,11 @@ function initVueComponents(app) {
initTooltip(elTooltip); initTooltip(elTooltip);
} }
$(el).find('.dropdown').dropdown(); $(el).find('.dropdown').dropdown();
this.setCheckboxes();
nextTick(() => { nextTick(() => {
this.$refs.search.focus(); this.$refs.search.focus();
}); });
this.hasMounted = true;
}, },
methods: { methods: {
@ -156,39 +170,6 @@ function initVueComponents(app) {
this.updateHistory(); this.updateHistory();
}, },
setCheckboxes() {
switch (this.archivedFilter) {
case 'unarchived':
$('#archivedFilterCheckbox').checkbox('set unchecked');
break;
case 'archived':
$('#archivedFilterCheckbox').checkbox('set checked');
break;
case 'both':
$('#archivedFilterCheckbox').checkbox('set indeterminate');
break;
default:
this.archivedFilter = 'unarchived';
$('#archivedFilterCheckbox').checkbox('set unchecked');
break;
}
switch (this.privateFilter) {
case 'public':
$('#privateFilterCheckbox').checkbox('set unchecked');
break;
case 'private':
$('#privateFilterCheckbox').checkbox('set checked');
break;
case 'both':
$('#privateFilterCheckbox').checkbox('set indeterminate');
break;
default:
this.privateFilter = 'both';
$('#privateFilterCheckbox').checkbox('set indeterminate');
break;
}
},
changeReposFilter(filter) { changeReposFilter(filter) {
this.reposFilter = filter; this.reposFilter = filter;
this.repos = []; this.repos = [];
@ -245,45 +226,29 @@ function initVueComponents(app) {
}, },
toggleArchivedFilter() { toggleArchivedFilter() {
switch (this.archivedFilter) { if (this.archivedFilter === 'unarchived') {
case 'both':
this.archivedFilter = 'unarchived';
break;
case 'unarchived':
this.archivedFilter = 'archived'; this.archivedFilter = 'archived';
break; } else if (this.archivedFilter === 'archived') {
case 'archived':
this.archivedFilter = 'both'; this.archivedFilter = 'both';
break; } else { // including both
default:
this.archivedFilter = 'unarchived'; this.archivedFilter = 'unarchived';
break;
} }
this.page = 1; this.page = 1;
this.repos = []; this.repos = [];
this.setCheckboxes();
this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0; this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0;
this.searchRepos(); this.searchRepos();
}, },
togglePrivateFilter() { togglePrivateFilter() {
switch (this.privateFilter) { if (this.privateFilter === 'both') {
case 'both':
this.privateFilter = 'public'; this.privateFilter = 'public';
break; } else if (this.privateFilter === 'public') {
case 'public':
this.privateFilter = 'private'; this.privateFilter = 'private';
break; } else { // including private
case 'private':
this.privateFilter = 'both'; this.privateFilter = 'both';
break;
default:
this.privateFilter = 'both';
break;
} }
this.page = 1; this.page = 1;
this.repos = []; this.repos = [];
this.setCheckboxes();
this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0; this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0;
this.searchRepos(); this.searchRepos();
}, },