Compare commits
9 Commits
14827fce3e
...
v1.22.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
70d2fe1c6c
|
|||
|
6329ad986d
|
|||
|
bcfa81494e
|
|||
|
bc42694707
|
|||
|
4390b7d699
|
|||
|
7bab8dfa09
|
|||
|
51c9555273
|
|||
|
632efeeab5
|
|||
|
deeea9961f
|
@@ -6,6 +6,7 @@ import Icon from '#components/icon';
|
||||
import AppMenuSettingsMapUi from './settings/map-ui';
|
||||
import AppMenuSettingsApis from './settings/apis';
|
||||
import AppMenuSettingsNostr from './settings/nostr';
|
||||
import AppMenuSettingsExperimental from './settings/experimental';
|
||||
|
||||
export default class AppMenuSettings extends Component {
|
||||
@service settings;
|
||||
@@ -35,6 +36,7 @@ export default class AppMenuSettings extends Component {
|
||||
<AppMenuSettingsMapUi @onChange={{this.updateSetting}} />
|
||||
<AppMenuSettingsApis @onChange={{this.updateSetting}} />
|
||||
<AppMenuSettingsNostr @onChange={{this.updateSetting}} />
|
||||
<AppMenuSettingsExperimental @onChange={{this.updateSetting}} />
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
49
app/components/app-menu/settings/experimental.gjs
Normal file
49
app/components/app-menu/settings/experimental.gjs
Normal file
@@ -0,0 +1,49 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { on } from '@ember/modifier';
|
||||
import { service } from '@ember/service';
|
||||
import { fn } from '@ember/helper';
|
||||
import Icon from '#components/icon';
|
||||
|
||||
export default class AppMenuSettingsExperimental extends Component {
|
||||
@service settings;
|
||||
|
||||
<template>
|
||||
{{! template-lint-disable no-nested-interactive }}
|
||||
<details>
|
||||
<summary>
|
||||
<Icon @name="alert-triangle" @size={{20}} />
|
||||
<span>Experimental</span>
|
||||
</summary>
|
||||
<div class="details-content form-layout">
|
||||
<div class="form-group">
|
||||
<label for="experimental-enable-photo-deletion">Enable photo deletion
|
||||
(own photos)</label>
|
||||
<select
|
||||
id="experimental-enable-photo-deletion"
|
||||
class="form-control"
|
||||
{{on "change" (fn @onChange "experimentalEnablePhotoDeletion")}}
|
||||
>
|
||||
<option
|
||||
value="true"
|
||||
selected={{if
|
||||
this.settings.experimentalEnablePhotoDeletion
|
||||
"selected"
|
||||
}}
|
||||
>
|
||||
On
|
||||
</option>
|
||||
<option
|
||||
value="false"
|
||||
selected={{unless
|
||||
this.settings.experimentalEnablePhotoDeletion
|
||||
"selected"
|
||||
}}
|
||||
>
|
||||
Off
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</template>
|
||||
}
|
||||
@@ -109,32 +109,6 @@ export default class AppMenuSettingsNostr extends Component {
|
||||
<span>Nostr</span>
|
||||
</summary>
|
||||
<div class="details-content form-layout">
|
||||
<div class="form-group">
|
||||
<label for="nostr-photo-fallback-uploads">Upload photos to fallback
|
||||
servers</label>
|
||||
<select
|
||||
id="nostr-photo-fallback-uploads"
|
||||
class="form-control"
|
||||
{{on "change" (fn @onChange "nostrPhotoFallbackUploads")}}
|
||||
>
|
||||
<option
|
||||
value="true"
|
||||
selected={{if this.settings.nostrPhotoFallbackUploads "selected"}}
|
||||
>
|
||||
Yes
|
||||
</option>
|
||||
<option
|
||||
value="false"
|
||||
selected={{unless
|
||||
this.settings.nostrPhotoFallbackUploads
|
||||
"selected"
|
||||
}}
|
||||
>
|
||||
No
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="new-read-relay">Read Relays</label>
|
||||
<ul class="relay-list">
|
||||
@@ -225,6 +199,32 @@ export default class AppMenuSettingsNostr extends Component {
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="nostr-photo-fallback-uploads">Upload photos to fallback
|
||||
servers</label>
|
||||
<select
|
||||
id="nostr-photo-fallback-uploads"
|
||||
class="form-control"
|
||||
{{on "change" (fn @onChange "nostrPhotoFallbackUploads")}}
|
||||
>
|
||||
<option
|
||||
value="true"
|
||||
selected={{if this.settings.nostrPhotoFallbackUploads "selected"}}
|
||||
>
|
||||
Yes
|
||||
</option>
|
||||
<option
|
||||
value="false"
|
||||
selected={{unless
|
||||
this.settings.nostrPhotoFallbackUploads
|
||||
"selected"
|
||||
}}
|
||||
>
|
||||
No
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Cached data</label>
|
||||
<button
|
||||
|
||||
@@ -1,9 +1,39 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
import { on } from '@ember/modifier';
|
||||
import config from 'marco/config/environment';
|
||||
import Icon from './icon';
|
||||
|
||||
const ModalContent = <template>
|
||||
<div class="modal-overlay" role="dialog" tabindex="-1" {{on "click" @close}}>
|
||||
<div
|
||||
class="modal-content"
|
||||
role="document"
|
||||
tabindex="0"
|
||||
{{on "click" @stopProp}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="close-modal-btn btn-text {{if @disableClose 'disabled'}}"
|
||||
disabled={{@disableClose}}
|
||||
{{on "click" @close}}
|
||||
>
|
||||
<Icon @name="x" @size={{24}} @color="currentColor" />
|
||||
</button>
|
||||
{{yield}}
|
||||
</div>
|
||||
</div>
|
||||
</template>;
|
||||
|
||||
export default class Modal extends Component {
|
||||
get isTesting() {
|
||||
return config.environment === 'test';
|
||||
}
|
||||
|
||||
get destinationElement() {
|
||||
return document.getElementById('modal-portal') || document.body;
|
||||
}
|
||||
|
||||
@action
|
||||
stopProp(e) {
|
||||
e.stopPropagation();
|
||||
@@ -18,28 +48,24 @@ export default class Modal extends Component {
|
||||
}
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="modal-overlay"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
{{on "click" this.close}}
|
||||
>
|
||||
<div
|
||||
class="modal-content"
|
||||
role="document"
|
||||
tabindex="0"
|
||||
{{on "click" this.stopProp}}
|
||||
{{#if this.isTesting}}
|
||||
<ModalContent
|
||||
@close={{this.close}}
|
||||
@stopProp={{this.stopProp}}
|
||||
@disableClose={{@disableClose}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="close-modal-btn btn-text {{if @disableClose 'disabled'}}"
|
||||
disabled={{@disableClose}}
|
||||
{{on "click" this.close}}
|
||||
>
|
||||
<Icon @name="x" @size={{24}} @color="currentColor" />
|
||||
</button>
|
||||
{{yield}}
|
||||
</div>
|
||||
</div>
|
||||
</ModalContent>
|
||||
{{else}}
|
||||
{{#in-element this.destinationElement}}
|
||||
<ModalContent
|
||||
@close={{this.close}}
|
||||
@stopProp={{this.stopProp}}
|
||||
@disableClose={{@disableClose}}
|
||||
>
|
||||
{{yield}}
|
||||
</ModalContent>
|
||||
{{/in-element}}
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import Icon from './icon';
|
||||
import fadeInImage from '../modifiers/fade-in-image';
|
||||
import { on } from '@ember/modifier';
|
||||
import { modifier } from 'ember-modifier';
|
||||
import config from 'marco/config/environment';
|
||||
|
||||
export default class PhotoCarousel extends Component {
|
||||
@tracked canScrollLeft = false;
|
||||
@@ -55,6 +56,8 @@ export default class PhotoCarousel extends Component {
|
||||
}
|
||||
});
|
||||
|
||||
isProgrammaticScroll = false;
|
||||
|
||||
scrollToNewPhoto = modifier((element, [eventId]) => {
|
||||
if (eventId && eventId !== this.lastEventId) {
|
||||
const isInitial = !this.lastEventId;
|
||||
@@ -65,6 +68,9 @@ export default class PhotoCarousel extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
this.internalEventId = eventId;
|
||||
this.isProgrammaticScroll = true;
|
||||
|
||||
const scrollAction = () => {
|
||||
const targetSlide = element.querySelector(
|
||||
`[data-event-id="${eventId}"]`
|
||||
@@ -78,11 +84,18 @@ export default class PhotoCarousel extends Component {
|
||||
// Restore smooth scroll after the jump
|
||||
setTimeout(() => {
|
||||
element.style.scrollBehavior = originalScrollBehavior;
|
||||
this.isProgrammaticScroll = false;
|
||||
}, 50);
|
||||
} else {
|
||||
// Use native CSS smooth scrolling for subsequent clicks
|
||||
element.scrollLeft = targetSlide.offsetLeft;
|
||||
// Clear programmatic scroll flag after a delay to let scroll finish
|
||||
setTimeout(() => {
|
||||
this.isProgrammaticScroll = false;
|
||||
}, 500);
|
||||
}
|
||||
} else {
|
||||
this.isProgrammaticScroll = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -111,10 +124,16 @@ export default class PhotoCarousel extends Component {
|
||||
}
|
||||
|
||||
let intersectionObserver;
|
||||
if (this.args.onVisiblePhotoChange && window.IntersectionObserver) {
|
||||
if (
|
||||
this.args.onVisiblePhotoChange &&
|
||||
window.IntersectionObserver &&
|
||||
config.environment !== 'test'
|
||||
) {
|
||||
// Set up intersection observer to track which photo is currently "most" visible
|
||||
intersectionObserver = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (this.isProgrammaticScroll) return;
|
||||
|
||||
for (let entry of entries) {
|
||||
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
|
||||
const eventId = entry.target.dataset.eventId;
|
||||
|
||||
@@ -1,22 +1,97 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { on } from '@ember/modifier';
|
||||
import { modifier } from 'ember-modifier';
|
||||
import { fn } from '@ember/helper';
|
||||
import { service } from '@ember/service';
|
||||
import { modifier } from 'ember-modifier';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { EventFactory } from 'applesauce-core';
|
||||
import Icon from '#components/icon';
|
||||
import { EventFactory } from 'applesauce-factory';
|
||||
import config from 'marco/config/environment';
|
||||
import DropdownMenu from './dropdown-menu';
|
||||
import PhotoCarousel from './photo-carousel';
|
||||
import DropdownMenu from '#components/dropdown-menu';
|
||||
import Icon from './icon';
|
||||
|
||||
const GalleryContent = <template>
|
||||
<div
|
||||
class="photo-gallery-overlay"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
{{on "click" @handleBackgroundClick}}
|
||||
{{@bindKeyboard @handleKeydown}}
|
||||
>
|
||||
{{! template-lint-disable no-invalid-interactive }}
|
||||
<div class="photo-gallery-content">
|
||||
<div class="actions-btn-container">
|
||||
<DropdownMenu
|
||||
@iconSize={{24}}
|
||||
@triggerIcon="more-horizontal"
|
||||
@iconColor="white"
|
||||
as |closeMenu|
|
||||
>
|
||||
<button
|
||||
class="dropdown-item"
|
||||
type="button"
|
||||
{{on "click" (fn @copyEventId closeMenu)}}
|
||||
>Copy Photo Event ID</button>
|
||||
{{#if @canDeletePhoto}}
|
||||
<button
|
||||
class="dropdown-item text-danger"
|
||||
type="button"
|
||||
{{on "click" (fn @deletePhotoTask.perform closeMenu)}}
|
||||
>Delete Photo</button>
|
||||
{{/if}}
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="close-btn btn-text"
|
||||
{{on "click" @handleClose}}
|
||||
aria-label="Close gallery"
|
||||
title="Close"
|
||||
>
|
||||
<Icon @name="x" @size={{24}} @color="white" />
|
||||
</button>
|
||||
|
||||
<div class="main-photo-container">
|
||||
<PhotoCarousel
|
||||
@variant="gallery-main"
|
||||
@photos={{@photos}}
|
||||
@scrollToEventId={{@currentPhoto.eventId}}
|
||||
@onVisiblePhotoChange={{@handleVisiblePhotoChange}}
|
||||
@name={{@placeName}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="thumbnail-strip-container">
|
||||
<PhotoCarousel
|
||||
@variant="gallery-thumbnails"
|
||||
@photos={{@photos}}
|
||||
@scrollToEventId={{@currentPhoto.eventId}}
|
||||
@onPhotoClick={{@selectPhoto}}
|
||||
@name={{@placeName}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>;
|
||||
|
||||
export default class PhotoGallery extends Component {
|
||||
get isTesting() {
|
||||
return config.environment === 'test';
|
||||
}
|
||||
|
||||
get destinationElement() {
|
||||
return document.getElementById('modal-portal') || document.body;
|
||||
}
|
||||
|
||||
@service toast;
|
||||
@service nostrAuth;
|
||||
@service nostrData;
|
||||
@service nostrRelay;
|
||||
@service blossom;
|
||||
@service settings;
|
||||
|
||||
@tracked currentPhoto = this.args.selectedPhoto || this.args.photos?.[0];
|
||||
|
||||
@@ -28,6 +103,12 @@ export default class PhotoGallery extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
get canDeletePhoto() {
|
||||
return (
|
||||
this.isCreator && this.settings.experimentalEnablePhotoDeletion === true
|
||||
);
|
||||
}
|
||||
|
||||
bindKeyboard = modifier((element, [handler]) => {
|
||||
document.addEventListener('keydown', handler);
|
||||
return () => document.removeEventListener('keydown', handler);
|
||||
@@ -174,67 +255,38 @@ export default class PhotoGallery extends Component {
|
||||
});
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="photo-gallery-overlay"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
{{on "click" this.handleBackgroundClick}}
|
||||
{{this.bindKeyboard this.handleKeydown}}
|
||||
>
|
||||
{{! template-lint-disable no-invalid-interactive }}
|
||||
<div class="photo-gallery-content">
|
||||
<div class="actions-btn-container">
|
||||
<DropdownMenu
|
||||
@iconSize={{24}}
|
||||
@triggerIcon="more-horizontal"
|
||||
@iconColor="white"
|
||||
as |closeMenu|
|
||||
>
|
||||
<button
|
||||
class="dropdown-item"
|
||||
type="button"
|
||||
{{on "click" (fn this.copyEventId closeMenu)}}
|
||||
>Copy Photo Event ID</button>
|
||||
{{#if this.isCreator}}
|
||||
<button
|
||||
class="dropdown-item text-danger"
|
||||
type="button"
|
||||
{{on "click" (fn this.deletePhotoTask.perform closeMenu)}}
|
||||
>Delete Photo</button>
|
||||
{{/if}}
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="close-btn btn-text"
|
||||
{{on "click" this.handleClose}}
|
||||
aria-label="Close gallery"
|
||||
title="Close"
|
||||
>
|
||||
<Icon @name="x" @size={{24}} @color="white" />
|
||||
</button>
|
||||
|
||||
<div class="main-photo-container">
|
||||
<PhotoCarousel
|
||||
@variant="gallery-main"
|
||||
@photos={{@photos}}
|
||||
@scrollToEventId={{this.currentPhoto.eventId}}
|
||||
@onVisiblePhotoChange={{this.handleVisiblePhotoChange}}
|
||||
@name={{@placeName}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="thumbnail-strip-container">
|
||||
<PhotoCarousel
|
||||
@variant="gallery-thumbnails"
|
||||
@photos={{@photos}}
|
||||
@scrollToEventId={{this.currentPhoto.eventId}}
|
||||
@onPhotoClick={{this.selectPhoto}}
|
||||
@name={{@placeName}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{#if this.isTesting}}
|
||||
<GalleryContent
|
||||
@handleBackgroundClick={{this.handleBackgroundClick}}
|
||||
@bindKeyboard={{this.bindKeyboard}}
|
||||
@handleKeydown={{this.handleKeydown}}
|
||||
@copyEventId={{this.copyEventId}}
|
||||
@canDeletePhoto={{this.canDeletePhoto}}
|
||||
@deletePhotoTask={{this.deletePhotoTask}}
|
||||
@handleClose={{this.handleClose}}
|
||||
@photos={{@photos}}
|
||||
@currentPhoto={{this.currentPhoto}}
|
||||
@handleVisiblePhotoChange={{this.handleVisiblePhotoChange}}
|
||||
@placeName={{@placeName}}
|
||||
@selectPhoto={{this.selectPhoto}}
|
||||
/>
|
||||
{{else}}
|
||||
{{#in-element this.destinationElement}}
|
||||
<GalleryContent
|
||||
@handleBackgroundClick={{this.handleBackgroundClick}}
|
||||
@bindKeyboard={{this.bindKeyboard}}
|
||||
@handleKeydown={{this.handleKeydown}}
|
||||
@copyEventId={{this.copyEventId}}
|
||||
@canDeletePhoto={{this.canDeletePhoto}}
|
||||
@deletePhotoTask={{this.deletePhotoTask}}
|
||||
@handleClose={{this.handleClose}}
|
||||
@photos={{@photos}}
|
||||
@currentPhoto={{this.currentPhoto}}
|
||||
@handleVisiblePhotoChange={{this.handleVisiblePhotoChange}}
|
||||
@placeName={{@placeName}}
|
||||
@selectPhoto={{this.selectPhoto}}
|
||||
/>
|
||||
{{/in-element}}
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ const DEFAULT_SETTINGS = {
|
||||
nostrPhotoFallbackUploads: false,
|
||||
nostrReadRelays: null,
|
||||
nostrWriteRelays: null,
|
||||
experimentalEnablePhotoDeletion: false,
|
||||
};
|
||||
|
||||
export default class SettingsService extends Service {
|
||||
@@ -20,6 +21,8 @@ export default class SettingsService extends Service {
|
||||
DEFAULT_SETTINGS.nostrPhotoFallbackUploads;
|
||||
@tracked nostrReadRelays = DEFAULT_SETTINGS.nostrReadRelays;
|
||||
@tracked nostrWriteRelays = DEFAULT_SETTINGS.nostrWriteRelays;
|
||||
@tracked experimentalEnablePhotoDeletion =
|
||||
DEFAULT_SETTINGS.experimentalEnablePhotoDeletion;
|
||||
|
||||
overpassApis = [
|
||||
{
|
||||
@@ -108,6 +111,8 @@ export default class SettingsService extends Service {
|
||||
this.nostrPhotoFallbackUploads = finalSettings.nostrPhotoFallbackUploads;
|
||||
this.nostrReadRelays = finalSettings.nostrReadRelays;
|
||||
this.nostrWriteRelays = finalSettings.nostrWriteRelays;
|
||||
this.experimentalEnablePhotoDeletion =
|
||||
finalSettings.experimentalEnablePhotoDeletion;
|
||||
|
||||
// Save to ensure migrated settings are stored in the new format
|
||||
this.saveSettings();
|
||||
@@ -122,6 +127,7 @@ export default class SettingsService extends Service {
|
||||
nostrPhotoFallbackUploads: this.nostrPhotoFallbackUploads,
|
||||
nostrReadRelays: this.nostrReadRelays,
|
||||
nostrWriteRelays: this.nostrWriteRelays,
|
||||
experimentalEnablePhotoDeletion: this.experimentalEnablePhotoDeletion,
|
||||
};
|
||||
localStorage.setItem('marco:settings', JSON.stringify(settings));
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export function getGeohashPrefixesInBbox(bbox) {
|
||||
|
||||
// Safety check to avoid infinite loops or massive arrays if bbox is weird
|
||||
if (Math.abs(maxLat - minLat) > 20 || Math.abs(maxLon - minLon) > 20) {
|
||||
console.warn(
|
||||
console.debug(
|
||||
'BBox too large for 4-char geohash scanning, aborting fine scan.'
|
||||
);
|
||||
return [];
|
||||
|
||||
@@ -6,6 +6,7 @@ import featherCamera from 'feather-icons/dist/icons/camera.svg?raw';
|
||||
import checkSquare from 'feather-icons/dist/icons/check-square.svg?raw';
|
||||
import chevronLeft from 'feather-icons/dist/icons/chevron-left.svg?raw';
|
||||
import chevronRight from 'feather-icons/dist/icons/chevron-right.svg?raw';
|
||||
import alertTriangle from 'feather-icons/dist/icons/alert-triangle.svg?raw';
|
||||
import clock from 'feather-icons/dist/icons/clock.svg?raw';
|
||||
import database from 'feather-icons/dist/icons/database.svg?raw';
|
||||
import edit from 'feather-icons/dist/icons/edit.svg?raw';
|
||||
@@ -146,6 +147,7 @@ const ICONS = {
|
||||
climbing_wall: climbingWall,
|
||||
check,
|
||||
'alert-circle': alertCircle,
|
||||
'alert-triangle': alertTriangle,
|
||||
'classical-building': classicalBuilding,
|
||||
'classical-building-with-dome-and-flag': classicalBuildingWithDomeAndFlag,
|
||||
'classical-building-with-flag': classicalBuildingWithFlag,
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
<link rel="stylesheet" href="/app/styles/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="modal-portal"></div>
|
||||
<script type="module">
|
||||
import Application from './app/app';
|
||||
import environment from './app/config/environment';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "marco",
|
||||
"version": "1.21.2",
|
||||
"version": "1.22.0",
|
||||
"private": true,
|
||||
"description": "Unhosted maps app",
|
||||
"repository": {
|
||||
|
||||
File diff suppressed because one or more lines are too long
25
release/assets/main-CjSZtg4Y.js
Normal file
25
release/assets/main-CjSZtg4Y.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -39,9 +39,10 @@
|
||||
<meta name="msapplication-TileColor" content="#F6E9A6">
|
||||
<meta name="msapplication-TileImage" content="/icons/icon-144.png">
|
||||
|
||||
<script type="module" crossorigin src="/assets/main-CjxGWim8.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-M5C-HUrg.css">
|
||||
<script type="module" crossorigin src="/assets/main-CjSZtg4Y.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-BmLeTC2Y.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="modal-portal"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -38,6 +38,7 @@ class MockOsmService extends Service {
|
||||
}
|
||||
|
||||
class MockStorageService extends Service {
|
||||
initialSyncDone = true;
|
||||
savedPlaces = [];
|
||||
findPlaceById() {
|
||||
return null;
|
||||
|
||||
@@ -29,6 +29,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
||||
this.nostrData = this.owner.lookup('service:nostrData');
|
||||
this.nostrRelay = this.owner.lookup('service:nostrRelay');
|
||||
this.toast = this.owner.lookup('service:toast');
|
||||
this.settings = this.owner.lookup('service:settings');
|
||||
|
||||
this.photos = [
|
||||
{
|
||||
@@ -50,6 +51,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
||||
|
||||
hooks.afterEach(function () {
|
||||
sinon.restore();
|
||||
localStorage.removeItem('marco:settings');
|
||||
});
|
||||
|
||||
test('it does not show delete button if user is not creator', async function (assert) {
|
||||
@@ -59,6 +61,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
||||
await render(
|
||||
<template>
|
||||
<div id="test-container">
|
||||
<div id="modal-portal"></div>
|
||||
<PhotoGallery
|
||||
@photos={{this.photos}}
|
||||
@selectedPhoto={{this.selectedPhoto}}
|
||||
@@ -76,13 +79,15 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
||||
.doesNotExist('Delete button is hidden for non-creator');
|
||||
});
|
||||
|
||||
test('it shows delete button if user is creator', async function (assert) {
|
||||
test('it shows delete button if user is creator and setting is enabled', async function (assert) {
|
||||
this.nostrAuth.pubkey = 'userA'; // Matches photo1's pubkey
|
||||
this.settings.update('experimentalEnablePhotoDeletion', true); // Enable the setting
|
||||
this.selectedPhoto = this.photos[0];
|
||||
|
||||
await render(
|
||||
<template>
|
||||
<div id="test-container">
|
||||
<div id="modal-portal"></div>
|
||||
<PhotoGallery
|
||||
@photos={{this.photos}}
|
||||
@selectedPhoto={{this.selectedPhoto}}
|
||||
@@ -97,12 +102,12 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
||||
assert.dom('.dropdown-popover').exists('Dropdown opened');
|
||||
assert
|
||||
.dom('.dropdown-item.text-danger')
|
||||
.exists('Delete button is visible for creator');
|
||||
assert.dom('.dropdown-item.text-danger').hasText('Delete Photo');
|
||||
.exists('Delete button is visible for creator when setting is enabled');
|
||||
});
|
||||
|
||||
test('it handles cancellation of deletion', async function (assert) {
|
||||
this.nostrAuth.pubkey = 'userA';
|
||||
this.settings.update('experimentalEnablePhotoDeletion', true);
|
||||
this.selectedPhoto = this.photos[0];
|
||||
|
||||
const confirmStub = sinon.stub(window, 'confirm').returns(false);
|
||||
@@ -111,6 +116,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
||||
await render(
|
||||
<template>
|
||||
<div id="test-container">
|
||||
<div id="modal-portal"></div>
|
||||
<PhotoGallery
|
||||
@photos={{this.photos}}
|
||||
@selectedPhoto={{this.selectedPhoto}}
|
||||
@@ -128,6 +134,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
||||
|
||||
test('it performs full deletion flow when confirmed', async function (assert) {
|
||||
this.nostrAuth.pubkey = 'userA';
|
||||
this.settings.update('experimentalEnablePhotoDeletion', true);
|
||||
// Override the mock's getter just for this test
|
||||
Object.defineProperty(this.nostrAuth, 'signer', {
|
||||
configurable: true,
|
||||
@@ -157,6 +164,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
||||
await render(
|
||||
<template>
|
||||
<div id="test-container">
|
||||
<div id="modal-portal"></div>
|
||||
<PhotoGallery
|
||||
@photos={{this.photos}}
|
||||
@selectedPhoto={{this.selectedPhoto}}
|
||||
@@ -230,6 +238,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
||||
await render(
|
||||
<template>
|
||||
<div id="test-container">
|
||||
<div id="modal-portal"></div>
|
||||
<PhotoGallery
|
||||
@photos={{this.photos}}
|
||||
@selectedPhoto={{this.selectedPhoto}}
|
||||
@@ -269,6 +278,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
||||
await render(
|
||||
<template>
|
||||
<div id="test-container">
|
||||
<div id="modal-portal"></div>
|
||||
<PhotoGallery
|
||||
@photos={{this.photos}}
|
||||
@selectedPhoto={{this.selectedPhoto}}
|
||||
@@ -278,9 +288,11 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
||||
);
|
||||
|
||||
// Let carousel settle
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
||||
// Right Arrow
|
||||
await triggerKeyEvent(document, 'keydown', 'ArrowRight');
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
||||
// Let's just assert that currentPhoto was updated internally, which trickles down.
|
||||
// The actual DOM update for the main image might be tricky if the carousel relies on scroll events.
|
||||
@@ -291,6 +303,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
||||
|
||||
// Right Arrow again
|
||||
await triggerKeyEvent(document, 'keydown', 'ArrowRight');
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
||||
assert
|
||||
.dom('.thumbnail-strip-container .carousel-slide.active img')
|
||||
@@ -298,6 +311,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
||||
|
||||
// Left Arrow
|
||||
await triggerKeyEvent(document, 'keydown', 'ArrowLeft');
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
||||
assert
|
||||
.dom('.thumbnail-strip-container .carousel-slide.active img')
|
||||
@@ -314,6 +328,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
||||
await render(
|
||||
<template>
|
||||
<div id="test-container">
|
||||
<div id="modal-portal"></div>
|
||||
<PhotoGallery
|
||||
@photos={{this.photos}}
|
||||
@selectedPhoto={{this.selectedPhoto}}
|
||||
|
||||
Reference in New Issue
Block a user