@page "/admin/{TableDisplayName}" @layout HopFrameLayout @rendermode InteractiveServer @implements IDisposable @using HopFrame.Core.Config @using HopFrame.Core.Callbacks @using HopFrame.Core.Services @using HopFrame.Web.Models @using HopFrame.Web.Plugins @using HopFrame.Web.Plugins.Events @using HopFrame.Web.Services @using Microsoft.JSInterop @if (!DisplaySelection) { @_config?.DisplayName }

@_config?.DisplayName

@if (!DisplaySelection && _buttonToggles.ShowRefreshButton) { Refresh } @foreach (var button in _pluginButtons.Where(pb => pb.IsForTable(_config)).Where(pb => pb.Position == PluginButtonPosition.TopLeft)) { @button.Title } @if (_hasCreatePolicy && DisplayActions && _buttonToggles.ShowAddEntityButton) { Add Entity } @foreach (var button in _pluginButtons.Where(pb => pb.IsForTable(_config)).Where(pb => pb.Position == PluginButtonPosition.TopRight)) { @button.Title }
@if (DisplaySelection) { } @foreach (var property in _config!.Properties.Where(prop => prop.List).OrderBy(prop => prop.Order)) { } @if (DisplayActions && (_hasDeletePolicy || _hasUpdatePolicy)) { @foreach (var button in _pluginButtons.Where(pb => pb.IsForTable(_config)).Where(pb => pb.Position == PluginButtonPosition.OnEntry)) { } @if (_hasUpdatePolicy && _buttonToggles.ShowEditButton) { } @if (_hasDeletePolicy && _buttonToggles.ShowDeleteButton) { } }
@if (_totalPages > 1) {
Page of @_totalPages
}
@inject IContextExplorer Explorer @inject NavigationManager Navigator @inject IJSRuntime Js @inject IDialogService Dialogs @inject IHopFrameAuthHandler Handler @inject ICallbackEmitter Emitter @inject IPluginOrchestrator PluginOrchestrator @inject ISearchSuggestionProvider SearchSuggestions @code { [Parameter] public required string TableDisplayName { get; set; } [Parameter] public bool DisplaySelection { get; set; } [Parameter] public bool DisplayActions { get; set; } = true; [Parameter] public RelationPickerDialogData? DialogData { get; set; } [Parameter] public DataGridSelectMode SelectionMode { get; set; } = DataGridSelectMode.Single; [Parameter] public int PerPage { get; set; } = 20; private TableConfig? _config; private ITableManager? _manager; public object[] CurrentlyDisplayedModels = []; private int _currentPage; private int _totalPages; private string? _searchTerm; private bool _loading; private bool _isSearchActive; private IList _searchSuggestions = []; private FluentSearch? _searchBox; private bool _hasUpdatePolicy; private bool _hasDeletePolicy; private bool _hasCreatePolicy; private bool _allSelected; private readonly CancellationTokenSource _tokenSource = new(); private List _pluginButtons = new(); private DefaultButtonToggles _buttonToggles = new(); internal static HopFrameTablePage? CurrentInstance { get; private set; } protected override void OnInitialized() { CurrentInstance = this; _config ??= Explorer.GetTable(TableDisplayName); if (_config is null || (_config.Ignored && DialogData is null)) { Navigator.NavigateTo("/admin", true); } } protected override async Task OnInitializedAsync() { if (!await Handler.IsAuthenticatedAsync(_config?.ViewPolicy)) { Navigator.NavigateTo("/admin", true); return; } var eventResult = await PluginOrchestrator.DispatchEvent(new TableInitializedEvent(this) { Table = _config! }); if (eventResult.IsCanceled) return; _pluginButtons = eventResult.PluginButtons; _buttonToggles = eventResult.DefaultButtons; _hasUpdatePolicy = await Handler.IsAuthenticatedAsync(_config?.UpdatePolicy); _hasDeletePolicy = await Handler.IsAuthenticatedAsync(_config?.DeletePolicy); _hasCreatePolicy = await Handler.IsAuthenticatedAsync(_config?.CreatePolicy); _manager ??= Explorer.GetTableManager(_config!.PropertyName); CurrentlyDisplayedModels = (await _manager!.LoadPage(_currentPage, PerPage)).ToArray(); _totalPages = await _manager.TotalPages(PerPage); } protected override async Task OnAfterRenderAsync(bool firstRender) { try { await Js.InvokeVoidAsync("removeBg"); } catch (Exception) { // ignored } } public void Dispose() { _searchCancel.Dispose(); _tokenSource.Dispose(); } private CancellationTokenSource _searchCancel = new(); private async Task OnSearch(ChangeEventArgs eventArgs) { await _searchCancel.CancelAsync(); _searchTerm = eventArgs.Value?.ToString(); if (_searchTerm is null) return; _searchCancel = new(); UpdateSearchSuggestions(); await Task.Delay(500, _searchCancel.Token); var eventResult = await PluginOrchestrator.DispatchEvent(new SearchEvent(this) { SearchTerm = _searchTerm, Table = _config!, CurrentPage = _currentPage }, _tokenSource.Token); if (eventResult.IsCanceled) { if (eventResult.SearchResult is null) return; CurrentlyDisplayedModels = eventResult.SearchResult.ToArray(); _totalPages = eventResult.TotalPages; return; } _searchTerm = eventResult.SearchTerm; await Reload(); } private async Task SearchSuggestionSelected(string? suggestion) { if (string.IsNullOrWhiteSpace(suggestion)) return; _searchTerm = SearchSuggestions.CompleteSearchSuggestion(_config!, _searchTerm ?? string.Empty, suggestion); _searchBox!.Value = _searchTerm; _searchBox.FocusAsync(); UpdateSearchSuggestions(); if (!suggestion.EndsWith('=')) await OnSearch(new() { Value = _searchTerm }); } private void UpdateSearchSuggestions() { if (_config is null) return; _searchSuggestions = SearchSuggestions.GenerateSearchSuggestions(_config, _searchTerm ?? string.Empty).ToList(); } private CancellationTokenSource _searchFocusCancel = new(); private async Task SearchFocus() { _isSearchActive = true; await _searchFocusCancel.CancelAsync(); _searchFocusCancel = new(); } private async Task SearchUnfocus() { await Task.Delay(10, _searchFocusCancel.Token); _isSearchActive = false; } public async Task Reload() { _loading = true; var eventResult = await PluginOrchestrator.DispatchEvent(new ReloadEvent(this) { Table = _config! }, _tokenSource.Token); if (eventResult.IsCanceled) { _loading = false; return; } if (!string.IsNullOrEmpty(_searchTerm)) { (var query, _totalPages) = await _manager!.Search(_searchTerm, _currentPage, PerPage); CurrentlyDisplayedModels = query.ToArray(); } else { await OnInitializedAsync(); } _loading = false; } public async Task ChangePage(int page) { var eventResult = await PluginOrchestrator.DispatchEvent(new PageChangeEvent(this) { CurrentPage = _currentPage, NewPage = page, TotalPages = _totalPages, Table = _config! }, _tokenSource.Token); if (eventResult.IsCanceled) return; page = eventResult.NewPage; if (page < 0 || page > _totalPages - 1) return; _currentPage = page; await Reload(); } private async Task DeleteEntry(object element) { if (!await Handler.IsAuthenticatedAsync(_config?.DeletePolicy)) { Navigator.NavigateTo("/admin", true); return; } var eventResult = await PluginOrchestrator.DispatchEvent(new DeleteEntryEvent(this) { Entity = element, Table = _config! }, _tokenSource.Token); if (eventResult.IsCanceled) return; var dialog = await Dialogs.ShowConfirmationAsync("Do you really want to delete this entry?"); var result = await dialog.Result; if (result.Cancelled) return; await _manager!.DeleteItem(element); await Emitter.DispatchCallback(CallbackTypes.DeleteEntry(_config!), element); await Reload(); } private async Task CreateOrEdit(object? element) { if (!await Handler.IsAuthenticatedAsync(element is null ? _config?.CreatePolicy : _config?.UpdatePolicy)) { Navigator.NavigateTo("/admin", true); return; } HopFrameTablePageEventArgs eventArgs; if (element is null) { eventArgs = new CreateEntryEvent(this) { Table = _config! }; } else { eventArgs = new UpdateEntryEvent(this) { Table = _config!, Entity = element }; } var eventResult = await PluginOrchestrator.DispatchEvent(eventArgs, _tokenSource.Token); if (eventResult.IsCanceled) return; var panel = await Dialogs.ShowPanelAsync(new EditorDialogData(_config!, element), new DialogParameters { TrapFocus = false }); var result = await panel.Result; var data = result.Data as EditorDialogData; if (result.Cancelled) return; if (element is null) { await _manager!.AddItem(data!.CurrentObject!); await Emitter.DispatchCallback(CallbackTypes.CreateEntry(_config!), data.CurrentObject!); } else { await _manager!.EditItem(data!.CurrentObject!); await Emitter.DispatchCallback(CallbackTypes.UpdateEntry(_config!), data.CurrentObject!); } await Reload(); } private void SelectItem(object item, bool selected) { var eventResult = PluginOrchestrator.DispatchEvent(new SelectEntryEvent(this) { Entity = item, Selected = selected, Table = _config! }, _tokenSource.Token).Result; if (eventResult.IsCanceled) return; selected = eventResult.Selected; if (!selected) DialogData!.SelectedObjects.Remove(item); else DialogData!.SelectedObjects.Add(item); } private void SelectAll() { var selected = CurrentlyDisplayedModels.All(DialogData!.SelectedObjects.Contains); foreach (var displayedModel in CurrentlyDisplayedModels) { SelectItem(displayedModel, !selected); } _allSelected = selected; } private async Task DisplayProperty(PropertyConfig config, object entry) { var display = await _manager!.DisplayProperty(entry, config); if (display.Length > config.DisplayLength) display = display[..config.DisplayLength] + "..."; return display; } public InputFile? FileInputElement; public Func, Task>? OnFileUpload; private async Task OnInputFiles(InputFileChangeEventArgs e) { if (OnFileUpload is null) return; if (e.FileCount == 1) { await OnFileUpload.Invoke([e.File]); } else { await OnFileUpload.Invoke(e.GetMultipleFiles()); } } public void RequestRender() { StateHasChanged(); } }