Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Component refactor and loading indicator stub #11

Merged
merged 18 commits into from
Jun 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
551 changes: 1 addition & 550 deletions dist/pwa-page.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/pwa-page.js.map

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions includes/page-templates.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,23 @@ function enqueue_assets() {
true
);

$urls = [
'schedule' => esc_url( site_url( __( 'schedule', 'wordcamp-pwa-page' ) ) ),
'posts' => esc_url( get_post_type_archive_link( 'post' ) ),
];

$pwa_config = /** @lang JavaScript */
<<<SCRIPT
window.WCPWAP = window.WCPWAP || {};

window.WCPWAP.urls = Object.assign(window.WCPWAP.urls || {}, {
schedule: '{$urls['schedule']}',
posts: '{$urls['posts']}',
});
SCRIPT;

wp_add_inline_script( 'wordcamp-pwa-page', $pwa_config, 'before' );

wp_enqueue_style(
'wordcamp-pwa-page',
WCPWAP_PLUGIN_URL . '/templates/assets/pwa-page.css',
Expand Down
104 changes: 104 additions & 0 deletions templates/assets/components/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { _x } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { fetchSessions, fetchTracks, fetchPosts } from '../api';
import { LatestPosts } from './posts';
import { Schedule } from './schedule';

/**
* Update every x seconds.
*
* @type {number}
*/
const UPDATE_INTERVAL = 60 * 1000; // Convert to milliseconds.

const entityLists = {
sessionList: fetchSessions,
trackList: fetchTracks,
postList: fetchPosts,
};

const DEFAULT_CONFIG = {
urls: {},
};

export class Page extends Component {

constructor( props ) {
super( props );

const config = window.WCPWAP || {};
this.updateIntervalId = -1;
this.state = {
config: { ...DEFAULT_CONFIG, ...config },
};

for ( const listName of Object.keys( entityLists ) ) {
this.state[ listName ] = {
isFetching: true,
error: null,
data: [],
};
}
}

/**
* Loop over the entityLists we defined earlier and set their initial state.
*/
updateLists() {
vdwijngaert marked this conversation as resolved.
Show resolved Hide resolved
let entityFetcher;
for ( const listName of Object.keys( entityLists ) ) {
entityFetcher = entityLists[ listName ];
entityFetcher().then( ( data ) => {
this.setState( { [ listName ]: { isFetching: false, data } } );
} ).catch( ( error ) => this.setState( ( state ) => ( {
...state,
[ listName ]: {
...state[ listName ],
isFetching: false,
error,
},
} ) ) );
}
}

componentDidMount() {
this.updateLists();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked closely, but does this need any kind of early return, to make sure we're not hitting the API every time this re-renders? It seems like React triggers a lot of re-renders (sometimes even multiple within a few milliseconds), and you have to be careful when hooking into it, to make sure you're not doing a lot of extra work and causing performance issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only when the component re-mounts. I don't think that will happen, as it is the root component, but I'm not sure.


this.updateIntervalId = window.setInterval( this.updateLists, UPDATE_INTERVAL );
}

componentWillUnmount() {
window.clearInterval( this.updateIntervalId );
}

render() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big deal, but it might be nice to move this to a separate file, like components/main-view.js, to separate the view from the controller. I think that's a common pattern in React.

Here's an example of what I mean:

https://github.com/iandunn/compassionate-comments/tree/b8f1690/admin/main

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true. I'm a fan of separation of concerns. This leaves some room for future improvements!

const { postList, sessionList, trackList, config } = this.state;

return <>
Copy link
Contributor

@iandunn iandunn Jun 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might need to use Fragment here, because support for <> was only recently added to G, and I don't think it'll be available until WP 5.3 is out. If you tested this in 5.2 and it worked, though, then it's fine to keep.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be supported, as it was implemented in React 16.2. As far as I can see, it has been in WP Core for quite some time. I'm on 5.2 and it works without a problem.

I find this to be less confusing than having a "Fragment" component. What do you think?

<div className="pwa-page-content">
<Schedule sessionList={ sessionList } trackList={ trackList } />

<a href={ config.urls.schedule } className="full-schedule">
{ _x( 'View Full Schedule', 'text', 'wordcamp-pwa-page' ) }
</a>
</div>

<div className="pwa-page-content">
<h2>{ _x( 'Latest Posts', 'title', 'wordcamp-pwa-page' ) }</h2>
<LatestPosts list={ postList } />

<a href={ config.urls.posts } className="all-posts">
{ _x( 'View all Posts', 'title', 'wordcamp-pwa-page' ) }
</a>
</div>
</>;
}

}
2 changes: 1 addition & 1 deletion templates/assets/components/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const Post = ( { post } ) => {
<span className="wordcamp-latest-post-excerpt">{ stripTagsAndEncodeText( excerpt ) }</span>
<span className="wordcamp-latest-post-categories">
{
categories.length && categories.map( ( c, index ) => {
!! categories.length && categories.map( ( c, index ) => {
const term = terms[ c ];
const {
name,
Expand Down
30 changes: 15 additions & 15 deletions templates/assets/components/posts.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
/**
* WordPress dependencies
*/
import { Fragment } from '@wordpress/element';

/**
* Internal dependencies
*/
import { Post } from './post';
import { LoadingIndicator } from './ui/loading';

export const LatestPosts = ( { posts } ) => (

<Fragment>
{ posts.filter( ( post ) => !! post ).map( ( post ) => {
export const LatestPosts = ( { list } ) => {
const { isFetching, data } = list;

return (
<Post key={ post.id } post={ post } />
);
} ) }
</Fragment>
if ( isFetching && data.length === 0 ) {
return <LoadingIndicator />;
}

);
return <>
{
data.filter( ( post ) => !! post ).map(
( post ) => <Post key={ post.id } post={ post } />
)
}
</>;
};
export default LatestPosts;
84 changes: 84 additions & 0 deletions templates/assets/components/schedule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* WordPress dependencies
*/
import { _x } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { LoadingIndicator } from './ui/loading';
/**
* External dependencies
*/
import { sortBy } from 'lodash';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the native sort() function may be able to replace this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used the original code (by @avillegasn ?). I haven't looked at the internals, maybe something to look into in the future.

import { SessionsGroup } from './sessions';

/**
* @param {{tracks: [], sessions: []}} data an object with tracks and sessions
* @returns {{next: *, now: *, track: T}[]} an array with schedule data
*/
const getScheduleData = ( data ) => {

const now = window.now || new Date();

if ( ! Array.isArray( data.tracks ) ) {
return;
}//end if

const scheduleData = data.tracks.map( ( track ) => {
const sessionsInTrack = sortBy(
data.sessions.filter(
( session ) => session.session_track.includes( track.id )
),
( sessionInTrack ) => sessionInTrack.meta._wcpt_session_time
);

const indexOfNextSession = sessionsInTrack.findIndex(
( session ) => now < new Date( session.meta._wcpt_session_time * 1000 )
);

const nextSession = sessionsInTrack[ indexOfNextSession ];
const nowSession = sessionsInTrack[ indexOfNextSession - 1 ];

return {
track,
now: nowSession,
next: nextSession,
};
} );

return scheduleData;
};

export const Schedule = ( { sessionList, trackList } ) => {

if ( ( sessionList.isFetching || trackList.isFetching ) && sessionList.data.length === 0 ) {
return <LoadingIndicator />;
}

const tracks = getScheduleData( { sessions: sessionList.data, tracks: trackList.data } );

const onNowSessions = tracks.map( ( track ) => {
return {
track: track.track,
session: track.now,
};
} ).filter( ( sessionInTrack ) => !! sessionInTrack.session );

const upNextSessions = tracks.map( ( track ) => {
return {
track: track.track,
session: track.next,
};
} );

return <>
{ !! onNowSessions.length &&
<SessionsGroup sessions={ onNowSessions } title={ _x( 'On now', 'title', 'wordcamp-pwa-page' ) } />
}
{ !! upNextSessions.length &&
<SessionsGroup sessions={ upNextSessions } title={ _x( 'Up next', 'title', 'wordcamp-pwa-page' ) } />
}
</>;
vdwijngaert marked this conversation as resolved.
Show resolved Hide resolved
};
export default Schedule;
18 changes: 13 additions & 5 deletions templates/assets/components/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,34 @@ import { keyBy, flatten } from 'lodash';
* WordPress dependencies
*/
import { stripTagsAndEncodeText } from '@wordpress/sanitize';
import { _x } from '@wordpress/i18n';

export const Session = ( { session } ) => {

const {
session: {
link,
link = '#',
title: {
rendered: title,
rendered: title = '',
},
session_date_time: {
time,
time = '',
},
session_category: sessionCategories,
session_category: sessionCategories = [],
_embedded: {
'wp:term': embeddedTerms,
'wp:term': embeddedTerms = {},
speakers = [],
},
meta: {
_wcpt_session_type: sessionType = '',
},
} = {
link: '#',
title: { rendered: _x( 'Track finished', 'session title', 'wordcamp-pwa-page' ) },
session_date_time: { time: '' },
session_category: [],
_embedded: { 'wp:term': {}, speakers: [] },
meta: { _wcpt_session_type: '' },
},
track: {
name: trackName,
Expand Down
30 changes: 13 additions & 17 deletions templates/assets/components/sessions.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
/**
* WordPress dependencies
*/
import { Fragment } from '@wordpress/element';

/**
* Internal dependencies
*/
import { Session } from './session';

export const SessionsGroup = ( { title, sessions } ) => (

<Fragment>
<h3>{ title }</h3>
{ sessions.filter( ( session ) => !! session ).map( ( session ) => {

return (
<Session key={ session.id } session={ session } />
);
} ) }
</Fragment>
export const SessionsGroup = ( { title, sessions } ) => <>
<h3>{ title }</h3>
{
sessions.filter( ( session ) => !! session ).map( ( session, index ) => {
const sessionKey = session.session ? session.session.id : index;

);
return <Session
key={ `${ sessionKey }-${ session.track.id }` }
session={ session }
/>;
}
)
}
</>;
12 changes: 12 additions & 0 deletions templates/assets/components/ui/loading.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* WordPress dependencies
*/
import { _x } from '@wordpress/i18n';

export const LoadingIndicator = () => {
return (
<div className="wordcamp-loading">
{ _x( 'Loading...', 'pwa-placeholder', 'wordcamp-pwa-page' ) }
</div>
);
};
Loading