Skip to content

Commit cde81dd

Browse files
committed
feat(desktop): добавлен screenshot-режим и workflow для автоматических скриншотов
1 parent 72c6217 commit cde81dd

File tree

4 files changed

+265
-0
lines changed

4 files changed

+265
-0
lines changed

.github/workflows/screenshots.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Desktop Screenshots
2+
3+
on:
4+
workflow_dispatch: # Ручной запуск
5+
push:
6+
branches: [main]
7+
paths:
8+
- 'HQStudio.Desktop/**/*.xaml'
9+
- 'HQStudio.Desktop/**/*.cs'
10+
- '.github/workflows/screenshots.yml'
11+
12+
jobs:
13+
screenshots:
14+
runs-on: self-hosted
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Setup .NET
20+
uses: actions/setup-dotnet@v4
21+
with:
22+
dotnet-version: '8.0.x'
23+
24+
- name: Restore dependencies
25+
run: dotnet restore HQStudio.Desktop
26+
27+
- name: Build Desktop
28+
run: dotnet build HQStudio.Desktop -c Release --no-restore
29+
30+
- name: Create screenshots directory
31+
run: New-Item -ItemType Directory -Force -Path docs/screenshots
32+
33+
- name: Run Desktop in Screenshot Mode
34+
env:
35+
SCREENSHOT_MODE: 'true'
36+
SCREENSHOT_OUTPUT: 'docs/screenshots'
37+
run: |
38+
$process = Start-Process -FilePath "HQStudio.Desktop/bin/Release/net8.0-windows/HQStudio.Desktop.exe" -PassThru
39+
# Ждём завершения (макс 60 секунд)
40+
$process | Wait-Process -Timeout 60 -ErrorAction SilentlyContinue
41+
if (!$process.HasExited) {
42+
$process | Stop-Process -Force
43+
}
44+
45+
- name: List screenshots
46+
run: Get-ChildItem docs/screenshots -ErrorAction SilentlyContinue
47+
48+
- name: Commit screenshots
49+
run: |
50+
git config user.name "github-actions[bot]"
51+
git config user.email "github-actions[bot]@users.noreply.github.com"
52+
git add docs/screenshots/*.png
53+
git diff --staged --quiet || git commit -m "docs: обновлены скриншоты Desktop приложения [skip ci]"
54+
git push
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
inclusion: manual
3+
---
4+
5+
# Self-Hosted GitHub Actions Runner
6+
7+
## Статус
8+
Runner установлен на локальной машине для выполнения задач, требующих GUI (скриншоты Desktop приложения).
9+
10+
## Расположение
11+
```
12+
C:\actions-runner\
13+
```
14+
15+
## Запуск Runner'а
16+
17+
**ВАЖНО:** После перезагрузки ПК runner нужно запустить вручную!
18+
19+
```powershell
20+
C:\actions-runner\run.cmd
21+
```
22+
23+
Окно PowerShell должно оставаться открытым пока runner работает.
24+
25+
## Проверка статуса
26+
- GitHub: Settings → Actions → Runners
27+
- Должен быть статус "Idle" (зелёный)
28+
- Имя runner'а: `hqstudio-local`
29+
- Labels: `self-hosted`, `Windows`, `X64`, `screenshots`
30+
31+
## Если runner не запущен
32+
Workflows с `runs-on: self-hosted` будут висеть в очереди "Waiting for a runner".
33+
34+
## Автозапуск (опционально)
35+
Чтобы runner запускался автоматически при старте Windows:
36+
37+
1. Открыть Task Scheduler (taskschd.msc)
38+
2. Create Basic Task → "GitHub Actions Runner"
39+
3. Trigger: "When the computer starts"
40+
4. Action: Start a program
41+
- Program: `C:\actions-runner\run.cmd`
42+
- Start in: `C:\actions-runner`
43+
5. Finish
44+
45+
## Безопасность
46+
⚠️ Public репозиторий + self-hosted runner = риск. Злоумышленник может создать PR с вредоносным кодом, который выполнится на твоём ПК.
47+
48+
Рекомендации:
49+
- Не принимать PR от незнакомых людей без review
50+
- Использовать `pull_request_target` вместо `pull_request` для workflows
51+
- Или сделать репозиторий приватным

HQStudio.Desktop/App.xaml.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using HQStudio.Services;
2+
using HQStudio.Views;
23
using System.Windows;
34

45
namespace HQStudio
@@ -12,6 +13,13 @@ protected override void OnStartup(StartupEventArgs e)
1213
// Initialize theme
1314
ThemeService.Instance.Initialize();
1415

16+
// Screenshot mode - пропускаем авторизацию и делаем скриншоты
17+
if (ScreenshotService.IsScreenshotMode)
18+
{
19+
_ = RunScreenshotModeAsync();
20+
return;
21+
}
22+
1523
// Start data sync service if API is enabled
1624
if (SettingsService.Instance.UseApi)
1725
{
@@ -32,6 +40,67 @@ private async Task InitializeApiAsync()
3240
}
3341
}
3442

43+
private async Task RunScreenshotModeAsync()
44+
{
45+
Console.WriteLine("Screenshot mode enabled");
46+
47+
// 1. Скриншот окна входа
48+
var loginWindow = new LoginWindow();
49+
loginWindow.Show();
50+
await Task.Delay(1000);
51+
ScreenshotService.CaptureWindow(loginWindow, "01-login.png");
52+
53+
// 2. Открываем главное окно напрямую (без авторизации)
54+
var mainWindow = new MainWindow();
55+
mainWindow.Show();
56+
loginWindow.Close();
57+
58+
await Task.Delay(1500); // Ждём загрузки данных
59+
60+
// 3. Скриншот Dashboard
61+
ScreenshotService.CaptureWindow(mainWindow, "02-dashboard.png");
62+
63+
// 4. Навигация по разделам и скриншоты
64+
if (mainWindow.DataContext is ViewModels.MainViewModel vm)
65+
{
66+
// Заказы
67+
vm.NavigateCommand.Execute("Orders");
68+
await Task.Delay(1000);
69+
ScreenshotService.CaptureWindow(mainWindow, "03-orders.png");
70+
71+
// Клиенты
72+
vm.NavigateCommand.Execute("Clients");
73+
await Task.Delay(1000);
74+
ScreenshotService.CaptureWindow(mainWindow, "04-clients.png");
75+
76+
// Услуги
77+
vm.NavigateCommand.Execute("Services");
78+
await Task.Delay(1000);
79+
ScreenshotService.CaptureWindow(mainWindow, "05-services.png");
80+
81+
// Сотрудники
82+
vm.NavigateCommand.Execute("Staff");
83+
await Task.Delay(1000);
84+
ScreenshotService.CaptureWindow(mainWindow, "06-staff.png");
85+
86+
// Настройки
87+
vm.NavigateCommand.Execute("Settings");
88+
await Task.Delay(500);
89+
ScreenshotService.CaptureWindow(mainWindow, "07-settings.png");
90+
91+
// Переключаем тему и делаем скриншот
92+
ThemeService.Instance.ApplyTheme(false); // Light theme
93+
await Task.Delay(500);
94+
95+
vm.NavigateCommand.Execute("Dashboard");
96+
await Task.Delay(500);
97+
ScreenshotService.CaptureWindow(mainWindow, "08-dashboard-light.png");
98+
}
99+
100+
Console.WriteLine("Screenshots completed!");
101+
ScreenshotService.ExitAfterDelay(500);
102+
}
103+
35104
protected override void OnExit(ExitEventArgs e)
36105
{
37106
// Stop data sync
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading.Tasks;
4+
using System.Windows;
5+
using System.Windows.Media;
6+
using System.Windows.Media.Imaging;
7+
8+
namespace HQStudio.Services
9+
{
10+
/// <summary>
11+
/// Сервис для создания скриншотов окон приложения
12+
/// </summary>
13+
public static class ScreenshotService
14+
{
15+
public static bool IsScreenshotMode =>
16+
Environment.GetEnvironmentVariable("SCREENSHOT_MODE") == "true";
17+
18+
public static string OutputDirectory =>
19+
Environment.GetEnvironmentVariable("SCREENSHOT_OUTPUT") ?? "screenshots";
20+
21+
/// <summary>
22+
/// Делает скриншот указанного окна
23+
/// </summary>
24+
public static void CaptureWindow(Window window, string filename)
25+
{
26+
if (window == null) return;
27+
28+
try
29+
{
30+
// Убедимся что папка существует
31+
Directory.CreateDirectory(OutputDirectory);
32+
33+
// Получаем размеры окна
34+
var width = (int)window.ActualWidth;
35+
var height = (int)window.ActualHeight;
36+
37+
if (width <= 0 || height <= 0) return;
38+
39+
// Создаём RenderTargetBitmap
40+
var dpi = VisualTreeHelper.GetDpi(window);
41+
var renderTarget = new RenderTargetBitmap(
42+
(int)(width * dpi.DpiScaleX),
43+
(int)(height * dpi.DpiScaleY),
44+
dpi.PixelsPerInchX,
45+
dpi.PixelsPerInchY,
46+
PixelFormats.Pbgra32);
47+
48+
renderTarget.Render(window);
49+
50+
// Сохраняем как PNG
51+
var encoder = new PngBitmapEncoder();
52+
encoder.Frames.Add(BitmapFrame.Create(renderTarget));
53+
54+
var filepath = Path.Combine(OutputDirectory, filename);
55+
using var stream = File.Create(filepath);
56+
encoder.Save(stream);
57+
58+
Console.WriteLine($"Screenshot saved: {filepath}");
59+
}
60+
catch (Exception ex)
61+
{
62+
Console.WriteLine($"Screenshot error: {ex.Message}");
63+
}
64+
}
65+
66+
/// <summary>
67+
/// Делает скриншот с задержкой (для анимаций/прелоадеров)
68+
/// </summary>
69+
public static async Task CaptureWindowDelayedAsync(Window window, string filename, int delayMs = 500)
70+
{
71+
await Task.Delay(delayMs);
72+
73+
// Выполняем на UI потоке
74+
await window.Dispatcher.InvokeAsync(() => CaptureWindow(window, filename));
75+
}
76+
77+
/// <summary>
78+
/// Завершает приложение после скриншотов
79+
/// </summary>
80+
public static void ExitAfterDelay(int delayMs = 1000)
81+
{
82+
Task.Delay(delayMs).ContinueWith(_ =>
83+
{
84+
Application.Current.Dispatcher.Invoke(() =>
85+
{
86+
Application.Current.Shutdown(0);
87+
});
88+
});
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)