mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-09-30 08:38:22 +00:00
Initial version.
Features: - Upload PDF files let them analyze - Manage meta data and items - See processing in webapp
This commit is contained in:
91
modules/webapp/src/main/elm/Page/Upload/Data.elm
Normal file
91
modules/webapp/src/main/elm/Page/Upload/Data.elm
Normal file
@@ -0,0 +1,91 @@
|
||||
module Page.Upload.Data exposing (..)
|
||||
|
||||
import Http
|
||||
import Set exposing (Set)
|
||||
import File exposing (File)
|
||||
import Api.Model.BasicResult exposing (BasicResult)
|
||||
import Util.File exposing (makeFileId)
|
||||
import Comp.Dropzone
|
||||
|
||||
type alias Model =
|
||||
{ incoming: Bool
|
||||
, singleItem: Bool
|
||||
, files: List File
|
||||
, completed: Set String
|
||||
, errored: Set String
|
||||
, loading: Set String
|
||||
, dropzone: Comp.Dropzone.Model
|
||||
}
|
||||
|
||||
dropzoneSettings: Comp.Dropzone.Settings
|
||||
dropzoneSettings =
|
||||
let
|
||||
ds = Comp.Dropzone.defaultSettings
|
||||
in
|
||||
{ds | classList = (\m -> [("ui attached blue placeholder segment dropzone", True)
|
||||
,("dragging", m.hover)
|
||||
,("disabled", not m.active)
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
emptyModel: Model
|
||||
emptyModel =
|
||||
{ incoming = True
|
||||
, singleItem = False
|
||||
, files = []
|
||||
, completed = Set.empty
|
||||
, errored = Set.empty
|
||||
, loading = Set.empty
|
||||
, dropzone = Comp.Dropzone.init dropzoneSettings
|
||||
}
|
||||
|
||||
type Msg
|
||||
= SubmitUpload
|
||||
| SingleUploadResp String (Result Http.Error BasicResult)
|
||||
| GotProgress String Http.Progress
|
||||
| ToggleIncoming
|
||||
| ToggleSingleItem
|
||||
| Clear
|
||||
| DropzoneMsg Comp.Dropzone.Msg
|
||||
|
||||
|
||||
isLoading: Model -> File -> Bool
|
||||
isLoading model file =
|
||||
Set.member (makeFileId file)model.loading
|
||||
|
||||
isCompleted: Model -> File -> Bool
|
||||
isCompleted model file =
|
||||
Set.member (makeFileId file)model.completed
|
||||
|
||||
isError: Model -> File -> Bool
|
||||
isError model file =
|
||||
Set.member (makeFileId file) model.errored
|
||||
|
||||
isIdle: Model -> File -> Bool
|
||||
isIdle model file =
|
||||
not (isLoading model file || isCompleted model file || isError model file)
|
||||
|
||||
uploadAllTracker: String
|
||||
uploadAllTracker =
|
||||
"upload-all"
|
||||
|
||||
isInitial: Model -> Bool
|
||||
isInitial model =
|
||||
Set.isEmpty model.loading &&
|
||||
Set.isEmpty model.completed &&
|
||||
Set.isEmpty model.errored
|
||||
|
||||
isDone: Model -> Bool
|
||||
isDone model =
|
||||
List.map makeFileId model.files
|
||||
|> List.all (\id -> Set.member id model.completed || Set.member id model.errored)
|
||||
|
||||
isSuccessAll: Model -> Bool
|
||||
isSuccessAll model =
|
||||
List.map makeFileId model.files
|
||||
|> List.all (\id -> Set.member id model.completed)
|
||||
|
||||
hasErrors: Model -> Bool
|
||||
hasErrors model =
|
||||
not (Set.isEmpty model.errored)
|
94
modules/webapp/src/main/elm/Page/Upload/Update.elm
Normal file
94
modules/webapp/src/main/elm/Page/Upload/Update.elm
Normal file
@@ -0,0 +1,94 @@
|
||||
module Page.Upload.Update exposing (update)
|
||||
|
||||
import Api
|
||||
import Http
|
||||
import Set exposing (Set)
|
||||
import Page.Upload.Data exposing (..)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Comp.Dropzone
|
||||
import File
|
||||
import File.Select
|
||||
import Ports
|
||||
import Api.Model.ItemUploadMeta
|
||||
import Util.File exposing (makeFileId)
|
||||
import Util.Http
|
||||
|
||||
update: (Maybe String) -> Flags -> Msg -> Model -> (Model, Cmd Msg, Sub Msg)
|
||||
update sourceId flags msg model =
|
||||
case msg of
|
||||
ToggleIncoming ->
|
||||
({model|incoming = not model.incoming}, Cmd.none, Sub.none)
|
||||
|
||||
ToggleSingleItem ->
|
||||
({model|singleItem = not model.singleItem}, Cmd.none, Sub.none)
|
||||
|
||||
SubmitUpload ->
|
||||
let
|
||||
emptyMeta = Api.Model.ItemUploadMeta.empty
|
||||
meta = {emptyMeta | multiple = not model.singleItem
|
||||
, direction = if model.incoming then Just "incoming" else Just "outgoing"
|
||||
}
|
||||
fileids = List.map makeFileId model.files
|
||||
uploads = if model.singleItem then Api.uploadSingle flags sourceId meta uploadAllTracker model.files (SingleUploadResp uploadAllTracker)
|
||||
else Cmd.batch (Api.upload flags sourceId meta model.files SingleUploadResp)
|
||||
tracker = if model.singleItem then Http.track uploadAllTracker (GotProgress uploadAllTracker)
|
||||
else Sub.batch <| List.map (\id -> Http.track id (GotProgress id)) fileids
|
||||
(cm2, _, _) = Comp.Dropzone.update (Comp.Dropzone.setActive False) model.dropzone
|
||||
in
|
||||
({model|loading = Set.fromList fileids, dropzone = cm2}, uploads, tracker)
|
||||
|
||||
SingleUploadResp fileid (Ok res) ->
|
||||
let
|
||||
compl = if res.success then setCompleted model fileid
|
||||
else model.completed
|
||||
errs = if not res.success then setErrored model fileid
|
||||
else model.errored
|
||||
load = if fileid == uploadAllTracker then Set.empty
|
||||
else Set.remove fileid model.loading
|
||||
in
|
||||
({model|completed = compl, errored = errs, loading = load}
|
||||
, Ports.setProgress (fileid, 100), Sub.none
|
||||
)
|
||||
|
||||
SingleUploadResp fileid (Err err) ->
|
||||
let
|
||||
_ = Debug.log "error" err
|
||||
errs = setErrored model fileid
|
||||
load = if fileid == uploadAllTracker then Set.empty
|
||||
else Set.remove fileid model.loading
|
||||
in
|
||||
({model|errored = errs, loading = load}, Cmd.none, Sub.none)
|
||||
|
||||
GotProgress fileid progress ->
|
||||
let
|
||||
percent = case progress of
|
||||
Http.Sending p ->
|
||||
Http.fractionSent p
|
||||
|> (*) 100
|
||||
|> round
|
||||
_ -> 0
|
||||
updateBars = if percent == 0 then Cmd.none
|
||||
else if model.singleItem then Ports.setAllProgress (uploadAllTracker, percent)
|
||||
else Ports.setProgress (fileid, percent)
|
||||
in
|
||||
(model, updateBars, Sub.none)
|
||||
|
||||
Clear ->
|
||||
(emptyModel, Cmd.none, Sub.none)
|
||||
|
||||
DropzoneMsg m ->
|
||||
let
|
||||
(m2, c2, files) = Comp.Dropzone.update m model.dropzone
|
||||
nextFiles = List.append model.files files
|
||||
in
|
||||
({model| files = nextFiles, dropzone = m2}, Cmd.map DropzoneMsg c2, Sub.none)
|
||||
|
||||
setCompleted: Model -> String -> Set String
|
||||
setCompleted model fileid =
|
||||
if fileid == uploadAllTracker then List.map makeFileId model.files |> Set.fromList
|
||||
else Set.insert fileid model.completed
|
||||
|
||||
setErrored: Model -> String -> Set String
|
||||
setErrored model fileid =
|
||||
if fileid == uploadAllTracker then List.map makeFileId model.files |> Set.fromList
|
||||
else Set.insert fileid model.errored
|
166
modules/webapp/src/main/elm/Page/Upload/View.elm
Normal file
166
modules/webapp/src/main/elm/Page/Upload/View.elm
Normal file
@@ -0,0 +1,166 @@
|
||||
module Page.Upload.View exposing (view)
|
||||
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick, onCheck)
|
||||
import Comp.Dropzone
|
||||
import File exposing (File)
|
||||
import Page exposing (Page(..))
|
||||
import Page.Upload.Data exposing (..)
|
||||
import Util.File exposing (makeFileId)
|
||||
import Util.Maybe
|
||||
import Util.Size
|
||||
|
||||
view: (Maybe String) -> Model -> Html Msg
|
||||
view mid model =
|
||||
div [class "upload-page ui grid container"]
|
||||
[div [class "row"]
|
||||
[div [class "sixteen wide column"]
|
||||
[div [class "ui top attached segment"]
|
||||
[renderForm model
|
||||
]
|
||||
,Html.map DropzoneMsg (Comp.Dropzone.view model.dropzone)
|
||||
,div [class "ui bottom attached segment"]
|
||||
[a [class "ui primary button", href "", onClick SubmitUpload]
|
||||
[text "Submit"
|
||||
]
|
||||
,a [class "ui secondary button", href "", onClick Clear]
|
||||
[text "Reset"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
,if isDone model && hasErrors model then renderErrorMsg model
|
||||
else span[class "invisible"][]
|
||||
,if List.isEmpty model.files then span[][]
|
||||
else if isSuccessAll model then renderSuccessMsg (Util.Maybe.nonEmpty mid) model
|
||||
else renderUploads model
|
||||
]
|
||||
|
||||
|
||||
renderErrorMsg: Model -> Html Msg
|
||||
renderErrorMsg model =
|
||||
div [class "row"]
|
||||
[div [class "sixteen wide column"]
|
||||
[div [class "ui large error message"]
|
||||
[h3 [class "ui header"]
|
||||
[i [class "meh outline icon"][]
|
||||
,text "Some files failed to upload"
|
||||
]
|
||||
,text "There were errors uploading some files."
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
renderSuccessMsg: Bool -> Model -> Html Msg
|
||||
renderSuccessMsg public model =
|
||||
div [class "row"]
|
||||
[div [class "sixteen wide column"]
|
||||
[div [class "ui large success message"]
|
||||
[h3 [class "ui header"]
|
||||
[i [class "smile outline icon"][]
|
||||
,text "All files uploaded"
|
||||
]
|
||||
,if public then p [][] else p []
|
||||
[text "Your files have been successfully uploaded. They are now being processed. Check the "
|
||||
,a [class "ui link", Page.href HomePage]
|
||||
[text "Items page"
|
||||
]
|
||||
,text " later where the files will arrive eventually. Or go to the "
|
||||
,a [class "ui link", Page.href QueuePage]
|
||||
[text "Processing Page"
|
||||
]
|
||||
,text " to view the current processing state."
|
||||
]
|
||||
,p []
|
||||
[text "Click "
|
||||
,a [class "ui link", href "", onClick Clear]
|
||||
[text "Reset"
|
||||
]
|
||||
,text " to upload more files."
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
renderUploads: Model -> Html Msg
|
||||
renderUploads model =
|
||||
div [class "row"]
|
||||
[div [class "sixteen wide column"]
|
||||
[div [class "ui basic segment"]
|
||||
[h2 [class "ui header"]
|
||||
[text "Selected Files"
|
||||
]
|
||||
,div [class "ui items"] <|
|
||||
if model.singleItem then
|
||||
(List.map (renderFileItem model (Just uploadAllTracker)) model.files)
|
||||
else
|
||||
(List.map (renderFileItem model Nothing) model.files)
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
renderFileItem: Model -> Maybe String -> File -> Html Msg
|
||||
renderFileItem model mtracker file =
|
||||
let
|
||||
name = File.name file
|
||||
size = File.size file
|
||||
|> toFloat
|
||||
|> Util.Size.bytesReadable Util.Size.B
|
||||
in
|
||||
div [class "item"]
|
||||
[i [classList [("large", True)
|
||||
,("file outline icon", isIdle model file)
|
||||
,("loading spinner icon", isLoading model file)
|
||||
,("green check icon", isCompleted model file)
|
||||
,("red bolt icon", isError model file)
|
||||
]][]
|
||||
,div [class "middle aligned content"]
|
||||
[div [class "header"]
|
||||
[text name
|
||||
]
|
||||
,div [class "right floated meta"]
|
||||
[text size
|
||||
]
|
||||
,div [class "description"]
|
||||
[div [classList [("ui small indicating progress", True)
|
||||
,(uploadAllTracker, Util.Maybe.nonEmpty mtracker)
|
||||
]
|
||||
, id (makeFileId file)
|
||||
]
|
||||
[div [class "bar"]
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
renderForm: Model -> Html Msg
|
||||
renderForm model =
|
||||
div [class "row"]
|
||||
[Html.form [class "ui form"]
|
||||
[div [class "grouped fields"]
|
||||
[div [class "field"]
|
||||
[div [class "ui radio checkbox"]
|
||||
[input [type_ "radio", checked model.incoming, onCheck (\_ ->ToggleIncoming)][]
|
||||
,label [][text "Incoming"]
|
||||
]
|
||||
]
|
||||
,div [class "field"]
|
||||
[div [class "ui radio checkbox"]
|
||||
[input [type_ "radio", checked (not model.incoming), onCheck (\_ -> ToggleIncoming)][]
|
||||
,label [][text "Outgoing"]
|
||||
]
|
||||
]
|
||||
]
|
||||
,div [class "inline field"]
|
||||
[div [class "ui checkbox"]
|
||||
[input [type_ "checkbox", checked model.singleItem, onCheck (\_ -> ToggleSingleItem)][]
|
||||
,label [][text "All files are one single item"]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
Reference in New Issue
Block a user