polish(mainwindow): empty state, table widths, strings, theme tooltip
Walks the v2 polish punch list against MainWindow. - Theme button tooltip is now "Theme (System / Dark / Light)" per the v2 shape brief, replacing the previous "Toggle theme (Ctrl+T)". - Participants table column widths match spec: Output 130px (was 150), ISO pill 100px (was 110). The 24px state LED, 110px audio meter, and 52px row height already matched. The 106px Preview thumbnail column and 32px gear-button column are intentional deviations (live thumbs were restored at 4944de5; per-ISO override gear added at the same time) and are now called out in the column-spec comment so a future reader doesn't try to "fix" them. - Empty-state placeholder finally renders when ParticipantCount == 0: mono sentence "no ndi sources yet — open teams and start a meeting" + a tertiary Refresh discovery button — exactly the copy specified by the shape brief's empty-states section. CountToVisibilityConverter is now declared in MainWindow.Resources (it shipped as a class but was never registered). - OnClosing wraps WindowStateStore.Save in a try/catch so a serialization or filesystem fault on shutdown can never block the window from closing. Save itself already swallows its own IO errors; this is defense-in-depth for anything that escapes. - MessageBox copy in MainWindow.xaml.cs (Hide/show Teams, Launch Teams, Stop Teams) moves to Properties/Strings.resx + a hand-written Properties/Strings.Designer.cs accessor. ResourceManager reads it by basename "TeamsISO.App.Properties.Strings"; LogicalName is set on the EmbeddedResource so the manifest name is predictable regardless of how MSBuild would otherwise compute it. Future-localization seam. OnLaunchTeamsRightClick's confirmation dialog is intentional — it guards a destructive mid-show action — and the code-behind comment now says so; the palette also offers Stop Teams as the keyboard surface, so the right-click affordance isn't the only one. Build clean (0 warnings, 0 errors); 160 tests still pass (56 App + 104 Engine, Category!=ndi&requires!=ndi filter). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
37390026b3
commit
33fca8e955
5 changed files with 186 additions and 18 deletions
|
|
@ -39,6 +39,7 @@
|
||||||
FalseValue="Visible"/>
|
FalseValue="Visible"/>
|
||||||
<conv:EnumDescriptionConverter x:Key="EnumDesc"/>
|
<conv:EnumDescriptionConverter x:Key="EnumDesc"/>
|
||||||
<conv:LevelThresholdConverter x:Key="LevelGate"/>
|
<conv:LevelThresholdConverter x:Key="LevelGate"/>
|
||||||
|
<conv:CountToVisibilityConverter x:Key="CountToVis"/>
|
||||||
</Window.Resources>
|
</Window.Resources>
|
||||||
|
|
||||||
<Window.InputBindings>
|
<Window.InputBindings>
|
||||||
|
|
@ -138,7 +139,7 @@
|
||||||
Command="{Binding ToggleThemeCommand}"
|
Command="{Binding ToggleThemeCommand}"
|
||||||
Padding="6,4"
|
Padding="6,4"
|
||||||
Margin="0,0,2,0"
|
Margin="0,0,2,0"
|
||||||
ToolTip="Toggle theme (Ctrl+T)">
|
ToolTip="Theme (System / Dark / Light)">
|
||||||
<Path Data="M 8,2 C 8,2 4,4 4,8 C 4,12 8,14 8,14 C 5,14 2,11 2,8 C 2,5 5,2 8,2 Z"
|
<Path Data="M 8,2 C 8,2 4,4 4,8 C 4,12 8,14 8,14 C 5,14 2,11 2,8 C 2,5 5,2 8,2 Z"
|
||||||
Stroke="{DynamicResource Wd.Text.Secondary}"
|
Stroke="{DynamicResource Wd.Text.Secondary}"
|
||||||
StrokeThickness="1.4"
|
StrokeThickness="1.4"
|
||||||
|
|
@ -410,7 +411,7 @@
|
||||||
<!--
|
<!--
|
||||||
Participants table — v2 "Studio Terminal" layout.
|
Participants table — v2 "Studio Terminal" layout.
|
||||||
|
|
||||||
Five columns:
|
Spec columns (from docs/shapes/2026-05-13-…studio-terminal.md):
|
||||||
1. State LED 24px — 8×8 hard-edged square. Filled cyan
|
1. State LED 24px — 8×8 hard-edged square. Filled cyan
|
||||||
when LIVE; filled coral on ERROR;
|
when LIVE; filled coral on ERROR;
|
||||||
filled amber on NO SIGNAL / STARTING;
|
filled amber on NO SIGNAL / STARTING;
|
||||||
|
|
@ -422,12 +423,22 @@
|
||||||
each lit when DisplayedAudioLevel
|
each lit when DisplayedAudioLevel
|
||||||
crosses its threshold (0.2, 0.4,
|
crosses its threshold (0.2, 0.4,
|
||||||
0.6, 0.8, 1.0). No averaging.
|
0.6, 0.8, 1.0). No averaging.
|
||||||
4. Output name 150px — JetBrains Mono 12 — the NDI source
|
4. Output name 130px — JetBrains Mono 12 — the NDI source
|
||||||
name TeamsISO broadcasts as.
|
name TeamsISO broadcasts as.
|
||||||
5. ISO toggle pill 110px — LIVE = cyan-muted fill with cyan
|
5. ISO toggle pill 100px — LIVE = cyan-muted fill with cyan
|
||||||
text; OFF = hollow neutral; ERROR
|
text; OFF = hollow neutral; ERROR
|
||||||
gets the existing trigger swap.
|
gets the existing trigger swap.
|
||||||
|
|
||||||
|
Deliberate deviations from the spec (operator preference, see
|
||||||
|
4944de5 — "restore live thumbnail preview column"):
|
||||||
|
• A 106px live thumbnail column sits between State LED and
|
||||||
|
Name. Replaces the table's previous role as the only place
|
||||||
|
to see what the operator is broadcasting; the pop-out
|
||||||
|
preview window is the secondary view.
|
||||||
|
• A 32px ghost-button cell on the right edge of Name opens
|
||||||
|
the per-ISO override dialog (framerate / resolution /
|
||||||
|
aspect / audio). Hidden on hover-out.
|
||||||
|
|
||||||
Row height 52 (down from 56). Active speaker = full-row
|
Row height 52 (down from 56). Active speaker = full-row
|
||||||
bg.active-speaker tint set by the global DataGridRow style
|
bg.active-speaker tint set by the global DataGridRow style
|
||||||
(avoids the impeccable side-stripe-border ban).
|
(avoids the impeccable side-stripe-border ban).
|
||||||
|
|
@ -438,6 +449,7 @@
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
CornerRadius="{StaticResource Radius.M}"
|
CornerRadius="{StaticResource Radius.M}"
|
||||||
Background="{DynamicResource Wd.Surface}">
|
Background="{DynamicResource Wd.Surface}">
|
||||||
|
<Grid>
|
||||||
<DataGrid x:Name="ParticipantsGrid"
|
<DataGrid x:Name="ParticipantsGrid"
|
||||||
ItemsSource="{Binding ParticipantsView}"
|
ItemsSource="{Binding ParticipantsView}"
|
||||||
AutoGenerateColumns="False"
|
AutoGenerateColumns="False"
|
||||||
|
|
@ -451,7 +463,8 @@
|
||||||
CanUserResizeRows="False"
|
CanUserResizeRows="False"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionUnit="FullRow"
|
SelectionUnit="FullRow"
|
||||||
RowHeight="52">
|
RowHeight="52"
|
||||||
|
Visibility="{Binding ParticipantCount, Converter={StaticResource CountToVis}}">
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
<!-- Col 1 — State LED. 8×8 hard-edged Rectangle.
|
<!-- Col 1 — State LED. 8×8 hard-edged Rectangle.
|
||||||
Default fill is hollow (transparent with stroke). DataTriggers
|
Default fill is hollow (transparent with stroke). DataTriggers
|
||||||
|
|
@ -601,7 +614,7 @@
|
||||||
|
|
||||||
<!-- Col 4 — Output name (mono). The NDI source name TeamsISO
|
<!-- Col 4 — Output name (mono). The NDI source name TeamsISO
|
||||||
will broadcast this participant as. -->
|
will broadcast this participant as. -->
|
||||||
<DataGridTemplateColumn Header="Output" Width="150" IsReadOnly="True">
|
<DataGridTemplateColumn Header="Output" Width="130" IsReadOnly="True">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Text="{Binding OutputName}"
|
<TextBlock Text="{Binding OutputName}"
|
||||||
|
|
@ -639,7 +652,7 @@
|
||||||
|
|
||||||
<!-- Col 5 — ISO toggle pill. LIVE = cyan-muted fill + cyan border + cyan text.
|
<!-- Col 5 — ISO toggle pill. LIVE = cyan-muted fill + cyan border + cyan text.
|
||||||
OFF = hollow neutral. Error states use the existing IsoToggle style. -->
|
OFF = hollow neutral. Error states use the existing IsoToggle style. -->
|
||||||
<DataGridTemplateColumn Header="ISO" Width="110">
|
<DataGridTemplateColumn Header="ISO" Width="100">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Button Command="{Binding ToggleIsoCommand}"
|
<Button Command="{Binding ToggleIsoCommand}"
|
||||||
|
|
@ -665,6 +678,30 @@
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
</DataGrid.Columns>
|
</DataGrid.Columns>
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
|
|
||||||
|
<!-- Empty-state placeholder. Renders when no NDI participants
|
||||||
|
have been discovered yet. Mono sentence + one tertiary
|
||||||
|
Refresh button — no illustration, no mascot, per the v2
|
||||||
|
shape brief's empty-states section. -->
|
||||||
|
<StackPanel HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Visibility="{Binding ParticipantCount, Converter={StaticResource CountToVis}, ConverterParameter=empty}">
|
||||||
|
<TextBlock Text="no ndi sources yet — open teams and start a meeting"
|
||||||
|
FontFamily="{StaticResource Wd.Font.Mono}"
|
||||||
|
FontSize="12"
|
||||||
|
Foreground="{DynamicResource Wd.Text.Tertiary}"
|
||||||
|
HorizontalAlignment="Center"/>
|
||||||
|
<Button Style="{StaticResource Wd.Button.Ghost}"
|
||||||
|
Command="{Binding RefreshDiscoveryCommand}"
|
||||||
|
Content="Refresh discovery (Ctrl+R)"
|
||||||
|
Padding="14,7"
|
||||||
|
Margin="0,14,0,0"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
FontFamily="{StaticResource Wd.Font.Mono}"
|
||||||
|
FontSize="11"
|
||||||
|
ToolTip="Rebuild the NDI finder"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,12 @@ public partial class MainWindow : Window
|
||||||
/// <summary>Persist the placement on close so next launch lands in the same spot.</summary>
|
/// <summary>Persist the placement on close so next launch lands in the same spot.</summary>
|
||||||
private void OnClosing(object? sender, System.ComponentModel.CancelEventArgs e)
|
private void OnClosing(object? sender, System.ComponentModel.CancelEventArgs e)
|
||||||
{
|
{
|
||||||
WindowStateStore.Save(this);
|
// A failure persisting window state must NEVER block the window from
|
||||||
|
// closing — operator's shutdown comes first. WindowStateStore.Save
|
||||||
|
// already swallows its own IO errors; this is defense-in-depth for
|
||||||
|
// anything that escapes (NRE, future regression, etc.).
|
||||||
|
try { WindowStateStore.Save(this); }
|
||||||
|
catch { /* best-effort: forgo placement memory for one launch */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Opens the About dialog — version, NDI runtime, build SHA.</summary>
|
/// <summary>Opens the About dialog — version, NDI runtime, build SHA.</summary>
|
||||||
|
|
@ -87,8 +92,8 @@ public partial class MainWindow : Window
|
||||||
if (!TeamsLauncher.IsRunning())
|
if (!TeamsLauncher.IsRunning())
|
||||||
{
|
{
|
||||||
MessageBox.Show(
|
MessageBox.Show(
|
||||||
"Microsoft Teams isn't running. Click the camera icon above to launch it first.",
|
Properties.Strings.HideShowTeams_NotRunning_Message,
|
||||||
"TeamsISO — Hide / show Teams",
|
Properties.Strings.HideShowTeams_Title,
|
||||||
MessageBoxButton.OK,
|
MessageBoxButton.OK,
|
||||||
MessageBoxImage.Information);
|
MessageBoxImage.Information);
|
||||||
return;
|
return;
|
||||||
|
|
@ -125,8 +130,8 @@ public partial class MainWindow : Window
|
||||||
if (!TeamsLauncher.TryLaunch(out var error))
|
if (!TeamsLauncher.TryLaunch(out var error))
|
||||||
{
|
{
|
||||||
MessageBox.Show(
|
MessageBox.Show(
|
||||||
$"Could not launch Microsoft Teams.\n\n{error}",
|
string.Format(Properties.Strings.LaunchTeams_Failed_MessageFormat, error),
|
||||||
"TeamsISO — Launch Teams",
|
Properties.Strings.LaunchTeams_Title,
|
||||||
MessageBoxButton.OK,
|
MessageBoxButton.OK,
|
||||||
MessageBoxImage.Warning);
|
MessageBoxImage.Warning);
|
||||||
}
|
}
|
||||||
|
|
@ -159,15 +164,19 @@ public partial class MainWindow : Window
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Right-click on the Launch button asks to stop Teams. Split out from the
|
/// Right-click on the Launch button asks to stop Teams. Split out from the
|
||||||
/// left-click so a normal click is "open / surface" rather than the previous
|
/// left-click so a normal click is "open / surface" rather than the previous
|
||||||
/// "open OR ambush you with a stop dialog".
|
/// "open OR ambush you with a stop dialog". The confirmation dialog here is
|
||||||
|
/// intentional — Stop Teams is a destructive mid-show action; explicit
|
||||||
|
/// confirmation is the right pattern, not the "ambush" anti-pattern that
|
||||||
|
/// was fixed for left-click. The palette also offers Stop Teams for
|
||||||
|
/// keyboard-first operators.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnLaunchTeamsRightClick(object sender, MouseButtonEventArgs e)
|
private void OnLaunchTeamsRightClick(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
if (!TeamsLauncher.IsRunning()) return;
|
if (!TeamsLauncher.IsRunning()) return;
|
||||||
|
|
||||||
var confirm = MessageBox.Show(
|
var confirm = MessageBox.Show(
|
||||||
"Microsoft Teams is currently running.\n\nClose all Teams windows now?",
|
Properties.Strings.StopTeams_Confirm_Message,
|
||||||
"TeamsISO — Stop Teams",
|
Properties.Strings.StopTeams_Title,
|
||||||
MessageBoxButton.YesNo,
|
MessageBoxButton.YesNo,
|
||||||
MessageBoxImage.Question);
|
MessageBoxImage.Question);
|
||||||
if (confirm != MessageBoxResult.Yes) return;
|
if (confirm != MessageBoxResult.Yes) return;
|
||||||
|
|
@ -177,9 +186,9 @@ public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
MessageBox.Show(
|
MessageBox.Show(
|
||||||
asked == 0
|
asked == 0
|
||||||
? "No Teams windows responded to close."
|
? Properties.Strings.StopTeams_NoneResponded
|
||||||
: $"Sent close to {asked} Teams window(s); some may still be exiting.",
|
: string.Format(Properties.Strings.StopTeams_AskedFormat, asked),
|
||||||
"TeamsISO — Stop Teams",
|
Properties.Strings.StopTeams_Title,
|
||||||
MessageBoxButton.OK,
|
MessageBoxButton.OK,
|
||||||
MessageBoxImage.Information);
|
MessageBoxImage.Information);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
src/TeamsISO.App/Properties/Strings.Designer.cs
generated
Normal file
36
src/TeamsISO.App/Properties/Strings.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Hand-written strongly-typed accessor for Properties/Strings.resx. Kept
|
||||||
|
// out of Visual Studio's "ResXFileCodeGenerator" auto-regeneration loop
|
||||||
|
// so the .csproj stays simple and the file doesn't churn on every save.
|
||||||
|
// If you add a key in Strings.resx, add a matching property here.
|
||||||
|
|
||||||
|
// The compiler treats `*.Designer.cs` as auto-generated and refuses
|
||||||
|
// nullable annotations without an explicit directive — opt in.
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Resources;
|
||||||
|
|
||||||
|
namespace TeamsISO.App.Properties;
|
||||||
|
|
||||||
|
internal static class Strings
|
||||||
|
{
|
||||||
|
private static readonly ResourceManager ResourceManager = new(
|
||||||
|
baseName: "TeamsISO.App.Properties.Strings",
|
||||||
|
assembly: typeof(Strings).Assembly);
|
||||||
|
|
||||||
|
public static CultureInfo? Culture { get; set; }
|
||||||
|
|
||||||
|
private static string Get(string key) =>
|
||||||
|
ResourceManager.GetString(key, Culture) ?? string.Empty;
|
||||||
|
|
||||||
|
public static string HideShowTeams_Title => Get(nameof(HideShowTeams_Title));
|
||||||
|
public static string HideShowTeams_NotRunning_Message => Get(nameof(HideShowTeams_NotRunning_Message));
|
||||||
|
|
||||||
|
public static string LaunchTeams_Title => Get(nameof(LaunchTeams_Title));
|
||||||
|
public static string LaunchTeams_Failed_MessageFormat => Get(nameof(LaunchTeams_Failed_MessageFormat));
|
||||||
|
|
||||||
|
public static string StopTeams_Title => Get(nameof(StopTeams_Title));
|
||||||
|
public static string StopTeams_Confirm_Message => Get(nameof(StopTeams_Confirm_Message));
|
||||||
|
public static string StopTeams_NoneResponded => Get(nameof(StopTeams_NoneResponded));
|
||||||
|
public static string StopTeams_AskedFormat => Get(nameof(StopTeams_AskedFormat));
|
||||||
|
}
|
||||||
75
src/TeamsISO.App/Properties/Strings.resx
Normal file
75
src/TeamsISO.App/Properties/Strings.resx
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
User-facing English strings shown by MainWindow's MessageBox prompts.
|
||||||
|
Pulled out of code-behind so a future localizer has a single seam to
|
||||||
|
translate. Strings.Designer.cs is a hand-rolled accessor backed by
|
||||||
|
ResourceManager — no Visual-Studio auto-regeneration needed.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
|
||||||
|
<resheader name="version"><value>2.0</value></resheader>
|
||||||
|
<resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader>
|
||||||
|
<resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader>
|
||||||
|
|
||||||
|
<data name="HideShowTeams_Title" xml:space="preserve">
|
||||||
|
<value>TeamsISO — Hide / show Teams</value>
|
||||||
|
</data>
|
||||||
|
<data name="HideShowTeams_NotRunning_Message" xml:space="preserve">
|
||||||
|
<value>Microsoft Teams isn't running. Click the camera icon above to launch it first.</value>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<data name="LaunchTeams_Title" xml:space="preserve">
|
||||||
|
<value>TeamsISO — Launch Teams</value>
|
||||||
|
</data>
|
||||||
|
<data name="LaunchTeams_Failed_MessageFormat" xml:space="preserve">
|
||||||
|
<value>Could not launch Microsoft Teams.
|
||||||
|
|
||||||
|
{0}</value>
|
||||||
|
<comment>{0} = error string from TeamsLauncher.TryLaunch.</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<data name="StopTeams_Title" xml:space="preserve">
|
||||||
|
<value>TeamsISO — Stop Teams</value>
|
||||||
|
</data>
|
||||||
|
<data name="StopTeams_Confirm_Message" xml:space="preserve">
|
||||||
|
<value>Microsoft Teams is currently running.
|
||||||
|
|
||||||
|
Close all Teams windows now?</value>
|
||||||
|
</data>
|
||||||
|
<data name="StopTeams_NoneResponded" xml:space="preserve">
|
||||||
|
<value>No Teams windows responded to close.</value>
|
||||||
|
</data>
|
||||||
|
<data name="StopTeams_AskedFormat" xml:space="preserve">
|
||||||
|
<value>Sent close to {0} Teams window(s); some may still be exiting.</value>
|
||||||
|
<comment>{0} = number of windows the launcher asked to close.</comment>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
|
|
@ -39,6 +39,17 @@
|
||||||
</AssemblyAttribute>
|
</AssemblyAttribute>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Strings.resx — user-facing English MessageBox copy. Embedded as a
|
||||||
|
.NET resource so ResourceManager can resolve TeamsISO.App.Properties.Strings
|
||||||
|
by basename. Strings.Designer.cs is hand-written (see file comment).
|
||||||
|
-->
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Update="Properties\Strings.resx">
|
||||||
|
<LogicalName>TeamsISO.App.Properties.Strings.resources</LogicalName>
|
||||||
|
</EmbeddedResource>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Wild Dragon brand assets — embedded as resources so the published binary is self-contained. -->
|
<!-- Wild Dragon brand assets — embedded as resources so the published binary is self-contained. -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Resource Include="Assets\dragon-mark.png" />
|
<Resource Include="Assets\dragon-mark.png" />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue