diff --git a/package.json b/package.json
index 1f73bc104..2fa9157fb 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,8 @@
"private": true,
"dependencies": {
"emotion": "^9.0.2",
+ "interweave": "^8.0.2",
+ "makecancelable": "^1.0.0",
"prop-types": "^15.6.1",
"react": "^16.2.0",
"react-dom": "^16.2.0",
diff --git a/src/components/05_pages/Permissions/Permissions.js b/src/components/05_pages/Permissions/Permissions.js
index db8ca5e9a..fd8679850 100644
--- a/src/components/05_pages/Permissions/Permissions.js
+++ b/src/components/05_pages/Permissions/Permissions.js
@@ -1,17 +1,160 @@
-import React from 'react';
-import { css } from 'emotion';
+import React, { Component, Fragment } from 'react';
+import makeCancelable from 'makecancelable';
+import { Markup } from 'interweave';
-const styles = {
- title: css`
- text-decoration: underline;
- `,
-};
+import Loading from '../../Helpers/Loading';
+import { Table, TBody, THead } from '../../UI/table';
-const Permissions = () => (
-
-
Permissions
-
This will be the permissions page.
-
-);
+const Permissions = class Permissions extends Component {
+ state = {
+ loaded: false,
+ rawPermissions: [],
+ renderablePermissions: [],
+ };
+ componentDidMount() {
+ this.cancelFetch = makeCancelable(
+ Promise.all([
+ fetch(
+ `${
+ process.env.REACT_APP_DRUPAL_BASE_URL
+ }/admin-api/permissions?_format=json`,
+ ).then(res => res.json()),
+ fetch(
+ `${
+ process.env.REACT_APP_DRUPAL_BASE_URL
+ }/jsonapi/user_role/user_role`,
+ { headers: { Accept: 'application/vnd.api+json' } },
+ ).then(res => res.json()),
+ ])
+ .then(([permissions, { data: roles }]) =>
+ this.setState({
+ rawPermissions: permissions,
+ renderablePermissions: permissions,
+ changedRoles: [],
+ // Move admin roles to the right.
+ roles: roles.sort((a, b) => {
+ if (a.attributes.is_admin && b.attributes.is_admin) {
+ return a.attributes.id - b.attributes.id;
+ } else if (a.attributes.is_admin) {
+ return 1;
+ } else if (b.attributes.is_admin) {
+ return -1;
+ }
+ return a.attributes.id - b.attributes.id;
+ }),
+ loaded: true,
+ }),
+ )
+ .catch(err => this.setState({ loaded: false, err })),
+ );
+ }
+ componentWillUnmount() {
+ this.cancelFetch();
+ }
+ onPermissionCheck = (roleName, permission) => {
+ this.setState(prevState => ({
+ changedRoles: [...new Set(prevState.changedRoles).add(roleName).values()],
+ roles: this.togglePermission(permission, roleName, prevState.roles),
+ }));
+ };
+ togglePermission = (permission, roleName, roles) => {
+ const roleIndex = roles.map(role => role.attributes.id).indexOf(roleName);
+ const role = roles[roleIndex];
+ const index = role.attributes.permissions.indexOf(permission);
+ if (index !== -1) {
+ role.attributes.permissions.splice(index, 1);
+ } else {
+ role.attributes.permissions.push(permission);
+ }
+ roles[roleIndex] = role;
+ return roles;
+ };
+ groupPermissions = permissions =>
+ Object.entries(
+ permissions.reduce((acc, cur) => {
+ acc[cur.provider] = acc[cur.provider] || {
+ providerLabel: cur.provider_label,
+ permissions: [],
+ };
+ acc[cur.provider].permissions.push(cur);
+ return acc;
+ }, {}),
+ );
+ createTableRows = (groupedPermissions, roles) =>
+ [].concat(
+ ...groupedPermissions.map(
+ ([providerMachineName, { providerLabel, permissions }]) => [
+ {
+ key: `permissionGroup-${providerMachineName}`,
+ colspan: roles.length + 1,
+ tds: [[`td-${providerMachineName}`, {providerLabel}]],
+ },
+ ...permissions.map(permission => ({
+ key: `permissionGroup-${providerMachineName}-${permission.title}`,
+ tds: [
+ [
+ `td-${providerMachineName}-${permission.title}`,
+ ,
+ ],
+ ...roles.map(({ attributes }, index) => [
+ `td-${providerMachineName}-${permission.title}-${index}-cb`,
+ attributes.is_admin && attributes.id === 'administrator' ? (
+
+ ) : (
+
+ this.onPermissionCheck(attributes.id, permission.id)
+ }
+ checked={attributes.permissions.includes(permission.id)}
+ />
+ ),
+ ]),
+ ],
+ })),
+ ],
+ ),
+ );
+ handleKeyPress = event => {
+ const input = event.target.value.toLowerCase();
+ this.setState(prevState => ({
+ ...prevState,
+ renderablePermissions: prevState.rawPermissions.filter(
+ ({ title, description, provider, provider_label: providerLabel }) =>
+ `${title}${description}${provider}${providerLabel}`.includes(input),
+ ),
+ }));
+ };
+ render() {
+ return !this.state.loaded ? (
+
+ ) : (
+
+
+
+
+ label.toUpperCase(),
+ ),
+ ]}
+ />
+
+
+
+ );
+ }
+};
export default Permissions;
diff --git a/src/components/Helpers/Loading.js b/src/components/Helpers/Loading.js
new file mode 100644
index 000000000..72562817a
--- /dev/null
+++ b/src/components/Helpers/Loading.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import { css, keyframes } from 'emotion';
+
+const rotate = keyframes`
+ from {
+ transform: rotate(-10deg);
+ }
+ to {
+ transform: rotate(10deg);
+ }
+`;
+
+const styles = {
+ wrap: css`
+ margin: 100px auto 0;
+ `,
+ peace: css`
+ display: inline-block;
+ vertical-align: middle;
+ animation-direction: alternate;
+ animation-iteration-count: infinite;
+ animation-duration: 0.5s;
+ animation-timing-function: cubic-bezier(0, 0, 1, 1);
+ transform-origin: bottom;
+ font-size: 50px;
+ animation-name: ${rotate};
+ `,
+};
+
+const Loading = () => (
+
+
+ ✌️
+
+
+);
+
+export default Loading;
diff --git a/src/components/UI/table.js b/src/components/UI/table.js
new file mode 100644
index 000000000..e029ec07f
--- /dev/null
+++ b/src/components/UI/table.js
@@ -0,0 +1,73 @@
+import React from 'react';
+import { css } from 'emotion';
+import {
+ node,
+ bool,
+ oneOfType,
+ arrayOf,
+ string,
+ shape,
+ number,
+} from 'prop-types';
+
+const TABLE = ({ children, zebra, ...props }) => {
+ const styles = css`
+ ${zebra ? 'tbody tr:nth-child(odd) {background-color: #e8e8e8;}' : ''};
+ `;
+ return (
+
+ );
+};
+TABLE.propTypes = {
+ children: oneOfType([arrayOf(node), node]).isRequired,
+ zebra: bool,
+};
+TABLE.defaultProps = {
+ zebra: false,
+};
+
+const TR = ({ children, ...props }) => {children}
;
+TR.propTypes = {
+ children: oneOfType([arrayOf(node), node]).isRequired,
+};
+
+const TD = ({ children, ...props }) => {children} | ;
+TD.propTypes = {
+ children: oneOfType([arrayOf(node), node]).isRequired,
+};
+
+const THEAD = ({ data }) => (
+
+ {data.map(label => {label} | )}
+
+);
+THEAD.propTypes = {
+ data: arrayOf(string).isRequired,
+};
+
+const TBODY = ({ rows }) => (
+
+ {rows.map(({ colspan, tds, key }) => (
+
+ {tds.map(([tdKey, tdValue]) => (
+
+ {tdValue}
+ |
+ ))}
+
+ ))}
+
+);
+TBODY.propTypes = {
+ rows: arrayOf(
+ shape({
+ colspan: number,
+ key: string,
+ tds: arrayOf(node).isRequired,
+ }),
+ ).isRequired,
+};
+
+export { TR, TD, TABLE as Table, TBODY as TBody, THEAD as THead };
diff --git a/yarn.lock b/yarn.lock
index 681b12ffa..ed726b912 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3700,6 +3700,13 @@ interpret@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
+interweave@^8.0.2:
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/interweave/-/interweave-8.0.2.tgz#51001199c58d311f8b1d0100c4f6b9d494e97480"
+ dependencies:
+ babel-runtime "^6.26.0"
+ prop-types "^15.6.0"
+
invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
version "2.2.3"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.3.tgz#1a827dfde7dcbd7c323f0ca826be8fa7c5e9d688"
@@ -4647,6 +4654,10 @@ make-dir@^1.0.0:
dependencies:
pify "^3.0.0"
+makecancelable@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/makecancelable/-/makecancelable-1.0.0.tgz#c7e2606e59db7a4bf8098ff5b52d7f13173499e5"
+
makeerror@1.0.x:
version "1.0.11"
resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"