PluginProbe
Extendify / 3.2.1
Extendify v3.2.1
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / QuickEdit / components / modals / ProductImageModal.jsx

ProductImageModal.jsx in Extendify 3.2.1, at src/QuickEdit/components/modals/ProductImageModal.jsx

90 lines 2.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { track } from '@shared/lib/track';
2 import { useEffect, useRef } from '@wordpress/element';
3 import { __ } from '@wordpress/i18n';
4 import { loadProduct, saveProduct } from '../../lib/api';
5 import { pushUndo } from '../../state/undo';
6
7 export const ProductImageModal = ({ productId, onAfterSave }) => {
8 const opened = useRef(false);
9 const beforeImageId = useRef(null);
10
11 useEffect(() => {
12 if (opened.current) return undefined;
13 opened.current = true;
14 if (!window.wp?.media) {
15 onAfterSave(false);
16 return undefined;
17 }
18
19 // Prefetch current image id for the undo before-state.
20 loadProduct(productId)
21 .then((res) => {
22 beforeImageId.current = Number(res.image_id) || 0;
23 })
24 .catch(() => {
25 // non-fatal — undo entry just won't have a before
26 });
27
28 const frame = window.wp.media({
29 title: __('Replace product image', 'extendify-local'),
30 button: { text: __('Use image', 'extendify-local') },
31 library: { type: 'image' },
32 multiple: false,
33 });
34 frame.on('open', () => {
35 // Class lives on QE's own modal element, NOT on body — see
36 // InlineEditor.jsx for the same pattern. Putting the class on
37 // body would also style the AI Agent's media frames (or any
38 // other wp.media frame open at the same time) and blank their
39 // content.
40 frame.modal?.$el?.addClass('extendify-quick-edit-mode-browse');
41 if (frame.content?.mode) frame.content.mode('browse');
42 });
43 const cleanup = () => {
44 frame.modal?.$el?.removeClass('extendify-quick-edit-mode-browse');
45 };
46 frame.on('close', cleanup);
47
48 let pickedAndSaving = false;
49 frame.on('select', async () => {
50 pickedAndSaving = true;
51 const att = frame.state().get('selection').first()?.toJSON();
52 if (!att?.id) {
53 onAfterSave(false);
54 return;
55 }
56 try {
57 await saveProduct({
58 productId,
59 field: 'image',
60 value: att.id,
61 });
62 if (beforeImageId.current && beforeImageId.current !== att.id) {
63 pushUndo({
64 kind: 'product-image',
65 productReplay: true,
66 productId,
67 field: 'image',
68 beforeValue: beforeImageId.current,
69 });
70 }
71 track('save', { kind: 'product', field: 'image' });
72 onAfterSave(true);
73 } catch (_err) {
74 track('save_failed', { kind: 'product', field: 'image' });
75 onAfterSave(false);
76 }
77 });
78 frame.on('close', () => {
79 if (!pickedAndSaving) onAfterSave(false);
80 });
81 frame.open();
82
83 return () => {
84 cleanup();
85 };
86 }, [productId, onAfterSave]);
87
88 return null;
89 };
90