diff options
author | Giteabot <teabot@gitea.io> | 2023-06-13 08:42:25 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-13 12:42:25 +0000 |
commit | 3ea544d89cebdf1c6874857281370497538ac335 (patch) | |
tree | 6704377840bedf0221ca310323af07c71836cfe5 /web_src | |
parent | 9cef7a4600d96220fb46817bb2efc9f7b7c4a58f (diff) | |
download | gitea-3ea544d89cebdf1c6874857281370497538ac335.tar.gz gitea-3ea544d89cebdf1c6874857281370497538ac335.zip |
Change access token UI to select dropdowns (#25109) (#25230)
Backport #25109 by @jtran
The current UI to create API access tokens uses checkboxes that have a
complicated relationship where some need to be checked and/or disabled
in certain states. It also requires that a user interact with it to
understand what their options really are.
This branch changes to use `<select>`s. It better fits the available
options, and it's closer to [GitHub's
UI](https://github.com/settings/personal-access-tokens/new), which is
good, in my opinion. It's more mobile friendly since the tap-areas are
larger. If we ever add more permissions, like Maintainer, there's a
natural place that doesn't take up more screen real-estate.
This branch also fixes a few minor issues:
- Hide the error about selecting at least one permission after second
submission
- Fix help description to call it "authorization" since that's what
permissions are about (not authentication)
Related: #24767.
<img width="883" alt="Screenshot 2023-06-07 at 5 07 34 PM"
src="https://github.com/go-gitea/gitea/assets/10803/6b63d807-c9be-4a4b-8e53-ecab6cbb8f76">
---
When it's open:
<img width="881" alt="Screenshot 2023-06-07 at 5 07 59 PM"
src="https://github.com/go-gitea/gitea/assets/10803/2432c6d0-39c2-4ca4-820e-c878ffdbfb69">
Co-authored-by: Jonathan Tran <jon@allspice.io>
Diffstat (limited to 'web_src')
-rw-r--r-- | web_src/css/index.css | 1 | ||||
-rw-r--r-- | web_src/css/modules/select.css | 25 | ||||
-rw-r--r-- | web_src/js/components/ScopedAccessTokenSelector.vue | 187 |
3 files changed, 107 insertions, 106 deletions
diff --git a/web_src/css/index.css b/web_src/css/index.css index 689c3f4a29..c9cc1d8032 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -1,6 +1,7 @@ @import "./modules/normalize.css"; @import "./modules/animations.css"; @import "./modules/button.css"; +@import "./modules/select.css"; @import "./modules/tippy.css"; @import "./modules/modal.css"; @import "./modules/breadcrumb.css"; diff --git a/web_src/css/modules/select.css b/web_src/css/modules/select.css new file mode 100644 index 0000000000..57a87888e0 --- /dev/null +++ b/web_src/css/modules/select.css @@ -0,0 +1,25 @@ +.gitea-select { + position: relative; +} + +.gitea-select select { + appearance: none; /* hide default triangle */ +} + +/* ::before and ::after pseudo elements don't work on select elements, + so we need to put it on the parent. */ +.gitea-select::after { + position: absolute; + top: 12px; + right: 8px; + pointer-events: none; + content: ''; + width: 14px; + height: 14px; + mask-size: cover; + -webkit-mask-size: cover; + mask-image: var(--octicon-chevron-right); + -webkit-mask-image: var(--octicon-chevron-right); + transform: rotate(90deg); /* point the chevron down */ + background: currentcolor; +} diff --git a/web_src/js/components/ScopedAccessTokenSelector.vue b/web_src/js/components/ScopedAccessTokenSelector.vue index 769f3262b4..b4b9b979ea 100644 --- a/web_src/js/components/ScopedAccessTokenSelector.vue +++ b/web_src/js/components/ScopedAccessTokenSelector.vue @@ -1,97 +1,100 @@ <template> - <div class="scoped-access-token-category"> - <div class="field gt-pl-2"> - <label class="checkbox-label"> - <input - ref="category" - v-model="categorySelected" - class="scope-checkbox scoped-access-token-input" - type="checkbox" - name="scope" - :value="'write:' + category" - @input="onCategoryInput" - > - {{ category }} - </label> - </div> - <div class="field gt-pl-4"> - <div class="inline field"> - <label class="checkbox-label"> - <input - ref="read" - v-model="readSelected" - :disabled="disableIndividual || writeSelected" - class="scope-checkbox scoped-access-token-input" - type="checkbox" - name="scope" - :value="'read:' + category" - @input="onIndividualInput" - > - read:{{ category }} - </label> - </div> - <div class="inline field"> - <label class="checkbox-label"> - <input - ref="write" - v-model="writeSelected" - :disabled="disableIndividual" - class="scope-checkbox scoped-access-token-input" - type="checkbox" - name="scope" - :value="'write:' + category" - @input="onIndividualInput" - > - write:{{ category }} - </label> - </div> + <div v-for="category in categories" :key="category" class="field gt-pl-2 gt-pb-2 access-token-category"> + <label class="category-label" :for="'access-token-scope-' + category"> + {{ category }} + </label> + <div class="gitea-select"> + <select + class="ui selection access-token-select" + name="scope" + :id="'access-token-scope-' + category" + > + <option value=""> + {{ noAccessLabel }} + </option> + <option :value="'read:' + category"> + {{ readLabel }} + </option> + <option :value="'write:' + category"> + {{ writeLabel }} + </option> + </select> </div> </div> </template> <script> import {createApp} from 'vue'; -import {showElem} from '../utils/dom.js'; +import {hideElem, showElem} from '../utils/dom.js'; const sfc = { props: { - category: { + isAdmin: { + type: Boolean, + required: true, + }, + noAccessLabel: { + type: String, + required: true, + }, + readLabel: { + type: String, + required: true, + }, + writeLabel: { type: String, required: true, }, }, - data: () => ({ - categorySelected: false, - disableIndividual: false, - readSelected: false, - writeSelected: false, - }), + computed: { + categories() { + const categories = [ + 'activitypub', + ]; + if (this.isAdmin) { + categories.push('admin'); + } + categories.push( + 'issue', + 'misc', + 'notification', + 'organization', + 'package', + 'repository', + 'user'); + return categories; + } + }, + + mounted() { + document.getElementById('scoped-access-submit').addEventListener('click', this.onClickSubmit); + }, + + unmounted() { + document.getElementById('scoped-access-submit').removeEventListener('click', this.onClickSubmit); + }, methods: { - /** - * When entire category is toggled - * @param {Event} e - */ - onCategoryInput(e) { + onClickSubmit(e) { e.preventDefault(); - this.disableIndividual = this.$refs.category.checked; - this.writeSelected = this.$refs.category.checked; - this.readSelected = this.$refs.category.checked; - }, - /** - * When an individual level of category is toggled - * @param {Event} e - */ - onIndividualInput(e) { - e.preventDefault(); - if (this.$refs.write.checked) { - this.readSelected = true; + const warningEl = document.getElementById('scoped-access-warning'); + // check that at least one scope has been selected + for (const el of document.getElementsByClassName('access-token-select')) { + if (el.value) { + // Hide the error if it was visible from previous attempt. + hideElem(warningEl); + // Submit the form. + document.getElementById('scoped-access-form').submit(); + // Don't show the warning. + return; + } } - this.categorySelected = this.$refs.write.checked; - }, - } + // no scopes selected, show validation error + showElem(warningEl); + } + }, }; export default sfc; @@ -100,39 +103,11 @@ export default sfc; * Initialize category toggle sections */ export function initScopedAccessTokenCategories() { - for (const el of document.getElementsByTagName('scoped-access-token-category')) { - const category = el.getAttribute('category'); - createApp(sfc, { - category, - }).mount(el); + for (const el of document.getElementsByClassName('scoped-access-token-mount')) { + createApp({}) + .component('scoped-access-token-selector', sfc) + .mount(el); } - - document.getElementById('scoped-access-submit')?.addEventListener('click', (e) => { - e.preventDefault(); - // check that at least one scope has been selected - for (const el of document.getElementsByClassName('scoped-access-token-input')) { - if (el.checked) { - document.getElementById('scoped-access-form').submit(); - } - } - // no scopes selected, show validation error - showElem(document.getElementById('scoped-access-warning')); - }); } </script> - -<style scoped> -.scoped-access-token-category { - padding-top: 10px; - padding-bottom: 10px; -} - -.checkbox-label { - cursor: pointer; -} - -.scope-checkbox { - margin: 4px 5px 0 0; -} -</style> |