summaryrefslogtreecommitdiffstats
path: root/web_src
diff options
context:
space:
mode:
authorGiteabot <teabot@gitea.io>2023-06-13 08:42:25 -0400
committerGitHub <noreply@github.com>2023-06-13 12:42:25 +0000
commit3ea544d89cebdf1c6874857281370497538ac335 (patch)
tree6704377840bedf0221ca310323af07c71836cfe5 /web_src
parent9cef7a4600d96220fb46817bb2efc9f7b7c4a58f (diff)
downloadgitea-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.css1
-rw-r--r--web_src/css/modules/select.css25
-rw-r--r--web_src/js/components/ScopedAccessTokenSelector.vue187
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>