Compare commits

...
3 Commits
Author SHA1 Message Date
raucao 46bb2efc70 1.30.0
CI / Lint (push) Successful in 40s
CI / Test (push) Successful in 54s
2026-08-21 11:35:31 -06:00
raucao 6feb776247 Merge pull request 'Add media server selection to Nostr settings' (#82) from feature/blossom_settings into master
CI / Lint (push) Successful in 1m1s
CI / Test (push) Successful in 1m14s
Reviewed-on: #82
2026-08-21 17:34:18 +00:00
raucao 2746ec035d Add media server selection to Nostr settings
CI / Lint (pull_request) Successful in 1m3s
CI / Test (pull_request) Successful in 1m16s
Release Drafter / Update release notes draft (pull_request) Successful in 5s
Also, hide the fallback option when there's no server to fall back to
2026-08-21 11:27:03 -06:00
11 changed files with 115 additions and 28 deletions
+56 -19
View File
@@ -4,12 +4,14 @@ import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { service } from '@ember/service';
import { fn } from '@ember/helper';
import eq from 'ember-truth-helpers/helpers/eq';
import Icon from '#components/icon';
import {
excludeRequiredRelays,
mergeRequiredRelays,
normalizeRelayUrl,
} from '../../../utils/nostr';
import { DEFAULT_BLOSSOM_SERVER } from '../../../services/blossom';
const stripProtocol = (url) => (url ? url.replace(/^wss?:\/\//, '') : '');
@@ -89,6 +91,15 @@ export default class AppMenuSettingsNostr extends Component {
});
}
get mediaServerOptions() {
const servers = this.nostrData.blossomServers;
return servers.length ? servers : [DEFAULT_BLOSSOM_SERVER];
}
get hasMultipleMediaServers() {
return this.mediaServerOptions.length > 1;
}
@action
updateNewReadRelay(event) {
this.newReadRelay = event.target.value;
@@ -342,31 +353,57 @@ export default class AppMenuSettingsNostr extends Component {
</div>
<div class="form-group">
<label for="nostr-photo-fallback-uploads">Upload photos to fallback
servers</label>
<label for="nostr-media-server">Media server</label>
<select
id="nostr-photo-fallback-uploads"
id="nostr-media-server"
class="form-control"
{{on "change" (fn @onChange "nostrPhotoFallbackUploads")}}
{{on "change" (fn @onChange "nostrMediaServer")}}
>
<option
value="true"
selected={{if this.settings.nostrPhotoFallbackUploads "selected"}}
>
Yes
</option>
<option
value="false"
selected={{unless
this.settings.nostrPhotoFallbackUploads
"selected"
}}
>
No
</option>
{{#each this.mediaServerOptions as |server|}}
<option
value={{server}}
selected={{if
(eq server this.settings.nostrMediaServer)
"selected"
}}
>
{{server}}
</option>
{{/each}}
</select>
</div>
{{#if this.hasMultipleMediaServers}}
<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>
{{/if}}
<div class="form-group">
<label>Cached data</label>
<button
+5 -4
View File
@@ -24,14 +24,15 @@ export default class BlossomService extends Service {
@service settings;
get servers() {
const servers = this.nostrData.blossomServers;
const allServers = servers.length ? servers : [DEFAULT_BLOSSOM_SERVER];
const discovered = this.nostrData.blossomServers;
const mainServer = this.settings.nostrMediaServer || DEFAULT_BLOSSOM_SERVER;
const fallbacks = discovered.filter((server) => server !== mainServer);
if (!this.settings.nostrPhotoFallbackUploads) {
return [allServers[0]];
return [mainServer];
}
return allServers;
return [mainServer, ...fallbacks];
}
async _getAuthHeader(action, hash, serverUrl) {
+11
View File
@@ -15,6 +15,7 @@ import {
uniqNormalizedRelays,
} from '../utils/nostr';
import { getGeohashPrefixesInBbox } from '../utils/geohash-coverage';
import { DEFAULT_BLOSSOM_SERVER } from './blossom';
const DIRECTORY_RELAYS = [
'wss://relay.primal.net',
@@ -437,6 +438,16 @@ export default class NostrDataService extends Service {
} else {
this.blossomServers = [];
}
if (this.blossomServers.length > 0) {
const current = this.settings.nostrMediaServer;
if (
current === DEFAULT_BLOSSOM_SERVER ||
!this.blossomServers.includes(current)
) {
this.settings.update('nostrMediaServer', this.blossomServers[0]);
}
}
});
// 1. Await cache initialization and populate the EventStore with local data
+6
View File
@@ -1,12 +1,15 @@
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { DEFAULT_BLOSSOM_SERVER } from './blossom';
const DEFAULT_SETTINGS = {
overpassApi: 'https://overpass-api.de/api/interpreter',
mapKinetic: true,
photonApi: 'https://photon.komoot.io/api/',
showQuickSearchButtons: true,
nostrPhotoFallbackUploads: false,
nostrMediaServer: DEFAULT_BLOSSOM_SERVER,
nostrReadRelays: null,
nostrWriteRelays: null,
nostrReadRelayExclusions: null,
@@ -21,6 +24,7 @@ export default class SettingsService extends Service {
@tracked showQuickSearchButtons = DEFAULT_SETTINGS.showQuickSearchButtons;
@tracked nostrPhotoFallbackUploads =
DEFAULT_SETTINGS.nostrPhotoFallbackUploads;
@tracked nostrMediaServer = DEFAULT_SETTINGS.nostrMediaServer;
@tracked nostrReadRelays = DEFAULT_SETTINGS.nostrReadRelays;
@tracked nostrWriteRelays = DEFAULT_SETTINGS.nostrWriteRelays;
@tracked nostrReadRelayExclusions = DEFAULT_SETTINGS.nostrReadRelayExclusions;
@@ -114,6 +118,7 @@ export default class SettingsService extends Service {
this.photonApi = finalSettings.photonApi;
this.showQuickSearchButtons = finalSettings.showQuickSearchButtons;
this.nostrPhotoFallbackUploads = finalSettings.nostrPhotoFallbackUploads;
this.nostrMediaServer = finalSettings.nostrMediaServer;
this.nostrReadRelays = finalSettings.nostrReadRelays;
this.nostrWriteRelays = finalSettings.nostrWriteRelays;
this.nostrReadRelayExclusions = finalSettings.nostrReadRelayExclusions;
@@ -132,6 +137,7 @@ export default class SettingsService extends Service {
photonApi: this.photonApi,
showQuickSearchButtons: this.showQuickSearchButtons,
nostrPhotoFallbackUploads: this.nostrPhotoFallbackUploads,
nostrMediaServer: this.nostrMediaServer,
nostrReadRelays: this.nostrReadRelays,
nostrWriteRelays: this.nostrWriteRelays,
nostrReadRelayExclusions: this.nostrReadRelayExclusions,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "marco",
"version": "1.29.1",
"version": "1.30.0",
"private": true,
"description": "Unhosted maps app",
"repository": {
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
+1 -1
View File
@@ -39,7 +39,7 @@
<meta name="msapplication-TileColor" content="#F6E9A6">
<meta name="msapplication-TileImage" content="/icons/icon-144.png">
<script type="module" crossorigin src="/assets/main-D_udYDYo.js"></script>
<script type="module" crossorigin src="/assets/main-Dw0KrEfS.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-DWxZH-9E.css">
</head>
<body>
@@ -8,6 +8,7 @@ import {
mergeRequiredRelays,
uniqNormalizedRelays,
} from 'marco/utils/nostr';
import { DEFAULT_BLOSSOM_SERVER } from 'marco/services/blossom';
class MockNostrDataService extends Service {
@service settings;
@@ -18,6 +19,8 @@ class MockNostrDataService extends Service {
mailboxReadRelays = ['wss://mailbox.example.com'];
mailboxWriteRelays = ['wss://mailbox-write.example.com'];
blossomServers = [];
get configuredReadRelays() {
const configured = uniqNormalizedRelays([
...this.mailboxReadRelays,
@@ -185,4 +188,33 @@ module('Integration | Component | app-menu/settings/nostr', function (hooks) {
'wss://mailbox-write.example.com',
]);
});
test('media server dropdown shows only the default server when no blossom servers discovered', async function (assert) {
const element = await renderAndOpenDetails(this);
const select = element.querySelector('#nostr-media-server');
assert.dom(select).exists();
assert.strictEqual(select.options.length, 1);
assert.dom(select.options[0]).hasValue(DEFAULT_BLOSSOM_SERVER);
assert.dom(select.options[0]).hasText(DEFAULT_BLOSSOM_SERVER);
assert.strictEqual(select.value, DEFAULT_BLOSSOM_SERVER);
assert
.dom('#nostr-photo-fallback-uploads')
.doesNotExist('fallback toggle hidden with single media server');
});
test('media server dropdown lists discovered blossom servers', async function (assert) {
const servers = ['https://cdn.example.com', 'https://cdn.satellite.earth'];
this.owner.lookup('service:nostrData').blossomServers = servers;
const element = await renderAndOpenDetails(this);
const select = element.querySelector('#nostr-media-server');
assert.strictEqual(select.options.length, servers.length);
assert.dom(select.options[0]).hasValue(servers[0]);
assert.dom(select.options[1]).hasValue(servers[1]);
assert
.dom('#nostr-photo-fallback-uploads')
.exists('fallback toggle visible with multiple media servers');
});
});