Skip to content

Commit da195e2

Browse files
MV88allyoucanmap
authored andcommitted
Geostory - Normal editor - Title (C039_geostory) (#4102)
1 parent 6fb67a5 commit da195e2

27 files changed

Lines changed: 642 additions & 49 deletions

web/client/components/geostory/builder/SectionsPreview.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const sectionToItem = ({
6262
{
6363
glyph: 'zoom-to',
6464
visible: contents.length === 1,
65-
tooltip: 'Zoom to content'
65+
tooltipId: "geostory.zoomToContent"
6666
}
6767
]} />,
6868
title: title,
@@ -85,7 +85,8 @@ const sectionToItem = ({
8585
}}
8686
buttons={[
8787
{
88-
glyph: 'zoom-to'
88+
glyph: 'zoom-to',
89+
tooltipId: "geostory.zoomToContent"
8990
}
9091
]} />,
9192
title: capitalize(content.type),

web/client/components/geostory/common/AddBar.jsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
/*
2+
* Copyright 2019, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
19
import React from 'react';
210
import PropTypes from 'prop-types';
311
import { Glyphicon, Button } from 'react-bootstrap';
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
2+
/*
3+
* Copyright 2019, GeoSolutions Sas.
4+
* All rights reserved.
5+
*
6+
* This source code is licensed under the BSD-style license found in the
7+
* LICENSE file in the root directory of this source tree.
8+
*/
9+
10+
import React from "react";
11+
import { DropdownButton as DropdownButtonRB, Glyphicon, MenuItem } from 'react-bootstrap';
12+
import tooltip from '../../misc/enhancers/tooltip';
13+
import find from 'lodash/find';
14+
15+
const DropdownButton = tooltip(DropdownButtonRB);
16+
17+
/**
18+
* Dropdown button for Toolbar component
19+
*/
20+
export default function ToolbarDropdownButton({
21+
value,
22+
options = [],
23+
onSelect = () => {},
24+
glyph = '',
25+
tooltipId,
26+
className = 'square-button-md no-border',
27+
disabled
28+
}) {
29+
const glyphOption = (find(options, (option) => option.value === value) || { }).glyph;
30+
return (
31+
<DropdownButton
32+
noCaret
33+
tooltipId={tooltipId}
34+
className={className}
35+
disabled={disabled}
36+
title={<Glyphicon glyph={glyphOption || glyph}/>}>
37+
{options.map((option = {}) => (
38+
<MenuItem
39+
key={option.value}
40+
active={value && value === option.value}
41+
onClick={() => onSelect(option.value)}>
42+
{option.label}
43+
</MenuItem>
44+
))}
45+
</DropdownButton>
46+
);
47+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2019, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
import React from 'react';
9+
import ReactDOM from 'react-dom';
10+
11+
import expect from 'expect';
12+
import ToolbarDropdownButton from '../ToolbarDropdownButton';
13+
describe('ToolbarDropdownButton component', () => {
14+
beforeEach((done) => {
15+
document.body.innerHTML = '<div id="container"></div>';
16+
setTimeout(done);
17+
});
18+
afterEach((done) => {
19+
ReactDOM.unmountComponentAtNode(document.getElementById("container"));
20+
document.body.innerHTML = '';
21+
setTimeout(done);
22+
});
23+
it('ToolbarDropdownButton rendering with defaults', () => {
24+
ReactDOM.render(<ToolbarDropdownButton />, document.getElementById("container"));
25+
const container = document.getElementById('container');
26+
const el = container.querySelector('.square-button-md.no-border');
27+
expect(el).toExist();
28+
});
29+
});

web/client/components/geostory/contents/Column.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ export default ({ id, contents = [], mode, add = () => {}, update= () => {} }) =
3636
tooltipId: 'geostory.addTextContent',
3737
template: ContentTypes.TEXT
3838
}, {
39-
glyph: 'picture',
40-
tooltipId: 'geostory.addTextContent',
41-
template: ContentTypes.MEDIA
39+
glyph: 'picture',
40+
tooltipId: 'geostory.addMediaContent',
41+
template: ContentTypes.MEDIA
4242
}]}
4343
/>
4444
);
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
2+
/*
3+
* Copyright 2019, GeoSolutions Sas.
4+
* All rights reserved.
5+
*
6+
* This source code is licensed under the BSD-style license found in the
7+
* LICENSE file in the root directory of this source tree.
8+
*/
9+
10+
import React from "react";
11+
import Toolbar from '../../misc/toolbar/Toolbar';
12+
import ToolbarDropdownButton from '../common/ToolbarDropdownButton';
13+
import Message from '../../I18N/Message';
14+
15+
const toolButtons = {
16+
size: ({ size, update }) => ({
17+
Element: () => <ToolbarDropdownButton
18+
value={size}
19+
glyph="resize-horizontal"
20+
tooltipId="geostory.contentToolbar.contentSize"
21+
options={[{
22+
value: 'small',
23+
label: <Message msgId="geostory.contentToolbar.smallSizeLabel"/>
24+
}, {
25+
value: 'medium',
26+
label: <Message msgId="geostory.contentToolbar.mediumSizeLabel"/>
27+
}, {
28+
value: 'large',
29+
label: <Message msgId="geostory.contentToolbar.largeSizeLabel"/>
30+
}, {
31+
value: 'full',
32+
label: <Message msgId="geostory.contentToolbar.fullSizeLabel"/>
33+
}]}
34+
onSelect={(selected) => update('size', selected)}/>
35+
}),
36+
align: ({ size, align, update }) => ({
37+
Element: () => <ToolbarDropdownButton
38+
value={align}
39+
disabled={size === 'full'}
40+
glyph="align-center"
41+
tooltipId="geostory.contentToolbar.contentAlign"
42+
options={[{
43+
value: 'left',
44+
label: <Message msgId="geostory.contentToolbar.leftAlignLabel"/>,
45+
glyph: 'align-left'
46+
}, {
47+
value: 'center',
48+
label: <Message msgId="geostory.contentToolbar.centerAlignLabel"/>,
49+
glyph: 'align-center'
50+
}, {
51+
value: 'right',
52+
label: <Message msgId="geostory.contentToolbar.rightAlignLabel"/>,
53+
glyph: 'align-right'
54+
}]}
55+
onSelect={(selected) => update('align', selected)}/>
56+
}),
57+
theme: ({ theme, update }) => ({
58+
Element: () => <ToolbarDropdownButton
59+
value={theme}
60+
glyph="dropper"
61+
tooltipId="geostory.contentToolbar.contentTheme"
62+
options={[{
63+
value: 'bright',
64+
label: <Message msgId="geostory.contentToolbar.brightThemeLabel"/>
65+
}, {
66+
value: 'bright-text',
67+
label: <Message msgId="geostory.contentToolbar.brightTextThemeLabel"/>
68+
}, {
69+
value: 'dark',
70+
label: <Message msgId="geostory.contentToolbar.darkThemeLabel"/>
71+
}, {
72+
value: 'dark-text',
73+
label: <Message msgId="geostory.contentToolbar.darkTextThemeLabel"/>
74+
}]}
75+
onSelect={(selected) => update('theme', selected)}/>
76+
})
77+
};
78+
/**
79+
* Toolbar to update properties of content,
80+
* @prop {array} tools list of tool's names to display in the edit toolbar, available tools `size`, `align` and `theme`
81+
* @prop {string} size one of `small`, `medium`, `large` and `full`
82+
* @prop {string} align one of `left`, `center` and `right`
83+
* @prop {string} theme one of `bright`, `bright-text`, `dark` and `dark-text`
84+
* @prop {function} update handler for select properties events, parameters (key, value)
85+
* @example
86+
*/
87+
export default function ContentToolbar({
88+
tools = [],
89+
...props
90+
}) {
91+
return (
92+
<div className="ms-content-toolbar">
93+
<Toolbar
94+
btnDefaultProps={{
95+
className: 'square-button-md no-border'
96+
}}
97+
buttons={tools
98+
.filter((id) => toolButtons[id])
99+
.map(id => toolButtons[id](props))}/>
100+
</div>
101+
);
102+
}

web/client/components/geostory/contents/ContentWrapper.jsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88
import React from "react";
9+
import ContentToolbar from './ContentToolbar';
10+
import { Modes } from "../../../utils/GeoStoryUtils";
911

1012
/**
1113
* Return a class name from props of a content
1214
* @prop {string} theme one of 'bright', 'dark', 'dark-transparent' or 'bright-transparent'
1315
* @prop {string} align one of 'center', 'left' or 'right'
1416
* @prop {string} size one of 'full', 'large', 'medium' or 'small'
1517
*/
16-
const getClassNameFromProps = ({ theme = 'bright', align = 'center', size = 'full' }) => {
18+
const getClassNameFromProps = ({ theme = 'bright', align = 'center', size = 'large' }) => {
1719
const themeClassName = ` ms-${theme}`;
1820
const alignClassName = ` ms-align-${align}`;
1921
const sizeClassName = ` ms-size-${size}`;
@@ -25,12 +27,15 @@ const getClassNameFromProps = ({ theme = 'bright', align = 'center', size = 'ful
2527
* - center the content accordingly.
2628
* - Add inViewRef property, to apply IntersectionObserver
2729
*/
28-
export default ({ inViewRef, children, type, contentWrapperStyle, ...props }) =>
30+
export default ({ id, inViewRef, children, type, contentWrapperStyle, mode, ...props }) =>
2931
(<div
3032
ref={inViewRef}
3133
style={contentWrapperStyle}
3234
className={`ms-content ms-content-${type}${getClassNameFromProps(props)}`}>
3335
<div className="ms-content-body">
36+
{mode === Modes.EDIT
37+
&& <ContentToolbar
38+
{...props}/>}
3439
{children}
3540
</div>
3641
</div>);

web/client/components/geostory/contents/Contents.jsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,29 @@ import AddBar from "../common/AddBar";
1616
* Renders the contents and associate the handlers modifying the handlers accordingly. Adds also the add buttons after each content.
1717
* @prop {string} className
1818
* @prop {object} contentProps
19-
* @prop {ojbect[]} addButtons buttons for the popup toolbar. If empty or not present, the add button will not show.
19+
* @prop {object[]} addButtons buttons for the popup toolbar. If empty or not present, the add button will not show.
2020
* The object handles the click event (TODO: allow customization) and triggers add handler with the correct path and position.
2121
* You can configure the type with `template` property of this object as 3rd argument of `add` handler
2222
* @prop {string} mode
2323
* @prop {component} ContentComponent component to use as content.
2424
* @prop {function} add handler for add events. parameters are (path, position, element)
2525
* @prop {function} update handler for update events.parameters are (path, value, mode)
26+
* @prop {object} tools list of tool's names to display in the edit toolbar
27+
* @example
28+
* ```
29+
* // tools configuration
30+
* // this example renders edit toolbar only for contents of type `text`
31+
* // with 3 buttons related to size, align and theme properties.
32+
* const tools = {
33+
* text: ['size', 'align', 'theme'] // see ContentToolbar component for available properties
34+
* };
35+
* ```
2636
*/
2737
export default ({
2838
className,
2939
contentProps = {},
3040
addButtons = [],
41+
tools = [],
3142
contents=[],
3243
ContentComponent=Content,
3344
mode,
@@ -44,7 +55,8 @@ export default ({
4455
add={(path, ...args) => add(`contents[{"id": "${id}"}].` + path, ...args)}
4556
update={(path, ...args) => update(`contents[{"id": "${id}"}].` + path, ...args)}
4657
{...contentProps}
47-
{...props} />)];
58+
{...props}
59+
tools={tools && tools[props.type]} />)];
4860
if (mode === Modes.EDIT && addButtons.length > 0) {
4961
content.push(
5062
<AddBar
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2019, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
import React from 'react';
10+
import ReactDOM from 'react-dom';
11+
import expect from 'expect';
12+
import ReactTestUtils from 'react-dom/test-utils';
13+
import {includes, castArray} from 'lodash';
14+
15+
import ContentToolbar from '../ContentToolbar';
16+
17+
describe('ContentToolbar component', () => {
18+
beforeEach((done) => {
19+
document.body.innerHTML = '<div id="container"></div>';
20+
setTimeout(done);
21+
});
22+
afterEach((done) => {
23+
ReactDOM.unmountComponentAtNode(document.getElementById("container"));
24+
document.body.innerHTML = '';
25+
setTimeout(done);
26+
});
27+
it('ContentToolbar rendering with defaults', () => {
28+
ReactDOM.render(<ContentToolbar />, document.getElementById("container"));
29+
const container = document.getElementById('container');
30+
const el = container.querySelector('.ms-content-toolbar');
31+
expect(el).toExist();
32+
});
33+
it('ContentToolbar rendering all supported items', () => {
34+
ReactDOM.render(<ContentToolbar tools={["align", "size", "theme"]}/>, document.getElementById("container"));
35+
const buttons = document.getElementsByTagName('button');
36+
expect(buttons).toExist();
37+
expect(buttons.length).toEqual(3);
38+
});
39+
const testItems = [{
40+
name: "align",
41+
length: 3,
42+
totButtons: 1,
43+
aTag: ["left", "center", "right"]
44+
},
45+
{
46+
name: "size",
47+
length: 4,
48+
totButtons: 1,
49+
aTag: ["small", "medium", "large", "full"]
50+
},
51+
{
52+
name: "theme",
53+
length: 4,
54+
totButtons: 1,
55+
aTag: ["bright", "bright-text", "dark", "dark-text"]
56+
}];
57+
testItems.forEach(tool => {
58+
it(`ContentToolbar rendering ${tool.name} item and click event`, () => {
59+
ReactDOM.render(<ContentToolbar
60+
tools={[tool.name]}
61+
update={(t, selected) => {
62+
expect(t).toEqual(tool.name);
63+
expect(includes(tool.aTag, selected)).toEqual(true);
64+
}}
65+
/>, document.getElementById("container"));
66+
const buttons = document.getElementsByTagName('button');
67+
expect(buttons).toExist();
68+
expect(buttons.length).toEqual(tool.totButtons);
69+
70+
const list = document.getElementsByTagName('li');
71+
expect(list).toExist();
72+
expect(list.length).toEqual(tool.length, `the ${tool.name} had wrong number of li tags, expect ${list.length} toEqual ${tool.length}`);
73+
74+
const aTags = document.getElementsByTagName('a');
75+
expect(aTags).toExist();
76+
castArray(aTags).forEach((a, i) => {
77+
ReactTestUtils.Simulate.click(aTags[i]);
78+
});
79+
});
80+
});
81+
});
82+
83+

0 commit comments

Comments
 (0)