diff --git a/package-lock.json b/package-lock.json index ae5d9d64..6c15efa1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,7 @@ "requires": true, "packages": { "": { + "name": "Swate", "dependencies": { "@nfdi4plants/exceljs": "^0.3.0", "bulma": "^1.0.1", diff --git a/src/Client/Client.fs b/src/Client/Client.fs index 57e702f3..342688ad 100644 --- a/src/Client/Client.fs +++ b/src/Client/Client.fs @@ -37,7 +37,8 @@ let View (model : Model) (dispatch : Msg -> unit) = let v = {colorstate with SetTheme = setColorstate} React.contextProvider(LocalStorage.Darkmode.themeContext, v, Html.div [ - prop.className "flex grow" + prop.id "ClientView" + prop.className "flex w-full h-full overflow-auto" prop.children [ match model.PersistentStorageState.Host with | Some Swatehost.Excel -> diff --git a/src/Client/Helper.fs b/src/Client/Helper.fs index 849ba98e..9a345ad5 100644 --- a/src/Client/Helper.fs +++ b/src/Client/Helper.fs @@ -148,21 +148,13 @@ type Navigator = [] let navigator : Navigator = jsNative -/// -/// take "count" many items from array if existing. if not enough items return as many as possible -/// -/// -/// -let takeFromArray (count: int) (array: 'a []) = - let exit (acc: 'a list) = List.rev acc |> Array.ofList - let rec takeRec (l2: 'a list) (acc: 'a list) index = - if index >= count then - exit acc - else - match l2 with - | [] -> exit acc - | item::tail -> - let newAcc = item::acc - takeRec tail newAcc (index+1) - - takeRec (Array.toList array) [] 0 \ No newline at end of file +module Array = + + /// + /// Take "count" many items from array if existing. if not enough items return as many as possible + /// + /// + /// + let takeSafe (count: int) (array: 'a []) = + let count = System.Math.Min(count, array.Length) + Array.take count array \ No newline at end of file diff --git a/src/Client/MainComponents/CellStyles.fs b/src/Client/MainComponents/CellStyles.fs index 39258e6e..82d7431d 100644 --- a/src/Client/MainComponents/CellStyles.fs +++ b/src/Client/MainComponents/CellStyles.fs @@ -7,8 +7,10 @@ open Feliz.Bulma open Fable.Core let cellStyle (specificStyle: IStyleAttribute list) = prop.style [ - style.minWidth 100 - style.height 22 + style.minWidth 200 + style.maxWidth 600 + style.height 35 + style.minHeight(35) style.border(length.px 1, borderStyle.solid, "darkgrey") yield! specificStyle ] @@ -17,15 +19,20 @@ let cellInnerContainerStyle (specificStyle: IStyleAttribute list) = prop.style [ style.display.flex; style.justifyContent.spaceBetween; style.height(length.percent 100); - style.minHeight(35) style.width(length.percent 100) - style.alignItems.center yield! specificStyle ] -let basicValueDisplayCell (v: string) = +let basicValueDisplayCell (v: string) (isHeader: bool) = Html.span [ + if v.Length > 60 then + prop.title v prop.style [ + style.textOverflow.ellipsis + style.overflow.hidden + //style.whitespace.nowrap + if not isHeader then + style.maxHeight 35 style.flexGrow 1 style.padding(length.em 0.5,length.em 0.75) ] diff --git a/src/Client/MainComponents/Cells.fs b/src/Client/MainComponents/Cells.fs index 28053d50..0b00b108 100644 --- a/src/Client/MainComponents/Cells.fs +++ b/src/Client/MainComponents/Cells.fs @@ -170,10 +170,13 @@ type Cell = prop.readOnly readonly cellStyle [] prop.onContextMenu (CellAux.contextMenuController (columnIndex, -1) model dispatch) - if columnType.IsRefColumn then - prop.className "bg-gray-300 dark:bg-gray-700" - else - prop.className "bg-white dark:bg-black-800" + prop.className [ + "w-[300px]" // horizontal resize property sets width, but cannot override style.width. Therefore we set width as class, which makes it overridable by resize property. + if columnType.IsRefColumn then + "bg-gray-300 dark:bg-gray-700" + else + "bg-white dark:bg-black-800" + ] prop.children [ Html.div [ cellInnerContainerStyle [style.custom("backgroundColor","inherit")] @@ -191,7 +194,7 @@ type Cell = match columnType with | TSR | TAN -> $"{columnType} ({cellValue})" | _ -> cellValue - basicValueDisplayCell cellValue + basicValueDisplayCell cellValue true if columnType = Main && not header.IsSingleColumn then extendHeaderButton(state_extend, columnIndex, setState_extend) ] @@ -335,7 +338,7 @@ type Cell = if columnType = Main && oasetter.IsSome then CellStyles.compositeCellDisplay oasetter.Value.oa displayValue else - basicValueDisplayCell displayValue + basicValueDisplayCell displayValue false ] ] ] diff --git a/src/Client/MainComponents/ContextMenu.fs b/src/Client/MainComponents/ContextMenu.fs index 4be4f4ad..05ac539e 100644 --- a/src/Client/MainComponents/ContextMenu.fs +++ b/src/Client/MainComponents/ContextMenu.fs @@ -5,6 +5,7 @@ open Feliz.Bulma open Spreadsheet open ARCtrl open Model +open Shared module private Shared = @@ -59,7 +60,8 @@ module Table = Clear : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit TransformCell : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit UpdateAllCells : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit - //EditColumn : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit + GetTermDetails : OntologyAnnotation -> (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit + EditColumn : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit RowIndex : int ColumnIndex : int } @@ -68,7 +70,11 @@ module Table = /// This element will remove the contextmenu when clicking anywhere else let isHeader = Shared.isHeader funcs.RowIndex let buttonList = [ - //button ("Edit Column", "fa-solid fa-table-columns", funcs.EditColumn rmv, []) + Shared.button ("Edit Column", "fa-solid fa-table-columns", funcs.EditColumn rmv, []) + if (isHeader && header.IsTermColumn) || Shared.isUnitOrTermCell contextCell then + let oa = if isHeader then header.ToTerm() else contextCell.Value.ToOA() + if oa.TermAccessionShort <> "" then + Shared.button ("Details", "fa-solid fa-magnifying-glass", funcs.GetTermDetails oa rmv, []) if not isHeader then Shared.button ("Fill Column", "fa-solid fa-pen", funcs.FillColumn rmv, []) if Shared.isUnitOrTermCell contextCell then @@ -106,8 +112,6 @@ module Table = ] ] - open Shared - let onContextMenu (index: int*int, model: Model, dispatch) = fun (e: Browser.Types.MouseEvent) -> e.stopPropagation() e.preventDefault() @@ -124,12 +128,14 @@ module Table = let cell = model.SpreadsheetModel.ActiveTable.TryGetCellAt(ci, ri) let header = model.SpreadsheetModel.ActiveTable.Headers.[ci] let isSelectedCell = model.SpreadsheetModel.SelectedCells.Contains index - //let editColumnEvent _ = Modals.Controller.renderModal("EditColumn_Modal", Modals.EditColumn.Main (fst index) model dispatch) + let editColumnEvent _ = Modals.Controller.renderModal("EditColumn_Modal", Modals.EditColumn.Main (fst index) model dispatch) let triggerMoveColumnModal _ = Modals.Controller.renderModal("MoveColumn_Modal", Modals.MoveColumn.Main(ci, model, dispatch)) let triggerUpdateColumnModal _ = let columnIndex = fst index let column = model.SpreadsheetModel.ActiveTable.GetColumn columnIndex Modals.Controller.renderModal("UpdateColumn_Modal", Modals.UpdateColumn.Main(ci, column, dispatch)) + let triggerTermModal oa _ = + Modals.Controller.renderModal("TermDetails_Modal", Modals.TermModal.Main(oa, dispatch)) let funcs = { DeleteRow = fun rmv e -> rmv e; deleteRowEvent e DeleteColumn = fun rmv e -> rmv e; Spreadsheet.DeleteColumn (ci) |> Messages.SpreadsheetMsg |> dispatch @@ -161,7 +167,8 @@ module Table = rmv e; Spreadsheet.UpdateCell (index, nextCell) |> Messages.SpreadsheetMsg |> dispatch UpdateAllCells = fun rmv e -> rmv e; triggerUpdateColumnModal e - //EditColumn = fun rmv e -> rmv e; editColumnEvent e + GetTermDetails = fun oa rmv e -> rmv e; triggerTermModal oa e + EditColumn = fun rmv e -> rmv e; editColumnEvent e RowIndex = snd index ColumnIndex = fst index } diff --git a/src/Client/MainComponents/MainViewContainer.fs b/src/Client/MainComponents/MainViewContainer.fs index 48f29d9f..806ac4a3 100644 --- a/src/Client/MainComponents/MainViewContainer.fs +++ b/src/Client/MainComponents/MainViewContainer.fs @@ -1,4 +1,4 @@ -module MainComponents.MainViewContainer +module MainComponents.MainViewContainer open Feliz diff --git a/src/Client/Modals/EditColumn.fs b/src/Client/Modals/EditColumn.fs index 27424191..28d64eca 100644 --- a/src/Client/Modals/EditColumn.fs +++ b/src/Client/Modals/EditColumn.fs @@ -1,211 +1,224 @@ module Modals.EditColumn -//open Feliz -//open Feliz.Bulma -//open ExcelColors -//open Model -//open Messages -//open Shared -//open OfficeInteropTypes -//open TermTypes -//open Spreadsheet - -//type private ColumnType = -//| Freetext -//| Unit -//| Term - -//type private EditState = { -// NextType: ColumnType -// BuildingBlockType: BuildingBlockType option -//} with -// static member init(columnHeader: HeaderCell) = -// let b_type, c_type = -// match columnHeader with -// | unit when columnHeader.HasUnit -> Some columnHeader.BuildingBlockType, Unit -// | term when columnHeader.Term.IsSome -> Some columnHeader.BuildingBlockType, Term -// | ft -> Some columnHeader.BuildingBlockType, Freetext -// { -// NextType = c_type -// BuildingBlockType = b_type -// } - -//let private updateField state setState = -// Bulma.field.div [ -// Bulma.label [prop.text "Column type"] -// Bulma.control.div [ -// prop.style [style.display.inlineFlex; style.justifyContent.spaceEvenly;] -// prop.children [ -// Html.label [ -// prop.className "radio pl-1 pr-1 nonSelectText" -// prop.children [ -// Html.input [ -// prop.isChecked (state.NextType = Freetext) -// prop.type' "radio"; prop.name "c_type" -// prop.onChange(fun (e:Browser.Types.Event) -> -// setState {state with NextType = Freetext} -// ) -// ] -// Html.text " Freetext" -// ] -// ] -// Html.label [ -// prop.className "radio pl-1 pr-1 nonSelectText" -// prop.children [ -// Html.input [ -// prop.isChecked (state.NextType = Term) -// prop.type' "radio"; prop.name "c_type" -// prop.onChange(fun (e:Browser.Types.Event) -> -// setState {state with NextType = Term} -// ) -// ] -// Html.text " Term" -// ] -// ] -// Html.label [ -// prop.className "radio pl-1 pr-1 nonSelectText" -// prop.children [ -// Html.input [ -// prop.isChecked (state.NextType = Unit) -// prop.type' "radio"; prop.name "c_type" -// prop.onChange(fun (e:Browser.Types.Event) -> -// setState {state with NextType = Unit} -// ) -// ] -// Html.span " Unit" -// ] -// ] -// ] -// ] -// ] - -//open Fable.Core.JsInterop - -//let private buildingBlockField (state: EditState) (setState: EditState -> unit) = -// let options = BuildingBlockType.TermColumns -// Bulma.field.div [ -// Bulma.label [prop.text "Select building block type"] -// Bulma.select [ -// prop.value (Option.defaultValue BuildingBlockType.Parameter state.BuildingBlockType |> fun x -> x.toString) -// prop.onChange(fun (e: Browser.Types.Event) -> -// let b_type = string e.target?value |> BuildingBlockType.ofString -// let nextState = {state with BuildingBlockType = Some b_type} -// setState nextState -// ) -// prop.children [ -// for termColumn in options do -// yield Html.option [ -// prop.value termColumn.toString -// prop.text termColumn.toString -// ] -// ] -// ] -// ] - -//let private previewField (column : (int*SwateCell) []) state = -// Bulma.field.div [ -// Bulma.label [prop.text "Preview"] -// Bulma.tableContainer [ -// Bulma.table [ -// prop.style [style.height (length.percent 50)] -// Bulma.table.isStriped -// prop.children [ -// Html.thead [ -// Html.tr [ -// let header = (snd column.[0]) -// let headerUpdated = -// match state.NextType, state.BuildingBlockType with -// | Unit, Some bb -> header.toUnitHeader(bb) -// | Unit, None -> header.toUnitHeader() -// | Term, Some bb -> header.toTermHeader(bb) -// | Term, None -> header.toTermHeader() -// | Freetext, Some bb -> header.toFreetextHeader(bb) -// | Freetext, None -> header.toFreetextHeader() -// match state.NextType with -// | Freetext -> -// Html.th $"{headerUpdated.DisplayValue}" -// | Term -> -// let uid = headerUpdated.Term.Value.TermAccession -// Html.th $"{headerUpdated.DisplayValue}" -// Html.th $"{ColumnCoreNames.TermAccessionNumber.toString} ({uid})" -// | Unit -> -// let uid = headerUpdated.Term.Value.TermAccession -// Html.th $"{headerUpdated.DisplayValue}" -// Html.th $"{ColumnCoreNames.Unit.toString}" -// Html.th $"{ColumnCoreNames.TermAccessionNumber.toString} ({uid})" -// ] -// ] -// Html.tbody [ -// prop.children [ -// for _,cell in Array.skip 1 column |> Array.truncate 9 do -// let cellUpdated = -// match state.NextType with -// | Unit -> cell.toUnitCell() -// | Freetext -> cell.toFreetextCell() -// | Term -> cell.toTermCell() -// Html.tr [ -// match state.NextType with -// | Unit -> -// Html.td $"{cellUpdated.Unit.Value}" -// Html.td $"{cellUpdated.Unit.Unit.Name}" -// Html.td $"{cellUpdated.Unit.Unit.TermAccession}" -// | Freetext -> -// Html.td $"{cellUpdated.Freetext.Value}" -// | Term -> -// Html.td $"{cellUpdated.Term.Term.Name}" -// Html.td $"{cellUpdated.Term.Term.TermAccession}" -// ] -// ] -// ] -// ] -// ] -// ] -// ] - -//let private footer columnIndex rmv (lastState: EditState) (state: EditState) dispatch = -// Bulma.modalCardFoot [ -// Bulma.button.a [ -// prop.onClick rmv -// Bulma.color.isInfo -// prop.text "Back" -// ] -// Bulma.button.a [ -// prop.disabled <| (state = lastState) -// prop.onClick (fun e -> -// let nt = match state.NextType with | Unit -> SwateCell.emptyUnit | Term -> SwateCell.emptyTerm | Freetext -> SwateCell.emptyFreetext -// Spreadsheet.EditColumn (columnIndex, nt, state.BuildingBlockType) |> SpreadsheetMsg |> dispatch -// rmv e; -// ) -// Bulma.color.isSuccess -// prop.text "Update" -// ] -// ] - -//[] -//let Main (columnIndex: int) (model: Model) (dispatch) (rmv: _ -> unit) = -// let column : (int*SwateCell) [] = model.SpreadsheetModel.getColumn(columnIndex) -// let header = column |> Array.sortBy fst |> Array.head |> snd |> fun header -> header.Header -// let last = EditState.init(header) -// let state, setState = React.useState(EditState.init(header)) -// Bulma.modal [ -// Bulma.modal.isActive -// prop.children [ -// Bulma.modalBackground [ prop.onClick rmv ] -// Bulma.modalCard [ -// prop.style [style.maxHeight(length.percent 70)] -// prop.children [ -// Bulma.modalCardHead [ -// Bulma.modalCardTitle "Update Column" -// Bulma.delete [ prop.onClick rmv ] -// ] -// Bulma.modalCardBody [ -// updateField state setState -// if state.NextType = Unit || state.NextType = Term then -// buildingBlockField state setState -// previewField column state -// ] -// footer columnIndex rmv last state dispatch -// ] -// ] -// ] -// ] \ No newline at end of file +open Feliz +open Feliz.Bulma +open ExcelColors +open Messages +open Shared +open TermTypes +open Spreadsheet +open Model + +open ARCtrl + +type private State = + { + NextHeaderType: BuildingBlock.HeaderCellType option + NextIOType: IOType option + } with + static member init() = { + NextHeaderType = None + NextIOType = None + } + +module private EditColumnComponents = + + let BackButton cancel = + Bulma.button.button [ + prop.onClick cancel + color.isWarning + prop.text "Back" + ] + + let SubmitButton(submit) = + Bulma.button.button [ + color.isSuccess + prop.text "Submit" + prop.onClick submit + ] + + let SelectHeaderTypeOption(headerType: BuildingBlock.HeaderCellType) = + let txt = headerType.ToString() + Html.option [ + prop.value txt + prop.text txt + ] + + let SelectHeaderType(state, setState) = + Bulma.select [ + prop.onChange (fun (e: string) -> {state with NextHeaderType = Some (BuildingBlock.HeaderCellType.fromString e)} |> setState ) + prop.children [ + // -- term columns -- + SelectHeaderTypeOption BuildingBlock.HeaderCellType.Characteristic + SelectHeaderTypeOption BuildingBlock.HeaderCellType.Component + SelectHeaderTypeOption BuildingBlock.HeaderCellType.Factor + SelectHeaderTypeOption BuildingBlock.HeaderCellType.Parameter + // -- io columns -- + SelectHeaderTypeOption BuildingBlock.HeaderCellType.Input + SelectHeaderTypeOption BuildingBlock.HeaderCellType.Output + // -- single columns -- + SelectHeaderTypeOption BuildingBlock.HeaderCellType.Date + SelectHeaderTypeOption BuildingBlock.HeaderCellType.Performer + SelectHeaderTypeOption BuildingBlock.HeaderCellType.ProtocolDescription + SelectHeaderTypeOption BuildingBlock.HeaderCellType.ProtocolREF + SelectHeaderTypeOption BuildingBlock.HeaderCellType.ProtocolType + SelectHeaderTypeOption BuildingBlock.HeaderCellType.ProtocolUri + SelectHeaderTypeOption BuildingBlock.HeaderCellType.ProtocolVersion + ] + ] + + let SelectIOTypeOption(ioType: IOType) = + let txt = ioType.ToString() + Html.option [ + prop.value txt + prop.text txt + ] + + let SelectIOType(state, setState) = + Bulma.select [ + prop.onChange (fun (e: string) -> {state with NextIOType = Some (IOType.ofString e)} |> setState ) + prop.children [ + SelectIOTypeOption IOType.Source + SelectIOTypeOption IOType.Sample + SelectIOTypeOption IOType.Material + SelectIOTypeOption IOType.Data + ] + ] + + let Preview(column: CompositeColumn) = + let parsedStrList = ARCtrl.Spreadsheet.CompositeColumn.toStringCellColumns column |> List.transpose + let headers, body = + if column.Cells.Length >= 2 then + parsedStrList.[0], parsedStrList.[1..] + else + parsedStrList.[0], [] + Bulma.tableContainer [ + prop.style [style.overflowY.auto; style.flexGrow 1] + prop.children [ + Bulma.table [ + table.isFullWidth + prop.children [ + Html.thead [ + Html.tr [ + for header in headers do + Html.th header + ] + ] + Html.tbody [ + for row in body do + Html.tr [ + for cell in row do + Html.td cell + ] + ] + ] + ] + ] + ] + + + + +open EditColumnComponents + +[] +let Main (columnIndex: int) (model: Model) (dispatch) (rmv: _ -> unit) = + let column0 = model.SpreadsheetModel.ActiveTable.GetColumn columnIndex + let state, setState = React.useState(State.init) + let cellsToTermCells(column:CompositeColumn) = + [|for c in column.Cells do if c.isUnitized || c.isTerm then c else c.ToTermCell()|] + let cellsToFreeText(column) = + [|for c in column.Cells do if c.isFreeText then c else c.ToFreeTextCell()|] + let cellsToDataOrFreeText(column) = + [|for c in column.Cells do if c.isFreeText || c.isData then c else c.ToDataCell()|] + let updateColumn (column: CompositeColumn) = + let header = column0.Header + match state.NextHeaderType, state.NextIOType with + | None, _ -> column + // -- term columns -- + | Some BuildingBlock.HeaderCellType.Characteristic, _ -> + CompositeColumn.create(CompositeHeader.Characteristic (header.ToTerm()), cellsToTermCells(column)) + | Some BuildingBlock.HeaderCellType.Parameter, _ -> + CompositeColumn.create(CompositeHeader.Parameter (header.ToTerm()), cellsToTermCells(column)) + | Some BuildingBlock.HeaderCellType.Component, _ -> + CompositeColumn.create(CompositeHeader.Component (header.ToTerm()), cellsToTermCells(column)) + | Some BuildingBlock.HeaderCellType.Factor, _ -> + CompositeColumn.create(CompositeHeader.Factor (header.ToTerm()), cellsToTermCells(column)) + // -- input columns -- + | Some BuildingBlock.HeaderCellType.Input, Some IOType.Data -> + CompositeColumn.create(CompositeHeader.Input IOType.Data, cellsToDataOrFreeText(column)) + | Some BuildingBlock.HeaderCellType.Input, Some io -> + CompositeColumn.create(CompositeHeader.Input io, cellsToFreeText(column)) + | Some BuildingBlock.HeaderCellType.Input, None -> + CompositeColumn.create(CompositeHeader.Input IOType.Sample, cellsToFreeText(column)) + // -- output columns -- + | Some BuildingBlock.HeaderCellType.Output, Some IOType.Data -> + CompositeColumn.create(CompositeHeader.Output IOType.Data, cellsToDataOrFreeText(column)) + | Some BuildingBlock.HeaderCellType.Output, Some io -> + CompositeColumn.create(CompositeHeader.Output io, cellsToFreeText(column)) + | Some BuildingBlock.HeaderCellType.Output, None -> + CompositeColumn.create(CompositeHeader.Output IOType.Sample, cellsToFreeText(column)) + // -- single columns -- + | Some BuildingBlock.HeaderCellType.ProtocolREF, _ -> + CompositeColumn.create(CompositeHeader.ProtocolREF, cellsToFreeText(column)) + | Some BuildingBlock.HeaderCellType.Date, _ -> + CompositeColumn.create(CompositeHeader.Date, cellsToFreeText(column)) + | Some BuildingBlock.HeaderCellType.Performer, _ -> + CompositeColumn.create(CompositeHeader.Performer, cellsToFreeText(column)) + | Some BuildingBlock.HeaderCellType.ProtocolDescription, _ -> + CompositeColumn.create(CompositeHeader.ProtocolDescription, cellsToFreeText(column)) + | Some BuildingBlock.HeaderCellType.ProtocolType, _ -> + CompositeColumn.create(CompositeHeader.ProtocolType, cellsToTermCells(column)) + | Some BuildingBlock.HeaderCellType.ProtocolUri, _ -> + CompositeColumn.create(CompositeHeader.ProtocolUri, cellsToFreeText(column)) + | Some BuildingBlock.HeaderCellType.ProtocolVersion, _ -> + CompositeColumn.create(CompositeHeader.ProtocolVersion, cellsToFreeText(column)) + let submit (e) = + let nxtCol = updateColumn column0 + Spreadsheet.SetColumn (columnIndex, nxtCol) |> SpreadsheetMsg |> dispatch + rmv(e) + let previewColumn = + let cells = Array.takeSafe 10 column0.Cells + updateColumn {column0 with Cells = cells} + + Bulma.modal [ + Bulma.modal.isActive + prop.children [ + Bulma.modalBackground [ prop.onClick rmv ] + Bulma.modalCard [ + prop.style [style.maxHeight(length.percent 70)] + prop.children [ + Bulma.modalCardHead [ + Bulma.modalCardTitle "Update Column" + Bulma.delete [ prop.onClick rmv ] + ] + Bulma.modalCardBody [ + Bulma.field.div [ + Bulma.buttons [ + prop.children [ + SelectHeaderType(state, setState) + match state.NextHeaderType with + | Some BuildingBlock.HeaderCellType.Output | Some BuildingBlock.HeaderCellType.Input -> + SelectIOType(state, setState) + | _ -> Html.none + ] + ] + ] + Bulma.field.div [ + prop.style [style.maxHeight (length.perc 85); style.overflow.hidden; style.display.flex] + prop.children [ + Preview(previewColumn) + ] + ] + ] + Bulma.modalCardFoot [ + prop.className "flex grow justify-between" + prop.children [ + BackButton rmv + SubmitButton submit + ] + ] + ] + ] + ] + ] \ No newline at end of file diff --git a/src/Client/Modals/TermModal.fs b/src/Client/Modals/TermModal.fs index a81a2da3..a8a4af72 100644 --- a/src/Client/Modals/TermModal.fs +++ b/src/Client/Modals/TermModal.fs @@ -3,33 +3,27 @@ module Modals.TermModal open Shared.TermTypes open Feliz open Feliz.Bulma +open ARCtrl -let l = 200 - -let private isTooLong (str:string) = str.Length > l - -type private UI = { - DescriptionTooLong: bool - IsExpanded: bool - DescriptionShort: string - DescriptionLong: string -} with - static member init(description: string) = - let isTooLong = isTooLong description - let descriptionShort = - if isTooLong then description.Substring(0,l).Trim() + ".. " else description - { - IsExpanded = false - DescriptionTooLong = isTooLong - DescriptionShort = descriptionShort - DescriptionLong = description - } - +type private State = + | Loading + | Found of Term + | NotFound [] -let Main (term: Term, dispatch) (rmv: _ -> unit) = +let Main (oa: OntologyAnnotation, dispatch) (rmv: _ -> unit) = + + let state, setState = React.useState(State.Loading) - let state, setState = React.useState(UI.init(term.Description)) + React.useEffectOnce (fun _ -> + async { + let! term = Api.ontology.getTermById(oa.TermAccessionShort) + match term with + | Some t -> Found t |> setState + | None -> NotFound |> setState + } + |> Async.StartImmediate + ) Bulma.modal [ Bulma.modal.isActive @@ -39,50 +33,38 @@ let Main (term: Term, dispatch) (rmv: _ -> unit) = prop.style [style.backgroundColor.transparent] ] Bulma.modalCard [ - prop.style [style.maxWidth 300; style.border (1,borderStyle.solid,NFDIColors.black); style.borderRadius 0] + prop.className "shadow" prop.children [ Bulma.modalCardHead [ - prop.className "p-2" - prop.children [ - Bulma.modalCardTitle [ - Bulma.size.isSize6 - prop.text $"{term.Name} ({term.Accession})" - ] - Bulma.delete [ - Bulma.delete.isSmall - prop.onClick rmv - ] + Bulma.modalCardTitle [ + Bulma.title.h4 oa.NameText + Bulma.subtitle.h6 oa.TermAccessionShort + ] + Bulma.delete [ + Bulma.delete.isSmall + prop.onClick rmv ] ] Bulma.modalCardBody [ - prop.className "p-2 has-text-justified" + prop.className "has-text-justified" prop.children [ Bulma.content [ - Html.p [ - Html.span ( if state.IsExpanded then state.DescriptionLong else state.DescriptionShort) - if state.DescriptionTooLong && not state.IsExpanded then - Html.a [ - prop.onClick(fun _ -> setState {state with IsExpanded = true}) - prop.text "(more)" - ] - ] - //if term.IsObsolete then - Html.div [ - prop.style [style.display.flex; style.justifyContent.spaceBetween] - prop.children [ - Html.span [ - Html.a [ - prop.href (Shared.URLs.OntobeeOntologyPrefix + term.FK_Ontology) - prop.text "Source" - ] - ] - if term.IsObsolete then - Html.span [ - Bulma.color.hasTextDanger - prop.text "obsolete" - ] + match state with + | Loading -> Html.p "loading .." + | NotFound -> + Html.p [ + prop.dangerouslySetInnerHTML $"Unable to find term with id {oa.TermAccessionShort} in database." ] - ] + | Found term -> + Html.h6 "Description" + Html.p term.Description + Html.h6 "Source Ontology" + Html.p term.FK_Ontology + if term.IsObsolete then + Html.p [ + color.hasTextDanger + prop.text "Obsolete" + ] ] ] ] diff --git a/src/Client/Modals/UpdateColumn.fs b/src/Client/Modals/UpdateColumn.fs index 709be490..330554e2 100644 --- a/src/Client/Modals/UpdateColumn.fs +++ b/src/Client/Modals/UpdateColumn.fs @@ -85,7 +85,7 @@ module private Components = ] Html.tbody [ let previewCount = 5 - let preview = takeFromArray previewCount cellValues + let preview = Array.takeSafe previewCount cellValues for i in 0 .. (preview.Length-1) do let cell0 = column.Cells.[i].ToString() let cell = preview.[i] diff --git a/src/Client/Model.fs b/src/Client/Model.fs index e1be7df1..17deed75 100644 --- a/src/Client/Model.fs +++ b/src/Client/Model.fs @@ -182,7 +182,8 @@ module BuildingBlock = open ARCtrl - type [] HeaderCellType = + [] + type HeaderCellType = | Component | Characteristic | Factor @@ -222,7 +223,26 @@ module BuildingBlock = | Output -> true | _ -> false - type [] BodyCellType = + static member fromString(str: string) = + match str with + | "Component" -> Component + | "Characteristic" -> Characteristic + | "Factor" -> Factor + | "Parameter" -> Parameter + | "ProtocolType" -> ProtocolType + | "ProtocolDescription" -> ProtocolDescription + | "ProtocolUri" -> ProtocolUri + | "ProtocolVersion" -> ProtocolVersion + | "ProtocolREF" -> ProtocolREF + | "Performer" -> Performer + | "Date" -> Date + | "Input" -> Input + | "Output" -> Output + | anyElse -> failwithf "BuildingBlock.HeaderCellType.fromString: '%s' is not a valid HeaderCellType" anyElse + + + [] + type BodyCellType = | Term | Unitized | Text diff --git a/src/Client/Spreadsheet/Controller/Clipboard.fs b/src/Client/Spreadsheet/Controller/Clipboard.fs index 77a0784f..f13c0b8f 100644 --- a/src/Client/Spreadsheet/Controller/Clipboard.fs +++ b/src/Client/Spreadsheet/Controller/Clipboard.fs @@ -103,7 +103,7 @@ let pasteCellsIntoSelected (state: Spreadsheet.Model) : JS.Promise takeFromArray rowCount + let cellsTrimmed = cells |> Array.takeSafe rowCount let indicesTrimmed = (Set.toArray selectedSingleColumnCells).[0..cellsTrimmed.Length-1] let indexedCellsTrimmed = Array.zip indicesTrimmed cellsTrimmed Generic.setCells indexedCellsTrimmed state diff --git a/src/Client/Views/SidebarView.fs b/src/Client/Views/SidebarView.fs index a106c16e..74b387ff 100644 --- a/src/Client/Views/SidebarView.fs +++ b/src/Client/Views/SidebarView.fs @@ -43,7 +43,6 @@ let private tabRow (model:Model) (tabs: seq) = Bulma.tabs [ Bulma.tabs.isCentered; Bulma.tabs.isFullWidth; Bulma.tabs.isBoxed prop.style [ - //style.custom ("overflow","visible style.paddingTop(length.rem 1); style.borderBottom (2, borderStyle.solid, NFDIColors.Mint.Base) ] tabs @@ -103,24 +102,6 @@ module private ResizeObserver = ) -let private viewContainer (model: Model) (dispatch: Msg -> unit) (state: SidebarStyle) (setState: SidebarStyle -> unit) (children: ReactElement list) = - - Html.div [ - prop.id Sidebar_Id - prop.onLoad(fun e -> - let ele = Browser.Dom.document.getElementById(Sidebar_Id) - ResizeObserver.observer(state, setState).observe(ele) - ) - prop.style [ - style.display.flex - style.flexGrow 1 - style.flexDirection.column - style.position.relative - style.maxWidth (length.perc 100) - style.overflowY.auto - ] - prop.children children - ] type SidebarView = @@ -187,33 +168,40 @@ type SidebarView = [] static member Main (model: Model, dispatch: Msg -> unit) = let state, setState = React.useState(SidebarStyle.init) - viewContainer model dispatch state setState [ - SidebarComponents.Navbar.NavbarComponent model dispatch state.Size - - Bulma.container [ - Bulma.container.isFluid - prop.className "pl-4 pr-4" - prop.children [ - tabs model dispatch state.Size - - //str <| state.Size.ToString() - - //Button.button [ - // Button.OnClick (fun _ -> - // //Spreadsheet.Controller.deleteRow 2 model.SpreadsheetModel - // //() - // //Spreadsheet.DeleteColumn 1 |> SpreadsheetMsg |> dispatch - // () - // ) - //] [ str "Test button" ] - - match model.PersistentStorageState.Host, not model.ExcelState.HasAnnotationTable with - | Some Swatehost.Excel, true -> - SidebarComponents.AnnotationTableMissingWarning.annotationTableMissingWarningComponent model dispatch - | _ -> () - - SidebarView.content model dispatch + Html.div [ + prop.className "grow overflow-auto h-full" + prop.id Sidebar_Id + prop.onLoad(fun e -> + let ele = Browser.Dom.document.getElementById(Sidebar_Id) + ResizeObserver.observer(state, setState).observe(ele) + ) + prop.children [ + + SidebarComponents.Navbar.NavbarComponent model dispatch state.Size + Html.div [ + prop.className "pl-4 pr-4 h-full" + prop.children [ + tabs model dispatch state.Size + + //str <| state.Size.ToString() + + //Button.button [ + // Button.OnClick (fun _ -> + // //Spreadsheet.Controller.deleteRow 2 model.SpreadsheetModel + // //() + // //Spreadsheet.DeleteColumn 1 |> SpreadsheetMsg |> dispatch + // () + // ) + //] [ str "Test button" ] + + match model.PersistentStorageState.Host, not model.ExcelState.HasAnnotationTable with + | Some Swatehost.Excel, true -> + SidebarComponents.AnnotationTableMissingWarning.annotationTableMissingWarningComponent model dispatch + | _ -> () + + SidebarView.content model dispatch + ] ] + SidebarView.footer (model, dispatch) ] - SidebarView.footer (model, dispatch) ] \ No newline at end of file diff --git a/src/Client/Views/SplitWindowView.fs b/src/Client/Views/SplitWindowView.fs index 8540a028..ad6990f5 100644 --- a/src/Client/Views/SplitWindowView.fs +++ b/src/Client/Views/SplitWindowView.fs @@ -106,9 +106,8 @@ let Main (left:seq) (right:seq) (mainModel:Model) (d React.useEffect(model.WriteToLocalStorage, [|box model|]) React.useEffectOnce(fun _ -> Browser.Dom.window.addEventListener("resize", onResize_event model setModel)) Html.div [ - prop.style [ - style.display.flex - ] + prop.id "SplitWindowView" + prop.className "flex overflow-auto grow" prop.children [ MainComponents.MainViewContainer.Main(minWidth, left) if mainModel.SpreadsheetModel.TableViewIsActive() && mainModel.PersistentStorageState.ShowSideBar then diff --git a/src/Client/index.html b/src/Client/index.html index 0a257848..6f234b66 100644 --- a/src/Client/index.html +++ b/src/Client/index.html @@ -61,7 +61,7 @@ -
+