2019-02-18 01:25:07 +00:00
|
|
|
[<AutoOpen>]
|
|
|
|
module PrayerTracker.Views.CommonFunctions
|
|
|
|
|
2022-07-13 02:43:01 +00:00
|
|
|
open System.IO
|
|
|
|
open System.Text.Encodings.Web
|
2019-02-18 01:25:07 +00:00
|
|
|
open Giraffe
|
2021-09-19 02:42:40 +00:00
|
|
|
open Giraffe.ViewEngine
|
2019-02-18 01:25:07 +00:00
|
|
|
open Microsoft.AspNetCore.Antiforgery
|
|
|
|
open Microsoft.AspNetCore.Http
|
|
|
|
open Microsoft.AspNetCore.Mvc.Localization
|
|
|
|
open Microsoft.Extensions.Localization
|
|
|
|
|
|
|
|
/// Encoded text for a localized string
|
2019-03-15 04:30:28 +00:00
|
|
|
let locStr (text : LocalizedString) = str text.Value
|
2019-02-18 01:25:07 +00:00
|
|
|
|
|
|
|
/// Raw text for a localized HTML string
|
|
|
|
let rawLocText (writer : StringWriter) (text : LocalizedHtmlString) =
|
2022-07-13 02:43:01 +00:00
|
|
|
text.WriteTo (writer, HtmlEncoder.Default)
|
|
|
|
let txt = string writer
|
|
|
|
writer.GetStringBuilder().Clear () |> ignore
|
|
|
|
rawText txt
|
2019-02-18 01:25:07 +00:00
|
|
|
|
|
|
|
/// A space (used for back-to-back localization string breaks)
|
|
|
|
let space = rawText " "
|
|
|
|
|
|
|
|
/// Generate a Material Design icon
|
|
|
|
let icon name = i [ _class "material-icons" ] [ rawText name ]
|
|
|
|
|
|
|
|
/// Generate a Material Design icon, specifying the point size (must be defined in CSS)
|
2022-07-31 13:37:06 +00:00
|
|
|
let iconSized size name = i [ _class $"material-icons md-%i{size}" ] [ rawText name ]
|
2019-02-18 01:25:07 +00:00
|
|
|
|
|
|
|
/// Generate a CSRF prevention token
|
|
|
|
let csrfToken (ctx : HttpContext) =
|
2022-07-13 02:43:01 +00:00
|
|
|
let antiForgery = ctx.GetService<IAntiforgery> ()
|
|
|
|
let tokenSet = antiForgery.GetAndStoreTokens ctx
|
|
|
|
input [ _type "hidden"; _name tokenSet.FormFieldName; _value tokenSet.RequestToken ]
|
2019-02-18 01:25:07 +00:00
|
|
|
|
|
|
|
/// Create a summary for a table of items
|
|
|
|
let tableSummary itemCount (s : IStringLocalizer) =
|
2022-07-13 02:43:01 +00:00
|
|
|
div [ _class "pt-center-text" ] [
|
|
|
|
small [] [
|
|
|
|
match itemCount with
|
|
|
|
| 0 -> s["No Entries to Display"]
|
|
|
|
| 1 -> s["Displaying {0} Entry", itemCount]
|
|
|
|
| _ -> s["Displaying {0} Entries", itemCount]
|
|
|
|
|> locStr
|
|
|
|
]
|
2019-02-18 01:25:07 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
/// Generate a list of named HTML colors
|
|
|
|
let namedColorList name selected attrs (s : IStringLocalizer) =
|
2022-07-13 02:43:01 +00:00
|
|
|
// The list of HTML named colors (name, display, text color)
|
|
|
|
seq {
|
|
|
|
("aqua", s["Aqua"], "black")
|
|
|
|
("black", s["Black"], "white")
|
|
|
|
("blue", s["Blue"], "white")
|
|
|
|
("fuchsia", s["Fuchsia"], "black")
|
|
|
|
("gray", s["Gray"], "white")
|
|
|
|
("green", s["Green"], "white")
|
|
|
|
("lime", s["Lime"], "black")
|
|
|
|
("maroon", s["Maroon"], "white")
|
|
|
|
("navy", s["Navy"], "white")
|
|
|
|
("olive", s["Olive"], "white")
|
|
|
|
("purple", s["Purple"], "white")
|
|
|
|
("red", s["Red"], "black")
|
|
|
|
("silver", s["Silver"], "black")
|
|
|
|
("teal", s["Teal"], "white")
|
|
|
|
("white", s["White"], "black")
|
|
|
|
("yellow", s["Yellow"], "black")
|
2019-02-18 01:25:07 +00:00
|
|
|
}
|
2022-07-13 02:43:01 +00:00
|
|
|
|> Seq.map (fun color ->
|
|
|
|
let colorName, text, txtColor = color
|
|
|
|
option
|
|
|
|
[ _value colorName
|
|
|
|
_style $"background-color:{colorName};color:{txtColor};"
|
|
|
|
if colorName = selected then _selected
|
|
|
|
] [ encodedText (text.Value.ToLower ()) ])
|
|
|
|
|> List.ofSeq
|
|
|
|
|> select (_name name :: attrs)
|
2019-02-18 01:25:07 +00:00
|
|
|
|
2022-08-14 23:46:37 +00:00
|
|
|
/// Convert a named color to its hex notation
|
|
|
|
let colorToHex (color : string) =
|
|
|
|
match color with
|
|
|
|
| it when it.StartsWith "#" -> color
|
|
|
|
| "aqua" -> "#00ffff"
|
|
|
|
| "black" -> "#000000"
|
|
|
|
| "blue" -> "#0000ff"
|
|
|
|
| "fuchsia" -> "#ff00ff"
|
|
|
|
| "gray" -> "#808080"
|
|
|
|
| "green" -> "#008000"
|
|
|
|
| "lime" -> "#00ff00"
|
|
|
|
| "maroon" -> "#800000"
|
|
|
|
| "navy" -> "#000080"
|
|
|
|
| "olive" -> "#808000"
|
|
|
|
| "purple" -> "#800080"
|
|
|
|
| "red" -> "#ff0000"
|
|
|
|
| "silver" -> "#c0c0c0"
|
|
|
|
| "teal" -> "#008080"
|
|
|
|
| "white" -> "#ffffff"
|
|
|
|
| "yellow" -> "#ffff00"
|
|
|
|
| it -> it
|
|
|
|
|
2019-02-18 01:25:07 +00:00
|
|
|
/// Generate an input[type=radio] that is selected if its value is the current value
|
|
|
|
let radio name domId value current =
|
2022-07-31 13:37:06 +00:00
|
|
|
input [ _type "radio"
|
|
|
|
_name name
|
2022-08-15 02:27:33 +00:00
|
|
|
if domId <> "" then _id domId
|
2022-07-31 13:37:06 +00:00
|
|
|
_value value
|
|
|
|
if value = current then _checked ]
|
2019-02-18 01:25:07 +00:00
|
|
|
|
|
|
|
/// Generate a select list with the current value selected
|
|
|
|
let selectList name selected attrs items =
|
2022-07-13 02:43:01 +00:00
|
|
|
items
|
|
|
|
|> Seq.map (fun (value, text) ->
|
|
|
|
option
|
|
|
|
[ _value value
|
|
|
|
if value = selected then _selected
|
|
|
|
] [ encodedText text ])
|
|
|
|
|> List.ofSeq
|
|
|
|
|> select (List.concat [ [ _name name; _id name ]; attrs ])
|
2019-02-18 01:25:07 +00:00
|
|
|
|
|
|
|
/// Generate the text for a default entry at the top of a select list
|
2022-07-31 13:37:06 +00:00
|
|
|
let selectDefault text = $"— %s{text} —"
|
2019-02-18 01:25:07 +00:00
|
|
|
|
|
|
|
/// Generate a standard submit button with icon and text
|
2019-03-15 04:30:28 +00:00
|
|
|
let submit attrs ico text = button (_type "submit" :: attrs) [ icon ico; rawText " "; locStr text ]
|
2019-02-18 01:25:07 +00:00
|
|
|
|
2022-07-31 21:56:32 +00:00
|
|
|
/// Create an HTML onsubmit event handler
|
|
|
|
let _onsubmit = attr "onsubmit"
|
|
|
|
|
|
|
|
/// A "rel='noopener'" attribute
|
|
|
|
let _relNoOpener = _rel "noopener"
|
|
|
|
|
2022-08-01 01:25:44 +00:00
|
|
|
/// A class attribute that designates a row of fields, with the additional classes passed
|
|
|
|
let _fieldRowWith classes =
|
|
|
|
let extraClasses = if List.isEmpty classes then "" else $""" {classes |> String.concat " "}"""
|
|
|
|
_class $"pt-field-row{extraClasses}"
|
|
|
|
|
|
|
|
/// The class that designates a row of fields
|
|
|
|
let _fieldRow = _fieldRowWith []
|
|
|
|
|
|
|
|
/// A class attribute that designates an input field, with the additional classes passed
|
|
|
|
let _inputFieldWith classes =
|
|
|
|
let extraClasses = if List.isEmpty classes then "" else $""" {classes |> String.concat " "}"""
|
|
|
|
_class $"pt-field{extraClasses}"
|
|
|
|
|
|
|
|
/// The class that designates an input field / label pair
|
|
|
|
let _inputField = _inputFieldWith []
|
|
|
|
|
|
|
|
/// The class that designates a checkbox / label pair
|
|
|
|
let _checkboxField = _class "pt-checkbox-field"
|
|
|
|
|
2022-08-01 20:22:37 +00:00
|
|
|
/// Create an input field of the given type, with matching name and ID and the given value
|
|
|
|
let inputField typ name value attrs =
|
|
|
|
List.concat [ [ _type typ; _name name; _id name; if value <> "" then _value value ]; attrs ] |> input
|
|
|
|
|
|
|
|
/// Generate a table heading with the given localized column names
|
|
|
|
let tableHeadings (s : IStringLocalizer) (headings : string list) =
|
|
|
|
headings
|
|
|
|
|> List.map (fun heading -> th [ _scope "col" ] [ locStr s[heading] ])
|
|
|
|
|> tr []
|
|
|
|
|> List.singleton
|
|
|
|
|> thead []
|
|
|
|
|
|
|
|
/// For a list of strings, prepend a pound sign and string them together with commas (CSS selector by ID)
|
|
|
|
let toHtmlIds it = it |> List.map (fun x -> $"#%s{x}") |> String.concat ", "
|
|
|
|
|
2021-09-19 02:42:40 +00:00
|
|
|
/// The name this function used to have when the view engine was part of Giraffe
|
|
|
|
let renderHtmlNode = RenderView.AsString.htmlNode
|
|
|
|
|
2022-07-13 02:43:01 +00:00
|
|
|
|
|
|
|
open Microsoft.AspNetCore.Html
|
|
|
|
|
2021-09-19 02:42:40 +00:00
|
|
|
/// Render an HTML node, then return the value as an HTML string
|
|
|
|
let renderHtmlString = renderHtmlNode >> HtmlString
|
|
|
|
|
|
|
|
|
2019-02-18 01:25:07 +00:00
|
|
|
/// Utility methods to help with time zones (and localization of their names)
|
|
|
|
module TimeZones =
|
|
|
|
|
2022-08-02 01:57:55 +00:00
|
|
|
open PrayerTracker.Entities
|
2022-07-13 02:43:01 +00:00
|
|
|
|
|
|
|
/// Cross-reference between time zone Ids and their English names
|
2022-08-14 02:35:48 +00:00
|
|
|
let private xref = [
|
|
|
|
TimeZoneId "America/Chicago", "Central"
|
|
|
|
TimeZoneId "America/Denver", "Mountain"
|
|
|
|
TimeZoneId "America/Los_Angeles", "Pacific"
|
|
|
|
TimeZoneId "America/New_York", "Eastern"
|
|
|
|
TimeZoneId "America/Phoenix", "Mountain (Arizona)"
|
|
|
|
TimeZoneId "Europe/Berlin", "Central European"
|
|
|
|
]
|
2022-07-13 02:43:01 +00:00
|
|
|
|
|
|
|
/// Get the name of a time zone, given its Id
|
2022-08-02 01:57:55 +00:00
|
|
|
let name timeZoneId (s : IStringLocalizer) =
|
2022-08-14 02:35:48 +00:00
|
|
|
match xref |> List.tryFind (fun it -> fst it = timeZoneId) with
|
|
|
|
| Some tz -> s[snd tz]
|
|
|
|
| None ->
|
|
|
|
let tzId = TimeZoneId.toString timeZoneId
|
|
|
|
LocalizedString (tzId, tzId)
|
|
|
|
|
|
|
|
/// All known time zones in their defined order
|
|
|
|
let all = xref |> List.map fst
|
2022-07-31 21:56:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
open Giraffe.ViewEngine.Htmx
|
|
|
|
|
|
|
|
/// Known htmx targets
|
|
|
|
module Target =
|
|
|
|
|
|
|
|
/// htmx links target the body element
|
|
|
|
let body = _hxTarget "body"
|
|
|
|
|
|
|
|
/// htmx links target the #pt-body element
|
|
|
|
let content = _hxTarget "#pt-body"
|