├── .github └── workflows │ └── Build and publish nuget package.yml ├── .gitignore ├── README.md ├── samples ├── FunBlazor.MudBlazorDemo │ ├── FunBlazor.MudBlazorDemo.Client │ │ ├── FunBlazor.MudBlazorDemo.Client.fsproj │ │ ├── Pages │ │ │ └── Counter.fs │ │ └── Startup.fs │ ├── FunBlazor.MudBlazorDemo.sln │ └── FunBlazor.MudBlazorDemo │ │ ├── Components │ │ ├── App.fs │ │ ├── Layout │ │ │ ├── MainLayout.fs │ │ │ └── NavMenu.fs │ │ ├── Pages │ │ │ ├── From.fs │ │ │ └── Home.fs │ │ └── Routes.fs │ │ ├── FunBlazor.MudBlazorDemo.fsproj │ │ ├── Properties │ │ └── launchSettings.json │ │ ├── Startup.fs │ │ └── wwwroot │ │ └── favicon.png ├── FunWasm.AntDesignDemo │ ├── App.fs │ ├── FunWasm.AntDesignDemo.fsproj │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Startup.fs │ └── wwwroot │ │ ├── app.css │ │ ├── favicon.ico │ │ └── index.html ├── FunWasm.FluentUIDemo │ ├── App.fs │ ├── FunWasm.FluentUIDemo.fsproj │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Startup.fs │ └── wwwroot │ │ ├── app.css │ │ ├── favicon.ico │ │ └── index.html ├── FunWasm.MudBlazorDemo │ ├── App.fs │ ├── FunWasm.MudBlazorDemo.fsproj │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.fs │ └── wwwroot │ │ ├── app.css │ │ ├── favicon.ico │ │ └── index.html ├── FunWasm.RadzenDemo │ ├── App.fs │ ├── FunWasm.RadzenDemo.fsproj │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Startup.fs │ └── wwwroot │ │ ├── app.css │ │ ├── favicon.ico │ │ └── index.html └── MudBlazorThreeAuth │ ├── MudBlazorThreeAuth.Client │ ├── Fun.Blazor.Bindings │ │ └── HomagGroup.Blazor3D.fs │ ├── ILLink.Substitutions.xml │ ├── MudBlazorThreeAuth.Client.fsproj │ ├── Pages │ │ ├── Counter.fs │ │ ├── DataGridDemo.fs │ │ └── ThreejsDemo.fs │ ├── Properties │ │ └── launchSettings.json │ └── Startup.fs │ ├── MudBlazorThreeAuth.sln │ ├── MudBlazorThreeAuth │ ├── Components │ │ ├── App.fs │ │ ├── Layout │ │ │ ├── EmptyLayout.fs │ │ │ ├── MainLayout.fs │ │ │ └── NavMenu.fs │ │ ├── Pages │ │ │ ├── Account │ │ │ │ └── Login.fs │ │ │ ├── From.fs │ │ │ └── Home.fs │ │ └── Routes.fs │ ├── Endpoints │ │ └── Account.fs │ ├── MudBlazorThreeAuth.fsproj │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.fs │ └── wwwroot │ │ └── favicon.png │ └── README.md ├── templates.fsproj └── templates ├── BlazorApp ├── .template.config │ └── template.json ├── BlazorApp.Client │ ├── BlazorApp.Client.fsproj │ ├── Pages │ │ └── Counter.fs │ └── Startup.fs ├── BlazorApp.sln ├── BlazorApp │ ├── BlazorApp.fsproj │ ├── Components │ │ ├── App.fs │ │ ├── Layout │ │ │ ├── MainLayout.fs │ │ │ └── NavMenu.fs │ │ ├── Pages │ │ │ ├── Form.fs │ │ │ └── Home.fs │ │ └── Routes.fs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.fs │ └── wwwroot │ │ └── favicon.png └── README.md ├── BlazorServerApp ├── .template.config │ └── template.json ├── BlazorServerApp.fsproj ├── Components │ ├── App.fs │ ├── Layout │ │ ├── MainLayout.fs │ │ └── NavMenu.fs │ ├── Pages │ │ ├── Counter.fs │ │ ├── Form.fs │ │ └── Home.fs │ └── Routes.fs ├── Properties │ └── launchSettings.json ├── README.md ├── Startup.fs └── wwwroot │ └── favicon.png ├── SSRApp ├── .template.config │ └── template.json ├── Properties │ └── launchSettings.json ├── README.md ├── SSRApp.fsproj ├── Startup.fs ├── View │ ├── App.fs │ ├── Components │ │ ├── Counter.fs │ │ ├── Login.fs │ │ └── OrderList.fs │ ├── Layout │ │ ├── MainLayout.fs │ │ └── NavMenu.fs │ ├── Pages │ │ ├── CounterPage.fs │ │ ├── FromPage.fs │ │ └── HomePage.fs │ └── Routes.fs └── wwwroot │ └── favicon.png └── WASMApp ├── .template.config └── template.json ├── App.fs ├── Properties └── launchSettings.json ├── README.md ├── Startup.fs ├── WASMApp.fsproj └── wwwroot ├── app.css ├── favicon.ico └── index.html /.github/workflows/Build and publish nuget package.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish nuget packages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Setup .NET Core 17 | uses: actions/setup-dotnet@v3 18 | with: 19 | dotnet-version: 9.0.x 20 | 21 | - name: Publish nuget packages 22 | run: | 23 | dotnet pack -c Release templates.fsproj -o . 24 | dotnet nuget push *.nupkg -s https://api.nuget.org/v3/index.json -k ${{secrets.NUGET_API_KEY}} --skip-duplicate 25 | 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | 365 | deploy/ 366 | /slaveOftime-blazor/wwwroot/css/tailwind-generated.css 367 | /slaveOftime-blazor/SiteData.db 368 | /Fun.Blazor.Docs.Wasm/wwwroot/code-docs 369 | 370 | global.json 371 | WASMShoelace/wwwroot/app-generated.css 372 | templates/ConsoleApp/wwwroot/index.html 373 | templates/ConsoleApp/wwwroot/app-generated.css 374 | /FalcoDemo/reports 375 | reports/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repo contains some basic showcase demos. .NET 9 SDK is required for run them. 2 | 3 | Check the README.md file under each demo. 4 | 5 | Template is now available: [![Nuget](https://img.shields.io/nuget/v/Fun.Blazor.Templates)](https://www.nuget.org/packages/Fun.Blazor.Templates) 6 | 7 | ```shell 8 | dotnet new --install Fun.Blazor.Templates::4.1.1 9 | dotnet new fun-blazor -o FunBlazorDemo1 10 | ``` 11 | -------------------------------------------------------------------------------- /samples/FunBlazor.MudBlazorDemo/FunBlazor.MudBlazorDemo.Client/FunBlazor.MudBlazorDemo.Client.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /samples/FunBlazor.MudBlazorDemo/FunBlazor.MudBlazorDemo.Client/Pages/Counter.fs: -------------------------------------------------------------------------------- 1 | namespace FunBlazor.MudBlazorDemo.Client.Pages 2 | 3 | open Microsoft.AspNetCore.Components 4 | open Microsoft.AspNetCore.Components.Web 5 | open MudBlazor 6 | open Fun.Blazor 7 | 8 | [] 9 | [] 10 | type Counter() as this = 11 | inherit FunComponent() 12 | 13 | let mutable count = 0 14 | 15 | [] 16 | member val Snackbar = Unchecked.defaultof with get, set 17 | 18 | override _.Render() = fragment { 19 | // Because the whole app is static, so for the dynamic part like snackbar, they should be rendered together with dynamic component 20 | MudSnackbarProvider'' { } 21 | PageTitle'' { "Counter" } 22 | SectionContent'' { 23 | SectionName "header" 24 | h1 { "Counter" } 25 | } 26 | p { $"Current count: {count}" } 27 | MudButton'' { 28 | Color Color.Success 29 | OnClick(fun _ -> 30 | count <- count + 1 31 | this.Snackbar.Add($"Count = {count}", severity = Severity.Success) |> ignore 32 | ) 33 | "Click me" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /samples/FunBlazor.MudBlazorDemo/FunBlazor.MudBlazorDemo.Client/Startup.fs: -------------------------------------------------------------------------------- 1 | #nowarn "0020" 2 | 3 | open System 4 | open MudBlazor.Services 5 | open Microsoft.AspNetCore.Components.WebAssembly.Hosting 6 | 7 | let builder = WebAssemblyHostBuilder.CreateDefault(Environment.GetCommandLineArgs()) 8 | 9 | builder.Services.AddMudServices() 10 | 11 | builder.Build().RunAsync() 12 | -------------------------------------------------------------------------------- /samples/FunBlazor.MudBlazorDemo/FunBlazor.MudBlazorDemo.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FunBlazor.MudBlazorDemo", "FunBlazor.MudBlazorDemo\FunBlazor.MudBlazorDemo.fsproj", "{67B635AF-5096-4009-8F6D-E11797B1100E}" 7 | EndProject 8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FunBlazor.MudBlazorDemo.Client", "FunBlazor.MudBlazorDemo.Client\FunBlazor.MudBlazorDemo.Client.fsproj", "{7E9C7EDB-8AF5-4BCA-B990-31F6B05CBB79}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(SolutionProperties) = preSolution 16 | HideSolutionNode = FALSE 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {67B635AF-5096-4009-8F6D-E11797B1100E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {67B635AF-5096-4009-8F6D-E11797B1100E}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {67B635AF-5096-4009-8F6D-E11797B1100E}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {67B635AF-5096-4009-8F6D-E11797B1100E}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {7E9C7EDB-8AF5-4BCA-B990-31F6B05CBB79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {7E9C7EDB-8AF5-4BCA-B990-31F6B05CBB79}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {7E9C7EDB-8AF5-4BCA-B990-31F6B05CBB79}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {7E9C7EDB-8AF5-4BCA-B990-31F6B05CBB79}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /samples/FunBlazor.MudBlazorDemo/FunBlazor.MudBlazorDemo/Components/App.fs: -------------------------------------------------------------------------------- 1 | namespace FunBlazor.MudBlazorDemo.Components 2 | 3 | open Fun.Blazor 4 | open Microsoft.AspNetCore.Components.Web 5 | 6 | 7 | type App() = 8 | inherit FunComponent() 9 | 10 | override _.Render() = fragment { 11 | doctype "html" 12 | html' { 13 | lang "EN" 14 | head { 15 | baseUrl "/" 16 | meta { charset "utf-8" } 17 | meta { 18 | name "viewport" 19 | content "width=device-width, initial-scale=1.0" 20 | } 21 | link { 22 | rel "icon" 23 | type' "image/png" 24 | href "favicon.png" 25 | } 26 | styleElt { 27 | ruleset ".active" { 28 | color "green" 29 | fontWeightBold 30 | } 31 | } 32 | stylesheet "https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" 33 | stylesheet "_content/MudBlazor/MudBlazor.min.css" 34 | HeadOutlet'' 35 | } 36 | body { 37 | html.blazor () 38 | script { src "_framework/blazor.web.js" } 39 | script { src "_content/MudBlazor/MudBlazor.min.js" } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /samples/FunBlazor.MudBlazorDemo/FunBlazor.MudBlazorDemo/Components/Layout/MainLayout.fs: -------------------------------------------------------------------------------- 1 | namespace FunBlazor.MudBlazorDemo.Components.Layout 2 | 3 | open Microsoft.AspNetCore.Components 4 | open MudBlazor 5 | open Fun.Blazor 6 | 7 | type MainLayout() as this = 8 | inherit LayoutComponentBase() 9 | 10 | let mutable isDrawerOpen = true 11 | 12 | let content = fragment { 13 | MudThemeProvider'' 14 | MudPopoverProvider'' { interactiveAuto } 15 | MudLayout'' { 16 | MudAppBar'' { 17 | // This will not work, because the layout is not all in server/client interactive mode, 18 | // some pages are SSR, so by default the whole app is static and cannot interact. 19 | // MudIconButton'' { 20 | // Icon Icons.Material.Filled.Menu 21 | // Color Color.Inherit 22 | // Edge Edge.Start 23 | // OnClick(fun _ -> isDrawerOpen <- not isDrawerOpen) 24 | // } 25 | SectionOutlet'' { SectionName "header" } 26 | } 27 | MudDrawer'' { 28 | Open isDrawerOpen 29 | // OpenChanged(fun x -> isDrawerOpen <- x) 30 | NavMenu.Create() 31 | } 32 | MudMainContent'' { MudContainer'' { this.Body } } 33 | } 34 | } 35 | 36 | override _.BuildRenderTree(builder) = content.Invoke(this, builder, 0) |> ignore 37 | -------------------------------------------------------------------------------- /samples/FunBlazor.MudBlazorDemo/FunBlazor.MudBlazorDemo/Components/Layout/NavMenu.fs: -------------------------------------------------------------------------------- 1 | namespace FunBlazor.MudBlazorDemo.Components.Layout 2 | 3 | open Microsoft.AspNetCore.Components.Routing 4 | open MudBlazor 5 | 6 | 7 | type NavMenu = 8 | static member Create() = MudNavMenu'' { 9 | MudNavLink'' { 10 | Href "" 11 | Match NavLinkMatch.All 12 | Icon Icons.Material.Filled.Home 13 | "Home" 14 | } 15 | MudNavLink'' { 16 | Href "counter" 17 | Match NavLinkMatch.Prefix 18 | Icon Icons.Material.Filled.Add 19 | "Counter" 20 | } 21 | MudNavLink'' { 22 | Href "form" 23 | Match NavLinkMatch.Prefix 24 | Icon Icons.Material.Filled.List 25 | "Form demo" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /samples/FunBlazor.MudBlazorDemo/FunBlazor.MudBlazorDemo/Components/Pages/From.fs: -------------------------------------------------------------------------------- 1 | namespace FunBlazor.MudBlazorDemo.Components.Pages 2 | 3 | open System 4 | open System.Threading.Tasks 5 | open Microsoft.AspNetCore.Components 6 | open Microsoft.AspNetCore.Components.Web 7 | open Microsoft.AspNetCore.Components.Forms 8 | open MudBlazor 9 | open Fun.Blazor 10 | 11 | [] 12 | [] 13 | type Form() as this = 14 | inherit FunComponent() 15 | 16 | let mutable isSubmitting = false 17 | 18 | [] 19 | member val Query: string = null with get, set 20 | 21 | member _.Submit() = task { 22 | isSubmitting <- true 23 | this.StateHasChanged() 24 | 25 | do! Task.Delay 1000 26 | isSubmitting <- false 27 | this.StateHasChanged() 28 | } 29 | 30 | member _.FormView = form { 31 | onsubmit (ignore >> this.Submit) 32 | method "post" 33 | dataEnhance 34 | formName "person-info" 35 | html.blazor () 36 | MudTextField'' { 37 | "name", nameof this.Query 38 | Value this.Query 39 | Error(String.IsNullOrEmpty this.Query || this.Query.Length > 5) 40 | ErrorText $"{nameof this.Query} is not valid" 41 | } 42 | MudButton'' { 43 | ButtonType ButtonType.Submit 44 | "Submit" 45 | } 46 | } 47 | 48 | override _.Render() = fragment { 49 | PageTitle'' { "Form demo" } 50 | SectionContent'' { 51 | SectionName "header" 52 | h1 { "Form demo" } 53 | } 54 | div { 55 | style { height "100vh" } 56 | MudLink'' { 57 | Href "form?#person-info" 58 | "check the form" 59 | } 60 | } 61 | h2 { 62 | id "person-info" 63 | "person info" 64 | } 65 | this.FormView 66 | region { if isSubmitting then MudProgressLinear'' { Indeterminate true } } 67 | div { style { height "100vh" } } 68 | } 69 | -------------------------------------------------------------------------------- /samples/FunBlazor.MudBlazorDemo/FunBlazor.MudBlazorDemo/Components/Pages/Home.fs: -------------------------------------------------------------------------------- 1 | namespace FunBlazor.MudBlazorDemo.Components.Pages 2 | 3 | open System 4 | open System.Threading.Tasks 5 | open Microsoft.AspNetCore.Components 6 | open Microsoft.AspNetCore.Components.Web 7 | open MudBlazor 8 | open Fun.Blazor 9 | 10 | [] 11 | [] 12 | type Home() as this = 13 | inherit FunComponent() 14 | 15 | let mutable items = [] 16 | 17 | [] 18 | member val query = Nullable() with get, set 19 | 20 | member _.FilteredItems = 21 | if this.query.HasValue then 22 | items |> Seq.filter (fun x -> x > this.query.Value) 23 | else 24 | items 25 | 26 | member _.MainContent = fragment { 27 | div { 28 | a { 29 | style { color (if this.query.HasValue then "hotpink" else "grey") } 30 | href "?query=3" 31 | "filter: bigger than 3" 32 | } 33 | } 34 | ul { 35 | region { 36 | for i in this.FilteredItems do 37 | li { 38 | style { color "green" } 39 | $"item {i}" 40 | } 41 | } 42 | } 43 | } 44 | 45 | override _.OnInitializedAsync() = task { 46 | do! Task.Delay 2000 47 | items <- [ 1..5 ] 48 | this.StateHasChanged() 49 | 50 | do! Task.Delay 1000 51 | items <- [ 1..10 ] 52 | this.StateHasChanged() 53 | } 54 | 55 | override _.Render() = fragment { 56 | PageTitle'' { "Home" } 57 | SectionContent'' { 58 | SectionName "header" 59 | h1 { "Home" } 60 | } 61 | region { 62 | if items.IsEmpty then 63 | MudProgressLinear'' { Indeterminate true } 64 | else 65 | this.MainContent 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /samples/FunBlazor.MudBlazorDemo/FunBlazor.MudBlazorDemo/Components/Routes.fs: -------------------------------------------------------------------------------- 1 | namespace FunBlazor.MudBlazorDemo.Components 2 | 3 | open System.Reflection 4 | open Fun.Blazor 5 | open FunBlazor.MudBlazorDemo.Components.Layout 6 | 7 | type Routes() = 8 | inherit FunComponent() 9 | 10 | override _.Render() = Router'' { 11 | AppAssembly(Assembly.GetExecutingAssembly()) 12 | Found(fun routeData -> RouteView'' { 13 | RouteData routeData 14 | DefaultLayout typeof 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /samples/FunBlazor.MudBlazorDemo/FunBlazor.MudBlazorDemo/FunBlazor.MudBlazorDemo.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /samples/FunBlazor.MudBlazorDemo/FunBlazor.MudBlazorDemo/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:57640/", 7 | "sslPort": 44319 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "FunBlazor.MudBlazorDemo": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:51871;http://localhost:51872" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /samples/FunBlazor.MudBlazorDemo/FunBlazor.MudBlazorDemo/Startup.fs: -------------------------------------------------------------------------------- 1 | #nowarn "0020" 2 | 3 | open System 4 | open Microsoft.AspNetCore.Builder 5 | open Microsoft.Extensions.Hosting 6 | open Microsoft.Extensions.DependencyInjection 7 | open MudBlazor.Services 8 | open FunBlazor.MudBlazorDemo.Components 9 | open FunBlazor.MudBlazorDemo.Client.Pages 10 | 11 | let builder = WebApplication.CreateBuilder(Environment.GetCommandLineArgs()) 12 | 13 | builder.Services.AddRazorComponents() 14 | .AddInteractiveServerComponents() 15 | .AddInteractiveWebAssemblyComponents() 16 | 17 | builder.Services.AddFunBlazorServer() 18 | builder.Services.AddMudServices() 19 | 20 | 21 | let app = builder.Build() 22 | 23 | app.UseStaticFiles() 24 | app.UseAntiforgery() 25 | 26 | app.MapRazorComponents() 27 | .AddInteractiveServerRenderMode() 28 | .AddInteractiveWebAssemblyRenderMode() 29 | .AddAdditionalAssemblies(typeof.Assembly); 30 | 31 | app.Run() 32 | -------------------------------------------------------------------------------- /samples/FunBlazor.MudBlazorDemo/FunBlazor.MudBlazorDemo/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaveOftime/Fun.Blazor.Samples/5b8f5371390e3376e3e3db545652f5416f99e566/samples/FunBlazor.MudBlazorDemo/FunBlazor.MudBlazorDemo/wwwroot/favicon.png -------------------------------------------------------------------------------- /samples/FunWasm.AntDesignDemo/App.fs: -------------------------------------------------------------------------------- 1 | // hot-reload 2 | // hot-reload is the flag to let cli know this file should be included 3 | // It has dependency requirement: the root is the app which is used in the Startup.fs 4 | // All other files which want have hot reload, need to drill down to that file, and all the middle file should also add the '// hot-reload' flag at the top of taht file 5 | [] 6 | module FunWasm.AntDesignDemo.App 7 | 8 | open AntDesign 9 | open Fun.Blazor 10 | open Fun.Blazor.Router 11 | open Microsoft.AspNetCore.Components.Web 12 | 13 | type IShareStore with 14 | 15 | member store.Count = store.CreateCVal(nameof store.Count, 0) 16 | member store.IsMenuOpen = store.CreateCVal(nameof store.IsMenuOpen, false) 17 | 18 | let homePage = fragment { 19 | SectionContent'' { 20 | SectionName "Title" 21 | "Home" 22 | } 23 | Title'' { 24 | Level 1 25 | Type TextElementType.Danger 26 | "Hi from FunBlazor" 27 | } 28 | } 29 | 30 | let counterPage = 31 | html.inject (fun (store: IShareStore, snackbar: NotificationService) -> fragment { 32 | SectionContent'' { 33 | SectionName "Title" 34 | "Counter" 35 | } 36 | adapt { 37 | let! count = store.Count 38 | div { 39 | "Here is the count: " 40 | count 41 | } 42 | } 43 | Button'' { 44 | Type ButtonType.Primary 45 | OnClick(fun _ -> task { 46 | store.Count.Publish((+) 1) 47 | let! _ = snackbar.Open(NotificationConfig(Message = $"Count = {store.Count.Value}")) 48 | () 49 | }) 50 | "Increase by 1" 51 | } 52 | }) 53 | 54 | 55 | let appbar = 56 | html.injectWithNoKey (fun (store: IShareStore) -> Header'' { 57 | Button'' { 58 | Shape ButtonShape.Circle 59 | Icon IconType.Outline.MenuFold 60 | OnClick(fun _ -> store.IsMenuOpen.Publish(not)) 61 | } 62 | span { 63 | style { color "white" } 64 | SectionOutlet'' { SectionName "Title" } 65 | } 66 | }) 67 | 68 | let navmenus = Sider'' { 69 | Width 200 70 | Menu'' { 71 | Mode MenuMode.Inline 72 | style { 73 | height "100%" 74 | borderRight "0" 75 | } 76 | MenuItem'' { 77 | a { 78 | href "/" 79 | "Home" 80 | } 81 | } 82 | MenuItem'' { 83 | a { 84 | href "/counter" 85 | "Counter" 86 | } 87 | } 88 | } 89 | } 90 | 91 | 92 | let routes = html.route [| routeCi "/counter" counterPage; routeAny homePage |] 93 | 94 | 95 | let app = ErrorBoundary'' { 96 | ErrorContent(fun e -> Alert'' { 97 | Type AlertType.Error 98 | string e 99 | }) 100 | AntContainer'' 101 | Layout'' { 102 | style { height "100%" } 103 | appbar 104 | Layout'' { 105 | navmenus 106 | Layout'' { 107 | style { padding 10 } 108 | Content'' { routes } 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /samples/FunWasm.AntDesignDemo/FunWasm.AntDesignDemo.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /samples/FunWasm.AntDesignDemo/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:57135/", 7 | "sslPort": 44390 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | }, 17 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" 18 | }, 19 | "FunWasm.AntDesignDemo": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 26 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /samples/FunWasm.AntDesignDemo/README.md: -------------------------------------------------------------------------------- 1 | This is a blazor WASM mode app with Fun.Blazor 2 | 3 | wwwroot/index.html is the entry point for browser. it will load all the necessary files. 4 | 5 | App.fs contains UI logic 6 | 7 | Startup.fs is for hooking up everything and configuring server 8 | 9 | ```bash 10 | dotnet run 11 | ``` 12 | -------------------------------------------------------------------------------- /samples/FunWasm.AntDesignDemo/Startup.fs: -------------------------------------------------------------------------------- 1 | #nowarn "0020" 2 | 3 | open System 4 | open Microsoft.Extensions.DependencyInjection 5 | open Microsoft.AspNetCore.Components.WebAssembly.Hosting 6 | open AntDesign 7 | open Fun.Blazor 8 | open FunWasm.AntDesignDemo 9 | 10 | let builder = WebAssemblyHostBuilder.CreateDefault(Environment.GetCommandLineArgs()) 11 | 12 | builder.AddFunBlazor("#app", app) 13 | builder.Services.AddFunBlazorWasm() 14 | builder.Services.AddAntDesign() 15 | 16 | builder.Build().RunAsync() 17 | -------------------------------------------------------------------------------- /samples/FunWasm.AntDesignDemo/wwwroot/app.css: -------------------------------------------------------------------------------- 1 |  2 | #blazor-error-ui { 3 | background: lightyellow; 4 | bottom: 0; 5 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 6 | display: none; 7 | left: 0; 8 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 9 | position: fixed; 10 | width: 100%; 11 | z-index: 1000; 12 | } 13 | 14 | #blazor-error-ui .dismiss { 15 | cursor: pointer; 16 | position: absolute; 17 | right: 0.75rem; 18 | top: 0.5rem; 19 | } 20 | 21 | .blazor-error-boundary { 22 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 23 | padding: 1rem 1rem 1rem 3.7rem; 24 | color: white; 25 | } 26 | 27 | .blazor-error-boundary::after { 28 | content: "An error has occurred." 29 | } 30 | 31 | .loading-progress { 32 | position: relative; 33 | display: block; 34 | width: 8rem; 35 | height: 8rem; 36 | margin: 20vh auto 1rem auto; 37 | } 38 | 39 | .loading-progress circle { 40 | fill: none; 41 | stroke: #e0e0e0; 42 | stroke-width: 0.6rem; 43 | transform-origin: 50% 50%; 44 | transform: rotate(-90deg); 45 | } 46 | 47 | .loading-progress circle:last-child { 48 | stroke: #1b6ec2; 49 | stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; 50 | transition: stroke-dasharray 0.05s ease-in-out; 51 | } 52 | 53 | .loading-progress-text { 54 | position: absolute; 55 | text-align: center; 56 | font-weight: bold; 57 | inset: calc(20vh + 3.25rem) 0 auto 0.2rem; 58 | } 59 | 60 | .loading-progress-text:after { 61 | content: var(--blazor-load-percentage-text, "Loading"); 62 | } 63 | -------------------------------------------------------------------------------- /samples/FunWasm.AntDesignDemo/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaveOftime/Fun.Blazor.Samples/5b8f5371390e3376e3e3db545652f5416f99e566/samples/FunWasm.AntDesignDemo/wwwroot/favicon.ico -------------------------------------------------------------------------------- /samples/FunWasm.AntDesignDemo/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | FunWasm.AntDesignDemo 8 | 9 | 10 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 |
28 |
29 |
30 | An unhandled error has occurred. 31 | Reload 32 | 🗙 33 |
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /samples/FunWasm.FluentUIDemo/App.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module FunWasm.FluentUIDemo.App 3 | 4 | open Microsoft.AspNetCore.Components.Web 5 | open Microsoft.AspNetCore.Components.Routing 6 | open Microsoft.FluentUI.AspNetCore.Components 7 | open Fun.Blazor 8 | open Fun.Blazor.Router 9 | 10 | 11 | type IShareStore with 12 | 13 | member store.Count = store.CreateCVal(nameof store.Count, 0) 14 | member store.IsMenuOpen = store.CreateCVal(nameof store.IsMenuOpen, true) 15 | 16 | let homePage = fragment { 17 | SectionContent'' { 18 | SectionName "Title" 19 | "Home" 20 | } 21 | FluentLabel'' { 22 | Typo Typography.H1 23 | Color Color.Accent 24 | "Hi from FunBlazor" 25 | } 26 | } 27 | 28 | let counterPage = 29 | html.inject (fun (store: IShareStore, snackbar: IToastService) -> fragment { 30 | SectionContent'' { 31 | SectionName "Title" 32 | "Counter" 33 | } 34 | adapt { 35 | let! count = store.Count 36 | div { 37 | "Here is the count: " 38 | count 39 | } 40 | } 41 | FluentButton'' { 42 | Appearance Appearance.Accent 43 | OnClick(fun _ -> 44 | store.Count.Publish((+) 1) 45 | snackbar.ShowSuccess($"Count = {store.Count.Value}") 46 | ) 47 | "Increase by 1" 48 | } 49 | }) 50 | 51 | 52 | let appbar = 53 | html.injectWithNoKey (fun (store: IShareStore) -> FluentHeader'' { 54 | FluentIcon'' { 55 | Value(Icons.Regular.Size24.Navigation()) 56 | Color Color.Fill 57 | OnClick(fun _ -> store.IsMenuOpen.Publish(not)) 58 | } 59 | FluentSpacer'' { Width 20 } 60 | SectionOutlet'' { SectionName "Title" } 61 | }) 62 | 63 | let navmenus = 64 | html.injectWithNoKey (fun (store: IShareStore) -> adaptiview () { 65 | let! binding = store.IsMenuOpen.WithSetter() 66 | FluentNavMenu'' { 67 | Width 200 68 | Expanded' binding 69 | FluentNavLink'' { 70 | Href "/" 71 | Match NavLinkMatch.All 72 | Icon(Icons.Regular.Size20.Home()) 73 | "Home" 74 | } 75 | FluentNavLink'' { 76 | Href "/counter" 77 | Match NavLinkMatch.Prefix 78 | Icon(Icons.Regular.Size20.NumberSymbolSquare()) 79 | "Counter" 80 | } 81 | } 82 | }) 83 | 84 | let routes = html.route [| routeCi "/counter" counterPage; routeAny homePage |] 85 | 86 | 87 | let app = ErrorBoundary'' { 88 | ErrorContent(fun e -> FluentLabel'' { 89 | Color Color.Error 90 | string e 91 | }) 92 | FluentToastProvider'' 93 | FluentLayout'' { 94 | appbar 95 | FluentStack'' { 96 | Width "100%" 97 | Orientation Orientation.Horizontal 98 | navmenus 99 | FluentBodyContent'' { 100 | style { overflowHidden } 101 | routes 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /samples/FunWasm.FluentUIDemo/FunWasm.FluentUIDemo.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /samples/FunWasm.FluentUIDemo/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:57135/", 7 | "sslPort": 44390 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | }, 17 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" 18 | }, 19 | "FunWasm.FluentUIDemo": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 26 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /samples/FunWasm.FluentUIDemo/README.md: -------------------------------------------------------------------------------- 1 | This is a blazor WASM mode app with Fun.Blazor 2 | 3 | wwwroot/index.html is the entry point for browser. it will load all the necessary files. 4 | 5 | App.fs contains UI logic 6 | 7 | Startup.fs is for hooking up everything and configuring server 8 | 9 | ```bash 10 | dotnet run 11 | ``` 12 | -------------------------------------------------------------------------------- /samples/FunWasm.FluentUIDemo/Startup.fs: -------------------------------------------------------------------------------- 1 | #nowarn "0020" 2 | 3 | open System 4 | open Microsoft.Extensions.DependencyInjection 5 | open Microsoft.AspNetCore.Components.WebAssembly.Hosting 6 | open Microsoft.FluentUI.AspNetCore.Components 7 | open FunWasm.FluentUIDemo 8 | 9 | let builder = WebAssemblyHostBuilder.CreateDefault(Environment.GetCommandLineArgs()) 10 | 11 | builder.AddFunBlazor("#app", app) 12 | 13 | builder.Services.AddFunBlazorWasm() 14 | builder.Services.AddFluentUIComponents() 15 | 16 | builder.Build().RunAsync() 17 | -------------------------------------------------------------------------------- /samples/FunWasm.FluentUIDemo/wwwroot/app.css: -------------------------------------------------------------------------------- 1 |  2 | #blazor-error-ui { 3 | background: lightyellow; 4 | bottom: 0; 5 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 6 | display: none; 7 | left: 0; 8 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 9 | position: fixed; 10 | width: 100%; 11 | z-index: 1000; 12 | } 13 | 14 | #blazor-error-ui .dismiss { 15 | cursor: pointer; 16 | position: absolute; 17 | right: 0.75rem; 18 | top: 0.5rem; 19 | } 20 | 21 | .blazor-error-boundary { 22 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 23 | padding: 1rem 1rem 1rem 3.7rem; 24 | color: white; 25 | } 26 | 27 | .blazor-error-boundary::after { 28 | content: "An error has occurred." 29 | } 30 | 31 | .loading-progress { 32 | position: relative; 33 | display: block; 34 | width: 8rem; 35 | height: 8rem; 36 | margin: 20vh auto 1rem auto; 37 | } 38 | 39 | .loading-progress circle { 40 | fill: none; 41 | stroke: #e0e0e0; 42 | stroke-width: 0.6rem; 43 | transform-origin: 50% 50%; 44 | transform: rotate(-90deg); 45 | } 46 | 47 | .loading-progress circle:last-child { 48 | stroke: #1b6ec2; 49 | stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; 50 | transition: stroke-dasharray 0.05s ease-in-out; 51 | } 52 | 53 | .loading-progress-text { 54 | position: absolute; 55 | text-align: center; 56 | font-weight: bold; 57 | inset: calc(20vh + 3.25rem) 0 auto 0.2rem; 58 | } 59 | 60 | .loading-progress-text:after { 61 | content: var(--blazor-load-percentage-text, "Loading"); 62 | } 63 | -------------------------------------------------------------------------------- /samples/FunWasm.FluentUIDemo/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaveOftime/Fun.Blazor.Samples/5b8f5371390e3376e3e3db545652f5416f99e566/samples/FunWasm.FluentUIDemo/wwwroot/favicon.ico -------------------------------------------------------------------------------- /samples/FunWasm.FluentUIDemo/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | FunWasm.FluentUIDemo 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 | An unhandled error has occurred. 24 | Reload 25 | 🗙 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /samples/FunWasm.MudBlazorDemo/App.fs: -------------------------------------------------------------------------------- 1 | // hot-reload 2 | [] 3 | module FunWasm.MudBlazorDemo.App 4 | 5 | open Microsoft.AspNetCore.Components.Web 6 | open Microsoft.AspNetCore.Components.Routing 7 | open MudBlazor 8 | open Fun.Blazor 9 | open Fun.Blazor.Router 10 | 11 | 12 | type IShareStore with 13 | 14 | member store.Count = store.CreateCVal(nameof store.Count, 0) 15 | member store.IsMenuOpen = store.CreateCVal(nameof store.IsMenuOpen, true) 16 | 17 | let homePage = fragment { 18 | SectionContent'' { 19 | SectionName "Title" 20 | "Home" 21 | } 22 | MudText'' { 23 | Typo Typo.h1 24 | Color Color.Info 25 | "Hi from FunBlazor" 26 | } 27 | } 28 | 29 | let counterPage = 30 | html.inject (fun (store: IShareStore, snackbar: ISnackbar) -> fragment { 31 | SectionContent'' { 32 | SectionName "Title" 33 | "Counter" 34 | } 35 | adapt { 36 | let! count = store.Count 37 | div { 38 | "Here is the count: " 39 | count 40 | } 41 | } 42 | MudButton'' { 43 | Variant Variant.Filled 44 | Color Color.Primary 45 | OnClick(fun _ -> 46 | store.Count.Publish((+) 1) 47 | snackbar.Add($"Count = {store.Count.Value}", severity = Severity.Success) |> ignore 48 | ) 49 | "Increase by 1" 50 | } 51 | }) 52 | 53 | 54 | let appbar = 55 | html.injectWithNoKey (fun (store: IShareStore) -> MudAppBar'' { 56 | MudIconButton'' { 57 | Icon Icons.Material.Filled.Menu 58 | Color Color.Inherit 59 | Edge Edge.Start 60 | OnClick(fun _ -> store.IsMenuOpen.Publish(not)) 61 | } 62 | SectionOutlet'' { SectionName "Title" } 63 | }) 64 | 65 | let navmenus = 66 | // We should use a fixed key or no key here to avoid infinite re-redner caused MudDrawer and MudLayout 67 | html.injectWithNoKey (fun (store: IShareStore) -> adapt { 68 | let! binding = store.IsMenuOpen.WithSetter() 69 | MudDrawer'' { 70 | Open' binding 71 | Elevation 1 72 | MudNavMenu'' { 73 | MudNavLink'' { 74 | Href "/" 75 | Match NavLinkMatch.All 76 | "Home" 77 | } 78 | MudNavLink'' { 79 | Href "/counter" 80 | Match NavLinkMatch.Prefix 81 | "Counter" 82 | } 83 | } 84 | } 85 | }) 86 | 87 | 88 | let routes = html.route [| routeCi "/counter" counterPage; routeAny homePage |] 89 | 90 | 91 | let app = ErrorBoundary'' { 92 | ErrorContent(fun e -> MudAlert'' { 93 | Severity Severity.Error 94 | string e 95 | }) 96 | MudThemeProvider'' 97 | MudDialogProvider'' 98 | MudSnackbarProvider'' 99 | MudLayout'' { 100 | appbar 101 | navmenus 102 | MudMainContent'' { 103 | MudContainer'' { 104 | MaxWidth MaxWidth.ExtraLarge 105 | routes 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /samples/FunWasm.MudBlazorDemo/FunWasm.MudBlazorDemo.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /samples/FunWasm.MudBlazorDemo/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:57135/", 7 | "sslPort": 44390 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | }, 17 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" 18 | }, 19 | "FunWasm.MudBlazorDemo": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 26 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /samples/FunWasm.MudBlazorDemo/Startup.fs: -------------------------------------------------------------------------------- 1 | #nowarn "0020" 2 | 3 | open System 4 | open Microsoft.Extensions.DependencyInjection 5 | open Microsoft.AspNetCore.Components.WebAssembly.Hosting 6 | open MudBlazor.Services 7 | open FunWasm.MudBlazorDemo 8 | 9 | let builder = WebAssemblyHostBuilder.CreateDefault(Environment.GetCommandLineArgs()) 10 | 11 | builder.AddFunBlazor("#app", app) 12 | 13 | builder.Services.AddFunBlazorWasm() 14 | builder.Services.AddMudServices() 15 | 16 | builder.Build().RunAsync() 17 | -------------------------------------------------------------------------------- /samples/FunWasm.MudBlazorDemo/wwwroot/app.css: -------------------------------------------------------------------------------- 1 |  2 | #blazor-error-ui { 3 | background: lightyellow; 4 | bottom: 0; 5 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 6 | display: none; 7 | left: 0; 8 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 9 | position: fixed; 10 | width: 100%; 11 | z-index: 1000; 12 | } 13 | 14 | #blazor-error-ui .dismiss { 15 | cursor: pointer; 16 | position: absolute; 17 | right: 0.75rem; 18 | top: 0.5rem; 19 | } 20 | 21 | .blazor-error-boundary { 22 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 23 | padding: 1rem 1rem 1rem 3.7rem; 24 | color: white; 25 | } 26 | 27 | .blazor-error-boundary::after { 28 | content: "An error has occurred." 29 | } 30 | 31 | .loading-progress { 32 | position: relative; 33 | display: block; 34 | width: 8rem; 35 | height: 8rem; 36 | margin: 20vh auto 1rem auto; 37 | } 38 | 39 | .loading-progress circle { 40 | fill: none; 41 | stroke: #e0e0e0; 42 | stroke-width: 0.6rem; 43 | transform-origin: 50% 50%; 44 | transform: rotate(-90deg); 45 | } 46 | 47 | .loading-progress circle:last-child { 48 | stroke: #1b6ec2; 49 | stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; 50 | transition: stroke-dasharray 0.05s ease-in-out; 51 | } 52 | 53 | .loading-progress-text { 54 | position: absolute; 55 | text-align: center; 56 | font-weight: bold; 57 | inset: calc(20vh + 3.25rem) 0 auto 0.2rem; 58 | } 59 | 60 | .loading-progress-text:after { 61 | content: var(--blazor-load-percentage-text, "Loading"); 62 | } 63 | -------------------------------------------------------------------------------- /samples/FunWasm.MudBlazorDemo/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaveOftime/Fun.Blazor.Samples/5b8f5371390e3376e3e3db545652f5416f99e566/samples/FunWasm.MudBlazorDemo/wwwroot/favicon.ico -------------------------------------------------------------------------------- /samples/FunWasm.MudBlazorDemo/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | FunWasm.MudBlazorDemo 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 | An unhandled error has occurred. 24 | Reload 25 | 🗙 26 |
27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /samples/FunWasm.RadzenDemo/App.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module FunWasm.RadzenDemo.App 3 | 4 | open FSharp.Data.Adaptive 5 | open Fun.Blazor 6 | open Radzen 7 | open Radzen.Blazor.Blazor 8 | 9 | let private isDrawerOpen = cval false 10 | 11 | let private counter = adapt { 12 | let amount = 1 13 | let! count, setCount = cval(1).WithSetter() 14 | 15 | div { 16 | div { $"Here is the count {count}" } 17 | RadzenButton'' { 18 | Click(fun _ -> setCount (count + amount)) 19 | "Increase by " 20 | amount 21 | } 22 | } 23 | } 24 | 25 | 26 | let app = RadzenLayout'' { 27 | RadzenTheme'' { Theme "material" } 28 | RadzenHeader'' { 29 | RadzenStack'' { 30 | Orientation Orientation.Horizontal 31 | AlignItems AlignItems.Center 32 | Gap "0" 33 | RadzenSidebarToggle'' { Click(fun _ -> isDrawerOpen.Publish(not)) } 34 | RadzenLabel'' { "Header" } 35 | } 36 | } 37 | adapt { 38 | let! binding = isDrawerOpen.WithSetter() 39 | RadzenSidebar'' { 40 | Expanded' binding 41 | RadzenPanelMenu'' { 42 | RadzenPanelMenuItem'' { 43 | Icon "home" 44 | Text "Text" 45 | } 46 | } 47 | } 48 | } 49 | RadzenBody'' { 50 | counter 51 | br 52 | adapt { 53 | let! txt = cval("

hi

").WithSetter() 54 | RadzenHtmlEditor'' { Value' txt } 55 | } 56 | } 57 | RadzenFooter'' { "footer" } 58 | } 59 | -------------------------------------------------------------------------------- /samples/FunWasm.RadzenDemo/FunWasm.RadzenDemo.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /samples/FunWasm.RadzenDemo/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51234/", 7 | "sslPort": 44390 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | }, 17 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" 18 | }, 19 | "FunWasm.RadzenDemo": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "applicationUrl": "https://localhost:51235;http://localhost:51236", 26 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /samples/FunWasm.RadzenDemo/README.md: -------------------------------------------------------------------------------- 1 | This is a blazor WASM mode app with Fun.Blazor 2 | 3 | wwwroot/index.html is the entry point for browser. it will load all the necessary files. 4 | 5 | App.fs contains UI logic 6 | 7 | Startup.fs is for hooking up everything and configuring server 8 | 9 | ```bash 10 | dotnet run 11 | ``` 12 | -------------------------------------------------------------------------------- /samples/FunWasm.RadzenDemo/Startup.fs: -------------------------------------------------------------------------------- 1 | #nowarn "0020" 2 | 3 | open System 4 | open Microsoft.Extensions.DependencyInjection 5 | open Microsoft.AspNetCore.Components.WebAssembly.Hosting 6 | open Radzen 7 | open FunWasm.RadzenDemo 8 | 9 | let builder = WebAssemblyHostBuilder.CreateDefault(Environment.GetCommandLineArgs()) 10 | 11 | builder.AddFunBlazor("#app", app) 12 | builder.Services.AddFunBlazorWasm() 13 | builder.Services.AddRadzenComponents() 14 | 15 | builder.Build().RunAsync() 16 | -------------------------------------------------------------------------------- /samples/FunWasm.RadzenDemo/wwwroot/app.css: -------------------------------------------------------------------------------- 1 |  2 | #blazor-error-ui { 3 | background: lightyellow; 4 | bottom: 0; 5 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 6 | display: none; 7 | left: 0; 8 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 9 | position: fixed; 10 | width: 100%; 11 | z-index: 1000; 12 | } 13 | 14 | #blazor-error-ui .dismiss { 15 | cursor: pointer; 16 | position: absolute; 17 | right: 0.75rem; 18 | top: 0.5rem; 19 | } 20 | 21 | .blazor-error-boundary { 22 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 23 | padding: 1rem 1rem 1rem 3.7rem; 24 | color: white; 25 | } 26 | 27 | .blazor-error-boundary::after { 28 | content: "An error has occurred." 29 | } 30 | 31 | .loading-progress { 32 | position: relative; 33 | display: block; 34 | width: 8rem; 35 | height: 8rem; 36 | margin: 20vh auto 1rem auto; 37 | } 38 | 39 | .loading-progress circle { 40 | fill: none; 41 | stroke: #e0e0e0; 42 | stroke-width: 0.6rem; 43 | transform-origin: 50% 50%; 44 | transform: rotate(-90deg); 45 | } 46 | 47 | .loading-progress circle:last-child { 48 | stroke: #1b6ec2; 49 | stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; 50 | transition: stroke-dasharray 0.05s ease-in-out; 51 | } 52 | 53 | .loading-progress-text { 54 | position: absolute; 55 | text-align: center; 56 | font-weight: bold; 57 | inset: calc(20vh + 3.25rem) 0 auto 0.2rem; 58 | } 59 | 60 | .loading-progress-text:after { 61 | content: var(--blazor-load-percentage-text, "Loading"); 62 | } 63 | -------------------------------------------------------------------------------- /samples/FunWasm.RadzenDemo/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaveOftime/Fun.Blazor.Samples/5b8f5371390e3376e3e3db545652f5416f99e566/samples/FunWasm.RadzenDemo/wwwroot/favicon.ico -------------------------------------------------------------------------------- /samples/FunWasm.RadzenDemo/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | FunWasm.RadzenDemo 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 | An unhandled error has occurred. 22 | Reload 23 | 🗙 24 |
25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth.Client/Fun.Blazor.Bindings/HomagGroup.Blazor3D.fs: -------------------------------------------------------------------------------- 1 | namespace rec HomagGroup.Blazor3D.DslInternals 2 | 3 | open System.Threading.Tasks 4 | open FSharp.Data.Adaptive 5 | open Fun.Blazor 6 | open Fun.Blazor.Operators 7 | open HomagGroup.Blazor3D.DslInternals 8 | 9 | type ViewerBuilder<'FunBlazorGeneric when 'FunBlazorGeneric :> Microsoft.AspNetCore.Components.IComponent>() = 10 | inherit ComponentWithDomAttrBuilder<'FunBlazorGeneric>() 11 | [] member inline _.ViewerSettings ([] render: AttrRenderFragment, x: HomagGroup.Blazor3D.Settings.ViewerSettings) = render ==> ("ViewerSettings" => x) 12 | [] member inline _.Scene ([] render: AttrRenderFragment, x: HomagGroup.Blazor3D.Scenes.Scene) = render ==> ("Scene" => x) 13 | [] member inline _.UseDefaultScene ([] render: AttrRenderFragment) = render ==> ("UseDefaultScene" =>>> true) 14 | [] member inline _.UseDefaultScene ([] render: AttrRenderFragment, x: bool) = render ==> ("UseDefaultScene" =>>> x) 15 | [] member inline _.Camera ([] render: AttrRenderFragment, x: HomagGroup.Blazor3D.Cameras.Camera) = render ==> ("Camera" => x) 16 | [] member inline _.OrbitControls ([] render: AttrRenderFragment, x: HomagGroup.Blazor3D.Controls.OrbitControls) = render ==> ("OrbitControls" => x) 17 | 18 | 19 | 20 | // ======================================================================================================================= 21 | 22 | namespace HomagGroup.Blazor3D 23 | 24 | [] 25 | module DslCE = 26 | 27 | open System.Diagnostics.CodeAnalysis 28 | open HomagGroup.Blazor3D.DslInternals 29 | 30 | type Viewer' [)>] () = inherit ViewerBuilder() 31 | 32 | [] 33 | module DslCEInstances = 34 | 35 | open System.Diagnostics.CodeAnalysis 36 | open HomagGroup.Blazor3D.DslInternals 37 | 38 | let Viewer'' = Viewer'() 39 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth.Client/ILLink.Substitutions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth.Client/MudBlazorThreeAuth.Client.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net9.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth.Client/Pages/Counter.fs: -------------------------------------------------------------------------------- 1 | namespace MudBlazorThreeAuth.Client.Pages 2 | 3 | open Microsoft.AspNetCore.Components 4 | open Microsoft.AspNetCore.Components.Web 5 | open MudBlazor 6 | open Fun.Blazor 7 | 8 | [] 9 | [] 10 | type Counter() = 11 | inherit FunComponent() 12 | 13 | let mutable count = 0 14 | 15 | override _.Render() = fragment { 16 | PageTitle'' { "Counter" } 17 | SectionContent'' { 18 | SectionName "header" 19 | h1 { "Counter" } 20 | } 21 | MudText'' { 22 | Typo Typo.body1 23 | $"Current count: {count}" 24 | } 25 | MudButton'' { 26 | Color Color.Success 27 | Variant Variant.Filled 28 | OnClick(fun _ -> count <- count + 1) 29 | "Click me" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth.Client/Pages/DataGridDemo.fs: -------------------------------------------------------------------------------- 1 | namespace MudBlazorThreeAuth.Client.Pages 2 | 3 | open System 4 | open System.Threading.Tasks 5 | open FSharp.Data.Adaptive 6 | open Microsoft.AspNetCore.Components 7 | open MudBlazor 8 | open Fun.Result 9 | open Fun.Blazor 10 | 11 | type Demo = { Name: string; Age: int; Birthday: DateTime } 12 | 13 | type DataGrid = 14 | static member Create() = 15 | html.inject ( 16 | "data-grid-demo", 17 | fun (hook: IComponentHook) -> 18 | let items = cval LoadingState.NotStartYet 19 | 20 | hook.AddFirstAfterRenderTask(fun () -> task { 21 | items.Publish LoadingState.start 22 | 23 | do! Task.Delay 1000 24 | 25 | [ 26 | for i in 1..50 do 27 | { 28 | Name = $"name-{i}" 29 | Age = Random.Shared.Next(1, 100) 30 | Birthday = DateTime.Now.AddYears(Random.Shared.Next(1, 50)) 31 | } 32 | ] 33 | |> LoadingState.Loaded 34 | |> items.Publish 35 | }) 36 | 37 | adapt { 38 | let! items = items 39 | MudDataGrid'' { 40 | Items(defaultArg items.Value List.empty) 41 | Loading items.IsLoadingNow 42 | SortMode SortMode.Single 43 | Filterable 44 | FilterMode DataGridFilterMode.ColumnFilterRow 45 | Columns [| 46 | PropertyColumn'' { Property(fun x -> x.Name) } 47 | PropertyColumn'' { Property(fun x -> x.Age) } 48 | PropertyColumn'' { Property(fun x -> x.Birthday) } 49 | |] 50 | } 51 | } 52 | ) 53 | 54 | [] 55 | [] 56 | type DataGridDemo() = 57 | inherit FunComponent() 58 | override _.Render() = 59 | // Demo how to use module or static method to provide component of view instead of use the class based 60 | DataGrid.Create() 61 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth.Client/Pages/ThreejsDemo.fs: -------------------------------------------------------------------------------- 1 | namespace MudBlazorThreeAuth.Client.Pages 2 | 3 | open System 4 | open Microsoft.AspNetCore.Authorization 5 | open Microsoft.AspNetCore.Components 6 | open Microsoft.AspNetCore.Components.Web 7 | open Fun.Result 8 | open Fun.Blazor 9 | open MudBlazor 10 | open HomagGroup.Blazor3D 11 | open HomagGroup.Blazor3D.Enums 12 | open HomagGroup.Blazor3D.Maths 13 | open HomagGroup.Blazor3D.Scenes 14 | open HomagGroup.Blazor3D.Lights 15 | open HomagGroup.Blazor3D.Objects 16 | open HomagGroup.Blazor3D.Viewers 17 | open HomagGroup.Blazor3D.Settings 18 | open HomagGroup.Blazor3D.Geometires 19 | 20 | // https://github.com/HomagGroup/Blazor3D?tab=readme-ov-file#with-custom-scene 21 | // Try to use it with their example doc and use class based style 22 | [] 23 | // If user is not login to access this page then will redirect user to login page automatically 24 | [] 25 | [] 26 | type ThreejsDemo() as this = 27 | inherit FunComponent() 28 | 29 | let mutable viewerRef = Option.None 30 | let mutable isLoading = false 31 | 32 | let viewSettings = ViewerSettings(ContainerId = "demo") 33 | 34 | let scene = 35 | let scene = Scene() 36 | scene.Add(AmbientLight()) 37 | scene.Add(PointLight(Position = Vector3(X = 1, Y = 3, Z = 0))) 38 | scene 39 | 40 | let loadStl (url) = task { 41 | isLoading <- true 42 | 43 | try 44 | match viewerRef with 45 | | Some viewerRef -> 46 | let settings = ImportSettings(Format = Import3DFormats.Stl, FileURL = url) 47 | do! viewerRef.Import3DModelAsync(settings) |> Task.map ignore 48 | | _ -> () 49 | 50 | with ex -> 51 | this.Snackbar.Add(ex.Message, severity = Severity.Error) |> ignore 52 | 53 | isLoading <- false 54 | } 55 | 56 | let addRandomCube () = task { 57 | match viewerRef with 58 | | Some viewerRef -> 59 | scene.Add( 60 | Mesh( 61 | Geometry = BoxGeometry(Width = 1, Height = 1, Depth = 1), 62 | Position = Vector3(X = Random.Shared.Next(-5, 5), Y = Random.Shared.Next(-5, 5), Z = Random.Shared.Next(-5, 5)) 63 | ) 64 | ) 65 | do! viewerRef.UpdateScene() 66 | 67 | | _ -> () 68 | } 69 | 70 | [] 71 | member val Snackbar: ISnackbar = Unchecked.defaultof with get, set 72 | 73 | override _.Render() = fragment { 74 | PageTitle'' { "Threejs" } 75 | SectionContent'' { 76 | SectionName "header" 77 | h1 { "Threejs" } 78 | } 79 | div { 80 | MudButtonGroup'' { 81 | Variant Variant.Outlined 82 | MudButton'' { 83 | StartIcon Icons.Material.Filled.Add 84 | OnClick(fun _ -> addRandomCube ()) 85 | "Add cube" 86 | } 87 | MudButton'' { 88 | StartIcon Icons.Material.Filled.Download 89 | OnClick(fun _ -> loadStl "https://threejs.org/examples/models/stl/ascii/slotted_disk.stl") 90 | "Load slotted_disk stl model" 91 | } 92 | } 93 | } 94 | region { if isLoading then MudProgressLinear'' { Indeterminate } } 95 | Viewer'' { 96 | Scene scene 97 | ViewerSettings viewSettings 98 | ref (fun x -> viewerRef <- Some x) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth.Client/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "MudBlazorThreeAuth.Client": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "applicationUrl": "https://localhost:52057;http://localhost:52058" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth.Client/Startup.fs: -------------------------------------------------------------------------------- 1 | #nowarn "0020" 2 | 3 | open System 4 | open MudBlazor.Services 5 | open Microsoft.AspNetCore.Components.WebAssembly.Hosting 6 | 7 | let builder = WebAssemblyHostBuilder.CreateDefault(Environment.GetCommandLineArgs()) 8 | 9 | builder.Services.AddMudServices() 10 | 11 | builder.Build().RunAsync() 12 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "MudBlazorThreeAuth", "MudBlazorThreeAuth\MudBlazorThreeAuth.fsproj", "{67B635AF-5096-4009-8F6D-E11797B1100E}" 7 | EndProject 8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "MudBlazorThreeAuth.Client", "MudBlazorThreeAuth.Client\MudBlazorThreeAuth.Client.fsproj", "{7E9C7EDB-8AF5-4BCA-B990-31F6B05CBB79}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(SolutionProperties) = preSolution 16 | HideSolutionNode = FALSE 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {67B635AF-5096-4009-8F6D-E11797B1100E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {67B635AF-5096-4009-8F6D-E11797B1100E}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {67B635AF-5096-4009-8F6D-E11797B1100E}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {67B635AF-5096-4009-8F6D-E11797B1100E}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {7E9C7EDB-8AF5-4BCA-B990-31F6B05CBB79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {7E9C7EDB-8AF5-4BCA-B990-31F6B05CBB79}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {7E9C7EDB-8AF5-4BCA-B990-31F6B05CBB79}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {7E9C7EDB-8AF5-4BCA-B990-31F6B05CBB79}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth/Components/App.fs: -------------------------------------------------------------------------------- 1 | namespace MudBlazorThreeAuth.Components 2 | 3 | open Fun.Blazor 4 | open Microsoft.AspNetCore.Components.Web 5 | 6 | type App() = 7 | inherit FunComponent() 8 | 9 | override _.Render() = fragment { 10 | doctype "html" 11 | html' { 12 | lang "EN" 13 | head { 14 | baseUrl "/" 15 | meta { charset "utf-8" } 16 | meta { 17 | name "viewport" 18 | content "width=device-width, initial-scale=1.0" 19 | } 20 | link { 21 | rel "icon" 22 | type' "image/png" 23 | href "favicon.png" 24 | } 25 | styleElt { 26 | ruleset ".active" { 27 | color "green" 28 | fontWeightBold 29 | } 30 | } 31 | stylesheet "https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" 32 | stylesheet "_content/MudBlazor/MudBlazor.min.css" 33 | HeadOutlet'' 34 | } 35 | body { 36 | html.blazor () 37 | script { src "_framework/blazor.web.js" } 38 | script { src "_content/MudBlazor/MudBlazor.min.js" } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth/Components/Layout/EmptyLayout.fs: -------------------------------------------------------------------------------- 1 | namespace MudBlazorThreeAuth.Components.Layout 2 | 3 | open Microsoft.AspNetCore.Components 4 | open MudBlazor 5 | open Fun.Blazor 6 | open Microsoft.AspNetCore.Components.Web 7 | 8 | // This can be used for login page etc. 9 | type EmptyLayout() as this = 10 | inherit LayoutComponentBase() 11 | 12 | let content = div { 13 | // Make those interactive, because by default it is static in this context 14 | MudThemeProvider'' { renderMode RenderMode.InteractiveAuto } 15 | MudPopoverProvider'' { renderMode RenderMode.InteractiveAuto } 16 | MudSnackbarProvider'' { renderMode RenderMode.InteractiveAuto } 17 | MudDialogProvider'' { renderMode RenderMode.InteractiveAuto } 18 | 19 | main { this.Body } 20 | } 21 | 22 | override _.BuildRenderTree(builder) = content.Invoke(this, builder, 0) |> ignore 23 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth/Components/Layout/MainLayout.fs: -------------------------------------------------------------------------------- 1 | namespace MudBlazorThreeAuth.Components.Layout 2 | 3 | open Microsoft.AspNetCore.Components 4 | open MudBlazor 5 | open Fun.Blazor 6 | open Microsoft.AspNetCore.Components.Web 7 | 8 | type MainLayout() as this = 9 | inherit LayoutComponentBase() 10 | 11 | let content = div { 12 | NavMenu.Create() 13 | SectionOutlet'' { SectionName "header" } 14 | 15 | // Make those interactive, because by default it is static in this context 16 | MudThemeProvider'' { renderMode RenderMode.InteractiveAuto } 17 | MudPopoverProvider'' { renderMode RenderMode.InteractiveAuto } 18 | MudSnackbarProvider'' { renderMode RenderMode.InteractiveAuto } 19 | MudDialogProvider'' { renderMode RenderMode.InteractiveAuto } 20 | 21 | main { this.Body } 22 | } 23 | 24 | override _.BuildRenderTree(builder) = content.Invoke(this, builder, 0) |> ignore 25 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth/Components/Layout/NavMenu.fs: -------------------------------------------------------------------------------- 1 | namespace MudBlazorThreeAuth.Components.Layout 2 | 3 | open Microsoft.AspNetCore.Components.Routing 4 | open Microsoft.AspNetCore.Components.Authorization 5 | open Fun.Blazor 6 | open MudBlazor 7 | 8 | type NavMenu = 9 | static member private LogoutBtn = 10 | html.inject (fun (auth: AuthenticationStateProvider) -> task { 11 | let! state = auth.GetAuthenticationStateAsync() 12 | if state.User <> null && state.User.Identity <> null && state.User.Identity.IsAuthenticated then 13 | return form { 14 | method "post" 15 | action "/api/logout" 16 | MudButton'' { 17 | Color Color.Warning 18 | Variant Variant.Outlined 19 | ButtonType ButtonType.Submit 20 | "Logout " + state.User.Identity.Name 21 | } 22 | } 23 | else 24 | return html.none 25 | }) 26 | 27 | static member Create() = nav { 28 | style { 29 | displayFlex 30 | alignItemsCenter 31 | gap 10 32 | } 33 | NavLink'' { 34 | href "" 35 | Match NavLinkMatch.All 36 | "Home" 37 | } 38 | NavLink'' { 39 | href "counter" 40 | "Counter" 41 | } 42 | NavLink'' { 43 | href "data-grid-demo" 44 | "Data Grid Demo" 45 | } 46 | NavLink'' { 47 | href "threejs" 48 | "Threejs Demo" 49 | } 50 | NavLink'' { 51 | href "form" 52 | "Form demo" 53 | } 54 | NavMenu.LogoutBtn 55 | } 56 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth/Components/Pages/Account/Login.fs: -------------------------------------------------------------------------------- 1 | namespace MudBlazorThreeAuth.Components.Account.Pages 2 | 3 | open System 4 | open Microsoft.AspNetCore.Components 5 | open Fun.Blazor 6 | open MudBlazor 7 | open MudBlazorThreeAuth.Components.Layout 8 | 9 | [] 10 | [)>] 11 | type Login() = 12 | inherit FunComponent() 13 | 14 | [] 15 | member val ReturnUrl = String.Empty with get, set 16 | 17 | override this.Render() = div { 18 | style { 19 | displayFlex 20 | flexDirectionColumn 21 | alignItemsCenter 22 | justifyContentCenter 23 | height "100vh" 24 | } 25 | MudThemeProvider'' 26 | MudPaper'' { 27 | style { 28 | width 300 29 | padding 12 30 | } 31 | Elevation 2 32 | MudForm'' { 33 | style { gap 8 } 34 | action $"/api/login?returnUrl={this.ReturnUrl}" 35 | method "post" 36 | MudTextField'' { 37 | name "User" 38 | Placeholder "User Name" 39 | } 40 | MudTextField'' { 41 | name "Password" 42 | Placeholder "Password" 43 | InputType InputType.Password 44 | } 45 | MudButton'' { 46 | ButtonType ButtonType.Submit 47 | Color Color.Primary 48 | Variant Variant.Filled 49 | "Login" 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth/Components/Pages/From.fs: -------------------------------------------------------------------------------- 1 | namespace MudBlazorThreeAuth.Components.Pages 2 | 3 | open System 4 | open System.Threading.Tasks 5 | open Microsoft.AspNetCore.Components 6 | open Microsoft.AspNetCore.Components.Web 7 | open Microsoft.AspNetCore.Components.Forms 8 | open Fun.Blazor 9 | 10 | [] 11 | [] 12 | type Form() as this = 13 | inherit FunComponent() 14 | 15 | let mutable isSubmitting = false 16 | 17 | [] 18 | member val Query: string = null with get, set 19 | 20 | member _.Submit() = task { 21 | isSubmitting <- true 22 | this.StateHasChanged() 23 | 24 | do! Task.Delay 1000 25 | isSubmitting <- false 26 | this.StateHasChanged() 27 | } 28 | 29 | member _.FormView = form { 30 | onsubmit (ignore >> this.Submit) 31 | method "post" 32 | dataEnhance 33 | formName "person-info" 34 | html.blazor () 35 | input { 36 | type' InputTypes.text 37 | name (nameof this.Query) 38 | value this.Query 39 | } 40 | button { 41 | type' InputTypes.submit 42 | "Submit" 43 | } 44 | region { 45 | if String.IsNullOrEmpty this.Query || this.Query.Length > 5 then 46 | div { 47 | style { color "red" } 48 | $"{nameof this.Query} is not valid" 49 | } 50 | } 51 | } 52 | 53 | override _.Render() = fragment { 54 | PageTitle'' { "Form demo" } 55 | SectionContent'' { 56 | SectionName "header" 57 | h1 { "Form demo" } 58 | } 59 | div { 60 | style { height "100vh" } 61 | a { 62 | href "form?#person-info" 63 | "check the form" 64 | } 65 | } 66 | h2 { 67 | id "person-info" 68 | "person info" 69 | } 70 | this.FormView 71 | region { if isSubmitting then progress.create () } 72 | div { style { height "100vh" } } 73 | } 74 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth/Components/Pages/Home.fs: -------------------------------------------------------------------------------- 1 | namespace MudBlazorThreeAuth.Components.Pages 2 | 3 | open System 4 | open System.Threading.Tasks 5 | open Microsoft.AspNetCore.Components 6 | open Microsoft.AspNetCore.Components.Web 7 | open Fun.Blazor 8 | open MudBlazor 9 | 10 | [] 11 | [] 12 | type Home() as this = 13 | inherit FunComponent() 14 | 15 | let mutable items = [] 16 | 17 | [] 18 | member val query = Nullable() with get, set 19 | 20 | member _.FilteredItems = 21 | if this.query.HasValue then 22 | items |> Seq.filter (fun x -> x > this.query.Value) 23 | else 24 | items 25 | 26 | member _.MainContent = fragment { 27 | div { 28 | a { 29 | style { color (if this.query.HasValue then "hotpink" else "grey") } 30 | href "?query=3" 31 | "filter: bigger than 3" 32 | } 33 | } 34 | ul { 35 | for i in this.FilteredItems do 36 | li { 37 | style { color "green" } 38 | $"item {i}" 39 | } 40 | } 41 | } 42 | 43 | override _.OnInitializedAsync() = task { 44 | do! Task.Delay 2000 45 | items <- [ 1..5 ] 46 | this.StateHasChanged() 47 | 48 | do! Task.Delay 1000 49 | items <- [ 1..10 ] 50 | this.StateHasChanged() 51 | } 52 | 53 | override _.Render() = fragment { 54 | PageTitle'' { "Home" } 55 | SectionContent'' { 56 | SectionName "header" 57 | h1 { "Home" } 58 | } 59 | region { 60 | if items.IsEmpty then 61 | MudProgressLinear'' { Indeterminate } 62 | else 63 | this.MainContent 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth/Components/Routes.fs: -------------------------------------------------------------------------------- 1 | namespace MudBlazorThreeAuth.Components 2 | 3 | open System.Reflection 4 | open Microsoft.AspNetCore.Components.Authorization 5 | open Fun.Blazor 6 | open MudBlazorThreeAuth.Components.Layout 7 | 8 | type Routes() = 9 | inherit FunComponent() 10 | 11 | override _.Render() = Router'' { 12 | AppAssembly(Assembly.GetExecutingAssembly()) 13 | Found(fun routeData -> AuthorizeRouteView'' { 14 | RouteData routeData 15 | DefaultLayout typeof 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth/Endpoints/Account.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module MudBlazorThreeAuth.Endpoints.AccountExtensions 3 | 4 | open System 5 | open System.Security.Claims 6 | open Microsoft.AspNetCore.Builder 7 | open Microsoft.AspNetCore.Routing 8 | open Microsoft.AspNetCore.Http 9 | open Microsoft.AspNetCore.Authentication 10 | open Microsoft.AspNetCore.Authentication.Cookies 11 | 12 | // I am using asp.net core minimal APIs here, you can use others like controller/giraffe etc. 13 | type IEndpointRouteBuilder with 14 | 15 | member endpoint.MapAccountApi() = 16 | endpoint.MapPost( 17 | "/api/login", 18 | Func<_, _, _>(fun (ctx: HttpContext) (returnUrl: string) -> task { 19 | let user = ctx.Request.Form["User"] 20 | let password = ctx.Request.Form["Password"] 21 | 22 | // Handle authentication 23 | 24 | let issuer = "demo" 25 | let claims = [ Claim(ClaimTypes.Name, user, ClaimValueTypes.String, issuer) ] 26 | 27 | let userIdentity = 28 | ClaimsIdentity( 29 | claims, 30 | CookieAuthenticationDefaults.AuthenticationScheme, 31 | ClaimsIdentity.DefaultNameClaimType, 32 | ClaimsIdentity.DefaultRoleClaimType 33 | ) 34 | 35 | let userPrincipal = ClaimsPrincipal(userIdentity) 36 | 37 | let authProperties = 38 | AuthenticationProperties(ExpiresUtc = DateTimeOffset.UtcNow.AddDays(1), IsPersistent = true, AllowRefresh = false) 39 | 40 | do! ctx.SignInAsync(userPrincipal, authProperties) 41 | 42 | if String.IsNullOrEmpty returnUrl |> not then 43 | return Results.Redirect(returnUrl) 44 | else 45 | return Results.Ok() 46 | }) 47 | ) 48 | |> ignore 49 | 50 | endpoint.MapPost( 51 | "/api/logout", 52 | Func<_, _>(fun (ctx: HttpContext) -> task { 53 | do! ctx.SignOutAsync() 54 | return Results.Redirect("/") 55 | }) 56 | ) 57 | |> ignore 58 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth/MudBlazorThreeAuth.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:57640/", 7 | "sslPort": 44319 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "MudBlazorThreeAuth": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:51871;http://localhost:51872" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth/Startup.fs: -------------------------------------------------------------------------------- 1 | #nowarn "0020" 2 | 3 | open System 4 | open Microsoft.AspNetCore.Builder 5 | open Microsoft.Extensions.Hosting 6 | open Microsoft.Extensions.DependencyInjection 7 | open MudBlazor.Services 8 | open MudBlazorThreeAuth.Endpoints 9 | open MudBlazorThreeAuth.Components 10 | open MudBlazorThreeAuth.Client.Pages 11 | 12 | 13 | let builder = WebApplication.CreateBuilder(Environment.GetCommandLineArgs()) 14 | 15 | builder.Services.AddRazorComponents().AddInteractiveServerComponents().AddInteractiveWebAssemblyComponents() 16 | 17 | builder.Services.AddFunBlazorServer() 18 | builder.Services.AddMudServices() 19 | builder.Services.AddAuthentication().AddCookie() 20 | builder.Services.AddAuthorization() 21 | 22 | let app = builder.Build() 23 | 24 | app.UseStaticFiles() 25 | app.UseAuthentication().UseAuthorization() 26 | app.UseAntiforgery() 27 | 28 | app.MapAccountApi() 29 | 30 | app 31 | .MapRazorComponents() 32 | .AddInteractiveServerRenderMode() 33 | .AddInteractiveWebAssemblyRenderMode() 34 | .AddAdditionalAssemblies(typeof.Assembly) 35 | 36 | app.Run() 37 | -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/MudBlazorThreeAuth/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaveOftime/Fun.Blazor.Samples/5b8f5371390e3376e3e3db545652f5416f99e566/samples/MudBlazorThreeAuth/MudBlazorThreeAuth/wwwroot/favicon.png -------------------------------------------------------------------------------- /samples/MudBlazorThreeAuth/README.md: -------------------------------------------------------------------------------- 1 | This project is trying to follow the default blazor official template 2 | 3 | 4 | ## Dev 5 | 6 | Open terminal and run 7 | 8 | dotnet run --project .\MudBlazorThreeAuth\MudBlazorThreeAuth.fsproj 9 | 10 | 11 | ## Blazor3D 12 | 13 | This is not a popular binding in blazor community, so the Fun.Blazor binding is not provided for it by default, but we can create it by [cli](https://slaveoftime.github.io/Fun.Blazor.Docs/Docs/Tooling/Code-Generation): 14 | 15 | ```bash 16 | cd .\MudBlazorThreeAuth.Client\ 17 | dotnet fun-blazor generate .\MudBlazorThreeAuth.Client.fsproj 18 | ``` -------------------------------------------------------------------------------- /templates.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | dotnet templates for Fun.Blazor 6 | 7 | 8 | netstandard2.0 9 | true 10 | false 11 | content 12 | 13 | 14 | Template 15 | Fun.Blazor.Templates 16 | 4.1.2 17 | templates;blazor;fun-blazor;fsharp 18 | https://github.com/slaveOftime/Fun.Blazor.Samples 19 | git 20 | https://github.com/slaveOftime/Fun.Blazor.Samples 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /templates/BlazorApp/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "identity": "Fun.Blazor.App", 4 | "shortName": "fun-blazor", 5 | "name": "Fun.Blazor.App", 6 | "author": "slaveoftime", 7 | "classifications": [ 8 | "Web", 9 | "F#", 10 | "blazor", 11 | "Fun.Blazor" 12 | ], 13 | "type": { 14 | "language": "F#" 15 | }, 16 | "tags": { 17 | "type": "project", 18 | "language": "F#" 19 | }, 20 | "sourceName": "BlazorApp", 21 | "preferNameDirectory": true 22 | } -------------------------------------------------------------------------------- /templates/BlazorApp/BlazorApp.Client/BlazorApp.Client.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /templates/BlazorApp/BlazorApp.Client/Pages/Counter.fs: -------------------------------------------------------------------------------- 1 | namespace BlazorApp.Client.Pages 2 | 3 | open Microsoft.AspNetCore.Components 4 | open Microsoft.AspNetCore.Components.Web 5 | open Fun.Blazor 6 | 7 | [] 8 | [] 9 | type Counter() = 10 | inherit FunComponent() 11 | 12 | let mutable count = 0 13 | 14 | override _.Render() = fragment { 15 | PageTitle'' { "Counter" } 16 | SectionContent'' { 17 | SectionName "header" 18 | h1 { "Counter" } 19 | } 20 | p { $"Current count: {count}" } 21 | button { 22 | style { color "green" } 23 | onclick (fun _ -> count <- count + 1) 24 | "Click me" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /templates/BlazorApp/BlazorApp.Client/Startup.fs: -------------------------------------------------------------------------------- 1 | #nowarn "0020" 2 | 3 | open System 4 | open Microsoft.AspNetCore.Components.WebAssembly.Hosting 5 | 6 | let builder = WebAssemblyHostBuilder.CreateDefault(Environment.GetCommandLineArgs()) 7 | 8 | builder.Build().RunAsync() 9 | -------------------------------------------------------------------------------- /templates/BlazorApp/BlazorApp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BlazorApp", "BlazorApp\BlazorApp.fsproj", "{67B635AF-5096-4009-8F6D-E11797B1100E}" 7 | EndProject 8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BlazorApp.Client", "BlazorApp.Client\BlazorApp.Client.fsproj", "{7E9C7EDB-8AF5-4BCA-B990-31F6B05CBB79}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(SolutionProperties) = preSolution 16 | HideSolutionNode = FALSE 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {67B635AF-5096-4009-8F6D-E11797B1100E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {67B635AF-5096-4009-8F6D-E11797B1100E}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {67B635AF-5096-4009-8F6D-E11797B1100E}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {67B635AF-5096-4009-8F6D-E11797B1100E}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {7E9C7EDB-8AF5-4BCA-B990-31F6B05CBB79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {7E9C7EDB-8AF5-4BCA-B990-31F6B05CBB79}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {7E9C7EDB-8AF5-4BCA-B990-31F6B05CBB79}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {7E9C7EDB-8AF5-4BCA-B990-31F6B05CBB79}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /templates/BlazorApp/BlazorApp/BlazorApp.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /templates/BlazorApp/BlazorApp/Components/App.fs: -------------------------------------------------------------------------------- 1 | namespace BlazorApp.Components 2 | 3 | open Fun.Blazor 4 | open Microsoft.AspNetCore.Components.Web 5 | 6 | 7 | type App() = 8 | inherit FunComponent() 9 | 10 | override _.Render() = fragment { 11 | doctype "html" 12 | html' { 13 | lang "EN" 14 | head { 15 | baseUrl "/" 16 | meta { charset "utf-8" } 17 | meta { 18 | name "viewport" 19 | content "width=device-width, initial-scale=1.0" 20 | } 21 | link { 22 | rel "icon" 23 | type' "image/png" 24 | href "favicon.png" 25 | } 26 | styleElt { 27 | ruleset ".active" { 28 | color "green" 29 | fontWeightBold 30 | } 31 | } 32 | HeadOutlet'' 33 | } 34 | body { 35 | html.blazor () 36 | script { src "_framework/blazor.web.js" } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /templates/BlazorApp/BlazorApp/Components/Layout/MainLayout.fs: -------------------------------------------------------------------------------- 1 | namespace BlazorApp.Components.Layout 2 | 3 | open Microsoft.AspNetCore.Components 4 | open Fun.Blazor 5 | 6 | type MainLayout() as this = 7 | inherit LayoutComponentBase() 8 | 9 | let content = div { 10 | NavMenu.Create() 11 | SectionOutlet'' { SectionName "header" } 12 | main { this.Body } 13 | } 14 | 15 | override _.BuildRenderTree(builder) = content.Invoke(this, builder, 0) |> ignore 16 | -------------------------------------------------------------------------------- /templates/BlazorApp/BlazorApp/Components/Layout/NavMenu.fs: -------------------------------------------------------------------------------- 1 | namespace BlazorApp.Components.Layout 2 | 3 | open Microsoft.AspNetCore.Components.Routing 4 | open Fun.Blazor 5 | 6 | 7 | [] 8 | module Extensions = 9 | open Fun.Blazor.Operators 10 | 11 | type NavLink' with 12 | 13 | [] 14 | member inline _.Href([] render: AttrRenderFragment, url: string) = render ==> ("href" => url) 15 | 16 | let NavLink'' = NavLink'() 17 | 18 | 19 | type NavMenu = 20 | static member Create() = nav { 21 | style { 22 | displayFlex 23 | alignItemsCenter 24 | gap 10 25 | } 26 | NavLink'' { 27 | Href "" 28 | Match NavLinkMatch.All 29 | "Home" 30 | } 31 | NavLink'' { 32 | Href "counter" 33 | "Counter" 34 | } 35 | NavLink'' { 36 | Href "form" 37 | "Form demo" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /templates/BlazorApp/BlazorApp/Components/Pages/Form.fs: -------------------------------------------------------------------------------- 1 | namespace BlazorApp.Components.Pages 2 | 3 | open System 4 | open System.Threading.Tasks 5 | open Microsoft.AspNetCore.Components 6 | open Microsoft.AspNetCore.Components.Web 7 | open Microsoft.AspNetCore.Components.Forms 8 | open Fun.Blazor 9 | 10 | [] 11 | [] 12 | type Form() as this = 13 | inherit FunComponent() 14 | 15 | let mutable isSubmitting = false 16 | 17 | [] 18 | member val Query: string = null with get, set 19 | 20 | member _.Submit() = task { 21 | isSubmitting <- true 22 | this.StateHasChanged() 23 | 24 | do! Task.Delay 1000 25 | isSubmitting <- false 26 | this.StateHasChanged() 27 | } 28 | 29 | member _.FormView = form { 30 | onsubmit (ignore >> this.Submit) 31 | method "post" 32 | dataEnhance 33 | formName "person-info" 34 | html.blazor () 35 | input { 36 | type' InputTypes.text 37 | name (nameof this.Query) 38 | value this.Query 39 | } 40 | button { 41 | type' InputTypes.submit 42 | "Submit" 43 | } 44 | region { 45 | if String.IsNullOrEmpty this.Query || this.Query.Length > 5 then 46 | div { 47 | style { color "red" } 48 | $"{nameof this.Query} is not valid" 49 | } 50 | } 51 | } 52 | 53 | override _.Render() = fragment { 54 | PageTitle'' { "Form demo" } 55 | SectionContent'' { 56 | SectionName "header" 57 | h1 { "Form demo" } 58 | } 59 | div { 60 | style { height "100vh" } 61 | a { 62 | href "form?#person-info" 63 | "check the form" 64 | } 65 | } 66 | h2 { 67 | id "person-info" 68 | "person info" 69 | } 70 | this.FormView 71 | region { if isSubmitting then progress.create () } 72 | div { style { height "100vh" } } 73 | } 74 | -------------------------------------------------------------------------------- /templates/BlazorApp/BlazorApp/Components/Pages/Home.fs: -------------------------------------------------------------------------------- 1 | namespace BlazorApp.Components.Pages 2 | 3 | open System 4 | open System.Threading.Tasks 5 | open Microsoft.AspNetCore.Components 6 | open Microsoft.AspNetCore.Components.Web 7 | open Fun.Blazor 8 | 9 | [] 10 | [] 11 | type Home() as this = 12 | inherit FunComponent() 13 | 14 | let mutable items = [] 15 | 16 | [] 17 | member val query = Nullable() with get, set 18 | 19 | member _.FilteredItems = 20 | if this.query.HasValue then 21 | items |> Seq.filter (fun x -> x > this.query.Value) 22 | else 23 | items 24 | 25 | member _.MainContent = fragment { 26 | div { 27 | if this.query.HasValue then 28 | a { 29 | href "?query=" 30 | "clear filter" 31 | } 32 | else 33 | a { 34 | style { color (if this.query.HasValue then "hotpink" else "grey") } 35 | href "?query=3" 36 | "filter: bigger than 3" 37 | } 38 | } 39 | ul { 40 | for i in this.FilteredItems do 41 | li { 42 | style { color "green" } 43 | $"item {i}" 44 | } 45 | } 46 | } 47 | 48 | override _.OnInitializedAsync() = task { 49 | do! Task.Delay 1000 50 | items <- [ 1..5 ] 51 | this.StateHasChanged() 52 | 53 | do! Task.Delay 1000 54 | items <- [ 1..10 ] 55 | this.StateHasChanged() 56 | } 57 | 58 | override _.Render() = fragment { 59 | PageTitle'' { "Home" } 60 | SectionContent'' { 61 | SectionName "header" 62 | h1 { "Home" } 63 | } 64 | region { if items.IsEmpty then progress.create () else this.MainContent } 65 | } 66 | -------------------------------------------------------------------------------- /templates/BlazorApp/BlazorApp/Components/Routes.fs: -------------------------------------------------------------------------------- 1 | namespace BlazorApp.Components 2 | 3 | open System.Reflection 4 | open Fun.Blazor 5 | open BlazorApp.Components.Layout 6 | 7 | type Routes() = 8 | inherit FunComponent() 9 | 10 | override _.Render() = Router'' { 11 | AppAssembly(Assembly.GetExecutingAssembly()) 12 | Found(fun routeData -> RouteView'' { 13 | RouteData routeData 14 | DefaultLayout typeof 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /templates/BlazorApp/BlazorApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:57640/", 7 | "sslPort": 44319 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "BlazorApp": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:51871;http://localhost:51872" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /templates/BlazorApp/BlazorApp/Startup.fs: -------------------------------------------------------------------------------- 1 | #nowarn "0020" 2 | 3 | open System 4 | open Microsoft.AspNetCore.Builder 5 | open Microsoft.Extensions.Hosting 6 | open Microsoft.Extensions.DependencyInjection 7 | open BlazorApp.Components 8 | open BlazorApp.Client.Pages 9 | 10 | let builder = WebApplication.CreateBuilder(Environment.GetCommandLineArgs()) 11 | 12 | builder.Services.AddRazorComponents() 13 | .AddInteractiveServerComponents() 14 | .AddInteractiveWebAssemblyComponents() 15 | 16 | builder.Services.AddFunBlazorServer() 17 | 18 | 19 | let app = builder.Build() 20 | 21 | app.UseStaticFiles() 22 | app.UseAntiforgery() 23 | 24 | app.MapRazorComponents() 25 | .AddInteractiveServerRenderMode() 26 | .AddInteractiveWebAssemblyRenderMode() 27 | .AddAdditionalAssemblies(typeof.Assembly); 28 | 29 | app.Run() 30 | -------------------------------------------------------------------------------- /templates/BlazorApp/BlazorApp/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaveOftime/Fun.Blazor.Samples/5b8f5371390e3376e3e3db545652f5416f99e566/templates/BlazorApp/BlazorApp/wwwroot/favicon.png -------------------------------------------------------------------------------- /templates/BlazorApp/README.md: -------------------------------------------------------------------------------- 1 | This project is trying to follow the default blazor official template 2 | 3 | ```bash 4 | dotnet run --project .\BlazorApp\BlazorApp.fsproj 5 | ``` 6 | -------------------------------------------------------------------------------- /templates/BlazorServerApp/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "identity": "Fun.Blazor.Server.App", 4 | "shortName": "fun-server-app", 5 | "name": "Fun.Blazor.Server.App", 6 | "author": "slaveoftime", 7 | "classifications": [ 8 | "Web", 9 | "F#", 10 | "blazor", 11 | "Fun.Blazor" 12 | ], 13 | "type": { 14 | "language": "F#" 15 | }, 16 | "tags": { 17 | "type": "project", 18 | "language": "F#" 19 | }, 20 | "sourceName": "BlazorServerApp", 21 | "preferNameDirectory": true 22 | } -------------------------------------------------------------------------------- /templates/BlazorServerApp/BlazorServerApp.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /templates/BlazorServerApp/Components/App.fs: -------------------------------------------------------------------------------- 1 | namespace BlazorServerApp.Components 2 | 3 | open Fun.Blazor 4 | open Microsoft.AspNetCore.Components.Web 5 | 6 | 7 | type App() = 8 | inherit FunComponent() 9 | 10 | override _.Render() = fragment { 11 | doctype "html" 12 | html' { 13 | lang "EN" 14 | head { 15 | baseUrl "/" 16 | meta { charset "utf-8" } 17 | meta { 18 | name "viewport" 19 | content "width=device-width, initial-scale=1.0" 20 | } 21 | link { 22 | rel "icon" 23 | type' "image/png" 24 | href "favicon.png" 25 | } 26 | styleElt { 27 | ruleset ".active" { 28 | color "green" 29 | fontWeightBold 30 | } 31 | } 32 | HeadOutlet'' { interactiveServer } 33 | } 34 | body { 35 | html.blazor (renderMode = RenderMode.InteractiveServer) 36 | script { src "_framework/blazor.server.js" } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /templates/BlazorServerApp/Components/Layout/MainLayout.fs: -------------------------------------------------------------------------------- 1 | namespace BlazorServerApp.Components.Layout 2 | 3 | open Microsoft.AspNetCore.Components 4 | open Fun.Blazor 5 | 6 | type MainLayout() as this = 7 | inherit LayoutComponentBase() 8 | 9 | let content = div { 10 | NavMenu.Create() 11 | SectionOutlet'' { SectionName "header" } 12 | main { this.Body } 13 | } 14 | 15 | override _.BuildRenderTree(builder) = content.Invoke(this, builder, 0) |> ignore 16 | -------------------------------------------------------------------------------- /templates/BlazorServerApp/Components/Layout/NavMenu.fs: -------------------------------------------------------------------------------- 1 | namespace BlazorServerApp.Components.Layout 2 | 3 | open Microsoft.AspNetCore.Components.Routing 4 | open Fun.Blazor 5 | 6 | 7 | type NavMenu = 8 | static member Create() = nav { 9 | style { 10 | displayFlex 11 | alignItemsCenter 12 | gap 10 13 | } 14 | NavLink'' { 15 | href "" 16 | Match NavLinkMatch.All 17 | "Home" 18 | } 19 | NavLink'' { 20 | href "counter" 21 | "Counter" 22 | } 23 | NavLink'' { 24 | href "form" 25 | "Form demo" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /templates/BlazorServerApp/Components/Pages/Counter.fs: -------------------------------------------------------------------------------- 1 | namespace BlazorServerApp.Components.Pages 2 | 3 | open Microsoft.AspNetCore.Components 4 | open Microsoft.AspNetCore.Components.Web 5 | open Fun.Blazor 6 | 7 | [] 8 | type Counter() = 9 | inherit FunComponent() 10 | 11 | let mutable count = 0 12 | 13 | override _.Render() = fragment { 14 | PageTitle'' { "Counter" } 15 | SectionContent'' { 16 | SectionName "header" 17 | h1 { "Counter" } 18 | } 19 | p { $"Current count: {count}" } 20 | button { 21 | style { color "green" } 22 | onclick (fun _ -> count <- count + 1) 23 | "Click me" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /templates/BlazorServerApp/Components/Pages/Form.fs: -------------------------------------------------------------------------------- 1 | namespace BlazorServerApp.Components.Pages 2 | 3 | open System 4 | open System.Threading.Tasks 5 | open Microsoft.AspNetCore.Components 6 | open Microsoft.AspNetCore.Components.Web 7 | open Microsoft.AspNetCore.Components.Forms 8 | open Fun.Blazor 9 | 10 | [] 11 | type Form() as this = 12 | inherit FunComponent() 13 | 14 | let mutable isSubmitting = false 15 | 16 | [] 17 | member val Query: string = null with get, set 18 | 19 | member _.Submit() = task { 20 | isSubmitting <- true 21 | this.StateHasChanged() 22 | 23 | do! Task.Delay 1000 24 | isSubmitting <- false 25 | this.StateHasChanged() 26 | } 27 | 28 | member _.FormView = form { 29 | onsubmit (ignore >> this.Submit) 30 | html.blazor () 31 | input { 32 | value this.Query 33 | onchange (fun e -> this.Query <- string e.Value) 34 | } 35 | button { 36 | type' InputTypes.submit 37 | "Submit" 38 | } 39 | region { 40 | if String.IsNullOrEmpty this.Query || this.Query.Length > 5 then 41 | div { 42 | style { color "red" } 43 | $"{nameof this.Query} is not valid" 44 | } 45 | } 46 | } 47 | 48 | override _.Render() = fragment { 49 | PageTitle'' { "Form demo" } 50 | SectionContent'' { 51 | SectionName "header" 52 | h1 { "Form demo" } 53 | } 54 | div { 55 | style { height "100vh" } 56 | a { 57 | href "form?#person-info" 58 | "check the form" 59 | } 60 | } 61 | h2 { 62 | id "person-info" 63 | "person info" 64 | } 65 | this.FormView 66 | region { if isSubmitting then progress.create () } 67 | div { style { height "100vh" } } 68 | } 69 | -------------------------------------------------------------------------------- /templates/BlazorServerApp/Components/Pages/Home.fs: -------------------------------------------------------------------------------- 1 | namespace BlazorServerApp.Components.Pages 2 | 3 | open System 4 | open System.Threading.Tasks 5 | open Microsoft.AspNetCore.Components 6 | open Microsoft.AspNetCore.Components.Web 7 | open Fun.Blazor 8 | 9 | [] 10 | type Home() as this = 11 | inherit FunComponent() 12 | 13 | let mutable items = [] 14 | 15 | [] 16 | member val query = Nullable() with get, set 17 | 18 | member _.FilteredItems = 19 | if this.query.HasValue then 20 | items |> Seq.filter (fun x -> x > this.query.Value) 21 | else 22 | items 23 | 24 | member _.MainContent = fragment { 25 | div { 26 | if this.query.HasValue then 27 | a { 28 | href "?query=" 29 | "clear filter" 30 | } 31 | else 32 | a { 33 | style { color (if this.query.HasValue then "hotpink" else "grey") } 34 | href "?query=3" 35 | "filter: bigger than 3" 36 | } 37 | } 38 | ul { 39 | for i in this.FilteredItems do 40 | li { 41 | style { color "green" } 42 | $"item {i}" 43 | } 44 | } 45 | } 46 | 47 | override _.OnInitializedAsync() = task { 48 | do! Task.Delay 1000 49 | items <- [ 1..5 ] 50 | this.StateHasChanged() 51 | 52 | do! Task.Delay 1000 53 | items <- [ 1..10 ] 54 | this.StateHasChanged() 55 | } 56 | 57 | override _.Render() = fragment { 58 | PageTitle'' { "Home" } 59 | SectionContent'' { 60 | SectionName "header" 61 | h1 { "Home" } 62 | } 63 | region { if items.IsEmpty then progress { } else this.MainContent } 64 | } 65 | -------------------------------------------------------------------------------- /templates/BlazorServerApp/Components/Routes.fs: -------------------------------------------------------------------------------- 1 | namespace BlazorServerApp.Components 2 | 3 | open System.Reflection 4 | open Fun.Blazor 5 | open BlazorServerApp.Components.Layout 6 | 7 | type Routes() = 8 | inherit FunComponent() 9 | 10 | override _.Render() = Router'' { 11 | AppAssembly(Assembly.GetExecutingAssembly()) 12 | Found(fun routeData -> RouteView'' { 13 | RouteData routeData 14 | DefaultLayout typeof 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /templates/BlazorServerApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:57670/", 7 | "sslPort": 44719 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "BlazorServerApp": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:51671;http://localhost:51672" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /templates/BlazorServerApp/README.md: -------------------------------------------------------------------------------- 1 | This project is trying to follow the default blazor official template. 2 | This project is only using blazor server mode, which means it will use websocket. 3 | 4 | ```bash 5 | dotnet run 6 | ``` 7 | -------------------------------------------------------------------------------- /templates/BlazorServerApp/Startup.fs: -------------------------------------------------------------------------------- 1 | #nowarn "0020" 2 | 3 | open System 4 | open Microsoft.AspNetCore.Builder 5 | open Microsoft.Extensions.Hosting 6 | open Microsoft.Extensions.DependencyInjection 7 | open BlazorServerApp.Components 8 | 9 | let builder = WebApplication.CreateBuilder(Environment.GetCommandLineArgs()) 10 | 11 | builder.Services.AddRazorComponents().AddInteractiveServerComponents() 12 | 13 | builder.Services.AddFunBlazorServer() 14 | 15 | 16 | let app = builder.Build() 17 | 18 | app.UseHttpsRedirection() 19 | app.UseStaticFiles() 20 | app.UseAntiforgery() 21 | 22 | app.MapRazorComponents().AddInteractiveServerRenderMode() 23 | 24 | app.Run() 25 | -------------------------------------------------------------------------------- /templates/BlazorServerApp/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaveOftime/Fun.Blazor.Samples/5b8f5371390e3376e3e3db545652f5416f99e566/templates/BlazorServerApp/wwwroot/favicon.png -------------------------------------------------------------------------------- /templates/SSRApp/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "identity": "Fun.SSR.App", 4 | "shortName": "fun-ssr", 5 | "name": "Fun.SSR.App", 6 | "author": "slaveoftime", 7 | "classifications": [ 8 | "Web", 9 | "F#", 10 | "blazor", 11 | "Fun.Blazor", 12 | "SSR" 13 | ], 14 | "type": { 15 | "language": "F#" 16 | }, 17 | "tags": { 18 | "type": "project", 19 | "language": "F#" 20 | }, 21 | "sourceName": "SSRApp", 22 | "preferNameDirectory": true 23 | } -------------------------------------------------------------------------------- /templates/SSRApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:57640/", 7 | "sslPort": 44319 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "SSRApp": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:7564;http://localhost:7565" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /templates/SSRApp/README.md: -------------------------------------------------------------------------------- 1 | This project is using blazor ssr + blazor custom elements + htmx 2 | 3 | ```bash 4 | dotnet run 5 | ``` 6 | -------------------------------------------------------------------------------- /templates/SSRApp/SSRApp.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /templates/SSRApp/Startup.fs: -------------------------------------------------------------------------------- 1 | #nowarn "0020" 2 | 3 | open System 4 | open System.Reflection 5 | open Microsoft.AspNetCore.Builder 6 | open Microsoft.Extensions.Hosting 7 | open Microsoft.Extensions.DependencyInjection 8 | 9 | let builder = WebApplication.CreateBuilder(Environment.GetCommandLineArgs()) 10 | let services = builder.Services 11 | 12 | services.AddRazorComponents().AddInteractiveServerComponents() 13 | services.AddServerSideBlazor(fun x -> x.RootComponents.RegisterCustomElementForFunBlazor(Assembly.GetExecutingAssembly())) 14 | services.AddFunBlazorServer() 15 | 16 | 17 | let app = builder.Build() 18 | 19 | app.UseStaticFiles() 20 | app.UseAntiforgery() 21 | 22 | app.MapRazorComponentsForSSR(Assembly.GetExecutingAssembly()) 23 | app.MapCustomElementsForSSR(Assembly.GetExecutingAssembly()) 24 | 25 | app.MapRazorComponents().AddInteractiveServerRenderMode() 26 | 27 | app.Run() 28 | -------------------------------------------------------------------------------- /templates/SSRApp/View/App.fs: -------------------------------------------------------------------------------- 1 | namespace SSRApp.View 2 | 3 | open Microsoft.AspNetCore.Components.Web 4 | open Fun.Blazor 5 | 6 | 7 | type App() = 8 | inherit FunComponent() 9 | 10 | override _.Render() = fragment { 11 | doctype "html" 12 | html' { 13 | lang "EN" 14 | head { 15 | baseUrl "/" 16 | meta { charset "utf-8" } 17 | meta { 18 | name "viewport" 19 | content "width=device-width, initial-scale=1.0" 20 | } 21 | link { 22 | rel "icon" 23 | type' "image/png" 24 | href "favicon.png" 25 | } 26 | styleElt { 27 | ruleset ".active" { 28 | color "green" 29 | fontWeightBold 30 | } 31 | } 32 | HeadOutlet'' 33 | CustomElement.lazyBlazorJs () 34 | } 35 | body { 36 | html.blazor () 37 | script { src "https://unpkg.com/htmx.org@1.9.9" } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /templates/SSRApp/View/Components/Counter.fs: -------------------------------------------------------------------------------- 1 | namespace SSRApp.View.Components 2 | 3 | open Fun.Blazor 4 | open Microsoft.AspNetCore.Components 5 | 6 | [] 7 | type Counter() as this = 8 | inherit FunComponent() 9 | 10 | let mutable count = 0 11 | 12 | [] 13 | member val init_count = 0 with get, set 14 | 15 | override _.OnInitialized() = count <- this.init_count 16 | 17 | override _.Render() = fragment { 18 | p { $"Current count: {count}" } 19 | button { 20 | onclick (fun _ -> count <- count + 1) 21 | "Click me" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /templates/SSRApp/View/Components/Login.fs: -------------------------------------------------------------------------------- 1 | namespace SSRApp.View.Components 2 | 3 | open System 4 | open Microsoft.AspNetCore.Components 5 | open Microsoft.AspNetCore.Components.Forms 6 | open Fun.Css 7 | open Fun.Htmx 8 | open Fun.Blazor 9 | 10 | type Login() as this = 11 | inherit FunComponent() 12 | 13 | [] 14 | member val Name = "" with get, set 15 | 16 | [] 17 | member val Password = "" with get, set 18 | 19 | override _.Render() = form { 20 | hxPostComponent typeof 21 | html.blazor () 22 | input { 23 | type' InputTypes.text 24 | name (nameof this.Name) 25 | value this.Name 26 | } 27 | input { 28 | type' InputTypes.password 29 | name (nameof this.Password) 30 | value this.Password 31 | } 32 | button { 33 | type' InputTypes.submit 34 | "Login" 35 | } 36 | if String.IsNullOrEmpty this.Password || this.Password.Length < 3 then 37 | div { 38 | style { color color.red } 39 | "Wrong password" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /templates/SSRApp/View/Components/OrderList.fs: -------------------------------------------------------------------------------- 1 | namespace SSRApp.View.Components 2 | 3 | open System 4 | open System.Threading.Tasks 5 | open Microsoft.AspNetCore.Components 6 | open Fun.Css 7 | open Fun.Htmx 8 | open Fun.Blazor 9 | 10 | type OrderList() as this = 11 | inherit FunComponent() 12 | 13 | let mutable orders = [] 14 | 15 | [] 16 | member val Query = "" with get, set 17 | 18 | override _.OnInitializedAsync() = task { 19 | do! Task.Delay 1000 20 | 21 | orders <- 22 | [ 23 | for i in 1..10 do 24 | {| 25 | Id = i 26 | Name = $"Item {i}" 27 | Quality = Random.Shared.Next(1, 100) 28 | |} 29 | ] 30 | |> (if String.IsNullOrEmpty this.Query then 31 | id 32 | else 33 | Seq.filter (fun x -> x.Name.Contains(this.Query, StringComparison.OrdinalIgnoreCase)) >> Seq.toList) 34 | } 35 | 36 | override _.Render() = section { 37 | id "order-list" 38 | div { 39 | input { 40 | hxTrigger' (hxEvt.keyboard.keyup, delayMs = 500) 41 | hxGetComponent typeof // Load the component it self and replace the whole content 42 | hxTarget "#order-list" 43 | hxSwap_outerHTML 44 | hxIndicator ".htmx-indicator" 45 | name (nameof this.Query) 46 | value this.Query 47 | placeholder "Search by name..." 48 | } 49 | } 50 | progress { class' "htmx-indicator" } 51 | ul { 52 | for order in orders do 53 | li { 54 | style { color (if order.Quality % 3 = 0 then color.blue else color.darkCyan) } 55 | $"Order: {order.Name} [{order.Quality}]" 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /templates/SSRApp/View/Layout/MainLayout.fs: -------------------------------------------------------------------------------- 1 | namespace SSRApp.View.Layout 2 | 3 | open Microsoft.AspNetCore.Components 4 | open Fun.Blazor 5 | 6 | type MainLayout() as this = 7 | inherit LayoutComponentBase() 8 | 9 | let content = div { 10 | NavMenu.Create() 11 | SectionOutlet'' { SectionName "header" } 12 | main { this.Body } 13 | } 14 | 15 | override _.BuildRenderTree(builder) = content.Invoke(this, builder, 0) |> ignore 16 | -------------------------------------------------------------------------------- /templates/SSRApp/View/Layout/NavMenu.fs: -------------------------------------------------------------------------------- 1 | namespace SSRApp.View.Layout 2 | 3 | open Microsoft.AspNetCore.Components.Routing 4 | open Fun.Blazor 5 | 6 | 7 | [] 8 | module Extensions = 9 | open Fun.Blazor.Operators 10 | 11 | type NavLink' with 12 | 13 | [] 14 | member inline _.Href([] render: AttrRenderFragment, url: string) = render ==> ("href" => url) 15 | 16 | let NavLink'' = NavLink'() 17 | 18 | 19 | type NavMenu = 20 | static member Create() = nav { 21 | style { 22 | displayFlex 23 | alignItemsCenter 24 | gap 10 25 | } 26 | NavLink'' { 27 | Href "" 28 | Match NavLinkMatch.All 29 | "Home" 30 | } 31 | NavLink'' { 32 | Href "counter" 33 | "Counter" 34 | } 35 | NavLink'' { 36 | Href "form" 37 | "Form demo" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /templates/SSRApp/View/Pages/CounterPage.fs: -------------------------------------------------------------------------------- 1 | namespace SSRApp.View.Pages 2 | 3 | open Microsoft.AspNetCore.Components 4 | open Microsoft.AspNetCore.Components.Web 5 | open Fun.Htmx 6 | open Fun.Blazor 7 | open SSRApp.View.Components 8 | 9 | [] 10 | type CounterPage() = 11 | inherit FunComponent() 12 | 13 | override _.Render() = fragment { 14 | PageTitle'' { "Counter" } 15 | SectionContent'' { 16 | SectionName "header" 17 | h1 { "Counter" } 18 | } 19 | 20 | h2 { "Prerendered for readonly" } 21 | html.blazor (ComponentAttrBuilder().Add((fun x -> x.init_count), 5)) 22 | 23 | h2 { "Use custom element for interactivity" } 24 | html.customElement ( 25 | ComponentAttrBuilder().Add((fun x -> x.init_count), 10), 26 | preRender = true, 27 | renderAfter = RenderAfter.Delay 2000, 28 | preRenderNode = h3 { "Lazy load in 2 secs" } 29 | ) 30 | h3 { 31 | hxTrigger' (hxEvt.load, delayMs = 2000) 32 | hxGetCustomElement (QueryBuilder().Add((fun x -> x.init_count), 15)) 33 | hxSwap_outerHTML 34 | "Lazy load with htmx in 2 secs" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /templates/SSRApp/View/Pages/FromPage.fs: -------------------------------------------------------------------------------- 1 | namespace SSRApp.View.Pages 2 | 3 | open Microsoft.AspNetCore.Components 4 | open Microsoft.AspNetCore.Components.Web 5 | open Fun.Htmx 6 | open Fun.Blazor 7 | open SSRApp.View.Components 8 | 9 | [] 10 | type Form() = 11 | inherit FunComponent() 12 | 13 | override _.Render() = fragment { 14 | PageTitle'' { "Form demo" } 15 | SectionContent'' { 16 | SectionName "header" 17 | h1 { "Form demo" } 18 | } 19 | section { 20 | hxTrigger' hxEvt.load 21 | hxGetComponent typeof 22 | hxSwap_outerHTML 23 | "Loading login page..." 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /templates/SSRApp/View/Pages/HomePage.fs: -------------------------------------------------------------------------------- 1 | namespace SSRApp.View.Pages 2 | 3 | open Microsoft.AspNetCore.Components 4 | open Microsoft.AspNetCore.Components.Web 5 | open Fun.Htmx 6 | open Fun.Blazor 7 | open SSRApp.View.Components 8 | 9 | [] 10 | type Home() = 11 | inherit FunComponent() 12 | 13 | override _.Render() = fragment { 14 | PageTitle'' { "Home" } 15 | SectionContent'' { 16 | SectionName "header" 17 | h1 { "Home" } 18 | } 19 | section { 20 | hxGetComponent typeof 21 | hxTrigger' hxEvt.intersect 22 | "Loading order list ..." 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /templates/SSRApp/View/Routes.fs: -------------------------------------------------------------------------------- 1 | namespace SSRApp.View 2 | 3 | open System.Reflection 4 | open Fun.Blazor 5 | open SSRApp.View.Layout 6 | 7 | type Routes() = 8 | inherit FunComponent() 9 | 10 | override _.Render() = Router'' { 11 | AppAssembly(Assembly.GetExecutingAssembly()) 12 | Found(fun routeData -> RouteView'' { 13 | RouteData routeData 14 | DefaultLayout typeof 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /templates/SSRApp/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaveOftime/Fun.Blazor.Samples/5b8f5371390e3376e3e3db545652f5416f99e566/templates/SSRApp/wwwroot/favicon.png -------------------------------------------------------------------------------- /templates/WASMApp/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "identity": "Fun.Blazor.WasmApp", 4 | "shortName": "fun-wasm", 5 | "name": "Fun.Blazor.WasmApp", 6 | "author": "slaveoftime", 7 | "classifications": [ 8 | "Web", 9 | "F#", 10 | "blazor", 11 | "Fun.Blazor" 12 | ], 13 | "type": { 14 | "language": "F#" 15 | }, 16 | "tags": { 17 | "type": "project", 18 | "language": "F#" 19 | }, 20 | "sourceName": "WASMApp", 21 | "preferNameDirectory": true 22 | } -------------------------------------------------------------------------------- /templates/WASMApp/App.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module WASMApp.App 3 | 4 | open FSharp.Data.Adaptive 5 | open Fun.Blazor 6 | 7 | let app = adapt { 8 | let amount = 1 9 | let! count, setCount = cval(1).WithSetter() 10 | 11 | div { 12 | div { $"Here is the count {count}" } 13 | button { 14 | onclick (fun _ -> setCount (count + amount)) 15 | "Increase by "; amount 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /templates/WASMApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:57135/", 7 | "sslPort": 44390 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | }, 17 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" 18 | }, 19 | "WASMApp": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 26 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /templates/WASMApp/README.md: -------------------------------------------------------------------------------- 1 | This is a blazor WASM mode app with Fun.Blazor 2 | 3 | wwwroot/index.html is the entry point for browser. it will load all the necessary files. 4 | 5 | App.fs contains UI logic 6 | 7 | Startup.fs is for hooking up everything and configuring server 8 | 9 | ```bash 10 | dotnet run 11 | ``` 12 | -------------------------------------------------------------------------------- /templates/WASMApp/Startup.fs: -------------------------------------------------------------------------------- 1 | #nowarn "0020" 2 | 3 | open System 4 | open Microsoft.Extensions.DependencyInjection 5 | open Microsoft.AspNetCore.Components.WebAssembly.Hosting 6 | open WASMApp 7 | 8 | let builder = WebAssemblyHostBuilder.CreateDefault(Environment.GetCommandLineArgs()) 9 | 10 | builder.AddFunBlazor("#app", app) 11 | builder.Services.AddFunBlazorWasm() 12 | 13 | builder.Build().RunAsync() 14 | -------------------------------------------------------------------------------- /templates/WASMApp/WASMApp.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /templates/WASMApp/wwwroot/app.css: -------------------------------------------------------------------------------- 1 |  2 | #blazor-error-ui { 3 | background: lightyellow; 4 | bottom: 0; 5 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 6 | display: none; 7 | left: 0; 8 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 9 | position: fixed; 10 | width: 100%; 11 | z-index: 1000; 12 | } 13 | 14 | #blazor-error-ui .dismiss { 15 | cursor: pointer; 16 | position: absolute; 17 | right: 0.75rem; 18 | top: 0.5rem; 19 | } 20 | 21 | .blazor-error-boundary { 22 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 23 | padding: 1rem 1rem 1rem 3.7rem; 24 | color: white; 25 | } 26 | 27 | .blazor-error-boundary::after { 28 | content: "An error has occurred." 29 | } 30 | 31 | .loading-progress { 32 | position: relative; 33 | display: block; 34 | width: 8rem; 35 | height: 8rem; 36 | margin: 20vh auto 1rem auto; 37 | } 38 | 39 | .loading-progress circle { 40 | fill: none; 41 | stroke: #e0e0e0; 42 | stroke-width: 0.6rem; 43 | transform-origin: 50% 50%; 44 | transform: rotate(-90deg); 45 | } 46 | 47 | .loading-progress circle:last-child { 48 | stroke: #1b6ec2; 49 | stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; 50 | transition: stroke-dasharray 0.05s ease-in-out; 51 | } 52 | 53 | .loading-progress-text { 54 | position: absolute; 55 | text-align: center; 56 | font-weight: bold; 57 | inset: calc(20vh + 3.25rem) 0 auto 0.2rem; 58 | } 59 | 60 | .loading-progress-text:after { 61 | content: var(--blazor-load-percentage-text, "Loading"); 62 | } 63 | -------------------------------------------------------------------------------- /templates/WASMApp/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaveOftime/Fun.Blazor.Samples/5b8f5371390e3376e3e3db545652f5416f99e566/templates/WASMApp/wwwroot/favicon.ico -------------------------------------------------------------------------------- /templates/WASMApp/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | WASMApp 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 | An unhandled error has occurred. 22 | Reload 23 | 🗙 24 |
25 | 26 | 27 | 28 | --------------------------------------------------------------------------------