Fix photo dropdown not closing after deletion #92

Merged
raucao merged 1 commits from bug/photo_menu into master 2026-08-30 15:05:13 +00:00
2 changed files with 80 additions and 12 deletions
+25 -11
View File
@@ -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) {