Skip to content
Snippets Groups Projects
Commit 14d7d33d authored by Victor Löfgren's avatar Victor Löfgren
Browse files

Resolve "Fix move slides on backend"

parent 6697d93c
1 merge request!132Resolve "Fix move slides on backend"
Pipeline #44567 passed with warnings
......@@ -10,6 +10,7 @@ import { getCities } from '../../actions/cities'
import { getEditorCompetition, setEditorSlideId, setEditorViewId } from '../../actions/editor'
import { getTypes } from '../../actions/typesAction'
import { useAppDispatch, useAppSelector } from '../../hooks'
import { RichSlide } from '../../interfaces/ApiRichModels'
import { renderSlideIcon } from '../../utils/renderSlideIcon'
import { RemoveMenuItem } from '../admin/styledComp'
import { Content, InnerContent } from '../views/styled'
......@@ -50,6 +51,7 @@ interface CompetitionParams {
const PresentationEditorPage: React.FC = () => {
const { competitionId }: CompetitionParams = useParams()
const dispatch = useAppDispatch()
const [sortedSlides, setSortedSlides] = useState<RichSlide[]>([])
const activeSlideId = useAppSelector((state) => state.editor.activeSlideId)
const activeViewTypeId = useAppSelector((state) => state.editor.activeViewTypeId)
const competition = useAppSelector((state) => state.editor.competition)
......@@ -60,6 +62,10 @@ const PresentationEditorPage: React.FC = () => {
dispatch(getCities())
}, [])
useEffect(() => {
setSortedSlides(competition.slides.sort((a, b) => (a.order > b.order ? 1 : -1)))
}, [competition])
const setActiveSlideId = (id: number) => {
dispatch(setEditorSlideId(id))
}
......@@ -111,16 +117,19 @@ const PresentationEditorPage: React.FC = () => {
}
const onDragEnd = async (result: DropResult) => {
// dropped outside the list
if (!result.destination) {
// dropped outside the list or same place
if (!result.destination || result.destination.index === result.source.index) {
return
}
const draggedIndex = result.source.index
const draggedSlideId = competition.slides.find((slide) => slide.order === draggedIndex)?.id
const draggedSlideId = sortedSlides[draggedIndex].id
const slidesCopy = [...sortedSlides]
const [removed] = slidesCopy.splice(draggedIndex, 1)
slidesCopy.splice(result.destination.index, 0, removed)
setSortedSlides(slidesCopy)
if (draggedSlideId) {
await axios
.put(`/api/competitions/${competitionId}/slides/${draggedSlideId}/order`, { order: result.destination.index })
.then(() => dispatch(getEditorCompetition(competitionId)))
.catch(console.log)
}
}
......@@ -161,26 +170,26 @@ const PresentationEditorPage: React.FC = () => {
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable">
{(provided) => (
<div key={provided.innerRef.toString()} ref={provided.innerRef} {...provided.droppableProps}>
{competition.slides &&
competition.slides.map((slide, index) => (
<Draggable key={slide.order} draggableId={slide.id.toString()} index={index}>
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<SlideListItem
divider
button
selected={slide.id === activeSlideId}
onClick={() => setActiveSlideId(slide.id)}
onContextMenu={(event) => handleRightClick(event, slide.id)}
>
{renderSlideIcon(slide)}
<ListItemText primary={`Sida ${slide.order + 1}`} />
</SlideListItem>
</div>
)}
</Draggable>
))}
<div ref={provided.innerRef} {...provided.droppableProps}>
{sortedSlides.map((slide, index) => (
<Draggable key={slide.id} draggableId={slide.id.toString()} index={index}>
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<SlideListItem
divider
key={slide.order}
button
selected={slide.id === activeSlideId}
onClick={() => setActiveSlideId(slide.id)}
onContextMenu={(event) => handleRightClick(event, slide.id)}
>
{renderSlideIcon(slide)}
<ListItemText primary={`Sida ${slide.id}`} />
</SlideListItem>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
......
......@@ -8,7 +8,10 @@ import app.database.controller as dbc
from app.apis import item_response, list_response, protect_route
from app.core.dto import SlideDTO
from app.core.parsers import sentinel
from app.database.controller.get import slide_count
from app.database.models import Competition
from flask_restx import Resource, reqparse
from flask_restx.errors import abort
api = SlideDTO.api
schema = SlideDTO.schema
......@@ -78,26 +81,18 @@ class SlideOrder(Resource):
""" Edits the specified slide order using the provided arguments. """
args = slide_parser_edit.parse_args(strict=True)
order = args.get("order")
new_order = args.get("order")
item_slide = dbc.get.slide(competition_id, slide_id)
if order == item_slide.order:
if new_order == item_slide.order:
return item_response(schema.dump(item_slide))
# clamp order between 0 and max
order_count = dbc.get.slide_count(competition_id)
if order < 0:
order = 0
elif order >= order_count - 1:
order = order_count - 1
# get slide at the requested order
item_slide_id = dbc.get.slide(competition_id, order)
# switch place between them
item_slide = dbc.edit.switch_order(item_slide, item_slide_id)
if not (0 <= new_order < dbc.get.slide_count(competition_id)):
abort(codes.BAD_REQUEST, f"Cant change to invalid slide order '{new_order}'")
item_competition = dbc.get.one(Competition, competition_id)
dbc.utils.move_slides(item_competition, item_slide.order, new_order)
return item_response(schema.dump(item_slide))
......
......@@ -9,25 +9,70 @@ from app.database.models import Code
from flask_restx import abort
def move_slides(item_competition, start_order, end_order):
""" Changes a slide order and then arranges other affected slides. """
def move_slides(item_competition, from_order, to_order):
"""
Move slide from from_order to to_order in item_competition.
"""
num_slides = len(item_competition.slides)
assert 0 <= from_order < num_slides, "Invalid order to move from"
assert 0 <= to_order < num_slides, "Invalid order to move to"
# This function is sooo terrible, someone please tell me how to update
# multiple values in the database at the same time with unique constraints.
# If you update all the values at the same time none of them will collide
# but that database doesn't know that so you have to update them to some
# other value before and then change every value back to the correct one,
# so 2 commits.
# An example will follow the entire code to make it clear what it does
# Lets say we have 5 slides, and we want to move the slide at index 1
# to index 4.
# We begin with a list of slides with orders [0, 1, 2, 3, 4]
slides = item_competition.slides
# Move up
if start_order < end_order:
for i in range(start_order + 1, end_order):
slides[i].order -= 1
# Move down
elif start_order > end_order:
for i in range(end_order, start_order):
slides[i].order += 1
change = 1 if to_order < from_order else -1
start_order = min(from_order, to_order)
end_order = max(from_order, to_order)
# start = 5, end = 1
# 1->2, 2->3, 4->5
# 5 = 1
# Move slides up 100
for item_slide in slides:
item_slide.order += 100
# Our slide orders now look like [100, 101, 102, 103, 104]
# Move slides between from and to order either up or down, but minus in front
for item_slide in slides:
if start_order <= item_slide.order - 100 <= end_order:
item_slide.order = -(item_slide.order + change)
# Our slide orders now look like [100, -100, -101, -102, -103]
# Find the slide that was to be moved and change it to correct order with minus in front
for item_slide in slides:
if item_slide.order == -(from_order + change + 100):
item_slide.order = -(to_order + 100)
break
# Our slide orders now look like [100, -104, -101, -102, -103]
db.session.commit()
# Negate all order so that they become positive
for item_slide in slides:
if start_order <= -(item_slide.order + 100) <= end_order:
item_slide.order = -(item_slide.order)
# Our slide orders now look like [100, 104, 101, 102, 103]
for item_slide in slides:
item_slide.order -= 100
# Our slide orders now look like [0, 4, 1, 2, 3]
# We have now successfully moved slide 1 to 4
slides[start_order].order = end_order
return commit_and_refresh(item_competition)
......
......@@ -3,10 +3,10 @@ This file tests the database controller functions.
"""
import app.database.controller as dbc
from app.database.models import City, Competition, Media, MediaType, Role, User, Code
from app.database.models import City, Code, Competition, Media, MediaType, Role, User
from tests import app, client, db
from tests.test_helpers import add_default_values, assert_exists, assert_insert_fail, delete
from tests.test_helpers import add_default_values, assert_exists, assert_insert_fail, assert_slide_order, delete
def test_user(client):
......@@ -151,6 +151,33 @@ def check_slides_copy(item_slide_original, item_slide_copy, num_slides, order):
assert item_slide_copy == item_slides[order]
def test_move_slides(client):
add_default_values()
item_comp = dbc.get.one(Competition, 1)
for _ in range(9):
dbc.add.slide(item_comp.id)
# Move from beginning to end
item_comp = dbc.utils.move_slides(item_comp, 0, 9)
assert_slide_order(item_comp, [9, 0, 1, 2, 3, 4, 5, 6, 7, 8])
# Move from end to beginning
item_comp = dbc.utils.move_slides(item_comp, 9, 0)
assert_slide_order(item_comp, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# Move some things in the middle
item_comp = dbc.utils.move_slides(item_comp, 3, 7)
assert_slide_order(item_comp, [0, 1, 2, 7, 3, 4, 5, 6, 8, 9])
item_comp = dbc.utils.move_slides(item_comp, 1, 5)
assert_slide_order(item_comp, [0, 5, 1, 7, 2, 3, 4, 6, 8, 9])
item_comp = dbc.utils.move_slides(item_comp, 8, 2)
assert_slide_order(item_comp, [0, 6, 1, 8, 3, 4, 5, 7, 2, 9])
"""
def test_question(client):
add_default_values()
......
......@@ -153,3 +153,11 @@ def change_order_test(client, cid, slide_id, new_slide_id, h):
# Changes order
response, _ = put(client, f"/api/competitions/{cid}/slides/{slide_id}/order", {"order": new_order}, headers=h)
assert response.status_code == codes.OK
def assert_slide_order(item_comp, correct_order):
"""
Assert that the slides in the given competition are in the correct order
"""
for slide, order in zip(item_comp.slides, correct_order):
assert slide.order == order
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment