diff --git a/src/Client/Client.fsproj b/src/Client/Client.fsproj index 93fe14cc..27cb9759 100644 --- a/src/Client/Client.fsproj +++ b/src/Client/Client.fsproj @@ -35,6 +35,7 @@ + @@ -90,7 +91,7 @@ - + diff --git a/src/Client/GenericComponents.fs b/src/Client/GenericComponents.fs index 179d3539..eb6d41dd 100644 --- a/src/Client/GenericComponents.fs +++ b/src/Client/GenericComponents.fs @@ -10,6 +10,7 @@ module DaisyUiExtensions = static member active = prop.className "modal-open" type Components = + 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 b31dd277..acce3812 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -4,6 +4,9 @@ open Feliz open Feliz.DaisyUI open Browser.Types open LocalStorage.Widgets +open Modals +open Types.TableImport +open Types.JsonImport module private InitExtensions = @@ -212,6 +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 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|]) @@ -222,13 +231,38 @@ type Widget = ] let insertContent() = [ - Html.div [ - Protocol.TemplateFromDB.addFromDBToTableButton model dispatch - ] Html.div [ prop.style [style.maxHeight (length.px 350); style.overflow.auto] prop.children [ - Protocol.TemplateFromDB.displaySelectedProtocolEle 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 new file mode 100644 index 00000000..544634ba --- /dev/null +++ b/src/Client/Modals/ModalElements.fs @@ -0,0 +1,108 @@ +namespace Modals + +open Feliz +open Feliz.DaisyUI +open Model +open Messages +open Shared +open Types.TableImport + +open ARCtrl +open JsonImport +open Components +open Fable.React.Helpers + +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 [ + 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 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 + 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) + ] diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index 5e333e8b..9dd84c41 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 Types.TableImport open ARCtrl open JsonImport @@ -12,66 +13,83 @@ open Components type SelectiveImportModal = - 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 RadioPluginsBox(boxName, icon, importType: TableJoinOptions, radioGroupName, radioData: (TableJoinOptions * string)[], setImportType: TableJoinOptions -> unit) = + let myradio(target: TableJoinOptions, txt: string) = + let isChecked = importType = 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 + myradio(radioData.[i]) ] - ] - static member private Box (title: string, icon: string, content: ReactElement, ?className: string list) = + ]) + + static member CheckBoxForTableColumnSelection(columns: CompositeColumn [], index, selectionInformation: SelectedColumns, setSelectedColumns: SelectedColumns -> unit) = Html.div [ - prop.className [ - "rounded shadow p-2 flex flex-col gap-2 border" - if className.IsSome then - className.Value |> String.concat " " - ] + prop.style [style.display.flex; style.justifyContent.center] 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 + 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) ] - 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") + 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 - 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" @@ -99,25 +117,25 @@ 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) 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 @@ -126,39 +144,24 @@ 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 + SelectiveImportModal.TableWithImportColumnCheckboxes(table0, selectedColumns, setSelectedColumns) + else + SelectiveImportModal.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) = + static member Main (import: ArcFiles, dispatch, rmv) = let state, setState = React.useState(SelectiveImportModalState.init) let tables, disArcfile = match import with @@ -170,17 +173,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 [ @@ -195,18 +202,29 @@ type SelectiveImportModal = Components.DeleteButton(props=[prop.onClick rmv]) ] ] - SelectiveImportModal.ImportTypeRadio(state.ImportType, fun it -> {state with ImportType = it} |> setState) + SelectiveImportModal.RadioPluginsBox( + "Import Type", + "fa-solid fa-cog", + state.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) 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/OfficeInterop/OfficeInterop.fs b/src/Client/OfficeInterop/OfficeInterop.fs index a42fca99..edcf7282 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,11 +930,44 @@ 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 /// /// -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 +975,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 +1008,16 @@ 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 { - //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! (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) @@ -977,11 +1025,11 @@ let joinTable (tableToAdd: ArcTable, options: TableJoinOptions option) = //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() @@ -989,14 +1037,16 @@ let joinTable (tableToAdd: ArcTable, options: TableJoinOptions option) = 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) @@ -1025,9 +1075,12 @@ let joinTable (tableToAdd: ArcTable, options: TableJoinOptions option) = 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.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] @@ -1469,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] } @@ -1792,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"] } ) @@ -2380,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/ProtocolTemplates/ProtocolView.fs b/src/Client/Pages/ProtocolTemplates/ProtocolView.fs index 8b4fb71e..8ad27ee5 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. " @@ -39,7 +38,7 @@ type Templates = // Box 1 SidebarComponents.SidebarLayout.Description "Add template from database." - TemplateFromDB.Main(model, dispatch) + Modals.SelectiveTemplateFromDBModal.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 new file mode 100644 index 00000000..fac0583b --- /dev/null +++ b/src/Client/Pages/ProtocolTemplates/SelectiveTemplateFromDB.fs @@ -0,0 +1,95 @@ +namespace Modals + +open Feliz +open Feliz.DaisyUI +open Model +open Messages +open Shared +open Types.TableImport + +open ARCtrl +open JsonImport + +type SelectiveTemplateFromDBModal = + + 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 + Html.div [ + prop.style [style.overflowX.auto; style.marginBottom (length.rem 1)] + prop.children [ + 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 + SelectiveImportModal.TableWithImportColumnCheckboxes(model.ProtocolState.TemplateSelected.Value.Table, selectionInformation, setSelectedColumns) + ] + ] + + 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, importType) |> 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) + let importTypeState, setImportTypeState = React.useState(SelectiveImportModalState.init) + 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/Pages/ProtocolTemplates/TemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs deleted file mode 100644 index f6659a05..00000000 --- a/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs +++ /dev/null @@ -1,93 +0,0 @@ -namespace Protocol - -open Feliz -open Feliz.DaisyUI -open Messages -open Model -open Shared - -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) 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) |> 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) - button.error - Html.i [prop.className "fa-solid fa-times"] |> prop.children - ] - ] - ] - - static member displaySelectedProtocolEle (model:Model) 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 "Column" - Html.th "Column TAN" - //Html.th "Unit" - //Html.th "Unit TAN" - ] - ] - Html.tbody [ - for column in model.ProtocolState.TemplateSelected.Value.Table.Columns do - //let unitOption = column.TryGetColumnUnits() - yield - Html.tr [ - 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) = - SidebarComponents.SidebarLayout.LogicContainer [ - Html.div [ - TemplateFromDB.toProtocolSearchElement model dispatch - ] - - Html.div [ - TemplateFromDB.addFromDBToTableButton model dispatch - ] - if model.ProtocolState.TemplateSelected.IsSome then - Html.div [ - TemplateFromDB.displaySelectedProtocolEle model dispatch - ] - Html.div [ - TemplateFromDB.addFromDBToTableButton model dispatch - ] - ] diff --git a/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs b/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs index cf287e9b..4052aa35 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 + 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) @@ -119,10 +109,10 @@ type TemplateFromFile = 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 +121,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/Pages/TermSearch/TermSearchView.fs b/src/Client/Pages/TermSearch/TermSearchView.fs index d9cff126..8302d11c 100644 --- a/src/Client/Pages/TermSearch/TermSearchView.fs +++ b/src/Client/Pages/TermSearch/TermSearchView.fs @@ -8,7 +8,6 @@ open Elmish open TermSearch open Model - 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 diff --git a/src/Client/SidebarComponents/LayoutHelper.fs b/src/Client/SidebarComponents/LayoutHelper.fs index d1578854..5358322b 100644 --- a/src/Client/SidebarComponents/LayoutHelper.fs +++ b/src/Client/SidebarComponents/LayoutHelper.fs @@ -20,11 +20,6 @@ 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 [ @@ -39,6 +34,13 @@ type SidebarLayout = 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" + 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..982376da 100644 --- a/src/Client/States/ModalState.fs +++ b/src/Client/States/ModalState.fs @@ -5,6 +5,8 @@ open ARCtrl open Feliz +open Model + module ModalState = type TableModals = diff --git a/src/Client/States/OfficeInteropState.fs b/src/Client/States/OfficeInteropState.fs index 009a3785..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 + | AddTemplate 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 3b747069..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 +| 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/States/SpreadsheetInterface.fs b/src/Client/States/SpreadsheetInterface.fs index e9e385e7..ed69e50e 100644 --- a/src/Client/States/SpreadsheetInterface.fs +++ b/src/Client/States/SpreadsheetInterface.fs @@ -1,6 +1,7 @@ namespace SpreadsheetInterface open Shared +open Types.TableImport 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 +| 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/Types.fs b/src/Client/Types.fs index 5477c772..36771ca7 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 TableImport = + + type SelectedColumns = { + Columns: bool [] + } + with + static member init(length) = + { + Columns = Array.init length (fun _ -> true) + } diff --git a/src/Client/Update/InterfaceUpdate.fs b/src/Client/Update/InterfaceUpdate.fs index 81e2b304..dba7b580 100644 --- a/src/Client/Update/InterfaceUpdate.fs +++ b/src/Client/Update/InterfaceUpdate.fs @@ -164,13 +164,13 @@ module Interface = model, cmd | _ -> failwith "not implemented" - | AddTemplate table -> + | AddTemplate (table, selectedColumns, importType) -> match host with | Some Swatehost.Excel -> - let cmd = OfficeInterop.AddTemplate table |> 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 |> SpreadsheetMsg |> Cmd.ofMsg + let cmd = Spreadsheet.AddTemplate (table, selectedColumns, importType) |> SpreadsheetMsg |> Cmd.ofMsg model, cmd | _ -> failwith "not implemented" | JoinTable (table, index, options) -> @@ -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 fb82cb49..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 -> + | AddTemplate (table, selectedColumns, importType) -> let cmd = Cmd.OfPromise.either OfficeInterop.Core.joinTable - (table, Some ARCtrl.TableJoinOptions.WithValues) + (table, selectedColumns, Some importType.ImportType) (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..1e2ce94c 100644 --- a/src/Client/Update/SpreadsheetUpdate.fs +++ b/src/Client/Update/SpreadsheetUpdate.fs @@ -103,13 +103,18 @@ 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, 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 t -> JoinTable(t, 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) + |> 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 diff --git a/src/Client/Update/UpdateUtil.fs b/src/Client/Update/UpdateUtil.fs index 6295fa63..3c7c3620 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 Types.TableImport 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 67abd0a4..c04bddf4 100644 --- a/src/Shared/ARCtrl.Helper.fs +++ b/src/Shared/ARCtrl.Helper.fs @@ -93,18 +93,22 @@ 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 = [||]} + 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/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