Skip to content

Commit

Permalink
IIIF #5 - Adding resources index and edit pages
Browse files Browse the repository at this point in the history
  • Loading branch information
dleadbetter committed Jul 11, 2022
1 parent 56e87e1 commit b0d3ac5
Show file tree
Hide file tree
Showing 14 changed files with 291 additions and 28 deletions.
56 changes: 41 additions & 15 deletions client/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import {
import Layout from './components/Layout';
import AuthenticatedRoute from './components/AuthenticatedRoute';
import Dashboard from './pages/Dashboard';
import Organizations from './pages/Organizations';
import Users from './pages/Users';
import Login from './pages/Login';
import Organization from './pages/Organization';
import Organizations from './pages/Organizations';
import Project from './pages/Project';
import Projects from './pages/Projects';
import Resource from './pages/Resource';
import Resources from './pages/Resources';
import User from './pages/User';
import Project from './pages/Project';
import Users from './pages/Users';

const App: ComponentType<any> = useDragDrop(() => (
<Router>
Expand Down Expand Up @@ -49,20 +51,44 @@ const App: ComponentType<any> = useDragDrop(() => (
/>
<Route
element={<Organization />}
path='/organizations/:id'
path='/organizations/:organizationId'
/>
<Route
element={<Projects />}
path='/projects'
/>
<Route
element={<Project />}
path='/projects/new'
/>
<Route
element={<Project />}
path='/projects/:id'
/>
>
<Route
element={<Projects />}
index
/>
<Route
element={<Project />}
path='new'
/>
<Route
path=':projectId'
>
<Route
element={<Project />}
index
/>
<Route
path='resources'
>
<Route
element={<Resources />}
index
/>
<Route
element={<Resource />}
path='new'
/>
<Route
element={<Resource />}
path=':resourceId'
/>
</Route>
</Route>
</Route>
<Route
element={<Users />}
path='/users'
Expand All @@ -73,7 +99,7 @@ const App: ComponentType<any> = useDragDrop(() => (
/>
<Route
element={<User />}
path='/users/:id'
path='/users/:userId'
/>
</Route>
<Route
Expand Down
6 changes: 4 additions & 2 deletions client/src/components/MenuLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import _ from 'underscore';

type Props = {
children?: Node | (active: boolean) => Node,
parent?: boolean,
to: string
};

const MenuLink: ComponentType<any> = (props: Props) => {
const { pathname } = useResolvedPath(props.to);
const active = useMatch({ path: pathname, end: false });
const url = `${props.to}${props.parent ? '/*' : ''}`;
const { pathname } = useResolvedPath(url);
const active = useMatch({ path: pathname, end: true });

return (
<Menu.Item
Expand Down
22 changes: 18 additions & 4 deletions client/src/components/Sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import cx from 'classnames';
import React, { useCallback, type ComponentType } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router-dom';
import { Icon, Menu, Popup } from 'semantic-ui-react';
import AuthenticationService from '../services/Authentication';
import MenuLink from './MenuLink';
Expand All @@ -18,6 +18,7 @@ type Props = Translateable & {

const Sidebar: ComponentType<any> = withTranslation()((props: Props) => {
const navigate = useNavigate();
const params = useParams();

/**
* Logs the user out and navigates to the index page.
Expand Down Expand Up @@ -74,7 +75,7 @@ const Sidebar: ComponentType<any> = withTranslation()((props: Props) => {
trigger={(
<MenuLink
className={styles.item}
index
parent
to='/organizations'
>
<Icon
Expand All @@ -93,14 +94,27 @@ const Sidebar: ComponentType<any> = withTranslation()((props: Props) => {
trigger={(
<MenuLink
className={styles.item}
index
parent
to='/projects'
>
<Icon
className={styles.icon}
name='folder outline'
size='big'
/>
{ params.projectId && (
<Menu.Menu>
<MenuLink
content={props.t('Sidebar.labels.details')}
to={`/projects/${params.projectId}`}
/>
<MenuLink
content={props.t('Sidebar.labels.resources')}
parent
to={`/projects/${params.projectId}/resources`}
/>
</Menu.Menu>
)}
</MenuLink>
)}
/>
Expand All @@ -111,7 +125,7 @@ const Sidebar: ComponentType<any> = withTranslation()((props: Props) => {
trigger={(
<MenuLink
className={styles.item}
index
parent
to='users'
>
<Icon
Expand Down
1 change: 1 addition & 0 deletions client/src/components/SimpleEditPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ const SimpleEditPage: ComponentType<any> = (props: Props) => {
<div>
<Form
error={!_.isEmpty(props.errors)}
noValidate
>
<Message
error
Expand Down
11 changes: 7 additions & 4 deletions client/src/hooks/EditPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import _ from 'underscore';
import { useTranslation } from 'react-i18next';

type Config = {
id: string,
onInitialize: (item: any) => Promise<any>,
onSave: (item: any) => Promise<any>,
required?: Array<string>,
Expand All @@ -17,24 +18,26 @@ type Config = {
const withEditPage = (WrappedComponent: ComponentType<any>, config: Config): any => (props: any) => {
const location = useLocation();
const navigate = useNavigate();
const { id } = useParams();
const { t } = useTranslation();

const params = useParams();
const id = params[config.id];

let tab;

/**
* Adds the authorization error.
*
* @type {function(*): {}}
*/
const resolveValidationError = useCallback((params) => {
const resolveValidationError = useCallback((errorProps) => {
const errors = {};

if (config.resolveValidationError) {
_.extend(errors, config.resolveValidationError(params));
_.extend(errors, config.resolveValidationError(errorProps));
}

if (params.status === 403) {
if (errorProps.status === 403) {
_.extend(errors, { base: t('Common.errors.unauthorized') });
}

Expand Down
5 changes: 5 additions & 0 deletions client/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@
"content": "A bucket on AWS S3 will be created after you save the record.",
"header": "AWS S3"
}
},
"tabs": {
"resources": "Resources"
}
},
"Routes": {
Expand All @@ -93,8 +96,10 @@
"Sidebar": {
"labels": {
"dashboard": "Dashboard",
"details": "Details",
"organizations": "Organizations",
"projects": "Projects",
"resources": "Resources",
"users": "Users"
}
},
Expand Down
1 change: 1 addition & 0 deletions client/src/pages/Organization.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const OrganizationForm = withTranslation()((props) => (
));

const Organization: ComponentType<any> = withEditPage(OrganizationForm, {
id: 'organizationId',
onInitialize: (id) => (
OrganizationsService
.fetchOne(id)
Expand Down
5 changes: 3 additions & 2 deletions client/src/pages/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { AssociatedDropdown, FileInputButton, LazyImage } from '@performant-soft
import React, { type ComponentType, useEffect } from 'react';
import { withTranslation } from 'react-i18next';
import uuid from 'react-uuid';
import { Button, Form, Message } from 'semantic-ui-react';
import { Button, Form } from 'semantic-ui-react';
import _ from 'underscore';
import Organization from '../transforms/Organization';
import OrganizationsService from '../services/Organizations';
import ProjectsService from '../services/Projects';
import SimpleEditPage from '../components/SimpleEditPage';
import withEditPage from '../hooks/EditPage';
import AuthenticationService from '../services/Authentication';
import _ from 'underscore';

const ProjectForm = withTranslation()((props) => {
/**
Expand Down Expand Up @@ -113,6 +113,7 @@ const ProjectForm = withTranslation()((props) => {
});

const Project: ComponentType<any> = withEditPage(ProjectForm, {
id: 'projectId',
onInitialize: (
(id) => ProjectsService
.fetchOne(id)
Expand Down
84 changes: 84 additions & 0 deletions client/src/pages/Resource.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// @flow

import { FileInputButton, LazyImage } from '@performant-software/semantic-components';
import React, { type ComponentType, useEffect } from 'react';
import { withTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { Button, Form } from 'semantic-ui-react';
import _ from 'underscore';
import ResourcesService from '../services/Resources';
import withEditPage from '../hooks/EditPage';
import SimpleEditPage from '../components/SimpleEditPage';

const ProjectForm = withTranslation()((props) => {
const { projectId } = useParams();

useEffect(() => {
props.onSetState({ project_id: projectId });
}, [projectId]);

return (
<SimpleEditPage
{...props}
>
<SimpleEditPage.Tab
key='details'
name={props.t('Common.tabs.details')}
>
<Form.Input
label={props.t('Resource.labels.content')}
>
<LazyImage
preview={props.item.content_url}
src={props.item.content_url}
size='medium'
>
{ !props.item.content_url && (
<FileInputButton
color='green'
content={props.t('Common.buttons.upload')}
icon='cloud upload'
onSelection={(files) => {
const file = _.first(files);
props.onSetState({ content: file, content_url: URL.createObjectURL(file) });
}}
/>
)}
{ props.item.content_url && (
<Button
color='red'
content={props.t('Common.buttons.remove')}
icon='times'
onClick={() => props.onSetState({ content: null, content_url: null, content_remove: true })}
/>
)}
</LazyImage>
</Form.Input>
<Form.Input
error={props.isError('name')}
label={props.t('Project.labels.name')}
onChange={props.onTextInputChange.bind(this, 'name')}
required={props.isRequired('name')}
value={props.item.name}
/>
</SimpleEditPage.Tab>
</SimpleEditPage>
);
});

const Resource: ComponentType<any> = withEditPage(ProjectForm, {
id: 'resourceId',
onInitialize: (
(id) => ResourcesService
.fetchOne(id)
.then(({ data }) => data.resource)
),
onSave: (
(resource) => ResourcesService
.save(resource)
.then(({ data }) => data.resource)
),
required: ['name']
});

export default Resource;
34 changes: 34 additions & 0 deletions client/src/pages/Resources.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// @flow

import { ItemList, LazyImage } from '@performant-software/semantic-components';
import React, { type ComponentType } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import ResourcesService from '../services/Resources';

const Resources: ComponentType<any> = () => {
const navigate = useNavigate();
const { projectId } = useParams();

return (
<ItemList
actions={[{
name: 'edit',
onClick: (item) => navigate(item.id.toString())
}, {
name: 'delete'
}]}
addButton={{
location: 'top',
onClick: () => navigate('new')
}}
collectionName='resources'
onLoad={(params) => ResourcesService.fetchAll({ ...params, project_id: projectId })}
onDelete={(resource) => ResourcesService.delete(resource)}
renderHeader={(resource) => resource.name}
renderImage={(resource) => <LazyImage src={resource.content_url} />}
renderMeta={() => ''}
/>
);
};

export default Resources;
Loading

0 comments on commit b0d3ac5

Please sign in to comment.