From dfe85e678c6c9e383ea2641a0b5a1a117a5942c3 Mon Sep 17 00:00:00 2001 From: Adam McNevin Date: Fri, 29 Mar 2024 12:05:31 -1000 Subject: [PATCH 1/9] Temp remove max selection size and allow add props --- castle/cms/static/patterns/relateditems.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/castle/cms/static/patterns/relateditems.js b/castle/cms/static/patterns/relateditems.js index f13c0c27c..893de178f 100644 --- a/castle/cms/static/patterns/relateditems.js +++ b/castle/cms/static/patterns/relateditems.js @@ -138,13 +138,15 @@ define([ valid.push(item); } }); - if(that.props.maximumSelectionSize === 1){ - if(valid.length > 0){ - that.state.selected = [valid[0]]; - } - }else{ - that.state.selected = that.state.selected.concat(valid); - } + // TODO: Where is is this prop set? + // if(that.props.maximumSelectionSize === 1){ + // if(valid.length > 0){ + // that.state.selected = [valid[0]]; + // } + // }else{ + // that.state.selected = that.state.selected.concat(valid); + // } + that.state.selected = that.state.selected.concat(valid); that.selectionUpdated(); }, @@ -361,7 +363,9 @@ define([ className: 'plone-btn plone-btn-default castle-btn-browse', onClick: this.browseClicked }, 'Browse')]; - if(this.props.allowAdd){ + // TODO: WHere is this prop set? + // if(this.props.allowAdd){ + if(!this.props.allowAdd){ buttons.push(D.button({ className: 'plone-btn plone-btn-default castle-btn-add', onClick: this.addClicked From 923fad012b9868a1133fa1d875504176b5d0a95f Mon Sep 17 00:00:00 2001 From: Adam McNevin Date: Tue, 2 Apr 2024 12:49:25 -0600 Subject: [PATCH 2/9] Adjust settings for 'related items' selection --- castle/cms/static/patterns/imagewidget.js | 6 +++--- castle/cms/static/patterns/relateditems.js | 20 ++++++++------------ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/castle/cms/static/patterns/imagewidget.js b/castle/cms/static/patterns/imagewidget.js index 6fafe95f3..8578e881e 100644 --- a/castle/cms/static/patterns/imagewidget.js +++ b/castle/cms/static/patterns/imagewidget.js @@ -175,9 +175,9 @@ define([ } var pattern = new RelatedItems($re, cutils.extend(options, { vocabularyUrl: $('body').attr('data-portal-url') + '/@@getVocabulary?name=plone.app.vocabularies.Catalog', - maximumSelectionSize: 1, - multiple: false, - allowAdd: false, + maximumSelectionSize: -1, // Set to -1 to remove selection limit + multiple: true, // Allows for multiple images to be selected at once via checkboxes + allowAdd: false, // Allows all dexterity types to be added as related items noItemsSelectedText: 'No image selected', initial_selection: selection, selectableTypes: ['Image'], diff --git a/castle/cms/static/patterns/relateditems.js b/castle/cms/static/patterns/relateditems.js index 893de178f..f13c0c27c 100644 --- a/castle/cms/static/patterns/relateditems.js +++ b/castle/cms/static/patterns/relateditems.js @@ -138,15 +138,13 @@ define([ valid.push(item); } }); - // TODO: Where is is this prop set? - // if(that.props.maximumSelectionSize === 1){ - // if(valid.length > 0){ - // that.state.selected = [valid[0]]; - // } - // }else{ - // that.state.selected = that.state.selected.concat(valid); - // } - that.state.selected = that.state.selected.concat(valid); + if(that.props.maximumSelectionSize === 1){ + if(valid.length > 0){ + that.state.selected = [valid[0]]; + } + }else{ + that.state.selected = that.state.selected.concat(valid); + } that.selectionUpdated(); }, @@ -363,9 +361,7 @@ define([ className: 'plone-btn plone-btn-default castle-btn-browse', onClick: this.browseClicked }, 'Browse')]; - // TODO: WHere is this prop set? - // if(this.props.allowAdd){ - if(!this.props.allowAdd){ + if(this.props.allowAdd){ buttons.push(D.button({ className: 'plone-btn plone-btn-default castle-btn-add', onClick: this.addClicked From a787f81129fff976cacf80a344d7de37ee873c56 Mon Sep 17 00:00:00 2001 From: Adam McNevin Date: Tue, 2 Apr 2024 14:57:42 -0600 Subject: [PATCH 3/9] Adjust behavior for multiple related items --- castle/cms/static/patterns/relateditems.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/castle/cms/static/patterns/relateditems.js b/castle/cms/static/patterns/relateditems.js index f13c0c27c..af7dd4c1d 100644 --- a/castle/cms/static/patterns/relateditems.js +++ b/castle/cms/static/patterns/relateditems.js @@ -74,7 +74,9 @@ define([ }, selectionUpdated: function(){ + console.log('=== selection updated ===') this.props.updateValue(this.state.selected); + console.log(this.state) this.load(); }, @@ -133,11 +135,16 @@ define([ addSelection: function(selection){ var that = this; var valid = []; + console.log('=== selection ===') selection.forEach(function(item){ + console.log(that.state.selected) + if(that.state.selected.indexOf(item) === -1){ valid.push(item); } }); + console.log('=== selectionsize ===') + console.log(that.props.maximumSelectionSize) if(that.props.maximumSelectionSize === 1){ if(valid.length > 0){ that.state.selected = [valid[0]]; @@ -168,6 +175,8 @@ define([ if(that.state.selected.length > 60){ method = 'POST'; } + console.log('=== props ===') + console.log(that.props) $.ajax({ url: that.props.vocabularyUrl, dataType: 'JSON', @@ -218,8 +227,15 @@ define([ }, onSelectItems: function(items){ var uids = []; + // XXX: With 'multiple' enabled, the existing lead image disappears from the + // 'selected' array but remains in the 'items' array when new images are selected. + // This is probably a bug + that.state.items = [] // For now, reset 'items' prop items.forEach(function(item){ uids.push(item.UID); + // XXX: Items are not normally added to the 'items' prop when selecting multiple images + // For now, add them manually + that.state.items.push(item) }); that.addSelection(uids); }, @@ -361,6 +377,7 @@ define([ className: 'plone-btn plone-btn-default castle-btn-browse', onClick: this.browseClicked }, 'Browse')]; + if(this.props.allowAdd){ buttons.push(D.button({ className: 'plone-btn plone-btn-default castle-btn-add', From 848728351a5e38bc88af414e5a4b28826c4d167d Mon Sep 17 00:00:00 2001 From: Adam McNevin Date: Fri, 5 Apr 2024 14:16:03 -0600 Subject: [PATCH 4/9] Working on 'LeadImage' schema --- castle/cms/behaviors/leadimage.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/castle/cms/behaviors/leadimage.py b/castle/cms/behaviors/leadimage.py index 6c631cb8f..72454f780 100644 --- a/castle/cms/behaviors/leadimage.py +++ b/castle/cms/behaviors/leadimage.py @@ -6,17 +6,37 @@ from zope.component import adapter from zope.interface import implementer from zope.interface import provider +from zope.schema.interfaces import IFromUnicode import zope.schema as schema - -@provider(IFormFieldProvider) +# TODO: Currently attempting to convert the image schema +# to a list of namedBlobImages. +# The list requires the IFromUnicode interface to function, +# though it causes the 'multimedia' tab to disappear in the modal. +# These two interfaces may not be compatible together + +def multi_provider(*interfaces): + def decorator(cls): + for interface in interfaces: + provider(interface)(cls) + return cls + return decorator + +# @provider(IFormFieldProvider) +@multi_provider(IFormFieldProvider, IFromUnicode) class IRequiredLeadImage(ILeadImage): - image = namedfile.NamedBlobImage( - title=_(u'label_leadimage', default=u'Lead Image'), + # image = namedfile.NamedBlobImage( + # title=_(u'label_leadimage', default=u'Lead Image'), + # description=_(u'help_leadimage', default=u''), + # required=True + # ) + image = schema.List( + title=_(u'label_leadimage', default=u'Lead Image(s)'), description=_(u'help_leadimage', default=u''), - required=True + required=True, + value_type=namedfile.NamedBlobImage() ) image_caption = schema.TextLine( From 6c59aad7d881bbd50bb4a21bb59fa9f1a4c39987 Mon Sep 17 00:00:00 2001 From: Adam McNevin Date: Tue, 16 Apr 2024 06:22:56 -1000 Subject: [PATCH 5/9] Add proper image widget to schema --- castle/cms/behaviors/leadimage.py | 100 +++++++++++++++------ castle/cms/static/patterns/imagewidget.js | 6 +- castle/cms/static/patterns/relateditems.js | 17 ---- 3 files changed, 76 insertions(+), 47 deletions(-) diff --git a/castle/cms/behaviors/leadimage.py b/castle/cms/behaviors/leadimage.py index 72454f780..3f9b456a3 100644 --- a/castle/cms/behaviors/leadimage.py +++ b/castle/cms/behaviors/leadimage.py @@ -6,37 +6,83 @@ from zope.component import adapter from zope.interface import implementer from zope.interface import provider -from zope.schema.interfaces import IFromUnicode +from plone.autoform import directives as form +from plone.supermodel import model +from plone.supermodel.directives import fieldset +from z3c.relationfield.schema import RelationChoice +from z3c.relationfield.schema import RelationList +from zope.interface import provider +from plone.app.z3cform.widget import RelatedItemsWidget as BaseRelatedItemsWidget # noqa + import zope.schema as schema -# TODO: Currently attempting to convert the image schema -# to a list of namedBlobImages. -# The list requires the IFromUnicode interface to function, -# though it causes the 'multimedia' tab to disappear in the modal. -# These two interfaces may not be compatible together - -def multi_provider(*interfaces): - def decorator(cls): - for interface in interfaces: - provider(interface)(cls) - return cls - return decorator - -# @provider(IFormFieldProvider) -@multi_provider(IFormFieldProvider, IFromUnicode) -class IRequiredLeadImage(ILeadImage): - - # image = namedfile.NamedBlobImage( - # title=_(u'label_leadimage', default=u'Lead Image'), - # description=_(u'help_leadimage', default=u''), - # required=True - # ) - image = schema.List( - title=_(u'label_leadimage', default=u'Lead Image(s)'), + +# Needed to prevent circular dependency +class RelatedItemsWidget(BaseRelatedItemsWidget): + initialPath = None + base_criteria = [] + + def _base_args(self): + args = super(RelatedItemsWidget, self)._base_args() + args['pattern_options']['width'] = '' + args['pattern_options']['initialPath'] = self.initialPath + base_criteria = self.base_criteria[:] + args['pattern_options']['baseCriteria'] = base_criteria + return args + + +class ImageRelatedItemWidget(RelatedItemsWidget): + + initialPath = '/image-repository' + + def _base_args(self): + args = super(ImageRelatedItemWidget, self)._base_args() + args['pattern_options']['maximumSelectionSize'] = 1 + args['pattern_options']['selectableTypes'] = ['Image'] + args['pattern_options']['baseCriteria'].append({ + 'i': 'portal_type', + 'o': 'plone.app.querystring.operation.selection.any', + 'v': ['Image', 'Folder'] + }) + return args + + +@provider(IFormFieldProvider) +class IRequiredLeadImage(model.Schema): + + image = namedfile.NamedBlobImage( + title=_(u'label_leadimage', default=u'Lead Image'), description=_(u'help_leadimage', default=u''), - required=True, - value_type=namedfile.NamedBlobImage() + required=True + ) + + alternate_image = RelationList( + title=_(u'label_related_items', default=u'Alternate Lead Image'), + description=u'An optional secondary image than can be displayed in a Feature tile', + default=[], + value_type=RelationChoice( + title=u'Related', + vocabulary='plone.app.vocabularies.Catalog' + ), + required=False + ) + + form.widget( + 'alternate_image', + ImageRelatedItemWidget, + vocabulary='plone.app.vocabularies.Catalog', + pattern_options={ + 'recentlyUsed': True, # Just turn on. Config in plone.app.widgets. + } + ) + + fieldset( + 'multimedia', + label=_(u'Multimedia'), + fields=['image', + 'alternate_image', + 'image_caption'] ) image_caption = schema.TextLine( diff --git a/castle/cms/static/patterns/imagewidget.js b/castle/cms/static/patterns/imagewidget.js index 8578e881e..6fafe95f3 100644 --- a/castle/cms/static/patterns/imagewidget.js +++ b/castle/cms/static/patterns/imagewidget.js @@ -175,9 +175,9 @@ define([ } var pattern = new RelatedItems($re, cutils.extend(options, { vocabularyUrl: $('body').attr('data-portal-url') + '/@@getVocabulary?name=plone.app.vocabularies.Catalog', - maximumSelectionSize: -1, // Set to -1 to remove selection limit - multiple: true, // Allows for multiple images to be selected at once via checkboxes - allowAdd: false, // Allows all dexterity types to be added as related items + maximumSelectionSize: 1, + multiple: false, + allowAdd: false, noItemsSelectedText: 'No image selected', initial_selection: selection, selectableTypes: ['Image'], diff --git a/castle/cms/static/patterns/relateditems.js b/castle/cms/static/patterns/relateditems.js index af7dd4c1d..f13c0c27c 100644 --- a/castle/cms/static/patterns/relateditems.js +++ b/castle/cms/static/patterns/relateditems.js @@ -74,9 +74,7 @@ define([ }, selectionUpdated: function(){ - console.log('=== selection updated ===') this.props.updateValue(this.state.selected); - console.log(this.state) this.load(); }, @@ -135,16 +133,11 @@ define([ addSelection: function(selection){ var that = this; var valid = []; - console.log('=== selection ===') selection.forEach(function(item){ - console.log(that.state.selected) - if(that.state.selected.indexOf(item) === -1){ valid.push(item); } }); - console.log('=== selectionsize ===') - console.log(that.props.maximumSelectionSize) if(that.props.maximumSelectionSize === 1){ if(valid.length > 0){ that.state.selected = [valid[0]]; @@ -175,8 +168,6 @@ define([ if(that.state.selected.length > 60){ method = 'POST'; } - console.log('=== props ===') - console.log(that.props) $.ajax({ url: that.props.vocabularyUrl, dataType: 'JSON', @@ -227,15 +218,8 @@ define([ }, onSelectItems: function(items){ var uids = []; - // XXX: With 'multiple' enabled, the existing lead image disappears from the - // 'selected' array but remains in the 'items' array when new images are selected. - // This is probably a bug - that.state.items = [] // For now, reset 'items' prop items.forEach(function(item){ uids.push(item.UID); - // XXX: Items are not normally added to the 'items' prop when selecting multiple images - // For now, add them manually - that.state.items.push(item) }); that.addSelection(uids); }, @@ -377,7 +361,6 @@ define([ className: 'plone-btn plone-btn-default castle-btn-browse', onClick: this.browseClicked }, 'Browse')]; - if(this.props.allowAdd){ buttons.push(D.button({ className: 'plone-btn plone-btn-default castle-btn-add', From bfd530e746c04c4a30e99c50e704aebbd8853699 Mon Sep 17 00:00:00 2001 From: Adam McNevin Date: Tue, 16 Apr 2024 10:00:35 -1000 Subject: [PATCH 6/9] Use duplicate React 'imagewidget' rather than 'RelationList' widget --- castle/cms/behaviors/leadimage.py | 64 ++++--------------------------- castle/cms/tiles/content.py | 6 +++ castle/cms/widgets.py | 6 +++ castle/cms/widgets.zcml | 1 + 4 files changed, 20 insertions(+), 57 deletions(-) diff --git a/castle/cms/behaviors/leadimage.py b/castle/cms/behaviors/leadimage.py index 3f9b456a3..7f4895a30 100644 --- a/castle/cms/behaviors/leadimage.py +++ b/castle/cms/behaviors/leadimage.py @@ -6,48 +6,12 @@ from zope.component import adapter from zope.interface import implementer from zope.interface import provider -from plone.autoform import directives as form from plone.supermodel import model from plone.supermodel.directives import fieldset -from z3c.relationfield.schema import RelationChoice -from z3c.relationfield.schema import RelationList -from zope.interface import provider -from plone.app.z3cform.widget import RelatedItemsWidget as BaseRelatedItemsWidget # noqa - import zope.schema as schema -# Needed to prevent circular dependency -class RelatedItemsWidget(BaseRelatedItemsWidget): - initialPath = None - base_criteria = [] - - def _base_args(self): - args = super(RelatedItemsWidget, self)._base_args() - args['pattern_options']['width'] = '' - args['pattern_options']['initialPath'] = self.initialPath - base_criteria = self.base_criteria[:] - args['pattern_options']['baseCriteria'] = base_criteria - return args - - -class ImageRelatedItemWidget(RelatedItemsWidget): - - initialPath = '/image-repository' - - def _base_args(self): - args = super(ImageRelatedItemWidget, self)._base_args() - args['pattern_options']['maximumSelectionSize'] = 1 - args['pattern_options']['selectableTypes'] = ['Image'] - args['pattern_options']['baseCriteria'].append({ - 'i': 'portal_type', - 'o': 'plone.app.querystring.operation.selection.any', - 'v': ['Image', 'Folder'] - }) - return args - - @provider(IFormFieldProvider) class IRequiredLeadImage(model.Schema): @@ -57,24 +21,16 @@ class IRequiredLeadImage(model.Schema): required=True ) - alternate_image = RelationList( - title=_(u'label_related_items', default=u'Alternate Lead Image'), - description=u'An optional secondary image than can be displayed in a Feature tile', - default=[], - value_type=RelationChoice( - title=u'Related', - vocabulary='plone.app.vocabularies.Catalog' - ), + alternate_image = namedfile.NamedBlobImage( + title=_(u'label_alternateleadimage', default=u'Alternate Lead Image'), + description=_(u'label_alternateleadimage', default=u'An alternate image that can be displayed in a Feature tile.'), required=False ) - form.widget( - 'alternate_image', - ImageRelatedItemWidget, - vocabulary='plone.app.vocabularies.Catalog', - pattern_options={ - 'recentlyUsed': True, # Just turn on. Config in plone.app.widgets. - } + image_caption = schema.TextLine( + title=_(u'label_leadimage_caption', default=u'Lead Image Caption'), + description=_(u'help_leadimage_caption', default=u''), + required=False, ) fieldset( @@ -85,12 +41,6 @@ class IRequiredLeadImage(model.Schema): 'image_caption'] ) - image_caption = schema.TextLine( - title=_(u'label_leadimage_caption', default=u'Lead Image Caption'), - description=_(u'help_leadimage_caption', default=u''), - required=False, - ) - @implementer(IRequiredLeadImage) @adapter(IDexterityContent) diff --git a/castle/cms/tiles/content.py b/castle/cms/tiles/content.py index a63c0e992..793762e1f 100644 --- a/castle/cms/tiles/content.py +++ b/castle/cms/tiles/content.py @@ -57,6 +57,12 @@ def validate_content(data): required=False, ) + alternate_image = schema.Bool( + title=u'Use Alternate Image', + description=u'Displays the alternate image, if any', + required=False, + ) + more_text = schema.TextLine( title=u'More text', default=u'', diff --git a/castle/cms/widgets.py b/castle/cms/widgets.py index deede71c1..166885c3c 100644 --- a/castle/cms/widgets.py +++ b/castle/cms/widgets.py @@ -633,6 +633,12 @@ def RequiredLeadImageFocalNamedImageFieldWidget(field, request): widget = z3c.form.widget.FieldWidget(field, FocalNamedImageWidget(request)) return widget +@adapter(getSpecification(IRequiredLeadImage['alternate_image']), ICastleLayer) +@implementer(IFieldWidget) +def AlternateLeadImageFocalNamedImageFieldWidget(field, request): + widget = z3c.form.widget.FieldWidget(field, FocalNamedImageWidget(request)) + return widget + @adapter(INamedImageField, ICastleLayer) @implementer(IFieldWidget) diff --git a/castle/cms/widgets.zcml b/castle/cms/widgets.zcml index 7d0dc8242..d929681d6 100644 --- a/castle/cms/widgets.zcml +++ b/castle/cms/widgets.zcml @@ -31,6 +31,7 @@ + Date: Thu, 18 Apr 2024 05:39:17 -1000 Subject: [PATCH 7/9] Retrieve alternate image for feature tile if selected --- castle/cms/browser/utils.py | 15 +++++++++++++-- castle/cms/tiles/content.py | 6 ------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/castle/cms/browser/utils.py b/castle/cms/browser/utils.py index ed36b5f37..13363fb78 100644 --- a/castle/cms/browser/utils.py +++ b/castle/cms/browser/utils.py @@ -38,6 +38,7 @@ from zope.interface import implements from zope.viewlet.interfaces import IViewlet from zope.viewlet.interfaces import IViewletManager +from plone.app.uuid.utils import uuidToCatalogBrain import json @@ -368,9 +369,19 @@ def focal_image_tag( attributes=None, focal=None, attempt_overlay=False, + use_alternate_image=False ): - # read https://github.com/jonom/jquery-focuspoint on how to calc - image_info = utils.get_image_info(brain) + if use_alternate_image and brain.alternate_image: + # we have to manually retrieve the alternate 'Image' object here + # so we can get the proper url for 'src' + uid = brain.alternate_image.reference + brain = uuidToCatalogBrain(uid) + image_info = utils.get_image_info(brain) + brain = brain.getObject() + + else: + # read https://github.com/jonom/jquery-focuspoint on how to calc + image_info = utils.get_image_info(brain) image_attributes = {} if attributes is not None: diff --git a/castle/cms/tiles/content.py b/castle/cms/tiles/content.py index 793762e1f..a63c0e992 100644 --- a/castle/cms/tiles/content.py +++ b/castle/cms/tiles/content.py @@ -57,12 +57,6 @@ def validate_content(data): required=False, ) - alternate_image = schema.Bool( - title=u'Use Alternate Image', - description=u'Displays the alternate image, if any', - required=False, - ) - more_text = schema.TextLine( title=u'More text', default=u'', From a143343fc28adef69bdd90a4cd95c1f66cd3d1e0 Mon Sep 17 00:00:00 2001 From: Adam McNevin Date: Thu, 18 Apr 2024 06:38:33 -1000 Subject: [PATCH 8/9] Change alternate lead image description --- castle/cms/behaviors/leadimage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/castle/cms/behaviors/leadimage.py b/castle/cms/behaviors/leadimage.py index 7f4895a30..91851e282 100644 --- a/castle/cms/behaviors/leadimage.py +++ b/castle/cms/behaviors/leadimage.py @@ -23,7 +23,7 @@ class IRequiredLeadImage(model.Schema): alternate_image = namedfile.NamedBlobImage( title=_(u'label_alternateleadimage', default=u'Alternate Lead Image'), - description=_(u'label_alternateleadimage', default=u'An alternate image that can be displayed in a Feature tile.'), + description=_(u'label_alternateleadimage', default=u'An image that can be displayed in tiles that support alternate images.'), required=False ) From 73ed9f66852123765a68e04fcbf8dd98ee38f1f3 Mon Sep 17 00:00:00 2001 From: Adam McNevin Date: Thu, 18 Apr 2024 06:40:29 -1000 Subject: [PATCH 9/9] Change alternate lead image description again --- castle/cms/behaviors/leadimage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/castle/cms/behaviors/leadimage.py b/castle/cms/behaviors/leadimage.py index 91851e282..e5d8e4a8f 100644 --- a/castle/cms/behaviors/leadimage.py +++ b/castle/cms/behaviors/leadimage.py @@ -23,7 +23,7 @@ class IRequiredLeadImage(model.Schema): alternate_image = namedfile.NamedBlobImage( title=_(u'label_alternateleadimage', default=u'Alternate Lead Image'), - description=_(u'label_alternateleadimage', default=u'An image that can be displayed in tiles that support alternate images.'), + description=_(u'label_alternateleadimage', default=u'An image to be displayed in tiles that support alternate lead images.'), required=False )