From b162e42914e5f8853bbb0d870e5aac8020849730 Mon Sep 17 00:00:00 2001 From: Jose Buitron Date: Wed, 13 Sep 2023 12:34:14 -0500 Subject: [PATCH 01/14] feat: Show chapter menu, move up, move down and delete --- src/common/components/ConfirmMenuItem.js | 56 ++++ src/localization/locales/en-US.json | 4 + src/storyMap/components/StoryMapForm.test.js | 212 +++++++++++- .../StoryMapForm/ChaptersSideBar.js | 316 ++++++++++++------ src/storyMap/components/StoryMapForm/index.js | 21 ++ 5 files changed, 502 insertions(+), 107 deletions(-) create mode 100644 src/common/components/ConfirmMenuItem.js diff --git a/src/common/components/ConfirmMenuItem.js b/src/common/components/ConfirmMenuItem.js new file mode 100644 index 000000000..3101198c6 --- /dev/null +++ b/src/common/components/ConfirmMenuItem.js @@ -0,0 +1,56 @@ +/* + * Copyright © 2021-2023 Technology Matters + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ +import React, { useCallback, useEffect, useState } from 'react'; +import { MenuItem } from '@mui/material'; + +import ConfirmationDialog from 'common/components/ConfirmationDialog'; + +const ConfirmMenuItem = props => { + const [openConfirmation, setOpenConfirmation] = useState(false); + const { confirmTitle, confirmMessage, confirmButton, onConfirm, children } = + props; + + useEffect(() => { + setOpenConfirmation(false); + }, []); + + const onClick = useCallback(event => { + setOpenConfirmation(true); + event.stopPropagation(); + }, []); + + const onCancel = useCallback(event => { + setOpenConfirmation(false); + event.stopPropagation(); + }, []); + + return ( + <> + + {children} + + ); +}; + +export default ConfirmMenuItem; diff --git a/src/localization/locales/en-US.json b/src/localization/locales/en-US.json index 6d1a99c55..e0df0da5a 100644 --- a/src/localization/locales/en-US.json +++ b/src/localization/locales/en-US.json @@ -889,6 +889,10 @@ "form_chapter_alignment_left": "Left", "form_chapter_alignment_center": "Center", "form_chapter_alignment_right": "Right", + "form_chapter_open_menu": "Open menu", + "form_chapter_menu_label": "{{chapterLabel}} menu", + "form_chapter_move_up": "Move Chapter Up", + "form_chapter_move_down": "Move Chapter Down", "form_location_dialog_title": "Set map location for <1>{{title}}", "form_location_dialog_title_blank": "Set map location", "location_dialog_cancel_button": "Cancel", diff --git a/src/storyMap/components/StoryMapForm.test.js b/src/storyMap/components/StoryMapForm.test.js index ca6ce8cd7..7fbbb9e68 100644 --- a/src/storyMap/components/StoryMapForm.test.js +++ b/src/storyMap/components/StoryMapForm.test.js @@ -62,6 +62,11 @@ const BASE_CONFIG = { zoom: 5, }, }, + { + id: 'chapter-3', + title: 'Chapter 3', + description: 'Chapter 3 description', + }, ], }; @@ -395,13 +400,13 @@ test('StoryMapForm: Sidebar navigation', async () => { }); const title = within(sidebarList).getByRole('button', { - name: 'T Title', + name: 'Title', }); const chapter1 = within(sidebarList).getByRole('button', { - name: '1 Chapter 1', + name: 'Chapter 1', }); const chapter2 = within(sidebarList).getByRole('button', { - name: '2 Chapter 2', + name: 'Chapter 2', }); await waitFor(() => expect(scrollama).toHaveBeenCalled()); @@ -505,7 +510,7 @@ test('StoryMapForm: Adds new chapter', async () => { description: 'Chapter 2 description', }) ); - expect(saveCall[0].chapters[2]).toEqual( + expect(saveCall[0].chapters[3]).toEqual( expect.objectContaining({ alignment: 'left', title: 'New chapter', @@ -516,14 +521,14 @@ test('StoryMapForm: Adds new chapter', async () => { onChapterExit: [], }) ); - expect(saveCall[0].chapters[2].media).toEqual( + expect(saveCall[0].chapters[3].media).toEqual( expect.objectContaining({ filename: 'test.jpg', type: 'image/jpeg', }) ); - expect(saveCall[0].chapters[2].media.contentId).toEqual( + expect(saveCall[0].chapters[3].media.contentId).toEqual( Object.keys(saveCall[1])[0] ); }); @@ -674,3 +679,198 @@ test('StoryMapForm: Change chapter location', async () => { }) ); }); + +test('StoryMapForm: Move chapter down with menu', async () => { + const { onSaveDraft } = await setup(BASE_CONFIG); + + const chaptersSection = screen.getByRole('navigation', { + name: 'Chapters sidebar', + }); + + const chapter1 = within(chaptersSection).getByRole('button', { + name: 'Chapter 1', + }); + + const menuButton = within(chapter1).getByRole('button', { + name: 'Open menu', + }); + await act(async () => fireEvent.click(menuButton)); + + const menu = screen.getByRole('menu', { + name: 'Chapter 1 menu', + }); + + const moveDownButton = within(menu).getByRole('menuitem', { + name: 'Move Chapter Down', + }); + + await act(async () => fireEvent.click(moveDownButton)); + + await act(async () => + fireEvent.click(screen.getByRole('button', { name: 'Save draft' })) + ); + expect(onSaveDraft).toHaveBeenCalledTimes(1); + const saveCall = onSaveDraft.mock.calls[0]; + + expect(saveCall[0].chapters[0]).toEqual( + expect.objectContaining({ + id: 'chapter-2', + title: 'Chapter 2', + description: 'Chapter 2 description', + }) + ); + expect(saveCall[0].chapters[1]).toEqual( + expect.objectContaining({ + id: 'chapter-1', + title: 'Chapter 1', + description: 'Chapter 1 description', + }) + ); +}); + +test('StoryMapForm: Move chapter up with menu', async () => { + const { onSaveDraft } = await setup(BASE_CONFIG); + + const chaptersSection = screen.getByRole('navigation', { + name: 'Chapters sidebar', + }); + + const chapter2 = within(chaptersSection).getByRole('button', { + name: 'Chapter 2', + }); + + const menuButton = within(chapter2).getByRole('button', { + name: 'Open menu', + }); + await act(async () => fireEvent.click(menuButton)); + + const menu = screen.getByRole('menu', { + name: 'Chapter 2 menu', + }); + + const moveUpButton = within(menu).getByRole('menuitem', { + name: 'Move Chapter Up', + }); + + await act(async () => fireEvent.click(moveUpButton)); + + await act(async () => + fireEvent.click(screen.getByRole('button', { name: 'Save draft' })) + ); + expect(onSaveDraft).toHaveBeenCalledTimes(1); + const saveCall = onSaveDraft.mock.calls[0]; + + expect(saveCall[0].chapters[0]).toEqual( + expect.objectContaining({ + id: 'chapter-2', + title: 'Chapter 2', + description: 'Chapter 2 description', + }) + ); + expect(saveCall[0].chapters[1]).toEqual( + expect.objectContaining({ + id: 'chapter-1', + title: 'Chapter 1', + description: 'Chapter 1 description', + }) + ); +}); + +test('StoryMapForm: Show correct sort buttons if chapter is first', async () => { + await setup(BASE_CONFIG); + + const chaptersSection = screen.getByRole('navigation', { + name: 'Chapters sidebar', + }); + + const chapter1 = within(chaptersSection).getByRole('button', { + name: 'Chapter 1', + }); + const menuButton = within(chapter1).getByRole('button', { + name: 'Open menu', + }); + await act(async () => fireEvent.click(menuButton)); + const menu = screen.getByRole('menu', { + name: 'Chapter 1 menu', + }); + const moveUpButton = within(menu).queryByRole('menuitem', { + name: 'Move Chapter Up', + }); + + expect(moveUpButton).not.toBeInTheDocument(); +}); + +test('StoryMapForm: Show correct sort buttons if chapter is last', async () => { + await setup(BASE_CONFIG); + + const chaptersSection = screen.getByRole('navigation', { + name: 'Chapters sidebar', + }); + + const chapter3 = within(chaptersSection).getByRole('button', { + name: 'Chapter 3', + }); + const menuButton = within(chapter3).getByRole('button', { + name: 'Open menu', + }); + await act(async () => fireEvent.click(menuButton)); + const menu = screen.getByRole('menu', { + name: 'Chapter 3 menu', + }); + const moveDownButton = within(menu).queryByRole('menuitem', { + name: 'Move Chapter Down', + }); + + expect(moveDownButton).not.toBeInTheDocument(); +}); + +test('StoryMapForm: Delete chapter', async () => { + const { onSaveDraft } = await setup(BASE_CONFIG); + + const chaptersSection = screen.getByRole('navigation', { + name: 'Chapters sidebar', + }); + + const chapter1 = within(chaptersSection).getByRole('button', { + name: 'Chapter 1', + }); + const menuButton = within(chapter1).getByRole('button', { + name: 'Open menu', + }); + await act(async () => fireEvent.click(menuButton)); + const menu = screen.getByRole('menu', { + name: 'Chapter 1 menu', + }); + const deleteButton = within(menu).getByRole('menuitem', { + name: 'Delete Chapter', + }); + + await act(async () => fireEvent.click(deleteButton)); + + // Confirmation dialog + await act(async () => + fireEvent.click(screen.getByRole('button', { name: 'Delete Chapter' })) + ); + + await act(async () => + fireEvent.click(screen.getByRole('button', { name: 'Save draft' })) + ); + expect(onSaveDraft).toHaveBeenCalledTimes(1); + const saveCall = onSaveDraft.mock.calls[0]; + + expect(saveCall[0].chapters.length).toEqual(2); + expect(saveCall[0].chapters[0]).toEqual( + expect.objectContaining({ + id: 'chapter-2', + title: 'Chapter 2', + description: 'Chapter 2 description', + }) + ); + expect(saveCall[0].chapters[1]).toEqual( + expect.objectContaining({ + id: 'chapter-3', + title: 'Chapter 3', + description: 'Chapter 3 description', + }) + ); +}); diff --git a/src/storyMap/components/StoryMapForm/ChaptersSideBar.js b/src/storyMap/components/StoryMapForm/ChaptersSideBar.js index 21a32b660..9b0576d63 100644 --- a/src/storyMap/components/StoryMapForm/ChaptersSideBar.js +++ b/src/storyMap/components/StoryMapForm/ChaptersSideBar.js @@ -14,24 +14,222 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import AddIcon from '@mui/icons-material/Add'; -import DeleteIcon from '@mui/icons-material/Delete'; -import { Button, Grid, List, ListItem, Paper, Typography } from '@mui/material'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import { + Button, + Divider, + Grid, + IconButton, + List, + ListItem, + Menu, + MenuItem, + Paper, + Stack, + Typography, +} from '@mui/material'; -import ConfirmButton from 'common/components/ConfirmButton'; +import ConfirmMenuItem from 'common/components/ConfirmMenuItem'; -const ChaptersSidebar = props => { +const SideBarItem = props => { const { t } = useTranslation(); - const { config, currentStepId, onAdd, onDelete, height } = props; - const { chapters } = config; + const { item, onDelete, onMoveChapter, chaptersLength } = props; + const [menuAnchorEl, setMenuAnchorEl] = useState(null); + const openMenu = useMemo(() => Boolean(menuAnchorEl), [menuAnchorEl]); const scrollTo = id => { const element = document.getElementById(id); element?.scrollIntoView({ block: 'start', behavior: 'smooth' }); }; + const handleOpenMenuClick = useCallback(event => { + setMenuAnchorEl(event.currentTarget); + event.stopPropagation(); + }, []); + const handleMenuClose = useCallback(() => { + setMenuAnchorEl(null); + }, []); + + const handleMoveUp = useCallback( + event => { + onMoveChapter(item.id, item.index - 1); + handleMenuClose(); + event.stopPropagation(); + }, + [handleMenuClose, item.id, item.index, onMoveChapter] + ); + + const handleMoveDown = useCallback( + event => { + onMoveChapter(item.id, item.index + 1); + handleMenuClose(); + event.stopPropagation(); + }, + [handleMenuClose, item.id, item.index, onMoveChapter] + ); + + const sortActions = useMemo(() => { + if (chaptersLength === 1) { + return []; + } + if (item.index === 0) { + return [ + { + label: t('storyMap.form_chapter_move_down'), + onClick: handleMoveDown, + }, + ]; + } + if (item.index > 0 && item.index < chaptersLength - 1) { + return [ + { + label: t('storyMap.form_chapter_move_up'), + onClick: handleMoveUp, + }, + { + label: t('storyMap.form_chapter_move_down'), + onClick: handleMoveDown, + }, + ]; + } + if (item.index === chaptersLength - 1) { + return [ + { + label: t('storyMap.form_chapter_move_up'), + onClick: handleMoveUp, + }, + ]; + } + }, [handleMoveDown, handleMoveUp, item.index, chaptersLength, t]); + + return ( + + + + ); +}; + +const ChaptersSidebar = props => { + const { t } = useTranslation(); + const { config, currentStepId, onAdd, onDelete, onMoveChapter, height } = + props; + const { chapters } = config; + const listItems = useMemo( () => [ { @@ -45,7 +243,9 @@ const ChaptersSidebar = props => { id: chapter.id, active: currentStepId === chapter.id, deletable: true, - index: index + 1, + sortable: true, + index: index, + indexLabel: index + 1, })), ], [chapters, currentStepId, t] @@ -61,99 +261,13 @@ const ChaptersSidebar = props => { > {listItems.map(item => ( - - - + ))} - + ); }; @@ -233,6 +235,7 @@ const ChaptersSidebar = props => { const { config, currentStepId, onAdd, onDelete, onMoveChapter, height } = props; const { chapters } = config; + const sensorAPIRef = useRef(null); const listItems = useMemo( () => [ @@ -263,34 +266,57 @@ const ChaptersSidebar = props => { aria-label={t('storyMap.form_chapters_sidebar_section_label')} sx={{ height, overflow: 'auto', width: '200px' }} > - - {listItems.map(item => ( - - ))} - - - - + { + sensorAPIRef.current = api; + }, + ]} + > + + {provided => ( + + {listItems.map((item, index) => ( + + {(provided, snapshot) => ( + + + + )} + + ))} + + + + + )} + + ); }; From 8b568a090237a66c9c675f7a04dc0c7e0f54493d Mon Sep 17 00:00:00 2001 From: Jose Buitron Date: Thu, 14 Sep 2023 11:42:44 -0500 Subject: [PATCH 05/14] fix: Inset map fix --- src/storyMap/components/StoryMap.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/storyMap/components/StoryMap.js b/src/storyMap/components/StoryMap.js index 1a5158c3f..3a5e9988e 100644 --- a/src/storyMap/components/StoryMap.js +++ b/src/storyMap/components/StoryMap.js @@ -530,10 +530,10 @@ const StoryMap = props => { map={map} initialLocation={initialLocation} > - {insetMap => ( + {insetMapContext => ( Date: Thu, 14 Sep 2023 11:44:28 -0500 Subject: [PATCH 06/14] feat: Drag and drop label and reuse functionality --- src/assets/drag-icon.svg | 1 + src/localization/locales/en-US.json | 1 + .../StoryMapForm/ChaptersSideBar.js | 151 +++++++++++++----- 3 files changed, 113 insertions(+), 40 deletions(-) create mode 100644 src/assets/drag-icon.svg diff --git a/src/assets/drag-icon.svg b/src/assets/drag-icon.svg new file mode 100644 index 000000000..da1810bc9 --- /dev/null +++ b/src/assets/drag-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/localization/locales/en-US.json b/src/localization/locales/en-US.json index e0df0da5a..7e4bc1c1d 100644 --- a/src/localization/locales/en-US.json +++ b/src/localization/locales/en-US.json @@ -893,6 +893,7 @@ "form_chapter_menu_label": "{{chapterLabel}} menu", "form_chapter_move_up": "Move Chapter Up", "form_chapter_move_down": "Move Chapter Down", + "form_chapter_dragging_label": "Dragging {{chapterLabel}}", "form_location_dialog_title": "Set map location for <1>{{title}}", "form_location_dialog_title_blank": "Set map location", "location_dialog_cancel_button": "Cancel", diff --git a/src/storyMap/components/StoryMapForm/ChaptersSideBar.js b/src/storyMap/components/StoryMapForm/ChaptersSideBar.js index c13c5727f..0249f1e6d 100644 --- a/src/storyMap/components/StoryMapForm/ChaptersSideBar.js +++ b/src/storyMap/components/StoryMapForm/ChaptersSideBar.js @@ -20,6 +20,7 @@ import { useTranslation } from 'react-i18next'; import AddIcon from '@mui/icons-material/Add'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import { + Box, Button, Divider, Grid, @@ -33,12 +34,25 @@ import { Typography, } from '@mui/material'; +import { withProps } from 'react-hoc'; + import ConfirmMenuItem from 'common/components/ConfirmMenuItem'; import StrictModeDroppable from 'common/components/StrictModeDroppable'; +import dragIcon from 'assets/drag-icon.svg'; + +const DragIcon = withProps(Box, { + component: 'img', + alt: '', + src: dragIcon, + width: 24, + height: 24, +}); + const SideBarItem = props => { const { t } = useTranslation(); - const { item, onDelete, onMoveChapter, chaptersLength } = props; + const { item, onDelete, onMoveDown, onMoveUp, chaptersLength, isDragging } = + props; const [menuAnchorEl, setMenuAnchorEl] = useState(null); const openMenu = useMemo(() => Boolean(menuAnchorEl), [menuAnchorEl]); @@ -57,20 +71,20 @@ const SideBarItem = props => { const handleMoveUp = useCallback( event => { - onMoveChapter(item.id, item.index - 1); + onMoveUp(item.id); handleMenuClose(); event.stopPropagation(); }, - [handleMenuClose, item.id, item.index, onMoveChapter] + [handleMenuClose, item.id, onMoveUp] ); const handleMoveDown = useCallback( event => { - onMoveChapter(item.id, item.index + 1); + onMoveDown(item.id); handleMenuClose(); event.stopPropagation(); }, - [handleMenuClose, item.id, item.index, onMoveChapter] + [handleMenuClose, item.id, onMoveDown] ); const sortActions = useMemo(() => { @@ -116,18 +130,24 @@ const SideBarItem = props => { width: '100%', borderRadius: 0, pl: 0, - bgcolor: item.active ? 'blue.mid' : 'transparent', + bgcolor: item.active || isDragging ? 'blue.mid' : 'transparent', '&:hover': { bgcolor: item.active ? 'blue.mid' : 'gray.lite1' }, }} onClick={() => scrollTo(item.id)} - aria-label={item.label} + aria-label={ + isDragging + ? t('storyMap.form_chapter_dragging_label', { + chapterLabel: item.label, + }) + : item.label + } > @@ -206,12 +226,13 @@ const SideBarItem = props => { {item.indexLabel} + {isDragging && } ({ + borderRadius: '4px', bgcolor: 'gray.dark2', color: 'white', p: 1, @@ -219,7 +240,7 @@ const SideBarItem = props => { display: 'flex', alignItems: 'center', overflow: 'hidden', - }} + })} > {item.label} @@ -236,16 +257,21 @@ const ChaptersSidebar = props => { props; const { chapters } = config; const sensorAPIRef = useRef(null); + const [draggingId, setDraggingId] = useState(null); - const listItems = useMemo( - () => [ - { - label: `${t('storyMap.form_title_chapter_label')}`, - id: 'story-map-title', - active: currentStepId === 'story-map-title', - index: 'T', - }, - ...chapters.map((chapter, index) => ({ + const titleItem = useMemo( + () => ({ + label: `${t('storyMap.form_title_chapter_label')}`, + id: 'story-map-title', + active: currentStepId === 'story-map-title', + index: 'T', + }), + [currentStepId, t] + ); + + const chapterItems = useMemo( + () => + chapters.map((chapter, index) => ({ label: chapter.title || t('storyMap.form_chapter_no_title_label'), id: chapter.id, active: currentStepId === chapter.id, @@ -254,10 +280,51 @@ const ChaptersSidebar = props => { index: index, indexLabel: index + 1, })), - ], [chapters, currentStepId, t] ); + const onDragStart = useCallback(data => { + setDraggingId(data.draggableId); + }, []); + + const onDragEnd = useCallback( + ({ destination, source }) => { + setDraggingId(null); + const draggingElement = chapterItems[source.index]; + const destinationIndex = destination.index; + + onMoveChapter(draggingElement.id, destinationIndex); + }, + [chapterItems, onMoveChapter] + ); + + const moveChapter = useCallback( + (chapterId, action) => { + const api = sensorAPIRef.current; + const preDrag = api.tryGetLock(chapterId, () => {}); + const drag = preDrag.snapLift(); + drag[action](); + setTimeout(() => { + drag.drop(); + }, 300); + }, + [sensorAPIRef] + ); + + const onMoveChapterDown = useCallback( + chapterId => { + moveChapter(chapterId, 'moveDown'); + }, + [moveChapter] + ); + + const onMoveChapterUp = useCallback( + chapterId => { + moveChapter(chapterId, 'moveUp'); + }, + [moveChapter] + ); + return ( { aria-label={t('storyMap.form_chapters_sidebar_section_label')} sx={{ height, overflow: 'auto', width: '200px' }} > + { sensorAPIRef.current = api; @@ -276,7 +346,7 @@ const ChaptersSidebar = props => { {provided => ( - {listItems.map((item, index) => ( + {chapterItems.map((item, index) => ( {(provided, snapshot) => ( { )} ))} - - - + {draggingId && provided.placeholder} )} + ); }; From fb03ba9072ada73efad7b27d68663c8d6b82bcea Mon Sep 17 00:00:00 2001 From: Jose Buitron Date: Thu, 14 Sep 2023 11:45:45 -0500 Subject: [PATCH 07/14] fix: Added drag animation delay constant --- src/storyMap/components/StoryMapForm/ChaptersSideBar.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/storyMap/components/StoryMapForm/ChaptersSideBar.js b/src/storyMap/components/StoryMapForm/ChaptersSideBar.js index 0249f1e6d..5b1b0ae5e 100644 --- a/src/storyMap/components/StoryMapForm/ChaptersSideBar.js +++ b/src/storyMap/components/StoryMapForm/ChaptersSideBar.js @@ -41,6 +41,8 @@ import StrictModeDroppable from 'common/components/StrictModeDroppable'; import dragIcon from 'assets/drag-icon.svg'; +const DRAG_ANIMATION_DELAY = 300; + const DragIcon = withProps(Box, { component: 'img', alt: '', @@ -304,9 +306,7 @@ const ChaptersSidebar = props => { const preDrag = api.tryGetLock(chapterId, () => {}); const drag = preDrag.snapLift(); drag[action](); - setTimeout(() => { - drag.drop(); - }, 300); + setTimeout(() => drag.drop(), DRAG_ANIMATION_DELAY); }, [sensorAPIRef] ); From 6a97c017a3255e611d2a0de635f965dbec42253e Mon Sep 17 00:00:00 2001 From: Jose Buitron Date: Thu, 14 Sep 2023 11:46:05 -0500 Subject: [PATCH 08/14] fix: Wait for drag animation for tests --- src/storyMap/components/StoryMapForm.test.js | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/storyMap/components/StoryMapForm.test.js b/src/storyMap/components/StoryMapForm.test.js index 56aa5f3b9..826f508da 100644 --- a/src/storyMap/components/StoryMapForm.test.js +++ b/src/storyMap/components/StoryMapForm.test.js @@ -706,6 +706,14 @@ test('StoryMapForm: Move chapter down with menu', async () => { await act(async () => fireEvent.click(moveDownButton)); + await waitFor(() => + expect( + screen.queryByRole('button', { + name: 'Dragging Chapter 1', + }) + ).not.toBeInTheDocument() + ); + await act(async () => fireEvent.click(screen.getByRole('button', { name: 'Save draft' })) ); @@ -754,6 +762,20 @@ test('StoryMapForm: Move chapter up with menu', async () => { await act(async () => fireEvent.click(moveUpButton)); + expect( + screen.getByRole('button', { + name: 'Dragging Chapter 2', + }) + ).toBeInTheDocument(); + + await waitFor(() => + expect( + screen.queryByRole('button', { + name: 'Dragging Chapter 2', + }) + ).not.toBeInTheDocument() + ); + await act(async () => fireEvent.click(screen.getByRole('button', { name: 'Save draft' })) ); From 3627b98a83349fbad84588ec4706ea9a47bf78c9 Mon Sep 17 00:00:00 2001 From: Jose Buitron Date: Thu, 14 Sep 2023 11:47:17 -0500 Subject: [PATCH 09/14] chore: Added ES translations --- src/localization/locales/es-ES.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/localization/locales/es-ES.json b/src/localization/locales/es-ES.json index 26c93db3c..961b6d653 100644 --- a/src/localization/locales/es-ES.json +++ b/src/localization/locales/es-ES.json @@ -900,6 +900,7 @@ "form_chapter_menu_label": "{{chapterLabel}} menú", "form_chapter_move_up": "Mover capítulo hacia arriba", "form_chapter_move_down": "Mover capítulo hacia abajo", + "form_chapter_dragging_label": "Arrastrando {{chapterLabel}}", "form_location_dialog_title": "Establecer la ubicación del mapa para <1>{{title}}", "form_location_dialog_title_blank": "Establecer ubicación en el mapa", "location_dialog_cancel_button": "Cancelar", From d33cc157ff2012b893d1cbcfbc52ff1468828bbe Mon Sep 17 00:00:00 2001 From: Jose Buitron Date: Thu, 14 Sep 2023 12:06:19 -0500 Subject: [PATCH 10/14] fix: Do nothing if no destination index --- src/storyMap/components/StoryMapForm/ChaptersSideBar.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/storyMap/components/StoryMapForm/ChaptersSideBar.js b/src/storyMap/components/StoryMapForm/ChaptersSideBar.js index 5b1b0ae5e..2c2b3cb23 100644 --- a/src/storyMap/components/StoryMapForm/ChaptersSideBar.js +++ b/src/storyMap/components/StoryMapForm/ChaptersSideBar.js @@ -293,7 +293,11 @@ const ChaptersSidebar = props => { ({ destination, source }) => { setDraggingId(null); const draggingElement = chapterItems[source.index]; - const destinationIndex = destination.index; + const destinationIndex = destination?.index; + + if (!destinationIndex) { + return; + } onMoveChapter(draggingElement.id, destinationIndex); }, From 4f86f3c5cd000e4a264b44b87b3fe93e5bb1d800 Mon Sep 17 00:00:00 2001 From: Jose Buitron Date: Thu, 14 Sep 2023 12:20:04 -0500 Subject: [PATCH 11/14] fix: Check if destination index is undefined --- src/storyMap/components/StoryMapForm/ChaptersSideBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storyMap/components/StoryMapForm/ChaptersSideBar.js b/src/storyMap/components/StoryMapForm/ChaptersSideBar.js index 2c2b3cb23..39086876e 100644 --- a/src/storyMap/components/StoryMapForm/ChaptersSideBar.js +++ b/src/storyMap/components/StoryMapForm/ChaptersSideBar.js @@ -295,7 +295,7 @@ const ChaptersSidebar = props => { const draggingElement = chapterItems[source.index]; const destinationIndex = destination?.index; - if (!destinationIndex) { + if (destinationIndex === undefined) { return; } From c4260a3ba742e38947bd51e836d368efc19ac230 Mon Sep 17 00:00:00 2001 From: Jose Buitron Date: Thu, 14 Sep 2023 12:31:41 -0500 Subject: [PATCH 12/14] fix: Fixed sidebar layout --- .../components/StoryMapForm/ChaptersSideBar.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/storyMap/components/StoryMapForm/ChaptersSideBar.js b/src/storyMap/components/StoryMapForm/ChaptersSideBar.js index 39086876e..4e7879f78 100644 --- a/src/storyMap/components/StoryMapForm/ChaptersSideBar.js +++ b/src/storyMap/components/StoryMapForm/ChaptersSideBar.js @@ -335,7 +335,14 @@ const ChaptersSidebar = props => { item component="nav" aria-label={t('storyMap.form_chapters_sidebar_section_label')} - sx={{ height, overflow: 'auto', width: '200px' }} + sx={{ + height, + overflow: 'auto', + width: '200px', + display: 'flex', + alignItems: 'stretch', + flexDirection: 'column', + }} > {