Skip to content

Commit a84a44e

Browse files
committed
Saving functionality WIP
1 parent 37ba28e commit a84a44e

File tree

7 files changed

+195
-100
lines changed

7 files changed

+195
-100
lines changed

js/asset-edit.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

php/assets/class-rest-assets.php

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,23 +105,70 @@ public function rest_endpoints( $endpoints ) {
105105
* @return WP_Error|WP_HTTP_Response|WP_REST_Response
106106
*/
107107
public function rest_save_asset( $request ) {
108+
$media = $this->assets->media;
109+
$attachment_id = $request->get_param( 'ID' );
110+
$type = $media->get_resource_type( $attachment_id );
108111

112+
$return = array();
113+
114+
// Save transformations if present.
115+
$transformations = $request->get_param( 'transformations' );
116+
117+
if ( isset( $transformations ) ) {
118+
$result = $this->save_transformation_type( $attachment_id, $transformations, $type, 'transformations' );
119+
$return = array_merge( $return, $result );
120+
}
121+
122+
// Save text overlay even if empty (allow clearing).
123+
$text_overlay = $request->get_param( 'textOverlay' );
124+
125+
if ( isset( $text_overlay ) && array_key_exists( 'transformation', (array) $text_overlay ) ) {
126+
$result = $this->save_transformation_type( $attachment_id, $text_overlay['transformation'], $type, 'text_overlay' );
127+
$return = array_merge( $return, $result );
128+
}
129+
130+
// Save image overlay even if empty (allow clearing).
131+
$image_overlay = $request->get_param( 'imageOverlay' );
132+
133+
if ( isset( $image_overlay ) && array_key_exists( 'transformation', (array) $image_overlay ) ) {
134+
$result = $this->save_transformation_type( $attachment_id, $image_overlay['transformation'], $type, 'image_overlay' );
135+
$return = array_merge( $return, $result );
136+
}
137+
138+
return rest_ensure_response( $return );
139+
}
140+
141+
/**
142+
* Shared helper to save a transformation type (main, text overlay, image overlay).
143+
*
144+
* @param int $attachment_id Attachment ID.
145+
* @param string $transformation Transformation string.
146+
* @param string $type Resource type.
147+
* @param string $save_type Type to save (transformations, text_overlay, image_overlay).
148+
* @return array
149+
*/
150+
protected function save_transformation_type( $attachment_id, $transformation, $type, $save_type ) {
109151
$media = $this->assets->media;
110-
$attachment_id = $request->get_param( 'ID' );
111-
$transformations = $request->get_param( 'transformations' );
112-
$type = $media->get_resource_type( $attachment_id );
113-
$transformation_array = $media->get_transformations_from_string( $transformations, $type );
152+
$transformation_array = $media->get_transformations_from_string( $transformation, $type );
114153
$cleaned = Api::generate_transformation_string( $transformation_array, $type );
115-
Relate::update_transformations( $attachment_id, $cleaned );
116-
$return = array(
117-
'transformations' => $cleaned,
118-
);
119154

120-
if ( $cleaned !== $transformations ) {
121-
$return['note'] = __( 'Some transformations were invalid and were removed.', 'cloudinary' );
155+
switch ( $save_type ) {
156+
case 'transformations':
157+
Relate::update_transformations( $attachment_id, $cleaned );
158+
break;
159+
case 'text_overlay':
160+
break;
161+
case 'image_overlay':
162+
break;
122163
}
123164

124-
return rest_ensure_response( $return );
165+
$result = array( $save_type => $cleaned );
166+
167+
if ( $cleaned !== $transformation ) {
168+
$result['note'] = __( 'Some transformations were invalid and were removed.', 'cloudinary' );
169+
}
170+
171+
return $result;
125172
}
126173

127174
/**

php/class-assets.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,8 @@ public function build_item( $item ) {
12021202
'file' => ! empty( $parts[1] ) ? $parts[1] : wp_basename( $item['sized_url'] ),
12031203
'size' => $break,
12041204
'transformations' => $item['transformations'] ? $item['transformations'] : null,
1205+
'text_overlay' => $item['text_overlay'] ? $item['text_overlay'] : null,
1206+
'image_overlay' => $item['image_overlay'] ? $item['image_overlay'] : null,
12051207
'edit_url' => admin_url( add_query_arg( $args, 'admin.php' ) ),
12061208
);
12071209

php/class-utils.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,14 +366,18 @@ protected static function upgrade_install() {
366366
protected static function get_upgrade_sequence() {
367367
$upgrade_sequence = array();
368368
$sequences = array(
369-
'3.0.0' => array(
369+
'3.0.0' => array(
370370
'range' => array( '3.0.0' ),
371371
'method' => array( 'Cloudinary\Utils', 'upgrade_3_0_1' ),
372372
),
373-
'3.1.9' => array(
373+
'3.1.9' => array(
374374
'range' => array( '3.0.1', '3.1.9' ),
375375
'method' => array( 'Cloudinary\Utils', 'upgrade_3_1_9' ),
376376
),
377+
'3.2.14' => array(
378+
'range' => array( '3.1.9', '3.2.14' ),
379+
'method' => array( 'Cloudinary\Utils', 'upgrade_3_2_14' ),
380+
),
377381

378382
);
379383
$previous_version = get_option( Sync::META_KEYS['db_version'], '3.0.0' );
@@ -449,6 +453,22 @@ public static function upgrade_3_1_9() {
449453
update_option( Sync::META_KEYS['db_version'], get_plugin_instance()->version );
450454
}
451455

456+
/**
457+
* Upgrade DB from v3.1.9 to v3.2.14.
458+
* Adds columns for overlay data.
459+
*/
460+
public static function upgrade_3_2_14() {
461+
global $wpdb;
462+
$tablename = self::get_relationship_table();
463+
464+
// Add new columns for overlays.
465+
$wpdb->query( "ALTER TABLE {$tablename} ADD COLUMN `text_overlay` TEXT DEFAULT NULL AFTER `transformations`" ); // phpcs:ignore WordPress.DB
466+
$wpdb->query( "ALTER TABLE {$tablename} ADD COLUMN `image_overlay` TEXT DEFAULT NULL AFTER `text_overlay`" ); // phpcs:ignore WordPress.DB
467+
468+
// Set DB Version.
469+
update_option( Sync::META_KEYS['db_version'], get_plugin_instance()->version );
470+
}
471+
452472
/**
453473
* Gets the URL for opening a Support Request.
454474
*

php/relate/class-relationship.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
* @property string|null $public_id
2020
* @property string|null $signature
2121
* @property string|null $transformations
22+
* @property string|null $text_overlay
23+
* @property string|null $image_overlay
2224
* @property string|null $sized_url
2325
* @property string|null $media_context
2426
*/

src/js/asset-edit.js

Lines changed: 79 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ const AssetEdit = {
3434

3535
// Buttons
3636
saveButton: document.getElementById( 'cld-asset-edit-save' ),
37+
saveTextOverlayButton: document.getElementById( 'cld-asset-save-text-overlay' ),
38+
saveImageOverlayButton: document.getElementById( 'cld-asset-save-image-overlay' ),
3739
removeTextOverlayButton: document.getElementById( 'cld-asset-remove-text-overlay' ),
3840
removeImageOverlayButton: document.getElementById( 'cld-asset-remove-image-overlay' ),
3941

@@ -42,13 +44,42 @@ const AssetEdit = {
4244
imageGrid: document.getElementById( 'edit-overlay-grid-image' ),
4345
imagePreviewWrapper: document.getElementById( 'edit-overlay-select-image-preview' ),
4446

47+
// Mapping
48+
textOverlayMap: null,
49+
imageOverlayMap: null,
50+
4551
init() {
4652
const item = JSON.parse( this.wrap.dataset.item );
4753
this.id = item.ID;
4854
this.base = item.base + item.size + '/';
4955
this.publicId = item.file;
5056
this.transformationsInput.value = item.transformations ? item.transformations : '';
5157

58+
// Set up centralized text overlay mapping as a property
59+
this.textOverlayMap = [
60+
{ key: 'text', input: this.textOverlayTextInput, defaultValue: '', event: 'input' },
61+
{ key: 'color', input: this.textOverlayColorInput, defaultValue: '', event: 'input' },
62+
{ key: 'fontFace', input: this.textOverlayFontFaceInput, defaultValue: 'Arial', event: 'input' },
63+
{ key: 'fontSize', input: this.textOverlayFontSizeInput, defaultValue: 20, event: 'input' },
64+
{ key: 'position', input: this.textOverlayPositionInput, defaultValue: '', event: 'change' },
65+
{ key: 'xOffset', input: this.textOverlayXOffsetInput, defaultValue: 0, event: 'input' },
66+
{ key: 'yOffset', input: this.textOverlayYOffsetInput, defaultValue: 0, event: 'input' }
67+
];
68+
69+
// Set up centralized image overlay mapping as a property
70+
this.imageOverlayMap = [
71+
{ key: 'imageId', input: this.imageOverlayImageIdInput, defaultValue: '', event: 'input' },
72+
{ key: 'size', input: this.imageOverlaySizeInput, defaultValue: 100, event: 'input' },
73+
{ key: 'opacity', input: this.imageOverlayOpacityInput, defaultValue: 20, event: 'input' },
74+
{ key: 'position', input: this.imageOverlayPositionInput, defaultValue: '', event: 'change' },
75+
{ key: 'xOffset', input: this.imageOverlayXOffsetInput, defaultValue: 0, event: 'input' },
76+
{ key: 'yOffset', input: this.imageOverlayYOffsetInput, defaultValue: 0, event: 'input' }
77+
];
78+
79+
// Set overlay input values from item data using unified helper
80+
this.setOverlayInputs(this.textOverlayMap, item.text_overlay);
81+
this.setOverlayInputs(this.imageOverlayMap, item.image_overlay);
82+
5283
// Init components.
5384
this.initPreview();
5485
this.initEditor();
@@ -64,16 +95,7 @@ const AssetEdit = {
6495
this.transformationsInput.addEventListener( 'input', ( ev ) => {
6596
this.preview.setSrc( this.getSrc() );
6697
} );
67-
68-
// Add overlay preview updates
6998
this.addOverlayEventListeners();
70-
71-
this.transformationsInput.addEventListener( 'keydown', ( ev ) => {
72-
if ( 'Enter' === ev.code ) {
73-
ev.preventDefault();
74-
this.saveButton.dispatchEvent( new Event( 'click' ) );
75-
}
76-
} );
7799
},
78100
addOverlayEventListeners() {
79101
const updatePreviewForTextOverlay = () => {
@@ -107,32 +129,18 @@ const AssetEdit = {
107129
});
108130
}
109131

110-
// Secondary text overlay inputs (only update if text exists)
111-
const secondaryTextInputs = [
112-
{ input: this.textOverlayColorInput, event: 'input' },
113-
{ input: this.textOverlayFontFaceInput, event: 'input' },
114-
{ input: this.textOverlayFontSizeInput, event: 'input' },
115-
{ input: this.textOverlayPositionInput, event: 'change' },
116-
{ input: this.textOverlayXOffsetInput, event: 'input' },
117-
{ input: this.textOverlayYOffsetInput, event: 'input' }
118-
];
132+
// Use textOverlayMap for secondary text overlay inputs (exclude text input)
133+
const secondaryTextInputs = this.textOverlayMap.filter(({ key }) => key !== 'text');
119134

120-
// Secondary image overlay inputs (only update if image exists)
121-
const secondaryImageInputs = [
122-
{ input: this.imageOverlaySizeInput, event: 'input' },
123-
{ input: this.imageOverlayOpacityInput, event: 'input' },
124-
{ input: this.imageOverlayPositionInput, event: 'change' },
125-
{ input: this.imageOverlayXOffsetInput, event: 'input' },
126-
{ input: this.imageOverlayYOffsetInput, event: 'input' }
127-
];
135+
// Use imageOverlayMap for secondary image overlay inputs (exclude imageId)
136+
const secondaryImageInputs = this.imageOverlayMap.filter(({ key }) => key !== 'imageId');
128137

129138
// Add event listeners with appropriate logic for each overlay type
130139
secondaryTextInputs.forEach(({ input, event }) => {
131140
if (input) {
132141
// Special handling for color input to ensure value is updated
133142
if (input === this.textOverlayColorInput) {
134143
input.addEventListener(event, () => {
135-
// Use setTimeout to ensure the input value has been updated
136144
setTimeout(updatePreviewForTextOverlay, 0);
137145
});
138146
} else {
@@ -151,8 +159,8 @@ const AssetEdit = {
151159
this.editor = AssetEditor.init();
152160
this.editor.onBefore( () => this.preview.reset() );
153161
this.editor.onComplete( ( result ) => {
154-
this.transformationsInput.value = result.transformations;
155-
this.preview.setSrc( this.base + result.transformations + this.publicId, true );
162+
this.preview.setSrc( this.getSrc(), true );
163+
156164
if ( result.note ) {
157165
alert( result.note );
158166
}
@@ -164,6 +172,24 @@ const AssetEdit = {
164172
transformations: this.transformationsInput.value,
165173
} );
166174
} );
175+
176+
this.saveTextOverlayButton.addEventListener('click', () => {
177+
const textOverlay = this.getOverlayData(this.textOverlayMap);
178+
textOverlay.transformation = this.buildTextOverlay();
179+
this.editor.save({
180+
ID: this.id,
181+
textOverlay
182+
});
183+
});
184+
185+
this.saveImageOverlayButton.addEventListener('click', () => {
186+
const imageOverlay = this.getOverlayData(this.imageOverlayMap);
187+
imageOverlay.transformation = this.buildImageOverlay();
188+
this.editor.save({
189+
ID: this.id,
190+
imageOverlay
191+
});
192+
});
167193
},
168194
initGravityGrid( gridId ) {
169195
const grid = document.getElementById( gridId );
@@ -282,21 +308,10 @@ const AssetEdit = {
282308
}
283309
},
284310
clearTextOverlay() {
285-
// Define text overlay fields with their default values
286-
const textFields = [
287-
{ input: this.textOverlayTextInput, value: '' },
288-
{ input: this.textOverlayColorInput, value: '' },
289-
{ input: this.textOverlayFontFaceInput, value: 'Arial' },
290-
{ input: this.textOverlayFontSizeInput, value: 20 },
291-
{ input: this.textOverlayPositionInput, value: '' },
292-
{ input: this.textOverlayXOffsetInput, value: 0 },
293-
{ input: this.textOverlayYOffsetInput, value: 0 }
294-
];
295-
296-
// Reset all text overlay fields
297-
textFields.forEach(({ input, value }) => {
311+
// Use textOverlayMap for text overlay fields and their default values
312+
this.textOverlayMap.forEach(({ input, defaultValue }) => {
298313
if (input) {
299-
input.value = value;
314+
input.value = defaultValue;
300315
}
301316
});
302317

@@ -310,20 +325,10 @@ const AssetEdit = {
310325
this.preview.setSrc(this.getSrc());
311326
},
312327
clearImageOverlay() {
313-
// Define image overlay fields with their default values
314-
const imageFields = [
315-
{ input: this.imageOverlayImageIdInput, value: '' },
316-
{ input: this.imageOverlaySizeInput, value: 100 },
317-
{ input: this.imageOverlayOpacityInput, value: 20 },
318-
{ input: this.imageOverlayPositionInput, value: '' },
319-
{ input: this.imageOverlayXOffsetInput, value: 0 },
320-
{ input: this.imageOverlayYOffsetInput, value: 0 }
321-
];
322-
323-
// Reset all image overlay fields
324-
imageFields.forEach(({ input, value }) => {
328+
// Use imageOverlayMap for image overlay fields and their default values
329+
this.imageOverlayMap.forEach(({ input, defaultValue }) => {
325330
if (input) {
326-
input.value = value;
331+
input.value = defaultValue;
327332
}
328333
});
329334

@@ -456,7 +461,24 @@ const AssetEdit = {
456461
urlParts.push(this.publicId);
457462

458463
return urlParts.join('/').replace(/\/+/g, '/');
459-
}
464+
},
465+
getOverlayData(map) {
466+
const overlay = {};
467+
468+
map.forEach(({ key, input }) => {
469+
overlay[key] = input?.value || '';
470+
});
471+
472+
return overlay;
473+
},
474+
475+
setOverlayInputs(map, data) {
476+
map.forEach(({ key, input, defaultValue }) => {
477+
if (input) {
478+
input.value = (data && data[key] !== undefined) ? data[key] : defaultValue;
479+
}
480+
});
481+
},
460482
};
461483

462484
window.addEventListener( 'load', () => AssetEdit.init() );

0 commit comments

Comments
 (0)