View Javadoc
1   /**
2    * This file Copyright (c) 2013-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.dam.app.ui.field.upload;
35  
36  import info.magnolia.dam.app.ui.field.configuration.PreviewComponentProvider;
37  import info.magnolia.dam.app.ui.field.configuration.ThumbnailComponentProvider;
38  import info.magnolia.dam.app.ui.field.definition.DamUploadFieldDefinition;
39  import info.magnolia.i18nsystem.SimpleTranslator;
40  import info.magnolia.objectfactory.ComponentProvider;
41  import info.magnolia.ui.api.context.UiContext;
42  import info.magnolia.ui.api.overlay.OverlayCloser;
43  import info.magnolia.ui.form.field.upload.basic.BasicUploadField;
44  import info.magnolia.ui.imageprovider.ImageProvider;
45  import info.magnolia.ui.mediaeditor.MediaEditorPresenter;
46  import info.magnolia.ui.mediaeditor.MediaEditorPresenterFactory;
47  import info.magnolia.ui.mediaeditor.event.MediaEditorCompletedEvent;
48  import info.magnolia.ui.vaadin.overlay.MessageStyleTypeEnum;
49  
50  import java.io.FileInputStream;
51  import java.io.FileNotFoundException;
52  import java.io.InputStream;
53  
54  import org.apache.commons.io.FileUtils;
55  import org.apache.commons.lang3.StringUtils;
56  import org.slf4j.Logger;
57  import org.slf4j.LoggerFactory;
58  
59  import com.google.common.net.MediaType;
60  import com.vaadin.server.Resource;
61  import com.vaadin.server.StreamResource;
62  import com.vaadin.server.StreamResource.StreamSource;
63  import com.vaadin.ui.Alignment;
64  import com.vaadin.ui.Button;
65  import com.vaadin.ui.Button.ClickEvent;
66  import com.vaadin.ui.Component;
67  import com.vaadin.ui.CssLayout;
68  import com.vaadin.ui.Layout;
69  import com.vaadin.ui.NativeButton;
70  import com.vaadin.v7.ui.HorizontalLayout;
71  import com.vaadin.v7.ui.Label;
72  
73  /**
74   * Dam implementation of {@link BasicUploadField}.<br>
75   * Override default Layout components of the CompletedLayout:
76   * <ul>
77   * <li>createCompletedActionLayout (Contains the available actions based on the MediaType)</li>
78   * <li>createThumbnailComponent() (Display a preview Image or an Icon)</li>
79   * <li>getFileDetailSize() (Add file size/duration informations )</li>
80   * </ul>
81   * An Uploaded file is mapped to a mediaType (based on the MimeType). <br>
82   * The mediaType define upload configuration like:
83   * <ul>
84   * <li>{@link info.magnolia.dam.app.ui.field.configuration.ThumbnailComponentProvider} to use in order to display the preview Image</li>
85   * <li>should this media have an edit button and in case, which mediaEtior to call</li>
86   * <li>should this media have an open preview action and in case, which {@link info.magnolia.dam.app.ui.field.configuration.PreviewComponentProvider} to use</li>
87   * </ul>
88   * <b>Check {@link info.magnolia.dam.app.ui.field.configuration.EditAssetAppConfiguration} for additional informations regarding MediaEditor configuration</b>
89   *
90   * @param <D> {@link AssetUploadReceiver} implemented class.
91   */
92  public class DamUploadField<D extends AssetUploadReceiver> extends BasicUploadField<D> {
93      private static final long serialVersionUID = 1L;
94      private static final Logger log = LoggerFactory.getLogger(DamUploadField.class);
95  
96      private final SimpleTranslator i18n;
97  
98      // Used to open the MediaEditor
99      private MediaEditorPresenterFactory mediaEditorFactory;
100     private ComponentProvider componentProvider;
101     private String lightboxCaption;
102     private String editFileCaption;
103 
104     public DamUploadField(ImageProvider imageProvider, UiContext uiContext, MediaEditorPresenterFactory mediaEditorFactory, ComponentProvider componentProvider, DamUploadFieldDefinition definition, SimpleTranslator i18n) {
105         super(imageProvider, uiContext, definition, i18n);
106         // Propagate definition.
107         populateFromDefinition(definition);
108 
109         this.mediaEditorFactory = mediaEditorFactory;
110         this.componentProvider = componentProvider;
111         this.i18n = i18n;
112     }
113 
114     /**
115      * Add File Info.
116      */
117     @Override
118     protected Component getFileDetailSize() {
119         Label label = new Label();
120         label.setCaption(i18n.translate(fileDetailSizeCaption));
121         StringBuilder sb = new StringBuilder();
122         if (getValue().isImage()) {
123             sb.append(getValue().getWidth() + " x " + getValue().getHeight() + ", ");
124         }
125         sb.append(FileUtils.byteCountToDisplaySize(getValue().getFileSize()));
126         if (getValue().getDuration() > 0) {
127             sb.append("(x:x min)");
128         }
129 
130         label.setValue(sb.toString());
131         return label;
132     }
133 
134     /**
135      * Override of CompletedLayout section.<br>
136      * Used the configuration in order to define the action to perform and Thumbnail component to display.
137      */
138     @Override
139     protected Layout createCompletedActionLayout() {
140         if (isReadOnly()) {
141             return new HorizontalLayout();
142         }
143         // Action Button (Upload new or delete). Default is always Upload
144         HorizontalLayout actionLayout = new HorizontalLayout();
145         actionLayout.setSizeUndefined();
146         actionLayout.addStyleName("buttons");
147         actionLayout.setSpacing(true);
148         // Add an Edit Button if the MediaFile has configured it.
149         if (getValue() != null && !getValue().isEmpty() && getValue().getEditAssetAppConfiguration().hasEditConfig()) {
150             Button edit = createEditButton();
151             actionLayout.addComponent(edit);
152         }
153         // Add Upload Button
154         getUpload().setButtonCaption(getCaption(selectAnotherCaption, null));
155         actionLayout.addComponent(getUpload());
156         // Add a Remove Button if a file is present.
157         if (getValue() != null && !getValue().isEmpty()) {
158             Button delete = createDeleteButton();
159             actionLayout.addComponent(delete);
160             actionLayout.setComponentAlignment(delete, Alignment.MIDDLE_LEFT);
161         }
162         return actionLayout;
163     }
164 
165     /**
166      * Create Edit button.<br>
167      * Delegate the call to the media Editor to openMediaEditor {@link MediaEditorPresenter#start(InputStream)}.
168      */
169     private Button createEditButton() {
170         Button editButton = new Button(getCaption(editFileCaption, null), createEditButtonListener());
171         editButton.setDisableOnClick(true);
172         editButton.addStyleName("edit");
173         return editButton;
174     }
175 
176     private Button.ClickListener createEditButtonListener() {
177         return new Button.ClickListener() {
178             private static final long serialVersionUID = 1L;
179 
180             @Override
181             public void buttonClick(ClickEvent event) {
182                 // Launch MediaEditor for this item.
183                 try {
184                     openMediaEditor();
185                 } catch (FileNotFoundException fnfe) {
186                     log.warn("could not open MediaEditor");
187                     uiContext.openAlert(MessageStyleTypeEnum.ERROR, "ERROR", i18n.translate("dam.assets.uploadField.alert.couldNotOpenMediaEditor")
188                                     + " "
189                                     + getValue().getEditAssetAppConfiguration().getEditConfig().getMediaEditorId(),
190                             "ok", null);
191                 } finally {
192                     event.getButton().setEnabled(true);
193                 }
194             }
195         };
196     }
197 
198     /**
199      * Open a mediaEditor populated with the binary of this file.
200      * Media Editor Id is configured in the related MediaFile definition.
201      *
202      * @throws FileNotFoundException
203      */
204     private void openMediaEditor() throws FileNotFoundException {
205         // Check
206         String presenterById;
207         if (getValue().getEditAssetAppConfiguration() == null || !getValue().getEditAssetAppConfiguration().hasEditConfig()) {
208             log.warn("No Media Editor defined for the following mimeType {} ", getValue().getMimeType());
209             return;
210         } else {
211             presenterById = "ui-mediaeditor:" + getValue().getEditAssetAppConfiguration().getEditConfig().getMediaEditorId();
212             log.debug("Will open the following mediaEditor Presenter {}", presenterById);
213         }
214 
215         final NativeButton mediaEditorPlaceholder = new NativeButton(i18n.translate("dam.assets.uploadField.button.mediaEditorPlaceholder"));
216         mediaEditorPlaceholder.addStyleName("btn-form btn-form-commit");
217 
218         MediaEditorPresenter mediaEditorPresenter = mediaEditorFactory.getPresenterById(presenterById);
219         final InputStream inputStream = new FileInputStream(getValue().getFile());
220         final OverlayCloser overlayCloser = uiContext.openOverlay(mediaEditorPresenter.start(inputStream));
221         mediaEditorPresenter.addCompletionHandler(new MediaEditorCompletedEvent.Handler() {
222             @Override
223             public void onSubmit(MediaEditorCompletedEvent event) {
224                 getValue().updateContent(event.getStream());
225                 // Update the display to show changes to media.
226                 updateDisplay();
227                 overlayCloser.close();
228                 closeInputStream(inputStream);
229                 getPropertyDataSource().setValue(getValue());
230             }
231 
232             @Override
233             public void onCancel(MediaEditorCompletedEvent event) {
234                 overlayCloser.close();
235                 closeInputStream(inputStream);
236             }
237 
238             private void closeInputStream(InputStream inputStream) {
239                 try {
240                     if (inputStream != null) {
241                         inputStream.close();
242                     }
243                 } catch (Exception e) {
244                     log.warn("Could not close the InputStream ", e);
245                 }
246             }
247         });
248     }
249 
250     /**
251      * Create a preview image with a button if configured:
252      * <ul>
253      * <li>in lower-left to open the media in a lightbox</li>
254      * <li>a button in the lower-right to open the MediaEditor.</li>
255      * </ul>
256      */
257     @Override
258     protected Component createThumbnailComponent() {
259 
260         CssLayout previewLayout = new CssLayout();
261         previewLayout.addStyleName("file-preview-area");
262         previewLayout.setWidth("150px");
263         previewLayout.setHeight("150px");
264         // Create the preview Component (Icon / Image thumbnail/...)
265         Component image = createPreviewComponent();
266         if (image != null) {
267             image.addStyleName("preview-image");
268             previewLayout.addComponent(image);
269         }
270 
271         // Add buttons to the preview layout (Add Edit) IF CONFIGURED
272         if (getValue().getEditAssetAppConfiguration().hasEditConfig() && !isReadOnly()) {
273             Button editButton = new Button();
274             editButton.addStyleName("edit-button");
275             editButton.setHtmlContentAllowed(true);
276             editButton.setCaption("<span class=\"" + getValue().getEditAssetAppConfiguration().getEditConfig().getIconStyleName() + "\"></span>");
277             editButton.setDescription(getCaption(editFileCaption, null));
278             previewLayout.addComponent(editButton);
279 
280             // Button handlers
281             editButton.setDisableOnClick(true);
282             editButton.addClickListener(createEditButtonListener());
283         }
284 
285         // Add buttons to the preview layout (Add LightBox) IF CONFIGURED
286         if (getValue().getEditAssetAppConfiguration().hasPreviewConfig()) {
287             Button lightboxButton = new Button();
288             lightboxButton.addStyleName("lightbox-button");
289             lightboxButton.setHtmlContentAllowed(true);
290             lightboxButton.setCaption("<span class=\"" + getValue().getEditAssetAppConfiguration().getPreviewConfig().getIconStyleName() + "\"></span>");
291             lightboxButton.setDescription(i18n.translate(lightboxCaption));
292             previewLayout.addComponent(lightboxButton);
293 
294             lightboxButton.addClickListener(new Button.ClickListener() {
295                 private static final long serialVersionUID = 1L;
296 
297                 @Override
298                 public void buttonClick(ClickEvent event) {
299                     // Launch Lightbox component
300                     Class<? extends PreviewComponentProvider> previewActionClass = getValue().getEditAssetAppConfiguration().getPreviewConfig().getPreviewComponentProviderClass();
301                     if (previewActionClass != null) {
302                         PreviewComponentProvider implementation = componentProvider.newInstance(previewActionClass);
303                         implementation.open(getResource());
304                     } else {
305                         log.warn("No Preview Defined is defined ");
306                     }
307                 }
308             });
309         }
310         return previewLayout;
311     }
312 
313     /**
314      * Override of CompletedLayout section.<br>
315      * Change the Thumbnail section of the Completed Layout.<br>
316      * Thumbnail is only available for Images and Video.
317      */
318     private Component createPreviewComponent() {
319         Class<? extends ThumbnailComponentProvider> thumbnailProviderClass = getValue().getEditAssetAppConfiguration().getThumbnailComponentProviderClass();
320         if (thumbnailProviderClass != null) {
321             ThumbnailComponentProvider implementation = componentProvider.newInstance(thumbnailProviderClass);
322             return implementation.createThumbnailComponent(null, getValue().getFile(), getValue().getMimeType());
323         } else {
324             log.warn("No ThumbnailComponentProvider is defined ");
325             return null;
326         }
327     }
328 
329     /**
330      * Create a resource from the File.
331      */
332     private Resource getResource() {
333         final StreamSource source = new StreamResource.StreamSource() {
334             private static final long serialVersionUID = 1L;
335 
336             @Override
337             public InputStream getStream() {
338                 try {
339                     return new FileInputStream(getValue().getFile());
340                 } catch (FileNotFoundException fnfe) {
341                     log.warn("could not found File", fnfe);
342                     return null;
343                 }
344             }
345         };
346 
347         return new StreamResource(source, "") {
348             @Override
349             public String getMIMEType() {
350                 return getValue().getMimeType();
351             }
352         };
353     }
354 
355     protected void populateFromDefinition(DamUploadFieldDefinition definition) {
356         super.populateFromDefinition(definition);
357         this.setLightboxCaption(definition.getLightboxCaption());
358         this.setEditFileCaption(definition.getEditFileCaption());
359     }
360 
361     /**
362      * Caption section.
363      */
364     @Override
365     protected void setCaptionExtension(String mimeType) {
366         if (StringUtils.isNotBlank(mimeType)) {
367             captionExtension = MediaType.parse(mimeType).type();
368         } else if (StringUtils.isNotBlank(getValue().getMimeType())) {
369             captionExtension = MediaType.parse(getValue().getMimeType()).type();
370         }
371     }
372 
373     public void setLightboxCaption(String lightboxCaption) {
374         this.lightboxCaption = lightboxCaption;
375     }
376 
377     public void setEditFileCaption(String editFileCaption) {
378         this.editFileCaption = editFileCaption;
379     }
380 }