diff --git a/.github/workflows/continuous.yml b/.github/workflows/continuous.yml index d9b93db1..6fa040bf 100644 --- a/.github/workflows/continuous.yml +++ b/.github/workflows/continuous.yml @@ -9,6 +9,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v1 + - name: Run tests + run: ./build.cmd Test - name: Run './build.cmd PublishGitHubRelease' run: ./build.cmd PublishGitHubRelease env: diff --git a/CHANGELOG.md b/CHANGELOG.md index b321de6b..a6cd9ad4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,37 @@ This is the Changelog for the OpenProject Revit Add-in. It follows the guidelines described in https://keepachangelog.com/en/1.0.0/. The versions follow [semantic versioning](https://semver.org/). +## Unreleased + +### Added + +- Amended README with information about reporting bugs and other useful information + +### Changed + +- Viewpoint snapshot data is given in an improper state to the OpenProject instance frontend. The current hack has to be + maintained until the related + [work package](https://community.openproject.org/projects/bcfier/work_packages/39135/activity) is resolved and the + solution deployed. + +### Fixed + +- Section boxes with infinity values, which can exist after importing viewpoints with less then 6 clipping planes, no + longer create invalid viewpoint data when generating a new viewpoint. +- If there is a newer version of the AddIn released at GitHub, a notification dialog is displayed again +- When exporting a viewpoint from a Revit view, elements hidden by category are interpreted as hidden. However, applying + this viewpoint does not apply the category visibility, but sets all contained elements to hidden, as BCF viewpoints + are not aware of Revit categories. + +## [2.3.1] - 2021-10-04 + +### Fixed + +- Fixed an issue that led to an exception when installing the Revit Add-In for the very first time +- Made the loading of the `OpenProject.Configuration.json` more robust, so that missing or outdated keys no longer lead + to errors +- Improved logging and error dialog communication + ## [2.3.0] - 2021-09-21 ### Added @@ -35,7 +66,7 @@ in https://keepachangelog.com/en/1.0.0/. The versions follow [semantic versionin - Loading times for viewpoints were slightly improved, on machines without enough capability the asynchronous zoom for orthogonal viewpoints can take several seconds (see this [official issue](https://thebuildingcoder.typepad.com/blog/2020/10/save-and-restore-3d-view-camera-settings.html)). -- An issue was fixed, causing the cursor in input fields in the embedded browser vanishing occasionally. +- An issue was fixed, causing the cursor in input fields in the embedded browser vanishing occasionally. ## [2.2.5] - 2021-04-16 diff --git a/build/Build.cs b/build/Build.cs index fb450d23..ddd87f67 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -12,10 +12,13 @@ using Nuke.Common.Utilities.Collections; using Nuke.GitHub; using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; +using Nuke.Common.Tools.Git; +using Octokit; using static Nuke.Common.IO.CompressionTasks; using static Nuke.Common.IO.FileSystemTasks; using static Nuke.Common.IO.PathConstruction; @@ -221,7 +224,7 @@ public static class RepositoryInfo .Executes(() => { DotNetTest(c => c - .SetConfiguration("Debug") + .SetConfiguration("Debug-2022") .SetProjectFile(RootDirectory / "test" / "OpenProject.Tests" / "OpenProject.Tests.csproj") .SetTestAdapterPath(".") .SetLoggers($"xunit;LogFilePath={OutputDirectory / "testresults.xml"}")); @@ -281,7 +284,19 @@ public static class RepositoryInfo SignFilesIfRequirementsMet(OutputDirectory / "OpenProject.Revit.exe"); }); + static bool ShouldBuildRelease() + { + const string flag = "[release skip]"; + + var message = GitTasks.Git("log -1 --pretty=format:%B", logOutput: false) + .Select(output => output.Text) + .Aggregate((s, s1) => $"{s}\n{s1}"); + + return !message.Contains(flag); + } + Target PublishGitHubRelease => _ => _ + .OnlyWhenDynamic(() => ShouldBuildRelease()) .DependsOn(CreateSetup) .DependsOn(BuildDocumentation) .Requires(() => GitHubAuthenticationToken) @@ -290,10 +305,10 @@ public static class RepositoryInfo var releaseTag = $"v{GitVersion.SemVer}"; var isStableRelease = GitVersion.BranchName.Equals("master") || GitVersion.BranchName.Equals("origin/master") || GitVersion.BranchName.Equals("main") || GitVersion.BranchName.Equals("origin/main"); - var repositoryInfo = GetGitHubRepositoryInfo(GitRepository); + var (gitHubOwner, repositoryName) = GetGitHubRepositoryInfo(GitRepository); var installationFile = GlobFiles(OutputDirectory, "OpenProject.Revit.exe").NotEmpty().Single(); - var artifactPaths = new string[] + var artifactPaths = new[] { installationFile, OutputDirectory / "InstallationInstructions.pdf", @@ -304,8 +319,8 @@ await PublishRelease(x => x .SetPrerelease(!isStableRelease) .SetArtifactPaths(artifactPaths) .SetCommitSha(GitVersion.Sha) - .SetRepositoryName(repositoryInfo.repositoryName) - .SetRepositoryOwner(repositoryInfo.gitHubOwner) + .SetRepositoryName(repositoryName) + .SetRepositoryOwner(gitHubOwner) .SetTag(releaseTag) .SetToken(GitHubAuthenticationToken)); }); diff --git a/global.json b/global.json index 496b7c1a..200e186e 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "5.0.301" + "version": "5.0.402" } -} \ No newline at end of file +} diff --git a/src/OpenProject.Browser/Models/Release.cs b/src/OpenProject.Browser/Models/Release.cs new file mode 100644 index 00000000..8187118e --- /dev/null +++ b/src/OpenProject.Browser/Models/Release.cs @@ -0,0 +1,25 @@ +using System; + +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable InconsistentNaming + +namespace OpenProject.Browser.Models +{ + /// + /// Class model for deserialization of github release JSON data from the github rest API. + /// + // ReSharper disable once ClassNeverInstantiated.Global + public sealed class Release + { + public string tag_name { private get; set; } + public DateTime published_at { private get; set; } + public string html_url { private get; set; } + + public Version Version() => Models.Version.Parse(tag_name); + + public DateTime PublishedAt() => published_at; + + public string DownloadUrl() => html_url; + } +} diff --git a/src/OpenProject.Browser/Models/Version.cs b/src/OpenProject.Browser/Models/Version.cs new file mode 100644 index 00000000..9c11d416 --- /dev/null +++ b/src/OpenProject.Browser/Models/Version.cs @@ -0,0 +1,92 @@ +using System; +using Serilog; + +namespace OpenProject.Browser.Models +{ + /// + /// Immutable class representing semantic versions. + /// + public sealed class Version : IComparable + { + private readonly int _major; + private readonly int _minor; + private readonly int _patch; + private readonly string _suffix; + + private Version(int major, int minor, int patch, string suffix) + { + _major = major; + _minor = minor; + _patch = patch; + _suffix = suffix; + } + + /// + /// Parses a string into a semantic version. Returns 'v0.0.0' if an invalid string is given. + /// + /// The input string + /// A semantic version object + public static Version Parse(string versionString) + { + var separators = new[] { '.', '-' }; + var split = versionString.Split(separators); + + var major = 0; + var minor = 0; + var patch = 0; + var suffix = ""; + + try + { + if (split.Length >= 1) + { + var str = split[0].StartsWith("v", StringComparison.InvariantCultureIgnoreCase) ? split[0][1..] : split[0]; + major = int.Parse(str); + } + + if (split.Length >= 2) + minor = int.Parse(split[1]); + if (split.Length >= 3) + patch = int.Parse(split[2]); + if (split.Length >= 4) + suffix = split[3]; + } + catch (FormatException) + { + Log.Error("{version} is no valid version string.", versionString); + return new Version(0, 0, 0, ""); + } + + return new Version(major, minor, patch, suffix); + } + + /// + public int CompareTo(Version other) + { + if (ReferenceEquals(this, other)) return 0; + if (ReferenceEquals(null, other)) return 1; + + if (_major > other._major) + return 1; + if (_major < other._major) + return -1; + + if (_minor > other._minor) + return 1; + if (_minor < other._minor) + return -1; + + if (_patch > other._patch) + return 1; + if (_patch < other._patch) + return -1; + + var stringComparison = string.CompareOrdinal(_suffix, other._suffix); + return stringComparison > 0 ? 1 : stringComparison < 0 ? -1 : 0; + } + + /// + public override string ToString() => + _suffix.Length > 0 ? $"v{_major}.{_minor}.{_patch}-{_suffix}" : $"v{_major}.{_minor}.{_patch}"; + } +} diff --git a/src/OpenProject.Browser/Services/ConfigurationHandler.cs b/src/OpenProject.Browser/Services/ConfigurationHandler.cs index 1c5f8b94..3d592613 100644 --- a/src/OpenProject.Browser/Services/ConfigurationHandler.cs +++ b/src/OpenProject.Browser/Services/ConfigurationHandler.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using Config.Net; using Newtonsoft.Json; @@ -25,11 +24,7 @@ static ConfigurationHandler() } } - private static IOpenProjectSettings Settings { get; } - - public static bool ShouldEnableDevelopmentTools() => Settings.EnableDevelopmentTools; - - public static List LoadAllInstances() => Settings.GetOpenProjectInstances(); + public static IOpenProjectSettings Settings { get; } public static void RemoveSavedInstance(string instanceUrl) { diff --git a/src/OpenProject.Browser/Services/GitHubService.cs b/src/OpenProject.Browser/Services/GitHubService.cs new file mode 100644 index 00000000..53412aa4 --- /dev/null +++ b/src/OpenProject.Browser/Services/GitHubService.cs @@ -0,0 +1,39 @@ +using System.Net; +using OpenProject.Browser.Models; +using OpenProject.Shared; +using Optional; +using RestSharp; + +namespace OpenProject.Browser.Services +{ + public sealed class GitHubService : IGitHubService + { + private RestClient _client; + + private RestClient Client + { + get + { + if (_client != null) + return _client; + + _client = new RestClient(@"https://api.github.com/"); + return _client; + } + } + + /// + public Option GetLatestRelease() + { + var request = + new RestRequest($"repos/{RepositoryInfo.GitHubOwner}/{RepositoryInfo.GitHubRepository}/releases/latest", Method.GET); + request.AddHeader("Content-Type", "application/json"); + request.RequestFormat = DataFormat.Json; + request.OnBeforeDeserialization = resp => { resp.ContentType = "application/json"; }; + + var response = Client.Execute(request); + + return response.StatusCode == HttpStatusCode.OK ? response.Data.SomeNotNull() : Option.None(); + } + } +} diff --git a/src/OpenProject.Browser/Services/Interfaces/IGitHubService.cs b/src/OpenProject.Browser/Services/Interfaces/IGitHubService.cs new file mode 100644 index 00000000..17ffa0a0 --- /dev/null +++ b/src/OpenProject.Browser/Services/Interfaces/IGitHubService.cs @@ -0,0 +1,17 @@ +using OpenProject.Browser.Models; +using Optional; + +namespace OpenProject.Browser.Services +{ + /// + /// A service for communication to the repository on github. + /// + public interface IGitHubService + { + /// + /// Get the latest published released of the OpenProject Revit AddIn. + /// + /// A release object. + Option GetLatestRelease(); + } +} diff --git a/src/OpenProject.Browser/Services/MessageHandler.cs b/src/OpenProject.Browser/Services/MessageHandler.cs index cc8a5294..b36cd97f 100644 --- a/src/OpenProject.Browser/Services/MessageHandler.cs +++ b/src/OpenProject.Browser/Services/MessageHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Windows; using Serilog; @@ -17,5 +18,31 @@ public static void ShowError(Exception exception, string message) Log.Error(exception, message); MessageBox.Show(message, "ERROR", MessageBoxButton.OK, MessageBoxImage.Error); } + + /// + /// Displays an update dialog, which indicates that a newer add-in version is available for download. + /// User can decide whether to download update or not. + /// + /// The string representation of the currently installed version. + /// The string representation of the new version. + /// The release date of the new version. + /// The URL where the new version can be downloaded from. + public static void ShowUpdateDialog(string oldVersion, string newVersion, DateTime releaseDate, string downloadUrl) + { + Log.Information("New version {new} available. Currently installed: {old}", newVersion, oldVersion); + var messageBoxText = $"A new release '{newVersion}' was released at {releaseDate.ToLongDateString()}. " + + $"'{oldVersion}' is currently installed.\nDo you want to download the new version?"; + MessageBoxResult messageBoxResult = MessageBox.Show( + messageBoxText, + "New version available", + MessageBoxButton.YesNo, + MessageBoxImage.Information + ); + + if (messageBoxResult != MessageBoxResult.Yes) return; + + Process.Start(new ProcessStartInfo("cmd", $"/c start {downloadUrl}") { CreateNoWindow = true }); + Log.Information("Download of new version {new} started.", newVersion); + } } } diff --git a/src/OpenProject.Browser/Services/ServiceProviderConfiguration.cs b/src/OpenProject.Browser/Services/ServiceProviderConfiguration.cs index fc357c1c..da3506f6 100644 --- a/src/OpenProject.Browser/Services/ServiceProviderConfiguration.cs +++ b/src/OpenProject.Browser/Services/ServiceProviderConfiguration.cs @@ -32,6 +32,7 @@ internal static IServiceCollection ConfigureIoCContainer() services.AddSingleton(); // Interface implementations + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/OpenProject.Browser/Settings/IOpenProjectSettings.cs b/src/OpenProject.Browser/Settings/IOpenProjectSettings.cs index 2ebd9d73..88c5e86e 100644 --- a/src/OpenProject.Browser/Settings/IOpenProjectSettings.cs +++ b/src/OpenProject.Browser/Settings/IOpenProjectSettings.cs @@ -5,5 +5,6 @@ public interface IOpenProjectSettings public bool EnableDevelopmentTools { get; set; } public string OpenProjectInstances { get; set; } public string LastVisitedPage { get; set; } + public bool CheckForUpdates { get; set; } } } diff --git a/src/OpenProject.Browser/Settings/OpenProject.Configuration.json b/src/OpenProject.Browser/Settings/OpenProject.Configuration.json index e4bfa639..31c0d277 100644 --- a/src/OpenProject.Browser/Settings/OpenProject.Configuration.json +++ b/src/OpenProject.Browser/Settings/OpenProject.Configuration.json @@ -1,4 +1,5 @@ { "EnableDevelopmentTools": false, - "OpenProjectInstances": [] + "OpenProjectInstances": [], + "CheckForUpdates": true } diff --git a/src/OpenProject.Browser/ViewModels/MainWindowViewModel.cs b/src/OpenProject.Browser/ViewModels/MainWindowViewModel.cs index da36f036..d4fc92e2 100644 --- a/src/OpenProject.Browser/ViewModels/MainWindowViewModel.cs +++ b/src/OpenProject.Browser/ViewModels/MainWindowViewModel.cs @@ -9,7 +9,7 @@ namespace OpenProject.Browser.ViewModels /// public sealed class MainWindowViewModel { - private const double _windowMinWidth = 800.00; + private const double _windowMinWidth = 320.00; /// /// The view of the nested bcfier panel. @@ -19,7 +19,12 @@ public sealed class MainWindowViewModel /// /// The main window calculated width. /// - public double Width => Math.Max(_windowMinWidth, SystemParameters.PrimaryScreenHeight * 0.25); + public double MinWidth => _windowMinWidth; + + /// + /// The main window calculated width. + /// + public double Width => Math.Max(_windowMinWidth, SystemParameters.PrimaryScreenWidth * 0.33); /// /// The main window default height. diff --git a/src/OpenProject.Browser/ViewModels/WebViewModel.cs b/src/OpenProject.Browser/ViewModels/WebViewModel.cs index 87bd7125..86415694 100644 --- a/src/OpenProject.Browser/ViewModels/WebViewModel.cs +++ b/src/OpenProject.Browser/ViewModels/WebViewModel.cs @@ -1,24 +1,30 @@ using System; using System.Linq; using CefSharp.Wpf; +using OpenProject.Browser.Services; using OpenProject.Browser.WebViewIntegration; +using OpenProject.Shared; +using Version = OpenProject.Browser.Models.Version; namespace OpenProject.Browser.ViewModels { public sealed class WebViewModel { private readonly JavaScriptBridge _javaScriptBridge; + private readonly IGitHubService _gitHubService; private readonly BrowserManager _browserManager; public ChromiumWebBrowser Browser => _browserManager.Browser; - public WebViewModel(BrowserManager browserManager, JavaScriptBridge javaScriptBridge) + public WebViewModel(IGitHubService gitHubService, BrowserManager browserManager, JavaScriptBridge javaScriptBridge) { + _gitHubService = gitHubService; _browserManager = browserManager; _javaScriptBridge = javaScriptBridge; _browserManager.Initialize(); InitializeIpcConnection(); + CheckForUpdates(); } private void InitializeIpcConnection() @@ -34,5 +40,27 @@ private void InitializeIpcConnection() var clientPort = int.Parse(args[1]); IpcManager.StartIpcCommunication(_javaScriptBridge, serverPort, clientPort); } + + private void CheckForUpdates() + { + if (!ConfigurationHandler.Settings.CheckForUpdates) + return; + + var latestRelease = _gitHubService.GetLatestRelease(); + + latestRelease.MatchSome(release => + { + Version latestVersion = release.Version(); + Version currentVersion = Version.Parse(VersionsService.Version); + if (latestVersion.CompareTo(currentVersion) > 0) + { + MessageHandler.ShowUpdateDialog( + currentVersion.ToString(), + latestVersion.ToString(), + release.PublishedAt(), + release.DownloadUrl()); + } + }); + } } } diff --git a/src/OpenProject.Browser/Views/MainWindowView.xaml.cs b/src/OpenProject.Browser/Views/MainWindowView.xaml.cs index 2a27e362..e651f4e7 100644 --- a/src/OpenProject.Browser/Views/MainWindowView.xaml.cs +++ b/src/OpenProject.Browser/Views/MainWindowView.xaml.cs @@ -20,6 +20,7 @@ public MainWindowView(MainWindowViewModel viewModel, JavaScriptBridge javaScript // We have to set layout here, as the layout in wpf doesn't work with bindings at runtime. Width = viewModel.Width; + MinWidth = viewModel.MinWidth; Height = viewModel.Height; Top = viewModel.Top; Left = viewModel.Left; diff --git a/src/OpenProject.Browser/WebViewIntegration/BrowserManager.cs b/src/OpenProject.Browser/WebViewIntegration/BrowserManager.cs index 957d0851..5e302d71 100644 --- a/src/OpenProject.Browser/WebViewIntegration/BrowserManager.cs +++ b/src/OpenProject.Browser/WebViewIntegration/BrowserManager.cs @@ -6,6 +6,7 @@ using CefSharp; using CefSharp.Wpf; using OpenProject.Browser.Services; +using OpenProject.Browser.Settings; namespace OpenProject.Browser.WebViewIntegration { @@ -39,7 +40,7 @@ public void Initialize() { Browser = _chromiumWebBrowser; - var knownGoodUrls = ConfigurationHandler.LoadAllInstances(); + var knownGoodUrls = ConfigurationHandler.Settings.GetOpenProjectInstances(); var lastVisitedPage = ConfigurationHandler.LastVisitedPage(); var isWhiteListedUrl = knownGoodUrls.Any(goodUrl => lastVisitedPage.StartsWith(goodUrl, StringComparison.InvariantCultureIgnoreCase)); @@ -78,7 +79,7 @@ public void Initialize() } }); - if (!ConfigurationHandler.ShouldEnableDevelopmentTools()) return; + if (!ConfigurationHandler.Settings.EnableDevelopmentTools) return; var devToolsEnabled = false; Browser.IsBrowserInitializedChanged += (s, e) => diff --git a/src/OpenProject.Browser/WebViewIntegration/IpcManager.cs b/src/OpenProject.Browser/WebViewIntegration/IpcManager.cs index ae92247a..2ad4eab7 100644 --- a/src/OpenProject.Browser/WebViewIntegration/IpcManager.cs +++ b/src/OpenProject.Browser/WebViewIntegration/IpcManager.cs @@ -13,14 +13,14 @@ public static void StartIpcCommunication(JavaScriptBridge javaScriptBridge, int server.Start(serverPort); server.ReceivedRequest += (sender, e) => { - var deserialized = JsonConvert.DeserializeObject(e.Request); + var deserialized = JsonConvert.DeserializeObject(e.Request); javaScriptBridge.SendMessageToOpenProject( deserialized.MessageType, deserialized.TrackingId, deserialized.MessagePayload); }; var client = new IpcClient(); client.Initialize(clientPort); - javaScriptBridge.OnWebUIMessageReveived += (s, e) => client.Send(JsonConvert.SerializeObject(e)); + javaScriptBridge.OnWebUiMessageReceived += (s, e) => client.Send(JsonConvert.SerializeObject(e)); } } } diff --git a/src/OpenProject.Browser/WebViewIntegration/JavaScriptBridge.cs b/src/OpenProject.Browser/WebViewIntegration/JavaScriptBridge.cs index 5f0d1a17..7c9c78bf 100644 --- a/src/OpenProject.Browser/WebViewIntegration/JavaScriptBridge.cs +++ b/src/OpenProject.Browser/WebViewIntegration/JavaScriptBridge.cs @@ -5,6 +5,7 @@ using CefSharp.Wpf; using Newtonsoft.Json; using OpenProject.Browser.Services; +using OpenProject.Browser.Settings; using OpenProject.Shared; using Serilog; @@ -32,9 +33,9 @@ public JavaScriptBridge(OpenProjectInstanceValidator instanceValidator) private ChromiumWebBrowser _webBrowser; - public event WebUIMessageReceivedEventHandler OnWebUIMessageReveived; + public event WebUiMessageReceivedEventHandler OnWebUiMessageReceived; - public delegate void WebUIMessageReceivedEventHandler(object sender, WebUIMessageEventArgs e); + public delegate void WebUiMessageReceivedEventHandler(object sender, WebUiMessageEventArgs e); public event AppForegroundRequestReceivedEventHandler OnAppForegroundRequestReceived; @@ -83,7 +84,7 @@ public void SendMessageToRevit(string messageType, string trackingId, string mes break; case MessageTypes.ALL_INSTANCES_REQUESTED: { - var allInstances = JsonConvert.SerializeObject(ConfigurationHandler.LoadAllInstances()); + var allInstances = JsonConvert.SerializeObject(ConfigurationHandler.Settings.GetOpenProjectInstances()); SendMessageToOpenProject(MessageTypes.ALL_INSTANCES, trackingId, allInstances); break; } @@ -101,8 +102,8 @@ public void SendMessageToRevit(string messageType, string trackingId, string mes break; default: { - var eventArgs = new WebUIMessageEventArgs(messageType, trackingId, messagePayload); - OnWebUIMessageReveived?.Invoke(this, eventArgs); + var eventArgs = new WebUiMessageEventArgs(messageType, trackingId, messagePayload); + OnWebUiMessageReceived?.Invoke(this, eventArgs); // For some UI operations, revit should be focused RevitMainWindowHandler.SetFocusToRevit(); break; diff --git a/src/OpenProject.Browser/WebViewIntegration/LandingPage/LandingPage.zip b/src/OpenProject.Browser/WebViewIntegration/LandingPage/LandingPage.zip index e5fd955b..54c61667 100644 Binary files a/src/OpenProject.Browser/WebViewIntegration/LandingPage/LandingPage.zip and b/src/OpenProject.Browser/WebViewIntegration/LandingPage/LandingPage.zip differ diff --git a/src/OpenProject.Browser/WebViewIntegration/OpenProjectBrowserRequestHandler.cs b/src/OpenProject.Browser/WebViewIntegration/OpenProjectBrowserRequestHandler.cs index 0ef4bc19..d23c59be 100644 --- a/src/OpenProject.Browser/WebViewIntegration/OpenProjectBrowserRequestHandler.cs +++ b/src/OpenProject.Browser/WebViewIntegration/OpenProjectBrowserRequestHandler.cs @@ -4,6 +4,7 @@ using System.Security.Cryptography.X509Certificates; using CefSharp; using OpenProject.Browser.Services; +using OpenProject.Browser.Settings; namespace OpenProject.Browser.WebViewIntegration { @@ -60,7 +61,7 @@ private static bool ShouldOpenRequestInEmbeddedBrowser(IRequest request, bool is if (isValidLocalUrl) return true; - var knownGoodUrls = ConfigurationHandler.LoadAllInstances(); + var knownGoodUrls = ConfigurationHandler.Settings.GetOpenProjectInstances(); var isValidExternalUrl = knownGoodUrls.Any(goodUrl => request.Url.StartsWith(goodUrl, StringComparison.InvariantCultureIgnoreCase)); diff --git a/src/OpenProject.Revit/Data/ProjectPositionWrapper.cs b/src/OpenProject.Revit/Data/ProjectPositionWrapper.cs index 85b7b195..ff2178fe 100644 --- a/src/OpenProject.Revit/Data/ProjectPositionWrapper.cs +++ b/src/OpenProject.Revit/Data/ProjectPositionWrapper.cs @@ -1,5 +1,4 @@ -using System; -using Autodesk.Revit.DB; +using Autodesk.Revit.DB; using OpenProject.Shared.Math3D; namespace OpenProject.Revit.Data @@ -47,10 +46,10 @@ public ProjectPositionWrapper() public ProjectPositionWrapper(ProjectPosition projectPosition) { - EastWest = Convert.ToDecimal(projectPosition.EastWest); - NorthSouth = Convert.ToDecimal(projectPosition.NorthSouth); - Elevation = Convert.ToDecimal(projectPosition.Elevation); - Angle = Convert.ToDecimal(projectPosition.Angle); + EastWest = projectPosition.EastWest.ToDecimal(); + NorthSouth = projectPosition.NorthSouth.ToDecimal(); + Elevation = projectPosition.Elevation.ToDecimal(); + Angle = projectPosition.Angle.ToDecimal(); } /// diff --git a/src/OpenProject.Revit/Data/RevitUtils.cs b/src/OpenProject.Revit/Data/RevitUtils.cs index 05ab834e..8a9f33bc 100644 --- a/src/OpenProject.Revit/Data/RevitUtils.cs +++ b/src/OpenProject.Revit/Data/RevitUtils.cs @@ -1,7 +1,6 @@ using System; using Autodesk.Revit.DB; using OpenProject.Shared.Math3D; - using decMath = DecimalMath.DecimalEx; namespace OpenProject.Revit.Data @@ -26,7 +25,7 @@ public static Position TransformCameraPosition( var i = reverse ? -1 : 1; Vector3 translation = projectBase.GetTranslation() * i; - var rotation = i * Convert.ToDecimal(projectBase.Angle); + var rotation = i * projectBase.Angle; // do translation before rotation if we transform from global to local coordinates Vector3 center = reverse @@ -83,10 +82,7 @@ public static XYZ ToRevitXyz(this Vector3 vec) => /// /// The vector3 object /// The Revit vector object - public static Vector3 ToVector3(this XYZ vec) => - new(Convert.ToDecimal(vec.X), - Convert.ToDecimal(vec.Y), - Convert.ToDecimal(vec.Z)); + public static Vector3 ToVector3(this XYZ vec) => new(vec.X.ToDecimal(), vec.Y.ToDecimal(), vec.Z.ToDecimal()); /// /// Converts some basic revit view values to a view box height and a view box width. @@ -96,7 +92,7 @@ public static Vector3 ToVector3(this XYZ vec) => /// The bottom left corner of the revit view. /// The right direction of the revit view. /// A tuple of the height and the width of the view box. - public static ( double viewBoxHeight, double viewBoxWidth) ConvertToViewBoxValues( + public static (double viewBoxHeight, double viewBoxWidth) ConvertToViewBoxValues( XYZ topRight, XYZ bottomLeft, XYZ right) { XYZ diagonal = topRight.Subtract(bottomLeft); @@ -142,20 +138,28 @@ public static double ToInternalRevitUnit(this double meters) /// /// The vector with values in feet /// The vector with values in meter - public static Vector3 ToMeters(this Vector3 vec) => - new(Convert.ToDecimal(Convert.ToDouble(vec.X).ToMeters()), - Convert.ToDecimal(Convert.ToDouble(vec.Y).ToMeters()), - Convert.ToDecimal(Convert.ToDouble(vec.Z).ToMeters())); + public static Vector3 ToMeters(this Vector3 vec) + { + var x = vec.X.IsFinite() ? Convert.ToDouble(vec.X).ToMeters().ToDecimal() : vec.X; + var y = vec.Y.IsFinite() ? Convert.ToDouble(vec.Y).ToMeters().ToDecimal() : vec.Y; + var z = vec.Z.IsFinite() ? Convert.ToDouble(vec.Z).ToMeters().ToDecimal() : vec.Z; + + return new Vector3(x, y, z); + } /// /// Converts a vector containing values in meter units to feet. Feet are the internal Revit units. /// /// The vector with values in meter /// The vector with values in feet - public static Vector3 ToInternalUnits(this Vector3 vec) => - new(Convert.ToDecimal(Convert.ToDouble(vec.X).ToInternalRevitUnit()), - Convert.ToDecimal(Convert.ToDouble(vec.Y).ToInternalRevitUnit()), - Convert.ToDecimal(Convert.ToDouble(vec.Z).ToInternalRevitUnit())); + public static Vector3 ToInternalUnits(this Vector3 vec) + { + var x = vec.X.IsFinite() ? Convert.ToDouble(vec.X).ToInternalRevitUnit().ToDecimal() : vec.X; + var y = vec.Y.IsFinite() ? Convert.ToDouble(vec.Y).ToInternalRevitUnit().ToDecimal() : vec.Y; + var z = vec.Z.IsFinite() ? Convert.ToDouble(vec.Z).ToInternalRevitUnit().ToDecimal() : vec.Z; + + return new Vector3(x, y, z); + } /// /// Converts a position containing values in feet units to meter. Feet are the internal Revit units. diff --git a/src/OpenProject.Revit/Data/RevitView.cs b/src/OpenProject.Revit/Data/RevitView.cs deleted file mode 100644 index 93d186ca..00000000 --- a/src/OpenProject.Revit/Data/RevitView.cs +++ /dev/null @@ -1,282 +0,0 @@ -using Autodesk.Revit.DB; -using Autodesk.Revit.UI; -using OpenProject.Shared; -using OpenProject.Shared.ViewModels.Bcf; -using System; -using System.Collections.Generic; -using System.Linq; -using OpenProject.Shared.Math3D; - -namespace OpenProject.Revit.Data -{ - //Methods for working with views - public static class RevitView - { - // - //Generate a VisualizationInfo of the current view - // - // - public static BcfViewpointViewModel GenerateViewpoint(UIDocument uiDoc) - { - try - { - Document doc = uiDoc.Document; - var bcfViewpoint = new BcfViewpointViewModel(); - - //Corners of the active UI view - XYZ bottomLeft = uiDoc.GetOpenUIViews()[0].GetZoomCorners()[0]; - XYZ topRight = uiDoc.GetOpenUIViews()[0].GetZoomCorners()[1]; - ProjectPosition projectPosition = doc.ActiveProjectLocation.GetProjectPosition(XYZ.Zero); - - //It's a 3d view - if (uiDoc.ActiveView.ViewType == ViewType.ThreeD) - { - var view3D = (View3D)uiDoc.ActiveView; - - // it is a orthogonal view - if (!view3D.IsPerspective) - { - //center of the UI view - var viewCenter = new XYZ((topRight.X + bottomLeft.X) / 2, - (topRight.Y + bottomLeft.Y) / 2, - (topRight.Z + bottomLeft.Z) / 2); - - var (viewBoxHeight, _) = RevitUtils.ConvertToViewBoxValues(topRight, bottomLeft, view3D.RightDirection); - - Position cameraPosition = RevitUtils.TransformCameraPosition( - new ProjectPositionWrapper(projectPosition), - new Position( - viewCenter.ToVector3(), - uiDoc.ActiveView.ViewDirection.ToVector3(), - uiDoc.ActiveView.UpDirection.ToVector3())); - - Vector3 c = cameraPosition.Center.ToMeters(); - Vector3 vi = cameraPosition.Forward; - Vector3 up = cameraPosition.Up; - - bcfViewpoint.Viewpoint = new iabi.BCF.APIObjects.V21.Viewpoint_GET - { - Orthogonal_camera = new iabi.BCF.APIObjects.V21.Orthogonal_camera - { - View_to_world_scale = Convert.ToSingle(viewBoxHeight.ToMeters()), - Camera_view_point = - new iabi.BCF.APIObjects.V21.Point - { - X = Convert.ToSingle(c.X), - Y = Convert.ToSingle(c.Y), - Z = Convert.ToSingle(c.Z) - }, - Camera_up_vector = - new iabi.BCF.APIObjects.V21.Direction - { - X = Convert.ToSingle(up.X), - Y = Convert.ToSingle(up.Y), - Z = Convert.ToSingle(up.Z) - }, - Camera_direction = new iabi.BCF.APIObjects.V21.Direction - { - X = Convert.ToSingle(vi.X * -1), - Y = Convert.ToSingle(vi.Y * -1), - Z = Convert.ToSingle(vi.Z * -1) - } - } - }; - } - // it is a perspective view - else - { - XYZ viewCenter = uiDoc.ActiveView.Origin; - - Position cameraPosition = RevitUtils.TransformCameraPosition( - new ProjectPositionWrapper(projectPosition), - new Position( - viewCenter.ToVector3(), - uiDoc.ActiveView.ViewDirection.ToVector3(), - uiDoc.ActiveView.UpDirection.ToVector3())); - - Vector3 c = cameraPosition.Center.ToMeters(); - Vector3 vi = cameraPosition.Forward; - Vector3 up = cameraPosition.Up; - - bcfViewpoint.Viewpoint = new iabi.BCF.APIObjects.V21.Viewpoint_GET - { - Perspective_camera = new iabi.BCF.APIObjects.V21.Perspective_camera - { - Field_of_view = 45, //revit default value - Camera_view_point = - new iabi.BCF.APIObjects.V21.Point - { - X = Convert.ToSingle(c.X), - Y = Convert.ToSingle(c.Y), - Z = Convert.ToSingle(c.Z) - }, - Camera_up_vector = - new iabi.BCF.APIObjects.V21.Direction - { - X = Convert.ToSingle(up.X), - Y = Convert.ToSingle(up.Y), - Z = Convert.ToSingle(up.Z) - }, - Camera_direction = new iabi.BCF.APIObjects.V21.Direction - { - X = Convert.ToSingle(vi.X * -1), - Y = Convert.ToSingle(vi.Y * -1), - Z = Convert.ToSingle(vi.Z * -1) - } - } - }; - } - } - - SetViewpointComponents(bcfViewpoint, doc, uiDoc); - SetViewpointClippingPlanes(bcfViewpoint, uiDoc); - - return bcfViewpoint; - } - catch (Exception ex1) - { - TaskDialog.Show("Error generating viewpoint", "exception: " + ex1); - } - - return null; - } - - private static void SetViewpointComponents(BcfViewpointViewModel bcfViewpoint, Document doc, UIDocument uiDoc) - { - var versionName = doc.Application.VersionName; - - var visibleElems = new FilteredElementCollector(doc, doc.ActiveView.Id) - .WhereElementIsNotElementType() - .WhereElementIsViewIndependent() - .ToElementIds(); - var hiddenElems = new FilteredElementCollector(doc) - .WhereElementIsNotElementType() - .WhereElementIsViewIndependent() - .Where(x => x.IsHidden(doc.ActiveView) - || !doc.ActiveView.IsElementVisibleInTemporaryViewMode(TemporaryViewMode.TemporaryHideIsolate, - x.Id)).Select(x => x.Id) - .ToList(); //would need to check how much this is affecting performance - - var selectedElems = uiDoc.Selection.GetElementIds(); - - bcfViewpoint.Components = new iabi.BCF.APIObjects.V21.Components(); - if (hiddenElems.Count > visibleElems.Count) - { - bcfViewpoint.Components.Visibility = new iabi.BCF.APIObjects.V21.Visibility - { - Default_visibility = false, - Exceptions = visibleElems - .Select(visibleComponent => new iabi.BCF.APIObjects.V21.Component - { - Originating_system = versionName, - Ifc_guid = IfcGuid.ToIfcGuid(ExportUtils.GetExportId(doc, visibleComponent)), - Authoring_tool_id = visibleComponent.IntegerValue.ToString() - }) - .ToList() - }; - } - else - { - bcfViewpoint.Components.Visibility = new iabi.BCF.APIObjects.V21.Visibility - { - Default_visibility = true, - Exceptions = hiddenElems - .Select(hiddenComponent => new iabi.BCF.APIObjects.V21.Component - { - Originating_system = versionName, - Ifc_guid = IfcGuid.ToIfcGuid(ExportUtils.GetExportId(doc, hiddenComponent)), - Authoring_tool_id = hiddenComponent.IntegerValue.ToString() - }) - .ToList() - }; - } - - if (selectedElems.Any()) - { - bcfViewpoint.Components.Selection = new List(); - foreach (var selectedComponent in selectedElems) - { - bcfViewpoint.Components.Selection.Add(new iabi.BCF.APIObjects.V21.Component - { - Originating_system = versionName, - Ifc_guid = IfcGuid.ToIfcGuid(ExportUtils.GetExportId(doc, selectedComponent)), - Authoring_tool_id = selectedComponent.IntegerValue.ToString() - }); - } - } - } - - private static void SetViewpointClippingPlanes(BcfViewpointViewModel bcfViewpoint, UIDocument uiDoc) - { - // There are actually no clipping planes in a 3d view in revit, rather it's a - // section box, meaning a set of 6 clipping planes that wrap the visible part - // of a model inside a cuboid shape. - if (uiDoc.ActiveView is not View3D { IsSectionBoxActive: true } view3d) - return; - - bcfViewpoint.Viewpoint.Clipping_planes = new List(); - var revitSectionBox = view3d.GetSectionBox(); - - // The Min and Max represent two corners of the section box on opposite - // sides, so they're describing a cuboid 3D geometry, which we can now - // use to derive the six actual planes from - XYZ transformedMin = revitSectionBox.Transform.OfPoint(revitSectionBox.Min); - XYZ transformedMax = revitSectionBox.Transform.OfPoint(revitSectionBox.Max); - - // A clipping plane is defined as a point and a direction, so we now - // can use each point (Min and Max) three times, each with one direction in - // X, Y and Z pointing outwards from the center - // The vectors for the Min point should point away from the center (negative) - // and the vectors for the Max point should be positive - bcfViewpoint.Viewpoint.Clipping_planes = new[] - { - new - { - Location = transformedMin, - Direction = revitSectionBox.Transform.OfVector(new XYZ(-1, 0, 0)) - }, - new - { - Location = transformedMin, - Direction = revitSectionBox.Transform.OfVector(new XYZ(0, -1, 0)) - }, - new - { - Location = transformedMin, - Direction = revitSectionBox.Transform.OfVector(new XYZ(0, 0, -1)) - }, - new - { - Location = transformedMax, - Direction = revitSectionBox.Transform.OfVector(new XYZ(1, 0, 0)) - }, - new - { - Location = transformedMax, - Direction = revitSectionBox.Transform.OfVector(new XYZ(0, 1, 0)) - }, - new - { - Location = transformedMax, - Direction = revitSectionBox.Transform.OfVector(new XYZ(0, 0, 1)) - } - } - .Select(cp => new iabi.BCF.APIObjects.V21.Clipping_plane - { - Location = new iabi.BCF.APIObjects.V21.Location - { - X = Convert.ToSingle(cp.Location.X.ToMeters()), - Y = Convert.ToSingle(cp.Location.Y.ToMeters()), - Z = Convert.ToSingle(cp.Location.Z.ToMeters()) - }, - Direction = new iabi.BCF.APIObjects.V21.Direction - { - X = Convert.ToSingle(cp.Direction.X), - Y = Convert.ToSingle(cp.Direction.Y), - Z = Convert.ToSingle(cp.Direction.Z) - } - }) - .ToList(); - } - } -} diff --git a/src/OpenProject.Revit/Entry/IpcHandler.cs b/src/OpenProject.Revit/Entry/IpcHandler.cs index 6576f92a..8243bb46 100644 --- a/src/OpenProject.Revit/Entry/IpcHandler.cs +++ b/src/OpenProject.Revit/Entry/IpcHandler.cs @@ -1,16 +1,17 @@ using Autodesk.Revit.DB; using Autodesk.Revit.UI; -using OpenProject.Revit.Data; -using OpenProject.Shared.ViewModels.Bcf; using OpenProject.Shared; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Windows; +using iabi.BCF.APIObjects.V21; +using Newtonsoft.Json.Converters; +using OpenProject.Revit.Extensions; +using OpenProject.Revit.Services; +using OpenProject.Shared.BcfApi; using ZetaIpc.Runtime.Client; using ZetaIpc.Runtime.Server; using ZetaIpc.Runtime.Helper; @@ -46,35 +47,57 @@ public int StartLocalServerAndReturnPort() server.Start(freePort); server.ReceivedRequest += (_, e) => { - var eventArgs = JsonConvert.DeserializeObject(e.Request); + var eventArgs = JsonConvert.DeserializeObject(e.Request); + if (eventArgs == null) return; + var localMessageType = eventArgs.MessageType; var localTrackingId = eventArgs.TrackingId; var localMessagePayload = eventArgs.MessagePayload; - _callbackStack.Push(() => + + lock (_callbackStackLock) { - switch (localMessageType) + _callbackStack.Push(() => { - case MessageTypes.VIEWPOINT_DATA: + switch (localMessageType) { - var bcfViewpoint = MessageDeserializer.DeserializeBcfViewpoint( - new WebUIMessageEventArgs(localMessageType, localTrackingId, localMessagePayload)); - OpenView(bcfViewpoint); - break; + case MessageTypes.VIEWPOINT_DATA: + { + try + { + BcfViewpointWrapper bcfViewpoint = MessageDeserializer.DeserializeBcfViewpoint( + new WebUiMessageEventArgs(localMessageType, localTrackingId, localMessagePayload)); + OpenViewpointEventHandler.ShowBcfViewpoint(bcfViewpoint); + } + catch (Exception exception) + { + MessageHandler.ShowError(exception, "Error opening a viewpoint."); + } + + break; + } + case MessageTypes.VIEWPOINT_GENERATION_REQUESTED: + try + { + AddViewpoint(localTrackingId); + } + catch (Exception exception) + { + MessageHandler.ShowError(exception, "Error generating a viewpoint."); + } + + break; } - case MessageTypes.VIEWPOINT_GENERATION_REQUESTED: - AddView(localTrackingId); - break; - } - }); + }); + } }; return freePort; } - public void StartLocalClient(int bcfierWinServerPort) + public void StartLocalClient(int ipcWinServerPort) { var client = new IpcClient(); - client.Initialize(bcfierWinServerPort); + client.Initialize(ipcWinServerPort); _sendData = message => { try @@ -92,144 +115,60 @@ public void StartLocalClient(int bcfierWinServerPort) public void SendShutdownRequestToDesktopApp() { - var eventArgs = new WebUIMessageEventArgs(MessageTypes.CLOSE_DESKTOP_APPLICATION, "0", string.Empty); + var eventArgs = new WebUiMessageEventArgs(MessageTypes.CLOSE_DESKTOP_APPLICATION, "0", string.Empty); var jsonEventArgs = JsonConvert.SerializeObject(eventArgs); _sendData(jsonEventArgs); } - /// - /// Raises the External Event to accomplish a transaction in a modeless window - /// http://help.autodesk.com/view/RVT/2014/ENU/?guid=GUID-0A0D656E-5C44-49E8-A891-6C29F88E35C0 - /// http://matteocominetti.com/starting-a-transaction-from-an-external-application-running-outside-of-api-context-is-not-allowed/ - /// - private void OpenView(BcfViewpointViewModel bcfViewpoint) - { - try - { - var uiDoc = _uiApp.ActiveUIDocument; - - if (uiDoc.ActiveView.ViewType == ViewType.Schedule) - { - MessageBox.Show("BCFier can't take snapshots of schedules.", - "Warning!", MessageBoxButton.OK, MessageBoxImage.Warning); - return; - } - - OpenViewpointEventHandler.ShowBcfViewpoint(bcfViewpoint); - } - catch (Exception ex1) - { - TaskDialog.Show("Error opening a View!", "exception: " + ex1); - } - } - public void SendOpenSettingsRequestToDesktopApp() { - var eventArgs = new WebUIMessageEventArgs(MessageTypes.GO_TO_SETTINGS, "0", string.Empty); + var eventArgs = new WebUiMessageEventArgs(MessageTypes.GO_TO_SETTINGS, "0", string.Empty); var jsonEventArgs = JsonConvert.SerializeObject(eventArgs); _sendData(jsonEventArgs); } public void SendBringBrowserToForegroundRequestToDesktopApp() { - var eventArgs = new WebUIMessageEventArgs(MessageTypes.SET_BROWSER_TO_FOREGROUND, "0", string.Empty); + var eventArgs = new WebUiMessageEventArgs(MessageTypes.SET_BROWSER_TO_FOREGROUND, "0", string.Empty); var jsonEventArgs = JsonConvert.SerializeObject(eventArgs); _sendData(jsonEventArgs); } /// - /// Same as in the windows app, but here we generate a VisInfo that is attached to the view + /// Generates a viewpoint from the active view and sends the data as an event to bridge. /// /// The local message tracking id. - private void AddView(string trackingId) + private void AddViewpoint(string trackingId) { - try + if (_uiApp.ActiveUIDocument.ActiveView.ViewType != ViewType.ThreeD) { - if (_uiApp.ActiveUIDocument.ActiveView.ViewType != ViewType.ThreeD) - { - TaskDialog.Show("Invalid active UI document", - "To capture viewpoints the active document must be a 3D view."); - return; - } - - var generatedViewpoint = RevitView.GenerateViewpoint(_uiApp.ActiveUIDocument); - var snapshot = GetRevitSnapshot(_uiApp.ActiveUIDocument.Document); - var messageContent = new ViewpointGeneratedApiMessage - { - SnapshotPngBase64 = "data:image/png;base64," + ConvertToBase64(snapshot), - Viewpoint = MessageSerializer.SerializeBcfViewpoint(generatedViewpoint) - }; - - var serializerSettings = new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }; + MessageHandler.ShowWarning( + "Invalid view", + "Active UI document is not a 3D view", + "In order to capture BCF viewpoints, the OpenProject Revit Add-In requires a 3D view."); + return; + } - var jsonPayload = - JObject.Parse(JsonConvert.SerializeObject(messageContent.Viewpoint.Viewpoint, serializerSettings)); - if (messageContent.Viewpoint.Components != null) - { - jsonPayload["components"] = - JObject.Parse(JsonConvert.SerializeObject(messageContent.Viewpoint.Components, serializerSettings)); - } + JObject payload = GenerateJsonViewpoint(); - jsonPayload["snapshot"] = messageContent.SnapshotPngBase64; - var payloadString = jsonPayload.ToString(); + // TODO: remove hack of snapshot data once #39135 is deployed + payload["snapshot"] = payload["snapshot"]?["snapshot_data"]; - var eventArgs = new WebUIMessageEventArgs(MessageTypes.VIEWPOINT_GENERATED, trackingId, payloadString); - var jsonEventArgs = JsonConvert.SerializeObject(eventArgs); - _sendData(jsonEventArgs); - } - catch (Exception exception) - { - TaskDialog.Show("Error adding a View!", "exception: " + exception); - } + var eventArgs = new WebUiMessageEventArgs(MessageTypes.VIEWPOINT_GENERATED, trackingId, payload.ToString()); + _sendData(JsonConvert.SerializeObject(eventArgs)); } - private static Stream GetRevitSnapshot(Document doc) + private JObject GenerateJsonViewpoint() { - try - { - var tempPath = Path.Combine(Path.GetTempPath(), "BCFier"); - Directory.CreateDirectory(tempPath); - var tempImg = Path.Combine(tempPath, Path.GetTempFileName() + ".png"); - var options = new ImageExportOptions - { - FilePath = tempImg, - HLRandWFViewsFileType = ImageFileType.PNG, - ShadowViewsFileType = ImageFileType.PNG, - ExportRange = ExportRange.VisibleRegionOfCurrentView, - ZoomType = ZoomFitType.FitToPage, - ImageResolution = ImageResolution.DPI_72, - PixelSize = 1000 - }; - doc.ExportImage(options); - - var memStream = new MemoryStream(); - using (var fs = File.OpenRead(tempImg)) - { - fs.CopyTo(memStream); - } - - File.Delete(tempImg); - - memStream.Position = 0; - return memStream; - } - catch (Exception exception) + var serializerSettings = new JsonSerializerSettings { - TaskDialog.Show("Error!", "exception: " + exception); - throw; - } - } + ContractResolver = new CamelCasePropertyNamesContractResolver(), + }; - private static string ConvertToBase64(Stream stream) - { - using var memoryStream = new MemoryStream(); - stream.CopyTo(memoryStream); - var bytes = memoryStream.ToArray(); + serializerSettings.Converters.Add(new StringEnumConverter(new SnakeCaseNamingStrategy(), false)); - return Convert.ToBase64String(bytes); + Viewpoint_POST viewpoint = _uiApp.ActiveUIDocument.GenerateViewpoint(); + return JObject.Parse(JsonConvert.SerializeObject(viewpoint, serializerSettings)); } } } diff --git a/src/OpenProject.Revit/Entry/OpenViewpointEventHandler.cs b/src/OpenProject.Revit/Entry/OpenViewpointEventHandler.cs index 6a4a5546..fcbb1cee 100644 --- a/src/OpenProject.Revit/Entry/OpenViewpointEventHandler.cs +++ b/src/OpenProject.Revit/Entry/OpenViewpointEventHandler.cs @@ -2,11 +2,11 @@ using Autodesk.Revit.UI; using OpenProject.Revit.Data; using OpenProject.Revit.Extensions; -using OpenProject.Shared.ViewModels.Bcf; using System; using System.Collections.Generic; using System.Linq; using OpenProject.Revit.Services; +using OpenProject.Shared.BcfApi; using OpenProject.Shared.Math3D; using OpenProject.Shared.Math3D.Enumeration; using Serilog; @@ -37,7 +37,7 @@ public void Execute(UIApplication app) /// public string GetName() => nameof(OpenViewpointEventHandler); - private BcfViewpointViewModel _bcfViewpoint; + private BcfViewpointWrapper _bcfViewpoint; private static OpenViewpointEventHandler _instance; @@ -60,8 +60,12 @@ private static OpenViewpointEventHandler Instance /// Wraps the raising of the external event and thus the execution of the event callback, /// that show given bcf viewpoint. /// + /// + /// http://help.autodesk.com/view/RVT/2014/ENU/?guid=GUID-0A0D656E-5C44-49E8-A891-6C29F88E35C0 + /// http://matteocominetti.com/starting-a-transaction-from-an-external-application-running-outside-of-api-context-is-not-allowed/ + /// /// The bcf viewpoint to be shown in current view. - public static void ShowBcfViewpoint(BcfViewpointViewModel bcfViewpoint) + public static void ShowBcfViewpoint(BcfViewpointWrapper bcfViewpoint) { Log.Information("Received 'Opening BCF Viewpoint event'. Attempting to open viewpoint ..."); Instance._bcfViewpoint = bcfViewpoint; @@ -135,13 +139,7 @@ private static void ResetView(UIDocument uiDocument, View3D view) view.DisableTemporaryViewMode(TemporaryViewMode.RevealHiddenElements); view.IsSectionBoxActive = false; - var currentlyHiddenElements = new FilteredElementCollector(uiDocument.Document) - .WhereElementIsNotElementType() - .WhereElementIsViewIndependent() - .Where(element => element.IsHidden(view)) - .Select(element => element.Id) - .ToList(); - + var currentlyHiddenElements = uiDocument.Document.GetHiddenElementsOfView(view).ToList(); if (currentlyHiddenElements.Any()) { Log.Information("Unhide {n} currently hidden elements ...", currentlyHiddenElements.Count); @@ -177,13 +175,7 @@ private void ApplyViewOrientationAndVisibility(UIDocument uiDocument, View3D vie view.SetOrientation(viewOrientation3D); Log.Information("Applying element visibility ..."); - var currentlyVisibleElements = new FilteredElementCollector(uiDocument.Document, view.Id) - .WhereElementIsNotElementType() - .WhereElementIsViewIndependent() - .Where(element => element.CanBeHidden(view)) - .Select(element => element.Id) - .ToList(); - + var currentlyVisibleElements = uiDocument.Document.GetVisibleElementsOfView(view); var map = uiDocument.Document.GetIfcGuidElementIdMap(currentlyVisibleElements); var exceptionElements = GetViewpointVisibilityExceptions(map); var selectedElements = GetViewpointSelection(map); diff --git a/src/OpenProject.Revit/Extensions/BcfExtensions.cs b/src/OpenProject.Revit/Extensions/BcfExtensions.cs new file mode 100644 index 00000000..c8f8daf9 --- /dev/null +++ b/src/OpenProject.Revit/Extensions/BcfExtensions.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Autodesk.Revit.DB; +using Autodesk.Revit.UI; +using iabi.BCF.APIObjects.V21; +using OpenProject.Revit.Data; +using OpenProject.Shared; +using OpenProject.Shared.BcfApi; +using OpenProject.Shared.Math3D; +using OpenProject.Shared.Math3D.Enumeration; + +namespace OpenProject.Revit.Extensions +{ + /// + /// A namespace for static extensions for Revit document classes with focus on getting BCF data out + /// of the current state. + /// + public static class BcfExtensions + { + /// + /// Generates a model meant to send a viewpoint creation request to the BCF API from a Revit UI document. + /// + /// The Revit UI document + /// A BCF model for creating viewpoints. + public static Viewpoint_POST GenerateViewpoint(this UIDocument uiDocument) + { + (Orthogonal_camera ortho, Perspective_camera perspective) = uiDocument.GetBcfCameraValues(); + + return new Viewpoint_POST + { + Clipping_planes = uiDocument.GetBcfClippingPlanes(), + Snapshot = uiDocument.GetBcfSnapshotData(), + Components = uiDocument.GetBcfComponents(), + Orthogonal_camera = ortho, + Perspective_camera = perspective + }; + } + + private static List GetBcfClippingPlanes(this UIDocument uiDocument) + { + if (uiDocument.ActiveView is not View3D view3D) + return new List(); + + BoundingBoxXYZ sectionBox = view3D.GetSectionBox(); + XYZ transformedMin = sectionBox.Transform.OfPoint(sectionBox.Min); + XYZ transformedMax = sectionBox.Transform.OfPoint(sectionBox.Max); + Vector3 minCorner = transformedMin.ToVector3().ToMeters(); + Vector3 maxCorner = transformedMax.ToVector3().ToMeters(); + + return new AxisAlignedBoundingBox(minCorner, maxCorner).ToClippingPlanes(); + } + + private static Snapshot_POST GetBcfSnapshotData(this UIDocument uiDocument) + { + return new Snapshot_POST + { + Snapshot_type = Snapshot_type.Png, + Snapshot_data = "data:image/png;base64," + uiDocument.GetBase64RevitSnapshot() + }; + } + + private static Components GetBcfComponents(this UIDocument uiDocument) + { + var hiddenElementsOfView = uiDocument.Document.GetHiddenElementsOfView(uiDocument.ActiveView).ToList(); + var visibleElementsOfView = uiDocument.Document.GetVisibleElementsOfView(uiDocument.ActiveView).ToList(); + + var defaultVisibility = visibleElementsOfView.Count >= hiddenElementsOfView.Count; + var exceptions = (defaultVisibility ? hiddenElementsOfView : visibleElementsOfView) + .Select(uiDocument.Document.ElementIdToComponentSelector()) + .ToList(); + + var selectedComponents = uiDocument.Selection.GetElementIds() + .Select(uiDocument.Document.ElementIdToComponentSelector()) + .ToList(); + + return new Components + { + Selection = selectedComponents, + Visibility = new iabi.BCF.APIObjects.V21.Visibility + { + Default_visibility = defaultVisibility, + Exceptions = exceptions + } + }; + } + + private static (Orthogonal_camera orthogonalCamera, Perspective_camera perspectiveCamera) GetBcfCameraValues( + this UIDocument uiDocument) + { + if (uiDocument.ActiveView is not View3D view3D) + return (null, null); + + CameraType cameraType = view3D.IsPerspective ? CameraType.Perspective : CameraType.Orthogonal; + Position cameraPosition = uiDocument.GetCameraPosition(view3D.IsPerspective); + Vector3 center = cameraPosition.Center.ToMeters(); + + var cameraViewpoint = new iabi.BCF.APIObjects.V21.Point + { + X = Convert.ToSingle(center.X), + Y = Convert.ToSingle(center.Y), + Z = Convert.ToSingle(center.Z) + }; + + var cameraUpVector = new Direction + { + X = Convert.ToSingle(cameraPosition.Up.X), + Y = Convert.ToSingle(cameraPosition.Up.Y), + Z = Convert.ToSingle(cameraPosition.Up.Z) + }; + + var cameraDirection = new Direction + { + X = Convert.ToSingle(cameraPosition.Forward.X * -1), + Y = Convert.ToSingle(cameraPosition.Forward.Y * -1), + Z = Convert.ToSingle(cameraPosition.Forward.Z * -1) + }; + + Orthogonal_camera ortho = null; + Perspective_camera perspective = null; + + switch (cameraType) + { + case CameraType.Perspective: + perspective = new Perspective_camera + { + Field_of_view = 45, // revit default value + Camera_direction = cameraDirection, + Camera_up_vector = cameraUpVector, + Camera_view_point = cameraViewpoint + }; + break; + case CameraType.Orthogonal: + ortho = new Orthogonal_camera + { + View_to_world_scale = uiDocument.GetViewBoxHeight(), + Camera_direction = cameraDirection, + Camera_up_vector = cameraUpVector, + Camera_view_point = cameraViewpoint + }; + break; + default: + throw new ArgumentOutOfRangeException(nameof(cameraType), cameraType, "invalid camera type"); + } + + return (ortho, perspective); + } + + private static float GetViewBoxHeight(this UIDocument uiDocument) + { + var zoomCorners = uiDocument.GetOpenUIViews()[0].GetZoomCorners(); + XYZ bottomLeft = zoomCorners[0]; + XYZ topRight = zoomCorners[1]; + var (viewBoxHeight, _) = + RevitUtils.ConvertToViewBoxValues(topRight, bottomLeft, uiDocument.ActiveView.RightDirection); + + return Convert.ToSingle(viewBoxHeight.ToMeters()); + } + + private static Position GetCameraPosition(this UIDocument uiDocument, bool isPerspective) + { + ProjectPosition projectPosition = uiDocument.Document.ActiveProjectLocation.GetProjectPosition(XYZ.Zero); + var zoomCorners = uiDocument.GetOpenUIViews()[0].GetZoomCorners(); + XYZ bottomLeft = zoomCorners[0]; + XYZ topRight = zoomCorners[1]; + XYZ viewCenter = uiDocument.ActiveView.Origin; + if (!isPerspective) + viewCenter = new XYZ((topRight.X + bottomLeft.X) / 2, + (topRight.Y + bottomLeft.Y) / 2, + (topRight.Z + bottomLeft.Z) / 2); + + return RevitUtils.TransformCameraPosition( + new ProjectPositionWrapper(projectPosition), + new Position( + viewCenter.ToVector3(), + uiDocument.ActiveView.ViewDirection.ToVector3(), + uiDocument.ActiveView.UpDirection.ToVector3())); + } + + private static string GetBase64RevitSnapshot(this UIDocument uiDocument) + { + var tmpPath = ConfigurationConstant.OpenProjectTempDirectory; + Directory.CreateDirectory(tmpPath); + var tempImg = Path.Combine(tmpPath, Path.GetTempFileName() + ".png"); + var options = new ImageExportOptions + { + FilePath = tempImg, + HLRandWFViewsFileType = ImageFileType.PNG, + ShadowViewsFileType = ImageFileType.PNG, + ExportRange = ExportRange.VisibleRegionOfCurrentView, + ZoomType = ZoomFitType.FitToPage, + ImageResolution = ImageResolution.DPI_72, + PixelSize = 1000 + }; + uiDocument.Document.ExportImage(options); + + var bytes = File.ReadAllBytes(tempImg); + File.Delete(tempImg); + return Convert.ToBase64String(bytes); + } + } +} diff --git a/src/OpenProject.Revit/Extensions/RevitExtensions.cs b/src/OpenProject.Revit/Extensions/RevitDocumentExtensions.cs similarity index 61% rename from src/OpenProject.Revit/Extensions/RevitExtensions.cs rename to src/OpenProject.Revit/Extensions/RevitDocumentExtensions.cs index 3f44dd9f..5568460a 100644 --- a/src/OpenProject.Revit/Extensions/RevitExtensions.cs +++ b/src/OpenProject.Revit/Extensions/RevitDocumentExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Autodesk.Revit.DB; +using iabi.BCF.APIObjects.V21; using OpenProject.Shared; using OpenProject.Shared.Math3D.Enumeration; using Serilog; @@ -9,9 +10,9 @@ namespace OpenProject.Revit.Extensions { /// - /// Extension written for handling of classes of the Revit API. + /// Extensions written for handling of classes of the Revit API. /// - public static class RevitExtensions + public static class RevitDocumentExtensions { private const string _openProjectOrthogonalViewName = "OpenProject Orthogonal"; private const string _openProjectPerspectiveViewName = "OpenProject Perspective"; @@ -22,7 +23,8 @@ public static class RevitExtensions /// A revit document /// A list of element ids /// The map between IFC GUIDs and revit element ids. - public static Dictionary GetIfcGuidElementIdMap(this Document doc, IEnumerable elements) + public static Dictionary GetIfcGuidElementIdMap(this Document doc, + IEnumerable elements) { var map = new Dictionary(); foreach (ElementId element in elements) @@ -35,6 +37,55 @@ public static Dictionary GetIfcGuidElementIdMap(this Document return map; } + /// + /// Gets all visible elements in the given view of the document. + /// + /// The Revit document + /// The Revit view + /// A list of element ids of all elements, that are currently visible. + public static IEnumerable GetVisibleElementsOfView(this Document doc, View view) => + new FilteredElementCollector(doc, view.Id) + .WhereElementIsNotElementType() + .WhereElementIsViewIndependent() + .Where(element => element.CanBeHidden(view)) + .Select(element => element.Id); + + /// + /// Gets all invisible elements in the given view of the document. + /// + /// The Revit document + /// The Revit view + /// A list of element ids of all elements, that are currently hidden. + public static IEnumerable GetHiddenElementsOfView(this Document doc, View view) + { + bool ElementIsHiddenInView(Element element) => + element.IsHidden(view) || + element.Category is { CategoryType: CategoryType.Model } && + view.GetCategoryHidden(element.Category.Id); + + return new FilteredElementCollector(doc) + .WhereElementIsNotElementType() + .WhereElementIsViewIndependent() + .Where(ElementIsHiddenInView) + .Select(element => element.Id); + } + + /// + /// Gets a selector, that converts Revit element ids into BCF API components. + /// This is done in the context of a specific Revit Document. + /// + /// The Revit document + /// A selector converting to . + public static Func ElementIdToComponentSelector(this Document doc) + { + return id => new Component + { + Originating_system = doc.Application.VersionName, + Ifc_guid = IfcGuid.ToIfcGuid(ExportUtils.GetExportId(doc, id)), + Authoring_tool_id = id.ToString() + }; + } + /// /// Gets the correct 3D view for displaying OpenProject content. The type of the view is dependent of the requested /// camera type, either orthogonal or perspective. If the view is not yet available, it is created. @@ -42,7 +93,7 @@ public static Dictionary GetIfcGuidElementIdMap(this Document /// The current revit document. /// The camera type for the requested view. /// A with the correct settings to display OpenProject content. - /// Throws, if camera type is neither orthogonal nor perspective. + /// Throws, if camera type is neither orthogonal nor perspective. public static View3D GetOpenProjectView(this Document doc, CameraType type) { var viewName = type switch diff --git a/src/OpenProject.Shared/BcfApi/BcfApiExtensions.cs b/src/OpenProject.Shared/BcfApi/BcfApiExtensions.cs index 8990e46f..38f4d388 100644 --- a/src/OpenProject.Shared/BcfApi/BcfApiExtensions.cs +++ b/src/OpenProject.Shared/BcfApi/BcfApiExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using iabi.BCF.APIObjects.V21; using OpenProject.Shared.Math3D; @@ -8,14 +9,118 @@ public static class BcfApiExtensions { public static Vector3 ToVector3(this Direction direction) => new Vector3( - Convert.ToDecimal(direction.X), - Convert.ToDecimal(direction.Y), - Convert.ToDecimal(direction.Z)); + direction.X.ToDecimal(), + direction.Y.ToDecimal(), + direction.Z.ToDecimal()); public static Vector3 ToVector3(this Point point) => new Vector3( - Convert.ToDecimal(point.X), - Convert.ToDecimal(point.Y), - Convert.ToDecimal(point.Z)); + point.X.ToDecimal(), + point.Y.ToDecimal(), + point.Z.ToDecimal()); + + /// + /// Converts a axis aligned bounding box into a list of bcf api clipping planes. + /// + /// The bounding box that defines the clipping. Can contain infinite values, + /// which are interpreted as if the view is not clipped in that direction. + /// An optional clipping center. Important for positioning the clipping planes not + /// too far away from the model. If no clipping center is given, the center of the clipping box is used, which + /// can result in very odd clipping plane locations, if the clipping box contains infinite values. + /// A list of clipping planes. + public static List ToClippingPlanes( + this AxisAlignedBoundingBox clippingBox, + Vector3 clippingCenter = null) + { + Vector3 center = clippingCenter ?? (clippingBox.Min + clippingBox.Max) * 0.5m; + + var planes = new List(); + + if (clippingBox.Min.X.IsFinite()) + { + planes.Add(new Clipping_plane + { + Location = new Location + { + X = Convert.ToSingle(clippingBox.Min.X), + Y = Convert.ToSingle(center.Y), + Z = Convert.ToSingle(center.Z) + }, + Direction = new Direction { X = -1, Y = 0, Z = 0 } + }); + } + + if (clippingBox.Min.Y.IsFinite()) + { + planes.Add(new Clipping_plane + { + Location = new Location + { + X = Convert.ToSingle(center.X), + Y = Convert.ToSingle(clippingBox.Min.Y), + Z = Convert.ToSingle(center.Z) + }, + Direction = new Direction { X = 0, Y = -1, Z = 0 } + }); + } + + if (clippingBox.Min.Z.IsFinite()) + { + planes.Add(new Clipping_plane + { + Location = new Location + { + X = Convert.ToSingle(center.X), + Y = Convert.ToSingle(center.Y), + Z = Convert.ToSingle(clippingBox.Min.Z) + }, + Direction = new Direction { X = 0, Y = 0, Z = -1 } + }); + } + + if (clippingBox.Max.X.IsFinite()) + { + planes.Add(new Clipping_plane + { + Location = new Location + { + X = Convert.ToSingle(clippingBox.Max.X), + Y = Convert.ToSingle(center.Y), + Z = Convert.ToSingle(center.Z) + }, + Direction = new Direction { X = 1, Y = 0, Z = 0 } + }); + } + + if (clippingBox.Max.Y.IsFinite()) + { + planes.Add(new Clipping_plane + { + Location = new Location + { + X = Convert.ToSingle(center.X), + Y = Convert.ToSingle(clippingBox.Max.Y), + Z = Convert.ToSingle(center.Z) + }, + Direction = new Direction { X = 0, Y = 1, Z = 0 } + }); + } + + if (clippingBox.Max.Z.IsFinite()) + { + planes.Add(new Clipping_plane + { + Location = new Location + { + X = Convert.ToSingle(center.X), + Y = Convert.ToSingle(center.Y), + Z = Convert.ToSingle(clippingBox.Max.Z) + }, + Direction = new Direction { X = 0, Y = 0, Z = 1 } + }); + } + + return planes; + } } } diff --git a/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointViewModel.cs b/src/OpenProject.Shared/BcfApi/BcfViewpointWrapper.cs similarity index 73% rename from src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointViewModel.cs rename to src/OpenProject.Shared/BcfApi/BcfViewpointWrapper.cs index e3fdd036..8056dc4e 100644 --- a/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointViewModel.cs +++ b/src/OpenProject.Shared/BcfApi/BcfViewpointWrapper.cs @@ -1,22 +1,17 @@ -using System; -using System.Collections.Generic; -using Dangl; +using System.Collections.Generic; using iabi.BCF.APIObjects.V21; -using OpenProject.Shared.BcfApi; using OpenProject.Shared.Math3D; using Optional; -namespace OpenProject.Shared.ViewModels.Bcf +namespace OpenProject.Shared.BcfApi { /// /// A view model for BCF viewpoints. /// - public sealed class BcfViewpointViewModel : BindableBase + public sealed class BcfViewpointWrapper { public Viewpoint_GET Viewpoint { get; set; } - public string SnapshotData { get; set; } - public Components Components { get; set; } /// @@ -33,7 +28,7 @@ public Option GetCamera() var c = new PerspectiveCamera(); Perspective_camera bcfPerspective = Viewpoint.Perspective_camera; - c.FieldOfView = Convert.ToDecimal(bcfPerspective.Field_of_view); + c.FieldOfView = bcfPerspective.Field_of_view.ToDecimal(); c.Position = new Position( bcfPerspective.Camera_view_point.ToVector3(), bcfPerspective.Camera_direction.ToVector3(), @@ -47,7 +42,7 @@ public Option GetCamera() var c = new OrthogonalCamera(); Orthogonal_camera bcfOrthogonal = Viewpoint.Orthogonal_camera; - c.ViewToWorldScale = Convert.ToDecimal(bcfOrthogonal.View_to_world_scale); + c.ViewToWorldScale = bcfOrthogonal.View_to_world_scale.ToDecimal(); c.Position = new Position( bcfOrthogonal.Camera_view_point.ToVector3(), bcfOrthogonal.Camera_direction.ToVector3(), @@ -62,7 +57,8 @@ public Option GetCamera() /// /// Gets the list of visibility exceptions or an empty list if any path element is null. /// - public List GetVisibilityExceptions() => Components?.Visibility?.Exceptions ?? new List(); + public IEnumerable GetVisibilityExceptions() => + Components?.Visibility?.Exceptions ?? new List(); /// /// Gets the visibility default, or false, if any path element is null. @@ -72,11 +68,11 @@ public Option GetCamera() /// /// Gets the list of selected components or an empty list if any path element is null. /// - public List GetSelection() => Components?.Selection ?? new List(); + public IEnumerable GetSelection() => Components?.Selection ?? new List(); /// /// Gets the list of viewpoint clipping planes or an empty list if any path element is null. /// - public List GetClippingPlanes() => Viewpoint?.Clipping_planes ?? new List(); + public IEnumerable GetClippingPlanes() => Viewpoint?.Clipping_planes ?? new List(); } } diff --git a/src/OpenProject.Shared/ConfigurationConstant.cs b/src/OpenProject.Shared/ConfigurationConstant.cs index 1cc12ea3..9fb40251 100644 --- a/src/OpenProject.Shared/ConfigurationConstant.cs +++ b/src/OpenProject.Shared/ConfigurationConstant.cs @@ -18,5 +18,8 @@ public static class ConfigurationConstant public static readonly string OpenProjectBrowserExecutablePath = Path.Combine(_openProjectBrowserFolderName, _openProjectBrowserExecutableName); + + public static readonly string OpenProjectTempDirectory = + Path.Combine(Path.GetTempPath(), _openProjectRevitAddInFolderName); } } diff --git a/src/OpenProject.Shared/Math3D/Extensions.cs b/src/OpenProject.Shared/Math3D/Extensions.cs new file mode 100644 index 00000000..c917975e --- /dev/null +++ b/src/OpenProject.Shared/Math3D/Extensions.cs @@ -0,0 +1,42 @@ +using System; + +namespace OpenProject.Shared.Math3D +{ + public static class Extensions + { + /// + /// Checks a decimal number against infinity. + /// + /// A decimal value + /// true, if a decimal value is finite, false otherwise + public static bool IsFinite(this decimal number) => number > decimal.MinValue && number < decimal.MaxValue; + + /// + /// Converts a double value to a decimal value without throwing a . + /// If the double is bigger then the maximal value of decimals, the decimal maximal value is returned. + /// + /// The double value + /// The converted decimal value + public static decimal ToDecimal(this double @double) + { + if (@double < 0) + return @double < (double)decimal.MinValue ? decimal.MinValue : (decimal)@double; + + return @double > (double)decimal.MaxValue ? decimal.MaxValue : (decimal)@double; + } + + /// + /// Converts a float value to a decimal value without throwing a . + /// If the float is bigger then the maximal value of decimals, the decimal maximal value is returned. + /// + /// The float value + /// The converted decimal value + public static decimal ToDecimal(this float @float) + { + if (@float < 0) + return @float < (float)decimal.MinValue ? decimal.MinValue : (decimal)@float; + + return @float > (float)decimal.MaxValue ? decimal.MaxValue : (decimal)@float; + } + } +} diff --git a/src/OpenProject.Shared/Math3D/OrthogonalCamera.cs b/src/OpenProject.Shared/Math3D/OrthogonalCamera.cs index 87a57e3f..8d88984d 100644 --- a/src/OpenProject.Shared/Math3D/OrthogonalCamera.cs +++ b/src/OpenProject.Shared/Math3D/OrthogonalCamera.cs @@ -1,5 +1,4 @@ -using System.Runtime.CompilerServices; -using OpenProject.Shared.Math3D.Enumeration; +using OpenProject.Shared.Math3D.Enumeration; namespace OpenProject.Shared.Math3D { diff --git a/src/OpenProject.Shared/Math3D/Vector3.cs b/src/OpenProject.Shared/Math3D/Vector3.cs index 23f6eed5..21fb9025 100644 --- a/src/OpenProject.Shared/Math3D/Vector3.cs +++ b/src/OpenProject.Shared/Math3D/Vector3.cs @@ -48,6 +48,14 @@ public decimal AngleBetween(Vector3 vec) return DecimalMath.DecimalEx.ACos(this * vec / (Euclidean() * vec.Euclidean())); } + /// + /// Calculates the sum of this vector and the given one. + /// + /// The left vector. + /// The right vector. + /// The dot product value. + public static Vector3 operator +(Vector3 v1, Vector3 v2) => new Vector3(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z); + /// /// Calculates the dot product of this vector and the given one. /// diff --git a/src/OpenProject.Shared/MessageDeserializer.cs b/src/OpenProject.Shared/MessageDeserializer.cs index b9720d33..814ed419 100644 --- a/src/OpenProject.Shared/MessageDeserializer.cs +++ b/src/OpenProject.Shared/MessageDeserializer.cs @@ -1,29 +1,24 @@ using iabi.BCF.APIObjects.V21; using Newtonsoft.Json.Linq; -using OpenProject.Shared.ViewModels.Bcf; using System; +using OpenProject.Shared.BcfApi; namespace OpenProject.Shared { public static class MessageDeserializer { - public static BcfViewpointViewModel DeserializeBcfViewpoint(WebUIMessageEventArgs webUIMessage) + public static BcfViewpointWrapper DeserializeBcfViewpoint(WebUiMessageEventArgs webUiMessage) { - if (webUIMessage.MessageType != MessageTypes.VIEWPOINT_DATA) - { + if (webUiMessage.MessageType != MessageTypes.VIEWPOINT_DATA) throw new InvalidOperationException("Tried to deserialize a message with the wrong data type"); - } - var jObject = JObject.Parse(webUIMessage.MessagePayload.Trim('"').Replace("\\\"", "\"")); + JObject jObject = JObject.Parse(webUiMessage.MessagePayload.Trim('"').Replace("\\\"", "\"")); - var bcfViewpoint = new BcfViewpointViewModel + return new BcfViewpointWrapper { Viewpoint = jObject.ToObject(), - SnapshotData = jObject["snapshot"]?["snapshot_data"]?.ToString(), Components = jObject["components"]?.ToObject() }; - - return bcfViewpoint; } } } diff --git a/src/OpenProject.Shared/MessageSerializer.cs b/src/OpenProject.Shared/MessageSerializer.cs deleted file mode 100644 index 0993e598..00000000 --- a/src/OpenProject.Shared/MessageSerializer.cs +++ /dev/null @@ -1,17 +0,0 @@ -using OpenProject.Shared.ViewModels.Bcf; - -namespace OpenProject.Shared -{ - public static class MessageSerializer - { - public static ViewpointApiMessage SerializeBcfViewpoint(BcfViewpointViewModel bcfViewpointViewModel) - { - var apiViewpoint = new ViewpointApiMessage - { - Viewpoint = bcfViewpointViewModel.Viewpoint, - Components = bcfViewpointViewModel.Components - }; - return apiViewpoint; - } - } -} diff --git a/src/OpenProject.Shared/OpenProject.Shared.csproj b/src/OpenProject.Shared/OpenProject.Shared.csproj index 86f71560..c8edcdb7 100644 --- a/src/OpenProject.Shared/OpenProject.Shared.csproj +++ b/src/OpenProject.Shared/OpenProject.Shared.csproj @@ -6,7 +6,6 @@ - diff --git a/src/OpenProject.Shared/VersionsService.cs b/src/OpenProject.Shared/VersionsService.cs index e72d5b1b..1f431a7e 100644 --- a/src/OpenProject.Shared/VersionsService.cs +++ b/src/OpenProject.Shared/VersionsService.cs @@ -5,11 +5,11 @@ namespace OpenProject.Shared [System.CodeDom.Compiler.GeneratedCode("GitVersionBuild", "")] public static class VersionsService { - public static string Version => "2.3.1-beta0001"; - public static string CommitInfo => "Branch.dev.Sha.25c297551e0ff3a048396401e89b6fdf8abe9d0c"; - public static string CommitDate => "2021-09-22"; - public static string CommitHash => "25c297551e0ff3a048396401e89b6fdf8abe9d0c"; - public static string InformationalVersion => "2.3.1-beta.1+Branch.dev.Sha.25c297551e0ff3a048396401e89b6fdf8abe9d0c"; - public static DateTime BuildDateUtc { get; } = new DateTime(2021, 9, 30, 13, 53, 42, DateTimeKind.Utc); + public static string Version => "2.3.3-beta0018"; + public static string CommitInfo => "Branch.dev.Sha.d52a81904aad56d88abd4b2ad4d5969d350c52ae"; + public static string CommitDate => "2021-11-03"; + public static string CommitHash => "d52a81904aad56d88abd4b2ad4d5969d350c52ae"; + public static string InformationalVersion => "2.3.3-beta.18+Branch.dev.Sha.d52a81904aad56d88abd4b2ad4d5969d350c52ae"; + public static DateTime BuildDateUtc { get; } = new DateTime(2021, 11, 3, 14, 47, 10, DateTimeKind.Utc); } } \ No newline at end of file diff --git a/src/OpenProject.Shared/ViewModels/Bcf/BcfCommentViewModel.cs b/src/OpenProject.Shared/ViewModels/Bcf/BcfCommentViewModel.cs deleted file mode 100644 index 0a7d55f3..00000000 --- a/src/OpenProject.Shared/ViewModels/Bcf/BcfCommentViewModel.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Dangl; -using System; - -namespace OpenProject.Shared.ViewModels.Bcf -{ - public class BcfCommentViewModel : BindableBase - { - private string _author; - private string _modifiedAuthor; - private DateTime _creationDate = DateTime.UtcNow; - private DateTime? _modifiedDate; - private string _text; - private Guid _id = Guid.NewGuid(); - private string _status; - private Guid? _viewpointId; - - public string Author - { - get => _author; - set => SetProperty(ref _author, value); - } - - public string ModifiedAuthor - { - get => _modifiedAuthor; - set => SetProperty(ref _modifiedAuthor, value); - } - - public DateTime CreationDate - { - get => _creationDate; - set => SetProperty(ref _creationDate, value); - } - - public DateTime? ModifiedDate - { - get => _modifiedDate; - set => SetProperty(ref _modifiedDate, value); - } - - public string Text - { - get => _text; - set => SetProperty(ref _text, value); - } - - public Guid Id - { - get => _id; - set => SetProperty(ref _id, value); - } - - public string Status - { - get => _status; - set => SetProperty(ref _status, value); - } - - public Guid? ViewpointId - { - get => _viewpointId; - set => SetProperty(ref _viewpointId, value); - } - } -} diff --git a/src/OpenProject.Shared/ViewModels/Bcf/BcfFileViewModel.cs b/src/OpenProject.Shared/ViewModels/Bcf/BcfFileViewModel.cs deleted file mode 100644 index 674f5b05..00000000 --- a/src/OpenProject.Shared/ViewModels/Bcf/BcfFileViewModel.cs +++ /dev/null @@ -1,143 +0,0 @@ -using Dangl; -using System; -using System.Collections.ObjectModel; -using System.Linq; - -namespace OpenProject.Shared.ViewModels.Bcf -{ - public class BcfFileViewModel : BindableBase - { - public BcfFileViewModel() - { - BcfIssues.CollectionChanged += (s, e) => - { - FilterTopics(); - - if (e.NewItems?.Count > 0) - { - foreach (var bcfIssue in e.NewItems.OfType()) - { - bcfIssue.PropertyChanged += BcfIssue_PropertyChanged; - } - } - if (e.OldItems?.Count > 0) - { - foreach (var bcfIssue in e.OldItems.OfType()) - { - bcfIssue.PropertyChanged -= BcfIssue_PropertyChanged; - } - } - }; - } - - private void BcfIssue_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(BcfIssueViewModel.IsModified) - && (sender as BcfIssueViewModel).IsModified) - { - IsModified = true; - } - } - - private string _fileName; - private string _fullName; - private bool _isModified; - private Guid _id = Guid.NewGuid(); - private BcfVersion _bcfVersion; - private string _projectId; - private string _projectName; - private BcfIssueViewModel _selectedBcfIssue; - private string _textSearch; - - public string FileName - { - get => _fileName; - set => SetProperty(ref _fileName, value); - } - - public string FullName - { - get => _fullName; - set => SetProperty(ref _fullName, value); - } - - public bool IsModified - { - get => _isModified; - set => SetProperty(ref _isModified, value); - } - - public Guid Id - { - get => _id; - set => SetProperty(ref _id, value); - } - - public BcfVersion BcfVersion - { - get => _bcfVersion; - set => SetProperty(ref _bcfVersion, value); - } - - public ObservableCollection BcfIssues { get; } = new ObservableCollection(); - - public ObservableCollection BcfIssuesFiltered { get; } = new ObservableCollection(); - - public string ProjectId - { - get => _projectId; - set => SetProperty(ref _projectId, value); - } - - public string ProjectName - { - get => _projectName; - set => SetProperty(ref _projectName, value); - } - - public BcfIssueViewModel SelectedBcfIssue - { - get => _selectedBcfIssue; - set => SetProperty(ref _selectedBcfIssue, value); - } - - public string TextSearch - { - get => _textSearch; - set - { - if (SetProperty(ref _textSearch, value)) - { - FilterTopics(); - } - } - } - - private void FilterTopics() - { - BcfIssuesFiltered.Clear(); - - var searchString = TextSearch?.ToLowerInvariant(); - if (string.IsNullOrWhiteSpace(searchString)) - { - foreach (var issue in BcfIssues) - { - BcfIssuesFiltered.Add(issue); - } - return; - } - - foreach (var issue in BcfIssues) - { - var shouldShowIssue = (issue.Markup?.BcfTopic?.Title?.ToLowerInvariant().Contains(searchString) ?? false) - || (issue.Markup?.BcfTopic?.Description?.ToLowerInvariant().Contains(searchString) ?? false) - || (issue.Markup?.Comments?.Any(c => c.Text?.ToLowerInvariant()?.Contains(searchString) ?? false) ?? false); - - if (shouldShowIssue) - { - BcfIssuesFiltered.Add(issue); - } - } - } - } -} diff --git a/src/OpenProject.Shared/ViewModels/Bcf/BcfHeaderFileViewModel.cs b/src/OpenProject.Shared/ViewModels/Bcf/BcfHeaderFileViewModel.cs deleted file mode 100644 index 02815e1e..00000000 --- a/src/OpenProject.Shared/ViewModels/Bcf/BcfHeaderFileViewModel.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Dangl; -using System; - -namespace OpenProject.Shared.ViewModels.Bcf -{ - public class BcfHeaderFileViewModel : BindableBase - { - private DateTime? _fileDate; - private string _fileName; - private string _ifcProject; - private string _ifcSpatialStructureElement; - private bool _isExternal; - private string _reference; - - public DateTime? FileDate - { - get => _fileDate; - set => SetProperty(ref _fileDate, value); - } - - public string FileName - { - get => _fileName; - set => SetProperty(ref _fileName, value); - } - - public string IfcProject - { - get => _ifcProject; - set => SetProperty(ref _ifcProject, value); - } - - public string IfcSpatialStructureElement - { - get => _ifcSpatialStructureElement; - set => SetProperty(ref _ifcSpatialStructureElement, value); - } - - public bool IsExternal - { - get => _isExternal; - set => SetProperty(ref _isExternal, value); - } - - public string Reference - { - get => _reference; - set => SetProperty(ref _reference, value); - } - } -} diff --git a/src/OpenProject.Shared/ViewModels/Bcf/BcfIssueViewModel.cs b/src/OpenProject.Shared/ViewModels/Bcf/BcfIssueViewModel.cs deleted file mode 100644 index de3f0f96..00000000 --- a/src/OpenProject.Shared/ViewModels/Bcf/BcfIssueViewModel.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Dangl; -using OpenProject.Shared.ViewModels.ChangeListeners; -using System.Collections.ObjectModel; - -namespace OpenProject.Shared.ViewModels.Bcf -{ - public class BcfIssueViewModel : BindableBase - { - private BcfMarkupViewModel _markup; - private bool _isModified; - private BcfIssueViewModelChangeListener _changeListener; - - public BcfIssueViewModel() - { - _changeListener = new BcfIssueViewModelChangeListener(this); - } - - public bool DisableListeningForChanges - { - get => _changeListener.DisableListening; - set => _changeListener.DisableListening = value; - } - - public BcfMarkupViewModel Markup - { - get => _markup; - set => SetProperty(ref _markup, value); - } - - public bool IsModified - { - get => _isModified; - internal set => SetProperty(ref _isModified, value); - } - - public ObservableCollection Viewpoints { get; } = new ObservableCollection(); - } -} diff --git a/src/OpenProject.Shared/ViewModels/Bcf/BcfMarkupViewModel.cs b/src/OpenProject.Shared/ViewModels/Bcf/BcfMarkupViewModel.cs deleted file mode 100644 index 86987ee8..00000000 --- a/src/OpenProject.Shared/ViewModels/Bcf/BcfMarkupViewModel.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Dangl; -using System.Collections.ObjectModel; - -namespace OpenProject.Shared.ViewModels.Bcf -{ - public class BcfMarkupViewModel : BindableBase - { - private BcfTopicViewModel _bcfTopic; - - public BcfTopicViewModel BcfTopic - { - get => _bcfTopic; - set => SetProperty(ref _bcfTopic, value); - } - - public ObservableCollection Comments { get; } = new ObservableCollection(); - - public ObservableCollection HeaderFiles { get; } = new ObservableCollection(); - public ObservableCollection ViewpointReferences { get; } = new ObservableCollection(); - } -} diff --git a/src/OpenProject.Shared/ViewModels/Bcf/BcfMarkupViewpointReferenceViewModel.cs b/src/OpenProject.Shared/ViewModels/Bcf/BcfMarkupViewpointReferenceViewModel.cs deleted file mode 100644 index b43ea275..00000000 --- a/src/OpenProject.Shared/ViewModels/Bcf/BcfMarkupViewpointReferenceViewModel.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Dangl; -using System; - -namespace OpenProject.Shared.ViewModels.Bcf -{ - public class BcfMarkupViewpointReferenceViewModel : BindableBase - { - private Guid _id = Guid.NewGuid(); - private byte[] _snapshot; - - public Guid Id - { - get => _id; - set => SetProperty(ref _id, value); - } - - public byte[] Snapshot - { - get => _snapshot; - set => SetProperty(ref _snapshot, value); - } - } -} diff --git a/src/OpenProject.Shared/ViewModels/Bcf/BcfPointOrVectorViewModel.cs b/src/OpenProject.Shared/ViewModels/Bcf/BcfPointOrVectorViewModel.cs deleted file mode 100644 index 23a55d15..00000000 --- a/src/OpenProject.Shared/ViewModels/Bcf/BcfPointOrVectorViewModel.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Dangl; - -namespace OpenProject.Shared.ViewModels.Bcf -{ - public class BcfPointOrVectorViewModel : BindableBase - { - private double _x; - private double _y; - private double _z; - - public double X - { - get => _x; - set => SetProperty(ref _x, value); - } - - public double Y - { - get => _y; - set => SetProperty(ref _y, value); - } - - public double Z - { - get => _z; - set => SetProperty(ref _z, value); - } - } -} diff --git a/src/OpenProject.Shared/ViewModels/Bcf/BcfTopicViewModel.cs b/src/OpenProject.Shared/ViewModels/Bcf/BcfTopicViewModel.cs deleted file mode 100644 index cce92980..00000000 --- a/src/OpenProject.Shared/ViewModels/Bcf/BcfTopicViewModel.cs +++ /dev/null @@ -1,92 +0,0 @@ -using Dangl; -using System; -using System.Collections.ObjectModel; - -namespace OpenProject.Shared.ViewModels.Bcf -{ - public class BcfTopicViewModel : BindableBase - { - private Guid _id = Guid.NewGuid(); - private string _author; - private string _modifiedAuthor; - private DateTime _creationDate = DateTime.UtcNow; - private DateTime? _modifiedDate; - private string _assignedTo; - private string _description; - private string _title; - private string _status; - private string _type; - private string _priority; - - public Guid Id - { - get => _id; - set => SetProperty(ref _id, value); - } - - public string Author - { - get => _author; - set => SetProperty(ref _author, value); - } - - public string ModifiedAuthor - { - get => _modifiedAuthor; - set => SetProperty(ref _modifiedAuthor, value); - } - - public DateTime CreationDate - { - get => _creationDate; - set => SetProperty(ref _creationDate, value); - } - - public DateTime? ModifiedDate - { - get => _modifiedDate; - set => SetProperty(ref _modifiedDate, value); - } - - public string AssignedTo - { - get => _assignedTo; - set => SetProperty(ref _assignedTo, value); - } - - public string Description - { - get => _description; - set => SetProperty(ref _description, value); - } - - public string Title - { - get => _title; - set => SetProperty(ref _title, value); - } - - public string Status - { - get => _status; - set => SetProperty(ref _status, value); - } - - public string Type - { - get => _type; - set => SetProperty(ref _type, value); - } - - public string Priority - { - get => _priority; - set => SetProperty(ref _priority, value); - } - - public ObservableCollection Labels { get; } = new ObservableCollection(); - - public ObservableCollection AvailableStati { get; } = new ObservableCollection(); - public ObservableCollection AvailableTypes { get; } = new ObservableCollection(); - } -} diff --git a/src/OpenProject.Shared/ViewModels/Bcf/BcfVersion.cs b/src/OpenProject.Shared/ViewModels/Bcf/BcfVersion.cs deleted file mode 100644 index 4d737a00..00000000 --- a/src/OpenProject.Shared/ViewModels/Bcf/BcfVersion.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace OpenProject.Shared.ViewModels.Bcf -{ - public enum BcfVersion - { - Unknown, - V20, - V21 - } -} diff --git a/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointCameraViewModelBase.cs b/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointCameraViewModelBase.cs deleted file mode 100644 index e4b4451a..00000000 --- a/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointCameraViewModelBase.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Dangl; - -namespace OpenProject.Shared.ViewModels.Bcf -{ - public abstract class BcfViewpointCameraViewModelBase : BindableBase - { - private double _directionX; - private double _directionY; - private double _directionZ; - private double _upX; - private double _upY; - private double _upZ; - private double _viewPointX; - private double _viewPointY; - private double _viewPointZ; - - public double DirectionX - { - get => _directionX; - set => SetProperty(ref _directionX, value); - } - - public double DirectionY - { - get => _directionY; - set => SetProperty(ref _directionY, value); - } - - public double DirectionZ - { - get => _directionZ; - set => SetProperty(ref _directionZ, value); - } - - public double UpX - { - get => _upX; - set => SetProperty(ref _upX, value); - } - - public double UpY - { - get => _upY; - set => SetProperty(ref _upY, value); - } - - public double UpZ - { - get => _upZ; - set => SetProperty(ref _upZ, value); - } - - public double ViewPointX - { - get => _viewPointX; - set => SetProperty(ref _viewPointX, value); - } - - public double ViewPointY - { - get => _viewPointY; - set => SetProperty(ref _viewPointY, value); - } - - public double ViewPointZ - { - get => _viewPointZ; - set => SetProperty(ref _viewPointZ, value); - } - } -} diff --git a/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointClippingPlaneViewModel.cs b/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointClippingPlaneViewModel.cs deleted file mode 100644 index 7eb3ed7f..00000000 --- a/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointClippingPlaneViewModel.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Dangl; - -namespace OpenProject.Shared.ViewModels.Bcf -{ - public class BcfViewpointClippingPlaneViewModel : BindableBase - { - private BcfPointOrVectorViewModel _direction; - private BcfPointOrVectorViewModel _location; - - public BcfPointOrVectorViewModel Direction - { - get => _direction; - set => SetProperty(ref _direction, value); - } - - public BcfPointOrVectorViewModel Location - { - get => _location; - set => SetProperty(ref _location, value); - } - } -} diff --git a/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointComponentViewModel.cs b/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointComponentViewModel.cs deleted file mode 100644 index f253bef9..00000000 --- a/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointComponentViewModel.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Dangl; - -namespace OpenProject.Shared.ViewModels.Bcf -{ - public class BcfViewpointComponentViewModel : BindableBase - { - private string _authoringToolId; - private byte[] _color; - private string _ifcGuid; - private string _originatingSystem; - private bool _isSelected; - private bool _isVisible; - - public string AuthoringToolId - { - get => _authoringToolId; - set => SetProperty(ref _authoringToolId, value); - } - - public byte[] Color - { - get => _color; - set => SetProperty(ref _color, value); - } - - public string IfcGuid - { - get => _ifcGuid; - set => SetProperty(ref _ifcGuid, value); - } - - public string OriginatingSystem - { - get => _originatingSystem; - set => SetProperty(ref _originatingSystem, value); - } - - public bool IsSelected - { - get => _isSelected; - set => SetProperty(ref _isSelected, value); - } - - public bool IsVisible - { - get => _isVisible; - set => SetProperty(ref _isVisible, value); - } - } -} diff --git a/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointLineViewModel.cs b/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointLineViewModel.cs deleted file mode 100644 index 82582d47..00000000 --- a/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointLineViewModel.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Dangl; - -namespace OpenProject.Shared.ViewModels.Bcf -{ - public class BcfViewpointLineViewModel : BindableBase - { - private BcfPointOrVectorViewModel _start; - private BcfPointOrVectorViewModel _end; - - public BcfPointOrVectorViewModel Start - { - get => _start; - set => SetProperty(ref _start, value); - } - - public BcfPointOrVectorViewModel End - { - get => _end; - set => SetProperty(ref _end, value); - } - } -} diff --git a/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointOrthogonalCameraViewModel.cs b/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointOrthogonalCameraViewModel.cs deleted file mode 100644 index 965e8f46..00000000 --- a/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointOrthogonalCameraViewModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace OpenProject.Shared.ViewModels.Bcf -{ - public class BcfViewpointOrthogonalCameraViewModel : BcfViewpointCameraViewModelBase - { - private double _viewToWorldScale; - - public double ViewToWorldScale - { - get => _viewToWorldScale; - set => SetProperty(ref _viewToWorldScale, value); - } - } -} diff --git a/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointPerspectiveCameraViewModel.cs b/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointPerspectiveCameraViewModel.cs deleted file mode 100644 index 7563eda8..00000000 --- a/src/OpenProject.Shared/ViewModels/Bcf/BcfViewpointPerspectiveCameraViewModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace OpenProject.Shared.ViewModels.Bcf -{ - public class BcfViewpointPerspectiveCameraViewModel : BcfViewpointCameraViewModelBase - { - private double _fieldOfView; - - public double FieldOfView - { - get => _fieldOfView; - set => SetProperty(ref _fieldOfView, value); - } - } -} diff --git a/src/OpenProject.Shared/ViewModels/ChangeListeners/BcfIssueViewModelChangeListener.cs b/src/OpenProject.Shared/ViewModels/ChangeListeners/BcfIssueViewModelChangeListener.cs deleted file mode 100644 index 3ee70f6b..00000000 --- a/src/OpenProject.Shared/ViewModels/ChangeListeners/BcfIssueViewModelChangeListener.cs +++ /dev/null @@ -1,81 +0,0 @@ -using OpenProject.Shared.ViewModels.Bcf; -using System; -using System.ComponentModel; - -namespace OpenProject.Shared.ViewModels.ChangeListeners -{ - internal class BcfIssueViewModelChangeListener - { - private readonly BcfIssueViewModel _bcfIssue; - - public BcfIssueViewModelChangeListener(BcfIssueViewModel bcfIssue) - { - bcfIssue.PropertyChanged += BcfIssue_PropertyChanged; - _bcfIssue = bcfIssue; - - _collectionChangeListener = new CollectionChangeListener(_bcfIssue.Viewpoints); - _collectionChangeListener.PropertyChanged += (s, e) => - { - if (!DisableListening) - { - UpdateModifiedDate(); - } - }; - } - - public bool DisableListening { get; set; } - - private ChangeListener _markupChangeListener; - private CollectionChangeListener _collectionChangeListener; - - private void UpdateModifiedDate() - { - var disableListeningWasActivated = DisableListening; - DisableListening = true; - - _bcfIssue.IsModified = true; - if (_bcfIssue.Markup == null) - { - _bcfIssue.Markup = new BcfMarkupViewModel(); - } - if (_bcfIssue.Markup.BcfTopic == null) - { - _bcfIssue.Markup.BcfTopic = new BcfTopicViewModel(); - } - - _bcfIssue.Markup.BcfTopic.ModifiedDate = DateTime.UtcNow; - _bcfIssue.IsModified = true; - - if (!disableListeningWasActivated) - { - DisableListening = false; - } - } - - private void BcfIssue_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (!DisableListening) - { - UpdateModifiedDate(); - } - - if (e.PropertyName == nameof(BcfIssueViewModel.Markup)) - { - if (_markupChangeListener != null) - { - _markupChangeListener.PropertyChanged -= MarkupChangeListener_PropertyChanged; - } - _markupChangeListener = new ChangeListener(_bcfIssue.Markup); - _markupChangeListener.PropertyChanged += MarkupChangeListener_PropertyChanged; - } - } - - private void MarkupChangeListener_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (!DisableListening) - { - UpdateModifiedDate(); - } - } - } -} diff --git a/src/OpenProject.Shared/ViewModels/ChangeListeners/ChangeListener.cs b/src/OpenProject.Shared/ViewModels/ChangeListeners/ChangeListener.cs deleted file mode 100644 index a4aa8573..00000000 --- a/src/OpenProject.Shared/ViewModels/ChangeListeners/ChangeListener.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Linq; - -namespace OpenProject.Shared.ViewModels.ChangeListeners -{ - public class ChangeListener : INotifyPropertyChanged - { - public INotifyPropertyChanged NotifyPropertyChanged { get; } - - public ChangeListener(INotifyPropertyChanged notifyPropertyChanged) - { - NotifyPropertyChanged = notifyPropertyChanged; - NotifyPropertyChanged.PropertyChanged += NotifyPropertyChanged_PropertyChanged; - - var childProperties = notifyPropertyChanged.GetType() - .GetProperties() - .Where(cp => cp.PropertyType.GetInterfaces().Any(i => i == typeof(INotifyPropertyChanged))) - .Where(cp => cp.PropertyType.GetInterfaces().All(i => i != typeof(INotifyCollectionChanged))) - .ToList(); - foreach (var childProperty in childProperties) - { - if (childProperty.PropertyType.GetInterfaces().Contains(typeof(INotifyPropertyChanged))) - { - var propertyValue = childProperty.GetValue(notifyPropertyChanged) as INotifyPropertyChanged; - if (propertyValue != null) - { - ListenToChangesInChild(childProperty.Name, propertyValue); - } - } - } - - var childCollections = NotifyPropertyChanged.GetType() - .GetProperties() - .Where(cp => cp.PropertyType.GetInterfaces().Any(i => i == typeof(INotifyCollectionChanged))) - .ToList(); - foreach (var childCollection in childCollections) - { - var childCollectionChanged = childCollection.GetValue(notifyPropertyChanged) as INotifyCollectionChanged; - if (childCollectionChanged != null) - { - var collectionChangeListener = new CollectionChangeListener(childCollectionChanged); - _collectionChangeListenersByPropertyName.Add(childCollection.Name, collectionChangeListener); - collectionChangeListener.PropertyChanged += (s, e) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ChildModified))); - } - } - } - - public event PropertyChangedEventHandler PropertyChanged; - - public bool ChildModified { get; private set; } - private readonly Dictionary _changeListenersByPropertyName = new Dictionary(); - private readonly Dictionary _collectionChangeListenersByPropertyName = new Dictionary(); - - private void NotifyPropertyChanged_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - ChildModified = true; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ChildModified))); - - if (e.PropertyName == nameof(ChildModified)) - { - return; - } - - var propertyValue = sender.GetType() - .GetProperties() - .FirstOrDefault(p => p.Name == e.PropertyName) - .GetValue(sender); - - if (propertyValue is INotifyPropertyChanged notifiyPropertyChanged) - { - ListenToChangesInChild(e.PropertyName, notifiyPropertyChanged); - } - } - - private void ListenToChangesInChild(string propertyName, INotifyPropertyChanged childPropertyValue) - { - if (_changeListenersByPropertyName.ContainsKey(propertyName) - && _changeListenersByPropertyName[propertyName].NotifyPropertyChanged != childPropertyValue) - { - _changeListenersByPropertyName[propertyName].PropertyChanged -= NotifyPropertyChanged_PropertyChanged; - var newListener = new ChangeListener(childPropertyValue); - newListener.PropertyChanged += NotifyPropertyChanged_PropertyChanged; - _changeListenersByPropertyName[propertyName] = newListener; - } - else if (!_changeListenersByPropertyName.ContainsKey(propertyName)) - { - var newListener = new ChangeListener(childPropertyValue); - newListener.PropertyChanged += NotifyPropertyChanged_PropertyChanged; - _changeListenersByPropertyName.Add(propertyName, newListener); - } - } - } -} diff --git a/src/OpenProject.Shared/ViewModels/ChangeListeners/CollectionChangeListener.cs b/src/OpenProject.Shared/ViewModels/ChangeListeners/CollectionChangeListener.cs deleted file mode 100644 index fc2f4c30..00000000 --- a/src/OpenProject.Shared/ViewModels/ChangeListeners/CollectionChangeListener.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel; - -namespace OpenProject.Shared.ViewModels.ChangeListeners -{ - public class CollectionChangeListener : INotifyPropertyChanged - { - public event PropertyChangedEventHandler PropertyChanged; - - private Dictionary _changeListenersByCollectionElement = new Dictionary(); - - public bool ChildModified { get; private set; } - - public CollectionChangeListener(INotifyCollectionChanged notifyCollectionChanged) - { - notifyCollectionChanged.CollectionChanged += NotifyCollectionChanged_CollectionChanged; - - var enumerable = notifyCollectionChanged as IEnumerable; - if (enumerable != null) - { - foreach (var element in enumerable) - { - var childChangeListener = new ChangeListener(element); - childChangeListener.PropertyChanged += ChildChangeListener_PropertyChanged; - _changeListenersByCollectionElement.Add(element, childChangeListener); - } - } - } - - private void NotifyOfChange() - { - ChildModified = true; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ChangeListener.ChildModified))); - } - - private void ChildChangeListener_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - NotifyOfChange(); - } - - private void NotifyCollectionChanged_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - var hasAddedOrRemoved = false; - if (e.NewItems?.Count > 0) - { - foreach (var newItem in e.NewItems) - { - if (newItem is INotifyPropertyChanged propertyChanged) - { - hasAddedOrRemoved = true; - var childChangeListener = new ChangeListener(propertyChanged); - childChangeListener.PropertyChanged += ChildChangeListener_PropertyChanged; - _changeListenersByCollectionElement.Add(propertyChanged, childChangeListener); - } - } - } - if (e.OldItems?.Count > 0) - { - foreach (var oldItem in e.OldItems) - { - if (oldItem is INotifyPropertyChanged propertyChanged) - { - if (_changeListenersByCollectionElement.ContainsKey(oldItem)) - { - hasAddedOrRemoved = true; - _changeListenersByCollectionElement[oldItem].PropertyChanged -= ChildChangeListener_PropertyChanged; - _changeListenersByCollectionElement.Remove(oldItem); - } - } - } - } - if (e.Action == NotifyCollectionChangedAction.Move - || e.Action == NotifyCollectionChangedAction.Reset) - { - hasAddedOrRemoved = true; - if (e.Action == NotifyCollectionChangedAction.Reset) - { - foreach (var oldListener in _changeListenersByCollectionElement.Values) - { - oldListener.PropertyChanged -= ChildChangeListener_PropertyChanged; - } - _changeListenersByCollectionElement.Clear(); - } - } - - if (hasAddedOrRemoved) - { - NotifyOfChange(); - } - } - } -} diff --git a/src/OpenProject.Shared/ViewpointApiMessage.cs b/src/OpenProject.Shared/ViewpointApiMessage.cs deleted file mode 100644 index b57b094b..00000000 --- a/src/OpenProject.Shared/ViewpointApiMessage.cs +++ /dev/null @@ -1,10 +0,0 @@ -using iabi.BCF.APIObjects.V21; - -namespace OpenProject.Shared -{ - public class ViewpointApiMessage - { - public Viewpoint_GET Viewpoint { get; set; } - public Components Components { get; set; } - } -} diff --git a/src/OpenProject.Shared/ViewpointGeneratedApiMessage.cs b/src/OpenProject.Shared/ViewpointGeneratedApiMessage.cs deleted file mode 100644 index 1a760cca..00000000 --- a/src/OpenProject.Shared/ViewpointGeneratedApiMessage.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace OpenProject.Shared -{ - public class ViewpointGeneratedApiMessage - { - public ViewpointApiMessage Viewpoint { get; set; } - public string SnapshotPngBase64 { get; set; } - } -} diff --git a/src/OpenProject.Shared/WebUIMessageEventArgs.cs b/src/OpenProject.Shared/WebUIMessageEventArgs.cs deleted file mode 100644 index b8b67e2d..00000000 --- a/src/OpenProject.Shared/WebUIMessageEventArgs.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace OpenProject.Shared -{ - public class WebUIMessageEventArgs : EventArgs - { - public WebUIMessageEventArgs(string messageType, - string trackingId, - string messagePayload) - { - MessageType = messageType; - TrackingId = trackingId; - MessagePayload = messagePayload; - } - - public string MessageType { get; } - public string MessagePayload { get; } - public string TrackingId { get; } - } -} diff --git a/src/OpenProject.Shared/WebUiMessageEventArgs.cs b/src/OpenProject.Shared/WebUiMessageEventArgs.cs new file mode 100644 index 00000000..6e88dcf7 --- /dev/null +++ b/src/OpenProject.Shared/WebUiMessageEventArgs.cs @@ -0,0 +1,22 @@ +using System; + +namespace OpenProject.Shared +{ + /// + /// Immutable class wrapping event arguments for a web UI message. Contains the message type, + /// a tracking id and the payload. + /// + public sealed class WebUiMessageEventArgs : EventArgs + { + public WebUiMessageEventArgs(string messageType, string trackingId, string messagePayload) + { + MessageType = messageType; + TrackingId = trackingId; + MessagePayload = messagePayload; + } + + public string MessageType { get; } + public string MessagePayload { get; } + public string TrackingId { get; } + } +} diff --git a/test/OpenProject.Tests/Models/VersionTests.cs b/test/OpenProject.Tests/Models/VersionTests.cs new file mode 100644 index 00000000..8ac4362d --- /dev/null +++ b/test/OpenProject.Tests/Models/VersionTests.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using OpenProject.Browser.Models; +using Xunit; + +namespace OpenProject.Tests.Models +{ + public class VersionTests + { + public static IEnumerable DataForParse() + { + yield return new object[] { "0.1.0", "v0.1.0" }; + yield return new object[] { "abc", "v0.0.0" }; + yield return new object[] { "v2.42.1337", "v2.42.1337" }; + yield return new object[] { "V2.42.1337", "v2.42.1337" }; + yield return new object[] { "ver2.42.1337", "v0.0.0" }; + yield return new object[] { "0.91.2-beta3", "v0.91.2-beta3" }; + yield return new object[] { "v0.91.3-b4ef3a", "v0.91.3-b4ef3a" }; + yield return new object[] { "0.91.3.1107", "v0.91.3-1107" }; + } + + [Theory] + [MemberData(nameof(DataForParse))] + public void Parse_ReturnsExpectedVersion(string versionString, string expectedVersion) + { + // Act + Version version = Version.Parse(versionString); + + // Assert + Assert.Equal(expectedVersion, version.ToString()); + } + + public static IEnumerable DataForCompare() + { + yield return new object[] { Version.Parse("1.0.0"), Version.Parse("0.1.0"), 1 }; + yield return new object[] { Version.Parse("1.0.0"), Version.Parse("1.1.0"), -1 }; + yield return new object[] { Version.Parse("1.0.0"), Version.Parse("1.0.0"), 0 }; + yield return new object[] { Version.Parse("1.1.13"), Version.Parse("1.1.3"), 1 }; + yield return new object[] { Version.Parse("1.1.13"), Version.Parse("1.1.13-beta3"), -1 }; + yield return new object[] { Version.Parse("1.1.3-abc"), Version.Parse("1.1.3-cba"), -1 }; + yield return new object[] { Version.Parse("1.1.3-b3ee44"), Version.Parse("1.1.3-02aa3e"), 1 }; + yield return new object[] { Version.Parse("1.1.3-0006"), Version.Parse("1.1.3-0007"), -1 }; + yield return new object[] { Version.Parse("1.1.3-0006"), Version.Parse("1.1.3-00007"), 1 }; + } + + [Theory] + [MemberData(nameof(DataForCompare))] + public void Compare_FindsTheBiggerVersion(Version version1, Version version2, int expected) + { + // Act/Assert + Assert.Equal(expected, version1.CompareTo(version2)); + } + } +} diff --git a/test/OpenProject.Tests/ViewModels/Bcf/BcfViewpointViewModelTest.cs b/test/OpenProject.Tests/Shared/BcfApi/BcfViewpointWrapper.cs similarity index 86% rename from test/OpenProject.Tests/ViewModels/Bcf/BcfViewpointViewModelTest.cs rename to test/OpenProject.Tests/Shared/BcfApi/BcfViewpointWrapper.cs index 890f5807..73fcc248 100644 --- a/test/OpenProject.Tests/ViewModels/Bcf/BcfViewpointViewModelTest.cs +++ b/test/OpenProject.Tests/Shared/BcfApi/BcfViewpointWrapper.cs @@ -1,19 +1,18 @@ using System.Collections.Generic; using iabi.BCF.APIObjects.V21; using OpenProject.Shared.Math3D; -using OpenProject.Shared.ViewModels.Bcf; using Xunit; -namespace OpenProject.Tests.ViewModels.Bcf +namespace OpenProject.Tests.Shared.BcfApi { - public class BcfViewpointViewModelTest + public class BcfViewpointWrapper { public static IEnumerable GetCameraTestData() { // viewpoint with orthogonal camera yield return new object[] { - new BcfViewpointViewModel + new OpenProject.Shared.BcfApi.BcfViewpointWrapper { Viewpoint = new Viewpoint_GET { @@ -39,7 +38,7 @@ public static IEnumerable GetCameraTestData() // viewpoint with perspective camera yield return new object[] { - new BcfViewpointViewModel + new OpenProject.Shared.BcfApi.BcfViewpointWrapper { Viewpoint = new Viewpoint_GET { @@ -65,7 +64,7 @@ public static IEnumerable GetCameraTestData() // viewpoint without camera yield return new object[] { - new BcfViewpointViewModel + new OpenProject.Shared.BcfApi.BcfViewpointWrapper { Viewpoint = new Viewpoint_GET { Orthogonal_camera = null, Perspective_camera = null } }, null }; @@ -73,7 +72,7 @@ public static IEnumerable GetCameraTestData() [Theory] [MemberData(nameof(GetCameraTestData))] - public void GetCamera_ReturnsExpectedCameraForGivenViewpoint(BcfViewpointViewModel bcfViewpoint, Camera camera) + public void GetCamera_ReturnsExpectedCameraForGivenViewpoint(OpenProject.Shared.BcfApi.BcfViewpointWrapper bcfViewpoint, Camera camera) { // Act / Assert bcfViewpoint.GetCamera().Match( diff --git a/test/OpenProject.Tests/Shared/Math3D/BcfApiExtensionsTests.cs b/test/OpenProject.Tests/Shared/Math3D/BcfApiExtensionsTests.cs new file mode 100644 index 00000000..5e9e609a --- /dev/null +++ b/test/OpenProject.Tests/Shared/Math3D/BcfApiExtensionsTests.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using Castle.Core.Internal; +using iabi.BCF.APIObjects.V21; +using OpenProject.Shared.BcfApi; +using OpenProject.Shared.Math3D; +using Xunit; + +namespace OpenProject.Tests.Shared.Math3D +{ + public class BcfApiExtensionsTests + { + public static IEnumerable DataForToClippingPlane() + { + yield return new object[] + { + AxisAlignedBoundingBox.Infinite, + null, + new List() + }; + + yield return new object[] + { + new AxisAlignedBoundingBox(Vector3.InfiniteMin, new Vector3(decimal.MaxValue, decimal.MaxValue, 42)), + Vector3.Zero, + new List + { + new Clipping_plane + { + Direction = new Direction { X = 0, Y = 0, Z = 1 }, + Location = new Location { X = 0, Y = 0, Z = 42 } + } + } + }; + + yield return new object[] + { + new AxisAlignedBoundingBox(Vector3.Zero, new Vector3(1, 1, 1)), + null, + new List + { + new Clipping_plane + { + Direction = new Direction { X = -1, Y = 0, Z = 0 }, + Location = new Location { X = 0, Y = 0.5f, Z = 0.5f } + }, + new Clipping_plane + { + Direction = new Direction { X = 0, Y = -1, Z = 0 }, + Location = new Location { X = 0.5f, Y = 0, Z = 0.5f } + }, + new Clipping_plane + { + Direction = new Direction { X = 0, Y = 0, Z = -1 }, + Location = new Location { X = 0.5f, Y = 0.5f, Z = 0 } + }, + new Clipping_plane + { + Direction = new Direction { X = 1, Y = 0, Z = 0 }, + Location = new Location { X = 1, Y = 0.5f, Z = 0.5f } + }, + new Clipping_plane + { + Direction = new Direction { X = 0, Y = 1, Z = 0 }, + Location = new Location { X = 0.5f, Y = 1, Z = 0.5f } + }, + new Clipping_plane + { + Direction = new Direction { X = 0, Y = 0, Z = 1 }, + Location = new Location { X = 0.5f, Y = 0.5f, Z = 1 } + } + } + }; + + yield return new object[] + { + new AxisAlignedBoundingBox(Vector3.Zero, new Vector3(1, 1, 1)), + new Vector3(0.2m, 0.2m, 0.2m), + new List + { + new Clipping_plane + { + Direction = new Direction { X = -1, Y = 0, Z = 0 }, + Location = new Location { X = 0, Y = 0.2f, Z = 0.2f } + }, + new Clipping_plane + { + Direction = new Direction { X = 0, Y = -1, Z = 0 }, + Location = new Location { X = 0.2f, Y = 0, Z = 0.2f } + }, + new Clipping_plane + { + Direction = new Direction { X = 0, Y = 0, Z = -1 }, + Location = new Location { X = 0.2f, Y = 0.2f, Z = 0 } + }, + new Clipping_plane + { + Direction = new Direction { X = 1, Y = 0, Z = 0 }, + Location = new Location { X = 1, Y = 0.2f, Z = 0.2f } + }, + new Clipping_plane + { + Direction = new Direction { X = 0, Y = 1, Z = 0 }, + Location = new Location { X = 0.2f, Y = 1, Z = 0.2f } + }, + new Clipping_plane + { + Direction = new Direction { X = 0, Y = 0, Z = 1 }, + Location = new Location { X = 0.2f, Y = 0.2f, Z = 1 } + } + } + }; + } + + + [Theory] + [MemberData(nameof(DataForToClippingPlane))] + public void AxisAlignedBoundingBox_ToClippingPlanes_ConvertsAabbToCorrectListOfPlanes( + AxisAlignedBoundingBox box, Vector3 center, List expected) + { + // Act + var planes = box.ToClippingPlanes(center); + + // Assert + Assert.Equal(expected.Count, planes.Count); + + foreach (Clipping_plane plane in planes) + Assert.Contains(expected, p => + Math.Abs(plane.Direction.X - p.Direction.X) < 0.01 && + Math.Abs(plane.Direction.Y - p.Direction.Y) < 0.01 && + Math.Abs(plane.Direction.Z - p.Direction.Z) < 0.01 && + Math.Abs(plane.Location.X - p.Location.X) < 0.01 && + Math.Abs(plane.Location.Y - p.Location.Y) < 0.01 && + Math.Abs(plane.Location.Z - p.Location.Z) < 0.01); + } + } +} diff --git a/test/OpenProject.Tests/ViewModels/Bcf/BcfCommentviewModelTests.cs b/test/OpenProject.Tests/ViewModels/Bcf/BcfCommentviewModelTests.cs deleted file mode 100644 index 4e9f5b0f..00000000 --- a/test/OpenProject.Tests/ViewModels/Bcf/BcfCommentviewModelTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using OpenProject.Shared.ViewModels.Bcf; -using System; -using Xunit; - -namespace OpenProject.Tests.ViewModels.Bcf -{ - public class BcfCommentviewModelTests - { - [Fact] - public void InstantiatesWithRanomdGuidForId() - { - var actual = new BcfCommentViewModel().Id; - Assert.NotEqual(Guid.Empty, actual); - } - - [Fact] - public void CanSetGuidAsId() - { - var viewModel = new BcfCommentViewModel(); - var guid = Guid.NewGuid(); - Assert.NotEqual(guid, viewModel.Id); - viewModel.Id = guid; - Assert.Equal(guid, viewModel.Id); - } - - [Fact] - public void ViewpointIdIsNullAfterInstantiation() - { - var viewModel = new BcfCommentViewModel(); - Assert.Null(viewModel.ViewpointId); - } - - [Fact] - public void InstantiatesWithCreationDateSetToUTcNow() - { - var viewModel = new BcfCommentViewModel(); - Assert.True((DateTime.UtcNow - viewModel.CreationDate).TotalMinutes <= 5); - } - } -} diff --git a/test/OpenProject.Tests/ViewModels/Bcf/BcfFileViewModelTests.cs b/test/OpenProject.Tests/ViewModels/Bcf/BcfFileViewModelTests.cs deleted file mode 100644 index 515ebd90..00000000 --- a/test/OpenProject.Tests/ViewModels/Bcf/BcfFileViewModelTests.cs +++ /dev/null @@ -1,98 +0,0 @@ -using OpenProject.Shared.ViewModels.Bcf; -using System; -using Xunit; - -namespace OpenProject.Tests.ViewModels.Bcf -{ - public class BcfFileViewModelTests - { - [Fact] - public void InstantiatesWithRanomdGuidForId() - { - var actual = new BcfFileViewModel().Id; - Assert.NotEqual(Guid.Empty, actual); - } - - [Fact] - public void CanSetGuidAsId() - { - var viewModel = new BcfFileViewModel(); - var guid = Guid.NewGuid(); - Assert.NotEqual(guid, viewModel.Id); - viewModel.Id = guid; - Assert.Equal(guid, viewModel.Id); - } - - [Fact] - public void DefaultsToUnknownBcfVersion() - { - var actual = new BcfFileViewModel().BcfVersion; - Assert.Equal(BcfVersion.Unknown, actual); - } - - [Fact] - public void BcfIssuesFilteredIsInstantiatedAtStart() - { - Assert.NotNull(new BcfFileViewModel().BcfIssuesFiltered); - Assert.Empty(new BcfFileViewModel().BcfIssuesFiltered); - } - - [Fact] - public void BcfIssuesIsInstantiatedAtStart() - { - Assert.NotNull(new BcfFileViewModel().BcfIssues); - Assert.Empty(new BcfFileViewModel().BcfIssues); - } - - [Fact] - public void ReturnsUnfilteredTopicListIfNoFilter() - { - var bcfFile = new BcfFileViewModel(); - bcfFile.BcfIssues.Add(new BcfIssueViewModel()); - Assert.Single(bcfFile.BcfIssues); - Assert.Single(bcfFile.BcfIssuesFiltered); - } - - [Fact] - public void FiltersTopicsWhenTextFilterPropertyIsChangedOrTopicIsAdded() - { - var bcfFile = new BcfFileViewModel(); - bcfFile.BcfIssues.Add(new BcfIssueViewModel - { - Markup = new BcfMarkupViewModel - { - BcfTopic = new BcfTopicViewModel - { - Title = "Hello World!" - } - } - }); - Assert.Single(bcfFile.BcfIssues); - Assert.Single(bcfFile.BcfIssuesFiltered); - - bcfFile.TextSearch = "BCF"; - - Assert.Single(bcfFile.BcfIssues); - Assert.Empty(bcfFile.BcfIssuesFiltered); - - bcfFile.BcfIssues.Add(new BcfIssueViewModel - { - Markup = new BcfMarkupViewModel - { - BcfTopic = new BcfTopicViewModel - { - Title = "BCF Topic" - } - } - }); - - Assert.Equal(2, bcfFile.BcfIssues.Count); - Assert.Single(bcfFile.BcfIssuesFiltered); - - bcfFile.TextSearch = null; - - Assert.Equal(2, bcfFile.BcfIssues.Count); - Assert.Equal(2, bcfFile.BcfIssuesFiltered.Count); - } - } -} diff --git a/test/OpenProject.Tests/ViewModels/Bcf/BcfIssueViewModelTests.cs b/test/OpenProject.Tests/ViewModels/Bcf/BcfIssueViewModelTests.cs deleted file mode 100644 index 40cb1d88..00000000 --- a/test/OpenProject.Tests/ViewModels/Bcf/BcfIssueViewModelTests.cs +++ /dev/null @@ -1,152 +0,0 @@ -using OpenProject.Shared.ViewModels.Bcf; -using System.Linq; -using Xunit; - -namespace OpenProject.Tests.ViewModels.Bcf -{ - public class BcfIssueViewModelTests - { - [Fact] - public void SetsIsModifiedWhenTopicIsAdded() - { - var issue = new BcfIssueViewModel(); - Assert.False(issue.IsModified); - issue.Markup = new BcfMarkupViewModel - { - BcfTopic = new BcfTopicViewModel() - }; - - Assert.True(issue.IsModified); - Assert.NotNull(issue.Markup.BcfTopic.ModifiedDate); - } - - [Fact] - public void DoesNotUpdateIsChangedOnReleasedProperty() - { - var issue = new BcfIssueViewModel(); - Assert.False(issue.IsModified); - var markupOld = new BcfMarkupViewModel - { - BcfTopic = new BcfTopicViewModel() - }; - issue.Markup = markupOld; - issue.Markup = new BcfMarkupViewModel - { - BcfTopic = new BcfTopicViewModel() - }; - Assert.True(issue.IsModified); - Assert.NotNull(issue.Markup.BcfTopic.ModifiedDate); - - var originalModifiedDate = issue.Markup.BcfTopic.ModifiedDate.Value; - - markupOld.BcfTopic.Title = "New Title"; - Assert.Equal(originalModifiedDate, issue.Markup.BcfTopic.ModifiedDate.Value); - } - - [Fact] - public void SetsIsModifiedWhenValueInsideIsModified() - { - var issue = new BcfIssueViewModel(); - issue.DisableListeningForChanges = true; - issue.Markup = new BcfMarkupViewModel - { - BcfTopic = new BcfTopicViewModel() - }; - issue.DisableListeningForChanges = false; - - Assert.False(issue.IsModified); - issue.Markup.BcfTopic.Title = "Changed Title"; - Assert.True(issue.IsModified); - } - - [Fact] - public void UpdatesModifiedDateWhenValueIsModified_InTopic() - { - var issue = new BcfIssueViewModel(); - issue.DisableListeningForChanges = true; - issue.Markup = new BcfMarkupViewModel - { - BcfTopic = new BcfTopicViewModel() - }; - issue.DisableListeningForChanges = false; - - Assert.False(issue.IsModified); - Assert.Null(issue.Markup.BcfTopic.ModifiedDate); - issue.Markup.BcfTopic.Title = "Changed Title"; - Assert.True(issue.IsModified); - Assert.NotNull(issue.Markup.BcfTopic.ModifiedDate); - } - - [Fact] - public void UpdatesModifiedDateWhenValueIsModified_CommentAdded() - { - var issue = new BcfIssueViewModel(); - issue.DisableListeningForChanges = true; - issue.Markup = new BcfMarkupViewModel - { - BcfTopic = new BcfTopicViewModel() - }; - issue.DisableListeningForChanges = false; - - Assert.False(issue.IsModified); - Assert.Null(issue.Markup.BcfTopic.ModifiedDate); - issue.Markup.Comments.Add(new BcfCommentViewModel - { - Text = "New Comment" - }); - Assert.True(issue.IsModified); - Assert.NotNull(issue.Markup.BcfTopic.ModifiedDate); - } - - [Fact] - public void UpdatesModifiedDateWhenValueIsModified_CommentTextChanged() - { - var issue = new BcfIssueViewModel(); - issue.DisableListeningForChanges = true; - issue.Markup = new BcfMarkupViewModel - { - BcfTopic = new BcfTopicViewModel() - }; - issue.Markup.Comments.Add(new BcfCommentViewModel - { - Text = "Hello World!" - }); - issue.DisableListeningForChanges = false; - - Assert.False(issue.IsModified); - Assert.Null(issue.Markup?.BcfTopic?.ModifiedDate); - issue.Markup.Comments.Single().Text = "Hello Beautiful World!"; - Assert.True(issue.IsModified); - Assert.NotNull(issue.Markup.BcfTopic.ModifiedDate); - } - - [Fact] - public void UpdatesModifiedDateWhenValueIsModified_ViewpointAdded() - { - var issue = new BcfIssueViewModel(); - Assert.False(issue.IsModified); - Assert.Null(issue.Markup?.BcfTopic?.ModifiedDate); - issue.Viewpoints.Add(new BcfViewpointViewModel()); - Assert.True(issue.IsModified); - Assert.NotNull(issue.Markup?.BcfTopic?.ModifiedDate); - } - - [Fact] - public void DoesNotUpdateWhenMarkupSetToNullAndChangedThen() - { - var issue = new BcfIssueViewModel(); - issue.DisableListeningForChanges = true; - var markup = new BcfMarkupViewModel(); - issue.Markup = markup; - issue.DisableListeningForChanges = false; - Assert.False(issue.IsModified); - Assert.Null(issue.Markup?.BcfTopic?.ModifiedDate); - issue.Markup = null; - Assert.True(issue.IsModified); - Assert.NotNull(issue.Markup.BcfTopic.ModifiedDate); - var expectedModifiedDate = issue.Markup.BcfTopic.ModifiedDate; - markup.Comments.Add(new BcfCommentViewModel()); - Assert.Equal(expectedModifiedDate, issue.Markup.BcfTopic.ModifiedDate); - } - } -} diff --git a/test/OpenProject.Tests/ViewModels/Bcf/BcfMarkupViewpointReferenceViewModelTests.cs b/test/OpenProject.Tests/ViewModels/Bcf/BcfMarkupViewpointReferenceViewModelTests.cs deleted file mode 100644 index c71aa479..00000000 --- a/test/OpenProject.Tests/ViewModels/Bcf/BcfMarkupViewpointReferenceViewModelTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -using OpenProject.Shared.ViewModels.Bcf; -using System; -using Xunit; - -namespace OpenProject.Tests.ViewModels.Bcf -{ - public class BcfMarkupViewpointReferenceViewModelTests - { - [Fact] - public void InstantiatesWithRanomdGuidForId() - { - var actual = new BcfMarkupViewpointReferenceViewModel().Id; - Assert.NotEqual(Guid.Empty, actual); - } - - [Fact] - public void CanSetGuidAsId() - { - var viewModel = new BcfMarkupViewpointReferenceViewModel(); - var guid = Guid.NewGuid(); - Assert.NotEqual(guid, viewModel.Id); - viewModel.Id = guid; - Assert.Equal(guid, viewModel.Id); - } - } -} diff --git a/test/OpenProject.Tests/ViewModels/Bcf/BcfTopicViewModelTests.cs b/test/OpenProject.Tests/ViewModels/Bcf/BcfTopicViewModelTests.cs deleted file mode 100644 index 53c7b0da..00000000 --- a/test/OpenProject.Tests/ViewModels/Bcf/BcfTopicViewModelTests.cs +++ /dev/null @@ -1,41 +0,0 @@ -using OpenProject.Shared.ViewModels.Bcf; -using System; -using Xunit; - -namespace OpenProject.Tests.ViewModels.Bcf -{ - public class BcfTopicViewModelTests - { - [Fact] - public void InstantiatesWithRanomdGuidForId() - { - var actual = new BcfTopicViewModel().Id; - Assert.NotEqual(Guid.Empty, actual); - } - - [Fact] - public void CanSetGuidAsId() - { - var viewModel = new BcfTopicViewModel(); - var guid = Guid.NewGuid(); - Assert.NotEqual(guid, viewModel.Id); - viewModel.Id = guid; - Assert.Equal(guid, viewModel.Id); - } - - [Fact] - public void InstantiatesWithCreationDateSetToUTcNow() - { - var viewModel = new BcfTopicViewModel(); - Assert.True((DateTime.UtcNow - viewModel.CreationDate).TotalMinutes <= 5); - } - - [Fact] - public void InstantiatesWithEmptyListOfLabels() - { - var viewModel = new BcfTopicViewModel(); - Assert.NotNull(viewModel.Labels); - Assert.Empty(viewModel.Labels); - } - } -} diff --git a/test/OpenProject.Tests/ViewModels/ChangeListeners/ChangeListenerTests.cs b/test/OpenProject.Tests/ViewModels/ChangeListeners/ChangeListenerTests.cs deleted file mode 100644 index 2c30132b..00000000 --- a/test/OpenProject.Tests/ViewModels/ChangeListeners/ChangeListenerTests.cs +++ /dev/null @@ -1,117 +0,0 @@ -using OpenProject.Shared.ViewModels.Bcf; -using OpenProject.Shared.ViewModels.ChangeListeners; -using System.Linq; -using Xunit; - -namespace OpenProject.Tests.ViewModels.ChangeListeners -{ - public class ChangeListenerTests - { - [Fact] - public void NotifiesOfChangeWhenElementAdded() - { - var bcfMarkup = new BcfMarkupViewModel(); - var changeListener = new ChangeListener(bcfMarkup); - var hasNotified = false; - changeListener.PropertyChanged += (s, e) => hasNotified = true; - bcfMarkup.BcfTopic = new BcfTopicViewModel(); - Assert.True(hasNotified); - } - - [Fact] - public void NotifiesOfChangeWhenBcfTopicTitleChanged() - { - var bcfTopic = new BcfTopicViewModel(); - var changeListener = new ChangeListener(bcfTopic); - var hasNotified = false; - changeListener.PropertyChanged += (s, e) => hasNotified = true; - bcfTopic.Title = "Hello World!"; - Assert.True(hasNotified); - } - - [Fact] - public void NotifiesOfChangeWhenPropertyInChildIsChanged_WhenChangeListenerCreatedAfterPropertyIsAssigned() - { - var bcfMarkup = new BcfMarkupViewModel(); - bcfMarkup.BcfTopic = new BcfTopicViewModel(); - var changeListener = new ChangeListener(bcfMarkup); - var hasNotified = false; - changeListener.PropertyChanged += (s, e) => hasNotified = true; - bcfMarkup.BcfTopic.Title = "Hello World!"; - Assert.True(hasNotified); - } - - [Fact] - public void NotifiesOfChangeWhenPropertyInChildIsChanged_WhenChangeListenerCreatedBeforePropertyIsAssigned() - { - var bcfMarkup = new BcfMarkupViewModel(); - var changeListener = new ChangeListener(bcfMarkup); - bcfMarkup.BcfTopic = new BcfTopicViewModel(); - var hasNotified = false; - changeListener.PropertyChanged += (s, e) => hasNotified = true; - bcfMarkup.BcfTopic.Title = "Hello World!"; - Assert.True(hasNotified); - } - - [Fact] - public void NotifiesOfChangeWhenListElementAdded() - { - var bcfMarkup = new BcfMarkupViewModel(); - var changeListener = new ChangeListener(bcfMarkup); - var hasNotified = false; - changeListener.PropertyChanged += (s, e) => hasNotified = true; - bcfMarkup.Comments.Add(new BcfCommentViewModel()); - Assert.True(hasNotified); - } - - [Fact] - public void NotifiesOfChangeWhenListPropertyRemoved() - { - var bcfMarkup = new BcfMarkupViewModel(); - var changeListener = new ChangeListener(bcfMarkup); - var hasNotified = false; - bcfMarkup.Comments.Add(new BcfCommentViewModel()); - changeListener.PropertyChanged += (s, e) => hasNotified = true; - bcfMarkup.Comments.Clear(); - Assert.True(hasNotified); - } - - [Fact] - public void NotifiesOfChangeWhenPropertyInListElementChanged_WhenElementPresentBeforeListenerCreated() - { - var bcfMarkup = new BcfMarkupViewModel(); - bcfMarkup.Comments.Add(new BcfCommentViewModel()); - var changeListener = new ChangeListener(bcfMarkup); - var hasNotified = false; - changeListener.PropertyChanged += (s, e) => hasNotified = true; - bcfMarkup.Comments.Single().Text = "Hello World!"; - Assert.True(hasNotified); - } - - [Fact] - public void NotifiesOfChangeWhenPropertyInListElementChanged_WhenElementPresentAfterListenerCreated() - { - var bcfMarkup = new BcfMarkupViewModel(); - var changeListener = new ChangeListener(bcfMarkup); - var hasNotified = false; - bcfMarkup.Comments.Add(new BcfCommentViewModel()); - changeListener.PropertyChanged += (s, e) => hasNotified = true; - bcfMarkup.Comments.Single().Text = "Hello World!"; - Assert.True(hasNotified); - } - - [Fact] - public void DoesNotNotifyOfChangeInRemovedListElement() - { - var bcfMarkup = new BcfMarkupViewModel(); - var changeListener = new ChangeListener(bcfMarkup); - var hasNotified = false; - var comment = new BcfCommentViewModel(); - bcfMarkup.Comments.Add(comment); - bcfMarkup.Comments.Clear(); - changeListener.PropertyChanged += (s, e) => hasNotified = true; - comment.Text = "Hello World!"; - Assert.False(hasNotified); - } - } -} diff --git a/test/OpenProject.Tests/ViewModels/ChangeListeners/CollectionChangeListenerTests.cs b/test/OpenProject.Tests/ViewModels/ChangeListeners/CollectionChangeListenerTests.cs deleted file mode 100644 index 2c3b006d..00000000 --- a/test/OpenProject.Tests/ViewModels/ChangeListeners/CollectionChangeListenerTests.cs +++ /dev/null @@ -1,101 +0,0 @@ -using OpenProject.Shared.ViewModels.Bcf; -using OpenProject.Shared.ViewModels.ChangeListeners; -using System.Collections.ObjectModel; -using Xunit; - -namespace OpenProject.Tests.ViewModels.ChangeListeners -{ - public class CollectionChangeListenerTests - { - [Fact] - public void NotifiesOfChangeWhenListElementAdded() - { - var collection = new ObservableCollection(); - var changeListener = new CollectionChangeListener(collection); - var hasNotified = false; - changeListener.PropertyChanged += (s, e) => hasNotified = true; - collection.Add(new BcfTopicViewModel()); - Assert.True(hasNotified); - } - - [Fact] - public void NotifiesOfChangeWhenListElementRemoved() - { - var collection = new ObservableCollection(); - collection.Add(new BcfTopicViewModel()); - var changeListener = new CollectionChangeListener(collection); - var hasNotified = false; - changeListener.PropertyChanged += (s, e) => hasNotified = true; - collection.Clear(); - Assert.True(hasNotified); - } - - [Fact] - public void NotifiesOfChangeWhenPropertyInListElementChanged_WhenElementExistedBeforeListenerCreation() - { - var collection = new ObservableCollection(); - var topic = new BcfTopicViewModel(); - collection.Add(topic); - var changeListener = new CollectionChangeListener(collection); - var hasNotified = false; - changeListener.PropertyChanged += (s, e) => hasNotified = true; - topic.Title = "Hello World!"; - Assert.True(hasNotified); - } - - [Fact] - public void NotifiesOfChangeWhenPropertyInListElementChanged_WhenElementAddedAfterListenerCreation() - { - var collection = new ObservableCollection(); - var topic = new BcfTopicViewModel(); - var changeListener = new CollectionChangeListener(collection); - collection.Add(topic); - var hasNotified = false; - changeListener.PropertyChanged += (s, e) => hasNotified = true; - topic.Title = "Hello World!"; - Assert.True(hasNotified); - } - - [Fact] - public void DoesNotNotifyOfChangeInRemovedListElement_WhenElementAddedBeforeListenerCreation() - { - var collection = new ObservableCollection(); - var topic = new BcfTopicViewModel(); - collection.Add(topic); - var changeListener = new CollectionChangeListener(collection); - collection.Remove(topic); - var hasNotified = false; - changeListener.PropertyChanged += (s, e) => hasNotified = true; - topic.Title = "Hello World!"; - Assert.False(hasNotified); - } - - [Fact] - public void DoesNotNotifyOfChangeInRemovedListElement_WhenElementAddedAfterListenerCreation() - { - var collection = new ObservableCollection(); - var topic = new BcfTopicViewModel(); - var changeListener = new CollectionChangeListener(collection); - collection.Add(topic); - collection.Remove(topic); - var hasNotified = false; - changeListener.PropertyChanged += (s, e) => hasNotified = true; - topic.Title = "Hello World!"; - Assert.False(hasNotified); - } - - [Fact] - public void DoesNotNotifyOfChangeInRemovedListElement_WhenListCleared() - { - var collection = new ObservableCollection(); - var topic = new BcfTopicViewModel(); - var changeListener = new CollectionChangeListener(collection); - collection.Add(topic); - collection.Clear(); - var hasNotified = false; - changeListener.PropertyChanged += (s, e) => hasNotified = true; - topic.Title = "Hello World!"; - Assert.False(hasNotified); - } - } -}