fixing uxd issues (#477)

* fixing uxd issues

* add select all checkbox

* added intermidate "style" to select all checkbox
This commit is contained in:
Erik Jan de Wit 2021-04-07 07:42:30 +02:00 committed by GitHub
parent dd19613557
commit 2417c285e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 80 additions and 62 deletions

View file

@ -312,7 +312,6 @@ export const ClientDetails = () => {
> >
<Tabs <Tabs
activeKey={activeTab2} activeKey={activeTab2}
isSecondary
onSelect={(_, key) => setActiveTab2(key as number)} onSelect={(_, key) => setActiveTab2(key as number)}
> >
<Tab <Tab

View file

@ -34,6 +34,7 @@
"client": "Client scope", "client": "Client scope",
"assigned": "Assigned type" "assigned": "Assigned type"
}, },
"assignedClientScope": "Assigned client scope",
"assignedType": "Assigned type", "assignedType": "Assigned type",
"hideInheritedRoles": "Hide inherited roles", "hideInheritedRoles": "Hide inherited roles",
"inherentFrom": "Inherited from", "inherentFrom": "Inherited from",

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
Button, Button,
@ -10,18 +10,13 @@ import {
DropdownDirection, DropdownDirection,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { CaretUpIcon } from "@patternfly/react-icons"; import { CaretUpIcon } from "@patternfly/react-icons";
import {
Table,
TableBody,
TableHeader,
TableVariant,
} from "@patternfly/react-table";
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation"; import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
import { import {
ClientScopeType, ClientScopeType,
clientScopeTypesDropdown, clientScopeTypesDropdown,
} from "../../components/client-scope/ClientScopeTypes"; } from "../../components/client-scope/ClientScopeTypes";
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
export type AddScopeDialogProps = { export type AddScopeDialogProps = {
clientScopes: ClientScopeRepresentation[]; clientScopes: ClientScopeRepresentation[];
@ -32,11 +27,7 @@ export type AddScopeDialogProps = {
) => void; ) => void;
}; };
type Row = { import "./client-scopes.css";
selected: boolean;
scope: ClientScopeRepresentation;
cells: (string | undefined)[];
};
export const AddScopeDialog = ({ export const AddScopeDialog = ({
clientScopes, clientScopes,
@ -46,26 +37,14 @@ export const AddScopeDialog = ({
}: AddScopeDialogProps) => { }: AddScopeDialogProps) => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
const [addToggle, setAddToggle] = useState(false); const [addToggle, setAddToggle] = useState(false);
const [rows, setRows] = useState<Row[]>([]); const [rows, setRows] = useState<ClientScopeRepresentation[]>([]);
useEffect(() => { const loader = () => Promise.resolve(clientScopes);
setRows(
clientScopes.map((scope) => {
return {
selected: false,
scope,
cells: [scope.name, scope.description],
};
})
);
}, [clientScopes]);
const action = (scope: ClientScopeType) => { const action = (scope: ClientScopeType) => {
const scopes = rows const scopes = rows.map((row) => {
.filter((row) => row.selected) return { scope: row, type: scope };
.map((row) => { });
return { scope: row.scope, type: scope };
});
onAdd(scopes); onAdd(scopes);
setAddToggle(false); setAddToggle(false);
toggleDialog(); toggleDialog();
@ -79,12 +58,14 @@ export const AddScopeDialog = ({
onClose={toggleDialog} onClose={toggleDialog}
actions={[ actions={[
<Dropdown <Dropdown
className="keycloak__client-scopes-add__add-dropdown"
id="add-dropdown" id="add-dropdown"
key="add-dropdown" key="add-dropdown"
direction={DropdownDirection.up} direction={DropdownDirection.up}
isOpen={addToggle} isOpen={addToggle}
toggle={ toggle={
<DropdownToggle <DropdownToggle
isDisabled={rows.length === 0}
onToggle={() => setAddToggle(!addToggle)} onToggle={() => setAddToggle(!addToggle)}
isPrimary isPrimary
toggleIndicator={CaretUpIcon} toggleIndicator={CaretUpIcon}
@ -100,12 +81,7 @@ export const AddScopeDialog = ({
key="cancel" key="cancel"
variant={ButtonVariant.secondary} variant={ButtonVariant.secondary}
onClick={() => { onClick={() => {
setRows( setRows([]);
rows.map((row) => {
row.selected = false;
return row;
})
);
toggleDialog(); toggleDialog();
}} }}
> >
@ -113,28 +89,21 @@ export const AddScopeDialog = ({
</Button>, </Button>,
]} ]}
> >
<Table <KeycloakDataTable
variant={TableVariant.compact} loader={loader}
cells={[t("common:name"), t("common:description")]} ariaLabelKey="client-scopes:chooseAMapperType"
onSelect={(_, isSelected, rowIndex) => { searchPlaceholderKey="client-scopes:searchFor"
if (rowIndex === -1) { canSelectAll
setRows( onSelect={(rows) => setRows(rows)}
rows.map((row) => { columns={[
row.selected = isSelected; {
return row; name: "name",
}) },
); {
} else { name: "description",
rows[rowIndex].selected = isSelected; },
setRows([...rows]); ]}
} />
}}
rows={rows}
aria-label={t("chooseAMapperType")}
>
<TableHeader />
<TableBody />
</Table>
</Modal> </Modal>
); );
}; };

View file

@ -27,6 +27,8 @@ import {
import { useAlerts } from "../../components/alert/Alerts"; import { useAlerts } from "../../components/alert/Alerts";
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable"; import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
import "./client-scopes.css";
export type ClientScopesProps = { export type ClientScopesProps = {
clientId: string; clientId: string;
protocol: string; protocol: string;
@ -197,9 +199,11 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
loader={loader} loader={loader}
ariaLabelKey="clients:clientScopeList" ariaLabelKey="clients:clientScopeList"
searchPlaceholderKey="clients:searchByName" searchPlaceholderKey="clients:searchByName"
canSelectAll
onSelect={(rows) => setSelectedRows([...rows])} onSelect={(rows) => setSelectedRows([...rows])}
searchTypeComponent={ searchTypeComponent={
<Dropdown <Dropdown
className="keycloak__client-scopes__searchtype"
toggle={ toggle={
<DropdownToggle <DropdownToggle
id="toggle-id" id="toggle-id"
@ -245,6 +249,7 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
key="add-dropdown" key="add-dropdown"
isOpen={addToggle} isOpen={addToggle}
selections={[]} selections={[]}
isDisabled={selectedRows.length === 0}
placeholderText={t("changeTypeTo")} placeholderText={t("changeTypeTo")}
onToggle={() => setAddToggle(!addToggle)} onToggle={() => setAddToggle(!addToggle)}
onSelect={async (_, value) => { onSelect={async (_, value) => {
@ -323,6 +328,7 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
columns={[ columns={[
{ {
name: "name", name: "name",
displayKey: "clients:assignedClientScope",
}, },
{ {
name: "type", name: "type",
@ -331,6 +337,24 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
}, },
{ name: "description" }, { name: "description" },
]} ]}
actions={[
{
title: t("common:remove"),
onRowClick: async (row) => {
try {
await removeScope(adminClient, clientId, row, row.type);
addAlert(t("clientScopeRemoveSuccess"), AlertVariant.success);
refresh();
} catch (error) {
addAlert(
t("clientScopeRemoveError", { error }),
AlertVariant.danger
);
}
return true;
},
},
]}
emptyState={ emptyState={
<ListEmptyState <ListEmptyState
message={t("clients:emptyClientScopes")} message={t("clients:emptyClientScopes")}

View file

@ -0,0 +1,8 @@
.keycloak__client-scopes__searchtype button {
width: 200px;
}
.keycloak__client-scopes-add__add-dropdown {
margin-right: var(--pf-global--spacer--md);
}

View file

@ -153,6 +153,22 @@ export function KeycloakDataTable<T>({
const refresh = () => setKey(new Date().getTime()); const refresh = () => setKey(new Date().getTime());
const handleError = useErrorHandler(); const handleError = useErrorHandler();
useEffect(() => {
if (canSelectAll) {
const checkboxes = document
.getElementsByClassName("pf-c-table__check")
.item(0);
if (checkboxes) {
const checkAllCheckbox = checkboxes.children!.item(
0
)! as HTMLInputElement;
checkAllCheckbox.indeterminate =
selected.length > 0 &&
selected.length < (filteredData || rows)!.length;
}
}
}, [selected]);
useEffect(() => { useEffect(() => {
return asyncStateFetch( return asyncStateFetch(
async () => { async () => {
@ -242,25 +258,26 @@ export function KeycloakDataTable<T>({
); );
const _onSelect = (isSelected: boolean, rowIndex: number) => { const _onSelect = (isSelected: boolean, rowIndex: number) => {
const data = filteredData || rows;
if (rowIndex === -1) { if (rowIndex === -1) {
setRows( setRows(
rows!.map((row) => { data!.map((row) => {
row.selected = isSelected; row.selected = isSelected;
return row; return row;
}) })
); );
} else { } else {
rows![rowIndex].selected = isSelected; data![rowIndex].selected = isSelected;
setRows([...rows!]); setRows([...rows!]);
} }
const difference = _.differenceBy( const difference = _.differenceBy(
selected, selected,
rows!.map((row) => row.data), data!.map((row) => row.data),
"id" "id"
); );
const selectedRows = [ const selectedRows = [
...difference, ...difference,
...rows!.filter((row) => row.selected).map((row) => row.data), ...data!.filter((row) => row.selected).map((row) => row.data),
]; ];
setSelected(selectedRows); setSelected(selectedRows);
onSelect!(selectedRows); onSelect!(selectedRows);