Merge pull request #707 from eikek/reprocess

Reprocess
This commit is contained in:
mergify[bot] 2021-03-12 00:05:30 +00:00 committed by GitHub
commit cacad17df6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 448 additions and 108 deletions

View File

@ -145,7 +145,8 @@ object OUpload {
data.meta.validFileTypes, data.meta.validFileTypes,
data.meta.skipDuplicates, data.meta.skipDuplicates,
data.meta.fileFilter.some, data.meta.fileFilter.some,
data.meta.tags.some data.meta.tags.some,
false
) )
args = args =
if (data.multiple) files.map(f => ProcessItemArgs(meta, List(f))) if (data.multiple) files.map(f => ProcessItemArgs(meta, List(f)))

View File

@ -13,6 +13,8 @@ import io.circe.generic.semiauto._
* *
* If the `itemId' is set to some value, the item is tried to load to * If the `itemId' is set to some value, the item is tried to load to
* ammend with the given files. Otherwise a new item is created. * ammend with the given files. Otherwise a new item is created.
*
* It is also re-used by the 'ReProcessItem' task.
*/ */
case class ProcessItemArgs(meta: ProcessMeta, files: List[File]) { case class ProcessItemArgs(meta: ProcessMeta, files: List[File]) {
@ -24,6 +26,8 @@ case class ProcessItemArgs(meta: ProcessMeta, files: List[File]) {
case _ => s"${files.size} files from ${meta.sourceAbbrev}" case _ => s"${files.size} files from ${meta.sourceAbbrev}"
} }
def isNormalProcessing: Boolean =
!meta.reprocess
} }
object ProcessItemArgs { object ProcessItemArgs {
@ -40,7 +44,8 @@ object ProcessItemArgs {
validFileTypes: Seq[MimeType], validFileTypes: Seq[MimeType],
skipDuplicate: Boolean, skipDuplicate: Boolean,
fileFilter: Option[Glob], fileFilter: Option[Glob],
tags: Option[List[String]] tags: Option[List[String]],
reprocess: Boolean
) )
object ProcessMeta { object ProcessMeta {

View File

@ -113,7 +113,7 @@ object JoexAppImpl {
.withTask( .withTask(
JobTask.json( JobTask.json(
ReProcessItemArgs.taskName, ReProcessItemArgs.taskName,
ReProcessItem[F](cfg, fts, analyser, regexNer), ReProcessItem[F](cfg, fts, itemOps, analyser, regexNer),
ReProcessItem.onCancel[F] ReProcessItem.onCancel[F]
) )
) )

View File

@ -40,14 +40,14 @@ object ConvertPdf {
Task { ctx => Task { ctx =>
def convert(ra: RAttachment): F[(RAttachment, Option[RAttachmentMeta])] = def convert(ra: RAttachment): F[(RAttachment, Option[RAttachmentMeta])] =
isConverted(ctx)(ra).flatMap { isConverted(ctx)(ra).flatMap {
case true => case true if ctx.args.isNormalProcessing =>
ctx.logger.info( ctx.logger.info(
s"Conversion to pdf already done for attachment ${ra.name}." s"Conversion to pdf already done for attachment ${ra.name}."
) *> ) *>
ctx.store ctx.store
.transact(RAttachmentMeta.findById(ra.id)) .transact(RAttachmentMeta.findById(ra.id))
.map(rmOpt => (ra, rmOpt)) .map(rmOpt => (ra, rmOpt))
case false => case _ =>
findMime(ctx)(ra).flatMap(m => findMime(ctx)(ra).flatMap(m =>
convertSafe(cfg, JsoupSanitizer.clean, ctx, item)(ra, m) convertSafe(cfg, JsoupSanitizer.clean, ctx, item)(ra, m)
) )

View File

@ -10,11 +10,19 @@ import docspell.store.records.RItem
object LinkProposal { object LinkProposal {
def apply[F[_]: Sync](data: ItemData): Task[F, ProcessItemArgs, ItemData] = def onlyNew[F[_]: Sync](data: ItemData): Task[F, ProcessItemArgs, ItemData] =
if (data.item.state.isValid) if (data.item.state.isValid)
Task Task
.log[F, ProcessItemArgs](_.debug(s"Not linking proposals on existing item")) .log[F, ProcessItemArgs](_.debug(s"Not linking proposals on existing item"))
.map(_ => data) .map(_ => data)
else
LinkProposal[F](data)
def apply[F[_]: Sync](data: ItemData): Task[F, ProcessItemArgs, ItemData] =
if (data.item.state == ItemState.Confirmed)
Task
.log[F, ProcessItemArgs](_.debug(s"Not linking proposals on confirmed item"))
.map(_ => data)
else else
Task { ctx => Task { ctx =>
val proposals = data.finalProposals val proposals = data.finalProposals

View File

@ -22,8 +22,8 @@ object ProcessItem {
ExtractArchive(item) ExtractArchive(item)
.flatMap(Task.setProgress(20)) .flatMap(Task.setProgress(20))
.flatMap(processAttachments0(cfg, fts, analyser, regexNer, (40, 60, 80))) .flatMap(processAttachments0(cfg, fts, analyser, regexNer, (40, 60, 80)))
.flatMap(LinkProposal[F]) .flatMap(LinkProposal.onlyNew[F])
.flatMap(SetGivenData[F](itemOps)) .flatMap(SetGivenData.onlyNew[F](itemOps))
.flatMap(Task.setProgress(99)) .flatMap(Task.setProgress(99))
.flatMap(RemoveEmptyItem(itemOps)) .flatMap(RemoveEmptyItem(itemOps))

View File

@ -5,6 +5,7 @@ import cats.effect._
import cats.implicits._ import cats.implicits._
import docspell.analysis.TextAnalyser import docspell.analysis.TextAnalyser
import docspell.backend.ops.OItem
import docspell.common._ import docspell.common._
import docspell.ftsclient.FtsClient import docspell.ftsclient.FtsClient
import docspell.joex.Config import docspell.joex.Config
@ -22,12 +23,17 @@ object ReProcessItem {
def apply[F[_]: ConcurrentEffect: ContextShift]( def apply[F[_]: ConcurrentEffect: ContextShift](
cfg: Config, cfg: Config,
fts: FtsClient[F], fts: FtsClient[F],
itemOps: OItem[F],
analyser: TextAnalyser[F], analyser: TextAnalyser[F],
regexNer: RegexNerFile[F] regexNer: RegexNerFile[F]
): Task[F, Args, Unit] = ): Task[F, Args, Unit] =
loadItem[F] Task
.flatMap(safeProcess[F](cfg, fts, analyser, regexNer)) .log[F, Args](_.info("===== Start reprocessing ======"))
.map(_ => ()) .flatMap(_ =>
loadItem[F]
.flatMap(safeProcess[F](cfg, fts, itemOps, analyser, regexNer))
.map(_ => ())
)
def onCancel[F[_]]: Task[F, Args, Unit] = def onCancel[F[_]]: Task[F, Args, Unit] =
logWarn("Now cancelling re-processing.") logWarn("Now cancelling re-processing.")
@ -58,6 +64,11 @@ object ReProcessItem {
a.copy(fileId = src.fileId, name = src.name) a.copy(fileId = src.fileId, name = src.name)
} }
) )
_ <- OptionT.liftF(
ctx.logger.debug(
s"Loaded item and ${attachSrc.size} attachments to reprocess"
)
)
} yield ItemData( } yield ItemData(
item, item,
attachSrc, attachSrc,
@ -76,6 +87,7 @@ object ReProcessItem {
def processFiles[F[_]: ConcurrentEffect: ContextShift]( def processFiles[F[_]: ConcurrentEffect: ContextShift](
cfg: Config, cfg: Config,
fts: FtsClient[F], fts: FtsClient[F],
itemOps: OItem[F],
analyser: TextAnalyser[F], analyser: TextAnalyser[F],
regexNer: RegexNerFile[F], regexNer: RegexNerFile[F],
data: ItemData data: ItemData
@ -89,13 +101,14 @@ object ReProcessItem {
data.item.cid, data.item.cid,
args.itemId.some, args.itemId.some,
lang, lang,
None, //direction None, //direction
"", //source-id data.item.source, //source-id
None, //folder None, //folder
Seq.empty, Seq.empty,
false, false,
None, None,
None None,
true
), ),
Nil Nil
).pure[F] ).pure[F]
@ -103,6 +116,8 @@ object ReProcessItem {
getLanguage[F].flatMap { lang => getLanguage[F].flatMap { lang =>
ProcessItem ProcessItem
.processAttachments[F](cfg, fts, analyser, regexNer)(data) .processAttachments[F](cfg, fts, analyser, regexNer)(data)
.flatMap(LinkProposal[F])
.flatMap(SetGivenData[F](itemOps))
.contramap[Args](convertArgs(lang)) .contramap[Args](convertArgs(lang))
} }
} }
@ -121,12 +136,13 @@ object ReProcessItem {
def safeProcess[F[_]: ConcurrentEffect: ContextShift]( def safeProcess[F[_]: ConcurrentEffect: ContextShift](
cfg: Config, cfg: Config,
fts: FtsClient[F], fts: FtsClient[F],
itemOps: OItem[F],
analyser: TextAnalyser[F], analyser: TextAnalyser[F],
regexNer: RegexNerFile[F] regexNer: RegexNerFile[F]
)(data: ItemData): Task[F, Args, ItemData] = )(data: ItemData): Task[F, Args, ItemData] =
isLastRetry[F].flatMap { isLastRetry[F].flatMap {
case true => case true =>
processFiles[F](cfg, fts, analyser, regexNer, data).attempt processFiles[F](cfg, fts, itemOps, analyser, regexNer, data).attempt
.flatMap({ .flatMap({
case Right(d) => case Right(d) =>
Task.pure(d) Task.pure(d)
@ -136,7 +152,7 @@ object ReProcessItem {
).andThen(_ => Sync[F].raiseError(ex)) ).andThen(_ => Sync[F].raiseError(ex))
}) })
case false => case false =>
processFiles[F](cfg, fts, analyser, regexNer, data) processFiles[F](cfg, fts, itemOps, analyser, regexNer, data)
} }
private def logWarn[F[_]](msg: => String): Task[F, Args, Unit] = private def logWarn[F[_]](msg: => String): Task[F, Args, Unit] =

View File

@ -8,13 +8,20 @@ import docspell.common._
import docspell.joex.scheduler.Task import docspell.joex.scheduler.Task
object SetGivenData { object SetGivenData {
type Args = ProcessItemArgs
def apply[F[_]: Sync]( def onlyNew[F[_]: Sync](ops: OItem[F])(data: ItemData): Task[F, Args, ItemData] =
ops: OItem[F]
)(data: ItemData): Task[F, ProcessItemArgs, ItemData] =
if (data.item.state.isValid) if (data.item.state.isValid)
Task Task
.log[F, ProcessItemArgs](_.debug(s"Not setting data on existing item")) .log[F, Args](_.debug(s"Not setting data on existing item"))
.map(_ => data)
else
SetGivenData[F](ops)(data)
def apply[F[_]: Sync](ops: OItem[F])(data: ItemData): Task[F, Args, ItemData] =
if (data.item.state == ItemState.Confirmed)
Task
.log[F, Args](_.debug(s"Not setting data on confirmed item"))
.map(_ => data) .map(_ => data)
else else
setFolder(data, ops).flatMap(d => setTags[F](d, ops)) setFolder(data, ops).flatMap(d => setTags[F](d, ops))
@ -22,7 +29,7 @@ object SetGivenData {
private def setFolder[F[_]: Sync]( private def setFolder[F[_]: Sync](
data: ItemData, data: ItemData,
ops: OItem[F] ops: OItem[F]
): Task[F, ProcessItemArgs, ItemData] = ): Task[F, Args, ItemData] =
Task { ctx => Task { ctx =>
val itemId = data.item.id val itemId = data.item.id
val folderId = ctx.args.meta.folderId val folderId = ctx.args.meta.folderId
@ -41,7 +48,7 @@ object SetGivenData {
private def setTags[F[_]: Sync]( private def setTags[F[_]: Sync](
data: ItemData, data: ItemData,
ops: OItem[F] ops: OItem[F]
): Task[F, ProcessItemArgs, ItemData] = ): Task[F, Args, ItemData] =
Task { ctx => Task { ctx =>
val itemId = data.item.id val itemId = data.item.id
val collective = ctx.args.meta.collective val collective = ctx.args.meta.collective

View File

@ -84,10 +84,10 @@ object TextExtraction {
val rm = item.findOrCreate(ra.id, lang) val rm = item.findOrCreate(ra.id, lang)
rm.content match { rm.content match {
case Some(_) => case Some(_) if ctx.args.isNormalProcessing =>
ctx.logger.info("TextExtraction skipped, since text is already available.") *> ctx.logger.info("TextExtraction skipped, since text is already available.") *>
makeTextData((rm, Nil)).pure[F] makeTextData((rm, Nil)).pure[F]
case None => case _ =>
extractTextToMeta[F](ctx, cfg, lang, item)(ra) extractTextToMeta[F](ctx, cfg, lang, item)(ra)
.map(makeTextData) .map(makeTextData)
} }

View File

@ -2113,7 +2113,11 @@ paths:
summary: Start reprocessing the files of the item. summary: Start reprocessing the files of the item.
description: | description: |
This submits a job that will re-process the files (either all This submits a job that will re-process the files (either all
or the ones specified) of the item and replace the metadata. or the ones specified) of the item and replace their metadata.
If the item is not in "confirmed" state, its associated metada
is also updated. Otherwise only the file metadata is updated
(text analysis).
security: security:
- authTokenHeader: [] - authTokenHeader: []
parameters: parameters:
@ -2515,7 +2519,8 @@ paths:
description: | description: |
Given a list of item-ids, submits all these items for Given a list of item-ids, submits all these items for
reprocessing. All attachments of these items will be reprocessing. All attachments of these items will be
reprocessed. Item metadata is not changed. reprocessed. Item metadata may be changed if an item is not
confirmed. Confirmed items are not changed.
security: security:
- authTokenHeader: [] - authTokenHeader: []
requestBody: requestBody:

View File

@ -89,6 +89,8 @@ module Api exposing
, register , register
, removeMember , removeMember
, removeTagsMultiple , removeTagsMultiple
, reprocessItem
, reprocessMultiple
, sendMail , sendMail
, setAttachmentName , setAttachmentName
, setCollectiveSettings , setCollectiveSettings
@ -1423,6 +1425,20 @@ getJobQueueStateTask flags =
--- Item (Mulit Edit) --- Item (Mulit Edit)
reprocessMultiple :
Flags
-> Set String
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
reprocessMultiple flags items receive =
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/items/reprocess"
, account = getAccount flags
, body = Http.jsonBody (Api.Model.IdList.encode (Set.toList items |> IdList))
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
confirmMultiple : confirmMultiple :
Flags Flags
-> Set String -> Set String
@ -1637,6 +1653,21 @@ deleteAllItems flags ids receive =
--- Item --- Item
reprocessItem :
Flags
-> String
-> List String
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
reprocessItem flags itemId attachIds receive =
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ itemId ++ "/reprocess"
, account = getAccount flags
, body = Http.jsonBody (Api.Model.IdList.encode (IdList attachIds))
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
attachmentPreviewURL : String -> String attachmentPreviewURL : String -> String
attachmentPreviewURL id = attachmentPreviewURL id =
"/api/v1/sec/attachment/" ++ id ++ "/preview?withFallback=true" "/api/v1/sec/attachment/" ++ id ++ "/preview?withFallback=true"

View File

@ -0,0 +1,76 @@
module Comp.ConfirmModal exposing
( Settings
, defaultSettings
, view
)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Styles as S
type alias Settings msg =
{ enabled : Bool
, extraClass : String
, headerIcon : String
, headerClass : String
, confirmText : String
, cancelText : String
, message : String
, confirm : msg
, cancel : msg
}
defaultSettings : msg -> msg -> String -> Settings msg
defaultSettings confirm cancel confirmMsg =
{ enabled = True
, extraClass = ""
, headerIcon = "fa fa-exclamation-circle mr-3"
, headerClass = "text-2xl font-bold text-center w-full"
, confirmText = "Ok"
, cancelText = "Cancel"
, message = confirmMsg
, confirm = confirm
, cancel = cancel
}
view : Settings msg -> Html msg
view settings =
div
[ class S.dimmer
, class settings.extraClass
, classList
[ ( "hidden", not settings.enabled )
]
]
[ div [ class settings.headerClass ]
[ i
[ class settings.headerIcon
, class "text-gray-200 font-semibold"
, classList [ ( "hidden", settings.headerClass == "" ) ]
]
[]
, span [ class "text-gray-200 font-semibold" ]
[ text settings.message
]
]
, div [ class "flex flex-row space-x-2 text-xs mt-2" ]
[ a
[ class (S.primaryButton ++ "block font-semibold")
, href "#"
, onClick settings.confirm
]
[ text settings.confirmText
]
, a
[ class (S.secondaryButton ++ "block font-semibold")
, href "#"
, onClick settings.cancel
]
[ text settings.cancelText
]
]
]

View File

@ -28,6 +28,7 @@ import Api.Model.SentMails exposing (SentMails)
import Api.Model.Tag exposing (Tag) import Api.Model.Tag exposing (Tag)
import Api.Model.TagList exposing (TagList) import Api.Model.TagList exposing (TagList)
import Comp.AttachmentMeta import Comp.AttachmentMeta
import Comp.ConfirmModal
import Comp.CustomFieldMultiInput import Comp.CustomFieldMultiInput
import Comp.DatePicker import Comp.DatePicker
import Comp.DetailEdit import Comp.DetailEdit
@ -72,7 +73,7 @@ type alias Model =
, nameSaveThrottle : Throttle Msg , nameSaveThrottle : Throttle Msg
, notesModel : Maybe String , notesModel : Maybe String
, notesField : NotesField , notesField : NotesField
, deleteItemConfirm : Comp.YesNoDimmer.Model , itemModal : Maybe (Comp.ConfirmModal.Settings Msg)
, itemDatePicker : DatePicker , itemDatePicker : DatePicker
, itemDate : Maybe Int , itemDate : Maybe Int
, itemProposals : ItemProposals , itemProposals : ItemProposals
@ -87,7 +88,7 @@ type alias Model =
, attachMeta : Dict String Comp.AttachmentMeta.Model , attachMeta : Dict String Comp.AttachmentMeta.Model
, attachMetaOpen : Bool , attachMetaOpen : Bool
, pdfNativeView : Maybe Bool , pdfNativeView : Maybe Bool
, deleteAttachConfirm : Comp.YesNoDimmer.Model , attachModal : Maybe (Comp.ConfirmModal.Settings Msg)
, addFilesOpen : Bool , addFilesOpen : Bool
, addFilesModel : Comp.Dropzone.Model , addFilesModel : Comp.Dropzone.Model
, selectedFiles : List File , selectedFiles : List File
@ -180,7 +181,7 @@ emptyModel =
, nameSaveThrottle = Throttle.create 1 , nameSaveThrottle = Throttle.create 1
, notesModel = Nothing , notesModel = Nothing
, notesField = ViewNotes , notesField = ViewNotes
, deleteItemConfirm = Comp.YesNoDimmer.emptyModel , itemModal = Nothing
, itemDatePicker = Comp.DatePicker.emptyModel , itemDatePicker = Comp.DatePicker.emptyModel
, itemDate = Nothing , itemDate = Nothing
, itemProposals = Api.Model.ItemProposals.empty , itemProposals = Api.Model.ItemProposals.empty
@ -195,7 +196,7 @@ emptyModel =
, attachMeta = Dict.empty , attachMeta = Dict.empty
, attachMetaOpen = False , attachMetaOpen = False
, pdfNativeView = Nothing , pdfNativeView = Nothing
, deleteAttachConfirm = Comp.YesNoDimmer.emptyModel , attachModal = Nothing
, addFilesOpen = False , addFilesOpen = False
, addFilesModel = Comp.Dropzone.init [] , addFilesModel = Comp.Dropzone.init []
, selectedFiles = [] , selectedFiles = []
@ -247,7 +248,8 @@ type Msg
| SetDueDateSuggestion Int | SetDueDateSuggestion Int
| ItemDatePickerMsg Comp.DatePicker.Msg | ItemDatePickerMsg Comp.DatePicker.Msg
| DueDatePickerMsg Comp.DatePicker.Msg | DueDatePickerMsg Comp.DatePicker.Msg
| DeleteItemConfirm Comp.YesNoDimmer.Msg | DeleteItemConfirmed
| ItemModalCancelled
| RequestDelete | RequestDelete
| SaveResp (Result Http.Error BasicResult) | SaveResp (Result Http.Error BasicResult)
| DeleteResp (Result Http.Error BasicResult) | DeleteResp (Result Http.Error BasicResult)
@ -265,7 +267,8 @@ type Msg
| AttachMetaMsg String Comp.AttachmentMeta.Msg | AttachMetaMsg String Comp.AttachmentMeta.Msg
| TogglePdfNativeView Bool | TogglePdfNativeView Bool
| RequestDeleteAttachment String | RequestDeleteAttachment String
| DeleteAttachConfirm String Comp.YesNoDimmer.Msg | DeleteAttachConfirmed String
| AttachModalCancelled
| DeleteAttachResp (Result Http.Error BasicResult) | DeleteAttachResp (Result Http.Error BasicResult)
| AddFilesToggle | AddFilesToggle
| AddFilesMsg Comp.Dropzone.Msg | AddFilesMsg Comp.Dropzone.Msg
@ -304,6 +307,11 @@ type Msg
| ToggleAttachmentDropdown | ToggleAttachmentDropdown
| ToggleAkkordionTab String | ToggleAkkordionTab String
| ToggleOpenAllAkkordionTabs | ToggleOpenAllAkkordionTabs
| RequestReprocessFile String
| ReprocessFileConfirmed String
| ReprocessFileResp (Result Http.Error BasicResult)
| RequestReprocessItem
| ReprocessItemConfirmed
type SaveNameState type SaveNameState

View File

@ -3,6 +3,7 @@ module Comp.ItemDetail.SingleAttachment exposing (view)
import Api import Api
import Api.Model.Attachment exposing (Attachment) import Api.Model.Attachment exposing (Attachment)
import Comp.AttachmentMeta import Comp.AttachmentMeta
import Comp.ConfirmModal
import Comp.ItemDetail.Model import Comp.ItemDetail.Model
exposing exposing
( Model ( Model
@ -11,7 +12,6 @@ import Comp.ItemDetail.Model
, SaveNameState(..) , SaveNameState(..)
) )
import Comp.MenuBar as MB import Comp.MenuBar as MB
import Comp.YesNoDimmer
import Data.UiSettings exposing (UiSettings) import Data.UiSettings exposing (UiSettings)
import Dict import Dict
import Html exposing (..) import Html exposing (..)
@ -37,12 +37,7 @@ view settings model pos attach =
[ ( "hidden", not (attachmentVisible model pos) ) [ ( "hidden", not (attachmentVisible model pos) )
] ]
] ]
[ Html.map (DeleteAttachConfirm attach.id) [ renderModal model
(Comp.YesNoDimmer.viewN
True
(Comp.YesNoDimmer.defaultSettings2 "Really delete this file?")
model.deleteAttachConfirm
)
, div , div
[ class "flex flex-row px-2 py-2 text-sm" [ class "flex flex-row px-2 py-2 text-sm"
, class S.border , class S.border
@ -213,6 +208,13 @@ attachHeader settings model _ attach =
, href "#" , href "#"
] ]
} }
, { icon = "fa fa-redo-alt"
, label = "Re-process this file"
, attrs =
[ onClick (RequestReprocessFile attach.id)
, href "#"
]
}
, { icon = "fa fa-trash" , { icon = "fa fa-trash"
, label = "Delete this file" , label = "Delete this file"
, attrs = , attrs =
@ -344,3 +346,13 @@ menuItem model pos attach =
|> text |> text
] ]
] ]
renderModal : Model -> Html Msg
renderModal model =
case model.attachModal of
Just confirmModal ->
Comp.ConfirmModal.view confirmModal
Nothing ->
span [ class "hidden" ] []

View File

@ -16,6 +16,7 @@ import Api.Model.ReferenceList exposing (ReferenceList)
import Api.Model.Tag exposing (Tag) import Api.Model.Tag exposing (Tag)
import Browser.Navigation as Nav import Browser.Navigation as Nav
import Comp.AttachmentMeta import Comp.AttachmentMeta
import Comp.ConfirmModal
import Comp.CustomFieldMultiInput import Comp.CustomFieldMultiInput
import Comp.DatePicker import Comp.DatePicker
import Comp.DetailEdit import Comp.DetailEdit
@ -43,7 +44,6 @@ import Comp.MarkdownInput
import Comp.OrgForm import Comp.OrgForm
import Comp.PersonForm import Comp.PersonForm
import Comp.SentMails import Comp.SentMails
import Comp.YesNoDimmer
import Data.CustomFieldChange exposing (CustomFieldChange(..)) import Data.CustomFieldChange exposing (CustomFieldChange(..))
import Data.Direction import Data.Direction
import Data.Fields exposing (Field) import Data.Fields exposing (Field)
@ -532,22 +532,28 @@ update key flags inav settings msg model =
RemoveDueDate -> RemoveDueDate ->
resultModelCmd ( { model | dueDate = Nothing }, setDueDate flags model Nothing ) resultModelCmd ( { model | dueDate = Nothing }, setDueDate flags model Nothing )
DeleteItemConfirm m -> DeleteItemConfirmed ->
let let
( cm, confirmed ) =
Comp.YesNoDimmer.update m model.deleteItemConfirm
cmd = cmd =
if confirmed then Api.deleteItem flags model.item.id DeleteResp
Api.deleteItem flags model.item.id DeleteResp
else
Cmd.none
in in
resultModelCmd ( { model | deleteItemConfirm = cm }, cmd ) resultModelCmd ( { model | itemModal = Nothing }, cmd )
ItemModalCancelled ->
resultModel { model | itemModal = Nothing }
RequestDelete -> RequestDelete ->
update key flags inav settings (DeleteItemConfirm Comp.YesNoDimmer.activate) model let
confirmMsg =
"Really delete this item? This cannot be undone."
confirm =
Comp.ConfirmModal.defaultSettings
DeleteItemConfirmed
ItemModalCancelled
confirmMsg
in
resultModel { model | itemModal = Just confirm }
SetCorrOrgSuggestion idname -> SetCorrOrgSuggestion idname ->
resultModelCmd ( model, setCorrOrg flags model (Just idname) ) resultModelCmd ( model, setCorrOrg flags model (Just idname) )
@ -913,19 +919,15 @@ update key flags inav settings msg model =
, attachmentDropdownOpen = False , attachmentDropdownOpen = False
} }
DeleteAttachConfirm attachId lmsg -> DeleteAttachConfirmed attachId ->
let let
( cm, confirmed ) =
Comp.YesNoDimmer.update lmsg model.deleteAttachConfirm
cmd = cmd =
if confirmed then Api.deleteAttachment flags attachId DeleteAttachResp
Api.deleteAttachment flags attachId DeleteAttachResp
else
Cmd.none
in in
resultModelCmd ( { model | deleteAttachConfirm = cm }, cmd ) resultModelCmd ( { model | attachModal = Nothing }, cmd )
AttachModalCancelled ->
resultModel { model | attachModal = Nothing }
DeleteAttachResp (Ok res) -> DeleteAttachResp (Ok res) ->
if res.success then if res.success then
@ -938,12 +940,20 @@ update key flags inav settings msg model =
resultModel model resultModel model
RequestDeleteAttachment id -> RequestDeleteAttachment id ->
update key let
flags confirmModal =
inav Comp.ConfirmModal.defaultSettings
settings (DeleteAttachConfirmed id)
(DeleteAttachConfirm id Comp.YesNoDimmer.activate) AttachModalCancelled
{ model | attachmentDropdownOpen = False } "Really delete this file?"
model_ =
{ model
| attachmentDropdownOpen = False
, attachModal = Just confirmModal
}
in
resultModel model_
AddFilesToggle -> AddFilesToggle ->
resultModel resultModel
@ -1508,6 +1518,73 @@ update key flags inav settings msg model =
in in
resultModel { model | editMenuTabsOpen = next } resultModel { model | editMenuTabsOpen = next }
RequestReprocessFile id ->
let
confirmMsg =
if model.item.state == "created" then
"Reprocessing this file may change metadata of "
++ "this item, since it is unconfirmed. Do you want to proceed?"
else
"Reprocessing this file will not change metadata of "
++ "this item, since it has been confirmed. Do you want to proceed?"
confirmModal =
Comp.ConfirmModal.defaultSettings
(ReprocessFileConfirmed id)
AttachModalCancelled
confirmMsg
model_ =
{ model
| attachmentDropdownOpen = False
, attachModal = Just confirmModal
}
in
resultModel model_
ReprocessFileConfirmed id ->
let
cmd =
Api.reprocessItem flags model.item.id [ id ] ReprocessFileResp
in
resultModelCmd ( { model | attachModal = Nothing }, cmd )
ReprocessFileResp _ ->
resultModel model
RequestReprocessItem ->
let
confirmMsg =
if model.item.state == "created" then
"Reprocessing this item may change its metadata, "
++ "since it is unconfirmed. Do you want to proceed?"
else
"Reprocessing this item will not change its metadata, "
++ "since it has been confirmed. Do you want to proceed?"
confirmModal =
Comp.ConfirmModal.defaultSettings
ReprocessItemConfirmed
ItemModalCancelled
confirmMsg
model_ =
{ model
| attachmentDropdownOpen = False
, itemModal = Just confirmModal
}
in
resultModel model_
ReprocessItemConfirmed ->
let
cmd =
Api.reprocessItem flags model.item.id [] ReprocessFileResp
in
resultModelCmd ( { model | itemModal = Nothing }, cmd )
--- Helper --- Helper

View File

@ -1,6 +1,7 @@
module Comp.ItemDetail.View2 exposing (view) module Comp.ItemDetail.View2 exposing (view)
import Comp.Basic as B import Comp.Basic as B
import Comp.ConfirmModal
import Comp.DetailEdit import Comp.DetailEdit
import Comp.ItemDetail.AddFilesForm import Comp.ItemDetail.AddFilesForm
import Comp.ItemDetail.ItemInfoHeader import Comp.ItemDetail.ItemInfoHeader
@ -16,7 +17,6 @@ import Comp.ItemDetail.SingleAttachment
import Comp.ItemMail import Comp.ItemMail
import Comp.MenuBar as MB import Comp.MenuBar as MB
import Comp.SentMails import Comp.SentMails
import Comp.YesNoDimmer
import Data.Icons as Icons import Data.Icons as Icons
import Data.ItemNav exposing (ItemNav) import Data.ItemNav exposing (ItemNav)
import Data.UiSettings exposing (UiSettings) import Data.UiSettings exposing (UiSettings)
@ -34,15 +34,20 @@ view inav settings model =
[ header settings model [ header settings model
, menuBar inav settings model , menuBar inav settings model
, body inav settings model , body inav settings model
, Html.map DeleteItemConfirm , itemModal model
(Comp.YesNoDimmer.viewN
True
(Comp.YesNoDimmer.defaultSettings2 "Really delete the complete item?")
model.deleteItemConfirm
)
] ]
itemModal : Model -> Html Msg
itemModal model =
case model.itemModal of
Just confirm ->
Comp.ConfirmModal.view confirm
Nothing ->
span [ class "hidden" ] []
header : UiSettings -> Model -> Html Msg header : UiSettings -> Model -> Html Msg
header settings model = header settings model =
div [ class "my-3" ] div [ class "my-3" ]
@ -166,6 +171,15 @@ menuBar inav settings model =
] ]
[ i [ class "fa fa-eye-slash font-thin" ] [] [ i [ class "fa fa-eye-slash font-thin" ] []
] ]
, MB.CustomElement <|
a
[ class S.secondaryBasicButton
, href "#"
, onClick RequestReprocessItem
, title "Reprocess this item"
]
[ i [ class "fa fa-redo" ] []
]
, MB.CustomElement <| , MB.CustomElement <|
a a
[ class S.deleteButton [ class S.deleteButton

View File

@ -22,6 +22,7 @@ import Api.Model.BasicResult exposing (BasicResult)
import Api.Model.ItemLightList exposing (ItemLightList) import Api.Model.ItemLightList exposing (ItemLightList)
import Api.Model.SearchStats exposing (SearchStats) import Api.Model.SearchStats exposing (SearchStats)
import Browser.Dom as Dom import Browser.Dom as Dom
import Comp.ConfirmModal
import Comp.FixedDropdown import Comp.FixedDropdown
import Comp.ItemCardList import Comp.ItemCardList
import Comp.ItemDetail.FormChange exposing (FormChange) import Comp.ItemDetail.FormChange exposing (FormChange)
@ -64,7 +65,7 @@ type alias Model =
type alias SelectViewModel = type alias SelectViewModel =
{ ids : Set String { ids : Set String
, action : SelectActionMode , action : SelectActionMode
, deleteAllConfirm : Comp.YesNoDimmer.Model , confirmModal : Maybe (Comp.ConfirmModal.Settings Msg)
, editModel : Comp.ItemDetail.MultiEditMenu.Model , editModel : Comp.ItemDetail.MultiEditMenu.Model
, saveNameState : SaveNameState , saveNameState : SaveNameState
, saveCustomFieldState : Set String , saveCustomFieldState : Set String
@ -75,7 +76,7 @@ initSelectViewModel : SelectViewModel
initSelectViewModel = initSelectViewModel =
{ ids = Set.empty { ids = Set.empty
, action = NoneAction , action = NoneAction
, deleteAllConfirm = Comp.YesNoDimmer.initActive , confirmModal = Nothing
, editModel = Comp.ItemDetail.MultiEditMenu.init , editModel = Comp.ItemDetail.MultiEditMenu.init
, saveNameState = SaveSuccess , saveNameState = SaveSuccess
, saveCustomFieldState = Set.empty , saveCustomFieldState = Set.empty
@ -187,7 +188,8 @@ type Msg
| SelectAllItems | SelectAllItems
| SelectNoItems | SelectNoItems
| RequestDeleteSelected | RequestDeleteSelected
| DeleteSelectedConfirmMsg Comp.YesNoDimmer.Msg | DeleteSelectedConfirmed
| CloseConfirmModal
| EditSelectedItems | EditSelectedItems
| EditMenuMsg Comp.ItemDetail.MultiEditMenu.Msg | EditMenuMsg Comp.ItemDetail.MultiEditMenu.Msg
| MultiUpdateResp FormChange (Result Http.Error BasicResult) | MultiUpdateResp FormChange (Result Http.Error BasicResult)
@ -199,6 +201,8 @@ type Msg
| TogglePreviewFullWidth | TogglePreviewFullWidth
| PowerSearchMsg Comp.PowerSearchInput.Msg | PowerSearchMsg Comp.PowerSearchInput.Msg
| KeyUpPowerSearchbarMsg (Maybe KeyCode) | KeyUpPowerSearchbarMsg (Maybe KeyCode)
| RequestReprocessSelected
| ReprocessSelectedConfirmed
type SearchType type SearchType
@ -210,6 +214,7 @@ type SelectActionMode
= NoneAction = NoneAction
| DeleteSelected | DeleteSelected
| EditSelected | EditSelected
| ReprocessSelected
type alias SearchParam = type alias SearchParam =

View File

@ -3,6 +3,7 @@ module Page.Home.Update exposing (update)
import Api import Api
import Api.Model.ItemLightList exposing (ItemLightList) import Api.Model.ItemLightList exposing (ItemLightList)
import Browser.Navigation as Nav import Browser.Navigation as Nav
import Comp.ConfirmModal
import Comp.FixedDropdown import Comp.FixedDropdown
import Comp.ItemCardList import Comp.ItemCardList
import Comp.ItemDetail.FormChange exposing (FormChange(..)) import Comp.ItemDetail.FormChange exposing (FormChange(..))
@ -10,7 +11,6 @@ import Comp.ItemDetail.MultiEditMenu exposing (SaveNameState(..))
import Comp.LinkTarget exposing (LinkTarget) import Comp.LinkTarget exposing (LinkTarget)
import Comp.PowerSearchInput import Comp.PowerSearchInput
import Comp.SearchMenu import Comp.SearchMenu
import Comp.YesNoDimmer
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.ItemQuery as Q import Data.ItemQuery as Q
import Data.ItemSelection import Data.ItemSelection
@ -358,34 +358,20 @@ update mId key flags settings msg model =
_ -> _ ->
noSub ( model, Cmd.none ) noSub ( model, Cmd.none )
DeleteSelectedConfirmMsg lmsg -> DeleteSelectedConfirmed ->
case model.viewMode of case model.viewMode of
SelectView svm -> SelectView svm ->
let let
( confirmModel, confirmed ) =
Comp.YesNoDimmer.update lmsg svm.deleteAllConfirm
cmd = cmd =
if confirmed then Api.deleteAllItems flags svm.ids DeleteAllResp
Api.deleteAllItems flags svm.ids DeleteAllResp
else
Cmd.none
act =
if confirmModel.active || confirmed then
DeleteSelected
else
NoneAction
in in
noSub noSub
( { model ( { model
| viewMode = | viewMode =
SelectView SelectView
{ svm { svm
| deleteAllConfirm = confirmModel | confirmModal = Nothing
, action = act , action = DeleteSelected
} }
} }
, cmd , cmd
@ -416,6 +402,74 @@ update mId key flags settings msg model =
DeleteAllResp (Err _) -> DeleteAllResp (Err _) ->
noSub ( model, Cmd.none ) noSub ( model, Cmd.none )
RequestReprocessSelected ->
case model.viewMode of
SelectView svm ->
if svm.ids == Set.empty then
noSub ( model, Cmd.none )
else
let
lmsg =
Comp.ConfirmModal.defaultSettings
ReprocessSelectedConfirmed
CloseConfirmModal
"Really reprocess all selected items? Metadata of unconfirmed items may change."
model_ =
{ model
| viewMode =
SelectView
{ svm
| action = ReprocessSelected
, confirmModal = Just lmsg
}
}
in
noSub ( model_, Cmd.none )
_ ->
noSub ( model, Cmd.none )
CloseConfirmModal ->
case model.viewMode of
SelectView svm ->
noSub
( { model
| viewMode = SelectView { svm | confirmModal = Nothing, action = NoneAction }
}
, Cmd.none
)
_ ->
noSub ( model, Cmd.none )
ReprocessSelectedConfirmed ->
case model.viewMode of
SelectView svm ->
if svm.ids == Set.empty then
noSub ( model, Cmd.none )
else
let
cmd =
Api.reprocessMultiple flags svm.ids DeleteAllResp
in
noSub
( { model
| viewMode =
SelectView
{ svm
| confirmModal = Nothing
, action = ReprocessSelected
}
}
, cmd
)
_ ->
noSub ( model, Cmd.none )
RequestDeleteSelected -> RequestDeleteSelected ->
case model.viewMode of case model.viewMode of
SelectView svm -> SelectView svm ->
@ -425,12 +479,22 @@ update mId key flags settings msg model =
else else
let let
lmsg = lmsg =
DeleteSelectedConfirmMsg Comp.YesNoDimmer.activate Comp.ConfirmModal.defaultSettings
DeleteSelectedConfirmed
CloseConfirmModal
"Really delete all selected items?"
model_ = model_ =
{ model | viewMode = SelectView { svm | action = DeleteSelected } } { model
| viewMode =
SelectView
{ svm
| action = DeleteSelected
, confirmModal = Just lmsg
}
}
in in
update mId key flags settings lmsg model_ noSub ( model_, Cmd.none )
_ -> _ ->
noSub ( model, Cmd.none ) noSub ( model, Cmd.none )

View File

@ -1,6 +1,7 @@
module Page.Home.View2 exposing (viewContent, viewSidebar) module Page.Home.View2 exposing (viewContent, viewSidebar)
import Comp.Basic as B import Comp.Basic as B
import Comp.ConfirmModal
import Comp.ItemCardList import Comp.ItemCardList
import Comp.MenuBar as MB import Comp.MenuBar as MB
import Comp.PowerSearchInput import Comp.PowerSearchInput
@ -67,13 +68,13 @@ deleteSelectedDimmer model =
in in
case model.viewMode of case model.viewMode of
SelectView svm -> SelectView svm ->
[ Html.map DeleteSelectedConfirmMsg case svm.confirmModal of
(Comp.YesNoDimmer.viewN Just confirm ->
(selectAction == DeleteSelected) [ Comp.ConfirmModal.view confirm
deleteAllDimmer ]
svm.deleteAllConfirm
) Nothing ->
] []
_ -> _ ->
[] []
@ -219,6 +220,16 @@ editMenuBar model svm =
, ( "bg-gray-200 dark:bg-bluegray-600", svm.action == EditSelected ) , ( "bg-gray-200 dark:bg-bluegray-600", svm.action == EditSelected )
] ]
} }
, MB.CustomButton
{ tagger = RequestReprocessSelected
, label = ""
, icon = Just "fa fa-redo"
, title = "Reprocess " ++ selectCount ++ " selected items"
, inputClass =
[ ( btnStyle, True )
, ( "bg-gray-200 dark:bg-bluegray-600", svm.action == ReprocessSelected )
]
}
, MB.CustomButton , MB.CustomButton
{ tagger = RequestDeleteSelected { tagger = RequestDeleteSelected
, label = "" , label = ""

View File

@ -313,7 +313,7 @@ editLinkTableCellStyle =
dimmer : String dimmer : String
dimmer = dimmer =
" absolute top-0 left-0 w-full h-full bg-black bg-opacity-90 dark:bg-bluegray-900 dark:bg-opacity-90 z-50 flex flex-col items-center justify-center px-4 py-2 " " absolute top-0 left-0 w-full h-full bg-black bg-opacity-90 dark:bg-bluegray-900 dark:bg-opacity-90 z-50 flex flex-col items-center justify-center px-4 md:px-8 py-2 "
dimmerLight : String dimmerLight : String