feature: support to use input control in custom action

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo 2025-06-25 16:28:54 +08:00
parent a8803ca188
commit 676785f8b1
No known key found for this signature in database
26 changed files with 659 additions and 56 deletions

View file

@ -1,4 +1,5 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.Models
{
@ -9,6 +10,52 @@ namespace SourceGit.Models
Branch,
}
public enum CustomActionControlType
{
TextBox = 0,
PathSelector,
CheckBox,
}
public class CustomActionControl : ObservableObject
{
public CustomActionControlType Type
{
get => _type;
set => SetProperty(ref _type, value);
}
public string Label
{
get => _label;
set => SetProperty(ref _label, value);
}
public string Description
{
get => _description;
set => SetProperty(ref _description, value);
}
public string StringValue
{
get => _stringValue;
set => SetProperty(ref _stringValue, value);
}
public bool BoolValue
{
get => _boolValue;
set => SetProperty(ref _boolValue, value);
}
private CustomActionControlType _type = CustomActionControlType.TextBox;
private string _label = string.Empty;
private string _description = string.Empty;
private string _stringValue = string.Empty;
private bool _boolValue = false;
}
public class CustomAction : ObservableObject
{
public string Name
@ -35,6 +82,12 @@ namespace SourceGit.Models
set => SetProperty(ref _arguments, value);
}
public AvaloniaList<CustomActionControl> Controls
{
get;
set;
} = [];
public bool WaitForExit
{
get => _waitForExit;

View file

@ -93,6 +93,7 @@
<StreamGeometry x:Key="Icons.OpenWith">M683 409v204L1024 308 683 0v191c-413 0-427 526-427 526c117-229 203-307 427-307zm85 492H102V327h153s38-63 114-122H51c-28 0-51 27-51 61v697c0 34 23 61 51 61h768c28 0 51-27 51-61V614l-102 100v187z</StreamGeometry>
<StreamGeometry x:Key="Icons.OrderByName">M841 627A43 43 0 00811 555h-299v85h196l-183 183A43 43 0 00555 896h299v-85h-196l183-183zM299 170H213v512H85l171 171 171-171H299zM725 128h-85c-18 0-34 11-40 28l-117 313h91L606 384h154l32 85h91l-117-313A43 43 0 00725 128zm-88 171 32-85h26l32 85h-90z</StreamGeometry>
<StreamGeometry x:Key="Icons.OrderByTime">M512 0a512 512 0 01512 512 57 57 0 01-114 0 398 398 0 10-398 398 57 57 0 010 114A512 512 0 01512 0zm367 600 121 120a57 57 0 01-80 81l-40-40V967a57 57 0 01-50 57l-7 0a57 57 0 01-57-57v-205l-40 40a57 57 0 01-75 5l-5-5a57 57 0 01-0-80l120-121a80 80 0 01113-0zM512 272a57 57 0 0157 57V499h114a57 57 0 0156 50L740 556a57 57 0 01-57 57H512a57 57 0 01-57-57v-228a57 57 0 0150-57L512 272z</StreamGeometry>
<StreamGeometry x:Key="Icons.Parameter">M834 0H190C85 0 0 85 0 189v646c0 104 85 189 189 189h645c104 0 189-85 189-189V189C1024 85 939 0 834 0zM658 748c-25 29-62 47-111 54v54h-66v-56c-38-4-72-19-101-44-29-26-43-71-43-135v-28h144v35c0 39 1 63 4 72 3 9 10 14 22 14 10 0 17-3 22-10 5-7 7-16 7-29 0-32-2-55-7-69-5-14-20-29-46-45-44-28-74-48-90-61-16-13-29-31-41-55-12-24-17-50-17-80 0-43 12-77 37-101 24-24 61-40 110-45v-46h66v46c44 6 78 21 100 45 22 24 33 57 33 100 0 6-0 15-1 27H535v-24c0-25-2-42-5-50-3-8-10-12-21-12-9 0-15 3-20 10-4 7-7 17-7 30 0 23 5 38 14 47 9 9 35 27 78 53 37 22 62 39 75 51 13 12 25 28 34 50 9 22 14 48 14 80 0 51-12 92-37 121z</StreamGeometry>
<StreamGeometry x:Key="Icons.Password">M640 96c-158 0-288 130-288 288 0 17 3 31 5 46L105 681 96 691V928h224v-96h96v-96h96v-95c38 18 82 31 128 31 158 0 288-130 288-288s-130-288-288-288zm0 64c123 0 224 101 224 224s-101 224-224 224a235 235 0 01-109-28l-8-4H448v96h-96v96H256v96H160v-146l253-254 12-11-3-17C419 417 416 400 416 384c0-123 101-224 224-224zm64 96a64 64 0 100 128 64 64 0 100-128z</StreamGeometry>
<StreamGeometry x:Key="Icons.Paste">M544 85c49 0 90 37 95 85h75a96 96 0 0196 89L811 267a32 32 0 01-28 32L779 299a32 32 0 01-32-28L747 267a32 32 0 00-28-32L715 235h-91a96 96 0 01-80 42H395c-33 0-62-17-80-42L224 235a32 32 0 00-32 28L192 267v576c0 16 12 30 28 32l4 0h128a32 32 0 0132 28l0 4a32 32 0 01-32 32h-128a96 96 0 01-96-89L128 843V267a96 96 0 0189-96L224 171h75a96 96 0 0195-85h150zm256 256a96 96 0 0196 89l0 7v405a96 96 0 01-89 96L800 939h-277a96 96 0 01-96-89L427 843v-405a96 96 0 0189-96L523 341h277zm-256-192H395a32 32 0 000 64h150a32 32 0 100-64z</StreamGeometry>
<StreamGeometry x:Key="Icons.Pattern">M470 722q-23 3-43 3T384 722v-150l-106 106q-34-26-60-59L324 512H174q-3-23-3-43t3-42h150L218 320q16-20 28-32t32-27L384 367V217q23-4 43-4t43 4v150l106-106q34 26 60 59l-106 107h150q3 22 3 42T680 512h-150l106 107q-16 20-28 32t-32 27l-106-106v150zM0 811q0-36 25-61t61-25 61 25 25 61-25 61-61 25-61-25T0 811z</StreamGeometry>

View file

@ -304,8 +304,6 @@
<x:String x:Key="Text.EditRepositoryNode.Target" xml:space="preserve">Ziel:</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForGroup" xml:space="preserve">Ausgewählte Gruppe bearbeiten</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForRepository" xml:space="preserve">Ausgewähltes Repository bearbeiten</x:String>
<x:String x:Key="Text.ExecuteCustomAction" xml:space="preserve">Führe benutzerdefinierte Aktion aus</x:String>
<x:String x:Key="Text.ExecuteCustomAction.Name" xml:space="preserve">Name der Aktion:</x:String>
<x:String x:Key="Text.Fetch" xml:space="preserve">Fetch</x:String>
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">Alle Remotes fetchen</x:String>
<x:String x:Key="Text.Fetch.Force" xml:space="preserve">Aktiviere '--force' Option</x:String>

View file

@ -162,6 +162,9 @@
<x:String x:Key="Text.Configure.CustomAction.Arguments" xml:space="preserve">Arguments:</x:String>
<x:String x:Key="Text.Configure.CustomAction.Arguments.Tip" xml:space="preserve">${REPO} - Repository's path; ${BRANCH} - Selected branch; ${SHA} - Selected commit's SHA</x:String>
<x:String x:Key="Text.Configure.CustomAction.Executable" xml:space="preserve">Executable File:</x:String>
<x:String x:Key="Text.Configure.CustomAction.InputControls" xml:space="preserve">Input Controls:</x:String>
<x:String x:Key="Text.Configure.CustomAction.InputControls.Edit" xml:space="preserve">Edit</x:String>
<x:String x:Key="Text.Configure.CustomAction.InputControls.Tip" xml:space="preserve">You can use $1, $2 ... in arguments for input control values</x:String>
<x:String x:Key="Text.Configure.CustomAction.Name" xml:space="preserve">Name:</x:String>
<x:String x:Key="Text.Configure.CustomAction.Scope" xml:space="preserve">Scope:</x:String>
<x:String x:Key="Text.Configure.CustomAction.Scope.Branch" xml:space="preserve">Branch</x:String>
@ -195,6 +198,14 @@
<x:String x:Key="Text.Configure.Proxy.Placeholder" xml:space="preserve">HTTP proxy used by this repository</x:String>
<x:String x:Key="Text.Configure.User" xml:space="preserve">User Name</x:String>
<x:String x:Key="Text.Configure.User.Placeholder" xml:space="preserve">User name for this repository</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls" xml:space="preserve">Edit Custom Action Controls</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.CheckedValue" xml:space="preserve">Checked Value:</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.Description" xml:space="preserve">Description:</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.Description.Placeholder" xml:space="preserve">Used as placeholder in TextBox/PathSelector or tooltip in CheckBox</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.DefaultValue" xml:space="preserve">Default:</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.IsFolder" xml:space="preserve">Is Folder:</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.Label" xml:space="preserve">Label:</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.Type" xml:space="preserve">Type:</x:String>
<x:String x:Key="Text.ConfigureWorkspace" xml:space="preserve">Workspaces</x:String>
<x:String x:Key="Text.ConfigureWorkspace.Color" xml:space="preserve">Color</x:String>
<x:String x:Key="Text.ConfigureWorkspace.Name" xml:space="preserve">Name</x:String>
@ -300,8 +311,7 @@
<x:String x:Key="Text.EditRepositoryNode.Target" xml:space="preserve">Target:</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForGroup" xml:space="preserve">Edit Selected Group</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForRepository" xml:space="preserve">Edit Selected Repository</x:String>
<x:String x:Key="Text.ExecuteCustomAction" xml:space="preserve">Run Custom Action</x:String>
<x:String x:Key="Text.ExecuteCustomAction.Name" xml:space="preserve">Action Name:</x:String>
<x:String x:Key="Text.ExecuteCustomAction.SimpleWait" xml:space="preserve">Action is running, please wait...</x:String>
<x:String x:Key="Text.Fetch" xml:space="preserve">Fetch</x:String>
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">Fetch all remotes</x:String>
<x:String x:Key="Text.Fetch.Force" xml:space="preserve">Force override local refs</x:String>

View file

@ -300,8 +300,6 @@
<x:String x:Key="Text.EditRepositoryNode.Target" xml:space="preserve">Destino:</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForGroup" xml:space="preserve">Editar Grupo Seleccionado</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForRepository" xml:space="preserve">Editar Repositorio Seleccionado</x:String>
<x:String x:Key="Text.ExecuteCustomAction" xml:space="preserve">Ejecutar Acción Personalizada</x:String>
<x:String x:Key="Text.ExecuteCustomAction.Name" xml:space="preserve">Nombre de la Acción:</x:String>
<x:String x:Key="Text.Fetch" xml:space="preserve">Fetch</x:String>
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">Fetch todos los remotos</x:String>
<x:String x:Key="Text.Fetch.Force" xml:space="preserve">Utilizar opción '--force'</x:String>

View file

@ -273,8 +273,6 @@
<x:String x:Key="Text.EditRepositoryNode.Target" xml:space="preserve">Cible :</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForGroup" xml:space="preserve">Éditer le groupe sélectionné</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForRepository" xml:space="preserve">Éditer le dépôt sélectionné</x:String>
<x:String x:Key="Text.ExecuteCustomAction" xml:space="preserve">Lancer action personnalisée</x:String>
<x:String x:Key="Text.ExecuteCustomAction.Name" xml:space="preserve">Nom de l'action :</x:String>
<x:String x:Key="Text.Fetch" xml:space="preserve">Fetch</x:String>
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">Fetch toutes les branches distantes</x:String>
<x:String x:Key="Text.Fetch.Force" xml:space="preserve">Outrepasser les vérifications de refs</x:String>

View file

@ -290,8 +290,6 @@
<x:String x:Key="Text.EditRepositoryNode.Target" xml:space="preserve">Destinazione:</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForGroup" xml:space="preserve">Modifica Gruppo Selezionato</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForRepository" xml:space="preserve">Modifica Repository Selezionato</x:String>
<x:String x:Key="Text.ExecuteCustomAction" xml:space="preserve">Esegui Azione Personalizzata</x:String>
<x:String x:Key="Text.ExecuteCustomAction.Name" xml:space="preserve">Nome Azione:</x:String>
<x:String x:Key="Text.Fetch" xml:space="preserve">Recupera</x:String>
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">Recupera da tutti i remoti</x:String>
<x:String x:Key="Text.Fetch.Force" xml:space="preserve">Forza la sovrascrittura dei riferimenti locali</x:String>

View file

@ -272,8 +272,6 @@
<x:String x:Key="Text.EditRepositoryNode.Target" xml:space="preserve">対象:</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForGroup" xml:space="preserve">選択中のグループを編集</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForRepository" xml:space="preserve">選択中のリポジトリを編集</x:String>
<x:String x:Key="Text.ExecuteCustomAction" xml:space="preserve">カスタムアクションを実行</x:String>
<x:String x:Key="Text.ExecuteCustomAction.Name" xml:space="preserve">アクション名:</x:String>
<x:String x:Key="Text.Fetch" xml:space="preserve">フェッチ</x:String>
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">すべてのリモートをフェッチ</x:String>
<x:String x:Key="Text.Fetch.Force" xml:space="preserve">ローカル参照を強制的に上書き</x:String>

View file

@ -247,8 +247,6 @@
<x:String x:Key="Text.EditRepositoryNode.Target" xml:space="preserve">Alvo:</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForGroup" xml:space="preserve">Editar Grupo Selecionado</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForRepository" xml:space="preserve">Editar Repositório Selecionado</x:String>
<x:String x:Key="Text.ExecuteCustomAction" xml:space="preserve">Executar ação customizada</x:String>
<x:String x:Key="Text.ExecuteCustomAction.Name" xml:space="preserve">Nome da ação:</x:String>
<x:String x:Key="Text.Fetch" xml:space="preserve">Buscar</x:String>
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">Buscar todos os remotos</x:String>
<x:String x:Key="Text.Fetch.NoTags" xml:space="preserve">Buscar sem tags</x:String>

View file

@ -301,8 +301,6 @@
<x:String x:Key="Text.EditRepositoryNode.Target" xml:space="preserve">Цель:</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForGroup" xml:space="preserve">Редактировать выбранную группу</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForRepository" xml:space="preserve">Редактировать выбранный репозиторий</x:String>
<x:String x:Key="Text.ExecuteCustomAction" xml:space="preserve">Выполнить пользовательское действие</x:String>
<x:String x:Key="Text.ExecuteCustomAction.Name" xml:space="preserve">Имя действия:</x:String>
<x:String x:Key="Text.Fetch" xml:space="preserve">Извлечь</x:String>
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">Извлечь все внешние репозитории</x:String>
<x:String x:Key="Text.Fetch.Force" xml:space="preserve">Разрешить опцию (--force)</x:String>

View file

@ -272,8 +272,6 @@
<x:String x:Key="Text.EditRepositoryNode.Target" xml:space="preserve">இலக்கு:</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForGroup" xml:space="preserve">தேர்ந்தெடுக்கப்பட்ட குழுவைத் திருத்து</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForRepository" xml:space="preserve">தேர்ந்தெடுக்கப்பட்ட களஞ்சியத்தைத் திருத்து</x:String>
<x:String x:Key="Text.ExecuteCustomAction" xml:space="preserve">தனிப்பயன் செயலை இயக்கு</x:String>
<x:String x:Key="Text.ExecuteCustomAction.Name" xml:space="preserve">செயல் பெயர்:</x:String>
<x:String x:Key="Text.Fetch" xml:space="preserve">பெறு</x:String>
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">எல்லா தொலைகளையும் பெறு</x:String>
<x:String x:Key="Text.Fetch.Force" xml:space="preserve">உள்ளக குறிப்புகளை கட்டாயமாக மீறு</x:String>

View file

@ -277,8 +277,6 @@
<x:String x:Key="Text.EditRepositoryNode.Target" xml:space="preserve">Ціль:</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForGroup" xml:space="preserve">Редагувати вибрану групу</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForRepository" xml:space="preserve">Редагувати вибраний репозиторій</x:String>
<x:String x:Key="Text.ExecuteCustomAction" xml:space="preserve">Виконати спеціальну дію</x:String>
<x:String x:Key="Text.ExecuteCustomAction.Name" xml:space="preserve">Ім'я дії:</x:String>
<x:String x:Key="Text.Fetch" xml:space="preserve">Витягти</x:String>
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">Витягти всі віддалені сховища</x:String>
<x:String x:Key="Text.Fetch.Force" xml:space="preserve">Примусово перезаписати локальні refs</x:String>

View file

@ -166,6 +166,9 @@
<x:String x:Key="Text.Configure.CustomAction.Arguments" xml:space="preserve">命令行参数 </x:String>
<x:String x:Key="Text.Configure.CustomAction.Arguments.Tip" xml:space="preserve">请使用${REPO}代替仓库路径,${BRANCH}代替选中的分支,${SHA}代替提交哈希</x:String>
<x:String x:Key="Text.Configure.CustomAction.Executable" xml:space="preserve">可执行文件路径 </x:String>
<x:String x:Key="Text.Configure.CustomAction.InputControls" xml:space="preserve">输入控件 </x:String>
<x:String x:Key="Text.Configure.CustomAction.InputControls.Edit" xml:space="preserve">编辑</x:String>
<x:String x:Key="Text.Configure.CustomAction.InputControls.Tip" xml:space="preserve">请在命令行参数中使用 $1, $2 等占位符表示输入控件的值</x:String>
<x:String x:Key="Text.Configure.CustomAction.Name" xml:space="preserve">名称 </x:String>
<x:String x:Key="Text.Configure.CustomAction.Scope" xml:space="preserve">作用目标 </x:String>
<x:String x:Key="Text.Configure.CustomAction.Scope.Branch" xml:space="preserve">选中的分支</x:String>
@ -199,6 +202,14 @@
<x:String x:Key="Text.Configure.Proxy.Placeholder" xml:space="preserve">HTTP网络代理</x:String>
<x:String x:Key="Text.Configure.User" xml:space="preserve">用户名</x:String>
<x:String x:Key="Text.Configure.User.Placeholder" xml:space="preserve">应用于本仓库的用户名</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls" xml:space="preserve">编辑自定义操作输入控件</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.CheckedValue" xml:space="preserve">启用时命令行参数 </x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.Description" xml:space="preserve">描述 </x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.Description.Placeholder" xml:space="preserve">TextBox及Path Selector中用作PlaceholderCheckBox中用作ToolTip</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.DefaultValue" xml:space="preserve">默认值 </x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.IsFolder" xml:space="preserve">目标是否是目录 </x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.Label" xml:space="preserve">名称 </x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.Type" xml:space="preserve">类型 </x:String>
<x:String x:Key="Text.ConfigureWorkspace" xml:space="preserve">工作区</x:String>
<x:String x:Key="Text.ConfigureWorkspace.Color" xml:space="preserve">颜色</x:String>
<x:String x:Key="Text.ConfigureWorkspace.Name" xml:space="preserve">名称</x:String>
@ -304,8 +315,7 @@
<x:String x:Key="Text.EditRepositoryNode.Target" xml:space="preserve">目标 </x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForGroup" xml:space="preserve">编辑分组</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForRepository" xml:space="preserve">编辑仓库</x:String>
<x:String x:Key="Text.ExecuteCustomAction" xml:space="preserve">执行自定义操作</x:String>
<x:String x:Key="Text.ExecuteCustomAction.Name" xml:space="preserve">自定义操作 </x:String>
<x:String x:Key="Text.ExecuteCustomAction.SimpleWait" xml:space="preserve">操作正在进行中,请耐心等待...</x:String>
<x:String x:Key="Text.Fetch" xml:space="preserve">拉取(fetch)</x:String>
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">拉取所有的远程仓库</x:String>
<x:String x:Key="Text.Fetch.Force" xml:space="preserve">强制覆盖本地REFs</x:String>

View file

@ -166,6 +166,9 @@
<x:String x:Key="Text.Configure.CustomAction.Arguments" xml:space="preserve">指令參數:</x:String>
<x:String x:Key="Text.Configure.CustomAction.Arguments.Tip" xml:space="preserve">使用 ${REPO} 表示存放庫路徑、${BRANCH} 表示所選的分支、${SHA} 表示所選的提交編號</x:String>
<x:String x:Key="Text.Configure.CustomAction.Executable" xml:space="preserve">可執行檔案路徑:</x:String>
<x:String x:Key="Text.Configure.CustomAction.InputControls" xml:space="preserve">輸入控件:</x:String>
<x:String x:Key="Text.Configure.CustomAction.InputControls.Edit" xml:space="preserve">編輯</x:String>
<x:String x:Key="Text.Configure.CustomAction.InputControls.Tip" xml:space="preserve">請使用占位符如 $1, $2 來代表輸入控制項的值</x:String>
<x:String x:Key="Text.Configure.CustomAction.Name" xml:space="preserve">名稱:</x:String>
<x:String x:Key="Text.Configure.CustomAction.Scope" xml:space="preserve">執行範圍:</x:String>
<x:String x:Key="Text.Configure.CustomAction.Scope.Branch" xml:space="preserve">選取的分支</x:String>
@ -199,6 +202,14 @@
<x:String x:Key="Text.Configure.Proxy.Placeholder" xml:space="preserve">HTTP 網路代理</x:String>
<x:String x:Key="Text.Configure.User" xml:space="preserve">使用者名稱</x:String>
<x:String x:Key="Text.Configure.User.Placeholder" xml:space="preserve">用於本存放庫的使用者名稱</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls" xml:space="preserve">編輯自訂動作輸入控件</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.CheckedValue" xml:space="preserve">啟用時的指令行參數:</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.Description" xml:space="preserve">描述:</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.Description.Placeholder" xml:space="preserve">在 TextBox/PathSelector 中用作占位符,或在 CheckBox 中用作工具提示</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.DefaultValue" xml:space="preserve">預設值:</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.IsFolder" xml:space="preserve">目標路徑是否為資料夾:</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.Label" xml:space="preserve">名稱:</x:String>
<x:String x:Key="Text.ConfigureCustomActionControls.Type" xml:space="preserve">類型:</x:String>
<x:String x:Key="Text.ConfigureWorkspace" xml:space="preserve">工作區</x:String>
<x:String x:Key="Text.ConfigureWorkspace.Color" xml:space="preserve">顏色</x:String>
<x:String x:Key="Text.ConfigureWorkspace.Name" xml:space="preserve">名稱</x:String>
@ -304,8 +315,7 @@
<x:String x:Key="Text.EditRepositoryNode.Target" xml:space="preserve">目標:</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForGroup" xml:space="preserve">編輯群組</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForRepository" xml:space="preserve">編輯存放庫</x:String>
<x:String x:Key="Text.ExecuteCustomAction" xml:space="preserve">執行自訂動作</x:String>
<x:String x:Key="Text.ExecuteCustomAction.Name" xml:space="preserve">自訂動作:</x:String>
<x:String x:Key="Text.ExecuteCustomAction.SimpleWait" xml:space="preserve">動作正在執行中,請稍候...</x:String>
<x:String x:Key="Text.Fetch" xml:space="preserve">提取 (fetch)</x:String>
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">提取所有的遠端存放庫</x:String>
<x:String x:Key="Text.Fetch.Force" xml:space="preserve">強制覆寫本機 REFs</x:String>

View file

@ -0,0 +1,62 @@
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
{
public class ConfigureCustomActionControls : ObservableObject
{
public AvaloniaList<Models.CustomActionControl> Controls
{
get;
}
public Models.CustomActionControl Edit
{
get => _edit;
set => SetProperty(ref _edit, value);
}
public ConfigureCustomActionControls(AvaloniaList<Models.CustomActionControl> controls)
{
Controls = controls;
}
public void Add()
{
var added = new Models.CustomActionControl() { Type = Models.CustomActionControlType.TextBox };
Controls.Add(added);
Edit = added;
}
public void Remove()
{
if (_edit == null)
return;
Controls.Remove(_edit);
Edit = null;
}
public void MoveUp()
{
if (_edit == null)
return;
var idx = Controls.IndexOf(_edit);
if (idx > 0)
Controls.Move(idx - 1, idx);
}
public void MoveDown()
{
if (_edit == null)
return;
var idx = Controls.IndexOf(_edit);
if (idx < Controls.Count - 1)
Controls.Move(idx + 1, idx);
}
private Models.CustomActionControl _edit;
}
}

View file

@ -1,8 +1,75 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
{
public interface ICustomActionControlParameter
{
string GetValue();
}
public class CustomActionControlTextBox : ICustomActionControlParameter
{
public string Label { get; set; } = string.Empty;
public string Placeholder { get; set; } = string.Empty;
public string Text { get; set; } = string.Empty;
public CustomActionControlTextBox(string label, string placeholder, string defaultValue)
{
Label = label;
Placeholder = placeholder;
Text = defaultValue;
}
public string GetValue() => Text;
}
public class CustomActionControlPathSelector : ObservableObject, ICustomActionControlParameter
{
public string Label { get; set; } = string.Empty;
public string Placeholder { get; set; } = string.Empty;
public bool IsFolder { get; set; } = false;
public string Path
{
get => _path;
set => SetProperty(ref _path, value);
}
public CustomActionControlPathSelector(string label, string placeholder, bool isFolder, string defaultValue)
{
Label = label;
Placeholder = placeholder;
IsFolder = isFolder;
_path = defaultValue;
}
public string GetValue() => _path;
private string _path = string.Empty;
}
public class CustomActionControlCheckBox : ICustomActionControlParameter
{
public string Label { get; set; } = string.Empty;
public string ToolTip { get; set; } = string.Empty;
public string CheckedValue { get; set; } = string.Empty;
public bool IsChecked { get; set; }
public CustomActionControlCheckBox(string label, string tooltip, string checkedValue, bool isChecked)
{
Label = label;
ToolTip = string.IsNullOrEmpty(tooltip) ? null : tooltip;
CheckedValue = checkedValue;
IsChecked = isChecked;
}
public string GetValue() => IsChecked ? CheckedValue : string.Empty;
}
public class ExecuteCustomAction : Popup
{
public Models.CustomAction CustomAction
@ -10,25 +77,38 @@ namespace SourceGit.ViewModels
get;
}
public List<ICustomActionControlParameter> ControlParameters
{
get;
} = [];
public bool IsSimpleMode
{
get => ControlParameters.Count == 0;
}
public ExecuteCustomAction(Repository repo, Models.CustomAction action)
{
_repo = repo;
_args = action.Arguments.Replace("${REPO}", GetWorkdir());
_commandline = action.Arguments.Replace("${REPO}", GetWorkdir());
CustomAction = action;
PrepareControlParameters();
}
public ExecuteCustomAction(Repository repo, Models.CustomAction action, Models.Branch branch)
{
_repo = repo;
_args = action.Arguments.Replace("${REPO}", GetWorkdir()).Replace("${BRANCH}", branch.FriendlyName);
_commandline = action.Arguments.Replace("${REPO}", GetWorkdir()).Replace("${BRANCH}", branch.FriendlyName);
CustomAction = action;
PrepareControlParameters();
}
public ExecuteCustomAction(Repository repo, Models.CustomAction action, Models.Commit commit)
{
_repo = repo;
_args = action.Arguments.Replace("${REPO}", GetWorkdir()).Replace("${SHA}", commit.SHA);
_commandline = action.Arguments.Replace("${REPO}", GetWorkdir()).Replace("${SHA}", commit.SHA);
CustomAction = action;
PrepareControlParameters();
}
public override Task<bool> Sure()
@ -36,15 +116,22 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false);
ProgressDescription = "Run custom action ...";
var cmdline = _commandline;
for (var i = 0; i < ControlParameters.Count; i++)
{
var param = ControlParameters[i];
cmdline = cmdline.Replace($"${i}", param.GetValue());
}
var log = _repo.CreateLog(CustomAction.Name);
Use(log);
return Task.Run(() =>
{
if (CustomAction.WaitForExit)
Commands.ExecuteCustomAction.RunAndWait(_repo.FullPath, CustomAction.Executable, _args, log);
Commands.ExecuteCustomAction.RunAndWait(_repo.FullPath, CustomAction.Executable, cmdline, log);
else
Commands.ExecuteCustomAction.Run(_repo.FullPath, CustomAction.Executable, _args);
Commands.ExecuteCustomAction.Run(_repo.FullPath, CustomAction.Executable, cmdline);
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true));
@ -52,12 +139,31 @@ namespace SourceGit.ViewModels
});
}
private void PrepareControlParameters()
{
foreach (var ctl in CustomAction.Controls)
{
switch (ctl.Type)
{
case Models.CustomActionControlType.TextBox:
ControlParameters.Add(new CustomActionControlTextBox(ctl.Label, ctl.Description, ctl.StringValue));
break;
case Models.CustomActionControlType.CheckBox:
ControlParameters.Add(new CustomActionControlCheckBox(ctl.Label, ctl.Description, ctl.StringValue, ctl.BoolValue));
break;
case Models.CustomActionControlType.PathSelector:
ControlParameters.Add(new CustomActionControlPathSelector(ctl.Label, ctl.Description, ctl.BoolValue, ctl.StringValue));
break;
}
}
}
private string GetWorkdir()
{
return OperatingSystem.IsWindows() ? _repo.FullPath.Replace("/", "\\") : _repo.FullPath;
}
private readonly Repository _repo = null;
private readonly string _args;
private readonly string _commandline = string.Empty;
}
}

View file

@ -806,9 +806,7 @@ namespace SourceGit.ViewModels
item.Header = dup.Name;
item.Click += (_, e) =>
{
if (_repo.CanCreatePopup())
_repo.ShowAndStartPopup(new ExecuteCustomAction(_repo, dup, commit));
_repo.ExecCustomAction(dup, commit);
e.Handled = true;
};

View file

@ -819,17 +819,34 @@ namespace SourceGit.ViewModels
}
public void ApplyPatch()
{
if (CanCreatePopup())
ShowPopup(new Apply(this));
}
public void ExecCustomAction(Models.CustomAction action, object scope)
{
if (!CanCreatePopup())
return;
ShowPopup(new Apply(this));
var popup = null as ExecuteCustomAction;
if (scope is Models.Branch b)
popup = new ExecuteCustomAction(this, action, b);
else if (scope is Models.Commit c)
popup = new ExecuteCustomAction(this, action, c);
else
popup = new ExecuteCustomAction(this, action);
if (action.Controls.Count == 0)
ShowAndStartPopup(popup);
else
ShowPopup(popup);
}
public void Cleanup()
{
if (!CanCreatePopup())
return;
ShowAndStartPopup(new Cleanup(this));
if (CanCreatePopup())
ShowAndStartPopup(new Cleanup(this));
}
public void ClearFilter()
@ -1706,9 +1723,7 @@ namespace SourceGit.ViewModels
item.Header = dup.Name;
item.Click += (_, e) =>
{
if (CanCreatePopup())
ShowAndStartPopup(new ExecuteCustomAction(this, dup));
ExecCustomAction(dup, null);
e.Handled = true;
};
@ -2805,9 +2820,7 @@ namespace SourceGit.ViewModels
item.Header = dup.Name;
item.Click += (_, e) =>
{
if (CanCreatePopup())
ShowAndStartPopup(new ExecuteCustomAction(this, dup, branch));
ExecCustomAction(dup, branch);
e.Handled = true;
};

View file

@ -0,0 +1,180 @@
<v:ChromelessWindow xmlns="https://github.com/avaloniaui"
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"
xmlns:m="using:SourceGit.Models"
xmlns:v="using:SourceGit.Views"
xmlns:vm="using:SourceGit.ViewModels"
mc:Ignorable="d" d:DesignWidth="520" d:DesignHeight="230"
x:Class="SourceGit.Views.ConfigureCustomActionControls"
x:DataType="vm:ConfigureCustomActionControls"
x:Name="ThisControl"
Icon="/App.ico"
Title="{DynamicResource Text.ConfigureCustomActionControls}"
Width="560" SizeToContent="Height"
CanResize="False"
WindowStartupLocation="CenterOwner">
<Grid RowDefinitions="Auto,Auto">
<!-- TitleBar -->
<Grid Grid.Row="0" Height="28" IsVisible="{Binding !#ThisControl.UseSystemWindowFrame}">
<Border Background="{DynamicResource Brush.TitleBar}"
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}"
PointerPressed="BeginMoveWindow"/>
<Path Width="14" Height="14"
Margin="10,0,0,0"
HorizontalAlignment="Left"
Data="{StaticResource Icons.Settings}"
IsVisible="{OnPlatform True, macOS=False}"/>
<TextBlock Classes="bold"
Text="{DynamicResource Text.ConfigureCustomActionControls}"
HorizontalAlignment="Center" VerticalAlignment="Center"
IsHitTestVisible="False"/>
<v:CaptionButtons HorizontalAlignment="Right"
IsCloseButtonOnly="True"
IsVisible="{OnPlatform True, macOS=False}"/>
</Grid>
<!-- Body -->
<Grid Grid.Row="1" ColumnDefinitions="200,*" Height="340" Margin="8,8,8,16">
<Border Grid.Column="0"
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"
Background="{DynamicResource Brush.Contents}">
<Grid RowDefinitions="*,1,Auto">
<ListBox Grid.Row="0"
Background="Transparent"
ItemsSource="{Binding Controls}"
SelectedItem="{Binding Edit, Mode=TwoWay}"
SelectionMode="Single">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Height" Value="26"/>
<Setter Property="Padding" Value="4,2"/>
</Style>
</ListBox.Styles>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="m:CustomActionControl">
<Grid Margin="4,0" ColumnDefinitions="Auto,*">
<Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.Parameter}" Fill="{DynamicResource Brush.FG1}"/>
<TextBlock Grid.Column="1" Text="{Binding Label, Mode=OneWay}" Margin="6,0,0,0" TextTrimming="CharacterEllipsis"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Rectangle Grid.Row="1" Height="1" Fill="{DynamicResource Brush.Border2}" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>
<Grid Grid.Row="2" ColumnDefinitions="Auto,Auto,*,Auto,Auto" Background="{DynamicResource Brush.ToolBar}">
<Button Grid.Column="0"
Classes="icon_button"
Width="28" Height="28"
Command="{Binding Add}">
<Path Width="14" Height="14" Data="{StaticResource Icons.Plus}"/>
</Button>
<Button Grid.Column="1"
Classes="icon_button"
Width="28" Height="28"
Command="{Binding Remove}">
<Path Width="14" Height="14" Data="{StaticResource Icons.Minus}"/>
</Button>
<Button Grid.Column="3"
Classes="icon_button"
Width="28" Height="28"
Command="{Binding MoveUp}"
IsVisible="{Binding Edit, Converter={x:Static ObjectConverters.IsNotNull}}">
<Path Width="14" Height="14" Margin="0,6,0,0" Data="{StaticResource Icons.Up}"/>
</Button>
<Button Grid.Column="4"
Classes="icon_button"
Width="28" Height="28"
Command="{Binding MoveDown}"
IsVisible="{Binding Edit, Converter={x:Static ObjectConverters.IsNotNull}}">
<Path Width="14" Height="14" Margin="0,6,0,0" Data="{StaticResource Icons.Down}"/>
</Button>
</Grid>
</Grid>
</Border>
<ContentControl Grid.Column="1" Margin="16,0,0,0">
<ContentControl.Content>
<Binding Path="Edit">
<Binding.TargetNullValue>
<Path Width="64" Height="64"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Fill="{DynamicResource Brush.FG2}"
Data="{StaticResource Icons.Empty}"/>
</Binding.TargetNullValue>
</Binding>
</ContentControl.Content>
<ContentControl.DataTemplates>
<DataTemplate DataType="m:CustomActionControl">
<StackPanel Orientation="Vertical">
<!-- Label -->
<TextBlock Text="{DynamicResource Text.ConfigureCustomActionControls.Label}"/>
<TextBox Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding Label, Mode=TwoWay}"/>
<!-- Type -->
<TextBlock Margin="0,12,0,0" Text="{DynamicResource Text.ConfigureCustomActionControls.Type}"/>
<ComboBox Margin="0,4,0,0" Height="28" HorizontalAlignment="Stretch" SelectedIndex="{Binding Type, Mode=TwoWay}">
<ComboBoxItem Content="TextBox"/>
<ComboBoxItem Content="Path Selector"/>
<ComboBoxItem Content="CheckBox"/>
</ComboBox>
<!-- Description -->
<TextBlock Margin="0,12,0,0" Text="{DynamicResource Text.ConfigureCustomActionControls.Description}"/>
<TextBox Margin="0,4,0,0"
CornerRadius="3"
Height="28"
Text="{Binding Description, Mode=TwoWay}"
Watermark="{DynamicResource Text.ConfigureCustomActionControls.Description.Placeholder}"/>
<!-- Default value -->
<TextBlock Margin="0,12,0,0" Text="{DynamicResource Text.ConfigureCustomActionControls.DefaultValue}"/>
<TextBox Margin="0,4,0,0"
CornerRadius="3"
Height="28"
Text="{Binding StringValue, Mode=TwoWay}"
IsVisible="{Binding Type, Converter={x:Static ObjectConverters.NotEqual}, ConverterParameter={x:Static m:CustomActionControlType.CheckBox}}"/>
<CheckBox Height="28"
Margin="0,4,0,0"
IsChecked="{Binding BoolValue, Mode=TwoWay}"
IsVisible="{Binding Type, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:CustomActionControlType.CheckBox}}"/>
<!-- PathSelector needs a bool to determine whether we want a folder or a file -->
<TextBlock Margin="0,12,0,0"
Text="{DynamicResource Text.ConfigureCustomActionControls.IsFolder}"
IsVisible="{Binding Type, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:CustomActionControlType.PathSelector}}"/>
<CheckBox Height="28"
Margin="0,4,0,0"
IsChecked="{Binding BoolValue, Mode=TwoWay}"
IsVisible="{Binding Type, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:CustomActionControlType.PathSelector}}"/>
<!-- CheckBox needs a checked value for commandline -->
<TextBlock Margin="0,12,0,0"
Text="{DynamicResource Text.ConfigureCustomActionControls.CheckedValue}"
IsVisible="{Binding Type, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:CustomActionControlType.CheckBox}}"/>
<TextBox Margin="0,4,0,0"
CornerRadius="3"
Height="28"
Text="{Binding StringValue, Mode=TwoWay}"
IsVisible="{Binding Type, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:CustomActionControlType.CheckBox}}"/>
</StackPanel>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
</Grid>
</Grid>
</v:ChromelessWindow>

View file

@ -0,0 +1,10 @@
namespace SourceGit.Views
{
public partial class ConfigureCustomActionControls : ChromelessWindow
{
public ConfigureCustomActionControls()
{
InitializeComponent();
}
}
}

View file

@ -9,11 +9,81 @@
<StackPanel Orientation="Vertical" Margin="8,0">
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.ExecuteCustomAction}"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,16,0,0">
<TextBlock Text="{DynamicResource Text.ExecuteCustomAction.Name}"/>
<Path Width="14" Height="14" Margin="8,0,0,0" Data="{StaticResource Icons.Action}"/>
<TextBlock Text="{Binding CustomAction.Name}" Margin="8,0,0,0"/>
</StackPanel>
Text="{Binding CustomAction.Name}"/>
<Grid Margin="0,16,0,0" IsVisible="{Binding IsSimpleMode}">
<TextBlock Text="{DynamicResource Text.ExecuteCustomAction.SimpleWait}"
HorizontalAlignment="Center"/>
</Grid>
<ListBox Margin="0,16,0,0"
IsVisible="{Binding !IsSimpleMode}"
ItemsSource="{Binding ControlParameters, Mode=OneWay}">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Height" Value="32"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter, ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent"/>
</Style>
</ListBox.Styles>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.DataTemplates>
<DataTemplate DataType="vm:CustomActionControlTextBox">
<Grid ColumnDefinitions="120,*">
<TextBlock Grid.Column="0"
Text="{Binding Label}"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"/>
<TextBox Grid.Column="1"
Height="28"
VerticalAlignment="Center"
CornerRadius="3"
Watermark="{Binding Placeholder, Mode=OneWay}"
Text="{Binding Text, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="vm:CustomActionControlCheckBox">
<Grid ColumnDefinitions="120,*">
<CheckBox Grid.Column="1"
Content="{Binding Label}"
ToolTip.Tip="{Binding ToolTip, Mode=OneWay}"
IsChecked="{Binding IsChecked, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="vm:CustomActionControlPathSelector">
<Grid ColumnDefinitions="120,*">
<TextBlock Grid.Column="0"
Text="{Binding Label}"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"/>
<TextBox Grid.Column="1"
Height="28"
CornerRadius="3"
Watermark="{Binding Placeholder, Mode=OneWay}"
Text="{Binding Path, Mode=TwoWay}">
<TextBox.InnerRightContent>
<Button Classes="icon_button" Width="30" Height="30" Click="SelectPath">
<Path Data="{StaticResource Icons.Folder.Open}" Fill="{DynamicResource Brush.FG1}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
</Grid>
</DataTemplate>
</ListBox.DataTemplates>
</ListBox>
</StackPanel>
</UserControl>

View file

@ -1,4 +1,8 @@
using System;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
namespace SourceGit.Views
{
@ -8,5 +12,53 @@ namespace SourceGit.Views
{
InitializeComponent();
}
private async void SelectPath(object sender, RoutedEventArgs e)
{
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel == null)
return;
var control = sender as Control;
if (control == null)
return;
var selector = control.DataContext as ViewModels.CustomActionControlPathSelector;
if (selector == null)
return;
if (selector.IsFolder)
{
try
{
var options = new FolderPickerOpenOptions() { AllowMultiple = false };
var selected = await topLevel.StorageProvider.OpenFolderPickerAsync(options);
if (selected.Count == 1)
{
var folder = selected[0];
var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString();
selector.Path = folderPath;
}
}
catch (Exception exception)
{
App.RaiseException(string.Empty, $"Failed to select parent folder: {exception.Message}");
}
}
else
{
var options = new FilePickerOpenOptions()
{
AllowMultiple = false,
FileTypeFilter = [new FilePickerFileType("File") { Patterns = ["*.*"] }]
};
var selected = await topLevel.StorageProvider.OpenFilePickerAsync(options);
if (selected.Count == 1)
selector.Path = selected[0].Path.LocalPath;
}
e.Handled = true;
}
}
}

View file

@ -548,7 +548,7 @@
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Configure.CustomAction}"/>
</TabItem.Header>
<Grid MinHeight="340" Margin="0,8,0,16">
<Grid MinHeight="380" Margin="0,8,0,16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*" MaxWidth="400"/>
@ -659,6 +659,15 @@
<TextBox Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding Arguments, Mode=TwoWay}"/>
<TextBlock Margin="0,2,0,0" TextWrapping="Wrap" Text="{DynamicResource Text.Configure.CustomAction.Arguments.Tip}" Foreground="{DynamicResource Brush.FG2}"/>
<TextBlock Margin="0,12,0,0" Text="{DynamicResource Text.Configure.CustomAction.InputControls}"/>
<Button Margin="0,4,0,0" Classes="flat" Height="28" Click="EditCustomActionControls">
<StackPanel Orientation="Horizontal">
<Path Width="14" Height="14" Data="{StaticResource Icons.Edit}" Fill="{DynamicResource Brush.FG1}"/>
<TextBlock Margin="4,0,0,0" Text="{DynamicResource Text.Configure.CustomAction.InputControls.Edit}"/>
</StackPanel>
</Button>
<TextBlock Margin="0,2,0,0" TextWrapping="Wrap" Text="{DynamicResource Text.Configure.CustomAction.InputControls.Tip}" Foreground="{DynamicResource Brush.FG2}"/>
<CheckBox Margin="0,8,0,0" Content="{DynamicResource Text.Configure.CustomAction.WaitForExit}" IsChecked="{Binding WaitForExit, Mode=TwoWay}"/>
</StackPanel>
</DataTemplate>

View file

@ -439,6 +439,20 @@ namespace SourceGit.Views
e.Handled = true;
}
private async void EditCustomActionControls(object sender, RoutedEventArgs e)
{
if (sender is not Button { DataContext: Models.CustomAction act })
return;
var dialog = new ConfigureCustomActionControls()
{
DataContext = new ViewModels.ConfigureCustomActionControls(act.Controls)
};
await dialog.ShowDialog(this);
e.Handled = true;
}
private void UpdateGitVersion()
{
GitVersion = Native.OS.GitVersionString;

View file

@ -381,7 +381,7 @@
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Configure.CustomAction}"/>
</TabItem.Header>
<Grid ColumnDefinitions="200,*" Height="340" Margin="0,8,0,16">
<Grid ColumnDefinitions="200,*" Height="380" Margin="0,8,0,16">
<Border Grid.Column="0"
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"
Background="{DynamicResource Brush.Contents}">
@ -487,6 +487,15 @@
<TextBox Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding Arguments, Mode=TwoWay}"/>
<TextBlock Margin="0,2,0,0" TextWrapping="Wrap" Text="{DynamicResource Text.Configure.CustomAction.Arguments.Tip}" Foreground="{DynamicResource Brush.FG2}"/>
<TextBlock Margin="0,12,0,0" Text="{DynamicResource Text.Configure.CustomAction.InputControls}"/>
<Button Margin="0,4,0,0" Classes="flat" Height="28" Click="EditCustomActionControls">
<StackPanel Orientation="Horizontal">
<Path Width="14" Height="14" Data="{StaticResource Icons.Edit}" Fill="{DynamicResource Brush.FG1}"/>
<TextBlock Margin="4,0,0,0" Text="{DynamicResource Text.Configure.CustomAction.InputControls.Edit}"/>
</StackPanel>
</Button>
<TextBlock Margin="0,2,0,0" TextWrapping="Wrap" Text="{DynamicResource Text.Configure.CustomAction.InputControls.Tip}" Foreground="{DynamicResource Brush.FG2}"/>
<CheckBox Margin="0,8,0,0" Content="{DynamicResource Text.Configure.CustomAction.WaitForExit}" IsChecked="{Binding WaitForExit, Mode=TwoWay}"/>
</StackPanel>
</DataTemplate>

View file

@ -12,6 +12,14 @@ namespace SourceGit.Views
InitializeComponent();
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (!e.Handled && e.Key == Key.Escape)
Close();
}
protected override void OnClosing(WindowClosingEventArgs e)
{
base.OnClosing(e);
@ -35,12 +43,18 @@ namespace SourceGit.Views
e.Handled = true;
}
protected override void OnKeyDown(KeyEventArgs e)
private async void EditCustomActionControls(object sender, RoutedEventArgs e)
{
base.OnKeyDown(e);
if (sender is not Button { DataContext: Models.CustomAction act })
return;
if (!e.Handled && e.Key == Key.Escape)
Close();
var dialog = new ConfigureCustomActionControls()
{
DataContext = new ViewModels.ConfigureCustomActionControls(act.Controls)
};
await dialog.ShowDialog(this);
e.Handled = true;
}
}
}