Skip to content

Commit 09a90ec

Browse files
authored
Pro 8087 aspect ratio image widget (#5036)
* share code between AposTheme mixin and composable * moves the postprocess logic at the input relationship level because it makes more sense and live preview works * in AposEditorMixin, postprocess method does nothing anymore * moves postprocess logic in piece-type ui lib folder (no vue related) * adds warning in empty function postprocess
1 parent e0cfe36 commit 09a90ec

11 files changed

Lines changed: 140 additions & 101 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@
99
* `apos.doc.edit` now accepts an optional `values` object as the final parameter, containing initial values for some or all fields. This is supported only when editing existing documents.
1010
* When specifying a modal name to be executed, developers may now register "transformers" to be invoked first, using pipe syntax. For example, the modal name `aposSectionTemplateLibraryWidgetToDoc|AposDocEditor` will invoke the transformer `aposSectionTemplateLibraryWidgetToDoc` with the original props, and pass the returned result to `AposDocEditor`. Note that transformers are awaited. Transformers are registered in frontend admin UI code by passing a name and a function to `apos.ui.addTransformer`.
1111
* Adds quick image upload UI to `@apostrophecms/image-widget`.
12+
* Makes autocropping work when uploading or selecting images from the new quick image upload UI.
1213

1314
### Fixes
1415

1516
* The `?render-areas=1` API feature now correctly disregards areas in separate documents loaded via relationship fields. Formerly their presence resulted in an error, not a rendering.
1617
* Make conditional fields work in Image Editor.
1718
* Importing a custom icon from an npm module using a `~` path per the admin UI now works per the documentation, as long as the Vue component used for the icon is structured like those found in `@apostrophecms/vue-material-design-icons`.
1819
* The `button: true` flag works again for piece module utility operations. Previously the button appeared but did not trigger the desired operation.
20+
* Fixes the widget data being cloned to be saved before the `postprocess` method being called, which leads to a loss of data in `AposWidgetEditor` (like the autocrop data).
21+
* In editors like `AposWidgetEditor` relationships are now post processed after they are updated in `AposInputRelationship` only for the relationship that has been updated.
22+
It allows live preview to work well with it, it also avoids complexity and fixes updated data not being properly synced between the editor and the `AposSchema`.
1923

2024
### Changes
2125

modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,6 @@ export default {
637637
638638
let doc;
639639
try {
640-
await this.postprocess();
641640
doc = await requestMethod(route, {
642641
busy: true,
643642
body,

modules/@apostrophecms/image-widget/ui/apos/components/AposImageWidget.vue

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ import { klona } from 'klona';
2424
import {
2525
computed, watch
2626
} from 'vue';
27-
import { useAposWidget } from 'Modules/@apostrophecms/widget-type/composables/AposWidget';
28-
import aposWidgetProps from 'Modules/@apostrophecms/widget-type/composables/AposWidgetProps';
27+
import { useAposWidget } from 'Modules/@apostrophecms/widget-type/composables/AposWidget.js';
28+
import aposWidgetProps from 'Modules/@apostrophecms/widget-type/composables/AposWidgetProps.js';
29+
import { postprocessRelationships } from 'Modules/@apostrophecms/piece-type/lib/postprocessRelationships.js';
2930
3031
const props = defineProps(aposWidgetProps);
31-
3232
const imgModuleOptions = apos.modules['@apostrophecms/image'];
3333
const widgetModuleOptions = apos.modules[`${props.type}-widget`];
3434
const accept = imgModuleOptions.schema.find(field => field.name === 'attachment').accept;
@@ -79,10 +79,13 @@ async function selectFromManager() {
7979
apos.area.widgetOptions = apos.area.widgetOptions.slice(1);
8080
8181
if (selectedImg) {
82-
emit('update', {
82+
const widgetData = {
8383
...props.modelValue,
8484
_image: [ selectedImg ]
85-
});
85+
};
86+
await postprocessRelationships(widgetModuleOptions.schema, widgetData, props.options);
87+
88+
emit('update', widgetData);
8689
}
8790
}
8891
@@ -171,31 +174,30 @@ async function upload(files = []) {
171174
},
172175
draft: true
173176
});
174-
175-
const formData = new window.FormData();
177+
const formData = new FormData();
176178
formData.append('file', file);
177179
178180
// Make an async request to upload the image.
179181
const attachment = await apos.http.post('/api/v1/@apostrophecms/attachment/upload', {
180182
busy: true,
181183
body: formData
182184
});
183-
184185
const imageData = Object.assign(emptyDoc, {
185186
title: attachment.title,
186187
attachment
187188
});
188-
189189
const imgPiece = await apos.http.post(imgModuleOptions.action, {
190190
busy: true,
191191
body: imageData,
192192
draft: true
193193
});
194-
195-
emit('update', {
194+
const widgetData = {
196195
...props.modelValue,
197196
_image: [ imgPiece ]
198-
});
197+
};
198+
await postprocessRelationships(widgetModuleOptions.schema, widgetData, props.options);
199+
200+
emit('update', widgetData);
199201
} catch (e) {
200202
const msg = e.body?.message ? e.body.message : this.$t('apostrophe:uploadError');
201203
await apos.notify(msg, {

modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js

Lines changed: 5 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -215,60 +215,15 @@ export default {
215215
},
216216
triggerValidate() {
217217
this.triggerValidation = true;
218-
this.$nextTick(async () => {
218+
this.$nextTick(() => {
219219
this.triggerValidation = false;
220220
});
221221
},
222-
// Perform any postprocessing required by direct or nested schema fields
223-
// before the object can be saved
224222
async postprocess() {
225-
// Relationship fields may have postprocessors (e.g. autocropping)
226-
const relationships = findRelationships(this.schema, this.docFields.data);
227-
for (const relationship of relationships) {
228-
if (!(relationship.value && relationship.field.postprocessor)) {
229-
continue;
230-
}
231-
const withType = relationship.field.withType;
232-
const module = apos.modules[withType];
233-
relationship.context[relationship.field.name] = (await apos.http.post(`${module.action}/${relationship.field.postprocessor}`, {
234-
qs: {
235-
aposMode: 'draft'
236-
},
237-
body: {
238-
relationship: relationship.value,
239-
// Pass the options of the widget currently being edited, some
240-
// postprocessors need these
241-
// (e.g. autocropping cares about widget aspectRatio)
242-
widgetOptions: apos.area.widgetOptions[0]
243-
},
244-
busy: true
245-
})).relationship;
246-
}
247-
function findRelationships(schema, object) {
248-
let relationships = [];
249-
for (const field of schema) {
250-
if (field.type === 'relationship') {
251-
relationships.push({
252-
context: object,
253-
field,
254-
value: object[field.name]
255-
});
256-
} else if (field.type === 'array') {
257-
for (const value of (object[field.name] || [])) {
258-
relationships = [
259-
...relationships,
260-
findRelationships(field.schema, value)
261-
];
262-
}
263-
} else if (field.type === 'object') {
264-
relationships = [
265-
...relationships,
266-
findRelationships(field.schema, object[field.name] || {})
267-
];
268-
}
269-
}
270-
return relationships;
271-
}
223+
// eslint-disable-next-line no-console
224+
console.warn(
225+
'The function postprocess from AposEditorMixin does not do anything anymore.\nRelationship postprocessing is made at input level in AposInputRelationship and in some cases globally like in AposImageWidget.'
226+
);
272227
}
273228
}
274229
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Perform any postprocessing required by direct or nested schema fields
2+
// before the object can be saved
3+
export async function postprocessRelationships(schema, data, widgetOptions) {
4+
// Relationship fields may have postprocessors (e.g. autocropping)
5+
const relationships = findRelationships(schema, data);
6+
7+
for (const {
8+
value, field, context
9+
} of relationships) {
10+
context[field.name] = await getPostprocessedRelationship(
11+
value,
12+
field,
13+
widgetOptions
14+
);
15+
}
16+
}
17+
18+
export async function getPostprocessedRelationship(
19+
value,
20+
field,
21+
widgetOptions
22+
) {
23+
if (!field.postprocessor || !value) {
24+
return value;
25+
}
26+
const mod = apos.modules[field.withType];
27+
const response = await apos.http.post(`${mod.action}/${field.postprocessor}`, {
28+
qs: {
29+
aposMode: 'draft'
30+
},
31+
body: {
32+
relationship: value,
33+
// Pass the options of the widget currently being edited, some
34+
// postprocessors need these
35+
// (e.g. autocropping cares about widget aspectRatio)
36+
widgetOptions
37+
},
38+
busy: true
39+
});
40+
41+
return response.relationship;
42+
}
43+
44+
function findRelationships(schema, object) {
45+
let relationships = [];
46+
for (const field of schema) {
47+
if (field.type === 'relationship') {
48+
relationships.push({
49+
context: object,
50+
field,
51+
value: object[field.name]
52+
});
53+
} else if (field.type === 'array') {
54+
for (const value of (object[field.name] || [])) {
55+
relationships = [
56+
...relationships,
57+
findRelationships(field.schema, value)
58+
];
59+
}
60+
} else if (field.type === 'object') {
61+
relationships = [
62+
...relationships,
63+
findRelationships(field.schema, object[field.name] || {})
64+
];
65+
}
66+
}
67+
return relationships;
68+
}

modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { klona } from 'klona';
22
import AposInputMixin from 'Modules/@apostrophecms/schema/mixins/AposInputMixin';
33
import newInstance from 'apostrophe/modules/@apostrophecms/schema/lib/newInstance.js';
4+
import { getPostprocessedRelationship } from 'Modules/@apostrophecms/piece-type/lib/postprocessRelationships.js';
45

56
export default {
67
name: 'AposInputRelationship',
@@ -89,7 +90,7 @@ export default {
8990
customFields: [ 'help' ]
9091
};
9192
},
92-
chooserComponent () {
93+
chooserComponent() {
9394
return apos.modules[this.field.withType].components.managerModal;
9495
},
9596
disableUnpublished() {
@@ -107,8 +108,11 @@ export default {
107108

108109
return widgetOptions.minSize || [];
109110
},
110-
duplicate () {
111+
duplicate() {
111112
return this.modelValue?.duplicate ? 'apos-input--error' : null;
113+
},
114+
widgetOptions() {
115+
return apos.area.widgetOptions[0];
112116
}
113117
},
114118
watch: {
@@ -152,8 +156,12 @@ export default {
152156

153157
this.disabled = !!this.limitReached;
154158
},
155-
updateSelected(items) {
156-
this.next = items;
159+
async updateSelected(items) {
160+
this.next = await getPostprocessedRelationship(
161+
items,
162+
this.field,
163+
this.widgetOptions
164+
);
157165
},
158166
async search(qs) {
159167
const action = apos.modules[this.field.withType].action;
@@ -269,6 +277,9 @@ export default {
269277
this.updateSelected(result);
270278
}
271279
},
280+
getDefault() {
281+
return newInstance(this.field.schema);
282+
},
272283
async editRelationship (item) {
273284
const editor = this.field.editor || 'AposRelationshipEditor';
274285

@@ -279,19 +290,24 @@ export default {
279290
'model-value': item._fields
280291
});
281292

282-
if (result) {
283-
this.next = this.next.map((rel) => {
284-
return rel._id === item._id
285-
? {
286-
...item,
287-
_fields: result
288-
}
289-
: rel;
290-
});
293+
if (!result) {
294+
return;
291295
}
292-
},
293-
getDefault() {
294-
return newInstance(this.field.schema);
296+
297+
const updatedItems = this.next.map((rel) => {
298+
return rel._id === item._id
299+
? {
300+
...item,
301+
_fields: result
302+
}
303+
: rel;
304+
});
305+
306+
this.next = await getPostprocessedRelationship(
307+
updatedItems,
308+
this.field,
309+
this.widgetOptions
310+
);
295311
}
296312
}
297313
};

modules/@apostrophecms/schema/ui/apos/logic/AposSchema.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ export default {
265265
this.$emit('reset');
266266
});
267267
},
268-
updateNextAndEmit() {
268+
async updateNextAndEmit() {
269269
if (!this.schemaReady) {
270270
return;
271271
}
@@ -296,6 +296,9 @@ export default {
296296
)
297297
) {
298298
changeFound = true;
299+
300+
// fieldState never gets the relationships postprocessed data
301+
// that's why it gets seen as different than next all the time
299302
this.next.data[field.name] = this.fieldState[field.name].data;
300303
} else {
301304
this.next.data[field.name] = this.modelValue.data[field.name];
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { computed } from 'vue';
22

3-
export function useAposTheme () {
4-
const themeClass = computed(() => {
5-
const classes = [];
6-
classes.push(`apos-theme--primary-${window.apos.ui.theme.primary}`);
7-
return classes;
8-
});
9-
3+
export function useAposTheme() {
4+
const themeClass = computed(_themeClass);
105
return { themeClass };
116
};
7+
8+
export function _themeClass() {
9+
const classes = [];
10+
classes.push(`apos-theme--primary-${window.apos.ui.theme.primary}`);
11+
return classes;
12+
}
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1+
import { _themeClass } from '../composables/AposTheme.js';
2+
13
// Provides computed classes for decorating top-level Apos vue apps with a UI
24
// theme
3-
45
export default {
56
computed: {
67
themeClass() {
7-
const classes = [];
8-
classes.push(`apos-theme--primary-${window.apos.ui.theme.primary}`);
9-
return classes;
8+
return _themeClass();
109
}
1110
}
1211
};

0 commit comments

Comments
 (0)