Media Previews (#11888)
Co-authored-by: Niels Lyngsø <nsl@umbraco.com> Co-authored-by: Mads Rasmussen <madsr@hey.com> Co-authored-by: Paul Johnson <pmj@umbraco.com>
This commit is contained in:
1
.github/config/codeql-config.yml
vendored
1
.github/config/codeql-config.yml
vendored
@@ -5,3 +5,4 @@ paths:
|
||||
|
||||
paths-ignore:
|
||||
- '**/node_modules'
|
||||
- 'src/Umbraco.Web.UI/wwwroot'
|
||||
4
.github/workflows/codeql-analysis.yml
vendored
4
.github/workflows/codeql-analysis.yml
vendored
@@ -22,8 +22,8 @@ jobs:
|
||||
with:
|
||||
config-file: ./.github/config/codeql-config.yml
|
||||
|
||||
- name: Build
|
||||
run: dotnet build umbraco-netcore-only.sln # also runs npm build
|
||||
- name: dotnet build
|
||||
run: dotnet build umbraco-netcore-only.sln
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
|
||||
@@ -134,7 +134,7 @@ function dependencies() {
|
||||
"./node_modules/angular-messages/angular-messages.min.js.map"
|
||||
],
|
||||
"base": "./node_modules/angular-messages"
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "angular-mocks",
|
||||
"src": ["./node_modules/angular-mocks/angular-mocks.js"],
|
||||
@@ -285,11 +285,11 @@ function dependencies() {
|
||||
// add streams for node modules
|
||||
nodeModules.forEach(module => {
|
||||
var task = gulp.src(module.src, { base: module.base, allowEmpty: true });
|
||||
|
||||
|
||||
_.forEach(config.roots, function(root){
|
||||
task = task.pipe(gulp.dest(root + config.targets.lib + "/" + module.name))
|
||||
});
|
||||
|
||||
|
||||
stream.add(task);
|
||||
});
|
||||
|
||||
@@ -299,12 +299,12 @@ function dependencies() {
|
||||
_.forEach(config.roots, function(root){
|
||||
libTask = libTask.pipe(gulp.dest(root + config.targets.lib))
|
||||
});
|
||||
|
||||
|
||||
stream.add(libTask);
|
||||
|
||||
//Copies all static assets into /root / assets folder
|
||||
//css, fonts and image files
|
||||
|
||||
|
||||
var assetsTask = gulp.src(config.sources.globs.assets, { allowEmpty: true });
|
||||
assetsTask = assetsTask.pipe(imagemin([
|
||||
imagemin.gifsicle({interlaced: true}),
|
||||
@@ -321,8 +321,8 @@ function dependencies() {
|
||||
_.forEach(config.roots, function(root){
|
||||
assetsTask = assetsTask.pipe(gulp.dest(root + config.targets.assets));
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
stream.add(assetsTask);
|
||||
|
||||
// Copies all the less files related to the preview into their folder
|
||||
@@ -342,13 +342,13 @@ function dependencies() {
|
||||
configTask = configTask.pipe(gulp.dest(root + config.targets.views + "/propertyeditors/grid/config"));
|
||||
});
|
||||
stream.add(configTask);
|
||||
|
||||
|
||||
var dashboardTask = gulp.src("src/views/dashboard/default/*.jpg", { allowEmpty: true });
|
||||
_.forEach(config.roots, function(root){
|
||||
dashboardTask = dashboardTask .pipe(gulp.dest(root + config.targets.views + "/dashboard/default"));
|
||||
});
|
||||
stream.add(dashboardTask);
|
||||
|
||||
|
||||
return stream;
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ const coreBuild = parallel(dependencies, js, less, views);
|
||||
// ***********************************************************
|
||||
|
||||
exports.build = series(coreBuild, testUnit);
|
||||
exports.buildDev = series(setDevelopmentMode, coreBuild);
|
||||
|
||||
exports.coreBuild = coreBuild;
|
||||
exports.dev = series(setDevelopmentMode, coreBuild, runUnitTestServer, watchTask);
|
||||
exports.watch = series(watchTask);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"e2e": "gulp testE2e",
|
||||
"build": "gulp build",
|
||||
"build:skip-tests": "gulp coreBuild",
|
||||
"build:dev": "gulp buildDev",
|
||||
"dev": "gulp dev",
|
||||
"fastdev": "gulp fastdev",
|
||||
"watch": "gulp watch"
|
||||
|
||||
@@ -117,12 +117,11 @@
|
||||
vm.files = _.map(files, function (file) {
|
||||
var f = {
|
||||
fileName: file,
|
||||
fileSrc: file,
|
||||
isImage: mediaHelper.detectIfImageByExtension(file),
|
||||
extension: getExtension(file)
|
||||
};
|
||||
|
||||
f.fileSrc = getThumbnail(f);
|
||||
|
||||
return f;
|
||||
});
|
||||
|
||||
@@ -190,21 +189,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function getThumbnail(file) {
|
||||
|
||||
if (file.extension === 'svg') {
|
||||
return file.fileName;
|
||||
}
|
||||
|
||||
if (!file.isImage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var thumbnailUrl = mediaHelper.getThumbnailFromPath(file.fileName);
|
||||
|
||||
return thumbnailUrl;
|
||||
}
|
||||
|
||||
function getExtension(fileName) {
|
||||
var extension = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length);
|
||||
return extension.toLowerCase();
|
||||
@@ -238,7 +222,8 @@
|
||||
isImage: isImage,
|
||||
extension: extension,
|
||||
fileName: files[i].name,
|
||||
isClientSide: true
|
||||
isClientSide: true,
|
||||
fileData: files[i]
|
||||
};
|
||||
|
||||
// Save the file object to the files collection
|
||||
@@ -247,6 +232,7 @@
|
||||
//special check for a comma in the name
|
||||
newVal += files[i].name.split(',').join('-') + ",";
|
||||
|
||||
// TODO: I would love to remove this part. But I'm affright it would be breaking if removed. Its not used by File upload anymore as each preview handles the client-side data on their own.
|
||||
if (isImage || extension === "svg") {
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name umbraco.services.mediaPreview
|
||||
* @description A service providing views used for dealing with previewing files.
|
||||
*
|
||||
* ##usage
|
||||
* The service allows for registering and retrieving the view for one or more file extensions.
|
||||
*
|
||||
* You can register your own custom view in this way:
|
||||
*
|
||||
* <pre>
|
||||
* angular.module('umbraco').run(['mediaPreview', function (mediaPreview) {
|
||||
* mediaPreview.registerPreview(['docx'], "app_plugins/My_PACKAGE/preview.html");
|
||||
* }]);
|
||||
* </pre>
|
||||
*
|
||||
* Here is a example of a preview template. (base on the audio-preview).
|
||||
*
|
||||
* <pre>
|
||||
* <audio ng-if="vm.clientSide" name="{{vm.name}}" controls>
|
||||
* <source ng-init="previewUrl = URL.createObjectURL(vm.clientSideData)" ng-src="{{previewUrl}}"/>
|
||||
* </audio>
|
||||
* <audio ng-if="!vm.clientSide" name="{{vm.name}}" controls>
|
||||
* <source ng-src="{{vm.source}}" />
|
||||
* </audio>
|
||||
* </pre>
|
||||
*
|
||||
* Notice that there often is a need to differentiate based on the file-data origin. In the state of the file still begin located locally its often needed to create an Object-URL for the data to be useable in HTML. As well you might want to provide links for the uploaded file when it is uploaded to the server. See 'vm.clientSide' and 'vm.clientSideData'.
|
||||
*
|
||||
**/
|
||||
function mediaPreview() {
|
||||
|
||||
const DEFAULT_FILE_PREVIEW = "views/components/media/umbfilepreview/umb-file-preview.html";
|
||||
|
||||
var _mediaPreviews = [];
|
||||
|
||||
function init(service) {
|
||||
service.registerPreview(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes.split(","), "views/components/media/umbimagepreview/umb-image-preview.html");
|
||||
service.registerPreview(["svg"], "views/components/media/umbimagepreview/umb-image-preview.html");
|
||||
service.registerPreview(["mp4", "mov", "webm", "ogv"], "views/components/media/umbvideopreview/umb-video-preview.html");
|
||||
service.registerPreview(["mp3", "weba", "oga", "opus"], "views/components/media/umbaudiopreview/umb-audio-preview.html");
|
||||
}
|
||||
|
||||
var service = {
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.mediaPreview#getMediaPreview
|
||||
* @methodOf umbraco.services.mediaPreview
|
||||
*
|
||||
* @param {string} fileExtension A string with the file extension, example: "pdf"
|
||||
*
|
||||
* @description
|
||||
* The registered view matching this file extensions will be returned.
|
||||
*
|
||||
*/
|
||||
getMediaPreview: function (fileExtension) {
|
||||
|
||||
fileExtension = fileExtension.toLowerCase();
|
||||
|
||||
var previewObject = _mediaPreviews.find((preview) => preview.fileExtensions.indexOf(fileExtension) !== -1);
|
||||
|
||||
if(previewObject !== undefined) {
|
||||
return previewObject.view;
|
||||
}
|
||||
|
||||
return DEFAULT_FILE_PREVIEW;
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.mediaPreview#registerPreview
|
||||
* @methodOf umbraco.services.mediaPreview
|
||||
*
|
||||
* @param {array} fileExtensions An array of file extensions, example: ["pdf", "jpg"]
|
||||
* @param {array} view A URL to the view to be used for these file extensions.
|
||||
*
|
||||
* @description
|
||||
* The registered view will be used when file extensions match the given file.
|
||||
*
|
||||
*/
|
||||
registerPreview: function (fileExtensions, view) {
|
||||
_mediaPreviews.push({
|
||||
fileExtensions: fileExtensions.map(e => e.toLowerCase()),
|
||||
view: view
|
||||
})
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
init(service);
|
||||
|
||||
return service;
|
||||
} angular.module('umbraco.services').factory('mediaPreview', mediaPreview);
|
||||
@@ -202,6 +202,11 @@
|
||||
@import "components/contextdialogs/umb-dialog-datatype-delete.less";
|
||||
|
||||
@import "components/umbemailmarketing.less";
|
||||
@import "../views/components/media/umbmediapreview/umb-media-preview.less";
|
||||
@import "../views/components/media/umbaudiopreview/umb-audio-preview.less";
|
||||
@import "../views/components/media/umbfilepreview/umb-file-preview.less";
|
||||
@import "../views/components/media/umbimagepreview/umb-image-preview.less";
|
||||
@import "../views/components/media/umbvideopreview/umb-video-preview.less";
|
||||
|
||||
// Editors
|
||||
@import "../views/common/infiniteeditors/rollback/rollback.less";
|
||||
|
||||
@@ -38,4 +38,8 @@
|
||||
border-color: @gray-1;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-property-file-upload--actions {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -828,11 +828,15 @@
|
||||
.umb-fileupload {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
border: 1px solid @inputBorder;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
.umb-property-editor--limit-width();
|
||||
}
|
||||
|
||||
.umb-fileupload .preview {
|
||||
border-radius: 5px;
|
||||
border: 1px solid @gray-6;
|
||||
padding: 3px;
|
||||
background: @gray-9;
|
||||
float: left;
|
||||
|
||||
@@ -38,6 +38,8 @@ angular.module("umbraco")
|
||||
entityResource.getById(vm.mediaEntry.mediaKey, "Media").then(function (mediaEntity) {
|
||||
vm.media = mediaEntity;
|
||||
vm.imageSrc = mediaHelper.resolveFileFromEntity(mediaEntity, true);
|
||||
vm.fileSrc = mediaHelper.resolveFileFromEntity(mediaEntity, false);
|
||||
vm.fileExtension = mediaHelper.getFileExtension(vm.fileSrc);
|
||||
vm.loading = false;
|
||||
vm.hasDimensions = false;
|
||||
vm.isCroppable = false;
|
||||
|
||||
@@ -1,127 +1,148 @@
|
||||
<div class="umb-media-entry-editor" ng-controller="Umbraco.Editors.MediaEntryEditorController as vm">
|
||||
<div
|
||||
class="umb-media-entry-editor"
|
||||
ng-controller="Umbraco.Editors.MediaEntryEditorController as vm"
|
||||
>
|
||||
<ng-form name="vm.imageCropperForm" val-form-manager>
|
||||
<umb-editor-view umb-tabs ng-if="!page.loading">
|
||||
<umb-editor-header
|
||||
name="vm.title"
|
||||
name-required="false"
|
||||
name-locked="true"
|
||||
hide-alias="true"
|
||||
hide-icon="true"
|
||||
hide-description="true"
|
||||
>
|
||||
</umb-editor-header>
|
||||
|
||||
<ng-form name="vm.imageCropperForm" val-form-manager>
|
||||
<div class="umb-editor-container umb-panel-body umb-scrollable">
|
||||
<div ng-if="vm.media.trashed" class="umb-editor--trashed-message">
|
||||
<umb-icon icon="icon-trash" class="icon"></umb-icon>
|
||||
<localize key="content_nodeIsInTrash"
|
||||
>This item is in the Recycle Bin</localize
|
||||
>
|
||||
</div>
|
||||
|
||||
<umb-editor-view umb-tabs ng-if="!page.loading">
|
||||
<div class="umb-media-entry-editor__pane">
|
||||
<div class="umb-media-entry-editor__crops" ng-if="vm.isCroppable">
|
||||
<button
|
||||
class="btn-reset umb-outline"
|
||||
ng-class="{'--is-active':vm.currentCrop === null}"
|
||||
ng-click="vm.deselectCrop()"
|
||||
>
|
||||
<span class="__text">Media</span>
|
||||
</button>
|
||||
|
||||
<umb-editor-header
|
||||
name="vm.title"
|
||||
name-required="false"
|
||||
name-locked="true"
|
||||
hide-alias="true"
|
||||
hide-icon="true"
|
||||
hide-description="true">
|
||||
</umb-editor-header>
|
||||
|
||||
<div class="umb-editor-container umb-panel-body umb-scrollable">
|
||||
|
||||
<div ng-if="vm.media.trashed" class="umb-editor--trashed-message">
|
||||
<umb-icon icon="icon-trash" class="icon"></umb-icon> <localize key="content_nodeIsInTrash">This item is in the Recycle Bin</localize>
|
||||
</div>
|
||||
|
||||
<div class="umb-media-entry-editor__pane">
|
||||
<div class="umb-media-entry-editor__crops" ng-if="vm.isCroppable">
|
||||
|
||||
<button class="btn-reset umb-outline" ng-class="{'--is-active':vm.currentCrop === null}" ng-click="vm.deselectCrop()">
|
||||
<span class="__text">Media</span>
|
||||
</button>
|
||||
|
||||
<button ng-repeat="crop in vm.mediaEntry.crops track by crop.alias" class="btn-reset umb-outline" ng-class="{'--is-active':vm.currentCrop.alias === crop.alias, '--is-defined':!!vm.currentCrop.coordinates}" ng-click="vm.selectCrop(crop)">
|
||||
|
||||
<umb-image-thumbnail center="vm.mediaEntry.focalPoint"
|
||||
crop="crop.coordinates"
|
||||
src="vm.imageSrc"
|
||||
height="{{crop.height}}"
|
||||
width="{{crop.width}}"
|
||||
max-size="75">
|
||||
</umb-image-thumbnail>
|
||||
<span class="__text">{{crop.label}}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="imagecropper umb-media-entry-editor__imagecropper">
|
||||
|
||||
<div ng-if="vm.currentCrop" class="umb-cropper__container">
|
||||
|
||||
<umb-image-crop height="{{vm.currentCrop.height}}"
|
||||
width="{{vm.currentCrop.width}}"
|
||||
crop="vm.currentCrop.coordinates"
|
||||
alias="{{vm.currentCrop.alias}}"
|
||||
force-update="{{vm.forceUpdateCrop}}"
|
||||
center="vm.mediaEntry.focalPoint"
|
||||
src="vm.imageSrc">
|
||||
<button class="btn btn-link" ng-click="vm.resetCrop()">
|
||||
<umb-icon icon="icon-wrong"></umb-icon> <localize key="imagecropper_reset">Reset this crop</localize>
|
||||
</button>
|
||||
</umb-image-crop>
|
||||
|
||||
</div>
|
||||
|
||||
<div ng-if="!vm.currentCrop" class="umb-cropper-imageholder">
|
||||
|
||||
<umb-image-gravity
|
||||
class="umb-media-entry-editor__imageholder"
|
||||
ng-if="vm.imageSrc"
|
||||
src="vm.imageSrc"
|
||||
center="vm.mediaEntry.focalPoint"
|
||||
disable-focal-point="!vm.model.enableFocalPointSetter"
|
||||
on-value-changed="vm.focalPointChanged(left, top)"
|
||||
on-image-loaded="vm.onImageLoaded(isCroppable, hasDimensions)">
|
||||
</umb-image-gravity>
|
||||
|
||||
<umb-file-icon
|
||||
class="umb-media-entry-editor__imageholder"
|
||||
ng-if="vm.loading === false && !vm.imageSrc"
|
||||
ng-class="{'trashed': vm.media.trashed}"
|
||||
extension="{{vm.media.extension}}"
|
||||
icon="{{vm.media.icon}}"
|
||||
size="m"
|
||||
text="{{vm.media.name}}">
|
||||
</umb-file-icon>
|
||||
|
||||
<div class="umb-media-entry-editor__imageholder-actions">
|
||||
<button class="btn btn-link" ng-click="vm.repickMedia()">
|
||||
<umb-icon icon="icon-wrong"></umb-icon> <localize key="mediaPicker_changeMedia">Replace media</localize>
|
||||
</button>
|
||||
<button class="btn btn-link" ng-click="vm.openMedia()">
|
||||
<umb-icon icon="icon-out"></umb-icon> <localize key="mediaPicker_openMedia">Open media</localize>
|
||||
</button>
|
||||
<button type="button" ng-show="vm.model.enableFocalPointSetter && (vm.mediaEntry.focalPoint.left !== 0.5 && vm.mediaEntry.focalPoint.top !== 0.5)" class="btn btn-link" ng-click="vm.focalPointChanged(0.5, 0.5)">
|
||||
<umb-icon icon="icon-axis-rotation"></umb-icon> <localize key="content_resetFocalPoint">Reset focal point</localize>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
ng-repeat="crop in vm.mediaEntry.crops track by crop.alias"
|
||||
class="btn-reset umb-outline"
|
||||
ng-class="{'--is-active':vm.currentCrop.alias === crop.alias, '--is-defined':!!vm.currentCrop.coordinates}"
|
||||
ng-click="vm.selectCrop(crop)"
|
||||
>
|
||||
<umb-image-thumbnail
|
||||
center="vm.mediaEntry.focalPoint"
|
||||
crop="crop.coordinates"
|
||||
src="vm.imageSrc"
|
||||
height="{{crop.height}}"
|
||||
width="{{crop.width}}"
|
||||
max-size="75"
|
||||
>
|
||||
</umb-image-thumbnail>
|
||||
<span class="__text">{{crop.label}}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="imagecropper umb-media-entry-editor__imagecropper">
|
||||
<div ng-if="vm.currentCrop" class="umb-cropper__container">
|
||||
<umb-image-crop
|
||||
height="{{vm.currentCrop.height}}"
|
||||
width="{{vm.currentCrop.width}}"
|
||||
crop="vm.currentCrop.coordinates"
|
||||
alias="{{vm.currentCrop.alias}}"
|
||||
force-update="{{vm.forceUpdateCrop}}"
|
||||
center="vm.mediaEntry.focalPoint"
|
||||
src="vm.imageSrc"
|
||||
>
|
||||
<button class="btn btn-link" ng-click="vm.resetCrop()">
|
||||
<umb-icon icon="icon-wrong"></umb-icon>
|
||||
<localize key="imagecropper_reset">Reset this crop</localize>
|
||||
</button>
|
||||
</umb-image-crop>
|
||||
</div>
|
||||
|
||||
<umb-editor-footer>
|
||||
<div ng-if="!vm.currentCrop" class="umb-cropper-imageholder">
|
||||
<umb-image-gravity
|
||||
class="umb-media-entry-editor__imageholder"
|
||||
ng-if="vm.imageSrc"
|
||||
src="vm.imageSrc"
|
||||
center="vm.mediaEntry.focalPoint"
|
||||
disable-focal-point="!vm.model.enableFocalPointSetter"
|
||||
on-value-changed="vm.focalPointChanged(left, top)"
|
||||
on-image-loaded="vm.onImageLoaded(isCroppable, hasDimensions)"
|
||||
>
|
||||
</umb-image-gravity>
|
||||
|
||||
<!-- Missing breadcrumbs -->
|
||||
<umb-media-preview
|
||||
ng-if="vm.loading === false && !vm.imageSrc"
|
||||
class="umb-media-entry-editor__previewholder"
|
||||
ng-class="{'trashed': vm.media.trashed}"
|
||||
extension="vm.fileExtension"
|
||||
source="vm.fileSrc"
|
||||
icon="vm.media.icon"
|
||||
name="vm.media.name"
|
||||
>
|
||||
</umb-media-preview>
|
||||
|
||||
<umb-editor-footer-content-right>
|
||||
<div class="umb-media-entry-editor__imageholder-actions">
|
||||
<button class="btn btn-link" ng-click="vm.repickMedia()">
|
||||
<umb-icon icon="icon-wrong"></umb-icon>
|
||||
<localize key="mediaPicker_changeMedia"
|
||||
>Replace media</localize
|
||||
>
|
||||
</button>
|
||||
<button class="btn btn-link" ng-click="vm.openMedia()">
|
||||
<umb-icon icon="icon-out"></umb-icon>
|
||||
<localize key="mediaPicker_openMedia">Open media</localize>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
ng-show="vm.model.enableFocalPointSetter && (vm.mediaEntry.focalPoint.left !== 0.5 && vm.mediaEntry.focalPoint.top !== 0.5)"
|
||||
class="btn btn-link"
|
||||
ng-click="vm.focalPointChanged(0.5, 0.5)"
|
||||
>
|
||||
<umb-icon icon="icon-axis-rotation"></umb-icon>
|
||||
<localize key="content_resetFocalPoint"
|
||||
>Reset focal point</localize
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<umb-button
|
||||
action="vm.close()"
|
||||
shortcut="esc"
|
||||
button-style="link"
|
||||
label="{{vm.closeLabel}}"
|
||||
type="button">
|
||||
</umb-button>
|
||||
<umb-editor-footer>
|
||||
<!-- Missing breadcrumbs -->
|
||||
|
||||
<umb-button
|
||||
action="vm.submitAndClose()"
|
||||
button-style="primary"
|
||||
state="vm.saveButtonState"
|
||||
label="{{vm.submitLabel}}"
|
||||
type="button">
|
||||
</umb-button>
|
||||
<umb-editor-footer-content-right>
|
||||
<umb-button
|
||||
action="vm.close()"
|
||||
shortcut="esc"
|
||||
button-style="link"
|
||||
label="{{vm.closeLabel}}"
|
||||
type="button"
|
||||
>
|
||||
</umb-button>
|
||||
|
||||
</umb-editor-footer-content-right>
|
||||
|
||||
</umb-editor-footer>
|
||||
</umb-editor-view>
|
||||
</ng-form>
|
||||
<umb-button
|
||||
action="vm.submitAndClose()"
|
||||
button-style="primary"
|
||||
state="vm.saveButtonState"
|
||||
label="{{vm.submitLabel}}"
|
||||
type="button"
|
||||
>
|
||||
</umb-button>
|
||||
</umb-editor-footer-content-right>
|
||||
</umb-editor-footer>
|
||||
</umb-editor-view>
|
||||
</ng-form>
|
||||
</div>
|
||||
|
||||
@@ -110,11 +110,23 @@
|
||||
}
|
||||
|
||||
.umb-media-entry-editor__imageholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
position: relative;
|
||||
height: calc(100% - 50px);
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
.umb-media-entry-editor__previewholder {
|
||||
|
||||
position: relative;
|
||||
height: calc(100% - 50px);
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.umb-media-entry-editor__imageholder-actions {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<div class="umb-audio-preview" ng-controller="umbAudioPreviewController as controller">
|
||||
<audio ng-if="vm.clientSide" name="{{vm.name}}" controls>
|
||||
<source ng-init="previewUrl = controller.getClientSideUrl(vm.clientSideData)" ng-src="{{previewUrl}}">
|
||||
</audio>
|
||||
<audio ng-if="!vm.clientSide" name="{{vm.name}}" controls>
|
||||
<source ng-src="{{vm.source}}">
|
||||
</audio>
|
||||
</div>
|
||||
@@ -0,0 +1,18 @@
|
||||
.umb-audio-preview {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
audio {
|
||||
max-width: 100%;
|
||||
}
|
||||
audio::-webkit-media-controls-panel {
|
||||
background-color: white;
|
||||
}
|
||||
audio::-webkit-media-controls {
|
||||
padding: 6px;
|
||||
}
|
||||
audio::-webkit-media-controls-enclosure {
|
||||
&:extend(.shadow-depth-1);
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
angular.module("umbraco")
|
||||
.controller("umbAudioPreviewController",
|
||||
function () {
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.getClientSideUrl = function(source) {
|
||||
return URL.createObjectURL(source);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
<div class="umb-file-preview">
|
||||
<a
|
||||
class="umb-file-preview--file"
|
||||
ng-show="!vm.clientSide"
|
||||
href="#"
|
||||
ng-href="{{vm.source}}"
|
||||
alt="{{vm.name}}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<umb-file-icon extension="{{vm.extension}}" size="m"> </umb-file-icon>
|
||||
<div class="mt2">{{vm.name}}</div>
|
||||
</a>
|
||||
<div class="umb-file-preview--file" ng-show="vm.clientSide">
|
||||
<umb-file-icon extension="{{vm.extension}}" size="m"> </umb-file-icon>
|
||||
<div class="mt2">{{vm.name}}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
.umb-file-preview {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.umb-file-preview--file {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
max-width: 320px;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="umb-image-preview" ng-controller="umbImagePreviewController as controller">
|
||||
<img class="umb-image-preview--image" ng-if="vm.clientSide" ng-init="previewUrl = controller.getClientSideUrl(vm.clientSideData)" ng-src="{{previewUrl}}" alt="{{vm.name}}" />
|
||||
<a ng-if="!vm.clientSide" href="#" ng-href="{{vm.source}}" target="_blank" rel="noopener">
|
||||
<img class="umb-image-preview--image" ng-init="previewUrl = controller.getThumbnail(vm.source)" ng-src="{{previewUrl}}" alt="{{vm.name}}" />
|
||||
</a>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
.umb-image-preview {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
|
||||
|
||||
|
||||
angular.module("umbraco")
|
||||
.controller("umbImagePreviewController",
|
||||
function (mediaHelper) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.getThumbnail = function(source) {
|
||||
return mediaHelper.getThumbnailFromPath(source) || source;
|
||||
}
|
||||
vm.getClientSideUrl = function(sourceData) {
|
||||
return URL.createObjectURL(sourceData);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
umb-media-preview {
|
||||
position: relative;
|
||||
}
|
||||
.umb-media-preview {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
min-height: 240px;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
angular
|
||||
.module("umbraco")
|
||||
.component("umbMediaPreview", {
|
||||
template: "<div ng-include='vm.previewView' class='umb-media-preview'></div><umb-load-indicator ng-if='vm.loading'></umb-load-indicator>",
|
||||
controller: UmbMediaPreviewController,
|
||||
controllerAs: "vm",
|
||||
bindings: {
|
||||
extension: "<",
|
||||
source: "<",
|
||||
name: "<",
|
||||
clientSide: "<?",
|
||||
clientSideData: "<?"
|
||||
}
|
||||
});
|
||||
|
||||
function UmbMediaPreviewController($scope, mediaPreview) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.loading = false;
|
||||
|
||||
vm.$onInit = function() {
|
||||
vm.previewView = mediaPreview.getMediaPreview(vm.extension);
|
||||
}
|
||||
|
||||
$scope.$on("mediaPreviewLoadingStart", () => {
|
||||
vm.loading = true;
|
||||
})
|
||||
$scope.$on("mediaPreviewLoadingComplete", () => {
|
||||
vm.loading = false;
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,8 @@
|
||||
<div class="umb-video-preview" ng-controller="umbVideoPreviewController as controller">
|
||||
<video ng-if="vm.clientSide" name="{{vm.name}}" controls>
|
||||
<source ng-init="previewUrl = controller.getClientSideUrl(vm.clientSideData)" ng-src="{{previewUrl}}">
|
||||
</video>
|
||||
<video ng-if="!vm.clientSide" name="{{vm.name}}" controls>
|
||||
<source ng-src="{{vm.source}}">
|
||||
</video>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
.umb-video-preview {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
video {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
|
||||
|
||||
angular.module("umbraco")
|
||||
.controller("umbVideoPreviewController",
|
||||
function () {
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.getClientSideUrl = function(source) {
|
||||
return URL.createObjectURL(source);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -30,7 +30,7 @@
|
||||
<umb-file-icon ng-if="vm.loading === false && !vm.thumbnail"
|
||||
ng-class="{'trashed': vm.media.trashed}"
|
||||
title="{{vm.media.name}}"
|
||||
extension="{{vm.media.extension}}"
|
||||
extension="{{vm.fileExtension}}"
|
||||
icon="{{vm.media.icon}}"
|
||||
size="s"
|
||||
text="{{vm.media.name}}">
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
vm.media = mediaEntity;
|
||||
checkErrorState();
|
||||
vm.thumbnail = mediaHelper.resolveFileFromEntity(mediaEntity, true);
|
||||
vm.fileExtension = mediaHelper.getFileExtension(vm.media.metaData.MediaPath);
|
||||
|
||||
vm.loading = false;
|
||||
}, function () {
|
||||
|
||||
@@ -1,58 +1,68 @@
|
||||
<div class="umb-property-file-upload">
|
||||
<ng-form name="vm.fileUploadForm" ng-class="{ 'drag-over': vm.dragover }">
|
||||
<input
|
||||
type="hidden"
|
||||
ng-model="mandatoryValidator"
|
||||
ng-required="vm.required && !vm.files.length"
|
||||
/>
|
||||
|
||||
<ng-form name="vm.fileUploadForm" ng-class="{ 'drag-over': vm.dragover }">
|
||||
<input type="hidden" ng-model="mandatoryValidator" ng-required="vm.required && !vm.files.length" />
|
||||
<div
|
||||
class="fileinput-button umb-upload-button-big"
|
||||
ng-hide="vm.files.length > 0"
|
||||
>
|
||||
<umb-icon icon="icon-page-up" class="icon"></umb-icon>
|
||||
<p><localize key="media_clickToUpload">Click to upload</localize></p>
|
||||
<umb-single-file-upload
|
||||
accept-file-ext="vm.acceptFileExt"
|
||||
></umb-single-file-upload>
|
||||
</div>
|
||||
|
||||
<div class="fileinput-button umb-upload-button-big" ng-hide="vm.files.length > 0">
|
||||
<umb-icon icon="icon-page-up" class="icon"></umb-icon>
|
||||
<p><localize key="media_clickToUpload">Click to upload</localize></p>
|
||||
<umb-single-file-upload accept-file-ext="vm.acceptFileExt"></umb-single-file-upload>
|
||||
<div ng-if="vm.files.length > 0">
|
||||
<div ng-if="!vm.hideSelection">
|
||||
<div class="umb-fileupload clearfix" ng-repeat="file in vm.files">
|
||||
<umb-media-preview
|
||||
extension="file.extension"
|
||||
source="file.fileSrc"
|
||||
name="file.fileName"
|
||||
client-side="file.isClientSide"
|
||||
client-side-data="file.fileData"
|
||||
></umb-media-preview>
|
||||
</div>
|
||||
|
||||
<div ng-if="vm.files.length > 0">
|
||||
<div ng-if="!vm.hideSelection">
|
||||
|
||||
<div class="umb-fileupload clearfix" ng-repeat="file in vm.files">
|
||||
|
||||
<div ng-if="file.isImage || file.extension === 'svg'">
|
||||
<div class="gravity-container">
|
||||
<div class="viewport">
|
||||
<img ng-if="file.isClientSide" ng-src="{{file.fileSrc}}" style="max-width: 100%; max-height: 100%" alt="{{file.fileName}}" />
|
||||
<a ng-if="!file.isClientSide" href="#" ng-href="{{file.fileSrc}}" target="_blank" rel="noopener">
|
||||
<img ng-src="{{file.fileSrc}}" style="max-width: 100%; max-height: 100%" alt="{{file.fileName}}" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="!file.isImage && file.extension !== 'svg'">
|
||||
<a class="span6 thumbnail tc" ng-show="!file.isClientSide" href="#" ng-href="{{file.fileName}}" target="_blank" rel="noopener">
|
||||
<umb-file-icon
|
||||
extension="{{file.extension}}"
|
||||
size="m">
|
||||
</umb-file-icon>
|
||||
<div class="mt2">{{file.fileName}}</div>
|
||||
</a>
|
||||
<div class="span6 thumbnail tc" ng-show="file.isClientSide">
|
||||
<umb-file-icon
|
||||
extension="{{file.extension}}"
|
||||
size="m">
|
||||
</umb-file-icon>
|
||||
<div class="mt2">{{file.fileName}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-link btn-crop-delete" ng-click="vm.clear()"><umb-icon icon="icon-delete" class="red"></umb-icon> <localize key="content_uploadClear">Remove file</localize></button>
|
||||
<button type="button" class="sr-only" ng-if="file.isImage" ng-click="vm.clear()"><localize key="content_uploadClearImageContext">Click here to remove the image from the media item</localize></button>
|
||||
<button type="button" class="sr-only" ng-if="!file.isImage" ng-click="vm.clear()"><localize key="content_uploadClearFileContext">Click here to remove the file from the media item</localize></button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div ng-if="vm.hideSelection">
|
||||
<div ng-transclude></div>
|
||||
</div>
|
||||
<div class="umb-property-file-upload--actions">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-link"
|
||||
aria-hidden="true"
|
||||
ng-click="vm.clear()"
|
||||
>
|
||||
<i class="icon-trash"></i>
|
||||
<localize key="content_uploadClear">Remove file</localize>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="sr-only"
|
||||
ng-if="file.isImage"
|
||||
ng-click="vm.clear()"
|
||||
>
|
||||
<localize key="content_uploadClearImageContext"
|
||||
>Click here to remove the image from the media item</localize
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="sr-only"
|
||||
ng-if="!file.isImage"
|
||||
ng-click="vm.clear()"
|
||||
>
|
||||
<localize key="content_uploadClearFileContext"
|
||||
>Click here to remove the file from the media item</localize
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</ng-form>
|
||||
</div>
|
||||
<div ng-if="vm.hideSelection">
|
||||
<div ng-transclude></div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user