From 67f1575707cd3d8a675a6f5dc600d49167a31e90 Mon Sep 17 00:00:00 2001 From: eikek Date: Wed, 26 Jan 2022 21:22:35 +0100 Subject: [PATCH] Create upload component --- modules/webapp/src/main/elm/App/Update.elm | 2 +- .../webapp/src/main/elm/Comp/UploadForm.elm | 555 ++++++++++++++++++ .../src/main/elm/Messages/Comp/UploadForm.elm | 91 +++ .../src/main/elm/Messages/Page/Upload.elm | 79 +-- .../webapp/src/main/elm/Page/Upload/Data.elm | 95 +-- .../src/main/elm/Page/Upload/Update.elm | 180 +----- .../webapp/src/main/elm/Page/Upload/View2.elm | 273 +-------- 7 files changed, 668 insertions(+), 607 deletions(-) create mode 100644 modules/webapp/src/main/elm/Comp/UploadForm.elm create mode 100644 modules/webapp/src/main/elm/Messages/Comp/UploadForm.elm diff --git a/modules/webapp/src/main/elm/App/Update.elm b/modules/webapp/src/main/elm/App/Update.elm index 394f87e0..2a342dec 100644 --- a/modules/webapp/src/main/elm/App/Update.elm +++ b/modules/webapp/src/main/elm/App/Update.elm @@ -669,7 +669,7 @@ initPage model_ page = UploadPage _ -> Util.Update.andThen2 [ updateQueue Page.Queue.Data.StopRefresh - , updateUpload Page.Upload.Data.Clear + , updateUpload Page.Upload.Data.reset ] model diff --git a/modules/webapp/src/main/elm/Comp/UploadForm.elm b/modules/webapp/src/main/elm/Comp/UploadForm.elm new file mode 100644 index 00000000..b96ce99c --- /dev/null +++ b/modules/webapp/src/main/elm/Comp/UploadForm.elm @@ -0,0 +1,555 @@ +module Comp.UploadForm exposing (Model, Msg, init, reset, update, view) + +import Api +import Api.Model.BasicResult exposing (BasicResult) +import Api.Model.ItemUploadMeta +import Comp.Dropzone +import Comp.FixedDropdown +import Comp.Progress +import Data.DropdownStyle as DS +import Data.Flags exposing (Flags) +import Data.Language exposing (Language) +import Data.UiSettings exposing (UiSettings) +import Dict exposing (Dict) +import File exposing (File) +import Html exposing (Html, a, div, h2, h3, i, input, label, p, span, text) +import Html.Attributes exposing (action, checked, class, classList, href, id, type_) +import Html.Events exposing (onCheck, onClick) +import Http +import Messages.Comp.UploadForm exposing (Texts) +import Page exposing (Page(..)) +import Set exposing (Set) +import Styles +import Util.File exposing (makeFileId) +import Util.Maybe +import Util.Size + + +type alias Model = + { incoming : Bool + , singleItem : Bool + , files : List File + , completed : Set String + , errored : Set String + , loading : Dict String Int + , dropzone : Comp.Dropzone.Model + , skipDuplicates : Bool + , languageModel : Comp.FixedDropdown.Model Language + , language : Maybe Language + } + + +type Msg + = SubmitUpload + | SingleUploadResp String (Result Http.Error BasicResult) + | GotProgress String Http.Progress + | ToggleIncoming + | ToggleSingleItem + | Clear + | DropzoneMsg Comp.Dropzone.Msg + | ToggleSkipDuplicates + | LanguageMsg (Comp.FixedDropdown.Msg Language) + + +init : Model +init = + { incoming = True + , singleItem = False + , files = [] + , completed = Set.empty + , errored = Set.empty + , loading = Dict.empty + , dropzone = Comp.Dropzone.init [] + , skipDuplicates = True + , languageModel = + Comp.FixedDropdown.init Data.Language.all + , language = Nothing + } + + +reset : Msg +reset = + Clear + + +isLoading : Model -> File -> Bool +isLoading model file = + Dict.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" + + +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) + + + +--- Update + + +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 ) + + ToggleSkipDuplicates -> + ( { model | skipDuplicates = not model.skipDuplicates }, Cmd.none, Sub.none ) + + SubmitUpload -> + let + emptyMeta = + Api.Model.ItemUploadMeta.empty + + meta = + { emptyMeta + | multiple = not model.singleItem + , skipDuplicates = Just model.skipDuplicates + , direction = + if model.incoming then + Just "incoming" + + else + Just "outgoing" + , language = Maybe.map Data.Language.toIso3 model.language + } + + 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 + + nowLoading = + List.map (\fid -> ( fid, 0 )) fileids + |> Dict.fromList + in + ( { model | loading = nowLoading, 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 + Dict.empty + + else + Dict.remove fileid model.loading + in + ( { model | completed = compl, errored = errs, loading = load } + , Cmd.none + , Sub.none + ) + + SingleUploadResp fileid (Err _) -> + let + errs = + setErrored model fileid + + load = + if fileid == uploadAllTracker then + Dict.empty + + else + Dict.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 + + newLoading = + if model.singleItem then + Dict.insert uploadAllTracker percent model.loading + + else + Dict.insert fileid percent model.loading + in + ( { model | loading = newLoading } + , Cmd.none + , Sub.none + ) + + Clear -> + ( init, 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 ) + + LanguageMsg lm -> + let + ( dm, sel ) = + Comp.FixedDropdown.update lm model.languageModel + in + ( { model + | languageModel = dm + , language = Util.Maybe.or [ sel, model.language ] + } + , Cmd.none + , 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 + + + +--- View + + +view : Texts -> Maybe String -> Flags -> UiSettings -> Model -> Html Msg +view texts mid _ _ model = + div + [ id "content" + , class Styles.content + ] + [ div [ class "container mx-auto" ] + [ div [ class "px-0 flex flex-col" ] + [ div [ class "py-4" ] + [ if mid == Nothing then + renderForm texts model + + else + span [ class "hidden" ] [] + ] + , div [ class "py-0" ] + [ Html.map DropzoneMsg + (Comp.Dropzone.view2 texts.dropzone model.dropzone) + ] + , div [ class "py-4" ] + [ a + [ class Styles.primaryButton + , href "#" + , onClick SubmitUpload + ] + [ text texts.basics.submit + ] + , a + [ class Styles.secondaryButton + , class "ml-2" + , href "#" + , onClick Clear + ] + [ text texts.reset + ] + ] + ] + , renderErrorMsg texts model + , renderSuccessMsg texts (Util.Maybe.nonEmpty mid) model + , renderUploads texts model + ] + ] + + +renderForm : Texts -> Model -> Html Msg +renderForm texts model = + let + languageCfg = + { display = texts.languageLabel + , icon = \_ -> Nothing + , style = DS.mainStyleWith "w-40" + , selectPlaceholder = texts.basics.selectPlaceholder + } + in + div [ class "row" ] + [ Html.form [ action "#" ] + [ div [ class "flex flex-col mb-3" ] + [ label [ class "inline-flex items-center" ] + [ input + [ type_ "radio" + , checked model.incoming + , onCheck (\_ -> ToggleIncoming) + , class Styles.radioInput + ] + [] + , span [ class "ml-2" ] [ text texts.basics.incoming ] + ] + , label [ class "inline-flex items-center" ] + [ input + [ type_ "radio" + , checked (not model.incoming) + , onCheck (\_ -> ToggleIncoming) + , class Styles.radioInput + ] + [] + , span [ class "ml-2" ] [ text texts.basics.outgoing ] + ] + ] + , div [ class "flex flex-col mb-3" ] + [ label [ class "inline-flex items-center" ] + [ input + [ type_ "checkbox" + , checked model.singleItem + , onCheck (\_ -> ToggleSingleItem) + , class Styles.checkboxInput + ] + [] + , span [ class "ml-2" ] + [ text texts.allFilesOneItem + ] + ] + ] + , div [ class "flex flex-col mb-3" ] + [ label [ class "inline-flex items-center" ] + [ input + [ type_ "checkbox" + , checked model.skipDuplicates + , onCheck (\_ -> ToggleSkipDuplicates) + , class Styles.checkboxInput + ] + [] + , span [ class "ml-2" ] + [ text texts.skipExistingFiles + ] + ] + ] + , div [ class "flex flex-col mb-3" ] + [ label [ class "inline-flex items-center mb-2" ] + [ span [ class "mr-2" ] [ text (texts.language ++ ":") ] + , Html.map LanguageMsg + (Comp.FixedDropdown.viewStyled2 + languageCfg + False + model.language + model.languageModel + ) + ] + , div [ class "text-gray-400 text-xs" ] + [ text texts.languageInfo + ] + ] + ] + ] + + +renderErrorMsg : Texts -> Model -> Html Msg +renderErrorMsg texts model = + div + [ class "row" + , classList [ ( "hidden", not (isDone model && hasErrors model) ) ] + ] + [ div [ class "mt-4" ] + [ div [ class Styles.errorMessage ] + [ text texts.uploadErrorMessage + ] + ] + ] + + +renderSuccessMsg : Texts -> Bool -> Model -> Html Msg +renderSuccessMsg texts public model = + div + [ class "row" + , classList [ ( "hidden", List.isEmpty model.files || not (isSuccessAll model) ) ] + ] + [ div [ class "mt-4" ] + [ div [ class Styles.successMessage ] + [ h3 [ class Styles.header2, class "text-green-800 dark:text-lime-800" ] + [ i [ class "fa fa-smile font-thin" ] [] + , span [ class "ml-2" ] + [ text texts.successBox.allFilesUploaded + ] + ] + , p + [ classList [ ( "hidden", public ) ] + ] + [ text texts.successBox.line1 + , a + [ class Styles.successMessageLink + , Page.href (SearchPage Nothing) + ] + [ text texts.successBox.itemsPage + ] + , text texts.successBox.line2 + , a + [ class Styles.successMessageLink + , Page.href QueuePage + ] + [ text texts.successBox.processingPage + ] + , text texts.successBox.line3 + ] + , p [] + [ text texts.successBox.resetLine1 + , a + [ class Styles.successMessageLink + , href "#" + , onClick Clear + ] + [ text texts.successBox.reset + ] + , text texts.successBox.resetLine2 + ] + ] + ] + ] + + +renderUploads : Texts -> Model -> Html Msg +renderUploads texts model = + div + [ class "mt-4" + , classList [ ( "hidden", List.isEmpty model.files || isSuccessAll model ) ] + ] + [ h2 [ class Styles.header2 ] + [ text texts.selectedFiles + , text (" (" ++ (List.length model.files |> String.fromInt) ++ ")") + ] + , div [] <| + if model.singleItem then + List.map (renderFileItem model (Just uploadAllTracker)) model.files + + else + List.map (renderFileItem model Nothing) model.files + ] + + +getProgress : Model -> File -> Int +getProgress model file = + let + key = + if model.singleItem then + uploadAllTracker + + else + makeFileId file + in + Dict.get key model.loading + |> Maybe.withDefault 0 + + +renderFileItem : Model -> Maybe String -> File -> Html Msg +renderFileItem model _ file = + let + name = + File.name file + + size = + File.size file + |> toFloat + |> Util.Size.bytesReadable Util.Size.B + in + div [ class "flex flex-col w-full mb-4" ] + [ div [ class "flex flex-row items-center" ] + [ div [ class "inline-flex items-center" ] + [ i + [ classList + [ ( "mr-2 text-lg", True ) + , ( "fa fa-file font-thin", isIdle model file ) + , ( "fa fa-spinner animate-spin ", isLoading model file ) + , ( "fa fa-check ", isCompleted model file ) + , ( "fa fa-bolt", isError model file ) + ] + ] + [] + , div [ class "middle aligned content" ] + [ div [ class "header" ] + [ text name + ] + ] + ] + , div [ class "flex-grow inline-flex justify-end" ] + [ text size + ] + ] + , div [ class "h-4" ] + [ Comp.Progress.progress2 (getProgress model file) + ] + ] diff --git a/modules/webapp/src/main/elm/Messages/Comp/UploadForm.elm b/modules/webapp/src/main/elm/Messages/Comp/UploadForm.elm new file mode 100644 index 00000000..43efc7d1 --- /dev/null +++ b/modules/webapp/src/main/elm/Messages/Comp/UploadForm.elm @@ -0,0 +1,91 @@ +module Messages.Comp.UploadForm exposing (Texts, de, gb) + +import Data.Language exposing (Language) +import Messages.Basics +import Messages.Comp.Dropzone +import Messages.Data.Language + + +type alias Texts = + { basics : Messages.Basics.Texts + , dropzone : Messages.Comp.Dropzone.Texts + , reset : String + , allFilesOneItem : String + , skipExistingFiles : String + , language : String + , languageInfo : String + , uploadErrorMessage : String + , successBox : + { allFilesUploaded : String + , line1 : String + , itemsPage : String + , line2 : String + , processingPage : String + , line3 : String + , resetLine1 : String + , reset : String + , resetLine2 : String + } + , selectedFiles : String + , languageLabel : Language -> String + } + + +gb : Texts +gb = + { basics = Messages.Basics.gb + , dropzone = Messages.Comp.Dropzone.gb + , reset = "Reset" + , allFilesOneItem = "All files are one single item" + , skipExistingFiles = "Skip files already present in docspell" + , language = "Language" + , languageInfo = + "Used for text extraction and analysis. The collective's " + ++ "default language is used if not specified here." + , uploadErrorMessage = "There were errors uploading some files." + , successBox = + { allFilesUploaded = "All files uploaded" + , line1 = + "Your files have been successfully uploaded. " + ++ "They are now being processed. Check the " + , itemsPage = "Items Page" + , line2 = " later where the files will arrive eventually. Or go to the " + , processingPage = "Processing Page" + , line3 = " to view the current processing state." + , resetLine1 = " Click " + , reset = "Reset" + , resetLine2 = " to upload more files." + } + , selectedFiles = "Selected Files" + , languageLabel = Messages.Data.Language.gb + } + + +de : Texts +de = + { basics = Messages.Basics.de + , dropzone = Messages.Comp.Dropzone.de + , reset = "Zurücksetzen" + , allFilesOneItem = "Alle Dateien sind ein Dokument" + , skipExistingFiles = "Lasse Dateien aus, die schon in Docspell sind" + , language = "Sprache" + , languageInfo = + "Wird für Texterkennung und -analyse verwendet. Die Standardsprache des Kollektivs " + ++ "wird verwendet, falls hier nicht angegeben." + , uploadErrorMessage = "Es gab Fehler beim Hochladen der Dateien." + , successBox = + { allFilesUploaded = "Alle Dateien hochgeladen" + , line1 = + "Deine Dateien wurden erfolgreich hochgeladen und sie werden nun verarbeitet. " + ++ "Gehe nachher zur " + , itemsPage = "Hauptseite" + , line2 = " wo die Dateien als Dokumente erscheinen werden oder gehe zur " + , processingPage = "Verarbeitungsseite," + , line3 = " welche einen Einblick in den aktuellen Status gibt." + , resetLine1 = " Klicke " + , reset = "Zurücksetzen" + , resetLine2 = " um weitere Dateien hochzuladen." + } + , selectedFiles = "Ausgewählte Dateien" + , languageLabel = Messages.Data.Language.de + } diff --git a/modules/webapp/src/main/elm/Messages/Page/Upload.elm b/modules/webapp/src/main/elm/Messages/Page/Upload.elm index 6f4d3973..b694403b 100644 --- a/modules/webapp/src/main/elm/Messages/Page/Upload.elm +++ b/modules/webapp/src/main/elm/Messages/Page/Upload.elm @@ -11,92 +11,21 @@ module Messages.Page.Upload exposing , gb ) -import Data.Language exposing (Language) -import Messages.Basics -import Messages.Comp.Dropzone -import Messages.Data.Language +import Messages.Comp.UploadForm type alias Texts = - { basics : Messages.Basics.Texts - , dropzone : Messages.Comp.Dropzone.Texts - , reset : String - , allFilesOneItem : String - , skipExistingFiles : String - , language : String - , languageInfo : String - , uploadErrorMessage : String - , successBox : - { allFilesUploaded : String - , line1 : String - , itemsPage : String - , line2 : String - , processingPage : String - , line3 : String - , resetLine1 : String - , reset : String - , resetLine2 : String - } - , selectedFiles : String - , languageLabel : Language -> String + { uploadForm : Messages.Comp.UploadForm.Texts } gb : Texts gb = - { basics = Messages.Basics.gb - , dropzone = Messages.Comp.Dropzone.gb - , reset = "Reset" - , allFilesOneItem = "All files are one single item" - , skipExistingFiles = "Skip files already present in docspell" - , language = "Language" - , languageInfo = - "Used for text extraction and analysis. The collective's " - ++ "default language is used if not specified here." - , uploadErrorMessage = "There were errors uploading some files." - , successBox = - { allFilesUploaded = "All files uploaded" - , line1 = - "Your files have been successfully uploaded. " - ++ "They are now being processed. Check the " - , itemsPage = "Items Page" - , line2 = " later where the files will arrive eventually. Or go to the " - , processingPage = "Processing Page" - , line3 = " to view the current processing state." - , resetLine1 = " Click " - , reset = "Reset" - , resetLine2 = " to upload more files." - } - , selectedFiles = "Selected Files" - , languageLabel = Messages.Data.Language.gb + { uploadForm = Messages.Comp.UploadForm.gb } de : Texts de = - { basics = Messages.Basics.de - , dropzone = Messages.Comp.Dropzone.de - , reset = "Zurücksetzen" - , allFilesOneItem = "Alle Dateien sind ein Dokument" - , skipExistingFiles = "Lasse Dateien aus, die schon in Docspell sind" - , language = "Sprache" - , languageInfo = - "Wird für Texterkennung und -analyse verwendet. Die Standardsprache des Kollektivs " - ++ "wird verwendet, falls hier nicht angegeben." - , uploadErrorMessage = "Es gab Fehler beim Hochladen der Dateien." - , successBox = - { allFilesUploaded = "Alle Dateien hochgeladen" - , line1 = - "Deine Dateien wurden erfolgreich hochgeladen und sie werden nun verarbeitet. " - ++ "Gehe nachher zur " - , itemsPage = "Hauptseite" - , line2 = " wo die Dateien als Dokumente erscheinen werden oder gehe zur " - , processingPage = "Verarbeitungsseite," - , line3 = " welche einen Einblick in den aktuellen Status gibt." - , resetLine1 = " Klicke " - , reset = "Zurücksetzen" - , resetLine2 = " um weitere Dateien hochzuladen." - } - , selectedFiles = "Ausgewählte Dateien" - , languageLabel = Messages.Data.Language.de + { uploadForm = Messages.Comp.UploadForm.de } diff --git a/modules/webapp/src/main/elm/Page/Upload/Data.elm b/modules/webapp/src/main/elm/Page/Upload/Data.elm index 0c72ca82..db41be9a 100644 --- a/modules/webapp/src/main/elm/Page/Upload/Data.elm +++ b/modules/webapp/src/main/elm/Page/Upload/Data.elm @@ -9,106 +9,27 @@ module Page.Upload.Data exposing ( Model , Msg(..) , emptyModel - , hasErrors - , isCompleted - , isDone - , isError - , isIdle - , isLoading - , isSuccessAll - , uploadAllTracker + , reset ) -import Api.Model.BasicResult exposing (BasicResult) -import Comp.Dropzone -import Comp.FixedDropdown -import Data.Language exposing (Language) -import Dict exposing (Dict) -import File exposing (File) -import Http -import Set exposing (Set) -import Util.File exposing (makeFileId) +import Comp.UploadForm type alias Model = - { incoming : Bool - , singleItem : Bool - , files : List File - , completed : Set String - , errored : Set String - , loading : Dict String Int - , dropzone : Comp.Dropzone.Model - , skipDuplicates : Bool - , languageModel : Comp.FixedDropdown.Model Language - , language : Maybe Language + { uploadForm : Comp.UploadForm.Model } emptyModel : Model emptyModel = - { incoming = True - , singleItem = False - , files = [] - , completed = Set.empty - , errored = Set.empty - , loading = Dict.empty - , dropzone = Comp.Dropzone.init [] - , skipDuplicates = True - , languageModel = - Comp.FixedDropdown.init Data.Language.all - , language = Nothing + { uploadForm = Comp.UploadForm.init } type Msg - = SubmitUpload - | SingleUploadResp String (Result Http.Error BasicResult) - | GotProgress String Http.Progress - | ToggleIncoming - | ToggleSingleItem - | Clear - | DropzoneMsg Comp.Dropzone.Msg - | ToggleSkipDuplicates - | LanguageMsg (Comp.FixedDropdown.Msg Language) + = UploadMsg Comp.UploadForm.Msg -isLoading : Model -> File -> Bool -isLoading model file = - Dict.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" - - -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) +reset : Msg +reset = + UploadMsg Comp.UploadForm.reset diff --git a/modules/webapp/src/main/elm/Page/Upload/Update.elm b/modules/webapp/src/main/elm/Page/Upload/Update.elm index 8142f1ad..74d67819 100644 --- a/modules/webapp/src/main/elm/Page/Upload/Update.elm +++ b/modules/webapp/src/main/elm/Page/Upload/Update.elm @@ -7,187 +7,17 @@ module Page.Upload.Update exposing (update) -import Api -import Api.Model.ItemUploadMeta -import Comp.Dropzone -import Comp.FixedDropdown +import Comp.UploadForm import Data.Flags exposing (Flags) -import Data.Language -import Dict -import Http import Page.Upload.Data exposing (..) -import Set exposing (Set) -import Util.File exposing (makeFileId) -import Util.Maybe 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 ) - - ToggleSkipDuplicates -> - ( { model | skipDuplicates = not model.skipDuplicates }, Cmd.none, Sub.none ) - - SubmitUpload -> + UploadMsg lm -> let - emptyMeta = - Api.Model.ItemUploadMeta.empty - - meta = - { emptyMeta - | multiple = not model.singleItem - , skipDuplicates = Just model.skipDuplicates - , direction = - if model.incoming then - Just "incoming" - - else - Just "outgoing" - , language = Maybe.map Data.Language.toIso3 model.language - } - - 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 - - nowLoading = - List.map (\fid -> ( fid, 0 )) fileids - |> Dict.fromList + ( um, uc, us ) = + Comp.UploadForm.update sourceId flags lm model.uploadForm in - ( { model | loading = nowLoading, 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 - Dict.empty - - else - Dict.remove fileid model.loading - in - ( { model | completed = compl, errored = errs, loading = load } - , Cmd.none - , Sub.none - ) - - SingleUploadResp fileid (Err _) -> - let - errs = - setErrored model fileid - - load = - if fileid == uploadAllTracker then - Dict.empty - - else - Dict.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 - - newLoading = - if model.singleItem then - Dict.insert uploadAllTracker percent model.loading - - else - Dict.insert fileid percent model.loading - in - ( { model | loading = newLoading } - , Cmd.none - , 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 ) - - LanguageMsg lm -> - let - ( dm, sel ) = - Comp.FixedDropdown.update lm model.languageModel - in - ( { model - | languageModel = dm - , language = Util.Maybe.or [ sel, model.language ] - } - , Cmd.none - , 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 + ( { model | uploadForm = um }, Cmd.map UploadMsg uc, Sub.map UploadMsg us ) diff --git a/modules/webapp/src/main/elm/Page/Upload/View2.elm b/modules/webapp/src/main/elm/Page/Upload/View2.elm index 3d67e9fe..1f6a2224 100644 --- a/modules/webapp/src/main/elm/Page/Upload/View2.elm +++ b/modules/webapp/src/main/elm/Page/Upload/View2.elm @@ -7,24 +7,14 @@ module Page.Upload.View2 exposing (viewContent, viewSidebar) -import Comp.Dropzone -import Comp.FixedDropdown -import Comp.Progress -import Data.DropdownStyle as DS +import Comp.UploadForm import Data.Flags exposing (Flags) import Data.UiSettings exposing (UiSettings) -import Dict -import File exposing (File) import Html exposing (..) import Html.Attributes exposing (..) -import Html.Events exposing (onCheck, onClick) import Messages.Page.Upload exposing (Texts) import Page exposing (Page(..)) import Page.Upload.Data exposing (..) -import Styles as S -import Util.File exposing (makeFileId) -import Util.Maybe -import Util.Size viewSidebar : Maybe String -> Bool -> Flags -> UiSettings -> Model -> Html Msg @@ -37,261 +27,6 @@ viewSidebar _ _ _ _ _ = viewContent : Texts -> Maybe String -> Flags -> UiSettings -> Model -> Html Msg -viewContent texts mid _ _ model = - div - [ id "content" - , class S.content - ] - [ div [ class "container mx-auto" ] - [ div [ class "px-0 flex flex-col" ] - [ div [ class "py-4" ] - [ if mid == Nothing then - renderForm texts model - - else - span [ class "hidden" ] [] - ] - , div [ class "py-0" ] - [ Html.map DropzoneMsg - (Comp.Dropzone.view2 texts.dropzone model.dropzone) - ] - , div [ class "py-4" ] - [ a - [ class S.primaryButton - , href "#" - , onClick SubmitUpload - ] - [ text texts.basics.submit - ] - , a - [ class S.secondaryButton - , class "ml-2" - , href "#" - , onClick Clear - ] - [ text texts.reset - ] - ] - ] - , renderErrorMsg texts model - , renderSuccessMsg texts (Util.Maybe.nonEmpty mid) model - , renderUploads texts model - ] - ] - - -renderForm : Texts -> Model -> Html Msg -renderForm texts model = - let - languageCfg = - { display = texts.languageLabel - , icon = \_ -> Nothing - , style = DS.mainStyleWith "w-40" - , selectPlaceholder = texts.basics.selectPlaceholder - } - in - div [ class "row" ] - [ Html.form [ action "#" ] - [ div [ class "flex flex-col mb-3" ] - [ label [ class "inline-flex items-center" ] - [ input - [ type_ "radio" - , checked model.incoming - , onCheck (\_ -> ToggleIncoming) - , class S.radioInput - ] - [] - , span [ class "ml-2" ] [ text texts.basics.incoming ] - ] - , label [ class "inline-flex items-center" ] - [ input - [ type_ "radio" - , checked (not model.incoming) - , onCheck (\_ -> ToggleIncoming) - , class S.radioInput - ] - [] - , span [ class "ml-2" ] [ text texts.basics.outgoing ] - ] - ] - , div [ class "flex flex-col mb-3" ] - [ label [ class "inline-flex items-center" ] - [ input - [ type_ "checkbox" - , checked model.singleItem - , onCheck (\_ -> ToggleSingleItem) - , class S.checkboxInput - ] - [] - , span [ class "ml-2" ] - [ text texts.allFilesOneItem - ] - ] - ] - , div [ class "flex flex-col mb-3" ] - [ label [ class "inline-flex items-center" ] - [ input - [ type_ "checkbox" - , checked model.skipDuplicates - , onCheck (\_ -> ToggleSkipDuplicates) - , class S.checkboxInput - ] - [] - , span [ class "ml-2" ] - [ text texts.skipExistingFiles - ] - ] - ] - , div [ class "flex flex-col mb-3" ] - [ label [ class "inline-flex items-center mb-2" ] - [ span [ class "mr-2" ] [ text (texts.language ++ ":") ] - , Html.map LanguageMsg - (Comp.FixedDropdown.viewStyled2 - languageCfg - False - model.language - model.languageModel - ) - ] - , div [ class "text-gray-400 text-xs" ] - [ text texts.languageInfo - ] - ] - ] - ] - - -renderErrorMsg : Texts -> Model -> Html Msg -renderErrorMsg texts model = - div - [ class "row" - , classList [ ( "hidden", not (isDone model && hasErrors model) ) ] - ] - [ div [ class "mt-4" ] - [ div [ class S.errorMessage ] - [ text texts.uploadErrorMessage - ] - ] - ] - - -renderSuccessMsg : Texts -> Bool -> Model -> Html Msg -renderSuccessMsg texts public model = - div - [ class "row" - , classList [ ( "hidden", List.isEmpty model.files || not (isSuccessAll model) ) ] - ] - [ div [ class "mt-4" ] - [ div [ class S.successMessage ] - [ h3 [ class S.header2, class "text-green-800 dark:text-lime-800" ] - [ i [ class "fa fa-smile font-thin" ] [] - , span [ class "ml-2" ] - [ text texts.successBox.allFilesUploaded - ] - ] - , p - [ classList [ ( "hidden", public ) ] - ] - [ text texts.successBox.line1 - , a - [ class S.successMessageLink - , Page.href (SearchPage Nothing) - ] - [ text texts.successBox.itemsPage - ] - , text texts.successBox.line2 - , a - [ class S.successMessageLink - , Page.href QueuePage - ] - [ text texts.successBox.processingPage - ] - , text texts.successBox.line3 - ] - , p [] - [ text texts.successBox.resetLine1 - , a - [ class S.successMessageLink - , href "#" - , onClick Clear - ] - [ text texts.successBox.reset - ] - , text texts.successBox.resetLine2 - ] - ] - ] - ] - - -renderUploads : Texts -> Model -> Html Msg -renderUploads texts model = - div - [ class "mt-4" - , classList [ ( "hidden", List.isEmpty model.files || isSuccessAll model ) ] - ] - [ h2 [ class S.header2 ] - [ text texts.selectedFiles - , text (" (" ++ (List.length model.files |> String.fromInt) ++ ")") - ] - , div [] <| - if model.singleItem then - List.map (renderFileItem model (Just uploadAllTracker)) model.files - - else - List.map (renderFileItem model Nothing) model.files - ] - - -getProgress : Model -> File -> Int -getProgress model file = - let - key = - if model.singleItem then - uploadAllTracker - - else - makeFileId file - in - Dict.get key model.loading - |> Maybe.withDefault 0 - - -renderFileItem : Model -> Maybe String -> File -> Html Msg -renderFileItem model _ file = - let - name = - File.name file - - size = - File.size file - |> toFloat - |> Util.Size.bytesReadable Util.Size.B - in - div [ class "flex flex-col w-full mb-4" ] - [ div [ class "flex flex-row items-center" ] - [ div [ class "inline-flex items-center" ] - [ i - [ classList - [ ( "mr-2 text-lg", True ) - , ( "fa fa-file font-thin", isIdle model file ) - , ( "fa fa-spinner animate-spin ", isLoading model file ) - , ( "fa fa-check ", isCompleted model file ) - , ( "fa fa-bolt", isError model file ) - ] - ] - [] - , div [ class "middle aligned content" ] - [ div [ class "header" ] - [ text name - ] - ] - ] - , div [ class "flex-grow inline-flex justify-end" ] - [ text size - ] - ] - , div [ class "h-4" ] - [ Comp.Progress.progress2 (getProgress model file) - ] - ] +viewContent texts sourceId flags settings model = + Html.map UploadMsg + (Comp.UploadForm.view texts.uploadForm sourceId flags settings model.uploadForm)