some module organization

This commit is contained in:
caleb 2025-07-27 22:58:04 -07:00
parent d50eb26b14
commit 7de147dabb
10 changed files with 281 additions and 22 deletions

View File

@ -1,23 +1,59 @@
namespace web_api_cookbook namespace web_api_cookbook
open web_api_cookbook
open web_api_cookbook.Exercises
open WebSharper open WebSharper
open WebSharper.UI open WebSharper.UI
open WebSharper.UI.Client open WebSharper.UI.Client
open WebSharper.UI.Html open WebSharper.UI.Html
open Units.Animation
open Units.Time
[<JavaScript>]
module Exercise =
let private c = attr.``class``
let private showHide () =
let show = Var.Create false
let text =
function
| true -> "(Hide)"
| false -> "(Show)"
|> View.MapCached <| show.View
let toggle =
fun _ _ curr ->
Var.Set show (not curr)
|> on.clickView show.View
show.View, div [c "button button--text"; toggle] [textView text]
let doc title description content =
let expanded, showHide = showHide ()
div [c "exercise"] [
h2 [c "exercise__title"] [showHide; text title]
fun () ->
div [c "exercise__section"] [
div [c "exercise__description"] [text description]
div [c "exercise__content"] [content ()]
]
|> Doc.When expanded
]
[<JavaScript>] [<JavaScript>]
module Client = module Client =
[<SPAEntryPoint>] [<SPAEntryPoint>]
let Main () = let Main () =
let isClicked = Var.Create false
let opacityAnimated = Animate.valueWhen isClicked.View 1.0 0. 120.<frames/s> 1.<s>
let opacityStyle = View.MapCached (sprintf "opacity: %f") opacityAnimated
let onClick = on.click (fun _ _ -> Var.Set isClicked true)
div [] [ div [] [
UI.Components.button [attr.styleDyn opacityStyle; onClick] "Hide Me" h1 [] [text "Using Various Browser Web APIs via WebSharper (in F#)"]
p [] [
text "WebSharper is a web framework that provides a functional reactive programming '-ish' API for use in web development."
text "It supports a variety of applicative and monadic combinators that work natuarally with F#, making it a pleasure to use."]
p [] [text "On this page I've implemented code snippets that use various features using the browser's Web API, using WebSharper. The source code can be viewed "; a [] [text "here."]]
Exercise.doc
"Using requestAnimationFrame"
"Trying to use Request Animation Frame"
RequestAnimationFrame.doc
Exercise.doc
"Syncing LocalStorage Across Tabs"
"Using LocalStorage to keep data synced between tabs."
LocalStorageSync.doc
] ]
|> Doc.RunById "main" |> Doc.RunById "main"

View File

@ -0,0 +1,57 @@
namespace web_api_cookbook.Exercises
open web_api_cookbook
open web_api_cookbook.UI
open web_api_cookbook.UI.Components
open WebSharper
open WebSharper.UI
open WebSharper.UI.Client
open WebSharper.UI.Html
[<JavaScript>]
module LocalStorageSync =
let doc () =
let resetKeyInput, keyInput, keyInputDoc = InputType.text [] "Key: "
let resetValInput, valInput, valInputDoc = InputType.text [] "Value: "
let args = View.Map2 tuple2 keyInput valInput
let clear () =
resetKeyInput ()
resetValInput ()
let onAdd =
fun _ _ (key, value) ->
LocalStorage.setItem key value
clear ()
|> on.clickView args
let addButton = Button.plain [onAdd] "Add Element"
let addedElements =
fun k element ->
let value = View.Map snd element
let onDelete =
fun _ _ ->
LocalStorage.removeItem k
|> on.click
Doc.Concat [
div [] [text <| sprintf "Key: %s" k]
div [] [textView <| View.Map (sprintf "Value: %s") value]
Button.plain [onDelete] "Delete Element"
]
|> Doc.BindSeqCachedViewBy fst <| LocalStorage.view ()
Doc.Concat [
div [] [text "Elements Added to Local Storage:"]
div [attr.``class`` "gridwrap"] [
addedElements
]
div [] [text "Add New Element:"]
div [attr.``class`` "gridwrap"] [
keyInputDoc
valInputDoc
addButton
]
]

View File

@ -0,0 +1,23 @@
namespace web_api_cookbook.Exercises
open WebSharper
open WebSharper.UI
open WebSharper.UI.Client
open WebSharper.UI.Html
open web_api_cookbook.UI
open web_api_cookbook.Units.Animation
open web_api_cookbook.Units.Time
open web_api_cookbook.UI.Components
[<JavaScript>]
module RequestAnimationFrame =
let doc () =
let isClicked = Var.Create false
let opacityAnimated = Animate.valueWhen isClicked.View 1.0 0. 120.<frames/s> 1.<s>
let opacityStyle = View.MapCached (sprintf "opacity: %f") opacityAnimated
let animateOnClick =
fun _ _ ->
Var.Set isClicked true
|> on.click
Button.plain [attr.styleDyn opacityStyle; animateOnClick] "Hide Me"

View File

@ -1,6 +1,8 @@
namespace web_api_cookbook namespace web_api_cookbook
open WebSharper open WebSharper
open WebSharper.UI
open WebSharper.UI.Client
[<JavaScript>] [<JavaScript>]
module Option = module Option =
@ -20,6 +22,15 @@ module Units =
[<Measure>] [<Measure>]
type s type s
[<JavaScript>]
module Doc =
let When show content =
function
| true -> content ()
| false -> Doc.Empty
|> View.MapCached <| show
|> Doc.EmbedView
[<JavaScript>] [<JavaScript>]
module Math = module Math =
let clamp a b c = let clamp a b c =
@ -29,3 +40,8 @@ module Math =
[<Inline>] [<Inline>]
let inline lerp a b t = a + t * (b - a) let inline lerp a b t = a + t * (b - a)
[<AutoOpen>]
[<JavaScript>]
module Tuple2 =
let tuple2 a b = a,b

View File

@ -1,21 +1,15 @@
namespace web_api_cookbook namespace web_api_cookbook.UI
open web_api_cookbook
open web_api_cookbook.Units.Animation
open web_api_cookbook.Units.Time
open WebSharper open WebSharper
open WebSharper.JavaScript
open WebSharper.UI open WebSharper.UI
open WebSharper.UI.Client open WebSharper.JavaScript
open WebSharper.UI.Html
[<JavaScript>]
module UI =
module Components =
let button attrs label =
button (attrs @ [attr.``type`` "button"]) [ text label ]
[<JavaScript>] [<JavaScript>]
module Animate = module Animate =
open Units.Animation
open Units.Time
let value (startPoint:float) endPoint (targetFps:float<frames/s>) (animationSeconds:float<s>) = let value (startPoint:float) endPoint (targetFps:float<frames/s>) (animationSeconds:float<s>) =
let frameInterval = 1. / targetFps * 1000.0<ms/s> let frameInterval = 1. / targetFps * 1000.0<ms/s>
let frameCount = animationSeconds * targetFps let frameCount = animationSeconds * targetFps

23
UI/Components.fs Normal file
View File

@ -0,0 +1,23 @@
namespace web_api_cookbook.UI
open WebSharper
open WebSharper.JavaScript
open WebSharper.UI
open WebSharper.UI.Client
open WebSharper.UI.Html
[<JavaScript>]
module Components =
module Button =
let plain attrs label =
button (attrs @ [attr.``type`` "button"; attr.``class`` "button"]) [ text label ]
module InputType =
let reset var () =
Var.Set var ""
let text attrs label =
let inputVal = Var.Create ""
let input = Doc.InputType.Text attrs inputVal
let doc = span [] [text label; input]
reset inputVal, inputVal.View, doc

54
UI/LocalStorage.fs Normal file
View File

@ -0,0 +1,54 @@
namespace web_api_cookbook.UI
open WebSharper
open WebSharper.UI
open WebSharper.JavaScript
[<JavaScript>]
module LocalStorage =
let allItems : Var<list<string * string>> = Var.Create []
let getItem (key: string) : option<string> =
match JS.Window.LocalStorage.GetItem key with
| null -> None
| value -> Some value
let private putItem (key: string) (value:string) : unit =
Var.Get allItems
|> List.filter (fun (k, _) -> k <> key)
|> fun filtered -> (key, value) :: filtered
|> Var.Set allItems
let private deleteItem (key: string) : unit =
Var.Get allItems
|> List.filter (fun (k, _) -> k <> key)
|> Var.Set allItems
let setItem (key: string) (value: string) : unit =
JS.Window.LocalStorage.SetItem(key, value)
putItem key value
let removeItem (key: string) : unit =
JS.Window.LocalStorage.RemoveItem(key)
deleteItem key
JS.Window.AddEventListener("storage", fun (e:Dom.Event) ->
let key:string option = e?key |> Option.ofObj
let newValue:string option = e?newValue |> Option.ofObj
Console.Info key
Console.Info newValue
match key, newValue with
| Some key, Some newVal -> putItem key newVal
| Some key, None -> removeItem key
| _ -> ())
let view () =
let items =
[
for i in 0 .. JS.Window.LocalStorage.Length - 1 do
let key = JS.Window.LocalStorage.Key i
let value = JS.Window.LocalStorage.GetItem key
yield (key, value)
]
allItems.Set items
allItems.View

View File

@ -10,7 +10,11 @@
<ItemGroup> <ItemGroup>
<Compile Include="Prelude.fs" /> <Compile Include="Prelude.fs" />
<Compile Include="Ui.fs" /> <Compile Include="UI/LocalStorage.fs" />
<Compile Include="UI/Components.fs" />
<Compile Include="UI/Animate.fs" />
<Compile Include="Exercises/RequestAnimationFrame.fs" />
<Compile Include="Exercises/LocalStorageSync.fs" />
<Compile Include="Client.fs" /> <Compile Include="Client.fs" />
<Compile Include="Startup.fs" /> <Compile Include="Startup.fs" />
<None Include="package.json" /> <None Include="package.json" />

View File

@ -4,7 +4,7 @@
<title>web_api_cookbook</title> <title>web_api_cookbook</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="Scripts/web_api_cookbook.css" /> <link rel="stylesheet" type="text/css" href="style.css" />
<script type="text/javascript" src="Scripts/web_api_cookbook.head.js"></script> <script type="text/javascript" src="Scripts/web_api_cookbook.head.js"></script>
</head> </head>
<body> <body>

52
wwwroot/style.css Normal file
View File

@ -0,0 +1,52 @@
h1 {
font-size: 1.2rem; /* or 28px */
margin-bottom: 0.5em;
}
h2 {
font-size: 1.1rem; /* or 22px */
margin-bottom: 0.4em;
}
h1, h2 {
line-height: 1.2;
}
.gridwrap {
display: grid;
grid-template-columns: repeat(3, 15em);
}
.exercise {
margin_bottom: .5em;
}
.exercise__title {
display: flex;
gap: .5em;
align-items: center;
margin-bottom: .5em;
}
.exercise__section {
border-left: 2px solid lightgray;
padding-left: .5em;
}
.exercise__description {
color: #666;
font-style: italic;
}
.exercise__content {
margin-top: .5em;
}
.button {
cursor: pointer;
}
.button--text {
font-style:italic;
color: blue
}