Initial version.

Features:

- Upload PDF files let them analyze

- Manage meta data and items

- See processing in webapp
This commit is contained in:
Eike Kettner
2019-07-23 00:53:30 +02:00
parent 6154e6a387
commit 831cd8b655
341 changed files with 23634 additions and 484 deletions

View File

@@ -0,0 +1,45 @@
module Page.CollectiveSettings.Data exposing (..)
import Http
import Comp.SourceManage
import Comp.UserManage
import Comp.Settings
import Data.Language
import Api.Model.BasicResult exposing (BasicResult)
import Api.Model.CollectiveSettings exposing (CollectiveSettings)
import Api.Model.ItemInsights exposing (ItemInsights)
type alias Model =
{ currentTab: Maybe Tab
, sourceModel: Comp.SourceManage.Model
, userModel: Comp.UserManage.Model
, settingsModel: Comp.Settings.Model
, insights: ItemInsights
, submitResult: Maybe BasicResult
}
emptyModel: Model
emptyModel =
{ currentTab = Just InsightsTab
, sourceModel = Comp.SourceManage.emptyModel
, userModel = Comp.UserManage.emptyModel
, settingsModel = Comp.Settings.init Api.Model.CollectiveSettings.empty
, insights = Api.Model.ItemInsights.empty
, submitResult = Nothing
}
type Tab
= SourceTab
| UserTab
| InsightsTab
| SettingsTab
type Msg
= SetTab Tab
| SourceMsg Comp.SourceManage.Msg
| UserMsg Comp.UserManage.Msg
| SettingsMsg Comp.Settings.Msg
| Init
| GetInsightsResp (Result Http.Error ItemInsights)
| CollectiveSettingsResp (Result Http.Error CollectiveSettings)
| SubmitResp (Result Http.Error BasicResult)

View File

@@ -0,0 +1,82 @@
module Page.CollectiveSettings.Update exposing (update)
import Api
import Api.Model.BasicResult exposing (BasicResult)
import Page.CollectiveSettings.Data exposing (..)
import Data.Flags exposing (Flags)
import Data.Language
import Comp.SourceManage
import Comp.UserManage
import Comp.Settings
import Util.Http
update: Flags -> Msg -> Model -> (Model, Cmd Msg)
update flags msg model =
case msg of
SetTab t ->
let
m = { model | currentTab = Just t }
in
case t of
SourceTab ->
update flags (SourceMsg Comp.SourceManage.LoadSources) m
UserTab ->
update flags (UserMsg Comp.UserManage.LoadUsers) m
InsightsTab ->
update flags Init m
SettingsTab ->
update flags Init m
SourceMsg m ->
let
(m2, c2) = Comp.SourceManage.update flags m model.sourceModel
in
({model | sourceModel = m2}, Cmd.map SourceMsg c2)
UserMsg m ->
let
(m2, c2) = Comp.UserManage.update flags m model.userModel
in
({model | userModel = m2}, Cmd.map UserMsg c2)
SettingsMsg m ->
let
(m2, c2, msett) = Comp.Settings.update flags m model.settingsModel
cmd = case msett of
Nothing -> Cmd.none
Just sett ->
Api.setCollectiveSettings flags sett SubmitResp
in
({model | settingsModel = m2, submitResult = Nothing}, Cmd.batch [cmd, Cmd.map SettingsMsg c2])
Init ->
({model|submitResult = Nothing}
,Cmd.batch
[ Api.getInsights flags GetInsightsResp
, Api.getCollectiveSettings flags CollectiveSettingsResp
]
)
GetInsightsResp (Ok data) ->
({model|insights = data}, Cmd.none)
GetInsightsResp (Err err) ->
(model, Cmd.none)
CollectiveSettingsResp (Ok data) ->
({model | settingsModel = Comp.Settings.init data }, Cmd.none)
CollectiveSettingsResp (Err err) ->
(model, Cmd.none)
SubmitResp (Ok res) ->
({model | submitResult = Just res}, Cmd.none)
SubmitResp (Err err) ->
let
res = BasicResult False (Util.Http.errorToString err)
in
({model | submitResult = Just res}, Cmd.none)

View File

@@ -0,0 +1,197 @@
module Page.CollectiveSettings.View exposing (view)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Api.Model.NameCount exposing (NameCount)
import Util.Html exposing (classActive)
import Data.Flags exposing (Flags)
import Page.CollectiveSettings.Data exposing (..)
import Comp.SourceManage
import Comp.UserManage
import Comp.Settings
import Util.Size
import Util.Maybe
view: Flags -> Model -> Html Msg
view flags model =
div [class "collectivesetting-page ui padded grid"]
[div [class "four wide column"]
[h4 [class "ui top attached ablue-comp header"]
[text "Collective"
]
,div [class "ui attached fluid segment"]
[div [class "ui fluid vertical secondary menu"]
[div [classActive (model.currentTab == Just InsightsTab) "link icon item"
,onClick (SetTab InsightsTab)
]
[i [class "chart bar outline icon"][]
,text "Insights"
]
,div [classActive (model.currentTab == Just SourceTab) "link icon item"
,onClick (SetTab SourceTab)
]
[i [class "upload icon"][]
,text "Sources"
]
, div [classActive (model.currentTab == Just SettingsTab) "link icon item"
,onClick (SetTab SettingsTab)
]
[i [class "language icon"][]
,text "Document Language"
]
,div [classActive (model.currentTab == Just UserTab) "link icon item"
,onClick (SetTab UserTab)
]
[i [class "user icon"][]
,text "Users"
]
]
]
]
,div [class "twelve wide column"]
[div [class ""]
(case model.currentTab of
Just SourceTab -> viewSources flags model
Just UserTab -> viewUsers model
Just InsightsTab -> viewInsights model
Just SettingsTab -> viewSettings model
Nothing -> []
)
]
]
viewInsights: Model -> List (Html Msg)
viewInsights model =
[h1 [class "ui header"]
[i [class "chart bar outline icon"][]
,div [class "content"]
[text "Insights"
]
]
,div [class "ui basic blue segment"]
[h4 [class "ui header"]
[text "Items"
]
,div [class "ui statistics"]
[div [class "ui statistic"]
[div [class "value"]
[String.fromInt (model.insights.incomingCount + model.insights.outgoingCount) |> text
]
,div [class "label"]
[text "Items"
]
]
,div [class "ui statistic"]
[div [class "value"]
[String.fromInt model.insights.incomingCount |> text
]
,div [class "label"]
[text "Incoming"
]
]
,div [class "ui statistic"]
[div [class "value"]
[String.fromInt model.insights.outgoingCount |> text
]
,div [class "label"]
[text "Outgoing"
]
]
]
]
,div [class "ui basic blue segment"]
[h4 [class "ui header"]
[text "Size"
]
,div [class "ui statistics"]
[div [class "ui statistic"]
[div [class "value"]
[toFloat model.insights.itemSize |> Util.Size.bytesReadable Util.Size.B |> text
]
,div [class "label"]
[text "Size"
]
]
]
]
,div [class "ui basic blue segment"]
[h4 [class "ui header"]
[text "Tags"
]
,div [class "ui statistics"]
(List.map makeTagStats model.insights.tagCloud.items)
]
]
makeTagStats: NameCount -> Html Msg
makeTagStats nc =
div [class "ui statistic"]
[div [class "value"]
[String.fromInt nc.count |> text
]
,div [class "label"]
[text nc.name
]
]
viewSources: Flags -> Model -> List (Html Msg)
viewSources flags model =
[h2 [class "ui header"]
[i [class "ui upload icon"][]
,div [class "content"]
[text "Sources"
]
]
,Html.map SourceMsg (Comp.SourceManage.view flags model.sourceModel)
]
viewUsers: Model -> List (Html Msg)
viewUsers model =
[h2 [class "ui header"]
[i [class "ui user icon"][]
,div [class "content"]
[text "Users"
]
]
,Html.map UserMsg (Comp.UserManage.view model.userModel)
]
viewSettings: Model -> List (Html Msg)
viewSettings model =
[div [class "ui grid"]
[div [class "row"]
[div [class "sixteen wide colum"]
[h2 [class "ui header"]
[i [class "ui language icon"][]
,div [class "content"]
[text "Document Language"
]
]
]
]
,div [class "row"]
[div [class "six wide column"]
[div [class "ui basic segment"]
[text "The language of your documents. This helps text recognition (OCR) and text analysis."
]
]
]
,div [class "row"]
[div [class "six wide column"]
[Html.map SettingsMsg (Comp.Settings.view model.settingsModel)
,div [classList [("ui message", True)
,("hidden", Util.Maybe.isEmpty model.submitResult)
,("success", Maybe.map .success model.submitResult |> Maybe.withDefault False)
,("error", Maybe.map .success model.submitResult |> Maybe.map not |> Maybe.withDefault False)
]]
[Maybe.map .message model.submitResult
|> Maybe.withDefault ""
|> text
]
]
]
]
]

View File

@@ -1,15 +1,36 @@
module Page.Home.Data exposing (..)
import Http
import Comp.SearchMenu
import Comp.ItemList
import Comp.ItemDetail
import Api.Model.ItemLightList exposing (ItemLightList)
import Api.Model.ItemDetail exposing (ItemDetail)
type alias Model =
{
{ searchMenuModel: Comp.SearchMenu.Model
, itemListModel: Comp.ItemList.Model
, searchInProgress: Bool
, itemDetailModel: Comp.ItemDetail.Model
, viewMode: ViewMode
}
emptyModel: Model
emptyModel =
{
{ searchMenuModel = Comp.SearchMenu.emptyModel
, itemListModel = Comp.ItemList.emptyModel
, itemDetailModel = Comp.ItemDetail.emptyModel
, searchInProgress = False
, viewMode = Listing
}
type Msg
= Dummy
= Init
| SearchMenuMsg Comp.SearchMenu.Msg
| ItemListMsg Comp.ItemList.Msg
| ItemSearchResp (Result Http.Error ItemLightList)
| DoSearch
| ItemDetailMsg Comp.ItemDetail.Msg
| ItemDetailResp (Result Http.Error ItemDetail)
type ViewMode = Listing | Detail

View File

@@ -3,7 +3,98 @@ module Page.Home.Update exposing (update)
import Api
import Data.Flags exposing (Flags)
import Page.Home.Data exposing (..)
import Comp.SearchMenu
import Comp.ItemList
import Comp.ItemDetail
import Util.Update
update: Flags -> Msg -> Model -> (Model, Cmd Msg)
update flags msg model =
(model, Cmd.none)
case msg of
Init ->
Util.Update.andThen1
[ update flags (SearchMenuMsg Comp.SearchMenu.Init)
, update flags (ItemDetailMsg Comp.ItemDetail.Init)
, doSearch flags
]
model
SearchMenuMsg m ->
let
nextState = Comp.SearchMenu.update flags m model.searchMenuModel
newModel = {model | searchMenuModel = Tuple.first nextState.modelCmd}
(m2, c2) = if nextState.stateChange then doSearch flags newModel else (newModel, Cmd.none)
in
(m2, Cmd.batch [c2, Cmd.map SearchMenuMsg (Tuple.second nextState.modelCmd)])
ItemListMsg m ->
let
(m2, c2, mitem) = Comp.ItemList.update flags m model.itemListModel
cmd = case mitem of
Just item ->
Api.itemDetail flags item.id ItemDetailResp
Nothing ->
Cmd.none
in
({model | itemListModel = m2}, Cmd.batch [ Cmd.map ItemListMsg c2, cmd ])
ItemSearchResp (Ok list) ->
let
m = {model|searchInProgress = False, viewMode = Listing}
in
update flags (ItemListMsg (Comp.ItemList.SetResults list)) m
ItemSearchResp (Err err) ->
({model|searchInProgress = False}, Cmd.none)
DoSearch ->
doSearch flags model
ItemDetailMsg m ->
let
(m2, c2, nav) = Comp.ItemDetail.update flags m model.itemDetailModel
newModel = {model | itemDetailModel = m2}
newCmd = Cmd.map ItemDetailMsg c2
in
case nav of
Comp.ItemDetail.NavBack ->
doSearch flags newModel
Comp.ItemDetail.NavPrev ->
case Comp.ItemList.prevItem model.itemListModel m2.item.id of
Just n ->
(newModel, Cmd.batch [newCmd, Api.itemDetail flags n.id ItemDetailResp])
Nothing ->
(newModel, newCmd)
Comp.ItemDetail.NavNext ->
case Comp.ItemList.nextItem model.itemListModel m2.item.id of
Just n ->
(newModel, Cmd.batch [newCmd, Api.itemDetail flags n.id ItemDetailResp])
Nothing ->
(newModel, newCmd)
Comp.ItemDetail.NavNextOrBack ->
case Comp.ItemList.nextItem model.itemListModel m2.item.id of
Just n ->
(newModel, Cmd.batch [newCmd, Api.itemDetail flags n.id ItemDetailResp])
Nothing ->
doSearch flags newModel
Comp.ItemDetail.NavNone ->
(newModel, newCmd)
ItemDetailResp (Ok item) ->
let
m = {model | viewMode = Detail}
in
update flags (ItemDetailMsg (Comp.ItemDetail.SetItem item)) m
ItemDetailResp (Err err) ->
let
_ = Debug.log "Error" err
in
(model, Cmd.none)
doSearch: Flags -> Model -> (Model, Cmd Msg)
doSearch flags model =
let
mask = Comp.SearchMenu.getItemSearch model.searchMenuModel
in
({model|searchInProgress = True, viewMode = Listing}, Api.itemSearch flags mask ItemSearchResp)

View File

@@ -6,18 +6,69 @@ import Html.Events exposing (onClick)
import Page exposing (Page(..))
import Page.Home.Data exposing (..)
import Comp.SearchMenu
import Comp.ItemList
import Comp.ItemDetail
import Data.Flags
import Util.Html exposing (onClickk)
view: Model -> Html Msg
view model =
div [class "home-page ui fluid grid"]
[div [class "three wide column"]
[h3 [][text "Menu"]
div [class "home-page ui padded grid"]
[div [class "four wide column"]
[div [class "ui top attached ablue-comp menu"]
[h4 [class "header item"]
[text "Search"
]
,div [class "right floated menu"]
[a [class "item"
,onClick DoSearch
,href ""
]
[i [class "ui search icon"][]
]
]
]
,div [class "ui attached fluid segment"]
[(Html.map SearchMenuMsg (Comp.SearchMenu.view model.searchMenuModel))
]
]
,div [class "seven wide column", style "border-left" "1px solid"]
[h3 [][text "List"]
,div [class "twelve wide column"]
[case model.viewMode of
Listing ->
if model.searchInProgress then resultPlaceholder
else (Html.map ItemListMsg (Comp.ItemList.view model.itemListModel))
Detail ->
Html.map ItemDetailMsg (Comp.ItemDetail.view model.itemDetailModel)
]
]
resultPlaceholder: Html Msg
resultPlaceholder =
div [class "ui basic segment"]
[div [class "ui active inverted dimmer"]
[div [class "ui medium text loader"]
[text "Searching "
]
]
,div [class "six wide column", style "border-left" "1px solid", style "height" "100vh"]
[h3 [][text "DocView"]
,div [class "ui middle aligned very relaxed divided basic list segment"]
[div [class "item"]
[div [class "ui fluid placeholder"]
[div [class "full line"][]
,div [class "full line"][]
]
]
,div [class "item"]
[div [class "ui fluid placeholder"]
[div [class "full line"][]
,div [class "full line"][]
]
]
,div [class "item"]
[div [class "ui fluid placeholder"]
[div [class "full line"][]
,div [class "full line"][]
]
]
]
]

View File

@@ -1,6 +1,7 @@
module Page.Login.Data exposing (..)
import Http
import Page exposing (Page(..))
import Api.Model.AuthResult exposing (AuthResult)
type alias Model =
@@ -9,8 +10,8 @@ type alias Model =
, result: Maybe AuthResult
}
empty: Model
empty =
emptyModel: Model
emptyModel =
{ username = ""
, password = ""
, result = Nothing

View File

@@ -9,8 +9,8 @@ import Api.Model.UserPass exposing (UserPass)
import Api.Model.AuthResult exposing (AuthResult)
import Util.Http
update: Flags -> Msg -> Model -> (Model, Cmd Msg, Maybe AuthResult)
update flags msg model =
update: Maybe Page -> Flags -> Msg -> Model -> (Model, Cmd Msg, Maybe AuthResult)
update referrer flags msg model =
case msg of
SetUsername str ->
({model | username = str}, Cmd.none, Nothing)
@@ -21,19 +21,22 @@ update flags msg model =
(model, Api.login flags (UserPass model.username model.password) AuthResp, Nothing)
AuthResp (Ok lr) ->
if lr.success
then ({model|result = Just lr, password = ""}, setAccount lr, Just lr)
else ({model|result = Just lr, password = ""}, Ports.removeAccount "", Just lr)
let
gotoRef = Maybe.withDefault HomePage referrer |> Page.goto
in
if lr.success
then ({model|result = Just lr, password = ""}, Cmd.batch [setAccount lr, gotoRef], Just lr)
else ({model|result = Just lr, password = ""}, Ports.removeAccount (), Just lr)
AuthResp (Err err) ->
let
empty = Api.Model.AuthResult.empty
lr = {empty|message = Util.Http.errorToString err}
in
({model|password = "", result = Just lr}, Ports.removeAccount "", Just empty)
({model|password = "", result = Just lr}, Ports.removeAccount (), Just empty)
setAccount: AuthResult -> Cmd msg
setAccount result =
if result.success
then Ports.setAccount result
else Ports.removeAccount ""
else Ports.removeAccount ()

View File

@@ -3,7 +3,7 @@ module Page.Login.View exposing (view)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput, onSubmit)
import Page exposing (Page(..))
import Page.Login.Data exposing (..)
view: Model -> Html Msg
@@ -11,31 +11,51 @@ view model =
div [class "login-page"]
[div [class "ui centered grid"]
[div [class "row"]
[div [class "eight wide column ui segment login-view"]
[h1 [class "ui dividing header"][text "Sign in to Docspell"]
,Html.form [class "ui large error form", onSubmit Authenticate]
[div [class "six wide column ui segment login-view"]
[h1 [class "ui center aligned icon header"]
[i [class "umbrella icon"][]
,div [class "content"]
[text "Sign in to Docspell"
]
]
,Html.form [class "ui large error raised form segment", onSubmit Authenticate]
[div [class "field"]
[label [][text "Username"]
,input [type_ "text"
,onInput SetUsername
,value model.username
][]
,div [class "ui left icon input"]
[input [type_ "text"
,onInput SetUsername
,value model.username
,placeholder "Collective / Login"
,autofocus True
][]
,i [class "user icon"][]
]
]
,div [class "field"]
[label [][text "Password"]
,input [type_ "password"
,onInput SetPassword
,value model.password
][]
,div [class "ui left icon input"]
[input [type_ "password"
,onInput SetPassword
,value model.password
,placeholder "Password"
][]
,i [class "lock icon"][]
]
]
,button [class "ui primary button"
,button [class "ui primary fluid button"
,type_ "submit"
,onClick Authenticate
]
[text "Login"
]
]
,(resultMessage model)
,div[class "ui very basic right aligned segment"]
[text "No account? "
,a [class "ui icon link", Page.href RegisterPage]
[i [class "edit icon"][]
,text "Sign up!"
]
]
]
]
]

View File

@@ -0,0 +1,36 @@
module Page.ManageData.Data exposing (..)
import Comp.TagManage
import Comp.EquipmentManage
import Comp.OrgManage
import Comp.PersonManage
type alias Model =
{ currentTab: Maybe Tab
, tagManageModel: Comp.TagManage.Model
, equipManageModel: Comp.EquipmentManage.Model
, orgManageModel: Comp.OrgManage.Model
, personManageModel: Comp.PersonManage.Model
}
emptyModel: Model
emptyModel =
{ currentTab = Nothing
, tagManageModel = Comp.TagManage.emptyModel
, equipManageModel = Comp.EquipmentManage.emptyModel
, orgManageModel = Comp.OrgManage.emptyModel
, personManageModel = Comp.PersonManage.emptyModel
}
type Tab
= TagTab
| EquipTab
| OrgTab
| PersonTab
type Msg
= SetTab Tab
| TagManageMsg Comp.TagManage.Msg
| EquipManageMsg Comp.EquipmentManage.Msg
| OrgManageMsg Comp.OrgManage.Msg
| PersonManageMsg Comp.PersonManage.Msg

View File

@@ -0,0 +1,52 @@
module Page.ManageData.Update exposing (update)
import Page.ManageData.Data exposing (..)
import Data.Flags exposing (Flags)
import Comp.TagManage
import Comp.EquipmentManage
import Comp.OrgManage
import Comp.PersonManage
update: Flags -> Msg -> Model -> (Model, Cmd Msg)
update flags msg model =
case msg of
SetTab t ->
let
m = { model | currentTab = Just t }
in
case t of
TagTab ->
update flags (TagManageMsg Comp.TagManage.LoadTags) m
EquipTab ->
update flags (EquipManageMsg Comp.EquipmentManage.LoadEquipments) m
OrgTab ->
update flags (OrgManageMsg Comp.OrgManage.LoadOrgs) m
PersonTab ->
update flags (PersonManageMsg Comp.PersonManage.LoadPersons) m
TagManageMsg m ->
let
(m2, c2) = Comp.TagManage.update flags m model.tagManageModel
in
({model | tagManageModel = m2}, Cmd.map TagManageMsg c2)
EquipManageMsg m ->
let
(m2, c2) = Comp.EquipmentManage.update flags m model.equipManageModel
in
({model | equipManageModel = m2}, Cmd.map EquipManageMsg c2)
OrgManageMsg m ->
let
(m2, c2) = Comp.OrgManage.update flags m model.orgManageModel
in
({model | orgManageModel = m2}, Cmd.map OrgManageMsg c2)
PersonManageMsg m ->
let
(m2, c2) = Comp.PersonManage.update flags m model.personManageModel
in
({model | personManageModel = m2}, Cmd.map PersonManageMsg c2)

View File

@@ -0,0 +1,104 @@
module Page.ManageData.View exposing (view)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Util.Html exposing (classActive)
import Page.ManageData.Data exposing (..)
import Comp.TagManage
import Comp.EquipmentManage
import Comp.OrgManage
import Comp.PersonManage
view: Model -> Html Msg
view model =
div [class "managedata-page ui padded grid"]
[div [class "four wide column"]
[h4 [class "ui top attached ablue-comp header"]
[text "Manage Data"
]
,div [class "ui attached fluid segment"]
[div [class "ui fluid vertical secondary menu"]
[div [classActive (model.currentTab == Just TagTab) "link icon item"
,onClick (SetTab TagTab)
]
[i [class "tag icon"][]
,text "Tag"
]
,div [classActive (model.currentTab == Just EquipTab) "link icon item"
,onClick (SetTab EquipTab)
]
[i [class "box icon"][]
,text "Equipment"
]
,div [classActive (model.currentTab == Just OrgTab) "link icon item"
,onClick (SetTab OrgTab)
]
[i [class "factory icon"][]
,text "Organization"
]
,div [classActive (model.currentTab == Just PersonTab) "link icon item"
,onClick (SetTab PersonTab)
]
[i [class "user icon"][]
,text "Person"
]
]
]
]
,div [class "twelve wide column"]
[div [class ""]
(case model.currentTab of
Just TagTab -> viewTags model
Just EquipTab -> viewEquip model
Just OrgTab -> viewOrg model
Just PersonTab -> viewPerson model
Nothing -> []
)
]
]
viewTags: Model -> List (Html Msg)
viewTags model =
[h2 [class "ui header"]
[i [class "ui tag icon"][]
,div [class "content"]
[text "Tags"
]
]
,Html.map TagManageMsg (Comp.TagManage.view model.tagManageModel)
]
viewEquip: Model -> List (Html Msg)
viewEquip model =
[h2 [class "ui header"]
[i [class "ui box icon"][]
,div [class "content"]
[text "Equipment"
]
]
,Html.map EquipManageMsg (Comp.EquipmentManage.view model.equipManageModel)
]
viewOrg: Model -> List (Html Msg)
viewOrg model =
[h2 [class "ui header"]
[i [class "ui factory icon"][]
,div [class "content"]
[text "Organizations"
]
]
,Html.map OrgManageMsg (Comp.OrgManage.view model.orgManageModel)
]
viewPerson: Model -> List (Html Msg)
viewPerson model =
[h2 [class "ui header"]
[i [class "ui user icon"][]
,div [class "content"]
[text "Person"
]
]
,Html.map PersonManageMsg (Comp.PersonManage.view model.personManageModel)
]

View File

@@ -0,0 +1,39 @@
module Page.NewInvite.Data exposing (..)
import Http
import Api.Model.InviteResult exposing (InviteResult)
type alias Model =
{ password: String
, result: State
}
type State
= Empty
| Failed String
| Success InviteResult
isFailed: State -> Bool
isFailed state =
case state of
Failed _ -> True
_ -> False
isSuccess: State -> Bool
isSuccess state =
case state of
Success _ -> True
_ -> False
emptyModel: Model
emptyModel =
{ password = ""
, result = Empty
}
type Msg
= SetPassword String
| GenerateInvite
| Reset
| InviteResp (Result Http.Error InviteResult)

View File

@@ -0,0 +1,27 @@
module Page.NewInvite.Update exposing (update)
import Api
import Data.Flags exposing (Flags)
import Page.NewInvite.Data exposing (..)
import Api.Model.GenInvite exposing (GenInvite)
import Api.Model.InviteResult
import Util.Http
update: Flags -> Msg -> Model -> (Model, Cmd Msg)
update flags msg model =
case msg of
SetPassword str ->
({model|password = str}, Cmd.none)
Reset ->
(emptyModel, Cmd.none)
GenerateInvite ->
(model, Api.newInvite flags (GenInvite model.password) InviteResp)
InviteResp (Ok res) ->
if res.success then ({model | result = (Success res)}, Cmd.none)
else ({model | result = (Failed res.message)}, Cmd.none)
InviteResp (Err err) ->
({model|result = Failed (Util.Http.errorToString err)}, Cmd.none)

View File

@@ -0,0 +1,102 @@
module Page.NewInvite.View exposing (view)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput, onSubmit)
import Data.Flags exposing (Flags)
import Page.NewInvite.Data exposing (..)
import Api.Model.InviteResult
import Util.Maybe
view: Flags -> Model -> Html Msg
view flags model =
div [class "newinvite-page"]
[div [class "ui centered grid"]
[div [class "row"]
[div [class "eight wide column ui segment newinvite-view"]
[h1 [class "ui cener aligned icon header"]
[i [class "umbrella icon"][]
,text "Create new invitations"
]
,inviteMessage flags
,Html.form [classList [("ui large form raised segment", True)
,("error", isFailed model.result)
,("success", isSuccess model.result)
]
, onSubmit GenerateInvite]
[div [class "required field"]
[label [][text "New Invitation Password"]
,div [class "ui left icon input"]
[input [type_ "password"
,onInput SetPassword
,value model.password
,autofocus True
][]
,i [class "key icon"][]
]
]
,button [class "ui primary button"
,type_ "submit"
]
[text "Submit"
]
,a [class "ui right floated button", href "", onClick Reset]
[text "Reset"
]
,resultMessage model
]
]
]
]
]
resultMessage: Model -> Html Msg
resultMessage model =
div [classList [("ui message", True)
,("error", isFailed model.result)
,("success", isSuccess model.result)
,("hidden", model.result == Empty)
]]
[case model.result of
Failed m ->
div [class "content"]
[div [class "header"][text "Error"]
,p [][text m]
]
Success r ->
div [class "content"]
[div [class "header"][text "Success"]
,p [][text r.message]
,p [][text "Invitation Key:"]
,pre[][Maybe.withDefault "" r.key |> text
]
]
Empty ->
span[][]
]
inviteMessage: Flags -> Html Msg
inviteMessage flags =
div [classList [("ui message", True)
,("hidden", flags.config.signupMode /= "invite")
]]
[p [][text
"""Docspell requires an invite when signing up. You can
create these invites here and send them to friends so
they can signup with docspell."""
]
,p [][text
"""Each invite can only be used once. You'll need to
create one key for each person you want to invite."""
]
,p [][text
"""Creating an invite requires providing the password
from the configuration."""
]
]

View File

@@ -0,0 +1,79 @@
module Page.Queue.Data exposing (..)
import Http
import Api.Model.JobQueueState exposing (JobQueueState)
import Api.Model.JobDetail exposing (JobDetail)
import Api.Model.BasicResult exposing (BasicResult)
import Time
import Util.Duration
import Util.Maybe
import Comp.YesNoDimmer
type alias Model =
{ state: JobQueueState
, error: String
, pollingInterval: Float
, init: Bool
, stopRefresh: Bool
, currentMillis: Int
, showLog: Maybe JobDetail
, deleteConfirm: Comp.YesNoDimmer.Model
, cancelJobRequest: Maybe String
}
emptyModel: Model
emptyModel =
{ state = Api.Model.JobQueueState.empty
, error = ""
, pollingInterval = 1200
, init = False
, stopRefresh = False
, currentMillis = 0
, showLog = Nothing
, deleteConfirm = Comp.YesNoDimmer.emptyModel
, cancelJobRequest = Nothing
}
type Msg
= Init
| StateResp (Result Http.Error JobQueueState)
| StopRefresh
| NewTime Time.Posix
| ShowLog JobDetail
| QuitShowLog
| RequestCancelJob JobDetail
| DimmerMsg JobDetail Comp.YesNoDimmer.Msg
| CancelResp (Result Http.Error BasicResult)
getRunningTime: Model -> JobDetail -> Maybe String
getRunningTime model job =
let
mkTime: Int -> Int -> Maybe String
mkTime start end =
if start < end then Just <| Util.Duration.toHuman (end - start)
else Nothing
in
case (job.started, job.finished) of
(Just sn, Just fn) ->
Util.Maybe.or
[ mkTime sn fn
, mkTime sn model.currentMillis
]
(Just sn, Nothing) ->
mkTime sn model.currentMillis
(Nothing, _) ->
Nothing
getSubmittedTime: Model -> JobDetail -> Maybe String
getSubmittedTime model job =
if model.currentMillis > job.submitted then
Just <| Util.Duration.toHuman (model.currentMillis - job.submitted)
else
Nothing
getDuration: Model -> JobDetail -> Maybe String
getDuration model job =
if job.state == "stuck" then getSubmittedTime model job
else Util.Maybe.or [ (getRunningTime model job), (getSubmittedTime model job) ]

View File

@@ -0,0 +1,76 @@
module Page.Queue.Update exposing (update)
import Api
import Ports
import Page.Queue.Data exposing (..)
import Data.Flags exposing (Flags)
import Util.Http
import Time
import Task
import Comp.YesNoDimmer
update: Flags -> Msg -> Model -> (Model, Cmd Msg)
update flags msg model =
case msg of
Init ->
let
start = if model.init
then Cmd.none
else Cmd.batch
[Api.getJobQueueState flags StateResp
,getNewTime
]
in
({model|init = True, stopRefresh = False}, start)
StateResp (Ok s) ->
let
progressCmd =
List.map (\job -> Ports.setProgress (job.id, job.progress)) s.progress
_ = Debug.log "stopRefresh" model.stopRefresh
refresh =
if model.pollingInterval <= 0 || model.stopRefresh then Cmd.none
else Cmd.batch
[Api.getJobQueueStateIn flags model.pollingInterval StateResp
,getNewTime
]
in
({model | state = s, stopRefresh = False}, Cmd.batch (refresh :: progressCmd))
StateResp (Err err) ->
({model | error = Util.Http.errorToString err }, Cmd.none)
StopRefresh ->
({model | stopRefresh = True, init = False }, Cmd.none)
NewTime t ->
({model | currentMillis = Time.posixToMillis t}, Cmd.none)
ShowLog job ->
({model | showLog = Just job}, Cmd.none)
QuitShowLog ->
({model | showLog = Nothing}, Cmd.none)
RequestCancelJob job ->
let
newModel = {model|cancelJobRequest = Just job.id}
in
update flags (DimmerMsg job Comp.YesNoDimmer.Activate) newModel
DimmerMsg job m ->
let
(cm, confirmed) = Comp.YesNoDimmer.update m model.deleteConfirm
cmd = if confirmed then Api.cancelJob flags job.id CancelResp else Cmd.none
in
({model | deleteConfirm = cm}, cmd)
CancelResp (Ok r) ->
(model, Cmd.none)
CancelResp (Err err) ->
(model, Cmd.none)
getNewTime : Cmd Msg
getNewTime =
Task.perform NewTime Time.now

View File

@@ -0,0 +1,217 @@
module Page.Queue.View exposing (view)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Page.Queue.Data exposing (..)
import Api.Model.JobQueueState exposing (JobQueueState)
import Api.Model.JobDetail exposing (JobDetail)
import Api.Model.JobLogEvent exposing (JobLogEvent)
import Data.Priority
import Comp.YesNoDimmer
import Util.Time exposing (formatDateTime, formatIsoDateTime)
import Util.Duration
view: Model -> Html Msg
view model =
div [class "queue-page ui grid container"] <|
List.concat
[ case model.showLog of
Just job ->
[renderJobLog job]
Nothing ->
List.map (renderProgressCard model) model.state.progress
|> List.map (\el -> div [class "row"][div [class "column"][el]])
, [div [class "two column row"]
[renderWaiting model
,renderCompleted model
]
]
]
renderJobLog: JobDetail -> Html Msg
renderJobLog job =
div [class "ui fluid card"]
[div [class "content"]
[i [class "delete link icon", onClick QuitShowLog][]
,text job.name
]
,div [class "content"]
[div [class "job-log"]
(List.map renderLogLine job.logs)
]
]
renderWaiting: Model -> Html Msg
renderWaiting model =
div [class "column"]
[div [class "ui center aligned basic segment"]
[i [class "ui large angle double up icon"][]
]
,div [class "ui centered cards"]
(List.map (renderInfoCard model) model.state.queued)
]
renderCompleted: Model -> Html Msg
renderCompleted model =
div [class "column"]
[div [class "ui center aligned basic segment"]
[i [class "ui large angle double down icon"][]
]
,div [class "ui centered cards"]
(List.map (renderInfoCard model) model.state.completed)
]
renderProgressCard: Model -> JobDetail -> Html Msg
renderProgressCard model job =
div [class "ui fluid card"]
[div [id job.id, class "ui top attached indicating progress"]
[div [class "bar"]
[]
]
,Html.map (DimmerMsg job) (Comp.YesNoDimmer.view2 (model.cancelJobRequest == Just job.id) dimmerSettings model.deleteConfirm)
,div [class "content"]
[ div [class "right floated meta"]
[div [class "ui label"]
[text job.state
,div [class "detail"]
[Maybe.withDefault "" job.worker |> text
]
]
,div [class "ui basic label"]
[i [class "clock icon"][]
,div [class "detail"]
[getDuration model job |> Maybe.withDefault "-:-" |> text
]
]
]
, i [class "asterisk loading icon"][]
, text job.name
]
,div [class "content"]
[div [class "job-log"]
(List.map renderLogLine job.logs)
]
,div [class "meta"]
[div [class "right floated"]
[button [class "ui button", onClick (RequestCancelJob job)]
[text "Cancel"
]
]
]
]
renderLogLine: JobLogEvent -> Html Msg
renderLogLine log =
span [class (String.toLower log.level)]
[formatIsoDateTime log.time |> text
,text ": "
,text log.message
, br[][]
]
isFinal: JobDetail -> Bool
isFinal job =
case job.state of
"failed" -> True
"success" -> True
"cancelled" -> True
_ -> False
dimmerSettings: Comp.YesNoDimmer.Settings
dimmerSettings =
let
defaults = Comp.YesNoDimmer.defaultSettings
in
{ defaults | headerClass = "ui inverted header", headerIcon = "", message = "Cancel/Delete this job?"}
renderInfoCard: Model -> JobDetail -> Html Msg
renderInfoCard model job =
div [classList [("ui fluid card", True)
,(jobStateColor job, True)
]
]
[Html.map (DimmerMsg job) (Comp.YesNoDimmer.view2 (model.cancelJobRequest == Just job.id) dimmerSettings model.deleteConfirm)
,div [class "content"]
[div [class "right floated"]
[if isFinal job || job.state == "stuck" then
span [onClick (ShowLog job)]
[i [class "file link icon", title "Show log"][]
]
else
span[][]
,i [class "delete link icon", title "Remove", onClick (RequestCancelJob job)][]
]
,if isFinal job then
span [class "invisible"][]
else
div [class "right floated"]
[div [class "meta"]
[getDuration model job |> Maybe.withDefault "-:-" |> text
]
]
,i [classList [("check icon", job.state == "success")
,("redo icon", job.state == "stuck")
,("bolt icon", job.state == "failed")
,("meh outline icon", job.state == "canceled")
,("cog icon", not (isFinal job) && job.state /= "stuck")
]
][]
,text job.name
]
,div [class "content"]
[div [class "right floated"]
[if isFinal job then
div [class ("ui basic label " ++ jobStateColor job)]
[i [class "clock icon"][]
,div [class "detail"]
[getDuration model job |> Maybe.withDefault "-:-" |> text
]
]
else
span [class "invisible"][]
,div [class ("ui basic label " ++ jobStateColor job)]
[text "Prio"
,div [class "detail"]
[code [][Data.Priority.fromString job.priority
|> Maybe.map Data.Priority.toName
|> Maybe.withDefault job.priority
|> text
]
]
]
,div [class ("ui basic label " ++ jobStateColor job)]
[text "Retries"
,div [class "detail"]
[job.retries |> String.fromInt |> text
]
]
]
,jobStateLabel job
,div [class "ui basic label"]
[Util.Time.formatDateTime job.submitted |> text
]
]
]
jobStateColor: JobDetail -> String
jobStateColor job =
case job.state of
"success" -> "green"
"failed" -> "red"
"canceled" -> "orange"
"stuck" -> "purple"
"scheduled" -> "blue"
"waiting" -> "grey"
_ -> ""
jobStateLabel: JobDetail -> Html Msg
jobStateLabel job =
let
col = jobStateColor job
in
div [class ("ui label " ++ col)]
[text job.state
]

View File

@@ -0,0 +1,44 @@
module Page.Register.Data exposing (..)
import Http
import Api.Model.BasicResult exposing (BasicResult)
type alias Model =
{ result: Maybe BasicResult
, collId: String
, login: String
, pass1: String
, pass2: String
, showPass1: Bool
, showPass2: Bool
, errorMsg: List String
, loading: Bool
, successMsg: String
, invite: Maybe String
}
emptyModel: Model
emptyModel =
{ result = Nothing
, collId = ""
, login = ""
, pass1 = ""
, pass2 = ""
, showPass1 = False
, showPass2 = False
, errorMsg = []
, successMsg = ""
, loading = False
, invite = Nothing
}
type Msg
= SetCollId String
| SetLogin String
| SetPass1 String
| SetPass2 String
| SetInvite String
| RegisterSubmit
| ToggleShowPass1
| ToggleShowPass2
| SubmitResp (Result Http.Error BasicResult)

View File

@@ -0,0 +1,84 @@
module Page.Register.Update exposing (update)
import Api
import Api.Model.Registration exposing (Registration)
import Page.Register.Data exposing (..)
import Data.Flags exposing (Flags)
import Page exposing (Page(..))
update: Flags -> Msg -> Model -> (Model, Cmd Msg)
update flags msg model =
case msg of
RegisterSubmit ->
case model.errorMsg of
[] ->
let
reg = { collectiveName = model.collId
, login = model.login
, password = model.pass1
, invite = model.invite
}
in
(model, Api.register flags reg SubmitResp)
_ ->
(model, Cmd.none)
SetCollId str ->
let
m = {model|collId = str}
err = validateForm m
in
({m|errorMsg = err}, Cmd.none)
SetLogin str ->
let
m = {model|login = str}
err = validateForm m
in
({m|errorMsg = err}, Cmd.none)
SetPass1 str ->
let
m = {model|pass1 = str}
err = validateForm m
in
({m|errorMsg = err}, Cmd.none)
SetPass2 str ->
let
m = {model|pass2 = str}
err = validateForm m
in
({m|errorMsg = err}, Cmd.none)
SetInvite str ->
({model | invite = if str == "" then Nothing else Just str}, Cmd.none)
ToggleShowPass1 ->
({model|showPass1 = not model.showPass1}, Cmd.none)
ToggleShowPass2 ->
({model|showPass2 = not model.showPass2}, Cmd.none)
SubmitResp (Ok r) ->
let
m = emptyModel
cmd = if r.success then Page.goto (LoginPage Nothing) else Cmd.none
in
({m|result = if r.success then Nothing else Just r}, cmd)
SubmitResp (Err err) ->
(model, Cmd.none)
validateForm: Model -> List String
validateForm model =
if model.collId == "" ||
model.login == "" ||
model.pass1 == "" ||
model.pass2 == "" then
[ "All fields are required!"]
else if model.pass1 /= model.pass2 then
["The passwords do not match."]
else
[]

View File

@@ -0,0 +1,120 @@
module Page.Register.View exposing (view)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput, onSubmit)
import Data.Flags exposing (Flags)
import Page.Register.Data exposing (..)
import Page exposing (Page(..))
view: Flags -> Model -> Html Msg
view flags model =
div [class "register-page"]
[div [class "ui centered grid"]
[div [class "row"]
[div [class "six wide column ui segment register-view"]
[h1 [class "ui cener aligned icon header"]
[i [class "umbrella icon"][]
,text "Sign up @ Docspell"
]
,Html.form [class "ui large error form raised segment", onSubmit RegisterSubmit]
[div [class "required field"]
[label [][text "Collective ID"]
,div [class "ui left icon input"]
[input [type_ "text"
,onInput SetCollId
,value model.collId
,autofocus True
][]
,i [class "users icon"][]
]
]
,div [class "required field"]
[label [][text "User Login"]
,div [class "ui left icon input"]
[input [type_ "text"
,onInput SetLogin
,value model.login
][]
,i [class "user icon"][]
]
]
,div [class "required field"
]
[label [][text "Password"]
,div [class "ui left icon action input"]
[input [type_ <| if model.showPass1 then "text" else "password"
,onInput SetPass1
,value model.pass1
][]
,i [class "lock icon"][]
,button [class "ui icon button", onClick ToggleShowPass1]
[i [class "eye icon"][]
]
]
]
,div [class "required field"
]
[label [][text "Password (repeat)"]
,div [class "ui left icon action input"]
[input [type_ <| if model.showPass2 then "text" else "password"
,onInput SetPass2
,value model.pass2
][]
,i [class "lock icon"][]
,button [class "ui icon button", onClick ToggleShowPass2]
[i [class "eye icon"][]
]
]
]
,div [classList [("field", True)
,("invisible", flags.config.signupMode /= "invite")
]]
[label [][text "Invitation Key"]
,div [class "ui left icon input"]
[input [type_ "text"
,onInput SetInvite
,model.invite |> Maybe.withDefault "" |> value
][]
,i [class "key icon"][]
]
]
,button [class "ui primary button"
,type_ "submit"
]
[text "Submit"
]
]
,(resultMessage model)
,div [class "ui very basic right aligned segment"]
[text "Already signed up? "
,a [class "ui link", Page.href (LoginPage Nothing)]
[i [class "sign-in icon"][]
,text "Sign in"
]
]
]
]
]
]
resultMessage: Model -> Html Msg
resultMessage model =
case model.result of
Just r ->
if r.success
then
div [class "ui success message"]
[text "Registration successful."
]
else
div [class "ui error message"]
[text r.message
]
Nothing ->
if List.isEmpty model.errorMsg then
span [class "invisible"][]
else
div [class "ui error message"]
(List.map (\s -> div[][text s]) model.errorMsg)

View 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)

View 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

View 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"]
]
]
]
]

View File

@@ -0,0 +1,20 @@
module Page.UserSettings.Data exposing (..)
import Comp.ChangePasswordForm
type alias Model =
{ currentTab: Maybe Tab
, changePassModel: Comp.ChangePasswordForm.Model
}
emptyModel: Model
emptyModel =
{ currentTab = Nothing
, changePassModel = Comp.ChangePasswordForm.emptyModel
}
type Tab = ChangePassTab
type Msg
= SetTab Tab
| ChangePassMsg Comp.ChangePasswordForm.Msg

View File

@@ -0,0 +1,20 @@
module Page.UserSettings.Update exposing (update)
import Page.UserSettings.Data exposing (..)
import Data.Flags exposing (Flags)
import Comp.ChangePasswordForm
update: Flags -> Msg -> Model -> (Model, Cmd Msg)
update flags msg model =
case msg of
SetTab t ->
let
m = { model | currentTab = Just t }
in
(m, Cmd.none)
ChangePassMsg m ->
let
(m2, c2) = Comp.ChangePasswordForm.update flags m model.changePassModel
in
({model | changePassModel = m2}, Cmd.map ChangePassMsg c2)

View File

@@ -0,0 +1,47 @@
module Page.UserSettings.View exposing (view)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Util.Html exposing (classActive)
import Page.UserSettings.Data exposing (..)
import Comp.ChangePasswordForm
view: Model -> Html Msg
view model =
div [class "usersetting-page ui padded grid"]
[div [class "four wide column"]
[h4 [class "ui top attached ablue-comp header"]
[text "User"
]
,div [class "ui attached fluid segment"]
[div [class "ui fluid vertical secondary menu"]
[div [classActive (model.currentTab == Just ChangePassTab) "link icon item"
,onClick (SetTab ChangePassTab)
]
[i [class "user secret icon"][]
,text "Change Password"
]
]
]
]
,div [class "twelve wide column"]
[div [class ""]
(case model.currentTab of
Just ChangePassTab -> viewChangePassword model
Nothing -> []
)
]
]
viewChangePassword: Model -> List (Html Msg)
viewChangePassword model =
[h2 [class "ui header"]
[i [class "ui user secret icon"][]
,div [class "content"]
[text "Change Password"
]
]
,Html.map ChangePassMsg (Comp.ChangePasswordForm.view model.changePassModel)
]