瀏覽代碼

Added user api

Krzysztof Krysiński 2 周之前
父節點
當前提交
1af3deaf06
共有 26 個文件被更改,包括 234 次插入69 次删除
  1. 2 1
      samples/Sample10_VisualTree/VisualTreeSampleExtension.cs
  2. 3 0
      samples/Sample10_VisualTree/extension.json
  3. 9 0
      src/PixiEditor.Extensions.CommonApi/User/IUserDataProvider.cs
  4. 3 1
      src/PixiEditor.Extensions.CommonApi/Windowing/IWindowProvider.cs
  5. 5 1
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/TextField.cs
  6. 15 0
      src/PixiEditor.Extensions.Sdk/Api/UserData/UserDataProvider.cs
  7. 30 0
      src/PixiEditor.Extensions.Sdk/Bridge/Interop.User.cs
  8. 18 0
      src/PixiEditor.Extensions.Sdk/Bridge/Native.User.cs
  9. 3 0
      src/PixiEditor.Extensions.Sdk/PixiEditorApi.cs
  10. 二進制
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll
  11. 二進制
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Extensions.MSPackageBuilder.dll
  12. 37 0
      src/PixiEditor.Extensions.WasmRuntime/Api/UserApi.cs
  13. 30 0
      src/PixiEditor.Extensions.WasmRuntime/Utilities/InteropUtility.cs
  14. 7 7
      src/PixiEditor.Extensions.WasmRuntime/WasmExtensionInstance.cs
  15. 2 0
      src/PixiEditor.Extensions/ExtensionServices.cs
  16. 15 1
      src/PixiEditor.Extensions/FlyUI/Elements/TextField.cs
  17. 2 3
      src/PixiEditor.Extensions/Metadata/ExtensionPermissions.cs
  18. 2 0
      src/PixiEditor/Helpers/ServiceCollectionHelpers.cs
  19. 0 46
      src/PixiEditor/Initialization/ClassicDesktopEntry.cs
  20. 25 0
      src/PixiEditor/Models/ExtensionServices/UserDataProvider.cs
  21. 2 0
      src/PixiEditor/ViewModels/SubViewModels/ExtensionsViewModel.cs
  22. 8 4
      src/PixiEditor/ViewModels/SubViewModels/FileViewModel.cs
  23. 1 1
      src/PixiEditor/ViewModels/SubViewModels/UserViewModel.cs
  24. 9 2
      src/PixiEditor/ViewModels/SubViewModels/WindowViewModel.cs
  25. 4 2
      src/PixiEditor/Views/Auth/LoginForm.axaml
  26. 2 0
      src/PixiEditor/Views/Auth/LoginPopup.axaml.cs

+ 2 - 1
samples/Sample10_VisualTree/VisualTreeSampleExtension.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+using System;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 using PixiEditor.Extensions.CommonApi.Windowing;
 using PixiEditor.Extensions.Sdk;
 using PixiEditor.Extensions.Sdk.Api.FlyUI;

+ 3 - 0
samples/Sample10_VisualTree/extension.json

@@ -32,5 +32,8 @@
   "license": "MIT",
   "categories": [
     "Extension"
+  ],
+  "Permissions": [
+    "ReadUserData"
   ]
 }

+ 9 - 0
src/PixiEditor.Extensions.CommonApi/User/IUserDataProvider.cs

@@ -0,0 +1,9 @@
+namespace PixiEditor.Extensions.CommonApi.User;
+
+public interface IUserDataProvider
+{
+    public bool IsLoggedIn { get; }
+    public string Username { get; }
+    public string AccountProviderName { get; }
+    public string[] GetOwnedContent();
+}

+ 3 - 1
src/PixiEditor.Extensions.CommonApi/Windowing/IWindowProvider.cs

@@ -15,5 +15,7 @@ public enum BuiltInWindowType
     [Description("PalettesBrowser")]
     PalettesBrowser,
     [Description("HelloTherePopup")]
-    StartupWindow
+    StartupWindow,
+    [Description("LoginPopup")]
+    AccountManagement,
 }

+ 5 - 1
src/PixiEditor.Extensions.Sdk/Api/FlyUI/TextField.cs

@@ -14,9 +14,12 @@ public class TextField : LayoutElement
     }
     public string Text { get; set; }
 
-    public TextField(string? text = null, Cursor? cursor = null) : base(cursor)
+    public string? Placeholder { get; set; } = string.Empty;
+
+    public TextField(string? text = null, string? placeholder = null, Cursor? cursor = null) : base(cursor)
     {
         Text = text ?? string.Empty;
+        Placeholder = placeholder;
         TextChanged += e => Text = e.Text;
     }
 
@@ -24,6 +27,7 @@ public class TextField : LayoutElement
     {
         ControlDefinition textField = new ControlDefinition(UniqueId, GetType());
         textField.AddProperty(Text);
+        textField.AddProperty(Placeholder);
         return textField;
     }
 }

+ 15 - 0
src/PixiEditor.Extensions.Sdk/Api/UserData/UserDataProvider.cs

@@ -0,0 +1,15 @@
+using PixiEditor.Extensions.CommonApi.User;
+using PixiEditor.Extensions.Sdk.Bridge;
+
+namespace PixiEditor.Extensions.Sdk.Api.UserData;
+
+public class UserDataProvider : IUserDataProvider
+{
+    public bool IsLoggedIn => Native.is_user_logged_in();
+    public string Username => Native.get_username();
+    public string AccountProviderName => Native.get_account_provider_name();
+    public string[] GetOwnedContent()
+    {
+        return Interop.GetOwnedContent();
+    }
+}

+ 30 - 0
src/PixiEditor.Extensions.Sdk/Bridge/Interop.User.cs

@@ -0,0 +1,30 @@
+using PixiEditor.Extensions.Sdk.Utilities;
+
+namespace PixiEditor.Extensions.Sdk.Bridge;
+
+internal static partial class Interop
+{
+    public static string[] GetOwnedContent()
+    {
+        IntPtr ptr = Native.get_owned_content();
+        if (ptr == IntPtr.Zero)
+        {
+            return [];
+        }
+
+        List<string> contentList = new List<string>();
+        byte[] arr = InteropUtility.PrefixedIntPtrToByteArray(ptr);
+        int length = BitConverter.ToInt32(arr, 0);
+        int offset = 4;
+        for (int i = 0; i < length; i++)
+        {
+            int strLength = BitConverter.ToInt32(arr, offset);
+            offset += 4;
+            string content = System.Text.Encoding.UTF8.GetString(arr, offset, strLength);
+            contentList.Add(content);
+            offset += strLength;
+        }
+
+        return contentList.ToArray();
+    }
+}

+ 18 - 0
src/PixiEditor.Extensions.Sdk/Bridge/Native.User.cs

@@ -0,0 +1,18 @@
+using System.Runtime.CompilerServices;
+
+namespace PixiEditor.Extensions.Sdk.Bridge;
+
+internal static partial class Native
+{
+    [MethodImpl(MethodImplOptions.InternalCall)]
+    public static extern bool is_user_logged_in();
+
+    [MethodImpl(MethodImplOptions.InternalCall)]
+    public static extern string get_username();
+
+    [MethodImpl(MethodImplOptions.InternalCall)]
+    public static extern string get_account_provider_name();
+
+    [MethodImpl(MethodImplOptions.InternalCall)]
+    public static extern IntPtr get_owned_content();
+}

+ 3 - 0
src/PixiEditor.Extensions.Sdk/PixiEditorApi.cs

@@ -5,6 +5,7 @@ using PixiEditor.Extensions.Sdk.Api.IO;
 using PixiEditor.Extensions.Sdk.Api.Logging;
 using PixiEditor.Extensions.Sdk.Api.Palettes;
 using PixiEditor.Extensions.Sdk.Api.Ui;
+using PixiEditor.Extensions.Sdk.Api.UserData;
 using PixiEditor.Extensions.Sdk.Api.UserPreferences;
 using PixiEditor.Extensions.Sdk.Api.Window;
 
@@ -19,6 +20,7 @@ public class PixiEditorApi
     public CommandProvider Commands { get; }
     public DocumentProvider Documents { get; }
     public VisualTreeProvider VisualTreeProvider { get; }
+    public UserDataProvider UserDataProvider { get; }
 
     public PixiEditorApi()
     {
@@ -29,5 +31,6 @@ public class PixiEditorApi
         Commands = new CommandProvider();
         Documents = new DocumentProvider();
         VisualTreeProvider = new VisualTreeProvider();
+        UserDataProvider = new UserDataProvider();
     }
 }

二進制
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll


二進制
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Extensions.MSPackageBuilder.dll


+ 37 - 0
src/PixiEditor.Extensions.WasmRuntime/Api/UserApi.cs

@@ -0,0 +1,37 @@
+using PixiEditor.Extensions.Metadata;
+using PixiEditor.Extensions.WasmRuntime.Utilities;
+using static System.Array;
+
+namespace PixiEditor.Extensions.WasmRuntime.Api;
+
+internal class UserApi : ApiGroupHandler
+{
+    [ApiFunction("is_user_logged_in")]
+    public bool IsLoggedIn()
+    {
+        return Api.UserDataProvider?.IsLoggedIn ?? false;
+    }
+
+    [ApiFunction("get_username")]
+    public string GetUsername()
+    {
+        PermissionUtility.ThrowIfLacksPermissions(Metadata, ExtensionPermissions.ReadUserData);
+        return Api.UserDataProvider?.Username ?? string.Empty;
+    }
+
+    [ApiFunction("get_account_provider_name")]
+    public string GetAccountProviderName()
+    {
+        return Api.UserDataProvider?.AccountProviderName ?? string.Empty;
+    }
+
+    [ApiFunction("get_owned_content")]
+    public byte[] GetOwnedContent()
+    {
+        PermissionUtility.ThrowIfLacksPermissions(Metadata, ExtensionPermissions.ReadUserData);
+        string[] content = Api.UserDataProvider.GetOwnedContent();
+
+        byte[] bytes = InteropUtility.SerializeToBytes(content);
+        return bytes;
+    }
+}

+ 30 - 0
src/PixiEditor.Extensions.WasmRuntime/Utilities/InteropUtility.cs

@@ -0,0 +1,30 @@
+namespace PixiEditor.Extensions.WasmRuntime.Utilities;
+
+public static class InteropUtility
+{
+    public static byte[] SerializeToBytes(string[] list)
+    {
+        if (list == null || list.Length == 0)
+        {
+            return [];
+        }
+
+        List<byte> bytes = new List<byte>();
+        bytes.AddRange(BitConverter.GetBytes(list.Length));
+        foreach (var item in list)
+        {
+            if (item == null)
+            {
+                bytes.AddRange(BitConverter.GetBytes(0));
+            }
+            else
+            {
+                byte[] itemBytes = System.Text.Encoding.UTF8.GetBytes(item);
+                bytes.AddRange(BitConverter.GetBytes(itemBytes.Length));
+                bytes.AddRange(itemBytes);
+            }
+        }
+
+        return bytes.ToArray();
+    }
+}

+ 7 - 7
src/PixiEditor.Extensions.WasmRuntime/WasmExtensionInstance.cs

@@ -59,7 +59,7 @@ public partial class WasmExtensionInstance : Extension
 
     protected override void OnLoaded()
     {
-        Instance.GetAction("load").Invoke();
+        Instance.GetAction("load")?.Invoke();
         base.OnLoaded();
     }
 
@@ -73,32 +73,32 @@ public partial class WasmExtensionInstance : Extension
         LayoutBuilder = new LayoutBuilder((ElementMap)Api.Services.GetService(typeof(ElementMap)));
 
         //SetElementMap();
-        Instance.GetAction("initialize").Invoke();
+        Instance.GetAction("initialize")?.Invoke();
         base.OnInitialized();
     }
 
     protected override void OnUserReady()
     {
-        Instance.GetAction("user_ready").Invoke();
+        Instance.GetAction("user_ready")?.Invoke();
         base.OnUserReady();
     }
 
     protected override void OnMainWindowLoaded()
     {
-        Instance.GetAction("main_window_loaded").Invoke();
+        Instance.GetAction("main_window_loaded")?.Invoke();
         base.OnMainWindowLoaded();
     }
 
     private void OnAsyncCallCompleted(int handle, int result)
     {
         Dispatcher.UIThread.Invoke(() =>
-            Instance.GetAction<int, int>("async_call_completed").Invoke(handle, result));
+            Instance.GetAction<int, int>("async_call_completed")?.Invoke(handle, result));
     }
 
     private void OnAsyncCallFaulted(int handle, string exceptionMessage)
     {
         Dispatcher.UIThread.Invoke(() =>
-            Instance.GetAction<int, string>("async_call_faulted").Invoke(handle, exceptionMessage));
+            Instance.GetAction<int, string>("async_call_faulted")?.Invoke(handle, exceptionMessage));
     }
 
     private void SetElementMap()
@@ -106,7 +106,7 @@ public partial class WasmExtensionInstance : Extension
         var elementMap = (ElementMap)Api.Services.GetService(typeof(ElementMap));
         byte[] map = elementMap.Serialize();
         var ptr = WasmMemoryUtility.WriteBytes(map);
-        Instance.GetAction<int, int>("set_element_map").Invoke(ptr, map.Length);
+        Instance.GetAction<int, int>("set_element_map")?.Invoke(ptr, map.Length);
 
         WasmMemoryUtility.Free(ptr);
     }

+ 2 - 0
src/PixiEditor.Extensions/ExtensionServices.cs

@@ -5,6 +5,7 @@ using PixiEditor.Extensions.CommonApi.IO;
 using PixiEditor.Extensions.CommonApi.Logging;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Extensions.CommonApi.Ui;
+using PixiEditor.Extensions.CommonApi.User;
 using PixiEditor.Extensions.CommonApi.UserPreferences;
 using PixiEditor.Extensions.CommonApi.Windowing;
 using PixiEditor.Extensions.IO;
@@ -22,6 +23,7 @@ public class ExtensionServices
     public IDocumentProvider Documents => Services.GetService<IDocumentProvider>();
     public ICommandSupervisor CommandSupervisor => Services.GetService<ICommandSupervisor>();
     public IVisualTreeProvider VisualTree => Services.GetService<IVisualTreeProvider>();
+    public IUserDataProvider UserDataProvider => Services.GetService<IUserDataProvider>();
     public ILogger Logger => Services.GetService<ILogger>();
 
     public ExtensionServices(IServiceProvider services)

+ 15 - 1
src/PixiEditor.Extensions/FlyUI/Elements/TextField.cs

@@ -1,3 +1,4 @@
+using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Data;
 using PixiEditor.Extensions.CommonApi.FlyUI.Events;
@@ -7,6 +8,7 @@ namespace PixiEditor.Extensions.FlyUI.Elements;
 internal class TextField : LayoutElement
 {
     private string text;
+    private string? placeholder;
     public event ElementEventHandler TextChanged
     {
         add => AddEvent(nameof(TextChanged), value);
@@ -15,9 +17,12 @@ internal class TextField : LayoutElement
 
     public string Text { get => text; set => SetField(ref text, value); }
 
-    public TextField(string text)
+    public string? Placeholder { get => placeholder; set => SetField(ref placeholder, value); }
+
+    public TextField(string text, string? placeholder = null)
     {
         Text = text;
+        Placeholder = placeholder ?? string.Empty;
     }
 
     protected override Control CreateNativeControl()
@@ -28,6 +33,13 @@ internal class TextField : LayoutElement
             new Binding(nameof(Text)) { Source = this, Mode = BindingMode.TwoWay };
         textBox.Bind(TextBox.TextProperty, binding);
 
+        Binding placeholderBinding =
+            new Binding(nameof(Placeholder)) { Source = this, Mode = BindingMode.OneWay };
+
+        textBox.Bind(TextBox.WatermarkProperty, placeholderBinding);
+
+        textBox.Padding = new Thickness(5);
+
         textBox.TextChanged += (s, e) =>
         {
             RaiseEvent(nameof(TextChanged), new TextEventArgs(textBox.Text));
@@ -39,10 +51,12 @@ internal class TextField : LayoutElement
     protected override IEnumerable<object> GetControlProperties()
     {
         yield return Text;
+        yield return Placeholder;
     }
 
     protected override void DeserializeControlProperties(List<object> values)
     {
         Text = (string)values[0];
+        Placeholder = values.ElementAtOrDefault(1) as string;
     }
 }

+ 2 - 3
src/PixiEditor.Extensions/Metadata/ExtensionPermissions.cs

@@ -1,6 +1,4 @@
-using System.Runtime.CompilerServices;
-
-namespace PixiEditor.Extensions.Metadata;
+namespace PixiEditor.Extensions.Metadata;
 
 [Flags]
 [Newtonsoft.Json.JsonConverter(typeof(JsonEnumFlagConverter))]
@@ -19,5 +17,6 @@ public enum ExtensionPermissions
     ///     the editor.
     /// </summary>
     OpenDocuments = 2,
+    ReadUserData = 4,
     FullAccess = ~0,
 }

+ 2 - 0
src/PixiEditor/Helpers/ServiceCollectionHelpers.cs

@@ -11,6 +11,7 @@ using PixiEditor.Extensions.CommonApi.Logging;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Extensions.CommonApi.Palettes.Parsers;
 using PixiEditor.Extensions.CommonApi.Ui;
+using PixiEditor.Extensions.CommonApi.User;
 using PixiEditor.Extensions.CommonApi.UserPreferences;
 using PixiEditor.Extensions.CommonApi.Windowing;
 using PixiEditor.Extensions.FlyUI;
@@ -216,5 +217,6 @@ internal static class ServiceCollectionHelpers
             .AddSingleton<ICommandSupervisor, CommandSupervisor>()
             .AddSingleton<ILogger, ConsoleLogger>()
             .AddSingleton<IVisualTreeProvider, VisualTreeProvider>()
+            .AddSingleton<IUserDataProvider, UserDataProvider>()
             .AddSingleton<IFileSystemProvider, FileSystemProvider>();
 }

+ 0 - 46
src/PixiEditor/Initialization/ClassicDesktopEntry.cs

@@ -121,54 +121,8 @@ internal class ClassicDesktopEntry
         ViewModels_ViewModelMain viewModel = Services.GetRequiredService<ViewModels_ViewModelMain>();
         viewModel.Setup(Services);
 
-#if FOUNDERS_PACK_REQUIRED
-        if (!IsFoundersPackOwner())
-        {
-            IPlatform.Current.IdentityProvider.OwnedProductsUpdated += IdentityProviderOnOwnedProductsUpdated;
-            loginPopup = new LoginPopup();
-            loginPopup.SystemDecorations = SystemDecorations.BorderOnly;
-            loginPopup.CanMinimize = false;
-            loginPopup.DataContext = viewModel.UserViewModel;
-            loginPopup.ShowStandalone();
-            loginPopup.Closed += (_, _) =>
-            {
-                if (IsFoundersPackOwner())
-                {
-                    desktop.MainWindow = new MainWindow(extensionLoader);
-                    desktop.MainWindow.Show();
-                }
-                else
-                {
-                    desktop.Shutdown();
-                }
-            };
-        }
-        else
-        {
-            
-            desktop.MainWindow = new MainWindow(extensionLoader);
-            desktop.MainWindow.Show();
-        }
-#else
         desktop.MainWindow = new MainWindow(extensionLoader);
         desktop.MainWindow.Show();
-#endif
-    }
-
-    private void IdentityProviderOnOwnedProductsUpdated(List<ProductData> obj)
-    {
-        if (IsFoundersPackOwner())
-        {
-            IPlatform.Current.IdentityProvider.OwnedProductsUpdated -= IdentityProviderOnOwnedProductsUpdated;
-            loginPopup?.Close();
-            loginPopup = null!;
-        }
-    }
-
-    private static bool IsFoundersPackOwner()
-    {
-        return IPlatform.Current.IdentityProvider.IsLoggedIn &&
-               IPlatform.Current.AdditionalContentProvider.IsContentOwned("PixiEditor.FoundersPack");
     }
 
     private void InitPlatform()

+ 25 - 0
src/PixiEditor/Models/ExtensionServices/UserDataProvider.cs

@@ -0,0 +1,25 @@
+using PixiEditor.Extensions.CommonApi.User;
+using PixiEditor.ViewModels.SubViewModels;
+using PixiEditor.ViewModels.SubViewModels.AdditionalContent;
+
+namespace PixiEditor.Models.ExtensionServices;
+
+internal class UserDataProvider : IUserDataProvider
+{
+    public UserViewModel UserViewModel { get; }
+    public AdditionalContentViewModel AdditionalContentViewModel { get; }
+    public bool IsLoggedIn => UserViewModel.IsLoggedIn;
+    public string Username => UserViewModel.Username;
+    public string AccountProviderName => UserViewModel.IdentityProvider?.ProviderName ?? string.Empty;
+
+    public UserDataProvider(UserViewModel userViewModel, AdditionalContentViewModel additionalContentViewModel)
+    {
+        UserViewModel = userViewModel;
+        AdditionalContentViewModel = additionalContentViewModel;
+    }
+
+    public string[] GetOwnedContent()
+    {
+        return UserViewModel.IdentityProvider?.User?.OwnedProducts?.Select(x => x.Id).ToArray() ?? [];
+    }
+}

+ 2 - 0
src/PixiEditor/ViewModels/SubViewModels/ExtensionsViewModel.cs

@@ -8,6 +8,7 @@ using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.ExtensionServices;
 using PixiEditor.UI.Common.Localization;
 using PixiEditor.Views;
+using PixiEditor.Views.Auth;
 using PixiEditor.Views.Windows;
 
 namespace PixiEditor.ViewModels.SubViewModels;
@@ -69,6 +70,7 @@ internal class ExtensionsViewModel : SubViewModel<ViewModelMain>
     {
         windowProvider?.RegisterWindow<PalettesBrowser>();
         windowProvider?.RegisterWindow<HelloTherePopup>();
+        windowProvider?.RegisterWindow<LoginPopup>();
     }
 
     private void Owner_OnEarlyStartupEvent()

+ 8 - 4
src/PixiEditor/ViewModels/SubViewModels/FileViewModel.cs

@@ -153,9 +153,12 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
             if (preferences.GetLocalPreference("OnboardingV2Shown", false) == false)
             {
                 preferences.UpdateLocalPreference("OnboardingV2Shown", true);
-                Owner.WindowSubViewModel.OpenOnboardingWindow().Closed += (sender, eventArgs) =>
+                Owner.WindowSubViewModel.OpenAccountWindow(true).Closed += (sender, eventArgs) =>
                 {
-                    Owner.InvokeUserReadyEvent();
+                    Owner.WindowSubViewModel.OpenOnboardingWindow().Closed += (_, _) =>
+                    {
+                        Owner.InvokeUserReadyEvent();
+                    };
                 };
             }
             else if (preferences!.GetPreference("ShowStartupWindow", true))
@@ -620,7 +623,8 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
                     {
                         Dispatcher.UIThread.Post(() =>
                         {
-                            if (IPreferences.Current.GetPreference<bool>(PreferencesConstants.OpenDirectoryOnExport, true))
+                            if (IPreferences.Current.GetPreference<bool>(PreferencesConstants.OpenDirectoryOnExport,
+                                    true))
                             {
                                 IOperatingSystem.Current.OpenFolder(result.finalPath);
                             }
@@ -700,7 +704,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
             PreferencesConstants.AutosaveEnabled,
             PreferencesConstants.AutosaveEnabledDefault);
 
-        if(!autosaveEnabled)
+        if (!autosaveEnabled)
         {
             return;
         }

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/UserViewModel.cs

@@ -149,7 +149,7 @@ internal class UserViewModel : SubViewModel<ViewModelMain>
             }
             else
             {
-                bool productDownloadedAtLeastOnce = IPreferences.Current.GetPreference<bool>(
+                bool productDownloadedAtLeastOnce = IPreferences.Current.GetLocalPreference<bool>(
                     $"product_{product.Id}_downloaded_at_least_once", false);
                 if (!productDownloadedAtLeastOnce)
                 {

+ 9 - 2
src/PixiEditor/ViewModels/SubViewModels/WindowViewModel.cs

@@ -291,9 +291,16 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>, IWindowHandler
 
     [Commands_Command.Basic("PixiEditor.Window.OpenAccountWindow", "OPEN_ACCOUNT_WINDOW", "OPEN_ACCOUNT_WINDOW",
         MenuItemOrder = 6, AnalyticsTrack = true)]
-    public void OpenAccountWindow()
+    public LoginPopup OpenAccountWindow(bool dialog = false)
     {
-        LoginPopup popup = new LoginPopup() { DataContext = Owner.UserViewModel };
+        LoginPopup popup = new LoginPopup();
+        if (dialog)
+        {
+            popup.ShowDialog(MainWindow.Current);
+            return popup;
+        }
+
         popup.Show();
+        return popup;
     }
 }

+ 4 - 2
src/PixiEditor/Views/Auth/LoginForm.axaml

@@ -55,7 +55,7 @@
                 <Setter Property="Foreground" Value="{StaticResource FoundersGradientBrushShifted}" />
             </Style>
         </Panel.Styles>
-        <StackPanel IsVisible="{Binding !IsLoggedIn}" VerticalAlignment="Top" Spacing="12">
+        <StackPanel IsVisible="{Binding !IsLoggedIn}" Name="RootPanel" VerticalAlignment="Top" Spacing="12">
             <TextBox Text="{Binding CurrentEmail, Mode=TwoWay}" Watermark="{ui:Translate Key=ENTER_EMAIL}" Name="Email"
                      IsVisible="{Binding !IsLoggedIn}" />
             <Button Name="LoginButton"
@@ -123,7 +123,9 @@
                     Classes="foundersButton"
                     Padding="8, 4" />
         </StackPanel>
-        <DockPanel LastChildFill="True" IsVisible="{Binding IsLoggedIn}" Margin="5">
+        <DockPanel LastChildFill="True"
+                   Name="LoggedInPanel"
+                   IsVisible="{Binding IsLoggedIn}" Margin="5">
             <Button IsVisible="{Binding IdentityProvider.AllowsLogout}"
                     Content="{ui:Translate Key=LOGOUT}"
                     DockPanel.Dock="Bottom"

+ 2 - 0
src/PixiEditor/Views/Auth/LoginPopup.axaml.cs

@@ -3,6 +3,7 @@ using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Markup.Xaml;
+using PixiEditor.ViewModels;
 using PixiEditor.ViewModels.SubViewModels;
 using PixiEditor.Views.Dialogs;
 
@@ -13,6 +14,7 @@ public partial class LoginPopup : PixiEditorPopup
     public LoginPopup()
     {
         InitializeComponent();
+        DataContext = ViewModelMain.Current.UserViewModel;
     }
 
     protected override async void OnGotFocus(GotFocusEventArgs e)