Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e17cf894b8 | ||
|
|
dec492e39a | ||
|
|
20540937fe | ||
|
|
3dd29d7bb7 | ||
|
|
90ac2b0c7c | ||
|
|
e459255b23 | ||
|
|
9165716899 | ||
|
|
49cf63c6b8 | ||
|
|
d0b5f99e28 | ||
|
|
e1b28d2f83 | ||
|
|
24a2a23b23 | ||
|
|
25536e9bcb | ||
|
|
2f984fc411 | ||
|
|
596eee712b | ||
|
|
7b18037862 | ||
|
|
41cffb1a1c | ||
|
|
6ddfba93d9 | ||
|
|
6a1e12c83a | ||
|
|
e24261ab02 | ||
|
|
60d294b20f | ||
|
|
1ee051a106 | ||
|
|
ea1a167dd3 | ||
|
|
1a91a6b22e | ||
|
|
941c525409 | ||
|
|
d7f741c2ad | ||
|
|
ab73b28582 | ||
|
|
95ccf3a984 | ||
|
|
93eccc3470 | ||
|
|
f86f20321d | ||
|
|
475e2da999 | ||
|
|
985cac99e6 | ||
|
|
832b9bdf97 | ||
|
|
264502e1e6 | ||
|
|
6015dbda2f | ||
|
|
d879ee02fc | ||
|
|
27c55c6f09 | ||
|
|
395e380a3f | ||
|
|
ae6e61d6fa | ||
|
|
5ae7151916 | ||
|
|
0e2821eeca | ||
|
|
3ef374f79d | ||
|
|
9413cdc500 | ||
|
|
80d57342f9 | ||
|
|
089ec5dc54 | ||
|
|
a9ec669a27 | ||
|
|
7ed503390d | ||
|
|
33bc06a366 | ||
|
|
9c6a7ea2e2 | ||
|
|
3126d7b672 | ||
|
|
b5be5e4995 | ||
|
|
6a0b4b13f2 | ||
|
|
d3ad7b3797 | ||
|
|
6ae99e641b | ||
|
|
d1bf35607e | ||
|
|
603bcdffa4 | ||
|
|
71f2d56301 | ||
|
|
7f816d96cc | ||
|
|
5a2e2a5292 | ||
|
|
73f0c3e313 | ||
|
|
ff94887899 | ||
|
|
a8a357dc4b | ||
|
|
21d76a5245 | ||
|
|
0627495113 | ||
|
|
3397760955 | ||
|
|
ec9169306f | ||
|
|
1e48cb85d2 | ||
|
|
055e265f2f | ||
|
|
f692453ee2 | ||
|
|
272cc36788 | ||
|
|
5948ee6a4c | ||
|
|
47fc720df0 | ||
|
|
8008ba1298 | ||
|
|
7251beb2f1 | ||
|
|
79c48e4852 | ||
|
|
a709220e3a | ||
|
|
f5d23bce49 | ||
|
|
be4d1d5cab | ||
|
|
8dad2eb81b | ||
|
|
8019709534 | ||
|
|
83b9b0d457 | ||
|
|
e559c3ee38 | ||
|
|
a829c2e135 | ||
|
|
173baf899d | ||
|
|
ad2f45f062 | ||
|
|
85d04eab4b | ||
|
|
ec8ad133d1 | ||
|
|
c8b4d2ed8f | ||
|
|
b377ac8e38 | ||
|
|
12e0d5496d | ||
|
|
8593b5ac5f | ||
|
|
122dfccc00 | ||
|
|
7e95f151ed | ||
|
|
087c9aacbb | ||
|
|
5bea17da29 | ||
|
|
af25086090 | ||
|
|
80673696e9 | ||
|
|
1668908489 | ||
|
|
18164f1075 | ||
|
|
c461cdc37e | ||
|
|
bd843c77b2 | ||
|
|
9f3ba75ce6 | ||
|
|
a3fbb17615 | ||
|
|
eb8c4003a3 | ||
|
|
a4559ae168 | ||
|
|
03f0adbdd6 | ||
|
|
17c0a3b438 | ||
|
|
e1652d99dc | ||
|
|
1c029fc70e | ||
|
|
7ab42ec1de | ||
|
|
4b21a5abb6 | ||
|
|
b78e775285 | ||
|
|
a872d126ce | ||
|
|
5bc679d8e6 | ||
|
|
ae2b4a481d | ||
|
|
d90c403060 | ||
|
|
3a8fcaf099 | ||
|
|
473732b5ca | ||
|
|
8fa971c4e5 | ||
|
|
fd48f021e0 | ||
|
|
6868bfa7ca | ||
|
|
dcde49c7b3 | ||
|
|
21631f5cca | ||
|
|
89a75ba782 | ||
|
|
7893e804a4 | ||
|
|
f19d457209 | ||
|
|
2d94753690 | ||
|
|
37b4fd283a | ||
|
|
46b8bb037a | ||
|
|
073b15c5f8 | ||
|
|
b08fb11a78 | ||
|
|
f5c91c10e3 | ||
|
|
223fafcf1c | ||
|
|
cbdf629b49 | ||
|
|
614e86f38a | ||
|
|
1519fc1f1a | ||
|
|
9eb31ec872 | ||
|
|
5c568e6c33 | ||
|
|
b07c56247a | ||
|
|
ad82c96d8c | ||
|
|
2bca8b345f | ||
|
|
6c2ee1bfff |
No files matched your search
@@ -0,0 +1,23 @@
|
||||
name: CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Build client
|
||||
working-directory: ./FluentTerminal.Client
|
||||
run: |
|
||||
yarn
|
||||
yarn build
|
||||
|
||||
- name: Setup MSBuild.exe
|
||||
uses: warrenbuckley/Setup-MSBuild@v1
|
||||
|
||||
- name: MSBuild
|
||||
run: msbuild FluentTerminal.sln -t:build -p:Configuration=Debug -p:Platform=x64 -p:AppxPackage=false -m -restore
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -23,7 +23,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
public void ShowCreateKeyBindingDialog_Default_UsesCreateKeyBindingDialog()
|
||||
{
|
||||
var createKeyBindingDialog = new Mock<ICreateKeyBindingDialog>();
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, () => createKeyBindingDialog.Object, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<ISshProfileSelectionDialog>, Mock.Of<IAboutDialog>);
|
||||
var dialogService = new DialogService(Mock.Of<IMessageDialog>, () => createKeyBindingDialog.Object, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<IAboutDialog>);
|
||||
|
||||
dialogService.ShowCreateKeyBindingDialog();
|
||||
|
||||
@@ -36,9 +36,9 @@ namespace FluentTerminal.App.Services.Test
|
||||
var title = string.Empty;
|
||||
var content = _fixture.Create<string>();
|
||||
var buttons = _fixture.CreateMany<DialogButton>(2);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<ISshProfileSelectionDialog>, Mock.Of<IAboutDialog>);
|
||||
var dialogService = new DialogService(Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<IAboutDialog>);
|
||||
|
||||
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
||||
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsync(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
||||
|
||||
showMessageDialogAsnyc.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("title");
|
||||
}
|
||||
@@ -49,9 +49,9 @@ namespace FluentTerminal.App.Services.Test
|
||||
var title = _fixture.Create<string>();
|
||||
var content = string.Empty;
|
||||
var buttons = _fixture.CreateMany<DialogButton>(2);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<ISshProfileSelectionDialog>, Mock.Of<IAboutDialog>);
|
||||
var dialogService = new DialogService(Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<IAboutDialog>);
|
||||
|
||||
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
||||
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsync(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
||||
|
||||
showMessageDialogAsnyc.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("content");
|
||||
}
|
||||
@@ -61,9 +61,9 @@ namespace FluentTerminal.App.Services.Test
|
||||
{
|
||||
var title = _fixture.Create<string>();
|
||||
var content = _fixture.Create<string>();
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<ISshProfileSelectionDialog>, Mock.Of<IAboutDialog>);
|
||||
var dialogService = new DialogService(Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<IAboutDialog>);
|
||||
|
||||
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content);
|
||||
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsync(title, content);
|
||||
|
||||
showMessageDialogAsnyc.Should().Throw<ArgumentException>().And.ParamName.Should().Be("buttons");
|
||||
}
|
||||
@@ -75,30 +75,19 @@ namespace FluentTerminal.App.Services.Test
|
||||
var content = _fixture.Create<string>();
|
||||
var buttons = _fixture.CreateMany<DialogButton>(2);
|
||||
var messageDialog = new Mock<IMessageDialog>();
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, () => messageDialog.Object, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<ISshProfileSelectionDialog>, Mock.Of<IAboutDialog>);
|
||||
var dialogService = new DialogService(() => messageDialog.Object, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<IAboutDialog>);
|
||||
|
||||
dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
||||
dialogService.ShowMessageDialogAsync(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
||||
|
||||
messageDialog.Verify(x => x.ShowAsync(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShowProfileSelectionDialogAsync_Default_UsesShellProfileSelectionDialog()
|
||||
{
|
||||
var shellProfileSelectionDialog = new Mock<IShellProfileSelectionDialog>();
|
||||
var dialogService = new DialogService(() => shellProfileSelectionDialog.Object, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<ISshProfileSelectionDialog>, Mock.Of<IAboutDialog>);
|
||||
|
||||
dialogService.ShowProfileSelectionDialogAsync();
|
||||
|
||||
shellProfileSelectionDialog.Verify(x => x.SelectProfile(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShowInputDialogAsync_UsesIInputDialogSetTitle()
|
||||
{
|
||||
var title = "title";
|
||||
var inputDialog = new Mock<IInputDialog>();
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, () => inputDialog.Object, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<ISshProfileSelectionDialog>, Mock.Of<IAboutDialog>);
|
||||
var dialogService = new DialogService(Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, () => inputDialog.Object, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<IAboutDialog>);
|
||||
|
||||
dialogService.ShowInputDialogAsync(title);
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoFixture" Version="4.11.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="5.9.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="Moq" Version="4.13.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="FluentAssertions" Version="5.10.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="Moq" Version="4.13.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@@ -38,7 +38,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
|
||||
new SettingsService(defaultValueProvider.Object, applicationDataContainers);
|
||||
@@ -60,6 +61,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var applicationDataContainers = new ApplicationDataContainers
|
||||
{
|
||||
ShellProfiles = shellProfilesContainer.Object,
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
@@ -88,6 +90,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
@@ -113,6 +116,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider.Object, applicationDataContainers);
|
||||
@@ -136,6 +140,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
@@ -157,6 +162,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var currentThemeChangedEventInvoked = false;
|
||||
@@ -181,7 +187,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
|
||||
@@ -206,7 +213,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
RoamingSettings = roamingSettings.Object,
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
Messenger.Default.Register<CurrentThemeChangedMessage>(this,
|
||||
@@ -229,7 +237,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
|
||||
@@ -251,7 +260,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
|
||||
@@ -274,7 +284,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
|
||||
@@ -297,7 +308,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
|
||||
@@ -320,7 +332,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
|
||||
@@ -342,7 +355,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
|
||||
@@ -364,7 +378,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
Messenger.Default.Register<TerminalOptionsChangedMessage>(this,
|
||||
@@ -388,7 +403,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
|
||||
@@ -410,7 +426,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
|
||||
@@ -432,7 +449,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
Messenger.Default.Register<ApplicationSettingsChangedMessage>(this,
|
||||
@@ -459,7 +477,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
|
||||
@@ -483,7 +502,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider.Object, applicationDataContainers);
|
||||
|
||||
@@ -508,7 +528,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
|
||||
@@ -531,7 +552,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
Messenger.Default.Register<KeyBindingsChangedMessage>(this,
|
||||
@@ -553,7 +575,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider.Object, applicationDataContainers);
|
||||
|
||||
@@ -578,7 +601,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
Messenger.Default.Register<KeyBindingsChangedMessage>(this,
|
||||
@@ -602,6 +626,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
@@ -627,6 +652,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
Themes = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider.Object, applicationDataContainers);
|
||||
@@ -648,6 +674,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var applicationDataContainers = new ApplicationDataContainers
|
||||
{
|
||||
ShellProfiles = shellProfilesContainer.Object,
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
@@ -670,6 +697,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var applicationDataContainers = new ApplicationDataContainers
|
||||
{
|
||||
ShellProfiles = shellProfilesContainer.Object,
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
@@ -691,6 +719,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var applicationDataContainers = new ApplicationDataContainers
|
||||
{
|
||||
ShellProfiles = shellProfilesContainer.Object,
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
@@ -715,6 +744,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var applicationDataContainers = new ApplicationDataContainers
|
||||
{
|
||||
ShellProfiles = shellProfilesContainer.Object,
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = localSettings.Object,
|
||||
@@ -743,6 +773,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var applicationDataContainers = new ApplicationDataContainers
|
||||
{
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
SshProfiles = Mock.Of<IApplicationDataContainer>(),
|
||||
KeyBindings = Mock.Of<IApplicationDataContainer>(),
|
||||
LocalSettings = Mock.Of<IApplicationDataContainer>(),
|
||||
RoamingSettings = roamingSettings.Object,
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
|
||||
trayProcessCommunicationService.Initialize(appServiceConnection.Object);
|
||||
|
||||
var result = await trayProcessCommunicationService.CreateTerminal(id, terminalSize, shellProfile, sessionType);
|
||||
var result = await trayProcessCommunicationService.CreateTerminalAsync(id, terminalSize, shellProfile, sessionType);
|
||||
|
||||
result.Should().BeEquivalentTo(response);
|
||||
}
|
||||
@@ -68,7 +68,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
|
||||
trayProcessCommunicationService.Initialize(appServiceConnection.Object);
|
||||
|
||||
await trayProcessCommunicationService.ResizeTerminal(terminalId, terminalSize);
|
||||
await trayProcessCommunicationService.ResizeTerminalAsync(terminalId, terminalSize);
|
||||
|
||||
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<ValueSet>(d => (byte)d[MessageKeys.Type] == (byte)MessageIdentifiers.ResizeTerminalRequest)), Times.Once);
|
||||
}
|
||||
@@ -88,7 +88,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
|
||||
trayProcessCommunicationService.Initialize(appServiceConnection.Object);
|
||||
|
||||
await trayProcessCommunicationService.Write(terminalId, data);
|
||||
await trayProcessCommunicationService.WriteAsync(terminalId, data);
|
||||
|
||||
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<ValueSet>(d => (byte)d[MessageKeys.Type] == Constants.TerminalBufferRequestIdentifier)), Times.Once);
|
||||
}
|
||||
@@ -107,7 +107,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
|
||||
trayProcessCommunicationService.Initialize(appServiceConnection.Object);
|
||||
|
||||
await trayProcessCommunicationService.CloseTerminal(terminalId);
|
||||
await trayProcessCommunicationService.CloseTerminalAsync(terminalId);
|
||||
|
||||
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<ValueSet>(d => (byte)d[MessageKeys.Type] == (byte)MessageIdentifiers.TerminalExitedRequest)), Times.Once);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Windows.Storage;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
|
||||
namespace FluentTerminal.App.Services.Adapters
|
||||
{
|
||||
@@ -56,7 +57,8 @@ namespace FluentTerminal.App.Services.Adapters
|
||||
|
||||
public void WriteValueAsJson<T>(string name, T value)
|
||||
{
|
||||
_applicationDataContainer.Values[name] = JsonConvert.SerializeObject(value);
|
||||
_applicationDataContainer.Values[name] = JsonConvert.SerializeObject(value,
|
||||
PreserveDictionaryKeyCaseContractResolver.SerializerSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using FluentTerminal.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.Services.Dialogs
|
||||
{
|
||||
public interface IShellProfileSelectionDialog
|
||||
{
|
||||
ShellProfile SelectedProfile { get; }
|
||||
|
||||
Task<ShellProfile> SelectProfile();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using FluentTerminal.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.Services.Dialogs
|
||||
{
|
||||
public interface ISshProfileSelectionDialog
|
||||
{
|
||||
SshProfile SelectedProfile { get; }
|
||||
|
||||
Task<SshProfile> SelectProfile();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
using System;
|
||||
using FluentTerminal.Models;
|
||||
|
||||
namespace FluentTerminal.App.Services.EventArgs
|
||||
{
|
||||
public class NewWindowRequestedEventArgs : System.EventArgs
|
||||
{
|
||||
public Models.Enums.NewWindowAction Action { get; set; }
|
||||
public Guid ProfileId { get; set; }
|
||||
public ShellProfile Profile { get; }
|
||||
|
||||
public NewWindowRequestedEventArgs(ShellProfile profile) =>
|
||||
Profile = profile ?? throw new ArgumentNullException(nameof(profile));
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="MvvmLightLibs" Version="5.4.1.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="PListNet" Version="3.0.0" />
|
||||
<PackageReference Include="RestSharp" Version="106.6.10" />
|
||||
<PackageReference Include="RestSharp" Version="106.10.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
|
||||
<PackageReference Include="System.Runtime.InteropServices.WindowsRuntime" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using FluentTerminal.Models.Enums;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
// This validator is needed because accelerator key has to be member of VirtualKey enum.
|
||||
public interface IAcceleratorKeyValidator
|
||||
{
|
||||
bool Valid(int key);
|
||||
|
||||
bool Valid(ExtendedVirtualKey key);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using FluentTerminal.App.Services.EventArgs;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI.Core;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
@@ -14,9 +15,13 @@ namespace FluentTerminal.App.Services
|
||||
int Id { get; }
|
||||
string Title { get; set; }
|
||||
|
||||
Task RunOnDispatcherThread(Action action, bool enforceNewSchedule = true);
|
||||
Task ExecuteOnUiThreadAsync(Action action, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal,
|
||||
bool enforceNewSchedule = false);
|
||||
|
||||
Task<bool> TryClose();
|
||||
Task<T> ExecuteOnUiThreadAsync<T>(Func<T> func, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal,
|
||||
bool enforceNewSchedule = false);
|
||||
|
||||
Task<bool> TryCloseAsync();
|
||||
|
||||
bool ToggleFullScreen();
|
||||
bool IsApiContractPresent(string api, ushort version);
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface IClipboardService
|
||||
{
|
||||
Task<string> GetText();
|
||||
Task<string> GetTextAsync();
|
||||
|
||||
void SetText(string text);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FluentTerminal.Models;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface ICommandHistoryService
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the most recent <paramref name="top"/> commands from history.
|
||||
/// </summary>
|
||||
/// <param name="includeProfiles">Indicates if never used saved profiles should also be returned.</param>
|
||||
/// <param name="top">Maximal number of items to return.</param>
|
||||
/// <param name="profilesProvider">List of all saved profiles. If this argument isn't specified, the method will
|
||||
/// get the list from <see cref="ISettingsService"/>. The purpose of this argument is to avoid loading the
|
||||
/// profiles from settings again (since it includes file system access and JSON deserialization), if we
|
||||
/// already have them loaded.</param>
|
||||
List<ExecutedCommand> GetHistoryRecentFirst(bool includeProfiles = false, int top = int.MaxValue,
|
||||
Func<List<ShellProfile>> profilesProvider = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the most used <paramref name="top"/> commands from history.
|
||||
/// </summary>
|
||||
/// <param name="includeProfiles">Indicates if never used saved profiles should also be returned.</param>
|
||||
/// <param name="top">Maximal number of items to return.</param>
|
||||
/// <param name="profilesProvider">List of all saved profiles. If this argument isn't specified, the method will
|
||||
/// get the list from <see cref="ISettingsService"/>. The purpose of this argument is to avoid loading the
|
||||
/// profiles from settings again (since it includes file system access and JSON deserialization), if we
|
||||
/// already have them loaded.</param>
|
||||
List<ExecutedCommand> GetHistoryMostUsedFirst(bool includeProfiles = false, int top = int.MaxValue,
|
||||
Func<List<ShellProfile>> profilesProvider = null);
|
||||
|
||||
void MarkUsed(ShellProfile profile);
|
||||
|
||||
void Delete(ExecutedCommand executedCommand);
|
||||
}
|
||||
}
|
||||
@@ -5,25 +5,23 @@ namespace FluentTerminal.App.Services
|
||||
{
|
||||
public enum DialogButton
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
OK,
|
||||
Cancel
|
||||
}
|
||||
|
||||
public interface IDialogService
|
||||
{
|
||||
Task<DialogButton> ShowMessageDialogAsnyc(string title, string content, params DialogButton[] buttons);
|
||||
|
||||
Task<ShellProfile> ShowProfileSelectionDialogAsync();
|
||||
Task<DialogButton> ShowMessageDialogAsync(string title, string content, params DialogButton[] buttons);
|
||||
|
||||
Task<KeyBinding> ShowCreateKeyBindingDialog();
|
||||
|
||||
Task<string> ShowInputDialogAsync(string title);
|
||||
|
||||
Task<SshProfile> ShowSshConnectionInfoDialogAsync(SshProfile input = null);
|
||||
|
||||
Task<ShellProfile> ShowCustomCommandDialogAsync(ShellProfile input = null);
|
||||
|
||||
Task<SshProfile> ShowSshProfileSelectionDialogAsync();
|
||||
|
||||
Task ShowAboutDialogAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface IDispatcherTimer
|
||||
{
|
||||
void Start();
|
||||
|
||||
void Stop();
|
||||
|
||||
TimeSpan Interval { get; set; }
|
||||
bool IsEnabled { get; }
|
||||
|
||||
event EventHandler<object> Tick;
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,12 @@ namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface IFileSystemService
|
||||
{
|
||||
Task<File> OpenFile(IEnumerable<string> fileTypes);
|
||||
Task<File> OpenFileAsync(IEnumerable<string> fileTypes);
|
||||
|
||||
Task<string> BrowseForDirectory();
|
||||
Task<string> BrowseForDirectoryAsync();
|
||||
|
||||
Task SaveTextFile(string name, string fileTypeDescription, string fileType, string content);
|
||||
Task SaveTextFileAsync(string name, string fileTypeDescription, string fileType, string content);
|
||||
|
||||
Task<ImageFile> SaveImageInRoamingAsync(ImageFile imageFile);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using FluentTerminal.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface IImageFileSystemService
|
||||
{
|
||||
Task<ImageFile> ImportTemporaryImageFileAsync(IEnumerable<string> fileTypes);
|
||||
|
||||
Task RemoveTemporaryBackgroundThemeImageAsync();
|
||||
|
||||
Task RemoveImportedImageAsync(string fileName);
|
||||
|
||||
string EncodeImage(ImageFile imageFile);
|
||||
|
||||
Task<ImageFile> ImportThemeImageAsync(ImageFile backgroundImage, string encodedImage);
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,14 @@ namespace FluentTerminal.App.Services
|
||||
TerminalTheme GetCurrentTheme();
|
||||
Guid GetCurrentThemeId();
|
||||
ShellProfile GetDefaultShellProfile();
|
||||
SshProfile GetDefaultSshProfile();
|
||||
Guid GetDefaultShellProfileId();
|
||||
Guid GetDefaultSshProfileId();
|
||||
IDictionary<string, ICollection<KeyBinding>> GetCommandKeyBindings();
|
||||
IEnumerable<ShellProfile> GetShellProfiles();
|
||||
IEnumerable<SshProfile> GetSshProfiles();
|
||||
/// <summary>
|
||||
/// Returns union of <see cref="GetShellProfiles"/> and <see cref="GetSshProfiles"/>.
|
||||
/// </summary>
|
||||
IEnumerable<ShellProfile> GetAllProfiles();
|
||||
IEnumerable<TabTheme> GetTabThemes();
|
||||
TerminalOptions GetTerminalOptions();
|
||||
TerminalTheme GetTheme(Guid id);
|
||||
@@ -28,7 +30,6 @@ namespace FluentTerminal.App.Services
|
||||
void NotifyApplicationSettingsChanged(ApplicationSettings applicationSettings);
|
||||
void SaveCurrentThemeId(Guid id);
|
||||
void SaveDefaultShellProfileId(Guid id);
|
||||
void SaveDefaultSshProfileId(Guid id);
|
||||
void SaveKeyBindings(string command, ICollection<KeyBinding> keyBindings);
|
||||
void SaveShellProfile(ShellProfile shellProfile, bool newShell = false);
|
||||
void SaveSshProfile(SshProfile sshProfile, bool newShell = false);
|
||||
|
||||
@@ -5,8 +5,8 @@ namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface IStartupTaskService
|
||||
{
|
||||
Task DisableStartupTask();
|
||||
Task<StartupTaskStatus> EnableStartupTask();
|
||||
Task<StartupTaskStatus> GetStatus();
|
||||
Task DisableStartupTaskAsync();
|
||||
Task<StartupTaskStatus> EnableStartupTaskAsync();
|
||||
Task<StartupTaskStatus> GetStatusAsync();
|
||||
}
|
||||
}
|
||||
@@ -10,5 +10,7 @@ namespace FluentTerminal.App.Services
|
||||
IEnumerable<string> SupportedFileTypes { get; }
|
||||
|
||||
Task<TerminalTheme> Parse(string fileName, Stream fileContent);
|
||||
|
||||
Task<ExportedTerminalTheme> Import(string fileName, Stream fileContent);
|
||||
}
|
||||
}
|
||||
@@ -12,24 +12,26 @@ namespace FluentTerminal.App.Services
|
||||
|
||||
void Initialize(IAppServiceConnection appServiceConnection);
|
||||
|
||||
Task<CreateTerminalResponse> CreateTerminal(byte id, TerminalSize size, ShellProfile shellProfile, SessionType sessionType);
|
||||
Task<CreateTerminalResponse> CreateTerminalAsync(byte id, TerminalSize size, ShellProfile shellProfile,
|
||||
SessionType sessionType);
|
||||
|
||||
Task<PauseTerminalOutputResponse> PauseTerminalOutput(byte id, bool pause);
|
||||
Task<PauseTerminalOutputResponse> PauseTerminalOutputAsync(byte id, bool pause);
|
||||
|
||||
Task ResizeTerminal(byte id, TerminalSize size);
|
||||
Task ResizeTerminalAsync(byte id, TerminalSize size);
|
||||
|
||||
Task UpdateToggleWindowKeyBindings();
|
||||
Task UpdateToggleWindowKeyBindingsAsync();
|
||||
|
||||
Task Write(byte terminalId, byte[] data);
|
||||
Task WriteAsync(byte terminalId, byte[] data);
|
||||
|
||||
void SubscribeForTerminalOutput(byte terminalId, Action<byte[]> callback);
|
||||
|
||||
void UnsubscribeFromTerminalOutput(byte terminalId);
|
||||
|
||||
Task CloseTerminal(byte terminalId);
|
||||
Task CloseTerminalAsync(byte terminalId);
|
||||
|
||||
byte GetNextTerminalId();
|
||||
|
||||
Task<string> GetUserName();
|
||||
Task<string> GetUserNameAsync();
|
||||
|
||||
Task SaveTextFileAsync(string path, string content);
|
||||
|
||||
@@ -39,10 +41,12 @@ namespace FluentTerminal.App.Services
|
||||
|
||||
Task<bool> CheckFileExistsAsync(string path);
|
||||
|
||||
void MuteTerminal(bool mute);
|
||||
Task MuteTerminalAsync(bool mute);
|
||||
|
||||
void UpdateSettings(ApplicationSettings settings);
|
||||
|
||||
Task<string> GetCommandPathAsync(string command);
|
||||
|
||||
Task QuitApplicationAsync();
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface IUpdateService
|
||||
{
|
||||
Task CheckForUpdate(bool notifyNoUpdate = false);
|
||||
Task CheckForUpdateAsync(bool notifyNoUpdate = false);
|
||||
Version GetCurrentVersion();
|
||||
Task<Version> GetLatestVersionAsync();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
UnderlineSelectedTab = false,
|
||||
InactiveTabColorMode = InactiveTabColorMode.Background,
|
||||
NewTerminalLocation = NewTerminalLocation.Tab,
|
||||
TabWindowCascadingAppMenu = true,
|
||||
TabsPosition = TabsPosition.Top,
|
||||
CopyOnSelect = false,
|
||||
MouseMiddleClickAction = MouseAction.None,
|
||||
@@ -40,7 +41,9 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
MuteTerminalBeeps = true,
|
||||
EnableLogging = false,
|
||||
PrintableOutputOnly = true,
|
||||
LogDirectoryPath = logDirectoryPath
|
||||
LogDirectoryPath = logDirectoryPath,
|
||||
UseConPty = true,
|
||||
ShowTextCopied = false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -112,6 +115,18 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
};
|
||||
|
||||
case Command.ReconnectTab:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.ReconnectTab),
|
||||
Ctrl = true,
|
||||
Alt = true,
|
||||
Key = (int)ExtendedVirtualKey.R
|
||||
}
|
||||
};
|
||||
|
||||
case Command.NewTab:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
@@ -124,18 +139,6 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
};
|
||||
|
||||
case Command.ConfigurableNewTab:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.ConfigurableNewTab),
|
||||
Ctrl = true,
|
||||
Alt = true,
|
||||
Key = (int)ExtendedVirtualKey.T
|
||||
}
|
||||
};
|
||||
|
||||
case Command.NewSshTab:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
@@ -167,8 +170,8 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Command = nameof(Command.NewCustomCommandTab),
|
||||
Ctrl = true,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.Q
|
||||
Alt = true,
|
||||
Key = (int)ExtendedVirtualKey.T
|
||||
}
|
||||
};
|
||||
|
||||
@@ -180,35 +183,10 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
Command = nameof(Command.NewCustomCommandWindow),
|
||||
Ctrl = true,
|
||||
Alt = true,
|
||||
Key = (int)ExtendedVirtualKey.Q
|
||||
Key = (int)ExtendedVirtualKey.N
|
||||
}
|
||||
};
|
||||
|
||||
case Command.SavedSshNewTab:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.SavedSshNewTab),
|
||||
Ctrl = true,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.U
|
||||
}
|
||||
};
|
||||
|
||||
case Command.SavedSshNewWindow:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.SavedSshNewWindow),
|
||||
Ctrl = true,
|
||||
Alt = true,
|
||||
Key = (int)ExtendedVirtualKey.U
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
case Command.ChangeTabTitle:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
@@ -245,18 +223,6 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
};
|
||||
|
||||
case Command.ConfigurableNewWindow:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.ConfigurableNewWindow),
|
||||
Ctrl = true,
|
||||
Alt = true,
|
||||
Key = (int)ExtendedVirtualKey.N
|
||||
}
|
||||
};
|
||||
|
||||
case Command.ShowSettings:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
@@ -265,7 +231,8 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
Command = nameof(Command.ShowSettings),
|
||||
Ctrl = true,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.Comma
|
||||
// I've had to replace ExtendedVirtualKey.Comma because it isn't a member of VirtualKey enum, thus cannot be shown in menu.
|
||||
Key = (int)ExtendedVirtualKey.S
|
||||
}
|
||||
};
|
||||
|
||||
@@ -432,11 +399,12 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
ScrollBarStyle = ScrollBarStyle.Hidden,
|
||||
FontFamily = "Consolas",
|
||||
FontSize = 13,
|
||||
BoldText = false,
|
||||
FontWeight = 400,
|
||||
BackgroundOpacity = 0.8,
|
||||
Padding = 12,
|
||||
ScrollBackLimit = 1000,
|
||||
WordSeparator = " ()[]{}:;|│!&*<>@"&squo;"
|
||||
WordSeparator = " ()[]{}:;|│!&*<>@"&squo;",
|
||||
UseAcrylicBackground = true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -458,8 +426,8 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
Location = @"C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe",
|
||||
PreInstalled = true,
|
||||
WorkingDirectory = string.Empty,
|
||||
LineEndingTranslation = LineEndingStyle.DoNotModify,
|
||||
UseConPty = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8), // Windows 10 1903+
|
||||
UseBuffer = false,
|
||||
EnvironmentVariables = new Dictionary<string, string>
|
||||
{
|
||||
["TERM"] = "xterm-256color"
|
||||
@@ -486,8 +454,8 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
Location = @"C:\Windows\System32\cmd.exe",
|
||||
PreInstalled = true,
|
||||
WorkingDirectory = string.Empty,
|
||||
LineEndingTranslation = LineEndingStyle.DoNotModify,
|
||||
UseConPty = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 7), // Windows 10 1809+
|
||||
UseBuffer = true,
|
||||
EnvironmentVariables = new Dictionary<string, string>
|
||||
{
|
||||
["TERM"] = "xterm-256color"
|
||||
@@ -514,8 +482,8 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
Location = @"C:\windows\system32\wsl.exe",
|
||||
PreInstalled = true,
|
||||
WorkingDirectory = string.Empty,
|
||||
LineEndingTranslation = LineEndingStyle.ToLF,
|
||||
UseConPty = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 7), // Windows 10 1809+
|
||||
UseBuffer = true, //TODO: Set to false if the buffer causes issues with WSL.
|
||||
EnvironmentVariables = new Dictionary<string, string>
|
||||
{
|
||||
["TERM"] = "xterm-256color"
|
||||
|
||||
@@ -7,29 +7,23 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
public class DialogService : IDialogService
|
||||
{
|
||||
private readonly Func<IShellProfileSelectionDialog> _shellProfileSelectionDialogFactory;
|
||||
private readonly Func<IMessageDialog> _messageDialogFactory;
|
||||
private readonly Func<ICreateKeyBindingDialog> _createKeyBindingDialogFactory;
|
||||
private readonly Func<IInputDialog> _inputDialogFactory;
|
||||
private readonly Func<ISshConnectionInfoDialog> _sshConnectionInfoDialogFactory;
|
||||
private readonly Func<ICustomCommandDialog> _customCommandDialogFactory;
|
||||
private readonly Func<ISshProfileSelectionDialog> _sshProfileSelectionDialogFactory;
|
||||
private readonly Func<IAboutDialog> _aboutDialogFactory;
|
||||
|
||||
public DialogService(Func<IShellProfileSelectionDialog> shellProfileSelectionDialogFactory,
|
||||
Func<IMessageDialog> messageDialogFactory, Func<ICreateKeyBindingDialog> createKeyBindingDialogFactory,
|
||||
public DialogService(Func<IMessageDialog> messageDialogFactory, Func<ICreateKeyBindingDialog> createKeyBindingDialogFactory,
|
||||
Func<IInputDialog> inputDialogFactory, Func<ISshConnectionInfoDialog> sshConnectionInfoDialogFactory,
|
||||
Func<ICustomCommandDialog> customCommandDialogFactory,
|
||||
Func<ISshProfileSelectionDialog> sshProfileSelectionDialogFactory,
|
||||
Func<IAboutDialog> aboutDialogFactory)
|
||||
{
|
||||
_shellProfileSelectionDialogFactory = shellProfileSelectionDialogFactory;
|
||||
_messageDialogFactory = messageDialogFactory;
|
||||
_createKeyBindingDialogFactory = createKeyBindingDialogFactory;
|
||||
_inputDialogFactory = inputDialogFactory;
|
||||
_sshConnectionInfoDialogFactory = sshConnectionInfoDialogFactory;
|
||||
_customCommandDialogFactory = customCommandDialogFactory;
|
||||
_sshProfileSelectionDialogFactory = sshProfileSelectionDialogFactory;
|
||||
_aboutDialogFactory = aboutDialogFactory;
|
||||
}
|
||||
|
||||
@@ -40,7 +34,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
return dialog.CreateKeyBinding();
|
||||
}
|
||||
|
||||
public Task<DialogButton> ShowMessageDialogAsnyc(string title, string content, params DialogButton[] buttons)
|
||||
public Task<DialogButton> ShowMessageDialogAsync(string title, string content, params DialogButton[] buttons)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
@@ -77,26 +71,12 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
return dialog.GetInput();
|
||||
}
|
||||
|
||||
public Task<ShellProfile> ShowProfileSelectionDialogAsync()
|
||||
{
|
||||
var dialog = _shellProfileSelectionDialogFactory();
|
||||
|
||||
return dialog.SelectProfile();
|
||||
}
|
||||
|
||||
public Task<SshProfile> ShowSshConnectionInfoDialogAsync(SshProfile input = null) =>
|
||||
_sshConnectionInfoDialogFactory().GetSshConnectionInfoAsync(input);
|
||||
|
||||
public Task<ShellProfile> ShowCustomCommandDialogAsync(ShellProfile input = null) =>
|
||||
_customCommandDialogFactory().GetCustomCommandAsync(input);
|
||||
|
||||
public Task<SshProfile> ShowSshProfileSelectionDialogAsync()
|
||||
{
|
||||
var dialog = _sshProfileSelectionDialogFactory();
|
||||
|
||||
return dialog.SelectProfile();
|
||||
}
|
||||
|
||||
public Task ShowAboutDialogAsync()
|
||||
{
|
||||
var dialog = _aboutDialogFactory();
|
||||
|
||||
@@ -9,9 +9,20 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
public class FluentTerminalThemeParser : IThemeParser
|
||||
{
|
||||
public IEnumerable<string> SupportedFileTypes { get; } = new string[] { ".flutecolors" };
|
||||
public IEnumerable<string> SupportedFileTypes { get; } = new[] { ".flutecolors" };
|
||||
|
||||
public async Task<TerminalTheme> Parse(string fileName, Stream fileContent)
|
||||
public Task<ExportedTerminalTheme> Import(string fileName, Stream fileContent)
|
||||
{
|
||||
return DeserializeThemeAsync<ExportedTerminalTheme>(fileName, fileContent);
|
||||
}
|
||||
|
||||
public Task<TerminalTheme> Parse(string fileName, Stream fileContent)
|
||||
{
|
||||
return DeserializeThemeAsync<TerminalTheme>(fileName, fileContent);
|
||||
}
|
||||
|
||||
private async Task<T> DeserializeThemeAsync<T>(string fileName, Stream fileContent)
|
||||
where T : TerminalTheme
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(fileName))
|
||||
{
|
||||
@@ -25,8 +36,8 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
|
||||
using (var streamReader = new StreamReader(fileContent))
|
||||
{
|
||||
var content = await streamReader.ReadToEndAsync();
|
||||
var theme = JsonConvert.DeserializeObject<TerminalTheme>(content);
|
||||
var content = await streamReader.ReadToEndAsync().ConfigureAwait(false);
|
||||
var theme = JsonConvert.DeserializeObject<T>(content);
|
||||
|
||||
theme.PreInstalled = false;
|
||||
theme.Id = Guid.NewGuid();
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
@@ -14,7 +15,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
private const byte Opacity30Percent = 77;
|
||||
|
||||
public IEnumerable<string> SupportedFileTypes { get; } = new string[] { ".itermcolors" };
|
||||
public IEnumerable<string> SupportedFileTypes { get; } = new[] { ".itermcolors" };
|
||||
|
||||
private static class ITermThemeKeys
|
||||
{
|
||||
@@ -36,7 +37,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
public const string Ansi15Color = "Ansi 15 Color";
|
||||
|
||||
public const string BackgroundColor = "Background Color";
|
||||
public const string BoldColor = "Bold Color";
|
||||
//public const string BoldColor = "Bold Color";
|
||||
public const string CursorColor = "Cursor Color";
|
||||
public const string CursorTextColor = "Cursor Text Color";
|
||||
public const string ForegroundColor = "Foreground Color";
|
||||
@@ -133,5 +134,30 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
return (alpha / 256.0).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public Task<ExportedTerminalTheme> Import(string fileName, Stream fileContent)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(fileName))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fileName));
|
||||
}
|
||||
|
||||
if (fileContent == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fileContent));
|
||||
}
|
||||
|
||||
var node = PList.Load(fileContent) as DictionaryNode ?? throw new ParseThemeException("Root node was not a dictionary.");
|
||||
|
||||
var terminalTheme = new TerminalTheme
|
||||
{
|
||||
Name = Path.GetFileNameWithoutExtension(fileName),
|
||||
Colors = GetColors(node),
|
||||
Id = Guid.NewGuid(),
|
||||
PreInstalled = false
|
||||
};
|
||||
|
||||
return Task.FromResult(new ExportedTerminalTheme(terminalTheme, string.Empty));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentTerminal.Models;
|
||||
|
||||
namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
// The purpose of this class is to ensure backward compatibility of mosh profiles and command history
|
||||
// TODO: This class should be probably deleted in few months or so
|
||||
public static class MoshBackwardCompatibility
|
||||
{
|
||||
private static readonly string MoshCommandExe = $"{Constants.MoshCommandName}.exe";
|
||||
|
||||
private static readonly Regex OldFormatRx = new Regex(@".+\s(?<ports>\d{1,5}:\d{1,5})$", RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex TargetRx =
|
||||
new Regex(@"(^|\s+)(?<user>[^\s@;]+)@(?<host>[^\s@:]+)(:(?<port>\d{1,5}))?\s+", RegexOptions.Compiled);
|
||||
|
||||
private static bool IsMoshProfile(ShellProfile profile) =>
|
||||
Constants.MoshCommandName.Equals(profile.Location, StringComparison.OrdinalIgnoreCase) ||
|
||||
MoshCommandExe.Equals(profile.Location, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the profile contains obsolete mosh syntax, and changes it if it does.
|
||||
/// </summary>
|
||||
/// <param name="profile">The profile. Note that the method doesn't change the input profile at all, but creates and
|
||||
/// returns its clone if there's something to change.</param>
|
||||
/// <remarks>
|
||||
/// The method actually checks if <see cref="ShellProfile.Arguments"/> of the input <paramref name="profile"/> is in
|
||||
/// obsolete <c>mosh</c> format <c>[ssh_arguments] [target] [mosh_port_range]</c>, and if it is changes it to the new
|
||||
/// format <c>--ssh="[ssh_arguments]" -p [mosh_ports] [target]</c>.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// If the method detected that the input profile is obsolete mosh profile, it will create its clone with the updated
|
||||
/// command line arguments, and return it. Otherwise it will simply return unchanged input profile (the same reference
|
||||
/// as the input profile). It allows us to simply check if anything is changed by comparing the input and output
|
||||
/// profiles for reference equality.
|
||||
/// </returns>
|
||||
public static ShellProfile FixProfile(ShellProfile profile)
|
||||
{
|
||||
// If there aren't any arguments, or if the profile isn't a mosh profile, there's nothing to do. Just return.
|
||||
if (string.IsNullOrWhiteSpace(profile.Arguments) || !IsMoshProfile(profile)) return profile;
|
||||
|
||||
var arguments = profile.Arguments.Trim();
|
||||
|
||||
var moshPortsMatch = OldFormatRx.Match(arguments);
|
||||
|
||||
if (!moshPortsMatch.Success) return profile;// Arguments don't end with mosh ports (i.e. 60000:61000), so it isn't an obsolete format.
|
||||
|
||||
var targetMatches = TargetRx.Matches(arguments);
|
||||
|
||||
if (targetMatches.Count != 1) return profile;// Target part (user@host:port) not found or found multiple times. Cannot reliably determine the target, so don't change.
|
||||
|
||||
var targetMatch = targetMatches[0];
|
||||
|
||||
// Removing [mosh_ports] from the original arguments
|
||||
arguments = arguments.Substring(0, arguments.Length - moshPortsMatch.Groups["ports"].Length).Trim();
|
||||
|
||||
var newProfile = profile.Clone();
|
||||
|
||||
// Removing [target] from the original arguments, thus getting "pure" ssh arguments
|
||||
var sshArguments = targetMatch.Index > 0 ? arguments.Substring(0, targetMatch.Index).Trim() : string.Empty;
|
||||
|
||||
if (targetMatch.Index + targetMatch.Length < arguments.Length)
|
||||
{
|
||||
sshArguments += $" {arguments.Substring(targetMatch.Index + targetMatch.Length).Trim()}";
|
||||
}
|
||||
|
||||
// If the ssh port is defined in the original target (i.e. user@host:port), which was allowed in the obsolete mosh, extracting the port part into a separate ssh argument
|
||||
if (targetMatch.Groups["port"].Success && uint.TryParse(targetMatch.Groups["port"].Value, out var port) &&
|
||||
port != SshProfile.DefaultSshPort)
|
||||
{
|
||||
sshArguments += $" -p {targetMatch.Groups["port"].Value}";
|
||||
}
|
||||
|
||||
// Replacing any double quotes in ssh arguments with single quotes, since ssh arguments will be double-quoted as a whole
|
||||
sshArguments = sshArguments.Replace("\"", "'").Trim();
|
||||
|
||||
arguments = $"-p {moshPortsMatch.Groups["ports"].Value}";
|
||||
|
||||
if (!string.IsNullOrEmpty(sshArguments))
|
||||
{
|
||||
// If there were some ssh arguments, add them as defined in the new format
|
||||
arguments += $" --ssh=\"ssh {sshArguments}\"";
|
||||
}
|
||||
|
||||
// Finally append the target to the arguments
|
||||
arguments += $" {targetMatch.Groups["user"]}@{targetMatch.Groups["host"]}";
|
||||
|
||||
newProfile.Arguments = arguments;
|
||||
|
||||
return newProfile;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.Models.Messages;
|
||||
using GalaSoft.MvvmLight.Messaging;
|
||||
|
||||
@@ -80,7 +81,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
config.SshProfiles.Add(profile);
|
||||
}
|
||||
|
||||
return JsonConvert.SerializeObject(config);
|
||||
return JsonConvert.SerializeObject(config, PreserveDictionaryKeyCaseContractResolver.SerializerSettings);
|
||||
}
|
||||
|
||||
public void ImportSettings(string serializedSettings)
|
||||
@@ -129,7 +130,6 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
existingProfile.Arguments = profile.Arguments;
|
||||
existingProfile.TabThemeId = profile.TabThemeId;
|
||||
existingProfile.TerminalThemeId = profile.TerminalThemeId;
|
||||
existingProfile.LineEndingTranslation = profile.LineEndingTranslation;
|
||||
existingProfile.KeyBindings = profile.KeyBindings;
|
||||
SaveShellProfile(profile, isNew);
|
||||
continue;
|
||||
@@ -157,7 +157,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
public void DeleteSshProfile(Guid id)
|
||||
{
|
||||
_sshProfiles.Delete(id.ToString());
|
||||
Messenger.Default.Send(new SshProfileDeletedMessage(id));
|
||||
Messenger.Default.Send(new ShellProfileDeletedMessage(id));
|
||||
Messenger.Default.Send(new KeyBindingsChangedMessage());
|
||||
}
|
||||
|
||||
@@ -217,13 +217,6 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
public SshProfile GetDefaultSshProfile()
|
||||
{
|
||||
var id = GetDefaultSshProfileId();
|
||||
var profile = _sshProfiles.ReadValueFromJson(id.ToString(), default(SshProfile));
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
public ShellProfile GetShellProfile(Guid id)
|
||||
{
|
||||
@@ -242,14 +235,6 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
return _defaultValueProvider.GetDefaultShellProfileId();
|
||||
}
|
||||
public Guid GetDefaultSshProfileId()
|
||||
{
|
||||
if (_localSettings.TryGetValue(DefaultSshProfileKey, out object value))
|
||||
{
|
||||
return (Guid)value;
|
||||
}
|
||||
return System.Guid.Empty;
|
||||
}
|
||||
|
||||
public IDictionary<string, ICollection<KeyBinding>> GetCommandKeyBindings()
|
||||
{
|
||||
@@ -264,20 +249,43 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
|
||||
public IEnumerable<ShellProfile> GetShellProfiles()
|
||||
{
|
||||
return _shellProfiles.GetAll().Select(x => JsonConvert.DeserializeObject<ShellProfile>((string)x)).ToList();
|
||||
return _shellProfiles.GetAll().Select(x => JsonConvert.DeserializeObject<ShellProfile>((string) x))
|
||||
.Select(MoshBackwardCompatibilityFixProfile);
|
||||
}
|
||||
|
||||
public IEnumerable<SshProfile> GetSshProfiles()
|
||||
{
|
||||
if (_sshProfiles == null)
|
||||
return _sshProfiles.GetAll().Select(x => JsonConvert.DeserializeObject<SshProfile>((string) x))
|
||||
.Select(MoshBackwardCompatibilityFixProfile).Cast<SshProfile>();
|
||||
}
|
||||
|
||||
public IEnumerable<ShellProfile> GetAllProfiles()
|
||||
{
|
||||
return GetShellProfiles().Union(GetSshProfiles());
|
||||
}
|
||||
|
||||
private ShellProfile MoshBackwardCompatibilityFixProfile(ShellProfile profile)
|
||||
{
|
||||
var fixedProfile = MoshBackwardCompatibility.FixProfile(profile);
|
||||
|
||||
if (ReferenceEquals(fixedProfile, profile))
|
||||
{
|
||||
return new List<SshProfile>();
|
||||
// Nothing changed
|
||||
return fixedProfile;
|
||||
}
|
||||
|
||||
if (fixedProfile is SshProfile sshProfile)
|
||||
{
|
||||
DeleteSshProfile(profile.Id);
|
||||
SaveSshProfile(sshProfile);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _sshProfiles.GetAll()
|
||||
.Select(x => JsonConvert.DeserializeObject<SshProfile>((string)x)).ToList();
|
||||
DeleteShellProfile(profile.Id);
|
||||
SaveShellProfile(fixedProfile);
|
||||
}
|
||||
|
||||
return fixedProfile;
|
||||
}
|
||||
|
||||
public IEnumerable<TabTheme> GetTabThemes()
|
||||
@@ -313,12 +321,12 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
public void SaveApplicationSettings(ApplicationSettings applicationSettings)
|
||||
{
|
||||
_roamingSettings.WriteValueAsJson(nameof(ApplicationSettings), applicationSettings);
|
||||
Messenger.Default.Send(new ApplicationSettingsChangedMessage(applicationSettings));
|
||||
Messenger.Default.Send(new ApplicationSettingsChangedMessage(applicationSettings.Clone()));
|
||||
}
|
||||
|
||||
public void NotifyApplicationSettingsChanged(ApplicationSettings applicationSettings)
|
||||
{
|
||||
Messenger.Default.Send(new ApplicationSettingsChangedMessage(applicationSettings));
|
||||
Messenger.Default.Send(new ApplicationSettingsChangedMessage(applicationSettings.Clone()));
|
||||
}
|
||||
|
||||
public void SaveCurrentThemeId(Guid id)
|
||||
@@ -331,10 +339,8 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
public void SaveDefaultShellProfileId(Guid id)
|
||||
{
|
||||
_localSettings.SetValue(DefaultShellProfileKey, id);
|
||||
}
|
||||
public void SaveDefaultSshProfileId(Guid id)
|
||||
{
|
||||
_localSettings.SetValue(DefaultSshProfileKey, id);
|
||||
|
||||
Messenger.Default.Send(new DefaultShellProfileChangedMessage(id));
|
||||
}
|
||||
|
||||
public void SaveKeyBindings(string command, ICollection<KeyBinding> keyBindings)
|
||||
@@ -359,7 +365,12 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Messenger.Default.Send(new ShellProfileAddedMessage(shellProfile));
|
||||
}
|
||||
else
|
||||
{
|
||||
Messenger.Default.Send(new ShellProfileChangedMessage(shellProfile));
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveSshProfile(SshProfile sshProfile, bool newShell = false)
|
||||
{
|
||||
_sshProfiles.WriteValueAsJson(sshProfile.Id.ToString(), sshProfile);
|
||||
@@ -369,7 +380,11 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
|
||||
if (newShell)
|
||||
{
|
||||
Messenger.Default.Send(new SshProfileAddedMessage(sshProfile));
|
||||
Messenger.Default.Send(new ShellProfileAddedMessage(sshProfile));
|
||||
}
|
||||
else
|
||||
{
|
||||
Messenger.Default.Send(new ShellProfileChangedMessage(sshProfile));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,14 +8,35 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Foundation.Collections;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
|
||||
namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
public class TrayProcessCommunicationService : ITrayProcessCommunicationService
|
||||
{
|
||||
#region Static
|
||||
|
||||
private static string _userName;
|
||||
private static string _sshConfigDir;
|
||||
private static readonly Dictionary<string, string> CommandPaths = new Dictionary<string, string>();
|
||||
private static readonly Dictionary<string, string> CommandErrors = new Dictionary<string, string>();
|
||||
|
||||
private static ValueSet CreateMessage(IMessage content)
|
||||
{
|
||||
return new ValueSet
|
||||
{
|
||||
[MessageKeys.Type] = content.Identifier,
|
||||
[MessageKeys.Content] = JsonConvert.SerializeObject(content,
|
||||
PreserveDictionaryKeyCaseContractResolver.SerializerSettings)
|
||||
};
|
||||
}
|
||||
|
||||
#endregion Static
|
||||
|
||||
private readonly ISettingsService _settingsService;
|
||||
private IAppServiceConnection _appServiceConnection;
|
||||
private readonly Dictionary<byte, Action<byte[]>> _terminalOutputHandlers;
|
||||
// ReSharper disable once RedundantDefaultMemberInitializer
|
||||
private byte _nextTerminalId = 0;
|
||||
|
||||
public event EventHandler<TerminalExitStatus> TerminalExited;
|
||||
@@ -31,9 +52,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
return _nextTerminalId++;
|
||||
}
|
||||
|
||||
private string _userName;
|
||||
|
||||
public async Task<string> GetUserName()
|
||||
public async Task<string> GetUserNameAsync()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_userName))
|
||||
{
|
||||
@@ -41,52 +60,44 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
return _userName;
|
||||
}
|
||||
|
||||
StringValueResponse response;
|
||||
var response = await GetResponseAsync<StringValueResponse>(new GetUserNameRequest()).ConfigureAwait(false);
|
||||
|
||||
// No need to crash for username, so try/catch
|
||||
try
|
||||
if (response.Success)
|
||||
{
|
||||
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(new GetUserNameRequest()));
|
||||
response = JsonConvert.DeserializeObject<StringValueResponse>((string)responseMessage[MessageKeys.Content]);
|
||||
_userName = response.Value;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Instance.Error(e, "Error while trying to get username.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Logger.Instance.Debug("Received GetUserNameResponse: {@response}", response);
|
||||
|
||||
_userName = response.Value;
|
||||
|
||||
return _userName;
|
||||
}
|
||||
|
||||
public async Task SaveTextFileAsync(string path, string content)
|
||||
{
|
||||
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(new SaveTextFileRequest {Path = path, Content = content}));
|
||||
|
||||
var response = JsonConvert.DeserializeObject<CommonResponse>((string)responseMessage[MessageKeys.Content]);
|
||||
var response =
|
||||
await GetResponseAsync<CommonResponse>(new SaveTextFileRequest {Path = path, Content = content})
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!response.Success)
|
||||
{
|
||||
throw new SaveTextFileException(string.IsNullOrEmpty(response.Error) ? "Failed to save the file." : response.Error);
|
||||
throw new SaveTextFileException(string.IsNullOrEmpty(response.Error)
|
||||
? "Failed to save the file."
|
||||
: response.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private static string _sshConfigDir;
|
||||
|
||||
public async Task<string> GetSshConfigDirAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_sshConfigDir))
|
||||
if (!string.IsNullOrEmpty(_sshConfigDir))
|
||||
{
|
||||
var response = await GetSshConfigFolderAsync(false);
|
||||
return _sshConfigDir;
|
||||
}
|
||||
|
||||
if (response?.Success ?? false)
|
||||
{
|
||||
_sshConfigDir = response.Path;
|
||||
}
|
||||
var response =
|
||||
await GetResponseAsync<GetSshConfigFolderResponse>(new GetSshConfigFolderRequest
|
||||
{ IncludeContent = false }).ConfigureAwait(false);
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
_sshConfigDir = response.Path;
|
||||
}
|
||||
|
||||
return _sshConfigDir;
|
||||
@@ -94,42 +105,31 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
|
||||
public async Task<string[]> GetFilesFromSshConfigDirAsync()
|
||||
{
|
||||
var response = await GetSshConfigFolderAsync(false);
|
||||
var response =
|
||||
await GetResponseAsync<GetSshConfigFolderResponse>(new GetSshConfigFolderRequest
|
||||
{IncludeContent = true}).ConfigureAwait(false);
|
||||
|
||||
if (response == null || !response.Success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_sshConfigDir))
|
||||
if (response.Success)
|
||||
{
|
||||
_sshConfigDir = response.Path;
|
||||
|
||||
return response.Files;
|
||||
}
|
||||
|
||||
return response.Files;
|
||||
}
|
||||
|
||||
private async Task<GetSshConfigFolderResponse> GetSshConfigFolderAsync(bool includeContent)
|
||||
{
|
||||
var responseMessage =
|
||||
await _appServiceConnection.SendMessageAsync(CreateMessage(new GetSshConfigFolderRequest
|
||||
{IncludeContent = includeContent}));
|
||||
|
||||
return JsonConvert.DeserializeObject<GetSshConfigFolderResponse>(
|
||||
(string) responseMessage[MessageKeys.Content]);
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<bool> CheckFileExistsAsync(string path)
|
||||
{
|
||||
var responseMessage =
|
||||
await _appServiceConnection.SendMessageAsync(CreateMessage(new CheckFileExistsRequest {Path = path}));
|
||||
var response = await GetResponseAsync<CommonResponse>(new CheckFileExistsRequest {Path = path})
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return JsonConvert.DeserializeObject<CommonResponse>((string) responseMessage[MessageKeys.Content]).Success;
|
||||
return response.Success;
|
||||
}
|
||||
|
||||
public void MuteTerminal(bool mute)
|
||||
public Task MuteTerminalAsync(bool mute)
|
||||
{
|
||||
_appServiceConnection.SendMessageAsync(CreateMessage(new MuteTerminalRequest { Mute = mute }));
|
||||
return _appServiceConnection.SendMessageAsync(CreateMessage(new MuteTerminalRequest {Mute = mute}));
|
||||
}
|
||||
|
||||
public void UpdateSettings(ApplicationSettings settings)
|
||||
@@ -137,7 +137,8 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
_appServiceConnection.SendMessageAsync(CreateMessage(new UpdateSettingsRequest { Settings = settings }));
|
||||
}
|
||||
|
||||
public async Task<CreateTerminalResponse> CreateTerminal(byte id, TerminalSize size, ShellProfile shellProfile, SessionType sessionType)
|
||||
public async Task<CreateTerminalResponse> CreateTerminalAsync(byte id, TerminalSize size, ShellProfile shellProfile,
|
||||
SessionType sessionType)
|
||||
{
|
||||
var request = new CreateTerminalRequest
|
||||
{
|
||||
@@ -149,24 +150,17 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
|
||||
Logger.Instance.Debug("Sending CreateTerminalRequest: {@request}", request);
|
||||
|
||||
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
var response = JsonConvert.DeserializeObject<CreateTerminalResponse>((string)responseMessage[MessageKeys.Content]);
|
||||
var response = await GetResponseAsync<CreateTerminalResponse>(request).ConfigureAwait(false);
|
||||
|
||||
Logger.Instance.Debug("Received CreateTerminalResponse: {@response}", response);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<PauseTerminalOutputResponse> PauseTerminalOutput(byte id, bool pause)
|
||||
public Task<PauseTerminalOutputResponse> PauseTerminalOutputAsync(byte id, bool pause)
|
||||
{
|
||||
var request = new PauseTerminalOutputRequest
|
||||
{
|
||||
Id = id,
|
||||
Pause = pause
|
||||
};
|
||||
|
||||
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
return JsonConvert.DeserializeObject<PauseTerminalOutputResponse>((string)responseMessage[MessageKeys.Content]);
|
||||
return GetResponseAsync<PauseTerminalOutputResponse>(
|
||||
new PauseTerminalOutputRequest {Id = id, Pause = pause});
|
||||
}
|
||||
|
||||
public void Initialize(IAppServiceConnection appServiceConnection)
|
||||
@@ -206,15 +200,10 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
}
|
||||
|
||||
public Task ResizeTerminal(byte id, TerminalSize size)
|
||||
public Task ResizeTerminalAsync(byte id, TerminalSize size)
|
||||
{
|
||||
var request = new ResizeTerminalRequest
|
||||
{
|
||||
TerminalId = id,
|
||||
NewSize = size
|
||||
};
|
||||
|
||||
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
return _appServiceConnection.SendMessageAsync(CreateMessage(new ResizeTerminalRequest
|
||||
{TerminalId = id, NewSize = size}));
|
||||
}
|
||||
|
||||
public void SubscribeForTerminalOutput(byte terminalId, Action<byte[]> callback)
|
||||
@@ -230,19 +219,15 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
}
|
||||
|
||||
public Task UpdateToggleWindowKeyBindings()
|
||||
public Task UpdateToggleWindowKeyBindingsAsync()
|
||||
{
|
||||
var keyBindings = _settingsService.GetCommandKeyBindings()[nameof(Command.ToggleWindow)];
|
||||
|
||||
var request = new SetToggleWindowKeyBindingsRequest
|
||||
{
|
||||
KeyBindings = keyBindings
|
||||
};
|
||||
|
||||
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
return _appServiceConnection.SendMessageAsync(CreateMessage(new SetToggleWindowKeyBindingsRequest
|
||||
{KeyBindings = keyBindings}));
|
||||
}
|
||||
|
||||
public Task Write(byte id, byte[] data)
|
||||
public Task WriteAsync(byte id, byte[] data)
|
||||
{
|
||||
var message = new ValueSet
|
||||
{
|
||||
@@ -254,7 +239,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
return _appServiceConnection.SendMessageAsync(message);
|
||||
}
|
||||
|
||||
public Task CloseTerminal(byte terminalId)
|
||||
public Task CloseTerminalAsync(byte terminalId)
|
||||
{
|
||||
var request = new TerminalExitedRequest(terminalId, -1);
|
||||
|
||||
@@ -263,10 +248,6 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, string> CommandPaths = new Dictionary<string, string>();
|
||||
|
||||
private static readonly Dictionary<string, string> CommandErrors = new Dictionary<string, string>();
|
||||
|
||||
public async Task<string> GetCommandPathAsync(string command)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(command))
|
||||
@@ -288,11 +269,8 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
throw new Exception(error);
|
||||
}
|
||||
|
||||
var request = new GetCommandPathRequest {Command = command};
|
||||
|
||||
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
var response =
|
||||
JsonConvert.DeserializeObject<StringValueResponse>((string) responseMessage[MessageKeys.Content]);
|
||||
var response = await GetResponseAsync<StringValueResponse>(new GetCommandPathRequest {Command = command})
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
@@ -306,13 +284,17 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
throw new Exception(response.Error);
|
||||
}
|
||||
|
||||
private ValueSet CreateMessage(IMessage content)
|
||||
public Task QuitApplicationAsync()
|
||||
{
|
||||
return new ValueSet
|
||||
{
|
||||
[MessageKeys.Type] = content.Identifier,
|
||||
[MessageKeys.Content] = JsonConvert.SerializeObject(content)
|
||||
};
|
||||
return _appServiceConnection.SendMessageAsync(CreateMessage(new QuitApplicationRequest()));
|
||||
}
|
||||
|
||||
private async Task<T> GetResponseAsync<T>(IMessage request)
|
||||
{
|
||||
var messageResponse =
|
||||
await _appServiceConnection.SendMessageAsync(CreateMessage(request)).ConfigureAwait(false);
|
||||
|
||||
return JsonConvert.DeserializeObject<T>((string)messageResponse[MessageKeys.Content]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,9 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
_notificationService = notificationService;
|
||||
}
|
||||
|
||||
public async Task CheckForUpdate(bool notifyNoUpdate = false)
|
||||
public async Task CheckForUpdateAsync(bool notifyNoUpdate = false)
|
||||
{
|
||||
var latest = await GetLatestVersionAsync();
|
||||
var latest = await GetLatestVersionAsync().ConfigureAwait(false);
|
||||
if (latest > GetCurrentVersion())
|
||||
{
|
||||
_notificationService.ShowNotification("Update available",
|
||||
@@ -42,7 +42,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
var restClient = new RestClient(apiEndpoint);
|
||||
var restRequest = new RestRequest("/repos/felixse/fluentterminal/releases", Method.GET);
|
||||
|
||||
var restResponse = await restClient.ExecuteTaskAsync(restRequest);
|
||||
var restResponse = await restClient.ExecuteAsync(restRequest).ConfigureAwait(false);
|
||||
if (restResponse.IsSuccessful)
|
||||
{
|
||||
dynamic restResponseData = JsonConvert.DeserializeObject(restResponse.Content);
|
||||
|
||||
@@ -11,9 +11,10 @@ namespace FluentTerminal.App.Services
|
||||
{
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
private Func<Task<string>> _selectedTextCallback;
|
||||
private bool _closingFromUI;
|
||||
private bool _closingFromUi;
|
||||
private bool _exited;
|
||||
private readonly bool _requireShellProcessStart;
|
||||
private string _fallbackTitle;
|
||||
|
||||
public Terminal(ITrayProcessCommunicationService trayProcessCommunicationService, byte? terminalId = null)
|
||||
{
|
||||
@@ -23,6 +24,12 @@ namespace FluentTerminal.App.Services
|
||||
_requireShellProcessStart = !terminalId.HasValue;
|
||||
}
|
||||
|
||||
public void Reconnect()
|
||||
{
|
||||
_exited = false;
|
||||
_trayProcessCommunicationService.TerminalExited += OnTerminalExited;
|
||||
}
|
||||
|
||||
private void OnTerminalExited(object sender, TerminalExitStatus status)
|
||||
{
|
||||
if (status.TerminalId != Id)
|
||||
@@ -33,7 +40,7 @@ namespace FluentTerminal.App.Services
|
||||
_exited = true;
|
||||
Exited?.Invoke(this, status.ExitCode);
|
||||
|
||||
if (_closingFromUI || status.ExitCode <= 0)
|
||||
if (_closingFromUi || status.ExitCode <= 0)
|
||||
{
|
||||
Closed?.Invoke(this, System.EventArgs.Empty);
|
||||
}
|
||||
@@ -68,23 +75,21 @@ namespace FluentTerminal.App.Services
|
||||
/// </summary>
|
||||
public event EventHandler<string> TitleChanged;
|
||||
|
||||
public string FallbackTitle { get; private set; }
|
||||
|
||||
public byte Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// To be called by either view or viewmodel
|
||||
/// </summary>
|
||||
public async Task Close()
|
||||
public Task CloseAsync()
|
||||
{
|
||||
if (_exited)
|
||||
{
|
||||
Closed?.Invoke(this, System.EventArgs.Empty);
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
_closingFromUI = true;
|
||||
_closingFromUi = true;
|
||||
_trayProcessCommunicationService.UnsubscribeFromTerminalOutput(Id);
|
||||
await _trayProcessCommunicationService.CloseTerminal(Id).ConfigureAwait(true);
|
||||
return _trayProcessCommunicationService.CloseTerminalAsync(Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -115,9 +120,9 @@ namespace FluentTerminal.App.Services
|
||||
/// <summary>
|
||||
/// To be called by view
|
||||
/// </summary>
|
||||
public async Task SetSize(TerminalSize size)
|
||||
public async Task SetSizeAsync(TerminalSize size)
|
||||
{
|
||||
await _trayProcessCommunicationService.ResizeTerminal(Id, size);
|
||||
await _trayProcessCommunicationService.ResizeTerminalAsync(Id, size).ConfigureAwait(false);
|
||||
SizeChanged?.Invoke(this, size);
|
||||
}
|
||||
|
||||
@@ -128,18 +133,15 @@ namespace FluentTerminal.App.Services
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
title = FallbackTitle;
|
||||
title = _fallbackTitle;
|
||||
}
|
||||
TitleChanged?.Invoke(this, title);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// to be called by view when ready
|
||||
/// To be called by view when ready
|
||||
/// </summary>
|
||||
/// <param name="shellProfile"></param>
|
||||
/// <param name="size"></param>
|
||||
/// <param name="sessionType"></param>
|
||||
public async Task<TerminalResponse> StartShellProcess(ShellProfile shellProfile, TerminalSize size, SessionType sessionType, string termState)
|
||||
public async Task<TerminalResponse> StartShellProcessAsync(ShellProfile shellProfile, TerminalSize size, SessionType sessionType, string termState)
|
||||
{
|
||||
if (!_requireShellProcessStart && !string.IsNullOrEmpty(termState))
|
||||
{
|
||||
@@ -150,24 +152,25 @@ namespace FluentTerminal.App.Services
|
||||
|
||||
if (_requireShellProcessStart)
|
||||
{
|
||||
var response = await _trayProcessCommunicationService.CreateTerminal(Id, size, shellProfile, sessionType).ConfigureAwait(true);
|
||||
var response = await _trayProcessCommunicationService
|
||||
.CreateTerminalAsync(Id, size, shellProfile, sessionType).ConfigureAwait(false);
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
FallbackTitle = response.ShellExecutableName;
|
||||
SetTitle(FallbackTitle);
|
||||
_fallbackTitle = response.Name;
|
||||
SetTitle(_fallbackTitle);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
else
|
||||
{
|
||||
return await _trayProcessCommunicationService.PauseTerminalOutput(Id, false);
|
||||
return await _trayProcessCommunicationService.PauseTerminalOutputAsync(Id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public Task Write(byte[] data)
|
||||
{
|
||||
return _trayProcessCommunicationService.Write(Id, data);
|
||||
return _trayProcessCommunicationService.WriteAsync(Id, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -8,10 +8,11 @@ namespace FluentTerminal.App.Services.Utilities
|
||||
/// </summary>
|
||||
public static class I18N
|
||||
{
|
||||
private static readonly ResourceLoader ResourceLoader = ResourceLoader.GetForViewIndependentUse();
|
||||
|
||||
public static string Translate(string resource)
|
||||
{
|
||||
var resourceLoader = ResourceLoader.GetForCurrentView();
|
||||
return resourceLoader.GetString(resource.Replace('.', '/'));
|
||||
return ResourceLoader.GetString(resource.Replace('.', '/'));
|
||||
}
|
||||
|
||||
public static string TranslateWithFallback(string resource, string fallback)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace FluentTerminal.App.Services.Utilities
|
||||
{
|
||||
public class PreserveDictionaryKeyCaseContractResolver : DefaultContractResolver
|
||||
{
|
||||
public static readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings
|
||||
{ ContractResolver = new PreserveDictionaryKeyCaseContractResolver() };
|
||||
|
||||
protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
|
||||
{
|
||||
var contract = base.CreateDictionaryContract(objectType);
|
||||
|
||||
contract.DictionaryKeyResolver = key => key;
|
||||
|
||||
return contract;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using System.Reflection;
|
||||
|
||||
namespace FluentTerminal.App.Services.Utilities
|
||||
{
|
||||
public class TerminalThemeContractResolver : DefaultContractResolver
|
||||
public class TerminalThemeContractResolver : PreserveDictionaryKeyCaseContractResolver
|
||||
{
|
||||
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
|
||||
{
|
||||
@@ -24,4 +24,4 @@ namespace FluentTerminal.App.Services.Utilities
|
||||
return property;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -1,4 +1,5 @@
|
||||
using System.Threading;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
@@ -7,7 +8,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
private const int DelayMilliseconds = 5000;
|
||||
|
||||
private readonly CommandProfileProviderViewModel _vm;
|
||||
private readonly Action _saveAction;
|
||||
|
||||
private readonly object _lock = new object();
|
||||
|
||||
@@ -18,9 +19,9 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private bool _toSave = true;
|
||||
|
||||
internal DelayedHistorySaver(CommandProfileProviderViewModel vm)
|
||||
internal DelayedHistorySaver(Action saveAction)
|
||||
{
|
||||
_vm = vm;
|
||||
_saveAction = saveAction;
|
||||
}
|
||||
|
||||
public void SetSuccessfulSessionStart()
|
||||
@@ -86,7 +87,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
if (_toSave)
|
||||
{
|
||||
_vm.SaveToHistory();
|
||||
_saveAction.Invoke();
|
||||
}
|
||||
|
||||
_toSave = false;
|
||||
@@ -13,4 +13,11 @@
|
||||
<ProjectReference Include="..\FluentTerminal.Models\FluentTerminal.Models.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Windows.Foundation.UniversalApiContract">
|
||||
<HintPath>$(MSBuildProgramFiles32)\Windows Kits\10\References\10.0.16299.0\Windows.Foundation.UniversalApiContract\5.0.0.0\Windows.Foundation.UniversalApiContract.winmd</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,18 +1,21 @@
|
||||
using FluentTerminal.Models;
|
||||
using System;
|
||||
using FluentTerminal.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
public interface ITerminalView
|
||||
public interface ITerminalView : IDisposable
|
||||
{
|
||||
Task ChangeTheme(TerminalTheme theme);
|
||||
Task ChangeKeyBindings();
|
||||
Task ChangeOptions(TerminalOptions options);
|
||||
Task Initialize(TerminalViewModel viewModel);
|
||||
Task ChangeThemeAsync(TerminalTheme theme);
|
||||
Task ChangeKeyBindingsAsync();
|
||||
Task ChangeOptionsAsync(TerminalOptions options);
|
||||
Task InitializeAsync(TerminalViewModel viewModel);
|
||||
Task ReconnectAsync();
|
||||
void DisposalPrepare();
|
||||
Task FindNext(string searchText);
|
||||
Task FindPrevious(string searchText);
|
||||
Task FocusTerminal();
|
||||
Task<string> SerializeXtermState();
|
||||
Task FindNextAsync(SearchRequest request);
|
||||
Task FindPreviousAsync(SearchRequest request);
|
||||
Task FocusTerminalAsync();
|
||||
Task<string> SerializeXtermStateAsync();
|
||||
void Paste(string text);
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ namespace FluentTerminal.App.ViewModels.Infrastructure
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: If we're using class field _isExecuting, maybe we should introduce some locking?
|
||||
_isExecuting = true;
|
||||
await _execute();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Menu
|
||||
{
|
||||
public class ExpandableMenuItemViewModel : MenuItemViewModelBase
|
||||
{
|
||||
public ObservableCollection<MenuItemViewModelBase> SubItems { get; }
|
||||
|
||||
public ExpandableMenuItemViewModel(string text, IEnumerable<MenuItemViewModelBase> subItems = null,
|
||||
string description = null, Mdl2Icon icon = null) : base(text, description, icon)
|
||||
{
|
||||
if (subItems == null)
|
||||
{
|
||||
SubItems = new ObservableCollection<MenuItemViewModelBase>();
|
||||
}
|
||||
else if (subItems is ObservableCollection<MenuItemViewModelBase> observableSubItems)
|
||||
{
|
||||
SubItems = observableSubItems;
|
||||
}
|
||||
else
|
||||
{
|
||||
SubItems = new ObservableCollection<MenuItemViewModelBase>(subItems);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool EquivalentTo(MenuItemViewModelBase other)
|
||||
{
|
||||
if (!base.EquivalentTo(other)) return false;
|
||||
|
||||
if (!(other is ExpandableMenuItemViewModel otherExpandable) ||
|
||||
SubItems.Count != otherExpandable.SubItems.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !SubItems.Where((t, i) => !t.EquivalentTo(otherExpandable.SubItems[i])).Any();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Menu
|
||||
{
|
||||
public sealed class Mdl2Icon
|
||||
{
|
||||
public string Color { get; }
|
||||
public string Glyph { get; }
|
||||
|
||||
private Mdl2Icon(string glyph, string color = null)
|
||||
{
|
||||
Glyph = glyph;
|
||||
Color = color;
|
||||
}
|
||||
|
||||
public static Mdl2Icon Link(string color = null) => new Mdl2Icon("\uE71B", color);
|
||||
|
||||
public static Mdl2Icon Copy(string color = null) => new Mdl2Icon("\uE8C8", color);
|
||||
|
||||
public static Mdl2Icon Paste(string color = null) => new Mdl2Icon("\uE77F", color);
|
||||
|
||||
public static Mdl2Icon Edit(string color = null) => new Mdl2Icon("\uE70F", color);
|
||||
|
||||
public static Mdl2Icon Search(string color = null) => new Mdl2Icon("\uE721", color);
|
||||
|
||||
public static Mdl2Icon Add(string color = null) => new Mdl2Icon("\uE710", color);
|
||||
|
||||
public static Mdl2Icon Cancel(string color = null) => new Mdl2Icon("\uE711", color);
|
||||
|
||||
public static Mdl2Icon PaginationDotOutline10(string color = null) => new Mdl2Icon("\uF126", color);
|
||||
|
||||
public static Mdl2Icon PaginationDotSolid10(string color = null) => new Mdl2Icon("\uF127", color);
|
||||
|
||||
public static Mdl2Icon NewWindow(string color = null) => new Mdl2Icon("\uE78B", color);
|
||||
|
||||
public static Mdl2Icon History(string color = null) => new Mdl2Icon("\uE81C", color);
|
||||
|
||||
public static Mdl2Icon Settings(string color = null) => new Mdl2Icon("\uE713", color);
|
||||
|
||||
public static Mdl2Icon Info(string color = null) => new Mdl2Icon("\uE946", color);
|
||||
|
||||
public static Mdl2Icon FavoriteStar(string color = null) => new Mdl2Icon("\uE734", color);
|
||||
|
||||
public static Mdl2Icon Globe(string color = null) => new Mdl2Icon("\uE774", color);
|
||||
|
||||
public static Mdl2Icon Play(string color = null) => new Mdl2Icon("\uE768", color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
using FluentTerminal.Models;
|
||||
using GalaSoft.MvvmLight;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Menu
|
||||
{
|
||||
public class MenuItemKeyBindingViewModel : ViewModelBase
|
||||
{
|
||||
#region Properties
|
||||
|
||||
private int _key;
|
||||
|
||||
public int Key
|
||||
{
|
||||
get => _key;
|
||||
set => Set(ref _key, value);
|
||||
}
|
||||
|
||||
private int _keyModifiers;
|
||||
|
||||
// Corresponds to https://docs.microsoft.com/en-us/uwp/api/windows.system.virtualkeymodifiers
|
||||
public int KeyModifiers
|
||||
{
|
||||
get => _keyModifiers;
|
||||
private set => Set(ref _keyModifiers, value);
|
||||
}
|
||||
|
||||
private bool _ctrl;
|
||||
|
||||
public bool Ctrl
|
||||
{
|
||||
get => _ctrl;
|
||||
set
|
||||
{
|
||||
if (Set(ref _ctrl, value))
|
||||
{
|
||||
SetKeyModifiers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _alt;
|
||||
|
||||
public bool Alt
|
||||
{
|
||||
get => _alt;
|
||||
set
|
||||
{
|
||||
if (Set(ref _alt, value))
|
||||
{
|
||||
SetKeyModifiers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _shift;
|
||||
|
||||
public bool Shift
|
||||
{
|
||||
get => _shift;
|
||||
set
|
||||
{
|
||||
if (Set(ref _shift, value))
|
||||
{
|
||||
SetKeyModifiers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _windows;
|
||||
|
||||
public bool Windows
|
||||
{
|
||||
get => _windows;
|
||||
set
|
||||
{
|
||||
if (Set(ref _windows, value))
|
||||
{
|
||||
SetKeyModifiers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Constructors
|
||||
|
||||
public MenuItemKeyBindingViewModel(int key = 0, bool ctrl = false, bool alt = false, bool shift = false,
|
||||
bool windows = false)
|
||||
{
|
||||
_key = key;
|
||||
_ctrl = ctrl;
|
||||
_alt = alt;
|
||||
_shift = shift;
|
||||
_windows = windows;
|
||||
|
||||
SetKeyModifiers();
|
||||
}
|
||||
|
||||
public MenuItemKeyBindingViewModel(KeyBinding keyBinding) : this(keyBinding.Key, keyBinding.Ctrl,
|
||||
keyBinding.Alt, keyBinding.Shift, keyBinding.Meta)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
#region Methods
|
||||
|
||||
private void SetKeyModifiers()
|
||||
{
|
||||
// Based on VirtualKeyModifiers enum values (https://docs.microsoft.com/en-us/uwp/api/windows.system.virtualkeymodifiers)
|
||||
var result = 0;
|
||||
|
||||
if (_ctrl)
|
||||
{
|
||||
result |= 1;
|
||||
}
|
||||
|
||||
if (_alt)
|
||||
{
|
||||
result |= 2;
|
||||
}
|
||||
|
||||
if (_shift)
|
||||
{
|
||||
result |= 4;
|
||||
}
|
||||
|
||||
if (_windows)
|
||||
{
|
||||
result |= 8;
|
||||
}
|
||||
|
||||
KeyModifiers = result;
|
||||
}
|
||||
|
||||
public bool EquivalentTo(MenuItemKeyBindingViewModel other)
|
||||
{
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
|
||||
if (other == null) return false;
|
||||
|
||||
return _key == other._key && _keyModifiers == other._keyModifiers;
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Menu
|
||||
{
|
||||
public class MenuItemViewModel : MenuItemViewModelBase
|
||||
{
|
||||
#region Properties
|
||||
|
||||
private MenuItemKeyBindingViewModel _keyBinding;
|
||||
|
||||
public MenuItemKeyBindingViewModel KeyBinding
|
||||
{
|
||||
get => _keyBinding;
|
||||
set => Set(ref _keyBinding, value);
|
||||
}
|
||||
|
||||
public ICommand Command { get; }
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Constructors
|
||||
|
||||
public MenuItemViewModel(string text, ICommand command, string description = null, Mdl2Icon icon = null,
|
||||
MenuItemKeyBindingViewModel keyBinding = null) : base(text, description, icon)
|
||||
{
|
||||
_keyBinding = keyBinding;
|
||||
Command = command ?? throw new ArgumentNullException(nameof(command));
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
#region Methods
|
||||
|
||||
public override bool EquivalentTo(MenuItemViewModelBase other)
|
||||
{
|
||||
if (!base.EquivalentTo(other)) return false;
|
||||
|
||||
if (!(other is MenuItemViewModel menuItem)) return false;
|
||||
|
||||
return _keyBinding?.EquivalentTo(menuItem._keyBinding) ?? menuItem._keyBinding == null;
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using FluentTerminal.Models;
|
||||
using GalaSoft.MvvmLight;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Menu
|
||||
{
|
||||
public class MenuItemViewModelBase : ViewModelBase
|
||||
{
|
||||
#region Properties
|
||||
|
||||
private string _text;
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => _text;
|
||||
set => Set(ref _text, value);
|
||||
}
|
||||
|
||||
private string _description;
|
||||
|
||||
public string Description
|
||||
{
|
||||
get => _description;
|
||||
set => Set(ref _description, value);
|
||||
}
|
||||
|
||||
private Mdl2Icon _icon;
|
||||
|
||||
public Mdl2Icon Icon
|
||||
{
|
||||
get => _icon;
|
||||
set => Set(ref _icon, value);
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Constructor
|
||||
|
||||
protected MenuItemViewModelBase(string text, string description, Mdl2Icon icon)
|
||||
{
|
||||
_text = text;
|
||||
_description = description;
|
||||
_icon = icon;
|
||||
}
|
||||
|
||||
#endregion Constructor
|
||||
|
||||
#region Methods
|
||||
|
||||
public virtual bool EquivalentTo(MenuItemViewModelBase other)
|
||||
{
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
|
||||
if (other == null) return false;
|
||||
|
||||
return _text.NullableEqualTo(other._text) && _description.NullableEqualTo(other._description) &&
|
||||
(_icon?.Equals(other._icon) ?? other._icon == null);
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using GalaSoft.MvvmLight;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Menu
|
||||
{
|
||||
public class MenuViewModel : ViewModelBase
|
||||
{
|
||||
public ObservableCollection<MenuItemViewModelBase> Items { get; }
|
||||
|
||||
public MenuViewModel(IEnumerable<MenuItemViewModelBase> items = null)
|
||||
{
|
||||
if (items == null)
|
||||
{
|
||||
Items = new ObservableCollection<MenuItemViewModelBase>();
|
||||
}
|
||||
else if (items is ObservableCollection<MenuItemViewModelBase> observableItems)
|
||||
{
|
||||
Items = observableItems;
|
||||
}
|
||||
else
|
||||
{
|
||||
Items = new ObservableCollection<MenuItemViewModelBase>(items);
|
||||
}
|
||||
}
|
||||
|
||||
public bool EquivalentTo(MenuViewModel other)
|
||||
{
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
|
||||
if (other == null) return false;
|
||||
|
||||
if (Items.Count != other.Items.Count) return false;
|
||||
|
||||
return !Items.Where((t, i) => !t.EquivalentTo(other.Items[i])).Any();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Menu
|
||||
{
|
||||
public class RadioMenuItemViewModel : MenuItemViewModelBase
|
||||
{
|
||||
public object BindingSource { get; }
|
||||
public string BindingPath { get; }
|
||||
public string GroupName { get; }
|
||||
|
||||
public RadioMenuItemViewModel(string text, string groupName, object bindingSource, string bindingPath = null, string description = null, Mdl2Icon icon = null)
|
||||
: base(text, description, icon)
|
||||
{
|
||||
GroupName = groupName ?? throw new ArgumentNullException(nameof(groupName));
|
||||
BindingSource = bindingSource ?? throw new ArgumentNullException(nameof(bindingSource));
|
||||
BindingPath = bindingPath;
|
||||
}
|
||||
|
||||
public override bool EquivalentTo(MenuItemViewModelBase other)
|
||||
{
|
||||
if (!base.EquivalentTo(other)) return false;
|
||||
|
||||
if (!(other is RadioMenuItemViewModel radioMenuItem)) return false;
|
||||
|
||||
return GroupName == radioMenuItem.GroupName && BindingSource == radioMenuItem.BindingSource && BindingPath == radioMenuItem.BindingPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace FluentTerminal.App.ViewModels.Menu
|
||||
{
|
||||
public class SeparatorMenuItemViewModel : MenuItemViewModelBase
|
||||
{
|
||||
public SeparatorMenuItemViewModel()
|
||||
: base(string.Empty, string.Empty, null)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool EquivalentTo(MenuItemViewModelBase other)
|
||||
{
|
||||
return other is SeparatorMenuItemViewModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Menu
|
||||
{
|
||||
public class ToggleMenuItemViewModel : MenuItemViewModelBase
|
||||
{
|
||||
public object BindingSource { get; }
|
||||
public string BindingPath { get; }
|
||||
|
||||
public ToggleMenuItemViewModel(string text, object bindingSource, string bindingPath = null, string description = null, Mdl2Icon icon = null)
|
||||
: base(text, description, icon)
|
||||
{
|
||||
BindingSource = bindingSource ?? throw new ArgumentNullException(nameof(bindingSource));
|
||||
BindingPath = bindingPath;
|
||||
}
|
||||
|
||||
public override bool EquivalentTo(MenuItemViewModelBase other)
|
||||
{
|
||||
if (!base.EquivalentTo(other)) return false;
|
||||
|
||||
if (!(other is ToggleMenuItemViewModel toggleMenuItem)) return false;
|
||||
|
||||
return BindingSource == toggleMenuItem.BindingSource && BindingPath == toggleMenuItem.BindingPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight;
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
public class OverlayViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IDispatcherTimer _overlayTimer;
|
||||
private readonly DispatcherTimer _overlayTimer;
|
||||
private bool _showOverlay;
|
||||
private string _overlayContent;
|
||||
|
||||
public OverlayViewModel(IDispatcherTimer dispatcherTimer)
|
||||
// Important! The constructor has to be called from the UI thread.
|
||||
public OverlayViewModel()
|
||||
{
|
||||
_overlayTimer = dispatcherTimer;
|
||||
_overlayTimer.Interval = new TimeSpan(0, 0, 2);
|
||||
_overlayTimer = new DispatcherTimer {Interval = new TimeSpan(0, 0, 2)};
|
||||
}
|
||||
|
||||
public bool ShowOverlay
|
||||
|
||||
+54
-45
@@ -17,7 +17,6 @@ namespace FluentTerminal.App.ViewModels
|
||||
/// <summary>
|
||||
/// Base class for all profile view models. Implements logic for saving, editing, resetting, etc.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class ProfileViewModelBase<T> : ViewModelBase where T : ProfileProviderViewModelBase
|
||||
{
|
||||
#region Fields
|
||||
@@ -63,6 +62,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
#region Events
|
||||
|
||||
// Needs to be triggered from UI thread
|
||||
public event EventHandler Deleted;
|
||||
|
||||
#endregion Events
|
||||
@@ -102,9 +102,9 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
Initialize(shellProfile);
|
||||
|
||||
DeleteCommand = new AsyncCommand(Delete, CanDelete);
|
||||
DeleteCommand = new AsyncCommand(DeleteAsync, CanDelete);
|
||||
EditCommand = new RelayCommand(Edit);
|
||||
CancelEditCommand = new AsyncCommand(CancelEdit);
|
||||
CancelEditCommand = new AsyncCommand(CancelEditAsync);
|
||||
SaveChangesCommand = new AsyncCommand(SaveChangesAsync);
|
||||
AddKeyboardShortcutCommand = new AsyncCommand(AddKeyboardShortcut);
|
||||
}
|
||||
@@ -113,6 +113,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
#region Methods
|
||||
|
||||
// Requires UI thread
|
||||
// Loads view model properties from the input shellProfile
|
||||
protected void Initialize(ShellProfile shellProfile)
|
||||
{
|
||||
@@ -134,9 +135,11 @@ namespace FluentTerminal.App.ViewModels
|
||||
|| !(_environmentVariablesBeforeEdit?.SequenceEqual(EnvironmentVariables.ToDictionary(x => x.Name, x => x.Value)) ?? false);
|
||||
}
|
||||
|
||||
private async Task Delete()
|
||||
// Requires UI thread
|
||||
private async Task DeleteAsync()
|
||||
{
|
||||
var result = await DialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"),
|
||||
// ConfigureAwait(true) because we need to trigger Deleted event from the calling (UI) thread.
|
||||
var result = await DialogService.ShowMessageDialogAsync(I18N.Translate("PleaseConfirm"),
|
||||
I18N.Translate("ConfirmDeleteProfile"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
@@ -162,58 +165,64 @@ namespace FluentTerminal.App.ViewModels
|
||||
InEditMode = true;
|
||||
}
|
||||
|
||||
private async Task CancelEdit()
|
||||
// Requires UI thread
|
||||
private async Task CancelEditAsync()
|
||||
{
|
||||
if (IsNew)
|
||||
{
|
||||
await Delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HasChanges())
|
||||
{
|
||||
var result = await DialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"),
|
||||
I18N.Translate("ConfirmDiscardChanges"), DialogButton.OK, DialogButton.Cancel)
|
||||
.ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
// Cancelled, so rollback
|
||||
ProfileVm.RejectChanges();
|
||||
|
||||
KeyBindings.Editable = false;
|
||||
InEditMode = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyBindings.Editable = false;
|
||||
InEditMode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task SaveChangesAsync()
|
||||
{
|
||||
var error = await ProfileVm.AcceptChangesAsync();
|
||||
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
await DialogService.ShowMessageDialogAsnyc(I18N.Translate("InvalidInput"), error, DialogButton.OK);
|
||||
await DeleteAsync().ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (EnvironmentVariables.Select(x => x.Name).Any(x => string.IsNullOrWhiteSpace(x)))
|
||||
if (!HasChanges())
|
||||
{
|
||||
await DialogService.ShowMessageDialogAsnyc(I18N.Translate("InvalidInput"), I18N.Translate("EmptyEnvironmentVariableName"), DialogButton.OK);
|
||||
KeyBindings.Editable = false;
|
||||
InEditMode = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards
|
||||
if (await DialogService.ShowMessageDialogAsync(I18N.Translate("PleaseConfirm"),
|
||||
I18N.Translate("ConfirmDiscardChanges"), DialogButton.OK, DialogButton.Cancel)
|
||||
.ConfigureAwait(true) == DialogButton.OK)
|
||||
{
|
||||
// Cancelled, so rollback
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards
|
||||
await ProfileVm.RejectChangesAsync().ConfigureAwait(true);
|
||||
|
||||
KeyBindings.Editable = false;
|
||||
InEditMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Requires UI thread
|
||||
private async Task SaveChangesAsync()
|
||||
{
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
|
||||
var error = await ProfileVm.AcceptChangesAsync().ConfigureAwait(true);
|
||||
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
await DialogService.ShowMessageDialogAsync(I18N.Translate("InvalidInput"), error, DialogButton.OK)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (EnvironmentVariables.Select(x => x.Name).Any(string.IsNullOrWhiteSpace))
|
||||
{
|
||||
await DialogService.ShowMessageDialogAsync(I18N.Translate("InvalidInput"),
|
||||
I18N.Translate("EmptyEnvironmentVariableName"), DialogButton.OK).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (EnvironmentVariables.Select(x => x.Name).Distinct().Count() != EnvironmentVariables.Count)
|
||||
{
|
||||
await DialogService.ShowMessageDialogAsnyc(I18N.Translate("InvalidInput"), I18N.Translate("DuplicateEnvironmentVariable"), DialogButton.OK);
|
||||
await DialogService.ShowMessageDialogAsync(I18N.Translate("InvalidInput"),
|
||||
I18N.Translate("DuplicateEnvironmentVariable"), DialogButton.OK).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -239,9 +248,9 @@ namespace FluentTerminal.App.ViewModels
|
||||
IsNew = false;
|
||||
}
|
||||
|
||||
public Task AddKeyboardShortcut()
|
||||
private Task AddKeyboardShortcut()
|
||||
{
|
||||
return KeyBindings.ShowAddKeyBindingDialog();
|
||||
return KeyBindings.ShowAddKeyBindingDialogAsync();
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
@@ -90,7 +90,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
protected override async Task CopyToProfileAsync(ShellProfile profile)
|
||||
{
|
||||
await base.CopyToProfileAsync(profile);
|
||||
await base.CopyToProfileAsync(profile).ConfigureAwait(false);
|
||||
|
||||
profile.Location = _location;
|
||||
profile.Arguments = _arguments;
|
||||
@@ -99,7 +99,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
public override async Task<string> ValidateAsync()
|
||||
{
|
||||
var error = await base.ValidateAsync();
|
||||
var error = await base.ValidateAsync().ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
@@ -123,18 +123,22 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
!Model.Arguments.NullableEqualTo(_arguments);
|
||||
}
|
||||
|
||||
// Requires UI thread
|
||||
private async Task BrowseForCustomShell()
|
||||
{
|
||||
var file = await _fileSystemService.OpenFile(new[] { ".exe" }).ConfigureAwait(true);
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
|
||||
var file = await _fileSystemService.OpenFileAsync(new[] { ".exe" }).ConfigureAwait(true);
|
||||
if (file != null)
|
||||
{
|
||||
Location = file.Path;
|
||||
}
|
||||
}
|
||||
|
||||
// Requires UI thread
|
||||
private async Task BrowseForWorkingDirectory()
|
||||
{
|
||||
var directory = await _fileSystemService.BrowseForDirectory().ConfigureAwait(true);
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
|
||||
var directory = await _fileSystemService.BrowseForDirectoryAsync().ConfigureAwait(true);
|
||||
if (directory != null)
|
||||
{
|
||||
WorkingDirectory = directory;
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
using System;
|
||||
using FluentTerminal.Models;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Profiles
|
||||
{
|
||||
public class ExecutedCommand
|
||||
{
|
||||
public string Value { get; set; }
|
||||
|
||||
public bool IsProfile { get; set; }
|
||||
|
||||
public DateTime LastExecution { get; set; }
|
||||
|
||||
public int ExecutionCount { get; set; }
|
||||
|
||||
public ShellProfile ShellProfile { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@ using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Windows.UI.Core;
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using FluentTerminal.Models.Messages;
|
||||
using GalaSoft.MvvmLight;
|
||||
|
||||
@@ -18,13 +18,6 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
/// </summary>
|
||||
public abstract class ProfileProviderViewModelBase : ViewModelBase
|
||||
{
|
||||
#region Static
|
||||
|
||||
private static readonly LineEndingStyle[] LineEndingStylesArray =
|
||||
Enum.GetValues(typeof(LineEndingStyle)).Cast<LineEndingStyle>().ToArray();
|
||||
|
||||
#endregion Static
|
||||
|
||||
#region Fields
|
||||
|
||||
protected readonly ISettingsService SettingsService;
|
||||
@@ -67,17 +60,6 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
public ObservableCollection<TerminalTheme> TerminalThemes { get; }
|
||||
|
||||
public ObservableCollection<LineEndingStyle> LineEndingStyles { get; } =
|
||||
new ObservableCollection<LineEndingStyle>(LineEndingStylesArray);
|
||||
|
||||
private LineEndingStyle _lineEndingTranslation;
|
||||
|
||||
public LineEndingStyle LineEndingTranslation
|
||||
{
|
||||
get => _lineEndingTranslation;
|
||||
set => Set(ref _lineEndingTranslation, value);
|
||||
}
|
||||
|
||||
private TabTheme _selectedTabTheme;
|
||||
|
||||
public TabTheme SelectedTabTheme
|
||||
@@ -129,13 +111,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
if (theme == null)
|
||||
{
|
||||
// How to handle attempt to setting null theme?
|
||||
// 1) Throw an exception:
|
||||
//throw new ArgumentNullException();
|
||||
// 2) Ignore the attempt:
|
||||
//return;
|
||||
// 3) Use default theme (probably the best):
|
||||
theme = TerminalThemes.First();
|
||||
return;
|
||||
}
|
||||
else if (!TerminalThemes.Contains(theme))
|
||||
{
|
||||
@@ -172,6 +148,14 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
set => Set(ref _useConPty, value);
|
||||
}
|
||||
|
||||
private bool _useBuffer;
|
||||
|
||||
public bool UseBuffer
|
||||
{
|
||||
get => _useBuffer;
|
||||
set => Set(ref _useBuffer, value);
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Constructor
|
||||
@@ -214,7 +198,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
return;
|
||||
}
|
||||
|
||||
ApplicationView.RunOnDispatcherThread(() =>
|
||||
ApplicationView.ExecuteOnUiThreadAsync(() =>
|
||||
{
|
||||
TerminalThemes.Remove(theme);
|
||||
|
||||
@@ -222,18 +206,18 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
{
|
||||
SelectedTerminalTheme = TerminalThemes.First();
|
||||
}
|
||||
}, false);
|
||||
}, CoreDispatcherPriority.Low, true);
|
||||
}
|
||||
|
||||
private void OnThemeAdded(ThemeAddedMessage message)
|
||||
{
|
||||
ApplicationView.RunOnDispatcherThread(() => TerminalThemes.Add(message.Theme), false);
|
||||
ApplicationView.ExecuteOnUiThreadAsync(() => TerminalThemes.Add(message.Theme), CoreDispatcherPriority.Low);
|
||||
}
|
||||
|
||||
private void Initialize(ShellProfile profile)
|
||||
{
|
||||
LineEndingTranslation = profile.LineEndingTranslation;
|
||||
UseConPty = profile.UseConPty;
|
||||
UseBuffer = profile.UseBuffer;
|
||||
TerminalThemeId = profile.TerminalThemeId;
|
||||
TabThemeId = profile.TabThemeId;
|
||||
}
|
||||
@@ -245,8 +229,8 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
protected virtual Task CopyToProfileAsync(ShellProfile profile)
|
||||
{
|
||||
profile.LineEndingTranslation = _lineEndingTranslation;
|
||||
profile.UseConPty = _useConPty;
|
||||
profile.UseBuffer = _useBuffer;
|
||||
profile.TerminalThemeId = _terminalThemeId;
|
||||
profile.TabThemeId = _tabThemeId;
|
||||
return Task.CompletedTask;
|
||||
@@ -263,8 +247,8 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
/// </summary>
|
||||
public virtual bool HasChanges()
|
||||
{
|
||||
return Model.LineEndingTranslation != _lineEndingTranslation ||
|
||||
Model.UseConPty != _useConPty ||
|
||||
return Model.UseConPty != _useConPty ||
|
||||
Model.UseBuffer != _useBuffer ||
|
||||
!Model.TerminalThemeId.Equals(_terminalThemeId) ||
|
||||
Model.TabThemeId != _tabThemeId;
|
||||
}
|
||||
@@ -280,19 +264,19 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
/// message returned by <see cref="ValidateAsync"/> method.</returns>
|
||||
public async Task<string> AcceptChangesAsync(bool acceptIfInvalid = false)
|
||||
{
|
||||
var error = await ValidateAsync();
|
||||
var error = await ValidateAsync().ConfigureAwait(false);
|
||||
|
||||
if (acceptIfInvalid || string.IsNullOrEmpty(error))
|
||||
{
|
||||
await CopyToProfileAsync(Model);
|
||||
await CopyToProfileAsync(Model).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
public void RejectChanges()
|
||||
public Task RejectChangesAsync()
|
||||
{
|
||||
ApplicationView.RunOnDispatcherThread(() => LoadFromProfile(Model), false);
|
||||
return ApplicationView.ExecuteOnUiThreadAsync(() => LoadFromProfile(Model));
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
@@ -300,7 +284,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
#region Links/shortcuts related
|
||||
|
||||
private const string UseConPtyQueryStringName = "conpty";
|
||||
private const string LineEndingQueryStringName = "lineending";
|
||||
private const string UseBufferQueryStringName = "buffer";
|
||||
private const string TerminalThemeIdQueryStringName = "theme";
|
||||
private const string TabThemeIdQueryStringName = "tab";
|
||||
|
||||
@@ -340,8 +324,7 @@ URL={0}
|
||||
|
||||
public string GetBaseQueryString()
|
||||
{
|
||||
var queryString =
|
||||
$"{UseConPtyQueryStringName}={_useConPty}&{LineEndingQueryStringName}={_lineEndingTranslation}";
|
||||
var queryString = $"{UseConPtyQueryStringName}={_useConPty}&{UseBufferQueryStringName}={_useBuffer}";
|
||||
|
||||
if (_tabThemeId != TabThemes.First().Id)
|
||||
{
|
||||
@@ -372,11 +355,11 @@ URL={0}
|
||||
}
|
||||
|
||||
keyValue = queryStringParams.FirstOrDefault(t =>
|
||||
LineEndingQueryStringName.Equals(t.Item1, StringComparison.OrdinalIgnoreCase));
|
||||
UseBufferQueryStringName.Equals(t.Item1, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (!string.IsNullOrEmpty(keyValue?.Item2) && Enum.TryParse(keyValue.Item2, true, out LineEndingStyle lineEndingTranslation))
|
||||
if (!string.IsNullOrEmpty(keyValue?.Item2) && bool.TryParse(keyValue.Item2?.ToLower(), out bool useBuffer))
|
||||
{
|
||||
LineEndingTranslation = lineEndingTranslation;
|
||||
UseConPty = useBuffer;
|
||||
}
|
||||
|
||||
keyValue = queryStringParams.FirstOrDefault(t =>
|
||||
|
||||
@@ -123,14 +123,16 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
public SshConnectViewModel(ISettingsService settingsService, IApplicationView applicationView,
|
||||
ITrayProcessCommunicationService trayProcessCommunicationService, IFileSystemService fileSystemService,
|
||||
SshProfile original = null) : base(settingsService, applicationView, true,
|
||||
original ?? new SshProfile { UseMosh = settingsService.GetApplicationSettings().UseMoshByDefault })
|
||||
original ?? new SshProfile { UseMosh = settingsService.GetApplicationSettings().UseMoshByDefault,
|
||||
RequestConPty = settingsService.GetApplicationSettings().UseConPty
|
||||
})
|
||||
{
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
_fileSystemService = fileSystemService;
|
||||
|
||||
Initialize((SshProfile)Model);
|
||||
|
||||
BrowseForIdentityFileCommand = new AsyncCommand(BrowseForIdentityFile);
|
||||
BrowseForIdentityFileCommand = new AsyncCommand(BrowseForIdentityFileAsync);
|
||||
}
|
||||
|
||||
#endregion Constructor
|
||||
@@ -147,13 +149,13 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
if (string.IsNullOrEmpty(Username))
|
||||
{
|
||||
_trayProcessCommunicationService.GetUserName().ContinueWith(t =>
|
||||
_trayProcessCommunicationService.GetUserNameAsync().ContinueWith(t =>
|
||||
{
|
||||
var username = t.Result;
|
||||
|
||||
if (string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(username))
|
||||
{
|
||||
ApplicationView.RunOnDispatcherThread(() => Username = username, false);
|
||||
ApplicationView.ExecuteOnUiThreadAsync(() => Username = username);
|
||||
}
|
||||
}, TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
}
|
||||
@@ -164,9 +166,11 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
MoshPortTo = sshProfile.MoshPortTo;
|
||||
}
|
||||
|
||||
private async Task BrowseForIdentityFile()
|
||||
// Requires UI thread
|
||||
private async Task BrowseForIdentityFileAsync()
|
||||
{
|
||||
var file = await _fileSystemService.OpenFile(new[] { "*" });
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
|
||||
var file = await _fileSystemService.OpenFileAsync(new[] { "*" }).ConfigureAwait(true);
|
||||
if (file != null)
|
||||
{
|
||||
IdentityFile = file.Path;
|
||||
@@ -175,20 +179,28 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
private string GetArgumentsString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
var sshArguments = GetSshArguments();
|
||||
|
||||
if (_sshPort != SshProfile.DefaultSshPort)
|
||||
sb.Append($"-p {_sshPort:#####} ");
|
||||
if (!_useMosh)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(sshArguments) ? $"{_username}@{_host}" : $"{sshArguments} {_username}@{_host}";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_identityFile))
|
||||
sb.Append($"-i \"{_identityFile}\" ");
|
||||
return string.IsNullOrEmpty(sshArguments)
|
||||
? $"-p {_moshPortFrom}:{_moshPortTo} {_username}@{_host}"
|
||||
: $"-p {_moshPortFrom}:{_moshPortTo} --ssh=\"ssh {sshArguments}\" {_username}@{_host}";
|
||||
}
|
||||
|
||||
sb.Append($"{_username}@{_host}");
|
||||
private string GetSshArguments()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_identityFile))
|
||||
{
|
||||
return _sshPort == SshProfile.DefaultSshPort ? null : $"-p {_sshPort:#####}";
|
||||
}
|
||||
|
||||
if (_useMosh)
|
||||
sb.Append($" {_moshPortFrom}:{_moshPortTo}");
|
||||
|
||||
return sb.ToString();
|
||||
return _sshPort == SshProfile.DefaultSshPort
|
||||
? $"-i \"{_identityFile}\""
|
||||
: $"-p {_sshPort:#####} -i \"{_identityFile}\"";
|
||||
}
|
||||
|
||||
protected override void LoadFromProfile(ShellProfile profile)
|
||||
@@ -200,9 +212,9 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
protected override async Task CopyToProfileAsync(ShellProfile profile)
|
||||
{
|
||||
await base.CopyToProfileAsync(profile);
|
||||
await base.CopyToProfileAsync(profile).ConfigureAwait(false);
|
||||
|
||||
SshProfile sshProfile = (SshProfile) profile;
|
||||
var sshProfile = (SshProfile) profile;
|
||||
|
||||
sshProfile.Location = _useMosh ? Constants.MoshCommandName : Constants.SshCommandName;
|
||||
sshProfile.Arguments = GetArgumentsString();
|
||||
@@ -220,14 +232,14 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
public override async Task<string> ValidateAsync()
|
||||
{
|
||||
var error = await base.ValidateAsync();
|
||||
var error = await base.ValidateAsync().ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
var result = await GetSshInfoValidationResultAsync();
|
||||
var result = await GetSshInfoValidationResultAsync().ConfigureAwait(false);
|
||||
|
||||
if (result == SshConnectionInfoValidationResult.Valid)
|
||||
{
|
||||
@@ -254,7 +266,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
original.MoshPortFrom != _moshPortFrom || original.MoshPortTo != _moshPortTo;
|
||||
}
|
||||
|
||||
public async Task<SshConnectionInfoValidationResult> GetSshInfoValidationResultAsync()
|
||||
private async Task<SshConnectionInfoValidationResult> GetSshInfoValidationResultAsync()
|
||||
{
|
||||
var result = SshConnectionInfoValidationResult.Valid;
|
||||
|
||||
@@ -273,7 +285,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
result |= SshConnectionInfoValidationResult.SshPortZeroOrNegative;
|
||||
}
|
||||
|
||||
if (!await CheckIdentityFileExistsAsync())
|
||||
if (!await CheckIdentityFileExistsAsync().ConfigureAwait(false))
|
||||
{
|
||||
result |= SshConnectionInfoValidationResult.IdentityFileDoesNotExist;
|
||||
}
|
||||
@@ -296,6 +308,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
return result;
|
||||
}
|
||||
|
||||
// Requires UI thread
|
||||
private async Task<bool> CheckIdentityFileExistsAsync()
|
||||
{
|
||||
var identityFile = _identityFile;
|
||||
@@ -315,7 +328,8 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
}
|
||||
else
|
||||
{
|
||||
var sshConfigDir = await _trayProcessCommunicationService.GetSshConfigDirAsync();
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
|
||||
var sshConfigDir = await _trayProcessCommunicationService.GetSshConfigDirAsync().ConfigureAwait(true);
|
||||
|
||||
if (string.IsNullOrEmpty(sshConfigDir))
|
||||
{
|
||||
@@ -325,7 +339,8 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
fullPath = Path.Combine(sshConfigDir, identityFile);
|
||||
}
|
||||
|
||||
if (await _trayProcessCommunicationService.CheckFileExistsAsync(fullPath))
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
|
||||
if (await _trayProcessCommunicationService.CheckFileExistsAsync(fullPath).ConfigureAwait(true))
|
||||
{
|
||||
_validatedIdentityFile = identityFile;
|
||||
|
||||
@@ -435,14 +450,14 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
public override async Task<Tuple<bool, string>> GetUrlAsync()
|
||||
{
|
||||
var error = await base.ValidateAsync();
|
||||
var error = await base.ValidateAsync().ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
return Tuple.Create(false, error);
|
||||
}
|
||||
|
||||
var result = await GetSshInfoValidationResultAsync();
|
||||
var result = await GetSshInfoValidationResultAsync().ConfigureAwait(false);
|
||||
|
||||
if (result != SshConnectionInfoValidationResult.Valid &&
|
||||
// For links we can ignore missing username
|
||||
@@ -459,7 +474,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
return Tuple.Create(false, error);
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(_useMosh ? MoshUriScheme : SshUriScheme);
|
||||
var sb = new StringBuilder(_useMosh ? MoshUriScheme : SshUriScheme);
|
||||
|
||||
sb.Append("://");
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.ViewModels.Infrastructure;
|
||||
using GalaSoft.MvvmLight;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Settings
|
||||
@@ -18,7 +17,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
_updateService = updateService;
|
||||
_applicationView = applicationView;
|
||||
|
||||
CheckForUpdatesCommand = new AsyncCommand(() => CheckForUpdate(true));
|
||||
CheckForUpdatesCommand = new AsyncCommand(() => CheckForUpdateAsync(true));
|
||||
}
|
||||
|
||||
public IAsyncCommand CheckForUpdatesCommand { get; }
|
||||
@@ -28,7 +27,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
get
|
||||
{
|
||||
var version = _updateService.GetCurrentVersion();
|
||||
return ConvertVersionToString(version);
|
||||
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,19 +58,16 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
|
||||
public Task OnNavigatedTo()
|
||||
{
|
||||
return CheckForUpdate(false);
|
||||
return CheckForUpdateAsync(false);
|
||||
}
|
||||
|
||||
public async Task CheckForUpdate(bool notifyNoUpdate)
|
||||
private async Task CheckForUpdateAsync(bool notifyNoUpdate)
|
||||
{
|
||||
var version = ConvertVersionToString(await _updateService.GetLatestVersionAsync());
|
||||
await _applicationView.RunOnDispatcherThread(() => LatestVersion = version);
|
||||
await _updateService.CheckForUpdate(notifyNoUpdate);
|
||||
}
|
||||
|
||||
private string ConvertVersionToString(Version version)
|
||||
{
|
||||
return string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, version.Revision);
|
||||
var version = await _updateService.GetLatestVersionAsync().ConfigureAwait(false);
|
||||
await _applicationView.ExecuteOnUiThreadAsync(() =>
|
||||
LatestVersion = $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}")
|
||||
.ConfigureAwait(false);
|
||||
await _updateService.CheckForUpdateAsync(notifyNoUpdate).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using FluentTerminal.App.ViewModels.Infrastructure;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -40,8 +39,8 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
|
||||
_applicationSettings = _settingsService.GetApplicationSettings();
|
||||
|
||||
RestoreDefaultsCommand = new AsyncCommand(RestoreDefaults);
|
||||
BrowseLogDirectoryCommand = new AsyncCommand(BrowseLogDirectory);
|
||||
RestoreDefaultsCommand = new AsyncCommand(RestoreDefaultsAsync);
|
||||
BrowseLogDirectoryCommand = new AsyncCommand(BrowseLogDirectoryAsync);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Languages => _applicationLanguageService.Languages;
|
||||
@@ -62,9 +61,11 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnNavigatedTo()
|
||||
// Requires UI thread
|
||||
public async Task OnNavigatedToAsync()
|
||||
{
|
||||
var startupTaskStatus = await _startupTaskService.GetStatus();
|
||||
// ConfigureAwait(true) because we want to execute SetStartupTaskPropertiesForStatus from the calling (UI) thread.
|
||||
var startupTaskStatus = await _startupTaskService.GetStatusAsync().ConfigureAwait(true);
|
||||
SetStartupTaskPropertiesForStatus(startupTaskStatus);
|
||||
}
|
||||
|
||||
@@ -172,7 +173,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
|
||||
_trayProcessCommunicationService.MuteTerminal(value);
|
||||
_trayProcessCommunicationService.MuteTerminalAsync(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,6 +219,20 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseConPty
|
||||
{
|
||||
get => _applicationSettings.UseConPty;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.UseConPty != value)
|
||||
{
|
||||
_applicationSettings.UseConPty = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool BottomIsSelected
|
||||
{
|
||||
get => TabsPosition == TabsPosition.Bottom;
|
||||
@@ -268,6 +283,21 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
_applicationSettings.NewTerminalLocation = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
RaisePropertyChanged(nameof(TabIsSelected));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TabWindowCascadingAppMenu
|
||||
{
|
||||
get => _applicationSettings.TabWindowCascadingAppMenu;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.TabWindowCascadingAppMenu != value)
|
||||
{
|
||||
_applicationSettings.TabWindowCascadingAppMenu = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,11 +311,10 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
get => _startupTaskEnabled;
|
||||
set
|
||||
{
|
||||
if (_startupTaskEnabled != value)
|
||||
if (Set(ref _startupTaskEnabled, value))
|
||||
{
|
||||
_startupTaskEnabled = value;
|
||||
RaisePropertyChanged(nameof(StartupTaskEnabled));
|
||||
SetStartupTaskState(value);
|
||||
// ReSharper disable once AssignmentIsFullyDiscarded
|
||||
_ = SetStartupTaskStateAsync(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,7 +328,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
public bool TabIsSelected
|
||||
{
|
||||
get => NewTerminalLocation == NewTerminalLocation.Tab;
|
||||
set { if (value) NewTerminalLocation = NewTerminalLocation.Tab; }
|
||||
set => NewTerminalLocation = value ? NewTerminalLocation.Tab : NewTerminalLocation.Window;
|
||||
}
|
||||
|
||||
public TabsPosition TabsPosition
|
||||
@@ -362,15 +391,27 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
public bool WindowIsSelected
|
||||
public bool ShowTextCopied
|
||||
{
|
||||
get => NewTerminalLocation == NewTerminalLocation.Window;
|
||||
set { if (value) NewTerminalLocation = NewTerminalLocation.Window; }
|
||||
get => _applicationSettings.ShowTextCopied;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.ShowTextCopied != value)
|
||||
{
|
||||
_applicationSettings.ShowTextCopied = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RestoreDefaults()
|
||||
// Requires UI thread
|
||||
private async Task RestoreDefaultsAsync()
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmRestoreGeneralSettings"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards
|
||||
var result = await _dialogService.ShowMessageDialogAsync(I18N.Translate("PleaseConfirm"),
|
||||
I18N.Translate("ConfirmRestoreGeneralSettings"), DialogButton.OK, DialogButton.Cancel)
|
||||
.ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
@@ -390,9 +431,12 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
EnableLogging = defaults.EnableLogging;
|
||||
PrintableOutputOnly = defaults.PrintableOutputOnly;
|
||||
LogDirectoryPath = defaults.LogDirectoryPath;
|
||||
UseConPty = defaults.UseConPty;
|
||||
ShowTextCopied = defaults.ShowTextCopied;
|
||||
}
|
||||
}
|
||||
|
||||
// Requires UI thread
|
||||
private void SetStartupTaskPropertiesForStatus(StartupTaskStatus startupTaskStatus)
|
||||
{
|
||||
switch (startupTaskStatus)
|
||||
@@ -420,27 +464,39 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
StartupTaskErrorMessage = I18N.Translate("DisabledByPolicy");
|
||||
CanEnableStartupTask = false;
|
||||
break;
|
||||
|
||||
case StartupTaskStatus.EnabledByPolicy:
|
||||
StartupTaskEnabled = true;
|
||||
StartupTaskErrorMessage = I18N.TranslateWithFallback("EnabledByPolicy", "Enabled by policy.");
|
||||
CanEnableStartupTask = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetStartupTaskState(bool enabled)
|
||||
// Requires UI thread
|
||||
private async Task SetStartupTaskStateAsync(bool enabled)
|
||||
{
|
||||
StartupTaskStatus status;
|
||||
if (enabled)
|
||||
{
|
||||
status = await _startupTaskService.EnableStartupTask();
|
||||
// ConfigureAwait(true) because we need to execute SetStartupTaskPropertiesForStatus in the calling (UI) thread.
|
||||
status = await _startupTaskService.EnableStartupTaskAsync().ConfigureAwait(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _startupTaskService.DisableStartupTask();
|
||||
status = await _startupTaskService.GetStatus();
|
||||
// ConfigureAwait(true) because we need to execute SetStartupTaskPropertiesForStatus in the calling (UI) thread.
|
||||
await _startupTaskService.DisableStartupTaskAsync().ConfigureAwait(true);
|
||||
// ConfigureAwait(true) because we need to execute SetStartupTaskPropertiesForStatus in the calling (UI) thread.
|
||||
status = await _startupTaskService.GetStatusAsync().ConfigureAwait(true);
|
||||
}
|
||||
SetStartupTaskPropertiesForStatus(status);
|
||||
}
|
||||
|
||||
private async Task BrowseLogDirectory()
|
||||
// Requires UI thread
|
||||
private async Task BrowseLogDirectoryAsync()
|
||||
{
|
||||
var folder = await _fileSystemService.BrowseForDirectory();
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
|
||||
var folder = await _fileSystemService.BrowseForDirectoryAsync().ConfigureAwait(true);
|
||||
|
||||
if (folder != null)
|
||||
{
|
||||
|
||||
@@ -17,10 +17,11 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
Model = keyBinding;
|
||||
Parent = parent;
|
||||
_dialogService = dialogService;
|
||||
EditCommand = new AsyncCommand(Edit);
|
||||
DeleteCommand = new AsyncCommand(Delete);
|
||||
EditCommand = new AsyncCommand(EditAsync);
|
||||
DeleteCommand = new AsyncCommand(DeleteAsync);
|
||||
}
|
||||
|
||||
// Needs to be triggered from the UI thread
|
||||
public event EventHandler Deleted;
|
||||
|
||||
public event EventHandler Edited;
|
||||
@@ -97,8 +98,10 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> Edit()
|
||||
// Requires UI thread
|
||||
public async Task<bool> EditAsync()
|
||||
{
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
|
||||
var keyBinding = await _dialogService.ShowCreateKeyBindingDialog().ConfigureAwait(true);
|
||||
|
||||
if (keyBinding != null)
|
||||
@@ -117,9 +120,12 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task Delete()
|
||||
// Requires UI thread
|
||||
private async Task DeleteAsync()
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmDeleteKeybindings"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
// ConfigureAwait(true) because we need to trigger Deleted event in the calling (UI) thread.
|
||||
var result = await _dialogService.ShowMessageDialogAsync(I18N.Translate("PleaseConfirm"),
|
||||
I18N.Translate("ConfirmDeleteKeybindings"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
|
||||
@@ -23,8 +23,8 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
_settingsService = settingsService;
|
||||
_dialogService = dialogService;
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
|
||||
AddCommand = new RelayCommand<string>(async command => await Add(command).ConfigureAwait(false));
|
||||
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaultsAsync().ConfigureAwait(false));
|
||||
AddCommand = new RelayCommand<string>(async command => await AddAsync(command).ConfigureAwait(false));
|
||||
|
||||
Initialize(_settingsService.GetCommandKeyBindings());
|
||||
}
|
||||
@@ -33,12 +33,12 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
public ObservableCollection<KeyBindingsViewModel> KeyBindings { get; } = new ObservableCollection<KeyBindingsViewModel>();
|
||||
public RelayCommand RestoreDefaultsCommand { get; }
|
||||
|
||||
private async Task Add(string command)
|
||||
private Task AddAsync(string command)
|
||||
{
|
||||
var keyBinding = KeyBindings.FirstOrDefault(k => k.Command == command);
|
||||
await keyBinding?.ShowAddKeyBindingDialog();
|
||||
return KeyBindings.First(k => k.Command == command).ShowAddKeyBindingDialogAsync();
|
||||
}
|
||||
|
||||
// Requires UI thread
|
||||
private void ClearKeyBindings()
|
||||
{
|
||||
foreach (var keyBinding in KeyBindings)
|
||||
@@ -49,6 +49,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
KeyBindings.Clear();
|
||||
}
|
||||
|
||||
// Requires UI thread
|
||||
private void Initialize(IDictionary<string, ICollection<KeyBinding>> keyBindings)
|
||||
{
|
||||
ClearKeyBindings();
|
||||
@@ -69,12 +70,15 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
private void OnEdited(string command, ICollection<KeyBinding> keyBindings)
|
||||
{
|
||||
_settingsService.SaveKeyBindings(command, keyBindings);
|
||||
_trayProcessCommunicationService.UpdateToggleWindowKeyBindings();
|
||||
_trayProcessCommunicationService.UpdateToggleWindowKeyBindingsAsync();
|
||||
}
|
||||
|
||||
private async Task RestoreDefaults()
|
||||
// Requires UI thread
|
||||
private async Task RestoreDefaultsAsync()
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmRestoreKeybindings"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
// ConfigureAwait(true) because we need to execute Initialize in the calling (UI) thread.
|
||||
var result = await _dialogService.ShowMessageDialogAsync(I18N.Translate("PleaseConfirm"),
|
||||
I18N.Translate("ConfirmRestoreKeybindings"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
|
||||
@@ -42,21 +42,25 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
|
||||
public ObservableCollection<KeyBindingViewModel> KeyBindings { get; } = new ObservableCollection<KeyBindingViewModel>();
|
||||
|
||||
public async Task ShowAddKeyBindingDialog()
|
||||
// Requires UI thread
|
||||
public async Task ShowAddKeyBindingDialogAsync()
|
||||
{
|
||||
var newKeyBinding = new KeyBindingViewModel(new KeyBinding { Command = Command }, _dialogService, this);
|
||||
|
||||
if (await newKeyBinding.Edit().ConfigureAwait(true))
|
||||
// ConfigureAwait(true) because we need to execute Add method in the calling (UI) thread.
|
||||
if (await newKeyBinding.EditAsync().ConfigureAwait(true))
|
||||
{
|
||||
Add(newKeyBinding.Model);
|
||||
}
|
||||
}
|
||||
|
||||
// Requires UI thread
|
||||
public void Clear()
|
||||
{
|
||||
KeyBindings.Clear();
|
||||
}
|
||||
|
||||
// Requires UI thread
|
||||
public void Add(KeyBinding keyBinding)
|
||||
{
|
||||
var viewModel = new KeyBindingViewModel(keyBinding, _dialogService, this);
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
_defaultValueProvider = defaultValueProvider;
|
||||
_applicationSettings = _settingsService.GetApplicationSettings();
|
||||
|
||||
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
|
||||
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaultsAsync().ConfigureAwait(false));
|
||||
}
|
||||
|
||||
public bool CopyOnSelect
|
||||
@@ -71,6 +71,12 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
set { if (value) MouseRightClickAction = MouseAction.Paste; }
|
||||
}
|
||||
|
||||
public bool MouseRightClickCopySelectionOrPasteIsSelected
|
||||
{
|
||||
get => MouseRightClickAction == MouseAction.CopySelectionOrPaste;
|
||||
set { if (value) MouseRightClickAction = MouseAction.CopySelectionOrPaste; }
|
||||
}
|
||||
|
||||
public MouseAction MouseMiddleClickAction
|
||||
{
|
||||
get => _applicationSettings.MouseMiddleClickAction;
|
||||
@@ -103,11 +109,21 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
set { if (value) MouseMiddleClickAction = MouseAction.Paste; }
|
||||
}
|
||||
|
||||
public bool MouseMiddleClickCopySelectionOrPasteIsSelected
|
||||
{
|
||||
get => MouseMiddleClickAction == MouseAction.CopySelectionOrPaste;
|
||||
set { if (value) MouseMiddleClickAction = MouseAction.CopySelectionOrPaste; }
|
||||
}
|
||||
|
||||
public RelayCommand RestoreDefaultsCommand { get; }
|
||||
|
||||
private async Task RestoreDefaults()
|
||||
// Requires UI thread
|
||||
private async Task RestoreDefaultsAsync()
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmRestoreMouseSettings"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
|
||||
var result = await _dialogService.ShowMessageDialogAsync(I18N.Translate("PleaseConfirm"),
|
||||
I18N.Translate("ConfirmRestoreMouseSettings"), DialogButton.OK, DialogButton.Cancel)
|
||||
.ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
|
||||
@@ -90,7 +90,8 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
Id = Guid.NewGuid(),
|
||||
PreInstalled = false,
|
||||
Name = "New profile",
|
||||
KeyBindings = new List<KeyBinding>()
|
||||
KeyBindings = new List<KeyBinding>(),
|
||||
UseConPty = _settingsService.GetApplicationSettings().UseConPty
|
||||
};
|
||||
|
||||
AddShellProfile(shellProfile);
|
||||
|
||||
@@ -52,14 +52,14 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
|
||||
private void OnSshProfileDeleted(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is SshProfileViewModel shellProfile)
|
||||
if (sender is SshProfileViewModel sshProfile)
|
||||
{
|
||||
if (SelectedSshProfile == shellProfile)
|
||||
if (SelectedSshProfile == sshProfile)
|
||||
{
|
||||
SelectedSshProfile = SshProfiles.First();
|
||||
}
|
||||
SshProfiles.Remove(shellProfile);
|
||||
_settingsService.DeleteSshProfile(shellProfile.Id);
|
||||
SshProfiles.Remove(sshProfile);
|
||||
_settingsService.DeleteSshProfile(sshProfile.Id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,8 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
PreInstalled = false,
|
||||
Name = "New SSH profile",
|
||||
KeyBindings = new List<KeyBinding>(),
|
||||
UseMosh = _settingsService.GetApplicationSettings().UseMoshByDefault
|
||||
UseMosh = _settingsService.GetApplicationSettings().UseMoshByDefault,
|
||||
RequestConPty = _settingsService.GetApplicationSettings().UseConPty
|
||||
};
|
||||
|
||||
AddSshProfile(shellProfile);
|
||||
|
||||
@@ -68,6 +68,20 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseAcrylicBackground
|
||||
{
|
||||
get => _terminalOptions.UseAcrylicBackground;
|
||||
set
|
||||
{
|
||||
if (_terminalOptions.UseAcrylicBackground != value)
|
||||
{
|
||||
_terminalOptions.UseAcrylicBackground = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string FontFamily
|
||||
{
|
||||
get => _terminalOptions.FontFamily;
|
||||
@@ -82,6 +96,20 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
public int FontWeight
|
||||
{
|
||||
get => _terminalOptions.FontWeight;
|
||||
set
|
||||
{
|
||||
if (_terminalOptions.FontWeight != value)
|
||||
{
|
||||
_terminalOptions.FontWeight = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double BackgroundOpacity
|
||||
{
|
||||
get => _terminalOptions.BackgroundOpacity;
|
||||
@@ -150,34 +178,6 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
public bool BoldText
|
||||
{
|
||||
get => _terminalOptions.BoldText;
|
||||
set
|
||||
{
|
||||
if (_terminalOptions.BoldText != value)
|
||||
{
|
||||
_terminalOptions.BoldText = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowTextCopied
|
||||
{
|
||||
get => _terminalOptions.ShowTextCopied;
|
||||
set
|
||||
{
|
||||
if (_terminalOptions.ShowTextCopied != value)
|
||||
{
|
||||
_terminalOptions.ShowTextCopied = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<FontInfo> Fonts { get; }
|
||||
|
||||
public int FontSize
|
||||
@@ -226,9 +226,13 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RestoreDefaults()
|
||||
// Requires UI thread
|
||||
private async Task RestoreDefaultsAsync()
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmRestoreTerminalOptions"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
|
||||
var result = await _dialogService.ShowMessageDialogAsync(I18N.Translate("PleaseConfirm"),
|
||||
I18N.Translate("ConfirmRestoreTerminalOptions"), DialogButton.OK, DialogButton.Cancel)
|
||||
.ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
@@ -238,10 +242,9 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
ScrollBarStyle = defaults.ScrollBarStyle;
|
||||
FontFamily = defaults.FontFamily;
|
||||
FontSize = defaults.FontSize;
|
||||
BoldText = defaults.BoldText;
|
||||
FontWeight = defaults.FontWeight;
|
||||
BackgroundOpacity = defaults.BackgroundOpacity;
|
||||
ScrollBackLimit = defaults.ScrollBackLimit.ToString();
|
||||
ShowTextCopied = defaults.ShowTextCopied;
|
||||
WordSeparator = defaults.WordSeparator;
|
||||
}
|
||||
}
|
||||
@@ -252,10 +255,10 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
_dialogService = dialogService;
|
||||
_defaultValueProvider = defaultValueProvider;
|
||||
|
||||
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
|
||||
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaultsAsync().ConfigureAwait(false));
|
||||
|
||||
Fonts = systemFontService.GetSystemFontFamilies().OrderBy(s => s.Name);
|
||||
Sizes = Enumerable.Range(1, 72);
|
||||
Sizes = Enumerable.Range(2, 72);
|
||||
|
||||
_terminalOptions = _settingsService.GetTerminalOptions();
|
||||
}
|
||||
|
||||
@@ -21,20 +21,28 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
private double _backgroundOpacity;
|
||||
private readonly IThemeParserFactory _themeParserFactory;
|
||||
private readonly IFileSystemService _fileSystemService;
|
||||
private readonly IImageFileSystemService _imageFileSystemService;
|
||||
|
||||
public event EventHandler<string> SelectedThemeBackgroundColorChanged;
|
||||
public event EventHandler<ImageFile> SelectedThemeBackgroundImageChanged;
|
||||
public event EventHandler<ThemeViewModel> SelectedThemeChanged;
|
||||
|
||||
public ThemesPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider,
|
||||
IThemeParserFactory themeParserFactory, IFileSystemService fileSystemService)
|
||||
public ThemesPageViewModel(ISettingsService settingsService,
|
||||
IDialogService dialogService,
|
||||
IDefaultValueProvider defaultValueProvider,
|
||||
IThemeParserFactory themeParserFactory,
|
||||
IFileSystemService fileSystemService,
|
||||
IImageFileSystemService imageFileSystemService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_dialogService = dialogService;
|
||||
_defaultValueProvider = defaultValueProvider;
|
||||
_themeParserFactory = themeParserFactory;
|
||||
_fileSystemService = fileSystemService;
|
||||
_imageFileSystemService = imageFileSystemService;
|
||||
|
||||
CreateThemeCommand = new RelayCommand(CreateTheme);
|
||||
ImportThemeCommand = new AsyncCommand(ImportTheme);
|
||||
ImportThemeCommand = new AsyncCommand(ImportThemeAsync);
|
||||
CloneCommand = new RelayCommand<ThemeViewModel>(CloneTheme);
|
||||
|
||||
MessengerInstance.Register<TerminalOptionsChangedMessage>(this, OnTerminalOptionsChanged);
|
||||
@@ -44,7 +52,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
var activeThemeId = _settingsService.GetCurrentThemeId();
|
||||
foreach (var theme in _settingsService.GetThemes())
|
||||
{
|
||||
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService, fileSystemService, false);
|
||||
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService, _fileSystemService, _imageFileSystemService, false);
|
||||
viewModel.Activated += OnThemeActivated;
|
||||
viewModel.Deleted += OnThemeDeleted;
|
||||
|
||||
@@ -76,11 +84,17 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
if (_selectedTheme != null)
|
||||
{
|
||||
_selectedTheme.BackgroundChanged -= OnSelectedThemeBackgroundChanged;
|
||||
_selectedTheme.BackgroundImageChanged -= OnSelectedThemeBackgroundImageChanged;
|
||||
}
|
||||
|
||||
Set(ref _selectedTheme, value);
|
||||
SelectedThemeChanged?.Invoke(this, _selectedTheme);
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
_selectedTheme.BackgroundOpacity = BackgroundOpacity;
|
||||
value.BackgroundChanged += OnSelectedThemeBackgroundChanged;
|
||||
value.BackgroundImageChanged += OnSelectedThemeBackgroundImageChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,28 +127,49 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
AddTheme(theme);
|
||||
}
|
||||
|
||||
private async Task ImportTheme()
|
||||
// Requires UI thread
|
||||
private async Task ImportThemeAsync()
|
||||
{
|
||||
var file = await _fileSystemService.OpenFile(_themeParserFactory.SupportedFileTypes).ConfigureAwait(true);
|
||||
// ConfigureAwait(true) because we need to execute AddTheme method in the calling (UI) thread.
|
||||
var file = await _fileSystemService.OpenFileAsync(_themeParserFactory.SupportedFileTypes)
|
||||
.ConfigureAwait(true);
|
||||
|
||||
if (file != null)
|
||||
{
|
||||
var parser = _themeParserFactory.GetParser(file.FileType);
|
||||
|
||||
if (parser == null)
|
||||
{
|
||||
await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("ImportThemeFailed"), I18N.Translate("NoSuitableParserFound"), DialogButton.OK).ConfigureAwait(false);
|
||||
await _dialogService.ShowMessageDialogAsync(I18N.Translate("ImportThemeFailed"),
|
||||
I18N.Translate("NoSuitableParserFound"), DialogButton.OK).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var theme = await parser.Parse(file.Name, file.Content).ConfigureAwait(true);
|
||||
// ConfigureAwait(true) because we need to execute AddTheme method in the calling (UI) thread.
|
||||
var exportedTheme = await parser.Import(file.Name, file.Content).ConfigureAwait(true);
|
||||
|
||||
AddTheme(theme);
|
||||
if (!string.IsNullOrWhiteSpace(exportedTheme.EncodedImage))
|
||||
{
|
||||
// ConfigureAwait(true) because we need to execute AddTheme method in the calling (UI) thread.
|
||||
var importedImage = await _imageFileSystemService
|
||||
.ImportThemeImageAsync(exportedTheme.BackgroundImage, exportedTheme.EncodedImage)
|
||||
.ConfigureAwait(true);
|
||||
|
||||
exportedTheme.BackgroundImage = importedImage;
|
||||
}
|
||||
|
||||
var terminalTheme = new TerminalTheme(exportedTheme);
|
||||
|
||||
AddTheme(terminalTheme);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("ImportThemeFailed"), exception.Message, DialogButton.OK).ConfigureAwait(false);
|
||||
await _dialogService
|
||||
.ShowMessageDialogAsync(I18N.Translate("ImportThemeFailed"), exception.Message, DialogButton.OK)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,7 +178,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
_settingsService.SaveTheme(theme, true);
|
||||
|
||||
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService, _fileSystemService, true);
|
||||
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService, _fileSystemService, _imageFileSystemService, true);
|
||||
viewModel.EditCommand.Execute(null);
|
||||
viewModel.Activated += OnThemeActivated;
|
||||
viewModel.Deleted += OnThemeDeleted;
|
||||
@@ -192,5 +227,10 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
SelectedThemeBackgroundColorChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void OnSelectedThemeBackgroundImageChanged(object sender, ImageFile e)
|
||||
{
|
||||
SelectedThemeBackgroundImageChanged?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,14 @@ namespace FluentTerminal.App.ViewModels
|
||||
public SettingsViewModel(ISettingsService settingsService, IDefaultValueProvider defaultValueProvider, IDialogService dialogService,
|
||||
ITrayProcessCommunicationService trayProcessCommunicationService, IThemeParserFactory themeParserFactory, ISystemFontService systemFontService,
|
||||
IFileSystemService fileSystemService, IStartupTaskService startupTaskService, IApplicationView applicationView,
|
||||
IApplicationLanguageService applicationLanguageService, ApplicationDataContainers containers)
|
||||
IApplicationLanguageService applicationLanguageService, ApplicationDataContainers containers,
|
||||
IImageFileSystemService imageFileSystemService)
|
||||
{
|
||||
KeyBindings = new KeyBindingsPageViewModel(settingsService, dialogService, trayProcessCommunicationService);
|
||||
General = new GeneralPageViewModel(settingsService, dialogService, defaultValueProvider, startupTaskService, applicationLanguageService, trayProcessCommunicationService, fileSystemService);
|
||||
Profiles = new ProfilesPageViewModel(settingsService, dialogService, defaultValueProvider, fileSystemService, applicationView);
|
||||
Terminal = new TerminalPageViewModel(settingsService, dialogService, defaultValueProvider, systemFontService);
|
||||
Themes = new ThemesPageViewModel(settingsService, dialogService, defaultValueProvider, themeParserFactory, fileSystemService);
|
||||
Themes = new ThemesPageViewModel(settingsService, dialogService, defaultValueProvider, themeParserFactory, fileSystemService, imageFileSystemService);
|
||||
Mouse = new MousePageViewModel(settingsService, dialogService, defaultValueProvider);
|
||||
SshProfiles = new SshProfilesPageViewModel(settingsService, dialogService, fileSystemService,
|
||||
applicationView, trayProcessCommunicationService, containers.HistoryContainer);
|
||||
|
||||
@@ -60,21 +60,23 @@ namespace FluentTerminal.App.ViewModels
|
||||
shellProfile);
|
||||
|
||||
SetDefaultCommand = new RelayCommand(SetDefault);
|
||||
RestoreDefaultsCommand = new AsyncCommand(RestoreDefaults);
|
||||
RestoreDefaultsCommand = new AsyncCommand(RestoreDefaultsAsync);
|
||||
}
|
||||
|
||||
#endregion Constrcutor
|
||||
|
||||
#region Methods
|
||||
|
||||
private async Task RestoreDefaults()
|
||||
// Requires UI thread
|
||||
private async Task RestoreDefaultsAsync()
|
||||
{
|
||||
if (InEditMode || !ProfileVm.PreInstalled)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
var result = await DialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"),
|
||||
// ConfigureAwait(true) because we need to execute Initialize method in the calling (UI) thread.
|
||||
var result = await DialogService.ShowMessageDialogAsync(I18N.Translate("PleaseConfirm"),
|
||||
I18N.Translate("ConfirmRestoreProfile"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using FluentTerminal.Models;
|
||||
using GalaSoft.MvvmLight;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
public class TabThemeViewModel : ViewModelBase
|
||||
{
|
||||
private bool _isSelected;
|
||||
|
||||
public TabThemeViewModel(TabTheme theme, TerminalViewModel terminal)
|
||||
{
|
||||
Theme = theme;
|
||||
Terminal = terminal;
|
||||
}
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set
|
||||
{
|
||||
if (Set(ref _isSelected, value) && value)
|
||||
{
|
||||
Terminal.TabTheme = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TabTheme Theme { get; }
|
||||
public TerminalViewModel Terminal { get; }
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,15 @@ using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI.Core;
|
||||
using FluentTerminal.Models.Messages;
|
||||
using System.Windows.Input;
|
||||
using FluentTerminal.App.ViewModels.Menu;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
@@ -25,21 +30,25 @@ namespace FluentTerminal.App.ViewModels
|
||||
#endregion Static
|
||||
|
||||
#region Serialize terminal state
|
||||
|
||||
private class TerminalState
|
||||
{
|
||||
public bool HasCustomTitle { get; set; }
|
||||
public string ShellTitle { get; set; }
|
||||
public string TabTitle { get; set; }
|
||||
public TerminalTheme TerminalTheme { get; set; }
|
||||
public TabTheme TabTheme { get; set; }
|
||||
public int TabThemeId { get; set; }
|
||||
public bool ShowSearchPanel { get; set; }
|
||||
public string SearchText { get; set; }
|
||||
public bool SearchMatchCase { get; set; }
|
||||
public bool SearchWholeWord { get; set; }
|
||||
public bool SearchWithRegex { get; set; }
|
||||
public string XtermBufferState { get; set; }
|
||||
public byte TerminalId { get; set; }
|
||||
public ShellProfile ShellProfile { get; set; }
|
||||
}
|
||||
|
||||
public async Task<string> Serialize()
|
||||
public async Task<string> SerializeAsync()
|
||||
{
|
||||
TerminalState state = new TerminalState
|
||||
{
|
||||
@@ -47,10 +56,13 @@ namespace FluentTerminal.App.ViewModels
|
||||
ShellTitle = ShellTitle,
|
||||
TabTitle = TabTitle,
|
||||
TerminalTheme = TerminalTheme,
|
||||
TabTheme = TabTheme,
|
||||
TabThemeId = TabTheme.Theme.Id,
|
||||
ShowSearchPanel = ShowSearchPanel,
|
||||
SearchText = SearchText,
|
||||
XtermBufferState = await SerializeXtermState(),
|
||||
SearchMatchCase = SearchMatchCase,
|
||||
SearchWholeWord = SearchWholeWord,
|
||||
SearchWithRegex = SearchWithRegex,
|
||||
XtermBufferState = await SerializeXtermStateAsync().ConfigureAwait(false),
|
||||
TerminalId = Terminal.Id,
|
||||
ShellProfile = ShellProfile
|
||||
};
|
||||
@@ -67,12 +79,17 @@ namespace FluentTerminal.App.ViewModels
|
||||
ShellTitle = state.ShellTitle;
|
||||
TabTitle = state.TabTitle;
|
||||
TerminalTheme = state.TerminalTheme;
|
||||
TabTheme = state.TabTheme;
|
||||
TabTheme = TabThemes.FirstOrDefault(t => t.Theme.Id == state.TabThemeId);
|
||||
ShowSearchPanel = state.ShowSearchPanel;
|
||||
SearchText = state.SearchText;
|
||||
SearchMatchCase = state.SearchMatchCase;
|
||||
SearchWholeWord = state.SearchWholeWord;
|
||||
SearchWithRegex = state.SearchWithRegex;
|
||||
XtermBufferState = state.XtermBufferState;
|
||||
_terminalId = state.TerminalId;
|
||||
ShellProfile = state.ShellProfile;
|
||||
|
||||
TabTheme.IsSelected = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,21 +102,29 @@ namespace FluentTerminal.App.ViewModels
|
||||
private bool _hasExitedWithError;
|
||||
private string _searchText;
|
||||
private bool _showSearchPanel;
|
||||
private TabTheme _tabTheme;
|
||||
private bool _searchMatchCase;
|
||||
private bool _searchWholeWord;
|
||||
private bool _searchWithRegex;
|
||||
private TabThemeViewModel _tabTheme;
|
||||
private TerminalTheme _terminalTheme;
|
||||
private TerminalOptions _terminalOptions;
|
||||
private string _tabTitle;
|
||||
private string _shellTitle;
|
||||
private bool _hasCustomTitle;
|
||||
private byte? _terminalId;
|
||||
private bool _hasSelection;
|
||||
private string _hoveredUri;
|
||||
private MenuViewModel _contextMenu;
|
||||
private MenuViewModel _tabContextMenu;
|
||||
|
||||
public TerminalViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService,
|
||||
IKeyboardCommandService keyboardCommandService, ApplicationSettings applicationSettings, ShellProfile shellProfile,
|
||||
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService, string terminalState = null)
|
||||
IApplicationView applicationView, IClipboardService clipboardService, string terminalState = null)
|
||||
{
|
||||
MessengerInstance.Register<ApplicationSettingsChangedMessage>(this, OnApplicationSettingsChanged);
|
||||
MessengerInstance.Register<CurrentThemeChangedMessage>(this, OnCurrentThemeChanged);
|
||||
MessengerInstance.Register<TerminalOptionsChangedMessage>(this, OnTerminalOptionsChanged);
|
||||
MessengerInstance.Register<KeyBindingsChangedMessage>(this, OnKeyBindingsChanged);
|
||||
|
||||
SettingsService = settingsService;
|
||||
|
||||
@@ -116,24 +141,31 @@ namespace FluentTerminal.App.ViewModels
|
||||
ShellProfile = shellProfile;
|
||||
TerminalTheme = shellProfile.TerminalThemeId == Guid.Empty ? SettingsService.GetCurrentTheme() : SettingsService.GetTheme(shellProfile.TerminalThemeId);
|
||||
|
||||
TabThemes = new ObservableCollection<TabTheme>(SettingsService.GetTabThemes());
|
||||
TabTheme = TabThemes.FirstOrDefault(t => t.Id == ShellProfile.TabThemeId);
|
||||
|
||||
CloseCommand = new AsyncCommand(CloseTab, CanExecuteCommand);
|
||||
TabThemes = new ObservableCollection<TabThemeViewModel>(SettingsService.GetTabThemes().Select(theme => new TabThemeViewModel(theme, this)));
|
||||
CloseCommand = new AsyncCommand(TryCloseAsync, CanExecuteCommand);
|
||||
CloseLeftTabsCommand = new RelayCommand(CloseLeftTabs, CanExecuteCommand);
|
||||
CloseRightTabsCommand = new RelayCommand(CloseRightTabs, CanExecuteCommand);
|
||||
CloseOtherTabsCommand = new RelayCommand(CloseOtherTabs, CanExecuteCommand);
|
||||
FindNextCommand = new RelayCommand(FindNext, CanExecuteCommand);
|
||||
FindPreviousCommand = new RelayCommand(FindPrevious, CanExecuteCommand);
|
||||
CloseSearchPanelCommand = new RelayCommand(CloseSearchPanel, CanExecuteCommand);
|
||||
SelectTabThemeCommand = new RelayCommand<string>(SelectTabTheme, CanExecuteCommand);
|
||||
EditTitleCommand = new AsyncCommand(EditTitle, CanExecuteCommand);
|
||||
EditTitleCommand = new AsyncCommand(EditTitleAsync, CanExecuteCommand);
|
||||
DuplicateTabCommand = new RelayCommand(DuplicateTab, CanExecuteCommand);
|
||||
ReconnectTabCommand = new AsyncCommand(ReconnectTabAsync, () => CanExecuteCommand() && HasExitedWithError);
|
||||
CopyCommand = new AsyncCommand(Copy, () => HasSelection);
|
||||
PasteCommand = new AsyncCommand(Paste);
|
||||
CopyLinkCommand = new AsyncCommand(() => CopyTextAsync(HoveredUri), () => !string.IsNullOrWhiteSpace(HoveredUri));
|
||||
ShowSearchPanelCommand = new RelayCommand(() => ShowSearchPanel = true, () => !ShowSearchPanel);
|
||||
|
||||
if (!String.IsNullOrEmpty(terminalState))
|
||||
if (!string.IsNullOrEmpty(terminalState))
|
||||
{
|
||||
Restore(terminalState);
|
||||
}
|
||||
else
|
||||
{
|
||||
var defaultTabTheme = TabThemes.FirstOrDefault(t => t.Theme.Id == ShellProfile.TabThemeId);
|
||||
defaultTabTheme.IsSelected = true;
|
||||
}
|
||||
|
||||
Terminal = new Terminal(TrayProcessCommunicationService, _terminalId);
|
||||
Terminal.KeyboardCommandReceived += Terminal_KeyboardCommandReceived;
|
||||
@@ -143,8 +175,20 @@ namespace FluentTerminal.App.ViewModels
|
||||
Terminal.Exited += Terminal_Exited;
|
||||
Terminal.Closed += Terminal_Closed;
|
||||
|
||||
Overlay = new OverlayViewModel(dispatcherTimer);
|
||||
ContextMenu = BuidContextMenu();
|
||||
TabContextMenu = BuildTabContextMenu();
|
||||
}
|
||||
|
||||
public MenuViewModel ContextMenu
|
||||
{
|
||||
get => _contextMenu;
|
||||
set => Set(ref _contextMenu, value);
|
||||
}
|
||||
|
||||
public MenuViewModel TabContextMenu
|
||||
{
|
||||
get => _tabContextMenu;
|
||||
set => Set(ref _tabContextMenu, value);
|
||||
}
|
||||
|
||||
private bool _disposalRequested;
|
||||
@@ -161,12 +205,10 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public event EventHandler Activated;
|
||||
public event EventHandler Closed;
|
||||
public event EventHandler<string> FindNextRequested;
|
||||
public event EventHandler<string> FindPreviousRequested;
|
||||
public event EventHandler<SearchRequest> FindNextRequested;
|
||||
public event EventHandler<SearchRequest> FindPreviousRequested;
|
||||
public event EventHandler SearchStarted;
|
||||
public event EventHandler<TerminalTheme> ThemeChanged;
|
||||
public event EventHandler<string> ShellTitleChanged;
|
||||
public event EventHandler<string> CustomTitleChanged;
|
||||
public event EventHandler CloseLeftTabsRequested;
|
||||
public event EventHandler CloseRightTabsRequested;
|
||||
public event EventHandler CloseOtherTabsRequested;
|
||||
@@ -181,7 +223,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
// The effective background theme depends on whether it is selected (use the theme), or if it is inactive
|
||||
// (if we're set to underline inactive tabs, use the null theme).
|
||||
get => IsSelected || (!IsSelected && ApplicationSettings.InactiveTabColorMode == InactiveTabColorMode.Background) ?
|
||||
_tabTheme :
|
||||
_tabTheme?.Theme :
|
||||
SettingsService.GetTabThemes().FirstOrDefault(t => t.Color == null);
|
||||
}
|
||||
|
||||
@@ -209,6 +251,18 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public RelayCommand DuplicateTabCommand { get; }
|
||||
|
||||
public AsyncCommand ReconnectTabCommand { get; }
|
||||
|
||||
public ICommand CopyLinkCommand { get; private set; }
|
||||
|
||||
public ICommand CopyCommand { get; private set; }
|
||||
|
||||
public ICommand PasteCommand { get; private set; }
|
||||
|
||||
public ICommand ShowSearchPanelCommand { get; private set; }
|
||||
|
||||
public double BackgroundOpacity => _terminalOptions?.BackgroundOpacity ?? 1.0;
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
@@ -218,8 +272,13 @@ namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
if (IsSelected)
|
||||
{
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.Search), () => ShowSearchPanel = !ShowSearchPanel);
|
||||
HasNewOutput = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_keyboardCommandService.DeregisterCommandHandler(nameof(Command.Search));
|
||||
}
|
||||
RaisePropertyChanged(nameof(IsUnderlined));
|
||||
RaisePropertyChanged(nameof(BackgroundTabTheme));
|
||||
RaisePropertyChanged(nameof(ShowCloseButton));
|
||||
@@ -227,6 +286,24 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasSelection
|
||||
{
|
||||
get => _hasSelection;
|
||||
set => Set(ref _hasSelection, value);
|
||||
}
|
||||
|
||||
public string HoveredUri
|
||||
{
|
||||
get => _hoveredUri;
|
||||
set
|
||||
{
|
||||
if (Set(ref _hoveredUri, value))
|
||||
{
|
||||
ContextMenu = BuidContextMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsHovered
|
||||
{
|
||||
get => _isHovered;
|
||||
@@ -245,7 +322,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
|
||||
public bool IsUnderlined => (IsSelected && ApplicationSettings.UnderlineSelectedTab) ||
|
||||
(!IsSelected && ApplicationSettings.InactiveTabColorMode == InactiveTabColorMode.Underlined && TabTheme.Color != null);
|
||||
(!IsSelected && ApplicationSettings.InactiveTabColorMode == InactiveTabColorMode.Underlined && TabTheme.Theme.Color != null);
|
||||
|
||||
public bool HasNewOutput
|
||||
{
|
||||
@@ -262,6 +339,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
HasNewOutput = false;
|
||||
}
|
||||
ReconnectTabCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +349,23 @@ namespace FluentTerminal.App.ViewModels
|
||||
set => Set(ref _searchText, value);
|
||||
}
|
||||
|
||||
public RelayCommand<string> SelectTabThemeCommand { get; }
|
||||
public bool SearchMatchCase
|
||||
{
|
||||
get => _searchMatchCase;
|
||||
set => Set(ref _searchMatchCase, value);
|
||||
}
|
||||
|
||||
public bool SearchWholeWord
|
||||
{
|
||||
get => _searchWholeWord;
|
||||
set => Set(ref _searchWholeWord, value);
|
||||
}
|
||||
|
||||
public bool SearchWithRegex
|
||||
{
|
||||
get => _searchWithRegex;
|
||||
set => Set(ref _searchWithRegex, value);
|
||||
}
|
||||
|
||||
public ISettingsService SettingsService { get; }
|
||||
|
||||
@@ -283,7 +377,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
set => Set(ref _showSearchPanel, value);
|
||||
}
|
||||
|
||||
public TabTheme TabTheme
|
||||
public TabThemeViewModel TabTheme
|
||||
{
|
||||
get => _tabTheme;
|
||||
set
|
||||
@@ -294,11 +388,11 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<TabTheme> TabThemes { get; }
|
||||
public ObservableCollection<TabThemeViewModel> TabThemes { get; }
|
||||
|
||||
public Terminal Terminal { get; private set; }
|
||||
|
||||
public OverlayViewModel Overlay { get; private set; }
|
||||
public OverlayViewModel Overlay { get; set; }
|
||||
|
||||
public TerminalTheme TerminalTheme
|
||||
{
|
||||
@@ -334,10 +428,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
if (Set(ref _tabTitle, title))
|
||||
{
|
||||
CustomTitleChanged?.Invoke(this, title);
|
||||
}
|
||||
Set(ref _tabTitle, title);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,25 +446,32 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public ITrayProcessCommunicationService TrayProcessCommunicationService { get; }
|
||||
|
||||
public Task Close()
|
||||
public Task CloseAsync()
|
||||
{
|
||||
MessengerInstance.Unregister(this);
|
||||
|
||||
return Terminal.Close();
|
||||
return Terminal.CloseAsync();
|
||||
}
|
||||
|
||||
public void CopyText(string text)
|
||||
public Task CopyTextAsync(string text)
|
||||
{
|
||||
ClipboardService.SetText(text);
|
||||
if (_terminalOptions.ShowTextCopied)
|
||||
return ApplicationView.ExecuteOnUiThreadAsync(() =>
|
||||
{
|
||||
Overlay.Show(I18N.Translate("TextCopied"));
|
||||
}
|
||||
ClipboardService.SetText(text);
|
||||
|
||||
if (ApplicationSettings.ShowTextCopied)
|
||||
{
|
||||
Overlay.Show(I18N.Translate("TextCopied"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task EditTitle()
|
||||
// Requires UI thread
|
||||
public async Task EditTitleAsync()
|
||||
{
|
||||
var result = await DialogService.ShowInputDialogAsync(I18N.Translate("EditTitleString"));
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
|
||||
var result = await DialogService.ShowInputDialogAsync(I18N.Translate("EditTitleString"))
|
||||
.ConfigureAwait(true);
|
||||
if (result != null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(result))
|
||||
@@ -411,11 +509,6 @@ namespace FluentTerminal.App.ViewModels
|
||||
return Initialized && !_disposalRequested;
|
||||
}
|
||||
|
||||
private async Task CloseTab()
|
||||
{
|
||||
await TryClose().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void CloseLeftTabs()
|
||||
{
|
||||
CloseLeftTabsRequested?.Invoke(this, EventArgs.Empty);
|
||||
@@ -433,19 +526,19 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private void FindNext()
|
||||
{
|
||||
FindNextRequested?.Invoke(this, SearchText);
|
||||
FindNextRequested?.Invoke(this, new SearchRequest { MatchCase = SearchMatchCase, Regex = SearchWithRegex, Term = SearchText, WholeWord = SearchWholeWord });
|
||||
}
|
||||
|
||||
public ITerminalView TerminalView { get; set; }
|
||||
|
||||
public async Task<string> SerializeXtermState()
|
||||
private Task<string> SerializeXtermStateAsync()
|
||||
{
|
||||
return await TerminalView?.SerializeXtermState();
|
||||
return TerminalView?.SerializeXtermStateAsync() ?? Task.FromResult(string.Empty);
|
||||
}
|
||||
|
||||
private void FindPrevious()
|
||||
{
|
||||
FindPreviousRequested?.Invoke(this, SearchText);
|
||||
FindPreviousRequested?.Invoke(this, new SearchRequest { MatchCase = SearchMatchCase, Regex = SearchWithRegex, Term = SearchText, WholeWord = SearchWholeWord });
|
||||
}
|
||||
|
||||
private void DuplicateTab()
|
||||
@@ -453,38 +546,61 @@ namespace FluentTerminal.App.ViewModels
|
||||
DuplicateTabRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private async void OnApplicationSettingsChanged(ApplicationSettingsChangedMessage message)
|
||||
public async Task ReconnectTabAsync()
|
||||
{
|
||||
await ApplicationView.RunOnDispatcherThread(() =>
|
||||
HasExitedWithError = false;
|
||||
await TerminalView?.ReconnectAsync();
|
||||
}
|
||||
|
||||
private void OnApplicationSettingsChanged(ApplicationSettingsChangedMessage message)
|
||||
{
|
||||
ApplicationSettings = message.ApplicationSettings;
|
||||
|
||||
ApplicationView.ExecuteOnUiThreadAsync(() =>
|
||||
{
|
||||
ApplicationSettings = message.ApplicationSettings;
|
||||
RaisePropertyChanged(nameof(IsUnderlined));
|
||||
RaisePropertyChanged(nameof(BackgroundTabTheme));
|
||||
});
|
||||
}
|
||||
|
||||
private async void OnCurrentThemeChanged(CurrentThemeChangedMessage message)
|
||||
private void OnCurrentThemeChanged(CurrentThemeChangedMessage message)
|
||||
{
|
||||
await ApplicationView.RunOnDispatcherThread(() =>
|
||||
// only change theme if not overwritten by profile
|
||||
if (ShellProfile.TerminalThemeId == Guid.Empty)
|
||||
{
|
||||
// only change theme if not overwritten by profile
|
||||
if (ShellProfile.TerminalThemeId == Guid.Empty)
|
||||
var currentTheme = SettingsService.GetTheme(message.ThemeId);
|
||||
|
||||
ApplicationView.ExecuteOnUiThreadAsync(() =>
|
||||
{
|
||||
var currentTheme = SettingsService.GetTheme(message.ThemeId);
|
||||
TerminalTheme = currentTheme;
|
||||
ThemeChanged?.Invoke(this, currentTheme);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTerminalOptionsChanged(TerminalOptionsChangedMessage message)
|
||||
{
|
||||
_terminalOptions = message.TerminalOptions;
|
||||
ApplicationView.ExecuteOnUiThreadAsync(() => RaisePropertyChanged(nameof(BackgroundOpacity)), CoreDispatcherPriority.Low);
|
||||
}
|
||||
|
||||
private void SelectTabTheme(string id)
|
||||
private void OnKeyBindingsChanged(KeyBindingsChangedMessage message)
|
||||
{
|
||||
TabTheme = TabThemes.FirstOrDefault(t => t.Id == int.Parse(id));
|
||||
ApplicationView.ExecuteOnUiThreadAsync(() =>
|
||||
{
|
||||
var contextMenu = BuidContextMenu();
|
||||
if (!contextMenu.EquivalentTo(ContextMenu))
|
||||
{
|
||||
ContextMenu = contextMenu;
|
||||
}
|
||||
|
||||
var tabContextMenu = BuildTabContextMenu();
|
||||
if (!tabContextMenu.EquivalentTo(TabContextMenu))
|
||||
{
|
||||
TabContextMenu = tabContextMenu;
|
||||
}
|
||||
|
||||
}, CoreDispatcherPriority.Low, true);
|
||||
}
|
||||
|
||||
private void Terminal_Exited(object sender, int exitCode)
|
||||
@@ -494,13 +610,12 @@ namespace FluentTerminal.App.ViewModels
|
||||
tracker.SetInvalid();
|
||||
}
|
||||
|
||||
ApplicationView.RunOnDispatcherThread(() => HasExitedWithError = exitCode > 0);
|
||||
ApplicationView.ExecuteOnUiThreadAsync(() => HasExitedWithError = exitCode > 0);
|
||||
}
|
||||
|
||||
private void Terminal_Closed(object sender, EventArgs e)
|
||||
{
|
||||
ApplicationView.RunOnDispatcherThread(() => Closed?.Invoke(this, EventArgs.Empty));
|
||||
|
||||
Closed?.Invoke(this, EventArgs.Empty);
|
||||
Terminal.KeyboardCommandReceived -= Terminal_KeyboardCommandReceived;
|
||||
Terminal.OutputReceived -= Terminal_OutputReceived;
|
||||
Terminal.SizeChanged -= Terminal_SizeChanged;
|
||||
@@ -515,43 +630,41 @@ namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
case nameof(Command.Copy):
|
||||
{
|
||||
var selection = await Terminal.GetSelectedText().ConfigureAwait(true);
|
||||
CopyText(selection);
|
||||
break;
|
||||
await Copy().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
case nameof(Command.Paste):
|
||||
{
|
||||
string content = await ClipboardService.GetText().ConfigureAwait(true);
|
||||
if (content != null)
|
||||
{
|
||||
content = ShellProfile.TranslateLineEndings(content);
|
||||
await Terminal.Write(Encoding.UTF8.GetBytes(content)).ConfigureAwait(true);
|
||||
}
|
||||
break;
|
||||
await Paste().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
case nameof(Command.PasteWithoutNewlines):
|
||||
{
|
||||
string content = await ClipboardService.GetText().ConfigureAwait(true);
|
||||
string content = await ClipboardService.GetTextAsync().ConfigureAwait(false);
|
||||
if (content != null)
|
||||
{
|
||||
content = ShellProfile.NewlinePattern.Replace(content, string.Empty);
|
||||
await Terminal.Write(Encoding.UTF8.GetBytes(content)).ConfigureAwait(true);
|
||||
TerminalView.Paste(content);
|
||||
}
|
||||
break;
|
||||
return;
|
||||
}
|
||||
case nameof(Command.Search):
|
||||
{
|
||||
ShowSearchPanel = !ShowSearchPanel;
|
||||
if (ShowSearchPanel)
|
||||
await ApplicationView.ExecuteOnUiThreadAsync(() =>
|
||||
{
|
||||
SearchStarted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
break;
|
||||
ShowSearchPanel = !ShowSearchPanel;
|
||||
if (ShowSearchPanel)
|
||||
{
|
||||
SearchStarted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
_keyboardCommandService.SendCommand(e);
|
||||
break;
|
||||
await ApplicationView.ExecuteOnUiThreadAsync(() => _keyboardCommandService.SendCommand(e))
|
||||
.ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -565,39 +678,152 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
if (!IsSelected && ApplicationSettings.ShowNewOutputIndicator)
|
||||
{
|
||||
ApplicationView.RunOnDispatcherThread(() => HasNewOutput = true);
|
||||
ApplicationView.ExecuteOnUiThreadAsync(() => HasNewOutput = true);
|
||||
}
|
||||
}
|
||||
|
||||
private void Terminal_SizeChanged(object sender, TerminalSize e)
|
||||
{
|
||||
Overlay.Show($"{e.Columns} x {e.Rows}");
|
||||
ApplicationView.ExecuteOnUiThreadAsync(() => Overlay.Show($"{e.Columns} x {e.Rows}"));
|
||||
}
|
||||
|
||||
private void Terminal_TitleChanged(object sender, string e)
|
||||
{
|
||||
ShellTitle = e;
|
||||
ShellTitleChanged?.Invoke(this, e);
|
||||
ApplicationView.ExecuteOnUiThreadAsync(() => ShellTitle = e);
|
||||
}
|
||||
|
||||
private async Task TryClose()
|
||||
private async Task TryCloseAsync()
|
||||
{
|
||||
if (ShellProfile?.Tag is ISessionSuccessTracker tracker)
|
||||
{
|
||||
tracker.SetInvalid();
|
||||
}
|
||||
|
||||
if (ApplicationSettings.ConfirmClosingTabs)
|
||||
if (!ApplicationSettings.ConfirmClosingTabs || await DialogService
|
||||
.ShowMessageDialogAsync(I18N.Translate("PleaseConfirm"),
|
||||
string.Format(I18N.Translate("ConfirmCloseTab"), ShellTitle), DialogButton.OK,
|
||||
DialogButton.Cancel).ConfigureAwait(false) == DialogButton.OK)
|
||||
{
|
||||
var result = await DialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), String.Format(I18N.Translate("ConfirmCloseTab"), ShellTitle), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
await CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (result == DialogButton.Cancel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
private async Task Copy()
|
||||
{
|
||||
var selection = await Terminal.GetSelectedText().ConfigureAwait(false);
|
||||
await CopyTextAsync(selection).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task Paste()
|
||||
{
|
||||
var content = await ClipboardService.GetTextAsync().ConfigureAwait(false);
|
||||
if (content != null)
|
||||
{
|
||||
TerminalView.Paste(content);
|
||||
}
|
||||
}
|
||||
|
||||
private MenuViewModel BuidContextMenu()
|
||||
{
|
||||
var commandKeyBindings = SettingsService.GetCommandKeyBindings();
|
||||
var contextMenu = new MenuViewModel();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(HoveredUri))
|
||||
{
|
||||
var copyLink = new MenuItemViewModel(I18N.Translate("CopyLink"), CopyLinkCommand, icon: Mdl2Icon.Link());
|
||||
contextMenu.Items.Add(copyLink);
|
||||
contextMenu.Items.Add(new SeparatorMenuItemViewModel());
|
||||
}
|
||||
|
||||
await Close().ConfigureAwait(true);
|
||||
var copy = new MenuItemViewModel(I18N.Translate("Command.Copy"), CopyCommand, icon: Mdl2Icon.Copy());
|
||||
AddKeyBindings(copy, Command.Copy, commandKeyBindings);
|
||||
contextMenu.Items.Add(copy);
|
||||
|
||||
var paste = new MenuItemViewModel(I18N.Translate("Command.Paste"), PasteCommand, icon: Mdl2Icon.Paste());
|
||||
AddKeyBindings(paste, Command.Paste, commandKeyBindings);
|
||||
contextMenu.Items.Add(paste);
|
||||
|
||||
contextMenu.Items.Add(new SeparatorMenuItemViewModel());
|
||||
|
||||
var editTitle = new MenuItemViewModel(I18N.Translate("EditTitle.Text"), EditTitleCommand, icon: Mdl2Icon.Edit());
|
||||
AddKeyBindings(editTitle, Command.ChangeTabTitle, commandKeyBindings);
|
||||
contextMenu.Items.Add(editTitle);
|
||||
|
||||
var search = new MenuItemViewModel(I18N.Translate("Search.Text"), ShowSearchPanelCommand, icon: Mdl2Icon.Search());
|
||||
AddKeyBindings(search, Command.Search, commandKeyBindings);
|
||||
contextMenu.Items.Add(search);
|
||||
|
||||
contextMenu.Items.Add(new SeparatorMenuItemViewModel());
|
||||
|
||||
var duplicate = new MenuItemViewModel(I18N.Translate("Command.DuplicateTab"), DuplicateTabCommand);
|
||||
AddKeyBindings(duplicate, Command.DuplicateTab, commandKeyBindings);
|
||||
contextMenu.Items.Add(duplicate);
|
||||
|
||||
contextMenu.Items.Add(new SeparatorMenuItemViewModel());
|
||||
|
||||
var close = new MenuItemViewModel(I18N.Translate("Close"), CloseCommand, icon: Mdl2Icon.Cancel());
|
||||
AddKeyBindings(close, Command.CloseTab, commandKeyBindings);
|
||||
contextMenu.Items.Add(close);
|
||||
|
||||
return contextMenu;
|
||||
}
|
||||
|
||||
private MenuViewModel BuildTabContextMenu()
|
||||
{
|
||||
var commandKeyBindings = SettingsService.GetCommandKeyBindings();
|
||||
var contextMenu = new MenuViewModel();
|
||||
|
||||
RadioMenuItemViewModel createTabColorItem(TabThemeViewModel tabTheme)
|
||||
{
|
||||
//todo localise name
|
||||
var icon = tabTheme.Theme.Id == 0 ? Mdl2Icon.PaginationDotOutline10() : Mdl2Icon.PaginationDotSolid10(tabTheme.Theme.Color);
|
||||
return new RadioMenuItemViewModel(I18N.Translate($"TabTheme.{tabTheme.Theme.Name}"), $"{Terminal.Id}_tabTheme", tabTheme, bindingPath: nameof(TabThemeViewModel.IsSelected) , icon: icon);
|
||||
}
|
||||
|
||||
var tabColor = new ExpandableMenuItemViewModel(I18N.Translate("Color.Text"));
|
||||
|
||||
foreach (var tabTheme in TabThemes)
|
||||
{
|
||||
tabColor.SubItems.Add(createTabColorItem(tabTheme));
|
||||
}
|
||||
|
||||
contextMenu.Items.Add(tabColor);
|
||||
|
||||
var editTitle = new MenuItemViewModel(I18N.Translate("EditTitle.Text"), EditTitleCommand, icon: Mdl2Icon.Edit());
|
||||
AddKeyBindings(editTitle, Command.ChangeTabTitle, commandKeyBindings);
|
||||
contextMenu.Items.Add(editTitle);
|
||||
|
||||
contextMenu.Items.Add(new SeparatorMenuItemViewModel());
|
||||
|
||||
var duplicate = new MenuItemViewModel(I18N.Translate("Command.DuplicateTab"), DuplicateTabCommand);
|
||||
AddKeyBindings(duplicate, Command.DuplicateTab, commandKeyBindings);
|
||||
contextMenu.Items.Add(duplicate);
|
||||
|
||||
contextMenu.Items.Add(new SeparatorMenuItemViewModel());
|
||||
|
||||
var closeLeft = new MenuItemViewModel(I18N.Translate("CloseLeft.Text"), CloseLeftTabsCommand);
|
||||
contextMenu.Items.Add(closeLeft);
|
||||
|
||||
var closeRight = new MenuItemViewModel(I18N.Translate("CloseRight.Text"), CloseRightTabsCommand);
|
||||
contextMenu.Items.Add(closeRight);
|
||||
|
||||
var closeOther = new MenuItemViewModel(I18N.Translate("CloseOther.Text"), CloseOtherTabsCommand);
|
||||
contextMenu.Items.Add(closeOther);
|
||||
|
||||
var close = new MenuItemViewModel(I18N.Translate("Close"), CloseCommand, icon: Mdl2Icon.Cancel());
|
||||
AddKeyBindings(close, Command.CloseTab, commandKeyBindings);
|
||||
contextMenu.Items.Add(close);
|
||||
|
||||
return contextMenu;
|
||||
}
|
||||
|
||||
private void AddKeyBindings(MenuItemViewModel menuItem, Command command, IDictionary<string, ICollection<KeyBinding>> commandKeyBindings)
|
||||
{
|
||||
var keyBindings = commandKeyBindings[command.ToString()];
|
||||
if (keyBindings?.Any() == true)
|
||||
{
|
||||
menuItem.KeyBinding = new MenuItemKeyBindingViewModel(keyBindings.First());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.App.ViewModels.Infrastructure;
|
||||
using FluentTerminal.Models;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
@@ -41,16 +43,25 @@ namespace FluentTerminal.App.ViewModels
|
||||
private string _selection;
|
||||
private string _white;
|
||||
private string _yellow;
|
||||
private ImageFile _backgroundThemeFile;
|
||||
private readonly IFileSystemService _fileSystemService;
|
||||
private readonly IImageFileSystemService _imageFileSystemService;
|
||||
|
||||
public event EventHandler<string> BackgroundChanged;
|
||||
public event EventHandler<ImageFile> BackgroundImageChanged;
|
||||
|
||||
public ThemeViewModel(TerminalTheme theme, ISettingsService settingsService, IDialogService dialogService, IFileSystemService fileSystemService, bool isNew)
|
||||
public ThemeViewModel(TerminalTheme theme,
|
||||
ISettingsService settingsService,
|
||||
IDialogService dialogService,
|
||||
IFileSystemService fileSystemService,
|
||||
IImageFileSystemService imageFileSystemService,
|
||||
bool isNew)
|
||||
{
|
||||
Model = theme;
|
||||
_settingsService = settingsService;
|
||||
_dialogService = dialogService;
|
||||
_fileSystemService = fileSystemService;
|
||||
_imageFileSystemService = imageFileSystemService;
|
||||
_isNew = isNew;
|
||||
|
||||
Name = Model.Name;
|
||||
@@ -81,12 +92,16 @@ namespace FluentTerminal.App.ViewModels
|
||||
CursorAccent = Model.Colors.CursorAccent;
|
||||
Selection = Model.Colors.Selection;
|
||||
|
||||
BackgroundThemeFile = Model.BackgroundImage;
|
||||
|
||||
SetActiveCommand = new RelayCommand(SetActive);
|
||||
DeleteCommand = new RelayCommand(async () => await Delete().ConfigureAwait(false), NotPreInstalled);
|
||||
DeleteCommand = new AsyncCommand(DeleteAsync, NotPreInstalled);
|
||||
EditCommand = new RelayCommand(Edit, NotPreInstalled);
|
||||
CancelEditCommand = new RelayCommand(async () => await CancelEdit().ConfigureAwait(false));
|
||||
SaveChangesCommand = new RelayCommand(SaveChanges);
|
||||
ExportCommand = new RelayCommand(async () => await Export().ConfigureAwait(false), NotPreInstalled);
|
||||
CancelEditCommand = new AsyncCommand(CancelEditAsync);
|
||||
SaveChangesCommand = new AsyncCommand(SaveChangesAsync);
|
||||
ExportCommand = new AsyncCommand(Export, NotPreInstalled);
|
||||
ChooseBackgroundImageCommand = new AsyncCommand(ChooseBackgroundImageAsync, NotPreInstalled);
|
||||
DeleteBackgroundImageCommand = new AsyncCommand(DeleteBackgroundImageAsync, NotPreInstalled);
|
||||
}
|
||||
|
||||
public event EventHandler Activated;
|
||||
@@ -177,7 +192,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
set => Set(ref _brightYellow, value);
|
||||
}
|
||||
|
||||
public RelayCommand CancelEditCommand { get; }
|
||||
public ICommand CancelEditCommand { get; }
|
||||
|
||||
public string Cursor
|
||||
{
|
||||
@@ -197,9 +212,9 @@ namespace FluentTerminal.App.ViewModels
|
||||
set => Set(ref _cyan, value);
|
||||
}
|
||||
|
||||
public RelayCommand DeleteCommand { get; }
|
||||
public RelayCommand EditCommand { get; }
|
||||
public RelayCommand ExportCommand { get; }
|
||||
public ICommand DeleteCommand { get; }
|
||||
public ICommand EditCommand { get; }
|
||||
public ICommand ExportCommand { get; }
|
||||
|
||||
public string Foreground
|
||||
{
|
||||
@@ -245,7 +260,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
set => Set(ref _red, value);
|
||||
}
|
||||
|
||||
public RelayCommand SaveChangesCommand { get; }
|
||||
public ICommand SaveChangesCommand { get; }
|
||||
|
||||
public string Selection
|
||||
{
|
||||
@@ -253,7 +268,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
set => Set(ref _selection, value);
|
||||
}
|
||||
|
||||
public RelayCommand SetActiveCommand { get; }
|
||||
public ICommand SetActiveCommand { get; }
|
||||
|
||||
public string White
|
||||
{
|
||||
@@ -267,7 +282,22 @@ namespace FluentTerminal.App.ViewModels
|
||||
set => Set(ref _yellow, value);
|
||||
}
|
||||
|
||||
public void SaveChanges()
|
||||
public ImageFile BackgroundThemeFile
|
||||
{
|
||||
get => _backgroundThemeFile;
|
||||
set
|
||||
{
|
||||
Set(ref _backgroundThemeFile, value);
|
||||
BackgroundImageChanged?.Invoke(this, value);
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand ChooseBackgroundImageCommand { get; }
|
||||
|
||||
public ICommand DeleteBackgroundImageCommand { get; }
|
||||
|
||||
// Requires UI thread
|
||||
private async Task SaveChangesAsync()
|
||||
{
|
||||
Model.Name = Name;
|
||||
Model.Author = Author;
|
||||
@@ -296,92 +326,114 @@ namespace FluentTerminal.App.ViewModels
|
||||
Model.Colors.CursorAccent = CursorAccent;
|
||||
Model.Colors.Selection = Selection;
|
||||
|
||||
if (Model.BackgroundImage != null &&
|
||||
BackgroundThemeFile != Model?.BackgroundImage)
|
||||
{
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
|
||||
await _imageFileSystemService
|
||||
.RemoveImportedImageAsync($"{Model.BackgroundImage?.Name}{Model.BackgroundImage?.FileType}")
|
||||
.ConfigureAwait(true);
|
||||
}
|
||||
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
|
||||
BackgroundThemeFile = await SaveBackgroundImageAsync().ConfigureAwait(true);
|
||||
|
||||
Model.BackgroundImage = BackgroundThemeFile;
|
||||
|
||||
_settingsService.SaveTheme(Model);
|
||||
|
||||
InEditMode = false;
|
||||
_isNew = false;
|
||||
}
|
||||
|
||||
private async Task CancelEdit()
|
||||
// Requires UI thread
|
||||
private async Task CancelEditAsync()
|
||||
{
|
||||
if (_isNew)
|
||||
{
|
||||
await Delete();
|
||||
await DeleteAsync().ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
TerminalTheme changedTheme = new TerminalTheme()
|
||||
{
|
||||
Name = Name,
|
||||
Author = Author,
|
||||
BackgroundImage = BackgroundThemeFile,
|
||||
Colors = new TerminalColors()
|
||||
{
|
||||
Black = Black,
|
||||
Red = Red,
|
||||
Green = Green,
|
||||
Yellow = Yellow,
|
||||
Blue = Blue,
|
||||
Magenta = Magenta,
|
||||
Cyan = Cyan,
|
||||
White = White,
|
||||
|
||||
BrightBlack = BrightBlack,
|
||||
BrightRed = BrightRed,
|
||||
BrightGreen = BrightGreen,
|
||||
BrightYellow = BrightYellow,
|
||||
BrightBlue = BrightBlue,
|
||||
BrightMagenta = BrightMagenta,
|
||||
BrightCyan = BrightCyan,
|
||||
BrightWhite = BrightWhite,
|
||||
|
||||
Background = Background,
|
||||
Foreground = Foreground,
|
||||
Cursor = Cursor,
|
||||
CursorAccent = CursorAccent,
|
||||
Selection = Selection
|
||||
}
|
||||
};
|
||||
|
||||
if (!_fallbackTheme.Equals(changedTheme))
|
||||
{
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
|
||||
var result = await _dialogService.ShowMessageDialogAsync(I18N.Translate("PleaseConfirm"),
|
||||
I18N.Translate("ConfirmDiscardChanges"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
Black = _fallbackTheme.Colors.Black;
|
||||
Red = _fallbackTheme.Colors.Red;
|
||||
Green = _fallbackTheme.Colors.Green;
|
||||
Yellow = _fallbackTheme.Colors.Yellow;
|
||||
Blue = _fallbackTheme.Colors.Blue;
|
||||
Magenta = _fallbackTheme.Colors.Magenta;
|
||||
Cyan = _fallbackTheme.Colors.Cyan;
|
||||
White = _fallbackTheme.Colors.White;
|
||||
|
||||
BrightBlack = _fallbackTheme.Colors.BrightBlack;
|
||||
BrightRed = _fallbackTheme.Colors.BrightRed;
|
||||
BrightGreen = _fallbackTheme.Colors.BrightGreen;
|
||||
BrightYellow = _fallbackTheme.Colors.BrightYellow;
|
||||
BrightBlue = _fallbackTheme.Colors.BrightBlue;
|
||||
BrightMagenta = _fallbackTheme.Colors.BrightMagenta;
|
||||
BrightCyan = _fallbackTheme.Colors.BrightCyan;
|
||||
BrightWhite = _fallbackTheme.Colors.BrightWhite;
|
||||
|
||||
Background = _fallbackTheme.Colors.Background;
|
||||
Foreground = _fallbackTheme.Colors.Foreground;
|
||||
Cursor = _fallbackTheme.Colors.Cursor;
|
||||
CursorAccent = _fallbackTheme.Colors.CursorAccent;
|
||||
Selection = _fallbackTheme.Colors.Selection;
|
||||
|
||||
Name = _fallbackTheme.Name;
|
||||
Author = _fallbackTheme.Author;
|
||||
|
||||
BackgroundThemeFile = _fallbackTheme.BackgroundImage;
|
||||
|
||||
InEditMode = false;
|
||||
|
||||
await _imageFileSystemService.RemoveTemporaryBackgroundThemeImageAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TerminalTheme changedTheme = new TerminalTheme()
|
||||
{
|
||||
Name = Name,
|
||||
Author = Author,
|
||||
Colors = new TerminalColors()
|
||||
{
|
||||
Black = Black,
|
||||
Red = Red,
|
||||
Green = Green,
|
||||
Yellow = Yellow,
|
||||
Blue = Blue,
|
||||
Magenta = Magenta,
|
||||
Cyan = Cyan,
|
||||
White = White,
|
||||
|
||||
BrightBlack = BrightBlack,
|
||||
BrightRed = BrightRed,
|
||||
BrightGreen = BrightGreen,
|
||||
BrightYellow = BrightYellow,
|
||||
BrightBlue = BrightBlue,
|
||||
BrightMagenta = BrightMagenta,
|
||||
BrightCyan = BrightCyan,
|
||||
BrightWhite = BrightWhite,
|
||||
|
||||
Background = Background,
|
||||
Foreground = Foreground,
|
||||
Cursor = Cursor,
|
||||
CursorAccent = CursorAccent,
|
||||
Selection = Selection
|
||||
}
|
||||
};
|
||||
|
||||
if (!_fallbackTheme.Equals(changedTheme))
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmDiscardChanges"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
Black = _fallbackTheme.Colors.Black;
|
||||
Red = _fallbackTheme.Colors.Red;
|
||||
Green = _fallbackTheme.Colors.Green;
|
||||
Yellow = _fallbackTheme.Colors.Yellow;
|
||||
Blue = _fallbackTheme.Colors.Blue;
|
||||
Magenta = _fallbackTheme.Colors.Magenta;
|
||||
Cyan = _fallbackTheme.Colors.Cyan;
|
||||
White = _fallbackTheme.Colors.White;
|
||||
|
||||
BrightBlack = _fallbackTheme.Colors.BrightBlack;
|
||||
BrightRed = _fallbackTheme.Colors.BrightRed;
|
||||
BrightGreen = _fallbackTheme.Colors.BrightGreen;
|
||||
BrightYellow = _fallbackTheme.Colors.BrightYellow;
|
||||
BrightBlue = _fallbackTheme.Colors.BrightBlue;
|
||||
BrightMagenta = _fallbackTheme.Colors.BrightMagenta;
|
||||
BrightCyan = _fallbackTheme.Colors.BrightCyan;
|
||||
BrightWhite = _fallbackTheme.Colors.BrightWhite;
|
||||
|
||||
Background = _fallbackTheme.Colors.Background;
|
||||
Foreground = _fallbackTheme.Colors.Foreground;
|
||||
Cursor = _fallbackTheme.Colors.Cursor;
|
||||
CursorAccent = _fallbackTheme.Colors.CursorAccent;
|
||||
Selection = _fallbackTheme.Colors.Selection;
|
||||
|
||||
Name = _fallbackTheme.Name;
|
||||
Author = _fallbackTheme.Author;
|
||||
|
||||
InEditMode = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
InEditMode = false;
|
||||
}
|
||||
InEditMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,16 +442,38 @@ namespace FluentTerminal.App.ViewModels
|
||||
return !Model.PreInstalled;
|
||||
}
|
||||
|
||||
private async Task Delete()
|
||||
// Requires UI thread
|
||||
private async Task DeleteAsync()
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmDeleteTheme"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
// ConfigureAwait(true) because we need to trigger Deleted event in the calling (UI) thread.
|
||||
var result = await _dialogService.ShowMessageDialogAsync(I18N.Translate("PleaseConfirm"),
|
||||
I18N.Translate("ConfirmDeleteTheme"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
// ConfigureAwait(true) because we need to trigger Deleted event in the calling (UI) thread.
|
||||
await DeleteBackgroundImageIfExistsAsync().ConfigureAwait(true);
|
||||
// ConfigureAwait(true) because we need to trigger Deleted event in the calling (UI) thread.
|
||||
await _imageFileSystemService.RemoveTemporaryBackgroundThemeImageAsync().ConfigureAwait(true);
|
||||
|
||||
Deleted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
// Requires UI thread
|
||||
private async Task DeleteBackgroundImageAsync()
|
||||
{
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
|
||||
var result = await _dialogService.ShowMessageDialogAsync(I18N.Translate("PleaseConfirm"),
|
||||
I18N.Translate("ConfirmDeleteBackgroundImage"), DialogButton.OK, DialogButton.Cancel)
|
||||
.ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
BackgroundThemeFile = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void Edit()
|
||||
{
|
||||
_fallbackTheme = new TerminalTheme(Model);
|
||||
@@ -413,8 +487,60 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private Task Export()
|
||||
{
|
||||
var content = JsonConvert.SerializeObject(Model, Formatting.Indented, new JsonSerializerSettings { ContractResolver = new TerminalThemeContractResolver() });
|
||||
return _fileSystemService.SaveTextFile(Name, "Fluent Terminal Theme", ".flutecolors", content);
|
||||
var encodedImage = _imageFileSystemService.EncodeImage(BackgroundThemeFile);
|
||||
var exportedTheme = new ExportedTerminalTheme(Model, encodedImage);
|
||||
var content = JsonConvert.SerializeObject(exportedTheme, Formatting.Indented, new JsonSerializerSettings { ContractResolver = new TerminalThemeContractResolver() });
|
||||
return _fileSystemService.SaveTextFileAsync(Name, "Fluent Terminal Theme", ".flutecolors", content);
|
||||
}
|
||||
|
||||
private async Task<ImageFile> SaveBackgroundImageAsync()
|
||||
{
|
||||
if (BackgroundThemeFile == null)
|
||||
{
|
||||
return BackgroundThemeFile;
|
||||
}
|
||||
|
||||
if (BackgroundThemeFile == Model.BackgroundImage)
|
||||
{
|
||||
return BackgroundThemeFile;
|
||||
}
|
||||
|
||||
var importedBackgroundThemeFile =
|
||||
await _fileSystemService.SaveImageInRoamingAsync(BackgroundThemeFile).ConfigureAwait(false);
|
||||
|
||||
await _imageFileSystemService.RemoveTemporaryBackgroundThemeImageAsync().ConfigureAwait(false);
|
||||
|
||||
return importedBackgroundThemeFile;
|
||||
}
|
||||
|
||||
// Requires UI thread
|
||||
private async Task ChooseBackgroundImageAsync()
|
||||
{
|
||||
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
|
||||
var chosenImage = await _imageFileSystemService
|
||||
.ImportTemporaryImageFileAsync(new[] {".jpeg", ".png", ".jpg"}).ConfigureAwait(true);
|
||||
|
||||
if(chosenImage != null)
|
||||
{
|
||||
BackgroundThemeFile = chosenImage;
|
||||
}
|
||||
}
|
||||
|
||||
// Requires UI thread
|
||||
private async Task DeleteBackgroundImageIfExistsAsync()
|
||||
{
|
||||
if (BackgroundThemeFile != null)
|
||||
{
|
||||
var imageFile = Model.BackgroundImage;
|
||||
|
||||
BackgroundThemeFile = null;
|
||||
|
||||
if (imageFile != null)
|
||||
{
|
||||
await _imageFileSystemService.RemoveImportedImageAsync($"{imageFile.Name}{imageFile.FileType}")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Microsoft.Xaml.Interactivity;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace FluentTerminal.App.Actions
|
||||
{
|
||||
public class FocusAction : DependencyObject, IAction
|
||||
{
|
||||
public object Execute(object sender, object parameter)
|
||||
{
|
||||
var control = TargetObject ?? sender as Control;
|
||||
control?.Focus(FocusState.Programmatic);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Control TargetObject
|
||||
{
|
||||
get { return (Control)GetValue(TargetObjectProperty); }
|
||||
set { SetValue(TargetObjectProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TargetObjectProperty =
|
||||
DependencyProperty.Register(nameof(TargetObject), typeof(Control), typeof(FocusAction), new PropertyMetadata(null));
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel.AppService;
|
||||
using Windows.Foundation.Collections;
|
||||
@@ -27,7 +25,7 @@ namespace FluentTerminal.App.Adapters
|
||||
|
||||
public async Task<ValueSet> SendMessageAsync(ValueSet message)
|
||||
{
|
||||
var response = await _appServiceConnection.SendMessageAsync(message).AsTask();
|
||||
var response = await _appServiceConnection.SendMessageAsync(message);
|
||||
|
||||
if (response.Status == AppServiceResponseStatus.Success)
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ using Windows.Foundation.Metadata;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Core.Preview;
|
||||
using Windows.UI.ViewManagement;
|
||||
using FluentTerminal.App.Utilities;
|
||||
|
||||
namespace FluentTerminal.App.Adapters
|
||||
{
|
||||
@@ -24,7 +25,7 @@ namespace FluentTerminal.App.Adapters
|
||||
_applicationView = ApplicationView.GetForCurrentView();
|
||||
_applicationView.Consolidated += _applicationView_Consolidated;
|
||||
_dispatcher = CoreApplication.GetCurrentView().Dispatcher;
|
||||
SystemNavigationManagerPreview.GetForCurrentView().CloseRequested += OnCloseRequest;
|
||||
SystemNavigationManagerPreview.GetForCurrentView().CloseRequested += OnCloseRequested;
|
||||
|
||||
Logger.Instance.Debug("Created ApplicationViewAdapter for ApplicationView with Id: {Id}", _applicationView.Id);
|
||||
}
|
||||
@@ -48,27 +49,21 @@ namespace FluentTerminal.App.Adapters
|
||||
return ApiInformation.IsApiContractPresent(api, version);
|
||||
}
|
||||
|
||||
public Task RunOnDispatcherThread(Action action, bool enforceNewSchedule = true)
|
||||
{
|
||||
if (!enforceNewSchedule && _dispatcher.HasThreadAccess)
|
||||
{
|
||||
action();
|
||||
public Task ExecuteOnUiThreadAsync(Action action, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal,
|
||||
bool enforceNewSchedule = false) => _dispatcher.ExecuteAsync(action, priority, enforceNewSchedule);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public Task<T> ExecuteOnUiThreadAsync<T>(Func<T> func, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal,
|
||||
bool enforceNewSchedule = false) => _dispatcher.ExecuteAsync(func, priority, enforceNewSchedule);
|
||||
|
||||
return _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => action()).AsTask();
|
||||
}
|
||||
|
||||
public async Task<bool> TryClose()
|
||||
public async Task<bool> TryCloseAsync()
|
||||
{
|
||||
if (_closed)
|
||||
{
|
||||
Logger.Instance.Debug("ApplicationViewAdapter.TryClose was called, but was already closed. ApplicationView.Id: {Id}", _applicationView.Id);
|
||||
Logger.Instance.Debug("ApplicationViewAdapter.TryCloseAsync was called, but was already closed. ApplicationView.Id: {Id}", _applicationView.Id);
|
||||
return true;
|
||||
}
|
||||
Logger.Instance.Debug("TryClose ApplicationView with Id: {Id}", _applicationView.Id);
|
||||
_closed = await _applicationView.TryConsolidateAsync().AsTask();
|
||||
Logger.Instance.Debug("TryCloseAsync ApplicationView with Id: {Id}", _applicationView.Id);
|
||||
_closed = await _applicationView.TryConsolidateAsync();
|
||||
return _closed;
|
||||
}
|
||||
|
||||
@@ -79,28 +74,27 @@ namespace FluentTerminal.App.Adapters
|
||||
_applicationView.ExitFullScreenMode();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _applicationView.TryEnterFullScreenMode();
|
||||
}
|
||||
|
||||
return _applicationView.TryEnterFullScreenMode();
|
||||
}
|
||||
|
||||
private async void OnCloseRequest(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
|
||||
private async void OnCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
|
||||
{
|
||||
var deferral = e.GetDeferral();
|
||||
|
||||
var args = new CancelableEventArgs();
|
||||
|
||||
if (CloseRequested != null)
|
||||
if (CloseRequested is { } closeRequestedHandler)
|
||||
{
|
||||
await CloseRequested.Invoke(this, args);
|
||||
// ConfigureAwait(true) because SystemNavigationManagerPreview.GetForCurrentView().CloseRequested requires the calling (UI) thread
|
||||
await closeRequestedHandler(this, args).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
e.Handled = args.Cancelled;
|
||||
|
||||
if (!e.Handled)
|
||||
{
|
||||
SystemNavigationManagerPreview.GetForCurrentView().CloseRequested -= OnCloseRequest;
|
||||
SystemNavigationManagerPreview.GetForCurrentView().CloseRequested -= OnCloseRequested;
|
||||
}
|
||||
|
||||
deferral.Complete();
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace FluentTerminal.App.Adapters
|
||||
{
|
||||
public class DispatcherTimerAdapter : IDispatcherTimer
|
||||
{
|
||||
private readonly DispatcherTimer _dispatcherTimer;
|
||||
|
||||
public DispatcherTimerAdapter()
|
||||
{
|
||||
_dispatcherTimer = new DispatcherTimer();
|
||||
}
|
||||
|
||||
private void OnDispatcherTimerTick(object sender, object e)
|
||||
{
|
||||
Tick?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public TimeSpan Interval
|
||||
{
|
||||
get => _dispatcherTimer.Interval;
|
||||
set => _dispatcherTimer.Interval = value;
|
||||
}
|
||||
|
||||
public bool IsEnabled => _dispatcherTimer.IsEnabled;
|
||||
|
||||
public event EventHandler<object> Tick;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_dispatcherTimer.Tick += OnDispatcherTimerTick;
|
||||
_dispatcherTimer.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_dispatcherTimer.Stop();
|
||||
_dispatcherTimer.Tick -= OnDispatcherTimerTick;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,11 +38,10 @@ namespace FluentTerminal.App.Adapters
|
||||
_messageDialog.Commands.Add(command);
|
||||
}
|
||||
|
||||
public async Task<DialogButton> ShowAsync()
|
||||
public Task<DialogButton> ShowAsync()
|
||||
{
|
||||
var result = await _messageDialog.ShowAsync();
|
||||
|
||||
return (DialogButton)result.Id;
|
||||
return _messageDialog.ShowAsync().AsTask().ContinueWith(t => (DialogButton) t.Result.Id,
|
||||
TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,8 @@
|
||||
<converters:ColorResourceKeyFallbackConverter x:Key="ColorResourceKeyFallbackConverter" />
|
||||
<converters:TrueToBoldConverter x:Key="TrueToBoldConverter" />
|
||||
<converters:I18NConverter x:Key="I18NConverter" />
|
||||
<converters:NullToCollapsedConverter x:Key="NullToCollapsedConverter" />
|
||||
<converters:MenuViewModelToFlyoutMenuConverter x:Key="MenuViewModelToFlyoutMenuConverter" />
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
+189
-223
@@ -27,7 +27,6 @@ using Windows.ApplicationModel.AppService;
|
||||
using Windows.ApplicationModel.Background;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Storage;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Popups;
|
||||
using Windows.UI.ViewManagement;
|
||||
using Windows.UI.Xaml;
|
||||
@@ -68,6 +67,9 @@ namespace FluentTerminal.App
|
||||
|
||||
UnhandledException += OnUnhandledException;
|
||||
|
||||
TaskScheduler.UnobservedTaskException += (sender, e) =>
|
||||
Logger.Instance.Error(e.Exception, "Unobserved Task Exception");
|
||||
|
||||
var applicationDataContainers = new ApplicationDataContainers
|
||||
{
|
||||
LocalSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings),
|
||||
@@ -78,8 +80,11 @@ namespace FluentTerminal.App
|
||||
SshProfiles = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.SshProfilesContainerName, ApplicationDataCreateDisposition.Always)),
|
||||
HistoryContainer = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.ExecutedCommandsContainerName, ApplicationDataCreateDisposition.Always))
|
||||
};
|
||||
|
||||
var builder = new ContainerBuilder();
|
||||
builder.RegisterInstance(applicationDataContainers);
|
||||
builder.RegisterType<SettingsService>().As<ISettingsService>().SingleInstance();
|
||||
builder.RegisterType<CommandHistoryService>().As<ICommandHistoryService>().SingleInstance();
|
||||
builder.RegisterType<DefaultValueProvider>().As<IDefaultValueProvider>().SingleInstance();
|
||||
builder.RegisterType<TrayProcessCommunicationService>().As<ITrayProcessCommunicationService>().SingleInstance();
|
||||
builder.RegisterType<DialogService>().As<IDialogService>().SingleInstance();
|
||||
@@ -93,9 +98,8 @@ namespace FluentTerminal.App
|
||||
builder.RegisterType<FluentTerminalThemeParser>().As<IThemeParser>().SingleInstance();
|
||||
builder.RegisterType<ClipboardService>().As<IClipboardService>().SingleInstance();
|
||||
builder.RegisterType<FileSystemService>().As<IFileSystemService>().SingleInstance();
|
||||
builder.RegisterType<ImageFileSystemService>().As<IImageFileSystemService>().SingleInstance();
|
||||
builder.RegisterType<SystemFontService>().As<ISystemFontService>().SingleInstance();
|
||||
builder.RegisterType<ShellProfileSelectionDialog>().As<IShellProfileSelectionDialog>().InstancePerDependency();
|
||||
builder.RegisterType<SshProfileSelectionDialog>().As<ISshProfileSelectionDialog>().InstancePerDependency();
|
||||
builder.RegisterType<CreateKeyBindingDialog>().As<ICreateKeyBindingDialog>().InstancePerDependency();
|
||||
builder.RegisterType<InputDialog>().As<IInputDialog>().InstancePerDependency();
|
||||
builder.RegisterType<AboutDialog>().As<IAboutDialog>().InstancePerDependency();
|
||||
@@ -103,11 +107,10 @@ namespace FluentTerminal.App
|
||||
builder.RegisterType<SshInfoDialog>().As<ISshConnectionInfoDialog>().InstancePerDependency();
|
||||
builder.RegisterType<CustomCommandDialog>().As<ICustomCommandDialog>().InstancePerDependency();
|
||||
builder.RegisterType<ApplicationViewAdapter>().As<IApplicationView>().InstancePerDependency();
|
||||
builder.RegisterType<DispatcherTimerAdapter>().As<IDispatcherTimer>().InstancePerDependency();
|
||||
builder.RegisterType<StartupTaskService>().As<IStartupTaskService>().SingleInstance();
|
||||
builder.RegisterType<ApplicationLanguageService>().As<IApplicationLanguageService>().SingleInstance();
|
||||
builder.RegisterType<ShellProfileMigrationService>().As<IShellProfileMigrationService>().SingleInstance();
|
||||
builder.RegisterInstance(applicationDataContainers);
|
||||
builder.RegisterType<AcceleratorKeyValidator>().As<IAcceleratorKeyValidator>().SingleInstance();
|
||||
|
||||
_container = builder.Build();
|
||||
|
||||
@@ -187,7 +190,7 @@ namespace FluentTerminal.App
|
||||
{
|
||||
if (protocolActivated.Uri == new Uri("ftcmd://fluent.terminal?focus"))
|
||||
{
|
||||
await ShowOrCreateWindow(protocolActivated.ViewSwitcher);
|
||||
await ShowOrCreateWindowAsync(protocolActivated.ViewSwitcher);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -207,7 +210,7 @@ namespace FluentTerminal.App
|
||||
// in order to be able to communicate with user.
|
||||
mainViewModel = _container.Resolve<MainViewModel>();
|
||||
|
||||
await CreateMainView(typeof(MainPage), mainViewModel, true);
|
||||
await CreateMainViewAsync(typeof(MainPage), mainViewModel, true);
|
||||
|
||||
applicationView = mainViewModel.ApplicationView;
|
||||
}
|
||||
@@ -225,7 +228,7 @@ namespace FluentTerminal.App
|
||||
"Invalid Link")
|
||||
.ShowAsync();
|
||||
|
||||
mainViewModel?.ApplicationView.TryClose();
|
||||
mainViewModel?.ApplicationView.TryCloseAsync();
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -247,19 +250,19 @@ namespace FluentTerminal.App
|
||||
"Invalid Link")
|
||||
.ShowAsync();
|
||||
|
||||
mainViewModel?.ApplicationView.TryClose();
|
||||
mainViewModel?.ApplicationView.TryCloseAsync();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_applicationSettings.AutoFallbackToWindowsUsernameInLinks && string.IsNullOrEmpty(vm.Username))
|
||||
{
|
||||
vm.Username = await _trayProcessCommunicationService.GetUserName();
|
||||
vm.Username = await _trayProcessCommunicationService.GetUserNameAsync();
|
||||
}
|
||||
|
||||
var error = await vm.AcceptChangesAsync(true);
|
||||
|
||||
SshProfile profile = (SshProfile) vm.Model;
|
||||
var profile = (SshProfile) vm.Model;
|
||||
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
@@ -269,16 +272,16 @@ namespace FluentTerminal.App
|
||||
if (profile == null)
|
||||
{
|
||||
// User clicked "Cancel" in the dialog.
|
||||
mainViewModel?.ApplicationView.TryClose();
|
||||
mainViewModel?.ApplicationView.TryCloseAsync();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mainViewModel == null)
|
||||
await CreateTerminal(profile, _applicationSettings.NewTerminalLocation, protocolActivated.ViewSwitcher);
|
||||
await CreateTerminalAsync(profile, _applicationSettings.NewTerminalLocation, protocolActivated.ViewSwitcher);
|
||||
else
|
||||
await mainViewModel.AddTerminalAsync(profile);
|
||||
await mainViewModel.AddTabAsync(profile);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -291,7 +294,7 @@ namespace FluentTerminal.App
|
||||
{
|
||||
vm = CommandProfileProviderViewModel.ParseUri(protocolActivated.Uri, _settingsService,
|
||||
applicationView, _trayProcessCommunicationService,
|
||||
_container.Resolve<ApplicationDataContainers>().HistoryContainer);
|
||||
_container.Resolve<ICommandHistoryService>());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -300,7 +303,7 @@ namespace FluentTerminal.App
|
||||
"Invalid Link")
|
||||
.ShowAsync();
|
||||
|
||||
mainViewModel?.ApplicationView.TryClose();
|
||||
mainViewModel?.ApplicationView.TryCloseAsync();
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -317,7 +320,7 @@ namespace FluentTerminal.App
|
||||
if (profile == null)
|
||||
{
|
||||
// User clicked "Cancel" in the dialog.
|
||||
mainViewModel?.ApplicationView.TryClose();
|
||||
mainViewModel?.ApplicationView.TryCloseAsync();
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -325,11 +328,11 @@ namespace FluentTerminal.App
|
||||
|
||||
if (mainViewModel == null)
|
||||
{
|
||||
await CreateTerminal(profile, _applicationSettings.NewTerminalLocation, protocolActivated.ViewSwitcher);
|
||||
await CreateTerminalAsync(profile, _applicationSettings.NewTerminalLocation, protocolActivated.ViewSwitcher);
|
||||
}
|
||||
else
|
||||
{
|
||||
await mainViewModel.AddTerminalAsync(profile);
|
||||
await mainViewModel.AddTabAsync(profile);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -339,7 +342,8 @@ namespace FluentTerminal.App
|
||||
"Invalid Link")
|
||||
.ShowAsync();
|
||||
|
||||
mainViewModel?.ApplicationView.TryClose();
|
||||
// ReSharper disable once AssignmentIsFullyDiscarded
|
||||
_ = mainViewModel?.ApplicationView.TryCloseAsync();
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -352,103 +356,109 @@ namespace FluentTerminal.App
|
||||
arguments = "new";
|
||||
}
|
||||
|
||||
_commandLineParser.ParseArguments(SplitArguments(arguments), typeof(NewVerb), typeof(RunVerb), typeof(SettingsVerb)).WithParsed(async verb =>
|
||||
{
|
||||
if (verb is SettingsVerb settingsVerb)
|
||||
{
|
||||
if (!settingsVerb.Import && !settingsVerb.Export)
|
||||
{
|
||||
await ShowSettings().ConfigureAwait(true);
|
||||
}
|
||||
else if (settingsVerb.Export)
|
||||
{
|
||||
var exportFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("config.json", CreationCollisionOption.OpenIfExists);
|
||||
|
||||
var settings = _settingsService.ExportSettings();
|
||||
await FileIO.WriteTextAsync(exportFile, settings);
|
||||
await new MessageDialog($"{I18N.Translate("SettingsExported")} {exportFile.Path}").ShowAsync();
|
||||
}
|
||||
else if (settingsVerb.Import)
|
||||
{
|
||||
var file = await ApplicationData.Current.LocalFolder.GetFileAsync("config.json");
|
||||
var content = await FileIO.ReadTextAsync(file);
|
||||
_settingsService.ImportSettings(content);
|
||||
await new MessageDialog($"{I18N.Translate("SettingsImported")} {file.Path}").ShowAsync();
|
||||
}
|
||||
}
|
||||
else if (verb is NewVerb newVerb)
|
||||
{
|
||||
var profile = default(ShellProfile);
|
||||
if (!string.IsNullOrWhiteSpace(newVerb.Profile))
|
||||
{
|
||||
profile = _settingsService.GetShellProfiles().FirstOrDefault(x => x.Name.Equals(newVerb.Profile, StringComparison.CurrentCultureIgnoreCase));
|
||||
}
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
profile = _settingsService.GetDefaultShellProfile();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(newVerb.Directory) && newVerb.Directory != ".")
|
||||
{
|
||||
profile.WorkingDirectory = newVerb.Directory;
|
||||
}
|
||||
else
|
||||
{
|
||||
profile.WorkingDirectory = commandLineActivated.Operation.CurrentDirectoryPath;
|
||||
}
|
||||
|
||||
var location = newVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation
|
||||
: newVerb.Target == Target.Tab ? NewTerminalLocation.Tab
|
||||
: NewTerminalLocation.Window;
|
||||
|
||||
await CreateTerminal(profile, location).ConfigureAwait(true);
|
||||
}
|
||||
else if (verb is RunVerb runVerb)
|
||||
{
|
||||
var profile = new ShellProfile
|
||||
{
|
||||
Id = Guid.Empty,
|
||||
Location = null,
|
||||
Arguments = runVerb.Command,
|
||||
WorkingDirectory = runVerb.Directory
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(runVerb.Theme))
|
||||
{
|
||||
var theme = _settingsService.GetThemes().FirstOrDefault(x => x.Name.Equals(runVerb.Theme, StringComparison.CurrentCultureIgnoreCase));
|
||||
if (theme != null)
|
||||
{
|
||||
profile.TerminalThemeId = theme.Id;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(profile.WorkingDirectory))
|
||||
{
|
||||
profile.WorkingDirectory = commandLineActivated.Operation.CurrentDirectoryPath;
|
||||
}
|
||||
|
||||
var location = runVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation
|
||||
: runVerb.Target == Target.Tab ? NewTerminalLocation.Tab
|
||||
: NewTerminalLocation.Window;
|
||||
|
||||
await CreateTerminal(profile, location).ConfigureAwait(true);
|
||||
}
|
||||
});
|
||||
_commandLineParser
|
||||
.ParseArguments(SplitArguments(arguments), typeof(NewVerb), typeof(RunVerb), typeof(SettingsVerb))
|
||||
.WithParsed(async verb => await CliRunAsync(verb, commandLineActivated));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowOrCreateWindow(ActivationViewSwitcher viewSwitcher)
|
||||
private async Task CliRunAsync(object verb, CommandLineActivatedEventArgs commandLineActivated)
|
||||
{
|
||||
if (_mainViewModels.Count == 0)
|
||||
if (verb is SettingsVerb settingsVerb)
|
||||
{
|
||||
await CreateTerminal(_settingsService.GetDefaultShellProfile(), NewTerminalLocation.Tab, viewSwitcher);
|
||||
if (!settingsVerb.Import && !settingsVerb.Export)
|
||||
{
|
||||
await ShowSettingsAsync();
|
||||
}
|
||||
else if (settingsVerb.Export)
|
||||
{
|
||||
var exportFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("config.json", CreationCollisionOption.OpenIfExists);
|
||||
|
||||
var settings = _settingsService.ExportSettings();
|
||||
await FileIO.WriteTextAsync(exportFile, settings);
|
||||
await new MessageDialog($"{I18N.Translate("SettingsExported")} {exportFile.Path}").ShowAsync();
|
||||
}
|
||||
else if (settingsVerb.Import)
|
||||
{
|
||||
var file = await ApplicationData.Current.LocalFolder.GetFileAsync("config.json");
|
||||
var content = await FileIO.ReadTextAsync(file);
|
||||
_settingsService.ImportSettings(content);
|
||||
await new MessageDialog($"{I18N.Translate("SettingsImported")} {file.Path}").ShowAsync();
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (verb is NewVerb newVerb)
|
||||
{
|
||||
var viewModel = _mainViewModels.Find(o => o.ApplicationView.Id == _activeWindowId) ?? _mainViewModels.Last();
|
||||
await ShowAsStandaloneAsync(viewModel, viewSwitcher);
|
||||
var profile = default(ShellProfile);
|
||||
if (!string.IsNullOrWhiteSpace(newVerb.Profile))
|
||||
{
|
||||
profile = _settingsService.GetShellProfiles().FirstOrDefault(x => x.Name.Equals(newVerb.Profile, StringComparison.CurrentCultureIgnoreCase));
|
||||
}
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
profile = _settingsService.GetDefaultShellProfile();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(newVerb.Directory) && newVerb.Directory != ".")
|
||||
{
|
||||
profile.WorkingDirectory = newVerb.Directory;
|
||||
}
|
||||
else
|
||||
{
|
||||
profile.WorkingDirectory = commandLineActivated.Operation.CurrentDirectoryPath;
|
||||
}
|
||||
|
||||
var location = newVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation
|
||||
: newVerb.Target == Target.Tab ? NewTerminalLocation.Tab
|
||||
: NewTerminalLocation.Window;
|
||||
|
||||
await CreateTerminalAsync(profile, location);
|
||||
}
|
||||
else if (verb is RunVerb runVerb)
|
||||
{
|
||||
var profile = new ShellProfile
|
||||
{
|
||||
Id = Guid.Empty,
|
||||
Location = null,
|
||||
Arguments = runVerb.Command,
|
||||
WorkingDirectory = runVerb.Directory
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(runVerb.Buffer) && bool.TryParse(runVerb.Buffer, out var useBuffer))
|
||||
{
|
||||
profile.UseBuffer = useBuffer;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(runVerb.Theme))
|
||||
{
|
||||
var theme = _settingsService.GetThemes().FirstOrDefault(x => x.Name.Equals(runVerb.Theme, StringComparison.CurrentCultureIgnoreCase));
|
||||
if (theme != null)
|
||||
{
|
||||
profile.TerminalThemeId = theme.Id;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(profile.WorkingDirectory))
|
||||
{
|
||||
profile.WorkingDirectory = commandLineActivated.Operation.CurrentDirectoryPath;
|
||||
}
|
||||
|
||||
var location = runVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation
|
||||
: runVerb.Target == Target.Tab ? NewTerminalLocation.Tab
|
||||
: NewTerminalLocation.Window;
|
||||
|
||||
await CreateTerminalAsync(profile, location);
|
||||
}
|
||||
}
|
||||
|
||||
private Task ShowOrCreateWindowAsync(ActivationViewSwitcher viewSwitcher)
|
||||
{
|
||||
var viewModel = _mainViewModels.Find(o => o.ApplicationView.Id == _activeWindowId) ??
|
||||
_mainViewModels.LastOrDefault();
|
||||
|
||||
return viewModel == null
|
||||
? CreateTerminalAsync(_settingsService.GetDefaultShellProfile(), NewTerminalLocation.Tab, viewSwitcher)
|
||||
: ShowAsStandaloneAsync(viewModel, viewSwitcher);
|
||||
}
|
||||
|
||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||
@@ -459,48 +469,49 @@ namespace FluentTerminal.App
|
||||
}
|
||||
|
||||
_isLaunching = true;
|
||||
|
||||
if (!_alreadyLaunched)
|
||||
{
|
||||
await InitializeLogger();
|
||||
await InitializeLoggerAsync();
|
||||
|
||||
// TODO: Check the reason for such strange using of tasks!
|
||||
Task.Run(async () => await JumpListHelper.Update(_settingsService.GetShellProfiles()));
|
||||
// ReSharper disable once AssignmentIsFullyDiscarded
|
||||
_ = JumpListHelper.UpdateAsync(_settingsService);
|
||||
|
||||
var viewModel = _container.Resolve<MainViewModel>();
|
||||
if (args.Arguments.StartsWith(JumpListHelper.ShellProfileFlag))
|
||||
{
|
||||
await viewModel.AddLocalTabOrWindowAsync(Guid.Parse(args.Arguments.Replace(JumpListHelper.ShellProfileFlag, string.Empty)));
|
||||
await viewModel.AddProfileByGuidAsync(Guid.Parse(args.Arguments.Replace(JumpListHelper.ShellProfileFlag, string.Empty)));
|
||||
}
|
||||
else
|
||||
{
|
||||
await viewModel.AddLocalTabAsync();
|
||||
await viewModel.AddDefaultProfileAsync(NewTerminalLocation.Tab);
|
||||
}
|
||||
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
|
||||
await CreateMainViewAsync(typeof(MainPage), viewModel, true);
|
||||
Window.Current.Activate();
|
||||
}
|
||||
else if (args.Arguments.StartsWith(JumpListHelper.ShellProfileFlag))
|
||||
{
|
||||
var location = _applicationSettings.NewTerminalLocation;
|
||||
var profile = _settingsService.GetShellProfile(Guid.Parse(args.Arguments.Replace(JumpListHelper.ShellProfileFlag, string.Empty)));
|
||||
await CreateTerminal(profile, location, args.ViewSwitcher).ConfigureAwait(true);
|
||||
await CreateTerminalAsync(profile, location, args.ViewSwitcher);
|
||||
}
|
||||
else
|
||||
{
|
||||
var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true);
|
||||
await viewModel.AddLocalTabAsync();
|
||||
var viewModel = await CreateNewTerminalWindowAsync();
|
||||
await viewModel.AddDefaultProfileAsync(NewTerminalLocation.Tab);
|
||||
await ShowAsStandaloneAsync(viewModel, args.ViewSwitcher);
|
||||
}
|
||||
|
||||
_isLaunching = false;
|
||||
}
|
||||
|
||||
private static async Task InitializeLogger()
|
||||
private static async Task InitializeLoggerAsync()
|
||||
{
|
||||
var logDirectory = await ApplicationData.Current.LocalCacheFolder.CreateFolderAsync("Logs", CreationCollisionOption.OpenIfExists);
|
||||
var logFile = Path.Combine(logDirectory.Path, "fluentterminal.app.log");
|
||||
var configFile = await logDirectory.CreateFileAsync("config.json", CreationCollisionOption.OpenIfExists);
|
||||
var configContent = await FileIO.ReadTextAsync(configFile);
|
||||
|
||||
|
||||
if (string.IsNullOrWhiteSpace(configContent))
|
||||
{
|
||||
configContent = JsonConvert.SerializeObject(new Logger.Configuration());
|
||||
@@ -525,7 +536,7 @@ namespace FluentTerminal.App
|
||||
|
||||
_appServiceConnection = new AppServiceConnectionAdapter(details.AppServiceConnection);
|
||||
|
||||
_trayReady.SetResult(0);
|
||||
_trayReady.TrySetResult(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -540,11 +551,11 @@ namespace FluentTerminal.App
|
||||
Application.Current.Exit();
|
||||
}
|
||||
|
||||
private async Task CreateMainView(Type pageType, INotifyPropertyChanged viewModel, bool extendViewIntoTitleBar)
|
||||
private async Task CreateMainViewAsync(Type pageType, INotifyPropertyChanged viewModel, bool extendViewIntoTitleBar)
|
||||
{
|
||||
ApplicationViewSwitcher.DisableSystemViewActivationPolicy();
|
||||
|
||||
await StartSystemTray().ConfigureAwait(true);
|
||||
await StartSystemTray();
|
||||
|
||||
if (!(Window.Current.Content is Frame rootFrame))
|
||||
{
|
||||
@@ -572,9 +583,9 @@ namespace FluentTerminal.App
|
||||
Window.Current.Activate();
|
||||
}
|
||||
|
||||
private async Task<MainViewModel> CreateNewTerminalWindow()
|
||||
private async Task<MainViewModel> CreateNewTerminalWindowAsync()
|
||||
{
|
||||
var viewModel = await CreateSecondaryView<MainViewModel>(typeof(MainPage), true).ConfigureAwait(true);
|
||||
var viewModel = await CreateSecondaryViewAsync<MainViewModel>(typeof(MainPage), true);
|
||||
viewModel.Closed += OnMainViewModelClosed;
|
||||
viewModel.NewWindowRequested += OnNewWindowRequested;
|
||||
viewModel.ShowSettingsRequested += OnShowSettingsRequested;
|
||||
@@ -585,22 +596,24 @@ namespace FluentTerminal.App
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
private async Task<TViewModel> CreateSecondaryView<TViewModel>(Type pageType, bool extendViewIntoTitleBar)
|
||||
private async Task<TViewModel> CreateSecondaryViewAsync<TViewModel>(Type pageType, bool extendViewIntoTitleBar)
|
||||
{
|
||||
int windowId = 0;
|
||||
var windowId = 0;
|
||||
TViewModel viewModel = default;
|
||||
await CoreApplication.CreateNewView().Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||
var newView = CoreApplication.CreateNewView();
|
||||
// ConfigureAwait(true) because ApplicationViewSwitcher.TryShowAsStandaloneAsync requires the same thread as CoreApplication.CreateNewView
|
||||
await newView.Dispatcher.ExecuteAsync(() =>
|
||||
{
|
||||
viewModel = _container.Resolve<TViewModel>();
|
||||
|
||||
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = extendViewIntoTitleBar;
|
||||
newView.TitleBar.ExtendViewIntoTitleBar = extendViewIntoTitleBar;
|
||||
var frame = new Frame();
|
||||
frame.Navigate(pageType, viewModel);
|
||||
Window.Current.Content = frame;
|
||||
Window.Current.Activate();
|
||||
|
||||
windowId = ApplicationView.GetForCurrentView().Id;
|
||||
});
|
||||
}).ConfigureAwait(true);
|
||||
|
||||
if (viewModel is SettingsViewModel settingsViewModel)
|
||||
{
|
||||
@@ -638,7 +651,15 @@ namespace FluentTerminal.App
|
||||
|
||||
_mainViewModels.Remove(viewModel);
|
||||
|
||||
Window.Current.Content = null;
|
||||
try
|
||||
{
|
||||
// try/catch because the method cannot be called on the last window
|
||||
Window.Current.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -655,149 +676,94 @@ namespace FluentTerminal.App
|
||||
{
|
||||
Logger.Instance.Debug("App.xaml.cs on tab tear off");
|
||||
|
||||
var newViewModel = await CreateNewTerminalWindow().ConfigureAwait(true);
|
||||
await newViewModel.AddTerminalAsync(await model.Serialize(), 0);
|
||||
var newViewModel = await CreateNewTerminalWindowAsync();
|
||||
await newViewModel.AddTabAsync(await model.SerializeAsync(), 0);
|
||||
}
|
||||
|
||||
private async void OnNewWindowRequested(object sender, NewWindowRequestedEventArgs e)
|
||||
{
|
||||
ShellProfile profile = null;
|
||||
switch (e.Action)
|
||||
{
|
||||
case NewWindowAction.StartDefaultLocalTerminal:
|
||||
break;
|
||||
case NewWindowAction.ShowProfileSelection:
|
||||
profile = await _dialogService.ShowProfileSelectionDialogAsync();
|
||||
if (profile == null)
|
||||
{
|
||||
// Nothing to do if user cancels.
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case NewWindowAction.ShowSshProfileSelection:
|
||||
profile = await _dialogService.ShowSshProfileSelectionDialogAsync();
|
||||
if (profile == null)
|
||||
{
|
||||
// Nothing to do if user cancels.
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case NewWindowAction.ShowCustomCommandDialog:
|
||||
profile = await _dialogService.ShowCustomCommandDialogAsync();
|
||||
if (profile == null)
|
||||
{
|
||||
// Nothing to do if user cancels.
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case NewWindowAction.ShowSshInfoDialog:
|
||||
profile = await _dialogService.ShowSshConnectionInfoDialogAsync();
|
||||
if (profile == null)
|
||||
{
|
||||
// Nothing to do if user cancels.
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case NewWindowAction.StartLocalTerminal:
|
||||
profile = _settingsService.GetShellProfile(e.ProfileId);
|
||||
break;
|
||||
case NewWindowAction.StartSshTerminal:
|
||||
profile = _settingsService.GetSshProfile(e.ProfileId);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException("this ProfileSelection not implemented yet");
|
||||
}
|
||||
var viewModel = await CreateNewTerminalWindowAsync().ConfigureAwait(false);
|
||||
|
||||
var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true);
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
await viewModel.AddLocalTabAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await viewModel.AddTerminalAsync(profile);
|
||||
}
|
||||
await viewModel.AddTabAsync(e.Profile).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnSettingsClosed(object sender, EventArgs e)
|
||||
{
|
||||
Task.Run(async () => await JumpListHelper.Update(_settingsService.GetShellProfiles()));
|
||||
// ReSharper disable once AssignmentIsFullyDiscarded
|
||||
_ = JumpListHelper.UpdateAsync(_settingsService);
|
||||
_settingsViewModel.Closed -= OnSettingsClosed;
|
||||
_settingsViewModel = null;
|
||||
_settingsWindowId = null;
|
||||
}
|
||||
|
||||
private async void OnShowSettingsRequested(object sender, EventArgs e)
|
||||
private void OnShowSettingsRequested(object sender, EventArgs e)
|
||||
{
|
||||
await ShowSettings().ConfigureAwait(true);
|
||||
// ReSharper disable once AssignmentIsFullyDiscarded
|
||||
_ = ShowSettingsAsync();
|
||||
}
|
||||
|
||||
private async Task ShowAsStandaloneAsync(MainViewModel viewModel, ActivationViewSwitcher viewSwitcher = null)
|
||||
private Task ShowAsStandaloneAsync(MainViewModel viewModel, ActivationViewSwitcher viewSwitcher = null)
|
||||
{
|
||||
int viewId = viewModel.ApplicationView.Id;
|
||||
var viewId = viewModel.ApplicationView.Id;
|
||||
if (viewSwitcher != null)
|
||||
{
|
||||
await viewModel.ApplicationView.RunOnDispatcherThread(async () => await viewSwitcher.ShowAsStandaloneAsync(viewId));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(viewId);
|
||||
return viewModel.ApplicationView.ExecuteOnUiThreadAsync(async () => await viewSwitcher.ShowAsStandaloneAsync(viewId));
|
||||
}
|
||||
|
||||
return ApplicationViewSwitcher.TryShowAsStandaloneAsync(viewId).AsTask();
|
||||
}
|
||||
|
||||
private async Task CreateTerminal(ShellProfile profile, NewTerminalLocation location, ActivationViewSwitcher viewSwitcher = null)
|
||||
private async Task CreateTerminalAsync(ShellProfile profile, NewTerminalLocation location, ActivationViewSwitcher viewSwitcher = null)
|
||||
{
|
||||
|
||||
|
||||
if (!_alreadyLaunched)
|
||||
{
|
||||
var viewModel = _container.Resolve<MainViewModel>();
|
||||
await viewModel.AddTerminalAsync(profile);
|
||||
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
|
||||
await viewModel.AddTabAsync(profile);
|
||||
await CreateMainViewAsync(typeof(MainPage), viewModel, true);
|
||||
}
|
||||
else if (location == NewTerminalLocation.Tab && _mainViewModels.Count > 0)
|
||||
{
|
||||
var item = _mainViewModels.FirstOrDefault(o => o.ApplicationView.Id == _activeWindowId) ??
|
||||
_mainViewModels.Last();
|
||||
|
||||
MainViewModel item = _mainViewModels.FirstOrDefault(o => o.ApplicationView.Id == _activeWindowId);
|
||||
if (item == null)
|
||||
{
|
||||
item = _mainViewModels.Last();
|
||||
}
|
||||
|
||||
await item.AddTerminalAsync(profile);
|
||||
await item.AddTabAsync(profile);
|
||||
await ShowAsStandaloneAsync(item, viewSwitcher);
|
||||
}
|
||||
else
|
||||
{
|
||||
var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true);
|
||||
await viewModel.AddTerminalAsync(profile);
|
||||
var viewModel = await CreateNewTerminalWindowAsync();
|
||||
await viewModel.AddTabAsync(profile);
|
||||
await ShowAsStandaloneAsync(viewModel, viewSwitcher);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowSettings()
|
||||
private Task ShowSettingsAsync()
|
||||
{
|
||||
if (!_alreadyLaunched)
|
||||
{
|
||||
var viewModel = _container.Resolve<SettingsViewModel>();
|
||||
await CreateMainView(typeof(SettingsPage), viewModel, true).ConfigureAwait(true);
|
||||
return CreateMainViewAsync(typeof(SettingsPage), _container.Resolve<SettingsViewModel>(), true);
|
||||
}
|
||||
else if (_settingsViewModel == null)
|
||||
|
||||
if (_settingsViewModel == null)
|
||||
{
|
||||
await CreateSecondaryView<SettingsViewModel>(typeof(SettingsPage), true).ConfigureAwait(true);
|
||||
return CreateSecondaryViewAsync<SettingsViewModel>(typeof(SettingsPage), true);
|
||||
}
|
||||
else
|
||||
|
||||
if (_settingsWindowId.HasValue)
|
||||
{
|
||||
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(_settingsWindowId.Value);
|
||||
return ApplicationViewSwitcher.TryShowAsStandaloneAsync(_settingsWindowId.Value).AsTask();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task StartSystemTray()
|
||||
private Task StartSystemTray()
|
||||
{
|
||||
var launch = FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("AppLaunchedParameterGroup").AsTask();
|
||||
await Task.WhenAll(launch, _trayReady.Task).ConfigureAwait(true);
|
||||
_trayProcessCommunicationService.Initialize(_appServiceConnection);
|
||||
|
||||
return Task.WhenAll(launch, _trayReady.Task).ContinueWith(
|
||||
t => _trayProcessCommunicationService.Initialize(_appServiceConnection),
|
||||
TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,9 @@ namespace FluentTerminal.App.CommandLineArguments
|
||||
[Option("theme")]
|
||||
public string Theme { get; set; }
|
||||
|
||||
[Option("buffer")]
|
||||
public string Buffer { get; set; }
|
||||
|
||||
[Option("target")]
|
||||
public Target Target { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Windows.UI.Xaml.Data;
|
||||
// ReSharper disable LocalizableElement
|
||||
|
||||
namespace FluentTerminal.App.Converters
|
||||
{
|
||||
public class BooleanNegationConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (targetType == typeof(bool))
|
||||
{
|
||||
if (value is bool isValue)
|
||||
{
|
||||
return !isValue;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"{nameof(value)} argument has to be of type bool.", nameof(value));
|
||||
}
|
||||
|
||||
if (targetType == typeof(bool?))
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value is bool isValue)
|
||||
{
|
||||
return !isValue;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"{nameof(value)} argument has to be of type bool or Nullable<bool>.", nameof(value));
|
||||
}
|
||||
|
||||
throw new ArgumentException($"{nameof(targetType)} argument has to {typeof(bool)}.",
|
||||
nameof(targetType));
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
|
||||
Convert(value, targetType, parameter, language);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using FluentTerminal.App.ViewModels.Menu;
|
||||
using Microsoft.Toolkit.Uwp.Helpers;
|
||||
using System;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Media;
|
||||
// ReSharper disable LocalizableElement
|
||||
|
||||
namespace FluentTerminal.App.Converters
|
||||
{
|
||||
public class IconConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is Mdl2Icon mdl2Icon)
|
||||
{
|
||||
var fontIcon = new FontIcon { FontFamily = new FontFamily("Segoe MDL2 Assets"), Glyph = mdl2Icon.Glyph };
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(mdl2Icon.Color))
|
||||
{
|
||||
fontIcon.Foreground = new SolidColorBrush(mdl2Icon.Color.ToColor());
|
||||
}
|
||||
|
||||
return fontIcon;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
using System;
|
||||
using Windows.System;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using FluentTerminal.App.ViewModels.Menu;
|
||||
using FluentTerminal.App.Views;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
// ReSharper disable LocalizableElement
|
||||
|
||||
namespace FluentTerminal.App.Converters
|
||||
{
|
||||
public class MenuItemViewModelBaseToMenuFlayoutItemBaseConverter : IValueConverter
|
||||
{
|
||||
#region Static
|
||||
|
||||
private static readonly IconConverter IconConverter = new IconConverter();
|
||||
|
||||
private static MenuFlyoutItemBase GetItem(MenuItemViewModelBase viewModel)
|
||||
{
|
||||
if (viewModel is ExpandableMenuItemViewModel expandable)
|
||||
{
|
||||
return GetExpandableItem(expandable);
|
||||
}
|
||||
|
||||
if (viewModel is SeparatorMenuItemViewModel)
|
||||
{
|
||||
return GetSeparatorItem();
|
||||
}
|
||||
|
||||
if (viewModel is MenuItemViewModel regular)
|
||||
{
|
||||
return GetRegularItem(regular);
|
||||
}
|
||||
|
||||
if (viewModel is ToggleMenuItemViewModel toggle)
|
||||
{
|
||||
return GetToggleItem(toggle);
|
||||
}
|
||||
|
||||
if (viewModel is RadioMenuItemViewModel radio)
|
||||
{
|
||||
return GetRadioItem(radio);
|
||||
}
|
||||
|
||||
// Won't happen ever, but still...
|
||||
throw new NotImplementedException(
|
||||
$"Unexpected {nameof(MenuItemViewModelBase)} type: {viewModel.GetType()}");
|
||||
}
|
||||
|
||||
private static MenuFlyoutSeparator GetSeparatorItem()
|
||||
{
|
||||
return new MenuFlyoutSeparator();
|
||||
}
|
||||
|
||||
private static MenuFlyoutItem GetRegularItem(MenuItemViewModel viewModel)
|
||||
{
|
||||
var item = new MenuFlyoutItem();
|
||||
|
||||
item.SetBinding(MenuFlyoutItem.TextProperty, new Binding
|
||||
{
|
||||
Source = viewModel,
|
||||
Path = new PropertyPath(nameof(MenuItemViewModelBase.Text)),
|
||||
Mode = BindingMode.OneWay
|
||||
});
|
||||
|
||||
item.SetBinding(ToolTipService.ToolTipProperty, new Binding
|
||||
{
|
||||
Source = viewModel,
|
||||
Path = new PropertyPath(nameof(MenuItemViewModelBase.Description)),
|
||||
Mode = BindingMode.OneWay
|
||||
});
|
||||
|
||||
item.SetBinding(MenuFlyoutItem.IconProperty, new Binding
|
||||
{
|
||||
Source = viewModel,
|
||||
Path = new PropertyPath(nameof(MenuItemViewModelBase.Icon)),
|
||||
Converter = IconConverter,
|
||||
Mode = BindingMode.OneWay
|
||||
});
|
||||
|
||||
item.SetBinding(MenuFlyoutItem.CommandProperty, new Binding
|
||||
{
|
||||
Source = viewModel,
|
||||
Path = new PropertyPath(nameof(MenuItemViewModel.Command)),
|
||||
Mode = BindingMode.OneTime
|
||||
});
|
||||
|
||||
if (viewModel.KeyBinding != null)
|
||||
{
|
||||
item.KeyboardAccelerators?.Add(new KeyboardAccelerator
|
||||
{
|
||||
Key = (VirtualKey) viewModel.KeyBinding.Key,
|
||||
Modifiers = (VirtualKeyModifiers) viewModel.KeyBinding.KeyModifiers,
|
||||
IsEnabled = true
|
||||
});
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private static MenuFlyoutSubItem GetExpandableItem(ExpandableMenuItemViewModel viewModel)
|
||||
{
|
||||
var item = new MenuFlyoutSubItem();
|
||||
|
||||
item.SetBinding(MenuFlyoutSubItem.TextProperty, new Binding
|
||||
{
|
||||
Source = viewModel,
|
||||
Path = new PropertyPath(nameof(MenuItemViewModelBase.Text)),
|
||||
Mode = BindingMode.OneWay
|
||||
});
|
||||
|
||||
item.SetBinding(ToolTipService.ToolTipProperty, new Binding
|
||||
{
|
||||
Source = viewModel,
|
||||
Path = new PropertyPath(nameof(MenuItemViewModelBase.Description)),
|
||||
Mode = BindingMode.OneWay
|
||||
});
|
||||
|
||||
item.SetBinding(MenuFlyoutSubItem.IconProperty, new Binding
|
||||
{
|
||||
Source = viewModel,
|
||||
Path = new PropertyPath(nameof(MenuItemViewModelBase.Icon)),
|
||||
Converter = IconConverter,
|
||||
Mode = BindingMode.OneWay
|
||||
});
|
||||
|
||||
item.SetBinding(MenuExtension.SubItemsProperty, new Binding
|
||||
{
|
||||
Source = viewModel,
|
||||
Path = new PropertyPath(nameof(ExpandableMenuItemViewModel.SubItems)),
|
||||
Mode = BindingMode.OneWay
|
||||
});
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private static ToggleMenuFlyoutItem GetToggleItem(ToggleMenuItemViewModel viewModel)
|
||||
{
|
||||
var item = new ToggleMenuFlyoutItem();
|
||||
|
||||
item.SetBinding(MenuFlyoutItem.TextProperty, new Binding
|
||||
{
|
||||
Source = viewModel,
|
||||
Path = new PropertyPath(nameof(MenuItemViewModelBase.Text)),
|
||||
Mode = BindingMode.OneWay
|
||||
});
|
||||
|
||||
item.SetBinding(ToolTipService.ToolTipProperty, new Binding
|
||||
{
|
||||
Source = viewModel,
|
||||
Path = new PropertyPath(nameof(MenuItemViewModelBase.Description)),
|
||||
Mode = BindingMode.OneWay
|
||||
});
|
||||
|
||||
item.SetBinding(MenuFlyoutItem.IconProperty, new Binding
|
||||
{
|
||||
Source = viewModel,
|
||||
Path = new PropertyPath(nameof(MenuItemViewModelBase.Icon)),
|
||||
Converter = IconConverter,
|
||||
Mode = BindingMode.OneWay
|
||||
});
|
||||
|
||||
item.SetBinding(ToggleMenuFlyoutItem.IsCheckedProperty, new Binding
|
||||
{
|
||||
Source = viewModel.BindingSource,
|
||||
Path = new PropertyPath(viewModel.BindingPath),
|
||||
Mode = BindingMode.TwoWay
|
||||
});
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private static RadioMenuFlyoutItem GetRadioItem(RadioMenuItemViewModel viewModel)
|
||||
{
|
||||
var item = new RadioMenuFlyoutItem();
|
||||
|
||||
item.SetBinding(MenuFlyoutItem.TextProperty, new Binding
|
||||
{
|
||||
Source = viewModel,
|
||||
Path = new PropertyPath(nameof(MenuItemViewModelBase.Text)),
|
||||
Mode = BindingMode.OneWay
|
||||
});
|
||||
|
||||
item.SetBinding(ToolTipService.ToolTipProperty, new Binding
|
||||
{
|
||||
Source = viewModel,
|
||||
Path = new PropertyPath(nameof(MenuItemViewModelBase.Description)),
|
||||
Mode = BindingMode.OneWay
|
||||
});
|
||||
|
||||
item.SetBinding(MenuFlyoutItem.IconProperty, new Binding
|
||||
{
|
||||
Source = viewModel,
|
||||
Path = new PropertyPath(nameof(MenuItemViewModelBase.Icon)),
|
||||
Converter = IconConverter,
|
||||
Mode = BindingMode.OneWay
|
||||
});
|
||||
|
||||
item.SetBinding(RadioMenuFlyoutItem.GroupNameProperty, new Binding
|
||||
{
|
||||
Source = viewModel,
|
||||
Path = new PropertyPath(nameof(RadioMenuItemViewModel.GroupName)),
|
||||
Mode = BindingMode.OneWay
|
||||
});
|
||||
|
||||
item.SetBinding(RadioMenuFlyoutItem.IsCheckedProperty, new Binding
|
||||
{
|
||||
Source = viewModel.BindingSource,
|
||||
Path = new PropertyPath(viewModel.BindingPath),
|
||||
Mode = BindingMode.TwoWay
|
||||
});
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
#endregion Static
|
||||
|
||||
#region IValueConverter
|
||||
|
||||
// ReSharper disable once AssignNullToNotNullAttribute
|
||||
public object Convert(object value, Type targetType = null, object parameter = null, string language = null)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return value is MenuItemViewModelBase menuItemViewModel
|
||||
? GetItem(menuItemViewModel)
|
||||
: throw new ArgumentException(
|
||||
$"Invalid {nameof(value)} argument type: {value.GetType()}. {typeof(MenuItemViewModelBase)} expected.",
|
||||
nameof(value));
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
#endregion IValueConverter
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using FluentTerminal.App.ViewModels.Menu;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.Foundation.Metadata;
|
||||
|
||||
namespace FluentTerminal.App.Converters
|
||||
{
|
||||
public class MenuViewModelToFlyoutMenuConverter : IValueConverter
|
||||
{
|
||||
private static readonly MenuItemViewModelBaseToMenuFlayoutItemBaseConverter ItemConverter =
|
||||
new MenuItemViewModelBaseToMenuFlayoutItemBaseConverter();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(value is MenuViewModel menuViewModel))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Invalid {nameof(value)} argument type: {value.GetType()}. {typeof(MenuViewModel)} expected.");
|
||||
}
|
||||
|
||||
var menuFlyout = new MenuFlyout
|
||||
{
|
||||
Placement = FlyoutPlacementMode.Bottom
|
||||
};
|
||||
|
||||
foreach (var menuItemViewModelBase in menuViewModel.Items)
|
||||
{
|
||||
menuFlyout.Items?.Add((MenuFlyoutItemBase) ItemConverter.Convert(menuItemViewModelBase));
|
||||
}
|
||||
|
||||
return menuFlyout;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Data;
|
||||
|
||||
namespace FluentTerminal.App.Converters
|
||||
{
|
||||
public class NullToCollapsedConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
return value == null ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.App.ViewModels;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using Microsoft.Toolkit.Uwp.Helpers;
|
||||
using System;
|
||||
@@ -12,8 +12,9 @@ namespace FluentTerminal.App.Converters
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is TabTheme theme && Enum.TryParse<TabThemeKey>((string)parameter, true, out TabThemeKey tabThemeKey))
|
||||
if (value is TabThemeViewModel viewModel && Enum.TryParse<TabThemeKey>((string)parameter, true, out TabThemeKey tabThemeKey))
|
||||
{
|
||||
var theme = viewModel.Theme;
|
||||
switch (tabThemeKey)
|
||||
{
|
||||
case TabThemeKey.Background:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.App.ViewModels;
|
||||
using FluentTerminal.Models;
|
||||
using System;
|
||||
using Windows.UI.Xaml.Data;
|
||||
|
||||
@@ -8,9 +9,9 @@ namespace FluentTerminal.App.Converters
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is TabTheme theme && parameter is string idString && int.TryParse(idString, out var id))
|
||||
if (value is TerminalViewModel terminal && parameter is string idString && int.TryParse(idString, out var id))
|
||||
{
|
||||
return theme.Id == id;
|
||||
return terminal.TabTheme.Theme.Id == id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -9,11 +9,15 @@ using System.Threading.Tasks;
|
||||
using Windows.System;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using FluentTerminal.App.Services;
|
||||
|
||||
namespace FluentTerminal.App.Dialogs
|
||||
{
|
||||
// ReSharper disable once RedundantExtendsListEntry
|
||||
public sealed partial class CreateKeyBindingDialog : ContentDialog, ICreateKeyBindingDialog, INotifyPropertyChanged
|
||||
{
|
||||
private readonly IAcceleratorKeyValidator _acceleratorKeyValidator;
|
||||
|
||||
private bool _ctrl;
|
||||
private bool _shift;
|
||||
private bool _alt;
|
||||
@@ -47,11 +51,19 @@ namespace FluentTerminal.App.Dialogs
|
||||
public int Key
|
||||
{
|
||||
get => _key;
|
||||
set => Set(ref _key, value);
|
||||
set
|
||||
{
|
||||
// We're ignoring invalid keys.
|
||||
if (_acceleratorKeyValidator.Valid(value))
|
||||
{
|
||||
Set(ref _key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CreateKeyBindingDialog()
|
||||
public CreateKeyBindingDialog(IAcceleratorKeyValidator acceleratorKeyValidator)
|
||||
{
|
||||
_acceleratorKeyValidator = acceleratorKeyValidator;
|
||||
InitializeComponent();
|
||||
ResetCommand = new RelayCommand(Reset);
|
||||
PreviewKeyDown += RegisterKeyBindingDialog_PreviewKeyDown;
|
||||
@@ -65,23 +77,13 @@ namespace FluentTerminal.App.Dialogs
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public async Task<KeyBinding> CreateKeyBinding()
|
||||
public Task<KeyBinding> CreateKeyBinding()
|
||||
{
|
||||
var result = await ShowAsync();
|
||||
|
||||
if (result == ContentDialogResult.Primary)
|
||||
{
|
||||
return new KeyBinding
|
||||
{
|
||||
Alt = Alt,
|
||||
Ctrl = Ctrl,
|
||||
Key = Key,
|
||||
Meta = Meta,
|
||||
Shift = Shift
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
return ShowAsync().AsTask()
|
||||
.ContinueWith(
|
||||
t => t.Result == ContentDialogResult.Primary
|
||||
? new KeyBinding {Alt = Alt, Ctrl = Ctrl, Key = Key, Meta = Meta, Shift = Shift}
|
||||
: null, TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
}
|
||||
|
||||
private void OnResetButtonPreviewKeyDown(object sender, Windows.UI.Xaml.Input.KeyRoutedEventArgs e)
|
||||
@@ -132,6 +134,11 @@ namespace FluentTerminal.App.Dialogs
|
||||
|
||||
private void Set<T>(ref T field, T value, [CallerMemberName]string propertyName = "")
|
||||
{
|
||||
if (field?.Equals(value) ?? value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
field = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonClick="OnPrimaryButtonClick"
|
||||
PrimaryButtonText="Button1"
|
||||
SecondaryButtonClick="OnSecondaryButtonClick"
|
||||
SecondaryButtonText="Button2"
|
||||
mc:Ignorable="d">
|
||||
<ContentDialog.Resources>
|
||||
@@ -33,6 +34,7 @@
|
||||
Margin="4,8,4,0"
|
||||
AutoMaximizeSuggestionArea="True"
|
||||
ItemsSource="{x:Bind ViewModel.Commands, Mode=OneWay}"
|
||||
KeyDown="CommandTextBox_OnKeyDown"
|
||||
KeyUp="CommandTextBox_OnKeyUp"
|
||||
PlaceholderText="Enter a command (e.g. 'ssh username@host')"
|
||||
PreviewKeyUp="CommandTextBox_OnPreviewKeyUp"
|
||||
@@ -43,7 +45,14 @@
|
||||
TextChanged="CommandTextBox_OnTextChanged">
|
||||
<AutoSuggestBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="viewModels:CommandItemViewModel">
|
||||
<ContentControl HorizontalAlignment="Stretch" Content="{x:Bind RichTextBlock, Mode=OneWay}" />
|
||||
<Grid HorizontalAlignment="Stretch">
|
||||
<ContentControl HorizontalAlignment="Stretch" Content="{x:Bind RichTextBlock, Mode=OneWay}" />
|
||||
<!-- I've had to create a transparent overlay over RichTextBox in order to make mouse selection work. Weird but true. -->
|
||||
<Grid
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="Transparent" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</AutoSuggestBox.ItemTemplate>
|
||||
|
||||
@@ -73,39 +82,16 @@
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Row -->
|
||||
<TextBlock
|
||||
x:Uid="LineEndingModeOnPaste"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="0,8,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Line ending on paste:" />
|
||||
<ComboBox
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="0,8,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{x:Bind ViewModel.LineEndingStyles}"
|
||||
SelectedItem="{x:Bind ViewModel.LineEndingTranslation, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="enums:LineEndingStyle">
|
||||
<TextBlock Text="{x:Bind Converter={StaticResource I18NConverter}, ConverterParameter=LineEndingStyle}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<!-- Row -->
|
||||
<TextBlock
|
||||
x:Uid="SshInfoDialog_TabColor"
|
||||
Grid.Row="1"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="0,8,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Tab color:" />
|
||||
<ComboBox
|
||||
Grid.Row="1"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="0,8,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
@@ -116,13 +102,13 @@
|
||||
<!-- Row -->
|
||||
<TextBlock
|
||||
x:Uid="Theme"
|
||||
Grid.Row="2"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0,8,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Theme:" />
|
||||
<ComboBox
|
||||
Grid.Row="2"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="0,8,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
@@ -132,13 +118,22 @@
|
||||
|
||||
<!-- Row -->
|
||||
<CheckBox
|
||||
Grid.Row="3"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,8,0,0"
|
||||
Content="{x:Bind Converter={StaticResource I18NConverter}, ConverterParameter=UseConPty}"
|
||||
IsChecked="{x:Bind ViewModel.UseConPty, Mode=TwoWay}" />
|
||||
|
||||
<!-- Row -->
|
||||
<CheckBox
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,8,0,0"
|
||||
Content="{x:Bind Converter={StaticResource I18NConverter}, ConverterParameter=UseBuffer}"
|
||||
IsChecked="{x:Bind ViewModel.UseBuffer, Mode=TwoWay}" />
|
||||
|
||||
</Grid>
|
||||
</controls:Expander>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ using FluentTerminal.App.Utilities;
|
||||
using FluentTerminal.App.ViewModels.Profiles;
|
||||
using FluentTerminal.Models;
|
||||
using System.Linq;
|
||||
using Windows.Foundation;
|
||||
using Windows.System;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using FluentTerminal.App.ViewModels;
|
||||
@@ -27,21 +28,27 @@ namespace FluentTerminal.App.Dialogs
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IApplicationView _applicationView;
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
private readonly IApplicationDataContainer _historyContainer;
|
||||
private readonly ICommandHistoryService _historyService;
|
||||
|
||||
private ExecutedCommand _lastChosenCommand;
|
||||
private IAsyncOperation<ContentDialogResult> _showDialogOperation;
|
||||
private ContentDialogResult _dialogResult = ContentDialogResult.None;
|
||||
|
||||
private CommandItemViewModel _lastChosenCommand;
|
||||
|
||||
// TODO: The following field is for hacking strange behavior that deletes command text after Tab selection. Consider finding a better fix.
|
||||
private CommandItemViewModel _tabSelectedCommand;
|
||||
|
||||
public CommandProfileProviderViewModel ViewModel { get; private set; }
|
||||
|
||||
public IAsyncCommand SaveLinkCommand { get; }
|
||||
|
||||
public CustomCommandDialog(ISettingsService settingsService, IApplicationView applicationView,
|
||||
ITrayProcessCommunicationService trayProcessCommunicationService, ApplicationDataContainers containers)
|
||||
ITrayProcessCommunicationService trayProcessCommunicationService, ICommandHistoryService historyService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_applicationView = applicationView;
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
_historyContainer = containers.HistoryContainer;
|
||||
_historyService = historyService;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
@@ -65,7 +72,11 @@ namespace FluentTerminal.App.Dialogs
|
||||
|
||||
var error = await ViewModel.AcceptChangesAsync();
|
||||
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
if (string.IsNullOrEmpty(error))
|
||||
{
|
||||
_dialogResult = ContentDialogResult.Primary;
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Cancel = true;
|
||||
|
||||
@@ -77,6 +88,11 @@ namespace FluentTerminal.App.Dialogs
|
||||
deferral.Complete();
|
||||
}
|
||||
|
||||
private void OnSecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
_dialogResult = ContentDialogResult.Secondary;
|
||||
}
|
||||
|
||||
private async Task SaveLink()
|
||||
{
|
||||
var link = await ViewModel.GetUrlAsync();
|
||||
@@ -117,11 +133,19 @@ namespace FluentTerminal.App.Dialogs
|
||||
{
|
||||
ViewModel.SetFilter(sender.Text.Trim());
|
||||
}
|
||||
// TODO: Else branch added for Tab-selection hack mentioned above.
|
||||
else if (_tabSelectedCommand != null)
|
||||
{
|
||||
ViewModel.Command = _tabSelectedCommand.ExecutedCommand.Value;
|
||||
CommandSelected(_tabSelectedCommand);
|
||||
|
||||
_tabSelectedCommand = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void CommandTextBox_OnSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
|
||||
{
|
||||
_lastChosenCommand = (args.SelectedItem as CommandItemViewModel)?.ExecutedCommand;
|
||||
_lastChosenCommand = args.SelectedItem as CommandItemViewModel;
|
||||
}
|
||||
|
||||
private void CommandTextBox_OnQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
||||
@@ -130,34 +154,53 @@ namespace FluentTerminal.App.Dialogs
|
||||
|
||||
if (args.ChosenSuggestion is CommandItemViewModel commandItem)
|
||||
{
|
||||
var executedCommand = ViewModel.Commands
|
||||
.FirstOrDefault(c =>
|
||||
c.ExecutedCommand.Value.Equals(commandItem.ExecutedCommand.Value.ToString(),
|
||||
StringComparison.OrdinalIgnoreCase))?.ExecutedCommand;
|
||||
CommandSelected(commandItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (executedCommand != null)
|
||||
{
|
||||
ViewModel.SetProfile(executedCommand.ShellProfile.Clone());
|
||||
}
|
||||
private void CommandSelected(CommandItemViewModel commandItem)
|
||||
{
|
||||
var executedCommand = ViewModel.Commands.FirstOrDefault(c =>
|
||||
c.ExecutedCommand.Value.Equals(commandItem.ExecutedCommand.Value,
|
||||
StringComparison.OrdinalIgnoreCase))?.ExecutedCommand;
|
||||
|
||||
if (executedCommand != null)
|
||||
{
|
||||
ViewModel.SetProfile(executedCommand.ShellProfile.Clone());
|
||||
}
|
||||
}
|
||||
|
||||
private void CommandTextBox_OnPreviewKeyUp(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
var command = _lastChosenCommand;
|
||||
switch (e.Key)
|
||||
{
|
||||
case VirtualKey.Delete:
|
||||
|
||||
if (e.Key == VirtualKey.Delete && !ViewModel.IsProfileCommand(command))
|
||||
{
|
||||
ViewModel.RemoveCommand(command);
|
||||
e.Handled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Handled = false;
|
||||
if (_lastChosenCommand?.ExecutedCommand is { } command)
|
||||
{
|
||||
if (!ViewModel.IsProfileCommand(command))
|
||||
{
|
||||
ViewModel.RemoveCommand(command);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Handled = false;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
default:
|
||||
|
||||
e.Handled = false;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void CommandTextBox_OnKeyUp(object sender, KeyRoutedEventArgs e)
|
||||
private async void CommandTextBox_OnKeyUp(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
@@ -169,7 +212,7 @@ namespace FluentTerminal.App.Dialogs
|
||||
CommandTextBox.IsSuggestionListOpen = true;
|
||||
}
|
||||
|
||||
break;
|
||||
return;
|
||||
|
||||
case VirtualKey.Enter:
|
||||
|
||||
@@ -179,48 +222,56 @@ namespace FluentTerminal.App.Dialogs
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Try to find a better way to handle [Enter] on auto-complete field.
|
||||
// Weird way to move the focus to the primary button...
|
||||
var error = await ViewModel.AcceptChangesAsync();
|
||||
|
||||
if (!(FocusManager.FindLastFocusableElement(this) is Control secondaryButton) ||
|
||||
!secondaryButton.Name.Equals("SecondaryButton"))
|
||||
if (string.IsNullOrEmpty(error))
|
||||
{
|
||||
return;
|
||||
_dialogResult = ContentDialogResult.Primary;
|
||||
|
||||
_showDialogOperation.Cancel();
|
||||
}
|
||||
|
||||
secondaryButton.Focus(FocusState.Programmatic);
|
||||
|
||||
var options = new FindNextElementOptions
|
||||
else
|
||||
{
|
||||
SearchRoot = this,
|
||||
XYFocusNavigationStrategyOverride = XYFocusNavigationStrategyOverride.Projection
|
||||
};
|
||||
|
||||
if (FocusManager.FindNextElement(FocusNavigationDirection.Left, options) is Control primaryButton)
|
||||
{
|
||||
primaryButton.Focus(FocusState.Programmatic);
|
||||
await new MessageDialog(error, I18N.Translate("InvalidInput")).ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ShellProfile> GetCustomCommandAsync(ShellProfile input = null)
|
||||
private void CommandTextBox_OnKeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
_tabSelectedCommand = null;
|
||||
|
||||
if (e.Key == VirtualKey.Tab)
|
||||
{
|
||||
_tabSelectedCommand = _lastChosenCommand;
|
||||
|
||||
if (_lastChosenCommand != null)
|
||||
{
|
||||
ViewModel.Command = _lastChosenCommand.ExecutedCommand.Value;
|
||||
CommandSelected(_lastChosenCommand);
|
||||
}
|
||||
}
|
||||
|
||||
e.Handled = false;
|
||||
}
|
||||
|
||||
public Task<ShellProfile> GetCustomCommandAsync(ShellProfile input = null)
|
||||
{
|
||||
ViewModel = new CommandProfileProviderViewModel(_settingsService, _applicationView,
|
||||
_trayProcessCommunicationService, _historyContainer, input);
|
||||
_trayProcessCommunicationService, _historyService, input);
|
||||
|
||||
SetupFocus();
|
||||
|
||||
if (await ShowAsync() != ContentDialogResult.Primary)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
_showDialogOperation = ShowAsync();
|
||||
|
||||
ViewModel.Model.Tag = new DelayedHistorySaver(ViewModel);
|
||||
|
||||
return ViewModel.Model;
|
||||
return _showDialogOperation.AsTask().ContinueWith(t =>
|
||||
(t.Status == TaskStatus.Canceled || t.Status == TaskStatus.RanToCompletion) &&
|
||||
_dialogResult == ContentDialogResult.Primary
|
||||
? ViewModel.Model
|
||||
: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,12 @@ using FluentTerminal.App.Utilities;
|
||||
|
||||
namespace FluentTerminal.App.Dialogs
|
||||
{
|
||||
// ReSharper disable once RedundantExtendsListEntry
|
||||
public sealed partial class InputDialog : ContentDialog, IInputDialog
|
||||
{
|
||||
|
||||
private bool EnterPressed = false;
|
||||
// ReSharper disable once RedundantDefaultMemberInitializer
|
||||
private bool _enterPressed = false;
|
||||
public string Input { get; private set; }
|
||||
|
||||
public InputDialog(ISettingsService settingsService)
|
||||
@@ -25,14 +27,11 @@ namespace FluentTerminal.App.Dialogs
|
||||
RequestedTheme = ContrastHelper.GetIdealThemeForBackgroundColor(currentTheme.Colors.Background);
|
||||
}
|
||||
|
||||
public async Task<string> GetInput()
|
||||
public Task<string> GetInput()
|
||||
{
|
||||
var result = await ShowAsync();
|
||||
if (result == ContentDialogResult.Primary || EnterPressed)
|
||||
{
|
||||
return Input;
|
||||
}
|
||||
return null;
|
||||
return ShowAsync().AsTask()
|
||||
.ContinueWith(t => t.Result == ContentDialogResult.Primary || _enterPressed ? Input : null,
|
||||
TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
}
|
||||
|
||||
public void SetTitle(string title)
|
||||
@@ -44,7 +43,7 @@ namespace FluentTerminal.App.Dialogs
|
||||
{
|
||||
if (e.Key == VirtualKey.Enter)
|
||||
{
|
||||
EnterPressed = true;
|
||||
_enterPressed = true;
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<ContentDialog
|
||||
x:Class="FluentTerminal.App.Dialogs.ShellProfileSelectionDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
x:Uid="SelectProfileUI"
|
||||
Title="Select Profile"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
PrimaryButtonText="Start"
|
||||
mc:Ignorable="d">
|
||||
<ComboBox
|
||||
HorizontalAlignment="Stretch"
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{x:Bind Profiles}"
|
||||
SelectedItem="{x:Bind SelectedProfile, Mode=TwoWay}" />
|
||||
</ContentDialog>
|
||||
@@ -1,40 +0,0 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Dialogs;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.App.Utilities;
|
||||
using FluentTerminal.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace FluentTerminal.App.Dialogs
|
||||
{
|
||||
public sealed partial class ShellProfileSelectionDialog : ContentDialog, IShellProfileSelectionDialog
|
||||
{
|
||||
public IEnumerable<ShellProfile> Profiles { get; }
|
||||
|
||||
public ShellProfile SelectedProfile { get; private set; }
|
||||
|
||||
public ShellProfileSelectionDialog(ISettingsService settingsService)
|
||||
{
|
||||
Profiles = settingsService.GetShellProfiles();
|
||||
SelectedProfile = Profiles.First();
|
||||
this.InitializeComponent();
|
||||
SecondaryButtonText = I18N.Translate("Cancel");
|
||||
var currentTheme = settingsService.GetCurrentTheme();
|
||||
RequestedTheme = ContrastHelper.GetIdealThemeForBackgroundColor(currentTheme.Colors.Background);
|
||||
}
|
||||
|
||||
public async Task<ShellProfile> SelectProfile()
|
||||
{
|
||||
var result = await ShowAsync();
|
||||
if (result == ContentDialogResult.Primary)
|
||||
{
|
||||
return SelectedProfile;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,7 @@
|
||||
Margin="0,8,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Mosh ports:"
|
||||
Visibility="{x:Bind ViewModel.UseMosh, Converter={StaticResource TrueToVisibleConverter}}" />
|
||||
Visibility="{x:Bind ViewModel.UseMosh, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
|
||||
<TextBox
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
@@ -148,14 +148,14 @@
|
||||
VerticalAlignment="Center"
|
||||
BeforeTextChanging="Port_OnBeforeTextChanging"
|
||||
Text="{x:Bind ViewModel.MoshPortFrom, Mode=TwoWay}"
|
||||
Visibility="{x:Bind ViewModel.UseMosh, Converter={StaticResource TrueToVisibleConverter}}" />
|
||||
Visibility="{x:Bind ViewModel.UseMosh, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Margin="2,8,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text=":"
|
||||
Visibility="{x:Bind ViewModel.UseMosh, Converter={StaticResource TrueToVisibleConverter}}" />
|
||||
Visibility="{x:Bind ViewModel.UseMosh, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
|
||||
<TextBox
|
||||
Grid.Row="0"
|
||||
Grid.Column="3"
|
||||
@@ -163,42 +163,18 @@
|
||||
VerticalAlignment="Center"
|
||||
BeforeTextChanging="Port_OnBeforeTextChanging"
|
||||
Text="{x:Bind ViewModel.MoshPortTo, Mode=TwoWay}"
|
||||
Visibility="{x:Bind ViewModel.UseMosh, Converter={StaticResource TrueToVisibleConverter}}" />
|
||||
|
||||
<!-- Row -->
|
||||
<TextBlock
|
||||
x:Uid="LineEndingModeOnPaste"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0,8,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Line ending on paste:" />
|
||||
<ComboBox
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Margin="0,8,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{x:Bind ViewModel.LineEndingStyles}"
|
||||
SelectedItem="{x:Bind ViewModel.LineEndingTranslation, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="enums:LineEndingStyle">
|
||||
<TextBlock Text="{x:Bind Converter={StaticResource I18NConverter}, ConverterParameter=LineEndingStyle}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
Visibility="{x:Bind ViewModel.UseMosh, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
|
||||
|
||||
<!-- Row -->
|
||||
<TextBlock
|
||||
x:Uid="SshInfoDialog_TabColor"
|
||||
Grid.Row="2"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0,8,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Tab color:" />
|
||||
<ComboBox
|
||||
Grid.Row="2"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Margin="0,8,0,0"
|
||||
@@ -210,13 +186,13 @@
|
||||
<!-- Row -->
|
||||
<TextBlock
|
||||
x:Uid="Theme"
|
||||
Grid.Row="3"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="0,8,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Theme:" />
|
||||
<ComboBox
|
||||
Grid.Row="3"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Margin="0,8,0,0"
|
||||
@@ -227,13 +203,22 @@
|
||||
|
||||
<!-- Row -->
|
||||
<CheckBox
|
||||
Grid.Row="4"
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="4"
|
||||
Margin="0,8,0,0"
|
||||
Content="{x:Bind Converter={StaticResource I18NConverter}, ConverterParameter=UseConPty}"
|
||||
IsChecked="{x:Bind ViewModel.UseConPty, Mode=TwoWay}" />
|
||||
|
||||
<!-- Row -->
|
||||
<CheckBox
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="4"
|
||||
Margin="0,8,0,0"
|
||||
Content="{x:Bind Converter={StaticResource I18NConverter}, ConverterParameter=UseBuffer}"
|
||||
IsChecked="{x:Bind ViewModel.UseBuffer, Mode=TwoWay}" />
|
||||
|
||||
</Grid>
|
||||
</controls:Expander>
|
||||
|
||||
|
||||
@@ -146,17 +146,12 @@ namespace FluentTerminal.App.Dialogs
|
||||
|
||||
if (string.IsNullOrEmpty(ViewModel.Username))
|
||||
{
|
||||
ViewModel.Username = await _trayProcessCommunicationService.GetUserName();
|
||||
ViewModel.Username = await _trayProcessCommunicationService.GetUserNameAsync();
|
||||
}
|
||||
|
||||
SetupFocus();
|
||||
|
||||
if (await ShowAsync() != ContentDialogResult.Primary)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (SshProfile)ViewModel.Model;
|
||||
return await ShowAsync() == ContentDialogResult.Primary ? (SshProfile) ViewModel.Model : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<ContentDialog
|
||||
x:Class="FluentTerminal.App.Dialogs.SshProfileSelectionDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
x:Uid="SelectSshProfileUI"
|
||||
Title="Select SSH Profile"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonText="Select"
|
||||
SecondaryButtonText="Cancel"
|
||||
mc:Ignorable="d">
|
||||
<ComboBox
|
||||
HorizontalAlignment="Stretch"
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{x:Bind Profiles}"
|
||||
SelectedItem="{x:Bind SelectedProfile, Mode=TwoWay}" />
|
||||
</ContentDialog>
|
||||
@@ -1,54 +0,0 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Dialogs;
|
||||
using FluentTerminal.App.Utilities;
|
||||
using FluentTerminal.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace FluentTerminal.App.Dialogs
|
||||
{
|
||||
public sealed partial class SshProfileSelectionDialog : ContentDialog, ISshProfileSelectionDialog
|
||||
{
|
||||
private IDialogService _dialogService;
|
||||
|
||||
public ObservableCollection<SshProfile> Profiles { get; }
|
||||
|
||||
public SshProfile SelectedProfile { get; private set; }
|
||||
|
||||
public SshProfileSelectionDialog(ISettingsService settingsService, IDialogService dialogService)
|
||||
{
|
||||
_dialogService = dialogService;
|
||||
Profiles = new ObservableCollection<SshProfile>(settingsService.GetSshProfiles().OrderBy(p => p.Name));
|
||||
|
||||
var selectedProfile = settingsService.GetDefaultSshProfile();
|
||||
|
||||
if (selectedProfile != null)
|
||||
{
|
||||
selectedProfile = Profiles.FirstOrDefault(p => p.Id.Equals(selectedProfile.Id));
|
||||
}
|
||||
|
||||
SelectedProfile = selectedProfile ?? Profiles.FirstOrDefault();
|
||||
|
||||
this.InitializeComponent();
|
||||
var currentTheme = settingsService.GetCurrentTheme();
|
||||
RequestedTheme = ContrastHelper.GetIdealThemeForBackgroundColor(currentTheme.Colors.Background);
|
||||
}
|
||||
|
||||
public async Task<SshProfile> SelectProfile()
|
||||
{
|
||||
if (!Profiles.Any())
|
||||
{
|
||||
await _dialogService.ShowMessageDialogAsnyc("SSH Profile Selection", "There are no saved SSH Profiles.",
|
||||
DialogButton.OK);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await ShowAsync() == ContentDialogResult.Primary ? SelectedProfile : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>FluentTerminal.App</RootNamespace>
|
||||
<AssemblyName>FluentTerminal.App</AssemblyName>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<DefaultLanguage>en</DefaultLanguage>
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.18362.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.16299.0</TargetPlatformMinVersion>
|
||||
@@ -28,7 +28,7 @@
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<LangVersion>7.1</LangVersion>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
@@ -41,7 +41,7 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
|
||||
<LangVersion>7.1</LangVersion>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@@ -53,6 +53,7 @@
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
|
||||
<OutputPath>bin\ARM\Release\</OutputPath>
|
||||
@@ -65,6 +66,7 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@@ -76,7 +78,7 @@
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<LangVersion>7.1</LangVersion>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
@@ -89,15 +91,15 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
|
||||
<LangVersion>7.1</LangVersion>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Actions\FocusAction.cs" />
|
||||
<Compile Include="Adapters\ApplicationViewAdapter.cs" />
|
||||
<Compile Include="Adapters\AppServiceConnectionAdapter.cs" />
|
||||
<Compile Include="Adapters\DispatcherTimerAdapter.cs" />
|
||||
<Compile Include="App.xaml.cs">
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@@ -106,9 +108,14 @@
|
||||
<Compile Include="CommandLineArguments\RunVerb.cs" />
|
||||
<Compile Include="CommandLineArguments\SettingsVerb.cs" />
|
||||
<Compile Include="CommandLineArguments\Target.cs" />
|
||||
<Compile Include="Converters\MenuViewModelToFlyoutMenuConverter.cs" />
|
||||
<Compile Include="Converters\BackgroundToApplicationThemeConverter.cs" />
|
||||
<Compile Include="Converters\BooleanNegationConverter.cs" />
|
||||
<Compile Include="Converters\ColorResourceKeyFallbackConverter.cs" />
|
||||
<Compile Include="Converters\I18NConverter.cs" />
|
||||
<Compile Include="Converters\IconConverter.cs" />
|
||||
<Compile Include="Converters\MenuItemViewModelBaseToMenuFlayoutItemBaseConverter.cs" />
|
||||
<Compile Include="Converters\NullToCollapsedConverter.cs" />
|
||||
<Compile Include="Converters\TabColorFallbackConverter.cs" />
|
||||
<Compile Include="Converters\EnumValueToVisibiltyConverter.cs" />
|
||||
<Compile Include="Converters\FalseToVisibleConverter.cs" />
|
||||
@@ -126,6 +133,10 @@
|
||||
<Compile Include="Dialogs\AboutDialog.xaml.cs">
|
||||
<DependentUpon>AboutDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Services\AcceleratorKeyValidator.cs" />
|
||||
<Compile Include="Services\CommandHistoryService.cs" />
|
||||
<Compile Include="Utilities\DelayedAction.cs" />
|
||||
<Compile Include="Utilities\DispatcherExtensions.cs" />
|
||||
<Compile Include="Utilities\InteractiveSurface.cs" />
|
||||
<Compile Include="ViewModels\CommandItemViewModel.cs" />
|
||||
<Compile Include="Dialogs\CustomCommandDialog.xaml.cs">
|
||||
@@ -134,26 +145,19 @@
|
||||
<Compile Include="Dialogs\InputDialog.xaml.cs">
|
||||
<DependentUpon>InputDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Dialogs\ShellProfileSelectionDialog.xaml.cs">
|
||||
<DependentUpon>ShellProfileSelectionDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Dialogs\SshInfoDialog.xaml.cs">
|
||||
<DependentUpon>SshInfoDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Dialogs\SshProfileSelectionDialog.xaml.cs">
|
||||
<DependentUpon>SshProfileSelectionDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Services\ApplicationLanguageService.cs" />
|
||||
<Compile Include="Services\ClipboardService.cs" />
|
||||
<Compile Include="Services\FileSystemService.cs" />
|
||||
<Compile Include="Services\ImageFileSystemService.cs" />
|
||||
<Compile Include="Services\StartupTaskService.cs" />
|
||||
<Compile Include="Services\SystemFontService.cs" />
|
||||
<Compile Include="Utilities\ColorExtensions.cs" />
|
||||
<Compile Include="Utilities\ContrastHelper.cs" />
|
||||
<Compile Include="Utilities\DebouncedAction.cs" />
|
||||
<Compile Include="Utilities\JumpListHelper.cs" />
|
||||
<Compile Include="ViewModels\CommandProfileProviderViewModel.cs" />
|
||||
<Compile Include="ViewModels\DelayedHistorySaver.cs" />
|
||||
<Compile Include="Views\BooleanTemplateSelector.cs" />
|
||||
<Compile Include="Views\EnvironmentVariablesView.xaml.cs">
|
||||
<DependentUpon>EnvironmentVariablesView.xaml</DependentUpon>
|
||||
@@ -168,6 +172,7 @@
|
||||
<Compile Include="Dialogs\CreateKeyBindingDialog.xaml.cs">
|
||||
<DependentUpon>CreateKeyBindingDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\MenuExtension.cs" />
|
||||
<Compile Include="Views\OverlayView.xaml.cs">
|
||||
<DependentUpon>OverlayView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@@ -236,11 +241,14 @@
|
||||
<PRIResource Include="Strings\fr\Resources.resw" />
|
||||
<PRIResource Include="Strings\es\Resources.resw" />
|
||||
<PRIResource Include="Strings\de\Resources.resw" />
|
||||
<PRIResource Include="Strings\zh-CN\Resources.resw" />
|
||||
<PRIResource Include="Strings\zh-Hant\Resources.resw" />
|
||||
<PRIResource Include="Strings\en\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Properties\Default.rd.xml" />
|
||||
<PRIResource Include="Strings\it\Resources.resw" />
|
||||
<PRIResource Include="Strings\nl\Resources.resw" />
|
||||
<PRIResource Include="Strings\pl\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="App.xaml">
|
||||
@@ -259,18 +267,10 @@
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Dialogs\ShellProfileSelectionDialog.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Dialogs\SshInfoDialog.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Dialogs\SshProfileSelectionDialog.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\EnvironmentVariablesView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
@@ -351,31 +351,31 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac">
|
||||
<Version>4.9.4</Version>
|
||||
<Version>5.1.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CommandLineParser">
|
||||
<Version>2.6.0</Version>
|
||||
<Version>2.7.82</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
||||
<Version>6.2.9</Version>
|
||||
<Version>6.2.10</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Toolkit.Uwp.UI">
|
||||
<Version>5.1.1</Version>
|
||||
<Version>6.0.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls">
|
||||
<Version>5.1.1</Version>
|
||||
<Version>6.0.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.UI.Xaml">
|
||||
<Version>2.2.190830001</Version>
|
||||
<Version>2.3.200213001</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Uwp.Managed">
|
||||
<Version>2.0.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json">
|
||||
<Version>12.0.2</Version>
|
||||
<Version>12.0.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Win2D.uwp">
|
||||
<Version>1.23.0</Version>
|
||||
<Version>1.24.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -401,6 +401,42 @@
|
||||
<Name>Windows Desktop Extensions for the UWP</Name>
|
||||
</SDKReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Strings\ro\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Strings\hi\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Strings\pt-BR\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Strings\ar-iq\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Strings\he\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Strings\az\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Strings\ja\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Strings\ko\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Strings\uk\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Strings\ar\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Strings\id\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Strings\zh-Hans\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' ">
|
||||
<VisualStudioVersion>14.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using Windows.System;
|
||||
using FluentTerminal.Models.Enums;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public class AcceleratorKeyValidator : IAcceleratorKeyValidator
|
||||
{
|
||||
public bool Valid(int key) => Enum.IsDefined(typeof(VirtualKey), key);
|
||||
|
||||
public bool Valid(ExtendedVirtualKey key) => Valid((int) key);
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,27 @@ namespace FluentTerminal.App.Services
|
||||
private readonly Dictionary<string, string> _languages = new Dictionary<string, string>
|
||||
{
|
||||
[""] = "System default",
|
||||
["zh-Hans-CN"] = "Chinese (Traditional)",
|
||||
["ar"] = "العربية",
|
||||
["ar-IQ"] = "اللهجة العراقية",
|
||||
["az"] = "azərbaycan dili",
|
||||
["de"] = "Deutsch",
|
||||
["en"] = "English",
|
||||
["es"] = "Español",
|
||||
["fr"] = "Français",
|
||||
["ru"] = "Pусский"
|
||||
["he"] = "עברית",
|
||||
["hi"] = "हिन्दुस्तानी",
|
||||
["id"] = "Bahasa Indonesia",
|
||||
["it"] = "Italiano",
|
||||
["ja"] = "日本語",
|
||||
["ko"] = "한국어",
|
||||
["nl"] = "Nederlands",
|
||||
["pl"] = "Polski",
|
||||
["pt-BR"] = "Português-Brasil",
|
||||
["ro"] = "Română",
|
||||
["ru"] = "Pусский",
|
||||
["uk"] = "Українська",
|
||||
["zh-Hans"] = "繁體中文",
|
||||
["zh-Hant"] = "简体中文"
|
||||
};
|
||||
|
||||
public IEnumerable<string> Languages => _languages.Values;
|
||||
@@ -32,6 +47,10 @@ namespace FluentTerminal.App.Services
|
||||
|
||||
public string GetCurrentLanguage()
|
||||
{
|
||||
if (!_languages.ContainsKey(ApplicationLanguages.PrimaryLanguageOverride))
|
||||
{
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "en";
|
||||
}
|
||||
return _languages[ApplicationLanguages.PrimaryLanguageOverride];
|
||||
}
|
||||
}
|
||||
|
||||
Loaded 100 of 317 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user