diff --git a/FluentTerminal.App/App.xaml.cs b/FluentTerminal.App/App.xaml.cs index cf679c4..3d60690 100644 --- a/FluentTerminal.App/App.xaml.cs +++ b/FluentTerminal.App/App.xaml.cs @@ -1,8 +1,9 @@ using Autofac; +using FluentTerminal.App.Services; +using FluentTerminal.App.Services.Implementation; using FluentTerminal.App.ViewModels; using FluentTerminal.App.Views; using System; -using System.Collections.Generic; using System.Threading.Tasks; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; @@ -21,9 +22,10 @@ namespace FluentTerminal.App public static BackgroundTaskDeferral AppServiceDeferral = null; public static AppServiceConnection Connection = null; public static App Instance; + private bool _alreadyLaunched; private IContainer _container; + private int _openTerminalWindows; private int? _settingsWindowId; - private List _windowIds = new List(); public App() { @@ -31,54 +33,68 @@ namespace FluentTerminal.App Instance = this; var builder = new ContainerBuilder(); + builder.RegisterType().As().InstancePerDependency(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); builder.RegisterType().InstancePerDependency(); + builder.RegisterType().InstancePerDependency(); _container = builder.Build(); } - public async Task CreateNewWindow(Type pageType, bool ExtendViewIntoTitleBar) + public async Task CreateNewTerminalWindow() { - var newView = CoreApplication.CreateNewView(); - int windowId = 0; - await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => - { - CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = ExtendViewIntoTitleBar; - var frame = new Frame(); - frame.Navigate(pageType, null); - Window.Current.Content = frame; - Window.Current.Activate(); - - windowId = ApplicationView.GetForCurrentView().Id; - }); - - for (int i = _windowIds.Count - 1; i >= 0; i--) - if (await ApplicationViewSwitcher.TryShowAsStandaloneAsync( - windowId, ViewSizePreference.Default, - _windowIds[i], ViewSizePreference.Default) - ) break; - - _windowIds.Add(windowId); - return windowId; + var id = await CreateSecondaryView(typeof(MainPage), true); + await ApplicationViewSwitcher.TryShowAsStandaloneAsync(id); } - public T Resolve() + public void SettingsWindowClosed() { - return _container.Resolve(); - } - - public async Task ShowNew() - { - var id = await CreateNewWindow(typeof(MainPage), true); - bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(id); + _settingsWindowId = null; } public async Task ShowSettings() { if (_settingsWindowId == null) { - _settingsWindowId = await CreateNewWindow(typeof(Views.SettingsPage), false); + _settingsWindowId = await CreateSecondaryView(typeof(SettingsPage), true); + } + await ApplicationViewSwitcher.TryShowAsStandaloneAsync(_settingsWindowId.Value); + } + + public void TerminalWindowClosed() + { + _openTerminalWindows--; + } + + protected override async void OnActivated(IActivatedEventArgs args) + { + if (args is CommandLineActivatedEventArgs commandLineActivated) + { + var argument = commandLineActivated.Operation.Arguments.Trim(); + if (_alreadyLaunched) + { + if (argument == "settings") + { + await ShowSettings(); + } + else if (argument == "new") + { + await CreateNewTerminalWindow(); + } + } + else + { + if (argument == "settings") + { + CreateMainView(typeof(SettingsPage), true); + } + else if (argument == "new") + { + CreateMainView(typeof(MainPage), true); + } + } } - bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(_settingsWindowId.Value); } protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args) @@ -97,9 +113,21 @@ namespace FluentTerminal.App { var launch = FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync().AsTask(); var clearCache = WebView.ClearTemporaryWebDataAsync().AsTask(); - await Task.WhenAll(launch, clearCache); + if (!_alreadyLaunched) + { + CreateMainView(typeof(MainPage), true); + Window.Current.Activate(); + } + else if (_openTerminalWindows == 0) + { + await CreateSecondaryView(typeof(MainPage), true); + } + } + + private void CreateMainView(Type pageType, bool extendViewIntoTitleBar) + { Frame rootFrame = Window.Current.Content as Frame; if (rootFrame == null) { @@ -109,17 +137,53 @@ namespace FluentTerminal.App if (rootFrame.Content == null) { - CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true; - rootFrame.Navigate(typeof(MainPage), e.Arguments); - _windowIds.Add(ApplicationView.GetForCurrentView().Id); - } - else if (false) //todo: add option to create new windows on launch - { - await CreateNewWindow(typeof(MainPage), true); + CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = extendViewIntoTitleBar; + rootFrame.Navigate(pageType, GetViewModelForPageType(pageType)); + + if (pageType == typeof(MainPage)) + { + _openTerminalWindows++; + } } + _alreadyLaunched = true; Window.Current.Activate(); } + private async Task CreateSecondaryView(Type pageType, bool ExtendViewIntoTitleBar) + { + int windowId = 0; + await CoreApplication.CreateNewView().Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + { + CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = ExtendViewIntoTitleBar; + var frame = new Frame(); + frame.Navigate(pageType, GetViewModelForPageType(pageType)); + Window.Current.Content = frame; + Window.Current.Activate(); + + windowId = ApplicationView.GetForCurrentView().Id; + }); + + if (pageType == typeof(MainPage)) + { + _openTerminalWindows++; + } + + return windowId; + } + + private object GetViewModelForPageType(Type pageType) + { + if (pageType == typeof(MainPage)) + { + return _container.Resolve(); + } + else if (pageType == typeof(SettingsPage)) + { + return _container.Resolve(); + } + throw new Exception("No ViewModel defined for Page: " + pageType.Name); + } + private void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args) { if (args.Request.Message.ContainsKey("exit")) diff --git a/FluentTerminal.App/Assets/LockScreenLogo.scale-200.png b/FluentTerminal.App/Assets/LockScreenLogo.scale-200.png deleted file mode 100644 index 735f57a..0000000 Binary files a/FluentTerminal.App/Assets/LockScreenLogo.scale-200.png and /dev/null differ diff --git a/FluentTerminal.App/Assets/SplashScreen.scale-200.png b/FluentTerminal.App/Assets/SplashScreen.scale-200.png deleted file mode 100644 index 023e7f1..0000000 Binary files a/FluentTerminal.App/Assets/SplashScreen.scale-200.png and /dev/null differ diff --git a/FluentTerminal.App/Assets/Square150x150Logo.scale-200.png b/FluentTerminal.App/Assets/Square150x150Logo.scale-200.png deleted file mode 100644 index af49fec..0000000 Binary files a/FluentTerminal.App/Assets/Square150x150Logo.scale-200.png and /dev/null differ diff --git a/FluentTerminal.App/Assets/Square44x44Logo.scale-200.png b/FluentTerminal.App/Assets/Square44x44Logo.scale-200.png deleted file mode 100644 index ce342a2..0000000 Binary files a/FluentTerminal.App/Assets/Square44x44Logo.scale-200.png and /dev/null differ diff --git a/FluentTerminal.App/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/FluentTerminal.App/Assets/Square44x44Logo.targetsize-24_altform-unplated.png deleted file mode 100644 index f6c02ce..0000000 Binary files a/FluentTerminal.App/Assets/Square44x44Logo.targetsize-24_altform-unplated.png and /dev/null differ diff --git a/FluentTerminal.App/Assets/StoreLogo.png b/FluentTerminal.App/Assets/StoreLogo.png deleted file mode 100644 index 7385b56..0000000 Binary files a/FluentTerminal.App/Assets/StoreLogo.png and /dev/null differ diff --git a/FluentTerminal.App/Assets/Wide310x150Logo.scale-200.png b/FluentTerminal.App/Assets/Wide310x150Logo.scale-200.png deleted file mode 100644 index 288995b..0000000 Binary files a/FluentTerminal.App/Assets/Wide310x150Logo.scale-200.png and /dev/null differ diff --git a/FluentTerminal.App/Converters/FalseToVisibleConverter.cs b/FluentTerminal.App/Converters/FalseToVisibleConverter.cs new file mode 100644 index 0000000..0f3587b --- /dev/null +++ b/FluentTerminal.App/Converters/FalseToVisibleConverter.cs @@ -0,0 +1,27 @@ +using System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Data; + +namespace FluentTerminal.App.Converters +{ + public class FalseToVisibleConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is Boolean collapsed) + { + return collapsed ? Visibility.Collapsed : Visibility.Visible; + } + return null; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + if (value is Visibility visibility) + { + return visibility == Visibility.Collapsed; + } + return null; + } + } +} diff --git a/FluentTerminal.App/FluentTerminal.App.csproj b/FluentTerminal.App/FluentTerminal.App.csproj index 64821ee..d0bec3c 100644 --- a/FluentTerminal.App/FluentTerminal.App.csproj +++ b/FluentTerminal.App/FluentTerminal.App.csproj @@ -19,7 +19,7 @@ true FluentTerminal.App_StoreKey.pfx 8FA8EF727E767FAE5409DDD1694C1CEE054A2962 - True + False Always x86|x64 @@ -99,11 +99,20 @@ App.xaml + - - + + + + + + + + + + @@ -113,6 +122,21 @@ SettingsPage.xaml + + ColorSettings.xaml + + + FontSettings.xaml + + + GeneralSettings.xaml + + + KeyBindingSettings.xaml + + + ShellSettings.xaml + TerminalView.xaml @@ -123,6 +147,8 @@ + + @@ -173,13 +199,6 @@ - - - - - - - @@ -194,11 +213,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -237,6 +352,26 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -262,6 +397,10 @@ + + {5EA40453-A0AA-449E-8406-D6EA51D7E70E} + FluentTerminal.Models + {BAAE6239-343F-454C-977F-F81F91715961} FluentTerminal.RuntimeComponent diff --git a/FluentTerminal.App/Models/TerminalConfiguration.cs b/FluentTerminal.App/Models/TerminalConfiguration.cs deleted file mode 100644 index 5bfbbba..0000000 --- a/FluentTerminal.App/Models/TerminalConfiguration.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace FluentTerminal.App.Models -{ - public class TerminalConfiguration - { - public bool CursorBlinking { get; set; } - public string FontFamily { get; set; } - public int FontSize { get; set; } - } -} diff --git a/FluentTerminal.App/Models/TerminalSize.cs b/FluentTerminal.App/Models/TerminalSize.cs deleted file mode 100644 index 170a70f..0000000 --- a/FluentTerminal.App/Models/TerminalSize.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace FluentTerminal.App.Models -{ - public class TerminalSize - { - public TerminalSize(int columns, int rows) - { - Columns = columns; - Rows = rows; - } - - public int Columns { get; } - public int Rows { get; } - } -} \ No newline at end of file diff --git a/FluentTerminal.App/Package.appxmanifest b/FluentTerminal.App/Package.appxmanifest index 007eaa3..2f49700 100644 --- a/FluentTerminal.App/Package.appxmanifest +++ b/FluentTerminal.App/Package.appxmanifest @@ -30,6 +30,11 @@ + + + + + diff --git a/FluentTerminal.App/Services/IDefaultValueProvider.cs b/FluentTerminal.App/Services/IDefaultValueProvider.cs new file mode 100644 index 0000000..1637116 --- /dev/null +++ b/FluentTerminal.App/Services/IDefaultValueProvider.cs @@ -0,0 +1,9 @@ +using FluentTerminal.Models; + +namespace FluentTerminal.App.Services +{ + public interface IDefaultValueProvider + { + ShellConfiguration GetDefaultShellConfiguration(); + } +} diff --git a/FluentTerminal.App/Services/ISettingsService.cs b/FluentTerminal.App/Services/ISettingsService.cs new file mode 100644 index 0000000..e7668da --- /dev/null +++ b/FluentTerminal.App/Services/ISettingsService.cs @@ -0,0 +1,10 @@ +using FluentTerminal.Models; + +namespace FluentTerminal.App.Services +{ + public interface ISettingsService + { + ShellConfiguration GetShellConfiguration(); + void SaveShellConfiguration(ShellConfiguration spawnConfiguration); + } +} diff --git a/FluentTerminal.App/Services/ITerminalService.cs b/FluentTerminal.App/Services/ITerminalService.cs new file mode 100644 index 0000000..c0d5aca --- /dev/null +++ b/FluentTerminal.App/Services/ITerminalService.cs @@ -0,0 +1,11 @@ +using FluentTerminal.Models; +using System.Threading.Tasks; + +namespace FluentTerminal.App.Services +{ + public interface ITerminalService + { + Task CreateTerminal(TerminalSize size, ShellConfiguration shellConfiguration); + Task ResizeTerminal(int id, TerminalSize size); + } +} diff --git a/FluentTerminal.App/Services/Implementation/DefaultValueProvider.cs b/FluentTerminal.App/Services/Implementation/DefaultValueProvider.cs new file mode 100644 index 0000000..aad8553 --- /dev/null +++ b/FluentTerminal.App/Services/Implementation/DefaultValueProvider.cs @@ -0,0 +1,19 @@ +using FluentTerminal.Models; +using FluentTerminal.Models.Enums; + +namespace FluentTerminal.App.Services.Implementation +{ + internal class DefaultValueProvider : IDefaultValueProvider + { + public ShellConfiguration GetDefaultShellConfiguration() + { + return new ShellConfiguration + { + Shell = ShellType.PowerShell, + CustomShellLocation = string.Empty, + WorkingDirectory = string.Empty, + Arguments = string.Empty + }; + } + } +} diff --git a/FluentTerminal.App/Services/Implementation/SettingsService.cs b/FluentTerminal.App/Services/Implementation/SettingsService.cs new file mode 100644 index 0000000..e337734 --- /dev/null +++ b/FluentTerminal.App/Services/Implementation/SettingsService.cs @@ -0,0 +1,38 @@ +using FluentTerminal.App.Utilities; +using FluentTerminal.Models; +using Windows.Storage; + +namespace FluentTerminal.App.Services.Implementation +{ + internal class SettingsService : ISettingsService + { + private readonly IDefaultValueProvider _defaultValueProvider; + private ApplicationDataContainer _localSettings; + private ApplicationDataContainer _roamingSettings; + + public SettingsService(IDefaultValueProvider defaultValueProvider) + { + _defaultValueProvider = defaultValueProvider; + _localSettings = ApplicationData.Current.LocalSettings; + _roamingSettings = ApplicationData.Current.RoamingSettings; + ApplicationData.Current.DataChanged += OnDataChanged; + } + + private void OnDataChanged(ApplicationData sender, object args) + { + //todo handle data changed for roaming settings + } + + public ShellConfiguration GetShellConfiguration() + { + return _localSettings.ReadValueFromJson(nameof(ShellConfiguration), _defaultValueProvider.GetDefaultShellConfiguration()); + } + + public void SaveShellConfiguration(ShellConfiguration spawnConfiguration) + { + _localSettings.WriteValueAsJson(nameof(ShellConfiguration), spawnConfiguration); + } + + + } +} diff --git a/FluentTerminal.App/Services/Implementation/TerminalService.cs b/FluentTerminal.App/Services/Implementation/TerminalService.cs new file mode 100644 index 0000000..28ed3e8 --- /dev/null +++ b/FluentTerminal.App/Services/Implementation/TerminalService.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; +using FluentTerminal.App.Utilities; +using FluentTerminal.Models; +using Windows.Web.Http; + +namespace FluentTerminal.App.Services.Implementation +{ + internal class TerminalService : ITerminalService + { + private readonly HttpClient _httpClient; + + public TerminalService() + { + _httpClient = new HttpClient(); + } + + public async Task CreateTerminal(TerminalSize size, ShellConfiguration shellConfiguration) + { + var request = new CreateTerminalRequest + { + Size = size, + Configuration = shellConfiguration + }; + + var response = await _httpClient.PostAsync(new Uri($"http://localhost:9000/terminals"), HttpJsonContent.From(request)); + var createTerminalResponse = await response.Content.ReadAs(); + + return createTerminalResponse; + } + + public Task ResizeTerminal(int id, TerminalSize size) + { + return _httpClient.PostAsync(new Uri($"http://localhost:9000/terminals/{id}/resize"), HttpJsonContent.From(size)).AsTask(); + } + } +} diff --git a/FluentTerminal.App/Utilities/ApplicationDataContainerExtensions.cs b/FluentTerminal.App/Utilities/ApplicationDataContainerExtensions.cs new file mode 100644 index 0000000..62c797b --- /dev/null +++ b/FluentTerminal.App/Utilities/ApplicationDataContainerExtensions.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json; +using Windows.Storage; + +namespace FluentTerminal.App.Utilities +{ + public static class ApplicationDataContainerExtensions + { + public static T ReadValueFromJson(this ApplicationDataContainer container, string name, T fallbackValue) + { + if (container.Values.TryGetValue(name, out object value)) + { + JsonConvert.PopulateObject((string)value, fallbackValue); + } + return fallbackValue; + } + + public static void WriteValueAsJson(this ApplicationDataContainer container, string name, T value) + { + var serialized = JsonConvert.SerializeObject(value); + container.Values[name] = serialized; + } + } +} diff --git a/FluentTerminal.App/Utilities/HttpJsonContent.cs b/FluentTerminal.App/Utilities/HttpJsonContent.cs new file mode 100644 index 0000000..51ecd19 --- /dev/null +++ b/FluentTerminal.App/Utilities/HttpJsonContent.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; +using Windows.Web.Http; + +namespace FluentTerminal.App.Utilities +{ + public static class HttpJsonContent + { + public static HttpStringContent From(object content) + { + var serialized = JsonConvert.SerializeObject(content); + return new HttpStringContent(serialized, Windows.Storage.Streams.UnicodeEncoding.Utf8, "application/json"); + } + } +} diff --git a/FluentTerminal.App/Utilities/IHttpContentExtensions.cs b/FluentTerminal.App/Utilities/IHttpContentExtensions.cs new file mode 100644 index 0000000..29213ab --- /dev/null +++ b/FluentTerminal.App/Utilities/IHttpContentExtensions.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; +using System; +using System.Threading.Tasks; +using Windows.Web.Http; + +namespace FluentTerminal.App.Utilities +{ + public static class IHttpContentExtensions + { + public static async Task ReadAs(this IHttpContent content) + { + var contentBody = await content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(contentBody); + } + } +} diff --git a/FluentTerminal.App/ViewModels/MainViewModel.cs b/FluentTerminal.App/ViewModels/MainViewModel.cs index 60b8671..35e03a1 100644 --- a/FluentTerminal.App/ViewModels/MainViewModel.cs +++ b/FluentTerminal.App/ViewModels/MainViewModel.cs @@ -1,24 +1,32 @@ -using GalaSoft.MvvmLight; +using FluentTerminal.App.Services; +using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.Command; using System.Collections.ObjectModel; +using System.Threading.Tasks; namespace FluentTerminal.App.ViewModels { public class MainViewModel : ViewModelBase { + private readonly ISettingsService _settingsService; + private readonly ITerminalService _terminalService; private TerminalViewModel _selectedTerminal; private string _title; - public MainViewModel() + public MainViewModel(ISettingsService settingsService, ITerminalService terminalService) { + _settingsService = settingsService; + _terminalService = terminalService; Title = "Fluent Terminal"; AddTerminalCommand = new RelayCommand(AddTerminal); + ShowSettingsCommand = new RelayCommand(async () => await ShowSettings()); AddTerminal(); } public RelayCommand AddTerminalCommand { get; } + public RelayCommand ShowSettingsCommand { get; } public TerminalViewModel SelectedTerminal { @@ -36,7 +44,7 @@ namespace FluentTerminal.App.ViewModels private void AddTerminal() { - var terminal = new TerminalViewModel(); + var terminal = new TerminalViewModel(_settingsService, _terminalService); terminal.PropertyChanged += (s, e) => { if (e.PropertyName == nameof(TerminalViewModel.Title) && Terminals.Count == 1) @@ -48,5 +56,10 @@ namespace FluentTerminal.App.ViewModels SelectedTerminal = terminal; } + + private Task ShowSettings() + { + return App.Instance.ShowSettings(); + } } } \ No newline at end of file diff --git a/FluentTerminal.App/ViewModels/SettingsViewModel.cs b/FluentTerminal.App/ViewModels/SettingsViewModel.cs new file mode 100644 index 0000000..fb746b3 --- /dev/null +++ b/FluentTerminal.App/ViewModels/SettingsViewModel.cs @@ -0,0 +1,157 @@ +using FluentTerminal.App.Services; +using FluentTerminal.Models; +using FluentTerminal.Models.Enums; +using GalaSoft.MvvmLight; +using GalaSoft.MvvmLight.Command; +using System; +using System.Threading.Tasks; +using Windows.Storage.Pickers; +using Windows.UI.Popups; + +namespace FluentTerminal.App.ViewModels +{ + public class SettingsViewModel : ViewModelBase + { + private readonly ISettingsService _settingsService; + private readonly IDefaultValueProvider _defaultValueProvider; + private ShellConfiguration _shellConfiguration; + + public SettingsViewModel(ISettingsService settingsService, IDefaultValueProvider defaultValueProvider) + { + _settingsService = settingsService; + _defaultValueProvider = defaultValueProvider; + + BrowseForCustomShellCommand = new RelayCommand(async () => await BrowseForCustomShell()); + BrowseForWorkingDirectoryCommand = new RelayCommand(async () => await BrowseForWorkingDirectory()); + RestoreDefaultsCommand = new RelayCommand(async (area) => await RestoreDefaults(area)); + + _shellConfiguration = _settingsService.GetShellConfiguration(); + ShellType = _shellConfiguration.Shell; + } + + public RelayCommand BrowseForCustomShellCommand { get; } + public RelayCommand BrowseForWorkingDirectoryCommand { get; } + public RelayCommand RestoreDefaultsCommand { get; } + + public bool CMDIsSelected + { + get => ShellType == ShellType.CMD; + set => ShellType = ShellType.CMD; + } + + public bool CustomShellIsSelected + { + get => ShellType == ShellType.Custom; + set => ShellType = ShellType.Custom; + } + + public string CustomShellLocation + { + get => _shellConfiguration.CustomShellLocation; + set + { + if (_shellConfiguration.CustomShellLocation != value) + { + _shellConfiguration.CustomShellLocation = value; + _settingsService.SaveShellConfiguration(_shellConfiguration); + RaisePropertyChanged(); + } + } + } + + public string WorkingDirectory + { + get => _shellConfiguration.WorkingDirectory; + set + { + if (_shellConfiguration.WorkingDirectory != value) + { + _shellConfiguration.WorkingDirectory = value; + _settingsService.SaveShellConfiguration(_shellConfiguration); + RaisePropertyChanged(); + } + } + } + + public string Arguments + { + get => _shellConfiguration.Arguments; + set + { + if (_shellConfiguration.Arguments != value) + { + _shellConfiguration.Arguments = value; + _settingsService.SaveShellConfiguration(_shellConfiguration); + RaisePropertyChanged(); + } + } + } + + public bool PoweShellIsSelected + { + get => ShellType == ShellType.PowerShell; + set => ShellType = ShellType.PowerShell; + } + + public ShellType ShellType + { + get => _shellConfiguration.Shell; + set + { + if (_shellConfiguration.Shell != value) + { + _shellConfiguration.Shell = value; + _settingsService.SaveShellConfiguration(_shellConfiguration); + RaisePropertyChanged(); + RaisePropertyChanged(nameof(CustomShellIsSelected)); + RaisePropertyChanged(nameof(PoweShellIsSelected)); + RaisePropertyChanged(nameof(CMDIsSelected)); + } + } + } + + private async Task BrowseForCustomShell() + { + var picker = new FileOpenPicker(); + picker.SuggestedStartLocation = PickerLocationId.ComputerFolder; + picker.FileTypeFilter.Add(".exe"); + + var file = await picker.PickSingleFileAsync().AsTask(); + if (file != null) + { + CustomShellLocation = file.Path; + } + } + + private async Task BrowseForWorkingDirectory() + { + var picker = new FolderPicker(); + picker.FileTypeFilter.Add(".whatever"); // else a ComException is thrown + picker.SuggestedStartLocation = PickerLocationId.ComputerFolder; + + var folder = await picker.PickSingleFolderAsync(); + if (folder != null) + { + WorkingDirectory = folder.Path; + } + } + + private async Task RestoreDefaults(string area) + { + var dialog = new MessageDialog("Are you sure you want to restore default values for this page?", "Please confirm"); + dialog.Commands.Add(new UICommand("OK")); + dialog.Commands.Add(new UICommand("Cancel")); + + var result = await dialog.ShowAsync(); + + if (result.Label == "OK" && area == "shell") + { + var defaults = _defaultValueProvider.GetDefaultShellConfiguration(); + ShellType = defaults.Shell; + CustomShellLocation = defaults.CustomShellLocation; + WorkingDirectory = defaults.WorkingDirectory; + Arguments = defaults.Arguments; + } + } + } +} \ No newline at end of file diff --git a/FluentTerminal.App/ViewModels/TerminalViewModel.cs b/FluentTerminal.App/ViewModels/TerminalViewModel.cs index 1826458..919ebbc 100644 --- a/FluentTerminal.App/ViewModels/TerminalViewModel.cs +++ b/FluentTerminal.App/ViewModels/TerminalViewModel.cs @@ -1,21 +1,24 @@ -using FluentTerminal.App.Models; +using FluentTerminal.App.Services; using FluentTerminal.App.Views; +using FluentTerminal.Models; using GalaSoft.MvvmLight; using System; using System.Threading.Tasks; +using Windows.UI.Popups; using Windows.UI.Xaml; -using Windows.Web.Http; namespace FluentTerminal.App.ViewModels { public class TerminalViewModel : ViewModelBase { - private static HttpClient _httpClient; private ITerminalView _terminalView; private string _title; private string _resizeOverlayContent; private bool _showResizeOverlay; private DispatcherTimer _resizeOverlayTimer; + private readonly ISettingsService _settingsService; + private readonly ITerminalService _terminalService; + private int _terminalId; public int Id { get; private set; } @@ -32,11 +35,6 @@ namespace FluentTerminal.App.ViewModels get => _showResizeOverlay; set { - if (!Initialized) - { - return; - } - Set(ref _showResizeOverlay, value); if (value) { @@ -55,8 +53,10 @@ namespace FluentTerminal.App.ViewModels set => Set(ref _resizeOverlayContent, value); } - public TerminalViewModel() + public TerminalViewModel(ISettingsService settingsService, ITerminalService terminalService) { + _settingsService = settingsService; + _terminalService = terminalService; _resizeOverlayTimer = new DispatcherTimer(); _resizeOverlayTimer.Interval = new TimeSpan(0, 0, 2); _resizeOverlayTimer.Tick += OnResizeOverlayTimerFinished; @@ -68,25 +68,29 @@ namespace FluentTerminal.App.ViewModels ShowResizeOverlay = false; } - static TerminalViewModel() - { - _httpClient = new HttpClient(); - } - public async Task OnViewIsReady(ITerminalView terminalView) { _terminalView = terminalView; - _terminalView.TerminalSizeChanged += OnTerminalSizeChanged; - _terminalView.TerminalTitleChanged += OnTerminalTitleChanged; - var size = await _terminalView.CreateTerminal(null); + var size = await _terminalView.CreateTerminal(); + var configuration = _settingsService.GetShellConfiguration(); - var response = await _httpClient.PostAsync(new Uri($"http://localhost:9000/terminals?cols={size.Columns}&rows={size.Rows}"), null); - var url = await response.Content.ReadAsStringAsync(); - Id = int.Parse(url.Split(":")[2].Trim('"')); + var response = await _terminalService.CreateTerminal(size, configuration); - await _terminalView.ConnectToSocket(url); - Initialized = true; + if (response.Success) + { + _terminalId = response.Id; + _terminalView.TerminalSizeChanged += OnTerminalSizeChanged; + _terminalView.TerminalTitleChanged += OnTerminalTitleChanged; + + await _terminalView.ConnectToSocket(response.WebSocketUrl); + Initialized = true; + } + else + { + var dialog = new MessageDialog(response.Error, "Error"); + await dialog.ShowAsync(); + } } private void OnTerminalTitleChanged(object sender, string e) @@ -96,9 +100,13 @@ namespace FluentTerminal.App.ViewModels private async void OnTerminalSizeChanged(object sender, TerminalSize e) { + if (!Initialized) + { + return; + } ResizeOverlayContent = $"{e.Columns} x {e.Rows}"; ShowResizeOverlay = true; - await _httpClient.PostAsync(new Uri($"http://localhost:9000/terminals/{Id}/size?cols={e.Columns}&rows={e.Rows}"), null); + await _terminalService.ResizeTerminal(_terminalId, e); } } } diff --git a/FluentTerminal.App/Views/ITerminalView.cs b/FluentTerminal.App/Views/ITerminalView.cs index fdece5e..e14cf11 100644 --- a/FluentTerminal.App/Views/ITerminalView.cs +++ b/FluentTerminal.App/Views/ITerminalView.cs @@ -1,4 +1,4 @@ -using FluentTerminal.App.Models; +using FluentTerminal.Models; using System; using System.Threading.Tasks; @@ -9,7 +9,7 @@ namespace FluentTerminal.App.Views event EventHandler TerminalSizeChanged; event EventHandler TerminalTitleChanged; - Task CreateTerminal(TerminalConfiguration configuration); + Task CreateTerminal(); Task ConnectToSocket(string url); } } \ No newline at end of file diff --git a/FluentTerminal.App/Views/MainPage.xaml b/FluentTerminal.App/Views/MainPage.xaml index d307ddf..12d9fae 100644 --- a/FluentTerminal.App/Views/MainPage.xaml +++ b/FluentTerminal.App/Views/MainPage.xaml @@ -31,16 +31,17 @@ - + diff --git a/FluentTerminal.App/Views/MainPage.xaml.cs b/FluentTerminal.App/Views/MainPage.xaml.cs index b06e38e..fe083b6 100644 --- a/FluentTerminal.App/Views/MainPage.xaml.cs +++ b/FluentTerminal.App/Views/MainPage.xaml.cs @@ -3,9 +3,11 @@ using System.ComponentModel; using Windows.ApplicationModel.Core; using Windows.UI; using Windows.UI.Core; +using Windows.UI.Core.Preview; using Windows.UI.ViewManagement; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; namespace FluentTerminal.App.Views { @@ -32,11 +34,10 @@ namespace FluentTerminal.App.Views } } - public MainViewModel ViewModel { get; } + public MainViewModel ViewModel { get; private set; } public MainPage() { - ViewModel = App.Instance.Resolve(); InitializeComponent(); Root.DataContext = this; SetTitleBarColors(); @@ -44,6 +45,20 @@ namespace FluentTerminal.App.Views Loaded += OnLoaded; Unloaded += OnUnloaded; Window.Current.Activated += OnWindowActivated; + SystemNavigationManagerPreview.GetForCurrentView().CloseRequested += OnCloseRequest; + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (e.Parameter is MainViewModel viewModel) + { + ViewModel = viewModel; + } + } + + private void OnCloseRequest(object sender, SystemNavigationCloseRequestedPreviewEventArgs e) + { + App.Instance.TerminalWindowClosed(); } private async void OnWindowActivated(object sender, WindowActivatedEventArgs e) @@ -91,10 +106,5 @@ namespace FluentTerminal.App.Views PropertyChanged(this, new PropertyChangedEventArgs(nameof(CoreTitleBarPadding))); } } - - private async void OnSettingsButtonClick(object sender, RoutedEventArgs e) - { - await App.Instance.ShowSettings(); - } } } diff --git a/FluentTerminal.App/Views/SettingsPage.xaml b/FluentTerminal.App/Views/SettingsPage.xaml index 69ec4c4..2dd9c3c 100644 --- a/FluentTerminal.App/Views/SettingsPage.xaml +++ b/FluentTerminal.App/Views/SettingsPage.xaml @@ -2,12 +2,95 @@ x:Class="FluentTerminal.App.Views.SettingsPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:converters="using:FluentTerminal.App.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="using:FluentTerminal.App.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/FluentTerminal.App/Views/SettingsPage.xaml.cs b/FluentTerminal.App/Views/SettingsPage.xaml.cs index 758e541..d312948 100644 --- a/FluentTerminal.App/Views/SettingsPage.xaml.cs +++ b/FluentTerminal.App/Views/SettingsPage.xaml.cs @@ -1,12 +1,87 @@ -using Windows.UI.Xaml.Controls; +using FluentTerminal.App.ViewModels; +using FluentTerminal.App.Views.SettingsPages; +using System.Linq; +using Windows.ApplicationModel.Core; +using Windows.UI; +using Windows.UI.Core.Preview; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; namespace FluentTerminal.App.Views { public sealed partial class SettingsPage : Page { + public double CoreTitleBarHeight { get; } + + public SettingsViewModel ViewModel { get; private set; } + public SettingsPage() { - this.InitializeComponent(); + InitializeComponent(); + Root.DataContext = this; + + ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar; + titleBar.ButtonBackgroundColor = Colors.Transparent; + titleBar.ButtonInactiveBackgroundColor = Colors.Transparent; + titleBar.ButtonForegroundColor = (Color)this.Resources["SystemBaseHighColor"]; + + ApplicationView.GetForCurrentView().Title = "Settings"; + + CoreApplicationViewTitleBar coreTitleBar = CoreApplication.GetCurrentView().TitleBar; + coreTitleBar.LayoutMetricsChanged += TitleBar_LayoutMetricsChanged; + CoreTitleBarHeight = coreTitleBar.Height; + + SystemNavigationManagerPreview.GetForCurrentView().CloseRequested += SettingsPage_CloseRequested; + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (e.Parameter is SettingsViewModel viewModel) + { + ViewModel = viewModel; + } + } + + private void SettingsPage_CloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e) + { + App.Instance.SettingsWindowClosed(); + } + + private void TitleBar_LayoutMetricsChanged(CoreApplicationViewTitleBar sender, object args) + { + AppTitle.Margin = new Thickness(CoreApplication.GetCurrentView().TitleBar.SystemOverlayLeftInset + 12, 8, 0, 0); + } + + private void NavigationView_Loaded(object sender, RoutedEventArgs e) + { + NavigationView.SelectedItem = NavigationView.MenuItems.Cast().FirstOrDefault(m => m.Tag.ToString() == "shell"); + } + + private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) + { + if (args.SelectedItem is NavigationViewItem item) + { + switch (item.Tag) + { + case "general": + ContentFrame.Navigate(typeof(GeneralSettings), ViewModel); + break; + case "shell": + ContentFrame.Navigate(typeof(ShellSettings), ViewModel); + break; + case "colors": + ContentFrame.Navigate(typeof(ColorSettings), ViewModel); + break; + case "font": + ContentFrame.Navigate(typeof(FontSettings), ViewModel); + break; + case "keyBindings": + ContentFrame.Navigate(typeof(KeyBindingSettings), ViewModel); + break; + } + } } } } diff --git a/FluentTerminal.App/Views/SettingsPages/ColorSettings.xaml b/FluentTerminal.App/Views/SettingsPages/ColorSettings.xaml new file mode 100644 index 0000000..10550ea --- /dev/null +++ b/FluentTerminal.App/Views/SettingsPages/ColorSettings.xaml @@ -0,0 +1,11 @@ + + + + diff --git a/FluentTerminal.App/Views/SettingsPages/ColorSettings.xaml.cs b/FluentTerminal.App/Views/SettingsPages/ColorSettings.xaml.cs new file mode 100644 index 0000000..0af2252 --- /dev/null +++ b/FluentTerminal.App/Views/SettingsPages/ColorSettings.xaml.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// Die Elementvorlage "Leere Seite" wird unter https://go.microsoft.com/fwlink/?LinkId=234238 dokumentiert. + +namespace FluentTerminal.App.Views.SettingsPages +{ + /// + /// Eine leere Seite, die eigenständig verwendet oder zu der innerhalb eines Rahmens navigiert werden kann. + /// + public sealed partial class ColorSettings : Page + { + public ColorSettings() + { + this.InitializeComponent(); + } + } +} diff --git a/FluentTerminal.App/Views/SettingsPages/FontSettings.xaml b/FluentTerminal.App/Views/SettingsPages/FontSettings.xaml new file mode 100644 index 0000000..3feafa0 --- /dev/null +++ b/FluentTerminal.App/Views/SettingsPages/FontSettings.xaml @@ -0,0 +1,11 @@ + + + + diff --git a/FluentTerminal.App/Views/SettingsPages/FontSettings.xaml.cs b/FluentTerminal.App/Views/SettingsPages/FontSettings.xaml.cs new file mode 100644 index 0000000..c5565d7 --- /dev/null +++ b/FluentTerminal.App/Views/SettingsPages/FontSettings.xaml.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// Die Elementvorlage "Leere Seite" wird unter https://go.microsoft.com/fwlink/?LinkId=234238 dokumentiert. + +namespace FluentTerminal.App.Views.SettingsPages +{ + /// + /// Eine leere Seite, die eigenständig verwendet oder zu der innerhalb eines Rahmens navigiert werden kann. + /// + public sealed partial class FontSettings : Page + { + public FontSettings() + { + this.InitializeComponent(); + } + } +} diff --git a/FluentTerminal.App/Views/SettingsPages/GeneralSettings.xaml b/FluentTerminal.App/Views/SettingsPages/GeneralSettings.xaml new file mode 100644 index 0000000..b4dd2dd --- /dev/null +++ b/FluentTerminal.App/Views/SettingsPages/GeneralSettings.xaml @@ -0,0 +1,10 @@ + + + diff --git a/FluentTerminal.App/Views/SettingsPages/GeneralSettings.xaml.cs b/FluentTerminal.App/Views/SettingsPages/GeneralSettings.xaml.cs new file mode 100644 index 0000000..251ca34 --- /dev/null +++ b/FluentTerminal.App/Views/SettingsPages/GeneralSettings.xaml.cs @@ -0,0 +1,12 @@ +using Windows.UI.Xaml.Controls; + +namespace FluentTerminal.App.Views.SettingsPages +{ + public sealed partial class GeneralSettings : Page + { + public GeneralSettings() + { + this.InitializeComponent(); + } + } +} diff --git a/FluentTerminal.App/Views/SettingsPages/KeyBindingSettings.xaml b/FluentTerminal.App/Views/SettingsPages/KeyBindingSettings.xaml new file mode 100644 index 0000000..8d569de --- /dev/null +++ b/FluentTerminal.App/Views/SettingsPages/KeyBindingSettings.xaml @@ -0,0 +1,11 @@ + + + + diff --git a/FluentTerminal.App/Views/SettingsPages/KeyBindingSettings.xaml.cs b/FluentTerminal.App/Views/SettingsPages/KeyBindingSettings.xaml.cs new file mode 100644 index 0000000..a7552ed --- /dev/null +++ b/FluentTerminal.App/Views/SettingsPages/KeyBindingSettings.xaml.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// Die Elementvorlage "Leere Seite" wird unter https://go.microsoft.com/fwlink/?LinkId=234238 dokumentiert. + +namespace FluentTerminal.App.Views.SettingsPages +{ + /// + /// Eine leere Seite, die eigenständig verwendet oder zu der innerhalb eines Rahmens navigiert werden kann. + /// + public sealed partial class KeyBindingSettings : Page + { + public KeyBindingSettings() + { + this.InitializeComponent(); + } + } +} diff --git a/FluentTerminal.App/Views/SettingsPages/ShellSettings.xaml b/FluentTerminal.App/Views/SettingsPages/ShellSettings.xaml new file mode 100644 index 0000000..422c9e9 --- /dev/null +++ b/FluentTerminal.App/Views/SettingsPages/ShellSettings.xaml @@ -0,0 +1,52 @@ + + + + + + + + + + +