Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Widget bugs fixed 🐛 #645

Merged
merged 12 commits into from
Feb 27, 2025
92 changes: 47 additions & 45 deletions src/Client/MainComponents/Widgets.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ open Browser.Types
open LocalStorage.Widgets
open Swate
open Modals
open Types.JsonImport
open Types.FileImport
open Swate.Components

module private InitExtensions =

Expand Down Expand Up @@ -64,14 +65,23 @@ module private ResizeEventListener =

open Fable.Core.JsInterop

let adaptElement (size: Rect) (position: Rect) setWidth setPosition =
let innerWidth = int Browser.Dom.window.innerWidth
let adaptElement (innerWidth: int) (innerHeight: int) (size: Rect) (position: Rect) setWidth setPosition =
let combinedWidth = size.X + position.X
if innerWidth <= combinedWidth then
(Some {X = 0; Y = position.Y}) |> setPosition
let combinedHeight = size.Y + position.Y
if innerWidth <= size.X then
(Some {X = innerWidth; Y = size.Y}) |> setWidth

let newXPosition =
if innerWidth <= combinedWidth then
System.Math.Max(0,innerWidth - size.X)
else
position.X
let newYPosition =
if innerHeight <= combinedHeight then
System.Math.Max(0,innerHeight - size.Y)
else
position.Y
setPosition (Some {X = newXPosition; Y = newYPosition})


let onmousemove (startPosition: Rect) (startSize: Rect) setSize = fun (e: Event) ->
let e : MouseEvent = !!e
Expand All @@ -87,11 +97,11 @@ module private ResizeEventListener =
if element.current.IsSome then
Size.write(prefix, {X = int element.current.Value.offsetWidth; Y = int element.current.Value.offsetHeight})

let windowSizeChange setInnerWidth =
let windowSizeChange setInnerWidth setInnerHeight =
React.useEffect(fun () ->
let onResize _ =
setInnerWidth Browser.Dom.window.innerWidth
setInnerWidth Browser.Dom.window.innerWidth
setInnerHeight Browser.Dom.window.innerHeight
Browser.Dom.window.addEventListener("resize", onResize)
// Cleanup function to remove event listener when the component unmounts
React.createDisposable(fun () ->
Expand All @@ -107,31 +117,33 @@ type Widget =
| _DataAnnotator

[<ReactComponent>]
static member Base(content: ReactElement, prefix: string, rmv: MouseEvent -> unit) =
static member Base(content: ReactElement, prefix: string, rmv: MouseEvent -> unit, key: string) =
let position, setPosition = React.useState(fun _ -> Rect.initPositionFromPrefix prefix)
let size, setSize = React.useState(fun _ -> Rect.initSizeFromPrefix prefix)
let innerWidth, setInnerWidth = React.useState(fun _ -> Browser.Dom.window.innerWidth)
let innerHeight, setInnerHeight = React.useState(fun _ -> Browser.Dom.window.innerHeight)
let element = React.useElementRef()

ResizeEventListener.windowSizeChange setInnerWidth
ResizeEventListener.windowSizeChange setInnerWidth setInnerHeight

let debouncedAdaptElement =
React.useDebouncedCallback(fun () ->
match position, size with
| Some position, Some size ->
ResizeEventListener.adaptElement (int innerWidth) (int innerHeight) size position setSize setPosition
| _, _ -> ()
, 100)

React.useEffectOnce(fun _ ->
position |> Option.iter (fun position ->
if size.IsSome then
ResizeEventListener.adaptElement size.Value position setSize setPosition
)
debouncedAdaptElement()
)

React.useEffect(
(fun () ->
//Adapt position when the size of the element is changed so that it is visible
position |> Option.iter (fun position ->
if size.IsSome then
ResizeEventListener.adaptElement size.Value position setSize setPosition
)
()
debouncedAdaptElement()
//React shall only be used, when the size of the element is changed
), [| Browser.Dom.window.innerWidth :> obj |]
), [| box innerWidth; box innerHeight |]
)

React.useLayoutEffectOnce(fun _ -> position |> Option.iter (fun position -> MoveEventListener.ensurePositionInsideWindow element position |> Some |> setPosition)) // Reposition widget inside window
Expand Down Expand Up @@ -172,7 +184,7 @@ type Widget =
]
resizeElement <| Html.div [
prop.onMouseDown(fun e -> e.stopPropagation())
prop.className "cursor-default flex flex-col grow max-h-[60%] overflow-y-auto"
prop.className "cursor-default flex flex-col grow max-h-[60%] overflow-visible"
prop.children [
Html.div [
prop.onMouseDown(fun e -> // move
Expand All @@ -194,7 +206,7 @@ type Widget =
]
]
Html.div [
prop.className "p-2 max-h-[80vh] overflow-y-auto"
prop.className "p-2 max-h-[80vh] overflow-visible flex flex-col"
prop.children [
content
]
Expand All @@ -205,33 +217,20 @@ type Widget =
static member BuildingBlock (model, dispatch, rmv: MouseEvent -> unit) =
let content = BuildingBlock.SearchComponent.Main model dispatch
let prefix = WidgetLiterals.BuildingBlock
Widget.Base(content, prefix, rmv)
Widget.Base(content, prefix, rmv, prefix)

[<ReactComponent>]
static member Templates (model: Model, importTypeStateData, dispatch, rmv: MouseEvent -> unit) =
let isProtocolSearch, setProtocolSearch = React.useState(true)
static member Templates (model: Model, dispatch, rmv: MouseEvent -> unit) =
React.useEffectOnce(fun _ -> Messages.Protocol.GetAllProtocolsRequest |> Messages.ProtocolMsg |> dispatch)
let selectContent() =
Protocol.SearchContainer.Main(model, setProtocolSearch, importTypeStateData, dispatch)
let insertContent() =
Html.div [
prop.style [style.maxHeight (length.px 350); style.overflow.auto]
prop.className "flex flex-col gap-2"
prop.children (SelectiveTemplateFromDB.Main(model, true, setProtocolSearch, importTypeStateData, dispatch))
]

let content =
Html.div [
prop.children [
if isProtocolSearch then
selectContent ()
else
insertContent ()
]
]
if model.ProtocolState.ShowSearch then
Protocol.SearchContainer.Main(model, dispatch)
else
SelectiveTemplateFromDB.Main(model, dispatch)

let prefix = WidgetLiterals.Templates
Widget.Base(content, prefix, rmv)
Widget.Base(content, prefix, rmv, prefix)

static member FilePicker (model, dispatch, rmv) =
let content = Html.div [
Expand All @@ -241,11 +240,14 @@ type Widget =
]
]
let prefix = WidgetLiterals.FilePicker
Widget.Base(content, prefix, rmv)
Widget.Base(content, prefix, rmv, prefix)

static member DataAnnotator (model, dispatch, rmv) =
let content = Html.div [
Pages.DataAnnotator.Main(model, dispatch)
prop.className "min-w-80"
prop.children [
Pages.DataAnnotator.Main(model, dispatch)
]
]
let prefix = WidgetLiterals.DataAnnotator
Widget.Base(content, prefix, rmv)
Widget.Base(content, prefix, rmv, prefix)
11 changes: 7 additions & 4 deletions src/Client/Messages.fs
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,19 @@ module BuildingBlock =
module Protocol =

type Msg =
// Client
| UpdateTemplates of Template []
// UI
| UpdateShowSearch of bool
| UpdateImportConfig of Types.FileImport.SelectiveImportConfig
| UpdateLoading of bool
//
| UpdateTemplates of Template []
| SelectProtocols of Template list
| AddProtocol of Template
| RemoveSelectedProtocols
// // ------ Protocol from Database ------
| GetAllProtocolsForceRequest
| GetAllProtocolsRequest
| GetAllProtocolsResponse of string
| SelectProtocols of Template list
| AddProtocol of Template
| ProtocolIncreaseTimesUsed of protocolName:string

type SettingsDataStewardMsg =
Expand Down
7 changes: 4 additions & 3 deletions src/Client/Modals/ModalElements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@ open Messages
open Swate.Components.Shared

open ARCtrl
open JsonImport
open FileImport
open Fable.React.Helpers

type ModalElements =

static member Button(text: string, onClickAction, buttonInput, ?isDisabled: bool) =
static member Button(text: string, onClickAction, buttonInput, ?isDisabled: bool, ?className: string) =
let isDisabled = defaultArg isDisabled false
Daisy.button.a [
button.primary
button.wide
if isDisabled then
button.error
if className.IsSome then
prop.className className.Value
prop.disabled isDisabled
prop.onClick (fun _ -> onClickAction buttonInput)
prop.text text
Expand Down
2 changes: 1 addition & 1 deletion src/Client/Modals/ModalProvider.fs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type ModalProvider =
| TableModals.TermDetails term ->
Modals.TermModal.Main (term, dispatch)
| TableModals.SelectiveFileImport arcfile ->
Modals.SelectiveImportModal.Main (arcfile, dispatch)
Modals.SelectiveImportModal.Main (arcfile, model, dispatch)
| TableModals.BatchUpdateColumnValues (columnIndex, column) ->
Modals.UpdateColumn.Main (columnIndex, column, dispatch)
| TableModals.TableCellContext (mouseX, mouseY, ci, ri) ->
Expand Down
66 changes: 36 additions & 30 deletions src/Client/Modals/SelectiveImportModal.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ open Messages
open Swate.Components.Shared

open ARCtrl
open JsonImport
open FileImport
open Swate.Components

type SelectiveImportModal =
Expand All @@ -27,30 +27,30 @@ type SelectiveImportModal =
]
])

static member CheckBoxForTableColumnSelection(columns: CompositeColumn [], tableIndex, columnIndex, selectionInformation: SelectiveImportModalState, setSelectedColumns: SelectiveImportModalState -> unit) =
static member CheckBoxForTableColumnSelection(columns: CompositeColumn [], tableIndex, columnIndex, isActive: bool, model: Model.Model, dispatch) =
let importConfig = model.ProtocolState.ImportConfig
let isChecked = importConfig.DeselectedColumns |> Set.contains (tableIndex, columnIndex) |> not
Html.div [
prop.style [style.display.flex; style.justifyContent.center]
prop.className "flex justify-center"
prop.children [
Daisy.checkbox [
prop.type'.checkbox
prop.disabled (not isActive)
prop.style [
style.height(length.perc 100)
]
prop.isChecked (not (Set.contains (tableIndex, columnIndex) selectionInformation.DeselectedColumns))
prop.isChecked isChecked
prop.onChange (fun (b: bool) ->
if columns.Length > 0 then
let selectedData = selectionInformation.toggleDeselectedColumns(tableIndex, columnIndex)
{selectionInformation with DeselectedColumns = selectedData} |> setSelectedColumns)
let nextImportConfig = importConfig.toggleDeselectColumn(tableIndex, columnIndex)
nextImportConfig |> Protocol.UpdateImportConfig |> ProtocolMsg |> dispatch
)
]
]
]

static member TableWithImportColumnCheckboxes(table: ArcTable, ?tableIndex, ?selectionInformation: SelectiveImportModalState, ?setDeselectedColumns: SelectiveImportModalState -> unit) =
static member TableWithImportColumnCheckboxes(table: ArcTable, tableIndex, isActive: bool, model: Model.Model, dispatch: Messages.Msg -> unit) =
let columns = table.Columns
let tableIndex = defaultArg tableIndex 0
let displayCheckBox =
//Determine whether to display checkboxes or not
selectionInformation.IsSome && setDeselectedColumns.IsSome
Daisy.table [
prop.children [
Html.thead [
Expand All @@ -60,8 +60,7 @@ type SelectiveImportModal =
Html.label [
prop.className "join flex flex-row centered gap-2"
prop.children [
if displayCheckBox then
SelectiveImportModal.CheckBoxForTableColumnSelection(columns, tableIndex, columnIndex, selectionInformation.Value, setDeselectedColumns.Value)
SelectiveImportModal.CheckBoxForTableColumnSelection(columns, tableIndex, columnIndex, isActive, model, dispatch)
Html.text (columns.[columnIndex].Header.ToString())
]
]
Expand Down Expand Up @@ -109,13 +108,20 @@ type SelectiveImportModal =
)

[<ReactComponent>]
static member TableImport(tableIndex: int, table0: ArcTable, state: SelectiveImportModalState, addTableImport: int -> bool -> unit, rmvTableImport: int -> unit, deselectedColumns, setSelectedColumns, ?templateName) =
static member TableImport(tableIndex: int, table0: ArcTable, model: Model.Model, dispatch, ?templateName) =
let importConfig = model.ProtocolState.ImportConfig
let name = defaultArg templateName table0.Name
let guid = React.useMemo(fun () -> System.Guid.NewGuid().ToString())
let radioGroup = "radioGroup_" + guid
let import = state.ImportTables |> List.tryFind (fun it -> it.Index = tableIndex)
let radioGroup = "RADIO_GROUP" + table0.Name + string tableIndex
let import = importConfig.ImportTables |> List.tryFind (fun it -> it.Index = tableIndex)
let isActive = import.IsSome
let isDisabled = state.ImportMetadata
let isDisabled = importConfig.ImportMetadata
let addTableImport = fun (i: int) (fullImport: bool) ->
let newImportTable: ImportTable = {Index = i; FullImport = fullImport}
let newImportTables = newImportTable::importConfig.ImportTables |> List.distinct
{importConfig with ImportTables = newImportTables} |> Protocol.UpdateImportConfig |> ProtocolMsg |> dispatch
let rmvTableImport = fun i ->
let tableRemoved = importConfig.ImportTables |> List.filter (fun it -> it.Index <> i)
{importConfig with ImportTables = tableRemoved} |> Protocol.UpdateImportConfig |> ProtocolMsg |> dispatch
ModalElements.Box (name, "fa-solid fa-table", React.fragment [
Html.div [
ModalElements.RadioPlugin (radioGroup, "Import",
Expand All @@ -136,9 +142,12 @@ 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-success text-sm font-bold space-x-2"
prop.className [
"p-1 min-h-0 h-5 text-sm font-bold space-x-2"
if isActive then "text-primary-content" else "text-success"
]
prop.children [
Html.span (if isActive then "Select Columns" else "Preview Table")
Html.i [prop.className "fa-solid fa-magnifying-glass"]
Expand All @@ -147,25 +156,22 @@ type SelectiveImportModal =
Daisy.collapseContent [
prop.className "overflow-x-auto"
prop.children [
if isActive then
SelectiveImportModal.TableWithImportColumnCheckboxes(table0, tableIndex, deselectedColumns, setSelectedColumns)
else
SelectiveImportModal.TableWithImportColumnCheckboxes(table0)
SelectiveImportModal.TableWithImportColumnCheckboxes(table0, tableIndex, isActive, model, dispatch)
]
]
]
],
className = [if isActive then "!bg-primary !text-primary-content"])

[<ReactComponent>]
static member Main (import: ArcFiles, dispatch, rmv) =
static member Main (import: ArcFiles, model, dispatch, rmv) =
let tables, disArcfile =
match import with
| Assay a -> a.Tables, ArcFilesDiscriminate.Assay
| Study (s,_) -> s.Tables, ArcFilesDiscriminate.Study
| Template t -> ResizeArray([t.Table]), ArcFilesDiscriminate.Template
| Investigation _ -> ResizeArray(), ArcFilesDiscriminate.Investigation
let importDataState, setImportDataState = React.useState(SelectiveImportModalState.init())
let importDataState, setImportDataState = React.useState(SelectiveImportConfig.init())
let setMetadataImport = fun b ->
if b then
{
Expand All @@ -174,7 +180,7 @@ type SelectiveImportModal =
ImportTables = [for ti in 0 .. tables.Count-1 do {ImportTable.Index = ti; ImportTable.FullImport = true}]
} |> setImportDataState
else
SelectiveImportModalState.init() |> setImportDataState
SelectiveImportConfig.init() |> setImportDataState
let addTableImport = fun (i: int) (fullImport: bool) ->
let newImportTable: ImportTable = {Index = i; FullImport = fullImport}
let newImportTables = newImportTable::importDataState.ImportTables |> List.distinct
Expand Down Expand Up @@ -209,7 +215,7 @@ type SelectiveImportModal =
SelectiveImportModal.MetadataImport(importDataState.ImportMetadata, setMetadataImport, disArcfile)
for ti in 0 .. (tables.Count-1) do
let t = tables.[ti]
SelectiveImportModal.TableImport(ti, t, importDataState, addTableImport, rmvTableImport, importDataState, setImportDataState)
SelectiveImportModal.TableImport(ti, t, model, dispatch)
Daisy.cardActions [
Daisy.button.button [
button.info
Expand All @@ -226,6 +232,6 @@ type SelectiveImportModal =
]
]

static member Main(import: ArcFiles, dispatch: Messages.Msg -> unit) =
static member Main(import: ArcFiles, model, dispatch: Messages.Msg -> unit) =
let rmv = Util.RMV_MODAL dispatch
SelectiveImportModal.Main (import, dispatch, rmv = rmv)
SelectiveImportModal.Main (import, model, dispatch, rmv = rmv)
Loading
Loading