Skip to content

Commit bd50ae7

Browse files
authored
Merge pull request #171 from Atypical-Consulting/feature/url-folder-hierarchy
feat(blazor): reflect current folder hierarchy in the URL
2 parents 31b8032 + 3d586b6 commit bd50ae7

6 files changed

Lines changed: 68 additions & 9 deletions

File tree

src/Atypical.VirtualFileSystem.DemoBlazorApp/Components/Pages/Home.razor

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,14 +186,40 @@
186186

187187
private bool HasItems => GetCurrentItems().Any();
188188

189+
// The current directory is reflected in the URL as ?path=docs/src so the
190+
// location is deep-linkable (refresh / share / back-forward restore it).
191+
[SupplyParameterFromQuery(Name = "path")]
192+
public string? PathQuery { get; set; }
193+
189194
protected override void OnInitialized()
190195
{
191196
StateService.OnDataChanged += RequestRender;
192-
StateService.OnCurrentPathChanged += RequestRender;
197+
StateService.OnCurrentPathChanged += OnCurrentPathChanged;
193198
StateService.OnSortChanged += RequestRender;
194199
StateService.OnViewModeChanged += RequestRender;
195200
}
196201

202+
// Runs on first render and whenever the query string changes (deep link,
203+
// refresh, browser back/forward): make the current folder match the URL.
204+
protected override void OnParametersSet()
205+
{
206+
var desired = VFSStateService.FromQueryPath(PathQuery);
207+
if (StateService.CurrentPath != desired)
208+
StateService.NavigateTo(desired);
209+
}
210+
211+
// When navigation happens through state (breadcrumb, opening a folder,
212+
// import, etc.) push the matching URL so the address bar stays in sync.
213+
private void OnCurrentPathChanged()
214+
{
215+
var target = VFSStateService.ToFolderUrl(StateService.CurrentPath);
216+
var current = "/" + Navigation.ToBaseRelativePath(Navigation.Uri);
217+
if (!string.Equals(current, target, StringComparison.Ordinal))
218+
Navigation.NavigateTo(target);
219+
220+
InvokeAsync(StateHasChanged);
221+
}
222+
197223
// Service events may fire on non-UI threads; marshal to the Dispatcher.
198224
private void RequestRender() => InvokeAsync(StateHasChanged);
199225

@@ -483,7 +509,7 @@
483509
public void Dispose()
484510
{
485511
StateService.OnDataChanged -= RequestRender;
486-
StateService.OnCurrentPathChanged -= RequestRender;
512+
StateService.OnCurrentPathChanged -= OnCurrentPathChanged;
487513
StateService.OnSortChanged -= RequestRender;
488514
StateService.OnViewModeChanged -= RequestRender;
489515
}

src/Atypical.VirtualFileSystem.DemoBlazorApp/Components/Pages/Recent.razor

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,7 @@
8787

8888
if (entry.IsDirectory)
8989
{
90-
StateService.NavigateTo(entry.Path);
91-
Navigation.NavigateTo("/");
90+
Navigation.NavigateTo(VFSStateService.ToFolderUrl(entry.Path));
9291
}
9392
else
9493
{

src/Atypical.VirtualFileSystem.DemoBlazorApp/Components/Pages/Search.razor

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,8 @@
227227
{
228228
if (result.IsDirectory)
229229
{
230-
StateService.NavigateTo(result.Path.Value);
231230
RecentFiles.RecordAccess(result.Path.Value, true);
232-
Navigation.NavigateTo("/");
231+
Navigation.NavigateTo(VFSStateService.ToFolderUrl(result.Path.Value));
233232
}
234233
else
235234
{

src/Atypical.VirtualFileSystem.DemoBlazorApp/Components/Pages/Starred.razor

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,7 @@
9393

9494
if (item.IsDirectory)
9595
{
96-
StateService.NavigateTo(item.Path);
97-
Navigation.NavigateTo("/");
96+
Navigation.NavigateTo(VFSStateService.ToFolderUrl(item.Path));
9897
}
9998
else
10099
{

src/Atypical.VirtualFileSystem.DemoBlazorApp/Services/VFSStateService.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,42 @@ public string CurrentPath
7474
}
7575
}
7676

77+
/// <summary>The VFS root path scheme.</summary>
78+
public const string RootPath = "vfs://";
79+
80+
/// <summary>
81+
/// Builds the relative app URL that represents a directory, e.g.
82+
/// "vfs://docs/src" -> "/?path=docs/src" (root -> "/"). Segments are
83+
/// individually escaped so the hierarchy stays readable in the address bar.
84+
/// </summary>
85+
public static string ToFolderUrl(string? vfsPath)
86+
{
87+
var relative = StripScheme(vfsPath);
88+
if (string.IsNullOrEmpty(relative))
89+
return "/";
90+
91+
var escaped = string.Join("/", relative.Split('/').Select(Uri.EscapeDataString));
92+
return $"/?path={escaped}";
93+
}
94+
95+
/// <summary>
96+
/// Converts a "path" query value back into a full VFS path, e.g.
97+
/// "docs/src" -> "vfs://docs/src" (null/empty -> "vfs://").
98+
/// </summary>
99+
public static string FromQueryPath(string? queryPath)
100+
{
101+
var relative = (queryPath ?? string.Empty).Replace('\\', '/').Trim('/');
102+
return string.IsNullOrEmpty(relative) ? RootPath : $"{RootPath}{relative}";
103+
}
104+
105+
private static string StripScheme(string? vfsPath)
106+
{
107+
var value = vfsPath ?? string.Empty;
108+
if (value.StartsWith(RootPath, StringComparison.Ordinal))
109+
value = value[RootPath.Length..];
110+
return value.Trim('/');
111+
}
112+
77113
// View preferences
78114
public ViewMode ViewMode
79115
{

src/Atypical.VirtualFileSystem.DemoBlazorApp/wwwroot/css/output.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)