Files
DorkNet/launcher/MainWindow.xaml
T
Alexa 7c974e39ab launcher: Photon AppId setup walkthrough
The two empty "REALTIME APPID / VOICE APPID" textboxes assumed the
user already knew what Photon was, where to sign up, and which app
type to create. New 5-step wizard fills that gap:

1. What's Photon? — one-paragraph explanation tying back to DorkNet
2. Make an account — Open Dashboard button launches browser
3. Create a Realtime app — numbered click-by-click instructions
4. Create a Voice app — same, with a Skip button (voice is optional)
5. Paste your AppIds — text fields with live regex validation,
   region picker, Save commits to AppState

Hyphens optional on input; we normalise to canonical 8-4-4-4-12 form
before writing back. The existing Photon section keeps its three
fields for advanced users, with a "Setup walkthrough" button at the
top opening the wizard prefilled with current values.
2026-05-27 11:14:23 +03:00

470 lines
29 KiB
XML

<Window x:Class="DorkNet.Launcher.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DorkNet"
Width="960" Height="700"
MinWidth="720" MinHeight="540"
WindowStartupLocation="CenterScreen"
Background="{StaticResource Bg}"
TextElement.Foreground="{StaticResource Ink}"
FontFamily="Segoe UI Variable, Segoe UI"
TextOptions.TextFormattingMode="Ideal"
TextOptions.TextRenderingMode="ClearType">
<!-- Soft radial-ish glow in the top-left corner of the window for
atmosphere. WPF doesn't have a true radial gradient on Background
without nested Border tricks, so we stack a linear-gradient
overlay on top of the solid Bg. -->
<Grid>
<Grid.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#2A1E48" Offset="0" />
<GradientStop Color="#16102A" Offset="0.6" />
<GradientStop Color="#16102A" Offset="1" />
</LinearGradientBrush>
</Grid.Background>
<Grid Margin="36,28">
<!-- View slots — only one Visible at a time; code-behind toggles. -->
<!-- ── First-run wizard ───────────────────────────────────── -->
<StackPanel x:Name="FirstRunView" Visibility="Visible"
VerticalAlignment="Center">
<StackPanel HorizontalAlignment="Center" Margin="0,0,0,40">
<TextBlock Text="DorkNet"
FontSize="56" FontWeight="Bold"
Foreground="{StaticResource WordmarkBrush}"
HorizontalAlignment="Center"
FontFamily="Segoe UI Variable, Segoe UI" />
<TextBlock Text="Play together on your own Rec Room server"
Foreground="{StaticResource InkSoft}"
FontSize="16"
HorizontalAlignment="Center" Margin="0,6,0,0" />
</StackPanel>
<Grid Margin="40,0" MaxWidth="780" HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Style="{StaticResource ChoiceCardBtn}"
Background="{StaticResource CardBgHost}"
Click="OnPickHost">
<StackPanel>
<Border CornerRadius="14" Width="56" Height="56"
Background="#3D2B57" Margin="0,0,0,18"
HorizontalAlignment="Left">
<TextBlock Text="🛠" FontSize="32"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<TextBlock Text="Host a server" FontSize="22"
FontWeight="Bold" Margin="0,0,0,10" />
<TextBlock Foreground="{StaticResource InkSoft}"
FontSize="13" TextWrapping="Wrap" LineHeight="20"
Text="Run a server for you and your friends. Pick a Rec Room version, the launcher downloads the matching server, opens a tunnel, and hands you a shareable join code." />
<Border Margin="0,18,0,0" CornerRadius="8"
Background="{StaticResource Brand}" Padding="14,8"
HorizontalAlignment="Left">
<TextBlock Text="Get started →" Foreground="White"
FontWeight="SemiBold" FontSize="13" />
</Border>
</StackPanel>
</Button>
<Button Grid.Column="2" Style="{StaticResource ChoiceCardBtn}"
Background="{StaticResource CardBgJoin}"
Click="OnPickJoin">
<StackPanel>
<Border CornerRadius="14" Width="56" Height="56"
Background="#1C3C4A" Margin="0,0,0,18"
HorizontalAlignment="Left">
<TextBlock Text="🎮" FontSize="32"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<TextBlock Text="Join a friend" FontSize="22"
FontWeight="Bold" Margin="0,0,0,10" />
<TextBlock Foreground="{StaticResource InkSoft}"
FontSize="13" TextWrapping="Wrap" LineHeight="20"
Text="Paste a join code from your host, point at your Rec Room install. The launcher fetches the matching patcher and gets you connected." />
<Border Margin="0,18,0,0" CornerRadius="8"
Background="{StaticResource Teal}" Padding="14,8"
HorizontalAlignment="Left">
<TextBlock Text="Get started →" Foreground="#0E0A1F"
FontWeight="SemiBold" FontSize="13" />
</Border>
</StackPanel>
</Button>
</Grid>
<TextBlock Text="You can switch modes anytime under Settings."
HorizontalAlignment="Center"
Foreground="{StaticResource InkFaint}"
FontSize="12" Margin="0,32,0,0" />
</StackPanel>
<!-- ── Host view ──────────────────────────────────────────── -->
<Grid x:Name="HostView" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header bar with wordmark + view label + settings button -->
<Grid Grid.Row="0" Margin="0,0,0,28">
<StackPanel Orientation="Horizontal">
<TextBlock Text="DorkNet" FontSize="22" FontWeight="Bold"
Foreground="{StaticResource WordmarkBrush}" />
<Border Style="{StaticResource StatusChip}" Margin="14,0,0,0"
VerticalAlignment="Center">
<TextBlock Text="HOST MODE" FontSize="10"
FontWeight="Bold" Foreground="{StaticResource Brand}" />
</Border>
</StackPanel>
<Button HorizontalAlignment="Right" Style="{StaticResource GhostBtn}"
Content="Settings" Click="OnOpenSettings" />
</Grid>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto"
Padding="0,0,8,0">
<StackPanel>
<!-- Setup panel -->
<Border Style="{StaticResource Panel}" Margin="0,0,0,16">
<StackPanel>
<TextBlock Style="{StaticResource SectionTitle}" Text="Setup" />
<TextBlock Style="{StaticResource FieldLabel}" Text="REC ROOM INSTALL" />
<DockPanel Margin="0,0,0,16">
<Button DockPanel.Dock="Right" Style="{StaticResource Btn}"
Content="Pick…" Click="OnPickRecRoom" Margin="8,0,0,0" />
<Border Background="{StaticResource BgDeeper}"
BorderBrush="{StaticResource BorderSoft}" BorderThickness="1"
CornerRadius="10" Padding="12,10">
<TextBlock x:Name="HostRecRoomPath"
Text="(not picked)"
Foreground="{StaticResource InkSoft}"
TextTrimming="CharacterEllipsis" FontSize="13" />
</Border>
</DockPanel>
<TextBlock Style="{StaticResource FieldLabel}" Text="SERVER NAME (SHOWN TO JOINERS)" />
<TextBox x:Name="ServerNameInput" Margin="0,0,0,16"
LostFocus="OnServerNameChanged" />
<TextBlock Style="{StaticResource FieldLabel}" Text="REC ROOM VERSION TO HOST FOR" />
<ComboBox x:Name="VersionSelect" Margin="0,0,0,16" />
<TextBlock Style="{StaticResource FieldLabel}" Text="WHO CAN JOIN?" />
<StackPanel Orientation="Horizontal">
<RadioButton x:Name="HostModeInternet" GroupName="HostingMode"
Content="Friends anywhere (internet via Cloudflare)"
IsChecked="True" Checked="OnHostingModeChanged"
Foreground="{StaticResource Ink}" />
<RadioButton x:Name="HostModeLocal" GroupName="HostingMode"
Content="Same WiFi only (no internet needed)"
Margin="22,0,0,0" Checked="OnHostingModeChanged"
Foreground="{StaticResource Ink}" />
</StackPanel>
</StackPanel>
</Border>
<!-- Photon panel -->
<Border Style="{StaticResource Panel}" Margin="0,0,0,16">
<StackPanel>
<Grid Margin="0,0,0,12">
<TextBlock Style="{StaticResource SectionTitle}"
Text="Photon AppIds" Margin="0" />
<Button HorizontalAlignment="Right" Style="{StaticResource Btn}"
Content="Setup walkthrough" Click="OnOpenPhotonWizard"
Padding="14,6" FontSize="12" />
</Grid>
<TextBlock Foreground="{StaticResource InkSoft}" FontSize="12"
Margin="0,0,0,12" TextWrapping="Wrap">
Photon Cloud powers in-room realtime sync. Free to register at
<Hyperlink NavigateUri="https://dashboard.photonengine.com"
Foreground="{StaticResource Brand}"
RequestNavigate="OnNavigate">dashboard.photonengine.com</Hyperlink>;
DorkNet never bundles one. First time? Click "Setup walkthrough" above.
</TextBlock>
<TextBlock Style="{StaticResource FieldLabel}" Text="REALTIME APPID" />
<TextBox x:Name="PhotonAppIdInput" Margin="0,0,0,12"
LostFocus="OnPhotonChanged" />
<TextBlock Style="{StaticResource FieldLabel}" Text="VOICE APPID (OPTIONAL)" />
<TextBox x:Name="PhotonVoiceAppIdInput" Margin="0,0,0,12"
LostFocus="OnPhotonChanged" />
<TextBlock Style="{StaticResource FieldLabel}" Text="CLOUD REGION" />
<ComboBox x:Name="PhotonRegionSelect"
SelectionChanged="OnPhotonChanged">
<ComboBoxItem Content="US" Tag="us" />
<ComboBoxItem Content="EU" Tag="eu" />
<ComboBoxItem Content="Asia" Tag="asia" />
<ComboBoxItem Content="Japan" Tag="jp" />
<ComboBoxItem Content="South America" Tag="sa" />
<ComboBoxItem Content="Korea" Tag="kr" />
<ComboBoxItem Content="India" Tag="in" />
<ComboBoxItem Content="Australia" Tag="au" />
</ComboBox>
</StackPanel>
</Border>
<!-- Action area -->
<StackPanel Orientation="Horizontal" Margin="0,4,0,16">
<Button x:Name="HostStartBtn" Style="{StaticResource PrimaryBtn}"
Content="Start hosting" Click="OnHostStart" />
<Button x:Name="HostStopBtn" Style="{StaticResource Btn}"
Content="Stop" Click="OnHostStop"
Margin="10,0,0,0" Visibility="Collapsed" />
<Button x:Name="HostLaunchBtn" Style="{StaticResource TealBtn}"
Content="Launch Rec Room" Click="OnLaunchRecRoom"
Margin="10,0,0,0" Visibility="Collapsed" />
</StackPanel>
<!-- Progress steps — populated when the user hits
"Start hosting". Hidden until then so the
idle Host view stays clean. -->
<Border x:Name="HostStepsPanel" Style="{StaticResource Panel}"
Margin="0,0,0,16" Visibility="Collapsed">
<StackPanel>
<TextBlock Style="{StaticResource SectionTitle}" Text="Progress" />
<ItemsControl x:Name="HostStepsList"
ItemTemplate="{StaticResource StepRowTemplate}" />
<TextBlock x:Name="HostStatusText"
Foreground="{StaticResource InkSoft}"
TextWrapping="Wrap" FontSize="13"
Margin="0,4,0,0" />
</StackPanel>
</Border>
<!-- Join code panel — appears once the host is up.
QR encodes the same code text; friends can
scan with a phone to grab it without typing. -->
<Border x:Name="JoinCodePanel" Visibility="Collapsed"
Style="{StaticResource Panel}"
Background="{StaticResource CardBgHost}">
<StackPanel>
<TextBlock Style="{StaticResource SectionTitle}"
Text="🎉 Share this with friends" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="White"
CornerRadius="10" Padding="8"
VerticalAlignment="Top"
Margin="0,0,16,0">
<Image x:Name="JoinCodeQrImage"
Width="180" Height="180"
RenderOptions.BitmapScalingMode="NearestNeighbor"
SnapsToDevicePixels="True"
UseLayoutRounding="True" />
</Border>
<StackPanel Grid.Column="1">
<TextBlock Style="{StaticResource FieldLabel}"
Text="JOIN CODE" />
<Border Background="{StaticResource BgDeeper}"
BorderBrush="{StaticResource Border}" BorderThickness="1"
CornerRadius="10" Padding="12,10">
<TextBox x:Name="JoinCodeText" IsReadOnly="True"
FontFamily="Cascadia Mono, Consolas" FontSize="12"
Background="Transparent" BorderThickness="0"
TextWrapping="Wrap" />
</Border>
<WrapPanel Margin="0,12,0,0">
<Button Style="{StaticResource PrimaryBtn}"
Content="Copy code" Click="OnCopyJoinCode" />
<Button Style="{StaticResource Btn}"
Content="Copy as message"
Click="OnCopyJoinCodeMessage"
Margin="10,0,0,0" />
<Button Style="{StaticResource Btn}"
Content="Email to friend"
Click="OnEmailJoinCode"
Margin="10,0,0,0" />
</WrapPanel>
<TextBlock Foreground="{StaticResource InkFaint}" FontSize="11"
TextWrapping="Wrap" Margin="0,12,0,0"
Text="Your friend opens DorkNet, picks &quot;Join a friend&quot;, and pastes the code. The QR encodes the same text — handy for getting it onto a phone for forwarding." />
</StackPanel>
</Grid>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</Grid>
<!-- ── Join view ──────────────────────────────────────────── -->
<Grid x:Name="JoinView" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="0,0,0,28">
<StackPanel Orientation="Horizontal">
<TextBlock Text="DorkNet" FontSize="22" FontWeight="Bold"
Foreground="{StaticResource WordmarkBrush}" />
<Border Style="{StaticResource StatusChip}" Margin="14,0,0,0"
VerticalAlignment="Center">
<TextBlock Text="JOIN MODE" FontSize="10"
FontWeight="Bold" Foreground="{StaticResource Teal}" />
</Border>
</StackPanel>
<Button HorizontalAlignment="Right" Style="{StaticResource GhostBtn}"
Content="Settings" Click="OnOpenSettings" />
</Grid>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto"
Padding="0,0,8,0">
<StackPanel>
<Border Style="{StaticResource Panel}" Margin="0,0,0,16">
<StackPanel>
<TextBlock Style="{StaticResource SectionTitle}" Text="Join code" />
<TextBlock Style="{StaticResource FieldLabel}"
Text="PASTE THE JOIN CODE YOUR HOST SHARED" />
<TextBox x:Name="JoinCodeInput"
AcceptsReturn="True" TextWrapping="Wrap"
Height="80" VerticalContentAlignment="Top"
FontFamily="Cascadia Mono, Consolas" FontSize="12"
TextChanged="OnJoinCodeInputChanged" />
<Button Style="{StaticResource Btn}"
Content="Preview" Click="OnDecodeJoinCode"
HorizontalAlignment="Left" Margin="0,10,0,0" />
</StackPanel>
</Border>
<Border x:Name="JoinPreviewBorder" Visibility="Collapsed"
Style="{StaticResource Panel}"
Background="{StaticResource CardBgJoin}"
Margin="0,0,0,16">
<StackPanel>
<TextBlock Style="{StaticResource SectionTitle}" Text="Server preview" />
<TextBlock x:Name="JoinPreviewText"
Foreground="{StaticResource Ink}"
TextWrapping="Wrap" FontSize="14" />
</StackPanel>
</Border>
<Border Style="{StaticResource Panel}" Margin="0,0,0,16">
<StackPanel>
<TextBlock Style="{StaticResource SectionTitle}" Text="Your Rec Room install" />
<TextBlock Style="{StaticResource FieldLabel}" Text="LOCATION" />
<DockPanel>
<Button DockPanel.Dock="Right" Style="{StaticResource Btn}"
Content="Pick…" Click="OnPickRecRoom" Margin="8,0,0,0" />
<Border Background="{StaticResource BgDeeper}"
BorderBrush="{StaticResource BorderSoft}" BorderThickness="1"
CornerRadius="10" Padding="12,10">
<TextBlock x:Name="JoinRecRoomPath"
Text="(not picked)"
Foreground="{StaticResource InkSoft}"
TextTrimming="CharacterEllipsis" FontSize="13" />
</Border>
</DockPanel>
</StackPanel>
</Border>
<StackPanel Orientation="Horizontal" Margin="0,4,0,16">
<Button x:Name="JoinApplyBtn" Style="{StaticResource PrimaryBtn}"
Content="Patch" Click="OnJoinApply"
IsEnabled="False" />
<Button x:Name="JoinLaunchBtn" Style="{StaticResource TealBtn}"
Content="Launch Rec Room" Click="OnLaunchRecRoom"
Margin="10,0,0,0" Visibility="Collapsed" />
</StackPanel>
<Border x:Name="JoinStepsPanel" Style="{StaticResource Panel}"
Visibility="Collapsed">
<StackPanel>
<TextBlock Style="{StaticResource SectionTitle}" Text="Progress" />
<ItemsControl x:Name="JoinStepsList"
ItemTemplate="{StaticResource StepRowTemplate}" />
<TextBlock x:Name="JoinStatusText"
Foreground="{StaticResource InkSoft}"
TextWrapping="Wrap" FontSize="13"
Margin="0,4,0,0" />
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</Grid>
<!-- ── Settings view ──────────────────────────────────────── -->
<Grid x:Name="SettingsView" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="0,0,0,28">
<StackPanel Orientation="Horizontal">
<TextBlock Text="DorkNet" FontSize="22" FontWeight="Bold"
Foreground="{StaticResource WordmarkBrush}" />
<Border Style="{StaticResource StatusChip}" Margin="14,0,0,0"
VerticalAlignment="Center">
<TextBlock Text="SETTINGS" FontSize="10"
FontWeight="Bold" Foreground="{StaticResource Sunshine}" />
</Border>
</StackPanel>
<Button HorizontalAlignment="Right" Style="{StaticResource GhostBtn}"
Content="← Back" Click="OnBackFromSettings" />
</Grid>
<StackPanel Grid.Row="1">
<Border Style="{StaticResource Panel}" Margin="0,0,0,16">
<StackPanel>
<TextBlock Style="{StaticResource SectionTitle}" Text="Switch mode" />
<StackPanel Orientation="Horizontal">
<Button Style="{StaticResource Btn}"
Content="Host" Click="OnPickHost" />
<Button Style="{StaticResource Btn}"
Content="Join" Click="OnPickJoin"
Margin="10,0,0,0" />
</StackPanel>
</StackPanel>
</Border>
<Border Style="{StaticResource Panel}" Margin="0,0,0,16">
<StackPanel>
<TextBlock Style="{StaticResource SectionTitle}" Text="Cache locations" />
<TextBlock x:Name="CachePathsText"
Foreground="{StaticResource InkSoft}"
FontSize="12" TextWrapping="Wrap"
FontFamily="Cascadia Mono, Consolas" />
</StackPanel>
</Border>
<Border Style="{StaticResource Panel}">
<StackPanel>
<TextBlock Style="{StaticResource SectionTitle}" Text="About" />
<TextBlock Foreground="{StaticResource InkSoft}" FontSize="13">
DorkNet Launcher v0.1 ·
<Hyperlink NavigateUri="https://github.com/DorkSquadRR/DorkNet"
Foreground="{StaticResource Brand}"
RequestNavigate="OnNavigate">github.com/DorkSquadRR/DorkNet</Hyperlink>
· AGPL-3.0
</TextBlock>
<TextBlock Foreground="{StaticResource InkFaint}" FontSize="11"
Margin="0,8,0,0">
Not affiliated with Rec Room Inc. or Against Gravity.
</TextBlock>
</StackPanel>
</Border>
</StackPanel>
</Grid>
</Grid>
</Grid>
</Window>