View Javadoc
1   /**
2    * This file Copyright (c) 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.EditAssetAppConfiguration;
37  import info.magnolia.dam.app.ui.field.configuration.PreviewComponentProvider;
38  import info.magnolia.dam.app.ui.field.configuration.ThumbnailComponentProvider;
39  import info.magnolia.dam.app.ui.field.configuration.image.ImageThumbnailComponentProvider;
40  import info.magnolia.dam.app.ui.field.definition.DamUploadFieldDefinition;
41  import info.magnolia.i18nsystem.SimpleTranslator;
42  import info.magnolia.icons.MagnoliaIcons;
43  import info.magnolia.objectfactory.ComponentProvider;
44  import info.magnolia.ui.api.context.UiContext;
45  import info.magnolia.ui.api.overlay.OverlayCloser;
46  import info.magnolia.ui.form.field.upload.basic.ResurfaceBasicUploadField;
47  import info.magnolia.ui.imageprovider.ImageProvider;
48  import info.magnolia.ui.mediaeditor.MediaEditorPresenter;
49  import info.magnolia.ui.mediaeditor.MediaEditorPresenterFactory;
50  import info.magnolia.ui.mediaeditor.event.MediaEditorCompletedEvent;
51  import info.magnolia.ui.vaadin.overlay.MessageStyleTypeEnum;
52  
53  import java.io.FileInputStream;
54  import java.io.FileNotFoundException;
55  import java.io.InputStream;
56  
57  import org.apache.commons.io.FileUtils;
58  import org.slf4j.Logger;
59  import org.slf4j.LoggerFactory;
60  
61  import com.vaadin.server.Resource;
62  import com.vaadin.server.StreamResource;
63  import com.vaadin.ui.Button;
64  import com.vaadin.ui.Component;
65  import com.vaadin.ui.CssLayout;
66  import com.vaadin.ui.Label;
67  import com.vaadin.ui.Layout;
68  
69  /**
70   * Dam implementation of {@link ResurfaceBasicUploadField}.<br>
71   * Override default Layout components of the CompletedLayout:
72   * <ul>
73   * <li>createCompletedActionLayout (Contains the available actions based on the MediaType)</li>
74   * <li>createThumbnailComponent() (Display a preview Image or an Icon)</li>
75   * <li>getFileDetailSize() (Add file size/duration informations )</li>
76   * </ul>
77   * An Uploaded file is mapped to a mediaType (based on the MimeType). <br>
78   * The mediaType define upload configuration like:
79   * <ul>
80   * <li>{@link info.magnolia.dam.app.ui.field.configuration.ThumbnailComponentProvider} to use in order to display the preview Image</li>
81   * <li>should this media have an edit button and in case, which mediaEtior to call</li>
82   * <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>
83   * </ul>
84   * <b>Check {@link info.magnolia.dam.app.ui.field.configuration.EditAssetAppConfiguration} for additional information regarding MediaEditor configuration</b>
85   *
86   * @param <D> {@link AssetUploadReceiver} implemented class.
87   */
88  public class ResurfaceDamUploadField<D extends AssetUploadReceiver> extends ResurfaceBasicUploadField<D> {
89  
90      private static final Logger log = LoggerFactory.getLogger(ResurfaceDamUploadField.class);
91  
92      private final MediaEditorPresenterFactory mediaEditorFactory;
93      private final ComponentProvider componentProvider;
94      private boolean imageThumbnail = false;
95  
96      public ResurfaceDamUploadField(ImageProvider imageProvider, UiContext uiContext, MediaEditorPresenterFactory mediaEditorFactory, ComponentProvider componentProvider, DamUploadFieldDefinition definition, SimpleTranslator i18n) {
97          super(imageProvider, uiContext, definition, i18n);
98          // Propagate definition.
99          populateFromDefinition(definition);
100 
101         this.mediaEditorFactory = mediaEditorFactory;
102         this.componentProvider = componentProvider;
103     }
104 
105     /**
106      * Add File Info.
107      */
108     @Override
109     protected Component getFileDetailSize() {
110         Label label = new Label();
111         label.setCaption(getI18n().translate(fileDetailSizeCaption));
112         StringBuilder sb = new StringBuilder();
113         if (getValue().isImage()) {
114             sb.append(getValue().getWidth() + " x " + getValue().getHeight() + ", ");
115         }
116         sb.append(FileUtils.byteCountToDisplaySize(getValue().getFileSize()));
117         if (getValue().getDuration() > 0) {
118             sb.append("(x:x min)");
119         }
120 
121         label.setValue(sb.toString());
122         return label;
123     }
124 
125     @Override
126     protected void addControlActions(CssLayout uploadZone) {
127         super.addControlActions(uploadZone);
128         final EditAssetAppConfiguration editAssetAppConfiguration = getValue().getEditAssetAppConfiguration();
129         if (getValue() != null && !getValue().isEmpty() && editAssetAppConfiguration.hasEditConfig()) {
130             uploadZone.addComponent(createEditButton());
131         }
132         // Add buttons to the preview layout (Add LightBox) IF CONFIGURED
133         if (getValue().getEditAssetAppConfiguration().hasPreviewConfig()) {
134             uploadZone.addComponent(createOpenLightboxButton());
135         }
136     }
137 
138     @Override
139     protected Component createThumbnailComponent() {
140         final Component previewComponent = createPreviewComponent();
141         previewComponent.addStyleName("file-preview");
142         return previewComponent;
143     }
144 
145     @Override
146     protected Layout createCompletedActionLayout() {
147         final Layout completedActionLayout = super.createCompletedActionLayout();
148         completedActionLayout.removeStyleName("done-img");
149         if (imageThumbnail) {
150             completedActionLayout.addStyleName("done-img");
151         }
152         return completedActionLayout;
153     }
154 
155     @Override
156     protected void buildEmptyLayout() {
157         super.buildEmptyLayout();
158         getUploadZone().removeStyleName("done-img");
159     }
160 
161     /**
162      * Create Edit button.<br>
163      * Delegate the call to the media Editor to openMediaEditor {@link MediaEditorPresenter#start(InputStream)}.
164      */
165     private Button createEditButton() {
166         Button editButton = createControlButton(deleteCaption, MagnoliaIcons.EDIT);
167 
168         editButton.addClickListener((Button.ClickListener) event -> {
169             // Launch MediaEditor for this item.
170             try {
171                 openMediaEditor();
172             } catch (FileNotFoundException fnfe) {
173                 log.warn("could not open MediaEditor");
174                 uiContext.openAlert(MessageStyleTypeEnum.ERROR, "ERROR", getI18n().translate("dam.assets.uploadField.alert.couldNotOpenMediaEditor")
175                                 + " "
176                                 + getValue().getEditAssetAppConfiguration().getEditConfig().getMediaEditorId(),
177                         "ok", null);
178             } finally {
179                 event.getButton().setEnabled(true);
180             }
181         });
182 
183         editButton.setDescription(getI18n().translate("field.upload.edit.file"));
184         editButton.setDisableOnClick(true);
185         return editButton;
186     }
187 
188     private Button createOpenLightboxButton() {
189         Button lightboxButton = createControlButton(deleteCaption, MagnoliaIcons.SEARCH);
190         lightboxButton.addClickListener((Button.ClickListener) event -> {
191             // Launch Lightbox component
192             Class<? extends PreviewComponentProvider> previewActionClass = getValue().getEditAssetAppConfiguration().getPreviewConfig().getPreviewComponentProviderClass();
193             if (previewActionClass != null) {
194                 PreviewComponentProvider implementation = componentProvider.newInstance(previewActionClass);
195                 implementation.open(getResource());
196             } else {
197                 log.warn("No Preview Defined is defined ");
198             }
199         });
200         lightboxButton.setDescription(getI18n().translate("field.upload.select.lightbox"));
201         return lightboxButton;
202     }
203 
204     /**
205      * Open a mediaEditor populated with the binary of this file.
206      * Media Editor Id is configured in the related MediaFile definition.
207      *
208      * @throws FileNotFoundException
209      */
210     private void openMediaEditor() throws FileNotFoundException {
211         // Check
212         String presenterById;
213         if (getValue().getEditAssetAppConfiguration() == null || !getValue().getEditAssetAppConfiguration().hasEditConfig()) {
214             log.warn("No Media Editor defined for the following mimeType {} ", getValue().getMimeType());
215             return;
216         } else {
217             presenterById = "ui-mediaeditor:" + getValue().getEditAssetAppConfiguration().getEditConfig().getMediaEditorId();
218             log.debug("Will open the following mediaEditor Presenter {}", presenterById);
219         }
220 
221         final Button mediaEditorPlaceholder = new Button(getI18n().translate("dam.assets.uploadField.button.mediaEditorPlaceholder"));
222         mediaEditorPlaceholder.addStyleName("btn-form btn-form-commit");
223 
224         MediaEditorPresenter mediaEditorPresenter = mediaEditorFactory.getPresenterById(presenterById);
225         final InputStream inputStream = new FileInputStream(getValue().getFile());
226         final OverlayCloser overlayCloser = uiContext.openOverlay(mediaEditorPresenter.start(inputStream));
227         mediaEditorPresenter.addCompletionHandler(new MediaEditorCompletedEvent.Handler() {
228             @Override
229             public void onSubmit(MediaEditorCompletedEvent event) {
230                 getValue().updateContent(event.getStream());
231                 // Update the display to show changes to media.
232                 updateDisplay();
233                 overlayCloser.close();
234                 closeInputStream(inputStream);
235                 getPropertyDataSource().setValue(getValue());
236             }
237 
238             @Override
239             public void onCancel(MediaEditorCompletedEvent event) {
240                 overlayCloser.close();
241                 closeInputStream(inputStream);
242             }
243 
244             private void closeInputStream(InputStream inputStream) {
245                 try {
246                     if (inputStream != null) {
247                         inputStream.close();
248                     }
249                 } catch (Exception e) {
250                     log.warn("Could not close the InputStream ", e);
251                 }
252             }
253         });
254     }
255 
256     /**
257      * Create a resource from the File.
258      */
259     private Resource getResource() {
260         final StreamResource.StreamSource source = (StreamResource.StreamSource) () -> {
261             try {
262                 return new FileInputStream(getValue().getFile());
263             } catch (FileNotFoundException fnfe) {
264                 log.warn("could not found File", fnfe);
265                 return null;
266             }
267         };
268 
269         return new StreamResource(source, "") {
270             @Override
271             public String getMIMEType() {
272                 return getValue().getMimeType();
273             }
274         };
275     }
276 
277     private Component createPreviewComponent() {
278 
279         final EditAssetAppConfiguration editAssetAppConfiguration = getValue().getEditAssetAppConfiguration();
280 
281         if (editAssetAppConfiguration == null) {
282             return super.createThumbnailComponent();
283         }
284 
285         Class<? extends ThumbnailComponentProvider> thumbnailProviderClass = editAssetAppConfiguration.getThumbnailComponentProviderClass();
286         if (thumbnailProviderClass != null) {
287             ThumbnailComponentProvider implementation = componentProvider.newInstance(thumbnailProviderClass);
288             final Component thumbnailComponent = implementation.createThumbnailComponent(null, getValue().getFile(), getValue().getMimeType());
289             if (ImageThumbnailComponentProvider.class.isInstance(implementation)) {
290                 thumbnailComponent.addStyleName("img-preview");
291                 this.imageThumbnail = true;
292             } else {
293                 this.imageThumbnail = false;
294             }
295             return thumbnailComponent;
296         } else {
297             log.warn("No ThumbnailComponentProvider is defined ");
298             return super.createThumbnailComponent();
299         }
300     }
301 }