mirror of
https://fast.feibisi.com/https://github.com/lubusIN/visual-blueprint-builder.git
synced 2025-10-04 04:11:06 +08:00
blueprint gallery integration (#50)
* blueprint gallery feature added * Created utils.js for common functions and integrated it into OpenJson and Gallery components to streamline JSON handling and validation * fixed gallery UI * fixed Open and Gallery button UI in sidebar
This commit is contained in:
parent
44ce7661ea
commit
a8828ed90a
5 changed files with 251 additions and 78 deletions
|
@ -134,4 +134,10 @@ body.editor-styles-wrapper {
|
|||
}
|
||||
.dataforms-layouts-regular__field{
|
||||
min-height: 32px!important
|
||||
}
|
||||
}
|
||||
.blueprint_gallery_json {
|
||||
box-shadow: inset 0 0 0 1px #ccc;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
|
144
src/editor/blueprint-gallery.js
Normal file
144
src/editor/blueprint-gallery.js
Normal file
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useState, useEffect } from '@wordpress/element';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { store as noticesStore } from '@wordpress/notices';
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
Card,
|
||||
CardBody,
|
||||
__experimentalText as Text,
|
||||
__experimentalHeading as Heading,
|
||||
__experimentalGrid as Grid,
|
||||
__experimentalVStack as VStack,
|
||||
} from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { handleBlueprintData } from './utils';
|
||||
|
||||
function Gallery({ onSubmitData }) {
|
||||
const { createNotice } = useDispatch(noticesStore);
|
||||
const [isModalOpen, setModalOpen] = useState(false);
|
||||
const [blueprintList, setBlueprintList] = useState(null);
|
||||
|
||||
|
||||
/**
|
||||
* Fetches the list of blueprints from the remote JSON file.
|
||||
*/
|
||||
useEffect(() => {
|
||||
const fetchBlueprintList = async () => {
|
||||
const apiUrl = 'https://raw.githubusercontent.com/WordPress/blueprints/trunk/index.json';
|
||||
try {
|
||||
const response = await fetch(apiUrl);
|
||||
if (!response.ok) throw new Error('Failed to fetch blueprints');
|
||||
const data = await response.json();
|
||||
setBlueprintList(data);
|
||||
} catch (error) {
|
||||
createNotice('error', __('Error fetching blueprint list:', error, 'wp-playground-blueprint-editor'));
|
||||
}
|
||||
};
|
||||
|
||||
fetchBlueprintList();
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Fetches details for a selected blueprint.
|
||||
* @param {string} blueprintName - The name of the blueprint to fetch.
|
||||
*/
|
||||
const fetchBlueprintDetails = async (blueprintName) => {
|
||||
const blueprintUrl = `https://raw.githubusercontent.com/WordPress/blueprints/trunk/${blueprintName}`;
|
||||
try {
|
||||
const response = await fetch(blueprintUrl);
|
||||
if (!response.ok) throw new Error(`Failed to fetch blueprint details: ${response.statusText}`);
|
||||
const data = await response.json();
|
||||
|
||||
// Replace 'mu-plugins' with 'plugins' in the blueprint data
|
||||
const updatedSteps = data.steps.map((step) => {
|
||||
if (step.path?.includes('mu-plugins')) {
|
||||
return { ...step, path: step.path.replace('mu-plugins', 'plugins') };
|
||||
}
|
||||
return step;
|
||||
});
|
||||
handleBlueprintData({ ...data, steps: updatedSteps }, createNotice, onSubmitData);
|
||||
} catch (error) {
|
||||
createNotice('error', __('Error fetching blueprint from Gallery', 'wp-playground-blueprint-editor') + `: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Open modal button */}
|
||||
<Button
|
||||
className='blueprint_gallery_json'
|
||||
__next40pxDefaultSize
|
||||
onClick={() => setModalOpen(true)}>
|
||||
{__('Gallery', 'wp-playground-blueprint-editor')}
|
||||
</Button>
|
||||
|
||||
{/* Blueprint gallery modal */}
|
||||
{isModalOpen && (
|
||||
<Modal
|
||||
title={__('Blueprint Gallery', 'wp-playground-blueprint-editor')}
|
||||
onRequestClose={() => setModalOpen(false)}
|
||||
shouldCloseOnClickOutside
|
||||
shouldCloseOnEsc
|
||||
size="large"
|
||||
>
|
||||
{blueprintList ? (
|
||||
<Grid columns={2} gap={6}>
|
||||
{Object.entries(blueprintList).map(([blueprintName, blueprintDetails], index) => (
|
||||
<Card
|
||||
key={index}
|
||||
elevation={3}
|
||||
>
|
||||
<CardBody style={{ height: '100%', justifyContent: 'space-between', display: 'flex', flexDirection: 'column' }}>
|
||||
{/* Blueprint Info */}
|
||||
<VStack align='start' spacing={4} >
|
||||
<Heading level={4}>
|
||||
{blueprintDetails.title}
|
||||
</Heading>
|
||||
<Text>
|
||||
{__('By', 'wp-playground-blueprint-editor')} {blueprintDetails.author}
|
||||
</Text>
|
||||
<Text
|
||||
lineHeight={'1.5em'}
|
||||
size={15}
|
||||
color='#777'
|
||||
>
|
||||
{blueprintDetails.description}
|
||||
</Text>
|
||||
</VStack>
|
||||
|
||||
|
||||
{/* Action Button */}
|
||||
<Button
|
||||
variant="secondary"
|
||||
style={{
|
||||
borderRadius: '4px',
|
||||
alignSelf: 'flex-end',
|
||||
|
||||
}}
|
||||
onClick={() => fetchBlueprintDetails(blueprintName)}
|
||||
>
|
||||
{__('Import', 'wp-playground-blueprint-editor')}
|
||||
</Button>
|
||||
|
||||
</CardBody>
|
||||
</Card>
|
||||
))}
|
||||
</Grid>
|
||||
) : (
|
||||
<Text>{__('Loading blueprints...', 'wp-playground-blueprint-editor')}</Text>
|
||||
)}
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Gallery;
|
|
@ -1,45 +1,12 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { createBlock } from '@wordpress/blocks';
|
||||
import { dispatch, useDispatch } from '@wordpress/data';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { FormFileUpload, DropZone } from '@wordpress/components';
|
||||
import { store as noticesStore } from '@wordpress/notices';
|
||||
import {handleBlueprintData} from './utils'
|
||||
|
||||
const OpenJson = ({ onSubmitData }) => {
|
||||
const { createNotice } = useDispatch(noticesStore);
|
||||
|
||||
// Utility to convert camelCase to kebab-case with special handling for "WordPress"
|
||||
const convertToKebabCase = (str) => {
|
||||
|
||||
return str
|
||||
.replace(/WordPress/g, 'Wordpress') // Temporarily normalize "WordPress" casing
|
||||
.replace(/([a-z])([A-Z])/g, '$1-$2') // Convert camelCase to kebab-case
|
||||
.replace(/Wordpress/g, 'wordpress') // Convert "Wordpress" back to "wordpress"
|
||||
.toLowerCase(); // Convert the entire string to lowercase
|
||||
};
|
||||
|
||||
|
||||
// Validate steps from the JSON schema
|
||||
const validateBlueprintSteps = (steps) => {
|
||||
const validBlocks = [];
|
||||
const invalidSteps = [];
|
||||
|
||||
steps.forEach((step, index) => {
|
||||
const blockType = `playground-step/${convertToKebabCase(step.step)}`;
|
||||
try {
|
||||
const block = createBlock(blockType, step || {});
|
||||
validBlocks.push(block);
|
||||
} catch (error) {
|
||||
invalidSteps.push({
|
||||
stepIndex: index + 1,
|
||||
stepData: step,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return { validBlocks, invalidSteps };
|
||||
};
|
||||
|
||||
// Process JSON file content
|
||||
const processJsonFile = (file) => {
|
||||
if (file.type === 'application/json') {
|
||||
|
@ -47,7 +14,7 @@ const OpenJson = ({ onSubmitData }) => {
|
|||
reader.onload = () => {
|
||||
try {
|
||||
const jsonData = JSON.parse(reader.result);
|
||||
handleBlueprintData(jsonData);
|
||||
handleBlueprintData(jsonData , createNotice, onSubmitData);
|
||||
}
|
||||
catch (err) {
|
||||
createNotice('error', __('Invalid JSON file.', 'wp-playground-blueprint-editor'));
|
||||
|
@ -59,33 +26,6 @@ const OpenJson = ({ onSubmitData }) => {
|
|||
}
|
||||
};
|
||||
|
||||
// Handle JSON data processing
|
||||
const handleBlueprintData = (jsonData) => {
|
||||
if (!jsonData) {
|
||||
createNotice('error', __('Invalid blueprint schema.', 'wp-playground-blueprint-editor'));
|
||||
return;
|
||||
}
|
||||
|
||||
const { steps } = jsonData;
|
||||
const { validBlocks, invalidSteps } = validateBlueprintSteps(steps);
|
||||
|
||||
if (validBlocks.length > 0) {
|
||||
dispatch('core/block-editor').insertBlocks(validBlocks);
|
||||
createNotice('success', __('Blueprint imported successfully.', 'wp-playground-blueprint-editor'));
|
||||
}
|
||||
|
||||
if (invalidSteps.length > 0) {
|
||||
const errorDetails = invalidSteps
|
||||
.map(({ stepIndex, stepData, error }) => `Step ${stepIndex}: ${stepData.step} (${error})`)
|
||||
.join(', ');
|
||||
createNotice('warning', __(`Some steps are invalid: ${errorDetails}.`, 'wp-playground-blueprint-editor'));
|
||||
}
|
||||
|
||||
if (onSubmitData) {
|
||||
onSubmitData(jsonData);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle file selection from input
|
||||
const handleFileSelection = (event) => {
|
||||
const file = event.target.files[0];
|
||||
|
@ -110,7 +50,7 @@ const OpenJson = ({ onSubmitData }) => {
|
|||
accept="application/json"
|
||||
onChange={handleFileSelection}
|
||||
>
|
||||
{__('Open Blueprint', 'wp-playground-blueprint-editor')}
|
||||
{__('Open', 'wp-playground-blueprint-editor')}
|
||||
<DropZone
|
||||
onFilesDrop={handleFileDrop}
|
||||
accept="application/json"
|
||||
|
|
|
@ -15,6 +15,8 @@ import {
|
|||
Toolbar,
|
||||
ToolbarButton,
|
||||
ToggleControl,
|
||||
Flex,
|
||||
FlexBlock,
|
||||
__experimentalVStack as VStack,
|
||||
__experimentalHStack as HStack,
|
||||
__experimentalText as Text,
|
||||
|
@ -25,6 +27,7 @@ import {
|
|||
*/
|
||||
import OpenJson from './open-json';
|
||||
import { PHP_VERSIONS, WP_VERSIONS, PLAYGROUND_BASE, PLAYGROUND_BUILDER_BASE, PLAYGROUND_BLUEPRINT_SCHEMA_URL } from './constant';
|
||||
import Gallery from './blueprint-gallery';
|
||||
import SiteOptionsSettings from './site-options-settings';
|
||||
|
||||
/**
|
||||
|
@ -81,9 +84,9 @@ function BlueprintSidebarSettings() {
|
|||
try {
|
||||
const preparedSchema = prepareSchema();
|
||||
downloadBlob('playground-blueprint.json', preparedSchema, 'application/json');
|
||||
createSuccessNotice(__('Blueprint downloaded successfully!','wp-playground-blueprint-editor'), { type: 'snackbar' });
|
||||
createSuccessNotice(__('Blueprint downloaded successfully!', 'wp-playground-blueprint-editor'), { type: 'snackbar' });
|
||||
} catch (error) {
|
||||
createErrorNotice(__('Failed to download Blueprint JSON.','wp-playground-blueprint-editor'));
|
||||
createErrorNotice(__('Failed to download Blueprint JSON.', 'wp-playground-blueprint-editor'));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -92,10 +95,10 @@ function BlueprintSidebarSettings() {
|
|||
*/
|
||||
const handleCopy = useCopyToClipboard(() => {
|
||||
if (!schema.steps.length) {
|
||||
createErrorNotice(__('No Blueprint steps to copy!','wp-playground-blueprint-editor'));
|
||||
createErrorNotice(__('No Blueprint steps to copy!', 'wp-playground-blueprint-editor'));
|
||||
return ''; // Return empty string for invalid data
|
||||
}
|
||||
createSuccessNotice(__('Blueprint schema copied to clipboard!','wp-playground-blueprint-editor'), { type: 'snackbar' });
|
||||
createSuccessNotice(__('Blueprint schema copied to clipboard!', 'wp-playground-blueprint-editor'), { type: 'snackbar' });
|
||||
return prepareSchema();
|
||||
});
|
||||
|
||||
|
@ -114,7 +117,7 @@ function BlueprintSidebarSettings() {
|
|||
*/
|
||||
const handleJsonDataSubmit = (data) => {
|
||||
if (!data) {
|
||||
createErrorNotice(__('Failed to update Blueprint configuration.','wp-playground-blueprint-editor'));
|
||||
createErrorNotice(__('Failed to update Blueprint configuration.', 'wp-playground-blueprint-editor'));
|
||||
return;
|
||||
}
|
||||
updateBlueprintConfig({
|
||||
|
@ -127,20 +130,27 @@ function BlueprintSidebarSettings() {
|
|||
siteOptions: data.siteOptions || undefined,
|
||||
extra_libraries: data.extraLibraries || undefined,
|
||||
});
|
||||
createSuccessNotice(__('Blueprint configuration updated successfully!','wp-playground-blueprint-editor'), { type: 'snackbar' });
|
||||
createSuccessNotice(__('Blueprint configuration updated successfully!', 'wp-playground-blueprint-editor'), { type: 'snackbar' });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PluginPostStatusInfo>
|
||||
<VStack spacing={5} style={{ width: '100%' }}>
|
||||
<OpenJson onSubmitData={handleJsonDataSubmit} />
|
||||
<VStack spacing={5} style={{ width: '100%' }}>
|
||||
<Flex>
|
||||
<FlexBlock>
|
||||
<OpenJson onSubmitData={handleJsonDataSubmit} />
|
||||
</FlexBlock>
|
||||
<FlexBlock>
|
||||
<Gallery onSubmitData={handleJsonDataSubmit} />
|
||||
</FlexBlock>
|
||||
</Flex>
|
||||
<Toolbar style={{ justifyContent: 'space-between' }}>
|
||||
<ToolbarButton icon={globe} label={__('Open in Playground', 'wp-playground-blueprint-editor')} href={PLAYGROUND_BASE + prepareSchema()} target="_blank" />
|
||||
<ToolbarButton icon={download} label={__('Download JSON', 'wp-playground-blueprint-editor')} onClick={handleDownload} />
|
||||
<ToolbarButton icon={copy} label={__('Copy JSON', 'wp-playground-blueprint-editor')} ref={handleCopy} />
|
||||
<ToolbarButton icon={code} label={__('Open in Builder', 'wp-playground-blueprint-editor')} href={PLAYGROUND_BUILDER_BASE + prepareSchema()} target="_blank" />
|
||||
</Toolbar>
|
||||
<ToolbarButton icon={globe} label={__('Open in Playground', 'wp-playground-blueprint-editor')} href={PLAYGROUND_BASE + prepareSchema()} target="_blank" />
|
||||
<ToolbarButton icon={download} label={__('Download JSON', 'wp-playground-blueprint-editor')} onClick={handleDownload} />
|
||||
<ToolbarButton icon={copy} label={__('Copy JSON', 'wp-playground-blueprint-editor')} ref={handleCopy} />
|
||||
<ToolbarButton icon={code} label={__('Open in Builder', 'wp-playground-blueprint-editor')} href={PLAYGROUND_BUILDER_BASE + prepareSchema()} target="_blank" />
|
||||
</Toolbar>
|
||||
</VStack>
|
||||
</PluginPostStatusInfo>
|
||||
<PluginDocumentSettingPanel name='playground-settings' title={__('Playground Settings', 'wp-playground-blueprint-editor')}>
|
||||
|
|
73
src/editor/utils.js
Normal file
73
src/editor/utils.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { createBlock } from "@wordpress/blocks";
|
||||
import { dispatch } from '@wordpress/data';
|
||||
/**
|
||||
* Utility function to convert a camelCase string to kebab-case with special handling for "WordPress".
|
||||
*
|
||||
*/
|
||||
export const convertToKebabCase = (str) => {
|
||||
return str
|
||||
.replace(/WordPress/g, 'Wordpress') // Temporarily normalize "WordPress" casing
|
||||
.replace(/([a-z])([A-Z])/g, '$1-$2') // Convert camelCase to kebab-case
|
||||
.replace(/Wordpress/g, 'wordpress') // Convert "Wordpress" back to "wordpress"
|
||||
.toLowerCase(); // Convert the entire string to lowercase
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates steps from a JSON schema and creates valid WordPress blocks.
|
||||
*
|
||||
*/
|
||||
export const validateBlueprintSteps = (steps) => {
|
||||
const validBlocks = [];
|
||||
const invalidSteps = [];
|
||||
|
||||
steps.forEach((step, index) => {
|
||||
const blockType = `playground-step/${convertToKebabCase(step.step)}`;
|
||||
try {
|
||||
const block = createBlock(blockType, step || {});
|
||||
validBlocks.push(block);
|
||||
} catch (error) {
|
||||
invalidSteps.push({
|
||||
stepIndex: index + 1,
|
||||
stepData: step,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return { validBlocks, invalidSteps };
|
||||
};
|
||||
|
||||
/**
|
||||
* Processes JSON blueprint data, validates it, and inserts valid blocks into the WordPress editor.
|
||||
*
|
||||
* jsonData - The parsed JSON data from the openJson and Gallery.
|
||||
* createNotice - Function to create notices in the WordPress admin.
|
||||
* onSubmitData - Optional callback for additional data handling.
|
||||
*/
|
||||
export const handleBlueprintData = (jsonData, createNotice, onSubmitData) => {
|
||||
if (!jsonData) {
|
||||
createNotice('error', __('Invalid blueprint schema.', 'wp-playground-blueprint-editor'));
|
||||
return;
|
||||
}
|
||||
|
||||
const { steps } = jsonData;
|
||||
const { validBlocks, invalidSteps } = validateBlueprintSteps(steps);
|
||||
|
||||
if (validBlocks.length > 0) {
|
||||
dispatch('core/block-editor').insertBlocks(validBlocks);
|
||||
createNotice('success', __('Blueprint imported successfully.', 'wp-playground-blueprint-editor'));
|
||||
}
|
||||
|
||||
if (invalidSteps.length > 0) {
|
||||
const errorDetails = invalidSteps
|
||||
.map(({ stepIndex, stepData, error }) => `Step ${stepIndex}: ${stepData.step} (${error})`)
|
||||
.join(', ');
|
||||
createNotice('warning', __(`Some steps are invalid: ${errorDetails}.`, 'wp-playground-blueprint-editor'));
|
||||
}
|
||||
|
||||
if (onSubmitData) {
|
||||
onSubmitData(jsonData);
|
||||
}
|
||||
};
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue