From ba818e48594895d101d203021f66b9ac90c48571 Mon Sep 17 00:00:00 2001 From: Branko Zachemsky Date: Wed, 13 May 2026 08:39:22 +0200 Subject: [PATCH 1/6] - verticaly moving fix - move outside fix - same tab name fix - show toast for inform user --- .../Components/VisualComposerContainer.razor | 1 + .../VisualComposerContainer.razor.cs | 41 +++++++++++++++++++ .../VisualComposerItem.razor.cs | 3 +- .../VisualComposerItemData.cs | 1 + 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor b/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor index b9f1302ff..e8658ef5e 100644 --- a/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor +++ b/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor @@ -135,6 +135,7 @@ {
@*
diff --git a/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor.cs b/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor.cs index e09290f76..0c707a631 100644 --- a/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor.cs +++ b/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Components.Web; using Microsoft.JSInterop; using Operon.Components; +using Operon.Components.Toast; using System.Text.RegularExpressions; namespace AXOpen.VisualComposer.Components @@ -27,6 +28,9 @@ public partial class VisualComposerContainer [Inject] private ProtectedLocalStorage _protectedLocalStorage { set; get; } + [Inject] + private IToastService _toastService { get; set; } + //private bool _editSVG { get; set; } = false; private bool _inDesignMode { get; set; } = false; private Guid _backgroundId { get; set; } = Guid.NewGuid(); @@ -213,7 +217,16 @@ private async Task MoveObjectDown(VisualComposerItemData item) private async Task CreateNewViewAsync(string name, SaveLocationType saveLocationType, bool isWatchTable) { if (string.IsNullOrEmpty(name)) + { + _toastService?.AddToast(eToastType.Warning, "View not created", "Please enter a view name.", 5); return; + } + + if (_serverStorageAllViews.Contains(name) || _localStorageData.ContainsKey(name)) + { + _toastService?.AddToast(eToastType.Warning, "View not created", $"A view with the name '{name}' already exists.", 5); + return; + } _currentViewName = name; @@ -233,7 +246,16 @@ private async Task CreateNewViewAsync(string name, SaveLocationType saveLocation private async Task CreateCopyViewAsync(string name, SaveLocationType saveLocationType) { if (string.IsNullOrEmpty(name)) + { + _toastService?.AddToast(eToastType.Warning, "View not created", "Please enter a view name.", 5); return; + } + + if (_serverStorageAllViews.Contains(name) || _localStorageData.ContainsKey(name)) + { + _toastService?.AddToast(eToastType.Warning, "View not created", $"A view with the name '{name}' already exists.", 5); + return; + } var oldViewName = _currentViewName; @@ -471,13 +493,19 @@ private async Task ImportViewAsync(InputFileChangeEventArgs e) { var file = e.File; if (file == null || !file.Name.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) + { + _toastService?.AddToast(eToastType.Warning, "Import failed", "Please select a valid .json file.", 5); return; + } using var stream = file.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024); // 10MB max var importedView = await System.Text.Json.JsonSerializer.DeserializeAsync(stream); if (importedView == null) + { + _toastService?.AddToast(eToastType.Warning, "Import failed", "The file could not be deserialized.", 5); return; + } var viewName = Path.GetFileNameWithoutExtension(file.Name); var originalName = viewName; @@ -511,10 +539,13 @@ await Serializing.SerializeAsync( // Load the imported view await LoadAsync(viewName); + _toastService?.AddToast(eToastType.Success, "View imported", $"View '{viewName}' was imported successfully.", 5); + StateHasChanged(); } catch (Exception ex) { + _toastService?.AddToast(eToastType.Warning, "Import failed", $"Error importing view: {ex.Message}", 7); Console.WriteLine($"Error importing view: {ex.Message}"); } } @@ -672,6 +703,7 @@ private async Task UploadFile(InputFileChangeEventArgs e) catch (Exception ex) { CurrentView.ImgSrc = null; + _toastService?.AddToast(eToastType.Warning, "Upload failed", $"Error uploading background image: {ex.Message}", 7); Console.WriteLine($"VisualComposer Error: {ex.Message}"); } @@ -776,6 +808,15 @@ private void Leave(PointerEventArgs eventArgs) } } + private void Up(PointerEventArgs eventArgs) + { + foreach (var item in _items) + { + if (item.UpEvent != null) + item.UpEvent.Invoke(this, eventArgs); + } + } + private string GetBgColor(SaveLocationType location) { if (location == SaveLocationType.Server) diff --git a/src/base/src/AXOpen.VisualComposer/Components/VisualComposerItem/VisualComposerItem.razor.cs b/src/base/src/AXOpen.VisualComposer/Components/VisualComposerItem/VisualComposerItem.razor.cs index 53fc42485..cf1cb1af8 100644 --- a/src/base/src/AXOpen.VisualComposer/Components/VisualComposerItem/VisualComposerItem.razor.cs +++ b/src/base/src/AXOpen.VisualComposer/Components/VisualComposerItem/VisualComposerItem.razor.cs @@ -21,6 +21,7 @@ protected override void OnAfterRender(bool firstRender) { Origin!.MoveEvent = new EventHandler((sender, e) => MoveAsync((PointerEventArgs)e)); Origin!.LeaveEvent = new EventHandler((sender, e) => Leave((PointerEventArgs)e)); + Origin!.UpEvent = new EventHandler((sender, e) => Up((PointerEventArgs)e)); } private async Task MoveAsync(PointerEventArgs eventArgs) @@ -28,7 +29,7 @@ private async Task MoveAsync(PointerEventArgs eventArgs) if (_isDragging) { double offsetX = ((eventArgs.ClientX - _startX) / Parent!.ElementSize.Width * 100) * (1 / Parent.CurrentView.Scale); - double offsetY = ((eventArgs.ClientY - _startY) / Parent!.ElementSize.Width * 100) * (1 / Parent.CurrentView.Scale); + double offsetY = ((eventArgs.ClientY - _startY) / Parent!.ElementSize.Height * 100) * (1 / Parent.CurrentView.Scale); Origin._left += offsetX; Origin._top += offsetY; diff --git a/src/base/src/AXOpen.VisualComposer/Components/VisualComposerItem/VisualComposerItemData.cs b/src/base/src/AXOpen.VisualComposer/Components/VisualComposerItem/VisualComposerItemData.cs index b9a0871c4..2cd044bf8 100644 --- a/src/base/src/AXOpen.VisualComposer/Components/VisualComposerItem/VisualComposerItemData.cs +++ b/src/base/src/AXOpen.VisualComposer/Components/VisualComposerItem/VisualComposerItemData.cs @@ -15,6 +15,7 @@ public class VisualComposerItemData public EventHandler MoveEvent { get; set; } public EventHandler LeaveEvent { get; set; } + public EventHandler UpEvent { get; set; } private ITwinElement? _twinElement; From 4cf889aeb7763524dc20481e446f72291988ff3d Mon Sep 17 00:00:00 2001 From: Branko Zachemsky Date: Wed, 13 May 2026 09:11:46 +0200 Subject: [PATCH 2/6] plus is on left add bigger gap --- .../Components/AccordionComponent.razor | 26 +++++++++---------- .../Components/VisualComposerContainer.razor | 16 +++--------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/base/src/AXOpen.VisualComposer/Components/AccordionComponent.razor b/src/base/src/AXOpen.VisualComposer/Components/AccordionComponent.razor index 8706e8900..5dbc0fd30 100644 --- a/src/base/src/AXOpen.VisualComposer/Components/AccordionComponent.razor +++ b/src/base/src/AXOpen.VisualComposer/Components/AccordionComponent.razor @@ -1,22 +1,21 @@ -@using AXSharp.Connector; +@using AXSharp.Connector; @using System.Reflection @using AXSharp.Connector.ValueTypes
-
- +
+ @if (!Parent.CurrentView.IsWatchTable) + { +
+ +
+ }

@SourceTwinElement.Symbol

- @if (!Parent.CurrentView.IsWatchTable) - { -
- -
- }
@if (IsExpanded) { @@ -32,13 +31,12 @@ else if (kid is ITwinPrimitive prim) { -
- -

@prim.Symbol

-

@(((dynamic)kid).Cyclic)

+
+

@prim.Symbol

+

@(((dynamic)kid).Cyclic)

} } @@ -55,4 +53,4 @@ public VisualComposerContainer Parent { get; set; } private bool IsExpanded { get; set; } = false; -} \ No newline at end of file +} diff --git a/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor b/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor index e8658ef5e..aaef37bb4 100644 --- a/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor +++ b/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor @@ -201,24 +201,16 @@ { @if (!CurrentView.IsWatchTable || item is ITwinPrimitive) { -
- @if (item is ITwinObject) - { - - } - else if (item is ITwinPrimitive) - { - - } +
+ + +

@item.Symbol

@if (item is ITwinPrimitive) {

@(((dynamic)item).Cyclic)

} - - -
} } From 08c7a4a5873fdd7144bbcdc176f2ce88606d88d7 Mon Sep 17 00:00:00 2001 From: Branko Zachemsky Date: Wed, 13 May 2026 09:45:58 +0200 Subject: [PATCH 3/6] caching extended option in controller objects --- .../Components/VisualComposerContainer.razor | 8 ++-- .../VisualComposerContainer.razor.cs | 42 ++++++++++++++++++- .../SerializableControllerObjectsOptions.cs | 27 ++++++++++++ 3 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 src/base/src/AXOpen.VisualComposer/Serializing/SerializableControllerObjectsOptions.cs diff --git a/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor b/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor index aaef37bb4..f54aa17c2 100644 --- a/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor +++ b/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor @@ -218,8 +218,8 @@
diff --git a/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor.cs b/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor.cs index 0c707a631..45b7cc5f4 100644 --- a/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor.cs +++ b/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor.cs @@ -1,4 +1,4 @@ -using AngleSharp.Dom; +using AngleSharp.Dom; using AXOpen.VisualComposer.Components.VisualComposerItem; using AXOpen.VisualComposer.Serializing; using AXSharp.Connector; @@ -61,6 +61,8 @@ public partial class VisualComposerContainer private double _optionsMoveRight { get; set; } = 15; private bool _customPresentation { get; set; } = false; + private const string OptionsStorageKey = "VisualComposer_ControllerObjectsOptions"; + // Watch table filtering and sorting private string? _watchTableFilter { get; set; } = null; private bool? _watchTableSortAscending { get; set; } = null; @@ -826,6 +828,42 @@ private string GetBgColor(SaveLocationType location) return ""; } + private async Task LoadOptionsAsync() + { + var saved = await LocalStorage.LoadAsync(_protectedLocalStorage, OptionsStorageKey); + if (saved != null) + { + _options._left = saved.Left; + _options._top = saved.Top; + _options._transform = Types.TransformType.FromString(saved.Transform) ?? Types.TransformType.TopCenter; + _options._presentation = saved.Presentation; + _options._width = saved.Width; + _options._height = saved.Height; + _options._zIndex = saved.ZIndex; + _options._scale = saved.Scale; + _options._rotate = saved.Rotate; + _options._roles = saved.Roles; + _options._presentationTemplate = saved.PresentationTemplate; + _options._background = saved.Background; + _options._backgroundColorLight = saved.BackgroundColorLight; + _options._backgroundColorDark = saved.BackgroundColorDark; + _options._pollingInterval = saved.PollingInterval; + + _optionsMove = saved.OptionsMove; + _optionsMoveDirection = saved.OptionsMoveDirection; + _optionsMoveBottom = saved.OptionsMoveBottom; + _optionsMoveRight = saved.OptionsMoveRight; + _customPresentation = saved.CustomPresentation; + } + } + + private async Task SaveOptionsAsync() + { + var data = new SerializableControllerObjectsOptions(_options, _optionsMove, _optionsMoveDirection, _optionsMoveBottom, _optionsMoveRight, _customPresentation); + + await LocalStorage.SaveAsync(_protectedLocalStorage, OptionsStorageKey, data); + } + private void ToggleSort() { if (_watchTableSortAscending == null) @@ -866,4 +904,4 @@ public enum SaveLocationType Local } } -} \ No newline at end of file +} diff --git a/src/base/src/AXOpen.VisualComposer/Serializing/SerializableControllerObjectsOptions.cs b/src/base/src/AXOpen.VisualComposer/Serializing/SerializableControllerObjectsOptions.cs new file mode 100644 index 000000000..570ade22e --- /dev/null +++ b/src/base/src/AXOpen.VisualComposer/Serializing/SerializableControllerObjectsOptions.cs @@ -0,0 +1,27 @@ +using AXOpen.VisualComposer.Components.VisualComposerItem; + +namespace AXOpen.VisualComposer.Serializing +{ + public class SerializableControllerObjectsOptions : SerializableItem + { + public bool OptionsMove { get; set; } + public int OptionsMoveDirection { get; set; } + public double OptionsMoveBottom { get; set; } + public double OptionsMoveRight { get; set; } + public bool CustomPresentation { get; set; } + + public SerializableControllerObjectsOptions() + { + + } + + public SerializableControllerObjectsOptions(VisualComposerItemData visualComposerItemData, bool optionsMove, int optionsMoveDirection, double optionsMoveBottom, double optionsMoveRight, bool customPresentation) : base(visualComposerItemData) + { + OptionsMove = optionsMove; + OptionsMoveDirection = optionsMoveDirection; + OptionsMoveBottom = optionsMoveBottom; + OptionsMoveRight = optionsMoveRight; + CustomPresentation = customPresentation; + } + } +} From 64b12613e440bae070ecb603d25b650ea905d8e6 Mon Sep 17 00:00:00 2001 From: Branko Zachemsky Date: Wed, 13 May 2026 10:33:41 +0200 Subject: [PATCH 4/6] Add filter mode --- .../Components/VisualComposerContainer.razor | 6 +++ .../VisualComposerContainer.razor.cs | 38 ++++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor b/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor index f54aa17c2..77fc2f296 100644 --- a/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor +++ b/src/base/src/AXOpen.VisualComposer/Components/VisualComposerContainer.razor @@ -178,6 +178,12 @@