diff --git a/Blazor.Diagrams.sln b/Blazor.Diagrams.sln index 491396633..5f73d1027 100644 --- a/Blazor.Diagrams.sln +++ b/Blazor.Diagrams.sln @@ -41,6 +41,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Layouts", "docs\Layouts\Lay EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Diagrams.Tests", "tests\Blazor.Diagrams.Tests\Blazor.Diagrams.Tests.csproj", "{ED3B0D8F-F29A-4C66-A167-C36824A76902}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "site", "site", "{F1E6F4C0-3EC7-4CFF-834A-0CF207CCFF3E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Site", "site\Site\Site.csproj", "{F26307EC-C188-44BD-B3E5-960318F43C0C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -91,6 +95,10 @@ Global {ED3B0D8F-F29A-4C66-A167-C36824A76902}.Debug|Any CPU.Build.0 = Debug|Any CPU {ED3B0D8F-F29A-4C66-A167-C36824A76902}.Release|Any CPU.ActiveCfg = Release|Any CPU {ED3B0D8F-F29A-4C66-A167-C36824A76902}.Release|Any CPU.Build.0 = Release|Any CPU + {F26307EC-C188-44BD-B3E5-960318F43C0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F26307EC-C188-44BD-B3E5-960318F43C0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F26307EC-C188-44BD-B3E5-960318F43C0C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F26307EC-C188-44BD-B3E5-960318F43C0C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -107,6 +115,7 @@ Global {3D104DB4-C7F0-42CA-9D78-AB2C8A8AE3D5} = {A9FC9B20-A9F1-4066-8B59-83BD26D3B1C8} {78C85C89-B464-4083-8829-78BA52BB4780} = {A9FC9B20-A9F1-4066-8B59-83BD26D3B1C8} {ED3B0D8F-F29A-4C66-A167-C36824A76902} = {CEEAE4C2-CE68-4FC3-9E0F-D4781B91F7F4} + {F26307EC-C188-44BD-B3E5-960318F43C0C} = {F1E6F4C0-3EC7-4CFF-834A-0CF207CCFF3E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {969540A2-8162-4063-A4E3-B488F69BD582} diff --git a/CHANGELOG.md b/CHANGELOG.md index fd2c8f1c9..0a89280b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Diagrams (3.0.0-beta.6) - 2023-05-09 + +### Added + +- `Style` parameter to `PortRenderer` +- `TargetAttached` to links, which triggers when a dragged link attaches to a target + - If port snapping is enabled, it will trigger only once when you let go of the mouse +- `SuspendSorting` to `Diagram` in order to suspend sorting models in each `OrderChanged` + - If you know what you're doing, you could save some processing and avoid sorting everytime +- `RefreshOrders` to be called after unsuspending sorting in order to sort the models again and refresh the diagram + +### Changed + +- `BaseLayer.Add` now returns the specific type given to it in argument +- **[BREAKING]** CSS classes are now prefixed with `diagram-` to avoid clashes with other libraries + - `diagram-group`, `diagram-node`, `diagram-link`, `diagram-port`, `diagram-link-label`, `diagram-link-vertex`, `diagram-control` + +### Fixed + +- Portless links in children not refreshing when moving the parent group +- Link's `GetBounds` not returning a valid box +- Port snapping choosing the first port in radius rather than the closest one +- Remove `Console.WriteLine` from `KeyboardShortcutsBehavior` +- Diagram overwriting `Order` when it's not zero (zero being the default int value, which we now consider as not set) + ## Diagrams (3.0.0-beta.5) - 2022-11-23 ### Added diff --git a/samples/SharedDemo/wwwroot/css/styles.css b/samples/SharedDemo/wwwroot/css/styles.css index 63c008968..3f06bf575 100644 --- a/samples/SharedDemo/wwwroot/css/styles.css +++ b/samples/SharedDemo/wwwroot/css/styles.css @@ -1550,15 +1550,15 @@ body { max-width: 400px; } -.custom-node.selected, .custom-node.selected .port { +.custom-node.selected, .custom-node.selected .diagram-port { border: 1px solid #6e9fd4; } -.custom-node:hover .port { +.custom-node:hover .diagram-port { visibility: visible; } -.custom-node .port { +.custom-node .diagram-port { width: 20px; height: 20px; border-radius: 50%; @@ -1568,18 +1568,18 @@ body { visibility: hidden; } - .custom-node .port:hover, .node > .card .port.has-links { + .custom-node .diagram-port:hover, .diagram-node > .card .diagram-port.has-links { visibility: visible; background-color: black; } - .custom-node .port.bottom { + .custom-node .diagram-port.bottom { position: absolute; bottom: -10px; left: 115px; } - .custom-node .port.top { + .custom-node .diagram-port.top { position: absolute; top: -10px; left: 115px; @@ -1599,16 +1599,16 @@ body { border-color: blue; } - .colored-node .port { + .colored-node .diagram-port { width: 30px; height: 30px; } - .colored-node .port.blue { + .colored-node .diagram-port.blue { background-color: blue; } - .colored-node .port.red { + .colored-node .diagram-port.red { background-color: red; } @@ -1616,12 +1616,12 @@ body { background: #40babd !important; } -div.group:not(.default) { +div.diagram-group:not(.default) { outline: 2px solid black; background-color: #6fbb6e; } -div.group:not(.default) > span.title { +div.diagram-group:not(.default) > span.title { padding: 20px; position: absolute; left: 50%; @@ -1634,11 +1634,11 @@ div.group:not(.default) > span.title { text-transform: uppercase; } -g.group:not(.default) rect { +g.diagram-group:not(.default) rect { outline: 2px solid black; } -g.group:not(.default) text { +g.diagram-group:not(.default) text { text-anchor: middle; dominant-baseline: middle; } \ No newline at end of file diff --git a/site/Site/App.razor b/site/Site/App.razor new file mode 100644 index 000000000..11850a587 --- /dev/null +++ b/site/Site/App.razor @@ -0,0 +1,12 @@ + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
\ No newline at end of file diff --git a/site/Site/Components/Documentation/MyDiagram.razor b/site/Site/Components/Documentation/MyDiagram.razor new file mode 100644 index 000000000..dae68b753 --- /dev/null +++ b/site/Site/Components/Documentation/MyDiagram.razor @@ -0,0 +1,5 @@ +
+ + + +
\ No newline at end of file diff --git a/site/Site/Components/Documentation/MyDiagram.razor.cs b/site/Site/Components/Documentation/MyDiagram.razor.cs new file mode 100644 index 000000000..32b5845e8 --- /dev/null +++ b/site/Site/Components/Documentation/MyDiagram.razor.cs @@ -0,0 +1,51 @@ +using Blazor.Diagrams; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.PathGenerators; +using Blazor.Diagrams.Core.Routers; +using Blazor.Diagrams.Options; + +namespace Site.Components.Documentation; + +public partial class MyDiagram +{ + private BlazorDiagram Diagram { get; set; } = null!; + + protected override void OnInitialized() + { + var options = new BlazorDiagramOptions + { + AllowMultiSelection = true, + Zoom = + { + Enabled = false, + }, + Links = + { + DefaultRouter = new NormalRouter(), + DefaultPathGenerator = new SmoothPathGenerator() + }, + }; + + Diagram = new BlazorDiagram(options); + + var firstNode = Diagram.Nodes.Add(new NodeModel(position: new Point(50, 50)) + { + Title = "Node 1" + }); + var secondNode = Diagram.Nodes.Add(new NodeModel(position: new Point(200, 100)) + { + Title = "Node 2" + }); + var leftPort = secondNode.AddPort(PortAlignment.Left); + var rightPort = secondNode.AddPort(PortAlignment.Right); + + // The connection point will be the intersection of + // a line going from the target to the center of the source + var sourceAnchor = new ShapeIntersectionAnchor(firstNode); + // The connection point will be the port's position + var targetAnchor = new SinglePortAnchor(leftPort); + var link = Diagram.Links.Add(new LinkModel(sourceAnchor, targetAnchor)); + } +} diff --git a/site/Site/Components/Documentation/MyDiagram.razor.css b/site/Site/Components/Documentation/MyDiagram.razor.css new file mode 100644 index 000000000..7c72f2956 --- /dev/null +++ b/site/Site/Components/Documentation/MyDiagram.razor.css @@ -0,0 +1,5 @@ +.diagram-container { + width: 100%; + height: 400px; + border: 1px solid black; +} \ No newline at end of file diff --git a/site/Site/Components/Documentation/NavigationButtons.razor b/site/Site/Components/Documentation/NavigationButtons.razor new file mode 100644 index 000000000..7ced83530 --- /dev/null +++ b/site/Site/Components/Documentation/NavigationButtons.razor @@ -0,0 +1,33 @@ +
+ + @if (PreviousTitle != null) + { + +
+ + + + @PreviousTitle +
+
+ } + + @if (NextTitle != null) + { + +
+ @NextTitle + + + +
+
+ } +
+ +@code { + [Parameter] public string? PreviousTitle { get; set; } + [Parameter] public string? PreviousLink { get; set; } + [Parameter] public string? NextTitle { get; set; } + [Parameter] public string? NextLink { get; set; } +} \ No newline at end of file diff --git a/site/Site/Components/Footer.razor b/site/Site/Components/Footer.razor new file mode 100644 index 000000000..20712847d --- /dev/null +++ b/site/Site/Components/Footer.razor @@ -0,0 +1,71 @@ + \ No newline at end of file diff --git a/site/Site/Components/Landing/AddNodeWidget.razor b/site/Site/Components/Landing/AddNodeWidget.razor new file mode 100644 index 000000000..37fc0c457 --- /dev/null +++ b/site/Site/Components/Landing/AddNodeWidget.razor @@ -0,0 +1,27 @@ +
+
+ + + + Add +
+
+ @Node.Value + + + +
+
+ +@code { + + [Parameter] + public AddNodeModel Node { get; set; } = null!; + +} \ No newline at end of file diff --git a/site/Site/Components/Landing/ColoredNodeWidget.razor b/site/Site/Components/Landing/ColoredNodeWidget.razor new file mode 100644 index 000000000..590cba0df --- /dev/null +++ b/site/Site/Components/Landing/ColoredNodeWidget.razor @@ -0,0 +1,27 @@ +@using System.Text; +@using Blazor.Diagrams.Extensions; +@{ + var borderColor = Node.Selected ? $"outline-{Node.Color}-darker" : $"outline-{Node.Color}"; + var extraClasses = new StringBuilder().Append($"bg-{Node.Color}").AppendIf(" rounded-full", Node.Round); +} + +
+ @Node.Title + + @foreach (var port in Node.Ports) + { + var classes = new StringBuilder("absolute -translate-y-1/2 ") + .Append(extraClasses) + .AppendIf(" top-1/2 -right-3", port.Alignment == PortAlignment.Right) + .AppendIf(" top-1/2 -left-3", port.Alignment == PortAlignment.Left); + + + } +
+ +@code { + + [Parameter] + public ColoredNodeModel Node { get; set; } = null!; + +} \ No newline at end of file diff --git a/site/Site/Components/Landing/Features/FeaturesExample.razor b/site/Site/Components/Landing/Features/FeaturesExample.razor new file mode 100644 index 000000000..15af784b0 --- /dev/null +++ b/site/Site/Components/Landing/Features/FeaturesExample.razor @@ -0,0 +1,34 @@ +
+ +
+
+
+

+ Lots of features +

+

+ Blazor Diagrams offers many features, including: +

+
    +
  • Touch support
  • +
  • Fully customization and extensibility
  • +
  • Link routers, path generators, markers and labels
  • +
  • Controls on top of nodes/links
  • +
  • Node Ports
  • +
  • And much more
  • +
+
+
+
+ + + + + + + +
+
+
+
+
\ No newline at end of file diff --git a/site/Site/Components/Landing/Features/FeaturesExample.razor.cs b/site/Site/Components/Landing/Features/FeaturesExample.razor.cs new file mode 100644 index 000000000..e23036ad8 --- /dev/null +++ b/site/Site/Components/Landing/Features/FeaturesExample.razor.cs @@ -0,0 +1,114 @@ +using Blazor.Diagrams; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.PathGenerators; +using Blazor.Diagrams.Core.Positions; +using Blazor.Diagrams.Core.Routers; +using Site.Models.Landing; + +namespace Site.Components.Landing.Features +{ + public partial class FeaturesExample + { + private readonly BlazorDiagram _diagram = new(); + + protected override void OnInitialized() + { + _diagram.Options.Zoom.Enabled = false; + _diagram.Options.GridSize = 10; + _diagram.Options.Links.RequireTarget = false; + _diagram.Options.Links.DefaultPathGenerator = new StraightPathGenerator(); + _diagram.RegisterComponent(); + _diagram.RegisterComponent(); + + var smoothPathGenerator = new SmoothPathGenerator(); + + // ArrowHeadControl example + var node1 = _diagram.Nodes.Add(new ColoredNodeModel("Locked", true, "color2", new Point(20, 20))); + node1.Locked = true; + var link1 = _diagram.Links.Add(new LinkModel(new ShapeIntersectionAnchor(node1), new PositionAnchor(new Point(340, 60))) + { + Color = "#DC9A7A", + SelectedColor = "#874423", + TargetMarker = LinkMarker.Arrow + }); + _diagram.Controls.AddFor(link1).Add(new ArrowHeadControl(false)); + link1.Labels.Add(new LinkLabelModel(link1, "I am a free link", 0.5, new Point(0, -15))); + + // Labels example + var node2 = _diagram.Nodes.Add(new ColoredNodeModel("Movable", true, "color1", new Point(20, 350))); + var link2 = _diagram.Links.Add(new LinkModel(node1, node2) + { + Color = "#DC9A7A", + SelectedColor = "#874423", + TargetMarker = LinkMarker.Arrow + }); + link2.Labels.Add(new LinkLabelModel(link1, "Start", 0.1, new Point(0, 0))); + link2.Labels.Add(new LinkLabelModel(link1, "Middle", 0.5, new Point(0, 0))); + link2.Labels.Add(new LinkLabelModel(link1, "End", 0.9, new Point(0, 0))); + + // Controls example + var node3 = _diagram.Nodes.Add(new ColoredNodeModel("Select me", false, "color3", new Point(320, 370))); + var link3 = _diagram.Links.Add(new LinkModel(node1, node3) + { + Color = "#9EA5E3", + SelectedColor = "#2b3595", + PathGenerator = smoothPathGenerator, + TargetMarker = LinkMarker.Arrow + }); + link3.Labels.Add(new LinkLabelModel(link3, "Select me to show controls", 0.5, new Point(0, -15))); + + _diagram.Controls.AddFor(link3) + .Add(new ArrowHeadControl(true)) + .Add(new ArrowHeadControl(false)) + .Add(new BoundaryControl()) + .Add(new RemoveControl(new LinkPathPositionProvider(0.8, 0, -10))); + + _diagram.Controls.AddFor(node3) + .Add(new BoundaryControl()) + .Add(new RemoveControl(new BoundsBasedPositionProvider(1, 0, 10, -10))); + + // Ports and Routes example + var node4 = _diagram.Nodes.Add(new ColoredNodeModel("With ports", false, "color1", new Point(560, 20))); + var node5 = _diagram.Nodes.Add(new ColoredNodeModel("Locked", true, "color2", new Point(720, 350))); + node5.Locked = true; + + var port1 = node3.AddPort(PortAlignment.Right); + var port2 = node4.AddPort(PortAlignment.Left); + var port3 = node4.AddPort(PortAlignment.Right); + var port4 = node5.AddPort(PortAlignment.Left); + var port5 = node5.AddPort(PortAlignment.Right); + + var link4 = _diagram.Links.Add(new LinkModel(port1, port2) + { + Color = "#9EA5E3", + SelectedColor = "#2b3595", + PathGenerator = smoothPathGenerator, + TargetMarker = LinkMarker.Arrow + }); + link4.Labels.Add(new LinkLabelModel(link4, "Smooth PathGenerator", 0.5)); + + var link5 = _diagram.Links.Add(new LinkModel(port3, port4) + { + Color = "#A0B15B", + SelectedColor = "#515a2b", + Router = new OrthogonalRouter(), + PathGenerator = new StraightPathGenerator(20), + TargetMarker = new LinkMarker("M 0 -8 L 8 -8 4 0 8 8 0 8 4 0 z", 8) + }); + link5.Labels.Add(new LinkLabelModel(link5, "Orthogonal Router", 0.3)); + link5.Labels.Add(new LinkLabelModel(link5, "Custom marker", 0.8)); + + _diagram.Links.Add(new LinkModel(port3, port5) + { + Color = "#A0B15B", + SelectedColor = "#515a2b", + Router = new OrthogonalRouter(), + PathGenerator = new StraightPathGenerator(), + TargetMarker = LinkMarker.Arrow + }); + } + } +} diff --git a/site/Site/Components/Landing/Groups/GroupWidget.razor b/site/Site/Components/Landing/Groups/GroupWidget.razor new file mode 100644 index 000000000..912079b6a --- /dev/null +++ b/site/Site/Components/Landing/Groups/GroupWidget.razor @@ -0,0 +1,15 @@ +
+ + + @foreach (var port in Group.Ports) + { + + } +
+ +@code { + + [Parameter] + public ColoredGroupModel Group { get; set; } = null!; + +} \ No newline at end of file diff --git a/site/Site/Components/Landing/Groups/GroupsExample.razor b/site/Site/Components/Landing/Groups/GroupsExample.razor new file mode 100644 index 000000000..2c0199ed6 --- /dev/null +++ b/site/Site/Components/Landing/Groups/GroupsExample.razor @@ -0,0 +1,37 @@ +
+
+
+
+
+ + + + + + + + + +
+
+
+

+ Groups +

+

+ Blazor Diagrams supports groups and nested hierarchies. You can have: +

+
    +
  • Nodes inside of groups
  • +
  • Groups inside of other groups
  • +
  • Group auto size to its children's bounds
  • +
  • Links, of course
  • +
+
+
+
+
\ No newline at end of file diff --git a/site/Site/Components/Landing/Groups/GroupsExample.razor.cs b/site/Site/Components/Landing/Groups/GroupsExample.razor.cs new file mode 100644 index 000000000..1d47bd8f6 --- /dev/null +++ b/site/Site/Components/Landing/Groups/GroupsExample.razor.cs @@ -0,0 +1,50 @@ +using Blazor.Diagrams; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.PathGenerators; +using Site.Models.Landing; +using Site.Models.Landing.Groups; + +namespace Site.Components.Landing.Groups +{ + public partial class GroupsExample + { + private readonly BlazorDiagram _diagram = new(); + + protected override void OnInitialized() + { + _diagram.Options.Zoom.Enabled = false; + _diagram.Options.GridSize = 30; + _diagram.Options.LinksLayerOrder = 2; + _diagram.Options.NodesLayerOrder = 1; + _diagram.Options.Links.DefaultPathGenerator = new StraightPathGenerator(); + _diagram.RegisterComponent(); + _diagram.RegisterComponent(); + + var node1 = _diagram.Nodes.Add(new ColoredNodeModel("Node 1", false, "color3", new Point(90, 60))); + var node2 = _diagram.Nodes.Add(new ColoredNodeModel("Node 2", false, "color3", new Point(390, 60))); + var node3 = _diagram.Nodes.Add(new ColoredNodeModel("Node 3", false, "color1", new Point(180, 240))); + var node4 = _diagram.Nodes.Add(new ColoredNodeModel("Node 4", false, "color1", new Point(330, 240))); + + var group1 = _diagram.Groups.Add(new ColoredGroupModel(new[] { node3, node4 }, "color2")); + var group2 = _diagram.Groups.Add(new ColoredGroupModel(new[] { (NodeModel)group1, node1, node2 }, "color2")); + + _diagram.Links.Add(new LinkModel(node1, node2) + { + TargetMarker = LinkMarker.Arrow, + }); + _diagram.Links.Add(new LinkModel(node2, node3) + { + TargetMarker = LinkMarker.Arrow, + }); + _diagram.Links.Add(new LinkModel(node3, node1) + { + TargetMarker = LinkMarker.Arrow, + }); + _diagram.Links.Add(new LinkModel(node2, group1) + { + TargetMarker = LinkMarker.Arrow, + }); + } + } +} diff --git a/site/Site/Components/Landing/LandingShowcaseDiagram.razor b/site/Site/Components/Landing/LandingShowcaseDiagram.razor new file mode 100644 index 000000000..bc38c3be7 --- /dev/null +++ b/site/Site/Components/Landing/LandingShowcaseDiagram.razor @@ -0,0 +1,27 @@ +
+
+ + + + + + + + + +
+

+ Blazor Diagrams +

+

+ A fully customizable, extensible and all-purpose
open-source diagrams library for Blazor +

+ + + + + Get started + +
+
+
\ No newline at end of file diff --git a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs new file mode 100644 index 000000000..835c8fdcd --- /dev/null +++ b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs @@ -0,0 +1,82 @@ +using Blazor.Diagrams; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; +using Site.Models.Landing; + +namespace Site.Components.Landing; + +public partial class LandingShowcaseDiagram +{ + private readonly BlazorDiagram _diagram = new(); + + protected override void OnInitialized() + { + _diagram.Options.Zoom.Enabled = false; + _diagram.Options.GridSize = 20; + _diagram.RegisterComponent(); + _diagram.RegisterComponent(); + + _diagram.Nodes.Added += OnNodeAdded; + _diagram.Nodes.Removed += OnNodeRemoved; + _diagram.Links.Added += OnLinkAdded; + _diagram.Links.Removed += OnLinkRemoved; + + var n1 = _diagram.Nodes.Add(new NumberNodeModel(new Point(200, 100))); + var n2 = _diagram.Nodes.Add(new NumberNodeModel(new Point(200, 260))); + var n3 = _diagram.Nodes.Add(new NumberNodeModel(new Point(480, 260))); + var a1 = _diagram.Nodes.Add(new AddNodeModel(new Point(500, 100))); + var a2 = _diagram.Nodes.Add(new AddNodeModel(new Point(750, 200))); + + _diagram.Links.Add(new LinkModel(n1.Ports[0], a1.Ports[0])); + _diagram.Links.Add(new LinkModel(n2.Ports[0], a1.Ports[1])); + _diagram.Links.Add(new LinkModel(a1.Ports[2], a2.Ports[0])); + _diagram.Links.Add(new LinkModel(n3.Ports[0], a2.Ports[1])); + + n1.Value = 3; + n2.Value = 91; + n3.Value = 25; + } + + private void OnNodeAdded(NodeModel node) + { + (node as BaseOperation)!.ValueChanged += OnValueChanged; + } + + private void OnNodeRemoved(NodeModel node) + { + (node as BaseOperation)!.ValueChanged -= OnValueChanged; + } + + private void OnValueChanged(BaseOperation op) + { + foreach (var link in _diagram.Links) + { + var sp = (link.Source as SinglePortAnchor)!; + var tp = (link.Target as SinglePortAnchor)!; + var otherNode = sp.Port.Parent == op ? tp.Port.Parent : sp.Port.Parent; + otherNode.Refresh(); + } + } + + private void OnLinkAdded(BaseLinkModel link) + { + link.TargetChanged += OnLinKTargetChanged; + } + + private void OnLinKTargetChanged(BaseLinkModel link, Anchor? oldTarget, Anchor? newTarget) + { + if (oldTarget == null && newTarget != null) // First attach + { + (newTarget.Model as PortModel)!.Parent.Refresh(); + } + } + + private void OnLinkRemoved(BaseLinkModel link) + { + (link.Source.Model as PortModel)!.Parent.Refresh(); + if (link.Target != null) (link.Target.Model as PortModel)!.Parent.Refresh(); + link.TargetChanged -= OnLinKTargetChanged; + } +} \ No newline at end of file diff --git a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.css b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.css new file mode 100644 index 000000000..7a13d8421 --- /dev/null +++ b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.css @@ -0,0 +1,10 @@ +::deep g.diagram-link path:not(.selection-helper) { + stroke-dasharray: 5; + animation: dash .5s linear infinite; +} + +@keyframes dash { + to { + stroke-dashoffset: -10; + } +} \ No newline at end of file diff --git a/site/Site/Components/Landing/LinkLabelWidget.razor b/site/Site/Components/Landing/LinkLabelWidget.razor new file mode 100644 index 000000000..27917d257 --- /dev/null +++ b/site/Site/Components/Landing/LinkLabelWidget.razor @@ -0,0 +1,10 @@ +
+ @Label.Content +
+ +@code { + + [Parameter] + public LinkLabelModel Label { get; set; } = null!; + +} \ No newline at end of file diff --git a/site/Site/Components/Landing/NumberNodeWidget.razor b/site/Site/Components/Landing/NumberNodeWidget.razor new file mode 100644 index 000000000..527eee13a --- /dev/null +++ b/site/Site/Components/Landing/NumberNodeWidget.razor @@ -0,0 +1,21 @@ +
+
+ + + + Number +
+
+ + +
+
+ +@code { + + [Parameter] + public NumberNodeModel Node { get; set; } = null!; + +} \ No newline at end of file diff --git a/site/Site/Components/Landing/StatisticsLine.razor b/site/Site/Components/Landing/StatisticsLine.razor new file mode 100644 index 000000000..25fedc4a9 --- /dev/null +++ b/site/Site/Components/Landing/StatisticsLine.razor @@ -0,0 +1,27 @@ +
+
+
+ +
+

@_stars

+
Stars
+
+ +
+

MIT

+
License
+
+ +
+

@_downloads

+
Downloads
+
+ +
+

@_version

+
Version
+
+ +
+
+
\ No newline at end of file diff --git a/site/Site/Components/Landing/StatisticsLine.razor.cs b/site/Site/Components/Landing/StatisticsLine.razor.cs new file mode 100644 index 000000000..6d19fbbd2 --- /dev/null +++ b/site/Site/Components/Landing/StatisticsLine.razor.cs @@ -0,0 +1,57 @@ +using Microsoft.AspNetCore.Components; +using System.Net.Http.Json; +using System.Text.Json; + +namespace Site.Components.Landing +{ + public partial class StatisticsLine + { + private int _stars; + private int _downloads; + private string _version = "1.0.0"; + + [Inject] private HttpClient HttpClient { get; set; } = null!; + + protected override async Task OnInitializedAsync() + { + (_version, _downloads) = await GetVersionAndDownloads(); + _stars = await GetStars(); + } + + private async Task GetStars() + { + var content = await HttpClient.GetFromJsonAsync("https://api.github.com/repos/Blazor-Diagrams/Blazor.Diagrams"); + if (content == null) + return 0; + + return content.RootElement.GetProperty("stargazers_count").GetInt32(); + } + + private async Task<(string, int)> GetVersionAndDownloads() + { + var content = await HttpClient.GetFromJsonAsync("https://api.nuget.org/v3/index.json"); + if (content != null) + { + foreach (var resource in content.RootElement.GetProperty("resources").EnumerateArray()) + { + if (resource.GetProperty("@type").GetString() == "SearchQueryService") + { + var url = resource.GetProperty("@id").GetString(); + var packageContent = await HttpClient.GetFromJsonAsync(url + "?prerelease=true&q=packageid:Z.Blazor.Diagrams"); + if (packageContent != null) + { + foreach (var data in packageContent.RootElement.GetProperty("data").EnumerateArray()) + { + var version = data.GetProperty("version").GetString()!; + var downloads = data.GetProperty("totalDownloads").GetInt32(); + return (version, downloads); + } + } + } + } + } + + return ("1.0.0", 0); + } + } +} diff --git a/site/Site/Components/Landing/SvgAndHtml/BatteryChargerNodeWidget.razor b/site/Site/Components/Landing/SvgAndHtml/BatteryChargerNodeWidget.razor new file mode 100644 index 000000000..fb4519062 --- /dev/null +++ b/site/Site/Components/Landing/SvgAndHtml/BatteryChargerNodeWidget.razor @@ -0,0 +1,18 @@ +
+

+ HTML Charger +

+ +
+ +@code { + [Parameter] public BatteryChargerNodeModel Node { get; set; } = null!; + + private void OnValueChanged(ChangeEventArgs e) + { + Node.Setter(int.Parse(e.Value!.ToString()!)); + } +} diff --git a/site/Site/Components/Landing/SvgAndHtml/BatteryNodeWidget.razor b/site/Site/Components/Landing/SvgAndHtml/BatteryNodeWidget.razor new file mode 100644 index 000000000..abfedbe29 --- /dev/null +++ b/site/Site/Components/Landing/SvgAndHtml/BatteryNodeWidget.razor @@ -0,0 +1,19 @@ +@{ + var color = Node.Percentage > 70 ? "green" : (Node.Percentage > 30 ? "orange" : "red"); +} + + + + + + + + + + + +SVG Battery + +@code { + [Parameter] public BatteryNodeModel Node { get; set; } = null!; +} \ No newline at end of file diff --git a/site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor b/site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor new file mode 100644 index 000000000..4fd73bcfb --- /dev/null +++ b/site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor @@ -0,0 +1,29 @@ +
+ +
+
+
+
+ + + + + + + + +
+
+
+

+ SVG and HTML +

+

+ Blazor Diagrams contains two layers, SVG and HTML.
+ The HTML layer is responsible for rendering nodes only, for maximum customization and interactivity. + While the SVG layer is responsible for rendering the links, but can also render nodes. +

+
+
+
+
\ No newline at end of file diff --git a/site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor.cs b/site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor.cs new file mode 100644 index 000000000..68e253383 --- /dev/null +++ b/site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor.cs @@ -0,0 +1,35 @@ +using Blazor.Diagrams; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.PathGenerators; +using Site.Models.Landing.SvgAndHtml; + +namespace Site.Components.Landing.SvgAndHtml +{ + public partial class SvgAndHtmlExample + { + private readonly BlazorDiagram _diagram = new(); + + protected override void OnInitialized() + { + _diagram.Options.GridSize = 30; + _diagram.Options.Constraints.ShouldDeleteLink = _ => ValueTask.FromResult(false); + _diagram.Options.Zoom.Enabled = false; + _diagram.Options.Links.DefaultPathGenerator = new StraightPathGenerator(); + _diagram.RegisterComponent(); + _diagram.RegisterComponent(); + + var battery = new BatteryNodeModel(new Point(90, 150)); + var port1 = battery.AddPort(PortAlignment.Right); + var port2 = battery.AddPort(PortAlignment.Right); + + _diagram.Nodes.Add(battery); + var charger1 = _diagram.Nodes.Add(new BatteryChargerNodeModel(() => battery.FirstCharge, i => battery.FirstCharge = i, new Point(300, 60))); + var charger2 = _diagram.Nodes.Add(new BatteryChargerNodeModel(() => battery.SecondCharge, i => battery.SecondCharge = i, new Point(300, 180))); + + _diagram.Links.Add(new LinkModel(new SinglePortAnchor(port1), new ShapeIntersectionAnchor(charger1))); + _diagram.Links.Add(new LinkModel(new SinglePortAnchor(port2), new ShapeIntersectionAnchor(charger2))); + } + } +} diff --git a/site/Site/Components/Landing/WidgetsExample.razor b/site/Site/Components/Landing/WidgetsExample.razor new file mode 100644 index 000000000..428275e23 --- /dev/null +++ b/site/Site/Components/Landing/WidgetsExample.razor @@ -0,0 +1,41 @@ +
+ +
+
+
+

+ Widgets +

+

+ Blazor Diagrams includes a couple of widgets to speed up your development. +

+
    +
  • GridWidget (dots or lines)
  • +
  • SelectionBoxWidget (shift and drag)
  • +
  • NavigatorWidget
  • +
+
+
+
+ + + + + + + + + +
+ + +
+
+
+
+
+
\ No newline at end of file diff --git a/site/Site/Components/Landing/WidgetsExample.razor.cs b/site/Site/Components/Landing/WidgetsExample.razor.cs new file mode 100644 index 000000000..212231ed7 --- /dev/null +++ b/site/Site/Components/Landing/WidgetsExample.razor.cs @@ -0,0 +1,49 @@ +using Blazor.Diagrams; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.PathGenerators; +using Site.Models.Landing; + +namespace Site.Components.Landing +{ + public partial class WidgetsExample + { + private readonly BlazorDiagram _diagram = new(); + private bool _gridPoints; + + public bool GridPoints + { + get => _gridPoints; + set + { + _gridPoints = value; + _diagram.Refresh(); + } + } + + protected override void OnInitialized() + { + _diagram.Options.Zoom.Enabled = false; + _diagram.Options.GridSize = 30; + _diagram.Options.Links.DefaultPathGenerator = new StraightPathGenerator(); + _diagram.RegisterComponent(); + + var node1 = _diagram.Nodes.Add(new ColoredNodeModel("Node 1", false, "color1", new Point(90, 60))); + var node2 = _diagram.Nodes.Add(new ColoredNodeModel("Node 2", true, "color2", new Point(450, 60))); + var node3 = _diagram.Nodes.Add(new ColoredNodeModel("Node 3", false, "color3", new Point(270, 240))); + + _diagram.Links.Add(new LinkModel(node1, node2) + { + TargetMarker = LinkMarker.Arrow + }); + _diagram.Links.Add(new LinkModel(node2, node3) + { + TargetMarker = LinkMarker.Arrow + }); + _diagram.Links.Add(new LinkModel(node3, node1) + { + TargetMarker = LinkMarker.Arrow + }); + } + } +} diff --git a/site/Site/Models/Documentation/Menu.cs b/site/Site/Models/Documentation/Menu.cs new file mode 100644 index 000000000..d1a3a39d9 --- /dev/null +++ b/site/Site/Models/Documentation/Menu.cs @@ -0,0 +1,8 @@ +namespace Site.Models.Documentation +{ + public record Menu(IEnumerable Items, IEnumerable Groups); + + public record MenuGroup(string Title, IEnumerable Children); + + public record MenuItem(string Title, string Link, string? Icon = null); +} diff --git a/site/Site/Models/Landing/AddNodeModel.cs b/site/Site/Models/Landing/AddNodeModel.cs new file mode 100644 index 000000000..6bfd2c63f --- /dev/null +++ b/site/Site/Models/Landing/AddNodeModel.cs @@ -0,0 +1,43 @@ +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Site.Models.Landing; + +public class AddNodeModel : BaseOperation +{ + public AddNodeModel(Point position) : base(position) + { + AddPort(new CalculatorPortModel(this, true)); + AddPort(new CalculatorPortModel(this, true)); + AddPort(new CalculatorPortModel(this, false)); + } + + public override void Refresh() + { + var i1 = Ports[0]; + var i2 = Ports[1]; + + if (i1.Links.Count > 0 && i2.Links.Count > 0) + { + var l1 = i1.Links[0]; + var l2 = i2.Links[0]; + Value = GetInputValue(i1, l1) + GetInputValue(i2, l2); + } + else + { + Value = 0; + } + + base.Refresh(); + } + + private static double GetInputValue(PortModel port, BaseLinkModel link) + { + var sp = (link.Source as SinglePortAnchor)!; + var tp = (link.Source as SinglePortAnchor)!; + var p = sp.Port == port ? tp : sp; + return (p.Port.Parent as BaseOperation)!.Value; + } +} \ No newline at end of file diff --git a/site/Site/Models/Landing/BaseOperation.cs b/site/Site/Models/Landing/BaseOperation.cs new file mode 100644 index 000000000..2861076ef --- /dev/null +++ b/site/Site/Models/Landing/BaseOperation.cs @@ -0,0 +1,29 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; + +namespace Site.Models.Landing; + +public abstract class BaseOperation : NodeModel +{ + private double _value; + + public event Action? ValueChanged; + + protected BaseOperation(Point position) : base(position) + { + + } + + public double Value + { + get => _value; + set + { + if (_value == value) + return; + + _value = value; + ValueChanged?.Invoke(this); + } + } +} \ No newline at end of file diff --git a/site/Site/Models/Landing/CalculatorPortModel.cs b/site/Site/Models/Landing/CalculatorPortModel.cs new file mode 100644 index 000000000..e57926220 --- /dev/null +++ b/site/Site/Models/Landing/CalculatorPortModel.cs @@ -0,0 +1,23 @@ +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Site.Models.Landing; + +public class CalculatorPortModel : PortModel +{ + public CalculatorPortModel(NodeModel parent, bool input) : base(parent, + input ? PortAlignment.Left : PortAlignment.Right) + { + Input = input; + } + + public bool Input { get; } + + public override bool CanAttachTo(ILinkable other) + { + if (other is not CalculatorPortModel port) + return false; + + return port.Input != Input; + } +} \ No newline at end of file diff --git a/site/Site/Models/Landing/ColoredNodeModel.cs b/site/Site/Models/Landing/ColoredNodeModel.cs new file mode 100644 index 000000000..3571552d2 --- /dev/null +++ b/site/Site/Models/Landing/ColoredNodeModel.cs @@ -0,0 +1,23 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; + +namespace Site.Models.Landing +{ + public class ColoredNodeModel : NodeModel + { + public ColoredNodeModel(string title, bool round, string color, Point position) : base(position) + { + Title = title; + Round = round; + Color = color; + } + + public bool Round { get; } + public string Color { get; } + + public override IShape GetShape() + { + return Round ? Shapes.Circle(this) : Shapes.Rectangle(this); + } + } +} diff --git a/site/Site/Models/Landing/Groups/ColoredGroupModel.cs b/site/Site/Models/Landing/Groups/ColoredGroupModel.cs new file mode 100644 index 000000000..eac06140a --- /dev/null +++ b/site/Site/Models/Landing/Groups/ColoredGroupModel.cs @@ -0,0 +1,14 @@ +using Blazor.Diagrams.Core.Models; + +namespace Site.Models.Landing.Groups +{ + public class ColoredGroupModel : GroupModel + { + public ColoredGroupModel(IEnumerable children, string color, byte padding = 30, bool autoSize = true) : base(children, padding, autoSize) + { + Color = color; + } + + public string Color { get; } + } +} diff --git a/site/Site/Models/Landing/NumberNodeModel.cs b/site/Site/Models/Landing/NumberNodeModel.cs new file mode 100644 index 000000000..f3b221d60 --- /dev/null +++ b/site/Site/Models/Landing/NumberNodeModel.cs @@ -0,0 +1,11 @@ +using Blazor.Diagrams.Core.Geometry; + +namespace Site.Models.Landing; + +public class NumberNodeModel : BaseOperation +{ + public NumberNodeModel(Point position) : base(position) + { + AddPort(new CalculatorPortModel(this, false)); + } +} \ No newline at end of file diff --git a/site/Site/Models/Landing/SvgAndHtml/BatteryChargerNodeModel.cs b/site/Site/Models/Landing/SvgAndHtml/BatteryChargerNodeModel.cs new file mode 100644 index 000000000..98407d4a6 --- /dev/null +++ b/site/Site/Models/Landing/SvgAndHtml/BatteryChargerNodeModel.cs @@ -0,0 +1,18 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; + +namespace Site.Models.Landing.SvgAndHtml +{ + public class BatteryChargerNodeModel : NodeModel + { + public BatteryChargerNodeModel(Func getter, Action setter, Point position) : base(position) + { + Getter = getter; + Setter = setter; + } + + public BatteryNodeModel? Battery { get; private set; } + public Func Getter { get; } + public Action Setter { get; } + } +} diff --git a/site/Site/Models/Landing/SvgAndHtml/BatteryNodeModel.cs b/site/Site/Models/Landing/SvgAndHtml/BatteryNodeModel.cs new file mode 100644 index 000000000..b1aa29bf3 --- /dev/null +++ b/site/Site/Models/Landing/SvgAndHtml/BatteryNodeModel.cs @@ -0,0 +1,37 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Models; + +namespace Site.Models.Landing.SvgAndHtml +{ + public class BatteryNodeModel : SvgNodeModel + { + private int _firstCharge = 5; + private int _secondCharge = 15; + + public BatteryNodeModel(Point position) : base(position) + { + } + + public int FirstCharge + { + get => _firstCharge; + set + { + _firstCharge = value; + Refresh(); + } + } + + public int SecondCharge + { + get => _secondCharge; + set + { + _secondCharge = value; + Refresh(); + } + } + + public int Percentage => FirstCharge + SecondCharge; + } +} diff --git a/site/Site/Pages/Documentation/Diagram/Api.razor b/site/Site/Pages/Documentation/Diagram/Api.razor new file mode 100644 index 000000000..af548a11f --- /dev/null +++ b/site/Site/Pages/Documentation/Diagram/Api.razor @@ -0,0 +1,362 @@ +@page "/documentation/diagram-api" +@layout DocumentationLayout +@inherits DocumentationPage + +@{ + RenderFragment readonlySubtitle = @Read only; +} + +Diagram API - Documentation - Blazor Diagrams + +

Diagram API

+ +

Properties

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDescription
Options @readonlySubtitleBlazorDiagramOptions-
Nodes @readonlySubtitleNodeLayer-Acts as a list for the nodes
Links @readonlySubtitleLinkLayer-Acts as a list for the links
Groups @readonlySubtitleGroupLayer-Acts as a list for the groups
Controls @readonlySubtitleControlsLayer-Only way to Get/Add/Remove controls for a model
Container @readonlySubtitleRectangle?nullBoundaries representing the canvas, retrieved with JS
Pan @readonlySubtitlePoint?Zero
Zoom @readonlySubtitleDouble1
SuspendRefreshBooleanfalseSuspends the refreshes related to the diagram (canvas)
OrderedSelectables @readonlySubtitleIReadOnlyList<SelectableModel>-Ordered list of models using the Order property
+ +

Events

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
PointerDownAction<Model?, PointerEventArgs>?
PointerMoveAction<Model?, PointerEventArgs>?
PointerUpAction<Model?, PointerEventArgs>?
PointerEnterAction<Model?, PointerEventArgs>?
PointerLeaveAction<Model?, PointerEventArgs>?
KeyDownAction<KeyboardEventArgs>?
WheelAction<WheelEventArgs>?
PointerClickAction<Model?, PointerEventArgs>?
PointerDoubleClickAction<Model?, PointerEventArgs>?
SelectionChangedAction<SelectableModel>?
PanChangedAction?
ZoomChangedAction?
ContainerChangedAction?
ChangedAction?
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameReturn TypeDescription
RegisterComponent<TModel, TComponent>(bool replace = false)voidRegister a Blazor component to render for every instance of TModel
RegisterComponent(Type modelType, Type componentType, bool replace = false)voidRegister a Blazor component to render for every instance of modelType
GetComponent(Type modelType, bool checkSubclasses = true)Type?Returns the component Type registered for the modelType
GetComponent<TModel>(bool checkSubclasses = true)Type?Returns the component Type registered for TModel
GetComponent(Model model, bool checkSubclasses = true)Type?Returns the component Type registered for typeof(Model)
Refresh()void
Batch(Action action)void
GetSelectedModels()IEnumerable<SelectableModel>
SelectModel(SelectableModel model, bool unselectOthers)void
UnselectModel(SelectableModel model)void
UnselectAll()void
RegisterBehavior(Behavior behavior)void
GetBehavior<T>() where T : BehaviorT?
UnregisterBehavior<T>() where T : Behaviorvoid
ZoomToFit(double margin void
SetPan(double x, double y)void
UpdatePan(double deltaX, double deltaY)void
SetZoom(double newZoom)void
SetContainer(Rectangle newRect)void
GetRelativeMousePoint(double clientX, double clientY)Point
GetRelativePoint(double clientX, double clientY)Point
GetScreenPoint(double clientX, double clientY)Point
SendToBack(SelectableModel model)void
SendToFront(SelectableModel model)void
GetMinOrder()int
GetMaxOrder()int
TriggerPointerDown(Model? model, PointerEventArgs e) void
TriggerPointerMove(Model? model, PointerEventArgs e) void
TriggerPointerUp(Model? model, PointerEventArgs e) void
TriggerPointerEnter(Model? model, PointerEventArgs e) void
TriggerPointerLeave(Model? model, PointerEventArgs e) void
TriggerKeyDown(KeyboardEventArgs e) void
TriggerWheel(WheelEventArgs e) void
TriggerPointerClick(Model? model, PointerEventArgs e) void
TriggerPointerDoubleClick(Model? model, PointerEventArgs e) void
+ + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Diagram/Api.razor.css b/site/Site/Pages/Documentation/Diagram/Api.razor.css new file mode 100644 index 000000000..50dbcec91 --- /dev/null +++ b/site/Site/Pages/Documentation/Diagram/Api.razor.css @@ -0,0 +1,3 @@ +tr td:first-child { + font-weight: 600; +} diff --git a/site/Site/Pages/Documentation/Diagram/Behaviors.razor b/site/Site/Pages/Documentation/Diagram/Behaviors.razor new file mode 100644 index 000000000..7a0c20e93 --- /dev/null +++ b/site/Site/Pages/Documentation/Diagram/Behaviors.razor @@ -0,0 +1,120 @@ +@page "/documentation/diagram-behaviors" +@layout DocumentationLayout +@inherits DocumentationPage + +Diagram Behaviors - Documentation - Blazor Diagrams + +

Diagram Behaviors

+ +

+ Behaviors are a way to encapsulate a functionality. + They are separated into classes mainly for readability, separation of concerns, and for the library users to be able to remove/replace them. + The behaviors inherit from the base class Behavior and use the available events to do what they need. +

+ +

Example

+ +

+using Blazor.Diagrams.Core.Models.Base;
+using Blazor.Diagrams.Core.Events;
+
+namespace Blazor.Diagrams.Core.Behaviors
+{
+    public class SelectionBehavior : Behavior
+    {
+        public SelectionBehavior(Diagram diagram) : base(diagram)
+        {
+            Diagram.PointerDown += OnPointerDown;
+        }
+
+        private void OnPointerDown(Model? model, PointerEventArgs e)
+        {
+            var ctrlKey = e.CtrlKey;
+
+            switch (model)
+            {
+                case null:
+                    Diagram.UnselectAll();
+                    break;
+                case SelectableModel sm when ctrlKey && sm.Selected:
+                    Diagram.UnselectModel(sm);
+                    break;
+                case SelectableModel sm:
+                {
+                    if (!sm.Selected)
+                    {
+                        Diagram.SelectModel(sm, !ctrlKey || !Diagram.Options.AllowMultiSelection);
+                    }
+
+                    break;
+                }
+            }
+        }
+
+        public override void Dispose()
+        {
+            Diagram.PointerDown -= OnPointerDown;
+        }
+    }
+}
+
+ +

+ As you can see, the behavior simply uses the PointerDown event in order to (un)select stuff. +

+ +

Replacing a behavior

+ +Let's say you didn't like how the SelectionBehavior works, let's replace it by a simpler one.
+We just want to keep selecting models without CTRL and only unselect everything once the canvas is clicked. + +

+using Blazor.Diagrams.Core.Models.Base;
+using Blazor.Diagrams.Core.Events;
+
+namespace Blazor.Diagrams.Core.Behaviors
+{
+    public class MySelectionBehavior : Behavior
+    {
+        public SelectionBehavior(Diagram diagram) : base(diagram)
+        {
+            Diagram.PointerDown += OnPointerDown;
+        }
+
+        private void OnPointerDown(Model? model, PointerEventArgs e)
+        {
+            if (model == null) // Canvas
+            {
+                Diagram.UnselectAll();
+            }
+            else if (model is SelectableModel sm)
+            {
+                Diagram.SelectModel(sm);
+            }
+        }
+
+        public override void Dispose()
+        {
+            Diagram.PointerDown -= OnPointerDown;
+        }
+    }
+}
+
+ +

+ A very simple class indeed. Inherting from Behavior automatically forces you to have the constructor, in order to have the Diagram instance, + but also the Dispose method in order to remove any event handlers in case the behavior is unregistered.

+ Let's now replace the old behavior with ours: +

+ +

+// In case you want to keep the old behavior intance
+var oldSelectionBehavior = Diagram.GetBehavior<SelectionBehavior>()!;
+Diagram.UnregisterBehavior<SelectionBehavior>();
+Diagram.RegisterBehavior(new MySelectionBehavior(Diagram));
+
+ + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Diagram/KeyboardShortcuts.razor b/site/Site/Pages/Documentation/Diagram/KeyboardShortcuts.razor new file mode 100644 index 000000000..89d1b39f2 --- /dev/null +++ b/site/Site/Pages/Documentation/Diagram/KeyboardShortcuts.razor @@ -0,0 +1,59 @@ +@page "/documentation/keyboard-shortcuts" +@layout DocumentationLayout +@inherits DocumentationPage + +Keyboard Shortcuts - Documentation - Blazor Diagrams + +

Keyboard Shortcuts

+ +

+ Blazor Diagrams provides a utility behavior to handle keyboard shortcuts: KeyboardShortcutsBehavior. +

+ +

Default shortcuts

+ + + + + + + + + + + + + + + + + + + + + +
KeysImplementationDescription
DeleteKeyboardShortcutsDefaults.DeleteSelectionDeletes the selected models (nodes, groups and links)
Ctrl+Alt+gKeyboardShortcutsDefaults.GroupingGroups the selected models
+ +

Adding a shortcut

+ +

+var ksb = _blazorDiagram.GetBehavior<KeyboardShortcutsBehavior>();
+ksb.SetShortcut("s", ctrl: true, shift: true, alt: true, SaveToMyServer);
+
+private async ValueTask SaveToMyServer()
+{
+    await FakeHttpCall();
+}
+
+ +

Removing a shortcut

+ +

+var ksb = _blazorDiagram.GetBehavior<KeyboardShortcutsBehavior>();
+ksb.RemoveShortcut("s", ctrl: true, shift: true, alt: true);
+
+ + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Diagram/Options.razor b/site/Site/Pages/Documentation/Diagram/Options.razor new file mode 100644 index 000000000..f74f01c67 --- /dev/null +++ b/site/Site/Pages/Documentation/Diagram/Options.razor @@ -0,0 +1,284 @@ +@page "/documentation/diagram-options" +@layout DocumentationLayout +@inherits DocumentationPage + +Diagram Options - Documentation - Blazor Diagrams + +

Diagram Options

+ +

+ Here are all the available options in BlazorDiagramOptions: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDescription
LinksLayerOrderInteger0The order (z-index) of the SVG layer
NodesLayerOrderInteger0The order (z-index) of the HTML layer
ZoomBlazorDiagramZoomOptions
LinksBlazorDiagramLinkOptions
GroupsBlazorDiagramGroupOptions
ConstraintsBlazorDiagramConstraintsOptions
VirtualizationBlazorDiagramVirtualizationOptions
+ +

Zoom

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDescription
EnabledBooleantrue
InverseBooleanfalse
MinimumDouble0.1
MaximumDouble2
ScaleFactorDouble1.05
+ +

Links

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDescription
DefaultColorStringblack
DefaultSelectedColorStringrgb(110, 159, 212)
DefaultRouterRouterNormalRouter
DefaultPathGeneratorPathGeneratorSmoothPathGenerator
EnableSnappingBooleanfalseIf true, dragging new links will snap them to the closest ports
SnappingRadiusDouble50Used to calculate the distance and decide whether to snap the link
RequireTargetBooleantrueIf false, links can be free (without target)
FactoryLinkFactory-Delegate to control how a link is instanciated
TargetAnchorFactoryAnchorFactory-Delegate to control how the target anchor is instanciated
+ +

Groups

+ + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDescription
EnabledBooleanfalse
FactoryGroupFactory-Delegate to control how a group is instanciated
+ +

Constraints

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDescription
ShouldDeleteNodeFunc<NodeModel, ValueTask<bool>>true
ShouldDeleteLinkFunc<BaseLinkModel, ValueTask<bool>>true
ShouldDeleteGroupFunc<GroupModel, ValueTask<bool>>true
+ +

Virtualization

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDescription
EnabledBooleanfalse
OnNodesBooleantrue
OnGroupsBooleanfalse
OnLinksBooleanfalse
+ + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Diagram/Overview.razor b/site/Site/Pages/Documentation/Diagram/Overview.razor new file mode 100644 index 000000000..0ce5b26f1 --- /dev/null +++ b/site/Site/Pages/Documentation/Diagram/Overview.razor @@ -0,0 +1,66 @@ +@page "/documentation/diagram" +@layout DocumentationLayout +@inherits DocumentationPage + +Diagram - Documentation - Blazor Diagrams + +

Diagram

+ +

Structure

+ +The component DiagramCanvas generates the follolwing structure: + +

+<div class="diagram-canvas" tabindex="-1">
+    <!-- LAYERS -->
+</div>
+
+ +You can add your own CSS classes using the Class parameter. + +

Layers

+ +

+ It is very important to know that in Blazor Diagrams, everything is rendered in 2 layers: +

+ +

SVG

+ +

+<svg class="diagram-svg-layer" style="transform: translate(0px, 0px) scale(1); z-index: 0;">
+
+ +

+ This layer is basically a svg. It is mainly used to render links, since they require the <path> element.
+ However, it is also possible to render nodes in it by inherting from SvgNodeModel or SvgGroupModel. + This lets you use any svg elements and features with ease. +

+ +

HTML

+ +

+<div class="diagram-html-layer" style="transform: translate(0px, 0px) scale(1); z-index: 0;">
+
+ +

+ This layer is a simple html div. It is mainly used to render nodes. + It is very useful since you can re-use any html you might already have in your nodes, as well as include interactivity through inputs for example. +

+ +

Additional Content

+ +You can add additional content to each layer using the available render fragments: + +

+<DiagramCanvas>
+    <AdditionalHtml>
+        <!-- EXTRA HTML -->
+    </AdditionalHtml>
+    <AdditionalSvg>
+        <!-- EXTRA SVG -->
+    </AdditionalSvg>
+</DiagramCanvas>
+
+ + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/DocumentationPage.cs b/site/Site/Pages/Documentation/DocumentationPage.cs new file mode 100644 index 000000000..881f60445 --- /dev/null +++ b/site/Site/Pages/Documentation/DocumentationPage.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; + +namespace Site.Pages.Documentation +{ + public class DocumentationPage : ComponentBase + { + [Inject] protected IJSRuntime JSRuntime { get; set; } = null!; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await JSRuntime.InvokeVoidAsync("Prism.highlightAll"); + } + } +} diff --git a/site/Site/Pages/Documentation/GettingStarted/DiagramCreation.razor b/site/Site/Pages/Documentation/GettingStarted/DiagramCreation.razor new file mode 100644 index 000000000..f2069f5f2 --- /dev/null +++ b/site/Site/Pages/Documentation/GettingStarted/DiagramCreation.razor @@ -0,0 +1,126 @@ +@page "/documentation/diagram-creation" +@layout DocumentationLayout +@inherits DocumentationPage + +Diagram Creation - Documentation - Blazor Diagrams + +

Diagram Creation

+ +

+ You've installed the library, now let's create a diagram! + Blazor.Diagrams is very code/oop oriented at the moment, which means all the diagram information needs to be given to it using code. +

+ +

Creating a Diagram

+ +

+ First, we'll start by creating a BlazorDiagram, which holds all the data of a diagram, as well as its behaviors.
+ Create a new component and initialize a new diagram: +

+ +
MyDiagram.razor.cs
+

+using Blazor.Diagrams;
+
+namespace MyNamespace;
+
+public partial class MyDiagram
+{
+    private BlazorDiagram Diagram { get; set; } = null!;
+
+    protected override void OnInitialized()
+    {
+        Diagram = new BlazorDiagram();
+    }
+}
+
+ +

Changing Options

+ +
MyDiagram.razor.cs
+

+using Blazor.Diagrams;
+using Blazor.Diagrams.Core.PathGenerators;
+using Blazor.Diagrams.Core.Routers;
+using Blazor.Diagrams.Options;
+
+namespace Site.Components.Documentation;
+
+public partial class MyDiagram
+{
+    private BlazorDiagram Diagram { get; set; } = null!;
+
+    protected override void OnInitialized()
+    {
+        var options = new BlazorDiagramOptions
+        {
+            AllowMultiSelection = true,
+            Zoom =
+            {
+                Enabled = false,
+            },
+            Links =
+            {
+                DefaultRouter = new NormalRouter(),
+                DefaultPathGenerator = new SmoothPathGenerator()
+            },
+        };
+
+        Diagram = new BlazorDiagram(options);
+    }
+}
+
+ +

+ You can check out all the available options here. +

+ +

Adding Nodes

+ +

+ Continuing on our initialization code before, let's create two nodes: +

+ +
MyDiagram.razor.cs
+

+var firstNode = Diagram.Nodes.Add(new NodeModel(position: new Point(50, 50))
+{
+    Title = "Node 1"
+});
+var secondNode = Diagram.Nodes.Add(new NodeModel(position: new Point(200, 100))
+{
+    Title = "Node 2"
+});
+var leftPort = secondNode.AddPort(PortAlignment.Left);
+var rightPort = secondNode.AddPort(PortAlignment.Right);
+
+ +

+ As you can see, we give the nodes a position and a title, as well as 2 ports for the second node. +

+ +

Adding a Link

+ +

+ In Blazor.Diagrams, links are created between 2 anchors (input and output). + The library comes with a few out of the box anchors, we'll be using two of them to show how a link can be created from a node to a port. +

+ +
MyDiagram.razor.cs
+

+// The connection point will be the intersection of
+// a line going from the target to the center of the source
+var sourceAnchor = new ShapeIntersectionAnchor(firstNode);
+// The connection point will be the port's position
+var targetAnchor = new SinglePortAnchor(leftPort);
+var link = Diagram.Links.Add(new LinkModel(sourceAnchor, targetAnchor));
+
+ +

+ You now have a diagram containing 2 nodes, 2 ports and one link. +

+ + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/GettingStarted/Display.razor b/site/Site/Pages/Documentation/GettingStarted/Display.razor new file mode 100644 index 000000000..b249297f7 --- /dev/null +++ b/site/Site/Pages/Documentation/GettingStarted/Display.razor @@ -0,0 +1,57 @@ +@page "/documentation/display" +@layout DocumentationLayout +@inherits DocumentationPage + +Display - Documentation - Blazor Diagrams + +

Display

+ +

+ You've created a diagram and populated it, let's display it!
+ First, let's setup our component so that it has a specific width and height. + In order for us to display the diagram, a visible parent is mandatory, since the canvas uses 100% width/height. +

+ +
MyDiagram.razor
+

+<div class="diagram-container">
+
+</div>
+
+ +Now create a scoped CSS file with the following content: +
MyDiagram.razor.css
+

+.diagram-container {
+	width: 100%;
+    height: 400px;
+    border: 1px solid black; /* Just visual */
+}
+
+ +Now that we have a visible container, let's display the diagram: + +
MyDiagram.razor
+

+@@using Blazor.Diagrams.Components
+
+<div class="diagram-container">
+    <CascadingValue Value="Diagram" IsFixed="true">
+        <DiagramCanvas></DiagramCanvas>
+    </CascadingValue>
+</div>
+
+ +

+ If you used the default styles and did everything correctly, here's how the diagram would look:
+ It's interactive, play with the nodes! +

+ + + +

+ Congratulations! You're now ready to move to the interesting stuff. +

+ + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/GettingStarted/Installation.razor b/site/Site/Pages/Documentation/GettingStarted/Installation.razor new file mode 100644 index 000000000..d4993daf1 --- /dev/null +++ b/site/Site/Pages/Documentation/GettingStarted/Installation.razor @@ -0,0 +1,55 @@ +@page "/documentation/installation" +@layout DocumentationLayout +@inherits DocumentationPage + +Installation - Documentation - Blazor Diagrams + +

Installation

+ +

+ Let's begin by installing the NuGet package.
+ You can use Visual Studio's Nuget Package Manager, or one of the following commands: +

+ +.NET CLI + +

+dotnet add package Z.Blazor.Diagrams
+
+ +Package Manager + +

+Install-Package Z.Blazor.Diagrams
+
+ +

Project Setup

+

+ Next, we'll setup the project and add the static resources for the library to work. +

+ +
wwwroot/index.html or Pages/_Host.cshtml
+

+<!DOCTYPE html>
+<html>
+
+<head>
+    <!-- ... -->
+    <link href="_content/Z.Blazor.Diagrams/style.min.css" rel="stylesheet" />
+    <!-- If you want the default style -->
+    <link href="_content/Z.Blazor.Diagrams/default.styles.min.css" rel="stylesheet" />
+</head>
+
+<body>
+    <!-- ... -->
+    <script src="_content/Z.Blazor.Diagrams/script.min.js"></script>
+</body>
+
+</html>
+
+ +

+ You're now ready to create your first diagram! +

+ + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Groups/Overview.razor b/site/Site/Pages/Documentation/Groups/Overview.razor new file mode 100644 index 000000000..4e7b56ada --- /dev/null +++ b/site/Site/Pages/Documentation/Groups/Overview.razor @@ -0,0 +1,68 @@ +@page "/documentation/groups-overview" +@layout DocumentationLayout +@inherits DocumentationPage + +Groups - Documentation - Blazor Diagrams + +

Groups

+ +

+ In Blazor Diagrams, Groups are a way to group nodes together.
+ Groups can also contain other groups, so you can create hierarchies. +

+ +

Structure

+ +

+ The component GroupRenderer generates the follolwing structure: +

+ +

+<div class="diagram-group ..."
+     data-group-id="28e9606d-08dd-47d5-a4c7-b95e541bcf1e"
+     style="top: 10px; left: 10px; width: 100px; height: 100px">
+    <!-- YOUR CONTENT WILL BE HERE -->
+</div>
+
+ +

+ The classes that the div can have (beside diagram-group) are: locked, selected and default. +

+ +

Creating a group

+ +

Interactively

+ +

+ By default, users can group selected nodes/groups using the keys Ctrl+g (if Options.Groups.Enabled is set to true). + Check out Keyboard Shortcuts in order to change the required keys. +

+ +

Programmatically

+ +Assuming we have two nodes, we can simply: + +

+var node1 = new NodeModel(new Point(10, 10));
+var node2 = new NodeModel(new Point(50, 50));
+var group = BlazorDiagram.Groups.Group(node1, node2);
+
+ +

! Important Note

+ +Most of the time, groups will have a background of some sort (e.g. a color), which will hide all the links beneath it since +if you remember, the nodes layer is above the links layer. There are 2 solutions for this: + +

SVG Group

+ +If you use svg nodes, then make sure you add your groups first, then your links. +
+If you're grouping nodes dynamically, make sure to bring links associated to those groups and their nodes to the front (or the created groups to the back) using the ordering methods available. + +

Layers order

+ +The BlazorDiagramOptions class contains two properties in order to control the order (z-index) of the layers: LinksLayerOrder and NodesLayerOrder. +All you have to do for groups to work properly is to set LinksLayerOrder to a bigger value than the other. + + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Groups/SVG.razor b/site/Site/Pages/Documentation/Groups/SVG.razor new file mode 100644 index 000000000..449bac86d --- /dev/null +++ b/site/Site/Pages/Documentation/Groups/SVG.razor @@ -0,0 +1,60 @@ +@page "/documentation/groups-svg" +@layout DocumentationLayout +@inherits DocumentationPage + +SVG Groups - Documentation - Blazor Diagrams + +

SVG Groups

+ +

+ SVG groups are groups that will be rendered in the SVG layer.
+ In this page, we will talk about the differences between them and normal groups. +

+ +

Structure

+ +

+ The component GroupRenderer generates the follolwing structure: +

+ +

+<g class="diagram-group ..."
+   data-group-id="28e9606d-08dd-47d5-a4c7-b95e541bcf1e"
+   transform="translate(10 10)">
+
+   <rect width="100" height="100" fill="none"></rect>
+
+    <!-- YOUR CONTENT WILL BE HERE -->
+
+</g>
+
+ +

The rect is there for you to be able to style the background of the group, since we can't style g elements.

+ +

Creating a SVG group

+ +

Interactively

+ +First, we will need how groups are created: + +

+yourDiagram.Options.Groups.Factory = (diagram, children) => new SvgGroupModel(children);
+
+ +

+ By default, users can group selected nodes/groups using the keys Ctrl+g (if Options.Groups.Enabled is set to true). + Check out Keyboard Shortcuts in order to change the required keys. +

+ +

Programmatically

+ +Assuming we have two nodes, we can simply: + +

+var node1 = new SvgNodeModel(new Point(10, 10));
+var node2 = new SvgNodeModel(new Point(50, 50));
+var group = new SvgGroupModel(new[] { node1, node2 });
+
+ + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Index.razor b/site/Site/Pages/Documentation/Index.razor new file mode 100644 index 000000000..a04f46c7a --- /dev/null +++ b/site/Site/Pages/Documentation/Index.razor @@ -0,0 +1,6 @@ +@page "/documentation" +@layout DocumentationLayout + +Documentation - Blazor Diagrams + +Overview \ No newline at end of file diff --git a/site/Site/Pages/Landing/Index.razor b/site/Site/Pages/Landing/Index.razor new file mode 100644 index 000000000..5e3a2b116 --- /dev/null +++ b/site/Site/Pages/Landing/Index.razor @@ -0,0 +1,70 @@ +@page "/" + +Blazor Diagrams + + + + + + + + + @* configurable *@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ Get in touch +

+
+
+
+

+ Don't hesitate to ask about anything either on GitHub or even by email! +

+
\ No newline at end of file diff --git a/site/Site/Program.cs b/site/Site/Program.cs new file mode 100644 index 000000000..41906bb16 --- /dev/null +++ b/site/Site/Program.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Site; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); +builder.RootComponents.Add("#app"); +builder.RootComponents.Add("head::after"); + +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + +await builder.Build().RunAsync(); \ No newline at end of file diff --git a/site/Site/Properties/launchSettings.json b/site/Site/Properties/launchSettings.json new file mode 100644 index 000000000..4219fb5f4 --- /dev/null +++ b/site/Site/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:40851", + "sslPort": 44376 + } + }, + "profiles": { + "Site": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:8032;http://localhost:8031", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/site/Site/Shared/DocumentationLayout.razor b/site/Site/Shared/DocumentationLayout.razor new file mode 100644 index 000000000..106e8b8dc --- /dev/null +++ b/site/Site/Shared/DocumentationLayout.razor @@ -0,0 +1,116 @@ +@inherits LayoutComponentBase + +@* Header *@ + + +
+ + @* Sidebar Toggle *@ +
+
+ +
+
+ + @* Sidebar *@ + + + @* Content *@ +
+
+ @Body + +
+
+

Copyright © 2022

+
+
+
+
+
\ No newline at end of file diff --git a/site/Site/Shared/DocumentationLayout.razor.cs b/site/Site/Shared/DocumentationLayout.razor.cs new file mode 100644 index 000000000..0f0984fb2 --- /dev/null +++ b/site/Site/Shared/DocumentationLayout.razor.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Components; + +namespace Site.Shared +{ + public partial class DocumentationLayout + { + [Inject] private NavigationManager NavigationManager { get; set; } = null!; + + private (string, string) GetMenuItemExtraClasses(string link) + { + if (IsActive(link, false)) + return ("font-semibold text-main", "bg-main text-white"); + + return ("font-medium hover:text-slate-900", "bg-gray-100 text-black"); + } + + private string GetGroupMenuItemExtraClasses(string link) + { + if (IsActive(link, true)) + return "text-palette-main border-palette-main font-semibold"; + + return "text-slate-700 hover:border-slate-400 hover:text-slate-900"; + } + + private bool IsActive(string link, bool fullMatch) + { + var relativePath = "/" + NavigationManager.ToBaseRelativePath(NavigationManager.Uri).ToLower(); + return (fullMatch && relativePath == link) || (!fullMatch && relativePath.StartsWith(link)); + } + } +} diff --git a/site/Site/Shared/DocumentationLayout.razor.css b/site/Site/Shared/DocumentationLayout.razor.css new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/site/Site/Shared/DocumentationLayout.razor.css @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/site/Site/Shared/LandingLayout.razor b/site/Site/Shared/LandingLayout.razor new file mode 100644 index 000000000..0eb50b998 --- /dev/null +++ b/site/Site/Shared/LandingLayout.razor @@ -0,0 +1,53 @@ +@inherits LayoutComponentBase + + + + + + +@Body \ No newline at end of file diff --git a/site/Site/Shared/LandingLayout.razor.css b/site/Site/Shared/LandingLayout.razor.css new file mode 100644 index 000000000..e69de29bb diff --git a/site/Site/Site.csproj b/site/Site/Site.csproj new file mode 100644 index 000000000..a85adb61e --- /dev/null +++ b/site/Site/Site.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + enable + enable + + + + + + + + + <_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css" /> + <_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css.map" /> + <_ContentIncludedByDefault Remove="wwwroot\sample-data\weather.json" /> + + + + + + + + + diff --git a/site/Site/Static/Documentation.cs b/site/Site/Static/Documentation.cs new file mode 100644 index 000000000..4ebb32f1f --- /dev/null +++ b/site/Site/Static/Documentation.cs @@ -0,0 +1,34 @@ +using Site.Models.Documentation; + +namespace Site.Static +{ + public static class Documentation + { + public static readonly Menu Menu = new(new List + { + new MenuItem("Documentation", "/documentation", Icons.BookOpen), + new MenuItem("Examples", "/examples", Icons.FolderOpen), + }, new List + { + new MenuGroup("Getting Started", new List + { + new MenuItem("Installation", "/documentation/installation"), + new MenuItem("Diagram Creation", "/documentation/diagram-creation"), + new MenuItem("Display", "/documentation/display"), + }), + new MenuGroup("Diagram", new List + { + new MenuItem("Overview", "/documentation/diagram"), + new MenuItem("Behaviors", "/documentation/diagram-behaviors"), + new MenuItem("Options", "/documentation/diagram-options"), + new MenuItem("Keyboard Shortcuts", "/documentation/keyboard-shortcuts"), + new MenuItem("API", "/documentation/diagram-api"), + }), + new MenuGroup("Groups", new List + { + new MenuItem("Overview", "/documentation/groups-overview"), + new MenuItem("SVG", "/documentation/groups-svg"), + }) + }); + } +} diff --git a/site/Site/Static/Icons.cs b/site/Site/Static/Icons.cs new file mode 100644 index 000000000..078529be9 --- /dev/null +++ b/site/Site/Static/Icons.cs @@ -0,0 +1,9 @@ +namespace Site.Static +{ + public static class Icons + { + public static readonly string Github = "M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z"; + public static readonly string BookOpen = "M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25"; + public static readonly string FolderOpen = "M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776"; + } +} diff --git a/site/Site/_Imports.razor b/site/Site/_Imports.razor new file mode 100644 index 000000000..b1a980cc7 --- /dev/null +++ b/site/Site/_Imports.razor @@ -0,0 +1,30 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.AspNetCore.Components.WebAssembly.Http +@using Microsoft.JSInterop +@using Site +@using Site.Shared +@using Site.Components +@using Site.Components.Landing +@using Site.Components.Landing.SvgAndHtml +@using Site.Components.Landing.Groups +@using Site.Components.Landing.Features +@using Site.Models +@using Site.Models.Landing +@using Site.Models.Landing.Groups +@using Site.Models.Landing.SvgAndHtml +@using Site.Models.Documentation +@using Blazor.Diagrams.Core +@using Blazor.Diagrams.Core.Models +@using Blazor.Diagrams.Components +@using Blazor.Diagrams.Core.Extensions +@using Blazor.Diagrams.Core.Geometry +@using Blazor.Diagrams.Components.Renderers +@using Blazor.Diagrams.Components.Widgets +@using Site.Static +@using Site.Components.Documentation +@using Blazor.Diagrams \ No newline at end of file diff --git a/site/Site/tailwind.config.js b/site/Site/tailwind.config.js new file mode 100644 index 000000000..9dc2ac896 --- /dev/null +++ b/site/Site/tailwind.config.js @@ -0,0 +1,13 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + darkMode: 'class', + content: ["./**/*.{razor,html,cshtml,cs}"], + theme: { + extend: { + colors: { + 'palette': { main: "#40BABD" }, + } + } + }, + plugins: [], +} \ No newline at end of file diff --git a/site/Site/wwwroot/css/app.css b/site/Site/wwwroot/css/app.css new file mode 100644 index 000000000..804be6c5f --- /dev/null +++ b/site/Site/wwwroot/css/app.css @@ -0,0 +1,1730 @@ +/* +! tailwindcss v3.1.8 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +*/ + +html { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font family by default. +2. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +::-webkit-backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +.container { + width: 100%; +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 1536px) { + .container { + max-width: 1536px; + } +} + +h1 { + margin-top: 1rem; + margin-bottom: 1rem; + font-size: 1.875rem; + line-height: 2.25rem; + font-weight: 800; +} + +h2 { + margin-top: 1rem; + margin-bottom: 1rem; + font-size: 1.5rem; + line-height: 2rem; + font-weight: 700; +} + +h3 { + margin-top: 1rem; + margin-bottom: 1rem; + font-size: 1.25rem; + line-height: 1.75rem; + font-weight: 700; +} + +table { + margin-top: 1rem; + margin-bottom: 1rem; + width: 100%; + table-layout: auto; + border-collapse: collapse; +} + +tr { + border-bottom-width: 1px; +} + +td, th { + padding-left: 1.5rem; + padding-right: 1.5rem; + padding-top: 1rem; + padding-bottom: 1rem; + text-align: left; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +.visible { + visibility: visible; +} + +.static { + position: static; +} + +.fixed { + position: fixed; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.sticky { + position: -webkit-sticky; + position: sticky; +} + +.inset-x-0 { + left: 0px; + right: 0px; +} + +.top-0 { + top: 0px; +} + +.left-0 { + left: 0px; +} + +.bottom-0 { + bottom: 0px; +} + +.top-1\/2 { + top: 50%; +} + +.-right-3 { + right: -0.75rem; +} + +.-left-3 { + left: -0.75rem; +} + +.z-30 { + z-index: 30; +} + +.z-20 { + z-index: 20; +} + +.z-\[60\] { + z-index: 60; +} + +.m-8 { + margin: 2rem; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.my-4 { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.my-6 { + margin-top: 1.5rem; + margin-bottom: 1.5rem; +} + +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.my-0 { + margin-top: 0px; + margin-bottom: 0px; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + +.mt-2 { + margin-top: 0.5rem; +} + +.mr-2 { + margin-right: 0.5rem; +} + +.mt-0 { + margin-top: 0px; +} + +.mr-3 { + margin-right: 0.75rem; +} + +.ml-auto { + margin-left: auto; +} + +.mb-3 { + margin-bottom: 0.75rem; +} + +.ml-0\.5 { + margin-left: 0.125rem; +} + +.ml-0 { + margin-left: 0px; +} + +.-ml-px { + margin-left: -1px; +} + +.mt-4 { + margin-top: 1rem; +} + +.mt-10 { + margin-top: 2.5rem; +} + +.ml-2 { + margin-left: 0.5rem; +} + +.mb-8 { + margin-bottom: 2rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.mb-12 { + margin-bottom: 3rem; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.block { + display: block; +} + +.inline-block { + display: inline-block; +} + +.inline { + display: inline; +} + +.flex { + display: flex; +} + +.table { + display: table; +} + +.grid { + display: grid; +} + +.hidden { + display: none; +} + +.h-8 { + height: 2rem; +} + +.h-6 { + height: 1.5rem; +} + +.h-5 { + height: 1.25rem; +} + +.h-4 { + height: 1rem; +} + +.h-1 { + height: 0.25rem; +} + +.h-full { + height: 100%; +} + +.w-full { + width: 100%; +} + +.w-6 { + width: 1.5rem; +} + +.w-8 { + width: 2rem; +} + +.w-5 { + width: 1.25rem; +} + +.w-80 { + width: 20rem; +} + +.w-4 { + width: 1rem; +} + +.w-1\/6 { + width: 16.666667%; +} + +.max-w-\[90rem\] { + max-width: 90rem; +} + +.max-w-3xl { + max-width: 48rem; +} + +.max-w-5xl { + max-width: 64rem; +} + +.flex-1 { + flex: 1 1 0%; +} + +.flex-grow { + flex-grow: 1; +} + +.-translate-x-full { + --tw-translate-x: -100%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.-translate-y-1\/2 { + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.transform { + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.list-inside { + list-style-position: inside; +} + +.list-disc { + list-style-type: disc; +} + +.grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.flex-row { + flex-direction: row; +} + +.flex-col { + flex-direction: column; +} + +.flex-wrap { + flex-wrap: wrap; +} + +.items-start { + align-items: flex-start; +} + +.items-center { + align-items: center; +} + +.justify-end { + justify-content: flex-end; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.gap-4 { + gap: 1rem; +} + +.gap-x-2 { + -moz-column-gap: 0.5rem; + column-gap: 0.5rem; +} + +.space-y-8 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(2rem * var(--tw-space-y-reverse)); +} + +.space-y-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); +} + +.space-y-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} + +.overflow-y-auto { + overflow-y: auto; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.rounded-full { + border-radius: 9999px; +} + +.rounded { + border-radius: 0.25rem; +} + +.rounded-l-md { + border-top-left-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; +} + +.rounded-r-md { + border-top-right-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; +} + +.rounded-t { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.border { + border-width: 1px; +} + +.border-y { + border-top-width: 1px; + border-bottom-width: 1px; +} + +.border-l-2 { + border-left-width: 2px; +} + +.border-t { + border-top-width: 1px; +} + +.border-r { + border-right-width: 1px; +} + +.border-l { + border-left-width: 1px; +} + +.border-b { + border-bottom-width: 1px; +} + +.border-slate-100 { + --tw-border-opacity: 1; + border-color: rgb(241 245 249 / var(--tw-border-opacity)); +} + +.border-transparent { + border-color: transparent; +} + +.border-slate-200 { + --tw-border-opacity: 1; + border-color: rgb(226 232 240 / var(--tw-border-opacity)); +} + +.border-palette-main { + --tw-border-opacity: 1; + border-color: rgb(64 186 189 / var(--tw-border-opacity)); +} + +.border-gray-100 { + --tw-border-opacity: 1; + border-color: rgb(243 244 246 / var(--tw-border-opacity)); +} + +.border-black { + --tw-border-opacity: 1; + border-color: rgb(0 0 0 / var(--tw-border-opacity)); +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.bg-gray-100 { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); +} + +.bg-gray-700 { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); +} + +.fill-current { + fill: currentColor; +} + +.p-1 { + padding: 0.25rem; +} + +.p-4 { + padding: 1rem; +} + +.p-2 { + padding: 0.5rem; +} + +.p-6 { + padding: 1.5rem; +} + +.px-8 { + padding-left: 2rem; + padding-right: 2rem; +} + +.py-6 { + padding-top: 1.5rem; + padding-bottom: 1.5rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.py-10 { + padding-top: 2.5rem; + padding-bottom: 2.5rem; +} + +.py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.py-8 { + padding-top: 2rem; + padding-bottom: 2rem; +} + +.py-0 { + padding-top: 0px; + padding-bottom: 0px; +} + +.pl-4 { + padding-left: 1rem; +} + +.pr-4 { + padding-right: 1rem; +} + +.pt-12 { + padding-top: 3rem; +} + +.pt-24 { + padding-top: 6rem; +} + +.pt-6 { + padding-top: 1.5rem; +} + +.pb-10 { + padding-bottom: 2.5rem; +} + +.pr-6 { + padding-right: 1.5rem; +} + +.text-center { + text-align: center; +} + +.align-middle { + vertical-align: middle; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.text-5xl { + font-size: 3rem; + line-height: 1; +} + +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} + +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.font-bold { + font-weight: 700; +} + +.font-semibold { + font-weight: 600; +} + +.font-medium { + font-weight: 500; +} + +.font-normal { + font-weight: 400; +} + +.uppercase { + text-transform: uppercase; +} + +.leading-normal { + line-height: 1.5; +} + +.leading-tight { + line-height: 1.25; +} + +.leading-none { + line-height: 1; +} + +.tracking-normal { + letter-spacing: 0em; +} + +.text-black { + --tw-text-opacity: 1; + color: rgb(0 0 0 / var(--tw-text-opacity)); +} + +.text-pink-600 { + --tw-text-opacity: 1; + color: rgb(219 39 119 / var(--tw-text-opacity)); +} + +.text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); +} + +.text-gray-800 { + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity)); +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.text-pink-800 { + --tw-text-opacity: 1; + color: rgb(157 23 77 / var(--tw-text-opacity)); +} + +.text-slate-700 { + --tw-text-opacity: 1; + color: rgb(51 65 85 / var(--tw-text-opacity)); +} + +.text-slate-900 { + --tw-text-opacity: 1; + color: rgb(15 23 42 / var(--tw-text-opacity)); +} + +.text-slate-500 { + --tw-text-opacity: 1; + color: rgb(100 116 139 / var(--tw-text-opacity)); +} + +.text-palette-main { + --tw-text-opacity: 1; + color: rgb(64 186 189 / var(--tw-text-opacity)); +} + +.text-gray-600 { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity)); +} + +.underline { + -webkit-text-decoration-line: underline; + text-decoration-line: underline; +} + +.no-underline { + -webkit-text-decoration-line: none; + text-decoration-line: none; +} + +.opacity-75 { + opacity: 0.75; +} + +.opacity-25 { + opacity: 0.25; +} + +.shadow { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-lg { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.drop-shadow-lg { + --tw-drop-shadow: drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.transition { + transition-property: color, background-color, border-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-text-decoration-color, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-text-decoration-color, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-all { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.duration-300 { + transition-duration: 300ms; +} + +.ease-in-out { + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + +#blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; +} + +#header { + box-shadow: 0 1px 2px 0 #eee; +} + +#docs-sidebar { + box-shadow: 1px 0 2px 0 #eee; +} + +#header img { + margin-bottom: 6px; +} + +.bg-main { + background-color: #40BABD; +} + +.text-main { + color: #40BABD; +} + +.bg-color1 { + background-color: #A0B15B; +} + +.bg-color2 { + background-color: #DC9A7A; +} + +.bg-color3 { + background-color: #9EA5E3; +} + +.outline-color1 { + box-shadow: 0px 0px 0px 2px #A0B15B inset; +} + +.outline-color2 { + box-shadow: 0px 0px 0px 2px #DC9A7A inset; +} + +.outline-color3 { + box-shadow: 0px 0px 0px 2px #9EA5E3 inset; +} + +.outline-color1-darker { + box-shadow: 0px 0px 0px 2px #515a2b inset; +} + +.outline-color2-darker { + box-shadow: 0px 0px 0px 2px #874423 inset; +} + +.outline-color3-darker { + box-shadow: 0px 0px 0px 2px #2b3595 inset; +} + +.diagram-link-label > div { + display: inline-block; + color: #000000; + font-size: 0.875rem; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + transform: translate(-50%, -50%); + white-space: nowrap; + background-color: white; +} + +.filename { + background: #f5f2f0; + margin-bottom: -0.5rem; + margin-top: 0.5rem; + padding: 0.5rem 0.75rem; + border-bottom: 1px solid #999; + font-size: 16px; +} + +.hover\:scale-105:hover { + --tw-scale-x: 1.05; + --tw-scale-y: 1.05; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.hover\:border-slate-400:hover { + --tw-border-opacity: 1; + border-color: rgb(148 163 184 / var(--tw-border-opacity)); +} + +.hover\:bg-palette-main:hover { + --tw-bg-opacity: 1; + background-color: rgb(64 186 189 / var(--tw-bg-opacity)); +} + +.hover\:text-pink-500:hover { + --tw-text-opacity: 1; + color: rgb(236 72 153 / var(--tw-text-opacity)); +} + +.hover\:text-gray-900:hover { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +.hover\:text-gray-800:hover { + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity)); +} + +.hover\:text-palette-main:hover { + --tw-text-opacity: 1; + color: rgb(64 186 189 / var(--tw-text-opacity)); +} + +.hover\:text-gray-600:hover { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity)); +} + +.hover\:text-slate-900:hover { + --tw-text-opacity: 1; + color: rgb(15 23 42 / var(--tw-text-opacity)); +} + +.hover\:text-white:hover { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.hover\:underline:hover { + -webkit-text-decoration-line: underline; + text-decoration-line: underline; +} + +.hover\:no-underline:hover { + -webkit-text-decoration-line: none; + text-decoration-line: none; +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.dark .dark\:border-gray-700 { + --tw-border-opacity: 1; + border-color: rgb(55 65 81 / var(--tw-border-opacity)); +} + +.dark .dark\:border-slate-800 { + --tw-border-opacity: 1; + border-color: rgb(30 41 59 / var(--tw-border-opacity)); +} + +.dark .dark\:border-slate-200\/5 { + border-color: rgb(226 232 240 / 0.05); +} + +.dark .dark\:bg-slate-900 { + --tw-bg-opacity: 1; + background-color: rgb(15 23 42 / var(--tw-bg-opacity)); +} + +.dark .dark\:bg-gray-800 { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +} + +.dark .dark\:text-slate-200 { + --tw-text-opacity: 1; + color: rgb(226 232 240 / var(--tw-text-opacity)); +} + +@media (min-width: 640px) { + .sm\:mb-0 { + margin-bottom: 0px; + } + + .sm\:flex { + display: flex; + } + + .sm\:px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; + } +} + +@media (min-width: 768px) { + .md\:col-span-2 { + grid-column: span 2 / span 2; + } + + .md\:col-span-3 { + grid-column: span 3 / span 3; + } + + .md\:mb-6 { + margin-bottom: 1.5rem; + } + + .md\:mr-0 { + margin-right: 0px; + } + + .md\:block { + display: block; + } + + .md\:grid { + display: grid; + } + + .md\:w-2\/5 { + width: 40%; + } + + .md\:grid-cols-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + + .md\:flex-row { + flex-direction: row; + } + + .md\:gap-4 { + gap: 1rem; + } + + .md\:px-8 { + padding-left: 2rem; + padding-right: 2rem; + } + + .md\:text-left { + text-align: left; + } +} + +@media (min-width: 1024px) { + .lg\:top-0 { + top: 0px; + } + + .lg\:right-auto { + right: auto; + } + + .lg\:bottom-0 { + bottom: 0px; + } + + .lg\:left-\[max\(0px\2c calc\(50\%-45rem\)\)\] { + left: max(0px,calc(50% - 45rem)); + } + + .lg\:z-10 { + z-index: 10; + } + + .lg\:mx-0 { + margin-left: 0px; + margin-right: 0px; + } + + .lg\:mt-0 { + margin-top: 0px; + } + + .lg\:block { + display: block; + } + + .lg\:flex { + display: flex; + } + + .lg\:hidden { + display: none; + } + + .lg\:w-auto { + width: auto; + } + + .lg\:translate-x-0 { + --tw-translate-x: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + } + + .lg\:items-center { + align-items: center; + } + + .lg\:bg-transparent { + background-color: transparent; + } + + .lg\:p-0 { + padding: 0px; + } + + .lg\:pl-\[22rem\] { + padding-left: 22rem; + } + + .lg\:text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; + } + + .lg\:text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; + } + + .lg\:leading-6 { + line-height: 1.5rem; + } +} + +@media (min-width: 1280px) { + .xl\:ml-0 { + margin-left: 0px; + } + + .xl\:mr-64 { + margin-right: 16rem; + } + + .xl\:max-w-none { + max-width: none; + } + + .xl\:pr-16 { + padding-right: 4rem; + } +} diff --git a/site/Site/wwwroot/css/input.css b/site/Site/wwwroot/css/input.css new file mode 100644 index 000000000..a670b948a --- /dev/null +++ b/site/Site/wwwroot/css/input.css @@ -0,0 +1,128 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer components { + h1 { + @apply text-3xl font-extrabold my-4; + } + + h2 { + @apply text-2xl font-bold my-4; + } + + h3 { + @apply text-xl font-bold my-4; + } + + table { + @apply border-collapse table-auto w-full my-4; + } + + tr { + @apply border-b; + } + + td, th { + @apply px-6 py-4 text-left; + } +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } + +#header { + box-shadow: 0 1px 2px 0 #eee; +} + +#docs-sidebar { + box-shadow: 1px 0 2px 0 #eee; +} + +#header img { + margin-bottom: 6px; +} + +.bg-main { + background-color: #40BABD; +} + +.text-main { + color: #40BABD; +} + +.bg-color1 { + background-color: #A0B15B; +} + +.bg-color2 { + background-color: #DC9A7A; +} + +.bg-color3 { + background-color: #9EA5E3; +} + +.outline-color1 { + box-shadow: 0px 0px 0px 2px #A0B15B inset; +} + +.outline-color2 { + box-shadow: 0px 0px 0px 2px #DC9A7A inset; +} + +.outline-color3 { + box-shadow: 0px 0px 0px 2px #9EA5E3 inset; +} + +.outline-color1-darker { + box-shadow: 0px 0px 0px 2px #515a2b inset; +} + +.outline-color2-darker { + box-shadow: 0px 0px 0px 2px #874423 inset; +} + +.outline-color3-darker { + box-shadow: 0px 0px 0px 2px #2b3595 inset; +} + +.diagram-link-label > div { + display: inline-block; + color: #000000; + font-size: 0.875rem; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + white-space: nowrap; + background-color: white; +} + +.filename { + background: #f5f2f0; + margin-bottom: -0.5rem; + margin-top: 0.5rem; + padding: 0.5rem 0.75rem; + border-bottom: 1px solid #999; + font-size: 16px; +} diff --git a/site/Site/wwwroot/css/prism.css b/site/Site/wwwroot/css/prism.css new file mode 100644 index 000000000..c5414b0b7 --- /dev/null +++ b/site/Site/wwwroot/css/prism.css @@ -0,0 +1,6 @@ +/* PrismJS 1.29.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+csharp+cshtml&plugins=line-highlight+line-numbers+normalize-whitespace+toolbar+copy-to-clipboard */ +code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help} +pre[data-line]{position:relative;padding:1em 0 1em 3em}.line-highlight{position:absolute;left:0;right:0;padding:inherit 0;margin-top:1em;background:hsla(24,20%,50%,.08);background:linear-gradient(to right,hsla(24,20%,50%,.1) 70%,hsla(24,20%,50%,0));pointer-events:none;line-height:inherit;white-space:pre}@media print{.line-highlight{-webkit-print-color-adjust:exact;color-adjust:exact}}.line-highlight:before,.line-highlight[data-end]:after{content:attr(data-start);position:absolute;top:.4em;left:.6em;min-width:1em;padding:0 .5em;background-color:hsla(24,20%,50%,.4);color:#f4f1ef;font:bold 65%/1.5 sans-serif;text-align:center;vertical-align:.3em;border-radius:999px;text-shadow:none;box-shadow:0 1px #fff}.line-highlight[data-end]:after{content:attr(data-end);top:auto;bottom:.4em}.line-numbers .line-highlight:after,.line-numbers .line-highlight:before{content:none}pre[id].linkable-line-numbers span.line-numbers-rows{pointer-events:all}pre[id].linkable-line-numbers span.line-numbers-rows>span:before{cursor:pointer}pre[id].linkable-line-numbers span.line-numbers-rows>span:hover:before{background-color:rgba(128,128,128,.2)} +pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right} +div.code-toolbar{position:relative}div.code-toolbar>.toolbar{position:absolute;z-index:10;top:.3em;right:.2em;transition:opacity .3s ease-in-out;opacity:0}div.code-toolbar:hover>.toolbar{opacity:1}div.code-toolbar:focus-within>.toolbar{opacity:1}div.code-toolbar>.toolbar>.toolbar-item{display:inline-block}div.code-toolbar>.toolbar>.toolbar-item>a{cursor:pointer}div.code-toolbar>.toolbar>.toolbar-item>button{background:0 0;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}div.code-toolbar>.toolbar>.toolbar-item>a,div.code-toolbar>.toolbar>.toolbar-item>button,div.code-toolbar>.toolbar>.toolbar-item>span{color:#bbb;font-size:.8em;padding:0 .5em;background:#f5f2f0;background:rgba(224,224,224,.2);box-shadow:0 2px 0 0 rgba(0,0,0,.2);border-radius:.5em}div.code-toolbar>.toolbar>.toolbar-item>a:focus,div.code-toolbar>.toolbar>.toolbar-item>a:hover,div.code-toolbar>.toolbar>.toolbar-item>button:focus,div.code-toolbar>.toolbar>.toolbar-item>button:hover,div.code-toolbar>.toolbar>.toolbar-item>span:focus,div.code-toolbar>.toolbar>.toolbar-item>span:hover{color:inherit;text-decoration:none} diff --git a/site/Site/wwwroot/favicon.ico b/site/Site/wwwroot/favicon.ico new file mode 100644 index 000000000..475ad21e2 Binary files /dev/null and b/site/Site/wwwroot/favicon.ico differ diff --git a/site/Site/wwwroot/icon-192.png b/site/Site/wwwroot/icon-192.png new file mode 100644 index 000000000..166f56da7 Binary files /dev/null and b/site/Site/wwwroot/icon-192.png differ diff --git a/site/Site/wwwroot/img/ZBD.png b/site/Site/wwwroot/img/ZBD.png new file mode 100644 index 000000000..e641498c8 Binary files /dev/null and b/site/Site/wwwroot/img/ZBD.png differ diff --git a/site/Site/wwwroot/index.html b/site/Site/wwwroot/index.html new file mode 100644 index 000000000..d77c6dbcf --- /dev/null +++ b/site/Site/wwwroot/index.html @@ -0,0 +1,33 @@ + + + + + + + Site + + + + + + + + + + +
Loading...
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + + + + diff --git a/site/Site/wwwroot/js/prism.js b/site/Site/wwwroot/js/prism.js new file mode 100644 index 000000000..2091ce848 --- /dev/null +++ b/site/Site/wwwroot/js/prism.js @@ -0,0 +1,14 @@ +/* PrismJS 1.29.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+csharp+cshtml&plugins=line-highlight+line-numbers+normalize-whitespace+toolbar+copy-to-clipboard */ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); +Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var t={"included-cdata":{pattern://i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml; +!function(s){var e=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:[^;{\\s\"']|\\s+(?!\\s)|"+e.source+")*?(?:;|(?=\\s*\\{))"),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+e.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+e.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:e,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism); +Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}; +Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))"),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript; +!function(e){function n(e,n){return e.replace(/<<(\d+)>>/g,(function(e,s){return"(?:"+n[+s]+")"}))}function s(e,s,a){return RegExp(n(e,s),a||"")}function a(e,n){for(var s=0;s>/g,(function(){return"(?:"+e+")"}));return e.replace(/<>/g,"[^\\s\\S]")}var t="bool byte char decimal double dynamic float int long object sbyte short string uint ulong ushort var void",r="class enum interface record struct",i="add alias and ascending async await by descending from(?=\\s*(?:\\w|$)) get global group into init(?=\\s*;) join let nameof not notnull on or orderby partial remove select set unmanaged value when where with(?=\\s*{)",o="abstract as base break case catch checked const continue default delegate do else event explicit extern finally fixed for foreach goto if implicit in internal is lock namespace new null operator out override params private protected public readonly ref return sealed sizeof stackalloc static switch this throw try typeof unchecked unsafe using virtual volatile while yield";function l(e){return"\\b(?:"+e.trim().replace(/ /g,"|")+")\\b"}var d=l(r),p=RegExp(l(t+" "+r+" "+i+" "+o)),c=l(r+" "+i+" "+o),u=l(t+" "+r+" "+o),g=a("<(?:[^<>;=+\\-*/%&|^]|<>)*>",2),b=a("\\((?:[^()]|<>)*\\)",2),h="@?\\b[A-Za-z_]\\w*\\b",f=n("<<0>>(?:\\s*<<1>>)?",[h,g]),m=n("(?!<<0>>)<<1>>(?:\\s*\\.\\s*<<1>>)*",[c,f]),k="\\[\\s*(?:,\\s*)*\\]",y=n("<<0>>(?:\\s*(?:\\?\\s*)?<<1>>)*(?:\\s*\\?)?",[m,k]),w=n("[^,()<>[\\];=+\\-*/%&|^]|<<0>>|<<1>>|<<2>>",[g,b,k]),v=n("\\(<<0>>+(?:,<<0>>+)+\\)",[w]),x=n("(?:<<0>>|<<1>>)(?:\\s*(?:\\?\\s*)?<<2>>)*(?:\\s*\\?)?",[v,m,k]),$={keyword:p,punctuation:/[<>()?,.:[\]]/},_="'(?:[^\r\n'\\\\]|\\\\.|\\\\[Uux][\\da-fA-F]{1,8})'",B='"(?:\\\\.|[^\\\\"\r\n])*"';e.languages.csharp=e.languages.extend("clike",{string:[{pattern:s("(^|[^$\\\\])<<0>>",['@"(?:""|\\\\[^]|[^\\\\"])*"(?!")']),lookbehind:!0,greedy:!0},{pattern:s("(^|[^@$\\\\])<<0>>",[B]),lookbehind:!0,greedy:!0}],"class-name":[{pattern:s("(\\busing\\s+static\\s+)<<0>>(?=\\s*;)",[m]),lookbehind:!0,inside:$},{pattern:s("(\\busing\\s+<<0>>\\s*=\\s*)<<1>>(?=\\s*;)",[h,x]),lookbehind:!0,inside:$},{pattern:s("(\\busing\\s+)<<0>>(?=\\s*=)",[h]),lookbehind:!0},{pattern:s("(\\b<<0>>\\s+)<<1>>",[d,f]),lookbehind:!0,inside:$},{pattern:s("(\\bcatch\\s*\\(\\s*)<<0>>",[m]),lookbehind:!0,inside:$},{pattern:s("(\\bwhere\\s+)<<0>>",[h]),lookbehind:!0},{pattern:s("(\\b(?:is(?:\\s+not)?|as)\\s+)<<0>>",[y]),lookbehind:!0,inside:$},{pattern:s("\\b<<0>>(?=\\s+(?!<<1>>|with\\s*\\{)<<2>>(?:\\s*[=,;:{)\\]]|\\s+(?:in|when)\\b))",[x,u,h]),inside:$}],keyword:p,number:/(?:\b0(?:x[\da-f_]*[\da-f]|b[01_]*[01])|(?:\B\.\d+(?:_+\d+)*|\b\d+(?:_+\d+)*(?:\.\d+(?:_+\d+)*)?)(?:e[-+]?\d+(?:_+\d+)*)?)(?:[dflmu]|lu|ul)?\b/i,operator:/>>=?|<<=?|[-=]>|([-+&|])\1|~|\?\?=?|[-+*/%&|^!=<>]=?/,punctuation:/\?\.?|::|[{}[\];(),.:]/}),e.languages.insertBefore("csharp","number",{range:{pattern:/\.\./,alias:"operator"}}),e.languages.insertBefore("csharp","punctuation",{"named-parameter":{pattern:s("([(,]\\s*)<<0>>(?=\\s*:)",[h]),lookbehind:!0,alias:"punctuation"}}),e.languages.insertBefore("csharp","class-name",{namespace:{pattern:s("(\\b(?:namespace|using)\\s+)<<0>>(?:\\s*\\.\\s*<<0>>)*(?=\\s*[;{])",[h]),lookbehind:!0,inside:{punctuation:/\./}},"type-expression":{pattern:s("(\\b(?:default|sizeof|typeof)\\s*\\(\\s*(?!\\s))(?:[^()\\s]|\\s(?!\\s)|<<0>>)*(?=\\s*\\))",[b]),lookbehind:!0,alias:"class-name",inside:$},"return-type":{pattern:s("<<0>>(?=\\s+(?:<<1>>\\s*(?:=>|[({]|\\.\\s*this\\s*\\[)|this\\s*\\[))",[x,m]),inside:$,alias:"class-name"},"constructor-invocation":{pattern:s("(\\bnew\\s+)<<0>>(?=\\s*[[({])",[x]),lookbehind:!0,inside:$,alias:"class-name"},"generic-method":{pattern:s("<<0>>\\s*<<1>>(?=\\s*\\()",[h,g]),inside:{function:s("^<<0>>",[h]),generic:{pattern:RegExp(g),alias:"class-name",inside:$}}},"type-list":{pattern:s("\\b((?:<<0>>\\s+<<1>>|record\\s+<<1>>\\s*<<5>>|where\\s+<<2>>)\\s*:\\s*)(?:<<3>>|<<4>>|<<1>>\\s*<<5>>|<<6>>)(?:\\s*,\\s*(?:<<3>>|<<4>>|<<6>>))*(?=\\s*(?:where|[{;]|=>|$))",[d,f,h,x,p.source,b,"\\bnew\\s*\\(\\s*\\)"]),lookbehind:!0,inside:{"record-arguments":{pattern:s("(^(?!new\\s*\\()<<0>>\\s*)<<1>>",[f,b]),lookbehind:!0,greedy:!0,inside:e.languages.csharp},keyword:p,"class-name":{pattern:RegExp(x),greedy:!0,inside:$},punctuation:/[,()]/}},preprocessor:{pattern:/(^[\t ]*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(#)\b(?:define|elif|else|endif|endregion|error|if|line|nullable|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}});var E=B+"|"+_,R=n("/(?![*/])|//[^\r\n]*[\r\n]|/\\*(?:[^*]|\\*(?!/))*\\*/|<<0>>",[E]),z=a(n("[^\"'/()]|<<0>>|\\(<>*\\)",[R]),2),S="\\b(?:assembly|event|field|method|module|param|property|return|type)\\b",j=n("<<0>>(?:\\s*\\(<<1>>*\\))?",[m,z]);e.languages.insertBefore("csharp","class-name",{attribute:{pattern:s("((?:^|[^\\s\\w>)?])\\s*\\[\\s*)(?:<<0>>\\s*:\\s*)?<<1>>(?:\\s*,\\s*<<1>>)*(?=\\s*\\])",[S,j]),lookbehind:!0,greedy:!0,inside:{target:{pattern:s("^<<0>>(?=\\s*:)",[S]),alias:"keyword"},"attribute-arguments":{pattern:s("\\(<<0>>*\\)",[z]),inside:e.languages.csharp},"class-name":{pattern:RegExp(m),inside:{punctuation:/\./}},punctuation:/[:,]/}}});var A=":[^}\r\n]+",F=a(n("[^\"'/()]|<<0>>|\\(<>*\\)",[R]),2),P=n("\\{(?!\\{)(?:(?![}:])<<0>>)*<<1>>?\\}",[F,A]),U=a(n("[^\"'/()]|/(?!\\*)|/\\*(?:[^*]|\\*(?!/))*\\*/|<<0>>|\\(<>*\\)",[E]),2),Z=n("\\{(?!\\{)(?:(?![}:])<<0>>)*<<1>>?\\}",[U,A]);function q(n,a){return{interpolation:{pattern:s("((?:^|[^{])(?:\\{\\{)*)<<0>>",[n]),lookbehind:!0,inside:{"format-string":{pattern:s("(^\\{(?:(?![}:])<<0>>)*)<<1>>(?=\\}$)",[a,A]),lookbehind:!0,inside:{punctuation:/^:/}},punctuation:/^\{|\}$/,expression:{pattern:/[\s\S]+/,alias:"language-csharp",inside:e.languages.csharp}}},string:/[\s\S]+/}}e.languages.insertBefore("csharp","string",{"interpolation-string":[{pattern:s('(^|[^\\\\])(?:\\$@|@\\$)"(?:""|\\\\[^]|\\{\\{|<<0>>|[^\\\\{"])*"',[P]),lookbehind:!0,greedy:!0,inside:q(P,F)},{pattern:s('(^|[^@\\\\])\\$"(?:\\\\.|\\{\\{|<<0>>|[^\\\\"{])*"',[Z]),lookbehind:!0,greedy:!0,inside:q(Z,U)}],char:{pattern:RegExp(_),greedy:!0}}),e.languages.dotnet=e.languages.cs=e.languages.csharp}(Prism); +!function(e){function s(e,s){for(var a=0;a/g,(function(){return"(?:"+e+")"}));return e.replace(//g,"[^\\s\\S]").replace(//g,'(?:@(?!")|"(?:[^\r\n\\\\"]|\\\\.)*"|@"(?:[^\\\\"]|""|\\\\[^])*"(?!")|\'(?:(?:[^\r\n\'\\\\]|\\\\.|\\\\[Uux][\\da-fA-F]{1,8})\'|(?=[^\\\\](?!\'))))').replace(//g,"(?:/(?![/*])|//.*[\r\n]|/\\*[^*]*(?:\\*(?!/)[^*]*)*\\*/)")}var a=s("\\((?:[^()'\"@/]|||)*\\)",2),t=s("\\[(?:[^\\[\\]'\"@/]|||)*\\]",1),r=s("\\{(?:[^{}'\"@/]|||)*\\}",2),n="@(?:await\\b\\s*)?(?:(?!await\\b)\\w+\\b|"+a+")(?:[?!]?\\.\\w+\\b|(?:"+s("<(?:[^<>'\"@/]||)*>",1)+")?"+a+"|"+t+")*(?![?!\\.(\\[]|<(?!/))",l="(?:\"[^\"@]*\"|'[^'@]*'|[^\\s'\"@>=]+(?=[\\s>])|[\"'][^\"'@]*(?:(?:@(?![\\w()])|"+n+")[^\"'@]*)+[\"'])",i="(?:\\s(?:\\s*[^\\s>/=]+(?:\\s*=\\s*|(?=[\\s/>])))+)?".replace(//,l),g="(?!\\d)[^\\s>/=$<%]+"+i+"\\s*/?>",o="\\B@?(?:<([a-zA-Z][\\w:]*)"+i+"\\s*>(?:[^<]|(?:[^<]|)*",2)+")*|<"+g+")";e.languages.cshtml=e.languages.extend("markup",{});var c={pattern:/\S[\s\S]*/,alias:"language-csharp",inside:e.languages.insertBefore("csharp","string",{html:{pattern:RegExp(o),greedy:!0,inside:e.languages.cshtml}},{csharp:e.languages.extend("csharp",{})})},p={pattern:RegExp("(^|[^@])"+n),lookbehind:!0,greedy:!0,alias:"variable",inside:{keyword:/^@/,csharp:c}};e.languages.cshtml.tag.pattern=RegExp(" ",document.body.appendChild(t),e=38===t.offsetHeight,document.body.removeChild(t)}return e}()?parseInt:parseFloat)(getComputedStyle(o).lineHeight),p=Prism.util.isActive(o,t),g=o.querySelector("code"),m=p?o:g||o,v=[],y=g.textContent.match(n),b=y?y.length+1:1,A=g&&m!=g?function(e,t){var i=getComputedStyle(e),n=getComputedStyle(t);function r(e){return+e.substr(0,e.length-2)}return t.offsetTop+r(n.borderTopWidth)+r(n.paddingTop)-r(i.paddingTop)}(o,g):0;h.forEach((function(e){var t=e.split("-"),i=+t[0],n=+t[1]||i;if(!((n=Math.min(b+d,n))i&&r.setAttribute("data-end",String(n)),r.style.top=(i-d-1)*f+A+"px",r.textContent=new Array(n-i+2).join(" \n")}));v.push((function(){r.style.width=o.scrollWidth+"px"})),v.push((function(){m.appendChild(r)}))}}));var P=o.id;if(p&&Prism.util.isActive(o,i)&&P){l(o,i)||v.push((function(){o.classList.add(i)}));var E=parseInt(o.getAttribute("data-start")||"1");s(".line-numbers-rows > span",o).forEach((function(e,t){var i=t+E;e.onclick=function(){var e=P+"."+i;r=!1,location.hash=e,setTimeout((function(){r=!0}),1)}}))}return function(){v.forEach(a)}}};var o=0;Prism.hooks.add("before-sanity-check",(function(e){var t=e.element.parentElement;if(u(t)){var i=0;s(".line-highlight",t).forEach((function(e){i+=e.textContent.length,e.parentNode.removeChild(e)})),i&&/^(?: \n)+$/.test(e.code.slice(-i))&&(e.code=e.code.slice(0,-i))}})),Prism.hooks.add("complete",(function e(i){var n=i.element.parentElement;if(u(n)){clearTimeout(o);var r=Prism.plugins.lineNumbers,s=i.plugins&&i.plugins.lineNumbers;l(n,t)&&r&&!s?Prism.hooks.add("line-numbers",e):(Prism.plugins.lineHighlight.highlightLines(n)(),o=setTimeout(c,1))}})),window.addEventListener("hashchange",c),window.addEventListener("resize",(function(){s("pre").filter(u).map((function(e){return Prism.plugins.lineHighlight.highlightLines(e)})).forEach(a)}))}function s(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function l(e,t){return e.classList.contains(t)}function a(e){e()}function u(e){return!!(e&&/pre/i.test(e.nodeName)&&(e.hasAttribute("data-line")||e.id&&Prism.util.isActive(e,i)))}function c(){var e=location.hash.slice(1);s(".temporary.line-highlight").forEach((function(e){e.parentNode.removeChild(e)}));var t=(e.match(/\.([\d,-]+)$/)||[,""])[1];if(t&&!document.getElementById(e)){var i=e.slice(0,e.lastIndexOf(".")),n=document.getElementById(i);n&&(n.hasAttribute("data-line")||n.setAttribute("data-line",""),Prism.plugins.lineHighlight.highlightLines(n,t,"temporary ")(),r&&document.querySelector(".temporary.line-highlight").scrollIntoView())}}}(); +!function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){var e="line-numbers",n=/\n(?!$)/g,t=Prism.plugins.lineNumbers={getLine:function(n,t){if("PRE"===n.tagName&&n.classList.contains(e)){var i=n.querySelector(".line-numbers-rows");if(i){var r=parseInt(n.getAttribute("data-start"),10)||1,s=r+(i.children.length-1);ts&&(t=s);var l=t-r;return i.children[l]}}},resize:function(e){r([e])},assumeViewportIndependence:!0},i=void 0;window.addEventListener("resize",(function(){t.assumeViewportIndependence&&i===window.innerWidth||(i=window.innerWidth,r(Array.prototype.slice.call(document.querySelectorAll("pre.line-numbers"))))})),Prism.hooks.add("complete",(function(t){if(t.code){var i=t.element,s=i.parentNode;if(s&&/pre/i.test(s.nodeName)&&!i.querySelector(".line-numbers-rows")&&Prism.util.isActive(i,e)){i.classList.remove(e),s.classList.add(e);var l,o=t.code.match(n),a=o?o.length+1:1,u=new Array(a+1).join("");(l=document.createElement("span")).setAttribute("aria-hidden","true"),l.className="line-numbers-rows",l.innerHTML=u,s.hasAttribute("data-start")&&(s.style.counterReset="linenumber "+(parseInt(s.getAttribute("data-start"),10)-1)),t.element.appendChild(l),r([s]),Prism.hooks.run("line-numbers",t)}}})),Prism.hooks.add("line-numbers",(function(e){e.plugins=e.plugins||{},e.plugins.lineNumbers=!0}))}function r(e){if(0!=(e=e.filter((function(e){var n,t=(n=e,n?window.getComputedStyle?getComputedStyle(n):n.currentStyle||null:null)["white-space"];return"pre-wrap"===t||"pre-line"===t}))).length){var t=e.map((function(e){var t=e.querySelector("code"),i=e.querySelector(".line-numbers-rows");if(t&&i){var r=e.querySelector(".line-numbers-sizer"),s=t.textContent.split(n);r||((r=document.createElement("span")).className="line-numbers-sizer",t.appendChild(r)),r.innerHTML="0",r.style.display="block";var l=r.getBoundingClientRect().height;return r.innerHTML="",{element:e,lines:s,lineHeights:[],oneLinerHeight:l,sizer:r}}})).filter(Boolean);t.forEach((function(e){var n=e.sizer,t=e.lines,i=e.lineHeights,r=e.oneLinerHeight;i[t.length-1]=void 0,t.forEach((function(e,t){if(e&&e.length>1){var s=n.appendChild(document.createElement("span"));s.style.display="block",s.textContent=e}else i[t]=r}))})),t.forEach((function(e){for(var n=e.sizer,t=e.lineHeights,i=0,r=0;rt&&(o[l]="\n"+o[l],a=s)}n[i]=o.join("")}return n.join("\n")}},"undefined"!=typeof module&&module.exports&&(module.exports=n),Prism.plugins.NormalizeWhitespace=new n({"remove-trailing":!0,"remove-indent":!0,"left-trim":!0,"right-trim":!0}),Prism.hooks.add("before-sanity-check",(function(e){var n=Prism.plugins.NormalizeWhitespace;if((!e.settings||!1!==e.settings["whitespace-normalization"])&&Prism.util.isActive(e.element,"whitespace-normalization",!0))if(e.element&&e.element.parentNode||!e.code){var r=e.element.parentNode;if(e.code&&r&&"pre"===r.nodeName.toLowerCase()){for(var i in null==e.settings&&(e.settings={}),t)if(Object.hasOwnProperty.call(t,i)){var o=t[i];if(r.hasAttribute("data-"+i))try{var a=JSON.parse(r.getAttribute("data-"+i)||"true");typeof a===o&&(e.settings[i]=a)}catch(e){}}for(var l=r.childNodes,s="",c="",u=!1,m=0;m3.0.0 3.0.0 https://github.com/zHaytam/Blazor.Diagrams - 3.0.0-beta.5 + 3.0.0-beta.6 Z.Blazor.Diagrams.Algorithms blazor diagrams diagramming svg drag algorithms layouts Z.Blazor.Diagrams.Algorithms diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 4171853ef..2ac47210e 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -93,6 +93,7 @@ private void OnPointerUp(Model? model, MouseEventArgs e) if (_ongoingLink.IsAttached) // Snapped already { + _ongoingLink.TriggerTargetAttached(); _ongoingLink = null; return; } @@ -101,6 +102,7 @@ private void OnPointerUp(Model? model, MouseEventArgs e) { var targetAnchor = Diagram.Options.Links.TargetAnchorFactory(Diagram, _ongoingLink, linkable); _ongoingLink.SetTarget(targetAnchor); + _ongoingLink.TriggerTargetAttached(); _ongoingLink.Refresh(); _ongoingLink.RefreshLinks(); } @@ -114,17 +116,29 @@ private void OnPointerUp(Model? model, MouseEventArgs e) private PortModel? FindNearPortToAttachTo() { - var ongoingPosition = _targetPositionAnchor!.GetPosition(_ongoingLink!)!; - foreach (var port in Diagram.Nodes.SelectMany(n => n.Ports)) + if (_ongoingLink is null || _targetPositionAnchor is null) + return null; + + PortModel? nearestSnapPort = null; + var nearestSnapPortDistance = double.PositiveInfinity; + + var position = _targetPositionAnchor!.GetPosition(_ongoingLink)!; + + foreach (var port in Diagram.Nodes.SelectMany((NodeModel n) => n.Ports)) { - if (ongoingPosition.DistanceTo(port.MiddlePosition) < Diagram.Options.Links.SnappingRadius - && (_ongoingLink!.Source.Model == null || _ongoingLink.Source.Model.CanAttachTo(port))) + var distance = position.DistanceTo(port.Position); + + if (distance <= Diagram.Options.Links.SnappingRadius && (_ongoingLink.Source.Model?.CanAttachTo(port) != false)) { - return port; + if (distance < nearestSnapPortDistance) + { + nearestSnapPortDistance = distance; + nearestSnapPort = port; + } } } - return null; + return nearestSnapPort; } public override void Dispose() diff --git a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs index 86f6b6a53..c48293617 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs @@ -34,7 +34,6 @@ public bool RemoveShortcut(string key, bool ctrl, bool shift, bool alt) private async void OnDiagramKeyDown(KeyboardEventArgs e) { var k = KeysUtils.GetStringRepresentation(e.CtrlKey, e.ShiftKey, e.AltKey, e.Key); - Console.WriteLine(k); if (_shortcuts.TryGetValue(k, out var action)) { await action(Diagram); diff --git a/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs index 94c730eee..016427eb8 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs @@ -10,10 +10,9 @@ public SelectionBehavior(Diagram diagram) : base(diagram) Diagram.PointerDown += OnPointerDown; } - private void OnPointerDown(Model? model, PointerEventArgs e) => Process(model, e.CtrlKey); - - private void Process(Model? model, bool ctrlKey) + private void OnPointerDown(Model? model, PointerEventArgs e) { + var ctrlKey = e.CtrlKey; switch (model) { case null: diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj index ca57e422b..0c6375669 100644 --- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj +++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj @@ -10,7 +10,7 @@ 3.0.0 3.0.0 https://github.com/Blazor-Diagrams/Blazor.Diagrams - 3.0.0-beta.5 + 3.0.0-beta.6 Z.Blazor.Diagrams.Core blazor diagrams diagramming svg drag Z.Blazor.Diagrams.Core diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 502295be7..4467a62f8 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -21,7 +21,6 @@ public abstract class Diagram { private readonly Dictionary _behaviors; private readonly List _orderedSelectables; - private bool _suspendSorting; public event Action? PointerDown; public event Action? PointerMove; @@ -77,6 +76,7 @@ protected Diagram() public Point Pan { get; private set; } = Point.Zero; public double Zoom { get; private set; } = 1; public bool SuspendRefresh { get; set; } + public bool SuspendSorting { get; set; } public IReadOnlyList OrderedSelectables => _orderedSelectables; public void Refresh() @@ -304,12 +304,12 @@ public void SendToBack(SelectableModel model) // Todo: can optimize this by only updating the order of items before model Batch(() => { - _suspendSorting = true; + SuspendSorting = true; for (var i = 0; i < _orderedSelectables.Count; i++) { _orderedSelectables[i].Order = i + 1; } - _suspendSorting = false; + SuspendSorting = false; }); } @@ -324,9 +324,9 @@ public void SendToFront(SelectableModel model) _orderedSelectables.Add(model); - _suspendSorting = true; + SuspendSorting = true; model.Order = maxOrder + 1; - _suspendSorting = false; + SuspendSorting = false; Refresh(); } @@ -340,11 +340,29 @@ public int GetMaxOrder() return _orderedSelectables.Count > 0 ? _orderedSelectables[^1].Order : 0; } + /// + /// Sorts the list of selectables based on their order + /// + public void RefreshOrders(bool refresh = true) + { + _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order)); + + if (refresh) + { + Refresh(); + } + } + private void OnSelectableAdded(SelectableModel model) { - var maxOrder = GetMaxOrder(); _orderedSelectables.Add(model); - model.Order = maxOrder + 1; + + if (model.Order == 0) + { + var maxOrder = GetMaxOrder(); + model.Order = maxOrder + 1; + } + model.OrderChanged += OnModelOrderChanged; } @@ -356,11 +374,10 @@ private void OnSelectableRemoved(SelectableModel model) private void OnModelOrderChanged(Model model) { - if (_suspendSorting) + if (SuspendSorting) return; - _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order)); - Refresh(); + RefreshOrders(); } #endregion diff --git a/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs b/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs index 0880a7ec8..57d21bc69 100644 --- a/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs @@ -17,7 +17,7 @@ public BaseLayer(Diagram diagram) Diagram = diagram; } - public virtual T Add(T item) + public virtual TSpecific Add(TSpecific item) where TSpecific : T { if (item is null) throw new ArgumentNullException(nameof(item)); diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs index 75f420cfe..da51dd783 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs @@ -10,9 +10,10 @@ namespace Blazor.Diagrams.Core.Models.Base; public abstract class BaseLinkModel : SelectableModel, IHasBounds, ILinkable { private readonly List _links = new(); - + public event Action? SourceChanged; public event Action? TargetChanged; + public event Action? TargetAttached; protected BaseLinkModel(Anchor source, Anchor target) { @@ -86,20 +87,23 @@ public void SetTarget(Anchor anchor) var maxX = double.NegativeInfinity; var maxY = double.NegativeInfinity; - foreach (var path in PathGeneratorResult.Paths) - { - var bbox = path.GetBBox(); - minX = Math.Min(minX, bbox.Left); - minY = Math.Min(minY, bbox.Top); - maxX = Math.Max(maxX, bbox.Right); - maxY = Math.Max(maxY, bbox.Bottom); - } + var path = PathGeneratorResult.FullPath; + var bbox = path.GetBBox(); + minX = Math.Min(minX, bbox.Left); + minY = Math.Min(minY, bbox.Top); + maxX = Math.Max(maxX, bbox.Right); + maxY = Math.Max(maxY, bbox.Bottom); return new Rectangle(minX, minY, maxX, maxY); } public bool CanAttachTo(ILinkable other) => true; + /// + /// Triggers the TargetAttached event + /// + public void TriggerTargetAttached() => TargetAttached?.Invoke(this); + private void GeneratePath() { if (Diagram != null) diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs index 4153fbb17..88a9636fc 100644 --- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs +++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs @@ -61,7 +61,10 @@ public override void SetPosition(double x, double y) base.SetPosition(x, y); foreach (var node in Children) + { node.UpdatePositionSilently(deltaX, deltaY); + node.RefreshLinks(); + } Refresh(); RefreshLinks(); diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index aff38c09b..6113ca473 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -9,7 +9,7 @@ 3.0.0 https://github.com/Blazor-Diagrams/Blazor.Diagrams A fully customizable and extensible all-purpose diagrams library for Blazor - 3.0.0-beta.5 + 3.0.0-beta.6 true blazor diagrams diagramming svg drag Z.Blazor.Diagrams diff --git a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs index afb34664b..e3e8f4a64 100644 --- a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs +++ b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs @@ -57,7 +57,7 @@ private RenderFragment RenderControl(Model model, Control control, Point positio { builder.OpenElement(0, svg ? "g" : "div"); builder.AddAttribute(1, "class", - $"{(control is ExecutableControl ? "executable " : "")}control {control.GetType().Name}"); + $"{(control is ExecutableControl ? "executable " : "")}diagram-control {control.GetType().Name}"); if (svg) builder.AddAttribute(2, "transform", $"translate({position.X.ToInvariantString()} {position.Y.ToInvariantString()})"); diff --git a/src/Blazor.Diagrams/Components/DefaultLinkLabelWidget.razor b/src/Blazor.Diagrams/Components/DefaultLinkLabelWidget.razor index 7f2a1c0a7..e4814d1d3 100644 --- a/src/Blazor.Diagrams/Components/DefaultLinkLabelWidget.razor +++ b/src/Blazor.Diagrams/Components/DefaultLinkLabelWidget.razor @@ -1,4 +1,4 @@ -