From 21fe1c6264ca091880296287c674b858e260fe93 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Mon, 15 Sep 2025 09:38:14 +0200 Subject: [PATCH 1/6] * small fixes which were inspired by first Retrieval demo --- src/AasxCsharpLibrary/AdminShellUtil.cs | 12 + .../Extensions/ExtendEnvironment.cs | 14 ++ src/AasxIntegrationBase/LogInstance.cs | 10 + .../MainWindow.CommandBindings.cs | 4 +- src/AasxPackageExplorer/MainWindow.xaml.cs | 21 +- .../options-debug.MIHO.json | 9 +- src/AasxPackageLogic/AasxScript.cs | 2 +- src/AasxPackageLogic/ExplorerMenuFactory.cs | 3 + src/AasxPackageLogic/IExecuteMainCommand.cs | 2 +- .../MainWindowAnyUiDialogs.cs | 124 +++++++++- .../PackageCentral/PackageCentral.cs | 5 + .../PackageContainerHttpRepoSubset.cs | 222 ++++++++++++------ .../PackageContainerListBase.cs | 8 +- .../PackageContainerListHttpRestRepository.cs | 24 ++ src/AasxWpfControlLibrary/AnyUiWpf.cs | 6 + .../Flyouts/SelectFromListFlyout.xaml.cs | 98 ++++++++ .../PackageContainerListOfListControl.xaml.cs | 26 +- src/AnyUi/AnyUiBase.cs | 2 + src/AnyUi/AnyUiDialogueDataBase.cs | 1 + src/AnyUi/AnyUiSmallWidgetToolkit.cs | 5 +- 20 files changed, 509 insertions(+), 89 deletions(-) diff --git a/src/AasxCsharpLibrary/AdminShellUtil.cs b/src/AasxCsharpLibrary/AdminShellUtil.cs index 6a22a2c3e..e9d89ac53 100644 --- a/src/AasxCsharpLibrary/AdminShellUtil.cs +++ b/src/AasxCsharpLibrary/AdminShellUtil.cs @@ -1099,6 +1099,18 @@ public static void SetFieldLazyValue( return; } + // list of strings is vary common, therefore a special case is justified + if (tut?.IsGenericType == true + && tut.GetGenericTypeDefinition() == typeof(List<>) + && tut.GetGenericArguments().Count() == 1 + && tut.GetGenericArguments()[0] == typeof(string) + && value is IEnumerable vstr2) + { + var lststr = vstr2.ToList(); + f.SetValue(obj, lststr); + return; + } + // 2024-01-04: make function more suitable for switch (Type.GetTypeCode(tut)) { diff --git a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs index 4e1a154ea..f2c51227e 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs @@ -417,6 +417,20 @@ public static IEnumerable AllConceptDescriptions(this AasCo yield return cd; } + /// + /// Enumerates any Identifiables in the Environment. Will not return null. + /// Is tolerant, if the list is null. + /// + public static IEnumerable AllIdentifiables(this AasCore.Aas3_0.IEnvironment env) + { + foreach (var aas in env.AllAssetAdministrationShells()) + yield return aas; + foreach (var sm in env.AllSubmodels()) + yield return sm; + foreach (var cd in env.AllConceptDescriptions()) + yield return cd; + } + /// /// Returns the number of AssetAdministrationShells. /// Is tolerant, if the list is null. diff --git a/src/AasxIntegrationBase/LogInstance.cs b/src/AasxIntegrationBase/LogInstance.cs index 483db6f7b..37a6d2005 100644 --- a/src/AasxIntegrationBase/LogInstance.cs +++ b/src/AasxIntegrationBase/LogInstance.cs @@ -250,12 +250,18 @@ public void Append(StoredPrint sp) /// public int NumberErrors = 0; + /// + /// Incremented for each blue (important) message + /// + public int NumberBlues = 0; + /// /// Clears errors /// public void ClearNumberErrors() { NumberErrors = 0; + NumberBlues = 0; } #region //////// Append to Log @@ -282,6 +288,10 @@ public void Info(string msg, params object[] args) /// public void Info(StoredPrint.Color color, string msg, params object[] args) { + if (color == StoredPrint.Color.Red) + NumberErrors++; + if (color == StoredPrint.Color.Blue) + NumberBlues++; var p = new StoredPrint(color, String.Format(msg, args)); Append(p); } diff --git a/src/AasxPackageExplorer/MainWindow.CommandBindings.cs b/src/AasxPackageExplorer/MainWindow.CommandBindings.cs index 35959ae16..221576a45 100644 --- a/src/AasxPackageExplorer/MainWindow.CommandBindings.cs +++ b/src/AasxPackageExplorer/MainWindow.CommandBindings.cs @@ -97,7 +97,7 @@ public async Task CommandExecution_RedrawAllAsync() /// public bool ScriptModeShutdown = false; - public async Task ExecuteMainMenuCommand(string menuItemName, params object[] args) + public async Task ExecuteMainMenuCommand(string menuItemName, bool scriptMode, params object[] args) { if (menuItemName?.HasContent() != true) { @@ -123,7 +123,7 @@ public async Task ExecuteMainMenuCommand(string menuItemName, params object var ticket = new AasxMenuActionTicket() { MenuItem = mi, - ScriptMode = true, + ScriptMode = scriptMode, ArgValue = new AasxMenuArgDictionary() }; diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 36272df42..b016f4be3 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -1068,6 +1068,7 @@ private async void Window_Loaded(object sender, RoutedEventArgs e) // Package Central starting .. PackageCentral.CentralRuntimeOptions = UiBuildRuntimeOptionsForMainAppLoad(); + PackageCentral.ExecuteMainCommand = this; // start with empty repository and load, if given by options RepoListControl.PackageCentral = PackageCentral; @@ -1285,12 +1286,16 @@ private async void Window_Loaded(object sender, RoutedEventArgs e) }; // what happens on a file drop -> dispatch - RepoListControl.FileDrop += (senderList, fr, files) => + RepoListControl.FileDrop += async (senderList, fr, files) => { // access if (files == null || files.Length < 1) return; + // hand over the full list for potential bulk adding + if (fr != null) + await fr.AddByListOfAasxFn(PackageCentral, files); + // more than one? foreach (var fn in files) { @@ -1387,6 +1392,11 @@ private async void Window_Loaded(object sender, RoutedEventArgs e) if (container == null) Log.Singleton.Error($"Failed to auto-load AASX from {location}"); + else if (container.Env?.AasEnv != null && container.Env.AasEnv.AllIdentifiables().Count() < 1) + { + Log.Singleton.Info(StoredPrint.Color.Blue, + $"Auto-load request seem to result in empty data! Auto-load location: {location}"); + } else UiLoadPackageWithNew(PackageCentral.MainItem, takeOverContainer: container, onlyAuxiliary: false, indexItems: true, @@ -1590,14 +1600,21 @@ private void MainTimer_HandleLogMessages() // always tell the errors var ne = Log.Singleton.NumberErrors; + var nb = Log.Singleton.NumberBlues; if (ne > 0) { LabelNumberErrors.Content = "Errors: " + ne; LabelNumberErrors.Background = new SolidColorBrush(Color.FromRgb(0xd4, 0x20, 0x44)); // #D42044 } else + if (nb > 0) + { + LabelNumberErrors.Content = "Major: " + nb; + LabelNumberErrors.Background = Brushes.LightBlue; + } + else { - LabelNumberErrors.Content = "No errors"; + LabelNumberErrors.Content = "No attention"; LabelNumberErrors.Background = Brushes.White; } } diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index 45645dc4f..d3aba831c 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -5,6 +5,7 @@ "NoHints": true /* This file shall be loaded as main package at start of application | Arg: */, "AasxToLoad": "http://localhost:8081/shells/aHR0cHM6Ly9hZG1pbi1zaGVsbC1pby9pZHRhL2Fhcy9Qcm9kdWN0Q2hhbmdlTm90aWZpY2F0aW9ucy8xLzA" + // "AasxToLoad": "http://localhost:8081/shells/aHR1cHM6Ly9hZG1pbi1zaGVsbC1pby9pZHRhL2Fhcy9Qcm9kdWN0Q2hhbmdlTm90aWZpY2F0aW9ucy8xLzA" // "AasxToLoad": "C:\\Users\\Micha\\Desktop\\test-smt-on-basyx\\IDTA_02036-1-0_SMT_ProductChangeNotification_Draft_v35_manual_fixes.aasx" /* This file shall be loaded as aux package at start of application | Arg: */, "AuxToLoad": null @@ -31,14 +32,14 @@ "Method": "Ask", // None, Ask, Basic, CertificateStore, File, InteractiveEntry, Secret "Username": "Admin", "Password": "", - // "Password": "SuperSafePasswd", + // "Password": "superSafePasswd", // "AuthServer": "", "AuthServer": "https://www.admin-shell-io.com/50001/.well-known/openid-configuration", // for rest of authentication // "AuthServer": "https://credential.aas-voyager.com/token", // for secret based OpenID Connect "CertFile": "", "CertPassword": "", - "CertPick": "GEANT", - // "CertPick" : "", + // "CertPick": "GEANT", + "CertPick" : "", // "SecretId": "", "SecretId": "aorzelski@phoenixcontact.com", // "SecretValue": "", @@ -119,7 +120,7 @@ /* If other then -1, then time in ms for the splash window to stay on the screen. | Arg: */, "SplashTime": 0 /* Fraction of main window with dedicated to left column of screen when resizing window. | Arg: Percent 0-100.0 */, - "PercentageLeftColumn": 12.0 + "PercentageLeftColumn": 15.0 /* Fraction of main window with dedicated to right column of screen (content section) when resizing window. | Arg: Percent 0-100.0 */, "PercentageRightColumn": 60.0 /* If true, use always internal browser */, diff --git a/src/AasxPackageLogic/AasxScript.cs b/src/AasxPackageLogic/AasxScript.cs index 7856da498..a2bde07a7 100644 --- a/src/AasxPackageLogic/AasxScript.cs +++ b/src/AasxPackageLogic/AasxScript.cs @@ -602,7 +602,7 @@ public void StartEnginBackground( if (script?.HasContent() != true) return; if (_logLevel >= 1) - Log.Singleton.Info(StoredPrint.Color.Blue, "Starting script with len {0} hash 0x{1:x}", + Log.Singleton.Info("Starting script with len {0} hash 0x{1:x}", script.Length, script.GetHashCode()); if (_worker != null) { diff --git a/src/AasxPackageLogic/ExplorerMenuFactory.cs b/src/AasxPackageLogic/ExplorerMenuFactory.cs index 8061db1a3..59e4dbbd2 100644 --- a/src/AasxPackageLogic/ExplorerMenuFactory.cs +++ b/src/AasxPackageLogic/ExplorerMenuFactory.cs @@ -126,6 +126,9 @@ public static AasxMenu CreateMainMenu() .AddWpfBlazor(name: "ApiUploadAssistant", header: "Upload assistant …", args: new AasxMenuListOfArgDefs() .AddFromReflection(new PackageContainerHttpRepoSubset.UploadAssistantJobRecord())) + .AddWpfBlazor(name: "ApiUploadFiles", header: "Upload files …", + args: new AasxMenuListOfArgDefs() + .AddFromReflection(new PackageContainerHttpRepoSubset.UploadFilesJobRecord())) .AddWpf(name: "CreateRepoFromApi", header: "Create (local) file repository from API base …", args: new AasxMenuListOfArgDefs() .AddFromReflection(new PackageContainerHttpRepoSubset.ConnectExtendedRecord())) diff --git a/src/AasxPackageLogic/IExecuteMainCommand.cs b/src/AasxPackageLogic/IExecuteMainCommand.cs index 75ff7e6c4..fafa6244d 100644 --- a/src/AasxPackageLogic/IExecuteMainCommand.cs +++ b/src/AasxPackageLogic/IExecuteMainCommand.cs @@ -8,6 +8,6 @@ namespace AasxPackageLogic { public interface IExecuteMainCommand { - Task ExecuteMainMenuCommand(string menuItemName, params object[] args); + Task ExecuteMainMenuCommand(string menuItemName, bool scriptMode, params object[] args); } } diff --git a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs index 01dd0e327..9dc9607fb 100644 --- a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs +++ b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs @@ -611,13 +611,129 @@ await val.PerformDialogue(ticket, DisplayContext, // make distinct (default comparer should work on list of Identifiables) var idfs2 = idfs.Distinct(); - // call assistant - await PackageContainerHttpRepoSubset.PerformUploadAssistant( + // call assistant in 3 steps + // get the record + var recordJob = new UploadAssistantJobRecord(); + ticket?.ArgValue?.PopulateObjectFromArgs(recordJob); + + // ask for the record + if (!await PackageContainerHttpRepoSubset.PerformUploadAssistantDialogue( ticket, DisplayContext, "Upload current to Registry or Repository", + recordJob, + idfs: idfs2)) + return; + + // now, perform + await PackageContainerHttpRepoSubset.PerformUploadAssistant( + ticket, DisplayContext, + recordJob, PackageCentral.Main, - idfs2, - PackageCentral.CentralRuntimeOptions); + idfs: idfs2, + runtimeOptions: PackageCentral.CentralRuntimeOptions); + } + catch (Exception ex) + { + LogErrorToTicket(ticket, ex, "when performing api upload assistant"); + } + } + + if (cmd == "apiuploadfiles") + { + // start + ticket.StartExec(); + + //do + try + { + // get the (extended) record + var recordJob = new UploadFilesJobRecord(); + ticket?.ArgValue?.PopulateObjectFromArgs(recordJob); + + // get the file list from the record? + var fns = recordJob.Filenames; + + if (ticket?.ScriptMode != true) + { + // define dialogue and map presets into dialogue items + var uc = new AnyUiDialogueDataSelectFromList( + "Select files to be uploaded .."); + uc.SelectFiles = true; + uc.ListOfItems = new AnyUiDialogueListItemList(recordJob.Filenames?.Select( + (s) => new AnyUiDialogueListItem() { Text = s, Tag = s } )) + ?? new AnyUiDialogueListItemList(); + + // perform dialogue + var res = await DisplayContext.StartFlyoverModalAsync(uc); + if (!res || !uc.Result) + return; + + fns = uc.ListOfItems.Select((it) => it.Tag as string).ToList(); + } + + // any files + if (fns.Count < 1) + { + LogErrorToTicket(ticket, "No files specified. Aborting!"); + return; + } + + // ask for the record + if (!await PackageContainerHttpRepoSubset.PerformUploadAssistantDialogue( + ticket, DisplayContext, + "Upload current to Registry or Repository", + recordJob, + idfs: null)) + return; + + // over the single files + foreach (var fn in fns) + { + // check extension + var ext = System.IO.Path.GetExtension(fn).ToLower(); + if (ext == ".aasx" || ext == ".xml" || ext == ".json") + { + // log + Log.Singleton.Info("Loading package file: {0}", fn); + + // open + try + { + // load + var env = new AdminShellPackageFileBasedEnv(fn, indirectLoadSave: false); + if (env?.AasEnv == null) + { + LogErrorToTicket(ticket, $"Error reading file contents! Skipping file: {fn}"); + continue; + } + + // basically make a list of all Identifiables + var idfs2 = new List(); + idfs2.AddRange(env.AasEnv.AllIdentifiables()); + + // now, perform + await PackageContainerHttpRepoSubset.PerformUploadAssistant( + ticket, DisplayContext, + recordJob, + env, + idfs: idfs2, + doNotAskForRowsToUpload: true, + runtimeOptions: PackageCentral.CentralRuntimeOptions); + } + catch (Exception ex) + { + throw new PackageContainerException( + $"While opening aasx {fn} from source {this.ToString()} " + + $"at {AdminShellUtil.ShortLocation(ex)} gave: {ex.Message}"); + } + + } + else + { + LogErrorToTicket(ticket, $"Extension/ file type could not be recognised! Skipping file: {fn}"); + } + + } } catch (Exception ex) { diff --git a/src/AasxPackageLogic/PackageCentral/PackageCentral.cs b/src/AasxPackageLogic/PackageCentral/PackageCentral.cs index 848f5b0f5..625de1280 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageCentral.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageCentral.cs @@ -233,6 +233,11 @@ public enum Selector { Main, MainAux, MainAuxFileRepo } /// public PackCntChangeEventHandler ChangeEventHandler = null; + /// + /// Allow to execute certain main commands, e.g. from container + /// + public IExecuteMainCommand ExecuteMainCommand = null; + // // Container members // diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index bfa5e2456..ab49d4af6 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -1151,7 +1151,7 @@ protected static async Task LoadFromSourceInternalAsyn // get the record data (as supplemental infos to the fullItemLocation) var record = (containerOptions as PackageContainerHttpRepoSubsetOptions)?.Record; - // invalidate cursor data (as a new request is about to be started) + // invalidate cursor data (as a new request is about to be started) string cursor = null; // TODO: very long function, needs to be refactored @@ -1411,6 +1411,13 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( lambdaReportAll(numAAS, numSM, numCD, ++numDiv); + if (isAllAAS) + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.AllAas); + if (isAllSM) + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.AllSM); + if (isAllCD) + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.AllCD); + try { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); @@ -1676,6 +1683,8 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); var aas = Jsonization.Deserialize.AssetAdministrationShellFrom(node); + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleAas); + record.AasId = aas.Id; lambdaReportAll(++numAAS, numSM, numCD, numDiv); trackLoadedIdentifiables?.Add(aas); if (prepAas.AddIfNew(aas, new AasIdentifiableSideInfo() @@ -1719,6 +1728,8 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); var sm = Jsonization.Deserialize.SubmodelFrom(node); + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleSM); + record.SmId = sm.Id; lambdaReportAll(numAAS, ++numSM, numCD, numDiv); trackLoadedIdentifiables?.Add(sm); if (prepSM.AddIfNew(sm, new AasIdentifiableSideInfo() @@ -1762,6 +1773,8 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); var cd = Jsonization.Deserialize.ConceptDescriptionFrom(node); + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleCD); + record.CdId = cd.Id; lambdaReportAll(numAAS, numSM, ++numCD, numDiv); trackLoadedIdentifiables?.Add(cd); if (prepCD.AddIfNew(cd, new AasIdentifiableSideInfo() @@ -2744,6 +2757,10 @@ public static async Task PerformConnectExtendedDialogue( if (ticket?.ScriptMode == true) return true; + // some memory + AnyUiUIElement testElem1 = null; + int heightQueryEditor = 120; + // ok, go on .. var uc = new AnyUiDialogueDataModalPanel(caption); uc.ActivateRenderPanel(record, @@ -3004,7 +3021,7 @@ public static async Task PerformConnectExtendedDialogue( if ((scope & ConnectExtendedScope.Query) > 0) { - // Query + // Query check box AnyUiUIElement.RegisterControl( helper.Set( helper.AddSmallCheckBoxTo(g, row, 0, @@ -3021,13 +3038,58 @@ public static async Task PerformConnectExtendedDialogue( return new AnyUiLambdaActionModalPanelReRender(uc); }); - helper.AddSmallLabelTo(g, row + 1, 0, content: "Query:", + // Complex query line + helper.AddSmallLabelTo(g, row + 1, 0, content: "Preset/ Size:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + var g2 = helper.AddSmallGridTo(g, row + 1, 1, 1, 3, new[] { "*", "#", "#" }); + + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallComboBoxTo(g2, 0, 0, + isEditable: true, + // items: Options.Curr.BaseAddresses?.ToArray(), + items: Options.Curr.BaseAddresses? + .Concat(Options.Curr.KnownEndpoints?.Select((o) => o.BaseAddress)).ToArray(), + text: "" + record.BaseAddress, + margin: new AnyUiThickness(0, 0, 0, 0), + padding: new AnyUiThickness(0, 0, 0, 0), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch)), + (s) => { record.BaseAddress = s; }); + + AnyUiUIElement.RegisterControl( + helper.AddSmallButtonTo(g2, 0, 1, + margin: new AnyUiThickness(5, 0, 5, 0), + content: " \uff0b ", + modalDialogStyle: true, + foreground: AnyUiBrushes.White, + toolTip: "Enlarges the size of the query text editor"), + setValue: (o) => { + heightQueryEditor += 50; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); + + var b2 = AnyUiUIElement.RegisterControl( + helper.AddSmallButtonTo(g2, 0, 2, + margin: new AnyUiThickness(5, 0, 5, 0), + content: " \u2212 ", + modalDialogStyle: true, + foreground: AnyUiBrushes.White, + toolTip: "Decreases the size of the query text editor"), + setValue: (o) => { + heightQueryEditor = Math.Max(120, heightQueryEditor - 50); + return new AnyUiLambdaActionModalPanelReRender(uc); + }); + + helper.AddSmallLabelTo(g, row + 2, 0, content: "Query:", verticalAlignment: AnyUiVerticalAlignment.Top, verticalContentAlignment: AnyUiVerticalAlignment.Top); - AnyUiUIElement.SetStringFromControl( + // Query text itself + testElem1 = AnyUiUIElement.SetStringFromControl( helper.Set( - helper.AddSmallTextBoxTo(g, row + 1, 1, + helper.AddSmallTextBoxTo(g, row + 2, 1, text: $"{record.QueryScript}", verticalAlignment: AnyUiVerticalAlignment.Stretch, verticalContentAlignment: AnyUiVerticalAlignment.Top, @@ -3035,10 +3097,10 @@ public static async Task PerformConnectExtendedDialogue( fontSize: 0.7, multiLine: true), horizontalAlignment: AnyUiHorizontalAlignment.Stretch, - minHeight: 120), + minHeight: heightQueryEditor), (s) => { record.QueryScript = s; }); - row += 2; + row += 3; } if ((scope & ConnectExtendedScope.Filters) > 0) @@ -3389,33 +3451,27 @@ public class UploadAssistantJobRecord public bool RegisterAas = false; } + public class UploadFilesJobRecord : UploadAssistantJobRecord + { + [AasxMenuArgument(help: "List of file names to upload.")] + public List Filenames = new List(); + } - - public static async Task PerformUploadAssistant( + public static async Task PerformUploadAssistantDialogue( AasxMenuActionTicket ticket, AnyUiContextBase displayContext, string caption, - AdminShellPackageEnvBase packEnv, - IEnumerable idfs, - PackCntRuntimeOptions runtimeOptions = null, - PackageContainerListBase containerList = null) + UploadAssistantJobRecord recordJob, + IEnumerable idfs) { // access - if (displayContext == null || caption?.HasContent() != true || packEnv == null || idfs == null) + if (displayContext == null || caption?.HasContent() != true || recordJob == null) return false; - // build statistics - var numAas = idfs.Where((idf) => idf is Aas.IAssetAdministrationShell).Count(); - var numSm = idfs.Where((idf) => idf is Aas.ISubmodel).Count(); - var numCD = idfs.Where((idf) => idf is Aas.IConceptDescription).Count(); - // // Screen 1 : Job attributes // - - var recordJob = new UploadAssistantJobRecord(); - ticket?.ArgValue?.PopulateObjectFromArgs(recordJob); - + var ucJob = new AnyUiDialogueDataModalPanel(caption); ucJob.ActivateRenderPanel(recordJob, disableScrollArea: false, @@ -3435,21 +3491,29 @@ public static async Task PerformUploadAssistant( // dynamic rows int row = 0; - // Statistics - helper.AddSmallLabelTo(g, row, 0, content: "Available for upload:"); + // build statistics + if (idfs != null) + { + var numAas = idfs.Where((idf) => idf is Aas.IAssetAdministrationShell).Count(); + var numSm = idfs.Where((idf) => idf is Aas.ISubmodel).Count(); + var numCD = idfs.Where((idf) => idf is Aas.IConceptDescription).Count(); - helper.AddSmallLabelTo(g, row, 1, content: "# of AAS: " + numAas); - helper.AddSmallLabelTo(g, row + 1, 1, content: "# of Submodel: " + numSm); - helper.AddSmallLabelTo(g, row + 2, 1, content: "# of ConceptDescription: " + numCD); + // show statistics + helper.AddSmallLabelTo(g, row, 0, content: "Available for upload:"); - row += 3; + helper.AddSmallLabelTo(g, row, 1, content: "# of AAS: " + numAas); + helper.AddSmallLabelTo(g, row + 1, 1, content: "# of Submodel: " + numSm); + helper.AddSmallLabelTo(g, row + 2, 1, content: "# of ConceptDescription: " + numCD); - // separation - helper.AddSmallBorderTo(g, row, 0, - borderThickness: new AnyUiThickness(0.5), borderBrush: AnyUiBrushes.White, - colSpan: 2, - margin: new AnyUiThickness(0, 0, 0, 20)); - row++; + row += 3; + + // separation + helper.AddSmallBorderTo(g, row, 0, + borderThickness: new AnyUiThickness(0.5), borderBrush: AnyUiBrushes.White, + colSpan: 2, + margin: new AnyUiThickness(0, 0, 0, 20)); + row++; + } // Base address + Type helper.AddSmallLabelTo(g, row, 0, content: "Base address:", @@ -3563,8 +3627,26 @@ public static async Task PerformUploadAssistant( return false; } + // ok + return true; + } + + public static async Task PerformUploadAssistant( + AasxMenuActionTicket ticket, + AnyUiContextBase displayContext, + UploadAssistantJobRecord record, + AdminShellPackageEnvBase packEnv, + IEnumerable idfs, + bool doNotAskForRowsToUpload = false, + PackCntRuntimeOptions runtimeOptions = null, + PackageContainerListBase containerList = null) + { + // access + if (displayContext == null || packEnv == null || idfs == null) + return false; + // some obvious checks - if (recordJob.BaseAddress?.HasContent() != true) + if (record.BaseAddress?.HasContent() != true) { Log.Singleton.Error("No BaseAddress given. Aborting!"); return false; @@ -3597,8 +3679,8 @@ public static async Task PerformUploadAssistant( // build list var rows = sorted .Where((idf) => ( - (!(idf is Aas.ISubmodel) || recordJob.IncludeSubmodels) - && (!(idf is Aas.IConceptDescription) || recordJob.IncludeCDs) + (!(idf is Aas.ISubmodel) || record.IncludeSubmodels) + && (!(idf is Aas.IConceptDescription) || record.IncludeCDs) )) .Select((idf) => new AnyUiDialogueDataGridRow() { @@ -3618,10 +3700,10 @@ public static async Task PerformUploadAssistant( // in order to re-use sockets BaseUriDict baseUri = null; HttpClient client = null; - if (recordJob.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) + if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) { // Note: it seems to be also possible to create an HttpClient with "" as BaseAddress and pass Host via URL!! - baseUri = new BaseUriDict(recordJob.BaseAddress); + baseUri = new BaseUriDict(record.BaseAddress); // TODO client = PackageHttpDownloadUtil.CreateHttpClient(baseUri.GetBaseUriForAasRepo(), runtimeOptions, containerList); } @@ -3650,7 +3732,7 @@ public static async Task PerformUploadAssistant( workerCheck.DoWork += async (sender, e) => { // ask server - if (recordJob.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) + if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) { var numRes = await PackageHttpDownloadUtil.DownloadListOfIdentifiables( client, @@ -3709,7 +3791,7 @@ public static async Task PerformUploadAssistant( { // status row.Cells[4] = "-"; - if (recordJob.OverwriteIfExist) + if (record.OverwriteIfExist) row.Cells[4] = "PUT"; row.Cells[5] = (idf.Administration?.ToStringExtended(1)) ?? "-"; lambdaStat(true); @@ -3727,29 +3809,35 @@ public static async Task PerformUploadAssistant( await displayContext.StartFlyoverModalAsync(ucProgExist); // - // Screen 3: show list of elements + // Screen 3: show list of elements? // - var ucSelect = new AnyUiDialogueDataSelectFromDataGrid( - "Select element(s) to be uploaded ..", - maxWidth: 9999); - ucSelect.ColumnDefs = AnyUiListOfGridLength.Parse(new[] { "50:", "1*", "3*", "70:", "70:", "70:" }); - ucSelect.ColumnHeaders = new[] { "Type", "IdShort", "Id", "V.Local", "Action", "V.Server" }; - ucSelect.Rows = rows.ToList(); - ucSelect.EmptySelectOk = true; + IList rowsToUpload = rows; - if (ticket?.ScriptMode != true) + if (!doNotAskForRowsToUpload) { - // if not in script mode, perform dialogue - if (!(await displayContext.StartFlyoverModalAsync(ucSelect))) - return false; - } + var ucSelect = new AnyUiDialogueDataSelectFromDataGrid( + "Select element(s) to be uploaded ..", + maxWidth: 9999); - // translate result items - var rowsToUpload = ucSelect.ResultItems; - if (rowsToUpload == null || rowsToUpload.Count() < 1) - // nothings means: everything! - rowsToUpload = rows; + ucSelect.ColumnDefs = AnyUiListOfGridLength.Parse(new[] { "50:", "1*", "3*", "70:", "70:", "70:" }); + ucSelect.ColumnHeaders = new[] { "Type", "IdShort", "Id", "V.Local", "Action", "V.Server" }; + ucSelect.Rows = rows.ToList(); + ucSelect.EmptySelectOk = true; + + if (ticket?.ScriptMode != true) + { + // if not in script mode, perform dialogue + if (!(await displayContext.StartFlyoverModalAsync(ucSelect))) + return false; + } + + // translate result items + rowsToUpload = ucSelect.ResultItems; + if (rowsToUpload == null || rowsToUpload.Count() < 1) + // nothings means: everything! + rowsToUpload = rows; + } // // Screen 4: make a progress on the upload of Identifiables @@ -3773,7 +3861,7 @@ public static async Task PerformUploadAssistant( workerUpload.DoWork += async (sender, e) => { // ask server - if (recordJob.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) + if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) { // will collect AAS for later registering List uploadedAas = new List(); @@ -3838,7 +3926,7 @@ public static async Task PerformUploadAssistant( // thumbnails (for AAS) // - if (recordJob.IncludeThumbFiles + if (record.IncludeThumbFiles && idf is Aas.IAssetAdministrationShell aas && aas.AssetInformation?.DefaultThumbnail?.Path?.HasContent() == true) { @@ -3922,7 +4010,7 @@ public static async Task PerformUploadAssistant( // attachments (for Submodels) // - if (recordJob.IncludeSupplFiles + if (record.IncludeSupplFiles && idf is Aas.ISubmodel submodel && submodel.SubmodelElements != null) { // Note: the Part 2 PDF says '/', the swagger says '.' @@ -4081,7 +4169,7 @@ await Parallel.ForEachAsync(rowsToUpload, // Register (just not parallel..)? // - if (recordJob.RegisterAas && uploadedAas.Count > 0 + if (record.RegisterAas && uploadedAas.Count > 0 && baseUri.GetBaseUriForBasicDiscovery() != null) { foreach (var aas in uploadedAas) @@ -4115,18 +4203,18 @@ await Parallel.ForEachAsync(rowsToUpload, { runtimeOptions?.Log?.Error("When Put/ Push to Registry/ Repository, {0} element(s) were not uploaded " + "while {1} uploaded ok. Location: {2}", - numNOK, numOK, recordJob.BaseAddress); + numNOK, numOK, record.BaseAddress); } else if (numOK < 1) { runtimeOptions?.Log?.Info(StoredPrint.Color.Blue, "No need of Put/ Push element(s) " + - "to Registry/ Repository. Location {0}", recordJob.BaseAddress); + "to Registry/ Repository. Location {0}", record.BaseAddress); } else { runtimeOptions?.Log?.Info(StoredPrint.Color.Blue, "Successful Put/ Push of {0} element(s) " + "to Registry/ Repository. Location {1}", - numOK, recordJob.BaseAddress); + numOK, record.BaseAddress); } // ok diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs index 94bf833c2..e3186514a 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs @@ -254,7 +254,8 @@ public void AddByAasPackage(PackageCentral packageCentral, AdminShellPackageEnvB } } - public void AddByAasxFn(PackageCentral packageCentral, string fn) + // by default, allow adding single files + public virtual void AddByAasxFn(PackageCentral packageCentral, string fn) { try { @@ -273,6 +274,11 @@ public void AddByAasxFn(PackageCentral packageCentral, string fn) } } + // and "disable" the bulk adding + public virtual async Task AddByListOfAasxFn(PackageCentral packageCentral, IEnumerable fns) + { + await Task.Yield(); + } public virtual void DeletePackageFromServer(PackageContainerRepoItem repoItem) { diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs index 12bbfe085..75fe506c1 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs @@ -231,5 +231,29 @@ public override async Task FindByAssetId(string aid) return null; } + // + // Upload + // + + // make adding a single file "deaf" + public override void AddByAasxFn(PackageCentral packageCentral, string fn) + { + } + + // and "enable" the bulk adding + public override async Task AddByListOfAasxFn(PackageCentral packageCentral, IEnumerable fns) + { + // access + if (packageCentral?.ExecuteMainCommand == null || fns == null) + return; + + // try to trigger UI + await packageCentral.ExecuteMainCommand.ExecuteMainMenuCommand( + "ApiUploadFiles", false, + "BaseType", "Repository", + "BaseAddress", "" + this.Endpoint?.ToString(), + "Filenames", fns); + } + } } diff --git a/src/AasxWpfControlLibrary/AnyUiWpf.cs b/src/AasxWpfControlLibrary/AnyUiWpf.cs index cce73e8ef..4e7461ee5 100644 --- a/src/AasxWpfControlLibrary/AnyUiWpf.cs +++ b/src/AasxWpfControlLibrary/AnyUiWpf.cs @@ -1154,6 +1154,12 @@ private void InitRenderRecs() wpf.Content = cntl.Content; wpf.ToolTip = cntl.ToolTip; + + if (cntl.ModalDialogStyle) + { + wpf.SetResourceReference(Control.StyleProperty, "TranspRoundCorner");; + } + // callbacks wpf.Click += async (sender, e) => { diff --git a/src/AasxWpfControlLibrary/Flyouts/SelectFromListFlyout.xaml.cs b/src/AasxWpfControlLibrary/Flyouts/SelectFromListFlyout.xaml.cs index 742392bff..6b133ff59 100644 --- a/src/AasxWpfControlLibrary/Flyouts/SelectFromListFlyout.xaml.cs +++ b/src/AasxWpfControlLibrary/Flyouts/SelectFromListFlyout.xaml.cs @@ -71,6 +71,96 @@ private void UserControl_Loaded(object sender, RoutedEventArgs e) this.ButtonsPanel.Children.Add(b); } } + + // special case: select files + if (DiaData.SelectFiles) + { + this.ButtonsPanel.Children.Clear(); + + // b1 = Plus + var b1 = new Button() + { + Content = " + ", + Foreground = Brushes.White, + FontSize = 18, + FontWeight = FontWeights.Bold, + Padding = new Thickness(4), + Margin = new Thickness(4) + }; + b1.SetResourceReference(Control.StyleProperty, "TranspRoundCorner"); + DockPanel.SetDock(b1, Dock.Left); + this.ButtonsPanel.Children.Add(b1); + + // b2 = Minus + var b2 = new Button() + { + Content = " \u2212 ", + Foreground = Brushes.White, + FontSize = 18, + FontWeight = FontWeights.Bold, + Padding = new Thickness(4), + Margin = new Thickness(4) + }; + b2.SetResourceReference(Control.StyleProperty, "TranspRoundCorner"); + DockPanel.SetDock(b2, Dock.Left); + this.ButtonsPanel.Children.Add(b2); + + // b3 = OK + var b3 = new Button() + { + Content = "OK", + Foreground = Brushes.White, + FontSize = 18, + Padding = new Thickness(4), + Margin = new Thickness(4) + }; + b3.SetResourceReference(Control.StyleProperty, "TranspRoundCorner"); + b3.Click += ButtonSelect_Click; + this.ButtonsPanel.Children.Add(b3); + + // add actions + b1.Click += (s1, e1) => + { + var dlg = new Microsoft.Win32.OpenFileDialog(); + dlg.CheckFileExists = true; + dlg.Multiselect = true; + if (dlg.ShowDialog() ?? false && dlg.FileNames != null) + foreach (var fn in dlg.FileNames) + { + var fi = new AnyUiDialogueListItem() { Text = fn, Tag = fn }; + DiaData.ListOfItems.Add(fi); + ListBoxPresets.Items.Add("" + fi.Text); + } + }; + + b2.Click += (s2, e2) => + { + var i = ListBoxPresets.SelectedIndex; + if (i >= 0 && i < DiaData.ListOfItems.Count) + { + DiaData.ListOfItems.RemoveAt(i); + ListBoxPresets.Items.RemoveAt(i); + } + }; + + // allow drop + ListBoxPresets.AllowDrop = true; + ListBoxPresets.Drop += (s4, e4) => + { + if (e4.Data.GetDataPresent(DataFormats.FileDrop)) + { + string[] files = (string[])e4.Data.GetData(DataFormats.FileDrop); + foreach (var fn in files) + { + var fi = new AnyUiDialogueListItem() { Text = fn, Tag = fn }; + DiaData.ListOfItems.Add(fi); + ListBoxPresets.Items.Add("" + fi.Text); + } + } + + e4.Handled = true; + }; + } } // @@ -96,6 +186,14 @@ public void LambdaActionAvailable(AnyUiLambdaActionBase la) private bool PrepareResult() { + // special case: file list + if (DiaData.SelectFiles) + { + DiaData.Result = true; + return true; + } + + // normal case var i = ListBoxPresets.SelectedIndex; if (DiaData.ListOfItems != null && i >= 0 && i < DiaData.ListOfItems.Count) { diff --git a/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListOfListControl.xaml.cs b/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListOfListControl.xaml.cs index 4ce4e9a9a..c0ce7a272 100644 --- a/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListOfListControl.xaml.cs +++ b/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListOfListControl.xaml.cs @@ -504,12 +504,24 @@ public async Task CommandBinding_FileRepoAll(Control senderList, PackageContaine } } - if (cmd == "filerepouploadtoapi" && fr is PackageContainerListHttpRestRepository frRepo) { - await _executeMainCommand?.ExecuteMainMenuCommand( - "ApiUploadAssistant", - "BaseType", "Repository", - "BaseAddress", "" + frRepo.Endpoint?.ToString()); + if (cmd == "filerepouploadtoapi" && fr is PackageContainerListHttpRestRepository frRepo) + { + await _executeMainCommand?.ExecuteMainMenuCommand( + "ApiUploadAssistant", true, + "BaseType", "Repository", + "BaseAddress", "" + frRepo.Endpoint?.ToString()); + } + } + + { + if (cmd == "filerepofilestoapi" && fr is PackageContainerListHttpRestRepository frRepo) + { + await _executeMainCommand?.ExecuteMainMenuCommand( + "ApiUploadFiles", false, + "BaseType", "Repository", + "BaseAddress", "" + frRepo.Endpoint?.ToString()); + } } } @@ -610,7 +622,9 @@ private async Task PackageContainerListControl_ButtonClick( || fr is PackageContainerListHttpRestRegistry) { menu.AddAction( - "FileRepoUploadToApi", "Upload to API ..", icon: "\U0001f879"); + "FileRepoUploadToApi", "Upload selected Identifiable to API ..", icon: "\U0001f879"); + menu.AddAction( + "FileRepoFilesToApi", "Upload package files to API ..", icon: "\U0001f879"); } } diff --git a/src/AnyUi/AnyUiBase.cs b/src/AnyUi/AnyUiBase.cs index 5b0645694..2138cc858 100644 --- a/src/AnyUi/AnyUiBase.cs +++ b/src/AnyUi/AnyUiBase.cs @@ -1393,6 +1393,8 @@ public class AnyUiButton : AnyUiContentControl public new string Content = null; public string ToolTip = null; + public bool ModalDialogStyle = false; + /// /// If set to true, Blazor will not create a special action session /// for executing all the implications of the lambda. This special diff --git a/src/AnyUi/AnyUiDialogueDataBase.cs b/src/AnyUi/AnyUiDialogueDataBase.cs index bd7c5d7a7..89788f302 100644 --- a/src/AnyUi/AnyUiDialogueDataBase.cs +++ b/src/AnyUi/AnyUiDialogueDataBase.cs @@ -553,6 +553,7 @@ public class AnyUiDialogueDataSelectFromList : AnyUiDialogueDataBase // in public AnyUiDialogueListItemList ListOfItems = null; public string[] AlternativeSelectButtons = null; + public bool SelectFiles = false; // out public int ResultIndex = -1; diff --git a/src/AnyUi/AnyUiSmallWidgetToolkit.cs b/src/AnyUi/AnyUiSmallWidgetToolkit.cs index 62f06c8bf..032e1a0d3 100644 --- a/src/AnyUi/AnyUiSmallWidgetToolkit.cs +++ b/src/AnyUi/AnyUiSmallWidgetToolkit.cs @@ -366,7 +366,8 @@ public AnyUiButton AddSmallButtonTo( AnyUiGrid g, int row, int col, AnyUiThickness margin = null, AnyUiThickness padding = null, string content = "", AnyUiBrush foreground = null, AnyUiBrush background = null, double? setHeight = null, AnyUiVerticalAlignment? verticalAlignment = null, - bool? directInvoke = null, string toolTip = null) + bool? directInvoke = null, string toolTip = null, + bool? modalDialogStyle = null) { var but = new AnyUiButton(); but.Margin = margin; @@ -385,6 +386,8 @@ public AnyUiButton AddSmallButtonTo( if (directInvoke.HasValue) but.DirectInvoke = directInvoke.Value; but.Content = content; + if (modalDialogStyle != null) + but.ModalDialogStyle = modalDialogStyle.Value; if (toolTip != null) but.ToolTip = toolTip; AnyUiGrid.SetRow(but, row); From 48225dd5826c0671491c9e13a00a62bd50a03a47 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Thu, 18 Sep 2025 09:09:42 +0200 Subject: [PATCH 2/6] * improve queries to standardized API --- .../AasxPackageExplorer.csproj | 3 + .../options-debug.MIHO.json | 6 +- src/AasxPackageExplorer/options-debug.json | 1 + src/AasxPackageExplorer/query-presets.json | 54 ++ src/AasxPackageLogic/Options.cs | 4 + .../PackageContainerHttpRepoSubset.cs | 529 ++++++++++++------ src/AasxWpfControlLibrary/AnyUiWpf.cs | 1 + src/AnyUi/AnyUiSmallWidgetToolkit.cs | 14 +- 8 files changed, 440 insertions(+), 172 deletions(-) create mode 100644 src/AasxPackageExplorer/query-presets.json diff --git a/src/AasxPackageExplorer/AasxPackageExplorer.csproj b/src/AasxPackageExplorer/AasxPackageExplorer.csproj index 518af423b..924eebf84 100644 --- a/src/AasxPackageExplorer/AasxPackageExplorer.csproj +++ b/src/AasxPackageExplorer/AasxPackageExplorer.csproj @@ -81,6 +81,9 @@ + + PreserveNewest + PreserveNewest diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index d3aba831c..6d0e0a499 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -39,7 +39,7 @@ "CertFile": "", "CertPassword": "", // "CertPick": "GEANT", - "CertPick" : "", + "CertPick": "", // "SecretId": "", "SecretId": "aorzelski@phoenixcontact.com", // "SecretValue": "", @@ -76,7 +76,7 @@ /* Maximum parallel write operations, such as HTTP uploads. */, "MaxParallelWriteOps": 1 /* For connecting to repositories/ registry, default pagination limit. */, - "DefaultConnectPageLimit" : 20 + "DefaultConnectPageLimit": 20 /* If not -1, the left of window | Arg: */, "WindowLeft": -1 /* If not -1, the top of window | Arg: */, @@ -113,6 +113,8 @@ "ExtensionsPresetFile": "extension-presets.json" /* Path to JSON file defining data specification presets. | Arg: */, "DataSpecPresetFile": "data-spec-presets.json" + /* Path to JSON file defining query presets. */, + "QueryPresetFile": "query-presets.json" /* Home address of the content browser on startup, on change of AASX | Arg: */, "ContentHome": "https://github.com/eclipse-aaspe/package-explorer/blob/main/README.md" /* If unset, use transparent flyover dialogs, where possible */, diff --git a/src/AasxPackageExplorer/options-debug.json b/src/AasxPackageExplorer/options-debug.json index 2ac617534..eaa230a8a 100644 --- a/src/AasxPackageExplorer/options-debug.json +++ b/src/AasxPackageExplorer/options-debug.json @@ -21,6 +21,7 @@ "ExtensionsPresetFile": "extension-presets.json", "IdentifierKeyValuePairsFile": "local-identifier-presets.json", "DataSpecPresetFile": "data-spec-presets.json", + "QueryPresetFile": "query-presets.json", "ContentHome": "https://github.com/admin-shell/io/blob/master/README.md", "UseFlyovers": true, "SplashTime": 0, diff --git a/src/AasxPackageExplorer/query-presets.json b/src/AasxPackageExplorer/query-presets.json new file mode 100644 index 000000000..a3576fca2 --- /dev/null +++ b/src/AasxPackageExplorer/query-presets.json @@ -0,0 +1,54 @@ +[ + { + "name": "Get all AAS with specific idShort", + "elementType": "AAS", + "query": { + "$condition": { + "$or": [ + { + "$contains": [ + { + "$field": "$aas#idShort" + }, + { + "$strVal": "Test" + } + ] + }, + { + "$lt": [ + { + "$numCast": { + "$field": "$aas#administration.revision" + } + }, + { + "$numVal": 11 + } + ] + }, + { + "$ne": [ + { + "$field": "$aas#assetInformation.globalAssetId" + }, + { + "$field": "$aas#assetInformation.assetKind" + } + ] + }, + { + "$contains": [ + { + "$field": "$aas#submodels.keys[]" + }, + { + "$strVal": "BillOfMaterial" + } + ] + } + ] + } + } + } +] diff --git a/src/AasxPackageLogic/Options.cs b/src/AasxPackageLogic/Options.cs index a059e479f..ae66ac2a9 100644 --- a/src/AasxPackageLogic/Options.cs +++ b/src/AasxPackageLogic/Options.cs @@ -422,6 +422,10 @@ public class OptionsInformation Cmd = "-dataspecpreset", Arg = "")] public string DataSpecPresetFile = null; + [OptionDescription(Description = "Path to JSON file defining query presets.", + Cmd = "-querypreset", Arg = "")] + public string QueryPresetFile = null; + [OptionDescription(Description = "Home address of the content browser on startup, on change of AASX", Cmd = "-contenthome", Arg = "")] public string ContentHome = @"https://github.com/admin-shell/io/blob/master/README.md"; diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index ab49d4af6..9f88d7ac6 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -34,6 +34,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using Newtonsoft.Json; using Newtonsoft.Json.Linq; using RestSharp; +using static AasxPackageLogic.DispEditHelperMiniModules; using Aas = AasCore.Aas3_0; namespace AasxPackageLogic.PackageCentral @@ -160,7 +161,11 @@ public static bool IsValidUriForRepoAllAAS(string location) RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); // prevent the false alarm for looking for single assetIds - if (m.Success && location.Contains("assetId")) + if (m.Success && location.Contains("assetId", StringComparison.InvariantCultureIgnoreCase)) + return false; + + // prevent the false alarm of having a query + if (m.Success && location.Contains("/query/", StringComparison.InvariantCultureIgnoreCase)) return false; // ok? @@ -171,6 +176,11 @@ public static bool IsValidUriForRepoSingleAAS(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/shells/([^?]{1,999})", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + // prevent the false alarm of having a query + if (m.Success && location.Contains("/query/", StringComparison.InvariantCultureIgnoreCase)) + return false; + return m.Success; } @@ -178,6 +188,11 @@ public static bool IsValidUriForRepoAllSubmodel(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/submodels(|/|/?\?(.*))$", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + // prevent the false alarm of having a query + if (m.Success && location.Contains("/query/", StringComparison.InvariantCultureIgnoreCase)) + return false; + return m.Success; } @@ -185,6 +200,11 @@ public static bool IsValidUriForRepoSingleSubmodel(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/submodels/(.{1,999})$", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + // prevent the false alarm of having a query + if (m.Success && location.Contains("/query/", StringComparison.InvariantCultureIgnoreCase)) + return false; + if (m.Success) return true; @@ -196,6 +216,11 @@ public static bool IsValidUriForRepoAllCD(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/concept-descriptions(|/|/?\?(.*))$", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + // prevent the false alarm of having a query + if (m.Success && location.Contains("/query/", StringComparison.InvariantCultureIgnoreCase)) + return false; + return m.Success; } @@ -203,12 +228,17 @@ public static bool IsValidUriForRepoSingleCD(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/concept-descriptions/(.{1,999})$", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + // prevent the false alarm of having a query + if (m.Success && location.Contains("/query/", StringComparison.InvariantCultureIgnoreCase)) + return false; + return m.Success; } public static bool IsValidUriForRepoQuery(string location) { - var m = Regex.Match(location, @"^(http(|s))://(.*?)/graphql(|/|/?\?(.*))$", + var m = Regex.Match(location, @"^(http(|s))://(.*?)/query/(shells|submodels|conceptdescriptions)(|/|/?\?(.*))$", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); return m.Success; } @@ -221,6 +251,11 @@ public static bool IsValidUriForRegistryAllAAS(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/shell-descriptors(|/|/?\?(.*))$", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + // prevent the false alarm of having a query + if (m.Success && location.Contains("/query/", StringComparison.InvariantCultureIgnoreCase)) + return false; + return m.Success; } @@ -228,6 +263,11 @@ public static bool IsValidUriForRegistrySingleAAS(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/shell-descriptors/([^?]{1,999})", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + // prevent the false alarm of having a query + if (m.Success && location.Contains("/query/", StringComparison.InvariantCultureIgnoreCase)) + return false; + return m.Success; } @@ -242,6 +282,11 @@ public static bool IsValidUriForRepoAasByAssetIds(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/shells/{0,1}\?(.*)assetIds=([-A-Za-z0-9_]{1,999})", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + // prevent the false alarm of having a query + if (m.Success && location.Contains("/query/", StringComparison.InvariantCultureIgnoreCase)) + return false; + return m.Success; } @@ -612,7 +657,7 @@ public static Uri BuildUriForRepoSingleCD( /// Note: this is an AASPE specific, proprietary extension. /// This REST ressource does not exist in the official specification! /// - public static Uri BuildUriForRepoQuery(Uri baseUri, string query) + public static Uri BuildUriForRepoQuery(Uri baseUri, string query, string elementName) { // access if (query?.HasContent() != true) @@ -622,7 +667,7 @@ public static Uri BuildUriForRepoQuery(Uri baseUri, string query) // endpoint name is required for the real call. // However, lets store the query as BASE64 query parameter var queryEnc = AdminShellUtil.Base64UrlEncode(query); - var uri = new UriBuilder(CombineUri(baseUri, $"graphql")); + var uri = new UriBuilder(CombineUri(baseUri, $"/query/{elementName}")); uri.Query = $"query={queryEnc}"; return uri.Uri; } @@ -1826,17 +1871,31 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( return null; } - // but, the query needs to be reformatted as JSON - // query = "{ searchSMs(expression: \"\"\"$LOG \"\"\") { url smId } }"; - // query = "{ searchSMs(expression: \"\"\"$LOG filter=or(str_contains(sm.IdShort, \"Technical\"), str_contains(sm.IdShort, \"Nameplate\")) \"\"\") { url smId } }"; - query = query.Replace("\\", "\\\\"); - query = query.Replace("\"", "\\\""); - query = query.Replace("\r", " "); - query = query.Replace("\n", " "); - var jsonQuery = $"{{ \"query\" : \"{query}\" }} "; + // in prior versions of the AASX servers, there was more to re-format + var jsonQuery = ""; + if (false) + { + + // but, the query needs to be reformatted as JSON + // query = "{ searchSMs(expression: \"\"\"$LOG \"\"\") { url smId } }"; + // query = "{ searchSMs(expression: \"\"\"$LOG filter=or(str_contains(sm.IdShort, \"Technical\"), str_contains(sm.IdShort, \"Nameplate\")) \"\"\") { url smId } }"; + query = query.Replace("\\", "\\\\"); + query = query.Replace("\"", "\\\""); + query = query.Replace("\r", " "); + query = query.Replace("\n", " "); + jsonQuery = $"{{ \"query\" : \"{query}\" }} "; + } + else + { + query = query.Replace("\r", " "); + query = query.Replace("\n", " "); + query = Regex.Replace(query, @"\s+", " "); + jsonQuery = query; + } // there are subsequent fetch operations necessary var fetchItems = new List(); + int numTotal = 0, numError = 0; // HTTP POST var statCode = await PackageHttpDownloadUtil.HttpPostRequestToMemoryStream( @@ -1849,93 +1908,57 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( { try { - lambdaReportAll(numAAS, numSM, numCD, ++numDiv); - - var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - if (node["data"]?["searchSMs"] is JsonArray smdata - && smdata.Count >= 1) + var overallNode = System.Text.Json.Nodes.JsonNode.Parse(ms); + + // paging metadata . resultType may overrule the elementType + var qet = record.QueryElementType; + var rT = overallNode["paging_metadata"]?["resultType"]?.ToString().ToLower(); + if (rT == null) // typo in Basyx.milestone07 + rT = overallNode["paging_metadata"]?["resulType"]?.ToString().ToLower(); + if (rT == "aas" || rT == "assetadministrationshell") + qet = "AAS"; + if (rT == "sm" || rT == "submodel") + qet = "Submodel"; + if (rT == "cd" || rT == "conceptdescription") + qet = "ConceptDescription"; + + // try to get result data + JsonArray resArr = null; + if (overallNode["result"] is JsonArray ra + && ra.Count >= 1) + resArr = ra; + + // go on + var resIndex = -1; + foreach (var resNode in resArr) { - foreach (var smrec in smdata) - { - var url = smrec["url"]?.ToString(); - var smId = smrec["smId"]?.ToString(); - if (smId?.HasContent() == true) - fetchItems.Add(new FetchItem() { Type = FetchItemType.SmId, Value = smId }); - else - if (url?.HasContent() == true) - fetchItems.Add(new FetchItem() { Type = FetchItemType.SmUrl, Value = url }); - } - } - - } - catch (Exception ex) - { - runtimeOptions?.Log?.Error(ex, "Parsing graphql result set"); - } - }); - - if (statCode != HttpStatusCode.OK) - { - Log.Singleton.Error("Could not fetch new dynamic elements by graphql. Aborting!"); - Log.Singleton.Error(" POST request was: {0}", jsonQuery); - return null; - } - - // only makes sense, if query returns something - if (fetchItems.Count < 1) - { - Log.Singleton.Info(StoredPrint.Color.Blue, "Query resulted in zero elements, " + - "which could be fetched. Aborting!"); - return null; - } - - // skip items? - if (record.PageSkip > 0) - { - fetchItems.RemoveRange(0, Math.Min(fetchItems.Count, record.PageSkip)); - } - var numItem = 0; - - // TODO: convert to parallel for each async - var dlErrors = 0; - foreach (var fi in fetchItems) - { - // reached end - numItem++; - if (record.PageLimit > 0 && numItem > record.PageLimit) - break; - - // prepare download - Uri loc = null; - if (fi.Type == FetchItemType.SmUrl) - loc = new Uri(fi.Value); - if (fi.Type == FetchItemType.SmId) - loc = BuildUriForRepoSingleSubmodel( - baseUri.GetBaseUriForSmRepo(), fi.Value, encryptIds: true); - - if (loc == null) - continue; - - // download (and skip errors) - try - { - await PackageHttpDownloadUtil.HttpGetToMemoryStream( - null, - sourceUri: loc, - allowFakeResponses: allowFakeResponses, - runtimeOptions: runtimeOptions, - lambdaDownloadDoneOrFail: (code, ms, contentFn) => - { - if (code != HttpStatusCode.OK) - return; - + resIndex++; + numTotal++; try { - var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - var sm = Jsonization.Deserialize.SubmodelFrom(node); - lambdaReportAll(numAAS, ++numSM, numCD, numDiv); - if (fi.Type == FetchItemType.SmUrl || fi.Type == FetchItemType.SmId) + if (qet.Equals("AAS", StringComparison.InvariantCultureIgnoreCase)) + { + var aas = Jsonization.Deserialize.AssetAdministrationShellFrom(resNode); + lambdaReportAll(++numAAS, numSM, numCD, numDiv); + trackLoadedIdentifiables?.Add(aas); + if (prepAas.AddIfNew(aas, new AasIdentifiableSideInfo() + { + IsStub = false, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = aas.Id, + IdShort = aas.IdShort, + QueriedEndpoint = new Uri(fullItemLocation), + DesignatedEndpoint = BuildUriForRepoSingleAAS( + baseUri.GetBaseUriForAasRepo(), aas.Id, encryptIds: true) + })) + { + trackNewIdentifiables?.Add(aas); + } + } + else if (qet.Equals("Submodel", StringComparison.InvariantCultureIgnoreCase)) { + var sm = Jsonization.Deserialize.SubmodelFrom(resNode); + lambdaReportAll(numAAS, ++numSM, numCD, numDiv); trackLoadedIdentifiables?.Add(sm); if (prepSM.AddIfNew(sm, new AasIdentifiableSideInfo() { @@ -1945,29 +1968,62 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( IdShort = sm.IdShort, QueriedEndpoint = new Uri(fullItemLocation), DesignatedEndpoint = BuildUriForRepoSingleSubmodel( - baseUri.GetBaseUriForSmRepo(), sm.Id, encryptIds: true) + baseUri.GetBaseUriForAasRepo(), sm.Id, encryptIds: true) })) { trackNewIdentifiables?.Add(sm); } + } + else if (qet.Equals("ConceptDescription", StringComparison.InvariantCultureIgnoreCase)) + { + var cd = Jsonization.Deserialize.ConceptDescriptionFrom(resNode); + lambdaReportAll(numAAS, numSM, ++numCD, numDiv); + trackLoadedIdentifiables?.Add(cd); + if (prepCD.AddIfNew(cd, new AasIdentifiableSideInfo() + { + IsStub = false, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = cd.Id, + IdShort = cd.IdShort, + QueriedEndpoint = new Uri(fullItemLocation), + DesignatedEndpoint = BuildUriForRepoSingleCD( + baseUri.GetBaseUriForAasRepo(), cd.Id, encryptIds: true) + })) + { + trackNewIdentifiables?.Add(cd); + } } - } - catch (Exception ex) + else + { + runtimeOptions?.Log?.Error($"Trying to deserialize to impossible " + + $"Identifiable type {qet}. Aborting!"); + break; + } + } catch (Exception ex) { - dlErrors++; - runtimeOptions?.Log?.Error(ex, "Parsing individual fetch element of query."); + runtimeOptions?.Log?.Error(ex, + $"Parsing of single Identifiable {qet} with index {resIndex} of query " + + $"result set failed. Skipping."); + numError++; } - }); - } - catch (Exception ex) - { - dlErrors++; - LogInternally.That.CompletelyIgnoredError(ex); - } + } + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing query result set"); + } + }); + + if (statCode != HttpStatusCode.OK) + { + Log.Singleton.Error("Could not fetch new dynamic elements by query. Aborting!"); + Log.Singleton.Error(" POST request was: {0}", jsonQuery); + return null; } - Log.Singleton.Info(StoredPrint.Color.Blue, "Executed GraphQL query. Receiving list of {0} elements, " + - "found {1} errors when individually downloading elements.", fetchItems.Count, dlErrors); + // to be fixed + Log.Singleton.Info(StoredPrint.Color.Blue, "Executed query. Receiving list of {0} elements, " + + "found {1} errors when individually downloading elements.", numTotal, numError); } // start auto-load missing Submodels? @@ -2352,6 +2408,9 @@ public enum BaseTypeEnum { Repository, Registry, RegOfReg } // public string QueryScript = "{\r\n searchSMs(\r\n expression: \"\"\"$LOG\r\n filter=\r\n or(\r\n str_contains(sm.IdShort, \"Technical\"),\r\n str_contains(sm.IdShort, \"Nameplate\")\r\n )\r\n \"\"\"\r\n )\r\n {\r\n url\r\n smId\r\n }\r\n}"; // public string QueryScript = "{\r\n searchSMs(\r\n expression: \"\"\"$LOG$QL\r\n ( contains(sm.idShort, \"Technical\") and\r\n sme.value ge 100 and\r\n sme.value le 200 )\r\n or\r\n ( contains(sm.idShort, \"Nameplate\") and\r\n contains(sme.idShort,\"ManufacturerName\") and\r\n not(contains(sme.value,\"Phoenix\")))\r\n \"\"\"\r\n )\r\n {\r\n url\r\n smId\r\n }\r\n}"; + [AasxMenuArgument(help: "Specifies the AAS meta model element type name to be queried (AAS, Submodel, ConceptDescription).")] + public string QueryElementType = "AAS"; + [AasxMenuArgument(help: "Filter elements on (Id, IdShort, DisplayName, Description) after getting.")] public bool FilterByText; @@ -2630,7 +2689,20 @@ public static BasedLocation BuildLocationFrom( // Query? if (record.ExecuteQuery) { - var uri = BuildUriForRepoQuery(baseUris.GetBaseUriForQuery(), record.QueryScript); + // do some manual stuff for element type + var qet = ("" + record.QueryElementType).ToLower().Trim(); + string et = null; + if (qet == "aas" || qet == "shells") + et = "shells"; + if (qet == "submodel" || qet == "submodels") + et = "submodels"; + if (qet == "conceptdescription" || qet == "conceptdescriptions") + et = "conceptdescriptions"; + if (et == null) + return null; + + // build + var uri = BuildUriForRepoQuery(baseUris.GetBaseUriForQuery(), record.QueryScript, et); return new BasedLocation(baseUris, uri); } @@ -2704,6 +2776,63 @@ public enum ConnectExtendedScope Header = 0x0080 } + protected class QueryPresetDef + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("elementType")] + public string ElementType { get; set; } + + [JsonProperty("query")] + public JToken Query { get; set; } // <-- dynamic JSON + } + + protected static List ReadQueryPresets() + { + var pfn = Options.Curr.QueryPresetFile; + if (pfn == null || !System.IO.File.Exists(pfn)) + { + Log.Singleton.Error( + $"JSON file for query presets not defined nor existing ({pfn})."); + return null; + } + try + { + // read file contents + var init = System.IO.File.ReadAllText(pfn); + var presets = JsonConvert.DeserializeObject>(init); + + return presets; + } + catch (Exception ex) + { + Log.Singleton.Error( + $"JSON file for query presets not readable ({pfn}): {ex.Message}"); + return null; + } + } + + protected static string ValidateJson(string strInput) + { + try + { + var obj = JToken.Parse(strInput); + return "Seems to be JSON format."; + } + catch (JsonReaderException jex) + { + //Exception in parsing json + Console.WriteLine(jex.Message); + return "JSON error: " + jex.Message; + } + catch (Exception ex) //some other exception + { + Console.WriteLine(ex.ToString()); + return "General error: " + ex.Message; + } + } + public static async Task PerformConnectExtendedDialogue( AasxMenuActionTicket ticket, AnyUiContextBase displayContext, @@ -2758,8 +2887,14 @@ public static async Task PerformConnectExtendedDialogue( return true; // some memory - AnyUiUIElement testElem1 = null; int heightQueryEditor = 120; + var validateResult = ""; + + // read query presets + var queryPresets = ReadQueryPresets(); + var queryPresetNames = (new[] {""}).ToList(); + if (queryPresets != null) + queryPresetNames.AddRange(queryPresets.Select((p) => p.Name)); // ok, go on .. var uc = new AnyUiDialogueDataModalPanel(caption); @@ -3022,85 +3157,143 @@ public static async Task PerformConnectExtendedDialogue( if ((scope & ConnectExtendedScope.Query) > 0) { // Query check box - AnyUiUIElement.RegisterControl( - helper.Set( - helper.AddSmallCheckBoxTo(g, row, 0, + { + var g2 = helper.AddSmallGridTo(g, row, 0, 1, 3, new[] { "#", "#", "#" }); + g2.GridColumnSpan = 2; + + AnyUiUIElement.RegisterControl( + helper.AddSmallCheckBoxTo(g2, 0, 0, content: "Get by query definition", isChecked: record.ExecuteQuery, verticalContentAlignment: AnyUiVerticalAlignment.Center), - colSpan: 2), - (o) => - { - if ((bool)o) - record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.Query); - else - record.ExecuteQuery = false; - return new AnyUiLambdaActionModalPanelReRender(uc); - }); + (o) => + { + if ((bool)o) + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.Query); + else + record.ExecuteQuery = false; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); - // Complex query line - helper.AddSmallLabelTo(g, row + 1, 0, content: "Preset/ Size:", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center); + helper.AddSmallLabelTo(g2, 0, 1, content: " .. for: ", verticalCenter: true); - var g2 = helper.AddSmallGridTo(g, row + 1, 1, 1, 3, new[] { "*", "#", "#" }); + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallComboBoxTo(g2, 0, 2, + isEditable: false, + items: new[] { "AAS", "Submodel", "ConceptDescription" }, + text: "" + record.QueryElementType, + margin: new AnyUiThickness(10, 0, 0, 0), + padding: new AnyUiThickness(5, 0, 5, 0), + minWidth: 200, + horizontalAlignment: AnyUiHorizontalAlignment.Stretch)), + (s) => { record.QueryElementType = s; }); + } - AnyUiUIElement.SetStringFromControl( - helper.Set( - helper.AddSmallComboBoxTo(g2, 0, 0, - isEditable: true, - // items: Options.Curr.BaseAddresses?.ToArray(), - items: Options.Curr.BaseAddresses? - .Concat(Options.Curr.KnownEndpoints?.Select((o) => o.BaseAddress)).ToArray(), - text: "" + record.BaseAddress, - margin: new AnyUiThickness(0, 0, 0, 0), - padding: new AnyUiThickness(0, 0, 0, 0), - horizontalAlignment: AnyUiHorizontalAlignment.Stretch)), - (s) => { record.BaseAddress = s; }); + // Complex query preset and size line + { + helper.AddSmallLabelTo(g, row + 1, 0, content: "Preset/ Size:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); - AnyUiUIElement.RegisterControl( - helper.AddSmallButtonTo(g2, 0, 1, - margin: new AnyUiThickness(5, 0, 5, 0), - content: " \uff0b ", - modalDialogStyle: true, - foreground: AnyUiBrushes.White, - toolTip: "Enlarges the size of the query text editor"), - setValue: (o) => { - heightQueryEditor += 50; - return new AnyUiLambdaActionModalPanelReRender(uc); - }); + var g2 = helper.AddSmallGridTo(g, row + 1, 1, 1, 3, new[] { "*", "#", "#" }); - var b2 = AnyUiUIElement.RegisterControl( - helper.AddSmallButtonTo(g2, 0, 2, - margin: new AnyUiThickness(5, 0, 5, 0), - content: " \u2212 ", - modalDialogStyle: true, - foreground: AnyUiBrushes.White, - toolTip: "Decreases the size of the query text editor"), - setValue: (o) => { - heightQueryEditor = Math.Max(120, heightQueryEditor - 50); - return new AnyUiLambdaActionModalPanelReRender(uc); - }); + AnyUiComboBox cbPreset = null; + cbPreset = AnyUiUIElement.RegisterControl( + helper.Set( + helper.AddSmallComboBoxTo(g2, 0, 0, + isEditable: false, + items: queryPresetNames.ToArray(), + text: "", + margin: new AnyUiThickness(0, 0, 0, 0), + padding: new AnyUiThickness(0, 0, 0, 0), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch)), + (o) => { + if (!cbPreset.SelectedIndex.HasValue) + return new AnyUiLambdaActionNone(); + int i2 = cbPreset.SelectedIndex.Value - 1; + if (i2 < 0 || i2 >= queryPresets.Count) + return new AnyUiLambdaActionNone(); + + record.QueryScript = queryPresets[i2].Query.ToString(); + return new AnyUiLambdaActionModalPanelReRender(uc); + }); + + AnyUiUIElement.RegisterControl( + helper.AddSmallButtonTo(g2, 0, 1, + margin: new AnyUiThickness(5, 0, 5, 0), + content: " \uff0b ", + modalDialogStyle: true, + foreground: AnyUiBrushes.White, + toolTip: "Enlarges the size of the query text editor"), + setValue: (o) => + { + heightQueryEditor += 50; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); + var b2 = AnyUiUIElement.RegisterControl( + helper.AddSmallButtonTo(g2, 0, 2, + margin: new AnyUiThickness(5, 0, 5, 0), + content: " \u2212 ", + modalDialogStyle: true, + foreground: AnyUiBrushes.White, + toolTip: "Decreases the size of the query text editor"), + setValue: (o) => + { + heightQueryEditor = Math.Max(120, heightQueryEditor - 50); + return new AnyUiLambdaActionModalPanelReRender(uc); + }); + } + + // Query text itself helper.AddSmallLabelTo(g, row + 2, 0, content: "Query:", verticalAlignment: AnyUiVerticalAlignment.Top, verticalContentAlignment: AnyUiVerticalAlignment.Top); - // Query text itself - testElem1 = AnyUiUIElement.SetStringFromControl( + AnyUiUIElement.SetStringFromControl( helper.Set( helper.AddSmallTextBoxTo(g, row + 2, 1, text: $"{record.QueryScript}", - verticalAlignment: AnyUiVerticalAlignment.Stretch, + verticalAlignment: AnyUiVerticalAlignment.Top, verticalContentAlignment: AnyUiVerticalAlignment.Top, textWrap: AnyUiTextWrapping.Wrap, + fontMono: true, fontSize: 0.7, - multiLine: true), + multiLine: true, + verticalScroll: AnyUiScrollBarVisibility.Visible), horizontalAlignment: AnyUiHorizontalAlignment.Stretch, - minHeight: heightQueryEditor), + minHeight: heightQueryEditor, maxHeight: heightQueryEditor), (s) => { record.QueryScript = s; }); - row += 3; + // Validate + { + helper.AddSmallLabelTo(g, row + 3, 0, content: "Validate:", verticalCenter: true); + + var g2 = helper.AddSmallGridTo(g, row + 3, 1, 1, 2, new[] { "#", "*" }); + + AnyUiUIElement.RegisterControl( + helper.AddSmallButtonTo(g2, 0, 0, + margin: new AnyUiThickness(5, 0, 5, 0), + content: "Check", + modalDialogStyle: true, + foreground: AnyUiBrushes.White, + toolTip: "Checks if the JSON is valid. Does not check against JSON schema."), + setValue: (o) => + { + validateResult = ValidateJson(record.QueryScript) ; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); + + helper.Set( + helper.AddSmallTextBoxTo(g2, 0, 1, + text: $"{validateResult}", + verticalCenter: true, + readOnly: true), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch); + } + + row += 4; } if ((scope & ConnectExtendedScope.Filters) > 0) diff --git a/src/AasxWpfControlLibrary/AnyUiWpf.cs b/src/AasxWpfControlLibrary/AnyUiWpf.cs index 4e7461ee5..c780e3c8f 100644 --- a/src/AasxWpfControlLibrary/AnyUiWpf.cs +++ b/src/AasxWpfControlLibrary/AnyUiWpf.cs @@ -952,6 +952,7 @@ private void InitRenderRecs() wpf.VerticalScrollBarVisibility = (ScrollBarVisibility) ((int) cntl.VerticalScrollBarVisibility); + if (cntl.MaxLines != null) wpf.MaxLines = cntl.MaxLines.Value; wpf.Text = cntl.Text; diff --git a/src/AnyUi/AnyUiSmallWidgetToolkit.cs b/src/AnyUi/AnyUiSmallWidgetToolkit.cs index 032e1a0d3..be8535a53 100644 --- a/src/AnyUi/AnyUiSmallWidgetToolkit.cs +++ b/src/AnyUi/AnyUiSmallWidgetToolkit.cs @@ -228,7 +228,10 @@ public AnyUiTextBox AddSmallTextBoxTo( double? fontSize = null, AnyUiTextWrapping? textWrap = null, bool? multiLine = null, - bool verticalCenter = false) + bool verticalCenter = false, + bool? fontMono = null, + bool? readOnly = null, + AnyUiScrollBarVisibility? verticalScroll = null) { var tb = new AnyUiTextBox(); tb.Margin = margin; @@ -251,6 +254,14 @@ public AnyUiTextBox AddSmallTextBoxTo( } if (textWrap.HasValue) tb.TextWrapping = textWrap.Value; + if (fontMono.HasValue) + tb.FontMono = fontMono.Value; + if (readOnly.HasValue) + tb.IsReadOnly = readOnly.Value; + + tb.VerticalScrollBarVisibility = AnyUiScrollBarVisibility.Auto; + if (verticalScroll.HasValue) + tb.VerticalScrollBarVisibility = verticalScroll.Value; // (MIHO, 2020-11-13): be default constrain to one line tb.MultiLine = false; @@ -259,7 +270,6 @@ public AnyUiTextBox AddSmallTextBoxTo( if (multiLine.HasValue) tb.MultiLine = multiLine.Value; - tb.VerticalScrollBarVisibility = AnyUiScrollBarVisibility.Auto; AnyUiGrid.SetRow(tb, row); AnyUiGrid.SetColumn(tb, col); From 42ca0be4368726d93c60188ccdd79910588a8fe4 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Fri, 3 Oct 2025 21:36:47 +0200 Subject: [PATCH 3/6] * query working * more query options * show last blue message * SMT AsciiDoc to use podman --- .../AnyUI/AnyUiContextPlusDialogs.cs | 3 +- src/AasxPackageExplorer/MainWindow.xaml | 3 +- src/AasxPackageExplorer/MainWindow.xaml.cs | 42 ++++- .../options-debug.MIHO.json | 19 +- src/AasxPackageExplorer/query-presets.json | 145 ++++++++++---- .../PackageContainerHttpRepoSubset.cs | 177 ++++++++++++------ .../AasxPluginExportTable.options.json | 37 ++-- .../ExportTableOptions.cs | 2 + src/AasxPluginExportTable/Smt/ExportSmt.cs | 14 +- src/AasxWpfControlLibrary/AnyUiWpf.cs | 21 ++- src/BlazorExplorer/AnyUI/AnyUiHtml.cs | 3 +- 11 files changed, 335 insertions(+), 131 deletions(-) diff --git a/src/AasxIntegrationBase/AnyUI/AnyUiContextPlusDialogs.cs b/src/AasxIntegrationBase/AnyUI/AnyUiContextPlusDialogs.cs index c0a9e9a44..0a88ea16c 100644 --- a/src/AasxIntegrationBase/AnyUI/AnyUiContextPlusDialogs.cs +++ b/src/AasxIntegrationBase/AnyUI/AnyUiContextPlusDialogs.cs @@ -445,7 +445,8 @@ public async virtual Task MenuExecuteSystemCommand( string caption, string workDir, string cmd, - string args) + string args, + string[] ignoreError = null) { await Task.Yield(); throw new NotImplementedException("MenuExecuteSystemCommand"); diff --git a/src/AasxPackageExplorer/MainWindow.xaml b/src/AasxPackageExplorer/MainWindow.xaml index 81652d73c..6b634fb0b 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml +++ b/src/AasxPackageExplorer/MainWindow.xaml @@ -382,7 +382,8 @@