diff --git a/package-lock.json b/package-lock.json index a7dec072..b21e6339 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,7 @@ "requires": true, "packages": { "": { + "name": "Swate", "dependencies": { "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^8.0.0", @@ -2613,9 +2614,10 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", + "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", diff --git a/src/Client/Client.fsproj b/src/Client/Client.fsproj index 3fd60958..07c67a35 100644 --- a/src/Client/Client.fsproj +++ b/src/Client/Client.fsproj @@ -126,7 +126,7 @@ - + diff --git a/src/Client/Init.fs b/src/Client/Init.fs index f27e7a91..577b6d7a 100644 --- a/src/Client/Init.fs +++ b/src/Client/Init.fs @@ -7,9 +7,7 @@ open Model open Messages open Update -let initializeModel () = - let dt = LocalStorage.Darkmode.DataTheme.GET() - LocalStorage.Darkmode.DataTheme.SET dt +let initialModel = { PageState = PageState .init() PersistentStorageState = PersistentStorageState .init() @@ -21,14 +19,12 @@ let initializeModel () = ProtocolState = Protocol.Model .init() CytoscapeModel = Cytoscape.Model .init() DataAnnotatorModel = DataAnnotator.Model .init() - SpreadsheetModel = Spreadsheet.Model .fromLocalStorage() - History = LocalHistory.Model .init().UpdateFromSessionStorage() + SpreadsheetModel = Spreadsheet.Model .init() + History = LocalHistory.Model .init() } // defines the initial state and initial command (= side-effect) of the application let init (pageOpt: Routing.Route option) : Model * Cmd = - let initialModel, pageCmd = initializeModel () |> urlUpdate pageOpt - let cmd = Cmd.ofMsg <| InterfaceMsg (SpreadsheetInterface.Initialize initialModel.PersistentStorageState.Host.Value) - let batch = Cmd.batch [|pageCmd; cmd|] - initialModel, batch \ No newline at end of file + let model, cmd = urlUpdate pageOpt initialModel + model, cmd \ No newline at end of file diff --git a/src/Client/MainComponents/Navbar.fs b/src/Client/MainComponents/Navbar.fs index 095c07c8..1111e5ec 100644 --- a/src/Client/MainComponents/Navbar.fs +++ b/src/Client/MainComponents/Navbar.fs @@ -163,7 +163,7 @@ let Main(model: Model, dispatch, widgets, setWidgets) = ] Daisy.navbarCenter [ prop.children [ - QuickAccessButtonListStart model.History dispatch + // QuickAccessButtonListStart model.History dispatch WidgetNavbarList(model, dispatch, addWidget) ] ] diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs index 218ebce0..b31dd277 100644 --- a/src/Client/MainComponents/Widgets.fs +++ b/src/Client/MainComponents/Widgets.fs @@ -245,18 +245,21 @@ type Widget = static member FilePicker (model, dispatch, rmv) = let content = Html.div [ - FilePicker.uploadButton model dispatch - if model.FilePickerState.FileNames <> [] then - FilePicker.fileSortElements model dispatch + prop.className "@container/filePickerWidget min-w-32" + prop.children [ + FilePicker.uploadButton model dispatch "@md/filePickerWidget:flex-row" + if model.FilePickerState.FileNames <> [] then + FilePicker.fileSortElements model dispatch - Html.div [ - prop.style [style.maxHeight (length.px 350); style.overflow.auto] - prop.children [ - FilePicker.FileNameTable.table model dispatch + Html.div [ + prop.style [style.maxHeight (length.px 350); style.overflow.auto] + prop.children [ + FilePicker.FileNameTable.table model dispatch + ] ] - ] - //fileNameElements model dispatch - FilePicker.insertButton model dispatch + //fileNameElements model dispatch + FilePicker.insertButton model dispatch + ] ] let prefix = WidgetLiterals.FilePicker Widget.Base(content, prefix, rmv) diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs index dddda498..8ee955e8 100644 --- a/src/Client/Modals/SelectiveImportModal.fs +++ b/src/Client/Modals/SelectiveImportModal.fs @@ -12,166 +12,139 @@ open Components type SelectiveImportModal = - static member private ImportTypeRadio(importType: TableJoinOptions, setImportType: TableJoinOptions -> unit) = - let myradio(target: TableJoinOptions, txt: string) = - let isChecked = importType = target - Daisy.formControl [ - Daisy.label [ - prop.children [ - Daisy.radio [ - prop.name "importType" - prop.isChecked isChecked - prop.onChange (fun (b:bool) -> if b then setImportType target) - ] - Daisy.labelText txt + 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 hover:bg-base-200 transition-colors" + prop.children [ + Daisy.radio [ + prop.disabled isDisabled + radio.xs + prop.name radioGroup + prop.isChecked isChecked + prop.onChange onChange ] + Daisy.labelText txt ] ] - Daisy.card [ - Daisy.cardBody [ - Daisy.label [ - Daisy.labelText [ - Html.i [prop.className "fa-solid fa-cog"] - Html.text (" Import Type") - ] - ] - Html.div [ - prop.className "is-flex is-justify-content-space-between" + ] + 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" + 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 [ - myradio(ARCtrl.TableJoinOptions.Headers, " Column Headers") - myradio(ARCtrl.TableJoinOptions.WithUnit, " ..With Units") - myradio(ARCtrl.TableJoinOptions.WithValues, " ..With Values") + 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") + ] + ]) + static member private MetadataImport(isActive: bool, setActive: bool -> unit, disArcFile: ArcFilesDiscriminate) = let name = string disArcFile - Daisy.card [ - if isActive then prop.className "bg-info" - prop.children [ - Html.div [ - Daisy.label [ - Daisy.labelText [ - Html.i [prop.className "fa-solid fa-lightbulb"] - Html.textf " %s Metadata" name - ] - ] - Daisy.formControl [ - Daisy.label [ - Daisy.checkbox [ - prop.type'.checkbox - prop.onChange (fun (b:bool) -> setActive b) - ] - Daisy.labelText " Import" + SelectiveImportModal.Box (sprintf "%s Metadata" name, "fa-solid fa-lightbulb", React.fragment [ + Daisy.formControl [ + Daisy.label [ + prop.className "cursor-pointer" + prop.children [ + Daisy.checkbox [ + prop.type'.checkbox + prop.onChange (fun (b:bool) -> setActive b) ] - ] - Html.span [ - prop.className "text-warning" - prop.text "Importing metadata will overwrite the current file." + Daisy.labelText "Import" ] ] ] - ] + Html.span [ + prop.className "text-warning bg-warning-content flex flex-row gap-2 justify-center items-center" + prop.children [ + Html.i [prop.className "fa-solid fa-exclamation-triangle"] + Html.text " Importing metadata will overwrite the current file." + ] + ] + ], + className = [if isActive then "bg-info text-info-content"] + ) [] - static member private TableImport(index: int, table: 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) = let showData, setShowData = React.useState(false) - let name = table.Name + let name = table0.Name let radioGroup = "radioGroup_" + name let import = state.ImportTables |> List.tryFind (fun it -> it.Index = index) let isActive = import.IsSome - let disableAppend = state.ImportMetadata - Daisy.card [ - if isActive then prop.className "bg-success" - prop.children [ - Html.div [ - Daisy.label [ - Daisy.labelText [ - Html.i [prop.className "fa-solid fa-table"] - Html.span (" " + name) - Daisy.button.label [ - if showData then button.active - button.sm - prop.onClick (fun _ -> setShowData (not showData)) - prop.style [style.float'.right; style.cursor.pointer] - prop.children [ - Html.i [ - prop.style [style.transitionProperty "transform"; style.transitionDuration (System.TimeSpan.FromSeconds 0.35)] - prop.className ["fa-solid"; "fa-angle-down"; if showData then "fa-rotate-180"] + let isDisabled = state.ImportMetadata + SelectiveImportModal.Box (name, "fa-solid fa-table", React.fragment [ + Html.div [ + SelectiveImportModal.Radio (radioGroup, "Import", + isActive && import.Value.FullImport, + (fun (b:bool) -> addTableImport index true), + isDisabled + ) + SelectiveImportModal.Radio (radioGroup, "Append to active table", + isActive && not import.Value.FullImport, + (fun (b:bool) -> addTableImport index false), + isDisabled + ) + SelectiveImportModal.Radio (radioGroup, "No Import", + not isActive, + (fun (b:bool) -> rmvTableImport index), + isDisabled + ) + ] + Daisy.collapse [ + Html.input [prop.type'.checkbox] + Daisy.collapseTitle [ + prop.className "p-1 min-h-0" + prop.text "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.div [ - Daisy.formControl [ - let isInnerActive = isActive && import.Value.FullImport - Daisy.label [ - Daisy.radio [ - prop.type'.radio - prop.name radioGroup - prop.isChecked isInnerActive - prop.onChange (fun (b:bool) -> addTableImport index true) - ] - Daisy.labelText " Import" - ] - ] - Daisy.formControl [ - let isInnerActive = isActive && not import.Value.FullImport - Daisy.label [ - Daisy.radio [ - prop.type'.radio - prop.name radioGroup - if disableAppend then prop.disabled true - prop.isChecked isInnerActive - prop.onChange (fun (b:bool) -> addTableImport index true) - ] - Daisy.labelText " Append to active table" - ] - ] - Daisy.formControl [ - let isInnerActive = not isActive - Daisy.label [ - Daisy.radio [ - prop.type'.radio - prop.name radioGroup - if disableAppend then prop.disabled true - prop.isChecked isInnerActive - prop.onChange (fun (b:bool) -> addTableImport index true) - ] - Daisy.labelText " No Import" - ] - ] - ] - ] - if showData then - Html.div [ - prop.className "overflow-x-auto" - prop.children [ - Daisy.table [ - prop.children [ - Html.thead [ + Html.tbody [ + for ri in 0 .. (table0.RowCount-1) do + let row = table0.GetRow(ri, true) Html.tr [ - for c in table.Headers do - Html.th (c.ToString()) + for c in row do + Html.td (c.ToString()) ] - ] - 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()) - ] - ] ] ] ] ] - ] - ] + ] + ]], + className = [if isActive then "bg-primary text-primary-content"] + ) [] static member Main (import: ArcFiles) (model: Spreadsheet.Model) dispatch (rmv: _ -> unit) = @@ -194,32 +167,30 @@ type SelectiveImportModal = modal.active prop.children [ Daisy.modalBackdrop [ prop.onClick rmv ] - Daisy.card [ - prop.style [style.maxHeight(length.percent 70); style.overflowY.hidden] + Daisy.modalBox.div [ + prop.className "w-4/5 overflow-y-auto flex flex-col @container/importModal gap-2" prop.children [ - Daisy.cardBody [ - Daisy.cardActions [ - prop.className "justify-end" - prop.children [ - Components.DeleteButton(props=[prop.onClick rmv]) - ] + Daisy.cardTitle [ + prop.className "justify-between" + prop.children [ + Html.p "Import" + Components.DeleteButton(props=[prop.onClick rmv]) ] - Daisy.cardTitle "Import" - SelectiveImportModal.ImportTypeRadio(state.ImportType, 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] - SelectiveImportModal.TableImport(ti, t, state, addTableImport, rmvTableImport) - 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 - rmv e - ) - ] + ] + SelectiveImportModal.ImportTypeRadio(state.ImportType, 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] + SelectiveImportModal.TableImport(ti, t, state, addTableImport, rmvTableImport) + 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 + rmv e + ) ] ] ] diff --git a/src/Client/Modals/UpdateColumn.fs b/src/Client/Modals/UpdateColumn.fs index 427530d9..890a24d0 100644 --- a/src/Client/Modals/UpdateColumn.fs +++ b/src/Client/Modals/UpdateColumn.fs @@ -54,10 +54,8 @@ module private Components = prop.className "grow" tabs.bordered prop.children [ - Html.ul [ - Tab(FunctionPage.Create, currentPage, setPage) - Tab(FunctionPage.Update, currentPage, setPage) - ] + Tab(FunctionPage.Create, currentPage, setPage) + Tab(FunctionPage.Update, currentPage, setPage) ] ] @@ -67,8 +65,8 @@ module private Components = Html.td [ let s0,marked,s2 = split (fst markedIndices) (snd markedIndices) cell0 Html.span s0 - Html.span [ - prop.className "has-background-info" + Html.mark [ + prop.className "bg-info text-info-content" prop.text marked ] Html.span s2 @@ -117,30 +115,33 @@ type UpdateColumn = ) |> setPreview React.fragment [ - Daisy.label [ - Daisy.labelText "BAse" - ] - Daisy.input [ - prop.autoFocus true - prop.valueOrDefault baseStr - prop.onChange(fun s -> - setBaseStr s - updateCells s suffix - ) + Daisy.formControl [ + Daisy.label [ + Daisy.labelText "Base" + ] + Daisy.input [ + input.bordered + prop.autoFocus true + prop.valueOrDefault baseStr + prop.onChange(fun s -> + setBaseStr s + updateCells s suffix + ) + ] ] - Html.label [ - prop.className "is-flex is-align-items-center checkbox" - prop.style [style.gap (length.rem 0.5)] - prop.children [ - Html.input [ - prop.type' "checkbox" - prop.isChecked suffix - prop.onChange(fun e -> - setSuffix e - updateCells baseStr e - ) + Daisy.formControl [ + Daisy.label [ + prop.className "cursor-pointer" + prop.children [ + Daisy.labelText "Add number suffix" + Daisy.checkbox [ + prop.isChecked suffix + prop.onChange(fun e -> + setSuffix e + updateCells baseStr e + ) + ] ] - Html.p [prop.text "Add number suffix"; prop.className "text-sm"] ] ] ] @@ -168,29 +169,34 @@ type UpdateColumn = else () Html.div [ - prop.className "is-flex is-flex-direction-row" - prop.style [style.gap (length.rem 1)] + prop.className "flex gap-2" prop.children [ - Daisy.label [ - Daisy.labelText "Regex" - ] - Daisy.input [ - prop.autoFocus true - prop.valueOrDefault regex - prop.onChange (fun s -> - setRegex s; - updateCells replacement s - ) - ] - Daisy.label [ - Daisy.labelText "Replacement" + Daisy.formControl [ + Daisy.label [ + Daisy.labelText "Regex" + ] + Daisy.input [ + prop.autoFocus true + input.bordered + prop.valueOrDefault regex + prop.onChange (fun s -> + setRegex s; + updateCells replacement s + ) + ] ] - Daisy.input [ - prop.valueOrDefault replacement - prop.onChange (fun s -> - setReplacement s; - updateCells s regex - ) + Daisy.formControl [ + Daisy.label [ + Daisy.labelText "Replacement" + ] + Daisy.input [ + input.bordered + prop.valueOrDefault replacement + prop.onChange (fun s -> + setReplacement s; + updateCells s regex + ) + ] ] ] ] @@ -218,31 +224,35 @@ type UpdateColumn = modal.active prop.children [ Daisy.modalBackdrop [ prop.onClick rmv ] - Daisy.card [ - prop.style [style.maxHeight(length.percent 70); style.overflowY.hidden] + Daisy.modalBox.div [ + prop.style [style.maxHeight(length.percent 70); style.maxWidth(length.px 900); style.overflowY.hidden] prop.children [ - Daisy.cardBody [ - Daisy.cardActions [ - prop.className "justify-end" - prop.children [ - Components.DeleteButton(props=[prop.onClick rmv]) - ] - ] - Daisy.cardTitle "Update Column" - Components.TabNavigation(currentPage, setPage) - match currentPage with - | FunctionPage.Create -> UpdateColumn.CreateForm(getCellStrings(), setPreview) - | FunctionPage.Update -> UpdateColumn.UpdateForm(getCellStrings(), setPreview, regex, setRegex) - Components.PreviewTable(column, preview, regex) - Daisy.cardActions [ - Daisy.button.button [ - button.info - prop.style [style.marginLeft length.auto] - prop.text "Submit" - prop.onClick(fun e -> - submit() - rmv e - ) + Daisy.card [ + prop.children [ + Daisy.cardBody [ + Daisy.cardTitle [ + prop.className "flex flex-row justify-between" + prop.children [ + Html.p "Update Column" + Components.DeleteButton(props=[prop.onClick rmv]) + ] + ] + Components.TabNavigation(currentPage, setPage) + match currentPage with + | FunctionPage.Create -> UpdateColumn.CreateForm(getCellStrings(), setPreview) + | FunctionPage.Update -> UpdateColumn.UpdateForm(getCellStrings(), setPreview, regex, setRegex) + Components.PreviewTable(column, preview, regex) + Daisy.cardActions [ + Daisy.button.button [ + button.info + prop.style [style.marginLeft length.auto] + prop.text "Submit" + prop.onClick(fun e -> + submit() + rmv e + ) + ] + ] ] ] ] diff --git a/src/Client/Model.fs b/src/Client/Model.fs index 4636e1d4..3d8f9066 100644 --- a/src/Client/Model.fs +++ b/src/Client/Model.fs @@ -121,7 +121,7 @@ type PersistentStorageState = { } with static member init () = { SearchableOntologies = [||] - Host = None + Host = Some Swatehost.Browser AppVersion = "" ShowSideBar = false HasOntologiesLoaded = false diff --git a/src/Client/Pages/FilePicker/FilePickerView.fs b/src/Client/Pages/FilePicker/FilePickerView.fs index 3ba15e7e..4dbf8fe7 100644 --- a/src/Client/Pages/FilePicker/FilePickerView.fs +++ b/src/Client/Pages/FilePicker/FilePickerView.fs @@ -8,63 +8,66 @@ open Messages open Feliz open Feliz.DaisyUI -let update (filePickerMsg:FilePicker.Msg) (currentState: FilePicker.Model) (model: Model.Model) : FilePicker.Model * Cmd = +let update (filePickerMsg:FilePicker.Msg) (state: FilePicker.Model) (model: Model.Model) : FilePicker.Model * Cmd = match filePickerMsg with | LoadNewFiles fileNames -> - let nextState : FilePicker.Model = { - FileNames = fileNames |> List.mapi (fun i x -> i + 1, x) + let nextModel = { + model with + Model.FilePickerState.FileNames = fileNames |> List.mapi (fun i x -> i + 1, x) + Model.PageState.SidebarPage = Routing.SidebarPage.FilePicker } - let nextCmd = UpdateModel {model with Model.PageState.SidebarPage = Routing.SidebarPage.FilePicker} |> Cmd.ofMsg - nextState, nextCmd + let nextCmd = UpdateModel nextModel|> Cmd.ofMsg + state, nextCmd | UpdateFileNames newFileNames -> let nextState : FilePicker.Model = { FileNames = newFileNames } nextState, Cmd.none -let uploadButton (model:Model) dispatch = +/// "parentContainerResizeClass": uses tailwind container queries. Expects a string like "@md/parentId:flex-row" +let uploadButton (model:Model) dispatch (parentContainerResizeClass: string) = let inputId = "filePicker_OnFilePickerMainFunc" Html.div [ - Html.input [ - prop.style [style.display.none] - prop.id inputId - prop.multiple true - prop.type'.file - prop.onChange (fun (ev: File list) -> - let files = ev //ev.target?files - - let fileNames = - files |> List.map (fun f -> f.name) - - fileNames |> LoadNewFiles |> FilePickerMsg |> dispatch - - //let picker = Browser.Dom.document.getElementById(inputId) - //// https://stackoverflow.com/questions/3528359/html-input-type-file-file-selection-event/3528376 - //picker?value <- null - ) + prop.className [ + "flex flex-col gap-2" + parentContainerResizeClass ] - match model.PersistentStorageState.Host with + prop.children [ + Html.input [ + prop.style [style.display.none] + prop.id inputId + prop.multiple true + prop.type'.file + prop.onChange (fun (ev: File list) -> + let files = ev //ev.target?files + + let fileNames = + files |> List.map (fun f -> f.name) + + fileNames |> LoadNewFiles |> FilePickerMsg |> dispatch + + //let picker = Browser.Dom.document.getElementById(inputId) + //// https://stackoverflow.com/questions/3528359/html-input-type-file-file-selection-event/3528376 + //picker?value <- null + ) + ] + match model.PersistentStorageState.Host with | Some (Swatehost.ARCitect) -> - Html.div [ - prop.className "flex flex-row gap-2" - prop.children [ - Daisy.button.button [ - button.primary - button.block - prop.onClick(fun _ -> - ARCitect.RequestPaths false |> ARCitect.ARCitect.send - ) - prop.text "Pick Files" - ] - Daisy.button.button [ - button.primary - button.block - prop.onClick(fun _ -> - ARCitect.RequestPaths true |> ARCitect.ARCitect.send - ) - prop.text "Pick Directories" - ] - ] + Daisy.button.button [ + button.primary + button.block + prop.onClick(fun _ -> + ARCitect.RequestPaths false |> ARCitect.ARCitect.send + ) + prop.text "Pick Files" + ] + Daisy.button.button [ + button.primary + button.block + prop.onClick(fun _ -> + ARCitect.RequestPaths true |> ARCitect.ARCitect.send + ) + prop.text "Pick Directories" ] | _ -> Daisy.button.button [ @@ -76,6 +79,7 @@ let uploadButton (model:Model) dispatch = ) prop.text "Pick file names" ] + ] ] let insertButton (model:Model) dispatch = @@ -214,7 +218,7 @@ module FileNameTable = let fileContainer (model:Model) dispatch = SidebarComponents.SidebarLayout.LogicContainer [ - uploadButton model dispatch + uploadButton model dispatch "@md/sidebar:flex-row" if model.FilePickerState.FileNames <> [] then fileSortElements model dispatch diff --git a/src/Client/Pages/JsonExporter/JsonExporter.fs b/src/Client/Pages/JsonExporter/JsonExporter.fs index 665ef029..f14cffe0 100644 --- a/src/Client/Pages/JsonExporter/JsonExporter.fs +++ b/src/Client/Pages/JsonExporter/JsonExporter.fs @@ -34,254 +34,6 @@ let download(filename, text) = document.body.removeChild(element) |> ignore () -//open Messages -//open Feliz -//open Feliz.DaisyUI - -//let dropdownItem (exportType:JsonExportType) (model:Model) msg (isActive:bool) = -// Bulma.dropdownItem.a [ -// prop.tabIndex 0 -// prop.onClick (fun e -> -// e.stopPropagation() -// exportType |> msg -// ) -// prop.onKeyDown (fun k -> if (int k.which) = 13 then exportType |> msg) -// prop.children [ -// Html.span [ -// prop.className "has-tooltip-right has-tooltip-multiline" -// prop.custom ("data-tooltip", exportType.toExplanation) -// prop.style [style.fontSize(length.rem 1.1); style.paddingRight 10; style.textAlign.center; style.color NFDIColors.Yellow.Darker20] -// Html.i [prop.className "fa-solid fa-circle-info"] |> prop.children -// ] - -// Html.span (exportType.ToString()) -// ] -// ] - -//let parseTableToISAJsonEle (model:Model) (dispatch:Messages.Msg -> unit) = -// mainFunctionContainer [ -// Bulma.field.div [ -// Bulma.field.hasAddons -// prop.children [ -// Bulma.control.div [ -// Bulma.dropdown [ -// if model.JsonExporterModel.ShowTableExportTypeDropdown then Bulma.dropdown.isActive -// prop.children [ -// Bulma.dropdownTrigger [ -// Daisy.button.a [ -// prop.onClick(fun e -> e.stopPropagation(); UpdateShowTableExportTypeDropdown (not model.JsonExporterModel.ShowTableExportTypeDropdown) |> JsonExporterMsg |> dispatch ) -// prop.children [ -// span [Style [MarginRight "5px"]] [str (model.JsonExporterModel.TableJsonExportType.ToString())] -// Html.i [prop.className "fa-solid fa-angle-down"] -// ] -// ] -// ] -// Bulma.dropdownMenu [ -// Bulma.dropdownContent [ -// let msg = (UpdateTableJsonExportType >> JsonExporterMsg >> dispatch) -// dropdownItem JsonExportType.Assay model msg (model.JsonExporterModel.TableJsonExportType = JsonExportType.Assay) -// dropdownItem JsonExportType.ProcessSeq model msg (model.JsonExporterModel.TableJsonExportType = JsonExportType.ProcessSeq) -// ] -// ] -// ] -// ] -// ] -// Bulma.control.div [ -// Bulma.control.isExpanded -// Daisy.button.a [ -// button.info -// button.block -// prop.onClick(fun _ -> -// InterfaceMsg SpreadsheetInterface.ExportJsonTable |> dispatch -// ) -// prop.text "Download as isa json" -// ] |> prop.children -// ] -// ] -// ] -// ] - -//let parseTablesToISAJsonEle (model:Model) (dispatch:Messages.Msg -> unit) = -// mainFunctionContainer [ -// Bulma.field.div [ -// Bulma.field.hasAddons -// prop.children [ -// Bulma.control.div [ -// Bulma.dropdown [ -// if model.JsonExporterModel.ShowWorkbookExportTypeDropdown then Bulma.dropdown.isActive -// prop.children [ -// Bulma.dropdownTrigger [ -// Daisy.button.a [ -// prop.onClick (fun e -> e.stopPropagation(); UpdateShowWorkbookExportTypeDropdown (not model.JsonExporterModel.ShowWorkbookExportTypeDropdown) |> JsonExporterMsg |> dispatch ) -// prop.children [ -// span [Style [MarginRight "5px"]] [str (model.JsonExporterModel.WorkbookJsonExportType.ToString())] -// Html.i [prop.className "fa-solid fa-angle-down"] -// ] -// ] -// ] -// Bulma.dropdownMenu [ -// Bulma.dropdownContent [ -// let msg = (UpdateWorkbookJsonExportType >> JsonExporterMsg >> dispatch) -// dropdownItem JsonExportType.Assay model msg (model.JsonExporterModel.WorkbookJsonExportType = JsonExportType.Assay) -// dropdownItem JsonExportType.ProcessSeq model msg (model.JsonExporterModel.WorkbookJsonExportType = JsonExportType.ProcessSeq) -// ] -// ] -// ] -// ] -// ] -// Bulma.control.div [ -// Bulma.control.isExpanded -// Daisy.button.a [ -// button.info -// button.block -// prop.onClick(fun _ -> -// InterfaceMsg SpreadsheetInterface.ExportJsonTables |> dispatch -// ) -// prop.text "Download as isa json" -// ] -// |> prop.children -// ] -// ] -// ] -// ] - -//// SND ELEMENT -//open Browser.Types - -//let fileUploadButton (model:Model) dispatch (id: string) = -// Bulma.label [ -// prop.className "mb-2 has-text-weight-normal" -// prop.children [ -// Bulma.fileInput [ -// prop.id id -// prop.type' "file"; -// prop.style [style.display.none] -// prop.onChange (fun (ev: File list) -> -// let files = ev//: Browser.Types.FileList = ev.target?files - -// let blobs = -// files -// |> List.map (fun f -> f.slice() ) - -// let reader = Browser.Dom.FileReader.Create() - -// reader.onload <- fun evt -> -// let byteArr = -// let arraybuffer : Fable.Core.JS.ArrayBuffer = evt.target?result -// let uintArr = Fable.Core.JS.Constructors.Uint8Array.Create arraybuffer -// uintArr.ToString().Split([|","|], System.StringSplitOptions.RemoveEmptyEntries) -// |> Array.map (fun byteStr -> byte byteStr) - -// StoreXLSXByteArray byteArr |> JsonExporterMsg |> dispatch - -// reader.onerror <- fun evt -> -// curry GenericLog Cmd.none ("Error", evt.Value) |> DevMsg |> dispatch - -// reader.readAsArrayBuffer(blobs |> List.head) - -// let picker = Browser.Dom.document.getElementById(id) -// // https://stackoverflow.com/questions/3528359/html-input-type-file-file-selection-event/3528376 -// picker?value <- null -// ) -// ] -// Daisy.button.a [ -// button.info; -// button.block -// prop.text "Upload Excel file" -// ] -// ] -// ] - - -//let xlsxUploadAndParsingMainElement (model:Model) (dispatch: Msg -> unit) = -// let inputId = "xlsxConverter_uploadButton" -// mainFunctionContainer [ -// // Upload xlsx file to byte [] -// fileUploadButton model dispatch inputId -// // Request parsing -// Bulma.field.div [ -// Bulma.field.hasAddons -// prop.children [ -// Bulma.control.div [ -// Bulma.dropdown [ -// if model.JsonExporterModel.ShowXLSXExportTypeDropdown then Bulma.dropdown.isActive -// prop.children [ -// Bulma.dropdownTrigger [ -// Daisy.button.a [ -// prop.onClick (fun e -> e.stopPropagation(); UpdateShowXLSXExportTypeDropdown (not model.JsonExporterModel.ShowXLSXExportTypeDropdown) |> JsonExporterMsg |> dispatch ) -// prop.children [ -// span [Style [MarginRight "5px"]] [str (model.JsonExporterModel.XLSXParsingExportType.ToString())] -// Html.i [prop.className "fa-solid fa-angle-down"] -// ] -// ] -// ] -// Bulma.dropdownMenu [ -// Bulma.dropdownContent [ -// let msg = (UpdateXLSXParsingExportType >> JsonExporterMsg >> dispatch) -// dropdownItem JsonExportType.Assay model msg (model.JsonExporterModel.XLSXParsingExportType = JsonExportType.Assay) -// dropdownItem JsonExportType.ProcessSeq model msg (model.JsonExporterModel.XLSXParsingExportType = JsonExportType.ProcessSeq) -// dropdownItem JsonExportType.ProtocolTemplate model msg (model.JsonExporterModel.XLSXParsingExportType = JsonExportType.ProtocolTemplate) -// ] -// ] -// ] -// ] -// ] -// Bulma.control.div [ -// Bulma.control.isExpanded -// Daisy.button.a [ -// let hasContent = model.JsonExporterModel.XLSXByteArray <> Array.empty -// button.info -// if hasContent then -// Daisy.button.isActive -// else -// button.error -// prop.disabled true -// button.block -// prop.onClick(fun _ -> -// if hasContent then -// ParseXLSXToJsonRequest model.JsonExporterModel.XLSXByteArray |> JsonExporterMsg |> dispatch -// ) -// prop.text "Download as isa json" -// ] -// |> prop.children -// ] -// ] -// ] -// ] - -//let jsonExporterMainElement (model:Model) (dispatch: Messages.Msg -> unit) = - - //Bulma.content [ - - // prop.onSubmit (fun e -> e.preventDefault()) - // prop.onKeyDown (fun k -> if (int k.which) = 13 then k.preventDefault()) - // prop.onClick (fun e -> CloseAllDropdowns |> JsonExporterMsg |> dispatch) - // prop.style [style.minHeight(length.vh 100)] - // prop.children [ - // Bulma.label "Json Exporter" - - // Bulma.help [ - // str "Export swate annotation tables to " - // a [Href @"https://en.wikipedia.org/wiki/JSON"] [str "JSON"] - // str " format. Official ISA-JSON types can be found " - // a [Href @"https://isa-specs.readthedocs.io/en/latest/isajson.html#"] [str "here"] - // str "." - // ] - - // Bulma.label "Export active table" - - // parseTableToISAJsonEle model dispatch - - // Bulma.label "Export workbook" - - // parseTablesToISAJsonEle model dispatch - - // Bulma.label "Export Swate conform xlsx file." - - // xlsxUploadAndParsingMainElement model dispatch - // ] - //] - type private JsonExportState = { ExportFormat: JsonExportFormat } with @@ -293,7 +45,7 @@ type FileExporter = static member private FileFormat(efm: JsonExportFormat, state: JsonExportState, setState) = Html.option [ - prop.text (string efm) + prop.text (efm.AsStringRdbl) ] [] @@ -356,7 +108,7 @@ type FileExporter = Html.text ": A simple ARCtrl specific format." ] Html.li [ - Html.b "ARCtrlCompressed" + Html.b "ARCtrl Compressed" Html.text ": A compressed ARCtrl specific format." ] Html.li [ @@ -370,12 +122,12 @@ type FileExporter = Html.text ")." ] Html.li [ - Html.b "ROCrate" - Html.text ": ROCrate format (" + Html.b "RO-Crate Metadata" + Html.text ": RO-Crate format (" Html.a [ prop.target.blank prop.href "https://www.researchobject.org/ro-crate/" - prop.text "ROCrate" + prop.text "RO-Crate" ] Html.text ", " Html.a [ diff --git a/src/Client/SharedComponents/Metadata/Assay.fs b/src/Client/SharedComponents/Metadata/Assay.fs index eca5b57c..ebee9927 100644 --- a/src/Client/SharedComponents/Metadata/Assay.fs +++ b/src/Client/SharedComponents/Metadata/Assay.fs @@ -8,7 +8,7 @@ open Components open Components.Forms [] -let Main(assay: ArcAssay, setArcAssay: ArcAssay -> unit, setDatamap: ArcAssay -> DataMap option -> unit) = +let Main(assay: ArcAssay, setArcAssay: ArcAssay -> unit, setDatamap: ArcAssay -> DataMap option -> unit, model: Model.Model) = Generic.Section [ Generic.BoxedField( "Assay Metadata", @@ -20,9 +20,9 @@ let Main(assay: ArcAssay, setArcAssay: ArcAssay -> unit, setDatamap: ArcAssay -> setArcAssay nextAssay ), "Identifier", - validator = {| fn = (fun s -> ARCtrl.Helper.Identifier.tryCheckValidCharacters s); msg = "Invalid Identifier" |} + validator = {| fn = (fun s -> ARCtrl.Helper.Identifier.tryCheckValidCharacters s); msg = "Invalid Identifier" |}, + disabled = Generic.isDisabledInARCitect model.PersistentStorageState.Host ) - FormComponents.OntologyAnnotationInput( assay.MeasurementType |> Option.defaultValue (OntologyAnnotation()), (fun oa -> diff --git a/src/Client/SharedComponents/Metadata/Forms.fs b/src/Client/SharedComponents/Metadata/Forms.fs index 9c9c8310..6e29152e 100644 --- a/src/Client/SharedComponents/Metadata/Forms.fs +++ b/src/Client/SharedComponents/Metadata/Forms.fs @@ -404,7 +404,8 @@ type FormComponents = ] [] - static member TextInput(value: string, setValue: string -> unit, ?label: string, ?validator: {| fn: string -> bool; msg: string |}, ?placeholder: string, ?isarea: bool, ?isJoin) = + static member TextInput(value: string, setValue: string -> unit, ?label: string, ?validator: {| fn: string -> bool; msg: string |}, ?placeholder: string, ?isarea: bool, ?isJoin, ?disabled) = + let disabled = defaultArg disabled false let isJoin = defaultArg isJoin false let loading, setLoading = React.useState(false) let isValid, setIsValid = React.useState(true) @@ -452,6 +453,8 @@ type FormComponents = match isarea with | Some true -> Daisy.textarea [ + prop.disabled disabled + prop.readOnly disabled prop.className "grow ghost" if placeholder.IsSome then prop.placeholder placeholder.Value prop.ref ref @@ -459,6 +462,8 @@ type FormComponents = ] | _ -> Html.input [ + prop.disabled disabled + prop.readOnly disabled prop.className "trunacte w-full" if placeholder.IsSome then prop.placeholder placeholder.Value prop.ref ref diff --git a/src/Client/SharedComponents/Metadata/Generic.fs b/src/Client/SharedComponents/Metadata/Generic.fs index 81f559b7..5aeffef6 100644 --- a/src/Client/SharedComponents/Metadata/Generic.fs +++ b/src/Client/SharedComponents/Metadata/Generic.fs @@ -4,6 +4,10 @@ open Feliz.DaisyUI open Feliz type Generic = + + static member isDisabledInARCitect (host: Swatehost option) = + + host.IsSome && host.Value = Swatehost.ARCitect static member FieldTitle (title:string) = Html.h5 [ prop.className "text-primary font-semibold mt-6 mb-2" diff --git a/src/Client/SharedComponents/Metadata/Investigation.fs b/src/Client/SharedComponents/Metadata/Investigation.fs index 4e302ccd..d5a8cadd 100644 --- a/src/Client/SharedComponents/Metadata/Investigation.fs +++ b/src/Client/SharedComponents/Metadata/Investigation.fs @@ -7,7 +7,7 @@ open ARCtrl open Components open Components.Forms -let Main(investigation: ArcInvestigation, setInvestigation: ArcInvestigation -> unit) = +let Main(investigation: ArcInvestigation, setInvestigation: ArcInvestigation -> unit, model: Model.Model) = Generic.Section [ Generic.BoxedField( "Investigation Metadata", @@ -17,7 +17,8 @@ let Main(investigation: ArcInvestigation, setInvestigation: ArcInvestigation -> (fun s -> let nextInvestigation = IdentifierSetters.setInvestigationIdentifier s investigation setInvestigation nextInvestigation), - "Identifier" + "Identifier", + disabled = Generic.isDisabledInARCitect model.PersistentStorageState.Host ) FormComponents.TextInput ( Option.defaultValue "" investigation.Title, diff --git a/src/Client/SharedComponents/Metadata/Study.fs b/src/Client/SharedComponents/Metadata/Study.fs index eed69075..619ea3ff 100644 --- a/src/Client/SharedComponents/Metadata/Study.fs +++ b/src/Client/SharedComponents/Metadata/Study.fs @@ -6,7 +6,7 @@ open Components open Components.Forms open System -let Main(study: ArcStudy, assignedAssays: ArcAssay list, setArcStudy: (ArcStudy * ArcAssay list) -> unit, setDatamap: ArcStudy -> DataMap option -> unit) = +let Main(study: ArcStudy, assignedAssays: ArcAssay list, setArcStudy: (ArcStudy * ArcAssay list) -> unit, setDatamap: ArcStudy -> DataMap option -> unit, model: Model.Model) = Generic.Section [ Generic.BoxedField( "Study Metadata", @@ -16,7 +16,16 @@ let Main(study: ArcStudy, assignedAssays: ArcAssay list, setArcStudy: (ArcStudy (fun s -> let nextStudy = IdentifierSetters.setStudyIdentifier s study setArcStudy (nextStudy , assignedAssays)), - "Identifier" + "Identifier", + validator = {| fn = (fun s -> ARCtrl.Helper.Identifier.tryCheckValidCharacters s); msg = "Invalid Identifier" |}, + disabled = Generic.isDisabledInARCitect model.PersistentStorageState.Host + ) + FormComponents.TextInput ( + Option.defaultValue "" study.Title, + (fun s -> + study.Title <- s |> Option.whereNot String.IsNullOrWhiteSpace + setArcStudy (study , assignedAssays)), + "Title" ) FormComponents.TextInput ( Option.defaultValue "" study.Description, diff --git a/src/Client/SharedComponents/TermSearchInput.fs b/src/Client/SharedComponents/TermSearchInput.fs index 27b4a8ce..0d573be8 100644 --- a/src/Client/SharedComponents/TermSearchInput.fs +++ b/src/Client/SharedComponents/TermSearchInput.fs @@ -242,6 +242,7 @@ type TermSearch = Html.div [ prop.id id prop.className [ + "min-w-[400px]" "grid grid-cols-[auto,1fr,1fr,auto] absolute left-0 z-50 w-full bg-base-200 rounded shadow-md border-2 border-primary py-2 pl-4 max-h-[400px] overflow-y-auto w-full" if not show then "hidden"; diff --git a/src/Client/SidebarComponents/Navbar.fs b/src/Client/SidebarComponents/Navbar.fs index b7e8c8f7..876fab13 100644 --- a/src/Client/SidebarComponents/Navbar.fs +++ b/src/Client/SidebarComponents/Navbar.fs @@ -89,7 +89,7 @@ let NoMetadataModalContent refresh (dispatch: Messages.Msg -> unit) = AddMetaDataButtons refresh dispatch ]) -let UpdateMetadataModalContent excelMetadataType setExcelMetadataType closeModal (dispatch: Messages.Msg -> unit) = +let UpdateMetadataModalContent excelMetadataType setExcelMetadataType closeModal model (dispatch: Messages.Msg -> unit) = React.fragment [ match excelMetadataType with | { Metadata = Some (ArcFiles.Assay assay)} -> @@ -100,7 +100,7 @@ let UpdateMetadataModalContent excelMetadataType setExcelMetadataType closeModal } let setAssayDataMap (assay: ArcAssay) (dataMap: DataMap option) = assay.DataMap <- dataMap - Assay.Main(assay, setAssay, setAssayDataMap) + Assay.Main(assay, setAssay, setAssayDataMap, model) | { Metadata = Some (ArcFiles.Study (study, assays))} -> let setStudy (study: ArcStudy, assays: ArcAssay list) = setExcelMetadataType { @@ -109,14 +109,14 @@ let UpdateMetadataModalContent excelMetadataType setExcelMetadataType closeModal } let setStudyDataMap (study: ArcStudy) (dataMap: DataMap option) = study.DataMap <- dataMap - Study.Main(study, assays, setStudy, setStudyDataMap) + Study.Main(study, assays, setStudy, setStudyDataMap, model) | { Metadata = Some (ArcFiles.Investigation investigation)} -> let setInvestigation (investigation: ArcInvestigation) = setExcelMetadataType { excelMetadataType with Metadata = Some (ArcFiles.Investigation investigation) } - Investigation.Main(investigation, setInvestigation) + Investigation.Main(investigation, setInvestigation, model) | { Metadata = Some (ArcFiles.Template template)} -> let setTemplate (template: Template) = setExcelMetadataType { @@ -161,7 +161,7 @@ let UpdateMetadataModalContent excelMetadataType setExcelMetadataType closeModal // Define a modal dialog component [] -let SelectModalDialog (closeModal: unit -> unit) (dispatch: Messages.Msg -> unit) = +let SelectModalDialog (closeModal: unit -> unit) model (dispatch: Messages.Msg -> unit) = let (excelMetadataType, setExcelMetadataType) = React.useState(ExcelMetadataState.init) let refreshMetadataState = fun () -> @@ -198,7 +198,7 @@ let SelectModalDialog (closeModal: unit -> unit) (dispatch: Messages.Msg -> unit | { Metadata = None } -> NoMetadataModalContent refreshMetadataState dispatch | { Metadata = Some metadata } -> - UpdateMetadataModalContent excelMetadataType setExcelMetadataType closeModal dispatch + UpdateMetadataModalContent excelMetadataType setExcelMetadataType closeModal model dispatch ] ] ] @@ -283,6 +283,7 @@ let NavbarComponent (model : Model) (dispatch : Messages.Msg -> unit) = if state.ExcelMetadataModalActive then SelectModalDialog toggleMetdadataModal + model dispatch Html.div [ prop.ariaLabel "logo" diff --git a/src/Client/Spreadsheet/IO.fs b/src/Client/Spreadsheet/IO.fs index f9015d59..f3ce5a70 100644 --- a/src/Client/Spreadsheet/IO.fs +++ b/src/Client/Spreadsheet/IO.fs @@ -13,13 +13,13 @@ module Xlsx = let ws = fswb.GetWorksheets() let arcfile = match ws with - | _ when ws.Exists (fun ws -> ARCtrl.Spreadsheet.ArcAssay.isMetadataSheetName ws.Name) -> - ArcAssay.fromFsWorkbook fswb |> Assay - | _ when ws.Exists (fun ws -> ARCtrl.Spreadsheet.ArcStudy.isMetadataSheetName ws.Name) -> + | _ when ws.Exists (fun ws -> ARCtrl.Spreadsheet.ArcAssay.isMetadataSheetName ws.Name) -> + ArcAssay.fromFsWorkbook fswb |> Assay + | _ when ws.Exists (fun ws -> ARCtrl.Spreadsheet.ArcStudy.isMetadataSheetName ws.Name) -> ArcStudy.fromFsWorkbook fswb |> Study - | _ when ws.Exists (fun ws -> ARCtrl.Spreadsheet.ArcInvestigation.isMetadataSheetName ws.Name) -> + | _ when ws.Exists (fun ws -> ARCtrl.Spreadsheet.ArcInvestigation.isMetadataSheetName ws.Name) -> ArcInvestigation.fromFsWorkbook fswb |> Investigation - | _ when ws.Exists (fun ws -> ARCtrl.Spreadsheet.Template.metaDataSheetName = ws.Name || ARCtrl.Spreadsheet.Template.obsoletemetaDataSheetName = ws.Name) -> + | _ when ws.Exists (fun ws -> ARCtrl.Spreadsheet.Template.metadataSheetName = ws.Name || ARCtrl.Spreadsheet.Template.obsoleteMetadataSheetName = ws.Name) -> ARCtrl.Spreadsheet.Template.fromFsWorkbook fswb |> Template | _ -> failwith "Unable to identify given file. Missing metadata sheet with correct name." return arcfile diff --git a/src/Client/Update.fs b/src/Client/Update.fs index bf9ceef0..8f235c34 100644 --- a/src/Client/Update.fs +++ b/src/Client/Update.fs @@ -9,17 +9,16 @@ open Routing open Messages open Model -let urlUpdate (route: Route option) (currentModel:Model) : Model * Cmd = - match route with - | Some (Route.Home queryIntegerOption) -> - let swatehost = Swatehost.ofQueryParam queryIntegerOption - let nextModel = { - currentModel with - Model.PersistentStorageState.Host = Some swatehost - } - nextModel,Cmd.none - | None -> - currentModel, Cmd.none +let urlUpdate (route: Route option) (model:Model) : Model * Cmd = + log "hit urlUpdate" + let cmd (host: Swatehost) = SpreadsheetInterface.Initialize host |> InterfaceMsg |> Cmd.ofMsg + let host = + match route with + | Some (Routing.Route.Home queryIntegerOption) -> + Swatehost.ofQueryParam queryIntegerOption + | None -> + Swatehost.Browser + {model with Model.PersistentStorageState.Host = Some host}, cmd host module AdvancedSearch = diff --git a/src/Client/Update/InterfaceUpdate.fs b/src/Client/Update/InterfaceUpdate.fs index 21de66ec..81e2b304 100644 --- a/src/Client/Update/InterfaceUpdate.fs +++ b/src/Client/Update/InterfaceUpdate.fs @@ -14,6 +14,26 @@ open Shared open Fable.Core.JsInterop open Shared.ARCtrlHelper +module private ModelUtil = + + open LocalHistory + open Model + + type Model.Model with + member this.UpdateFromLocalStorage() = + match this.PersistentStorageState.Host with + | Some Swatehost.Browser -> + let dt = LocalStorage.Darkmode.DataTheme.GET() + LocalStorage.Darkmode.DataTheme.SET dt + { this with + Model.SpreadsheetModel = Spreadsheet.Model.fromLocalStorage() + Model.History = this.History.UpdateFromSessionStorage() + } + | _ -> + this + +open ModelUtil + /// This seems like such a hack :( module private ExcelHelper = @@ -62,7 +82,6 @@ module Interface = let innerUpdate (model: Model) (msg: SpreadsheetInterface.Msg) = match msg with - // This is very bloated, might be good to reduce | Initialize host -> let cmd = Cmd.batch [ @@ -79,7 +98,9 @@ module Interface = ARCitect.ARCitect.send ARCitect.Init ) ] - model, cmd + /// Updates from local storage if standalone in browser + let nextModel = model.UpdateFromLocalStorage() + nextModel, cmd | CreateAnnotationTable usePrevOutput -> match host with | Some Swatehost.Excel -> diff --git a/src/Client/Update/SpreadsheetUpdate.fs b/src/Client/Update/SpreadsheetUpdate.fs index d99f6b4c..12fd7c91 100644 --- a/src/Client/Update/SpreadsheetUpdate.fs +++ b/src/Client/Update/SpreadsheetUpdate.fs @@ -23,11 +23,11 @@ module Spreadsheet = /// let updateHistoryStorageMsg (msg: Spreadsheet.Msg) (state: Spreadsheet.Model, model: Model, cmd) = match msg with - | UpdateActiveView _ | UpdateHistoryPosition _ | Reset | UpdateSelectedCells _ - | UpdateActiveCell _ | CopySelectedCell | CopyCell _ | MoveSelectedCell _ | SetActiveCellFromSelected -> + | UpdateActiveView _ | UpdateHistoryPosition _ | Reset | UpdateSelectedCells _ + | UpdateActiveCell _ | CopySelectedCell | CopyCell _ | MoveSelectedCell _ | SetActiveCellFromSelected -> state.SaveToLocalStorage() // This will cache the most up to date table state to local storage. state, model, cmd - | _ -> + | _ -> state.SaveToLocalStorage() // This will cache the most up to date table state to local storage. let nextHistory = model.History.SaveSessionSnapshot state // this will cache the table state for certain operations in session storage. if model.PersistentStorageState.Host = Some Swatehost.ARCitect then @@ -54,7 +54,7 @@ module Spreadsheet = // (Messages.curry Messages.GenericError Cmd.none >> Messages.DevMsg) //let newHistoryController (state, model, cmd) = - // updateSessionStorageMsg msg, model + // updateSessionStorageMsg msg, model let innerUpdate (state: Spreadsheet.Model) (model: Model) (msg: Spreadsheet.Msg) = match msg with @@ -92,7 +92,7 @@ module Spreadsheet = let msg = fun t -> JoinTable(t, index, options) |> SpreadsheetMsg let cmd = Table.selectiveTablePrepare state.ActiveTable table - |> msg + |> msg |> Cmd.ofMsg state, model, cmd | JoinTable (table, index, options) -> @@ -103,8 +103,8 @@ module Spreadsheet = let nextState = if reset then Spreadsheet.Model.init(arcFile) - else - { state with + else + { state with ArcFile = Some arcFile } nextState, model, Cmd.none @@ -125,18 +125,18 @@ module Spreadsheet = nextState, model, Cmd.none | UpdateCells arr -> Controller.Generic.setCells arr state - let nextState = + let nextState = {state with ArcFile = state.ArcFile} nextState, model, Cmd.none | UpdateHeader (index, header) -> - let nextState = + let nextState = state.ActiveTable.UpdateHeader(index, header) {state with ArcFile = state.ArcFile} nextState, model, Cmd.none | UpdateActiveView nextView -> - let nextState = { - state with - ActiveView = nextView + let nextState = { + state with + ActiveView = nextView SelectedCells = Set.empty } nextState, model, Cmd.none @@ -156,7 +156,7 @@ module Spreadsheet = state, model | _ -> /// Run this first so an error breaks the function before any mutables are changed - let nextState = + let nextState = Spreadsheet.Model.fromSessionStorage(newPosition) Browser.WebStorage.sessionStorage.setItem(Keys.swate_session_history_position, string newPosition) let nextModel = {model with History.HistoryCurrentPosition = newPosition} @@ -203,7 +203,7 @@ module Spreadsheet = let cmd = match state.SelectedCells.IsEmpty with | true -> Cmd.none - | false -> + | false -> let moveBy = match keypressed with | Key.Down -> (0,1) @@ -224,7 +224,7 @@ module Spreadsheet = UpdateSelectedCells s |> SpreadsheetMsg |> Cmd.ofMsg state, model, cmd | SetActiveCellFromSelected -> - let cmd = + let cmd = if state.SelectedCells.IsEmpty then Cmd.none else @@ -236,30 +236,30 @@ module Spreadsheet = let nextState = { state with ActiveCell = next } nextState, model, Cmd.none | CopyCell index -> - let cmd = - Cmd.OfPromise.attempt - (Controller.Clipboard.copyCellByIndex index) + let cmd = + Cmd.OfPromise.attempt + (Controller.Clipboard.copyCellByIndex index) state (curry GenericError Cmd.none >> DevMsg) state, model, cmd | CopyCells indices -> - let cmd = - Cmd.OfPromise.attempt - (Controller.Clipboard.copyCellsByIndex indices) + let cmd = + Cmd.OfPromise.attempt + (Controller.Clipboard.copyCellsByIndex indices) state (curry GenericError Cmd.none >> DevMsg) state, model, cmd | CopySelectedCell -> - let cmd = - Cmd.OfPromise.attempt - (Controller.Clipboard.copySelectedCell) + let cmd = + Cmd.OfPromise.attempt + (Controller.Clipboard.copySelectedCell) state (curry GenericError Cmd.none >> DevMsg) state, model, cmd | CopySelectedCells -> - let cmd = - Cmd.OfPromise.attempt - (Controller.Clipboard.copySelectedCells) + let cmd = + Cmd.OfPromise.attempt + (Controller.Clipboard.copySelectedCells) state (curry GenericError Cmd.none >> DevMsg) state, model, cmd @@ -319,7 +319,7 @@ module Spreadsheet = let nextState = Controller.Table.fillColumnWithCell index state nextState, model, Cmd.none //| EditColumn (columnIndex, newCellType, b_type) -> - // let cmd = createPromiseCmd <| fun _ -> Controller.editColumn (columnIndex, newCellType, b_type) state + // let cmd = createPromiseCmd <| fun _ -> Controller.editColumn (columnIndex, newCellType, b_type) state // state, model, cmd | ImportXlsx bytes -> let cmd = @@ -357,8 +357,8 @@ module Spreadsheet = state, model, Cmd.none try innerUpdate state model msg - |> Helper.updateHistoryStorageMsg msg + // |> Helper.updateHistoryStorageMsg msg with - | e -> + | e -> let cmd = GenericError (Cmd.none, e) |> DevMsg |> Cmd.ofMsg state, model, cmd \ No newline at end of file diff --git a/src/Client/Views/SpreadsheetView.fs b/src/Client/Views/SpreadsheetView.fs index dc029a3c..c9deac7e 100644 --- a/src/Client/Views/SpreadsheetView.fs +++ b/src/Client/Views/SpreadsheetView.fs @@ -90,17 +90,17 @@ let Main (model: Model, dispatch) = assay |> Assay |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch let setAssayDataMap assay dataMap = dataMap |> SpreadsheetInterface.UpdateDatamap |> InterfaceMsg |> dispatch - Components.Metadata.Assay.Main(assay, setAssay, setAssayDataMap) + Components.Metadata.Assay.Main(assay, setAssay, setAssayDataMap, model) | Some (ArcFiles.Study (study, assays)) -> let setStudy (study, assays) = (study, assays) |> Study |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch let setStudyDataMap study dataMap = dataMap |> SpreadsheetInterface.UpdateDatamap |> InterfaceMsg |> dispatch - Components.Metadata.Study.Main(study, assays, setStudy, setStudyDataMap) + Components.Metadata.Study.Main(study, assays, setStudy, setStudyDataMap, model) | Some (ArcFiles.Investigation investigation) -> let setInvesigation investigation = investigation |> Investigation |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch - Components.Metadata.Investigation.Main(investigation, setInvesigation) + Components.Metadata.Investigation.Main(investigation, setInvesigation, model) | Some (ArcFiles.Template template) -> let setTemplate template = template |> ArcFiles.Template |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch diff --git a/src/Server/Server.fsproj b/src/Server/Server.fsproj index d92ebb54..e5db47cb 100644 --- a/src/Server/Server.fsproj +++ b/src/Server/Server.fsproj @@ -19,7 +19,7 @@ - + diff --git a/src/Shared/ARCtrl.Helper.fs b/src/Shared/ARCtrl.Helper.fs index 8b23df91..67abd0a4 100644 --- a/src/Shared/ARCtrl.Helper.fs +++ b/src/Shared/ARCtrl.Helper.fs @@ -24,7 +24,7 @@ module ARCtrlHelper = with member this.HasTableAt(index: int) = match this with - | Template _ -> index = 0 // Template always has exactly one table + | Template _ -> index = 0 // Template always has exactly one table | Investigation i -> false | Study (s,_) -> s.TableCount <= index | Assay a -> a.TableCount <= index @@ -56,6 +56,13 @@ module ARCtrlHelper = | "rocrate" -> ROCrate | _ -> failwithf "Unknown JSON export format: %s" str + member this.AsStringRdbl = + match this with + | ARCtrl -> "ARCtrl" + | ARCtrlCompressed -> "ARCtrl Compressed" + | ISA -> "ISA" + | ROCrate -> "RO-Crate Metadata" + module Table = /// @@ -82,7 +89,7 @@ module 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. + /// The output of this function can be used with the SpreadsheetInterface.JoinTable Message. /// /// The active/current table /// The new table, which will be added to the existing one. @@ -123,7 +130,7 @@ module Helper = let ele = arr.[currentColumnIndex] arr.RemoveAt(currentColumnIndex) arr.Insert(newColumnIndex, ele) - + let dictMoveColumn (currentColumnIndex: int) (newColumnIndex: int) (table: Dictionary) = /// This is necessary to always access the correct value for an index. /// It is possible to only copy the specific target column at "currentColumnIndex" and sort the keys in the for loop depending on "currentColumnIndex" and "newColumnIndex". @@ -133,7 +140,7 @@ module Helper = let range = [System.Math.Min(currentColumnIndex, newColumnIndex) .. System.Math.Max(currentColumnIndex,newColumnIndex)] for columnIndex, rowIndex in backupTable.Keys do let value = backupTable.[(columnIndex,rowIndex)] - let newColumnIndex = + let newColumnIndex = if columnIndex = currentColumnIndex then newColumnIndex elif List.contains columnIndex range then @@ -171,7 +178,7 @@ with | Component | Characteristic | Factor - | Parameter + | Parameter | ProtocolType -> true | _ -> false member this.HasOA() = @@ -184,7 +191,7 @@ with member this.HasIOType() = match this with - | Input + | Input | Output -> true | _ -> false @@ -321,10 +328,10 @@ module Extensions = let updateBody = Helper.dictMoveColumn currentIndex nextIndex this.Values () - + type Template with - member this.FileName + member this.FileName with get() = this.Name.Replace(" ","_") + ".xlsx" type CompositeHeader with @@ -349,18 +356,18 @@ module Extensions = /// This will only run successfully if the inner values are of the same type /// /// The header from which the inner value will be taken. - member this.UpdateDeepWith(other:CompositeHeader) = + member this.UpdateDeepWith(other:CompositeHeader) = match this, other with | h1, h2 when this.IsIOType && other.IsIOType -> let io1 = h2.TryIOType().Value - match h1 with - | CompositeHeader.Input _ -> CompositeHeader.Input io1 + match h1 with + | CompositeHeader.Input _ -> CompositeHeader.Input io1 | CompositeHeader.Output _ -> CompositeHeader.Output io1 | _ -> failwith "Error 1 in UpdateSurfaceTo. This should never hit." | h1, h2 when this.IsTermColumn && other.IsTermColumn && not this.IsFeaturedColumn && not other.IsFeaturedColumn -> let oa1 = h2.ToTerm() h1.UpdateWithOA oa1 - | _ -> + | _ -> this member this.TryOA() = @@ -394,7 +401,7 @@ module Extensions = /// /// This is an override of an existing ARCtrl version which does not return what i want 😤 /// - member this.GetContentSwate() = + member this.GetContentSwate() = match this with | CompositeCell.FreeText s -> [|s|] | CompositeCell.Term oa -> [| oa.NameText; defaultArg oa.TermSourceREF ""; defaultArg oa.TermAccessionNumber ""|] @@ -414,7 +421,7 @@ module Extensions = // | Error msg -> raise (exn msg) /// - /// + /// /// /// /// @@ -445,19 +452,19 @@ module Extensions = member this.ToTabStr() = this.GetContentSwate() |> String.concat "\t" - static member fromTabStr (str:string) (header: CompositeHeader) = + static member fromTabStr (str:string) (header: CompositeHeader) = let content = str.Split('\t', System.StringSplitOptions.TrimEntries) CompositeCell.fromContentValid(content, header) static member ToTabTxt (cells: CompositeCell []) = - cells + cells |> Array.map (fun c -> c.ToTabStr()) |> String.concat (System.Environment.NewLine) static member fromTabTxt (tabTxt: string) (header: CompositeHeader) = let lines = tabTxt.Split(System.Environment.NewLine, System.StringSplitOptions.None) let cells = lines |> Array.map (fun line -> CompositeCell.fromTabStr line header) - cells + cells member this.ConvertToValidCell (header: CompositeHeader) = match this with @@ -492,7 +499,7 @@ module Extensions = member this.UpdateMainField(s: string) = match this with - | CompositeCell.Term oa -> + | CompositeCell.Term oa -> oa.Name <- Some s CompositeCell.Term oa | CompositeCell.Unitized (_, oa) -> CompositeCell.Unitized (s, oa) diff --git a/src/Shared/Shared.fsproj b/src/Shared/Shared.fsproj index 18beb732..31d37d4f 100644 --- a/src/Shared/Shared.fsproj +++ b/src/Shared/Shared.fsproj @@ -14,7 +14,7 @@ - - + + \ No newline at end of file