Skip to content

Commit

Permalink
Reorder and Rotate images using new UI (#438)
Browse files Browse the repository at this point in the history
  • Loading branch information
ciur authored Aug 25, 2024
1 parent c72f110 commit 4b98bb2
Show file tree
Hide file tree
Showing 27 changed files with 1,156 additions and 90 deletions.
4 changes: 2 additions & 2 deletions papermerge/core/page_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
logger = logging.getLogger(__name__)


def apply_pages_op(items: List[PageAndRotOp]) -> List[PyDocVer]:
def apply_pages_op(items: List[PageAndRotOp]) -> List[PyDocument]:
pages = Page.objects.filter(
pk__in=[item.page.id for item in items]
)
Expand Down Expand Up @@ -51,7 +51,7 @@ def apply_pages_op(items: List[PageAndRotOp]) -> List[PyDocVer]:
)
notify_generate_previews(str(doc.id))

return doc.versions.all()
return doc


def copy_pdf_pages(
Expand Down
7 changes: 4 additions & 3 deletions papermerge/core/routers/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from papermerge.core.page_ops import move_pages as api_move_pages
from papermerge.core.schemas import ExtractPagesOut, MovePagesOut
from papermerge.core.schemas.documents import DocumentVersion as PyDocVer
from papermerge.core.schemas.documents import Document as PyDocument
from papermerge.core.schemas.pages import (ExtractPagesIn, MovePagesIn,
PageAndRotOp)
from papermerge.core.utils import image
Expand Down Expand Up @@ -136,7 +137,7 @@ def apply_page_operations(
schemas.User,
Security(get_current_user, scopes=[scopes.PAGE_UPDATE])
],
) -> List[PyDocVer]:
) -> PyDocument:
"""Applies reorder, delete and/or rotate operation(s) on a set of pages.
Required scope: `{scope}`
Expand All @@ -156,9 +157,9 @@ def apply_page_operations(
When `angle` > 0 -> the rotation is clockwise.
When `angle` < 0 -> the rotation is counterclockwise.
"""
new_versions = apply_pages_op(items)
new_doc = apply_pages_op(items)

return [PyDocVer.model_validate(version) for version in new_versions]
return PyDocument.model_validate(new_doc)


@router.post("/move")
Expand Down
6 changes: 3 additions & 3 deletions tests/core/test_page_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ def test_apply_pages_op(_):
items = [PageAndRotOp(page=pypage, angle=0)]

# It should copy only first page which contains word "cat"
versions = apply_pages_op(items)
new_doc = apply_pages_op(items)

# now there one more version (originally was only one doc ver)
assert versions.count() == 2
newly_created_version = versions.last()
assert new_doc.versions.count() == 2
newly_created_version = new_doc.versions.last()

# newly create version should have only one page
assert newly_created_version.pages.count() == 1
Expand Down
12 changes: 12 additions & 0 deletions ui2/src/app/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,15 @@
main {
margin: 0.5rem;
}

.borderline-top {
border-top: #7474ff 3px solid;
}

.borderline-bottom {
border-bottom: #7474ff 3px solid;
}

.dragged {
opacity: 0.3;
}
4 changes: 3 additions & 1 deletion ui2/src/app/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import groupDetailsReducer from "@/slices/groupDetails"
import userDetailsReducer from "@/slices/userDetails"
import {uploaderReducer} from "@/slices/uploader"
import sizesSliceReducer from "@/slices/sizes"
import dragndropReducer from "@/slices/dragndrop"

export const store = configureStore({
reducer: {
Expand All @@ -23,6 +24,7 @@ export const store = configureStore({
groupDetails: groupDetailsReducer,
userDetails: userDetailsReducer,
uploader: uploaderReducer,
sizes: sizesSliceReducer
sizes: sizesSliceReducer,
dragndrop: dragndropReducer
}
})
11 changes: 10 additions & 1 deletion ui2/src/components/Viewer/ActionButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import {useContext, useRef, useEffect} from "react"
import {Group} from "@mantine/core"
import {useViewportSize} from "@mantine/hooks"
import {useDispatch} from "react-redux"
import {useDispatch, useSelector} from "react-redux"
import ToggleSecondaryPanel from "@/components/DualPanel/ToggleSecondaryPanel"
import {updateActionPanel} from "@/slices/sizes"
import PanelContext from "@/contexts/PanelContext"
import EditTitleButton from "./EditTitleButton"

import type {PanelMode} from "@/types"
import type {RootState} from "@/app/types"
import DownloadButton from "./DownloadButton/DownloadButton"
import {selectSelectedPages} from "@/slices/dualPanel/dualPanel"
import RotateCCButton from "./RotateCCButton"
import RotateButton from "./RotateButton"

export default function ActionButtons() {
const {height, width} = useViewportSize()
const dispatch = useDispatch()
const ref = useRef<HTMLDivElement>(null)
const mode: PanelMode = useContext(PanelContext)
const selectedPages = useSelector((state: RootState) =>
selectSelectedPages(state, mode)
)

useEffect(() => {
if (ref?.current) {
Expand All @@ -34,6 +41,8 @@ export default function ActionButtons() {
<Group>
<EditTitleButton />
<DownloadButton />
{selectedPages.length > 0 && <RotateButton />}
{selectedPages.length > 0 && <RotateCCButton />}
</Group>
<Group>
<ToggleSecondaryPanel />
Expand Down
Empty file.
23 changes: 16 additions & 7 deletions ui2/src/components/Viewer/DownloadButton/DownloadButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {RootState} from "@/app/types"
import {download_file} from "@/httpClient"

import PanelContext from "@/contexts/PanelContext"
import {DocumentVersion} from "@/types"
import {DocumentVersionWithPageRot} from "@/types"

export default function DownloadButton() {
const mode = useContext(PanelContext)
Expand All @@ -16,15 +16,24 @@ export default function DownloadButton() {
selectDocumentVersions(state, mode)
)

const onClick = (v: DocumentVersion) => {
const onClick = (v: DocumentVersionWithPageRot) => {
download_file(v)
}

const versionComponents = vers?.map(v => (
<Menu.Item key={v.id} onClick={() => onClick(v)}>
Version {v.number} - {v.short_description}
</Menu.Item>
))
const versionComponents = vers?.map(v => {
if (v.short_description) {
return (
<Menu.Item key={v.id} onClick={() => onClick(v)}>
Version {v.number} - {v.short_description}
</Menu.Item>
)
}
return (
<Menu.Item key={v.id} onClick={() => onClick(v)}>
Version {v.number}
</Menu.Item>
)
})

return (
<Menu>
Expand Down
14 changes: 7 additions & 7 deletions ui2/src/components/Viewer/Page/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import {
selectDocumentCurrentPage,
selectZoomFactor
} from "@/slices/dualPanel/dualPanel"
import {PageType, PanelMode} from "@/types"
import {PageAndRotOp, PanelMode} from "@/types"
import {useProtectedJpg} from "@/hooks/protected_image"
import {RootState} from "@/app/types"
import classes from "./Page.module.css"

type Args = {
page: PageType
page: PageAndRotOp
}

export default function Page({page}: Args) {
const protectedImage = useProtectedJpg(page.jpg_url)
const protectedImage = useProtectedJpg(page.page.jpg_url)
const mode: PanelMode = useContext(PanelContext)
const currentPage = useSelector((state: RootState) =>
selectDocumentCurrentPage(state, mode)
Expand All @@ -27,21 +27,21 @@ export default function Page({page}: Args) {
)

useEffect(() => {
if (currentPage == page.number) {
if (currentPage == page.page.number) {
if (targetRef.current) {
targetRef.current.scrollIntoView(false)
}
}
}, [currentPage, protectedImage, page.number])
}, [currentPage, protectedImage, page.page.number])

return (
<Stack className={classes.page}>
<img
style={{width: `${zoomFactor}%`}}
style={{transform: `rotate(${page.angle}deg)`, width: `${zoomFactor}%`}}
ref={targetRef}
src={protectedImage.data || ""}
/>
<div>{page.number}</div>
<div>{page.page.number}</div>
</Stack>
)
}
2 changes: 1 addition & 1 deletion ui2/src/components/Viewer/Pages/Pages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function Pages() {
selectDocumentCurrentVersion(state, mode)
)

const pages = docVersion?.pages.map(p => <Page key={p.id} page={p} />)
const pages = docVersion?.pages.map(p => <Page key={p.page.id} page={p} />)

return (
<Stack justify="center" className={classes.pages}>
Expand Down
28 changes: 28 additions & 0 deletions ui2/src/components/Viewer/RotateButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {useContext} from "react"
import {useDispatch, useSelector} from "react-redux"
import {Tooltip, ActionIcon} from "@mantine/core"
import {IconRotateClockwise} from "@tabler/icons-react"
import {rotatePages, selectSelectedPages} from "@/slices/dualPanel/dualPanel"
import PanelContext from "@/contexts/PanelContext"
import {PanelMode} from "@/types"
import type {RootState} from "@/app/types"

export default function RotateCCButton() {
const mode: PanelMode = useContext(PanelContext)
const dispatch = useDispatch()
const selectedPages = useSelector((state: RootState) =>
selectSelectedPages(state, mode)
)
const onClick = () => {
const pages = selectedPages.map(p => p.page)
dispatch(rotatePages({mode, pages, angle: 90}))
}

return (
<Tooltip label="Rotate selected pages clockwise" withArrow>
<ActionIcon size={"lg"} variant="default" onClick={onClick}>
<IconRotateClockwise stroke={1.4} />
</ActionIcon>
</Tooltip>
)
}
28 changes: 28 additions & 0 deletions ui2/src/components/Viewer/RotateCCButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {useContext} from "react"
import {useDispatch, useSelector} from "react-redux"
import {Tooltip, ActionIcon} from "@mantine/core"
import {IconRotate} from "@tabler/icons-react"
import {rotatePages, selectSelectedPages} from "@/slices/dualPanel/dualPanel"
import PanelContext from "@/contexts/PanelContext"
import {PanelMode} from "@/types"
import type {RootState} from "@/app/types"

export default function RotateCCButton() {
const mode: PanelMode = useContext(PanelContext)
const dispatch = useDispatch()
const selectedPages = useSelector((state: RootState) =>
selectSelectedPages(state, mode)
)
const onClick = () => {
const pages = selectedPages.map(p => p.page)
dispatch(rotatePages({mode, pages, angle: -90}))
}

return (
<Tooltip label="Rotate selected pages counter-clockwise" withArrow>
<ActionIcon size={"lg"} variant="default" onClick={onClick}>
<IconRotate stroke={1.4} />
</ActionIcon>
</Tooltip>
)
}
29 changes: 0 additions & 29 deletions ui2/src/components/Viewer/Thumbnail.tsx

This file was deleted.

5 changes: 5 additions & 0 deletions ui2/src/components/Viewer/Thumbnail/Thumbnail.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.thumbnail {
.checkbox {
align-self: self-start;
}
}
Loading

0 comments on commit 4b98bb2

Please sign in to comment.