Improve source view and add qrcode for urls

The qr-code for urls is added so that these urls are easy to copy into
a phone. Then buttons for copying them into the clipboard have been
added.
This commit is contained in:
Eike Kettner 2020-08-03 23:58:41 +02:00
parent ed8f16fe73
commit dbd27057d1
9 changed files with 257 additions and 148 deletions

View File

@ -616,9 +616,10 @@ def compileElm(
def createWebjarSource(wj: Seq[ModuleID], out: File): Seq[File] = {
val target = out / "Webjars.scala"
val badChars = "-.".toSet
val fields = wj
.map(m =>
s"""val ${m.name.toLowerCase.filter(_ != '-')} = "/${m.name}/${m.revision}" """
s"""val ${m.name.toLowerCase.filter(c => !badChars.contains(c))} = "/${m.name}/${m.revision}" """
)
.mkString("\n\n")
val content = s"""package docspell.restserver.webapp

View File

@ -126,6 +126,7 @@ object TemplateRoutes {
Seq(
"/app/assets" + Webjars.jquery + "/jquery.min.js",
"/app/assets" + Webjars.semanticui + "/semantic.min.js",
"/app/assets" + Webjars.clipboardjs + "/clipboard.min.js",
s"/app/assets/docspell-webapp/${BuildInfo.version}/docspell-app.js"
),
s"/app/assets/docspell-webapp/${BuildInfo.version}/favicon",

View File

@ -23,7 +23,6 @@ import Html.Attributes exposing (..)
import Html.Events exposing (onCheck, onInput)
import Http
import Markdown
import QRCode
import Util.Folder exposing (mkFolderOption)
@ -232,14 +231,6 @@ update flags msg model =
--- View
qrCodeView : String -> Html msg
qrCodeView message =
QRCode.encode message
|> Result.map QRCode.toSvg
|> Result.withDefault
(Html.text "Error generating QR-Code")
view : Flags -> UiSettings -> Model -> Html Msg
view flags settings model =
let
@ -310,55 +301,6 @@ disappear then.
"""
]
]
, urlInfoMessage flags model
]
urlInfoMessage : Flags -> Model -> Html Msg
urlInfoMessage flags model =
let
appUrl =
flags.config.baseUrl ++ "/app/upload/" ++ model.source.id
apiUrl =
flags.config.baseUrl ++ "/api/v1/open/upload/item/" ++ model.source.id
in
div
[ classList
[ ( "ui info icon message", True )
, ( "hidden", not model.enabled || model.source.id == "" )
]
]
[ div [ class "content" ]
[ h3 [ class "ui dividingheader" ]
[ i [ class "info icon" ] []
, text "Public Uploads"
]
, p []
[ text "This source defines URLs that can be used by anyone to send files to "
, text "you. There is a web page that you can share or the API url can be used "
, text "with other clients."
]
, dl [ class "ui list" ]
[ dt [] [ text "Public Upload Page" ]
, dd []
[ a [ href appUrl, target "_blank" ] [ code [] [ text appUrl ] ]
]
]
, dl [ class "ui list" ]
[ dt [] [ text "Public API Upload URL" ]
, dd []
[ p []
[ code []
[ text apiUrl
]
]
, p [ class "qr-code" ]
[ qrCodeView apiUrl
]
]
]
]
]

View File

@ -8,10 +8,10 @@ module Comp.SourceManage exposing
import Api
import Api.Model.BasicResult exposing (BasicResult)
import Api.Model.Source
import Api.Model.Source exposing (Source)
import Api.Model.SourceList exposing (SourceList)
import Comp.SourceForm
import Comp.SourceTable
import Comp.SourceTable exposing (SelectMode(..))
import Comp.YesNoDimmer
import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
@ -19,53 +19,64 @@ import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onSubmit)
import Http
import Ports
import QRCode
import Util.Http
import Util.Maybe
type alias Model =
{ tableModel : Comp.SourceTable.Model
, formModel : Comp.SourceForm.Model
, viewMode : ViewMode
{ formModel : Comp.SourceForm.Model
, viewMode : SelectMode
, formError : Maybe String
, loading : Bool
, deleteConfirm : Comp.YesNoDimmer.Model
, sources : List Source
}
type ViewMode
= Table
| Form
init : Flags -> ( Model, Cmd Msg )
init flags =
let
( fm, fc ) =
Comp.SourceForm.init flags
in
( { tableModel = Comp.SourceTable.emptyModel
, formModel = fm
, viewMode = Table
( { formModel = fm
, viewMode = None
, formError = Nothing
, loading = False
, deleteConfirm = Comp.YesNoDimmer.emptyModel
, sources = []
}
, Cmd.map FormMsg fc
, Cmd.batch
[ Cmd.map FormMsg fc
, Ports.initClipboard appClipboardData
, Ports.initClipboard apiClipboardData
]
)
appClipboardData : ( String, String )
appClipboardData =
( "app-url", "#app-url-copy-to-clipboard-btn" )
apiClipboardData : ( String, String )
apiClipboardData =
( "api-url", "#api-url-copy-to-clipboard-btn" )
type Msg
= TableMsg Comp.SourceTable.Msg
| FormMsg Comp.SourceForm.Msg
| LoadSources
| SourceResp (Result Http.Error SourceList)
| SetViewMode ViewMode
| InitNewSource
| Submit
| SubmitResp (Result Http.Error BasicResult)
| YesNoMsg Comp.YesNoDimmer.Msg
| RequestDelete
| SetTableView
@ -77,29 +88,31 @@ update flags msg model =
case msg of
TableMsg m ->
let
( tm, tc ) =
Comp.SourceTable.update flags m model.tableModel
( tc, sel ) =
Comp.SourceTable.update flags m
( m2, c2 ) =
( { model
| tableModel = tm
, viewMode = Maybe.map (\_ -> Form) tm.selected |> Maybe.withDefault Table
| viewMode = sel
, formError =
if Util.Maybe.nonEmpty tm.selected then
Nothing
if Comp.SourceTable.isEdit sel then
model.formError
else
model.formError
Nothing
}
, Cmd.map TableMsg tc
)
( m3, c3 ) =
case tm.selected of
Just source ->
case sel of
Edit source ->
update flags (FormMsg (Comp.SourceForm.SetSource source)) m2
Nothing ->
Display _ ->
( m2, Cmd.none )
None ->
( m2, Cmd.none )
in
( m3, Cmd.batch [ c2, c3 ] )
@ -115,34 +128,27 @@ update flags msg model =
( { model | loading = True }, Api.getSources flags SourceResp )
SourceResp (Ok sources) ->
let
m2 =
{ model | viewMode = Table, loading = False }
in
update flags (TableMsg (Comp.SourceTable.SetSources sources.items)) m2
( { model
| viewMode = None
, loading = False
, sources = sources.items
}
, Cmd.none
)
SourceResp (Err _) ->
( { model | loading = False }, Cmd.none )
SetViewMode m ->
let
m2 =
{ model | viewMode = m }
in
case m of
Table ->
update flags (TableMsg Comp.SourceTable.Deselect) m2
Form ->
( m2, Cmd.none )
SetTableView ->
( { model | viewMode = None }, Cmd.none )
InitNewSource ->
let
nm =
{ model | viewMode = Form, formError = Nothing }
source =
Api.Model.Source.empty
nm =
{ model | viewMode = Edit source, formError = Nothing }
in
update flags (FormMsg (Comp.SourceForm.SetSource source)) nm
@ -164,7 +170,7 @@ update flags msg model =
if res.success then
let
( m2, c2 ) =
update flags (SetViewMode Table) model
update flags SetTableView model
( m3, c3 ) =
update flags LoadSources m2
@ -202,13 +208,25 @@ update flags msg model =
--- View
qrCodeView : String -> Html msg
qrCodeView message =
QRCode.encode message
|> Result.map QRCode.toSvg
|> Result.withDefault
(Html.text "Error generating QR-Code")
view : Flags -> UiSettings -> Model -> Html Msg
view flags settings model =
if model.viewMode == Table then
viewTable model
case model.viewMode of
None ->
viewTable model
else
div [] (viewForm flags settings model)
Edit _ ->
div [] (viewForm flags settings model)
Display source ->
viewLinks flags settings source
viewTable : Model -> Html Msg
@ -218,7 +236,7 @@ viewTable model =
[ i [ class "plus icon" ] []
, text "Create new"
]
, Html.map TableMsg (Comp.SourceTable.view model.tableModel)
, Html.map TableMsg (Comp.SourceTable.view model.sources)
, div
[ classList
[ ( "ui dimmer", True )
@ -230,6 +248,112 @@ viewTable model =
]
viewLinks : Flags -> UiSettings -> Source -> Html Msg
viewLinks flags _ source =
let
appUrl =
flags.config.baseUrl ++ "/app/upload/" ++ source.id
apiUrl =
flags.config.baseUrl ++ "/api/v1/open/upload/item/" ++ source.id
in
div
[]
[ h3 [ class "ui dividing header" ]
[ text "Public Uploads: "
, text source.abbrev
, div [ class "sub header" ]
[ text source.id
]
]
, p []
[ text "This source defines URLs that can be used by anyone to send files to "
, text "you. There is a web page that you can share or the API url can be used "
, text "with other clients."
]
, p []
[ text "There have been "
, String.fromInt source.counter |> text
, text " items created through this source."
]
, h4 [ class "ui header" ]
[ text "Public Upload Page"
]
, div [ class "ui attached message" ]
[ div [ class "ui fluid left action input" ]
[ a
[ class "ui left icon button"
, title "Copy to clipboard"
, href "#"
, Tuple.second appClipboardData
|> String.dropLeft 1
|> id
, attribute "data-clipboard-target" "#app-url"
]
[ i [ class "copy icon" ] []
]
, a
[ class "ui icon button"
, href appUrl
, target "_blank"
, title "Open in new tab/window"
]
[ i [ class "link external icon" ] []
]
, input
[ type_ "text"
, id "app-url"
, value appUrl
, readonly True
]
[]
]
]
, div [ class "ui attached segment" ]
[ div [ class "qr-code" ]
[ qrCodeView appUrl
]
]
, h4 [ class "ui header" ]
[ text "Public API Upload URL"
]
, div [ class "ui attached message" ]
[ div [ class "ui fluid left action input" ]
[ a
[ class "ui left icon button"
, title "Copy to clipboard"
, href "#"
, Tuple.second apiClipboardData
|> String.dropLeft 1
|> id
, attribute "data-clipboard-target" "#api-url"
]
[ i [ class "copy icon" ] []
]
, input
[ type_ "text"
, value apiUrl
, readonly True
, id "api-url"
]
[]
]
]
, div [ class "ui attached segment" ]
[ div [ class "qr-code" ]
[ qrCodeView apiUrl
]
]
, div [ class "ui divider" ] []
, button
[ class "ui button"
, onClick SetTableView
]
[ text "Back"
]
]
viewForm : Flags -> UiSettings -> Model -> List (Html Msg)
viewForm flags settings model =
let
@ -264,7 +388,7 @@ viewForm flags settings model =
, button [ class "ui primary button", type_ "submit" ]
[ text "Submit"
]
, a [ class "ui secondary button", onClick (SetViewMode Table), href "" ]
, a [ class "ui secondary button", onClick SetTableView, href "" ]
[ text "Cancel"
]
, if not newSource then

View File

@ -1,7 +1,7 @@
module Comp.SourceTable exposing
( Model
, Msg(..)
, emptyModel
( Msg
, SelectMode(..)
, isEdit
, update
, view
)
@ -14,44 +14,47 @@ import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
type alias Model =
{ sources : List Source
, selected : Maybe Source
}
type SelectMode
= Edit Source
| Display Source
| None
emptyModel : Model
emptyModel =
{ sources = []
, selected = Nothing
}
isEdit : SelectMode -> Bool
isEdit m =
case m of
Edit _ ->
True
Display _ ->
False
None ->
False
type Msg
= SetSources (List Source)
| Select Source
| Deselect
= Select Source
| Show Source
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
update _ msg model =
update : Flags -> Msg -> ( Cmd Msg, SelectMode )
update _ msg =
case msg of
SetSources list ->
( { model | sources = list, selected = Nothing }, Cmd.none )
Select source ->
( { model | selected = Just source }, Cmd.none )
( Cmd.none, Edit source )
Deselect ->
( { model | selected = Nothing }, Cmd.none )
Show source ->
( Cmd.none, Display source )
view : Model -> Html Msg
view model =
table [ class "ui selectable table" ]
view : List Source -> Html Msg
view sources =
table [ class "ui table" ]
[ thead []
[ tr []
[ th [ class "collapsing" ] [ text "Abbrev" ]
[ th [ class "collapsing" ] []
, th [ class "collapsing" ] [ text "Abbrev" ]
, th [ class "collapsing" ] [ text "Enabled" ]
, th [ class "collapsing" ] [ text "Counter" ]
, th [ class "collapsing" ] [ text "Priority" ]
@ -59,17 +62,37 @@ view model =
]
]
, tbody []
(List.map (renderSourceLine model) model.sources)
(List.map renderSourceLine sources)
]
renderSourceLine : Model -> Source -> Html Msg
renderSourceLine model source =
renderSourceLine : Source -> Html Msg
renderSourceLine source =
tr
[ classList [ ( "active", model.selected == Just source ) ]
, onClick (Select source)
]
[]
[ td [ class "collapsing" ]
[ a
[ class "ui basic tiny primary button"
, href "#"
, onClick (Select source)
]
[ i [ class "edit icon" ] []
, text "Edit"
]
, a
[ classList
[ ( "ui basic tiny primary button", True )
, ( "disabled", not source.enabled )
]
, href "#"
, disabled (not source.enabled)
, onClick (Show source)
]
[ i [ class "eye icon" ] []
, text "Show"
]
]
, td [ class "collapsing" ]
[ text source.abbrev
]
, td [ class "collapsing" ]

View File

@ -1,5 +1,6 @@
port module Ports exposing
( getUiSettings
, initClipboard
, loadUiSettings
, onUiSettingsSaved
, removeAccount
@ -78,3 +79,6 @@ getUiSettings flags =
Nothing ->
Cmd.none
port initClipboard : ( String, String ) -> Cmd msg

View File

@ -105,10 +105,12 @@
}
.default-layout .qr-code svg {
border: 1px solid #ccc;
width: 200px;
height: 200px;
}
.default-layout .qr-code.bordered svg {
border: 1px solid #ccc;
}
.markdown-preview {
overflow: auto;

View File

@ -83,3 +83,13 @@ elmApp.ports.requestUiSettings.subscribe(function(args) {
}
}
});
var docspell_clipboards = {};
elmApp.ports.initClipboard.subscribe(function(args) {
var page = args[0];
if (!docspell_clipboards[page]) {
var sel = args[1];
docspell_clipboards[page] = new ClipboardJS(sel);
}
});

View File

@ -9,6 +9,7 @@ object Dependencies {
val BitpeaceVersion = "0.5.0"
val CalevVersion = "0.3.1"
val CirceVersion = "0.13.0"
val ClipboardJsVersion = "2.0.6"
val DoobieVersion = "0.9.0"
val EmilVersion = "0.6.2"
val FastparseVersion = "2.1.3"
@ -245,10 +246,11 @@ object Dependencies {
val betterMonadicFor = "com.olegpy" %% "better-monadic-for" % BetterMonadicForVersion
val webjars = Seq(
"org.webjars" % "swagger-ui" % SwaggerUIVersion,
"org.webjars" % "Semantic-UI" % SemanticUIVersion,
"org.webjars" % "jquery" % JQueryVersion,
"org.webjars" % "viewerjs" % ViewerJSVersion
"org.webjars" % "swagger-ui" % SwaggerUIVersion,
"org.webjars" % "Semantic-UI" % SemanticUIVersion,
"org.webjars" % "jquery" % JQueryVersion,
"org.webjars" % "viewerjs" % ViewerJSVersion,
"org.webjars" % "clipboard.js" % ClipboardJsVersion
)
val icu4j = Seq(