Changeset 8712
- Timestamp:
- 05/01/2019 10:11:33 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/speakers/block-content.js
r8647 r8712 2 2 * External dependencies 3 3 */ 4 import { get } from 'lodash';4 import { get } from 'lodash'; 5 5 import classnames from 'classnames'; 6 6 … … 8 8 * WordPress dependencies 9 9 */ 10 const { Disabled } = wp.components;10 const { Disabled } = wp.components; 11 11 const { Component, Fragment } = wp.element; 12 const { __, _n } = wp.i18n;13 const { escapeAttribute } = wp.escapeHtml;12 const { __, _n } = wp.i18n; 13 const { escapeAttribute } = wp.escapeHtml; 14 14 15 15 /** 16 16 * Internal dependencies 17 17 */ 18 import { AvatarImage } from '../shared/avatar'; 19 import { ItemTitle, ItemHTMLContent, ItemPermalink } from '../shared/block-content'; 20 import { tokenSplit, arrayTokenReplace } from '../shared/i18n'; 21 import GridContentLayout from '../shared/grid-layout/block-content'; 18 import { AvatarImage } from '../shared/avatar'; 19 import { ItemTitle, ItemHTMLContent, ItemPermalink, BlockNoContent } from '../shared/block-content'; 20 import { tokenSplit, arrayTokenReplace } from '../shared/i18n'; 21 import GridContentLayout from '../shared/grid-layout/block-content'; 22 import { filterEntities } from '../blocks-store'; 23 22 24 import './block-content.scss'; 23 25 26 /** 27 * Component for the section of each speaker post that displays information about relevant sessions. 28 * 29 * @param {Object} props { 30 * @type {Object} speaker 31 * @type {Array} tracks 32 * } 33 * 34 * @return {Element} 35 */ 24 36 function SpeakerSessions( { speaker, tracks } ) { 25 37 const sessions = get( speaker, '_embedded.sessions', [] ); … … 84 96 } 85 97 98 /** 99 * Component for displaying the block content. 100 */ 86 101 class SpeakersBlockContent extends Component { 102 /** 103 * Run additional operations during component initialization. 104 * 105 * @param {Object} props 106 */ 107 constructor( props ) { 108 super( props ); 109 110 this.getFilteredPosts = this.getFilteredPosts.bind( this ); 111 } 112 113 /** 114 * Filter and sort the content that will be rendered. 115 * 116 * @returns {Array} 117 */ 118 getFilteredPosts() { 119 const { attributes, entities } = this.props; 120 const { wcb_speaker: posts } = entities; 121 const { mode, item_ids, sort } = attributes; 122 123 const args = {}; 124 125 if ( Array.isArray( item_ids ) && item_ids.length > 0 ) { 126 args.filter = [ 127 { 128 fieldName : mode === 'wcb_speaker' ? 'id' : 'speaker_group', 129 fieldValue : item_ids, 130 }, 131 ]; 132 } 133 134 args.sort = sort; 135 136 return filterEntities( posts, args ); 137 } 138 139 /** 140 * Render the block content. 141 * 142 * @return {Element} 143 */ 87 144 render() { 88 const { attributes, speakerPosts, tracks } = this.props; 89 const { 90 show_avatars, avatar_size, avatar_align, 91 content, show_session, 92 } = attributes; 145 const { attributes, entities } = this.props; 146 const { wcb_track: tracks } = entities; 147 const { show_avatars, avatar_size, avatar_align, content, show_session } = attributes; 148 149 const posts = this.getFilteredPosts(); 150 const isLoading = ! Array.isArray( posts ); 151 const hasPosts = ! isLoading && posts.length > 0; 152 153 if ( isLoading || ! hasPosts ) { 154 return ( 155 <BlockNoContent loading={ isLoading } /> 156 ); 157 } 93 158 94 159 return ( … … 97 162 { ...this.props } 98 163 > 99 { speakerPosts.map( ( post ) =>164 { posts.map( ( post ) => 100 165 <div 101 166 key={ post.slug } … … 142 207 /> 143 208 } 144 </div> ,209 </div> 145 210 ) } 146 211 </GridContentLayout> -
sites/trunk/wordcamp.org/public_html/wp-content/mu-plugins/blocks/assets/src/speakers/block-controls.js
r8526 r8712 8 8 */ 9 9 const { Button, Placeholder } = wp.components; 10 const { __ } = wp.i18n;10 const { __ } = wp.i18n; 11 11 12 12 /** 13 13 * Internal dependencies 14 14 */ 15 import { BlockControls, Placeholder NoContent, PlaceholderSpecificMode } from '../shared/block-controls';16 import SpeakersBlockContent from './block-content';17 import SpeakersSelect from './speakers-select';18 import { LABEL } 15 import { BlockControls, PlaceholderSpecificMode } from '../shared/block-controls'; 16 import SpeakersBlockContent from './block-content'; 17 import SpeakersSelect from './speakers-select'; 18 import { LABEL } from './index'; 19 19 20 /** 21 * Component for displaying a UI within the block. 22 */ 20 23 class SpeakersBlockControls extends BlockControls { 24 /** 25 * Render the internal block UI. 26 * 27 * @return {Element} 28 */ 21 29 render() { 22 const { icon, attributes, setAttributes , speakerPosts} = this.props;30 const { icon, attributes, setAttributes } = this.props; 23 31 const { mode } = attributes; 24 25 const hasPosts = Array.isArray( speakerPosts ) && speakerPosts.length;26 27 if ( mode && ! hasPosts ) {28 return (29 <PlaceholderNoContent30 icon={ icon }31 label={ LABEL }32 loading={ ! Array.isArray( speakerPosts ) }33 />34 );35 }36 32 37 33 let output; -
sites/trunk/wordcamp.org/public_html/wp-content/mu-plugins/blocks/assets/src/speakers/edit.js
r8611 r8712 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 /** 15 8 * Internal dependencies 16 9 */ 17 import SpeakersBlockControls from './block-controls';10 import SpeakersBlockControls from './block-controls'; 18 11 import SpeakersInspectorControls from './inspector-controls'; 19 import SpeakersToolbar from './toolbar'; 20 import { ICON } from './index'; 12 import SpeakersToolbar from './toolbar'; 13 import { ICON } from './index'; 14 import { WC_BLOCKS_STORE } from '../blocks-store'; 21 15 22 16 const blockData = window.WordCampBlocks.speakers || {}; 23 17 24 const MAX_POSTS = 100; 25 26 const ALL_POSTS_QUERY = { 27 orderby : 'title', 28 order : 'asc', 29 per_page : MAX_POSTS, 30 _embed : true, 31 }; 32 33 const ALL_TERMS_QUERY = { 34 orderby : 'name', 35 order : 'asc', 36 per_page : MAX_POSTS, 37 }; 38 18 /** 19 * Top-level component for the editing UI for the block. 20 */ 39 21 class SpeakersEdit extends Component { 40 constructor( props ) { 41 super( props ); 42 43 this.fetchSpeakers(); 44 } 45 46 fetchSpeakers() { 47 const allSpeakerPosts = apiFetch( { 48 path: addQueryArgs( `/wp/v2/speakers`, ALL_POSTS_QUERY ), 49 } ); 50 const allSpeakerTerms = apiFetch( { 51 path: addQueryArgs( `/wp/v2/speaker_group`, ALL_TERMS_QUERY ), 52 } ); 53 54 this.state = { 55 allSpeakerPosts : allSpeakerPosts, // Promise 56 allSpeakerTerms : allSpeakerTerms, // Promise 57 } 58 } 59 22 /** 23 * Render the block's editing UI. 24 * 25 * @return {Element} 26 */ 60 27 render() { 61 28 const { mode } = this.props.attributes; … … 66 33 icon={ ICON } 67 34 { ...this.props } 68 { ...this.state }69 35 /> 70 36 { mode && … … 79 45 } 80 46 81 const speakersSelect = ( select, props ) => { 82 const { mode, item_ids, sort } = props.attributes; 83 const { getEntityRecords } = select( 'core' ); 84 const [ orderby, order ] = split( sort, '_', 2 ); 47 const speakersSelect = ( select ) => { 48 const { getEntities } = select( WC_BLOCKS_STORE ); 85 49 86 const args = { 87 orderby : orderby, 88 order : order, 89 per_page : MAX_POSTS, // -1 is not allowed for per_page. 90 _embed : true, 91 context : 'view', 50 const entities = { 51 wcb_speaker : getEntities( 'postType', 'wcb_speaker', { _embed: true } ), 52 wcb_speaker_group : getEntities( 'taxonomy', 'wcb_speaker_group' ), 53 wcb_track : getEntities( 'taxonomy', 'wcb_track' ), 92 54 }; 93 55 94 if ( Array.isArray( item_ids ) ) {95 switch ( mode ) {96 case 'wcb_speaker':97 args.include = item_ids;98 break;99 case 'wcb_speaker_group':100 args.speaker_group = item_ids;101 break;102 }103 }104 105 const speakersQuery = pickBy( args, ( value ) => ! isUndefined( value ) );106 107 56 return { 108 blockData : blockData, 109 speakerPosts : getEntityRecords( 'postType', 'wcb_speaker', speakersQuery ), 110 tracks : getEntityRecords( 'taxonomy', 'wcb_track', { per_page: MAX_POSTS } ), 57 blockData, 58 entities, 111 59 }; 112 60 }; -
sites/trunk/wordcamp.org/public_html/wp-content/mu-plugins/blocks/assets/src/speakers/index.js
r8627 r8712 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/speakers/inspector-controls.js
r8647 r8712 33 33 }; 34 34 35 /** 36 * Component for block controls that appear in the Inspector Panel. 37 */ 35 38 class SpeakerInspectorControls extends Component { 39 /** 40 * Render the controls. 41 * 42 * @return {Element} 43 */ 36 44 render() { 37 45 const { attributes, setAttributes, blockData } = this.props; -
sites/trunk/wordcamp.org/public_html/wp-content/mu-plugins/blocks/assets/src/speakers/speakers-select.js
r8633 r8712 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 const { __ } = wp.i18n;10 const { __ } = wp.i18n; 12 11 13 12 /** 14 13 * Internal dependencies 15 14 */ 16 import { AvatarImage } from '../shared/avatar'; 17 import ItemSelect from '../shared/item-select'; 15 import ItemSelect, { buildOptions, Option } from '../shared/item-select'; 18 16 17 /** 18 * Component for selecting posts/terms for populating the block content. 19 */ 19 20 class SpeakersSelect extends Component { 21 /** 22 * Run additional operations during component initialization. 23 * 24 * @param {Object} props 25 */ 20 26 constructor( props ) { 21 27 super( props ); 22 28 23 this.state = { 24 wcb_speaker : [], 25 wcb_speaker_group : [], 26 loading : true, 27 }; 28 29 this.buildSelectOptions = this.buildSelectOptions.bind( this ); 30 this.fetchSelectOptions( props ); 29 this.buildSelectOptions = this.buildSelectOptions.bind( this ); 30 this.getCurrentSelectValue = this.getCurrentSelectValue.bind( this ); 31 this.isLoading = this.isLoading.bind( this ); 31 32 } 32 33 33 fetchSelectOptions( props ) { 34 const { allSpeakerPosts, allSpeakerTerms } = 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_speaker, wcb_speaker_group } = entities; 35 42 36 const parsedPosts = allSpeakerPosts.then( 37 ( fetchedPosts ) => { 38 const posts = fetchedPosts.map( ( post ) => { 39 return { 40 label : post.title.rendered.trim() || __( '(Untitled)', 'wordcamporg' ), 41 value : post.id, 42 type : 'wcb_speaker', 43 avatar : post.avatar_urls[ '24' ], 44 }; 45 } ); 43 const optionGroups = [ 44 { 45 entityType : 'post', 46 type : 'wcb_speaker', 47 label : __( 'Speakers', 'wordcamporg' ), 48 items : wcb_speaker, 49 }, 50 { 51 entityType : 'term', 52 type : 'wcb_speaker_group', 53 label : __( 'Groups', 'wordcamporg' ), 54 items : wcb_speaker_group, 55 }, 56 ]; 46 57 47 this.setState( { wcb_speaker: posts } ); 48 } 49 ); 50 51 const parsedTerms = allSpeakerTerms.then( 52 ( fetchedTerms ) => { 53 const terms = fetchedTerms.map( ( term ) => { 54 return { 55 label : term.name || __( '(Untitled)', 'wordcamporg' ), 56 value : term.id, 57 type : 'wcb_speaker_group', 58 count : term.count, 59 }; 60 } ); 61 62 this.setState( { wcb_speaker_group: terms } ); 63 } 64 ); 65 66 Promise.all( [ parsedPosts, parsedTerms ] ).then( () => { 67 this.setState( { loading: false } ); 68 } ); 58 return buildOptions( optionGroups ); 69 59 } 70 60 71 buildSelectOptions( mode ) { 72 const { getOwnPropertyDescriptors } = Object; 73 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; 74 69 75 const labels = { 76 wcb_speaker : __( 'Speakers', 'wordcamporg' ), 77 wcb_speaker_group : __( 'Groups', 'wordcamporg' ), 78 }; 79 80 for ( const type in getOwnPropertyDescriptors( this.state ) ) { 81 if ( ( ! mode || type === mode ) && this.state[ type ].length ) { 82 options.push( { 83 label : labels[ type ], 84 options : this.state[ type ], 85 } ); 86 } 87 } 88 89 return options; 90 } 91 92 render() { 93 const { label, icon, attributes, setAttributes } = this.props; 94 const { mode, item_ids } = attributes; 95 const options = this.buildSelectOptions( mode ); 70 const options = flatMap( this.buildSelectOptions(), ( group ) => { 71 return group.options; 72 } ); 96 73 97 74 let value = []; 98 75 99 76 if ( mode && item_ids.length ) { 100 const modeOptions = get( options, '[0].options', [] ); 101 102 value = modeOptions.filter( ( option ) => { 103 return includes( item_ids, option.value ); 77 value = options.filter( ( option ) => { 78 return mode === option.type && includes( item_ids, option.value ); 104 79 } ); 105 80 } 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, icon, setAttributes } = this.props; 106 105 107 106 return ( … … 109 108 className="wordcamp-speakers-select" 110 109 label={ label } 111 value={ value } 112 buildSelectOptions={ this.buildSelectOptions } 110 value={ this.getCurrentSelectValue() } 113 111 onChange={ ( changed ) => setAttributes( changed ) } 114 mode={ mode }115 112 selectProps={ { 116 isLoading : this.state.loading, 117 formatGroupLabel : ( groupData ) => { 113 options : this.buildSelectOptions(), 114 isLoading : this.isLoading(), 115 formatOptionLabel : ( optionData ) => { 118 116 return ( 119 <span className="wordcamp-item-select-option-group-label"> 120 { groupData.label } 121 </span> 122 ); 123 }, 124 formatOptionLabel: ( optionData ) => { 125 return ( 126 <SpeakersOption 127 icon={ icon } 117 <Option 118 icon={ 'wcb_speaker_group' === optionData.type ? icon : null } 128 119 { ...optionData } 129 120 /> … … 136 127 } 137 128 138 function SpeakersOption( { type, icon, label = '', avatar = '', count = 0 } ) {139 let image, content;140 141 switch ( type ) {142 case 'wcb_speaker' :143 image = (144 <AvatarImage145 className="wordcamp-item-select-option-avatar"146 name={ label }147 size={ 24 }148 url={ avatar }149 />150 );151 content = (152 <span className="wordcamp-item-select-option-label">153 { label }154 </span>155 );156 break;157 158 case 'wcb_speaker_group' :159 image = (160 <div className="wordcamp-item-select-option-icon-container">161 <Dashicon162 className="wordcamp-item-select-option-icon"163 icon={ icon }164 size={ 16 }165 />166 </div>167 );168 content = (169 <span className="wordcamp-item-select-option-label">170 { label }171 <span className="wordcamp-item-select-option-label-term-count">172 { count }173 </span>174 </span>175 );176 break;177 }178 179 return (180 <div className="wordcamp-item-select-option">181 { image }182 { content }183 </div>184 );185 }186 187 129 export default SpeakersSelect; -
sites/trunk/wordcamp.org/public_html/wp-content/mu-plugins/blocks/assets/src/speakers/toolbar.js
r8611 r8712 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 SpeakersToolbar 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/speakers.php
r8647 r8712 95 95 */ 96 96 function get_speaker_posts( array $attributes ) { 97 if ( empty( $attributes['mode'] ) ) { 98 return []; 99 } 100 97 101 $post_args = [ 98 102 'post_type' => 'wcb_speaker',
Note: See TracChangeset
for help on using the changeset viewer.