Skip to content

Commit

Permalink
add download button (#436)
Browse files Browse the repository at this point in the history
  • Loading branch information
ciur authored Aug 21, 2024
1 parent e1da027 commit d45682e
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 9 deletions.
8 changes: 6 additions & 2 deletions papermerge/core/db/doc_ver.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,18 @@ def get_last_doc_ver(

def get_doc_ver(
engine: Engine,
id: UUID # noqa
id: UUID, # noqa
user_id: UUID
) -> schemas.DocumentVersion:
"""
Returns last version of the document
identified by doc_id
"""
with Session(engine) as session: # noqa
stmt = select(DocumentVersion).where(DocumentVersion.id == id)
stmt = select(DocumentVersion).join(Document).where(
Document.user_id == user_id,
DocumentVersion.id == id
)
db_doc_ver = session.scalars(stmt).one()
model_doc_ver = schemas.DocumentVersion.model_validate(db_doc_ver)

Expand Down
34 changes: 32 additions & 2 deletions papermerge/core/routers/document_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
import uuid
from typing import Annotated

from fastapi import APIRouter, HTTPException, Security
from fastapi import APIRouter, HTTPException, Security, Depends
from fastapi.responses import FileResponse

from papermerge.core import schemas, utils
from papermerge.core import schemas, utils, db
from papermerge.core.db import exceptions as db_exc
from papermerge.core.auth import get_current_user, scopes
from papermerge.core.models import DocumentVersion

Expand Down Expand Up @@ -56,3 +57,32 @@ def download_document_version(
filename=doc_ver.file_name,
content_disposition_type='attachment'
)


@router.get("/{document_version_id}", response_model=schemas.DocumentVersion)
@utils.docstring_parameter(scope=scopes.NODE_VIEW)
def document_version_details(
document_version_id: uuid.UUID,
user: Annotated[
schemas.User,
Security(get_current_user, scopes=[scopes.NODE_VIEW])
],
engine: db.Engine = Depends(db.get_engine)
):
"""Get document version details
Required scope: `{scope}`
"""
try:
doc_ver = db.get_doc_ver(
engine,
id=document_version_id,
user_id=user.id
)
except db_exc.PageNotFound:
raise HTTPException(
status_code=404,
detail="Page not found"
)

return doc_ver
6 changes: 5 additions & 1 deletion papermerge/core/routers/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ def get_page_jpg_url(
"""
try:
page = db.get_page(engine, id=page_id, user_id=user.id)
doc_ver = db.get_doc_ver(engine, id=page.document_version_id)
doc_ver = db.get_doc_ver(
engine,
id=page.document_version_id,
user_id=user.id
)
except db_exc.PageNotFound:
raise HTTPException(
status_code=404,
Expand Down
2 changes: 1 addition & 1 deletion ui2/src/app/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
}

main {
margin: 0.25rem;
margin: 0.5rem;
}
2 changes: 2 additions & 0 deletions ui2/src/components/Viewer/ActionButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import PanelContext from "@/contexts/PanelContext"
import EditTitleButton from "./EditTitleButton"

import type {PanelMode} from "@/types"
import DownloadButton from "./DownloadButton/DownloadButton"

export default function ActionButtons() {
const {height, width} = useViewportSize()
Expand All @@ -32,6 +33,7 @@ export default function ActionButtons() {
<Group ref={ref} justify="space-between">
<Group>
<EditTitleButton />
<DownloadButton />
</Group>
<Group>
<ToggleSecondaryPanel />
Expand Down
41 changes: 41 additions & 0 deletions ui2/src/components/Viewer/DownloadButton/DownloadButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {useContext} from "react"
import {useSelector} from "react-redux"
import {Menu, Tooltip, ActionIcon} from "@mantine/core"
import {IconDownload} from "@tabler/icons-react"
import {selectDocumentVersions} from "@/slices/dualPanel/dualPanel"
import {RootState} from "@/app/types"
import {download_file} from "@/httpClient"

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

export default function DownloadButton() {
const mode = useContext(PanelContext)

const vers = useSelector((state: RootState) =>
selectDocumentVersions(state, mode)
)

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

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

return (
<Menu>
<Menu.Target>
<Tooltip label="Download" withArrow>
<ActionIcon size={"lg"} variant="default">
<IconDownload stroke={1.4} />
</ActionIcon>
</Tooltip>
</Menu.Target>
<Menu.Dropdown>{versionComponents}</Menu.Dropdown>
</Menu>
)
}
Empty file.
47 changes: 47 additions & 0 deletions ui2/src/httpClient.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {DocumentVersion} from "@/types"
import {getBaseURL, getDefaultHeaders} from "@/utils"

import axios from "axios"
Expand All @@ -12,3 +13,49 @@ const client = axios.create({
client.defaults.headers.common = defaultHeaders

export default client

function get_file_ext(file_name: string): string {
var arr = file_name.split(".")
return arr.pop()!
}

function get_file_basename(file_name: string): string {
var arr = file_name.split(".")
arr.pop()
return arr.join(".")
}

/*
download_file of specific document version
First gets the current `download_url`, as in case of S3 storage
(S3 private storage actually) there expiration keys are
valid for couple of minutes only
*/
async function download_file(doc_ver: DocumentVersion) {
const resp1 = await client.get(`/api/document-versions/${doc_ver.id}`)
const v = resp1.data as DocumentVersion
// now, with `download_url` at hand, actual download starts!
v.download_url
const resp2 = await client.get(v.download_url, {responseType: "blob"})
const blob = resp2.data
const url = window.URL.createObjectURL(blob)
/*
Based on:
- stackoverflow.com/questions/32545632/how-can-i-download-a-file-using-window-fetch
- https://stackoverflow.com/a/78401145
*/
let a = document.createElement("a")
const ext = get_file_ext(v.file_name)
const basename = get_file_basename(v.file_name)

a.href = url
a.download = `${basename}-v${v.number}-${v.short_description}.${ext}`
// we need to append the element to the dom -> otherwise it will not work in firefox
document.body.appendChild(a)
a.click()
//afterwards we remove the element again
a.remove()
}

export {download_file}
3 changes: 2 additions & 1 deletion ui2/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ body {
padding-top: 1rem;
display: flex;
flex-direction: column;
background-color: #f9f9f9;
background-color: light-dark(#f9f9f9, black);
flex-grow: 1;
}

a {
Expand Down
4 changes: 2 additions & 2 deletions ui2/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,6 @@ async function getCurrentUser(): Promise<User> {
})
}

export {getCurrentUser}

export function makeRandomString(length: number): string {
let result = ""
const characters =
Expand All @@ -136,3 +134,5 @@ export function equalUUIDs(id1: string, id2: string): boolean {

return i1 == i2
}

export {getCurrentUser}

0 comments on commit d45682e

Please sign in to comment.