Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion web/client/actions/__tests__/geostory-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ import {
enableDraw,
ENABLE_DRAW,
RESET_GEOSTORY,
resetGeostory
resetGeostory,
APPLY_TO_MAPS,
applyToMaps,
DUPLICATE_ITEM,
duplicateItem
} from '../geostory';

describe('test geostory action creators', () => {
Expand Down Expand Up @@ -295,4 +299,17 @@ describe('test geostory action creators', () => {
const action = resetGeostory();
expect(action.type).toBe(RESET_GEOSTORY);
});
it('applyToMaps', () => {
const action = applyToMaps('center', {x: 1, y: 2, crs: 'EPSG:4326'}, 'sections[{"id":"s1"}].contents[{"id":"c1"}]');
expect(action.type).toBe(APPLY_TO_MAPS);
expect(action.property).toBe('center');
expect(action.value).toEqual({x: 1, y: 2, crs: 'EPSG:4326'});
expect(action.currentContentPath).toBe('sections[{"id":"s1"}].contents[{"id":"c1"}]');
});
it('duplicateItem', () => {
const action = duplicateItem('sections', 'section-1');
expect(action.type).toBe(DUPLICATE_ITEM);
expect(action.containerPath).toBe('sections');
expect(action.itemId).toBe('section-1');
});
});
27 changes: 27 additions & 0 deletions web/client/actions/geostory.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export const ENABLE_DRAW = "GEOSTORY:ENABLE_DRAW";
export const GEOSTORY_EXPORT = "GEOSTORY:EXPORT";
export const GEOSTORY_IMPORT = "GEOSTORY:IMPORT";
export const RESET_GEOSTORY = "GEOSTORY:RESET";
export const APPLY_TO_MAPS = "GEOSTORY:APPLY_TO_MAPS";
export const DUPLICATE_ITEM = "GEOSTORY:DUPLICATE_ITEM";

/**
* Adds an entry to current story. The entry can be a section, a content or anything to append in an array (even sub-content)
Expand Down Expand Up @@ -276,3 +278,28 @@ export const geostoryImport = (file) => ({type: GEOSTORY_IMPORT, file});
* reset geostory on page unmount
*/
export const resetGeostory = () => ({ type: RESET_GEOSTORY });

/**
* Apply a map property (center or zoom) to all other map contents in the story.
* @param {string} property the map property to apply ('center' or 'zoom')
* @param {any} value the value to set
* @param {string} currentContentPath path of the current focused content (to skip)
*/
export const applyToMaps = (property, value, currentContentPath) => ({
type: APPLY_TO_MAPS,
property,
value,
currentContentPath
});

/**
* Duplicates an item (section or content) in the geostory.
* The duplicate is placed after the original.
* @param {string} containerPath predicate-based path to the container array
* @param {string} itemId the id of the item to duplicate
*/
export const duplicateItem = (containerPath, itemId) => ({
type: DUPLICATE_ITEM,
containerPath,
itemId
});
3 changes: 3 additions & 0 deletions web/client/components/geostory/builder/Builder.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class Builder extends React.Component {
onSelect: PropTypes.func,
onRemove: PropTypes.func,
onUpdate: PropTypes.func,
onDuplicate: PropTypes.func,
selected: PropTypes.string,
storyFonts: PropTypes.array
};
Expand Down Expand Up @@ -87,6 +88,7 @@ class Builder extends React.Component {
onSort,
onUpdate,
onSelect,
onDuplicate,
storyFonts
} = this.props;
const SettingsButton = isSettingsChanged ? WithConfirmButton : ToolbarButton;
Expand Down Expand Up @@ -179,6 +181,7 @@ class Builder extends React.Component {
isCollapsed={isCollapsed}
sections={story && story.sections}
onSort={onSort}
onDuplicate={onDuplicate}
/> : !isSettingsEnabled ? <div className="ms-story-empty-content-parent">
<div className="ms-story-empty-content-child">
<Message msgId="geostory.builder.noContents" />
Expand Down
55 changes: 47 additions & 8 deletions web/client/components/geostory/builder/SectionsPreview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const ConnectedIcon = compose(

const previewContents = {
title: () => null,
paragraph: ({ id, contents, isCollapsed, scrollTo, onSort, sectionId, onUpdate }) => (
paragraph: ({ id, contents, isCollapsed, scrollTo, onSort, sectionId, onUpdate, onDuplicate }) => (
<div style={{ position: 'relative' }}>
<DraggableSideGrid
containerId={id}
Expand All @@ -95,6 +95,7 @@ const previewContents = {
? `${content.type}${content.align || 'center'}`
: content.type;
const PreviewContents = previewContents[content.type];
const containerPath = `sections[{"id":"${sectionId}"}].contents[{"id":"${contents[0].id}"}].contents`;
return {
id: content.id,
preview: <ConnectedIcon type={contentType} resourceId={content.resourceId}/>,
Expand All @@ -103,6 +104,14 @@ const previewContents = {
className: 'square-button no-border'
}}
buttons={[
{
glyph: 'duplicate',
tooltipId: "geostory.duplicateSection",
onClick: (evt) => {
evt.stopPropagation();
if (onDuplicate) onDuplicate(containerPath, content.id);
}
},
{
glyph: 'zoom-to',
tooltipId: "geostory.zoomToContent",
Expand All @@ -119,12 +128,13 @@ const previewContents = {
onSort={onSort}
scrollTo={scrollTo}
isCollapsed={isCollapsed}
onDuplicate={onDuplicate}
contents={content.contents}/>
};
})} />
</div>
),
column: ({ sectionId, id, contents, isCollapsed, scrollTo, onSort, onUpdate, sectionType }) => (
column: ({ sectionId, id, contents, isCollapsed, scrollTo, onSort, onUpdate, sectionType, onDuplicate }) => (
<div style={{ position: 'relative' }}>
<DraggableSideGrid
containerId={id}
Expand All @@ -144,6 +154,7 @@ const previewContents = {
? `${content.type}${content.align || 'center'}`
: content.type;
const PreviewContents = previewContents[content.type];
const containerPath = `sections[{"id":"${sectionId}"}].contents[{"id":"${id}"}].contents`;
return {
id: content.id,
preview: <ConnectedIcon type={contentType} resourceId={content.resourceId}/>,
Expand All @@ -152,6 +163,14 @@ const previewContents = {
className: 'square-button no-border'
}}
buttons={[
{
glyph: 'duplicate',
tooltipId: "geostory.duplicateSection",
onClick: (evt) => {
evt.stopPropagation();
if (onDuplicate) onDuplicate(containerPath, content.id);
}
},
{
glyph: 'zoom-to',
visible: sectionType !== SectionTypes.CAROUSEL,
Expand All @@ -164,12 +183,12 @@ const previewContents = {
onUpdate={(text) => onUpdate(`sections[{"id": "${sectionId}"}].contents[{"id": "${id}"}].contents[{"id": "${content.id}"}]`, {title: text}, "merge")}/>,
description: `type: ${content.type}`,
body: !isCollapsed && PreviewContents
&& <PreviewContents id={id} onSort={onSort}/>
&& <PreviewContents id={id} onSort={onSort} onDuplicate={onDuplicate}/>
};
})} />
</div>
),
immersive: ({ id, contents, isCollapsed, scrollTo, onUpdate, onSort, currentPage }) => (
immersive: ({ id, contents, isCollapsed, scrollTo, onUpdate, onSort, currentPage, onDuplicate }) => (
<div style={{ position: 'relative' }}>
<DraggableSideGrid
containerId={id}
Expand All @@ -189,6 +208,7 @@ const previewContents = {
? `${content.type}${content.align || 'center'}`
: content.type;
const PreviewContents = previewContents[content.type];
const containerPath = `sections[{"id":"${id}"}].contents`;
return {
className: currentPage && currentPage.columns && currentPage.columns[id] && currentPage.columns[id] === content.id && currentPage.sectionId === id
? 'ms-highlight'
Expand All @@ -200,6 +220,14 @@ const previewContents = {
className: 'square-button no-border'
}}
buttons={[
{
glyph: 'duplicate',
tooltipId: "geostory.duplicateSection",
onClick: (evt) => {
evt.stopPropagation();
if (onDuplicate) onDuplicate(containerPath, content.id);
}
},
{
glyph: 'zoom-to',
tooltipId: "geostory.zoomToContent",
Expand All @@ -218,12 +246,13 @@ const previewContents = {
onUpdate={onUpdate}
scrollTo={scrollTo}
isCollapsed={isCollapsed}
onDuplicate={onDuplicate}
contents={content.contents}/>
};
})} />
</div>
),
carousel: ({ id, contents, isCollapsed, scrollTo, onUpdate, onSort, currentPage }) => (
carousel: ({ id, contents, isCollapsed, scrollTo, onUpdate, onSort, currentPage, onDuplicate }) => (
<div style={{ position: 'relative' }}>
<DraggableSideGrid
containerId={id}
Expand Down Expand Up @@ -251,7 +280,6 @@ const previewContents = {
preview: <Icon type={contentType} thumbnail={content?.thumbnail?.image} />,
tools: null,
title: <TitleEditable
// render again when it gets a new title from the state
key={content.title}
title={content.title || capitalize(content.type)}
onUpdate={(text) => onUpdate(`sections[{"id": "${id}"}].contents[{"id":"${content.id}"}]`, {title: text}, "merge")}/>,
Expand All @@ -264,6 +292,7 @@ const previewContents = {
onUpdate={onUpdate}
scrollTo={scrollTo}
isCollapsed={isCollapsed}
onDuplicate={onDuplicate}
sectionType={SectionTypes.CAROUSEL}
contents={content.contents}/>
};
Expand All @@ -283,6 +312,7 @@ const sectionToItem = ({
onSort,
onSelect,
onUpdate,
onDuplicate = () => {},
selected = "title_section_id1"
}) => ({
contents,
Expand All @@ -304,6 +334,14 @@ const sectionToItem = ({
className: 'square-button no-border'
}}
buttons={[
{
glyph: 'duplicate',
tooltipId: "geostory.duplicateSection",
onClick: (evt) => {
evt.stopPropagation();
onDuplicate('sections', id);
}
},
{
onClick: scrollToHandler(id, scrollTo),
glyph: 'zoom-to',
Expand All @@ -324,6 +362,7 @@ const sectionToItem = ({
selected={selected}
scrollTo={scrollTo}
isCollapsed={isCollapsed}
onDuplicate={onDuplicate}
contents={contents}/>
: null
};
Expand All @@ -335,7 +374,7 @@ const sectionToItem = ({
* @SectionsPreview
* @param {object[]} [sections=[]] Array of sections to display
*/
export default ({ sections = [], scrollTo, onSelect = () => {}, isCollapsed, currentPage, selected, onSort, onUpdate }) => (<DraggableSideGrid
export default ({ sections = [], scrollTo, onSelect = () => {}, isCollapsed, currentPage, selected, onSort, onUpdate, onDuplicate }) => (<DraggableSideGrid
isDraggable
onSort={(sortIdTo, sortIdFrom, itemDataTo, itemDataFrom) => {
if (itemDataTo.containerId === 'story-sections'
Expand All @@ -351,5 +390,5 @@ export default ({ sections = [], scrollTo, onSelect = () => {}, isCollapsed, cur
cardComponent={DraggableSideCard}
size="sm"
items={
sections.map(sectionToItem({ currentPage, onSelect, isCollapsed, scrollTo, selected, onUpdate, onSort }))
sections.map(sectionToItem({ currentPage, onSelect, isCollapsed, scrollTo, selected, onUpdate, onSort, onDuplicate }))
} />);
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import React from 'react';
import ReactDOM from 'react-dom';
import ReactTestUtils from 'react-dom/test-utils';
import expect from 'expect';
import {Provider} from 'react-redux';
import HTML5Backend from 'react-dnd-html5-backend';
Expand Down Expand Up @@ -56,4 +57,31 @@ describe('SectionsPreview component', () => {
expect(el.querySelectorAll('.items-list > div').length).toBe(19); // one for each card in the side grid
expect(el.querySelectorAll('.mapstore-side-preview').length).toBe(19);
});
it('SectionsPreview renders duplicate buttons for sections', () => {
ReactDOM.render(
<Provider store={{subscribe: () => {}, getState: () => ({})}}>
<Comp sections={STORY.sections} isCollapsed />
</Provider>, document.getElementById("container"));
const container = document.getElementById('container');
const duplicateButtons = container.querySelectorAll('.glyphicon-duplicate');
expect(duplicateButtons.length).toBeGreaterThan(0);
});
it('SectionsPreview calls onDuplicate when duplicate button is clicked', () => {
const actions = {
onDuplicate: () => {}
};
const spy = expect.spyOn(actions, 'onDuplicate');
ReactDOM.render(
<Provider store={{subscribe: () => {}, getState: () => ({})}}>
<Comp sections={STORY.sections} isCollapsed onDuplicate={actions.onDuplicate}/>
</Provider>, document.getElementById("container"));
const container = document.getElementById('container');
const duplicateButtons = container.querySelectorAll('.glyphicon-duplicate');
expect(duplicateButtons.length).toBeGreaterThan(0);
if (duplicateButtons.length > 0) {
const btn = duplicateButtons[0].closest('button') || duplicateButtons[0];
ReactTestUtils.Simulate.click(btn);
expect(spy).toHaveBeenCalled();
}
});
});
6 changes: 5 additions & 1 deletion web/client/components/geostory/common/MapEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ const MapEditor = ({
closeNodeEditor,
CloseBtn = () => (null),
isDrawEnabled,
hideIdentifyOptions
hideIdentifyOptions,
onApplyToMaps = () => {},
isCarouselSection = false
}) => {
return (mode === Modes.EDIT && isFocused ? <div
key="left-column"
Expand Down Expand Up @@ -120,6 +122,8 @@ const MapEditor = ({
selectedNodes={selectedNodes}
onSelect={onNodeSelect}
hideIdentifyOptions={hideIdentifyOptions}
onApplyToMaps={onApplyToMaps}
isCarouselSection={isCarouselSection}
/>
]}
</BorderLayout>}
Expand Down
Loading
Loading