/** * External dependencies */ import Charts from './Components/Charts.js'; import ChartSelect from './Components/ChartSelect.js'; import ChartRender from './Components/ChartRender.js'; import { CSVToArray, buildChartPopup, tryGetPublishedChartData } from './utils.js'; /** * WordPress dependencies */ const { __ } = wp.i18n; const { apiFetch, apiRequest } = wp; const { Component, Fragment } = wp.element; const { Button, ButtonGroup, Dashicon, Placeholder, Notice, Spinner } = wp.components; class Editor extends Component { constructor() { super( ...arguments ); this.getChart = this.getChart.bind( this ); this.editChart = this.editChart.bind( this ); this.editSettings = this.editSettings.bind( this ); this.editPermissions = this.editPermissions.bind( this ); this.readUploadedFile = this.readUploadedFile.bind( this ); this.editURL = this.editURL.bind( this ); this.editSchedule = this.editSchedule.bind( this ); this.editJSONSchedule = this.editJSONSchedule.bind( this ); this.editJSONURL = this.editJSONURL.bind( this ); this.editJSONHeaders = this.editJSONHeaders.bind( this ); this.editJSONRoot = this.editJSONRoot.bind( this ); this.editJSONPaging = this.editJSONPaging.bind( this ); this.JSONImportData = this.JSONImportData.bind( this ); this.editDatabaseSchedule = this.editDatabaseSchedule.bind( this ); this.databaseImportData = this.databaseImportData.bind( this ); this.uploadData = this.uploadData.bind( this ); this.getChartData = this.getChartData.bind( this ); this.editChartData = this.editChartData.bind( this ); this.updateChart = this.updateChart.bind( this ); this.createChart = this.createChart.bind( this ); this.state = { /** * Block Route Status * * home - Initial screen. * showCharts - Display list of charts to pick. * chartSelect - Chart selected. * renderChart - Chart render. */ route: ( this.props.attributes.route ? this.props.attributes.route : 'home' ), chart: null, isModified: false, isLoading: false, isScheduled: false }; } async componentDidMount() { // Fetch review again if block loaded after saving. if ( this.props.attributes.id ) { let result = await apiFetch({ path: `wp/v2/visualizer/${this.props.attributes.id}` }).catch( function( error ) { }); if ( result ) { this.setState({ chart: result['chart_data'] }); } else { // if the chart is not found. this.setState({ route: 'error' }); } } } async getChart( id ) { await this.setState({ isLoading: 'getChart' }); const chartDataRequest = await tryGetPublishedChartData( id ); if ( 'publish' !== chartDataRequest.chartStatus ) { this.setState({ route: 'showCharts', isLoading: false }); this.props.setAttributes({ route: 'showCharts' }); return; } this.setState({ route: 'chartSelect', chart: chartDataRequest.result['chart_data'], isLoading: true, isModified: true }); this.props.setAttributes({ id, route: 'chartSelect', lazy: -1 }); } editChart() { this.setState({ route: 'chartSelect' }); this.props.setAttributes({ route: 'chartSelect' }); } editSettings( settings ) { let chart = { ...this.state.chart }; if ( '1' !== settings.pagination ) { delete settings.pageSize; } chart['visualizer-settings'] = settings; this.setState({ chart, isModified: true }); } editPermissions( permissions ) { let chart = { ...this.state.chart }; chart['visualizer-permissions'] = permissions; this.setState({ chart, isModified: true }); } readUploadedFile( e ) { const fileTobeRead = e.current.files[0]; const fileReader = new FileReader(); fileReader.onload = () => { const data = CSVToArray( fileReader.result ); this.editChartData( data, 'Visualizer_Source_Csv' ); }; fileReader.readAsText( fileTobeRead ); } editURL( url ) { let chart = { ...this.state.chart }; chart['visualizer-chart-url'] = url; this.setState({ chart }); } editSchedule( schedule ) { let chart = { ...this.state.chart }; chart['visualizer-chart-schedule'] = schedule; this.setState({ chart, isModified: true }); } editJSONSchedule( schedule ) { let chart = { ...this.state.chart }; chart['visualizer-json-schedule'] = schedule; this.setState({ chart, isModified: true }); } editJSONURL( url ) { let chart = { ...this.state.chart }; chart['visualizer-json-url'] = url; this.setState({ chart }); } editJSONHeaders( headers ) { let chart = { ...this.state.chart }; delete headers.username; delete headers.password; chart['visualizer-json-headers'] = headers; this.setState({ chart }); } editJSONRoot( root ) { let chart = { ...this.state.chart }; chart['visualizer-json-root'] = root; this.setState({ chart }); } editJSONPaging( root ) { let chart = { ...this.state.chart }; chart['visualizer-json-paging'] = root; this.setState({ chart }); } JSONImportData( name, series, data ) { let chart = { ...this.state.chart }; chart['visualizer-source'] = name; chart['visualizer-default-data'] = 0; chart['visualizer-series'] = series; chart['visualizer-data'] = data; this.setState({ chart, isModified: true }); } editDatabaseSchedule( schedule ) { let chart = { ...this.state.chart }; chart['visualizer-db-schedule'] = schedule; this.setState({ chart, isModified: true }); } databaseImportData( query, name, series, data ) { let chart = { ...this.state.chart }; chart['visualizer-source'] = name; chart['visualizer-default-data'] = 0; chart['visualizer-series'] = series; chart['visualizer-data'] = data; chart['visualizer-db-query'] = query; this.setState({ chart, isModified: true }); } uploadData( scheduled = false ) { this.setState({ isLoading: 'uploadData', isScheduled: scheduled }); apiRequest({ path: `/visualizer/v1/upload-data?url=${this.state.chart['visualizer-chart-url']}`, method: 'POST' }).then( ( data ) => { if ( 2 <= Object.keys( data ).length ) { let chart = { ...this.state.chart }; chart['visualizer-source'] = 'Visualizer_Source_Csv_Remote'; chart['visualizer-default-data'] = 0; chart['visualizer-series'] = data.series; chart['visualizer-data'] = data.data; let series = chart['visualizer-series']; let settings = chart['visualizer-settings']; let map = series; let fieldName = 'series'; if ( 'pie' === chart['visualizer-chart-type']) { map = chart['visualizer-data']; fieldName = 'slices'; } map.map( ( i, index ) => { if ( 'pie' !== chart['visualizer-chart-type'] && 0 === index ) { return; } const seriesIndex = 'pie' !== chart['visualizer-chart-type'] ? index - 1 : index; if ( settings[fieldName][seriesIndex] === undefined ) { settings[fieldName][seriesIndex] = {}; settings[fieldName][seriesIndex].temp = 1; } }); settings[fieldName] = settings[fieldName].filter( ( i, index ) => { const length = 'pie' !== chart['visualizer-chart-type'] ? map.length - 1 : map.length; return index < length; }); chart['visualizer-settings'] = settings; this.setState({ chart, isModified: true, isLoading: false }); return data; } this.setState({ isLoading: false }); }, ( err ) => { this.setState({ isLoading: false }); return err; } ); } async getChartData( id ) { await this.setState({ isLoading: 'getChartData' }); let result = await apiFetch({ path: `wp/v2/visualizer/${id}` }); let chart = { ...this.state.chart }; chart['visualizer-source'] = 'Visualizer_Source_Csv'; chart['visualizer-default-data'] = 0; chart['visualizer-series'] = result['chart_data']['visualizer-series']; chart['visualizer-data'] = result['chart_data']['visualizer-data']; this.setState({ isLoading: false, chart }); } editChartData( chartData, source ) { let chart = { ...this.state.chart }; let series = []; let settings = { ...chart['visualizer-settings'] }; let type = chart['visualizer-chart-type']; chartData[0].map( ( i, index ) => { series[index] = { label: i, type: chartData[1][index] }; }); chartData.splice( 0, 2 ); let map = series; let fieldName = 'series'; switch ( type ) { case 'pie': map = chartData; fieldName = 'slices'; // pie charts are finicky about a number being a number // and editing a number makes it a string // so let's convert it back into a number. chartData.map( ( i, index ) => { switch ( series[1].type ) { case 'number': i[1] = parseFloat( i[1]); break; } }); break; case 'tabular': // table charts are finicky about a boolean being a boolean // and editing a boolean makes it a string // so let's convert it back into a boolean. chartData.map( ( i, index ) => { series.map( ( seriesObject, seriesIndex ) => { switch ( seriesObject.type ) { case 'boolean': if ( 'string' === typeof i[seriesIndex]) { i[seriesIndex] = 'true' === i[seriesIndex]; } break; } }); }); break; } map.map( ( i, index ) => { if ( 'pie' !== type && 0 === index ) { return; } const seriesIndex = 'pie' !== type ? index - 1 : index; if ( Array.isArray( settings[fieldName]) && settings[fieldName][seriesIndex] === undefined ) { settings[fieldName][seriesIndex] = {}; settings[fieldName][seriesIndex].temp = 1; } }); if ( Array.isArray( settings[fieldName]) ) { settings[fieldName] = settings[fieldName].filter( ( i, index ) => { const length = -1 >= [ 'pie', 'tabular', 'dataTable' ].indexOf( type ) ? map.length - 1 : map.length; return index < length; }); } chart['visualizer-source'] = source; chart['visualizer-default-data'] = 0; chart['visualizer-data'] = chartData; chart['visualizer-series'] = series; chart['visualizer-settings'] = settings; chart['visualizer-chart-url'] = ''; this.setState({ chart, isModified: true, isScheduled: false }); } updateChart() { this.setState({ isLoading: 'updateChart' }); const data = this.state.chart; if ( false === this.state.isScheduled ) { data['visualizer-chart-schedule'] = ''; } let dataChartStylingOption = 'series'; if ( 'pie' === data['visualizer-chart-type']) { dataChartStylingOption = 'slices'; } // no series for bubble and timeline charts. if ( undefined !== data['visualizer-settings'][dataChartStylingOption] && -1 >= [ 'bubble', 'timeline' ].indexOf( data['visualizer-chart-type']) ) { Object.keys( data['visualizer-settings'][dataChartStylingOption]) .map( i => { if ( data['visualizer-settings'][dataChartStylingOption][i] !== undefined ) { if ( data['visualizer-settings'][dataChartStylingOption][i].temp !== undefined ) { delete data['visualizer-settings'][dataChartStylingOption][i].temp; } } } ); } apiRequest({ path: `/visualizer/v1/update-chart?id=${ this.props.attributes.id }`, method: 'POST', data: data }).then( ( data ) => { this.setState({ isLoading: false, isModified: false }); return data; }, ( err ) => { return err; } ); } /** * Create a new chart via popup. * * @returns {void} */ async createChart() { // Use the same popup like in Chart Library. const createChartPopup = new ( buildChartPopup() )({ action: visualizerLocalize.createChart }); // eslint-disable-next-line camelcase window.send_to_editor = () => { createChartPopup.close(); }; window.parent.addEventListener( 'message', ( event ) => { if ( 'visualizer:mediaframe:close' === event.data ) { createChartPopup.close(); } else if ( event.data.chartID ) { const chartID = parseInt( event.data.chartID, 10 ); this.getChart( chartID ); } }, false ); createChartPopup.open(); } render() { if ( 'error' === this.state.route ) { return ( { __( 'This chart is not available; it might have been deleted. Please delete this block and resubmit your chart.' ) } ); } if ( '1' === visualizerLocalize.isFullSiteEditor ) { return ( { __( 'Visualizer block charts are currently not available for selection here, you must visit the library, get the shortcode, and add the chart here in a shortcode tag.' ) } ); } if ( 'renderChart' === this.state.route && null !== this.state.chart ) { return ( ); } return (
{ __( 'Visualizer' ) }
{ 'home' === this.state.route && (
{ __( 'Make a new chart or display an existing one?' ) }
{ /* You can apply "locked" class to lock any of the following list items. */ } { __( 'Create a new chart' ) }
{ this.setState({ route: 'showCharts' }); this.props.setAttributes({ route: 'showCharts' }); } } > { __( 'Display an existing chart' ) }
) } { ( ( 'getChart' === this.state.isLoading ) || ( 'chartSelect' === this.state.route && null === this.state.chart ) || ( 'renderChart' === this.state.route && null === this.state.chart ) ) && ( ) } { ( 'showCharts' === this.state.route && false === this.state.isLoading ) && } { ( 'chartSelect' === this.state.route && null !== this.state.chart ) && }
{ ( 'showCharts' === this.state.route || 'chartSelect' === this.state.route ) && { 'chartSelect' === this.state.route && { false === this.state.isModified ? : } } }
); } } export default Editor;