bulk move for media almost complete

This commit is contained in:
2021-12-16 16:59:27 +01:00
parent b3cf96e8e1
commit cb3f7f4b1a
31 changed files with 491 additions and 75 deletions
@@ -52,39 +52,39 @@ public abstract class ZeroApiTreeEntityStoreController<TModel, TStore> : ZeroApi
}
protected async Task<ActionResult<Result>> MoveModel<TEdit>(string pageId, string newParentId)
protected async Task<ActionResult<Result>> MoveModel<TEdit>(string id, string newParentId)
{
return await PutOperation<TEdit>(async () => await Store.Move(pageId, newParentId));
return await PutOperation<TEdit>(async () => await Store.Move(id, newParentId));
}
protected async Task<ActionResult<Result>> MoveModel(string pageId, string newParentId)
protected async Task<ActionResult<Result>> MoveModel(string id, string newParentId)
{
return await PutOperation(async () => await Store.Move(pageId, newParentId));
return await PutOperation(async () => await Store.Move(id, newParentId));
}
protected async Task<ActionResult<Result>> CopyModel<TEdit>(string pageId, string newParentId)
protected async Task<ActionResult<Result>> CopyModel<TEdit>(string id, string newParentId)
{
return await PutOperation<TEdit>(async () => await Store.Copy(pageId, newParentId));
return await PutOperation<TEdit>(async () => await Store.Copy(id, newParentId));
}
protected async Task<ActionResult<Result>> CopyModel(string pageId, string newParentId)
protected async Task<ActionResult<Result>> CopyModel(string id, string newParentId)
{
return await PutOperation(async () => await Store.Copy(pageId, newParentId));
return await PutOperation(async () => await Store.Copy(id, newParentId));
}
protected async Task<ActionResult<Result>> CopyModelWithDescendants<TEdit>(string pageId, string newParentId)
protected async Task<ActionResult<Result>> CopyModelWithDescendants<TEdit>(string id, string newParentId)
{
return await PutOperation<TEdit>(async () => await Store.CopyWithDescendants(pageId, newParentId));
return await PutOperation<TEdit>(async () => await Store.CopyWithDescendants(id, newParentId));
}
protected async Task<ActionResult<Result>> CopyModelWithDescendants(string pageId, string newParentId)
protected async Task<ActionResult<Result>> CopyModelWithDescendants(string id, string newParentId)
{
return await PutOperation(async () => await Store.CopyWithDescendants(pageId, newParentId));
return await PutOperation(async () => await Store.CopyWithDescendants(id, newParentId));
}
@@ -7,6 +7,8 @@ public class zero_Api_Media_ChildCounts : ZeroIndex<zero.Media.Media, zero_Api_M
public class Result : ZeroIdEntity
{
public int ChildCount { get; set; }
public int ChildFolderCount { get; set; }
}
protected override void Create()
@@ -14,13 +16,15 @@ public class zero_Api_Media_ChildCounts : ZeroIndex<zero.Media.Media, zero_Api_M
Map = items => items.Where(x => x.ParentId != null).Select(item => new Result
{
Id = item.ParentId,
ChildCount = 1
ChildCount = 1,
ChildFolderCount = item.IsFolder ? 1 : 0
});
Reduce = results => results.GroupBy(x => new { x.Id }).Select(group => new Result()
{
Id = group.Key.Id,
ChildCount = group.Sum(x => x.ChildCount)
ChildCount = group.Sum(x => x.ChildCount),
ChildFolderCount = group.Sum(x => x.ChildFolderCount)
});
StoreAllFields(FieldStorage.Yes);
@@ -11,12 +11,12 @@ public class zero_Api_Media_Listing : ZeroIndex<zero.Media.Media>
Name = item.Name,
ParentId = item.ParentId,
CreatedDate = item.CreatedDate,
Type = item.Type
IsFolder = item.IsFolder
});
Index(x => x.Name, FieldIndexing.Search);
Index(x => x.ParentId, FieldIndexing.Exact);
Index(x => x.CreatedDate, FieldIndexing.Exact);
Index(x => x.Type, FieldIndexing.Exact);
Index(x => x.IsFolder, FieldIndexing.Exact);
}
}
@@ -0,0 +1,6 @@
namespace zero.Api.Endpoints.Media;
public class MediaBulkDeleteOperation
{
public string[] Ids { get; set; }
}
@@ -0,0 +1,8 @@
namespace zero.Api.Endpoints.Media;
public class MediaBulkMoveOperation
{
public string ParentId { get; set; }
public string[] Ids { get; set; }
}
+1 -1
View File
@@ -2,7 +2,7 @@
public class MediaEdit : DisplayModel<zero.Media.Media>
{
public MediaType Type { get; set; }
public bool IsFolder { get; set; }
public string ParentId { get; set; }
}
@@ -26,7 +26,7 @@ public class MediaMapperProfile : ZeroMapperProfile
target.Id = source.Id;
target.Name = source.Name;
target.ParentId = source.ParentId;
target.IsFolder = source.Type == MediaType.Folder;
target.IsFolder = source.IsFolder;
target.Children = 0;
target.Size = source.Size;
@@ -40,7 +40,7 @@ public class MediaMapperProfile : ZeroMapperProfile
protected virtual void Map(zero.Media.Media source, MediaEdit target, IZeroMapperContext ctx)
{
this.MapDisplayData(source, target);
target.Type = source.Type;
target.IsFolder = source.IsFolder;
target.ParentId = source.ParentId;
}
+1 -1
View File
@@ -2,5 +2,5 @@
public class MediaSave : SaveModel<zero.Media.Media>
{
public MediaType Type { get; set; }
public bool IsFolder { get; set; }
}
+42 -5
View File
@@ -19,7 +19,7 @@ public class MediaController : ZeroApiTreeEntityStoreController<zero.Media.Media
[HttpGet("empty")]
[ZeroAuthorize(MediaPermissions.Create)]
public virtual async Task<ActionResult<zero.Media.Media>> Empty(MediaType type, string flavor = null) => await EmptyModel(flavor, x => x.Type = type);
public virtual async Task<ActionResult<zero.Media.Media>> Empty(bool folder = false, string flavor = null) => await EmptyModel(flavor, x => x.IsFolder = folder);
[HttpGet("{id}")]
@@ -29,12 +29,12 @@ public class MediaController : ZeroApiTreeEntityStoreController<zero.Media.Media
[HttpGet("{id}/children")]
[ZeroAuthorize(MediaPermissions.Read)]
public virtual async Task<ActionResult<Paged>> GetChildren(string id, [FromQuery] ListQuery<zero.Media.Media> query)
public virtual async Task<ActionResult<Paged>> GetChildren(string id, [FromQuery] ListQuery<zero.Media.Media> query, [FromQuery] bool folders = false)
{
id = id == "root" ? null : id;
query.OrderQuery = q => q.OrderBy(x => x.Type).OrderByDescending(x => x.CreatedDate);
Paged<zero.Media.Media> result = await Store.LoadChildren<zero_Api_Media_Listing>(id, query.Page, query.PageSize, q => q.Filter(query));
query.OrderQuery = q => q.OrderByDescending(x => x.IsFolder).ThenByDescending(x => x.CreatedDate);
Paged<zero.Media.Media> result = await Store.LoadChildren<zero_Api_Media_Listing>(id, query.Page, query.PageSize, q => q.WhereIf(x => x.IsFolder, folders).Filter(query));
Paged<MediaBasic> mappedResult = Mapper.Map<zero.Media.Media, MediaBasic>(result);
// get children for all folders
@@ -48,7 +48,12 @@ public class MediaController : ZeroApiTreeEntityStoreController<zero.Media.Media
{
if (item.IsFolder)
{
item.Children = children.FirstOrDefault(x => x.Id == item.Id)?.ChildCount ?? 0;
zero_Api_Media_ChildCounts.Result childCounts = children.FirstOrDefault(x => x.Id == item.Id);
if (childCounts != null)
{
item.Children = folders ? childCounts.ChildFolderCount : childCounts.ChildCount;
}
}
}
@@ -65,6 +70,38 @@ public class MediaController : ZeroApiTreeEntityStoreController<zero.Media.Media
}
[HttpPut("bulk/move")]
[ZeroAuthorize(MediaPermissions.Update)]
public virtual async Task<ActionResult<IEnumerable<Result>>> BulkMove(MediaBulkMoveOperation operation)
{
List<Result<string>> results = new();
foreach (string id in operation.Ids)
{
Result<zero.Media.Media> result = await Store.Move(id, operation.ParentId);
results.Add(result.ConvertTo(id));
}
return results;
}
[HttpDelete("bulk/delete")]
[ZeroAuthorize(MediaPermissions.Update)]
public virtual async Task<ActionResult<IEnumerable<Result>>> BulkDelete(MediaBulkDeleteOperation operation)
{
List<Result<string>> results = new();
foreach (string id in operation.Ids)
{
Result<string[]> result = await Store.DeleteWithDescendants(id);
results.Add(result.ConvertTo(id));
}
return results;
}
//[HttpGet("")]
//[ZeroAuthorize(MediaPermissions.Read)]
//public virtual Task<ActionResult<Paged>> Get([FromQuery] ListQuery<zero.Media.Media> query)
+29
View File
@@ -70,6 +70,35 @@ public class ApiResponseFilterAttribute : ResultFilterAttribute
result.Value = response;
}
// format bulk patch results
else if (result.Value is IEnumerable<Result<string>> list)
{
int countSucceeded = list.Count(x => x.IsSuccess);
int countFailed = list.Count() - countSucceeded;
BulkOperationApiResponse response = countFailed > 0 ? new BulkOperationWithErrorsApiResponse() : new BulkOperationApiResponse();
response.CountSucceeded = countSucceeded;
response.CountFailed = countFailed;
if (countFailed > 0 && response is BulkOperationWithErrorsApiResponse errorResponse)
{
errorResponse.Errors = list.Where(x => !x.IsSuccess).SelectMany(x => x.Errors.Select(e => new BulkOperationErrorApiResponseError()
{
AffectedId = x.Model,
Category = ApiErrorCodes.Categories.Validation,
Code = "// TODO",
Property = e.Property,
Message = e.Message
})).ToList();
}
response.Success = countSucceeded > 0;
response.Status = countSucceeded < 1 ? StatusCodes.Status400BadRequest : result.StatusCode.Value;
result.StatusCode = response.Status;
result.Value = response;
}
// format model results
else
{
@@ -0,0 +1,19 @@
namespace zero.Api.Models;
public class BulkOperationApiResponse : ApiResponse
{
public int CountSucceeded { get; set; }
public int CountFailed { get; set; }
}
public class BulkOperationWithErrorsApiResponse : BulkOperationApiResponse
{
public List<BulkOperationErrorApiResponseError> Errors { get; set; } = new();
}
public class BulkOperationErrorApiResponseError : ErrorApiResponseError
{
public string AffectedId { get; set; }
}
@@ -1,5 +1,5 @@
<template>
<div class="ui-datagrid-outer">
<div class="ui-datagrid-outer" :class="{'is-selecting': configuration.selectable && selected.length > 0}">
<div class="ui-datagrid">
<div class="ui-datagrid-items" :style="'grid-template-columns: repeat(auto-fill, minmax(' + configuration.width + 'px, 1fr))'" :class="{'is-block': configuration.block }">
<div class="ui-datagrid-item" v-for="(item, index) in items" :key="index" v-on:contextmenu="onRightClicked(item, $event)">
@@ -107,7 +107,8 @@
},
debouncedUpdate: null,
actionProps: {
item: null
item: null,
selected: false
},
selected: []
}),
@@ -233,6 +234,7 @@
this.actionProps.item = item;
this.actionProps.event = ev;
this.actionProps.selected = this.configuration.selectable && this.selected.indexOf(item) > -1;
dropdown.toggle();
@@ -82,7 +82,7 @@
computed: {
actionsDefined()
{
return this.$scopedSlots.hasOwnProperty('actions');
return this.$slots.hasOwnProperty('actions');
}
},
@@ -12,5 +12,5 @@ export default {
update: async (model: any, config?: ApiRequestConfig) => await put('applications/' + model.id, model, { ...config, system: true }),
delete: async (id: string, config?: ApiRequestConfig) => await del('applications/' + id, { ...config, system: true }),
delete: async (id: string, config?: ApiRequestConfig) => await del('applications/' + id, null, { ...config, system: true }),
};
@@ -12,5 +12,5 @@ export default {
update: (model: any, config?: ApiRequestConfig) => put('countries/' + model.id, model, config),
delete: (id: string, config?: ApiRequestConfig) => del('countries/' + id, config),
delete: (id: string, config?: ApiRequestConfig) => del('countries/' + id, null, config),
};
@@ -12,5 +12,5 @@ export default {
update: (model: any, config?: ApiRequestConfig) => put('languages/' + model.id, model, config),
delete: (id: string, config?: ApiRequestConfig) => del('languages/' + id, config),
delete: (id: string, config?: ApiRequestConfig) => del('languages/' + id, null, config),
};
@@ -9,8 +9,13 @@ export default {
getChildren: (id: string, query: ApiRequestQuery, config?: ApiRequestConfig) => get(`media/${id}/children`, { ...config, params: { ...query } }),
getFolderChildren: (id: string, query: ApiRequestQuery, config?: ApiRequestConfig) => get(`media/${id}/children`, { ...config, params: { ...query, folders: true } }),
getHierarchy: (id: string, config?: ApiRequestConfig) => get(`media/${id}/hierarchy`, { ...config }),
bulkDelete: (ids: string[], config?: ApiRequestConfig) => del(`media/bulk/delete`, { ids }, { ...config }),
bulkMove: (ids: string[], parentId: string, config?: ApiRequestConfig) => put(`media/bulk/move`, { parentId, ids }, { ...config }),
//create: (model: any, config?: ApiRequestConfig) => post('countries', model, config),
@@ -0,0 +1,211 @@
<template>
<ui-trinity class="pages-move">
<template v-slot:header>
<ui-header-bar title="@ui.move.title" :back-button="false" :close-button="true" />
</template>
<template v-slot:footer>
<ui-button type="light onbg" label="@ui.close" @click="config.close" />
<ui-button type="accent" label="@ui.move.action" @click="onSave" :state="state" :disabled="!loaded" />
</template>
<p class="pages-move-text" v-localize:html="{ key: '@ui.move.text', tokens: { name: model.name } }"></p>
<div class="ui-box pages-move-items">
<ui-tree v-if="loaded" ref="tree" :get="getItems" @select="onSelect" :selection="selected" />
</div>
</ui-trinity>
</template>
<script>
import api from '../api';
//import MediaFolderApi from 'zero/api/media-folder.js';
//import MediaApi from 'zero/api/media.js';
//import Notification from 'zero/helpers/notification.js'
export default {
props: {
model: Object,
config: Object
},
data: () => ({
hierarchy: [],
items: [],
selected: [],
state: 'default',
cache: {},
prevItem: null,
loaded: false,
ids: [],
newParentId: null
}),
async mounted()
{
this.newParentId = this.model.parentId;
this.selected = [this.model.parentId];
this.hierarchy = (await api.getHierarchy(this.model.parentId)).data.map(x => x.id);
this.hierarchy.splice(this.hierarchy.length - 1, 1);
this.ids = this.model.items.map(x => x.id);
this.loaded = true;
},
methods: {
onSelect(item)
{
item.isSelected = true;
if (this.prevItem && this.prevItem.id != item.id)
{
this.prevItem.isSelected = false;
}
this.prevItem = item;
this.newParentId = item.id;
this.selected = [item];
//this.config.confirm(item);
},
async getItems(parent)
{
const id = !parent ? 'root' : parent;
if (this.cache[id])
{
return Promise.resolve(this.cache[id]);
}
const response = await api.getFolderChildren(id, { pageSize: 50 });
const items = response.data;
if (!parent)
{
items.splice(0, 0, {
id: null,
parentId: null,
image: null,
name: '@page.root',
children: 0,
isFolder: true,
root: true
});
}
const result = items.map(item =>
{
return {
id: item.id,
parentId: item.parentId,
sort: 0,
name: item.name,
icon: item.root ? 'fth-arrow-down-circle' : (item.isFolder ? 'fth-folder' : 'fth-file'),
isOpen: this.hierarchy.indexOf(item.id) > -1,
modifier: null,
hasChildren: item.children > 0,
childCount: item.children,
isInactive: false,
hasActions: false,
disabled: item.id == 'recyclebin' || this.ids.indexOf(item.id) > -1
};
})
this.cache[id] = result;
return result;
//response.forEach(item =>
//{
// //item.disabled = true;
// item.isSelected = this.model.parentId == item.id;
// if (item.isSelected)
// {
// this.prevItem = item;
// }
// item.disabled = item.id === 'recyclebin' || item.id == this.model.id;
// item.hasActions = false;
//});
},
onSave()
{
if (this.model.parentId == this.newParentId)
{
this.config.close();
return;
}
this.state = 'loading';
// TODO
// 1. bulk move
// 2. close overlay
// 3. update current output
// 4. show notification
// 5. eventually bulk result
//(this.config.isFolder ? MediaFolderApi : MediaApi).move(this.model.id, this.selected.id).then(res =>
//{
// if (res.success)
// {
// this.state = 'success';
// this.config.confirm(res.model);
// }
// else
// {
// this.state = 'error';
// Notification.error(res.errors[0].message);
// }
//});
}
}
}
</script>
<style lang="scss">
.pages-move .ui-box
{
margin: 0;
padding: 16px 0;
.ui-tree-item.is-disabled
{
opacity: .5;
}
.ui-tree-item.is-selected, .ui-tree-item:hover:not(.is-disabled)
{
background: var(--color-tree-selected);
}
.ui-tree-item.is-selected
{
&:after
{
font-family: "Feather";
content: "\e83e";
font-size: 16px;
color: var(--color-primary);
}
.ui-tree-item-text
{
font-weight: bold;
}
}
}
.pages-move content
{
padding-top: 0;
}
.pages-move-text
{
margin: 0 0 20px;
}
</style>
@@ -0,0 +1,36 @@
import api from '../../api';
import * as overlays from '../../../../services/overlay';
export class Actions
{
constructor()
{
}
remove(items: any[])
{
console.info({ action: 'remove', items });
}
async move(items: any[])
{
const result = await overlays.open({
component: () => import('../../overlays/move.vue'),
display: 'editor',
width: 560,
model: {
parentId: items[0].parentId,
name: items.length + ' items',
items: items
}
});
console.info(result);
}
};
@@ -61,12 +61,12 @@
border-radius: var(--radius);
}
.media-items.is-selecting .media-item
.ui-datagrid-outer.is-selecting .media-item
{
opacity: .5;
opacity: .55;
}
.media-items.is-selecting .media-item.is-selected
.ui-datagrid-outer.is-selecting .media-item.is-selected
{
opacity: 1;
}
@@ -121,9 +121,10 @@
left: -10px;
top: -10px;
background: var(--color-accent);
color: white;
color: var(--color-accent-fg);
box-shadow: 1px 1px 0 1px var(--color-shadow);
font-size: 11px;
.is-selected &
{
display: inline-flex;
@@ -1,26 +1,57 @@
<template>
<div>
selection
<div class="media-selection">
<ui-button type="light onbg" label="@media.selection.clear" @click="clearSelection" />
<ui-dropdown align="right">
<template v-slot:button>
<ui-button type="accent" :label="localize('@media.selection.selected', { tokens: { count: selected.length }})" caret="down" />
</template>
<ui-dropdown-button label="@ui.move.title" icon="fth-corner-down-right" @click="move" />
<ui-dropdown-button label="@ui.delete" icon="fth-trash" @click="remove" />
</ui-dropdown>
</div>
</template>
<script>
import { localize } from '../../../../services/localization';
export default {
name: 'mediaOverviewSelection',
props: {
value: {
type: Object,
default: () => { }
},
selected: {
type: Boolean,
default: false
type: Array,
default: () => []
}
},
methods: {
localize,
clearSelection()
{
this.$emit('clear');
},
move()
{
this.$emit('move', this.selected);
},
remove()
{
this.$emit('remove', this.selected);
}
}
};
</script>
<style lang="scss">
.media-selection
{
display: flex;
gap: 10px;
}
</style>
@@ -14,19 +14,20 @@
</h2>
</template>
<ui-search v-model="gridConfig.search" class="onbg" />
{{selected}}
<template v-if="selected.length < 1">
<ui-search v-model="gridConfig.search" class="onbg" />
</template>
<media-selection v-else :selected="selected" @clear="clearSelection" @move="actions.move" @remove="actions.remove" />
</ui-header-bar>
<div class="ui-view-box">
<div class="media-items" :class="{ 'is-selecting': selecting }">
<div class="media-items" :class="{ 'is-selecting': selected.length > 0 }">
<ui-datagrid ref="grid" v-model="gridConfig" @select="onSelected" @count="count = $event">
<template v-slot:actions="props">
<template v-if="selected.length < 1" v-slot:actions="props">
<ui-dropdown-button v-if="props.item && props.item.isFolder" label="@ui.open.title" icon="fth-arrow-right" @click="goToFolder(props.item.id)" />
<ui-dropdown-button label="@ui.edit.title" icon="fth-edit-2" @click="edit(props.item, props.item.isFolder)" />
<ui-dropdown-button label="@ui.move.title" icon="fth-corner-down-right" @click="move(props.item, props.item.isFolder)" />
<ui-dropdown-button label="Select" icon="fth-check-circle" @click="$refs.grid.select(props.item)" />
<ui-dropdown-button label="@ui.selection.select" icon="fth-check-circle-2" @click="$refs.grid.select(props.item)" />
<ui-dropdown-separator />
<ui-dropdown-button label="@ui.delete" icon="fth-trash" @click="remove(props.item, props.item.isFolder)" />
</template>
@@ -44,13 +45,16 @@
import { defineComponent } from 'vue';
import api from '../../api';
import MediaItem from './overview-item.vue';
import MediaSelection from './overview-selection.vue';
import { Actions } from './overview-actions';
export default defineComponent({
props: ['parentId'],
components: { MediaItem },
components: { MediaItem, MediaSelection },
data: () => ({
actions: null,
paging: {},
hierarchy: {},
search: null,
@@ -72,12 +76,26 @@
},
watch: {
'$route': function (val)
{
this.clearSelection();
}
},
created()
{
this.gridConfig.items = this.getItems;
},
mounted()
{
this.actions = new Actions();
},
methods: {
async getItems(query)
{
@@ -89,6 +107,7 @@
query.search = this.gridConfig.search;
query.folderId = this.parentId;
query.searchIsGlobal = true;
query.pageSize = 50;
const hierarchy = await api.getHierarchy(this.id);
this.hierarchy = hierarchy.data;
@@ -99,6 +118,14 @@
onSelected(selection)
{
this.selected = selection;
},
clearSelection()
{
if (this.$refs.grid)
{
this.$refs.grid.clearSelection();
}
}
}
+2 -2
View File
@@ -15,9 +15,9 @@ export function post(url: string, data: any, config?: ApiRequestConfig)
return send({ method: 'post', url, data, ...config });
}
export function del(url: string, config?: ApiRequestConfig)
export function del(url: string, data: any, config?: ApiRequestConfig)
{
return send({ method: 'delete', url, ...config });
return send({ method: 'delete', url, data, ...config });
}
export function put(url: string, data: any, config?: ApiRequestConfig)
@@ -3,7 +3,7 @@
// theme-agnostic colors
:root
{
--color-accent: #F9AA19; // #F9AA19
--color-accent: #67bdb1; // #F9AA19 // #67bdb1
--color-accent-fg: #ffffff;
--color-negative: rgb(216, 40, 83);
@@ -51,6 +51,10 @@
},
"name": "Name",
"select": "Select",
"selection": {
"select": "Select",
"deselect": "Deselect"
},
"icon": "Icon",
"icon_select": "Select an icon",
"tab_general": "General",
@@ -646,7 +650,9 @@
"addfile_text": "Add one or more files to the current filter"
},
"selection": {
"clear": "Clear selection"
"clear": "Clear selection",
"selected": "{count} selected",
"selectaction": "Action"
},
"fields": {
"foldername_placeholder": "Enter a name ...",
+1 -1
View File
@@ -40,7 +40,7 @@ public class MediaCreator : IMediaCreator
Media model = await Store.Empty();
model.Name = normalizedFilename;
model.ParentId = folderId;
model.Type = isImage ? MediaType.Image : MediaType.File;
model.IsFolder = false;
// create directory which hosts the media file
// the media directory is a flat folder where each folder contains one media file (+ thumbs)
+3 -3
View File
@@ -62,7 +62,7 @@ public class MediaManagement : IMediaManagement
public virtual async Task<Media> GetFile(string id)
{
Media file = await Store.Load(id);
return file != null && file.Type != MediaType.Folder ? file : null;
return file != null && !file.IsFolder ? file : null;
}
@@ -93,7 +93,7 @@ public class MediaManagement : IMediaManagement
public virtual async Task<Media> GetFolder(string id)
{
Media folder = await Store.Load(id);
return folder != null && folder.Type == MediaType.Folder ? folder : null;
return folder != null && folder.IsFolder ? folder : null;
}
@@ -101,7 +101,7 @@ public class MediaManagement : IMediaManagement
public virtual async Task<Result<Media>> CreateFolder(Media folder)
{
folder.IsActive = true;
folder.Type = MediaType.Folder;
folder.IsFolder = true;
return await Store.Create(folder);
}
+2 -2
View File
@@ -22,7 +22,7 @@ public class MediaStore : TreeEntityStore<Media>, IMediaStore
}
Media parent = await Load(parentId);
return parent != null && parent.Type == MediaType.Folder;
return parent != null && parent.IsFolder;
}
@@ -32,7 +32,7 @@ public class MediaStore : TreeEntityStore<Media>, IMediaStore
validator.RuleFor(x => x.Name).Length(2, 120);
validator.RuleFor(x => x.IsActive).Equal(true);
validator.When(x => x.Type == MediaType.Folder, () =>
validator.When(x => x.IsFolder, () =>
{
validator.RuleFor(x => x.ParentId).Exists(Context.Store);
});
+5 -5
View File
@@ -11,6 +11,11 @@ public class Media : ZeroEntity, ISupportsTrees
IsActive = true;
}
/// <summary>
/// Whether this media item is a folder or a file
/// </summary>
public bool IsFolder { get; set; }
/// <summary>
/// Id/name of the phyiscal folder which is stored on disk/cloud
/// </summary>
@@ -56,9 +61,4 @@ public class Media : ZeroEntity, ISupportsTrees
/// Optional focal point for an image
/// </summary>
public MediaFocalPoint FocalPoint { get; set; }
/// <summary>
/// Type of the media
/// </summary>
public MediaType Type { get; set; }
}
-8
View File
@@ -1,8 +0,0 @@
namespace zero.Media;
public enum MediaType
{
Folder = 0,
File = 1,
Image = 2
}
+3 -1
View File
@@ -56,7 +56,9 @@ public partial class StoreOperations : IStoreOperations
IRavenQueryable<T> queryable = Session.Query<T, TIndex>().Where(x => x.ParentId == parentId).Statistics(out QueryStatistics statistics);
querySelector ??= x => x.OrderBy(x => x.Sort);
List<T> result = await querySelector(queryable).Paging(pageNumber, pageSize).ToListAsync();
var query = querySelector(queryable).Paging(pageNumber, pageSize);
List<T> result = await query.ToListAsync();
return new Paged<T>(result, statistics.TotalResults, pageNumber, pageSize);
}