some module organization
This commit is contained in:
parent
d50eb26b14
commit
c03182e08c
23
Client.fs
23
Client.fs
@ -1,23 +0,0 @@
|
||||
namespace web_api_cookbook
|
||||
|
||||
open WebSharper
|
||||
open WebSharper.UI
|
||||
open WebSharper.UI.Client
|
||||
open WebSharper.UI.Html
|
||||
open Units.Animation
|
||||
open Units.Time
|
||||
|
||||
|
||||
[<JavaScript>]
|
||||
module Client =
|
||||
[<SPAEntryPoint>]
|
||||
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 [] [
|
||||
UI.Components.button [attr.styleDyn opacityStyle; onClick] "Hide Me"
|
||||
]
|
||||
|> Doc.RunById "main"
|
||||
58
src/Client.fs
Normal file
58
src/Client.fs
Normal file
@ -0,0 +1,58 @@
|
||||
namespace web_api_cookbook
|
||||
|
||||
open web_api_cookbook
|
||||
open web_api_cookbook.Exercises
|
||||
open WebSharper
|
||||
open WebSharper.UI
|
||||
open WebSharper.UI.Client
|
||||
open WebSharper.UI.Html
|
||||
|
||||
[<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>]
|
||||
module Client =
|
||||
[<SPAEntryPoint>]
|
||||
let Main () =
|
||||
div [] [
|
||||
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. 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 of the browser's Web API, and WebSharper. The source code can be viewed "; a [] [text "here."]]
|
||||
Exercise.doc
|
||||
"Using requestAnimationFrame"
|
||||
"The requestAnimationFrame function is used to provide a callback to the browser, which is invoked before the browser repaints the page. This can be used to efficiently do animations asynchronously. This snippet adjusts the opacity of a button over time after it is clicked."
|
||||
RequestAnimationFrame.doc
|
||||
Exercise.doc
|
||||
"Syncing Data Across Tabs via LocalStorage"
|
||||
"Besides simply storing data, LocalStorage can be used as a simple method for syncing state between different tabs. In addition to storing the data, storage events must also be observed to keep the data synchronized. You can see this in action by opening this page in two tabs and entering/deleting data using the below form."
|
||||
LocalStorageSync.doc
|
||||
]
|
||||
|> Doc.RunById "main"
|
||||
57
src/Exercises/LocalStorageSync.fs
Normal file
57
src/Exercises/LocalStorageSync.fs
Normal 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
|
||||
]
|
||||
]
|
||||
23
src/Exercises/RequestAnimationFrame.fs
Normal file
23
src/Exercises/RequestAnimationFrame.fs
Normal 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"
|
||||
@ -1,6 +1,8 @@
|
||||
namespace web_api_cookbook
|
||||
|
||||
open WebSharper
|
||||
open WebSharper.UI
|
||||
open WebSharper.UI.Client
|
||||
|
||||
[<JavaScript>]
|
||||
module Option =
|
||||
@ -20,6 +22,15 @@ module Units =
|
||||
[<Measure>]
|
||||
type s
|
||||
|
||||
[<JavaScript>]
|
||||
module Doc =
|
||||
let When show content =
|
||||
function
|
||||
| true -> content ()
|
||||
| false -> Doc.Empty
|
||||
|> View.MapCached <| show
|
||||
|> Doc.EmbedView
|
||||
|
||||
[<JavaScript>]
|
||||
module Math =
|
||||
let clamp a b c =
|
||||
@ -29,3 +40,8 @@ module Math =
|
||||
|
||||
[<Inline>]
|
||||
let inline lerp a b t = a + t * (b - a)
|
||||
|
||||
[<AutoOpen>]
|
||||
[<JavaScript>]
|
||||
module Tuple2 =
|
||||
let tuple2 a b = a,b
|
||||
@ -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.JavaScript
|
||||
open WebSharper.UI
|
||||
open WebSharper.UI.Client
|
||||
open WebSharper.UI.Html
|
||||
|
||||
[<JavaScript>]
|
||||
module UI =
|
||||
module Components =
|
||||
let button attrs label =
|
||||
button (attrs @ [attr.``type`` "button"]) [ text label ]
|
||||
open WebSharper.JavaScript
|
||||
|
||||
[<JavaScript>]
|
||||
module Animate =
|
||||
open Units.Animation
|
||||
open Units.Time
|
||||
|
||||
let value (startPoint:float) endPoint (targetFps:float<frames/s>) (animationSeconds:float<s>) =
|
||||
let frameInterval = 1. / targetFps * 1000.0<ms/s>
|
||||
let frameCount = animationSeconds * targetFps
|
||||
23
src/UI/Components.fs
Normal file
23
src/UI/Components.fs
Normal 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
src/UI/LocalStorage.fs
Normal file
54
src/UI/LocalStorage.fs
Normal 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
|
||||
@ -9,10 +9,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Prelude.fs" />
|
||||
<Compile Include="Ui.fs" />
|
||||
<Compile Include="Client.fs" />
|
||||
<Compile Include="Startup.fs" />
|
||||
<Compile Include="src/Prelude.fs" />
|
||||
<Compile Include="src/UI/LocalStorage.fs" />
|
||||
<Compile Include="src/UI/Components.fs" />
|
||||
<Compile Include="src/UI/Animate.fs" />
|
||||
<Compile Include="src/Exercises/RequestAnimationFrame.fs" />
|
||||
<Compile Include="src/Exercises/LocalStorageSync.fs" />
|
||||
<Compile Include="src/Client.fs" />
|
||||
<Compile Include="src/Startup.fs" />
|
||||
<None Include="package.json" />
|
||||
<None Include="esbuild.config.mjs" />
|
||||
<None Include="vite.config.js" />
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<title>web_api_cookbook</title>
|
||||
<meta charset="utf-8" />
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
52
wwwroot/style.css
Normal file
52
wwwroot/style.css
Normal 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
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user