FEATURE introducing block
This commit is contained in:
parent
d994a331eb
commit
02eb19eb9e
65
plugins/carhop-blocks/src/image-stack/block.json
Executable file
65
plugins/carhop-blocks/src/image-stack/block.json
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 3,
|
||||
"name": "carhop-blocks/image-stack",
|
||||
"version": "0.1.0",
|
||||
"title": "Image Stack (Focal Point)",
|
||||
"category": "carhop-blocks",
|
||||
"icon": "images-alt2",
|
||||
"description": "Layer multiple images with individual focal point positioning.",
|
||||
"example": {
|
||||
"attributes": {
|
||||
"images": [
|
||||
{
|
||||
"id": 1,
|
||||
"url": "https://images.unsplash.com/photo-1506905925346-21bda4d32df4",
|
||||
"alt": "Mountain landscape",
|
||||
"focalPoint": {
|
||||
"x": 0.5,
|
||||
"y": 0.3
|
||||
},
|
||||
"scale": 0.8
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"url": "https://images.unsplash.com/photo-1511884642898-4c92249e20b6",
|
||||
"alt": "Forest scene",
|
||||
"focalPoint": {
|
||||
"x": 0.7,
|
||||
"y": 0.6
|
||||
},
|
||||
"scale": 0.6
|
||||
}
|
||||
],
|
||||
"height": 400
|
||||
}
|
||||
},
|
||||
"attributes": {
|
||||
"images": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"height": {
|
||||
"type": "number",
|
||||
"default": 400
|
||||
}
|
||||
},
|
||||
"supports": {
|
||||
"html": false,
|
||||
"align": [
|
||||
"wide",
|
||||
"full"
|
||||
],
|
||||
"spacing": {
|
||||
"margin": true,
|
||||
"padding": true
|
||||
}
|
||||
},
|
||||
"textdomain": "image-stack",
|
||||
"editorScript": "file:./index.js",
|
||||
"editorStyle": "file:./index.css",
|
||||
"style": "file:./style-index.css"
|
||||
}
|
||||
243
plugins/carhop-blocks/src/image-stack/edit.js
Executable file
243
plugins/carhop-blocks/src/image-stack/edit.js
Executable file
|
|
@ -0,0 +1,243 @@
|
|||
import { __ } from "@wordpress/i18n";
|
||||
import {
|
||||
useBlockProps,
|
||||
InspectorControls,
|
||||
MediaUpload,
|
||||
MediaUploadCheck,
|
||||
} from "@wordpress/block-editor";
|
||||
import {
|
||||
PanelBody,
|
||||
Button,
|
||||
FocalPointPicker,
|
||||
RangeControl,
|
||||
ToolbarGroup,
|
||||
ToolbarButton,
|
||||
} from "@wordpress/components";
|
||||
import { BlockControls } from "@wordpress/block-editor";
|
||||
import { useState } from "@wordpress/element";
|
||||
|
||||
import "./editor.scss";
|
||||
|
||||
export default function Edit({ attributes, setAttributes }) {
|
||||
const { images, height } = attributes;
|
||||
const [selectedImageIndex, setSelectedImageIndex] = useState(null);
|
||||
|
||||
const blockProps = useBlockProps();
|
||||
|
||||
const onSelectImages = (media) => {
|
||||
const newImages = media.map((img) => ({
|
||||
id: img.id,
|
||||
url: img.url,
|
||||
alt: img.alt || "",
|
||||
focalPoint: { x: 0.5, y: 0.5 },
|
||||
scale: 0.8,
|
||||
rotation: 0,
|
||||
}));
|
||||
setAttributes({ images: [...images, ...newImages] });
|
||||
};
|
||||
|
||||
const updateImageFocalPoint = (index, focalPoint) => {
|
||||
const newImages = [...images];
|
||||
newImages[index] = { ...newImages[index], focalPoint };
|
||||
setAttributes({ images: newImages });
|
||||
};
|
||||
|
||||
const updateImageScale = (index, scale) => {
|
||||
const newImages = [...images];
|
||||
newImages[index] = { ...newImages[index], scale };
|
||||
setAttributes({ images: newImages });
|
||||
};
|
||||
|
||||
const updateImageRotation = (index, rotation) => {
|
||||
const newImages = [...images];
|
||||
newImages[index] = { ...newImages[index], rotation };
|
||||
setAttributes({ images: newImages });
|
||||
};
|
||||
|
||||
const removeImage = (index) => {
|
||||
const newImages = images.filter((_, i) => i !== index);
|
||||
setAttributes({ images: newImages });
|
||||
if (selectedImageIndex === index) {
|
||||
setSelectedImageIndex(null);
|
||||
}
|
||||
};
|
||||
|
||||
const moveImage = (index, direction) => {
|
||||
const newImages = [...images];
|
||||
const newIndex = index + direction;
|
||||
if (newIndex >= 0 && newIndex < images.length) {
|
||||
[newImages[index], newImages[newIndex]] = [
|
||||
newImages[newIndex],
|
||||
newImages[index],
|
||||
];
|
||||
setAttributes({ images: newImages });
|
||||
setSelectedImageIndex(newIndex);
|
||||
}
|
||||
};
|
||||
|
||||
const getImageStyle = (image) => {
|
||||
const focalPoint = image.focalPoint || { x: 0.5, y: 0.5 };
|
||||
const scale = image.scale || 0.8;
|
||||
const rotation = image.rotation || 0;
|
||||
return {
|
||||
left: `${focalPoint.x * 100}%`,
|
||||
top: `${focalPoint.y * 100}%`,
|
||||
transform: ` rotate(${rotation}deg) scale(${scale})`,
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<BlockControls>
|
||||
<ToolbarGroup>
|
||||
<MediaUploadCheck>
|
||||
<MediaUpload
|
||||
onSelect={onSelectImages}
|
||||
allowedTypes={["image"]}
|
||||
multiple
|
||||
gallery
|
||||
value={images.map((img) => img.id)}
|
||||
render={({ open }) => (
|
||||
<ToolbarButton onClick={open}>
|
||||
{__("Add Images", "image-stack")}
|
||||
</ToolbarButton>
|
||||
)}
|
||||
/>
|
||||
</MediaUploadCheck>
|
||||
</ToolbarGroup>
|
||||
</BlockControls>
|
||||
|
||||
<InspectorControls>
|
||||
<PanelBody title={__("Container Settings", "image-stack")}>
|
||||
<RangeControl
|
||||
label={__("Container Height", "image-stack")}
|
||||
value={height}
|
||||
onChange={(value) => setAttributes({ height: value })}
|
||||
min={200}
|
||||
max={800}
|
||||
step={10}
|
||||
/>
|
||||
</PanelBody>
|
||||
|
||||
{images.length > 0 && (
|
||||
<PanelBody title={__("Images", "image-stack")} initialOpen={true}>
|
||||
{images.map((image, index) => (
|
||||
<PanelBody
|
||||
key={image.id}
|
||||
title={`${__("Image", "image-stack")} ${index + 1}`}
|
||||
initialOpen={selectedImageIndex === index}
|
||||
onToggle={() =>
|
||||
setSelectedImageIndex(
|
||||
selectedImageIndex === index ? null : index
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="image-stack-image-preview">
|
||||
<img src={image.url} alt={image.alt} />
|
||||
</div>
|
||||
|
||||
<FocalPointPicker
|
||||
label={__("Focal Point", "image-stack")}
|
||||
url={image.url}
|
||||
value={image.focalPoint || { x: 0.5, y: 0.5 }}
|
||||
onChange={(focalPoint) =>
|
||||
updateImageFocalPoint(index, focalPoint)
|
||||
}
|
||||
/>
|
||||
|
||||
<RangeControl
|
||||
label={__("Scale", "image-stack")}
|
||||
value={image.scale || 0.8}
|
||||
onChange={(scale) => updateImageScale(index, scale)}
|
||||
min={0.1}
|
||||
max={3}
|
||||
step={0.05}
|
||||
/>
|
||||
|
||||
<RangeControl
|
||||
label={__("Rotation (deg)", "image-stack")}
|
||||
value={image.rotation || 0}
|
||||
onChange={(rotation) => updateImageRotation(index, rotation)}
|
||||
min={-180}
|
||||
max={180}
|
||||
step={1}
|
||||
/>
|
||||
|
||||
<div className="image-stack-image-controls">
|
||||
<Button
|
||||
isSecondary
|
||||
isSmall
|
||||
disabled={index === 0}
|
||||
onClick={() => moveImage(index, -1)}
|
||||
>
|
||||
{__("↑ Move Up", "image-stack")}
|
||||
</Button>
|
||||
<Button
|
||||
isSecondary
|
||||
isSmall
|
||||
disabled={index === images.length - 1}
|
||||
onClick={() => moveImage(index, 1)}
|
||||
>
|
||||
{__("↓ Move Down", "image-stack")}
|
||||
</Button>
|
||||
<Button
|
||||
isDestructive
|
||||
isSmall
|
||||
onClick={() => removeImage(index)}
|
||||
>
|
||||
{__("Remove", "image-stack")}
|
||||
</Button>
|
||||
</div>
|
||||
</PanelBody>
|
||||
))}
|
||||
</PanelBody>
|
||||
)}
|
||||
</InspectorControls>
|
||||
|
||||
<div {...blockProps}>
|
||||
<div
|
||||
className="image-stack-container"
|
||||
style={{ height: `${height}px` }}
|
||||
>
|
||||
{images.length === 0 && (
|
||||
<div className="image-stack-placeholder">
|
||||
<MediaUploadCheck>
|
||||
<MediaUpload
|
||||
onSelect={onSelectImages}
|
||||
allowedTypes={["image"]}
|
||||
multiple
|
||||
gallery
|
||||
render={({ open }) => (
|
||||
<Button variant="primary" onClick={open}>
|
||||
{__("Ajouter une Image", "image-stack")}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
</MediaUploadCheck>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{images.map((image, index) => (
|
||||
<div
|
||||
key={image.id}
|
||||
className={`image-stack-item ${
|
||||
selectedImageIndex === index ? "is-selected" : ""
|
||||
}`}
|
||||
onClick={() => setSelectedImageIndex(index)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
setSelectedImageIndex(index);
|
||||
}
|
||||
}}
|
||||
style={getImageStyle(image)}
|
||||
>
|
||||
<img src={image.url} alt={image.alt} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
56
plugins/carhop-blocks/src/image-stack/editor.scss
Executable file
56
plugins/carhop-blocks/src/image-stack/editor.scss
Executable file
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* The following styles get applied inside the editor only.
|
||||
*/
|
||||
|
||||
.wp-block-carhop-blocks-image-stack {
|
||||
overflow: visible;
|
||||
.image-stack-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(240, 240, 240, 0.5);
|
||||
background-color: red;
|
||||
border: 2px dashed #ccc;
|
||||
}
|
||||
|
||||
.image-stack-item {
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
outline: 3px solid #007cba;
|
||||
outline-offset: -3px;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image-stack-image-preview {
|
||||
margin-bottom: 16px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.image-stack-image-controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.components-button {
|
||||
flex: 1;
|
||||
min-width: 80px;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
11
plugins/carhop-blocks/src/image-stack/index.js
Executable file
11
plugins/carhop-blocks/src/image-stack/index.js
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
import { registerBlockType } from "@wordpress/blocks";
|
||||
import "./style.scss";
|
||||
|
||||
import Edit from "./edit";
|
||||
import save from "./save";
|
||||
import metadata from "./block.json";
|
||||
|
||||
registerBlockType(metadata.name, {
|
||||
edit: Edit,
|
||||
save,
|
||||
});
|
||||
33
plugins/carhop-blocks/src/image-stack/save.js
Executable file
33
plugins/carhop-blocks/src/image-stack/save.js
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
import { useBlockProps } from "@wordpress/block-editor";
|
||||
|
||||
export default function save({ attributes }) {
|
||||
const { images, height } = attributes;
|
||||
const blockProps = useBlockProps.save();
|
||||
|
||||
const getImageStyle = (image) => {
|
||||
const focalPoint = image.focalPoint || { x: 0.5, y: 0.5 };
|
||||
const scale = image.scale || 0.8;
|
||||
const rotation = image.rotation || 0;
|
||||
return {
|
||||
left: `${focalPoint.x * 100}%`,
|
||||
top: `${focalPoint.y * 100}%`,
|
||||
transform: `rotate(${rotation}deg) scale(${scale})`,
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div {...blockProps}>
|
||||
<div className="image-stack-container" style={{ height: `${height}px` }}>
|
||||
{images.map((image) => (
|
||||
<div
|
||||
key={image.id}
|
||||
className="image-stack-item"
|
||||
style={getImageStyle(image)}
|
||||
>
|
||||
<img src={image.url} alt={image.alt} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
23
plugins/carhop-blocks/src/image-stack/style.scss
Executable file
23
plugins/carhop-blocks/src/image-stack/style.scss
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* The following styles get applied both on the front of your site
|
||||
* and in the editor.
|
||||
*/
|
||||
|
||||
.wp-block-carhop-blocks-image-stack {
|
||||
.image-stack-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.image-stack-item {
|
||||
position: absolute;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
5
plugins/carhop-blocks/src/image-stack/view.js
Executable file
5
plugins/carhop-blocks/src/image-stack/view.js
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Frontend JavaScript for the Image Stack block.
|
||||
* Currently no interactive features required, but this file
|
||||
* is available for future enhancements.
|
||||
*/
|
||||
Loading…
Reference in New Issue
Block a user