diff --git a/IfSharp.sln b/IfSharp.sln index 26d8f5a..5fb5bdb 100644 --- a/IfSharp.sln +++ b/IfSharp.sln @@ -9,6 +9,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{3C993D34 ProjectSection(SolutionItems) = preProject build.cmd = build.cmd build.fsx = build.fsx + paket.dependencies = paket.dependencies + paket.lock = paket.lock EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ipython-profile", "ipython-profile", "{F5A3E866-86FB-44AD-9ED1-DB65D6EB0058}" @@ -40,6 +42,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "IfSharp", "src\IfSharp\IfSh EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "IfSharp.Widgets", "src\IfSharp.Widgets\IfSharp.Widgets.fsproj", "{264A3F75-98C9-4B30-BA57-C54C16087C87}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "IfSharp.Kernel.Tests", "tests\IfSharp.Kernel.Tests\IfSharp.Kernel.Tests.fsproj", "{52627028-50B5-427F-BDDB-9DEADB72E7D6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -72,6 +76,14 @@ Global {264A3F75-98C9-4B30-BA57-C54C16087C87}.Release|Any CPU.Build.0 = Release|Any CPU {264A3F75-98C9-4B30-BA57-C54C16087C87}.Release|x64.ActiveCfg = Release|x64 {264A3F75-98C9-4B30-BA57-C54C16087C87}.Release|x64.Build.0 = Release|x64 + {52627028-50B5-427F-BDDB-9DEADB72E7D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52627028-50B5-427F-BDDB-9DEADB72E7D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52627028-50B5-427F-BDDB-9DEADB72E7D6}.Debug|x64.ActiveCfg = Debug|x64 + {52627028-50B5-427F-BDDB-9DEADB72E7D6}.Debug|x64.Build.0 = Debug|x64 + {52627028-50B5-427F-BDDB-9DEADB72E7D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52627028-50B5-427F-BDDB-9DEADB72E7D6}.Release|Any CPU.Build.0 = Release|Any CPU + {52627028-50B5-427F-BDDB-9DEADB72E7D6}.Release|x64.ActiveCfg = Release|x64 + {52627028-50B5-427F-BDDB-9DEADB72E7D6}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/paket.dependencies b/paket.dependencies index 6c7123c..dec78d6 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -13,8 +13,10 @@ nuget Newtonsoft.Json ~> 10.0.3 nuget FAKE >= 4.58.6 nuget xunit 2.1 nuget xunit.runner.console 2.1 +nuget xunit.runner.visualstudio nuget Paket.Core ~> 5.194.0 nuget Trinet.Core.IO.Ntfs +nuget PropertyChanged.Fody #https://github.com/dotnet/corefx/issues/19914 # nuget System.Net.Http 4.3.1 \ No newline at end of file diff --git a/paket.lock b/paket.lock index 78b2d3d..da0ad90 100644 --- a/paket.lock +++ b/paket.lock @@ -7,6 +7,7 @@ NUGET FSharp.Core (>= 4.0.1.7-alpha) NETStandard.Library (>= 1.6) FAKE (5.8.4) + Fody (3.3.5) FSharp.Compiler.Service (25.0.1) FSharp.Core (>= 4.1.18) System.Collections.Immutable (>= 1.5) @@ -27,6 +28,8 @@ NUGET FSharp.Core (> 4.3) Mono.Cecil (>= 0.10.0-beta6) Newtonsoft.Json + PropertyChanged.Fody (2.6) + Fody (>= 3.3.2) System.Collections.Immutable (1.5) System.Reflection.Metadata (1.6) System.Collections.Immutable (>= 1.5) @@ -45,3 +48,4 @@ NUGET xunit.extensibility.execution (2.1) xunit.extensibility.core (2.1) xunit.runner.console (2.1) + xunit.runner.visualstudio (2.4.1) diff --git a/src/IfSharp.Kernel/Kernel.fs b/src/IfSharp.Kernel/Kernel.fs index 621e951..755a9df 100644 --- a/src/IfSharp.Kernel/Kernel.fs +++ b/src/IfSharp.Kernel/Kernel.fs @@ -759,4 +759,7 @@ type IfSharpKernel(connectionInformation : ConnectionInformation) = //Async.Start (async { doHeartbeat() } ) Async.Start (async { doShell() } ) - Async.Start (async { doControl() } ) \ No newline at end of file + Async.Start (async { doControl() } ) + + /// Sends an update + member __.SendWidgetUpdate w = sendWidget w \ No newline at end of file diff --git a/src/IfSharp.Kernel/Printers.fs b/src/IfSharp.Kernel/Printers.fs index 511443b..749ab69 100644 --- a/src/IfSharp.Kernel/Printers.fs +++ b/src/IfSharp.Kernel/Printers.fs @@ -14,6 +14,9 @@ type IWidget = type IWidgetCollection = abstract member GetChildren : unit -> IWidget[] +type IKernel = + abstract member SendWidgetUpdate : IWidget -> unit + type WidgetDataDTO = { buffer_paths: string[] diff --git a/src/IfSharp.Widgets/Color.fs b/src/IfSharp.Widgets/Color.fs index 224d30c..7b22ac6 100644 --- a/src/IfSharp.Widgets/Color.fs +++ b/src/IfSharp.Widgets/Color.fs @@ -1,8 +1,5 @@ namespace IfSharp.Widgets -open IfSharp.Kernel -open Newtonsoft.Json - type ColorPicker() = inherit DOMWidget(modelName = "ColorPickerModel", viewName = "ColorPickerView") member val value = "" with get,set // Color('black', help="The color value.").tag(sync=True) diff --git a/src/IfSharp.Widgets/FodyWeavers.xml b/src/IfSharp.Widgets/FodyWeavers.xml new file mode 100644 index 0000000..4e68ed1 --- /dev/null +++ b/src/IfSharp.Widgets/FodyWeavers.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/IfSharp.Widgets/FodyWeavers.xsd b/src/IfSharp.Widgets/FodyWeavers.xsd new file mode 100644 index 0000000..2f1b8aa --- /dev/null +++ b/src/IfSharp.Widgets/FodyWeavers.xsd @@ -0,0 +1,54 @@ + + + + + + + + + + + Used to control if the On_PropertyName_Changed feature is enabled. + + + + + Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form. + + + + + Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project. + + + + + Used to control if equality checks should use the Equals method resolved from the base class. + + + + + Used to control if equality checks should use the static Equals method resolved from the base class. + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/src/IfSharp.Widgets/IfSharp.Widgets.fsproj b/src/IfSharp.Widgets/IfSharp.Widgets.fsproj index e508fa8..3e4d6b2 100644 --- a/src/IfSharp.Widgets/IfSharp.Widgets.fsproj +++ b/src/IfSharp.Widgets/IfSharp.Widgets.fsproj @@ -1,5 +1,6 @@  + Debug @@ -82,6 +83,7 @@ + @@ -122,4 +124,16 @@ + + + + + ..\..\packages\PropertyChanged.Fody\lib\net452\PropertyChanged.dll + True + True + + + + + \ No newline at end of file diff --git a/src/IfSharp.Widgets/Widgets.fs b/src/IfSharp.Widgets/Widgets.fs index 1959a4c..fa57cfd 100644 --- a/src/IfSharp.Widgets/Widgets.fs +++ b/src/IfSharp.Widgets/Widgets.fs @@ -39,6 +39,7 @@ module Internals = ) open Internals +open System.ComponentModel /// A serializer that only supports writing instances of IWidget such that the notebook /// can link the values together in the UI @@ -115,6 +116,7 @@ type ButtonStyleSerializer() = type Widget(modelName: string, viewName: string, ?modelModule, ?modelModuleVersion, ?viewModule, ?viewModuleVersion) as this = let domClasses = ResizeArray<_>() + let ev = new Event<_,_>() let key = WidgetManager.Register(this) member val comm_id = key @@ -134,6 +136,12 @@ type Widget(modelName: string, viewName: string, ?modelModule, ?modelModuleVersi member __.RemoveClass(className) = domClasses.Remove className + member this.SendUpdate() = + App.Kernel |> Option.iter (fun k -> k.SendWidgetUpdate this) + + [] + member __.PropertyChanged = ev.Publish + interface IWidget with member __.Key = key @@ -145,6 +153,11 @@ type Widget(modelName: string, viewName: string, ?modelModule, ?modelModuleVersi |> Seq.cast |> Seq.toArray + interface INotifyPropertyChanged with + + [] + member __.PropertyChanged = ev.Publish + /// The WidgetManager contains an in-memory dictionary of all instances of Widget that /// have been creates in order to keep track of UI element in the notebook and WidgetManager() = @@ -257,13 +270,20 @@ type DOMWidget(modelName, viewName, ?modelModule, ?modelModuleVersion, ?viewModu [)>] member val style = DescriptionStyle() with get, set +type ValueWidget<'t>(modelName, viewName) = + inherit DOMWidget(modelName, viewName) + member val value : 't = Unchecked.defaultof<'t> with get,set + + member this.OnvalueChanged() = + stdout.WriteLine("OnvalueChanged") + this.SendUpdate() + type Html(?value) = inherit DOMWidget(modelName = "HTMLModel", viewName = "HTMLView") member val value = defaultArg value "" with get,set type IntSlider() = - inherit DOMWidget(modelName = "IntSliderModel", viewName = "IntSliderView") - member val value = 7 with get,set + inherit ValueWidget(modelName = "IntSliderModel", viewName = "IntSliderView") member val min = 0 with get,set member val max = 10 with get,set member val step = 1 with get,set @@ -283,8 +303,7 @@ type IntSlider() = // indent : {True,False} // indent the control to align with other controls with a description. The style.description_width attribute controls this width for consistence with other controls. type Checkbox() = - inherit DOMWidget(modelName = "CheckboxModel", viewName = "CheckboxView") - member val value = false with get,set // Bool(False, help="Bool value").tag(sync=True) + inherit ValueWidget(modelName = "CheckboxModel", viewName = "CheckboxView") member val disabled = false with get,set // Bool(False, help="Enable or disable user changes.").tag(sync=True) member val indent = false with get,set // Bool(True, help="Indent the control to align with other controls with a description.").tag(sync=True) @@ -300,7 +319,7 @@ type Checkbox() = /// icon: str /// font-awesome icon name type ToggleButton() = - inherit DOMWidget(modelName = "ToggleButtonModel", viewName = "ToggleButtonView") + inherit ValueWidget(modelName = "ToggleButtonModel", viewName = "ToggleButtonView") member val value = false with get,set member val tooltip = "" with get,set @@ -315,7 +334,6 @@ type ToggleButton() = // value: {True,False} // value of the Valid widget type Valid() = - inherit DOMWidget(modelName = "ValidModel", viewName = "ValidView") - member val value = false with get,set // Bool(False, help="Bool value").tag(sync=True) + inherit ValueWidget(modelName = "ValidModel", viewName = "ValidView") member val disabled = false with get,set // Bool(False, help="Enable or disable user changes.").tag(sync=True) member val readout = "" with get,set // Unicode('Invalid', help="Message displayed when the value is False").tag(sync=True) diff --git a/src/IfSharp.Widgets/paket.references b/src/IfSharp.Widgets/paket.references index fa39795..2caafa7 100644 --- a/src/IfSharp.Widgets/paket.references +++ b/src/IfSharp.Widgets/paket.references @@ -1,2 +1,3 @@ FSharp.Core -Newtonsoft.JSON \ No newline at end of file +Newtonsoft.JSON +PropertyChanged.Fody \ No newline at end of file diff --git a/src/IfSharp/App.config b/src/IfSharp/App.config index 3392f17..a86e60b 100644 --- a/src/IfSharp/App.config +++ b/src/IfSharp/App.config @@ -6,7 +6,6 @@ - True @@ -67,6 +66,11 @@ + + True + + + True diff --git a/tests/IfSharp.Kernel.Tests/ExampleTest.fs b/tests/IfSharp.Kernel.Tests/ExampleTest.fs deleted file mode 100644 index faa8eb6..0000000 --- a/tests/IfSharp.Kernel.Tests/ExampleTest.fs +++ /dev/null @@ -1,11 +0,0 @@ -module ExampleTest - -open Xunit - -open IfSharp.Kernel - -[] -let ``Trivial example test``() = - - let asSvg = Util.Svg "test" - Assert.Equal("test", asSvg.Svg) \ No newline at end of file diff --git a/tests/IfSharp.Kernel.Tests/IfSharp.Kernel.Tests.fsproj b/tests/IfSharp.Kernel.Tests/IfSharp.Kernel.Tests.fsproj index 07194f3..56f7833 100644 --- a/tests/IfSharp.Kernel.Tests/IfSharp.Kernel.Tests.fsproj +++ b/tests/IfSharp.Kernel.Tests/IfSharp.Kernel.Tests.fsproj @@ -9,7 +9,7 @@ Library IfSharp.Kernel.Tests IfSharp.Kernel.Tests - v4.7.1 + v4.7.2 4.3.1.0 IfSharp.Kernel.Tests ..\..\ @@ -73,6 +73,14 @@ + + + + <__paket__xunit_runner_visualstudio_props>net20\xunit.runner.visualstudio + + + + @@ -87,26 +95,28 @@ --> + - - + - - IfSharp.Kernel {2fe619b3-4756-4285-b31f-232607f62d78} True + + IfSharp.Widgets + {264a3f75-98c9-4b30-ba57-c54c16087c87} + True + - @@ -174,6 +184,17 @@ + + + + + ..\..\packages\Newtonsoft.Json\lib\net45\Newtonsoft.Json.dll + True + True + + + + diff --git a/tests/IfSharp.Kernel.Tests/Scratch.fsx b/tests/IfSharp.Kernel.Tests/Scratch.fsx deleted file mode 100644 index 5f28270..0000000 --- a/tests/IfSharp.Kernel.Tests/Scratch.fsx +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tests/IfSharp.Kernel.Tests/WidgetTests.fs b/tests/IfSharp.Kernel.Tests/WidgetTests.fs new file mode 100644 index 0000000..49f2421 --- /dev/null +++ b/tests/IfSharp.Kernel.Tests/WidgetTests.fs @@ -0,0 +1,80 @@ +module WidgetTests + +open Xunit +open IfSharp.Widgets +open IfSharp.Kernel +open Newtonsoft.Json +open System.IO + +let getParents (w: #IWidget) = w.GetParents() +let getKey (w: #IWidget) = w.Key + +[] +let propertyChangeEventsShouldWork() = + let propertiesChanged = ResizeArray() + + let w = Html() + w.PropertyChanged.Add(fun x -> propertiesChanged.Add x.PropertyName) + w.description <- "description" + + Assert.Equal(1, propertiesChanged.Count) + Assert.Equal("description", propertiesChanged |> Seq.head) + +[] +let selectionContainerFormalConstructorShouldWork() = + let children = + [| + "Html1", Html() :> IWidget + "Html2", Html() :> IWidget + |] + + let container = SelectionContainer("modelName", "viewName", children) + Assert.Equal(2, container.children.Length) + Assert.Equal(2, container._titles.Count) + Assert.Equal<_[]>([| children.[0] |> snd; children.[1] |> snd |], container.children) + Assert.Equal<_[]>([| children.[0] |> fst; children.[1] |> fst |], container._titles.Values |> Seq.toArray) + +[] +let getParentsShouldWork() = + let dw = DOMWidget("modelName", "viewName") + let parents = getParents dw + Assert.Equal(2, parents.Length) + Assert.Equal(dw.layout, parents.[0] :?> Layout) + Assert.Equal(dw.style, parents.[1] :?> DescriptionStyle) + +[] +let widgetSerializerShoulWriteWidget() = + use sw = new StringWriter() + use jw = new JsonTextWriter(sw) + let serializer = JsonSerializer() + let ws = WidgetSerializer() + let widget = Html() + ws.WriteJson(jw, widget, serializer) + + let actual = sw.ToString() + Assert.Contains("IPY_MODEL", actual) + Assert.Contains(widget |> getKey |> string, actual) + +[] +let widgetSerializerShouldWriteWidgetArray() = + use sw = new StringWriter() + use jw = new JsonTextWriter(sw) + let serializer = JsonSerializer() + let ws = WidgetSerializer() + let widgets = + [| + Html() :> IWidget + VBox() :> IWidget + HBox() :> IWidget + |] + + ws.WriteJson(jw, widgets, serializer) + + let actual = JsonConvert.DeserializeObject(sw.ToString()) + Assert.Equal(3, actual.Length) + Assert.Contains("IPY_MODEL", actual.[0]) + Assert.Contains("IPY_MODEL", actual.[1]) + Assert.Contains("IPY_MODEL", actual.[2]) + Assert.Contains(widgets.[0] |> getKey |> string, actual.[0]) + Assert.Contains(widgets.[1] |> getKey |> string, actual.[1]) + Assert.Contains(widgets.[2] |> getKey |> string, actual.[2]) diff --git a/tests/IfSharp.Kernel.Tests/app.config b/tests/IfSharp.Kernel.Tests/app.config index 1a5fdea..8f38bec 100644 --- a/tests/IfSharp.Kernel.Tests/app.config +++ b/tests/IfSharp.Kernel.Tests/app.config @@ -1,9 +1,11 @@  - + - + + + True @@ -44,6 +46,11 @@ + + True + + + True @@ -59,4 +66,25 @@ - \ No newline at end of file + + True + + + + + True + + + + + True + + + + + True + + + + + \ No newline at end of file diff --git a/tests/IfSharp.Kernel.Tests/paket.references b/tests/IfSharp.Kernel.Tests/paket.references index 5772b3d..69e8243 100644 --- a/tests/IfSharp.Kernel.Tests/paket.references +++ b/tests/IfSharp.Kernel.Tests/paket.references @@ -1,4 +1,6 @@ FSharp.Compiler.Service Chessie NetMQ -xUnit \ No newline at end of file +xunit +xunit.runner.visualstudio +Newtonsoft.JSON \ No newline at end of file