using Raven.Client.Documents; using Raven.Client.Documents.Linq; using Raven.Client.Documents.Session; namespace zero.Backoffice.Modules; public class MediaTreeService : IMediaTreeService { protected IMediaStore Media { get; private set; } protected PageOptions PageOptions { get; set; } public PageTreeService(IPagesStore pages, IZeroOptions options, IRoutes routes) { Media = pages; Routes = routes; PageOptions = options.For(); } /// /// FOR MEDIA public virtual async Task> Load(MediaListItemQuery query) { bool hasSearch = !query.Search.IsNullOrWhiteSpace(); bool isRoot = query.FolderId.IsNullOrWhiteSpace(); query.SearchFor(entity => entity.Name); query.OrderQuery = q => q .OrderByDescending(x => x.IsFolder) .ThenBy(query.OrderBy, query.OrderIsDescending, query.OrderType == ListQueryOrderType.String ? OrderingType.String : OrderingType.Double); IRavenQueryable dbQuery = Session.Query().ProjectInto(); if (!hasSearch || !query.SearchIsGlobal) { dbQuery = dbQuery.WhereIf(x => x.ParentId == query.FolderId, !query.FolderId.IsNullOrEmpty(), x => x.ParentId == null); } Paged result = await dbQuery.ToQueriedListAsyncX(query); string[] ids = result.Items.Where(x => x.IsFolder).Select(x => x.Id).ToArray(); List children = await Session.Query() .Where(x => x.ParentId.In(ids)) .ToListAsync(); foreach (MediaListItem item in result.Items) { item.Children = children.FirstOrDefault(x => x.ParentId == item.Id)?.ChildrenCount ?? 0; } return result; } /// /// FOR MEDIA FOLDER public async Task> LoadAsTree(string parentId = null, string activeId = null) { List items = new(); string[] openIds = Array.Empty(); IList folders = await Session.Query() .WhereIf(x => x.ParentId == parentId, !parentId.IsNullOrEmpty(), x => x.ParentId == null) .OrderByDescending(x => x.CreatedDate).ThenBy(x => x.Name) .ToListAsync(); // get hierarchy so we know if we should set the folder to open if (!activeId.IsNullOrEmpty()) { MediaFolder_ByHierarchy.Result result = await Session.Query() .ProjectInto() .Include(x => x.Path.Select(p => p.Id)) .FirstOrDefaultAsync(x => x.Id == activeId); if (result != null) { openIds = result.Path.Select(x => x.Id).ToArray(); } } // get children for all folders string[] folderIds = folders.Select(x => x.Id).ToArray(); IList children = await Session.Query() .ProjectInto() .Where(x => x.Id.In(folderIds)) .ToListAsync(); foreach (MediaFolder folder in folders) { int childCount = children.Count(x => x.Id == folder.Id); items.Add(new TreeItem() { Id = folder.Id, Name = folder.Name, HasChildren = childCount > 0, ChildCount = childCount, ParentId = folder.ParentId, Sort = folder.Sort, Icon = "fth-folder", IsOpen = openIds.Contains(folder.Id), IsInactive = !folder.IsActive, HasActions = true, Modifier = !folder.IsActive ? new TreeItemModifier() { Icon = "fth-minus-circle color-yellow", Name = "Inactive" } : null }); } return items; } /// public async Task> GetChildren(string parentId = null, string activeId = null, string search = null) { IList items = new List(); IReadOnlyCollection pageTypes = PageOptions.GetAllItems(); string[] openIds = new string[0] { }; Paged pages = null; IList children = null; bool isSearch = !search.IsNullOrWhiteSpace(); if (isSearch) { pages = await Media.Load(1, Int32.MaxValue, q => q.SearchIf(x => x.Name, search, "*").OrderBy(x => x.Sort, OrderingType.Long)); var urls = await Routes.GetUrls(pages.Items.ToArray()); foreach (Page page in pages.Items) { if (urls.TryGetValue(page, out string url)) { page.Url = url; } } } else { pages = await Media.Load(1, Int32.MaxValue, q => q .WhereIf(x => x.ParentId == parentId, !parentId.IsNullOrEmpty(), x => x.ParentId == null) .OrderBy(x => x.Sort, OrderingType.Long)); // get hierarchy so we know if we should set the page to open if (!activeId.IsNullOrEmpty()) { Pages_ByHierarchy.Result result = await Media.Session.Query() .ProjectInto() .Include(x => x.Path.Select(p => p.Id)) .FirstOrDefaultAsync(x => x.Id == activeId); if (result != null) { openIds = result.Path.Select(x => x.Id).ToArray(); // .Union(new string[1] { activeId }) } } // get children for all pages string[] pageIds = pages.Items.Select(x => x.Id).ToArray(); children = await Media.Session.Query() .ProjectInto() .Where(x => x.Id.In(pageIds)) .ToListAsync(); } // function to get modifier icon TreeItemModifier GetModifier(Page page) { if (page.PublishDate > DateTimeOffset.Now || page.UnpublishDate > DateTimeOffset.Now) { return new TreeItemModifier("@page.schedule.scheduled", "fth-clock"); } if (!page.IsActive) { return new TreeItemModifier("@ui.inactive", "fth-minus-circle color-red"); } return null; } // build tree foreach (Page page in pages.Items) { PageType pageType = pageTypes.FirstOrDefault(x => x.Alias == page.PageTypeAlias); if (pageType == null) { continue; // TODO the page type does not exist anymore } int childCount = isSearch ? 0 : children.Count(x => x.Id == page.Id); items.Add(new TreeItem() { Id = page.Id, Name = page.Name, HasChildren = childCount > 0, ChildCount = childCount, ParentId = page.ParentId, Sort = page.Sort, Icon = pageType.Icon, IsOpen = openIds.Contains(page.Id), IsInactive = !page.IsActive, HasActions = true, Modifier = GetModifier(page), Description = isSearch ? page.Url : null }); } if (parentId.IsNullOrEmpty()) { items.Add(new TreeItem() { Id = "recyclebin", ParentId = null, Sort = 99999, Name = "@recyclebin.name", Icon = "fth-trash", HasChildren = false, HasActions = true }); } return items; } } public interface IMediaTreeService { /// /// Get media children as tree items /// Task> GetChildren(string parentId = null, string activeId = null, string search = null); }