-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[UI] Implement DataViews in editor (#251)
* Add DataViews to rdb editor Signed-off-by: brookewp <[email protected]> * Add filters and type for shopify demo Signed-off-by: brookewp <[email protected]> * Temp add images Signed-off-by: brookewp <[email protected]> * Combine list and search modal Signed-off-by: brookewp <[email protected]> * Clean up and show media with title when present Signed-off-by: brookewp <[email protected]> * Move logic for filtering out IDs so they remain searchable Signed-off-by: brookewp <[email protected]> * Fix no results from search Signed-off-by: brookewp <[email protected]> * Add custom button and remove list view for now Signed-off-by: brookewp <[email protected]> * Set pagination to bottom of modal Signed-off-by: brookewp <[email protected]> * Consolidate and remove filters temporarily Signed-off-by: brookewp <[email protected]> * Update src/blocks/remote-data-container/components/modals/DataViewsModal.tsx Co-authored-by: Max Schmeling <[email protected]> * Update to show search title Signed-off-by: brookewp <[email protected]> * lint fix Signed-off-by: brookewp <[email protected]> --------- Signed-off-by: brookewp <[email protected]> Co-authored-by: Max Schmeling <[email protected]>
- Loading branch information
1 parent
c61e82c
commit 0b64945
Showing
10 changed files
with
260 additions
and
175 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
176 changes: 141 additions & 35 deletions
176
src/blocks/remote-data-container/components/item-list/ItemList.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,52 +1,158 @@ | ||
import { Spinner } from '@wordpress/components'; | ||
import { useInstanceId } from '@wordpress/compose'; | ||
import { DataViews, filterSortAndPaginate, View } from '@wordpress/dataviews/wp'; | ||
import { useEffect, useMemo, useState } from '@wordpress/element'; | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
import { ItemPreview } from '@/blocks/remote-data-container/components/item-list/ItemPreview'; | ||
import { | ||
cloneBlockWithAttributes, | ||
usePatterns, | ||
} from '@/blocks/remote-data-container/hooks/usePatterns'; | ||
import { __ } from '@/utils/i18n'; | ||
import { usePatterns } from '@/blocks/remote-data-container/hooks/usePatterns'; | ||
|
||
interface ItemListProps { | ||
blockName: string; | ||
loading: boolean; | ||
noResultsText: string; | ||
onSelect: ( data: RemoteDataQueryInput ) => void; | ||
placeholderText: string; | ||
results?: RemoteData[ 'results' ]; | ||
searchTerms: string; | ||
setSearchTerms: ( newValue: string ) => void; | ||
} | ||
|
||
export function ItemList( props: ItemListProps ) { | ||
const { defaultPattern: pattern } = usePatterns( props.blockName ); | ||
const { blockName, loading, onSelect, results, searchTerms, setSearchTerms } = props; | ||
const { defaultPattern: pattern } = usePatterns( blockName ); | ||
|
||
if ( props.loading || ! pattern ) { | ||
return <Spinner />; | ||
} | ||
const instanceId = useInstanceId( ItemList, blockName ); | ||
|
||
if ( ! props.results ) { | ||
return <p>{ __( props.placeholderText ) }</p>; | ||
} | ||
// ensure each result has an 'id' key | ||
const data = useMemo( () => { | ||
return ( results ?? [] ).map( ( item: Record< string, unknown > ) => | ||
item.id | ||
? item | ||
: { | ||
...item, | ||
id: Object.keys( item ).find( key => /(^|_)(id)$/i.test( key ) ) // Regex to match 'id' or part of '_id' | ||
? item[ Object.keys( item ).find( key => /(^|_)(id)$/i.test( key ) ) as string ] | ||
: instanceId, | ||
} | ||
) as RemoteData[ 'results' ]; | ||
}, [ results ] ); | ||
|
||
if ( props.results.length === 0 ) { | ||
return <p>{ __( props.noResultsText ) }</p>; | ||
} | ||
// get fields from results data to use as columns | ||
const { fields, mediaField, tableFields, titleField } = useMemo( () => { | ||
const getFields: string[] = Array.from( | ||
new Set( | ||
data | ||
?.flatMap( item => Object.keys( item ) ) | ||
.filter( ( key: string ) => ! /(^|_)(id)$/i.test( key ) ) // Filters out keys containing 'id' or similar patterns | ||
) | ||
); | ||
|
||
// generic search for title | ||
const title: string = | ||
getFields.find( | ||
( field: string ) => | ||
field.toLowerCase().includes( 'title' ) || field.toLowerCase().includes( 'name' ) | ||
) || ''; | ||
|
||
// generic search for media | ||
const media: string = | ||
getFields.find( | ||
( field: string ) => | ||
field.toLowerCase().includes( 'url' ) || field.toLowerCase().includes( 'image' ) | ||
) || ''; | ||
|
||
const fieldObject: { | ||
id: string; | ||
label: string; | ||
enableGlobalSearch: boolean; | ||
render?: ( { item }: { item: RemoteData[ 'results' ][ 0 ] } ) => JSX.Element; | ||
enableSorting: boolean; | ||
}[] = getFields.map( field => { | ||
return { | ||
id: field, | ||
label: field ?? '', | ||
enableGlobalSearch: true, | ||
render: | ||
field === media | ||
? ( { item }: { item: RemoteData[ 'results' ][ 0 ] } ) => { | ||
return ( | ||
<img | ||
// temporary until we pull in more data | ||
alt="" | ||
src={ item[ field ] as string } | ||
/> | ||
); | ||
} | ||
: undefined, | ||
enableSorting: field !== media, | ||
}; | ||
} ); | ||
|
||
return { fields: fieldObject, tableFields: getFields, titleField: title, mediaField: media }; | ||
}, [ data ] ); | ||
|
||
const [ view, setView ] = useState< View >( { | ||
type: 'table' as const, | ||
perPage: 8, | ||
page: 1, | ||
search: '', | ||
fields: [], | ||
filters: [], | ||
layout: {}, | ||
titleField, | ||
mediaField, | ||
} ); | ||
|
||
const defaultLayouts = mediaField | ||
? { | ||
table: {}, | ||
grid: {}, | ||
} | ||
: { table: {} }; | ||
|
||
// this prevents just an empty table rendering | ||
useEffect( () => { | ||
if ( tableFields.length > 0 ) { | ||
setView( prevView => ( { | ||
...prevView, | ||
fields: tableFields.filter( field => field !== mediaField ), | ||
} ) ); | ||
} | ||
}, [ mediaField, tableFields ] ); | ||
|
||
useEffect( () => { | ||
if ( view.search !== searchTerms ) { | ||
setSearchTerms( view.search ?? '' ); | ||
} | ||
}, [ view, searchTerms ] ); | ||
|
||
// filter, sort and paginate data | ||
const { data: filteredData, paginationInfo } = useMemo( () => { | ||
return filterSortAndPaginate( data ?? [], view, fields ); | ||
}, [ data, view ] ); | ||
|
||
const actions = [ | ||
{ | ||
id: 'choose', | ||
icon: <>{ __( 'Choose' ) }</>, | ||
isPrimary: true, | ||
label: '', | ||
callback: ( items: RemoteData[ 'results' ] ) => { | ||
items.map( item => onSelect( item ) ); | ||
}, | ||
}, | ||
]; | ||
|
||
return ( | ||
<ul> | ||
{ props.results.map( ( result, index ) => { | ||
const blocks = | ||
pattern?.blocks.map( block => | ||
cloneBlockWithAttributes( block, result, props.blockName ) | ||
) ?? []; | ||
|
||
return ( | ||
<ItemPreview | ||
key={ index } | ||
blocks={ blocks } | ||
onSelect={ () => props.onSelect( result ) } | ||
/> | ||
); | ||
} ) } | ||
</ul> | ||
<DataViews | ||
actions={ actions } | ||
data={ filteredData } | ||
defaultLayouts={ defaultLayouts } | ||
fields={ fields } | ||
getItemId={ ( item: { id?: string } ) => item.id || '' } | ||
isLoading={ loading || ! pattern || ! results || results.length === 0 } | ||
isItemClickable={ () => true } | ||
onClickItem={ item => onSelect( item ) } | ||
onChangeView={ setView } | ||
paginationInfo={ paginationInfo } | ||
view={ view } | ||
/> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
src/blocks/remote-data-container/components/modals/DataViewsModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
import { ModalWithButtonTrigger } from './BaseModal'; | ||
import { useModalState } from '../../hooks/useModalState'; | ||
import { ItemList } from '../item-list/ItemList'; | ||
import { useSearchResults } from '@/blocks/remote-data-container/hooks/useSearchResults'; | ||
import { sendTracksEvent } from '@/blocks/remote-data-container/utils/tracks'; | ||
import { getBlockDataSourceType } from '@/utils/localized-block-data'; | ||
|
||
interface DataViewsModalProps { | ||
blockName: string; | ||
headerImage?: string; | ||
onSelect: ( data: RemoteDataQueryInput ) => void; | ||
queryKey: string; | ||
title: string; | ||
} | ||
|
||
export const DataViewsModal: React.FC< DataViewsModalProps > = props => { | ||
const { blockName, onSelect, queryKey, title } = props; | ||
|
||
const { loading, results, searchTerms, setSearchTerms } = useSearchResults( { | ||
blockName, | ||
queryKey, | ||
} ); | ||
|
||
const { close, isOpen, open } = useModalState(); | ||
|
||
function onSelectItem( data: RemoteDataQueryInput ): void { | ||
onSelect( data ); | ||
sendTracksEvent( 'remotedatablocks_add_block', { | ||
action: 'select_item', | ||
selected_option: 'search_from_list', | ||
data_source_type: getBlockDataSourceType( blockName ), | ||
} ); | ||
close(); | ||
} | ||
|
||
return ( | ||
<ModalWithButtonTrigger | ||
buttonText={ __( 'Choose' ) } | ||
className="rdb-editor_data-views-modal" | ||
isOpen={ isOpen } | ||
onClose={ close } | ||
onOpen={ open } | ||
title={ title } | ||
> | ||
<ItemList | ||
blockName={ props.blockName } | ||
loading={ loading } | ||
onSelect={ onSelectItem } | ||
results={ results } | ||
searchTerms={ searchTerms } | ||
setSearchTerms={ setSearchTerms } | ||
/> | ||
</ModalWithButtonTrigger> | ||
); | ||
}; |
46 changes: 0 additions & 46 deletions
46
src/blocks/remote-data-container/components/modals/ItemListModal.tsx
This file was deleted.
Oops, something went wrong.
29 changes: 0 additions & 29 deletions
29
src/blocks/remote-data-container/components/modals/ListModal.tsx
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.