First commit

This commit is contained in:
Alfonso Garcia-Caro
2021-08-21 21:04:36 +09:00
commit 1de71f05aa
25 changed files with 11966 additions and 0 deletions

358
.gitignore vendored Normal file
View File

@@ -0,0 +1,358 @@
*.fs.js
*/public/*.js
*/public/*.txt
*/public/*.png
out/
.idea/
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Alfonso Garcia-Caro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

5
README.md Normal file
View File

@@ -0,0 +1,5 @@
# Elmish.Lit
Write Fable Elmish apps using HTML templates with [lit-html](https://lit-html.polymer-project.org/guide). Check the sample for, well, a sample.
If you're using VS Code, you can install the [template-fsharp-highlight](https://marketplace.visualstudio.com/items?itemName=alfonsogarciacaro.vscode-template-fsharp-highlight) extension for syntax highlighting of HTML templates.

3
RELEASE_NOTES.md Normal file
View File

@@ -0,0 +1,3 @@
### 1.0.0-beta-001
* First beta release

7
build.fsx Normal file
View File

@@ -0,0 +1,7 @@
#load "node_modules/fable-publish-utils/PublishUtils.fs"
open PublishUtils
match args with
| IgnoreCase "publish"::_ ->
pushFableNuget "src/Elmish.Lit.fsproj" [] doNothing
| _ -> ()

BIN
fable_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

6
global.json Normal file
View File

@@ -0,0 +1,6 @@
{
"sdk": {
"version": "5.0.300",
"rollForward": "minor"
}
}

26
package-lock.json generated Normal file
View File

@@ -0,0 +1,26 @@
{
"name": "Elmish.Lit",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"devDependencies": {
"fable-publish-utils": "^2.2.0"
}
},
"node_modules/fable-publish-utils": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/fable-publish-utils/-/fable-publish-utils-2.2.0.tgz",
"integrity": "sha512-/P3iaJj2WLQiMdBEB6wVWTUdeg3RLk9i7ltvQ0HvM7k6+5spwBLwdL2gYfFV8E4n44wlWuMXy6SxyP0OMrGBQA==",
"dev": true
}
},
"dependencies": {
"fable-publish-utils": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/fable-publish-utils/-/fable-publish-utils-2.2.0.tgz",
"integrity": "sha512-/P3iaJj2WLQiMdBEB6wVWTUdeg3RLk9i7ltvQ0HvM7k6+5spwBLwdL2gYfFV8E4n44wlWuMXy6SxyP0OMrGBQA==",
"dev": true
}
}
}

11
package.json Normal file
View File

@@ -0,0 +1,11 @@
{
"private": true,
"scripts": {
"publish": "dotnet fsi build.fsx publish"
},
"dependencies": {
},
"devDependencies": {
"fable-publish-utils": "^2.2.0"
}
}

View File

@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"fable": {
"version": "3.2.11",
"commands": [
"fable"
]
}
}
}

3
sample/README.md Normal file
View File

@@ -0,0 +1,3 @@
# Elmish.Lit Sample
To test, in this directory run `npm install && npm start`

11128
sample/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

21
sample/package.json Normal file
View File

@@ -0,0 +1,21 @@
{
"private": "true",
"scripts": {
"postinstall": "dotnet tool restore",
"start": "dotnet fable watch src -o out --run webpack serve",
"deploy": "NODE_ENV=production dotnet fable src -o out --run webpack && gh-pages -d public"
},
"dependencies": {
"lit-html": "^1.4.1"
},
"devDependencies": {
"css-loader": "^5.1.1",
"file-loader": "^6.2.0",
"gh-pages": "^3.1.0",
"resolve-url-loader": "^3.1.2",
"style-loader": "^2.0.0",
"webpack": "^5.24.0",
"webpack-cli": "^4.5.0",
"webpack-dev-server": "^3.11.2"
}
}

BIN
sample/public/fable.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

27
sample/public/index.html Normal file
View File

@@ -0,0 +1,27 @@
<!doctype html>
<html>
<head>
<title>Fable</title>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css"
integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous" />
<link rel="shortcut icon" href="fable.ico" />
<style>
html, body {
height: 100%;
}
</style>
<script defer src='bundle.js'></script>
</head>
<body>
<div id="app-container"></div>
</body>
</html>

132
sample/src/App.fs Normal file
View File

@@ -0,0 +1,132 @@
module App
open System
open Elmish
open Elmish.Lit
open Lit
open Feliz
open Browser
open Browser.Types
open Helpers
type Model =
{ CurrentTime: DateTime
Value: string }
type Messages =
| Tick of DateTime
| ChangeValue of string
let initialState() =
{ CurrentTime = DateTime.Now
Value = "World" }, Cmd.none
let update msg model =
match msg with
| Tick next -> { model with CurrentTime = next }, Cmd.none
| ChangeValue newValue -> { model with Value = newValue }, Cmd.none
let timerTick dispatch =
window.setInterval(fun _ ->
dispatch (Tick DateTime.Now)
, 1000) |> ignore
let clockHand (time: Time) =
let length = time.Length
let angle = 2.0 * Math.PI * time.ClockPercentage
let handX = (50.0 + length * cos (angle - Math.PI / 2.0))
let handY = (50.0 + length * sin (angle - Math.PI / 2.0))
svg $"""
<line
x1="50"
y1="50"
x2={handX}
y2={handY}
stroke={time.Stroke}
stroke-width={time.StrokeWidth}>
</line>
"""
let handTop (time: Time) =
let length = time.Length
let revolution = float time.Value
let angle = 2.0 * Math.PI * (revolution / time.FullRound)
let handX = (50.0 + length * cos (angle - Math.PI / 2.0))
let handY = (50.0 + length * sin (angle - Math.PI / 2.0))
svg $"""
<circle
cx={handX}
cy={handY}
r="2"
fill={time.Stroke}>
</circle>
"""
let clock (time: DateTime) =
html $"""
<svg viewBox="0 0 100 100"
width="350px">
<circle
cx="50"
cy="50"
r="45"
fill="#0B79CE"></circle>
{clockHand time.AsHour}
{handTop time.AsHour}
{clockHand time.AsMinute}
{handTop time.AsMinute}
{clockHand time.AsSecond}
{handTop time.AsSecond}
<circle
cx="50"
cy="50"
r="3"
fill="#0B79CE"
stroke="#023963"
stroke-width="1">
</circle>
</svg>
"""
let nameInput value dispatch =
let containerCss = [
Css.marginLeft (length.rem 2)
Css.displayFlex
Css.justifyContentSpaceAround
Css.alignItemsFlexStart
Css.flexDirectionColumn
]
let inputCss = [
Css.padding (length.rem 0.25)
Css.fontSize (length.px 16)
Css.width (length.px 250)
Css.marginBottom (length.rem 1)
]
html $"""
<div style={styles containerCss}>
<input
style={styles inputCss}
value={value}
@keyup={fun (ev: Event) ->
ev.target.Value |> dispatch}>
<span>Hello {value}!</span>
</div>
"""
let view model dispatch =
html $"""
{clock model.CurrentTime}
{nameInput model.Value (ChangeValue >> dispatch)}
"""
Program.mkProgram initialState update view
|> Program.withSubscription (fun _ -> Cmd.ofSub timerTick)
|> Program.withLit "app-container"
|> Program.run

15
sample/src/App.fsproj Normal file
View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Helpers.fs" />
<Compile Include="App.fs" />
</ItemGroup>
<!-- <ItemGroup>
<ProjectReference Include="..\..\src\Elmish.Lit.fsproj" />
</ItemGroup> -->
<ItemGroup>
<PackageReference Include="Elmish.Lit" Version="1.0.0-beta-001" />
</ItemGroup>
</Project>

44
sample/src/Helpers.fs Normal file
View File

@@ -0,0 +1,44 @@
module Helpers
type Time =
| Hour of int
| Minute of int
| Second of int
member this.Value =
match this with
| Hour n -> n
| Second n -> n
| Minute n -> n
member this.ClockPercentage =
(float this.Value) / this.FullRound
member this.Stroke =
match this with
| Hour _ -> "lightgreen"
| Minute _ -> "white"
| Second _ -> "#023963"
member this.StrokeWidth =
match this with
| Hour _ | Minute _ -> 2
| Second _ -> 1
member this.Length =
match this with
| Hour _ -> 25.
| Minute _ -> 35.
| Second _ -> 40.
member this.FullRound =
match this with
| Hour _ -> 12.
| Second _ | Minute _ -> 60.
type System.DateTime with
member this.AsHour = Hour this.Hour
member this.AsMinute = Minute this.Minute
member this.AsSecond = Second this.Second
type Browser.Types.EventTarget with
member this.Value = (this :?> Browser.Types.HTMLInputElement).value

35
sample/webpack.config.js Normal file
View File

@@ -0,0 +1,35 @@
// @ts-check
const path = require('path');
const mode = process.env.NODE_ENV || 'development';
const prod = mode === 'production';
console.log(`Bundling for ${mode}...`)
module.exports = {
entry: {
bundle: ['./out/App.js']
},
output: {
path: __dirname + '/public',
filename: '[name].js',
chunkFilename: '[name].[id].js',
},
mode,
devtool: prod ? false : 'source-map',
devServer: {
contentBase: path.join(__dirname, "public"),
hot: true,
},
module: {
rules: [
{
test: /\.css$/i,
use: ["style-loader", "css-loader", "resolve-url-loader"],
},
{
test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)(\?.*)?$/,
use: ['file-loader']
}
],
},
};

24
src/Directory.Build.props Normal file
View File

@@ -0,0 +1,24 @@
<Project>
<PropertyGroup>
<PackageProjectUrl>http://fable.io</PackageProjectUrl>
<RepositoryUrl>https://github.com/fable-compiler/Fable.git</RepositoryUrl>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageIcon>fable_logo.png</PackageIcon>
<PackageTags>fsharp;fable;javascript;f#;js</PackageTags>
<Authors>Alfonso García-Caro Núñez</Authors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- For SourceLink. See: https://github.com/dotnet/sourcelink#using-source-link-in-net-projects -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)\..\fable_logo.png" Pack="true" Visible="false" PackagePath="" />
<None Include="$(MSBuildThisFileDirectory)\..\LICENSE" Pack="true" Visible="false" PackagePath=""/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
</Project>

14
src/Elmish.Lit.fs Normal file
View File

@@ -0,0 +1,14 @@
[<RequireQualifiedAccess>]
module Elmish.Lit.Program
open Elmish
let withLit (id: string) (program: Program<'arg, 'model, 'msg, Lit.TemplateResult>): Program<'arg, 'model, 'msg, Lit.TemplateResult> =
let el = Browser.Dom.document.getElementById(id)
if isNull el then
failwith $"Cannot find element with id {id}"
let setState model dispatch =
Program.view program model dispatch |> Lit.render el
Program.withSetState setState program

23
src/Elmish.Lit.fsproj Normal file
View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.0.0</Version>
<PackageVersion>1.0.0-beta-001</PackageVersion>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="Template.fs" />
<Compile Include="Feliz.Css.fs" />
<Compile Include="Lit.fs" />
<Compile Include="Elmish.Lit.fs" />
</ItemGroup>
<ItemGroup>
<Content Include="*.fsproj; *.fs" PackagePath="fable\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fable.Browser.Dom" Version="2.4.4" />
<PackageReference Include="Fable.Elmish" Version="3.1.0" />
<PackageReference Include="Feliz.Engine" Version="1.0.0-beta-004" />
</ItemGroup>
</Project>

10
src/Feliz.Css.fs Normal file
View File

@@ -0,0 +1,10 @@
namespace Feliz
open Fable.Core
[<Erase>]
type CssStyle = CssStyle of key: string * value: string
[<AutoOpen>]
module Css =
let Css = CssEngine(fun k v -> CssStyle(k, v))

28
src/Lit.fs Normal file
View File

@@ -0,0 +1,28 @@
module Lit
open Fable.Core.JsInterop
open Browser.Types
type TemplateResult =
interface end
module Bindings =
let html: Template.JsTag<TemplateResult> = importMember "lit-html"
let svg: Template.JsTag<TemplateResult> = importMember "lit-html"
let render (t: TemplateResult) (el: HTMLElement): unit = importMember "lit-html"
let styleMap (styles: obj): obj = importMember "lit-html/directives/style-map"
let html: Template.Tag<_> = Template.transform Bindings.html
/// svg is required for nested templates within an svg element
let svg: Template.Tag<_> = Template.transform Bindings.svg
let render el t = Bindings.render t el
/// Equivalent to lit-hmtl styleMap, accepting a list of Feliz styles
let styles (styles: Feliz.CssStyle seq) =
let map = obj()
styles
|> Seq.iter (fun (Feliz.CssStyle(key, value)) ->
map?(key) <- value)
Bindings.styleMap map

13
src/Template.fs Normal file
View File

@@ -0,0 +1,13 @@
module Template
open System
open System.Text.RegularExpressions
type JsTag<'T> = delegate of strs: string[] * [<ParamArray>] args: obj[] -> 'T
type Tag<'T> = FormattableString -> 'T
let transform (tag: JsTag<'T>): Tag<'T> =
fun fmt ->
let strs = Regex(@"\{\d+\}").Split(fmt.Format)
let args = fmt.GetArguments()
tag.Invoke(strs, args)