From b74953d36dcc993e994e05990e743d9a47f1be0d Mon Sep 17 00:00:00 2001 From: patrick blume Date: Mon, 9 Dec 2024 14:20:39 +0100 Subject: [PATCH 01/23] Create basic structure for adding multiple templates --- src/Client/Messages.fs | 73 ++++++----- src/Client/Modals/ModalElements.fs | 22 ---- src/Client/Model.fs | 118 +++++++++--------- src/Client/OfficeInterop/OfficeInterop.fs | 2 +- .../ProtocolSearchViewComponent.fs | 96 +++++++++++--- .../Pages/ProtocolTemplates/ProtocolState.fs | 13 ++ 6 files changed, 184 insertions(+), 140 deletions(-) diff --git a/src/Client/Messages.fs b/src/Client/Messages.fs index 00a8b033..94b90179 100644 --- a/src/Client/Messages.fs +++ b/src/Client/Messages.fs @@ -7,9 +7,7 @@ open Fable.Remoting.Client open Fable.SimpleJson open Database -open OfficeInterop open Model -open Routing open ARCtrl open Fable.Core @@ -25,13 +23,13 @@ type System.Exception with | ex -> ex.Message -let curry f a b = f (a,b) +let curry f a b = f (a, b) module TermSearch = type Msg = - | UpdateSelectedTerm of OntologyAnnotation option - | UpdateParentTerm of OntologyAnnotation option + | UpdateSelectedTerm of OntologyAnnotation option + | UpdateParentTerm of OntologyAnnotation option module AdvancedSearch = @@ -53,39 +51,40 @@ type DevMsg = module PersistentStorage = type Msg = - | NewSearchableOntologies of Ontology [] - | UpdateAppVersion of string - | UpdateShowSidebar of bool + | NewSearchableOntologies of Ontology [] + | UpdateAppVersion of string + | UpdateShowSidebar of bool module FilePicker = type Msg = - | LoadNewFiles of string list - | UpdateFileNames of newFileNames:(int*string) list + | LoadNewFiles of string list + | UpdateFileNames of newFileNames:(int*string) list module BuildingBlock = open TermSearch type Msg = - | UpdateHeaderWithIO of CompositeHeaderDiscriminate * IOType - | UpdateHeaderCellType of CompositeHeaderDiscriminate - | UpdateHeaderArg of U2 option - | UpdateBodyCellType of CompositeCellDiscriminate - | UpdateBodyArg of U2 option + | UpdateHeaderWithIO of CompositeHeaderDiscriminate * IOType + | UpdateHeaderCellType of CompositeHeaderDiscriminate + | UpdateHeaderArg of U2 option + | UpdateBodyCellType of CompositeCellDiscriminate + | UpdateBodyArg of U2 option module Protocol = type Msg = // Client - | UpdateTemplates of Template [] - | UpdateLoading of bool + | UpdateTemplates of Template [] + | UpdateLoading of bool | RemoveSelectedProtocol // // ------ Protocol from Database ------ | GetAllProtocolsForceRequest | GetAllProtocolsRequest - | GetAllProtocolsResponse of string - | SelectProtocol of Template - | ProtocolIncreaseTimesUsed of protocolName:string + | GetAllProtocolsResponse of string + | SelectProtocol of Template + | AddProtocol of Template + | ProtocolIncreaseTimesUsed of protocolName:string type SettingsDataStewardMsg = // Client @@ -95,26 +94,26 @@ type TopLevelMsg = | CloseSuggestions type Msg = -| UpdateModel of Model -| DevMsg of DevMsg -| OntologyMsg of Ontologies.Msg -| TermSearchMsg of TermSearch.Msg -| AdvancedSearchMsg of AdvancedSearch.Msg -| OfficeInteropMsg of OfficeInterop.Msg -| PersistentStorageMsg of PersistentStorage.Msg -| FilePickerMsg of FilePicker.Msg -| BuildingBlockMsg of BuildingBlock.Msg -| ProtocolMsg of Protocol.Msg +| UpdateModel of Model +| DevMsg of DevMsg +| OntologyMsg of Ontologies.Msg +| TermSearchMsg of TermSearch.Msg +| AdvancedSearchMsg of AdvancedSearch.Msg +| OfficeInteropMsg of OfficeInterop.Msg +| PersistentStorageMsg of PersistentStorage.Msg +| FilePickerMsg of FilePicker.Msg +| BuildingBlockMsg of BuildingBlock.Msg +| ProtocolMsg of Protocol.Msg // | CytoscapeMsg of Cytoscape.Msg -| DataAnnotatorMsg of DataAnnotator.Msg -| SpreadsheetMsg of Spreadsheet.Msg +| DataAnnotatorMsg of DataAnnotator.Msg +| SpreadsheetMsg of Spreadsheet.Msg /// This is used to forward Msg to SpreadsheetMsg/OfficeInterop -| InterfaceMsg of SpreadsheetInterface.Msg -| Batch of seq -| Run of (unit -> unit) -| UpdateHistory of LocalHistory.Model +| InterfaceMsg of SpreadsheetInterface.Msg +| Batch of seq +| Run of (unit -> unit) +| UpdateHistory of LocalHistory.Model /// Top level msg to test specific api interactions, only for dev. | TestMyAPI | TestMyPostAPI -| UpdateModal of Model.ModalState.ModalTypes option +| UpdateModal of Model.ModalState.ModalTypes option | DoNothing \ No newline at end of file diff --git a/src/Client/Modals/ModalElements.fs b/src/Client/Modals/ModalElements.fs index 544634ba..4c12382c 100644 --- a/src/Client/Modals/ModalElements.fs +++ b/src/Client/Modals/ModalElements.fs @@ -73,28 +73,6 @@ type ModalElements = ] ] - static member BoxWithChildren(children: ReactElement list, ?title: string, ?icon: string, ?className: string list) = - Html.div [ - prop.className [ - "rounded shadow p-2 flex flex-col gap-2 border" - if className.IsSome then - className.Value |> String.concat " " - ] - prop.children [ - Html.h3 [ - prop.className "font-semibold gap-2 flex flex-row items-center" - if icon.IsSome || title.IsSome then - prop.children [ - if icon.IsSome then - Html.i [prop.className icon.Value] - if title.IsSome then - Html.span title.Value - ] - prop.children children - ] - ] - ] - static member SelectorButton<'a when 'a : equality> (targetselector: 'a, selector: 'a, setSelector: 'a -> unit, ?isDisabled) = Daisy.button.button [ join.item diff --git a/src/Client/Model.fs b/src/Client/Model.fs index c4b9a87e..6e39a8ef 100644 --- a/src/Client/Model.fs +++ b/src/Client/Model.fs @@ -1,17 +1,15 @@ namespace Model -open Fable.React -open Fable.React.Props open Shared open Feliz open Routing open Database type LogItem = - | Debug of (System.DateTime*string) - | Info of (System.DateTime*string) - | Error of (System.DateTime*string) - | Warning of (System.DateTime*string) + | Debug of (System.DateTime*string) + | Info of (System.DateTime*string) + | Error of (System.DateTime*string) + | Warning of (System.DateTime*string) static member ofInteropLogginMsg (msg:InteropLogging.Msg) = match msg.LogIdentifier with @@ -26,25 +24,25 @@ type LogItem = static member private WarningCell = Html.td [prop.className "bg-warning text-warning-content font-semibold"; prop.text "Warning"] static member toTableRow = function - | Debug (t,m) -> + | Debug (t, m) -> Html.tr [ Html.td (sprintf "[%s]" (t.ToShortTimeString())) LogItem.DebugCell Html.td m ] - | Info (t,m) -> + | Info (t, m) -> Html.tr [ Html.td (sprintf "[%s]" (t.ToShortTimeString())) LogItem.InfoCell Html.td m ] - | Error (t,m) -> + | Error (t, m) -> Html.tr [ Html.td (sprintf "[%s]" (t.ToShortTimeString())) LogItem.ErrorCell Html.td m ] - | Warning (t,m) -> + | Warning (t, m) -> Html.tr [ Html.td (sprintf "[%s]" (t.ToShortTimeString())) LogItem.WarningCell @@ -64,13 +62,13 @@ module TermSearch = open ARCtrl type Model = { - SelectedTerm : OntologyAnnotation option - ParentTerm : OntologyAnnotation option + SelectedTerm : OntologyAnnotation option + ParentTerm : OntologyAnnotation option } with static member init () = { - SelectedTerm = None - ParentTerm = None + SelectedTerm = None + ParentTerm = None } module AdvancedSearch = @@ -92,20 +90,20 @@ module AdvancedSearch = HasAdvancedSearchResultsLoading : bool } with static member init () = { - ModalId = "" - HasModalVisible = false - HasOntologyDropdownVisible = false - AdvancedSearchOptions = AdvancedSearchTypes.AdvancedSearchOptions.init () - AdvancedSearchTermResults = [||] - HasAdvancedSearchResultsLoading = false - Subpage = InputFormSubpage + ModalId = "" + HasModalVisible = false + HasOntologyDropdownVisible = false + AdvancedSearchOptions = AdvancedSearchTypes.AdvancedSearchOptions.init () + AdvancedSearchTermResults = [||] + HasAdvancedSearchResultsLoading = false + Subpage = InputFormSubpage } static member BuildingBlockHeaderId = "BuildingBlockHeader_ATS_Id" static member BuildingBlockBodyId = "BuildingBlockBody_ATS_Id" type DevState = { - Log : LogItem list - DisplayLogList : LogItem list + Log : LogItem list + DisplayLogList : LogItem list } with static member init () = { DisplayLogList = [] @@ -138,12 +136,12 @@ type PageState = { } member this.IsHome = match this.MainPage with - | MainPage.Default -> true - | _ -> false + | MainPage.Default -> true + | _ -> false module FilePicker = type Model = { - FileNames : (int*string) list + FileNames : (int*string) list } with static member init () = { FileNames = [] @@ -154,7 +152,6 @@ open Fable.Core module BuildingBlock = open ARCtrl - open ARCtrl.Helper [] type DropdownPage = @@ -164,15 +161,15 @@ module BuildingBlock = member this.toString = match this with - | Main -> "Main Page" - | More -> "More" - | IOTypes (t) -> t.ToString() + | Main -> "Main Page" + | More -> "More" + | IOTypes t -> t.ToString() member this.toTooltip = match this with - | More -> "More" - | IOTypes (t) -> $"Per table only one {t} is allowed. The value of this column must be a unique identifier." - | _ -> "" + | More -> "More" + | IOTypes t -> $"Per table only one {t} is allowed. The value of this column must be a unique identifier." + | _ -> "" type BuildingBlockUIState = { DropdownIsActive : bool @@ -193,10 +190,10 @@ module BuildingBlock = } with static member init () = { - HeaderCellType = CompositeHeaderDiscriminate.Parameter - HeaderArg = None - BodyCellType = CompositeCellDiscriminate.Term - BodyArg = None + HeaderCellType = CompositeHeaderDiscriminate.Parameter + HeaderArg = None + BodyCellType = CompositeCellDiscriminate.Term + BodyArg = None } member this.TryHeaderOA() = @@ -226,14 +223,13 @@ module Protocol = | All | OnlyCurated | Community of string - member this.ToStringRdb() = match this with | All -> "All" | OnlyCurated -> "DataPLANT official" | Community name -> name - static member fromString(str:string) = + static member fromString(str: string) = match str with | "All" -> All | "DataPLANT official" -> OnlyCurated @@ -247,19 +243,21 @@ module Protocol = /// This model is used for both protocol insert and protocol search type Model = { // Client - Loading : bool - LastUpdated : System.DateTime option + Loading : bool + LastUpdated : System.DateTime option // ------ Protocol from Database ------ - TemplateSelected : ARCtrl.Template option - Templates : ARCtrl.Template [] + TemplateSelected : ARCtrl.Template option + TemplatesSelected : ARCtrl.Template list + Templates : ARCtrl.Template [] } with static member init () = { // Client - Loading = false - LastUpdated = None - TemplateSelected = None + Loading = false + LastUpdated = None + TemplateSelected = None + TemplatesSelected = [] // ------ Protocol from Database ------ - Templates = [||] + Templates = [||] } type RequestBuildingBlockInfoStates = @@ -274,25 +272,25 @@ type RequestBuildingBlockInfoStates = type Model = { ///PageState - PageState : PageState + PageState : PageState ///Data that needs to be persistent once loaded - PersistentStorageState : PersistentStorageState + PersistentStorageState : PersistentStorageState ///Error handling, Logging, etc. - DevState : DevState + DevState : DevState ///States regarding term search - TermSearchState : TermSearch.Model + TermSearchState : TermSearch.Model ///Use this in the future to model excel stuff like table data - ExcelState : OfficeInterop.Model + ExcelState : OfficeInterop.Model ///States regarding File picker functionality - FilePickerState : FilePicker.Model - ProtocolState : Protocol.Model + FilePickerState : FilePicker.Model + ProtocolState : Protocol.Model ///Insert annotation columns - AddBuildingBlockState : BuildingBlock.Model - CytoscapeModel : Cytoscape.Model + AddBuildingBlockState : BuildingBlock.Model + CytoscapeModel : Cytoscape.Model /// - DataAnnotatorModel : DataAnnotator.Model + DataAnnotatorModel : DataAnnotator.Model /// Contains all information about spreadsheet view - SpreadsheetModel : Spreadsheet.Model - History : LocalHistory.Model - ModalState : ModalState + SpreadsheetModel : Spreadsheet.Model + History : LocalHistory.Model + ModalState : ModalState } \ No newline at end of file diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index 5ddc26b6..90648be3 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -939,7 +939,7 @@ let selectiveTablePrepare (activeTable: ArcTable) (toJoinTable: ArcTable) (remov columnsToRemove <- containsAtIndex.Value::columnsToRemove //Remove duplicates because unselected and already existing columns can overlap - let columnsToRemove = columnsToRemove |> Set.ofList |> Set.toList + let columnsToRemove = columnsToRemove |> List.distinct tablecopy.RemoveColumns (Array.ofList columnsToRemove) tablecopy.IteriColumns(fun i c0 -> diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs b/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs index bf613334..d3f4ea2a 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs @@ -8,6 +8,7 @@ open Messages open Feliz open Feliz.DaisyUI +open Modals /// Fields of Template that can be searched [] @@ -16,7 +17,7 @@ type SearchFields = | Organisation | Authors - static member private ofFieldString (str:string) = + static member private ofFieldString (str: string) = let str = str.ToLower() match str with | "/o" | "/org" -> Some Organisation @@ -36,7 +37,7 @@ type SearchFields = | Organisation -> "organisation" | Authors -> "authors" - static member GetOfQuery(query:string) = + static member GetOfQuery(query: string) = SearchFields.ofFieldString query open ARCtrl @@ -73,7 +74,7 @@ module ComponentAux = [] let SearchFieldId = "template_searchfield_main" - let queryField (model:Model) (state: TemplateFilterConfig) (setState: TemplateFilterConfig -> unit) = + let queryField (model: Model) (state: TemplateFilterConfig) (setState: TemplateFilterConfig -> unit) = Html.div [ Html.p $"Search by {state.Searchfield.toNameRdb}" let hasSearchAddon = state.Searchfield <> SearchFields.Name @@ -114,10 +115,9 @@ module ComponentAux = ] ] - let Tag (tag:OntologyAnnotation, color: IReactProperty, isRemovable: bool, onclick: (Browser.Types.MouseEvent -> unit) option) = + let Tag (tag: OntologyAnnotation, color: IReactProperty, isRemovable: bool, onclick: (Browser.Types.MouseEvent -> unit) option) = Daisy.badge [ color - prop.className [ if onclick.IsSome then "cursor-pointer hover:brightness-110" "text-nowrap" @@ -157,7 +157,7 @@ module ComponentAux = ] ] - let tagQueryField (model:Model) (state: TemplateFilterConfig) (setState: TemplateFilterConfig -> unit) = + let tagQueryField (model: Model) (state: TemplateFilterConfig) (setState: TemplateFilterConfig -> unit) = let allTags = model.ProtocolState.Templates |> Seq.collect (fun x -> x.Tags) |> Seq.distinct |> Seq.filter (fun x -> state.ProtocolFilterTags |> List.contains x |> not ) |> Array.ofSeq let allErTags = model.ProtocolState.Templates |> Seq.collect (fun x -> x.EndpointRepositories) |> Seq.distinct |> Seq.filter (fun x -> state.ProtocolFilterErTags |> List.contains x |> not ) |> Array.ofSeq let hitTagList, hitErTagList = @@ -281,7 +281,7 @@ module ComponentAux = ] ] - let TagDisplayField (model:Model) (state: TemplateFilterConfig) (setState: TemplateFilterConfig -> unit) = + let TagDisplayField (model: Model) (state: TemplateFilterConfig) (setState: TemplateFilterConfig -> unit) = Html.div [ prop.className "flex" prop.children [ @@ -304,6 +304,7 @@ module ComponentAux = let curatedTag = Daisy.badge [prop.text "curated"; badge.primary] let communitytag = Daisy.badge [prop.text "community"; badge.warning] + let curatedCommunityTag = Daisy.badge [ prop.style [style.custom("background", "linear-gradient(90deg, rgba(31,194,167,1) 50%, rgba(255,192,0,1) 50%)")] @@ -317,9 +318,10 @@ module ComponentAux = let createAuthorStringHelper (author: Person) = let mi = if author.MidInitials.IsSome then author.MidInitials.Value else "" $"{author.FirstName} {mi} {author.LastName}" + let createAuthorsStringHelper (authors: ResizeArray) = authors |> Seq.map createAuthorStringHelper |> String.concat ", " - let protocolElement i (template:ARCtrl.Template) (isShown:bool) (setIsShown: bool -> unit) (model:Model) dispatch = + let protocolElement i (template: ARCtrl.Template) (isShown: bool) (setIsShown: bool -> unit) (model: Model) dispatch = [ Html.tr [ prop.key $"{i}_{template.Id}" @@ -387,16 +389,27 @@ module ComponentAux = ] ] Html.div [ - prop.className "flex justify-center" + prop.className "flex justify-center gap-2" prop.children [ Daisy.button.a [ button.sm prop.onClick (fun _ -> SelectProtocol template |> ProtocolMsg |> dispatch ) - button.wide; button.success + button.wide + button.success prop.text "select" ] + Daisy.button.a [ + button.sm + prop.onClick (fun _ -> + setIsShown (not isShown) + AddProtocol template |> ProtocolMsg |> dispatch + ) + button.wide + button.success + prop.text "add" + ] ] ] ] @@ -404,7 +417,7 @@ module ComponentAux = ] ] - let RefreshButton (model:Model) dispatch = + let RefreshButton (model: Model) dispatch = Daisy.button.button [ button.sm prop.onClick (fun _ -> Messages.Protocol.GetAllProtocolsForceRequest |> ProtocolMsg |> dispatch) @@ -414,6 +427,7 @@ module ComponentAux = ] module FilterHelper = + open ComponentAux let sortTableBySearchQuery (searchfield: SearchFields) (searchQuery: string) (protocol: ARCtrl.Template []) = @@ -425,7 +439,7 @@ module FilterHelper = then let query = query.ToLower() let queryBigram = query |> Shared.SorensenDice.createBigrams - let createScore (str:string) = + let createScore (str: string) = str |> Shared.SorensenDice.createBigrams |> Shared.SorensenDice.calculateDistance queryBigram @@ -469,6 +483,7 @@ module FilterHelper = scoredTemplate else protocol + let filterTableByTags tags ertags tagfilter (templates: ARCtrl.Template []) = if tags <> [] || ertags <> [] then let tagArray = tags@ertags |> ResizeArray @@ -476,17 +491,15 @@ module FilterHelper = Array.ofSeq filteredTemplates else templates - let filterTableByCommunityFilter communityfilter (protocol:ARCtrl.Template []) = + + let filterTableByCommunityFilter communityfilter (protocol: ARCtrl.Template []) = match communityfilter with | Protocol.CommunityFilter.All -> protocol | Protocol.CommunityFilter.OnlyCurated -> protocol |> Array.filter (fun x -> x.Organisation.IsOfficial()) | Protocol.CommunityFilter.Community name -> protocol |> Array.filter (fun x -> x.Organisation.ToString() = name) -open Feliz -open System open ComponentAux - type Search = static member InfoField() = @@ -541,7 +554,7 @@ type Search = ] [] - static member Component (templates, model:Model, dispatch, ?maxheight: Styles.ICssUnit) = + static member Component (templates, model: Model, dispatch, ?maxheight: Styles.ICssUnit) = let maxheight = defaultArg maxheight (length.px 600) let showIds, setShowIds = React.useState(fun _ -> []) Html.div [ @@ -581,14 +594,57 @@ type Search = | [||] -> Html.tr [ Html.td "Empty" ] | _ -> - for i in 0 .. templates.Length-1 do + for i in 0..templates.Length-1 do let isShown = showIds |> List.contains i let setIsShown (show: bool) = - if show then i::showIds |> setShowIds else showIds |> List.filter (fun x -> x <> i) |> setShowIds + if show then i::showIds |> setShowIds else showIds |> List.filter (fun id -> id <> i) |> setShowIds yield! protocolElement i templates.[i] isShown setIsShown model dispatch ] ] ] + let names = + Html.div [ + prop.className "flex gap-2" + prop.children [ + let names = List.rev model.ProtocolState.TemplatesSelected |> List.map (fun template -> template.Name) + for i in 0..names.Length-1 do + Html.div [ yield! [prop.text $"\"{names.[i]}\""]] + ] + ] + let button = + Html.div [ + prop.className "flex justify-center gap-2" + prop.children [ + Daisy.button.a [ + button.sm + //prop.onClick (fun _ -> + // SelectProtocol template |> ProtocolMsg |> dispatch + //) + button.wide + button.success + + if model.ProtocolState.TemplatesSelected.Length > 0 then + button.active + else + button.disabled + + prop.text "Select templates" + ] + ] + ] + let element = + Html.div [ + prop.style [style.overflowX.auto; style.marginBottom (length.rem 1)] + prop.children [ + Html.div [ + prop.children [ + names + ] + ] + button + ] + ] + ModalElements.Box("Selected Templates", "fa-solid fa-cog", element) ] - ] \ No newline at end of file + ] diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs index d44a2e59..3ba2b9be 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs @@ -61,6 +61,19 @@ module Protocol = Model.PageState.SidebarPage = Routing.SidebarPage.Protocol } state, Cmd.ofMsg (UpdateModel nextModel) + | AddProtocol prot -> + log "AddProtocol" + let templates = + if List.contains prot model.ProtocolState.TemplatesSelected then + model.ProtocolState.TemplatesSelected + else + prot::model.ProtocolState.TemplatesSelected + let nextModel = { + model with + Model.ProtocolState.TemplatesSelected = templates + //Model.PageState.SidebarPage = Routing.SidebarPage.Protocol + } + state, Cmd.ofMsg (UpdateModel nextModel) | ProtocolIncreaseTimesUsed templateId -> failwith "ParseUploadedFileRequest IS NOT IMPLEMENTED YET" //let cmd = From 488a75bb5a41e14313cee9f695b75bbc5b8fcb5a Mon Sep 17 00:00:00 2001 From: patrick blume Date: Tue, 10 Dec 2024 15:13:54 +0100 Subject: [PATCH 02/23] Enable viewing of all selected templates --- src/Client/MainComponents/Widgets.fs | 2 +- src/Client/Messages.fs | 1 + .../ProtocolSearchViewComponent.fs | 18 ++++--- .../Pages/ProtocolTemplates/ProtocolState.fs | 12 ++++- .../SelectiveTemplateFromDB.fs | 47 ++++++++++++++----- src/Client/Routing.fs | 36 +++++++------- src/Client/Views/SidebarView.fs | 2 +- 7 files changed, 74 insertions(+), 44 deletions(-) diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index 0a4faec1..05a2d1c2 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -264,7 +264,7 @@ type Widget = ModalElements.Box( model.ProtocolState.TemplateSelected.Value.Name, "", - SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch, false)) + SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model.ProtocolState.TemplateSelected, selectedColumns, setSelectedColumns, dispatch, false)) ] Html.div [ SelectiveTemplateFromDBModal.AddFromDBToTableButton model selectedColumns importTypeState useTemplateName.TemplateName dispatch diff --git a/src/Client/Messages.fs b/src/Client/Messages.fs index 94b90179..923d62b6 100644 --- a/src/Client/Messages.fs +++ b/src/Client/Messages.fs @@ -83,6 +83,7 @@ module Protocol = | GetAllProtocolsRequest | GetAllProtocolsResponse of string | SelectProtocol of Template + | SelectProtocols of Template list | AddProtocol of Template | ProtocolIncreaseTimesUsed of protocolName:string diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs b/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs index d3f4ea2a..95f913a8 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs @@ -618,17 +618,14 @@ type Search = prop.children [ Daisy.button.a [ button.sm - //prop.onClick (fun _ -> - // SelectProtocol template |> ProtocolMsg |> dispatch - //) + prop.onClick (fun _ -> + if model.ProtocolState.TemplatesSelected.Length > 1 then + SelectProtocols model.ProtocolState.TemplatesSelected |> ProtocolMsg |> dispatch + else + SelectProtocol model.ProtocolState.TemplatesSelected.Head |> ProtocolMsg |> dispatch + ) button.wide button.success - - if model.ProtocolState.TemplatesSelected.Length > 0 then - button.active - else - button.disabled - prop.text "Select templates" ] ] @@ -645,6 +642,7 @@ type Search = button ] ] - ModalElements.Box("Selected Templates", "fa-solid fa-cog", element) + if model.ProtocolState.TemplatesSelected.Length > 0 then + ModalElements.Box("Selected Templates", "fa-solid fa-cog", element) ] ] diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs index 3ba2b9be..9e8667c1 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs @@ -61,6 +61,15 @@ module Protocol = Model.PageState.SidebarPage = Routing.SidebarPage.Protocol } state, Cmd.ofMsg (UpdateModel nextModel) + | SelectProtocols prots -> + log "SelectProtocols" + let newProts = prots |> List.rev + let nextModel = { + model with + Model.ProtocolState.TemplatesSelected = newProts + Model.PageState.SidebarPage = Routing.SidebarPage.Protocol + } + state, Cmd.ofMsg (UpdateModel nextModel) | AddProtocol prot -> log "AddProtocol" let templates = @@ -87,6 +96,7 @@ module Protocol = | RemoveSelectedProtocol -> let nextState = { state with - TemplateSelected = None + TemplateSelected = None + TemplatesSelected = [] } nextState, Cmd.none diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index 73457284..46320b80 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -35,7 +35,7 @@ type SelectiveTemplateFromDBModal = ] ] - static member ToProtocolSearchElement (model: Model) dispatch = + static member ToProtocolSearchElement(model: Model) dispatch = Daisy.button.button [ prop.onClick(fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) button.primary @@ -44,32 +44,37 @@ type SelectiveTemplateFromDBModal = ] [] - static member displaySelectedProtocolElements (model: Model, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit, dispatch, ?hasIcon: bool) = + static member displaySelectedProtocolElements(selectedTemplate: Template option, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit, dispatch, ?hasIcon: bool) = let hasIcon = defaultArg hasIcon true Html.div [ prop.style [style.overflowX.auto; style.marginBottom (length.rem 1)] prop.children [ - if model.ProtocolState.TemplateSelected.IsSome then + if selectedTemplate.IsSome then if hasIcon then Html.i [prop.className "fa-solid fa-cog"] - Html.span $"Template: {model.ProtocolState.TemplateSelected.Value.Name}" - if model.ProtocolState.TemplateSelected.IsSome then - SelectiveImportModal.TableWithImportColumnCheckboxes(model.ProtocolState.TemplateSelected.Value.Table, selectionInformation, setSelectedColumns) + Html.span $"Template: {selectedTemplate.Value.Name}" + if selectedTemplate.IsSome then + SelectiveImportModal.TableWithImportColumnCheckboxes(selectedTemplate.Value.Table, selectionInformation, setSelectedColumns) ] ] static member AddFromDBToTableButton (model: Model) selectionInformation importType useTemplateName dispatch = let addTemplate (templatePot: Template option, selectedColumns) = - if model.ProtocolState.TemplateSelected.IsNone then + if model.ProtocolState.TemplateSelected.IsNone && model.ProtocolState.TemplatesSelected.Length = 0 then failwith "No template selected!" - if templatePot.IsSome then + if templatePot.IsSome || model.ProtocolState.TemplatesSelected.Length = 1 then + let table = templatePot.Value.Table + SpreadsheetInterface.AddTemplate(table, selectedColumns, importType, useTemplateName) |> InterfaceMsg |> dispatch + if model.ProtocolState.TemplatesSelected.Length > 1 then let table = templatePot.Value.Table SpreadsheetInterface.AddTemplate(table, selectedColumns, importType, useTemplateName) |> InterfaceMsg |> dispatch Html.div [ prop.className "join flex flex-row justify-center gap-2" prop.children [ - ModalElements.Button("Add template", addTemplate, (model.ProtocolState.TemplateSelected, selectionInformation.Columns), model.ProtocolState.TemplateSelected.IsNone) - if model.ProtocolState.TemplateSelected.IsSome then + let isDisabled = + model.ProtocolState.TemplateSelected.IsSome || model.ProtocolState.TemplatesSelected.Length = 0 + ModalElements.Button("Add template", addTemplate, (model.ProtocolState.TemplateSelected, selectionInformation.Columns), isDisabled) + if model.ProtocolState.TemplateSelected.IsSome || model.ProtocolState.TemplatesSelected.Length > 0 then Daisy.button.a [ button.outline prop.onClick (fun _ -> Protocol.RemoveSelectedProtocol |> ProtocolMsg |> dispatch) @@ -80,7 +85,7 @@ type SelectiveTemplateFromDBModal = ] [] - static member Main (model: Model, dispatch) = + static member Main(model: Model, dispatch) = let length = if model.ProtocolState.TemplateSelected.IsSome then model.ProtocolState.TemplateSelected.Value.Table.Columns.Length @@ -88,11 +93,12 @@ type SelectiveTemplateFromDBModal = let selectedColumns, setSelectedColumns = React.useState(SelectedColumns.init length) let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) let useTemplateName, setUseTemplateName = React.useState(AdaptTableName.init) + log("model.ProtocolState.TemplatesSelected.Length", model.ProtocolState.TemplatesSelected.Length) SidebarComponents.SidebarLayout.LogicContainer [ Html.div [ SelectiveTemplateFromDBModal.ToProtocolSearchElement model dispatch ] - if model.ProtocolState.TemplateSelected.IsSome then + if model.ProtocolState.TemplateSelected.IsSome || model.ProtocolState.TemplatesSelected.Length > 0 then Html.div [ SelectiveImportModal.RadioPluginsBox( "Import Type", @@ -107,6 +113,12 @@ type SelectiveTemplateFromDBModal = fun importType -> {importTypeState with ImportType = importType} |> setImportTypeState ) ] + if model.ProtocolState.TemplateSelected.IsSome || model.ProtocolState.TemplatesSelected.Length = 1 then + let template = + if model.ProtocolState.TemplateSelected.IsSome then + model.ProtocolState.TemplateSelected.Value + else + model.ProtocolState.TemplatesSelected.Head Html.div [ ModalElements.Box( "Rename Table", @@ -117,8 +129,17 @@ type SelectiveTemplateFromDBModal = ModalElements.Box( model.ProtocolState.TemplateSelected.Value.Name, "fa-solid fa-cog", - SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch, false)) + SelectiveTemplateFromDBModal.displaySelectedProtocolElements(Some template, selectedColumns, setSelectedColumns, dispatch, false)) ] + else if model.ProtocolState.TemplatesSelected.Length > 1 then + for i in 0..model.ProtocolState.TemplatesSelected.Length-1 do + let template = model.ProtocolState.TemplatesSelected.[i] + Html.div [ + ModalElements.Box( + template.Name, + "fa-solid fa-cog", + SelectiveTemplateFromDBModal.displaySelectedProtocolElements(Some template, selectedColumns, setSelectedColumns, dispatch, false)) + ] Html.div [ SelectiveTemplateFromDBModal.AddFromDBToTableButton model selectedColumns importTypeState useTemplateName.TemplateName dispatch ] diff --git a/src/Client/Routing.fs b/src/Client/Routing.fs index 5d014d43..11047b57 100644 --- a/src/Client/Routing.fs +++ b/src/Client/Routing.fs @@ -15,13 +15,13 @@ type SidebarPage = member this.AsStringRdbl = match this with - | BuildingBlock -> "Building Blocks" - | TermSearch -> "Terms" - | FilePicker -> "File Picker" - | Protocol -> "Templates" - | ProtocolSearch -> "Template Search" - | JsonExport -> "Json Export" - | DataAnnotator -> "Data Annotator" + | BuildingBlock -> "Building Blocks" + | TermSearch -> "Terms" + | FilePicker -> "File Picker" + | Protocol -> "Templates" + | ProtocolSearch -> "Template Search" + | JsonExport -> "Json Export" + | DataAnnotator -> "Data Annotator" member this.AsIcon() = let createElem (icons: ReactElement list) = @@ -31,19 +31,19 @@ type SidebarPage = ] match this with - | TermSearch -> + | TermSearch -> createElem [Html.i [prop.className "fa-solid fa-magnifying-glass-plus" ]] - | BuildingBlock -> + | BuildingBlock -> createElem [ Html.i [prop.className "fa-solid fa-circle-plus" ]; Html.i [prop.className "fa-solid fa-table-columns" ]] - | Protocol -> + | Protocol -> createElem [ Html.i [prop.className "fa-solid fa-circle-plus" ];Html.i [prop.className "fa-solid fa-table" ]] - | ProtocolSearch -> + | ProtocolSearch -> createElem [ Html.i [prop.className "fa-solid fa-table" ]; Html.i [prop.className "fa-solid fa-magnifying-glass" ]] - | JsonExport -> + | JsonExport -> createElem [ Html.i [prop.className "fa-solid fa-file-export" ]] - | FilePicker -> + | FilePicker -> createElem [ Html.i [prop.className "fa-solid fa-file-signature" ]] - | DataAnnotator -> + | DataAnnotator -> createElem [ Html.i [prop.className "fa-solid fa-object-group" ]] [] @@ -55,10 +55,10 @@ type MainPage = member this.AsStringRdbl = match this with - | About -> "About" + | About -> "About" | PrivacyPolicy -> "Privacy Policy" - | Settings -> "Settings" - | Default -> "Home" + | Settings -> "Settings" + | Default -> "Home" /// The different pages of the application. If you add a new page, then add an entry here. [] @@ -74,7 +74,7 @@ module Routing = open Elmish.Navigation /// The URL is turned into a Result. - let route : Parser Route,_> = + let route : Parser Route, _> = oneOf [ map Route.Home (s "" intParam "is_swatehost") ] diff --git a/src/Client/Views/SidebarView.fs b/src/Client/Views/SidebarView.fs index 307179d5..5bd733df 100644 --- a/src/Client/Views/SidebarView.fs +++ b/src/Client/Views/SidebarView.fs @@ -10,7 +10,7 @@ open Feliz open Feliz.DaisyUI type SidebarView = - static member private content (model:Model) (dispatch: Msg -> unit) = + static member private content (model: Model) (dispatch: Msg -> unit) = Html.div [ prop.className "grow overflow-y-auto" prop.children [ From dc3e485308731ba3198879d51be1c357e4b6cb2d Mon Sep 17 00:00:00 2001 From: patrick blume Date: Wed, 11 Dec 2024 10:12:47 +0100 Subject: [PATCH 03/23] Add minor refactoring --- src/Client/MainComponents/Widgets.fs | 10 ++-- .../Pages/ProtocolTemplates/ProtocolState.fs | 4 +- .../SelectiveTemplateFromDB.fs | 46 ++++++++++++++----- src/Client/States/SpreadsheetInterface.fs | 1 + 4 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index 05a2d1c2..712730a1 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -34,7 +34,7 @@ module private MoveEventListener = open Fable.Core.JsInterop - let ensurePositionInsideWindow (element:IRefValue) (position: Rect) = + let ensurePositionInsideWindow (element: IRefValue) (position: Rect) = let maxX = Browser.Dom.window.innerWidth - element.current.Value.offsetWidth; let tempX = position.X let newX = System.Math.Min(System.Math.Max(tempX,0),int maxX) @@ -43,18 +43,18 @@ module private MoveEventListener = let newY = System.Math.Min(System.Math.Max(tempY,0),int maxY) {X = newX; Y = newY} - let calculatePosition (element:IRefValue) (startPosition: Rect) = fun (e: Event) -> + let calculatePosition (element: IRefValue) (startPosition: Rect) = fun (e: Event) -> let e : MouseEvent = !!e let tempX = int e.clientX - startPosition.X let tempY = int e.clientY - startPosition.Y let tempPosition = {X = tempX; Y = tempY} ensurePositionInsideWindow element tempPosition - let onmousemove (element:IRefValue) (startPosition: Rect) setPosition = fun (e: Event) -> + let onmousemove (element: IRefValue) (startPosition: Rect) setPosition = fun (e: Event) -> let nextPosition = calculatePosition element startPosition e setPosition (Some nextPosition) - let onmouseup (prefix,element:IRefValue) onmousemove = + let onmouseup (prefix,element: IRefValue) onmousemove = Browser.Dom.document.removeEventListener("mousemove", onmousemove) if element.current.IsSome then let rect = element.current.Value.getBoundingClientRect() @@ -77,7 +77,7 @@ module private ResizeEventListener = let onmouseup (prefix, element: IRefValue) onmousemove = Browser.Dom.document.removeEventListener("mousemove", onmousemove) if element.current.IsSome then - Size.write(prefix,{X = int element.current.Value.offsetWidth; Y = int element.current.Value.offsetHeight}) + Size.write(prefix, {X = int element.current.Value.offsetWidth; Y = int element.current.Value.offsetHeight}) module private Elements = diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs index 9e8667c1..bb35e790 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs @@ -58,14 +58,16 @@ module Protocol = let nextModel = { model with Model.ProtocolState.TemplateSelected = Some prot + Model.ProtocolState.TemplatesSelected = [] Model.PageState.SidebarPage = Routing.SidebarPage.Protocol } state, Cmd.ofMsg (UpdateModel nextModel) | SelectProtocols prots -> log "SelectProtocols" - let newProts = prots |> List.rev + let newProts = prots let nextModel = { model with + Model.ProtocolState.TemplateSelected = None Model.ProtocolState.TemplatesSelected = newProts Model.PageState.SidebarPage = Routing.SidebarPage.Protocol } diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index 46320b80..16751a98 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -59,21 +59,45 @@ type SelectiveTemplateFromDBModal = ] static member AddFromDBToTableButton (model: Model) selectionInformation importType useTemplateName dispatch = - let addTemplate (templatePot: Template option, selectedColumns) = - if model.ProtocolState.TemplateSelected.IsNone && model.ProtocolState.TemplatesSelected.Length = 0 then + let addTemplate (model: Model, selectedColumns) = + let template = + if model.ProtocolState.TemplateSelected.IsNone && model.ProtocolState.TemplatesSelected.Length = 0 then + failwith "No template selected!" + if model.ProtocolState.TemplateSelected.IsSome then + model.ProtocolState.TemplateSelected.Value + else + model.ProtocolState.TemplatesSelected.Head + SpreadsheetInterface.AddTemplate(template.Table, selectedColumns, importType, useTemplateName) |> InterfaceMsg |> dispatch + Html.div [ + prop.className "join flex flex-row justify-center gap-2" + prop.children [ + let isDisabled = + model.ProtocolState.TemplateSelected.IsSome || model.ProtocolState.TemplatesSelected.Length = 0 + ModalElements.Button("Add template", addTemplate, (model, selectionInformation.Columns), isDisabled) + if model.ProtocolState.TemplateSelected.IsSome || model.ProtocolState.TemplatesSelected.Length > 0 then + Daisy.button.a [ + button.outline + prop.onClick (fun _ -> Protocol.RemoveSelectedProtocol |> ProtocolMsg |> dispatch) + button.error + Html.i [prop.className "fa-solid fa-times"] |> prop.children + ] + ] + ] + + static member AddTemplatesFromDBToTableButton (model: Model) selectionInformation importType useTemplateName dispatch = + let addTemplate (model: Model, selectedColumns) = + let templates = model.ProtocolState.TemplatesSelected + if templates.Length = 0 then failwith "No template selected!" - if templatePot.IsSome || model.ProtocolState.TemplatesSelected.Length = 1 then - let table = templatePot.Value.Table - SpreadsheetInterface.AddTemplate(table, selectedColumns, importType, useTemplateName) |> InterfaceMsg |> dispatch if model.ProtocolState.TemplatesSelected.Length > 1 then - let table = templatePot.Value.Table - SpreadsheetInterface.AddTemplate(table, selectedColumns, importType, useTemplateName) |> InterfaceMsg |> dispatch + let reversedTables = templates |> List.rev |> List.map (fun item -> item.Table) |> Array.ofList + SpreadsheetInterface.AddTemplates(reversedTables, selectedColumns, importType, useTemplateName) |> InterfaceMsg |> dispatch Html.div [ prop.className "join flex flex-row justify-center gap-2" prop.children [ let isDisabled = model.ProtocolState.TemplateSelected.IsSome || model.ProtocolState.TemplatesSelected.Length = 0 - ModalElements.Button("Add template", addTemplate, (model.ProtocolState.TemplateSelected, selectionInformation.Columns), isDisabled) + ModalElements.Button("Add template", addTemplate, (model, selectionInformation.Columns), isDisabled) if model.ProtocolState.TemplateSelected.IsSome || model.ProtocolState.TemplatesSelected.Length > 0 then Daisy.button.a [ button.outline @@ -93,7 +117,6 @@ type SelectiveTemplateFromDBModal = let selectedColumns, setSelectedColumns = React.useState(SelectedColumns.init length) let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) let useTemplateName, setUseTemplateName = React.useState(AdaptTableName.init) - log("model.ProtocolState.TemplatesSelected.Length", model.ProtocolState.TemplatesSelected.Length) SidebarComponents.SidebarLayout.LogicContainer [ Html.div [ SelectiveTemplateFromDBModal.ToProtocolSearchElement model dispatch @@ -132,8 +155,9 @@ type SelectiveTemplateFromDBModal = SelectiveTemplateFromDBModal.displaySelectedProtocolElements(Some template, selectedColumns, setSelectedColumns, dispatch, false)) ] else if model.ProtocolState.TemplatesSelected.Length > 1 then - for i in 0..model.ProtocolState.TemplatesSelected.Length-1 do - let template = model.ProtocolState.TemplatesSelected.[i] + let templates = model.ProtocolState.TemplatesSelected |> List.rev + for i in 0..templates.Length-1 do + let template = templates.[i] Html.div [ ModalElements.Box( template.Name, diff --git a/src/Client/States/SpreadsheetInterface.fs b/src/Client/States/SpreadsheetInterface.fs index e11be4bb..232936c9 100644 --- a/src/Client/States/SpreadsheetInterface.fs +++ b/src/Client/States/SpreadsheetInterface.fs @@ -19,6 +19,7 @@ type Msg = | AddDataAnnotation of {| fragmentSelectors: string []; fileName: string; fileType: string; targetColumn: DataAnnotator.TargetColumn |} /// This function will do preprocessing on the table to join | AddTemplate of ArcTable * bool[] * SelectiveImportModalState * string option +| AddTemplates of ArcTable[] * bool[] * SelectiveImportModalState * string option | JoinTable of ArcTable * columnIndex: int option * options: TableJoinOptions option | UpdateArcFile of ArcFiles /// Inserts TermMinimal to selected fields of one column From 41c76e649b346473d1fca505ae0b72e3fee0cfb7 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Thu, 12 Dec 2024 11:21:54 +0100 Subject: [PATCH 04/23] Removed templateSelected Adapted SelectedColumnState to contain multiple Columns --- src/Client/MainComponents/Widgets.fs | 23 ++- src/Client/Messages.fs | 3 +- src/Client/Modals/SelectiveImportModal.fs | 50 ++--- src/Client/Model.fs | 2 - src/Client/OfficeInterop/OfficeInterop.fs | 190 +++++++++++++----- .../ProtocolSearchViewComponent.fs | 7 +- .../Pages/ProtocolTemplates/ProtocolState.fs | 13 +- .../SelectiveTemplateFromDB.fs | 107 +++++----- src/Client/States/OfficeInteropState.fs | 1 + src/Client/States/Spreadsheet.fs | 1 + src/Client/States/SpreadsheetInterface.fs | 4 +- src/Client/Types.fs | 6 +- src/Client/Update/InterfaceUpdate.fs | 13 +- src/Client/Update/OfficeInteropUpdate.fs | 13 +- src/Client/Update/UpdateUtil.fs | 7 +- src/Shared/ARCtrl.Helper.fs | 1 - 16 files changed, 262 insertions(+), 179 deletions(-) diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index 712730a1..4c178c93 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -215,11 +215,12 @@ type Widget = static member Templates (model: Model, dispatch, rmv: MouseEvent -> unit) = let templates, setTemplates = React.useState(model.ProtocolState.Templates) let config, setConfig = React.useState(TemplateFilterConfig.init) - let selectedColumnsLength = - if model.ProtocolState.TemplateSelected.IsSome then - model.ProtocolState.TemplateSelected.Value.Table.Columns.Length - else 0 - let selectedColumns, setSelectedColumns = React.useState(SelectedColumns.init selectedColumnsLength) + let selectedColumns, setSelectedColumns = + let columns = + model.ProtocolState.TemplatesSelected + |> Array.ofSeq + |> Array.map (fun t -> Array.init t.Table.Columns.Length (fun _ -> true)) + React.useState(SelectedColumns.init columns) let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) let useTemplateName, setUseTemplateName = React.useState(AdaptTableName.init) let filteredTemplates = Protocol.Search.filterTemplates (templates, config) @@ -239,7 +240,7 @@ type Widget = Html.div [ SelectiveTemplateFromDBModal.ToProtocolSearchElement model dispatch ] - if model.ProtocolState.TemplateSelected.IsSome then + if model.ProtocolState.TemplatesSelected.Length > 0 then Html.div [ SelectiveImportModal.RadioPluginsBox( "Import Type", @@ -258,23 +259,23 @@ type Widget = ModalElements.Box( "Rename Table", "fa-solid fa-cog", - SelectiveTemplateFromDBModal.CheckBoxForTakeOverTemplateName(useTemplateName, setUseTemplateName, model.ProtocolState.TemplateSelected.Value.Name)) + SelectiveTemplateFromDBModal.CheckBoxForTakeOverTemplateName(useTemplateName, setUseTemplateName, model.ProtocolState.TemplatesSelected.Head.Name)) ] Html.div [ ModalElements.Box( - model.ProtocolState.TemplateSelected.Value.Name, + model.ProtocolState.TemplatesSelected.Head.Name, "", - SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model.ProtocolState.TemplateSelected, selectedColumns, setSelectedColumns, dispatch, false)) + SelectiveTemplateFromDBModal.displaySelectedProtocolElements(Some model.ProtocolState.TemplatesSelected.Head, 0, selectedColumns, setSelectedColumns, dispatch, false)) ] Html.div [ - SelectiveTemplateFromDBModal.AddFromDBToTableButton model selectedColumns importTypeState useTemplateName.TemplateName dispatch + SelectiveTemplateFromDBModal.AddFromDBToTableButton "Add template" model selectedColumns importTypeState useTemplateName.TemplateName dispatch ] ] ] ] ] let content = - let switchContent = if model.ProtocolState.TemplateSelected.IsNone then selectContent() else insertContent() + let switchContent = if model.ProtocolState.TemplatesSelected.Length = 0 then selectContent() else insertContent() Html.div [ prop.className "flex flex-col gap-4 @container/templateWidget" prop.children switchContent diff --git a/src/Client/Messages.fs b/src/Client/Messages.fs index 923d62b6..2deb4f04 100644 --- a/src/Client/Messages.fs +++ b/src/Client/Messages.fs @@ -77,12 +77,11 @@ module Protocol = // Client | UpdateTemplates of Template [] | UpdateLoading of bool - | RemoveSelectedProtocol + | RemoveSelectedProtocols // // ------ Protocol from Database ------ | GetAllProtocolsForceRequest | GetAllProtocolsRequest | GetAllProtocolsResponse of string - | SelectProtocol of Template | SelectProtocols of Template list | AddProtocol of Template | ProtocolIncreaseTimesUsed of protocolName:string diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index 9dd84c41..63bcca8c 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -24,7 +24,7 @@ type SelectiveImportModal = ] ]) - static member CheckBoxForTableColumnSelection(columns: CompositeColumn [], index, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit) = + static member CheckBoxForTableColumnSelection(columns: CompositeColumn [], tableIndex, columnIndex, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit) = Html.div [ prop.style [style.display.flex; style.justifyContent.center] prop.children [ @@ -34,20 +34,21 @@ type SelectiveImportModal = style.height(length.perc 100) ] prop.isChecked - (if selectionInformation.Columns.Length > 0 then - selectionInformation.Columns.[index] + (if selectionInformation.SelectedColumns.Length > 0 then + selectionInformation.SelectedColumns.[tableIndex].[columnIndex] else true) prop.onChange (fun (b: bool) -> if columns.Length > 0 then - let selectedData = selectionInformation.Columns - selectedData.[index] <- b - {selectionInformation with Columns = selectedData} |> setSelectedColumns) + let selectedData = selectionInformation.SelectedColumns + selectedData.[tableIndex].[columnIndex] <- b + {selectionInformation with SelectedColumns = selectedData} |> setSelectedColumns) ] ] ] - static member TableWithImportColumnCheckboxes(table: ArcTable, ?selectionInformation: SelectedColumns, ?setSelectedColumns: SelectedColumns -> unit) = + static member TableWithImportColumnCheckboxes(table: ArcTable, ?tableIndex, ?selectionInformation: SelectedColumns, ?setSelectedColumns: SelectedColumns -> unit) = let columns = table.Columns + let tableIndex = defaultArg tableIndex 0 let displayCheckBox = //Determine whether to display checkboxes or not selectionInformation.IsSome && setSelectedColumns.IsSome @@ -55,20 +56,20 @@ type SelectiveImportModal = prop.children [ Html.thead [ Html.tr [ - for i in 0..columns.Length-1 do + for columnIndex in 0..columns.Length-1 do Html.th [ Html.label [ prop.className "join flex flex-row centered gap-2" prop.children [ if displayCheckBox then - SelectiveImportModal.CheckBoxForTableColumnSelection(columns, i, selectionInformation.Value, setSelectedColumns.Value) - Html.text (columns.[i].Header.ToString()) + SelectiveImportModal.CheckBoxForTableColumnSelection(columns, tableIndex, columnIndex, selectionInformation.Value, setSelectedColumns.Value) + Html.text (columns.[columnIndex].Header.ToString()) Html.div [ prop.onClick (fun e -> if columns.Length > 0 && selectionInformation.IsSome then - let selectedData = selectionInformation.Value.Columns - selectedData.[i] <- not selectedData.[i] - {selectionInformation.Value with Columns = selectedData} |> setSelectedColumns.Value) + let selectedData = selectionInformation.Value.SelectedColumns + selectedData.[tableIndex].[columnIndex] <- not selectedData.[tableIndex].[columnIndex] + {selectionInformation.Value with SelectedColumns = selectedData} |> setSelectedColumns.Value) ] ] ] @@ -117,27 +118,27 @@ type SelectiveImportModal = ) [] - static member private TableImport(index: int, table0: ArcTable, state: SelectiveImportModalState, addTableImport: int -> bool -> unit, rmvTableImport: int -> unit, selectedColumns, setSelectedColumns) = + static member private TableImport(tableIndex: int, table0: ArcTable, state: SelectiveImportModalState, addTableImport: int -> bool -> unit, rmvTableImport: int -> unit, selectedColumns, setSelectedColumns) = let name = table0.Name let radioGroup = "radioGroup_" + name - let import = state.ImportTables |> List.tryFind (fun it -> it.Index = index) + let import = state.ImportTables |> List.tryFind (fun it -> it.Index = tableIndex) let isActive = import.IsSome let isDisabled = state.ImportMetadata ModalElements.Box (name, "fa-solid fa-table", React.fragment [ Html.div [ ModalElements.RadioPlugin (radioGroup, "Import", isActive && import.Value.FullImport, - (fun (b:bool) -> addTableImport index true), + (fun (b:bool) -> addTableImport tableIndex true), isDisabled ) ModalElements.RadioPlugin (radioGroup, "Append to active table", isActive && not import.Value.FullImport, - (fun (b:bool) -> addTableImport index false), + (fun (b:bool) -> addTableImport tableIndex false), isDisabled ) ModalElements.RadioPlugin (radioGroup, "No Import", not isActive, - (fun (b:bool) -> rmvTableImport index), + (fun (b:bool) -> rmvTableImport tableIndex), isDisabled ) ] @@ -151,7 +152,7 @@ type SelectiveImportModal = prop.className "overflow-x-auto" prop.children [ if isActive then - SelectiveImportModal.TableWithImportColumnCheckboxes(table0, selectedColumns, setSelectedColumns) + SelectiveImportModal.TableWithImportColumnCheckboxes(table0, tableIndex, selectedColumns, setSelectedColumns) else SelectiveImportModal.TableWithImportColumnCheckboxes(table0) ] @@ -184,10 +185,12 @@ type SelectiveImportModal = {state with ImportTables = newImportTables} |> setState let rmvTableImport = fun i -> {state with ImportTables = state.ImportTables |> List.filter (fun it -> it.Index <> i)} |> setState - let selectedColumns = - tables - |> Array.ofSeq - |> Array.map (fun t -> React.useState(SelectedColumns.init t.Columns.Length)) + let selectedColumns, setSelectedColumns = + let columns = + tables + |> Array.ofSeq + |> Array.map (fun t -> Array.init t.Columns.Length (fun _ -> true)) + React.useState(SelectedColumns.init columns) Daisy.modal.div [ modal.active prop.children [ @@ -216,7 +219,6 @@ type SelectiveImportModal = SelectiveImportModal.MetadataImport(state.ImportMetadata, setMetadataImport, disArcfile) for ti in 0 .. (tables.Count-1) do let t = tables.[ti] - let selectedColumns, setSelectedColumns = fst selectedColumns.[ti], snd selectedColumns.[ti] SelectiveImportModal.TableImport(ti, t, state, addTableImport, rmvTableImport, selectedColumns, setSelectedColumns) Daisy.cardActions [ Daisy.button.button [ diff --git a/src/Client/Model.fs b/src/Client/Model.fs index 6e39a8ef..3bf68fc4 100644 --- a/src/Client/Model.fs +++ b/src/Client/Model.fs @@ -246,7 +246,6 @@ module Protocol = Loading : bool LastUpdated : System.DateTime option // ------ Protocol from Database ------ - TemplateSelected : ARCtrl.Template option TemplatesSelected : ARCtrl.Template list Templates : ARCtrl.Template [] } with @@ -254,7 +253,6 @@ module Protocol = // Client Loading = false LastUpdated = None - TemplateSelected = None TemplatesSelected = [] // ------ Protocol from Database ------ Templates = [||] diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index 90648be3..24c95c03 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -965,7 +965,7 @@ let selectiveTablePrepare (activeTable: ArcTable) (toJoinTable: ArcTable) (remov /// Prepare the given table to be joined with the currently active annotation table /// /// -let prepareTemplateInMemory (table: Table) (tableToAdd: ArcTable) (selectedColumns:bool []) (context: RequestContext) = +let prepareTemplateInMemoryForExcel (table: Table) (tableToAdd: ArcTable) (selectedColumns:bool []) (context: RequestContext) = promise { let! originTableRes = ArcTable.fromExcelTable(table, context) @@ -1000,14 +1000,89 @@ let prepareTemplateInMemory (table: Table) (tableToAdd: ArcTable) (selectedColum return finalTable, Some (targetIndex) } +/// +/// Prepare the given table to be joined with the currently active annotation table +/// +/// +let prepareTemplateInMemory (originTable: ArcTable) (tableToAdd: ArcTable) (selectedColumns:bool []) = + let selectedColumnIndices = + selectedColumns + |> Array.mapi (fun i item -> if item = false then Some i else None) + |> Array.choose (fun x -> x) + |> List.ofArray + + let finalTable = Table.selectiveTablePrepare originTable tableToAdd selectedColumnIndices + + finalTable + +let exportArcTableToExcle (excelTable: Table) (arcTable:ArcTable) (templateName: string option) (context: RequestContext) = + promise { + let newTableRange = excelTable.getRange() + + let _ = newTableRange.load(propertyNames = U2.Case2 (ResizeArray["rowCount"])) + + do! context.sync().``then``(fun _ -> + excelTable.delete() + ) + + let! (newTable, _) = AnnotationTable.createAtRange(false, false, newTableRange, context) + + let _ = newTable.load(propertyNames = U2.Case2 (ResizeArray["name"; "values"; "columns"])) + + let tableSeqs = arcTable.ToStringSeqs() + + do! context.sync().``then``(fun _ -> + if templateName.IsSome then + //Should be updated to remove all kinds of extra symbols + let templateName = Helper.removeChars Helper.charsToRemove templateName.Value + newTable.name <- $"annotationTable{templateName}" + else + newTable.name <- excelTable.name + + let headerNames = + let names = AnnotationTable.getHeaders tableSeqs + names + |> Array.map (fun name -> extendName names name) + + headerNames + |> Array.iteri(fun i header -> + ExcelUtil.Table.addColumn i newTable header (int newTableRange.rowCount) "" |> ignore) + ) + + let bodyRange = newTable.getDataBodyRange() + + let _ = bodyRange.load(propertyNames = U2.Case2 (ResizeArray["columnCount"; "rowCount"; "values"])) + + do! context.sync().``then``(fun _ -> + + //We delete the annotation table because we cannot overwrite an existing one + //As a result we create a new annotation table that has one column + //We delete the newly created column of the newly created table + newTable.columns.getItemAt(bodyRange.columnCount - 1.).delete() + ) + + let newBodyRange = newTable.getDataBodyRange() + + let _ = + newTable.columns.load(propertyNames = U2.Case2 (ResizeArray["name"; "items"])) |> ignore + newBodyRange.load(propertyNames = U2.Case2 (ResizeArray["name"; "columnCount"; "values"])) + + do! context.sync() + + let bodyValues = AnnotationTable.getBody tableSeqs + newBodyRange.values <- bodyValues + + do! AnnotationTable.format(newTable, context, true) + } + /// /// Add the given arc table to the active annotation table at the desired index /// /// /// /// -let joinTable (tableToAdd: ArcTable, selectedColumns: bool [], options: TableJoinOptions option, templateName: string option) = - Excel.run(fun context -> +let joinTable (tableToAdd: ArcTable, selectedColumns: bool [], options: TableJoinOptions option, templateName: string option, context0) = + excelRunWith context0 <| fun context -> promise { //When a name is available get the annotation and arctable for easy access of indices and value adaption //Annotation table enables a easy way to adapt the table, updating existing and adding new columns @@ -1015,7 +1090,7 @@ let joinTable (tableToAdd: ArcTable, selectedColumns: bool [], options: TableJoi match result with | Some excelTable -> - let! (refinedTableToAdd: ArcTable, index: int option) = prepareTemplateInMemory excelTable tableToAdd selectedColumns context + let! (refinedTableToAdd: ArcTable, index: int option) = prepareTemplateInMemoryForExcel excelTable tableToAdd selectedColumns context //Arctable enables a fast check for the existence of input- and output-columns and their indices let! arcTableRes = ArcTable.fromExcelTable(excelTable, context) @@ -1025,66 +1100,77 @@ let joinTable (tableToAdd: ArcTable, selectedColumns: bool [], options: TableJoi | Result.Ok arcTable -> arcTable.Join(refinedTableToAdd, ?index=index, ?joinOptions=options, forceReplace=true) - let newTableRange = excelTable.getRange() - - let _ = newTableRange.load(propertyNames = U2.Case2 (ResizeArray["rowCount"])) + do! exportArcTableToExcle excelTable arcTable templateName context - do! context.sync().``then``(fun _ -> - excelTable.delete() - ) + return [InteropLogging.Msg.create InteropLogging.Info $"Joined template {refinedTableToAdd.Name} to table {excelTable.name}!"] + | Result.Error _ -> + return [InteropLogging.Msg.create InteropLogging.Error "No arc table could be created! This should not happen at this stage! Please report this as a bug to the developers.!"] + | None -> return [InteropLogging.NoActiveTableMsg] + } - let! (newTable, _) = AnnotationTable.createAtRange(false, false, newTableRange, context) +/// +/// Add the given arc table to the active annotation table at the desired index +/// +/// +/// +/// +let joinTables (tablesToAdd: ArcTable [], selectedColumnsCollection: bool [] [], options: TableJoinOptions option) = + Excel.run(fun context -> + promise { + //When a name is available get the annotation and arctable for easy access of indices and value adaption + //Annotation table enables a easy way to adapt the table, updating existing and adding new columns + let! result = AnnotationTable.tryGetActive context - let _ = newTable.load(propertyNames = U2.Case2 (ResizeArray["name"; "values"; "columns"])) + match result with + | Some excelTable -> + let! originTableRes = ArcTable.fromExcelTable(excelTable, context) - let tableSeqs = arcTable.ToStringSeqs() + match originTableRes with + | Result.Error _ -> + return failwith $"Failed to create arc table for table {excelTable.name}" + | Result.Ok originTable -> - do! context.sync().``then``(fun _ -> - if templateName.IsSome then - //Should be updated to remove all kinds of extra symbols - let templateName = Helper.removeChars Helper.charsToRemove templateName.Value - newTable.name <- $"annotationTable{templateName}" + let selectedRange = context.workbook.getSelectedRange() + let tableStartIndex = excelTable.getRange() + let _ = + tableStartIndex.load(propertyNames=U2.Case2 (ResizeArray[|"columnIndex"|])) |> ignore + selectedRange.load(propertyNames=U2.Case2 (ResizeArray[|"columnIndex"|])) + + // sync with proxy objects after loading values from excel + do! context.sync().``then``( fun _ -> ()) + + let targetIndex = + let adaptedStartIndex = selectedRange.columnIndex - tableStartIndex.columnIndex + if adaptedStartIndex > float (originTable.ColumnCount) then originTable.ColumnCount + else int adaptedStartIndex + 1 + + let rec loop (originTable: ArcTable) (tablesToAdd: ArcTable []) (selectedColumns: bool[][]) (options: TableJoinOptions option) i = + let tableToAdd = tablesToAdd.[i] + let refinedTableToAdd = prepareTemplateInMemory originTable tableToAdd selectedColumns.[i] + + let newTable = + if i > 0 then + originTable.Join(refinedTableToAdd, ?joinOptions=options, forceReplace=true) + originTable + else + refinedTableToAdd + + if i = tablesToAdd.Length-1 then + newTable else - newTable.name <- excelTable.name - - let headerNames = - let names = AnnotationTable.getHeaders tableSeqs - names - |> Array.map (fun name -> extendName names name) - - headerNames - |> Array.iteri(fun i header -> - ExcelUtil.Table.addColumn i newTable header (int newTableRange.rowCount) "" |> ignore) - ) - - let bodyRange = newTable.getDataBodyRange() - - let _ = bodyRange.load(propertyNames = U2.Case2 (ResizeArray["columnCount"; "rowCount"; "values"])) + loop newTable tablesToAdd selectedColumns options (i + 1) - do! context.sync().``then``(fun _ -> + let processedAddTable = loop originTable tablesToAdd selectedColumnsCollection options 0 - //We delete the annotation table because we cannot overwrite an existing one - //As a result we create a new annotation table that has one column - //We delete the newly created column of the newly created table - newTable.columns.getItemAt(bodyRange.columnCount - 1.).delete() - ) + let finalTable = prepareTemplateInMemory originTable processedAddTable [||] - let newBodyRange = newTable.getDataBodyRange() + originTable.Join(finalTable, targetIndex, ?joinOptions=options, forceReplace=true) - let _ = - newTable.columns.load(propertyNames = U2.Case2 (ResizeArray["name"; "items"])) |> ignore - newBodyRange.load(propertyNames = U2.Case2 (ResizeArray["name"; "columnCount"; "values"])) - - do! context.sync() + do! exportArcTableToExcle excelTable originTable None context - let bodyValues = AnnotationTable.getBody tableSeqs - newBodyRange.values <- bodyValues + let templateNames = tablesToAdd |> Array.map (fun item -> item.Name) - do! AnnotationTable.format(newTable, context, true) - - return [InteropLogging.Msg.create InteropLogging.Info $"Joined template {refinedTableToAdd.Name} to table {excelTable.name}!"] - | Result.Error _ -> - return [InteropLogging.Msg.create InteropLogging.Error "No arc table could be created! This should not happen at this stage! Please report this as a bug to the developers.!"] + return [InteropLogging.Msg.create InteropLogging.Info $"Joined templates {templateNames} to table {excelTable.name}!"] | None -> return [InteropLogging.NoActiveTableMsg] } ) diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs b/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs index 95f913a8..5ccd593b 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs @@ -394,7 +394,7 @@ module ComponentAux = Daisy.button.a [ button.sm prop.onClick (fun _ -> - SelectProtocol template |> ProtocolMsg |> dispatch + SelectProtocols [template] |> ProtocolMsg |> dispatch ) button.wide button.success @@ -619,10 +619,7 @@ type Search = Daisy.button.a [ button.sm prop.onClick (fun _ -> - if model.ProtocolState.TemplatesSelected.Length > 1 then - SelectProtocols model.ProtocolState.TemplatesSelected |> ProtocolMsg |> dispatch - else - SelectProtocol model.ProtocolState.TemplatesSelected.Head |> ProtocolMsg |> dispatch + SelectProtocols model.ProtocolState.TemplatesSelected |> ProtocolMsg |> dispatch ) button.wide button.success diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs index bb35e790..b17c038c 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs @@ -53,21 +53,11 @@ module Protocol = Templates = templates } nextState, Cmd.none - | SelectProtocol prot -> - log "SelectProtocol" - let nextModel = { - model with - Model.ProtocolState.TemplateSelected = Some prot - Model.ProtocolState.TemplatesSelected = [] - Model.PageState.SidebarPage = Routing.SidebarPage.Protocol - } - state, Cmd.ofMsg (UpdateModel nextModel) | SelectProtocols prots -> log "SelectProtocols" let newProts = prots let nextModel = { model with - Model.ProtocolState.TemplateSelected = None Model.ProtocolState.TemplatesSelected = newProts Model.PageState.SidebarPage = Routing.SidebarPage.Protocol } @@ -95,10 +85,9 @@ module Protocol = state, Cmd.none // Client - | RemoveSelectedProtocol -> + | RemoveSelectedProtocols -> let nextState = { state with - TemplateSelected = None TemplatesSelected = [] } nextState, Cmd.none diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index 16751a98..e96207dd 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -44,7 +44,7 @@ type SelectiveTemplateFromDBModal = ] [] - static member displaySelectedProtocolElements(selectedTemplate: Template option, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit, dispatch, ?hasIcon: bool) = + static member displaySelectedProtocolElements(selectedTemplate: Template option, templateIndex, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit, dispatch, ?hasIcon: bool) = let hasIcon = defaultArg hasIcon true Html.div [ prop.style [style.overflowX.auto; style.marginBottom (length.rem 1)] @@ -54,54 +54,51 @@ type SelectiveTemplateFromDBModal = Html.i [prop.className "fa-solid fa-cog"] Html.span $"Template: {selectedTemplate.Value.Name}" if selectedTemplate.IsSome then - SelectiveImportModal.TableWithImportColumnCheckboxes(selectedTemplate.Value.Table, selectionInformation, setSelectedColumns) + SelectiveImportModal.TableWithImportColumnCheckboxes(selectedTemplate.Value.Table, templateIndex, selectionInformation, setSelectedColumns) ] ] - static member AddFromDBToTableButton (model: Model) selectionInformation importType useTemplateName dispatch = + static member AddFromDBToTableButton name (model: Model) selectionInformation importType useTemplateName dispatch = let addTemplate (model: Model, selectedColumns) = let template = - if model.ProtocolState.TemplateSelected.IsNone && model.ProtocolState.TemplatesSelected.Length = 0 then + if model.ProtocolState.TemplatesSelected.Length = 0 then failwith "No template selected!" - if model.ProtocolState.TemplateSelected.IsSome then - model.ProtocolState.TemplateSelected.Value else model.ProtocolState.TemplatesSelected.Head SpreadsheetInterface.AddTemplate(template.Table, selectedColumns, importType, useTemplateName) |> InterfaceMsg |> dispatch Html.div [ prop.className "join flex flex-row justify-center gap-2" prop.children [ - let isDisabled = - model.ProtocolState.TemplateSelected.IsSome || model.ProtocolState.TemplatesSelected.Length = 0 - ModalElements.Button("Add template", addTemplate, (model, selectionInformation.Columns), isDisabled) - if model.ProtocolState.TemplateSelected.IsSome || model.ProtocolState.TemplatesSelected.Length > 0 then + let isDisabled = model.ProtocolState.TemplatesSelected.Length = 0 + ModalElements.Button(name, addTemplate, (model, selectionInformation.SelectedColumns.[0]), isDisabled) + if model.ProtocolState.TemplatesSelected.Length > 0 then Daisy.button.a [ button.outline - prop.onClick (fun _ -> Protocol.RemoveSelectedProtocol |> ProtocolMsg |> dispatch) + prop.onClick (fun _ -> Protocol.RemoveSelectedProtocols |> ProtocolMsg |> dispatch) button.error Html.i [prop.className "fa-solid fa-times"] |> prop.children ] ] ] - static member AddTemplatesFromDBToTableButton (model: Model) selectionInformation importType useTemplateName dispatch = - let addTemplate (model: Model, selectedColumns) = + static member AddTemplatesFromDBToTableButton name (model: Model) (selectionInformation: SelectedColumns) importType dispatch = + let addTemplates (model: Model, selectedColumns) = let templates = model.ProtocolState.TemplatesSelected if templates.Length = 0 then failwith "No template selected!" if model.ProtocolState.TemplatesSelected.Length > 1 then - let reversedTables = templates |> List.rev |> List.map (fun item -> item.Table) |> Array.ofList - SpreadsheetInterface.AddTemplates(reversedTables, selectedColumns, importType, useTemplateName) |> InterfaceMsg |> dispatch + let reversedTables = templates |> List.map (fun item -> item.Table) |> Array.ofList + SpreadsheetInterface.AddTemplates(reversedTables, selectedColumns, importType) |> InterfaceMsg |> dispatch Html.div [ prop.className "join flex flex-row justify-center gap-2" prop.children [ - let isDisabled = - model.ProtocolState.TemplateSelected.IsSome || model.ProtocolState.TemplatesSelected.Length = 0 - ModalElements.Button("Add template", addTemplate, (model, selectionInformation.Columns), isDisabled) - if model.ProtocolState.TemplateSelected.IsSome || model.ProtocolState.TemplatesSelected.Length > 0 then + let isDisabled = model.ProtocolState.TemplatesSelected.Length = 0 + let selectedColumnValues = selectionInformation.SelectedColumns + ModalElements.Button(name, addTemplates, (model, selectedColumnValues), isDisabled) + if model.ProtocolState.TemplatesSelected.Length > 0 then Daisy.button.a [ button.outline - prop.onClick (fun _ -> Protocol.RemoveSelectedProtocol |> ProtocolMsg |> dispatch) + prop.onClick (fun _ -> Protocol.RemoveSelectedProtocols |> ProtocolMsg |> dispatch) button.error Html.i [prop.className "fa-solid fa-times"] |> prop.children ] @@ -110,61 +107,59 @@ type SelectiveTemplateFromDBModal = [] static member Main(model: Model, dispatch) = - let length = - if model.ProtocolState.TemplateSelected.IsSome then - model.ProtocolState.TemplateSelected.Value.Table.Columns.Length - else 0 - let selectedColumns, setSelectedColumns = React.useState(SelectedColumns.init length) + let selectedColumns, setSelectedColumns = + let columns = + model.ProtocolState.TemplatesSelected + |> Array.ofSeq + |> Array.map (fun t -> Array.init t.Table.Columns.Length (fun _ -> true)) + React.useState(SelectedColumns.init columns) let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) let useTemplateName, setUseTemplateName = React.useState(AdaptTableName.init) SidebarComponents.SidebarLayout.LogicContainer [ Html.div [ SelectiveTemplateFromDBModal.ToProtocolSearchElement model dispatch ] - if model.ProtocolState.TemplateSelected.IsSome || model.ProtocolState.TemplatesSelected.Length > 0 then - Html.div [ - SelectiveImportModal.RadioPluginsBox( - "Import Type", - "fa-solid fa-cog", - importTypeState.ImportType, - "importType", - [| - ARCtrl.TableJoinOptions.Headers, " Column Headers"; - ARCtrl.TableJoinOptions.WithUnit, " ..With Units"; - ARCtrl.TableJoinOptions.WithValues, " ..With Values"; - |], - fun importType -> {importTypeState with ImportType = importType} |> setImportTypeState - ) - ] - if model.ProtocolState.TemplateSelected.IsSome || model.ProtocolState.TemplatesSelected.Length = 1 then - let template = - if model.ProtocolState.TemplateSelected.IsSome then - model.ProtocolState.TemplateSelected.Value - else - model.ProtocolState.TemplatesSelected.Head + if model.ProtocolState.TemplatesSelected.Length > 0 then + SelectiveImportModal.RadioPluginsBox( + "Import Type", + "fa-solid fa-cog", + importTypeState.ImportType, + "importType", + [| + ARCtrl.TableJoinOptions.Headers, " Column Headers"; + ARCtrl.TableJoinOptions.WithUnit, " ..With Units"; + ARCtrl.TableJoinOptions.WithValues, " ..With Values"; + |], + fun importType -> {importTypeState with ImportType = importType} |> setImportTypeState + ) + if model.ProtocolState.TemplatesSelected.Length = 1 then + let template = model.ProtocolState.TemplatesSelected.Head Html.div [ ModalElements.Box( "Rename Table", "fa-solid fa-cog", - SelectiveTemplateFromDBModal.CheckBoxForTakeOverTemplateName(useTemplateName, setUseTemplateName, model.ProtocolState.TemplateSelected.Value.Name)) + SelectiveTemplateFromDBModal.CheckBoxForTakeOverTemplateName(useTemplateName, setUseTemplateName, template.Name)) ] Html.div [ ModalElements.Box( - model.ProtocolState.TemplateSelected.Value.Name, + template.Name, "fa-solid fa-cog", - SelectiveTemplateFromDBModal.displaySelectedProtocolElements(Some template, selectedColumns, setSelectedColumns, dispatch, false)) + SelectiveTemplateFromDBModal.displaySelectedProtocolElements(Some template, 0, selectedColumns, setSelectedColumns, dispatch, false)) + ] + Html.div [ + SelectiveTemplateFromDBModal.AddFromDBToTableButton "Add template" model selectedColumns importTypeState useTemplateName.TemplateName dispatch ] else if model.ProtocolState.TemplatesSelected.Length > 1 then - let templates = model.ProtocolState.TemplatesSelected |> List.rev - for i in 0..templates.Length-1 do - let template = templates.[i] + let templates = model.ProtocolState.TemplatesSelected + for templateIndex in 0..templates.Length-1 do + let template = templates.[templateIndex] Html.div [ ModalElements.Box( template.Name, "fa-solid fa-cog", - SelectiveTemplateFromDBModal.displaySelectedProtocolElements(Some template, selectedColumns, setSelectedColumns, dispatch, false)) + SelectiveTemplateFromDBModal.displaySelectedProtocolElements(Some template, templateIndex, selectedColumns, setSelectedColumns, dispatch, false)) ] - Html.div [ - SelectiveTemplateFromDBModal.AddFromDBToTableButton model selectedColumns importTypeState useTemplateName.TemplateName dispatch - ] + Html.div [ + SelectiveTemplateFromDBModal.AddTemplatesFromDBToTableButton "Add templates" model selectedColumns importTypeState dispatch + ] ] diff --git a/src/Client/States/OfficeInteropState.fs b/src/Client/States/OfficeInteropState.fs index ac5e8bd3..af9bd1a7 100644 --- a/src/Client/States/OfficeInteropState.fs +++ b/src/Client/States/OfficeInteropState.fs @@ -32,6 +32,7 @@ type Msg = | AddAnnotationBlock of CompositeColumn | AddAnnotationBlocks of CompositeColumn [] //* OfficeInterop.Types.Xml.ValidationTypes.TableValidation option | AddTemplate of ArcTable * bool[] * SelectiveImportModalState * string option + | AddTemplates of ArcTable[] * bool[][] * SelectiveImportModalState | JoinTable of ArcTable * options: TableJoinOptions option | RemoveBuildingBlock | UpdateUnitForCells diff --git a/src/Client/States/Spreadsheet.fs b/src/Client/States/Spreadsheet.fs index f1f9d9fe..907013f9 100644 --- a/src/Client/States/Spreadsheet.fs +++ b/src/Client/States/Spreadsheet.fs @@ -203,6 +203,7 @@ type Msg = | AddAnnotationBlocks of CompositeColumn [] | AddDataAnnotation of {| fragmentSelectors: string []; fileName: string; fileType: string; targetColumn: DataAnnotator.TargetColumn |} | AddTemplate of ArcTable * bool[] * SelectiveImportModalState * string option +| AddTemplates of ArcTable[] * bool[][] * SelectiveImportModalState * string option | JoinTable of ArcTable * index: int option * options: TableJoinOptions option * string option | UpdateArcFile of ArcFiles | InitFromArcFile of ArcFiles diff --git a/src/Client/States/SpreadsheetInterface.fs b/src/Client/States/SpreadsheetInterface.fs index 232936c9..90da62f7 100644 --- a/src/Client/States/SpreadsheetInterface.fs +++ b/src/Client/States/SpreadsheetInterface.fs @@ -19,14 +19,14 @@ type Msg = | AddDataAnnotation of {| fragmentSelectors: string []; fileName: string; fileType: string; targetColumn: DataAnnotator.TargetColumn |} /// This function will do preprocessing on the table to join | AddTemplate of ArcTable * bool[] * SelectiveImportModalState * string option -| AddTemplates of ArcTable[] * bool[] * SelectiveImportModalState * string option +| AddTemplates of ArcTable[] * bool[][] * SelectiveImportModalState | JoinTable of ArcTable * columnIndex: int option * options: TableJoinOptions option | UpdateArcFile of ArcFiles /// Inserts TermMinimal to selected fields of one column | InsertOntologyAnnotation of OntologyAnnotation | InsertFileNames of string list | ImportXlsx of byte [] -| ImportJson of {|importState: SelectiveImportModalState; importedFile: ArcFiles; selectedColumns: (SelectedColumns * (SelectedColumns -> unit))[]|} +| ImportJson of {|importState: SelectiveImportModalState; importedFile: ArcFiles; selectedColumns: SelectedColumns|} /// Starts chain to export active table to isa json | ExportJson of ArcFiles * JsonExportFormat | UpdateUnitForCells diff --git a/src/Client/Types.fs b/src/Client/Types.fs index 36771ca7..75c41326 100644 --- a/src/Client/Types.fs +++ b/src/Client/Types.fs @@ -60,10 +60,10 @@ type Style = module TableImport = type SelectedColumns = { - Columns: bool [] + SelectedColumns: bool [] [] } with - static member init(length) = + static member init(selectedColumns) = { - Columns = Array.init length (fun _ -> true) + SelectedColumns = selectedColumns } diff --git a/src/Client/Update/InterfaceUpdate.fs b/src/Client/Update/InterfaceUpdate.fs index 027525a8..917c0909 100644 --- a/src/Client/Update/InterfaceUpdate.fs +++ b/src/Client/Update/InterfaceUpdate.fs @@ -173,6 +173,15 @@ module Interface = let cmd = Spreadsheet.AddTemplate (table, selectedColumns, importType, templateName) |> SpreadsheetMsg |> Cmd.ofMsg model, cmd | _ -> failwith "not implemented" + | AddTemplates (tables, selectedColumns, importType) -> + match host with + | Some Swatehost.Excel -> + let cmd = OfficeInterop.AddTemplates (tables, selectedColumns, importType) |> OfficeInteropMsg |> Cmd.ofMsg + model, cmd + //| Some Swatehost.Browser | Some Swatehost.ARCitect -> + // let cmd = Spreadsheet.AddTemplates (tables, selectedColumns, importType, templateName) |> SpreadsheetMsg |> Cmd.ofMsg + // model, cmd + | _ -> failwith "not implemented" | JoinTable (table, index, options) -> match host with | Some Swatehost.Excel -> @@ -201,9 +210,7 @@ module Interface = model, cmd | _ -> failwith "not implemented" | ImportJson data -> - let selectedColumns = - data.selectedColumns - |> Array.map (fun (sc, _) -> sc) + let selectedColumns = data.selectedColumns match host with | Some Swatehost.Excel -> /// In Excel we must get the current information from worksheets and update them with the imported information diff --git a/src/Client/Update/OfficeInteropUpdate.fs b/src/Client/Update/OfficeInteropUpdate.fs index 2aa719df..458877c5 100644 --- a/src/Client/Update/OfficeInteropUpdate.fs +++ b/src/Client/Update/OfficeInteropUpdate.fs @@ -80,7 +80,16 @@ module OfficeInterop = let cmd = Cmd.OfPromise.either OfficeInterop.Core.joinTable - (table, selectedColumns, Some importType.ImportType, templateName) + (table, selectedColumns, Some importType.ImportType, templateName, None) + (curry GenericInteropLogs Cmd.none >> DevMsg) + (curry GenericError Cmd.none >> DevMsg) + state, model, cmd + + | AddTemplates (tables, selectedColumns, importType) -> + let cmd = + Cmd.OfPromise.either + OfficeInterop.Core.joinTables + (tables, selectedColumns, Some importType.ImportType) (curry GenericInteropLogs Cmd.none >> DevMsg) (curry GenericError Cmd.none >> DevMsg) state, model, cmd @@ -89,7 +98,7 @@ module OfficeInterop = let cmd = Cmd.OfPromise.either OfficeInterop.Core.joinTable - (table, [||], options, None) + (table, [||], options, None, None) (curry GenericInteropLogs Cmd.none >> DevMsg) (curry GenericError Cmd.none >> DevMsg) state, model, cmd diff --git a/src/Client/Update/UpdateUtil.fs b/src/Client/Update/UpdateUtil.fs index 3c7c3620..5dd94781 100644 --- a/src/Client/Update/UpdateUtil.fs +++ b/src/Client/Update/UpdateUtil.fs @@ -16,16 +16,15 @@ module JsonImportHelper = open ARCtrl open JsonImport - let updateWithMetadata (uploadedFile: ArcFiles) (state: SelectiveImportModalState) (selectedColumns: SelectedColumns []) = + let updateWithMetadata (uploadedFile: ArcFiles) (state: SelectiveImportModalState) (selectedColumns: SelectedColumns) = if not state.ImportMetadata then failwith "Metadata must be imported" /// This updates the existing tables based on import config (joinOptions) let createUpdatedTables (arcTables: ResizeArray) = [ for importTable in state.ImportTables do - - let selectedColumn = selectedColumns.[importTable.Index] + let selectedColumn = selectedColumns.SelectedColumns.[importTable.Index] let selectedColumnIndices = - selectedColumn.Columns + selectedColumn |> Array.mapi (fun i item -> if item = false then Some i else None) |> Array.choose (fun x -> x) |> List.ofArray diff --git a/src/Shared/ARCtrl.Helper.fs b/src/Shared/ARCtrl.Helper.fs index c04bddf4..bd8f30a5 100644 --- a/src/Shared/ARCtrl.Helper.fs +++ b/src/Shared/ARCtrl.Helper.fs @@ -86,7 +86,6 @@ module Table = /// This function is meant to prepare a table for joining with another table. /// /// It removes columns that are already present in the active table. - /// It removes all values from the new table. /// It also fills new Input/Output columns with the input/output values of the active table. /// /// The output of this function can be used with the SpreadsheetInterface.JoinTable Message. From 1486fc600177e18b750d75cdf2d02b850a675856 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Thu, 12 Dec 2024 13:13:28 +0100 Subject: [PATCH 05/23] Implement selectImportType --- src/Client/Modals/SelectiveImportModal.fs | 8 +++--- .../SelectiveTemplateFromDB.fs | 25 ++++++++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index 63bcca8c..7ec66936 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -118,7 +118,7 @@ type SelectiveImportModal = ) [] - static member private TableImport(tableIndex: int, table0: ArcTable, state: SelectiveImportModalState, addTableImport: int -> bool -> unit, rmvTableImport: int -> unit, selectedColumns, setSelectedColumns) = + static member TableImport(tableIndex: int, table0: ArcTable, state: SelectiveImportModalState, addTableImport: int -> bool -> unit, rmvTableImport: int -> unit, selectedColumns, setSelectedColumns) = let name = table0.Name let radioGroup = "radioGroup_" + name let import = state.ImportTables |> List.tryFind (fun it -> it.Index = tableIndex) @@ -128,17 +128,17 @@ type SelectiveImportModal = Html.div [ ModalElements.RadioPlugin (radioGroup, "Import", isActive && import.Value.FullImport, - (fun (b:bool) -> addTableImport tableIndex true), + (fun (b: bool) -> addTableImport tableIndex true), isDisabled ) ModalElements.RadioPlugin (radioGroup, "Append to active table", isActive && not import.Value.FullImport, - (fun (b:bool) -> addTableImport tableIndex false), + (fun (b: bool) -> addTableImport tableIndex false), isDisabled ) ModalElements.RadioPlugin (radioGroup, "No Import", not isActive, - (fun (b:bool) -> rmvTableImport tableIndex), + (fun (b: bool) -> rmvTableImport tableIndex), isDisabled ) ] diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index e96207dd..736df52a 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -113,8 +113,14 @@ type SelectiveTemplateFromDBModal = |> Array.ofSeq |> Array.map (fun t -> Array.init t.Table.Columns.Length (fun _ -> true)) React.useState(SelectedColumns.init columns) + let useTemplateNameState, setUseTemplateNameState = React.useState(AdaptTableName.init) let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) - let useTemplateName, setUseTemplateName = React.useState(AdaptTableName.init) + let addTableImport = fun (i: int) (fullImport: bool) -> + let newImportTable: ImportTable = {Index = i; FullImport = fullImport} + let newImportTables = newImportTable::importTypeState.ImportTables |> List.distinct + {importTypeState with ImportTables = newImportTables} |> setImportTypeState + let rmvTableImport = fun i -> + {importTypeState with ImportTables = importTypeState.ImportTables |> List.filter (fun it -> it.Index <> i)} |> setImportTypeState SidebarComponents.SidebarLayout.LogicContainer [ Html.div [ SelectiveTemplateFromDBModal.ToProtocolSearchElement model dispatch @@ -138,7 +144,7 @@ type SelectiveTemplateFromDBModal = ModalElements.Box( "Rename Table", "fa-solid fa-cog", - SelectiveTemplateFromDBModal.CheckBoxForTakeOverTemplateName(useTemplateName, setUseTemplateName, template.Name)) + SelectiveTemplateFromDBModal.CheckBoxForTakeOverTemplateName(useTemplateNameState, setUseTemplateNameState, template.Name)) ] Html.div [ ModalElements.Box( @@ -147,18 +153,19 @@ type SelectiveTemplateFromDBModal = SelectiveTemplateFromDBModal.displaySelectedProtocolElements(Some template, 0, selectedColumns, setSelectedColumns, dispatch, false)) ] Html.div [ - SelectiveTemplateFromDBModal.AddFromDBToTableButton "Add template" model selectedColumns importTypeState useTemplateName.TemplateName dispatch + SelectiveTemplateFromDBModal.AddFromDBToTableButton "Add template" model selectedColumns importTypeState useTemplateNameState.TemplateName dispatch ] else if model.ProtocolState.TemplatesSelected.Length > 1 then let templates = model.ProtocolState.TemplatesSelected for templateIndex in 0..templates.Length-1 do let template = templates.[templateIndex] - Html.div [ - ModalElements.Box( - template.Name, - "fa-solid fa-cog", - SelectiveTemplateFromDBModal.displaySelectedProtocolElements(Some template, templateIndex, selectedColumns, setSelectedColumns, dispatch, false)) - ] + SelectiveImportModal.TableImport(templateIndex, template.Table, importTypeState, addTableImport, rmvTableImport, selectedColumns, setSelectedColumns) + //Html.div [ + // ModalElements.Box( + // template.Name, + // "fa-solid fa-cog", + // SelectiveTemplateFromDBModal.displaySelectedProtocolElements(Some template, templateIndex, selectedColumns, setSelectedColumns, dispatch, false)) + //] Html.div [ SelectiveTemplateFromDBModal.AddTemplatesFromDBToTableButton "Add templates" model selectedColumns importTypeState dispatch ] From 57251b39e8c961452664205b66400d6e44ae5940 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Thu, 12 Dec 2024 18:41:40 +0100 Subject: [PATCH 06/23] Enable import of multiple templates with add and join --- src/Client/OfficeInterop/OfficeInterop.fs | 196 ++++++++++++++---- .../SelectiveTemplateFromDB.fs | 4 +- src/Client/Update/OfficeInteropUpdate.fs | 2 +- 3 files changed, 155 insertions(+), 47 deletions(-) diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index 24c95c03..6fb22248 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -76,8 +76,6 @@ module ARCtrlUtil = module ARCtrlExtensions = - open System - type ArcFiles with /// @@ -1015,7 +1013,7 @@ let prepareTemplateInMemory (originTable: ArcTable) (tableToAdd: ArcTable) (sele finalTable -let exportArcTableToExcle (excelTable: Table) (arcTable:ArcTable) (templateName: string option) (context: RequestContext) = +let joinArcTablesInExcle (excelTable: Table) (arcTable:ArcTable) (templateName: string option) (context: RequestContext) = promise { let newTableRange = excelTable.getRange() @@ -1100,7 +1098,7 @@ let joinTable (tableToAdd: ArcTable, selectedColumns: bool [], options: TableJoi | Result.Ok arcTable -> arcTable.Join(refinedTableToAdd, ?index=index, ?joinOptions=options, forceReplace=true) - do! exportArcTableToExcle excelTable arcTable templateName context + do! joinArcTablesInExcle excelTable arcTable templateName context return [InteropLogging.Msg.create InteropLogging.Info $"Joined template {refinedTableToAdd.Name} to table {excelTable.name}!"] | Result.Error _ -> @@ -1108,19 +1106,145 @@ let joinTable (tableToAdd: ArcTable, selectedColumns: bool [], options: TableJoi | None -> return [InteropLogging.NoActiveTableMsg] } +/// +/// Sort the tables and selected columns based on the selected import type +/// +/// +/// +/// +let selectTablesToAdd (tablesToAdd: ArcTable []) (selectedColumnsCollection: bool [] []) (importTables: JsonImport.ImportTable list) = + + let importData = + importTables + |> List.map (fun importTable -> tablesToAdd.[importTable.Index], selectedColumnsCollection.[importTable.Index], importTable.FullImport) + + let mutable tablesToAdd, selectedColumnsCollectionToAdd, tablesToJoin, selectedColumnsCollectionToJoin = + list.Empty, list.Empty, list.Empty, list.Empty + + importData + |> List.iter (fun (table, selectedColumns, importType) -> + if importType then + tablesToAdd <- table::tablesToAdd + selectedColumnsCollectionToAdd <- selectedColumns::selectedColumnsCollectionToAdd + else + tablesToJoin <- table::tablesToJoin + selectedColumnsCollectionToJoin <- selectedColumns::selectedColumnsCollectionToJoin + ) + + tablesToAdd |> Array.ofList, selectedColumnsCollectionToAdd |> Array.ofList, tablesToJoin |> Array.ofList, selectedColumnsCollectionToJoin |> Array.ofList + +/// +/// Join selected template tables with active table +/// +/// +/// +/// +/// +/// +/// +let joinTemplatesToTable (originTable:ArcTable) (excelTable: Table) tablesToJoin selectedColumnsCollection options (context: RequestContext) = + promise { + let selectedRange = context.workbook.getSelectedRange() + let tableStartIndex = excelTable.getRange() + let _ = + tableStartIndex.load(propertyNames=U2.Case2 (ResizeArray[|"columnIndex"|])) |> ignore + selectedRange.load(propertyNames=U2.Case2 (ResizeArray[|"columnIndex"|])) + + do! context.sync() + + let targetIndex = + let adaptedStartIndex = selectedRange.columnIndex - tableStartIndex.columnIndex + if adaptedStartIndex > float (originTable.ColumnCount) then originTable.ColumnCount + else int adaptedStartIndex + 1 + + let rec loop (originTable: ArcTable) (tablesToAdd: ArcTable []) (selectedColumns: bool[][]) (options: TableJoinOptions option) i = + let tableToAdd = tablesToAdd.[i] + let refinedTableToAdd = prepareTemplateInMemory originTable tableToAdd selectedColumns.[i] + + let newTable = + if i > 0 then + originTable.Join(refinedTableToAdd, ?joinOptions=options, forceReplace=true) + originTable + else + refinedTableToAdd + + if i = tablesToAdd.Length-1 then + newTable + else + loop newTable tablesToAdd selectedColumns options (i + 1) + + let processedJoinTable = loop originTable tablesToJoin selectedColumnsCollection options 0 + + let finalTable = prepareTemplateInMemory originTable processedJoinTable [||] + + originTable.Join(finalTable, targetIndex, ?joinOptions=options, forceReplace=true) + + do! joinArcTablesInExcle excelTable originTable None context + + let templateNames = tablesToJoin |> Array.map (fun item -> item.Name) + + return [InteropLogging.Msg.create InteropLogging.Info $"Joined templates {templateNames} to table {excelTable.name}!"] + } + +/// +/// Add new tamplate tables and worksheets +/// +/// +/// +/// +/// +let addTemplates (tablesToAdd: ArcTable[]) (selectedColumnsCollection: bool [] []) (options: TableJoinOptions option) (context: RequestContext) = + promise { + let! result = AnnotationTable.tryGetActive context + + for i in 0..tablesToAdd.Length - 1 do + let tableToAdd = tablesToAdd.[i] + let selectedColumnCollection = selectedColumnsCollection.[i] + let newName = ExcelUtil.createNewTableName() + let originTable = ArcTable.init(newName) + let finalTable = + let endTable = prepareTemplateInMemory originTable tableToAdd selectedColumnCollection + originTable.Join(endTable, ?joinOptions = options) + originTable + + let activeWorksheet = + if i = 0 && result.IsNone then context.workbook.worksheets.getActiveWorksheet() + else + let newWorkSheet = context.workbook.worksheets.add(tableToAdd.Name) + newWorkSheet.activate() + newWorkSheet + + let tableValues = finalTable.ToStringSeqs() + let range = activeWorksheet.getRangeByIndexes(0, 0, float (finalTable.RowCount + 1), (tableValues.Item 0).Count) + + range.values <- finalTable.ToStringSeqs() + + let newTable = activeWorksheet.tables.add(U2.Case1 range, true) + newTable.name <- finalTable.Name + newTable.style <- ExcelUtil.TableStyleLight + + do! AnnotationTable.format(newTable, context, true) + + let templateNames = tablesToAdd |> Array.map (fun item -> item.Name) + return [InteropLogging.Msg.create InteropLogging.Info $"Added templates {templateNames}!"] + } + /// /// Add the given arc table to the active annotation table at the desired index /// /// /// /// -let joinTables (tablesToAdd: ArcTable [], selectedColumnsCollection: bool [] [], options: TableJoinOptions option) = +let joinTables (tablesToAdd: ArcTable [], selectedColumnsCollection: bool [] [], options: TableJoinOptions option, importTables: JsonImport.ImportTable list) = Excel.run(fun context -> promise { //When a name is available get the annotation and arctable for easy access of indices and value adaption //Annotation table enables a easy way to adapt the table, updating existing and adding new columns - let! result = AnnotationTable.tryGetActive context + let tablesToAdd, selectedColumnsCollectionToAdd, tablesToJoin, selectedColumnsCollectionToJoin = + selectTablesToAdd tablesToAdd selectedColumnsCollection importTables + + let! result = AnnotationTable.tryGetActive context match result with | Some excelTable -> let! originTableRes = ArcTable.fromExcelTable(excelTable, context) @@ -1130,48 +1254,32 @@ let joinTables (tablesToAdd: ArcTable [], selectedColumnsCollection: bool [] [], return failwith $"Failed to create arc table for table {excelTable.name}" | Result.Ok originTable -> - let selectedRange = context.workbook.getSelectedRange() - let tableStartIndex = excelTable.getRange() - let _ = - tableStartIndex.load(propertyNames=U2.Case2 (ResizeArray[|"columnIndex"|])) |> ignore - selectedRange.load(propertyNames=U2.Case2 (ResizeArray[|"columnIndex"|])) - - // sync with proxy objects after loading values from excel - do! context.sync().``then``( fun _ -> ()) - - let targetIndex = - let adaptedStartIndex = selectedRange.columnIndex - tableStartIndex.columnIndex - if adaptedStartIndex > float (originTable.ColumnCount) then originTable.ColumnCount - else int adaptedStartIndex + 1 - - let rec loop (originTable: ArcTable) (tablesToAdd: ArcTable []) (selectedColumns: bool[][]) (options: TableJoinOptions option) i = - let tableToAdd = tablesToAdd.[i] - let refinedTableToAdd = prepareTemplateInMemory originTable tableToAdd selectedColumns.[i] - - let newTable = - if i > 0 then - originTable.Join(refinedTableToAdd, ?joinOptions=options, forceReplace=true) - originTable - else - refinedTableToAdd - - if i = tablesToAdd.Length-1 then - newTable + let! msgJoin = + if tablesToJoin.Length > 0 then + joinTemplatesToTable originTable excelTable tablesToJoin selectedColumnsCollectionToJoin options context else - loop newTable tablesToAdd selectedColumns options (i + 1) - - let processedAddTable = loop originTable tablesToAdd selectedColumnsCollection options 0 + promise{return []} - let finalTable = prepareTemplateInMemory originTable processedAddTable [||] - - originTable.Join(finalTable, targetIndex, ?joinOptions=options, forceReplace=true) - - do! exportArcTableToExcle excelTable originTable None context + let! msgAdd = + if tablesToJoin.Length > 0 then + addTemplates tablesToAdd selectedColumnsCollectionToAdd options context + else + promise{return []} - let templateNames = tablesToAdd |> Array.map (fun item -> item.Name) + if msgJoin.IsEmpty then + return msgAdd + else + return [msgAdd.Head; msgJoin.Head] + | None -> + let! msgAdd = + if tablesToJoin.Length > 0 then + addTemplates tablesToAdd selectedColumnsCollectionToAdd options context + else + promise{return []} - return [InteropLogging.Msg.create InteropLogging.Info $"Joined templates {templateNames} to table {excelTable.name}!"] - | None -> return [InteropLogging.NoActiveTableMsg] + if msgAdd.IsEmpty then + return [InteropLogging.NoActiveTableMsg] + else return msgAdd } ) diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index 736df52a..ed194605 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -87,8 +87,8 @@ type SelectiveTemplateFromDBModal = if templates.Length = 0 then failwith "No template selected!" if model.ProtocolState.TemplatesSelected.Length > 1 then - let reversedTables = templates |> List.map (fun item -> item.Table) |> Array.ofList - SpreadsheetInterface.AddTemplates(reversedTables, selectedColumns, importType) |> InterfaceMsg |> dispatch + let importTables = templates |> List.map (fun item -> item.Table) |> Array.ofList + SpreadsheetInterface.AddTemplates(importTables, selectedColumns, importType) |> InterfaceMsg |> dispatch Html.div [ prop.className "join flex flex-row justify-center gap-2" prop.children [ diff --git a/src/Client/Update/OfficeInteropUpdate.fs b/src/Client/Update/OfficeInteropUpdate.fs index 458877c5..4b0c62a9 100644 --- a/src/Client/Update/OfficeInteropUpdate.fs +++ b/src/Client/Update/OfficeInteropUpdate.fs @@ -89,7 +89,7 @@ module OfficeInterop = let cmd = Cmd.OfPromise.either OfficeInterop.Core.joinTables - (tables, selectedColumns, Some importType.ImportType) + (tables, selectedColumns, Some importType.ImportType, importType.ImportTables) (curry GenericInteropLogs Cmd.none >> DevMsg) (curry GenericError Cmd.none >> DevMsg) state, model, cmd From 0fee87f9c59fbde54293c5fd23aabf5384808fe1 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Fri, 13 Dec 2024 12:09:38 +0100 Subject: [PATCH 07/23] Enable adding and joining of templates --- src/Client/OfficeInterop/OfficeInterop.fs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index 6fb22248..0b6a45fe 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -1246,7 +1246,8 @@ let joinTables (tablesToAdd: ArcTable [], selectedColumnsCollection: bool [] [], let! result = AnnotationTable.tryGetActive context match result with - | Some excelTable -> + | excelTable when excelTable.IsSome && tablesToJoin.Length > 0 -> + let excelTable = excelTable.Value let! originTableRes = ArcTable.fromExcelTable(excelTable, context) match originTableRes with @@ -1271,15 +1272,11 @@ let joinTables (tablesToAdd: ArcTable [], selectedColumnsCollection: bool [] [], else return [msgAdd.Head; msgJoin.Head] | None -> - let! msgAdd = - if tablesToJoin.Length > 0 then - addTemplates tablesToAdd selectedColumnsCollectionToAdd options context - else - promise{return []} - - if msgAdd.IsEmpty then + if tablesToAdd.Length > 0 then + let! msgAdd = addTemplates tablesToAdd selectedColumnsCollectionToAdd options context + return msgAdd + else return [InteropLogging.NoActiveTableMsg] - else return msgAdd } ) From 8de6cb0c0b64aa0eb264665aca530f4e8fb86945 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Fri, 13 Dec 2024 13:00:26 +0100 Subject: [PATCH 08/23] Enable merging of different cell types from template --- src/Client/OfficeInterop/OfficeInterop.fs | 35 +---------------------- src/Shared/ARCtrl.Helper.fs | 17 +++++++++++ 2 files changed, 18 insertions(+), 34 deletions(-) diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index 0b6a45fe..99f62e62 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -926,39 +926,6 @@ let addCompositeColumn (excelTable: Table) (arcTable: ArcTable) (newColumn: Comp loggingList -let selectiveTablePrepare (activeTable: ArcTable) (toJoinTable: ArcTable) (removeColumns: int list): ArcTable = - // Remove existing columns - let mutable columnsToRemove = removeColumns - // find duplicate columns - let tablecopy = toJoinTable.Copy() - for header in activeTable.Headers do - let containsAtIndex = tablecopy.Headers |> Seq.tryFindIndex (fun h -> h = header) - if containsAtIndex.IsSome then - columnsToRemove <- containsAtIndex.Value::columnsToRemove - - //Remove duplicates because unselected and already existing columns can overlap - let columnsToRemove = columnsToRemove |> List.distinct - - tablecopy.RemoveColumns (Array.ofList columnsToRemove) - tablecopy.IteriColumns(fun i c0 -> - let c1 = {c0 with Cells = tablecopy.Columns.[i].Cells} - let c2 = - if c1.Header.isInput then - match activeTable.TryGetInputColumn() with - | Some ic -> - {c1 with Cells = ic.Cells} - | _ -> c1 - elif c1.Header.isOutput then - match activeTable.TryGetOutputColumn() with - | Some oc -> - {c1 with Cells = oc.Cells} - | _ -> c1 - else - c1 - tablecopy.UpdateColumn(i, c2.Header, c2.Cells) - ) - tablecopy - /// /// Prepare the given table to be joined with the currently active annotation table /// @@ -976,7 +943,7 @@ let prepareTemplateInMemoryForExcel (table: Table) (tableToAdd: ArcTable) (selec |> Array.mapi (fun i item -> if item = false then Some i else None) |> Array.choose (fun x -> x) |> List.ofArray - + let finalTable = Table.selectiveTablePrepare originTable tableToAdd selectedColumnIndices let selectedRange = context.workbook.getSelectedRange() diff --git a/src/Shared/ARCtrl.Helper.fs b/src/Shared/ARCtrl.Helper.fs index bd8f30a5..0d672952 100644 --- a/src/Shared/ARCtrl.Helper.fs +++ b/src/Shared/ARCtrl.Helper.fs @@ -82,6 +82,22 @@ module Table = tablecopy.RemoveColumns (Array.ofList columnsToRemove) tablecopy + /// + /// Convert one array of cell types to another one, based on the first array of cell types + /// + /// + /// + let convertCellTypes (cellsOrigin: CompositeCell []) (cellsToAdd: CompositeCell []) = + let newCellsToAdd = cellsToAdd + for i in 0..cellsOrigin.Length - 1 do + if cellsOrigin.[i].isFreeText then + if cellsToAdd.[i].isData then + newCellsToAdd.[i] <- cellsToAdd.[i].ToDataCell() + else + if cellsToAdd.[i].isFreeText then + newCellsToAdd.[i] <- cellsToAdd.[i].ToFreeTextCell() + newCellsToAdd + /// /// This function is meant to prepare a table for joining with another table. /// @@ -106,6 +122,7 @@ module Table = let columnsToRemove = columnsToRemove |> Set.ofList |> Set.toList tablecopy.RemoveColumns (Array.ofList columnsToRemove) + tablecopy.IteriColumns(fun i c0 -> let c1 = {c0 with Cells = tablecopy.Columns.[i].Cells} let c2 = From d72624105a8983fd181bf0ce975f29eb8cd165e5 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Mon, 16 Dec 2024 11:49:16 +0100 Subject: [PATCH 09/23] Implement import of multiple templates in swate alpha --- src/Client/Modals/SelectiveImportModal.fs | 4 +- src/Client/OfficeInterop/OfficeInterop.fs | 22 ++--- .../Pages/ProtocolTemplates/ProtocolSearch.fs | 11 ++- .../Pages/ProtocolTemplates/ProtocolState.fs | 3 +- .../SelectiveTemplateFromDB.fs | 14 ++- src/Client/States/Spreadsheet.fs | 2 +- src/Client/Update/InterfaceUpdate.fs | 10 +- src/Client/Update/SpreadsheetUpdate.fs | 6 +- src/Client/Update/UpdateUtil.fs | 92 ++++++++++++------- 9 files changed, 95 insertions(+), 69 deletions(-) diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index 7ec66936..8f1f47b3 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -118,8 +118,8 @@ type SelectiveImportModal = ) [] - static member TableImport(tableIndex: int, table0: ArcTable, state: SelectiveImportModalState, addTableImport: int -> bool -> unit, rmvTableImport: int -> unit, selectedColumns, setSelectedColumns) = - let name = table0.Name + static member TableImport(tableIndex: int, table0: ArcTable, state: SelectiveImportModalState, addTableImport: int -> bool -> unit, rmvTableImport: int -> unit, selectedColumns, setSelectedColumns, ?templateName) = + let name = defaultArg templateName table0.Name let radioGroup = "radioGroup_" + name let import = state.ImportTables |> List.tryFind (fun it -> it.Index = tableIndex) let isActive = import.IsSome diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index 99f62e62..a12a49d6 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -1213,15 +1213,12 @@ let joinTables (tablesToAdd: ArcTable [], selectedColumnsCollection: bool [] [], let! result = AnnotationTable.tryGetActive context match result with - | excelTable when excelTable.IsSome && tablesToJoin.Length > 0 -> - let excelTable = excelTable.Value + | Some excelTable -> let! originTableRes = ArcTable.fromExcelTable(excelTable, context) - match originTableRes with | Result.Error _ -> return failwith $"Failed to create arc table for table {excelTable.name}" | Result.Ok originTable -> - let! msgJoin = if tablesToJoin.Length > 0 then joinTemplatesToTable originTable excelTable tablesToJoin selectedColumnsCollectionToJoin options context @@ -1229,21 +1226,18 @@ let joinTables (tablesToAdd: ArcTable [], selectedColumnsCollection: bool [] [], promise{return []} let! msgAdd = - if tablesToJoin.Length > 0 then - addTemplates tablesToAdd selectedColumnsCollectionToAdd options context - else - promise{return []} + addTemplates tablesToAdd selectedColumnsCollectionToAdd options context - if msgJoin.IsEmpty then - return msgAdd + if msgAdd.IsEmpty then + return msgJoin else - return [msgAdd.Head; msgJoin.Head] + return [msgJoin.Head; msgAdd.Head] | None -> - if tablesToAdd.Length > 0 then + if tablesToJoin.Length > 0 then + return [InteropLogging.NoActiveTableMsg] + else let! msgAdd = addTemplates tablesToAdd selectedColumnsCollectionToAdd options context return msgAdd - else - return [InteropLogging.NoActiveTableMsg] } ) diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs index 78dab3e2..ce591411 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs @@ -53,10 +53,13 @@ type SearchContainer = Html.p "Search the database for protocol templates." - SidebarComponents.SidebarLayout.LogicContainer [ - Protocol.Search.InfoField() - Protocol.Search.FileSortElement(model, config, setConfig) - Protocol.Search.Component (filteredTemplates, model, dispatch) + Html.div [ + prop.className "relative flex p-4 shadow-md gap-4 flex-col" + prop.children [ + Protocol.Search.InfoField() + Protocol.Search.FileSortElement(model, config, setConfig) + Protocol.Search.Component (filteredTemplates, model, dispatch) + ] ] ] ] \ No newline at end of file diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs index b17c038c..3b8d964f 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs @@ -55,10 +55,9 @@ module Protocol = nextState, Cmd.none | SelectProtocols prots -> log "SelectProtocols" - let newProts = prots let nextModel = { model with - Model.ProtocolState.TemplatesSelected = newProts + Model.ProtocolState.TemplatesSelected = prots Model.PageState.SidebarPage = Routing.SidebarPage.Protocol } state, Cmd.ofMsg (UpdateModel nextModel) diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index ed194605..3283482c 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -117,7 +117,7 @@ type SelectiveTemplateFromDBModal = let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) let addTableImport = fun (i: int) (fullImport: bool) -> let newImportTable: ImportTable = {Index = i; FullImport = fullImport} - let newImportTables = newImportTable::importTypeState.ImportTables |> List.distinct + let newImportTables = newImportTable::importTypeState.ImportTables |> List.distinctBy (fun x -> x.Index) {importTypeState with ImportTables = newImportTables} |> setImportTypeState let rmvTableImport = fun i -> {importTypeState with ImportTables = importTypeState.ImportTables |> List.filter (fun it -> it.Index <> i)} |> setImportTypeState @@ -157,15 +157,13 @@ type SelectiveTemplateFromDBModal = ] else if model.ProtocolState.TemplatesSelected.Length > 1 then let templates = model.ProtocolState.TemplatesSelected + let names = + templates + |> List.map (fun item -> item.Name) + |> Array.ofSeq for templateIndex in 0..templates.Length-1 do let template = templates.[templateIndex] - SelectiveImportModal.TableImport(templateIndex, template.Table, importTypeState, addTableImport, rmvTableImport, selectedColumns, setSelectedColumns) - //Html.div [ - // ModalElements.Box( - // template.Name, - // "fa-solid fa-cog", - // SelectiveTemplateFromDBModal.displaySelectedProtocolElements(Some template, templateIndex, selectedColumns, setSelectedColumns, dispatch, false)) - //] + SelectiveImportModal.TableImport(templateIndex, template.Table, importTypeState, addTableImport, rmvTableImport, selectedColumns, setSelectedColumns, template.Name) Html.div [ SelectiveTemplateFromDBModal.AddTemplatesFromDBToTableButton "Add templates" model selectedColumns importTypeState dispatch ] diff --git a/src/Client/States/Spreadsheet.fs b/src/Client/States/Spreadsheet.fs index 907013f9..dac4355c 100644 --- a/src/Client/States/Spreadsheet.fs +++ b/src/Client/States/Spreadsheet.fs @@ -203,7 +203,7 @@ type Msg = | AddAnnotationBlocks of CompositeColumn [] | AddDataAnnotation of {| fragmentSelectors: string []; fileName: string; fileType: string; targetColumn: DataAnnotator.TargetColumn |} | AddTemplate of ArcTable * bool[] * SelectiveImportModalState * string option -| AddTemplates of ArcTable[] * bool[][] * SelectiveImportModalState * string option +| AddTemplates of ArcTable[] * bool[][] * SelectiveImportModalState | JoinTable of ArcTable * index: int option * options: TableJoinOptions option * string option | UpdateArcFile of ArcFiles | InitFromArcFile of ArcFiles diff --git a/src/Client/Update/InterfaceUpdate.fs b/src/Client/Update/InterfaceUpdate.fs index 917c0909..3a725974 100644 --- a/src/Client/Update/InterfaceUpdate.fs +++ b/src/Client/Update/InterfaceUpdate.fs @@ -178,9 +178,9 @@ module Interface = | Some Swatehost.Excel -> let cmd = OfficeInterop.AddTemplates (tables, selectedColumns, importType) |> OfficeInteropMsg |> Cmd.ofMsg model, cmd - //| Some Swatehost.Browser | Some Swatehost.ARCitect -> - // let cmd = Spreadsheet.AddTemplates (tables, selectedColumns, importType, templateName) |> SpreadsheetMsg |> Cmd.ofMsg - // model, cmd + | Some Swatehost.Browser | Some Swatehost.ARCitect -> + let cmd = Spreadsheet.AddTemplates (tables, selectedColumns, importType) |> SpreadsheetMsg |> Cmd.ofMsg + model, cmd | _ -> failwith "not implemented" | JoinTable (table, index, options) -> match host with @@ -229,7 +229,7 @@ module Interface = match arcfileOpt, activeTable with | Some arcfile, Ok activeTable -> arcfile.Tables() |> Seq.tryFindIndex (fun table -> table = activeTable) | _ -> None - return UpdateUtil.JsonImportHelper.updateTables data.importedFile data.importState (*selectedColumns*) activeTableIndex arcfileOpt + return UpdateUtil.JsonImportHelper.updateArcFileTables data.importedFile data.importState activeTableIndex arcfileOpt selectedColumns } let updateArcFile (arcFile: ArcFiles) = SpreadsheetInterface.UpdateArcFile arcFile |> InterfaceMsg let cmd = @@ -243,7 +243,7 @@ module Interface = let cmd = match data.importState.ImportMetadata with | true -> UpdateUtil.JsonImportHelper.updateWithMetadata data.importedFile data.importState selectedColumns - | false -> UpdateUtil.JsonImportHelper.updateTables data.importedFile data.importState model.SpreadsheetModel.ActiveView.TryTableIndex model.SpreadsheetModel.ArcFile + | false -> UpdateUtil.JsonImportHelper.updateArcFileTables data.importedFile data.importState model.SpreadsheetModel.ActiveView.TryTableIndex model.SpreadsheetModel.ArcFile selectedColumns |> SpreadsheetInterface.UpdateArcFile |> InterfaceMsg |> Cmd.ofMsg model, cmd | _ -> failwith "not implemented" diff --git a/src/Client/Update/SpreadsheetUpdate.fs b/src/Client/Update/SpreadsheetUpdate.fs index e6defdf9..b3269966 100644 --- a/src/Client/Update/SpreadsheetUpdate.fs +++ b/src/Client/Update/SpreadsheetUpdate.fs @@ -112,12 +112,16 @@ module Spreadsheet = |> Array.mapi (fun i item -> if item = false then Some i else None) |> Array.choose (fun x -> x) |> List.ofArray - let cmd = Table.selectiveTablePrepare state.ActiveTable table selectedColumnsIndices |> msg |> Cmd.ofMsg state, model, cmd + | AddTemplates (tables, selectedColumns, importType) -> + let arcFile = model.SpreadsheetModel.ArcFile + let updatedArcFile = UpdateUtil.JsonImportHelper.updateTables (tables |> ResizeArray) importType model.SpreadsheetModel.ActiveView.TryTableIndex arcFile selectedColumns + let nextState = {state with ArcFile = Some updatedArcFile} + nextState, model, Cmd.none | JoinTable (table, index, options, templateName) -> let nextState = Controller.BuildingBlocks.joinTable table index options state templateName nextState, model, Cmd.none diff --git a/src/Client/Update/UpdateUtil.fs b/src/Client/Update/UpdateUtil.fs index 5dd94781..d9fac52d 100644 --- a/src/Client/Update/UpdateUtil.fs +++ b/src/Client/Update/UpdateUtil.fs @@ -16,13 +16,19 @@ module JsonImportHelper = open ARCtrl open JsonImport - let updateWithMetadata (uploadedFile: ArcFiles) (state: SelectiveImportModalState) (selectedColumns: SelectedColumns) = - if not state.ImportMetadata then failwith "Metadata must be imported" - /// This updates the existing tables based on import config (joinOptions) - let createUpdatedTables (arcTables: ResizeArray) = - [ - for importTable in state.ImportTables do - let selectedColumn = selectedColumns.SelectedColumns.[importTable.Index] + /// + /// + /// + /// + /// + /// + /// + let createUpdatedTables (arcTables: ResizeArray) (state: SelectiveImportModalState) (selectedColumns: bool [] []) fullImport = + [ + for importTable in state.ImportTables do + let fullImport = defaultArg fullImport importTable.FullImport + if importTable.FullImport = fullImport then + let selectedColumn = selectedColumns.[importTable.Index] let selectedColumnIndices = selectedColumn |> Array.mapi (fun i item -> if item = false then Some i else None) @@ -33,25 +39,32 @@ module JsonImportHelper = let appliedTable = ArcTable.init(sourceTable.Name) let finalTable = Table.selectiveTablePrepare appliedTable sourceTable selectedColumnIndices - let values = - sourceTable.Columns - |> Array.map (fun item -> item.Cells |> Array.map (fun itemi -> itemi.ToString())) appliedTable.Join(finalTable, joinOptions=state.ImportType) appliedTable - ] - |> ResizeArray + ] + |> ResizeArray + + /// + /// + /// + /// + /// + /// + let updateWithMetadata (uploadedFile: ArcFiles) (state: SelectiveImportModalState) (selectedColumns: SelectedColumns) = + if not state.ImportMetadata then failwith "Metadata must be imported" + /// This updates the existing tables based on import config (joinOptions) let arcFile = match uploadedFile with | Assay a as arcFile -> - let tables = createUpdatedTables a.Tables + let tables = createUpdatedTables a.Tables state selectedColumns.SelectedColumns None a.Tables <- tables arcFile | Study (s,_) as arcFile -> - let tables = createUpdatedTables s.Tables + let tables = createUpdatedTables s.Tables state selectedColumns.SelectedColumns None s.Tables <- tables arcFile | Template t as arcFile -> - let table = createUpdatedTables (ResizeArray[t.Table]) + let table = createUpdatedTables (ResizeArray[t.Table]) state selectedColumns.SelectedColumns None t.Table <- table.[0] arcFile | Investigation _ as arcFile -> @@ -59,44 +72,38 @@ module JsonImportHelper = arcFile /// - /// + /// /// - /// + /// /// - /// Required to append imported tables to the active table. - /// - let updateTables (import: ArcFiles) (importState: SelectiveImportModalState) (activeTableIndex: int option) (existingOpt: ArcFiles option) = + /// + /// + /// + let updateTables (importTables: ResizeArray) (importState: SelectiveImportModalState) (activeTableIndex: int option) (existingOpt: ArcFiles option) (selectedColumns: bool [] []) = match existingOpt with | Some existing -> - let importTables = - match import with - | Assay a -> a.Tables - | Study (s,_) -> s.Tables - | Template t -> ResizeArray([t.Table]) - | Investigation _ -> ResizeArray() let existingTables = match existing with | Assay a -> a.Tables | Study (s,_) -> s.Tables | Template t -> ResizeArray([t.Table]) - | Investigation _ -> ResizeArray() + | Investigation _ -> ResizeArray() let appendTables = // only append if the active table exists (this is to handle join call on investigations) match activeTableIndex with | Some i when i >= 0 && i < existingTables.Count -> let activeTable = existingTables.[i] - let tables = importState.ImportTables |> Seq.filter (fun x -> not x.FullImport) |> Seq.map (fun x -> importTables.[x.Index]) + let selectedColumnTables = createUpdatedTables importTables importState selectedColumns (Some false) |> Array.ofSeq |> Array.rev /// Everything will be appended against this table, which in the end will be appended to the main table let tempTable = activeTable.Copy() - for table in tables do + for table in selectedColumnTables do let preparedTemplate = Table.distinctByHeader tempTable table tempTable.Join(preparedTemplate, joinOptions=importState.ImportType) existingTables.[i] <- tempTable | _ -> () let addTables = - importState.ImportTables - |> Seq.filter (fun x -> x.FullImport) - |> Seq.map (fun x -> importTables.[x.Index]) + let selectedColumnTables = createUpdatedTables importTables importState selectedColumns (Some true) |> Array.ofSeq |> Array.rev + selectedColumnTables |> Seq.map (fun table -> // update tables based on joinOptions let nTable = ArcTable.init(table.Name) nTable.Join(table, joinOptions=importState.ImportType) @@ -107,11 +114,32 @@ module JsonImportHelper = existing | None -> // failwith "Error! Can only append information if metadata sheet exists!" + + /// + /// + /// + /// + /// + /// Required to append imported tables to the active table. + /// + let updateArcFileTables (import: ArcFiles) (importState: SelectiveImportModalState) (activeTableIndex: int option) (existingOpt: ArcFiles option) (selectedColumns: SelectedColumns) = + let importTables = + match import with + | Assay a -> a.Tables + | Study (s,_) -> s.Tables + | Template t -> ResizeArray([t.Table]) + | Investigation _ -> ResizeArray() + updateTables importTables importState activeTableIndex existingOpt selectedColumns.SelectedColumns module JsonExportHelper = open ARCtrl open ARCtrl.Json + /// + /// + /// + /// + /// let parseToJsonString (arcfile: ArcFiles, jef: JsonExportFormat) = let name, jsonString = let n = System.DateTime.Now.ToUniversalTime().ToString("yyyyMMdd_hhmmss") From d43aae3794bc03a8510c61838a729cf96006716e Mon Sep 17 00:00:00 2001 From: patrick blume Date: Tue, 17 Dec 2024 11:21:38 +0100 Subject: [PATCH 10/23] implement review changes --- src/Client/Helper.fs | 6 +- src/Client/MainComponents/Widgets.fs | 84 +++++++++++-------- src/Client/Modals/ModalElements.fs | 1 - src/Client/Modals/SelectiveImportModal.fs | 7 +- src/Client/OfficeInterop/OfficeInterop.fs | 5 +- .../Pages/ProtocolTemplates/ProtocolState.fs | 1 - .../Pages/ProtocolTemplates/ProtocolView.fs | 2 +- .../SelectiveTemplateFromDB.fs | 18 ++-- .../Spreadsheet/Controller/BuildingBlocks.fs | 4 +- 9 files changed, 67 insertions(+), 61 deletions(-) diff --git a/src/Client/Helper.fs b/src/Client/Helper.fs index ddf1fceb..11fedc13 100644 --- a/src/Client/Helper.fs +++ b/src/Client/Helper.fs @@ -123,11 +123,7 @@ let throttleAndDebounce(fn: 'a -> unit, timespan: int) = timespan id <- Some timeoutId -let charsToRemove = " -/\()[]{}" - -//Regex is incapable to remove the whitespaces -let removeChars (stripChars:string) (text:string) = - text.Split(stripChars.ToCharArray(), StringSplitOptions.RemoveEmptyEntries) |> String.Concat +let charsToRemove = "\s-" type Clipboard = abstract member writeText: string -> JS.Promise diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index 4c178c93..d62dba9c 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -213,7 +213,7 @@ type Widget = [] static member Templates (model: Model, dispatch, rmv: MouseEvent -> unit) = - let templates, setTemplates = React.useState(model.ProtocolState.Templates) + let templates = model.ProtocolState.Templates let config, setConfig = React.useState(TemplateFilterConfig.init) let selectedColumns, setSelectedColumns = let columns = @@ -221,11 +221,15 @@ type Widget = |> Array.ofSeq |> Array.map (fun t -> Array.init t.Table.Columns.Length (fun _ -> true)) React.useState(SelectedColumns.init columns) + let useTemplateNameState, setUseTemplateNameState = React.useState(AdaptTableName.init) let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) - let useTemplateName, setUseTemplateName = React.useState(AdaptTableName.init) let filteredTemplates = Protocol.Search.filterTemplates (templates, config) + let addTableImport = fun (i: int) (fullImport: bool) -> + let newImportTable: ImportTable = {Index = i; FullImport = fullImport} + let newImportTables = newImportTable::importTypeState.ImportTables |> List.distinctBy (fun x -> x.Index) + {importTypeState with ImportTables = newImportTables} |> setImportTypeState + let rmvTableImport = fun _ -> () React.useEffectOnce(fun _ -> Messages.Protocol.GetAllProtocolsRequest |> Messages.ProtocolMsg |> dispatch) - React.useEffect((fun _ -> setTemplates model.ProtocolState.Templates), [|box model.ProtocolState.Templates|]) let selectContent() = [ Protocol.Search.FileSortElement(model, config, setConfig, "@md/templateWidget:grid-cols-3") @@ -236,46 +240,56 @@ type Widget = Html.div [ prop.style [style.maxHeight (length.px 350); style.overflow.auto] prop.children [ - SidebarComponents.SidebarLayout.LogicContainer [ + Html.div [ + SelectiveTemplateFromDB.ToProtocolSearchElement model dispatch + ] + if model.ProtocolState.TemplatesSelected.Length > 0 then + SelectiveImportModal.RadioPluginsBox( + "Import Type", + "fa-solid fa-cog", + importTypeState.ImportType, + "importType ", + [| + ARCtrl.TableJoinOptions.Headers, " Column Headers"; + ARCtrl.TableJoinOptions.WithUnit, " ..With Units"; + ARCtrl.TableJoinOptions.WithValues, " ..With Values"; + |], + fun importType -> {importTypeState with ImportType = importType} |> setImportTypeState + ) + if model.ProtocolState.TemplatesSelected.Length = 1 then + let template = model.ProtocolState.TemplatesSelected.Head Html.div [ - SelectiveTemplateFromDBModal.ToProtocolSearchElement model dispatch + ModalElements.Box( + "Rename Table", + "fa-solid fa-cog", + SelectiveTemplateFromDB.CheckBoxForTakeOverTemplateName(useTemplateNameState, setUseTemplateNameState, template.Name)) ] - if model.ProtocolState.TemplatesSelected.Length > 0 then - Html.div [ - SelectiveImportModal.RadioPluginsBox( - "Import Type", - "fa-solid fa-cog", - importTypeState.ImportType, - "importType ", - [| - ARCtrl.TableJoinOptions.Headers, " Column Headers"; - ARCtrl.TableJoinOptions.WithUnit, " ..With Units"; - ARCtrl.TableJoinOptions.WithValues, " ..With Values"; - |], - fun importType -> {importTypeState with ImportType = importType} |> setImportTypeState - ) - ] - Html.div [ - ModalElements.Box( - "Rename Table", - "fa-solid fa-cog", - SelectiveTemplateFromDBModal.CheckBoxForTakeOverTemplateName(useTemplateName, setUseTemplateName, model.ProtocolState.TemplatesSelected.Head.Name)) - ] - Html.div [ - ModalElements.Box( - model.ProtocolState.TemplatesSelected.Head.Name, - "", - SelectiveTemplateFromDBModal.displaySelectedProtocolElements(Some model.ProtocolState.TemplatesSelected.Head, 0, selectedColumns, setSelectedColumns, dispatch, false)) - ] Html.div [ - SelectiveTemplateFromDBModal.AddFromDBToTableButton "Add template" model selectedColumns importTypeState useTemplateName.TemplateName dispatch + ModalElements.Box( + template.Name, + "fa-solid fa-cog", + SelectiveTemplateFromDB.displaySelectedProtocolElements(Some template, 0, selectedColumns, setSelectedColumns, dispatch, false)) + ] + Html.div [ + SelectiveTemplateFromDB.AddFromDBToTableButton "Add template" model selectedColumns importTypeState useTemplateNameState.TemplateName dispatch + ] + else if model.ProtocolState.TemplatesSelected.Length > 1 then + let templates = model.ProtocolState.TemplatesSelected + for templateIndex in 0..templates.Length-1 do + let template = templates.[templateIndex] + SelectiveImportModal.TableImport(templateIndex, template.Table, importTypeState, addTableImport, rmvTableImport, selectedColumns, setSelectedColumns, template.Name, templateIndex.ToString()) + Html.div [ + SelectiveTemplateFromDB.AddTemplatesFromDBToTableButton "Add templates" model selectedColumns importTypeState dispatch ] - ] ] ] ] + let content = - let switchContent = if model.ProtocolState.TemplatesSelected.Length = 0 then selectContent() else insertContent() + let switchContent = + if model.ProtocolState.TemplatesSelected.Length > 0 && model.PageState.SidebarPage = Routing.SidebarPage.Protocol then + insertContent() + else selectContent() Html.div [ prop.className "flex flex-col gap-4 @container/templateWidget" prop.children switchContent diff --git a/src/Client/Modals/ModalElements.fs b/src/Client/Modals/ModalElements.fs index 4c12382c..75b98ea2 100644 --- a/src/Client/Modals/ModalElements.fs +++ b/src/Client/Modals/ModalElements.fs @@ -5,7 +5,6 @@ open Feliz.DaisyUI open Model open Messages open Shared -open Types.TableImport open ARCtrl open JsonImport diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index 8f1f47b3..6dab3131 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -65,7 +65,7 @@ type SelectiveImportModal = SelectiveImportModal.CheckBoxForTableColumnSelection(columns, tableIndex, columnIndex, selectionInformation.Value, setSelectedColumns.Value) Html.text (columns.[columnIndex].Header.ToString()) Html.div [ - prop.onClick (fun e -> + prop.onClick (fun _ -> if columns.Length > 0 && selectionInformation.IsSome then let selectedData = selectionInformation.Value.SelectedColumns selectedData.[tableIndex].[columnIndex] <- not selectedData.[tableIndex].[columnIndex] @@ -118,9 +118,10 @@ type SelectiveImportModal = ) [] - static member TableImport(tableIndex: int, table0: ArcTable, state: SelectiveImportModalState, addTableImport: int -> bool -> unit, rmvTableImport: int -> unit, selectedColumns, setSelectedColumns, ?templateName) = + static member TableImport(tableIndex: int, table0: ArcTable, state: SelectiveImportModalState, addTableImport: int -> bool -> unit, rmvTableImport: int -> unit, selectedColumns, setSelectedColumns, ?templateName, ?radioGroup) = let name = defaultArg templateName table0.Name - let radioGroup = "radioGroup_" + name + let radioGroup = defaultArg radioGroup "" + let radioGroup = "radioGroup_" + radioGroup let import = state.ImportTables |> List.tryFind (fun it -> it.Index = tableIndex) let isActive = import.IsSome let isDisabled = state.ImportMetadata diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index a12a49d6..3f48c01b 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -997,9 +997,8 @@ let joinArcTablesInExcle (excelTable: Table) (arcTable:ArcTable) (templateName: let tableSeqs = arcTable.ToStringSeqs() do! context.sync().``then``(fun _ -> - if templateName.IsSome then - //Should be updated to remove all kinds of extra symbols - let templateName = Helper.removeChars Helper.charsToRemove templateName.Value + if templateName.IsSome then + let templateName = System.Text.RegularExpressions.Regex.Replace(templateName.Value, Helper.charsToRemove, "") newTable.name <- $"annotationTable{templateName}" else newTable.name <- excelTable.name diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs index 3b8d964f..99384be0 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs @@ -71,7 +71,6 @@ module Protocol = let nextModel = { model with Model.ProtocolState.TemplatesSelected = templates - //Model.PageState.SidebarPage = Routing.SidebarPage.Protocol } state, Cmd.ofMsg (UpdateModel nextModel) | ProtocolIncreaseTimesUsed templateId -> diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolView.fs b/src/Client/Pages/ProtocolTemplates/ProtocolView.fs index 8ad27ee5..8a2ee134 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolView.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolView.fs @@ -38,7 +38,7 @@ type Templates = // Box 1 SidebarComponents.SidebarLayout.Description "Add template from database." - Modals.SelectiveTemplateFromDBModal.Main(model, dispatch) + Modals.SelectiveTemplateFromDB.Main(model, dispatch) // Box 2 SidebarComponents.SidebarLayout.Description (Html.p [ diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index 3283482c..d63c14a8 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -19,7 +19,7 @@ type AdaptTableName = { TemplateName = None } -type SelectiveTemplateFromDBModal = +type SelectiveTemplateFromDB = static member CheckBoxForTakeOverTemplateName(adaptTableName: AdaptTableName, setAdaptTableName: AdaptTableName -> unit, templateName) = Html.label [ @@ -67,7 +67,7 @@ type SelectiveTemplateFromDBModal = model.ProtocolState.TemplatesSelected.Head SpreadsheetInterface.AddTemplate(template.Table, selectedColumns, importType, useTemplateName) |> InterfaceMsg |> dispatch Html.div [ - prop.className "join flex flex-row justify-center gap-2" + prop.className "flex flex-row justify-center gap-2" prop.children [ let isDisabled = model.ProtocolState.TemplatesSelected.Length = 0 ModalElements.Button(name, addTemplate, (model, selectionInformation.SelectedColumns.[0]), isDisabled) @@ -123,7 +123,7 @@ type SelectiveTemplateFromDBModal = {importTypeState with ImportTables = importTypeState.ImportTables |> List.filter (fun it -> it.Index <> i)} |> setImportTypeState SidebarComponents.SidebarLayout.LogicContainer [ Html.div [ - SelectiveTemplateFromDBModal.ToProtocolSearchElement model dispatch + SelectiveTemplateFromDB.ToProtocolSearchElement model dispatch ] if model.ProtocolState.TemplatesSelected.Length > 0 then SelectiveImportModal.RadioPluginsBox( @@ -144,27 +144,23 @@ type SelectiveTemplateFromDBModal = ModalElements.Box( "Rename Table", "fa-solid fa-cog", - SelectiveTemplateFromDBModal.CheckBoxForTakeOverTemplateName(useTemplateNameState, setUseTemplateNameState, template.Name)) + SelectiveTemplateFromDB.CheckBoxForTakeOverTemplateName(useTemplateNameState, setUseTemplateNameState, template.Name)) ] Html.div [ ModalElements.Box( template.Name, "fa-solid fa-cog", - SelectiveTemplateFromDBModal.displaySelectedProtocolElements(Some template, 0, selectedColumns, setSelectedColumns, dispatch, false)) + SelectiveTemplateFromDB.displaySelectedProtocolElements(Some template, 0, selectedColumns, setSelectedColumns, dispatch, false)) ] Html.div [ - SelectiveTemplateFromDBModal.AddFromDBToTableButton "Add template" model selectedColumns importTypeState useTemplateNameState.TemplateName dispatch + SelectiveTemplateFromDB.AddFromDBToTableButton "Add template" model selectedColumns importTypeState useTemplateNameState.TemplateName dispatch ] else if model.ProtocolState.TemplatesSelected.Length > 1 then let templates = model.ProtocolState.TemplatesSelected - let names = - templates - |> List.map (fun item -> item.Name) - |> Array.ofSeq for templateIndex in 0..templates.Length-1 do let template = templates.[templateIndex] SelectiveImportModal.TableImport(templateIndex, template.Table, importTypeState, addTableImport, rmvTableImport, selectedColumns, setSelectedColumns, template.Name) Html.div [ - SelectiveTemplateFromDBModal.AddTemplatesFromDBToTableButton "Add templates" model selectedColumns importTypeState dispatch + SelectiveTemplateFromDB.AddTemplatesFromDBToTableButton "Add templates" model selectedColumns importTypeState dispatch ] ] diff --git a/src/Client/Spreadsheet/Controller/BuildingBlocks.fs b/src/Client/Spreadsheet/Controller/BuildingBlocks.fs index cd7235b8..977dc86a 100644 --- a/src/Client/Spreadsheet/Controller/BuildingBlocks.fs +++ b/src/Client/Spreadsheet/Controller/BuildingBlocks.fs @@ -9,6 +9,8 @@ open ExcelJS.Fable open Excel open GlobalBindings +open Regex + module SidebarControllerAux = /// @@ -100,7 +102,7 @@ let joinTable(tableToAdd: ArcTable) (index: int option) (options: TableJoinOptio if templateName.IsSome then //Should be updated to remove all kinds of extra symbols - let templateName = Helper.removeChars Helper.charsToRemove templateName.Value + let templateName = System.Text.RegularExpressions.Regex.Replace(templateName.Value, Helper.charsToRemove, "") let newTable = ArcTable.create(templateName, state.ActiveTable.Headers, state.ActiveTable.Values) state.ArcFile.Value.Tables().SetTable(state.ActiveTable.Name, newTable) From a8e7eae90743799fdf3507b4c8fef3d6a2c84df0 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Tue, 17 Dec 2024 12:34:38 +0100 Subject: [PATCH 11/23] Simplify code --- src/Client/Helper.fs | 2 - src/Client/MainComponents/Widgets.fs | 63 ++++------------ src/Client/Modals/SelectiveImportModal.fs | 45 ++++++------ src/Client/OfficeInterop/OfficeInterop.fs | 3 +- .../SelectiveTemplateFromDB.fs | 72 +++++++++---------- src/Client/States/SpreadsheetInterface.fs | 3 +- src/Client/Types.fs | 17 ++--- src/Client/Update/UpdateUtil.fs | 13 ++-- 8 files changed, 83 insertions(+), 135 deletions(-) diff --git a/src/Client/Helper.fs b/src/Client/Helper.fs index 11fedc13..7fb14397 100644 --- a/src/Client/Helper.fs +++ b/src/Client/Helper.fs @@ -123,8 +123,6 @@ let throttleAndDebounce(fn: 'a -> unit, timespan: int) = timespan id <- Some timeoutId -let charsToRemove = "\s-" - type Clipboard = abstract member writeText: string -> JS.Promise abstract member readText: unit -> JS.Promise diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index d62dba9c..84e39419 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -5,7 +5,6 @@ open Feliz.DaisyUI open Browser.Types open LocalStorage.Widgets open Modals -open Types.TableImport open Types.JsonImport module private InitExtensions = @@ -210,78 +209,42 @@ type Widget = let prefix = WidgetLiterals.BuildingBlock Widget.Base(content, prefix, rmv, help) - [] static member Templates (model: Model, dispatch, rmv: MouseEvent -> unit) = let templates = model.ProtocolState.Templates let config, setConfig = React.useState(TemplateFilterConfig.init) - let selectedColumns, setSelectedColumns = + let importTypeState, setImportTypeState = let columns = model.ProtocolState.TemplatesSelected |> Array.ofSeq |> Array.map (fun t -> Array.init t.Table.Columns.Length (fun _ -> true)) - React.useState(SelectedColumns.init columns) - let useTemplateNameState, setUseTemplateNameState = React.useState(AdaptTableName.init) - let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) + React.useState(SelectiveImportModalState.init columns) let filteredTemplates = Protocol.Search.filterTemplates (templates, config) let addTableImport = fun (i: int) (fullImport: bool) -> let newImportTable: ImportTable = {Index = i; FullImport = fullImport} let newImportTables = newImportTable::importTypeState.ImportTables |> List.distinctBy (fun x -> x.Index) {importTypeState with ImportTables = newImportTables} |> setImportTypeState - let rmvTableImport = fun _ -> () + let rmvTableImport = fun i -> + {importTypeState with ImportTables = importTypeState.ImportTables |> List.filter (fun it -> it.Index <> i)} |> setImportTypeState React.useEffectOnce(fun _ -> Messages.Protocol.GetAllProtocolsRequest |> Messages.ProtocolMsg |> dispatch) let selectContent() = [ Protocol.Search.FileSortElement(model, config, setConfig, "@md/templateWidget:grid-cols-3") Protocol.Search.Component (filteredTemplates, model, dispatch, length.px 350) ] + let templateSelectionElements = + SelectiveTemplateFromDB.DisplayTemplateSelection( + model, + importTypeState, + setImportTypeState, + addTableImport, + rmvTableImport, + dispatch) let insertContent() = [ Html.div [ prop.style [style.maxHeight (length.px 350); style.overflow.auto] - prop.children [ - Html.div [ - SelectiveTemplateFromDB.ToProtocolSearchElement model dispatch - ] - if model.ProtocolState.TemplatesSelected.Length > 0 then - SelectiveImportModal.RadioPluginsBox( - "Import Type", - "fa-solid fa-cog", - importTypeState.ImportType, - "importType ", - [| - ARCtrl.TableJoinOptions.Headers, " Column Headers"; - ARCtrl.TableJoinOptions.WithUnit, " ..With Units"; - ARCtrl.TableJoinOptions.WithValues, " ..With Values"; - |], - fun importType -> {importTypeState with ImportType = importType} |> setImportTypeState - ) - if model.ProtocolState.TemplatesSelected.Length = 1 then - let template = model.ProtocolState.TemplatesSelected.Head - Html.div [ - ModalElements.Box( - "Rename Table", - "fa-solid fa-cog", - SelectiveTemplateFromDB.CheckBoxForTakeOverTemplateName(useTemplateNameState, setUseTemplateNameState, template.Name)) - ] - Html.div [ - ModalElements.Box( - template.Name, - "fa-solid fa-cog", - SelectiveTemplateFromDB.displaySelectedProtocolElements(Some template, 0, selectedColumns, setSelectedColumns, dispatch, false)) - ] - Html.div [ - SelectiveTemplateFromDB.AddFromDBToTableButton "Add template" model selectedColumns importTypeState useTemplateNameState.TemplateName dispatch - ] - else if model.ProtocolState.TemplatesSelected.Length > 1 then - let templates = model.ProtocolState.TemplatesSelected - for templateIndex in 0..templates.Length-1 do - let template = templates.[templateIndex] - SelectiveImportModal.TableImport(templateIndex, template.Table, importTypeState, addTableImport, rmvTableImport, selectedColumns, setSelectedColumns, template.Name, templateIndex.ToString()) - Html.div [ - SelectiveTemplateFromDB.AddTemplatesFromDBToTableButton "Add templates" model selectedColumns importTypeState dispatch - ] - ] + prop.children templateSelectionElements ] ] diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index 6dab3131..d50b72d9 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -5,7 +5,6 @@ open Feliz.DaisyUI open Model open Messages open Shared -open Types.TableImport open ARCtrl open JsonImport @@ -24,7 +23,7 @@ type SelectiveImportModal = ] ]) - static member CheckBoxForTableColumnSelection(columns: CompositeColumn [], tableIndex, columnIndex, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit) = + static member CheckBoxForTableColumnSelection(columns: CompositeColumn [], tableIndex, columnIndex, selectionInformation: SelectiveImportModalState, setSelectedColumns: SelectiveImportModalState -> unit) = Html.div [ prop.style [style.display.flex; style.justifyContent.center] prop.children [ @@ -46,7 +45,7 @@ type SelectiveImportModal = ] ] - static member TableWithImportColumnCheckboxes(table: ArcTable, ?tableIndex, ?selectionInformation: SelectedColumns, ?setSelectedColumns: SelectedColumns -> unit) = + static member TableWithImportColumnCheckboxes(table: ArcTable, ?tableIndex, ?selectionInformation: SelectiveImportModalState, ?setSelectedColumns: SelectiveImportModalState -> unit) = let columns = table.Columns let tableIndex = defaultArg tableIndex 0 let displayCheckBox = @@ -129,17 +128,17 @@ type SelectiveImportModal = Html.div [ ModalElements.RadioPlugin (radioGroup, "Import", isActive && import.Value.FullImport, - (fun (b: bool) -> addTableImport tableIndex true), + (fun _ -> addTableImport tableIndex true), isDisabled ) ModalElements.RadioPlugin (radioGroup, "Append to active table", isActive && not import.Value.FullImport, - (fun (b: bool) -> addTableImport tableIndex false), + (fun _ -> addTableImport tableIndex false), isDisabled ) ModalElements.RadioPlugin (radioGroup, "No Import", not isActive, - (fun (b: bool) -> rmvTableImport tableIndex), + (fun _ -> rmvTableImport tableIndex), isDisabled ) ] @@ -164,34 +163,32 @@ type SelectiveImportModal = [] static member Main (import: ArcFiles, dispatch, rmv) = - let state, setState = React.useState(SelectiveImportModalState.init) let tables, disArcfile = match import with | Assay a -> a.Tables, ArcFilesDiscriminate.Assay | Study (s,_) -> s.Tables, ArcFilesDiscriminate.Study | Template t -> ResizeArray([t.Table]), ArcFilesDiscriminate.Template | Investigation _ -> ResizeArray(), ArcFilesDiscriminate.Investigation + let columns = + tables + |> Array.ofSeq + |> Array.map (fun t -> Array.init t.Columns.Length (fun _ -> true)) + let importDataState, setImportDataState = React.useState(SelectiveImportModalState.init columns) let setMetadataImport = fun b -> if b then { - state with + importDataState with ImportMetadata = true; ImportTables = [for ti in 0 .. tables.Count-1 do {ImportTable.Index = ti; ImportTable.FullImport = true}] - } |> setState + } |> setImportDataState else - SelectiveImportModalState.init() |> setState + SelectiveImportModalState.init(columns) |> setImportDataState let addTableImport = fun (i: int) (fullImport: bool) -> let newImportTable: ImportTable = {Index = i; FullImport = fullImport} - let newImportTables = newImportTable::state.ImportTables |> List.distinct - {state with ImportTables = newImportTables} |> setState + let newImportTables = newImportTable::importDataState.ImportTables |> List.distinct + {importDataState with ImportTables = newImportTables} |> setImportDataState let rmvTableImport = fun i -> - {state with ImportTables = state.ImportTables |> List.filter (fun it -> it.Index <> i)} |> setState - let selectedColumns, setSelectedColumns = - let columns = - tables - |> Array.ofSeq - |> Array.map (fun t -> Array.init t.Columns.Length (fun _ -> true)) - React.useState(SelectedColumns.init columns) + {importDataState with ImportTables = importDataState.ImportTables |> List.filter (fun it -> it.Index <> i)} |> setImportDataState Daisy.modal.div [ modal.active prop.children [ @@ -209,25 +206,25 @@ type SelectiveImportModal = SelectiveImportModal.RadioPluginsBox( "Import Type", "fa-solid fa-cog", - state.ImportType, + importDataState.ImportType, "importType", [| ARCtrl.TableJoinOptions.Headers, " Column Headers"; ARCtrl.TableJoinOptions.WithUnit, " ..With Units"; ARCtrl.TableJoinOptions.WithValues, " ..With Values"; |], - fun importType -> {state with ImportType = importType} |> setState) - SelectiveImportModal.MetadataImport(state.ImportMetadata, setMetadataImport, disArcfile) + fun importType -> {importDataState with ImportType = importType} |> setImportDataState) + SelectiveImportModal.MetadataImport(importDataState.ImportMetadata, setMetadataImport, disArcfile) for ti in 0 .. (tables.Count-1) do let t = tables.[ti] - SelectiveImportModal.TableImport(ti, t, state, addTableImport, rmvTableImport, selectedColumns, setSelectedColumns) + SelectiveImportModal.TableImport(ti, t, importDataState, addTableImport, rmvTableImport, importDataState, setImportDataState) Daisy.cardActions [ Daisy.button.button [ button.info prop.style [style.marginLeft length.auto] prop.text "Submit" prop.onClick(fun e -> - {| importState = state; importedFile = import; selectedColumns = selectedColumns |} |> SpreadsheetInterface.ImportJson |> InterfaceMsg |> dispatch + {| importState = importDataState; importedFile = import; selectedColumns = importDataState.SelectedColumns |} |> SpreadsheetInterface.ImportJson |> InterfaceMsg |> dispatch rmv e ) ] diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index 3f48c01b..81522fec 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -998,7 +998,8 @@ let joinArcTablesInExcle (excelTable: Table) (arcTable:ArcTable) (templateName: do! context.sync().``then``(fun _ -> if templateName.IsSome then - let templateName = System.Text.RegularExpressions.Regex.Replace(templateName.Value, Helper.charsToRemove, "") + let name = System.Text.RegularExpressions.Regex.Replace(templateName.Value, "\s", "") + let templateName = System.Text.RegularExpressions.Regex.Replace(name, "-", "") newTable.name <- $"annotationTable{templateName}" else newTable.name <- excelTable.name diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index d63c14a8..760c5837 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -5,23 +5,12 @@ open Feliz.DaisyUI open Model open Messages open Shared -open Types.TableImport - open ARCtrl open JsonImport -type AdaptTableName = { - TemplateName: string option - } - with - static member init() = - { - TemplateName = None - } - type SelectiveTemplateFromDB = - static member CheckBoxForTakeOverTemplateName(adaptTableName: AdaptTableName, setAdaptTableName: AdaptTableName -> unit, templateName) = + static member CheckBoxForTakeOverTemplateName(adaptTableName: SelectiveImportModalState, setAdaptTableName: SelectiveImportModalState -> unit, templateName) = Html.label [ prop.className "join flex flex-row centered gap-2" prop.children [ @@ -44,7 +33,7 @@ type SelectiveTemplateFromDB = ] [] - static member displaySelectedProtocolElements(selectedTemplate: Template option, templateIndex, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit, dispatch, ?hasIcon: bool) = + static member DisplaySelectedProtocolElements(selectedTemplate: Template option, templateIndex, selectionInformation: SelectiveImportModalState, setSelectedColumns: SelectiveImportModalState -> unit, dispatch, ?hasIcon: bool) = let hasIcon = defaultArg hasIcon true Html.div [ prop.style [style.overflowX.auto; style.marginBottom (length.rem 1)] @@ -81,7 +70,7 @@ type SelectiveTemplateFromDB = ] ] - static member AddTemplatesFromDBToTableButton name (model: Model) (selectionInformation: SelectedColumns) importType dispatch = + static member AddTemplatesFromDBToTableButton name (model: Model) importType dispatch = let addTemplates (model: Model, selectedColumns) = let templates = model.ProtocolState.TemplatesSelected if templates.Length = 0 then @@ -93,7 +82,7 @@ type SelectiveTemplateFromDB = prop.className "join flex flex-row justify-center gap-2" prop.children [ let isDisabled = model.ProtocolState.TemplatesSelected.Length = 0 - let selectedColumnValues = selectionInformation.SelectedColumns + let selectedColumnValues = importType.SelectedColumns ModalElements.Button(name, addTemplates, (model, selectedColumnValues), isDisabled) if model.ProtocolState.TemplatesSelected.Length > 0 then Daisy.button.a [ @@ -105,23 +94,8 @@ type SelectiveTemplateFromDB = ] ] - [] - static member Main(model: Model, dispatch) = - let selectedColumns, setSelectedColumns = - let columns = - model.ProtocolState.TemplatesSelected - |> Array.ofSeq - |> Array.map (fun t -> Array.init t.Table.Columns.Length (fun _ -> true)) - React.useState(SelectedColumns.init columns) - let useTemplateNameState, setUseTemplateNameState = React.useState(AdaptTableName.init) - let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) - let addTableImport = fun (i: int) (fullImport: bool) -> - let newImportTable: ImportTable = {Index = i; FullImport = fullImport} - let newImportTables = newImportTable::importTypeState.ImportTables |> List.distinctBy (fun x -> x.Index) - {importTypeState with ImportTables = newImportTables} |> setImportTypeState - let rmvTableImport = fun i -> - {importTypeState with ImportTables = importTypeState.ImportTables |> List.filter (fun it -> it.Index <> i)} |> setImportTypeState - SidebarComponents.SidebarLayout.LogicContainer [ + static member DisplayTemplateSelection (model: Model, importTypeState, setImportTypeState, addTableImport, rmvTableImport, dispatch) = + [ Html.div [ SelectiveTemplateFromDB.ToProtocolSearchElement model dispatch ] @@ -144,23 +118,47 @@ type SelectiveTemplateFromDB = ModalElements.Box( "Rename Table", "fa-solid fa-cog", - SelectiveTemplateFromDB.CheckBoxForTakeOverTemplateName(useTemplateNameState, setUseTemplateNameState, template.Name)) + SelectiveTemplateFromDB.CheckBoxForTakeOverTemplateName(importTypeState, setImportTypeState, template.Name)) ] Html.div [ ModalElements.Box( template.Name, "fa-solid fa-cog", - SelectiveTemplateFromDB.displaySelectedProtocolElements(Some template, 0, selectedColumns, setSelectedColumns, dispatch, false)) + SelectiveTemplateFromDB.DisplaySelectedProtocolElements(Some template, 0, importTypeState, setImportTypeState, dispatch, false)) ] Html.div [ - SelectiveTemplateFromDB.AddFromDBToTableButton "Add template" model selectedColumns importTypeState useTemplateNameState.TemplateName dispatch + SelectiveTemplateFromDB.AddFromDBToTableButton "Add template" model importTypeState importTypeState importTypeState.TemplateName dispatch ] else if model.ProtocolState.TemplatesSelected.Length > 1 then let templates = model.ProtocolState.TemplatesSelected for templateIndex in 0..templates.Length-1 do let template = templates.[templateIndex] - SelectiveImportModal.TableImport(templateIndex, template.Table, importTypeState, addTableImport, rmvTableImport, selectedColumns, setSelectedColumns, template.Name) + SelectiveImportModal.TableImport(templateIndex, template.Table, importTypeState, addTableImport, rmvTableImport, importTypeState, setImportTypeState, template.Name, template.Name) Html.div [ - SelectiveTemplateFromDB.AddTemplatesFromDBToTableButton "Add templates" model selectedColumns importTypeState dispatch + SelectiveTemplateFromDB.AddTemplatesFromDBToTableButton "Add templates" model importTypeState dispatch ] ] + + [] + static member Main (model: Model, dispatch) = + let importTypeState, setImportTypeState = + let columns = + model.ProtocolState.TemplatesSelected + |> Array.ofSeq + |> Array.map (fun t -> Array.init t.Table.Columns.Length (fun _ -> true)) + React.useState(SelectiveImportModalState.init columns) + let addTableImport = fun (i: int) (fullImport: bool) -> + let newImportTable: ImportTable = {Index = i; FullImport = fullImport} + let newImportTables = newImportTable::importTypeState.ImportTables |> List.distinctBy (fun x -> x.Index) + {importTypeState with ImportTables = newImportTables} |> setImportTypeState + let rmvTableImport = fun i -> + {importTypeState with ImportTables = importTypeState.ImportTables |> List.filter (fun it -> it.Index <> i)} |> setImportTypeState + SidebarComponents.SidebarLayout.LogicContainer( + SelectiveTemplateFromDB.DisplayTemplateSelection( + model, + importTypeState, + setImportTypeState, + addTableImport, + rmvTableImport, + dispatch) + ) diff --git a/src/Client/States/SpreadsheetInterface.fs b/src/Client/States/SpreadsheetInterface.fs index 90da62f7..14568fdd 100644 --- a/src/Client/States/SpreadsheetInterface.fs +++ b/src/Client/States/SpreadsheetInterface.fs @@ -1,7 +1,6 @@ namespace SpreadsheetInterface open Shared -open Types.TableImport open ARCtrl open JsonImport @@ -26,7 +25,7 @@ type Msg = | InsertOntologyAnnotation of OntologyAnnotation | InsertFileNames of string list | ImportXlsx of byte [] -| ImportJson of {|importState: SelectiveImportModalState; importedFile: ArcFiles; selectedColumns: SelectedColumns|} +| ImportJson of {|importState: SelectiveImportModalState; importedFile: ArcFiles; selectedColumns: bool [] []|} /// Starts chain to export active table to isa json | ExportJson of ArcFiles * JsonExportFormat | UpdateUnitForCells diff --git a/src/Client/Types.fs b/src/Client/Types.fs index 75c41326..2c086fa8 100644 --- a/src/Client/Types.fs +++ b/src/Client/Types.fs @@ -14,12 +14,16 @@ module JsonImport = ImportType: ARCtrl.TableJoinOptions ImportMetadata: bool ImportTables: ImportTable list + SelectedColumns: bool [] [] + TemplateName: string option } with - static member init() = + static member init(selectedColumns) = { ImportType = ARCtrl.TableJoinOptions.Headers ImportMetadata = false ImportTables = [] + SelectedColumns = selectedColumns + TemplateName = None } open Fable.Core @@ -56,14 +60,3 @@ type Style = | None -> None |> Option.map _.StyleString |> Option.defaultValue "" - -module TableImport = - - type SelectedColumns = { - SelectedColumns: bool [] [] - } - with - static member init(selectedColumns) = - { - SelectedColumns = selectedColumns - } diff --git a/src/Client/Update/UpdateUtil.fs b/src/Client/Update/UpdateUtil.fs index d9fac52d..0e2d6b17 100644 --- a/src/Client/Update/UpdateUtil.fs +++ b/src/Client/Update/UpdateUtil.fs @@ -2,7 +2,6 @@ module Update.UpdateUtil open ARCtrl open Shared -open Types.TableImport open Fable.Remoting.Client let download(filename, bytes:byte []) = bytes.SaveFileAs(filename) @@ -50,21 +49,21 @@ module JsonImportHelper = /// /// /// - let updateWithMetadata (uploadedFile: ArcFiles) (state: SelectiveImportModalState) (selectedColumns: SelectedColumns) = + let updateWithMetadata (uploadedFile: ArcFiles) (state: SelectiveImportModalState) (selectedColumns: bool [] []) = if not state.ImportMetadata then failwith "Metadata must be imported" /// This updates the existing tables based on import config (joinOptions) let arcFile = match uploadedFile with | Assay a as arcFile -> - let tables = createUpdatedTables a.Tables state selectedColumns.SelectedColumns None + let tables = createUpdatedTables a.Tables state selectedColumns None a.Tables <- tables arcFile | Study (s,_) as arcFile -> - let tables = createUpdatedTables s.Tables state selectedColumns.SelectedColumns None + let tables = createUpdatedTables s.Tables state selectedColumns None s.Tables <- tables arcFile | Template t as arcFile -> - let table = createUpdatedTables (ResizeArray[t.Table]) state selectedColumns.SelectedColumns None + let table = createUpdatedTables (ResizeArray[t.Table]) state selectedColumns None t.Table <- table.[0] arcFile | Investigation _ as arcFile -> @@ -122,14 +121,14 @@ module JsonImportHelper = /// /// Required to append imported tables to the active table. /// - let updateArcFileTables (import: ArcFiles) (importState: SelectiveImportModalState) (activeTableIndex: int option) (existingOpt: ArcFiles option) (selectedColumns: SelectedColumns) = + let updateArcFileTables (import: ArcFiles) (importState: SelectiveImportModalState) (activeTableIndex: int option) (existingOpt: ArcFiles option) (selectedColumns: bool [] []) = let importTables = match import with | Assay a -> a.Tables | Study (s,_) -> s.Tables | Template t -> ResizeArray([t.Table]) | Investigation _ -> ResizeArray() - updateTables importTables importState activeTableIndex existingOpt selectedColumns.SelectedColumns + updateTables importTables importState activeTableIndex existingOpt selectedColumns module JsonExportHelper = open ARCtrl From ec72dc37dfb1e08e525e0f24de596f6d90c55a0a Mon Sep 17 00:00:00 2001 From: patrick blume Date: Tue, 17 Dec 2024 12:57:19 +0100 Subject: [PATCH 12/23] Implement regex fix --- src/Client/OfficeInterop/OfficeInterop.fs | 3 +-- src/Client/Spreadsheet/Controller/BuildingBlocks.fs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index 81522fec..16171ede 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -998,8 +998,7 @@ let joinArcTablesInExcle (excelTable: Table) (arcTable:ArcTable) (templateName: do! context.sync().``then``(fun _ -> if templateName.IsSome then - let name = System.Text.RegularExpressions.Regex.Replace(templateName.Value, "\s", "") - let templateName = System.Text.RegularExpressions.Regex.Replace(name, "-", "") + let templateName = System.Text.RegularExpressions.Regex.Replace(templateName.Value, "\W", "") newTable.name <- $"annotationTable{templateName}" else newTable.name <- excelTable.name diff --git a/src/Client/Spreadsheet/Controller/BuildingBlocks.fs b/src/Client/Spreadsheet/Controller/BuildingBlocks.fs index 977dc86a..b60b6ae2 100644 --- a/src/Client/Spreadsheet/Controller/BuildingBlocks.fs +++ b/src/Client/Spreadsheet/Controller/BuildingBlocks.fs @@ -102,7 +102,7 @@ let joinTable(tableToAdd: ArcTable) (index: int option) (options: TableJoinOptio if templateName.IsSome then //Should be updated to remove all kinds of extra symbols - let templateName = System.Text.RegularExpressions.Regex.Replace(templateName.Value, Helper.charsToRemove, "") + let templateName = System.Text.RegularExpressions.Regex.Replace(templateName.Value, "\W", "") let newTable = ArcTable.create(templateName, state.ActiveTable.Headers, state.ActiveTable.Values) state.ArcFile.Value.Tables().SetTable(state.ActiveTable.Name, newTable) From 500dc723300f4032d1c018b95b21eb1b1e5c77a4 Mon Sep 17 00:00:00 2001 From: Kevin F Date: Tue, 17 Dec 2024 13:33:04 +0100 Subject: [PATCH 13/23] Reduce code duplication :fire: --- src/Client/MainComponents/Widgets.fs | 23 +--------- src/Client/Modals/SelectiveImportModal.fs | 10 +++-- .../Pages/ProtocolTemplates/ProtocolState.fs | 8 ++-- .../Pages/ProtocolTemplates/ProtocolView.fs | 4 +- .../SelectiveTemplateFromDB.fs | 43 +++++++------------ 5 files changed, 32 insertions(+), 56 deletions(-) diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index 84e39419..8769559e 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -213,38 +213,19 @@ type Widget = static member Templates (model: Model, dispatch, rmv: MouseEvent -> unit) = let templates = model.ProtocolState.Templates let config, setConfig = React.useState(TemplateFilterConfig.init) - let importTypeState, setImportTypeState = - let columns = - model.ProtocolState.TemplatesSelected - |> Array.ofSeq - |> Array.map (fun t -> Array.init t.Table.Columns.Length (fun _ -> true)) - React.useState(SelectiveImportModalState.init columns) let filteredTemplates = Protocol.Search.filterTemplates (templates, config) - let addTableImport = fun (i: int) (fullImport: bool) -> - let newImportTable: ImportTable = {Index = i; FullImport = fullImport} - let newImportTables = newImportTable::importTypeState.ImportTables |> List.distinctBy (fun x -> x.Index) - {importTypeState with ImportTables = newImportTables} |> setImportTypeState - let rmvTableImport = fun i -> - {importTypeState with ImportTables = importTypeState.ImportTables |> List.filter (fun it -> it.Index <> i)} |> setImportTypeState React.useEffectOnce(fun _ -> Messages.Protocol.GetAllProtocolsRequest |> Messages.ProtocolMsg |> dispatch) let selectContent() = [ Protocol.Search.FileSortElement(model, config, setConfig, "@md/templateWidget:grid-cols-3") Protocol.Search.Component (filteredTemplates, model, dispatch, length.px 350) ] - let templateSelectionElements = - SelectiveTemplateFromDB.DisplayTemplateSelection( - model, - importTypeState, - setImportTypeState, - addTableImport, - rmvTableImport, - dispatch) let insertContent() = [ Html.div [ prop.style [style.maxHeight (length.px 350); style.overflow.auto] - prop.children templateSelectionElements + prop.className "flex flex-col gap-2" + prop.children (SelectiveTemplateFromDB.Main(model, dispatch)) ] ] diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index d50b72d9..e531c388 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -12,7 +12,11 @@ open Components type SelectiveImportModal = + [] static member RadioPluginsBox(boxName, icon, importType: TableJoinOptions, radioGroupName, radioData: (TableJoinOptions * string)[], setImportType: TableJoinOptions -> unit) = + + let guid = React.useMemo(fun () -> System.Guid.NewGuid().ToString()) + let radioGroupName = radioGroupName + guid let myradio(target: TableJoinOptions, txt: string) = let isChecked = importType = target ModalElements.RadioPlugin(radioGroupName, txt, isChecked, fun (b: bool) -> if b then setImportType target) @@ -50,12 +54,12 @@ type SelectiveImportModal = let tableIndex = defaultArg tableIndex 0 let displayCheckBox = //Determine whether to display checkboxes or not - selectionInformation.IsSome && setSelectedColumns.IsSome + selectionInformation.IsSome && setSelectedColumns.IsSome Daisy.table [ prop.children [ Html.thead [ Html.tr [ - for columnIndex in 0..columns.Length-1 do + for columnIndex in 0..columns.Length-1 do Html.th [ Html.label [ prop.className "join flex flex-row centered gap-2" @@ -145,7 +149,7 @@ type SelectiveImportModal = Daisy.collapse [ Html.input [prop.type'.checkbox; prop.className "min-h-0 h-5"] Daisy.collapseTitle [ - prop.className "p-1 min-h-0 h-5 text-sm" + prop.className "p-1 min-h-0 h-5 text-sm" prop.text (if isActive then "Select Columns" else "Preview Table") ] Daisy.collapseContent [ diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs index 99384be0..ec6f5648 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs @@ -68,11 +68,11 @@ module Protocol = model.ProtocolState.TemplatesSelected else prot::model.ProtocolState.TemplatesSelected - let nextModel = { - model with - Model.ProtocolState.TemplatesSelected = templates + let nextState = { + state with + TemplatesSelected = templates } - state, Cmd.ofMsg (UpdateModel nextModel) + nextState, Cmd.none | ProtocolIncreaseTimesUsed templateId -> failwith "ParseUploadedFileRequest IS NOT IMPLEMENTED YET" //let cmd = diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolView.fs b/src/Client/Pages/ProtocolTemplates/ProtocolView.fs index 8a2ee134..a3d92f36 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolView.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolView.fs @@ -38,7 +38,9 @@ type Templates = // Box 1 SidebarComponents.SidebarLayout.Description "Add template from database." - Modals.SelectiveTemplateFromDB.Main(model, dispatch) + SidebarComponents.SidebarLayout.LogicContainer [ + Modals.SelectiveTemplateFromDB.Main(model, dispatch) + ] // Box 2 SidebarComponents.SidebarLayout.Description (Html.p [ diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index 760c5837..ad6ad012 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -94,12 +94,25 @@ type SelectiveTemplateFromDB = ] ] - static member DisplayTemplateSelection (model: Model, importTypeState, setImportTypeState, addTableImport, rmvTableImport, dispatch) = - [ + [] + static member Main (model: Model, dispatch) = + let importTypeState, setImportTypeState = + let columns = + model.ProtocolState.TemplatesSelected + |> Array.ofSeq + |> Array.map (fun t -> Array.init t.Table.Columns.Length (fun _ -> true)) + React.useState(SelectiveImportModalState.init columns) + let addTableImport = fun (i: int) (fullImport: bool) -> + let newImportTable: ImportTable = {Index = i; FullImport = fullImport} + let newImportTables = newImportTable::importTypeState.ImportTables |> List.distinctBy (fun x -> x.Index) + {importTypeState with ImportTables = newImportTables} |> setImportTypeState + let rmvTableImport = fun i -> + {importTypeState with ImportTables = importTypeState.ImportTables |> List.filter (fun it -> it.Index <> i)} |> setImportTypeState + React.fragment [ Html.div [ SelectiveTemplateFromDB.ToProtocolSearchElement model dispatch ] - if model.ProtocolState.TemplatesSelected.Length > 0 then + if model.ProtocolState.TemplatesSelected.Length > 0 then SelectiveImportModal.RadioPluginsBox( "Import Type", "fa-solid fa-cog", @@ -138,27 +151,3 @@ type SelectiveTemplateFromDB = SelectiveTemplateFromDB.AddTemplatesFromDBToTableButton "Add templates" model importTypeState dispatch ] ] - - [] - static member Main (model: Model, dispatch) = - let importTypeState, setImportTypeState = - let columns = - model.ProtocolState.TemplatesSelected - |> Array.ofSeq - |> Array.map (fun t -> Array.init t.Table.Columns.Length (fun _ -> true)) - React.useState(SelectiveImportModalState.init columns) - let addTableImport = fun (i: int) (fullImport: bool) -> - let newImportTable: ImportTable = {Index = i; FullImport = fullImport} - let newImportTables = newImportTable::importTypeState.ImportTables |> List.distinctBy (fun x -> x.Index) - {importTypeState with ImportTables = newImportTables} |> setImportTypeState - let rmvTableImport = fun i -> - {importTypeState with ImportTables = importTypeState.ImportTables |> List.filter (fun it -> it.Index <> i)} |> setImportTypeState - SidebarComponents.SidebarLayout.LogicContainer( - SelectiveTemplateFromDB.DisplayTemplateSelection( - model, - importTypeState, - setImportTypeState, - addTableImport, - rmvTableImport, - dispatch) - ) From c5739f16f4391bfc1639428d23d6871204617b67 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Tue, 17 Dec 2024 14:01:30 +0100 Subject: [PATCH 14/23] Move selected box above template search --- src/Client/MainComponents/Widgets.fs | 1 + src/Client/Modals/SelectiveImportModal.fs | 6 +- .../Pages/ProtocolTemplates/ProtocolSearch.fs | 2 + .../ProtocolSearchViewComponent.fs | 78 ++++++++++--------- .../SelectiveTemplateFromDB.fs | 2 +- 5 files changed, 47 insertions(+), 42 deletions(-) diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index 8769559e..1a5fa7d5 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -218,6 +218,7 @@ type Widget = let selectContent() = [ Protocol.Search.FileSortElement(model, config, setConfig, "@md/templateWidget:grid-cols-3") + ModalElements.Box("Selected Templates", "fa-solid fa-cog", Search.SelectedTemplatesElement model dispatch) Protocol.Search.Component (filteredTemplates, model, dispatch, length.px 350) ] let insertContent() = diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index e531c388..ee6600b4 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -121,10 +121,10 @@ type SelectiveImportModal = ) [] - static member TableImport(tableIndex: int, table0: ArcTable, state: SelectiveImportModalState, addTableImport: int -> bool -> unit, rmvTableImport: int -> unit, selectedColumns, setSelectedColumns, ?templateName, ?radioGroup) = + static member TableImport(tableIndex: int, table0: ArcTable, state: SelectiveImportModalState, addTableImport: int -> bool -> unit, rmvTableImport: int -> unit, selectedColumns, setSelectedColumns, ?templateName) = let name = defaultArg templateName table0.Name - let radioGroup = defaultArg radioGroup "" - let radioGroup = "radioGroup_" + radioGroup + let guid = React.useMemo(fun () -> System.Guid.NewGuid().ToString()) + let radioGroup = "radioGroup_" + guid let import = state.ImportTables |> List.tryFind (fun it -> it.Index = tableIndex) let isActive = import.IsSome let isDisabled = state.ImportMetadata diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs index ce591411..0de05a37 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs @@ -6,6 +6,7 @@ open Model open Messages open Feliz open Feliz.DaisyUI +open Modals module private HelperProtocolSearch = @@ -58,6 +59,7 @@ type SearchContainer = prop.children [ Protocol.Search.InfoField() Protocol.Search.FileSortElement(model, config, setConfig) + ModalElements.Box("Selected Templates", "fa-solid fa-cog", Search.SelectedTemplatesElement model dispatch) Protocol.Search.Component (filteredTemplates, model, dispatch) ] ] diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs b/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs index 5ccd593b..2b6ec09b 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs @@ -553,6 +553,46 @@ type Search = ] ] + static member private displayTemplateNames model = + Html.div [ + prop.className "flex gap-2" + prop.children [ + let names = List.rev model.ProtocolState.TemplatesSelected |> List.map (fun template -> template.Name) + for i in 0..names.Length-1 do + Html.div [ yield! [prop.text $"\"{names.[i]}\""]] + ] + ] + static member private selectTemplatesButton model dispatch = + Html.div [ + prop.className "flex justify-center gap-2" + prop.children [ + Daisy.button.a [ + button.sm + prop.onClick (fun _ -> + SelectProtocols model.ProtocolState.TemplatesSelected |> ProtocolMsg |> dispatch + ) + button.wide + if model.ProtocolState.TemplatesSelected.Length > 0 then + button.success + else + button.disabled + prop.text "Select templates" + ] + ] + ] + static member SelectedTemplatesElement model dispatch = + Html.div [ + prop.style [style.overflowX.auto; style.marginBottom (length.rem 1)] + prop.children [ + Html.div [ + prop.children [ + Search.displayTemplateNames model + ] + ] + Search.selectTemplatesButton model dispatch + ] + ] + [] static member Component (templates, model: Model, dispatch, ?maxheight: Styles.ICssUnit) = let maxheight = defaultArg maxheight (length.px 600) @@ -603,43 +643,5 @@ type Search = ] ] ] - let names = - Html.div [ - prop.className "flex gap-2" - prop.children [ - let names = List.rev model.ProtocolState.TemplatesSelected |> List.map (fun template -> template.Name) - for i in 0..names.Length-1 do - Html.div [ yield! [prop.text $"\"{names.[i]}\""]] - ] - ] - let button = - Html.div [ - prop.className "flex justify-center gap-2" - prop.children [ - Daisy.button.a [ - button.sm - prop.onClick (fun _ -> - SelectProtocols model.ProtocolState.TemplatesSelected |> ProtocolMsg |> dispatch - ) - button.wide - button.success - prop.text "Select templates" - ] - ] - ] - let element = - Html.div [ - prop.style [style.overflowX.auto; style.marginBottom (length.rem 1)] - prop.children [ - Html.div [ - prop.children [ - names - ] - ] - button - ] - ] - if model.ProtocolState.TemplatesSelected.Length > 0 then - ModalElements.Box("Selected Templates", "fa-solid fa-cog", element) ] ] diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index ad6ad012..19c26028 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -146,7 +146,7 @@ type SelectiveTemplateFromDB = let templates = model.ProtocolState.TemplatesSelected for templateIndex in 0..templates.Length-1 do let template = templates.[templateIndex] - SelectiveImportModal.TableImport(templateIndex, template.Table, importTypeState, addTableImport, rmvTableImport, importTypeState, setImportTypeState, template.Name, template.Name) + SelectiveImportModal.TableImport(templateIndex, template.Table, importTypeState, addTableImport, rmvTableImport, importTypeState, setImportTypeState, template.Name) Html.div [ SelectiveTemplateFromDB.AddTemplatesFromDBToTableButton "Add templates" model importTypeState dispatch ] From 7d14e3afd3859372a056335442cdeae6b5a747c9 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Tue, 17 Dec 2024 14:04:09 +0100 Subject: [PATCH 15/23] removed line --- src/Client/MainComponents/Widgets.fs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index 1a5fa7d5..f1cecc2b 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -21,7 +21,6 @@ module private InitExtensions = | Some p -> Some p | None -> None - open InitExtensions open Fable.Core From 5b22abf4f75a82e9d5c32e3898734f8defe19525 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Wed, 18 Dec 2024 10:02:54 +0100 Subject: [PATCH 16/23] Replace worksheet name with template name instead of table name --- src/Client/OfficeInterop/OfficeInterop.fs | 26 ++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index 16171ede..91666dbf 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -996,13 +996,29 @@ let joinArcTablesInExcle (excelTable: Table) (arcTable:ArcTable) (templateName: let tableSeqs = arcTable.ToStringSeqs() - do! context.sync().``then``(fun _ -> - if templateName.IsSome then - let templateName = System.Text.RegularExpressions.Regex.Replace(templateName.Value, "\W", "") - newTable.name <- $"annotationTable{templateName}" + if templateName.IsSome then + let worksheets = context.workbook.worksheets + let activeWorksheet = context.workbook.worksheets.getActiveWorksheet() + let _ = + activeWorksheet.load(propertyNames = U2.Case2 (ResizeArray["name"])) |> ignore + worksheets.load(propertyNames = U2.Case2 (ResizeArray["items"; "name"])) + + do! context.sync() + + let worksheetNames = worksheets.items |> Seq.map (fun item -> item.name) |> Array.ofSeq + let templateName = System.Text.RegularExpressions.Regex.Replace(templateName.Value, "\W", "") + + if (Array.contains templateName worksheetNames) then + let nameCount = + worksheetNames + |> Array.filter(fun item -> item.Contains(templateName)) + |> Array.length + let templateName = templateName + (nameCount.ToString()) + activeWorksheet.name <- templateName else - newTable.name <- excelTable.name + activeWorksheet.name <- templateName + do! context.sync().``then``(fun _ -> let headerNames = let names = AnnotationTable.getHeaders tableSeqs names From f6aa09ad806f9a3fc8a98a4dcbe0f95f25c06f1b Mon Sep 17 00:00:00 2001 From: patrick blume Date: Wed, 18 Dec 2024 10:12:22 +0100 Subject: [PATCH 17/23] Add summaries --- .../SelectiveTemplateFromDB.fs | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index 19c26028..a66be425 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -4,12 +4,17 @@ open Feliz open Feliz.DaisyUI open Model open Messages -open Shared open ARCtrl open JsonImport type SelectiveTemplateFromDB = + /// + /// + /// + /// + /// + /// static member CheckBoxForTakeOverTemplateName(adaptTableName: SelectiveImportModalState, setAdaptTableName: SelectiveImportModalState -> unit, templateName) = Html.label [ prop.className "join flex flex-row centered gap-2" @@ -24,6 +29,11 @@ type SelectiveTemplateFromDB = ] ] + /// + /// + /// + /// + /// static member ToProtocolSearchElement(model: Model) dispatch = Daisy.button.button [ prop.onClick(fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) @@ -32,6 +42,15 @@ type SelectiveTemplateFromDB = prop.text "Browse database" ] + /// + /// + /// + /// + /// + /// + /// + /// + /// [] static member DisplaySelectedProtocolElements(selectedTemplate: Template option, templateIndex, selectionInformation: SelectiveImportModalState, setSelectedColumns: SelectiveImportModalState -> unit, dispatch, ?hasIcon: bool) = let hasIcon = defaultArg hasIcon true @@ -47,6 +66,15 @@ type SelectiveTemplateFromDB = ] ] + /// + /// + /// + /// + /// + /// + /// + /// + /// static member AddFromDBToTableButton name (model: Model) selectionInformation importType useTemplateName dispatch = let addTemplate (model: Model, selectedColumns) = let template = @@ -70,6 +98,13 @@ type SelectiveTemplateFromDB = ] ] + /// + /// + /// + /// + /// + /// + /// static member AddTemplatesFromDBToTableButton name (model: Model) importType dispatch = let addTemplates (model: Model, selectedColumns) = let templates = model.ProtocolState.TemplatesSelected @@ -94,6 +129,11 @@ type SelectiveTemplateFromDB = ] ] + /// + /// + /// + /// + /// [] static member Main (model: Model, dispatch) = let importTypeState, setImportTypeState = From f568dcd61f7996b59c2a59a7326b23966bb68d2e Mon Sep 17 00:00:00 2001 From: patrick blume Date: Wed, 18 Dec 2024 14:12:26 +0100 Subject: [PATCH 18/23] implemented review changes --- src/Client/Pages/ProtocolTemplates/ProtocolState.fs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs index ec6f5648..861215fd 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs @@ -62,7 +62,6 @@ module Protocol = } state, Cmd.ofMsg (UpdateModel nextModel) | AddProtocol prot -> - log "AddProtocol" let templates = if List.contains prot model.ProtocolState.TemplatesSelected then model.ProtocolState.TemplatesSelected From 4f0359ce7bac3e9a6dd7263009173ca64c37cbba Mon Sep 17 00:00:00 2001 From: patrick blume Date: Thu, 19 Dec 2024 13:55:39 +0100 Subject: [PATCH 19/23] Implement review changes --- src/Client/Index.fs | 2 +- src/Client/Init.fs | 1 - src/Client/MainComponents/Widgets.fs | 2 +- src/Client/Model.fs | 24 +++---- src/Client/OfficeInterop/OfficeInterop.fs | 63 ++++++++++++------- src/Client/Pages/FilePicker/FilePickerView.fs | 2 +- .../Pages/ProtocolTemplates/ProtocolSearch.fs | 8 +-- .../Pages/ProtocolTemplates/ProtocolState.fs | 7 ++- .../SelectiveTemplateFromDB.fs | 2 +- src/Client/Routing.fs | 2 +- src/Client/SharedComponents/NavbarBurger.fs | 2 +- src/Client/SidebarComponents/Tabs.fs | 18 +++--- src/Client/Views/MainPageViews.fs | 14 ++--- src/Client/Views/SidebarView.fs | 16 ++--- 14 files changed, 88 insertions(+), 75 deletions(-) diff --git a/src/Client/Index.fs b/src/Client/Index.fs index 09e6e976..55cfbae6 100644 --- a/src/Client/Index.fs +++ b/src/Client/Index.fs @@ -31,7 +31,7 @@ let View (model : Model) (dispatch : Msg -> unit) = prop.className "flex w-full overflow-auto h-screen" prop.children [ Modals.ModalProvider.Main(model, dispatch) - match model.PageState.IsHome, model.PersistentStorageState.Host with + match model.ProtocolState.IsHome, model.PersistentStorageState.Host with | false, _ -> View.MainPageView.Main(model, dispatch) | _, Some Swatehost.Excel -> diff --git a/src/Client/Init.fs b/src/Client/Init.fs index 95e54fda..6f202d83 100644 --- a/src/Client/Init.fs +++ b/src/Client/Init.fs @@ -9,7 +9,6 @@ open Update let initialModel = { - PageState = PageState .init() PersistentStorageState = PersistentStorageState .init() DevState = DevState .init() TermSearchState = TermSearch.Model .init() diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index f1cecc2b..a73e5ca3 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -231,7 +231,7 @@ type Widget = let content = let switchContent = - if model.ProtocolState.TemplatesSelected.Length > 0 && model.PageState.SidebarPage = Routing.SidebarPage.Protocol then + if model.ProtocolState.TemplatesSelected.Length > 0 && model.ProtocolState.WidgetTypes = Routing.WidgetTypes.Protocol then insertContent() else selectContent() Html.div [ diff --git a/src/Client/Model.fs b/src/Client/Model.fs index 3bf68fc4..e9e54639 100644 --- a/src/Client/Model.fs +++ b/src/Client/Model.fs @@ -125,20 +125,6 @@ type PersistentStorageState = { HasOntologiesLoaded = false } -type PageState = { - SidebarPage : Routing.SidebarPage - MainPage: Routing.MainPage -} with - static member init () = - { - SidebarPage = SidebarPage.BuildingBlock - MainPage = MainPage.Default - } - member this.IsHome = - match this.MainPage with - | MainPage.Default -> true - | _ -> false - module FilePicker = type Model = { FileNames : (int*string) list @@ -248,6 +234,8 @@ module Protocol = // ------ Protocol from Database ------ TemplatesSelected : ARCtrl.Template list Templates : ARCtrl.Template [] + WidgetTypes : Routing.WidgetTypes + MainPage : Routing.MainPage } with static member init () = { // Client @@ -256,7 +244,13 @@ module Protocol = TemplatesSelected = [] // ------ Protocol from Database ------ Templates = [||] + WidgetTypes = WidgetTypes.BuildingBlock + MainPage = MainPage.Default } + member this.IsHome = + match this.MainPage with + | MainPage.Default -> true + | _ -> false type RequestBuildingBlockInfoStates = | Inactive @@ -269,8 +263,6 @@ type RequestBuildingBlockInfoStates = | RequestDataBaseInformation -> "Search Database " type Model = { - ///PageState - PageState : PageState ///Data that needs to be persistent once loaded PersistentStorageState : PersistentStorageState ///Error handling, Logging, etc. diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index 91666dbf..4848ddd5 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -980,6 +980,34 @@ let prepareTemplateInMemory (originTable: ArcTable) (tableToAdd: ArcTable) (sele finalTable +/// +/// Checks whether the given worksheet name exists or not and updates it, when it already exists by adding a number +/// +/// +/// +let getNewActiveWorkSheetName (worksheetName: string) (context: RequestContext) = + promise { + let worksheets = context.workbook.worksheets + let activeWorksheet = context.workbook.worksheets.getActiveWorksheet() + let _ = + activeWorksheet.load(propertyNames = U2.Case2 (ResizeArray["name"])) |> ignore + worksheets.load(propertyNames = U2.Case2 (ResizeArray["items"; "name"])) + + do! context.sync() + + let worksheetNames = worksheets.items |> Seq.map (fun item -> item.name) |> Array.ofSeq + let worksheetName = System.Text.RegularExpressions.Regex.Replace(worksheetName, "\W", "") + + if (Array.contains worksheetName worksheetNames) then + let nameCount = + worksheetNames + |> Array.filter(fun item -> item.Contains(worksheetName)) + |> Array.length + return (worksheetName + (nameCount.ToString())) + else + return worksheetName + } + let joinArcTablesInExcle (excelTable: Table) (arcTable:ArcTable) (templateName: string option) (context: RequestContext) = promise { let newTableRange = excelTable.getRange() @@ -997,26 +1025,13 @@ let joinArcTablesInExcle (excelTable: Table) (arcTable:ArcTable) (templateName: let tableSeqs = arcTable.ToStringSeqs() if templateName.IsSome then - let worksheets = context.workbook.worksheets + let! workSheetName = getNewActiveWorkSheetName templateName.Value context let activeWorksheet = context.workbook.worksheets.getActiveWorksheet() - let _ = - activeWorksheet.load(propertyNames = U2.Case2 (ResizeArray["name"])) |> ignore - worksheets.load(propertyNames = U2.Case2 (ResizeArray["items"; "name"])) + let _ = activeWorksheet.load(propertyNames = U2.Case2 (ResizeArray["name"])) do! context.sync() - let worksheetNames = worksheets.items |> Seq.map (fun item -> item.name) |> Array.ofSeq - let templateName = System.Text.RegularExpressions.Regex.Replace(templateName.Value, "\W", "") - - if (Array.contains templateName worksheetNames) then - let nameCount = - worksheetNames - |> Array.filter(fun item -> item.Contains(templateName)) - |> Array.length - let templateName = templateName + (nameCount.ToString()) - activeWorksheet.name <- templateName - else - activeWorksheet.name <- templateName + activeWorksheet.name <- workSheetName do! context.sync().``then``(fun _ -> let headerNames = @@ -1189,12 +1204,16 @@ let addTemplates (tablesToAdd: ArcTable[]) (selectedColumnsCollection: bool [] [ originTable.Join(endTable, ?joinOptions = options) originTable - let activeWorksheet = - if i = 0 && result.IsNone then context.workbook.worksheets.getActiveWorksheet() + let! activeWorksheet = + if i = 0 && result.IsNone then + promise { return context.workbook.worksheets.getActiveWorksheet() } else - let newWorkSheet = context.workbook.worksheets.add(tableToAdd.Name) - newWorkSheet.activate() - newWorkSheet + promise { + let! workSheetName = getNewActiveWorkSheetName tableToAdd.Name context + let newWorkSheet = context.workbook.worksheets.add(workSheetName) + newWorkSheet.activate() + return newWorkSheet + } let tableValues = finalTable.ToStringSeqs() let range = activeWorksheet.getRangeByIndexes(0, 0, float (finalTable.RowCount + 1), (tableValues.Item 0).Count) @@ -1245,6 +1264,8 @@ let joinTables (tablesToAdd: ArcTable [], selectedColumnsCollection: bool [] [], if msgAdd.IsEmpty then return msgJoin + else if msgJoin.IsEmpty then + return msgAdd else return [msgJoin.Head; msgAdd.Head] | None -> diff --git a/src/Client/Pages/FilePicker/FilePickerView.fs b/src/Client/Pages/FilePicker/FilePickerView.fs index 4dbf8fe7..cfdafa8e 100644 --- a/src/Client/Pages/FilePicker/FilePickerView.fs +++ b/src/Client/Pages/FilePicker/FilePickerView.fs @@ -14,7 +14,7 @@ let update (filePickerMsg:FilePicker.Msg) (state: FilePicker.Model) (model: Mode let nextModel = { model with Model.FilePickerState.FileNames = fileNames |> List.mapi (fun i x -> i + 1, x) - Model.PageState.SidebarPage = Routing.SidebarPage.FilePicker + Model.ProtocolState.WidgetTypes = Routing.WidgetTypes.FilePicker } let nextCmd = UpdateModel nextModel|> Cmd.ofMsg state, nextCmd diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs index 0de05a37..391e5e34 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs @@ -15,14 +15,14 @@ module private HelperProtocolSearch = prop.children [ Html.ul [ Html.li [Html.a [ - prop.onClick (fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.Protocol} |> dispatch) - prop.text (Routing.SidebarPage.Protocol.AsStringRdbl) + prop.onClick (fun _ -> UpdateModel {model with Model.ProtocolState.WidgetTypes = Routing.WidgetTypes.Protocol} |> dispatch) + prop.text (Routing.WidgetTypes.Protocol.AsStringRdbl) ]] Html.li [ prop.className "is-active" prop.children (Html.a [ - prop.onClick (fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) - prop.text (Routing.SidebarPage.ProtocolSearch.AsStringRdbl) + prop.onClick (fun _ -> UpdateModel {model with Model.ProtocolState.WidgetTypes = Routing.WidgetTypes.ProtocolSearch} |> dispatch) + prop.text (Routing.WidgetTypes.ProtocolSearch.AsStringRdbl) ]) ] ] diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs index 861215fd..2c794e83 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs @@ -57,8 +57,8 @@ module Protocol = log "SelectProtocols" let nextModel = { model with - Model.ProtocolState.TemplatesSelected = prots - Model.PageState.SidebarPage = Routing.SidebarPage.Protocol + Model.ProtocolState.TemplatesSelected = prots + Model.ProtocolState.WidgetTypes = Routing.WidgetTypes.Protocol } state, Cmd.ofMsg (UpdateModel nextModel) | AddProtocol prot -> @@ -69,7 +69,8 @@ module Protocol = prot::model.ProtocolState.TemplatesSelected let nextState = { state with - TemplatesSelected = templates + TemplatesSelected = templates + WidgetTypes = Routing.WidgetTypes.ProtocolSearch } nextState, Cmd.none | ProtocolIncreaseTimesUsed templateId -> diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index a66be425..2b4c055e 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -36,7 +36,7 @@ type SelectiveTemplateFromDB = /// static member ToProtocolSearchElement(model: Model) dispatch = Daisy.button.button [ - prop.onClick(fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) + prop.onClick(fun _ -> UpdateModel {model with Model.ProtocolState.WidgetTypes = Routing.WidgetTypes.ProtocolSearch} |> dispatch) button.primary button.block prop.text "Browse database" diff --git a/src/Client/Routing.fs b/src/Client/Routing.fs index 11047b57..fd0dd18c 100644 --- a/src/Client/Routing.fs +++ b/src/Client/Routing.fs @@ -4,7 +4,7 @@ open Elmish.UrlParser open Feliz [] -type SidebarPage = +type WidgetTypes = | BuildingBlock | TermSearch | FilePicker diff --git a/src/Client/SharedComponents/NavbarBurger.fs b/src/Client/SharedComponents/NavbarBurger.fs index 09a3f93e..372533bb 100644 --- a/src/Client/SharedComponents/NavbarBurger.fs +++ b/src/Client/SharedComponents/NavbarBurger.fs @@ -52,7 +52,7 @@ type NavbarBurger = [] static member private Dropdown(isOpen, setIsOpen, model: Model.Model, dispatch) = - let navigateTo = fun (mainPage: Routing.MainPage) -> {model with Model.PageState.MainPage = mainPage} |> Messages.UpdateModel |> dispatch + let navigateTo = fun (mainPage: Routing.MainPage) -> {model with Model.ProtocolState.MainPage = mainPage} |> Messages.UpdateModel |> dispatch Components.BaseDropdown.Main( isOpen, setIsOpen, diff --git a/src/Client/SidebarComponents/Tabs.fs b/src/Client/SidebarComponents/Tabs.fs index 3957716e..c4b54928 100644 --- a/src/Client/SidebarComponents/Tabs.fs +++ b/src/Client/SidebarComponents/Tabs.fs @@ -9,12 +9,12 @@ open Feliz.DaisyUI type Tabs = - static member private NavigationTab (pageLink: Routing.SidebarPage) (model:Model) (dispatch:Msg-> unit) = - let isActive = pageLink = model.PageState.SidebarPage + static member private NavigationTab (pageLink: Routing.WidgetTypes) (model:Model) (dispatch:Msg-> unit) = + let isActive = pageLink = model.ProtocolState.WidgetTypes Daisy.tab [ if isActive then tab.active prop.className "navigation" // this class does not do anything, but disables styling. - prop.onClick (fun e -> e.preventDefault(); UpdateModel { model with Model.PageState.SidebarPage = pageLink } |> dispatch) + prop.onClick (fun e -> e.preventDefault(); UpdateModel { model with Model.ProtocolState.WidgetTypes = pageLink } |> dispatch) prop.children (pageLink.AsIcon()) ] @@ -25,11 +25,11 @@ type Tabs = tabs.boxed prop.className "w-full" prop.children [ - Tabs.NavigationTab Routing.SidebarPage.BuildingBlock model dispatch - Tabs.NavigationTab Routing.SidebarPage.TermSearch model dispatch - Tabs.NavigationTab Routing.SidebarPage.Protocol model dispatch - Tabs.NavigationTab Routing.SidebarPage.FilePicker model dispatch - Tabs.NavigationTab Routing.SidebarPage.DataAnnotator model dispatch - Tabs.NavigationTab Routing.SidebarPage.JsonExport model dispatch + Tabs.NavigationTab Routing.WidgetTypes.BuildingBlock model dispatch + Tabs.NavigationTab Routing.WidgetTypes.TermSearch model dispatch + Tabs.NavigationTab Routing.WidgetTypes.Protocol model dispatch + Tabs.NavigationTab Routing.WidgetTypes.FilePicker model dispatch + Tabs.NavigationTab Routing.WidgetTypes.DataAnnotator model dispatch + Tabs.NavigationTab Routing.WidgetTypes.JsonExport model dispatch ] ] \ No newline at end of file diff --git a/src/Client/Views/MainPageViews.fs b/src/Client/Views/MainPageViews.fs index fa757a61..6ed7ac8e 100644 --- a/src/Client/Views/MainPageViews.fs +++ b/src/Client/Views/MainPageViews.fs @@ -14,7 +14,7 @@ open MainPageUtil type MainPageView = static member DrawerSideContentItem (model: Model.Model, route: Routing.MainPage, onclick) = - let isActive = model.PageState.MainPage = route + let isActive = model.ProtocolState.MainPage = route Html.li [ prop.onClick onclick prop.children [ @@ -35,7 +35,7 @@ type MainPageView = prop.ariaLabel "Back to spreadsheet view" button.link button.sm - prop.onClick (fun _ -> UpdateModel {model with Model.PageState.MainPage = Routing.MainPage.Default} |> dispatch) + prop.onClick (fun _ -> UpdateModel {model with Model.ProtocolState.MainPage = Routing.MainPage.Default} |> dispatch) prop.children [ Html.i [prop.className "fa-solid fa-arrow-left"] Html.span "Back" @@ -44,9 +44,9 @@ type MainPageView = Html.ul [ prop.className "menu gap-y-1" prop.children [ - MainPageView.DrawerSideContentItem(model, Routing.MainPage.Settings, fun _ -> UpdateModel {model with Model.PageState.MainPage = Routing.MainPage.Settings} |> dispatch) - MainPageView.DrawerSideContentItem(model, Routing.MainPage.About, fun _ -> UpdateModel {model with Model.PageState.MainPage = Routing.MainPage.About} |> dispatch) - MainPageView.DrawerSideContentItem(model, Routing.MainPage.PrivacyPolicy, fun _ -> UpdateModel {model with Model.PageState.MainPage = Routing.MainPage.PrivacyPolicy} |> dispatch) + MainPageView.DrawerSideContentItem(model, Routing.MainPage.Settings, fun _ -> UpdateModel {model with Model.ProtocolState.MainPage = Routing.MainPage.Settings} |> dispatch) + MainPageView.DrawerSideContentItem(model, Routing.MainPage.About, fun _ -> UpdateModel {model with Model.ProtocolState.MainPage = Routing.MainPage.About} |> dispatch) + MainPageView.DrawerSideContentItem(model, Routing.MainPage.PrivacyPolicy, fun _ -> UpdateModel {model with Model.ProtocolState.MainPage = Routing.MainPage.PrivacyPolicy} |> dispatch) ] ] ] @@ -78,7 +78,7 @@ type MainPageView = Html.div [ prop.ariaLabel "logo" - prop.onClick (fun _ -> UpdateModel {model with Model.PageState.MainPage = Routing.MainPage.Default} |> dispatch) + prop.onClick (fun _ -> UpdateModel {model with Model.ProtocolState.MainPage = Routing.MainPage.Default} |> dispatch) prop.className "cursor-pointer" prop.children [ Html.img [ @@ -90,7 +90,7 @@ type MainPageView = ] static member MainContent(model: Model.Model, dispatch) = - match model.PageState.MainPage with + match model.ProtocolState.MainPage with | Routing.MainPage.Settings -> Pages.Settings.Main(model, dispatch) | Routing.MainPage.About -> diff --git a/src/Client/Views/SidebarView.fs b/src/Client/Views/SidebarView.fs index 5bd733df..5eb06235 100644 --- a/src/Client/Views/SidebarView.fs +++ b/src/Client/Views/SidebarView.fs @@ -14,26 +14,26 @@ type SidebarView = Html.div [ prop.className "grow overflow-y-auto" prop.children [ - match model.PageState with - | {SidebarPage = Routing.SidebarPage.BuildingBlock } -> + match model.ProtocolState with + | {WidgetTypes = Routing.WidgetTypes.BuildingBlock } -> BuildingBlock.Core.addBuildingBlockComponent model dispatch - | {SidebarPage = Routing.SidebarPage.TermSearch } -> + | {WidgetTypes = Routing.WidgetTypes.TermSearch } -> TermSearch.Main (model, dispatch) - | {SidebarPage = Routing.SidebarPage.FilePicker } -> + | {WidgetTypes = Routing.WidgetTypes.FilePicker } -> FilePicker.filePickerComponent model dispatch - | {SidebarPage = Routing.SidebarPage.Protocol } -> + | {WidgetTypes = Routing.WidgetTypes.Protocol } -> Protocol.Templates.Main (model, dispatch) - | {SidebarPage = Routing.SidebarPage.DataAnnotator } -> + | {WidgetTypes = Routing.WidgetTypes.DataAnnotator } -> Pages.DataAnnotator.Main(model, dispatch) - | {SidebarPage = Routing.SidebarPage.JsonExport } -> + | {WidgetTypes = Routing.WidgetTypes.JsonExport } -> JsonExporter.Core.FileExporter.Main(model, dispatch) - | {SidebarPage = Routing.SidebarPage.ProtocolSearch } -> + | {WidgetTypes = Routing.WidgetTypes.ProtocolSearch } -> Protocol.SearchContainer.Main model dispatch ] ] From 31a0d134a7c53570683d201a86af1847b345340c Mon Sep 17 00:00:00 2001 From: patrick blume Date: Thu, 19 Dec 2024 14:31:15 +0100 Subject: [PATCH 20/23] Undo changes to pagestate --- src/Client/Index.fs | 2 +- src/Client/Init.fs | 1 + src/Client/MainComponents/Widgets.fs | 2 +- src/Client/Model.fs | 24 ++++++++++++------- src/Client/Pages/FilePicker/FilePickerView.fs | 2 +- .../Pages/ProtocolTemplates/ProtocolSearch.fs | 8 +++---- .../Pages/ProtocolTemplates/ProtocolState.fs | 3 +-- .../SelectiveTemplateFromDB.fs | 2 +- src/Client/Routing.fs | 2 +- src/Client/SharedComponents/NavbarBurger.fs | 2 +- src/Client/SidebarComponents/Tabs.fs | 18 +++++++------- src/Client/Views/MainPageViews.fs | 14 +++++------ src/Client/Views/SidebarView.fs | 16 ++++++------- 13 files changed, 52 insertions(+), 44 deletions(-) diff --git a/src/Client/Index.fs b/src/Client/Index.fs index 55cfbae6..09e6e976 100644 --- a/src/Client/Index.fs +++ b/src/Client/Index.fs @@ -31,7 +31,7 @@ let View (model : Model) (dispatch : Msg -> unit) = prop.className "flex w-full overflow-auto h-screen" prop.children [ Modals.ModalProvider.Main(model, dispatch) - match model.ProtocolState.IsHome, model.PersistentStorageState.Host with + match model.PageState.IsHome, model.PersistentStorageState.Host with | false, _ -> View.MainPageView.Main(model, dispatch) | _, Some Swatehost.Excel -> diff --git a/src/Client/Init.fs b/src/Client/Init.fs index 6f202d83..95e54fda 100644 --- a/src/Client/Init.fs +++ b/src/Client/Init.fs @@ -9,6 +9,7 @@ open Update let initialModel = { + PageState = PageState .init() PersistentStorageState = PersistentStorageState .init() DevState = DevState .init() TermSearchState = TermSearch.Model .init() diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index a73e5ca3..f1cecc2b 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -231,7 +231,7 @@ type Widget = let content = let switchContent = - if model.ProtocolState.TemplatesSelected.Length > 0 && model.ProtocolState.WidgetTypes = Routing.WidgetTypes.Protocol then + if model.ProtocolState.TemplatesSelected.Length > 0 && model.PageState.SidebarPage = Routing.SidebarPage.Protocol then insertContent() else selectContent() Html.div [ diff --git a/src/Client/Model.fs b/src/Client/Model.fs index e9e54639..1508b1f1 100644 --- a/src/Client/Model.fs +++ b/src/Client/Model.fs @@ -125,6 +125,20 @@ type PersistentStorageState = { HasOntologiesLoaded = false } +type PageState = { + SidebarPage : Routing.SidebarPage + MainPage: Routing.MainPage +} with + static member init () = + { + SidebarPage = SidebarPage.BuildingBlock + MainPage = MainPage.Default + } + member this.IsHome = + match this.MainPage with + | MainPage.Default -> true + | _ -> false + module FilePicker = type Model = { FileNames : (int*string) list @@ -234,8 +248,6 @@ module Protocol = // ------ Protocol from Database ------ TemplatesSelected : ARCtrl.Template list Templates : ARCtrl.Template [] - WidgetTypes : Routing.WidgetTypes - MainPage : Routing.MainPage } with static member init () = { // Client @@ -244,13 +256,7 @@ module Protocol = TemplatesSelected = [] // ------ Protocol from Database ------ Templates = [||] - WidgetTypes = WidgetTypes.BuildingBlock - MainPage = MainPage.Default } - member this.IsHome = - match this.MainPage with - | MainPage.Default -> true - | _ -> false type RequestBuildingBlockInfoStates = | Inactive @@ -263,6 +269,8 @@ type RequestBuildingBlockInfoStates = | RequestDataBaseInformation -> "Search Database " type Model = { + ///PageState + PageState : PageState ///Data that needs to be persistent once loaded PersistentStorageState : PersistentStorageState ///Error handling, Logging, etc. diff --git a/src/Client/Pages/FilePicker/FilePickerView.fs b/src/Client/Pages/FilePicker/FilePickerView.fs index cfdafa8e..4dbf8fe7 100644 --- a/src/Client/Pages/FilePicker/FilePickerView.fs +++ b/src/Client/Pages/FilePicker/FilePickerView.fs @@ -14,7 +14,7 @@ let update (filePickerMsg:FilePicker.Msg) (state: FilePicker.Model) (model: Mode let nextModel = { model with Model.FilePickerState.FileNames = fileNames |> List.mapi (fun i x -> i + 1, x) - Model.ProtocolState.WidgetTypes = Routing.WidgetTypes.FilePicker + Model.PageState.SidebarPage = Routing.SidebarPage.FilePicker } let nextCmd = UpdateModel nextModel|> Cmd.ofMsg state, nextCmd diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs index 391e5e34..0de05a37 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs @@ -15,14 +15,14 @@ module private HelperProtocolSearch = prop.children [ Html.ul [ Html.li [Html.a [ - prop.onClick (fun _ -> UpdateModel {model with Model.ProtocolState.WidgetTypes = Routing.WidgetTypes.Protocol} |> dispatch) - prop.text (Routing.WidgetTypes.Protocol.AsStringRdbl) + prop.onClick (fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.Protocol} |> dispatch) + prop.text (Routing.SidebarPage.Protocol.AsStringRdbl) ]] Html.li [ prop.className "is-active" prop.children (Html.a [ - prop.onClick (fun _ -> UpdateModel {model with Model.ProtocolState.WidgetTypes = Routing.WidgetTypes.ProtocolSearch} |> dispatch) - prop.text (Routing.WidgetTypes.ProtocolSearch.AsStringRdbl) + prop.onClick (fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) + prop.text (Routing.SidebarPage.ProtocolSearch.AsStringRdbl) ]) ] ] diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs index 2c794e83..f834e52d 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs @@ -58,7 +58,7 @@ module Protocol = let nextModel = { model with Model.ProtocolState.TemplatesSelected = prots - Model.ProtocolState.WidgetTypes = Routing.WidgetTypes.Protocol + Model.PageState.SidebarPage = Routing.SidebarPage.Protocol } state, Cmd.ofMsg (UpdateModel nextModel) | AddProtocol prot -> @@ -70,7 +70,6 @@ module Protocol = let nextState = { state with TemplatesSelected = templates - WidgetTypes = Routing.WidgetTypes.ProtocolSearch } nextState, Cmd.none | ProtocolIncreaseTimesUsed templateId -> diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index 2b4c055e..a66be425 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -36,7 +36,7 @@ type SelectiveTemplateFromDB = /// static member ToProtocolSearchElement(model: Model) dispatch = Daisy.button.button [ - prop.onClick(fun _ -> UpdateModel {model with Model.ProtocolState.WidgetTypes = Routing.WidgetTypes.ProtocolSearch} |> dispatch) + prop.onClick(fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) button.primary button.block prop.text "Browse database" diff --git a/src/Client/Routing.fs b/src/Client/Routing.fs index fd0dd18c..11047b57 100644 --- a/src/Client/Routing.fs +++ b/src/Client/Routing.fs @@ -4,7 +4,7 @@ open Elmish.UrlParser open Feliz [] -type WidgetTypes = +type SidebarPage = | BuildingBlock | TermSearch | FilePicker diff --git a/src/Client/SharedComponents/NavbarBurger.fs b/src/Client/SharedComponents/NavbarBurger.fs index 372533bb..09a3f93e 100644 --- a/src/Client/SharedComponents/NavbarBurger.fs +++ b/src/Client/SharedComponents/NavbarBurger.fs @@ -52,7 +52,7 @@ type NavbarBurger = [] static member private Dropdown(isOpen, setIsOpen, model: Model.Model, dispatch) = - let navigateTo = fun (mainPage: Routing.MainPage) -> {model with Model.ProtocolState.MainPage = mainPage} |> Messages.UpdateModel |> dispatch + let navigateTo = fun (mainPage: Routing.MainPage) -> {model with Model.PageState.MainPage = mainPage} |> Messages.UpdateModel |> dispatch Components.BaseDropdown.Main( isOpen, setIsOpen, diff --git a/src/Client/SidebarComponents/Tabs.fs b/src/Client/SidebarComponents/Tabs.fs index c4b54928..3957716e 100644 --- a/src/Client/SidebarComponents/Tabs.fs +++ b/src/Client/SidebarComponents/Tabs.fs @@ -9,12 +9,12 @@ open Feliz.DaisyUI type Tabs = - static member private NavigationTab (pageLink: Routing.WidgetTypes) (model:Model) (dispatch:Msg-> unit) = - let isActive = pageLink = model.ProtocolState.WidgetTypes + static member private NavigationTab (pageLink: Routing.SidebarPage) (model:Model) (dispatch:Msg-> unit) = + let isActive = pageLink = model.PageState.SidebarPage Daisy.tab [ if isActive then tab.active prop.className "navigation" // this class does not do anything, but disables styling. - prop.onClick (fun e -> e.preventDefault(); UpdateModel { model with Model.ProtocolState.WidgetTypes = pageLink } |> dispatch) + prop.onClick (fun e -> e.preventDefault(); UpdateModel { model with Model.PageState.SidebarPage = pageLink } |> dispatch) prop.children (pageLink.AsIcon()) ] @@ -25,11 +25,11 @@ type Tabs = tabs.boxed prop.className "w-full" prop.children [ - Tabs.NavigationTab Routing.WidgetTypes.BuildingBlock model dispatch - Tabs.NavigationTab Routing.WidgetTypes.TermSearch model dispatch - Tabs.NavigationTab Routing.WidgetTypes.Protocol model dispatch - Tabs.NavigationTab Routing.WidgetTypes.FilePicker model dispatch - Tabs.NavigationTab Routing.WidgetTypes.DataAnnotator model dispatch - Tabs.NavigationTab Routing.WidgetTypes.JsonExport model dispatch + Tabs.NavigationTab Routing.SidebarPage.BuildingBlock model dispatch + Tabs.NavigationTab Routing.SidebarPage.TermSearch model dispatch + Tabs.NavigationTab Routing.SidebarPage.Protocol model dispatch + Tabs.NavigationTab Routing.SidebarPage.FilePicker model dispatch + Tabs.NavigationTab Routing.SidebarPage.DataAnnotator model dispatch + Tabs.NavigationTab Routing.SidebarPage.JsonExport model dispatch ] ] \ No newline at end of file diff --git a/src/Client/Views/MainPageViews.fs b/src/Client/Views/MainPageViews.fs index 6ed7ac8e..fa757a61 100644 --- a/src/Client/Views/MainPageViews.fs +++ b/src/Client/Views/MainPageViews.fs @@ -14,7 +14,7 @@ open MainPageUtil type MainPageView = static member DrawerSideContentItem (model: Model.Model, route: Routing.MainPage, onclick) = - let isActive = model.ProtocolState.MainPage = route + let isActive = model.PageState.MainPage = route Html.li [ prop.onClick onclick prop.children [ @@ -35,7 +35,7 @@ type MainPageView = prop.ariaLabel "Back to spreadsheet view" button.link button.sm - prop.onClick (fun _ -> UpdateModel {model with Model.ProtocolState.MainPage = Routing.MainPage.Default} |> dispatch) + prop.onClick (fun _ -> UpdateModel {model with Model.PageState.MainPage = Routing.MainPage.Default} |> dispatch) prop.children [ Html.i [prop.className "fa-solid fa-arrow-left"] Html.span "Back" @@ -44,9 +44,9 @@ type MainPageView = Html.ul [ prop.className "menu gap-y-1" prop.children [ - MainPageView.DrawerSideContentItem(model, Routing.MainPage.Settings, fun _ -> UpdateModel {model with Model.ProtocolState.MainPage = Routing.MainPage.Settings} |> dispatch) - MainPageView.DrawerSideContentItem(model, Routing.MainPage.About, fun _ -> UpdateModel {model with Model.ProtocolState.MainPage = Routing.MainPage.About} |> dispatch) - MainPageView.DrawerSideContentItem(model, Routing.MainPage.PrivacyPolicy, fun _ -> UpdateModel {model with Model.ProtocolState.MainPage = Routing.MainPage.PrivacyPolicy} |> dispatch) + MainPageView.DrawerSideContentItem(model, Routing.MainPage.Settings, fun _ -> UpdateModel {model with Model.PageState.MainPage = Routing.MainPage.Settings} |> dispatch) + MainPageView.DrawerSideContentItem(model, Routing.MainPage.About, fun _ -> UpdateModel {model with Model.PageState.MainPage = Routing.MainPage.About} |> dispatch) + MainPageView.DrawerSideContentItem(model, Routing.MainPage.PrivacyPolicy, fun _ -> UpdateModel {model with Model.PageState.MainPage = Routing.MainPage.PrivacyPolicy} |> dispatch) ] ] ] @@ -78,7 +78,7 @@ type MainPageView = Html.div [ prop.ariaLabel "logo" - prop.onClick (fun _ -> UpdateModel {model with Model.ProtocolState.MainPage = Routing.MainPage.Default} |> dispatch) + prop.onClick (fun _ -> UpdateModel {model with Model.PageState.MainPage = Routing.MainPage.Default} |> dispatch) prop.className "cursor-pointer" prop.children [ Html.img [ @@ -90,7 +90,7 @@ type MainPageView = ] static member MainContent(model: Model.Model, dispatch) = - match model.ProtocolState.MainPage with + match model.PageState.MainPage with | Routing.MainPage.Settings -> Pages.Settings.Main(model, dispatch) | Routing.MainPage.About -> diff --git a/src/Client/Views/SidebarView.fs b/src/Client/Views/SidebarView.fs index 5eb06235..5bd733df 100644 --- a/src/Client/Views/SidebarView.fs +++ b/src/Client/Views/SidebarView.fs @@ -14,26 +14,26 @@ type SidebarView = Html.div [ prop.className "grow overflow-y-auto" prop.children [ - match model.ProtocolState with - | {WidgetTypes = Routing.WidgetTypes.BuildingBlock } -> + match model.PageState with + | {SidebarPage = Routing.SidebarPage.BuildingBlock } -> BuildingBlock.Core.addBuildingBlockComponent model dispatch - | {WidgetTypes = Routing.WidgetTypes.TermSearch } -> + | {SidebarPage = Routing.SidebarPage.TermSearch } -> TermSearch.Main (model, dispatch) - | {WidgetTypes = Routing.WidgetTypes.FilePicker } -> + | {SidebarPage = Routing.SidebarPage.FilePicker } -> FilePicker.filePickerComponent model dispatch - | {WidgetTypes = Routing.WidgetTypes.Protocol } -> + | {SidebarPage = Routing.SidebarPage.Protocol } -> Protocol.Templates.Main (model, dispatch) - | {WidgetTypes = Routing.WidgetTypes.DataAnnotator } -> + | {SidebarPage = Routing.SidebarPage.DataAnnotator } -> Pages.DataAnnotator.Main(model, dispatch) - | {WidgetTypes = Routing.WidgetTypes.JsonExport } -> + | {SidebarPage = Routing.SidebarPage.JsonExport } -> JsonExporter.Core.FileExporter.Main(model, dispatch) - | {WidgetTypes = Routing.WidgetTypes.ProtocolSearch } -> + | {SidebarPage = Routing.SidebarPage.ProtocolSearch } -> Protocol.SearchContainer.Main model dispatch ] ] From 286e7dcc2a8ff4cf63bdd10a258131da7e91d9d6 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Thu, 19 Dec 2024 15:41:37 +0100 Subject: [PATCH 21/23] Implement review changes --- src/Client/MainComponents/Widgets.fs | 7 ++-- src/Client/Model.fs | 2 ++ .../Pages/ProtocolTemplates/ProtocolSearch.fs | 3 +- .../Pages/ProtocolTemplates/ProtocolState.fs | 5 +-- .../SelectiveTemplateFromDB.fs | 2 +- src/Client/Routing.fs | 4 --- src/Client/Views/SidebarView.fs | 32 +++++++++---------- 7 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index f1cecc2b..a4bc4699 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -231,9 +231,10 @@ type Widget = let content = let switchContent = - if model.ProtocolState.TemplatesSelected.Length > 0 && model.PageState.SidebarPage = Routing.SidebarPage.Protocol then - insertContent() - else selectContent() + if model.ProtocolState.TemplatesSelected.Length > 0 && not model.ProtocolState.IsProtocolSearch then + insertContent () + else + selectContent () Html.div [ prop.className "flex flex-col gap-4 @container/templateWidget" prop.children switchContent diff --git a/src/Client/Model.fs b/src/Client/Model.fs index 1508b1f1..2bc98497 100644 --- a/src/Client/Model.fs +++ b/src/Client/Model.fs @@ -248,6 +248,7 @@ module Protocol = // ------ Protocol from Database ------ TemplatesSelected : ARCtrl.Template list Templates : ARCtrl.Template [] + IsProtocolSearch : bool } with static member init () = { // Client @@ -256,6 +257,7 @@ module Protocol = TemplatesSelected = [] // ------ Protocol from Database ------ Templates = [||] + IsProtocolSearch = false } type RequestBuildingBlockInfoStates = diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs index 0de05a37..283dd914 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs @@ -21,8 +21,7 @@ module private HelperProtocolSearch = Html.li [ prop.className "is-active" prop.children (Html.a [ - prop.onClick (fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) - prop.text (Routing.SidebarPage.ProtocolSearch.AsStringRdbl) + prop.onClick (fun _ -> UpdateModel {model with Model.ProtocolState.IsProtocolSearch = true} |> dispatch) ]) ] ] diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs index f834e52d..ef70d720 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs @@ -54,11 +54,11 @@ module Protocol = } nextState, Cmd.none | SelectProtocols prots -> - log "SelectProtocols" let nextModel = { model with Model.ProtocolState.TemplatesSelected = prots - Model.PageState.SidebarPage = Routing.SidebarPage.Protocol + Model.ProtocolState.IsProtocolSearch = false + Model.PageState.SidebarPage = Routing.SidebarPage.Protocol } state, Cmd.ofMsg (UpdateModel nextModel) | AddProtocol prot -> @@ -70,6 +70,7 @@ module Protocol = let nextState = { state with TemplatesSelected = templates + IsProtocolSearch = true } nextState, Cmd.none | ProtocolIncreaseTimesUsed templateId -> diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index a66be425..3416aeec 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -36,7 +36,7 @@ type SelectiveTemplateFromDB = /// static member ToProtocolSearchElement(model: Model) dispatch = Daisy.button.button [ - prop.onClick(fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) + prop.onClick(fun _ -> UpdateModel {model with Model.ProtocolState.IsProtocolSearch = true} |> dispatch) button.primary button.block prop.text "Browse database" diff --git a/src/Client/Routing.fs b/src/Client/Routing.fs index 11047b57..e11f166c 100644 --- a/src/Client/Routing.fs +++ b/src/Client/Routing.fs @@ -9,7 +9,6 @@ type SidebarPage = | TermSearch | FilePicker | Protocol - | ProtocolSearch | JsonExport | DataAnnotator @@ -19,7 +18,6 @@ type SidebarPage = | TermSearch -> "Terms" | FilePicker -> "File Picker" | Protocol -> "Templates" - | ProtocolSearch -> "Template Search" | JsonExport -> "Json Export" | DataAnnotator -> "Data Annotator" @@ -37,8 +35,6 @@ type SidebarPage = createElem [ Html.i [prop.className "fa-solid fa-circle-plus" ]; Html.i [prop.className "fa-solid fa-table-columns" ]] | Protocol -> createElem [ Html.i [prop.className "fa-solid fa-circle-plus" ];Html.i [prop.className "fa-solid fa-table" ]] - | ProtocolSearch -> - createElem [ Html.i [prop.className "fa-solid fa-table" ]; Html.i [prop.className "fa-solid fa-magnifying-glass" ]] | JsonExport -> createElem [ Html.i [prop.className "fa-solid fa-file-export" ]] | FilePicker -> diff --git a/src/Client/Views/SidebarView.fs b/src/Client/Views/SidebarView.fs index 5bd733df..32d3d04b 100644 --- a/src/Client/Views/SidebarView.fs +++ b/src/Client/Views/SidebarView.fs @@ -14,27 +14,27 @@ type SidebarView = Html.div [ prop.className "grow overflow-y-auto" prop.children [ - match model.PageState with - | {SidebarPage = Routing.SidebarPage.BuildingBlock } -> - BuildingBlock.Core.addBuildingBlockComponent model dispatch - - | {SidebarPage = Routing.SidebarPage.TermSearch } -> - TermSearch.Main (model, dispatch) + if model.ProtocolState.IsProtocolSearch then + Protocol.SearchContainer.Main model dispatch + else + match model.PageState with + | {SidebarPage = Routing.SidebarPage.BuildingBlock } -> + BuildingBlock.Core.addBuildingBlockComponent model dispatch - | {SidebarPage = Routing.SidebarPage.FilePicker } -> - FilePicker.filePickerComponent model dispatch + | {SidebarPage = Routing.SidebarPage.TermSearch } -> + TermSearch.Main (model, dispatch) - | {SidebarPage = Routing.SidebarPage.Protocol } -> - Protocol.Templates.Main (model, dispatch) + | {SidebarPage = Routing.SidebarPage.FilePicker } -> + FilePicker.filePickerComponent model dispatch - | {SidebarPage = Routing.SidebarPage.DataAnnotator } -> - Pages.DataAnnotator.Main(model, dispatch) + | {SidebarPage = Routing.SidebarPage.Protocol } -> + Protocol.Templates.Main (model, dispatch) - | {SidebarPage = Routing.SidebarPage.JsonExport } -> - JsonExporter.Core.FileExporter.Main(model, dispatch) + | {SidebarPage = Routing.SidebarPage.DataAnnotator } -> + Pages.DataAnnotator.Main(model, dispatch) - | {SidebarPage = Routing.SidebarPage.ProtocolSearch } -> - Protocol.SearchContainer.Main model dispatch + | {SidebarPage = Routing.SidebarPage.JsonExport } -> + JsonExporter.Core.FileExporter.Main(model, dispatch) ] ] From 02780e21eeb0334884dbca08ec7e5f99328c5dfe Mon Sep 17 00:00:00 2001 From: patrick blume Date: Fri, 20 Dec 2024 12:36:44 +0100 Subject: [PATCH 22/23] implement review requests --- src/Client/MainComponents/Widgets.fs | 6 ++-- .../Pages/ProtocolTemplates/ProtocolState.fs | 1 - .../Pages/ProtocolTemplates/ProtocolView.fs | 9 ++++-- src/Client/Views/SidebarView.fs | 29 +++++++++---------- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index a4bc4699..a0db0352 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -231,10 +231,10 @@ type Widget = let content = let switchContent = - if model.ProtocolState.TemplatesSelected.Length > 0 && not model.ProtocolState.IsProtocolSearch then - insertContent () - else + if model.ProtocolState.IsProtocolSearch then selectContent () + else + insertContent () Html.div [ prop.className "flex flex-col gap-4 @container/templateWidget" prop.children switchContent diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs index ef70d720..c9cc9a72 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs @@ -70,7 +70,6 @@ module Protocol = let nextState = { state with TemplatesSelected = templates - IsProtocolSearch = true } nextState, Cmd.none | ProtocolIncreaseTimesUsed templateId -> diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolView.fs b/src/Client/Pages/ProtocolTemplates/ProtocolView.fs index a3d92f36..74893703 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolView.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolView.fs @@ -38,9 +38,12 @@ type Templates = // Box 1 SidebarComponents.SidebarLayout.Description "Add template from database." - SidebarComponents.SidebarLayout.LogicContainer [ - Modals.SelectiveTemplateFromDB.Main(model, dispatch) - ] + if model.ProtocolState.IsProtocolSearch then + Protocol.SearchContainer.Main model dispatch + else + SidebarComponents.SidebarLayout.LogicContainer [ + Modals.SelectiveTemplateFromDB.Main(model, dispatch) + ] // Box 2 SidebarComponents.SidebarLayout.Description (Html.p [ diff --git a/src/Client/Views/SidebarView.fs b/src/Client/Views/SidebarView.fs index 32d3d04b..d5c74071 100644 --- a/src/Client/Views/SidebarView.fs +++ b/src/Client/Views/SidebarView.fs @@ -14,27 +14,24 @@ type SidebarView = Html.div [ prop.className "grow overflow-y-auto" prop.children [ - if model.ProtocolState.IsProtocolSearch then - Protocol.SearchContainer.Main model dispatch - else - match model.PageState with - | {SidebarPage = Routing.SidebarPage.BuildingBlock } -> - BuildingBlock.Core.addBuildingBlockComponent model dispatch + match model.PageState with + | {SidebarPage = Routing.SidebarPage.BuildingBlock } -> + BuildingBlock.Core.addBuildingBlockComponent model dispatch - | {SidebarPage = Routing.SidebarPage.TermSearch } -> - TermSearch.Main (model, dispatch) + | {SidebarPage = Routing.SidebarPage.TermSearch } -> + TermSearch.Main (model, dispatch) - | {SidebarPage = Routing.SidebarPage.FilePicker } -> - FilePicker.filePickerComponent model dispatch + | {SidebarPage = Routing.SidebarPage.FilePicker } -> + FilePicker.filePickerComponent model dispatch - | {SidebarPage = Routing.SidebarPage.Protocol } -> - Protocol.Templates.Main (model, dispatch) + | {SidebarPage = Routing.SidebarPage.Protocol } -> + Protocol.Templates.Main (model, dispatch) - | {SidebarPage = Routing.SidebarPage.DataAnnotator } -> - Pages.DataAnnotator.Main(model, dispatch) + | {SidebarPage = Routing.SidebarPage.DataAnnotator } -> + Pages.DataAnnotator.Main(model, dispatch) - | {SidebarPage = Routing.SidebarPage.JsonExport } -> - JsonExporter.Core.FileExporter.Main(model, dispatch) + | {SidebarPage = Routing.SidebarPage.JsonExport } -> + JsonExporter.Core.FileExporter.Main(model, dispatch) ] ] From 2046195c4c7e2325a45316828f02853be2fcfde0 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Sat, 21 Dec 2024 02:03:36 +0100 Subject: [PATCH 23/23] Implement review changes --- src/Client/MainComponents/Widgets.fs | 14 +++++--- src/Client/Modals/SelectiveImportModal.fs | 4 +-- src/Client/Model.fs | 2 -- .../Pages/ProtocolTemplates/ProtocolSearch.fs | 18 +++++----- .../ProtocolSearchViewComponent.fs | 25 +++++++++---- .../Pages/ProtocolTemplates/ProtocolState.fs | 4 +-- .../Pages/ProtocolTemplates/ProtocolView.fs | 11 ++++-- .../SelectiveTemplateFromDB.fs | 35 ++++++++++--------- src/Client/Routing.fs | 1 - src/Client/Types.fs | 4 +-- src/Client/Views/SpreadsheetView.fs | 4 ++- 11 files changed, 72 insertions(+), 50 deletions(-) diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index a0db0352..b62f272a 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -209,29 +209,33 @@ type Widget = Widget.Base(content, prefix, rmv, help) [] - static member Templates (model: Model, dispatch, rmv: MouseEvent -> unit) = + static member Templates (model: Model, importTypeState, setImportTypeState, dispatch, rmv: MouseEvent -> unit) = let templates = model.ProtocolState.Templates let config, setConfig = React.useState(TemplateFilterConfig.init) + let isProtocolSearch, setProtocolSearch = React.useState(true) let filteredTemplates = Protocol.Search.filterTemplates (templates, config) React.useEffectOnce(fun _ -> Messages.Protocol.GetAllProtocolsRequest |> Messages.ProtocolMsg |> dispatch) let selectContent() = [ Protocol.Search.FileSortElement(model, config, setConfig, "@md/templateWidget:grid-cols-3") - ModalElements.Box("Selected Templates", "fa-solid fa-cog", Search.SelectedTemplatesElement model dispatch) - Protocol.Search.Component (filteredTemplates, model, dispatch, length.px 350) + ModalElements.Box("Selected Templates", "fa-solid fa-cog", Search.SelectedTemplatesElement model setProtocolSearch importTypeState setImportTypeState dispatch) + Protocol.Search.Component (filteredTemplates, model, setProtocolSearch, importTypeState, setImportTypeState, dispatch, length.px 350) ] let insertContent() = [ Html.div [ prop.style [style.maxHeight (length.px 350); style.overflow.auto] prop.className "flex flex-col gap-2" - prop.children (SelectiveTemplateFromDB.Main(model, dispatch)) + prop.children (SelectiveTemplateFromDB.Main(model, true, setProtocolSearch, importTypeState, setImportTypeState, dispatch)) ] ] + //if model.ProtocolState.TemplatesSelected.Length = 0 then + // setProtocolSearch false + let content = let switchContent = - if model.ProtocolState.IsProtocolSearch then + if isProtocolSearch then selectContent () else insertContent () diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index ee6600b4..e27dc7cf 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -177,7 +177,7 @@ type SelectiveImportModal = tables |> Array.ofSeq |> Array.map (fun t -> Array.init t.Columns.Length (fun _ -> true)) - let importDataState, setImportDataState = React.useState(SelectiveImportModalState.init columns) + let importDataState, setImportDataState = React.useState(SelectiveImportModalState.init()) let setMetadataImport = fun b -> if b then { @@ -186,7 +186,7 @@ type SelectiveImportModal = ImportTables = [for ti in 0 .. tables.Count-1 do {ImportTable.Index = ti; ImportTable.FullImport = true}] } |> setImportDataState else - SelectiveImportModalState.init(columns) |> setImportDataState + SelectiveImportModalState.init() |> setImportDataState let addTableImport = fun (i: int) (fullImport: bool) -> let newImportTable: ImportTable = {Index = i; FullImport = fullImport} let newImportTables = newImportTable::importDataState.ImportTables |> List.distinct diff --git a/src/Client/Model.fs b/src/Client/Model.fs index 2bc98497..1508b1f1 100644 --- a/src/Client/Model.fs +++ b/src/Client/Model.fs @@ -248,7 +248,6 @@ module Protocol = // ------ Protocol from Database ------ TemplatesSelected : ARCtrl.Template list Templates : ARCtrl.Template [] - IsProtocolSearch : bool } with static member init () = { // Client @@ -257,7 +256,6 @@ module Protocol = TemplatesSelected = [] // ------ Protocol from Database ------ Templates = [||] - IsProtocolSearch = false } type RequestBuildingBlockInfoStates = diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs index 283dd914..91bedcb5 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs @@ -10,7 +10,7 @@ open Modals module private HelperProtocolSearch = - let breadcrumbEle (model:Model) dispatch = + let breadcrumbEle (model:Model) setIsProtocolSearch dispatch = Daisy.breadcrumbs [ prop.children [ Html.ul [ @@ -21,7 +21,9 @@ module private HelperProtocolSearch = Html.li [ prop.className "is-active" prop.children (Html.a [ - prop.onClick (fun _ -> UpdateModel {model with Model.ProtocolState.IsProtocolSearch = true} |> dispatch) + prop.onClick (fun _ -> + setIsProtocolSearch true + UpdateModel model |> dispatch) ]) ] ] @@ -33,7 +35,7 @@ open Fable.Core type SearchContainer = [] - static member Main (model:Model) dispatch = + static member Main (model:Model) setProtocolSearch importTypeState setImportTypeState dispatch = let templates, setTemplates = React.useState(model.ProtocolState.Templates) let config, setConfig = React.useState(TemplateFilterConfig.init) let filteredTemplates = Protocol.Search.filterTemplates (templates, config) @@ -46,7 +48,7 @@ type SearchContainer = // https://keycode.info/ prop.onKeyDown (fun k -> if k.key = "Enter" then k.preventDefault()) prop.children [ - HelperProtocolSearch.breadcrumbEle model dispatch + HelperProtocolSearch.breadcrumbEle model setProtocolSearch dispatch if isEmpty && not isLoading then Html.p [prop.className "text-error text-sm"; prop.text "No templates were found. This can happen if connection to the server was lost. You can try reload this site or contact a developer."] @@ -56,10 +58,10 @@ type SearchContainer = Html.div [ prop.className "relative flex p-4 shadow-md gap-4 flex-col" prop.children [ - Protocol.Search.InfoField() - Protocol.Search.FileSortElement(model, config, setConfig) - ModalElements.Box("Selected Templates", "fa-solid fa-cog", Search.SelectedTemplatesElement model dispatch) - Protocol.Search.Component (filteredTemplates, model, dispatch) + Protocol.Search.InfoField() + Protocol.Search.FileSortElement(model, config, setConfig) + ModalElements.Box("Selected Templates", "fa-solid fa-cog", Search.SelectedTemplatesElement model setProtocolSearch importTypeState setImportTypeState dispatch) + Protocol.Search.Component (filteredTemplates, model, setProtocolSearch, importTypeState, setImportTypeState, dispatch) ] ] ] diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs b/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs index 2b6ec09b..2f2d62db 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs @@ -10,6 +10,8 @@ open Feliz.DaisyUI open Modals +open JsonImport + /// Fields of Template that can be searched [] type SearchFields = @@ -321,7 +323,7 @@ module ComponentAux = let createAuthorsStringHelper (authors: ResizeArray) = authors |> Seq.map createAuthorStringHelper |> String.concat ", " - let protocolElement i (template: ARCtrl.Template) (isShown: bool) (setIsShown: bool -> unit) (model: Model) dispatch = + let protocolElement i (template: ARCtrl.Template) (isShown: bool) (setIsShown: bool -> unit) (model: Model) setProtocolSearch (importTypeState: SelectiveImportModalState) setImportTypeState dispatch = [ Html.tr [ prop.key $"{i}_{template.Id}" @@ -394,6 +396,9 @@ module ComponentAux = Daisy.button.a [ button.sm prop.onClick (fun _ -> + setProtocolSearch false + let columns = [|Array.init template.Table.Columns.Length (fun _ -> true)|] + {importTypeState with SelectedColumns = columns} |> setImportTypeState SelectProtocols [template] |> ProtocolMsg |> dispatch ) button.wide @@ -562,13 +567,20 @@ type Search = Html.div [ yield! [prop.text $"\"{names.[i]}\""]] ] ] - static member private selectTemplatesButton model dispatch = + + static member private selectTemplatesButton model setProtocolSearch importTypeState setImportTypeState dispatch = Html.div [ prop.className "flex justify-center gap-2" prop.children [ Daisy.button.a [ button.sm prop.onClick (fun _ -> + setProtocolSearch false + let columns = + model.ProtocolState.TemplatesSelected + |> Array.ofSeq + |> Array.map (fun t -> Array.init t.Table.Columns.Length (fun _ -> true)) + {importTypeState with SelectedColumns = columns} |> setImportTypeState SelectProtocols model.ProtocolState.TemplatesSelected |> ProtocolMsg |> dispatch ) button.wide @@ -580,7 +592,8 @@ type Search = ] ] ] - static member SelectedTemplatesElement model dispatch = + + static member SelectedTemplatesElement model setProtocolSearch importTypeState setImportTypeState dispatch = Html.div [ prop.style [style.overflowX.auto; style.marginBottom (length.rem 1)] prop.children [ @@ -589,12 +602,12 @@ type Search = Search.displayTemplateNames model ] ] - Search.selectTemplatesButton model dispatch + Search.selectTemplatesButton model setProtocolSearch importTypeState setImportTypeState dispatch ] ] [] - static member Component (templates, model: Model, dispatch, ?maxheight: Styles.ICssUnit) = + static member Component (templates, model: Model, setProtocolSearch, (importTypeState: SelectiveImportModalState), setImportTypeState, dispatch, ?maxheight: Styles.ICssUnit) = let maxheight = defaultArg maxheight (length.px 600) let showIds, setShowIds = React.useState(fun _ -> []) Html.div [ @@ -639,7 +652,7 @@ type Search = let setIsShown (show: bool) = if show then i::showIds |> setShowIds else showIds |> List.filter (fun id -> id <> i) |> setShowIds yield! - protocolElement i templates.[i] isShown setIsShown model dispatch + protocolElement i templates.[i] isShown setIsShown model setProtocolSearch importTypeState setImportTypeState dispatch ] ] ] diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs index c9cc9a72..13538937 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs @@ -57,8 +57,6 @@ module Protocol = let nextModel = { model with Model.ProtocolState.TemplatesSelected = prots - Model.ProtocolState.IsProtocolSearch = false - Model.PageState.SidebarPage = Routing.SidebarPage.Protocol } state, Cmd.ofMsg (UpdateModel nextModel) | AddProtocol prot -> @@ -69,7 +67,7 @@ module Protocol = prot::model.ProtocolState.TemplatesSelected let nextState = { state with - TemplatesSelected = templates + TemplatesSelected = templates } nextState, Cmd.none | ProtocolIncreaseTimesUsed templateId -> diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolView.fs b/src/Client/Pages/ProtocolTemplates/ProtocolView.fs index 74893703..11913eed 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolView.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolView.fs @@ -21,9 +21,14 @@ open Feliz open Feliz.DaisyUI open ARCtrl +open JsonImport + type Templates = + [] static member Main (model:Model, dispatch) = + let isProtocolSearch, setProtocolSearch = React.useState(false) + let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init()) SidebarComponents.SidebarLayout.Container [ SidebarComponents.SidebarLayout.Header "Templates" @@ -38,11 +43,11 @@ type Templates = // Box 1 SidebarComponents.SidebarLayout.Description "Add template from database." - if model.ProtocolState.IsProtocolSearch then - Protocol.SearchContainer.Main model dispatch + if isProtocolSearch then + Protocol.SearchContainer.Main model setProtocolSearch importTypeState setImportTypeState dispatch else SidebarComponents.SidebarLayout.LogicContainer [ - Modals.SelectiveTemplateFromDB.Main(model, dispatch) + Modals.SelectiveTemplateFromDB.Main(model, false, setProtocolSearch, importTypeState, setImportTypeState, dispatch) ] // Box 2 diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index 3416aeec..10da4753 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -34,9 +34,11 @@ type SelectiveTemplateFromDB = /// /// /// - static member ToProtocolSearchElement(model: Model) dispatch = + static member ToProtocolSearchElement(model: Model) setProtocolSearch dispatch = Daisy.button.button [ - prop.onClick(fun _ -> UpdateModel {model with Model.ProtocolState.IsProtocolSearch = true} |> dispatch) + prop.onClick(fun _ -> + setProtocolSearch true + UpdateModel model |> dispatch) button.primary button.block prop.text "Browse database" @@ -51,7 +53,6 @@ type SelectiveTemplateFromDB = /// /// /// - [] static member DisplaySelectedProtocolElements(selectedTemplate: Template option, templateIndex, selectionInformation: SelectiveImportModalState, setSelectedColumns: SelectiveImportModalState -> unit, dispatch, ?hasIcon: bool) = let hasIcon = defaultArg hasIcon true Html.div [ @@ -75,7 +76,7 @@ type SelectiveTemplateFromDB = /// /// /// - static member AddFromDBToTableButton name (model: Model) selectionInformation importType useTemplateName dispatch = + static member AddFromDBToTableButton name (model: Model) selectionInformation importType setImportType useTemplateName protocolSearchState setProtocolSearch dispatch = let addTemplate (model: Model, selectedColumns) = let template = if model.ProtocolState.TemplatesSelected.Length = 0 then @@ -91,7 +92,10 @@ type SelectiveTemplateFromDB = if model.ProtocolState.TemplatesSelected.Length > 0 then Daisy.button.a [ button.outline - prop.onClick (fun _ -> Protocol.RemoveSelectedProtocols |> ProtocolMsg |> dispatch) + prop.onClick (fun _ -> + Protocol.RemoveSelectedProtocols |> ProtocolMsg |> dispatch + {importType with SelectedColumns = Array.empty} |> setImportType + setProtocolSearch protocolSearchState) button.error Html.i [prop.className "fa-solid fa-times"] |> prop.children ] @@ -105,7 +109,7 @@ type SelectiveTemplateFromDB = /// /// /// - static member AddTemplatesFromDBToTableButton name (model: Model) importType dispatch = + static member AddTemplatesFromDBToTableButton name (model: Model) importType setImportType protocolSearchState setProtocolSearch dispatch = let addTemplates (model: Model, selectedColumns) = let templates = model.ProtocolState.TemplatesSelected if templates.Length = 0 then @@ -122,7 +126,10 @@ type SelectiveTemplateFromDB = if model.ProtocolState.TemplatesSelected.Length > 0 then Daisy.button.a [ button.outline - prop.onClick (fun _ -> Protocol.RemoveSelectedProtocols |> ProtocolMsg |> dispatch) + prop.onClick (fun _ -> + Protocol.RemoveSelectedProtocols |> ProtocolMsg |> dispatch + {importType with SelectedColumns = Array.empty} |> setImportType + setProtocolSearch protocolSearchState) button.error Html.i [prop.className "fa-solid fa-times"] |> prop.children ] @@ -135,13 +142,7 @@ type SelectiveTemplateFromDB = /// /// [] - static member Main (model: Model, dispatch) = - let importTypeState, setImportTypeState = - let columns = - model.ProtocolState.TemplatesSelected - |> Array.ofSeq - |> Array.map (fun t -> Array.init t.Table.Columns.Length (fun _ -> true)) - React.useState(SelectiveImportModalState.init columns) + static member Main (model: Model, protocolSearchState, setProtocolSearch, importTypeState, setImportTypeState, dispatch) = let addTableImport = fun (i: int) (fullImport: bool) -> let newImportTable: ImportTable = {Index = i; FullImport = fullImport} let newImportTables = newImportTable::importTypeState.ImportTables |> List.distinctBy (fun x -> x.Index) @@ -150,7 +151,7 @@ type SelectiveTemplateFromDB = {importTypeState with ImportTables = importTypeState.ImportTables |> List.filter (fun it -> it.Index <> i)} |> setImportTypeState React.fragment [ Html.div [ - SelectiveTemplateFromDB.ToProtocolSearchElement model dispatch + SelectiveTemplateFromDB.ToProtocolSearchElement model setProtocolSearch dispatch ] if model.ProtocolState.TemplatesSelected.Length > 0 then SelectiveImportModal.RadioPluginsBox( @@ -180,7 +181,7 @@ type SelectiveTemplateFromDB = SelectiveTemplateFromDB.DisplaySelectedProtocolElements(Some template, 0, importTypeState, setImportTypeState, dispatch, false)) ] Html.div [ - SelectiveTemplateFromDB.AddFromDBToTableButton "Add template" model importTypeState importTypeState importTypeState.TemplateName dispatch + SelectiveTemplateFromDB.AddFromDBToTableButton "Add template" model importTypeState importTypeState setImportTypeState importTypeState.TemplateName protocolSearchState setProtocolSearch dispatch ] else if model.ProtocolState.TemplatesSelected.Length > 1 then let templates = model.ProtocolState.TemplatesSelected @@ -188,6 +189,6 @@ type SelectiveTemplateFromDB = let template = templates.[templateIndex] SelectiveImportModal.TableImport(templateIndex, template.Table, importTypeState, addTableImport, rmvTableImport, importTypeState, setImportTypeState, template.Name) Html.div [ - SelectiveTemplateFromDB.AddTemplatesFromDBToTableButton "Add templates" model importTypeState dispatch + SelectiveTemplateFromDB.AddTemplatesFromDBToTableButton "Add templates" model importTypeState setImportTypeState protocolSearchState setProtocolSearch dispatch ] ] diff --git a/src/Client/Routing.fs b/src/Client/Routing.fs index e11f166c..86ca1e87 100644 --- a/src/Client/Routing.fs +++ b/src/Client/Routing.fs @@ -75,5 +75,4 @@ module Routing = map Route.Home (s "" intParam "is_swatehost") ] - let parsePath (location:Browser.Types.Location) : Route option = Elmish.UrlParser.parsePath route location \ No newline at end of file diff --git a/src/Client/Types.fs b/src/Client/Types.fs index 2c086fa8..69dd1beb 100644 --- a/src/Client/Types.fs +++ b/src/Client/Types.fs @@ -17,12 +17,12 @@ module JsonImport = SelectedColumns: bool [] [] TemplateName: string option } with - static member init(selectedColumns) = + static member init() = { ImportType = ARCtrl.TableJoinOptions.Headers ImportMetadata = false ImportTables = [] - SelectedColumns = selectedColumns + SelectedColumns = Array.empty TemplateName = None } diff --git a/src/Client/Views/SpreadsheetView.fs b/src/Client/Views/SpreadsheetView.fs index c9deac7e..a8fae436 100644 --- a/src/Client/Views/SpreadsheetView.fs +++ b/src/Client/Views/SpreadsheetView.fs @@ -30,10 +30,12 @@ let private ModalDisplay (widgets: Widget list, displayWidget: Widget -> ReactEl open Shared +open JsonImport [] let Main (model: Model, dispatch) = let widgets, setWidgets = React.useState([]) + let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init()) let rmvWidget (widget: Widget) = widgets |> List.except [widget] |> setWidgets let bringWidgetToFront (widget: Widget) = let newList = widgets |> List.except [widget] |> fun x -> widget::x |> List.rev @@ -43,7 +45,7 @@ let Main (model: Model, dispatch) = let bringWidgetToFront = fun _ -> bringWidgetToFront widget match widget with | Widget._BuildingBlock -> Widget.BuildingBlock (model, dispatch, rmv widget) - | Widget._Template -> Widget.Templates (model, dispatch, rmv widget) + | Widget._Template -> Widget.Templates (model, importTypeState, setImportTypeState, dispatch, rmv widget) | Widget._FilePicker -> Widget.FilePicker (model, dispatch, rmv widget) | Widget._DataAnnotator -> Widget.DataAnnotator(model, dispatch, rmv widget) |> WidgetOrderContainer bringWidgetToFront