PluginProbe
Tableberg – Simple Gutenberg Table Block / 1.1.5
Tableberg – Simple Gutenberg Table Block v1.1.5
1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 trunk 0.0.2 0.2.1 0.3.2 0.3.3 0.4.1 0.5.0 0.5.1 0.5.2 0.5.3 0.5.4 0.5.5 0.5.6 0.5.7 All 42 releases
tableberg / src / blocks / image / controls.tsx

controls.tsx in Tableberg – Simple Gutenberg Table Block 1.1.5, at src/blocks/image/controls.tsx

589 lines 23.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { useState, useMemo } from "react";
2 import {
3 InspectorControls,
4 BlockControls,
5 MediaUpload,
6 MediaUploadCheck,
7 __experimentalLinkControl as LinkControl,
8 } from "@wordpress/block-editor";
9 import {
10 Button,
11 SelectControl,
12 TextareaControl,
13 ToolbarButton,
14 ToolbarGroup,
15 Popover,
16 __experimentalToolsPanel as ToolsPanel,
17 __experimentalToolsPanelItem as ToolsPanelItem,
18 __experimentalUnitControl as UnitControl,
19 __experimentalToggleGroupControl as ToggleGroupControl,
20 __experimentalToggleGroupControlOption as ToggleGroupControlOption,
21 } from "@wordpress/components";
22 import { __ } from "@wordpress/i18n";
23 import {
24 alignNone,
25 caption as captionIcon,
26 fullscreen,
27 Icon,
28 link,
29 linkOff,
30 replace,
31 } from "@wordpress/icons";
32 import { prependHTTP } from "@wordpress/url";
33 import {
34 BorderControl,
35 BorderRadiusControl,
36 ToolbarWithDropdown,
37 } from "@tableberg/components";
38 import { CellKey } from "../../attributes";
39 import { useTableStore } from "../../store";
40 import { ImageElementAttributes, imageAttrDefaults } from "./element";
41 import { ElementBindings } from "../../dynamic-data/types";
42 import { DynamicDataPanel } from "../../components/DynamicDataPanel";
43 import { ElementDeleteButton } from "../../components/ElementDeleteButton";
44 import { ElementOptionsBlockControls } from "../../components/ElementOptionsButton";
45
46 const ASPECT_RATIO_OPTIONS = [
47 { label: __("Original", "tableberg"), value: "" },
48 { label: __("Square - 1:1", "tableberg"), value: "1" },
49 { label: __("Standard - 4:3", "tableberg"), value: "4/3" },
50 { label: __("Portrait - 3:4", "tableberg"), value: "3/4" },
51 { label: __("Classic - 3:2", "tableberg"), value: "3/2" },
52 { label: __("Classic Portrait - 2:3", "tableberg"), value: "2/3" },
53 { label: __("Wide - 16:9", "tableberg"), value: "16/9" },
54 { label: __("Tall - 9:16", "tableberg"), value: "9/16" },
55 ];
56
57 const SCALE_OPTIONS = [
58 {
59 value: "cover",
60 label: __("Cover", "tableberg"),
61 help: __("Fill the space by clipping what doesn't fit.", "tableberg"),
62 },
63 {
64 value: "contain",
65 label: __("Contain", "tableberg"),
66 help: __("Fit the content to the space without clipping.", "tableberg"),
67 },
68 ];
69
70 const SIZE_SLUG_OPTIONS = [
71 { label: __("Thumbnail", "tableberg"), value: "thumbnail" },
72 { label: __("Medium", "tableberg"), value: "medium" },
73 { label: __("Large", "tableberg"), value: "large" },
74 { label: __("Full Size", "tableberg"), value: "full" },
75 ];
76
77 export function ImageElementControls({
78 attributes,
79 bindings,
80 showCaption,
81 setShowCaption,
82 cellCoords,
83 elementIndex,
84 updateAttrs: updateAttrsProp,
85 }: {
86 attributes: ImageElementAttributes;
87 bindings?: ElementBindings;
88 showCaption: boolean;
89 setShowCaption: (show: boolean) => void;
90 cellCoords: CellKey;
91 elementIndex: number;
92 // Native block edits inject a setAttributes-based updater; store-backed
93 // element renderers use the table-store fallback.
94 updateAttrs?: (attrs: Partial<ImageElementAttributes>) => void;
95 }) {
96 const {
97 media,
98 alt,
99 aspectRatio,
100 height,
101 width,
102 scale,
103 sizeSlug,
104 href,
105 linkTarget,
106 lightbox,
107 align,
108 border,
109 borderRadius,
110 caption,
111 } = attributes;
112
113 const storeUpdateAttrs = useTableStore(
114 state => state.updateSelectedElementAttrs
115 );
116 const updateAttrs = updateAttrsProp ?? storeUpdateAttrs;
117
118 const [isEditingURL, setIsEditingURL] = useState(false);
119
120 const isURLSet = !!href;
121 const isLightboxEnabled = !!lightbox?.enabled;
122 const hasLinkAction = isURLSet || isLightboxEnabled;
123 const opensInNewTab = linkTarget === "_blank";
124
125 const linkValue = useMemo(
126 () => ({ url: href, opensInNewTab }),
127 [href, opensInNewTab]
128 );
129
130 const unlink = () => {
131 updateAttrs({
132 href: "",
133 linkTarget: "_self",
134 lightbox: imageAttrDefaults.lightbox,
135 });
136 setIsEditingURL(false);
137 };
138
139 const toggleLightbox = () => {
140 updateAttrs({
141 lightbox: {
142 enabled: !isLightboxEnabled,
143 },
144 ...(!isLightboxEnabled
145 ? {
146 href: "",
147 linkTarget: "_self",
148 }
149 : {}),
150 });
151 setIsEditingURL(false);
152 };
153
154 const onSelectMedia = (newMedia: any) => {
155 if (!newMedia || !newMedia.url) {
156 updateAttrs({ media: {} });
157 return;
158 }
159
160 updateAttrs({
161 media: {
162 id: newMedia.id,
163 url: newMedia.url,
164 alt: newMedia.alt || "",
165 title: newMedia.title || "",
166 sizes: newMedia.sizes || {},
167 },
168 alt: newMedia.alt || "",
169 });
170 };
171
172 const hasImage = !!media.url || !!media.id;
173 const scaleHelp = useMemo(() => {
174 return SCALE_OPTIONS.reduce(
175 (acc: { [key: string]: string }, option) => {
176 acc[option.value] = option.help;
177 return acc;
178 },
179 {}
180 );
181 }, []);
182
183 const resetAll = () => {
184 updateAttrs({
185 alt: "",
186 aspectRatio: "",
187 height: "",
188 scale: "cover",
189 width: "150px",
190 sizeSlug: "large",
191 lightbox: imageAttrDefaults.lightbox,
192 });
193 };
194
195 return (
196 <>
197 <BlockControls group="block">
198 <ToolbarWithDropdown
199 icon={alignNone}
200 title={__("Align image", "tableberg")}
201 value={align}
202 onChange={(newVal: string | undefined) => {
203 if (newVal) {
204 updateAttrs({
205 align: newVal as "left" | "center" | "right",
206 });
207 }
208 }}
209 controlset="alignment"
210 />
211 <ToolbarButton
212 onClick={() => {
213 setShowCaption(!showCaption);
214 if (showCaption && caption) {
215 updateAttrs({ caption: "" });
216 }
217 }}
218 icon={captionIcon}
219 isPressed={showCaption}
220 label={
221 showCaption
222 ? __("Remove caption", "tableberg")
223 : __("Add caption", "tableberg")
224 }
225 />
226 <ToolbarButton
227 icon={link}
228 title={
229 hasLinkAction
230 ? __("Edit link", "tableberg")
231 : __("Link", "tableberg")
232 }
233 onClick={() => setIsEditingURL(true)}
234 isActive={hasLinkAction}
235 />
236 <ElementDeleteButton
237 cellCoords={cellCoords}
238 elementIndex={elementIndex}
239 />
240 {isEditingURL && (
241 <Popover
242 placement="bottom"
243 onClose={() => setIsEditingURL(false)}
244 shift
245 >
246 {isLightboxEnabled ? (
247 <div
248 style={{
249 alignItems: "center",
250 border: "1px solid #1e1e1e",
251 borderRadius: "2px",
252 display: "flex",
253 gap: "16px",
254 justifyContent: "space-between",
255 minWidth: "560px",
256 padding: "12px 14px",
257 }}
258 >
259 <div
260 style={{
261 alignItems: "center",
262 display: "flex",
263 gap: "16px",
264 }}
265 >
266 <Icon icon={fullscreen} />
267 <span
268 style={{
269 display: "flex",
270 flexDirection: "column",
271 }}
272 >
273 <span>
274 {__(
275 "Enlarge on click",
276 "tableberg"
277 )}
278 </span>
279 <span
280 style={{
281 color: "#757575",
282 }}
283 >
284 {__(
285 "Scales the image with a lightbox effect",
286 "tableberg"
287 )}
288 </span>
289 </span>
290 </div>
291 <Button
292 icon={linkOff}
293 label={__(
294 "Disable Enlarge on click",
295 "tableberg"
296 )}
297 onClick={toggleLightbox}
298 showTooltip
299 variant="secondary"
300 />
301 </div>
302 ) : (
303 <>
304 <LinkControl
305 value={linkValue}
306 onChange={({
307 url: newURL = "",
308 opensInNewTab: newOpensInNewTab,
309 }: {
310 url?: string;
311 opensInNewTab?: boolean;
312 }) => {
313 updateAttrs({
314 href: prependHTTP(newURL),
315 linkTarget: newOpensInNewTab
316 ? "_blank"
317 : "_self",
318 lightbox:
319 imageAttrDefaults.lightbox,
320 });
321 }}
322 onRemove={unlink}
323 />
324 {hasImage && (
325 <div
326 style={{
327 borderTop: "1px solid #ddd",
328 padding: "8px 0",
329 }}
330 >
331 <Button
332 icon={fullscreen}
333 isPressed={isLightboxEnabled}
334 onClick={toggleLightbox}
335 style={{
336 alignItems: "flex-start",
337 display: "flex",
338 gap: "16px",
339 height: "auto",
340 justifyContent: "flex-start",
341 padding: "12px 16px",
342 textAlign: "left",
343 width: "100%",
344 }}
345 >
346 <span
347 style={{
348 display: "flex",
349 flexDirection: "column",
350 gap: "4px",
351 }}
352 >
353 <span>
354 {__(
355 "Enlarge on click",
356 "tableberg"
357 )}
358 </span>
359 <span
360 style={{
361 color: "#757575",
362 fontSize: "12px",
363 }}
364 >
365 {__(
366 "Scale the image with a lightbox effect.",
367 "tableberg"
368 )}
369 </span>
370 </span>
371 </Button>
372 </div>
373 )}
374 </>
375 )}
376 </Popover>
377 )}
378 </BlockControls>
379 <ElementOptionsBlockControls
380 cellCoords={cellCoords}
381 elementIndex={elementIndex}
382 />
383 {hasImage && (
384 <BlockControls>
385 <ToolbarGroup>
386 <MediaUploadCheck>
387 <MediaUpload
388 onSelect={onSelectMedia}
389 allowedTypes={["image"]}
390 value={media.id}
391 render={({ open }) => (
392 <ToolbarButton
393 onClick={open}
394 icon={replace}
395 label={__("Replace", "tableberg")}
396 />
397 )}
398 />
399 </MediaUploadCheck>
400 </ToolbarGroup>
401 </BlockControls>
402 )}
403 <InspectorControls>
404 <ToolsPanel
405 label={__("Settings", "tableberg")}
406 resetAll={resetAll}
407 >
408 <ToolsPanelItem
409 isShownByDefault
410 hasValue={() => !!alt}
411 label={__("Alternative Text", "tableberg")}
412 onDeselect={() => updateAttrs({ alt: "" })}
413 >
414 <TextareaControl
415 __nextHasNoMarginBottom
416 value={alt}
417 label={__("Alternative Text", "tableberg")}
418 onChange={(newValue: string) =>
419 updateAttrs({ alt: newValue })
420 }
421 help={__(
422 "Describe the image for screen readers.",
423 "tableberg"
424 )}
425 />
426 </ToolsPanelItem>
427 <ToolsPanelItem
428 isShownByDefault
429 label={__("Aspect ratio", "tableberg")}
430 onDeselect={() => updateAttrs({ aspectRatio: "" })}
431 hasValue={() => aspectRatio !== ""}
432 >
433 <SelectControl
434 value={aspectRatio || ""}
435 __nextHasNoMarginBottom
436 options={ASPECT_RATIO_OPTIONS}
437 label={__("Aspect ratio", "tableberg")}
438 onChange={newValue =>
439 updateAttrs({ aspectRatio: newValue })
440 }
441 />
442 </ToolsPanelItem>
443 {aspectRatio && (
444 <ToolsPanelItem
445 label={__("Scale", "tableberg")}
446 isShownByDefault
447 hasValue={() => scale !== "cover"}
448 onDeselect={() => updateAttrs({ scale: "cover" })}
449 >
450 <ToggleGroupControl
451 label={__("Scale", "tableberg")}
452 isBlock
453 help={scaleHelp[scale || "cover"]}
454 value={scale || "cover"}
455 onChange={newScale =>
456 updateAttrs({ scale: newScale as string })
457 }
458 __nextHasNoMarginBottom
459 >
460 {SCALE_OPTIONS.map(option => (
461 <ToggleGroupControlOption
462 key={option.value}
463 value={option.value}
464 label={option.label}
465 />
466 ))}
467 </ToggleGroupControl>
468 </ToolsPanelItem>
469 )}
470 <div
471 style={{
472 display: "flex",
473 gap: "10px",
474 gridColumn: "1 / -1",
475 }}
476 >
477 <ToolsPanelItem
478 isShownByDefault
479 label={__("Width", "tableberg")}
480 hasValue={() => width !== "" && width !== "150px"}
481 onDeselect={() => updateAttrs({ width: "150px" })}
482 >
483 <UnitControl
484 label={__("Width", "tableberg")}
485 placeholder={__("Auto", "tableberg")}
486 labelPosition="top"
487 units={[
488 { value: "px", label: "px", default: 0 },
489 ]}
490 min={0}
491 value={width}
492 onChange={(newWidth: string | undefined) =>
493 updateAttrs({ width: newWidth || "" })
494 }
495 />
496 </ToolsPanelItem>
497 <ToolsPanelItem
498 isShownByDefault
499 label={__("Height", "tableberg")}
500 hasValue={() => height !== ""}
501 onDeselect={() => updateAttrs({ height: "" })}
502 >
503 <UnitControl
504 label={__("Height", "tableberg")}
505 placeholder={__("Auto", "tableberg")}
506 labelPosition="top"
507 units={[
508 { value: "px", label: "px", default: 0 },
509 ]}
510 min={0}
511 value={height}
512 onChange={(newHeight: string | undefined) =>
513 updateAttrs({ height: newHeight || "" })
514 }
515 />
516 </ToolsPanelItem>
517 </div>
518 {hasImage &&
519 media.sizes &&
520 Object.keys(media.sizes).length > 0 && (
521 <ToolsPanelItem
522 isShownByDefault
523 label={__("Resolution", "tableberg")}
524 hasValue={() => sizeSlug !== "large"}
525 onDeselect={() =>
526 updateAttrs({ sizeSlug: "large" })
527 }
528 >
529 <SelectControl
530 label={__("Resolution", "tableberg")}
531 value={sizeSlug}
532 options={SIZE_SLUG_OPTIONS.filter(
533 opt => media.sizes?.[opt.value]
534 )}
535 onChange={(newSlug: string) =>
536 updateAttrs({ sizeSlug: newSlug })
537 }
538 help={__(
539 "Select the size of the source image.",
540 "tableberg"
541 )}
542 />
543 </ToolsPanelItem>
544 )}
545 </ToolsPanel>
546 </InspectorControls>
547 <InspectorControls group="border">
548 <BorderControl
549 value={border}
550 label={__("Border", "tableberg")}
551 onChange={(newBorder: any) =>
552 updateAttrs({ border: newBorder })
553 }
554 onDeselect={() =>
555 updateAttrs({ border: imageAttrDefaults.border })
556 }
557 hasValue={() =>
558 !!border.top ||
559 !!border.right ||
560 !!border.bottom ||
561 !!border.left
562 }
563 />
564 <BorderRadiusControl
565 label={__("Border Radius", "tableberg")}
566 value={borderRadius}
567 onChange={(newRadius: any) =>
568 updateAttrs({ borderRadius: newRadius })
569 }
570 onDeselect={() =>
571 updateAttrs({
572 borderRadius: imageAttrDefaults.borderRadius,
573 })
574 }
575 hasValue={() =>
576 !!borderRadius.topLeft ||
577 !!borderRadius.topRight ||
578 !!borderRadius.bottomLeft ||
579 !!borderRadius.bottomRight
580 }
581 />
582 </InspectorControls>
583 <InspectorControls>
584 <DynamicDataPanel elementType="image" bindings={bindings} />
585 </InspectorControls>
586 </>
587 );
588 }
589