Fix photo dropdown not closing after deletion #92
@@ -52,7 +52,7 @@ const GalleryContent = <template>
|
||||
<button
|
||||
class="dropdown-item text-danger"
|
||||
type="button"
|
||||
{{on "click" (fn @deletePhotoTask.perform closeMenu)}}
|
||||
{{on "click" (fn @deletePhoto closeMenu)}}
|
||||
>Delete Photo</button>
|
||||
{{/if}}
|
||||
</DropdownMenu>
|
||||
@@ -259,21 +259,27 @@ export default class PhotoGallery extends Component {
|
||||
this.zapModalOpen = false;
|
||||
}
|
||||
|
||||
deletePhotoTask = task(async (closeMenu) => {
|
||||
@action
|
||||
deletePhoto(closeMenu, e) {
|
||||
e?.stopPropagation();
|
||||
if (closeMenu) closeMenu();
|
||||
if (
|
||||
!confirm(
|
||||
'Are you sure you want to delete this photo? This cannot be undone.'
|
||||
)
|
||||
) {
|
||||
if (closeMenu) closeMenu();
|
||||
return;
|
||||
}
|
||||
this.deletePhotoTask.perform();
|
||||
}
|
||||
|
||||
deletePhotoTask = task(async () => {
|
||||
const deletedEventId = this.currentPhoto.eventId;
|
||||
const deletedIndex = this.args.photos?.indexOf(this.currentPhoto) ?? -1;
|
||||
|
||||
try {
|
||||
const eventId = this.currentPhoto.eventId;
|
||||
|
||||
// Publish Nostr kind: 5 deletion event first so we don't end up with dead blossom links on a failure
|
||||
const tags = [['e', eventId]];
|
||||
const tags = [['e', deletedEventId]];
|
||||
|
||||
if (this.currentPhoto.placeIdentifier) {
|
||||
tags.push(['i', this.currentPhoto.placeIdentifier]);
|
||||
@@ -316,12 +322,20 @@ export default class PhotoGallery extends Component {
|
||||
|
||||
this.toast.show('Photo deleted successfully');
|
||||
|
||||
if (closeMenu) closeMenu();
|
||||
this.handleClose();
|
||||
// Navigate to the next remaining photo, or close the gallery if none left.
|
||||
// this.args.photos may be stale until Glimmer re-renders, so filter locally.
|
||||
const remaining = (this.args.photos || []).filter(
|
||||
(p) => p.eventId !== deletedEventId
|
||||
);
|
||||
if (remaining.length === 0) {
|
||||
this.handleClose();
|
||||
} else {
|
||||
this.currentPhoto =
|
||||
remaining[Math.min(deletedIndex, remaining.length - 1)];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to delete photo:', e);
|
||||
this.toast.show('Failed to delete photo: ' + e.message);
|
||||
if (closeMenu) closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -333,7 +347,7 @@ export default class PhotoGallery extends Component {
|
||||
@handleKeydown={{this.handleKeydown}}
|
||||
@copyEventId={{this.copyEventId}}
|
||||
@canDeletePhoto={{this.canDeletePhoto}}
|
||||
@deletePhotoTask={{this.deletePhotoTask}}
|
||||
@deletePhoto={{this.deletePhoto}}
|
||||
@handleClose={{this.handleClose}}
|
||||
@photos={{@photos}}
|
||||
@currentPhoto={{this.currentPhoto}}
|
||||
@@ -356,7 +370,7 @@ export default class PhotoGallery extends Component {
|
||||
@handleKeydown={{this.handleKeydown}}
|
||||
@copyEventId={{this.copyEventId}}
|
||||
@canDeletePhoto={{this.canDeletePhoto}}
|
||||
@deletePhotoTask={{this.deletePhotoTask}}
|
||||
@deletePhoto={{this.deletePhoto}}
|
||||
@handleClose={{this.handleClose}}
|
||||
@photos={{@photos}}
|
||||
@currentPhoto={{this.currentPhoto}}
|
||||
|
||||
@@ -133,6 +133,9 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
||||
|
||||
assert.ok(confirmStub.calledOnce, 'confirmation dialog was shown');
|
||||
assert.ok(blossomSpy.notCalled, 'blossom.delete was NOT called');
|
||||
assert
|
||||
.dom('.dropdown-popover')
|
||||
.doesNotExist('dropdown menu was closed on cancel');
|
||||
});
|
||||
|
||||
test('it performs full deletion flow when confirmed', async function (assert) {
|
||||
@@ -226,7 +229,58 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
||||
toastSpy.calledWith('Photo deleted successfully'),
|
||||
'success toast was shown'
|
||||
);
|
||||
assert.ok(closed, 'gallery was closed after deletion');
|
||||
assert
|
||||
.dom('.dropdown-popover')
|
||||
.doesNotExist('dropdown menu was closed after deletion');
|
||||
assert.notOk(closed, 'gallery was kept open after deletion');
|
||||
});
|
||||
|
||||
test('it closes the gallery when deleting the last remaining photo', async function (assert) {
|
||||
this.nostrAuth.pubkey = USER_A;
|
||||
this.settings.update('experimentalEnablePhotoDeletion', true);
|
||||
Object.defineProperty(this.nostrAuth, 'signer', {
|
||||
configurable: true,
|
||||
get: () => ({
|
||||
signEvent: async (e) => ({
|
||||
...e,
|
||||
id: 'a3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1',
|
||||
sig: 'b3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1',
|
||||
pubkey: USER_A,
|
||||
}),
|
||||
getPublicKey: async () => USER_A,
|
||||
}),
|
||||
});
|
||||
|
||||
this.set('photos', [this.photos[0]]);
|
||||
this.selectedPhoto = this.photos[0];
|
||||
|
||||
let closed = false;
|
||||
this.handleClose = () => {
|
||||
closed = true;
|
||||
};
|
||||
|
||||
sinon.stub(window, 'confirm').returns(true);
|
||||
sinon.stub(this.blossom, 'delete').resolves();
|
||||
sinon.stub(this.nostrRelay, 'publish').resolves();
|
||||
sinon.stub(this.nostrData.store, 'add');
|
||||
|
||||
await render(
|
||||
<template>
|
||||
<div id="test-container">
|
||||
<div id="modal-portal"></div>
|
||||
<PhotoGallery
|
||||
@photos={{this.photos}}
|
||||
@selectedPhoto={{this.selectedPhoto}}
|
||||
@onClose={{this.handleClose}}
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
);
|
||||
|
||||
await click('.dropdown-trigger-btn');
|
||||
await click('.dropdown-item.text-danger');
|
||||
|
||||
assert.ok(closed, 'gallery was closed when last photo was deleted');
|
||||
});
|
||||
|
||||
test('it copies event id to clipboard', async function (assert) {
|
||||
|
||||
Reference in New Issue
Block a user