diff --git a/src/gutenberg-blocks/class-tainacan-gutenberg-block.php b/src/gutenberg-blocks/class-tainacan-gutenberg-block.php index 0f0e1dab4..42c70c443 100644 --- a/src/gutenberg-blocks/class-tainacan-gutenberg-block.php +++ b/src/gutenberg-blocks/class-tainacan-gutenberg-block.php @@ -18,6 +18,7 @@ function tainacan_blocks_add_gutenberg_blocks_actions() { add_action('init', 'tainacan_blocks_register_tainacan_dynamic_items_list'); add_action('init', 'tainacan_blocks_register_tainacan_carousel_items_list'); add_action('init', 'tainacan_blocks_register_tainacan_collections_list'); + add_action('init', 'tainacan_blocks_register_tainacan_carousel_collections_list'); add_action('init', 'tainacan_blocks_register_tainacan_facets_list'); add_action('init', 'tainacan_blocks_add_plugin_settings'); @@ -197,6 +198,36 @@ function tainacan_blocks_register_tainacan_collections_list(){ } } +function tainacan_blocks_register_tainacan_carousel_collections_list(){ + global $TAINACAN_BASE_URL; + + wp_enqueue_script( + 'carousel-collections-list-theme', + $TAINACAN_BASE_URL . '/assets/gutenberg_carousel_collections_list_theme-components.js', + array('wp-components') + ); + + wp_register_script( + 'carousel-collections-list', + $TAINACAN_BASE_URL . '/assets/gutenberg_carousel_collections_list-components.js', + array('wp-blocks', 'wp-element', 'wp-components', 'wp-editor') + ); + + wp_register_style( + 'carousel-collections-list', + $TAINACAN_BASE_URL . '/assets/css/tainacan-gutenberg-block-carousel-collections-list.css', + array('wp-edit-blocks') + ); + + if (function_exists('register_block_type')) { + register_block_type( 'tainacan/carousel-collections-list', array( + 'editor_script' => 'carousel-collections-list', + 'style' => 'carousel-collections-list', + 'script' => 'carousel-collections-list-theme' + ) ); + } +} + function tainacan_blocks_get_plugin_js_settings(){ global $TAINACAN_BASE_URL; @@ -221,5 +252,6 @@ function tainacan_blocks_add_plugin_settings() { wp_localize_script( 'dynamic-items-list', 'tainacan_plugin', $settings ); wp_localize_script( 'carousel-items-list', 'tainacan_plugin', $settings ); wp_localize_script( 'collections-list', 'tainacan_plugin', $settings ); + wp_localize_script( 'carousel-collections-list', 'tainacan_plugin', $settings ); wp_localize_script( 'facets-list', 'tainacan_plugin', $settings ); } diff --git a/src/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-list-theme.js b/src/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-list-theme.js new file mode 100644 index 000000000..5d6a56e91 --- /dev/null +++ b/src/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-list-theme.js @@ -0,0 +1,73 @@ +import Vue from 'vue'; +import CarouselCollectionsListTheme from './carousel-collections-list-theme.vue'; + +// This is rendered on the theme side. +document.addEventListener("DOMContentLoaded", () => { + + // Configure Vue logic before passing it to constructor: + let vueOptions = { + data: { + collectionId: '', + selectedItems: [], + maxItemsNumber: 12, + arrowsPosition: 'around', + autoPlay: false, + autoPlaySpeed: 3, + loopSlides: false, + hideTitle: true, + tainacanApiRoot: '', + tainacanBaseUrl: '', + className: '', + extraParams: {} + }, + render(h){ + return h(CarouselCollectionsListTheme, { + props: { + collectionId: this.collectionId, + selectedItems: this.selectedItems, + maxItemsNumber: this.maxItemsNumber, + arrowsPosition: this.arrowsPosition, + autoPlay: this.autoPlay, + autoPlaySpeed: this.autoPlaySpeed, + loopSlides: this.loopSlides, + hideTitle: this.hideTitle, + tainacanApiRoot: this.tainacanApiRoot, + tainacanBaseUrl: this.tainacanBaseUrl, + className: this.className, + extraParams: this.extraParams + } + }); + }, + beforeMount () { + this.className = this.$el.attributes.class != undefined ? this.$el.attributes.class.value : undefined; + this.selectedItems = this.$el.attributes['selected-collections'] != undefined ? JSON.parse(this.$el.attributes['selected-collections'].value) : undefined; + this.collectionId = this.$el.attributes['collection-id'] != undefined ? this.$el.attributes['collection-id'].value : undefined; + this.maxItemsNumber = this.$el.attributes['max-collections-number'] != undefined ? this.$el.attributes['max-collections-number'].value : undefined; + this.arrowsPosition = this.$el.attributes['arrows-position'] != undefined ? this.$el.attributes['arrows-position'].value : undefined; + this.autoPlay = this.$el.attributes['auto-play'] != undefined ? this.$el.attributes['auto-play'].value == 'true' : false; + this.autoPlaySpeed = this.$el.attributes['auto-play-speed'] != undefined ? this.$el.attributes['auto-play-speed'].value : 3; + this.loopSlides = this.$el.attributes['loop-slides'] != undefined ? this.$el.attributes['loop-slides'].value == 'true' : false; + this.hideTitle = this.$el.attributes['hide-title'] != undefined ? this.$el.attributes['hide-title'].value == 'true' : false; + this.tainacanApiRoot = this.$el.attributes['tainacan-api-root'] != undefined ? this.$el.attributes['tainacan-api-root'].value : undefined; + this.tainacanBaseUrl = this.$el.attributes['tainacan-base-url'] != undefined ? this.$el.attributes['tainacan-base-url'].value : undefined; + this.extraParams = this.$el.attributes['extra-params'] != undefined ? JSON.parse(this.$el.attributes['extra-params'].value) : undefined; + }, + methods: { + __(text, domain) { + return wp.i18n.__(text, domain); + } + } + }; + + // Gets all divs with content created by our block; + let blocks = document.getElementsByClassName('wp-block-tainacan-carousel-collections-list'); + + if (blocks) { + let blockIds = Object.values(blocks).map((block) => block.id); + + // Creates a new Vue Instance to manage each block isolatelly + for (let blockId of blockIds) { + new Vue( Object.assign({ el: '#' + blockId }, jQuery.extend(true, {}, vueOptions)) ); + } + } +}); \ No newline at end of file diff --git a/src/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-list-theme.vue b/src/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-list-theme.vue new file mode 100644 index 000000000..9f33d4ab1 --- /dev/null +++ b/src/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-list-theme.vue @@ -0,0 +1,221 @@ + + + + + diff --git a/src/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-list.scss b/src/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-list.scss new file mode 100644 index 000000000..743e049ec --- /dev/null +++ b/src/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-list.scss @@ -0,0 +1,223 @@ +@import '../../gutenberg-blocks-style.scss'; + +.wp-block-tainacan-carousel-collections-list { + margin: 2rem 0px; + + // Spinner + .spinner-container { + min-height: 56px; + padding: 1rem; + display: flex; + justify-content: center; + align-items: center; + color: #555758; + } + + // Skeleton loading + @-webkit-keyframes skeleton-animation { + 0%{opacity: 1.0} + 50%{opacity: 0.2} + 100%{opacity: 1.0} + } + @-moz-keyframes skeleton-animation { + 0%{opacity: 1.0} + 50%{opacity: 0.2} + 100%{opacity: 1.0} + } + @-o-keyframes skeleton-animation { + 0%{opacity: 1.0} + 50%{opacity: 0.2} + 100%{opacity: 1.0} + } + @keyframes skeleton-animation { + 0%{opacity: 1.0} + 50%{opacity: 0.2} + 100%{opacity: 1.0} + } + .skeleton { + border-radius: 2px; + background: #f2f2f2; + + -webkit-animation: skeleton-animation 1.8s ease infinite; + -moz-animation: skeleton-animation 1.8s ease infinite; + -o-animation: skeleton-animation 1.8s ease infinite; + animation: skeleton-animation 1.8s ease infinite; + } + + // Tainacan Carousel + .tainacan-carousel { + position: relative; + width: calc(100% + 50px); + left: -50px; + + .swiper-container { + margin: 0 50px; + + a>span, + a:hover>span { + color: black; + font-weight: bold; + text-decoration: none; + padding: 8px 16px; + display: block; + line-height: 1.2rem; + } + a:hover { + text-decoration: none; + } + } + } + + .preview-warning { + width: 100%; + font-size: 0.875rem; + font-style: italic; + color: #898d8f; + text-align: center; + margin: 4px auto; + } + + // Next and previous buttons + .swiper-button-prev, .swiper-button-next { + top: initial; + bottom: calc(50% + 10px); + background: none; + border: none; + width: 42px; + height: 42px; + padding: 0; + margin: 0 -4px; + + svg { + fill: #298596; + } + } + + // Carousel placeholder on editor side ---------------------------------------------------- + .items-list-edit-container, + .tainacan-carousel { + position: relative; + + & .skeleton { + min-height: 150px; + max-height: 150px; + } + + &.has-arrows-none .swiper-button-prev, + &.has-arrows-none .swiper-button-next { + display: none; + } + &.has-arrows-left .swiper-button-next { + left: 10px; + right: auto; + bottom: calc(50% + 36px); + } + &.has-arrows-right .swiper-button-prev { + right: 10px; + left: auto; + } + &.has-arrows-right .swiper-button-next { + bottom: calc(50% + 36px); + } + } + ul.items-list-edit { + display: flex; + align-items: flex-start; + overflow-x: scroll; + list-style: none; + margin: 0 36px; + + li.collection-list-item { + position: relative; + display: block; + margin: 16px; + width: calc(14.286% - 32px); + min-width: calc(14.286% - 32px); + + a { + color: #454647; + font-weight: bold; + line-height: normal; + } + + img { + height: auto; + + padding: 0px; + margin-bottom: 0.5rem; + } + + &:hover a { + color: #454647; + text-decoration: none; + } + + button { + position: absolute !important; + background-color: rgba(255, 255, 255, 0.75); + color: #454647; + padding: 2px; + margin-left: 5px; + min-width: 14px; + visibility: hidden; + position: relative; + opacity: 0; + right: -14px; + top: 0px; + justify-content: center; + z-index: 999; + } + + &:hover button { + visibility: visible; + background-color: rgba(255, 255, 255, 1) !important; + opacity: 1; + right: -8px; + top: -8px; + border: 1px solid #cbcbcb; + border-radius: 12px; + transition: opacity linear 0.15s, right linear 0.15s; + } + &:hover button:hover { + background-color: rgba(255, 255, 255, 1) !important; + border: 1px solid #cbcbcb !important; + } + } + } + @media only screen and (max-width: 1686px) { + + ul.items-list-edit li.collection-list-item { + width: calc(16.666% - 32px); + min-width: calc(16.666% - 32px); + } + } + @media only screen and (max-width: 1452px) { + + ul.items-list-edit li.collection-list-item { + width: calc(20% - 32px); + min-width: calc(20% - 32px); + } + } + @media only screen and (max-width: 1118px) { + + ul.items-list-edit li.collection-list-item { + width: calc(25% - 32px); + min-width: calc(25% - 32px); + } + } + @media only screen and (max-width: 854px) { + + ul.items-list-edit li.collection-list-item { + width: calc(33.333% - 32px); + min-width: calc(33.333% - 32px); + } + } + @media only screen and (max-width: 584px) { + + ul.items-list-edit li.collection-list-item { + width: calc(50% - 32px); + min-width: calc(50% - 32px); + } + } + +} diff --git a/src/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-modal.js b/src/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-modal.js new file mode 100644 index 000000000..145555eea --- /dev/null +++ b/src/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-modal.js @@ -0,0 +1,320 @@ +import tainacan from '../../api-client/axios.js'; +import axios from 'axios'; + +const { __ } = wp.i18n; + +const { TextControl, Button, Modal, RadioControl, Spinner } = wp.components; + +export default class CarouselCollectionsModal extends React.Component { + constructor(props) { + super(props); + + // Initialize state + this.state = { + collectionsPerPage: 24, + collectionId: undefined, + collectionName: '', + isLoadingCollections: false, + modalCollections: [], + totalModalCollections: 0, + collectionPage: 1, + temporaryCollectionId: '', + searchCollectionName: '', + collections: [], + collectionsRequestSource: undefined, + searchURL: '', + itemsPerPage: 12, + loadStrategy: 'search' + }; + + // Bind events + this.resetCollections = this.resetCollections.bind(this); + this.selectCollection = this.selectCollection.bind(this); + this.fetchCollections = this.fetchCollections.bind(this); + this.fetchModalCollections = this.fetchModalCollections.bind(this); + this.fetchCollection = this.fetchCollection.bind(this); + this.applySelectedSearchURL = this.applySelectedSearchURL.bind(this); + this.applySelectedItems = this.applySelectedItems.bind(this); + } + + componentWillMount() { + + this.setState({ + collectionId: this.props.existingCollectionId + }); + + if (this.props.existingCollectionId != null && this.props.existingCollectionId != undefined) { + this.fetchCollection(this.props.existingCollectionId); + this.setState({ + searchURL: this.props.existingSearchURL ? this.props.existingSearchURL : tainacan_plugin.admin_url + 'admin.php?page=tainacan_admin#/collections/'+ this.props.existingCollectionId + (this.props.loadStrategy == 'search' ? '/items/?iframemode=true&readmode=true' : '/items/?iframemode=true') }); + } else { + this.setState({ collectionPage: 1 }); + this.fetchModalCollections(); + } + } + + // COLLECTIONS RELATED -------------------------------------------------- + fetchModalCollections() { + + let someModalCollections = this.state.modalCollections; + if (this.state.collectionPage <= 1) + someModalCollections = []; + + let endpoint = '/collections/?orderby=title&order=asc&perpage=' + this.state.collectionsPerPage + '&paged=' + this.state.collectionPage; + + this.setState({ + isLoadingCollections: true, + collectionPage: this.state.collectionPage + 1, + modalCollections: someModalCollections + }); + + tainacan.get(endpoint) + .then(response => { + + let otherModalCollections = this.state.modalCollections; + for (let collection of response.data) { + otherModalCollections.push({ + name: collection.name, + id: collection.id + }); + } + + this.setState({ + isLoadingCollections: false, + modalCollections: otherModalCollections, + totalModalCollections: response.headers['x-wp-total'] + }); + + return otherModalCollections; + }) + .catch(error => { + console.log('Error trying to fetch collections: ' + error); + }); + } + + fetchCollection(collectionId) { + tainacan.get('/collections/' + collectionId) + .then((response) => { + this.setState({ collectionName: response.data.name }); + }).catch(error => { + console.log('Error trying to fetch collection: ' + error); + }); + } + + selectCollection(selectedCollectionId) { + this.setState({ + collectionId: selectedCollectionId, + searchURL: tainacan_plugin.admin_url + 'admin.php?page=tainacan_admin#/collections/' + selectedCollectionId + (this.props.loadStrategy == 'search' ? '/items/?iframemode=true&readmode=true' : '/items/?iframemode=true') + }); + + this.props.onSelectCollection(selectedCollectionId); + this.fetchCollection(selectedCollectionId); + } + + fetchCollections(name) { + + if (this.state.collectionsRequestSource != undefined) + this.state.collectionsRequestSource.cancel('Previous collections search canceled.'); + + let aCollectionRequestSource = axios.CancelToken.source(); + + this.setState({ + collectionsRequestSource: aCollectionRequestSource, + isLoadingCollections: true, + collections: [], + items: [] + }); + + let endpoint = '/collections/?orderby=title&order=asc&perpage=' + this.state.collectionsPerPage; + if (name != undefined && name != '') + endpoint += '&search=' + name; + + tainacan.get(endpoint, { cancelToken: aCollectionRequestSource.token }) + .then(response => { + let someCollections = response.data.map((collection) => ({ name: collection.name, id: collection.id + '' })); + + this.setState({ + isLoadingCollections: false, + collections: someCollections + }); + + return someCollections; + }) + .catch(error => { + console.log('Error trying to fetch collections: ' + error); + }); + } + + applySelectedSearchURL() { + let iframe = document.getElementById("itemsFrame"); + if (iframe) { + this.props.onApplySearchURL(iframe.contentWindow.location.href); + } + } + + applySelectedItems() { + let iframe = document.getElementById("itemsFrame"); + if (iframe) { + let params = new URLSearchParams(iframe.contentWindow.location.search); + let selectedItems = params.getAll('selecteditems'); + params.delete('selecteditems') + this.props.onApplySelectedItems(selectedItems); + } + } + + resetCollections() { + + this.setState({ + collectionId: null, + collectionPage: 1, + modalCollections: [] + }); + this.fetchModalCollections(); + } + + cancelSelection() { + + this.setState({ + modalCollections: [] + }); + + this.props.onCancelSelection(); + } + + render() { + return this.state.collectionId != null && this.state.collectionId != undefined ? ( + // Items modal + this.cancelSelection() } + contentLabel={ this.props.loadStrategy == 'selection' ? __('Select items that will be added on block', 'tainacan') : __('Configure your items search that will load items on block', 'tainacan')}> +