├── WebSharper.Mvu.TodoMvc ├── wwwroot │ ├── Scripts │ │ ├── WebSharper.Mvu.TodoMvc.css │ │ ├── WebSharper.Mvu.TodoMvc.head.html │ │ ├── WebSharper.Mvu.TodoMvc.head.js │ │ └── WebSharper.Core.JavaScript │ │ │ ├── Runtime.js │ │ │ └── Runtime.ts │ ├── rendering.css │ ├── base.css │ ├── index.html │ └── index.css ├── paket.references ├── wsconfig.json ├── package.json ├── esbuild.config.mjs ├── Properties │ └── launchSettings.json ├── WebSharper.Mvu.TodoMvc.fsproj ├── Program.fs ├── Startup.fs └── TodoMvc.fs ├── WebSharper.Mvu ├── paket.references ├── wsconfig.json ├── page.css ├── WebSharper.Mvu.fsproj ├── Resources.fs ├── ReduxDevTools.fs ├── RemoteDev.fs ├── Macros.fs ├── App.fsi └── App.fs ├── docs ├── images │ ├── paging.gif │ └── remotedev.png └── remotedev.md ├── WebSharper.Mvu.Tests ├── wwwroot │ ├── Scripts │ │ ├── WebSharper.Mvu.Tests.head.js │ │ └── WebSharper.Mvu.Tests.css │ └── index.html ├── paket.references ├── wsconfig.json ├── package.json ├── esbuild.config.mjs ├── Properties │ └── launchSettings.json ├── WebSharper.Mvu.Tests.fsproj ├── Program.fs ├── Client.fs └── package-lock.json ├── _config.yml ├── global.json ├── .gitignore ├── .config └── dotnet-tools.json ├── ivy.xml ├── webpack.config.js ├── paket.dependencies ├── package.json ├── nuget └── WebSharper.Mvu.paket.template ├── .paket ├── paket.targets └── Paket.Restore.targets ├── WebSharper.Mvu.sln ├── README.md ├── LICENSE.md └── paket.lock /WebSharper.Mvu.TodoMvc/wwwroot/Scripts/WebSharper.Mvu.TodoMvc.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/wwwroot/Scripts/WebSharper.Mvu.TodoMvc.head.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /WebSharper.Mvu/paket.references: -------------------------------------------------------------------------------- 1 | WebSharper 2 | WebSharper.FSharp 3 | WebSharper.UI 4 | -------------------------------------------------------------------------------- /WebSharper.Mvu/wsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "library", 3 | "sourceMap": true 4 | } 5 | -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/paket.references: -------------------------------------------------------------------------------- 1 | WebSharper 2 | WebSharper.FSharp 3 | WebSharper.UI 4 | -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/wsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "bundle", 3 | "outputDir": "build" 4 | } 5 | -------------------------------------------------------------------------------- /docs/images/paging.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-websharper/mvu/HEAD/docs/images/paging.gif -------------------------------------------------------------------------------- /docs/images/remotedev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-websharper/mvu/HEAD/docs/images/remotedev.png -------------------------------------------------------------------------------- /WebSharper.Mvu.Tests/wwwroot/Scripts/WebSharper.Mvu.Tests.head.js: -------------------------------------------------------------------------------- 1 | var wsbundle=(()=>{document.write("");})(); 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | exclude: 3 | - .paket 4 | - node_modules 5 | - packages 6 | -------------------------------------------------------------------------------- /WebSharper.Mvu.Tests/paket.references: -------------------------------------------------------------------------------- 1 | WebSharper 2 | WebSharper.FSharp 3 | WebSharper.AspNetCore 4 | WebSharper.UI -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/wwwroot/Scripts/WebSharper.Mvu.TodoMvc.head.js: -------------------------------------------------------------------------------- 1 | var wsbundle=(()=>{document.write("");})(); 2 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.0", 4 | "rollForward": "latestFeature", 5 | "allowPrerelease": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /WebSharper.Mvu.Tests/wsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://websharper.com/wsconfig.schema.json", 3 | "project": "bundle", 4 | "outputDir": "build" 5 | } 6 | -------------------------------------------------------------------------------- /WebSharper.Mvu.Tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WebSharper.Mvu.Tests", 3 | "version": "1.0.0", 4 | "devDependencies": { 5 | "esbuild": "^0.19.9" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | bin/ 3 | obj/ 4 | *.user 5 | wwwroot/Content/ 6 | wwwroot/Scripts/ 7 | node_modules/ 8 | paket-files/ 9 | packages/ 10 | .fake/ 11 | build/ 12 | .vscode/ 13 | websharper.log 14 | -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "paket": { 6 | "version": "8.0.3", 7 | "commands": [ 8 | "paket" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/wwwroot/rendering.css: -------------------------------------------------------------------------------- 1 | @keyframes highlight { 2 | from { 3 | background-color: yellow; 4 | } 5 | 6 | to { 7 | background-color: transparent; 8 | } 9 | } 10 | 11 | body * { 12 | animation: highlight 1.5s ease-out; 13 | } 14 | -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WebSharper.Mvu.TodoMvc", 3 | "version": "1.0.0", 4 | "devDependencies": { 5 | "@redux-devtools/cli": "^4.0.3", 6 | "esbuild": "^0.19.9" 7 | }, 8 | "dependencies": { 9 | "@redux-devtools/remote": "^0.9.5" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ivy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './WebSharper.Mvu/src/index.js', 5 | output: { 6 | filename: 'remotedev.js', 7 | path: path.resolve(__dirname, 'WebSharper.Mvu', 'dist') 8 | }, 9 | devServer: { 10 | contentBase: path.join(__dirname, 'WebSharper.Mvu.TodoMvc', 'wwwroot') 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /WebSharper.Mvu/page.css: -------------------------------------------------------------------------------- 1 | .ws-page-container { 2 | position: relative; 3 | overflow: hidden; 4 | } 5 | 6 | .ws-page { 7 | max-height: 100%; 8 | height: 100%; 9 | max-width: 100%; 10 | width: 100%; 11 | overflow: auto; 12 | position: absolute; 13 | left: 0; 14 | } 15 | 16 | .ws-page[aria-hidden=true] { 17 | left: -999999px; 18 | } 19 | -------------------------------------------------------------------------------- /WebSharper.Mvu.Tests/wwwroot/Scripts/WebSharper.Mvu.Tests.css: -------------------------------------------------------------------------------- 1 | .ws-page-container { 2 | position: relative; 3 | overflow: hidden; 4 | } 5 | 6 | .ws-page { 7 | max-height: 100%; 8 | height: 100%; 9 | max-width: 100%; 10 | width: 100%; 11 | overflow: auto; 12 | position: absolute; 13 | left: 0; 14 | } 15 | 16 | .ws-page[aria-hidden=true] { 17 | left: -999999px; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://api.nuget.org/v3/index.json 2 | source https://nuget.pkg.github.com/dotnet-websharper/index.json 3 | source ../localnuget 4 | 5 | framework: net8.0, netstandard2.0 6 | strategy: min 7 | storage: none 8 | 9 | nuget WebSharper prerelease 10 | nuget WebSharper.FSharp prerelease 11 | nuget WebSharper.UI prerelease 12 | nuget WebSharper.AspNetCore prerelease 13 | 14 | group wsbuild 15 | git https://github.com/dotnet-websharper/build-script websharper80 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "websharper.mvu", 3 | "version": "1.0.0", 4 | "description": "WebSharper MVU Architecture with TodoMvc app", 5 | "private": true, 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "webpack-dev-server --mode development", 9 | "remotedev": "remotedev" 10 | }, 11 | "devDependencies": { 12 | "remotedev": "^0.2.7", 13 | "remotedev-server": "^0.2.4", 14 | "webpack": "^4.3.0", 15 | "webpack-cli": "^2.0.13", 16 | "webpack-dev-server": "^3.1.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import { existsSync, cpSync, readdirSync } from 'fs' 2 | import { build } from 'esbuild' 3 | 4 | cpSync('./build/', './wwwroot/Scripts/', { recursive: true }); 5 | 6 | const files = readdirSync('./build/'); 7 | 8 | files.forEach(file => { 9 | if (file.endsWith('.js')) { 10 | var options = 11 | { 12 | entryPoints: ['./build/' + file], 13 | bundle: true, 14 | minify: true, 15 | format: 'iife', 16 | outfile: 'wwwroot/Scripts/' + file, 17 | globalName: 'wsbundle' 18 | }; 19 | 20 | console.log("Bundling:", file); 21 | build(options); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /WebSharper.Mvu.Tests/esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import { existsSync, cpSync, readdirSync } from 'fs' 2 | import { build } from 'esbuild' 3 | 4 | const files = readdirSync('./build/'); 5 | 6 | cpSync('./build/WebSharper.Mvu.Tests.css', './wwwroot/Scripts/WebSharper.Mvu.Tests.css', { force: true }); 7 | 8 | files.forEach(file => { 9 | if (file.endsWith('.js')) { 10 | var options = 11 | { 12 | entryPoints: ['./build/' + file], 13 | bundle: true, 14 | minify: true, 15 | format: 'iife', 16 | outfile: 'wwwroot/Scripts/' + file, 17 | globalName: 'wsbundle' 18 | }; 19 | 20 | console.log("Bundling:", file); 21 | build(options); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /WebSharper.Mvu.Tests/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:7755/", 7 | "sslPort": 44300 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "WebSharper.Mvu.Tests": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:7756/", 7 | "sslPort": 44338 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "WebSharper.Mvu.TodoMvc": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /nuget/WebSharper.Mvu.paket.template: -------------------------------------------------------------------------------- 1 | type file 2 | id WebSharper.Mvu 3 | authors IntelliFactory 4 | owners IntelliFactory 5 | projectUrl https://websharper.com/ 6 | repositoryType git 7 | repositoryUrl https://github.com/dotnet-websharper/mvu/ 8 | licenseUrl https://github.com/dotnet-websharper/mvu/blob/master/LICENSE.md 9 | iconUrl https://github.com/dotnet-websharper/core/raw/websharper50/tools/WebSharper.png 10 | description 11 | Model-View-Update architecture for WebSharper 12 | tags 13 | Web JavaScript F# C# 14 | files 15 | ../WebSharper.Mvu/bin/Release/netstandard2.0/WebSharper.Mvu.dll ==> lib/netstandard2.0 16 | ../WebSharper.Mvu/bin/Release/netstandard2.0/WebSharper.Mvu.xml ==> lib/netstandard2.0 17 | dependencies 18 | framework: netstandard2.0 19 | WebSharper ~> LOCKEDVERSION:[3] 20 | WebSharper.UI ~> LOCKEDVERSION:[3] 21 | -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/WebSharper.Mvu.TodoMvc.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 | 23 | -------------------------------------------------------------------------------- /WebSharper.Mvu/WebSharper.Mvu.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | page.css 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /WebSharper.Mvu/Resources.fs: -------------------------------------------------------------------------------- 1 | // $begin{copyright} 2 | // 3 | // This file is part of WebSharper 4 | // 5 | // Copyright (c) 2008-2018 IntelliFactory 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); you 8 | // may not use this file except in compliance with the License. You may 9 | // obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 16 | // implied. See the License for the specific language governing 17 | // permissions and limitations under the License. 18 | // 19 | // $end{copyright} 20 | namespace WebSharper.Mvu.Resources 21 | 22 | open WebSharper 23 | 24 | type PagerCss() = inherit Resources.BaseResource("page.css") 25 | 26 | [] 27 | do() 28 | -------------------------------------------------------------------------------- /WebSharper.Mvu.Tests/WebSharper.Mvu.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Bundle 6 | wwwroot/Content 7 | $(MSBuildThisFileDirectory)/wwwroot 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/Program.fs: -------------------------------------------------------------------------------- 1 | // $begin{copyright} 2 | // 3 | // This file is part of WebSharper 4 | // 5 | // Copyright (c) 2008-2018 IntelliFactory 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); you 8 | // may not use this file except in compliance with the License. You may 9 | // obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 16 | // implied. See the License for the specific language governing 17 | // permissions and limitations under the License. 18 | // 19 | // $end{copyright} 20 | namespace TodoMvc 21 | 22 | open System 23 | open System.Collections.Generic 24 | open System.IO 25 | open System.Linq 26 | open System.Threading.Tasks 27 | open Microsoft.AspNetCore 28 | open Microsoft.AspNetCore.Hosting 29 | open Microsoft.Extensions.Configuration 30 | open Microsoft.Extensions.Logging 31 | 32 | module Program = 33 | let BuildWebHost args = 34 | WebHost 35 | .CreateDefaultBuilder(args) 36 | .UseStartup() 37 | .Build() 38 | 39 | [] 40 | let main args = 41 | BuildWebHost(args).Run() 42 | 0 43 | -------------------------------------------------------------------------------- /WebSharper.Mvu.Tests/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebSharper.Mvu.Tests 5 | 6 | 7 | 8 | 9 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/Startup.fs: -------------------------------------------------------------------------------- 1 | // $begin{copyright} 2 | // 3 | // This file is part of WebSharper 4 | // 5 | // Copyright (c) 2008-2018 IntelliFactory 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); you 8 | // may not use this file except in compliance with the License. You may 9 | // obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 16 | // implied. See the License for the specific language governing 17 | // permissions and limitations under the License. 18 | // 19 | // $end{copyright} 20 | namespace TodoMvc 21 | 22 | open System 23 | open Microsoft.AspNetCore.Builder 24 | open Microsoft.AspNetCore.Hosting 25 | open Microsoft.AspNetCore.Http 26 | open Microsoft.Extensions.Configuration 27 | open Microsoft.Extensions.DependencyInjection 28 | open Microsoft.Extensions.Hosting 29 | 30 | type Startup() = 31 | 32 | member this.ConfigureServices(services: IServiceCollection) = 33 | () 34 | 35 | member this.Configure(app: IApplicationBuilder, env: IWebHostEnvironment) = 36 | if env.IsDevelopment() then app.UseDeveloperExceptionPage() |> ignore 37 | 38 | app.UseDefaultFiles() 39 | .UseStaticFiles() 40 | .Run(fun context -> 41 | context.Response.StatusCode <- 404 42 | context.Response.WriteAsync("Page not found")) 43 | -------------------------------------------------------------------------------- /docs/remotedev.md: -------------------------------------------------------------------------------- 1 | # RemoteDev integration 2 | 3 | WebSharper.Mvu integrates seamlessly with [RemoteDev](https://github.com/zalmoxisus/remotedev). This tool allows you to inspect the successive messages and states of your model, and even to replay old states and see the effect on your view. 4 | 5 | ![RemoteDev screenshot](images/remotedev.png) 6 | 7 | ## Installing and starting the tool 8 | 9 | RemoteDev can be used in multiple ways, documented on [its website](https://github.com/zalmoxisus/remotedev). We recommend the following setup: 10 | 11 | * Start the remotedev server: 12 | 13 | ```bash 14 | $ npm install -g remotedev-server # Run once to install the server 15 | $ remotedev # Start the remotedev server 16 | ``` 17 | 18 | * Install and start the [Redux devtools extension](https://github.com/zalmoxisus/redux-devtools-extension#installation) for your browser. 19 | 20 | Note that at the time of writing, `remotedev-server` is compatible with nodejs 6.x, but not 8.x. 21 | 22 | ## Code integration 23 | 24 | RemoteDev integration is applied using a single call to `App.WithRemoteDev`, which takes RemoteDev options as argument. Here is an example with options appropriate for use with `remotedev-server`: 25 | 26 | ```fsharp 27 | let Main() = 28 | App.Create initialModel update render 29 | |> App.WithRemoteDev (RemoteDev.Options(hostname = "localhost", port = 8000)) 30 | |> App.Run 31 | ``` 32 | 33 | This integration uses WebSharper.Json serialization to communicate with RemoteDev. The tool expects message values to be objects with a `"type"` field; therefore, you should use as Message type a discriminated union annotated like follows: 34 | 35 | ```fsharp 36 | [] 37 | type Message = 38 | | Message1 of id: int * value: string 39 | | // other message types... 40 | ``` 41 | 42 | Given the above, the value: 43 | 44 | ```fsharp 45 | Message1 (42, "Hello world!") 46 | ``` 47 | 48 | will be sent to RemoteDev as: 49 | 50 | ```json 51 | { "type": "Message1", "id": 42, "value": "Hello world!" } 52 | ``` 53 | -------------------------------------------------------------------------------- /WebSharper.Mvu.Tests/Program.fs: -------------------------------------------------------------------------------- 1 | // $begin{copyright} 2 | // 3 | // This file is part of WebSharper 4 | // 5 | // Copyright (c) 2008-2018 IntelliFactory 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); you 8 | // may not use this file except in compliance with the License. You may 9 | // obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 16 | // implied. See the License for the specific language governing 17 | // permissions and limitations under the License. 18 | // 19 | // $end{copyright} 20 | namespace WebSharper.Mvu.Tests 21 | 22 | open Microsoft.AspNetCore.Builder 23 | open Microsoft.Extensions.Hosting 24 | open Microsoft.AspNetCore.Http 25 | open Microsoft.Extensions.DependencyInjection 26 | open WebSharper.AspNetCore 27 | 28 | module Program = 29 | 30 | [] 31 | let main args = 32 | let builder = WebApplication.CreateBuilder(args) 33 | 34 | //Add services to the container. 35 | builder.Services.AddWebSharper() 36 | .AddAuthentication("WebSharper") 37 | .AddCookie("WebSharper", fun options -> ()) 38 | |> ignore 39 | 40 | let app = builder.Build() 41 | 42 | // Configure the HTTP request pipeline. 43 | if not (app.Environment.IsDevelopment()) then 44 | app.UseExceptionHandler("/Error") 45 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 46 | .UseHsts() 47 | |> ignore 48 | 49 | app.UseHttpsRedirection() 50 | .UseAuthentication() 51 | .UseDefaultFiles() 52 | .UseStaticFiles() 53 | .UseWebSharper(fun builder -> 54 | builder.UseSitelets(false) |> ignore 55 | ) 56 | |> ignore 57 | 58 | app.Run() 59 | 60 | 0 // Exit code 61 | -------------------------------------------------------------------------------- /WebSharper.Mvu/ReduxDevTools.fs: -------------------------------------------------------------------------------- 1 | // $begin{copyright} 2 | // 3 | // This file is part of WebSharper 4 | // 5 | // Copyright (c) 2008-2018 IntelliFactory 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); you 8 | // may not use this file except in compliance with the License. You may 9 | // obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 16 | // implied. See the License for the specific language governing 17 | // permissions and limitations under the License. 18 | // 19 | // $end{copyright} 20 | [] 21 | module WebSharper.ReduxDevTools 22 | 23 | open WebSharper 24 | open WebSharper.JavaScript 25 | 26 | [] 27 | type Options [] () = 28 | [] val mutable name : string 29 | [] val mutable actionCreators : obj 30 | [] val mutable latency : int 31 | [] val mutable maxAge : int 32 | [] val mutable trace : obj 33 | [] val mutable traceLimit : int 34 | [] val mutable serialize : obj 35 | [] val mutable actionsDenylist : obj 36 | [] val mutable actionsAllowlist : obj 37 | [] val mutable predicate : System.Func 38 | [] val mutable shouldRecordChanges : bool 39 | [] val mutable pauseActionType : bool 40 | [] val mutable autoPause : bool 41 | [] val mutable shouldStartLocked : bool 42 | [] val mutable shouldHotReload : bool 43 | [] val mutable shouldCatchErrors : bool 44 | [] val mutable features : obj 45 | [] val mutable actionSanitizer : System.Func 46 | [] val mutable stateSanitizer : System.Func 47 | 48 | [] 49 | let IsAvailable() = X 50 | 51 | [] 52 | let Connect(options: Options) = X 53 | -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/wwwroot/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TodoMVC with WebSharper UI 5 | 6 | 7 | 8 | 9 | 10 | 20 | 21 | 22 | 23 |
24 |
25 |

todos

26 | 27 |
28 |
29 | 30 | 31 |
    32 |
  • 33 |
    34 | 35 | 36 | 37 |
    38 | 39 |
  • 40 |
41 |
42 |
43 | ${ItemsLeft} 44 | 55 | 56 |
57 |
58 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /WebSharper.Mvu/RemoteDev.fs: -------------------------------------------------------------------------------- 1 | // $begin{copyright} 2 | // 3 | // This file is part of WebSharper 4 | // 5 | // Copyright (c) 2008-2018 IntelliFactory 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); you 8 | // may not use this file except in compliance with the License. You may 9 | // obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 16 | // implied. See the License for the specific language governing 17 | // permissions and limitations under the License. 18 | // 19 | // $end{copyright} 20 | [] 21 | module WebSharper.RemoteDev 22 | 23 | open WebSharper 24 | open WebSharper.JavaScript 25 | 26 | module MsgTypes = 27 | [] 28 | let Start = "START" 29 | [] 30 | let Action = "ACTION" 31 | [] 32 | let Dispatch = "DISPATCH" 33 | 34 | module PayloadTypes = 35 | [] 36 | let ImportState = "IMPORT_STATE" 37 | [] 38 | let JumpToState = "JUMP_TO_STATE" 39 | [] 40 | let JumpToAction = "JUMP_TO_ACTION" 41 | [] 42 | let Reset = "RESET" 43 | [] 44 | let Rollback = "ROLLBACK" 45 | [] 46 | let Commit = "COMMIT" 47 | 48 | [] 49 | type Options [] () = 50 | [] val mutable remote : bool 51 | [] val mutable port : int 52 | [] val mutable hostname : string 53 | [] val mutable secure : bool 54 | [] val mutable getActionType : (obj -> obj) 55 | [] val mutable serialize : obj 56 | 57 | type Action = 58 | { 59 | ``type``: string 60 | fields : obj array 61 | } 62 | 63 | type LiftedState = 64 | { 65 | actionsById : Action array 66 | computedStates : obj array 67 | currentStateIndex : int 68 | nextActionId : int 69 | } 70 | 71 | type Payload = 72 | { 73 | nextLiftedState : LiftedState 74 | ``type``: string 75 | } 76 | 77 | type Msg = 78 | { 79 | state : string 80 | action : obj 81 | ``type`` : string 82 | payload : Payload 83 | } 84 | 85 | type Listener = Msg -> unit 86 | 87 | type Unsubscribe = unit -> unit 88 | 89 | [] 90 | type Connection = 91 | member this.init(x: obj) = X 92 | member this.subscribe(l: Listener) = X 93 | member this.unsubscribe() = X 94 | member this.send(x: obj, y: obj) = X 95 | member this.error(x: obj) = X 96 | 97 | [] 98 | let Connect(options: Options) = X 99 | 100 | [] 101 | let ExtractState(message: Msg) = X 102 | -------------------------------------------------------------------------------- /WebSharper.Mvu/Macros.fs: -------------------------------------------------------------------------------- 1 | // $begin{copyright} 2 | // 3 | // This file is part of WebSharper 4 | // 5 | // Copyright (c) 2008-2018 IntelliFactory 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); you 8 | // may not use this file except in compliance with the License. You may 9 | // obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 16 | // implied. See the License for the specific language governing 17 | // permissions and limitations under the License. 18 | // 19 | // $end{copyright} 20 | module private WebSharper.Mvu.Macros 21 | 22 | #nowarn "25" // incomplete match on `let [x; y] = ...` 23 | 24 | open WebSharper 25 | open WebSharper.Core 26 | open WebSharper.Core.AST 27 | module I = WebSharper.Core.AST.IgnoreSourcePos 28 | 29 | [] 30 | module private Impl = 31 | let meth' name param ret gen = Hashed ({ MethodName = name; Parameters = param; ReturnType = ret; Generics = gen } : MethodInfo) 32 | let meth g name param ret = 33 | match List.length g with 34 | | 0 -> NonGeneric (meth' name param ret 0) 35 | | n -> Generic (meth' name param ret n) g 36 | let TP = TypeParameter 37 | let T0 = TP 0 38 | let T1 = TP 1 39 | let T2 = TP 2 40 | let T = ConcreteType 41 | let (^->) x y = FSharpFuncType(x, y) 42 | 43 | let wsui n = Hashed ({ Assembly = "WebSharper.UI"; FullName = "WebSharper.UI." + n } : TypeDefinitionInfo) 44 | let mVar = NonGeneric (wsui "Var") 45 | let tVar t = GenericType (wsui "Var`1") [t] 46 | let tMvu n = Hashed ({ Assembly = "WebSharper.Mvu"; FullName = "WebSharper.Mvu." + n } : TypeDefinitionInfo) 47 | let tApp ts = Generic (tMvu "App`3") ts 48 | let pAppVar = meth [] "get_Var" [] (tVar T1) 49 | let fLens t u = meth [t; u] "Lens" [tVar T0; T0 ^-> T1; T0 ^-> T1 ^-> T0] (tVar T1) 50 | 51 | type WithRouting() = 52 | inherit Macro() 53 | 54 | let tryGetId (call: MacroCall) e = 55 | match e with 56 | | I.Var i -> 57 | match call.BoundVars.TryGetValue(i) with 58 | | true, x -> x, fun r -> MacroUsedBoundVar(i, r) 59 | | false, _ -> e, id 60 | | _ -> e, id 61 | 62 | override this.TranslateCall(call) = 63 | let [router; getter; app] = call.Arguments 64 | let getter, wrap = tryGetId call getter 65 | match WebSharper.UI.Macros.Lens.MakeSetter call.Compilation getter with 66 | | MacroOk setter -> 67 | let tRoute :: ([_; tModel; _] as appTArgs) = call.Method.Generics 68 | let var = Call (Some app, tApp appTArgs, pAppVar, []) 69 | let lensedVar = Call (None, mVar, fLens tModel tRoute, [var; getter; setter]) 70 | let f = 71 | { call.Method with 72 | Entity = Hashed { 73 | call.Method.Entity.Value with 74 | MethodName = "withRouting" 75 | Parameters = tVar T0 :: call.Method.Entity.Value.Parameters } } 76 | Call (None, call.DefiningType, f, lensedVar :: call.Arguments) 77 | |> MacroOk 78 | | x -> x 79 | -------------------------------------------------------------------------------- /.paket/paket.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | true 7 | $(MSBuildThisFileDirectory) 8 | $(MSBuildThisFileDirectory)..\ 9 | $(PaketRootPath)paket.lock 10 | $(PaketRootPath)paket-files\paket.restore.cached 11 | /Library/Frameworks/Mono.framework/Commands/mono 12 | mono 13 | 14 | 15 | 16 | 17 | $(PaketRootPath)paket.exe 18 | $(PaketToolsPath)paket.exe 19 | "$(PaketExePath)" 20 | $(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" 21 | 22 | 23 | 24 | 25 | 26 | $(MSBuildProjectFullPath).paket.references 27 | 28 | 29 | 30 | 31 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references 32 | 33 | 34 | 35 | 36 | $(MSBuildProjectDirectory)\paket.references 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | $(PaketCommand) restore --references-file "$(PaketReferences)" 49 | 50 | RestorePackages; $(BuildDependsOn); 51 | 52 | 53 | 54 | true 55 | 56 | 57 | 58 | $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)')) 59 | $([System.IO.File]::ReadAllText('$(PaketLockFilePath)')) 60 | true 61 | false 62 | true 63 | 64 | 65 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /WebSharper.Mvu.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{884106FE-FCAC-4AE6-AAA7-D87B07CCB109}" 7 | ProjectSection(SolutionItems) = preProject 8 | paket.dependencies = paket.dependencies 9 | EndProjectSection 10 | EndProject 11 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "WebSharper.Mvu.TodoMvc", "WebSharper.Mvu.TodoMvc\WebSharper.Mvu.TodoMvc.fsproj", "{BA3D28A2-47E6-4701-B3E8-784686B19F52}" 12 | EndProject 13 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "WebSharper.Mvu", "WebSharper.Mvu\WebSharper.Mvu.fsproj", "{827B05D1-DD4D-4AA1-BC7F-C5FF2C5A7C9B}" 14 | EndProject 15 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "WebSharper.Mvu.Tests", "WebSharper.Mvu.Tests\WebSharper.Mvu.Tests.fsproj", "{565B3EAB-A5BF-4B6D-8A4F-EF13E0ABD667}" 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4A10FF25-13A2-42A2-A384-3825EA12DB3E}" 18 | ProjectSection(SolutionItems) = preProject 19 | README.md = README.md 20 | EndProjectSection 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{072FA63B-55FD-498D-A847-47F218D7EA49}" 23 | ProjectSection(SolutionItems) = preProject 24 | docs\remotedev.md = docs\remotedev.md 25 | EndProjectSection 26 | EndProject 27 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "images", "images", "{2566FB6E-5A30-41AD-B958-A96C3837808C}" 28 | ProjectSection(SolutionItems) = preProject 29 | docs\images\remotedev.png = docs\images\remotedev.png 30 | EndProjectSection 31 | EndProject 32 | Global 33 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 34 | Debug|Any CPU = Debug|Any CPU 35 | Debug|x64 = Debug|x64 36 | Debug|x86 = Debug|x86 37 | Release|Any CPU = Release|Any CPU 38 | Release|x64 = Release|x64 39 | Release|x86 = Release|x86 40 | EndGlobalSection 41 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 42 | {BA3D28A2-47E6-4701-B3E8-784686B19F52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {BA3D28A2-47E6-4701-B3E8-784686B19F52}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {BA3D28A2-47E6-4701-B3E8-784686B19F52}.Debug|x64.ActiveCfg = Debug|Any CPU 45 | {BA3D28A2-47E6-4701-B3E8-784686B19F52}.Debug|x64.Build.0 = Debug|Any CPU 46 | {BA3D28A2-47E6-4701-B3E8-784686B19F52}.Debug|x86.ActiveCfg = Debug|Any CPU 47 | {BA3D28A2-47E6-4701-B3E8-784686B19F52}.Debug|x86.Build.0 = Debug|Any CPU 48 | {BA3D28A2-47E6-4701-B3E8-784686B19F52}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {BA3D28A2-47E6-4701-B3E8-784686B19F52}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {BA3D28A2-47E6-4701-B3E8-784686B19F52}.Release|x64.ActiveCfg = Release|Any CPU 51 | {BA3D28A2-47E6-4701-B3E8-784686B19F52}.Release|x64.Build.0 = Release|Any CPU 52 | {BA3D28A2-47E6-4701-B3E8-784686B19F52}.Release|x86.ActiveCfg = Release|Any CPU 53 | {BA3D28A2-47E6-4701-B3E8-784686B19F52}.Release|x86.Build.0 = Release|Any CPU 54 | {827B05D1-DD4D-4AA1-BC7F-C5FF2C5A7C9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {827B05D1-DD4D-4AA1-BC7F-C5FF2C5A7C9B}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {827B05D1-DD4D-4AA1-BC7F-C5FF2C5A7C9B}.Debug|x64.ActiveCfg = Debug|Any CPU 57 | {827B05D1-DD4D-4AA1-BC7F-C5FF2C5A7C9B}.Debug|x64.Build.0 = Debug|Any CPU 58 | {827B05D1-DD4D-4AA1-BC7F-C5FF2C5A7C9B}.Debug|x86.ActiveCfg = Debug|Any CPU 59 | {827B05D1-DD4D-4AA1-BC7F-C5FF2C5A7C9B}.Debug|x86.Build.0 = Debug|Any CPU 60 | {827B05D1-DD4D-4AA1-BC7F-C5FF2C5A7C9B}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {827B05D1-DD4D-4AA1-BC7F-C5FF2C5A7C9B}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {827B05D1-DD4D-4AA1-BC7F-C5FF2C5A7C9B}.Release|x64.ActiveCfg = Release|Any CPU 63 | {827B05D1-DD4D-4AA1-BC7F-C5FF2C5A7C9B}.Release|x64.Build.0 = Release|Any CPU 64 | {827B05D1-DD4D-4AA1-BC7F-C5FF2C5A7C9B}.Release|x86.ActiveCfg = Release|Any CPU 65 | {827B05D1-DD4D-4AA1-BC7F-C5FF2C5A7C9B}.Release|x86.Build.0 = Release|Any CPU 66 | {565B3EAB-A5BF-4B6D-8A4F-EF13E0ABD667}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {565B3EAB-A5BF-4B6D-8A4F-EF13E0ABD667}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {565B3EAB-A5BF-4B6D-8A4F-EF13E0ABD667}.Debug|x64.ActiveCfg = Debug|Any CPU 69 | {565B3EAB-A5BF-4B6D-8A4F-EF13E0ABD667}.Debug|x64.Build.0 = Debug|Any CPU 70 | {565B3EAB-A5BF-4B6D-8A4F-EF13E0ABD667}.Debug|x86.ActiveCfg = Debug|Any CPU 71 | {565B3EAB-A5BF-4B6D-8A4F-EF13E0ABD667}.Debug|x86.Build.0 = Debug|Any CPU 72 | {565B3EAB-A5BF-4B6D-8A4F-EF13E0ABD667}.Release|Any CPU.ActiveCfg = Release|Any CPU 73 | {565B3EAB-A5BF-4B6D-8A4F-EF13E0ABD667}.Release|Any CPU.Build.0 = Release|Any CPU 74 | {565B3EAB-A5BF-4B6D-8A4F-EF13E0ABD667}.Release|x64.ActiveCfg = Release|Any CPU 75 | {565B3EAB-A5BF-4B6D-8A4F-EF13E0ABD667}.Release|x64.Build.0 = Release|Any CPU 76 | {565B3EAB-A5BF-4B6D-8A4F-EF13E0ABD667}.Release|x86.ActiveCfg = Release|Any CPU 77 | {565B3EAB-A5BF-4B6D-8A4F-EF13E0ABD667}.Release|x86.Build.0 = Release|Any CPU 78 | EndGlobalSection 79 | GlobalSection(SolutionProperties) = preSolution 80 | HideSolutionNode = FALSE 81 | EndGlobalSection 82 | GlobalSection(NestedProjects) = preSolution 83 | {2566FB6E-5A30-41AD-B958-A96C3837808C} = {072FA63B-55FD-498D-A847-47F218D7EA49} 84 | EndGlobalSection 85 | GlobalSection(ExtensibilityGlobals) = postSolution 86 | SolutionGuid = {0D684D8E-9241-4ACE-B751-AE03F302FB1D} 87 | EndGlobalSection 88 | EndGlobal 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebSharper.Mvu 2 | 3 | WebSharper.Mvu provides an [Elm](https://guide.elm-lang.org/architecture/)-inspired MVU (Model-View-Update) architecture for WebSharper client-side applications. 4 | 5 | It is based on [WebSharper.UI](http://developers.websharper.com/docs/v4.x/fs/ui) for its reactivity and HTML rendering. 6 | 7 | ## The MVU architecture 8 | 9 | Model-View-Update is an application architecture that aims to make the behavior and state of GUIs clear and predictable. 10 | 11 | The state of the application is stored as a single **Model**, which is an immutable value (generally a record). 12 | 13 | This is rendered by a **View** [1], which defines how the model is transformed into DOM elements. 14 | 15 | Finally, all changes to the model are applied by a pure **Update** function, which takes messages sent by the view and applies changes accordingly. 16 | 17 | [1] Although in WebSharper.Mvu we tend to use the term **Render** instead, to avoid confusion with the WebSharper.UI `View` type. 18 | 19 | ## Features of WebSharper.Mvu 20 | 21 | WebSharper.Mvu provides a number of features on top of this architecture. 22 | 23 | ### Time-travel debugging with RemoteDev 24 | 25 | WebSharper.Mvu integrates seamlessly with [RemoteDev](https://github.com/zalmoxisus/remotedev). This tool allows you to inspect the successive messages and states of your model, and even to replay old states and see the effect on your view. 26 | 27 | ![RemoteDev screenshot](docs/images/remotedev.png) 28 | 29 | This is done by adding a single line to your app declaration: 30 | 31 | ```fsharp 32 | App.Create initialModel update render 33 | |> App.WithRemoteDev (RemoteDev.Options(hostname = "localhost", port = 8000)) 34 | |> App.Run 35 | ``` 36 | 37 | [Learn more about WebSharper.Mvu and RemoteDev.](docs/remotedev.md) 38 | 39 | ### Automatic local storage 40 | 41 | WebSharper.Mvu can automatically save the model to the local storage on every change. This allows you to keep the same application state across page refreshes, which is very useful for debugging. 42 | 43 | This is done by adding a single line to your app declaration: 44 | 45 | ```fsharp 46 | App.Create initialModel update render 47 | |> App.WithLocalStorage "key" 48 | |> App.Run 49 | ``` 50 | 51 | > If you want to use _both_ local storage and RemoteDev, you must add `App.WithLocalStorage` _before_ `App.WithRemoteDev`, or you will run into problems with RemoteDev. 52 | 53 | ### HTML templating 54 | 55 | WebSharper.Mvu can make use of WebSharper.UI's HTML templating facilities. This reinforces the separation of concerns by keeping the view contained in HTML files. The render function then just connects reactive content and event handlers to the strongly-typed template holes. 56 | 57 | Templating also allows you to touch up your view without having to recompile the application. 58 | 59 | [Learn more about WebSharper.UI HTML templating.](http://developers.websharper.com/docs/v4.x/fs/ui#templating) 60 | 61 | ### Paging 62 | 63 | The `Page` type makes it easy to write "multi-page SPAs": applications that are entirely client-side but still logically divided into different pages. It handles parameterized pages and allows using CSS transitions between pages. Pages can specify their DOM behavior, such as keeping elements around to allow for smoother transitions. 64 | 65 | Here is a small application that demonstrates this. [You can run it live on TrywebSharper.](http://try.websharper.com/snippet/loic.denuziere/0000Kc) 66 | 67 | ![Paging with transitions](docs/images/paging.gif) 68 | 69 | This is the structure of [the view for the above application](https://github.com/dotnet-websharper/mvu/blob/master/WebSharper.Mvu.Tests/Client.fs): 70 | 71 | ```fsharp 72 | type EndPoint = Home | EditEntry of string 73 | 74 | type Model = { EndPoint : EndPoint; (* ... *) } 75 | 76 | module Pages = 77 | 78 | let Home = Page.Single(attrs = [Attr.Class "home-page"], render = fun dispatch model -> 79 | // ... 80 | ) 81 | 82 | let EditEntry = Page.Create(attrs = [Attr.Class "entry-page"], render = fun key dispatch model -> 83 | // ... 84 | ) 85 | 86 | let render model = 87 | match model.EndPoint with 88 | | EndPoint.Home -> Pages.Home () 89 | | EndPoint.EditEntry key -> Pages.EditEntry key 90 | 91 | let main () = 92 | App.CreatePaged initialModel update render 93 | |> App.Run 94 | ``` 95 | 96 | The transitions are specified as [CSS transitions on the `home-page` and `entry-page` classes](https://github.com/dotnet-websharper/mvu/blob/master/WebSharper.Mvu.Tests/wwwroot/index.html). 97 | 98 | ### Routing 99 | 100 | The page's URL can be easily bound to the application model. The URL scheme is declared using a [WebSharper router](http://developers.websharper.com/docs/v4.x/fs/sitelets#sitelet-infer), and the parsed endpoint value is stored as a field in the model. 101 | 102 | Routing and paging work nicely together, but neither requires the other. 103 | 104 | Routing is implemented by adding a single line to your app declaration: 105 | 106 | ```fsharp 107 | type EndPoint = // ... 108 | 109 | type Model = { EndPoint : EndPoint; (* ... *) } 110 | 111 | let app = App.Create initialModel update render 112 | App.WithRouting (Router.Infer()) (fun model -> model.EndPoint) app 113 | |> App.Run 114 | ``` 115 | 116 | ## Differences with other MVU libraries 117 | 118 | The main point that differenciates WebSharper.Mvu from other MVU libraries is the way the render function works. 119 | 120 | In most MVU libraries, the view function directly takes a Model value as argument. It is called every time the model changes, and returns a new representation of the rendered document every time. This new representation is then applied to the DOM by a diffing DOM library such as React. 121 | 122 | In contrast, in WebSharper.Mvu, the render function takes a WebSharper.UI `View` as argument. It is called only once, and it is this `View` that changes every time the model is updated. This helps make more explicit which parts of the rendered document are static and which parts are reactive. 123 | 124 | ## Learn more... 125 | 126 | Try WebSharper.Mvu examples live: 127 | 128 | * [A simple counter](http://try.websharper.com/#/snippet/loic.denuziere/0000Kf) 129 | * [A list of counters](http://try.websharper.com/#/snippet/loic.denuziere/0000Kg) 130 | * [Paging](http://try.websharper.com/snippet/loic.denuziere/0000Kc) 131 | * [TodoMVC](http://try.websharper.com/#/snippet/loic.denuziere/0000Kj) 132 | 133 | Sample repositories: 134 | 135 | * [TodoMVC](https://github.com/websharper-samples/Mvu) 136 | * [CRUD client](https://github.com/websharper-samples/PeopleClient) 137 | 138 | Learn more about WebSharper: 139 | 140 | * [WebSharper](https://websharper.com) 141 | * [WebSharper UI](http://developers.websharper.com/docs/v4.x/fs/ui) 142 | * [WebSharper Forums](https://forums.websharper.com) 143 | -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/wwwroot/Scripts/WebSharper.Core.JavaScript/Runtime.js: -------------------------------------------------------------------------------- 1 | export function Create(ctor, copyFrom) { 2 | return Object.assign(Object.create(ctor.prototype), copyFrom); 3 | } 4 | 5 | export function Clone(obj) { 6 | return Object.assign(Object.create(Object.getPrototypeOf(obj)), obj); 7 | } 8 | 9 | const forceSymbol = Symbol("force") 10 | export function Force(obj) { obj[forceSymbol] } 11 | 12 | export function Lazy(factory) { 13 | var instance; 14 | function getInstance() { 15 | if (!instance) { 16 | instance = factory(i => instance = i); 17 | } 18 | return instance; 19 | } 20 | let res = new Proxy(function () { }, { 21 | get(_, key) { 22 | if (key == forceSymbol) { 23 | getInstance(); 24 | } 25 | return getInstance()[key]; 26 | }, 27 | set(_, key, value) { 28 | getInstance()[key] = value; 29 | return true; 30 | }, 31 | construct(_, args, newTarget) { 32 | return Reflect.construct(getInstance(), args, newTarget); 33 | } 34 | }); 35 | return res; 36 | } 37 | 38 | export function PrintObject(obj) { 39 | let res = "{ "; 40 | let empty = true; 41 | for (var field of Object.getOwnPropertyNames(obj)) { 42 | if (empty) { 43 | empty = false; 44 | } else { 45 | res += ", "; 46 | } 47 | res += field + " = " + obj[field]; 48 | } 49 | if (empty) { 50 | res += "}"; 51 | } else { 52 | res += " }"; 53 | } 54 | return res; 55 | } 56 | 57 | export function DeleteEmptyFields(obj, fields) { 58 | for (var i = 0; i < fields.length; i++) { 59 | var f = fields[i]; 60 | if (obj[f] === void 0) { delete obj[f]; } 61 | } 62 | return obj; 63 | } 64 | 65 | export function GetOptional(value) { 66 | return (value === void 0) ? null : { $: 1, $0: value }; 67 | } 68 | 69 | export function SetOptional(obj, field, value) { 70 | if (value) { 71 | obj[field] = value.$0; 72 | } else { 73 | delete obj[field]; 74 | } 75 | } 76 | 77 | export function SetOrDelete(obj, field, value) { 78 | if (value === void 0) { 79 | delete obj[field]; 80 | } else { 81 | obj[field] = value; 82 | } 83 | } 84 | 85 | export function Apply(f, obj, args) { 86 | return f.apply(obj, args); 87 | } 88 | 89 | export function Bind(f, obj) { 90 | return function (...args) { return f.apply(obj, args) }; 91 | } 92 | 93 | export function CreateFuncWithArgs(f) { 94 | return function (...args) { return f(args) }; 95 | } 96 | 97 | export function CreateFuncWithOnlyThis(f) { 98 | return function () { return f(this) }; 99 | } 100 | 101 | export function CreateFuncWithThis(f) { 102 | return function (...args) { return f(this)(...args) }; 103 | } 104 | 105 | export function CreateFuncWithThisArgs(f) { 106 | return function (...args) { return f(this)(args) }; 107 | } 108 | 109 | export function CreateFuncWithRest(length, f) { 110 | return function (...args) { return f([...(args.slice(0, length)), args.slice(length)]) }; 111 | } 112 | 113 | export function CreateFuncWithArgsRest(length, f) { 114 | return function (...args) { return f([args.slice(0, length), args.slice(length)]) }; 115 | } 116 | 117 | export function BindDelegate(func, obj) { 118 | var res = func.bind(obj); 119 | res.$Func = func; 120 | res.$Target = obj; 121 | return res; 122 | } 123 | 124 | export function CreateDelegate(invokes) { 125 | if (invokes.length == 0) return null; 126 | if (invokes.length == 1) return invokes[0]; 127 | var del = function () { 128 | var res; 129 | for (var i = 0; i < invokes.length; i++) { 130 | res = invokes[i].apply(null, arguments); 131 | } 132 | return res; 133 | }; 134 | del.$Invokes = invokes; 135 | return del; 136 | } 137 | 138 | export function CombineDelegates(dels) { 139 | var invokes = []; 140 | for (var i = 0; i < dels.length; i++) { 141 | var del = dels[i]; 142 | if (del) { 143 | if ("$Invokes" in del) 144 | invokes = invokes.concat(del.$Invokes); 145 | else 146 | invokes.push(del); 147 | } 148 | } 149 | return CreateDelegate(invokes); 150 | } 151 | 152 | export function DelegateEqual(d1, d2) { 153 | if (d1 === d2) return true; 154 | if (d1 == null || d2 == null) return false; 155 | var i1 = d1.$Invokes || [d1]; 156 | var i2 = d2.$Invokes || [d2]; 157 | if (i1.length != i2.length) return false; 158 | for (var i = 0; i < i1.length; i++) { 159 | var e1 = i1[i]; 160 | var e2 = i2[i]; 161 | if (!(e1 === e2 || ("$Func" in e1 && "$Func" in e2 && e1.$Func === e2.$Func && e1.$Target == e2.$Target))) 162 | return false; 163 | } 164 | return true; 165 | } 166 | 167 | export function ThisFunc(d) { 168 | return function (...args) { 169 | args.unshift(this); 170 | return d.apply(null, args); 171 | }; 172 | } 173 | 174 | export function ThisFuncOut(f) { 175 | return function (...args) { 176 | return f.apply(args.shift(), args); 177 | }; 178 | } 179 | 180 | export function ParamsFunc(length, d) { 181 | return function (...args) { 182 | return d.apply(null, args.slice(0, length).concat([args.slice(length)])); 183 | }; 184 | } 185 | 186 | export function ParamsFuncOut(length, f) { 187 | return function (...args) { 188 | return f.apply(null, args.slice(0, length).concat(args[length])); 189 | }; 190 | } 191 | 192 | export function ThisParamsFunc(length, d) { 193 | return function (...args) { 194 | args.unshift(this); 195 | return d.apply(null, args.slice(0, length + 1).concat([args.slice(length + 1)])); 196 | }; 197 | } 198 | 199 | export function ThisParamsFuncOut(length, f) { 200 | return function (...args) { 201 | return f.apply(args.shift(), args.slice(0, length).concat(args[length])); 202 | }; 203 | } 204 | 205 | export function Curried(f, n, args) { 206 | args = args || []; 207 | return (a) => { 208 | var allArgs = args.concat([a === void 0 ? null : a]); 209 | if (n == 1) 210 | return f(...allArgs); 211 | if (n == 2) 212 | return (a) => f(...allArgs, a === void 0 ? null : a); 213 | return Curried(f, n - 1, allArgs); 214 | } 215 | } 216 | 217 | export function Curried2(f) { 218 | return (a) => (b) => f(a, b); 219 | } 220 | 221 | export function Curried3(f) { 222 | return (a) => (b) => (c) => f(a, b, c); 223 | } 224 | 225 | export function UnionByType(types, value, optional) { 226 | var vt = typeof value; 227 | for (var i = 0; i < types.length; i++) { 228 | var t = types[i]; 229 | if (typeof t == "number") { 230 | if (Array.isArray(value) && (t == 0 || value.length == t)) { 231 | return { $: i, $0: value }; 232 | } 233 | } else { 234 | if (t == vt) { 235 | return { $: i, $0: value }; 236 | } 237 | } 238 | } 239 | if (!optional) { 240 | throw new Error("Type not expected for creating Choice value."); 241 | } 242 | } 243 | 244 | export function MarkResizable(arr) { 245 | Object.defineProperty(arr, "resizable", { enumerable: false, writable: false, configurable: false, value: true }); 246 | return arr; 247 | } 248 | 249 | export function MarkReadOnly(arr) { 250 | Object.defineProperty(arr, "readonly", { enumerable: false, writable: false, configurable: false, value: true }); 251 | return arr; 252 | } 253 | 254 | const Runtime = globalThis.WebSharperRuntime || { 255 | ScriptBasePath: "./", 256 | ScriptSkipAssemblyDir: false 257 | } 258 | 259 | globalThis.WebSharperRuntime = Runtime; 260 | export default Runtime; 261 | 262 | export function ScriptPath(a, f) { 263 | return Runtime.ScriptBasePath + (Runtime.ScriptSkipAssemblyDir ? "" : a + "/") + f; 264 | } 265 | 266 | export function GetterOf(o, n) { 267 | return Object.getOwnPropertyDescriptor(o, n).get; 268 | } 269 | 270 | export function SetterOf(o, n) { 271 | return Object.getOwnPropertyDescriptor(o, n).set; 272 | } 273 | 274 | let scriptsLoaded = []; 275 | 276 | export function LoadScript(u) { 277 | if (!scriptsLoaded.some(s => s == u.toLowerCase())) { 278 | if (!u.startsWith("http")) { 279 | u = Runtime.ScriptBasePath + u; 280 | } 281 | let xhr = new XMLHttpRequest(); 282 | xhr.open("GET", u, false); 283 | xhr.send(null); 284 | scriptsLoaded.push(u.toLowerCase()); 285 | if (xhr.status == 200) { 286 | globalThis.eval(xhr.responseText); 287 | } else { 288 | console.error("LoadScript failed:", u, xhr.statusText) 289 | } 290 | } 291 | } 292 | 293 | export function ignore() { } 294 | export function id(x) { return x } 295 | export function fst(x) { return x[0] } 296 | export function snd(x) { return x[1] } 297 | export function trd(x) { return x[2] } 298 | -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/wwwroot/Scripts/WebSharper.Core.JavaScript/Runtime.ts: -------------------------------------------------------------------------------- 1 | export function Create(ctor: { prototype: T }, copyFrom: U) : T & U { 2 | return Object.assign(Object.create(ctor.prototype), copyFrom); 3 | } 4 | 5 | export function Clone(obj: T): T { 6 | return Object.assign(Object.create(Object.getPrototypeOf(obj)), obj); 7 | } 8 | 9 | const forceSymbol = Symbol("force") 10 | export function Force(obj) { obj[forceSymbol] } 11 | 12 | export function Lazy(factory: (setter : (I) => I) => C) : C { 13 | var instance; 14 | function getInstance() { 15 | if (!instance) { 16 | instance = factory(i => instance = i); 17 | } 18 | return instance; 19 | } 20 | let res = new Proxy(function () { }, { 21 | get(_, key) { 22 | if (key == forceSymbol) { 23 | getInstance(); 24 | } 25 | return getInstance()[key]; 26 | }, 27 | set(_, key, value) { 28 | getInstance()[key] = value; 29 | return true; 30 | }, 31 | construct(_, args, newTarget) { 32 | return Reflect.construct(getInstance(), args, newTarget); 33 | } 34 | }); 35 | return res; 36 | } 37 | 38 | export function PrintObject(obj) { 39 | let res = "{ "; 40 | let empty = true; 41 | for (var field of Object.getOwnPropertyNames(obj)) { 42 | if (empty) { 43 | empty = false; 44 | } else { 45 | res += ", "; 46 | } 47 | res += field + " = " + obj[field]; 48 | } 49 | if (empty) { 50 | res += "}"; 51 | } else { 52 | res += " }"; 53 | } 54 | return res; 55 | } 56 | 57 | export function DeleteEmptyFields(obj, fields) { 58 | for (var i = 0; i < fields.length; i++) { 59 | var f = fields[i]; 60 | if (obj[f] === void 0) { delete obj[f]; } 61 | } 62 | return obj; 63 | } 64 | 65 | export function GetOptional(value) { 66 | return (value === void 0) ? null : { $: 1, $0: value }; 67 | } 68 | 69 | export function SetOptional(obj, field, value) { 70 | if (value) { 71 | obj[field] = value.$0; 72 | } else { 73 | delete obj[field]; 74 | } 75 | } 76 | 77 | export function SetOrDelete(obj, field, value) { 78 | if (value === void 0) { 79 | delete obj[field]; 80 | } else { 81 | obj[field] = value; 82 | } 83 | } 84 | 85 | export function Apply(f, obj, args) { 86 | return f.apply(obj, args); 87 | } 88 | 89 | export function Bind(f, obj) { 90 | return function (...args) { return f.apply(obj, args) }; 91 | } 92 | 93 | export function CreateFuncWithArgs(f) { 94 | return function (...args) { return f(args) }; 95 | } 96 | 97 | export function CreateFuncWithOnlyThis(f) { 98 | return function () { return f(this) }; 99 | } 100 | 101 | export function CreateFuncWithThis(f) { 102 | return function (...args) { return f(this)(...args) }; 103 | } 104 | 105 | export function CreateFuncWithThisArgs(f) { 106 | return function (...args) { return f(this)(args) }; 107 | } 108 | 109 | export function CreateFuncWithRest(length, f) { 110 | return function (...args) { return f([...(args.slice(0, length)), args.slice(length)]) }; 111 | } 112 | 113 | export function CreateFuncWithArgsRest(length, f) { 114 | return function (...args) { return f([args.slice(0, length), args.slice(length)]) }; 115 | } 116 | 117 | export function BindDelegate(func, obj) { 118 | var res = func.bind(obj); 119 | res.$Func = func; 120 | res.$Target = obj; 121 | return res; 122 | } 123 | 124 | export function CreateDelegate(invokes) { 125 | if (invokes.length == 0) return null; 126 | if (invokes.length == 1) return invokes[0]; 127 | var del = function () { 128 | var res; 129 | for (var i = 0; i < invokes.length; i++) { 130 | res = invokes[i].apply(null, arguments); 131 | } 132 | return res; 133 | }; 134 | (del).$Invokes = invokes; 135 | return del; 136 | } 137 | 138 | export function CombineDelegates(dels) { 139 | var invokes = []; 140 | for (var i = 0; i < dels.length; i++) { 141 | var del = dels[i]; 142 | if (del) { 143 | if ("$Invokes" in del) 144 | invokes = invokes.concat(del.$Invokes); 145 | else 146 | invokes.push(del); 147 | } 148 | } 149 | return CreateDelegate(invokes); 150 | } 151 | 152 | export function DelegateEqual(d1, d2) { 153 | if (d1 === d2) return true; 154 | if (d1 == null || d2 == null) return false; 155 | var i1 = d1.$Invokes || [d1]; 156 | var i2 = d2.$Invokes || [d2]; 157 | if (i1.length != i2.length) return false; 158 | for (var i = 0; i < i1.length; i++) { 159 | var e1 = i1[i]; 160 | var e2 = i2[i]; 161 | if (!(e1 === e2 || ("$Func" in e1 && "$Func" in e2 && e1.$Func === e2.$Func && e1.$Target == e2.$Target))) 162 | return false; 163 | } 164 | return true; 165 | } 166 | 167 | export function ThisFunc(d) { 168 | return function (...args) { 169 | args.unshift(this); 170 | return d.apply(null, args); 171 | }; 172 | } 173 | 174 | export function ThisFuncOut(f) { 175 | return function (...args) { 176 | return f.apply(args.shift(), args); 177 | }; 178 | } 179 | 180 | export function ParamsFunc(length, d) { 181 | return function (...args) { 182 | return d.apply(null, args.slice(0, length).concat([args.slice(length)])); 183 | }; 184 | } 185 | 186 | export function ParamsFuncOut(length, f) { 187 | return function (...args) { 188 | return f.apply(null, args.slice(0, length).concat(args[length])); 189 | }; 190 | } 191 | 192 | export function ThisParamsFunc(length, d) { 193 | return function (...args) { 194 | args.unshift(this); 195 | return d.apply(null, args.slice(0, length + 1).concat([args.slice(length + 1)])); 196 | }; 197 | } 198 | 199 | export function ThisParamsFuncOut(length, f) { 200 | return function (...args) { 201 | return f.apply(args.shift(), args.slice(0, length).concat(args[length])); 202 | }; 203 | } 204 | 205 | export function Curried(f, n, args) { 206 | args = args || []; 207 | return (a) => { 208 | var allArgs = args.concat([a === void 0 ? null : a]); 209 | if (n == 1) 210 | return f(...allArgs); 211 | if (n == 2) 212 | return (a) => f(...allArgs, a === void 0 ? null : a); 213 | return Curried(f, n - 1, allArgs); 214 | } 215 | } 216 | 217 | export function Curried2(f) { 218 | return (a) => (b) => f(a, b); 219 | } 220 | 221 | export function Curried3(f) { 222 | return (a) => (b) => (c) => f(a, b, c); 223 | } 224 | 225 | export function UnionByType(types, value, optional) { 226 | var vt = typeof value; 227 | for (var i = 0; i < types.length; i++) { 228 | var t = types[i]; 229 | if (typeof t == "number") { 230 | if (Array.isArray(value) && (t == 0 || value.length == t)) { 231 | return { $: i, $0: value }; 232 | } 233 | } else { 234 | if (t == vt) { 235 | return { $: i, $0: value }; 236 | } 237 | } 238 | } 239 | if (!optional) { 240 | throw new Error("Type not expected for creating Choice value."); 241 | } 242 | } 243 | 244 | export function MarkResizable(arr) { 245 | Object.defineProperty(arr, "resizable", { enumerable: false, writable: false, configurable: false, value: true }); 246 | return arr; 247 | } 248 | 249 | export function MarkReadOnly(arr) { 250 | Object.defineProperty(arr, "readonly", { enumerable: false, writable: false, configurable: false, value: true }); 251 | return arr; 252 | } 253 | 254 | const Runtime = globalThis.WebSharperRuntime || { 255 | ScriptBasePath: "./", 256 | ScriptSkipAssemblyDir: false 257 | } 258 | 259 | globalThis.WebSharperRuntime = Runtime; 260 | export default Runtime; 261 | 262 | export function ScriptPath(a, f) { 263 | return Runtime.ScriptBasePath + (Runtime.ScriptSkipAssemblyDir ? "" : a + "/") + f; 264 | } 265 | 266 | export function GetterOf(o, n) { 267 | return Object.getOwnPropertyDescriptor(o, n).get; 268 | } 269 | 270 | export function SetterOf(o, n) { 271 | return Object.getOwnPropertyDescriptor(o, n).set; 272 | } 273 | 274 | let scriptsLoaded = []; 275 | 276 | export function LoadScript(u) { 277 | if (!scriptsLoaded.some(s => s == u.toLowerCase())) { 278 | if (!u.startsWith("http")) { 279 | u = Runtime.ScriptBasePath + u; 280 | } 281 | let xhr = new XMLHttpRequest(); 282 | xhr.open("GET", u, false); 283 | xhr.send(null); 284 | scriptsLoaded.push(u.toLowerCase()); 285 | if (xhr.status == 200) { 286 | globalThis.eval(xhr.responseText); 287 | } else { 288 | console.error("LoadScript failed:", u, xhr.statusText) 289 | } 290 | } 291 | } 292 | 293 | export function ignore() { } 294 | export function id(x : T) : T { return x } 295 | export function fst(x: { [0]: T }) : T { return x[0] } 296 | export function snd(x: { [1]: T }): T { return x[1] } 297 | export function trd(x: { [2]: T }): T { return x[2] } 298 | -------------------------------------------------------------------------------- /WebSharper.Mvu.Tests/Client.fs: -------------------------------------------------------------------------------- 1 | // $begin{copyright} 2 | // 3 | // This file is part of WebSharper 4 | // 5 | // Copyright (c) 2008-2018 IntelliFactory 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); you 8 | // may not use this file except in compliance with the License. You may 9 | // obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 16 | // implied. See the License for the specific language governing 17 | // permissions and limitations under the License. 18 | // 19 | // $end{copyright} 20 | namespace WebSharper.Mvu.Tests 21 | 22 | open WebSharper 23 | open WebSharper.JavaScript 24 | open WebSharper.UI 25 | open WebSharper.UI.Client 26 | open WebSharper.UI.Html 27 | open WebSharper.UI.Templating 28 | open WebSharper.Mvu 29 | 30 | module Remoting = 31 | 32 | [] 33 | let SendToServer (entries: Map) = 34 | async { 35 | return Map.count entries 36 | } 37 | 38 | [] 39 | module Client = 40 | 41 | type EndPoint = 42 | | Home 43 | | EditEntry of string 44 | 45 | type Model = 46 | { 47 | EndPoint : EndPoint 48 | Entries : Map 49 | Input : string 50 | ServerResponse : option 51 | Counter : int 52 | } 53 | 54 | [] 55 | type Message = 56 | | Goto of endpoint: EndPoint 57 | | SetEntry of key: string * value: string 58 | | RemoveEntry of key: string 59 | | SendToServer 60 | | ServerReplied of response: int 61 | | AddTwoToCounter 62 | 63 | let Update (message: Message) (model: Model) = 64 | match message with 65 | | SetEntry (k, v) -> 66 | SetModel { model with Entries = Map.add k v model.Entries } 67 | | RemoveEntry k -> 68 | SetModel { 69 | model with 70 | Entries = Map.remove k model.Entries 71 | EndPoint = 72 | match model.EndPoint with 73 | | EditEntry k' when k = k' -> Home 74 | | ep -> ep 75 | } 76 | | Goto ep -> 77 | SetModel { model with EndPoint = ep } 78 | | SendToServer -> 79 | DispatchAsync ServerReplied (Remoting.SendToServer model.Entries) 80 | | ServerReplied x -> 81 | SetModel { model with ServerResponse = Some x } 82 | | AddTwoToCounter -> 83 | UpdateModel (fun m -> { m with Counter = m.Counter + 1 }) 84 | + 85 | UpdateModel (fun m -> { m with Counter = m.Counter + 1 }) 86 | 87 | module Pages = 88 | 89 | let showDate() = 90 | p [] [text ("Rendered at " + Date().ToTimeString())] 91 | 92 | let counter dispatch (model: View) = 93 | p [] [ 94 | text (string model.V.Counter + " ") 95 | button [on.click (fun _ _ -> dispatch AddTwoToCounter)] [text "Increment by two"] 96 | text " (this tests that UpdateModel works correctly)" 97 | ] 98 | 99 | let Home = Page.Single(attrs = [Attr.Class "home-page"], usesTransition = true, render = fun dispatch model -> 100 | let inp = Elt.input [Attr.Class "input"] [] 101 | let kinp = Elt.input [Attr.Class "input"] [] 102 | let vinp = Elt.input [Attr.Class "input"] [] 103 | Doc.Concat [ 104 | showDate() 105 | counter dispatch model 106 | h2 [Attr.Class "subtitle hidden"] [text "Entries:"] 107 | div [Attr.Class "section"] [ 108 | div [Attr.Class "field has-addons"] [ 109 | div [Attr.Class "control"] [inp] 110 | div [Attr.Class "control"] [ 111 | button [ 112 | Attr.Class "button" 113 | on.click (fun _ _ -> dispatch (Goto (EndPoint.EditEntry inp.Value))) 114 | ] [text "Go to add/edit entry page"] 115 | ] 116 | ] 117 | ] 118 | div [Attr.Class "section"] [ 119 | div [Attr.Class "field has-addons"] [ 120 | div [Attr.Class "control"] [kinp] 121 | div [Attr.Class "control"] [vinp] 122 | div [Attr.Class "control"] [ 123 | button [ 124 | Attr.Class "button" 125 | on.click (fun _ _ -> dispatch (SetEntry(kinp.Value, vinp.Value))) 126 | ] [text "Directly add/edit entry"] 127 | ] 128 | ] 129 | ] 130 | table [Attr.Class "table is-fullwidth"] [ 131 | V(Map.toSeq model.V.Entries).DocSeqCached(fst, fun key (v: View) -> 132 | tr [] [ 133 | th [] [text key] 134 | td [] [text (snd v.V)] 135 | td [] [ 136 | div [Attr.Class "buttons has-addons"] [ 137 | button [ 138 | Attr.Class "button is-small" 139 | on.click (fun _ _ -> dispatch (Goto (EndPoint.EditEntry key))) 140 | ] [text "Edit"] 141 | button [ 142 | Attr.Class "button is-small" 143 | on.click (fun _ _ -> dispatch (RemoveEntry key)) 144 | ] [text "Remove"] 145 | ] 146 | ] 147 | ]) 148 | ] 149 | div [Attr.Class "field"] [ 150 | div [Attr.Class "control"] [ 151 | button [ 152 | Attr.Class "button" 153 | on.click (fun _ _ -> dispatch SendToServer) 154 | ] [text "Send to server"] 155 | ] 156 | ] 157 | label [Attr.Class "label"] [ 158 | text (match model.V.ServerResponse with None -> "" | Some x -> string x) 159 | ] 160 | ]) 161 | 162 | let EditEntry = Page.Create(attrs = [Attr.Class "entry-page"], usesTransition = true, render = fun key dispatch model -> 163 | let value = V(Map.tryFind key model.V.Entries |> Option.defaultValue "") 164 | let var = Var.Make value (fun v -> dispatch (SetEntry (key, v))) 165 | div [Attr.Class "section"] [ 166 | showDate() 167 | label [Attr.Class "label"] [text ("Editing value for key: " + key)] 168 | div [Attr.Class "field has-addons"] [ 169 | div [Attr.Class "control"] [Doc.Input [Attr.Class "input"] var] 170 | div [Attr.Class "control"] [ 171 | button [ 172 | Attr.Class "button" 173 | on.click (fun _ _ -> dispatch (Goto EndPoint.Home)) 174 | ] [text "Ok"] 175 | ] 176 | button [ 177 | Attr.Class "button" 178 | on.click (fun _ _ -> dispatch (RemoveEntry key)) 179 | ] [text "Remove"] 180 | ] 181 | ]) 182 | 183 | let Render mdl = 184 | match mdl.EndPoint with 185 | | EndPoint.Home -> Pages.Home () 186 | | EndPoint.EditEntry key -> Pages.EditEntry key 187 | 188 | let InitModel = 189 | { 190 | EndPoint = EndPoint.Home 191 | Entries = Map.empty 192 | Input = "" 193 | ServerResponse = None 194 | Counter = 0 195 | } 196 | 197 | [] 198 | let Main () = 199 | App.CreatePaged InitModel Update Render 200 | |> App.WithLocalStorage "mvu-tests" 201 | |> App.WithLog (fun msg model -> 202 | New ["msg" => Json.Encode msg; "model" => Json.Encode model] 203 | |> Console.Log) 204 | |> App.Run 205 | |> Doc.RunPrepend JS.Document.Body 206 | -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/wwwroot/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | font-weight: inherit; 16 | color: inherit; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | } 22 | 23 | body { 24 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 25 | line-height: 1.4em; 26 | background: #f5f5f5; 27 | color: #4d4d4d; 28 | min-width: 230px; 29 | max-width: 550px; 30 | margin: 0 auto; 31 | -webkit-font-smoothing: antialiased; 32 | -moz-osx-font-smoothing: grayscale; 33 | font-weight: 300; 34 | } 35 | 36 | :focus { 37 | outline: 0; 38 | } 39 | 40 | .hidden { 41 | display: none; 42 | } 43 | 44 | .todoapp { 45 | background: #fff; 46 | margin: 130px 0 40px 0; 47 | position: relative; 48 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 49 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 50 | } 51 | 52 | .todoapp input::-webkit-input-placeholder { 53 | font-style: italic; 54 | font-weight: 300; 55 | color: #e6e6e6; 56 | } 57 | 58 | .todoapp input::-moz-placeholder { 59 | font-style: italic; 60 | font-weight: 300; 61 | color: #e6e6e6; 62 | } 63 | 64 | .todoapp input::input-placeholder { 65 | font-style: italic; 66 | font-weight: 300; 67 | color: #e6e6e6; 68 | } 69 | 70 | .todoapp h1 { 71 | position: absolute; 72 | top: -155px; 73 | width: 100%; 74 | font-size: 100px; 75 | font-weight: 100; 76 | text-align: center; 77 | color: rgba(175, 47, 47, 0.15); 78 | -webkit-text-rendering: optimizeLegibility; 79 | -moz-text-rendering: optimizeLegibility; 80 | text-rendering: optimizeLegibility; 81 | } 82 | 83 | .new-todo, 84 | .edit { 85 | position: relative; 86 | margin: 0; 87 | width: 100%; 88 | font-size: 24px; 89 | font-family: inherit; 90 | font-weight: inherit; 91 | line-height: 1.4em; 92 | border: 0; 93 | color: inherit; 94 | padding: 6px; 95 | border: 1px solid #999; 96 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 97 | box-sizing: border-box; 98 | -webkit-font-smoothing: antialiased; 99 | -moz-osx-font-smoothing: grayscale; 100 | } 101 | 102 | .new-todo { 103 | padding: 16px 16px 16px 60px; 104 | border: none; 105 | background: rgba(0, 0, 0, 0.003); 106 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 107 | } 108 | 109 | .main { 110 | position: relative; 111 | z-index: 2; 112 | border-top: 1px solid #e6e6e6; 113 | } 114 | 115 | .toggle-all { 116 | width: 1px; 117 | height: 1px; 118 | border: none; /* Mobile Safari */ 119 | opacity: 0; 120 | position: absolute; 121 | right: 100%; 122 | bottom: 100%; 123 | } 124 | 125 | .toggle-all + label { 126 | width: 60px; 127 | height: 34px; 128 | font-size: 0; 129 | position: absolute; 130 | top: -52px; 131 | left: -13px; 132 | -webkit-transform: rotate(90deg); 133 | transform: rotate(90deg); 134 | } 135 | 136 | .toggle-all + label:before { 137 | content: '❯'; 138 | font-size: 22px; 139 | color: #e6e6e6; 140 | padding: 10px 27px 10px 27px; 141 | } 142 | 143 | .toggle-all:checked + label:before { 144 | color: #737373; 145 | } 146 | 147 | .todo-list { 148 | margin: 0; 149 | padding: 0; 150 | list-style: none; 151 | } 152 | 153 | .todo-list li { 154 | position: relative; 155 | font-size: 24px; 156 | border-bottom: 1px solid #ededed; 157 | } 158 | 159 | .todo-list li:last-child { 160 | border-bottom: none; 161 | } 162 | 163 | .todo-list li.editing { 164 | border-bottom: none; 165 | padding: 0; 166 | } 167 | 168 | .todo-list li.editing .edit { 169 | display: block; 170 | width: 506px; 171 | padding: 12px 16px; 172 | margin: 0 0 0 43px; 173 | } 174 | 175 | .todo-list li.editing .view { 176 | display: none; 177 | } 178 | 179 | .todo-list li .toggle { 180 | text-align: center; 181 | width: 40px; 182 | /* auto, since non-WebKit browsers doesn't support input styling */ 183 | height: auto; 184 | position: absolute; 185 | top: 0; 186 | bottom: 0; 187 | margin: auto 0; 188 | border: none; /* Mobile Safari */ 189 | -webkit-appearance: none; 190 | appearance: none; 191 | } 192 | 193 | .todo-list li .toggle { 194 | opacity: 0; 195 | } 196 | 197 | .todo-list li .toggle + label { 198 | /* 199 | Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433 200 | IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/ 201 | */ 202 | background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E'); 203 | background-repeat: no-repeat; 204 | background-position: center left; 205 | } 206 | 207 | .todo-list li .toggle:checked + label { 208 | background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E'); 209 | } 210 | 211 | .todo-list li label { 212 | word-break: break-all; 213 | padding: 15px 15px 15px 60px; 214 | display: block; 215 | line-height: 1.2; 216 | transition: color 0.4s; 217 | } 218 | 219 | .todo-list li.completed label { 220 | color: #d9d9d9; 221 | text-decoration: line-through; 222 | } 223 | 224 | .todo-list li .destroy { 225 | display: none; 226 | position: absolute; 227 | top: 0; 228 | right: 10px; 229 | bottom: 0; 230 | width: 40px; 231 | height: 40px; 232 | margin: auto 0; 233 | font-size: 30px; 234 | color: #cc9a9a; 235 | margin-bottom: 11px; 236 | transition: color 0.2s ease-out; 237 | } 238 | 239 | .todo-list li .destroy:hover { 240 | color: #af5b5e; 241 | } 242 | 243 | .todo-list li .destroy:after { 244 | content: '×'; 245 | } 246 | 247 | .todo-list li:hover .destroy { 248 | display: block; 249 | } 250 | 251 | .todo-list li .edit { 252 | display: none; 253 | } 254 | 255 | .todo-list li.editing:last-child { 256 | margin-bottom: -1px; 257 | } 258 | 259 | .footer { 260 | color: #777; 261 | padding: 10px 15px; 262 | height: 20px; 263 | text-align: center; 264 | border-top: 1px solid #e6e6e6; 265 | } 266 | 267 | .footer:before { 268 | content: ''; 269 | position: absolute; 270 | right: 0; 271 | bottom: 0; 272 | left: 0; 273 | height: 50px; 274 | overflow: hidden; 275 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 276 | 0 8px 0 -3px #f6f6f6, 277 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 278 | 0 16px 0 -6px #f6f6f6, 279 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 280 | } 281 | 282 | .todo-count { 283 | float: left; 284 | text-align: left; 285 | } 286 | 287 | .todo-count strong { 288 | font-weight: 300; 289 | } 290 | 291 | .filters { 292 | margin: 0; 293 | padding: 0; 294 | list-style: none; 295 | position: absolute; 296 | right: 0; 297 | left: 0; 298 | } 299 | 300 | .filters li { 301 | display: inline; 302 | } 303 | 304 | .filters li a { 305 | color: inherit; 306 | margin: 3px; 307 | padding: 3px 7px; 308 | text-decoration: none; 309 | border: 1px solid transparent; 310 | border-radius: 3px; 311 | } 312 | 313 | .filters li a:hover { 314 | border-color: rgba(175, 47, 47, 0.1); 315 | } 316 | 317 | .filters li a.selected { 318 | border-color: rgba(175, 47, 47, 0.2); 319 | } 320 | 321 | .clear-completed, 322 | html .clear-completed:active { 323 | float: right; 324 | position: relative; 325 | line-height: 20px; 326 | text-decoration: none; 327 | cursor: pointer; 328 | } 329 | 330 | .clear-completed:hover { 331 | text-decoration: underline; 332 | } 333 | 334 | .info { 335 | margin: 65px auto 0; 336 | color: #bfbfbf; 337 | font-size: 10px; 338 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 339 | text-align: center; 340 | } 341 | 342 | .info p { 343 | line-height: 1; 344 | } 345 | 346 | .info a { 347 | color: inherit; 348 | text-decoration: none; 349 | font-weight: 400; 350 | } 351 | 352 | .info a:hover { 353 | text-decoration: underline; 354 | } 355 | 356 | /* 357 | Hack to remove background from Mobile Safari. 358 | Can't use it globally since it destroys checkboxes in Firefox 359 | */ 360 | @media screen and (-webkit-min-device-pixel-ratio:0) { 361 | .toggle-all, 362 | .todo-list li .toggle { 363 | background: none; 364 | } 365 | 366 | .todo-list li .toggle { 367 | height: 40px; 368 | } 369 | } 370 | 371 | @media (max-width: 430px) { 372 | .footer { 373 | height: 50px; 374 | } 375 | 376 | .filters { 377 | bottom: 10px; 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /WebSharper.Mvu.TodoMvc/TodoMvc.fs: -------------------------------------------------------------------------------- 1 | // $begin{copyright} 2 | // 3 | // This file is part of WebSharper 4 | // 5 | // Copyright (c) 2008-2018 IntelliFactory 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); you 8 | // may not use this file except in compliance with the License. You may 9 | // obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 16 | // implied. See the License for the specific language governing 17 | // permissions and limitations under the License. 18 | // 19 | // $end{copyright} 20 | [] 21 | module TodoMvc.Client 22 | 23 | open WebSharper 24 | open WebSharper.JavaScript 25 | open WebSharper.Sitelets.InferRouter 26 | open WebSharper.UI 27 | open WebSharper.UI.Html 28 | open WebSharper.UI.Client 29 | open WebSharper.UI.Templating 30 | open WebSharper.Mvu 31 | 32 | /// Parses the index.html file and provides types to fill it with dynamic content. 33 | type MasterTemplate = Template<"wwwroot/index.html", ClientLoad.FromDocument> 34 | 35 | /// Our application has three URL endpoints. 36 | type EndPoint = 37 | | [] All 38 | | [] Active 39 | | [] Completed 40 | 41 | /// This module defines the model, the update and the view for a single entry. 42 | module Entry = 43 | 44 | /// The unique identifier of a Todo entry. 45 | type Key = int 46 | 47 | /// The model for a Todo entry. 48 | type Model = 49 | { 50 | Id : Key 51 | Task : string 52 | IsCompleted : bool 53 | Editing : option 54 | } 55 | 56 | let KeyOf (e: Model) = e.Id 57 | 58 | let New (key: Key) (task: string) = 59 | { 60 | Id = key 61 | Task = task 62 | IsCompleted = false 63 | Editing = None 64 | } 65 | 66 | [] 67 | type Message = 68 | | Remove 69 | | StartEdit 70 | | Edit of text: string 71 | | CommitEdit 72 | | CancelEdit 73 | | SetCompleted of completed: bool 74 | 75 | /// Defines how a given Todo entry is updated based on a message. 76 | /// Returns Some to update the entry, or None to delete it. 77 | let Update (msg: Message) (e: Model) : option = 78 | match msg with 79 | | Remove -> 80 | None 81 | | StartEdit -> 82 | Some { e with Editing = e.Editing |> Option.orElse (Some e.Task) } 83 | | Edit value -> 84 | Some { e with Editing = Some value } 85 | | CommitEdit -> 86 | Some { e with 87 | Task = e.Editing |> Option.defaultValue e.Task 88 | Editing = None } 89 | | CancelEdit -> 90 | Some { e with Editing = None } 91 | | SetCompleted value -> 92 | Some { e with IsCompleted = value } 93 | 94 | /// Render a given Todo entry. 95 | let Render (dispatch: Message -> unit) (endpoint: View) (entry: View) = 96 | MasterTemplate.Entry() 97 | .Label(text entry.V.Task) 98 | .CssAttrs( 99 | Attr.ClassPred "completed" entry.V.IsCompleted, 100 | Attr.ClassPred "editing" entry.V.Editing.IsSome, 101 | Attr.ClassPred "hidden" ( 102 | match endpoint.V, entry.V.IsCompleted with 103 | | EndPoint.Completed, false -> true 104 | | EndPoint.Active, true -> true 105 | | _ -> false 106 | ) 107 | ) 108 | .EditingTask( 109 | V(entry.V.Editing |> Option.defaultValue ""), 110 | fun text -> dispatch (Message.Edit text) 111 | ) 112 | .EditBlur(fun _ -> dispatch Message.CommitEdit) 113 | .EditKeyup(fun e -> 114 | match e.Event.Key with 115 | | "Enter" -> dispatch Message.CommitEdit 116 | | "Escape" -> dispatch Message.CancelEdit 117 | | _ -> () 118 | ) 119 | .IsCompleted( 120 | V entry.V.IsCompleted, 121 | fun x -> dispatch (Message.SetCompleted x) 122 | ) 123 | .Remove(fun _ -> dispatch Message.Remove) 124 | .StartEdit(fun _ -> dispatch Message.StartEdit) 125 | .Doc() 126 | 127 | /// This module defines the model, the update and the view for a full todo list. 128 | module TodoList = 129 | 130 | /// The model for the full TodoList application. 131 | type Model = 132 | { 133 | EndPoint : EndPoint 134 | NewTask : string 135 | Entries : list 136 | NextKey : Entry.Key 137 | } 138 | 139 | static member Empty = 140 | { 141 | EndPoint = All 142 | NewTask = "" 143 | Entries = [] 144 | NextKey = 0 145 | } 146 | 147 | [] 148 | type Message = 149 | | EditNewTask of text: string 150 | | AddEntry 151 | | ClearCompleted 152 | | SetAllCompleted of completed: bool 153 | | EntryMessage of key: Entry.Key * message: Entry.Message 154 | 155 | /// Defines how the Todo list is updated based on a message. 156 | let Update (msg: Message) (model: Model) = 157 | match msg with 158 | | EditNewTask value -> 159 | { model with NewTask = value } 160 | | AddEntry -> 161 | { model with 162 | NewTask = "" 163 | Entries = model.Entries @ [Entry.New model.NextKey model.NewTask] 164 | NextKey = model.NextKey + 1 } 165 | | ClearCompleted -> 166 | { model with Entries = List.filter (fun e -> not e.IsCompleted) model.Entries } 167 | | SetAllCompleted c -> 168 | { model with Entries = List.map (fun e -> { e with IsCompleted = c }) model.Entries } 169 | | EntryMessage (key, msg) -> 170 | let updateEntry (e: Entry.Model) = 171 | if Entry.KeyOf e = key then Entry.Update msg e else Some e 172 | { model with Entries = List.choose updateEntry model.Entries } 173 | 174 | /// Render the whole application. 175 | let Render (dispatch: Message -> unit) (state: View) = 176 | let countNotCompleted = 177 | V(state.V.Entries 178 | |> List.filter (fun e -> not e.IsCompleted) 179 | |> List.length) 180 | MasterTemplate() 181 | .Entries( (* V(state.V.Entries) *) 182 | state.Map(fun x -> x.Entries).DocSeqCached(Entry.KeyOf, fun key (entry: View) -> 183 | let entryDispatch msg = dispatch (EntryMessage (key, msg)) 184 | Entry.Render entryDispatch (V state.V.EndPoint) entry 185 | ) 186 | ) 187 | .ClearCompleted(fun _ -> dispatch Message.ClearCompleted) 188 | .IsCompleted( 189 | V(countNotCompleted.V = 0), 190 | fun c -> dispatch (Message.SetAllCompleted c) 191 | ) 192 | .Task( 193 | V state.V.NewTask, 194 | fun text -> dispatch (Message.EditNewTask text) 195 | ) 196 | .Edit(fun e -> 197 | if e.Event.Key = "Enter" then 198 | dispatch Message.AddEntry 199 | e.Event.PreventDefault() 200 | ) 201 | .ItemsLeft( 202 | V(match countNotCompleted.V with 203 | | 1 -> "1 item left" 204 | | n -> string n + " items left") 205 | ) 206 | .CssFilterAll(Attr.ClassPred "selected" (state.V.EndPoint = EndPoint.All)) 207 | .CssFilterActive(Attr.ClassPred "selected" (state.V.EndPoint = EndPoint.Active)) 208 | .CssFilterCompleted(Attr.ClassPred "selected" (state.V.EndPoint = EndPoint.Completed)) 209 | .Bind() 210 | 211 | /// The entry point of our application, called on page load. 212 | [] 213 | let Main () = 214 | App.CreateSimple TodoList.Model.Empty TodoList.Update TodoList.Render 215 | |> App.WithRouting (Router.Infer()) (fun (model: TodoList.Model) -> model.EndPoint) 216 | // |> App.WithLocalStorage "todolist" 217 | //|> App.WithRemoteDev (RemoteDev.Options(hostname = "localhost", port = 8000)) 218 | |> App.WithReduxDevTools 219 | |> App.Run 220 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | ## TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | ## END OF TERMS AND CONDITIONS 179 | -------------------------------------------------------------------------------- /WebSharper.Mvu/App.fsi: -------------------------------------------------------------------------------- 1 | // $begin{copyright} 2 | // 3 | // This file is part of WebSharper 4 | // 5 | // Copyright (c) 2008-2018 IntelliFactory 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); you 8 | // may not use this file except in compliance with the License. You may 9 | // obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 16 | // implied. See the License for the specific language governing 17 | // permissions and limitations under the License. 18 | // 19 | // $end{copyright} 20 | namespace WebSharper.Mvu 21 | 22 | open WebSharper 23 | open WebSharper.Sitelets 24 | open WebSharper.UI 25 | 26 | /// A function that dispatches a message to the update function. 27 | type Dispatch<'Message> = 'Message -> unit 28 | 29 | /// An MVU application. 30 | [] 31 | type App<'Message, 'Model, 'Rendered> 32 | 33 | /// An action to take as a result of the Update function. 34 | type Action<'Message, 'Model> = 35 | /// Don't take any action. 36 | | DoNothing 37 | /// Set the model to the given value. 38 | | SetModel of 'Model 39 | /// Update the model based on the given function. 40 | /// Useful if several combined actions need to update the state. 41 | | UpdateModel of ('Model -> 'Model) 42 | /// Run the given command synchronously. The command can dispatch subsequent actions. 43 | | Command of (Dispatch<'Message> -> unit) 44 | /// Run the given command asynchronously. The command can dispatch subsequent actions. 45 | | CommandAsync of (Dispatch<'Message> -> Async) 46 | /// Run several actions in sequence. 47 | | CombinedAction of list> 48 | 49 | /// Run several actions in sequence. 50 | static member (+) 51 | : Action<'Message, 'Model> 52 | * Action<'Message, 'Model> 53 | -> Action<'Message, 'Model> 54 | 55 | [] 56 | module Action = 57 | 58 | /// Run the given asynchronous job then dispatch a message based on its result. 59 | val DispatchAsync<'T, 'Message, 'Model> 60 | : toMessage: ('T -> 'Message) 61 | -> action: Async<'T> 62 | -> Action<'Message, 'Model> 63 | 64 | [] 65 | type Page<'Message, 'Model> = 66 | 67 | /// 68 | /// Create a reactive page. 69 | /// 70 | /// Get the identifier of the current endpoint. A new instance of the page is only created for different values of the key. 71 | /// Render the page itself. 72 | /// Attributes to add to the wrapping div. 73 | /// If true, don't remove the page from the DOM when hidden. 74 | /// Pass true if this page uses CSS transitions to appear and disappear. 75 | static member Reactive<'EndPointArgs, 'Key> 76 | : key: ('EndPointArgs -> 'Key) 77 | * render: ('Key -> Dispatch<'Message> -> View<'Model> -> Doc) 78 | * ?attrs: seq 79 | * ?keepInDom: bool 80 | * ?usesTransition: bool 81 | -> ('EndPointArgs -> Page<'Message, 'Model>) 82 | when 'Key : equality 83 | 84 | /// 85 | /// Create a reactive page. A new instance of the page is created for different values of the endpoint args. 86 | /// 87 | /// Render the page itself. 88 | /// Attributes to add to the wrapping div. 89 | /// If true, don't remove the page from the DOM when hidden. 90 | /// Pass true if this page uses CSS transitions to appear and disappear. 91 | static member Create<'EndPointArgs> 92 | : render: ('EndPointArgs -> Dispatch<'Message> -> View<'Model> -> Doc) 93 | * ?attrs: seq 94 | * ?keepInDom: bool 95 | * ?usesTransition: bool 96 | -> ('EndPointArgs -> Page<'Message, 'Model>) 97 | when 'EndPointArgs : equality 98 | 99 | /// 100 | /// Create a reactive page. A single instance of the page is (lazily) created. 101 | /// 102 | /// Render the page itself. 103 | /// Attributes to add to the wrapping div. 104 | /// If true, don't remove the page from the DOM when hidden. 105 | /// Pass true if this page uses CSS transitions to appear and disappear. 106 | static member Single 107 | : render: (Dispatch<'Message> -> View<'Model> -> Doc) 108 | * ?attrs: seq 109 | * ?keepInDom: bool 110 | * ?usesTransition: bool 111 | -> (unit -> Page<'Message, 'Model>) 112 | 113 | /// Bring together the Model-View-Update system and augment it with extra capabilities. 114 | module App = 115 | 116 | /// 117 | /// Create an MVU application. 118 | /// 119 | /// The initial value of the model. 120 | /// Computes the new model on every message. 121 | /// Renders the application based on a reactive view of the model. 122 | val CreateSimple<'Message, 'Model, 'Rendered> 123 | : initModel: 'Model 124 | -> update: ('Message -> 'Model -> 'Model) 125 | -> render: (Dispatch<'Message> -> View<'Model> -> 'Rendered) 126 | -> App<'Message, 'Model, 'Rendered> 127 | 128 | /// 129 | /// Create an MVU application. 130 | /// 131 | /// The initial value of the model. 132 | /// Computes the new model and/or dispatches commands on every message. 133 | /// Renders the application based on a reactive view of the model. 134 | val Create<'Message, 'Model, 'Rendered> 135 | : initModel: 'Model 136 | -> update: ('Message -> 'Model -> Action<'Message, 'Model>) 137 | -> render: (Dispatch<'Message> -> View<'Model> -> 'Rendered) 138 | -> App<'Message, 'Model, 'Rendered> 139 | 140 | /// 141 | /// Create an MVU application using paging. 142 | /// 143 | /// The initial value of the model. 144 | /// Computes the new model on every message. 145 | /// Renders the application based on a reactive view of the model. 146 | val CreateSimplePaged<'Message, 'Model> 147 | : initModel: 'Model 148 | -> update: ('Message -> 'Model -> 'Model) 149 | -> render: ('Model -> Page<'Message, 'Model>) 150 | -> App<'Message, 'Model, Doc> 151 | 152 | /// 153 | /// Create an MVU application using paging. 154 | /// 155 | /// The initial value of the model. 156 | /// Computes the new model and/or dispatches commands on every message. 157 | /// Renders the application based on a reactive view of the model. 158 | val CreatePaged<'Message, 'Model> 159 | : initModel: 'Model 160 | -> update: ('Message -> 'Model -> Action<'Message, 'Model>) 161 | -> render: ('Model -> Page<'Message, 'Model>) 162 | -> App<'Message, 'Model, Doc> 163 | 164 | /// Run the application. 165 | val Run<'Message, 'Model, 'Rendered> 166 | : app: App<'Message, 'Model, 'Rendered> 167 | -> 'Rendered 168 | 169 | /// 170 | /// Add URL hash routing to an application's model. 171 | /// 172 | /// The URL router. 173 | /// Where the current endpoint is stored in the model. Must be a record field access. 174 | /// The application. 175 | val WithRouting<'Route, 'Message, 'Model, 'Rendered> 176 | : router: Router<'Route> 177 | -> getRoute: ('Model -> 'Route) 178 | -> app: App<'Message, 'Model, 'Rendered> 179 | -> App<'Message, 'Model, 'Rendered> 180 | when 'Route : equality 181 | 182 | /// 183 | /// Add URL hash routing to an application's model. 184 | /// 185 | /// The URL router. 186 | /// How to get the current endpoint from the model. 187 | /// How to set the current endpoint in the model. 188 | /// The application. 189 | val WithCustomRouting<'Route, 'Message, 'Model, 'Rendered> 190 | : router: Router<'Route> 191 | -> getRoute: ('Model -> 'Route) 192 | -> setRoute: ('Route -> 'Model -> 'Model) 193 | -> app: App<'Message, 'Model, 'Rendered> 194 | -> App<'Message, 'Model, 'Rendered> 195 | when 'Route : equality 196 | 197 | /// 198 | /// Add Local Storage capability to the application. 199 | /// On startup, load the model from local storage at the given key, 200 | /// or keep the initial model if there is nothing stored yet. 201 | /// On every update, store the model in local storage. 202 | /// 203 | /// The local storage key 204 | /// The application 205 | val WithLocalStorage<'Message, 'Model, 'Rendered> 206 | : key: string 207 | -> app: App<'Message, 'Model, 'Rendered> 208 | -> App<'Message, 'Model, 'Rendered> 209 | 210 | /// Run the given action on startup. 211 | val WithInitAction<'Message, 'Model, 'Rendered> 212 | : action: Action<'Message, 'Model> 213 | -> app: App<'Message, 'Model, 'Rendered> 214 | -> App<'Message, 'Model, 'Rendered> 215 | 216 | /// Dispatch the given message on startup. 217 | val WithInitMessage<'Message, 'Model, 'Rendered> 218 | : message: 'Message 219 | -> app: App<'Message, 'Model, 'Rendered> 220 | -> App<'Message, 'Model, 'Rendered> 221 | 222 | /// 223 | /// Add RemoteDev capability to the application. 224 | /// Allows inspecting the model's history and time-travel debugging. 225 | /// needs 'remoteDev 226 | /// 227 | /// The RemoteDev options 228 | /// The application 229 | val WithRemoteDev<'Message, 'Model, 'Rendered> 230 | : options: RemoteDev.Options 231 | -> app: App<'Message, 'Model, 'Rendered> 232 | -> App<'Message, 'Model, 'Rendered> 233 | 234 | /// 235 | /// Add Redux DevTools capability to the application. 236 | /// Allows inspecting the model's history and time-travel debugging. 237 | /// 238 | /// The application 239 | val WithReduxDevTools<'Message, 'Model, 'Rendered> 240 | : app: App<'Message, 'Model, 'Rendered> 241 | -> App<'Message, 'Model, 'Rendered> 242 | 243 | /// 244 | /// Add Redux DevTools capability to the application. 245 | /// Allows inspecting the model's history and time-travel debugging. 246 | /// 247 | /// The Redux DevTools options 248 | /// The application 249 | val WithReduxDevToolsOptions<'Message, 'Model, 'Rendered> 250 | : options: ReduxDevTools.Options 251 | -> app: App<'Message, 'Model, 'Rendered> 252 | -> App<'Message, 'Model, 'Rendered> 253 | 254 | /// Call this function on every update with the message and the new model. 255 | val WithLog<'Message, 'Model, 'Rendered> 256 | : log: ('Message -> 'Model -> unit) 257 | -> app: App<'Message, 'Model, 'Rendered> 258 | -> App<'Message, 'Model, 'Rendered> 259 | -------------------------------------------------------------------------------- /WebSharper.Mvu.Tests/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WebSharper.Mvu.Tests", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "WebSharper.Mvu.Tests", 9 | "version": "1.0.0", 10 | "devDependencies": { 11 | "esbuild": "^0.19.9" 12 | } 13 | }, 14 | "node_modules/@esbuild/aix-ppc64": { 15 | "version": "0.19.12", 16 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", 17 | "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", 18 | "cpu": [ 19 | "ppc64" 20 | ], 21 | "dev": true, 22 | "optional": true, 23 | "os": [ 24 | "aix" 25 | ], 26 | "engines": { 27 | "node": ">=12" 28 | } 29 | }, 30 | "node_modules/@esbuild/android-arm": { 31 | "version": "0.19.12", 32 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", 33 | "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", 34 | "cpu": [ 35 | "arm" 36 | ], 37 | "dev": true, 38 | "optional": true, 39 | "os": [ 40 | "android" 41 | ], 42 | "engines": { 43 | "node": ">=12" 44 | } 45 | }, 46 | "node_modules/@esbuild/android-arm64": { 47 | "version": "0.19.12", 48 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", 49 | "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", 50 | "cpu": [ 51 | "arm64" 52 | ], 53 | "dev": true, 54 | "optional": true, 55 | "os": [ 56 | "android" 57 | ], 58 | "engines": { 59 | "node": ">=12" 60 | } 61 | }, 62 | "node_modules/@esbuild/android-x64": { 63 | "version": "0.19.12", 64 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", 65 | "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", 66 | "cpu": [ 67 | "x64" 68 | ], 69 | "dev": true, 70 | "optional": true, 71 | "os": [ 72 | "android" 73 | ], 74 | "engines": { 75 | "node": ">=12" 76 | } 77 | }, 78 | "node_modules/@esbuild/darwin-arm64": { 79 | "version": "0.19.12", 80 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", 81 | "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", 82 | "cpu": [ 83 | "arm64" 84 | ], 85 | "dev": true, 86 | "optional": true, 87 | "os": [ 88 | "darwin" 89 | ], 90 | "engines": { 91 | "node": ">=12" 92 | } 93 | }, 94 | "node_modules/@esbuild/darwin-x64": { 95 | "version": "0.19.12", 96 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", 97 | "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", 98 | "cpu": [ 99 | "x64" 100 | ], 101 | "dev": true, 102 | "optional": true, 103 | "os": [ 104 | "darwin" 105 | ], 106 | "engines": { 107 | "node": ">=12" 108 | } 109 | }, 110 | "node_modules/@esbuild/freebsd-arm64": { 111 | "version": "0.19.12", 112 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", 113 | "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", 114 | "cpu": [ 115 | "arm64" 116 | ], 117 | "dev": true, 118 | "optional": true, 119 | "os": [ 120 | "freebsd" 121 | ], 122 | "engines": { 123 | "node": ">=12" 124 | } 125 | }, 126 | "node_modules/@esbuild/freebsd-x64": { 127 | "version": "0.19.12", 128 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", 129 | "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", 130 | "cpu": [ 131 | "x64" 132 | ], 133 | "dev": true, 134 | "optional": true, 135 | "os": [ 136 | "freebsd" 137 | ], 138 | "engines": { 139 | "node": ">=12" 140 | } 141 | }, 142 | "node_modules/@esbuild/linux-arm": { 143 | "version": "0.19.12", 144 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", 145 | "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", 146 | "cpu": [ 147 | "arm" 148 | ], 149 | "dev": true, 150 | "optional": true, 151 | "os": [ 152 | "linux" 153 | ], 154 | "engines": { 155 | "node": ">=12" 156 | } 157 | }, 158 | "node_modules/@esbuild/linux-arm64": { 159 | "version": "0.19.12", 160 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", 161 | "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", 162 | "cpu": [ 163 | "arm64" 164 | ], 165 | "dev": true, 166 | "optional": true, 167 | "os": [ 168 | "linux" 169 | ], 170 | "engines": { 171 | "node": ">=12" 172 | } 173 | }, 174 | "node_modules/@esbuild/linux-ia32": { 175 | "version": "0.19.12", 176 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", 177 | "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", 178 | "cpu": [ 179 | "ia32" 180 | ], 181 | "dev": true, 182 | "optional": true, 183 | "os": [ 184 | "linux" 185 | ], 186 | "engines": { 187 | "node": ">=12" 188 | } 189 | }, 190 | "node_modules/@esbuild/linux-loong64": { 191 | "version": "0.19.12", 192 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", 193 | "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", 194 | "cpu": [ 195 | "loong64" 196 | ], 197 | "dev": true, 198 | "optional": true, 199 | "os": [ 200 | "linux" 201 | ], 202 | "engines": { 203 | "node": ">=12" 204 | } 205 | }, 206 | "node_modules/@esbuild/linux-mips64el": { 207 | "version": "0.19.12", 208 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", 209 | "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", 210 | "cpu": [ 211 | "mips64el" 212 | ], 213 | "dev": true, 214 | "optional": true, 215 | "os": [ 216 | "linux" 217 | ], 218 | "engines": { 219 | "node": ">=12" 220 | } 221 | }, 222 | "node_modules/@esbuild/linux-ppc64": { 223 | "version": "0.19.12", 224 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", 225 | "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", 226 | "cpu": [ 227 | "ppc64" 228 | ], 229 | "dev": true, 230 | "optional": true, 231 | "os": [ 232 | "linux" 233 | ], 234 | "engines": { 235 | "node": ">=12" 236 | } 237 | }, 238 | "node_modules/@esbuild/linux-riscv64": { 239 | "version": "0.19.12", 240 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", 241 | "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", 242 | "cpu": [ 243 | "riscv64" 244 | ], 245 | "dev": true, 246 | "optional": true, 247 | "os": [ 248 | "linux" 249 | ], 250 | "engines": { 251 | "node": ">=12" 252 | } 253 | }, 254 | "node_modules/@esbuild/linux-s390x": { 255 | "version": "0.19.12", 256 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", 257 | "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", 258 | "cpu": [ 259 | "s390x" 260 | ], 261 | "dev": true, 262 | "optional": true, 263 | "os": [ 264 | "linux" 265 | ], 266 | "engines": { 267 | "node": ">=12" 268 | } 269 | }, 270 | "node_modules/@esbuild/linux-x64": { 271 | "version": "0.19.12", 272 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", 273 | "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", 274 | "cpu": [ 275 | "x64" 276 | ], 277 | "dev": true, 278 | "optional": true, 279 | "os": [ 280 | "linux" 281 | ], 282 | "engines": { 283 | "node": ">=12" 284 | } 285 | }, 286 | "node_modules/@esbuild/netbsd-x64": { 287 | "version": "0.19.12", 288 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", 289 | "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", 290 | "cpu": [ 291 | "x64" 292 | ], 293 | "dev": true, 294 | "optional": true, 295 | "os": [ 296 | "netbsd" 297 | ], 298 | "engines": { 299 | "node": ">=12" 300 | } 301 | }, 302 | "node_modules/@esbuild/openbsd-x64": { 303 | "version": "0.19.12", 304 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", 305 | "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", 306 | "cpu": [ 307 | "x64" 308 | ], 309 | "dev": true, 310 | "optional": true, 311 | "os": [ 312 | "openbsd" 313 | ], 314 | "engines": { 315 | "node": ">=12" 316 | } 317 | }, 318 | "node_modules/@esbuild/sunos-x64": { 319 | "version": "0.19.12", 320 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", 321 | "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", 322 | "cpu": [ 323 | "x64" 324 | ], 325 | "dev": true, 326 | "optional": true, 327 | "os": [ 328 | "sunos" 329 | ], 330 | "engines": { 331 | "node": ">=12" 332 | } 333 | }, 334 | "node_modules/@esbuild/win32-arm64": { 335 | "version": "0.19.12", 336 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", 337 | "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", 338 | "cpu": [ 339 | "arm64" 340 | ], 341 | "dev": true, 342 | "optional": true, 343 | "os": [ 344 | "win32" 345 | ], 346 | "engines": { 347 | "node": ">=12" 348 | } 349 | }, 350 | "node_modules/@esbuild/win32-ia32": { 351 | "version": "0.19.12", 352 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", 353 | "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", 354 | "cpu": [ 355 | "ia32" 356 | ], 357 | "dev": true, 358 | "optional": true, 359 | "os": [ 360 | "win32" 361 | ], 362 | "engines": { 363 | "node": ">=12" 364 | } 365 | }, 366 | "node_modules/@esbuild/win32-x64": { 367 | "version": "0.19.12", 368 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", 369 | "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", 370 | "cpu": [ 371 | "x64" 372 | ], 373 | "dev": true, 374 | "optional": true, 375 | "os": [ 376 | "win32" 377 | ], 378 | "engines": { 379 | "node": ">=12" 380 | } 381 | }, 382 | "node_modules/esbuild": { 383 | "version": "0.19.12", 384 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", 385 | "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", 386 | "dev": true, 387 | "hasInstallScript": true, 388 | "bin": { 389 | "esbuild": "bin/esbuild" 390 | }, 391 | "engines": { 392 | "node": ">=12" 393 | }, 394 | "optionalDependencies": { 395 | "@esbuild/aix-ppc64": "0.19.12", 396 | "@esbuild/android-arm": "0.19.12", 397 | "@esbuild/android-arm64": "0.19.12", 398 | "@esbuild/android-x64": "0.19.12", 399 | "@esbuild/darwin-arm64": "0.19.12", 400 | "@esbuild/darwin-x64": "0.19.12", 401 | "@esbuild/freebsd-arm64": "0.19.12", 402 | "@esbuild/freebsd-x64": "0.19.12", 403 | "@esbuild/linux-arm": "0.19.12", 404 | "@esbuild/linux-arm64": "0.19.12", 405 | "@esbuild/linux-ia32": "0.19.12", 406 | "@esbuild/linux-loong64": "0.19.12", 407 | "@esbuild/linux-mips64el": "0.19.12", 408 | "@esbuild/linux-ppc64": "0.19.12", 409 | "@esbuild/linux-riscv64": "0.19.12", 410 | "@esbuild/linux-s390x": "0.19.12", 411 | "@esbuild/linux-x64": "0.19.12", 412 | "@esbuild/netbsd-x64": "0.19.12", 413 | "@esbuild/openbsd-x64": "0.19.12", 414 | "@esbuild/sunos-x64": "0.19.12", 415 | "@esbuild/win32-arm64": "0.19.12", 416 | "@esbuild/win32-ia32": "0.19.12", 417 | "@esbuild/win32-x64": "0.19.12" 418 | } 419 | } 420 | } 421 | } 422 | -------------------------------------------------------------------------------- /WebSharper.Mvu/App.fs: -------------------------------------------------------------------------------- 1 | // $begin{copyright} 2 | // 3 | // This file is part of WebSharper 4 | // 5 | // Copyright (c) 2008-2018 IntelliFactory 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); you 8 | // may not use this file except in compliance with the License. You may 9 | // obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 16 | // implied. See the License for the specific language governing 17 | // permissions and limitations under the License. 18 | // 19 | // $end{copyright} 20 | namespace WebSharper.Mvu 21 | 22 | #nowarn "40" // let rec container 23 | 24 | open System.Collections.Generic 25 | open WebSharper 26 | open WebSharper.JavaScript 27 | open WebSharper.UI 28 | open WebSharper.UI.Html 29 | open WebSharper.UI.Client 30 | 31 | type Dispatch<'Message> = 'Message -> unit 32 | 33 | [] 34 | type App<'Message, 'Model, 'Rendered> = 35 | internal { 36 | Init : Dispatch<'Message> -> unit 37 | Var : Var<'Model> 38 | View : View<'Model> 39 | Update : Dispatch<'Message> -> 'Message -> 'Model -> option<'Model> 40 | Render : Dispatch<'Message> -> View<'Model> -> 'Rendered 41 | } 42 | 43 | [] 44 | type Action<'Message, 'Model> = 45 | | DoNothing 46 | | SetModel of 'Model 47 | | UpdateModel of ('Model -> 'Model) 48 | | Command of (Dispatch<'Message> -> unit) 49 | | CommandAsync of (Dispatch<'Message> -> Async) 50 | | CombinedAction of list> 51 | 52 | static member (+) (a1: Action<'Message, 'Model>, a2: Action<'Message, 'Model>) = 53 | match a1, a2 with 54 | | a, DoNothing | DoNothing, a -> a 55 | | CombinedAction l1, CombinedAction l2 -> CombinedAction (l1 @ l2) 56 | | CombinedAction l1, a2 -> CombinedAction (l1 @ [a2]) 57 | | a1, CombinedAction l2 -> CombinedAction (a1 :: l2) 58 | | a1, a2 -> CombinedAction [a1; a2] 59 | 60 | [] 61 | module Action = 62 | /// Run the given asynchronous job then dispatch a message based on its result. 63 | let DispatchAsync (toMessage: 'T -> 'Message) (action: Async<'T>) : Action<'Message, 'Model> = 64 | CommandAsync (fun dispatch -> async { 65 | let! res = action 66 | dispatch (toMessage res) 67 | }) 68 | 69 | [)>] 70 | [] 71 | type Page<'Message, 'Model> = 72 | internal { 73 | Render: Pager<'Message, 'Model> -> Dispatch<'Message> -> View<'Model> -> Elt 74 | KeepInDom: bool 75 | UsesTransition: bool 76 | } 77 | 78 | static member Reactive 79 | ( 80 | key: 'EndPointArgs -> 'K, 81 | render: 'K -> Dispatch<'Message> -> View<'Model> -> Doc, 82 | ?attrs: seq, 83 | ?keepInDom: bool, 84 | ?usesTransition: bool 85 | ) = 86 | let dic = Dictionary() 87 | let getOrRender (route: 'EndPointArgs) (pager: Pager<'Message, 'Model>) (dispatch: Dispatch<'Message>) (model: View<'Model>) = 88 | let k = key route 89 | match dic.TryGetValue k with 90 | | true, (var, doc) -> 91 | Var.Set var route 92 | doc 93 | | false, _ -> 94 | let var = Var.Create route 95 | let doc = 96 | Elt.div [ 97 | attr.``class`` "ws-page" 98 | (match attrs with Some attrs -> Attr.Concat attrs | None -> Attr.Empty) 99 | on.transitionEnd (fun el ev -> pager.RemoveIfNeeded el) 100 | ] [render k dispatch model] 101 | dic.[k] <- (var, doc) 102 | doc 103 | fun ep -> 104 | { 105 | Render = getOrRender ep 106 | KeepInDom = defaultArg keepInDom false 107 | UsesTransition = defaultArg usesTransition false 108 | } : Page<'Message, 'Model> 109 | 110 | static member Create(render, ?attrs, ?keepInDom, ?usesTransition) : 'EndPointArgs -> _ = 111 | Page<'Message, 'Model>.Reactive(id, render, ?attrs = attrs, ?keepInDom = keepInDom, ?usesTransition = usesTransition) 112 | 113 | static member Single(render, ?attrs, ?keepInDom, ?usesTransition) = 114 | Page<'Message, 'Model>.Reactive((fun () -> ()), (fun () -> render), ?attrs = attrs, ?keepInDom = keepInDom, ?usesTransition = usesTransition) 115 | 116 | and [] internal Pager<'Message, 'Model>(render: 'Model -> Page<'Message, 'Model>, dispatch: Dispatch<'Message>, model: View<'Model>) as this = 117 | let mutable toRemove = None : option 118 | 119 | let rec container : WebSharper.UI.Client.EltUpdater = 120 | let elt = 121 | Elt.div [ 122 | attr.``class`` "ws-page-container" 123 | on.viewUpdate model (fun el r -> 124 | let page = render r 125 | let elt = page.Render this dispatch model 126 | let domElt = elt.Dom 127 | let children = el.ChildNodes 128 | for i = 0 to children.Length - 1 do 129 | if children.[i] !==. domElt then 130 | (children.[i] :?> Dom.Element).SetAttribute("aria-hidden", "true") 131 | |> ignore 132 | domElt.RemoveAttribute("aria-hidden") 133 | match toRemove with 134 | | Some toRemove when toRemove !==. elt -> 135 | if page.UsesTransition then 136 | toRemove.Dom?dataset?wsRemoving <- "true" 137 | else 138 | el.RemoveChild toRemove.Dom |> ignore 139 | container.RemoveUpdated toRemove 140 | | _ -> () 141 | if not (el.Contains domElt) then 142 | domElt.SetAttribute("aria-hidden", "true") 143 | el.AppendChild domElt |> ignore 144 | container.AddUpdated elt 145 | JS.RequestAnimationFrame (fun _ -> domElt.RemoveAttribute("aria-hidden")) |> ignore 146 | toRemove <- if page.KeepInDom then None else Some elt 147 | ) 148 | ] [] 149 | elt.ToUpdater() 150 | 151 | member __.RemoveIfNeeded(elt: Dom.Element) = 152 | if elt?dataset?wsRemoving = "true" then 153 | elt?dataset?wsRemoving <- "false" 154 | container.Dom.RemoveChild elt |> ignore 155 | 156 | member __.Doc = container :> Doc 157 | 158 | [] 159 | module App = 160 | 161 | let private create initModel update render = 162 | let var = Var.Create initModel 163 | { 164 | Init = ignore 165 | Var = var 166 | View = var.View 167 | Update = update 168 | Render = render 169 | } 170 | 171 | let CreateSimple<'Message, 'Model, 'Rendered> 172 | (initModel: 'Model) 173 | (update: 'Message -> 'Model -> 'Model) 174 | (render: Dispatch<'Message> -> View<'Model> -> 'Rendered) = 175 | let update _ msg mdl = 176 | Some (update msg mdl) 177 | create initModel update render 178 | 179 | let rec private applyAction dispatch oldModel = function 180 | | DoNothing -> None 181 | | SetModel mdl -> Some mdl 182 | | UpdateModel f -> Some (f oldModel) 183 | | Command f -> f dispatch; None 184 | | CommandAsync f -> Async.Start (f dispatch); None 185 | | CombinedAction actions -> 186 | (None, actions) 187 | ||> List.fold (fun newModel action -> 188 | applyAction dispatch (defaultArg newModel oldModel) action 189 | |> Option.orElse newModel 190 | ) 191 | 192 | let Create<'Message, 'Model, 'Rendered> 193 | (initModel: 'Model) 194 | (update: 'Message -> 'Model -> Action<'Message, 'Model>) 195 | (render: Dispatch<'Message> -> View<'Model> -> 'Rendered) = 196 | let update dispatch msg mdl = 197 | update msg mdl |> applyAction dispatch mdl 198 | create initModel update render 199 | 200 | let CreatePaged<'Message, 'Model> 201 | (initModel: 'Model) 202 | (update: 'Message -> 'Model -> Action<'Message, 'Model>) 203 | (render: 'Model -> Page<'Message, 'Model>) = 204 | let render (dispatch: Dispatch<'Message>) (view: View<'Model>) = 205 | Pager<'Message, 'Model>(render, dispatch, view).Doc 206 | Create initModel update render 207 | 208 | let CreateSimplePaged<'Message, 'Model> 209 | (initModel: 'Model) 210 | (update: 'Message -> 'Model -> 'Model) 211 | (render: 'Model -> Page<'Message, 'Model>) = 212 | let update msg mdl = 213 | SetModel (update msg mdl) 214 | CreatePaged initModel update render 215 | 216 | let private withRouting<'Route, 'Message, 'Model, 'Rendered when 'Route : equality> 217 | (lensedRouter: Var<'Route>) 218 | (router: WebSharper.Sitelets.Router<'Route>) 219 | (getRoute: 'Model -> 'Route) 220 | (app: App<'Message, 'Model, 'Rendered>) = 221 | { app with 222 | Init = fun dispatch -> 223 | app.Init dispatch 224 | let defaultRoute = getRoute app.Var.Value 225 | Router.InstallHashInto lensedRouter defaultRoute router } 226 | 227 | [)>] 228 | let WithRouting<'Route, 'Message, 'Model, 'Rendered when 'Route : equality> 229 | (router: WebSharper.Sitelets.Router<'Route>) 230 | (getRoute: 'Model -> 'Route) 231 | (app: App<'Message, 'Model, 'Rendered>) = 232 | withRouting (app.Var.LensAuto getRoute) router getRoute app 233 | 234 | let WithCustomRouting<'Route, 'Message, 'Model, 'Rendered when 'Route : equality> 235 | (router: WebSharper.Sitelets.Router<'Route>) 236 | (getRoute: 'Model -> 'Route) 237 | (setRoute: 'Route -> 'Model -> 'Model) 238 | (app: App<'Message, 'Model, 'Rendered>) = 239 | withRouting (app.Var.Lens getRoute (fun m r -> setRoute r m)) router getRoute app 240 | 241 | let private withLocalStorage 242 | (serializer: Serializer<'Model>) 243 | (key: string) 244 | (app: App<_, 'Model, _>) = 245 | let init dispatch = 246 | match JS.Window.LocalStorage.GetItem(key) with 247 | | null -> () 248 | | v -> 249 | try app.Var.Set <| serializer.Decode (JSON.Parse v) 250 | with exn -> 251 | Console.Error("Error deserializing state from local storage", exn) 252 | app.Init dispatch 253 | let view = 254 | app.View.Map(fun v -> 255 | JS.Window.LocalStorage.SetItem(key, JSON.Stringify (serializer.Encode v)) 256 | v 257 | ) 258 | { app with View = view; Init = init } 259 | 260 | [] 261 | let WithLocalStorage key (app: App<_, 'Model, _>) = 262 | withLocalStorage Serializer.Typed<'Model> key app 263 | 264 | let WithInitAction (action: Action<'Message, 'Model>) (app: App<'Message, 'Model, _>) = 265 | let init dispatch = 266 | app.Init dispatch 267 | applyAction dispatch app.Var.Value action 268 | |> Option.iter app.Var.Set 269 | { app with Init = init } 270 | 271 | let WithInitMessage (message: 'Message) (app: App<'Message, 'Model, 'Rendered>) = 272 | WithInitAction (Command (fun dispatch -> dispatch message)) app 273 | 274 | let Run (app: App<_, _, _>) = 275 | let rec dispatch msg = app.Var.UpdateMaybe (app.Update dispatch msg) 276 | app.Init dispatch 277 | app.Render dispatch app.View 278 | 279 | let WithLog 280 | (log: 'Message -> 'Model -> unit) 281 | (app: App<'Message, 'Model, _>) = 282 | let update dispatch msg model = 283 | let newModel = app.Update dispatch msg model 284 | log msg (defaultArg newModel model) 285 | newModel 286 | { app with Update = update } 287 | 288 | let private withRemoteDev 289 | (msgSerializer: Serializer<'Message>) 290 | (modelSerializer: Serializer<'Model>) 291 | (options: RemoteDev.Options) 292 | (app: App<'Message, 'Model, _>) = 293 | let rdev = RemoteDev.Connect(options) 294 | // Not sure why this is necessary :/ 295 | let decode (m: obj) = 296 | match m with 297 | | :? string as s -> modelSerializer.Decode (JSON.Parse s) 298 | | m -> modelSerializer.Decode m 299 | rdev.subscribe(fun msg -> 300 | if msg.``type`` = RemoteDev.MsgTypes.Dispatch then 301 | match msg.payload.``type`` with 302 | | RemoteDev.PayloadTypes.JumpToAction 303 | | RemoteDev.PayloadTypes.JumpToState -> 304 | let state = decode (RemoteDev.ExtractState msg) 305 | app.Var.Set state 306 | | RemoteDev.PayloadTypes.ImportState -> 307 | let state = msg.payload.nextLiftedState.computedStates |> Array.last 308 | let state = decode state?state 309 | app.Var.Set state 310 | rdev.send(null, msg.payload.nextLiftedState) 311 | | _ -> () 312 | ) 313 | |> ignore 314 | let update dispatch msg model = 315 | let newModel = app.Update dispatch msg model 316 | match newModel with 317 | | Some newModel -> 318 | rdev.send( 319 | msgSerializer.Encode msg, 320 | modelSerializer.Encode newModel 321 | ) 322 | | None -> () 323 | newModel 324 | let init dispatch = 325 | app.Init dispatch 326 | app.View |> View.Get (fun st -> 327 | rdev.init(modelSerializer.Encode st) 328 | ) 329 | { app with Init = init; Update = update } 330 | 331 | [] 332 | let WithRemoteDev options (app: App<'Message, 'Model, _>) = 333 | withRemoteDev Serializer.Typed<'Message> Serializer.Typed<'Model> options app 334 | 335 | let private withReduxDevTools 336 | (msgSerializer: Serializer<'Message>) 337 | (modelSerializer: Serializer<'Model>) 338 | (options: ReduxDevTools.Options) 339 | (app: App<'Message, 'Model, _>) = 340 | if ReduxDevTools.IsAvailable() |> not then 341 | JavaScript.Console.Log("Redux DevTools is not installed or available") 342 | app 343 | else 344 | let rdev = ReduxDevTools.Connect(options) 345 | let mutable startState = JS.Undefined 346 | let update dispatch msg model = 347 | let newModel = app.Update dispatch msg model 348 | match newModel with 349 | | Some newModel -> 350 | rdev.send( 351 | msgSerializer.Encode msg, 352 | modelSerializer.Encode newModel 353 | ) 354 | | None -> () 355 | newModel 356 | let init dispatch = 357 | app.Init dispatch 358 | app.View |> View.Get (fun st -> 359 | startState <- st 360 | 361 | rdev.subscribe(fun msg -> 362 | match msg.``type`` with 363 | | RemoteDev.MsgTypes.Dispatch -> 364 | match msg.payload.``type`` with 365 | | RemoteDev.PayloadTypes.JumpToAction 366 | | RemoteDev.PayloadTypes.JumpToState -> 367 | let state = modelSerializer.Decode (JSON.Parse msg.state) 368 | app.Var.Set state 369 | | RemoteDev.PayloadTypes.ImportState -> 370 | let state = msg.payload.nextLiftedState.computedStates |> Array.last 371 | let state = modelSerializer.Decode (JSON.Parse state?state) 372 | app.Var.Set state 373 | rdev.send(null, msg.payload.nextLiftedState) 374 | | RemoteDev.PayloadTypes.Reset -> 375 | app.Var.Set startState 376 | rdev.init(modelSerializer.Encode startState) 377 | | RemoteDev.PayloadTypes.Rollback -> 378 | let msgState = JSON.Parse msg.state 379 | let state = modelSerializer.Decode msgState 380 | app.Var.Set state 381 | rdev.init(msgState) 382 | | RemoteDev.PayloadTypes.Commit -> 383 | app.View |> View.Get (fun st -> 384 | rdev.init(modelSerializer.Encode st) 385 | ) 386 | | _ -> 387 | JavaScript.Console.Log("Redux DevTools feature not handled by WebSharper.MVU: ", msg.payload.``type``) 388 | | RemoteDev.MsgTypes.Action -> 389 | let action = msgSerializer.Decode (JS.Eval ("(" + msg?payload + ")")) 390 | dispatch action 391 | | _ -> () 392 | ) 393 | |> ignore 394 | 395 | rdev.init(modelSerializer.Encode st) 396 | ) 397 | { app with Init = init; Update = update } 398 | 399 | [] 400 | let WithReduxDevTools (app: App<'Message, 'Model, _>) = 401 | withReduxDevTools Serializer.Typed<'Message> Serializer.Typed<'Model> (ReduxDevTools.Options()) app 402 | 403 | [] 404 | let WithReduxDevToolsOptions options (app: App<'Message, 'Model, _>) = 405 | withReduxDevTools Serializer.Typed<'Message> Serializer.Typed<'Model> options app 406 | -------------------------------------------------------------------------------- /paket.lock: -------------------------------------------------------------------------------- 1 | STORAGE: NONE 2 | STRATEGY: MIN 3 | RESTRICTION: || (== net8.0) (== netstandard2.0) 4 | NUGET 5 | remote: https://api.nuget.org/v3/index.json 6 | FSharp.Core (6.0) 7 | HtmlAgilityPack (1.11) 8 | System.Net.Http (>= 4.3.2) 9 | System.Xml.XmlDocument (>= 4.3) 10 | System.Xml.XPath (>= 4.3) 11 | System.Xml.XPath.XmlDocument (>= 4.3) 12 | Microsoft.AspNetCore.Authentication.Abstractions (2.0) 13 | Microsoft.AspNetCore.Http.Abstractions (>= 2.0) 14 | Microsoft.Extensions.Logging.Abstractions (>= 2.0) 15 | Microsoft.Extensions.Options (>= 2.0) 16 | Microsoft.AspNetCore.Hosting.Abstractions (2.0) 17 | Microsoft.AspNetCore.Hosting.Server.Abstractions (>= 2.0) 18 | Microsoft.AspNetCore.Http.Abstractions (>= 2.0) 19 | Microsoft.Extensions.Configuration.Abstractions (>= 2.0) 20 | Microsoft.Extensions.DependencyInjection.Abstractions (>= 2.0) 21 | Microsoft.Extensions.FileProviders.Abstractions (>= 2.0) 22 | Microsoft.Extensions.Hosting.Abstractions (>= 2.0) 23 | Microsoft.Extensions.Logging.Abstractions (>= 2.0) 24 | Microsoft.AspNetCore.Hosting.Server.Abstractions (2.0) 25 | Microsoft.AspNetCore.Http.Features (>= 2.0) 26 | Microsoft.Extensions.Configuration.Abstractions (>= 2.0) 27 | Microsoft.AspNetCore.Html.Abstractions (2.0) 28 | System.Text.Encodings.Web (>= 4.4) 29 | Microsoft.AspNetCore.Http.Abstractions (2.0) 30 | Microsoft.AspNetCore.Http.Features (>= 2.0) 31 | System.Text.Encodings.Web (>= 4.4) 32 | Microsoft.AspNetCore.Http.Features (2.0) 33 | Microsoft.Extensions.Primitives (>= 2.0) 34 | Microsoft.AspNetCore.Mvc.Abstractions (2.0) 35 | Microsoft.AspNetCore.Routing.Abstractions (>= 2.0) 36 | Microsoft.Net.Http.Headers (>= 2.0) 37 | Microsoft.AspNetCore.Routing.Abstractions (2.0) 38 | Microsoft.AspNetCore.Http.Abstractions (>= 2.0) 39 | Microsoft.Bcl.AsyncInterfaces (8.0) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (== netstandard2.0) 40 | System.Threading.Tasks.Extensions (>= 4.5.4) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< netstandard2.1)) (== netstandard2.0) 41 | Microsoft.Extensions.Configuration.Abstractions (2.0) 42 | Microsoft.Extensions.Primitives (>= 2.0) 43 | Microsoft.Extensions.DependencyInjection.Abstractions (2.0) 44 | Microsoft.Extensions.FileProviders.Abstractions (2.0) 45 | Microsoft.Extensions.Primitives (>= 2.0) 46 | Microsoft.Extensions.Hosting.Abstractions (2.0) 47 | Microsoft.Extensions.Logging.Abstractions (2.0) 48 | Microsoft.Extensions.Options (2.0) 49 | Microsoft.Extensions.DependencyInjection.Abstractions (>= 2.0) 50 | Microsoft.Extensions.Primitives (>= 2.0) 51 | Microsoft.Extensions.Primitives (2.0) 52 | System.Runtime.CompilerServices.Unsafe (>= 4.4) 53 | Microsoft.Net.Http.Headers (2.0) 54 | Microsoft.Extensions.Primitives (>= 2.0) 55 | System.Buffers (>= 4.4) 56 | Microsoft.NETCore.Platforms (3.1) 57 | Microsoft.NETCore.Targets (1.1) 58 | Microsoft.Win32.SystemEvents (4.7) - restriction: || (== net8.0) (&& (== netstandard2.0) (>= netcoreapp3.0)) 59 | Microsoft.NETCore.Platforms (>= 3.1) - restriction: || (== net8.0) (&& (== netstandard2.0) (>= netcoreapp2.0)) 60 | runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3) 61 | runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3) 62 | runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3) 63 | runtime.native.System (4.3) 64 | Microsoft.NETCore.Platforms (>= 1.1) 65 | Microsoft.NETCore.Targets (>= 1.1) 66 | runtime.native.System.Net.Http (4.3) 67 | Microsoft.NETCore.Platforms (>= 1.1) 68 | Microsoft.NETCore.Targets (>= 1.1) 69 | runtime.native.System.Security.Cryptography.Apple (4.3) 70 | runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple (>= 4.3) 71 | runtime.native.System.Security.Cryptography.OpenSsl (4.3) 72 | runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3) 73 | runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3) 74 | runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3) 75 | runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3) 76 | runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3) 77 | runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3) 78 | runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3) 79 | runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3) 80 | runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3) 81 | runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3) 82 | runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3) 83 | runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3) 84 | runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple (4.3) 85 | runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3) 86 | runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3) 87 | runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3) 88 | runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3) 89 | runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3) 90 | System.Buffers (4.5.1) 91 | System.Collections (4.3) 92 | Microsoft.NETCore.Platforms (>= 1.1) 93 | Microsoft.NETCore.Targets (>= 1.1) 94 | System.Runtime (>= 4.3) 95 | System.Collections.Concurrent (4.3) 96 | System.Collections (>= 4.3) 97 | System.Diagnostics.Debug (>= 4.3) 98 | System.Diagnostics.Tracing (>= 4.3) 99 | System.Globalization (>= 4.3) 100 | System.Reflection (>= 4.3) 101 | System.Resources.ResourceManager (>= 4.3) 102 | System.Runtime (>= 4.3) 103 | System.Runtime.Extensions (>= 4.3) 104 | System.Threading (>= 4.3) 105 | System.Threading.Tasks (>= 4.3) 106 | System.Configuration.ConfigurationManager (4.7) 107 | System.Security.Cryptography.ProtectedData (>= 4.7) 108 | System.Security.Permissions (>= 4.7) 109 | System.Diagnostics.Debug (4.3) 110 | Microsoft.NETCore.Platforms (>= 1.1) 111 | Microsoft.NETCore.Targets (>= 1.1) 112 | System.Runtime (>= 4.3) 113 | System.Diagnostics.DiagnosticSource (4.3) 114 | System.Collections (>= 4.3) 115 | System.Diagnostics.Tracing (>= 4.3) 116 | System.Reflection (>= 4.3) 117 | System.Runtime (>= 4.3) 118 | System.Threading (>= 4.3) 119 | System.Diagnostics.Tracing (4.3) 120 | Microsoft.NETCore.Platforms (>= 1.1) 121 | Microsoft.NETCore.Targets (>= 1.1) 122 | System.Runtime (>= 4.3) 123 | System.Drawing.Common (4.7) - restriction: || (== net8.0) (&& (== netstandard2.0) (>= netcoreapp3.0)) 124 | Microsoft.NETCore.Platforms (>= 3.1) - restriction: || (== net8.0) (&& (== netstandard2.0) (>= netcoreapp2.0)) (&& (== netstandard2.0) (>= netcoreapp3.0)) 125 | Microsoft.Win32.SystemEvents (>= 4.7) - restriction: || (== net8.0) (&& (== netstandard2.0) (>= netcoreapp2.0)) (&& (== netstandard2.0) (>= netcoreapp3.0)) 126 | System.Globalization (4.3) 127 | Microsoft.NETCore.Platforms (>= 1.1) 128 | Microsoft.NETCore.Targets (>= 1.1) 129 | System.Runtime (>= 4.3) 130 | System.Globalization.Calendars (4.3) 131 | Microsoft.NETCore.Platforms (>= 1.1) 132 | Microsoft.NETCore.Targets (>= 1.1) 133 | System.Globalization (>= 4.3) 134 | System.Runtime (>= 4.3) 135 | System.Globalization.Extensions (4.3) 136 | Microsoft.NETCore.Platforms (>= 1.1) 137 | System.Globalization (>= 4.3) 138 | System.Resources.ResourceManager (>= 4.3) 139 | System.Runtime (>= 4.3) 140 | System.Runtime.Extensions (>= 4.3) 141 | System.Runtime.InteropServices (>= 4.3) 142 | System.IO (4.3) 143 | Microsoft.NETCore.Platforms (>= 1.1) 144 | Microsoft.NETCore.Targets (>= 1.1) 145 | System.Runtime (>= 4.3) 146 | System.Text.Encoding (>= 4.3) 147 | System.Threading.Tasks (>= 4.3) 148 | System.IO.FileSystem (4.3) 149 | Microsoft.NETCore.Platforms (>= 1.1) 150 | Microsoft.NETCore.Targets (>= 1.1) 151 | System.IO (>= 4.3) 152 | System.IO.FileSystem.Primitives (>= 4.3) 153 | System.Runtime (>= 4.3) 154 | System.Runtime.Handles (>= 4.3) 155 | System.Text.Encoding (>= 4.3) 156 | System.Threading.Tasks (>= 4.3) 157 | System.IO.FileSystem.Primitives (4.3) 158 | System.Runtime (>= 4.3) 159 | System.Linq (4.3) 160 | System.Collections (>= 4.3) 161 | System.Diagnostics.Debug (>= 4.3) 162 | System.Resources.ResourceManager (>= 4.3) 163 | System.Runtime (>= 4.3) 164 | System.Runtime.Extensions (>= 4.3) 165 | System.Memory (4.5.5) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (== netstandard2.0) 166 | System.Buffers (>= 4.5.1) - restriction: || (&& (== net8.0) (>= monotouch)) (&& (== net8.0) (>= net461)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netstandard1.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= xamarinios)) (&& (== net8.0) (>= xamarinmac)) (&& (== net8.0) (>= xamarintvos)) (&& (== net8.0) (>= xamarinwatchos)) (== netstandard2.0) 167 | System.Numerics.Vectors (>= 4.4) - restriction: || (&& (== net8.0) (< netcoreapp2.0)) (== netstandard2.0) 168 | System.Runtime.CompilerServices.Unsafe (>= 4.5.3) - restriction: || (&& (== net8.0) (>= monotouch)) (&& (== net8.0) (>= net461)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netstandard1.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (&& (== net8.0) (>= xamarinios)) (&& (== net8.0) (>= xamarinmac)) (&& (== net8.0) (>= xamarintvos)) (&& (== net8.0) (>= xamarinwatchos)) (== netstandard2.0) 169 | System.Net.Http (4.3.2) 170 | Microsoft.NETCore.Platforms (>= 1.1) 171 | runtime.native.System (>= 4.3) 172 | runtime.native.System.Net.Http (>= 4.3) 173 | runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3) 174 | System.Collections (>= 4.3) 175 | System.Diagnostics.Debug (>= 4.3) 176 | System.Diagnostics.DiagnosticSource (>= 4.3) 177 | System.Diagnostics.Tracing (>= 4.3) 178 | System.Globalization (>= 4.3) 179 | System.Globalization.Extensions (>= 4.3) 180 | System.IO (>= 4.3) 181 | System.IO.FileSystem (>= 4.3) 182 | System.Net.Primitives (>= 4.3) 183 | System.Resources.ResourceManager (>= 4.3) 184 | System.Runtime (>= 4.3) 185 | System.Runtime.Extensions (>= 4.3) 186 | System.Runtime.Handles (>= 4.3) 187 | System.Runtime.InteropServices (>= 4.3) 188 | System.Security.Cryptography.Algorithms (>= 4.3) 189 | System.Security.Cryptography.Encoding (>= 4.3) 190 | System.Security.Cryptography.OpenSsl (>= 4.3) 191 | System.Security.Cryptography.Primitives (>= 4.3) 192 | System.Security.Cryptography.X509Certificates (>= 4.3) 193 | System.Text.Encoding (>= 4.3) 194 | System.Threading (>= 4.3) 195 | System.Threading.Tasks (>= 4.3) 196 | System.Net.Primitives (4.3) 197 | Microsoft.NETCore.Platforms (>= 1.1) 198 | Microsoft.NETCore.Targets (>= 1.1) 199 | System.Runtime (>= 4.3) 200 | System.Runtime.Handles (>= 4.3) 201 | System.Numerics.Vectors (4.4) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< netcoreapp2.0)) (== netstandard2.0) 202 | System.Reflection (4.3) 203 | Microsoft.NETCore.Platforms (>= 1.1) 204 | Microsoft.NETCore.Targets (>= 1.1) 205 | System.IO (>= 4.3) 206 | System.Reflection.Primitives (>= 4.3) 207 | System.Runtime (>= 4.3) 208 | System.Reflection.Emit.ILGeneration (4.7) - restriction: || (&& (== net8.0) (< netcoreapp2.0) (< netstandard2.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (< portable-net45+wp8)) (&& (== net8.0) (>= uap10.1)) (== netstandard2.0) 209 | System.Reflection.Emit.Lightweight (4.7) 210 | System.Reflection.Emit.ILGeneration (>= 4.7) - restriction: || (&& (== net8.0) (< netcoreapp2.0) (< netstandard2.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (< portable-net45+wp8)) (&& (== net8.0) (>= uap10.1)) (== netstandard2.0) 211 | System.Reflection.Primitives (4.3) 212 | Microsoft.NETCore.Platforms (>= 1.1) 213 | Microsoft.NETCore.Targets (>= 1.1) 214 | System.Runtime (>= 4.3) 215 | System.Resources.ResourceManager (4.3) 216 | Microsoft.NETCore.Platforms (>= 1.1) 217 | Microsoft.NETCore.Targets (>= 1.1) 218 | System.Globalization (>= 4.3) 219 | System.Reflection (>= 4.3) 220 | System.Runtime (>= 4.3) 221 | System.Runtime (4.3) 222 | Microsoft.NETCore.Platforms (>= 1.1) 223 | Microsoft.NETCore.Targets (>= 1.1) 224 | System.Runtime.CompilerServices.Unsafe (6.0) 225 | System.Runtime.Extensions (4.3) 226 | Microsoft.NETCore.Platforms (>= 1.1) 227 | Microsoft.NETCore.Targets (>= 1.1) 228 | System.Runtime (>= 4.3) 229 | System.Runtime.Handles (4.3) 230 | Microsoft.NETCore.Platforms (>= 1.1) 231 | Microsoft.NETCore.Targets (>= 1.1) 232 | System.Runtime (>= 4.3) 233 | System.Runtime.InteropServices (4.3) 234 | Microsoft.NETCore.Platforms (>= 1.1) 235 | Microsoft.NETCore.Targets (>= 1.1) 236 | System.Reflection (>= 4.3) 237 | System.Reflection.Primitives (>= 4.3) 238 | System.Runtime (>= 4.3) 239 | System.Runtime.Handles (>= 4.3) 240 | System.Runtime.Numerics (4.3) 241 | System.Globalization (>= 4.3) 242 | System.Resources.ResourceManager (>= 4.3) 243 | System.Runtime (>= 4.3) 244 | System.Runtime.Extensions (>= 4.3) 245 | System.Security.AccessControl (4.7) 246 | Microsoft.NETCore.Platforms (>= 3.1) - restriction: || (== net8.0) (&& (== netstandard2.0) (>= netcoreapp2.0)) 247 | System.Security.Principal.Windows (>= 4.7) 248 | System.Security.Cryptography.Algorithms (4.3) 249 | Microsoft.NETCore.Platforms (>= 1.1) 250 | runtime.native.System.Security.Cryptography.Apple (>= 4.3) 251 | runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3) 252 | System.Collections (>= 4.3) 253 | System.IO (>= 4.3) 254 | System.Resources.ResourceManager (>= 4.3) 255 | System.Runtime (>= 4.3) 256 | System.Runtime.Extensions (>= 4.3) 257 | System.Runtime.Handles (>= 4.3) 258 | System.Runtime.InteropServices (>= 4.3) 259 | System.Runtime.Numerics (>= 4.3) 260 | System.Security.Cryptography.Encoding (>= 4.3) 261 | System.Security.Cryptography.Primitives (>= 4.3) 262 | System.Text.Encoding (>= 4.3) 263 | System.Security.Cryptography.Cng (4.3) 264 | Microsoft.NETCore.Platforms (>= 1.1) 265 | System.IO (>= 4.3) 266 | System.Resources.ResourceManager (>= 4.3) 267 | System.Runtime (>= 4.3) 268 | System.Runtime.Extensions (>= 4.3) 269 | System.Runtime.Handles (>= 4.3) 270 | System.Runtime.InteropServices (>= 4.3) 271 | System.Security.Cryptography.Algorithms (>= 4.3) 272 | System.Security.Cryptography.Encoding (>= 4.3) 273 | System.Security.Cryptography.Primitives (>= 4.3) 274 | System.Text.Encoding (>= 4.3) 275 | System.Security.Cryptography.Csp (4.3) 276 | Microsoft.NETCore.Platforms (>= 1.1) 277 | System.IO (>= 4.3) 278 | System.Reflection (>= 4.3) 279 | System.Resources.ResourceManager (>= 4.3) 280 | System.Runtime (>= 4.3) 281 | System.Runtime.Extensions (>= 4.3) 282 | System.Runtime.Handles (>= 4.3) 283 | System.Runtime.InteropServices (>= 4.3) 284 | System.Security.Cryptography.Algorithms (>= 4.3) 285 | System.Security.Cryptography.Encoding (>= 4.3) 286 | System.Security.Cryptography.Primitives (>= 4.3) 287 | System.Text.Encoding (>= 4.3) 288 | System.Threading (>= 4.3) 289 | System.Security.Cryptography.Encoding (4.3) 290 | Microsoft.NETCore.Platforms (>= 1.1) 291 | runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3) 292 | System.Collections (>= 4.3) 293 | System.Collections.Concurrent (>= 4.3) 294 | System.Linq (>= 4.3) 295 | System.Resources.ResourceManager (>= 4.3) 296 | System.Runtime (>= 4.3) 297 | System.Runtime.Extensions (>= 4.3) 298 | System.Runtime.Handles (>= 4.3) 299 | System.Runtime.InteropServices (>= 4.3) 300 | System.Security.Cryptography.Primitives (>= 4.3) 301 | System.Text.Encoding (>= 4.3) 302 | System.Security.Cryptography.OpenSsl (4.3) 303 | runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3) 304 | System.Collections (>= 4.3) 305 | System.IO (>= 4.3) 306 | System.Resources.ResourceManager (>= 4.3) 307 | System.Runtime (>= 4.3) 308 | System.Runtime.Extensions (>= 4.3) 309 | System.Runtime.Handles (>= 4.3) 310 | System.Runtime.InteropServices (>= 4.3) 311 | System.Runtime.Numerics (>= 4.3) 312 | System.Security.Cryptography.Algorithms (>= 4.3) 313 | System.Security.Cryptography.Encoding (>= 4.3) 314 | System.Security.Cryptography.Primitives (>= 4.3) 315 | System.Text.Encoding (>= 4.3) 316 | System.Security.Cryptography.Primitives (4.3) 317 | System.Diagnostics.Debug (>= 4.3) 318 | System.Globalization (>= 4.3) 319 | System.IO (>= 4.3) 320 | System.Resources.ResourceManager (>= 4.3) 321 | System.Runtime (>= 4.3) 322 | System.Threading (>= 4.3) 323 | System.Threading.Tasks (>= 4.3) 324 | System.Security.Cryptography.ProtectedData (4.7) 325 | System.Memory (>= 4.5.3) - restriction: || (&& (== net8.0) (< netcoreapp2.1)) (== netstandard2.0) 326 | System.Security.Cryptography.X509Certificates (4.3) 327 | Microsoft.NETCore.Platforms (>= 1.1) 328 | runtime.native.System (>= 4.3) 329 | runtime.native.System.Net.Http (>= 4.3) 330 | runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3) 331 | System.Collections (>= 4.3) 332 | System.Diagnostics.Debug (>= 4.3) 333 | System.Globalization (>= 4.3) 334 | System.Globalization.Calendars (>= 4.3) 335 | System.IO (>= 4.3) 336 | System.IO.FileSystem (>= 4.3) 337 | System.IO.FileSystem.Primitives (>= 4.3) 338 | System.Resources.ResourceManager (>= 4.3) 339 | System.Runtime (>= 4.3) 340 | System.Runtime.Extensions (>= 4.3) 341 | System.Runtime.Handles (>= 4.3) 342 | System.Runtime.InteropServices (>= 4.3) 343 | System.Runtime.Numerics (>= 4.3) 344 | System.Security.Cryptography.Algorithms (>= 4.3) 345 | System.Security.Cryptography.Cng (>= 4.3) 346 | System.Security.Cryptography.Csp (>= 4.3) 347 | System.Security.Cryptography.Encoding (>= 4.3) 348 | System.Security.Cryptography.OpenSsl (>= 4.3) 349 | System.Security.Cryptography.Primitives (>= 4.3) 350 | System.Text.Encoding (>= 4.3) 351 | System.Threading (>= 4.3) 352 | System.Security.Permissions (4.7) 353 | System.Security.AccessControl (>= 4.7) 354 | System.Windows.Extensions (>= 4.7) - restriction: || (== net8.0) (&& (== netstandard2.0) (>= netcoreapp3.0)) 355 | System.Security.Principal.Windows (4.7) 356 | System.Text.Encoding (4.3) 357 | Microsoft.NETCore.Platforms (>= 1.1) 358 | Microsoft.NETCore.Targets (>= 1.1) 359 | System.Runtime (>= 4.3) 360 | System.Text.Encoding.Extensions (4.3) 361 | Microsoft.NETCore.Platforms (>= 1.1) 362 | Microsoft.NETCore.Targets (>= 1.1) 363 | System.Runtime (>= 4.3) 364 | System.Text.Encoding (>= 4.3) 365 | System.Text.Encodings.Web (8.0) 366 | System.Buffers (>= 4.5.1) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (== netstandard2.0) 367 | System.Memory (>= 4.5.5) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (== netstandard2.0) 368 | System.Runtime.CompilerServices.Unsafe (>= 6.0) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (&& (== net8.0) (< net7.0)) (== netstandard2.0) 369 | System.Text.Json (8.0.4) 370 | Microsoft.Bcl.AsyncInterfaces (>= 8.0) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (== netstandard2.0) 371 | System.Buffers (>= 4.5.1) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (== netstandard2.0) 372 | System.Memory (>= 4.5.5) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (== netstandard2.0) 373 | System.Runtime.CompilerServices.Unsafe (>= 6.0) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (&& (== net8.0) (< net7.0)) (== netstandard2.0) 374 | System.Text.Encodings.Web (>= 8.0) 375 | System.Threading.Tasks.Extensions (>= 4.5.4) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (== netstandard2.0) 376 | System.Text.RegularExpressions (4.3) 377 | System.Collections (>= 4.3) - restriction: || (&& (== net8.0) (< netcoreapp1.1)) (== netstandard2.0) 378 | System.Globalization (>= 4.3) - restriction: || (&& (== net8.0) (< netcoreapp1.1)) (== netstandard2.0) 379 | System.Resources.ResourceManager (>= 4.3) - restriction: || (&& (== net8.0) (< netcoreapp1.1)) (== netstandard2.0) 380 | System.Runtime (>= 4.3) 381 | System.Runtime.Extensions (>= 4.3) - restriction: || (&& (== net8.0) (< netcoreapp1.1)) (== netstandard2.0) 382 | System.Threading (>= 4.3) - restriction: || (&& (== net8.0) (< netcoreapp1.1)) (== netstandard2.0) 383 | System.Threading (4.3) 384 | System.Runtime (>= 4.3) 385 | System.Threading.Tasks (>= 4.3) 386 | System.Threading.Tasks (4.3) 387 | Microsoft.NETCore.Platforms (>= 1.1) 388 | Microsoft.NETCore.Targets (>= 1.1) 389 | System.Runtime (>= 4.3) 390 | System.Threading.Tasks.Extensions (4.5.4) 391 | System.Runtime.CompilerServices.Unsafe (>= 4.5.3) - restriction: || (&& (== net8.0) (>= net461)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netstandard1.0)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= wp8)) (== netstandard2.0) 392 | System.Windows.Extensions (4.7) - restriction: || (== net8.0) (&& (== netstandard2.0) (>= netcoreapp3.0)) 393 | System.Drawing.Common (>= 4.7) - restriction: || (== net8.0) (&& (== netstandard2.0) (>= netcoreapp3.0)) 394 | System.Xml.ReaderWriter (4.3) 395 | System.Collections (>= 4.3) 396 | System.Diagnostics.Debug (>= 4.3) 397 | System.Globalization (>= 4.3) 398 | System.IO (>= 4.3) 399 | System.IO.FileSystem (>= 4.3) 400 | System.IO.FileSystem.Primitives (>= 4.3) 401 | System.Resources.ResourceManager (>= 4.3) 402 | System.Runtime (>= 4.3) 403 | System.Runtime.Extensions (>= 4.3) 404 | System.Runtime.InteropServices (>= 4.3) 405 | System.Text.Encoding (>= 4.3) 406 | System.Text.Encoding.Extensions (>= 4.3) 407 | System.Text.RegularExpressions (>= 4.3) 408 | System.Threading.Tasks (>= 4.3) 409 | System.Threading.Tasks.Extensions (>= 4.3) 410 | System.Xml.XmlDocument (4.3) 411 | System.Collections (>= 4.3) 412 | System.Diagnostics.Debug (>= 4.3) 413 | System.Globalization (>= 4.3) 414 | System.IO (>= 4.3) 415 | System.Resources.ResourceManager (>= 4.3) 416 | System.Runtime (>= 4.3) 417 | System.Runtime.Extensions (>= 4.3) 418 | System.Text.Encoding (>= 4.3) 419 | System.Threading (>= 4.3) 420 | System.Xml.ReaderWriter (>= 4.3) 421 | System.Xml.XPath (4.3) 422 | System.Collections (>= 4.3) 423 | System.Diagnostics.Debug (>= 4.3) 424 | System.Globalization (>= 4.3) 425 | System.IO (>= 4.3) 426 | System.Resources.ResourceManager (>= 4.3) 427 | System.Runtime (>= 4.3) 428 | System.Runtime.Extensions (>= 4.3) 429 | System.Threading (>= 4.3) 430 | System.Xml.ReaderWriter (>= 4.3) 431 | System.Xml.XPath.XmlDocument (4.3) 432 | System.Collections (>= 4.3) 433 | System.Globalization (>= 4.3) 434 | System.IO (>= 4.3) 435 | System.Resources.ResourceManager (>= 4.3) 436 | System.Runtime (>= 4.3) 437 | System.Runtime.Extensions (>= 4.3) 438 | System.Threading (>= 4.3) 439 | System.Xml.ReaderWriter (>= 4.3) 440 | System.Xml.XmlDocument (>= 4.3) 441 | System.Xml.XPath (>= 4.3) 442 | remote: https://nuget.pkg.github.com/dotnet-websharper/index.json 443 | WebSharper (10.0.0.651) 444 | FSharp.Core (>= 6.0) 445 | System.Reflection.Emit.Lightweight (>= 4.7) 446 | WebSharper.AspNetCore (10.0.0.651) 447 | Microsoft.AspNetCore.Authentication.Abstractions (>= 2.0) 448 | Microsoft.AspNetCore.Hosting.Abstractions (>= 2.0) 449 | Microsoft.AspNetCore.Html.Abstractions (>= 2.0) 450 | Microsoft.AspNetCore.Mvc.Abstractions (>= 2.0) 451 | System.Configuration.ConfigurationManager (>= 4.7) 452 | System.Text.Json (>= 8.0.4) 453 | WebSharper (10.0.0.651) 454 | WebSharper.FSharp (10.0.0.651) 455 | WebSharper (10.0.0.651) 456 | WebSharper.UI (10.0.0.651) 457 | HtmlAgilityPack (>= 1.11) 458 | WebSharper (>= 10.0.0.651 < 10.0.1) 459 | 460 | GROUP wsbuild 461 | 462 | GIT 463 | remote: https://github.com/dotnet-websharper/build-script 464 | (ae4e3345219bb1ef82143ad331e582f9c81791ac) -------------------------------------------------------------------------------- /.paket/Paket.Restore.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 8 | 9 | $(MSBuildVersion) 10 | 15.0.0 11 | false 12 | true 13 | 14 | true 15 | $(MSBuildThisFileDirectory) 16 | $(MSBuildThisFileDirectory)..\ 17 | $(PaketRootPath)paket-files\paket.restore.cached 18 | $(PaketRootPath)paket.lock 19 | classic 20 | proj 21 | assembly 22 | native 23 | /Library/Frameworks/Mono.framework/Commands/mono 24 | mono 25 | 26 | 27 | $(PaketRootPath)paket.bootstrapper.exe 28 | $(PaketToolsPath)paket.bootstrapper.exe 29 | $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\ 30 | 31 | "$(PaketBootStrapperExePath)" 32 | $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)" 33 | 34 | 35 | 36 | 37 | true 38 | true 39 | 40 | 41 | True 42 | 43 | 44 | False 45 | 46 | $(BaseIntermediateOutputPath.TrimEnd('\').TrimEnd('\/')) 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | $(PaketRootPath)paket 56 | $(PaketToolsPath)paket 57 | 58 | 59 | 60 | 61 | 62 | $(PaketRootPath)paket.exe 63 | $(PaketToolsPath)paket.exe 64 | 65 | 66 | 67 | 68 | 69 | <_DotnetToolsJson Condition="Exists('$(PaketRootPath)/.config/dotnet-tools.json')">$([System.IO.File]::ReadAllText("$(PaketRootPath)/.config/dotnet-tools.json")) 70 | <_ConfigContainsPaket Condition=" '$(_DotnetToolsJson)' != ''">$(_DotnetToolsJson.Contains('"paket"')) 71 | <_ConfigContainsPaket Condition=" '$(_ConfigContainsPaket)' == ''">false 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | <_PaketCommand>dotnet paket 83 | 84 | 85 | 86 | 87 | 88 | $(PaketToolsPath)paket 89 | $(PaketBootStrapperExeDir)paket 90 | 91 | 92 | paket 93 | 94 | 95 | 96 | 97 | <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)")) 98 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(_PaketExeExtension)' == '.dll' ">dotnet "$(PaketExePath)" 99 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(OS)' != 'Windows_NT' AND '$(_PaketExeExtension)' == '.exe' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" 100 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' ">"$(PaketExePath)" 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | true 122 | $(NoWarn);NU1603;NU1604;NU1605;NU1608 123 | false 124 | true 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)')) 134 | 135 | 136 | 137 | 138 | 139 | 141 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[0].Replace(`"`, ``).Replace(` `, ``)) 142 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[1].Replace(`"`, ``).Replace(` `, ``)) 143 | 144 | 145 | 146 | 147 | %(PaketRestoreCachedKeyValue.Value) 148 | %(PaketRestoreCachedKeyValue.Value) 149 | 150 | 151 | 152 | 153 | true 154 | false 155 | true 156 | 157 | 158 | 162 | 163 | true 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | $(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.references.cached 183 | 184 | $(MSBuildProjectFullPath).paket.references 185 | 186 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references 187 | 188 | $(MSBuildProjectDirectory)\paket.references 189 | 190 | false 191 | true 192 | true 193 | references-file-or-cache-not-found 194 | 195 | 196 | 197 | 198 | $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)')) 199 | $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)')) 200 | references-file 201 | false 202 | 203 | 204 | 205 | 206 | false 207 | 208 | 209 | 210 | 211 | true 212 | target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths) 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | false 224 | true 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length) 236 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0]) 237 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1]) 238 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4]) 239 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5]) 240 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[6]) 241 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[7]) 242 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[8]) 243 | 244 | 245 | %(PaketReferencesFileLinesInfo.PackageVersion) 246 | All 247 | runtime 248 | $(ExcludeAssets);contentFiles 249 | $(ExcludeAssets);build;buildMultitargeting;buildTransitive 250 | %(PaketReferencesFileLinesInfo.Aliases) 251 | true 252 | true 253 | 254 | 255 | 256 | 257 | 258 | $(PaketIntermediateOutputPath)/$(MSBuildProjectFile).paket.clitools 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0]) 268 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1]) 269 | 270 | 271 | %(PaketCliToolFileLinesInfo.PackageVersion) 272 | 273 | 274 | 275 | 279 | 280 | 281 | 282 | 283 | 284 | false 285 | 286 | 287 | 288 | 289 | 290 | <_NuspecFilesNewLocation Include="$(PaketIntermediateOutputPath)\$(Configuration)\*.nuspec"/> 291 | 292 | 293 | 294 | 295 | 296 | $(MSBuildProjectDirectory)/$(MSBuildProjectFile) 297 | true 298 | false 299 | true 300 | false 301 | true 302 | false 303 | true 304 | false 305 | true 306 | false 307 | true 308 | $(PaketIntermediateOutputPath)\$(Configuration) 309 | $(PaketIntermediateOutputPath) 310 | 311 | 312 | 313 | <_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.$(PackageVersion.Split(`+`)[0]).nuspec"/> 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 373 | 374 | 423 | 424 | 469 | 470 | 514 | 515 | 558 | 559 | 560 | 561 | --------------------------------------------------------------------------------