Begins creation of collections carousel block.

This commit is contained in:
Mateus Machado Luna 2019-08-12 14:52:43 -03:00
parent 2c7c7cbff0
commit f61f8ef550
7 changed files with 1328 additions and 0 deletions

View File

@ -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 );
}

View File

@ -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)) );
}
}
});

View File

@ -0,0 +1,221 @@
<template>
<div :class="className">
<div v-if="!isLoading">
<div
:class="'tainacan-carousel has-arrows-' + arrowsPosition"
v-if="collections.length > 0">
<swiper
role="list"
:options="swiperOptions">
<swiper-slide
role="listitem"
:key="index"
v-for="(collection, index) of collections"
class="collection-list-item">
<a
:id="isNaN(collection.id) ? collection.id : 'collection-id-' + collection.id"
:href="collection.url"
target="_blank">
<img
:src="
collection.thumbnail && collection.thumbnail['tainacan-medium'][0] && collection.thumbnail['tainacan-medium'][0]
?
collection.thumbnail['tainacan-medium'][0]
:
(collection.thumbnail && collection.thumbnail['thumbnail'][0] && collection.thumbnail['thumbnail'][0]
?
collection.thumbnail['thumbnail'][0]
:
`${tainacanBaseUrl}/admin/images/placeholder_square.png`)
"
:alt="collection.title ? collection.title : $root.__('Thumbnail', 'tainacan')">
<span v-if="!hideTitle">{{ collection.title ? collection.title : '' }}</span>
</a>
</swiper-slide>
</swiper>
<button
class="swiper-button-prev"
slot="button-prev">
<svg
width="42"
height="42"
viewBox="0 0 24 24">
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
<path
d="M0 0h24v24H0z"
fill="none"/>
</svg>
</button>
<button
class="swiper-button-next"
slot="button-next">
<svg
width="42"
height="42"
viewBox="0 0 24 24">
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>
<path
d="M0 0h24v24H0z"
fill="none"/>
</svg>
</button>
</div>
<div
v-else
class="spinner-container">
{{ $root.__('No collections found.', 'tainacan') }}
</div>
<!-- Swiper buttons are hidden as they actually swipe from slide to slide -->
</div>
<div v-else>
<div :class="'tainacan-carousel has-arrows-' + arrowsPosition">
<swiper
role="list"
:options="swiperOptions">
<swiper-slide
role="listitem"
:key="index"
v-for="(collection, index) of 18"
class="collection-list-item skeleton">
<a>
<img>
<span v-if="!hideTitle" />
</a>
</swiper-slide>
</swiper>
<button
class="swiper-button-prev"
slot="button-prev">
<svg
width="42"
height="42"
viewBox="0 0 24 24">
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
<path
d="M0 0h24v24H0z"
fill="none"/>
</svg>
</button>
<button
class="swiper-button-next"
slot="button-next">
<svg
width="42"
height="42"
viewBox="0 0 24 24">
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>
<path
d="M0 0h24v24H0z"
fill="none"/>
</svg>
</button>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
import qs from 'qs';
import 'swiper/dist/css/swiper.css';
import { swiper, swiperSlide } from 'vue-awesome-swiper';
export default {
name: "CarouselCollectionsListTheme",
data() {
return {
collections: [],
collection: undefined,
itemsRequestSource: undefined,
isLoading: false,
isLoadingCollection: false,
localMaxCollectionsNumber: undefined,
localOrder: undefined,
tainacanAxios: undefined,
paged: undefined,
totalCollections: 0,
swiperOptions: {
mousewheel: true,
observer: true,
preventInteractionOnTransition: true,
allowClick: true,
allowTouchMove: true,
slidesPerView: 7,
slidesPerGroup: 1,
spaceBetween: 32,
slideToClickedSlide: true,
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
breakpoints: {
498: { slidesPerView: 2 },
768: { slidesPerView: 3 },
1024: { slidesPerView: 4 },
1366: { slidesPerView: 5 },
1600: { slidesPerView: 6 },
},
autoplay: this.autoPlay ? { delay: this.autoPlaySpeed*1000 } : false,
loop: this.loopSlides
},
}
},
components: {
swiper,
swiperSlide
},
props: {
collectionId: String,
searchURL: String,
selectedCollections: Array,
maxCollectionsNumber: Number,
arrowsPosition: String,
autoPlay: false,
autoPlaySpeed: Number,
loopSlides: Boolean,
hideTitle: Boolean,
tainacanApiRoot: String,
tainacanBaseUrl: String,
className: String
},
methods: {
fetchCollections() {
this.isLoading = true;
if (this.itemsRequestSource != undefined && typeof this.itemsRequestSource == 'function')
this.itemsRequestSource.cancel('Previous collections search canceled.');
this.itemsRequestSource = axios.CancelToken.source();
let endpoint = '/collection/' + this.collectionId + '/collections?' + qs.stringify({ postin: this.selectedCollections }) + '&fetch_only=title,url,thumbnail';
this.tainacanAxios.get(endpoint, { cancelToken: this.itemsRequestSource.token })
.then(response => {
for (let collection of response.data.collections)
this.collections.push(collection);
this.isLoading = false;
this.totalCollections = response.headers['x-wp-total'];
}).catch(() => {
this.isLoading = false;
// console.log(error);
});
},
},
created() {
this.tainacanAxios = axios.create({ baseURL: this.tainacanApiRoot });
this.fetchCollections();
},
}
</script>
<style lang="scss">
@import './carousel-collections-list.scss';
</style>

View File

@ -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);
}
}
}

View File

@ -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
<Modal
className="wp-block-tainacan-modal dynamic-modal"
title={ this.props.loadStrategy == 'selection' ? __('Select items to add on block', 'tainacan') : __('Configure the items search to be used on block', 'tainacan')}
onRequestClose={ () => 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')}>
<iframe
id="itemsFrame"
src={ this.state.searchURL } />
<div className="modal-footer-area">
<Button
isDefault
onClick={ () => { this.resetCollections() }}>
{__('Switch collection', 'tainacan')}
</Button>
{ this.props.loadStrategy == 'selection' ?
<Button
style={{ marginLeft: 'auto' }}
isPrimary
onClick={ () => this.applySelectedItems() }>
{__('Add the selected items', 'tainacan')}
</Button>
: null
}
{ this.props.loadStrategy == 'search' ?
<Button
isPrimary
onClick={ () => this.applySelectedSearchURL() }>
{__('Use this search', 'tainacan')}
</Button>
: null
}
</div>
</Modal>
) : (
// Collections modal
<Modal
className="wp-block-tainacan-modal"
title={__('Select a collection to fetch items from', 'tainacan')}
onRequestClose={ () => this.cancelSelection() }
contentLabel={__('Select items', 'tainacan')}>
<div>
<div className="modal-search-area">
<TextControl
label={__('Search for a collection', 'tainacan')}
value={ this.state.searchCollectionName }
onChange={(value) => {
this.setState({
searchCollectionName: value
});
_.debounce(this.fetchCollections(value), 300);
}}/>
</div>
{(
this.state.searchCollectionName != '' ? (
this.state.collections.length > 0 ?
(
<div>
<div className="modal-radio-list">
{
<RadioControl
selected={ this.state.temporaryCollectionId }
options={
this.state.collections.map((collection) => {
return { label: collection.name, value: '' + collection.id }
})
}
onChange={ ( aCollectionId ) => {
this.setState({ temporaryCollectionId: aCollectionId });
} } />
}
</div>
</div>
) :
this.state.isLoadingCollections ? (
<Spinner />
) :
<div className="modal-loadmore-section">
<p>{ __('Sorry, no collection found.', 'tainacan') }</p>
</div>
):
this.state.modalCollections.length > 0 ?
(
<div>
<div className="modal-radio-list">
{
<RadioControl
selected={ this.state.temporaryCollectionId }
options={
this.state.modalCollections.map((collection) => {
return { label: collection.name, value: '' + collection.id }
})
}
onChange={ ( aCollectionId ) => {
this.setState({ temporaryCollectionId: aCollectionId });
} } />
}
</div>
<div className="modal-loadmore-section">
<p>{ __('Showing', 'tainacan') + " " + this.state.modalCollections.length + " " + __('of', 'tainacan') + " " + this.state.totalModalCollections + " " + __('collections', 'tainacan') + "."}</p>
{
this.state.modalCollections.length < this.state.totalModalCollections ? (
<Button
isDefault
isSmall
onClick={ () => this.fetchModalCollections() }>
{__('Load more', 'tainacan')}
</Button>
) : null
}
</div>
</div>
) : this.state.isLoadingCollections ? <Spinner/> :
<div className="modal-loadmore-section">
<p>{ __('Sorry, no collection found.', 'tainacan') }</p>
</div>
)}
<div className="modal-footer-area">
<Button
isDefault
onClick={ () => { this.cancelSelection() }}>
{__('Cancel', 'tainacan')}
</Button>
<Button
isPrimary
disabled={ this.state.temporaryCollectionId == undefined || this.state.temporaryCollectionId == null || this.state.temporaryCollectionId == ''}
onClick={ () => { this.selectCollection(this.state.temporaryCollectionId); } }>
{ this.props.loadStrategy == 'selection' ? __('Select items', 'tainacan') : __('Configure search', 'tainacan')}
</Button>
</div>
</div>
</Modal>
);
}
}

View File

@ -0,0 +1,457 @@
const { registerBlockType } = wp.blocks;
const { __ } = wp.i18n;
const { RangeControl, Spinner, Button, ToggleControl, SelectControl, Placeholder, IconButton, ColorPicker, ColorPalette, BaseControl, PanelBody } = wp.components;
const { InspectorControls } = wp.editor;
import CarouselCollectionsModal from './carousel-collections-modal.js';
import tainacan from '../../api-client/axios.js';
import axios from 'axios';
import qs from 'qs';
registerBlockType('tainacan/carousel-collections-list', {
title: __('Tainacan Collections Carousel', 'tainacan'),
icon:
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
height="24px"
width="24px">
<path
fill="#298596"
d="M18,17v2H12a5.65,5.65,0,0,0-.36-2ZM2,7v7.57a5.74,5.74,0,0,1,2-1.2V7ZM20,6H15L13,4H8A2,2,0,0,0,6,6v7a6,6,0,0,1,5.19,3H20a2,2,0,0,0,2-2V8A2,2,0,0,0,20,6ZM7,16.05v6.06l3.06-3.06ZM5,22.11V16.05L1.94,19.11Z"/>
</svg>,
category: 'tainacan-blocks',
keywords: [ __( 'collections', 'tainacan' ), __( 'carousel', 'tainacan' ), __( 'slider', 'tainacan' ) ],
attributes: {
content: {
type: 'array',
source: 'children',
selector: 'div'
},
collectionId: {
type: String,
default: undefined
},
collections: {
type: Array,
default: []
},
isModalOpen: {
type: Boolean,
default: false
},
selectedCollections: {
type: Array,
default: []
},
itemsRequestSource: {
type: String,
default: undefined
},
maxCollectionsNumber: {
type: Number,
value: undefined
},
isLoading: {
type: Boolean,
value: false
},
isLoadingCollection: {
type: Boolean,
value: false
},
arrowsPosition: {
type: String,
value: 'search'
},
autoPlay: {
type: Boolean,
value: false
},
autoPlaySpeed: {
type: Number,
value: 3
},
loopSlides: {
type: Boolean,
value: false
},
hideTitle: {
type: Boolean,
value: true
},
showCollectionHeader: {
type: Boolean,
value: false
},
showCollectionLabel: {
type: Boolean,
value: false
},
collection: {
type: Object,
value: undefined
},
blockId: {
type: String,
default: undefined
},
collectionBackgroundColor: {
type: String,
default: "#454647"
},
collectionTextColor: {
type: String,
default: "#ffffff"
},
extraParams: {
type: Object,
default: {}
}
},
supports: {
align: ['full', 'wide'],
html: false,
multiple: false
},
edit({ attributes, setAttributes, className, isSelected, clientId }){
let {
collections,
content,
collectionId,
isModalOpen,
itemsRequestSource,
selectedCollections,
isLoading,
arrowsPosition,
autoPlay,
autoPlaySpeed,
loopSlides,
hideTitle
} = attributes;
// Obtains block's client id to render it on save function
setAttributes({ blockId: clientId });
function prepareItem(collection) {
return (
<li
key={ collection.id }
className="collection-list-item">
<IconButton
onClick={ () => removeItemOfId(collection.id) }
icon="no-alt"
label={__('Remove', 'tainacan')}/>
<a
id={ isNaN(collection.id) ? collection.id : 'collection-id-' + collection.id }
href={ collection.url }
target="_blank">
<img
src={
collection.thumbnail && collection.thumbnail['tainacan-medium'][0] && collection.thumbnail['tainacan-medium'][0]
?
collection.thumbnail['tainacan-medium'][0]
:
(collection.thumbnail && collection.thumbnail['thumbnail'][0] && collection.thumbnail['thumbnail'][0]
?
collection.thumbnail['thumbnail'][0]
:
`${tainacan_plugin.base_url}/admin/images/placeholder_square.png`)
}
alt={ collection.title ? collection.title : __( 'Thumbnail', 'tainacan' ) }/>
{ !hideTitle ? <span>{ collection.title ? collection.title : '' }</span> : null }
</a>
</li>
);
}
function setContent(){
isLoading = true;
setAttributes({
isLoading: isLoading
});
if (itemsRequestSource != undefined && typeof itemsRequestSource == 'function')
itemsRequestSource.cancel('Previous collections search canceled.');
itemsRequestSource = axios.CancelToken.source();
collections = [];
let endpoint = '/collection/' + collectionId + '/collections?'+ qs.stringify({ postin: selectedCollections }) + '&fetch_only=title,url,thumbnail';
tainacan.get(endpoint, { cancelToken: itemsRequestSource.token })
.then(response => {
for (let collection of response.data.collections)
collections.push(prepareItem(collection));
setAttributes({
content: <div></div>,
collections: collections,
isLoading: false,
itemsRequestSource: itemsRequestSource
});
});
}
function openCarouselModal() {
isModalOpen = true;
setAttributes( {
isModalOpen: isModalOpen
} );
}
function removeItemOfId(itemId) {
let existingItemIndex = collections.findIndex((existingItem) => existingItem.key == itemId);
if (existingItemIndex >= 0)
collections.splice(existingItemIndex, 1);
let existingSelectedItemIndex = selectedCollections.findIndex((existingSelectedItem) => existingSelectedItem == itemId);
if (existingSelectedItemIndex >= 0)
selectedCollections.splice(existingSelectedItemIndex, 1);
setAttributes({
selectedCollections: selectedCollections,
collections: collections,
content: <div></div>
});
}
// Executed only on the first load of page
if(content && content.length && content[0].type)
setContent();
return (
<div className={className}>
<div>
<InspectorControls>
<PanelBody
title={__('Carousel', 'tainacan')}
initialOpen={ true }
>
<div>
<ToggleControl
label={__('Hide title', 'tainacan')}
help={ !hideTitle ? __('Toggle to hide collection\'s title', 'tainacan') : __('Do not hide collection\'s title', 'tainacan')}
checked={ hideTitle }
onChange={ ( isChecked ) => {
hideTitle = isChecked;
setAttributes({ hideTitle: hideTitle });
setContent();
}
}
/>
<ToggleControl
label={__('Loop slides', 'tainacan')}
help={ !loopSlides ? __('Toggle to make slides loop from first to last', 'tainacan') : __('Do not loop slides from first to last', 'tainacan')}
checked={ loopSlides }
onChange={ ( isChecked ) => {
loopSlides = isChecked;
setAttributes({ loopSlides: loopSlides });
}
}
/>
<ToggleControl
label={__('Auto play', 'tainacan')}
help={ !autoPlay ? __('Toggle to automatically slide to next collection', 'tainacan') : __('Do not automatically slide to next collection', 'tainacan')}
checked={ autoPlay }
onChange={ ( isChecked ) => {
autoPlay = isChecked;
setAttributes({ autoPlay: autoPlay });
}
}
/>
{
autoPlay ?
<RangeControl
label={__('Seconds before translating to next', 'tainacan')}
value={ autoPlaySpeed }
onChange={ ( aAutoPlaySpeed ) => {
autoPlaySpeed = aAutoPlaySpeed;
setAttributes( { autoPlaySpeed: aAutoPlaySpeed } )
}}
min={ 1 }
max={ 5 }
/>
: null
}
<SelectControl
label={__('Arrows', 'tainacan')}
value={ arrowsPosition }
options={ [
{ label: __('Around', 'tainacan'), value: 'around' },
{ label: __('Left', 'tainacan'), value: 'left' },
{ label: __('Right', 'tainacan'), value: 'right' }
] }
onChange={ ( aPosition ) => {
arrowsPosition = aPosition;
setAttributes({ arrowsPosition: arrowsPosition });
}}/>
</div>
</PanelBody>
</InspectorControls>
</div>
{ isSelected ?
(
<div>
{ isModalOpen ?
<CarouselCollectionsModal
onApplySelectedCollections={ (aSelectionOfCollections) => {
selectedCollections = selectedCollections.concat(aSelectionOfCollections);
setAttributes({
selectedCollections: selectedCollections,
isModalOpen: false
});
setContent();
}}
onCancelSelection={ () => setAttributes({ isModalOpen: false }) }/>
: null
}
{ collections.length ? (
<div className="block-control">
<p>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
height="24px"
width="24px">
<path d="M18,17v2H12a5.65,5.65,0,0,0-.36-2ZM2,7v7.57a5.74,5.74,0,0,1,2-1.2V7ZM20,6H15L13,4H8A2,2,0,0,0,6,6v7a6,6,0,0,1,5.19,3H20a2,2,0,0,0,2-2V8A2,2,0,0,0,20,6ZM7,16.05v6.06l3.06-3.06ZM5,22.11V16.05L1.94,19.11Z"/>
</svg>
{__('List collections on a Carousel', 'tainacan')}
</p>
<Button
isPrimary
type="submit"
onClick={ () => openCarouselModal() }>
{__('Add more collections', 'tainacan')}
</Button>
</div>
): null
}
</div>
) : null
}
{ !collections.length && !isLoading ? (
<Placeholder
icon={(
<img
width={148}
src={ `${tainacan_plugin.base_url}/admin/images/tainacan_logo_header.svg` }
alt="Tainacan Logo"/>
)}>
<p>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
height="24px"
width="24px">
<path d="M18,17v2H12a5.65,5.65,0,0,0-.36-2ZM2,7v7.57a5.74,5.74,0,0,1,2-1.2V7ZM20,6H15L13,4H8A2,2,0,0,0,6,6v7a6,6,0,0,1,5.19,3H20a2,2,0,0,0,2-2V8A2,2,0,0,0,20,6ZM7,16.05v6.06l3.06-3.06ZM5,22.11V16.05L1.94,19.11Z"/>
</svg>
{__('List collections on a Carousel, using search or collection selection.', 'tainacan')}
</p>
<Button
isPrimary
type="submit"
onClick={ () => openCarouselModal() }>
{__('Select Collections', 'tainacan')}
</Button>
</Placeholder>
) : null
}
{ isLoading ?
<div class="spinner-container">
<Spinner />
</div> :
<div>
{ isSelected && collections.length ?
<div class="preview-warning">{__('Warning: this is just a demonstration. To see the carousel in action, either preview or publish your post.', 'tainacan')}</div>
: null
}
{ collections.length ? (
<div
className={'collections-list-edit-container has-arrows-' + arrowsPosition}>
<button
class="swiper-button-prev"
slot="button-prev"
style={{ cursor: 'not-allowed' }}>
<svg
width="42"
height="42"
viewBox="0 0 24 24">
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
<path
d="M0 0h24v24H0z"
fill="none"/>
</svg>
</button>
<ul className={'collections-list-edit'}>
{ collections }
</ul>
<button
class="swiper-button-next"
slot="button-next"
style={{ cursor: 'not-allowed' }}>
<svg
width="42"
height="42"
viewBox="0 0 24 24">
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>
<path
d="M0 0h24v24H0z"
fill="none"/>
</svg>
</button>
</div>
):null
}
</div>
}
</div>
);
},
save({ attributes, className }){
const {
content,
blockId,
collectionId,
selectedCollections,
arrowsPosition,
maxCollectionsNumber,
autoPlay,
autoPlaySpeed,
loopSlides,
hideTitle,
extraParams
} = attributes;
return <div
className={ className }
selected-collections={ JSON.stringify(selectedCollections) }
arrows-position={ arrowsPosition }
collection-id={ collectionId }
auto-play={ '' + autoPlay }
auto-play-speed={ autoPlaySpeed }
loop-slides={ '' + loopSlides }
hide-title={ '' + hideTitle }
max-collections-number={ maxCollectionsNumber }
tainacan-api-root={ tainacan_plugin.root }
tainacan-base-url={ tainacan_plugin.base_url }
extraParams={ JSON.stringify(extraParams) }
id={ 'wp-block-tainacan-carousel-collections-list_' + blockId }>
{ content }
</div>
}
});

View File

@ -17,6 +17,8 @@ module.exports = {
gutenberg_carousel_items_list: './src/gutenberg-blocks/tainacan-items/carousel-items-list/index.js',
gutenberg_carousel_items_list_theme: './src/gutenberg-blocks/tainacan-items/carousel-items-list/carousel-items-list-theme.js',
gutenberg_collections_list: './src/gutenberg-blocks/tainacan-collections/collections-list/index.js',
gutenberg_carousel_collections_list: './src/gutenberg-blocks/tainacan-collections/carousel-collections-list/index.js',
gutenberg_carousel_collections_list_theme: './src/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-list-theme.js',
gutenberg_facets_list: './src/gutenberg-blocks/tainacan-facets/facets-list/index.js',
gutenberg_facets_list_theme: './src/gutenberg-blocks/tainacan-facets/facets-list/facets-list-theme.js'
},