V8: Add alternate 'List View' to Media Picker - Take 2 (#7912)

This commit is contained in:
Marc Goodson
2020-07-27 15:13:07 +01:00
committed by GitHub
parent fc09eeefaf
commit 991f2d0f42
8 changed files with 270 additions and 103 deletions
@@ -369,7 +369,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (isContent || isMedia || isMember)
sql
.AndSelect<ContentVersionDto>(x => Alias(x.Id, "versionId"))
.AndSelect<ContentVersionDto>(x => Alias(x.Id, "versionId"), x=>x.VersionDate)
.AndSelect<ContentTypeDto>(x => x.Alias, x => x.Icon, x => x.Thumbnail, x => x.IsContainer, x => x.Variations);
if (isContent)
@@ -490,7 +490,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (isContent || isMedia || isMember)
sql
.AndBy<ContentVersionDto>(x => x.Id)
.AndBy<ContentVersionDto>(x => x.Id, x => x.VersionDate)
.AndBy<ContentTypeDto>(x => x.Alias, x => x.Icon, x => x.Thumbnail, x => x.IsContainer, x => x.Variations);
if (defaultSort)
@@ -594,6 +594,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
public string Text { get; set; }
public Guid NodeObjectType { get; set; }
public DateTime CreateDate { get; set; }
public DateTime VersionDate { get; set; }
public int Children { get; set; }
public int VersionId { get; set; }
public string Alias { get; set; }
@@ -627,6 +628,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
entity.Trashed = dto.Trashed;
entity.CreateDate = dto.CreateDate;
entity.UpdateDate = dto.VersionDate;
entity.CreatorId = dto.UserId ?? Constants.Security.UnknownUserId;
entity.Id = dto.NodeId;
entity.Key = dto.UniqueId;
@@ -322,6 +322,41 @@ Use this directive to generate a thumbnail grid of media items.
scope.$on('$destroy', function() {
unbindItemsWatcher();
});
//determine if sort is current
scope.sortColumn = "name";
scope.sortReverse = false;
scope.sortDirection = "asc";
//check sort status
scope.isSortDirection = function (col, direction) {
return col === scope.sortColumn && direction === scope.sortDirection;
};
//change sort
scope.setSort = function (col) {
if (scope.sortColumn === col) {
scope.sortReverse = !scope.sortReverse;
}
else {
scope.sortColumn = col;
if (col === "updateDate") {
scope.sortReverse = true;
}
else {
scope.sortReverse = false;
}
}
scope.sortDirection = scope.sortReverse ? "desc" : "asc";
}
// sort function
scope.sortBy = function (item) {
if (scope.sortColumn === "updateDate") {
return [-item['isFolder'],item['updateDate']];
}
else {
return [-item['isFolder'],item['name']];
}
};
}
@@ -345,7 +380,8 @@ Use this directive to generate a thumbnail grid of media items.
onlyImages: "@",
onlyFolders: "@",
includeSubFolders: "@",
currentFolderId: "@"
currentFolderId: "@",
showMediaList: "="
},
link: link
};
@@ -217,3 +217,19 @@
text-decoration: none;
box-shadow: 0 1px 2px rgba(0,0,0,.2);
}
/*for the listview*/
.umb-media-grid__list-item.selected, .umb-media-grid__list-item.selected:hover, .umb-media-grid__list-item.selected:focus {
border: 2px solid #f5c1bc !important;
}
.umb-media-grid__list-view .umb-table-cell.umb-table__name {
flex: 1 1 25%;
max-width: none;
white-space: normal;
}
.umb-media-grid__list-view .umb-table-cell.umb-table__name .item-name {
white-space:normal;
}
@@ -22,6 +22,10 @@ angular.module("umbraco")
vm.clickHandler = clickHandler;
vm.clickItemName = clickItemName;
vm.gotoFolder = gotoFolder;
vm.shouldShowUrl = shouldShowUrl;
vm.toggleListView = toggleListView;
vm.selectLayout = selectLayout;
vm.showMediaList = false;
var dialogOptions = $scope.model;
@@ -81,6 +85,11 @@ angular.module("umbraco")
filter: '',
dataTypeKey: dataTypeKey
};
vm.layout = {
layouts: [{ name: "Grid", icon: "icon-thumbnails-small", path: "gridpath", selected: true },
{ name: "List", icon: "icon-list", path: "listpath", selected: true }],
activeLayout: { name: "Grid", icon: "icon-thumbnails-small", path: "gridpath", selected: true }
};
// preload selected item
$scope.target = null;
@@ -232,6 +241,33 @@ angular.module("umbraco")
return getChildren(folder.id);
}
function shouldShowUrl() {
if (!$scope.model.target) {
return false;
}
if ($scope.model.target.id) {
return false;
}
if ($scope.model.target.url && $scope.model.target.url.toLower().indexOf("blob:") === 0) {
return false;
}
return true;
}
function toggleListView() {
vm.showMediaList = !vm.showMediaList;
}
function selectLayout(layout) {
//this somehow doesn't set the 'active=true' property for the chosen layout
vm.layout.activeLayout = layout;
//workaround
vm.layout.layouts.forEach(element => element.active = false);
layout.active = true;
//set whether to toggle the list
vm.showMediaList = (layout.name === "List");
}
function clickHandler(media, event, index) {
if (media.isFolder) {
@@ -445,7 +481,6 @@ angular.module("umbraco")
// set thumbnail and src
mediaItem.thumbnail = mediaHelper.resolveFileFromEntity(mediaItem, true);
mediaItem.image = mediaHelper.resolveFileFromEntity(mediaItem, false);
// set properties to match a media object
if (mediaItem.metaData) {
mediaItem.properties = [];
@@ -473,6 +508,9 @@ angular.module("umbraco")
}
);
}
if (mediaItem.metaData.UpdateDate !== null) {
mediaItem.updateDate = mediaItem.metaData.UpdateDate;
}
}
}
@@ -487,7 +525,9 @@ angular.module("umbraco")
data[i].thumbnail = mediaHelper.resolveFileFromEntity(data[i], true);
data[i].image = mediaHelper.resolveFileFromEntity(data[i], false);
}
if (data[i].metaData.UpdateDate !== null){
data[i].updateDate = data[i].metaData.UpdateDate;
}
data[i].filtered = allowedTypes && allowedTypes.indexOf(data[i].metaData.ContentTypeAlias) < 0;
}
@@ -22,37 +22,52 @@
<i class="icon-search" aria-hidden="true"></i>
<input type="text"
class="umb-search-field search-query -full-width-input"
ng-model="vm.searchOptions.filter"
localize="placeholder"
placeholder="@placeholders_search"
ng-change="vm.changeSearch()"
umb-auto-focus
ng-model="vm.searchOptions.filter"
localize="placeholder"
placeholder="@placeholders_search"
ng-change="vm.changeSearch()"
umb-auto-focus
no-dirty-check />
<div class="form-search__toggle">
<umb-checkbox
model="filterOptions.excludeSubFolders"
on-change="vm.toggle()"
text="Include subfolders in search"
label-key="general_excludeFromSubFolders">
model="filterOptions.excludeSubFolders"
on-change="vm.toggle()"
text="Include subfolders in search"
label-key="general_excludeFromSubFolders">
</umb-checkbox>
</div>
</div>
<div class="upload-button">
<umb-button
type="button"
label-key="general_upload"
action="vm.upload()"
disabled="lockedFolder"
button-style="action">
type="button"
label-key="general_upload"
action="vm.upload()"
disabled="lockedFolder"
button-style="action">
</umb-button>
</div>
</div>
</div>
<div class="row umb-control-group" ng-show="vm.searchOptions.filter && images.length > 0 && !vm.loading && !activeDrag">
<div style="margin-left:auto;text-align:right" >
<umb-layout-selector ng-show="vm.layout.layouts"
layouts="vm.layout.layouts"
active-layout="vm.layout.activeLayout"
on-layout-select="vm.selectLayout(layout)">
</umb-layout-selector>
</div>
</div>
<div class="row umb-control-group" ng-show="!vm.searchOptions.filter">
<div class="pull-right">
<umb-layout-selector ng-show="vm.layout.layouts"
layouts="vm.layout.layouts"
active-layout="vm.layout.activeLayout"
on-layout-select="vm.selectLayout(layout)">
</umb-layout-selector>
</div>
<ul class="umb-breadcrumbs">
<li ng-hide="startNodeId != -1" class="umb-breadcrumbs__ancestor">
<button type="button" class="umb-breadcrumbs__action umb-outline umb-outline--surronding" ng-click="vm.gotoFolder()" ng-class="{'--current':path.length === 0}">
@@ -65,19 +80,19 @@
<span class="umb-breadcrumbs__separator" aria-hidden="true">&#47;</span>
</li>
<li class="umb-breadcrumbs__ancestor" ng-show="!lockedFolder">
<button type="button" class="umb-breadcrumbs__action umb-outline umb-outline--surronding" ng-hide="model.showFolderInput" ng-click="model.showFolderInput = true">
<i class="icon icon-add small" aria-hidden="true"></i>
<span class="sr-only">
<localize key="visuallyHiddenTexts_createNewFolder">Create new folder</localize>
</span>
</button>
<button type="button" class="umb-breadcrumbs__action umb-outline umb-outline--surronding" ng-hide="model.showFolderInput" ng-click="model.showFolderInput = true">
<i class="icon icon-add small" aria-hidden="true"></i>
<span class="sr-only">
<localize key="visuallyHiddenTexts_createNewFolder">Create new folder</localize>
</span>
</button>
<input type="text"
class="umb-breadcrumbs__add-ancestor"
ng-show="model.showFolderInput"
ng-model="model.newFolderName"
ng-keydown="enterSubmitFolder($event)"
ng-blur="vm.submitFolder()"
<input type="text"
class="umb-breadcrumbs__add-ancestor"
ng-show="model.showFolderInput"
ng-model="model.newFolderName"
ng-keydown="enterSubmitFolder($event)"
ng-blur="vm.submitFolder()"
focus-when="{{model.showFolderInput}}" />
</li>
</ul>
@@ -85,44 +100,45 @@
</div>
<umb-file-dropzone
ng-if="vm.acceptedMediatypes.length > 0 && !vm.loading && !lockedFolder"
accepted-mediatypes="vm.acceptedMediatypes"
parent-id="{{currentFolder.id}}"
files-uploaded="vm.onUploadComplete"
files-queued="vm.onFilesQueue"
accept="{{vm.acceptedFileTypes}}"
max-file-size="{{vm.maxFileSize}}"
hide-dropzone="{{ !activeDrag && (images.length > 0 || vm.searchOptions.filter !== '') }}"
compact="{{ images.length > 0 }}">
ng-if="vm.acceptedMediatypes.length > 0 && !vm.loading && !lockedFolder"
accepted-mediatypes="vm.acceptedMediatypes"
parent-id="{{currentFolder.id}}"
files-uploaded="vm.onUploadComplete"
files-queued="vm.onFilesQueue"
accept="{{vm.acceptedFileTypes}}"
max-file-size="{{vm.maxFileSize}}"
hide-dropzone="{{ !activeDrag && (images.length > 0 || vm.searchOptions.filter !== '') }}"
compact="{{ images.length > 0 }}">
</umb-file-dropzone>
<umb-media-grid
ng-if="!vm.loading"
items="images"
on-click="vm.clickHandler"
on-click-name="vm.clickItemName"
item-max-width="150"
item-max-height="150"
item-min-width="100"
item-min-height="100"
disable-folder-select={{disableFolderSelect}}
only-images={{onlyImages}}
only-folders={{onlyFolders}}
include-sub-folders={{!filterOptions.excludeSubFolders}}
current-folder-id="{{currentFolder.id}}">
ng-if="!vm.loading"
items="images"
on-click="vm.clickHandler"
on-click-name="vm.clickItemName"
item-max-width="150"
item-max-height="150"
item-min-width="100"
item-min-height="100"
disable-folder-select={{disableFolderSelect}}
only-images={{onlyImages}}
only-folders={{onlyFolders}}
include-sub-folders={{!filterOptions.excludeSubFolders}}
current-folder-id="{{currentFolder.id}}"
show-media-list=vm.showMediaList>
</umb-media-grid>
<umb-load-indicator
ng-if="vm.loading">
ng-if="vm.loading">
</umb-load-indicator>
<div class="flex justify-center">
<umb-pagination
ng-if="vm.searchOptions.totalPages > 0 && !vm.loading"
page-number="vm.searchOptions.pageNumber"
total-pages="vm.searchOptions.totalPages"
on-change="vm.changePagination(pageNumber)">
ng-if="vm.searchOptions.totalPages > 0 && !vm.loading"
page-number="vm.searchOptions.pageNumber"
total-pages="vm.searchOptions.totalPages"
on-change="vm.changePagination(pageNumber)">
</umb-pagination>
</div>
@@ -132,8 +148,11 @@
</div>
</form>
</umb-editor-container>
<umb-editor-footer>
@@ -1,44 +1,96 @@
<div data-element="media-grid" class="umb-media-grid">
<div data-element="media-grid-item-{{$index}}"
class="umb-media-grid__item umb-outline umb-outline--surrounding"
title="{{item.name}}"
ng-click="clickItem(item, $event, $index)"
ng-repeat="item in items | filter:filterBy"
ng-style="item.flexStyle"
ng-class="{'-selected': item.selected, '-file': !item.thumbnail, '-folder': item.isFolder, '-svg': item.extension == 'svg', '-selectable': item.selectable, '-unselectable': !item.selectable}"
>
<div>
<!--<i ng-show="item.selected" class="icon-check umb-media-grid__checkmark"></i>-->
<div data-element="media-grid-item-edit"
class="umb-media-grid__item-overlay umb-outline"
ng-class="{'-locked': item.selected || !item.file || !item.thumbnail, '-can-open': (item.isFolder ? allowOpenFolder : allowOpenFile)}"
ng-click="clickItemName(item, $event, $index)"
tabindex="{{item.isFolder && item.selectable ? '0' : '-1'}}"
>
<i ng-if="onDetailsHover" class="icon-info umb-media-grid__info" ng-mouseover="hoverItemDetails(item, $event, true)" ng-mouseleave="hoverItemDetails(item, $event, false)"></i>
<div class="umb-media-grid__item-name">{{item.name}}</div>
<div class="media-list-grid">
<div data-element="media-grid" class="umb-media-grid" ng-show="!showMediaList">
<div data-element="media-grid-item-{{$index}}"
class="umb-media-grid__item umb-outline umb-outline--surrounding"
title="{{item.name}}"
ng-click="clickItem(item, $event, $index)"
ng-repeat="item in items | filter:filterBy"
ng-style="item.flexStyle"
ng-class="{'-selected': item.selected, '-file': !item.thumbnail, '-folder': item.isFolder, '-svg': item.extension == 'svg', '-selectable': item.selectable, '-unselectable': !item.selectable}">
<div>
<!--<i ng-show="item.selected" class="icon-check umb-media-grid__checkmark"></i>-->
<div data-element="media-grid-item-edit"
class="umb-media-grid__item-overlay umb-outline"
ng-class="{'-locked': item.selected || !item.file || !item.thumbnail, '-can-open': (item.isFolder ? allowOpenFolder : allowOpenFile)}"
ng-click="clickItemName(item, $event, $index)"
tabindex="{{item.isFolder && item.selectable ? '0' : '-1'}}">
<i ng-if="onDetailsHover" class="icon-info umb-media-grid__info" ng-mouseover="hoverItemDetails(item, $event, true)" ng-mouseleave="hoverItemDetails(item, $event, false)"></i>
<div class="umb-media-grid__item-name">{{item.name}}</div>
</div>
<!-- Check backgrund -->
<div class="umb-media-grid__image-background" ng-if="item.thumbnail || item.extension === 'svg'"></div>
<!-- Image thumbnail -->
<img umb-image-lazy-load="{{item.thumbnail}}" class="umb-media-grid__item-image" width="{{item.width}}" height="{{item.height}}" ng-if="item.thumbnail" alt="{{item.name}}" draggable="false" />
<!-- SVG -->
<img class="umb-media-grid__item-image" width="{{item.width}}" height="{{item.height}}" ng-if="!item.thumbnail && item.extension === 'svg'" ng-src="{{item.image}}" alt="{{item.name}}" draggable="false" />
<!-- Transparent image - fallback - used to keep image proportions on wrapper-->
<img class="umb-media-grid__item-image-placeholder" ng-if="!item.thumbnail && item.extension !== 'svg'" src="assets/img/transparent.png" alt="{{item.name}}" draggable="false" />
<!-- Icon for files -->
<umb-file-icon ng-if="!item.thumbnail && item.extension !== 'svg'"
class="umb-media-grid__item-file-icon"
extension="{{item.extension}}"
icon="{{item.icon}}"
size="s">
</umb-file-icon>
</div>
<!-- Check backgrund -->
<div class="umb-media-grid__image-background" ng-if="item.thumbnail || item.extension === 'svg'"></div>
<!-- Image thumbnail -->
<img umb-image-lazy-load="{{item.thumbnail}}" class="umb-media-grid__item-image" width="{{item.width}}" height="{{item.height}}" ng-if="item.thumbnail" alt="{{item.name}}" draggable="false" />
<!-- SVG -->
<img class="umb-media-grid__item-image" width="{{item.width}}" height="{{item.height}}" ng-if="!item.thumbnail && item.extension === 'svg'" ng-src="{{item.image}}" alt="{{item.name}}" draggable="false" />
<!-- Transparent image - fallback - used to keep image proportions on wrapper-->
<img class="umb-media-grid__item-image-placeholder" ng-if="!item.thumbnail && item.extension !== 'svg'" src="assets/img/transparent.png" alt="{{item.name}}" draggable="false" />
<!-- Icon for files -->
<umb-file-icon ng-if="!item.thumbnail && item.extension !== 'svg'"
class="umb-media-grid__item-file-icon"
extension="{{item.extension}}"
icon="{{item.icon}}"
size="s">
</umb-file-icon>
</div>
</div>
</div>
<!-- markup for the media list -->
<div class="umb-media-grid__list-view" ng-show="showMediaList">
<div class="umb-table umb-table--condensed">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell">
<input class="umb-table__input" type="checkbox"
ng-show="vm.allowSelectAll"
ng-click="vm.selectAll($event)"
ng-checked="vm.isSelectedAll()">
</div>
<div class="umb-table-cell umb-table__name">
<button type="button"
class="umb-table-head__link sortable"
ng-click="setSort('name')"
prevent-default>
<localize key="general_name">Name</localize>
<i class="umb-table-head__icon icon" aria-hidden="true" ng-class="{'icon-navigation-up': isSortDirection('name', 'asc'), 'icon-navigation-down': isSortDirection('name', 'desc')}"></i>
</button>
</div>
<div class="umb-table-cell">
<button type="button"
class="umb-table-head__link sortable"
ng-click="setSort('updateDate')"
prevent-default>
Last Updated
<i class="umb-table-head__icon icon" aria-hidden="true" ng-class="{'icon-navigation-up': isSortDirection('updateDate', 'asc'), 'icon-navigation-down': isSortDirection('updateDate', 'desc')}"></i>
</button>
</div>
</div>
</div>
<div class="umb-table-body">
<div class="umb-table-row umb-media-grid__list-item -selectable" ng-click="clickItem(item, $event, $index)" ng-repeat="item in items | filter:filterBy | orderBy:sortBy:sortReverse" ng-class="{'-selected': item.selected}">
<div class="umb-table-cell">
<i ng-show="item.selected" class="umb-table-body__icon umb-table-body__checkicon icon-check" aria-hidden="true"></i>
<i class="{{item.icon}}" aria-hidden="true" ng-if="!item.thumbnail && item.extension != 'svg' && !item.selected"></i><i class="icon-picture" aria-hidden="true" ng-if="item.thumbnail && !item.selected"></i>
</div>
<div class="umb-table-cell umb-table__name">
<ins ng-show="item.isFolder" ng-class="{'-locked': item.selected || !item.file || !item.thumbnail}" ng-click="clickItemName(item, $event, $index)" class="icon-navigation-right"></ins> <span data-src="{{item.value.src}}" class="item-name">{{item.name}}</span>
</div>
<div class="umb-table-cell">
<span class="muted small" style="font-size:0.8em">{{item.updateDate | date:'medium'}}</span>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -63,8 +63,7 @@ namespace Umbraco.Web.Models.ContentEditing
/// The path of the entity
/// </summary>
[DataMember(Name = "path")]
public string Path { get; set; }
public string Path { get; set; }
/// <summary>
/// A collection of extra data that is available for this specific entity/entity type
/// </summary>
@@ -41,6 +41,7 @@ namespace Umbraco.Web.Models.Mapping
target.Path = source.Path;
target.Trashed = source.Trashed;
target.Udi = Udi.Create(ObjectTypes.GetUdiType(source.NodeObjectType), source.Key);
if (source is IContentEntitySlim contentSlim)
{
@@ -54,6 +55,8 @@ namespace Umbraco.Web.Models.Mapping
if (source is IMediaEntitySlim mediaSlim)
{
//pass UpdateDate for MediaPicker ListView ordering
source.AdditionalData["UpdateDate"] = mediaSlim.UpdateDate;
source.AdditionalData["MediaPath"] = mediaSlim.MediaPath;
}