View Javadoc
1   /**
2    * This file Copyright (c) 2013-2015 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.lang.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.shared.ui.label.ContentMode;
64  import com.vaadin.ui.Alignment;
65  import com.vaadin.ui.Button;
66  import com.vaadin.ui.Button.ClickEvent;
67  import com.vaadin.ui.Component;
68  import com.vaadin.ui.CssLayout;
69  import com.vaadin.ui.HorizontalLayout;
70  import com.vaadin.ui.Label;
71  import com.vaadin.ui.Layout;
72  import com.vaadin.ui.NativeButton;
73  
74  /**
75   * Dam implementation of {@link BasicUploadField}.<br>
76   * Override default Layout components of the CompletedLayout:
77   * <ul>
78   * <li>createCompletedActionLayout (Contains the available actions based on the MediaType)</li>
79   * <li>createThumbnailComponent() (Display a preview Image or an Icon)</li>
80   * <li>getFileDetailSize() (Add file size/duration informations )</li>
81   * </ul>
82   * An Uploaded file is mapped to a mediaType (based on the MimeType). <br>
83   * The mediaType define upload configuration like:
84   * <ul>
85   * <li>{@link info.magnolia.dam.app.ui.field.configuration.ThumbnailComponentProvider} to use in order to display the preview Image</li>
86   * <li>should this media have an edit button and in case, which mediaEtior to call</li>
87   * <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>
88   * </ul>
89   * <b>Check {@link info.magnolia.dam.app.ui.field.configuration.EditAssetAppConfiguration} for additional informations regarding MediaEditor configuration</b>
90   * 
91   * @param <D> {@link AssetUploadReceiver} implemented class.
92   */
93  public class DamUploadField<D extends AssetUploadReceiver> extends BasicUploadField<D> {
94      private static final long serialVersionUID = 1L;
95      private static final Logger log = LoggerFactory.getLogger(DamUploadField.class);
96  
97      // Used to open the MediaEditor
98      private MediaEditorPresenterFactory mediaEditorFactory;
99      private ComponentProvider componentProvider;
100 
101     private final SimpleTranslator i18n;
102 
103     public DamUploadField(ImageProvider imageProvider, UiContext uiContext, MediaEditorPresenterFactory mediaEditorFactory, ComponentProvider componentProvider, DamUploadFieldDefinition definition, SimpleTranslator i18n) {
104         super(imageProvider, uiContext, definition, i18n);
105         // Propagate definition.
106         populateFromDefinition(definition);
107 
108         this.mediaEditorFactory = mediaEditorFactory;
109         this.componentProvider = componentProvider;
110         this.i18n = i18n;
111     }
112 
113 
114     /**
115      * Add File Info.
116      */
117     @Override
118     protected Component getFileDetailSize() {
119         Label label = new Label("", ContentMode.HTML);
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     /**
136      * Override of CompletedLayout section.<br>
137      * Used the configuration in order to define the action to perform and Thumbnail component to display.
138      */
139     @Override
140     protected Layout createCompletedActionLayout() {
141         if (isReadOnly()) {
142             return new HorizontalLayout();
143         }
144         // Action Button (Upload new or delete). Default is always Upload
145         HorizontalLayout actionLayout = new HorizontalLayout();
146         actionLayout.setSizeUndefined();
147         actionLayout.addStyleName("buttons");
148         actionLayout.setSpacing(true);
149         // Add an Edit Button if the MediaFile has configured it.
150         if (getValue() != null && !getValue().isEmpty() && getValue().getEditAssetAppConfiguration().hasEditConfig()) {
151             Button edit = createEditButton();
152             actionLayout.addComponent(edit);
153         }
154         // Add Upload Button
155         getUpload().setButtonCaption(getCaption(selectAnotherCaption, null));
156         actionLayout.addComponent(getUpload());
157         // Add a Remove Button if a file is present.
158         if (getValue() != null && !getValue().isEmpty()) {
159             Button delete = createDeleteButton();
160             actionLayout.addComponent(delete);
161             actionLayout.setComponentAlignment(delete, Alignment.MIDDLE_LEFT);
162         }
163         return actionLayout;
164     }
165 
166     /**
167      * Create Edit button.<br>
168      * Delegate the call to the media Editor to openMediaEditor {@link MediaEditorPresenter#start(InputStream)}.
169      */
170     private Button createEditButton() {
171         Button editButton = new Button(getCaption(editFileCaption, null));
172         editButton.addClickListener(new Button.ClickListener() {
173             private static final long serialVersionUID = 1L;
174 
175             @Override
176             public void buttonClick(ClickEvent event) {
177                 // Launch MediaEditor for this item.
178                 try {
179                     openMediaEditor();
180                 } catch (FileNotFoundException fnfe) {
181                     log.warn("could not open MediaEditor");
182                     uiContext.openAlert(MessageStyleTypeEnum.ERROR, "ERROR", i18n.translate("dam.assets.uploadField.alert.couldNotOpenMediaEditor")
183                             + " "
184                             + getValue().getEditAssetAppConfiguration().getEditConfig().getMediaEditorId(),
185                             "ok", null);
186                 }
187             }
188         });
189         editButton.addStyleName("edit");
190         return editButton;
191     }
192 
193     /**
194      * Open a mediaEditor populated with the binary of this file.
195      * Media Editor Id is configured in the related MediaFile definition.
196      *
197      * @throws FileNotFoundException
198      */
199     private void openMediaEditor() throws FileNotFoundException {
200         // Check
201         String presenterById = null;
202         if (getValue().getEditAssetAppConfiguration() == null || !getValue().getEditAssetAppConfiguration().hasEditConfig()) {
203             log.warn("No Media Editor defined for the following mimeType {} ", getValue().getMimeType());
204             return;
205         } else {
206             presenterById = "ui-mediaeditor:" + getValue().getEditAssetAppConfiguration().getEditConfig().getMediaEditorId();
207             log.debug("Will open the following mediaEditor Presenter {}", presenterById);
208         }
209 
210         final NativeButton mediaEditorPlaceholder = new NativeButton(i18n.translate("dam.assets.uploadField.button.mediaEditorPlaceholder"));
211         mediaEditorPlaceholder.addStyleName("btn-form btn-form-commit");
212 
213         MediaEditorPresenter mediaEditorPresenter = mediaEditorFactory.getPresenterById(presenterById);
214         final InputStream inputStream = new FileInputStream(getValue().getFile());
215         final OverlayCloser overlayCloser = uiContext.openOverlay(mediaEditorPresenter.start(inputStream));
216         mediaEditorPresenter.addCompletionHandler(new MediaEditorCompletedEvent.Handler() {
217             @Override
218             public void onSubmit(MediaEditorCompletedEvent event) {
219                 final InputStream is = event.getStream();
220                 getValue().updateContent(event.getStream());
221                 // Update the display to show changes to media.
222                 updateDisplay();
223                 overlayCloser.close();
224                 closeInputStream(inputStream);
225                 getPropertyDataSource().setValue(getValue());
226             }
227 
228             @Override
229             public void onCancel(MediaEditorCompletedEvent event) {
230                 overlayCloser.close();
231                 closeInputStream(inputStream);
232             }
233 
234             private void closeInputStream(InputStream inputStream) {
235                 try {
236                     if (inputStream != null) {
237                         inputStream.close();
238                     }
239                 } catch (Exception e) {
240                     log.warn("Could not close the InputStream ", e);
241                 }
242             }
243         });
244     }
245 
246     /**
247      * Create a preview image with a button if configured:
248      * <ul>
249      * <li>in lower-left to open the media in a lightbox</li>
250      * <li>a button in the lower-right to open the MediaEditor.</li>
251      * </ul>
252      */
253     @Override
254     protected Component createThumbnailComponent() {
255 
256         CssLayout previewLayout = new CssLayout();
257         previewLayout.addStyleName("file-preview-area");
258         previewLayout.setWidth("150px");
259         previewLayout.setHeight("150px");
260         // Create the preview Component (Icon / Image thumbnail/...)
261         Component image = createPreviewComponent();
262         if (image != null) {
263             image.addStyleName("preview-image");
264             previewLayout.addComponent(image);
265         }
266 
267         // Add buttons to the preview layout (Add Edit) IF CONFIGURED
268         if (getValue().getEditAssetAppConfiguration().hasEditConfig() && !isReadOnly()) {
269             Button editButton = new Button();
270             editButton.addStyleName("edit-button");
271             editButton.setHtmlContentAllowed(true);
272             editButton.setCaption("<span class=\"" + getValue().getEditAssetAppConfiguration().getEditConfig().getIconStyleName() + "\"></span>");
273             editButton.setDescription(getCaption(editFileCaption, null));
274             previewLayout.addComponent(editButton);
275 
276             // Button handlers
277             editButton.addClickListener(new Button.ClickListener() {
278                 private static final long serialVersionUID = 1L;
279 
280                 @Override
281                 public void buttonClick(ClickEvent event) {
282                     // Launch MediaEditor for this item.
283                     try {
284                         openMediaEditor();
285                     } catch (FileNotFoundException fnfe) {
286                         log.warn("could not open MediaEditor");
287                         uiContext.openAlert(MessageStyleTypeEnum.ERROR, "ERROR", i18n.translate("dam.assets.uploadField.alert.couldNotOpenMediaEditor")
288                                 + " "
289                                 + getValue().getEditAssetAppConfiguration().getEditConfig().getMediaEditorId(),
290                                 "ok", null);
291                     }
292                 }
293             });
294         }
295 
296         // Add buttons to the preview layout (Add LightBox) IF CONFIGURED
297         if (getValue().getEditAssetAppConfiguration().hasPreviewConfig()) {
298             Button lightboxButton = new Button();
299             lightboxButton.addStyleName("lightbox-button");
300             lightboxButton.setHtmlContentAllowed(true);
301             lightboxButton.setCaption("<span class=\"" + getValue().getEditAssetAppConfiguration().getPreviewConfig().getIconStyleName() + "\"></span>");
302             lightboxButton.setDescription(i18n.translate(lightboxCaption));
303             previewLayout.addComponent(lightboxButton);
304 
305             lightboxButton.addClickListener(new Button.ClickListener() {
306                 private static final long serialVersionUID = 1L;
307 
308                 @Override
309                 public void buttonClick(ClickEvent event) {
310                     // Launch Lightbox component
311                     Class<? extends PreviewComponentProvider> previewActionClass = getValue().getEditAssetAppConfiguration().getPreviewConfig().getPreviewComponentProviderClass();
312                     if (previewActionClass != null) {
313                         PreviewComponentProvider implementation = componentProvider.newInstance(previewActionClass);
314                         implementation.open(getResource());
315                     } else {
316                         log.warn("No Preview Defined is defined ");
317                     }
318                 }
319             });
320         }
321         return previewLayout;
322     }
323 
324     /**
325      * Override of CompletedLayout section.<br>
326      * Change the Thumbnail section of the Completed Layout.<br>
327      * Thumbnail is only available for Images and Video.
328      */
329     private Component createPreviewComponent() {
330         Class<? extends ThumbnailComponentProvider> thumbnailProviderClass = getValue().getEditAssetAppConfiguration().getThumbnailComponentProviderClass();
331         if (thumbnailProviderClass != null) {
332             ThumbnailComponentProvider implementation = componentProvider.newInstance(thumbnailProviderClass);
333             return implementation.createThumbnailComponent(null, getValue().getFile(), getValue().getMimeType());
334         } else {
335             log.warn("No ThumbnailComponentProvider is defined ");
336             return null;
337         }
338     }
339 
340     /**
341      * Create a resource from the File.
342      */
343     private Resource getResource() {
344         final StreamSource source = new StreamResource.StreamSource() {
345             private static final long serialVersionUID = 1L;
346 
347             @Override
348             public InputStream getStream() {
349                 try {
350                     return new FileInputStream(getValue().getFile());
351                 } catch (FileNotFoundException fnfe) {
352                     log.warn("could not found File", fnfe);
353                     return null;
354                 }
355             }
356         };
357 
358         final Resource resource = new StreamResource(source, "") {
359             @Override
360             public String getMIMEType() {
361                 return getValue().getMimeType();
362             }
363         };
364 
365         return resource;
366     }
367 
368     protected void populateFromDefinition(DamUploadFieldDefinition definition) {
369         super.populateFromDefinition(definition);
370         this.setLightboxCaption(definition.getLightboxCaption());
371         this.setEditFileCaption(definition.getEditFileCaption());
372     }
373 
374     /**
375      * Caption section.
376      */
377     @Override
378     protected void setCaptionExtension(String mimeType) {
379         if (StringUtils.isNotBlank(mimeType)) {
380             captionExtension = MediaType.parse(mimeType).type();
381         } else if (StringUtils.isNotBlank(getValue().getMimeType())) {
382             captionExtension = MediaType.parse(getValue().getMimeType()).type();
383         }
384     }
385 
386     private String lightboxCaption;
387     private String editFileCaption;
388 
389 
390     public void setLightboxCaption(String lightboxCaption) {
391         this.lightboxCaption = lightboxCaption;
392     }
393 
394     public void setEditFileCaption(String editFileCaption) {
395         this.editFileCaption = editFileCaption;
396     }
397 
398 }