diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 358409a..a67cee4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: Build CosmosDbExplorer on: push: branches: - - master + - "**" pull_request: branches: - master @@ -27,19 +27,37 @@ jobs: - name: Setup MSBuild.exe uses: microsoft/setup-msbuild@v1.0.2 - - name: Setup NuGet - uses: nuget/setup-nuget@v1 - with: - nuget-version: '5.7.x' + # Install the .NET Core workload + - name: Install .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '6.0.x' - - name: Restore NuGet Packages - run: nuget restore $env:Solution_Name + - name: Restore dependencies + run: dotnet restore $env:Solution_Name - name: Build Application - run: msbuild $env:Solution_Name /p:Configuration=Release + run: dotnet build $env:Solution_Name --no-restore --configuration Release #/p:Version=0.9.3.12 - name: Upload build artifacts uses: actions/upload-artifact@v2 with: name: CosmosDbExplorer - path: src\CosmosDbExplorer\bin\Release\ + path: src\CosmosDbExplorer\bin\Release\net6.0-windows + + # - name: Setup NuGet + # uses: nuget/setup-nuget@v1 + # with: + # nuget-version: '6.0.x' + + # - name: Restore NuGet Packages + # run: nuget restore $env:Solution_Name + + # - name: Build Application + # run: msbuild $env:Solution_Name /p:Configuration=Release + + # - name: Upload build artifacts + # uses: actions/upload-artifact@v2 + # with: + # name: CosmosDbExplorer + # path: src\CosmosDbExplorer\bin\Release\ diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 9371a11..d2154f7 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -37,21 +37,22 @@ jobs: - name: Setup MSBuild.exe uses: microsoft/setup-msbuild@v1.0.2 - - name: Setup NuGet - uses: nuget/setup-nuget@v1 - with: - nuget-version: '5.7.x' - - - name: Restore NuGet Packages - run: nuget restore $env:Solution_Name + # Install the .NET Core workload + - name: Install .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '6.0.x' + + - name: Restore dependencies + run: dotnet restore $env:Solution_Name - name: Build Application - run: msbuild $env:Solution_Name /p:Configuration=Release + run: dotnet build $env:Solution_Name --no-restore --configuration Release /p:Version=${{ steps.tag.outputs.version }} - name: Zip Release folder uses: papeloto/action-zip@v1 with: - files: src\CosmosDbExplorer\bin\Release\ + files: src\CosmosDbExplorer\bin\Release\net6.0-windows dest: CosmosDbExplorer.zip - name: Automatic Release @@ -81,29 +82,29 @@ jobs: - name: 'Replace tokens Chocolatey files #1' uses: datamonsters/replace-action@v2 with: - files: './src/CosmosDbExplorer/Chocolatey-Packages/cosmosdbexplorer/tools/chocolateyinstall.ps1' + files: './Chocolatey-Packages/cosmosdbexplorer/tools/chocolateyinstall.ps1' replacements: '$version=${{ steps.tag.outputs.version }},$tag-name=$GIT_TAG_NAME' - name: 'Replace tokens Chocolatey files #2' uses: datamonsters/replace-action@v2 with: - files: './src/CosmosDbExplorer/Chocolatey-Packages/cosmosdbexplorer/cosmosdbexplorer.nuspec' + files: './Chocolatey-Packages/cosmosdbexplorer/cosmosdbexplorer.nuspec' replacements: '$version=${{ steps.tag.outputs.version }},$tag-name=$GIT_TAG_NAME' - name: Pack Chocolatey uses: crazy-max/ghaction-chocolatey@v1 with: - args: pack ./src/CosmosDbExplorer/Chocolatey-Packages/cosmosdbexplorer/cosmosdbexplorer.nuspec + args: pack ./Chocolatey-Packages/cosmosdbexplorer/cosmosdbexplorer.nuspec - name: Push Chocolatey uses: crazy-max/ghaction-chocolatey@v1 with: - args: push ./src/CosmosDbExplorer/Chocolatey-Packages/cosmosdbexplorer/cosmosdbexplorer.${{ steps.tag.outputs.version }}.nupkg --source https://chocolatey.org/ --api-key=$CHOCOLATEY_API_KEY + args: push ./Chocolatey-Packages/cosmosdbexplorer/cosmosdbexplorer.${{ steps.tag.outputs.version }}.nupkg --source https://chocolatey.org/ --api-key=$CHOCOLATEY_API_KEY - name: Upload chocolatey artifacts uses: actions/upload-artifact@v2 with: - path: ./src/CosmosDbExplorer/Chocolatey-Packages/cosmosdbexplorer/cosmosdbexplorer.${{ steps.tag.outputs.version }}.nupkg + path: ./Chocolatey-Packages/cosmosdbexplorer/cosmosdbexplorer.${{ steps.tag.outputs.version }}.nupkg # Autoupdater autoupdater: diff --git a/src/CosmosDbExplorer/.editorconfig b/src/.editorconfig similarity index 100% rename from src/CosmosDbExplorer/.editorconfig rename to src/.editorconfig diff --git a/src/CosmosDbExplorer/Chocolatey-Packages/cosmosdbexplorer/ReadMe.md b/src/Chocolatey-Packages/cosmosdbexplorer/ReadMe.md similarity index 100% rename from src/CosmosDbExplorer/Chocolatey-Packages/cosmosdbexplorer/ReadMe.md rename to src/Chocolatey-Packages/cosmosdbexplorer/ReadMe.md diff --git a/src/CosmosDbExplorer/Chocolatey-Packages/cosmosdbexplorer/_TODO.txt b/src/Chocolatey-Packages/cosmosdbexplorer/_TODO.txt similarity index 100% rename from src/CosmosDbExplorer/Chocolatey-Packages/cosmosdbexplorer/_TODO.txt rename to src/Chocolatey-Packages/cosmosdbexplorer/_TODO.txt diff --git a/src/CosmosDbExplorer/Chocolatey-Packages/cosmosdbexplorer/cosmosdbexplorer.nuspec b/src/Chocolatey-Packages/cosmosdbexplorer/cosmosdbexplorer.nuspec similarity index 100% rename from src/CosmosDbExplorer/Chocolatey-Packages/cosmosdbexplorer/cosmosdbexplorer.nuspec rename to src/Chocolatey-Packages/cosmosdbexplorer/cosmosdbexplorer.nuspec diff --git a/src/CosmosDbExplorer/Chocolatey-Packages/cosmosdbexplorer/tools/chocolateyinstall.ps1 b/src/Chocolatey-Packages/cosmosdbexplorer/tools/chocolateyinstall.ps1 similarity index 90% rename from src/CosmosDbExplorer/Chocolatey-Packages/cosmosdbexplorer/tools/chocolateyinstall.ps1 rename to src/Chocolatey-Packages/cosmosdbexplorer/tools/chocolateyinstall.ps1 index 8d44f27..7fe94a6 100644 --- a/src/CosmosDbExplorer/Chocolatey-Packages/cosmosdbexplorer/tools/chocolateyinstall.ps1 +++ b/src/Chocolatey-Packages/cosmosdbexplorer/tools/chocolateyinstall.ps1 @@ -7,7 +7,9 @@ $packageName = $env:ChocolateyPackageName Install-ChocolateyZipPackage ` -PackageName $packageName ` -Url $url ` - -UnzipLocation $toolsDir + -UnzipLocation $toolsDir ` + -Checksum $checksum ` + -ChecksumType 'sha256' $softwareName = "CosmosDb Explorer" $exePath = $toolsDir + "\cosmosdbexplorer.exe" diff --git a/src/CosmosDbExplorer/Chocolatey-Packages/cosmosdbexplorer/tools/chocolateyuninstall.ps1 b/src/Chocolatey-Packages/cosmosdbexplorer/tools/chocolateyuninstall.ps1 similarity index 100% rename from src/CosmosDbExplorer/Chocolatey-Packages/cosmosdbexplorer/tools/chocolateyuninstall.ps1 rename to src/Chocolatey-Packages/cosmosdbexplorer/tools/chocolateyuninstall.ps1 diff --git a/src/CosmosDbExplorer/Infrastructure/Constants.cs b/src/CosmosDbExplorer.Core/Constants.cs similarity index 90% rename from src/CosmosDbExplorer/Infrastructure/Constants.cs rename to src/CosmosDbExplorer.Core/Constants.cs index 31d0f05..a816f5c 100644 --- a/src/CosmosDbExplorer/Infrastructure/Constants.cs +++ b/src/CosmosDbExplorer.Core/Constants.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; +using System.Text; -namespace CosmosDbExplorer.Infrastructure +namespace CosmosDbExplorer.Core { public static class Constants { diff --git a/src/CosmosDbExplorer.Core/Contracts/ICosmosDocument.cs b/src/CosmosDbExplorer.Core/Contracts/ICosmosDocument.cs new file mode 100644 index 0000000..1c9af31 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Contracts/ICosmosDocument.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Newtonsoft.Json.Linq; + +namespace CosmosDbExplorer.Core.Contracts +{ + public interface ICosmosDocument : ICosmosResource, IEquatable + { + string? Attachments { get; } + long TimeStamp { get; } + object? PartitionKey { get; } + bool HasPartitionKey { get; } + } +} diff --git a/src/CosmosDbExplorer.Core/Contracts/ICosmosQuery.cs b/src/CosmosDbExplorer.Core/Contracts/ICosmosQuery.cs new file mode 100644 index 0000000..73cdcbf --- /dev/null +++ b/src/CosmosDbExplorer.Core/Contracts/ICosmosQuery.cs @@ -0,0 +1,17 @@ +using System; +using CosmosDbExplorer.Core.Models; + +namespace CosmosDbExplorer.Core.Contracts +{ + public interface ICosmosQuery + { + string QueryText { get; set; } + int MaxItemCount { get; set; } + string? ContinuationToken { get; set; } + bool EnableScanInQuery { get; set; } + bool EnableCrossPartitionQuery { get; set; } + int MaxDOP { get; set; } + int MaxBufferItem { get; set; } + Optional PartitionKeyValue { get; set; } + } +} diff --git a/src/CosmosDbExplorer.Core/Contracts/ICosmosResource.cs b/src/CosmosDbExplorer.Core/Contracts/ICosmosResource.cs new file mode 100644 index 0000000..31e5d45 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Contracts/ICosmosResource.cs @@ -0,0 +1,9 @@ +namespace CosmosDbExplorer.Core.Contracts +{ + public interface ICosmosResource + { + string? Id { get; } + string? ETag { get; } + string? SelfLink { get; } + } +} diff --git a/src/CosmosDbExplorer.Core/Contracts/ICosmosScript.cs b/src/CosmosDbExplorer.Core/Contracts/ICosmosScript.cs new file mode 100644 index 0000000..a0444c6 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Contracts/ICosmosScript.cs @@ -0,0 +1,7 @@ +namespace CosmosDbExplorer.Core.Contracts +{ + public interface ICosmosScript : ICosmosResource + { + string Body { get; } + } +} diff --git a/src/CosmosDbExplorer.Core/Contracts/IDocumentRequestOptions.cs b/src/CosmosDbExplorer.Core/Contracts/IDocumentRequestOptions.cs new file mode 100644 index 0000000..ed1b1e8 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Contracts/IDocumentRequestOptions.cs @@ -0,0 +1,14 @@ +using CosmosDbExplorer.Core.Models; + +namespace CosmosDbExplorer.Core.Contracts +{ + public interface IDocumentRequestOptions + { + CosmosIndexingDirectives? IndexingDirective { get; set; } + CosmosConsistencyLevels? ConsistencyLevel { get; set; } + CosmosAccessConditionType AccessCondition { get; set; } + string? ETag { get; set; } + string[] PreTriggers { get; set; } + string[] PostTriggers { get; set; } + } +} diff --git a/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosClientService.cs b/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosClientService.cs new file mode 100644 index 0000000..63a0763 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosClientService.cs @@ -0,0 +1,12 @@ +using CosmosDbExplorer.Core.Models; +using Microsoft.Azure.Cosmos; + +namespace CosmosDbExplorer.Core.Contracts.Services +{ + public interface ICosmosClientService + { + CosmosClient GetClient(CosmosConnection connection); + + void DeleteClient(CosmosConnection connection); + } +} diff --git a/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosContainerService.cs b/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosContainerService.cs new file mode 100644 index 0000000..e46ec1b --- /dev/null +++ b/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosContainerService.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using CosmosDbExplorer.Core.Models; + +namespace CosmosDbExplorer.Core.Contracts.Services +{ + public interface ICosmosContainerService + { + Task CreateContainerAsync(CosmosContainer container, int? throughput, bool? isAutoscale, CancellationToken cancellationToken); + Task DeleteContainserAsync(CosmosContainer container, CancellationToken cancellationToken); + Task GetContainerMetricsAsync(CosmosContainer container, CancellationToken cancellationToken); + Task> GetContainersAsync(CancellationToken cancellationToken); + Task GetThroughputAsync(CosmosContainer container); + Task UpdateContainerAsync(CosmosContainer container); + Task UpdateThroughputAsync(CosmosContainer container, int throughput, bool isAutoscale); + } +} diff --git a/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosDatabaseService.cs b/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosDatabaseService.cs new file mode 100644 index 0000000..1f15faa --- /dev/null +++ b/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosDatabaseService.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +using CosmosDbExplorer.Core.Models; + +namespace CosmosDbExplorer.Core.Contracts.Services +{ + public interface ICosmosDatabaseService + { + Task CreateDatabaseAsync(CosmosDatabase database, int? throughput, bool? isAutoscale, CancellationToken cancellationToken); + Task DeleteDatabaseAsync(CosmosDatabase database, CancellationToken cancellationToken); + Task> GetDatabasesAsync(CancellationToken cancellationToken); + Task GetThroughputAsync(CosmosDatabase database); + Task UpdateThroughputAsync(CosmosDatabase database, int throughput, bool isAutoscale); + } +} diff --git a/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosDocumentService.cs b/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosDocumentService.cs new file mode 100644 index 0000000..764ddbc --- /dev/null +++ b/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosDocumentService.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using CosmosDbExplorer.Core.Models; +using Newtonsoft.Json.Linq; + +namespace CosmosDbExplorer.Core.Contracts.Services +{ + public interface ICosmosDocumentService + { + Task DeleteDocumentsAsync(IEnumerable documents, CancellationToken cancellationToken); + Task>> ExecuteQueryAsync(ICosmosQuery query, CancellationToken cancellationToken); + Task> GetDocumentAsync(ICosmosDocument document, IDocumentRequestOptions options, CancellationToken cancellation); + Task>> GetDocumentsAsync(string? filter, int? maxItemsCount, string? continuationToken, CancellationToken cancellationToken); + Task> SaveDocumentAsync(string content, IDocumentRequestOptions options, CancellationToken cancellation); + } +} diff --git a/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosScriptService.cs b/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosScriptService.cs new file mode 100644 index 0000000..80f9040 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosScriptService.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using CosmosDbExplorer.Core.Models; + +namespace CosmosDbExplorer.Core.Contracts.Services +{ + public interface ICosmosScriptService + { + Task DeleteStoredProcedureAsync(CosmosStoredProcedure asset); + Task DeleteTriggerAsync(CosmosTrigger asset); + Task DeleteUserDefinedFunctionAsync(CosmosUserDefinedFunction asset); + Task ExecuteStoredProcedureAsync(string storedProcedureId, string? partitionKey, dynamic[] parameters); + Task> GetStoredProceduresAsync(CancellationToken cancellationToken); + Task> GetTriggersAsync(CancellationToken cancellationToken); + Task> GetUserDefinedFunctionsAsync(CancellationToken cancellationToken); + Task SaveStoredProcedureAsync(CosmosStoredProcedure asset); + Task SaveTriggerAsync(CosmosTrigger asset); + Task SaveUserDefinedFunctionAsync(CosmosUserDefinedFunction asset); + } +} diff --git a/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosUserService.cs b/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosUserService.cs new file mode 100644 index 0000000..f663db1 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Contracts/Services/ICosmosUserService.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +using CosmosDbExplorer.Core.Models; + +namespace CosmosDbExplorer.Core.Contracts.Services +{ + public interface ICosmosUserService + { + Task> GetUsersAsync(CancellationToken cancellationToken); + } +} diff --git a/src/CosmosDbExplorer.Core/Contracts/Services/IFileService.cs b/src/CosmosDbExplorer.Core/Contracts/Services/IFileService.cs new file mode 100644 index 0000000..1376c22 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Contracts/Services/IFileService.cs @@ -0,0 +1,11 @@ +namespace CosmosDbExplorer.Core.Contracts.Services +{ + public interface IFileService + { + T Read(string folderPath, string fileName); + + void Save(string folderPath, string fileName, T content); + + void Delete(string folderPath, string fileName); + } +} diff --git a/src/CosmosDbExplorer.Core/CosmosDbExplorer.Core.csproj b/src/CosmosDbExplorer.Core/CosmosDbExplorer.Core.csproj new file mode 100644 index 0000000..2b8cbd8 --- /dev/null +++ b/src/CosmosDbExplorer.Core/CosmosDbExplorer.Core.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.1 + CosmosDbExplorer.Core + latest + enable + + + + + + + diff --git a/src/CosmosDbExplorer.Core/EnumDescriptionTypeConverter.cs b/src/CosmosDbExplorer.Core/EnumDescriptionTypeConverter.cs new file mode 100644 index 0000000..0a3818b --- /dev/null +++ b/src/CosmosDbExplorer.Core/EnumDescriptionTypeConverter.cs @@ -0,0 +1,33 @@ +using System; +using System.ComponentModel; + +namespace CosmosDbExplorer.Core +{ + public class EnumDescriptionTypeConverter : EnumConverter + { + public EnumDescriptionTypeConverter(Type type) + : base(type) + { + } + + public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) + { + if (destinationType == typeof(string)) + { + if (value != null) + { + var fi = value.GetType().GetField(value.ToString()); + if (fi != null) + { + var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); + return ((attributes.Length > 0) && (!string.IsNullOrEmpty(attributes[0].Description))) ? attributes[0].Description : value.ToString(); + } + } + + return string.Empty; + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } +} diff --git a/src/CosmosDbExplorer.Core/Helpers/CosmosExceptionHelper.cs b/src/CosmosDbExplorer.Core/Helpers/CosmosExceptionHelper.cs new file mode 100644 index 0000000..6a6b93e --- /dev/null +++ b/src/CosmosDbExplorer.Core/Helpers/CosmosExceptionHelper.cs @@ -0,0 +1,128 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; + +using Microsoft.Azure.Cosmos; + +namespace CosmosDbExplorer.Core.Helpers +{ + public static class CosmosExceptionHelper + { + public static string GetMessage(this CosmosException exception) + { + try + { + if (TryGetErrors(exception, out var errors)) + { + return errors ?? string.Empty; + } + + if (TryGetQueryException(exception, out var queryException)) + { + return queryException ?? string.Empty; + } + + if (TryGetResponseException(exception, out var responseException)) + { + return responseException ?? string.Empty; + } + + if (TryGetUpdatingOfferException(exception, out var offerException)) + { + return offerException ?? string.Empty; + } + + return exception.ResponseBody; + } + catch + { + return exception.ResponseBody; + } + } + + private static bool TryGetErrors(CosmosException exception, out string? message) + { + var regex = new Regex(@"Errors : (\[.*\])"); + var match = regex.Match(exception.Message.Replace(Environment.NewLine, string.Empty)); + + if (match.Success) + { + var json = match.Groups.Last().Value; + + var obj = Newtonsoft.Json.Linq.JArray.Parse(json); + + message = string.Join(Environment.NewLine, obj?.Values()); + return true; + } + else + { + message = null; + return false; + } + } + + private static bool TryGetUpdatingOfferException(CosmosException exception, out string? message) + { + try + { + var index = exception.ResponseBody.IndexOf(Environment.NewLine); + var start = "Message: ".Length; + + var json = exception.ResponseBody.Substring(start, index - start); + + var obj = Newtonsoft.Json.Linq.JObject.Parse(json); + + message = string.Join(Environment.NewLine, obj["Errors"]?.Values()); + return true; + } + catch + { + message = null; + return false; + } + + } + + private static bool TryGetResponseException(CosmosException exception, out string? message) + { + var regex = new Regex(@"Message:: ({.*})"); + var match = regex.Match(exception.Message); + + if (match.Success) + { + var json = match.Captures.First().Value.Replace("Message::", string.Empty); + + var obj = Newtonsoft.Json.Linq.JObject.Parse(json); + + message = string.Join(Environment.NewLine, obj["Errors"]?.Values()); + return true; + } + else + { + message = null; + return false; + } + } + + private static bool TryGetQueryException(CosmosException exception, out string? message) + { + var regex = new Regex(@"Microsoft.Azure.Cosmos.Query.Core.Exceptions.ExpectedQueryPartitionProviderException: ({.*})"); + var match = regex.Match(exception.Message); + + if (match.Success) + { + var json = match.Captures.First().Value.Replace("Microsoft.Azure.Cosmos.Query.Core.Exceptions.ExpectedQueryPartitionProviderException:", string.Empty); + + var obj = Newtonsoft.Json.Linq.JObject.Parse(json); + + message = string.Join(Environment.NewLine, obj["errors"]); + return true; + } + else + { + message = null; + return false; + } + } + } +} diff --git a/src/CosmosDbExplorer.Core/Helpers/HeadersHelper.cs b/src/CosmosDbExplorer.Core/Helpers/HeadersHelper.cs new file mode 100644 index 0000000..8e264ce --- /dev/null +++ b/src/CosmosDbExplorer.Core/Helpers/HeadersHelper.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Linq; + +using Microsoft.Azure.Cosmos; + +namespace CosmosDbExplorer.Core.Helpers +{ + public static class HeadersHelper + { + public static Dictionary ToDictionary(this Headers headers) + { + return headers.AllKeys().ToDictionary(key => key, key => headers.GetValueOrDefault(key)); + } + } +} diff --git a/src/CosmosDbExplorer.Core/Helpers/PartitionKeyHelper.cs b/src/CosmosDbExplorer.Core/Helpers/PartitionKeyHelper.cs new file mode 100644 index 0000000..063be5b --- /dev/null +++ b/src/CosmosDbExplorer.Core/Helpers/PartitionKeyHelper.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Azure.Cosmos; + +namespace CosmosDbExplorer.Core.Helpers +{ + public class PartitionKeyHelper + { + public static PartitionKey Get(object? partitionKey) + { + return partitionKey switch + { + null => PartitionKey.Null, + string s => new PartitionKey(s), + double d => new PartitionKey(d), + float f => new PartitionKey((double)f), + int i => new PartitionKey((double)i), + long l => new PartitionKey((double)l), + bool b => new PartitionKey(b), + _ => throw new ArgumentException("Partition Key type not supported") + }; + } + + public static PartitionKey? Parse(string? partitionKey) + { + if (string.IsNullOrWhiteSpace(partitionKey)) + { + return null; + } + + if (bool.TryParse(partitionKey, out var boolResult)) + { + return new PartitionKey(boolResult); + } + + if (double.TryParse(partitionKey, out var doubleResult)) + { + return new PartitionKey(doubleResult); + } + + if (string.Compare(partitionKey, "null", true) == 0) + { + return PartitionKey.Null; + } + + return new PartitionKey(partitionKey); + } + } +} diff --git a/src/CosmosDbExplorer/Infrastructure/Models/Author.cs b/src/CosmosDbExplorer.Core/Models/Author.cs similarity index 82% rename from src/CosmosDbExplorer/Infrastructure/Models/Author.cs rename to src/CosmosDbExplorer.Core/Models/Author.cs index 7457535..167e9b6 100644 --- a/src/CosmosDbExplorer/Infrastructure/Models/Author.cs +++ b/src/CosmosDbExplorer.Core/Models/Author.cs @@ -1,4 +1,4 @@ -namespace CosmosDbExplorer.Infrastructure.Models +namespace CosmosDbExplorer.Core.Models { public class Author { diff --git a/src/CosmosDbExplorer/Infrastructure/Models/Connection.cs b/src/CosmosDbExplorer.Core/Models/CosmosConnection.cs similarity index 63% rename from src/CosmosDbExplorer/Infrastructure/Models/Connection.cs rename to src/CosmosDbExplorer.Core/Models/CosmosConnection.cs index 2dbf5b8..c0a9e66 100644 --- a/src/CosmosDbExplorer/Infrastructure/Models/Connection.cs +++ b/src/CosmosDbExplorer.Core/Models/CosmosConnection.cs @@ -1,21 +1,20 @@ using System; using System.ComponentModel; -using System.Windows.Media; -using CosmosDbExplorer.Infrastructure.MarkupExtensions; +using System.Drawing; using Newtonsoft.Json; -namespace CosmosDbExplorer.Infrastructure.Models +namespace CosmosDbExplorer.Core.Models { - public class Connection : IEquatable + public class CosmosConnection : IEquatable { - public Connection(Guid id) + public CosmosConnection(Guid id) { Id = id; EnableEndpointDiscovery = true; } [JsonConstructor] - public Connection(Guid? id, string label, Uri endpoint, string secret, ConnectionType connectionType, bool enableEndpointDiscovery, Color? accentColor) + public CosmosConnection(Guid? id, string? label, Uri? endpoint, string? secret, ConnectionType connectionType, bool enableEndpointDiscovery, Color? accentColor) { Id = id ?? Guid.NewGuid(); Label = label; @@ -30,13 +29,13 @@ public Connection(Guid? id, string label, Uri endpoint, string secret, Connectio public Guid Id { get; protected set; } [JsonProperty] - public string Label { get; protected set; } + public string? Label { get; protected set; } [JsonProperty] - public Uri DatabaseUri { get; protected set; } + public Uri? DatabaseUri { get; protected set; } [JsonProperty] - public string AuthenticationKey { get; protected set; } + public string? AuthenticationKey { get; protected set; } [JsonProperty] public ConnectionType ConnectionType { get; protected set; } @@ -49,11 +48,11 @@ public Connection(Guid? id, string label, Uri endpoint, string secret, Connectio public bool IsLocalEmulator() { - return DatabaseUri == Constants.Emulator.Endpoint + return Constants.Emulator.Endpoint.Equals(DatabaseUri) && AuthenticationKey == Constants.Emulator.Secret; } - public bool Equals(Connection other) + public bool Equals(CosmosConnection other) { return Id.Equals(other.Id); } @@ -67,10 +66,9 @@ public override int GetHashCode() [TypeConverter(typeof(EnumDescriptionTypeConverter))] public enum ConnectionType { + [Description("Gateway")] Gateway, - [Description("Direct HTTPS")] - DirectHttps, - [Description("Direct TCP")] - DirectTcp + [Description("Direct")] + Direct, } } diff --git a/src/CosmosDbExplorer.Core/Models/CosmosContainer.cs b/src/CosmosDbExplorer.Core/Models/CosmosContainer.cs new file mode 100644 index 0000000..d7ef312 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Models/CosmosContainer.cs @@ -0,0 +1,68 @@ +using CosmosDbExplorer.Core.Contracts; +using Microsoft.Azure.Cosmos; +using Newtonsoft.Json; + +namespace CosmosDbExplorer.Core.Models +{ + public class CosmosContainer : ICosmosResource + { + public CosmosContainer(ContainerProperties properties) + { + Id = properties.Id; + ETag = properties.ETag; + SelfLink = properties.SelfLink; + DefaultTimeToLive = properties.DefaultTimeToLive; + PartitionKeyPath = properties.PartitionKeyPath; + PartitionKeyDefVersion = properties.PartitionKeyDefinitionVersion; + IndexingPolicy = JsonConvert.SerializeObject(properties.IndexingPolicy, Formatting.Indented); + GeospatialType = properties.GeospatialConfig.GeospatialType.ToLocalType(); + } + + public CosmosContainer(string id, bool isLargePartition) + { + Id = id; + PartitionKeyDefVersion = isLargePartition + ? PartitionKeyDefinitionVersion.V2 + : PartitionKeyDefinitionVersion.V1; + } + + public string Id { get; } + public string ETag { get; } + public string SelfLink { get; } + public string PartitionKeyPath { get; set; } + public string? PartitionKeyJsonPath => string.IsNullOrEmpty(PartitionKeyPath) ? null : PartitionKeyPath.Replace('/', '.'); + public bool? IsLargePartitionKey => PartitionKeyDefVersion > PartitionKeyDefinitionVersion.V1; + public int? DefaultTimeToLive { get; set; } // null = off, -1 = Default + public PartitionKeyDefinitionVersion? PartitionKeyDefVersion { get; } + public string IndexingPolicy { get; set; } + public CosmosGeospatialType GeospatialType { get; set; } + } + + public enum CosmosGeospatialType + { + Geography = 0, + Geometry = 1 + } + + public static class GeospatialTypeExtensions + { + public static CosmosGeospatialType ToLocalType(this GeospatialType geospatialType) + { + return geospatialType switch + { + GeospatialType.Geometry => CosmosGeospatialType.Geometry, + _ => CosmosGeospatialType.Geography, + }; + } + + public static GeospatialType FromLocalType(this CosmosGeospatialType geospatialType) + { + return geospatialType switch + { + CosmosGeospatialType.Geography => GeospatialType.Geography, + _ => GeospatialType.Geometry, + }; + } + } +} + diff --git a/src/CosmosDbExplorer.Core/Models/CosmosContainerMetric.cs b/src/CosmosDbExplorer.Core/Models/CosmosContainerMetric.cs new file mode 100644 index 0000000..e3a8056 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Models/CosmosContainerMetric.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Azure.Cosmos; + +namespace CosmosDbExplorer.Core.Models +{ + public class CosmosContainerMetric + { + public CosmosContainerMetric(ContainerResponse containerResponse) + { + var quota = Parse(containerResponse.Headers, "x-ms-resource-quota"); + var usage = Parse(containerResponse.Headers, "x-ms-resource-usage"); + + RequestCharge = containerResponse.RequestCharge; + //PartitionCount = containerResponse..Resource.PartitionKeyRangeStatistics.Count; + DocumentsSizeQuota = quota["documentsSize"]; + DocumentsSizeUsage = usage["documentsSize"]; + DocumentsCountQuota = quota["documentsCount"]; + DocumentsCountUsage = usage["documentsCount"]; + CollectionSizeQuota = quota["collectionSize"]; + CollectionSizeUsage = usage["collectionSize"]; + StoredProceduresQuota = quota["storedProcedures"]; + StoredProceduresUsage = usage["storedProcedures"]; + TriggersQuota = quota["triggers"]; + TriggersUsage = usage["triggers"]; + UserDefinedFunctionsQuota = quota["functions"]; + UserDefinedFunctionsUsage = usage["functions"]; + } + + public double RequestCharge { get; } + public int PartitionCount { get; } + public long DocumentsCountQuota { get; } + public long DocumentsCountUsage { get; } + public long DocumentsSizeQuota { get; } + public long DocumentsSizeUsage { get; } + //public List PartitionMetrics { get; } + public long CollectionSizeQuota { get; } + public long CollectionSizeUsage { get; } + public long StoredProceduresQuota { get; } + public long StoredProceduresUsage { get; } + public long TriggersQuota { get; } + public long TriggersUsage { get; } + public long UserDefinedFunctionsQuota { get; } + public long UserDefinedFunctionsUsage { get; } + public bool HasPartitionKey { get; } + + private Dictionary Parse(Headers headers, string headerName) + { + return headers[headerName] + .Split(';') + .Select(item => new { Key = item.Split('=')[0], Value = item.Split('=')[1] }) + .ToDictionary(d => d.Key, d => long.Parse(d.Value)); + } + } +} diff --git a/src/CosmosDbExplorer.Core/Models/CosmosDatabase.cs b/src/CosmosDbExplorer.Core/Models/CosmosDatabase.cs new file mode 100644 index 0000000..5de63e5 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Models/CosmosDatabase.cs @@ -0,0 +1,28 @@ +using CosmosDbExplorer.Core.Contracts; +using Microsoft.Azure.Cosmos; + +namespace CosmosDbExplorer.Core.Models +{ + public class CosmosDatabase : ICosmosResource + { + public CosmosDatabase(string id) + { + Id = id; + } + + public CosmosDatabase(DatabaseProperties properties, int? throughput, bool isServerless) + { + Id = properties.Id; + ETag = properties.ETag; + SelfLink = properties.SelfLink; + Throughput = throughput; + IsServerless = isServerless; + } + + public string Id { get; } + public string ETag { get; } + public string SelfLink { get; } + public int? Throughput { get; } // Null value indicates a container with no throughput provisioned. + public bool IsServerless { get; } + } +} diff --git a/src/CosmosDbExplorer.Core/Models/CosmosDocument.cs b/src/CosmosDbExplorer.Core/Models/CosmosDocument.cs new file mode 100644 index 0000000..9a4a3f3 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Models/CosmosDocument.cs @@ -0,0 +1,74 @@ +using System; +using CosmosDbExplorer.Core.Contracts; +using CosmosDbExplorer.Core.Services; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace CosmosDbExplorer.Core.Models +{ + public class CosmosDocument : ICosmosDocument + { + + public static CosmosDocument CreateFrom(JObject resource, string? partitionPath) + { + var instance = resource.ToObject(); + + if (instance is null) + { + throw new Exception("Cannot create CosmosDocument"); + } + + if (partitionPath is not null) + { + var doc = new Document(resource, partitionPath); + + if (doc.PK is not null) + { + instance.HasPartitionKey = true; + instance.PartitionKey = doc.PK; + } + } + + return instance; + } + + [JsonProperty("id")] + public string? Id { get; set; } + + [JsonProperty("_etag")] + public string? ETag { get; set; } + + [JsonProperty("_self")] + public string? SelfLink { get; set; } + + [JsonProperty("_attachments")] + public string? Attachments { get; set; } + + [JsonProperty("_ts")] + public long TimeStamp { get; set; } + + [JsonProperty("_partitionKey")] + public object? PartitionKey { get; set; } + + [JsonProperty("_hasPartitionKey")] + public bool HasPartitionKey { get; set; } + + public override bool Equals(object? obj) + { + return Equals(obj as ICosmosDocument); + } + + public bool Equals(ICosmosDocument? other) + { + return other != null + && Id == other.Id + && PartitionKey == other.PartitionKey; + } + + public override int GetHashCode() + { + return HashCode.Combine(Id, SelfLink, PartitionKey); + } + } +} diff --git a/src/CosmosDbExplorer.Core/Models/CosmosPermission.cs b/src/CosmosDbExplorer.Core/Models/CosmosPermission.cs new file mode 100644 index 0000000..a04b386 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Models/CosmosPermission.cs @@ -0,0 +1,52 @@ + +using System; +using System.Linq; + +using CosmosDbExplorer.Core.Contracts; + +using Microsoft.Azure.Cosmos; + +namespace CosmosDbExplorer.Core.Models +{ + public class CosmosPermission : ICosmosResource + { + public CosmosPermission(PermissionProperties properties) + { + Id = properties.Id; + ETag = properties.ETag; + SelfLink = properties.SelfLink; + PermissionMode = (CosmosPermissionMode)properties.PermissionMode; + PartitionKey = properties.ResourcePartitionKey?.ToString().Trim('[', '"', ']'); + ResourceUri = properties.ResourceUri.Split('/').Last(); + Token = properties.Token; + LastModifed = properties.LastModified; + } + + public CosmosPermission() + { + PermissionMode = CosmosPermissionMode.Read; + } + + public string? Id { get; set; } + public string? ETag { get; } + public string? SelfLink { get; } + public CosmosPermissionMode PermissionMode { get; set; } + public string? PartitionKey; + public string ResourceUri { get; set; } + public string Token { get; } + public DateTime? LastModifed { get; } + } + + public enum CosmosPermissionMode : byte + { + // + // Summary: + // Read permission mode will provide the user with Read only access to a resource. + Read = 0x1, + // + // Summary: + // All permission mode will provide the user with full access(read, insert, replace + // and delete) to a resource. + All = 0x2 + } +} diff --git a/src/CosmosDbExplorer.Core/Models/CosmosQuery.cs b/src/CosmosDbExplorer.Core/Models/CosmosQuery.cs new file mode 100644 index 0000000..86f1220 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Models/CosmosQuery.cs @@ -0,0 +1,16 @@ +using CosmosDbExplorer.Core.Contracts; + +namespace CosmosDbExplorer.Core.Models +{ + public class CosmosQuery : ICosmosQuery + { + public string QueryText { get; set; } = string.Empty; + public int MaxItemCount { get; set; } = 100; + public string? ContinuationToken { get; set; } + public bool EnableScanInQuery { get; set; } = false; + public bool EnableCrossPartitionQuery { get; set; } = false; + public int MaxDOP { get; set; } = -1; + public int MaxBufferItem { get; set; } = -1; + public Optional PartitionKeyValue { get; set; } = new Optional(null); + } +} diff --git a/src/CosmosDbExplorer.Core/Models/CosmosQueryResult.cs b/src/CosmosDbExplorer.Core/Models/CosmosQueryResult.cs new file mode 100644 index 0000000..fc9836e --- /dev/null +++ b/src/CosmosDbExplorer.Core/Models/CosmosQueryResult.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using CosmosDbExplorer.Core.Helpers; + +using Microsoft.Azure.Cosmos.Scripts; + +using Newtonsoft.Json.Linq; + +namespace CosmosDbExplorer.Core.Models +{ + public class CosmosResult + { + public double RequestCharge { get; internal set; } + public TimeSpan TimeElapsed { get; internal set; } + } + + public class CosmosQueryResult : CosmosResult + { + public T? Items { get; internal set; } + public Exception? Error { get; internal set; } + public string? ContinuationToken { get; internal set; } + public IEnumerable Warnings { get; internal set; } = Array.Empty(); + public Dictionary Headers { get; internal set; } = new Dictionary(); + public bool HasMore => !string.IsNullOrEmpty(ContinuationToken); + public JObject? Diagnostics { get; internal set; } + public string? IndexMetrics { get; internal set; } + } + + public class CosmosStoredProcedureResult : CosmosResult + { + + public CosmosStoredProcedureResult(StoredProcedureExecuteResponse response) + { + RequestCharge = response.RequestCharge; + ScriptLog = response.ScriptLog; + Headers = response.Headers.ToDictionary(); + Result = response.Resource; + } + + public string Result { get; } + public string ScriptLog { get; } + public Dictionary Headers { get; internal set; } = new Dictionary(); + } +} diff --git a/src/CosmosDbExplorer.Core/Models/CosmosStoredProcedure.cs b/src/CosmosDbExplorer.Core/Models/CosmosStoredProcedure.cs new file mode 100644 index 0000000..d9aba74 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Models/CosmosStoredProcedure.cs @@ -0,0 +1,32 @@ +using System; +using CosmosDbExplorer.Core.Contracts; +using Microsoft.Azure.Cosmos.Scripts; + +namespace CosmosDbExplorer.Core.Models +{ + public class CosmosStoredProcedure : ICosmosScript + { + public CosmosStoredProcedure(string id, string body, string? selfLink) + { + Id = id; + Body = body; + SelfLink = selfLink; + } + + public CosmosStoredProcedure(StoredProcedureProperties properties) + { + Id = properties.Id; + ETag = properties.ETag; + SelfLink = properties.SelfLink; + + Body = properties.Body; + LastModified = properties.LastModified; + } + + public string? Id { get; set; } + public string? ETag { get; private set; } + public string? SelfLink { get; private set; } + public string Body { get; } + public DateTime? LastModified { get; private set; } + } +} diff --git a/src/CosmosDbExplorer.Core/Models/CosmosThroughput.cs b/src/CosmosDbExplorer.Core/Models/CosmosThroughput.cs new file mode 100644 index 0000000..778fa50 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Models/CosmosThroughput.cs @@ -0,0 +1,29 @@ +using CosmosDbExplorer.Core.Contracts; + +using Microsoft.Azure.Cosmos; + +namespace CosmosDbExplorer.Core.Models +{ + public class CosmosThroughput : ICosmosResource + { + public CosmosThroughput(ThroughputResponse response) + { + ETag = response.ETag; + SelfLink = response.Resource.SelfLink; + IsReplacingPending = response.IsReplacePending.GetValueOrDefault(); + MinThroughtput = response.MinThroughput; + AutoscaleMaxThroughput = response.Resource.AutoscaleMaxThroughput; + Throughput = response.Resource.Throughput; + RequestCharge = response.RequestCharge; + } + + public string? Id { get; } + public string? ETag { get; } + public string? SelfLink { get; } + public bool IsReplacingPending { get; } + public int? MinThroughtput { get; } + public int? AutoscaleMaxThroughput { get; } + public int? Throughput { get; } + public double RequestCharge { get; } + } +} diff --git a/src/CosmosDbExplorer.Core/Models/CosmosTrigger.cs b/src/CosmosDbExplorer.Core/Models/CosmosTrigger.cs new file mode 100644 index 0000000..0f00eda --- /dev/null +++ b/src/CosmosDbExplorer.Core/Models/CosmosTrigger.cs @@ -0,0 +1,70 @@ +using System; +using CosmosDbExplorer.Core.Contracts; +using Microsoft.Azure.Cosmos.Scripts; + +namespace CosmosDbExplorer.Core.Models +{ + public class CosmosTrigger : ICosmosScript + { + public CosmosTrigger(string id, string body, string? selfLink) + { + Id = id; + Body = body; + SelfLink = selfLink; + } + + public CosmosTrigger(TriggerProperties properties) + { + Id = properties.Id; + ETag = properties.ETag; + SelfLink = properties.SelfLink; + Body = properties.Body; + Operation = (CosmosTriggerOperation)(int)properties.TriggerOperation; + Type = (CosmosTriggerType)(int)properties.TriggerType; + } + + public string? Id { get; set; } + public string? ETag { get; private set; } + public string? SelfLink { get; private set; } + public string Body { get; } + + public CosmosTriggerOperation Operation { get; set; } + public CosmosTriggerType Type { get; set; } + } + + public enum CosmosTriggerOperation + { + // + // Summary: + // Specifies all operations. + All = 0, + // + // Summary: + // Specifies create operations only. + Create = 1, + // + // Summary: + // Specifies update operations only. + Update = 2, + // + // Summary: + // Specifies delete operations only. + Delete = 3, + // + // Summary: + // Specifies replace operations only. + Replace = 4 + } + + public enum CosmosTriggerType + { + // + // Summary: + // Trigger should be executed before the associated operation(s). + Pre = 0, + // + // Summary: + // Trigger should be executed after the associated operation(s). + Post = 1 + } +} diff --git a/src/CosmosDbExplorer.Core/Models/CosmosUser.cs b/src/CosmosDbExplorer.Core/Models/CosmosUser.cs new file mode 100644 index 0000000..da416da --- /dev/null +++ b/src/CosmosDbExplorer.Core/Models/CosmosUser.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using CosmosDbExplorer.Core.Contracts; + +using Microsoft.Azure.Cosmos; + +namespace CosmosDbExplorer.Core.Models +{ + public class CosmosUser : ICosmosResource + { + public CosmosUser(UserProperties properties) + { + Id = properties.Id; + ETag = properties.ETag; + SelfLink = properties.SelfLink; + } + + public CosmosUser() { } + + public string? Id { get; set; } + public string? ETag { get; } + public string? SelfLink { get; } + } +} diff --git a/src/CosmosDbExplorer.Core/Models/CosmosUserDefinedFunction.cs b/src/CosmosDbExplorer.Core/Models/CosmosUserDefinedFunction.cs new file mode 100644 index 0000000..866dd59 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Models/CosmosUserDefinedFunction.cs @@ -0,0 +1,29 @@ +using CosmosDbExplorer.Core.Contracts; +using Microsoft.Azure.Cosmos.Scripts; + +namespace CosmosDbExplorer.Core.Models +{ + public class CosmosUserDefinedFunction : ICosmosScript + { + public CosmosUserDefinedFunction(string id, string body, string? selfLink) + { + Id = id; + Body = body; + SelfLink = selfLink; + } + + public CosmosUserDefinedFunction(UserDefinedFunctionProperties properties) + { + Id = properties.Id; + ETag = properties.ETag; + SelfLink = properties.SelfLink; + + Body = properties.Body; + } + + public string? Id { get; set; } + public string? ETag { get; private set; } + public string? SelfLink { get; private set; } + public string Body { get; } + } +} diff --git a/src/CosmosDbExplorer.Core/Models/DocumentRequestOptions.cs b/src/CosmosDbExplorer.Core/Models/DocumentRequestOptions.cs new file mode 100644 index 0000000..c5cbe6b --- /dev/null +++ b/src/CosmosDbExplorer.Core/Models/DocumentRequestOptions.cs @@ -0,0 +1,46 @@ +using System; +using System.ComponentModel; +using CosmosDbExplorer.Core.Contracts; + +namespace CosmosDbExplorer.Core.Models +{ + public class DocumentRequestOptions : IDocumentRequestOptions + { + public CosmosIndexingDirectives? IndexingDirective { get; set; } + public CosmosConsistencyLevels? ConsistencyLevel { get; set; } + public CosmosAccessConditionType AccessCondition { get; set; } + public string? ETag { get; set; } + public string[] PreTriggers { get; set; } = Array.Empty(); + public string[] PostTriggers { get; set; } = Array.Empty(); + } + + [TypeConverter(typeof(EnumDescriptionTypeConverter))] + public enum CosmosIndexingDirectives + { + Default = 0, + Include = 1, + Exclude = 2 + } + + [TypeConverter(typeof(EnumDescriptionTypeConverter))] + public enum CosmosConsistencyLevels + { + Strong = 0, + [Description("Bounded Staleness")] + BoundedStaleness = 1, + Session = 2, + Eventual = 3, + [Description("Consistent Prefix")] + ConsistentPrefix = 4 + } + + [TypeConverter(typeof(EnumDescriptionTypeConverter))] + public enum CosmosAccessConditionType + { + None = 0, + [Description("If Match")] + IfMatch = 1, + [Description("If Not Match")] + IfNotMatch = 2 + } +} diff --git a/src/CosmosDbExplorer.Core/Models/ExternalComponent.cs b/src/CosmosDbExplorer.Core/Models/ExternalComponent.cs new file mode 100644 index 0000000..a7b27c3 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Models/ExternalComponent.cs @@ -0,0 +1,9 @@ +namespace CosmosDbExplorer.Core.Models +{ + public class ExternalComponent + { + public string? Name { get; set; } + public string? LicenseUrl { get; set; } + public string? ProjectUrl { get; set; } + } +} diff --git a/src/CosmosDbExplorer.Core/Models/Optional.cs b/src/CosmosDbExplorer.Core/Models/Optional.cs new file mode 100644 index 0000000..3b4d41d --- /dev/null +++ b/src/CosmosDbExplorer.Core/Models/Optional.cs @@ -0,0 +1,22 @@ +namespace CosmosDbExplorer.Core.Models +{ + public class Optional + { + public Optional(T value) + { + Value = value; + IsSome = true; + } + + public Optional() + { + Value = default; + IsSome = false; + } + + public T? Value { get; } + + public bool IsSome { get; } + } + +} diff --git a/src/CosmosDbExplorer.Core/Services/CosmosClientService.cs b/src/CosmosDbExplorer.Core/Services/CosmosClientService.cs new file mode 100644 index 0000000..e939e98 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Services/CosmosClientService.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Concurrent; + +using CosmosDbExplorer.Core.Contracts.Services; +using CosmosDbExplorer.Core.Models; + +using Microsoft.Azure.Cosmos; + +namespace CosmosDbExplorer.Core.Services +{ + public class CosmosClientService : ICosmosClientService + { + private readonly ConcurrentDictionary _client = new(); + + public CosmosClient GetClient(CosmosConnection connection) + { + if (connection is null) + { + throw new ArgumentNullException(nameof(connection)); + } + + return _client.GetOrAdd(connection.Id, CreateClient(connection)); + } + + public void DeleteClient(CosmosConnection connection) + { + throw new NotImplementedException(); + } + + private CosmosClient CreateClient(CosmosConnection connection) + { + var options = new CosmosClientOptions + { + ConnectionMode = connection.ConnectionType == ConnectionType.Gateway ? ConnectionMode.Gateway : ConnectionMode.Direct, + EnableTcpConnectionEndpointRediscovery = connection.EnableEndpointDiscovery + }; + + return new CosmosClient(accountEndpoint: connection.DatabaseUri?.ToString(), authKeyOrResourceToken: connection.AuthenticationKey, options); + } + } +} diff --git a/src/CosmosDbExplorer.Core/Services/CosmosContainerService.cs b/src/CosmosDbExplorer.Core/Services/CosmosContainerService.cs new file mode 100644 index 0000000..459c71f --- /dev/null +++ b/src/CosmosDbExplorer.Core/Services/CosmosContainerService.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using CosmosDbExplorer.Core.Contracts.Services; +using CosmosDbExplorer.Core.Helpers; +using CosmosDbExplorer.Core.Models; +using Microsoft.Azure.Cosmos; + +using Newtonsoft.Json; + +namespace CosmosDbExplorer.Core.Services +{ + public class CosmosContainerService : ICosmosContainerService + { + private readonly CosmosClient _client; + private readonly CosmosDatabase _cosmosDatabase; + + public CosmosContainerService(ICosmosClientService clientService, CosmosConnection connection, CosmosDatabase cosmosDatabase) + { + _client = clientService.GetClient(connection); + _cosmosDatabase = cosmosDatabase; + } + + public async Task> GetContainersAsync(CancellationToken cancellationToken) + { + var db = _client.GetDatabase(_cosmosDatabase.Id); + var properties = db.GetContainerQueryIterator(); + var result = new List(); + + while (properties.HasMoreResults) + { + var response = await properties.ReadNextAsync(cancellationToken); + result.AddRange(response.Select(properties => new CosmosContainer(properties))); + } + + return result.OrderBy(r => r.Id).ToList(); + } + + public async Task CreateContainerAsync(CosmosContainer container, int? throughput, bool? isAutoscale, CancellationToken cancellationToken) + { + var db = _client.GetDatabase(_cosmosDatabase.Id); + + var containerProperties = new ContainerProperties() + { + Id = container.Id, + PartitionKeyPath = container.PartitionKeyPath, + DefaultTimeToLive = container.DefaultTimeToLive, + PartitionKeyDefinitionVersion = container.PartitionKeyDefVersion + }; + + try + { + if (throughput.HasValue) + { + var throughputProperties = isAutoscale.GetValueOrDefault(true) + ? ThroughputProperties.CreateManualThroughput(throughput.Value) + : ThroughputProperties.CreateAutoscaleThroughput(throughput.Value); + + var result = await db.CreateContainerAsync(containerProperties, throughputProperties, requestOptions: null, cancellationToken); + return new CosmosContainer(result.Resource); + } + else + { + var result = await db.CreateContainerAsync(containerProperties, throughput, requestOptions: null, cancellationToken); + return new CosmosContainer(result.Resource); + } + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage()); + } + } + + public async Task UpdateContainerAsync(CosmosContainer container) + { + var ct = _client.GetContainer(_cosmosDatabase.Id, container.Id); + var containerProperties = new ContainerProperties + { + Id = container.Id, + PartitionKeyPath = container.PartitionKeyPath, + DefaultTimeToLive = container.DefaultTimeToLive, + PartitionKeyDefinitionVersion = container.PartitionKeyDefVersion, + IndexingPolicy = JsonConvert.DeserializeObject(container.IndexingPolicy), + GeospatialConfig = new GeospatialConfig(container.GeospatialType.FromLocalType()) + }; + + try + { + var result = await ct.ReplaceContainerAsync(containerProperties); + return new CosmosContainer(result.Resource); + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage()); + } + } + + public async Task GetThroughputAsync(CosmosContainer container) + { + try + { + var ct = _client.GetContainer(_cosmosDatabase.Id, container.Id); + var response = await ct.ReadThroughputAsync(requestOptions: null); + + return new CosmosThroughput(response); + } + catch (CosmosException ce) when (ce.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return null; + } + } + + public async Task UpdateThroughputAsync(CosmosContainer container, int throughput, bool isAutoscale) + { + try + { + var ct = _client.GetContainer(_cosmosDatabase.Id, container.Id); + + var properties = isAutoscale + ? ThroughputProperties.CreateAutoscaleThroughput(throughput) + : ThroughputProperties.CreateManualThroughput(throughput); + + var result = await ct.ReplaceThroughputAsync(properties); + return new CosmosThroughput(result); + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage(), ex); + } + } + + + public async Task DeleteContainserAsync(CosmosContainer container, CancellationToken cancellationToken) + { + var client = _client.GetDatabase(_cosmosDatabase.Id).GetContainer(container.Id); + + try + { + await client.DeleteContainerAsync(cancellationToken: cancellationToken); + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage()); + } + } + + public async Task GetContainerMetricsAsync(CosmosContainer container, CancellationToken cancellationToken) + { + var ctx = _client.GetContainer(_cosmosDatabase.Id, container.Id); + var options = new ContainerRequestOptions + { + PopulateQuotaInfo = true, + }; + + var response = await ctx.ReadContainerAsync(options, cancellationToken); + return new CosmosContainerMetric(response); + } + } +} diff --git a/src/CosmosDbExplorer.Core/Services/CosmosDatabaseService.cs b/src/CosmosDbExplorer.Core/Services/CosmosDatabaseService.cs new file mode 100644 index 0000000..60766c2 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Services/CosmosDatabaseService.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using CosmosDbExplorer.Core.Contracts.Services; +using CosmosDbExplorer.Core.Helpers; +using CosmosDbExplorer.Core.Models; + +using Microsoft.Azure.Cosmos; + +namespace CosmosDbExplorer.Core.Services +{ + public class CosmosDatabaseService : ICosmosDatabaseService + { + private readonly CosmosClient _client; + + public CosmosDatabaseService(ICosmosClientService clientService, CosmosConnection connection) + { + _client = clientService.GetClient(connection); + } + + public async Task> GetDatabasesAsync(CancellationToken cancellationToken) + { + var properties = _client.GetDatabaseQueryIterator(); + var result = new List(); + + while (properties.HasMoreResults) + { + var response = await properties.ReadNextAsync(cancellationToken); + + foreach (var item in response) + { + var db = _client.GetDatabase(item.Id); + + try + { + var throughput = await db.ReadThroughputAsync(cancellationToken); + result.Add(new CosmosDatabase(item, throughput, false)); + } + catch (CosmosException ce) when (ce.StatusCode == HttpStatusCode.BadRequest && ce.ResponseBody.Contains("serverless", StringComparison.CurrentCultureIgnoreCase)) + { + result.Add(new CosmosDatabase(item, null, true)); + } + } + } + + return result.OrderBy(r => r.Id).ToList(); + } + + public async Task CreateDatabaseAsync(CosmosDatabase database, int? throughput, bool? isAutoscale, CancellationToken cancellationToken) + { + try + { + if (throughput.HasValue) + { + var throughputProperties = isAutoscale.GetValueOrDefault(true) + ? ThroughputProperties.CreateAutoscaleThroughput(throughput.Value) + : ThroughputProperties.CreateManualThroughput(throughput.Value); + + var result = await _client.CreateDatabaseAsync(database.Id, throughputProperties, requestOptions: null, cancellationToken: cancellationToken); + return new CosmosDatabase(result.Resource, throughput, true); + } + else + { + var result = await _client.CreateDatabaseAsync(database.Id, throughput, requestOptions: null, cancellationToken: cancellationToken); + + // Try to get a Cosmos Thoughput instance. Serverless throw an exception and the call must return null here. + var cosmosThroughput = await GetThroughputAsync(database); + + return cosmosThroughput is null + ? new CosmosDatabase(result.Resource, null, true) + : new CosmosDatabase(result.Resource, throughput, false); + } + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage()); + } + } + + public async Task DeleteDatabaseAsync(CosmosDatabase database, CancellationToken cancellationToken) + { + var db = _client.GetDatabase(database.Id); + + try + { + await db.DeleteAsync(cancellationToken: cancellationToken); + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage()); + } + } + + public async Task GetDatabaseMetricsAsync() + { + return await _client.ReadAccountAsync(); + } + + public async Task GetThroughputAsync(CosmosDatabase database) + { + try + { + var db = _client.GetDatabase(database.Id); + var result = await db.ReadThroughputAsync(requestOptions: null); + return new CosmosThroughput(result); + } + catch (CosmosException ce) when (ce.StatusCode == HttpStatusCode.BadRequest && ce.ResponseBody.Contains("serverless", StringComparison.CurrentCultureIgnoreCase)) + { + return null; + } + } + + public async Task UpdateThroughputAsync(CosmosDatabase database, int throughput, bool isAutoscale) + { + var db = _client.GetDatabase(database.Id); + + var properties = isAutoscale + ? ThroughputProperties.CreateAutoscaleThroughput(throughput) + : ThroughputProperties.CreateManualThroughput(throughput); + + var result = await db.ReplaceThroughputAsync(properties); + return new CosmosThroughput(result); + } + } +} diff --git a/src/CosmosDbExplorer.Core/Services/CosmosDocumentService.cs b/src/CosmosDbExplorer.Core/Services/CosmosDocumentService.cs new file mode 100644 index 0000000..ed37a3a --- /dev/null +++ b/src/CosmosDbExplorer.Core/Services/CosmosDocumentService.cs @@ -0,0 +1,286 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CosmosDbExplorer.Core.Contracts; +using CosmosDbExplorer.Core.Contracts.Services; +using CosmosDbExplorer.Core.Helpers; +using CosmosDbExplorer.Core.Models; +using Microsoft.Azure.Cosmos; +using Newtonsoft.Json.Linq; + +namespace CosmosDbExplorer.Core.Services +{ + public class CosmosDocumentService : ICosmosDocumentService + { + private readonly CosmosDatabase _cosmosDatabase; + private readonly CosmosContainer _cosmosContainer; + private readonly CosmosClient _cosmosClient; + private readonly Container _container; + + public CosmosDocumentService(ICosmosClientService clientService, CosmosConnection connection, CosmosDatabase database, CosmosContainer container) + { + _cosmosClient = clientService.GetClient(connection); + _cosmosDatabase = database; + _cosmosContainer = container; + + _container = _cosmosClient.GetContainer(_cosmosDatabase.Id, _cosmosContainer.Id); + } + + public async Task>> GetDocumentsAsync(string? filter, int? maxItemsCount, string? continuationToken, CancellationToken cancellationToken) + { + var result = new CosmosQueryResult>(); + + var token = _cosmosContainer.PartitionKeyJsonPath; + if (token != null) + { + token = $", c{token} as _partitionKey, true as _hasPartitionKey"; + } + + var sql = $"SELECT c.id, c._self, c._etag, c._ts, c._attachments {token} FROM c {filter}"; + + var options = new QueryRequestOptions + { + MaxItemCount = maxItemsCount, + // TODO: Handle Partition key and other IHaveRequestOptions values + //PartitionKey = + }; + + using (var resultSet = _container.GetItemQueryIterator( + queryText: sql, + continuationToken: continuationToken, + requestOptions: options)) + { + var response = await resultSet.ReadNextAsync(cancellationToken); + + result.RequestCharge = response.RequestCharge; + result.ContinuationToken = response.ContinuationToken; + result.Items = response.Resource.ToArray(); + result.Headers = response.Headers.ToDictionary(); + } + + return result; + } + + public async Task> GetDocumentAsync(ICosmosDocument document, IDocumentRequestOptions options, CancellationToken cancellationToken) + { + var result = new CosmosQueryResult(); + + var requestOptions = new ItemRequestOptions + { + IndexingDirective = options.IndexingDirective is not null ? Enum.Parse(options.IndexingDirective.ToString()) : null, + ConsistencyLevel = options.ConsistencyLevel is not null ? Enum.Parse(options.ConsistencyLevel.ToString()) : null, + IfMatchEtag = options.AccessCondition == CosmosAccessConditionType.IfMatch ? options.ETag : null, + IfNoneMatchEtag = options.AccessCondition == CosmosAccessConditionType.IfNotMatch ? options.ETag : null, + PreTriggers = options.PreTriggers, + PostTriggers = options.PostTriggers + }; + + try + { + var response = await _container.ReadItemAsync(document.Id, + partitionKey: PartitionKeyHelper.Get(document.PartitionKey), + requestOptions: requestOptions, + cancellationToken); + + result.RequestCharge = response.RequestCharge; + result.Items = response.Resource; + result.Headers = response.Headers.ToDictionary(); + //result.Diagnostics = JObject.Parse(result.Diagnostics?.ToString()); + + return result; + } + catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return result; + } + } + + public async Task> SaveDocumentAsync(string content, IDocumentRequestOptions options, CancellationToken cancellationToken) + { + var result = new CosmosQueryResult(); + + var requestOptions = new ItemRequestOptions + { + IndexingDirective = options.IndexingDirective is not null ? Enum.Parse(options.IndexingDirective.ToString()) : null, + ConsistencyLevel = options.ConsistencyLevel is not null ? Enum.Parse(options.ConsistencyLevel.ToString()) : null, + IfMatchEtag = options.AccessCondition == CosmosAccessConditionType.IfMatch && options.ETag != null ? options.ETag : null, + IfNoneMatchEtag = options.AccessCondition == CosmosAccessConditionType.IfNotMatch && options.ETag != null ? options.ETag : null, + PreTriggers = options.PreTriggers, + PostTriggers = options.PostTriggers + }; + + try + { + var document = GetDocuments(content, _cosmosContainer.PartitionKeyJsonPath).First(); + + var response = await _container.UpsertItemAsync(document.Resource, PartitionKeyHelper.Get(document.PK), requestOptions, cancellationToken); + + result.RequestCharge = response.RequestCharge; + result.Items = (JObject)response.Resource; + result.Headers = response.Headers.ToDictionary(); + //result.Diagnostics = JObject.Parse(result.Diagnostics?.ToString()); + + return result; + } + catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return result; + } + catch (CosmosException ce) + { + throw new Exception(ce.GetMessage()); + } + } + + public async Task DeleteDocumentsAsync(IEnumerable documents, CancellationToken cancellationToken) + { + var tasks = new List>>(documents.Count()); + + foreach (var item in documents) + { + tasks.Add(_container.DeleteItemAsync(item.Id, PartitionKeyHelper.Get(item.PartitionKey), cancellationToken: cancellationToken) + .ContinueWith>(itemResponse => + { + if (!itemResponse.IsCompletedSuccessfully) + { + var innerExceptions = itemResponse.Exception.Flatten(); + if (innerExceptions.InnerExceptions.FirstOrDefault(innerEx => innerEx is CosmosException) is CosmosException cosmosException) + { + throw new Exception(cosmosException.GetMessage()); + } + else + { + throw new Exception($"Exception {innerExceptions.InnerExceptions.FirstOrDefault()}."); + } + } + else + { + return itemResponse.Result; + } + })); + } + + await Task.WhenAll(tasks); + + var result = new CosmosResult + { + RequestCharge = tasks.Where(t => t.IsCompletedSuccessfully).Sum(t => t.Result.RequestCharge) + }; + + return result; + } + + public async Task ImportDocumentsAsync(string content, CancellationToken cancellationToken) + { + // TODO: https://docs.microsoft.com/en-us/azure/cosmos-db/sql/how-to-migrate-from-bulk-executor-library + var itemsToInsert = GetDocuments(content, _cosmosContainer.PartitionKeyJsonPath); + var tasks = new List(itemsToInsert.Length); + + foreach (var item in itemsToInsert) + { + tasks.Add(_container.CreateItemAsync(item.Resource, PartitionKeyHelper.Get(item.PK), cancellationToken: cancellationToken) + .ContinueWith(itemResponse => + { + if (!itemResponse.IsCompletedSuccessfully) + { + var innerExceptions = itemResponse.Exception.Flatten(); + if (innerExceptions.InnerExceptions.FirstOrDefault(innerEx => innerEx is CosmosException) is CosmosException cosmosException) + { + throw new Exception(cosmosException.GetMessage()); + } + else + { + throw new Exception($"Exception {innerExceptions.InnerExceptions.FirstOrDefault()}."); + } + } + })); + } + + // Wait until all are done + await Task.WhenAll(tasks); + + return tasks.Where(t => t.IsCompletedSuccessfully).Count(); + } + + public async Task>> ExecuteQueryAsync(ICosmosQuery query, CancellationToken cancellationToken) + { + var result = new CosmosQueryResult>(); + + var options = new QueryRequestOptions + { + PartitionKey = query.PartitionKeyValue.IsSome ? PartitionKeyHelper.Get(query.PartitionKeyValue.Value) : null, + EnableScanInQuery = query.EnableScanInQuery, + MaxItemCount = query.MaxItemCount, + MaxBufferedItemCount = query.MaxBufferItem, + MaxConcurrency = query.MaxDOP, + PopulateIndexMetrics = true, + //ConsistencyLevel = ConsistencyLevel.Strong + }; + + try + { + using (var resultSet = _container.GetItemQueryIterator( + queryText: query.QueryText, + continuationToken: query.ContinuationToken, + requestOptions: options)) + { + var response = await resultSet.ReadNextAsync(cancellationToken); + + result.RequestCharge = response.RequestCharge; + result.ContinuationToken = response.ContinuationToken; + result.Items = response.Resource.ToArray(); + result.Headers = response.Headers.ToDictionary(); + //result.Diagnostics = response.Diagnostics.ToString; + result.IndexMetrics = response.IndexMetrics; + } + + return result; + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage()); + } + } + + + + private Document[] GetDocuments(string content, string? pkPath) + { + if (pkPath is null) + { + throw new ArgumentNullException(nameof(pkPath)); + } + + var token = JToken.Parse(content); + + if (token == null) + { + return Array.Empty(); + } + + if (token is JArray) + { + return token.Children().Select(child => new Document((JObject)child, pkPath)).ToArray(); + } + else + { + return new[] { new Document((JObject)token, pkPath) }; + } + } + } + + internal class Document + { + public Document(JObject resource, string pkPath) + { + PK = resource.SelectToken(pkPath)?.ToObject(); + Resource = resource; + } + + public object? PK { get; set; } + public JToken Resource { get; set; } + } +} diff --git a/src/CosmosDbExplorer.Core/Services/CosmosScriptService.cs b/src/CosmosDbExplorer.Core/Services/CosmosScriptService.cs new file mode 100644 index 0000000..80ee1ef --- /dev/null +++ b/src/CosmosDbExplorer.Core/Services/CosmosScriptService.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using CosmosDbExplorer.Core.Contracts.Services; +using CosmosDbExplorer.Core.Helpers; +using CosmosDbExplorer.Core.Models; +using Microsoft.Azure.Cosmos; +using Microsoft.Azure.Cosmos.Scripts; + +namespace CosmosDbExplorer.Core.Services +{ + public class CosmosScriptService : ICosmosScriptService + { + //private readonly CosmosDatabase _cosmosDatabase; + //private readonly CosmosContainer _cosmosContainer; + //private readonly CosmosClient _cosmosClient; + //private readonly Container _container; + + private readonly Scripts _scripts; + + public CosmosScriptService(ICosmosClientService clientService, CosmosConnection connection, CosmosDatabase database, CosmosContainer container) + { + var cosmosClient = clientService.GetClient(connection); + + _scripts = cosmosClient.GetContainer(database.Id, container.Id).Scripts; + } + + public async Task> GetStoredProceduresAsync(CancellationToken cancellationToken) + { + var iterator = _scripts.GetStoredProcedureQueryIterator(); + var result = new List(); + + while (iterator.HasMoreResults) + { + var response = await iterator.ReadNextAsync(cancellationToken); + result.AddRange(response.OrderBy(r => r.Id).Select(r => new CosmosStoredProcedure(r))); + } + + return result; + } + + public async Task SaveStoredProcedureAsync(CosmosStoredProcedure asset) + { + var properties = new StoredProcedureProperties(asset.Id, asset.Body); + + try + { + if (asset.SelfLink != null) + { + var updatedobject = await _scripts.ReplaceStoredProcedureAsync(properties); + return new CosmosStoredProcedure(updatedobject); + } + else + { + var newobject = await _scripts.CreateStoredProcedureAsync(properties); + return new CosmosStoredProcedure(newobject); + } + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage()); + } + } + + public async Task ExecuteStoredProcedureAsync(string storedProcedureId, string? partitionKey, dynamic[] parameters) + { + var pk = PartitionKeyHelper.Parse(partitionKey) ?? PartitionKey.None; + + var options = new StoredProcedureRequestOptions + { + EnableScriptLogging = true, + }; + + try + { + var response = await _scripts.ExecuteStoredProcedureAsync(storedProcedureId, pk, parameters, options); + return new CosmosStoredProcedureResult(response); + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage()); + } + } + + public async Task DeleteStoredProcedureAsync(CosmosStoredProcedure asset) + { + try + { + var response = await _scripts.DeleteStoredProcedureAsync(asset.Id); + + return new CosmosResult + { + RequestCharge = response.RequestCharge, + TimeElapsed = response.Diagnostics.GetClientElapsedTime() + }; + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage()); + } + } + + public async Task> GetUserDefinedFunctionsAsync(CancellationToken cancellationToken) + { + var iterator = _scripts.GetUserDefinedFunctionQueryIterator(); + var result = new List(); + + while (iterator.HasMoreResults) + { + var response = await iterator.ReadNextAsync(cancellationToken); + result.AddRange(response.OrderBy(r => r.Id).Select(r => new CosmosUserDefinedFunction(r))); + } + + return result; + } + + public async Task SaveUserDefinedFunctionAsync(CosmosUserDefinedFunction asset) + { + var properties = new UserDefinedFunctionProperties + { + Body = asset.Body, + Id = asset.Id + }; + + try + { + if (asset.SelfLink != null) + { + var updatedobject = await _scripts.ReplaceUserDefinedFunctionAsync(properties); + return new CosmosUserDefinedFunction(updatedobject); + } + else + { + var newobject = await _scripts.CreateUserDefinedFunctionAsync(properties); + return new CosmosUserDefinedFunction(newobject); + } + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage()); + } + } + + public async Task DeleteUserDefinedFunctionAsync(CosmosUserDefinedFunction asset) + { + try + { + var response = await _scripts.DeleteUserDefinedFunctionAsync(asset.Id); + + return new CosmosResult + { + RequestCharge = response.RequestCharge, + TimeElapsed = response.Diagnostics.GetClientElapsedTime() + }; + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage()); + } + } + + public async Task> GetTriggersAsync(CancellationToken cancellationToken) + { + var iterator = _scripts.GetTriggerQueryIterator(); + var result = new List(); + + while (iterator.HasMoreResults) + { + var response = await iterator.ReadNextAsync(cancellationToken); + result.AddRange(response.OrderBy(r => r.Id).Select(r => new CosmosTrigger(r))); + } + + return result; + } + + public async Task SaveTriggerAsync(CosmosTrigger asset) + { + var properties = new TriggerProperties + { + Body = asset.Body, + Id = asset.Id, + TriggerOperation = (TriggerOperation)(int)asset.Operation, + TriggerType = (TriggerType)(int)asset.Type + }; + + try + { + if (asset.SelfLink != null) + { + var updatedobject = await _scripts.ReplaceTriggerAsync(properties); + return new CosmosTrigger(updatedobject); + } + else + { + var newobject = await _scripts.CreateTriggerAsync(properties); + return new CosmosTrigger(newobject); + } + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage()); + } + } + + public async Task DeleteTriggerAsync(CosmosTrigger asset) + { + try + { + var response = await _scripts.DeleteTriggerAsync(asset.Id); + + return new CosmosResult + { + RequestCharge = response.RequestCharge, + TimeElapsed = response.Diagnostics.GetClientElapsedTime() + }; + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage()); + } + } + } +} diff --git a/src/CosmosDbExplorer.Core/Services/CosmosUserService.cs b/src/CosmosDbExplorer.Core/Services/CosmosUserService.cs new file mode 100644 index 0000000..6829e4d --- /dev/null +++ b/src/CosmosDbExplorer.Core/Services/CosmosUserService.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using CosmosDbExplorer.Core.Contracts.Services; +using CosmosDbExplorer.Core.Helpers; +using CosmosDbExplorer.Core.Models; + +using Microsoft.Azure.Cosmos; + +namespace CosmosDbExplorer.Core.Services +{ + public class CosmosUserService : ICosmosUserService + { + private readonly Database _database; + + public CosmosUserService(ICosmosClientService clientService, CosmosConnection connection, CosmosDatabase database) + { + var client = clientService.GetClient(connection); + _database = client.GetDatabase(database.Id); + } + + public async Task> GetUsersAsync(CancellationToken cancellationToken) + { + var properties = _database.GetUserQueryIterator(); + var result = new List(); + + while (properties.HasMoreResults) + { + var response = await properties.ReadNextAsync(cancellationToken); + + result.AddRange(response.Select(properties => new CosmosUser(properties))); + } + + return result.OrderBy(r => r.Id).ToList(); + } + + public async Task> GetUserAsync(string id, CancellationToken cancellationToken) + { + var userCtx = _database.GetUser(id); + var response = await userCtx.ReadAsync(cancellationToken: cancellationToken); + + return new CosmosQueryResult + { + RequestCharge = response.RequestCharge, + Headers = response.Headers.ToDictionary(), + Items = new CosmosUser(response.Resource) + }; + } + + public async Task> SaveUserAsync(CosmosUser user, CancellationToken cancellationToken) + { + try + { + var response = await _database.CreateUserAsync(user.Id, cancellationToken: cancellationToken); + + return new CosmosQueryResult + { + RequestCharge = response.RequestCharge, + Headers = response.Headers.ToDictionary(), + Items = new CosmosUser(response.Resource) + }; + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage(), ex); + } + } + + public async Task DeleteUserAsync(CosmosUser user, CancellationToken cancellationToken) + { + var userCtx = _database.GetUser(user.Id); + var response = await userCtx.DeleteAsync(cancellationToken: cancellationToken); + + return new CosmosResult + { + RequestCharge = response.RequestCharge, + }; + } + + public async Task> GetPermissionsAsync(CosmosUser user, CancellationToken cancellationToken) + { + var userCtx = _database.GetUser(user.Id); + var properties = userCtx.GetPermissionQueryIterator(); + var result = new List(); + + while (properties.HasMoreResults) + { + var response = await properties.ReadNextAsync(cancellationToken); + + result.AddRange(response.Select(properties => new CosmosPermission(properties))); + } + + return result.OrderBy(r => r.Id).ToList(); + } + + public async Task> SavePermissionAsync(CosmosUser user, CosmosPermission permission, string container, CancellationToken cancellationToken) + { + try + { + var userCtx = _database.GetUser(user.Id); + + var permissionMode = (PermissionMode)permission.PermissionMode; + var pk = PartitionKeyHelper.Parse(permission.PartitionKey); + var ct = _database.GetContainer(container); + + var permissionProperties = new PermissionProperties(permission.Id, permissionMode, ct, pk); + var response = await userCtx.UpsertPermissionAsync(permissionProperties, cancellationToken: cancellationToken); + + return new CosmosQueryResult + { + RequestCharge = response.RequestCharge, + Headers = response.Headers.ToDictionary(), + Items = new CosmosPermission(response.Resource) + }; + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage(), ex); + } + } + + public async Task DeletePermissionAsync(CosmosUser user, CosmosPermission permission, CancellationToken cancellationToken) + { + try + { + var userCtx = _database.GetUser(user.Id); + var permCtx = userCtx.GetPermission(permission.Id); + + var response = await permCtx.DeleteAsync(cancellationToken: cancellationToken); + + return new CosmosResult + { + RequestCharge = response.RequestCharge, + }; + } + catch (CosmosException ex) + { + throw new Exception(ex.GetMessage(), ex); + } + } + } +} diff --git a/src/CosmosDbExplorer.Core/Services/FileService.cs b/src/CosmosDbExplorer.Core/Services/FileService.cs new file mode 100644 index 0000000..9e8f810 --- /dev/null +++ b/src/CosmosDbExplorer.Core/Services/FileService.cs @@ -0,0 +1,43 @@ +using System.IO; +using System.Text; + +using CosmosDbExplorer.Core.Contracts.Services; + +using Newtonsoft.Json; + +namespace CosmosDbExplorer.Core.Services +{ + public class FileService : IFileService + { + public T? Read(string folderPath, string fileName) + { + var path = Path.Combine(folderPath, fileName); + if (File.Exists(path)) + { + var json = File.ReadAllText(path); + return JsonConvert.DeserializeObject(json); + } + + return default; + } + + public void Save(string folderPath, string fileName, T content) + { + if (!Directory.Exists(folderPath)) + { + Directory.CreateDirectory(folderPath); + } + + var fileContent = JsonConvert.SerializeObject(content); + File.WriteAllText(Path.Combine(folderPath, fileName), fileContent, Encoding.UTF8); + } + + public void Delete(string folderPath, string fileName) + { + if (fileName != null && File.Exists(Path.Combine(folderPath, fileName))) + { + File.Delete(Path.Combine(folderPath, fileName)); + } + } + } +} diff --git a/src/CosmosDbExplorer.Core/readme.txt b/src/CosmosDbExplorer.Core/readme.txt new file mode 100644 index 0000000..dd20676 --- /dev/null +++ b/src/CosmosDbExplorer.Core/readme.txt @@ -0,0 +1,2 @@ +This core project is a .net standard project. +It's a great place to put all your logic that is not platform dependent (e.g. model/helper classes) so they can be reused. \ No newline at end of file diff --git a/src/CosmosDbExplorer.Tests/CosmosDbExplorer.Tests.csproj b/src/CosmosDbExplorer.Tests/CosmosDbExplorer.Tests.csproj deleted file mode 100644 index 48a639c..0000000 --- a/src/CosmosDbExplorer.Tests/CosmosDbExplorer.Tests.csproj +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - Debug - AnyCPU - {46565649-FD68-4042-BE52-9A92971F5192} - Library - Properties - CosmosDbExplorer.Tests - CosmosDbExplorer.Tests - v4.7.2 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Castle.Core.4.4.1\lib\net45\Castle.Core.dll - - - ..\packages\FluentAssertions.5.10.3\lib\net47\FluentAssertions.dll - - - ..\packages\FluentValidation.8.6.3\lib\net45\FluentValidation.dll - - - ..\packages\Moq.4.15.2\lib\net45\Moq.dll - - - - ..\packages\System.ComponentModel.Annotations.5.0.0\lib\net461\System.ComponentModel.Annotations.dll - - - - ..\packages\System.ComponentModel.Primitives.4.3.0\lib\net45\System.ComponentModel.Primitives.dll - True - True - - - - - ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll - - - ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - - - ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll - - - - - ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll - - - ..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll - - - ..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll - - - ..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll - - - - - - - - - - - - - - - - {5af0b2f1-b905-46cf-b493-71b1950a96a4} - CosmosDbExplorer - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - \ No newline at end of file diff --git a/src/CosmosDbExplorer.Tests/IndexValidationTests.cs b/src/CosmosDbExplorer.Tests/IndexValidationTests.cs deleted file mode 100644 index d0a3c2a..0000000 --- a/src/CosmosDbExplorer.Tests/IndexValidationTests.cs +++ /dev/null @@ -1,46 +0,0 @@ -using CosmosDbExplorer.ViewModel.Indexes; -using FluentValidation.TestHelper; -using System; -using Xunit; - -namespace CosmosDbExplorer.Tests -{ - public class IndexValidationTests - { - [Fact] - public void SimplePathIsValid() - { - const string value = @"/headquarters/employees/?"; - - var validator = new ExcludedPathViewModelValidator(); - validator.ShouldNotHaveValidationErrorFor(idx => idx.Path, value); - } - - [Fact] - public void ArrayPathIsValid() - { - const string value = @"/locations/[]/country/?"; - - var validator = new ExcludedPathViewModelValidator(); - validator.ShouldNotHaveValidationErrorFor(idx => idx.Path, value); - } - - [Fact] - public void PathToAnythingIsValid() - { - const string value = @"/headquarters/*"; - - var validator = new ExcludedPathViewModelValidator(); - validator.ShouldNotHaveValidationErrorFor(idx => idx.Path, value); - } - - [Fact] - public void EtagPathIsValid() - { - const string value = @"/""_etag""/?"; - - var validator = new ExcludedPathViewModelValidator(); - validator.ShouldNotHaveValidationErrorFor(idx => idx.Path, value); - } - } -} diff --git a/src/CosmosDbExplorer.Tests/Properties/AssemblyInfo.cs b/src/CosmosDbExplorer.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 575811f..0000000 --- a/src/CosmosDbExplorer.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("CosmosDbExplorer.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("One Step Beyond Group SA")] -[assembly: AssemblyProduct("CosmosDbExplorer.Tests")] -[assembly: AssemblyCopyright("Copyright © One Step Beyond Group SA 2019")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -[assembly: ComVisible(false)] - -[assembly: Guid("46565649-fd68-4042-be52-9a92971f5192")] - -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/CosmosDbExplorer.Tests/app.config b/src/CosmosDbExplorer.Tests/app.config deleted file mode 100644 index f10b4ac..0000000 --- a/src/CosmosDbExplorer.Tests/app.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/CosmosDbExplorer.Tests/packages.config b/src/CosmosDbExplorer.Tests/packages.config deleted file mode 100644 index 3fd1ee7..0000000 --- a/src/CosmosDbExplorer.Tests/packages.config +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/CosmosDbExplorer.sln b/src/CosmosDbExplorer.sln index 827f97b..71816a5 100644 --- a/src/CosmosDbExplorer.sln +++ b/src/CosmosDbExplorer.sln @@ -1,16 +1,11 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28803.352 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32104.313 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CosmosDbExplorer", "CosmosDbExplorer\CosmosDbExplorer.csproj", "{5AF0B2F1-B905-46CF-B493-71B1950A96A4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CosmosDbExplorer", "CosmosDbExplorer\CosmosDbExplorer.csproj", "{72E0A102-5CD5-44B0-9E8C-9EFF0821F7C0}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{14B2F32C-93E0-4831-8D9B-79E59F5AD256}" - ProjectSection(SolutionItems) = preProject - ..\README.md = ..\README.md - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CosmosDbExplorer.Tests", "CosmosDbExplorer.Tests\CosmosDbExplorer.Tests.csproj", "{46565649-FD68-4042-BE52-9A92971F5192}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CosmosDbExplorer.Core", "CosmosDbExplorer.Core\CosmosDbExplorer.Core.csproj", "{B26DE598-71A3-4E45-BEEA-7E6BF8E60DD3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -18,19 +13,19 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {5AF0B2F1-B905-46CF-B493-71B1950A96A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5AF0B2F1-B905-46CF-B493-71B1950A96A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5AF0B2F1-B905-46CF-B493-71B1950A96A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5AF0B2F1-B905-46CF-B493-71B1950A96A4}.Release|Any CPU.Build.0 = Release|Any CPU - {46565649-FD68-4042-BE52-9A92971F5192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {46565649-FD68-4042-BE52-9A92971F5192}.Debug|Any CPU.Build.0 = Debug|Any CPU - {46565649-FD68-4042-BE52-9A92971F5192}.Release|Any CPU.ActiveCfg = Release|Any CPU - {46565649-FD68-4042-BE52-9A92971F5192}.Release|Any CPU.Build.0 = Release|Any CPU + {72E0A102-5CD5-44B0-9E8C-9EFF0821F7C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72E0A102-5CD5-44B0-9E8C-9EFF0821F7C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72E0A102-5CD5-44B0-9E8C-9EFF0821F7C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72E0A102-5CD5-44B0-9E8C-9EFF0821F7C0}.Release|Any CPU.Build.0 = Release|Any CPU + {B26DE598-71A3-4E45-BEEA-7E6BF8E60DD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B26DE598-71A3-4E45-BEEA-7E6BF8E60DD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B26DE598-71A3-4E45-BEEA-7E6BF8E60DD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B26DE598-71A3-4E45-BEEA-7E6BF8E60DD3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {DA3AC4D7-E7C1-4582-9156-1AFC28C929B4} + SolutionGuid = {67DFB447-F067-45F1-A9DA-B32C6FA669C0} EndGlobalSection EndGlobal diff --git a/src/CosmosDbExplorer/App.config b/src/CosmosDbExplorer/App.config deleted file mode 100644 index a915160..0000000 --- a/src/CosmosDbExplorer/App.config +++ /dev/null @@ -1,63 +0,0 @@ - - - - -
- - -
- - - - - - - - - 20 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - https://www.bruttin.com/CosmosDbExplorer/autoupdate.json - - - - \ No newline at end of file diff --git a/src/CosmosDbExplorer/App.xaml b/src/CosmosDbExplorer/App.xaml index 85a2d2b..856231c 100644 --- a/src/CosmosDbExplorer/App.xaml +++ b/src/CosmosDbExplorer/App.xaml @@ -1,751 +1,50 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CosmosDbExplorer/App.xaml.cs b/src/CosmosDbExplorer/App.xaml.cs index a9973a9..863546b 100644 --- a/src/CosmosDbExplorer/App.xaml.cs +++ b/src/CosmosDbExplorer/App.xaml.cs @@ -1,53 +1,178 @@ using System; using System.Collections.Generic; -using System.Configuration; -using System.Data; using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; +using System.IO; +using System.Reflection; using System.Windows; using System.Windows.Threading; -using GalaSoft.MvvmLight.Threading; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.Views; +using CosmosDbExplorer.Core.Contracts.Services; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.Models; +using CosmosDbExplorer.Properties; +using CosmosDbExplorer.Services; +using CosmosDbExplorer.ViewModels; +using CosmosDbExplorer.Views; + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Toolkit.Mvvm.Messaging; namespace CosmosDbExplorer { - /// - /// Interaction logic for App.xaml - /// + // For more inforation about application lifecyle events see https://docs.microsoft.com/dotnet/framework/wpf/app-development/application-management-overview + + // WPF UI elements use language en-US by default. + // If you need to support other cultures make sure you add converters and review dates and numbers in your UI to ensure everything adapts correctly. + // Tracking issue for improving this is https://github.com/dotnet/wpf/issues/1946 public partial class App : Application { - static App() + private IHost? _host; + + public T? GetService() + where T : class + => _host?.Services.GetService(typeof(T)) as T; + + public App() { - DispatcherHelper.Initialize(); } - private void Application_Startup(object sender, StartupEventArgs e) + private async void OnStartup(object sender, StartupEventArgs e) { - // Global exception handling - AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomainUnandleException); - Application.Current.DispatcherUnhandledException += new DispatcherUnhandledExceptionEventHandler(AppDispatcherUnhandledException); - TaskScheduler.UnobservedTaskException += new EventHandler(UnobservedTaskException); + AvalonEdit.AvalonSyntax.LoadHighlighting(); + var appLocation = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); + + // Upgrade user settings in case of assembly version change + if (!System.Configuration.ConfigurationManager.OpenExeConfiguration(System.Configuration.ConfigurationUserLevel.PerUserRoamingAndLocal).HasFile) + { + Settings.Default.Upgrade(); + } + + // For more information about .NET generic host see https://docs.microsoft.com/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-3.0 + _host = Host.CreateDefaultBuilder(e.Args) + .ConfigureAppConfiguration(c => + { + c.SetBasePath(appLocation); + }) + .ConfigureServices(ConfigureServices) + .Build(); + + await _host.StartAsync(); } - private void UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) + private void ConfigureServices(HostBuilderContext context, IServiceCollection services) { - if (!Debugger.IsAttached) + // TODO WTS: Register your services, viewmodels and pages here + + // App Host + services.AddHostedService(); + + // Activation Handlers + + // Core Services + services.AddSingleton(); + services.AddSingleton(); + + // Services + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(GetDialogService()); + + services.AddSingleton(); + + // Cosmos Services + services.AddSingleton(); + services.AddTransient(provider => { - e.SetObserved(); - ShowUnhandledException(e.Exception, false); - } + return new Func((connection) => + new CosmosDatabaseService(provider.GetRequiredService(), connection)); + }); + + services.AddTransient(provider => + { + return new Func((connection, database) => + new CosmosContainerService(provider.GetRequiredService(), connection, database)); + }); + + services.AddTransient(provider => + { + return new Func((connection, database, container) => + new CosmosDocumentService(provider.GetRequiredService(), connection, database, container)); + }); + + services.AddTransient(provider => + { + return new Func((connection, database, container) => + new CosmosScriptService(provider.GetRequiredService(), connection, database, container)); + + }); + + // Views and ViewModels + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + // Configuration + services.Configure(context.Configuration.GetSection(nameof(AppConfig))); + services.AddSingleton(); } - private void CurrentDomainUnandleException(object sender, UnhandledExceptionEventArgs e) + private async void OnExit(object sender, ExitEventArgs e) { - if (!Debugger.IsAttached) + if (_host is not null) { - ShowUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); + await _host.StopAsync(); + _host.Dispose(); + _host = null; } } - private void AppDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) { + // TODO WTS: Please log and handle the exception as appropriate to your scenario + // For more info see https://docs.microsoft.com/dotnet/api/system.windows.application.dispatcherunhandledexception?view=netcore-3.0 if (Debugger.IsAttached) { e.Handled = false; @@ -59,7 +184,7 @@ private void AppDispatcherUnhandledException(object sender, DispatcherUnhandledE } } - private void ShowUnhandledException(Exception exception, bool isTerminating) + private static void ShowUnhandledException(Exception exception, bool isTerminating) { var details = exception.Message + (exception.InnerException != null ? "\n" + exception.InnerException.Message : null); var errorMessage = $@"An application error occurred. @@ -84,5 +209,14 @@ private void ShowUnhandledException(Exception exception, bool isTerminating) MessageBox.Show(errorMessage, "Application Error", MessageBoxButton.OK, MessageBoxImage.Error); } } + + private static IDialogService GetDialogService() + { + return Settings.Default.DialogService switch + { + "Metro" => new MetroDialogService(), + _ => new DialogService(), + }; + } } } diff --git a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/AvalonCommands.cs b/src/CosmosDbExplorer/AvalonEdit/AvalonCommands.cs similarity index 67% rename from src/CosmosDbExplorer/Infrastructure/AvalonEdit/AvalonCommands.cs rename to src/CosmosDbExplorer/AvalonEdit/AvalonCommands.cs index 5d7cc35..81b4312 100644 --- a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/AvalonCommands.cs +++ b/src/CosmosDbExplorer/AvalonEdit/AvalonCommands.cs @@ -1,14 +1,20 @@ using ICSharpCode.AvalonEdit; +using Microsoft.Toolkit.Mvvm.Input; -namespace CosmosDbExplorer.Infrastructure.AvalonEdit +namespace CosmosDbExplorer.AvalonEdit { public static class AvalonCommands { - public static readonly RelayCommand CommentCommand = new RelayCommand(OnCommentCommand); - public static readonly RelayCommand UnCommentCommand = new RelayCommand(OnUnCommentCommand); + public static readonly RelayCommand CommentCommand = new(OnCommentCommand); + public static readonly RelayCommand UnCommentCommand = new(OnUnCommentCommand); - private static void OnCommentCommand(TextEditor textEditor) + private static void OnCommentCommand(TextEditor? textEditor) { + if (textEditor == null) + { + return; + } + var document = textEditor.Document; var start = document.GetLineByOffset(textEditor.SelectionStart); var end = document.GetLineByOffset(textEditor.SelectionStart + textEditor.SelectionLength); @@ -27,8 +33,13 @@ private static void OnCommentCommand(TextEditor textEditor) } } - private static void OnUnCommentCommand(TextEditor textEditor) + private static void OnUnCommentCommand(TextEditor? textEditor) { + if (textEditor == null) + { + return; + } + var document = textEditor.Document; var start = document.GetLineByOffset(textEditor.SelectionStart); var end = document.GetLineByOffset(textEditor.SelectionStart + textEditor.SelectionLength); @@ -51,16 +62,13 @@ private static void OnUnCommentCommand(TextEditor textEditor) } } - private static string GetCommentPrefix(TextEditor textEditor) + private static string GetCommentPrefix(TextEditor? textEditor) { - switch (textEditor.SyntaxHighlighting.Name) + return textEditor?.SyntaxHighlighting.Name switch { - case "DocumentDb": - return "--"; - case "Json": - default: - return "//"; - } + "DocumentDb" => "--", + _ => "//", + }; } } } diff --git a/src/CosmosDbExplorer/AvalonEdit/AvalonSyntax.cs b/src/CosmosDbExplorer/AvalonEdit/AvalonSyntax.cs new file mode 100644 index 0000000..ede24ae --- /dev/null +++ b/src/CosmosDbExplorer/AvalonEdit/AvalonSyntax.cs @@ -0,0 +1,34 @@ +using System; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Xml; +using ICSharpCode.AvalonEdit.Highlighting; +using ICSharpCode.AvalonEdit.Highlighting.Xshd; + +namespace CosmosDbExplorer.AvalonEdit +{ + internal class AvalonSyntax + { + public static void LoadHighlighting() + { + var assembly = Assembly.GetExecutingAssembly(); + var regex = new Regex(@"CosmosDbExplorer\.AvalonEdit\.(?[a-zA-Z0-9-_]+).xshd"); + var resourceNames = assembly.GetManifestResourceNames(); + foreach (var resourceName in resourceNames) + { + var match = regex.Match(resourceName); + if (!match.Success) + { + continue; + } + + var syntaxName = match.Groups["syntaxName"].Value; + using var stream = assembly.GetManifestResourceStream(resourceName)!; + using var reader = XmlReader.Create(stream); + var definition = HighlightingLoader.Load(reader, HighlightingManager.Instance); + + HighlightingManager.Instance.RegisterHighlighting(syntaxName, Array.Empty(), definition); + } + } + } +} diff --git a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/BraceFoldingStrategy.cs b/src/CosmosDbExplorer/AvalonEdit/BraceFoldingStrategy.cs similarity index 95% rename from src/CosmosDbExplorer/Infrastructure/AvalonEdit/BraceFoldingStrategy.cs rename to src/CosmosDbExplorer/AvalonEdit/BraceFoldingStrategy.cs index e8ce6b9..191976e 100644 --- a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/BraceFoldingStrategy.cs +++ b/src/CosmosDbExplorer/AvalonEdit/BraceFoldingStrategy.cs @@ -1,8 +1,8 @@ -using ICSharpCode.AvalonEdit.Document; +using System.Collections.Generic; +using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Folding; -using System.Collections.Generic; -namespace CosmosDbExplorer.Infrastructure.AvalonEdit +namespace CosmosDbExplorer.AvalonEdit { /// /// Allows producing foldings from a document based on braces. diff --git a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/DocumentDbSql.xshd b/src/CosmosDbExplorer/AvalonEdit/DocumentDbSql.xshd similarity index 58% rename from src/CosmosDbExplorer/Infrastructure/AvalonEdit/DocumentDbSql.xshd rename to src/CosmosDbExplorer/AvalonEdit/DocumentDbSql.xshd index 44c96c1..54b9f90 100644 --- a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/DocumentDbSql.xshd +++ b/src/CosmosDbExplorer/AvalonEdit/DocumentDbSql.xshd @@ -1,33 +1,63 @@  - - + - - + + - - - - + + - - - - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - diff --git a/src/CosmosDbExplorer/AvalonEdit/JSON.xshd b/src/CosmosDbExplorer/AvalonEdit/JSON.xshd new file mode 100644 index 0000000..3bfc021 --- /dev/null +++ b/src/CosmosDbExplorer/AvalonEdit/JSON.xshd @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + " + " + + + ' + ' + + + : + + + , + + + + + + + , + + + + + + true + false + + + null + + + " + " + + + ' + ' + + + \{ + \} + + + \[ + \] + + + \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? + + + + + + + \ No newline at end of file diff --git a/src/CosmosDbExplorer/AvalonEdit/JavaScript-Mode.xshd b/src/CosmosDbExplorer/AvalonEdit/JavaScript-Mode.xshd new file mode 100644 index 0000000..2c3cd9e --- /dev/null +++ b/src/CosmosDbExplorer/AvalonEdit/JavaScript-Mode.xshd @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + break + continue + delete + else + for + function + if + in + new + return + this + typeof + var + void + while + with + abstract + boolean + byte + case + catch + char + class + const + debugger + default + do + double + enum + export + extends + final + finally + float + goto + implements + import + instanceof + int + interface + long + native + package + private + protected + public + short + static + super + switch + synchronized + throw + throws + transient + try + volatile + + + Array + Boolean + Date + Function + Global + Math + Number + Object + RegExp + String + + + false + null + true + NaN + Infinity + + + eval + parseInt + parseFloat + escape + unescape + isNaN + isFinite + + + // + + + /\* + \*/ + + + (?<!([})\]\w]+\s*))/ + / + + + " + " + + + + + + ' + ' + + + + + \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? + + \ No newline at end of file diff --git a/src/CosmosDbExplorer/Behaviors/AvalonTextEditorBehavior.cs b/src/CosmosDbExplorer/Behaviors/AvalonTextEditorBehavior.cs new file mode 100644 index 0000000..d2567e9 --- /dev/null +++ b/src/CosmosDbExplorer/Behaviors/AvalonTextEditorBehavior.cs @@ -0,0 +1,63 @@ + +using System.Windows; +using ICSharpCode.AvalonEdit; +using ICSharpCode.AvalonEdit.Search; +using Microsoft.Xaml.Behaviors; + +namespace CosmosDbExplorer.Behaviors +{ + + public class AvalonTextEditorBehavior : Behavior + { + protected override void OnAttached() + { + base.OnAttached(); + OnUseSearchChanged(); + } + + protected override void OnDetaching() + { + base.OnDetaching(); + } + + public bool UseSearch + { + get { return (bool)GetValue(UseSearchProperty); } + set { SetValue(UseSearchProperty, value); } + } + + public static readonly DependencyProperty UseSearchProperty = + DependencyProperty.Register( + "UseSearch", + typeof(bool), + typeof(AvalonTextEditorBehavior), + new PropertyMetadata(false, OnUseSearchPropertyChanged)); + + private static void OnUseSearchPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is AvalonTextEditorBehavior behavior) + { + behavior.OnUseSearchChanged(); + } + } + + private SearchPanel? _searchPanel; + private void OnUseSearchChanged() + { + if (AssociatedObject is null) + { + return; + } + + if (UseSearch) + { + _searchPanel ??= SearchPanel.Install(AssociatedObject.TextArea); + } + else if (_searchPanel != null) + { + _searchPanel.Uninstall(); + _searchPanel = null; + } + } + } +} diff --git a/src/CosmosDbExplorer/Behaviors/AvalonTextEditorBindingBehavior.cs b/src/CosmosDbExplorer/Behaviors/AvalonTextEditorBindingBehavior.cs new file mode 100644 index 0000000..72aecc1 --- /dev/null +++ b/src/CosmosDbExplorer/Behaviors/AvalonTextEditorBindingBehavior.cs @@ -0,0 +1,213 @@ +using System; +using System.Windows; +using ICSharpCode.AvalonEdit; +using Microsoft.Xaml.Behaviors; + +namespace CosmosDbExplorer.Behaviors +{ + public class AvalonTextEditorBindingBehavior : Behavior + { + protected override void OnAttached() + { + base.OnAttached(); + AssociatedObject.TextChanged += AssociatedObjectTextChanged; + AssociatedObject.TextArea.SelectionChanged += AssociatedObjectSelectionChanged; + AssociatedObject.TextArea.Caret.PositionChanged += AssociatedObjectCaretPositionChanged; + } + + protected override void OnDetaching() + { + AssociatedObject.TextChanged -= AssociatedObjectTextChanged; + base.OnDetaching(); + AssociatedObject.TextArea.SelectionChanged -= AssociatedObjectSelectionChanged; + AssociatedObject.TextArea.Caret.PositionChanged -= AssociatedObjectCaretPositionChanged; + } + + public string Text + { + get => (string)GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register( + nameof(Text), + typeof(string), + typeof(AvalonTextEditorBindingBehavior), + new FrameworkPropertyMetadata( + null, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + OnTextPropertyChanged, + CoerceTextProperty)); + + private static object CoerceTextProperty(DependencyObject d, object baseValue) + { + return baseValue ?? string.Empty; + } + + private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is AvalonTextEditorBindingBehavior b) + { + b.OnTextChanged(); + } + } + + private void OnTextChanged() + { + if (AssociatedObject is null) + { + return; + } + + if (AssociatedObject.Text != Text) + { + AssociatedObject.Text = Text ?? string.Empty; + } + } + + private void AssociatedObjectTextChanged(object? sender, EventArgs e) + { + if (Text != AssociatedObject.Text) + { + SetCurrentValue(TextProperty, AssociatedObject.Text ?? string.Empty); + } + } + + public string SelectedText + { + get => (string)GetValue(SelectedTextProperty); + set => SetValue(SelectedTextProperty, value); + } + + public static readonly DependencyProperty SelectedTextProperty = + DependencyProperty.Register( + "SelectedText", + typeof(string), + typeof(AvalonTextEditorBindingBehavior), + new FrameworkPropertyMetadata( + null, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + OnSelectedTextPropertyChanged)); + + private static void OnSelectedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is AvalonTextEditorBindingBehavior b) + { + b.OnSelectedTextChanged(); + } + } + + private void OnSelectedTextChanged() + { + if (AssociatedObject is null) + { + return; + } + + if (AssociatedObject.SelectedText != SelectedText) + { + AssociatedObject.SelectedText = SelectedText ?? string.Empty; + } + } + + + public (int start, int length) Selection + { + get { return ((int start, int length))GetValue(SelectionProperty); } + set { SetValue(SelectionProperty, value); } + } + + public static readonly DependencyProperty SelectionProperty = + DependencyProperty.Register( + "Selection", + typeof((int start, int length)), + typeof(AvalonTextEditorBindingBehavior), + new FrameworkPropertyMetadata( + (0, 0), + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + OnSelectionPropertyChanged)); + + private static void OnSelectionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is AvalonTextEditorBindingBehavior b) + { + b.OnSelectionChanged(); + } + } + + private void OnSelectionChanged() + { + if (AssociatedObject is null) + { + return; + } + + var associatedObjectSelection = (AssociatedObject.SelectionStart, AssociatedObject.SelectionLength); + if (associatedObjectSelection != Selection) + { + var (start, end) = Selection; + AssociatedObject.Select(start, end); + } + } + + private void AssociatedObjectSelectionChanged(object? sender, EventArgs e) + { + if (SelectedText != AssociatedObject.SelectedText) + { + SetCurrentValue(SelectedTextProperty, AssociatedObject.SelectedText ?? string.Empty); + } + + var associatedObjectSelection = (AssociatedObject.SelectionStart, AssociatedObject.SelectionLength); + if (Selection != associatedObjectSelection) + { + SetCurrentValue(SelectionProperty, associatedObjectSelection); + } + } + + public int CursorPosition + { + get { return (int)GetValue(CursorPositionProperty); } + set { SetValue(CursorPositionProperty, value); } + } + + public static readonly DependencyProperty CursorPositionProperty = + DependencyProperty.Register( + "CursorPosition", + typeof(int), + typeof(AvalonTextEditorBindingBehavior), + new FrameworkPropertyMetadata( + 0, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + OnCursorPositionPropertyChanged)); + + private static void OnCursorPositionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is AvalonTextEditorBindingBehavior b) + { + b.OnCursorPositionChanged(); + } + } + + private void OnCursorPositionChanged() + { + if (AssociatedObject is null) + { + return; + } + + if (AssociatedObject.CaretOffset != CursorPosition) + { + AssociatedObject.CaretOffset = CursorPosition; + } + } + + private void AssociatedObjectCaretPositionChanged(object? sender, EventArgs e) + { + if (CursorPosition != AssociatedObject.CaretOffset) + { + SetCurrentValue(CursorPositionProperty, AssociatedObject.CaretOffset); + } + } + } +} diff --git a/src/CosmosDbExplorer/Behaviors/AvalonTextEditorBraceFoldingBehavior.cs b/src/CosmosDbExplorer/Behaviors/AvalonTextEditorBraceFoldingBehavior.cs new file mode 100644 index 0000000..9e21f64 --- /dev/null +++ b/src/CosmosDbExplorer/Behaviors/AvalonTextEditorBraceFoldingBehavior.cs @@ -0,0 +1,134 @@ +using System; +using System.Windows; +using System.Windows.Threading; +using CosmosDbExplorer.AvalonEdit; +using ICSharpCode.AvalonEdit; +using ICSharpCode.AvalonEdit.Folding; +using Microsoft.Xaml.Behaviors; + +namespace CosmosDbExplorer.Behaviors +{ + public class AvalonTextEditorBraceFoldingBehavior : Behavior + { + private readonly BraceFoldingStrategy _foldingStrategy = new(); + private FoldingManager? _foldingManager; + private readonly DispatcherTimer _timer = new(); + + protected override void OnAttached() + { + base.OnAttached(); + + //_timer.Interval = System.TimeSpan.FromMilliseconds(Interval); + _timer.Tick += OnTimerTicked; + + //if (UseFolding) + //{ + // _timer.Start(); + //} + } + + protected override void OnDetaching() + { + _timer.Stop(); + _timer.Tick -= OnTimerTicked; + + if (_foldingManager is not null) + { + FoldingManager.Uninstall(_foldingManager); + } + + base.OnDetaching(); + } + + public int Interval + { + get { return (int)GetValue(IntervalProperty); } + set { SetValue(IntervalProperty, value); } + } + + public static readonly DependencyProperty IntervalProperty = + DependencyProperty.Register( + "Interval", + typeof(int), + typeof(AvalonTextEditorBraceFoldingBehavior), + new PropertyMetadata(1000, OnIntervalPropertyChanged)); + + private static void OnIntervalPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is AvalonTextEditorBraceFoldingBehavior behavior) + { + behavior.OnIntervalChanged(); + } + } + + public bool UseFolding + { + get { return (bool)GetValue(UseFoldingProperty); } + set { SetValue(UseFoldingProperty, value); } + } + + // Using a DependencyProperty as the backing store for UseFolding. This enables animation, styling, binding, etc... + public static readonly DependencyProperty UseFoldingProperty = + DependencyProperty.Register( + "UseFolding", + typeof(bool), + typeof(AvalonTextEditorBraceFoldingBehavior), + new PropertyMetadata(false, OnUseFoldingPropertyChanged)); + + private static void OnUseFoldingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is AvalonTextEditorBraceFoldingBehavior behavior) + { + behavior.OnUseFoldingChanged(); + } + } + + private void OnUseFoldingChanged() + { + if (UseFolding) + { + _timer.Start(); + } + else + { + if (_foldingManager != null) + { + _foldingManager.Clear(); + FoldingManager.Uninstall(_foldingManager); + } + + _timer.Stop(); + } + } + + private void OnIntervalChanged() + { + if (AssociatedObject is null) + { + return; + } + + _timer.Stop(); + _timer.Interval = System.TimeSpan.FromMilliseconds(Interval); + _timer.Start(); + } + + private void OnTimerTicked(object? sender, System.EventArgs e) + { + if (AssociatedObject is null) + { + return; + } + + if (_foldingManager == null && AssociatedObject?.TextArea?.Document?.Text != null) + { + _foldingManager = FoldingManager.Install(AssociatedObject.TextArea); + } + + if (_foldingManager != null) + { + _foldingStrategy.UpdateFoldings(_foldingManager, AssociatedObject.Document); + } + } + } +} diff --git a/src/CosmosDbExplorer/Behaviors/BackstageTabNavigationBehavior.cs b/src/CosmosDbExplorer/Behaviors/BackstageTabNavigationBehavior.cs new file mode 100644 index 0000000..af7cbd5 --- /dev/null +++ b/src/CosmosDbExplorer/Behaviors/BackstageTabNavigationBehavior.cs @@ -0,0 +1,80 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Navigation; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; + +using Fluent; + +using Microsoft.Xaml.Behaviors; + +namespace CosmosDbExplorer.Behaviors +{ + public class BackstageTabNavigationBehavior : Behavior + { + private IPageService _pageService; + + public BackstageTabNavigationBehavior() + { + } + + public void Initialize(IPageService pageService) + { + _pageService = pageService; + } + + protected override void OnAttached() + { + base.OnAttached(); + AssociatedObject.SelectionChanged += OnSelectionChanged; + } + + protected override void OnDetaching() + { + base.OnDetaching(); + AssociatedObject.SelectionChanged -= OnSelectionChanged; + } + + private void OnSelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) + { + if (e.RemovedItems.Count > 0 && e.RemovedItems[0] is BackstageTabItem oldItem) + { + var content = oldItem.Content as Frame; + if (content?.Content is FrameworkElement element) + { + if (element.DataContext is INavigationAware navigationAware) + { + navigationAware.OnNavigatedFrom(); + } + } + } + + if (e.AddedItems.Count > 0 && e.AddedItems[0] is BackstageTabItem tabItem) + { + var frame = new Frame() + { + Focusable = false, + NavigationUIVisibility = NavigationUIVisibility.Hidden + }; + + frame.Navigated += OnNavigated; + tabItem.Content = frame; + var page = _pageService.GetPage(tabItem.Tag as string); + frame.Navigate(page); + } + } + + private void OnNavigated(object sender, NavigationEventArgs e) + { + if (e.Content is FrameworkElement element) + { + if (element.DataContext is INavigationAware navigationAware) + { + navigationAware.OnNavigatedTo(e.ExtraData); + } + } + } + } +} diff --git a/src/CosmosDbExplorer/Behaviors/RibbonPageConfiguration.cs b/src/CosmosDbExplorer/Behaviors/RibbonPageConfiguration.cs new file mode 100644 index 0000000..ced6bdf --- /dev/null +++ b/src/CosmosDbExplorer/Behaviors/RibbonPageConfiguration.cs @@ -0,0 +1,42 @@ +using System.Collections.ObjectModel; +using System.Windows; +using System.Windows.Data; + +using Fluent; + +using Microsoft.Toolkit.Mvvm.ComponentModel; + +namespace CosmosDbExplorer.Behaviors +{ + public class RibbonPageConfiguration + { + public Collection HomeGroups { get; set; } = new Collection(); + + public Collection Tabs { get; set; } = new Collection(); + + public RibbonPageConfiguration() + { + } + + public void SetDataContext(ObservableObject viewModel, BindingMode bindingMode = BindingMode.OneWay) + { + foreach (var groups in HomeGroups) + { + groups.SetBinding(FrameworkElement.DataContextProperty, new Binding + { + Source = viewModel, + Mode = bindingMode + }); + } + + foreach (var tab in Tabs) + { + tab.SetBinding(FrameworkElement.DataContextProperty, new Binding + { + Source = viewModel, + Mode = bindingMode + }); + } + } + } +} diff --git a/src/CosmosDbExplorer/Behaviors/RibbonTabsBehavior.cs b/src/CosmosDbExplorer/Behaviors/RibbonTabsBehavior.cs new file mode 100644 index 0000000..76b2557 --- /dev/null +++ b/src/CosmosDbExplorer/Behaviors/RibbonTabsBehavior.cs @@ -0,0 +1,133 @@ +using System.Collections.ObjectModel; +using System.Linq; +using System.Windows; +using System.Windows.Controls; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Models; + +using Fluent; + +using Microsoft.Xaml.Behaviors; + +namespace CosmosDbExplorer.Behaviors +{ + // See how to add new Tabs and new groups in Home Tab from your pages https://github.com/microsoft/WindowsTemplateStudio/blob/release/docs/WPF/projectTypes/ribbon.md + public class RibbonTabsBehavior : Behavior + { + private INavigationService? _navigationService; + + public static readonly DependencyProperty IsHomeTabProperty = DependencyProperty.RegisterAttached( + "IsHomeTab", typeof(bool), typeof(RibbonTabsBehavior), new PropertyMetadata(default(bool))); + + public static void SetIsHomeTab(DependencyObject element, bool value) + => element.SetValue(IsHomeTabProperty, value); + + public static bool GetIsHomeTab(DependencyObject element) + => (bool)element.GetValue(IsHomeTabProperty); + + public static bool GetIsTabFromPage(RibbonTabItem item) + => (bool)item.GetValue(IsTabFromPageProperty); + + public static void SetIsTabFromPage(RibbonTabItem item, bool value) + => item.SetValue(IsTabFromPageProperty, value); + + public static readonly DependencyProperty IsTabFromPageProperty = + DependencyProperty.RegisterAttached("IsTabFromPage", typeof(bool), typeof(RibbonTabItem), new PropertyMetadata(false)); + + public static bool GetIsGroupFromPage(RibbonGroupBox item) + => (bool)item.GetValue(IsGroupFromPageProperty); + + public static void SetIsGroupFromPage(RibbonGroupBox item, bool value) + => item.SetValue(IsGroupFromPageProperty, value); + + public static readonly DependencyProperty IsGroupFromPageProperty = + DependencyProperty.RegisterAttached("IsGroupFromPage", typeof(bool), typeof(RibbonGroupBox), new PropertyMetadata(false)); + + public static RibbonPageConfiguration GetPageConfiguration(Page item) + => (RibbonPageConfiguration)item.GetValue(PageConfigurationProperty); + + public static void SetPageConfiguration(Page item, RibbonPageConfiguration value) + => item.SetValue(PageConfigurationProperty, value); + + public static readonly DependencyProperty PageConfigurationProperty = + DependencyProperty.Register("PageConfiguration", typeof(RibbonPageConfiguration), typeof(Page), new PropertyMetadata(new RibbonPageConfiguration())); + + public void Initialize(INavigationService navigationService) + { + _navigationService = navigationService; + _navigationService.Navigated += OnNavigated; + } + + public void Unsubscribe() + { + if (_navigationService != null) + { + _navigationService.Navigated -= OnNavigated; + } + } + + private void OnNavigated(object sender, string e) + { + var frame = sender as Frame; + if (frame != null && frame.Content is Page page) + { + UpdateTabs(page); + } + } + + private void UpdateTabs(Page page) + { + if (page != null) + { + var config = GetPageConfiguration(page); + SetupHomeGroups(config.HomeGroups); + SetupTabs(config.Tabs); + } + } + + private void SetupHomeGroups(Collection homeGroups) + { + var homeTab = AssociatedObject.Tabs.FirstOrDefault(GetIsHomeTab); + if (homeTab == null) + { + return; + } + + for (int i = homeTab.Groups.Count - 1; i >= 0; i--) + { + if (GetIsGroupFromPage(homeTab.Groups[i])) + { + homeTab.Groups.RemoveAt(i); + } + } + + foreach (var group in homeGroups) + { + if (GetIsGroupFromPage(group)) + { + homeTab.Groups.Add(group); + } + } + } + + private void SetupTabs(Collection tabs) + { + for (int i = AssociatedObject.Tabs.Count - 1; i >= 0; i--) + { + if (GetIsTabFromPage(AssociatedObject.Tabs[i])) + { + AssociatedObject.Tabs.RemoveAt(i); + } + } + + foreach (var tab in tabs) + { + if (GetIsTabFromPage(tab)) + { + AssociatedObject.Tabs.Add(tab); + } + } + } + } +} diff --git a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/SelectedTextBehavior.cs b/src/CosmosDbExplorer/Behaviors/SelectedTextBehavior.cs similarity index 93% rename from src/CosmosDbExplorer/Infrastructure/AvalonEdit/SelectedTextBehavior.cs rename to src/CosmosDbExplorer/Behaviors/SelectedTextBehavior.cs index c42d0d0..e4e0bc6 100644 --- a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/SelectedTextBehavior.cs +++ b/src/CosmosDbExplorer/Behaviors/SelectedTextBehavior.cs @@ -1,10 +1,10 @@ using System; using System.Windows; -using System.Windows.Interactivity; using ICSharpCode.AvalonEdit; using ICSharpCode.AvalonEdit.Editing; +using Microsoft.Xaml.Behaviors; -namespace CosmosDbExplorer.Infrastructure.AvalonEdit +namespace CosmosDbExplorer.Behaviors { public sealed class SelectedTextBehavior : Behavior { diff --git a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/TextAreaZoomBehavior.cs b/src/CosmosDbExplorer/Behaviors/TextAreaZoomBehavior.cs similarity index 91% rename from src/CosmosDbExplorer/Infrastructure/AvalonEdit/TextAreaZoomBehavior.cs rename to src/CosmosDbExplorer/Behaviors/TextAreaZoomBehavior.cs index 5938737..faa6fa4 100644 --- a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/TextAreaZoomBehavior.cs +++ b/src/CosmosDbExplorer/Behaviors/TextAreaZoomBehavior.cs @@ -1,9 +1,9 @@ using System.Windows; -using System.Windows.Interactivity; using System.Windows.Media; using ICSharpCode.AvalonEdit; +using Microsoft.Xaml.Behaviors; -namespace CosmosDbExplorer.Infrastructure.AvalonEdit +namespace CosmosDbExplorer.Behaviors { public class TextAreaZoomBehavior : Behavior { diff --git a/src/CosmosDbExplorer/Infrastructure/Behaviors/WatermarkBehavior.cs b/src/CosmosDbExplorer/Behaviors/WatermarkBehavior.cs similarity index 97% rename from src/CosmosDbExplorer/Infrastructure/Behaviors/WatermarkBehavior.cs rename to src/CosmosDbExplorer/Behaviors/WatermarkBehavior.cs index 358b88f..48c8e31 100644 --- a/src/CosmosDbExplorer/Infrastructure/Behaviors/WatermarkBehavior.cs +++ b/src/CosmosDbExplorer/Behaviors/WatermarkBehavior.cs @@ -1,16 +1,15 @@ -using System; -using System.Windows; +using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; -using System.Windows.Interactivity; using System.Windows.Media; +using Microsoft.Xaml.Behaviors; -namespace CosmosDbExplorer.Infrastructure.Behaviors +namespace CosmosDbExplorer.Behaviors { public class WatermarkBehavior : Behavior { - private WaterMarkAdorner _adorner; + private WaterMarkAdorner? _adorner; public string Text { diff --git a/src/CosmosDbExplorer/Contracts/Activation/IActivationHandler.cs b/src/CosmosDbExplorer/Contracts/Activation/IActivationHandler.cs new file mode 100644 index 0000000..782e773 --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/Activation/IActivationHandler.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace CosmosDbExplorer.Contracts.Activation +{ + public interface IActivationHandler + { + bool CanHandle(); + + Task HandleAsync(); + } +} diff --git a/src/CosmosDbExplorer/Contracts/Services/IApplicationInfoService.cs b/src/CosmosDbExplorer/Contracts/Services/IApplicationInfoService.cs new file mode 100644 index 0000000..9a3027c --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/Services/IApplicationInfoService.cs @@ -0,0 +1,9 @@ +using System; + +namespace CosmosDbExplorer.Contracts.Services +{ + public interface IApplicationInfoService + { + Version GetVersion(); + } +} diff --git a/src/CosmosDbExplorer/Contracts/Services/IDialogService.cs b/src/CosmosDbExplorer/Contracts/Services/IDialogService.cs new file mode 100644 index 0000000..c385810 --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/Services/IDialogService.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CosmosDbExplorer.Services.DialogSettings; + +namespace CosmosDbExplorer.Contracts.Services +{ + public interface IFileDialogService + { + void ShowOpenFileDialog(OpenFileDialogSettings settings, Action? afterHideCallback = null); + void ShowSaveFileDialog(SaveFileDialogSettings settings, Action? afterHideCallback = null); + void ShowFolderBrowserDialog(FolderBrowserDialogSettings settings, Action? afterHideCallback = null); + + } + + public interface IDialogService : IFileDialogService + { + Task ShowError(string message, string title, Action? afterHideCallback = null); + Task ShowError(Exception error, string title, Action? afterHideCallback = null); + Task ShowMessage(string message, string title, Action? afterHideCallback = null); + Task ShowQuestion(string message, string title, Action? afterHideCallback = null); + } +} diff --git a/src/CosmosDbExplorer/Contracts/Services/INavigationService.cs b/src/CosmosDbExplorer/Contracts/Services/INavigationService.cs new file mode 100644 index 0000000..c50790e --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/Services/INavigationService.cs @@ -0,0 +1,22 @@ +using System; +using System.Windows.Controls; + +namespace CosmosDbExplorer.Contracts.Services +{ + public interface INavigationService + { + event EventHandler Navigated; + + bool CanGoBack { get; } + + void Initialize(Frame shellFrame); + + bool NavigateTo(string pageKey, object? parameter = null, bool clearNavigation = false); + + void GoBack(); + + void UnsubscribeNavigation(); + + void CleanNavigation(); + } +} diff --git a/src/CosmosDbExplorer/Contracts/Services/IPageService.cs b/src/CosmosDbExplorer/Contracts/Services/IPageService.cs new file mode 100644 index 0000000..167ef61 --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/Services/IPageService.cs @@ -0,0 +1,12 @@ +using System; +using System.Windows.Controls; + +namespace CosmosDbExplorer.Contracts.Services +{ + public interface IPageService + { + Type GetPageType(string key); + + Page GetPage(string key); + } +} diff --git a/src/CosmosDbExplorer/Contracts/Services/IPersistAndRestoreService.cs b/src/CosmosDbExplorer/Contracts/Services/IPersistAndRestoreService.cs new file mode 100644 index 0000000..19ae0f5 --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/Services/IPersistAndRestoreService.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +using CosmosDbExplorer.Core.Models; + +namespace CosmosDbExplorer.Contracts.Services +{ + public interface IPersistAndRestoreService + { + void RestoreData(); + + void PersistData(); + + void ResetData(); + + List GetConnections(); + + void PersistConnection(CosmosConnection connection); + + void RemoveConnection(CosmosConnection connection); + + void ReorderConnections(int sourceIndex, int targetIndex); + } +} diff --git a/src/CosmosDbExplorer/Contracts/Services/IRightPaneService.cs b/src/CosmosDbExplorer/Contracts/Services/IRightPaneService.cs new file mode 100644 index 0000000..150dd8a --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/Services/IRightPaneService.cs @@ -0,0 +1,20 @@ +using System; +using System.Windows.Controls; + +using MahApps.Metro.Controls; + +namespace CosmosDbExplorer.Contracts.Services +{ + public interface IRightPaneService + { + event EventHandler PaneOpened; + + event EventHandler PaneClosed; + + void OpenInRightPane(string pageKey, object? parameter = null); + + void Initialize(Frame rightPaneFrame, SplitView splitView); + + void CleanUp(); + } +} diff --git a/src/CosmosDbExplorer/Contracts/Services/ISystemService.cs b/src/CosmosDbExplorer/Contracts/Services/ISystemService.cs new file mode 100644 index 0000000..6efb5a2 --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/Services/ISystemService.cs @@ -0,0 +1,7 @@ +namespace CosmosDbExplorer.Contracts.Services +{ + public interface ISystemService + { + void OpenInWebBrowser(string url); + } +} diff --git a/src/CosmosDbExplorer/Contracts/Services/IThemeSelectorService.cs b/src/CosmosDbExplorer/Contracts/Services/IThemeSelectorService.cs new file mode 100644 index 0000000..4fd3d96 --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/Services/IThemeSelectorService.cs @@ -0,0 +1,15 @@ +using System; + +using CosmosDbExplorer.Models; + +namespace CosmosDbExplorer.Contracts.Services +{ + public interface IThemeSelectorService + { + void InitializeTheme(); + + void SetTheme(AppTheme theme); + + AppTheme GetCurrentTheme(); + } +} diff --git a/src/CosmosDbExplorer/Services/IUIServices.cs b/src/CosmosDbExplorer/Contracts/Services/IUIServices.cs similarity index 64% rename from src/CosmosDbExplorer/Services/IUIServices.cs rename to src/CosmosDbExplorer/Contracts/Services/IUIServices.cs index 0b033f4..dac98b4 100644 --- a/src/CosmosDbExplorer/Services/IUIServices.cs +++ b/src/CosmosDbExplorer/Contracts/Services/IUIServices.cs @@ -1,4 +1,4 @@ -namespace CosmosDbExplorer.Services +namespace CosmosDbExplorer.Contracts.Services { public interface IUIServices { diff --git a/src/CosmosDbExplorer/Contracts/Services/IWindowManagerService.cs b/src/CosmosDbExplorer/Contracts/Services/IWindowManagerService.cs new file mode 100644 index 0000000..7b89623 --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/Services/IWindowManagerService.cs @@ -0,0 +1,15 @@ +using System.Windows; + +namespace CosmosDbExplorer.Contracts.Services +{ + public interface IWindowManagerService + { + Window MainWindow { get; } + + void OpenInNewWindow(string pageKey, object? parameter = null); + + bool? OpenInDialog(string pageKey, object? parameter = null); + + Window GetWindow(string pageKey); + } +} diff --git a/src/CosmosDbExplorer/Contracts/ViewModels/IAssetNode.cs b/src/CosmosDbExplorer/Contracts/ViewModels/IAssetNode.cs new file mode 100644 index 0000000..51a9f07 --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/ViewModels/IAssetNode.cs @@ -0,0 +1,13 @@ +using System.Windows.Media; +using CosmosDbExplorer.Core.Contracts; + +namespace CosmosDbExplorer.Contracts.ViewModels +{ + // TODO: Define an CosmosDB Resource Class + public interface IAssetNode : IContent + where T : ICosmosResource + { + System.Drawing.Color? AccentColor { get; } + T? Resource { get; } + } +} diff --git a/src/CosmosDbExplorer/Contracts/ViewModels/IAssetTabCommand.cs b/src/CosmosDbExplorer/Contracts/ViewModels/IAssetTabCommand.cs new file mode 100644 index 0000000..2d243b5 --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/ViewModels/IAssetTabCommand.cs @@ -0,0 +1,11 @@ +using System.Windows.Input; + +namespace CosmosDbExplorer.Contracts.ViewModels +{ + public interface IAssetTabCommand + { + ICommand SaveCommand { get; } + ICommand DiscardCommand { get; } + ICommand DeleteCommand { get; } + } +} diff --git a/src/CosmosDbExplorer/Contracts/ViewModels/ICanClose.cs b/src/CosmosDbExplorer/Contracts/ViewModels/ICanClose.cs new file mode 100644 index 0000000..647e7a1 --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/ViewModels/ICanClose.cs @@ -0,0 +1,9 @@ +using System; + +namespace CosmosDbExplorer.Contracts.ViewModels +{ + public interface ICanClose + { + Action SetResult { get; } + } +} diff --git a/src/CosmosDbExplorer/Contracts/ViewModels/ICanRefreshNode.cs b/src/CosmosDbExplorer/Contracts/ViewModels/ICanRefreshNode.cs new file mode 100644 index 0000000..03c60aa --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/ViewModels/ICanRefreshNode.cs @@ -0,0 +1,9 @@ +using System.Windows.Input; + +namespace CosmosDbExplorer.Contracts.ViewModels +{ + public interface ICanRefreshNode + { + ICommand RefreshCommand { get; } + } +} diff --git a/src/CosmosDbExplorer/Contracts/ViewModels/ICanRefreshTab.cs b/src/CosmosDbExplorer/Contracts/ViewModels/ICanRefreshTab.cs new file mode 100644 index 0000000..1c7d58b --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/ViewModels/ICanRefreshTab.cs @@ -0,0 +1,10 @@ +using System.Windows.Input; +using Microsoft.Toolkit.Mvvm.Input; + +namespace CosmosDbExplorer.Contracts.ViewModels +{ + public interface ICanRefreshTab + { + ICommand RefreshCommand { get; } + } +} diff --git a/src/CosmosDbExplorer/Contracts/ViewModels/IContent.cs b/src/CosmosDbExplorer/Contracts/ViewModels/IContent.cs new file mode 100644 index 0000000..708211e --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/ViewModels/IContent.cs @@ -0,0 +1,7 @@ +namespace CosmosDbExplorer.Contracts.ViewModels +{ + public interface IContent + { + string? ContentId { get; } + } +} diff --git a/src/CosmosDbExplorer/Contracts/ViewModels/IHaveOpenCommand.cs b/src/CosmosDbExplorer/Contracts/ViewModels/IHaveOpenCommand.cs new file mode 100644 index 0000000..589f952 --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/ViewModels/IHaveOpenCommand.cs @@ -0,0 +1,9 @@ +using Microsoft.Toolkit.Mvvm.Input; + +namespace CosmosDbExplorer.Contracts.ViewModels +{ + public interface IHaveOpenCommand + { + RelayCommand OpenCommand { get; } + } +} diff --git a/src/CosmosDbExplorer/Contracts/ViewModels/IHaveQuerySettings.cs b/src/CosmosDbExplorer/Contracts/ViewModels/IHaveQuerySettings.cs new file mode 100644 index 0000000..5976ddb --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/ViewModels/IHaveQuerySettings.cs @@ -0,0 +1,18 @@ +namespace CosmosDbExplorer.Contracts.ViewModels +{ + //public interface IHaveQuerySettings + //{ + // bool HideSystemProperties { get; set; } + // bool? EnableScanInQuery { get; set; } + // bool? EnableCrossPartitionQuery { get; set; } + // int? MaxItemCount { get; set; } + // int? MaxDOP { get; set; } + // int? MaxBufferItem { get; set; } + // string PartitionKeyValue { get; set; } + //} + + public interface IHaveSystemProperties + { + bool HideSystemProperties { get; set; } + } +} diff --git a/src/CosmosDbExplorer/Contracts/ViewModels/IHaveRequestOptions.cs b/src/CosmosDbExplorer/Contracts/ViewModels/IHaveRequestOptions.cs new file mode 100644 index 0000000..0a4ab41 --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/ViewModels/IHaveRequestOptions.cs @@ -0,0 +1,15 @@ +using CosmosDbExplorer.Core.Models; + +namespace CosmosDbExplorer.Contracts.ViewModels +{ + public interface IHaveRequestOptions + { + CosmosIndexingDirectives? IndexingDirective { get; set; } + CosmosConsistencyLevels? ConsistencyLevel { get; set; } + //string? PartitionKeyValue { get; set; } + CosmosAccessConditionType AccessConditionType { get; set; } + string? AccessCondition { get; set; } + string? PreTrigger { get; set; } + string? PostTrigger { get; set; } + } +} diff --git a/src/CosmosDbExplorer/Contracts/ViewModels/INavigationAware.cs b/src/CosmosDbExplorer/Contracts/ViewModels/INavigationAware.cs new file mode 100644 index 0000000..ad5a842 --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/ViewModels/INavigationAware.cs @@ -0,0 +1,9 @@ +namespace CosmosDbExplorer.Contracts.ViewModels +{ + public interface INavigationAware + { + void OnNavigatedTo(object parameter); + + void OnNavigatedFrom(); + } +} diff --git a/src/CosmosDbExplorer/Contracts/ViewModels/ITreeViewItemViewModel.cs b/src/CosmosDbExplorer/Contracts/ViewModels/ITreeViewItemViewModel.cs new file mode 100644 index 0000000..6bd8f7f --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/ViewModels/ITreeViewItemViewModel.cs @@ -0,0 +1,15 @@ +using System.Collections.ObjectModel; +using System.Windows.Input; + +namespace CosmosDbExplorer.Contracts.ViewModels +{ + public interface ITreeViewItemViewModel + { + ObservableCollection Children { get; } + bool HasDummyChild { get; } + bool IsExpanded { get; set; } + bool IsSelected { get; set; } + bool IsLoading { get; set; } + ITreeViewItemViewModel Parent { get; } + } +} diff --git a/src/CosmosDbExplorer/Contracts/Views/IShellDialogWindow.cs b/src/CosmosDbExplorer/Contracts/Views/IShellDialogWindow.cs new file mode 100644 index 0000000..19434b2 --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/Views/IShellDialogWindow.cs @@ -0,0 +1,9 @@ +using System.Windows.Controls; + +namespace CosmosDbExplorer.Contracts.Views +{ + public interface IShellDialogWindow + { + Frame GetDialogFrame(); + } +} diff --git a/src/CosmosDbExplorer/Contracts/Views/IShellWindow.cs b/src/CosmosDbExplorer/Contracts/Views/IShellWindow.cs new file mode 100644 index 0000000..0a838a6 --- /dev/null +++ b/src/CosmosDbExplorer/Contracts/Views/IShellWindow.cs @@ -0,0 +1,23 @@ +using System.Windows.Controls; + +using CosmosDbExplorer.Behaviors; + +using MahApps.Metro.Controls; + +namespace CosmosDbExplorer.Contracts.Views +{ + public interface IShellWindow + { + //Frame GetNavigationFrame(); + + void ShowWindow(); + + void CloseWindow(); + + Frame GetRightPaneFrame(); + + SplitView GetSplitView(); + + RibbonTabsBehavior GetRibbonTabsBehavior(); + } +} diff --git a/src/CosmosDbExplorer/Infrastructure/ListBoxMore.cs b/src/CosmosDbExplorer/Controls/ListBoxMore.cs similarity index 76% rename from src/CosmosDbExplorer/Infrastructure/ListBoxMore.cs rename to src/CosmosDbExplorer/Controls/ListBoxMore.cs index d5611b3..18b58c6 100644 --- a/src/CosmosDbExplorer/Infrastructure/ListBoxMore.cs +++ b/src/CosmosDbExplorer/Controls/ListBoxMore.cs @@ -1,13 +1,13 @@ using System.Windows; using System.Windows.Controls; -namespace CosmosDbExplorer.Infrastructure +namespace CosmosDbExplorer.Controls { - [TemplatePart(Name=PartMoreButtonName, Type = typeof(Button))] + [TemplatePart(Name = PartMoreButtonName, Type = typeof(Button))] public class ListBoxMore : ListBox { private const string PartMoreButtonName = "PART_MoreButton"; - private Button _moreButton; + private Button? _moreButton; public override void OnApplyTemplate() { diff --git a/src/CosmosDbExplorer/Controls/TreeViewEx.cs b/src/CosmosDbExplorer/Controls/TreeViewEx.cs new file mode 100644 index 0000000..1b873e8 --- /dev/null +++ b/src/CosmosDbExplorer/Controls/TreeViewEx.cs @@ -0,0 +1,57 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Extensions; + +namespace CosmosDbExplorer.Controls +{ + public class TreeViewEx : TreeView + { + protected override DependencyObject GetContainerForItemOverride() + { + return new TreeViewItemEx(); + } + } + + public class TreeViewItemEx : TreeViewItem + { + protected override DependencyObject GetContainerForItemOverride() + { + return new TreeViewItemEx(); + } + + protected override void OnMouseDoubleClick(MouseButtonEventArgs e) + { + var item = (e.OriginalSource as UIElement)?.GetAncestorOrSelf(); + + if (item != this) + { + return; + } + + e.Handled = ExecuteCommand(); + base.OnMouseDoubleClick(e); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + e.Handled = e.Key == Key.Enter && ExecuteCommand(); + base.OnKeyDown(e); + } + + private bool ExecuteCommand() + { + if (DataContext is IHaveOpenCommand vm) + { + if (vm.OpenCommand != null && vm.OpenCommand.CanExecute(null)) + { + vm.OpenCommand.Execute(null); + return true; + } + } + + return false; + } + } +} diff --git a/src/CosmosDbExplorer/Infrastructure/Converters/ActiveDocumentConverter.cs b/src/CosmosDbExplorer/Converters/ActiveDocumentConverter.cs similarity index 72% rename from src/CosmosDbExplorer/Infrastructure/Converters/ActiveDocumentConverter.cs rename to src/CosmosDbExplorer/Converters/ActiveDocumentConverter.cs index 9f22802..bc06bad 100644 --- a/src/CosmosDbExplorer/Infrastructure/Converters/ActiveDocumentConverter.cs +++ b/src/CosmosDbExplorer/Converters/ActiveDocumentConverter.cs @@ -1,12 +1,13 @@ using System; +using System.Globalization; using System.Windows.Data; -using CosmosDbExplorer.Infrastructure.Models; +using CosmosDbExplorer.ViewModels; -namespace CosmosDbExplorer.Infrastructure.Converters +namespace CosmosDbExplorer.Converters { public class ActiveDocumentConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is ToolViewModel) { @@ -16,7 +17,7 @@ public object Convert(object value, Type targetType, object parameter, System.Gl return value; } - public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { if (value is ToolViewModel) { diff --git a/src/CosmosDbExplorer/Converters/AvalonThemeConverter.cs b/src/CosmosDbExplorer/Converters/AvalonThemeConverter.cs new file mode 100644 index 0000000..232cfcf --- /dev/null +++ b/src/CosmosDbExplorer/Converters/AvalonThemeConverter.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; +using ControlzEx.Theming; +using CosmosDbExplorer.Helpers; + +namespace CosmosDbExplorer.Converters +{ + // TODO: Temporary solution. Must find a way to integrate AvalonDock with MahaApps Theme manager + public class AvalonThemeConverter : IValueConverter + { + private object? _darkTheme; + private object? _lightTheme; + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var themeName = value as string; + + return themeName switch + { + "Dark" => GetDarkTheme(), + "Light" => GetLightTheme(), + _ => WindowsThemeHelper.AppsUseLightTheme() ? GetLightTheme() : GetDarkTheme() + }; + } + + private object GetDarkTheme() => _darkTheme ??= new AvalonDock.Themes.Vs2013DarkTheme(); + + private object GetLightTheme() => _lightTheme ??= new AvalonDock.Themes.GenericTheme(); + + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/CosmosDbExplorer/Infrastructure/Converters/BoolToVisibilityConverter.cs b/src/CosmosDbExplorer/Converters/BoolToVisibilityConverter.cs similarity index 93% rename from src/CosmosDbExplorer/Infrastructure/Converters/BoolToVisibilityConverter.cs rename to src/CosmosDbExplorer/Converters/BoolToVisibilityConverter.cs index 2bcaaf3..e1896d2 100644 --- a/src/CosmosDbExplorer/Infrastructure/Converters/BoolToVisibilityConverter.cs +++ b/src/CosmosDbExplorer/Converters/BoolToVisibilityConverter.cs @@ -3,7 +3,7 @@ using System.Windows; using System.Windows.Data; -namespace CosmosDbExplorer.Infrastructure.Converters +namespace CosmosDbExplorer.Converters { public class BoolToVisibilityConverter : IValueConverter { diff --git a/src/CosmosDbExplorer/Infrastructure/Converters/BytesToGigaBytesConverter.cs b/src/CosmosDbExplorer/Converters/BytesToGigaBytesConverter.cs similarity index 90% rename from src/CosmosDbExplorer/Infrastructure/Converters/BytesToGigaBytesConverter.cs rename to src/CosmosDbExplorer/Converters/BytesToGigaBytesConverter.cs index 76cb4fa..52b550d 100644 --- a/src/CosmosDbExplorer/Infrastructure/Converters/BytesToGigaBytesConverter.cs +++ b/src/CosmosDbExplorer/Converters/BytesToGigaBytesConverter.cs @@ -2,7 +2,7 @@ using System.Globalization; using System.Windows.Data; -namespace CosmosDbExplorer.Infrastructure.Converters +namespace CosmosDbExplorer.Converters { public class BytesToGigaBytesConverter : IValueConverter { diff --git a/src/CosmosDbExplorer/Infrastructure/Converters/ColorToBrushConverter.cs b/src/CosmosDbExplorer/Converters/ColorToBrushConverter.cs similarity index 61% rename from src/CosmosDbExplorer/Infrastructure/Converters/ColorToBrushConverter.cs rename to src/CosmosDbExplorer/Converters/ColorToBrushConverter.cs index e726680..bae9093 100644 --- a/src/CosmosDbExplorer/Infrastructure/Converters/ColorToBrushConverter.cs +++ b/src/CosmosDbExplorer/Converters/ColorToBrushConverter.cs @@ -2,25 +2,26 @@ using System.Windows.Data; using System.Windows.Media; -namespace CosmosDbExplorer.Infrastructure.Converters +namespace CosmosDbExplorer.Converters { public class ColorToBrushConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + public object? Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (value == null) { return Binding.DoNothing; } - if (!(value is Color)) + if (value is not System.Drawing.Color) { throw new InvalidOperationException("Value must be a Color"); } - var color = value; + var drawingColor = (System.Drawing.Color)value; - return !color.Equals(Colors.Transparent) ? new SolidColorBrush((Color)value) : null; + var color = Color.FromArgb(drawingColor.A, drawingColor.R, drawingColor.G, drawingColor.B); + return !color.Equals(Colors.Transparent) ? new SolidColorBrush(color) : null; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) diff --git a/src/CosmosDbExplorer/Converters/EnumToBooleanConverter.cs b/src/CosmosDbExplorer/Converters/EnumToBooleanConverter.cs new file mode 100644 index 0000000..81dc060 --- /dev/null +++ b/src/CosmosDbExplorer/Converters/EnumToBooleanConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace CosmosDbExplorer.Converters +{ + public class EnumToBooleanConverter : IValueConverter + { + public Type? EnumType { get; set; } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (parameter is string enumString) + { + if (Enum.IsDefined(EnumType, value)) + { + var enumValue = Enum.Parse(EnumType, enumString); + + return enumValue.Equals(value); + } + } + + return false; + } + + public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (parameter is string enumString) + { + return Enum.Parse(EnumType, enumString); + } + + return null; + } + } +} diff --git a/src/CosmosDbExplorer/Converters/FontToSupportedGliphConverter.cs b/src/CosmosDbExplorer/Converters/FontToSupportedGliphConverter.cs new file mode 100644 index 0000000..63a733a --- /dev/null +++ b/src/CosmosDbExplorer/Converters/FontToSupportedGliphConverter.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; +using System.Windows.Media; + +namespace CosmosDbExplorer.Converters +{ + public class FontToSupportedGliphConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is not IReadOnlyCollection list) + { + return DependencyProperty.UnsetValue; + } + + var returnList = new List(); + foreach (var font in list) + { + //Instantiate a TypeFace object with the font settings you want to use + var ltypFace = new Typeface(font, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal); + + //Try to create a GlyphTypeface object from the TypeFace object + if (ltypFace.TryGetGlyphTypeface(out _)) + { + returnList.Add(font); + } + } + + return returnList; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/CosmosDbExplorer/Converters/InvertBoolConverter.cs b/src/CosmosDbExplorer/Converters/InvertBoolConverter.cs new file mode 100644 index 0000000..2111d51 --- /dev/null +++ b/src/CosmosDbExplorer/Converters/InvertBoolConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace CosmosDbExplorer.Converters +{ + public class InvertBoolConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value != null && value is bool boolean ? !boolean : (object)true; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return Convert(value, targetType, parameter, culture); + } + } +} diff --git a/src/CosmosDbExplorer/Infrastructure/Converters/IsNotNullToBooleanConverter.cs b/src/CosmosDbExplorer/Converters/IsNotNullToBooleanConverter.cs similarity index 90% rename from src/CosmosDbExplorer/Infrastructure/Converters/IsNotNullToBooleanConverter.cs rename to src/CosmosDbExplorer/Converters/IsNotNullToBooleanConverter.cs index 78fc969..669fd50 100644 --- a/src/CosmosDbExplorer/Infrastructure/Converters/IsNotNullToBooleanConverter.cs +++ b/src/CosmosDbExplorer/Converters/IsNotNullToBooleanConverter.cs @@ -2,7 +2,7 @@ using System.Globalization; using System.Windows.Data; -namespace CosmosDbExplorer.Infrastructure.Converters +namespace CosmosDbExplorer.Converters { public class IsNotNullToBooleanConverter : IValueConverter { diff --git a/src/CosmosDbExplorer/Infrastructure/Converters/LogConverter.cs b/src/CosmosDbExplorer/Converters/LogConverter.cs similarity index 88% rename from src/CosmosDbExplorer/Infrastructure/Converters/LogConverter.cs rename to src/CosmosDbExplorer/Converters/LogConverter.cs index 2e2c011..53682f7 100644 --- a/src/CosmosDbExplorer/Infrastructure/Converters/LogConverter.cs +++ b/src/CosmosDbExplorer/Converters/LogConverter.cs @@ -2,7 +2,7 @@ using System.Globalization; using System.Windows.Data; -namespace CosmosDbExplorer.Infrastructure.Converters +namespace CosmosDbExplorer.Converters { [ValueConversion(typeof(double), typeof(double))] public class LogConverter : IValueConverter @@ -37,12 +37,12 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu private double GetA(double x, double y, double z) { - return ((x * z) - Math.Pow(y, 2)) / (x - (2 * y) + z); + return (x * z - Math.Pow(y, 2)) / (x - 2 * y + z); } private double GetB(double x, double y, double z) { - return Math.Pow(y - x, 2) / (x - (2 * y) + z); + return Math.Pow(y - x, 2) / (x - 2 * y + z); } private double GetC(double x, double y, double z) diff --git a/src/CosmosDbExplorer/Infrastructure/Converters/NullToEnabledConverter.cs b/src/CosmosDbExplorer/Converters/NullToEnabledConverter.cs similarity index 89% rename from src/CosmosDbExplorer/Infrastructure/Converters/NullToEnabledConverter.cs rename to src/CosmosDbExplorer/Converters/NullToEnabledConverter.cs index 69cdfdf..59b6b53 100644 --- a/src/CosmosDbExplorer/Infrastructure/Converters/NullToEnabledConverter.cs +++ b/src/CosmosDbExplorer/Converters/NullToEnabledConverter.cs @@ -2,7 +2,7 @@ using System.Globalization; using System.Windows.Data; -namespace CosmosDbExplorer.Infrastructure.Converters +namespace CosmosDbExplorer.Converters { public class NullToEnabledConverter : IValueConverter { diff --git a/src/CosmosDbExplorer/Infrastructure/Converters/NullToVisibilityConverter.cs b/src/CosmosDbExplorer/Converters/NullToVisibilityConverter.cs similarity index 91% rename from src/CosmosDbExplorer/Infrastructure/Converters/NullToVisibilityConverter.cs rename to src/CosmosDbExplorer/Converters/NullToVisibilityConverter.cs index 9061a97..42dcd57 100644 --- a/src/CosmosDbExplorer/Infrastructure/Converters/NullToVisibilityConverter.cs +++ b/src/CosmosDbExplorer/Converters/NullToVisibilityConverter.cs @@ -3,7 +3,7 @@ using System.Windows; using System.Windows.Data; -namespace CosmosDbExplorer.Infrastructure.Converters +namespace CosmosDbExplorer.Converters { public class NullToVisibilityConverter : IValueConverter { diff --git a/src/CosmosDbExplorer/Converters/StringToFontFamillyConverter.cs b/src/CosmosDbExplorer/Converters/StringToFontFamillyConverter.cs new file mode 100644 index 0000000..042fb54 --- /dev/null +++ b/src/CosmosDbExplorer/Converters/StringToFontFamillyConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; +using System.Windows.Media; + +namespace CosmosDbExplorer.Converters +{ + public class StringToFontFamillyConverter : IValueConverter + { + public object? Convert(object? value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + { + return value; + } + + var con = new FontFamilyConverter(); + var ff = con.ConvertFromString((string)value); + return ff; + } + + public object? ConvertBack(object? value, Type targetType, object parameter, CultureInfo culture) + { + return value?.ToString(); + } + } +} diff --git a/src/CosmosDbExplorer/CosmosDbExplorer.csproj b/src/CosmosDbExplorer/CosmosDbExplorer.csproj index 25ee23d..4360827 100644 --- a/src/CosmosDbExplorer/CosmosDbExplorer.csproj +++ b/src/CosmosDbExplorer/CosmosDbExplorer.csproj @@ -1,502 +1,88 @@ - - - + - Debug - AnyCPU - {5AF0B2F1-B905-46CF-B493-71B1950A96A4} WinExe + net6.0-windows CosmosDbExplorer - CosmosDbExplorer - v4.7.2 - 512 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 4 - true - - - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - AnyCPU - pdbonly - false - bin\Release\ - TRACE - prompt - 4 - false - false - - + true + app.manifest + astronaut.png astronaut.ico - - - 9F62A3926A5B227285B516DBEC4075DCACB56EC9 - - - DocumentDbExplorer_TemporaryKey.pfx - - - false - - - false - - - LocalIntranet - - - Properties\app.manifest + Cosmos DB Explorer + enable - - - - - - - - - - - - - - 4.0 - - - - + + + - - MSBuild:Compile - Designer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - AboutView.xaml - - - AccountSettingsView.xaml - - - AddCollectionView.xaml - - - CollectionMetricsTabView.xaml - - - DatabaseView.xaml - - - DocumentsTabView.xaml - - - ImportDocumentView.xaml - - - JsonEditorView.xaml - - - - PartitionMetricChartTooltip.xaml - - - PermissionEditView.xaml - - - QueryEditorView.xaml - - - ScaleAndSettingsTabView.xaml - - - StoredProcedureTabView.xaml - - - TriggerTabView.xaml - - - UserDefFuncTabView.xaml - - - UserEditView.xaml - - - MSBuild:Compile - Designer - - - App.xaml - Code - - - - MainWindow.xaml - Code - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - + - - Code - - - True + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + True + True Resources.resx - + + True True Settings.settings - True - - ResXFileCodeGenerator + + + + + PublicResXFileCodeGenerator Resources.Designer.cs - - - - - + + + + + Always + + + True + \ + + SettingsSingleFileGenerator Settings.Designer.cs + - + + $(DefaultXamlRuntime) Designer - - - - - - - - - - - - - - False - Microsoft .NET Framework 4.7 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - - - - - - 1.7.0 - - - 5.0.4 - - - 2.0.6 - - - 1.0.2 - - - 3.8.2 - - - 6.1.0.326 - - - 8.6.3 - - - 6.6.0 - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - 2.4.3 - - - 0.9.7 - - - 2.16.2 - - - 5.4.1.1 - - - 12.0.3 - - - 3.4.0 - - - 5.0.0 - - - 4.5.4 - - - 5.0.0 - - - 4.5.0 - - - 5.0.0 - - - 5.0.0 - - - 5.0.0 - - - 5.0.0 - - - 5.0.0 - - - 4.3.1 - - - 4.3.1 - - - 4.3.2 - - - 4.5.0 - - - 1.9.0 - - - 2.0.2 - + - - \ No newline at end of file + diff --git a/src/CosmosDbExplorer/DialogResult.cs b/src/CosmosDbExplorer/DialogResult.cs deleted file mode 100644 index 92ad9ae..0000000 --- a/src/CosmosDbExplorer/DialogResult.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace CosmosDbExplorer -{ - internal class DialogResult - { - } -} \ No newline at end of file diff --git a/src/CosmosDbExplorer/Extensions/ListExtensions.cs b/src/CosmosDbExplorer/Extensions/ListExtensions.cs new file mode 100644 index 0000000..be90565 --- /dev/null +++ b/src/CosmosDbExplorer/Extensions/ListExtensions.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace CosmosDbExplorer.Extensions; + +public static class ListExtensions +{ + public static int Replace(this IList source, T oldValue, T newValue) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + var index = source.IndexOf(oldValue); + if (index != -1) + { + source[index] = newValue; + } + + return index; + } + + public static void ReplaceAll(this IList source, T oldValue, T newValue) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + int index; + do + { + index = source.IndexOf(oldValue); + if (index != -1) + { + source[index] = newValue; + } + } while (index != -1); + } + + public static IEnumerable Replace(this IEnumerable source, T oldValue, T newValue) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return source.Select(x => EqualityComparer.Default.Equals(x, oldValue) ? newValue : x); + } + + public static IList Move(this IList list, int oldIndex, int newIndex) + { + // exit if positions are equal or outside array + if (oldIndex == newIndex || 0 > oldIndex || oldIndex >= list.Count || 0 > newIndex || newIndex >= list.Count) + { + return list; + } + + var tmp = list[oldIndex]; + + // local variables + int i; + // move element down and shift other elements up + if (oldIndex < newIndex) + { + for (i = oldIndex; i < newIndex; i++) + { + list[i] = list[i + 1]; + } + } + // move element up and shift other elements down + else + { + for (i = oldIndex; i > newIndex; i--) + { + list[i] = list[i - 1]; + } + } + // put element from position 1 to destination + list[newIndex] = tmp; + + return list; + } +} diff --git a/src/CosmosDbExplorer/Infrastructure/Extensions/NameValueCollectionExtensions.cs b/src/CosmosDbExplorer/Extensions/NameValueCollectionExtensions.cs similarity index 86% rename from src/CosmosDbExplorer/Infrastructure/Extensions/NameValueCollectionExtensions.cs rename to src/CosmosDbExplorer/Extensions/NameValueCollectionExtensions.cs index 7df6a94..5112b92 100644 --- a/src/CosmosDbExplorer/Infrastructure/Extensions/NameValueCollectionExtensions.cs +++ b/src/CosmosDbExplorer/Extensions/NameValueCollectionExtensions.cs @@ -2,7 +2,7 @@ using System.Collections.Specialized; using System.Linq; -namespace CosmosDbExplorer.Infrastructure.Extensions +namespace CosmosDbExplorer.Extensions { public static class NameValueCollectionExtensions { diff --git a/src/CosmosDbExplorer/Extensions/ObservableCollectionExtensions.cs b/src/CosmosDbExplorer/Extensions/ObservableCollectionExtensions.cs new file mode 100644 index 0000000..3ff29b8 --- /dev/null +++ b/src/CosmosDbExplorer/Extensions/ObservableCollectionExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace CosmosDbExplorer.Extensions; + +public static class ObservableCollectionExtensions +{ + public static void AddRangeSorted(this ObservableCollection collection, IEnumerable toAdd, Func sortSelector) + { + var sortArr = Enumerable.Concat(collection, toAdd).OrderBy(sortSelector).ToList(); + foreach (var obj in toAdd.OrderBy(o => sortArr.IndexOf(o)).ToList()) + { + collection.Insert(sortArr.IndexOf(obj), obj); + } + } + + public static void AddSorted(this ObservableCollection collection, T toAdd, Func sortSelector) + { + AddRangeSorted(collection, new[] { toAdd }, sortSelector); + } +} diff --git a/src/CosmosDbExplorer/Extensions/StringHelper.cs b/src/CosmosDbExplorer/Extensions/StringHelper.cs new file mode 100644 index 0000000..95b87f9 --- /dev/null +++ b/src/CosmosDbExplorer/Extensions/StringHelper.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace CosmosDbExplorer.Extensions +{ + public static class StringExtensions + { + public static string SafeForFilename(this string input) + { + return Regex.Replace(input, "[" + string.Join("", Path.GetInvalidFileNameChars().Select(p => p.ToString())) + "]", "_"); + } + } +} diff --git a/src/CosmosDbExplorer/Extensions/UIElementExtensions.cs b/src/CosmosDbExplorer/Extensions/UIElementExtensions.cs new file mode 100644 index 0000000..012b102 --- /dev/null +++ b/src/CosmosDbExplorer/Extensions/UIElementExtensions.cs @@ -0,0 +1,21 @@ +using System.Windows; +using System.Windows.Media; + +namespace CosmosDbExplorer.Extensions +{ + public static class UIElementExtensions + { + public static TAncestor? GetAncestorOrSelf(this UIElement element) + where TAncestor : UIElement + { + var uiElement = element; + + while (uiElement != null && uiElement is not TAncestor) + { + uiElement = VisualTreeHelper.GetParent(uiElement) as UIElement; + } + + return uiElement as TAncestor; + } + } +} diff --git a/src/CosmosDbExplorer/FodyWeavers.xml b/src/CosmosDbExplorer/FodyWeavers.xml index 1a48b73..0a2f27b 100644 --- a/src/CosmosDbExplorer/FodyWeavers.xml +++ b/src/CosmosDbExplorer/FodyWeavers.xml @@ -1,5 +1,4 @@ - - + \ No newline at end of file diff --git a/src/CosmosDbExplorer/Infrastructure/JsonHelpers/DocumentDbWithoutSystemPropertyResolver.cs b/src/CosmosDbExplorer/Helpers/DocumentDbWithoutSystemPropertyResolver.cs similarity index 94% rename from src/CosmosDbExplorer/Infrastructure/JsonHelpers/DocumentDbWithoutSystemPropertyResolver.cs rename to src/CosmosDbExplorer/Helpers/DocumentDbWithoutSystemPropertyResolver.cs index 06c1da4..89f75d0 100644 --- a/src/CosmosDbExplorer/Infrastructure/JsonHelpers/DocumentDbWithoutSystemPropertyResolver.cs +++ b/src/CosmosDbExplorer/Helpers/DocumentDbWithoutSystemPropertyResolver.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -namespace CosmosDbExplorer.Infrastructure.JsonHelpers +namespace CosmosDbExplorer.Helpers { public class DocumentDbWithoutSystemPropertyResolver : DefaultContractResolver { diff --git a/src/CosmosDbExplorer/Helpers/FrameExtensions.cs b/src/CosmosDbExplorer/Helpers/FrameExtensions.cs new file mode 100644 index 0000000..1f907bf --- /dev/null +++ b/src/CosmosDbExplorer/Helpers/FrameExtensions.cs @@ -0,0 +1,23 @@ +namespace System.Windows.Controls +{ + public static class FrameExtensions + { + public static object? GetDataContext(this Frame frame) + { + if (frame.Content is FrameworkElement element) + { + return element.DataContext; + } + + return null; + } + + public static void CleanNavigation(this Frame frame) + { + while (frame.CanGoBack) + { + frame.RemoveBackEntry(); + } + } + } +} diff --git a/src/CosmosDbExplorer/Infrastructure/JsonHelpers/OrderedDictionaryConverter.cs b/src/CosmosDbExplorer/Helpers/OrderedDictionaryConverter.cs similarity index 91% rename from src/CosmosDbExplorer/Infrastructure/JsonHelpers/OrderedDictionaryConverter.cs rename to src/CosmosDbExplorer/Helpers/OrderedDictionaryConverter.cs index 0ef1bd7..1039d82 100644 --- a/src/CosmosDbExplorer/Infrastructure/JsonHelpers/OrderedDictionaryConverter.cs +++ b/src/CosmosDbExplorer/Helpers/OrderedDictionaryConverter.cs @@ -3,9 +3,8 @@ using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Newtonsoft.Json.Serialization; -namespace CosmosDbExplorer.Infrastructure.JsonHelpers +namespace CosmosDbExplorer.Helpers { public class OrderedDictionaryConverter : JsonConverter { diff --git a/src/CosmosDbExplorer/Helpers/UIHelper.cs b/src/CosmosDbExplorer/Helpers/UIHelper.cs new file mode 100644 index 0000000..a3da20e --- /dev/null +++ b/src/CosmosDbExplorer/Helpers/UIHelper.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media; + +namespace CosmosDbExplorer.Helpers +{ + public static class UIHelper + { + /// + /// Finds a parent of a given item on the visual tree. + /// + /// The type of the queried item. + /// A direct or indirect child of the queried item. + /// The first parent item that matches the submitted type parameter. + /// If not matching item can be found, a null reference is being returned. + public static T? FindVisualParent(DependencyObject child) + where T : DependencyObject + { + // get parent item + var parentObject = VisualTreeHelper.GetParent(child); + + // we’ve reached the end of the tree + if (parentObject == null) + { + return null; + } + + // check if the parent matches the type we’re looking for + if (parentObject is T parent) + { + return parent; + } + else + { + // use recursion to proceed with next level + return FindVisualParent(parentObject); + } + } + } +} diff --git a/src/CosmosDbExplorer/Helpers/WindowExtensions.cs b/src/CosmosDbExplorer/Helpers/WindowExtensions.cs new file mode 100644 index 0000000..6055278 --- /dev/null +++ b/src/CosmosDbExplorer/Helpers/WindowExtensions.cs @@ -0,0 +1,17 @@ +using System.Windows.Controls; + +namespace System.Windows +{ + public static class WindowExtensions + { + public static object? GetDataContext(this Window window) + { + if (window.Content is Frame frame) + { + return frame.GetDataContext(); + } + + return null; + } + } +} diff --git a/src/CosmosDbExplorer/Images/loading.gif b/src/CosmosDbExplorer/Images/loading.gif deleted file mode 100644 index 5cf9cc4..0000000 Binary files a/src/CosmosDbExplorer/Images/loading.gif and /dev/null differ diff --git a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/AvalonHasChangeBehavior.cs b/src/CosmosDbExplorer/Infrastructure/AvalonEdit/AvalonHasChangeBehavior.cs deleted file mode 100644 index f29bb82..0000000 --- a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/AvalonHasChangeBehavior.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Interactivity; -using ICSharpCode.AvalonEdit; - -namespace DocumentDbExplorer.Infrastructure.AvalonEdit -{ - public sealed class AvalonHasChangeBehavior : Behavior - { - private static readonly DependencyProperty HasChangeProperty = - DependencyProperty.Register("HasChange", typeof(bool), typeof(AvalonHasChangeBehavior)); - - public bool HasChange - { - get { return (bool)GetValue(HasChangeProperty); } - set { SetValue(HasChangeProperty, value); } - } - - protected override void OnAttached() - { - base.OnAttached(); - if (AssociatedObject != null) - { - AssociatedObject.TextChanged += AssociatedObjectOnTextChanged; - } - } - - protected override void OnDetaching() - { - base.OnDetaching(); - if (AssociatedObject != null) - { - AssociatedObject.TextChanged -= AssociatedObjectOnTextChanged; - } - } - - private void AssociatedObjectOnTextChanged(object sender, EventArgs eventArgs) - { - if (sender is TextEditor textEditor) - { - if (textEditor.Document != null) - { - HasChange = textEditor.Document.UndoStack.CanUndo; - } - } - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/AvalonTextBehavior.cs b/src/CosmosDbExplorer/Infrastructure/AvalonEdit/AvalonTextBehavior.cs deleted file mode 100644 index 33e70a9..0000000 --- a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/AvalonTextBehavior.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Interactivity; -using ICSharpCode.AvalonEdit; - -namespace DocumentDbExplorer.Infrastructure.AvalonEdit -{ - public sealed class AvalonEditBehaviour : Behavior - { - public static readonly DependencyProperty GiveMeTheTextProperty = - DependencyProperty.Register("GiveMeTheText", typeof(string), typeof(AvalonEditBehaviour), - new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, PropertyChangedCallback)); - - public string GiveMeTheText - { - get { return (string)GetValue(GiveMeTheTextProperty); } - set { SetValue(GiveMeTheTextProperty, value); } - } - - protected override void OnAttached() - { - base.OnAttached(); - if (AssociatedObject != null) - { - AssociatedObject.TextChanged += AssociatedObjectOnTextChanged; - } - } - - protected override void OnDetaching() - { - base.OnDetaching(); - if (AssociatedObject != null) - { - AssociatedObject.TextChanged -= AssociatedObjectOnTextChanged; - } - } - - private void AssociatedObjectOnTextChanged(object sender, EventArgs eventArgs) - { - var textEditor = sender as TextEditor; - if (textEditor != null) - { - if (textEditor.Document != null) - { - GiveMeTheText = textEditor.Document.Text; - } - } - } - - private static void PropertyChangedCallback( - DependencyObject dependencyObject, - DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) - { - var behavior = dependencyObject as AvalonEditBehaviour; - if (behavior.AssociatedObject != null) - { - var editor = behavior.AssociatedObject as TextEditor; - if (editor.Document != null) - { - var caretOffset = editor.CaretOffset; - editor.Document.Text = dependencyPropertyChangedEventArgs.NewValue as string ?? string.Empty; - editor.CaretOffset = caretOffset <= editor.Document.Text.Length ? caretOffset : 0; - } - } - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/CodeEditor.cs b/src/CosmosDbExplorer/Infrastructure/AvalonEdit/CodeEditor.cs deleted file mode 100644 index 2d2b884..0000000 --- a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/CodeEditor.cs +++ /dev/null @@ -1,241 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Media; -using ICSharpCode.AvalonEdit; -using ICSharpCode.AvalonEdit.Document; - -namespace DocumentDbExplorer.Infrastructure.AvalonEdit -{ - /// - /// Class that inherits from the AvalonEdit TextEditor control to - /// enable MVVM interaction. - /// - public class CodeEditor : TextEditor, INotifyPropertyChanged - { - // Vars. - private static bool _canScroll = true; - - /// - /// Default constructor to set up event handlers. - /// - public CodeEditor() - { - // Default options. - FontSize = 12; - FontFamily = new FontFamily("Consolas"); - Options = new TextEditorOptions - { - IndentationSize = 3, - ConvertTabsToSpaces = true - }; - } - - #region Text. - /// - /// Dependancy property for the editor text property binding. - /// - public static readonly DependencyProperty TextProperty = - DependencyProperty.Register("Text", typeof(string), typeof(CodeEditor), - new PropertyMetadata((obj, args) => - { - CodeEditor target = (CodeEditor)obj; - target.Text = (string)args.NewValue; - })); - - /// - /// Provide access to the Text. - /// - public new string Text - { - get { return base.Text; } - set { base.Text = value; } - } - - /// - /// Return the current text length. - /// - public int Length - { - get { return base.Text.Length; } - } - - /// - /// Override of OnTextChanged event. - /// - protected override void OnTextChanged(EventArgs e) - { - RaisePropertyChanged("Length"); - base.OnTextChanged(e); - } - - /// - /// Event handler to update properties based upon the selection changed event. - /// - void TextArea_SelectionChanged(object sender, EventArgs e) - { - SelectionStart = SelectionStart; - SelectionLength = SelectionLength; - } - - /// - /// Event that handles when the caret changes. - /// - void TextArea_CaretPositionChanged(object sender, EventArgs e) - { - try - { - _canScroll = false; - this.TextLocation = TextLocation; - } - finally - { - _canScroll = true; - } - } - #endregion // Text. - - #region Caret Offset. - /// - /// DependencyProperty for the TextEditorCaretOffset binding. - /// - public static DependencyProperty CaretOffsetProperty = - DependencyProperty.Register("CaretOffset", typeof(int), typeof(CodeEditor), - new PropertyMetadata((obj, args) => - { - var target = (CodeEditor)obj; - if (target.CaretOffset != (int)args.NewValue) - { - target.CaretOffset = (int)args.NewValue; - } - })); - - /// - /// Access to the SelectionStart property. - /// - public new int CaretOffset - { - get { return base.CaretOffset; } - set { SetValue(CaretOffsetProperty, value); } - } - #endregion // Caret Offset. - - #region Selection. - /// - /// DependencyProperty for the TextLocation. Setting this value - /// will scroll the TextEditor to the desired TextLocation. - /// - public static readonly DependencyProperty TextLocationProperty = - DependencyProperty.Register("TextLocation", typeof(TextLocation), typeof(CodeEditor), - new PropertyMetadata((obj, args) => - { - var target = (CodeEditor)obj; - var loc = (TextLocation)args.NewValue; - if (_canScroll) - { - target.ScrollTo(loc.Line, loc.Column); - } - })); - - /// - /// Get or set the TextLocation. Setting will scroll to that location. - /// - public TextLocation TextLocation - { - get { return base.Document.GetLocation(SelectionStart); } - set { SetValue(TextLocationProperty, value); } - } - - /// - /// DependencyProperty for the TextEditor SelectionLength property. - /// - public static readonly DependencyProperty SelectionLengthProperty = - DependencyProperty.Register("SelectionLength", typeof(int), typeof(CodeEditor), - new PropertyMetadata((obj, args) => - { - var target = (CodeEditor)obj; - if (target.SelectionLength != (int)args.NewValue) - { - target.SelectionLength = (int)args.NewValue; - target.Select(target.SelectionStart, (int)args.NewValue); - } - })); - - /// - /// Access to the SelectionLength property. - /// - public new int SelectionLength - { - get { return base.SelectionLength; } - set { SetValue(SelectionLengthProperty, value); } - } - - /// - /// DependencyProperty for the TextEditor SelectionStart property. - /// - public static readonly DependencyProperty SelectionStartProperty = - DependencyProperty.Register("SelectionStart", typeof(int), typeof(CodeEditor), - new PropertyMetadata((obj, args) => - { - var target = (CodeEditor)obj; - if (target.SelectionStart != (int)args.NewValue) - { - target.SelectionStart = (int)args.NewValue; - target.Select((int)args.NewValue, target.SelectionLength); - } - })); - - /// - /// Access to the SelectionStart property. - /// - public new int SelectionStart - { - get { return base.SelectionStart; } - set { SetValue(SelectionStartProperty, value); } - } - #endregion // Selection. - - #region Properties. - /// - /// The currently loaded file name. This is bound to the ViewModel - /// consuming the editor control. - /// - public string FilePath - { - get { return (string)GetValue(FilePathProperty); } - set { SetValue(FilePathProperty, value); } - } - - // Using a DependencyProperty as the backing store for FilePath. - // This enables animation, styling, binding, etc... - public static readonly DependencyProperty FilePathProperty = - DependencyProperty.Register("FilePath", typeof(string), typeof(CodeEditor), - new PropertyMetadata(string.Empty, OnFilePathChanged)); - - private static void OnFilePathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - - } - #endregion // Properties. - - #region Raise Property Changed. - /// - /// Implement the INotifyPropertyChanged event handler. - /// - public event PropertyChangedEventHandler PropertyChanged; - public void RaisePropertyChanged([CallerMemberName] string caller = null) - { - var handler = PropertyChanged; - if (handler != null) - { - PropertyChanged(this, new PropertyChangedEventArgs(caller)); - } - } - #endregion // Raise Property Changed. - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/JSON.xshd b/src/CosmosDbExplorer/Infrastructure/AvalonEdit/JSON.xshd deleted file mode 100644 index ce8949e..0000000 --- a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/JSON.xshd +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - true - false - - - - " - "(?=:) - - - - (?:(:\s*))" - [^\\]" - - - \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? - - \ No newline at end of file diff --git a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/SearchReplacePanel.cs b/src/CosmosDbExplorer/Infrastructure/AvalonEdit/SearchReplacePanel.cs deleted file mode 100644 index 8379377..0000000 --- a/src/CosmosDbExplorer/Infrastructure/AvalonEdit/SearchReplacePanel.cs +++ /dev/null @@ -1,848 +0,0 @@ -using System; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Controls.Primitives; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Threading; -using ICSharpCode.AvalonEdit.Document; -using ICSharpCode.AvalonEdit.Editing; -using ICSharpCode.AvalonEdit.Rendering; -using ICSharpCode.AvalonEdit.Search; -using ICSharpCode.AvalonEdit; -using Localization = ICSharpCode.AvalonEdit.Search.Localization; - -namespace RoslynPad.Editor -{ - public class SearchReplacePanel : Control - { - private TextArea _textArea; - private SearchReplaceInputHandler _handler; - private TextDocument _currentDocument; - private SearchReplaceResultBackgroundRenderer _renderer; - private TextBox _searchTextBox; - private SearchReplacePanelAdorner _adorner; - private ISearchStrategy _strategy; - - private readonly ToolTip _messageView = new ToolTip { Placement = PlacementMode.Bottom, StaysOpen = true, Focusable = false }; - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty UseRegexProperty = - DependencyProperty.Register("UseRegex", typeof(bool), typeof(SearchReplacePanel), - new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); - - /// - /// Gets/sets whether the search pattern should be interpreted as regular expression. - /// - public bool UseRegex - { - get { return (bool)GetValue(UseRegexProperty); } - set { SetValue(UseRegexProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty MatchCaseProperty = - DependencyProperty.Register("MatchCase", typeof(bool), typeof(SearchReplacePanel), - new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); - - /// - /// Gets/sets whether the search pattern should be interpreted case-sensitive. - /// - public bool MatchCase - { - get { return (bool)GetValue(MatchCaseProperty); } - set { SetValue(MatchCaseProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty WholeWordsProperty = - DependencyProperty.Register("WholeWords", typeof(bool), typeof(SearchReplacePanel), - new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); - - /// - /// Gets/sets whether the search pattern should only match whole words. - /// - public bool WholeWords - { - get { return (bool)GetValue(WholeWordsProperty); } - set { SetValue(WholeWordsProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty SearchPatternProperty = - DependencyProperty.Register("SearchPattern", typeof(string), typeof(SearchReplacePanel), - new FrameworkPropertyMetadata("", SearchPatternChangedCallback)); - - /// - /// Gets/sets the search pattern. - /// - public string SearchPattern - { - get { return (string)GetValue(SearchPatternProperty); } - set { SetValue(SearchPatternProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty MarkerBrushProperty = - DependencyProperty.Register("MarkerBrush", typeof(Brush), typeof(SearchReplacePanel), - new FrameworkPropertyMetadata(Brushes.LightGreen, MarkerBrushChangedCallback)); - - /// - /// Gets/sets the Brush used for marking search results in the TextView. - /// - public Brush MarkerBrush - { - get { return (Brush)GetValue(MarkerBrushProperty); } - set { SetValue(MarkerBrushProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty LocalizationProperty = - DependencyProperty.Register("Localization", typeof(Localization), typeof(SearchReplacePanel), - new FrameworkPropertyMetadata(new Localization())); - - /// - /// Gets/sets the localization for the SearchReplacePanel. - /// - public Localization Localization - { - get { return (Localization)GetValue(LocalizationProperty); } - set { SetValue(LocalizationProperty, value); } - } - - private static void MarkerBrushChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is SearchReplacePanel panel) - { - panel._renderer.MarkerBrush = (Brush)e.NewValue; - } - } - - static SearchReplacePanel() - { - DefaultStyleKeyProperty.OverrideMetadata(typeof(SearchReplacePanel), new FrameworkPropertyMetadata(typeof(SearchReplacePanel))); - } - - private static void SearchPatternChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is SearchReplacePanel panel) - { - panel.ValidateSearchText(); - panel.UpdateSearch(); - } - } - - private void UpdateSearch() - { - // only reset as long as there are results - // if no results are found, the "no matches found" message should not flicker. - // if results are found by the next run, the message will be hidden inside DoSearch ... - if (_renderer.CurrentResults.Count > 0) - { - _messageView.IsOpen = false; - } - - _strategy = SearchStrategyFactory.Create(SearchPattern ?? "", !MatchCase, WholeWords, UseRegex ? SearchMode.RegEx : SearchMode.Normal); - OnSearchOptionsChanged(new SearchOptionsChangedEventArgs(SearchPattern, MatchCase, UseRegex, WholeWords)); - DoSearch(true); - } - - /// - /// Creates a new SearchReplacePanel. - /// - private SearchReplacePanel() - { - } - - /// - /// Adds the commands used by SearchReplacePanel to the given CommandBindingCollection. - /// - /// - public void RegisterCommands(CommandBindingCollection commandBindings) - { - _handler.RegisterGlobalCommands(commandBindings); - } - - /// - /// Removes the SearchReplacePanel from the TextArea. - /// - public void Uninstall() - { - CloseAndRemove(); - _textArea.DefaultInputHandler.NestedInputHandlers.Remove(_handler); - } - - private void AttachInternal(TextArea textArea) - { - _textArea = textArea; - _adorner = new SearchReplacePanelAdorner(textArea, this); - DataContext = this; - - _renderer = new SearchReplaceResultBackgroundRenderer(); - _currentDocument = textArea.Document; - if (_currentDocument != null) - { - _currentDocument.TextChanged += TextArea_Document_TextChanged; - } - - textArea.DocumentChanged += TextArea_DocumentChanged; - KeyDown += SearchLayerKeyDown; - - CommandBindings.Add(new CommandBinding(SearchCommands.FindNext, (sender, e) => FindNext())); - CommandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, (sender, e) => FindPrevious())); - CommandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, (sender, e) => Close())); - - CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, (sender, e) => - { - IsReplaceMode = false; - Reactivate(); - })); - CommandBindings.Add(new CommandBinding(ApplicationCommands.Replace, (sender, e) => IsReplaceMode = true)); - CommandBindings.Add(new CommandBinding(SearchCommandsEx.ReplaceNext, (sender, e) => ReplaceNext(), (sender, e) => e.CanExecute = IsReplaceMode)); - CommandBindings.Add(new CommandBinding(SearchCommandsEx.ReplaceAll, (sender, e) => ReplaceAll(), (sender, e) => e.CanExecute = IsReplaceMode)); - - IsClosed = true; - } - - private void TextArea_DocumentChanged(object sender, EventArgs e) - { - if (_currentDocument != null) - { - _currentDocument.TextChanged -= TextArea_Document_TextChanged; - } - - _currentDocument = _textArea.Document; - if (_currentDocument != null) - { - _currentDocument.TextChanged += TextArea_Document_TextChanged; - DoSearch(false); - } - } - - private void TextArea_Document_TextChanged(object sender, EventArgs e) - { - DoSearch(false); - } - - /// - public override void OnApplyTemplate() - { - base.OnApplyTemplate(); - _searchTextBox = Template.FindName("PART_searchTextBox", this) as TextBox; - } - - private void ValidateSearchText() - { - if (_searchTextBox == null) - { - return; - } - - var be = _searchTextBox.GetBindingExpression(TextBox.TextProperty); - try - { - Validation.ClearInvalid(be); - UpdateSearch(); - } - catch (SearchPatternException ex) - { - var ve = new ValidationError(be.ParentBinding.ValidationRules[0], be, ex.Message, ex); - Validation.MarkInvalid(be, ve); - } - } - - /// - /// Reactivates the SearchReplacePanel by setting the focus on the search box and selecting all text. - /// - public void Reactivate() - { - if (_searchTextBox == null) - { - return; - } - - _searchTextBox.Focus(); - _searchTextBox.SelectAll(); - } - - /// - /// Moves to the next occurrence in the file. - /// - public void FindNext() - { - var result = _renderer.CurrentResults.FindFirstSegmentWithStartAfter(_textArea.Caret.Offset + 1) - ?? _renderer.CurrentResults.FirstSegment; - - if (result != null) - { - SelectResult(result); - } - } - - /// - /// Moves to the previous occurrence in the file. - /// - public void FindPrevious() - { - var result = _renderer.CurrentResults.FindFirstSegmentWithStartAfter(_textArea.Caret.Offset); - - if (result != null) - { - result = _renderer.CurrentResults.GetPreviousSegment(result); - } - - if (result == null) - { - result = _renderer.CurrentResults.LastSegment; - } - - if (result != null) - { - SelectResult(result); - } - } - - private void DoSearch(bool changeSelection) - { - if (IsClosed) - { - return; - } - - _renderer.CurrentResults.Clear(); - - if (!string.IsNullOrEmpty(SearchPattern)) - { - var offset = _textArea.Caret.Offset; - if (changeSelection) - { - _textArea.ClearSelection(); - } - - // We cast from ISearchResult to SearchResult; this is safe because we always use the built-in strategy - foreach (var result in _strategy.FindAll(_textArea.Document, 0, _textArea.Document.TextLength).OfType()) - { - if (changeSelection && result.StartOffset >= offset) - { - SelectResult(result); - changeSelection = false; - } - _renderer.CurrentResults.Add(result); - } - - if (_renderer.CurrentResults.Count == 0) - { - _messageView.IsOpen = true; - _messageView.Content = Localization.NoMatchesFoundText; - _messageView.PlacementTarget = _searchTextBox; - } - else - { - _messageView.IsOpen = false; - } - } - _textArea.TextView.InvalidateLayer(KnownLayer.Selection); - } - - private void SelectResult(TextSegment textSement) - { - _textArea.Caret.Offset = textSement.StartOffset; - _textArea.Selection = Selection.Create(_textArea, textSement.StartOffset, textSement.EndOffset); - _textArea.Caret.BringCaretToView(); - // show caret even if the editor does not have the Keyboard Focus - _textArea.Caret.Show(); - } - - private void SearchLayerKeyDown(object sender, KeyEventArgs e) - { - switch (e.Key) - { - case Key.Enter: - e.Handled = true; - if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) - { - FindPrevious(); - } - else - { - FindNext(); - } - - if (_searchTextBox != null) - { - var error = Validation.GetErrors(_searchTextBox).FirstOrDefault(); - if (error != null) - { - _messageView.Content = Localization.ErrorText + " " + error.ErrorContent; - _messageView.PlacementTarget = _searchTextBox; - _messageView.IsOpen = true; - } - } - break; - case Key.Escape: - e.Handled = true; - Close(); - break; - } - } - - /// - /// Gets whether the Panel is already closed. - /// - public bool IsClosed { get; private set; } - - /// - /// Closes the SearchReplacePanel. - /// - public void Close() - { - var hasFocus = IsKeyboardFocusWithin; - - var layer = AdornerLayer.GetAdornerLayer(_textArea); - layer?.Remove(_adorner); - - _messageView.IsOpen = false; - _textArea.TextView.BackgroundRenderers.Remove(_renderer); - if (hasFocus) - { - _textArea.Focus(); - } - - IsClosed = true; - - // Clear existing search results so that the segments don't have to be maintained - _renderer.CurrentResults.Clear(); - } - - /// - /// Closes the SearchReplacePanel and removes it. - /// - private void CloseAndRemove() - { - Close(); - _textArea.DocumentChanged -= TextArea_DocumentChanged; - if (_currentDocument != null) - { - _currentDocument.TextChanged -= TextArea_Document_TextChanged; - } - } - - /// - /// Opens the an existing search panel. - /// - public void Open() - { - if (!IsClosed) - { - return; - } - - var layer = AdornerLayer.GetAdornerLayer(_textArea); - layer?.Add(_adorner); - - _textArea.TextView.BackgroundRenderers.Add(_renderer); - IsClosed = false; - DoSearch(false); - } - - /// - /// Fired when SearchOptions are changed inside the SearchReplacePanel. - /// - public event EventHandler SearchOptionsChanged; - - /// - /// Raises the event. - /// - /// - protected virtual void OnSearchOptionsChanged(SearchOptionsChangedEventArgs searchOptionsChangedEventArgs) - { - SearchOptionsChanged?.Invoke(this, searchOptionsChangedEventArgs); - } - - public static readonly DependencyProperty IsReplaceModeProperty = DependencyProperty.Register( - "IsReplaceMode", typeof(bool), typeof(SearchReplacePanel), new FrameworkPropertyMetadata()); - - public bool IsReplaceMode - { - get => (bool)GetValue(IsReplaceModeProperty); - set => SetValue(IsReplaceModeProperty, value); - } - - public static readonly DependencyProperty ReplacePatternProperty = DependencyProperty.Register( - "ReplacePattern", typeof(string), typeof(SearchReplacePanel), new FrameworkPropertyMetadata()); - - public string ReplacePattern - { - get => (string)GetValue(ReplacePatternProperty); - set => SetValue(ReplacePatternProperty, value); - } - - /// - /// Creates a SearchReplacePanel and installs it to the TextEditor's TextArea. - /// - /// - public static SearchReplacePanel Install(TextEditor editor) - { - if (editor == null) - { - throw new ArgumentNullException(nameof(editor)); - } - - return Install(editor.TextArea); - } - - /// - /// Creates a SearchReplacePanel and installs it to the TextArea. - /// - /// - public static SearchReplacePanel Install(TextArea textArea) - { - if (textArea == null) - { - throw new ArgumentNullException(nameof(textArea)); - } - - var panel = new SearchReplacePanel { _textArea = textArea }; - panel.AttachInternal(textArea); - panel._handler = new SearchReplaceInputHandler(textArea, panel); - textArea.DefaultInputHandler.NestedInputHandlers.Add(panel._handler); - return panel; - } - - public void ReplaceNext() - { - if (!IsReplaceMode) - { - return; - } - - FindNext(); - if (!_textArea.Selection.IsEmpty) - { - _textArea.Selection.ReplaceSelectionWithText(ReplacePattern ?? string.Empty); - } - } - - public void ReplaceAll() - { - if (!IsReplaceMode) - { - return; - } - - var replacement = ReplacePattern ?? string.Empty; - var document = _textArea.Document; - using (document.RunUpdate()) - { - var segments = _renderer.CurrentResults.OrderByDescending(x => x.EndOffset).ToArray(); - foreach (var textSegment in segments) - { - document.Replace(textSegment.StartOffset, textSegment.Length, - new StringTextSource(replacement)); - } - } - } - - private class SearchReplaceInputHandler : TextAreaInputHandler - { - private readonly SearchReplacePanel _panel; - - internal SearchReplaceInputHandler(TextArea textArea, SearchReplacePanel panel) - : base(textArea) - { - RegisterCommands(); - _panel = panel; - } - - private void RegisterCommands() - { - CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind)); - CommandBindings.Add(new CommandBinding(ApplicationCommands.Replace, ExecuteReplace)); - CommandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext, CanExecuteWithOpenSearchPanel)); - CommandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious, CanExecuteWithOpenSearchPanel)); - CommandBindings.Add(new CommandBinding(SearchCommandsEx.ReplaceNext, ExecuteReplaceNext, CanExecuteWithOpenSearchPanel)); - CommandBindings.Add(new CommandBinding(SearchCommandsEx.ReplaceAll, ExecuteReplaceAll, CanExecuteWithOpenSearchPanel)); - CommandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, ExecuteCloseSearchPanel, CanExecuteWithOpenSearchPanel)); - } - - private void ExecuteFind(object sender, ExecutedRoutedEventArgs e) - { - FindOrReplace(isReplaceMode: false); - } - - private void ExecuteReplace(object sender, ExecutedRoutedEventArgs e) - { - FindOrReplace(isReplaceMode: true); - } - - private void FindOrReplace(bool isReplaceMode) - { - _panel.IsReplaceMode = isReplaceMode; - _panel.Open(); - if (!TextArea.Selection.IsEmpty && !TextArea.Selection.IsMultiline) - { - _panel.SearchPattern = TextArea.Selection.GetText(); - } - - TextArea.Dispatcher.InvokeAsync(() => _panel.Reactivate(), DispatcherPriority.Input); - } - - private void CanExecuteWithOpenSearchPanel(object sender, CanExecuteRoutedEventArgs e) - { - if (_panel.IsClosed) - { - e.CanExecute = false; - e.ContinueRouting = true; - } - else - { - e.CanExecute = true; - e.Handled = true; - } - } - - private void ExecuteFindNext(object sender, ExecutedRoutedEventArgs e) - { - if (_panel.IsClosed) - { - return; - } - - _panel.FindNext(); - e.Handled = true; - } - - private void ExecuteFindPrevious(object sender, ExecutedRoutedEventArgs e) - { - if (_panel.IsClosed) - { - return; - } - - _panel.FindPrevious(); - e.Handled = true; - } - - private void ExecuteReplaceNext(object sender, ExecutedRoutedEventArgs e) - { - if (_panel.IsClosed) - { - return; - } - - _panel.ReplaceNext(); - e.Handled = true; - } - - private void ExecuteReplaceAll(object sender, ExecutedRoutedEventArgs e) - { - if (_panel.IsClosed) - { - return; - } - - _panel.ReplaceAll(); - e.Handled = true; - } - - private void ExecuteCloseSearchPanel(object sender, ExecutedRoutedEventArgs e) - { - if (_panel.IsClosed) - { - return; - } - - _panel.Close(); - e.Handled = true; - } - - internal void RegisterGlobalCommands(CommandBindingCollection commandBindings) - { - commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind)); - commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext, CanExecuteWithOpenSearchPanel)); - commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious, CanExecuteWithOpenSearchPanel)); - } - } - } - - /// - /// EventArgs for event. - /// - public class SearchOptionsChangedEventArgs : EventArgs - { - /// - /// Gets the search pattern. - /// - public string SearchPattern { get; } - - /// - /// Gets whether the search pattern should be interpreted case-sensitive. - /// - public bool MatchCase { get; } - - /// - /// Gets whether the search pattern should be interpreted as regular expression. - /// - public bool UseRegex { get; } - - /// - /// Gets whether the search pattern should only match whole words. - /// - public bool WholeWords { get; } - - /// - /// Creates a new SearchOptionsChangedEventArgs instance. - /// - /// - /// - /// - /// - public SearchOptionsChangedEventArgs(string searchPattern, bool matchCase, bool useRegex, bool wholeWords) - { - SearchPattern = searchPattern; - MatchCase = matchCase; - UseRegex = useRegex; - WholeWords = wholeWords; - } - } - - internal class SearchReplacePanelAdorner : Adorner - { - private readonly SearchReplacePanel _panel; - - public SearchReplacePanelAdorner(TextArea textArea, SearchReplacePanel panel) - : base(textArea) - { - _panel = panel; - AddVisualChild(panel); - } - - protected override int VisualChildrenCount - { - get { return 1; } - } - - protected override Visual GetVisualChild(int index) - { - if (index != 0) - { - throw new ArgumentOutOfRangeException(); - } - - return _panel; - } - - protected override Size ArrangeOverride(Size finalSize) - { - _panel.Arrange(new Rect(new Point(0, 0), finalSize)); - return new Size(_panel.ActualWidth, _panel.ActualHeight); - } - } - - internal class SearchReplaceResultBackgroundRenderer : IBackgroundRenderer - { - private Brush _markerBrush; - private Pen _markerPen; - - public TextSegmentCollection CurrentResults { get; } = new TextSegmentCollection(); - - public KnownLayer Layer - { - get - { - // draw behind selection - return KnownLayer.Selection; - } - } - - public SearchReplaceResultBackgroundRenderer() - { - _markerBrush = Brushes.LightGreen; - _markerPen = new Pen(_markerBrush, 1); - } - - public Brush MarkerBrush - { - get { return _markerBrush; } - set - { - _markerBrush = value; - _markerPen = new Pen(_markerBrush, 1); - } - } - - public void Draw(TextView textView, DrawingContext drawingContext) - { - if (textView == null) - { - throw new ArgumentNullException(nameof(textView)); - } - - if (drawingContext == null) - { - throw new ArgumentNullException(nameof(drawingContext)); - } - - if (CurrentResults == null || !textView.VisualLinesValid) - { - return; - } - - var visualLines = textView.VisualLines; - if (visualLines.Count == 0) - { - return; - } - - var viewStart = visualLines[0].FirstDocumentLine.Offset; - var viewEnd = visualLines.Last().LastDocumentLine.EndOffset; - - foreach (var result in CurrentResults.FindOverlappingSegments(viewStart, viewEnd - viewStart)) - { - var geoBuilder = new BackgroundGeometryBuilder - { - //BorderThickness = markerPen != null ? markerPen.Thickness : 0, - AlignToWholePixels = true, - CornerRadius = 3 - }; - geoBuilder.AddSegment(textView, result); - var geometry = geoBuilder.CreateGeometry(); - if (geometry != null) - { - drawingContext.DrawGeometry(_markerBrush, _markerPen, geometry); - } - } - } - } - - public static class SearchCommandsEx - { - /// Replaces the next occurrence in the document. - public static readonly RoutedCommand ReplaceNext = new RoutedCommand("ReplaceNext", typeof(SearchReplacePanel), - new InputGestureCollection - { - new KeyGesture(Key.R, ModifierKeys.Alt) - }); - - /// Replaces all the occurrences in the document. - public static readonly RoutedCommand ReplaceAll = new RoutedCommand("ReplaceAll", typeof(SearchReplacePanel), - new InputGestureCollection - { - new KeyGesture(Key.A, ModifierKeys.Alt) - }); - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/Behaviors/DoubleClickTrigger.cs b/src/CosmosDbExplorer/Infrastructure/Behaviors/DoubleClickTrigger.cs deleted file mode 100644 index fda34bd..0000000 --- a/src/CosmosDbExplorer/Infrastructure/Behaviors/DoubleClickTrigger.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Windows; -using System.Windows.Input; -using System.Windows.Interactivity; - -namespace CosmosDbExplorer.Infrastructure.Behaviors -{ - public class DoubleClickTrigger : TriggerBase - { - protected override void OnAttached() - { - base.OnAttached(); - AssociatedObject.PreviewMouseDown += OnPreviewMouseDown; - } - - protected override void OnDetaching() - { - base.OnDetaching(); - AssociatedObject.PreviewMouseDown -= OnPreviewMouseDown; - } - - private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e) - { - if (e.ClickCount == 2) - { - InvokeActions(e); - } - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/Behaviors/ShortCutBehavior.cs b/src/CosmosDbExplorer/Infrastructure/Behaviors/ShortCutBehavior.cs deleted file mode 100644 index 7e826dc..0000000 --- a/src/CosmosDbExplorer/Infrastructure/Behaviors/ShortCutBehavior.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Windows; -using System.Windows.Input; -using System.Windows.Controls; - -namespace UI.Common.CommandBehaviors -{ - /// - /// this is an Attached behavior for KeyDown that is specific to handling Shortcuts - /// - public class ShortCutBehavior - { - public static DependencyProperty CommandProperty = - DependencyProperty.RegisterAttached( "Command", - typeof( ICommand ), - typeof( ShortCutBehavior ), - new UIPropertyMetadata( CommandChanged ) ); - - public static DependencyProperty CommandParameterProperty = - DependencyProperty.RegisterAttached( "CommandParameter", - typeof( object ), - typeof( ShortCutBehavior ) ); - - - public static ICommand GetCommand( DependencyObject obj ) - { - return (ICommand)obj.GetValue( CommandProperty ); - } - - public static void SetCommand( DependencyObject target, ICommand value ) - { - target.SetValue( CommandProperty, value ); - } - - public static void SetCommandParameter( DependencyObject target, object value ) - { - target.SetValue( CommandParameterProperty, value ); - } - - public static object GetCommandParameter( DependencyObject target ) - { - return target.GetValue( CommandParameterProperty ); - } - - private static void CommandChanged( DependencyObject target, DependencyPropertyChangedEventArgs e ) - { - var control = target as Control; - if (control != null) - { - if ((e.NewValue != null) && (e.OldValue == null)) - { - control.KeyDown += OnKeyDown; - } - else if ((e.NewValue == null) && (e.OldValue != null)) - { - control.KeyDown -= OnKeyDown; - } - } - } - - private static void OnKeyDown( object sender, KeyEventArgs e ) - { - var k = e.Key == Key.System ? e.SystemKey : e.Key; - - if (k == Key.System || - k == Key.LeftCtrl || - k == Key.RightCtrl || - k == Key.LeftAlt || - k == Key.RightAlt || - k == Key.LeftShift || - k == Key.RightShift) - { - return; - } - - var control = sender as Control; - var command = (ICommand)control.GetValue( CommandProperty ); - object commandParameter = e; - command.Execute( commandParameter ); - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/Behaviors/ShortCuts/DoubleShortCut.cs b/src/CosmosDbExplorer/Infrastructure/Behaviors/ShortCuts/DoubleShortCut.cs deleted file mode 100644 index 356f250..0000000 --- a/src/CosmosDbExplorer/Infrastructure/Behaviors/ShortCuts/DoubleShortCut.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Windows.Input; - -namespace UI.Common.ShortCuts -{ - public class DoubleShortCut - { - readonly Action _execute; - readonly ICommand _cmd; - readonly Key _keyFirst; - readonly Key _keySecond; - readonly ModifierKeys _modifier; - readonly bool _needsAgreement; - - public DoubleShortCut() - { } - - public DoubleShortCut( Action execute, Key keyfirst, Key keysecond, ModifierKeys modifier, bool needsAgreement = false ) - { - _execute = execute; - _cmd = null; - _keyFirst = keyfirst; - _keySecond = keysecond; - _modifier = modifier; - _needsAgreement = needsAgreement; - } - - public DoubleShortCut( ICommand cmd, Key keyfirst, Key keysecond, ModifierKeys modifier, bool needsAgreement = false ) - { - _execute = null; - _cmd = cmd; - _keyFirst = keyfirst; - _keySecond = keysecond; - _modifier = modifier; - _needsAgreement = needsAgreement; - } - - public bool CheckAndRunIt(ModifierKeys modifier, Key kfirst, Key ksecond, bool agreementOpen) - { - if (_needsAgreement) - { - if (!agreementOpen) - { - return false; - } - } - - if (modifier == _modifier && _keyFirst == kfirst && _keySecond == ksecond) - { - if (_cmd == null) - { - _execute(); - return true; - } - else - { - _cmd.Execute( null ); - return true; - } - } - return false; - } - - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/Behaviors/ShortCuts/SingleShortCut.cs b/src/CosmosDbExplorer/Infrastructure/Behaviors/ShortCuts/SingleShortCut.cs deleted file mode 100644 index 5b49b47..0000000 --- a/src/CosmosDbExplorer/Infrastructure/Behaviors/ShortCuts/SingleShortCut.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Windows.Input; - -namespace UI.Common.ShortCuts -{ - public class SingleShortCut - { - readonly Action _execute; - readonly ICommand _cmd; - readonly Key _key; - readonly ModifierKeys _modifier; - readonly bool _needsAgreement; - - public SingleShortCut() - { } - - public SingleShortCut( Action execute, Key key, ModifierKeys modifier, bool needsAgreement = false ) - { - _execute = execute; - _cmd = null; - _key = key; - _modifier = modifier; - _needsAgreement = needsAgreement; - } - - public SingleShortCut( ICommand cmd, Key key, ModifierKeys modifier, bool needsAgreement = false ) - { - _execute = null; - _cmd = cmd; - _key = key; - _modifier = modifier; - _needsAgreement = needsAgreement; - } - - public bool CheckAndRunIt(ModifierKeys modifier, Key k, bool agreementOpen) - { - if (_needsAgreement) - { - if (!agreementOpen) - { - return false; - } - } - - if (modifier == _modifier && _key == k) - { - if (_cmd == null) - { - _execute(); - return true; - } - else - { - _cmd.Execute( null ); - return true; - } - } - return false; - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/Converters/QueryMetricsToDocumentConverter.cs b/src/CosmosDbExplorer/Infrastructure/Converters/QueryMetricsToDocumentConverter.cs deleted file mode 100644 index 7d35306..0000000 --- a/src/CosmosDbExplorer/Infrastructure/Converters/QueryMetricsToDocumentConverter.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; -using ICSharpCode.AvalonEdit.Document; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.Infrastructure.Converters -{ - public class QueryMetricsToDocumentConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is QueryMetrics metrics) - { - return new TextDocument(metrics.ToString()); - } - - return null; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/Converters/QueryMetricsToStringConverter.cs b/src/CosmosDbExplorer/Infrastructure/Converters/QueryMetricsToStringConverter.cs deleted file mode 100644 index 7b4f534..0000000 --- a/src/CosmosDbExplorer/Infrastructure/Converters/QueryMetricsToStringConverter.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.Infrastructure.Converters -{ - public class QueryMetricsToStringConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is QueryMetrics metrics) - { - return metrics.ToString(); - } - - return null; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/Extensions/DocumentClientExceptionExtension.cs b/src/CosmosDbExplorer/Infrastructure/Extensions/DocumentClientExceptionExtension.cs deleted file mode 100644 index 93c824f..0000000 --- a/src/CosmosDbExplorer/Infrastructure/Extensions/DocumentClientExceptionExtension.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Microsoft.Azure.Documents; -using Newtonsoft.Json; - -namespace CosmosDbExplorer.Infrastructure.Extensions -{ - public static class DocumentClientExceptionExtension - { - private static readonly Regex ErrorDocumentRegex = new Regex("Message: (?.*), documentdb-dotnet-sdk/.*$", RegexOptions.Compiled); - - public static string Parse(this DocumentClientException exception) - { - var message = exception.Message; - if (exception.Message.StartsWith("Message: {")) - { - message = ParseSyntaxException(exception); - } - - return message.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); - } - - private static string ParseSyntaxException(DocumentClientException exception) - { - try - { - var json = ErrorDocumentRegex.Match(exception.Message).Groups["json"].Value; - - var errorDoc = JsonConvert.DeserializeObject(json); - - var sb = new StringBuilder(); - foreach (dynamic e in errorDoc.errors) - { - sb.Append($"{e.code}: {e.message}"); - sb.Append(Environment.NewLine); - } - - return sb.ToString(); - } - catch - { - return exception.Message; - } - } - } - - public class Location - { - [JsonProperty("start")] - public int Start { get; set; } - - [JsonProperty("end")] - public int End { get; set; } - } - - public class Error - { - [JsonProperty("severity")] - public string Severity { get; set; } - - [JsonProperty("location")] - public Location Location { get; set; } - - [JsonProperty("code")] - public string Code { get; set; } - - [JsonProperty("message")] - public string Message { get; set; } - - public override string ToString() - { - return $"{Code}: {Message}"; - } - } - - public class DocumentClientExceptionMessage - { - - [JsonProperty("errors")] - public IList Errors { get; set; } - - public override string ToString() - { - var message = new StringBuilder(); - - foreach (var error in Errors) - { - message.AppendLine(error.ToString()); - } - - return message.ToString(); - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/Extensions/DocumentExtensions.cs b/src/CosmosDbExplorer/Infrastructure/Extensions/DocumentExtensions.cs deleted file mode 100644 index 6e2433d..0000000 --- a/src/CosmosDbExplorer/Infrastructure/Extensions/DocumentExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Linq; -using Microsoft.Azure.Documents; -using Newtonsoft.Json.Linq; - -namespace CosmosDbExplorer.Infrastructure.Extensions -{ - - public static class DocumentExtensions - { - public static object GetPartitionKeyValue(this Document document, string selectToken) - { - return JObject.FromObject(document).SelectToken(selectToken).ToObject(); - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/Extensions/ListExtensions.cs b/src/CosmosDbExplorer/Infrastructure/Extensions/ListExtensions.cs deleted file mode 100644 index 771c2f6..0000000 --- a/src/CosmosDbExplorer/Infrastructure/Extensions/ListExtensions.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CosmosDbExplorer.Infrastructure.Extensions -{ - public static class ListExtensions - { - public static int Replace(this IList source, T oldValue, T newValue) - { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } - - var index = source.IndexOf(oldValue); - if (index != -1) - { - source[index] = newValue; - } - - return index; - } - - public static void ReplaceAll(this IList source, T oldValue, T newValue) - { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } - - int index; - do - { - index = source.IndexOf(oldValue); - if (index != -1) - { - source[index] = newValue; - } - } while (index != -1); - } - - public static IEnumerable Replace(this IEnumerable source, T oldValue, T newValue) - { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } - - return source.Select(x => EqualityComparer.Default.Equals(x, oldValue) ? newValue : x); - } - - public static IList Move(this IList list, int oldIndex, int newIndex) - { - // exit if positions are equal or outside array - if ((oldIndex == newIndex) || (0 > oldIndex) || (oldIndex >= list.Count) || (0 > newIndex) || (newIndex >= list.Count)) - { - return list; - } - - var tmp = list[oldIndex]; - - // local variables - int i; - // move element down and shift other elements up - if (oldIndex < newIndex) - { - for (i = oldIndex; i < newIndex; i++) - { - list[i] = list[i + 1]; - } - } - // move element up and shift other elements down - else - { - for (i = oldIndex; i > newIndex; i--) - { - list[i] = list[i - 1]; - } - } - // put element from position 1 to destination - list[newIndex] = tmp; - - return list; - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/Extensions/PartitionKeyDefinitionExtensions.cs b/src/CosmosDbExplorer/Infrastructure/Extensions/PartitionKeyDefinitionExtensions.cs deleted file mode 100644 index 049750b..0000000 --- a/src/CosmosDbExplorer/Infrastructure/Extensions/PartitionKeyDefinitionExtensions.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Linq; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.Infrastructure.Extensions -{ - public static class PartitionKeyDefinitionExtensions - { - public static string GetSelectToken(this PartitionKeyDefinition input) - { - return GetQueryToken(input)? - .Replace("[\"", "['") - .Replace("\"]", "']"); - } - - public static string GetQueryToken(this PartitionKeyDefinition input) - { - var partitionKey = input?.Paths.FirstOrDefault(); - - if (partitionKey == null) - { - return null; - } - - var nodes = partitionKey.TrimStart('/') - .Split('/') - .Select(node => node[0] == '"' ? $"[{node}]" : $".{node}"); - - return string.Concat(nodes); - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/Models/CollectionMetrics.cs b/src/CosmosDbExplorer/Infrastructure/Models/CollectionMetrics.cs deleted file mode 100644 index 8668633..0000000 --- a/src/CosmosDbExplorer/Infrastructure/Models/CollectionMetrics.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; - -namespace CosmosDbExplorer.Infrastructure.Models -{ - public class CollectionMetric - { - public CollectionMetric(ResourceResponse documentCollection) - { - var quotaUsage = documentCollection.CurrentResourceQuotaUsage - .Split(';') - .Select(item => new { Key = item.Split('=')[0], Value = item.Split('=')[1] }) - .ToDictionary(d => d.Key, d => d.Value); - - RequestCharge = documentCollection.RequestCharge; - PartitionCount = documentCollection.Resource.PartitionKeyRangeStatistics.Count; - DocumentSize = long.Parse(quotaUsage["documentsSize"]); - DocumentCount = long.Parse(quotaUsage["documentsCount"]); - PartitionMetrics = documentCollection.Resource.PartitionKeyRangeStatistics.ToList(); - CollectionSizeQuota = documentCollection.CollectionSizeQuota; - CollectionSizeUsage = documentCollection.CollectionSizeUsage; - StoredProceduresQuota = documentCollection.StoredProceduresQuota; - StoredProceduresUsage = documentCollection.StoredProceduresUsage; - TriggersQuota = documentCollection.TriggersQuota; - TriggersUsage = documentCollection.TriggersUsage; - UserDefinedFunctionsQuota = documentCollection.UserDefinedFunctionsQuota; - UserDefinedFunctionsUsage = documentCollection.UserDefinedFunctionsUsage; - } - - public double RequestCharge { get; } - public int PartitionCount { get; } - public long DocumentCount { get; } - public long DocumentSize { get; } - public List PartitionMetrics { get; } - public long CollectionSizeQuota { get; } - public long CollectionSizeUsage { get; } - public long StoredProceduresQuota { get; } - public long StoredProceduresUsage { get; } - public long TriggersQuota { get; } - public long TriggersUsage { get; } - public long UserDefinedFunctionsQuota { get; } - public long UserDefinedFunctionsUsage { get; } - public bool HasPartitionKey { get; } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/Models/ExternalComponent.cs b/src/CosmosDbExplorer/Infrastructure/Models/ExternalComponent.cs deleted file mode 100644 index 8dfdd49..0000000 --- a/src/CosmosDbExplorer/Infrastructure/Models/ExternalComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace CosmosDbExplorer.Infrastructure.Models -{ - public class ExternalComponent - { - public string Name { get; set; } - public string LicenseUrl { get; set; } - public string ProjectUrl { get; set; } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/Models/StatusBarInfo.cs b/src/CosmosDbExplorer/Infrastructure/Models/StatusBarInfo.cs deleted file mode 100644 index 4efe54f..0000000 --- a/src/CosmosDbExplorer/Infrastructure/Models/StatusBarInfo.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; - -namespace CosmosDbExplorer.Infrastructure.Models -{ - public class StatusBarInfo : IStatusBarInfo - { - public StatusBarInfo(double? requestCharge, Document resource, NameValueCollection responseHeaders) - { - RequestCharge = requestCharge; - Resource = resource; - ResponseHeaders = responseHeaders; - } - - public StatusBarInfo(ResourceResponse response) - { - RequestCharge = response?.RequestCharge; - Resource = response?.Resource; - ResponseHeaders = response?.ResponseHeaders; - } - - public StatusBarInfo(IEnumerable> response) - { - RequestCharge = response.Sum(r => r.RequestCharge); - Resource = null; - ResponseHeaders = null; - } - - public double? RequestCharge { get; } - - public Document Resource { get; } - - public NameValueCollection ResponseHeaders { get; } - } - - public interface IStatusBarInfo - { - double? RequestCharge { get; } - - Document Resource { get; } - - NameValueCollection ResponseHeaders { get; } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/Models/WindowViewModelBase.cs b/src/CosmosDbExplorer/Infrastructure/Models/WindowViewModelBase.cs deleted file mode 100644 index f1d83e1..0000000 --- a/src/CosmosDbExplorer/Infrastructure/Models/WindowViewModelBase.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using CosmosDbExplorer.Services; -using GalaSoft.MvvmLight.Messaging; - -namespace CosmosDbExplorer.Infrastructure.Models -{ - public abstract class WindowViewModelBase : UIViewModelBase - { - public event Action RequestClose; - - protected WindowViewModelBase(IMessenger messenger, IUIServices uiServices) - : base(messenger, uiServices) - { - } - - public virtual void Close() - { - RequestClose?.Invoke(); - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/RadioButtonEx.cs b/src/CosmosDbExplorer/Infrastructure/RadioButtonEx.cs deleted file mode 100644 index d9b2eb0..0000000 --- a/src/CosmosDbExplorer/Infrastructure/RadioButtonEx.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Controls; - -namespace CosmosDbExplorer.Infrastructure -{ - public class RadioButtonEx : RadioButton - { - public object RadioValue - { - get { return (object)GetValue(RadioValueProperty); } - set { SetValue(RadioValueProperty, value); } - } - - // Using a DependencyProperty as the backing store for RadioValue. - // This enables animation, styling, binding, etc... - public static readonly DependencyProperty RadioValueProperty = DependencyProperty.Register("RadioValue", typeof(object), typeof(RadioButtonEx), new UIPropertyMetadata(null)); - - public object RadioBinding - { - get { return (object)GetValue(RadioBindingProperty); } - set { SetValue(RadioBindingProperty, value); } - } - - // Using a DependencyProperty as the backing store for RadioBinding. - // This enables animation, styling, binding, etc... - public static readonly DependencyProperty RadioBindingProperty = DependencyProperty.Register("RadioBinding", typeof(object), typeof(RadioButtonEx), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnRadioBindingChanged)); - - private static void OnRadioBindingChanged( - DependencyObject d, - DependencyPropertyChangedEventArgs e) - { - var rb = (RadioButtonEx)d; - - switch (e.NewValue) - { - case bool boolValue: - if (bool.Parse(rb.RadioValue.ToString()).Equals(boolValue)) - { - rb.SetCurrentValue(RadioButton.IsCheckedProperty, true); - } - break; - case Enum enumValue: - if (Enum.Parse(e.NewValue.GetType(), rb.RadioValue.ToString()).Equals(enumValue)) - { - rb.SetCurrentValue(RadioButton.IsCheckedProperty, true); - } - break; - default: - if (rb.RadioValue.Equals(e.NewValue)) - { - rb.SetCurrentValue(RadioButton.IsCheckedProperty, true); - } - break; - } - } - - protected override void OnChecked(RoutedEventArgs e) - { - base.OnChecked(e); - SetCurrentValue(RadioBindingProperty, RadioValue); - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/RelayCommand.cs b/src/CosmosDbExplorer/Infrastructure/RelayCommand.cs deleted file mode 100644 index 629bfc4..0000000 --- a/src/CosmosDbExplorer/Infrastructure/RelayCommand.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Diagnostics; -using System.Windows.Input; - -namespace CosmosDbExplorer.Infrastructure -{ - public class RelayCommand : ICommand - { - private readonly Action _execute; - private readonly Func _canExecute; - - public RelayCommand(Action execute) - : this(execute, null) - { - } - - public RelayCommand(Action execute, Func canExecute) - { - _execute = execute ?? throw new ArgumentNullException(nameof(execute)); - _canExecute = canExecute; - } - - [DebuggerStepThrough] - public bool CanExecute(object parameter) - { - return _canExecute == null || _canExecute(); - } - - public event EventHandler CanExecuteChanged - { - add { CommandManager.RequerySuggested += value; } - remove { CommandManager.RequerySuggested -= value; } - } - - public void Execute(object parameter) - { - _execute(); - } - } - - public class RelayCommand : ICommand - { - private readonly Action _execute; - private readonly Predicate _canExecute; - - public RelayCommand(Action execute) - : this(execute, null) - { - } - - public RelayCommand(Action execute, Predicate canExecute) - { - _execute = execute ?? throw new ArgumentNullException(nameof(execute)); - _canExecute = canExecute; - } - - [DebuggerStepThrough] - public bool CanExecute(object parameter) - { - return _canExecute == null || _canExecute((T)parameter); - } - - public event EventHandler CanExecuteChanged - { - add { CommandManager.RequerySuggested += value; } - remove { CommandManager.RequerySuggested -= value; } - } - - public void Execute(object parameter) - { - _execute((T)parameter); - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/TemplateSelectors/DocumentDescriptionTemplateSelector.cs b/src/CosmosDbExplorer/Infrastructure/TemplateSelectors/DocumentDescriptionTemplateSelector.cs deleted file mode 100644 index 3591da2..0000000 --- a/src/CosmosDbExplorer/Infrastructure/TemplateSelectors/DocumentDescriptionTemplateSelector.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Windows; -using System.Windows.Controls; -using CosmosDbExplorer.Services; - -namespace CosmosDbExplorer.Infrastructure.TemplateSelectors -{ - public class DocumentDescriptionTemplateSelector : DataTemplateSelector - { - public DataTemplate DefaultTemplate { get; set; } - public DataTemplate PartitionTemplate { get; set; } - - public override DataTemplate SelectTemplate(object item, DependencyObject container) - { - var dd = (DocumentDescription)item; - - if (dd.PartitionKey != null) - { - return PartitionTemplate; - } - return DefaultTemplate; - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/TemplateSelectors/StatusBarItemTemplateSelector.cs b/src/CosmosDbExplorer/Infrastructure/TemplateSelectors/StatusBarItemTemplateSelector.cs deleted file mode 100644 index 08bea21..0000000 --- a/src/CosmosDbExplorer/Infrastructure/TemplateSelectors/StatusBarItemTemplateSelector.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Windows; -using System.Windows.Controls; -using CosmosDbExplorer.Infrastructure.Models; - -namespace CosmosDbExplorer.Infrastructure.TemplateSelectors -{ - public class StatusBarItemTemplateSelector : DataTemplateSelector - { - public DataTemplate UsedMemoryTemplate { get; set; } - public DataTemplate ZoomTemplate { get; set; } - public DataTemplate SimpleTextTemplate { get; set; } - public DataTemplate ProgressBarTemplate { get; set; } - - public override DataTemplate SelectTemplate(object item, DependencyObject container) - { - switch (((StatusBarItem)item).Type) - { - case StatusBarItemType.UsedMemory: - return UsedMemoryTemplate; - case StatusBarItemType.Zoom: - return ZoomTemplate; - case StatusBarItemType.SimpleText: - return SimpleTextTemplate; - case StatusBarItemType.ProgessBar: - return ProgressBarTemplate; - default: - return base.SelectTemplate(item, container); - } - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/TemplateSelectors/StoredProcParameterTemplateSelector.cs b/src/CosmosDbExplorer/Infrastructure/TemplateSelectors/StoredProcParameterTemplateSelector.cs deleted file mode 100644 index b8983a9..0000000 --- a/src/CosmosDbExplorer/Infrastructure/TemplateSelectors/StoredProcParameterTemplateSelector.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Windows; -using System.Windows.Controls; -using CosmosDbExplorer.ViewModel.Assets; - -namespace CosmosDbExplorer.Infrastructure.TemplateSelectors -{ - public class StoredProcParameterTemplateSelector : DataTemplateSelector - { - public DataTemplate JsonDataTemplate { get; set; } - public DataTemplate FileDataTemplate { get; set; } - - public override DataTemplate SelectTemplate(object item, DependencyObject container) - { - switch ((StoredProcParameterKind)item) - { - case StoredProcParameterKind.Json: - return JsonDataTemplate; - case StoredProcParameterKind.File: - return FileDataTemplate; - default: - return base.SelectTemplate(item, container); - } - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/TemplateSelectors/TabContentTemplateSelector.cs b/src/CosmosDbExplorer/Infrastructure/TemplateSelectors/TabContentTemplateSelector.cs deleted file mode 100644 index 9926b44..0000000 --- a/src/CosmosDbExplorer/Infrastructure/TemplateSelectors/TabContentTemplateSelector.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.Windows; -using System.Windows.Controls; -using CosmosDbExplorer.ViewModel; -using CosmosDbExplorer.ViewModel.Assets; - -namespace CosmosDbExplorer.Infrastructure.TemplateSelectors -{ - public class TabContentTemplateSelector : DataTemplateSelector - { - public DataTemplate DocumentsTemplate { get; set; } - public DataTemplate QueryEditorTemplate { get; set; } - public DataTemplate ImportDocumentTemplate { get; set; } - public DataTemplate DatabaseViewTemplate { get; set; } - public DataTemplate StoredProcedureViewTemplate { get; set; } - public DataTemplate UserDefFuncViewTemplate { get; set; } - public DataTemplate TriggerViewTemplate { get; set; } - public DataTemplate ScaleAndSettingsTemplate { get; set; } - public DataTemplate UserEditTempalate { get; set; } - public DataTemplate PermissionEditTemplate { get; set; } - public DataTemplate CollectionMetricsTemplate { get; set; } - - public override DataTemplate SelectTemplate(object item, DependencyObject container) - { - if (item is DatabaseViewModel) - { - return DatabaseViewTemplate; - } - - if (item is DocumentsTabViewModel) - { - return DocumentsTemplate; - } - - if (item is QueryEditorViewModel) - { - return QueryEditorTemplate; - } - - if (item is ImportDocumentViewModel) - { - return ImportDocumentTemplate; - } - - if (item is StoredProcedureTabViewModel) - { - return StoredProcedureViewTemplate; - } - - if (item is UserDefFuncTabViewModel) - { - return UserDefFuncViewTemplate; - } - - if (item is TriggerTabViewModel) - { - return TriggerViewTemplate; - } - - if (item is ScaleAndSettingsTabViewModel) - { - return ScaleAndSettingsTemplate; - } - - if (item is UserEditViewModel) - { - return UserEditTempalate; - } - - if (item is PermissionEditViewModel) - { - return PermissionEditTemplate; - } - - if (item is CollectionMetricsTabViewModel) - { - return CollectionMetricsTemplate; - } - - return base.SelectTemplate(item, container); - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/Validar/ValidationTemplate.cs b/src/CosmosDbExplorer/Infrastructure/Validar/ValidationTemplate.cs deleted file mode 100644 index b7b1f3c..0000000 --- a/src/CosmosDbExplorer/Infrastructure/Validar/ValidationTemplate.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.ComponentModel; -using System.Linq; -using FluentValidation; -using FluentValidation.Results; - -namespace CosmosDbExplorer.Infrastructure.Validar -{ - public class ValidationTemplate : IDataErrorInfo, INotifyDataErrorInfo - where T : INotifyPropertyChanged - { - private readonly INotifyPropertyChanged _target; - private readonly IValidator _validator; - private ValidationResult _validationResult; - - public ValidationTemplate(T target) - { - _target = target; - _validator = ValidationFactory.GetValidator(); - _validationResult = _validator.Validate(target); - target.PropertyChanged += Validate; - } - - private void Validate(object sender, PropertyChangedEventArgs e) - { - _validationResult = _validator.Validate(_target); - foreach (var error in _validationResult.Errors) - { - RaiseErrorsChanged(error.PropertyName); - } - } - - public IEnumerable GetErrors(string propertyName) - { - return _validationResult.Errors - .Where(x => x.PropertyName == propertyName) - .Select(x => x.ErrorMessage); - } - - public bool HasErrors => _validationResult.Errors.Count > 0; - - public string Error - { - get - { - var strings = _validationResult.Errors.Select(x => x.ErrorMessage) - .ToArray(); - return string.Join(Environment.NewLine, strings); - } - } - - public string this[string columnName] - { - get - { - var strings = _validationResult.Errors.Where(x => x.PropertyName == columnName) - .Select(x => x.ErrorMessage) - .ToArray(); - return string.Join(Environment.NewLine, strings); - } - } - - public event EventHandler ErrorsChanged; - - private void RaiseErrorsChanged(string propertyName) - { - var handler = ErrorsChanged; - handler?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); - } - } -} diff --git a/src/CosmosDbExplorer/MainWindow.xaml b/src/CosmosDbExplorer/MainWindow.xaml deleted file mode 100644 index 63efe0b..0000000 --- a/src/CosmosDbExplorer/MainWindow.xaml +++ /dev/null @@ -1,718 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/CosmosDbExplorer/MainWindow.xaml.cs b/src/CosmosDbExplorer/MainWindow.xaml.cs deleted file mode 100644 index d14af40..0000000 --- a/src/CosmosDbExplorer/MainWindow.xaml.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Windows.Threading; -using AutoUpdaterDotNET; -using CosmosDbExplorer.Properties; -using CosmosDbExplorer.ViewModel; -using Newtonsoft.Json; - -namespace CosmosDbExplorer -{ - /// - /// Interaction logic for MainWindow.xaml - /// - public partial class MainWindow - { - public MainWindow() - { - InitializeComponent(); - SetupAutoUpdater(); - } - - private void SetupAutoUpdater() - { - // Allow user to be reminded to update in 1 day - AutoUpdater.ShowRemindLaterButton = true; - AutoUpdater.LetUserSelectRemindLater = false; - AutoUpdater.RemindLaterTimeSpan = RemindLaterFormat.Days; - AutoUpdater.RemindLaterAt = 1; - - AutoUpdater.ReportErrors = false; - AutoUpdater.RunUpdateAsAdmin = false; - AutoUpdater.DownloadPath = Environment.CurrentDirectory; - AutoUpdater.ParseUpdateInfoEvent += AutoUpdateOnParseUpdateInfoEvent; - - var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(Settings.Default.AutoUpdaterIntervalInSeconds) }; - timer.Tick += delegate - { - AutoUpdater.Start(Settings.Default.AutoUpdaterUrl); - }; - timer.Start(); - } - - private void AutoUpdateOnParseUpdateInfoEvent(ParseUpdateInfoEventArgs args) - { - // Use JSON format for AutoUpdate release information file - dynamic json = JsonConvert.DeserializeObject(args.RemoteData); - args.UpdateInfo = new UpdateInfoEventArgs - { - CurrentVersion = json.version, - ChangelogURL = json.changelog, - Mandatory = new Mandatory { Value = json.mandatory }, - DownloadURL = json.url, - CheckSum = json.checksum != null ? new CheckSum { Value = json.checksum } : null - }; - } - - private void Window_Loaded(object sender, System.Windows.RoutedEventArgs e) - { - var vm = (MainViewModel)DataContext; - vm.RequestClose += () => Close(); - - AutoUpdater.Start(Settings.Default.AutoUpdaterUrl); - } - } -} diff --git a/src/CosmosDbExplorer/Infrastructure/MarkupExtensions/CursorExtensionConverter.cs b/src/CosmosDbExplorer/MarkupExtensions/CursorExtensionConverter.cs similarity index 93% rename from src/CosmosDbExplorer/Infrastructure/MarkupExtensions/CursorExtensionConverter.cs rename to src/CosmosDbExplorer/MarkupExtensions/CursorExtensionConverter.cs index 7613ba8..a733b50 100644 --- a/src/CosmosDbExplorer/Infrastructure/MarkupExtensions/CursorExtensionConverter.cs +++ b/src/CosmosDbExplorer/MarkupExtensions/CursorExtensionConverter.cs @@ -4,7 +4,7 @@ using System.Windows.Input; using System.Windows.Markup; -namespace CosmosDbExplorer.Infrastructure.MarkupExtensions +namespace CosmosDbExplorer.MarkupExtensions { public class CursorExtensionConverter : MarkupExtension, IValueConverter { diff --git a/src/CosmosDbExplorer/Infrastructure/MarkupExtensions/EnumBindingSourceExtension.cs b/src/CosmosDbExplorer/MarkupExtensions/EnumBindingSourceExtensions.cs similarity index 89% rename from src/CosmosDbExplorer/Infrastructure/MarkupExtensions/EnumBindingSourceExtension.cs rename to src/CosmosDbExplorer/MarkupExtensions/EnumBindingSourceExtensions.cs index c7d51d7..57c6457 100644 --- a/src/CosmosDbExplorer/Infrastructure/MarkupExtensions/EnumBindingSourceExtension.cs +++ b/src/CosmosDbExplorer/MarkupExtensions/EnumBindingSourceExtensions.cs @@ -2,7 +2,7 @@ using System.ComponentModel; using System.Windows.Markup; -namespace CosmosDbExplorer.Infrastructure.MarkupExtensions +namespace CosmosDbExplorer.MarkupExtensions { public class EnumBindingSourceExtension : MarkupExtension { @@ -35,7 +35,7 @@ public EnumBindingSourceExtension(Type enumType) EnumType = enumType; } - public override object ProvideValue(IServiceProvider serviceProvider) + public override object? ProvideValue(IServiceProvider serviceProvider) { //if (_enumType == null) //{ @@ -67,7 +67,7 @@ public EnumDescriptionTypeConverter(Type type) { } - public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) + public override object? ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) { if (destinationType == typeof(string)) { diff --git a/src/CosmosDbExplorer/Messages/ActivePaneChangedMessage.cs b/src/CosmosDbExplorer/Messages/ActivePaneChangedMessage.cs index 3ab4eec..46d2701 100644 --- a/src/CosmosDbExplorer/Messages/ActivePaneChangedMessage.cs +++ b/src/CosmosDbExplorer/Messages/ActivePaneChangedMessage.cs @@ -1,14 +1,14 @@ -using GalaSoft.MvvmLight; +using Microsoft.Toolkit.Mvvm.ComponentModel; namespace CosmosDbExplorer.Messages { public class ActivePaneChangedMessage { - public ActivePaneChangedMessage(ViewModelBase paneViewModel) + public ActivePaneChangedMessage(ObservableRecipient paneViewModel) { PaneViewModel = paneViewModel; } - public ViewModelBase PaneViewModel { get; } + public ObservableRecipient PaneViewModel { get; } } } diff --git a/src/CosmosDbExplorer/Messages/CloseDocumentMessage.cs b/src/CosmosDbExplorer/Messages/CloseDocumentMessage.cs index 9e28212..19bc6a7 100644 --- a/src/CosmosDbExplorer/Messages/CloseDocumentMessage.cs +++ b/src/CosmosDbExplorer/Messages/CloseDocumentMessage.cs @@ -1,4 +1,4 @@ -using CosmosDbExplorer.Infrastructure.Models; +using CosmosDbExplorer.ViewModels; namespace CosmosDbExplorer.Messages { diff --git a/src/CosmosDbExplorer/Messages/ConnectionSettingSavedMessage.cs b/src/CosmosDbExplorer/Messages/ConnectionSettingSavedMessage.cs index 9036f05..1cde07d 100644 --- a/src/CosmosDbExplorer/Messages/ConnectionSettingSavedMessage.cs +++ b/src/CosmosDbExplorer/Messages/ConnectionSettingSavedMessage.cs @@ -1,14 +1,14 @@ -using CosmosDbExplorer.Infrastructure.Models; +using CosmosDbExplorer.Core.Models; namespace CosmosDbExplorer.Messages { - class ConnectionSettingSavedMessage + public class ConnectionSettingSavedMessage { - public ConnectionSettingSavedMessage(Connection connection) + public ConnectionSettingSavedMessage(CosmosConnection connection) { Connection = connection; } - public Connection Connection { get; } + public CosmosConnection Connection { get; } } } diff --git a/src/CosmosDbExplorer/Messages/EditPermissionMessage.cs b/src/CosmosDbExplorer/Messages/EditPermissionMessage.cs new file mode 100644 index 0000000..41ee353 --- /dev/null +++ b/src/CosmosDbExplorer/Messages/EditPermissionMessage.cs @@ -0,0 +1,13 @@ +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.ViewModels.DatabaseNodes; + +namespace CosmosDbExplorer.Messages +{ + public class EditPermissionMessage : OpenTabMessageBase + { + public EditPermissionMessage(PermissionNodeViewModel node, CosmosConnection connection, CosmosDatabase database) + : base(node, connection, database, null) + { + } + } +} diff --git a/src/CosmosDbExplorer/Messages/EditStoredProcedureMessage.cs b/src/CosmosDbExplorer/Messages/EditStoredProcedureMessage.cs index d74982b..c78766b 100644 --- a/src/CosmosDbExplorer/Messages/EditStoredProcedureMessage.cs +++ b/src/CosmosDbExplorer/Messages/EditStoredProcedureMessage.cs @@ -1,13 +1,12 @@ -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.ViewModel; -using Microsoft.Azure.Documents; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.ViewModels.DatabaseNodes; namespace CosmosDbExplorer.Messages { public class EditStoredProcedureMessage : OpenTabMessageBase { - public EditStoredProcedureMessage(StoredProcedureNodeViewModel node, Connection connection, DocumentCollection collection) - : base(node, connection, collection) + public EditStoredProcedureMessage(StoredProcedureNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer container) + : base(node, connection, database, container) { } } diff --git a/src/CosmosDbExplorer/Messages/EditTriggerMessage.cs b/src/CosmosDbExplorer/Messages/EditTriggerMessage.cs index 9bcba8d..20dfe08 100644 --- a/src/CosmosDbExplorer/Messages/EditTriggerMessage.cs +++ b/src/CosmosDbExplorer/Messages/EditTriggerMessage.cs @@ -1,45 +1,12 @@ -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.ViewModel; -using Microsoft.Azure.Documents; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.ViewModels.DatabaseNodes; namespace CosmosDbExplorer.Messages { - public abstract class OpenTabMessageBase - { - protected OpenTabMessageBase(TNodeViewModel node, Connection connection, DocumentCollection collection) - { - Node = node; - Connection = connection; - Collection = collection; - } - - public TNodeViewModel Node { get; } - - public Connection Connection { get; } - - public DocumentCollection Collection { get; } - } - public class EditTriggerMessage : OpenTabMessageBase { - public EditTriggerMessage(TriggerNodeViewModel node, Connection connection, DocumentCollection collection) - : base(node, connection, collection) - { - } - } - - public class EditUserMessage : OpenTabMessageBase - { - public EditUserMessage(UserNodeViewModel node, Connection connection, DocumentCollection collection) - : base(node, connection, collection) - { - } - } - - public class EditPermissionMessage : OpenTabMessageBase - { - public EditPermissionMessage(PermissionNodeViewModel node, Connection connection, DocumentCollection collection) - : base(node, connection, collection) + public EditTriggerMessage(TriggerNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer container) + : base(node, connection, database, container) { } } diff --git a/src/CosmosDbExplorer/Messages/EditUserDefFuncMessage.cs b/src/CosmosDbExplorer/Messages/EditUserDefFuncMessage.cs index 07175a4..969d074 100644 --- a/src/CosmosDbExplorer/Messages/EditUserDefFuncMessage.cs +++ b/src/CosmosDbExplorer/Messages/EditUserDefFuncMessage.cs @@ -1,13 +1,12 @@ -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.ViewModel; -using Microsoft.Azure.Documents; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.ViewModels.DatabaseNodes; namespace CosmosDbExplorer.Messages { public class EditUserDefFuncMessage : OpenTabMessageBase { - public EditUserDefFuncMessage(UserDefFuncNodeViewModel node, Connection connection, DocumentCollection collection) - : base(node, connection, collection) + public EditUserDefFuncMessage(UserDefFuncNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer container) + : base(node, connection, database, container) { } } diff --git a/src/CosmosDbExplorer/Messages/EditUserMessage.cs b/src/CosmosDbExplorer/Messages/EditUserMessage.cs new file mode 100644 index 0000000..18bf3ba --- /dev/null +++ b/src/CosmosDbExplorer/Messages/EditUserMessage.cs @@ -0,0 +1,13 @@ +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.ViewModels.DatabaseNodes; + +namespace CosmosDbExplorer.Messages +{ + public class EditUserMessage : OpenTabMessageBase + { + public EditUserMessage(UserNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database) + : base(node, connection, database, null) + { + } + } +} diff --git a/src/CosmosDbExplorer/Messages/OpenCollectionMetricsViewMessage.cs b/src/CosmosDbExplorer/Messages/OpenCollectionMetricsViewMessage.cs deleted file mode 100644 index 0e720d9..0000000 --- a/src/CosmosDbExplorer/Messages/OpenCollectionMetricsViewMessage.cs +++ /dev/null @@ -1,15 +0,0 @@ -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.ViewModel; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.Messages -{ - - public class OpenCollectionMetricsViewMessage : OpenTabMessageBase - { - public OpenCollectionMetricsViewMessage(CollectionMetricsNodeViewModel node, Connection connection, DocumentCollection collection) - : base(node, connection, collection) - { - } - } -} diff --git a/src/CosmosDbExplorer/Messages/OpenContainerMetricsViewMessage.cs b/src/CosmosDbExplorer/Messages/OpenContainerMetricsViewMessage.cs new file mode 100644 index 0000000..509d904 --- /dev/null +++ b/src/CosmosDbExplorer/Messages/OpenContainerMetricsViewMessage.cs @@ -0,0 +1,13 @@ +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.ViewModels.DatabaseNodes; + +namespace CosmosDbExplorer.Messages +{ + public class OpenContainerMetricsViewMessage : OpenTabMessageBase + { + public OpenContainerMetricsViewMessage(MetricsNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer container) + : base(node, connection, database, container) + { + } + } +} diff --git a/src/CosmosDbExplorer/Messages/OpenDocumentsViewMessage.cs b/src/CosmosDbExplorer/Messages/OpenDocumentsViewMessage.cs index 5d3178d..c263642 100644 --- a/src/CosmosDbExplorer/Messages/OpenDocumentsViewMessage.cs +++ b/src/CosmosDbExplorer/Messages/OpenDocumentsViewMessage.cs @@ -1,13 +1,12 @@ -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.ViewModel; -using Microsoft.Azure.Documents; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.ViewModels.DatabaseNodes; namespace CosmosDbExplorer.Messages { public class OpenDocumentsViewMessage : OpenTabMessageBase { - public OpenDocumentsViewMessage(DocumentNodeViewModel node, Connection connection, DocumentCollection collection) - : base(node, connection, collection) + public OpenDocumentsViewMessage(DocumentNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer container) + : base(node, connection, database, container) { } } diff --git a/src/CosmosDbExplorer/Messages/OpenImportDocumentViewMessage.cs b/src/CosmosDbExplorer/Messages/OpenImportDocumentViewMessage.cs index 7a3393e..a44d3b3 100644 --- a/src/CosmosDbExplorer/Messages/OpenImportDocumentViewMessage.cs +++ b/src/CosmosDbExplorer/Messages/OpenImportDocumentViewMessage.cs @@ -1,13 +1,12 @@ -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.ViewModel; -using Microsoft.Azure.Documents; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.ViewModels.DatabaseNodes; namespace CosmosDbExplorer.Messages { - public class OpenImportDocumentViewMessage : OpenTabMessageBase + public class OpenImportDocumentViewMessage : OpenTabMessageBase { - public OpenImportDocumentViewMessage(CollectionNodeViewModel node, Connection connection, DocumentCollection collection) - : base(node, connection, collection) + public OpenImportDocumentViewMessage(ContainerNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer container) + : base(node, connection, database, container) { } } diff --git a/src/CosmosDbExplorer/Messages/OpenMetricsViewMessage.cs b/src/CosmosDbExplorer/Messages/OpenMetricsViewMessage.cs new file mode 100644 index 0000000..6bd8009 --- /dev/null +++ b/src/CosmosDbExplorer/Messages/OpenMetricsViewMessage.cs @@ -0,0 +1,13 @@ +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.ViewModels.DatabaseNodes; + +namespace CosmosDbExplorer.Messages +{ + public class OpenMetricsViewMessage : OpenTabMessageBase + { + public OpenMetricsViewMessage(MetricsNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer? container) + : base(node, connection, database, container) + { + } + } +} diff --git a/src/CosmosDbExplorer/Messages/OpenQueryViewMessage.cs b/src/CosmosDbExplorer/Messages/OpenQueryViewMessage.cs index f417fb0..cc403cd 100644 --- a/src/CosmosDbExplorer/Messages/OpenQueryViewMessage.cs +++ b/src/CosmosDbExplorer/Messages/OpenQueryViewMessage.cs @@ -1,13 +1,12 @@ -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.ViewModel; -using Microsoft.Azure.Documents; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.ViewModels.DatabaseNodes; namespace CosmosDbExplorer.Messages { - public class OpenQueryViewMessage : OpenTabMessageBase + public class OpenQueryViewMessage : OpenTabMessageBase { - public OpenQueryViewMessage(CollectionNodeViewModel node, Connection connection, DocumentCollection collection) - : base(node, connection, collection) + public OpenQueryViewMessage(ContainerNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer container) + : base(node, connection, database, container) { } } diff --git a/src/CosmosDbExplorer/Messages/OpenScaleAndSettingsViewMessage.cs b/src/CosmosDbExplorer/Messages/OpenScaleAndSettingsViewMessage.cs index 516bbb6..11d887d 100644 --- a/src/CosmosDbExplorer/Messages/OpenScaleAndSettingsViewMessage.cs +++ b/src/CosmosDbExplorer/Messages/OpenScaleAndSettingsViewMessage.cs @@ -1,13 +1,20 @@ -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.ViewModel; -using Microsoft.Azure.Documents; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.ViewModels.DatabaseNodes; namespace CosmosDbExplorer.Messages { public class OpenScaleAndSettingsViewMessage : OpenTabMessageBase { - public OpenScaleAndSettingsViewMessage(ScaleSettingsNodeViewModel node, Connection connection, DocumentCollection collection) - : base(node, connection, collection) + public OpenScaleAndSettingsViewMessage(ScaleSettingsNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer container) + : base(node, connection, database, container) + { + } + } + + public class OpenDatabaseScaleViewMessage : OpenTabMessageBase + { + public OpenDatabaseScaleViewMessage(DatabaseScaleNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer container) + : base(node, connection, database, container) { } } diff --git a/src/CosmosDbExplorer/Messages/OpenTabMessageBase.cs b/src/CosmosDbExplorer/Messages/OpenTabMessageBase.cs new file mode 100644 index 0000000..c7082cd --- /dev/null +++ b/src/CosmosDbExplorer/Messages/OpenTabMessageBase.cs @@ -0,0 +1,23 @@ +using CosmosDbExplorer.Core.Models; + +namespace CosmosDbExplorer.Messages +{ + public abstract class OpenTabMessageBase + { + protected OpenTabMessageBase(TNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer? container) + { + Node = node; + Connection = connection; + Database = database; + Container = container; + } + + public TNodeViewModel? Node { get; } + + public CosmosConnection? Connection { get; } + + public CosmosDatabase? Database { get; } + + public CosmosContainer? Container { get; } + } +} diff --git a/src/CosmosDbExplorer/Messages/RemoveConnectionMessage.cs b/src/CosmosDbExplorer/Messages/RemoveConnectionMessage.cs index 7ae0e67..f71da5e 100644 --- a/src/CosmosDbExplorer/Messages/RemoveConnectionMessage.cs +++ b/src/CosmosDbExplorer/Messages/RemoveConnectionMessage.cs @@ -1,14 +1,14 @@ -using CosmosDbExplorer.Infrastructure.Models; +using CosmosDbExplorer.Core.Models; namespace CosmosDbExplorer.Messages { public class RemoveConnectionMessage { - public RemoveConnectionMessage(Connection connection) + public RemoveConnectionMessage(CosmosConnection connection) { Connection = connection; } - public Connection Connection { get; } + public CosmosConnection Connection { get; } } } diff --git a/src/CosmosDbExplorer/Messages/RemoveNodeMessage.cs b/src/CosmosDbExplorer/Messages/RemoveNodeMessage.cs index 11a6fd7..9063805 100644 --- a/src/CosmosDbExplorer/Messages/RemoveNodeMessage.cs +++ b/src/CosmosDbExplorer/Messages/RemoveNodeMessage.cs @@ -1,7 +1,4 @@ -using CosmosDbExplorer.Infrastructure.Models; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.Messages +namespace CosmosDbExplorer.Messages { public class RemoveNodeMessage diff --git a/src/CosmosDbExplorer/Messages/TreeNodeSelectedMessage.cs b/src/CosmosDbExplorer/Messages/TreeNodeSelectedMessage.cs index 52b8940..44816df 100644 --- a/src/CosmosDbExplorer/Messages/TreeNodeSelectedMessage.cs +++ b/src/CosmosDbExplorer/Messages/TreeNodeSelectedMessage.cs @@ -1,4 +1,4 @@ -using CosmosDbExplorer.Infrastructure.Models; +using CosmosDbExplorer.ViewModels; namespace CosmosDbExplorer.Messages { diff --git a/src/CosmosDbExplorer/Messages/UpdateOrCreateNodeMessage.cs b/src/CosmosDbExplorer/Messages/UpdateOrCreateNodeMessage.cs index f7009a6..165d890 100644 --- a/src/CosmosDbExplorer/Messages/UpdateOrCreateNodeMessage.cs +++ b/src/CosmosDbExplorer/Messages/UpdateOrCreateNodeMessage.cs @@ -1,25 +1,23 @@ -using CosmosDbExplorer.Infrastructure.Models; -using Microsoft.Azure.Documents; +using CosmosDbExplorer.Core.Contracts; namespace CosmosDbExplorer.Messages { - - public class UpdateOrCreateNodeMessage - where T: Resource + public class UpdateOrCreateNodeMessage + where TResource : ICosmosResource { - public UpdateOrCreateNodeMessage(T resource, DocumentCollection collection, string oldAltLink) + public UpdateOrCreateNodeMessage(TResource resource, TParent container, string? oldAltLink) { Resource = resource; OldAltLink = oldAltLink; - Collection = collection; + Parent = container; } - public T Resource { get; } + public TResource Resource { get; } public bool IsNewResource => string.IsNullOrEmpty(OldAltLink); - public string OldAltLink { get; } + public string? OldAltLink { get; } - public DocumentCollection Collection { get; } + public TParent Parent { get; } } } diff --git a/src/CosmosDbExplorer/Models/AppConfig.cs b/src/CosmosDbExplorer/Models/AppConfig.cs new file mode 100644 index 0000000..2a38ef9 --- /dev/null +++ b/src/CosmosDbExplorer/Models/AppConfig.cs @@ -0,0 +1,9 @@ +namespace CosmosDbExplorer.Models +{ + public class AppConfig + { + public string? AppPropertiesFileName { get; set; } + + public string? ConnectionsFileName { get; set; } + } +} diff --git a/src/CosmosDbExplorer/Models/AppTheme.cs b/src/CosmosDbExplorer/Models/AppTheme.cs new file mode 100644 index 0000000..179ebd9 --- /dev/null +++ b/src/CosmosDbExplorer/Models/AppTheme.cs @@ -0,0 +1,15 @@ +namespace CosmosDbExplorer.Models +{ + public enum AppTheme + { + Default, + Light, + Dark + } + + public enum DialogStyles + { + Default, + Metro + } +} diff --git a/src/CosmosDbExplorer/Models/CheckedItem.cs b/src/CosmosDbExplorer/Models/CheckedItem.cs new file mode 100644 index 0000000..72ddc66 --- /dev/null +++ b/src/CosmosDbExplorer/Models/CheckedItem.cs @@ -0,0 +1,17 @@ +using Microsoft.Toolkit.Mvvm.ComponentModel; + +namespace CosmosDbExplorer.Models +{ + public class CheckedItem : ObservableObject + { + public CheckedItem(T item, bool isChecked = false) + { + Item = item; + IsChecked = isChecked; + } + + public T Item { get; } + + public bool IsChecked { get; set; } + } +} diff --git a/src/CosmosDbExplorer/Models/NodeContext.cs b/src/CosmosDbExplorer/Models/NodeContext.cs new file mode 100644 index 0000000..d5e8cbd --- /dev/null +++ b/src/CosmosDbExplorer/Models/NodeContext.cs @@ -0,0 +1,30 @@ + +using CosmosDbExplorer.Core.Models; + +namespace CosmosDbExplorer.Models +{ + public class NodeContext + { + public NodeContext(CosmosConnection connection) + { + Connection = connection; + } + + public NodeContext(NodeContext context, CosmosDatabase database) + : this(context.Connection) + { + Database = database; + } + + public NodeContext(NodeContext context, CosmosContainer container) + : this(context, context.Database) + { + Container = container; + } + + public CosmosConnection Connection { get; } + public CosmosDatabase? Database { get; } + public CosmosContainer? Container { get; } + public object? Data { get; set; } + } +} diff --git a/src/CosmosDbExplorer/Models/StatusBarInfo.cs b/src/CosmosDbExplorer/Models/StatusBarInfo.cs new file mode 100644 index 0000000..5bf1134 --- /dev/null +++ b/src/CosmosDbExplorer/Models/StatusBarInfo.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Linq; +using CosmosDbExplorer.Core.Contracts; +using CosmosDbExplorer.Core.Models; +using Newtonsoft.Json.Linq; + +namespace CosmosDbExplorer.Models +{ + public class StatusBarInfo : IStatusBarInfo + { + //public StatusBarInfo(double? requestCharge, CosmosDocument resource, Dictionary responseHeaders) + //{ + // RequestCharge = requestCharge; + // Resource = long.Parse(resource.ite); + // ResponseHeaders = responseHeaders; + //} + + public StatusBarInfo(CosmosQueryResult response) + { + RequestCharge = response.RequestCharge; + Resource = response.Items; + ResponseHeaders = response.Headers; + } + + public StatusBarInfo(CosmosResult response) + { + RequestCharge = response.RequestCharge; + } + + public StatusBarInfo(IEnumerable>> response) + { + RequestCharge = response.Sum(r => r.RequestCharge); + Resource = null; + ResponseHeaders = null; + } + + public double RequestCharge { get; } + + public JObject? Resource { get; } + + public Dictionary? ResponseHeaders { get; } + } + + public interface IStatusBarInfo + { + double RequestCharge { get; } + + JObject? Resource { get; } + + Dictionary? ResponseHeaders { get; } + } +} diff --git a/src/CosmosDbExplorer/Infrastructure/Models/StatusBarItemType.cs b/src/CosmosDbExplorer/Models/StatusBarItemType.cs similarity index 71% rename from src/CosmosDbExplorer/Infrastructure/Models/StatusBarItemType.cs rename to src/CosmosDbExplorer/Models/StatusBarItemType.cs index 0da0b6f..c2e15c4 100644 --- a/src/CosmosDbExplorer/Infrastructure/Models/StatusBarItemType.cs +++ b/src/CosmosDbExplorer/Models/StatusBarItemType.cs @@ -1,12 +1,16 @@ -using System.Windows.Controls; -using System.Windows.Data; -using GalaSoft.MvvmLight; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Controls; +using Microsoft.Toolkit.Mvvm.ComponentModel; -namespace CosmosDbExplorer.Infrastructure.Models +namespace CosmosDbExplorer.Models { public class StatusBarItem : ObservableObject { - public StatusBarItem(StatusBarItemContext dataContext, StatusBarItemType type, string title = null, Dock dock = Dock.Left) + public StatusBarItem(StatusBarItemContext dataContext, StatusBarItemType type, string? title = null, Dock dock = Dock.Left) { DataContext = dataContext; Type = type; @@ -24,7 +28,7 @@ public class StatusBarItemContext : ObservableObject { public bool IsVisible { get; set; } - public object Value { get; set; } + public object? Value { get; set; } } public class StatusBarItemContextCancellableCommand : StatusBarItemContext diff --git a/src/CosmosDbExplorer/Properties/AssemblyInfo.cs b/src/CosmosDbExplorer/Properties/AssemblyInfo.cs deleted file mode 100644 index c441bd0..0000000 --- a/src/CosmosDbExplorer/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Windows; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Cosmos DB Explorer")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Cosmos DB Explorer")] -[assembly: AssemblyCopyright("Copyright © Sacha Bruttin")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -//In order to begin building localizable applications, set -//CultureYouAreCodingWith in your .csproj file -//inside a . For example, if you are using US english -//in your source files, set the to en-US. Then uncomment -//the NeutralResourceLanguage attribute below. Update the "en-US" in -//the line below to match the UICulture setting in the project file. - -//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] - - -[assembly: ThemeInfo( - ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located - //(used if a resource is not found in the page, - // or application resource dictionaries) - ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located - //(used if a resource is not found in the page, - // app, or any theme specific resource dictionaries) -)] - - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.8.2.1")] -[assembly: AssemblyFileVersion("0.8.2.1")] -[assembly: Guid("D7DCA337-E3E6-4ED9-8AA9-ACCBF9FF59BD")] - diff --git a/src/CosmosDbExplorer/Properties/Resources.Designer.cs b/src/CosmosDbExplorer/Properties/Resources.Designer.cs index 2091f11..d444d2f 100644 --- a/src/CosmosDbExplorer/Properties/Resources.Designer.cs +++ b/src/CosmosDbExplorer/Properties/Resources.Designer.cs @@ -19,10 +19,10 @@ namespace CosmosDbExplorer.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { + public class Resources { private static global::System.Resources.ResourceManager resourceMan; @@ -36,7 +36,7 @@ internal Resources() { /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CosmosDbExplorer.Properties.Resources", typeof(Resources).Assembly); @@ -51,7 +51,7 @@ internal Resources() { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -59,5 +59,176 @@ internal Resources() { resourceCulture = value; } } + + /// + /// Looks up a localized string similar to About. + /// + public static string AboutPageTitle { + get { + return ResourceManager.GetString("AboutPageTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cosmos DB Explorer. + /// + public static string AppDisplayName { + get { + return ResourceManager.GetString("AppDisplayName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to https://localhost:8081. + /// + public static string EmulatorEndpoint { + get { + return ResourceManager.GetString("EmulatorEndpoint", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==. + /// + public static string EmulatorSecret { + get { + return ResourceManager.GetString("EmulatorSecret", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Main. + /// + public static string MainPageTitle { + get { + return ResourceManager.GetString("MainPageTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Settings page placeholder text. Your app description goes here.. + /// + public static string SettingsPageAboutText { + get { + return ResourceManager.GetString("SettingsPageAboutText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to About this application. + /// + public static string SettingsPageAboutTitle { + get { + return ResourceManager.GetString("SettingsPageAboutTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Choose Theme. + /// + public static string SettingsPageChooseThemeText { + get { + return ResourceManager.GetString("SettingsPageChooseThemeText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Personalization. + /// + public static string SettingsPagePersonalizationTitle { + get { + return ResourceManager.GetString("SettingsPagePersonalizationTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Privacy Statement. + /// + public static string SettingsPagePrivacyStatementText { + get { + return ResourceManager.GetString("SettingsPagePrivacyStatementText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dark. + /// + public static string SettingsPageRadioButtonDarkTheme { + get { + return ResourceManager.GetString("SettingsPageRadioButtonDarkTheme", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Light. + /// + public static string SettingsPageRadioButtonLightTheme { + get { + return ResourceManager.GetString("SettingsPageRadioButtonLightTheme", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Default. + /// + public static string SettingsPageRadioButtonWindowsDefaultTheme { + get { + return ResourceManager.GetString("SettingsPageRadioButtonWindowsDefaultTheme", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Settings. + /// + public static string SettingsPageTitle { + get { + return ResourceManager.GetString("SettingsPageTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to About. + /// + public static string ShellPageRibbonBackstageTabAboutHeader { + get { + return ResourceManager.GetString("ShellPageRibbonBackstageTabAboutHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Settings. + /// + public static string ShellPageRibbonBackstageTabSettingsHeader { + get { + return ResourceManager.GetString("ShellPageRibbonBackstageTabSettingsHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Common Group 1. + /// + public static string ShellPageRibbonHomeGroupCommon1Header { + get { + return ResourceManager.GetString("ShellPageRibbonHomeGroupCommon1Header", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Common Group 2. + /// + public static string ShellPageRibbonHomeGroupCommon2Header { + get { + return ResourceManager.GetString("ShellPageRibbonHomeGroupCommon2Header", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Home. + /// + public static string ShellPageRibbonTabHomeHeader { + get { + return ResourceManager.GetString("ShellPageRibbonTabHomeHeader", resourceCulture); + } + } } } diff --git a/src/CosmosDbExplorer/Properties/Resources.resx b/src/CosmosDbExplorer/Properties/Resources.resx index af7dbeb..0e26bfb 100644 --- a/src/CosmosDbExplorer/Properties/Resources.resx +++ b/src/CosmosDbExplorer/Properties/Resources.resx @@ -1,4 +1,4 @@ - + + @@ -68,9 +69,10 @@ - + + @@ -85,9 +87,10 @@ - + + @@ -109,9 +112,66 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Settings + + + Settings + + + Settings page placeholder text. Your app description goes here. + + + About this application + + + Choose Theme + + + Personalization + + + Privacy Statement + + + Dark + + + Light + + + Default + + + Main + + + Common Group 1 + + + Common Group 2 + + + Home + + + About + + + Cosmos DB Explorer + + + About + + + https://localhost:8081 + + + C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw== + \ No newline at end of file diff --git a/src/CosmosDbExplorer/Properties/Settings.Designer.cs b/src/CosmosDbExplorer/Properties/Settings.Designer.cs index fd03e80..af4fb8d 100644 --- a/src/CosmosDbExplorer/Properties/Settings.Designer.cs +++ b/src/CosmosDbExplorer/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace CosmosDbExplorer.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.2.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -25,31 +25,37 @@ public static Settings Default { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("20")] - public int MaxDocumentToRetrieve { + [global::System.Configuration.DefaultSettingValueAttribute("Consolas")] + public string EditorFontName { get { - return ((int)(this["MaxDocumentToRetrieve"])); + return ((string)(this["EditorFontName"])); } set { - this["MaxDocumentToRetrieve"] = value; + this["EditorFontName"] = value; } } - [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("https://www.bruttin.com/CosmosDbExplorer/autoupdate.json")] - public string AutoUpdaterUrl { + [global::System.Configuration.DefaultSettingValueAttribute("11")] + public int EditorFontSize { get { - return ((string)(this["AutoUpdaterUrl"])); + return ((int)(this["EditorFontSize"])); + } + set { + this["EditorFontSize"] = value; } } - [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("300")] - public int AutoUpdaterIntervalInSeconds { + [global::System.Configuration.DefaultSettingValueAttribute("Default")] + public string Theme { get { - return ((int)(this["AutoUpdaterIntervalInSeconds"])); + return ((string)(this["Theme"])); + } + set { + this["Theme"] = value; } } @@ -64,7 +70,74 @@ public string ExportFolder { this["ExportFolder"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("50")] + public int MaxDocumentToRetrieve { + get { + return ((int)(this["MaxDocumentToRetrieve"])); + } + set { + this["MaxDocumentToRetrieve"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Metro")] + public string DialogService { + get { + return ((string)(this["DialogService"])); + } + set { + this["DialogService"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("F5")] + public string ExecuteGesture { + get { + return ((string)(this["ExecuteGesture"])); + } + set { + this["ExecuteGesture"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowLineNumbers { + get { + return ((bool)(this["ShowLineNumbers"])); + } + set { + this["ShowLineNumbers"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool EnableWordWrap { + get { + return ((bool)(this["EnableWordWrap"])); + } + set { + this["EnableWordWrap"] = value; + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("https://www.bruttin.com/CosmosDbExplorer/autoupdate.json")] + public string AutoUpdaterUrl { + get { + return ((string)(this["AutoUpdaterUrl"])); + } + } } - - } diff --git a/src/CosmosDbExplorer/Properties/Settings.Extensions.cs b/src/CosmosDbExplorer/Properties/Settings.Extensions.cs index c57f104..aed95b9 100644 --- a/src/CosmosDbExplorer/Properties/Settings.Extensions.cs +++ b/src/CosmosDbExplorer/Properties/Settings.Extensions.cs @@ -7,7 +7,7 @@ namespace CosmosDbExplorer.Properties { internal sealed partial class Settings - { + { public string GetExportFolder() { if (string.IsNullOrEmpty(Default.ExportFolder) || string.IsNullOrWhiteSpace(Default.ExportFolder)) diff --git a/src/CosmosDbExplorer/Properties/Settings.settings b/src/CosmosDbExplorer/Properties/Settings.settings index 67a2ba7..82dd608 100644 --- a/src/CosmosDbExplorer/Properties/Settings.settings +++ b/src/CosmosDbExplorer/Properties/Settings.settings @@ -2,17 +2,35 @@ - - 20 + + Consolas - - https://www.bruttin.com/CosmosDbExplorer/autoupdate.json + + 11 - - 300 + + Default + + 50 + + + Metro + + + F5 + + + True + + + False + + + https://www.bruttin.com/CosmosDbExplorer/autoupdate.json + \ No newline at end of file diff --git a/src/CosmosDbExplorer/Properties/app.manifest b/src/CosmosDbExplorer/Properties/app.manifest deleted file mode 100644 index 65e0b09..0000000 --- a/src/CosmosDbExplorer/Properties/app.manifest +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/CosmosDbExplorer/Services/ApplicationHostService.cs b/src/CosmosDbExplorer/Services/ApplicationHostService.cs new file mode 100644 index 0000000..77c8af9 --- /dev/null +++ b/src/CosmosDbExplorer/Services/ApplicationHostService.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using CosmosDbExplorer.Contracts.Activation; +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.Views; +using CosmosDbExplorer.ViewModels; + +using Microsoft.Extensions.Hosting; + +namespace CosmosDbExplorer.Services +{ + public class ApplicationHostService : IHostedService + { + private readonly IServiceProvider _serviceProvider; + private readonly INavigationService _navigationService; + private readonly IPersistAndRestoreService _persistAndRestoreService; + private readonly IThemeSelectorService _themeSelectorService; + private readonly IRightPaneService _rightPaneService; + + private readonly IEnumerable _activationHandlers; + private IShellWindow _shellWindow; + private bool _isInitialized; + + public ApplicationHostService(IServiceProvider serviceProvider, IEnumerable activationHandlers, INavigationService navigationService, IRightPaneService rightPaneService, IThemeSelectorService themeSelectorService, IPersistAndRestoreService persistAndRestoreService) + { + _serviceProvider = serviceProvider; + _activationHandlers = activationHandlers; + _navigationService = navigationService; + _rightPaneService = rightPaneService; + _themeSelectorService = themeSelectorService; + _persistAndRestoreService = persistAndRestoreService; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + // Initialize services that you need before app activation + await InitializeAsync(); + + await HandleActivationAsync(); + + // Tasks after activation + await StartupAsync(); + _isInitialized = true; + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + _persistAndRestoreService.PersistData(); + await Task.CompletedTask; + } + + private async Task InitializeAsync() + { + if (!_isInitialized) + { + _persistAndRestoreService.RestoreData(); + _themeSelectorService.InitializeTheme(); + await Task.CompletedTask; + } + } + + private async Task StartupAsync() + { + if (!_isInitialized) + { + await Task.CompletedTask; + } + } + + private async Task HandleActivationAsync() + { + var activationHandler = _activationHandlers.FirstOrDefault(h => h.CanHandle()); + + if (activationHandler != null) + { + await activationHandler.HandleAsync(); + } + + await Task.CompletedTask; + + if (App.Current.Windows.OfType().Count() == 0) + { + // Default activation that navigates to the apps default page + _shellWindow = _serviceProvider.GetService(typeof(IShellWindow)) as IShellWindow; + //_navigationService.Initialize(_shellWindow.GetNavigationFrame()); + _rightPaneService.Initialize(_shellWindow.GetRightPaneFrame(), _shellWindow.GetSplitView()); + _shellWindow.ShowWindow(); + //_navigationService.NavigateTo(typeof(MainViewModel).FullName); + await Task.CompletedTask; + } + } + } +} diff --git a/src/CosmosDbExplorer/Services/ApplicationInfoService.cs b/src/CosmosDbExplorer/Services/ApplicationInfoService.cs new file mode 100644 index 0000000..54d19d2 --- /dev/null +++ b/src/CosmosDbExplorer/Services/ApplicationInfoService.cs @@ -0,0 +1,26 @@ +using System; +using System.Diagnostics; +using System.Reflection; + +using CosmosDbExplorer.Contracts.Services; + +namespace CosmosDbExplorer.Services +{ + public class ApplicationInfoService : IApplicationInfoService + { + + public ApplicationInfoService() + { + } + + public Version GetVersion() + { + // Set the app version in CosmosDbExplorer > Properties > Package > PackageVersion + var assemblyLocation = Assembly.GetExecutingAssembly().Location; + var version = FileVersionInfo.GetVersionInfo(assemblyLocation).FileVersion; + return new Version(version); + } + + + } +} diff --git a/src/CosmosDbExplorer/Services/DialogService.cs b/src/CosmosDbExplorer/Services/DialogService.cs index 8dc27eb..829d390 100644 --- a/src/CosmosDbExplorer/Services/DialogService.cs +++ b/src/CosmosDbExplorer/Services/DialogService.cs @@ -1,65 +1,20 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; using System.Threading.Tasks; using System.Windows; +using CosmosDbExplorer.Contracts.Services; using CosmosDbExplorer.Services.DialogSettings; -using GalaSoft.MvvmLight.Threading; +using MahApps.Metro.Controls; +using MahApps.Metro.Controls.Dialogs; using Microsoft.Win32; namespace CosmosDbExplorer.Services { - public interface IDialogService : GalaSoft.MvvmLight.Views.IDialogService + public class FileDialogService : IFileDialogService { - Task ShowSaveFileDialog(SaveFileDialogSettings settings, Action afterHideCallback); - Task ShowFolderBrowserDialog(FolderBrowserDialogSettings settings, Action afterHideCallback); - Task ShowOpenFileDialog(OpenFileDialogSettings settings, Action afterHideCallback); - } - - public class DialogService : IDialogService - { - public Task ShowError(string message, string title, string buttonText, Action afterHideCallback) - { - DispatcherHelper.RunAsync(() => MessageBox.Show(Application.Current.MainWindow, message, title, MessageBoxButton.OK, MessageBoxImage.Error)); - return Task.Run(() => afterHideCallback); - } - - public Task ShowError(Exception error, string title, string buttonText, Action afterHideCallback) - { - DispatcherHelper.RunAsync(() => MessageBox.Show(Application.Current.MainWindow, error.GetBaseException().Message, title, MessageBoxButton.OK, MessageBoxImage.Error)); - return Task.Run(() => afterHideCallback); - } - - public Task ShowMessage(string message, string title) - { - DispatcherHelper.RunAsync(() => MessageBox.Show(Application.Current.MainWindow, message, title, MessageBoxButton.OK, MessageBoxImage.Information)); - return null; - } - - public Task ShowMessage(string message, string title, string buttonText, Action afterHideCallback) - { - DispatcherHelper.RunAsync(() => MessageBox.Show(Application.Current.MainWindow, message, title, MessageBoxButton.OK, MessageBoxImage.Information)); - return Task.Run(() => afterHideCallback); - } - - public async Task ShowMessage(string message, string title, string buttonConfirmText, string buttonCancelText, Action afterHideCallback) - { - var confirmed = false; - await DispatcherHelper.RunAsync(() => - { - var result = MessageBox.Show(Application.Current.MainWindow, message, title, MessageBoxButton.YesNo, MessageBoxImage.Question); - confirmed = result == MessageBoxResult.Yes; - }); - - afterHideCallback(confirmed); - return confirmed; - } - - public Task ShowMessageBox(string message, string title) - { - DispatcherHelper.RunAsync(() => MessageBox.Show(Application.Current.MainWindow, message, title, MessageBoxButton.OK, MessageBoxImage.Information)); - return Task.FromResult(0); - } - - public Task ShowFolderBrowserDialog(FolderBrowserDialogSettings settings, Action afterHideCallback) + public void ShowFolderBrowserDialog(FolderBrowserDialogSettings settings, Action? afterHideCallback) { var dialog = new System.Windows.Forms.FolderBrowserDialog { @@ -68,13 +23,13 @@ public Task ShowFolderBrowserDialog(FolderBrowserDialogSettings settings, Description = settings.Description }; - var result = dialog.ShowDialog(/* TODO: Get Handle */); + var result = dialog.ShowDialog();/* TODO: Get Handle */ var confirmed = result == System.Windows.Forms.DialogResult.OK; - return Task.Run(() => { afterHideCallback(confirmed, new FolderDialogResult(dialog.SelectedPath)); return confirmed; }); + afterHideCallback?.Invoke(confirmed, new FolderDialogResult(dialog.SelectedPath)); } - public Task ShowOpenFileDialog(OpenFileDialogSettings settings, Action afterHideCallback) + public void ShowOpenFileDialog(OpenFileDialogSettings settings, Action? afterHideCallback = null) { var dialog = new OpenFileDialog { @@ -93,10 +48,10 @@ public Task ShowOpenFileDialog(OpenFileDialogSettings settings, Action { afterHideCallback(confirmed, new FileDialogResult(dialog.FileName, dialog.FileNames)); return confirmed; }); + afterHideCallback?.Invoke(confirmed, new FileDialogResult(dialog.FileName, dialog.FileNames)); } - public Task ShowSaveFileDialog(SaveFileDialogSettings settings, Action afterHideCallback) + public void ShowSaveFileDialog(SaveFileDialogSettings settings, Action? afterHideCallback = null) { var dialog = new SaveFileDialog { @@ -116,7 +71,72 @@ public Task ShowSaveFileDialog(SaveFileDialogSettings settings, Action { afterHideCallback(confirmed, new FileDialogResult(dialog.FileName, dialog.FileNames)); return confirmed; }); + afterHideCallback?.Invoke(confirmed, new FileDialogResult(dialog.FileName, dialog.FileNames)); + } + } + + public class MetroDialogService : FileDialogService, IDialogService + { + private static MetroWindow MainWindow => (MetroWindow)Application.Current.MainWindow; + + public async Task ShowError(string message, string title, Action? afterHideCallback = null) + { + var settings = new MetroDialogSettings { ColorScheme = MetroDialogColorScheme.Theme }; + var result = await MainWindow.ShowMessageAsync(title, message, MessageDialogStyle.Affirmative, settings); + afterHideCallback?.Invoke(); + } + + public Task ShowError(Exception error, string title, Action? afterHideCallback = null) + { + return ShowError(error.Message, title, afterHideCallback); + } + + public Task ShowMessage(string message, string title, Action? afterHideCallback = null) + { + return ShowError(message, title, afterHideCallback); + } + + public async Task ShowQuestion(string message, string title, Action? afterHideCallback = null) + { + var settings = new MetroDialogSettings + { + ColorScheme = MetroDialogColorScheme.Theme, + DefaultButtonFocus = MessageDialogResult.Negative, + NegativeButtonText = "NO", + AffirmativeButtonText = "YES" + }; + + var result = await MainWindow.ShowMessageAsync(title, message, MessageDialogStyle.AffirmativeAndNegative, settings); + afterHideCallback?.Invoke(result == MessageDialogResult.Affirmative); + } + } + + public class DialogService : FileDialogService, IDialogService + { + public Task ShowError(string message, string title, Action? afterHideCallback = null) + { + MessageBox.Show(Application.Current.MainWindow, message, title, MessageBoxButton.OK, MessageBoxImage.Error); + afterHideCallback?.Invoke(); + return Task.CompletedTask; + } + + public Task ShowError(Exception error, string title, Action? afterHideCallback = null) + { + return ShowError(error.Message, title, afterHideCallback); + } + + public Task ShowMessage(string message, string title, Action? afterHideCallback = null) + { + MessageBox.Show(Application.Current.MainWindow, message, title, MessageBoxButton.OK, MessageBoxImage.Information); + afterHideCallback?.Invoke(); + return Task.CompletedTask; + } + + public Task ShowQuestion(string message, string title, Action? afterHideCallback = null) + { + var result = MessageBox.Show(Application.Current.MainWindow, message, title, MessageBoxButton.YesNo, MessageBoxImage.Question); + afterHideCallback?.Invoke(result == MessageBoxResult.Yes); + return Task.CompletedTask; } } } diff --git a/src/CosmosDbExplorer/Services/DialogSettings/FolderBrowserDialogSettings.cs b/src/CosmosDbExplorer/Services/DialogSettings/FolderBrowserDialogSettings.cs index 4c8980f..ff7e96c 100644 --- a/src/CosmosDbExplorer/Services/DialogSettings/FolderBrowserDialogSettings.cs +++ b/src/CosmosDbExplorer/Services/DialogSettings/FolderBrowserDialogSettings.cs @@ -6,12 +6,12 @@ public class FolderBrowserDialogSettings /// Gets or sets the descriptive text displayed above the tree view control in the dialog /// box. /// - public string Description { get; set; } + public string? Description { get; set; } /// /// Gets or sets the path selected by the user. /// - public string SelectedPath { get; set; } + public string? SelectedPath { get; set; } /// /// Gets or sets a value indicating whether the New Folder button appears in the folder diff --git a/src/CosmosDbExplorer/Services/DocumentDbService.cs b/src/CosmosDbExplorer/Services/DocumentDbService.cs deleted file mode 100644 index 0644d89..0000000 --- a/src/CosmosDbExplorer/Services/DocumentDbService.cs +++ /dev/null @@ -1,583 +0,0 @@ -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Azure.Documents.Linq; -using Newtonsoft.Json.Linq; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.ViewModel.Interfaces; -using GalaSoft.MvvmLight.Messaging; -using CosmosDbExplorer.Messages; -using System.Threading; -using CosmosDbExplorer.Infrastructure.Extensions; -using System.IO; -using Newtonsoft.Json; - -namespace CosmosDbExplorer.Services -{ - public class DocumentDbService : IDocumentDbService - { - public DocumentDbService(IMessenger messenger) - { - messenger.Register(this, msg => - { - if (ClientInstances.ContainsKey(msg.Connection)) - { - ClientInstances.Remove(msg.Connection); - } - }); - } - - private static readonly Dictionary ClientInstances = new Dictionary(); - - private DocumentClient GetClient(Connection connection) - { - if (!ClientInstances.ContainsKey(connection)) - { - var policy = new ConnectionPolicy - { - ConnectionMode = connection.ConnectionType == ConnectionType.Gateway ? ConnectionMode.Gateway : ConnectionMode.Direct, - ConnectionProtocol = connection.ConnectionType == ConnectionType.DirectHttps ? Protocol.Https : Protocol.Tcp, - EnableEndpointDiscovery =connection.EnableEndpointDiscovery, - }; - - var client = new DocumentClient(connection.DatabaseUri, connection.AuthenticationKey, policy); - client.OpenAsync(); - - ClientInstances.Add(connection, client); - } - - return ClientInstances[connection]; - } - - public async Task CleanCollectionAsync(Connection connection, DocumentCollection collection) - { - const string sqlQuery = "SELECT * FROM c"; - var client = GetClient(connection); - var feedOptions = new FeedOptions { EnableCrossPartitionQuery = true, MaxItemCount = 500 }; - var results = client.CreateDocumentQuery(collection.DocumentsLink, sqlQuery, feedOptions).AsDocumentQuery(); - var partitionKeyPath = collection.PartitionKey.GetSelectToken(); - - //While there are more results - while (results.HasMoreResults) - { - var tasks = new List(); - - //enumerate and delete the documents in this batch - foreach (Document doc in await results.ExecuteNextAsync().ConfigureAwait(false)) - { - var requestOptions = new RequestOptions(); - if (partitionKeyPath != null) - { - requestOptions.PartitionKey = new PartitionKey(doc.GetPartitionKeyValue(partitionKeyPath)); - } - - tasks.Add(client.DeleteDocumentAsync(doc.SelfLink, requestOptions)); - //await client.DeleteDocumentAsync(doc.SelfLink, requestOptions).ConfigureAwait(false); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); - } - } - - public async Task RecreateCollectionAsync(Connection connection, Database database, DocumentCollection collection) - { - var throughput = await GetThroughputAsync(connection, collection).ConfigureAwait(false); - - // Copy StoredProcedure, Triggers, UDFs - var storeProcedures = await GetStoredProceduresAsync(connection, collection).ConfigureAwait(false); - var triggers = await GetTriggersAsync(connection, collection).ConfigureAwait(false); - var udfs = await GetUdfsAsync(connection, collection).ConfigureAwait(false); - - // Delete existing collection - await DeleteCollectionAsync(connection, collection).ConfigureAwait(false); - - // Create new collection object - var duplicate = new DocumentCollection - { - ConflictResolutionPolicy = collection.ConflictResolutionPolicy, - DefaultTimeToLive = collection.DefaultTimeToLive, - Id = collection.Id, - IndexingPolicy = collection.IndexingPolicy, - PartitionKey = collection.PartitionKey, - UniqueKeyPolicy = collection.UniqueKeyPolicy - }; - - var result = await CreateCollectionAsync(connection, database, duplicate, throughput).ConfigureAwait(false); - - // Add StoredProcedure, Triggers, UDFs - var tasks = new List(); - tasks.AddRange(storeProcedures.Select(sp => SaveStoredProcedureAsync(connection, result, sp.Id, sp.Body, null))); - tasks.AddRange(triggers.Select(t => SaveTriggerAsync(connection, result, t.Id, t.Body, t.TriggerType, t.TriggerOperation, null))); - tasks.AddRange(udfs.Select(udf => SaveUdfAsync(connection, result, udf.Id, udf.Body, null))); - - await Task.WhenAll(tasks); - - return result; - } - - public async Task>> DeleteDocumentsAsync(Connection connection, IEnumerable documents) - { - var client = GetClient(connection); - var tasks = documents.Select(doc => DeleteDocumentAsync(client, doc)).ToList(); - - await Task.WhenAll(tasks).ConfigureAwait(false); - - return tasks.Select(t => t.Result); - } - - private Task> DeleteDocumentAsync(DocumentClient client, DocumentDescription document) - { - var options = document.PartitionKey != null - ? new RequestOptions { PartitionKey = new PartitionKey(document.PartitionKey) } - : new RequestOptions(); - - return client.DeleteDocumentAsync(document.SelfLink, options); - } - - public Task> ExecuteQueryAsync(Connection connection, DocumentCollection collection, string query, IHaveQuerySettings querySettings, string continuationToken, CancellationToken cancellationToken) - { - var feedOptions = new FeedOptions - { - EnableCrossPartitionQuery = querySettings.EnableCrossPartitionQuery.GetValueOrDefault(), - EnableScanInQuery = querySettings.EnableScanInQuery, - MaxItemCount = querySettings.MaxItemCount, - MaxDegreeOfParallelism = querySettings.MaxDOP.GetValueOrDefault(-1), - MaxBufferedItemCount = querySettings.MaxBufferItem.GetValueOrDefault(-1), - JsonSerializerSettings = new JsonSerializerSettings { DateParseHandling = DateParseHandling.None }, - RequestContinuation = continuationToken, - PopulateQueryMetrics = true, - PartitionKey = GetPartitionKey(querySettings.PartitionKeyValue) - }; - - return GetClient(connection) - .CreateDocumentQuery(collection.DocumentsLink, query, feedOptions) - .AsDocumentQuery() - .ExecuteNextAsync(cancellationToken); - } - - public async Task> GetCollectionsAsync(Connection connection, Database database) - { - var client = GetClient(connection); - - var result = await client.ReadDocumentCollectionFeedAsync(database.SelfLink).ConfigureAwait(false); - return result.ToList(); - } - - public async Task> GetDatabasesAsync(Connection connection) - { - var client = GetClient(connection); - var result = await client.ReadDatabaseFeedAsync().ConfigureAwait(false); - - return result.ToList(); - } - - public Task> GetDocumentAsync(Connection connection, DocumentDescription document) - { - var options = new RequestOptions { JsonSerializerSettings = new JsonSerializerSettings { DateParseHandling = DateParseHandling.None } }; - - if (document.HasPartitionKey) - { - options.PartitionKey = new PartitionKey(document.PartitionKey ?? Undefined.Value); - } - - return GetClient(connection).ReadDocumentAsync(document.SelfLink, options); - } - - public async Task GetDocumentsAsync(Connection connection, DocumentCollection collection, string filter, int maxItems, string continuationToken, IHaveRequestOptions requestOptions) - { - var token = collection.PartitionKey.GetQueryToken(); - if (token != null) - { - token = $", c{token} as _partitionKey, true as _hasPartitionKey"; - } - - var sql = $"SELECT c.id, c._self {token} FROM c {filter}"; - - var feedOptions = new FeedOptions - { - MaxItemCount = maxItems, - RequestContinuation = continuationToken, - EnableCrossPartitionQuery = true, - EnableScanInQuery = false, - PartitionKey = GetPartitionKey(requestOptions?.PartitionKeyValue), - JsonSerializerSettings = new JsonSerializerSettings { DateParseHandling = DateParseHandling.None }, - MaxDegreeOfParallelism = -1, - MaxBufferedItemCount = -1, - PopulateQueryMetrics = true, - }; - - var data = await GetClient(connection) - .CreateDocumentQuery(collection.DocumentsLink, sql, feedOptions) - .AsDocumentQuery() - .ExecuteNextAsync().ConfigureAwait(true); - - return new DocumentDescriptionList(data.ToList()) - { - ContinuationToken = data.ResponseContinuation, - RequestCharge = data.RequestCharge - }; - } - - public async Task ImportDocumentAsync(Connection connection, DocumentCollection collection, string content, IHaveRequestOptions requestOptions, CancellationToken cancellationToken) - { - var documents = GetDocuments(content).ToList(); - var options = GetRequestOptions(requestOptions); - - foreach (var document in documents) - { - if (cancellationToken.IsCancellationRequested) - { - cancellationToken.ThrowIfCancellationRequested(); - } - - await GetClient(connection).UpsertDocumentAsync(collection.SelfLink, document, options, disableAutomaticIdGeneration: true).ConfigureAwait(false); - } - - return documents.Count; - } - - private static RequestOptions GetRequestOptions(IHaveRequestOptions request) - { - return new RequestOptions - { - ConsistencyLevel = request.ConsistencyLevel, - IndexingDirective = request.IndexingDirective, - PreTriggerInclude = request.PreTrigger != null ? new List { request.PreTrigger } : null, - PostTriggerInclude = request.PreTrigger != null ? new List { request.PostTrigger } : null, - PartitionKey = GetPartitionKey(request.PartitionKeyValue), - AccessCondition = request.AccessConditionType != null ? new AccessCondition { Condition = request.AccessCondition, Type = request.AccessConditionType.Value } : null, - JsonSerializerSettings = new JsonSerializerSettings { DateParseHandling = DateParseHandling.None } - }; - } - - private static PartitionKey GetPartitionKey(string partitionKeyValue) - { - if (string.IsNullOrEmpty(partitionKeyValue?.Trim())) - { - return null; - } - - var value = JToken.Parse(partitionKeyValue).ToObject(); - return new PartitionKey(value); - } - - private IEnumerable GetDocuments(string content) - { - var token = JToken.Parse(content); - - if (token is JArray) - { - return token.ToObject>(); - } - else - { - return new[] { token.ToObject() }; - } - } - - public Task> UpdateDocumentAsync(Connection connection, string altLink, string content, IHaveRequestOptions requestOptions) - { - var instance = Parse(content); - var options = GetRequestOptions(requestOptions); - return GetClient(connection).UpsertDocumentAsync(altLink, instance, options); - } - - public async Task SaveStoredProcedureAsync(Connection connection, DocumentCollection collection, string id, string function, string storeProcedureLink) - { - var item = new StoredProcedure { Id = id, Body = function }; - - if (!string.IsNullOrEmpty(storeProcedureLink)) - { - var oldId = storeProcedureLink.Split('/').Last(); - - if (item.Id != oldId) - { - var itemList = await GetStoredProceduresAsync(connection, collection).ConfigureAwait(false); - if (itemList.Any(t => t.Id == item.Id)) - { - throw new Exception("An item with the same id already exists!"); - } - } - - await GetClient(connection).DeleteStoredProcedureAsync(storeProcedureLink).ConfigureAwait(false); - } - - var response = await GetClient(connection).CreateStoredProcedureAsync(collection.SelfLink, item).ConfigureAwait(false); - return response.Resource; - } - - public async Task SaveUdfAsync(Connection connection, DocumentCollection collection, string id, string function, string altLink) - { - var item = new UserDefinedFunction { Id = id, Body = function }; - var client = GetClient(connection); - - if (!string.IsNullOrEmpty(altLink)) - { - var oldId = altLink.Split('/').Last(); - - if (item.Id != oldId) - { - var itemList = await GetUdfsAsync(connection, collection).ConfigureAwait(false); - if (itemList.Any(t => t.Id == item.Id)) - { - throw new Exception("An item with the same id already exists!"); - } - } - - await client.DeleteUserDefinedFunctionAsync(altLink).ConfigureAwait(false); - } - - var response = await client.CreateUserDefinedFunctionAsync(collection.SelfLink, item).ConfigureAwait(false); - return response.Resource; - } - - public Task DeleteStoredProcedureAsync(Connection connection, string storedProcedureLink) - { - return GetClient(connection).DeleteStoredProcedureAsync(storedProcedureLink); - } - - public Task DeleteUdfAsync(Connection connection, string udfLink) - { - return GetClient(connection).DeleteUserDefinedFunctionAsync(udfLink); - } - - public async Task> GetStoredProceduresAsync(Connection connection, DocumentCollection collection) - { - var response = await GetClient(connection).ReadStoredProcedureFeedAsync(collection.StoredProceduresLink).ConfigureAwait(false); - return response.Select(sp => sp).ToList(); - } - - public async Task> GetUdfsAsync(Connection connection, DocumentCollection collection) - { - var response = await GetClient(connection).ReadUserDefinedFunctionFeedAsync(collection.UserDefinedFunctionsLink).ConfigureAwait(false); - return response.Select(sp => sp).ToList(); - } - - public async Task> GetTriggersAsync(Connection connection, DocumentCollection collection) - { - var response = await GetClient(connection).ReadTriggerFeedAsync(collection.TriggersLink).ConfigureAwait(false); - return response.Select(sp => sp).ToList(); - } - - public async Task SaveTriggerAsync(Connection connection, DocumentCollection collection, string id, string trigger, TriggerType triggerType, TriggerOperation triggerOperation, string altLink) - { - var item = new Trigger { Id = id, Body = trigger, TriggerType = triggerType, TriggerOperation = triggerOperation }; - var client = GetClient(connection); - - if (!string.IsNullOrEmpty(altLink)) - { - var oldId = altLink.Split('/').Last(); - - if (item.Id != oldId) - { - var itemList = await GetTriggersAsync(connection, collection).ConfigureAwait(false); - if (itemList.Any(t => t.Id == item.Id)) - { - throw new Exception("An item with the same id already exists!"); - } - } - - await client.DeleteTriggerAsync(altLink).ConfigureAwait(false); - } - - var response = await client.CreateTriggerAsync(collection.SelfLink, item).ConfigureAwait(false); - return response.Resource; - } - - public Task DeleteTriggerAsync(Connection connection, string triggerLink) - { - return GetClient(connection).DeleteTriggerAsync(triggerLink); - } - - public async Task GetThroughputAsync(Connection connection, DocumentCollection collection) - { - var query = GetClient(connection).CreateOfferQuery().Where(o => o.ResourceLink == collection.SelfLink).AsDocumentQuery(); - - var result = await query.ExecuteNextAsync().ConfigureAwait(false); - var offer = result.Single(); - - return offer.Content.OfferThroughput; - } - - public async Task UpdateCollectionSettingsAsync(Connection connection, DocumentCollection collection, int throughput) - { - var tasks = new[] - { - GetClient(connection).ReplaceDocumentCollectionAsync(collection), - UpdateOfferThroughput(connection, collection, throughput) - }; - - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - private async Task UpdateOfferThroughput(Connection connection, DocumentCollection collection, int throughtput) - { - var client = GetClient(connection); - var query = client.CreateOfferQuery().Where(o => o.ResourceLink == collection.SelfLink).AsDocumentQuery(); - - var result = await query.ExecuteNextAsync().ConfigureAwait(false); - var offer = result.Single(); - - if (offer.Content.OfferThroughput != throughtput) - { - offer = new OfferV2(offer, throughtput); - await client.ReplaceOfferAsync(offer).ConfigureAwait(false); - } - } - - public async Task CreateCollectionAsync(Connection connection, Database database, DocumentCollection collection, int throughput) - { - var client = GetClient(connection); - var options = new RequestOptions { OfferThroughput = throughput }; - - if (database.SelfLink == null) - { - database = await client.CreateDatabaseIfNotExistsAsync(database).ConfigureAwait(false); - } - - return await client.CreateDocumentCollectionAsync(database.SelfLink, collection, options).ConfigureAwait(false); - } - - public Task DeleteCollectionAsync(Connection connection, DocumentCollection collection) - { - return GetClient(connection).DeleteDocumentCollectionAsync(collection.SelfLink); - } - - public Task DeleteDatabaseAsync(Connection connection, Database database) - { - return GetClient(connection).DeleteDatabaseAsync(database.SelfLink); - } - - public async Task> GetUsersAsync(Connection connection, Database database) - { - var response = await GetClient(connection).ReadUserFeedAsync(database.UsersLink).ConfigureAwait(false); - return response.Select(u => u).OrderBy(u => u.Id).ToList(); - } - - public async Task SaveUserAsync(Connection connection, Database database, User user) - { - if (user.SelfLink != null) - { - var response = await GetClient(connection).ReplaceUserAsync(user).ConfigureAwait(false); - return response.Resource; - } - else - { - var response = await GetClient(connection).CreateUserAsync(database.SelfLink, user).ConfigureAwait(false); - return response.Resource; - } - } - - public Task DeleteUserAsync(Connection connection, User user) - { - return GetClient(connection).DeleteUserAsync(user.SelfLink); - } - - public async Task> GetPermissionAsync(Connection connection, User user) - { - var response = await GetClient(connection).ReadPermissionFeedAsync(user.PermissionsLink).ConfigureAwait(false); - return response.Select(p => p).OrderBy(p => p.Id).ToList(); - } - - public async Task SavePermissionAsync(Connection connection, User user, Permission permission) - { - if (permission.SelfLink != null) - { - return await GetClient(connection).ReplacePermissionAsync(permission).ConfigureAwait(false); - } - - return await GetClient(connection).CreatePermissionAsync(user.SelfLink, permission).ConfigureAwait(false); - } - - public Task DeletePermissionAsync(Connection connection, Permission permission) - { - return GetClient(connection).DeletePermissionAsync(permission.SelfLink); - } - - public async Task GetPartitionKeyRangeCountAsync(Connection connection, DocumentCollection collection) - { - var metrics = await GetPartitionMetricsAsync(connection, collection).ConfigureAwait(false); - return metrics.PartitionCount; - } - - public async Task GetPartitionMetricsAsync(Connection connection, DocumentCollection collection) - { - var documentCollection = await GetClient(connection).ReadDocumentCollectionAsync(collection.AltLink, - new RequestOptions - { - PopulateQuotaInfo = true, - PopulatePartitionKeyRangeStatistics = true, - }).ConfigureAwait(false); - - return new CollectionMetric(documentCollection); - } - - public async Task> GetTopPartitionKeys(Connection connection, DocumentCollection collection, string partitionKeyRangeId, int sampleCount = 100) - { - var stats = new Dictionary(); - var partitionKey = collection.PartitionKey.Paths[0].TrimStart('/'); - var readCount = 0; - var client = GetClient(connection); - var options = new ChangeFeedOptions { StartFromBeginning = true, MaxItemCount = -1, PartitionKeyRangeId = partitionKeyRangeId }; - - while (readCount < sampleCount) - { - var results = await client.CreateDocumentChangeFeedQuery(collection.AltLink, options) - .ExecuteNextAsync().ConfigureAwait(false); - - if (results.Count == 0) - { - break; - } - - foreach (var document in results) - { - var key = document.GetPropertyValue(partitionKey) ?? "N/A"; - - if (stats.ContainsKey(key)) - { - stats[key]++; - } - else - { - stats.Add(key, 1); - } - } - - readCount += results.Count; - } - - return stats; - } - - public Task> ExecuteStoreProcedureAsync(Connection connection, string altLink, IList parameters, string partitionKey) - { - var options = new RequestOptions - { - EnableScriptLogging = true, - PartitionKey = GetPartitionKey(partitionKey) - }; - - return GetClient(connection).ExecuteStoredProcedureAsync(altLink, options, parameters.ToArray()); - } - - private JObject Parse(string content) - { - JObject o = null; - - using (var stringReader = new StringReader(content)) - { - using (var reader = new JsonTextReader(stringReader)) - { - reader.DateParseHandling = DateParseHandling.None; - o = JObject.Load(reader); - } - } - - return o; - } - } -} diff --git a/src/CosmosDbExplorer/Services/DocumentDescription.cs b/src/CosmosDbExplorer/Services/DocumentDescription.cs deleted file mode 100644 index 657ba04..0000000 --- a/src/CosmosDbExplorer/Services/DocumentDescription.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Microsoft.Azure.Documents; -using System.Collections.Generic; -using Newtonsoft.Json; -using CosmosDbExplorer.Infrastructure.Extensions; - -namespace CosmosDbExplorer.Services -{ - public class DocumentDescription - { - [JsonConstructor] - public DocumentDescription(string id, string selfLink, object partitionKey, bool hasPartitionKey) - { - Id = id; - SelfLink = selfLink; - PartitionKey = partitionKey; - HasPartitionKey = hasPartitionKey; - } - - public DocumentDescription(Document document, DocumentCollection collection) - { - Id = document.Id; - SelfLink = document.SelfLink; - - var token = collection.PartitionKey.GetSelectToken(); - - if (token != null) - { - PartitionKey = document.GetPartitionKeyValue(token); - HasPartitionKey = true; - } - } - - [JsonProperty(PropertyName ="id")] - public string Id { get; set; } - - [JsonProperty(PropertyName = "_self")] - public string SelfLink { get; set; } - - [JsonProperty(PropertyName = "_partitionKey")] - public object PartitionKey { get; set; } - - [JsonProperty(PropertyName = "_hasPartitionKey")] - public bool HasPartitionKey { get; set; } - - [JsonIgnore] - public bool IsSelected { get; set; } - } - - public class DocumentDescriptionList : List - { - public DocumentDescriptionList(IEnumerable collection) - : base(collection) - { - - } - - public bool HasMore - { - get { return ContinuationToken != null; } - } - - public string ContinuationToken { get; set; } - - public double RequestCharge { get; internal set; } - } -} diff --git a/src/CosmosDbExplorer/Services/IDocumentDbService.cs b/src/CosmosDbExplorer/Services/IDocumentDbService.cs deleted file mode 100644 index 66ffd46..0000000 --- a/src/CosmosDbExplorer/Services/IDocumentDbService.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; -using System.Collections.Generic; -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.ViewModel.Interfaces; -using System.Threading; - -namespace CosmosDbExplorer.Services -{ - public interface IDocumentDbService - { - Task> GetDatabasesAsync(Connection connection); - - Task DeleteDatabaseAsync(Connection connection, Database database); - - Task> GetCollectionsAsync(Connection connection, Database database); - - Task GetDocumentsAsync(Connection connection, DocumentCollection collection, string filter, int maxItems, string continuationToken, IHaveRequestOptions requestOptions); - - Task> GetDocumentAsync(Connection connection, DocumentDescription document); - - Task> UpdateDocumentAsync(Connection connection, string altLink, string content, IHaveRequestOptions requestOptions); - - Task> ExecuteQueryAsync(Connection connection, DocumentCollection collection, string query, IHaveQuerySettings querySettings, string continuationToken, CancellationToken cancellationToken); - - Task>> DeleteDocumentsAsync(Connection connection, IEnumerable documents); - - Task CleanCollectionAsync(Connection connection, DocumentCollection collection); - - Task RecreateCollectionAsync(Connection connection, Database database, DocumentCollection collection); - - Task ImportDocumentAsync(Connection connection, DocumentCollection collection, string content, IHaveRequestOptions requestOptions, CancellationToken cancellationToken); - - Task> GetStoredProceduresAsync(Connection connection, DocumentCollection collection); - - Task SaveStoredProcedureAsync(Connection connection, DocumentCollection collection, string id, string function, string storeProcedureLink); - - Task DeleteStoredProcedureAsync(Connection connection, string storedProcedureLink); - - Task> ExecuteStoreProcedureAsync(Connection connection, string altLink, IList parameters, string partitionKey); - - Task> GetUdfsAsync(Connection connection, DocumentCollection collection); - - Task SaveUdfAsync(Connection connection, DocumentCollection collection, string id, string function, string altLink); - - Task DeleteUdfAsync(Connection connection, string udfLink); - - Task> GetTriggersAsync(Connection connection, DocumentCollection collection); - - Task SaveTriggerAsync(Connection connection, DocumentCollection collection, string id, string trigger, TriggerType triggerType, TriggerOperation triggerOperation, string altLink); - - Task DeleteTriggerAsync(Connection connection, string triggerLink); - - Task GetThroughputAsync(Connection connection, DocumentCollection collection); - - Task UpdateCollectionSettingsAsync(Connection connection, DocumentCollection collection, int throughput); - - Task CreateCollectionAsync(Connection connection, Database database, DocumentCollection collection, int throughput); - - Task DeleteCollectionAsync(Connection connection, DocumentCollection collection); - - Task> GetUsersAsync(Connection connection, Database database); - - Task SaveUserAsync(Connection connection, Database database, User user); - - Task DeleteUserAsync(Connection connection, User user); - - Task> GetPermissionAsync(Connection connection, User user); - - Task SavePermissionAsync(Connection connection, User user, Permission permission); - - Task DeletePermissionAsync(Connection connection, Permission permission); - - Task GetPartitionKeyRangeCountAsync(Connection connection, DocumentCollection collection); - - Task> GetTopPartitionKeys(Connection connection, DocumentCollection collection, string partitionKeyRangeId, int sampleCount = 100); - - Task GetPartitionMetricsAsync(Connection connection, DocumentCollection collection); - } -} diff --git a/src/CosmosDbExplorer/Services/NavigationService.cs b/src/CosmosDbExplorer/Services/NavigationService.cs new file mode 100644 index 0000000..2f66a2e --- /dev/null +++ b/src/CosmosDbExplorer/Services/NavigationService.cs @@ -0,0 +1,101 @@ +using System; +using System.Windows.Controls; +using System.Windows.Navigation; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; + +namespace CosmosDbExplorer.Services +{ + public class NavigationService : INavigationService + { + private readonly IPageService _pageService; + private Frame _frame; + private object _lastParameterUsed; + + public event EventHandler Navigated; + + public bool CanGoBack => _frame.CanGoBack; + + public NavigationService(IPageService pageService) + { + _pageService = pageService; + } + + public void Initialize(Frame shellFrame) + { + if (_frame == null) + { + _frame = shellFrame; + _frame.Navigated += OnNavigated; + } + } + + public void UnsubscribeNavigation() + { + _frame.Navigated -= OnNavigated; + _frame = null; + } + + public void GoBack() + { + if (_frame.CanGoBack) + { + var vmBeforeNavigation = _frame.GetDataContext(); + _frame.GoBack(); + if (vmBeforeNavigation is INavigationAware navigationAware) + { + navigationAware.OnNavigatedFrom(); + } + } + } + + public bool NavigateTo(string pageKey, object? parameter = null, bool clearNavigation = false) + { + var pageType = _pageService.GetPageType(pageKey); + + if (_frame.Content?.GetType() != pageType || (parameter != null && !parameter.Equals(_lastParameterUsed))) + { + _frame.Tag = clearNavigation; + var page = _pageService.GetPage(pageKey); + var navigated = _frame.Navigate(page, parameter); + if (navigated) + { + _lastParameterUsed = parameter; + var dataContext = _frame.GetDataContext(); + if (dataContext is INavigationAware navigationAware) + { + navigationAware.OnNavigatedFrom(); + } + } + + return navigated; + } + + return false; + } + + public void CleanNavigation() + => _frame.CleanNavigation(); + + private void OnNavigated(object sender, NavigationEventArgs e) + { + if (sender is Frame frame) + { + bool clearNavigation = (bool)frame.Tag; + if (clearNavigation) + { + frame.CleanNavigation(); + } + + var dataContext = frame.GetDataContext(); + if (dataContext is INavigationAware navigationAware) + { + navigationAware.OnNavigatedTo(e.ExtraData); + } + + Navigated?.Invoke(sender, dataContext.GetType().FullName); + } + } + } +} diff --git a/src/CosmosDbExplorer/Services/PageService.cs b/src/CosmosDbExplorer/Services/PageService.cs new file mode 100644 index 0000000..d979bb7 --- /dev/null +++ b/src/CosmosDbExplorer/Services/PageService.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Controls; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.ViewModels; +using CosmosDbExplorer.Views; + +using Microsoft.Toolkit.Mvvm.ComponentModel; + +namespace CosmosDbExplorer.Services +{ + public class PageService : IPageService + { + private readonly Dictionary _pages = new Dictionary(); + private readonly IServiceProvider _serviceProvider; + + public PageService(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + Configure(); + Configure(); + Configure(); + Configure(); + Configure(); + } + + public Type GetPageType(string key) + { + Type pageType; + lock (_pages) + { + if (!_pages.TryGetValue(key, out pageType)) + { + throw new ArgumentException($"Page not found: {key}. Did you forget to call PageService.Configure?"); + } + } + + return pageType; + } + + public Page? GetPage(string key) + { + var pageType = GetPageType(key); + return _serviceProvider.GetService(pageType) as Page; + } + + private void Configure() + where VM : ObservableObject + where V : Page + { + lock (_pages) + { + var key = typeof(VM).FullName; + if (_pages.ContainsKey(key)) + { + throw new ArgumentException($"The key {key} is already configured in PageService"); + } + + var type = typeof(V); + if (_pages.Any(p => p.Value == type)) + { + throw new ArgumentException($"This type is already configured with key {_pages.First(p => p.Value == type).Key}"); + } + + _pages.Add(key, type); + } + } + } +} diff --git a/src/CosmosDbExplorer/Services/PersistAndRestoreService.cs b/src/CosmosDbExplorer/Services/PersistAndRestoreService.cs new file mode 100644 index 0000000..af4580f --- /dev/null +++ b/src/CosmosDbExplorer/Services/PersistAndRestoreService.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Core.Contracts.Services; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Extensions; +using CosmosDbExplorer.Models; + +using Microsoft.Extensions.Options; +using Newtonsoft.Json; + +namespace CosmosDbExplorer.Services +{ + public class PersistAndRestoreService : IPersistAndRestoreService + { + private readonly IFileService _fileService; + private readonly AppConfig _appConfig; + private readonly string _localAppData; + private readonly string _configurationFilePath; + + private const string ConfigurationFileName = "connection-settings.json"; + + private List _connections = new(); + + + public PersistAndRestoreService(IFileService fileService, IOptions appConfig) + { + _fileService = fileService; + _appConfig = appConfig.Value; + + _localAppData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CosmosDbExplorer"); + _configurationFilePath = Path.Combine(_localAppData, ConfigurationFileName); + + if (!Directory.Exists(_localAppData)) + { + Directory.CreateDirectory(_localAppData); + + // Use old config file if exists... + var oldConfigurationFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DocumentDbExplorer", ConfigurationFileName); + if (File.Exists(oldConfigurationFilePath)) + { + File.Copy(oldConfigurationFilePath, _configurationFilePath); + } + } + } + + public void PersistData() + { + Properties.Settings.Default.Save(); + } + + public void RestoreData() + { + Properties.Settings.Default.Reload(); + } + + public void ResetData() + { + Properties.Settings.Default.Reset(); + } + + public List GetConnections() + { + if (File.Exists(_configurationFilePath)) + { + _connections = _fileService.Read>(_localAppData, ConfigurationFileName); + } + + return _connections; + } + + public void PersistConnection(CosmosConnection connection) + { + var index = _connections.IndexOf(connection); + + if (index != -1) + { + _connections.Replace(_connections[index], connection); + } + else + { + _connections.Add(connection); + } + + SaveConnections(); + } + + + public void RemoveConnection(CosmosConnection connection) + { + if (_connections.Remove(connection)) + { + SaveConnections(); + } + } + + public void ReorderConnections(int sourceIndex, int targetIndex) + { + _connections = _connections.Move(sourceIndex, targetIndex).ToList(); + + SaveConnections(); + } + + private void SaveConnections() + { + _fileService.Save(_localAppData, ConfigurationFileName, _connections); + } + } +} diff --git a/src/CosmosDbExplorer/Services/RightPaneService.cs b/src/CosmosDbExplorer/Services/RightPaneService.cs new file mode 100644 index 0000000..2b141ad --- /dev/null +++ b/src/CosmosDbExplorer/Services/RightPaneService.cs @@ -0,0 +1,80 @@ +using System; +using System.Windows.Controls; +using System.Windows.Navigation; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; + +using MahApps.Metro.Controls; + +namespace CosmosDbExplorer.Services +{ + public class RightPaneService : IRightPaneService + { + private readonly IPageService _pageService; + private Frame _frame; + private object _lastParameterUsed; + private SplitView _splitView; + + public event EventHandler PaneOpened; + + public event EventHandler PaneClosed; + + public RightPaneService(IPageService pageService) + { + _pageService = pageService; + } + + public void Initialize(Frame rightPaneFrame, SplitView splitView) + { + _frame = rightPaneFrame; + _splitView = splitView; + _frame.Navigated += OnNavigated; + _splitView.PaneClosed += OnPaneClosed; + } + + public void CleanUp() + { + _frame.Navigated -= OnNavigated; + _splitView.PaneClosed -= OnPaneClosed; + } + + public void OpenInRightPane(string pageKey, object? parameter = null) + { + var pageType = _pageService.GetPageType(pageKey); + if (_frame.Content?.GetType() != pageType || (parameter != null && !parameter.Equals(_lastParameterUsed))) + { + var page = _pageService.GetPage(pageKey); + var navigated = _frame.Navigate(page, parameter); + if (navigated) + { + _lastParameterUsed = parameter; + var dataContext = _frame.GetDataContext(); + if (dataContext is INavigationAware navigationAware) + { + navigationAware.OnNavigatedFrom(); + } + } + } + + _splitView.IsPaneOpen = true; + PaneOpened?.Invoke(_splitView, EventArgs.Empty); + } + + private void OnNavigated(object sender, NavigationEventArgs e) + { + if (sender is Frame frame) + { + frame.CleanNavigation(); + var dataContext = frame.GetDataContext(); + if (dataContext is INavigationAware navigationAware) + { + navigationAware.OnNavigatedTo(e.ExtraData); + } + } + } + + private void OnPaneClosed(object? sender, EventArgs e) + => PaneClosed?.Invoke(sender, e); + } +} diff --git a/src/CosmosDbExplorer/Services/SettingsService.cs b/src/CosmosDbExplorer/Services/SettingsService.cs deleted file mode 100644 index aa0858b..0000000 --- a/src/CosmosDbExplorer/Services/SettingsService.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure.Extensions; -using CosmosDbExplorer.Infrastructure.Models; -using Newtonsoft.Json; - -namespace CosmosDbExplorer.Services -{ - public interface ISettingsService - { - Task> GetConnectionsAsync(); - Task SaveConnectionAsync(Connection connection); - Task RemoveConnection(Connection connection); - Task ReorderConnections(int sourceIndex, int targetIndex); - } - - public class SettingsService : ISettingsService - { - private const string ConfigurationFileName = "connection-settings.json"; - public static readonly string ConfigurationFilePath = - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CosmosDbExplorer" , ConfigurationFileName); - - public static Dictionary Connections; - - public SettingsService() - { - if (!Directory.Exists(Path.GetDirectoryName(ConfigurationFilePath))) - { - Directory.CreateDirectory(Path.GetDirectoryName(ConfigurationFilePath)); - - // Use old config file if exists... - var oldConfigurationFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DocumentDbExplorer", ConfigurationFileName); - if (File.Exists(oldConfigurationFilePath)) - { - File.Copy(oldConfigurationFilePath, ConfigurationFilePath); - } - } - } - - public async Task> GetConnectionsAsync() - { - if (Connections != null) - { - return Connections; - } - - if (File.Exists(ConfigurationFilePath)) - { - using (var reader = File.OpenText(ConfigurationFilePath)) - { - var json = await reader.ReadToEndAsync(); - Connections = JsonConvert.DeserializeObject>(json) - .ToDictionary(c => c.Id); - } - } - else - { - Connections = new Dictionary(); - } - - return Connections; - } - - public async Task RemoveConnection(Connection connection) - { - if (Connections.Remove(connection.Id)) - { - await SaveAsync(Connections.Values); - } - } - - public Task ReorderConnections(int sourceIndex, int targetIndex) - { - Connections = Connections.Values.ToList().Move(sourceIndex, targetIndex).ToDictionary(c => c.Id); - - return SaveAsync(Connections.Values); - } - - public async Task SaveConnectionAsync(Connection connection) - { - if (Connections.ContainsKey(connection.Id)) - { - Connections[connection.Id] = connection; - } - else - { - Connections.Add(connection.Id, connection); - } - - await SaveAsync(Connections.Values); - } - - private async Task SaveAsync(IEnumerable connections) - { - var json = JsonConvert.SerializeObject(connections, Formatting.Indented); - - using (var fs = File.Open(ConfigurationFilePath, FileMode.Create)) - { - var info = new UTF8Encoding(true).GetBytes(json); - await fs.WriteAsync(info, 0, info.Length); - } - } - } -} diff --git a/src/CosmosDbExplorer/Services/SystemService.cs b/src/CosmosDbExplorer/Services/SystemService.cs new file mode 100644 index 0000000..6b35065 --- /dev/null +++ b/src/CosmosDbExplorer/Services/SystemService.cs @@ -0,0 +1,24 @@ +using System.Diagnostics; + +using CosmosDbExplorer.Contracts.Services; + +namespace CosmosDbExplorer.Services +{ + public class SystemService : ISystemService + { + public SystemService() + { + } + + public void OpenInWebBrowser(string url) + { + // For more info see https://github.com/dotnet/corefx/issues/10361 + var psi = new ProcessStartInfo + { + FileName = url, + UseShellExecute = true + }; + Process.Start(psi); + } + } +} diff --git a/src/CosmosDbExplorer/Services/ThemeSelectorService.cs b/src/CosmosDbExplorer/Services/ThemeSelectorService.cs new file mode 100644 index 0000000..47d8bd8 --- /dev/null +++ b/src/CosmosDbExplorer/Services/ThemeSelectorService.cs @@ -0,0 +1,79 @@ +using System; +using System.Linq; +using System.Windows; +using ControlzEx.Theming; +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Models; +using CosmosDbExplorer.Styles; +using ICSharpCode.AvalonEdit.Highlighting; +using MahApps.Metro.Theming; + +namespace CosmosDbExplorer.Services +{ + public class ThemeSelectorService : IThemeSelectorService + { + private const string HcDarkTheme = "pack://application:,,,/Styles/Themes/HC.Dark.Blue.xaml"; + private const string HcLightTheme = "pack://application:,,,/Styles/Themes/HC.Light.Blue.xaml"; + + public ThemeSelectorService() + { + } + + public void InitializeTheme() + { + // TODO WTS: Mahapps.Metro supports syncronization with high contrast but you have to provide custom high contrast themes + // We've added basic high contrast dictionaries for Dark and Light themes + // Please complete these themes following the docs on https://mahapps.com/docs/themes/thememanager#creating-custom-themes + ThemeManager.Current.AddLibraryTheme(new LibraryTheme(new Uri(HcDarkTheme), MahAppsLibraryThemeProvider.DefaultInstance)); + ThemeManager.Current.AddLibraryTheme(new LibraryTheme(new Uri(HcLightTheme), MahAppsLibraryThemeProvider.DefaultInstance)); + + var theme = GetCurrentTheme(); + SetTheme(theme); + } + + public void SetTheme(AppTheme theme) + { + if (theme == AppTheme.Default) + { + ThemeManager.Current.ThemeSyncMode = ThemeSyncMode.SyncWithAppMode | ThemeSyncMode.SyncWithHighContrast; + ThemeManager.Current.SyncTheme(); + } + else + { + ThemeManager.Current.ThemeSyncMode = ThemeSyncMode.SyncWithHighContrast; + ThemeManager.Current.SyncTheme(); + ThemeManager.Current.ChangeTheme(Application.Current, $"{theme}.Blue", SystemParameters.HighContrast); + } + + UpdateHighlightingColor(theme); + Properties.Settings.Default.Theme = theme.ToString(); + } + + public AppTheme GetCurrentTheme() + { + return Enum.Parse(Properties.Settings.Default.Theme); + } + + private void UpdateHighlightingColor(AppTheme theme) + { + if (theme == AppTheme.Default) + { + theme = WindowsThemeHelper.AppsUseLightTheme() ? AppTheme.Light : AppTheme.Dark; + } + + UpdateHighlightingColor(HighlightingManager.Instance.GetDefinition("JSON"), theme); + UpdateHighlightingColor(HighlightingManager.Instance.GetDefinition("DocumentDbSql"), theme); + UpdateHighlightingColor(HighlightingManager.Instance.GetDefinition("JavaScript-Mode"), theme); + } + + private static void UpdateHighlightingColor(IHighlightingDefinition definition, AppTheme theme) + { + foreach (var color in definition.NamedHighlightingColors.Where(c => !c.Name.Contains('.')).Select(c => c.Name)) + { + var sourceColor = $"{theme}.{color}"; + definition.GetNamedColor(color).MergeWith(definition.GetNamedColor(sourceColor)); + } + } + + } +} diff --git a/src/CosmosDbExplorer/Services/UIServices.cs b/src/CosmosDbExplorer/Services/UIServices.cs index 4fbc731..9f643f1 100644 --- a/src/CosmosDbExplorer/Services/UIServices.cs +++ b/src/CosmosDbExplorer/Services/UIServices.cs @@ -1,5 +1,7 @@ -using CosmosDbExplorer.Messages; -using GalaSoft.MvvmLight.Messaging; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Messages; +using Microsoft.Toolkit.Mvvm.Messaging; namespace CosmosDbExplorer.Services { diff --git a/src/CosmosDbExplorer/Services/WindowManagerService.cs b/src/CosmosDbExplorer/Services/WindowManagerService.cs new file mode 100644 index 0000000..17874b0 --- /dev/null +++ b/src/CosmosDbExplorer/Services/WindowManagerService.cs @@ -0,0 +1,108 @@ +using System; +using System.Reflection; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Navigation; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Contracts.Views; + +using MahApps.Metro.Controls; + +namespace CosmosDbExplorer.Services +{ + public class WindowManagerService : IWindowManagerService + { + private readonly IServiceProvider _serviceProvider; + private readonly IPageService _pageService; + + public Window MainWindow + => Application.Current.MainWindow; + + public WindowManagerService(IServiceProvider serviceProvider, IPageService pageService) + { + _serviceProvider = serviceProvider; + _pageService = pageService; + } + + public void OpenInNewWindow(string key, object? parameter = null) + { + var window = GetWindow(key); + if (window != null) + { + window.Activate(); + } + else + { + window = new MetroWindow() + { + Title = ((AssemblyTitleAttribute)Attribute.GetCustomAttribute(Assembly.GetEntryAssembly(), typeof(AssemblyTitleAttribute), false))?.Title ?? "error retrieving assembly title", + Style = Application.Current.FindResource("CustomMetroWindow") as Style + }; + var frame = new Frame() + { + Focusable = false, + NavigationUIVisibility = NavigationUIVisibility.Hidden + }; + + window.Content = frame; + var page = _pageService.GetPage(key); + window.Closed += OnWindowClosed; + window.Show(); + frame.Navigated += OnNavigated; + var navigated = frame.Navigate(page, parameter); + } + } + + public bool? OpenInDialog(string key, object? parameter = null) + { + var shellWindow = _serviceProvider.GetService(typeof(IShellDialogWindow)) as Window; + var frame = ((IShellDialogWindow)shellWindow).GetDialogFrame(); + frame.Navigated += OnNavigated; + shellWindow.Closed += OnWindowClosed; + var page = _pageService.GetPage(key); + var navigated = frame.Navigate(page, parameter); + return shellWindow.ShowDialog(); + } + + public Window? GetWindow(string key) + { + foreach (Window window in Application.Current.Windows) + { + var dataContext = window.GetDataContext(); + if (dataContext?.GetType().FullName == key) + { + return window; + } + } + + return null; + } + + private void OnNavigated(object sender, NavigationEventArgs e) + { + if (sender is Frame frame) + { + var dataContext = frame.GetDataContext(); + if (dataContext is INavigationAware navigationAware) + { + navigationAware.OnNavigatedTo(e.ExtraData); + } + } + } + + private void OnWindowClosed(object sender, EventArgs e) + { + if (sender is Window window) + { + if (window.Content is Frame frame) + { + frame.Navigated -= OnNavigated; + } + + window.Closed -= OnWindowClosed; + } + } + } +} diff --git a/src/CosmosDbExplorer/Styles/CosmosControls.xaml b/src/CosmosDbExplorer/Styles/CosmosControls.xaml new file mode 100644 index 0000000..670e5fa --- /dev/null +++ b/src/CosmosDbExplorer/Styles/CosmosControls.xaml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/CosmosDbExplorer/Styles/CosmosDbExplorerThemeProvider.cs b/src/CosmosDbExplorer/Styles/CosmosDbExplorerThemeProvider.cs new file mode 100644 index 0000000..ccf94cd --- /dev/null +++ b/src/CosmosDbExplorer/Styles/CosmosDbExplorerThemeProvider.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; + +using ControlzEx.Theming; + +using MahApps.Metro.Theming; + +namespace CosmosDbExplorer.Styles +{ + public class CosmosDbExplorerThemeProvider : /*MahAppsLibraryThemeProvider*/LibraryThemeProvider + { + public static readonly CosmosDbExplorerThemeProvider DefaultInstance = new CosmosDbExplorerThemeProvider(); + + public CosmosDbExplorerThemeProvider() + : base(true) + { + + } + + public override void FillColorSchemeValues(Dictionary values, RuntimeThemeColorValues colorValues) + { + // Check if all needed parameters are not null + if (values is null) + { + throw new ArgumentNullException(nameof(values)); + } + + if (colorValues is null) + { + throw new ArgumentNullException(nameof(colorValues)); + } + + bool isDarkMode = colorValues.Options.BaseColorScheme.Name == ThemeManager.BaseColorDark; + + //values.Add("CosmosDbExplorer.AvalonEdit.LinkTextForegroundBrush", isDarkMode ? Color.FromRgb(155, 109, 90).ToString() : Colors.CornflowerBlue.ToString()); + + //base.FillColorSchemeValues(values, colorValues); + } + } +} diff --git a/src/CosmosDbExplorer/Styles/Icons.xaml b/src/CosmosDbExplorer/Styles/Icons.xaml new file mode 100644 index 0000000..d46af43 --- /dev/null +++ b/src/CosmosDbExplorer/Styles/Icons.xaml @@ -0,0 +1,522 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/CosmosDbExplorer/Styles/MetroWindow.xaml b/src/CosmosDbExplorer/Styles/MetroWindow.xaml new file mode 100644 index 0000000..2ddb27e --- /dev/null +++ b/src/CosmosDbExplorer/Styles/MetroWindow.xaml @@ -0,0 +1,14 @@ + + + + diff --git a/src/CosmosDbExplorer/Styles/TextBlock.xaml b/src/CosmosDbExplorer/Styles/TextBlock.xaml new file mode 100644 index 0000000..e8ad872 --- /dev/null +++ b/src/CosmosDbExplorer/Styles/TextBlock.xaml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CosmosDbExplorer/Styles/Themes/Dark.Blue.xaml b/src/CosmosDbExplorer/Styles/Themes/Dark.Blue.xaml new file mode 100644 index 0000000..4f58f50 --- /dev/null +++ b/src/CosmosDbExplorer/Styles/Themes/Dark.Blue.xaml @@ -0,0 +1,23 @@ + + + + Dark.Blue + CosmosDbExplorer + Cosmos Blue (Dark) + Dark + Blue + Blue + + #FF0078D7 + + False + + + + + \ No newline at end of file diff --git a/src/CosmosDbExplorer/Styles/Themes/HC.Dark.Blue.xaml b/src/CosmosDbExplorer/Styles/Themes/HC.Dark.Blue.xaml new file mode 100644 index 0000000..341593e --- /dev/null +++ b/src/CosmosDbExplorer/Styles/Themes/HC.Dark.Blue.xaml @@ -0,0 +1,40 @@ + + + + Dark.Blue + CosmosDbExplorer + Blue (Dark) + Dark + Blue + true + #FF0078D7 + + + + + + + #FFFFFFFF + #FF000000 + #FFFFFFFF + + + + + + + + + \ No newline at end of file diff --git a/src/CosmosDbExplorer/Styles/Themes/HC.Light.Blue.xaml b/src/CosmosDbExplorer/Styles/Themes/HC.Light.Blue.xaml new file mode 100644 index 0000000..812d635 --- /dev/null +++ b/src/CosmosDbExplorer/Styles/Themes/HC.Light.Blue.xaml @@ -0,0 +1,40 @@ + + + + Light.Blue + CosmosDbExplorer + Blue (Light) + Light + Blue + true + #FF0078D7 + + + + + + + #FF000000 + #FFFFFFFF + #FF000000 + + + + + + + + + \ No newline at end of file diff --git a/src/CosmosDbExplorer/Styles/Themes/Light.Blue.xaml b/src/CosmosDbExplorer/Styles/Themes/Light.Blue.xaml new file mode 100644 index 0000000..c5301c6 --- /dev/null +++ b/src/CosmosDbExplorer/Styles/Themes/Light.Blue.xaml @@ -0,0 +1,23 @@ + + + + Light.Blue + CosmosDbExplorer + Cosmos Blue (Dark) + Light + Blue + Blue + + #FF0078D7 + + False + + + + + \ No newline at end of file diff --git a/src/CosmosDbExplorer/Styles/_FontSizes.xaml b/src/CosmosDbExplorer/Styles/_FontSizes.xaml new file mode 100644 index 0000000..814c112 --- /dev/null +++ b/src/CosmosDbExplorer/Styles/_FontSizes.xaml @@ -0,0 +1,14 @@ + + + 12 + + 14 + + 18 + + 22 + + diff --git a/src/CosmosDbExplorer/Styles/_Thickness.xaml b/src/CosmosDbExplorer/Styles/_Thickness.xaml new file mode 100644 index 0000000..49abd94 --- /dev/null +++ b/src/CosmosDbExplorer/Styles/_Thickness.xaml @@ -0,0 +1,30 @@ + + + + 24,0,0,0 + 24,24,24,0 + 0,24,0,0 + 24,0,24,0 + 0, 0, 0, 24 + 24,24,24,24 + + + 12, 0, 0, 0 + 12, 12, 0, 0 + 12, 0, 12, 0 + 0, 12, 0, 0 + 0, 0, 12, 0 + 0, 12, 0, 12 + 12, 12, 12, 12 + + + 8, 0, 0, 0 + 0, 8, 0, 0 + 8, 8, 8, 8 + + + 0, 4, 0, 0 + + diff --git a/src/CosmosDbExplorer/TemplateSelectors/DocumentDescriptionTemplateSelector.cs b/src/CosmosDbExplorer/TemplateSelectors/DocumentDescriptionTemplateSelector.cs new file mode 100644 index 0000000..bc067bb --- /dev/null +++ b/src/CosmosDbExplorer/TemplateSelectors/DocumentDescriptionTemplateSelector.cs @@ -0,0 +1,22 @@ +using System.Windows; +using System.Windows.Controls; +using CosmosDbExplorer.Core.Contracts; +using CosmosDbExplorer.Models; + +namespace CosmosDbExplorer.TemplateSelectors +{ + public class DocumentDescriptionTemplateSelector : DataTemplateSelector + { + public DataTemplate? DefaultTemplate { get; set; } + public DataTemplate? PartitionTemplate { get; set; } + + public override DataTemplate? SelectTemplate(object item, DependencyObject container) + { + var dd = ((CheckedItem)item).Item; + + return dd.HasPartitionKey + ? PartitionTemplate + : DefaultTemplate; + } + } +} diff --git a/src/CosmosDbExplorer/TemplateSelectors/StatusBarItemTemplateSelector.cs b/src/CosmosDbExplorer/TemplateSelectors/StatusBarItemTemplateSelector.cs new file mode 100644 index 0000000..b26ba43 --- /dev/null +++ b/src/CosmosDbExplorer/TemplateSelectors/StatusBarItemTemplateSelector.cs @@ -0,0 +1,26 @@ +using System.Windows; +using System.Windows.Controls; +using CosmosDbExplorer.Models; + +namespace CosmosDbExplorer.TemplateSelectors +{ + public class StatusBarItemTemplateSelector : DataTemplateSelector + { + public DataTemplate? UsedMemoryTemplate { get; set; } + public DataTemplate? ZoomTemplate { get; set; } + public DataTemplate? SimpleTextTemplate { get; set; } + public DataTemplate? ProgressBarTemplate { get; set; } + + public override DataTemplate? SelectTemplate(object item, DependencyObject container) + { + return ((StatusBarItem)item).Type switch + { + StatusBarItemType.UsedMemory => UsedMemoryTemplate, + StatusBarItemType.Zoom => ZoomTemplate, + StatusBarItemType.SimpleText => SimpleTextTemplate, + StatusBarItemType.ProgessBar => ProgressBarTemplate, + _ => base.SelectTemplate(item, container), + }; + } + } +} diff --git a/src/CosmosDbExplorer/TemplateSelectors/StoredProcParameterTemplateSelector.cs b/src/CosmosDbExplorer/TemplateSelectors/StoredProcParameterTemplateSelector.cs new file mode 100644 index 0000000..ddbbb28 --- /dev/null +++ b/src/CosmosDbExplorer/TemplateSelectors/StoredProcParameterTemplateSelector.cs @@ -0,0 +1,21 @@ +using System.Windows; +using System.Windows.Controls; +using CosmosDbExplorer.ViewModels.Assets; + +namespace CosmosDbExplorer.TemplateSelectors +{ + public class StoredProcParameterTemplateSelector : DataTemplateSelector + { + public DataTemplate? JsonDataTemplate { get; set; } + public DataTemplate? FileDataTemplate { get; set; } + + public override DataTemplate? SelectTemplate(object item, DependencyObject container) + { + return (StoredProcParameterKind)item switch + { + StoredProcParameterKind.File => FileDataTemplate, + _ => JsonDataTemplate, + }; + } + } +} diff --git a/src/CosmosDbExplorer/TemplateSelectors/TabContentTemplateSelector.cs b/src/CosmosDbExplorer/TemplateSelectors/TabContentTemplateSelector.cs new file mode 100644 index 0000000..cbf8b1c --- /dev/null +++ b/src/CosmosDbExplorer/TemplateSelectors/TabContentTemplateSelector.cs @@ -0,0 +1,43 @@ +using System.Windows; +using System.Windows.Controls; +using CosmosDbExplorer.ViewModels; +using CosmosDbExplorer.ViewModels.Assets; + +namespace CosmosDbExplorer.TemplateSelectors +{ + public class TabContentTemplateSelector : DataTemplateSelector + { + public DataTemplate? DocumentsTemplate { get; set; } + public DataTemplate? QueryEditorTemplate { get; set; } + public DataTemplate? ImportDocumentTemplate { get; set; } + public DataTemplate? DatabaseViewTemplate { get; set; } + public DataTemplate? StoredProcedureViewTemplate { get; set; } + public DataTemplate? UserDefFuncViewTemplate { get; set; } + public DataTemplate? TriggerViewTemplate { get; set; } + public DataTemplate? ContainerScaleSettingsTemplate { get; set; } + public DataTemplate? DatabaseScaleTemplate { get; set; } + public DataTemplate? UserEditTempalate { get; set; } + public DataTemplate? PermissionEditTemplate { get; set; } + public DataTemplate? MetricsTemplate { get; set; } + + public override DataTemplate? SelectTemplate(object item, DependencyObject container) + { + return item switch + { + DatabaseViewModel => DatabaseViewTemplate, + DocumentsTabViewModel => DocumentsTemplate, + QueryEditorViewModel => QueryEditorTemplate, + ImportDocumentViewModel => ImportDocumentTemplate, + StoredProcedureTabViewModel => StoredProcedureViewTemplate, + UserDefFuncTabViewModel => UserDefFuncViewTemplate, + TriggerTabViewModel => TriggerViewTemplate, + ContainerScaleSettingsViewModel => ContainerScaleSettingsTemplate, + DatabaseScaleViewModel => DatabaseScaleTemplate, + UserEditViewModel => UserEditTempalate, + MetricsTabViewModel => MetricsTemplate, + PermissionEditViewModel => PermissionEditTemplate, + _ => base.SelectTemplate(item, container) + }; + } + } +} diff --git a/src/CosmosDbExplorer/Themes/Generic.xaml b/src/CosmosDbExplorer/Themes/Generic.xaml deleted file mode 100644 index 8db35a1..0000000 --- a/src/CosmosDbExplorer/Themes/Generic.xaml +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/CosmosDbExplorer/Infrastructure/Validar/PartitionKeyValidator.cs b/src/CosmosDbExplorer/Validar/PartitionKeyValidator.cs similarity index 63% rename from src/CosmosDbExplorer/Infrastructure/Validar/PartitionKeyValidator.cs rename to src/CosmosDbExplorer/Validar/PartitionKeyValidator.cs index 06a3352..65ca105 100644 --- a/src/CosmosDbExplorer/Infrastructure/Validar/PartitionKeyValidator.cs +++ b/src/CosmosDbExplorer/Validar/PartitionKeyValidator.cs @@ -1,14 +1,12 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using FluentValidation; using FluentValidation.Validators; using Newtonsoft.Json.Linq; -namespace CosmosDbExplorer.Infrastructure.Validar +namespace CosmosDbExplorer.Validar { - public class PartitionKeyValidator : PropertyValidator + public class PartitionKeyValidator : PropertyValidator { private static readonly JTokenType[] AcceptedTypes = new[] { @@ -18,15 +16,16 @@ public class PartitionKeyValidator : PropertyValidator JTokenType.Undefined, JTokenType.Null }; - public PartitionKeyValidator() - : base("Numeric, string, bool, null, Undefined are the only supported types.{Details}") - {} + public override string Name => "PartitionKeyValidator"; - protected override bool IsValid(PropertyValidatorContext context) + protected override string GetDefaultMessageTemplate(string errorCode) + => "Numeric, string, bool, null, Undefined are the only supported types.{Details}"; + + public override bool IsValid(ValidationContext context, TProperty value) { try { - var pk = context.PropertyValue as string; + var pk = value as string; var token = JToken.Parse(pk); diff --git a/src/CosmosDbExplorer/Infrastructure/Validar/ValidationFactory.cs b/src/CosmosDbExplorer/Validar/ValidationFactory.cs similarity index 95% rename from src/CosmosDbExplorer/Infrastructure/Validar/ValidationFactory.cs rename to src/CosmosDbExplorer/Validar/ValidationFactory.cs index ff5a7bf..5570f9d 100644 --- a/src/CosmosDbExplorer/Infrastructure/Validar/ValidationFactory.cs +++ b/src/CosmosDbExplorer/Validar/ValidationFactory.cs @@ -6,7 +6,7 @@ using FluentValidation; using FluentValidation.Results; -namespace CosmosDbExplorer.Infrastructure.Validar +namespace CosmosDbExplorer.Validar { public static class ValidationFactory diff --git a/src/CosmosDbExplorer/Validar/ValidationTemplate.cs b/src/CosmosDbExplorer/Validar/ValidationTemplate.cs new file mode 100644 index 0000000..8c7fc6e --- /dev/null +++ b/src/CosmosDbExplorer/Validar/ValidationTemplate.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.ComponentModel; +using System.Linq; +using FluentValidation; +using FluentValidation.Results; + +namespace CosmosDbExplorer.Validar +{ + public class ValidationTemplate : IDataErrorInfo//, INotifyDataErrorInfo + where T : class, INotifyPropertyChanged + { + private readonly INotifyPropertyChanged _target; + private readonly IValidator _validator; + private ValidationResult _validationResult; + private readonly ValidationContext _validationContext; + + public ValidationTemplate(T target) + { + _validator = ValidationFactory.GetValidator(); + _validationResult = _validator.Validate(new ValidationContext(target)); + target.PropertyChanged += Validate; + } + + private void Validate(object? sender, PropertyChangedEventArgs e) + { + _validationResult = _validator.Validate(new ValidationContext(sender as T)); + //foreach (var error in _validationResult.Errors) + //{ + // RaiseErrorsChanged(error.PropertyName); + //} + } + + //public IEnumerable GetErrors(string propertyName) + //{ + // return _validationResult.Errors + // .Where(x => x.PropertyName == propertyName) + // .Select(x => x.ErrorMessage); + //} + + //public bool HasErrors => _validationResult.Errors.Count > 0; + + public string Error + { + get + { + var strings = _validationResult.Errors.Select(x => x.ErrorMessage) + .ToArray(); + return string.Join(Environment.NewLine, strings); + } + } + + public string this[string columnName] + { + get + { + var strings = _validationResult.Errors.Where(x => x.PropertyName == columnName) + .Select(x => x.ErrorMessage) + .ToArray(); + return string.Join(Environment.NewLine, strings); + } + } + + //public event EventHandler? ErrorsChanged; + + //private void RaiseErrorsChanged(string propertyName) + //{ + // var handler = ErrorsChanged; + // handler?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); + //} + } +} diff --git a/src/CosmosDbExplorer/ViewModel/AboutViewModel.cs b/src/CosmosDbExplorer/ViewModel/AboutViewModel.cs deleted file mode 100644 index d41bb18..0000000 --- a/src/CosmosDbExplorer/ViewModel/AboutViewModel.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using CosmosDbExplorer.Infrastructure.Models; -using GalaSoft.MvvmLight; - -namespace CosmosDbExplorer.ViewModel -{ - public class AboutViewModel : ViewModelBase - { - private readonly Assembly _assembly; - private readonly FileVersionInfo _fvi; - - public AboutViewModel() - { - _assembly = Assembly.GetEntryAssembly(); - _fvi = FileVersionInfo.GetVersionInfo(_assembly.Location); - ExternalComponents = new List - { - new ExternalComponent { Name = "AvalonEdit", LicenseUrl = "http://opensource.org/licenses/MIT", ProjectUrl = "http://www.avalonedit.net"}, - new ExternalComponent { Name = "Extended WPF Toolkit", LicenseUrl = "https://github.com/xceedsoftware/wpftoolkit/blob/master/license.md", ProjectUrl = "https://github.com/xceedsoftware/wpftoolkit"}, - new ExternalComponent { Name = "Fluent.Ribbon", LicenseUrl = "https://github.com/fluentribbon/Fluent.Ribbon/blob/master/License.txt", ProjectUrl = "https://github.com/fluentribbon/Fluent.Ribbon"}, - new ExternalComponent { Name = "FluentValidation", LicenseUrl = "https://github.com/JeremySkinner/FluentValidation/blob/master/License.txt", ProjectUrl = "https://github.com/JeremySkinner/fluentvalidation"}, - new ExternalComponent { Name = "Fody", LicenseUrl = "http://www.opensource.org/licenses/mit-license.php", ProjectUrl = "http://github.com/Fody/Fody"}, - new ExternalComponent { Name = "MvvmLight", LicenseUrl = "http://www.galasoft.ch/license_MIT.txt", ProjectUrl = "http://www.galasoft.ch/mvvm"}, - new ExternalComponent { Name = "Json.NET", LicenseUrl = "https://raw.githubusercontent.com/JamesNK/Newtonsoft.Json/master/LICENSE.md", ProjectUrl = "https://www.newtonsoft.com/json"}, - new ExternalComponent { Name = "PropertyChanged.Fody", LicenseUrl = "http://www.opensource.org/licenses/mit-license.php", ProjectUrl = "http://github.com/Fody/PropertyChanged"}, - new ExternalComponent { Name = "Validar.Fody", LicenseUrl = "http://www.opensource.org/licenses/mit-license.php", ProjectUrl = "http://github.com/Fody/Validar"}, - new ExternalComponent { Name = "WpfAnimatedGif", LicenseUrl = "http://www.apache.org/licenses/LICENSE-2.0.txt", ProjectUrl = "https://github.com/XamlAnimatedGif/WpfAnimatedGif"}, - new ExternalComponent { Name = "GongSolutions.WPF.DragDrop", LicenseUrl = "https://github.com/punker76/gong-wpf-dragdrop#license", ProjectUrl = "https://github.com/punker76/gong-wpf-dragdrop"}, - new ExternalComponent { Name = "Autoupdater.NET.Official", LicenseUrl = "https://github.com/ravibpatel/AutoUpdater.NET/blob/master/LICENSE", ProjectUrl = "https://github.com/ravibpatel/AutoUpdater.NET"}, - new ExternalComponent { Name = "LiveCharts", LicenseUrl = "https://github.com/Live-Charts/Live-Charts/blob/master/LICENSE.TXT", ProjectUrl = "https://lvcharts.net/"}, - }.OrderBy(ec => ec.Name).ToList(); - } - - public string Version => _fvi.FileVersion; - - public string Title => ((AssemblyTitleAttribute)Attribute.GetCustomAttribute(_assembly, typeof(AssemblyTitleAttribute), false))?.Title ?? "error retrieving assembly title"; - - public List Authors => new List { new Author("Sacha Bruttin", "sachabruttin"), new Author("savbace", "savbace")}; - - public string LicenseUrl => "https://github.com/sachabruttin/CosmosDbExplorer/blob/master/LICENSE"; - - public string ProjectUrl => "https://www.bruttin.com/CosmosDbExplorer"; - - public List ExternalComponents { get; } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/AccountSettingsViewModel.cs b/src/CosmosDbExplorer/ViewModel/AccountSettingsViewModel.cs deleted file mode 100644 index cb931e5..0000000 --- a/src/CosmosDbExplorer/ViewModel/AccountSettingsViewModel.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System; -using System.ComponentModel; -using System.Windows.Media; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Messages; -using CosmosDbExplorer.Services; -using FluentValidation; -using GalaSoft.MvvmLight.Messaging; -using Validar; - -namespace CosmosDbExplorer.ViewModel -{ - [InjectValidation] - public class AccountSettingsViewModel : WindowViewModelBase - { - private RelayCommand _addAccountCommand; - private readonly IDialogService _dialogService; - private readonly ISettingsService _settingsService; - private bool _useLocalEmulator; - private Connection _connection; - - public AccountSettingsViewModel(IMessenger messenger, IDialogService dialogService, ISettingsService settingsService, IUIServices uiServices) - : base(messenger, uiServices) - { - _dialogService = dialogService; - _settingsService = settingsService; - } - - public void SetConnection(Connection connection) - { - _connection = connection; - - AccountEndpoint = _connection.DatabaseUri?.ToString(); - AccountSecret = _connection.AuthenticationKey; - Label = _connection.Label; - UseLocalEmulator = _connection.IsLocalEmulator(); - ConnectionType = _connection.ConnectionType; - AccentColor = _connection.AccentColor; - EnableEndpointDiscovery = _connection.EnableEndpointDiscovery; - } - - public string Title => "Account Settings"; - public string AccountEndpoint { get; set; } - public string AccountSecret { get; set; } - public string Label { get; set; } - public ConnectionType ConnectionType { get; set; } - public bool EnableEndpointDiscovery { get; set; } - public Color? AccentColor { get; set; } - - public void OnAccentColorChanged() - { - if (AccentColor != null && AccentColor.Value.Equals(Colors.Transparent)) - { - AccentColor = null; - } - } - - public bool UseLocalEmulator - { - get - { - return _useLocalEmulator; - } - set - { - _useLocalEmulator = value; - - if (_useLocalEmulator) - { - AccountEndpoint = Constants.Emulator.Endpoint.ToString(); - AccountSecret = Constants.Emulator.Secret; - } - else - { - AccountEndpoint = null; - AccountSecret = null; - } - - RaisePropertyChanged(() => UseLocalEmulator); - } - } - - /// - /// Gets the MyCommand. - /// - public RelayCommand AddAccountCommand - { - get - { - return _addAccountCommand - ?? (_addAccountCommand = new RelayCommand( - async () => - { - try - { - IsBusy = true; - var connection = new Connection(_connection.Id, Label, new Uri(AccountEndpoint), AccountSecret, ConnectionType, EnableEndpointDiscovery, AccentColor); - await _settingsService.SaveConnectionAsync(connection).ConfigureAwait(true); - MessengerInstance.Send(new ConnectionSettingSavedMessage(connection)); - - Close(); - } - catch (Exception ex) - { - await _dialogService.ShowError(ex, "Error saving connection", null, null).ConfigureAwait(false); - } - finally - { - IsBusy = false; - } - }, - () => !((INotifyDataErrorInfo)this).HasErrors)); - } - } - } - - public class AccountSettingsViewModelValidator : AbstractValidator - { - public AccountSettingsViewModelValidator() - { - RuleFor(x => x.AccountEndpoint).NotEmpty().When(x => !x.UseLocalEmulator); - RuleFor(x => x.AccountSecret).NotEmpty().When(x => !x.UseLocalEmulator); - RuleFor(x => x.Label).NotEmpty().When(x => !x.UseLocalEmulator); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/AddCollectionViewModel.cs b/src/CosmosDbExplorer/ViewModel/AddCollectionViewModel.cs deleted file mode 100644 index e98c93d..0000000 --- a/src/CosmosDbExplorer/ViewModel/AddCollectionViewModel.cs +++ /dev/null @@ -1,158 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Linq; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Extensions; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Services; -using FluentValidation; -using GalaSoft.MvvmLight.Messaging; -using Microsoft.Azure.Documents; -using Validar; - -namespace CosmosDbExplorer.ViewModel -{ - [InjectValidation] - public class AddCollectionViewModel : WindowViewModelBase - { - private RelayCommand _saveCommand; - private readonly IDocumentDbService _dbService; - private List _databases; - private string _selectedDatabase; - private readonly IDialogService _dialogService; - - public AddCollectionViewModel(IMessenger messenger, IDialogService dialogService, IDocumentDbService dbService, IUIServices uiServices) - : base(messenger, uiServices) - { - IsFixedStorage = true; - Throughput = 400; - Title = "New Collection"; - _dbService = dbService; - DatabaseNames = new ObservableCollection(); - _dialogService = dialogService; - } - - public string Title { get; } - - public Connection Connection { get; set; } - - public string CollectionId { get; set; } - - public bool IsFixedStorage { get; set; } - - public void OnIsFixedStorageChanged() - { - if (Throughput > 10000) - { - Throughput = 10000; - } - - RaisePropertyChanged(() => MinThroughput); - RaisePropertyChanged(() => MaxThroughput); - } - - public bool IsUnlimitedStorage { get; set; } - - public void OnIsUnlimitedStorageChanged() - { - if (Throughput < 1000) - { - Throughput = 1000; - } - - RaisePropertyChanged(() => MinThroughput); - RaisePropertyChanged(() => MaxThroughput); - } - - public string PartitionKey { get; set; } - - public int MaxThroughput => IsFixedStorage ? 10000 : 100000; - - public int MinThroughput => IsFixedStorage ? 400 : 1000; - - public int Throughput { get; set; } - - public void OnThroughputChanged() - { - const decimal hourly = 0.00008m; - EstimatedPrice = $"${hourly * Throughput:N3} hourly / {hourly * Throughput * 24:N2} daily."; - } - - public RelayCommand SaveCommand - { - get - { - return _saveCommand - ?? (_saveCommand = new RelayCommand( - async () => - { - IsBusy = true; - - var collection = new DocumentCollection { Id = CollectionId.Trim() }; - - if (IsUnlimitedStorage) - { - collection.PartitionKey.Paths.Add(PartitionKey); - } - - try - { - var db = Databases.Find(_ => _.Id == SelectedDatabase.Trim()) ?? new Database { Id = SelectedDatabase.Trim() }; - await _dbService.CreateCollectionAsync(Connection, db, collection, Throughput).ConfigureAwait(true); - IsBusy = false; - Close(); - } - catch (DocumentClientException clientEx) - { - await _dialogService.ShowError(clientEx.Parse(), "Error", "ok", null).ConfigureAwait(false); - } - catch (Exception ex) - { - await _dialogService.ShowError(ex, "Error", "ok", null).ConfigureAwait(false); - } - }, - () => !((INotifyDataErrorInfo)this).HasErrors)); - } - } - - public List Databases - { - get { return _databases; } - set - { - _databases = value; - _databases?.ForEach(db => DatabaseNames.Add(db.Id)); - } - } - - public ObservableCollection DatabaseNames { get; protected set; } - - public string SelectedDatabase - { - get { return _selectedDatabase; } - set - { - _selectedDatabase = value; - RaisePropertyChanged(() => SelectedDatabase); - } - } - - public string EstimatedPrice { get; set; } - } - - public class AddCollectionViewModelValidator : AbstractValidator - { - public AddCollectionViewModelValidator() - { - RuleFor(x => x.CollectionId).NotEmpty(); - RuleFor(x => x.SelectedDatabase).NotEmpty(); - RuleFor(x => x.Throughput).NotEmpty() - .Must(throughput => throughput % 100 == 0) - .WithMessage("Throughput must be a multiple of 100"); - RuleFor(x => x.PartitionKey).NotEmpty() - .When(x => x.IsUnlimitedStorage); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/Assets/AssetTabViewModelBase.cs b/src/CosmosDbExplorer/ViewModel/Assets/AssetTabViewModelBase.cs deleted file mode 100644 index 63db081..0000000 --- a/src/CosmosDbExplorer/ViewModel/Assets/AssetTabViewModelBase.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System; -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Extensions; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Messages; -using CosmosDbExplorer.Services; -using CosmosDbExplorer.ViewModel.Interfaces; -using GalaSoft.MvvmLight.Messaging; -using GalaSoft.MvvmLight.Threading; -using ICSharpCode.AvalonEdit.Document; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.ViewModel.Assets -{ - public abstract class AssetTabViewModelBase : PaneWithZoomViewModel, IAssetTabCommand - where TNode : TreeViewItemViewModel, IAssetNode - where TResource : Resource - { - private readonly IDialogService _dialogService; - private readonly IDocumentDbService _dbService; - - private DocumentCollection _collection; - private RelayCommand _discardCommand; - private RelayCommand _saveCommand; - private RelayCommand _deleteCommand; - - protected AssetTabViewModelBase(IMessenger messenger, IDialogService dialogService, IDocumentDbService dbService, IUIServices uiServices) - : base(messenger, uiServices) - { - Content = new TextDocument(GetDefaultContent()); - _dialogService = dialogService; - _dbService = dbService; - Header = GetDefaultHeader(); - Title = GetDefaultTitle(); - ContentId = Guid.NewGuid().ToString(); - } - - protected abstract string GetDefaultHeader(); - protected abstract string GetDefaultTitle(); - protected abstract string GetDefaultContent(); - protected abstract void SetInformationImpl(TResource resource); - protected abstract Task SaveAsyncImpl(IDocumentDbService dbService); - protected abstract Task DeleteAsyncImpl(IDocumentDbService dbService); - - protected string AltLink { get; set; } - - public TextDocument Content { get; set; } - - public override void Load(string contentId, TNode node, Connection connection, DocumentCollection collection) - { - ContentId = contentId; - Node = node; - Connection = connection; - Collection = collection; - AccentColor = connection.AccentColor; - SetInformation(Node?.Resource); - } - - public TNode Node { get; protected set; } - - protected void SetInformation(TResource resource) - { - if (resource != null) - { - Id = resource.Id; - AltLink = resource.AltLink; - ContentId = AltLink; - Header = resource.Id; - SetInformationImpl(resource); - } - } - - public Connection Connection { get; set; } - - public DocumentCollection Collection - { - get { return _collection; } - set - { - _collection = value; - var split = value.AltLink.Split(new char[] { '/' }); - ToolTip = $"{split[1]}>{split[3]}"; - } - } - - public string Id { get; set; } - - public bool IsDirty { get; set; } - - public bool IsNewDocument => AltLink == null; - - protected void SetText(string content) - { - DispatcherHelper.RunAsync(() => - { - Content = new TextDocument(content); - IsDirty = false; - }); - } - - public RelayCommand DiscardCommand - { - get - { - return _discardCommand - ?? (_discardCommand = new RelayCommand( - () => - { - if (IsNewDocument) - { - SetText(Constants.Default.UserDefiniedFunction); - } - else - { - SetInformation(Node.Resource); - } - }, - () => IsDirty)); - } - } - - public RelayCommand SaveCommand - { - get - { - return _saveCommand - ?? (_saveCommand = new RelayCommand( - async () => - { - try - { - var resource = await SaveAsyncImpl(_dbService).ConfigureAwait(false); - MessengerInstance.Send(new UpdateOrCreateNodeMessage(resource, Collection, AltLink)); - SetInformation(resource); - } - catch (DocumentClientException clientEx) - { - await _dialogService.ShowError(clientEx.Parse(), "Error", "ok", null).ConfigureAwait(false); - } - catch (Exception ex) - { - await _dialogService.ShowError(ex, "Error", "ok", null).ConfigureAwait(false); - } - }, - () => IsDirty)); - } - } - - public RelayCommand DeleteCommand - { - get - { - return _deleteCommand - ?? (_deleteCommand = new RelayCommand( - async () => - { - await _dialogService.ShowMessage("Are you sure...", "Delete", null, null, async confirm => - { - if (confirm) - { - await DeleteAsyncImpl(_dbService).ConfigureAwait(false); - MessengerInstance.Send(new RemoveNodeMessage(AltLink)); - MessengerInstance.Send(new CloseDocumentMessage(this)); - } - }).ConfigureAwait(false); - }, - () => !IsNewDocument)); - } - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/Assets/StoredProcedureTabViewModel.cs b/src/CosmosDbExplorer/ViewModel/Assets/StoredProcedureTabViewModel.cs deleted file mode 100644 index feb3175..0000000 --- a/src/CosmosDbExplorer/ViewModel/Assets/StoredProcedureTabViewModel.cs +++ /dev/null @@ -1,253 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Extensions; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Infrastructure.Validar; -using CosmosDbExplorer.Services; -using CosmosDbExplorer.Services.DialogSettings; -using FluentValidation; -using GalaSoft.MvvmLight.Ioc; -using GalaSoft.MvvmLight.Messaging; -using GalaSoft.MvvmLight.Threading; -using Microsoft.Azure.Documents; -using Newtonsoft.Json.Linq; -using Validar; - -namespace CosmosDbExplorer.ViewModel.Assets -{ - [InjectValidation] - public class StoredProcedureTabViewModel : AssetTabViewModelBase - { - private RelayCommand _executeCommand; - private RelayCommand _removeParameterCommand; - private RelayCommand _addParameterCommand; - private RelayCommand _browseParameterCommand; - private RelayCommand _saveLocalCommand; - private RelayCommand _goToNextPageCommand; - private readonly IDialogService _dialogService; - private readonly IDocumentDbService _dbService; - private readonly StatusBarItem _requestChargeStatusBarItem; - - public StoredProcedureTabViewModel(IMessenger messenger, IDialogService dialogService, IDocumentDbService dbService, IUIServices uiServices) - : base(messenger, dialogService, dbService, uiServices) - { - _dialogService = dialogService; - _dbService = dbService; - - ResultViewModel = SimpleIoc.Default.GetInstanceWithoutCaching(); - ResultViewModel.IsReadOnly = true; - - HeaderViewModel = SimpleIoc.Default.GetInstanceWithoutCaching(); - HeaderViewModel.IsReadOnly = true; - - _requestChargeStatusBarItem = new StatusBarItem(new StatusBarItemContext { Value = RequestCharge, IsVisible = IsBusy }, StatusBarItemType.SimpleText, "Request Charge", System.Windows.Controls.Dock.Left); - StatusBarItems.Add(_requestChargeStatusBarItem); - } - - protected override string GetDefaultHeader() { return "New Stored Procedure"; } - protected override string GetDefaultTitle() { return "Stored Procedure"; } - protected override string GetDefaultContent() { return Constants.Default.StoredProcedure; } - - public override void Load(string contentId, StoredProcedureNodeViewModel node, Connection connection, DocumentCollection collection) - { - IsCollectionPartitioned = collection.PartitionKey.Paths.Count > 0; - - base.Load(contentId, node, connection, collection); - } - - protected override void SetInformationImpl(StoredProcedure resource) - { - SetText(resource.Body); - } - - protected override Task SaveAsyncImpl(IDocumentDbService dbService) - { - return dbService.SaveStoredProcedureAsync(Connection, Collection, Id, Content.Text, AltLink); - } - - protected override Task DeleteAsyncImpl(IDocumentDbService dbService) - { - return dbService.DeleteStoredProcedureAsync(Connection, AltLink); - } - - public string Log { get; protected set; } - - public JsonViewerViewModel ResultViewModel { get; set; } - - public HeaderEditorViewModel HeaderViewModel { get; set; } - - public string RequestCharge { get; set; } - - public void OnRequestChargeChanged() - { - _requestChargeStatusBarItem.DataContext.Value = RequestCharge; - } - - protected override void OnIsBusyChanged() - { - _requestChargeStatusBarItem.DataContext.IsVisible = !IsBusy; - - base.OnIsBusyChanged(); - } - - public string PartitionKey { get; set; } - - public bool IsCollectionPartitioned { get; protected set; } - - public ObservableCollection Parameters { get; } = new ObservableCollection(); - - public RelayCommand AddParameterCommand - { - get - { - return _addParameterCommand ?? (_addParameterCommand = new RelayCommand( - () => Parameters.Add(new StoredProcParameterViewModel()), - () => !IsBusy && !IsDirty)); - } - } - - public RelayCommand RemoveParameterCommand - { - get - { - return _removeParameterCommand ?? (_removeParameterCommand = new RelayCommand( - item => - { - Parameters.Remove(item); - item.Dispose(); - }, - item => !IsBusy & !IsDirty)); - } - } - - public RelayCommand BrowseParameterCommand - { - get - { - return _browseParameterCommand ?? (_browseParameterCommand = new RelayCommand( - async item => - { - var options = new OpenFileDialogSettings - { - Title = "Select file...", - DefaultExt = "json", - Multiselect = false, Filter = "JSON|*.json" - }; - - await _dialogService.ShowOpenFileDialog(options, (confirm, result) => - { - if (confirm && item is StoredProcParameterViewModel vm) - { - vm.FileName = result.FileName; - } - }).ConfigureAwait(false); - }, - item => !IsBusy & !IsDirty)); - } - } - - public RelayCommand ExecuteCommand - { - get - { - return _executeCommand ?? (_executeCommand = new RelayCommand( - async () => - { - try - { - IsBusy = true; - var result = await _dbService.ExecuteStoreProcedureAsync(Connection, AltLink, Parameters.Select(p => p.GetValue()).ToArray(), PartitionKey).ConfigureAwait(false); - RequestCharge = $"Request Charge: {result.RequestCharge:N2}"; - - Log += $"{DateTime.Now:T} : {WebUtility.UrlDecode(result.ScriptLog)}{Environment.NewLine}"; - - ResultViewModel.SetText(result.Response, false); - HeaderViewModel.SetText(result.ResponseHeaders, false); - } - catch (DocumentClientException clientEx) - { - await _dialogService.ShowError(clientEx.Parse(), "Error", "ok", null).ConfigureAwait(false); - } - catch (Exception ex) - { - await _dialogService.ShowError(ex, "Error", "ok", null).ConfigureAwait(false); - } - finally - { - IsBusy = false; - } - }, - () => !IsBusy && !IsDirty && IsValid)); - } - } - - public RelayCommand SaveLocalCommand - { - get - { - return _saveLocalCommand ?? (_saveLocalCommand = new RelayCommand( - async () => - { - var settings = new SaveFileDialogSettings - { - DefaultExt = "json", - Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*", - AddExtension = true, - OverwritePrompt = true, - CheckFileExists = false, - Title = "Save document locally" - }; - - await _dialogService.ShowSaveFileDialog(settings, async (confirm, result) => - { - if (confirm) - { - try - { - IsBusy = true; - - await DispatcherHelper.RunAsync(() => File.WriteAllText(result.FileName, ResultViewModel.Content.Text)); - } - catch (Exception ex) - { - await _dialogService.ShowError(ex, "Error", "ok", null).ConfigureAwait(false); - } - finally - { - IsBusy = false; - } - } - }).ConfigureAwait(false); - }, - () => !IsBusy && !string.IsNullOrEmpty(ResultViewModel.Content?.Text))); - } - } - - public RelayCommand GoToNextPageCommand - { - get - { - return _goToNextPageCommand ?? (_goToNextPageCommand = new RelayCommand( - () => throw new NotImplementedException(), - () => false)); - } - } - - public bool IsValid => !((INotifyDataErrorInfo)this).HasErrors; - } - - public class StoredProcedureTabViewModelValidator : AbstractValidator - { - public StoredProcedureTabViewModelValidator() - { - When(x => x.IsCollectionPartitioned, - () => RuleFor(x => x.PartitionKey).NotEmpty().SetValidator(new PartitionKeyValidator())); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/Assets/TriggerTabViewModel.cs b/src/CosmosDbExplorer/ViewModel/Assets/TriggerTabViewModel.cs deleted file mode 100644 index 72da8f1..0000000 --- a/src/CosmosDbExplorer/ViewModel/Assets/TriggerTabViewModel.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Services; -using GalaSoft.MvvmLight.Messaging; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.ViewModel.Assets -{ - public class TriggerTabViewModel : AssetTabViewModelBase - { - private TriggerType _triggerType; - private TriggerOperation _triggerOperation; - - public TriggerTabViewModel(IMessenger messenger, IDialogService dialogService, IDocumentDbService dbService, IUIServices uiServices) - : base(messenger, dialogService, dbService, uiServices) - { - } - - protected override string GetDefaultHeader() { return "New Trigger"; } - protected override string GetDefaultTitle() { return "Trigger"; } - protected override string GetDefaultContent() { return Constants.Default.Trigger; } - - protected override void SetInformationImpl(Trigger resource) - { - TriggerOperation = resource.TriggerOperation; - TriggerType = resource.TriggerType; - SetText(resource.Body); - } - - public TriggerType TriggerType - { - get { return _triggerType; } - set - { - if (value != _triggerType) - { - _triggerType = value; - IsDirty = true; - RaisePropertyChanged(() => TriggerType); - } - } - } - - public TriggerOperation TriggerOperation - { - get { return _triggerOperation; } - set - { - if (value != _triggerOperation) - { - _triggerOperation = value; - IsDirty = true; - RaisePropertyChanged(() => TriggerOperation); - } - } - } - - protected override Task SaveAsyncImpl(IDocumentDbService dbService) - { - return dbService.SaveTriggerAsync(Connection, Collection, Id, Content.Text, TriggerType, TriggerOperation, AltLink); - } - - protected override Task DeleteAsyncImpl(IDocumentDbService dbService) - { - return dbService.DeleteTriggerAsync(Connection, AltLink); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/Assets/UserDefFuncTabViewModel.cs b/src/CosmosDbExplorer/ViewModel/Assets/UserDefFuncTabViewModel.cs deleted file mode 100644 index b67721c..0000000 --- a/src/CosmosDbExplorer/ViewModel/Assets/UserDefFuncTabViewModel.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Services; -using GalaSoft.MvvmLight.Messaging; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.ViewModel.Assets -{ - public class UserDefFuncTabViewModel : AssetTabViewModelBase - { - public UserDefFuncTabViewModel(IMessenger messenger, IDialogService dialogService, IDocumentDbService dbService, IUIServices uiServices) - : base(messenger, dialogService, dbService, uiServices) - { - } - - protected override string GetDefaultHeader() { return "New User Defined Function"; } - protected override string GetDefaultTitle() { return "User Defined Function"; } - protected override string GetDefaultContent() { return Constants.Default.UserDefiniedFunction; } - - protected override void SetInformationImpl(UserDefinedFunction resource) - { - SetText(resource.Body); - } - - protected override Task SaveAsyncImpl(IDocumentDbService dbService) - { - return dbService.SaveUdfAsync(Connection, Collection, Id, Content.Text, AltLink); - } - - protected override Task DeleteAsyncImpl(IDocumentDbService dbService) - { - return dbService.DeleteUdfAsync(Connection, AltLink); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/CollectionMetricsTabViewModel.cs b/src/CosmosDbExplorer/ViewModel/CollectionMetricsTabViewModel.cs deleted file mode 100644 index 792546c..0000000 --- a/src/CosmosDbExplorer/ViewModel/CollectionMetricsTabViewModel.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Extensions; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Services; -using CosmosDbExplorer.ViewModel.Interfaces; -using GalaSoft.MvvmLight.Messaging; -using GalaSoft.MvvmLight.Threading; -using LiveCharts; -using LiveCharts.Configurations; -using LiveCharts.Wpf; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.ViewModel -{ - public class CollectionMetricsTabViewModel : PaneViewModel, ICanRefreshTab - { - private readonly IDocumentDbService _dbService; - private DocumentCollection _collection; - private Connection _connection; - private RelayCommand _refreshCommand; - private readonly IDialogService _dialogService; - private readonly StatusBarItem _requestChargeStatusBarItem; - - public CollectionMetricsTabViewModel(IMessenger messenger, IUIServices uiServices, - IDialogService dialogService, - IDocumentDbService dbService) - : base(messenger, uiServices) - { - _dbService = dbService; - _dialogService = dialogService; - - Title = "Collection Metrics"; - Header = Title; - - _requestChargeStatusBarItem = new StatusBarItem(new StatusBarItemContext { Value = RequestCharge, IsVisible = IsBusy }, StatusBarItemType.SimpleText, "Request Charge", System.Windows.Controls.Dock.Left); - StatusBarItems.Add(_requestChargeStatusBarItem); - - ChartConfiguration(); - } - - private void ChartConfiguration() - { - // Chart configuration - var wrapper = Mappers.Xy() - .X((value, index) => index) - .Y(value => value.SizeInKB); - - Formatter = value => $"{Math.Round(value / (1024 * 1024.0), 3)} GiB"; - - Charting.For(wrapper); - } - - public override async void Load(string contentId, CollectionMetricsNodeViewModel node, Connection connection, DocumentCollection collection) - { - ContentId = contentId; - _connection = connection; - _collection = collection; - var split = _collection.AltLink.Split(new char[] { '/' }); - ToolTip = $"{split[1]}>{split[3]}"; - AccentColor = _connection.AccentColor; - - await LoadMetrics().ConfigureAwait(false); - } - - public string RequestCharge { get; set; } - - public void OnRequestChargeChanged() - { - _requestChargeStatusBarItem.DataContext.Value = RequestCharge; - } - - protected override void OnIsBusyChanged() - { - _requestChargeStatusBarItem.DataContext.IsVisible = !IsBusy; - - base.OnIsBusyChanged(); - } - - public CollectionMetric Metrics { get; set; } - - public SeriesCollection PartitionSizeSeries { get; set; } - - public string[] Labels { get; set; } - - public Func Formatter { get; set; } - - public RelayCommand RefreshCommand - { - get - { - return _refreshCommand - ?? (_refreshCommand = new RelayCommand( - async () => await LoadMetrics().ConfigureAwait(false), - () => !IsBusy)); - } - } - - private async Task LoadMetrics() - { - IsBusy = true; - - try - { - Metrics = await _dbService.GetPartitionMetricsAsync(_connection, _collection).ConfigureAwait(false); - RequestCharge = $"Request Charge: {Metrics.RequestCharge:N2}"; - - await DispatcherHelper.RunAsync(() => - { - var sorted = Metrics.PartitionMetrics.OrderBy(pm => int.Parse(pm.PartitionKeyRangeId)).ToArray(); - Labels = sorted.Select(pm => pm.PartitionKeyRangeId).ToArray(); - PartitionSizeSeries = new SeriesCollection - { - new ColumnSeries - { - Title = "Size", - Values = new ChartValues(sorted) - } - }; - }); - } - catch (DocumentClientException clientEx) - { - await _dialogService.ShowError(clientEx.Parse(), "Error", "ok", null).ConfigureAwait(false); - } - catch (Exception ex) - { - await _dialogService.ShowError(ex, "Error", "ok", null).ConfigureAwait(false); - } - finally - { - IsBusy = false; - } - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/AssetRootNodeViewModelBase.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/AssetRootNodeViewModelBase.cs deleted file mode 100644 index 4527126..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/AssetRootNodeViewModelBase.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Media; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Messages; -using CosmosDbExplorer.Services; -using GalaSoft.MvvmLight.Ioc; -using GalaSoft.MvvmLight.Messaging; -using GalaSoft.MvvmLight.Threading; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.ViewModel -{ - public abstract class AssetRootNodeViewModelBase : TreeViewItemViewModel, ICanRefreshNode, IHaveCollectionNodeViewModel - where TResource : Resource - { - private RelayCommand _refreshCommand; - - protected AssetRootNodeViewModelBase(CollectionNodeViewModel parent) - : base(parent, parent.MessengerInstance, true) - { - DbService = SimpleIoc.Default.GetInstance(); - MessengerInstance.Register>(this, InnerOnUpdateOrCreateNodeMessage); - } - - public string Name { get; protected set; } - - public new CollectionNodeViewModel Parent - { - get { return base.Parent; } - } - - public RelayCommand RefreshCommand - { - get - { - return _refreshCommand - ?? (_refreshCommand = new RelayCommand( - async () => - { - Children.Clear(); - await LoadChildren().ConfigureAwait(false); - })); - } - } - - public CollectionNodeViewModel CollectionNode => Parent; - - protected IDocumentDbService DbService { get; } - - private void InnerOnUpdateOrCreateNodeMessage(UpdateOrCreateNodeMessage message) - { - if (message.Collection == CollectionNode.Collection) - { - OnUpdateOrCreateNodeMessage(message); - } - } - - protected abstract void OnUpdateOrCreateNodeMessage(UpdateOrCreateNodeMessage message); - } - - public abstract class AssetNodeViewModelBase : - TreeViewItemViewModel, ICanEditDelete, IAssetNode - where TResource : Resource - where TParent : AssetRootNodeViewModelBase - { - protected AssetNodeViewModelBase(TParent parent, TResource resource) - : base(parent, parent.MessengerInstance, false) - { - Resource = resource; - } - - public string Name => Resource.Id; - - public string ContentId => Resource.AltLink; - - public Color? AccentColor => Parent.Parent.Parent.Parent.Connection.AccentColor; - - public TResource Resource { get; set; } - - public RelayCommand EditCommand => new RelayCommand(async () => await EditCommandImpl().ConfigureAwait(false)); - - protected abstract Task EditCommandImpl(); - - public RelayCommand DeleteCommand - { - get - { - return new RelayCommand(async () => await DeleteCommandImpl().ConfigureAwait(false)); - } - } - - protected abstract Task DeleteCommandImpl(); - - protected IDialogService DialogService => SimpleIoc.Default.GetInstance(); - - protected IDocumentDbService DbService => SimpleIoc.Default.GetInstance(); - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/CollectionMetricsNodeViewModel.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/CollectionMetricsNodeViewModel.cs deleted file mode 100644 index f471c66..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/CollectionMetricsNodeViewModel.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Messages; - -namespace CosmosDbExplorer.ViewModel -{ - - public class CollectionMetricsNodeViewModel : TreeViewItemViewModel, IHaveCollectionNodeViewModel, IContent - { - private RelayCommand _openCommand; - - public CollectionMetricsNodeViewModel(CollectionNodeViewModel parent) - : base(parent, parent.MessengerInstance, false) - { - Name = "Collection Metrics"; - } - - public string Name { get; set; } - - public RelayCommand OpenCommand - { - get - { - return _openCommand - ?? (_openCommand = new RelayCommand( - () => { - IsSelected = false; - MessengerInstance.Send(new OpenCollectionMetricsViewMessage(this, Parent.Parent.Parent.Connection, Parent.Collection)); - })); - } - } - - public CollectionNodeViewModel CollectionNode => Parent; - - public string ContentId => Parent.Collection.SelfLink + "/Metrics"; - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/CollectionNodeViewModel.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/CollectionNodeViewModel.cs deleted file mode 100644 index 9728a0b..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/CollectionNodeViewModel.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System; -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Messages; -using GalaSoft.MvvmLight.Threading; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.ViewModel -{ - public class CollectionNodeViewModel : ResourceNodeViewModelBase, IHaveCollectionNodeViewModel, IContent - { - private RelayCommand _openSqlQueryCommand; - private RelayCommand _openImportDocumentCommand; - private RelayCommand _clearAllDocumentsCommand; - private RelayCommand _newStoredProcedureCommand; - private RelayCommand _newUdfCommand; - private RelayCommand _newTriggerCommand; - private RelayCommand _deleteCollectionCommand; - private RelayCommand _recreateAsEmptyCommand; - - public CollectionNodeViewModel(DocumentCollection collection, DatabaseNodeViewModel parent) - : base(collection, parent, true) - { - Collection = collection; - } - - protected override async Task LoadChildren() - { - await DispatcherHelper.RunAsync(() => - { - Children.Add(new DocumentNodeViewModel(this)); - Children.Add(new ScaleSettingsNodeViewModel(this)); - Children.Add(new StoredProcedureRootNodeViewModel(this)); - Children.Add(new UserDefFuncRootNodeViewModel(this)); - Children.Add(new TriggerRootNodeViewModel(this)); - Children.Add(new CollectionMetricsNodeViewModel(this)); - }); - } - - public DocumentCollection Collection { get; } - - public RelayCommand OpenSqlQueryCommand - { - get - { - return _openSqlQueryCommand - ?? (_openSqlQueryCommand = new RelayCommand(() => MessengerInstance.Send(new OpenQueryViewMessage(this, Parent.Parent.Connection, Collection)))); - } - } - - public RelayCommand ClearAllDocumentsCommand - { - get - { - return _clearAllDocumentsCommand - ?? (_clearAllDocumentsCommand = new RelayCommand( - async () => - { - await DialogService.ShowMessage($"All documents will be removed from the collection {Parent.Name}.\n\nIf you have a lot of documents, this could take a while and be costly and it is perhaps preferable to use the 'Recreate' option.\n\nAre you sure you want to continue?", - "Cleanup collection", null, null, - async confirm => - { - if (confirm) - { - MessengerInstance.Send(new IsBusyMessage(true)); - await DbService.CleanCollectionAsync(Parent.Parent.Connection, Collection).ConfigureAwait(false); - MessengerInstance.Send(new IsBusyMessage(false)); - await DispatcherHelper.RunAsync(async () => await DialogService.ShowMessageBox($"Collection {Parent.Name} is now empty.", "Cleanup collection").ConfigureAwait(false)); - } - }).ConfigureAwait(false); - })); - } - } - - public RelayCommand RecreateAsEmptyCommand - { - get - { - return _recreateAsEmptyCommand - ?? (_recreateAsEmptyCommand = new RelayCommand( - async () => - { - await DialogService.ShowMessage($"Collection will be deleted and recreated with the same parameters and assets:\n\t- Stored Procedures\n\t- Triggers\n\t- User Defined Functions\n\nThis is fast and cost efficient but could affect your application(s) availability.\n\nAre you sure you want to continue?", - "Recreate empty collection", null, null, - async confirm => - { - if (confirm) - { - MessengerInstance.Send(new IsBusyMessage(true)); - await DbService.RecreateCollectionAsync(Parent.Parent.Connection, Parent.Database, Collection).ConfigureAwait(false); - MessengerInstance.Send(new IsBusyMessage(false)); - Parent.RefreshCommand.Execute(null); - await DispatcherHelper.RunAsync(async () => await DialogService.ShowMessageBox($"Collection {Parent.Name} is now empty.", "Cleanup collection").ConfigureAwait(false)); - } - }).ConfigureAwait(false); - })); - } - } - - public RelayCommand OpenImportDocumentCommand - { - get - { - return _openImportDocumentCommand - ?? (_openImportDocumentCommand = new RelayCommand( - () => MessengerInstance.Send(new OpenImportDocumentViewMessage(this, Parent.Parent.Connection, Collection)))); - } - } - - public RelayCommand NewStoredProcedureCommand - { - get - { - return _newStoredProcedureCommand - ?? (_newStoredProcedureCommand = new RelayCommand( - () => MessengerInstance.Send(new EditStoredProcedureMessage(null, Parent.Parent.Connection, Collection)) - )); - } - } - - public RelayCommand NewUdfCommand - { - get - { - return _newUdfCommand - ?? (_newUdfCommand = new RelayCommand( - () => MessengerInstance.Send(new EditUserDefFuncMessage(null, Parent.Parent.Connection, Collection)) - )); - } - } - - public RelayCommand NewTriggerCommand - { - get - { - return _newTriggerCommand - ?? (_newTriggerCommand = new RelayCommand( - () => MessengerInstance.Send(new EditTriggerMessage(null, Parent.Parent.Connection, Collection)) - )); - } - } - - public RelayCommand DeleteCollectionCommand - { - get - { - return _deleteCollectionCommand - ?? (_deleteCollectionCommand = new RelayCommand( - async () => - { - await DialogService.ShowMessage("Are you sure you want to delete this collection?", "Delete", null, null, - async confirm => - { - if (confirm) - { - MessengerInstance.Send(new IsBusyMessage(true)); - await DbService.DeleteCollectionAsync(Parent.Parent.Connection, Collection).ConfigureAwait(false); - MessengerInstance.Send(new IsBusyMessage(false)); - await DispatcherHelper.RunAsync(() => Parent.Children.Remove(this)); - } - }).ConfigureAwait(false); - } - )); - } - } - - public CollectionNodeViewModel CollectionNode => this; - - public string ContentId => Guid.NewGuid().ToString(); - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/ConnectionNodeViewModel.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/ConnectionNodeViewModel.cs deleted file mode 100644 index 99b4f18..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/ConnectionNodeViewModel.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Messages; -using CosmosDbExplorer.Services; -using CosmosDbExplorer.Views; -using GalaSoft.MvvmLight.Messaging; -using GalaSoft.MvvmLight.Threading; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.ViewModel -{ - public class ConnectionNodeViewModel : TreeViewItemViewModel, ICanRefreshNode - { - private RelayCommand _editConnectionCommand; - private RelayCommand _removeConnectionCommand; - private readonly IDocumentDbService _dbService; - private readonly IDialogService _dialogService; - private readonly ISettingsService _settingsService; - private RelayCommand _refreshCommand; - private RelayCommand _addNewCollectionCommand; - - public ConnectionNodeViewModel(IDocumentDbService dbService, IMessenger messenger, IDialogService dialogService, ISettingsService settingsService) : base(null, messenger, true) - { - _dbService = dbService; - _dialogService = dialogService; - _settingsService = settingsService; - } - - public Connection Connection { get; set; } - - public List Databases { get; protected set; } - - public string Name => Connection.DatabaseUri.ToString(); - - protected override async Task LoadChildren() - { - try - { - IsLoading = true; - Databases = await _dbService.GetDatabasesAsync(Connection); - - foreach (var db in Databases) - { - await DispatcherHelper.RunAsync(() => Children.Add(new DatabaseNodeViewModel(db, this))); - } - } - catch (HttpRequestException ex) - { - await DispatcherHelper.RunAsync(async () => await _dialogService.ShowError(ex, "Error", null, null)); - } - finally - { - IsLoading = false; - } - } - - public RelayCommand EditConnectionCommand - { - get - { - return _editConnectionCommand - ?? (_editConnectionCommand = new RelayCommand( - async () => - { - var form = new AccountSettingsView(); - var vm = (AccountSettingsViewModel)form.DataContext; - vm.SetConnection(Connection); - - if (form.ShowDialog().GetValueOrDefault(false)) - { - Children.Clear(); - await LoadChildren(); - } - } - )); - } - } - - public RelayCommand RemoveConnectionCommand - { - get - { - return _removeConnectionCommand - ?? (_removeConnectionCommand = new RelayCommand( - async () => - { - await _dialogService.ShowMessage("Are you sure that you want to delete this connection?", "Delete connection", null, null, - async confirm => - { - if (confirm) - { - await _settingsService.RemoveConnection(Connection); - MessengerInstance.Send(new RemoveConnectionMessage(Connection)); - } - }); - } - )); - } - } - - public RelayCommand RefreshCommand - { - get - { - return _refreshCommand - ?? (_refreshCommand = new RelayCommand( - async () => - { - Children.Clear(); - await LoadChildren(); - })); - } - } - - public RelayCommand AddNewCollectionCommand - { - get - { - return _addNewCollectionCommand - ?? (_addNewCollectionCommand = new RelayCommand( - async () => - { - var form = new AddCollectionView(); - var vm = (AddCollectionViewModel)form.DataContext; - - vm.Databases = Databases; - vm.Connection = Connection; - - if (form.ShowDialog().GetValueOrDefault(false)) - { - Children.Clear(); - await LoadChildren(); - } - })); - } - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/DatabaseNodeViewModel.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/DatabaseNodeViewModel.cs deleted file mode 100644 index 796c361..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/DatabaseNodeViewModel.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Views; -using GalaSoft.MvvmLight.Threading; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.ViewModel -{ - public class DatabaseNodeViewModel : ResourceNodeViewModelBase - { - private RelayCommand _addNewCollectionCommand; - private RelayCommand _deleteDatabaseCommand; - - public DatabaseNodeViewModel(Database database, ConnectionNodeViewModel parent) - : base(database, parent, true) - { - Database = database; - } - - public Database Database { get; } - - protected override async Task LoadChildren() - { - IsLoading = true; - - var collections = await DbService.GetCollectionsAsync(Parent.Connection, Database).ConfigureAwait(false); - - await DispatcherHelper.RunAsync(() => - { - Children.Add(new UsersNodeViewModel(Database, this)); - foreach (var collection in collections) - { - Children.Add(new CollectionNodeViewModel(collection, this)); - } - }); - - IsLoading = false; - } - - public RelayCommand AddNewCollectionCommand - { - get - { - return _addNewCollectionCommand - ?? (_addNewCollectionCommand = new RelayCommand( - async () => - { - var form = new AddCollectionView(); - var vm = (AddCollectionViewModel)form.DataContext; - - vm.Databases = Parent.Databases; - vm.Connection = Parent.Connection; - vm.SelectedDatabase = Database.Id; - - if (form.ShowDialog().GetValueOrDefault(false)) - { - Children.Clear(); - await LoadChildren().ConfigureAwait(false); - } - })); - } - } - - public RelayCommand DeleteDatabaseCommand - { - get - { - return _deleteDatabaseCommand - ?? (_deleteDatabaseCommand = new RelayCommand( - async () => - { - var msg = $"Are you sure you want to delete the database '{Name}' and all his content?"; - await DialogService.ShowMessage(msg, "Delete", null, null, - async confirm => - { - if (confirm) - { - UIServices.SetBusyState(true); - await DbService.DeleteDatabaseAsync(Parent.Connection, Database).ConfigureAwait(true); - Parent.Children.Remove(this); - UIServices.SetBusyState(false); - } - }).ConfigureAwait(true); - })); - } - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/DocumentNodeViewModel.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/DocumentNodeViewModel.cs deleted file mode 100644 index 62b5261..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/DocumentNodeViewModel.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Messages; - -namespace CosmosDbExplorer.ViewModel -{ - public class DocumentNodeViewModel : TreeViewItemViewModel, IHaveCollectionNodeViewModel, IContent - { - private RelayCommand _openDocumentCommand; - - public DocumentNodeViewModel(CollectionNodeViewModel parent) - : base(parent, parent.MessengerInstance, false) - { - Name = "Documents"; - } - - public string Name { get; set; } - - public RelayCommand OpenDocumentCommand - { - get - { - return _openDocumentCommand - ?? (_openDocumentCommand = new RelayCommand( - () => { - IsSelected = false; - MessengerInstance.Send(new OpenDocumentsViewMessage(this, Parent.Parent.Parent.Connection, Parent.Collection)); - })); - } - } - - public CollectionNodeViewModel CollectionNode => Parent; - - public string ContentId => Parent.Collection.SelfLink + "/Documents"; - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/IAssetNode.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/IAssetNode.cs deleted file mode 100644 index 0cea6be..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/IAssetNode.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Windows.Media; -using CosmosDbExplorer.Infrastructure.Models; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.ViewModel -{ - public interface IContent - { - string ContentId { get; } - } - - public interface IAssetNode : IContent - where T: Resource - { - Color? AccentColor { get; } - T Resource { get; set; } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/ICanRefreshNode.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/ICanRefreshNode.cs deleted file mode 100644 index 1af4ab1..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/ICanRefreshNode.cs +++ /dev/null @@ -1,9 +0,0 @@ -using CosmosDbExplorer.Infrastructure; - -namespace CosmosDbExplorer.ViewModel -{ - public interface ICanRefreshNode - { - RelayCommand RefreshCommand { get; } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/IHaveCollectionNodeViewModel.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/IHaveCollectionNodeViewModel.cs deleted file mode 100644 index fb81762..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/IHaveCollectionNodeViewModel.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CosmosDbExplorer.ViewModel -{ - public interface IHaveCollectionNodeViewModel - { - CollectionNodeViewModel CollectionNode { get; } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/PermissionNodeViewModel.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/PermissionNodeViewModel.cs deleted file mode 100644 index 519cfb7..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/PermissionNodeViewModel.cs +++ /dev/null @@ -1,48 +0,0 @@ -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Messages; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.ViewModel -{ - public class PermissionNodeViewModel : TreeViewItemViewModel, ICanRefreshNode, IContent - { - private RelayCommand _refreshCommand; - private RelayCommand _openCommand; - - public PermissionNodeViewModel(Permission permission, UserNodeViewModel parent) - : base(parent, parent.MessengerInstance, false) - { - Permission = permission; - } - - public Permission Permission { get; set; } - - public string Name => Permission?.Id; - - public string ContentId => Permission?.AltLink ?? "NewPermission"; - - public RelayCommand RefreshCommand - { - get - { - return _refreshCommand - ?? (_refreshCommand = new RelayCommand( - async () => - { - Children.Clear(); - await LoadChildren().ConfigureAwait(false); - })); - } - } - - public RelayCommand OpenCommand - { - get - { - return _openCommand ?? (_openCommand = new RelayCommand( - () => MessengerInstance.Send(new EditPermissionMessage(this, Parent.Parent.Parent.Parent.Connection, null)))); - } - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/ResourceNodeViewModelBase.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/ResourceNodeViewModelBase.cs deleted file mode 100644 index 4fd7b21..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/ResourceNodeViewModelBase.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.IO; -using System.Windows; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Services; -using GalaSoft.MvvmLight.Ioc; -using GalaSoft.MvvmLight.Threading; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.ViewModel -{ - public abstract class ResourceNodeViewModelBase : TreeViewItemViewModel, ICanRefreshNode - where TParent : TreeViewItemViewModel - { - private RelayCommand _refreshCommand; - private RelayCommand _copySelfLinkToClipboardCommand; - private RelayCommand _copyResourceToClipboardCommand; - private RelayCommand _copyAltLinkToClipboardCommand; - - protected ResourceNodeViewModelBase(Resource resource, TParent parent, bool lazyLoadChildren) - : base(parent, parent.MessengerInstance, lazyLoadChildren) - { - Resource = resource; - } - - public string Name => Resource.Id; - - public RelayCommand RefreshCommand - { - get - { - return _refreshCommand - ?? (_refreshCommand = new RelayCommand( - async () => - { - await DispatcherHelper.RunAsync(async () => - { - Children.Clear(); - await LoadChildren().ConfigureAwait(false); - }); - })); - } - } - - public RelayCommand CopySelfLinkToClipboardCommand - { - get - { - return _copySelfLinkToClipboardCommand - ?? (_copySelfLinkToClipboardCommand = new RelayCommand( - () => Clipboard.SetText(Resource.SelfLink) - )); - } - } - - public RelayCommand CopyAltLinkToClipboardCommand - { - get - { - return _copyAltLinkToClipboardCommand - ?? (_copyAltLinkToClipboardCommand = new RelayCommand( - () => Clipboard.SetText(Resource.AltLink) - )); - } - } - - public RelayCommand CopyResourceToClipboardCommand - { - get - { - return _copyResourceToClipboardCommand - ?? (_copyResourceToClipboardCommand = new RelayCommand( - () => - { - using (var stream = new MemoryStream()) - { - Resource.SaveTo(stream, SerializationFormattingPolicy.Indented); - var json = System.Text.Encoding.UTF8.GetString(stream.ToArray()); - Clipboard.SetText(json); - } - } - )); - } - } - - protected Resource Resource { get; set; } - - protected IDocumentDbService DbService => SimpleIoc.Default.GetInstance(); - - protected IDialogService DialogService => SimpleIoc.Default.GetInstance(); - - protected IUIServices UIServices => SimpleIoc.Default.GetInstance(); - - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/ScaleSettingsNodeViewModel.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/ScaleSettingsNodeViewModel.cs deleted file mode 100644 index 63f7e53..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/ScaleSettingsNodeViewModel.cs +++ /dev/null @@ -1,33 +0,0 @@ -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Messages; - -namespace CosmosDbExplorer.ViewModel -{ - public class ScaleSettingsNodeViewModel : TreeViewItemViewModel, IHaveCollectionNodeViewModel, IContent - { - private RelayCommand _openCommand; - - public ScaleSettingsNodeViewModel(CollectionNodeViewModel parent) - : base(parent, parent.MessengerInstance, false) - { - Name = "Scale & Settings"; - } - - public string Name { get; private set; } - - public string ContentId => Parent.Collection.SelfLink + "/ScaleSettings"; - - public RelayCommand OpenCommand - { - get - { - return _openCommand - ?? (_openCommand = new RelayCommand( - () => MessengerInstance.Send(new OpenScaleAndSettingsViewMessage(this, Parent.Parent.Parent.Connection, Parent.Collection)))); - } - } - - public CollectionNodeViewModel CollectionNode => Parent; - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/StoredProcedureNodeViewModel.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/StoredProcedureNodeViewModel.cs deleted file mode 100644 index 5f6fe29..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/StoredProcedureNodeViewModel.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using CosmosDbExplorer.Messages; -using GalaSoft.MvvmLight.Threading; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.ViewModel -{ - public class StoredProcedureRootNodeViewModel : AssetRootNodeViewModelBase - { - public StoredProcedureRootNodeViewModel(CollectionNodeViewModel parent) - : base(parent) - { - Name = "Stored Procedures"; - } - - protected override async Task LoadChildren() - { - IsLoading = true; - - var storedProcedure = await DbService.GetStoredProceduresAsync(Parent.Parent.Parent.Connection, Parent.Collection).ConfigureAwait(false); - - foreach (var sp in storedProcedure) - { - await DispatcherHelper.RunAsync(() => Children.Add(new StoredProcedureNodeViewModel(this, sp))); - } - - IsLoading = false; - } - - protected override void OnUpdateOrCreateNodeMessage(UpdateOrCreateNodeMessage message) - { - if (message.IsNewResource) - { - var item = new StoredProcedureNodeViewModel(this, message.Resource); - DispatcherHelper.RunAsync(() => Children.Add(item)); - } - else - { - var item = Children.Cast().FirstOrDefault(i => i.Resource.AltLink == message.OldAltLink); - - if (item != null) - { - item.Resource = message.Resource; - } - } - } - } - - public class StoredProcedureNodeViewModel : AssetNodeViewModelBase - { - public StoredProcedureNodeViewModel(StoredProcedureRootNodeViewModel parent, StoredProcedure resource) - : base(parent, resource) - { - } - - protected override Task DeleteCommandImpl() - { - return DialogService.ShowMessage("Are sure you want to delete this Stored Procedure?", "Delete", null, null, - async confirm => - { - if (confirm) - { - await DbService.DeleteStoredProcedureAsync(Parent.Parent.Parent.Parent.Connection, Resource.AltLink).ConfigureAwait(false); - await DispatcherHelper.RunAsync(() => Parent.Children.Remove(this)); - MessengerInstance.Send(new CloseDocumentMessage(ContentId)); - } - }); - } - - protected override Task EditCommandImpl() - { - MessengerInstance.Send(new EditStoredProcedureMessage(this, Parent.Parent.Parent.Parent.Connection, Parent.Parent.Collection)); - return Task.FromResult(0); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/TriggerNodeViewModel.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/TriggerNodeViewModel.cs deleted file mode 100644 index fbbe317..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/TriggerNodeViewModel.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using CosmosDbExplorer.Messages; -using GalaSoft.MvvmLight.Threading; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.ViewModel -{ - public class TriggerRootNodeViewModel : AssetRootNodeViewModelBase - { - public TriggerRootNodeViewModel(CollectionNodeViewModel parent) - : base(parent) - { - Name = "Triggers"; - } - - protected override async Task LoadChildren() - { - IsLoading = true; - - var triggers = await DbService.GetTriggersAsync(Parent.Parent.Parent.Connection, Parent.Collection).ConfigureAwait(false); - - foreach (var trigger in triggers) - { - await DispatcherHelper.RunAsync(() => Children.Add(new TriggerNodeViewModel(this, trigger))); - } - - IsLoading = false; - } - - protected override void OnUpdateOrCreateNodeMessage(UpdateOrCreateNodeMessage message) - { - if (message.IsNewResource) - { - var item = new TriggerNodeViewModel(this, message.Resource); - DispatcherHelper.RunAsync(() => Children.Add(item)); - } - else - { - var item = Children.Cast().FirstOrDefault(i => i.Resource.AltLink == message.OldAltLink); - - if (item != null) - { - item.Resource = message.Resource; - } - } - } - } - - public class TriggerNodeViewModel : AssetNodeViewModelBase - { - public TriggerNodeViewModel(TriggerRootNodeViewModel parent, Trigger resource) - : base(parent, resource) - { - } - - protected override Task DeleteCommandImpl() - { - return DialogService.ShowMessage("Are sure you want to delete this Trigger?", "Delete", null, null, - async confirm => - { - if (confirm) - { - await DbService.DeleteTriggerAsync(Parent.Parent.Parent.Parent.Connection, Resource.AltLink).ConfigureAwait(false); - await DispatcherHelper.RunAsync(() => Parent.Children.Remove(this)); - MessengerInstance.Send(new CloseDocumentMessage(ContentId)); - } - }); - } - - protected override Task EditCommandImpl() - { - MessengerInstance.Send(new EditTriggerMessage(this, Parent.Parent.Parent.Parent.Connection, Parent.Parent.Collection)); - return Task.FromResult(0); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/UserDefFuncNodeViewModel.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/UserDefFuncNodeViewModel.cs deleted file mode 100644 index 757cff3..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/UserDefFuncNodeViewModel.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using CosmosDbExplorer.Messages; -using GalaSoft.MvvmLight.Threading; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.ViewModel -{ - public class UserDefFuncRootNodeViewModel : AssetRootNodeViewModelBase - { - public UserDefFuncRootNodeViewModel(CollectionNodeViewModel parent) - : base(parent) - { - Name = "User Defined Functions"; - } - - protected override async Task LoadChildren() - { - IsLoading = true; - - var function = await DbService.GetUdfsAsync(Parent.Parent.Parent.Connection, Parent.Collection).ConfigureAwait(false); - - foreach (var func in function) - { - await DispatcherHelper.RunAsync(() => Children.Add(new UserDefFuncNodeViewModel(this, func))); - } - - IsLoading = false; - } - - protected override void OnUpdateOrCreateNodeMessage(UpdateOrCreateNodeMessage message) - { - if (message.IsNewResource) - { - var item = new UserDefFuncNodeViewModel(this, message.Resource); - DispatcherHelper.RunAsync(() => Children.Add(item)); - } - else - { - var item = Children.Cast().FirstOrDefault(i => i.Resource.AltLink == message.OldAltLink); - - if (item != null) - { - item.Resource = message.Resource; - } - } - } - } - - public class UserDefFuncNodeViewModel : AssetNodeViewModelBase - { - public UserDefFuncNodeViewModel(UserDefFuncRootNodeViewModel parent, UserDefinedFunction resource) - : base(parent, resource) - { - } - - protected override Task DeleteCommandImpl() - { - return DialogService.ShowMessage("Are sure you want to delete this User Defined Function?", "Delete", null, null, - async confirm => - { - if (confirm) - { - await DbService.DeleteUdfAsync(Parent.Parent.Parent.Parent.Connection, Resource.AltLink).ConfigureAwait(false); - await DispatcherHelper.RunAsync(() => Parent.Children.Remove(this)); - MessengerInstance.Send(new CloseDocumentMessage(ContentId)); - } - }); - } - - protected override Task EditCommandImpl() - { - MessengerInstance.Send(new EditUserDefFuncMessage(this, Parent.Parent.Parent.Parent.Connection, null)); - return Task.FromResult(0); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/UserNodeViewModel.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/UserNodeViewModel.cs deleted file mode 100644 index 2e045c5..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/UserNodeViewModel.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Messages; -using CosmosDbExplorer.Services; -using GalaSoft.MvvmLight.Ioc; -using GalaSoft.MvvmLight.Threading; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.ViewModel -{ - public class UserNodeViewModel : TreeViewItemViewModel, ICanRefreshNode, IContent - { - private readonly IDocumentDbService _dbService; - private RelayCommand _refreshCommand; - private RelayCommand _openCommand; - private RelayCommand _addPermissionCommand; - - public UserNodeViewModel(User user, UsersNodeViewModel parent) - : base(parent, parent.MessengerInstance, true) - { - User = user; - _dbService = SimpleIoc.Default.GetInstance(); - } - - public string Name => User.Id; - - public string ContentId => User.AltLink ?? "NewUser"; - - protected override async Task LoadChildren() - { - IsLoading = true; - - var permissions = await _dbService.GetPermissionAsync(Parent.Parent.Parent.Connection, User).ConfigureAwait(false); - - await DispatcherHelper.RunAsync(() => - { - foreach (var permission in permissions) - { - Children.Add(new PermissionNodeViewModel(permission, this)); - } - }); - - IsLoading = false; - } - - public RelayCommand RefreshCommand - { - get - { - return _refreshCommand - ?? (_refreshCommand = new RelayCommand( - async () => - { - await DispatcherHelper.RunAsync(async () => - { - Children.Clear(); - await LoadChildren().ConfigureAwait(false); - }); - })); - } - } - - public RelayCommand OpenCommand - { - get - { - return _openCommand - ?? (_openCommand = new RelayCommand( - () => MessengerInstance.Send(new EditUserMessage(this, Parent.Parent.Parent.Connection, null)))); - } - } - - public RelayCommand AddPermissionCommand - { - get - { - return _addPermissionCommand ?? (_addPermissionCommand = new RelayCommand( - () => MessengerInstance.Send(new EditPermissionMessage(new PermissionNodeViewModel(null, this), Parent.Parent.Parent.Connection, null) - ))); - } - } - - public User User { get; set; } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/UsersNodeViewModel.cs b/src/CosmosDbExplorer/ViewModel/DatabaseNodes/UsersNodeViewModel.cs deleted file mode 100644 index 57e2efd..0000000 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/UsersNodeViewModel.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Messages; -using CosmosDbExplorer.Services; -using GalaSoft.MvvmLight.Ioc; -using GalaSoft.MvvmLight.Threading; -using Microsoft.Azure.Documents; - -namespace CosmosDbExplorer.ViewModel -{ - public class UsersNodeViewModel : TreeViewItemViewModel, ICanRefreshNode - { - private readonly IDocumentDbService _dbService; - private RelayCommand _refreshCommand; - private RelayCommand _addUserCommand; - - public UsersNodeViewModel(Database database, DatabaseNodeViewModel parent) - : base(parent, parent.MessengerInstance, true) - { - Name = "Users"; - Database = database; - _dbService = SimpleIoc.Default.GetInstance(); - } - - public string Name { get; set; } - - public Database Database { get; } - - public RelayCommand RefreshCommand - { - get - { - return _refreshCommand - ?? (_refreshCommand = new RelayCommand( - async () => - { - await DispatcherHelper.RunAsync(async () => - { - Children.Clear(); - await LoadChildren().ConfigureAwait(false); - }); - })); - } - } - - public RelayCommand AddUserCommand - { - get - { - return _addUserCommand ?? (_addUserCommand = new RelayCommand( - () => MessengerInstance.Send(new EditUserMessage(new UserNodeViewModel(new User(), this), Parent.Parent.Connection, null) - ))); - } - } - - protected override async Task LoadChildren() - { - IsLoading = true; - - var users = await _dbService.GetUsersAsync(Parent.Parent.Connection, Database).ConfigureAwait(false); - - await DispatcherHelper.RunAsync(() => - { - foreach (var user in users) - { - Children.Add(new UserNodeViewModel(user, this)); - } - }); - - IsLoading = false; - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/DocumentsTabViewModel.cs b/src/CosmosDbExplorer/ViewModel/DocumentsTabViewModel.cs deleted file mode 100644 index 0450ebb..0000000 --- a/src/CosmosDbExplorer/ViewModel/DocumentsTabViewModel.cs +++ /dev/null @@ -1,541 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Extensions; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Infrastructure.Validar; -using CosmosDbExplorer.Properties; -using CosmosDbExplorer.Services; -using CosmosDbExplorer.Services.DialogSettings; -using CosmosDbExplorer.ViewModel.Interfaces; -using FluentValidation; -using GalaSoft.MvvmLight.Ioc; -using GalaSoft.MvvmLight.Messaging; -using GalaSoft.MvvmLight.Threading; -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; -using Validar; - -namespace CosmosDbExplorer.ViewModel -{ - [InjectValidation] - public class DocumentsTabViewModel : PaneWithZoomViewModel - , IHaveRequestOptions - , IHaveSystemProperties - { - private readonly IDocumentDbService _dbService; - private readonly IDialogService _dialogService; - private readonly StatusBarItem _requestChargeStatusBarItem; - private readonly StatusBarItem _progessBarStatusBarItem; - private RelayCommand _loadMoreCommand; - private RelayCommand _refreshLoadCommand; - private RelayCommand _newDocumentCommand; - private RelayCommand _discardCommand; - private RelayCommand _saveDocumentCommand; - private RelayCommand _deleteDocumentCommand; - private RelayCommand _editFilterCommand; - private RelayCommand _applyFilterCommand; - private RelayCommand _closeFilterCommand; - private RelayCommand _saveLocalCommand; - private ResourceResponse _currentDocument; - private RelayCommand _resetRequestOptionsCommand; - - public DocumentsTabViewModel(IMessenger messenger, IDocumentDbService dbService, IDialogService dialogService, IUIServices uiServices) - : base(messenger, uiServices) - { - Documents = new ObservableCollection(); - _dbService = dbService; - _dialogService = dialogService; - - EditorViewModel = SimpleIoc.Default.GetInstanceWithoutCaching(); - HeaderViewModel = SimpleIoc.Default.GetInstanceWithoutCaching(); - HeaderViewModel.IsReadOnly = true; - - Title = "Documents"; - Header = Title; - - _requestChargeStatusBarItem = new StatusBarItem(new StatusBarItemContext { Value = RequestCharge }, StatusBarItemType.SimpleText, "Request Charge", System.Windows.Controls.Dock.Left); - StatusBarItems.Add(_requestChargeStatusBarItem); - _progessBarStatusBarItem = new StatusBarItem(new StatusBarItemContextCancellableCommand { Value = IsRunning, IsVisible = IsRunning, IsCancellable = false }, StatusBarItemType.ProgessBar, "Progress", System.Windows.Controls.Dock.Left); - StatusBarItems.Add(_progessBarStatusBarItem); - } - - public override async void Load(string contentId, DocumentNodeViewModel node, Connection connection, DocumentCollection collection) - { - ContentId = contentId; - Node = node; - Connection = connection; - Collection = collection; - PartitionKey = collection.PartitionKey?.Paths.FirstOrDefault(); - var split = Node.Parent.Collection.AltLink.Split(new char[] { '/' }); - ToolTip = $"{split[1]}>{split[3]}"; - AccentColor = Node.Parent.Parent.Parent.Connection.AccentColor; - - await LoadDocuments(true).ConfigureAwait(false); - } - - public override void Cleanup() - { - SimpleIoc.Default.Unregister(EditorViewModel); - SimpleIoc.Default.Unregister(HeaderViewModel); - - base.Cleanup(); - } - - public DocumentNodeViewModel Node { get; protected set; } - - public string PartitionKey { get; set; } - - public ObservableCollection Documents { get; } - - public DocumentDescription SelectedDocument { get; set; } - - public async void OnSelectedDocumentChanged() - { - if (SelectedDocument != null) - { - IsRunning = true; - - if (_currentDocument?.Resource.SelfLink != SelectedDocument.SelfLink) - { - try - { - _currentDocument = await _dbService.GetDocumentAsync(Node.Parent.Parent.Parent.Connection, SelectedDocument).ConfigureAwait(false); - } - catch (DocumentClientException clientEx) - { - await _dialogService.ShowError(clientEx.Parse(), "Error", null, null).ConfigureAwait(false); - } - catch (Exception ex) - { - await _dialogService.ShowError(ex, "Error", "ok", null).ConfigureAwait(false); - } - } - - SetStatusBar(new StatusBarInfo(_currentDocument)); - IsRunning = false; - } - else - { - SetStatusBar(null); - } - } - - public string Filter { get; set; } - - public bool IsEditingFilter { get; set; } - - public bool HasMore { get; set; } - public string ContinuationToken { get; set; } - - public string RequestCharge { get; set; } - - public void OnRequestChargeChanged() - { - _requestChargeStatusBarItem.DataContext.Value = RequestCharge; - } - - public bool IsRunning { get; set; } - - public void OnIsRunningChanged() - { - _progessBarStatusBarItem.DataContext.IsVisible = IsRunning; - _requestChargeStatusBarItem.DataContext.IsVisible = !IsRunning; - } - - private void SetStatusBar(IStatusBarInfo response) - { - RequestCharge = response != null - ? $"Request Charge: {response.RequestCharge:N2}" - : null; - - EditorViewModel.SetText(response?.Resource, HideSystemProperties); - HeaderViewModel.SetText(response?.ResponseHeaders, HideSystemProperties); - } - - private async Task LoadDocuments(bool cleanContent = false) - { - try - { - IsRunning = true; - - if (cleanContent) - { - Documents.Clear(); - ContinuationToken = null; - } - - var list = await _dbService.GetDocumentsAsync(Connection, - Collection, - Filter, - Settings.Default.MaxDocumentToRetrieve, - ContinuationToken, - this) - .ConfigureAwait(true); - - HasMore = list.HasMore; - ContinuationToken = list.ContinuationToken; - RequestCharge = $"Request Charge: {list.RequestCharge:N2}"; - - foreach (var document in list) - { - Documents.Add(document); - } - - RaisePropertyChanged(() => ItemsCount); - } - catch (DocumentClientException clientEx) - { - await _dialogService.ShowError(clientEx.Parse(), "Error", "ok", null).ConfigureAwait(false); - } - catch (Exception ex) - { - await _dialogService.ShowError(ex, "Error", "ok", null).ConfigureAwait(false); - } - finally - { - IsRunning = false; - } - } - - public long TotalItemsCount { get; set; } - public long ItemsCount => Documents.Count; - - public DocumentEditorViewModel EditorViewModel { get; set; } - - public HeaderEditorViewModel HeaderViewModel { get; set; } - - protected Connection Connection { get; set; } - - protected DocumentCollection Collection { get; set; } - - public RelayCommand LoadMoreCommand - { - get - { - return _loadMoreCommand - ?? (_loadMoreCommand = new RelayCommand( - async () => await LoadDocuments(false).ConfigureAwait(false))); - } - } - - public RelayCommand RefreshLoadCommand - { - get - { - return _refreshLoadCommand - ?? (_refreshLoadCommand = new RelayCommand( - async () => await LoadDocuments(true).ConfigureAwait(false), - () => !IsRunning && IsValid)); - } - } - - public RelayCommand NewDocumentCommand - { - get - { - return _newDocumentCommand - ?? (_newDocumentCommand = new RelayCommand( - () => - { - SelectedDocument = null; - SetStatusBar(null); - EditorViewModel.SetText(new Document() { Id = "replace_with_the_new_document_id" }, HideSystemProperties); - }, - () => - { - // Can create new document if current document is not a new document - return !IsRunning && !EditorViewModel.IsNewDocument && !EditorViewModel.IsDirty; - })); - } - } - - public RelayCommand DiscardCommand - { - get - { - return _discardCommand - ?? (_discardCommand = new RelayCommand( - () => OnSelectedDocumentChanged(), - () => !IsRunning && EditorViewModel.IsDirty)); - } - } - - public RelayCommand SaveDocumentCommand - { - get - { - return _saveDocumentCommand - ?? (_saveDocumentCommand = new RelayCommand( - async () => - { - IsRunning = true; - try - { - var response = await _dbService.UpdateDocumentAsync(Connection, Collection.AltLink, EditorViewModel.Content.Text, this).ConfigureAwait(true); - var document = response.Resource; - - SetStatusBar(new StatusBarInfo(response)); - - var description = new DocumentDescription(document, Collection); - - if (SelectedDocument == null) - { - Documents.Add(description); - SelectedDocument = description; - } - - HeaderViewModel.SetText(response.ResponseHeaders, HideSystemProperties); - } - catch (DocumentClientException ex) - { - var message = ex.Parse(); - await _dialogService.ShowError(message, "Error", null, null).ConfigureAwait(false); - } - catch (Exception ex) - { - await _dialogService.ShowError(ex.Message, "Error", null, null).ConfigureAwait(false); - } - finally - { - IsRunning = false; - } - }, - () => !IsRunning && EditorViewModel.IsDirty && IsValid)); - } - } - - public RelayCommand DeleteDocumentCommand - { - get - { - return _deleteDocumentCommand - ?? (_deleteDocumentCommand = new RelayCommand( - async () => - { - var selectedDocuments = Documents.Where(doc => doc.IsSelected).ToList(); - var message = selectedDocuments.Count == 1 - ? $"Are you sure that you want to delete document '{selectedDocuments[0].Id}'?" - : $"Are you sure that you want to delete these {selectedDocuments.Count} documents?"; - - await _dialogService.ShowMessage(message, "Delete Document(s)", null, null, async confirm => - { - if (confirm) - { - IsRunning = true; - var response = await _dbService.DeleteDocumentsAsync(Node.Parent.Parent.Parent.Connection, selectedDocuments).ConfigureAwait(false); - IsRunning = false; - SetStatusBar(new StatusBarInfo(response)); - - await DispatcherHelper.RunAsync(() => - { - SelectedDocument = null; - foreach (var item in selectedDocuments) - { - Documents.Remove(item); - } - - EditorViewModel.SetText(new { result = "Delete operation succeeded!" }, HideSystemProperties); - }); - } - }).ConfigureAwait(false); - }, - () => !IsRunning && SelectedDocument != null && !EditorViewModel.IsNewDocument && IsValid)); - } - } - - public RelayCommand EditFilterCommand - { - get - { - return _editFilterCommand - ?? (_editFilterCommand = new RelayCommand( - () => IsEditingFilter = true)); - } - } - - public RelayCommand ApplyFilterCommand - { - get - { - return _applyFilterCommand - ?? (_applyFilterCommand = new RelayCommand( - async () => - { - IsEditingFilter = false; - await LoadDocuments(true).ConfigureAwait(false); - })); - } - } - - public RelayCommand CloseFilterCommand - { - get - { - return _closeFilterCommand - ?? (_closeFilterCommand = new RelayCommand( - () => IsEditingFilter = false)); - } - } - - public RelayCommand SaveLocalCommand - { - get - { - return _saveLocalCommand ?? - (_saveLocalCommand = new RelayCommand( - async () => - { - var selectedDocuments = Documents.Where(doc => doc.IsSelected).ToList(); - - if (selectedDocuments.Count == 1) - { - await SaveLocalSingleDocumentAsync().ConfigureAwait(false); - } - else - { - await SaveLocalMultipleDocumentsAsync(selectedDocuments).ConfigureAwait(false); - } - - }, - () => !IsRunning && SelectedDocument != null && IsValid)); - } - } - - private Task SaveLocalMultipleDocumentsAsync(List selectedDocuments) - { - var settings = new FolderBrowserDialogSettings - { - ShowNewFolderButton = true, - Description = "Select output folder...", - SelectedPath = Settings.Default.GetExportFolder() - }; - - return _dialogService.ShowFolderBrowserDialog(settings, async (confirm, result) => - { - if (confirm) - { - try - { - IsRunning = true; - - // Save path for future use - Settings.Default.ExportFolder = result.Path; - Settings.Default.Save(); - - var tasks = selectedDocuments.Select(doc => _dbService.GetDocumentAsync(Connection, doc)); - await Task.WhenAll(tasks).ConfigureAwait(false); - - foreach (var item in tasks) - { - var document = item.Result; - File.WriteAllText(Path.Combine(result.Path, $"{document.Resource.Id}.json"), document.Resource.ToString()); - } - } - catch (Exception ex) - { - await _dialogService.ShowError(ex, "Error", null, null).ConfigureAwait(false); - } - finally - { - IsRunning = false; - } - } - }); - } - - private Task SaveLocalSingleDocumentAsync() - { - var settings = new SaveFileDialogSettings - { - DefaultExt = "json", - Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*", - AddExtension = true, - FileName = $"{SelectedDocument.Id}.json", - OverwritePrompt = true, - CheckFileExists = false, - Title = "Save document locally" - }; - - return _dialogService.ShowSaveFileDialog(settings, async (confirm, result) => - { - if (confirm) - { - try - { - IsRunning = true; - - Settings.Default.ExportFolder = (new FileInfo(result.FileName)).DirectoryName; - Settings.Default.Save(); - - await DispatcherHelper.RunAsync(() => File.WriteAllText(result.FileName, EditorViewModel.Content.Text)); - } - catch (Exception ex) - { - await _dialogService.ShowError(ex, "Error", null, null).ConfigureAwait(false); - } - finally - { - IsRunning = false; - } - } - }); - } - - public bool IsValid => !((INotifyDataErrorInfo)this).HasErrors; - - public bool HideSystemProperties { get; set; } = true; - - public void OnHideSystemPropertiesChanged() - { - OnSelectedDocumentChanged(); - } - - public bool? EnableScanInQuery { get; set; } - public bool? EnableCrossPartitionQuery { get; set; } - public int? MaxItemCount { get; set; } - public int? MaxDOP { get; set; } - public int? MaxBufferItem { get; set; } - - public RelayCommand ResetRequestOptionsCommand - { - get - { - return _resetRequestOptionsCommand - ?? (_resetRequestOptionsCommand = new RelayCommand( - () => - { - IndexingDirective = null; - ConsistencyLevel = null; - PartitionKeyValue = null; - AccessConditionType = null; - AccessCondition = null; - PreTrigger = null; - PostTrigger = null; - })); - } - } - - public IndexingDirective? IndexingDirective { get; set; } - public ConsistencyLevel? ConsistencyLevel { get; set; } - public string PartitionKeyValue { get; set; } - public AccessConditionType? AccessConditionType { get; set; } - public string AccessCondition { get; set; } - public string PreTrigger { get; set; } - public string PostTrigger { get; set; } - } - - public class DocumentsTabViewModelValidator : AbstractValidator - { - public DocumentsTabViewModelValidator() - { - When(x => !string.IsNullOrEmpty(x.PartitionKeyValue?.Trim()), - () => RuleFor(x => x.PartitionKeyValue).SetValidator(new PartitionKeyValidator())); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/ImportDocumentViewModel.cs b/src/CosmosDbExplorer/ViewModel/ImportDocumentViewModel.cs deleted file mode 100644 index e3e201a..0000000 --- a/src/CosmosDbExplorer/ViewModel/ImportDocumentViewModel.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Extensions; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Services; -using CosmosDbExplorer.Services.DialogSettings; -using CosmosDbExplorer.ViewModel.Interfaces; -using GalaSoft.MvvmLight.Messaging; -using GalaSoft.MvvmLight.Threading; -using ICSharpCode.AvalonEdit.Document; -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; - -namespace CosmosDbExplorer.ViewModel -{ - public class ImportDocumentViewModel : PaneWithZoomViewModel, IHaveRequestOptions - { - private RelayCommand _executeCommand; - private readonly IDialogService _dialogService; - private readonly IDocumentDbService _dbService; - private RelayCommand _openFileCommand; - private RelayCommand _resetRequestOptionsCommand; - private readonly StatusBarItem _progessBarStatusBarItem; - private CancellationTokenSource _cancellationToken; - private RelayCommand _cancelCommand; - - public ImportDocumentViewModel(IMessenger messenger, IDialogService dialogService, IDocumentDbService dbService, IUIServices uiServices) - : base(messenger, uiServices) - { - Content = new TextDocument(); - _dialogService = dialogService; - _dbService = dbService; - - _progessBarStatusBarItem = new StatusBarItem(new StatusBarItemContextCancellableCommand { Value = CancelCommand, IsVisible = IsRunning, IsCancellable = false }, StatusBarItemType.ProgessBar, "Progress", System.Windows.Controls.Dock.Left); - StatusBarItems.Add(_progessBarStatusBarItem); - } - - public bool IsRunning { get; set; } - - public void OnIsRunningChanged() - { - _progessBarStatusBarItem.DataContext.IsVisible = IsRunning; - - if (IsRunning) - { - _cancellationToken = new CancellationTokenSource(); - } - else - { - _cancellationToken = null; - } - } - - public override void Load(string contentId, CollectionNodeViewModel node, Connection connection, DocumentCollection collection) - { - ContentId = contentId; - Node = node; - Header = "Import"; - Connection = connection; - Collection = collection; - - var split = Collection.AltLink.Split(new char[] { '/' }); - ToolTip = $"{split[1]}>{split[3]}"; - AccentColor = Connection.AccentColor; - } - - public CollectionNodeViewModel Node { get; protected set; } - - protected Connection Connection { get; set; } - - protected DocumentCollection Collection { get; set; } - - public TextDocument Content { get; set; } - - public bool IsDirty { get; set; } - - public RelayCommand ExecuteCommand - { - get - { - return _executeCommand - ?? (_executeCommand = new RelayCommand( - async () => - { - try - { - IsRunning = true; - var count = await _dbService.ImportDocumentAsync(Connection, Collection, Content.Text, this, _cancellationToken.Token).ConfigureAwait(false); - await _dialogService.ShowMessageBox($"{count} document(s) imported!", "Import").ConfigureAwait(false); - } - catch (OperationCanceledException) - { - await _dialogService.ShowMessage("Operation cancelled by user...", "Cancel").ConfigureAwait(false); - } - catch (DocumentClientException clientEx) - { - await _dialogService.ShowError(clientEx.Parse(), "Error", "ok", null).ConfigureAwait(false); - } - catch (Exception ex) - { - await _dialogService.ShowError(ex, "Error", "ok", null).ConfigureAwait(false); - } - finally - { - IsRunning = false; - } - }, - () => !IsRunning && !string.IsNullOrEmpty(Content?.Text))); - } - } - - public RelayCommand CancelCommand - { - get - { - return _cancelCommand ?? (_cancelCommand = new RelayCommand( - () => _cancellationToken.Cancel(), - () => IsRunning)); - } - } - - public RelayCommand OpenFileCommand - { - get - { - return _openFileCommand - ?? (_openFileCommand = new RelayCommand( - async () => - { - var settings = new OpenFileDialogSettings - { - DefaultExt = "json", - Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*", - AddExtension = true, - CheckFileExists = true, - Multiselect = false, - Title = "Open document" - }; - - await _dialogService.ShowOpenFileDialog(settings, - async (confirm, result) => - { - if (confirm) - { - await DispatcherHelper.RunAsync(async () => - { - using (var reader = File.OpenText(result.FileName)) - { - Content.FileName = result.FileName; - Content.Text = await reader.ReadToEndAsync().ConfigureAwait(true); - } - }); - } - }).ConfigureAwait(false); - } - )); - } - } - - public IndexingDirective? IndexingDirective { get; set; } - public ConsistencyLevel? ConsistencyLevel { get; set; } - public string PartitionKeyValue { get; set; } - public AccessConditionType? AccessConditionType { get; set; } - public string AccessCondition { get; set; } - public string PreTrigger { get; set; } - public string PostTrigger { get; set; } - - public RelayCommand ResetRequestOptionsCommand - { - get - { - return _resetRequestOptionsCommand - ?? (_resetRequestOptionsCommand = new RelayCommand( - () => - { - IndexingDirective = null; - ConsistencyLevel = null; - PartitionKeyValue = null; - AccessConditionType = null; - AccessCondition = null; - PreTrigger = null; - PostTrigger = null; - })); - } - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/Indexes/ExcludedPathViewModel.cs b/src/CosmosDbExplorer/ViewModel/Indexes/ExcludedPathViewModel.cs deleted file mode 100644 index 289354f..0000000 --- a/src/CosmosDbExplorer/ViewModel/Indexes/ExcludedPathViewModel.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.ComponentModel; -using CosmosDbExplorer.Infrastructure; -using FluentValidation; -using GalaSoft.MvvmLight; -using Microsoft.Azure.Documents; -using Validar; - -namespace CosmosDbExplorer.ViewModel.Indexes -{ - [InjectValidation] - public class ExcludedPathViewModel : ObservableObject, System.IEquatable - { - public ExcludedPathViewModel() - { - ExcludedPath = new ExcludedPath(); - } - - public ExcludedPathViewModel(ExcludedPath excludedPath) - { - ExcludedPath = excludedPath; - } - - public ExcludedPath ExcludedPath { get; } - - public string Path - { - get => ExcludedPath.Path; - set - { - ExcludedPath.Path = value; - RaisePropertyChanged(nameof(Path)); - } - } - - public bool HasErrors => ((INotifyDataErrorInfo)this).HasErrors; - - public bool Equals(ExcludedPathViewModel other) - { - if (other == null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return Path == other.Path; - } - - public override int GetHashCode() => Path?.GetHashCode() ?? 0; - } - - public class ExcludedPathViewModelValidator : AbstractValidator - { - public ExcludedPathViewModelValidator() - { - RuleFor(x => x.Path) - .NotEmpty() - .Matches(Constants.Validation.PathRegex); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/Indexes/IncludedPathViewModel.cs b/src/CosmosDbExplorer/ViewModel/Indexes/IncludedPathViewModel.cs deleted file mode 100644 index 5957ede..0000000 --- a/src/CosmosDbExplorer/ViewModel/Indexes/IncludedPathViewModel.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Linq; -using CosmosDbExplorer.Infrastructure; -using FluentValidation; -using GalaSoft.MvvmLight; -using Microsoft.Azure.Documents; -using Validar; - -namespace CosmosDbExplorer.ViewModel.Indexes -{ - [InjectValidation] - public class IncludedPathViewModel : ObservableObject, IEquatable - { - private RelayCommand _removeIndexCommand; - private RelayCommand _addIndexCommand; - - public IncludedPathViewModel(IncludedPath includedPath) - { - IncludedPath = includedPath; - Indexes = new BindingList(IncludedPath.Indexes.Select(i => new IndexViewModel(i)).ToList()); - Indexes.ListChanged += (s, e) => - { - if (Indexes.Any(i => i.HasErrors)) - { - return; - } - - IncludedPath.Indexes.Clear(); - foreach (var item in Indexes) - { - IncludedPath.Indexes.Add(item.GetIndex()); - } - - RaisePropertyChanged(nameof(Indexes)); - }; - } - - public IncludedPath IncludedPath { get; } - - public string Path - { - get => IncludedPath.Path; - set - { - IncludedPath.Path = value; - RaisePropertyChanged(nameof(Path)); - } - } - - public BindingList Indexes { get; } - - public RelayCommand RemoveIndexCommand => _removeIndexCommand ?? (_removeIndexCommand = new RelayCommand(p => Indexes.Remove(p))); - - public RelayCommand AddIndexCommand => _addIndexCommand ?? (_addIndexCommand = new RelayCommand(() => Indexes.Add(new IndexViewModel()))); - - public bool HasErrors => ((INotifyDataErrorInfo)this).HasErrors; - - public bool Equals(IncludedPathViewModel other) - { - if (other == null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return Path == other.Path; - } - - public override int GetHashCode() => Path?.GetHashCode() ?? 0; - } - - public class IncludedPathViewModelValidator : AbstractValidator - { - public IncludedPathViewModelValidator() - { - RuleFor(x => x.Path) - .NotEmpty() - .Matches(Constants.Validation.PathRegex); - - RuleFor(x => x.Indexes) - .Must(coll => coll.Distinct().Count() == coll.Count) - .WithMessage((vm, coll) => $"Duplicate indexes specified for the path '{vm.Path}' and data type '{string.Join("' and '", coll.GroupBy(g => g.DataType).Where(g => g.Skip(1).Any()).Select(g => g.Key.Value.ToString()))}'."); - - //RuleFor(x => x.Indexes) - // .NotEmpty(); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/Indexes/IndexViewModel.cs b/src/CosmosDbExplorer/ViewModel/Indexes/IndexViewModel.cs deleted file mode 100644 index bdd0b73..0000000 --- a/src/CosmosDbExplorer/ViewModel/Indexes/IndexViewModel.cs +++ /dev/null @@ -1,158 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using FluentValidation; -using GalaSoft.MvvmLight; -using Microsoft.Azure.Documents; -using PropertyChanged; -using Validar; - -namespace CosmosDbExplorer.ViewModel.Indexes -{ - [InjectValidation] - public class IndexViewModel : ObservableObject, IEquatable - { - public IndexViewModel() - { - } - - public IndexViewModel(Index index) - { - switch (index) - { - case RangeIndex rangeIndex: - DataType = rangeIndex.DataType; - Precision = rangeIndex.Precision; - IsMaxPrecision = rangeIndex.Precision == -1; - Kind = rangeIndex.Kind; - break; - case HashIndex hashIndex: - DataType = hashIndex.DataType; - Precision = hashIndex.Precision; - IsMaxPrecision = hashIndex.Precision == -1; - Kind = hashIndex.Kind; - break; - case SpatialIndex spatialIndex: - DataType = spatialIndex.DataType; - Precision = null; - Kind = spatialIndex.Kind; - break; - default: - throw new Exception("Index Type unknown"); - } - } - - public bool HasErrors => ((INotifyDataErrorInfo)this).HasErrors; - - public Index GetIndex() - { - switch (Kind) - { - case IndexKind.Hash: - return new HashIndex(DataType.Value, Precision.Value); - case IndexKind.Range: - return new RangeIndex(DataType.Value, Precision.Value); - default: - return new SpatialIndex(DataType.Value); - } - } - - public IndexKind? Kind { get; set; } - - protected void OnKindChanged() - { - switch (Kind) - { - case IndexKind.Hash: - MinPrecision = 1; - MaxPrecision = 8; - - if (Precision.GetValueOrDefault(3) > MaxPrecision) - { - Precision = 3; - } - break; - case IndexKind.Range: - MinPrecision = 1; - MaxPrecision = 100; - break; - case IndexKind.Spatial: - Precision = null; - break; - } - } - - public DataType? DataType { get; set; } - - [DependsOn(nameof(Kind))] - public DataType[] AvailableDataTypes - { - get - { - switch (Kind) - { - case IndexKind.Spatial: - return new [] { Microsoft.Azure.Documents.DataType.Point, Microsoft.Azure.Documents.DataType.Polygon, Microsoft.Azure.Documents.DataType.LineString }; - default: - return new [] { Microsoft.Azure.Documents.DataType.String, Microsoft.Azure.Documents.DataType.Number }; - } - } - } - - public short? Precision { get; set; } - - public short MinPrecision { get; set; } - - public short MaxPrecision { get; set; } - - public bool IsMaxPrecision { get; set; } - - protected void OnIsMaxPrecisionChanged() - { - if (IsMaxPrecision) - { - Precision = -1; - } - else - { - Precision = 3; - } - } - - public bool Equals(IndexViewModel other) - { - if (other == null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return DataType == other.DataType; - } - - public override int GetHashCode() - { - return DataType.GetValueOrDefault().GetHashCode(); - } - - [DependsOn(nameof(IsMaxPrecision), nameof(Kind))] - public bool CanSetPrecision => !IsMaxPrecision && Kind != IndexKind.Spatial; - - [DependsOn(nameof(Kind))] - public bool CanSetMaxPrecision => Kind != IndexKind.Spatial; - } - - public class IndexViewModelValidator : AbstractValidator - { - public IndexViewModelValidator() - { - RuleFor(x => x.Kind).NotEmpty(); - RuleFor(x => x.DataType).NotEmpty(); - RuleFor(x => x.Precision).NotEmpty().When(x => x.Kind.HasValue && x.Kind.Value != IndexKind.Spatial); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/Indexes/IndexingPolicyViewModel.cs b/src/CosmosDbExplorer/ViewModel/Indexes/IndexingPolicyViewModel.cs deleted file mode 100644 index 99845eb..0000000 --- a/src/CosmosDbExplorer/ViewModel/Indexes/IndexingPolicyViewModel.cs +++ /dev/null @@ -1,217 +0,0 @@ -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Linq; -using CosmosDbExplorer.Infrastructure; -using FluentValidation; -using GalaSoft.MvvmLight; -using Microsoft.Azure.Documents; -using Newtonsoft.Json; -using PropertyChanged; -using Validar; - -namespace CosmosDbExplorer.ViewModel.Indexes -{ - [InjectValidation] - public class IndexingPolicyViewModel : ObservableObject - { - private RelayCommand _removeIncludedPathCommand; - private RelayCommand _addIncludedPathCommand; - - private RelayCommand _removeExcludedPathCommand; - private RelayCommand _addExcludedPathCommand; - - private readonly bool _isCreating; - - public IndexingPolicyViewModel(IndexingPolicy policy) - { - _isCreating = true; - - // deep clone the indexing policy - var json = JsonConvert.SerializeObject(policy); - Policy = JsonConvert.DeserializeObject(json); - - _isCreating = false; - - IncludedPaths.ListChanged += (s, e) => - { - if (IncludedPaths.All(ip => !ip.HasErrors)) - { - RaisePropertyChanged(nameof(IncludedPaths)); - } - }; - - ExcludedPaths.ListChanged += (s, e) => - { - if (ExcludedPaths.All(ep => !ep.HasErrors)) - { - RaisePropertyChanged(nameof(ExcludedPaths)); - } - }; - - PropertyChanged += (s, e) => - { - if (e.PropertyName != nameof(IsValid)) - { - RaisePropertyChanged(nameof(IsValid)); - } - }; - } - - public IndexingPolicy Policy { get; set; } - - protected void OnPolicyChanged() - { - IncludedPaths.Clear(); - foreach (var vm in Policy.IncludedPaths.Select(ip => new IncludedPathViewModel(ip))) - { - IncludedPaths.Add(vm); - } - - ExcludedPaths.Clear(); - foreach (var vm in Policy.ExcludedPaths.Select(ep => new ExcludedPathViewModel(ep))) - { - ExcludedPaths.Add(vm); - } - - if (_isCreating) - { - IsChanged = false; - } - } - - public bool IsValid - { - get - { - return !((INotifyDataErrorInfo)this).HasErrors - && IncludedPaths.All(ip => !ip.HasErrors) - && ExcludedPaths.All(ep => !ep.HasErrors); - } - } - - public bool IsChanged { get; set; } - - public bool IsAutomatic - { - get => Policy.Automatic; - set - { - Policy.Automatic = value; - RaisePropertyChanged(nameof(IsAutomatic)); - } - } - - public IndexingMode Mode - { - get => Policy.IndexingMode; - set - { - Policy.IndexingMode = value; - RaisePropertyChanged(nameof(Mode)); - - if (Mode == IndexingMode.None) - { - Policy.IncludedPaths.Clear(); - IncludedPaths.Clear(); - } - } - } - - [DependsOn(nameof(Mode))] - public BindingList IncludedPaths { get; } = new BindingList(); - - [DependsOn(nameof(Mode))] - public BindingList ExcludedPaths { get; } = new BindingList(); - - public RelayCommand RemoveIncludedPathCommand - { - get - { - return _removeIncludedPathCommand ?? (_removeIncludedPathCommand = new RelayCommand(p => - { - Policy.IncludedPaths.Remove(p.IncludedPath); - IncludedPaths.Remove(p); - })); - } - } - - public RelayCommand AddIncludedPathCommand - { - get - { - return _addIncludedPathCommand ?? (_addIncludedPathCommand = new RelayCommand(() => - { - var path = new IncludedPath - { - Indexes = new Collection() { new HashIndex(DataType.String, 3) } - }; - - Policy.IncludedPaths.Add(path); - IncludedPaths.Add(new IncludedPathViewModel(path)); - })); - } - } - - public RelayCommand RemoveExcludedPathCommand - { - get - { - return _removeExcludedPathCommand ?? (_removeExcludedPathCommand = new RelayCommand(p => - { - Policy.ExcludedPaths.Remove(p.ExcludedPath); - ExcludedPaths.Remove(p); - })); - } - } - - public RelayCommand AddExcludedPathCommand - { - get - { - return _addExcludedPathCommand ?? (_addExcludedPathCommand = new RelayCommand(() => - { - var path = new ExcludedPath(); - Policy.ExcludedPaths.Add(path); - ExcludedPaths.Add(new ExcludedPathViewModel(path)); - })); - } - } - } - - public class IndexingPolicyViewModelValidator : AbstractValidator - { - public IndexingPolicyViewModelValidator() - { - RuleFor(x => x.IncludedPaths) - .Must(coll => coll.Distinct().Count() == coll.Count) - .WithMessage((vm, coll) => $"Path(s) '{string.Join("' and '", coll.GroupBy(g => g.Path).Where(g => g.Skip(1).Any()).Select(g => g.Key))}' are duplicated.") - .Must((vm, coll) => - { - var joined = coll.Select(c => c.Path).Concat(vm.ExcludedPaths.Select(ep => ep.Path)).ToList(); - return joined.Distinct().Count() == joined.Count; - }) - .WithMessage((vm, coll) => - { - var joined = coll.Select(c => c.Path).Concat(vm.ExcludedPaths.Select(ep => ep.Path)).ToList(); - var duplicated = joined.GroupBy(p => p).Where(g => g.Skip(1).Any()).Select(g => g.Key); - return $"Path(s) '{string.Join("' and '", duplicated)}' already present in Excluded Paths."; - }) - .NotEmpty().When(x => x.Mode != IndexingMode.None); - - RuleFor(x => x.ExcludedPaths) - .Must(coll => coll.Distinct().Count() == coll.Count) - .WithMessage((vm, coll) => $"Path(s) '{string.Join("' and '", coll.GroupBy(g => g.Path).Where(g => g.Skip(1).Any()).Select(g => g.Key))}' are duplicated.") - .Must((vm, coll) => - { - var joined = coll.Select(c => c.Path).Concat(vm.IncludedPaths.Select(ep => ep.Path)).ToList(); - return joined.Distinct().Count() == joined.Count; - }) - .WithMessage((vm, coll) => - { - var joined = coll.Select(c => c.Path).Concat(vm.IncludedPaths.Select(ep => ep.Path)).ToList(); - var duplicated = joined.GroupBy(p => p).Where(g => g.Skip(1).Any()).Select(g => g.Key); - return $"Path(s) '{string.Join("' and '", duplicated)}' already present in Included Paths."; - }); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/Interfaces/IAssetTabCommand.cs b/src/CosmosDbExplorer/ViewModel/Interfaces/IAssetTabCommand.cs deleted file mode 100644 index 6a174b1..0000000 --- a/src/CosmosDbExplorer/ViewModel/Interfaces/IAssetTabCommand.cs +++ /dev/null @@ -1,11 +0,0 @@ -using CosmosDbExplorer.Infrastructure; - -namespace CosmosDbExplorer.ViewModel.Interfaces -{ - public interface IAssetTabCommand - { - RelayCommand SaveCommand { get; } - RelayCommand DiscardCommand { get; } - RelayCommand DeleteCommand { get; } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/Interfaces/ICanRefreshTab.cs b/src/CosmosDbExplorer/ViewModel/Interfaces/ICanRefreshTab.cs deleted file mode 100644 index 2603677..0000000 --- a/src/CosmosDbExplorer/ViewModel/Interfaces/ICanRefreshTab.cs +++ /dev/null @@ -1,9 +0,0 @@ -using CosmosDbExplorer.Infrastructure; - -namespace CosmosDbExplorer.ViewModel.Interfaces -{ - public interface ICanRefreshTab - { - RelayCommand RefreshCommand { get; } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/Interfaces/IHaveQuerySettings.cs b/src/CosmosDbExplorer/ViewModel/Interfaces/IHaveQuerySettings.cs deleted file mode 100644 index 8d3699c..0000000 --- a/src/CosmosDbExplorer/ViewModel/Interfaces/IHaveQuerySettings.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace CosmosDbExplorer.ViewModel.Interfaces -{ - public interface IHaveQuerySettings - { - bool HideSystemProperties { get; set; } - bool? EnableScanInQuery { get; set; } - bool? EnableCrossPartitionQuery { get; set; } - int? MaxItemCount { get; set; } - int? MaxDOP { get; set; } - int? MaxBufferItem { get; set; } - string PartitionKeyValue { get; set; } - } - - public interface IHaveSystemProperties - { - bool HideSystemProperties { get; set; } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/Interfaces/IHaveRequestOptions.cs b/src/CosmosDbExplorer/ViewModel/Interfaces/IHaveRequestOptions.cs deleted file mode 100644 index daa6c18..0000000 --- a/src/CosmosDbExplorer/ViewModel/Interfaces/IHaveRequestOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; - -namespace CosmosDbExplorer.ViewModel.Interfaces -{ - public interface IHaveRequestOptions - { - IndexingDirective? IndexingDirective { get; set; } - ConsistencyLevel? ConsistencyLevel { get; set; } - string PartitionKeyValue { get; set; } - AccessConditionType? AccessConditionType { get; set; } - string AccessCondition { get; set; } - string PreTrigger { get; set; } - string PostTrigger { get; set; } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/JsonEditorViewModel.cs b/src/CosmosDbExplorer/ViewModel/JsonEditorViewModel.cs deleted file mode 100644 index ce1c81b..0000000 --- a/src/CosmosDbExplorer/ViewModel/JsonEditorViewModel.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System.Collections.Specialized; -using CosmosDbExplorer.Infrastructure.Extensions; -using CosmosDbExplorer.Infrastructure.JsonHelpers; -using GalaSoft.MvvmLight; -using GalaSoft.MvvmLight.Messaging; -using GalaSoft.MvvmLight.Threading; -using ICSharpCode.AvalonEdit.Document; -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; -using Newtonsoft.Json; - -namespace CosmosDbExplorer.ViewModel -{ - public abstract class JsonEditorViewModelBase : ViewModelBase - { - protected JsonEditorViewModelBase(IMessenger messenger) : base(messenger) - { - Content = new TextDocument(); - } - - public TextDocument Content { get; set; } - - public bool IsDirty { get; set; } - - public bool IsReadOnly { get; set; } - - public virtual void SetText(object content, bool removeSystemProperties) - { - var text = GetDocumentContent(content, removeSystemProperties) ?? string.Empty; - - DispatcherHelper.RunAsync(() => - { - Content.Text = text; - RaisePropertyChanged(() => HasContent); - IsDirty = false; - }); - } - - protected abstract string GetDocumentContent(object content, bool removeSystemProperties); - - public bool HasContent => Content.TextLength != 0; - } - - public class JsonViewerViewModel : JsonEditorViewModelBase - { - public JsonViewerViewModel(IMessenger messenger) : base(messenger) - { - } - - protected override string GetDocumentContent(object content, bool removeSystemProperties) - { - if (content == null) - { - return null; - } - - var settings = new JsonSerializerSettings - { - ContractResolver = removeSystemProperties ? new DocumentDbWithoutSystemPropertyResolver() : null, - Formatting = Formatting.Indented, - DateParseHandling = DateParseHandling.None - }; - - try - { - FeedResponse doc = (dynamic)content; - return JsonConvert.SerializeObject(doc, settings); - } - catch - { - return JsonConvert.SerializeObject(content, settings); - } - } - } - - public class DocumentEditorViewModel : JsonViewerViewModel - { - private Document _document; - - public DocumentEditorViewModel(IMessenger messenger) : base(messenger) - { - } - - public override void SetText(object content, bool removeSystemProperties) - { - _document = content as Document; - base.SetText(_document, removeSystemProperties); - } - - protected override string GetDocumentContent(object content, bool removeSystemProperties) - { - if (_document == null) - { - return null; - } - - var settings = new JsonSerializerSettings - { - ContractResolver = removeSystemProperties ? new DocumentDbWithoutSystemPropertyResolver() : null, - Formatting = Formatting.Indented, - DateParseHandling = DateParseHandling.None - }; - - return JsonConvert.SerializeObject(_document, settings); - } - - public string Id => _document?.Id; - - public bool IsNewDocument - { - get - { - return _document != null && _document.SelfLink == null; - } - } - } - - public class HeaderEditorViewModel : JsonEditorViewModelBase - { - public HeaderEditorViewModel(IMessenger messenger) : base(messenger) - { - } - - protected override string GetDocumentContent(object content, bool removeSystemProperties) - { - if (content == null) - { - return null; - } - - var settings = new JsonSerializerSettings - { - Formatting = Formatting.Indented, - DateParseHandling = DateParseHandling.None - }; - settings.Converters.Add(new OrderedDictionaryConverter()); - - return JsonConvert.SerializeObject(((NameValueCollection)content).ToDictionary(), settings); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/MainViewModel.cs b/src/CosmosDbExplorer/ViewModel/MainViewModel.cs deleted file mode 100644 index de16272..0000000 --- a/src/CosmosDbExplorer/ViewModel/MainViewModel.cs +++ /dev/null @@ -1,295 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Timers; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Messages; -using CosmosDbExplorer.Services; -using CosmosDbExplorer.ViewModel.Assets; -using CosmosDbExplorer.ViewModel.Interfaces; -using GalaSoft.MvvmLight; -using GalaSoft.MvvmLight.Ioc; -using GalaSoft.MvvmLight.Messaging; -using GalaSoft.MvvmLight.Threading; - -namespace CosmosDbExplorer.ViewModel -{ - /// - /// This class contains properties that the main View can data bind to. - /// - /// Use the mvvminpc snippet to add bindable properties to this ViewModel. - /// - /// - /// You can also use Blend to data bind with the tool's support. - /// - /// - /// See http://www.galasoft.ch/mvvm - /// - /// - public class MainViewModel : ViewModelBase - { - private readonly IDialogService _dialogService; - private readonly DatabaseViewModel _databaseViewModel; - private readonly ISimpleIoc _ioc; - private RelayCommand _showAboutCommand; - private RelayCommand _showAccountSettingsCommand; - private RelayCommand _exitCommand; - private IEnumerable _tools; - private RelayCommand _refreshCommand; - - public event Action RequestClose; - - /// - /// Initializes a new instance of the MainViewModel class. - /// - /// - /// - /// - public MainViewModel(IDialogService dialogService, IMessenger messenger, ISimpleIoc ioc) - : base(messenger) - { - if (IsInDesignMode) - { - // Code runs in Blend --> create design time data. - Title = "Design Mode"; - } - else - { - // Code runs "for real" - var assembly = Assembly.GetEntryAssembly(); - Title = ((AssemblyTitleAttribute)Attribute.GetCustomAttribute(assembly, typeof(AssemblyTitleAttribute), false))?.Title ?? "error retrieving assembly title"; - } - - _dialogService = dialogService; - _ioc = ioc; - _databaseViewModel = _ioc.GetInstance(); - Tabs = new ObservableCollection(); - - SpyUsedMemory(); - - RegisterMessages(); - } - - private void SpyUsedMemory() - { - var timer = new Timer(TimeSpan.FromSeconds(3).TotalMilliseconds); - timer.Elapsed += (s, e) => base.RaisePropertyChanged(() => UsedMemory); - timer.Start(); - } - - private void RegisterMessages() - { - MessengerInstance.Register(this, OnActivePaneChanged); - - MessengerInstance.Register(this, msg => OpenOrSelectTab(msg)); - MessengerInstance.Register(this, msg => OpenOrSelectTab(msg)); - MessengerInstance.Register(this, msg => OpenOrSelectTab(msg)); - MessengerInstance.Register(this, msg => OpenOrSelectTab(msg)); - MessengerInstance.Register(this, msg => OpenOrSelectTab(msg)); - MessengerInstance.Register(this, msg => OpenOrSelectTab(msg)); - MessengerInstance.Register(this, msg => OpenOrSelectTab(msg)); - - MessengerInstance.Register(this, msg => OpenOrSelectTab(msg)); - MessengerInstance.Register(this, msg => OpenOrSelectTab(msg)); - MessengerInstance.Register(this, msg => OpenOrSelectTab(msg)); - - MessengerInstance.Register(this, OnTreeNodeSelected); - MessengerInstance.Register(this, CloseDocument); - MessengerInstance.Register(this, msg => IsBusy = msg.IsBusy); - } - - private void OnActivePaneChanged(ActivePaneChangedMessage message) - { - if (message.PaneViewModel is DatabaseViewModel) - { - IsRequestOptionsVisible = false; - IsConnectionOptionsVisible = true; - SelectedRibbonTab = 1; - } - else - { - IsConnectionOptionsVisible = ShouldConnectionOptionBeVisible(); - OnSelectedTabChanged(); - SelectedRibbonTab = 0; - } - } - - private void OnTreeNodeSelected(TreeNodeSelectedMessage message) - { - CanRefreshNodeViewModel = message.Item as ICanRefreshNode; - Connection = message.Item as ConnectionNodeViewModel; - Database = message.Item as DatabaseNodeViewModel; - Collection = (message.Item as IHaveCollectionNodeViewModel)?.CollectionNode; - Users = message.Item as UsersNodeViewModel; - UserNode = message.Item as UserNodeViewModel; - CanEditDelete = message.Item as ICanEditDelete; - - IsConnectionOptionsVisible = ShouldConnectionOptionBeVisible(); - } - - private bool ShouldConnectionOptionBeVisible() - { - return CanRefreshNodeViewModel != null - || Connection != null - || Database != null - || Collection != null - || CanEditDelete != null - || Users != null - || UserNode != null; - } - - private void OpenOrSelectTab(OpenTabMessageBase message) - where TTabViewModel : PaneViewModel - where TNodeViewModel : TreeViewItemViewModel, IContent - { - var contentId = message.Node?.ContentId ?? Guid.NewGuid().ToString(); - var tab = Tabs.FirstOrDefault(t => t.ContentId == contentId); - - if (tab != null) - { - SelectedTab = tab; - } - else - { - var content = SimpleIoc.Default.GetInstanceWithoutCaching(contentId); //_ioc.GetInstance(contentId); - content.Load(contentId, message.Node, message.Connection, message.Collection); - - Tabs.Add(content); - SelectedTab = content; - } - } - - private void CloseDocument(CloseDocumentMessage msg) - { - DispatcherHelper.RunAsync(() => - { - var vm = Tabs.FirstOrDefault(t => t.ContentId == msg.ContentId); - - if (vm != null) - { - Tabs.Remove(vm); - _ioc.Unregister(vm); - vm = null; - SelectedTab = Tabs.LastOrDefault(); - } - }); - } - - public string Title { get; set; } - - public long UsedMemory => GC.GetTotalMemory(true) / 1014; - - public bool IsBusy { get; set; } - - public double Zoom { get; set; } - - public ObservableCollection Tabs { get; } - - public IEnumerable Tools - { - get - { - return _tools - ?? (_tools = new ToolViewModel[] { _databaseViewModel }); - } - } - - public PaneViewModelBase SelectedTab { get; set; } - - public void OnSelectedTabChanged() - { - IsTabDocumentsVisible = SelectedTab is DocumentsTabViewModel; - IsSettingsTabVisible = SelectedTab is ScaleAndSettingsTabViewModel; - IsAssetTabVisible = SelectedTab is IAssetTabCommand; - IsQueryTabVisible = SelectedTab is QueryEditorViewModel || SelectedTab is StoredProcedureTabViewModel ; - IsImportTabVisible = SelectedTab is ImportDocumentViewModel; - IsQuerySettingsVisible = SelectedTab is IHaveQuerySettings; - IsSystemPropertiesVisible = SelectedTab is IHaveSystemProperties; - IsRequestOptionsVisible = SelectedTab is IHaveRequestOptions; - IsConnectionOptionsVisible = false; // Only visible when selecting a tab - IsRefreshTabVisible = SelectedTab is ICanRefreshTab; - } - - public int SelectedRibbonTab { get; set; } - public bool IsConnectionOptionsVisible { get; set; } - public bool IsTabDocumentsVisible { get; set; } - public bool IsSettingsTabVisible { get; set; } - public bool IsAssetTabVisible { get; set; } - public bool IsQueryTabVisible { get; set; } - public bool IsImportTabVisible { get; set; } - public bool IsQuerySettingsVisible { get; set; } - public bool IsRequestOptionsVisible { get; set; } - public bool IsRefreshTabVisible { get; set; } - public bool IsSystemPropertiesVisible { get; set; } - - public ConnectionNodeViewModel Connection { get; set; } - public DatabaseNodeViewModel Database { get; set; } - public CollectionNodeViewModel Collection { get; set; } - public UsersNodeViewModel Users { get; set; } - public UserNodeViewModel UserNode { get; set; } - public ICanRefreshNode CanRefreshNodeViewModel { get; set; } - public ICanEditDelete CanEditDelete { get; set; } - - public RelayCommand ShowAboutCommand - { - get - { - return _showAboutCommand - ?? (_showAboutCommand = new RelayCommand( - async () => - { - var fvi = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location); - var name = ((AssemblyTitleAttribute)Attribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), typeof(AssemblyTitleAttribute), false))?.Title ?? "Unknown Title"; - await _dialogService.ShowMessageBox($"{name}\nVersion {fvi.FileVersion}", "About").ConfigureAwait(false); - })); - } - } - - public RelayCommand ShowAccountSettingsCommand - { - get - { - return _showAccountSettingsCommand - ?? (_showAccountSettingsCommand = new RelayCommand( - () => - { - var form = new Views.AccountSettingsView(); - var vm = (AccountSettingsViewModel)form.DataContext; - vm.SetConnection(new Connection(Guid.NewGuid())); - - var result = form.ShowDialog(); - })); - } - } - - public RelayCommand ExitCommand - { - get - { - return _exitCommand - ?? (_exitCommand = new RelayCommand(Close)); - } - } - - public RelayCommand RefreshCommand - { - get - { - return _refreshCommand - ?? (_refreshCommand = new RelayCommand( - () => CanRefreshNodeViewModel.RefreshCommand.Execute(null), - () => CanRefreshNodeViewModel?.RefreshCommand.CanExecute(null) == true - )); - } - } - - public virtual void Close() - { - RequestClose?.Invoke(); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/PermissionEditViewModel.cs b/src/CosmosDbExplorer/ViewModel/PermissionEditViewModel.cs deleted file mode 100644 index 212da73..0000000 --- a/src/CosmosDbExplorer/ViewModel/PermissionEditViewModel.cs +++ /dev/null @@ -1,214 +0,0 @@ -using System.ComponentModel; -using System.Windows; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Extensions; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Services; -using CosmosDbExplorer.ViewModel.Interfaces; -using FluentValidation; -using GalaSoft.MvvmLight.Messaging; -using GalaSoft.MvvmLight.Threading; -using Microsoft.Azure.Documents; -using Validar; - -namespace CosmosDbExplorer.ViewModel -{ - [InjectValidation] - public class PermissionEditViewModel : PaneViewModel, IAssetTabCommand - { - private readonly IDocumentDbService _dbService; - private readonly IDialogService _dialogService; - private RelayCommand _saveCommand; - private RelayCommand _deleteCommand; - private RelayCommand _discardCommand; - private RelayCommand _copyToClipboardCommand; - - public PermissionEditViewModel(IMessenger messenger, IDocumentDbService dbService, IDialogService dialogService, IUIServices uiServices) - : base(messenger, uiServices) - { - _dbService = dbService; - _dialogService = dialogService; - Header = "New Permission"; - Title = "Permission"; - PropertyChanged += (s, e) => IsDirty = IsEntityChanged(); - } - - public string PermissionId { get; set; } - public PermissionMode PermissionMode { get; set; } - public string ResourceLink { get; set; } - public string ResourcePartitionKey { get; set; } - - public Permission Permission { get; protected set; } - - public bool IsEntityChanged() - { - if (Permission != null) - { - if (PermissionId != Permission.Id) - { - return true; - } - - if (PermissionMode != Permission.PermissionMode) - { - return true; - } - - if (ResourceLink != Permission.ResourceLink) - { - return true; - } - - if (ResourcePartitionKey != Permission.ResourcePartitionKey?.ToString()) - { - return true; - } - } - - return false; - } - - private void SetInformation() - { - PermissionId = Permission?.Id; - PermissionMode = Permission?.PermissionMode ?? PermissionMode.Read; - ResourceLink = Permission?.ResourceLink; - ResourcePartitionKey = Permission?.ResourcePartitionKey?.ToString(); - - var split = Node.Parent.User.AltLink.Split(new char[] { '/' }); - ToolTip = $"{split[1]}>{split[3]}"; - - IsDirty = false; - } - - public bool IsNewDocument => Node?.Permission?.SelfLink == null; - - public bool IsValid => !((INotifyDataErrorInfo)this).HasErrors; - - public override void Load(string contentId, PermissionNodeViewModel node, Connection connection, DocumentCollection collection) - { - ContentId = contentId; - Node = node; - Permission = node?.Permission ?? new Permission(); - Header = node.Name ?? "New Permission"; - Title = "Permission"; - AccentColor = Node.Parent.Parent.Parent.Parent.Connection.AccentColor; - SetInformation(); - } - - public PermissionNodeViewModel Node { get; protected set; } - - public RelayCommand CopyToClipboardCommand - { - get - { - return _copyToClipboardCommand - ?? (_copyToClipboardCommand = new RelayCommand( - () => Clipboard.SetText(Permission?.Token))); - } - } - - public RelayCommand DiscardCommand - { - get - { - return _discardCommand - ?? (_discardCommand = new RelayCommand(SetInformation, () => IsDirty)); - } - } - - public RelayCommand SaveCommand - { - get - { - return _saveCommand - ?? (_saveCommand = new RelayCommand( - async () => - { - Permission permission = null; - - if (IsNewDocument) - { - permission = new Permission - { - Id = PermissionId, - }; - } - else - { - permission = Node.Permission; - } - - permission.Id = PermissionId; - permission.ResourceLink = ResourceLink; - permission.PermissionMode = PermissionMode; - permission.ResourcePartitionKey = ResourcePartitionKey != null - ? new PartitionKey(ResourcePartitionKey) - : null; - - try - { - permission = await _dbService.SavePermissionAsync(Node.Parent.Parent.Parent.Parent.Connection, Node.Parent.User, permission).ConfigureAwait(false); - - Header = permission.Id; - Node.Permission = permission; - ContentId = Node.ContentId; - - RaisePropertyChanged(() => IsNewDocument); - Node.Parent.RefreshCommand.Execute(null); - IsDirty = false; - } - catch (DocumentClientException ex) - { - var msg = ex.Parse(); - await _dialogService.ShowError(msg, "Error", null, null).ConfigureAwait(false); - } - }, - () => IsDirty && IsValid)); - } - } - - public RelayCommand DeleteCommand - { - get - { - return _deleteCommand - ?? (_deleteCommand = new RelayCommand( - async () => - { - await _dialogService.ShowMessage("Are you sure...", "Delete", null, null, async confirm => - { - if (confirm) - { - try - { - await _dbService.DeletePermissionAsync(Node.Parent.Parent.Parent.Parent.Connection, Node.Permission).ConfigureAwait(false); - } - catch (DocumentClientException ex) - { - await _dialogService.ShowError(ex.Parse(), "Error", null, null).ConfigureAwait(false); - } - finally - { - Node.Parent.RefreshCommand.Execute(null); - await DispatcherHelper.RunAsync(() => CloseCommand.Execute(null)); - } - } - }).ConfigureAwait(false); - }, - () => !IsNewDocument)); - } - } - - public bool IsDirty { get; private set; } - } - - public class PermissionEditViewModelValidator : AbstractValidator - { - public PermissionEditViewModelValidator() - { - RuleFor(x => x.PermissionId).NotEmpty(); - RuleFor(x => x.ResourceLink).NotEmpty(); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/QueryEditorViewModel.cs b/src/CosmosDbExplorer/ViewModel/QueryEditorViewModel.cs deleted file mode 100644 index 448867a..0000000 --- a/src/CosmosDbExplorer/ViewModel/QueryEditorViewModel.cs +++ /dev/null @@ -1,430 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Extensions; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Infrastructure.Validar; -using CosmosDbExplorer.Properties; -using CosmosDbExplorer.Services; -using CosmosDbExplorer.Services.DialogSettings; -using CosmosDbExplorer.ViewModel.Interfaces; -using FluentValidation; -using GalaSoft.MvvmLight.Ioc; -using GalaSoft.MvvmLight.Messaging; -using GalaSoft.MvvmLight.Threading; -using ICSharpCode.AvalonEdit.Document; -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; -using Newtonsoft.Json.Linq; -using Validar; - -namespace CosmosDbExplorer.ViewModel -{ - [InjectValidation] - public class QueryEditorViewModel : PaneWithZoomViewModel - , IHaveQuerySettings - , IHaveSystemProperties - { - private RelayCommand _executeCommand; - private readonly IDocumentDbService _dbService; - private readonly IDialogService _dialogService; - private RelayCommand _saveLocalCommand; - private FeedResponse _queryResult; - private RelayCommand _goToNextPageCommand; - private readonly StatusBarItem _requestChargeStatusBarItem; - private readonly StatusBarItem _queryInformationStatusBarItem; - private readonly StatusBarItem _progessBarStatusBarItem; - private CancellationTokenSource _cancellationToken; - private RelayCommand _cancelCommand; - private RelayCommand _saveQueryCommand; - private RelayCommand _openQueryCommand; - - public QueryEditorViewModel(IMessenger messenger, IDocumentDbService dbService, IDialogService dialogService, IUIServices uiServices) - : base(messenger, uiServices) - { - EditorViewModel = SimpleIoc.Default.GetInstanceWithoutCaching(); - EditorViewModel.IsReadOnly = true; - - HeaderViewModel = SimpleIoc.Default.GetInstanceWithoutCaching(); - HeaderViewModel.IsReadOnly = true; - - _dbService = dbService; - _dialogService = dialogService; - - _requestChargeStatusBarItem = new StatusBarItem(new StatusBarItemContext { Value = RequestCharge, IsVisible = IsRunning }, StatusBarItemType.SimpleText, "Request Charge", System.Windows.Controls.Dock.Left); - StatusBarItems.Add(_requestChargeStatusBarItem); - _queryInformationStatusBarItem = new StatusBarItem(new StatusBarItemContext { Value = QueryInformation, IsVisible = IsRunning }, StatusBarItemType.SimpleText, "Information", System.Windows.Controls.Dock.Left); - StatusBarItems.Add(_queryInformationStatusBarItem); - _progessBarStatusBarItem = new StatusBarItem(new StatusBarItemContextCancellableCommand { Value = CancelCommand, IsVisible = IsRunning, IsCancellable = true }, StatusBarItemType.ProgessBar, "Progress", System.Windows.Controls.Dock.Left); - StatusBarItems.Add(_progessBarStatusBarItem); - } - - public override void Load(string contentId, CollectionNodeViewModel node, Connection connection, DocumentCollection collection) - { - ContentId = contentId; - Node = node; - Header = "SQL Query"; - Connection = connection; - Collection = collection; - - Content = new TextDocument($"SELECT * FROM {Collection.Id} AS {Collection.Id.Substring(0, 1).ToLower()}"); - - var split = Collection.AltLink.Split(new char[] { '/' }); - ToolTip = $"{split[1]}>{split[3]}"; - AccentColor = Node.Parent.Parent.Connection.AccentColor; - } - - public CollectionNodeViewModel Node { get; protected set; } - - protected Connection Connection { get; set; } - - protected DocumentCollection Collection { get; set; } - - public TextDocument Content { get; set; } - - public string SelectedText { get; set; } - - public bool IsDirty { get; set; } - - public bool IsRunning { get; set; } - - public void OnIsRunningChanged() - { - _progessBarStatusBarItem.DataContext.IsVisible = IsRunning; - _requestChargeStatusBarItem.DataContext.IsVisible = !IsRunning; - _queryInformationStatusBarItem.DataContext.IsVisible = !IsRunning; - - if (IsRunning) - { - _cancellationToken = new CancellationTokenSource(); - } - else - { - _cancellationToken = null; - } - } - - public JsonViewerViewModel EditorViewModel { get; set; } - - public HeaderEditorViewModel HeaderViewModel { get; set; } - - public string RequestCharge { get; set; } - - public void OnRequestChargeChanged() - { - _requestChargeStatusBarItem.DataContext.Value = RequestCharge; - } - - public string QueryInformation { get; set; } - - public void OnQueryInformationChanged() - { - _queryInformationStatusBarItem.DataContext.Value = QueryInformation; - } - - public string ContinuationToken { get; set; } - - public Dictionary QueryMetrics { get; set; } - - public RelayCommand ExecuteCommand - { - get - { - return _executeCommand - ?? (_executeCommand = new RelayCommand( - async () => await ExecuteQueryAsync(null).ConfigureAwait(false), - () => !IsRunning - && !string.IsNullOrEmpty(Content.Text) - && IsValid)); - } - } - - public RelayCommand CancelCommand - { - get - { - return _cancelCommand ?? (_cancelCommand = new RelayCommand( - () => _cancellationToken.Cancel(), - () => IsRunning)); - } - } - - private async Task ExecuteQueryAsync(string token) - { - try - { - IsRunning = true; - - Clean(); - - ((StatusBarItemContextCancellableCommand)_progessBarStatusBarItem.DataContext).IsCancellable = true; - - var query = string.IsNullOrEmpty(SelectedText) ? Content.Text : SelectedText; - _queryResult = await _dbService.ExecuteQueryAsync(Connection, Collection, query, this, token, _cancellationToken.Token).ConfigureAwait(false); - - ((StatusBarItemContextCancellableCommand)_progessBarStatusBarItem.DataContext).IsCancellable = false; - - ContinuationToken = _queryResult.ResponseContinuation; - - RequestCharge = $"Request Charge: {_queryResult.RequestCharge:N2}"; - QueryInformation = $"Returned {_queryResult.Count:N0} document(s)." + - (ContinuationToken != null - ? " (more results available)" - : string.Empty); - - if (_queryResult.QueryMetrics != null) - { - QueryMetrics = new Dictionary - { - { "All", _queryResult.QueryMetrics.Select(q => q.Value).Aggregate((i, next) => i + next) } - }; - - foreach (var metric in _queryResult.QueryMetrics.OrderBy(q => int.Parse(q.Key))) - { - QueryMetrics.Add(metric.Key, metric.Value); - } - } - else - { - QueryMetrics = null; - } - - EditorViewModel.SetText(_queryResult, HideSystemProperties); - HeaderViewModel.SetText(_queryResult.ResponseHeaders, HideSystemProperties); - } - catch (OperationCanceledException) - { - Clean(); - } - catch (DocumentClientException clientEx) - { - Clean(); - await _dialogService.ShowError(clientEx.Parse(), "Error", "ok", null).ConfigureAwait(false); - } - catch (Exception ex) - { - Clean(); - await _dialogService.ShowError(ex, "Error", "ok", null).ConfigureAwait(false); - } - finally - { - IsRunning = false; - } - } - - private void Clean() - { - ContinuationToken = null; - RequestCharge = null; - QueryInformation = null; - QueryMetrics = null; - EditorViewModel.SetText(null, HideSystemProperties); - HeaderViewModel.SetText(null, HideSystemProperties); - - GC.Collect(); - } - - public RelayCommand GoToNextPageCommand - { - get - { - return _goToNextPageCommand - ?? (_goToNextPageCommand = new RelayCommand( - async () => await ExecuteQueryAsync(ContinuationToken).ConfigureAwait(false), - () => ContinuationToken != null - && !IsRunning - && !string.IsNullOrEmpty(Content.Text) - && IsValid)); - } - } - - public RelayCommand SaveLocalCommand - { - get - { - return _saveLocalCommand ?? - (_saveLocalCommand = new RelayCommand( - async () => - { - var settings = new SaveFileDialogSettings - { - DefaultExt = "json", - Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*", - AddExtension = true, - OverwritePrompt = true, - CheckFileExists = false, - Title = "Save document locally", - InitialDirectory = Settings.Default.GetExportFolder() - }; - - await _dialogService.ShowSaveFileDialog(settings, async (confirm, result) => - { - if (confirm) - { - try - { - IsRunning = true; - - Settings.Default.ExportFolder = (new FileInfo(result.FileName)).DirectoryName; - Settings.Default.Save(); - - await DispatcherHelper.RunAsync(() => File.WriteAllText(result.FileName, EditorViewModel.Content.Text)); - } - catch (Exception ex) - { - await _dialogService.ShowError(ex, "Error", "ok", null).ConfigureAwait(false); - } - finally - { - IsRunning = false; - } - } - }).ConfigureAwait(false); - }, - () => !IsRunning && !string.IsNullOrEmpty(EditorViewModel.Content?.Text))); - } - } - - public string FileName { get; set; } - - public RelayCommand SaveQueryCommand - { - get - { - return _saveQueryCommand ?? - (_saveQueryCommand = new RelayCommand( - async param => - { - if (FileName == null || param == "SaveAs") - { - var settings = new SaveFileDialogSettings - { - DefaultExt = "sql", - Filter = "SQL Query (*.sql)|*.sql|All files (*.*)|*.*", - AddExtension = true, - OverwritePrompt = true, - CheckFileExists = false, - Title = "Save Query" - }; - - await _dialogService.ShowSaveFileDialog(settings, async (confirm, result) => - { - if (confirm) - { - try - { - IsRunning = true; - FileName = result.FileName; - - await DispatcherHelper.RunAsync(() => File.WriteAllText(result.FileName, Content.Text)); - } - catch (Exception ex) - { - await _dialogService.ShowError(ex, "Error", "ok", null).ConfigureAwait(false); - } - finally - { - IsRunning = false; - } - } - }).ConfigureAwait(false); - } - else - { - await DispatcherHelper.RunAsync(() => File.WriteAllText(FileName, Content.Text)); - } - }, - param => !IsRunning && !string.IsNullOrEmpty(Content?.Text))); - } - } - - public RelayCommand OpenQueryCommand - { - get - { - return _openQueryCommand ?? - (_openQueryCommand = new RelayCommand( - async () => - { - var settings = new OpenFileDialogSettings - { - DefaultExt = "sql", - Filter = "SQL Query (*.sql)|*.sql|All files (*.*)|*.*", - AddExtension = true, - CheckFileExists = false, - Title = "Save Query" - }; - - await _dialogService.ShowOpenFileDialog(settings, async (confirm, result) => - { - if (confirm) - { - try - { - IsRunning = true; - FileName = result.FileName; - var txt = File.ReadAllText(result.FileName); - await DispatcherHelper.RunAsync(() => Content.Text = txt); - } - catch (Exception ex) - { - await _dialogService.ShowError(ex, "Error", "ok", null).ConfigureAwait(false); - } - finally - { - IsRunning = false; - } - } - }).ConfigureAwait(false); - }, - () => !IsRunning && !string.IsNullOrEmpty(Content?.Text))); - } - } - - public bool HideSystemProperties { get; set; } = true; - - public void OnHideSystemPropertiesChanged() - { - if (_queryResult != null) - { - EditorViewModel.SetText(_queryResult, HideSystemProperties); - } - } - - protected override void OnClose() - { - Clean(); - base.OnClose(); - } - - public override void Cleanup() - { - SimpleIoc.Default.Unregister(EditorViewModel); - SimpleIoc.Default.Unregister(HeaderViewModel); - - base.Cleanup(); - } - - public bool? EnableScanInQuery { get; set; } = false; - public bool? EnableCrossPartitionQuery { get; set; } = false; - public int? MaxItemCount { get; set; } = 100; - public int? MaxDOP { get; set; } = -1; - public int? MaxBufferItem { get; set; } = -1; - public string PartitionKeyValue { get; set; } - public bool IsValid => !((INotifyDataErrorInfo)this).HasErrors; - } - - public class QueryEditorViewModelValidator : AbstractValidator - { - public QueryEditorViewModelValidator() - { - When(x => !string.IsNullOrEmpty(x.PartitionKeyValue?.Trim()), - () => RuleFor(x => x.PartitionKeyValue).SetValidator(new PartitionKeyValidator())); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/ScaleAndSettingsTabViewModel.cs b/src/CosmosDbExplorer/ViewModel/ScaleAndSettingsTabViewModel.cs deleted file mode 100644 index 8468d2b..0000000 --- a/src/CosmosDbExplorer/ViewModel/ScaleAndSettingsTabViewModel.cs +++ /dev/null @@ -1,305 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Extensions; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Services; -using CosmosDbExplorer.ViewModel.Indexes; -using FluentValidation; -using GalaSoft.MvvmLight.Messaging; -using GalaSoft.MvvmLight.Threading; -using ICSharpCode.AvalonEdit.Document; -using Microsoft.Azure.Documents; -using Newtonsoft.Json; -using PropertyChanged; -using Validar; - -namespace CosmosDbExplorer.ViewModel -{ - [InjectValidation] - public class ScaleAndSettingsTabViewModel : PaneWithZoomViewModel - { - private readonly IDocumentDbService _dbService; - private RelayCommand _discardCommand; - private RelayCommand _saveCommand; - private const decimal HourlyPrice = 0.00008m; - private readonly IDialogService _dialogService; - private readonly IDisposable _textChangedObservable; - - public ScaleAndSettingsTabViewModel(IMessenger messenger, IDialogService dialogService, IDocumentDbService dbService, IUIServices uiServices) - : base(messenger, uiServices) - { - Content = new TextDocument(); - - _textChangedObservable = Observable.FromEventPattern(Content, nameof(Content.TextChanged)) - .Select(evt => ((TextDocument)evt.Sender).Text) - .Throttle(TimeSpan.FromMilliseconds(600)) - .Where(text => !string.IsNullOrEmpty(text)) - .DistinctUntilChanged() - .Subscribe(OnContentTextChanged); - - _dbService = dbService; - _dialogService = dialogService; - } - - private void OnContentTextChanged(string text) - { - DispatcherHelper.RunAsync(() => - { - try - { - IsLoading = true; - PolicyViewModel.Policy = JsonConvert.DeserializeObject(text); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"TextChanged => {ex.Message}"); - } - finally - { - IsLoading = false; - } - }); - } - - public override void Cleanup() - { - _textChangedObservable.Dispose(); - base.Cleanup(); - } - - [DoNotSetChanged] - public bool IsLoading { get; set; } - - public override void Load(string contentId, ScaleSettingsNodeViewModel node, Connection connection, DocumentCollection collection) - { - IsLoading = true; - - ContentId = contentId; - Node = node; - Title = node.Name; - Header = node.Name; - Connection = connection; - Collection = collection; - - var split = Collection.AltLink.Split(new char[] { '/' }); - ToolTip = $"{split[1]}>{split[3]}"; - - AccentColor = Connection.AccentColor; - - SetInformation(); - - IsLoading = false; - } - - public ScaleSettingsNodeViewModel Node { get; protected set; } - - private void SetInformation() - { - TimeToLiveInSecond = Collection.DefaultTimeToLive; - TimeToLive = Collection.DefaultTimeToLive == null - ? TimeToLive.Off - : Collection.DefaultTimeToLive == -1 ? TimeToLive.Default : TimeToLive.On; - - PartitionKey = Collection.PartitionKey?.Paths.FirstOrDefault(); - IsFixedStorage = PartitionKey == null; - - PolicyViewModel = new IndexingPolicyViewModel(Collection.IndexingPolicy); - //PolicyViewModel.PropertyChanged += (s, e) => - //{ - // if (e.PropertyName == nameof(PolicyViewModel.IsValid)) - // { - // return; - // } - - // if (!IsLoading && PolicyViewModel.IsValid) - // { - // Content.Text = JsonConvert.SerializeObject(PolicyViewModel.Policy, Formatting.Indented); - // } - //}; - - Content.Text = JsonConvert.SerializeObject(PolicyViewModel.Policy, Formatting.Indented); - - IsChanged = false; - } - - public IndexingPolicyViewModel PolicyViewModel { get; protected set; } - - public Connection Connection { get; protected set; } - - public DocumentCollection Collection { get; protected set; } - - public int Throughput { get; set; } - - public string PartitionKey { get; set; } - - public bool IsFixedStorage { get; set; } - - public int PartitionCount { get; set; } - - public int MaxThroughput => PartitionCount * 10000; - - public int MinThroughput => IsFixedStorage ? 400 : Math.Max(1000, PartitionCount * 100); - - public string EstimatedPrice => $"${HourlyPrice * Throughput:N3} hourly / {HourlyPrice * Throughput * 24:N2} daily."; - - public int? TimeToLiveInSecond { get; set; } - - public TextDocument Content { get; set; } - - public bool IsChanged { get; set; } - - public TimeToLive TimeToLive { get; set; } - - public void OnTimeToLiveChanged() - { - switch (TimeToLive) - { - case TimeToLive.Off: - TimeToLiveInSecond = null; - IsTimeLiveInSecondVisible = false; - break; - case TimeToLive.On: - TimeToLiveInSecond = TimeToLiveInSecond.GetValueOrDefault(-1) == -1 ? 1 : TimeToLiveInSecond; - IsTimeLiveInSecondVisible = true; - break; - case TimeToLive.Default: - TimeToLiveInSecond = -1; - IsTimeLiveInSecondVisible = false; - break; - } - } - - public bool IsTimeLiveInSecondVisible { get; set; } - - public async Task LoadDataAsync() - { - IsLoading = true; - - try - { - var throughputTask = _dbService.GetThroughputAsync(Connection, Collection); - var partitionTask = _dbService.GetPartitionKeyRangeCountAsync(Connection, Collection); - - var result = await Task.WhenAll(throughputTask, partitionTask).ConfigureAwait(false); - - PartitionCount = result[1]; - Throughput = result[0]; - } - catch (DocumentClientException clientEx) - { - await _dialogService.ShowError(clientEx.Parse(), "Error", "ok", null).ConfigureAwait(false); - } - catch (Exception ex) - { - await _dialogService.ShowError(ex, "Error", "ok", null).ConfigureAwait(false); - } - finally - { - IsLoading = false; - IsChanged = false; - } - } - - public RelayCommand DiscardCommand - { - get - { - return _discardCommand - ?? (_discardCommand = new RelayCommand( - async () => - { - SetInformation(); - await LoadDataAsync().ConfigureAwait(false); - IsChanged = false; - }, - () => IsDirty)); - } - } - - public RelayCommand SaveCommand - { - get - { - return _saveCommand - ?? (_saveCommand = new RelayCommand( - async () => - { - try - { - Collection.DefaultTimeToLive = TimeToLiveInSecond; - Collection.IndexingPolicy = JsonConvert.DeserializeObject(Content.Text); - - await _dbService.UpdateCollectionSettingsAsync(Connection, Collection, Throughput).ConfigureAwait(false); - IsChanged = false; - } - catch (OperationCanceledException) - { - await _dialogService.ShowMessage("Operation cancelled by user...", "Cancel").ConfigureAwait(false); - } - catch (DocumentClientException clientEx) - { - await _dialogService.ShowError(clientEx.Parse(), "Error", "ok", null).ConfigureAwait(false); - } - catch (Exception ex) - { - await _dialogService.ShowError(ex, "Error", "ok", null).ConfigureAwait(false); - } - }, - () => IsDirty && IsValid)); - } - } - - public bool IsDirty - { - get - { - return IsChanged || PolicyViewModel?.IsChanged == true; - } - } - - public bool IsValid - { - get - { - return !((INotifyDataErrorInfo)this).HasErrors && (PolicyViewModel?.IsValid == true); - } - } - } - - public enum TimeToLive - { - Off, - On, - Default - } - - public class ScaleAndSettingsTabViewModelValidator : AbstractValidator - { - public ScaleAndSettingsTabViewModelValidator() - { - RuleFor(x => x.Throughput).NotEmpty() - .Must(throughput => throughput % 100 == 0) - .WithMessage("Throughput must be a multiple of 100"); - - RuleFor(x => x.Content) - .Custom((content, ctx) => - { - DispatcherHelper.RunAsync(() => - { - try - { - JsonConvert.DeserializeObject(content.Text); - } - catch (Exception ex) - { - ctx.AddFailure(ex.Message); - } - }); - }); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/UserEditViewModel.cs b/src/CosmosDbExplorer/ViewModel/UserEditViewModel.cs deleted file mode 100644 index ef755a8..0000000 --- a/src/CosmosDbExplorer/ViewModel/UserEditViewModel.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure; -using CosmosDbExplorer.Infrastructure.Extensions; -using CosmosDbExplorer.Infrastructure.Models; -using CosmosDbExplorer.Services; -using CosmosDbExplorer.ViewModel.Interfaces; -using FluentValidation; -using GalaSoft.MvvmLight.Messaging; -using GalaSoft.MvvmLight.Threading; -using Microsoft.Azure.Documents; -using Validar; - -namespace CosmosDbExplorer.ViewModel -{ - [InjectValidation] - public class UserEditViewModel : PaneViewModel, IAssetTabCommand - { - private readonly IDocumentDbService _dbService; - private readonly IDialogService _dialogService; - private RelayCommand _saveCommand; - private RelayCommand _deleteCommand; - private RelayCommand _discardCommand; - - public UserEditViewModel(IMessenger messenger, IDocumentDbService dbService, IDialogService dialogService, IUIServices uiServices) - : base(messenger, uiServices) - { - _dbService = dbService; - _dialogService = dialogService; - Header = "New User"; - Title = "User"; - } - - public string UserId { get; set; } - - public void OnUserIdChanged() - { - IsDirty = UserId != Node.User.Id; - } - - private void SetInformation() - { - UserId = Node?.User?.Id; - - var split = Node.Parent.Database.AltLink.Split(new char[] { '/' }); - ToolTip = split[1]; - - IsDirty = false; - } - - public bool IsNewDocument => Node?.User?.SelfLink == null; - - public bool IsValid => !((INotifyDataErrorInfo)this).HasErrors; - - public override void Load(string contentId, UserNodeViewModel node, Connection connection, DocumentCollection collection) - { - ContentId = contentId; - Node = node; - Connection = connection; - Header = node.Name ?? "New User"; - Title = "User"; - AccentColor = node.Parent.Parent.Parent.Connection.AccentColor; - SetInformation(); - } - - protected Connection Connection { get; set; } - - public UserNodeViewModel Node { get; protected set; } - - public RelayCommand DiscardCommand - { - get - { - return _discardCommand - ?? (_discardCommand = new RelayCommand(SetInformation, () => IsDirty)); - } - } - - public RelayCommand SaveCommand - { - get - { - return _saveCommand - ?? (_saveCommand = new RelayCommand( - async () => - { - User user = null; - - if (IsNewDocument) - { - user = new User { Id = UserId }; - } - else - { - user = Node.User; - user.Id = UserId; - } - - try - { - user = await _dbService.SaveUserAsync(Connection, Node.Parent.Database, user).ConfigureAwait(false); - - Header = user.Id; - Node.User = user; - ContentId = Node.ContentId; - - RaisePropertyChanged(() => IsNewDocument); - Node.Parent.RefreshCommand.Execute(null); - IsDirty = false; - } - catch (DocumentClientException ex) - { - await _dialogService.ShowError(ex.Parse(), "Error", null, null).ConfigureAwait(false); - } - }, - () => IsDirty && IsValid)); - } - } - - public RelayCommand DeleteCommand - { - get - { - return _deleteCommand - ?? (_deleteCommand = new RelayCommand( - async () => - { - await _dialogService.ShowMessage("Are you sure...", "Delete", null, null, async confirm => - { - if (confirm) - { - try - { - await _dbService.DeleteUserAsync(Node.Parent.Parent.Parent.Connection, Node.User).ConfigureAwait(false); - } - catch (DocumentClientException ex) - { - await _dialogService.ShowError(ex.Parse(), "Error", null, null).ConfigureAwait(false); - } - finally - { - Node.Parent.RefreshCommand.Execute(null); - await DispatcherHelper.RunAsync(() => CloseCommand.Execute(null)); - } - } - }).ConfigureAwait(false); - }, - () => !IsNewDocument)); - } - } - - public bool IsDirty { get; private set; } - } - - public class UserEditViewModelValidator : AbstractValidator - { - public UserEditViewModelValidator() - { - RuleFor(x => x.UserId).NotEmpty(); - } - } -} diff --git a/src/CosmosDbExplorer/ViewModel/ViewModelLocator.cs b/src/CosmosDbExplorer/ViewModel/ViewModelLocator.cs deleted file mode 100644 index 7d42582..0000000 --- a/src/CosmosDbExplorer/ViewModel/ViewModelLocator.cs +++ /dev/null @@ -1,87 +0,0 @@ -/* - In App.xaml: - - - - - In the View: - DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelName}" - - You can also use Blend to do all this with the tool's support. - See http://www.galasoft.ch/mvvm -*/ - -using CommonServiceLocator; -using CosmosDbExplorer.Services; -using CosmosDbExplorer.ViewModel.Assets; -using GalaSoft.MvvmLight; -using GalaSoft.MvvmLight.Ioc; -using GalaSoft.MvvmLight.Messaging; - -namespace CosmosDbExplorer.ViewModel -{ - /// - /// This class contains static references to all the view models in the - /// application and provides an entry point for the bindings. - /// - public class ViewModelLocator - { - /// - /// Initializes a new instance of the ViewModelLocator class. - /// - public ViewModelLocator() - { - ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); - - if (ViewModelBase.IsInDesignModeStatic) - { - // Create design time view services and models - } - else - { - // Create run time view services and models - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(() => SimpleIoc.Default); - } - - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - - SimpleIoc.Default.Register(); - } - - public MainViewModel Main => ServiceLocator.Current.GetInstance(); - public AccountSettingsViewModel AccountSettings => SimpleIoc.Default.GetInstanceWithoutCaching(); - public DatabaseViewModel Database => ServiceLocator.Current.GetInstance(); - public AboutViewModel About => ServiceLocator.Current.GetInstance(); - - public AddCollectionViewModel AddCollection => SimpleIoc.Default.GetInstanceWithoutCaching(); - - public static void Cleanup() - { - // TODO Clear the ViewModels - } - } -} diff --git a/src/CosmosDbExplorer/ViewModels/AboutViewModel.cs b/src/CosmosDbExplorer/ViewModels/AboutViewModel.cs new file mode 100644 index 0000000..1bd196b --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/AboutViewModel.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Models; + +using Microsoft.Toolkit.Mvvm.ComponentModel; +using Microsoft.Toolkit.Mvvm.Input; + +namespace CosmosDbExplorer.ViewModels +{ + public class AboutViewModel : ObservableObject, INavigationAware + { + private readonly ISystemService _systemService; + private RelayCommand? _openLinkCommand; + private RelayCommand? _openGitHubCommand; + + public AboutViewModel(ISystemService systemService) + { + var assembly = Assembly.GetEntryAssembly(); + + if (assembly == null) + { + throw new NullReferenceException("Can not load entry assembly"); + } + + Version = FileVersionInfo.GetVersionInfo(assembly.Location)?.FileVersion ?? "version not found"; + Title = (Attribute.GetCustomAttribute(assembly, typeof(AssemblyTitleAttribute), false) as AssemblyTitleAttribute)?.Title ?? "error retrieving assembly title"; + + ExternalComponents = new List + { + new ExternalComponent { Name = "AvalonEdit", LicenseUrl = "http://opensource.org/licenses/MIT", ProjectUrl = "http://www.avalonedit.net"}, + //new ExternalComponent { Name = "Extended WPF Toolkit", LicenseUrl = "https://github.com/xceedsoftware/wpftoolkit/blob/master/license.md", ProjectUrl = "https://github.com/xceedsoftware/wpftoolkit"}, + new ExternalComponent { Name = "Fluent.Ribbon", LicenseUrl = "https://github.com/fluentribbon/Fluent.Ribbon/blob/master/License.txt", ProjectUrl = "https://github.com/fluentribbon/Fluent.Ribbon"}, + new ExternalComponent { Name = "FluentValidation", LicenseUrl = "https://github.com/JeremySkinner/FluentValidation/blob/master/License.txt", ProjectUrl = "https://github.com/JeremySkinner/fluentvalidation"}, + new ExternalComponent { Name = "Fody", LicenseUrl = "http://www.opensource.org/licenses/mit-license.php", ProjectUrl = "http://github.com/Fody/Fody"}, + //new ExternalComponent { Name = "Json.NET", LicenseUrl = "https://raw.githubusercontent.com/JamesNK/Newtonsoft.Json/master/LICENSE.md", ProjectUrl = "https://www.newtonsoft.com/json"}, + new ExternalComponent { Name = "PropertyChanged.Fody", LicenseUrl = "http://www.opensource.org/licenses/mit-license.php", ProjectUrl = "http://github.com/Fody/PropertyChanged"}, + new ExternalComponent { Name = "Validar.Fody", LicenseUrl = "http://www.opensource.org/licenses/mit-license.php", ProjectUrl = "http://github.com/Fody/Validar"}, + new ExternalComponent { Name = "GongSolutions.WPF.DragDrop", LicenseUrl = "https://github.com/punker76/gong-wpf-dragdrop#license", ProjectUrl = "https://github.com/punker76/gong-wpf-dragdrop"}, + new ExternalComponent { Name = "Autoupdater.NET.Official", LicenseUrl = "https://github.com/ravibpatel/AutoUpdater.NET/blob/master/LICENSE", ProjectUrl = "https://github.com/ravibpatel/AutoUpdater.NET"}, + //new ExternalComponent { Name = "LiveCharts", LicenseUrl = "https://github.com/Live-Charts/Live-Charts/blob/master/LICENSE.TXT", ProjectUrl = "https://lvcharts.net/"}, + }.OrderBy(ec => ec.Name).ToList(); + _systemService = systemService; + } + + public string Version { get; set; } + + public string Title { get; set; } + + public static List Authors => new() { new Author("Sacha Bruttin", "sachabruttin"), new Author("savbace", "savbace") }; + + public static string LicenseUrl => "https://github.com/sachabruttin/CosmosDbExplorer/blob/master/LICENSE"; + + public static string ProjectUrl => "https://www.bruttin.com/CosmosDbExplorer"; + + public List ExternalComponents { get; } + + public RelayCommand OpenLinkCommand => _openLinkCommand ??= new((url) => _systemService?.OpenInWebBrowser(url)); + + public RelayCommand OpenGitHubCommand => _openGitHubCommand ??= new((url) => _systemService?.OpenInWebBrowser($"https://github.com/{url}")); + + + public void OnNavigatedFrom() + { + } + + public void OnNavigatedTo(object parameter) + { + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/AccountSettingsViewModel.cs b/src/CosmosDbExplorer/ViewModels/AccountSettingsViewModel.cs new file mode 100644 index 0000000..f33912e --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/AccountSettingsViewModel.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Messages; +using CosmosDbExplorer.Properties; +using FluentValidation; +using Microsoft.Toolkit.Mvvm.ComponentModel; +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; +using PropertyChanged; +using Validar; + +namespace CosmosDbExplorer.ViewModels +{ + [InjectValidation] + public class AccountSettingsViewModel : UIViewModelBase, INavigationAware + { + private CosmosConnection? _connection = default; + private RelayCommand _saveAccountCommand; + private readonly IPersistAndRestoreService _persistAndRestoreService; + + public AccountSettingsViewModel(IPersistAndRestoreService persistAndRestoreService, IUIServices uiServices) + : base(uiServices) + { + _persistAndRestoreService = persistAndRestoreService; + } + + public string Title => "Account Settings"; + public object Icon => App.Current.FindResource("AddConnectionIcon"); + + [OnChangedMethod(nameof(UpdateSaveCommandStatus))] + public string? AccountEndpoint { get; set; } + + [OnChangedMethod(nameof(UpdateSaveCommandStatus))] + public string? AccountSecret { get; set; } + + [OnChangedMethod(nameof(UpdateSaveCommandStatus))] + public string? Label { get; set; } + + public ConnectionType ConnectionType { get; set; } + + public bool EnableEndpointDiscovery { get; set; } + + public Color? AccentColor { get; set; } + + public Action? SetResult { get; set; } + + protected void OnAccentColorChanged() + { + if (AccentColor != null && AccentColor.Value.Equals(Colors.Transparent)) + { + AccentColor = null; + } + } + + protected void UpdateSaveCommandStatus() => SaveAccountCommand.NotifyCanExecuteChanged(); + + public bool UseLocalEmulator { get; set; } + + public void OnUseLocalEmulatorChanged() + { + if (UseLocalEmulator) + { + AccountEndpoint = Resources.EmulatorEndpoint; + AccountSecret = Resources.EmulatorSecret; + } + else + { + AccountEndpoint = null; + AccountSecret = null; + } + } + + public RelayCommand SaveAccountCommand => _saveAccountCommand ??= new(SaveCommandExecute, SaveCommandCanExecute); + + public void SaveCommandExecute() + { + if (_connection == null || AccountEndpoint == null) + { + return; + } + + IsBusy = true; + + var accentColor = AccentColor is null + ? System.Drawing.Color.Transparent + : System.Drawing.Color.FromArgb(AccentColor.Value.A, AccentColor.Value.R, AccentColor.Value.G, AccentColor.Value.B); + + var connection = new CosmosConnection(_connection.Id, Label, new Uri(AccountEndpoint), AccountSecret, ConnectionType, EnableEndpointDiscovery, accentColor); + + _persistAndRestoreService.PersistConnection(connection); + Messenger.Send(new ConnectionSettingSavedMessage(connection)); + OnClose(); + + IsBusy = false; + } + + public bool SaveCommandCanExecute() => string.IsNullOrEmpty(((IDataErrorInfo)this).Error); + + public void OnNavigatedTo(object parameter) + { + SetConnection((CosmosConnection)parameter); + } + + public void OnNavigatedFrom() + { + + } + + private void SetConnection(CosmosConnection connection) + { + _connection = connection; + + AccountEndpoint = connection.DatabaseUri?.ToString(); + AccountSecret = connection.AuthenticationKey; + Label = connection.Label; + UseLocalEmulator = connection.IsLocalEmulator(); + ConnectionType = connection.ConnectionType; + + if (connection.AccentColor is not null) + { + AccentColor = Color.FromArgb(connection.AccentColor.Value.A, connection.AccentColor.Value.R, connection.AccentColor.Value.G, connection.AccentColor.Value.B); + } + + EnableEndpointDiscovery = _connection.EnableEndpointDiscovery; + } + + private void OnClose() + { + SetResult?.Invoke(true); + } + } + + public class AccountSettingsViewModelValidator : AbstractValidator + { + public AccountSettingsViewModelValidator() + { + RuleFor(x => x.AccountEndpoint).NotEmpty().When(x => !x.UseLocalEmulator); + RuleFor(x => x.AccountSecret).NotEmpty().When(x => !x.UseLocalEmulator); + RuleFor(x => x.Label).NotEmpty(); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/Assets/AssetTabViewModelBase.cs b/src/CosmosDbExplorer/ViewModels/Assets/AssetTabViewModelBase.cs new file mode 100644 index 0000000..b7cf8b3 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/Assets/AssetTabViewModelBase.cs @@ -0,0 +1,165 @@ +using System; +using System.Threading.Tasks; +using System.Windows.Input; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Contracts; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Messages; +using CosmosDbExplorer.ViewModels.DatabaseNodes; + +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; + +using PropertyChanged; + +namespace CosmosDbExplorer.ViewModels.Assets +{ + public abstract class AssetTabViewModelBase : PaneWithZoomViewModel, IAssetTabCommand + where TNode : TreeViewItemViewModel, IAssetNode + where TResource : ICosmosScript + { + //private readonly IDialogService _dialogService; + private readonly IServiceProvider _serviceProvider; + private readonly IDialogService _dialogService; + private RelayCommand _discardCommand; + private AsyncRelayCommand _saveCommand; + private AsyncRelayCommand _deleteCommand; + + protected AssetTabViewModelBase(IUIServices uiServices, IDialogService dialogService) + : base(uiServices) + { + Content = GetDefaultContent(); + _dialogService = dialogService; + Header = GetDefaultHeader(); + Title = GetDefaultTitle(); + ContentId = Guid.NewGuid().ToString(); + } + + protected abstract string GetDefaultHeader(); + protected abstract string GetDefaultTitle(); + protected abstract string GetDefaultContent(); + protected virtual void SetInformationImpl(TResource resource) + { + SetText(resource.Body); + } + + [OnChangedMethod(nameof(UpdateCommandStatus))] + protected string AltLink { get; set; } + + [OnChangedMethod(nameof(UpdateCommandStatus))] + public string Content { get; set; } + + public override void Load(string contentId, TNode? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer? container) + { + ContentId = contentId; + Node = node; + Connection = connection; + Container = container; + AccentColor = connection.AccentColor; + + if (node != null) + { + var databaseNode = ((DatabaseNodes.DatabaseNodeViewModel)node.Parent.Parent.Parent); + ToolTip = $"{Connection.Label}/{database.Id}/{Container.Id}"; + SetInformation(node.Resource); + } + } + + public TNode Node { get; protected set; } + + protected void SetInformation(TResource? resource) + { + if (resource != null) + { + Id = resource.Id; + AltLink = resource.SelfLink; + ContentId = AltLink; + Header = resource.Id; + SetInformationImpl(resource); + } + } + + public CosmosConnection Connection { get; set; } + + public CosmosContainer Container { get; set; } + + public string? Id { get; set; } + + [OnChangedMethod(nameof(UpdateCommandStatus))] + public bool IsDirty { get; set; } + + public bool IsNewDocument => AltLink == null; + + protected void SetText(string content) + { + Content = content; + IsDirty = false; + } + + public ICommand DiscardCommand => _discardCommand ??= new(DiscardCommandExecute, DiscardCommandCanExecute); + + protected virtual void DiscardCommandExecute() + { + if (IsNewDocument) + { + SetText(GetDefaultContent()); + } + else + { + SetInformation(Node.Resource); + } + } + + protected virtual bool DiscardCommandCanExecute() + { + return IsDirty; + } + + public ICommand SaveCommand => _saveCommand ??= new(SaveCommandExecute, SaveCommandCanExecute); + + protected virtual async Task SaveCommandExecute() + { + try + { + var resource = await SaveAsyncImpl(); + Messenger.Send(new UpdateOrCreateNodeMessage(resource, Container, AltLink)); + SetInformation(resource); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "Error"); + } + } + + protected virtual bool SaveCommandCanExecute() => IsDirty; + + protected abstract Task SaveAsyncImpl(); + + public ICommand DeleteCommand => _deleteCommand ??= new(DeleteCommandExecute, DeleteCommandCanExecute); + + protected virtual async Task DeleteCommandExecute() + { + await _dialogService.ShowQuestion("Are you sure...", "Delete", async confirm => + { + if (confirm) + { + await DeleteAsyncImpl(); + Messenger.Send(new RemoveNodeMessage(AltLink)); + Messenger.Send(new CloseDocumentMessage(this)); + } + }).ConfigureAwait(false); + } + + protected abstract Task DeleteAsyncImpl(); + protected virtual bool DeleteCommandCanExecute() => !IsNewDocument; + + protected virtual void UpdateCommandStatus() + { + ((AsyncRelayCommand)SaveCommand).NotifyCanExecuteChanged(); + ((AsyncRelayCommand)DeleteCommand).NotifyCanExecuteChanged(); + ((RelayCommand)DiscardCommand).NotifyCanExecuteChanged(); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModel/Assets/StoredProcParameterViewModel.cs b/src/CosmosDbExplorer/ViewModels/Assets/StoredProcParameterViewModel.cs similarity index 53% rename from src/CosmosDbExplorer/ViewModel/Assets/StoredProcParameterViewModel.cs rename to src/CosmosDbExplorer/ViewModels/Assets/StoredProcParameterViewModel.cs index 7298958..39f14bf 100644 --- a/src/CosmosDbExplorer/ViewModel/Assets/StoredProcParameterViewModel.cs +++ b/src/CosmosDbExplorer/ViewModels/Assets/StoredProcParameterViewModel.cs @@ -1,15 +1,14 @@ using System; using System.IO; using System.Linq; -using System.Reactive.Linq; +//using System.Reactive.Linq; using FluentValidation; -using GalaSoft.MvvmLight; -using GalaSoft.MvvmLight.Threading; using ICSharpCode.AvalonEdit.Document; +using Microsoft.Toolkit.Mvvm.ComponentModel; using Newtonsoft.Json.Linq; using Validar; -namespace CosmosDbExplorer.ViewModel.Assets +namespace CosmosDbExplorer.ViewModels.Assets { [InjectValidation] public class StoredProcParameterViewModel : ObservableObject, IDisposable @@ -18,39 +17,37 @@ public class StoredProcParameterViewModel : ObservableObject, IDisposable public StoredProcParameterViewModel() { - Document = new TextDocument(); - Kind = StoredProcParameterKind.Json; + Document = string.Empty; + Kind = StoredProcParameterKind.Array; - _textChangedObservable = Observable.FromEventPattern(Document, nameof(Document.TextChanged)) - .Select(evt => ((TextDocument)evt.Sender).Text) - .Throttle(TimeSpan.FromMilliseconds(600)) - .Where(text => !string.IsNullOrEmpty(text)) - .DistinctUntilChanged() - .Subscribe(OnContentTextChanged); + //_textChangedObservable = Observable.FromEventPattern(Document, nameof(Document.TextChanged)) + // .Select(evt => ((TextDocument)evt.Sender).Text) + // .Throttle(TimeSpan.FromMilliseconds(600)) + // .Where(text => !string.IsNullOrEmpty(text)) + // .DistinctUntilChanged() + // .Subscribe(OnContentTextChanged); } - private void OnContentTextChanged(string text) - { - DispatcherHelper.RunAsync(() => RaisePropertyChanged(nameof(Document))); - } + //private void OnContentTextChanged(string text) + //{ + // DispatcherHelper.RunAsync(() => RaisePropertyChanged(nameof(Document))); + //} public StoredProcParameterKind Kind { get; set; } public string FileName { get; set; } - public TextDocument Document { get; set; } + public string Document { get; set; } public object GetValue() { - switch (Kind) + return Kind switch { - case StoredProcParameterKind.Json: - return JToken.Parse(Document.Text); - case StoredProcParameterKind.File: - return JToken.Parse(File.ReadAllText(FileName)); - default: - return null; - } + StoredProcParameterKind.Object => JToken.Parse(Document), + StoredProcParameterKind.Array => JArray.Parse(Document), + StoredProcParameterKind.File => JToken.Parse(File.ReadAllText(FileName)), + _ => null + }; } #region IDisposable Support @@ -87,11 +84,22 @@ public StoredProcParameterViewModelValidator() { //RuleFor(x => x.Value).NotEmpty(); When(x => x.Kind == StoredProcParameterKind.File, () => RuleFor(x => x.FileName).Must(obj => File.Exists(obj as string)).WithMessage("File not found!")); - When(x => x.Kind == StoredProcParameterKind.Json, () => RuleFor(x => x.Document).Custom((doc, ctx) => + When(x => x.Kind == StoredProcParameterKind.Array, () => RuleFor(x => x.Document).Custom((doc, ctx) => + { + try + { + JArray.Parse(doc); + } + catch (Exception ex) + { + ctx.AddFailure(ex.Message); + } + })); + When(x => x.Kind == StoredProcParameterKind.Object, () => RuleFor(x => x.Document).Custom((doc, ctx) => { try { - JToken.Parse(doc.Text); + JToken.Parse(doc); } catch (Exception ex) { @@ -103,7 +111,8 @@ public StoredProcParameterViewModelValidator() public enum StoredProcParameterKind { - Json, + Object, + Array, File, } } diff --git a/src/CosmosDbExplorer/ViewModels/Assets/StoredProcedureTabViewModel.cs b/src/CosmosDbExplorer/ViewModels/Assets/StoredProcedureTabViewModel.cs new file mode 100644 index 0000000..8cf2fdb --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/Assets/StoredProcedureTabViewModel.cs @@ -0,0 +1,260 @@ +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Input; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.Models; +using CosmosDbExplorer.Services.DialogSettings; +using CosmosDbExplorer.ViewModels.DatabaseNodes; +using FluentValidation; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Input; +using Validar; + +namespace CosmosDbExplorer.ViewModels.Assets +{ + [InjectValidation] + public class StoredProcedureTabViewModel : AssetTabViewModelBase + { + private AsyncRelayCommand? _executeCommand; + private RelayCommand? _removeParameterCommand; + private RelayCommand? _addParameterCommand; + private RelayCommand? _browseParameterCommand; + private RelayCommand? _saveLocalCommand; + private RelayCommand? _goToNextPageCommand; + private readonly StatusBarItem _requestChargeStatusBarItem; + private readonly IServiceProvider _serviceProvider; + private readonly IDialogService _dialogService; + + public StoredProcedureTabViewModel(IServiceProvider serviceProvider, IUIServices uiServices, IDialogService dialogService) + : base(uiServices, dialogService) + { + HeaderViewModel = new HeaderEditorViewModel { IsReadOnly = true }; + IconSource = App.Current.FindResource("StoredProcedureIcon"); + + _requestChargeStatusBarItem = new StatusBarItem(new StatusBarItemContext { Value = RequestCharge, IsVisible = IsBusy }, StatusBarItemType.SimpleText, "Request Charge", System.Windows.Controls.Dock.Left); + StatusBarItems.Add(_requestChargeStatusBarItem); + _serviceProvider = serviceProvider; + _dialogService = dialogService; + } + + protected override string GetDefaultHeader() => "New Stored Procedure"; + protected override string GetDefaultTitle() => "Stored Procedure"; + protected override string GetDefaultContent() => "function storedProcedure(){}"; + + public override void Load(string contentId, StoredProcedureNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer? container) + { + if (connection is null) + { + throw new ArgumentNullException(nameof(connection)); + } + + if (database is null) + { + throw new ArgumentNullException(nameof(database)); + } + + if (container is null) + { + throw new ArgumentNullException(nameof(container)); + } + + _scriptService = ActivatorUtilities.CreateInstance(_serviceProvider, connection, database, container); + + IsCollectionPartitioned = !string.IsNullOrEmpty(container.PartitionKeyPath); // collection.PartitionKey.Paths.Count > 0; + base.Load(contentId, node, connection, database, container); + + UpdateCommandStatus(); + } + + protected override Task SaveAsyncImpl() + { + if (Id is null) + { + throw new Exception("Asset Id is null!"); + } + + var resource = new CosmosStoredProcedure(Id, Content, AltLink); + return _scriptService.SaveStoredProcedureAsync(resource); + } + + protected override Task DeleteAsyncImpl() + { + return _scriptService.DeleteStoredProcedureAsync(Node.Resource); + } + + public string Log { get; protected set; } + + public string? QueryResult { get; set; } + + public HeaderEditorViewModel HeaderViewModel { get; set; } + + public string RequestCharge { get; set; } + + public void OnRequestChargeChanged() + { + _requestChargeStatusBarItem.DataContext.Value = RequestCharge; + } + + protected override void OnIsBusyChanged() + { + _requestChargeStatusBarItem.DataContext.IsVisible = !IsBusy; + + base.OnIsBusyChanged(); + } + public string PartitionKey { get; set; } + + private CosmosScriptService _scriptService; + + public bool IsCollectionPartitioned { get; protected set; } + + public ObservableCollection Parameters { get; } = new ObservableCollection(); + + public RelayCommand AddParameterCommand => _addParameterCommand ??= new(() => Parameters.Add(new StoredProcParameterViewModel())/*, () => !IsBusy && !IsDirty*/); + + public RelayCommand RemoveParameterCommand => _removeParameterCommand ??= new(RemoveParameterCommandExecute/*, RemoveParameterCommandCanExecute*/); + + private void RemoveParameterCommandExecute(StoredProcParameterViewModel? item) + { + if (item == null) + { + return; + } + + Parameters.Remove(item); + item.Dispose(); + } + + private bool RemoveParameterCommandCanExecute(StoredProcParameterViewModel? item) => !IsBusy & !IsDirty; + + public RelayCommand BrowseParameterCommand => _browseParameterCommand ??= new(BrowseParameterCommandExecute/*, BrowseParameterCommandCanExecute*/); + + private void BrowseParameterCommandExecute(object? item) + { + throw new NotImplementedException(); + //var options = new OpenFileDialogSettings + //{ + // Title = "Select file...", + // DefaultExt = "json", + // Multiselect = false, + // Filter = "JSON|*.json" + //}; + + //await _dialogService.ShowOpenFileDialog(options, (confirm, result) => + //{ + // if (confirm && item is StoredProcParameterViewModel vm) + // { + // vm.FileName = result.FileName; + // } + //}).ConfigureAwait(false); + } + + private bool BrowseParameterCommandCanExecute(object? item) => !IsBusy & !IsDirty; + + + public AsyncRelayCommand ExecuteCommand => _executeCommand ??= new AsyncRelayCommand(ExecuteCommandExecute/*, ExecuteCommandCanExecute*/); + + private async Task ExecuteCommandExecute() + { + if (Id is null) + { + throw new NullReferenceException(nameof(Id)); + } + + QueryResult = null; + Log = string.Empty; + HeaderViewModel.SetText(null, false); + + try + { + IsBusy = true; + + var result = await _scriptService.ExecuteStoredProcedureAsync(Id, PartitionKey, Parameters.Select(p => p.GetValue()).ToArray()); + RequestCharge = $"Request Charge: {result.RequestCharge:N2}"; + + Log = result.ScriptLog; + QueryResult = result.Result; + HeaderViewModel.SetText(result.Headers, false); + } + //catch (DocumentClientException clientEx) + //{ + // await _dialogService.ShowError(clientEx.Parse(), "Error", "ok", null).ConfigureAwait(false); + //} + catch (Exception ex) + { + await _dialogService.ShowError(ex, "Error"); + } + finally + { + IsBusy = false; + UpdateCommandStatus(); + } + } + + protected override void UpdateCommandStatus() + { + base.UpdateCommandStatus(); + ExecuteCommand.NotifyCanExecuteChanged(); + SaveLocalCommand.NotifyCanExecuteChanged(); + AddParameterCommand.NotifyCanExecuteChanged(); + } + + private bool ExecuteCommandCanExecute() => !IsBusy && !IsDirty && IsValid; + + public RelayCommand SaveLocalCommand => _saveLocalCommand ??= new(SaveLocalCommandExecute/*, SaveLocalCommandCanExecute*/); + + private void SaveLocalCommandExecute() + { + var settings = new SaveFileDialogSettings + { + //DefaultExt = "json", + //Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*", + //AddExtension = true, + OverwritePrompt = true, + CheckFileExists = false, + Title = "Save document locally" + }; + + _dialogService.ShowSaveFileDialog(settings, async (confirm, result) => + { + if (confirm) + { + try + { + IsBusy = true; + System.IO.File.WriteAllText(result.FileName, QueryResult); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "Error"); + } + finally + { + IsBusy = false; + } + } + }); + } + + private bool SaveLocalCommandCanExecute() => !IsBusy && QueryResult is not null; + + public RelayCommand GoToNextPageCommand => _goToNextPageCommand ??= new(() => throw new NotImplementedException(), () => false); + + public bool IsValid => string.IsNullOrEmpty(((IDataErrorInfo)this).Error); //!((INotifyDataErrorInfo)this).HasErrors; + } + + public class StoredProcedureTabViewModelValidator : AbstractValidator + { + public StoredProcedureTabViewModelValidator() + { + //When(x => x.IsCollectionPartitioned, + // () => RuleFor(x => x.PartitionKey).NotEmpty().SetValidator(new PartitionKeyValidator())); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/Assets/TriggerTabViewModel.cs b/src/CosmosDbExplorer/ViewModels/Assets/TriggerTabViewModel.cs new file mode 100644 index 0000000..1635ada --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/Assets/TriggerTabViewModel.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading.Tasks; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.ViewModels.DatabaseNodes; + +using Microsoft.Extensions.DependencyInjection; + +using PropertyChanged; + +namespace CosmosDbExplorer.ViewModels.Assets +{ + public class TriggerTabViewModel : AssetTabViewModelBase + { + private readonly IServiceProvider _serviceProvider; + private CosmosScriptService _scriptService; + + public TriggerTabViewModel(IServiceProvider serviceProvider, IUIServices uiServices, IDialogService dialogService) + : base(uiServices, dialogService) + { + IconSource = App.Current.FindResource("TriggerIcon"); + _serviceProvider = serviceProvider; + } + + public override void Load(string contentId, TriggerNodeViewModel node, CosmosConnection connection, CosmosDatabase database, CosmosContainer container) + { + _scriptService = ActivatorUtilities.CreateInstance(_serviceProvider, connection, database, container); + + base.Load(contentId, node, connection, database, container); + } + + public CosmosTriggerType TriggerType { get; set; } + + public CosmosTriggerOperation TriggerOperation { get; set; } + + protected override string GetDefaultHeader() => "New Trigger"; + protected override string GetDefaultTitle() => "Trigger"; + protected override string GetDefaultContent() => "function trigger(){}"; + + + protected override void SetInformationImpl(CosmosTrigger resource) + { + TriggerOperation = resource.Operation; + TriggerType = resource.Type; + base.SetInformationImpl(resource); + } + + protected override Task SaveAsyncImpl() + { + if (Id is null) + { + throw new Exception("Asset Id is null!"); + } + + var resource = new CosmosTrigger(Id, Content, AltLink) + { + Operation = TriggerOperation, + Type = TriggerType + }; + + return _scriptService.SaveTriggerAsync(resource); + } + + protected override Task DeleteAsyncImpl() + { + return _scriptService.DeleteTriggerAsync(Node.Resource); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/Assets/UserDefFuncTabViewModel.cs b/src/CosmosDbExplorer/ViewModels/Assets/UserDefFuncTabViewModel.cs new file mode 100644 index 0000000..7f963a2 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/Assets/UserDefFuncTabViewModel.cs @@ -0,0 +1,53 @@ +using System; +using System.Threading.Tasks; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Core.Contracts.Services; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.ViewModels.DatabaseNodes; + +using Microsoft.Extensions.DependencyInjection; + +namespace CosmosDbExplorer.ViewModels.Assets +{ + public class UserDefFuncTabViewModel : AssetTabViewModelBase + { + private ICosmosScriptService _scriptService; + private readonly IServiceProvider _serviceProvider; + + public UserDefFuncTabViewModel(IServiceProvider serviceProvider, IUIServices uiServices, IDialogService dialogService) + : base(uiServices, dialogService) + { + IconSource = App.Current.FindResource("UdfIcon"); + _serviceProvider = serviceProvider; + } + + public override void Load(string contentId, UserDefFuncNodeViewModel node, CosmosConnection connection, CosmosDatabase database, CosmosContainer container) + { + _scriptService = ActivatorUtilities.CreateInstance(_serviceProvider, connection, database, container); + + base.Load(contentId, node, connection, database, container); + } + + protected override string GetDefaultHeader() => "New User Defined Function"; + protected override string GetDefaultTitle() => "User Defined Function"; + protected override string GetDefaultContent() => "function userDefinedFunction(){}"; + + protected override Task SaveAsyncImpl() + { + if (Id is null) + { + throw new Exception("Asset Id is null!"); + } + + var resource = new CosmosUserDefinedFunction(Id, Content, AltLink); + return _scriptService.SaveUserDefinedFunctionAsync(resource); + } + + protected override Task DeleteAsyncImpl() + { + return _scriptService.DeleteUserDefinedFunctionAsync(Node.Resource); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/ContainerPropertyViewModel.cs b/src/CosmosDbExplorer/ViewModels/ContainerPropertyViewModel.cs new file mode 100644 index 0000000..f045582 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/ContainerPropertyViewModel.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using FluentValidation; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.ComponentModel; +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; +using PropertyChanged; +using Validar; + +namespace CosmosDbExplorer.ViewModels +{ + [InjectValidation] + public class ContainerPropertyViewModel : UIViewModelBase, INavigationAware + { + //private readonly IDocumentDbService _dbService; + private readonly IServiceProvider _serviceProvider; + private readonly IDialogService _dialogService; + + private CosmosContainerService _containerService; + private AsyncRelayCommand _saveCommand; + + public ContainerPropertyViewModel(IServiceProvider serviceProvider, IDialogService dialogService, IUIServices uiServices) + : base(uiServices) + { + IsFixedStorage = true; + Throughput = 400; + Title = "Add Container"; + //_dbService = dbService; + _serviceProvider = serviceProvider; + _dialogService = dialogService; + } + + public Action? SetResult { get; set; } + + public string Title { get; } + + public CosmosConnection Connection { get; protected set; } + + [OnChangedMethod(nameof(UpdateSaveCommandStatus))] + public string ContainerId { get; set; } + + public bool IsFixedStorage { get; set; } + + public void OnIsFixedStorageChanged() + { + if (Throughput > 10000) + { + Throughput = 10000; + } + } + + public bool IsUnlimitedStorage { get; set; } + + public void OnIsUnlimitedStorageChanged() + { + if (Throughput < 1000) + { + Throughput = 1000; + } + } + + public bool ProvisionThroughput { get; set; } = false; + + [OnChangedMethod(nameof(UpdateSaveCommandStatus))] + public string PartitionKey { get; set; } + + public bool IsThroughputAutoscale { get; set; } = true; + + public bool IsLargePartition { get; set; } + + [DependsOn(nameof(IsUnlimitedStorage), nameof(IsFixedStorage))] + public int MaxThroughput => IsFixedStorage ? 10000 : 100000; + + [DependsOn(nameof(IsUnlimitedStorage), nameof(IsFixedStorage))] + public int MinThroughput => IsFixedStorage ? 400 : 1000; + + [OnChangedMethod(nameof(UpdateSaveCommandStatus))] + [AlsoNotifyFor(nameof(EstimatedPrice))] + public int Throughput { get; set; } + + public bool IsServerless { get; set; } + + protected void UpdateSaveCommandStatus() => SaveCommand.NotifyCanExecuteChanged(); + + public AsyncRelayCommand SaveCommand => _saveCommand ??= new(SaveCommandExecuteAsync, SaveCommandCanExecute); + + private async Task SaveCommandExecuteAsync() + { + IsBusy = true; + + try + { + var container = new CosmosContainer(ContainerId, IsLargePartition) + { + PartitionKeyPath = PartitionKey, + }; + + var createdContainer = await _containerService.CreateContainerAsync(container, Throughput, IsThroughputAutoscale, new System.Threading.CancellationToken()); + + Messenger.Send(new Messages.UpdateOrCreateNodeMessage(createdContainer, Connection, null)); + + OnClose(); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "Error during Container Creation"); + } + finally + { + IsBusy = false; + } + } + + private bool SaveCommandCanExecute() => string.IsNullOrEmpty(((IDataErrorInfo)this).Error); + + public CosmosDatabase Database { get; set; } + + + public string EstimatedPrice + { + get + { + const decimal hourly = 0.00008m; + return $"${hourly * Throughput:N3} hourly / {hourly * Throughput * 24:N2} daily."; + } + } + + + public void OnNavigatedFrom() + { + + } + + public void OnNavigatedTo(object parameter) + { + var (connection, database) = ((CosmosConnection, CosmosDatabase))parameter; + Connection = connection; + Database = database; + + IsServerless = database.IsServerless; + + _containerService = ActivatorUtilities.CreateInstance(_serviceProvider, Connection, Database); + } + + private void OnClose() + { + SetResult?.Invoke(true); + } + } + + public class ContainerPropertyViewModelValidator : AbstractValidator + { + public ContainerPropertyViewModelValidator() + { + RuleFor(x => x.ContainerId).NotEmpty(); + RuleFor(x => x.PartitionKey).NotEmpty(); + RuleFor(x => x.Throughput).NotEmpty().When(x => x.ProvisionThroughput); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/ContainerScaleSettingsViewModel.cs b/src/CosmosDbExplorer/ViewModels/ContainerScaleSettingsViewModel.cs new file mode 100644 index 0000000..8c97975 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/ContainerScaleSettingsViewModel.cs @@ -0,0 +1,316 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Core; +using CosmosDbExplorer.Core.Contracts.Services; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.ViewModels.DatabaseNodes; + +using FluentValidation; + +using Microsoft.Azure.Cosmos; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Input; + +using PropertyChanged; + +using Validar; + +namespace CosmosDbExplorer.ViewModels +{ + [InjectValidation] + public class ContainerScaleSettingsViewModel : PaneWithZoomViewModel + { + private readonly IServiceProvider _serviceProvider; + private readonly IDialogService _dialogService; + private readonly ISystemService _systemService; + private ICosmosContainerService? _containerService; + private AsyncRelayCommand? _saveCommand; + private RelayCommand? _discardCommand; + private RelayCommand? _openUrlCommand; + private CosmosThroughput? _originalThroughput; + + public ContainerScaleSettingsViewModel(IServiceProvider serviceProvider, IUIServices uiServices, IDialogService dialogService, ISystemService systemService) + : base(uiServices) + { + _serviceProvider = serviceProvider; + _dialogService = dialogService; + _systemService = systemService; + IconSource = App.Current.FindResource("ScaleSettingsIcon"); + } + + public ScaleSettingsNodeViewModel? Node { get; private set; } + public CosmosConnection? Connection { get; private set; } + public CosmosContainer? Container { get; private set; } + + public bool? IsTimeLiveInSecondVisible => TimeToLive == TimeToLiveType.On; + + [OnChangedMethod(nameof(UpdateCommandStatus))] + public int? TimeToLiveInSecond { get; set; } + + public TimeToLiveType? TimeToLive { get; set; } + + protected void OnTimeToLiveChanged() + { + TimeToLiveInSecond = TimeToLive switch + { + TimeToLiveType.On => TimeToLiveInSecond, + TimeToLiveType.Default => -1, + _ => null, + }; + + UpdateCommandStatus(); + } + + [OnChangedMethod(nameof(UpdateCommandStatus))] + public CosmosGeospatialType GeoType { get; set; } + + [OnChangedMethod(nameof(UpdateCommandStatus))] + public string? IndexingPolicy { get; set; } + + public bool IsIndexingPolicyChanged { get; set; } + + public bool IsThroughputAutoscale { get; set; } = true; + + public int MaxThroughput { get; set; } + + public int? MinThroughput { get; set; } + + public bool IsFixedStorage { get; set; } + + public void OnIsFixedStorageChanged() + { + if (Throughput > 10000) + { + Throughput = 10000; + } + } + + public bool IsUnlimitedStorage { get; set; } + + public void OnIsUnlimitedStorageChanged() + { + if (Throughput < 1000) + { + Throughput = 1000; + } + } + + [OnChangedMethod(nameof(UpdateCommandStatus))] + public int? Throughput { get; set; } + + public int Increment => IsThroughputAutoscale ? 1000 : 100; + + [DependsOn(nameof(IsThroughputAutoscale), nameof(Throughput))] + public string Information => $"{Throughput * 0.1} RU/s (10 % of max RU/s) - {Throughput} RU/s"; + + [DependsOn(nameof(IsThroughputAutoscale), nameof(Throughput))] + public string DataStoredInGb => $"{Throughput * 0.01}"; + + public AsyncRelayCommand SaveCommand => _saveCommand ??= new(SaveCommandExecute, () => HasThroughputChanged || HasIndexingPolicyChanged.GetValueOrDefault(false) || HasSettingsChanged); + + public RelayCommand DiscardCommand => _discardCommand ??= new(DiscardCommandExecute, () => HasThroughputChanged || HasIndexingPolicyChanged.GetValueOrDefault(false) || HasSettingsChanged); + + public RelayCommand OpenUrlCommand => _openUrlCommand ??= new RelayCommand(OpenUrl); + + private bool HasThroughputChanged => (_originalThroughput?.AutoscaleMaxThroughput ?? _originalThroughput?.Throughput) != Throughput; + private bool HasSettingsChanged => (Container?.DefaultTimeToLive != TimeToLiveInSecond) || (Container?.GeospatialType != GeoType); + private bool? HasIndexingPolicyChanged => !Container?.IndexingPolicy?.Equals(IndexingPolicy); + + public override async void Load(string contentId, ScaleSettingsNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer? container) + { + if (node is null) + { + throw new ArgumentNullException(nameof(node)); + } + + if (connection is null) + { + throw new ArgumentNullException(nameof(connection)); + } + + if (database is null) + { + throw new ArgumentNullException(nameof(database)); + } + + if (container is null) + { + throw new ArgumentNullException(nameof(container)); + } + + //IsLoading = true; + + ContentId = contentId; + Node = node; + Title = node.Name; + Header = node.Name; + Connection = connection; + Container = container; + + //var split = Container.SelfLink.Split(new char[] { '/' }); + //ToolTip = $"{split[1]}>{split[3]}"; + ToolTip = $"{Connection.Label}/{database.Id}/{Container.Id}"; + + AccentColor = Connection.AccentColor; + + + SetSettings(); + + _containerService = ActivatorUtilities.CreateInstance(_serviceProvider, connection, database); + + if (!database.IsServerless) + { + var response = await _containerService.GetThroughputAsync(container); + SetThroughputInfo(response); + } + + //IsLoading = false; + } + + private void SetSettings() + { + TimeToLiveInSecond = Container?.DefaultTimeToLive; + TimeToLive = TimeToLiveTypeExtensions.Get(Container?.DefaultTimeToLive); + GeoType = Container?.GeospatialType ?? CosmosGeospatialType.Geography; + IndexingPolicy = Container?.IndexingPolicy; + } + + private void SetThroughputInfo(CosmosThroughput? throughput) + { + if (throughput is not null) + { + _originalThroughput = throughput; + + MinThroughput = _originalThroughput.MinThroughtput; + MaxThroughput = int.MaxValue - (int.MaxValue % 1000); + IsThroughputAutoscale = _originalThroughput.AutoscaleMaxThroughput.HasValue; + Throughput = _originalThroughput.AutoscaleMaxThroughput ?? _originalThroughput.Throughput; + } + } + + private async Task SaveCommandExecute() + { + try + { + if (Container == null) + { + throw new Exception("Container not defined!"); + } + + IsBusy = true; + + if (HasThroughputChanged && Throughput is not null) + { + var throughput = await _containerService.UpdateThroughputAsync(Container, Throughput.Value, IsThroughputAutoscale); + SetThroughputInfo(throughput); + } + + if (HasSettingsChanged || HasIndexingPolicyChanged.GetValueOrDefault(false)) + { + var container = new CosmosContainer(Container.Id, Container.IsLargePartitionKey.GetValueOrDefault()) + { + DefaultTimeToLive = TimeToLiveTypeExtensions.ToCosmosValue(TimeToLive, TimeToLiveInSecond), + GeospatialType = GeoType, + IndexingPolicy = IndexingPolicy ?? string.Empty, + PartitionKeyPath = Container.PartitionKeyPath // Cannot be changed! + }; + + var response = await _containerService.UpdateContainerAsync(container); + // TODO:Send message + Container = response; + SetSettings(); + } + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "An unexpected error occured!"); + } + finally + { + IsBusy = false; + } + } + + private void OpenUrl(string? url) + { + if (!string.IsNullOrEmpty(url)) + { + _systemService.OpenInWebBrowser(url); + } + } + + private void DiscardCommandExecute() + { + SetThroughputInfo(_originalThroughput); + SetSettings(); + } + + private void UpdateCommandStatus() + { + SaveCommand.NotifyCanExecuteChanged(); + DiscardCommand.NotifyCanExecuteChanged(); + } + + } + + [TypeConverter(typeof(EnumDescriptionTypeConverter))] + public enum TimeToLiveType + { + Off = 0, + [Description("On (Default)")] + Default = 1, + On = 2 + } + + public static class TimeToLiveTypeExtensions + { + public static TimeToLiveType Get(int? ttl) + { + return ttl switch + { + null => TimeToLiveType.Off, + -1 => TimeToLiveType.Default, + _ => TimeToLiveType.On, + }; + } + + public static int? ToCosmosValue(this TimeToLiveType? value, int? timeToLiveInSecs) + { + return value switch + { + TimeToLiveType.Off => null, + TimeToLiveType.Default => -1, + _ => timeToLiveInSecs + }; + } + } + + public class ContainerScaleSettingsViewModelValidator : AbstractValidator + { + public ContainerScaleSettingsViewModelValidator() + { + RuleFor(x => x.Throughput) + .NotEmpty() + .GreaterThanOrEqualTo(x => x.MinThroughput) + .LessThanOrEqualTo(x => x.MaxThroughput) + .Custom((throughput, context) => + { + if (throughput % context.InstanceToValidate.Increment != 0) + { + context.AddFailure($"Value must be a multiple of {context.InstanceToValidate.Increment}."); + } + }); + + RuleFor(x => x.TimeToLiveInSecond) + .GreaterThan(0).NotEmpty().When(x => x.TimeToLive == TimeToLiveType.On) + .Equal(0).When(x => x.TimeToLive == TimeToLiveType.Default) + .Equal(-1).When(x => x.TimeToLive == TimeToLiveType.Off); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseNodes/AssetRootNodeViewModelBase.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/AssetRootNodeViewModelBase.cs new file mode 100644 index 0000000..dff9efd --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/AssetRootNodeViewModelBase.cs @@ -0,0 +1,93 @@ +using System.Threading.Tasks; +using System.Windows.Input; + +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Contracts; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Messages; +using CosmosDbExplorer.Models; + +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; + +namespace CosmosDbExplorer.ViewModels.DatabaseNodes +{ + public abstract class AssetRootNodeViewModelBase : TreeViewItemViewModel, ICanRefreshNode, IHaveContainerNodeViewModel + where TResource : ICosmosResource + { + private AsyncRelayCommand _refreshCommand; + private RelayCommand _openNewCommand; + + protected AssetRootNodeViewModelBase(ContainerNodeViewModel parent) + : base(parent, true) + { + Messenger.Register, UpdateOrCreateNodeMessage>(this, static (r, m) => r.InnerOnUpdateOrCreateNodeMessage(m)); + } + + public string Name { get; protected set; } + + public new ContainerNodeViewModel Parent + { + get { return base.Parent; } + } + + public ICommand RefreshCommand => _refreshCommand ??= new(RefreshCommandExecute); + + private Task RefreshCommandExecute() + { + Children.Clear(); + return LoadChildren(new System.Threading.CancellationToken()); + } + + public RelayCommand OpenNewCommand => _openNewCommand ??= new(OpenNewCommandExecute); + + protected abstract void OpenNewCommandExecute(); + + public ContainerNodeViewModel ContainerNode => Parent; + + private void InnerOnUpdateOrCreateNodeMessage(UpdateOrCreateNodeMessage message) + { + if (message.Parent == ContainerNode.Container) + { + OnUpdateOrCreateNodeMessage(message); + } + } + + protected abstract void OnUpdateOrCreateNodeMessage(UpdateOrCreateNodeMessage message); + } + + public abstract class AssetNodeViewModelBase : + TreeViewItemViewModel, IAssetNode, IHaveOpenCommand + where TResource : ICosmosResource + where TParent : AssetRootNodeViewModelBase + { + private RelayCommand _openCommand; + private RelayCommand _deleteCommand; + + protected AssetNodeViewModelBase(TParent parent, TResource resource) + : base(parent, false) + { + Resource = resource; + } + + public string Name => Resource.Id ?? "New"; + + public string ContentId => Resource.SelfLink ?? "New"; + + public System.Drawing.Color? AccentColor => Parent.Parent.Parent.Parent.Connection.AccentColor; + + public TResource Resource { get; set; } + + public RelayCommand OpenCommand => _openCommand ??= new(async () => await OpenCommandImp().ConfigureAwait(false)); + + protected abstract Task OpenCommandImp(); + + public RelayCommand DeleteCommand => _deleteCommand ??= new(async () => await DeleteCommandImpl().ConfigureAwait(false)); + + protected abstract Task DeleteCommandImpl(); + + //protected IDialogService DialogService => SimpleIoc.Default.GetInstance(); + + //protected IDocumentDbService DbService => SimpleIoc.Default.GetInstance(); + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseNodes/ConnectionNodeViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/ConnectionNodeViewModel.cs new file mode 100644 index 0000000..408f303 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/ConnectionNodeViewModel.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Contracts.Services; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.Messages; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; +using PropertyChanged; + +namespace CosmosDbExplorer.ViewModels.DatabaseNodes +{ + public class ConnectionNodeViewModel : TreeViewItemViewModel, ICanRefreshNode + { + private readonly IServiceProvider _serviceProvider; + private readonly IWindowManagerService _windowManagerService; + private readonly IRightPaneService _rightPaneService; + private readonly IDialogService _dialogService; + private readonly IPersistAndRestoreService _persistAndRestoreService; + private RelayCommand _addNewDatabaseCommand; + private RelayCommand _editConnectionCommand; + private RelayCommand _refreshCommand; + private AsyncRelayCommand _removeConnectionCommand; + + public ConnectionNodeViewModel(IServiceProvider serviceProvider, CosmosConnection connection) + : base(null, true) + { + _serviceProvider = serviceProvider; + _dialogService = serviceProvider.GetRequiredService(); + _windowManagerService = serviceProvider.GetRequiredService(); + _rightPaneService = _serviceProvider.GetRequiredService(); + _persistAndRestoreService = serviceProvider.GetRequiredService(); + Connection = connection; + + Messenger.Register>(this, static (r, m) => r.OnDatabaseCreated(m)); + } + + private void OnDatabaseCreated(UpdateOrCreateNodeMessage message) + { + if (message.Parent == Connection) + { + Children.Add(new DatabaseNodeViewModel(_serviceProvider, message.Resource, this)); + } + } + + public CosmosConnection Connection { get; set; } + + public IList Databases { get; protected set; } + + public string Name => Connection.DatabaseUri.ToString(); + + protected override async Task LoadChildren(CancellationToken token) + { + AddNewDatabaseCommand.NotifyCanExecuteChanged(); + + try + { + IsLoading = true; + + var service = ActivatorUtilities.CreateInstance(_serviceProvider, Connection); + Databases = await service.GetDatabasesAsync(token); + + // TODO: Handle cancellation + + foreach (var db in Databases) + { + Children.Add(new DatabaseNodeViewModel(_serviceProvider, db, this)); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex.Message); + } + finally + { + IsLoading = false; + } + } + + public RelayCommand EditConnectionCommand => _editConnectionCommand ??= new(EditConnectionCommandExecute); + + private void EditConnectionCommandExecute() + { + var vmName = typeof(AccountSettingsViewModel)?.FullName; + + if (!string.IsNullOrEmpty(vmName)) + { + _windowManagerService.OpenInDialog(vmName, Connection); + } + } + + public AsyncRelayCommand RemoveConnectionCommand => _removeConnectionCommand ??= new(RemoveConnectionCommandExecute); + + private async Task RemoveConnectionCommandExecute() + { + void confirmed(bool confirm) + { + if (confirm) + { + _persistAndRestoreService.RemoveConnection(Connection); + Messenger.Send(new RemoveConnectionMessage(Connection)); + } + } + + await _dialogService.ShowQuestion( + $"Are you sure that you want to delete this connection '{Connection.Label}'?", + "Delete connection", + confirmed); + } + + public RelayCommand AddNewDatabaseCommand => _addNewDatabaseCommand ??= new(AddNewDatabaseCommandExecute); + + private void AddNewDatabaseCommandExecute() + { + var vmName = typeof(DatabasePropertyViewModel).FullName; + + if (string.IsNullOrEmpty(vmName)) + { + return; + } + + _rightPaneService.OpenInRightPane(vmName, Connection); + } + + public ICommand RefreshCommand => _refreshCommand ??= new(RefreshCommandExecuteAsync); + + private async void RefreshCommandExecuteAsync() + { + Children.Clear(); + await LoadChildren(new CancellationToken()); + } + + protected override void NotifyCanExecuteChanged() + { + AddNewDatabaseCommand.NotifyCanExecuteChanged(); + ((RelayCommand)RefreshCommand).NotifyCanExecuteChanged(); + RemoveConnectionCommand.NotifyCanExecuteChanged(); + EditConnectionCommand.NotifyCanExecuteChanged(); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseNodes/ContainerNodeViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/ContainerNodeViewModel.cs new file mode 100644 index 0000000..5559e9f --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/ContainerNodeViewModel.cs @@ -0,0 +1,157 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.Messages; +using Microsoft.Azure.Documents; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; + +namespace CosmosDbExplorer.ViewModels.DatabaseNodes +{ + public class ContainerNodeViewModel : ResourceNodeViewModelBase, IHaveContainerNodeViewModel, IContent + { + private readonly IServiceProvider _serviceProvider; + private readonly CosmosContainerService _containerService; + private readonly IDialogService _dialogService; + private RelayCommand _openImportDocumentCommand; + private RelayCommand _newStoredProcedureCommand; + private RelayCommand _newUdfCommand; + private RelayCommand _newTriggerCommand; + private RelayCommand _openSqlQueryCommand; + private AsyncRelayCommand _deleteContainerCommand; + + private readonly StoredProcedureRootNodeViewModel _storedProcedureNode; + private readonly UserDefFuncRootNodeViewModel _userDefFuncNode; + private readonly TriggerRootNodeViewModel _triggerNode; + + public ContainerNodeViewModel(IServiceProvider serviceProvider, CosmosContainer container, DatabaseNodeViewModel parent) + : base(container, parent, true) + { + _serviceProvider = serviceProvider; + Container = container; + + _dialogService = _serviceProvider.GetRequiredService(); + _containerService = ActivatorUtilities.CreateInstance(_serviceProvider, Parent.Parent.Connection, Parent.Database); + + _storedProcedureNode = new StoredProcedureRootNodeViewModel(this, _serviceProvider); + _userDefFuncNode = new UserDefFuncRootNodeViewModel(this, _serviceProvider); + _triggerNode = new TriggerRootNodeViewModel(this, _serviceProvider); + } + + protected override Task LoadChildren(CancellationToken token) + { + Children.Add(new DocumentNodeViewModel(this)); + Children.Add(new ScaleSettingsNodeViewModel(this)); + Children.Add(_storedProcedureNode); + Children.Add(_userDefFuncNode); + Children.Add(_triggerNode); + Children.Add(new MetricsNodeViewModel(this)); + + return Task.CompletedTask; + } + + public CosmosContainer Container { get; } + + public RelayCommand OpenSqlQueryCommand => _openSqlQueryCommand ??= new(() => Messenger.Send(new OpenQueryViewMessage(this, Parent.Parent.Connection, Parent.Database, Container))); + + //public RelayCommand ClearAllDocumentsCommand + //{ + // get + // { + // return _clearAllDocumentsCommand + // ?? (_clearAllDocumentsCommand = new RelayCommand( + // async () => + // { + // await DialogService.ShowMessage($"All documents will be removed from the collection {Parent.Name}.\n\nIf you have a lot of documents, this could take a while and be costly and it is perhaps preferable to use the 'Recreate' option.\n\nAre you sure you want to continue?", + // "Cleanup collection", null, null, + // async confirm => + // { + // if (confirm) + // { + // MessengerInstance.Send(new IsBusyMessage(true)); + // await DbService.CleanCollectionAsync(Parent.Parent.Connection, Collection).ConfigureAwait(false); + // MessengerInstance.Send(new IsBusyMessage(false)); + // await DispatcherHelper.RunAsync(async () => await DialogService.ShowMessageBox($"Collection {Parent.Name} is now empty.", "Cleanup collection").ConfigureAwait(false)); + // } + // }).ConfigureAwait(false); + // })); + // } + //} + + //public RelayCommand RecreateAsEmptyCommand + //{ + // get + // { + // return _recreateAsEmptyCommand + // ?? (_recreateAsEmptyCommand = new RelayCommand( + // async () => + // { + // await DialogService.ShowMessage($"Collection will be deleted and recreated with the same parameters and assets:\n\t- Stored Procedures\n\t- Triggers\n\t- User Defined Functions\n\nThis is fast and cost efficient but could affect your application(s) availability.\n\nAre you sure you want to continue?", + // "Recreate empty collection", null, null, + // async confirm => + // { + // if (confirm) + // { + // MessengerInstance.Send(new IsBusyMessage(true)); + // await DbService.RecreateCollectionAsync(Parent.Parent.Connection, Parent.Database, Collection).ConfigureAwait(false); + // MessengerInstance.Send(new IsBusyMessage(false)); + // Parent.RefreshCommand.Execute(null); + // await DispatcherHelper.RunAsync(async () => await DialogService.ShowMessageBox($"Collection {Parent.Name} is now empty.", "Cleanup collection").ConfigureAwait(false)); + // } + // }).ConfigureAwait(false); + // })); + // } + //} + + public RelayCommand OpenImportDocumentCommand => _openImportDocumentCommand ??= new(() => Messenger.Send(new OpenImportDocumentViewMessage(this, Parent.Parent.Connection, Parent.Database, Container))); + + public RelayCommand NewStoredProcedureCommand => _newStoredProcedureCommand ??= new(() => Messenger.Send(new EditStoredProcedureMessage(null, Parent.Parent.Connection, Parent.Database, Container))); + + public RelayCommand NewUdfCommand => _newUdfCommand ??= new(() => Messenger.Send(new EditUserDefFuncMessage(null, Parent.Parent.Connection, Parent.Database, Container))); + + public RelayCommand NewTriggerCommand => _newTriggerCommand ??= new(() => Messenger.Send(new EditTriggerMessage(null, Parent.Parent.Connection, Parent.Database, Container))); + + public AsyncRelayCommand DeleteContainerCommand => _deleteContainerCommand ??= new(DeleteContainerCommandExecute); + + private async Task DeleteContainerCommandExecute() + { + async void OnDialogClose(bool confirm) + { + if (!confirm) + { + return; + } + + try + { + await _containerService.DeleteContainserAsync(Container, new CancellationToken()); + Messenger.Send(new RemoveNodeMessage(Container.SelfLink)); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, $"Error during container {Container.Id} deletion"); + } + } + + var msg = $"Are you sure you want to delete the container '{Container.Id}' and all his content?"; + await _dialogService.ShowQuestion(msg, "Delete Container", OnDialogClose); + } + + public ContainerNodeViewModel ContainerNode => this; + + protected override void NotifyCanExecuteChanged() + { + OpenImportDocumentCommand.NotifyCanExecuteChanged(); + NewStoredProcedureCommand.NotifyCanExecuteChanged(); + NewUdfCommand.NotifyCanExecuteChanged(); + NewTriggerCommand.NotifyCanExecuteChanged(); + OpenSqlQueryCommand.NotifyCanExecuteChanged(); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseNodes/DatabaseNodeViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/DatabaseNodeViewModel.cs new file mode 100644 index 0000000..fbba8b5 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/DatabaseNodeViewModel.cs @@ -0,0 +1,135 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.Messages; +using CosmosDbExplorer.Views; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; + +namespace CosmosDbExplorer.ViewModels.DatabaseNodes +{ + public class DatabaseNodeViewModel : ResourceNodeViewModelBase + { + private RelayCommand _addNewCollectionCommand; + private AsyncRelayCommand _deleteDatabaseCommand; + private readonly IServiceProvider _serviceProvider; + private readonly IRightPaneService _rightPaneService; + private readonly IWindowManagerService _windowManagerService; + private readonly IDialogService _dialogService; + private readonly CosmosContainerService _containerService; + private readonly CosmosDatabaseService _databaseService; + + public DatabaseNodeViewModel(IServiceProvider serviceProvider, CosmosDatabase database, ConnectionNodeViewModel parent) + : base(database, parent, true) + { + _serviceProvider = serviceProvider; + _rightPaneService = _serviceProvider.GetRequiredService(); + _windowManagerService = _serviceProvider.GetRequiredService(); + _dialogService = _serviceProvider.GetRequiredService(); + _containerService = ActivatorUtilities.CreateInstance(_serviceProvider, Parent.Connection, database); + _databaseService = ActivatorUtilities.CreateInstance(_serviceProvider, Parent.Connection); + + Database = database; + + Messenger.Register>(this, static (r, m) => r.OnNewContainerCreated(m)); + + } + + private void OnNewContainerCreated(UpdateOrCreateNodeMessage message) + { + if (message.Parent == Parent.Connection) + { + Children.Add(new ContainerNodeViewModel(_serviceProvider, message.Resource, this)); + _rightPaneService.CleanUp(); + } + } + + public CosmosDatabase Database { get; } + + protected override async Task LoadChildren(CancellationToken token) + { + try + { + IsLoading = true; + + AddNewContainerCommand.NotifyCanExecuteChanged(); + + var containers = await _containerService.GetContainersAsync(token); + + // TODO: Handle cancellation + if (Database.Throughput != null) + { + Children.Add(new DatabaseScaleNodeViewModel(this)); + } + + Children.Add(new UsersNodeViewModel(_serviceProvider, Database, this)); + + foreach (var container in containers.OrderBy(c => c.Id)) + { + Children.Add(new ContainerNodeViewModel(_serviceProvider, container, this)); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex.Message); + } + finally + { + IsLoading = false; + } + } + + public RelayCommand AddNewContainerCommand => _addNewCollectionCommand ??= new(AddNewContainerCommandExecute, () => !HasDummyChild); + + private void AddNewContainerCommandExecute() + { + var vmName = typeof(ContainerPropertyViewModel).FullName; + + if (string.IsNullOrEmpty(vmName)) + { + return; + } + + _rightPaneService.OpenInRightPane(vmName, (Parent.Connection, Database)); + } + + + public AsyncRelayCommand DeleteDatabaseCommand => _deleteDatabaseCommand ??= new(DeleteDatabaseCommandExecuteAsync); + + private async Task DeleteDatabaseCommandExecuteAsync() + { + async void OnDialogClose(bool confirm) + { + if (!confirm) + { + return; + } + + try + { + await _databaseService.DeleteDatabaseAsync(Database, new CancellationToken()); + Messenger.Send(new RemoveNodeMessage(Database.SelfLink)); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, $"Error during database {Database.Id} deletion"); + } + } + + var msg = $"Are you sure you want to delete the database '{Database.Id}' and all his content?"; + await _dialogService.ShowQuestion(msg, "Delete Database", OnDialogClose); + } + + + protected override void NotifyCanExecuteChanged() + { + AddNewContainerCommand.NotifyCanExecuteChanged(); + DeleteDatabaseCommand.NotifyCanExecuteChanged(); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseNodes/DatabaseScaleNodeViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/DatabaseScaleNodeViewModel.cs new file mode 100644 index 0000000..ac526c8 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/DatabaseScaleNodeViewModel.cs @@ -0,0 +1,24 @@ +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Messages; +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; + +namespace CosmosDbExplorer.ViewModels.DatabaseNodes +{ + public class DatabaseScaleNodeViewModel : TreeViewItemViewModel, IContent, IHaveOpenCommand + { + private RelayCommand _openCommand; + + public DatabaseScaleNodeViewModel(DatabaseNodeViewModel parent) + : base(parent, false) + { + Name = "Scale"; + } + + public string Name { get; private set; } + + public string ContentId => Parent.Database.SelfLink + "/Scale"; + + public RelayCommand OpenCommand => _openCommand ??= new(() => Messenger.Send(new OpenDatabaseScaleViewMessage(this, Parent.Parent.Connection, Parent.Database, null))); + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseNodes/DocumentNodeViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/DocumentNodeViewModel.cs new file mode 100644 index 0000000..6586640 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/DocumentNodeViewModel.cs @@ -0,0 +1,32 @@ +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Messages; +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; + +namespace CosmosDbExplorer.ViewModels.DatabaseNodes +{ + public class DocumentNodeViewModel : TreeViewItemViewModel, IHaveContainerNodeViewModel, IContent, IHaveOpenCommand + { + private RelayCommand _openCommand; + + public DocumentNodeViewModel(ContainerNodeViewModel parent) + : base(parent, false) + { + Name = "Items"; + } + + public string Name { get; set; } + + public RelayCommand OpenCommand => _openCommand ??= new(OpenCommandExecute); + + public ContainerNodeViewModel ContainerNode => Parent; + + public string ContentId => Parent.Container.SelfLink + "/Documents"; + + private void OpenCommandExecute() + { + IsSelected = false; + Messenger.Send(new OpenDocumentsViewMessage(this, Parent.Parent.Parent.Connection, Parent.Parent.Database, Parent.Container)); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/ICanEditDelete.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/ICanEditDelete.cs similarity index 56% rename from src/CosmosDbExplorer/ViewModel/DatabaseNodes/ICanEditDelete.cs rename to src/CosmosDbExplorer/ViewModels/DatabaseNodes/ICanEditDelete.cs index d39bcd8..2e9299d 100644 --- a/src/CosmosDbExplorer/ViewModel/DatabaseNodes/ICanEditDelete.cs +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/ICanEditDelete.cs @@ -1,7 +1,6 @@ -using System.Windows.Input; -using CosmosDbExplorer.Infrastructure; +using Microsoft.Toolkit.Mvvm.Input; -namespace CosmosDbExplorer.ViewModel +namespace CosmosDbExplorer.ViewModels.DatabaseNodes { public interface ICanEditDelete { diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseNodes/IHaveContainerNodeViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/IHaveContainerNodeViewModel.cs new file mode 100644 index 0000000..acf26c5 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/IHaveContainerNodeViewModel.cs @@ -0,0 +1,9 @@ +using CosmosDbExplorer.ViewModels.DatabaseNodes; + +namespace CosmosDbExplorer.ViewModels.DatabaseNodes +{ + public interface IHaveContainerNodeViewModel + { + ContainerNodeViewModel ContainerNode { get; } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseNodes/MetricsNodeViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/MetricsNodeViewModel.cs new file mode 100644 index 0000000..21a44a1 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/MetricsNodeViewModel.cs @@ -0,0 +1,32 @@ +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Messages; +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; + +namespace CosmosDbExplorer.ViewModels.DatabaseNodes +{ + public class MetricsNodeViewModel : TreeViewItemViewModel, IHaveContainerNodeViewModel, IContent, IHaveOpenCommand + { + private RelayCommand _openCommand; + + public MetricsNodeViewModel(ContainerNodeViewModel parent) + : base(parent, false) + { + Name = "Collection Metrics"; + } + + public string Name { get; set; } + + public RelayCommand OpenCommand => _openCommand ??= new(ExecuteOpenCommand); + + public ContainerNodeViewModel ContainerNode => Parent; + + public string ContentId => Parent.Container.SelfLink + "/Metrics"; + + private void ExecuteOpenCommand() + { + IsSelected = false; + Messenger.Send(new OpenMetricsViewMessage(this, Parent.Parent.Parent.Connection, Parent.Parent.Database, Parent.Container)); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseNodes/PermissionNodeViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/PermissionNodeViewModel.cs new file mode 100644 index 0000000..1ded4b7 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/PermissionNodeViewModel.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Messages; + +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; + +namespace CosmosDbExplorer.ViewModels.DatabaseNodes +{ + public class PermissionNodeViewModel : TreeViewItemViewModel + , ICanRefreshNode + , IContent + , IHaveOpenCommand + { + private AsyncRelayCommand _refreshCommand; + private RelayCommand _openCommand; + + public PermissionNodeViewModel(CosmosPermission permission, UserNodeViewModel parent) + : base(parent, false) + { + Permission = permission; + } + + public CosmosPermission Permission { get; set; } + + public string Name => Permission?.Id; + + public string ContentId => Permission?.SelfLink ?? "NewPermission"; + + public ICommand RefreshCommand => _refreshCommand ??= new(RefreshCommandExecute); + + private Task RefreshCommandExecute() + { + Children.Clear(); + return LoadChildren(new System.Threading.CancellationToken()); + } + + public RelayCommand OpenCommand => _openCommand ??= new(() => Messenger.Send(new EditPermissionMessage(this, Parent.Parent.Parent.Parent.Connection, Parent.Parent.Database))); + + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseNodes/ResourceNodeViewModelBase.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/ResourceNodeViewModelBase.cs new file mode 100644 index 0000000..ca7f94c --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/ResourceNodeViewModelBase.cs @@ -0,0 +1,39 @@ +using System.Threading; +using System.Windows; +using System.Windows.Input; + +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Contracts; +using Microsoft.Toolkit.Mvvm.Input; + +namespace CosmosDbExplorer.ViewModels.DatabaseNodes +{ + public abstract class ResourceNodeViewModelBase : TreeViewItemViewModel, ICanRefreshNode, IContent + where TParent : TreeViewItemViewModel + { + private RelayCommand _refreshCommand; + private RelayCommand _copySelfLinkToClipboardCommand; + + protected ResourceNodeViewModelBase(ICosmosResource resource, TParent parent, bool lazyLoadChildren) + : base(parent, lazyLoadChildren) + { + Resource = resource; + } + + public string Name => Resource?.Id ?? "Unknown"; + public string? ContentId => Resource?.SelfLink; + + public ICommand RefreshCommand => _refreshCommand ??= new(RefreshCommandExecute); + + private async void RefreshCommandExecute() + { + Children.Clear(); + await LoadChildren(new CancellationToken()).ConfigureAwait(false); + } + + public RelayCommand CopySelfLinkToClipboardCommand => _copySelfLinkToClipboardCommand ??= new(() => Clipboard.SetText(Resource.SelfLink)); + + protected ICosmosResource Resource { get; set; } + + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseNodes/ScaleSettingsNodeViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/ScaleSettingsNodeViewModel.cs new file mode 100644 index 0000000..1d5dd30 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/ScaleSettingsNodeViewModel.cs @@ -0,0 +1,26 @@ +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Messages; +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; + +namespace CosmosDbExplorer.ViewModels.DatabaseNodes +{ + public class ScaleSettingsNodeViewModel : TreeViewItemViewModel, IHaveContainerNodeViewModel, IContent, IHaveOpenCommand + { + private RelayCommand? _openCommand; + + public ScaleSettingsNodeViewModel(ContainerNodeViewModel parent) + : base(parent, false) + { + Name = "Settings"; + } + + public string Name { get; private set; } + + public string ContentId => Parent.Container.SelfLink + "/Settings"; + + public RelayCommand OpenCommand => _openCommand ??= new(() => Messenger.Send(new OpenScaleAndSettingsViewMessage(this, Parent.Parent.Parent.Connection, Parent.Parent.Database, Parent.Container))); + + public ContainerNodeViewModel ContainerNode => Parent; + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseNodes/StoredProcedureNodeViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/StoredProcedureNodeViewModel.cs new file mode 100644 index 0000000..f0bb188 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/StoredProcedureNodeViewModel.cs @@ -0,0 +1,94 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.Extensions; +using CosmosDbExplorer.Messages; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Messaging; + +namespace CosmosDbExplorer.ViewModels.DatabaseNodes +{ + public class StoredProcedureRootNodeViewModel : AssetRootNodeViewModelBase + { + private readonly IServiceProvider _serviceProvider; + + public StoredProcedureRootNodeViewModel(ContainerNodeViewModel parent, IServiceProvider serviceProvider) + : base(parent) + { + Name = "Stored Procedures"; + _serviceProvider = serviceProvider; + } + + protected override async Task LoadChildren(CancellationToken token) + { + IsLoading = true; + + var service = ActivatorUtilities.CreateInstance(_serviceProvider, Parent.Parent.Parent.Connection, Parent.Parent.Database, Parent.Container); + + var function = await service.GetStoredProceduresAsync(token); + + foreach (var func in function) + { + //await DispatcherHelper.RunAsync(() => Children.Add(new UserDefFuncNodeViewModel(this, func))); + Children.Add(new StoredProcedureNodeViewModel(this, func)); + } + + IsLoading = false; + } + + protected override void OpenNewCommandExecute() + { + Parent.NewStoredProcedureCommand.Execute(this); + } + + protected override void OnUpdateOrCreateNodeMessage(UpdateOrCreateNodeMessage message) + { + if (message.IsNewResource) + { + var item = new StoredProcedureNodeViewModel(this, message.Resource); + Children.AddSorted(item, i => ((StoredProcedureNodeViewModel)i).Name); + } + else + { + var item = Children.Cast().FirstOrDefault(i => i.Resource.SelfLink == message.OldAltLink); + + if (item != null) + { + item.Resource = message.Resource; + } + } + } + } + + public class StoredProcedureNodeViewModel : AssetNodeViewModelBase + { + public StoredProcedureNodeViewModel(StoredProcedureRootNodeViewModel parent, CosmosStoredProcedure resource) + : base(parent, resource) + { + } + + protected override Task DeleteCommandImpl() + { + throw new System.NotImplementedException(); + //return DialogService.ShowMessage("Are sure you want to delete this Stored Procedure?", "Delete", null, null, + // async confirm => + // { + // if (confirm) + // { + // await DbService.DeleteStoredProcedureAsync(Parent.Parent.Parent.Parent.Connection, Resource.AltLink).ConfigureAwait(false); + // await DispatcherHelper.RunAsync(() => Parent.Children.Remove(this)); + // MessengerInstance.Send(new CloseDocumentMessage(ContentId)); + // } + // }); + } + + protected override Task OpenCommandImp() + { + Messenger.Send(new EditStoredProcedureMessage(this, Parent.Parent.Parent.Parent.Connection, Parent.Parent.Parent.Database, Parent.Parent.Container)); + return Task.CompletedTask; + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseNodes/TriggerNodeViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/TriggerNodeViewModel.cs new file mode 100644 index 0000000..41ac776 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/TriggerNodeViewModel.cs @@ -0,0 +1,95 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.Extensions; +using CosmosDbExplorer.Messages; +using Microsoft.Azure.Documents; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Messaging; + +namespace CosmosDbExplorer.ViewModels.DatabaseNodes +{ + public class TriggerRootNodeViewModel : AssetRootNodeViewModelBase + { + private readonly IServiceProvider _serviceProvider; + + public TriggerRootNodeViewModel(ContainerNodeViewModel parent, IServiceProvider serviceProvider) + : base(parent) + { + Name = "Triggers"; + _serviceProvider = serviceProvider; + } + + protected override async Task LoadChildren(CancellationToken token) + { + IsLoading = true; + + var service = ActivatorUtilities.CreateInstance(_serviceProvider, Parent.Parent.Parent.Connection, Parent.Parent.Database, Parent.Container); + + var function = await service.GetTriggersAsync(token); + + foreach (var func in function) + { + //await DispatcherHelper.RunAsync(() => Children.Add(new UserDefFuncNodeViewModel(this, func))); + Children.Add(new TriggerNodeViewModel(this, func)); + } + + IsLoading = false; + } + + protected override void OpenNewCommandExecute() + { + Parent.NewTriggerCommand.Execute(this); + } + + protected override void OnUpdateOrCreateNodeMessage(UpdateOrCreateNodeMessage message) + { + if (message.IsNewResource) + { + var item = new TriggerNodeViewModel(this, message.Resource); + Children.AddSorted(item, i => ((TriggerNodeViewModel)i).Name); + } + else + { + var item = Children.Cast().FirstOrDefault(i => i.Resource.SelfLink == message.OldAltLink); + + if (item != null) + { + item.Resource = message.Resource; + } + } + } + } + + public class TriggerNodeViewModel : AssetNodeViewModelBase + { + public TriggerNodeViewModel(TriggerRootNodeViewModel parent, CosmosTrigger resource) + : base(parent, resource) + { + } + + protected override Task DeleteCommandImpl() + { + throw new System.NotImplementedException(); + //return DialogService.ShowMessage("Are sure you want to delete this Trigger?", "Delete", null, null, + // async confirm => + // { + // if (confirm) + // { + // await DbService.DeleteTriggerAsync(Parent.Parent.Parent.Parent.Connection, Resource.AltLink).ConfigureAwait(false); + // await DispatcherHelper.RunAsync(() => Parent.Children.Remove(this)); + // MessengerInstance.Send(new CloseDocumentMessage(ContentId)); + // } + // }); + } + + protected override Task OpenCommandImp() + { + Messenger.Send(new EditTriggerMessage(this, Parent.Parent.Parent.Parent.Connection, Parent.Parent.Parent.Database, Parent.Parent.Container)); + return Task.CompletedTask; + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseNodes/UserDefFuncNodeViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/UserDefFuncNodeViewModel.cs new file mode 100644 index 0000000..dcb5170 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/UserDefFuncNodeViewModel.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.Extensions; +using CosmosDbExplorer.Messages; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Messaging; + +namespace CosmosDbExplorer.ViewModels.DatabaseNodes +{ + public class UserDefFuncRootNodeViewModel : AssetRootNodeViewModelBase + { + private readonly IServiceProvider _serviceProvider; + + public UserDefFuncRootNodeViewModel(ContainerNodeViewModel parent, IServiceProvider serviceProvider) + : base(parent) + { + Name = "User Defined Functions"; + _serviceProvider = serviceProvider; + } + + protected override async Task LoadChildren(CancellationToken token) + { + IsLoading = true; + + var service = ActivatorUtilities.CreateInstance(_serviceProvider, Parent.Parent.Parent.Connection, Parent.Parent.Database, Parent.Container); + + var function = await service.GetUserDefinedFunctionsAsync(token); + + foreach (var func in function) + { + Children.Add(new UserDefFuncNodeViewModel(this, func)); + } + + IsLoading = false; + } + + protected override void OpenNewCommandExecute() + { + Parent.NewUdfCommand.Execute(this); + } + + protected override void OnUpdateOrCreateNodeMessage(UpdateOrCreateNodeMessage message) + { + if (message.IsNewResource) + { + var item = new UserDefFuncNodeViewModel(this, message.Resource); + Children.AddSorted(item, i => ((UserDefFuncNodeViewModel)i).Name); + } + else + { + var item = Children.Cast().FirstOrDefault(i => i.Resource.SelfLink == message.OldAltLink); + + if (item != null) + { + item.Resource = message.Resource; + } + } + } + } + + public class UserDefFuncNodeViewModel : AssetNodeViewModelBase + { + public UserDefFuncNodeViewModel(UserDefFuncRootNodeViewModel parent, CosmosUserDefinedFunction resource) + : base(parent, resource) + { + } + + protected override Task DeleteCommandImpl() + { + throw new System.NotImplementedException(); + //return DialogService.ShowMessage("Are sure you want to delete this User Defined Function?", "Delete", null, null, + // async confirm => + // { + // if (confirm) + // { + // await DbService.DeleteUdfAsync(Parent.Parent.Parent.Parent.Connection, Resource.AltLink).ConfigureAwait(false); + // await DispatcherHelper.RunAsync(() => Parent.Children.Remove(this)); + // MessengerInstance.Send(new CloseDocumentMessage(ContentId)); + // } + // }); + } + + protected override Task OpenCommandImp() + { + Messenger.Send(new EditUserDefFuncMessage(this, Parent.Parent.Parent.Parent.Connection, Parent.Parent.Parent.Database, Parent.Parent.Container)); + return Task.CompletedTask; + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseNodes/UserNodeViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/UserNodeViewModel.cs new file mode 100644 index 0000000..652f0b1 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/UserNodeViewModel.cs @@ -0,0 +1,66 @@ +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; + +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.Messages; + +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; + +namespace CosmosDbExplorer.ViewModels.DatabaseNodes +{ + public class UserNodeViewModel : TreeViewItemViewModel + , ICanRefreshNode + , IContent + , IHaveOpenCommand + { + private AsyncRelayCommand? _refreshCommand; + private RelayCommand? _openCommand; + private RelayCommand? _addPermissionCommand; + private readonly CosmosUserService _cosmosUserService; + + public UserNodeViewModel(CosmosUser user, UsersNodeViewModel parent, CosmosUserService cosmosUserService) + : base(parent, true) + { + User = user; + _cosmosUserService = cosmosUserService; + } + + public string Name => User.Id; + + public string ContentId => User.SelfLink ?? "NewUser"; + + protected override async Task LoadChildren(CancellationToken token) + { + IsLoading = true; + + var permissions = await _cosmosUserService.GetPermissionsAsync(User, token); + + foreach (var permission in permissions) + { + Children.Add(new PermissionNodeViewModel(permission, this)); + } + + IsLoading = false; + } + + public ICommand RefreshCommand => _refreshCommand ??= new(RefreshCommandExecute); + + private Task RefreshCommandExecute() + { + Children.Clear(); + return LoadChildren(new CancellationToken()); + } + + + public RelayCommand OpenCommand => _openCommand ??= new(() => Messenger.Send(new EditUserMessage(this, Parent.Parent.Parent.Connection, Parent.Database))); + + + public RelayCommand AddPermissionCommand => _addPermissionCommand ??= new(() => Messenger.Send(new EditPermissionMessage(new PermissionNodeViewModel(new CosmosPermission(), this), Parent.Parent.Parent.Connection, Parent.Database))); + + public CosmosUser User { get; set; } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseNodes/UsersNodeViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/UsersNodeViewModel.cs new file mode 100644 index 0000000..26eb738 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseNodes/UsersNodeViewModel.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; + +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.Messages; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; + +namespace CosmosDbExplorer.ViewModels.DatabaseNodes +{ + public class UsersNodeViewModel : TreeViewItemViewModel, ICanRefreshNode + { + private AsyncRelayCommand? _refreshCommand; + private RelayCommand? _addUserCommand; + + public UsersNodeViewModel(IServiceProvider serviceProvider, CosmosDatabase database, DatabaseNodeViewModel parent) + : base(parent, true) + { + Name = "Users"; + Database = database; + + _userService = ActivatorUtilities.CreateInstance(serviceProvider, Parent.Parent.Connection, database); + } + + public string Name { get; set; } + + public CosmosDatabase Database { get; } + + private readonly CosmosUserService _userService; + + public ICommand RefreshCommand => _refreshCommand ??= new(RefreshCommandExecute); + + private Task RefreshCommandExecute() + { + Children.Clear(); + return LoadChildren(new CancellationToken()); + } + + public RelayCommand AddUserCommand => _addUserCommand ??= new(() => Messenger.Send(new EditUserMessage(new UserNodeViewModel(new CosmosUser(), this, _userService), Parent.Parent.Connection, Parent.Database))); + + protected override async Task LoadChildren(CancellationToken token) + { + IsLoading = true; + + var users = await _userService.GetUsersAsync(token); + + foreach (var user in users) + { + Children.Add(new UserNodeViewModel(user, this, _userService)); + } + + IsLoading = false; + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabasePropertyViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabasePropertyViewModel.cs new file mode 100644 index 0000000..336b384 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabasePropertyViewModel.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using FluentValidation; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.ComponentModel; +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; +using PropertyChanged; +using Validar; + +namespace CosmosDbExplorer.ViewModels +{ + [InjectValidation] + public class DatabasePropertyViewModel : UIViewModelBase, INavigationAware + { + private readonly IServiceProvider _serviceProvider; + private readonly IDialogService _dialogService; + private CosmosDatabaseService _databaseService; + private AsyncRelayCommand _saveCommand; + + public DatabasePropertyViewModel(IServiceProvider serviceProvider, IDialogService dialogService, IUIServices uiServices) + : base(uiServices) + { + //IsFixedStorage = true; + Throughput = 400; + Title = "Add Container"; + _serviceProvider = serviceProvider; + _dialogService = dialogService; + } + + public Action? SetResult { get; set; } + + public string Title { get; } + + public CosmosConnection Connection { get; private set; } + + [OnChangedMethod(nameof(UpdateSaveCommandStatus))] + public string DatabaseId { get; set; } + + [OnChangedMethod(nameof(UpdateSaveCommandStatus))] + public bool ProvisionThroughput { get; set; } = true; + + public bool IsThroughputAutoscale { get; set; } = true; + + public int MaxThroughput => IsThroughputAutoscale ? 10000 : 100000; + + public int MinThroughput => IsThroughputAutoscale ? 400 : 1000; + + [OnChangedMethod(nameof(UpdateSaveCommandStatus))] + public int Throughput { get; set; } + + protected void UpdateSaveCommandStatus() => SaveCommand.NotifyCanExecuteChanged(); + + public AsyncRelayCommand SaveCommand => _saveCommand ??= new(SaveCommandExecute, SaveCommandCanExecute); + + private async Task SaveCommandExecute() + { + try + { + var database = new CosmosDatabase(DatabaseId); + var throughput = ProvisionThroughput ? Throughput : (int?)null; + var isAutoScale = ProvisionThroughput ? IsThroughputAutoscale : (bool?)null; + + var createdDatabase = await _databaseService.CreateDatabaseAsync(database, throughput, isAutoScale, new System.Threading.CancellationToken()); + + Messenger.Send(new Messages.UpdateOrCreateNodeMessage(createdDatabase, Connection, null)); + OnClose(); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "Error during Database creation"); + } + } + + private bool SaveCommandCanExecute() => string.IsNullOrEmpty(((IDataErrorInfo)this).Error); + + public void OnNavigatedFrom() + { + + } + + public void OnNavigatedTo(object parameter) + { + Connection = (CosmosConnection)parameter; + + _databaseService = ActivatorUtilities.CreateInstance(_serviceProvider, Connection); + } + + private void OnClose() + { + SetResult?.Invoke(true); + } + } + + public class DatabasePropertyViewModelValidator : AbstractValidator + { + public DatabasePropertyViewModelValidator() + { + RuleFor(x => x.DatabaseId).NotEmpty(); + RuleFor(x => x.Throughput).NotEmpty() + .When(x => x.ProvisionThroughput); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/DatabaseScaleViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabaseScaleViewModel.cs new file mode 100644 index 0000000..04e5814 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DatabaseScaleViewModel.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Core.Contracts.Services; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.ViewModels.DatabaseNodes; +using FluentValidation; +using Microsoft.Azure.Cosmos; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Input; +using PropertyChanged; +using Validar; + +namespace CosmosDbExplorer.ViewModels +{ + [InjectValidation] + public class DatabaseScaleViewModel : PaneWithZoomViewModel + { + private readonly IServiceProvider _serviceProvider; + private readonly IDialogService _dialogService; + private readonly ISystemService _systemService; + private ICosmosDatabaseService? _cosmosDatabaseService; + private ICommand? _openUrlCommand; + private AsyncRelayCommand? _saveCommand; + private RelayCommand? _discardCommand; + private CosmosThroughput? _originalThroughput; + + public DatabaseScaleViewModel(IServiceProvider serviceProvider, IUIServices uiServices, IDialogService dialogService, ISystemService systemService) + : base(uiServices) + { + _serviceProvider = serviceProvider; + _dialogService = dialogService; + _systemService = systemService; + IconSource = App.Current.FindResource("ScaleSettingsIcon"); + } + + public DatabaseScaleNodeViewModel? Node { get; private set; } + public CosmosConnection? Connection { get; private set; } + public CosmosDatabase? Database { get; private set; } + + public bool IsThroughputAutoscale { get; set; } = true; + + public int MaxThroughput { get; set; } + + public int? MinThroughput { get; set; } + + [OnChangedMethod(nameof(UpdateCommandStatus))] + public int? Throughput { get; set; } + + public int Increment => IsThroughputAutoscale ? 1000 : 100; + + [DependsOn(nameof(IsThroughputAutoscale), nameof(Throughput))] + public string Information => $"{Throughput * 0.1} RU/s (10 % of max RU/s) - {Throughput} RU/s"; + + [DependsOn(nameof(IsThroughputAutoscale), nameof(Throughput))] + public string DataStoredInGb => $"{Throughput * 0.01}"; + + public ICommand OpenUrlCommand => _openUrlCommand ??= new RelayCommand(OpenUrl); + + public AsyncRelayCommand SaveCommand => _saveCommand ??= new(SaveCommandExecute, () => HasThroughputChanged); + + public RelayCommand DiscardCommand => _discardCommand ??= new(DiscardCommandExecute, () => HasThroughputChanged); + private bool HasThroughputChanged => (_originalThroughput?.AutoscaleMaxThroughput ?? _originalThroughput?.Throughput) != Throughput; + + public override async void Load(string contentId, DatabaseScaleNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer? container) + { + if (node is null) + { + throw new ArgumentNullException(nameof(node)); + } + + if (connection is null) + { + throw new ArgumentNullException(nameof(connection)); + } + + if (database is null) + { + throw new ArgumentNullException(nameof(database)); + } + + ContentId = contentId; + Node = node; + Title = node.Name; + Header = node.Name; + Connection = connection; + Database = database; + + AccentColor = connection.AccentColor; + ToolTip = $"{Connection.Label}/{Database.Id}"; + + _cosmosDatabaseService = ActivatorUtilities.CreateInstance(_serviceProvider, connection); + var throughput = await _cosmosDatabaseService.GetThroughputAsync(Database); + + SetThroughputInfo(throughput); + } + + private void SetThroughputInfo(CosmosThroughput? throughput) + { + if (throughput is null) + { + throw new ArgumentNullException(nameof(throughput)); + } + + _originalThroughput = throughput; + + MinThroughput = _originalThroughput.MinThroughtput; + MaxThroughput = int.MaxValue - (int.MaxValue % 1000); + IsThroughputAutoscale = _originalThroughput.AutoscaleMaxThroughput.HasValue; + Throughput = _originalThroughput.AutoscaleMaxThroughput ?? _originalThroughput.Throughput; + } + + private void OpenUrl(string? url) + { + if (!string.IsNullOrEmpty(url)) + { + _systemService.OpenInWebBrowser(url); + } + } + + private async Task SaveCommandExecute() + { + if (_cosmosDatabaseService is null) + { + throw new NullReferenceException("Database service should not be null!"); + } + + if (Database is null) + { + throw new NullReferenceException("Database propery should not be null!"); + } + + try + { + IsBusy = true; + + if (HasThroughputChanged && Throughput is not null) + { + var throughput = await _cosmosDatabaseService.UpdateThroughputAsync(Database, Throughput.Value, IsThroughputAutoscale); + SetThroughputInfo(throughput); + } + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "An unexpected error occured!"); + } + finally + { + IsBusy = false; + } + } + + private void DiscardCommandExecute() + { + SetThroughputInfo(_originalThroughput); + } + + private void UpdateCommandStatus() + { + SaveCommand.NotifyCanExecuteChanged(); + DiscardCommand.NotifyCanExecuteChanged(); + } + } + + public class DatabaseScaleViewModelValidator : AbstractValidator + { + public DatabaseScaleViewModelValidator() + { + RuleFor(x => x.Throughput) + .NotEmpty() + .GreaterThanOrEqualTo(x => x.MinThroughput) + .LessThanOrEqualTo(x => x.MaxThroughput) + .Custom((throughput, context) => + { + if (throughput % context.InstanceToValidate.Increment != 0) + { + context.AddFailure($"Value must be a multiple of {context.InstanceToValidate.Increment}."); + } + }); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModel/DatabaseViewModel.cs b/src/CosmosDbExplorer/ViewModels/DatabaseViewModel.cs similarity index 62% rename from src/CosmosDbExplorer/ViewModel/DatabaseViewModel.cs rename to src/CosmosDbExplorer/ViewModels/DatabaseViewModel.cs index 1cf2d25..f6c9346 100644 --- a/src/CosmosDbExplorer/ViewModel/DatabaseViewModel.cs +++ b/src/CosmosDbExplorer/ViewModels/DatabaseViewModel.cs @@ -1,50 +1,50 @@ -using System.Collections.ObjectModel; +using System; +using System.Collections.ObjectModel; using System.Linq; -using System.Threading.Tasks; -using CosmosDbExplorer.Infrastructure.Models; +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Core.Contracts.Services; using CosmosDbExplorer.Messages; -using CosmosDbExplorer.Services; -using GalaSoft.MvvmLight.Ioc; -using GalaSoft.MvvmLight.Messaging; -using GalaSoft.MvvmLight.Threading; +using CosmosDbExplorer.ViewModels; +using CosmosDbExplorer.ViewModels.DatabaseNodes; using GongSolutions.Wpf.DragDrop; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Messaging; -namespace CosmosDbExplorer.ViewModel +namespace CosmosDbExplorer.ViewModels { public class DatabaseViewModel : ToolViewModel, IDropTarget { - private readonly ISettingsService _settingsService; + private readonly IServiceProvider _serviceProvider; + private readonly ICosmosClientService _cosmosClientService; + private readonly IPersistAndRestoreService _persistAndRestoreService; - public DatabaseViewModel(IMessenger messenger, IDocumentDbService dbService, ISettingsService settingsService, IUIServices uiServices) - : base(messenger, uiServices) + public DatabaseViewModel(IServiceProvider serviceProvider, IUIServices uiServices, ICosmosClientService cosmosClientService, IPersistAndRestoreService persistAndRestoreService) + : base(uiServices) { Header = "Connections"; Title = Header; + IconSource = App.Current.FindResource("ConnectionIcon"); IsVisible = true; - - _settingsService = settingsService; - + _serviceProvider = serviceProvider; + _cosmosClientService = cosmosClientService; + _persistAndRestoreService = persistAndRestoreService; + LoadNodes(); RegisterMessages(); } + private void RegisterMessages() { - MessengerInstance.Register(this, OnConnectionSettingsSaved); - MessengerInstance.Register(this, OnRemoveConnection); + Messenger.Register(this, static (r, msg) => r.OnConnectionSettingsSaved(msg)); + Messenger.Register(this, static (r, msg) => r.OnRemoveConnection(msg)); } public ObservableCollection Nodes { get; private set; } - public async Task LoadNodesAsync() + private void LoadNodes() { - var connections = await _settingsService.GetConnectionsAsync().ConfigureAwait(false); - var nodes = connections.Select(c => - { - var connection = SimpleIoc.Default.GetInstanceWithoutCaching(); - connection.Connection = c.Value; - - return connection; - }); + var connections = _persistAndRestoreService.GetConnections(); + var nodes = connections.Select(c => new ConnectionNodeViewModel(_serviceProvider, c)); Nodes = new ObservableCollection(nodes); } @@ -55,7 +55,7 @@ private void OnRemoveConnection(RemoveConnectionMessage msg) if (node != null) { - DispatcherHelper.RunAsync(() => Nodes.Remove(node)); + Nodes.Remove(node); } } @@ -69,8 +69,7 @@ private void OnConnectionSettingsSaved(ConnectionSettingSavedMessage msg) } else { - var connection = SimpleIoc.Default.GetInstanceWithoutCaching(); - connection.Connection = msg.Connection; + var connection = new ConnectionNodeViewModel(_serviceProvider, msg.Connection); Nodes.Add(connection); } } @@ -84,7 +83,7 @@ public void DragOver(IDropInfo dropInfo) } } - public async void Drop(IDropInfo dropInfo) + public void Drop(IDropInfo dropInfo) { var sourceItem = dropInfo.Data as ConnectionNodeViewModel; var targetItem = dropInfo.TargetItem as ConnectionNodeViewModel; @@ -126,7 +125,7 @@ public async void Drop(IDropInfo dropInfo) } Nodes.Move(sourceIndex, targetIndex); - await _settingsService.ReorderConnections(sourceIndex, targetIndex).ConfigureAwait(false); + _persistAndRestoreService.ReorderConnections(sourceIndex, targetIndex); } } } diff --git a/src/CosmosDbExplorer/ViewModels/DocumentsTabViewModel.cs b/src/CosmosDbExplorer/ViewModels/DocumentsTabViewModel.cs new file mode 100644 index 0000000..b831f15 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/DocumentsTabViewModel.cs @@ -0,0 +1,568 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Contracts; +using CosmosDbExplorer.Core.Contracts.Services; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.Extensions; +using CosmosDbExplorer.Models; +using CosmosDbExplorer.Properties; +using CosmosDbExplorer.Services.DialogSettings; +using CosmosDbExplorer.ViewModels.DatabaseNodes; + +using FluentValidation; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Input; + +using Newtonsoft.Json.Linq; + +using Validar; + +namespace CosmosDbExplorer.ViewModels +{ + [InjectValidation] + public class DocumentsTabViewModel : PaneWithZoomViewModel + , IHaveRequestOptions + , IHaveSystemProperties + { + private readonly StatusBarItem _requestChargeStatusBarItem; + private readonly StatusBarItem _progessBarStatusBarItem; + private readonly IServiceProvider _serviceProvider; + private readonly IDialogService _dialogService; + private JObject _currentDocument; + private ICosmosDocument _currentCosmosDocument; + + private ICosmosDocumentService _cosmosDocumentService; + private AsyncRelayCommand _loadMoreCommand; + private AsyncRelayCommand _refreshLoadCommand; + private RelayCommand _newDocumentCommand; + private RelayCommand _resetRequestOptionsCommand; + private RelayCommand _saveLocalCommand; + private RelayCommand _closeFilterCommand; + private AsyncRelayCommand _applyFilterCommand; + private RelayCommand _editFilterCommand; + private AsyncRelayCommand _deleteDocumentCommand; + private AsyncRelayCommand _saveDocumentCommand; + private RelayCommand _discardCommand; + private IDocumentRequestOptions _documentRequestOptions = new DocumentRequestOptions(); + + public DocumentsTabViewModel(IServiceProvider serviceProvider, IUIServices uiServices, IDialogService dialogService) + : base(uiServices) + { + _serviceProvider = serviceProvider; + _dialogService = dialogService; + Title = "Documents"; + Header = Title; + IconSource = App.Current.FindResource("DocumentIcon"); + + EditorViewModel = new DocumentEditorViewModel(); + EditorViewModel.PropertyChanged += (s, e) => NotifyCanExecuteChanged(); + + HeaderViewModel = new HeaderEditorViewModel { IsReadOnly = true }; + + _requestChargeStatusBarItem = new StatusBarItem(new StatusBarItemContext { Value = RequestCharge }, StatusBarItemType.SimpleText, "Request Charge", System.Windows.Controls.Dock.Left); + StatusBarItems.Add(_requestChargeStatusBarItem); + _progessBarStatusBarItem = new StatusBarItem(new StatusBarItemContextCancellableCommand { Value = IsRunning, IsVisible = IsRunning, IsCancellable = false }, StatusBarItemType.ProgessBar, "Progress", System.Windows.Controls.Dock.Left); + StatusBarItems.Add(_progessBarStatusBarItem); + } + + public override async void Load(string contentId, DocumentNodeViewModel node, CosmosConnection connection, CosmosDatabase database, CosmosContainer container) + { + ContentId = contentId; + Node = node; + Connection = connection; + Container = container; + PartitionKey = container.PartitionKeyPath; + + //var split = Node.Parent.Container.SelfLink.Split(new char[] { '/' }); + ToolTip = $"{Connection.Label}/{database.Id}/{Container.Id}"; + + AccentColor = Connection.AccentColor; + + _cosmosDocumentService = ActivatorUtilities.CreateInstance(_serviceProvider, connection, database, container); + + await LoadDocuments(true, new CancellationToken()).ConfigureAwait(false); + } + + public DocumentNodeViewModel Node { get; protected set; } + + public string PartitionKey { get; set; } + + public ObservableCollection> Documents { get; } = new(); + + public ICosmosDocument? SelectedDocument { get; set; } + + public async void OnSelectedDocumentChanged() + { + if (SelectedDocument != null && SelectedDocument != _currentCosmosDocument) + { + IsRunning = true; + + try + { + var response = await _cosmosDocumentService.GetDocumentAsync(SelectedDocument, _documentRequestOptions, new CancellationToken()); + _currentDocument = response.Items; + _currentCosmosDocument = CosmosDocument.CreateFrom(response.Items, Container.PartitionKeyJsonPath); + SetStatusBar(new StatusBarInfo(response)); + + EditorViewModel.SetText(_currentDocument, HideSystemProperties); + HeaderViewModel.SetText(response?.Headers, HideSystemProperties); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "Error"); + } + finally + { + IsRunning = false; + } + } + else + { + SetStatusBar(null); + } + + NotifyCanExecuteChanged(); + } + + private void NotifyCanExecuteChanged() + { + NewDocumentCommand.NotifyCanExecuteChanged(); + LoadMoreCommand.NotifyCanExecuteChanged(); + RefreshLoadCommand.NotifyCanExecuteChanged(); + DiscardCommand.NotifyCanExecuteChanged(); + SaveDocumentCommand.NotifyCanExecuteChanged(); + DeleteDocumentCommand.NotifyCanExecuteChanged(); + EditFilterCommand.NotifyCanExecuteChanged(); + ApplyFilterCommand.NotifyCanExecuteChanged(); + SaveLocalCommand.NotifyCanExecuteChanged(); + } + + public string Filter { get; set; } + + public bool IsEditingFilter { get; set; } + + public bool HasMore { get; set; } + public string? ContinuationToken { get; set; } + + public string? RequestCharge { get; set; } + + public void OnRequestChargeChanged() + { + _requestChargeStatusBarItem.DataContext.Value = RequestCharge; + } + + public bool IsRunning { get; set; } + + public void OnIsRunningChanged() + { + _progessBarStatusBarItem.DataContext.IsVisible = IsRunning; + _requestChargeStatusBarItem.DataContext.IsVisible = !IsRunning; + } + + public CosmosIndexingDirectives? IndexingDirective + { + get { return _documentRequestOptions.IndexingDirective; } + set + { + _documentRequestOptions.IndexingDirective = value; + OnPropertyChanged(); + } + } + public CosmosConsistencyLevels? ConsistencyLevel + { + get { return _documentRequestOptions.ConsistencyLevel; } + set + { + _documentRequestOptions.ConsistencyLevel = value; + OnPropertyChanged(); + } + } + + public CosmosAccessConditionType AccessConditionType + { + get { return _documentRequestOptions.AccessCondition; } + set + { + _documentRequestOptions.AccessCondition = value; + OnPropertyChanged(); + } + } + + public string? AccessCondition + { + get { return _documentRequestOptions.ETag; } + set + { + _documentRequestOptions.ETag = string.IsNullOrWhiteSpace(value) ? null : value.Trim(); + OnPropertyChanged(); + } + } + + public string? PreTrigger + { + get { return string.Join("; ", _documentRequestOptions.PreTriggers); } + set + { + if (string.IsNullOrWhiteSpace(value)) + { + _documentRequestOptions.PreTriggers = Array.Empty(); + } + else + { + _documentRequestOptions.PreTriggers = value.Split(';', StringSplitOptions.TrimEntries); + } + } + } + + public string? PostTrigger + { + get { return string.Join("; ", _documentRequestOptions.PostTriggers); } + set + { + if (string.IsNullOrWhiteSpace(value)) + { + _documentRequestOptions.PostTriggers = Array.Empty(); + } + else + { + _documentRequestOptions.PostTriggers = value.Split(';', StringSplitOptions.TrimEntries); + } + } + } + + public bool IsValid => string.IsNullOrEmpty(((IDataErrorInfo)this).Error); + + public bool HideSystemProperties { get; set; } + + protected void OnHideSystemPropertiesChanged() + { + EditorViewModel.SetText(_currentDocument, HideSystemProperties); + NotifyCanExecuteChanged(); + } + + private void SetStatusBar(IStatusBarInfo? response) + { + RequestCharge = response == null + ? null + : $"Request Charge: {response.RequestCharge:N2}"; + } + + private async Task LoadDocuments(bool cleanContent, CancellationToken cancellationToken) + { + try + { + IsRunning = true; + + if (cleanContent) + { + Documents.Clear(); + ContinuationToken = null; + } + + var result = await _cosmosDocumentService.GetDocumentsAsync( + Filter, + Settings.Default.MaxDocumentToRetrieve, + ContinuationToken, cancellationToken); + + HasMore = result.HasMore; + ContinuationToken = result.ContinuationToken; + RequestCharge = $"Request Charge: {result.RequestCharge:N2}"; + + foreach (var document in result.Items) + { + Documents.Add(new CheckedItem(document)); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex.Message); + } + finally + { + IsRunning = false; + NotifyCanExecuteChanged(); + } + } + + public long TotalItemsCount { get; set; } + public long ItemsCount => Documents.Count; + + public DocumentEditorViewModel EditorViewModel { get; } + + public HeaderEditorViewModel HeaderViewModel { get; } + + protected CosmosConnection Connection { get; set; } + + protected CosmosContainer Container { get; set; } + + public AsyncRelayCommand LoadMoreCommand => _loadMoreCommand ??= new(async () => await LoadDocuments(false, new CancellationToken()).ConfigureAwait(false)); + + public AsyncRelayCommand RefreshLoadCommand => _refreshLoadCommand ??= new(async () => await LoadDocuments(true, new CancellationToken()).ConfigureAwait(false), + () => !IsRunning && IsValid); + + + public RelayCommand NewDocumentCommand => _newDocumentCommand ??= new(NewDocumentExecute, NewDocumentCommandCanExecute); + + private void NewDocumentExecute() + { + SelectedDocument = null; + SetStatusBar(null); + + EditorViewModel.SetText(JObject.Parse("{\"id\": \"replace_with_the_new_document_id\"}"), HideSystemProperties); + } + + private bool NewDocumentCommandCanExecute() + { + // Can create new document if current document is not a new document + return !IsRunning && !EditorViewModel.IsNewDocument && !EditorViewModel.IsDirty; + } + + public RelayCommand DiscardCommand => _discardCommand ??= new(() => OnSelectedDocumentChanged(), () => !IsRunning && EditorViewModel.IsDirty); + + public AsyncRelayCommand SaveDocumentCommand => _saveDocumentCommand ??= new(SaveDocumentCommandExecute, () => !IsRunning && EditorViewModel.IsDirty && IsValid); + + private async Task SaveDocumentCommandExecute() + { + if (EditorViewModel?.Text is null) + { + return; + } + + IsRunning = true; + try + { + var response = await _cosmosDocumentService.SaveDocumentAsync(EditorViewModel.Text, _documentRequestOptions, new CancellationToken()); + + SetStatusBar(new StatusBarInfo(response)); + + _currentCosmosDocument = CosmosDocument.CreateFrom(response.Items, Container?.PartitionKeyJsonPath); + + if (SelectedDocument == null) + { + Documents.Add(new CheckedItem(_currentCosmosDocument)); + } + else + { + Documents.Select(d => d.Item).Replace(SelectedDocument, _currentCosmosDocument); + } + + SelectedDocument = _currentCosmosDocument; + + EditorViewModel.SetText(response.Items, HideSystemProperties); + HeaderViewModel.SetText(response.Headers, HideSystemProperties); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex.Message, "Error saving document"); + } + finally + { + IsRunning = false; + } + } + + public AsyncRelayCommand DeleteDocumentCommand => _deleteDocumentCommand ??= new(DeleteDocumentCommandExecute, () => !IsRunning && SelectedDocument != null && !EditorViewModel.IsNewDocument && IsValid); + + private async Task DeleteDocumentCommandExecute() + { + var selectedDocuments = Documents.Where(doc => doc.IsChecked).Select(doc => doc.Item).ToList(); + var message = selectedDocuments.Count == 1 + ? $"Are you sure that you want to delete document '{selectedDocuments[0].Id}'?" + : $"Are you sure that you want to delete these {selectedDocuments.Count} documents?"; + + async void deleteDocument(bool confirm) + { + if (!confirm) + { + return; + } + + IsRunning = true; + + try + { + var response = await _cosmosDocumentService.DeleteDocumentsAsync(selectedDocuments, new CancellationToken()); + SetStatusBar(new StatusBarInfo(response)); + + var toRemove = Documents.Where(doc => doc.IsChecked).ToList(); + SelectedDocument = null; + + foreach (var item in toRemove) + { + Documents.Remove(item); + } + + EditorViewModel.Clear(); + HeaderViewModel.Clear(); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "Unable to delete document"); + } + finally + { + IsRunning = false; + } + } + + await _dialogService.ShowQuestion(message, "Delete Document(s)", deleteDocument); + } + + public RelayCommand EditFilterCommand => _editFilterCommand ??= new(() => IsEditingFilter = true); + + public AsyncRelayCommand ApplyFilterCommand => _applyFilterCommand ??= new(async () => { IsEditingFilter = false; await LoadDocuments(true, new CancellationToken()).ConfigureAwait(false); }); + + public RelayCommand CloseFilterCommand => _closeFilterCommand ??= new(() => IsEditingFilter = false); + + public RelayCommand SaveLocalCommand => _saveLocalCommand ??= new(SaveLocalCommandExecute, () => !IsRunning && SelectedDocument != null && IsValid); + + private void SaveLocalCommandExecute() + { + var selectedDocuments = Documents.Where(doc => doc.IsChecked).ToList(); + + if (selectedDocuments.Count == 1) + { + SaveLocalSingleDocument(); + } + else + { + SaveLocalMultipleDocuments(selectedDocuments.Select(doc => doc.Item).ToList()); + } + } + + private void SaveLocalMultipleDocuments(List selectedDocuments) + { + var settings = new FolderBrowserDialogSettings + { + ShowNewFolderButton = true, + Description = "Select output folder...", + SelectedPath = Settings.Default.GetExportFolder() + }; + + async void saveFile(bool confirm, FolderDialogResult result) + { + if (!confirm) + { + return; + } + try + { + IsRunning = true; + + // Save path for future use + Settings.Default.ExportFolder = result.Path; + Settings.Default.Save(); + + var tasks = new List>>(selectedDocuments.Count()); + + foreach (var document in selectedDocuments) + { + tasks.Add(_cosmosDocumentService.GetDocumentAsync(document, _documentRequestOptions, new CancellationToken()) + .ContinueWith>(requestResult => + { + if (requestResult.IsCompletedSuccessfully) + { + var path = document.HasPartitionKey + ? Path.Combine(result.Path, $"{document.PartitionKey}-{document.Id}.json".SafeForFilename()) + : Path.Combine(result.Path, $"{document.Id}.json".SafeForFilename()); + + File.WriteAllTextAsync(path, requestResult.Result.Items.ToString()); + } + + return requestResult.Result; + })); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "Error"); + } + finally + { + IsRunning = false; + } + } + + _dialogService.ShowFolderBrowserDialog(settings, saveFile); + } + + private void SaveLocalSingleDocument() + { + var settings = new SaveFileDialogSettings + { + DefaultExt = "json", + Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*", + AddExtension = true, + FileName = $"{SelectedDocument?.Id}.json", + OverwritePrompt = true, + CheckFileExists = false, + Title = "Save document locally" + }; + + async void saveFile(bool confirm, FileDialogResult result) + { + if (!confirm) + { + return; + } + + try + { + IsRunning = true; + + Settings.Default.ExportFolder = (new FileInfo(result.FileName)).DirectoryName; + Settings.Default.Save(); + + await File.WriteAllTextAsync(result.FileName, EditorViewModel.Text); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "Error"); + } + finally + { + IsRunning = false; + } + } + + _dialogService.ShowSaveFileDialog(settings, saveFile); + } + + public RelayCommand ResetRequestOptionsCommand => _resetRequestOptionsCommand ??= new(ResetRequestOptionCommandExecute); + + private void ResetRequestOptionCommandExecute() + { + IndexingDirective = null; + ConsistencyLevel = null; + //PartitionKeyValue = null; + AccessConditionType = CosmosAccessConditionType.None; + AccessCondition = null; + PreTrigger = null; + PostTrigger = null; + } + + + } + + public class DocumentsTabViewModelValidator : AbstractValidator + { + public DocumentsTabViewModelValidator() + { + //When(x => !string.IsNullOrEmpty(x.PartitionKeyValue?.Trim()), + // () => RuleFor(x => x.PartitionKeyValue).SetValidator(new PartitionKeyValidator())); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/ImportDocumentViewModel.cs b/src/CosmosDbExplorer/ViewModels/ImportDocumentViewModel.cs new file mode 100644 index 0000000..8817722 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/ImportDocumentViewModel.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.Models; +using CosmosDbExplorer.Services.DialogSettings; +using CosmosDbExplorer.ViewModels.DatabaseNodes; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Input; + +namespace CosmosDbExplorer.ViewModels +{ + public class ImportDocumentViewModel : PaneWithZoomViewModel//, IHaveRequestOptions + { + private AsyncRelayCommand _executeCommand; + private readonly IDialogService _dialogService; + private RelayCommand _openFileCommand; + private RelayCommand _resetRequestOptionsCommand; + private readonly StatusBarItem _progessBarStatusBarItem; + private readonly IServiceProvider _serviceProvider; + private CancellationTokenSource _cancellationToken; + private RelayCommand _cancelCommand; + private CosmosDocumentService _cosmosDocumentService; + + public ImportDocumentViewModel(IServiceProvider serviceProvider, IDialogService dialogService, IUIServices uiServices) + : base(uiServices) + { + _serviceProvider = serviceProvider; + _dialogService = dialogService; + + _progessBarStatusBarItem = new StatusBarItem(new StatusBarItemContextCancellableCommand { Value = CancelCommand, IsVisible = IsRunning, IsCancellable = false }, StatusBarItemType.ProgessBar, "Progress", System.Windows.Controls.Dock.Left); + StatusBarItems.Add(_progessBarStatusBarItem); + } + + public bool IsRunning { get; set; } + + public void OnIsRunningChanged() + { + _progessBarStatusBarItem.DataContext.IsVisible = IsRunning; + + if (IsRunning) + { + _cancellationToken = new CancellationTokenSource(); + } + else + { + _cancellationToken = null; + } + } + + public override void Load(string contentId, ContainerNodeViewModel node, CosmosConnection connection, CosmosDatabase database, CosmosContainer container) + { + ContentId = Guid.NewGuid().ToString(); + Node = node; + Header = "Import"; + Connection = connection; + Container = container; + + //var split = Container.SelfLink.Split(new char[] { '/' }); + ToolTip = $"{Connection.Label}/{database.Id}/{Container.Id}"; + AccentColor = Connection.AccentColor; + + _cosmosDocumentService = ActivatorUtilities.CreateInstance(_serviceProvider, connection, database, container); + } + + public ContainerNodeViewModel Node { get; protected set; } + + protected CosmosConnection Connection { get; set; } + + protected CosmosContainer Container { get; set; } + + public string? Content { get; set; } + + protected void OnContentChanged() + { + ExecuteCommand.NotifyCanExecuteChanged(); + } + + public bool IsDirty { get; set; } + + public AsyncRelayCommand ExecuteCommand => _executeCommand ??= new(ExecuteCommandAsync, () => !IsRunning && !string.IsNullOrEmpty(Content)); + + private async Task ExecuteCommandAsync() + { + if (string.IsNullOrEmpty(Content)) + { + return; + } + + try + { + IsRunning = true; + var count = await _cosmosDocumentService.ImportDocumentsAsync(Content, _cancellationToken.Token); + await _dialogService.ShowMessage($"{count} document(s) imported!", "Import"); + } + catch (OperationCanceledException) + { + await _dialogService.ShowMessage("The Import operation has been cancelled!", "Operation Cancelled"); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "Error durring document import!"); + } + finally + { + IsRunning = false; + } + } + + + public RelayCommand CancelCommand => _cancelCommand ??= new(() => _cancellationToken.Cancel(), () => IsRunning); + + public RelayCommand OpenFileCommand => _openFileCommand ??= new(OpenFileCommandExecuteAsync); + + private void OpenFileCommandExecuteAsync() + { + var settings = new OpenFileDialogSettings + { + DefaultExt = "json", + Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*", + AddExtension = true, + CheckFileExists = true, + Multiselect = false, + Title = "Open document" + }; + + async void OnDialogClose(bool confirm, FileDialogResult result) + { + if (!confirm) + { + return; + } + + try + { + using (var reader = File.OpenText(result.FileName)) + { + //Content.FileName = result.FileName; + //Content.Text = await reader.ReadToEndAsync().ConfigureAwait(true); + Content = await reader.ReadToEndAsync().ConfigureAwait(true); + } + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, $"Error during container {Container.Id} deletion"); + } + } + + _dialogService.ShowOpenFileDialog(settings, OnDialogClose); + } + + //public IndexingDirective? IndexingDirective { get; set; } + //public ConsistencyLevel? ConsistencyLevel { get; set; } + public string? PartitionKeyValue { get; set; } + //public AccessConditionType? AccessConditionType { get; set; } + public string? AccessCondition { get; set; } + public string? PreTrigger { get; set; } + public string? PostTrigger { get; set; } + + public RelayCommand ResetRequestOptionsCommand => _resetRequestOptionsCommand ??= new(ResetRequestOptionsCommandExecute); + + private void ResetRequestOptionsCommandExecute() + { + //IndexingDirective = null; + //ConsistencyLevel = null; + PartitionKeyValue = null; + //AccessConditionType = null; + AccessCondition = null; + PreTrigger = null; + PostTrigger = null; + } + } +} + diff --git a/src/CosmosDbExplorer/ViewModels/JsonEditorViewModel.cs b/src/CosmosDbExplorer/ViewModels/JsonEditorViewModel.cs new file mode 100644 index 0000000..ce0d184 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/JsonEditorViewModel.cs @@ -0,0 +1,154 @@ +using System; +using CosmosDbExplorer.Helpers; +using Microsoft.Toolkit.Mvvm.ComponentModel; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace CosmosDbExplorer.ViewModels +{ + public abstract class JsonEditorViewModelBase : ObservableRecipient + { + protected JsonEditorViewModelBase() + { + } + + public string? Text { get; set; } + + public bool IsDirty { get; set; } + + public bool IsReadOnly { get; set; } + + public virtual void SetText(object? content, bool removeSystemProperties) + { + var text = GetDocumentContent(content, removeSystemProperties) ?? string.Empty; + + Text = text; + OnPropertyChanged(nameof(HasContent)); + IsDirty = false; + } + + public void Clear() + { + Text = null; + OnPropertyChanged(nameof(HasContent)); + IsDirty = false; + } + + protected abstract string? GetDocumentContent(object? content, bool removeSystemProperties); + + public bool HasContent => Text?.Length != 0; //Content.TextLength != 0; + } + + public class JsonViewerViewModel : JsonEditorViewModelBase + { + private static readonly string[] SystemResourceNames = new[] { "_rid", "_etag", "_ts", "_self", "_id", "_attachments", "_docs", "_sprocs", "_triggers", "_udfs", "_conflicts", "_colls", "_users" }; + + public JsonViewerViewModel() + { + } + + protected override string? GetDocumentContent(object? content, bool removeSystemProperties) + { + if (content == null) + { + return null; + } + + var _document = new JArray(content); + + return removeSystemProperties + ? RemoveCosmosSystemProperties(_document) + : _document.ToString(Formatting.Indented); + } + + private static string RemoveCosmosSystemProperties(JArray content) + { + var innerContent = JArray.FromObject(content); + + foreach (var obj in innerContent.Values()) + { + if (obj != null) + { + foreach (var item in SystemResourceNames) + { + obj.Remove(item); + } + } + } + + return innerContent.ToString(Formatting.Indented); + } + } + + public class DocumentEditorViewModel : JsonViewerViewModel + { + private static readonly string[] SystemResourceNames = new[] { "_rid", "_etag", "_ts", "_self", "_id", "_attachments", "_docs", "_sprocs", "_triggers", "_udfs", "_conflicts", "_colls", "_users" }; + private JObject? _document; + + public DocumentEditorViewModel() + { + } + + protected override string? GetDocumentContent(object? content, bool removeSystemProperties) + { + if (content == null) + { + return null; + } + + _document = (JObject)content; + + return removeSystemProperties + ? RemoveCosmosSystemProperties(_document) + : _document.ToString(Formatting.Indented); + } + + private static string RemoveCosmosSystemProperties(JObject content) + { + var document = new JObject(content); // create a copy of the object + + foreach (var item in SystemResourceNames) + { + document.Remove(item); + } + + return document.ToString(Formatting.Indented); + } + + + public string? Id => _document?.Property("id")?.Value(); + + public bool IsNewDocument + { + get + { + return _document != null && _document.GetValue("_self")?.Value() == null; + } + } + } + + public class HeaderEditorViewModel : JsonEditorViewModelBase + { + public HeaderEditorViewModel() + { + } + + protected override string? GetDocumentContent(object content, bool removeSystemProperties) + { + if (content == null) + { + return null; + } + + var settings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + DateParseHandling = DateParseHandling.None + }; + settings.Converters.Add(new OrderedDictionaryConverter()); + + return JsonConvert.SerializeObject(content, settings); + //return JsonConvert.SerializeObject(((NameValueCollection)content).ToDictionary(), settings); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/MetricsTabViewModel.cs b/src/CosmosDbExplorer/ViewModels/MetricsTabViewModel.cs new file mode 100644 index 0000000..3ab4abd --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/MetricsTabViewModel.cs @@ -0,0 +1,136 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.Models; +using CosmosDbExplorer.ViewModels.DatabaseNodes; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Input; + +namespace CosmosDbExplorer.ViewModels +{ + public class MetricsTabViewModel : PaneViewModel, ICanRefreshTab + { + private readonly StatusBarItem _requestChargeStatusBarItem; + private readonly IServiceProvider _serviceProvider; + private CosmosConnection? _connection; + private CosmosContainer? _container; + private CosmosContainerService? _cosmosContainerService; + private AsyncRelayCommand? _refreshCommand; + + public MetricsTabViewModel(IServiceProvider serviceProvider, IUIServices uiServices) + : base(uiServices) + { + Title = "Collection Metrics"; + Header = Title; + + _requestChargeStatusBarItem = new StatusBarItem(new StatusBarItemContext { Value = RequestCharge, IsVisible = IsBusy }, StatusBarItemType.SimpleText, "Request Charge", System.Windows.Controls.Dock.Left); + StatusBarItems.Add(_requestChargeStatusBarItem); + _serviceProvider = serviceProvider; + } + + public CosmosContainerMetric? Metrics { get; private set; } + + public string? RequestCharge { get; private set; } + + public void OnRequestChargeChanged() + { + _requestChargeStatusBarItem.DataContext.Value = RequestCharge; + } + + protected override void OnIsBusyChanged() + { + _requestChargeStatusBarItem.DataContext.IsVisible = !IsBusy; + + base.OnIsBusyChanged(); + } + + public override async void Load(string contentId, MetricsNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer? container) + { + if (connection is null) + { + throw new ArgumentNullException(nameof(connection)); + } + + if (container is null) + { + throw new ArgumentNullException(nameof(container)); + } + + if (database is null) + { + throw new ArgumentNullException(nameof(database)); + } + + ContentId = contentId; + _connection = connection; + _container = container; + + _cosmosContainerService = ActivatorUtilities.CreateInstance(_serviceProvider, connection, database); + + ToolTip = $"{connection.Label}/{database.Id}/{container.Id}"; + AccentColor = _connection.AccentColor; + + await LoadMetrics(); + } + + public ICommand RefreshCommand => _refreshCommand ??= new AsyncRelayCommand(LoadMetrics, () => !IsBusy); + + private async Task LoadMetrics() + { + if (_container is null) + { + throw new NullReferenceException(nameof(_container)); + } + + if (_cosmosContainerService is null) + { + throw new NullReferenceException(nameof(_cosmosContainerService)); + } + + IsBusy = true; + + try + { + var tokenSource = new CancellationTokenSource(); + + Metrics = await _cosmosContainerService.GetContainerMetricsAsync(_container, tokenSource.Token).ConfigureAwait(false); + RequestCharge = $"Request Charge: {Metrics.RequestCharge:N2}"; + + OnPropertyChanged(nameof(Metrics)); + + //await DispatcherHelper.RunAsync(() => + //{ + // var sorted = Metrics.PartitionMetrics.OrderBy(pm => int.Parse(pm.PartitionKeyRangeId)).ToArray(); + // Labels = sorted.Select(pm => pm.PartitionKeyRangeId).ToArray(); + // PartitionSizeSeries = new SeriesCollection + // { + // new ColumnSeries + // { + // Title = "Size", + // Values = new ChartValues(sorted) + // } + // }; + //}); + } + //catch (DocumentClientException clientEx) + //{ + // await _dialogService.ShowError(clientEx.Parse(), "Error", "ok", null).ConfigureAwait(false); + //} + //catch (Exception ex) + //{ + // await _dialogService.ShowError(ex, "Error", "ok", null).ConfigureAwait(false); + //} + finally + { + IsBusy = false; + } + } + } +} diff --git a/src/CosmosDbExplorer/Infrastructure/Models/PaneViewModel.cs b/src/CosmosDbExplorer/ViewModels/PaneViewModel.cs similarity index 65% rename from src/CosmosDbExplorer/Infrastructure/Models/PaneViewModel.cs rename to src/CosmosDbExplorer/ViewModels/PaneViewModel.cs index 57ec108..ce89cfa 100644 --- a/src/CosmosDbExplorer/Infrastructure/Models/PaneViewModel.cs +++ b/src/CosmosDbExplorer/ViewModels/PaneViewModel.cs @@ -1,31 +1,31 @@ using System.Collections.ObjectModel; -using System.Windows.Media; +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Core.Models; using CosmosDbExplorer.Messages; -using CosmosDbExplorer.Services; -using GalaSoft.MvvmLight.Messaging; -using GalaSoft.MvvmLight.Threading; -using Microsoft.Azure.Documents; +using CosmosDbExplorer.Models; +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; using PropertyChanged; -namespace CosmosDbExplorer.Infrastructure.Models +namespace CosmosDbExplorer.ViewModels { public abstract class PaneViewModelBase : UIViewModelBase { private RelayCommand _closeCommand; private readonly StatusBarItem _pathStatusBarItem; - protected PaneViewModelBase(IMessenger messenger, IUIServices uiServices) - : base(messenger, uiServices) + protected PaneViewModelBase(IUIServices uiServices) + : base(uiServices) { _pathStatusBarItem = new StatusBarItem(new StatusBarItemContext { Value = ToolTip, IsVisible = true }, StatusBarItemType.SimpleText, "Path", System.Windows.Controls.Dock.Left); StatusBarItems.Add(_pathStatusBarItem); } [DoNotSetChanged] - public string Title { get; set; } + public string? Title { get; set; } [DoNotSetChanged] - public string ToolTip { get; set; } + public string? ToolTip { get; set; } public virtual void OnToolTipChanged() { @@ -42,31 +42,30 @@ public virtual void OnToolTipChanged() public bool IsSelected { get; set; } [DoNotSetChanged] - public bool IsActive { get; set; } + public new bool IsActive { get; set; } [DoNotSetChanged] public bool IsClosed { get; set; } public virtual void OnIsActiveChanged() { - DispatcherHelper.RunAsync(() => MessengerInstance.Send(new ActivePaneChangedMessage(this))); + Messenger.Send(new ActivePaneChangedMessage(this)); } [DoNotSetChanged] public ObservableCollection StatusBarItems { get; protected set; } = new ObservableCollection(); [DoNotSetChanged] - public object IconSource { get; set; } + public object? IconSource { get; set; } [DoNotSetChanged] - public Color? AccentColor { get; set; } + public System.Drawing.Color? AccentColor { get; set; } public RelayCommand CloseCommand { get { - return _closeCommand - ?? (_closeCommand = new RelayCommand(OnClose, CanClose)); + return _closeCommand ??= new RelayCommand(OnClose, CanClose); } } @@ -77,8 +76,8 @@ protected virtual bool CanClose() protected virtual void OnClose() { - MessengerInstance.Send(new CloseDocumentMessage(this)); - Cleanup(); + Messenger.Send(new CloseDocumentMessage(this)); + OnDeactivated(); IsClosed = true; } } @@ -86,20 +85,20 @@ protected virtual void OnClose() public abstract class PaneViewModel : PaneViewModelBase where TNodeViewModel : TreeViewItemViewModel { - protected PaneViewModel(IMessenger messenger, IUIServices uiServices) - : base(messenger, uiServices) + protected PaneViewModel(IUIServices uiServices) + : base(uiServices) { } - public abstract void Load(string contentId, TNodeViewModel node, Connection connection, DocumentCollection collection); + public abstract void Load(string contentId, TNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer? container); } public abstract class PaneWithZoomViewModel : PaneViewModel where TNodeViewModel : TreeViewItemViewModel { - protected PaneWithZoomViewModel(IMessenger messenger, IUIServices uiServices) - : base(messenger, uiServices) + protected PaneWithZoomViewModel(IUIServices uiServices) + : base(uiServices) { StatusBarItems.Add(new StatusBarItem(new StatusBarItemContext { Value = this, IsVisible = true }, StatusBarItemType.Zoom, "Zoom", System.Windows.Controls.Dock.Right)); } @@ -110,8 +109,8 @@ protected PaneWithZoomViewModel(IMessenger messenger, IUIServices uiServices) public abstract class ToolViewModel : PaneViewModelBase { - protected ToolViewModel(IMessenger messenger, IUIServices uiServices) - : base(messenger, uiServices) + protected ToolViewModel(IUIServices uiServices) + : base(uiServices) { } diff --git a/src/CosmosDbExplorer/ViewModels/PermissionEditViewModel.cs b/src/CosmosDbExplorer/ViewModels/PermissionEditViewModel.cs new file mode 100644 index 0000000..c500e0c --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/PermissionEditViewModel.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.ViewModels.DatabaseNodes; + +using FluentValidation; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Input; + +using PropertyChanged; + +using Validar; + +namespace CosmosDbExplorer.ViewModels +{ + [InjectValidation] + public class PermissionEditViewModel : PaneViewModel, IAssetTabCommand + { + private readonly IServiceProvider _serviceProvider; + private readonly IDialogService _dialogService; + private AsyncRelayCommand _saveCommand; + private AsyncRelayCommand _deleteCommand; + private RelayCommand _discardCommand; + private RelayCommand _copyToClipboardCommand; + private CosmosUserService _userService; + + public PermissionEditViewModel(IServiceProvider serviceProvider, IDialogService dialogService, IUIServices uiServices) + : base(uiServices) + { + _serviceProvider = serviceProvider; + _dialogService = dialogService; + Header = "New Permission"; + Title = "Permission"; + IconSource = App.Current.FindResource("PermissionIcon"); + PropertyChanged += (s, e) => + { + var properties = new[] { nameof(PermissionId), nameof(PermissionMode), nameof(Container), nameof(ResourcePartitionKey) }; + + if (properties.Contains(e.PropertyName)) + { + IsDirty = IsEntityChanged(); + } + + OnIsDirtyChanged(); + }; + } + + public bool CanEditName { get; protected set; } + public string? PermissionId { get; set; } + public CosmosPermissionMode PermissionMode { get; set; } + public string Container { get; set; } + public string? ResourcePartitionKey { get; set; } + + public CosmosPermission Permission { get; protected set; } + + public bool IsEntityChanged() + { + if (Permission != null) + { + if (PermissionId != Permission.Id) + { + return true; + } + + if (PermissionMode != Permission.PermissionMode) + { + return true; + } + + if (Container != Permission.ResourceUri) + { + return true; + } + + if (ResourcePartitionKey != Permission.PartitionKey?.ToString()) + { + return true; + } + } + + return false; + } + + private void SetInformation() + { + if (Permission is null) + { + throw new NullReferenceException("Permission should not be null"); + } + + PermissionId = Permission.Id; + PermissionMode = Permission.PermissionMode; + Container = Permission.ResourceUri; + ResourcePartitionKey = Permission.PartitionKey; + + IsDirty = false; + } + + public bool IsNewDocument => Node?.Permission?.SelfLink == null; + + public bool IsValid => string.IsNullOrEmpty(((IDataErrorInfo)this).Error); + + public override void Load(string contentId, PermissionNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer? container) + { + if (node is null) + { + throw new ArgumentNullException(nameof(node)); + } + + if (connection is null) + { + throw new ArgumentNullException(nameof(connection)); + } + + if (database is null) + { + throw new ArgumentNullException(nameof(database)); + } + + ContentId = contentId; + Node = node; + Permission = node?.Permission ?? new CosmosPermission(); + Header = node.Name ?? "New Permission"; + Title = "Permission"; + AccentColor = Node.Parent.Parent.Parent.Parent.Connection.AccentColor; + ToolTip = $"{connection.Label}/{database.Id}/{node.Name}"; + + Containers = new ObservableCollection(Node.Parent.Parent.Parent.Children.OfType().Select(c => c.Name)); + + _userService = ActivatorUtilities.CreateInstance(_serviceProvider, connection, database); + SetInformation(); + } + + public PermissionNodeViewModel Node { get; protected set; } + + public ObservableCollection? Containers { get; protected set; } + + public RelayCommand CopyToClipboardCommand => _copyToClipboardCommand ??= new(() => System.Windows.Clipboard.SetText(Permission?.Token), () => !string.IsNullOrEmpty(Permission?.Token)); + + public ICommand DiscardCommand => _discardCommand ??= new(SetInformation, () => IsDirty); + + public ICommand SaveCommand => _saveCommand ??= new(SaveCommandExecute, () => IsDirty && IsValid); + + private async Task SaveCommandExecute() + { + CosmosPermission permission; + if (IsNewDocument) + { + permission = new CosmosPermission + { + Id = PermissionId, + }; + } + else + { + permission = Node.Permission; + } + + permission.Id = PermissionId; + permission.PermissionMode = PermissionMode; + permission.PartitionKey = ResourcePartitionKey; + + try + { + var result = await _userService.SavePermissionAsync(Node.Parent.User, permission, Container, new System.Threading.CancellationToken()); + + Header = result.Items.Id; + Node.Permission = result.Items; + ContentId = Node.ContentId; + + OnPropertyChanged(nameof(IsNewDocument)); + Node.Parent.RefreshCommand.Execute(null); + IsDirty = false; + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "Error"); + } + + } + + public ICommand DeleteCommand => _deleteCommand ??= new(DeleteCommandExecute, () => !IsNewDocument); + + private async Task DeleteCommandExecute() + { + async void deleteUser(bool confirm) + { + if (!confirm) + { + return; + } + + try + { + var result = await _userService.DeletePermissionAsync(Node.Parent.User, Node.Permission, new System.Threading.CancellationToken()); + Node.Parent.RefreshCommand.Execute(null); // Send Message? + CloseCommand.Execute(null); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "An error occured`!"); + } + } + + await _dialogService.ShowQuestion($"Do you want to delete the permission '{PermissionId}' ?", "Delete Permission", deleteUser); + } + + public bool IsDirty { get; protected set; } + + protected void OnIsDirtyChanged() + { + _saveCommand?.NotifyCanExecuteChanged(); + _deleteCommand?.NotifyCanExecuteChanged(); + _discardCommand?.NotifyCanExecuteChanged(); + } + } + + public class PermissionEditViewModelValidator : AbstractValidator + { + public PermissionEditViewModelValidator() + { + RuleFor(x => x.PermissionId).NotEmpty(); + RuleFor(x => x.Container).NotEmpty(); + //.Matches(@"dbs\/(\w|\s)*\/colls\/(\w|\s)*").WithMessage("Must be in the format 'dbs/[db id]/colls/[container id]"); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/QueryEditorViewModel.cs b/src/CosmosDbExplorer/ViewModels/QueryEditorViewModel.cs new file mode 100644 index 0000000..c123543 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/QueryEditorViewModel.cs @@ -0,0 +1,473 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Contracts; +using CosmosDbExplorer.Core.Contracts.Services; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.Models; +using CosmosDbExplorer.Properties; +using CosmosDbExplorer.Services.DialogSettings; +using CosmosDbExplorer.ViewModels.DatabaseNodes; +using FluentValidation; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Input; +using Newtonsoft.Json.Linq; +using PropertyChanged; +using Validar; + +namespace CosmosDbExplorer.ViewModels +{ + [InjectValidation] + public class QueryEditorViewModel : PaneWithZoomViewModel + , IHaveSystemProperties + { + private CosmosQueryResult>? _queryResult; + private readonly StatusBarItem _requestChargeStatusBarItem; + private readonly StatusBarItem _queryInformationStatusBarItem; + private readonly StatusBarItem _progessBarStatusBarItem; + private CancellationTokenSource? _cancellationTokenSource; + + private RelayCommand _saveLocalCommand; + private RelayCommand _saveQueryCommand; + private RelayCommand _openQueryCommand; + + private ICosmosDocumentService _documentService; + + private readonly ICosmosQuery _query; + private AsyncRelayCommand _goToNextPageCommand; + private AsyncRelayCommand _executeCommand; + private RelayCommand _cancelCommand; + + public QueryEditorViewModel(IServiceProvider serviceProvider, IUIServices uiServices, IDialogService dialogService) + : base(uiServices) + { + _serviceProvider = serviceProvider; + _dialogService = dialogService; + EditorViewModel = new JsonViewerViewModel { IsReadOnly = true }; + HeaderViewModel = new HeaderEditorViewModel { IsReadOnly = true }; + IconSource = App.Current.FindResource("SqlQueryIcon"); + + _query = new CosmosQuery(); + + _requestChargeStatusBarItem = new StatusBarItem(new StatusBarItemContext { Value = RequestCharge, IsVisible = IsRunning }, StatusBarItemType.SimpleText, "Request Charge", System.Windows.Controls.Dock.Left); + StatusBarItems.Add(_requestChargeStatusBarItem); + _queryInformationStatusBarItem = new StatusBarItem(new StatusBarItemContext { Value = QueryInformation, IsVisible = IsRunning }, StatusBarItemType.SimpleText, "Information", System.Windows.Controls.Dock.Left); + StatusBarItems.Add(_queryInformationStatusBarItem); + _progessBarStatusBarItem = new StatusBarItem(new StatusBarItemContextCancellableCommand { Value = CancelCommand, IsVisible = IsRunning, IsCancellable = true }, StatusBarItemType.ProgessBar, "Progress", System.Windows.Controls.Dock.Left); + StatusBarItems.Add(_progessBarStatusBarItem); + } + + public override void Load(string contentId, ContainerNodeViewModel node, CosmosConnection connection, CosmosDatabase database, CosmosContainer container) + { + ContentId = Guid.NewGuid().ToString(); + Node = node; + Header = "SQL Query"; + Connection = connection; + Container = container; + + _documentService = ActivatorUtilities.CreateInstance(_serviceProvider, connection, database, container); + + Content = $"SELECT * FROM {Container.Id} AS {Container.Id.Substring(0, 1).ToLower()}"; + + //var split = Container.SelfLink.Split(new char[] { '/' }); + ToolTip = $"{Connection.Label}/{database.Id}/{Container.Id}"; + AccentColor = Node.Parent.Parent.Connection.AccentColor; + } + + public ContainerNodeViewModel Node { get; protected set; } + + protected CosmosConnection Connection { get; set; } + + protected CosmosContainer Container { get; set; } + + [OnChangedMethod(nameof(NotifyCanExecuteChanged))] + public string Content { get; set; } + + [OnChangedMethod(nameof(NotifyCanExecuteChanged))] + public string SelectedText { get; set; } + + [OnChangedMethod(nameof(NotifyCanExecuteChanged))] + public bool IsDirty { get; set; } + + public bool IsRunning { get; set; } + + public void OnIsRunningChanged() + { + _progessBarStatusBarItem.DataContext.IsVisible = IsRunning; + _requestChargeStatusBarItem.DataContext.IsVisible = !IsRunning; + _queryInformationStatusBarItem.DataContext.IsVisible = !IsRunning; + + if (IsRunning) + { + _cancellationTokenSource = new CancellationTokenSource(); + } + else + { + _cancellationTokenSource = null; + } + + NotifyCanExecuteChanged(); + } + + private readonly IServiceProvider _serviceProvider; + private readonly IDialogService _dialogService; + + public JsonViewerViewModel EditorViewModel { get; set; } + + public HeaderEditorViewModel HeaderViewModel { get; set; } + + public string? RequestCharge { get; set; } + + public void OnRequestChargeChanged() + { + _requestChargeStatusBarItem.DataContext.Value = RequestCharge; + } + + public string? QueryInformation { get; set; } + + public void OnQueryInformationChanged() + { + _queryInformationStatusBarItem.DataContext.Value = QueryInformation; + } + + public string? ContinuationToken { get; set; } + + public string? QueryMetrics { get; set; } + + //public Dictionary QueryMetrics { get; set; } + + public AsyncRelayCommand ExecuteCommand => _executeCommand ??= new(() => ExecuteQueryAsync(null), () => !IsRunning && !string.IsNullOrEmpty(Content) && IsValid); + + public RelayCommand CancelCommand => _cancelCommand ??= new(() => _cancellationTokenSource?.Cancel(), () => IsRunning); + + private async Task ExecuteQueryAsync(string? token) + { + try + { + IsRunning = true; + + Clean(); + + ((StatusBarItemContextCancellableCommand)_progessBarStatusBarItem.DataContext).IsCancellable = true; + + _query.QueryText = string.IsNullOrEmpty(SelectedText) ? Content : SelectedText; + _query.PartitionKeyValue = GetPartitionKey(); + _query.ContinuationToken = token; + + _queryResult = await _documentService.ExecuteQueryAsync(_query, _cancellationTokenSource.Token); + + ((StatusBarItemContextCancellableCommand)_progessBarStatusBarItem.DataContext).IsCancellable = false; + + ContinuationToken = _queryResult.ContinuationToken; + + RequestCharge = $"Request Charge: {_queryResult.RequestCharge:N2}"; + QueryInformation = $"Returned {_queryResult.Items?.Count:N0} document(s)." + + (ContinuationToken != null + ? " (more results available)" + : string.Empty); + + QueryMetrics = _queryResult.IndexMetrics; + + //if (_queryResult.QueryMetrics != null) + //{ + // QueryMetrics = new Dictionary + // { + // { "All", _queryResult.QueryMetrics.Select(q => q.Value).Aggregate((i, next) => i + next) } + // }; + + // foreach (var metric in _queryResult.QueryMetrics.OrderBy(q => int.Parse(q.Key))) + // { + // QueryMetrics.Add(metric.Key, metric.Value); + // } + //} + //else + //{ + // QueryMetrics = null; + //} + + + EditorViewModel.SetText(_queryResult.Items, HideSystemProperties); + HeaderViewModel.SetText(_queryResult.Headers, HideSystemProperties); + + NotifyCanExecuteChanged(); + + } + catch (OperationCanceledException) + { + Clean(); + } + //catch (DocumentClientException clientEx) + //{ + // Clean(); + // await _dialogService.ShowError(clientEx.Parse(), "Error", "ok", null).ConfigureAwait(false); + //} + catch (Exception ex) + { + Clean(); + await _dialogService.ShowError(ex, "Error").ConfigureAwait(false); + } + finally + { + IsRunning = false; + } + } + + private Optional GetPartitionKey() + { + if (string.IsNullOrEmpty(PartitionKeyValue)) + { + return new Optional(); + } + + if (double.TryParse(PartitionKeyValue, out var numeric)) + { + return new Optional(numeric); + } + + if (bool.TryParse(PartitionKeyValue, out var boolean)) + { + return new Optional(boolean); + } + + if (PartitionKeyValue.Equals("null", StringComparison.OrdinalIgnoreCase)) + { + return new Optional(null); + } + + return new Optional(PartitionKeyValue); + } + + private void Clean() + { + ContinuationToken = null; + RequestCharge = null; + QueryInformation = null; + QueryMetrics = null; + EditorViewModel.SetText(null, HideSystemProperties); + HeaderViewModel.SetText(null, HideSystemProperties); + + GC.Collect(); + } + + private void NotifyCanExecuteChanged() + { + GoToNextPageCommand.NotifyCanExecuteChanged(); + SaveLocalCommand.NotifyCanExecuteChanged(); + SaveQueryCommand.NotifyCanExecuteChanged(); + OpenQueryCommand.NotifyCanExecuteChanged(); + } + + public AsyncRelayCommand GoToNextPageCommand => _goToNextPageCommand ??= new(GoToNextPageCommandExecute, () => GoToNextPageCommandCanExecute()); + + private Task GoToNextPageCommandExecute() + { + return ExecuteQueryAsync(ContinuationToken); + } + + private bool GoToNextPageCommandCanExecute() + { + return !string.IsNullOrEmpty(ContinuationToken) && !IsRunning && !string.IsNullOrEmpty(Content) && IsValid; + } + + + public RelayCommand SaveLocalCommand => _saveLocalCommand ??= new(SaveLocalCommandExecute, () => !IsRunning && !string.IsNullOrEmpty(EditorViewModel.Text)); + + private void SaveLocalCommandExecute() + { + var settings = new SaveFileDialogSettings + { + DefaultExt = "json", + Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*", + AddExtension = true, + OverwritePrompt = true, + CheckFileExists = false, + Title = "Save document locally", + InitialDirectory = Settings.Default.GetExportFolder() + }; + + async void saveFileAsyc(bool confirm, FileDialogResult result) + { + if (!confirm) + { + return; + } + + IsRunning = true; + + try + { + Settings.Default.ExportFolder = Path.GetDirectoryName(result.FileName); + Settings.Default.Save(); + + await File.WriteAllTextAsync(result.FileName, EditorViewModel.Text); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "An error during saving file"); + } + finally + { + IsRunning = false; + } + }; + + _dialogService?.ShowSaveFileDialog(settings, saveFileAsyc); + } + + public string FileName { get; set; } + + public RelayCommand SaveQueryCommand => _saveQueryCommand ??= new(SaveQueryCommandExecute, param => !IsRunning && !string.IsNullOrEmpty(Content)); + + private async void SaveQueryCommandExecute(string param) + { + if (FileName == null || param == "SaveAs") + { + var settings = new SaveFileDialogSettings + { + DefaultExt = "sql", + Filter = "SQL Query (*.sql)|*.sql|All files (*.*)|*.*", + AddExtension = true, + OverwritePrompt = true, + CheckFileExists = false, + Title = "Save Query" + }; + + async void saveFile(bool confirm, FileDialogResult result) + { + if (!confirm) + { + return; + } + + try + { + IsRunning = true; + FileName = result.FileName; + + await File.WriteAllTextAsync(result.FileName, Content); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "Error"); + } + finally + { + IsRunning = false; + } + + } + + _dialogService.ShowSaveFileDialog(settings, saveFile); + } + else + { + await File.WriteAllTextAsync(FileName, Content); + } + } + + public RelayCommand OpenQueryCommand => _openQueryCommand ??= new(OpenQueryCommandExecute, () => !IsRunning && !string.IsNullOrEmpty(Content)); + + private void OpenQueryCommandExecute() + { + var settings = new OpenFileDialogSettings + { + DefaultExt = "sql", + Filter = "SQL Query (*.sql)|*.sql|All files (*.*)|*.*", + AddExtension = true, + CheckFileExists = true, + Title = "Open Query" + }; + + async void openFile(bool confirm, FileDialogResult result) + { + if (!confirm) + { + return; + } + + IsRunning = true; + + try + { + Content = await File.ReadAllTextAsync(result.FileName); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "An error occured"); + } + finally + { + IsRunning = false; + } + } + + _dialogService.ShowOpenFileDialog(settings, openFile); + } + + public bool HideSystemProperties { get; set; } + + public void OnHideSystemPropertiesChanged() + { + if (_queryResult != null) + { + EditorViewModel.SetText(_queryResult.Items, HideSystemProperties); + } + } + + public bool EnableScanInQuery + { + get { return _query.EnableScanInQuery; } + set { _query.EnableScanInQuery = value; } + } + public bool EnableCrossPartitionQuery + { + get { return _query.EnableCrossPartitionQuery; } + set { _query.EnableCrossPartitionQuery = value; } + } + + public int MaxItemCount + { + get { return _query.MaxItemCount; } + set { _query.MaxItemCount = value; } + } + + public int MaxDOP + { + get { return _query.MaxDOP; } + set { _query.MaxDOP = value; } + } + + public int MaxBufferItem + { + get { return _query.MaxBufferItem; } + set { _query.MaxBufferItem = value; } + } + + public string? PartitionKeyValue { get; set; } + + public bool IsValid => string.IsNullOrEmpty(((IDataErrorInfo)this).Error);//!((INotifyDataErrorInfo)this).HasErrors; + + protected override void OnClose() + { + Clean(); + base.OnClose(); + } + } + + public class QueryEditorViewModelValidator : AbstractValidator + { + public QueryEditorViewModelValidator() + { + //When(x => !string.IsNullOrEmpty(x.PartitionKeyValue?.Trim()), + // () => RuleFor(x => x.PartitionKeyValue).SetValidator(new PartitionKeyValidator())); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/SettingsViewModel.cs b/src/CosmosDbExplorer/ViewModels/SettingsViewModel.cs new file mode 100644 index 0000000..7070009 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/SettingsViewModel.cs @@ -0,0 +1,63 @@ +using System; +using System.Windows.Input; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Models; +using CosmosDbExplorer.Properties; + +using Microsoft.Extensions.Options; +using Microsoft.Toolkit.Mvvm.ComponentModel; +using Microsoft.Toolkit.Mvvm.Input; + +namespace CosmosDbExplorer.ViewModels +{ + // TODO WTS: Change the URL for your privacy policy in the appsettings.json file, currently set to https://YourPrivacyUrlGoesHere + public class SettingsViewModel : ObservableObject, INavigationAware + { + private readonly IPersistAndRestoreService _persistAndRestoreService; + private readonly IThemeSelectorService _themeSelectorService; + private RelayCommand? _resetSettingsCommand; + + public AppTheme Theme { get; set; } + + public DialogStyles DialogStyle + { + get { return Enum.Parse(Settings.Default.DialogService); } + set { Settings.Default.DialogService = value.ToString(); } + } + + public string VersionDescription { get; set; } + + public RelayCommand ResetSettingsCommand => _resetSettingsCommand ??= new(OnResetSettingsCommand); + + public SettingsViewModel(IPersistAndRestoreService persistAndRestoreService, IThemeSelectorService themeSelectorService, IApplicationInfoService applicationInfoService) + { + _persistAndRestoreService = persistAndRestoreService; + _themeSelectorService = themeSelectorService; + + VersionDescription = $"{Properties.Resources.AppDisplayName} - {applicationInfoService.GetVersion()}"; + Theme = _themeSelectorService.GetCurrentTheme(); + } + + public void OnNavigatedTo(object parameter) + { + } + + public void OnNavigatedFrom() + { + _persistAndRestoreService.PersistData(); + } + + protected void OnThemeChanged() + { + _themeSelectorService.SetTheme(Theme); + } + + private void OnResetSettingsCommand() + { + _persistAndRestoreService.ResetData(); + Theme = _themeSelectorService.GetCurrentTheme(); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/ShellDialogViewModel.cs b/src/CosmosDbExplorer/ViewModels/ShellDialogViewModel.cs new file mode 100644 index 0000000..edd8be0 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/ShellDialogViewModel.cs @@ -0,0 +1,27 @@ +using System; +using System.Windows.Input; + +using Microsoft.Toolkit.Mvvm.ComponentModel; +using Microsoft.Toolkit.Mvvm.Input; + +namespace CosmosDbExplorer.ViewModels +{ + public class ShellDialogViewModel : ObservableObject + { + private ICommand _closeCommand; + + public ICommand CloseCommand => _closeCommand ??= new RelayCommand(OnClose); + + public Action SetResult { get; set; } + + public ShellDialogViewModel() + { + } + + private void OnClose() + { + bool result = true; + SetResult(result); + } + } +} diff --git a/src/CosmosDbExplorer/ViewModels/ShellViewModel.cs b/src/CosmosDbExplorer/ViewModels/ShellViewModel.cs new file mode 100644 index 0000000..9d9ca06 --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/ShellViewModel.cs @@ -0,0 +1,272 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Timers; +using System.Windows.Input; +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Messages; +using CosmosDbExplorer.ViewModels.Assets; +using CosmosDbExplorer.ViewModels.DatabaseNodes; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.ComponentModel; +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; +using PropertyChanged; + +namespace CosmosDbExplorer.ViewModels +{ + public class ShellViewModel : ObservableRecipient + { + private readonly IWindowManagerService _windowManagerService; + private readonly IRightPaneService _rightPaneService; + private readonly IApplicationInfoService _applicationInfoService; + private readonly DatabaseViewModel _databaseViewModel; + private readonly IServiceProvider _serviceProvider; + private ICommand _loadedCommand; + private ICommand _unloadedCommand; + private ICommand _exitCommand; + private RelayCommand _refreshCommand; + private RelayCommand _showAccountSettingsCommand; + + public ICommand LoadedCommand => _loadedCommand ??= new RelayCommand(OnLoaded); + + public ICommand UnloadedCommand => _unloadedCommand ??= new RelayCommand(OnUnloaded); + + public ShellViewModel(IWindowManagerService windowManagerService, IRightPaneService rightPaneService, IApplicationInfoService applicationInfoService, DatabaseViewModel databaseViewModel, IServiceProvider serviceProvider) + { + _windowManagerService = windowManagerService; + _rightPaneService = rightPaneService; + _applicationInfoService = applicationInfoService; + _databaseViewModel = databaseViewModel; + _serviceProvider = serviceProvider; + SpyUsedMemory(); + RegisterMessages(); + } + + private void OnLoaded() + { + } + + private void OnUnloaded() + { + _rightPaneService.CleanUp(); + } + + public string Title { get; set; } + + public long UsedMemory => GC.GetTotalMemory(true) / 1014; + + public bool IsBusy { get; set; } + + public double Zoom { get; set; } + + public ObservableCollection Tabs { get; } = new ObservableCollection(); + + public IEnumerable Tools => new ToolViewModel[] { _databaseViewModel }; + + public PaneViewModelBase? SelectedTab { get; set; } + + public void OnSelectedTabChanged() + { + IsTabDocumentsVisible = SelectedTab is DocumentsTabViewModel; + IsSettingsTabVisible = SelectedTab is ContainerScaleSettingsViewModel || SelectedTab is DatabaseScaleViewModel; + IsAssetTabVisible = SelectedTab is IAssetTabCommand; + IsQueryTabVisible = SelectedTab is QueryEditorViewModel || SelectedTab is StoredProcedureTabViewModel; + IsImportTabVisible = SelectedTab is ImportDocumentViewModel; + IsQuerySettingsVisible = SelectedTab is QueryEditorViewModel; + IsSystemPropertiesVisible = SelectedTab is IHaveSystemProperties; + IsRequestOptionsVisible = SelectedTab is IHaveRequestOptions; + IsConnectionOptionsVisible = false; // Only visible when selecting a tab + IsRefreshTabVisible = SelectedTab is ICanRefreshTab; + } + + public int SelectedRibbonTab { get; set; } + public bool IsConnectionOptionsVisible { get; set; } + public bool IsTabDocumentsVisible { get; set; } + public bool IsSettingsTabVisible { get; set; } + public bool IsAssetTabVisible { get; set; } + public bool IsQueryTabVisible { get; set; } + public bool IsImportTabVisible { get; set; } + public bool IsQuerySettingsVisible { get; set; } + public bool IsRequestOptionsVisible { get; set; } + public bool IsRefreshTabVisible { get; set; } + public bool IsSystemPropertiesVisible { get; set; } + + public ConnectionNodeViewModel? Connection { get; set; } + public DatabaseNodeViewModel? Database { get; set; } + public ContainerNodeViewModel? Container { get; set; } + public UsersNodeViewModel? Users { get; set; } + public UserNodeViewModel? UserNode { get; set; } + + [OnChangedMethod(nameof(NotifyCanExecuteChanged))] + public ICanRefreshNode? CanRefreshNodeViewModel { get; set; } + + public ICanEditDelete? CanEditDelete { get; set; } + + public ICommand ShowAccountSettingsCommand => _showAccountSettingsCommand ??= new RelayCommand(ShowAccountSettingsCommandExecute); + + private void ShowAccountSettingsCommandExecute() + { + var vmName = typeof(AccountSettingsViewModel).FullName; + + if (!string.IsNullOrEmpty(vmName)) + { + _windowManagerService.OpenInDialog(vmName, new CosmosConnection(Guid.NewGuid())); + } + } + + public ICommand RefreshCommand => _refreshCommand ??= new RelayCommand(() => CanRefreshNodeViewModel?.RefreshCommand.Execute(null), () => + { + if (CanRefreshNodeViewModel?.RefreshCommand == null) + { + return false; + } + + return CanRefreshNodeViewModel.RefreshCommand.CanExecute(null); + }); + + public ICommand ExitCommand => _exitCommand ??= new RelayCommand(Close); + + + private void NotifyCanExecuteChanged() + { + _refreshCommand?.NotifyCanExecuteChanged(); + } + + public virtual void Close() + { + throw new NotImplementedException(); + //RequestClose?.Invoke(); + } + + private void SpyUsedMemory() + { + var timer = new Timer(TimeSpan.FromSeconds(3).TotalMilliseconds); + timer.Elapsed += (s, e) => OnPropertyChanged(nameof(UsedMemory)); + timer.Start(); + } + + private void RegisterMessages() + { + Messenger.Register(this, static (r, msg) => r.OnActivePaneChanged(msg)); + + Messenger.Register(this, static (r, msg) => r.OpenOrSelectTab(msg)); + Messenger.Register(this, static (r, msg) => r.OpenOrSelectTab(msg)); + Messenger.Register(this, static (r, msg) => r.OpenOrSelectTab(msg)); + Messenger.Register(this, static (r, msg) => r.OpenOrSelectTab(msg)); + Messenger.Register(this, static (r, msg) => r.OpenOrSelectTab(msg)); + + Messenger.Register(this, static (r, msg) => r.OpenOrSelectTab(msg)); + Messenger.Register(this, static (r, msg) => r.OpenOrSelectTab(msg)); + Messenger.Register(this, static (r, msg) => r.OpenOrSelectTab(msg)); + + Messenger.Register(this, static (r, msg) => r.OpenOrSelectTab(msg)); + Messenger.Register(this, static (r, msg) => r.OpenOrSelectTab(msg)); + Messenger.Register(this, static (r, msg) => r.OpenOrSelectTab(msg)); + + Messenger.Register(this, static (r, msg) => r.OnTreeNodeSelected(msg)); + Messenger.Register(this, static (r, msg) => r.CloseDocument(msg)); + Messenger.Register(this, static (r, msg) => r.IsBusy = msg.IsBusy); + } + + private void OnActivePaneChanged(ActivePaneChangedMessage message) + { + if (message.PaneViewModel is DatabaseViewModel) + { + IsRequestOptionsVisible = false; + IsConnectionOptionsVisible = true; + SelectedRibbonTab = 1; + } + else + { + IsConnectionOptionsVisible = ShouldConnectionOptionBeVisible(); + OnSelectedTabChanged(); + SelectedRibbonTab = 0; + } + } + + private void OnTreeNodeSelected(TreeNodeSelectedMessage message) + { + if (message is null || message.Item is null) + { + return; + } + + CanRefreshNodeViewModel = message.Item as ICanRefreshNode; + Connection = message.Item as ConnectionNodeViewModel; + Database = message.Item as DatabaseNodeViewModel; + Container = (message.Item as IHaveContainerNodeViewModel)?.ContainerNode; + Users = message.Item as UsersNodeViewModel; + UserNode = message.Item as UserNodeViewModel; + CanEditDelete = message.Item as ICanEditDelete; + + IsConnectionOptionsVisible = ShouldConnectionOptionBeVisible(); + } + + private bool ShouldConnectionOptionBeVisible() + { + return CanRefreshNodeViewModel != null + || Connection != null + || Database != null + || Container != null + || CanEditDelete != null + || Users != null + || UserNode != null; + } + + private void OpenOrSelectTab(OpenTabMessageBase message) + where TTabViewModel : PaneViewModel + where TNodeViewModel : TreeViewItemViewModel, IContent + { + if (message is null) + { + throw new Exception("Node is null!"); + } + + var contentId = message.Node?.ContentId ?? Guid.NewGuid().ToString(); + + var tab = Tabs.FirstOrDefault(t => t.ContentId == contentId); + + if (tab != null) + { + SelectedTab = tab; + } + else + { + var content = _serviceProvider.GetService(); + + if (content != null) + { + content.Load(contentId, message.Node, message.Connection, message.Database, message.Container); + + Tabs.Add(content); + SelectedTab = content; + } + else + { + var type = typeof(TTabViewModel); + throw new Exception($"Don't forget to register type {type.Name} in IoC."); + } + } + } + + private void CloseDocument(CloseDocumentMessage msg) + { + //DispatcherHelper.RunAsync(() => + //{ + var vm = Tabs.FirstOrDefault(t => t.ContentId == msg.ContentId); + + if (vm != null) + { + Tabs.Remove(vm); + //_ioc.Unregister(vm); + vm = null; + SelectedTab = Tabs.LastOrDefault(); + } + //}); + } + } +} diff --git a/src/CosmosDbExplorer/Infrastructure/Models/TreeViewItemViewModel.cs b/src/CosmosDbExplorer/ViewModels/TreeViewItemViewModel.cs similarity index 54% rename from src/CosmosDbExplorer/Infrastructure/Models/TreeViewItemViewModel.cs rename to src/CosmosDbExplorer/ViewModels/TreeViewItemViewModel.cs index e156f35..78f824a 100644 --- a/src/CosmosDbExplorer/Infrastructure/Models/TreeViewItemViewModel.cs +++ b/src/CosmosDbExplorer/ViewModels/TreeViewItemViewModel.cs @@ -1,40 +1,31 @@ -using GalaSoft.MvvmLight; +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; -using GalaSoft.MvvmLight.Messaging; +using System.Linq; +using System.Text; +using System.Threading; using System.Threading.Tasks; +using CosmosDbExplorer.Contracts.ViewModels; using CosmosDbExplorer.Messages; -using GalaSoft.MvvmLight.Threading; -using CosmosDbExplorer.ViewModel; +using Microsoft.Toolkit.Mvvm.ComponentModel; +using Microsoft.Toolkit.Mvvm.Messaging; -namespace CosmosDbExplorer.Infrastructure.Models +namespace CosmosDbExplorer.ViewModels { - public interface ITreeViewItemViewModel - { - ObservableCollection Children { get; } - bool HasDummyChild { get; } - bool IsExpanded { get; set; } - bool IsSelected { get; set; } - bool IsLoading { get; set; } - TreeViewItemViewModel Parent { get; } - } - /// /// Base class for all ViewModel classes displayed by TreeViewItems. /// This acts as an adapter between a raw data object and a TreeViewItem. /// - public class TreeViewItemViewModel : ObservableObject + public class TreeViewItemViewModel : ObservableRecipient { - private static readonly TreeViewItemViewModel DummyChild = new TreeViewItemViewModel(); + private static readonly TreeViewItemViewModel DummyChild = new(); - private bool _isExpanded; - - protected TreeViewItemViewModel(TreeViewItemViewModel parent, IMessenger messenger, bool lazyLoadChildren) + protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren) { Parent = parent; - MessengerInstance = messenger; Children = new ObservableCollection(); - messenger.Register(this, OnRemoveNodeMessage); + Messenger.Register(this, static (r, m) => r.OnRemoveNodeMessage(m)); if (lazyLoadChildren) { @@ -48,7 +39,7 @@ private void OnRemoveNodeMessage(RemoveNodeMessage msg) { if (this is IContent assetNode && assetNode.ContentId == msg.AltLink) { - DispatcherHelper.RunAsync(() => Parent.Children.Remove(this)); + Parent.Children.Remove(this); } } } @@ -75,29 +66,22 @@ public bool HasDummyChild /// Gets/sets whether the TreeViewItem /// associated with this object is expanded. /// - public bool IsExpanded + public bool IsExpanded { get; set; } + + public async void OnIsExpandedChanged() { - get { return _isExpanded; } - set + // Expand all the way up to the root. + if (IsExpanded && Parent != null) { - if (value != _isExpanded) - { - _isExpanded = value; - RaisePropertyChanged(() => IsExpanded); - } - - // Expand all the way up to the root. - if (_isExpanded && Parent != null) - { - Parent.IsExpanded = true; - } + Parent.IsExpanded = true; + } - // Lazy load the child items, if necessary. - if (HasDummyChild) - { - Children.Remove(DummyChild); - Task.Run(() => LoadChildren()); - } + // Lazy load the child items, if necessary. + if (HasDummyChild) + { + Children.Remove(DummyChild); + var token = new CancellationToken(); + await LoadChildren(token); } } @@ -109,7 +93,13 @@ public bool IsExpanded public void OnIsSelectedChanged() { - MessengerInstance.Send(new TreeNodeSelectedMessage(this)); + Messenger.Send(new TreeNodeSelectedMessage(this)); + NotifyCanExecuteChanged(); + } + + protected virtual void NotifyCanExecuteChanged() + { + } public bool IsLoading { get; set; } @@ -118,24 +108,22 @@ public void OnIsSelectedChanged() /// Invoked when the child items need to be loaded on demand. /// Subclasses can override this to populate the Children collection. /// - protected virtual Task LoadChildren() + protected virtual Task LoadChildren(CancellationToken cancellationToken) { - return Task.FromResult(null); + return Task.CompletedTask; } public TreeViewItemViewModel Parent { get; } - - public IMessenger MessengerInstance { get; } } public class TreeViewItemViewModel : TreeViewItemViewModel where TParent : TreeViewItemViewModel { - public TreeViewItemViewModel(TParent parent, IMessenger messenger, bool lazyLoadChildren) - : base(parent, messenger, lazyLoadChildren) + public TreeViewItemViewModel(TParent parent, bool lazyLoadChildren) + : base(parent, lazyLoadChildren) { } - public new TParent Parent => base.Parent as TParent; + public new TParent? Parent => base.Parent as TParent; } } diff --git a/src/CosmosDbExplorer/Infrastructure/Models/UIViewModelBase.cs b/src/CosmosDbExplorer/ViewModels/UIViewModelBase.cs similarity index 50% rename from src/CosmosDbExplorer/Infrastructure/Models/UIViewModelBase.cs rename to src/CosmosDbExplorer/ViewModels/UIViewModelBase.cs index c083b85..b5e370e 100644 --- a/src/CosmosDbExplorer/Infrastructure/Models/UIViewModelBase.cs +++ b/src/CosmosDbExplorer/ViewModels/UIViewModelBase.cs @@ -1,18 +1,15 @@ -using System; -using CosmosDbExplorer.Services; -using GalaSoft.MvvmLight; -using GalaSoft.MvvmLight.Messaging; +using CosmosDbExplorer.Contracts.Services; +using Microsoft.Toolkit.Mvvm.ComponentModel; +using Microsoft.Toolkit.Mvvm.Messaging; using PropertyChanged; -namespace CosmosDbExplorer.Infrastructure.Models +namespace CosmosDbExplorer.ViewModels { - - public abstract class UIViewModelBase : ViewModelBase + public abstract class UIViewModelBase : ObservableRecipient { private readonly IUIServices _uiServices; - protected UIViewModelBase(IMessenger messenger, IUIServices uiServices) - : base(messenger) + protected UIViewModelBase(IUIServices uiServices) { _uiServices = uiServices; } diff --git a/src/CosmosDbExplorer/ViewModels/UserEditViewModel.cs b/src/CosmosDbExplorer/ViewModels/UserEditViewModel.cs new file mode 100644 index 0000000..2842fbc --- /dev/null +++ b/src/CosmosDbExplorer/ViewModels/UserEditViewModel.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.ViewModels; +using CosmosDbExplorer.Core.Models; +using CosmosDbExplorer.Core.Services; +using CosmosDbExplorer.ViewModels.DatabaseNodes; + +using FluentValidation; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Mvvm.Input; + +using Validar; + +namespace CosmosDbExplorer.ViewModels +{ + [InjectValidation] + public class UserEditViewModel : PaneViewModel, IAssetTabCommand + { + private readonly IServiceProvider _serviceProvider; + private readonly IDialogService _dialogService; + private AsyncRelayCommand _saveCommand; + private AsyncRelayCommand _deleteCommand; + private RelayCommand _discardCommand; + private CosmosUserService _userService; + + public UserEditViewModel(IServiceProvider serviceProvider, IDialogService dialogService, IUIServices uiServices) + : base(uiServices) + { + _serviceProvider = serviceProvider; + _dialogService = dialogService; + Header = "New User"; + Title = "User"; + IconSource = App.Current.FindResource("UserIcon"); + } + + public string? UserId { get; set; } + + public void OnUserIdChanged() + { + IsDirty = UserId != Node.User.Id; + } + + private void SetInformation() + { + if (Node is null) + { + throw new NullReferenceException("Node should not be null"); + } + + UserId = Node.User.Id; + + IsDirty = false; + } + + public bool IsNewDocument => Node?.User?.SelfLink == null; + + public bool IsValid => string.IsNullOrEmpty(((IDataErrorInfo)this).Error); + + public override void Load(string contentId, UserNodeViewModel? node, CosmosConnection? connection, CosmosDatabase? database, CosmosContainer? container) + { + if (node is null) + { + throw new ArgumentNullException(nameof(node)); + } + + if (connection is null) + { + throw new ArgumentNullException(nameof(connection)); + } + + if (database is null) + { + throw new ArgumentNullException(nameof(database)); + } + + ContentId = contentId; + Node = node; + Connection = connection; + Header = node.Name ?? "New User"; + Title = "User"; + AccentColor = node.Parent.Parent.Parent.Connection.AccentColor; + ToolTip = $"{connection.Label}/{database.Id}"; + + _userService = ActivatorUtilities.CreateInstance(_serviceProvider, connection, database); + + SetInformation(); + } + + protected CosmosConnection Connection { get; set; } + + public UserNodeViewModel Node { get; protected set; } + + public ICommand DiscardCommand => _discardCommand ??= new(SetInformation, () => IsDirty); + + public ICommand SaveCommand => _saveCommand ??= new(SaveCommandExecute, () => IsDirty && IsValid); + + private async Task SaveCommandExecute() + { + CosmosUser? user; + if (IsNewDocument) + { + user = new CosmosUser { Id = UserId }; + } + else + { + user = Node.User; + user.Id = UserId; + } + + try + { + var result = await _userService.SaveUserAsync(user, new System.Threading.CancellationToken()); + + Header = result.Items.Id ?? string.Empty; + Node.User = user; + ContentId = Node.ContentId; + + OnPropertyChanged(nameof(IsNewDocument)); + Node.Parent.RefreshCommand.Execute(null); + IsDirty = false; + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "Error"); + } + } + + public ICommand DeleteCommand => _deleteCommand ??= new(DeleteCommandExecute, () => !IsNewDocument); + + private async Task DeleteCommandExecute() + { + async void deleteUser(bool confirm) + { + if (!confirm) + { + return; + } + + try + { + var result = await _userService.DeleteUserAsync(Node.User, new System.Threading.CancellationToken()); + Node.Parent.RefreshCommand.Execute(null); // Send Message? + CloseCommand.Execute(null); + } + catch (Exception ex) + { + await _dialogService.ShowError(ex, "An error occured`!"); + } + } + + await _dialogService.ShowQuestion($"Do you want to delete the user '{Node.User.Id}' ?", "Delete User", deleteUser); + } + + public bool IsDirty { get; private set; } + + protected void OnIsDirtyChanged() + { + _saveCommand?.NotifyCanExecuteChanged(); + _deleteCommand?.NotifyCanExecuteChanged(); + _discardCommand?.NotifyCanExecuteChanged(); + } + } + + public class UserEditViewModelValidator : AbstractValidator + { + public UserEditViewModelValidator() + { + RuleFor(x => x.UserId).NotEmpty(); + } + } +} diff --git a/src/CosmosDbExplorer/Views/AboutPage.xaml b/src/CosmosDbExplorer/Views/AboutPage.xaml new file mode 100644 index 0000000..8ba3743 --- /dev/null +++ b/src/CosmosDbExplorer/Views/AboutPage.xaml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/CosmosDbExplorer/Views/AboutPage.xaml.cs b/src/CosmosDbExplorer/Views/AboutPage.xaml.cs new file mode 100644 index 0000000..a8cecb8 --- /dev/null +++ b/src/CosmosDbExplorer/Views/AboutPage.xaml.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using CosmosDbExplorer.ViewModels; + +namespace CosmosDbExplorer.Views +{ + /// + /// Interaction logic for AboutView.xaml + /// + public partial class AboutPage : Page + { + public AboutPage(AboutViewModel viewModel) + { + InitializeComponent(); + DataContext = viewModel; + } + } +} diff --git a/src/CosmosDbExplorer/Views/AboutView.xaml b/src/CosmosDbExplorer/Views/AboutView.xaml deleted file mode 100644 index 550a7fe..0000000 --- a/src/CosmosDbExplorer/Views/AboutView.xaml +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/CosmosDbExplorer/Views/AboutView.xaml.cs b/src/CosmosDbExplorer/Views/AboutView.xaml.cs deleted file mode 100644 index ca44770..0000000 --- a/src/CosmosDbExplorer/Views/AboutView.xaml.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Diagnostics; -using System.Windows.Controls; -using System.Windows.Navigation; - -namespace CosmosDbExplorer.Views -{ - /// - /// Interaction logic for AboutView.xaml - /// - public partial class AboutView : UserControl - { - public AboutView() - { - InitializeComponent(); - } - - private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e) - { - Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri)); - e.Handled = true; - } - - private void Hyperlink_RequestNavigate_Github(object sender, RequestNavigateEventArgs e) - { - Process.Start(new ProcessStartInfo($"https://github.com/{e.Uri.OriginalString}")); - e.Handled = true; - } - } -} diff --git a/src/CosmosDbExplorer/Views/AccountSettingsPage.xaml b/src/CosmosDbExplorer/Views/AccountSettingsPage.xaml new file mode 100644 index 0000000..1b1b577 --- /dev/null +++ b/src/CosmosDbExplorer/Views/AccountSettingsPage.xaml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + - - + + + + @@ -114,17 +83,19 @@ - - + + + - + @@ -135,17 +106,17 @@ - - + + - + - - - + - + - - + + - - + + - - - + + + - - - - - - - - - - + DataContext="{Binding EditorViewModel}" + UseFolding="True" + ZoomLevel="{Binding Path=DataContext.Zoom, Converter={StaticResource logConverter}, RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}}" + Visibility="{Binding HasContent, Converter={StaticResource boolToVisibilityConverter}}"/> + + + + + + + + + + diff --git a/src/CosmosDbExplorer/Views/DocumentsTabView.xaml.cs b/src/CosmosDbExplorer/Views/DocumentsTabView.xaml.cs index be88e83..f530806 100644 --- a/src/CosmosDbExplorer/Views/DocumentsTabView.xaml.cs +++ b/src/CosmosDbExplorer/Views/DocumentsTabView.xaml.cs @@ -1,7 +1,17 @@ -using System.Windows; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; using System.Windows.Media; -using CosmosDbExplorer.Infrastructure.Models; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; namespace CosmosDbExplorer.Views { @@ -14,13 +24,5 @@ public DocumentsTabView() { InitializeComponent(); } - - private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) - { - if (DataContext is PaneViewModelBase datacontext) - { - datacontext.IconSource = FindResource("DocumentIcon") as ImageSource; - } - } } } diff --git a/src/CosmosDbExplorer/Views/ImportDocumentView.xaml b/src/CosmosDbExplorer/Views/ImportDocumentView.xaml index fed8b49..b238cb8 100644 --- a/src/CosmosDbExplorer/Views/ImportDocumentView.xaml +++ b/src/CosmosDbExplorer/Views/ImportDocumentView.xaml @@ -3,22 +3,23 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:vm="clr-namespace:CosmosDbExplorer.ViewModel" - xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit" - xmlns:infra="clr-namespace:CosmosDbExplorer.Infrastructure.AvalonEdit" - xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" - mc:Ignorable="d" Loaded="UserControl_Loaded" - Background="{StaticResource {x:Static SystemColors.WindowBrushKey}}" - d:DesignHeight="300" d:DesignWidth="300" d:DataContext="{x:Type vm:ImportDocumentViewModel}"> - + xmlns:local="clr-namespace:CosmosDbExplorer.Views" + xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit" + xmlns:i="http://schemas.microsoft.com/xaml/behaviors" + xmlns:behaviors="clr-namespace:CosmosDbExplorer.Behaviors" + mc:Ignorable="d" + d:DesignHeight="450" d:DesignWidth="800"> + - + + + + - + @@ -47,9 +48,7 @@ - - - + diff --git a/src/CosmosDbExplorer/Views/ImportDocumentView.xaml.cs b/src/CosmosDbExplorer/Views/ImportDocumentView.xaml.cs index 50ea770..5d465d3 100644 --- a/src/CosmosDbExplorer/Views/ImportDocumentView.xaml.cs +++ b/src/CosmosDbExplorer/Views/ImportDocumentView.xaml.cs @@ -1,14 +1,17 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; using System.Windows.Media; -using System.Windows.Threading; -using System.Xml; -using CosmosDbExplorer.Infrastructure.AvalonEdit; -using CosmosDbExplorer.Infrastructure.Models; -using ICSharpCode.AvalonEdit.Folding; -using ICSharpCode.AvalonEdit.Highlighting; -using ICSharpCode.AvalonEdit.Highlighting.Xshd; -using ICSharpCode.AvalonEdit.Indentation.CSharp; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; namespace CosmosDbExplorer.Views { @@ -17,66 +20,9 @@ namespace CosmosDbExplorer.Views /// public partial class ImportDocumentView : UserControl { - private readonly BraceFoldingStrategy _foldingStrategy = new BraceFoldingStrategy(); - private FoldingManager _foldingManager; - public ImportDocumentView() { - RegisterCustomHighlighting("JSON"); - InitializeComponent(); - - editor.TextArea.IndentationStrategy = new CSharpIndentationStrategy(editor.Options); - - var foldingUpdateTimer = new DispatcherTimer - { - Interval = TimeSpan.FromSeconds(1) - }; - - foldingUpdateTimer.Tick += FoldingUpdateTimer_Tick; - foldingUpdateTimer.Start(); - } - - private void RegisterCustomHighlighting(string name) - { - // Load our custom highlighting definition - IHighlightingDefinition customHightlighting; - using (var stream = typeof(MainWindow).Assembly.GetManifestResourceStream($"CosmosDbExplorer.Infrastructure.AvalonEdit.{name}.xshd")) - { - if (stream == null) - { - throw new InvalidOperationException("Could not find embedded resource"); - } - - using (var reader = new XmlTextReader(stream)) - { - customHightlighting = HighlightingLoader.Load(reader, HighlightingManager.Instance); - } - } - - // and register it in the HighlightingManager - HighlightingManager.Instance.RegisterHighlighting(name, new string[] { $".{name.ToLower()}" }, customHightlighting); - } - - private void FoldingUpdateTimer_Tick(object sender, EventArgs e) - { - if (_foldingManager == null && editor.TextArea?.Document?.Text != null) - { - _foldingManager = FoldingManager.Install(editor.TextArea); - } - - if (_foldingStrategy != null && _foldingManager != null) - { - _foldingStrategy.UpdateFoldings(_foldingManager, editor.Document); - } - } - - private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e) - { - if (DataContext is PaneViewModelBase datacontext) - { - datacontext.IconSource = FindResource("ImportIcon") as TextBlock; - } } } } diff --git a/src/CosmosDbExplorer/Views/JsonEditorView.xaml b/src/CosmosDbExplorer/Views/JsonEditorView.xaml index 7c3fed9..16a8b64 100644 --- a/src/CosmosDbExplorer/Views/JsonEditorView.xaml +++ b/src/CosmosDbExplorer/Views/JsonEditorView.xaml @@ -2,22 +2,27 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:CosmosDbExplorer.Views" xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit" - xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" - xmlns:controls="clr-namespace:CosmosDbExplorer.Infrastructure.AvalonEdit" + xmlns:i="http://schemas.microsoft.com/xaml/behaviors" + xmlns:behaviors="clr-namespace:CosmosDbExplorer.Behaviors" + xmlns:viewmodel="clr-namespace:CosmosDbExplorer.ViewModels" + d:DataContext="{d:DesignInstance Type=viewmodel:JsonViewerViewModel}" mc:Ignorable="d" - d:DesignHeight="300" d:DesignWidth="300" - > + d:DesignHeight="450" d:DesignWidth="800"> + - + + + + diff --git a/src/CosmosDbExplorer/Views/JsonEditorView.xaml.cs b/src/CosmosDbExplorer/Views/JsonEditorView.xaml.cs index 084d8d6..62d28f0 100644 --- a/src/CosmosDbExplorer/Views/JsonEditorView.xaml.cs +++ b/src/CosmosDbExplorer/Views/JsonEditorView.xaml.cs @@ -1,100 +1,82 @@ -using ICSharpCode.AvalonEdit.Highlighting; -using ICSharpCode.AvalonEdit.Highlighting.Xshd; -using System; -using System.Windows.Controls; -using System.Xml; -using ICSharpCode.AvalonEdit.Folding; -using CosmosDbExplorer.Infrastructure.AvalonEdit; -using System.Windows.Threading; -using System.Windows; +using System; +using System.Collections.Generic; using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; namespace CosmosDbExplorer.Views { /// - /// Interaction logic for JsonEditor.xaml + /// Interaction logic for JsonEditorView.xaml /// public partial class JsonEditorView : UserControl { - private readonly BraceFoldingStrategy _foldingStrategy = new BraceFoldingStrategy(); - private FoldingManager _foldingManager; - public JsonEditorView() { - RegisterCustomHighlighting("JSON"); - InitializeComponent(); - RoslynPad.Editor.SearchReplacePanel.Install(editor); - - var foldingUpdateTimer = new DispatcherTimer - { - Interval = TimeSpan.FromSeconds(1) - }; + } - foldingUpdateTimer.Tick += FoldingUpdateTimer_Tick; - foldingUpdateTimer.Start(); + public static readonly DependencyProperty ZoomLevelProperty = + DependencyProperty.Register("ZoomLevel", typeof(double), typeof(JsonEditorView), new PropertyMetadata(0.5d, OnZoomLevelChanged)); - Unloaded += (s, e) => - { - if (foldingUpdateTimer?.IsEnabled == true) - { - foldingUpdateTimer.Stop(); - foldingUpdateTimer = null; - } - }; + public double ZoomLevel + { + get { return (double)GetValue(ZoomLevelProperty); } + set { SetValue(ZoomLevelProperty, value); } } - private void RegisterCustomHighlighting(string name) + private static void OnZoomLevelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (!HighlightingManager.Instance.HighlightingDefinitions.Any(d => string.Equals(d.Name, name, StringComparison.OrdinalIgnoreCase))) - { - // Load our custom highlighting definition - IHighlightingDefinition customHightlighting; - using (var stream = typeof(MainWindow).Assembly.GetManifestResourceStream($"CosmosDbExplorer.Infrastructure.AvalonEdit.{name}.xshd")) - { - if (stream == null) - { - throw new InvalidOperationException("Could not find embedded resource"); - } + var value = (double)e.NewValue; + var target = (JsonEditorView)d; + target.zoomBehavior.ZoomLevel = value; + } - using (var reader = new XmlTextReader(stream)) - { - customHightlighting = HighlightingLoader.Load(reader, HighlightingManager.Instance); - } - } - // and register it in the HighlightingManager - HighlightingManager.Instance.RegisterHighlighting(name, new string[] { $".{name.ToLower()}" }, customHightlighting); - } - } - private void FoldingUpdateTimer_Tick(object sender, EventArgs e) + public bool UseFolding { - if (_foldingManager == null && editor.TextArea?.Document?.Text != null) - { - _foldingManager = FoldingManager.Install(editor.TextArea); - } + get { return (bool)GetValue(UseFoldingProperty); } + set { SetValue(UseFoldingProperty, value); } + } + + // Using a DependencyProperty as the backing store for UseFolding. This enables animation, styling, binding, etc... + public static readonly DependencyProperty UseFoldingProperty = + DependencyProperty.Register("UseFolding", typeof(bool), typeof(JsonEditorView), new PropertyMetadata(false, OnUseFoldingChanged)); - if (_foldingStrategy != null && _foldingManager != null) - { - _foldingStrategy.UpdateFoldings(_foldingManager, editor.Document); - } + private static void OnUseFoldingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var value = (bool)e.NewValue; + var target = (JsonEditorView)d; + target.foldingBehavior.UseFolding = value; } - public static readonly DependencyProperty ZoomLevelProperty = - DependencyProperty.Register("ZoomLevel", typeof(double), typeof(JsonEditorView), new PropertyMetadata(0.5d, OnZoomLevelChanged)); - public double ZoomLevel + + public int FoldingInterval { - get { return (double)GetValue(ZoomLevelProperty); } - set { SetValue(ZoomLevelProperty, value); } + get { return (int)GetValue(FoldingIntervalProperty); } + set { SetValue(FoldingIntervalProperty, value); } } - private static void OnZoomLevelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + // Using a DependencyProperty as the backing store for FoldingInterval. This enables animation, styling, binding, etc... + public static readonly DependencyProperty FoldingIntervalProperty = + DependencyProperty.Register("FoldingInterval", typeof(int), typeof(JsonEditorView), new PropertyMetadata(1000, OnFoldingIntervalChanged)); + + private static void OnFoldingIntervalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - var value = (double)e.NewValue; + var value = (int)e.NewValue; var target = (JsonEditorView)d; - target.zoomBehavior.ZoomLevel = value; + target.foldingBehavior.Interval = value; } } } diff --git a/src/CosmosDbExplorer/Views/CollectionMetricsTabView.xaml b/src/CosmosDbExplorer/Views/MetricsTabView.xaml similarity index 89% rename from src/CosmosDbExplorer/Views/CollectionMetricsTabView.xaml rename to src/CosmosDbExplorer/Views/MetricsTabView.xaml index 2a619b7..607804c 100644 --- a/src/CosmosDbExplorer/Views/CollectionMetricsTabView.xaml +++ b/src/CosmosDbExplorer/Views/MetricsTabView.xaml @@ -1,15 +1,14 @@ - + d:DesignHeight="450" d:DesignWidth="800"> - + + @@ -58,7 +59,7 @@ - + @@ -71,14 +72,14 @@ - @@ -155,7 +156,7 @@ - + - + diff --git a/src/CosmosDbExplorer/Views/MetricsTabView.xaml.cs b/src/CosmosDbExplorer/Views/MetricsTabView.xaml.cs new file mode 100644 index 0000000..a0cfa81 --- /dev/null +++ b/src/CosmosDbExplorer/Views/MetricsTabView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace CosmosDbExplorer.Views +{ + /// + /// Interaction logic for MetricsTabView.xaml + /// + public partial class MetricsTabView : UserControl + { + public MetricsTabView() + { + InitializeComponent(); + } + } +} diff --git a/src/CosmosDbExplorer/Views/Pane/PaneStyleSelector.cs b/src/CosmosDbExplorer/Views/Pane/PaneStyleSelector.cs index 0da4cc9..30fd933 100644 --- a/src/CosmosDbExplorer/Views/Pane/PaneStyleSelector.cs +++ b/src/CosmosDbExplorer/Views/Pane/PaneStyleSelector.cs @@ -1,13 +1,13 @@ using System.Windows; using System.Windows.Controls; -using CosmosDbExplorer.Infrastructure.Models; +using CosmosDbExplorer.ViewModels; namespace CosmosDbExplorer.Views.Pane { public class PaneStyleSelector : StyleSelector { - public Style ToolStyle { get; set; } - public Style DocumentStyle { get; set; } + public Style? ToolStyle { get; set; } + public Style? DocumentStyle { get; set; } public override Style SelectStyle(object item, DependencyObject container) { diff --git a/src/CosmosDbExplorer/Views/PartitionMetricChartTooltip.xaml b/src/CosmosDbExplorer/Views/PartitionMetricChartTooltip.xaml deleted file mode 100644 index aa21028..0000000 --- a/src/CosmosDbExplorer/Views/PartitionMetricChartTooltip.xaml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/CosmosDbExplorer/Views/PartitionMetricChartTooltip.xaml.cs b/src/CosmosDbExplorer/Views/PartitionMetricChartTooltip.xaml.cs deleted file mode 100644 index 4a859a5..0000000 --- a/src/CosmosDbExplorer/Views/PartitionMetricChartTooltip.xaml.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.ComponentModel; -using LiveCharts; -using LiveCharts.Wpf; - -namespace CosmosDbExplorer.Views -{ - /// - /// Interaction logic for PartitionMetricChartTooltip.xaml - /// - public partial class PartitionMetricChartTooltip : IChartTooltip - { - private TooltipData _data; - - public PartitionMetricChartTooltip() - { - InitializeComponent(); - DataContext = this; - } - - public event PropertyChangedEventHandler PropertyChanged; - - public TooltipData Data - { - get { return _data; } - set - { - _data = value; - OnPropertyChanged(nameof(Data)); - } - } - - public TooltipSelectionMode? SelectionMode { get; set; } - - protected virtual void OnPropertyChanged(string propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - } -} diff --git a/src/CosmosDbExplorer/Views/PermissionEditView.xaml b/src/CosmosDbExplorer/Views/PermissionEditView.xaml index 77d7c91..71256ff 100644 --- a/src/CosmosDbExplorer/Views/PermissionEditView.xaml +++ b/src/CosmosDbExplorer/Views/PermissionEditView.xaml @@ -3,52 +3,79 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:sdk="clr-namespace:Microsoft.Azure.Documents;assembly=Microsoft.Azure.Documents.Client" - xmlns:mkup="clr-namespace:CosmosDbExplorer.Infrastructure.MarkupExtensions" - xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" + xmlns:local="clr-namespace:CosmosDbExplorer.Views" + xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" + xmlns:mkup="clr-namespace:CosmosDbExplorer.MarkupExtensions" + xmlns:databasenodes="clr-namespace:CosmosDbExplorer.ViewModels.DatabaseNodes" + xmlns:core="clr-namespace:CosmosDbExplorer.Core.Models;assembly=CosmosDbExplorer.Core" + xmlns:viewmodels="clr-namespace:CosmosDbExplorer.ViewModels" + d:DataContext="{d:DesignInstance Type=viewmodels:PermissionEditViewModel}" mc:Ignorable="d" - Background="{StaticResource {x:Static SystemColors.WindowBrushKey}}" - d:DesignHeight="450" d:DesignWidth="800" Loaded="UserControl_Loaded"> - - - + + + + diff --git a/src/CosmosDbExplorer/Views/PermissionEditView.xaml.cs b/src/CosmosDbExplorer/Views/PermissionEditView.xaml.cs index e513e93..39a7567 100644 --- a/src/CosmosDbExplorer/Views/PermissionEditView.xaml.cs +++ b/src/CosmosDbExplorer/Views/PermissionEditView.xaml.cs @@ -1,7 +1,17 @@ -using System.Windows; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; using System.Windows.Media; -using CosmosDbExplorer.Infrastructure.Models; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; namespace CosmosDbExplorer.Views { @@ -14,11 +24,5 @@ public PermissionEditView() { InitializeComponent(); } - - private void UserControl_Loaded(object sender, RoutedEventArgs e) - { - var vm = DataContext as PaneViewModelBase; - vm.IconSource = FindResource("PermissionIcon") as ImageSource; - } } } diff --git a/src/CosmosDbExplorer/Views/QueryEditorView.xaml b/src/CosmosDbExplorer/Views/QueryEditorView.xaml index ed254e9..5b54166 100644 --- a/src/CosmosDbExplorer/Views/QueryEditorView.xaml +++ b/src/CosmosDbExplorer/Views/QueryEditorView.xaml @@ -5,15 +5,15 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:CosmosDbExplorer.Views" xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit" - xmlns:vm="clr-namespace:CosmosDbExplorer.ViewModel" - xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" - xmlns:controls="clr-namespace:CosmosDbExplorer.Infrastructure.AvalonEdit" - xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock" + xmlns:i="http://schemas.microsoft.com/xaml/behaviors" + xmlns:bhv="clr-namespace:CosmosDbExplorer.Behaviors" + xmlns:avalon="clr-namespace:CosmosDbExplorer.AvalonEdit" + xmlns:viewmodels="clr-namespace:CosmosDbExplorer.ViewModels" + xmlns:properties="clr-namespace:CosmosDbExplorer.Properties" + d:DataContext="{d:DesignInstance Type=viewmodels:QueryEditorViewModel}" mc:Ignorable="d" - Background="{StaticResource {x:Static SystemColors.WindowBrushKey}}" - d:DesignHeight="600" d:DesignWidth="900" Loaded="UserControl_Loaded" - d:DataContext="{x:Type vm:QueryEditorViewModel}"> - + Style="{DynamicResource CosmosUserControl}" + d:DesignHeight="450" d:DesignWidth="800"> @@ -22,28 +22,29 @@ - - - - - - - + + + + + + + Style="{StaticResource CosmosEditor}" + IsModified="{Binding IsDirty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> - - - + + + - - + + + + @@ -71,14 +72,14 @@ @@ -90,73 +91,79 @@ - - - - + + + + - - + + - - + - - - - - - - - - + UseFolding="True" + ZoomLevel="{Binding Path=DataContext.Zoom, Converter={StaticResource logConverter}, RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}}" /> + + + + + + + + + + - + + Style="{StaticResource CosmosEditor}" + HorizontalScrollBarVisibility="Auto" > - - + + + + - + - + + UseFolding="False" + ZoomLevel="{Binding Path=DataContext.Zoom, Converter={StaticResource logConverter}, RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}}" /> - + - - - + + + - - + + - + diff --git a/src/CosmosDbExplorer/Views/QueryEditorView.xaml.cs b/src/CosmosDbExplorer/Views/QueryEditorView.xaml.cs index 97575f4..dfca9ed 100644 --- a/src/CosmosDbExplorer/Views/QueryEditorView.xaml.cs +++ b/src/CosmosDbExplorer/Views/QueryEditorView.xaml.cs @@ -1,56 +1,49 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; using System.Windows.Media; -using System.Xml; -using CosmosDbExplorer.Infrastructure.Models; -using ICSharpCode.AvalonEdit.Highlighting; -using ICSharpCode.AvalonEdit.Highlighting.Xshd; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +using CosmosDbExplorer.Properties; namespace CosmosDbExplorer.Views { /// - /// Interaction logic for QueryEditorView.xaml + /// Interaction logic for QueryEditor.xaml /// public partial class QueryEditorView : UserControl { public QueryEditorView() { - RegisterCustomHighlighting("DocumentDbSql"); InitializeComponent(); - // https://stackoverflow.com/a/1066009/20761 - NameScope.SetNameScope(editorContextMenu, NameScope.GetNameScope(this)); + Properties.Settings.Default.PropertyChanged += Default_PropertyChanged; + DefineGestures(); } - private void RegisterCustomHighlighting(string name) + private void Default_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) { - // Load our custom highlighting definition - IHighlightingDefinition customHightlighting; - using (var stream = typeof(MainWindow).Assembly.GetManifestResourceStream($"CosmosDbExplorer.Infrastructure.AvalonEdit.{name}.xshd")) - { - if (stream == null) - { - throw new InvalidOperationException("Could not find embedded resource"); - } - - using (var reader = new XmlTextReader(stream)) - { - customHightlighting = HighlightingLoader.Load(reader, HighlightingManager.Instance); - } - } + DefineGestures(); + } - // and register it in the HighlightingManager - HighlightingManager.Instance.RegisterHighlighting(name, new string[] { $".{name.ToLower()}" }, customHightlighting); + private static KeyGesture? GetGesture(string gesture) + { + var converter = new KeyGestureConverter(); + return converter.ConvertFromString(gesture) as KeyGesture; } - private void UserControl_Loaded(object sender, RoutedEventArgs e) + private void DefineGestures() { - if (DataContext is PaneViewModelBase datacontext) - { - datacontext.IconSource = FindResource("SqlQueryIcon") as ImageSource; - } + ExecuteKeyBinding.Gesture = GetGesture(Properties.Settings.Default.ExecuteGesture); } } } diff --git a/src/CosmosDbExplorer/Views/ScaleAndSettingsTabView.xaml b/src/CosmosDbExplorer/Views/ScaleAndSettingsTabView.xaml deleted file mode 100644 index c568f62..0000000 --- a/src/CosmosDbExplorer/Views/ScaleAndSettingsTabView.xaml +++ /dev/null @@ -1,293 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/CosmosDbExplorer/Views/ScaleAndSettingsTabView.xaml.cs b/src/CosmosDbExplorer/Views/ScaleAndSettingsTabView.xaml.cs deleted file mode 100644 index 6c7e682..0000000 --- a/src/CosmosDbExplorer/Views/ScaleAndSettingsTabView.xaml.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Windows.Controls; -using System.Windows.Media; -using System.Windows.Threading; -using System.Xml; -using CosmosDbExplorer.Infrastructure.AvalonEdit; -using CosmosDbExplorer.ViewModel; -using ICSharpCode.AvalonEdit.Folding; -using ICSharpCode.AvalonEdit.Highlighting; -using ICSharpCode.AvalonEdit.Highlighting.Xshd; - -namespace CosmosDbExplorer.Views -{ - /// - /// Interaction logic for ScaleAndSettingsTabView.xaml - /// - public partial class ScaleAndSettingsTabView : UserControl - { - private readonly BraceFoldingStrategy _foldingStrategy = new BraceFoldingStrategy(); - private FoldingManager _foldingManager; - - public ScaleAndSettingsTabView() - { - RegisterCustomHighlighting("JSON"); - - InitializeComponent(); - - RoslynPad.Editor.SearchReplacePanel.Install(editor); - - var foldingUpdateTimer = new DispatcherTimer - { - Interval = TimeSpan.FromSeconds(1) - }; - - foldingUpdateTimer.Tick += FoldingUpdateTimer_Tick; - //foldingUpdateTimer.Start(); - } - - private void RegisterCustomHighlighting(string name) - { - // Load our custom highlighting definition - IHighlightingDefinition customHightlighting; - using (var stream = typeof(MainWindow).Assembly.GetManifestResourceStream($"CosmosDbExplorer.Infrastructure.AvalonEdit.{name}.xshd")) - { - if (stream == null) - { - throw new InvalidOperationException("Could not find embedded resource"); - } - - using (var reader = new XmlTextReader(stream)) - { - customHightlighting = HighlightingLoader.Load(reader, HighlightingManager.Instance); - } - } - - // and register it in the HighlightingManager - HighlightingManager.Instance.RegisterHighlighting(name, new string[] { $".{name.ToLower()}" }, customHightlighting); - } - - private void FoldingUpdateTimer_Tick(object sender, EventArgs e) - { - if (_foldingManager == null && editor.TextArea?.Document?.Text != null) - { - _foldingManager = FoldingManager.Install(editor.TextArea); - } - - if (_foldingStrategy != null && _foldingManager != null) - { - _foldingStrategy.UpdateFoldings(_foldingManager, editor.Document); - } - } - - private async void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e) - { - var vm = DataContext as ScaleAndSettingsTabViewModel; - vm.IconSource = FindResource("ScaleSettingsIcon") as ImageSource; - await vm.LoadDataAsync().ConfigureAwait(false); - } - } -} diff --git a/src/CosmosDbExplorer/Views/SettingsPage.xaml b/src/CosmosDbExplorer/Views/SettingsPage.xaml new file mode 100644 index 0000000..ab7fdd7 --- /dev/null +++ b/src/CosmosDbExplorer/Views/SettingsPage.xaml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CosmosDbExplorer/Views/ShellWindow.xaml.cs b/src/CosmosDbExplorer/Views/ShellWindow.xaml.cs new file mode 100644 index 0000000..15c9cb3 --- /dev/null +++ b/src/CosmosDbExplorer/Views/ShellWindow.xaml.cs @@ -0,0 +1,102 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Threading; + +using AutoUpdaterDotNET; + +using CosmosDbExplorer.Behaviors; +using CosmosDbExplorer.Contracts.Services; +using CosmosDbExplorer.Contracts.Views; +using CosmosDbExplorer.Properties; +using CosmosDbExplorer.ViewModels; + +using Fluent; + +using MahApps.Metro.Controls; + +using Newtonsoft.Json; + +namespace CosmosDbExplorer.Views +{ + public partial class ShellWindow : MetroWindow, IShellWindow, IRibbonWindow + { + public RibbonTitleBar TitleBar + { + get => (RibbonTitleBar)GetValue(TitleBarProperty); + private set => SetValue(TitleBarPropertyKey, value); + } + + private static readonly DependencyPropertyKey TitleBarPropertyKey = DependencyProperty.RegisterReadOnly(nameof(TitleBar), typeof(RibbonTitleBar), typeof(ShellWindow), new PropertyMetadata()); + + public static readonly DependencyProperty TitleBarProperty = TitleBarPropertyKey.DependencyProperty; + + public ShellWindow(IPageService pageService, ShellViewModel viewModel) + { + InitializeComponent(); + DataContext = viewModel; + navigationBehavior.Initialize(pageService); + } + + //public Frame GetNavigationFrame() + // => shellFrame; + + public RibbonTabsBehavior GetRibbonTabsBehavior() + => tabsBehavior; + + public Frame GetRightPaneFrame() + => rightPaneFrame; + + public SplitView GetSplitView() + => splitView; + + public void ShowWindow() + => Show(); + + public void CloseWindow() + => Close(); + + private void OnLoaded(object sender, RoutedEventArgs e) + { + var window = sender as MetroWindow; + TitleBar = window.FindChild("RibbonTitleBar"); + TitleBar.InvalidateArrange(); + TitleBar.UpdateLayout(); + + AutoUpdater.Start(Settings.Default.AutoUpdaterUrl); + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + tabsBehavior.Unsubscribe(); + } + + private void SetupAutoUpdater() + { + // Allow user to be reminded to update in 1 day + AutoUpdater.ShowRemindLaterButton = true; + AutoUpdater.LetUserSelectRemindLater = false; + AutoUpdater.RemindLaterTimeSpan = RemindLaterFormat.Days; + AutoUpdater.RemindLaterAt = 1; + + AutoUpdater.ReportErrors = false; + AutoUpdater.RunUpdateAsAdmin = false; + AutoUpdater.DownloadPath = Environment.CurrentDirectory; + AutoUpdater.ParseUpdateInfoEvent += AutoUpdateOnParseUpdateInfoEvent; + } + + private void AutoUpdateOnParseUpdateInfoEvent(ParseUpdateInfoEventArgs args) + { + // Use JSON format for AutoUpdate release information file + dynamic json = JsonConvert.DeserializeObject(args.RemoteData); + args.UpdateInfo = new UpdateInfoEventArgs + { + CurrentVersion = json.version, + ChangelogURL = json.changelog, + Mandatory = new Mandatory { Value = json.mandatory }, + DownloadURL = json.url, + CheckSum = json.checksum != null ? new CheckSum { Value = json.checksum } : null + }; + } + } +} diff --git a/src/CosmosDbExplorer/Views/StoredProcedureTabView.xaml b/src/CosmosDbExplorer/Views/StoredProcedureTabView.xaml index ffc7560..1919cc8 100644 --- a/src/CosmosDbExplorer/Views/StoredProcedureTabView.xaml +++ b/src/CosmosDbExplorer/Views/StoredProcedureTabView.xaml @@ -3,64 +3,66 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" - xmlns:controls="clr-namespace:CosmosDbExplorer.Infrastructure.AvalonEdit" - xmlns:vm="clr-namespace:CosmosDbExplorer.ViewModel.Assets" - xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit" - xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" - xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock" - xmlns:views="clr-namespace:CosmosDbExplorer.Views" - xmlns:markup="clr-namespace:CosmosDbExplorer.Infrastructure.MarkupExtensions" - xmlns:templateselectors="clr-namespace:CosmosDbExplorer.Infrastructure.TemplateSelectors" - mc:Ignorable="d" Loaded="UserControl_Loaded" - d:DesignHeight="600" d:DesignWidth="800" d:DataContext="{x:Type vm:StoredProcedureTabViewModel}" - Background="{StaticResource {x:Static SystemColors.WindowBrushKey}}"> - + xmlns:local="clr-namespace:CosmosDbExplorer.Views" + xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" + xmlns:i="http://schemas.microsoft.com/xaml/behaviors" + xmlns:bhv="clr-namespace:CosmosDbExplorer.Behaviors" + xmlns:templateselectors="clr-namespace:CosmosDbExplorer.TemplateSelectors" + xmlns:assets="clr-namespace:CosmosDbExplorer.ViewModels.Assets" + xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit" + xmlns:markup="clr-namespace:CosmosDbExplorer.MarkupExtensions" + xmlns:vm="clr-namespace:CosmosDbExplorer.ViewModels.Assets" + d:DataContext="{d:DesignInstance Type=assets:StoredProcedureTabViewModel}" + mc:Ignorable="d" + Style="{DynamicResource CosmosUserControl}" + d:DesignHeight="450" d:DesignWidth="800"> - + - - - - + + + - + - - - - + + + + - + + + - - - - + + + + - + - - - + + @@ -74,13 +76,14 @@ Visibility="{Binding IsCollectionPartitioned, Converter={StaticResource boolToVisibilityConverter}}"> + + - - + - - - - + + - + - - - - + - - - - + + + + diff --git a/src/CosmosDbExplorer/Views/StoredProcedureTabView.xaml.cs b/src/CosmosDbExplorer/Views/StoredProcedureTabView.xaml.cs index 70c9b54..faf6a51 100644 --- a/src/CosmosDbExplorer/Views/StoredProcedureTabView.xaml.cs +++ b/src/CosmosDbExplorer/Views/StoredProcedureTabView.xaml.cs @@ -1,11 +1,17 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; using System.Windows.Media; -using System.Windows.Threading; -using CosmosDbExplorer.Infrastructure.AvalonEdit; -using CosmosDbExplorer.Infrastructure.Models; -using ICSharpCode.AvalonEdit.Folding; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; namespace CosmosDbExplorer.Views { @@ -14,52 +20,9 @@ namespace CosmosDbExplorer.Views /// public partial class StoredProcedureTabView : UserControl { - private readonly BraceFoldingStrategy _foldingStrategy = new BraceFoldingStrategy(); - private FoldingManager _foldingManager; - public StoredProcedureTabView() { InitializeComponent(); - - RoslynPad.Editor.SearchReplacePanel.Install(editor); - - var foldingUpdateTimer = new DispatcherTimer - { - Interval = TimeSpan.FromSeconds(1) - }; - - foldingUpdateTimer.Tick += FoldingUpdateTimer_Tick; - //foldingUpdateTimer.Start(); - } - - private void FoldingUpdateTimer_Tick(object sender, EventArgs e) - { - try - { - - if (_foldingManager == null && editor.TextArea?.Document?.Text != null) - { - _foldingManager = FoldingManager.Install(editor.TextArea); - } - - if (_foldingStrategy != null && _foldingManager != null && editor.Document != null) - { - _foldingStrategy.UpdateFoldings(_foldingManager, editor.Document); - } - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine("FoldingUpdateTimer_Tick Exception: " + ex.Message); - // silently fails - } - } - - private void UserControl_Loaded(object sender, RoutedEventArgs e) - { - if (DataContext is PaneViewModelBase datacontext) - { - datacontext.IconSource = FindResource("StoredProcedureIcon") as ImageSource; - } } } } diff --git a/src/CosmosDbExplorer/Views/TriggerTabView.xaml b/src/CosmosDbExplorer/Views/TriggerTabView.xaml index f1508cb..f1102b0 100644 --- a/src/CosmosDbExplorer/Views/TriggerTabView.xaml +++ b/src/CosmosDbExplorer/Views/TriggerTabView.xaml @@ -3,45 +3,50 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:vm="clr-namespace:CosmosDbExplorer.ViewModel.Assets" + xmlns:local="clr-namespace:CosmosDbExplorer.Views" + xmlns:assets="clr-namespace:CosmosDbExplorer.ViewModels.Assets" + xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" + xmlns:bhv="clr-namespace:CosmosDbExplorer.Behaviors" + xmlns:i="http://schemas.microsoft.com/xaml/behaviors" + xmlns:markup="clr-namespace:CosmosDbExplorer.MarkupExtensions" xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit" - xmlns:markup="clr-namespace:CosmosDbExplorer.Infrastructure.MarkupExtensions" - xmlns:db="clr-namespace:Microsoft.Azure.Documents;assembly=Microsoft.Azure.Documents.Client" - xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" - xmlns:controls="clr-namespace:CosmosDbExplorer.Infrastructure.AvalonEdit" - xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" - mc:Ignorable="d" Loaded="UserControl_Loaded" - Background="{StaticResource {x:Static SystemColors.WindowBrushKey}}" - d:DesignHeight="300" d:DesignWidth="300" d:DataContext="{x:Type vm:TriggerTabViewModel}"> + xmlns:core="clr-namespace:CosmosDbExplorer.Core.Models;assembly=CosmosDbExplorer.Core" + d:DataContext="{d:DesignInstance Type=assets:TriggerTabViewModel}" + Style="{DynamicResource CosmosUserControl}" + mc:Ignorable="d" + d:DesignHeight="450" d:DesignWidth="800"> - + - + + + diff --git a/src/CosmosDbExplorer/Views/TriggerTabView.xaml.cs b/src/CosmosDbExplorer/Views/TriggerTabView.xaml.cs index 5dcc8e8..352014a 100644 --- a/src/CosmosDbExplorer/Views/TriggerTabView.xaml.cs +++ b/src/CosmosDbExplorer/Views/TriggerTabView.xaml.cs @@ -1,56 +1,28 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; using System.Windows.Media; -using System.Windows.Threading; -using CosmosDbExplorer.Infrastructure.AvalonEdit; -using CosmosDbExplorer.Infrastructure.Models; -using ICSharpCode.AvalonEdit.Folding; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; namespace CosmosDbExplorer.Views { /// - /// Interaction logic for TriggerTabView.xaml + /// Interaction logic for TriggerTab.xaml /// public partial class TriggerTabView : UserControl { - private readonly BraceFoldingStrategy _foldingStrategy = new BraceFoldingStrategy(); - private FoldingManager _foldingManager; - public TriggerTabView() { InitializeComponent(); - - RoslynPad.Editor.SearchReplacePanel.Install(editor); - - var foldingUpdateTimer = new DispatcherTimer - { - Interval = TimeSpan.FromSeconds(1) - }; - - foldingUpdateTimer.Tick += FoldingUpdateTimer_Tick; - //foldingUpdateTimer.Start(); - } - - private void FoldingUpdateTimer_Tick(object sender, EventArgs e) - { - if (_foldingManager == null && editor.TextArea?.Document?.Text != null) - { - _foldingManager = FoldingManager.Install(editor.TextArea); - } - - if (_foldingStrategy != null && _foldingManager != null) - { - _foldingStrategy.UpdateFoldings(_foldingManager, editor.Document); - } - } - - private void UserControl_Loaded(object sender, RoutedEventArgs e) - { - if (DataContext is PaneViewModelBase datacontext) - { - datacontext.IconSource = FindResource("TriggerIcon") as ImageSource; - } } } } diff --git a/src/CosmosDbExplorer/Views/UserDefFuncTabView.xaml b/src/CosmosDbExplorer/Views/UserDefFuncTabView.xaml index db1f7a9..059e08a 100644 --- a/src/CosmosDbExplorer/Views/UserDefFuncTabView.xaml +++ b/src/CosmosDbExplorer/Views/UserDefFuncTabView.xaml @@ -3,33 +3,40 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" - xmlns:controls="clr-namespace:CosmosDbExplorer.Infrastructure.AvalonEdit" - xmlns:vm="clr-namespace:CosmosDbExplorer.ViewModel.Assets" - xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit" - xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" - mc:Ignorable="d" Loaded="UserControl_Loaded" - Background="{StaticResource {x:Static SystemColors.WindowBrushKey}}" - d:DesignHeight="300" d:DesignWidth="300" d:DataContext="{x:Type vm:UserDefFuncTabViewModel}"> + xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" + xmlns:bhv="clr-namespace:CosmosDbExplorer.Behaviors" + xmlns:local="clr-namespace:CosmosDbExplorer.Views" + xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit" + xmlns:assets="clr-namespace:CosmosDbExplorer.ViewModels.Assets" + xmlns:i="http://schemas.microsoft.com/xaml/behaviors" + xmlns:controls="clr-namespace:CosmosDbExplorer.Behaviors" + d:DataContext="{d:DesignInstance Type=assets:UserDefFuncTabViewModel}" + mc:Ignorable="d" + Style="{DynamicResource CosmosUserControl}" + d:DesignHeight="450" d:DesignWidth="800"> - + + + diff --git a/src/CosmosDbExplorer/Views/UserDefFuncTabView.xaml.cs b/src/CosmosDbExplorer/Views/UserDefFuncTabView.xaml.cs index 18e43ce..273c541 100644 --- a/src/CosmosDbExplorer/Views/UserDefFuncTabView.xaml.cs +++ b/src/CosmosDbExplorer/Views/UserDefFuncTabView.xaml.cs @@ -1,11 +1,17 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; using System.Windows.Media; -using System.Windows.Threading; -using CosmosDbExplorer.Infrastructure.AvalonEdit; -using CosmosDbExplorer.Infrastructure.Models; -using ICSharpCode.AvalonEdit.Folding; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; namespace CosmosDbExplorer.Views { @@ -14,43 +20,9 @@ namespace CosmosDbExplorer.Views /// public partial class UserDefFuncTabView : UserControl { - private readonly BraceFoldingStrategy _foldingStrategy = new BraceFoldingStrategy(); - private FoldingManager _foldingManager; - public UserDefFuncTabView() { InitializeComponent(); - - RoslynPad.Editor.SearchReplacePanel.Install(editor); - - var foldingUpdateTimer = new DispatcherTimer - { - Interval = TimeSpan.FromSeconds(1) - }; - - foldingUpdateTimer.Tick += FoldingUpdateTimer_Tick; - //foldingUpdateTimer.Start(); - } - - private void FoldingUpdateTimer_Tick(object sender, EventArgs e) - { - if (_foldingManager == null && editor.TextArea?.Document?.Text != null) - { - _foldingManager = FoldingManager.Install(editor.TextArea); - } - - if (_foldingStrategy != null && _foldingManager != null) - { - _foldingStrategy.UpdateFoldings(_foldingManager, editor.Document); - } - } - - private void UserControl_Loaded(object sender, RoutedEventArgs e) - { - if (DataContext is PaneViewModelBase datacontext) - { - datacontext.IconSource = FindResource("UdfIcon") as ImageSource; - } } } } diff --git a/src/CosmosDbExplorer/Views/UserEditView.xaml b/src/CosmosDbExplorer/Views/UserEditView.xaml index b62b3f8..77ebb36 100644 --- a/src/CosmosDbExplorer/Views/UserEditView.xaml +++ b/src/CosmosDbExplorer/Views/UserEditView.xaml @@ -3,16 +3,24 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="clr-namespace:CosmosDbExplorer.Views" - xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" + xmlns:local="clr-namespace:CosmosDbExplorer.Views" xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" xmlns:viewmodels="clr-namespace:CosmosDbExplorer.ViewModels" d:DataContext="{d:DesignInstance Type=viewmodels:UserEditViewModel}" mc:Ignorable="d" - Background="{StaticResource {x:Static SystemColors.WindowBrushKey}}" - d:DesignHeight="450" d:DesignWidth="800" Loaded="UserControl_Loaded"> - + d:DesignHeight="450" d:DesignWidth="800"> + + + + + + + + Unique user identifier + True + + + - diff --git a/src/CosmosDbExplorer/Views/UserEditView.xaml.cs b/src/CosmosDbExplorer/Views/UserEditView.xaml.cs index 41189fd..8668afe 100644 --- a/src/CosmosDbExplorer/Views/UserEditView.xaml.cs +++ b/src/CosmosDbExplorer/Views/UserEditView.xaml.cs @@ -1,7 +1,17 @@ -using System.Windows; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; using System.Windows.Media; -using CosmosDbExplorer.Infrastructure.Models; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; namespace CosmosDbExplorer.Views { @@ -14,11 +24,5 @@ public UserEditView() { InitializeComponent(); } - - private void UserControl_Loaded(object sender, RoutedEventArgs e) - { - var vm = DataContext as PaneViewModelBase; - vm.IconSource = FindResource("UserIcon") as ImageSource; - } } } diff --git a/src/CosmosDbExplorer/WTS.ProjectConfig.xml b/src/CosmosDbExplorer/WTS.ProjectConfig.xml new file mode 100644 index 0000000..094780b --- /dev/null +++ b/src/CosmosDbExplorer/WTS.ProjectConfig.xml @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/CosmosDbExplorer/app.manifest b/src/CosmosDbExplorer/app.manifest new file mode 100644 index 0000000..2742b15 --- /dev/null +++ b/src/CosmosDbExplorer/app.manifest @@ -0,0 +1,11 @@ + + + + + + + true/PM + PerMonitorV2, PerMonitor + + + \ No newline at end of file diff --git a/src/CosmosDbExplorer/appsettings.json b/src/CosmosDbExplorer/appsettings.json new file mode 100644 index 0000000..b8bdcc5 --- /dev/null +++ b/src/CosmosDbExplorer/appsettings.json @@ -0,0 +1,6 @@ +{ + "AppConfig": { + "appPropertiesFileName": "AppProperties.json", + "connectionsFileName": "connection-settings.json" + } +} diff --git a/src/MigrationBackup/bc6d5713/CosmosDbExplorer/CosmosDbExplorer.csproj b/src/MigrationBackup/bc6d5713/CosmosDbExplorer/CosmosDbExplorer.csproj deleted file mode 100644 index 78c0b84..0000000 --- a/src/MigrationBackup/bc6d5713/CosmosDbExplorer/CosmosDbExplorer.csproj +++ /dev/null @@ -1,576 +0,0 @@ - - - - - - - Debug - AnyCPU - {5AF0B2F1-B905-46CF-B493-71B1950A96A4} - WinExe - CosmosDbExplorer - CosmosDbExplorer - v4.7.2 - 512 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 4 - true - - - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - AnyCPU - pdbonly - false - bin\Release\ - TRACE - prompt - 4 - false - false - - - astronaut.ico - - - 9F62A3926A5B227285B516DBEC4075DCACB56EC9 - - - DocumentDbExplorer_TemporaryKey.pfx - - - false - - - false - - - LocalIntranet - - - Properties\app.manifest - - - - ..\packages\Autoupdater.NET.Official.1.5.1\lib\net40\AutoUpdater.NET.dll - - - ..\packages\CommonServiceLocator.2.0.4\lib\net47\CommonServiceLocator.dll - - - ..\packages\ControlzEx.3.0.2.4\lib\net462\ControlzEx.dll - - - ..\packages\Fluent.Ribbon.6.1.0.326\lib\net462\Fluent.dll - - - ..\packages\FluentValidation.8.1.3\lib\net45\FluentValidation.dll - - - ..\packages\MvvmLightLibs.5.4.1.1\lib\net45\GalaSoft.MvvmLight.dll - - - ..\packages\MvvmLightLibs.5.4.1.1\lib\net45\GalaSoft.MvvmLight.Extras.dll - - - ..\packages\MvvmLightLibs.5.4.1.1\lib\net45\GalaSoft.MvvmLight.Platform.dll - - - ..\packages\gong-wpf-dragdrop.1.1.0\lib\net46\GongSolutions.Wpf.DragDrop.dll - - - ..\packages\AvalonEdit.5.0.4\lib\Net40\ICSharpCode.AvalonEdit.dll - - - ..\packages\LiveCharts.0.9.7\lib\net45\LiveCharts.dll - - - ..\packages\LiveCharts.Wpf.0.9.7\lib\net45\LiveCharts.Wpf.dll - - - ..\packages\Microsoft.Azure.DocumentDB.2.2.3\lib\net461\Microsoft.Azure.Documents.Client.dll - - - ..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll - - - ..\packages\PropertyChanged.Fody.2.6.1\lib\net452\PropertyChanged.dll - - - - ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll - - - ..\packages\System.ComponentModel.Annotations.4.5.0\lib\net461\System.ComponentModel.Annotations.dll - - - - - ..\packages\System.ComponentModel.Primitives.4.3.0\lib\net45\System.ComponentModel.Primitives.dll - - - - ..\packages\System.IO.4.3.0\lib\net462\System.IO.dll - True - True - - - ..\packages\System.Memory.4.5.2\lib\netstandard2.0\System.Memory.dll - - - ..\packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll - True - True - - - ..\packages\System.Net.Http.WinHttpHandler.4.5.2\lib\net461\System.Net.Http.WinHttpHandler.dll - - - - ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll - - - ..\packages\System.Reactive.4.1.3\lib\net46\System.Reactive.dll - - - ..\packages\System.Reactive.Core.4.1.3\lib\net46\System.Reactive.Core.dll - - - ..\packages\System.Reactive.Interfaces.4.1.3\lib\net46\System.Reactive.Interfaces.dll - - - ..\packages\System.Reactive.Linq.4.1.3\lib\net46\System.Reactive.Linq.dll - - - ..\packages\System.Reactive.PlatformServices.4.1.3\lib\net46\System.Reactive.PlatformServices.dll - - - ..\packages\System.Reactive.Windows.Threading.4.1.3\lib\net46\System.Reactive.Windows.Threading.dll - - - False - False - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - ..\packages\System.Security.Cryptography.Algorithms.4.3.1\lib\net463\System.Security.Cryptography.Algorithms.dll - True - True - - - ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll - True - True - - - ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - True - True - - - ..\packages\System.Security.Cryptography.X509Certificates.4.3.2\lib\net461\System.Security.Cryptography.X509Certificates.dll - True - True - - - ..\packages\System.Threading.Tasks.Extensions.4.5.2\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll - - - ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll - - - - - ..\packages\MvvmLightLibs.5.4.1.1\lib\net45\System.Windows.Interactivity.dll - - - - - - - - 4.0 - - - ..\packages\Validar.Fody.1.7.4\lib\net452\Validar.dll - - - - - - ..\packages\WpfAnimatedGif.1.4.18\lib\net\WpfAnimatedGif.dll - - - ..\packages\Extended.Wpf.Toolkit.3.4.0\lib\net40\Xceed.Wpf.AvalonDock.dll - - - ..\packages\Extended.Wpf.Toolkit.3.4.0\lib\net40\Xceed.Wpf.AvalonDock.Themes.Aero.dll - - - ..\packages\Extended.Wpf.Toolkit.3.4.0\lib\net40\Xceed.Wpf.AvalonDock.Themes.Metro.dll - - - ..\packages\Extended.Wpf.Toolkit.3.4.0\lib\net40\Xceed.Wpf.AvalonDock.Themes.VS2010.dll - - - ..\packages\Extended.Wpf.Toolkit.3.4.0\lib\net40\Xceed.Wpf.DataGrid.dll - - - ..\packages\Extended.Wpf.Toolkit.3.4.0\lib\net40\Xceed.Wpf.Toolkit.dll - - - - - MSBuild:Compile - Designer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - AboutView.xaml - - - AccountSettingsView.xaml - - - AddCollectionView.xaml - - - CollectionMetricsTabView.xaml - - - DatabaseView.xaml - - - DocumentsTabView.xaml - - - ImportDocumentView.xaml - - - JsonEditorView.xaml - - - - PartitionMetricChartTooltip.xaml - - - PermissionEditView.xaml - - - QueryEditorView.xaml - - - ScaleAndSettingsTabView.xaml - - - StoredProcedureTabView.xaml - - - TriggerTabView.xaml - - - UserDefFuncTabView.xaml - - - UserEditView.xaml - - - MSBuild:Compile - Designer - - - App.xaml - Code - - - - MainWindow.xaml - Code - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - - - Code - - - True - True - Resources.resx - - - True - Settings.settings - True - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - - Designer - - - - - - - - - - - - - - False - Microsoft .NET Framework 4.7 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - \ No newline at end of file diff --git a/src/MigrationBackup/bc6d5713/CosmosDbExplorer/NuGetUpgradeLog.html b/src/MigrationBackup/bc6d5713/CosmosDbExplorer/NuGetUpgradeLog.html deleted file mode 100644 index 90c64bc..0000000 --- a/src/MigrationBackup/bc6d5713/CosmosDbExplorer/NuGetUpgradeLog.html +++ /dev/null @@ -1,202 +0,0 @@ - - - - - NuGetMigrationLog -

- NuGet Migration Report - CosmosDbExplorer

Overview

Migration to PackageReference was completed successfully. Please build and run your solution to verify that all packages are available.
- If you run into any problems, have feedback, questions, or concerns, please - file an issue on the NuGet GitHub repository.
- Changed files and this report have been backed up here: - D:\Sources\CosmosDbExplorer\work\src\MigrationBackup\bc6d5713\CosmosDbExplorer

Packages processed

Top-level dependencies:

Package IdVersion
Autoupdater.NET.Official - v1.5.1
AvalonEdit - v5.0.4
CommonServiceLocator - v2.0.4
Expression.Blend.Sdk - v1.0.2
Extended.Wpf.Toolkit - v3.4.0
Fluent.Ribbon - v6.1.0.326
FluentValidation - v8.1.3
Fody - v4.2.1
gong-wpf-dragdrop - v1.1.0
LiveCharts.Wpf - v0.9.7
Microsoft.Azure.DocumentDB - v2.2.3
MvvmLight - v5.4.1.1
Newtonsoft.Json - v12.0.1
PropertyChanged.Fody - v2.6.1
System.ComponentModel.Annotations - v4.5.0
System.Memory - v4.5.2
System.Net.Http.WinHttpHandler - v4.5.2
System.Numerics.Vectors - v4.5.0
System.Reactive.Core - v4.1.3
System.Reactive.Interfaces - v4.1.3
System.Reactive.Linq - v4.1.3
System.Reactive.PlatformServices - v4.1.3
System.Reactive.Windows.Threading - v4.1.3
System.Runtime - v4.3.1
System.Security.Cryptography.Algorithms - v4.3.1
System.Security.Cryptography.X509Certificates - v4.3.2
Validar.Fody - v1.7.4
WpfAnimatedGif - v1.4.18

Transitive dependencies:

Package IdVersion
ControlzEx - v3.0.2.4
LiveCharts - v0.9.7
MvvmLightLibs - v5.4.1.1
System.Buffers - v4.5.0
System.Collections.Concurrent - v4.3.0
System.ComponentModel.Primitives - v4.3.0
System.IO - v4.3.0
System.Net.Http - v4.3.4
System.Reactive - v4.1.3
System.Runtime.CompilerServices.Unsafe - v4.5.2
System.Security.Cryptography.Encoding - v4.3.0
System.Security.Cryptography.Primitives - v4.3.0
System.Threading.Tasks.Extensions - v4.5.2
System.ValueTuple - v4.5.0

Package compatibility issues

Description
Extended.Wpf.Toolkit - v3.4.0
install.ps1 script will be ignored when the package is installed after the migration.
LiveCharts.Wpf - v0.9.7
install.ps1 script will be ignored when the package is installed after the migration.
MvvmLight - v5.4.1.1
install.ps1 script will be ignored when the package is installed after the migration.
'content' assets will not be available when the package is installed after the migration.
\ No newline at end of file diff --git a/src/MigrationBackup/bc6d5713/CosmosDbExplorer/packages.config b/src/MigrationBackup/bc6d5713/CosmosDbExplorer/packages.config deleted file mode 100644 index 3a33648..0000000 --- a/src/MigrationBackup/bc6d5713/CosmosDbExplorer/packages.config +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file