Skip to content

Commit

Permalink
Client - Switch between grid and list of cards (#128)
Browse files Browse the repository at this point in the history
  • Loading branch information
luorlandini authored Apr 16, 2021
1 parent a9bca24 commit 752f8d3
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 63 deletions.
51 changes: 41 additions & 10 deletions geonode_mapstore_client/client/js/components/home/CardGrid.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import HTML from '@mapstore/framework/components/I18N/HTML';
import FaIcon from '@js/components/home/FaIcon';
import ResourceCard from '@js/components/home/ResourceCard';
import { withResizeDetector } from 'react-resize-detector';
import useLocalStorage from '@js/hooks/useLocalStorage';

const Cards = withResizeDetector(({
resources,
Expand All @@ -29,8 +30,46 @@ const Cards = withResizeDetector(({
const cardWidth = width >= size + margin * 2
? Math.floor((width - margin * count) / count)
: '100%';

const ulPadding = Math.floor(margin / 2);
const isSingleCard = count === 0 || count === 1;
const [cardLayoutStyle] = useLocalStorage('layoutCardsStyle');

const gridLayoutSpace = (idx) => {

const gridSpace = isSingleCard
? {
width: width - margin,
margin: ulPadding
}
: {
width: cardWidth,
marginRight: (idx + 1) % count === 0 ? 0 : margin,
marginTop: margin
};

return gridSpace;
};

const listLayoutSpace = {
width: '100%',
margin: ulPadding / 2
};


const layoutSpace = (cardLayoutStyle, idx) => {
let cardContainerSpace;
switch (cardLayoutStyle) {
case 'list':
cardContainerSpace = listLayoutSpace;
break;
default:
cardContainerSpace = gridLayoutSpace(idx);
}
return cardContainerSpace;
};


return (
<ul
style={isSingleCard
Expand All @@ -46,22 +85,14 @@ const Cards = withResizeDetector(({
return (
<li
key={resource.pk}
style={isSingleCard
? {
width: width - margin,
margin: ulPadding
}
: {
width: cardWidth,
marginRight: (idx + 1) % count === 0 ? 0 : margin,
marginTop: margin
}}
style={(layoutSpace(cardLayoutStyle, idx))}
>
<ResourceCard
active={isCardActive(resource)}
data={resource}
formatHref={formatHref}
links={links}
layoutCardsStyle={cardLayoutStyle}
/>
</li>
);
Expand Down
13 changes: 11 additions & 2 deletions geonode_mapstore_client/client/js/components/home/FiltersMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
*/

import React, { forwardRef } from 'react';
import { Dropdown } from 'react-bootstrap-v1';
import { Dropdown, Button } from 'react-bootstrap-v1';
import ReactResizeDetector from 'react-resize-detector';
import Message from '@mapstore/framework/components/I18N/Message';
import FaIcon from '@js/components/home/FaIcon';
import useLocalStorage from '@js/hooks/useLocalStorage';

const FiltersMenu = forwardRef(({
formatHref,
Expand All @@ -18,11 +20,12 @@ const FiltersMenu = forwardRef(({
filters,
style,
onClick,
layoutSwitcher,
defaultLabelId
}, ref) => {

const selectedSort = orderOptions.find(({ value }) => order === value);

const [cardLayoutStyle] = useLocalStorage('layoutCardsStyle');
return (
<div
className="gn-filters-menu"
Expand All @@ -40,9 +43,15 @@ const FiltersMenu = forwardRef(({
</div>
)}
</ReactResizeDetector>
<Button variant="default" onClick={layoutSwitcher} >
<FaIcon name={cardLayoutStyle === 'grid' ? 'th' : cardLayoutStyle } />
</Button>

<div
className="gn-filters-menu-tools"

>

{orderOptions.length > 0 && <Dropdown alignRight>
<Dropdown.Toggle
id="sort-dropdown"
Expand Down
108 changes: 57 additions & 51 deletions geonode_mapstore_client/client/js/components/home/ResourceCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { Card, Dropdown } from 'react-bootstrap-v1';
import Message from '@mapstore/framework/components/I18N/Message';
import FaIcon from '@js/components/home/FaIcon';
import Tag from '@js/components/home/Tag';


import {
getUserName,
getResourceTypesInfo
Expand All @@ -21,30 +23,33 @@ const ResourceCard = forwardRef(({
active,
links,
formatHref,
getTypesInfo
getTypesInfo,
layoutCardsStyle
}, ref) => {

const res = data;
const types = getTypesInfo();
const { icon } = types[res.doc_type] || types[res.resource_type] || {};

return (
<Card
ref={ref}
className={`gn-resource-card${active ? ' active' : ''}`}
className={`gn-resource-card${active ? ' active' : ''} ${layoutCardsStyle === 'list' ? 'rounded-0' : ''}`}
>
<a
className="gn-resource-card-link"
href={formatHref({
pathname: `/detail/${res.resource_type}/${res.pk}`
})}
/>
<Card.Img
variant="top"
src={res.thumbnail_url}
/>
<Card.Body>
<Card.Title>
{icon &&
<div className={`card-resource-${layoutCardsStyle}`}>
<Card.Img
variant={`${(layoutCardsStyle === 'list') ? 'left' : 'top'}`}
src={res.thumbnail_url}
/>
<Card.Body>
<Card.Title>
{icon &&
<>
<Tag
href={formatHref({
Expand All @@ -55,49 +60,50 @@ const ResourceCard = forwardRef(({
<FaIcon name={icon} />
</Tag>
</>}
<a href={res.detail_url}>
{res.title}
</a>
</Card.Title>
<Card.Text
className="gn-card-description"
>
{res.raw_abstract ? res.raw_abstract : '...'}
</Card.Text>
<Card.Text
lassName="gn-card-user"
>
<Message msgId="gnhome.author"/>: <a href={formatHref({
query: {
'filter{owner.username.in}': res.owner.username
}
})}>{getUserName(res.owner)}</a>
</Card.Text>
{links && links.length > 0 && <Dropdown
className="gn-card-options"
alignRight
>
<Dropdown.Toggle
id={`gn-card-options-${res.pk}`}
variant="default"
size="sm"
<a href={res.detail_url}>
{res.title}
</a>
</Card.Title>
<Card.Text
className="gn-card-description"
>
{res.raw_abstract ? res.raw_abstract : '...'}
</Card.Text>
<Card.Text
lassName="gn-card-user"
>
<Message msgId="gnhome.author"/>: <a href={formatHref({
query: {
'filter{owner.username.in}': res.owner.username
}
})}>{getUserName(res.owner)}</a>
</Card.Text>
{links && links.length > 0 && <Dropdown
className="gn-card-options"
alignRight
>
<FaIcon name="ellipsis-v" />
</Dropdown.Toggle>
<Dropdown.Menu>
{links.map(({ label, href }) => {
return (
<Dropdown.Item
key={href}
href={href}
>
{label}
</Dropdown.Item>
);
})}
</Dropdown.Menu>
</Dropdown>}
</Card.Body>
<Dropdown.Toggle
id={`gn-card-options-${res.pk}`}
variant="default"
size="sm"
>
<FaIcon name="ellipsis-v" />
</Dropdown.Toggle>
<Dropdown.Menu>
{links.map(({ label, href }) => {
return (
<Dropdown.Item
key={href}
href={href}
>
{label}
</Dropdown.Item>
);
})}
</Dropdown.Menu>
</Dropdown>}
</Card.Body>
</div>
</Card>
);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@

/*
* Copyright 2021, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import ReactDOM from 'react-dom';
import expect from 'expect';
import useLocalStorage from '@js/hooks/useLocalStorage';

function MockApp({ key, value }) {

const [inTest, setInTest] = useLocalStorage(key);
setInTest(value);
return (
<div className="MockApp">
<p id="lsValue" >{inTest}</p>
</div>
);

}
export default MockApp;


describe('Test useLocalStorage', () => {
beforeEach((done) => {
document.body.innerHTML = '<div id="container"></div>';
setTimeout(done);
});
afterEach((done) => {
ReactDOM.unmountComponentAtNode(document.getElementById("container"));
document.body.innerHTML = '';
setTimeout(done);
});

it('Test componet is rendered', () => {
ReactDOM.render(<MockApp key="test_key" value="test_value" />
, document.getElementById("container"));
const container = document.getElementById('container');
const el = container.querySelector('.MockApp');
expect(el).toExist();
});

it('Test component localStorage props', () => {
ReactDOM.render(<MockApp key="test_key" value="test_value" />, document.getElementById("container"));
const el = document.getElementsByClassName('MockApp');
expect(el).toExist();
const element = el[0].childNodes[0];
expect(element).toExist();
expect(element.innerHTML).toBe('test_value');
});


});
52 changes: 52 additions & 0 deletions geonode_mapstore_client/client/js/hooks/useLocalStorage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useEffect, useState } from 'react';

export default (key, initialValue) => {

const getValue = () => {
if (typeof window === 'undefined') {
return initialValue;
}

try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// Todo log error in persistent solution
console.log(`Error to get item key “${key}”:`, error);
return initialValue;
}
};

const [storedValue, setStoredValue] = useState(getValue);

const setValue = (value) => {

try {
const newValue = value instanceof Function ? value(storedValue) : value;
setStoredValue(newValue);
window.localStorage.setItem(key, JSON.stringify(newValue));
window.dispatchEvent(new Event('localStorage'));
} catch (error) {
// Todo log error in persistent solution
console.log(`Error “${key}”:`, error);
}
};

useEffect(() => {
setStoredValue(getValue());
}, []);

useEffect(() => {
const handleStoreChange = () => {
setStoredValue(getValue());
};
window.addEventListener('localStorage', handleStoreChange);

return () => {
window.removeEventListener('localStorage', handleStoreChange);
};
}, []);


return [storedValue, setValue];
};
Loading

0 comments on commit 752f8d3

Please sign in to comment.