diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f7682a1b..bf728d87 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ permissions: on: push: - branches: [ master, viewer-take-2 ] + branches: [ master ] pull_request: branches: [ master ] @@ -113,8 +113,8 @@ jobs: LATEST_NET_ONLY: true run: | envsubst < src/Parquet/Globals.cs > g.tmp && mv g.tmp src/Parquet/Globals.cs - dotnet restore src/Parquet.Floor.Desktop/Parquet.Floor.Desktop.csproj - dotnet publish src/Parquet.Floor.Desktop/Parquet.Floor.Desktop.csproj -c release -r ${{ matrix.rid }} -o floor-pub/${{ matrix.rid }} /p:Version=${{ env.VERSION }} /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.ASM_VERSION }} + dotnet restore src/Parquet.Floor/Parquet.Floor.csproj + dotnet publish src/Parquet.Floor/Parquet.Floor.csproj -c release -r ${{ matrix.rid }} -o floor-pub/${{ matrix.rid }} /p:Version=${{ env.VERSION }} /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.ASM_VERSION }} mkdir -p floor-dist zip -r -9 -j floor-dist/floor-${{ matrix.rid }}-${{ env.VERSION }}.zip floor-pub/${{ matrix.rid }}/* -x *.pdb *.xml diff --git a/docs/release-history.md b/docs/release-history.md index 12b2dcd2..ba0feeb2 100644 --- a/docs/release-history.md +++ b/docs/release-history.md @@ -8,6 +8,10 @@ - `NetBox` was exposing some internal types (#451) +### Experimental + +**Parquet Floor** (reference implementation of desktop viewer) user interface improvements. + ## 4.20.0 ### New features diff --git a/src/Parquet.Floor.Desktop/Parquet.Floor.Desktop.csproj b/src/Parquet.Floor.Desktop/Parquet.Floor.Desktop.csproj deleted file mode 100644 index 75e00be2..00000000 --- a/src/Parquet.Floor.Desktop/Parquet.Floor.Desktop.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - WinExe - net8.0 - enable - true - app.manifest - floor - true - true - true - ../Parquet.Floor/Assets/icon.ico - - - - - - - - - - diff --git a/src/Parquet.Floor.Desktop/Properties/launchSettings.json b/src/Parquet.Floor.Desktop/Properties/launchSettings.json deleted file mode 100644 index c1ff14d5..00000000 --- a/src/Parquet.Floor.Desktop/Properties/launchSettings.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "profiles": { - "WSL": { - "commandName": "WSL2", - "distributionName": "" - }, - "all_var": { - "commandName": "Project", - "commandLineArgs": "C:\\dev\\parquet-dotnet\\src\\Parquet.Test\\data\\all_var1.parquet" - }, - "customer": { - "commandName": "Project", - "commandLineArgs": "C:\\dev\\parquet-dotnet\\src\\Parquet.Test\\data\\customer.impala.parquet" - } - } -} \ No newline at end of file diff --git a/src/Parquet.Floor.Desktop/icon.ico b/src/Parquet.Floor.Desktop/icon.ico deleted file mode 100644 index f0485eb5..00000000 Binary files a/src/Parquet.Floor.Desktop/icon.ico and /dev/null differ diff --git a/src/Parquet.Floor/App.axaml b/src/Parquet.Floor/App.axaml index 96cb1f72..241de23f 100644 --- a/src/Parquet.Floor/App.axaml +++ b/src/Parquet.Floor/App.axaml @@ -1,8 +1,8 @@ - + xmlns:actipro="http://schemas.actiprosoftware.com/avaloniaui" + xmlns:generation="using:ActiproSoftware.UI.Avalonia.Themes.Generation"> @@ -10,5 +10,11 @@ + + + + + + diff --git a/src/Parquet.Floor/App.axaml.cs b/src/Parquet.Floor/App.axaml.cs index e9c5aea1..64e8ef46 100644 --- a/src/Parquet.Floor/App.axaml.cs +++ b/src/Parquet.Floor/App.axaml.cs @@ -18,14 +18,12 @@ public override void OnFrameworkInitializationCompleted() { // Without this line you will get duplicate validations from both Avalonia and CT BindingPlugins.DataValidators.RemoveAt(0); + var model = new MainViewModel(); + if(ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - desktop.MainWindow = new MainWindow { - DataContext = new MainViewModel() - }; + desktop.MainWindow = new MainWindow() { DataContext = model }; } else if(ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) { - singleViewPlatform.MainView = new MainView { - DataContext = new MainViewModel() - }; + singleViewPlatform.MainView = new MainView() { DataContext = model }; } base.OnFrameworkInitializationCompleted(); diff --git a/src/Parquet.Floor/Assets/icons/col/bytearray.png b/src/Parquet.Floor/Assets/icons/col/bytearray.png new file mode 100644 index 00000000..009bedaf Binary files /dev/null and b/src/Parquet.Floor/Assets/icons/col/bytearray.png differ diff --git a/src/Parquet.Floor/Assets/icons/col/list.png b/src/Parquet.Floor/Assets/icons/col/list.png new file mode 100644 index 00000000..7dc9051a Binary files /dev/null and b/src/Parquet.Floor/Assets/icons/col/list.png differ diff --git a/src/Parquet.Floor/Assets/icons/col/map.png b/src/Parquet.Floor/Assets/icons/col/map.png new file mode 100644 index 00000000..d83adafc Binary files /dev/null and b/src/Parquet.Floor/Assets/icons/col/map.png differ diff --git a/src/Parquet.Floor/Assets/icons/col/number.png b/src/Parquet.Floor/Assets/icons/col/number.png new file mode 100644 index 00000000..3c90624a Binary files /dev/null and b/src/Parquet.Floor/Assets/icons/col/number.png differ diff --git a/src/Parquet.Floor/Assets/icons/col/string.png b/src/Parquet.Floor/Assets/icons/col/string.png new file mode 100644 index 00000000..d6116eea Binary files /dev/null and b/src/Parquet.Floor/Assets/icons/col/string.png differ diff --git a/src/Parquet.Floor/Assets/icons/col/struct.png b/src/Parquet.Floor/Assets/icons/col/struct.png new file mode 100644 index 00000000..81dda823 Binary files /dev/null and b/src/Parquet.Floor/Assets/icons/col/struct.png differ diff --git a/src/Parquet.Floor/Assets/icons/diagram.png b/src/Parquet.Floor/Assets/icons/diagram.png deleted file mode 100644 index 4ba7283a..00000000 Binary files a/src/Parquet.Floor/Assets/icons/diagram.png and /dev/null differ diff --git a/src/Parquet.Floor/Assets/icons/glasses.png b/src/Parquet.Floor/Assets/icons/glasses.png deleted file mode 100644 index 307c3ef8..00000000 Binary files a/src/Parquet.Floor/Assets/icons/glasses.png and /dev/null differ diff --git a/src/Parquet.Floor/Assets/icons/information.png b/src/Parquet.Floor/Assets/icons/information.png deleted file mode 100644 index 90580669..00000000 Binary files a/src/Parquet.Floor/Assets/icons/information.png and /dev/null differ diff --git a/src/Parquet.Floor/Assets/icons/map.png b/src/Parquet.Floor/Assets/icons/map.png deleted file mode 100644 index f406662d..00000000 Binary files a/src/Parquet.Floor/Assets/icons/map.png and /dev/null differ diff --git a/src/Parquet.Floor/Assets/icons/open.png b/src/Parquet.Floor/Assets/icons/open.png deleted file mode 100644 index fa351386..00000000 Binary files a/src/Parquet.Floor/Assets/icons/open.png and /dev/null differ diff --git a/src/Parquet.Floor/Assets/icons/raw-extension.png b/src/Parquet.Floor/Assets/icons/raw-extension.png deleted file mode 100644 index ed36e28c..00000000 Binary files a/src/Parquet.Floor/Assets/icons/raw-extension.png and /dev/null differ diff --git a/src/Parquet.Floor/Assets/icons/trophy.png b/src/Parquet.Floor/Assets/icons/trophy.png deleted file mode 100644 index 22d0d95d..00000000 Binary files a/src/Parquet.Floor/Assets/icons/trophy.png and /dev/null differ diff --git a/src/Parquet.Floor/Extensions.cs b/src/Parquet.Floor/Extensions.cs new file mode 100644 index 00000000..3c7fc426 --- /dev/null +++ b/src/Parquet.Floor/Extensions.cs @@ -0,0 +1,74 @@ +using System.Diagnostics; +using Parquet.Meta; + +namespace Parquet.Floor { + static class Extensions { + public static string ToSimpleString(this LogicalType? lt) { + if(lt == null) + return string.Empty; + + if(lt.UUID != null) + return "UUID"; + + if(lt.STRING != null) + return "STRING"; + + if(lt.MAP != null) + return "MAP"; + + if(lt.LIST != null) + return "LIST"; + + if(lt.ENUM != null) + return "ENUM"; + + if(lt.DECIMAL != null) + return $"DECIMAL (precision: {lt.DECIMAL.Precision}, scale: {lt.DECIMAL.Scale})"; + + if(lt.DATE != null) + return $"DATE"; + + if(lt.TIME != null) { + string unit = lt.TIME.Unit.MICROS != null + ? "MICROS" + : lt.TIME.Unit.MILLIS != null + ? "MILLIS" + : "NANOS"; + return $"TIME (unit: {unit}, isAdjustedToUTC: {lt.TIME.IsAdjustedToUTC})"; + } + + if(lt.TIMESTAMP != null) { + string unit = lt.TIMESTAMP.Unit.MICROS != null + ? "MICROS" + : lt.TIMESTAMP.Unit.MILLIS != null + ? "MILLIS" + : "NANOS"; + return $"TIMESTAMP (unit: {unit}, isAdjustedToUTC: {lt.TIMESTAMP.IsAdjustedToUTC})"; + } + + if(lt.INTEGER != null) + return $"INTEGER (bitWidth: {lt.INTEGER.BitWidth}, isSigned: {lt.INTEGER.IsSigned})"; + + if(lt.UNKNOWN != null) + return "UNKNOWN"; + + if(lt.JSON != null) + return "JSON"; + + if(lt.BSON != null) + return "BSON"; + + if(lt.UUID != null) + return "UUID"; + + return "?"; + } + + public static void OpenInBrowser(this string url) { + var p = new Process(); + p.StartInfo.UseShellExecute = true; + p.StartInfo.FileName = url; + p.Start(); + } + } +} diff --git a/src/Parquet.Floor/Parquet.Floor.csproj b/src/Parquet.Floor/Parquet.Floor.csproj index 94591a56..ce1eef76 100644 --- a/src/Parquet.Floor/Parquet.Floor.csproj +++ b/src/Parquet.Floor/Parquet.Floor.csproj @@ -1,9 +1,17 @@  + WinExe net8.0 enable latest true + true + app.manifest + floor + true + true + true + Assets/icon.ico @@ -15,12 +23,6 @@ - - - - - - @@ -29,15 +31,19 @@ + + + + + + - - - - + + @@ -50,4 +56,8 @@ DataView.axaml + + + + diff --git a/src/Parquet.Floor.Desktop/Parquet.Floor.Desktop.csproj.user b/src/Parquet.Floor/Parquet.Floor.csproj.user similarity index 86% rename from src/Parquet.Floor.Desktop/Parquet.Floor.Desktop.csproj.user rename to src/Parquet.Floor/Parquet.Floor.csproj.user index 89082262..24c31915 100644 --- a/src/Parquet.Floor.Desktop/Parquet.Floor.Desktop.csproj.user +++ b/src/Parquet.Floor/Parquet.Floor.csproj.user @@ -4,6 +4,6 @@ ProjectDebugger - all_var + clean \ No newline at end of file diff --git a/src/Parquet.Floor.Desktop/Program.cs b/src/Parquet.Floor/Program.cs similarity index 80% rename from src/Parquet.Floor.Desktop/Program.cs rename to src/Parquet.Floor/Program.cs index 5d0637a1..052cab80 100644 --- a/src/Parquet.Floor.Desktop/Program.cs +++ b/src/Parquet.Floor/Program.cs @@ -1,7 +1,9 @@ using System; using Avalonia; +using Projektanker.Icons.Avalonia.FontAwesome; +using Projektanker.Icons.Avalonia; -namespace Parquet.Floor.Desktop; +namespace Parquet.Floor; class Program { // Initialization code. Don't use any Avalonia, third-party APIs or any @@ -25,9 +27,12 @@ public static AppBuilder BuildAvaloniaApp() { Console.WriteLine(ex); } + IconProvider.Current + .Register(); + return AppBuilder.Configure() .UsePlatformDetect() - .WithInterFont() + //.WithInterFont() .LogToTrace(); } diff --git a/src/Parquet.Floor/Styles.axaml b/src/Parquet.Floor/Styles.axaml index 1436aa12..3713f2e2 100644 --- a/src/Parquet.Floor/Styles.axaml +++ b/src/Parquet.Floor/Styles.axaml @@ -1,17 +1,15 @@  + xmlns:system="clr-namespace:System" + xmlns:i="https://github.com/projektanker/icons.avalonia"> - - + - - - - - - - - + + + + + + + 0,0,20,0 + + + 0,0,0,0 + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Parquet.Floor/Views/MainWindow.axaml b/src/Parquet.Floor/Views/MainWindow.axaml index e06bd103..2b211dc0 100644 --- a/src/Parquet.Floor/Views/MainWindow.axaml +++ b/src/Parquet.Floor/Views/MainWindow.axaml @@ -8,5 +8,5 @@ x:Class="Parquet.Floor.Views.MainWindow" Icon="/Assets/icon.ico" Title="Floor"> - + diff --git a/src/Parquet.Floor/Views/MainWindow.axaml.cs b/src/Parquet.Floor/Views/MainWindow.axaml.cs index 0b1ffed5..d73aa1ca 100644 --- a/src/Parquet.Floor/Views/MainWindow.axaml.cs +++ b/src/Parquet.Floor/Views/MainWindow.axaml.cs @@ -5,7 +5,5 @@ namespace Parquet.Floor.Views; public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); - - Title = $"Parquet Floor v{Parquet.Globals.Version}"; } } \ No newline at end of file diff --git a/src/Parquet.Floor/Views/DataViewCellTemplate.cs b/src/Parquet.Floor/Views/Templates/DataViewCellTemplate.cs similarity index 80% rename from src/Parquet.Floor/Views/DataViewCellTemplate.cs rename to src/Parquet.Floor/Views/Templates/DataViewCellTemplate.cs index 68bc684e..b7d57d14 100644 --- a/src/Parquet.Floor/Views/DataViewCellTemplate.cs +++ b/src/Parquet.Floor/Views/Templates/DataViewCellTemplate.cs @@ -7,12 +7,12 @@ using Avalonia.Layout; using Parquet.Schema; -namespace Parquet.Floor.Views { +namespace Parquet.Floor.Views.Templates { /// /// Implements pretty much all of the logic for rendering a parquet data cell, including complex types. /// - internal class DataViewCellTemplate : IDataTemplate { + class DataViewCellTemplate : IDataTemplate { private const string DataCellClassName = "data-cell"; private const string DataCellNullClassName = "data-cell-null"; @@ -47,16 +47,12 @@ private static TextBlock CreateTextBlock(string value, string? extraClassName = Text = value }; r.Classes.Add(DataCellClassName); - if(extraClassName != null) { - r.Classes.Add(extraClassName); - } + if(extraClassName != null) r.Classes.Add(extraClassName); return r; } public static Control BuildValue(object? value, Field f, int depth, string? extraClassName = null, bool forceData = false) { - if(value == null) { - return CreateNullTextBlock(); - } + if(value == null) return CreateNullTextBlock(); if(forceData || f.SchemaType == SchemaType.Data) { TextBlock tb = CreateTextBlock(value.ToString()!, extraClassName); @@ -68,8 +64,7 @@ public static Control BuildValue(object? value, Field f, int depth, string? extr }; var structField = (StructField)f; - if(value is IDictionary valueDictionary) { - foreach(Field field in structField.Fields) { + if(value is IDictionary valueDictionary) foreach(Field field in structField.Fields) { var vsp = new StackPanel { Orientation = Orientation.Horizontal @@ -82,7 +77,6 @@ public static Control BuildValue(object? value, Field f, int depth, string? extr valueDictionary.TryGetValue(field.Name, out object? fieldValue); vsp.Children.Add(BuildValue(fieldValue, field, depth + 1)); } - } return sp; } else if(f.SchemaType == SchemaType.Map) { @@ -92,8 +86,7 @@ public static Control BuildValue(object? value, Field f, int depth, string? extr var mapField = (MapField)f; - if(value is IDictionary valueDictionary) { - foreach(DictionaryEntry entry in valueDictionary) { + if(value is IDictionary valueDictionary) foreach(DictionaryEntry entry in valueDictionary) { var vsp = new StackPanel { Orientation = Orientation.Horizontal @@ -104,7 +97,6 @@ public static Control BuildValue(object? value, Field f, int depth, string? extr //vsp.Children.Add(CreateTextBlock(": ")); vsp.Children.Add(BuildValue(entry.Value, mapField.Value, depth + 1)); } - } return sp; } else if(f.SchemaType == SchemaType.List) { @@ -112,11 +104,7 @@ public static Control BuildValue(object? value, Field f, int depth, string? extr Orientation = Orientation.Vertical }; var listField = (ListField)f; - if(value is IEnumerable valueList) { - foreach(object? entry in valueList) { - sp.Children.Add(BuildValue(entry, listField.Item, depth + 1)); - } - } + if(value is IEnumerable valueList) foreach(object? entry in valueList) sp.Children.Add(BuildValue(entry, listField.Item, depth + 1)); return sp; } @@ -127,13 +115,7 @@ public static Control Build(object? param, Field f, int depth) { object? value = null; - if(param is Dictionary row) { - if(row.TryGetValue(f.Name, out object? mapValue)) { - if(mapValue != null) { - value = mapValue; - } - } - } + if(param is Dictionary row) if(row.TryGetValue(f.Name, out object? mapValue)) if(mapValue != null) value = mapValue; return BuildValue(value, f, depth); } diff --git a/src/Parquet.Floor/Views/Templates/DataViewHeaderTemplate.cs b/src/Parquet.Floor/Views/Templates/DataViewHeaderTemplate.cs new file mode 100644 index 00000000..54490c24 --- /dev/null +++ b/src/Parquet.Floor/Views/Templates/DataViewHeaderTemplate.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Parquet.Schema; +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using Avalonia.Layout; +using Avalonia; +using Parquet.Floor.ViewModels; +using Avalonia.Platform; +using Avalonia.Media.Imaging; + +namespace Parquet.Floor.Views.Templates { + class DataViewHeaderTemplate : IDataTemplate { + private readonly Field _field; + + public DataViewHeaderTemplate(Field field) { + _field = field; + } + + private static bool IsNumeric(Type t) => + t == typeof(short) || t == typeof(int) || t == typeof(long) || t == typeof(decimal); + + private static bool IsByteArray(Type t) => + t == typeof(byte[]); + + private static bool IsString(Type t) => t == typeof(string); + + private Control CreateIcon() { + + string? name = null; + + switch(_field.SchemaType) { + case SchemaType.Data: + if(_field is DataField df) { + if(IsNumeric(df.ClrType)) { + name = "number"; + } else if(IsString(df.ClrType)) { + name = "string"; + } else if(IsByteArray(df.ClrType)) { + name = "bytearray"; + } + } + break; + case SchemaType.Struct: + name = "struct"; + break; + case SchemaType.Map: + name = "map"; + break; + case SchemaType.List: + name = "list"; + break; + } + + + if(name == null) + return new Control(); + + var image = new Image { + Source = new Bitmap(AssetLoader.Open(new Uri($"avares://floor/Assets/icons/col/{name}.png"))) + }; + image.Classes.Add("dt-icon"); + return image; + + } + + public Control? Build(object? param) { + + var r = new StackPanel { + Orientation = Orientation.Horizontal, + HorizontalAlignment = HorizontalAlignment.Stretch + }; + r.Children.Add(new TextBlock { + Text = _field.Name + }); + r.Children.Add(CreateIcon()); + + //r.SetValue(ToolTip.TipProperty, "..."); + return r; + } + public bool Match(object? data) => true; + } +} diff --git a/src/Parquet.Floor.Desktop/app.manifest b/src/Parquet.Floor/app.manifest similarity index 100% rename from src/Parquet.Floor.Desktop/app.manifest rename to src/Parquet.Floor/app.manifest diff --git a/src/Parquet.sln b/src/Parquet.sln index 11aa2164..7d2bd12f 100644 --- a/src/Parquet.sln +++ b/src/Parquet.sln @@ -37,12 +37,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Parquet.PerfRunner", "Parquet.PerfRunner\Parquet.PerfRunner.csproj", "{6325A9B7-32B4-464C-84F4-9B62BE630E90}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UI", "UI", "{712F0F85-C1D0-4176-BFCD-20F09CB59191}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Parquet.Floor", "Parquet.Floor\Parquet.Floor.csproj", "{882BE8ED-7F5F-4392-8884-CF602622E569}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Parquet.Floor.Desktop", "Parquet.Floor.Desktop\Parquet.Floor.Desktop.csproj", "{A6B74525-BDCB-4872-B530-3CBC50D11306}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -65,10 +61,6 @@ Global {882BE8ED-7F5F-4392-8884-CF602622E569}.Debug|Any CPU.Build.0 = Debug|Any CPU {882BE8ED-7F5F-4392-8884-CF602622E569}.Release|Any CPU.ActiveCfg = Release|Any CPU {882BE8ED-7F5F-4392-8884-CF602622E569}.Release|Any CPU.Build.0 = Release|Any CPU - {A6B74525-BDCB-4872-B530-3CBC50D11306}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A6B74525-BDCB-4872-B530-3CBC50D11306}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A6B74525-BDCB-4872-B530-3CBC50D11306}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A6B74525-BDCB-4872-B530-3CBC50D11306}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -78,8 +70,6 @@ Global {AB829BBB-EF87-4038-8D32-E1C1629F357C} = {60294E19-6F8D-4D78-9A62-C50489095484} {F28C5308-5410-4066-9DA8-4DDC8ACB0B5B} = {AB829BBB-EF87-4038-8D32-E1C1629F357C} {6325A9B7-32B4-464C-84F4-9B62BE630E90} = {3F47B841-9074-4317-8ACA-0F2EEA34FA62} - {882BE8ED-7F5F-4392-8884-CF602622E569} = {712F0F85-C1D0-4176-BFCD-20F09CB59191} - {A6B74525-BDCB-4872-B530-3CBC50D11306} = {712F0F85-C1D0-4176-BFCD-20F09CB59191} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B5C12140-A3BF-47C9-A4AD-91F7C4682804}