Skip to content

Commit

Permalink
Add FreeText cell editor ✨ #395 #390
Browse files Browse the repository at this point in the history
  • Loading branch information
Freymaurer committed Mar 8, 2024
1 parent ec0245e commit aa9d7b3
Show file tree
Hide file tree
Showing 11 changed files with 331 additions and 20 deletions.
1 change: 1 addition & 0 deletions src/Client/Client.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
<Compile Include="Modals\BuildingBlockDetailsModal.fs" />
<Compile Include="Modals\CytoscapeView.fs" />
<Compile Include="Modals\MoveColumn.fs" />
<Compile Include="Modals\UpdateColumn.fs" />
<Compile Include="Spreadsheet\IO.fs" />
<Compile Include="Spreadsheet\Types.fs" />
<Compile Include="Spreadsheet\Clipboard.Controller.fs" />
Expand Down
5 changes: 5 additions & 0 deletions src/Client/Helper.fs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ type Navigator =
[<Emit("navigator")>]
let navigator : Navigator = jsNative

/// <summary>
/// take "count" many items from array if existing. if not enough items return as many as possible
/// </summary>
/// <param name="count"></param>
/// <param name="array"></param>
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 =
Expand Down
18 changes: 11 additions & 7 deletions src/Client/MainComponents/ContextMenu.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type private ContextFunctions = {
FillColumn : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit
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
RowIndex : int
ColumnIndex : int
Expand All @@ -42,7 +43,7 @@ let private contextmenu (mousex: int, mousey: int) (funcs:ContextFunctions) (con
]
let button (name:string, icon: string, msg, props) = Html.li [
Bulma.button.button [
prop.style [style.borderRadius 0; style.justifyContent.spaceBetween]
prop.style [style.borderRadius 0; style.justifyContent.spaceBetween; style.fontSize (length.rem 0.9)]
prop.onClick msg
prop.className "py-1"
Bulma.button.isFullWidth
Expand All @@ -57,14 +58,16 @@ let private contextmenu (mousex: int, mousey: int) (funcs:ContextFunctions) (con
]
]
let divider = Html.li [
Html.div [ prop.style [style.border(2, borderStyle.solid, NFDIColors.DarkBlue.Base); style.margin(2,0)] ]
Html.div [ prop.style [style.border(1, borderStyle.solid, NFDIColors.DarkBlue.Base); style.margin(2,0); style.width (length.perc 75); style.marginLeft length.auto] ]
]
let buttonList = [
//button ("Edit Column", "fa-solid fa-table-columns", funcs.EditColumn rmv, [])
button ("Fill Column", "fa-solid fa-file-signature", funcs.FillColumn rmv, [])
button ("Fill Column", "fa-solid fa-pen", funcs.FillColumn rmv, [])
if isUnitOrTermCell contextCell then
let text = if contextCell.Value.isTerm then "As Unit Cell" else "As Term Cell"
button (text, "fa-solid fa-arrow-right-arrow-left", funcs.TransformCell rmv, [])
else
button ("Update Column", "fa-solid fa-ellipsis-vertical", funcs.UpdateAllCells rmv, [])
button ("Clear", "fa-solid fa-eraser", funcs.Clear rmv, [])
divider
button ("Copy", "fa-solid fa-copy", funcs.Copy rmv, [])
Expand Down Expand Up @@ -110,26 +113,26 @@ let onContextMenu (index: int*int, model: Model, dispatch) = fun (e: Browser.Typ
let isSelectedCell = model.SpreadsheetModel.SelectedCells.Contains index
//let editColumnEvent _ = Modals.Controller.renderModal("EditColumn_Modal", Modals.EditColumn.Main (fst index) model dispatch)
let triggerMoveColumnModal _ = Modals.Controller.renderModal("MoveColumn_Modal", Modals.MoveColumn.Main(fst index, model, dispatch))
let triggerUpdateColumnModal _ =
let columnIndex = fst index
let column = model.SpreadsheetModel.ActiveTable.GetColumn columnIndex
Modals.Controller.renderModal("UpdateColumn_Modal", Modals.UpdateColumn.Main(fst index, column, dispatch))
let funcs = {
DeleteRow = fun rmv e -> rmv e; deleteRowEvent e
DeleteColumn = fun rmv e -> rmv e; Spreadsheet.DeleteColumn (fst index) |> Messages.SpreadsheetMsg |> dispatch
MoveColumn = fun rmv e -> rmv e; triggerMoveColumnModal e
Copy = fun rmv e ->
rmv e;
if isSelectedCell then
log "Copy Cells"
Spreadsheet.CopySelectedCells |> Messages.SpreadsheetMsg |> dispatch
else
log "Copy Cell"
Spreadsheet.CopyCell index |> Messages.SpreadsheetMsg |> dispatch
Cut = fun rmv e -> rmv e; Spreadsheet.CutCell index |> Messages.SpreadsheetMsg |> dispatch
Paste = fun rmv e ->
rmv e;
if isSelectedCell then
log "Paste Cells"
Spreadsheet.PasteSelectedCells |> Messages.SpreadsheetMsg |> dispatch
else
log "Paste Cell"
Spreadsheet.PasteCell index |> Messages.SpreadsheetMsg |> dispatch
PasteAll = fun rmv e ->
rmv e;
Expand All @@ -140,6 +143,7 @@ let onContextMenu (index: int*int, model: Model, dispatch) = fun (e: Browser.Typ
if cell.IsSome && (cell.Value.isTerm || cell.Value.isUnitized) then
let nextCell = if cell.Value.isTerm then cell.Value.ToUnitizedCell() else cell.Value.ToTermCell()
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
RowIndex = snd index
ColumnIndex = fst index
Expand Down
21 changes: 9 additions & 12 deletions src/Client/MainComponents/KeyboardShortcuts.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ let onKeydownEvent (dispatch: Messages.Msg -> unit) =
fun (e: Browser.Types.Event) ->
let e = e :?> Browser.Types.KeyboardEvent
match e.ctrlKey, e.which with
| false, 27. | false, 13. | false, 9. | false, 16. -> // escape, enter, tab, shift
()
| false, 46. -> // del
Spreadsheet.ClearSelected |> Messages.SpreadsheetMsg |> dispatch
| false, 37. -> // arrow left
Expand All @@ -14,21 +16,16 @@ let onKeydownEvent (dispatch: Messages.Msg -> unit) =
MoveSelectedCell Key.Right |> Messages.SpreadsheetMsg |> dispatch
| false, 40. -> // arrow down
MoveSelectedCell Key.Down |> Messages.SpreadsheetMsg |> dispatch
| false, key when key <> 27. && key <> 13 && key <> 9 -> // tab, escape, enter (not in this order :O)
| false, key ->
SetActiveCellFromSelected |> Messages.SpreadsheetMsg |> dispatch
| false, _ ->
()
// Ctrl + c
| _, _ ->
match (e.ctrlKey || e.metaKey), e.which with
// Ctrl + c
| true, 67. ->
Spreadsheet.CopySelectedCell |> Messages.SpreadsheetMsg |> dispatch
// Ctrl + x
| true, 88. ->
Spreadsheet.CutSelectedCell |> Messages.SpreadsheetMsg |> dispatch
// Ctrl + v
| true, 86. ->
Spreadsheet.PasteSelectedCell |> Messages.SpreadsheetMsg |> dispatch
| true, 67. -> // Ctrl + c
Spreadsheet.CopySelectedCells |> Messages.SpreadsheetMsg |> dispatch
| true, 88. -> // Ctrl + x
Spreadsheet.CutSelectedCells |> Messages.SpreadsheetMsg |> dispatch
| true, 86. -> // Ctrl + v
Spreadsheet.PasteSelectedCells |> Messages.SpreadsheetMsg |> dispatch
| _, _ -> ()

258 changes: 258 additions & 0 deletions src/Client/Modals/UpdateColumn.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
namespace Modals

open Feliz
open Feliz.Bulma
open Model
open Messages
open Shared
open OfficeInteropTypes

open ARCtrl.ISA
open System.Text.RegularExpressions

[<RequireQualifiedAccess>]
type private FunctionPage =
| Create
| Update

module private Components =

open System

let calculateRegex (regex:string) (input: string) =
try
let regex = Regex(regex)
let m = regex.Match(input)
match m.Success with
| true -> m.Index, m.Length
| false -> 0,0
with
| _ -> 0,0


let split (start: int) (length: int) (str: string) =
let s0, s1 =
str |> Seq.toList |> List.splitAt (start)
let s1, s2 =
s1 |> Seq.toList |> List.splitAt (length)
String.Join("", s0), String.Join("", s1), String.Join("", s2)

let Tab(targetPage: FunctionPage, currentPage, setPage) =
Bulma.tab [
if targetPage = currentPage then tab.isActive
prop.onClick (fun _ -> setPage targetPage)
prop.children [
Html.a [
prop.text (targetPage.ToString())
]
]
]

let TabNavigation(currentPage, setPage) =
Bulma.tabs [
prop.style [style.flexGrow 1]
tabs.isCentered
tabs.isFullWidth
prop.children [
Html.ul [
Tab(FunctionPage.Create, currentPage, setPage)
Tab(FunctionPage.Update, currentPage, setPage)
]
]
]

let PreviewRow(index:int,cell0: string, cell: string, markedIndices: int*int) =
Html.tr [
Html.td index
Html.td [
let s0,marked,s2 = split (fst markedIndices) (snd markedIndices) cell0
Html.span s0
Html.span [
prop.className "has-background-info"
prop.text marked
]
Html.span s2
]
Html.td (cell)
]

let PreviewTable(column: CompositeColumn, cellValues: string [], regex) =
Bulma.field.div [
Bulma.label "Preview"
Bulma.tableContainer [
Bulma.table [
Html.thead [
Html.tr [Html.th "";Html.th "Before"; Html.th "After"]
]
Html.tbody [
let previewCount = 5
let preview = takeFromArray previewCount cellValues
for i in 0 .. (preview.Length-1) do
let cell0 = column.Cells.[i].ToString()
let cell = preview.[i]
let regexMarkedIndex = calculateRegex regex cell0
PreviewRow(i,cell0,cell,regexMarkedIndex)
]
]
]
]

type UpdateColumn =

[<ReactComponent>]
static member private CreateForm(cellValues: string [], setPreview) =
let baseStr, setBaseStr = React.useState("")
let suffix, setSuffix = React.useState(false)
let updateCells (baseStr: string) (suffix:bool) =
cellValues
|> Array.mapi (fun i c ->
match suffix with
| true -> baseStr + string i
| false -> baseStr
)
|> setPreview
Bulma.field.div [
Bulma.field.div [
Bulma.label "Base"
Bulma.input.text [
prop.autoFocus true
prop.valueOrDefault baseStr
prop.onChange(fun s ->
setBaseStr s
updateCells s suffix
)
]
]
Bulma.field.div [
Bulma.control.div [
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
)
]
Bulma.help "Add number suffix"
]
]
]
]
]

[<ReactComponent>]
static member private UpdateForm(cellValues: string [], setPreview, regex: string, setRegex: string -> unit) =
let replacement, setReplacement = React.useState("")
let updateCells (replacement: string) (regex: string) =
if regex <> "" then
try
let regex = Regex(regex)
cellValues
|> Array.mapi (fun i c ->
let m = regex.Match(c)
match m.Success with
| true ->
let replaced = c.Replace(m.Value, replacement)
replaced
| false ->
c
)
|> setPreview
with
| _ -> ()
else
()
Bulma.field.div [
Bulma.field.div [
Html.div [
prop.className "is-flex is-flex-direction-row"
prop.style [style.gap (length.rem 1)]
prop.children [
Bulma.control.div [
prop.style [style.flexGrow 1]
prop.children [
Bulma.label "Regex"
Bulma.input.text [
prop.autoFocus true
prop.valueOrDefault regex
prop.onChange (fun s ->
setRegex s;
updateCells replacement s
)
]
]
]
Bulma.control.div [
prop.style [style.flexGrow 1]
prop.children [
Bulma.label "Replacement"
Bulma.input.text [
prop.valueOrDefault replacement
prop.onChange (fun s ->
setReplacement s;
updateCells s regex
)
]
]
]
]
]
]
]

[<ReactComponent>]
static member Main(index: int, column: CompositeColumn, dispatch) (rmv: _ -> unit) =
let getCellStrings() = column.Cells |> Array.map (fun c -> c.ToString())
let preview, setPreview = React.useState(getCellStrings)
let initPage = if preview.Length = 0 || preview |> String.concat "" = "" then FunctionPage.Create else FunctionPage.Update
let currentPage, setPage = React.useState(initPage)
/// This state is only used for update logic
let regex, setRegex = React.useState("")
let setPage = fun p ->
if p <> FunctionPage.Update then
setRegex ""
setPage p
let submit = fun () ->
preview
|> Array.map (fun x -> CompositeCell.FreeText x)
|> fun x -> CompositeColumn.create(column.Header, x)
|> fun x -> Spreadsheet.SetColumn (index, x)
|> SpreadsheetMsg
|> dispatch
Bulma.modal [
Bulma.modal.isActive
prop.children [
Bulma.modalBackground [ prop.onClick rmv ]
Bulma.modalCard [
prop.style [style.maxHeight(length.percent 70); style.overflowY.hidden]
prop.children [
Bulma.modalCardHead [
Bulma.modalCardTitle "Update Column"
Bulma.delete [ prop.onClick rmv ]
]
Bulma.modalCardBody [
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)
]
Bulma.modalCardFoot [
Bulma.button.button [
color.isInfo
prop.style [style.marginLeft length.auto]
prop.text "Submit"
prop.onClick(fun e ->
submit()
rmv e
)
]
]
]
]
]
]
Loading

0 comments on commit aa9d7b3

Please sign in to comment.