Changeset 8710
- Timestamp:
- 05/01/2019 10:08:54 PM (6 years ago)
- Location:
- sites/trunk/wordcamp.org/public_html/wp-content/mu-plugins/blocks
- Files:
-
- 8 edited
Legend:
- Unmodified
- Added
- Removed
-
sites/trunk/wordcamp.org/public_html/wp-content/mu-plugins/blocks/assets/src/organizers/block-content.js
r8647 r8710 8 8 */ 9 9 const { Component } = wp.element; 10 const { __ } = wp.i18n;11 10 12 11 /** 13 12 * Internal dependencies 14 13 */ 15 import { AvatarImage } from '../shared/avatar'; 16 import {ItemTitle, ItemHTMLContent, ItemPermalink} from '../shared/block-content'; 17 import GridContentLayout from '../shared/grid-layout/block-content'; 14 import { AvatarImage } from '../shared/avatar'; 15 import { BlockNoContent, ItemTitle, ItemHTMLContent } from '../shared/block-content'; 16 import GridContentLayout from '../shared/grid-layout/block-content'; 17 import { filterEntities } from '../blocks-store'; 18 18 19 /** 20 * Component for displaying the block content. 21 */ 19 22 class OrganizersBlockContent extends Component { 23 /** 24 * Run additional operations during component initialization. 25 * 26 * @param {Object} props 27 */ 28 constructor( props ) { 29 super( props ); 30 31 this.getFilteredPosts = this.getFilteredPosts.bind( this ); 32 } 33 34 /** 35 * Filter and sort the content that will be rendered. 36 * 37 * @returns {Array} 38 */ 39 getFilteredPosts() { 40 const { attributes, entities } = this.props; 41 const { wcb_organizer: posts } = entities; 42 const { mode, item_ids, sort } = attributes; 43 44 const args = {}; 45 46 if ( Array.isArray( item_ids ) && item_ids.length > 0 ) { 47 args.filter = [ 48 { 49 fieldName : mode === 'wcb_organizer' ? 'id' : 'organizer_team', 50 fieldValue : item_ids, 51 }, 52 ]; 53 } 54 55 args.sort = sort; 56 57 return filterEntities( posts, args ); 58 } 59 60 /** 61 * Render the block content. 62 * 63 * @return {Element} 64 */ 20 65 render() { 21 const { attributes , organizerPosts }= this.props;66 const { attributes } = this.props; 22 67 const { show_avatars, avatar_size, avatar_align, content } = attributes; 68 69 const posts = this.getFilteredPosts(); 70 const isLoading = ! Array.isArray( posts ); 71 const hasPosts = ! isLoading && posts.length > 0; 72 73 if ( isLoading || ! hasPosts ) { 74 return ( 75 <BlockNoContent loading={ isLoading } /> 76 ); 77 } 23 78 24 79 return ( … … 27 82 { ...this.props } 28 83 > 29 { organizerPosts.map( ( post ) => /* Note that organizer posts are not 'public', so there are no permalinks. */84 { posts.map( ( post ) => /* Note that organizer posts are not 'public', so there are no permalinks. */ 30 85 <div 31 86 key={ post.slug } … … 56 111 /> 57 112 } 58 </div> ,113 </div> 59 114 ) } 60 115 </GridContentLayout> -
sites/trunk/wordcamp.org/public_html/wp-content/mu-plugins/blocks/assets/src/organizers/block-controls.js
r8566 r8710 13 13 * Internal dependencies 14 14 */ 15 import { BlockControls, Placeholder NoContent, PlaceholderSpecificMode } from '../shared/block-controls';16 import OrganizersBlockContent 17 import OrganizersSelect 18 import { LABEL } 15 import { BlockControls, PlaceholderSpecificMode } from '../shared/block-controls'; 16 import OrganizersBlockContent from './block-content'; 17 import OrganizersSelect from './organizers-select'; 18 import { LABEL } from './index'; 19 19 20 /** 21 * Component for displaying a UI within the block. 22 */ 20 23 class OrganizersBlockControls extends BlockControls { 24 /** 25 * Render the internal block UI. 26 * 27 * @return {Element} 28 */ 21 29 render() { 22 const { icon, attributes, setAttributes, organizerPosts } = this.props; 23 const { mode } = attributes; 24 25 const hasPosts = Array.isArray( organizerPosts ) && organizerPosts.length; 26 27 if ( mode && ! hasPosts ) { 28 return ( 29 <PlaceholderNoContent 30 icon={ icon } 31 label={ LABEL } 32 loading={ ! Array.isArray( organizerPosts ) } 33 /> 34 ); 35 } 30 const { icon, attributes, setAttributes } = this.props; 31 const { mode } = attributes; 36 32 37 33 let output; -
sites/trunk/wordcamp.org/public_html/wp-content/mu-plugins/blocks/assets/src/organizers/edit.js
r8611 r8710 1 /**2 * External dependencies3 */4 import { isUndefined, pickBy, split } from 'lodash';5 6 1 /** 7 2 * WordPress dependencies 8 3 */ 9 const apiFetch = wp.apiFetch;10 4 const { withSelect } = wp.data; 11 5 const { Component, Fragment } = wp.element; 12 const { addQueryArgs } = wp.url;13 6 14 7 /** … … 19 12 import OrganizersToolbar from './toolbar'; 20 13 import { ICON } from './index'; 14 import { WC_BLOCKS_STORE } from '../blocks-store'; 21 15 22 16 const blockData = window.WordCampBlocks.organizers || {}; 23 const MAX_POSTS = 100;24 17 25 const ALL_POSTS_QUERY = { 26 orderby : 'title', 27 order : 'asc', 28 per_page : MAX_POSTS, 29 }; 30 31 const ALL_TERMS_QUERY = { 32 orderby : 'name', 33 order : 'asc', 34 per_page : MAX_POSTS, 35 }; 36 18 /** 19 * Top-level component for the editing UI for the block. 20 */ 37 21 class OrganizersEdit extends Component { 38 constructor( props ) { 39 super( props ); 40 41 this.state = { 42 allOrganizerPosts : null, 43 allOrganizerTerms : null, 44 }; 45 46 this.fetchOrganizerDetails(); 47 } 48 49 fetchOrganizerDetails() { 50 const allOrganizerPosts = apiFetch( { 51 path: addQueryArgs( '/wp/v2/organizers', ALL_POSTS_QUERY ), 52 } ); 53 54 const allOrganizerTerms = apiFetch( { 55 path: addQueryArgs( '/wp/v2/organizer_team', ALL_TERMS_QUERY ), 56 } ); 57 58 this.state = { 59 allOrganizerPosts : allOrganizerPosts, // Promise 60 allOrganizerTerms : allOrganizerTerms, // Promise 61 } 62 } 63 22 /** 23 * Render the block's editing UI. 24 * 25 * @return {Element} 26 */ 64 27 render() { 65 28 const { mode } = this.props.attributes; … … 70 33 icon={ ICON } 71 34 { ...this.props } 72 { ...this.state }73 35 /> 74 36 … … 84 46 } 85 47 86 const organizerSelect = ( select, props ) => { 87 const { mode, item_ids, sort } = props.attributes; 88 const { getEntityRecords } = select( 'core' ); 89 const [ orderby, order ] = split( sort, '_', 2 ); 48 const organizerSelect = ( select ) => { 49 const { getEntities } = select( WC_BLOCKS_STORE ); 90 50 91 const args = { 92 orderby : orderby, 93 order : order, 94 per_page : MAX_POSTS, // -1 is not allowed for per_page. 95 context : 'view', 51 const entities = { 52 wcb_organizer : getEntities( 'postType', 'wcb_organizer', { _embed: true } ), 53 wcb_organizer_team : getEntities( 'taxonomy', 'wcb_organizer_team' ), 96 54 }; 97 55 98 if ( Array.isArray( item_ids ) ) {99 switch ( mode ) {100 case 'wcb_organizer':101 args.include = item_ids;102 break;103 case 'wcb_organizer_team':104 args.organizer_team = item_ids;105 break;106 }107 }108 109 const organizersQuery = pickBy(110 args,111 ( value ) => ! isUndefined( value )112 );113 114 56 return { 115 blockData : blockData,116 organizerPosts : getEntityRecords( 'postType', 'wcb_organizer', organizersQuery ),57 blockData, 58 entities, 117 59 }; 118 60 }; -
sites/trunk/wordcamp.org/public_html/wp-content/mu-plugins/blocks/assets/src/organizers/index.js
r8627 r8710 14 14 15 15 const supports = { 16 'align': [ 'wide', 'full' ],16 align: [ 'wide', 'full' ], 17 17 }; 18 18 -
sites/trunk/wordcamp.org/public_html/wp-content/mu-plugins/blocks/assets/src/organizers/inspector-controls.js
r8647 r8710 34 34 }; 35 35 36 /** 37 * Component for block controls that appear in the Inspector Panel. 38 */ 36 39 class OrganizerInspectorControls extends Component { 40 /** 41 * Render the controls. 42 * 43 * @return {Element} 44 */ 37 45 render() { 38 const { attributes, setAttributes, blockData } 46 const { attributes, setAttributes, blockData } = this.props; 39 47 const { show_avatars, avatar_size, avatar_align, content, sort } = attributes; 40 const { schema = DEFAULT_SCHEMA, options = DEFAULT_OPTIONS } 48 const { schema = DEFAULT_SCHEMA, options = DEFAULT_OPTIONS } = blockData; 41 49 42 50 return ( -
sites/trunk/wordcamp.org/public_html/wp-content/mu-plugins/blocks/assets/src/organizers/organizers-select.js
r8633 r8710 2 2 * External dependencies 3 3 */ 4 import { get, includes } from 'lodash';4 import { every, flatMap, includes } from 'lodash'; 5 5 6 6 /** 7 7 * WordPress dependencies 8 8 */ 9 const { Dashicon } = wp.components;10 9 const { Component } = wp.element; 11 10 const { __ } = wp.i18n; … … 14 13 * Internal dependencies 15 14 */ 16 import { AvatarImage } from '../shared/avatar'; 17 import ItemSelect from '../shared/item-select'; 18 import { ICON } from './index'; 15 import ItemSelect, { buildOptions, Option } from '../shared/item-select'; 19 16 17 /** 18 * Component for selecting posts/terms for populating the block content. 19 */ 20 20 class OrganizersSelect extends Component { 21 /** 22 * Run additional operations during component initialization. 23 * 24 * @param {Object} props 25 */ 21 26 constructor( props ) { 22 27 super( props ); 23 28 24 this.state = { 25 wcb_organizer : [], 26 wcb_organizer_team : [], 27 loading : true, 28 }; 29 30 this.buildSelectOptions = this.buildSelectOptions.bind( this ); 31 this.fetchSelectOptions( props ); 29 this.buildSelectOptions = this.buildSelectOptions.bind( this ); 30 this.getCurrentSelectValue = this.getCurrentSelectValue.bind( this ); 31 this.isLoading = this.isLoading.bind( this ); 32 32 } 33 33 34 fetchSelectOptions( props ) { 35 const { allOrganizerPosts, allOrganizerTerms } = props; 34 /** 35 * Build or retrieve the options that will populate the Select dropdown. 36 * 37 * @return {Array} 38 */ 39 buildSelectOptions() { 40 const { entities } = this.props; 41 const { wcb_organizer, wcb_organizer_team } = entities; 36 42 37 const parsedPosts = allOrganizerPosts.then( 38 ( fetchedPosts ) => { 39 const posts = fetchedPosts.map( ( post ) => { 40 return { 41 label : post.title.rendered.trim() || __( '(Untitled)', 'wordcamporg' ), 42 value : post.id, 43 type : 'wcb_organizer', 44 avatar : post.avatar_urls[ '24' ], 45 }; 46 } ); 43 const optionGroups = [ 44 { 45 entityType : 'post', 46 type : 'wcb_organizer', 47 label : __( 'Organizers', 'wordcamporg' ), 48 items : wcb_organizer, 49 }, 50 { 51 entityType : 'term', 52 type : 'wcb_organizer_team', 53 label : __( 'Teams', 'wordcamporg' ), 54 items : wcb_organizer_team, 55 }, 56 ]; 47 57 48 this.setState( { wcb_organizer: posts } ); 49 } 50 ); 51 52 const parsedTerms = allOrganizerTerms.then( 53 ( fetchedTerms ) => { 54 const terms = fetchedTerms.map( ( term ) => { 55 return { 56 label : term.name.trim() || __( '(Untitled)', 'wordcamporg' ), 57 value : term.id, 58 type : 'wcb_organizer_team', 59 count : term.count, 60 }; 61 } ); 62 63 this.setState( { wcb_organizer_team: terms } ); 64 } 65 ); 66 67 Promise.all( [ parsedPosts, parsedTerms ] ).then( () => { 68 this.setState( { loading: false } ); 69 } ); 58 return buildOptions( optionGroups ); 70 59 } 71 60 72 buildSelectOptions( mode ) { 73 const { getOwnPropertyDescriptors } = Object; 74 const options = []; 61 /** 62 * Determine the currently selected options in the Select dropdown based on block attributes. 63 * 64 * @return {Array} 65 */ 66 getCurrentSelectValue() { 67 const { attributes } = this.props; 68 const { mode, item_ids } = attributes; 75 69 76 const labels = { 77 wcb_organizer : __( 'Organizers', 'wordcamporg' ), 78 wcb_organizer_team : __( 'Teams', 'wordcamporg' ), 79 }; 80 81 for ( const type in getOwnPropertyDescriptors( this.state ) ) { 82 if ( ! this.state[ type ].length ) { 83 continue; 84 } 85 86 if ( mode && type !== mode ) { 87 continue; 88 } 89 90 options.push( { 91 label : labels[ type ], 92 options : this.state[ type ], 93 } ); 94 } 95 96 return options; 97 } 98 99 render() { 100 const { label, attributes, setAttributes } = this.props; 101 const { mode, item_ids } = attributes; 102 const options = this.buildSelectOptions( mode ); 70 const options = flatMap( this.buildSelectOptions(), ( group ) => { 71 return group.options; 72 } ); 103 73 104 74 let value = []; 105 75 106 76 if ( mode && item_ids.length ) { 107 const modeOptions = get( options, '[0].options', [] ); 108 109 value = modeOptions.filter( ( option ) => { 110 return includes( item_ids, option.value ); 77 value = options.filter( ( option ) => { 78 return mode === option.type && includes( item_ids, option.value ); 111 79 } ); 112 80 } 113 81 82 return value; 83 } 84 85 /** 86 * Check if all of the entity groups have finished loading. 87 * 88 * @return {boolean} 89 */ 90 isLoading() { 91 const { entities } = this.props; 92 93 return ! every( entities, ( value ) => { 94 return Array.isArray( value ); 95 } ); 96 } 97 98 /** 99 * Render an ItemSelect component with block-specific settings. 100 * 101 * @return {Element} 102 */ 103 render() { 104 const { label, setAttributes } = this.props; 105 114 106 return ( 115 107 <ItemSelect 116 className="wordcamp-organizer -select"108 className="wordcamp-organizers-select" 117 109 label={ label } 118 value={ value } 119 buildSelectOptions={ this.buildSelectOptions } 110 value={ this.getCurrentSelectValue() } 120 111 onChange={ ( changed ) => setAttributes( changed ) } 121 mode={ mode }122 112 selectProps={ { 123 isLoading : this.state.loading, 124 formatGroupLabel : ( groupData ) => { 113 options : this.buildSelectOptions(), 114 isLoading : this.isLoading(), 115 formatOptionLabel : ( optionData ) => { 125 116 return ( 126 <span className="wordcamp-item-select-option-group-label"> 127 { groupData.label } 128 </span> 129 ); 130 }, 131 formatOptionLabel: ( optionData ) => { 132 return ( 133 <OrganizersOption { ...optionData } /> 117 <Option { ...optionData } /> 134 118 ); 135 119 }, … … 140 124 } 141 125 142 function OrganizersOption( { type, label = '', avatar = '', count = 0 } ) {143 let image, content;144 145 switch ( type ) {146 case 'wcb_organizer' :147 image = (148 <AvatarImage149 className="wordcamp-item-select-option-avatar"150 name={ label }151 size={ 24 }152 url={ avatar }153 />154 );155 content = (156 <span className="wordcamp-item-select-option-label">157 { label }158 </span>159 );160 break;161 162 case 'wcb_organizer_team' :163 image = (164 <div className="wordcamp-item-select-option-icon-container">165 <Dashicon166 className="wordcamp-item-select-option-icon"167 icon={ ICON }168 size={ 16 }169 />170 </div>171 );172 content = (173 <span className="wordcamp-item-select-option-label">174 { label }175 <span className="wordcamp-item-select-option-label-term-count">176 { count }177 </span>178 </span>179 );180 break;181 }182 183 return (184 <div className="wordcamp-item-select-option">185 { image }186 { content }187 </div>188 );189 }190 191 126 export default OrganizersSelect; -
sites/trunk/wordcamp.org/public_html/wp-content/mu-plugins/blocks/assets/src/organizers/toolbar.js
r8633 r8710 6 6 const { Component } = wp.element; 7 7 8 /** 9 * Component for adding UI buttons to the top of the block. 10 */ 8 11 class OrganizersToolbar extends Component { 12 /** 13 * Render the toolbar. 14 * 15 * @return {Element} 16 */ 9 17 render() { 10 18 const { attributes, setAttributes, blockData } = this.props; -
sites/trunk/wordcamp.org/public_html/wp-content/mu-plugins/blocks/includes/organizers.php
r8647 r8710 91 91 */ 92 92 function get_organizer_posts( array $attributes ) { 93 if ( empty( $attributes['mode'] ) ) { 94 return []; 95 } 96 93 97 $post_args = [ 94 98 'post_type' => 'wcb_organizer',
Note: See TracChangeset
for help on using the changeset viewer.