diff --git a/docs/CustomNodesLinks/Pages/_Host.cshtml b/docs/CustomNodesLinks/Pages/_Host.cshtml index 926eaa7de..fb4f1ccd2 100644 --- a/docs/CustomNodesLinks/Pages/_Host.cshtml +++ b/docs/CustomNodesLinks/Pages/_Host.cshtml @@ -23,8 +23,6 @@ - - diff --git a/docs/Diagram-Demo/Pages/_Host.cshtml b/docs/Diagram-Demo/Pages/_Host.cshtml index 00fbb2f33..4e0b1d381 100644 --- a/docs/Diagram-Demo/Pages/_Host.cshtml +++ b/docs/Diagram-Demo/Pages/_Host.cshtml @@ -27,7 +27,6 @@ - diff --git a/docs/Diagram-Demo/README.md b/docs/Diagram-Demo/README.md index e1a1d8e2c..b8a00ba28 100644 --- a/docs/Diagram-Demo/README.md +++ b/docs/Diagram-Demo/README.md @@ -18,7 +18,6 @@ This is a minimal example of intergration into a _Blazor_ app. - diff --git a/docs/Layouts/Pages/_Host.cshtml b/docs/Layouts/Pages/_Host.cshtml index 0c20d3017..a6b6fd1a7 100644 --- a/docs/Layouts/Pages/_Host.cshtml +++ b/docs/Layouts/Pages/_Host.cshtml @@ -27,7 +27,6 @@ - diff --git a/samples/ServerSide/Pages/_Host.cshtml b/samples/ServerSide/Pages/_Host.cshtml index 6ae15bc21..60eff4b35 100644 --- a/samples/ServerSide/Pages/_Host.cshtml +++ b/samples/ServerSide/Pages/_Host.cshtml @@ -44,6 +44,5 @@ - diff --git a/samples/SharedDemo/QuickStart.razor b/samples/SharedDemo/QuickStart.razor index 19ff6edba..54cb24c2e 100644 --- a/samples/SharedDemo/QuickStart.razor +++ b/samples/SharedDemo/QuickStart.razor @@ -42,7 +42,7 @@ <!-- if you want the default styling --> <link href="_content/Z.Blazor.Diagrams/default.styles.min.css" rel="stylesheet" /> <!-- in the body element --> -<script src="_content/Z.Blazor.Diagrams/script.min.js"></script> +
diff --git a/samples/Wasm/wwwroot/index.html b/samples/Wasm/wwwroot/index.html index 9b010e3c2..9ada6e1e8 100644 --- a/samples/Wasm/wwwroot/index.html +++ b/samples/Wasm/wwwroot/index.html @@ -85,7 +85,6 @@ - diff --git a/site/Site/Pages/Documentation/GettingStarted/Installation.razor b/site/Site/Pages/Documentation/GettingStarted/Installation.razor index d4993daf1..36d10fecf 100644 --- a/site/Site/Pages/Documentation/GettingStarted/Installation.razor +++ b/site/Site/Pages/Documentation/GettingStarted/Installation.razor @@ -42,7 +42,6 @@ Install-Package Z.Blazor.Diagrams <body> <!-- ... --> - <script src="_content/Z.Blazor.Diagrams/script.min.js"></script> </body> </html> diff --git a/site/Site/wwwroot/index.html b/site/Site/wwwroot/index.html index 284a46451..149a7f6dc 100644 --- a/site/Site/wwwroot/index.html +++ b/site/Site/wwwroot/index.html @@ -79,7 +79,6 @@ }); - diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs index 8c723e043..4234a13a9 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Extensions; @@ -8,7 +9,7 @@ namespace Blazor.Diagrams.Components; -public partial class DiagramCanvas : IDisposable +public partial class DiagramCanvas : IAsyncDisposable { private DotNetObjectReference? _reference; private bool _shouldRender; @@ -27,15 +28,22 @@ public partial class DiagramCanvas : IDisposable [Inject] public IJSRuntime JSRuntime { get; set; } = null!; - public void Dispose() + private IJSObjectReference? _module; + + public async ValueTask DisposeAsync() { BlazorDiagram.Changed -= OnDiagramChanged; if (_reference == null) + { return; - - if (elementReference.Id != null) - _ = JSRuntime.UnobserveResizes(elementReference); + } + if (elementReference.Id != null && _module is not null) + await _module.UnobserveResizes(elementReference); + if(_module is not null) + { + await _module.DisposeAsync(); + } _reference.Dispose(); } @@ -60,8 +68,10 @@ protected override async Task OnAfterRenderAsync(bool firstRender) if (firstRender) { - BlazorDiagram.SetContainer(await JSRuntime.GetBoundingClientRect(elementReference)); - await JSRuntime.ObserveResizes(elementReference, _reference!); + _module ??= await JSRuntime.InvokeAsync("import","./_content/Z.Blazor.Diagrams/script.min.js"); + BlazorDiagram.SetContainer(await _module.GetBoundingClientRect(elementReference)); + + await _module.ObserveResizes(elementReference, _reference!); } } diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.js b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.js new file mode 100644 index 000000000..e131b912e --- /dev/null +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.js @@ -0,0 +1,65 @@ +let boundListener = {}; +let isObservingResize = false; + +const mo = new MutationObserver(() => { + for (const id in boundListener) { + const canvas = boundListener[id]; + const lastBounds = canvas.lastBounds; + const bounds = canvas.elem.getBoundingClientRect(); + if (lastBounds.left !== bounds.left || + lastBounds.top !== bounds.top || + lastBounds.width !== bounds.width || + lastBounds.height !== bounds.height) { + updateBounds(canvas); + } + } +}) + +const ro = new ResizeObserver(entries => { + for (const entry of entries) { + let id = Array.from(entry.target.attributes).find(e => e.name.startsWith('_bl')).name.substring(4); + let element = boundListener[id]; + if (element) { + updateBounds(element); + } + } +}) + +export function getBoundingClientRect(el) { + return el.getBoundingClientRect(); +} + +function updateBounds(canvas) { + + canvas.lastBounds = canvas.elem.getBoundingClientRect(); + canvas.ref.invokeMethodAsync('OnResize', canvas.lastBounds) +} + +export function observe(element, ref, id) { + if (isObservingResize === false) { + mo.observe(document.body, { childList: true, subtree: true }); + window.addEventListener('scroll', () => { + for (id in boundListener) { + const canvas = boundListener[id]; + updateBounds(canvas) + } + }) + + isObservingResize = true; + } + + if (!element) return; + ro.observe(element); + boundListener[id] = { + elem: element, + ref: ref, + lastBounds: element.getBoundingClientRect() + }; +} + +export function unobserve(element, id) { + if (element) { + ro.unobserve(element); + } + delete boundListener[id]; +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs index 2dbe58d02..153f38d78 100644 --- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using System.Text; using System.Threading.Tasks; using Blazor.Diagrams.Core.Extensions; @@ -14,13 +15,14 @@ namespace Blazor.Diagrams.Components.Renderers; -public class NodeRenderer : ComponentBase, IDisposable +public class NodeRenderer : ComponentBase { private bool _becameVisible; private ElementReference _element; private bool _isSvg; private DotNetObjectReference? _reference; private bool _shouldRender; + private IJSObjectReference? _module; [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; @@ -28,14 +30,18 @@ public class NodeRenderer : ComponentBase, IDisposable [Inject] private IJSRuntime JsRuntime { get; set; } = null!; - public void Dispose() + public async ValueTask DisposeAsync() { Node.Changed -= OnNodeChanged; Node.VisibilityChanged -= OnVisibilityChanged; + if (_element.Id != null && !Node.ControlledSize) { - _ = JsRuntime.UnobserveResizes(_element); + if(_module is not null) + { + await _module.UnobserveResizes(_element); + } } _reference?.Dispose(); @@ -61,6 +67,8 @@ public void OnResize(Size size) Node.ReinitializePorts(); } + + protected override void OnInitialized() { base.OnInitialized(); @@ -129,13 +137,18 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) protected override async Task OnAfterRenderAsync(bool firstRender) { + if (firstRender) + { + _module ??= await JsRuntime.InvokeAsync("import", "./_content/Z.Blazor.Diagrams/script.min.js"); + } + if (firstRender || _becameVisible) { _becameVisible = false; - if (!Node.ControlledSize) + if (!Node.ControlledSize && _module is not null) { - await JsRuntime.ObserveResizes(_element, _reference!); + await _module.ObserveResizes(_element, _reference!); } } } diff --git a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs index bbd4786f1..1687fca06 100644 --- a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs @@ -1,11 +1,9 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Extensions; using Blazor.Diagrams.Models; + using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Web; @@ -13,13 +11,14 @@ namespace Blazor.Diagrams.Components.Renderers; -public class PortRenderer : ComponentBase, IDisposable +public class PortRenderer : ComponentBase, IAsyncDisposable { private ElementReference _element; private bool _isParentSvg; private bool _shouldRefreshPort; private bool _shouldRender = true; private bool _updatingDimensions; + private IJSObjectReference? _module; [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; [Inject] private IJSRuntime JSRuntime { get; set; } = null!; @@ -28,8 +27,12 @@ public class PortRenderer : ComponentBase, IDisposable [Parameter] public string? Style { get; set; } [Parameter] public RenderFragment? ChildContent { get; set; } - public void Dispose() + public async ValueTask DisposeAsync() { + if (_module != null) + { + await _module.DisposeAsync(); + } Port.Changed -= OnPortChanged; Port.VisibilityChanged -= OnPortChanged; } @@ -62,7 +65,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) { if (!Port.Visible) return; - + builder.OpenElement(0, _isParentSvg ? "g" : "div"); builder.AddAttribute(1, "style", Style); builder.AddAttribute(2, "class", @@ -80,6 +83,10 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) protected override async Task OnAfterRenderAsync(bool firstRender) { + if (firstRender) + { + _module ??= await JSRuntime.InvokeAsync("import", "./_content/Z.Blazor.Diagrams/script.min.js"); + } if (!Port.Initialized) { await UpdateDimensions(); @@ -123,12 +130,15 @@ private async Task UpdateDimensions() _updatingDimensions = true; var zoom = BlazorDiagram.Zoom; var pan = BlazorDiagram.Pan; - var rect = await JSRuntime.GetBoundingClientRect(_element); + _module ??= await JSRuntime.InvokeAsync("import", "./_content/Z.Blazor.Diagrams/script.min.js"); + + var rect = await _module.GetBoundingClientRect(_element); Port.Size = new Size(rect.Width / zoom, rect.Height / zoom); Port.Position = new Point((rect.Left - BlazorDiagram.Container.Left - pan.X) / zoom, (rect.Top - BlazorDiagram.Container.Top - pan.Y) / zoom); + Port.Initialized = true; _updatingDimensions = false; diff --git a/src/Blazor.Diagrams/Extensions/JSObjectReferenceExtensions.cs b/src/Blazor.Diagrams/Extensions/JSObjectReferenceExtensions.cs new file mode 100644 index 000000000..72b08d94d --- /dev/null +++ b/src/Blazor.Diagrams/Extensions/JSObjectReferenceExtensions.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading.Tasks; + +using Blazor.Diagrams.Core.Geometry; + +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; + +namespace Blazor.Diagrams.Extensions; + +public static class JSObjectReferenceExtensions +{ + public static async Task GetBoundingClientRect(this IJSObjectReference jsRuntime, ElementReference element) + { + return await jsRuntime.InvokeAsync("getBoundingClientRect", element); + } + + public static async Task ObserveResizes(this IJSObjectReference jsRuntime, ElementReference element, + DotNetObjectReference reference) where T : class + { + try + { + await jsRuntime.InvokeVoidAsync("observe", element, reference, element.Id); + } + catch (ObjectDisposedException) + { + // Ignore, DotNetObjectReference was likely disposed + } + } + + public static async Task UnobserveResizes(this IJSObjectReference jsRuntime, ElementReference element) + { + await jsRuntime.InvokeVoidAsync("unobserve", element, element.Id); + } +} + diff --git a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs index 459456863..1d7daac49 100644 --- a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs +++ b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs @@ -8,11 +8,6 @@ namespace Blazor.Diagrams.Extensions; public static class JSRuntimeExtensions { - public static async Task GetBoundingClientRect(this IJSRuntime jsRuntime, ElementReference element) - { - return await jsRuntime.InvokeAsync("ZBlazorDiagrams.getBoundingClientRect", element); - } - public static async Task ObserveResizes(this IJSRuntime jsRuntime, ElementReference element, DotNetObjectReference reference) where T : class { diff --git a/src/Blazor.Diagrams/wwwroot/script.js b/src/Blazor.Diagrams/wwwroot/script.js index be3b4728c..e131b912e 100644 --- a/src/Blazor.Diagrams/wwwroot/script.js +++ b/src/Blazor.Diagrams/wwwroot/script.js @@ -1,58 +1,65 @@ -var s = { - canvases: {}, - tracked: {}, - getBoundingClientRect: el => { - return el.getBoundingClientRect(); - }, - mo: new MutationObserver(() => { - for (id in s.canvases) { - const canvas = s.canvases[id]; - const lastBounds = canvas.lastBounds; - const bounds = canvas.elem.getBoundingClientRect(); - if (lastBounds.left !== bounds.left || lastBounds.top !== bounds.top || lastBounds.width !== bounds.width || - lastBounds.height !== bounds.height) { - canvas.lastBounds = bounds; - canvas.ref.invokeMethodAsync('OnResize', bounds); - } - } - }), - ro: new ResizeObserver(entries => { - for (const entry of entries) { - let id = Array.from(entry.target.attributes).find(e => e.name.startsWith('_bl')).name.substring(4); - let element = s.tracked[id]; - if (element) { - element.ref.invokeMethodAsync('OnResize', entry.target.getBoundingClientRect()); - } - } - }), - observe: (element, ref, id) => { - if (!element) return; - s.ro.observe(element); - s.tracked[id] = { - ref: ref - }; - if (element.classList.contains("diagram-canvas")) { - s.canvases[id] = { - elem: element, - ref: ref, - lastBounds: element.getBoundingClientRect() - }; +let boundListener = {}; +let isObservingResize = false; + +const mo = new MutationObserver(() => { + for (const id in boundListener) { + const canvas = boundListener[id]; + const lastBounds = canvas.lastBounds; + const bounds = canvas.elem.getBoundingClientRect(); + if (lastBounds.left !== bounds.left || + lastBounds.top !== bounds.top || + lastBounds.width !== bounds.width || + lastBounds.height !== bounds.height) { + updateBounds(canvas); } - }, - unobserve: (element, id) => { + } +}) + +const ro = new ResizeObserver(entries => { + for (const entry of entries) { + let id = Array.from(entry.target.attributes).find(e => e.name.startsWith('_bl')).name.substring(4); + let element = boundListener[id]; if (element) { - s.ro.unobserve(element); + updateBounds(element); } - delete s.tracked[id]; - delete s.canvases[id]; } -}; -window.ZBlazorDiagrams = s; -window.addEventListener('scroll', () => { - for (id in s.canvases) { - const canvas = s.canvases[id]; - canvas.lastBounds = canvas.elem.getBoundingClientRect(); - canvas.ref.invokeMethodAsync('OnResize', canvas.lastBounds); +}) + +export function getBoundingClientRect(el) { + return el.getBoundingClientRect(); +} + +function updateBounds(canvas) { + + canvas.lastBounds = canvas.elem.getBoundingClientRect(); + canvas.ref.invokeMethodAsync('OnResize', canvas.lastBounds) +} + +export function observe(element, ref, id) { + if (isObservingResize === false) { + mo.observe(document.body, { childList: true, subtree: true }); + window.addEventListener('scroll', () => { + for (id in boundListener) { + const canvas = boundListener[id]; + updateBounds(canvas) + } + }) + + isObservingResize = true; + } + + if (!element) return; + ro.observe(element); + boundListener[id] = { + elem: element, + ref: ref, + lastBounds: element.getBoundingClientRect() + }; +} + +export function unobserve(element, id) { + if (element) { + ro.unobserve(element); } -}); -s.mo.observe(document.body, {childList: true, subtree: true}); \ No newline at end of file + delete boundListener[id]; +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/wwwroot/script.min.js b/src/Blazor.Diagrams/wwwroot/script.min.js index a4065277c..86cd3dd4b 100644 --- a/src/Blazor.Diagrams/wwwroot/script.min.js +++ b/src/Blazor.Diagrams/wwwroot/script.min.js @@ -1 +1 @@ -var s={canvases:{},tracked:{},getBoundingClientRect:n=>n.getBoundingClientRect(),mo:new MutationObserver(()=>{for(id in s.canvases){const t=s.canvases[id],i=t.lastBounds,n=t.elem.getBoundingClientRect();(i.left!==n.left||i.top!==n.top||i.width!==n.width||i.height!==n.height)&&(t.lastBounds=n,t.ref.invokeMethodAsync("OnResize",n))}}),ro:new ResizeObserver(n=>{for(const t of n){let i=Array.from(t.target.attributes).find(n=>n.name.startsWith("_bl")).name.substring(4),n=s.tracked[i];n&&n.ref.invokeMethodAsync("OnResize",t.target.getBoundingClientRect())}}),observe:(n,t,i)=>{n&&(s.ro.observe(n),s.tracked[i]={ref:t},n.classList.contains("diagram-canvas")&&(s.canvases[i]={elem:n,ref:t,lastBounds:n.getBoundingClientRect()}))},unobserve:(n,t)=>{n&&s.ro.unobserve(n),delete s.tracked[t],delete s.canvases[t]}};window.ZBlazorDiagrams=s;window.addEventListener("scroll",()=>{for(id in s.canvases){const n=s.canvases[id];n.lastBounds=n.elem.getBoundingClientRect();n.ref.invokeMethodAsync("OnResize",n.lastBounds)}});s.mo.observe(document.body,{childList:!0,subtree:!0}); \ No newline at end of file +let boundListener={},isObservingResize=!1;const mo=new MutationObserver(()=>{for(const id in boundListener){const canvas=boundListener[id],lastBounds=canvas.lastBounds,bounds=canvas.elem.getBoundingClientRect();lastBounds.left!==bounds.left||lastBounds.top!==bounds.top||lastBounds.width!==bounds.width||lastBounds.height!==bounds.height&&updateBounds(canvas)}}}),ro=new ResizeObserver(entries=>{for(const entry of entries){let id=Array.from(entry.target.attributes).find(e=>"name"===e.name.substring(0,4)).name.substring(4),element=boundListener[id];element&&updateBounds(element)}});export function getBoundingClientRect(el){return el.getBoundingClientRect()}function updateBounds(canvas){canvas.lastBounds=canvas.elem.getBoundingClientRect(),canvas.ref.invokeMethodAsync("OnResize",canvas.lastBounds)}export function observe(element,ref,id){if(!1===isObservingResize&&(mo.observe(document.body,{childList:!0,subtree:!0}),window.addEventListener("scroll",()=>{for(id in boundListener){const canvas=boundListener[id];updateBounds(canvas)}}),isObservingResize=!0),element){ro.observe(element);const lastBounds=element.getBoundingClientRect();boundListener[id]={elem:element,ref:ref,lastBounds}}export function unobserve(element,id){element&&ro.unobserve(element),delete boundListener[id]} diff --git a/src/Blazor.Diagrams/wwwroot/script.min.js.gz b/src/Blazor.Diagrams/wwwroot/script.min.js.gz deleted file mode 100644 index a4db96478..000000000 Binary files a/src/Blazor.Diagrams/wwwroot/script.min.js.gz and /dev/null differ