I can move the images around now

This commit is contained in:
Priec
2026-06-22 19:03:33 +02:00
parent 2d2aa012ec
commit 51155f2fd2
3 changed files with 106 additions and 73 deletions

View File

@@ -127,62 +127,71 @@
</div>
{# --- Images gallery ------------------------------------------------------- #}
{# Existing images are reorderable (drag) and removable; the kept set is #}
{# submitted in order as repeated `existing_images` ids. New uploads accumulate #}
{# across separate "Add images" clicks into a DataTransfer that backs the hidden #}
{# `image` input (a native file input would otherwise replace its selection on #}
{# every pick); the controller stores and appends them after the kept images. #}
{# Unified drag-orderable gallery: existing images (with id) and new uploads #}
{# (placeholder blobs) live in a single list. The full order is submitted as #}
{# repeated `image_order` fields — an integer id for kept images or `new` for #}
{# each uploaded file. The DataTransfer backing the hidden `image` file input #}
{# is rebuilt after every reorder / add / remove so the file-part order matches #}
{# the relative order of `new` slots in `image_order`. #}
<script id="images-data" type="application/json">{% if product %}{{ product.images | json_encode() | safe }}{% else %}[]{% endif %}</script>
<div class="space-y-2" x-data="{
images: JSON.parse(document.getElementById('images-data').textContent),
staged: [],
init() {
const existing = JSON.parse(document.getElementById('images-data').textContent);
this.items = existing.map(im => ({ type: 'existing', id: im.id, image_id: im.image_id }));
},
items: [],
dt: new DataTransfer(),
dragIndex: null,
rebuildDt() {
this.dt = new DataTransfer();
for (const it of this.items) {
if (it.type === 'new') this.dt.items.add(it.file);
}
this.$refs.holder.files = this.dt.files;
},
onDrop(i) {
if (this.dragIndex === null || this.dragIndex === i) { this.dragIndex = null; return; }
this.images.splice(i, 0, this.images.splice(this.dragIndex, 1)[0]);
this.items.splice(i, 0, this.items.splice(this.dragIndex, 1)[0]);
this.dragIndex = null;
this.rebuildDt();
},
addFiles(e) {
for (const f of e.target.files) { this.dt.items.add(f); this.staged.push({ url: URL.createObjectURL(f) }); }
this.$refs.holder.files = this.dt.files;
for (const f of e.target.files) {
this.items.push({ type: 'new', file: f, url: URL.createObjectURL(f) });
}
this.rebuildDt();
e.target.value = '';
},
removeStaged(i) {
this.dt.items.remove(i);
URL.revokeObjectURL(this.staged[i].url);
this.staged.splice(i, 1);
this.$refs.holder.files = this.dt.files;
remove(i) {
const it = this.items[i];
if (it.type === 'new') URL.revokeObjectURL(it.url);
this.items.splice(i, 1);
this.rebuildDt();
},
}">
<span class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="images", lang=lang | default(value='sk')) }}</span>
<p class="{{ sublabel }}">{{ t(key="gallery-hint", lang=lang | default(value='sk')) }}</p>
<div class="flex flex-wrap gap-3" x-show="images.length || staged.length">
<template x-for="(im, i) in images" :key="im.id">
<div class="flex flex-wrap gap-3" x-show="items.length">
<template x-for="(it, i) in items" :key="it.type === 'existing' ? it.id : it.url">
<div draggable="true"
@dragstart="dragIndex = i"
@dragover.prevent
@drop.prevent="onDrop(i)"
:class="dragIndex === i ? 'opacity-50' : ''"
class="group relative size-24 cursor-move overflow-hidden rounded-radius border border-outline dark:border-outline-dark">
<input type="hidden" name="existing_images" :value="im.id">
<img :src="`/images/${im.image_id}`" alt="" class="size-full object-cover">
<input type="hidden" name="image_order" :value="it.type === 'existing' ? it.id : 'new'">
<img :src="it.type === 'existing' ? `/images/${it.image_id}` : it.url" alt="" class="size-full object-cover">
<span x-show="i === 0"
class="absolute left-1 top-1 rounded-radius bg-primary px-1.5 py-0.5 text-[10px] font-semibold text-on-primary dark:bg-primary-dark dark:text-on-primary-dark">{{ t(key="main-image", lang=lang | default(value='sk')) }}</span>
<button type="button" @click="images.splice(i, 1)"
class="absolute right-1 top-1 flex size-5 items-center justify-center rounded-full bg-surface/70 text-xs text-danger opacity-0 transition group-hover:opacity-100 dark:bg-surface-dark/70"
title="{{ t(key='delete', lang=lang | default(value='sk')) }}"></button>
</div>
</template>
{# Newly staged uploads (not yet saved): previews + remove. #}
<template x-for="(f, i) in staged" :key="f.url">
<div class="group relative size-24 overflow-hidden rounded-radius border border-dashed border-outline dark:border-outline-dark">
<img :src="f.url" alt="" class="size-full object-cover">
<span x-show="images.length === 0 && i === 0"
class="absolute left-1 top-1 rounded-radius bg-primary px-1.5 py-0.5 text-[10px] font-semibold text-on-primary dark:bg-primary-dark dark:text-on-primary-dark">{{ t(key="main-image", lang=lang | default(value='sk')) }}</span>
<button type="button" @click="removeStaged(i)"
<button type="button" @click="remove(i)"
class="absolute right-1 top-1 flex size-5 items-center justify-center rounded-full bg-surface/70 text-xs text-danger opacity-0 transition group-hover:opacity-100 dark:bg-surface-dark/70"
title="{{ t(key='delete', lang=lang | default(value='sk')) }}"></button>
</div>