26 changed files with 578 additions and 61 deletions
@ -0,0 +1,61 @@
|
||||
@using System.Runtime.InteropServices |
||||
@inject ISearchService searchService |
||||
@inject NavigationManager navigationManager |
||||
@inject IJSRuntime jsRuntime |
||||
|
||||
<button class="searchButtonContainer" @onclick="ButtonClicked"> |
||||
<div class="searchText"> |
||||
Search... |
||||
</div> |
||||
@if (false) |
||||
{ |
||||
<div class="searchHotkey"> |
||||
@CommandKey + K |
||||
</div> |
||||
|
||||
} |
||||
</button> |
||||
|
||||
<style> |
||||
.searchButtonContainer { |
||||
background-color: var(--primary); |
||||
border: 2px solid var(--primary-border); |
||||
border-radius: 8px; |
||||
font-weight: 800; |
||||
width: 350px; |
||||
|
||||
padding: 5px; |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
|
||||
} |
||||
|
||||
.searchHotkey { |
||||
padding: 2px; |
||||
|
||||
border: 2px solid var(--primary-border); |
||||
} |
||||
|
||||
</style> |
||||
|
||||
|
||||
@code { |
||||
[Parameter] |
||||
public RenderFragment ChildContent { get; set; } = default!; |
||||
|
||||
private string userAgent = ""; |
||||
|
||||
string CommandKey => userAgent.Contains("Mac OS") ? "CMD" : "Ctrl"; |
||||
|
||||
private void ButtonClicked(EventArgs eventArgs) |
||||
{ |
||||
searchService.Show(); |
||||
} |
||||
|
||||
protected override async Task OnInitializedAsync() |
||||
{ |
||||
userAgent = await jsRuntime.InvokeAsync<string>("getUserAgent"); |
||||
} |
||||
} |
||||
Binary file not shown.
@ -0,0 +1,172 @@
|
||||
@implements IDisposable; |
||||
@inject ISearchService searchService |
||||
@inject IJSRuntime jsRuntime |
||||
|
||||
|
||||
@inject NavigationManager navigationManager |
||||
|
||||
@if (searchService.IsLoaded()) |
||||
{ |
||||
<div class="searchBackground" onclick="@CloseDialog"> |
||||
<div class="searchContainer" |
||||
@onclick:preventDefault="true" |
||||
@onclick:stopPropagation="true"> |
||||
|
||||
<FormLayoutComponent> |
||||
<FormTextComponent Placeholder="Search..." OnChange="SearchChanged"></FormTextComponent> |
||||
</FormLayoutComponent> |
||||
|
||||
<div class="searchBox"> |
||||
@if (SearchText.Length > 0) |
||||
{ |
||||
foreach (var searchSection in searchService.Searches) |
||||
{ |
||||
var searchPoints = searchSection.Value.FindAll(x => x.Title.ToLower().Contains(SearchText.ToLower())); |
||||
|
||||
@if (searchPoints.Count > 0) |
||||
{ |
||||
<div> |
||||
<div class="searchSectionTitle"> |
||||
@searchSection.Key |
||||
</div> |
||||
<div class="searchContents"> |
||||
@foreach (var searchPoint in searchPoints) |
||||
{ |
||||
<button class="searchLink @searchPoint.PointType.ToLower()" @onclick="() => OnSearch(searchPoint)">@searchPoint.Title</button> |
||||
} |
||||
</div> |
||||
</div> |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
||||
|
||||
} |
||||
<style> |
||||
.pageContents * { |
||||
filter: blur(2px); |
||||
} |
||||
|
||||
.searchBackground { |
||||
position: fixed; |
||||
top: 0; |
||||
left: 0; |
||||
width: 100vw; |
||||
height: 100vh; |
||||
background-color: rgba(0, 0, 0, 0.5); |
||||
display: flex; |
||||
} |
||||
|
||||
.searchBox { |
||||
padding: 12px; |
||||
overflow-y: scroll; |
||||
overflow-x: hidden; |
||||
height: 530px; |
||||
border: 1px solid black; |
||||
border-radius: 2px; |
||||
} |
||||
|
||||
.searchContents { |
||||
display: flex; |
||||
flex-direction: column; |
||||
gap: 6px; |
||||
align-items: flex-start; |
||||
|
||||
padding: 12px; |
||||
} |
||||
|
||||
|
||||
.searchSectionTitle { |
||||
font-weight: bolder; |
||||
} |
||||
|
||||
|
||||
.searchContainer { |
||||
margin-left: auto; |
||||
margin-right: auto; |
||||
margin-top: 64px; |
||||
width: 800px; |
||||
height: 600px; |
||||
|
||||
background-color: var(--background); |
||||
border-width: var(--dialog-border-width); |
||||
border-style: solid; |
||||
border-color: var(--dialog-border-color); |
||||
border-radius: var(--dialog-radius); |
||||
|
||||
padding: 8px; |
||||
|
||||
|
||||
box-shadow: 1px 2px 2px black; |
||||
|
||||
} |
||||
|
||||
.searchLink { |
||||
text-decoration: underline; |
||||
|
||||
} |
||||
</style> |
||||
|
||||
@code { |
||||
|
||||
private ElementReference searchBox; |
||||
|
||||
private string SearchText { get; set; } = ""; |
||||
|
||||
protected override void OnInitialized() |
||||
{ |
||||
searchService.Subscribe(OnSearchChanged); |
||||
} |
||||
|
||||
private void OnSearchChanged() |
||||
{ |
||||
if (searchService.IsVisible) |
||||
{ |
||||
jsRuntime.InvokeVoidAsync("SetFocusToElement", "search-dialog-input"); |
||||
|
||||
} |
||||
} |
||||
|
||||
public void Dispose() |
||||
{ |
||||
searchService.Unsubscribe(OnSearchChanged); |
||||
} |
||||
|
||||
|
||||
public void CloseDialog() |
||||
{ |
||||
searchService.Hide(); |
||||
} |
||||
|
||||
public void NavigateTo(string url) |
||||
{ |
||||
if (url.Contains("#")) |
||||
{ |
||||
|
||||
navigationManager.NavigateTo(url, |
||||
navigationManager.Uri.Split("#").First().Contains(url.Split("#").First())); |
||||
} |
||||
else |
||||
{ |
||||
navigationManager.NavigateTo(url); |
||||
} |
||||
|
||||
} |
||||
|
||||
private void SearchChanged(ChangeEventArgs obj) |
||||
{ |
||||
SearchText = obj.Value!.ToString()!; |
||||
} |
||||
|
||||
private void OnSearch(SearchPointModel searchPoint) |
||||
{ |
||||
NavigateTo(searchPoint.Href); |
||||
searchService.Hide(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,28 @@
|
||||
@implements IDisposable; |
||||
|
||||
@inject ISearchService searchService |
||||
|
||||
@if (searchService.IsVisible) |
||||
{ |
||||
<SearchDialogComponent></SearchDialogComponent> |
||||
} |
||||
|
||||
@code { |
||||
|
||||
protected override void OnInitialized() |
||||
{ |
||||
searchService.Subscribe(OnUpdate); |
||||
|
||||
searchService.Load(); |
||||
} |
||||
|
||||
public void Dispose() |
||||
{ |
||||
searchService.Unsubscribe(OnUpdate); |
||||
} |
||||
|
||||
void OnUpdate() |
||||
{ |
||||
StateHasChanged(); |
||||
} |
||||
} |
||||
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
[{"Id":1,"ParentId":null,"NoteSectionModelId":3,"Href":"holdout","CreatedDate":"2022-02-18T00:00:00","UpdatedDate":"2022-02-18T00:00:00","Name":"Coop Holdout, Some distant place (Nuath)","Description":"First coop test map in pre-alpha.","Content":"coop/holdout","IsHidden":"False","IsPreAlpha":"True","NoteContentModels":[],"Parent":null,"PageOrder":0},{"Id":2,"ParentId":null,"NoteSectionModelId":2,"Href":"hotkeys","CreatedDate":"2022-04-13T00:00:00","UpdatedDate":"2022-04-13T00:00:00","Name":"Custom HotKey Setup","Description":"Customize your hotkeys in the pre-alpha.","Content":"settings/hotkeys","IsHidden":"False","IsPreAlpha":"True","NoteContentModels":[],"Parent":null,"PageOrder":0},{"Id":3,"ParentId":null,"NoteSectionModelId":1,"Href":"armor-types","CreatedDate":"2022-04-13T00:00:00","UpdatedDate":"2022-04-13T00:00:00","Name":"Armor Types","Description":"Heavy, Medium, and Light. What does it mean?","Content":"the-basics/armor-types","IsHidden":"False","IsPreAlpha":"True","NoteContentModels":[],"Parent":null,"PageOrder":0}] |
||||
[{"Id":1,"ParentId":null,"NoteSectionModelId":3,"Href":"holdout","CreatedDate":"2022-02-18T00:00:00","UpdatedDate":"2022-02-18T00:00:00","Name":"Coop Holdout, Some distant place (Nuath)","Description":"First coop test map in pre-alpha.","Content":"coop/holdout","LoadedContent":null,"IsHidden":"False","IsPreAlpha":"True","NoteContentModels":[],"Parent":null,"PageOrder":0},{"Id":2,"ParentId":null,"NoteSectionModelId":2,"Href":"hotkeys","CreatedDate":"2022-04-13T00:00:00","UpdatedDate":"2022-04-13T00:00:00","Name":"Custom HotKey Setup","Description":"Customize your hotkeys in the pre-alpha.","Content":"settings/hotkeys","LoadedContent":null,"IsHidden":"False","IsPreAlpha":"True","NoteContentModels":[],"Parent":null,"PageOrder":0},{"Id":3,"ParentId":null,"NoteSectionModelId":1,"Href":"armor-types","CreatedDate":"2022-04-13T00:00:00","UpdatedDate":"2022-04-13T00:00:00","Name":"Armor Types","Description":"Heavy, Medium, and Light. What does it mean?","Content":"the-basics/armor-types","LoadedContent":null,"IsHidden":"False","IsPreAlpha":"True","NoteContentModels":[],"Parent":null,"PageOrder":0}] |
||||
@ -1 +1 @@
|
||||
[{"Id":1,"Name":"Tools","Description":"Tools Stuff","Href":null,"Order":1,"IsPrivate":"False","WebPageModels":[]},{"Id":2,"Name":"Resources","Description":"Resources Stuff","Href":null,"Order":2,"IsPrivate":"False","WebPageModels":[]},{"Id":3,"Name":"General","Description":"About Stuff","Href":null,"Order":3,"IsPrivate":"False","WebPageModels":[]},{"Id":4,"Name":"Development","Description":"Development Stuff","Href":null,"Order":4,"IsPrivate":"False","WebPageModels":[]},{"Id":5,"Name":"Settings","Description":"Settings Stuff","Href":null,"Order":5,"IsPrivate":"False","WebPageModels":[]}] |
||||
[{"Id":1,"Name":"Tools","Description":"Tools Stuff","Order":1,"IsPrivate":"False","WebPageModels":[]},{"Id":2,"Name":"Resources","Description":"Resources Stuff","Order":2,"IsPrivate":"False","WebPageModels":[]},{"Id":3,"Name":"General","Description":"About Stuff","Order":3,"IsPrivate":"False","WebPageModels":[]},{"Id":4,"Name":"Development","Description":"Development Stuff","Order":4,"IsPrivate":"False","WebPageModels":[]},{"Id":5,"Name":"Settings","Description":"Settings Stuff","Order":5,"IsPrivate":"False","WebPageModels":[]}] |
||||
@ -0,0 +1,9 @@
|
||||
namespace Model.Website; |
||||
|
||||
public class SearchPointModel |
||||
{ |
||||
public string Title { get; set; } = ""; |
||||
public string Tags { get; set; } = ""; |
||||
public string PointType { get; set; } = ""; |
||||
public string Href { get; set; } = ""; |
||||
} |
||||
@ -0,0 +1,131 @@
|
||||
using Model.Entity.Data; |
||||
using Model.Feedback; |
||||
using Model.Website; |
||||
|
||||
namespace Services.Website; |
||||
|
||||
public class SearchService : ISearchService |
||||
{ |
||||
|
||||
private bool isLoaded = false; |
||||
public List<SearchPointModel> SearchPoints { get; set; } = new(); |
||||
|
||||
public Dictionary<string, List<SearchPointModel>> Searches { get; set; } = new(); |
||||
|
||||
|
||||
private IWebsiteService websiteService; |
||||
private INoteService noteService; |
||||
private IDocumentationService documentationService; |
||||
|
||||
public SearchService(IWebsiteService websiteService, INoteService noteService, IDocumentationService documentationService) |
||||
{ |
||||
this.websiteService = websiteService; |
||||
this.noteService = noteService; |
||||
this.documentationService = documentationService; |
||||
|
||||
// All Unit Data |
||||
} |
||||
|
||||
public bool IsVisible { get; set; } |
||||
|
||||
public void Subscribe(Action action) |
||||
{ |
||||
OnChange += action; |
||||
} |
||||
|
||||
public void Unsubscribe(Action action) |
||||
{ |
||||
OnChange += action; |
||||
} |
||||
|
||||
public void Search(string entityId) |
||||
{ |
||||
} |
||||
|
||||
public async Task Load() |
||||
{ |
||||
await websiteService.Load(); |
||||
await noteService.Load(); |
||||
await documentationService.Load(); |
||||
|
||||
|
||||
Searches.Add("Pages", new List<SearchPointModel>()); |
||||
Searches.Add("Notes", new List<SearchPointModel>()); |
||||
Searches.Add("Documents", new List<SearchPointModel>()); |
||||
Searches.Add("Entities", new List<SearchPointModel>()); |
||||
|
||||
foreach (var webPage in websiteService.WebPageModels) |
||||
{ |
||||
SearchPoints.Add(new SearchPointModel{ Title = webPage.Name, |
||||
PointType = "WebPage", |
||||
Href=webPage.Href |
||||
}); |
||||
|
||||
Searches["Pages"].Add(SearchPoints.Last()); |
||||
} |
||||
|
||||
foreach (var note in noteService.NoteContentModels) |
||||
{ |
||||
SearchPoints.Add(new SearchPointModel(){ Title = note.Name, |
||||
PointType = "Note", Href = note.GetNoteLink()}); |
||||
|
||||
Searches["Notes"].Add(SearchPoints.Last()); |
||||
|
||||
foreach (var noteHeader in note.GetHeaders()) |
||||
{ |
||||
SearchPoints.Add(new SearchPointModel { Title = noteHeader.Title, |
||||
PointType = "NoteHeader", Href = $"{note.GetNoteLink()}/#{noteHeader.Href}"}); |
||||
} |
||||
} |
||||
|
||||
|
||||
foreach (var entity in DATA.Get().Values) |
||||
{ |
||||
SearchPoints.Add(new SearchPointModel(){ |
||||
Title = entity.Info().Name, |
||||
Tags = $"{entity.EntityType},{entity.Descriptive}", |
||||
PointType = "Entity", |
||||
Href = $"database/{entity.Info().Name.ToLower()}" |
||||
}); |
||||
|
||||
Searches["Entities"].Add(SearchPoints.Last()); |
||||
} |
||||
|
||||
|
||||
foreach (var doc in documentationService.DocContentModels) |
||||
{ |
||||
SearchPoints.Add(new SearchPointModel { Title = doc.Name, |
||||
PointType = "Document", Href = doc.GetDocLink()}); |
||||
|
||||
Searches["Documents"].Add(SearchPoints.Last()); |
||||
} |
||||
|
||||
isLoaded = true; |
||||
} |
||||
|
||||
public bool IsLoaded() |
||||
{ |
||||
return isLoaded; |
||||
} |
||||
|
||||
public void Show() |
||||
{ |
||||
IsVisible = true; |
||||
|
||||
NotifyDataChanged(); |
||||
} |
||||
|
||||
public void Hide() |
||||
{ |
||||
IsVisible = false; |
||||
|
||||
NotifyDataChanged(); |
||||
} |
||||
|
||||
private event Action OnChange = null!; |
||||
|
||||
private void NotifyDataChanged() |
||||
{ |
||||
OnChange(); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue