-
Notifications
You must be signed in to change notification settings - Fork 46
/
ToolkitDocumentationRenderer.xaml.cs
199 lines (171 loc) · 8.06 KB
/
ToolkitDocumentationRenderer.xaml.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Labs.Core.SourceGenerators;
using CommunityToolkit.Labs.Core.SourceGenerators.Metadata;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Windows.Storage;
#if WINAPPSDK
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
#else
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
#endif
namespace CommunityToolkit.Labs.Shared.Renderers
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class ToolkitDocumentationRenderer : Page
{
private const string MarkdownRegexSampleTagExpression = @"^>\s*\[!SAMPLE\s*(?<sampleid>.*)\s*\]\s*$";
private static readonly Regex MarkdownRegexSampleTag = new Regex(MarkdownRegexSampleTagExpression, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);
public ToolkitDocumentationRenderer()
{
this.InitializeComponent();
}
/// <summary>
/// List of referenced samples in this page.
/// </summary>
public List<ToolkitSampleMetadata> Samples
{
get { return (List<ToolkitSampleMetadata>)GetValue(SamplesProperty); }
set { SetValue(SamplesProperty, value); }
}
/// <summary>
/// The backing <see cref="DependencyProperty"/> for the <see cref="Samples"/> property.
/// </summary>
public static readonly DependencyProperty SamplesProperty
=
DependencyProperty.Register(nameof(Samples), typeof(List<ToolkitSampleMetadata>), typeof(ToolkitDocumentationRenderer), new PropertyMetadata(null));
/// <summary>
/// Intermixed list of string doc snippets for Markdown and <see cref="ToolkitSampleMetadata"/>
/// objects for samples.
/// </summary>
public ObservableCollection<object> DocsAndSamples = new();
/// <summary>
/// The YAML front matter metadata about this documentation file.
/// </summary>
public ToolkitFrontMatter? Metadata
{
get { return (ToolkitFrontMatter?)GetValue(MetadataProperty); }
set { SetValue(MetadataProperty, value); }
}
/// <summary>
/// The backing <see cref="DependencyProperty"/> for the <see cref="Metadata"/> property.
/// </summary>
public static readonly DependencyProperty MetadataProperty =
DependencyProperty.Register(nameof(Metadata), typeof(ToolkitFrontMatter), typeof(ToolkitDocumentationRenderer), new PropertyMetadata(null, OnMetadataPropertyChanged));
private static async void OnMetadataPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
if (dependencyObject is ToolkitDocumentationRenderer renderer &&
renderer.Metadata != null &&
args.OldValue != args.NewValue)
{
await renderer.LoadData();
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Metadata = (ToolkitFrontMatter)e.Parameter;
}
private async Task LoadData()
{
if (Metadata is null)
{
return;
}
List<ToolkitSampleMetadata> samples = new();
if (Metadata.SampleIdReferences != null && Metadata.SampleIdReferences.Length > 0 &&
!string.IsNullOrWhiteSpace(Metadata.SampleIdReferences[0]))
{
foreach (var sampleid in Metadata.SampleIdReferences)
{
// We don't check here for key as we validate with SG.
samples.Add(ToolkitSampleRegistry.Listing[sampleid]);
}
}
Samples = samples;
var doctext = await GetDocumentationFileContents(Metadata);
var matches = MarkdownRegexSampleTag.Matches(doctext);
DocsAndSamples.Clear();
if (matches.Count == 0)
{
DocsAndSamples.Add(doctext);
}
else
{
int index = 0;
foreach (Match match in matches)
{
DocsAndSamples.Add(doctext.Substring(index, match.Index - index - 1));
DocsAndSamples.Add(ToolkitSampleRegistry.Listing[match.Groups["sampleid"].Value]);
index = match.Index + match.Length;
}
// Put rest of text at end
DocsAndSamples.Add(doctext.Substring(index));
}
}
private void SampleListHyperlink_Click(object sender, RoutedEventArgs e)
{
if (sender is HyperlinkButton btn && btn.DataContext is ToolkitSampleMetadata metadata)
{
var container = DocItemsControl.ContainerFromItem(metadata) as UIElement;
container?.StartBringIntoView();
}
}
private static async Task<string> GetDocumentationFileContents(ToolkitFrontMatter metadata)
{
// TODO: https://github.com/CommunityToolkit/Labs-Windows/issues/142
// MSBuild uses wildcard to find the files, and the wildcards decide where they end up
// Single experiments use relative paths, the allExperiment head uses absolute paths that grab from all experiments
// The wildcard captures decide the paths. This discrepency is accounted for manually.
// Logic here is the exact same that MSBuild uses to find and include the files we need.
var assemblyName = typeof(ToolkitSampleRenderer).Assembly.GetName().Name;
if (string.IsNullOrWhiteSpace(assemblyName))
throw new InvalidOperationException();
var isAllExperimentHead = assemblyName.StartsWith("CommunityToolkit.Labs.", StringComparison.OrdinalIgnoreCase);
var isProjectTemplateHead = assemblyName.StartsWith("ProjectTemplate");
var isSingleExperimentHead = !isAllExperimentHead && !isProjectTemplateHead;
if (metadata.FilePath is null || string.IsNullOrWhiteSpace(metadata.FilePath))
throw new InvalidOperationException("Missing or malformed path to markdown file. Unable to continue;");
var path = metadata.FilePath;
if (isSingleExperimentHead || isProjectTemplateHead)
{
var experimentName = assemblyName.Split(new[] { "." }, StringSplitOptions.RemoveEmptyEntries)[0];
path = path.Split(new[] { $"\\{experimentName}.Sample" }, StringSplitOptions.RemoveEmptyEntries)[1];
path = $"{experimentName}.Sample{path}";
}
var fileUri = new Uri($"ms-appx:///SourceAssets/{path}");
try
{
var file = await StorageFile.GetFileFromApplicationUriAsync(fileUri);
var textContents = await FileIO.ReadTextAsync(file);
// Remove YAML - need to use array overload as single string not supported on .NET Standard 2.0
var blocks = textContents.Split(new[] { "---" }, StringSplitOptions.RemoveEmptyEntries);
return blocks.LastOrDefault() ?? "Couldn't find content after YAML Front Matter removal.";
}
catch (Exception e)
{
return $"Exception Encountered Loading file '{fileUri}':\n{e.Message}\n{e.StackTrace}";
}
}
}
}