Added view to display all images thumbnails
authorThierry Florac <thierry.florac@onf.fr>
Mon, 11 Sep 2017 13:30:46 +0200
changeset 51 83ef19bfaa63
parent 50 3b2cb5046d86
child 52 1a9b1b622a18
Added view to display all images thumbnails
src/pyams_file/zmi/image.py
src/pyams_file/zmi/templates/image-thumbnails.pt
--- a/src/pyams_file/zmi/image.py	Mon Sep 11 13:28:59 2017 +0200
+++ b/src/pyams_file/zmi/image.py	Mon Sep 11 13:30:46 2017 +0200
@@ -14,9 +14,13 @@
 
 
 # import standard library
+import random
+import sys
+from collections import OrderedDict
 
 # import interfaces
-from pyams_file.interfaces import IImage, IThumbnail, IResponsiveImage, IImageWidget, IFileModifierForm
+from pyams_file.interfaces import IImage, IThumbnail, IResponsiveImage, IImageWidget, IFileModifierForm, IThumbnailer, \
+    IFileInfo, IThumbnailForm
 from pyams_form.interfaces.form import IWidgetsPrefixViewletsManager, IFormHelp
 from pyams_skin.interfaces.viewlet import IContextActions
 from pyams_skin.layer import IPyAMSLayer
@@ -26,12 +30,14 @@
 # import packages
 from pyams_file.image import ThumbnailGeometry
 from pyams_file.zmi import FileModifierAction
-from pyams_form.form import AJAXEditForm
+from pyams_form.form import AJAXEditForm, DialogDisplayForm
 from pyams_form.help import FormHelp
 from pyams_form.schema import CloseButton
 from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.viewlet.toolbar import ToolbarActionItem
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
+from pyams_utils.url import absolute_url
 from pyams_viewlet.viewlet import viewlet_config, Viewlet
 from pyams_zmi.form import AdminDialogEditForm
 from pyramid.view import view_config
@@ -44,11 +50,494 @@
 
 
 #
+# Image crop
+#
+
+@viewlet_config(name='image.crop.action', context=IImage, layer=IPyAMSLayer, view=IImageWidget,
+                manager=IContextActions, weight=21)
+class ImageCropAction(FileModifierAction):
+    """Image crop action"""
+
+    label = _("Crop image...")
+    label_css_class = 'fa fa-fw-md fa-crop'
+
+    url = 'crop.html'
+    modal_target = True
+
+
+class ICropFormButtons(Interface):
+    """Image crop form buttons"""
+
+    close = CloseButton(name='close', title=_("Close"))
+    crop = button.Button(name='crop', title=_("Crop"))
+
+
+@pagelet_config(name='crop.html', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
+@implementer(IFileModifierForm)
+class ImageCropForm(AdminDialogEditForm):
+    """Image crop form"""
+
+    legend = _("Crop image")
+    icon_css_class = 'fa fa-fw fa-crop'
+    dialog_class = 'modal-large'
+
+    fields = field.Fields(Interface)
+    buttons = button.Buttons(ICropFormButtons)
+    ajax_handler = 'crop.json'
+    edit_permission = None
+
+    @property
+    def title(self):
+        return self.context.title or self.context.filename
+
+    def updateActions(self):
+        super(ImageCropForm, self).updateActions()
+        if 'crop' in self.actions:
+            self.actions['crop'].addClass('btn-primary')
+
+
+@view_config(name='crop.json', context=IImage, request_type=IPyAMSLayer,
+             permission=VIEW_PERMISSION, renderer='json', xhr=True)
+class ImageCropAJAXForm(AJAXEditForm, ImageCropForm):
+    """Image crop form, AJAX renderer"""
+
+    def update_content(self, content, data):
+        image_size = self.context.get_image_size()
+        x1 = int(self.request.params.get('selection.x1', 0))
+        y1 = int(self.request.params.get('selection.y1', 0))
+        x2 = int(self.request.params.get('selection.x2', image_size[0]))
+        y2 = int(self.request.params.get('selection.y2', image_size[1]))
+        self.context.crop(x1, y1, x2, y2)
+
+    def get_ajax_output(self, changes):
+        return {'status': 'reload',
+                'smallbox': self.request.localizer.translate(self.successMessage),
+                'smallbox_status': 'success'}
+
+
+@viewlet_config(name='crop.widgets-prefix', context=IImage, layer=IAdminLayer, view=ImageCropForm,
+                manager=IWidgetsPrefixViewletsManager)
+@template_config(template='templates/image-crop.pt')
+class ImageCropViewletsPrefix(Viewlet):
+    """Image crop viewlets prefix"""
+
+
+@adapter_config(context=(IImage, IAdminLayer, ImageCropForm), provides=IFormHelp)
+class ImageCropFormHelpAdapter(FormHelp):
+    """Image crop form help adapter"""
+
+    message = _("""You can use this form to crop an image.
+
+**WARNING**: cropping an image will reset all selected thumbnails and adaptive images!!""")
+    message_format = 'rest'
+
+
+#
+# Image square thumbnail selection
+#
+
+@adapter_config(context=(IImage, IAdminLayer, IThumbnailForm), provides=IFormHelp)
+class ThumbnailFormHelpAdapter(FormHelp):
+    """Thumbnail form help adapter"""
+
+    message = _("""You can use this form to make a selection on an image.""")
+    message_format = 'rest'
+
+
+class IThumbnailFormButtons(Interface):
+    """Image crop form buttons"""
+
+    close = CloseButton(name='close', title=_("Close"))
+    crop = button.Button(name='crop', title=_("Select thumbnail"))
+
+
+@viewlet_config(name='image.thumb.square.action', context=IImage, layer=IPyAMSLayer, view=IImageWidget,
+                manager=IContextActions, weight=60)
+class ImageSquareThumbnailAction(FileModifierAction):
+    """Square thumbnail image selection"""
+
+    _label = _("Select square thumbnail...")
+
+    @property
+    def label(self):
+        return '<div>{label}</div><div class="padding-y-5"><img src="{url}/++thumb++square:200x128.jpeg?_=" ' \
+               '/></div>'.format(label=self.request.localizer.translate(self._label),
+                                 url=absolute_url(self.context, self.request))
+
+    label_css_class = 'fa fa-fw-md fa-instagram'
+
+    url = 'square-thumbnail.html'
+    modal_target = True
+
+
+@pagelet_config(name='square-thumbnail.html', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
+@implementer(IThumbnailForm)
+class ImageSquareThumbnailEditForm(AdminDialogEditForm):
+    """Image square thumbnail edit form"""
+
+    legend = _("Select square thumbnail")
+    icon_css_class = 'fa fa-fw fa-instagram'
+    dialog_class = 'modal-large'
+
+    fields = field.Fields(Interface)
+    buttons = button.Buttons(IThumbnailFormButtons)
+    ajax_handler = 'square-thumbnail.json'
+    edit_permission = None
+
+    @property
+    def title(self):
+        return self.context.title or self.context.filename
+
+    def updateActions(self):
+        super(ImageSquareThumbnailEditForm, self).updateActions()
+        if 'crop' in self.actions:
+            self.actions['crop'].addClass('btn-primary')
+
+
+@view_config(name='square-thumbnail.json', context=IImage, request_type=IPyAMSLayer,
+             permission=VIEW_PERMISSION, renderer='json', xhr=True)
+class ImageSquareThumbnailAJAXEditForm(AJAXEditForm, ImageSquareThumbnailEditForm):
+    """Image square thumbnail edit form, AJAX renderer"""
+
+    def update_content(self, content, data):
+        geometry = ThumbnailGeometry()
+        geometry.x1 = int(self.request.params.get('selection.x1'))
+        geometry.y1 = int(self.request.params.get('selection.y1'))
+        geometry.x2 = int(self.request.params.get('selection.x2'))
+        geometry.y2 = int(self.request.params.get('selection.y2'))
+        IThumbnail(self.context).set_geometry('square', geometry)
+
+    def get_ajax_output(self, changes):
+        return {'status': 'success',
+                'smallbox': self.request.localizer.translate(self.successMessage),
+                'smallbox_status': 'success'}
+
+
+@viewlet_config(name='square-thumbnail.widgets-prefix', context=IImage, layer=IAdminLayer,
+                view=ImageSquareThumbnailEditForm, manager=IWidgetsPrefixViewletsManager)
+@template_config(template='templates/image-square-thumbnail.pt')
+class ImageSquareThumbnailViewletsPrefix(Viewlet):
+    """Image square thumbnail viewlets prefix"""
+
+
+@adapter_config(context=(IImage, IAdminLayer, ImageSquareThumbnailEditForm), provides=IFormHelp)
+class ImageSquareThumbnailEditFormHelpAdapter(FormHelp):
+    """Square image thumbnail edit form help adapter"""
+
+    message = _("""You can use this form to select a square thumbnail of this image.
+
+**WARNING**: cropping or resizing an image will reset all selected thumbnails and adaptive images!!""")
+    message_format = 'rest'
+
+
+#
+# Image panoramic thumbnail selection
+#
+
+@viewlet_config(name='image.thumb.pano.action', context=IImage, layer=IAdminLayer, view=IImageWidget,
+                manager=IContextActions, weight=65)
+class ImagePanoThumbnailAction(FileModifierAction):
+    """Panoramic thumbnail image selection"""
+
+    _label = _("Select panoramic thumbnail...")
+
+    @property
+    def label(self):
+        return '<div>{label}</div><div class="padding-y-5"><img src="{url}/++thumb++pano:200x128.jpeg?_=" ' \
+               '/></div>'.format(label=self.request.localizer.translate(self._label),
+                                 url=absolute_url(self.context, self.request))
+
+    label_css_class = 'fa fa-fw-md fa-youtube-play'
+
+    url = 'pano-thumbnail.html'
+    modal_target = True
+
+
+@pagelet_config(name='pano-thumbnail.html', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
+@implementer(IThumbnailForm)
+class ImagePanoThumbnailEditForm(AdminDialogEditForm):
+    """Image panoramic thumbnail edit form"""
+
+    legend = _("Select panoramic thumbnail")
+    icon_css_class = 'fa fa-fw fa-youtube-play'
+    dialog_class = 'modal-large'
+
+    fields = field.Fields(Interface)
+    buttons = button.Buttons(IThumbnailFormButtons)
+    ajax_handler = 'pano-thumbnail.json'
+    edit_permission = None
+
+    @property
+    def title(self):
+        return self.context.title or self.context.filename
+
+    def updateActions(self):
+        super(ImagePanoThumbnailEditForm, self).updateActions()
+        if 'crop' in self.actions:
+            self.actions['crop'].addClass('btn-primary')
+
+
+@view_config(name='pano-thumbnail.json', context=IImage, request_type=IPyAMSLayer,
+             permission=VIEW_PERMISSION, renderer='json', xhr=True)
+class ImagePanoThumbnailAJAXEditForm(AJAXEditForm, ImagePanoThumbnailEditForm):
+    """Image panoramic thumbnail edit form, AJAX renderer"""
+
+    def update_content(self, content, data):
+        geometry = ThumbnailGeometry()
+        geometry.x1 = int(self.request.params.get('selection.x1'))
+        geometry.y1 = int(self.request.params.get('selection.y1'))
+        geometry.x2 = int(self.request.params.get('selection.x2'))
+        geometry.y2 = int(self.request.params.get('selection.y2'))
+        IThumbnail(self.context).set_geometry('pano', geometry)
+
+    def get_ajax_output(self, changes):
+        return {'status': 'success',
+                'smallbox': self.request.localizer.translate(self.successMessage),
+                'smallbox_status': 'success'}
+
+
+@viewlet_config(name='pano-thumbnail.widgets-prefix', context=IImage, layer=IAdminLayer,
+                view=ImagePanoThumbnailEditForm, manager=IWidgetsPrefixViewletsManager)
+@template_config(template='templates/image-pano-thumbnail.pt')
+class ImagePanoThumbnailViewletsPrefix(Viewlet):
+    """Image panoramic thumbnail viewlets prefix"""
+
+
+@adapter_config(context=(IImage, IAdminLayer, ImagePanoThumbnailEditForm), provides=IFormHelp)
+class ImagePanoThumbnailEditFormHelpAdapter(FormHelp):
+    """Panoramic image thumbnail edit form help adapter"""
+
+    message = _("""You can use this form to select a panoramic thumbnail of this image.
+
+**WARNING**: cropping or resizing an image will reset all selected thumbnails and adaptive images!!""")
+    message_format = 'rest'
+
+
+#
+# Image responsive selections
+#
+
+class IResponsiveImageSelectionFormButtons(Interface):
+    """Responsive image selection form buttons"""
+
+    close = CloseButton(name='close', title=_("Close"))
+    select = button.Button(name='select', title=_("Select thumbnail"))
+
+
+@implementer(IThumbnailForm)
+class ResponsiveImageSelectionForm(AdminDialogEditForm):
+    """Base responsive image selection edit form"""
+
+    dialog_class = 'modal-large'
+
+    fields = field.Fields(Interface)
+    buttons = button.Buttons(IResponsiveImageSelectionFormButtons)
+    edit_permission = None
+
+    @property
+    def title(self):
+        return self.context.title or self.context.filename
+
+    def updateActions(self):
+        super(ResponsiveImageSelectionForm, self).updateActions()
+        if 'select' in self.actions:
+            self.actions['select'].addClass('btn-primary')
+
+
+class ResponsiveImageSelectionAJAXForm(AJAXEditForm):
+    """Base responsive image selection edit form, JSON renderer"""
+
+    def update_content(self, content, data):
+        geometry = ThumbnailGeometry()
+        geometry.x1 = int(self.request.params.get('selection.x1'))
+        geometry.y1 = int(self.request.params.get('selection.y1'))
+        geometry.x2 = int(self.request.params.get('selection.x2'))
+        geometry.y2 = int(self.request.params.get('selection.y2'))
+        IThumbnail(self.context).set_geometry(self.selection_size, geometry)
+
+    def get_ajax_output(self, changes):
+        return {'status': 'success',
+                'smallbox': self.request.localizer.translate(self.successMessage),
+                'smallbox_status': 'success'}
+
+
+@viewlet_config(name='responsive-image.selection.widgets-prefix', context=IResponsiveImage, layer=IAdminLayer,
+                view=ResponsiveImageSelectionForm, manager=IWidgetsPrefixViewletsManager)
+@template_config(template='templates/image-selection.pt')
+class ResponsiveImageSelectionViewletsPrefix(Viewlet):
+    """Responsive image selection viewlets prefix"""
+
+
+#
+# Extra-small devices selection
+#
+
+@viewlet_config(name='responsive-image.selection.xs.action', context=IResponsiveImage, layer=IAdminLayer,
+                view=IImageWidget, manager=IContextActions, weight=41)
+class ResponsiveImageXsSelectionAction(FileModifierAction):
+    """Responsive image XS selection"""
+
+    _label = _("Select responsive XS image...")
+
+    @property
+    def label(self):
+        return '<div>{label}</div><div class="padding-y-5"><img src="{url}/++thumb++xs:200x128.jpeg?_=" ' \
+               '/></div>'.format(label=self.request.localizer.translate(self._label),
+                                 url=absolute_url(self.context, self.request))
+
+    label_css_class = 'fa fa-fw-md fa-mobile'
+
+    url = 'selection-xs.html'
+    modal_target = True
+
+
+@pagelet_config(name='selection-xs.html', context=IResponsiveImage, layer=IPyAMSLayer,
+                permission=VIEW_PERMISSION)
+class ResponsiveImageXsSelectionForm(ResponsiveImageSelectionForm):
+    """Responsive image XS selection edit form"""
+
+    legend = _("Select image for extra-small (XS) devices")
+    icon_css_class = 'fa fa-fw fa-mobile'
+
+    selection_size = 'xs'
+    ajax_handler = 'selection-xs.json'
+
+
+@view_config(name='selection-xs.json', context=IResponsiveImage, request_type=IPyAMSLayer,
+             permission=VIEW_PERMISSION, renderer='json', xhr=True)
+class ResponsiveImageXsSelectionAJAXEditForm(ResponsiveImageSelectionAJAXForm, ResponsiveImageXsSelectionForm):
+    """Responsive image XS selection edit form, JSON renderer"""
+
+
+#
+# Small devices selection
+#
+
+@viewlet_config(name='responsive-image.selection.sm.action', context=IResponsiveImage, layer=IAdminLayer,
+                view=IImageWidget, manager=IContextActions, weight=42)
+class ResponsiveImageSmSelectionAction(FileModifierAction):
+    """Responsive image SM selection"""
+
+    _label = _("Select responsive SM image...")
+
+    @property
+    def label(self):
+        return '<div>{label}</div><div class="padding-y-5"><img src="{url}/++thumb++sm:200x128.jpeg?_=" ' \
+               '/></div>'.format(label=self.request.localizer.translate(self._label),
+                                 url=absolute_url(self.context, self.request))
+
+    label_css_class = 'fa fa-fw-md fa-tablet'
+
+    url = 'selection-sm.html'
+    modal_target = True
+
+
+@pagelet_config(name='selection-sm.html', context=IResponsiveImage, layer=IPyAMSLayer,
+                permission=VIEW_PERMISSION)
+class ResponsiveImageSmSelectionForm(ResponsiveImageSelectionForm):
+    """Responsive image SM selection edit form"""
+
+    legend = _("Select image for small (SM) devices")
+    icon_css_class = 'fa fa-fw fa-tablet'
+
+    selection_size = 'sm'
+    ajax_handler = 'selection-sm.json'
+
+
+@view_config(name='selection-sm.json', context=IResponsiveImage, request_type=IPyAMSLayer,
+             permission=VIEW_PERMISSION, renderer='json', xhr=True)
+class ResponsiveImageSmSelectionAJAXEditForm(ResponsiveImageSelectionAJAXForm, ResponsiveImageSmSelectionForm):
+    """Responsive image SM selection edit form, JSON renderer"""
+
+
+#
+# Medium devices selection
+#
+
+@viewlet_config(name='responsive-image.selection.md.action', context=IResponsiveImage, layer=IAdminLayer,
+                view=IImageWidget, manager=IContextActions, weight=43)
+class ResponsiveImageMdSelectionAction(FileModifierAction):
+    """Responsive image MD selection"""
+
+    _label = _("Select responsive MD image...")
+
+    @property
+    def label(self):
+        return '<div>{label}</div><div class="padding-y-5"><img src="{url}/++thumb++md:200x128.jpeg?_=" ' \
+               '/></div>'.format(label=self.request.localizer.translate(self._label),
+                                 url=absolute_url(self.context, self.request))
+
+    label_css_class = 'fa fa-fw-md fa-desktop'
+
+    url = 'selection-md.html'
+    modal_target = True
+
+
+@pagelet_config(name='selection-md.html', context=IResponsiveImage, layer=IPyAMSLayer,
+                permission=VIEW_PERMISSION)
+class ResponsiveImageMdSelectionForm(ResponsiveImageSelectionForm):
+    """Responsive image MD selection edit form"""
+
+    legend = _("Select image for medium (MD) devices")
+    icon_css_class = 'fa fa-fw fa-desktop'
+
+    selection_size = 'md'
+    ajax_handler = 'selection-md.json'
+
+
+@view_config(name='selection-md.json', context=IResponsiveImage, request_type=IPyAMSLayer,
+             permission=VIEW_PERMISSION, renderer='json', xhr=True)
+class ResponsiveImageMdSelectionAJAXEditForm(ResponsiveImageSelectionAJAXForm, ResponsiveImageMdSelectionForm):
+    """Responsive image MD selection edit form, JSON renderer"""
+
+
+#
+# Large devices selection
+#
+
+@viewlet_config(name='responsive-image.selection.lg.action', context=IResponsiveImage, layer=IAdminLayer,
+                view=IImageWidget, manager=IContextActions, weight=44)
+class ResponsiveImageLgSelectionAction(FileModifierAction):
+    """Responsive image LG selection"""
+
+    _label = _("Select responsive LG image...")
+
+    @property
+    def label(self):
+        return '<div>{label}</div><div class="padding-y-5"><img src="{url}/++thumb++lg:200x128.jpeg?_=" ' \
+               '/></div>'.format(label=self.request.localizer.translate(self._label),
+                                 url=absolute_url(self.context, self.request))
+
+    label_css_class = 'fa fa-fw-md fa-television'
+
+    url = 'selection-lg.html'
+    modal_target = True
+
+
+@pagelet_config(name='selection-lg.html', context=IResponsiveImage, layer=IPyAMSLayer,
+                permission=VIEW_PERMISSION)
+class ResponsiveImageLgSelectionForm(ResponsiveImageSelectionForm):
+    """Responsive image LG selection edit form"""
+
+    legend = _("Select image for large (LG) devices")
+    icon_css_class = 'fa fa-fw fa-television'
+
+    selection_size = 'lg'
+    ajax_handler = 'selection-lg.json'
+
+
+@view_config(name='selection-lg.json', context=IResponsiveImage, request_type=IPyAMSLayer,
+             permission=VIEW_PERMISSION, renderer='json', xhr=True)
+class ResponsiveImageLgSelectionAJAXEditForm(ResponsiveImageSelectionAJAXForm, ResponsiveImageLgSelectionForm):
+    """Responsive image LG selection edit form, JSON renderer"""
+
+
+#
 # Image resize
 #
 
 @viewlet_config(name='image.resize.action', context=IImage, layer=IPyAMSLayer, view=IImageWidget,
-                manager=IContextActions, weight=20)
+                manager=IContextActions, weight=80)
 class ImageResizeAction(FileModifierAction):
     """Image resize action"""
 
@@ -146,413 +635,55 @@
 
 
 #
-# Image crop
+# See all thumbnails!!
 #
 
-@viewlet_config(name='image.crop.action', context=IImage, layer=IPyAMSLayer, view=IImageWidget,
-                manager=IContextActions, weight=21)
-class ImageCropAction(FileModifierAction):
-    """Image crop action"""
+@viewlet_config(name='display-all-thumbnails.action', context=IImage, layer=IAdminLayer,
+                view=IImageWidget, manager=IContextActions, weight=90)
+class ImageThumbnailsDisplayAction(ToolbarActionItem):
+    """Image thumbnails display action"""
 
-    label = _("Crop image...")
-    label_css_class = 'fa fa-fw-md fa-crop'
+    label = _("Display all thumbnails")
+    label_css_class = 'fa fa-fw fa-th-large text-primary'
+    hint_gravity = 'nw'
 
-    url = 'crop.html'
+    url = 'display-all-thumbnails.html'
     modal_target = True
 
 
-class ICropFormButtons(Interface):
-    """Image crop form buttons"""
-
-    close = CloseButton(name='close', title=_("Close"))
-    crop = button.Button(name='crop', title=_("Crop"))
-
-
-@pagelet_config(name='crop.html', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
-@implementer(IFileModifierForm)
-class ImageCropForm(AdminDialogEditForm):
-    """Image crop form"""
-
-    legend = _("Crop image")
-    icon_css_class = 'fa fa-fw fa-crop'
-    dialog_class = 'modal-large'
-
-    fields = field.Fields(Interface)
-    buttons = button.Buttons(ICropFormButtons)
-    ajax_handler = 'crop.json'
-    edit_permission = None
-
-    @property
-    def title(self):
-        return self.context.title or self.context.filename
-
-    def updateActions(self):
-        super(ImageCropForm, self).updateActions()
-        if 'crop' in self.actions:
-            self.actions['crop'].addClass('btn-primary')
-
-
-@view_config(name='crop.json', context=IImage, request_type=IPyAMSLayer,
-             permission=VIEW_PERMISSION, renderer='json', xhr=True)
-class ImageCropAJAXForm(AJAXEditForm, ImageCropForm):
-    """Image crop form, AJAX renderer"""
-
-    def update_content(self, content, data):
-        image_size = self.context.get_image_size()
-        x1 = int(self.request.params.get('selection.x1', 0))
-        y1 = int(self.request.params.get('selection.y1', 0))
-        x2 = int(self.request.params.get('selection.x2', image_size[0]))
-        y2 = int(self.request.params.get('selection.y2', image_size[1]))
-        self.context.crop(x1, y1, x2, y2)
-
-    def get_ajax_output(self, changes):
-        return {'status': 'reload',
-                'smallbox': self.request.localizer.translate(self.successMessage),
-                'smallbox_status': 'success'}
-
-
-@viewlet_config(name='crop.widgets-prefix', context=IImage, layer=IAdminLayer, view=ImageCropForm,
-                manager=IWidgetsPrefixViewletsManager)
-@template_config(template='templates/image-crop.pt')
-class ImageCropViewletsPrefix(Viewlet):
-    """Image crop viewlets prefix"""
-
-
-@adapter_config(context=(IImage, IAdminLayer, ImageCropForm), provides=IFormHelp)
-class ImageCropFormHelpAdapter(FormHelp):
-    """Image crop form help adapter"""
-
-    message = _("""You can use this form to crop an image.
-
-**WARNING**: cropping an image will reset all selected thumbnails and adaptive images!!""")
-    message_format = 'rest'
-
-
-#
-# Image square thumbnail selection
-#
-
-class IThumbnailFormButtons(Interface):
-    """Image crop form buttons"""
-
-    close = CloseButton(name='close', title=_("Close"))
-    crop = button.Button(name='crop', title=_("Select thumbnail"))
-
-
-@viewlet_config(name='image.thumb.square.action', context=IImage, layer=IPyAMSLayer, view=IImageWidget,
-                manager=IContextActions, weight=31)
-class ImageSquareThumbnailAction(FileModifierAction):
-    """Square thumbnail image selection"""
-
-    label = _("Select square thumbnail...")
-    label_css_class = 'fa fa-fw-md fa-instagram'
-
-    url = 'square-thumbnail.html'
-    modal_target = True
-
-
-@pagelet_config(name='square-thumbnail.html', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
-@implementer(IFileModifierForm)
-class ImageSquareThumbnailEditForm(AdminDialogEditForm):
-    """Image square thumbnail edit form"""
-
-    legend = _("Select square thumbnail")
-    icon_css_class = 'fa fa-fw fa-instagram'
-    dialog_class = 'modal-large'
-
-    fields = field.Fields(Interface)
-    buttons = button.Buttons(IThumbnailFormButtons)
-    ajax_handler = 'square-thumbnail.json'
-    edit_permission = None
-
-    @property
-    def title(self):
-        return self.context.title or self.context.filename
-
-    def updateActions(self):
-        super(ImageSquareThumbnailEditForm, self).updateActions()
-        if 'crop' in self.actions:
-            self.actions['crop'].addClass('btn-primary')
-
-
-@view_config(name='square-thumbnail.json', context=IImage, request_type=IPyAMSLayer,
-             permission=VIEW_PERMISSION, renderer='json', xhr=True)
-class ImageSquareThumbnailAJAXEditForm(AJAXEditForm, ImageSquareThumbnailEditForm):
-    """Image square thumbnail edit form, AJAX renderer"""
-
-    def update_content(self, content, data):
-        geometry = ThumbnailGeometry()
-        geometry.x1 = int(self.request.params.get('selection.x1'))
-        geometry.y1 = int(self.request.params.get('selection.y1'))
-        geometry.x2 = int(self.request.params.get('selection.x2'))
-        geometry.y2 = int(self.request.params.get('selection.y2'))
-        IThumbnail(self.context).set_geometry('square', geometry)
-
-    def get_ajax_output(self, changes):
-        return {'status': 'success',
-                'smallbox': self.request.localizer.translate(self.successMessage),
-                'smallbox_status': 'success'}
-
-
-@viewlet_config(name='square-thumbnail.widgets-prefix', context=IImage, layer=IAdminLayer,
-                view=ImageSquareThumbnailEditForm, manager=IWidgetsPrefixViewletsManager)
-@template_config(template='templates/image-square-thumbnail.pt')
-class ImageSquareThumbnailViewletsPrefix(Viewlet):
-    """Image square thumbnail viewlets prefix"""
-
-
-#
-# Image panoramic thumbnail selection
-#
-
-@viewlet_config(name='image.thumb.pano.action', context=IImage, layer=IAdminLayer, view=IImageWidget,
-                manager=IContextActions, weight=32)
-class ImagePanoThumbnailAction(FileModifierAction):
-    """Panoramic thumbnail image selection"""
-
-    label = _("Select panoramic thumbnail...")
-    label_css_class = 'fa fa-fw-md fa-youtube-play'
-
-    url = 'pano-thumbnail.html'
-    modal_target = True
-
-
-@pagelet_config(name='pano-thumbnail.html', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
-@implementer(IFileModifierForm)
-class ImagePanoThumbnailEditForm(AdminDialogEditForm):
-    """Image panoramic thumbnail edit form"""
-
-    legend = _("Select panoramic thumbnail")
-    icon_css_class = 'fa fa-fw fa-youtube-play'
-    dialog_class = 'modal-large'
-
-    fields = field.Fields(Interface)
-    buttons = button.Buttons(IThumbnailFormButtons)
-    ajax_handler = 'pano-thumbnail.json'
-    edit_permission = None
+@pagelet_config(name='display-all-thumbnails.html', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
+class ImageThumbnailsDisplayForm(DialogDisplayForm):
+    """Image thumbnails display form"""
 
     @property
     def title(self):
-        return self.context.title or self.context.filename
-
-    def updateActions(self):
-        super(ImagePanoThumbnailEditForm, self).updateActions()
-        if 'crop' in self.actions:
-            self.actions['crop'].addClass('btn-primary')
-
-
-@view_config(name='pano-thumbnail.json', context=IImage, request_type=IPyAMSLayer,
-             permission=VIEW_PERMISSION, renderer='json', xhr=True)
-class ImagePanoThumbnailAJAXEditForm(AJAXEditForm, ImagePanoThumbnailEditForm):
-    """Image panoramic thumbnail edit form, AJAX renderer"""
-
-    def update_content(self, content, data):
-        geometry = ThumbnailGeometry()
-        geometry.x1 = int(self.request.params.get('selection.x1'))
-        geometry.y1 = int(self.request.params.get('selection.y1'))
-        geometry.x2 = int(self.request.params.get('selection.x2'))
-        geometry.y2 = int(self.request.params.get('selection.y2'))
-        IThumbnail(self.context).set_geometry('pano', geometry)
+        info = IFileInfo(self.context)
+        return info.title or info.filename
 
-    def get_ajax_output(self, changes):
-        return {'status': 'success',
-                'smallbox': self.request.localizer.translate(self.successMessage),
-                'smallbox_status': 'success'}
-
-
-@viewlet_config(name='pano-thumbnail.widgets-prefix', context=IImage, layer=IAdminLayer,
-                view=ImagePanoThumbnailEditForm, manager=IWidgetsPrefixViewletsManager)
-@template_config(template='templates/image-pano-thumbnail.pt')
-class ImagePanoThumbnailViewletsPrefix(Viewlet):
-    """Image panoramic thumbnail viewlets prefix"""
-
-
-#
-# Image responsive selections
-#
-
-class IResponsiveImageSelectionFormButtons(Interface):
-    """Responsive image selection form buttons"""
-
-    close = CloseButton(name='close', title=_("Close"))
-    select = button.Button(name='select', title=_("Select image"))
-
-
-@implementer(IFileModifierForm)
-class ResponsiveImageSelectionForm(AdminDialogEditForm):
-    """Base responsive image selection edit form"""
-
+    legend = _("Display all image thumbnails")
+    icon_css_class = 'fa fa-fw fa-th-large'
     dialog_class = 'modal-large'
 
     fields = field.Fields(Interface)
-    buttons = button.Buttons(IResponsiveImageSelectionFormButtons)
-    edit_permission = None
-
-    @property
-    def title(self):
-        return self.context.title or self.context.filename
-
-    def updateActions(self):
-        super(ResponsiveImageSelectionForm, self).updateActions()
-        if 'select' in self.actions:
-            self.actions['select'].addClass('btn-primary')
-
-
-class ResponsiveImageSelectionAJAXForm(AJAXEditForm):
-    """Base responsive image selection edit form, JSON renderer"""
-
-    def update_content(self, content, data):
-        geometry = ThumbnailGeometry()
-        geometry.x1 = int(self.request.params.get('selection.x1'))
-        geometry.y1 = int(self.request.params.get('selection.y1'))
-        geometry.x2 = int(self.request.params.get('selection.x2'))
-        geometry.y2 = int(self.request.params.get('selection.y2'))
-        IThumbnail(self.context).set_geometry(self.selection_size, geometry)
-
-    def get_ajax_output(self, changes):
-        return {'status': 'success',
-                'smallbox': self.request.localizer.translate(self.successMessage),
-                'smallbox_status': 'success'}
-
-
-@viewlet_config(name='responsive-image.selection.widgets-prefix', context=IResponsiveImage, layer=IAdminLayer,
-                view=ResponsiveImageSelectionForm, manager=IWidgetsPrefixViewletsManager)
-@template_config(template='templates/image-selection.pt')
-class ResponsiveImageSelectionViewletsPrefix(Viewlet):
-    """Responsive image selection viewlets prefix"""
-
-
-#
-# Extra-small devices selection
-#
-
-@viewlet_config(name='responsive-image.selection.xs.action', context=IResponsiveImage, layer=IAdminLayer,
-                view=IImageWidget, manager=IContextActions, weight=41)
-class ResponsiveImageXsSelectionAction(FileModifierAction):
-    """Responsive image XS selection"""
-
-    label = _("Select responsive XS image...")
-    label_css_class = 'fa fa-fw-md fa-mobile'
-
-    url = 'selection-xs.html'
-    modal_target = True
-
-
-@pagelet_config(name='selection-xs.html', context=IResponsiveImage, layer=IPyAMSLayer,
-                permission=VIEW_PERMISSION)
-class ResponsiveImageXsSelectionForm(ResponsiveImageSelectionForm):
-    """Responsive image XS selection edit form"""
-
-    legend = _("Select image for extra-small (XS) devices")
-    icon_css_class = 'fa fa-fw fa-mobile'
-
-    selection_size = 'xs'
-    ajax_handler = 'selection-xs.json'
-
-
-@view_config(name='selection-xs.json', context=IResponsiveImage, request_type=IPyAMSLayer,
-             permission=VIEW_PERMISSION, renderer='json', xhr=True)
-class ResponsiveImageXsSelectionAJAXEditForm(ResponsiveImageSelectionAJAXForm, ResponsiveImageXsSelectionForm):
-    """Responsive image XS selection edit form, JSON renderer"""
 
 
-#
-# Small devices selection
-#
-
-@viewlet_config(name='responsive-image.selection.sm.action', context=IResponsiveImage, layer=IAdminLayer,
-                view=IImageWidget, manager=IContextActions, weight=42)
-class ResponsiveImageSmSelectionAction(FileModifierAction):
-    """Responsive image SM selection"""
-
-    label = _("Select responsive SM image...")
-    label_css_class = 'fa fa-fw-md fa-tablet'
-
-    url = 'selection-sm.html'
-    modal_target = True
-
-
-@pagelet_config(name='selection-sm.html', context=IResponsiveImage, layer=IPyAMSLayer,
-                permission=VIEW_PERMISSION)
-class ResponsiveImageSmSelectionForm(ResponsiveImageSelectionForm):
-    """Responsive image SM selection edit form"""
-
-    legend = _("Select image for small (SM) devices")
-    icon_css_class = 'fa fa-fw fa-tablet'
+@viewlet_config(name='image-thumbnails', context=IImage, layer=IPyAMSLayer,
+                view=ImageThumbnailsDisplayForm, manager=IWidgetsPrefixViewletsManager)
+@template_config(template='templates/image-thumbnails.pt')
+class ImageThumbnailsViewletsPrefix(Viewlet):
+    """Image thumbnails viewlets prefix"""
 
-    selection_size = 'sm'
-    ajax_handler = 'selection-sm.json'
-
-
-@view_config(name='selection-sm.json', context=IResponsiveImage, request_type=IPyAMSLayer,
-             permission=VIEW_PERMISSION, renderer='json', xhr=True)
-class ResponsiveImageSmSelectionAJAXEditForm(ResponsiveImageSelectionAJAXForm, ResponsiveImageSmSelectionForm):
-    """Responsive image SM selection edit form, JSON renderer"""
-
-
-#
-# Medium devices selection
-#
-
-@viewlet_config(name='responsive-image.selection.md.action', context=IResponsiveImage, layer=IAdminLayer,
-                view=IImageWidget, manager=IContextActions, weight=43)
-class ResponsiveImageMdSelectionAction(FileModifierAction):
-    """Responsive image MD selection"""
-
-    label = _("Select responsive MD image...")
-    label_css_class = 'fa fa-fw-md fa-desktop'
-
-    url = 'selection-md.html'
-    modal_target = True
-
+    @property
+    def random(self):
+        return random.randint(0, sys.maxsize)
 
-@pagelet_config(name='selection-md.html', context=IResponsiveImage, layer=IPyAMSLayer,
-                permission=VIEW_PERMISSION)
-class ResponsiveImageMdSelectionForm(ResponsiveImageSelectionForm):
-    """Responsive image MD selection edit form"""
-
-    legend = _("Select image for medium (MD) devices")
-    icon_css_class = 'fa fa-fw fa-desktop'
-
-    selection_size = 'md'
-    ajax_handler = 'selection-md.json'
-
-
-@view_config(name='selection-md.json', context=IResponsiveImage, request_type=IPyAMSLayer,
-             permission=VIEW_PERMISSION, renderer='json', xhr=True)
-class ResponsiveImageMdSelectionAJAXEditForm(ResponsiveImageSelectionAJAXForm, ResponsiveImageMdSelectionForm):
-    """Responsive image MD selection edit form, JSON renderer"""
-
-
-#
-# Large devices selection
-#
-
-@viewlet_config(name='responsive-image.selection.lg.action', context=IResponsiveImage, layer=IAdminLayer,
-                view=IImageWidget, manager=IContextActions, weight=44)
-class ResponsiveImageLgSelectionAction(FileModifierAction):
-    """Responsive image LG selection"""
-
-    label = _("Select responsive LG image...")
-    label_css_class = 'fa fa-fw-md fa-television'
-
-    url = 'selection-lg.html'
-    modal_target = True
-
-
-@pagelet_config(name='selection-lg.html', context=IResponsiveImage, layer=IPyAMSLayer,
-                permission=VIEW_PERMISSION)
-class ResponsiveImageLgSelectionForm(ResponsiveImageSelectionForm):
-    """Responsive image LG selection edit form"""
-
-    legend = _("Select image for large (LG) devices")
-    icon_css_class = 'fa fa-fw fa-television'
-
-    selection_size = 'lg'
-    ajax_handler = 'selection-lg.json'
-
-
-@view_config(name='selection-lg.json', context=IResponsiveImage, request_type=IPyAMSLayer,
-             permission=VIEW_PERMISSION, renderer='json', xhr=True)
-class ResponsiveImageLgSelectionAJAXEditForm(ResponsiveImageSelectionAJAXForm, ResponsiveImageLgSelectionForm):
-    """Responsive image LG selection edit form, JSON renderer"""
+    def get_thumbnails(self):
+        registry = self.request.registry
+        translate = self.request.localizer.translate
+        thumbnailers = OrderedDict()
+        adapters = sorted(registry.getAdapters((self.context, ), IThumbnailer),
+                          key=lambda x: x[1].weight)
+        for name, adapter in adapters:
+            thumbnailers.setdefault(translate(adapter.section), []).append({'name': name,
+                                                                            'label': translate(adapter.label)})
+        return thumbnailers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_file/zmi/templates/image-thumbnails.pt	Mon Sep 11 13:30:46 2017 +0200
@@ -0,0 +1,20 @@
+<tal:loop repeat="thumbnailer view.get_thumbnails().items()" i18n:domain="pyams_file">
+	<tal:var define="section thumbnailer[0]; adapters thumbnailer[1];">
+		<fieldset class="no-padding">
+			<legend class="inner switcher" data-ams-switcher-state="open" tal:content="section"></legend>
+			<tal:loop repeat="adapter adapters">
+				<div class="pull-left margin-5">
+					<p class="small"
+					   tal:condition="adapter['label'] != section"
+					   tal:content="adapter['label']">Label</p>
+					<div class="bordered">
+						<img tal:define="base extension:absolute_url(context);
+										 name adapter['name'];
+										 thname '{0}:'.format(name) if name else '';"
+							 tal:attributes="src string:${base}/++thumb++${thname}200x128.jpeg?_=${view.random}" />
+					</div>
+				</div>
+			</tal:loop>
+		</fieldset>
+	</tal:var>
+</tal:loop>