Add settings for experimental features
This commit is contained in:
@@ -6,6 +6,7 @@ import Icon from '#components/icon';
|
|||||||
import AppMenuSettingsMapUi from './settings/map-ui';
|
import AppMenuSettingsMapUi from './settings/map-ui';
|
||||||
import AppMenuSettingsApis from './settings/apis';
|
import AppMenuSettingsApis from './settings/apis';
|
||||||
import AppMenuSettingsNostr from './settings/nostr';
|
import AppMenuSettingsNostr from './settings/nostr';
|
||||||
|
import AppMenuSettingsExperimental from './settings/experimental';
|
||||||
|
|
||||||
export default class AppMenuSettings extends Component {
|
export default class AppMenuSettings extends Component {
|
||||||
@service settings;
|
@service settings;
|
||||||
@@ -35,6 +36,7 @@ export default class AppMenuSettings extends Component {
|
|||||||
<AppMenuSettingsMapUi @onChange={{this.updateSetting}} />
|
<AppMenuSettingsMapUi @onChange={{this.updateSetting}} />
|
||||||
<AppMenuSettingsApis @onChange={{this.updateSetting}} />
|
<AppMenuSettingsApis @onChange={{this.updateSetting}} />
|
||||||
<AppMenuSettingsNostr @onChange={{this.updateSetting}} />
|
<AppMenuSettingsNostr @onChange={{this.updateSetting}} />
|
||||||
|
<AppMenuSettingsExperimental @onChange={{this.updateSetting}} />
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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>
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@ const GalleryContent = <template>
|
|||||||
type="button"
|
type="button"
|
||||||
{{on "click" (fn @copyEventId closeMenu)}}
|
{{on "click" (fn @copyEventId closeMenu)}}
|
||||||
>Copy Photo Event ID</button>
|
>Copy Photo Event ID</button>
|
||||||
{{#if @isCreator}}
|
{{#if @canDeletePhoto}}
|
||||||
<button
|
<button
|
||||||
class="dropdown-item text-danger"
|
class="dropdown-item text-danger"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -91,6 +91,7 @@ export default class PhotoGallery extends Component {
|
|||||||
@service nostrData;
|
@service nostrData;
|
||||||
@service nostrRelay;
|
@service nostrRelay;
|
||||||
@service blossom;
|
@service blossom;
|
||||||
|
@service settings;
|
||||||
|
|
||||||
@tracked currentPhoto = this.args.selectedPhoto || this.args.photos?.[0];
|
@tracked currentPhoto = this.args.selectedPhoto || this.args.photos?.[0];
|
||||||
|
|
||||||
@@ -102,6 +103,12 @@ export default class PhotoGallery extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get canDeletePhoto() {
|
||||||
|
return (
|
||||||
|
this.isCreator && this.settings.experimentalEnablePhotoDeletion === true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bindKeyboard = modifier((element, [handler]) => {
|
bindKeyboard = modifier((element, [handler]) => {
|
||||||
document.addEventListener('keydown', handler);
|
document.addEventListener('keydown', handler);
|
||||||
return () => document.removeEventListener('keydown', handler);
|
return () => document.removeEventListener('keydown', handler);
|
||||||
@@ -254,7 +261,7 @@ export default class PhotoGallery extends Component {
|
|||||||
@bindKeyboard={{this.bindKeyboard}}
|
@bindKeyboard={{this.bindKeyboard}}
|
||||||
@handleKeydown={{this.handleKeydown}}
|
@handleKeydown={{this.handleKeydown}}
|
||||||
@copyEventId={{this.copyEventId}}
|
@copyEventId={{this.copyEventId}}
|
||||||
@isCreator={{this.isCreator}}
|
@canDeletePhoto={{this.canDeletePhoto}}
|
||||||
@deletePhotoTask={{this.deletePhotoTask}}
|
@deletePhotoTask={{this.deletePhotoTask}}
|
||||||
@handleClose={{this.handleClose}}
|
@handleClose={{this.handleClose}}
|
||||||
@photos={{@photos}}
|
@photos={{@photos}}
|
||||||
@@ -270,7 +277,7 @@ export default class PhotoGallery extends Component {
|
|||||||
@bindKeyboard={{this.bindKeyboard}}
|
@bindKeyboard={{this.bindKeyboard}}
|
||||||
@handleKeydown={{this.handleKeydown}}
|
@handleKeydown={{this.handleKeydown}}
|
||||||
@copyEventId={{this.copyEventId}}
|
@copyEventId={{this.copyEventId}}
|
||||||
@isCreator={{this.isCreator}}
|
@canDeletePhoto={{this.canDeletePhoto}}
|
||||||
@deletePhotoTask={{this.deletePhotoTask}}
|
@deletePhotoTask={{this.deletePhotoTask}}
|
||||||
@handleClose={{this.handleClose}}
|
@handleClose={{this.handleClose}}
|
||||||
@photos={{@photos}}
|
@photos={{@photos}}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const DEFAULT_SETTINGS = {
|
|||||||
nostrPhotoFallbackUploads: false,
|
nostrPhotoFallbackUploads: false,
|
||||||
nostrReadRelays: null,
|
nostrReadRelays: null,
|
||||||
nostrWriteRelays: null,
|
nostrWriteRelays: null,
|
||||||
|
experimentalEnablePhotoDeletion: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class SettingsService extends Service {
|
export default class SettingsService extends Service {
|
||||||
@@ -20,6 +21,8 @@ export default class SettingsService extends Service {
|
|||||||
DEFAULT_SETTINGS.nostrPhotoFallbackUploads;
|
DEFAULT_SETTINGS.nostrPhotoFallbackUploads;
|
||||||
@tracked nostrReadRelays = DEFAULT_SETTINGS.nostrReadRelays;
|
@tracked nostrReadRelays = DEFAULT_SETTINGS.nostrReadRelays;
|
||||||
@tracked nostrWriteRelays = DEFAULT_SETTINGS.nostrWriteRelays;
|
@tracked nostrWriteRelays = DEFAULT_SETTINGS.nostrWriteRelays;
|
||||||
|
@tracked experimentalEnablePhotoDeletion =
|
||||||
|
DEFAULT_SETTINGS.experimentalEnablePhotoDeletion;
|
||||||
|
|
||||||
overpassApis = [
|
overpassApis = [
|
||||||
{
|
{
|
||||||
@@ -108,6 +111,8 @@ export default class SettingsService extends Service {
|
|||||||
this.nostrPhotoFallbackUploads = finalSettings.nostrPhotoFallbackUploads;
|
this.nostrPhotoFallbackUploads = finalSettings.nostrPhotoFallbackUploads;
|
||||||
this.nostrReadRelays = finalSettings.nostrReadRelays;
|
this.nostrReadRelays = finalSettings.nostrReadRelays;
|
||||||
this.nostrWriteRelays = finalSettings.nostrWriteRelays;
|
this.nostrWriteRelays = finalSettings.nostrWriteRelays;
|
||||||
|
this.experimentalEnablePhotoDeletion =
|
||||||
|
finalSettings.experimentalEnablePhotoDeletion;
|
||||||
|
|
||||||
// Save to ensure migrated settings are stored in the new format
|
// Save to ensure migrated settings are stored in the new format
|
||||||
this.saveSettings();
|
this.saveSettings();
|
||||||
@@ -122,6 +127,7 @@ export default class SettingsService extends Service {
|
|||||||
nostrPhotoFallbackUploads: this.nostrPhotoFallbackUploads,
|
nostrPhotoFallbackUploads: this.nostrPhotoFallbackUploads,
|
||||||
nostrReadRelays: this.nostrReadRelays,
|
nostrReadRelays: this.nostrReadRelays,
|
||||||
nostrWriteRelays: this.nostrWriteRelays,
|
nostrWriteRelays: this.nostrWriteRelays,
|
||||||
|
experimentalEnablePhotoDeletion: this.experimentalEnablePhotoDeletion,
|
||||||
};
|
};
|
||||||
localStorage.setItem('marco:settings', JSON.stringify(settings));
|
localStorage.setItem('marco:settings', JSON.stringify(settings));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 checkSquare from 'feather-icons/dist/icons/check-square.svg?raw';
|
||||||
import chevronLeft from 'feather-icons/dist/icons/chevron-left.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 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 clock from 'feather-icons/dist/icons/clock.svg?raw';
|
||||||
import database from 'feather-icons/dist/icons/database.svg?raw';
|
import database from 'feather-icons/dist/icons/database.svg?raw';
|
||||||
import edit from 'feather-icons/dist/icons/edit.svg?raw';
|
import edit from 'feather-icons/dist/icons/edit.svg?raw';
|
||||||
@@ -146,6 +147,7 @@ const ICONS = {
|
|||||||
climbing_wall: climbingWall,
|
climbing_wall: climbingWall,
|
||||||
check,
|
check,
|
||||||
'alert-circle': alertCircle,
|
'alert-circle': alertCircle,
|
||||||
|
'alert-triangle': alertTriangle,
|
||||||
'classical-building': classicalBuilding,
|
'classical-building': classicalBuilding,
|
||||||
'classical-building-with-dome-and-flag': classicalBuildingWithDomeAndFlag,
|
'classical-building-with-dome-and-flag': classicalBuildingWithDomeAndFlag,
|
||||||
'classical-building-with-flag': classicalBuildingWithFlag,
|
'classical-building-with-flag': classicalBuildingWithFlag,
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
|||||||
this.nostrData = this.owner.lookup('service:nostrData');
|
this.nostrData = this.owner.lookup('service:nostrData');
|
||||||
this.nostrRelay = this.owner.lookup('service:nostrRelay');
|
this.nostrRelay = this.owner.lookup('service:nostrRelay');
|
||||||
this.toast = this.owner.lookup('service:toast');
|
this.toast = this.owner.lookup('service:toast');
|
||||||
|
this.settings = this.owner.lookup('service:settings');
|
||||||
|
|
||||||
this.photos = [
|
this.photos = [
|
||||||
{
|
{
|
||||||
@@ -50,6 +51,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
|||||||
|
|
||||||
hooks.afterEach(function () {
|
hooks.afterEach(function () {
|
||||||
sinon.restore();
|
sinon.restore();
|
||||||
|
localStorage.removeItem('marco:settings');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it does not show delete button if user is not creator', async function (assert) {
|
test('it does not show delete button if user is not creator', async function (assert) {
|
||||||
@@ -77,8 +79,9 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
|||||||
.doesNotExist('Delete button is hidden for non-creator');
|
.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.nostrAuth.pubkey = 'userA'; // Matches photo1's pubkey
|
||||||
|
this.settings.update('experimentalEnablePhotoDeletion', true); // Enable the setting
|
||||||
this.selectedPhoto = this.photos[0];
|
this.selectedPhoto = this.photos[0];
|
||||||
|
|
||||||
await render(
|
await render(
|
||||||
@@ -99,12 +102,12 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
|||||||
assert.dom('.dropdown-popover').exists('Dropdown opened');
|
assert.dom('.dropdown-popover').exists('Dropdown opened');
|
||||||
assert
|
assert
|
||||||
.dom('.dropdown-item.text-danger')
|
.dom('.dropdown-item.text-danger')
|
||||||
.exists('Delete button is visible for creator');
|
.exists('Delete button is visible for creator when setting is enabled');
|
||||||
assert.dom('.dropdown-item.text-danger').hasText('Delete Photo');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it handles cancellation of deletion', async function (assert) {
|
test('it handles cancellation of deletion', async function (assert) {
|
||||||
this.nostrAuth.pubkey = 'userA';
|
this.nostrAuth.pubkey = 'userA';
|
||||||
|
this.settings.update('experimentalEnablePhotoDeletion', true);
|
||||||
this.selectedPhoto = this.photos[0];
|
this.selectedPhoto = this.photos[0];
|
||||||
|
|
||||||
const confirmStub = sinon.stub(window, 'confirm').returns(false);
|
const confirmStub = sinon.stub(window, 'confirm').returns(false);
|
||||||
@@ -131,6 +134,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
|||||||
|
|
||||||
test('it performs full deletion flow when confirmed', async function (assert) {
|
test('it performs full deletion flow when confirmed', async function (assert) {
|
||||||
this.nostrAuth.pubkey = 'userA';
|
this.nostrAuth.pubkey = 'userA';
|
||||||
|
this.settings.update('experimentalEnablePhotoDeletion', true);
|
||||||
// Override the mock's getter just for this test
|
// Override the mock's getter just for this test
|
||||||
Object.defineProperty(this.nostrAuth, 'signer', {
|
Object.defineProperty(this.nostrAuth, 'signer', {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user