From 31006b937365b3ae089075e24cd08c44b4908934 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Tue, 19 Oct 2021 11:08:46 -0400 Subject: [PATCH] Autocomplete --- app/api/urls.py | 4 +- .../js/components/EditPermissionsPanel.jsx | 80 ++++++++++++++++--- .../app/js/css/EditPermissionsPanel.scss | 35 +++++++- 3 files changed, 105 insertions(+), 14 deletions(-) diff --git a/app/api/urls.py b/app/api/urls.py index 767cffbd..c806d1bd 100644 --- a/app/api/urls.py +++ b/app/api/urls.py @@ -27,8 +27,8 @@ tasks_router = routers.NestedSimpleRouter(router, r'projects', lookup='project') tasks_router.register(r'tasks', TaskViewSet, base_name='projects-tasks') admin_router = routers.DefaultRouter() -admin_router.register(r'admin/users', UserViewSet, basename='user') -admin_router.register(r'admin/groups', GroupViewSet, basename='group') +admin_router.register(r'admin/users', UserViewSet, base_name='admin-users') +admin_router.register(r'admin/groups', GroupViewSet, base_name='admin-groups') urlpatterns = [ url(r'processingnodes/options/$', ProcessingNodeOptionsView.as_view()), diff --git a/app/static/app/js/components/EditPermissionsPanel.jsx b/app/static/app/js/components/EditPermissionsPanel.jsx index c4ccd395..d08725d3 100644 --- a/app/static/app/js/components/EditPermissionsPanel.jsx +++ b/app/static/app/js/components/EditPermissionsPanel.jsx @@ -26,10 +26,14 @@ class EditPermissionsPanel extends React.Component { loading: false, permissions: [], validUsernames: {}, - validatingUser: false + validatingUser: false, + validationUnavailable: false }; this.backgroundFailedColor = Css.getValue('btn-danger', 'backgroundColor'); + this.autocompleteBorderColor = Css.getValue('btn-default', 'backgroundColor'); + this.backgroundColor = Css.getValue('theme-secondary', 'backgroundColor'); + this.highlightColor = Css.getValue('theme-background-highlight', 'backgroundColor'); } loadPermissions = () => { @@ -49,7 +53,7 @@ class EditPermissionsPanel extends React.Component { }); } - validateUsername = (username) => { + autocomplete = (perm) => { if (this.validateReq){ this.validateReq.abort(); this.validateReq = null; @@ -60,18 +64,29 @@ class EditPermissionsPanel extends React.Component { this.validateTimeout = null; } + + // Empty case + if (perm.username === ""){ + delete(perm.autocomplete); + this.setState({permissions: this.state.permissions}); + return; + } + this.setState({validatingUser: true}); - this.validateTimeout = setTimeout(() => { - this.validateReq = $.getJSON(`/api/users/?limit=30&search=${encodeURIComponent(username)}`) + this.validateReq = $.getJSON(`/api/users/?limit=30&search=${encodeURIComponent(perm.username)}`) .done((json) => { json.forEach(u => { this.state.validUsernames[u.username] = true; }); + + this.state.permissions.forEach(p => delete(p.autocomplete)); + perm.autocomplete = json; - this.setState({validUsernames: this.state.validUsernames}); + this.setState({validUsernames: this.state.validUsernames, permissions: this.state.permissions}); }).fail(() => { - + // Perhaps the user API is not enabled + this.setState({validationUnavailable: true}); }).always(() => { this.setState({validatingUser: false}); }); @@ -88,15 +103,18 @@ class EditPermissionsPanel extends React.Component { if (this.validateTimeout) clearTimeout(this.validateTimeout); } - handleChangePermissionRole = e => { - + handleChangePermissionRole = perm => { + return e => { + perm.permissions = this.extendedPermissions(e.target.value); + this.setState({permissions: this.state.permissions}); + } } handleChangePermissionUser = perm => { return e => { perm.username = e.target.value; - this.validateUsername(perm.username); + this.autocomplete(perm); // Update this.setState({permissions: this.state.permissions}); @@ -111,6 +129,14 @@ class EditPermissionsPanel extends React.Component { else return ""; } + extendedPermissions = simPerm => { + if (simPerm == "rw"){ + return ["add", "change", "delete", "view"]; + }else if (simPerm == "r"){ + return ["view"]; + }else return []; + } + permissionLabel = simPerm => { if (simPerm === "rw") return _("Read/Write"); else if (simPerm === "r") return _("Read"); @@ -122,7 +148,7 @@ class EditPermissionsPanel extends React.Component { } getColorFor = (username) => { - if (this.state.validatingUser || this.state.validUsernames[username]) return ""; + if (this.state.validationUnavailable || this.state.validatingUser || this.state.validUsernames[username]) return ""; else return this.backgroundFailedColor; } @@ -144,6 +170,31 @@ class EditPermissionsPanel extends React.Component { } } + acOnMouseEnter = e => { + e.target.style.backgroundColor = this.highlightColor; + } + + acOnMouseLeave = e => { + e.target.style.backgroundColor = ""; + } + + acOnClick = (perm, acEntry) => { + return e => { + perm.username = acEntry.username; + delete(perm.autocomplete); + this.setState({permissions: this.state.permissions}); + } + } + + onBlur = perm => { + return e => { + setTimeout(() => { + delete(perm.autocomplete); + this.setState({permissions: this.state.permissions}); + }, 150); + } + } + render() { const permissions = this.state.permissions.map((p, i) =>
@@ -154,17 +205,24 @@ class EditPermissionsPanel extends React.Component { onChange={this.handleChangePermissionUser(p)} type="text" autoComplete="off" + onBlur={this.onBlur(p)} disabled={p.owner} value={p.username} className="form-control username" placeholder={_("Username")} ref={(domNode) => this.lastTextbox = domNode} /> + {p.autocomplete ?
+ {p.autocomplete.map(ac =>
+
{ac.username}
+
{ac.email}
+
)} +
: ""}
{!p.owner ? : ""}
- {this.allPermissions().map(p => )}
diff --git a/app/static/app/js/css/EditPermissionsPanel.scss b/app/static/app/js/css/EditPermissionsPanel.scss index 89406a67..921ceed6 100644 --- a/app/static/app/js/css/EditPermissionsPanel.scss +++ b/app/static/app/js/css/EditPermissionsPanel.scss @@ -10,7 +10,7 @@ .user-indicator{ position: absolute; left: 12px; - top: 15px; + top: 16px; } .remove{ position: relative; @@ -33,6 +33,39 @@ .username-container{ flex-grow: 0.66; + position: relative; + + .autocomplete{ + left: 0; + top: 44px; + z-index: 2; + border-width: 1px; + border-style: solid; + width: 100%; + position: absolute; + border-radius: 2px; + max-height: 240px; + overflow-y: auto; + overflow-x: hidden; + + .ac-entry{ + padding: 10px 15px 10px 15px; + border-bottom-style: solid; + border-bottom-width: 1px; + + &:hover{ + cursor: pointer; + } + } + + .ac-entry:last-child{ + border: none; + } + + .ac-email{ + opacity: 0.8; + } + } } .role-container{ flex-grow: 0.33;