Uitrol 27-09-2026 18:25

This commit is contained in:
jf
2026-09-27 18:25:12 +02:00
parent 73d57ed17a
commit 781f915454
10 changed files with 370 additions and 2 deletions
+3
View File
@@ -151,6 +151,9 @@ public static class Tonen
6 => "Document",
7 => "Notitie",
8 => "Sociale media",
9 => "Donorcodicil",
10 => "Rijbewijs",
11 => "Paspoort",
_ => "Overig"
};
+13
View File
@@ -18,6 +18,19 @@ public sealed class Cockpitrepo
public Brush StatusKleur { get; set; } = Brushes.Black;
}
/// <summary>
/// Eén balk in een Cockpit-staafgrafiek (pagina's per project, wijzigingen per teamlid). De breedte
/// is al in pixels uitgerekend t.o.v. de grootste waarde, zodat de XAML alleen hoeft te tekenen.
/// Geport van RenH.Uitroller.
/// </summary>
public sealed class Cockpitbalk
{
public string Label { get; set; } = "";
public double BalkBreedte { get; set; }
public string Weergave { get; set; } = "";
public Brush Kleur { get; set; } = Brushes.SteelBlue;
}
/// <summary>
/// Eén regel in de tabel "Laatste wijzigingen": een commit uit de repo, met korte hash, auteur,
/// wanneer en de eerste regel van het bericht.
@@ -226,6 +226,63 @@
</StackPanel>
</Border>
<!-- Grafieken: pagina's per project (razor-bestanden in Web/AppUi) en
wijzigingen per teamlid (commits per auteur). Eenvoudige staafjes
(Rectangle met in code uitgerekende pixelbreedte), zoals bij RenH.Uitroller. -->
<Grid Margin="0,0,0,14">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="14" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="Pagina's per project" FontWeight="SemiBold" Foreground="{StaticResource TekstDof}" Margin="0,0,0,6" FontSize="12" />
<Border BorderBrush="{StaticResource Rand}" BorderThickness="1" Background="{StaticResource Vlak}" CornerRadius="10" Padding="10" MinHeight="70">
<ItemsControl x:Name="CockpitPaginas">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="130" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Label}" VerticalAlignment="Center" TextTrimming="CharacterEllipsis" Foreground="{StaticResource Tekst}" />
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
<Rectangle Height="16" RadiusX="3" RadiusY="3" Width="{Binding BalkBreedte}" Fill="{Binding Kleur}" />
<TextBlock Text="{Binding Weergave}" Margin="8,0,0,0" Foreground="{StaticResource TekstDof}" VerticalAlignment="Center" />
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Text="Wijzigingen per teamlid" FontWeight="SemiBold" Foreground="{StaticResource TekstDof}" Margin="0,0,0,6" FontSize="12" />
<Border BorderBrush="{StaticResource Rand}" BorderThickness="1" Background="{StaticResource Vlak}" CornerRadius="10" Padding="10" MinHeight="70">
<ItemsControl x:Name="CockpitTeamleden">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="130" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Label}" VerticalAlignment="Center" TextTrimming="CharacterEllipsis" Foreground="{StaticResource Tekst}" />
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
<Rectangle Height="16" RadiusX="3" RadiusY="3" Width="{Binding BalkBreedte}" Fill="{Binding Kleur}" />
<TextBlock Text="{Binding Weergave}" Margin="8,0,0,0" Foreground="{StaticResource TekstDof}" VerticalAlignment="Center" />
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</StackPanel>
</Grid>
<TextBlock Text="Laatste wijzigingen" FontWeight="SemiBold" Foreground="{StaticResource TekstDof}" Margin="0,0,0,6" FontSize="12" />
<Border BorderBrush="{StaticResource Rand}" BorderThickness="1" Background="{StaticResource Vlak}" CornerRadius="10" Padding="14" Margin="0,0,0,14">
<StackPanel>
+175 -1
View File
@@ -1,3 +1,4 @@
using System.IO;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
@@ -20,6 +21,7 @@ public partial class Hoofdvenster : Window
{
private readonly Instellingen _instellingen = Instellingen.Lees();
private readonly Uitrolgeheugen _geheugen = Uitrolgeheugen.Lees();
private readonly Vensterstand _vensterstand = Vensterstand.Lees();
private CancellationTokenSource? _stopper;
/// <summary>Ververst de Gitea-knoppen vanzelf, zodat "Van Gitea (x)" oplicht als er
@@ -34,6 +36,12 @@ public partial class Hoofdvenster : Window
{
InitializeComponent();
// Vensterpositie/-grootte van de vorige keer terugzetten (bewaren gebeurt in
// Venster_Closing); het tabblad pas zodra Tabs bestaat (na InitializeComponent).
VensterstandTerugzetten();
if (_vensterstand.Tabblad >= 0 && _vensterstand.Tabblad < Tabs.Items.Count)
Tabs.SelectedIndex = _vensterstand.Tabblad;
Doelregel.Text = $"Server: {_instellingen.Server}:{_instellingen.Poort} • " +
$"Project: {_instellingen.Projectmap}";
@@ -292,6 +300,57 @@ public partial class Hoofdvenster : Window
_teststopper?.Cancel();
_test?.Dispose();
_giteaTimer?.Stop();
VensterstandBewaren();
}
/// <summary>Zet het venster terug op de onthouden plek en grootte, mits die nog (deels) op een
/// beeldscherm valt (anders opent hij gewoon gecentreerd, zoals in de XAML). Voorkomt dat het
/// venster buiten beeld opent als een collega minder of andere schermen heeft. Geport van
/// RenH.Uitroller.</summary>
private void VensterstandTerugzetten()
{
if (!_vensterstand.Bruikbaar) return;
double schermL = SystemParameters.VirtualScreenLeft;
double schermB = SystemParameters.VirtualScreenTop;
double schermBr = SystemParameters.VirtualScreenWidth;
double schermH = SystemParameters.VirtualScreenHeight;
// Minstens een stukje van de titelbalk moet grijpbaar blijven binnen het totale schermvlak.
const double kier = 60;
bool zichtbaar =
(_vensterstand.Links + _vensterstand.Breedte) > schermL + kier &&
_vensterstand.Links < schermL + schermBr - kier &&
(_vensterstand.Boven + _vensterstand.Hoogte) > schermB + kier &&
_vensterstand.Boven < schermB + schermH - kier;
if (!zichtbaar) return;
WindowStartupLocation = WindowStartupLocation.Manual;
Left = _vensterstand.Links;
Top = _vensterstand.Boven;
Width = _vensterstand.Breedte;
Height = _vensterstand.Hoogte;
if (_vensterstand.Gemaximaliseerd) WindowState = WindowState.Maximized;
}
/// <summary>Bewaart de huidige vensterplek, -grootte en het geopende tabblad. Bij een
/// gemaximaliseerd/geminimaliseerd venster de onderliggende normale maat (RestoreBounds), zodat
/// un-maximaliseren goed terugkomt. Geport van RenH.Uitroller.</summary>
private void VensterstandBewaren()
{
Rect b = WindowState == WindowState.Normal
? new Rect(Left, Top, Width, Height)
: RestoreBounds;
if (b.IsEmpty || b.Width < 1 || b.Height < 1) return;
_vensterstand.Links = b.Left;
_vensterstand.Boven = b.Top;
_vensterstand.Breedte = b.Width;
_vensterstand.Hoogte = b.Height;
_vensterstand.Gemaximaliseerd = WindowState == WindowState.Maximized;
_vensterstand.Tabblad = Tabs.SelectedIndex;
_vensterstand.Bewaar();
}
private void KnopStop_Click(object sender, RoutedEventArgs e)
@@ -590,6 +649,8 @@ public partial class Hoofdvenster : Window
return new Giteaclient(h.Value.Basis, h.Value.Eigenaar, h.Value.Repo, token);
}
/// <summary>(Her)start de automatische verversing van de Gitea-knoppen volgens de instelling
/// (Instellingen.GiteaVerversSeconden). 0 = uit; anders minimaal 10 seconden.</summary>
private void GiteaTimerInstellen()
{
if (_giteaTimer == null)
@@ -598,7 +659,12 @@ public partial class Hoofdvenster : Window
_giteaTimer.Tick += GiteaTimer_Tick;
}
_giteaTimer.Stop();
_giteaTimer.Interval = TimeSpan.FromSeconds(120);
int sec = _instellingen.GiteaVerversSeconden;
if (sec <= 0) return; // 0 = automatische verversing uit
if (sec < 10) sec = 10; // ondergrens tegen te druk fetchen
_giteaTimer.Interval = TimeSpan.FromSeconds(sec);
_giteaTimer.Start();
}
@@ -1150,6 +1216,17 @@ public partial class Hoofdvenster : Window
CockpitStatus.Foreground = CockpitRood;
}
// Grafieken: pagina's per project (razor-bestanden in Web/AppUi) en wijzigingen per
// teamlid (commits per auteur, hele geschiedenis). Zelfde eenvoudige staafjes als bij
// RenH.Uitroller (Rectangle met in code uitgerekende pixelbreedte), geen grafiekbibliotheek.
try
{
var (paginas, teamleden) = await Task.Run(() => CockpitStatistiek(map));
CockpitPaginas.ItemsSource = paginas;
CockpitTeamleden.ItemsSource = teamleden;
}
catch { }
CockpitTijd.Text = $"Laatst ververst: {DateTime.Now:HH:mm:ss}";
// Claim-samenvatting: dezelfde lijst als op Samenwerken, hier alleen als tekst.
@@ -1174,6 +1251,103 @@ public partial class Hoofdvenster : Window
}
}
// Kleuren voor de teamlid-balken (herhaalt zich als er meer teamleden zijn dan kleuren).
// Geport van RenH.Uitroller.
private static readonly Brush[] CockpitPalet = new Brush[]
{
new SolidColorBrush(Color.FromRgb(0x3B, 0x6C, 0xB3)),
new SolidColorBrush(Color.FromRgb(0x2E, 0x7D, 0x32)),
new SolidColorBrush(Color.FromRgb(0xB2, 0x5E, 0x09)),
new SolidColorBrush(Color.FromRgb(0x8E, 0x4E, 0xC8)),
new SolidColorBrush(Color.FromRgb(0x1C, 0x8A, 0x8A)),
new SolidColorBrush(Color.FromRgb(0xB3, 0x26, 0x1A)),
};
private const double CockpitBalkMax = 220; // pixels voor de grootste balk
/// <summary>
/// Verzamelt de twee Cockpit-staafgrafieken. "Pagina's per project" telt .razor-bestanden per
/// UI-project (Web en AppUi delen dezelfde schermen, maar hebben elk hun eigen kopie - zie
/// eerder in dit project); "Wijzigingen per teamlid" telt commits per auteur over de hele
/// geschiedenis. Draait in de achtergrond, raakt geen UI aan.
/// </summary>
private (List<Cockpitbalk> Paginas, List<Cockpitbalk> Teamleden) CockpitStatistiek(string projectmap)
{
var projecten = new (string Label, string Map)[]
{
("MijnBesluit.Web", Path.Combine(projectmap, "src", "MijnBesluit.Web")),
("MijnBesluit.AppUi", Path.Combine(projectmap, "src", "MijnBesluit.AppUi")),
};
var paginaRuw = projecten.Select(p => (p.Label, Aantal: TelPaginas(p.Map))).ToList();
int paginaMax = paginaRuw.Count == 0 ? 0 : paginaRuw.Max(x => x.Aantal);
var paginas = paginaRuw.Select(x => new Cockpitbalk
{
Label = x.Label,
Weergave = x.Aantal.ToString(),
BalkBreedte = BalkPx(x.Aantal, paginaMax),
Kleur = CockpitPalet[0]
}).ToList();
var perAuteur = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var log = Gitstand.Voer(projectmap, "log --no-merges --pretty=format:%an", 20);
if (log.Ok)
{
foreach (var regel in log.Uitvoer.Split('\n'))
{
var naam = regel.Trim('\r', ' ');
if (naam.Length == 0) continue;
perAuteur[naam] = perAuteur.TryGetValue(naam, out var n) ? n + 1 : 1;
}
}
var top = perAuteur.OrderByDescending(kv => kv.Value).ToList();
int auteurMax = top.Count == 0 ? 0 : top[0].Value;
var teamleden = new List<Cockpitbalk>();
for (int i = 0; i < top.Count; i++)
{
teamleden.Add(new Cockpitbalk
{
Label = top[i].Key,
Weergave = top[i].Value.ToString(),
BalkBreedte = BalkPx(top[i].Value, auteurMax),
Kleur = CockpitPalet[i % CockpitPalet.Length]
});
}
return (paginas, teamleden);
}
private static double BalkPx(int waarde, int max)
{
if (waarde <= 0 || max <= 0) return 0;
return Math.Max(4, waarde / (double)max * CockpitBalkMax);
}
/// <summary>Telt de "pagina's" van een project: *.razor-bestanden, met uitzondering van
/// gegenereerde/externe mappen (bin, obj, .git, node_modules). Geport van RenH.Uitroller
/// (die *.cshtml/*.xaml telt - hier is dat *.razor, MijnBesluit's schermformaat).</summary>
private static int TelPaginas(string map)
{
try
{
if (string.IsNullOrWhiteSpace(map) || !Directory.Exists(map)) return 0;
var sep = Path.DirectorySeparatorChar;
int n = 0;
foreach (var f in Directory.EnumerateFiles(map, "*.razor", SearchOption.AllDirectories))
{
var lp = f.ToLowerInvariant();
if (lp.Contains(sep + "bin" + sep)) continue;
if (lp.Contains(sep + "obj" + sep)) continue;
if (lp.Contains(sep + "node_modules" + sep)) continue;
if (lp.Contains(sep + ".git" + sep)) continue;
n++;
}
return n;
}
catch { return 0; }
}
/// <summary>
/// Een klein invoervenster voor één regel tekst (bijv. de commit-omschrijving). WPF heeft geen
/// InputBox; dit is de eenvoudigste eigen variant. Null als de gebruiker annuleert.
+29
View File
@@ -20,6 +20,20 @@ public class Instellingen
/// <summary>Pad naar chrome.exe, als Chrome niet op de gewone plek staat.</summary>
public string? Chrome { get; set; }
/// <summary>
/// Het e-mailadres dat bij "Lokaal testen" alvast op de inlogpagina ingevuld wordt (via
/// ?email=). Leeg laten = niets invullen. Alleen invullen, niet inloggen - de code moet nog
/// steeds met de hand. Geport van RenH.Uitroller.
/// </summary>
public string Emailadres { get; set; } = "";
/// <summary>
/// Elke zoveel seconden het venster vanzelf kijkt of er iets te pushen/op te halen is en de
/// knoppen "Naar Gitea"/"Van Gitea" bijwerkt. 0 = uit. Minimaal 10 seconden (kleinere waarden
/// worden opgehoogd). Geport van RenH.Uitroller.
/// </summary>
public int GiteaVerversSeconden { get; set; } = 120;
/// <summary>
/// Gitea-gegevens (tabblad Ontwikkelen/Samenwerken: Naar/Van Gitea, release maken, claim-bord).
/// Normaal hoeft hier niets in te staan: de basis-URL, eigenaar en repo worden afgeleid van de
@@ -71,6 +85,21 @@ public class Instellingen
public void Bewaar() =>
File.WriteAllText(Pad, JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true }));
// De vier paden hieronder volgen hetzelfde "map boven de exe, met terugval ernaast"-patroon als
// Pad/PadNaastExe hierboven - los bestand per venster, zodat vensterstand.json niet botst met
// uitroller.json en het instellingenscherm zijn eigen stand apart bewaart. Geport van RenH.Uitroller.
private static string BovenExe(string bestand)
{
var ouder = Directory.GetParent(AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar));
return ouder is null ? Path.Combine(AppContext.BaseDirectory, bestand) : Path.Combine(ouder.FullName, bestand);
}
public static string VensterstandPadNaastExe => Path.Combine(AppContext.BaseDirectory, "vensterstand.json");
public static string VensterstandPad => BovenExe("vensterstand.json");
public static string InstellingenVensterPadNaastExe => Path.Combine(AppContext.BaseDirectory, "instellingenvenster.json");
public static string InstellingenVensterPad => BovenExe("instellingenvenster.json");
/// <summary>
/// Wat er mankeert om te kunnen bouwen en inpakken. Null als dat kan.
///
@@ -173,7 +173,7 @@
<TextBox x:Name="VeldTestadres" Margin="0,0,0,10" />
<TextBlock Text="Chrome.exe (leeg = de gewone plek)" Style="{StaticResource VeldLabel}" />
<Grid>
<Grid Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
@@ -182,6 +182,9 @@
<TextBox Grid.Column="0" x:Name="VeldChrome" />
<Button Grid.Column="2" Content="Bladeren…" Click="Bladeren_Chrome" />
</Grid>
<TextBlock Text="E-mailadres (invullen bij inloggen tijdens lokaal testen)" Style="{StaticResource VeldLabel}" />
<TextBox x:Name="VeldEmail" />
</StackPanel>
</GroupBox>
@@ -204,6 +207,9 @@
<TextBox x:Name="VeldGiteaRepo" />
</StackPanel>
</Grid>
<TextBlock Text="Auto-verversen (sec, 0 = uit, minimaal 10)" Style="{StaticResource VeldLabel}" Margin="0,10,0,3" />
<TextBox x:Name="VeldGiteaVerversen" Width="120" HorizontalAlignment="Left" />
</StackPanel>
</GroupBox>
</StackPanel>
@@ -34,6 +34,8 @@ public partial class Instellingenvenster : Window
VeldGiteaUrl.Text = _instellingen.GiteaUrl ?? "";
VeldGiteaEigenaar.Text = _instellingen.GiteaEigenaar ?? "";
VeldGiteaRepo.Text = _instellingen.GiteaRepo ?? "";
VeldEmail.Text = _instellingen.Emailadres;
VeldGiteaVerversen.Text = _instellingen.GiteaVerversSeconden.ToString();
}
private static void Kies(System.Windows.Controls.TextBox veld)
@@ -65,6 +67,13 @@ public partial class Instellingenvenster : Window
return;
}
if (!int.TryParse(VeldGiteaVerversen.Text.Trim(), out var verversen) || verversen < 0)
{
MessageBox.Show(this, "Auto-verversen moet 0 (uit) of een positief aantal seconden zijn.", "Instellingen",
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
_instellingen.Server = VeldServer.Text.Trim();
_instellingen.Poort = poort;
_instellingen.Geheim = VeldGeheim.Text.Trim();
@@ -75,6 +84,8 @@ public partial class Instellingenvenster : Window
_instellingen.GiteaUrl = string.IsNullOrWhiteSpace(VeldGiteaUrl.Text) ? null : VeldGiteaUrl.Text.Trim();
_instellingen.GiteaEigenaar = string.IsNullOrWhiteSpace(VeldGiteaEigenaar.Text) ? null : VeldGiteaEigenaar.Text.Trim();
_instellingen.GiteaRepo = string.IsNullOrWhiteSpace(VeldGiteaRepo.Text) ? null : VeldGiteaRepo.Text.Trim();
_instellingen.Emailadres = VeldEmail.Text.Trim();
_instellingen.GiteaVerversSeconden = verversen;
_instellingen.Bewaar();
DialogResult = true;
+5
View File
@@ -215,6 +215,11 @@ public sealed class Lokaaltest : IDisposable
{
var adres = _instellingen.Testadres.TrimEnd('/') + "/";
// Het e-mailadres alvast meegeven, zodat het op de inlogpagina ingevuld staat (de verse
// profielmap heeft geen onthoud-cookie). Alleen invullen, niet inloggen.
if (!string.IsNullOrWhiteSpace(_instellingen.Emailadres))
adres += "?email=" + Uri.EscapeDataString(_instellingen.Emailadres.Trim());
var chrome = new[]
{
_instellingen.Chrome,
+62
View File
@@ -0,0 +1,62 @@
using System.IO;
using System.Text.Json;
namespace MijnBesluit.Uitroller;
/// <summary>
/// De positie en grootte van een venster, onthouden tussen sessies. Los bestand
/// (vensterstand.json, resp. instellingenvenster.json) in de map boven de exe, net als
/// uitroller.json - zo botst het niet met de instellingen en overleeft het een build. Wordt bij
/// het sluiten geschreven en bij het openen teruggezet, mits het venster nog (deels) op een
/// beeldscherm valt. Geport van RenH.Uitroller.
/// </summary>
public class Vensterstand
{
public double Links { get; set; }
public double Boven { get; set; }
public double Breedte { get; set; }
public double Hoogte { get; set; }
public bool Gemaximaliseerd { get; set; }
public int Tabblad { get; set; }
/// <summary>Is er iets bruikbaars onthouden (een echte breedte/hoogte)?</summary>
public bool Bruikbaar => Breedte >= 200 && Hoogte >= 150;
public static Vensterstand Lees(string? pad = null, string? padNaastExe = null)
{
// Blijvende plek boven de exe; bestaat die nog niet maar wel de oude naast de exe, dan
// die eenmalig overnemen zodat de vensterstand niet verloren gaat bij de overstap. Met een
// eigen pad kan een tweede venster (bv. het instellingenscherm) zijn eigen stand bewaren.
pad ??= Instellingen.VensterstandPad;
padNaastExe ??= Instellingen.VensterstandPadNaastExe;
if (!File.Exists(pad) && File.Exists(padNaastExe))
{
try { File.Copy(padNaastExe, pad); } catch { }
}
if (!File.Exists(pad)) return new Vensterstand();
try
{
return JsonSerializer.Deserialize<Vensterstand>(File.ReadAllText(pad))
?? new Vensterstand();
}
catch
{
return new Vensterstand();
}
}
public void Bewaar(string? pad = null)
{
pad ??= Instellingen.VensterstandPad;
try
{
File.WriteAllText(pad,
JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true }));
}
catch
{
// Vensterpositie niet kunnen onthouden mag het afsluiten nooit in de weg zitten.
}
}
}
@@ -101,6 +101,11 @@
[SupplyParameterFromForm(FormName = "nieuwe-code")] private AanvraagGegevens Opnieuw { get; set; } = new();
[SupplyParameterFromQuery(Name = "terug")] private string? Terug { get; set; }
/// <summary>Alleen voor lokaal testen (zie MijnBesluit.Uitroller): het e-mailadres alvast
/// invullen op basis van ?email=, zodat je niet elke test opnieuw hoeft te typen. Vult alleen
/// in, logt niet in.</summary>
[SupplyParameterFromQuery(Name = "email")] private string? EmailQuery { get; set; }
private bool _codeVerstuurd;
private string? AfgeschermdAdres;
private string? Fout, Gelukt;
@@ -115,6 +120,9 @@
protected override void OnParametersSet()
{
if (!string.IsNullOrWhiteSpace(EmailQuery) && string.IsNullOrWhiteSpace(Aanvraag.Email))
Aanvraag.Email = EmailQuery;
if (AfgeschermdAdres is null && ToonCodeveld)
{
var adres = !string.IsNullOrWhiteSpace(Inlog.Email) ? Inlog.Email : Opnieuw.Email;