Skip to content

Commit 12a471c

Browse files
authored
Pro 7810 quick upload placeholder (#5019)
* Supports placeholder with new UI * Forces option placeholder to be true to avoid unexpected bugs, user still can disable it (handed internally) * Drag and drop style with placeholder * Remove label and better handling of all events in image uploader UI
1 parent 6f049ec commit 12a471c

4 files changed

Lines changed: 117 additions & 39 deletions

File tree

modules/@apostrophecms/image-widget/index.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,9 @@ module.exports = {
77
dimensionAttrs: false,
88
placeholder: true,
99
initialModal: false,
10-
placeholderClass: false,
11-
placeholderImage: 'jpg',
1210
linkWithType: [ '@apostrophecms/any-page-type' ],
1311
// Should we write e.g. a reset style for the `figure` element?
1412
inlineStyles: true,
15-
// contextual: true, TODO: FIX
1613
components: {
1714
widget: 'AposImageWidget'
1815
}
@@ -140,6 +137,8 @@ module.exports = {
140137
};
141138
},
142139
init(self) {
140+
self.showPlaceholder = self.options.placeholder !== false;
141+
self.options.placeholder = true;
143142
self.determineBestAssetUrl('placeholder');
144143
},
145144
handlers(self) {
@@ -180,5 +179,16 @@ module.exports = {
180179
}
181180
}
182181
};
182+
},
183+
extendMethods(self) {
184+
return {
185+
getBrowserData(_super, req) {
186+
return {
187+
..._super(req),
188+
showPlaceholder: self.showPlaceholder,
189+
placeholderUrl: self.options.placeholderUrl
190+
};
191+
}
192+
};
183193
}
184194
};

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<AposMediaUploaderUi
77
:min-size="props.options?.minSize"
88
:accept="accept"
9+
:placeholder="placeholder"
910
@upload="upload"
1011
@media="selectFromManager"
1112
/>
@@ -25,8 +26,9 @@ import {
2526
import { useAposWidget } from 'Modules/@apostrophecms/widget-type/composables/AposWidget';
2627
import aposWidgetProps from 'Modules/@apostrophecms/widget-type/composables/AposWidgetProps';
2728
28-
const moduleOptions = window.apos.modules['@apostrophecms/image'];
29-
const accept = moduleOptions.schema.find(field => field.name === 'attachment').accept;
29+
const imgModuleOptions = apos.modules['@apostrophecms/image'];
30+
const widgetModuleOptions = apos.modules['@apostrophecms/image-widget'];
31+
const accept = imgModuleOptions.schema.find(field => field.name === 'attachment').accept;
3032
3133
const emit = defineEmits([ 'edit', 'update' ]);
3234
@@ -39,14 +41,21 @@ const hasImage = computed(() => {
3941
return Boolean(props.modelValue?._image?.length);
4042
});
4143
44+
const placeholder = computed(() => {
45+
return widgetModuleOptions.showPlaceholder === false
46+
? null
47+
: widgetModuleOptions.placeholderUrl;
48+
});
49+
4250
watch(() => props.modelValue, async (newVal) => {
4351
if (hasImage.value) {
4452
await renderContent();
4553
}
4654
}, { immediate: true });
4755
4856
async function selectFromManager() {
49-
const modalItem = apos.modal.modals.find((modal) => modal.itemName === '@apostrophecms/image:manager');
57+
const modalItem = apos.modal.modals
58+
.find((modal) => modal.itemName === '@apostrophecms/image:manager');
5059
if (!modalItem) {
5160
return;
5261
}
@@ -151,7 +160,7 @@ async function upload(files = []) {
151160
}
152161
153162
try {
154-
const emptyDoc = await apos.http.post(moduleOptions.action, {
163+
const emptyDoc = await apos.http.post(imgModuleOptions.action, {
155164
busy: true,
156165
body: {
157166
_newInstance: true
@@ -173,7 +182,7 @@ async function upload(files = []) {
173182
attachment
174183
});
175184
176-
const imgPiece = await apos.http.post(moduleOptions.action, {
185+
const imgPiece = await apos.http.post(imgModuleOptions.action, {
177186
busy: true,
178187
body: imageData,
179188
draft: true

modules/@apostrophecms/image/ui/apos/components/AposMediaUploaderUi.vue

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
<template>
2-
<label
2+
<div
33
ref="mediaUploaderEl"
44
class="apos-media-uploader"
5+
:style="uploaderStyle"
56
:class="{
67
'apos-media-uploader--disabled': props.disabled,
78
'apos-is-dragging': dragging,
8-
'apos-is-dragging--over': dragover
9+
'apos-is-dragging--over': dragover,
10+
'apos-has-placeholder': hasPlaceholder
911
}"
1012
@drop.prevent="uploadMedia"
1113
@dragover.prevent=""
1214
@dragenter="dragOverEnter"
1315
@dragleave="dragOverLeave"
14-
@drop="dragOverLeave"
16+
@click.stop="openMedia"
1517
>
1618
<div class="apos-media-uploader__inner">
1719
<!-- if we want animations.. -->
@@ -46,12 +48,12 @@
4648
tabindex="-1"
4749
@input="uploadMedia"
4850
>
49-
</label>
51+
</div>
5052
</template>
5153

5254
<script setup>
5355
import {
54-
ref, inject, useTemplateRef, onMounted, onUnmounted, computed
56+
ref, inject, useTemplateRef, onMounted, onUnmounted, computed, onBeforeUnmount
5557
} from 'vue';
5658
5759
const $t = inject('i18n');
@@ -71,6 +73,10 @@ const props = defineProps({
7173
accept: {
7274
type: String,
7375
default: 'gif,.jpg,.png,.svg,.webp,.jpeg'
76+
},
77+
placeholder: {
78+
type: String,
79+
default: null
7480
}
7581
});
7682
const emit = defineEmits([ 'media', 'upload' ]);
@@ -112,6 +118,20 @@ const dragover = computed(() => {
112118
return dragOverCounter.value > 0;
113119
});
114120
121+
const hasPlaceholder = computed(() => {
122+
return Boolean(props.placeholder);
123+
});
124+
125+
const uploaderStyle = computed(() => {
126+
if (!props.placeholder) {
127+
return {};
128+
}
129+
130+
return {
131+
'background-image': `url(${props.placeholder})`
132+
};
133+
});
134+
115135
/**
116136
* @returns {String} - Formatted string containing configured maximum size
117137
*/
@@ -170,6 +190,10 @@ onMounted(() => {
170190
document.addEventListener('drop', dropListener);
171191
});
172192
193+
onBeforeUnmount(() => {
194+
unbindEmits();
195+
});
196+
173197
onUnmounted(() => {
174198
document.removeEventListener('dragenter', dragEnterListener);
175199
document.removeEventListener('dragleave', dragLeaveListener);
@@ -179,17 +203,28 @@ onUnmounted(() => {
179203
/** Bind click events on links contained in translated texts */
180204
function bindEmits() {
181205
mediaUploaderEl.value.querySelectorAll('[data-apos-click]').forEach((el) => {
182-
el.addEventListener('click', (event) => {
183-
const action = event.currentTarget.getAttribute('data-apos-click');
184-
if (action === 'openMedia') {
185-
openMedia();
186-
} else if (action === 'searchFile') {
187-
searchFile();
188-
}
189-
});
206+
el.addEventListener('click', btnClickEvent);
207+
});
208+
}
209+
210+
/** Unbind click events on links contained in translated texts */
211+
function unbindEmits() {
212+
mediaUploaderEl.value.querySelectorAll('[data-apos-click]').forEach((el) => {
213+
el.removeEventListener('click', btnClickEvent);
190214
});
191215
}
192216
217+
/** Bind each button event */
218+
function btnClickEvent(event) {
219+
event.stopPropagation();
220+
const action = event.currentTarget.getAttribute('data-apos-click');
221+
if (action === 'openMedia') {
222+
openMedia();
223+
} else if (action === 'searchFile') {
224+
searchFile();
225+
}
226+
};
227+
193228
function openMedia() {
194229
emit('media');
195230
}
@@ -206,7 +241,7 @@ function searchFile() {
206241
* @param {DragEvent} event - Dropped file event
207242
*/
208243
async function uploadMedia (event) {
209-
// Set `dragover` in case the media was dropped.
244+
// Reset drag over counter when dropping file
210245
dragOverCounter.value = 0;
211246
const files = event.dataTransfer ? event.dataTransfer.files : event.target.files;
212247
if (!props.accept) {
@@ -232,6 +267,7 @@ async function uploadMedia (event) {
232267
</script>
233268
<style>
234269
.apos-is-highlighted .apos-media-uploader {
270+
/* stylelint-disable-next-line declaration-no-important */
235271
outline-color: transparent !important;
236272
}
237273
</style>
@@ -240,7 +276,10 @@ async function uploadMedia (event) {
240276
@include apos-button-reset();
241277
242278
& {
279+
font-family: var(--a-family-default);
280+
position: relative;
243281
display: flex;
282+
overflow: hidden;
244283
box-sizing: border-box;
245284
align-items: center;
246285
justify-content: center;
@@ -251,15 +290,34 @@ async function uploadMedia (event) {
251290
grid-row: 1 / 3;
252291
min-height: 350px;
253292
background-color: var(--a-base-10);
293+
background-size: cover;
294+
}
295+
296+
&::before {
297+
content: '';
298+
z-index: $z-index-base;
299+
position: absolute;
300+
inset: 0;
301+
background: rgba(255 255 255 / 70%);
254302
}
255303
256304
&.apos-is-dragging {
257-
border: 1px solid var(--a-primary);
258-
box-shadow: 0 0 0 3px var(--a-primary-transparent-50),
305+
outline: 1px solid var(--a-primary);
306+
box-shadow: 0 0 0 3px var(--a-primary-transparent-50);
259307
}
260308
261309
&.apos-is-dragging--over {
262-
background-color: var(--a-primary-transparent-05);
310+
background-color: var(--a-white);
311+
312+
&::before {
313+
background-color: var(--a-primary-transparent-05);
314+
}
315+
}
316+
317+
&.apos-has-placeholder {
318+
&.apos-is-dragging--over::before {
319+
background-color: var(--a-primary-transparent-25);
320+
}
263321
}
264322
}
265323
@@ -278,6 +336,7 @@ async function uploadMedia (event) {
278336
}
279337
280338
.apos-media-uploader__inner {
339+
z-index: $z-index-default;
281340
display: flex;
282341
flex-direction: column;
283342
align-items: center;
@@ -303,10 +362,10 @@ async function uploadMedia (event) {
303362
}
304363
305364
:deep(.apos-media-uploader__btn) {
306-
all: unset;
307365
@include apos-button-reset();
308366
309367
& {
368+
all: unset;
310369
color: var(--a-primary);
311370
font-weight: var(--a-weight-light);
312371
text-decoration: underline;
@@ -322,5 +381,4 @@ async function uploadMedia (event) {
322381
color: var(--a-background-inverted);
323382
font-size: var(--a-type-large);
324383
}
325-
326384
</style>

modules/@apostrophecms/widget-type/ui/apos/composables/AposWidget.js

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,24 @@ export async function _renderContent(props) {
5252
_docId: props.docId,
5353
widget,
5454
areaFieldId: props.areaFieldId,
55-
type: props.type,
56-
livePreview: props.aposLivePreview
55+
type: props.type
5756
};
5857
try {
5958
if (props.rendering && (isEqual(props.rendering.parameters, body))) {
6059
return props.rendering.html;
61-
} else {
62-
// Don't use a placeholder here, it causes flickering in live preview
63-
// mode. It is better to display the old until we display the new, we
64-
// have "busy" for clarity
65-
const result = await apos.http.post(`${apos.area.action}/render-widget?aposEdit=1&aposMode=${props.mode}`, {
66-
busy: !props.aposLivePreview,
67-
body
68-
});
69-
if (result !== 'aposLivePreviewSchemaNotYetValid') {
70-
return result;
60+
}
61+
// Don't use a placeholder here, it causes flickering in live preview
62+
// mode. It is better to display the old until we display the new, we
63+
// have "busy" for clarity
64+
const result = await apos.http.post(`${apos.area.action}/render-widget?aposEdit=1&aposMode=${props.mode}`, {
65+
busy: !aposLivePreview,
66+
body: {
67+
...body,
68+
livePreview: aposLivePreview
7169
}
70+
});
71+
if (result !== 'aposLivePreviewSchemaNotYetValid') {
72+
return result;
7273
}
7374
} catch (e) {
7475
// eslint-disable-next-line no-console

0 commit comments

Comments
 (0)