Skip to content

Commit

Permalink
[UI] Data source code snippets (#321)
Browse files Browse the repository at this point in the history
* Export snackbar and move to match designs

Signed-off-by: brookewp <[email protected]>

* Create code snippet component

Signed-off-by: brookewp <[email protected]>

* Add to settings page

Signed-off-by: brookewp <[email protected]>

* Add method to fetch snippets

Signed-off-by: brookewp <[email protected]>

* Replace duplicate snackbar function

Signed-off-by: brookewp <[email protected]>

* Update logic and replace modal for working esc

Signed-off-by: brookewp <[email protected]>

* Add TabPanel to handle array of snippets

Signed-off-by: brookewp <[email protected]>

* Add title and icon to code snippet modal

Signed-off-by: brookewp <[email protected]>

* Switch to light build

Signed-off-by: brookewp <[email protected]>

---------

Signed-off-by: brookewp <[email protected]>
  • Loading branch information
brookewp authored Feb 1, 2025
1 parent 4209f7d commit 7a968e8
Show file tree
Hide file tree
Showing 9 changed files with 486 additions and 33 deletions.
290 changes: 289 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"@playwright/test": "^1.50.0",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.2.0",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/wordpress__block-editor": "11.5.16",
"@types/wordpress__blocks": "12.5.16",
"@vitest/coverage-v8": "3.0.4",
Expand Down Expand Up @@ -111,6 +112,7 @@
},
"dependencies": {
"@automattic/calypso-analytics": "^1.1.3",
"@wordpress/dataviews": "^4.10.0"
"@wordpress/dataviews": "^4.10.0",
"react-syntax-highlighter": "^15.6.1"
}
}
10 changes: 4 additions & 6 deletions src/blocks/remote-data-container/components/modals/BaseModal.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { Button, Modal } from '@wordpress/components';
import { ModalProps } from '@wordpress/components/build-types/modal/types';

import { __ } from '@/utils/i18n';

export interface BaseModalProps {
export type BaseModalProps = Omit< ModalProps, 'onRequestClose' > & {
children: JSX.Element;
className?: string;
headerActions?: JSX.Element;
headerImage?: string;
onClose: () => void;
size?: 'small' | 'medium' | 'large' | 'fill';
title: string;
}
};

export function BaseModal( props: BaseModalProps ) {
return (
Expand All @@ -30,7 +28,7 @@ export function BaseModal( props: BaseModalProps ) {
}
onRequestClose={ props.onClose }
size={ props.size ?? 'fill' }
title={ props.title }
{ ...props }
>
{ props.children }
</Modal>
Expand Down
30 changes: 30 additions & 0 deletions src/data-sources/DataSourceList.scss
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,33 @@ table.data-source-list {
padding: 4px 8px;
vertical-align: baseline;
}

.settings_page_remote-data-blocks-settings .components-modal__frame.has-size-medium:not(.components-confirm-dialog) {
max-width: calc(100% - 32px);
max-height: calc(100% - 120px);

.components-modal__header {
padding: 24px 32px 8px 56px;
}
}

.rdb-settings-page_data-source-code-snippet {

.components-tab-panel__tabs {
border-bottom: 1px solid var(--Alias-border-border-input, #aeaeae);
// to match tab panel in editor sidebar
height: 48px;
}

.components-tab-panel__tab-content {
border: 1px solid var(--Alias-border-border-input, #aeaeae);
border-top: none;

}
}

.rdb-settings-page_data-source-code-snippet-modal svg {
width: 25px;
margin-right: 8px;
}

94 changes: 75 additions & 19 deletions src/data-sources/DataSourceList.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {
__experimentalConfirmDialog as ConfirmDialog,
ExternalLink,
Icon,
Placeholder,
TabPanel,
} from '@wordpress/components';
import { useDispatch } from '@wordpress/data';
import {
Action,
DataViews,
Expand All @@ -14,9 +15,11 @@ import {
import { useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { info } from '@wordpress/icons';
import { store as noticesStore, NoticeStoreActions, WPNotice } from '@wordpress/notices';

import CodeSnippet from './components/CodeSnippet';
import { SUPPORTED_SERVICES, SUPPORTED_SERVICES_LABELS } from './constants';
import { BaseModal } from '@/blocks/remote-data-container/components/modals/BaseModal';
import { useModalState } from '@/blocks/remote-data-container/hooks/useModalState';
import DataSourceMetaTags from '@/data-sources/DataSourceMetaTags';
import { useDataSources } from '@/data-sources/hooks/useDataSources';
import { DataSourceConfig } from '@/data-sources/types';
Expand All @@ -28,19 +31,27 @@ import HttpIcon from '@/settings/icons/HttpIcon';
import { ShopifyIcon } from '@/settings/icons/ShopifyIcon';

const DataSourceList = () => {
const { createSuccessNotice, createErrorNotice } =
useDispatch< NoticeStoreActions >( noticesStore );
const {
dataSources,
loadingDataSources,
deleteDataSource,
deleteMultipleDataSources,
fetchDataSources,
getDataSourceSnippet,
addDataSource,
showSnackbar,
} = useDataSources();
const [ dataSourceToDelete, setDataSourceToDelete ] = useState<
DataSourceConfig | DataSourceConfig[] | null
>( null );
const [ codeSnippets, setCodeSnippets ] = useState<
{
name: string;
code: string;
}[]
>( [] );
const [ currentSource, setCurrentSource ] = useState< DataSourceConfig | null >( null );
const { close, isOpen, open } = useModalState();
const { pushState } = useSettingsContext();

const onCancelDeleteDialog = () => {
Expand Down Expand Up @@ -71,21 +82,6 @@ const DataSourceList = () => {
return SUPPORTED_SERVICES_LABELS[ service ] ?? 'HTTP';
};

function showSnackbar( type: 'success' | 'error', message: string ): void {
const SNACKBAR_OPTIONS: Partial< WPNotice > = {
isDismissible: true,
};

switch ( type ) {
case 'success':
createSuccessNotice( message, { ...SNACKBAR_OPTIONS, icon: '✅' } );
break;
case 'error':
createErrorNotice( message, { ...SNACKBAR_OPTIONS, icon: '❌' } );
break;
}
}

const getServiceIcon = (
service: ( typeof SUPPORTED_SERVICES )[ number ]
): React.ReactElement => {
Expand Down Expand Up @@ -241,6 +237,26 @@ const DataSourceList = () => {
}
},
},
{
id: 'view-code',
label: __( 'View Code', 'remote-data-blocks' ),
isEligible: ( item: DataSourceConfig ) => Boolean( item?.uuid ),
callback: ( [ item ]: DataSourceConfig[] ) => {
if ( item?.uuid ) {
setCurrentSource( item );
getDataSourceSnippet( item.uuid )
.then( snippets => {
if ( snippets ) {
setCodeSnippets( snippets );
open();
}
} )
.catch( () => {
showSnackbar( 'error', __( 'Failed to load code snippets.', 'remote-data-blocks' ) );
} );
}
},
},
];

if ( dataSources.length === 0 ) {
Expand Down Expand Up @@ -289,6 +305,46 @@ const DataSourceList = () => {
) }
</ConfirmDialog>
) }
{ codeSnippets && isOpen && (
<BaseModal
className="rdb-settings-page_data-source-code-snippet-modal"
icon={ getServiceIcon( currentSource?.service ?? 'generic-http' ) }
title={ __(
`${ currentSource?.service_config.display_name }: Data Source Code`,
'remote-data-blocks'
) }
onClose={ () => {
close();
setCodeSnippets( [] ); // Clear snippets when closing
} }
>
<>
<p style={ { marginBottom: '16px', padding: '0 8px' } }>
{ __(
"Below, you'll find the code used to register the block(s) for this data source, which can be used as a reference for extending the data source.\nTo get started, copy the code below and add it to your plugin directory. "
) }
<ExternalLink href="https://remotedatablocks.com/docs/extending/index/">
{ __( 'Learn more about extending', 'remote-data-blocks' ) }
</ExternalLink>
</p>
<TabPanel
className="rdb-settings-page_data-source-code-snippet"
tabs={ codeSnippets.map( ( { name } ) => ( {
name,
title: name,
} ) ) }
>
{ tab => {
return (
<CodeSnippet
code={ codeSnippets.find( snippet => snippet.name === tab.name )?.code ?? '' }
/>
);
} }
</TabPanel>
</>
</BaseModal>
) }
</>
);
};
Expand Down
55 changes: 55 additions & 0 deletions src/data-sources/components/CodeSnippet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { copy } from '@wordpress/icons';
import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter';
import php from 'react-syntax-highlighter/dist/esm/languages/prism/php';
import coy from 'react-syntax-highlighter/dist/esm/styles/prism/coy';

import { useDataSources } from '../hooks/useDataSources';

SyntaxHighlighter.registerLanguage( 'php', php );

const CodeSnippet = ( { code }: { code: string } ) => {
const { showSnackbar } = useDataSources();

const handleCopy = () => {
navigator.clipboard
.writeText( code )
.then( () => {
showSnackbar( 'success', __( 'Code copied to clipboard!', 'remote-data-blocks' ) );
} )
.catch( () => {
showSnackbar( 'error', __( 'Failed to copy code', 'remote-data-blocks' ) );
} );
};

return (
<div
style={ {
position: 'relative',
marginBottom: '1rem',
padding: '16px',
} }
>
<Button
onClick={ handleCopy }
icon={ copy }
variant="tertiary"
style={ {
position: 'absolute',
top: '8px',
right: '8px',
zIndex: '11',
background: '#fff',
} }
>
{ __( 'Copy' ) }
</Button>
<SyntaxHighlighter language="php" style={ coy } showLineNumbers>
{ code }
</SyntaxHighlighter>
</div>
);
};

export default CodeSnippet;
26 changes: 26 additions & 0 deletions src/data-sources/hooks/useDataSources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,30 @@ export const useDataSources = < SourceConfig extends DataSourceConfig = DataSour
);
}

async function getDataSourceSnippet( uuid: DataSourceConfig[ 'uuid' ] ) {
try {
const response = await apiFetch( {
path: `${ REST_BASE_DATA_SOURCES }/snippets/${ uuid }`,
method: 'GET',
} );
const result = response as {
snippets: {
name: string;
code: string;
}[];
};
return result.snippets;
} catch ( error ) {
if ( error instanceof Error ) {
showSnackbar(
'error',
sprintf( __( 'Failed to get code snippet: %s', 'remote-data-blocks' ), error.message )
);
}
throw error;
}
}

async function onSave( config: SourceConfig, mode: 'add' | 'edit' ): Promise< void > {
if ( mode === 'add' ) {
await addDataSource( config );
Expand Down Expand Up @@ -185,9 +209,11 @@ export const useDataSources = < SourceConfig extends DataSourceConfig = DataSour
dataSources,
deleteDataSource,
deleteMultipleDataSources,
getDataSourceSnippet,
loadingDataSources,
updateDataSource,
fetchDataSources,
onSave,
showSnackbar,
};
};
4 changes: 2 additions & 2 deletions src/settings/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const SettingsPage = () => {

return (
<div className="rdb-settings-page">
<Notices />

<SettingsContext.Provider value={ settingsContext }>
<div className="rdb-settings-page_header">
{ addOrEditScreen ? (
Expand Down Expand Up @@ -55,8 +57,6 @@ const SettingsPage = () => {
addOrEditScreen ? 'rdb-settings-page_add-edit' : 'rdb-settings-page_sources'
}` }
>
<Notices />

{ addOrEditScreen ? <DataSourceSettings /> : <DataSourceList /> }
</div>
</SettingsContext.Provider>
Expand Down
6 changes: 2 additions & 4 deletions src/settings/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,8 @@ body {

/* Snackbar notice */
.components-snackbar-list {
position: fixed;
bottom: 30px;
left: 160px;
width: calc(100% - 160px);
top: 4px;
z-index: 1000002;
}

&.folded .components-snackbar-list {
Expand Down

0 comments on commit 7a968e8

Please sign in to comment.