From bffed7f2f85a9cc3bf5e1f8a679b99e45390f5b1 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Wed, 27 Nov 2024 16:41:54 +0100 Subject: [PATCH 01/36] Enable selection of terms to import into excel --- src/Client/MainComponents/Widgets.fs | 6 ++- src/Client/OfficeInterop/OfficeInterop.fs | 18 ++++++-- .../Pages/ProtocolTemplates/TemplateFromDB.fs | 43 +++++++++++++++---- src/Client/States/OfficeInteropState.fs | 2 +- src/Client/States/SpreadsheetInterface.fs | 2 +- src/Client/Update/InterfaceUpdate.fs | 4 +- src/Client/Update/OfficeInteropUpdate.fs | 6 +-- src/Client/Update/SpreadsheetUpdate.fs | 2 +- src/Shared/ARCtrl.Helper.fs | 8 +++- 9 files changed, 67 insertions(+), 24 deletions(-) diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index b31dd277..abc2f23b 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -211,6 +211,8 @@ type Widget = [] static member Templates (model: Model, dispatch, rmv: MouseEvent -> unit) = let templates, setTemplates = React.useState(model.ProtocolState.Templates) + 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 config, setConfig = React.useState(TemplateFilterConfig.init) let filteredTemplates = Protocol.Search.filterTemplates (templates, config) React.useEffectOnce(fun _ -> Messages.Protocol.GetAllProtocolsRequest |> Messages.ProtocolMsg |> dispatch) @@ -223,12 +225,12 @@ type Widget = let insertContent() = [ Html.div [ - Protocol.TemplateFromDB.addFromDBToTableButton model dispatch + Protocol.TemplateFromDB.addFromDBToTableButton model selectedColumns dispatch ] Html.div [ prop.style [style.maxHeight (length.px 350); style.overflow.auto] prop.children [ - Protocol.TemplateFromDB.displaySelectedProtocolEle model dispatch + Protocol.TemplateFromDB.displaySelectedProtocolEle model selectedColumns setSelectedColumns dispatch ] ] ] diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index a42fca99..30ce67e5 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -924,7 +924,7 @@ let addCompositeColumn (excelTable: Table) (arcTable: ArcTable) (newColumn: Comp /// Prepare the given table to be joined with the currently active annotation table /// /// -let prepareTemplateInMemory (table: Table) (tableToAdd: ArcTable) (context: RequestContext) = +let prepareTemplateInMemory (table: Table) (tableToAdd: ArcTable) (selectedColumns:bool []) (context: RequestContext) = promise { let! originTableRes = ArcTable.fromExcelTable(table, context) @@ -932,7 +932,13 @@ let prepareTemplateInMemory (table: Table) (tableToAdd: ArcTable) (context: Requ | Result.Error _ -> return failwith $"Failed to create arc table for table {table.name}" | Result.Ok originTable -> - let finalTable = Table.selectiveTablePrepare originTable tableToAdd + 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 let selectedRange = context.workbook.getSelectedRange() @@ -959,17 +965,21 @@ let prepareTemplateInMemory (table: Table) (tableToAdd: ArcTable) (context: Requ /// /// /// -let joinTable (tableToAdd: ArcTable, options: TableJoinOptions option) = +let joinTable (tableToAdd: ArcTable, selectedColumns:bool [], options: TableJoinOptions option) = Excel.run(fun context -> promise { + let columns = tableToAdd.Columns + + columns + //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 match result with | Some excelTable -> - let! (tableToAdd: ArcTable, index: int option) = prepareTemplateInMemory excelTable tableToAdd context + let! (tableToAdd: ArcTable, index: int option) = prepareTemplateInMemory 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) diff --git a/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs index f6659a05..bbc0ea1c 100644 --- a/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs @@ -6,6 +6,15 @@ open Messages open Model open Shared +type SelectedColumns = { + Columns: bool [] +} +with + static member init(length) = + { + Columns = Array.init length (fun _ -> true) + } + type TemplateFromDB = static member toProtocolSearchElement (model:Model) dispatch = @@ -16,7 +25,7 @@ type TemplateFromDB = prop.text "Browse database" ] - static member addFromDBToTableButton (model:Model) dispatch = + static member addFromDBToTableButton (model:Model) selectionInformation dispatch = Html.div [ prop.className "join flex flex-row justify-center gap-2" prop.children [ @@ -30,21 +39,22 @@ type TemplateFromDB = if model.ProtocolState.TemplateSelected.IsNone then failwith "No template selected!" - SpreadsheetInterface.AddTemplate(model.ProtocolState.TemplateSelected.Value.Table) |> InterfaceMsg |> dispatch + SpreadsheetInterface.AddTemplate(model.ProtocolState.TemplateSelected.Value.Table, selectionInformation.Columns) |> InterfaceMsg |> dispatch ) prop.text "Add template" ] if model.ProtocolState.TemplateSelected.IsSome then Daisy.button.a [ button.outline - prop.onClick (fun e -> Protocol.RemoveSelectedProtocol |> ProtocolMsg |> dispatch) + prop.onClick (fun _ -> Protocol.RemoveSelectedProtocol |> ProtocolMsg |> dispatch) button.error Html.i [prop.className "fa-solid fa-times"] |> prop.children ] ] ] - static member displaySelectedProtocolEle (model:Model) dispatch = + [] + static member displaySelectedProtocolEle (model:Model) (selectionInformation:SelectedColumns) (setSelectedColumns:SelectedColumns -> unit) dispatch = Html.div [ prop.style [style.overflowX.auto; style.marginBottom (length.rem 1)] prop.children [ @@ -52,6 +62,7 @@ type TemplateFromDB = prop.children [ Html.thead [ Html.tr [ + Html.th "Selection" Html.th "Column" Html.th "Column TAN" //Html.th "Unit" @@ -59,10 +70,24 @@ type TemplateFromDB = ] ] Html.tbody [ - for column in model.ProtocolState.TemplateSelected.Value.Table.Columns do + for i in 0..model.ProtocolState.TemplateSelected.Value.Table.Columns.Length-1 do + let column = model.ProtocolState.TemplateSelected.Value.Table.Columns.[i] //let unitOption = column.TryGetColumnUnits() yield Html.tr [ + Html.div [ + prop.style [style.display.flex; style.justifyContent.center] + prop.children [ + Daisy.checkbox [ + prop.type'.checkbox + prop.isChecked selectionInformation.Columns.[i] + prop.onChange (fun (b: bool) -> + let selectedData = selectionInformation.Columns + selectedData.[i] <- b + {selectionInformation with Columns = selectedData} |> setSelectedColumns) + ] + ] + ] Html.td (column.Header.ToString()) Html.td (if column.Header.IsTermColumn then column.Header.ToTerm().TermAccessionShort else "-") //td [] [str (if unitOption.IsSome then insertBB.UnitTerm.Value.Name else "-")] @@ -75,19 +100,21 @@ type TemplateFromDB = ] 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) SidebarComponents.SidebarLayout.LogicContainer [ Html.div [ TemplateFromDB.toProtocolSearchElement model dispatch ] Html.div [ - TemplateFromDB.addFromDBToTableButton model dispatch + TemplateFromDB.addFromDBToTableButton model selectedColumns dispatch ] if model.ProtocolState.TemplateSelected.IsSome then Html.div [ - TemplateFromDB.displaySelectedProtocolEle model dispatch + TemplateFromDB.displaySelectedProtocolEle model selectedColumns setSelectedColumns dispatch ] Html.div [ - TemplateFromDB.addFromDBToTableButton model dispatch + TemplateFromDB.addFromDBToTableButton model selectedColumns dispatch ] ] diff --git a/src/Client/States/OfficeInteropState.fs b/src/Client/States/OfficeInteropState.fs index 009a3785..1f8bf8d6 100644 --- a/src/Client/States/OfficeInteropState.fs +++ b/src/Client/States/OfficeInteropState.fs @@ -31,7 +31,7 @@ type Msg = | ValidateBuildingBlock | AddAnnotationBlock of CompositeColumn | AddAnnotationBlocks of CompositeColumn [] //* OfficeInterop.Types.Xml.ValidationTypes.TableValidation option - | AddTemplate of ArcTable + | AddTemplate of ArcTable * bool[] | JoinTable of ArcTable * options: TableJoinOptions option | RemoveBuildingBlock | UpdateUnitForCells diff --git a/src/Client/States/SpreadsheetInterface.fs b/src/Client/States/SpreadsheetInterface.fs index e9e385e7..968e1aef 100644 --- a/src/Client/States/SpreadsheetInterface.fs +++ b/src/Client/States/SpreadsheetInterface.fs @@ -17,7 +17,7 @@ type Msg = | AddAnnotationBlocks of CompositeColumn [] | AddDataAnnotation of {| fragmentSelectors: string []; fileName: string; fileType: string; targetColumn: DataAnnotator.TargetColumn |} /// This function will do preprocessing on the table to join -| AddTemplate of ArcTable +| AddTemplate of ArcTable * bool[] | JoinTable of ArcTable * columnIndex: int option * options: TableJoinOptions option | UpdateArcFile of ArcFiles /// Inserts TermMinimal to selected fields of one column diff --git a/src/Client/Update/InterfaceUpdate.fs b/src/Client/Update/InterfaceUpdate.fs index 81e2b304..eba7f525 100644 --- a/src/Client/Update/InterfaceUpdate.fs +++ b/src/Client/Update/InterfaceUpdate.fs @@ -164,10 +164,10 @@ module Interface = model, cmd | _ -> failwith "not implemented" - | AddTemplate table -> + | AddTemplate (table, selectedColumns) -> match host with | Some Swatehost.Excel -> - let cmd = OfficeInterop.AddTemplate table |> OfficeInteropMsg |> Cmd.ofMsg + let cmd = OfficeInterop.AddTemplate (table, selectedColumns) |> OfficeInteropMsg |> Cmd.ofMsg model, cmd | Some Swatehost.Browser | Some Swatehost.ARCitect -> let cmd = Spreadsheet.AddTemplate table |> SpreadsheetMsg |> Cmd.ofMsg diff --git a/src/Client/Update/OfficeInteropUpdate.fs b/src/Client/Update/OfficeInteropUpdate.fs index fb82cb49..d013e664 100644 --- a/src/Client/Update/OfficeInteropUpdate.fs +++ b/src/Client/Update/OfficeInteropUpdate.fs @@ -76,11 +76,11 @@ module OfficeInterop = UpdateUtil.downloadFromString (jsonExport) state, model, Cmd.none - | AddTemplate table -> + | AddTemplate (table, selectedColumns) -> let cmd = Cmd.OfPromise.either OfficeInterop.Core.joinTable - (table, Some ARCtrl.TableJoinOptions.WithValues) + (table, selectedColumns, Some ARCtrl.TableJoinOptions.WithValues) (curry GenericInteropLogs Cmd.none >> DevMsg) (curry GenericError Cmd.none >> DevMsg) state, model, cmd @@ -89,7 +89,7 @@ module OfficeInterop = let cmd = Cmd.OfPromise.either OfficeInterop.Core.joinTable - (table, options) + (table, [||], options) (curry GenericInteropLogs Cmd.none >> DevMsg) (curry GenericError Cmd.none >> DevMsg) state, model, cmd diff --git a/src/Client/Update/SpreadsheetUpdate.fs b/src/Client/Update/SpreadsheetUpdate.fs index 300a3cfa..baecd9e5 100644 --- a/src/Client/Update/SpreadsheetUpdate.fs +++ b/src/Client/Update/SpreadsheetUpdate.fs @@ -109,7 +109,7 @@ module Spreadsheet = let options = Some ARCtrl.TableJoinOptions.WithValues // If changed to anything else we need different logic to keep input/output values let msg = fun t -> JoinTable(t, index, options) |> SpreadsheetMsg let cmd = - Table.selectiveTablePrepare state.ActiveTable table + Table.selectiveTablePrepare state.ActiveTable table [] |> msg |> Cmd.ofMsg state, model, cmd diff --git a/src/Shared/ARCtrl.Helper.fs b/src/Shared/ARCtrl.Helper.fs index 67abd0a4..83999645 100644 --- a/src/Shared/ARCtrl.Helper.fs +++ b/src/Shared/ARCtrl.Helper.fs @@ -93,15 +93,19 @@ module Table = /// /// The active/current table /// The new table, which will be added to the existing one. - let selectiveTablePrepare (activeTable: ArcTable) (toJoinTable: ArcTable) : ArcTable = + let selectiveTablePrepare (activeTable: ArcTable) (toJoinTable: ArcTable) (removeColumns:int list): ArcTable = // Remove existing columns - let mutable columnsToRemove = [] + 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 |> Set.ofList |> Set.toList + tablecopy.RemoveColumns (Array.ofList columnsToRemove) tablecopy.IteriColumns(fun i c0 -> let c1 = {c0 with Cells = [||]} From 2aa0ddfed5f8ac8fca814093e2e3af141ab348c7 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Thu, 28 Nov 2024 13:00:48 +0100 Subject: [PATCH 02/36] Fix error in swate alpha --- src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs index bbc0ea1c..c63b55aa 100644 --- a/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs @@ -80,11 +80,15 @@ type TemplateFromDB = prop.children [ Daisy.checkbox [ prop.type'.checkbox - prop.isChecked selectionInformation.Columns.[i] + prop.isChecked + (if selectionInformation.Columns.Length > 0 then + selectionInformation.Columns.[i] + else true) prop.onChange (fun (b: bool) -> - let selectedData = selectionInformation.Columns - selectedData.[i] <- b - {selectionInformation with Columns = selectedData} |> setSelectedColumns) + if selectionInformation.Columns.Length > 0 then + let selectedData = selectionInformation.Columns + selectedData.[i] <- b + {selectionInformation with Columns = selectedData} |> setSelectedColumns) ] ] ] From b179a4a5df9826904344c6eede69cb0d8a194eea Mon Sep 17 00:00:00 2001 From: patrick blume Date: Thu, 28 Nov 2024 13:29:17 +0100 Subject: [PATCH 03/36] Move basic modal element creation methods into separat file --- src/Client/Client.fable-temp.csproj | 154 +++++++++++++++++++ src/Client/Client.fsproj | 2 + src/Client/Modals/ModalElements.fs | 71 +++++++++ src/Client/Modals/SelectiveImportModal.fs | 81 ++-------- src/Client/Modals/SelectiveTemplateFromDB.fs | 41 +++++ 5 files changed, 285 insertions(+), 64 deletions(-) create mode 100644 src/Client/Client.fable-temp.csproj create mode 100644 src/Client/Modals/ModalElements.fs create mode 100644 src/Client/Modals/SelectiveTemplateFromDB.fs diff --git a/src/Client/Client.fable-temp.csproj b/src/Client/Client.fable-temp.csproj new file mode 100644 index 00000000..d1678662 --- /dev/null +++ b/src/Client/Client.fable-temp.csproj @@ -0,0 +1,154 @@ + + + + + net8.0 + FABLE_COMPILER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Client/Client.fsproj b/src/Client/Client.fsproj index 93fe14cc..63c15990 100644 --- a/src/Client/Client.fsproj +++ b/src/Client/Client.fsproj @@ -35,6 +35,7 @@ + @@ -52,6 +53,7 @@ + diff --git a/src/Client/Modals/ModalElements.fs b/src/Client/Modals/ModalElements.fs new file mode 100644 index 00000000..36eb9700 --- /dev/null +++ b/src/Client/Modals/ModalElements.fs @@ -0,0 +1,71 @@ +module Modals.ModalElements + +open Feliz +open Feliz.DaisyUI +open Model +open Messages +open Shared + +open ARCtrl +open JsonImport +open Components + + +type ModalElements = + + static member RadioPlugin(radioGroup: string, txt:string, isChecked, onChange: bool -> unit, ?isDisabled: bool) = + let isDisabled = defaultArg isDisabled false + Daisy.formControl [ + Daisy.label [ + prop.className [ + "cursor-pointer transition-colors" + if isDisabled then + "!cursor-not-allowed" + else + "hover:bg-base-300" + ] + prop.children [ + Daisy.radio [ + prop.disabled isDisabled + radio.xs + prop.name radioGroup + prop.isChecked isChecked + prop.onChange onChange + ] + Html.span [ + prop.className "text-sm" + prop.text txt + ] + ] + ] + ] + + static member Box(title: string, icon: string, content: ReactElement, ?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" + prop.children [ + Html.i [prop.className icon] + Html.span title + ] + ] + content + ] + ] + + static member ImportRadioPlugins(importType: TableJoinOptions, radioData: (TableJoinOptions * string)[], setImportType: TableJoinOptions -> unit) = + let myradio(target: TableJoinOptions, txt: string) = + let isChecked = importType = target + ModalElements.RadioPlugin("importType", txt, isChecked, fun (b: bool) -> if b then setImportType target) + ModalElements.Box ("Import Type", "fa-solid fa-cog", React.fragment [ + Html.div [ + for i in 0..radioData.Length-1 do + myradio(radioData.[i]) + ] + ]) diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index 5e333e8b..dd70ee28 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -1,4 +1,4 @@ -namespace Modals +namespace Modals.Import open Feliz open Feliz.DaisyUI @@ -10,68 +10,14 @@ open ARCtrl open JsonImport open Components -type SelectiveImportModal = +open Modals +open Modals.ModalElements - static member private Radio(radioGroup: string, txt:string, isChecked, onChange: bool -> unit, ?isDisabled: bool) = - let isDisabled = defaultArg isDisabled false - Daisy.formControl [ - Daisy.label [ - prop.className [ - "cursor-pointer transition-colors" - if isDisabled then - "!cursor-not-allowed" - else - "hover:bg-base-300" - ] - prop.children [ - Daisy.radio [ - prop.disabled isDisabled - radio.xs - prop.name radioGroup - prop.isChecked isChecked - prop.onChange onChange - ] - Html.span [ - prop.className "text-sm" - prop.text txt - ] - ] - ] - ] - static member private Box (title: string, icon: string, content: ReactElement, ?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" - prop.children [ - Html.i [prop.className icon] - Html.span title - ] - ] - content - ] - ] - - static member private ImportTypeRadio(importType: TableJoinOptions, setImportType: TableJoinOptions -> unit) = - let myradio(target: TableJoinOptions, txt: string) = - let isChecked = importType = target - SelectiveImportModal.Radio("importType", txt, isChecked, fun (b:bool) -> if b then setImportType target) - SelectiveImportModal.Box ("Import Type", "fa-solid fa-cog", React.fragment [ - Html.div [ - myradio(ARCtrl.TableJoinOptions.Headers, " Column Headers") - myradio(ARCtrl.TableJoinOptions.WithUnit, " ..With Units") - myradio(ARCtrl.TableJoinOptions.WithValues, " ..With Values") - ] - ]) +type SelectiveImportModal = static member private MetadataImport(isActive: bool, setActive: bool -> unit, disArcFile: ArcFilesDiscriminate) = let name = string disArcFile - SelectiveImportModal.Box (sprintf "%s Metadata" name, "fa-solid fa-lightbulb", React.fragment [ + ModalElements.Box (sprintf "%s Metadata" name, "fa-solid fa-lightbulb", React.fragment [ Daisy.formControl [ Daisy.label [ prop.className "cursor-pointer" @@ -105,19 +51,19 @@ type SelectiveImportModal = let import = state.ImportTables |> List.tryFind (fun it -> it.Index = index) let isActive = import.IsSome let isDisabled = state.ImportMetadata - SelectiveImportModal.Box (name, "fa-solid fa-table", React.fragment [ + ModalElements.Box (name, "fa-solid fa-table", React.fragment [ Html.div [ - SelectiveImportModal.Radio (radioGroup, "Import", + ModalElements.RadioPlugin (radioGroup, "Import", isActive && import.Value.FullImport, (fun (b:bool) -> addTableImport index true), isDisabled ) - SelectiveImportModal.Radio (radioGroup, "Append to active table", + ModalElements.RadioPlugin (radioGroup, "Append to active table", isActive && not import.Value.FullImport, (fun (b:bool) -> addTableImport index false), isDisabled ) - SelectiveImportModal.Radio (radioGroup, "No Import", + ModalElements.RadioPlugin (radioGroup, "No Import", not isActive, (fun (b:bool) -> rmvTableImport index), isDisabled @@ -195,7 +141,14 @@ type SelectiveImportModal = Components.DeleteButton(props=[prop.onClick rmv]) ] ] - SelectiveImportModal.ImportTypeRadio(state.ImportType, fun it -> {state with ImportType = it} |> setState) + ModalElements.ImportRadioPlugins( + state.ImportType, + [| + ARCtrl.TableJoinOptions.Headers, " Column Headers"; + ARCtrl.TableJoinOptions.WithUnit, " ..With Units"; + ARCtrl.TableJoinOptions.WithValues, " ..With Values"; + |], + fun it -> {state with ImportType = it} |> setState) SelectiveImportModal.MetadataImport(state.ImportMetadata, setMetadataImport, disArcfile) for ti in 0 .. (tables.Count-1) do let t = tables.[ti] diff --git a/src/Client/Modals/SelectiveTemplateFromDB.fs b/src/Client/Modals/SelectiveTemplateFromDB.fs new file mode 100644 index 00000000..e6a18518 --- /dev/null +++ b/src/Client/Modals/SelectiveTemplateFromDB.fs @@ -0,0 +1,41 @@ +namespace Modals.Template + +open Feliz +open Feliz.DaisyUI +open Model +open Messages +open Shared + +open ARCtrl +open JsonImport +open Components + + +type SelectiveTemplateFromDBModal = + + static member private Radio(radioGroup: string, txt:string, isChecked, onChange: bool -> unit, ?isDisabled: bool) = + let isDisabled = defaultArg isDisabled false + Daisy.formControl [ + Daisy.label [ + prop.className [ + "cursor-pointer transition-colors" + if isDisabled then + "!cursor-not-allowed" + else + "hover:bg-base-300" + ] + prop.children [ + Daisy.radio [ + prop.disabled isDisabled + radio.xs + prop.name radioGroup + prop.isChecked isChecked + prop.onChange onChange + ] + Html.span [ + prop.className "text-sm" + prop.text txt + ] + ] + ] + ] \ No newline at end of file From b46fc28aa74ff5c08020e5888da2a2a2b4dc4fd7 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Thu, 28 Nov 2024 15:54:17 +0100 Subject: [PATCH 04/36] Replace existing TemplateFromDB with Modal --- src/Client/Modals/ModalElements.fs | 13 ++ src/Client/Modals/ModalProvider.fs | 4 +- src/Client/Modals/SelectiveTemplateFromDB.fs | 143 +++++++++++++++--- .../Pages/ProtocolTemplates/ProtocolView.fs | 2 +- .../Pages/ProtocolTemplates/TemplateFromDB.fs | 8 +- .../ProtocolTemplates/TemplateFromFile.fs | 2 +- src/Client/SidebarComponents/LayoutHelper.fs | 1 + src/Client/States/ModalState.fs | 3 + 8 files changed, 146 insertions(+), 30 deletions(-) diff --git a/src/Client/Modals/ModalElements.fs b/src/Client/Modals/ModalElements.fs index 36eb9700..d275452c 100644 --- a/src/Client/Modals/ModalElements.fs +++ b/src/Client/Modals/ModalElements.fs @@ -13,6 +13,19 @@ open Components type ModalElements = + static member Button(text: string, onClickAction, buttonInput, ?isDisabled: bool) = + let isDisabled = defaultArg isDisabled false + Daisy.button.a [ + button.success + button.wide + if isDisabled then + button.error + prop.disabled isDisabled + prop.onClick (fun _ -> onClickAction buttonInput) + + prop.text text + ] + static member RadioPlugin(radioGroup: string, txt:string, isChecked, onChange: bool -> unit, ?isDisabled: bool) = let isDisabled = defaultArg isDisabled false Daisy.formControl [ diff --git a/src/Client/Modals/ModalProvider.fs b/src/Client/Modals/ModalProvider.fs index 5962d320..8f5c34d2 100644 --- a/src/Client/Modals/ModalProvider.fs +++ b/src/Client/Modals/ModalProvider.fs @@ -22,8 +22,10 @@ type ModalProvider = Modals.ResetTable.Main dispatch | TableModals.TermDetails term -> Modals.TermModal.Main (term, dispatch) + | TableModals.SelectiveTemplateImportFromDB -> + Modals.Template.SelectiveTemplateFromDBModal.Main (model, dispatch) | TableModals.SelectiveFileImport arcfile -> - Modals.SelectiveImportModal.Main (arcfile, dispatch) + Modals.Import.SelectiveImportModal.Main (arcfile, dispatch) | TableModals.BatchUpdateColumnValues (columnIndex, column) -> Modals.UpdateColumn.Main (columnIndex, column, dispatch) | TableModals.TableCellContext (mouseX, mouseY, ci, ri) -> diff --git a/src/Client/Modals/SelectiveTemplateFromDB.fs b/src/Client/Modals/SelectiveTemplateFromDB.fs index e6a18518..67023bda 100644 --- a/src/Client/Modals/SelectiveTemplateFromDB.fs +++ b/src/Client/Modals/SelectiveTemplateFromDB.fs @@ -10,32 +10,131 @@ open ARCtrl open JsonImport open Components +open Modals +open Modals.ModalElements + +type SelectedColumns = { + Columns: bool [] +} +with + static member init(length) = + { + Columns = Array.init length (fun _ -> true) + } type SelectiveTemplateFromDBModal = - static member private Radio(radioGroup: string, txt:string, isChecked, onChange: bool -> unit, ?isDisabled: bool) = - let isDisabled = defaultArg isDisabled false - Daisy.formControl [ - Daisy.label [ - prop.className [ - "cursor-pointer transition-colors" - if isDisabled then - "!cursor-not-allowed" - else - "hover:bg-base-300" - ] - prop.children [ - Daisy.radio [ - prop.disabled isDisabled - radio.xs - prop.name radioGroup - prop.isChecked isChecked - prop.onChange onChange + static member private LogicContainer (children: ReactElement list) = + Html.div [ + // prop.className "border-l-4 border-transparent px-4 py-2 shadow-md" + // prop.style [ + // let rndVal = rnd.Next(30,70) + // let colorArr = [|NFDIColors.LightBlue.Lighter10; NFDIColors.Mint.Lighter10;|] + // style.custom("borderImageSlice", "1") + // style.custom("borderImageSource", $"linear-gradient({colorArr.[if order then 0 else 1]} {100-rndVal}%%, {colorArr.[if order then 1 else 0]})") + // order <- not order + // ] + prop.className "relative flex p-4 animated-border shadow-md gap-4 flex-col" //experimental + prop.children children + ] + + [] + static member displaySelectedProtocolEle (model: Model) (selectionInformation:SelectedColumns) (setSelectedColumns:SelectedColumns -> unit) dispatch = + Html.div [ + prop.style [style.overflowX.auto; style.marginBottom (length.rem 1)] + prop.children [ + Daisy.table [ + prop.children [ + Html.thead [ + Html.tr [ + Html.th "Selection" + Html.th "Column" + Html.th "Column TAN" + ] + ] + Html.tbody [ + let length = + if model.ProtocolState.TemplateSelected.IsSome then model.ProtocolState.TemplateSelected.Value.Table.Columns.Length-1 + else 0 + for i in 0..length do + let column = model.ProtocolState.TemplateSelected.Value.Table.Columns.[i] + yield + Html.tr [ + Html.div [ + prop.style [style.display.flex; style.justifyContent.center] + prop.children [ + Daisy.checkbox [ + prop.type'.checkbox + prop.isChecked + (if selectionInformation.Columns.Length > 0 then + selectionInformation.Columns.[i] + else true) + prop.onChange (fun (b: bool) -> + if selectionInformation.Columns.Length > 0 then + let selectedData = selectionInformation.Columns + selectedData.[i] <- b + {selectionInformation with Columns = selectedData} |> setSelectedColumns) + ] + ] + ] + Html.td (column.Header.ToString()) + Html.td (if column.Header.IsTermColumn then column.Header.ToTerm().TermAccessionShort else "-") + ] + ] ] - Html.span [ - prop.className "text-sm" - prop.text txt + ] + ] + ] + + static member addFromDBToTableButton (model: Model) selectionInformation dispatch = + let addTemplate (templatePot: Template option, selectedColumns) = + if model.ProtocolState.TemplateSelected.IsNone then + failwith "No template selected!" + if templatePot.IsSome then + let table = templatePot.Value.Table + SpreadsheetInterface.AddTemplate(table, selectedColumns) |> 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 + 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 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) + SelectiveTemplateFromDBModal.LogicContainer [ + Html.div [ + Daisy.button.button [ + prop.onClick(fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) + button.primary + button.block + prop.text "Browse database" ] ] - ] \ No newline at end of file + Html.div [ + SelectiveTemplateFromDBModal.addFromDBToTableButton model selectedColumns dispatch + ] + if model.ProtocolState.TemplateSelected.IsSome then + Html.div [ + SelectiveTemplateFromDBModal.displaySelectedProtocolEle model selectedColumns setSelectedColumns dispatch + ] + Html.div [ + SelectiveTemplateFromDBModal.addFromDBToTableButton model selectedColumns dispatch + ] + ] + + //static member Main(model:Model, dispatch: Messages.Msg -> unit) = + // let rmv = Util.RMV_MODAL dispatch + // SelectiveTemplateFromDBModal.Main (model, dispatch, rmv) \ No newline at end of file diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolView.fs b/src/Client/Pages/ProtocolTemplates/ProtocolView.fs index 8b4fb71e..a65ee6a7 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolView.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolView.fs @@ -39,7 +39,7 @@ type Templates = // Box 1 SidebarComponents.SidebarLayout.Description "Add template from database." - TemplateFromDB.Main(model, dispatch) + Modals.Template.SelectiveTemplateFromDBModal.Main(model, dispatch) // Box 2 SidebarComponents.SidebarLayout.Description (Html.p [ diff --git a/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs index c63b55aa..c87ee209 100644 --- a/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs @@ -17,7 +17,7 @@ with type TemplateFromDB = - 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 @@ -25,7 +25,7 @@ type TemplateFromDB = prop.text "Browse database" ] - static member addFromDBToTableButton (model:Model) selectionInformation dispatch = + static member addFromDBToTableButton (model: Model) selectionInformation dispatch = Html.div [ prop.className "join flex flex-row justify-center gap-2" prop.children [ @@ -38,7 +38,6 @@ type TemplateFromDB = prop.onClick (fun _ -> if model.ProtocolState.TemplateSelected.IsNone then failwith "No template selected!" - SpreadsheetInterface.AddTemplate(model.ProtocolState.TemplateSelected.Value.Table, selectionInformation.Columns) |> InterfaceMsg |> dispatch ) prop.text "Add template" @@ -54,7 +53,7 @@ type TemplateFromDB = ] [] - static member displaySelectedProtocolEle (model:Model) (selectionInformation:SelectedColumns) (setSelectedColumns:SelectedColumns -> unit) dispatch = + static member displaySelectedProtocolEle (model: Model) (selectionInformation:SelectedColumns) (setSelectedColumns:SelectedColumns -> unit) dispatch = Html.div [ prop.style [style.overflowX.auto; style.marginBottom (length.rem 1)] prop.children [ @@ -110,7 +109,6 @@ type TemplateFromDB = Html.div [ TemplateFromDB.toProtocolSearchElement model dispatch ] - Html.div [ TemplateFromDB.addFromDBToTableButton model selectedColumns dispatch ] diff --git a/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs b/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs index cf287e9b..8c81319d 100644 --- a/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs +++ b/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs @@ -113,7 +113,7 @@ type TemplateFromFile = // modal! match state.UploadedFile with | Some af -> - Modals.SelectiveImportModal.Main (af, dispatch, rmv = (fun _ -> TemplateFromFileState.init() |> setState)) + Modals.Import.SelectiveImportModal.Main (af, dispatch, rmv = (fun _ -> TemplateFromFileState.init() |> setState)) | None -> Html.none Html.div [ Daisy.join [ diff --git a/src/Client/SidebarComponents/LayoutHelper.fs b/src/Client/SidebarComponents/LayoutHelper.fs index d1578854..692f5e89 100644 --- a/src/Client/SidebarComponents/LayoutHelper.fs +++ b/src/Client/SidebarComponents/LayoutHelper.fs @@ -39,6 +39,7 @@ type SidebarLayout = prop.className "relative flex p-4 animated-border shadow-md gap-4 flex-col" //experimental prop.children children ] + static member Header(txt: string) = Html.h3 [ prop.className "text-lg font-semibold" diff --git a/src/Client/States/ModalState.fs b/src/Client/States/ModalState.fs index 1b784b4e..bcdb1a3d 100644 --- a/src/Client/States/ModalState.fs +++ b/src/Client/States/ModalState.fs @@ -5,12 +5,15 @@ open ARCtrl open Feliz +open Model + module ModalState = type TableModals = | EditColumn of columIndex: int | MoveColumn of columnIndex: int | BatchUpdateColumnValues of columIndex: int * column: CompositeColumn + | SelectiveTemplateImportFromDB | SelectiveFileImport of ArcFiles | TermDetails of OntologyAnnotation | TableCellContext of mouseX: int * mouseY: int * columnIndex: int * rowIndex: int From 04a2cb704427f06ee1f498641b2fcf873c202bcd Mon Sep 17 00:00:00 2001 From: patrick blume Date: Fri, 29 Nov 2024 11:40:31 +0100 Subject: [PATCH 05/36] Add usage of selected columns to swate alpha --- src/Client/Client.fsproj | 1 - src/Client/MainComponents/Widgets.fs | 5 +- src/Client/Modals/SelectiveTemplateFromDB.fs | 12 -- .../Pages/ProtocolTemplates/TemplateFromDB.fs | 122 ------------------ src/Client/States/Spreadsheet.fs | 2 +- src/Client/Update/InterfaceUpdate.fs | 2 +- src/Client/Update/SpreadsheetUpdate.fs | 12 +- 7 files changed, 14 insertions(+), 142 deletions(-) delete mode 100644 src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs diff --git a/src/Client/Client.fsproj b/src/Client/Client.fsproj index 63c15990..50e238c0 100644 --- a/src/Client/Client.fsproj +++ b/src/Client/Client.fsproj @@ -92,7 +92,6 @@ - diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index abc2f23b..6cfd3fe9 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -4,6 +4,7 @@ open Feliz open Feliz.DaisyUI open Browser.Types open LocalStorage.Widgets +open Modals.Template module private InitExtensions = @@ -225,12 +226,12 @@ type Widget = let insertContent() = [ Html.div [ - Protocol.TemplateFromDB.addFromDBToTableButton model selectedColumns dispatch + SelectiveTemplateFromDBModal.displaySelectedProtocolEle model selectedColumns setSelectedColumns dispatch ] Html.div [ prop.style [style.maxHeight (length.px 350); style.overflow.auto] prop.children [ - Protocol.TemplateFromDB.displaySelectedProtocolEle model selectedColumns setSelectedColumns dispatch + SelectiveTemplateFromDBModal.displaySelectedProtocolEle model selectedColumns setSelectedColumns dispatch ] ] ] diff --git a/src/Client/Modals/SelectiveTemplateFromDB.fs b/src/Client/Modals/SelectiveTemplateFromDB.fs index 67023bda..72f76dbc 100644 --- a/src/Client/Modals/SelectiveTemplateFromDB.fs +++ b/src/Client/Modals/SelectiveTemplateFromDB.fs @@ -26,14 +26,6 @@ type SelectiveTemplateFromDBModal = static member private LogicContainer (children: ReactElement list) = Html.div [ - // prop.className "border-l-4 border-transparent px-4 py-2 shadow-md" - // prop.style [ - // let rndVal = rnd.Next(30,70) - // let colorArr = [|NFDIColors.LightBlue.Lighter10; NFDIColors.Mint.Lighter10;|] - // style.custom("borderImageSlice", "1") - // style.custom("borderImageSource", $"linear-gradient({colorArr.[if order then 0 else 1]} {100-rndVal}%%, {colorArr.[if order then 1 else 0]})") - // order <- not order - // ] prop.className "relative flex p-4 animated-border shadow-md gap-4 flex-col" //experimental prop.children children ] @@ -134,7 +126,3 @@ type SelectiveTemplateFromDBModal = SelectiveTemplateFromDBModal.addFromDBToTableButton model selectedColumns dispatch ] ] - - //static member Main(model:Model, dispatch: Messages.Msg -> unit) = - // let rmv = Util.RMV_MODAL dispatch - // SelectiveTemplateFromDBModal.Main (model, dispatch, rmv) \ No newline at end of file diff --git a/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs deleted file mode 100644 index c87ee209..00000000 --- a/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs +++ /dev/null @@ -1,122 +0,0 @@ -namespace Protocol - -open Feliz -open Feliz.DaisyUI -open Messages -open Model -open Shared - -type SelectedColumns = { - Columns: bool [] -} -with - static member init(length) = - { - Columns = Array.init length (fun _ -> true) - } - -type TemplateFromDB = - - static member toProtocolSearchElement (model: Model) dispatch = - Daisy.button.button [ - prop.onClick(fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) - button.primary - button.block - prop.text "Browse database" - ] - - static member addFromDBToTableButton (model: Model) selectionInformation dispatch = - Html.div [ - prop.className "join flex flex-row justify-center gap-2" - prop.children [ - Daisy.button.a [ - button.success - button.wide - if model.ProtocolState.TemplateSelected.IsNone then - button.error - prop.disabled true - prop.onClick (fun _ -> - if model.ProtocolState.TemplateSelected.IsNone then - failwith "No template selected!" - SpreadsheetInterface.AddTemplate(model.ProtocolState.TemplateSelected.Value.Table, selectionInformation.Columns) |> InterfaceMsg |> dispatch - ) - prop.text "Add template" - ] - if model.ProtocolState.TemplateSelected.IsSome 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 displaySelectedProtocolEle (model: Model) (selectionInformation:SelectedColumns) (setSelectedColumns:SelectedColumns -> unit) dispatch = - Html.div [ - prop.style [style.overflowX.auto; style.marginBottom (length.rem 1)] - prop.children [ - Daisy.table [ - prop.children [ - Html.thead [ - Html.tr [ - Html.th "Selection" - Html.th "Column" - Html.th "Column TAN" - //Html.th "Unit" - //Html.th "Unit TAN" - ] - ] - Html.tbody [ - for i in 0..model.ProtocolState.TemplateSelected.Value.Table.Columns.Length-1 do - let column = model.ProtocolState.TemplateSelected.Value.Table.Columns.[i] - //let unitOption = column.TryGetColumnUnits() - yield - Html.tr [ - Html.div [ - prop.style [style.display.flex; style.justifyContent.center] - prop.children [ - Daisy.checkbox [ - prop.type'.checkbox - prop.isChecked - (if selectionInformation.Columns.Length > 0 then - selectionInformation.Columns.[i] - else true) - prop.onChange (fun (b: bool) -> - if selectionInformation.Columns.Length > 0 then - let selectedData = selectionInformation.Columns - selectedData.[i] <- b - {selectionInformation with Columns = selectedData} |> setSelectedColumns) - ] - ] - ] - Html.td (column.Header.ToString()) - Html.td (if column.Header.IsTermColumn then column.Header.ToTerm().TermAccessionShort else "-") - //td [] [str (if unitOption.IsSome then insertBB.UnitTerm.Value.Name else "-")] - //td [] [str (if insertBB.HasUnit then insertBB.UnitTerm.Value.TermAccession else "-")] - ] - ] - ] - ] - ] - ] - - 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) - SidebarComponents.SidebarLayout.LogicContainer [ - Html.div [ - TemplateFromDB.toProtocolSearchElement model dispatch - ] - Html.div [ - TemplateFromDB.addFromDBToTableButton model selectedColumns dispatch - ] - if model.ProtocolState.TemplateSelected.IsSome then - Html.div [ - TemplateFromDB.displaySelectedProtocolEle model selectedColumns setSelectedColumns dispatch - ] - Html.div [ - TemplateFromDB.addFromDBToTableButton model selectedColumns dispatch - ] - ] diff --git a/src/Client/States/Spreadsheet.fs b/src/Client/States/Spreadsheet.fs index 3b747069..31efc352 100644 --- a/src/Client/States/Spreadsheet.fs +++ b/src/Client/States/Spreadsheet.fs @@ -201,7 +201,7 @@ type Msg = | AddAnnotationBlock of CompositeColumn | AddAnnotationBlocks of CompositeColumn [] | AddDataAnnotation of {| fragmentSelectors: string []; fileName: string; fileType: string; targetColumn: DataAnnotator.TargetColumn |} -| AddTemplate of ArcTable +| AddTemplate of ArcTable * bool[] | JoinTable of ArcTable * index: int option * options: TableJoinOptions option | UpdateArcFile of ArcFiles | InitFromArcFile of ArcFiles diff --git a/src/Client/Update/InterfaceUpdate.fs b/src/Client/Update/InterfaceUpdate.fs index eba7f525..3008a332 100644 --- a/src/Client/Update/InterfaceUpdate.fs +++ b/src/Client/Update/InterfaceUpdate.fs @@ -170,7 +170,7 @@ module Interface = let cmd = OfficeInterop.AddTemplate (table, selectedColumns) |> OfficeInteropMsg |> Cmd.ofMsg model, cmd | Some Swatehost.Browser | Some Swatehost.ARCitect -> - let cmd = Spreadsheet.AddTemplate table |> SpreadsheetMsg |> Cmd.ofMsg + let cmd = Spreadsheet.AddTemplate (table, selectedColumns) |> SpreadsheetMsg |> Cmd.ofMsg model, cmd | _ -> failwith "not implemented" | JoinTable (table, index, options) -> diff --git a/src/Client/Update/SpreadsheetUpdate.fs b/src/Client/Update/SpreadsheetUpdate.fs index baecd9e5..d040ad92 100644 --- a/src/Client/Update/SpreadsheetUpdate.fs +++ b/src/Client/Update/SpreadsheetUpdate.fs @@ -103,13 +103,19 @@ module Spreadsheet = | IsTable -> Controller.BuildingBlocks.addDataAnnotation data state | IsMetadata -> failwith "Unable to add data annotation in metadata view" nextState, model, Cmd.none - | AddTemplate table -> + | AddTemplate (table, selectedColumns) -> let index = Some (Spreadsheet.Controller.BuildingBlocks.SidebarControllerAux.getNextColumnIndex model.SpreadsheetModel) /// Filter out existing building blocks and keep input/output values. let options = Some ARCtrl.TableJoinOptions.WithValues // If changed to anything else we need different logic to keep input/output values - let msg = fun t -> JoinTable(t, index, options) |> SpreadsheetMsg + let msg = fun table -> JoinTable(table, index, options) |> SpreadsheetMsg + let selectedColumnsIndices = + selectedColumns + |> 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 [] + Table.selectiveTablePrepare state.ActiveTable table selectedColumnsIndices |> msg |> Cmd.ofMsg state, model, cmd From 12d4d78d0ae17da2ac46047d2fdc801b5cfc7329 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Mon, 2 Dec 2024 08:22:20 +0100 Subject: [PATCH 06/36] Adapt box layout --- src/Client/Modals/ModalElements.fs | 22 ++++++++++++ src/Client/Modals/SelectiveTemplateFromDB.fs | 35 +++++++++++--------- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/Client/Modals/ModalElements.fs b/src/Client/Modals/ModalElements.fs index d275452c..5bb525e9 100644 --- a/src/Client/Modals/ModalElements.fs +++ b/src/Client/Modals/ModalElements.fs @@ -72,6 +72,28 @@ type ModalElements = ] ] + static member BoxWithChildren(children: ReactElement list, boxClass: string, ?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 boxClass + 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 ImportRadioPlugins(importType: TableJoinOptions, radioData: (TableJoinOptions * string)[], setImportType: TableJoinOptions -> unit) = let myradio(target: TableJoinOptions, txt: string) = let isChecked = importType = target diff --git a/src/Client/Modals/SelectiveTemplateFromDB.fs b/src/Client/Modals/SelectiveTemplateFromDB.fs index 72f76dbc..fc3bfb97 100644 --- a/src/Client/Modals/SelectiveTemplateFromDB.fs +++ b/src/Client/Modals/SelectiveTemplateFromDB.fs @@ -100,29 +100,32 @@ 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 else 0 let selectedColumns, setSelectedColumns = React.useState(SelectedColumns.init length) - SelectiveTemplateFromDBModal.LogicContainer [ - Html.div [ - Daisy.button.button [ - prop.onClick(fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) - button.primary - button.block - prop.text "Browse database" - ] - ] - Html.div [ - SelectiveTemplateFromDBModal.addFromDBToTableButton model selectedColumns dispatch - ] - if model.ProtocolState.TemplateSelected.IsSome then + ModalElements.BoxWithChildren( + [ Html.div [ - SelectiveTemplateFromDBModal.displaySelectedProtocolEle model selectedColumns setSelectedColumns dispatch + Daisy.button.button [ + prop.onClick(fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) + button.primary + button.block + prop.text "Browse database" + ] ] Html.div [ SelectiveTemplateFromDBModal.addFromDBToTableButton model selectedColumns dispatch ] - ] + if model.ProtocolState.TemplateSelected.IsSome then + Html.div [ + SelectiveTemplateFromDBModal.displaySelectedProtocolEle model selectedColumns setSelectedColumns dispatch + ] + Html.div [ + SelectiveTemplateFromDBModal.addFromDBToTableButton model selectedColumns dispatch + ] + ], + "relative flex p-4 animated-border shadow-md gap-4 flex-col" + ) From 450cda896515001f745f257ae38fe4090756f717 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Tue, 3 Dec 2024 09:57:55 +0100 Subject: [PATCH 07/36] Enable column selection and import type selection for templates and json files --- src/Client/MainComponents/Widgets.fs | 5 +- src/Client/Modals/ModalElements.fs | 83 ++++++++++++- src/Client/Modals/SelectiveImportModal.fs | 61 +++++----- src/Client/Modals/SelectiveTemplateFromDB.fs | 111 ++++++------------ src/Client/OfficeInterop/OfficeInterop.fs | 67 +++++++++-- .../Pages/ProtocolTemplates/ProtocolView.fs | 1 - .../ProtocolTemplates/TemplateFromFile.fs | 34 +++--- src/Client/States/OfficeInteropState.fs | 2 +- src/Client/States/SpreadsheetInterface.fs | 5 +- src/Client/Update/InterfaceUpdate.fs | 15 ++- src/Client/Update/OfficeInteropUpdate.fs | 4 +- src/Client/Update/UpdateUtil.fs | 24 +++- src/Shared/ARCtrl.Helper.fs | 4 +- src/Shared/DTOs/SelectedColumnsModalDto.fs | 10 ++ src/Shared/Shared.fsproj | 1 + 15 files changed, 262 insertions(+), 165 deletions(-) create mode 100644 src/Shared/DTOs/SelectedColumnsModalDto.fs diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index 6cfd3fe9..452186df 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -5,6 +5,7 @@ open Feliz.DaisyUI open Browser.Types open LocalStorage.Widgets open Modals.Template +open Shared.DTOs.SelectedColumnsModalDto module private InitExtensions = @@ -226,12 +227,12 @@ type Widget = let insertContent() = [ Html.div [ - SelectiveTemplateFromDBModal.displaySelectedProtocolEle model selectedColumns setSelectedColumns dispatch + SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch) ] Html.div [ prop.style [style.maxHeight (length.px 350); style.overflow.auto] prop.children [ - SelectiveTemplateFromDBModal.displaySelectedProtocolEle model selectedColumns setSelectedColumns dispatch + SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch) ] ] ] diff --git a/src/Client/Modals/ModalElements.fs b/src/Client/Modals/ModalElements.fs index 5bb525e9..3dcde99a 100644 --- a/src/Client/Modals/ModalElements.fs +++ b/src/Client/Modals/ModalElements.fs @@ -5,11 +5,12 @@ open Feliz.DaisyUI open Model open Messages open Shared +open Shared.DTOs.SelectedColumnsModalDto open ARCtrl open JsonImport open Components - +open Fable.React.Helpers type ModalElements = @@ -72,7 +73,7 @@ type ModalElements = ] ] - static member BoxWithChildren(children: ReactElement list, boxClass: string, ?title: string, ?icon: string, ?className: string list) = + 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" @@ -81,7 +82,7 @@ type ModalElements = ] prop.children [ Html.h3 [ - prop.className boxClass + prop.className "font-semibold gap-2 flex flex-row items-center" if icon.IsSome || title.IsSome then prop.children [ if icon.IsSome then @@ -94,13 +95,85 @@ type ModalElements = ] ] - static member ImportRadioPlugins(importType: TableJoinOptions, radioData: (TableJoinOptions * string)[], setImportType: TableJoinOptions -> unit) = + static member SelectorButton<'a when 'a : equality> (targetselector: 'a, selector: 'a, setSelector: 'a -> unit, ?isDisabled) = + Daisy.button.button [ + join.item + if isDisabled.IsSome then + prop.disabled isDisabled.Value + prop.style [style.flexGrow 1] + if (targetselector = selector) then + button.primary + prop.onClick (fun _ -> setSelector targetselector) + prop.text (string targetselector) + ] + + static member RadioPluginsBox(boxName, icon, importType: TableJoinOptions, radioData: (TableJoinOptions * string)[], setImportType: TableJoinOptions -> unit) = let myradio(target: TableJoinOptions, txt: string) = let isChecked = importType = target ModalElements.RadioPlugin("importType", txt, isChecked, fun (b: bool) -> if b then setImportType target) - ModalElements.Box ("Import Type", "fa-solid fa-cog", React.fragment [ + ModalElements.Box (boxName, icon, React.fragment [ Html.div [ for i in 0..radioData.Length-1 do myradio(radioData.[i]) ] ]) + + static member checkBox(columns: CompositeColumn [], index, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit) = + Html.div [ + prop.style [style.display.flex; style.justifyContent.center] + prop.children [ + Daisy.checkbox [ + prop.type'.checkbox + prop.isChecked + (if selectionInformation.Columns.Length > 0 then + selectionInformation.Columns.[index] + else true) + prop.onChange (fun (b: bool) -> + if columns.Length > 0 then + let selectedData = selectionInformation.Columns + selectedData.[index] <- b + {selectionInformation with Columns = selectedData} |> setSelectedColumns) + ] + ] + ] + + static member TableWithImportColumnCheckboxes(table: ArcTable, ?selectionInformation: SelectedColumns, ?setSelectedColumns: SelectedColumns -> unit) = + let columns = table.Columns + let displayCheckBox = + //Determine whether to display checkboxes or not + selectionInformation.IsSome && setSelectedColumns.IsSome + Daisy.table [ + prop.children [ + Html.thead [ + Html.tr [ + for i in 0..columns.Length-1 do + Html.th [ + Html.label [ + prop.className "join flex flex-row centered gap-2" + prop.children [ + if displayCheckBox then ModalElements.checkBox(columns, i, selectionInformation.Value, setSelectedColumns.Value) + Html.text (columns.[i].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) + ] + ] + ] + ] + ] + ] + + Html.tbody [ + for ri in 0 .. (table.RowCount-1) do + let row = table.GetRow(ri, true) + Html.tr [ + for c in row do + Html.td (c.ToString()) + ] + ] + ] + ] + diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index dd70ee28..4b2582cf 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -5,6 +5,7 @@ open Feliz.DaisyUI open Model open Messages open Shared +open Shared.DTOs.SelectedColumnsModalDto open ARCtrl open JsonImport @@ -45,7 +46,7 @@ type SelectiveImportModal = ) [] - static member private TableImport(index: int, table0: ArcTable, state: SelectiveImportModalState, addTableImport: int -> bool -> unit, rmvTableImport: int -> unit) = + static member private TableImport(index: 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) @@ -72,36 +73,21 @@ 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.text "Preview Table" + prop.className "p-1 min-h-0 h-5 text-sm" + prop.text (if isActive then "Select Columns" else "Preview Table") ] Daisy.collapseContent [ prop.className "overflow-x-auto" prop.children [ - Daisy.table [ - table.xs - prop.children [ - Html.thead [ - Html.tr [ - for c in table0.Headers do - Html.th (c.ToString()) - ] - ] - Html.tbody [ - for ri in 0 .. (table0.RowCount-1) do - let row = table0.GetRow(ri, true) - Html.tr [ - for c in row do - Html.td (c.ToString()) - ] - ] - ] - ] + if isActive then + ModalElements.TableWithImportColumnCheckboxes(table0, selectedColumns, setSelectedColumns) + else + ModalElements.TableWithImportColumnCheckboxes(table0) ] ] - ]], - className = [if isActive then "!bg-primary !text-primary-content"] - ) + ] + ], + className = [if isActive then "!bg-primary !text-primary-content"]) [] static member Main(import: ArcFiles, dispatch, rmv) = @@ -116,17 +102,21 @@ type SelectiveImportModal = if b then { state with - ImportMetadata = true; - ImportTables = [ for ti in 0 .. tables.Count-1 do {ImportTable.Index = ti; ImportTable.FullImport = true}] + ImportMetadata = true; + ImportTables = [for ti in 0 .. tables.Count-1 do {ImportTable.Index = ti; ImportTable.FullImport = true}] } |> setState else SelectiveImportModalState.init() |> setState - let addTableImport = fun (i:int) (fullImport: bool) -> + 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 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)) Daisy.modal.div [ modal.active prop.children [ @@ -141,25 +131,28 @@ type SelectiveImportModal = Components.DeleteButton(props=[prop.onClick rmv]) ] ] - ModalElements.ImportRadioPlugins( + ModalElements.RadioPluginsBox( + "Import Type", + "fa-solid fa-cog", state.ImportType, [| - ARCtrl.TableJoinOptions.Headers, " Column Headers"; - ARCtrl.TableJoinOptions.WithUnit, " ..With Units"; + ARCtrl.TableJoinOptions.Headers, " Column Headers"; + ARCtrl.TableJoinOptions.WithUnit, " ..With Units"; ARCtrl.TableJoinOptions.WithValues, " ..With Values"; |], - fun it -> {state with ImportType = it} |> setState) + fun importType -> {state with ImportType = importType} |> setState) SelectiveImportModal.MetadataImport(state.ImportMetadata, setMetadataImport, disArcfile) for ti in 0 .. (tables.Count-1) do let t = tables.[ti] - SelectiveImportModal.TableImport(ti, t, state, addTableImport, rmvTableImport) + let selectedColumns, setSelectedColumns = fst selectedColumns.[ti], snd selectedColumns.[ti] + SelectiveImportModal.TableImport(ti, t, state, addTableImport, rmvTableImport, selectedColumns, setSelectedColumns) Daisy.cardActions [ Daisy.button.button [ button.info prop.style [style.marginLeft length.auto] prop.text "Submit" prop.onClick(fun e -> - {| importState = state; importedFile = import|} |> SpreadsheetInterface.ImportJson |> InterfaceMsg |> dispatch + {| importState = state; importedFile = import; selectedColumns = selectedColumns |} |> SpreadsheetInterface.ImportJson |> InterfaceMsg |> dispatch rmv e ) ] diff --git a/src/Client/Modals/SelectiveTemplateFromDB.fs b/src/Client/Modals/SelectiveTemplateFromDB.fs index fc3bfb97..9a7c003e 100644 --- a/src/Client/Modals/SelectiveTemplateFromDB.fs +++ b/src/Client/Modals/SelectiveTemplateFromDB.fs @@ -5,6 +5,7 @@ open Feliz.DaisyUI open Model open Messages open Shared +open Shared.DTOs.SelectedColumnsModalDto open ARCtrl open JsonImport @@ -13,16 +14,7 @@ open Components open Modals open Modals.ModalElements -type SelectedColumns = { - Columns: bool [] -} -with - static member init(length) = - { - Columns = Array.init length (fun _ -> true) - } - -type SelectiveTemplateFromDBModal = +type SelectiveTemplateFromDBModal = static member private LogicContainer (children: ReactElement list) = Html.div [ @@ -31,60 +23,27 @@ type SelectiveTemplateFromDBModal = ] [] - static member displaySelectedProtocolEle (model: Model) (selectionInformation:SelectedColumns) (setSelectedColumns:SelectedColumns -> unit) dispatch = + static member displaySelectedProtocolElements (model: Model, 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 [ - Daisy.table [ - prop.children [ - Html.thead [ - Html.tr [ - Html.th "Selection" - Html.th "Column" - Html.th "Column TAN" - ] - ] - Html.tbody [ - let length = - if model.ProtocolState.TemplateSelected.IsSome then model.ProtocolState.TemplateSelected.Value.Table.Columns.Length-1 - else 0 - for i in 0..length do - let column = model.ProtocolState.TemplateSelected.Value.Table.Columns.[i] - yield - Html.tr [ - Html.div [ - prop.style [style.display.flex; style.justifyContent.center] - prop.children [ - Daisy.checkbox [ - prop.type'.checkbox - prop.isChecked - (if selectionInformation.Columns.Length > 0 then - selectionInformation.Columns.[i] - else true) - prop.onChange (fun (b: bool) -> - if selectionInformation.Columns.Length > 0 then - let selectedData = selectionInformation.Columns - selectedData.[i] <- b - {selectionInformation with Columns = selectedData} |> setSelectedColumns) - ] - ] - ] - Html.td (column.Header.ToString()) - Html.td (if column.Header.IsTermColumn then column.Header.ToTerm().TermAccessionShort else "-") - ] - ] - ] - ] + if model.ProtocolState.TemplateSelected.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 + ModalElements.TableWithImportColumnCheckboxes(model.ProtocolState.TemplateSelected.Value.Table, selectionInformation, setSelectedColumns) ] ] - static member addFromDBToTableButton (model: Model) selectionInformation dispatch = + static member addFromDBToTableButton (model: Model) selectionInformation importType dispatch = let addTemplate (templatePot: Template option, selectedColumns) = if model.ProtocolState.TemplateSelected.IsNone then failwith "No template selected!" if templatePot.IsSome then let table = templatePot.Value.Table - SpreadsheetInterface.AddTemplate(table, selectedColumns) |> InterfaceMsg |> dispatch + SpreadsheetInterface.AddTemplate(table, selectedColumns, importType) |> InterfaceMsg |> dispatch Html.div [ prop.className "join flex flex-row justify-center gap-2" prop.children [ @@ -106,26 +65,32 @@ type SelectiveTemplateFromDBModal = model.ProtocolState.TemplateSelected.Value.Table.Columns.Length else 0 let selectedColumns, setSelectedColumns = React.useState(SelectedColumns.init length) - ModalElements.BoxWithChildren( - [ - Html.div [ - Daisy.button.button [ - prop.onClick(fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) - button.primary - button.block - prop.text "Browse database" - ] - ] - Html.div [ - SelectiveTemplateFromDBModal.addFromDBToTableButton model selectedColumns dispatch + let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) + Html.div [ + Html.div [ + Daisy.button.button [ + prop.onClick(fun _ -> UpdateModel { model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch } |> dispatch) + button.primary + button.block + prop.text "Browse database" ] if model.ProtocolState.TemplateSelected.IsSome then - Html.div [ - SelectiveTemplateFromDBModal.displaySelectedProtocolEle model selectedColumns setSelectedColumns dispatch - ] - Html.div [ - SelectiveTemplateFromDBModal.addFromDBToTableButton model selectedColumns dispatch + SelectiveTemplateFromDBModal.LogicContainer [ + Html.div [ + SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch, false) + ModalElements.RadioPluginsBox( + "Import Type", + "fa-solid fa-cog", + importTypeState.ImportType, + [| + ARCtrl.TableJoinOptions.Headers, " Column Headers"; + ARCtrl.TableJoinOptions.WithUnit, " ..With Units"; + ARCtrl.TableJoinOptions.WithValues, " ..With Values"; + |], + fun importType -> {importTypeState with ImportType = importType} |> setImportTypeState + ) + ] ] - ], - "relative flex p-4 animated-border shadow-md gap-4 flex-col" - ) + SelectiveTemplateFromDBModal.addFromDBToTableButton model selectedColumns importTypeState dispatch + ] + ] diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index 30ce67e5..e7af6c86 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -687,6 +687,16 @@ module AnnotationTable = |> Array.ofSeq |> Array.map (fun header -> header.ToString()) + /// + /// Get headers from range + /// + /// + let getBody (range: ResizeArray>) = + range + |> Array.ofSeq + |> (fun items -> items.[1..]) + |> ResizeArray + /// /// Try find annotation table in active worksheet and parse to ArcTable /// @@ -920,6 +930,39 @@ 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 |> 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 = + 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 /// @@ -965,21 +1008,16 @@ let prepareTemplateInMemory (table: Table) (tableToAdd: ArcTable) (selectedColum /// /// /// -let joinTable (tableToAdd: ArcTable, selectedColumns:bool [], options: TableJoinOptions option) = +let joinTable (tableToAdd: ArcTable, selectedColumns: bool [], options: TableJoinOptions option) = Excel.run(fun context -> promise { - - let columns = tableToAdd.Columns - - columns - //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 match result with | Some excelTable -> - let! (tableToAdd: ArcTable, index: int option) = prepareTemplateInMemory excelTable tableToAdd selectedColumns context + let! (refinedTableToAdd: ArcTable, index: int option) = prepareTemplateInMemory 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) @@ -987,11 +1025,11 @@ let joinTable (tableToAdd: ArcTable, selectedColumns:bool [], options: TableJoin //When both tables could be accessed succesfully then check what kind of column shall be added an whether it is already there or not match arcTableRes with | Result.Ok arcTable -> - arcTable.Join(tableToAdd, ?index=index, ?joinOptions=options, forceReplace=true) + arcTable.Join(refinedTableToAdd, ?index=index, ?joinOptions=options, forceReplace=true) let newTableRange = excelTable.getRange() - let _ = newTableRange.load(propertyNames = U2.Case2 (ResizeArray["rowCount";])) + let _ = newTableRange.load(propertyNames = U2.Case2 (ResizeArray["rowCount"])) do! context.sync().``then``(fun _ -> excelTable.delete() @@ -999,14 +1037,16 @@ let joinTable (tableToAdd: ArcTable, selectedColumns:bool [], options: TableJoin let! (newTable, _) = AnnotationTable.createAtRange(false, false, newTableRange, context) - let _ = newTable.load(propertyNames = U2.Case2 (ResizeArray["name"; "values"; "columns";])) + let _ = newTable.load(propertyNames = U2.Case2 (ResizeArray["name"; "values"; "columns"])) + + let tableSeqs = arcTable.ToStringSeqs() do! context.sync().``then``(fun _ -> newTable.name <- excelTable.name let headerNames = - let names = AnnotationTable.getHeaders (arcTable.ToStringSeqs()) + let names = AnnotationTable.getHeaders tableSeqs names |> Array.map (fun name -> extendName names name) @@ -1035,9 +1075,12 @@ let joinTable (tableToAdd: ArcTable, selectedColumns:bool [], options: TableJoin do! context.sync() + let bodyValues = AnnotationTable.getBody tableSeqs + newBodyRange.values <- bodyValues + do! AnnotationTable.format(newTable, context, true) - return [InteropLogging.Msg.create InteropLogging.Warning $"Joined template {tableToAdd.Name} to table {excelTable.name}!"] + return [InteropLogging.Msg.create InteropLogging.Warning $"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] diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolView.fs b/src/Client/Pages/ProtocolTemplates/ProtocolView.fs index a65ee6a7..bf28532e 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolView.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolView.fs @@ -27,7 +27,6 @@ type Templates = SidebarComponents.SidebarLayout.Container [ SidebarComponents.SidebarLayout.Header "Templates" - SidebarComponents.SidebarLayout.Description (Html.p [ Html.b "Search the database for templates." Html.text " The building blocks from these templates can be inserted into the Swate table. " diff --git a/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs b/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs index 8c81319d..524cbf24 100644 --- a/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs +++ b/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs @@ -19,6 +19,8 @@ open Shared open ARCtrl open Fable.Core.JsInterop +open Modals.ModalElements + type private TemplateFromFileState = { /// User select type to upload FileType: ArcFilesDiscriminate @@ -80,18 +82,6 @@ type TemplateFromFile = ) ] - static member private SelectorButton<'a when 'a : equality> (targetselector: 'a, selector: 'a, setSelector: 'a -> unit, ?isDisabled) = - Daisy.button.button [ - join.item - if isDisabled.IsSome then - prop.disabled isDisabled.Value - prop.style [style.flexGrow 1] - if (targetselector = selector) then - button.primary - prop.onClick (fun _ -> setSelector targetselector) - prop.text (string targetselector) - ] - [] static member Main(model: Model, dispatch) = let state, setState = React.useState(TemplateFromFileState.init) @@ -116,13 +106,17 @@ type TemplateFromFile = Modals.Import.SelectiveImportModal.Main (af, dispatch, rmv = (fun _ -> TemplateFromFileState.init() |> setState)) | None -> Html.none Html.div [ + SidebarComponents.SidebarLayout.Description (Html.p [ + Html.b "Import JSON files." + Html.text " You can use \"Json Export\" to create these files from existing Swate tables. " + ]) Daisy.join [ prop.className "w-full" prop.children [ - JsonExportFormat.ROCrate |> fun jef -> TemplateFromFile.SelectorButton (jef, state.JsonFormat, setJsonFormat, jsonFormatDisabled jef) - JsonExportFormat.ISA |> fun jef -> TemplateFromFile.SelectorButton (jef, state.JsonFormat, setJsonFormat, jsonFormatDisabled jef) - JsonExportFormat.ARCtrl |> fun jef -> TemplateFromFile.SelectorButton (jef, state.JsonFormat, setJsonFormat, jsonFormatDisabled jef) - JsonExportFormat.ARCtrlCompressed |> fun jef -> TemplateFromFile.SelectorButton (jef, state.JsonFormat, setJsonFormat, jsonFormatDisabled jef) + JsonExportFormat.ROCrate |> fun jef -> ModalElements.SelectorButton (jef, state.JsonFormat, setJsonFormat, jsonFormatDisabled jef) + JsonExportFormat.ISA |> fun jef -> ModalElements.SelectorButton (jef, state.JsonFormat, setJsonFormat, jsonFormatDisabled jef) + JsonExportFormat.ARCtrl |> fun jef -> ModalElements.SelectorButton (jef, state.JsonFormat, setJsonFormat, jsonFormatDisabled jef) + JsonExportFormat.ARCtrlCompressed |> fun jef -> ModalElements.SelectorButton (jef, state.JsonFormat, setJsonFormat, jsonFormatDisabled jef) ] ] ] @@ -131,10 +125,10 @@ type TemplateFromFile = Daisy.join [ prop.className "w-full" prop.children [ - ArcFilesDiscriminate.Assay |> fun ft -> TemplateFromFile.SelectorButton (ft, state.FileType, setFileType, fileTypeDisabled ft) - ArcFilesDiscriminate.Study |> fun ft -> TemplateFromFile.SelectorButton (ft, state.FileType, setFileType, fileTypeDisabled ft) - ArcFilesDiscriminate.Investigation |> fun ft -> TemplateFromFile.SelectorButton (ft, state.FileType, setFileType, fileTypeDisabled ft) - ArcFilesDiscriminate.Template |> fun ft -> TemplateFromFile.SelectorButton (ft, state.FileType, setFileType, fileTypeDisabled ft) + ArcFilesDiscriminate.Assay |> fun ft -> ModalElements.SelectorButton (ft, state.FileType, setFileType, fileTypeDisabled ft) + ArcFilesDiscriminate.Study |> fun ft -> ModalElements.SelectorButton (ft, state.FileType, setFileType, fileTypeDisabled ft) + ArcFilesDiscriminate.Investigation |> fun ft -> ModalElements.SelectorButton (ft, state.FileType, setFileType, fileTypeDisabled ft) + ArcFilesDiscriminate.Template |> fun ft -> ModalElements.SelectorButton (ft, state.FileType, setFileType, fileTypeDisabled ft) ] ] ] diff --git a/src/Client/States/OfficeInteropState.fs b/src/Client/States/OfficeInteropState.fs index 1f8bf8d6..e509900a 100644 --- a/src/Client/States/OfficeInteropState.fs +++ b/src/Client/States/OfficeInteropState.fs @@ -31,7 +31,7 @@ type Msg = | ValidateBuildingBlock | AddAnnotationBlock of CompositeColumn | AddAnnotationBlocks of CompositeColumn [] //* OfficeInterop.Types.Xml.ValidationTypes.TableValidation option - | AddTemplate of ArcTable * bool[] + | AddTemplate of ArcTable * bool[] * SelectiveImportModalState | JoinTable of ArcTable * options: TableJoinOptions option | RemoveBuildingBlock | UpdateUnitForCells diff --git a/src/Client/States/SpreadsheetInterface.fs b/src/Client/States/SpreadsheetInterface.fs index 968e1aef..24186201 100644 --- a/src/Client/States/SpreadsheetInterface.fs +++ b/src/Client/States/SpreadsheetInterface.fs @@ -1,6 +1,7 @@ namespace SpreadsheetInterface open Shared +open Shared.DTOs.SelectedColumnsModalDto open ARCtrl open JsonImport @@ -17,14 +18,14 @@ type Msg = | AddAnnotationBlocks of CompositeColumn [] | 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[] +| AddTemplate 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|} +| ImportJson of {|importState: SelectiveImportModalState; importedFile: ArcFiles; selectedColumns: (SelectedColumns * (SelectedColumns -> unit))[]|} /// Starts chain to export active table to isa json | ExportJson of ArcFiles * JsonExportFormat | UpdateUnitForCells diff --git a/src/Client/Update/InterfaceUpdate.fs b/src/Client/Update/InterfaceUpdate.fs index 3008a332..e2966e57 100644 --- a/src/Client/Update/InterfaceUpdate.fs +++ b/src/Client/Update/InterfaceUpdate.fs @@ -164,10 +164,10 @@ module Interface = model, cmd | _ -> failwith "not implemented" - | AddTemplate (table, selectedColumns) -> + | AddTemplate (table, selectedColumns, importType) -> match host with | Some Swatehost.Excel -> - let cmd = OfficeInterop.AddTemplate (table, selectedColumns) |> OfficeInteropMsg |> Cmd.ofMsg + let cmd = OfficeInterop.AddTemplate (table, selectedColumns, importType) |> OfficeInteropMsg |> Cmd.ofMsg model, cmd | Some Swatehost.Browser | Some Swatehost.ARCitect -> let cmd = Spreadsheet.AddTemplate (table, selectedColumns) |> SpreadsheetMsg |> Cmd.ofMsg @@ -201,6 +201,9 @@ module Interface = model, cmd | _ -> failwith "not implemented" | ImportJson data -> + let selectedColumns = + data.selectedColumns + |> Array.map (fun (sc, _) -> sc) match host with | Some Swatehost.Excel -> /// In Excel we must get the current information from worksheets and update them with the imported information @@ -208,7 +211,7 @@ module Interface = promise { match data.importState.ImportMetadata with | true -> // full import, does not require additional information - return UpdateUtil.JsonImportHelper.updateWithMetadata data.importedFile data.importState + return UpdateUtil.JsonImportHelper.updateWithMetadata data.importedFile data.importState selectedColumns | false -> // partial import, requires additional information let! arcfile = OfficeInterop.Core.Main.tryParseToArcFile() let arcfileOpt = arcfile |> Result.toOption @@ -217,9 +220,9 @@ module Interface = ) let activeTableIndex = match arcfileOpt, activeTable with - | Some arcfile, Ok activeTable -> arcfile.Tables() |> Seq.tryFindIndex (fun x -> x = activeTable) + | Some arcfile, Ok activeTable -> arcfile.Tables() |> Seq.tryFindIndex (fun table -> table = activeTable) | _ -> None - return UpdateUtil.JsonImportHelper.updateTables data.importedFile data.importState activeTableIndex arcfileOpt + return UpdateUtil.JsonImportHelper.updateTables data.importedFile data.importState (*selectedColumns*) activeTableIndex arcfileOpt } let updateArcFile (arcFile: ArcFiles) = SpreadsheetInterface.UpdateArcFile arcFile |> InterfaceMsg let cmd = @@ -232,7 +235,7 @@ module Interface = | Some Swatehost.Browser | Some Swatehost.ARCitect -> let cmd = match data.importState.ImportMetadata with - | true -> UpdateUtil.JsonImportHelper.updateWithMetadata data.importedFile data.importState + | true -> UpdateUtil.JsonImportHelper.updateWithMetadata data.importedFile data.importState selectedColumns | false -> UpdateUtil.JsonImportHelper.updateTables data.importedFile data.importState model.SpreadsheetModel.ActiveView.TryTableIndex model.SpreadsheetModel.ArcFile |> SpreadsheetInterface.UpdateArcFile |> InterfaceMsg |> Cmd.ofMsg model, cmd diff --git a/src/Client/Update/OfficeInteropUpdate.fs b/src/Client/Update/OfficeInteropUpdate.fs index d013e664..71be2cd5 100644 --- a/src/Client/Update/OfficeInteropUpdate.fs +++ b/src/Client/Update/OfficeInteropUpdate.fs @@ -76,11 +76,11 @@ module OfficeInterop = UpdateUtil.downloadFromString (jsonExport) state, model, Cmd.none - | AddTemplate (table, selectedColumns) -> + | AddTemplate (table, selectedColumns, importType) -> let cmd = Cmd.OfPromise.either OfficeInterop.Core.joinTable - (table, selectedColumns, Some ARCtrl.TableJoinOptions.WithValues) + (table, selectedColumns, Some importType.ImportType) (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 6295fa63..e2efdeba 100644 --- a/src/Client/Update/UpdateUtil.fs +++ b/src/Client/Update/UpdateUtil.fs @@ -2,6 +2,7 @@ module Update.UpdateUtil open ARCtrl open Shared +open Shared.DTOs.SelectedColumnsModalDto open Fable.Remoting.Client let download(filename, bytes:byte []) = bytes.SaveFileAs(filename) @@ -15,21 +16,34 @@ module JsonImportHelper = open ARCtrl open JsonImport - let updateWithMetadata (uploadedFile: ArcFiles) (state: SelectiveImportModalState) = + 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 it in state.ImportTables do - let sourceTable = arcTables.[it.Index] + for importTable in state.ImportTables do + + let selectedColumn = selectedColumns.[importTable.Index] + let selectedColumnIndices = + selectedColumn.Columns + |> Array.mapi (fun i item -> if item = false then Some i else None) + |> Array.choose (fun x -> x) + |> List.ofArray + + let sourceTable = arcTables.[importTable.Index] let appliedTable = ArcTable.init(sourceTable.Name) - appliedTable.Join(sourceTable, joinOptions=state.ImportType) + + 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 let arcFile = match uploadedFile with - | Assay a as arcFile-> + | Assay a as arcFile -> let tables = createUpdatedTables a.Tables a.Tables <- tables arcFile diff --git a/src/Shared/ARCtrl.Helper.fs b/src/Shared/ARCtrl.Helper.fs index 83999645..c04bddf4 100644 --- a/src/Shared/ARCtrl.Helper.fs +++ b/src/Shared/ARCtrl.Helper.fs @@ -93,7 +93,7 @@ module Table = /// /// The active/current table /// The new table, which will be added to the existing one. - let selectiveTablePrepare (activeTable: ArcTable) (toJoinTable: ArcTable) (removeColumns:int list): ArcTable = + let selectiveTablePrepare (activeTable: ArcTable) (toJoinTable: ArcTable) (removeColumns: int list): ArcTable = // Remove existing columns let mutable columnsToRemove = removeColumns // find duplicate columns @@ -108,7 +108,7 @@ module Table = tablecopy.RemoveColumns (Array.ofList columnsToRemove) tablecopy.IteriColumns(fun i c0 -> - let c1 = {c0 with Cells = [||]} + let c1 = {c0 with Cells = tablecopy.Columns.[i].Cells} let c2 = if c1.Header.isInput then match activeTable.TryGetInputColumn() with diff --git a/src/Shared/DTOs/SelectedColumnsModalDto.fs b/src/Shared/DTOs/SelectedColumnsModalDto.fs new file mode 100644 index 00000000..e12a11bb --- /dev/null +++ b/src/Shared/DTOs/SelectedColumnsModalDto.fs @@ -0,0 +1,10 @@ +module Shared.DTOs.SelectedColumnsModalDto + +type SelectedColumns = { + Columns: bool [] +} +with + static member init(length) = + { + Columns = Array.init length (fun _ -> true) + } \ No newline at end of file diff --git a/src/Shared/Shared.fsproj b/src/Shared/Shared.fsproj index a426ba67..9d397f96 100644 --- a/src/Shared/Shared.fsproj +++ b/src/Shared/Shared.fsproj @@ -5,6 +5,7 @@ + From c2064a5a78b023302cca0ae64a64c3cd7ae1cf99 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Tue, 3 Dec 2024 10:57:22 +0100 Subject: [PATCH 08/36] update layout --- src/Client/Modals/SelectiveTemplateFromDB.fs | 13 +++++++++---- .../Pages/ProtocolTemplates/TemplateFromFile.fs | 4 ---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Client/Modals/SelectiveTemplateFromDB.fs b/src/Client/Modals/SelectiveTemplateFromDB.fs index 9a7c003e..f1fbc11e 100644 --- a/src/Client/Modals/SelectiveTemplateFromDB.fs +++ b/src/Client/Modals/SelectiveTemplateFromDB.fs @@ -66,7 +66,7 @@ type SelectiveTemplateFromDBModal = else 0 let selectedColumns, setSelectedColumns = React.useState(SelectedColumns.init length) let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) - Html.div [ + SelectiveTemplateFromDBModal.LogicContainer [ Html.div [ Daisy.button.button [ prop.onClick(fun _ -> UpdateModel { model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch } |> dispatch) @@ -75,9 +75,8 @@ type SelectiveTemplateFromDBModal = prop.text "Browse database" ] if model.ProtocolState.TemplateSelected.IsSome then - SelectiveTemplateFromDBModal.LogicContainer [ + Html.div [ Html.div [ - SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch, false) ModalElements.RadioPluginsBox( "Import Type", "fa-solid fa-cog", @@ -89,8 +88,14 @@ type SelectiveTemplateFromDBModal = |], fun importType -> {importTypeState with ImportType = importType} |> setImportTypeState ) + ModalElements.Box( + model.ProtocolState.TemplateSelected.Value.Name, + "", + SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch, false)) ] ] - SelectiveTemplateFromDBModal.addFromDBToTableButton model selectedColumns importTypeState dispatch + Html.div [ + SelectiveTemplateFromDBModal.addFromDBToTableButton model selectedColumns importTypeState dispatch + ] ] ] diff --git a/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs b/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs index 524cbf24..efd3dd3f 100644 --- a/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs +++ b/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs @@ -106,10 +106,6 @@ type TemplateFromFile = Modals.Import.SelectiveImportModal.Main (af, dispatch, rmv = (fun _ -> TemplateFromFileState.init() |> setState)) | None -> Html.none Html.div [ - SidebarComponents.SidebarLayout.Description (Html.p [ - Html.b "Import JSON files." - Html.text " You can use \"Json Export\" to create these files from existing Swate tables. " - ]) Daisy.join [ prop.className "w-full" prop.children [ From 4d11c9a2d5118da92980dddf926b6e72e9f55601 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Tue, 3 Dec 2024 11:07:40 +0100 Subject: [PATCH 09/36] Improve layout by adding gaps --- src/Client/Modals/SelectiveTemplateFromDB.fs | 55 +++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/src/Client/Modals/SelectiveTemplateFromDB.fs b/src/Client/Modals/SelectiveTemplateFromDB.fs index f1fbc11e..c40ce431 100644 --- a/src/Client/Modals/SelectiveTemplateFromDB.fs +++ b/src/Client/Modals/SelectiveTemplateFromDB.fs @@ -22,6 +22,14 @@ type SelectiveTemplateFromDBModal = prop.children children ] + static member toProtocolSearchElement (model: Model) dispatch = + Daisy.button.button [ + prop.onClick(fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) + button.primary + button.block + prop.text "Browse database" + ] + [] static member displaySelectedProtocolElements (model: Model, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit, dispatch, ?hasIcon: bool) = let hasIcon = defaultArg hasIcon true @@ -68,34 +76,29 @@ type SelectiveTemplateFromDBModal = let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) SelectiveTemplateFromDBModal.LogicContainer [ Html.div [ - Daisy.button.button [ - prop.onClick(fun _ -> UpdateModel { model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch } |> dispatch) - button.primary - button.block - prop.text "Browse database" + SelectiveTemplateFromDBModal.toProtocolSearchElement model dispatch + ] + if model.ProtocolState.TemplateSelected.IsSome then + Html.div [ + ModalElements.RadioPluginsBox( + "Import Type", + "fa-solid fa-cog", + importTypeState.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 then - Html.div [ - Html.div [ - ModalElements.RadioPluginsBox( - "Import Type", - "fa-solid fa-cog", - importTypeState.ImportType, - [| - ARCtrl.TableJoinOptions.Headers, " Column Headers"; - ARCtrl.TableJoinOptions.WithUnit, " ..With Units"; - ARCtrl.TableJoinOptions.WithValues, " ..With Values"; - |], - fun importType -> {importTypeState with ImportType = importType} |> setImportTypeState - ) - ModalElements.Box( - model.ProtocolState.TemplateSelected.Value.Name, - "", - SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch, false)) - ] - ] Html.div [ - SelectiveTemplateFromDBModal.addFromDBToTableButton model selectedColumns importTypeState dispatch + ModalElements.Box( + model.ProtocolState.TemplateSelected.Value.Name, + "", + SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch, false)) ] + Html.div [ + SelectiveTemplateFromDBModal.addFromDBToTableButton model selectedColumns importTypeState dispatch ] ] From 2cf77988c569a84f8c6217d0cc0f7f07cf13c7c1 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Tue, 3 Dec 2024 11:56:07 +0100 Subject: [PATCH 10/36] Adapted Cehckbox height --- src/Client/Modals/ModalElements.fs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Client/Modals/ModalElements.fs b/src/Client/Modals/ModalElements.fs index 3dcde99a..2977473b 100644 --- a/src/Client/Modals/ModalElements.fs +++ b/src/Client/Modals/ModalElements.fs @@ -124,6 +124,9 @@ type ModalElements = prop.children [ Daisy.checkbox [ prop.type'.checkbox + prop.style [ + style.height(length.perc 100) + ] prop.isChecked (if selectionInformation.Columns.Length > 0 then selectionInformation.Columns.[index] @@ -151,7 +154,8 @@ type ModalElements = Html.label [ prop.className "join flex flex-row centered gap-2" prop.children [ - if displayCheckBox then ModalElements.checkBox(columns, i, selectionInformation.Value, setSelectedColumns.Value) + if displayCheckBox then + ModalElements.checkBox(columns, i, selectionInformation.Value, setSelectedColumns.Value) Html.text (columns.[i].Header.ToString()) Html.div [ prop.onClick (fun e -> From ace120f792b4ebd76c65ab0bde0c90aeb85a5f37 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Tue, 3 Dec 2024 15:05:05 +0100 Subject: [PATCH 11/36] Apply review changes --- src/Client/Client.fable-temp.csproj | 154 ------------------ src/Client/Modals/ModalElements.fs | 16 +- src/Client/Modals/SelectiveImportModal.fs | 7 +- src/Client/Modals/SelectiveTemplateFromDB.fs | 23 +-- .../Pages/BuildingBlock/BuildingBlockView.fs | 5 +- src/Client/SidebarComponents/LayoutHelper.fs | 15 +- 6 files changed, 28 insertions(+), 192 deletions(-) delete mode 100644 src/Client/Client.fable-temp.csproj diff --git a/src/Client/Client.fable-temp.csproj b/src/Client/Client.fable-temp.csproj deleted file mode 100644 index d1678662..00000000 --- a/src/Client/Client.fable-temp.csproj +++ /dev/null @@ -1,154 +0,0 @@ - - - - - net8.0 - FABLE_COMPILER - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Client/Modals/ModalElements.fs b/src/Client/Modals/ModalElements.fs index 2977473b..86622897 100644 --- a/src/Client/Modals/ModalElements.fs +++ b/src/Client/Modals/ModalElements.fs @@ -1,4 +1,4 @@ -module Modals.ModalElements +module Modals open Feliz open Feliz.DaisyUI @@ -14,6 +14,20 @@ open Fable.React.Helpers type ModalElements = + static member LogicContainer (children: ReactElement list) = + Html.div [ + // prop.className "border-l-4 border-transparent px-4 py-2 shadow-md" + // prop.style [ + // let rndVal = rnd.Next(30,70) + // let colorArr = [|NFDIColors.LightBlue.Lighter10; NFDIColors.Mint.Lighter10;|] + // style.custom("borderImageSlice", "1") + // style.custom("borderImageSource", $"linear-gradient({colorArr.[if order then 0 else 1]} {100-rndVal}%%, {colorArr.[if order then 1 else 0]})") + // order <- not order + // ] + prop.className "relative flex p-4 animated-border shadow-md gap-4 flex-col" //experimental + prop.children children + ] + static member Button(text: string, onClickAction, buttonInput, ?isDisabled: bool) = let isDisabled = defaultArg isDisabled false Daisy.button.a [ diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index 4b2582cf..c0166d94 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -1,4 +1,4 @@ -namespace Modals.Import +namespace Modals open Feliz open Feliz.DaisyUI @@ -11,9 +11,6 @@ open ARCtrl open JsonImport open Components -open Modals -open Modals.ModalElements - type SelectiveImportModal = static member private MetadataImport(isActive: bool, setActive: bool -> unit, disArcFile: ArcFilesDiscriminate) = @@ -90,7 +87,7 @@ type SelectiveImportModal = className = [if isActive then "!bg-primary !text-primary-content"]) [] - static member Main(import: ArcFiles, dispatch, rmv) = + static member Main (import: ArcFiles, dispatch, rmv) = let state, setState = React.useState(SelectiveImportModalState.init) let tables, disArcfile = match import with diff --git a/src/Client/Modals/SelectiveTemplateFromDB.fs b/src/Client/Modals/SelectiveTemplateFromDB.fs index c40ce431..13a4aeb3 100644 --- a/src/Client/Modals/SelectiveTemplateFromDB.fs +++ b/src/Client/Modals/SelectiveTemplateFromDB.fs @@ -1,4 +1,4 @@ -namespace Modals.Template +namespace Modals open Feliz open Feliz.DaisyUI @@ -11,18 +11,9 @@ open ARCtrl open JsonImport open Components -open Modals -open Modals.ModalElements - type SelectiveTemplateFromDBModal = - static member private LogicContainer (children: ReactElement list) = - Html.div [ - prop.className "relative flex p-4 animated-border shadow-md gap-4 flex-col" //experimental - prop.children children - ] - - 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 @@ -45,7 +36,7 @@ type SelectiveTemplateFromDBModal = ] ] - static member addFromDBToTableButton (model: Model) selectionInformation importType dispatch = + static member AddFromDBToTableButton (model: Model) selectionInformation importType dispatch = let addTemplate (templatePot: Template option, selectedColumns) = if model.ProtocolState.TemplateSelected.IsNone then failwith "No template selected!" @@ -67,16 +58,16 @@ 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 else 0 let selectedColumns, setSelectedColumns = React.useState(SelectedColumns.init length) let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) - SelectiveTemplateFromDBModal.LogicContainer [ + ModalElements.LogicContainer [ Html.div [ - SelectiveTemplateFromDBModal.toProtocolSearchElement model dispatch + SelectiveTemplateFromDBModal.ToProtocolSearchElement model dispatch ] if model.ProtocolState.TemplateSelected.IsSome then Html.div [ @@ -99,6 +90,6 @@ type SelectiveTemplateFromDBModal = SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch, false)) ] Html.div [ - SelectiveTemplateFromDBModal.addFromDBToTableButton model selectedColumns importTypeState dispatch + SelectiveTemplateFromDBModal.AddFromDBToTableButton model selectedColumns importTypeState dispatch ] ] diff --git a/src/Client/Pages/BuildingBlock/BuildingBlockView.fs b/src/Client/Pages/BuildingBlock/BuildingBlockView.fs index bb4b0e15..f1caa011 100644 --- a/src/Client/Pages/BuildingBlock/BuildingBlockView.fs +++ b/src/Client/Pages/BuildingBlock/BuildingBlockView.fs @@ -4,6 +4,7 @@ open Model open Messages open Messages.BuildingBlock open Shared +open Modals open Elmish @@ -81,13 +82,13 @@ let addBuildingBlockComponent (model:Model) (dispatch:Messages.Msg -> unit) = // Input forms, etc related to add building block. SidebarComponents.SidebarLayout.Description "Add annotation building blocks (columns) to the annotation table." - SidebarComponents.SidebarLayout.LogicContainer [ + ModalElements.LogicContainer [ SearchComponent.Main model dispatch ] match model.PersistentStorageState.Host with | Some Swatehost.Excel -> Html.p "Convert existing Building Block." - SidebarComponents.SidebarLayout.LogicContainer [ + ModalElements.LogicContainer [ CellConvertComponent.Main () ] | _ -> Html.none diff --git a/src/Client/SidebarComponents/LayoutHelper.fs b/src/Client/SidebarComponents/LayoutHelper.fs index 692f5e89..c8fda28e 100644 --- a/src/Client/SidebarComponents/LayoutHelper.fs +++ b/src/Client/SidebarComponents/LayoutHelper.fs @@ -20,26 +20,13 @@ module private LayoutHelperAux = if v > 5 then false else true type SidebarLayout = + static member Container (children: ReactElement list) = Html.div [ prop.className "flex flex-col gap-2 py-4" prop.children children ] - static member LogicContainer (children: ReactElement list) = - Html.div [ - // prop.className "border-l-4 border-transparent px-4 py-2 shadow-md" - // prop.style [ - // let rndVal = rnd.Next(30,70) - // let colorArr = [|NFDIColors.LightBlue.Lighter10; NFDIColors.Mint.Lighter10;|] - // style.custom("borderImageSlice", "1") - // style.custom("borderImageSource", $"linear-gradient({colorArr.[if order then 0 else 1]} {100-rndVal}%%, {colorArr.[if order then 1 else 0]})") - // order <- not order - // ] - prop.className "relative flex p-4 animated-border shadow-md gap-4 flex-col" //experimental - prop.children children - ] - static member Header(txt: string) = Html.h3 [ prop.className "text-lg font-semibold" From 849b155c5d206e651e61339a49986b14b7405655 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Tue, 3 Dec 2024 15:13:19 +0100 Subject: [PATCH 12/36] Implemente review changes --- src/Client/Client.fsproj | 2 +- src/Client/Modals/ModalElements.fs | 75 ------------------- src/Client/Modals/SelectiveImportModal.fs | 4 +- src/Client/Modals/SelectiveTemplateFromDB.fs | 76 +++++++++++++++++++- 4 files changed, 79 insertions(+), 78 deletions(-) diff --git a/src/Client/Client.fsproj b/src/Client/Client.fsproj index 50e238c0..6daac2d3 100644 --- a/src/Client/Client.fsproj +++ b/src/Client/Client.fsproj @@ -52,8 +52,8 @@ - + diff --git a/src/Client/Modals/ModalElements.fs b/src/Client/Modals/ModalElements.fs index 86622897..217b9f00 100644 --- a/src/Client/Modals/ModalElements.fs +++ b/src/Client/Modals/ModalElements.fs @@ -120,78 +120,3 @@ type ModalElements = prop.onClick (fun _ -> setSelector targetselector) prop.text (string targetselector) ] - - static member RadioPluginsBox(boxName, icon, importType: TableJoinOptions, radioData: (TableJoinOptions * string)[], setImportType: TableJoinOptions -> unit) = - let myradio(target: TableJoinOptions, txt: string) = - let isChecked = importType = target - ModalElements.RadioPlugin("importType", txt, isChecked, fun (b: bool) -> if b then setImportType target) - ModalElements.Box (boxName, icon, React.fragment [ - Html.div [ - for i in 0..radioData.Length-1 do - myradio(radioData.[i]) - ] - ]) - - static member checkBox(columns: CompositeColumn [], index, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit) = - Html.div [ - prop.style [style.display.flex; style.justifyContent.center] - prop.children [ - Daisy.checkbox [ - prop.type'.checkbox - prop.style [ - style.height(length.perc 100) - ] - prop.isChecked - (if selectionInformation.Columns.Length > 0 then - selectionInformation.Columns.[index] - else true) - prop.onChange (fun (b: bool) -> - if columns.Length > 0 then - let selectedData = selectionInformation.Columns - selectedData.[index] <- b - {selectionInformation with Columns = selectedData} |> setSelectedColumns) - ] - ] - ] - - static member TableWithImportColumnCheckboxes(table: ArcTable, ?selectionInformation: SelectedColumns, ?setSelectedColumns: SelectedColumns -> unit) = - let columns = table.Columns - let displayCheckBox = - //Determine whether to display checkboxes or not - selectionInformation.IsSome && setSelectedColumns.IsSome - Daisy.table [ - prop.children [ - Html.thead [ - Html.tr [ - for i in 0..columns.Length-1 do - Html.th [ - Html.label [ - prop.className "join flex flex-row centered gap-2" - prop.children [ - if displayCheckBox then - ModalElements.checkBox(columns, i, selectionInformation.Value, setSelectedColumns.Value) - Html.text (columns.[i].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) - ] - ] - ] - ] - ] - ] - - Html.tbody [ - for ri in 0 .. (table.RowCount-1) do - let row = table.GetRow(ri, true) - Html.tr [ - for c in row do - Html.td (c.ToString()) - ] - ] - ] - ] - diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index c0166d94..609ef5cc 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -11,6 +11,8 @@ open ARCtrl open JsonImport open Components +open Modals + type SelectiveImportModal = static member private MetadataImport(isActive: bool, setActive: bool -> unit, disArcFile: ArcFilesDiscriminate) = @@ -128,7 +130,7 @@ type SelectiveImportModal = Components.DeleteButton(props=[prop.onClick rmv]) ] ] - ModalElements.RadioPluginsBox( + SelectiveTemplateFromDBModal.RadioPluginsBox( "Import Type", "fa-solid fa-cog", state.ImportType, diff --git a/src/Client/Modals/SelectiveTemplateFromDB.fs b/src/Client/Modals/SelectiveTemplateFromDB.fs index 13a4aeb3..f12429e2 100644 --- a/src/Client/Modals/SelectiveTemplateFromDB.fs +++ b/src/Client/Modals/SelectiveTemplateFromDB.fs @@ -13,6 +13,80 @@ open Components type SelectiveTemplateFromDBModal = + static member RadioPluginsBox(boxName, icon, importType: TableJoinOptions, radioData: (TableJoinOptions * string)[], setImportType: TableJoinOptions -> unit) = + let myradio(target: TableJoinOptions, txt: string) = + let isChecked = importType = target + ModalElements.RadioPlugin("importType", txt, isChecked, fun (b: bool) -> if b then setImportType target) + ModalElements.Box (boxName, icon, React.fragment [ + Html.div [ + for i in 0..radioData.Length-1 do + myradio(radioData.[i]) + ] + ]) + + static member CheckBoxForTableColumnSelection(columns: CompositeColumn [], index, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit) = + Html.div [ + prop.style [style.display.flex; style.justifyContent.center] + prop.children [ + Daisy.checkbox [ + prop.type'.checkbox + prop.style [ + style.height(length.perc 100) + ] + prop.isChecked + (if selectionInformation.Columns.Length > 0 then + selectionInformation.Columns.[index] + else true) + prop.onChange (fun (b: bool) -> + if columns.Length > 0 then + let selectedData = selectionInformation.Columns + selectedData.[index] <- b + {selectionInformation with Columns = selectedData} |> setSelectedColumns) + ] + ] + ] + + static member TableWithImportColumnCheckboxes(table: ArcTable, ?selectionInformation: SelectedColumns, ?setSelectedColumns: SelectedColumns -> unit) = + let columns = table.Columns + let displayCheckBox = + //Determine whether to display checkboxes or not + selectionInformation.IsSome && setSelectedColumns.IsSome + Daisy.table [ + prop.children [ + Html.thead [ + Html.tr [ + for i in 0..columns.Length-1 do + Html.th [ + Html.label [ + prop.className "join flex flex-row centered gap-2" + prop.children [ + if displayCheckBox then + SelectiveTemplateFromDBModal.checkBox(columns, i, selectionInformation.Value, setSelectedColumns.Value) + Html.text (columns.[i].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) + ] + ] + ] + ] + ] + ] + + Html.tbody [ + for ri in 0 .. (table.RowCount-1) do + let row = table.GetRow(ri, true) + Html.tr [ + for c in row do + Html.td (c.ToString()) + ] + ] + ] + ] + static member ToProtocolSearchElement (model: Model) dispatch = Daisy.button.button [ prop.onClick(fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) @@ -71,7 +145,7 @@ type SelectiveTemplateFromDBModal = ] if model.ProtocolState.TemplateSelected.IsSome then Html.div [ - ModalElements.RadioPluginsBox( + SelectiveTemplateFromDBModal.RadioPluginsBox( "Import Type", "fa-solid fa-cog", importTypeState.ImportType, From 869ade6f9f5cde20024ca14a69db1c37c68a9acd Mon Sep 17 00:00:00 2001 From: patrick blume Date: Tue, 3 Dec 2024 15:14:46 +0100 Subject: [PATCH 13/36] Implemented review suggestions --- src/Client/Modals/SelectiveImportModal.fs | 4 ++-- src/Client/Modals/SelectiveTemplateFromDB.fs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index 609ef5cc..99d73a44 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -79,9 +79,9 @@ type SelectiveImportModal = prop.className "overflow-x-auto" prop.children [ if isActive then - ModalElements.TableWithImportColumnCheckboxes(table0, selectedColumns, setSelectedColumns) + SelectiveTemplateFromDBModal.TableWithImportColumnCheckboxes(table0, selectedColumns, setSelectedColumns) else - ModalElements.TableWithImportColumnCheckboxes(table0) + SelectiveTemplateFromDBModal.TableWithImportColumnCheckboxes(table0) ] ] ] diff --git a/src/Client/Modals/SelectiveTemplateFromDB.fs b/src/Client/Modals/SelectiveTemplateFromDB.fs index f12429e2..1baf811d 100644 --- a/src/Client/Modals/SelectiveTemplateFromDB.fs +++ b/src/Client/Modals/SelectiveTemplateFromDB.fs @@ -61,7 +61,7 @@ type SelectiveTemplateFromDBModal = prop.className "join flex flex-row centered gap-2" prop.children [ if displayCheckBox then - SelectiveTemplateFromDBModal.checkBox(columns, i, selectionInformation.Value, setSelectedColumns.Value) + SelectiveTemplateFromDBModal.CheckBoxForTableColumnSelection(columns, i, selectionInformation.Value, setSelectedColumns.Value) Html.text (columns.[i].Header.ToString()) Html.div [ prop.onClick (fun e -> @@ -106,7 +106,7 @@ type SelectiveTemplateFromDBModal = Html.i [prop.className "fa-solid fa-cog"] Html.span $"Template: {model.ProtocolState.TemplateSelected.Value.Name}" if model.ProtocolState.TemplateSelected.IsSome then - ModalElements.TableWithImportColumnCheckboxes(model.ProtocolState.TemplateSelected.Value.Table, selectionInformation, setSelectedColumns) + SelectiveTemplateFromDBModal.TableWithImportColumnCheckboxes(model.ProtocolState.TemplateSelected.Value.Table, selectionInformation, setSelectedColumns) ] ] From ca0a1d40654836e9df021b78d40037773989090c Mon Sep 17 00:00:00 2001 From: patrick blume Date: Tue, 3 Dec 2024 15:25:56 +0100 Subject: [PATCH 14/36] Implement review changes --- src/Client/Client.fsproj | 1 + src/Client/MainComponents/Widgets.fs | 4 ++-- src/Client/Modals/ModalElements.fs | 2 +- src/Client/Modals/ModalProvider.fs | 4 ++-- src/Client/Modals/SelectiveImportModal.fs | 4 +--- src/Client/Modals/SelectiveTemplateFromDB.fs | 2 +- src/Client/Pages/DataAnnotator/DataAnnotator.fs | 3 ++- src/Client/Pages/FilePicker/FilePickerView.fs | 4 +++- src/Client/Pages/JsonExporter/JsonExporter.fs | 4 +++- src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs | 4 +++- src/Client/Pages/ProtocolTemplates/ProtocolView.fs | 2 +- src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs | 6 +++--- src/Client/Pages/TermSearch/TermSearchView.fs | 3 ++- .../States/SelectedColumns.fs} | 5 +++-- src/Client/States/SpreadsheetInterface.fs | 2 +- src/Client/Update/UpdateUtil.fs | 2 +- src/Shared/Shared.fs | 10 +++++----- src/Shared/Shared.fsproj | 1 - 18 files changed, 35 insertions(+), 28 deletions(-) rename src/{Shared/DTOs/SelectedColumnsModalDto.fs => Client/States/SelectedColumns.fs} (74%) diff --git a/src/Client/Client.fsproj b/src/Client/Client.fsproj index 6daac2d3..138d64eb 100644 --- a/src/Client/Client.fsproj +++ b/src/Client/Client.fsproj @@ -23,6 +23,7 @@ + diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index 452186df..d63493f7 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -4,8 +4,8 @@ open Feliz open Feliz.DaisyUI open Browser.Types open LocalStorage.Widgets -open Modals.Template -open Shared.DTOs.SelectedColumnsModalDto +open Modals +open SelectedColumns module private InitExtensions = diff --git a/src/Client/Modals/ModalElements.fs b/src/Client/Modals/ModalElements.fs index 217b9f00..34dd3be3 100644 --- a/src/Client/Modals/ModalElements.fs +++ b/src/Client/Modals/ModalElements.fs @@ -5,7 +5,7 @@ open Feliz.DaisyUI open Model open Messages open Shared -open Shared.DTOs.SelectedColumnsModalDto +open SelectedColumns open ARCtrl open JsonImport diff --git a/src/Client/Modals/ModalProvider.fs b/src/Client/Modals/ModalProvider.fs index 8f5c34d2..57e8a2ab 100644 --- a/src/Client/Modals/ModalProvider.fs +++ b/src/Client/Modals/ModalProvider.fs @@ -23,9 +23,9 @@ type ModalProvider = | TableModals.TermDetails term -> Modals.TermModal.Main (term, dispatch) | TableModals.SelectiveTemplateImportFromDB -> - Modals.Template.SelectiveTemplateFromDBModal.Main (model, dispatch) + Modals.SelectiveTemplateFromDBModal.Main (model, dispatch) | TableModals.SelectiveFileImport arcfile -> - Modals.Import.SelectiveImportModal.Main (arcfile, dispatch) + Modals.SelectiveImportModal.Main (arcfile, dispatch) | TableModals.BatchUpdateColumnValues (columnIndex, column) -> Modals.UpdateColumn.Main (columnIndex, column, dispatch) | TableModals.TableCellContext (mouseX, mouseY, ci, ri) -> diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index 99d73a44..339b9299 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -5,14 +5,12 @@ open Feliz.DaisyUI open Model open Messages open Shared -open Shared.DTOs.SelectedColumnsModalDto +open SelectedColumns open ARCtrl open JsonImport open Components -open Modals - type SelectiveImportModal = static member private MetadataImport(isActive: bool, setActive: bool -> unit, disArcFile: ArcFilesDiscriminate) = diff --git a/src/Client/Modals/SelectiveTemplateFromDB.fs b/src/Client/Modals/SelectiveTemplateFromDB.fs index 1baf811d..b8437b8c 100644 --- a/src/Client/Modals/SelectiveTemplateFromDB.fs +++ b/src/Client/Modals/SelectiveTemplateFromDB.fs @@ -5,7 +5,7 @@ open Feliz.DaisyUI open Model open Messages open Shared -open Shared.DTOs.SelectedColumnsModalDto +open SelectedColumns open ARCtrl open JsonImport diff --git a/src/Client/Pages/DataAnnotator/DataAnnotator.fs b/src/Client/Pages/DataAnnotator/DataAnnotator.fs index 0df2ac5f..ebbd71b8 100644 --- a/src/Client/Pages/DataAnnotator/DataAnnotator.fs +++ b/src/Client/Pages/DataAnnotator/DataAnnotator.fs @@ -6,6 +6,7 @@ open Model open Messages open Feliz open Feliz.DaisyUI +open Modals module private DataAnnotatorHelper = @@ -342,7 +343,7 @@ type DataAnnotator = SidebarComponents.SidebarLayout.Description "Specify exact data points for annotation." - SidebarComponents.SidebarLayout.LogicContainer [ + ModalElements.LogicContainer [ ModalMangementComponent ref model (fun _ -> setShowModal true) rmvFile uploadFile match model.DataAnnotatorModel, showModal with | { DataFile = Some _; ParsedFile = Some _ }, true -> DataAnnotator.Modal(model, dispatch, rmvFile, fun _ -> setShowModal false) diff --git a/src/Client/Pages/FilePicker/FilePickerView.fs b/src/Client/Pages/FilePicker/FilePickerView.fs index 4dbf8fe7..3e91745e 100644 --- a/src/Client/Pages/FilePicker/FilePickerView.fs +++ b/src/Client/Pages/FilePicker/FilePickerView.fs @@ -8,6 +8,8 @@ open Messages open Feliz open Feliz.DaisyUI +open Modals + let update (filePickerMsg:FilePicker.Msg) (state: FilePicker.Model) (model: Model.Model) : FilePicker.Model * Cmd = match filePickerMsg with | LoadNewFiles fileNames -> @@ -216,7 +218,7 @@ module FileNameTable = let fileContainer (model:Model) dispatch = - SidebarComponents.SidebarLayout.LogicContainer [ + ModalElements.LogicContainer [ uploadButton model dispatch "@md/sidebar:flex-row" diff --git a/src/Client/Pages/JsonExporter/JsonExporter.fs b/src/Client/Pages/JsonExporter/JsonExporter.fs index f14cffe0..50ca09b5 100644 --- a/src/Client/Pages/JsonExporter/JsonExporter.fs +++ b/src/Client/Pages/JsonExporter/JsonExporter.fs @@ -21,6 +21,8 @@ open GlobalBindings open ARCtrl open ARCtrl.Spreadsheet +open Modals + let download(filename, text) = let element = document.createElement("a"); element.setAttribute("href", "data:text/plain;charset=utf-8," + Fable.Core.JS.encodeURIComponent(text)); @@ -139,7 +141,7 @@ type FileExporter = ] ] ]) - SidebarComponents.SidebarLayout.LogicContainer [ + ModalElements.LogicContainer [ FileExporter.JsonExport(model, dispatch) ] ] diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs index 78dab3e2..bda07645 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs @@ -7,6 +7,8 @@ open Messages open Feliz open Feliz.DaisyUI +open Modals + module private HelperProtocolSearch = let breadcrumbEle (model:Model) dispatch = @@ -53,7 +55,7 @@ type SearchContainer = Html.p "Search the database for protocol templates." - SidebarComponents.SidebarLayout.LogicContainer [ + ModalElements.LogicContainer [ Protocol.Search.InfoField() Protocol.Search.FileSortElement(model, config, setConfig) Protocol.Search.Component (filteredTemplates, model, dispatch) diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolView.fs b/src/Client/Pages/ProtocolTemplates/ProtocolView.fs index bf28532e..8ad27ee5 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.Template.SelectiveTemplateFromDBModal.Main(model, dispatch) + Modals.SelectiveTemplateFromDBModal.Main(model, dispatch) // Box 2 SidebarComponents.SidebarLayout.Description (Html.p [ diff --git a/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs b/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs index efd3dd3f..5ef922a3 100644 --- a/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs +++ b/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs @@ -19,7 +19,7 @@ open Shared open ARCtrl open Fable.Core.JsInterop -open Modals.ModalElements +open Modals type private TemplateFromFileState = { /// User select type to upload @@ -99,11 +99,11 @@ type TemplateFromFile = | ArcFilesDiscriminate.Template, JsonExportFormat.ROCrate | ArcFilesDiscriminate.Template, JsonExportFormat.ISA -> true | _ -> false - SidebarComponents.SidebarLayout.LogicContainer [ + ModalElements.LogicContainer [ // modal! match state.UploadedFile with | Some af -> - Modals.Import.SelectiveImportModal.Main (af, dispatch, rmv = (fun _ -> TemplateFromFileState.init() |> setState)) + Modals.SelectiveImportModal.Main (af, dispatch, rmv = (fun _ -> TemplateFromFileState.init() |> setState)) | None -> Html.none Html.div [ Daisy.join [ diff --git a/src/Client/Pages/TermSearch/TermSearchView.fs b/src/Client/Pages/TermSearch/TermSearchView.fs index d9cff126..5fb05392 100644 --- a/src/Client/Pages/TermSearch/TermSearchView.fs +++ b/src/Client/Pages/TermSearch/TermSearchView.fs @@ -8,6 +8,7 @@ open Elmish open TermSearch open Model +open Modals let update (termSearchMsg: TermSearch.Msg) (currentState:TermSearch.Model) : TermSearch.Model * Cmd = match termSearchMsg with @@ -100,7 +101,7 @@ let Main (model:Model, dispatch) = SidebarComponents.SidebarLayout.Description "Search for an ontology term to fill into the selected field(s)" - SidebarComponents.SidebarLayout.LogicContainer [ + ModalElements.LogicContainer [ Components.TermSearch.Input(setTerm, fullwidth=true, ?parent=model.TermSearchState.ParentTerm, advancedSearchDispatch=dispatch, ?onFocus=excelGetParentTerm, autofocus=true) addButton(model, dispatch) ] diff --git a/src/Shared/DTOs/SelectedColumnsModalDto.fs b/src/Client/States/SelectedColumns.fs similarity index 74% rename from src/Shared/DTOs/SelectedColumnsModalDto.fs rename to src/Client/States/SelectedColumns.fs index e12a11bb..d2e97768 100644 --- a/src/Shared/DTOs/SelectedColumnsModalDto.fs +++ b/src/Client/States/SelectedColumns.fs @@ -1,4 +1,4 @@ -module Shared.DTOs.SelectedColumnsModalDto +module SelectedColumns type SelectedColumns = { Columns: bool [] @@ -7,4 +7,5 @@ with static member init(length) = { Columns = Array.init length (fun _ -> true) - } \ No newline at end of file + } + diff --git a/src/Client/States/SpreadsheetInterface.fs b/src/Client/States/SpreadsheetInterface.fs index 24186201..bc486e78 100644 --- a/src/Client/States/SpreadsheetInterface.fs +++ b/src/Client/States/SpreadsheetInterface.fs @@ -1,7 +1,7 @@ namespace SpreadsheetInterface open Shared -open Shared.DTOs.SelectedColumnsModalDto +open SelectedColumns open ARCtrl open JsonImport diff --git a/src/Client/Update/UpdateUtil.fs b/src/Client/Update/UpdateUtil.fs index e2efdeba..3f3a6a21 100644 --- a/src/Client/Update/UpdateUtil.fs +++ b/src/Client/Update/UpdateUtil.fs @@ -2,7 +2,7 @@ module Update.UpdateUtil open ARCtrl open Shared -open Shared.DTOs.SelectedColumnsModalDto +open SelectedColumns open Fable.Remoting.Client let download(filename, bytes:byte []) = bytes.SaveFileAs(filename) diff --git a/src/Shared/Shared.fs b/src/Shared/Shared.fs index 028c701a..3ee48e0a 100644 --- a/src/Shared/Shared.fs +++ b/src/Shared/Shared.fs @@ -234,14 +234,14 @@ type IOntologyAPIv1 = { /// getTermSuggestions : (int*string) -> Async /// (nOfReturnedResults*queryString*parentOntology). If parentOntology = "" then isNull -> Error. - getTermSuggestionsByParentTerm : (int*string*SwateObsolete.TermMinimal) -> Async - getAllTermsByParentTerm : SwateObsolete.TermMinimal -> Async + getTermSuggestionsByParentTerm : (int*string*SwateObsolete.TermMinimal) -> Async + getAllTermsByParentTerm : SwateObsolete.TermMinimal -> Async /// (nOfReturnedResults*queryString*parentOntology). If parentOntology = "" then isNull -> Error. - getTermSuggestionsByChildTerm : (int*string*SwateObsolete.TermMinimal) -> Async - getAllTermsByChildTerm : SwateObsolete.TermMinimal -> Async + getTermSuggestionsByChildTerm : (int*string*SwateObsolete.TermMinimal) -> Async + getAllTermsByChildTerm : SwateObsolete.TermMinimal -> Async getTermsForAdvancedSearch : (AdvancedSearchTypes.AdvancedSearchOptions) -> Async getUnitTermSuggestions : (int*string) -> Async - getTermsByNames : SwateObsolete.TermSearchable [] -> Async + getTermsByNames : SwateObsolete.TermSearchable [] -> Async // Tree related requests getTreeByAccession : string -> Async diff --git a/src/Shared/Shared.fsproj b/src/Shared/Shared.fsproj index 9d397f96..a426ba67 100644 --- a/src/Shared/Shared.fsproj +++ b/src/Shared/Shared.fsproj @@ -5,7 +5,6 @@ - From 6ddefa415b68129d1ccc0ee7e13d7db4089037e2 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Tue, 3 Dec 2024 17:22:23 +0100 Subject: [PATCH 15/36] Implement review changes --- src/Client/Client.fsproj | 1 - src/Client/GenericComponents.fs | 15 +++++++++++++++ src/Client/MainComponents/Widgets.fs | 2 +- src/Client/Modals/ModalElements.fs | 18 ++---------------- src/Client/Modals/SelectiveImportModal.fs | 2 +- src/Client/Modals/SelectiveTemplateFromDB.fs | 4 ++-- .../Pages/BuildingBlock/BuildingBlockView.fs | 6 +++--- .../Pages/DataAnnotator/DataAnnotator.fs | 2 +- src/Client/Pages/FilePicker/FilePickerView.fs | 4 ++-- src/Client/Pages/JsonExporter/JsonExporter.fs | 4 ++-- .../Pages/ProtocolTemplates/ProtocolSearch.fs | 4 ++-- .../ProtocolTemplates/TemplateFromFile.fs | 3 ++- src/Client/Pages/TermSearch/TermSearchView.fs | 4 ++-- src/Client/States/SelectedColumns.fs | 11 ----------- src/Client/States/SpreadsheetInterface.fs | 2 +- src/Client/Types.fs | 13 ++++++++++++- src/Client/Update/UpdateUtil.fs | 2 +- 17 files changed, 49 insertions(+), 48 deletions(-) delete mode 100644 src/Client/States/SelectedColumns.fs diff --git a/src/Client/Client.fsproj b/src/Client/Client.fsproj index 138d64eb..6daac2d3 100644 --- a/src/Client/Client.fsproj +++ b/src/Client/Client.fsproj @@ -23,7 +23,6 @@ - diff --git a/src/Client/GenericComponents.fs b/src/Client/GenericComponents.fs index 179d3539..e36a9499 100644 --- a/src/Client/GenericComponents.fs +++ b/src/Client/GenericComponents.fs @@ -10,6 +10,21 @@ module DaisyUiExtensions = static member active = prop.className "modal-open" type Components = + + static member LogicContainer (children: ReactElement list) = + Html.div [ + // prop.className "border-l-4 border-transparent px-4 py-2 shadow-md" + // prop.style [ + // let rndVal = rnd.Next(30,70) + // let colorArr = [|NFDIColors.LightBlue.Lighter10; NFDIColors.Mint.Lighter10;|] + // style.custom("borderImageSlice", "1") + // style.custom("borderImageSource", $"linear-gradient({colorArr.[if order then 0 else 1]} {100-rndVal}%%, {colorArr.[if order then 1 else 0]})") + // order <- not order + // ] + prop.className "relative flex p-4 animated-border shadow-md gap-4 flex-col" //experimental + prop.children children + ] + static member DeleteButton(?children, ?props) = Daisy.button.button [ button.square diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index d63493f7..b5aff016 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -5,7 +5,7 @@ open Feliz.DaisyUI open Browser.Types open LocalStorage.Widgets open Modals -open SelectedColumns +open Types.TableManipulation module private InitExtensions = diff --git a/src/Client/Modals/ModalElements.fs b/src/Client/Modals/ModalElements.fs index 34dd3be3..a1f77584 100644 --- a/src/Client/Modals/ModalElements.fs +++ b/src/Client/Modals/ModalElements.fs @@ -1,11 +1,11 @@ -module Modals +namespace Modals open Feliz open Feliz.DaisyUI open Model open Messages open Shared -open SelectedColumns +open Types.TableManipulation open ARCtrl open JsonImport @@ -14,20 +14,6 @@ open Fable.React.Helpers type ModalElements = - static member LogicContainer (children: ReactElement list) = - Html.div [ - // prop.className "border-l-4 border-transparent px-4 py-2 shadow-md" - // prop.style [ - // let rndVal = rnd.Next(30,70) - // let colorArr = [|NFDIColors.LightBlue.Lighter10; NFDIColors.Mint.Lighter10;|] - // style.custom("borderImageSlice", "1") - // style.custom("borderImageSource", $"linear-gradient({colorArr.[if order then 0 else 1]} {100-rndVal}%%, {colorArr.[if order then 1 else 0]})") - // order <- not order - // ] - prop.className "relative flex p-4 animated-border shadow-md gap-4 flex-col" //experimental - prop.children children - ] - static member Button(text: string, onClickAction, buttonInput, ?isDisabled: bool) = let isDisabled = defaultArg isDisabled false Daisy.button.a [ diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index 339b9299..88e7e7f4 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -5,7 +5,7 @@ open Feliz.DaisyUI open Model open Messages open Shared -open SelectedColumns +open Types.TableManipulation open ARCtrl open JsonImport diff --git a/src/Client/Modals/SelectiveTemplateFromDB.fs b/src/Client/Modals/SelectiveTemplateFromDB.fs index b8437b8c..02a2e707 100644 --- a/src/Client/Modals/SelectiveTemplateFromDB.fs +++ b/src/Client/Modals/SelectiveTemplateFromDB.fs @@ -5,7 +5,7 @@ open Feliz.DaisyUI open Model open Messages open Shared -open SelectedColumns +open Types.TableManipulation open ARCtrl open JsonImport @@ -139,7 +139,7 @@ type SelectiveTemplateFromDBModal = else 0 let selectedColumns, setSelectedColumns = React.useState(SelectedColumns.init length) let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) - ModalElements.LogicContainer [ + Components.LogicContainer [ Html.div [ SelectiveTemplateFromDBModal.ToProtocolSearchElement model dispatch ] diff --git a/src/Client/Pages/BuildingBlock/BuildingBlockView.fs b/src/Client/Pages/BuildingBlock/BuildingBlockView.fs index f1caa011..52de0352 100644 --- a/src/Client/Pages/BuildingBlock/BuildingBlockView.fs +++ b/src/Client/Pages/BuildingBlock/BuildingBlockView.fs @@ -4,7 +4,7 @@ open Model open Messages open Messages.BuildingBlock open Shared -open Modals +open Components open Elmish @@ -82,13 +82,13 @@ let addBuildingBlockComponent (model:Model) (dispatch:Messages.Msg -> unit) = // Input forms, etc related to add building block. SidebarComponents.SidebarLayout.Description "Add annotation building blocks (columns) to the annotation table." - ModalElements.LogicContainer [ + Components.LogicContainer [ SearchComponent.Main model dispatch ] match model.PersistentStorageState.Host with | Some Swatehost.Excel -> Html.p "Convert existing Building Block." - ModalElements.LogicContainer [ + Components.LogicContainer [ CellConvertComponent.Main () ] | _ -> Html.none diff --git a/src/Client/Pages/DataAnnotator/DataAnnotator.fs b/src/Client/Pages/DataAnnotator/DataAnnotator.fs index ebbd71b8..ee1e98b7 100644 --- a/src/Client/Pages/DataAnnotator/DataAnnotator.fs +++ b/src/Client/Pages/DataAnnotator/DataAnnotator.fs @@ -343,7 +343,7 @@ type DataAnnotator = SidebarComponents.SidebarLayout.Description "Specify exact data points for annotation." - ModalElements.LogicContainer [ + Components.LogicContainer [ ModalMangementComponent ref model (fun _ -> setShowModal true) rmvFile uploadFile match model.DataAnnotatorModel, showModal with | { DataFile = Some _; ParsedFile = Some _ }, true -> DataAnnotator.Modal(model, dispatch, rmvFile, fun _ -> setShowModal false) diff --git a/src/Client/Pages/FilePicker/FilePickerView.fs b/src/Client/Pages/FilePicker/FilePickerView.fs index 3e91745e..8e709422 100644 --- a/src/Client/Pages/FilePicker/FilePickerView.fs +++ b/src/Client/Pages/FilePicker/FilePickerView.fs @@ -8,7 +8,7 @@ open Messages open Feliz open Feliz.DaisyUI -open Modals +open Components let update (filePickerMsg:FilePicker.Msg) (state: FilePicker.Model) (model: Model.Model) : FilePicker.Model * Cmd = match filePickerMsg with @@ -218,7 +218,7 @@ module FileNameTable = let fileContainer (model:Model) dispatch = - ModalElements.LogicContainer [ + Components.LogicContainer [ uploadButton model dispatch "@md/sidebar:flex-row" diff --git a/src/Client/Pages/JsonExporter/JsonExporter.fs b/src/Client/Pages/JsonExporter/JsonExporter.fs index 50ca09b5..e4f7242b 100644 --- a/src/Client/Pages/JsonExporter/JsonExporter.fs +++ b/src/Client/Pages/JsonExporter/JsonExporter.fs @@ -21,7 +21,7 @@ open GlobalBindings open ARCtrl open ARCtrl.Spreadsheet -open Modals +open Components let download(filename, text) = let element = document.createElement("a"); @@ -141,7 +141,7 @@ type FileExporter = ] ] ]) - ModalElements.LogicContainer [ + Components.LogicContainer [ FileExporter.JsonExport(model, dispatch) ] ] diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs index bda07645..2de85d1b 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs @@ -7,7 +7,7 @@ open Messages open Feliz open Feliz.DaisyUI -open Modals +open Components module private HelperProtocolSearch = @@ -55,7 +55,7 @@ type SearchContainer = Html.p "Search the database for protocol templates." - ModalElements.LogicContainer [ + Components.LogicContainer [ Protocol.Search.InfoField() Protocol.Search.FileSortElement(model, config, setConfig) Protocol.Search.Component (filteredTemplates, model, dispatch) diff --git a/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs b/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs index 5ef922a3..b28e4abc 100644 --- a/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs +++ b/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs @@ -20,6 +20,7 @@ open ARCtrl open Fable.Core.JsInterop open Modals +open Components type private TemplateFromFileState = { /// User select type to upload @@ -99,7 +100,7 @@ type TemplateFromFile = | ArcFilesDiscriminate.Template, JsonExportFormat.ROCrate | ArcFilesDiscriminate.Template, JsonExportFormat.ISA -> true | _ -> false - ModalElements.LogicContainer [ + Components.LogicContainer [ // modal! match state.UploadedFile with | Some af -> diff --git a/src/Client/Pages/TermSearch/TermSearchView.fs b/src/Client/Pages/TermSearch/TermSearchView.fs index 5fb05392..15923cdf 100644 --- a/src/Client/Pages/TermSearch/TermSearchView.fs +++ b/src/Client/Pages/TermSearch/TermSearchView.fs @@ -8,7 +8,7 @@ open Elmish open TermSearch open Model -open Modals +open Components let update (termSearchMsg: TermSearch.Msg) (currentState:TermSearch.Model) : TermSearch.Model * Cmd = match termSearchMsg with @@ -101,7 +101,7 @@ let Main (model:Model, dispatch) = SidebarComponents.SidebarLayout.Description "Search for an ontology term to fill into the selected field(s)" - ModalElements.LogicContainer [ + Components.LogicContainer [ Components.TermSearch.Input(setTerm, fullwidth=true, ?parent=model.TermSearchState.ParentTerm, advancedSearchDispatch=dispatch, ?onFocus=excelGetParentTerm, autofocus=true) addButton(model, dispatch) ] diff --git a/src/Client/States/SelectedColumns.fs b/src/Client/States/SelectedColumns.fs deleted file mode 100644 index d2e97768..00000000 --- a/src/Client/States/SelectedColumns.fs +++ /dev/null @@ -1,11 +0,0 @@ -module SelectedColumns - -type SelectedColumns = { - Columns: bool [] -} -with - static member init(length) = - { - Columns = Array.init length (fun _ -> true) - } - diff --git a/src/Client/States/SpreadsheetInterface.fs b/src/Client/States/SpreadsheetInterface.fs index bc486e78..aa989b02 100644 --- a/src/Client/States/SpreadsheetInterface.fs +++ b/src/Client/States/SpreadsheetInterface.fs @@ -1,7 +1,7 @@ namespace SpreadsheetInterface open Shared -open SelectedColumns +open Types.TableManipulation open ARCtrl open JsonImport diff --git a/src/Client/Types.fs b/src/Client/Types.fs index 5477c772..2acdb8b6 100644 --- a/src/Client/Types.fs +++ b/src/Client/Types.fs @@ -55,4 +55,15 @@ type Style = | Some subClasses -> subClasses.TryFind name | None -> None |> Option.map _.StyleString - |> Option.defaultValue "" \ No newline at end of file + |> Option.defaultValue "" + +module TableManipulation = + + type SelectedColumns = { + Columns: bool [] + } + with + static member init(length) = + { + Columns = Array.init length (fun _ -> true) + } diff --git a/src/Client/Update/UpdateUtil.fs b/src/Client/Update/UpdateUtil.fs index 3f3a6a21..ac13cb52 100644 --- a/src/Client/Update/UpdateUtil.fs +++ b/src/Client/Update/UpdateUtil.fs @@ -2,7 +2,7 @@ module Update.UpdateUtil open ARCtrl open Shared -open SelectedColumns +open Types.TableManipulation open Fable.Remoting.Client let download(filename, bytes:byte []) = bytes.SaveFileAs(filename) From 5feb2b14239555a543c61990e33fd2159c1be3f5 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Wed, 4 Dec 2024 08:50:44 +0100 Subject: [PATCH 16/36] Implement review changes --- src/Client/Client.fsproj | 2 +- src/Client/GenericComponents.fs | 14 ---- src/Client/MainComponents/Widgets.fs | 2 +- src/Client/Modals/ModalElements.fs | 2 +- src/Client/Modals/ModalProvider.fs | 2 - src/Client/Modals/SelectiveImportModal.fs | 82 +++++++++++++++++- src/Client/OfficeInterop/OfficeInterop.fs | 8 +- .../Pages/BuildingBlock/BuildingBlockView.fs | 5 +- .../Pages/DataAnnotator/DataAnnotator.fs | 3 +- src/Client/Pages/FilePicker/FilePickerView.fs | 4 +- src/Client/Pages/JsonExporter/JsonExporter.fs | 4 +- .../Pages/ProtocolTemplates/ProtocolSearch.fs | 4 +- .../SelectiveTemplateFromDB.fs | 83 +------------------ .../ProtocolTemplates/TemplateFromFile.fs | 3 +- src/Client/Pages/TermSearch/TermSearchView.fs | 4 +- src/Client/SidebarComponents/LayoutHelper.fs | 14 ++++ src/Client/States/ModalState.fs | 1 - src/Client/States/SpreadsheetInterface.fs | 2 +- src/Client/Types.fs | 2 +- src/Client/Update/UpdateUtil.fs | 2 +- 20 files changed, 114 insertions(+), 129 deletions(-) rename src/Client/{Modals => Pages/ProtocolTemplates}/SelectiveTemplateFromDB.fs (50%) diff --git a/src/Client/Client.fsproj b/src/Client/Client.fsproj index 6daac2d3..27cb9759 100644 --- a/src/Client/Client.fsproj +++ b/src/Client/Client.fsproj @@ -52,7 +52,6 @@ - @@ -92,6 +91,7 @@ + diff --git a/src/Client/GenericComponents.fs b/src/Client/GenericComponents.fs index e36a9499..eb6d41dd 100644 --- a/src/Client/GenericComponents.fs +++ b/src/Client/GenericComponents.fs @@ -11,20 +11,6 @@ module DaisyUiExtensions = type Components = - static member LogicContainer (children: ReactElement list) = - Html.div [ - // prop.className "border-l-4 border-transparent px-4 py-2 shadow-md" - // prop.style [ - // let rndVal = rnd.Next(30,70) - // let colorArr = [|NFDIColors.LightBlue.Lighter10; NFDIColors.Mint.Lighter10;|] - // style.custom("borderImageSlice", "1") - // style.custom("borderImageSource", $"linear-gradient({colorArr.[if order then 0 else 1]} {100-rndVal}%%, {colorArr.[if order then 1 else 0]})") - // order <- not order - // ] - prop.className "relative flex p-4 animated-border shadow-md gap-4 flex-col" //experimental - prop.children children - ] - static member DeleteButton(?children, ?props) = Daisy.button.button [ button.square diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index b5aff016..344c915f 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -5,7 +5,7 @@ open Feliz.DaisyUI open Browser.Types open LocalStorage.Widgets open Modals -open Types.TableManipulation +open Types.TableImport module private InitExtensions = diff --git a/src/Client/Modals/ModalElements.fs b/src/Client/Modals/ModalElements.fs index a1f77584..22089afa 100644 --- a/src/Client/Modals/ModalElements.fs +++ b/src/Client/Modals/ModalElements.fs @@ -5,7 +5,7 @@ open Feliz.DaisyUI open Model open Messages open Shared -open Types.TableManipulation +open Types.TableImport open ARCtrl open JsonImport diff --git a/src/Client/Modals/ModalProvider.fs b/src/Client/Modals/ModalProvider.fs index 57e8a2ab..5962d320 100644 --- a/src/Client/Modals/ModalProvider.fs +++ b/src/Client/Modals/ModalProvider.fs @@ -22,8 +22,6 @@ type ModalProvider = Modals.ResetTable.Main dispatch | TableModals.TermDetails term -> Modals.TermModal.Main (term, dispatch) - | TableModals.SelectiveTemplateImportFromDB -> - Modals.SelectiveTemplateFromDBModal.Main (model, dispatch) | TableModals.SelectiveFileImport arcfile -> Modals.SelectiveImportModal.Main (arcfile, dispatch) | TableModals.BatchUpdateColumnValues (columnIndex, column) -> diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index 88e7e7f4..30904754 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -5,7 +5,7 @@ open Feliz.DaisyUI open Model open Messages open Shared -open Types.TableManipulation +open Types.TableImport open ARCtrl open JsonImport @@ -13,6 +13,80 @@ open Components type SelectiveImportModal = + static member RadioPluginsBox(boxName, icon, importType: TableJoinOptions, radioData: (TableJoinOptions * string)[], setImportType: TableJoinOptions -> unit) = + let myradio(target: TableJoinOptions, txt: string) = + let isChecked = importType = target + ModalElements.RadioPlugin("importType", txt, isChecked, fun (b: bool) -> if b then setImportType target) + ModalElements.Box (boxName, icon, React.fragment [ + Html.div [ + for i in 0..radioData.Length-1 do + myradio(radioData.[i]) + ] + ]) + + static member CheckBoxForTableColumnSelection(columns: CompositeColumn [], index, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit) = + Html.div [ + prop.style [style.display.flex; style.justifyContent.center] + prop.children [ + Daisy.checkbox [ + prop.type'.checkbox + prop.style [ + style.height(length.perc 100) + ] + prop.isChecked + (if selectionInformation.Columns.Length > 0 then + selectionInformation.Columns.[index] + else true) + prop.onChange (fun (b: bool) -> + if columns.Length > 0 then + let selectedData = selectionInformation.Columns + selectedData.[index] <- b + {selectionInformation with Columns = selectedData} |> setSelectedColumns) + ] + ] + ] + + static member TableWithImportColumnCheckboxes(table: ArcTable, ?selectionInformation: SelectedColumns, ?setSelectedColumns: SelectedColumns -> unit) = + let columns = table.Columns + let displayCheckBox = + //Determine whether to display checkboxes or not + selectionInformation.IsSome && setSelectedColumns.IsSome + Daisy.table [ + prop.children [ + Html.thead [ + Html.tr [ + for i 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()) + 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) + ] + ] + ] + ] + ] + ] + + Html.tbody [ + for ri in 0 .. (table.RowCount-1) do + let row = table.GetRow(ri, true) + Html.tr [ + for c in row do + Html.td (c.ToString()) + ] + ] + ] + ] + static member private MetadataImport(isActive: bool, setActive: bool -> unit, disArcFile: ArcFilesDiscriminate) = let name = string disArcFile ModalElements.Box (sprintf "%s Metadata" name, "fa-solid fa-lightbulb", React.fragment [ @@ -77,9 +151,9 @@ type SelectiveImportModal = prop.className "overflow-x-auto" prop.children [ if isActive then - SelectiveTemplateFromDBModal.TableWithImportColumnCheckboxes(table0, selectedColumns, setSelectedColumns) + SelectiveImportModal.TableWithImportColumnCheckboxes(table0, selectedColumns, setSelectedColumns) else - SelectiveTemplateFromDBModal.TableWithImportColumnCheckboxes(table0) + SelectiveImportModal.TableWithImportColumnCheckboxes(table0) ] ] ] @@ -128,7 +202,7 @@ type SelectiveImportModal = Components.DeleteButton(props=[prop.onClick rmv]) ] ] - SelectiveTemplateFromDBModal.RadioPluginsBox( + SelectiveImportModal.RadioPluginsBox( "Import Type", "fa-solid fa-cog", state.ImportType, diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index e7af6c86..edcf7282 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -1080,7 +1080,7 @@ let joinTable (tableToAdd: ArcTable, selectedColumns: bool [], options: TableJoi do! AnnotationTable.format(newTable, context, true) - return [InteropLogging.Msg.create InteropLogging.Warning $"Joined template {refinedTableToAdd.Name} to table {excelTable.name}!"] + 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] @@ -1522,7 +1522,7 @@ let convertBuildingBlock () = if String.IsNullOrEmpty(msgText) then $"Converted building block of {snd selectedBuildingBlock.[0]} to unit" else msgText - return [InteropLogging.Msg.create InteropLogging.Warning msg] + return [InteropLogging.Msg.create InteropLogging.Info msg] | Result.Error ex -> return [InteropLogging.Msg.create InteropLogging.Error ex.Message] | None -> return [InteropLogging.NoActiveTableMsg] } @@ -1845,7 +1845,7 @@ let deleteTopLevelMetadata () = worksheet.delete() ) - return [InteropLogging.Msg.create InteropLogging.Warning $"The top level metadata work sheet has been deleted"] + return [InteropLogging.Msg.create InteropLogging.Info $"The top level metadata work sheet has been deleted"] } ) @@ -2433,7 +2433,7 @@ type Main = do! updateSelectedBuildingBlocks excelTable arcTable propertyColumns indexedTerms do! AnnotationTable.format(excelTable, context, true) - return [InteropLogging.Msg.create InteropLogging.Warning $"The annotation table {excelTable.name} is valid"] + return [InteropLogging.Msg.create InteropLogging.Info $"The annotation table {excelTable.name} is valid"] | Result.Error ex -> return [InteropLogging.Msg.create InteropLogging.Error ex.Message] } diff --git a/src/Client/Pages/BuildingBlock/BuildingBlockView.fs b/src/Client/Pages/BuildingBlock/BuildingBlockView.fs index 52de0352..bb4b0e15 100644 --- a/src/Client/Pages/BuildingBlock/BuildingBlockView.fs +++ b/src/Client/Pages/BuildingBlock/BuildingBlockView.fs @@ -4,7 +4,6 @@ open Model open Messages open Messages.BuildingBlock open Shared -open Components open Elmish @@ -82,13 +81,13 @@ let addBuildingBlockComponent (model:Model) (dispatch:Messages.Msg -> unit) = // Input forms, etc related to add building block. SidebarComponents.SidebarLayout.Description "Add annotation building blocks (columns) to the annotation table." - Components.LogicContainer [ + SidebarComponents.SidebarLayout.LogicContainer [ SearchComponent.Main model dispatch ] match model.PersistentStorageState.Host with | Some Swatehost.Excel -> Html.p "Convert existing Building Block." - Components.LogicContainer [ + SidebarComponents.SidebarLayout.LogicContainer [ CellConvertComponent.Main () ] | _ -> Html.none diff --git a/src/Client/Pages/DataAnnotator/DataAnnotator.fs b/src/Client/Pages/DataAnnotator/DataAnnotator.fs index ee1e98b7..0df2ac5f 100644 --- a/src/Client/Pages/DataAnnotator/DataAnnotator.fs +++ b/src/Client/Pages/DataAnnotator/DataAnnotator.fs @@ -6,7 +6,6 @@ open Model open Messages open Feliz open Feliz.DaisyUI -open Modals module private DataAnnotatorHelper = @@ -343,7 +342,7 @@ type DataAnnotator = SidebarComponents.SidebarLayout.Description "Specify exact data points for annotation." - Components.LogicContainer [ + SidebarComponents.SidebarLayout.LogicContainer [ ModalMangementComponent ref model (fun _ -> setShowModal true) rmvFile uploadFile match model.DataAnnotatorModel, showModal with | { DataFile = Some _; ParsedFile = Some _ }, true -> DataAnnotator.Modal(model, dispatch, rmvFile, fun _ -> setShowModal false) diff --git a/src/Client/Pages/FilePicker/FilePickerView.fs b/src/Client/Pages/FilePicker/FilePickerView.fs index 8e709422..4dbf8fe7 100644 --- a/src/Client/Pages/FilePicker/FilePickerView.fs +++ b/src/Client/Pages/FilePicker/FilePickerView.fs @@ -8,8 +8,6 @@ open Messages open Feliz open Feliz.DaisyUI -open Components - let update (filePickerMsg:FilePicker.Msg) (state: FilePicker.Model) (model: Model.Model) : FilePicker.Model * Cmd = match filePickerMsg with | LoadNewFiles fileNames -> @@ -218,7 +216,7 @@ module FileNameTable = let fileContainer (model:Model) dispatch = - Components.LogicContainer [ + SidebarComponents.SidebarLayout.LogicContainer [ uploadButton model dispatch "@md/sidebar:flex-row" diff --git a/src/Client/Pages/JsonExporter/JsonExporter.fs b/src/Client/Pages/JsonExporter/JsonExporter.fs index e4f7242b..f14cffe0 100644 --- a/src/Client/Pages/JsonExporter/JsonExporter.fs +++ b/src/Client/Pages/JsonExporter/JsonExporter.fs @@ -21,8 +21,6 @@ open GlobalBindings open ARCtrl open ARCtrl.Spreadsheet -open Components - let download(filename, text) = let element = document.createElement("a"); element.setAttribute("href", "data:text/plain;charset=utf-8," + Fable.Core.JS.encodeURIComponent(text)); @@ -141,7 +139,7 @@ type FileExporter = ] ] ]) - Components.LogicContainer [ + SidebarComponents.SidebarLayout.LogicContainer [ FileExporter.JsonExport(model, dispatch) ] ] diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs index 2de85d1b..78dab3e2 100644 --- a/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs +++ b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs @@ -7,8 +7,6 @@ open Messages open Feliz open Feliz.DaisyUI -open Components - module private HelperProtocolSearch = let breadcrumbEle (model:Model) dispatch = @@ -55,7 +53,7 @@ type SearchContainer = Html.p "Search the database for protocol templates." - Components.LogicContainer [ + SidebarComponents.SidebarLayout.LogicContainer [ Protocol.Search.InfoField() Protocol.Search.FileSortElement(model, config, setConfig) Protocol.Search.Component (filteredTemplates, model, dispatch) diff --git a/src/Client/Modals/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs similarity index 50% rename from src/Client/Modals/SelectiveTemplateFromDB.fs rename to src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index 02a2e707..b3033ae9 100644 --- a/src/Client/Modals/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -5,88 +5,13 @@ open Feliz.DaisyUI open Model open Messages open Shared -open Types.TableManipulation +open Types.TableImport open ARCtrl open JsonImport -open Components type SelectiveTemplateFromDBModal = - static member RadioPluginsBox(boxName, icon, importType: TableJoinOptions, radioData: (TableJoinOptions * string)[], setImportType: TableJoinOptions -> unit) = - let myradio(target: TableJoinOptions, txt: string) = - let isChecked = importType = target - ModalElements.RadioPlugin("importType", txt, isChecked, fun (b: bool) -> if b then setImportType target) - ModalElements.Box (boxName, icon, React.fragment [ - Html.div [ - for i in 0..radioData.Length-1 do - myradio(radioData.[i]) - ] - ]) - - static member CheckBoxForTableColumnSelection(columns: CompositeColumn [], index, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit) = - Html.div [ - prop.style [style.display.flex; style.justifyContent.center] - prop.children [ - Daisy.checkbox [ - prop.type'.checkbox - prop.style [ - style.height(length.perc 100) - ] - prop.isChecked - (if selectionInformation.Columns.Length > 0 then - selectionInformation.Columns.[index] - else true) - prop.onChange (fun (b: bool) -> - if columns.Length > 0 then - let selectedData = selectionInformation.Columns - selectedData.[index] <- b - {selectionInformation with Columns = selectedData} |> setSelectedColumns) - ] - ] - ] - - static member TableWithImportColumnCheckboxes(table: ArcTable, ?selectionInformation: SelectedColumns, ?setSelectedColumns: SelectedColumns -> unit) = - let columns = table.Columns - let displayCheckBox = - //Determine whether to display checkboxes or not - selectionInformation.IsSome && setSelectedColumns.IsSome - Daisy.table [ - prop.children [ - Html.thead [ - Html.tr [ - for i in 0..columns.Length-1 do - Html.th [ - Html.label [ - prop.className "join flex flex-row centered gap-2" - prop.children [ - if displayCheckBox then - SelectiveTemplateFromDBModal.CheckBoxForTableColumnSelection(columns, i, selectionInformation.Value, setSelectedColumns.Value) - Html.text (columns.[i].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) - ] - ] - ] - ] - ] - ] - - Html.tbody [ - for ri in 0 .. (table.RowCount-1) do - let row = table.GetRow(ri, true) - Html.tr [ - for c in row do - Html.td (c.ToString()) - ] - ] - ] - ] - static member ToProtocolSearchElement (model: Model) dispatch = Daisy.button.button [ prop.onClick(fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) @@ -106,7 +31,7 @@ type SelectiveTemplateFromDBModal = Html.i [prop.className "fa-solid fa-cog"] Html.span $"Template: {model.ProtocolState.TemplateSelected.Value.Name}" if model.ProtocolState.TemplateSelected.IsSome then - SelectiveTemplateFromDBModal.TableWithImportColumnCheckboxes(model.ProtocolState.TemplateSelected.Value.Table, selectionInformation, setSelectedColumns) + SelectiveImportModal.TableWithImportColumnCheckboxes(model.ProtocolState.TemplateSelected.Value.Table, selectionInformation, setSelectedColumns) ] ] @@ -139,13 +64,13 @@ type SelectiveTemplateFromDBModal = else 0 let selectedColumns, setSelectedColumns = React.useState(SelectedColumns.init length) let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) - Components.LogicContainer [ + SidebarComponents.SidebarLayout.LogicContainer [ Html.div [ SelectiveTemplateFromDBModal.ToProtocolSearchElement model dispatch ] if model.ProtocolState.TemplateSelected.IsSome then Html.div [ - SelectiveTemplateFromDBModal.RadioPluginsBox( + SelectiveImportModal.RadioPluginsBox( "Import Type", "fa-solid fa-cog", importTypeState.ImportType, diff --git a/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs b/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs index b28e4abc..4052aa35 100644 --- a/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs +++ b/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs @@ -20,7 +20,6 @@ open ARCtrl open Fable.Core.JsInterop open Modals -open Components type private TemplateFromFileState = { /// User select type to upload @@ -100,7 +99,7 @@ type TemplateFromFile = | ArcFilesDiscriminate.Template, JsonExportFormat.ROCrate | ArcFilesDiscriminate.Template, JsonExportFormat.ISA -> true | _ -> false - Components.LogicContainer [ + SidebarComponents.SidebarLayout.LogicContainer [ // modal! match state.UploadedFile with | Some af -> diff --git a/src/Client/Pages/TermSearch/TermSearchView.fs b/src/Client/Pages/TermSearch/TermSearchView.fs index 15923cdf..8302d11c 100644 --- a/src/Client/Pages/TermSearch/TermSearchView.fs +++ b/src/Client/Pages/TermSearch/TermSearchView.fs @@ -8,8 +8,6 @@ open Elmish open TermSearch open Model -open Components - let update (termSearchMsg: TermSearch.Msg) (currentState:TermSearch.Model) : TermSearch.Model * Cmd = match termSearchMsg with // Toggle the search by parent ontology option on/off by clicking on a checkbox @@ -101,7 +99,7 @@ let Main (model:Model, dispatch) = SidebarComponents.SidebarLayout.Description "Search for an ontology term to fill into the selected field(s)" - Components.LogicContainer [ + SidebarComponents.SidebarLayout.LogicContainer [ Components.TermSearch.Input(setTerm, fullwidth=true, ?parent=model.TermSearchState.ParentTerm, advancedSearchDispatch=dispatch, ?onFocus=excelGetParentTerm, autofocus=true) addButton(model, dispatch) ] diff --git a/src/Client/SidebarComponents/LayoutHelper.fs b/src/Client/SidebarComponents/LayoutHelper.fs index c8fda28e..5358322b 100644 --- a/src/Client/SidebarComponents/LayoutHelper.fs +++ b/src/Client/SidebarComponents/LayoutHelper.fs @@ -21,6 +21,20 @@ module private LayoutHelperAux = type SidebarLayout = + static member LogicContainer (children: ReactElement list) = + Html.div [ + // prop.className "border-l-4 border-transparent px-4 py-2 shadow-md" + // prop.style [ + // let rndVal = rnd.Next(30,70) + // let colorArr = [|NFDIColors.LightBlue.Lighter10; NFDIColors.Mint.Lighter10;|] + // style.custom("borderImageSlice", "1") + // style.custom("borderImageSource", $"linear-gradient({colorArr.[if order then 0 else 1]} {100-rndVal}%%, {colorArr.[if order then 1 else 0]})") + // order <- not order + // ] + prop.className "relative flex p-4 animated-border shadow-md gap-4 flex-col" //experimental + prop.children children + ] + static member Container (children: ReactElement list) = Html.div [ prop.className "flex flex-col gap-2 py-4" diff --git a/src/Client/States/ModalState.fs b/src/Client/States/ModalState.fs index bcdb1a3d..982376da 100644 --- a/src/Client/States/ModalState.fs +++ b/src/Client/States/ModalState.fs @@ -13,7 +13,6 @@ module ModalState = | EditColumn of columIndex: int | MoveColumn of columnIndex: int | BatchUpdateColumnValues of columIndex: int * column: CompositeColumn - | SelectiveTemplateImportFromDB | SelectiveFileImport of ArcFiles | TermDetails of OntologyAnnotation | TableCellContext of mouseX: int * mouseY: int * columnIndex: int * rowIndex: int diff --git a/src/Client/States/SpreadsheetInterface.fs b/src/Client/States/SpreadsheetInterface.fs index aa989b02..ed69e50e 100644 --- a/src/Client/States/SpreadsheetInterface.fs +++ b/src/Client/States/SpreadsheetInterface.fs @@ -1,7 +1,7 @@ namespace SpreadsheetInterface open Shared -open Types.TableManipulation +open Types.TableImport open ARCtrl open JsonImport diff --git a/src/Client/Types.fs b/src/Client/Types.fs index 2acdb8b6..36771ca7 100644 --- a/src/Client/Types.fs +++ b/src/Client/Types.fs @@ -57,7 +57,7 @@ type Style = |> Option.map _.StyleString |> Option.defaultValue "" -module TableManipulation = +module TableImport = type SelectedColumns = { Columns: bool [] diff --git a/src/Client/Update/UpdateUtil.fs b/src/Client/Update/UpdateUtil.fs index ac13cb52..3c7c3620 100644 --- a/src/Client/Update/UpdateUtil.fs +++ b/src/Client/Update/UpdateUtil.fs @@ -2,7 +2,7 @@ module Update.UpdateUtil open ARCtrl open Shared -open Types.TableManipulation +open Types.TableImport open Fable.Remoting.Client let download(filename, bytes:byte []) = bytes.SaveFileAs(filename) From 65c36e81c9dc53bdac46564ddd030970e70486db Mon Sep 17 00:00:00 2001 From: patrick blume Date: Wed, 4 Dec 2024 10:02:09 +0100 Subject: [PATCH 17/36] Enable the addition of names in excel --- src/Client/MainComponents/Widgets.fs | 4 +- src/Client/OfficeInterop/OfficeInterop.fs | 8 ++-- .../SelectiveTemplateFromDB.fs | 42 ++++++++++++++++--- src/Client/States/OfficeInteropState.fs | 2 +- src/Client/States/SpreadsheetInterface.fs | 2 +- src/Client/Update/InterfaceUpdate.fs | 4 +- src/Client/Update/OfficeInteropUpdate.fs | 6 +-- 7 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index 344c915f..053234a5 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -227,12 +227,12 @@ type Widget = let insertContent() = [ Html.div [ - SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch) + SelectiveTemplateFromDBModal.displayTableAndSelectionElements(model, selectedColumns, setSelectedColumns, dispatch) ] Html.div [ prop.style [style.maxHeight (length.px 350); style.overflow.auto] prop.children [ - SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch) + SelectiveTemplateFromDBModal.displayTableAndSelectionElements(model, selectedColumns, setSelectedColumns, dispatch) ] ] ] diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index edcf7282..0a81ef3f 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -1008,7 +1008,7 @@ let prepareTemplateInMemory (table: Table) (tableToAdd: ArcTable) (selectedColum /// /// /// -let joinTable (tableToAdd: ArcTable, selectedColumns: bool [], options: TableJoinOptions option) = +let joinTable (tableToAdd: ArcTable, selectedColumns: bool [], options: TableJoinOptions option, useTemplateName) = Excel.run(fun context -> promise { //When a name is available get the annotation and arctable for easy access of indices and value adaption @@ -1042,8 +1042,10 @@ let joinTable (tableToAdd: ArcTable, selectedColumns: bool [], options: TableJoi let tableSeqs = arcTable.ToStringSeqs() do! context.sync().``then``(fun _ -> - - newTable.name <- excelTable.name + if useTemplateName then + newTable.name <- tableToAdd.Name + else + newTable.name <- excelTable.name let headerNames = let names = AnnotationTable.getHeaders tableSeqs diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index b3033ae9..7c5123da 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -10,8 +10,31 @@ open Types.TableImport open ARCtrl open JsonImport +type AdaptTableName = { + UseTemplateName: bool + } + with + static member init() = + { + UseTemplateName = false + } + type SelectiveTemplateFromDBModal = + static member CheckBoxForTakeOverTemplateName(adaptTableName: AdaptTableName, setAdaptTableName: AdaptTableName -> unit, templateName) = + Html.label [ + prop.className "join flex flex-row centered gap-2" + prop.children [ + Daisy.checkbox [ + prop.type'.checkbox + prop.isChecked adaptTableName.UseTemplateName + prop.onChange (fun (b: bool) -> + { adaptTableName with UseTemplateName = b } |> setAdaptTableName) + ] + Html.text $"Use Template name: {templateName}" + ] + ] + static member ToProtocolSearchElement (model: Model) dispatch = Daisy.button.button [ prop.onClick(fun _ -> UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.ProtocolSearch} |> dispatch) @@ -21,7 +44,7 @@ type SelectiveTemplateFromDBModal = ] [] - static member displaySelectedProtocolElements (model: Model, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit, dispatch, ?hasIcon: bool) = + static member displayTableAndSelectionElements (model: Model, 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)] @@ -35,13 +58,13 @@ type SelectiveTemplateFromDBModal = ] ] - static member AddFromDBToTableButton (model: Model) selectionInformation importType dispatch = + static member AddFromDBToTableButton (model: Model) selectionInformation importType useTemplateName dispatch = let addTemplate (templatePot: Template option, selectedColumns) = if model.ProtocolState.TemplateSelected.IsNone then failwith "No template selected!" if templatePot.IsSome then let table = templatePot.Value.Table - SpreadsheetInterface.AddTemplate(table, selectedColumns, importType) |> InterfaceMsg |> dispatch + SpreadsheetInterface.AddTemplate(table, selectedColumns, importType, useTemplateName) |> InterfaceMsg |> dispatch Html.div [ prop.className "join flex flex-row justify-center gap-2" prop.children [ @@ -64,6 +87,7 @@ type SelectiveTemplateFromDBModal = else 0 let selectedColumns, setSelectedColumns = React.useState(SelectedColumns.init length) let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) + let useTemplateName, setUseTemplateName = React.useState(AdaptTableName.init) SidebarComponents.SidebarLayout.LogicContainer [ Html.div [ SelectiveTemplateFromDBModal.ToProtocolSearchElement model dispatch @@ -82,13 +106,19 @@ type SelectiveTemplateFromDBModal = fun importType -> {importTypeState with ImportType = importType} |> setImportTypeState ) ] + Html.div [ + ModalElements.Box( + "Rename Table", + "fa-solid fa-cog", + SelectiveTemplateFromDBModal.CheckBoxForTakeOverTemplateName(useTemplateName, setUseTemplateName, model.ProtocolState.TemplateSelected.Value.Name)) + ] Html.div [ ModalElements.Box( model.ProtocolState.TemplateSelected.Value.Name, - "", - SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch, false)) + "fa-solid fa-cog", + SelectiveTemplateFromDBModal.displayTableAndSelectionElements(model, selectedColumns, setSelectedColumns, dispatch, false)) ] Html.div [ - SelectiveTemplateFromDBModal.AddFromDBToTableButton model selectedColumns importTypeState dispatch + SelectiveTemplateFromDBModal.AddFromDBToTableButton model selectedColumns importTypeState useTemplateName.UseTemplateName dispatch ] ] diff --git a/src/Client/States/OfficeInteropState.fs b/src/Client/States/OfficeInteropState.fs index e509900a..b68bd909 100644 --- a/src/Client/States/OfficeInteropState.fs +++ b/src/Client/States/OfficeInteropState.fs @@ -31,7 +31,7 @@ type Msg = | ValidateBuildingBlock | AddAnnotationBlock of CompositeColumn | AddAnnotationBlocks of CompositeColumn [] //* OfficeInterop.Types.Xml.ValidationTypes.TableValidation option - | AddTemplate of ArcTable * bool[] * SelectiveImportModalState + | AddTemplate of ArcTable * bool[] * SelectiveImportModalState * bool | JoinTable of ArcTable * options: TableJoinOptions option | RemoveBuildingBlock | UpdateUnitForCells diff --git a/src/Client/States/SpreadsheetInterface.fs b/src/Client/States/SpreadsheetInterface.fs index ed69e50e..a4a06b37 100644 --- a/src/Client/States/SpreadsheetInterface.fs +++ b/src/Client/States/SpreadsheetInterface.fs @@ -18,7 +18,7 @@ type Msg = | AddAnnotationBlocks of CompositeColumn [] | 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 +| AddTemplate of ArcTable * bool[] * SelectiveImportModalState * bool | JoinTable of ArcTable * columnIndex: int option * options: TableJoinOptions option | UpdateArcFile of ArcFiles /// Inserts TermMinimal to selected fields of one column diff --git a/src/Client/Update/InterfaceUpdate.fs b/src/Client/Update/InterfaceUpdate.fs index e2966e57..40d52bd4 100644 --- a/src/Client/Update/InterfaceUpdate.fs +++ b/src/Client/Update/InterfaceUpdate.fs @@ -164,10 +164,10 @@ module Interface = model, cmd | _ -> failwith "not implemented" - | AddTemplate (table, selectedColumns, importType) -> + | AddTemplate (table, selectedColumns, importType, useTemplateName) -> match host with | Some Swatehost.Excel -> - let cmd = OfficeInterop.AddTemplate (table, selectedColumns, importType) |> OfficeInteropMsg |> Cmd.ofMsg + let cmd = OfficeInterop.AddTemplate (table, selectedColumns, importType, useTemplateName) |> OfficeInteropMsg |> Cmd.ofMsg model, cmd | Some Swatehost.Browser | Some Swatehost.ARCitect -> let cmd = Spreadsheet.AddTemplate (table, selectedColumns) |> SpreadsheetMsg |> Cmd.ofMsg diff --git a/src/Client/Update/OfficeInteropUpdate.fs b/src/Client/Update/OfficeInteropUpdate.fs index 71be2cd5..2daf8efb 100644 --- a/src/Client/Update/OfficeInteropUpdate.fs +++ b/src/Client/Update/OfficeInteropUpdate.fs @@ -76,11 +76,11 @@ module OfficeInterop = UpdateUtil.downloadFromString (jsonExport) state, model, Cmd.none - | AddTemplate (table, selectedColumns, importType) -> + | AddTemplate (table, selectedColumns, importType, useTemplateName) -> let cmd = Cmd.OfPromise.either OfficeInterop.Core.joinTable - (table, selectedColumns, Some importType.ImportType) + (table, selectedColumns, Some importType.ImportType, useTemplateName) (curry GenericInteropLogs Cmd.none >> DevMsg) (curry GenericError Cmd.none >> DevMsg) state, model, cmd @@ -89,7 +89,7 @@ module OfficeInterop = let cmd = Cmd.OfPromise.either OfficeInterop.Core.joinTable - (table, [||], options) + (table, [||], options, false) (curry GenericInteropLogs Cmd.none >> DevMsg) (curry GenericError Cmd.none >> DevMsg) state, model, cmd From 9cf7d210dae336d4cdfa743f2bb2baa039cf3f9c Mon Sep 17 00:00:00 2001 From: patrick blume Date: Wed, 4 Dec 2024 10:19:42 +0100 Subject: [PATCH 18/36] Enable usage of selectedColumns in swate alpha --- src/Client/MainComponents/Widgets.fs | 5 +---- src/Client/States/Spreadsheet.fs | 3 ++- src/Client/Update/InterfaceUpdate.fs | 2 +- src/Client/Update/SpreadsheetUpdate.fs | 5 ++--- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index 344c915f..e705f55b 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -226,13 +226,10 @@ type Widget = ] let insertContent() = [ - Html.div [ - SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch) - ] Html.div [ prop.style [style.maxHeight (length.px 350); style.overflow.auto] prop.children [ - SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch) + SelectiveTemplateFromDBModal.Main(model, dispatch) ] ] ] diff --git a/src/Client/States/Spreadsheet.fs b/src/Client/States/Spreadsheet.fs index 31efc352..bc8ce57b 100644 --- a/src/Client/States/Spreadsheet.fs +++ b/src/Client/States/Spreadsheet.fs @@ -3,6 +3,7 @@ namespace Spreadsheet open Shared open ARCtrl open Fable.Core +open JsonImport type ColumnType = | Main @@ -201,7 +202,7 @@ type Msg = | AddAnnotationBlock of CompositeColumn | AddAnnotationBlocks of CompositeColumn [] | AddDataAnnotation of {| fragmentSelectors: string []; fileName: string; fileType: string; targetColumn: DataAnnotator.TargetColumn |} -| AddTemplate of ArcTable * bool[] +| AddTemplate of ArcTable * bool[] * SelectiveImportModalState | JoinTable of ArcTable * index: int option * options: TableJoinOptions option | UpdateArcFile of ArcFiles | InitFromArcFile of ArcFiles diff --git a/src/Client/Update/InterfaceUpdate.fs b/src/Client/Update/InterfaceUpdate.fs index e2966e57..dba7b580 100644 --- a/src/Client/Update/InterfaceUpdate.fs +++ b/src/Client/Update/InterfaceUpdate.fs @@ -170,7 +170,7 @@ module Interface = let cmd = OfficeInterop.AddTemplate (table, selectedColumns, importType) |> OfficeInteropMsg |> Cmd.ofMsg model, cmd | Some Swatehost.Browser | Some Swatehost.ARCitect -> - let cmd = Spreadsheet.AddTemplate (table, selectedColumns) |> SpreadsheetMsg |> Cmd.ofMsg + let cmd = Spreadsheet.AddTemplate (table, selectedColumns, importType) |> SpreadsheetMsg |> Cmd.ofMsg model, cmd | _ -> failwith "not implemented" | JoinTable (table, index, options) -> diff --git a/src/Client/Update/SpreadsheetUpdate.fs b/src/Client/Update/SpreadsheetUpdate.fs index d040ad92..1e2ce94c 100644 --- a/src/Client/Update/SpreadsheetUpdate.fs +++ b/src/Client/Update/SpreadsheetUpdate.fs @@ -103,11 +103,10 @@ module Spreadsheet = | IsTable -> Controller.BuildingBlocks.addDataAnnotation data state | IsMetadata -> failwith "Unable to add data annotation in metadata view" nextState, model, Cmd.none - | AddTemplate (table, selectedColumns) -> + | AddTemplate (table, selectedColumns, importType) -> let index = Some (Spreadsheet.Controller.BuildingBlocks.SidebarControllerAux.getNextColumnIndex model.SpreadsheetModel) /// Filter out existing building blocks and keep input/output values. - let options = Some ARCtrl.TableJoinOptions.WithValues // If changed to anything else we need different logic to keep input/output values - let msg = fun table -> JoinTable(table, index, options) |> SpreadsheetMsg + let msg = fun table -> JoinTable(table, index, Some importType.ImportType) |> SpreadsheetMsg let selectedColumnsIndices = selectedColumns |> Array.mapi (fun i item -> if item = false then Some i else None) From 88edd0498317cb08bbf90f891055c445bbd74fad Mon Sep 17 00:00:00 2001 From: patrick blume Date: Wed, 4 Dec 2024 10:20:30 +0100 Subject: [PATCH 19/36] Rename Method to origin --- src/Client/MainComponents/Widgets.fs | 4 ++-- src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index 053234a5..344c915f 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -227,12 +227,12 @@ type Widget = let insertContent() = [ Html.div [ - SelectiveTemplateFromDBModal.displayTableAndSelectionElements(model, selectedColumns, setSelectedColumns, dispatch) + SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch) ] Html.div [ prop.style [style.maxHeight (length.px 350); style.overflow.auto] prop.children [ - SelectiveTemplateFromDBModal.displayTableAndSelectionElements(model, selectedColumns, setSelectedColumns, dispatch) + SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch) ] ] ] diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index 7c5123da..3eea92cc 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -44,7 +44,7 @@ type SelectiveTemplateFromDBModal = ] [] - static member displayTableAndSelectionElements (model: Model, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit, dispatch, ?hasIcon: bool) = + static member displaySelectedProtocolElements (model: Model, 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)] @@ -116,7 +116,7 @@ type SelectiveTemplateFromDBModal = ModalElements.Box( model.ProtocolState.TemplateSelected.Value.Name, "fa-solid fa-cog", - SelectiveTemplateFromDBModal.displayTableAndSelectionElements(model, selectedColumns, setSelectedColumns, dispatch, false)) + SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch, false)) ] Html.div [ SelectiveTemplateFromDBModal.AddFromDBToTableButton model selectedColumns importTypeState useTemplateName.UseTemplateName dispatch From 95dcf24e0bbffebd4b3f1a0ec51d76aa91f270e5 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Wed, 4 Dec 2024 13:24:33 +0100 Subject: [PATCH 20/36] Enable naming of radioGroup to enable usage in swate alpha --- src/Client/MainComponents/Widgets.fs | 39 +++++++++++++++++-- src/Client/Modals/ModalElements.fs | 2 +- src/Client/Modals/SelectiveImportModal.fs | 5 ++- .../SelectiveTemplateFromDB.fs | 1 + 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index e705f55b..acce3812 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -6,6 +6,7 @@ open Browser.Types open LocalStorage.Widgets open Modals open Types.TableImport +open Types.JsonImport module private InitExtensions = @@ -213,9 +214,13 @@ type Widget = [] static member Templates (model: Model, dispatch, rmv: MouseEvent -> unit) = let templates, setTemplates = React.useState(model.ProtocolState.Templates) - 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 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 importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) let filteredTemplates = Protocol.Search.filterTemplates (templates, config) React.useEffectOnce(fun _ -> Messages.Protocol.GetAllProtocolsRequest |> Messages.ProtocolMsg |> dispatch) React.useEffect((fun _ -> setTemplates model.ProtocolState.Templates), [|box model.ProtocolState.Templates|]) @@ -229,7 +234,35 @@ type Widget = Html.div [ prop.style [style.maxHeight (length.px 350); style.overflow.auto] prop.children [ - SelectiveTemplateFromDBModal.Main(model, dispatch) + SidebarComponents.SidebarLayout.LogicContainer [ + Html.div [ + SelectiveTemplateFromDBModal.ToProtocolSearchElement model dispatch + ] + if model.ProtocolState.TemplateSelected.IsSome 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( + model.ProtocolState.TemplateSelected.Value.Name, + "", + SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch, false)) + ] + Html.div [ + SelectiveTemplateFromDBModal.AddFromDBToTableButton model selectedColumns importTypeState dispatch + ] + ] ] ] ] diff --git a/src/Client/Modals/ModalElements.fs b/src/Client/Modals/ModalElements.fs index 22089afa..544634ba 100644 --- a/src/Client/Modals/ModalElements.fs +++ b/src/Client/Modals/ModalElements.fs @@ -27,7 +27,7 @@ type ModalElements = prop.text text ] - static member RadioPlugin(radioGroup: string, txt:string, isChecked, onChange: bool -> unit, ?isDisabled: bool) = + static member RadioPlugin(radioGroup: string, txt: string, isChecked, onChange: bool -> unit, ?isDisabled: bool) = let isDisabled = defaultArg isDisabled false Daisy.formControl [ Daisy.label [ diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index 30904754..9dd84c41 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -13,10 +13,10 @@ open Components type SelectiveImportModal = - static member RadioPluginsBox(boxName, icon, importType: TableJoinOptions, radioData: (TableJoinOptions * string)[], setImportType: TableJoinOptions -> unit) = + static member RadioPluginsBox(boxName, icon, importType: TableJoinOptions, radioGroupName, radioData: (TableJoinOptions * string)[], setImportType: TableJoinOptions -> unit) = let myradio(target: TableJoinOptions, txt: string) = let isChecked = importType = target - ModalElements.RadioPlugin("importType", txt, isChecked, fun (b: bool) -> if b then setImportType target) + ModalElements.RadioPlugin(radioGroupName, txt, isChecked, fun (b: bool) -> if b then setImportType target) ModalElements.Box (boxName, icon, React.fragment [ Html.div [ for i in 0..radioData.Length-1 do @@ -206,6 +206,7 @@ type SelectiveImportModal = "Import Type", "fa-solid fa-cog", state.ImportType, + "importType", [| ARCtrl.TableJoinOptions.Headers, " Column Headers"; ARCtrl.TableJoinOptions.WithUnit, " ..With Units"; diff --git a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs index b3033ae9..fac0583b 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -74,6 +74,7 @@ type SelectiveTemplateFromDBModal = "Import Type", "fa-solid fa-cog", importTypeState.ImportType, + "importType", [| ARCtrl.TableJoinOptions.Headers, " Column Headers"; ARCtrl.TableJoinOptions.WithUnit, " ..With Units"; From 463a70c579c42527799e6589030a004401b9a392 Mon Sep 17 00:00:00 2001 From: patrick blume Date: Thu, 5 Dec 2024 10:07:43 +0100 Subject: [PATCH 21/36] Updated takeover templatename for excel --- src/Client/MainComponents/Widgets.fs | 3 ++- src/Client/OfficeInterop/OfficeInterop.fs | 11 ++++++++--- .../ProtocolTemplates/SelectiveTemplateFromDB.fs | 10 +++++----- src/Client/States/OfficeInteropState.fs | 2 +- src/Client/States/SpreadsheetInterface.fs | 2 +- src/Client/Update/InterfaceUpdate.fs | 4 ++-- src/Client/Update/OfficeInteropUpdate.fs | 6 +++--- 7 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index acce3812..ebf5657d 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -221,6 +221,7 @@ type Widget = else 0 let selectedColumns, setSelectedColumns = React.useState(SelectedColumns.init selectedColumnsLength) let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) + let useTemplateName, setUseTemplateName = React.useState(AdaptTableName.init) let filteredTemplates = Protocol.Search.filterTemplates (templates, config) React.useEffectOnce(fun _ -> Messages.Protocol.GetAllProtocolsRequest |> Messages.ProtocolMsg |> dispatch) React.useEffect((fun _ -> setTemplates model.ProtocolState.Templates), [|box model.ProtocolState.Templates|]) @@ -260,7 +261,7 @@ type Widget = SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch, false)) ] Html.div [ - SelectiveTemplateFromDBModal.AddFromDBToTableButton model selectedColumns importTypeState dispatch + SelectiveTemplateFromDBModal.AddFromDBToTableButton model selectedColumns importTypeState useTemplateName.TemplateName dispatch ] ] ] diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index 0a81ef3f..81be4c91 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -1008,7 +1008,7 @@ let prepareTemplateInMemory (table: Table) (tableToAdd: ArcTable) (selectedColum /// /// /// -let joinTable (tableToAdd: ArcTable, selectedColumns: bool [], options: TableJoinOptions option, useTemplateName) = +let joinTable (tableToAdd: ArcTable, selectedColumns: bool [], options: TableJoinOptions option, templateName: string option) = Excel.run(fun context -> promise { //When a name is available get the annotation and arctable for easy access of indices and value adaption @@ -1042,8 +1042,13 @@ let joinTable (tableToAdd: ArcTable, selectedColumns: bool [], options: TableJoi let tableSeqs = arcTable.ToStringSeqs() do! context.sync().``then``(fun _ -> - if useTemplateName then - newTable.name <- tableToAdd.Name + if templateName.IsSome then + + let removeChars (stripChars:string) (text:string) = + text.Split(stripChars.ToCharArray(), StringSplitOptions.RemoveEmptyEntries) |> String.Concat + //Should be updated to remove all kinds of extra symbols + let templateName = removeChars " -/\()[]{}" templateName.Value + 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 ec222542..73457284 100644 --- a/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -11,12 +11,12 @@ open ARCtrl open JsonImport type AdaptTableName = { - UseTemplateName: bool + TemplateName: string option } with static member init() = { - UseTemplateName = false + TemplateName = None } type SelectiveTemplateFromDBModal = @@ -27,9 +27,9 @@ type SelectiveTemplateFromDBModal = prop.children [ Daisy.checkbox [ prop.type'.checkbox - prop.isChecked adaptTableName.UseTemplateName + prop.isChecked adaptTableName.TemplateName.IsSome prop.onChange (fun (b: bool) -> - { adaptTableName with UseTemplateName = b } |> setAdaptTableName) + { adaptTableName with TemplateName = if b then Some templateName else None} |> setAdaptTableName) ] Html.text $"Use Template name: {templateName}" ] @@ -120,6 +120,6 @@ type SelectiveTemplateFromDBModal = SelectiveTemplateFromDBModal.displaySelectedProtocolElements(model, selectedColumns, setSelectedColumns, dispatch, false)) ] Html.div [ - SelectiveTemplateFromDBModal.AddFromDBToTableButton model selectedColumns importTypeState useTemplateName.UseTemplateName dispatch + SelectiveTemplateFromDBModal.AddFromDBToTableButton model selectedColumns importTypeState useTemplateName.TemplateName dispatch ] ] diff --git a/src/Client/States/OfficeInteropState.fs b/src/Client/States/OfficeInteropState.fs index b68bd909..ac5e8bd3 100644 --- a/src/Client/States/OfficeInteropState.fs +++ b/src/Client/States/OfficeInteropState.fs @@ -31,7 +31,7 @@ type Msg = | ValidateBuildingBlock | AddAnnotationBlock of CompositeColumn | AddAnnotationBlocks of CompositeColumn [] //* OfficeInterop.Types.Xml.ValidationTypes.TableValidation option - | AddTemplate of ArcTable * bool[] * SelectiveImportModalState * bool + | AddTemplate of ArcTable * bool[] * SelectiveImportModalState * string option | JoinTable of ArcTable * options: TableJoinOptions option | RemoveBuildingBlock | UpdateUnitForCells diff --git a/src/Client/States/SpreadsheetInterface.fs b/src/Client/States/SpreadsheetInterface.fs index a4a06b37..e11be4bb 100644 --- a/src/Client/States/SpreadsheetInterface.fs +++ b/src/Client/States/SpreadsheetInterface.fs @@ -18,7 +18,7 @@ type Msg = | AddAnnotationBlocks of CompositeColumn [] | 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 * bool +| AddTemplate 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 diff --git a/src/Client/Update/InterfaceUpdate.fs b/src/Client/Update/InterfaceUpdate.fs index 8af2e3e7..3ddd3793 100644 --- a/src/Client/Update/InterfaceUpdate.fs +++ b/src/Client/Update/InterfaceUpdate.fs @@ -164,10 +164,10 @@ module Interface = model, cmd | _ -> failwith "not implemented" - | AddTemplate (table, selectedColumns, importType, useTemplateName) -> + | AddTemplate (table, selectedColumns, importType, templateName) -> match host with | Some Swatehost.Excel -> - let cmd = OfficeInterop.AddTemplate (table, selectedColumns, importType, useTemplateName) |> OfficeInteropMsg |> Cmd.ofMsg + let cmd = OfficeInterop.AddTemplate (table, selectedColumns, importType, templateName) |> OfficeInteropMsg |> Cmd.ofMsg model, cmd | Some Swatehost.Browser | Some Swatehost.ARCitect -> let cmd = Spreadsheet.AddTemplate (table, selectedColumns, importType) |> SpreadsheetMsg |> Cmd.ofMsg diff --git a/src/Client/Update/OfficeInteropUpdate.fs b/src/Client/Update/OfficeInteropUpdate.fs index 2daf8efb..2aa719df 100644 --- a/src/Client/Update/OfficeInteropUpdate.fs +++ b/src/Client/Update/OfficeInteropUpdate.fs @@ -76,11 +76,11 @@ module OfficeInterop = UpdateUtil.downloadFromString (jsonExport) state, model, Cmd.none - | AddTemplate (table, selectedColumns, importType, useTemplateName) -> + | AddTemplate (table, selectedColumns, importType, templateName) -> let cmd = Cmd.OfPromise.either OfficeInterop.Core.joinTable - (table, selectedColumns, Some importType.ImportType, useTemplateName) + (table, selectedColumns, Some importType.ImportType, templateName) (curry GenericInteropLogs Cmd.none >> DevMsg) (curry GenericError Cmd.none >> DevMsg) state, model, cmd @@ -89,7 +89,7 @@ module OfficeInterop = let cmd = Cmd.OfPromise.either OfficeInterop.Core.joinTable - (table, [||], options, false) + (table, [||], options, None) (curry GenericInteropLogs Cmd.none >> DevMsg) (curry GenericError Cmd.none >> DevMsg) state, model, cmd From e4d42d8eced46f2742c5daf97d2da88e3553e91d Mon Sep 17 00:00:00 2001 From: patrick blume Date: Thu, 5 Dec 2024 11:02:03 +0100 Subject: [PATCH 22/36] Rename table in swate alpha --- src/Client/Helper.fs | 7 +++++++ src/Client/MainComponents/Widgets.fs | 6 ++++++ src/Client/OfficeInterop/OfficeInterop.fs | 9 ++------- src/Client/Spreadsheet/Controller/BuildingBlocks.fs | 10 ++++++++-- src/Client/States/Spreadsheet.fs | 4 ++-- src/Client/Update/InterfaceUpdate.fs | 4 ++-- src/Client/Update/SpreadsheetUpdate.fs | 8 ++++---- 7 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/Client/Helper.fs b/src/Client/Helper.fs index 99b5b5d3..ddf1fceb 100644 --- a/src/Client/Helper.fs +++ b/src/Client/Helper.fs @@ -3,6 +3,8 @@ module Helper open Fable.Core +open System + let log (a) = Browser.Dom.console.log a let logw (a) = Browser.Dom.console.warn a @@ -121,6 +123,11 @@ 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 type Clipboard = abstract member writeText: string -> JS.Promise diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index ebf5657d..0a4faec1 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -254,6 +254,12 @@ type Widget = fun importType -> {importTypeState with ImportType = importType} |> setImportTypeState ) ] + Html.div [ + ModalElements.Box( + "Rename Table", + "fa-solid fa-cog", + SelectiveTemplateFromDBModal.CheckBoxForTakeOverTemplateName(useTemplateName, setUseTemplateName, model.ProtocolState.TemplateSelected.Value.Name)) + ] Html.div [ ModalElements.Box( model.ProtocolState.TemplateSelected.Value.Name, diff --git a/src/Client/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index 81be4c91..5ddc26b6 100644 --- a/src/Client/OfficeInterop/OfficeInterop.fs +++ b/src/Client/OfficeInterop/OfficeInterop.fs @@ -1,7 +1,5 @@ module OfficeInterop.Core -open System.Collections.Generic - open Fable.Core open ExcelJS.Fable open Excel @@ -1042,12 +1040,9 @@ let joinTable (tableToAdd: ArcTable, selectedColumns: bool [], options: TableJoi let tableSeqs = arcTable.ToStringSeqs() do! context.sync().``then``(fun _ -> - if templateName.IsSome then - - let removeChars (stripChars:string) (text:string) = - text.Split(stripChars.ToCharArray(), StringSplitOptions.RemoveEmptyEntries) |> String.Concat + if templateName.IsSome then //Should be updated to remove all kinds of extra symbols - let templateName = removeChars " -/\()[]{}" templateName.Value + let templateName = Helper.removeChars Helper.charsToRemove templateName.Value 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 0e155862..cd7235b8 100644 --- a/src/Client/Spreadsheet/Controller/BuildingBlocks.fs +++ b/src/Client/Spreadsheet/Controller/BuildingBlocks.fs @@ -1,6 +1,5 @@ module Spreadsheet.Controller.BuildingBlocks -open System.Collections.Generic open Spreadsheet open Types open ARCtrl @@ -97,7 +96,14 @@ let addDataAnnotation (data: {| fragmentSelectors: string []; fileName: string; state.ActiveTable.AddColumn(newHeader, values, forceReplace=true) {state with ArcFile = state.ArcFile} -let joinTable(tableToAdd: ArcTable) (index: int option) (options: TableJoinOptions option) (state: Spreadsheet.Model) : Spreadsheet.Model = +let joinTable(tableToAdd: ArcTable) (index: int option) (options: TableJoinOptions option) (state: Spreadsheet.Model) (templateName:string option): Spreadsheet.Model = + + if templateName.IsSome then + //Should be updated to remove all kinds of extra symbols + let templateName = Helper.removeChars Helper.charsToRemove templateName.Value + let newTable = ArcTable.create(templateName, state.ActiveTable.Headers, state.ActiveTable.Values) + state.ArcFile.Value.Tables().SetTable(state.ActiveTable.Name, newTable) + let table = state.ActiveTable table.Join(tableToAdd,?index=index, ?joinOptions=options, forceReplace=true) {state with ArcFile = state.ArcFile} diff --git a/src/Client/States/Spreadsheet.fs b/src/Client/States/Spreadsheet.fs index bc8ce57b..f1f9d9fe 100644 --- a/src/Client/States/Spreadsheet.fs +++ b/src/Client/States/Spreadsheet.fs @@ -202,8 +202,8 @@ type Msg = | AddAnnotationBlock of CompositeColumn | AddAnnotationBlocks of CompositeColumn [] | AddDataAnnotation of {| fragmentSelectors: string []; fileName: string; fileType: string; targetColumn: DataAnnotator.TargetColumn |} -| AddTemplate of ArcTable * bool[] * SelectiveImportModalState -| JoinTable of ArcTable * index: int option * options: TableJoinOptions option +| AddTemplate of ArcTable * bool[] * SelectiveImportModalState * string option +| JoinTable of ArcTable * index: int option * options: TableJoinOptions option * string option | UpdateArcFile of ArcFiles | InitFromArcFile of ArcFiles | InsertOntologyAnnotation of OntologyAnnotation diff --git a/src/Client/Update/InterfaceUpdate.fs b/src/Client/Update/InterfaceUpdate.fs index 3ddd3793..027525a8 100644 --- a/src/Client/Update/InterfaceUpdate.fs +++ b/src/Client/Update/InterfaceUpdate.fs @@ -170,7 +170,7 @@ module Interface = let cmd = OfficeInterop.AddTemplate (table, selectedColumns, importType, templateName) |> OfficeInteropMsg |> Cmd.ofMsg model, cmd | Some Swatehost.Browser | Some Swatehost.ARCitect -> - let cmd = Spreadsheet.AddTemplate (table, selectedColumns, importType) |> SpreadsheetMsg |> Cmd.ofMsg + let cmd = Spreadsheet.AddTemplate (table, selectedColumns, importType, templateName) |> SpreadsheetMsg |> Cmd.ofMsg model, cmd | _ -> failwith "not implemented" | JoinTable (table, index, options) -> @@ -179,7 +179,7 @@ module Interface = let cmd = OfficeInterop.JoinTable (table, options) |> OfficeInteropMsg |> Cmd.ofMsg model, cmd | Some Swatehost.Browser | Some Swatehost.ARCitect -> - let cmd = Spreadsheet.JoinTable (table, index, options) |> SpreadsheetMsg |> Cmd.ofMsg + let cmd = Spreadsheet.JoinTable (table, index, options, None) |> SpreadsheetMsg |> Cmd.ofMsg model, cmd | _ -> failwith "not implemented" | UpdateArcFile arcFiles -> diff --git a/src/Client/Update/SpreadsheetUpdate.fs b/src/Client/Update/SpreadsheetUpdate.fs index 1e2ce94c..e6defdf9 100644 --- a/src/Client/Update/SpreadsheetUpdate.fs +++ b/src/Client/Update/SpreadsheetUpdate.fs @@ -103,10 +103,10 @@ module Spreadsheet = | IsTable -> Controller.BuildingBlocks.addDataAnnotation data state | IsMetadata -> failwith "Unable to add data annotation in metadata view" nextState, model, Cmd.none - | AddTemplate (table, selectedColumns, importType) -> + | AddTemplate (table, selectedColumns, importType, templateName) -> let index = Some (Spreadsheet.Controller.BuildingBlocks.SidebarControllerAux.getNextColumnIndex model.SpreadsheetModel) /// Filter out existing building blocks and keep input/output values. - let msg = fun table -> JoinTable(table, index, Some importType.ImportType) |> SpreadsheetMsg + let msg = fun table -> JoinTable(table, index, Some importType.ImportType, templateName) |> SpreadsheetMsg let selectedColumnsIndices = selectedColumns |> Array.mapi (fun i item -> if item = false then Some i else None) @@ -118,8 +118,8 @@ module Spreadsheet = |> msg |> Cmd.ofMsg state, model, cmd - | JoinTable (table, index, options) -> - let nextState = Controller.BuildingBlocks.joinTable table index options state + | JoinTable (table, index, options, templateName) -> + let nextState = Controller.BuildingBlocks.joinTable table index options state templateName nextState, model, Cmd.none | UpdateArcFile arcFile -> let reset = state.ActiveView.ArcFileHasView(arcFile) //verify that active view is still valid From b74953d36dcc993e994e05990e743d9a47f1be0d Mon Sep 17 00:00:00 2001 From: patrick blume Date: Mon, 9 Dec 2024 14:20:39 +0100 Subject: [PATCH 23/36] 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 24/36] 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 25/36] 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 26/36] 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 27/36] 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 28/36] 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 29/36] 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 30/36] 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 31/36] 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 32/36] 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 33/36] 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 34/36] 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 35/36] 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 36/36] 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 ]