about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTheArcaneBrony <myrainbowdash949@gmail.com>2023-06-14 22:26:39 +0200
committerTheArcaneBrony <myrainbowdash949@gmail.com>2023-06-14 22:26:39 +0200
commitb48f78a381058c188ed61e6f372fbf86d95ad2f9 (patch)
tree178e5d85a92da68b42ffcc68cbbba9b8ea3c40fd
parentChange syntax style (diff)
downloadMatrixUtils-b48f78a381058c188ed61e6f372fbf86d95ad2f9.tar.xz
Add changes
-rw-r--r--MatrixRoomUtils.Core/Room.cs13
-rw-r--r--MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs23
-rw-r--r--MatrixRoomUtils.Web/FileUploadTest.razor2
-rw-r--r--MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj10
-rw-r--r--MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor25
-rw-r--r--MatrixRoomUtils.Web/Pages/RoomManager/RoomManagerTimeline.razor2
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/Index.razor22
-rw-r--r--MatrixRoomUtils.Web/Shared/RoomList.razor56
-rw-r--r--MatrixRoomUtils.Web/Shared/RoomList.razor.css8
-rw-r--r--MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor24
-rw-r--r--MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor42
-rw-r--r--MatrixRoomUtils.Web/Shared/RoomListItem.razor6
-rw-r--r--MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor2
-rw-r--r--MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor.css5
-rw-r--r--MatrixRoomUtils.Web/Shared/SimpleComponents/LinkButton.razor13
-rw-r--r--MatrixRoomUtils.Web/_Imports.razor7
-rw-r--r--MatrixRoomUtils.sln.DotSettings1
17 files changed, 240 insertions, 21 deletions
diff --git a/MatrixRoomUtils.Core/Room.cs b/MatrixRoomUtils.Core/Room.cs
index a867c0c..2d6dc46 100644
--- a/MatrixRoomUtils.Core/Room.cs
+++ b/MatrixRoomUtils.Core/Room.cs
@@ -3,6 +3,7 @@ using System.Text.Json;
 using System.Text.Json.Serialization;
 using System.Web;
 using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.RoomTypes;
 
 namespace MatrixRoomUtils.Core;
 
@@ -12,6 +13,8 @@ public class Room {
     public Room(HttpClient httpClient, string roomId) {
         _httpClient = httpClient;
         RoomId = roomId;
+        if(GetType() != typeof(SpaceRoom))
+            AsSpace = new SpaceRoom(_httpClient, RoomId);
     }
 
     public string RoomId { get; set; }
@@ -137,6 +140,16 @@ public class Room {
 
         return res.Value.Deserialize<CreateEvent>() ?? new CreateEvent();
     }
+
+    public async Task<string?> GetRoomType() {
+        var res = await GetStateAsync("m.room.create");
+        if (!res.HasValue) return null;
+        if (res.Value.TryGetProperty("type", out var type)) return type.GetString();
+        return null;
+    }
+
+
+    public SpaceRoom AsSpace;
 }
 
 public class MessagesResponse {
diff --git a/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs b/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs
new file mode 100644
index 0000000..e8d4823
--- /dev/null
+++ b/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs
@@ -0,0 +1,23 @@
+using System.Text.Json;
+using MatrixRoomUtils.Core.Extensions;
+
+namespace MatrixRoomUtils.Core.RoomTypes;
+
+public class SpaceRoom : Room {
+    public SpaceRoom(HttpClient httpClient, string roomId) : base(httpClient, roomId) { }
+
+    public async Task<List<Room>> GetRoomsAsync(bool includeRemoved = false) {
+        var rooms = new List<Room>();
+        var state = await GetStateAsync("");
+        if (state != null) {
+            var states = state.Value.Deserialize<StateEventResponse<object>[]>()!;
+            foreach (var stateEvent in states.Where(x => x.Type == "m.space.child")) {
+                var roomId = stateEvent.StateKey;
+                if(stateEvent.Content.ToJson() != "{}" || includeRemoved)
+                    rooms.Add(await RuntimeCache.CurrentHomeServer.GetRoom(roomId));
+            }
+        }
+
+        return rooms;
+    }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/FileUploadTest.razor b/MatrixRoomUtils.Web/FileUploadTest.razor
index 478a3e4..cbdb5ce 100644
--- a/MatrixRoomUtils.Web/FileUploadTest.razor
+++ b/MatrixRoomUtils.Web/FileUploadTest.razor
@@ -6,7 +6,7 @@
 
 @code {
 
-    private async void FilePicked(InputFileChangeEventArgs obj) {
+    private async Task FilePicked(InputFileChangeEventArgs obj) {
         Console.WriteLine("FilePicked");
         Console.WriteLine(obj.File.Name);
         Console.WriteLine(obj.File.Size);
diff --git a/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj b/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj
index c76452f..e1511c4 100644
--- a/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj
+++ b/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj
@@ -7,17 +7,17 @@
     </PropertyGroup>
 
     <ItemGroup>
-        <PackageReference Include="Blazored.LocalStorage" Version="4.3.0"/>
-        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.3"/>
-        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.3" PrivateAssets="all"/>
+        <PackageReference Include="Blazored.LocalStorage" Version="4.3.0" />
+        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.3" />
+        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.3" PrivateAssets="all" />
     </ItemGroup>
 
     <ItemGroup>
-        <ProjectReference Include="..\MatrixRoomUtils.Core\MatrixRoomUtils.Core.csproj"/>
+        <ProjectReference Include="..\MatrixRoomUtils.Core\MatrixRoomUtils.Core.csproj" />
     </ItemGroup>
 
     <ItemGroup>
-        <Folder Include="Shared\TimelineComponents\TimelineMessageComponents"/>
+        <Folder Include="Shared\TimelineComponents\TimelineMessageComponents" />
     </ItemGroup>
 
 </Project>
diff --git a/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor b/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor
index e6f95c7..a62362b 100644
--- a/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor
+++ b/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor
@@ -18,19 +18,26 @@
 <button class="btn btn-primary" @onclick="Search">Search</button>
 <br/>
 
+@if (Results.Count > 0) {
+    <p>Found @Results.Count rooms</p>
+}
+
 @foreach (var res in Results) {
     <div style="background-color: #ffffff11; border-radius: 0.5em; display: block; margin-top: 4px; padding: 4px;">
         <RoomListItem RoomName="@res.Name" RoomId="@res.RoomId"></RoomListItem>
         <p>
-            @res.CanonicalAlias
+            @if (!string.IsNullOrWhiteSpace(res.CanonicalAlias)) {
+                <span>@res.CanonicalAlias (@res.RoomId)</span><br/>
+            }
+            else {
+                <span>@res.RoomId</span><br/>
+            }
             @if (!string.IsNullOrWhiteSpace(res.Creator)) {
-                <span>
-                    , created by <InlineUserItem UserId="@res.Creator"></InlineUserItem>
-                </span>
+                <span>Created by <InlineUserItem UserId="@res.Creator"></InlineUserItem></span><br/>
             }
         </p>
-        <p>@res.StateEvents state events</p>
-        <p>@res.JoinedMembers members, of which @res.JoinedLocalMembers are on this server</p>
+        <span>@res.StateEvents state events</span><br/>
+        <span>@res.JoinedMembers members, of which @res.JoinedLocalMembers are on this server</span>
     </div>
 }
 
@@ -41,11 +48,11 @@
     public string? OrderBy { get; set; }
 
     [Parameter]
-    [SupplyParameterFromQuery(Name = "search_term")]
+    [SupplyParameterFromQuery(Name = "name_search")]
     public string SearchTerm { get; set; }
 
     [Parameter]
-    [SupplyParameterFromQuery(Name = "content_search_term")]
+    [SupplyParameterFromQuery(Name = "content_search")]
     public string ContentSearchTerm { get; set; }
 
     [Parameter]
@@ -69,6 +76,8 @@
             var room = searchRooms.Current;
             Console.WriteLine("Hit: " + room.ToJson(false));
             Results.Add(room);
+            if (Results.Count % 10 == 0)
+                StateHasChanged();
         }
     }
 
diff --git a/MatrixRoomUtils.Web/Pages/RoomManager/RoomManagerTimeline.razor b/MatrixRoomUtils.Web/Pages/RoomManager/RoomManagerTimeline.razor
index 2db7cab..b90cc09 100644
--- a/MatrixRoomUtils.Web/Pages/RoomManager/RoomManagerTimeline.razor
+++ b/MatrixRoomUtils.Web/Pages/RoomManager/RoomManagerTimeline.razor
@@ -1,4 +1,4 @@
-@page "/RoomManager/Timeline/{RoomId}"
+@page "/Rooms/{RoomId}/Timeline"
 @using MatrixRoomUtils.Web.Shared.TimelineComponents
 <h3>RoomManagerTimeline</h3>
 <hr/>
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
new file mode 100644
index 0000000..17551c9
--- /dev/null
+++ b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
@@ -0,0 +1,22 @@
+@page "/Rooms"
+<h3>Room list</h3>
+
+@if (Rooms != null) {
+    <RoomList Rooms="Rooms"></RoomList>
+}
+
+
+@code {
+
+    private List<Room> Rooms { get; set; }
+    
+    protected override async Task OnInitializedAsync()
+    {
+        await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
+
+        Rooms = await RuntimeCache.CurrentHomeServer.GetJoinedRooms();
+
+        await base.OnInitializedAsync();
+    }
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/RoomList.razor b/MatrixRoomUtils.Web/Shared/RoomList.razor
new file mode 100644
index 0000000..ca93fa6
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/RoomList.razor
@@ -0,0 +1,56 @@
+@using MatrixRoomUtils.Web.Shared.RoomListComponents;
+<p>@Rooms.Count rooms total, @RoomsWithTypes.Sum(x=>x.Value.Count) fetched so far...</p>
+@foreach (var category in RoomsWithTypes.OrderBy(x => x.Value.Count)) {
+    <RoomListCategory Category="@category"></RoomListCategory>
+}
+
+@code {
+
+    [Parameter]
+    public List<Room> Rooms { get; set; }
+
+    Dictionary<string, List<Room>> RoomsWithTypes = new();
+
+    protected override async Task OnInitializedAsync() {
+        if (RoomsWithTypes.Any()) return;
+
+        await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
+
+        var tasks = Rooms.Select(AddRoom);
+        await Task.WhenAll(tasks);
+
+        await base.OnInitializedAsync();
+    }
+
+    private string GetRoomTypeName(string? roomType) => roomType switch {
+        "m.space" => "Space",
+        "msc3588.stories.stories-room" => "Story room",
+        null => "Room",
+        _ => roomType
+        };
+
+    
+    private static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(8, 8);
+    private async Task AddRoom(Room room) {
+        await _semaphoreSlim.WaitAsync();
+        var roomType = GetRoomTypeName(await room.GetRoomType());
+
+        if (roomType == "Room") {
+            var shortcodeState = await room.GetStateAsync("org.matrix.mjolnir.shortcode");
+            if (shortcodeState.HasValue) roomType = "Legacy policy room";
+        }
+        
+        if (!RoomsWithTypes.ContainsKey(roomType)) {
+            RoomsWithTypes.Add(roomType, new List<Room>());
+        }
+        RoomsWithTypes[roomType].Add(room);
+
+    // if (RoomsWithTypes.Count % 10 == 0)
+        StateHasChanged();
+        await Task.Delay(100);
+        _semaphoreSlim.Release();
+    }
+
+    private bool _isSpaceChildrenOpen = false;
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/RoomList.razor.css b/MatrixRoomUtils.Web/Shared/RoomList.razor.css
new file mode 100644
index 0000000..a159305
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/RoomList.razor.css
@@ -0,0 +1,8 @@
+.room-list-item {
+    background-color: #ffffff11;
+    border-radius: 0.5em;
+    display: block;
+    margin-top: 4px;
+    padding: 4px;
+    width: fit-content;
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor
new file mode 100644
index 0000000..ecdcc68
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor
@@ -0,0 +1,24 @@
+<details>
+    <summary>@roomType (@rooms.Count)</summary>
+    @foreach (var room in rooms) {
+        <div class="room-list-item">
+            <RoomListItem Room="@room" ShowOwnProfile="@(roomType == "Room")"></RoomListItem>
+            <MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton href="@($"/Rooms/{room.RoomId}/Timeline")">View timeline</MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton>
+            
+            @if (roomType == "Space") {
+                <RoomListSpace Space="@room"></RoomListSpace>
+            }
+        </div>
+    }
+</details>
+<br/>
+
+@code {
+
+    [Parameter]
+    public KeyValuePair<string, List<Room>> Category { get; set; }
+
+    private string roomType => Category.Key;
+    private List<Room> rooms => Category.Value;
+    
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor
new file mode 100644
index 0000000..c90ae8f
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor
@@ -0,0 +1,42 @@
+<LinkButton href="@($"/Rooms/{Space.RoomId}/Space")">Manage space</LinkButton>
+
+<br/>
+<details @ontoggle="SpaceChildrenOpened">
+    <summary>@Children.Count children</summary>
+    @if (_shouldRenderChildren) {
+        <p>Breadcrumb: @Breadcrumbs</p>
+        <div style="margin-left: 8px;">
+            <RoomList Rooms="Children"></RoomList>
+        </div>
+    }
+</details>
+
+@code {
+
+    [Parameter]
+    public Room Space { get; set; }
+
+    [Parameter, CascadingParameter]
+    public string? Breadcrumbs {
+        get => _breadcrumbs + Space.RoomId;
+        set => _breadcrumbs = value;
+    }
+
+    private List<Room> Children { get; set; } = new();
+
+    protected override async Task OnInitializedAsync() {
+        if (Breadcrumbs == null) throw new ArgumentNullException(nameof(Breadcrumbs));
+        Children = (await Space.AsSpace.GetRoomsAsync()).Where(x => !Breadcrumbs.Contains(x.RoomId)).ToList();
+        await base.OnInitializedAsync();
+    }
+
+    private bool _shouldRenderChildren = false;
+    private string? _breadcrumbs;
+
+    private async Task SpaceChildrenOpened() {
+        if (_shouldRenderChildren) return;
+        _shouldRenderChildren = true;
+        Console.WriteLine($"[RoomList] Rendering children of {Space.RoomId}");
+    }
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/RoomListItem.razor b/MatrixRoomUtils.Web/Shared/RoomListItem.razor
index f58ab3a..6dc0683 100644
--- a/MatrixRoomUtils.Web/Shared/RoomListItem.razor
+++ b/MatrixRoomUtils.Web/Shared/RoomListItem.razor
@@ -2,7 +2,7 @@
 @using System.Text.Json
 <div class="roomListItem" style="background-color: #ffffff11; border-radius: 25px; margin: 8px; width: fit-Content; @(hasDangerousRoomVersion ? "border: red 4px solid;" : hasOldRoomVersion ? "border: #FF0 1px solid;" : "")">
     @if (ShowOwnProfile) {
-        <img class="imageUnloaded @(string.IsNullOrWhiteSpace(profileAvatar) ? "" : "imageLoaded")" style="@(ChildContent != null ? "vertical-align: baseline;" : "") width: 32px; height: 32px; border-radius: 50%; @(hasCustomProfileAvatar ? "border-color: red; border-width: 3px; border-style: dashed;" : "")" src="@(profileAvatar ?? "/icon-192.png")" @onload="Callback"/>
+        <img class="imageUnloaded @(string.IsNullOrWhiteSpace(profileAvatar) ? "" : "imageLoaded")" style="@(ChildContent != null ? "vertical-align: baseline;" : "") width: 32px; height: 32px; border-radius: 50%; @(hasCustomProfileAvatar ? "border-color: red; border-width: 3px; border-style: dashed;" : "")" src="@(profileAvatar ?? "/icon-192.png")" />
         <span style="vertical-align: middle; margin-right: 8px; border-radius: 75px; @(hasCustomProfileName ? "background-color: red;" : "")">@(profileName ?? "Loading...")</span>
         <span style="vertical-align: middle; padding-right: 8px; padding-left: 0px;">-></span>
     }
@@ -51,7 +51,7 @@
 
         await _semaphoreSlim.WaitAsync();
 
-        var hs = RuntimeCache.CurrentHomeServer; //await new AuthenticatedHomeServer(RuntimeCache.CurrentHomeServer.UserId, RuntimeCache.CurrentHomeServer.AccessToken, RuntimeCache.CurrentHomeServer.HomeServerDomain).Configure();
+        var hs = RuntimeCache.CurrentHomeServer;
 
         if (Room == null) {
             if (RoomId == null) {
@@ -119,6 +119,4 @@
             await LocalStorageWrapper.SaveCacheToLocalStorage(LocalStorage);
     }
 
-    private void Callback(ProgressEventArgs obj) => Console.WriteLine("prog: " + obj.ToJson(false));
-
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor b/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor
index d17d0de..966c44d 100644
--- a/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor
+++ b/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor
@@ -3,7 +3,7 @@
     <input autofocus type="@(IsPassword ? "password" : "text")" @bind="Value" @onfocusout="() => { isVisible = false; ValueChanged.InvokeAsync(Value); }" @ref="elementToFocus"/>
 }
 else {
-    <span tabindex="0" style="border-bottom: #ccc solid 1px; height: 1.4em; display: inline-block; @(string.IsNullOrEmpty(Value) ? "min-width: 50px;" : "")" @onfocusin="() => isVisible = true">@(Formatter?.Invoke(Value) ?? (IsPassword ? string.Join("", Value.Select(x => '*')) : Value))</span>
+    <span class="fancy-textbox-inline" tabindex="0" style="@(string.IsNullOrEmpty(Value) ? "min-width: 50px;" : "")" @onfocusin="() => isVisible = true">@(Formatter?.Invoke(Value) ?? (IsPassword ? string.Join("", Value.Select(x => '*')) : Value))</span>
 }
 
 @code {
diff --git a/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor.css b/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor.css
new file mode 100644
index 0000000..01b2c6f
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor.css
@@ -0,0 +1,5 @@
+.fancy-textbox-inline {
+    border-bottom: #ccc solid 1px;
+    height: 1.4em;
+    display: inline-block;
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/SimpleComponents/LinkButton.razor b/MatrixRoomUtils.Web/Shared/SimpleComponents/LinkButton.razor
new file mode 100644
index 0000000..8c9e73b
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/SimpleComponents/LinkButton.razor
@@ -0,0 +1,13 @@
+<a href="@href" class="btn btn-primary">
+    @ChildContent
+</a>
+
+@code {
+    
+    [Parameter]
+    public string href { get; set; }
+    
+    [Parameter]
+    public RenderFragment ChildContent { get; set; }
+    
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/_Imports.razor b/MatrixRoomUtils.Web/_Imports.razor
index 837780e..155837b 100644
--- a/MatrixRoomUtils.Web/_Imports.razor
+++ b/MatrixRoomUtils.Web/_Imports.razor
@@ -11,13 +11,18 @@
 @using MatrixRoomUtils.Web
 @using MatrixRoomUtils.Web.Classes
 @using MatrixRoomUtils.Web.Shared
+@using MatrixRoomUtils.Core.RoomTypes
+@using MatrixRoomUtils.Core.Extensions
+
+@using LinkButton = MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton
+
+
 
 @inject ILocalStorageService LocalStorage
 @inject NavigationManager NavigationManager
 
 @code
 {
-
     protected override async Task OnInitializedAsync() {
         if (!RuntimeCache.WasLoaded) {
             await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
diff --git a/MatrixRoomUtils.sln.DotSettings b/MatrixRoomUtils.sln.DotSettings
index 4ac5b79..3da5982 100644
--- a/MatrixRoomUtils.sln.DotSettings
+++ b/MatrixRoomUtils.sln.DotSettings
@@ -1,2 +1,3 @@
 <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AsyncVoidMethod/@EntryIndexedValue">ERROR</s:String>
 	<s:String x:Key="/Default/CustomTools/CustomToolsData/@EntryValue"></s:String></wpf:ResourceDictionary>
\ No newline at end of file