Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
387fd6efed
|
||
|
|
aaf0d8c3e9
|
||
|
|
9807a2b822
|
||
|
|
ac9870240f
|
||
|
|
1b185d1033
|
||
|
|
4d4ed8c1ea
|
||
|
|
1613ebf0bf
|
||
|
|
4cb8c94c9c
|
||
|
|
1f87c802e1
|
||
|
|
a6576fcda8
|
||
|
|
1822c4893f
|
||
|
|
39dca446ac
|
||
|
|
2e6827f0d0
|
||
|
|
8c3a805684
|
@@ -237,7 +237,7 @@ export default class PhotoCarousel extends Component {
|
|||||||
data-src={{photo.url}}
|
data-src={{photo.url}}
|
||||||
class="place-header-photo
|
class="place-header-photo
|
||||||
{{if photo.isLandscape 'landscape' 'portrait'}}"
|
{{if photo.isLandscape 'landscape' 'portrait'}}"
|
||||||
alt={{@name}}
|
alt={{photo.alt}}
|
||||||
{{fadeInImage photo.url}}
|
{{fadeInImage photo.url}}
|
||||||
/>
|
/>
|
||||||
{{else if this.isGalleryThumbnails}}
|
{{else if this.isGalleryThumbnails}}
|
||||||
@@ -245,7 +245,7 @@ export default class PhotoCarousel extends Component {
|
|||||||
data-src={{if photo.thumbUrl photo.thumbUrl photo.url}}
|
data-src={{if photo.thumbUrl photo.thumbUrl photo.url}}
|
||||||
class="place-header-photo
|
class="place-header-photo
|
||||||
{{if photo.isLandscape 'landscape' 'portrait'}}"
|
{{if photo.isLandscape 'landscape' 'portrait'}}"
|
||||||
alt={{@name}}
|
alt={{photo.alt}}
|
||||||
{{fadeInImage (if photo.thumbUrl photo.thumbUrl photo.url)}}
|
{{fadeInImage (if photo.thumbUrl photo.thumbUrl photo.url)}}
|
||||||
/>
|
/>
|
||||||
{{else}}
|
{{else}}
|
||||||
@@ -260,7 +260,7 @@ export default class PhotoCarousel extends Component {
|
|||||||
<img
|
<img
|
||||||
data-src={{photo.url}}
|
data-src={{photo.url}}
|
||||||
class="place-header-photo landscape"
|
class="place-header-photo landscape"
|
||||||
alt={{@name}}
|
alt={{photo.alt}}
|
||||||
{{fadeInImage photo.url}}
|
{{fadeInImage photo.url}}
|
||||||
/>
|
/>
|
||||||
</picture>
|
</picture>
|
||||||
@@ -269,7 +269,7 @@ export default class PhotoCarousel extends Component {
|
|||||||
<img
|
<img
|
||||||
data-src={{if photo.thumbUrl photo.thumbUrl photo.url}}
|
data-src={{if photo.thumbUrl photo.thumbUrl photo.url}}
|
||||||
class="place-header-photo portrait"
|
class="place-header-photo portrait"
|
||||||
alt={{@name}}
|
alt={{photo.alt}}
|
||||||
{{fadeInImage (if photo.thumbUrl photo.thumbUrl photo.url)}}
|
{{fadeInImage (if photo.thumbUrl photo.thumbUrl photo.url)}}
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { fn } from '@ember/helper';
|
|||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { modifier } from 'ember-modifier';
|
import { modifier } from 'ember-modifier';
|
||||||
import { task } from 'ember-concurrency';
|
import { task } from 'ember-concurrency';
|
||||||
import { EventFactory } from 'applesauce-factory';
|
import { EventFactory } from 'applesauce-core';
|
||||||
import config from 'marco/config/environment';
|
import config from 'marco/config/environment';
|
||||||
import DropdownMenu from './dropdown-menu';
|
import DropdownMenu from './dropdown-menu';
|
||||||
import PhotoCarousel from './photo-carousel';
|
import PhotoCarousel from './photo-carousel';
|
||||||
@@ -201,21 +201,17 @@ export default class PhotoGallery extends Component {
|
|||||||
const eventId = this.currentPhoto.eventId;
|
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
|
// Publish Nostr kind: 5 deletion event first so we don't end up with dead blossom links on a failure
|
||||||
const factory = new EventFactory({ signer: this.nostrAuth.signer });
|
|
||||||
const tags = [['e', eventId]];
|
const tags = [['e', eventId]];
|
||||||
|
|
||||||
if (this.currentPhoto.placeIdentifier) {
|
if (this.currentPhoto.placeIdentifier) {
|
||||||
tags.push(['i', this.currentPhoto.placeIdentifier]);
|
tags.push(['i', this.currentPhoto.placeIdentifier]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const template = {
|
const event = await EventFactory.fromKind(5)
|
||||||
kind: 5,
|
.content('Deleted photo')
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
.modifyPublicTags(() => tags)
|
||||||
content: 'Deleted photo',
|
.as(this.nostrAuth.signer)
|
||||||
tags,
|
.sign();
|
||||||
};
|
|
||||||
|
|
||||||
const event = await factory.sign(template);
|
|
||||||
await this.nostrRelay.publish(this.nostrData.activeWriteRelays, event);
|
await this.nostrRelay.publish(this.nostrData.activeWriteRelays, event);
|
||||||
|
|
||||||
// Remove from local store by adding the kind 5 to it
|
// Remove from local store by adding the kind 5 to it
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export default class PlacePhotoUpload extends Component {
|
|||||||
@tracked isPublishing = false;
|
@tracked isPublishing = false;
|
||||||
@tracked isDragging = false;
|
@tracked isDragging = false;
|
||||||
@tracked selectedTags = [];
|
@tracked selectedTags = [];
|
||||||
|
@tracked altText = '';
|
||||||
|
|
||||||
get place() {
|
get place() {
|
||||||
return this.args.place || {};
|
return this.args.place || {};
|
||||||
@@ -103,6 +104,7 @@ export default class PlacePhotoUpload extends Component {
|
|||||||
this.file = null;
|
this.file = null;
|
||||||
this.uploadedPhoto = null;
|
this.uploadedPhoto = null;
|
||||||
this.selectedTags = [];
|
this.selectedTags = [];
|
||||||
|
this.altText = '';
|
||||||
if (this.args.onUploadStateChange) {
|
if (this.args.onUploadStateChange) {
|
||||||
this.args.onUploadStateChange(false);
|
this.args.onUploadStateChange(false);
|
||||||
}
|
}
|
||||||
@@ -118,6 +120,11 @@ export default class PlacePhotoUpload extends Component {
|
|||||||
this.selectedTags = [tag];
|
this.selectedTags = [tag];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
updateAltText(event) {
|
||||||
|
this.altText = event.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
deletePhotoTask = task(async (photoData) => {
|
deletePhotoTask = task(async (photoData) => {
|
||||||
try {
|
try {
|
||||||
if (photoData.hash) {
|
if (photoData.hash) {
|
||||||
@@ -155,8 +162,6 @@ export default class PlacePhotoUpload extends Component {
|
|||||||
this.isPublishing = true;
|
this.isPublishing = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const factory = new EventFactory({ signer: this.nostrAuth.signer });
|
|
||||||
|
|
||||||
const tags = [['i', `osm:${osmType}:${osmId}`]];
|
const tags = [['i', `osm:${osmType}:${osmId}`]];
|
||||||
|
|
||||||
for (const tag of this.selectedTags) {
|
for (const tag of this.selectedTags) {
|
||||||
@@ -179,7 +184,10 @@ export default class PlacePhotoUpload extends Component {
|
|||||||
imeta.push(`dim ${photo.dim}`);
|
imeta.push(`dim ${photo.dim}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
imeta.push('alt A photo of a place');
|
const alt = this.altText.trim();
|
||||||
|
if (alt) {
|
||||||
|
imeta.push(`alt ${alt}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (photo.fallbackUrls && photo.fallbackUrls.length > 0) {
|
if (photo.fallbackUrls && photo.fallbackUrls.length > 0) {
|
||||||
for (const fallbackUrl of photo.fallbackUrls) {
|
for (const fallbackUrl of photo.fallbackUrls) {
|
||||||
@@ -198,17 +206,11 @@ export default class PlacePhotoUpload extends Component {
|
|||||||
tags.push(imeta);
|
tags.push(imeta);
|
||||||
|
|
||||||
// NIP-XX draft Place Photo event
|
// NIP-XX draft Place Photo event
|
||||||
const template = {
|
const event = await EventFactory.fromKind(360)
|
||||||
kind: 360,
|
.content('')
|
||||||
content: '',
|
.modifyPublicTags(() => tags)
|
||||||
tags,
|
.as(this.nostrAuth.signer)
|
||||||
};
|
.sign();
|
||||||
|
|
||||||
if (!template.created_at) {
|
|
||||||
template.created_at = Math.floor(Date.now() / 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
const event = await factory.sign(template);
|
|
||||||
await this.nostrRelay.publish(this.nostrData.activeWriteRelays, event);
|
await this.nostrRelay.publish(this.nostrData.activeWriteRelays, event);
|
||||||
this.nostrData.store.add(event);
|
this.nostrData.store.add(event);
|
||||||
|
|
||||||
@@ -217,6 +219,7 @@ export default class PlacePhotoUpload extends Component {
|
|||||||
// Clear out the file so user can upload more or be done
|
// Clear out the file so user can upload more or be done
|
||||||
this.file = null;
|
this.file = null;
|
||||||
this.uploadedPhoto = null;
|
this.uploadedPhoto = null;
|
||||||
|
this.altText = '';
|
||||||
|
|
||||||
if (this.args.onUploadStateChange) {
|
if (this.args.onUploadStateChange) {
|
||||||
this.args.onUploadStateChange(false);
|
this.args.onUploadStateChange(false);
|
||||||
@@ -243,13 +246,11 @@ export default class PlacePhotoUpload extends Component {
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if this.file}}
|
{{#if this.file}}
|
||||||
<div class="photo-grid">
|
|
||||||
<PlacePhotoUploadItem
|
<PlacePhotoUploadItem
|
||||||
@file={{this.file}}
|
@file={{this.file}}
|
||||||
@onSuccess={{this.handleUploadSuccess}}
|
@onSuccess={{this.handleUploadSuccess}}
|
||||||
@onRemove={{this.removeFile}}
|
@onRemove={{this.removeFile}}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if this.suggestedTags.length}}
|
{{#if this.suggestedTags.length}}
|
||||||
<div class="photo-tag-suggestions">
|
<div class="photo-tag-suggestions">
|
||||||
@@ -271,6 +272,19 @@ export default class PlacePhotoUpload extends Component {
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="photo-alt-input">Description (optional):</label>
|
||||||
|
<input
|
||||||
|
id="photo-alt-input"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Describe this photo"
|
||||||
|
value={{this.altText}}
|
||||||
|
disabled={{this.isPublishing}}
|
||||||
|
{{on "input" this.updateAltText}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary btn-publish"
|
class="btn btn-primary btn-publish"
|
||||||
|
|||||||
@@ -4,6 +4,16 @@ import { action } from '@ember/object';
|
|||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import { task } from 'ember-concurrency';
|
import { task } from 'ember-concurrency';
|
||||||
|
|
||||||
|
function getPlaceTime(place) {
|
||||||
|
const dateVal = place.createdAt;
|
||||||
|
if (!dateVal) return 0;
|
||||||
|
if (typeof dateVal === 'number') {
|
||||||
|
return dateVal;
|
||||||
|
}
|
||||||
|
const parsed = Date.parse(dateVal);
|
||||||
|
return isNaN(parsed) ? 0 : parsed;
|
||||||
|
}
|
||||||
|
|
||||||
export default class ListsListController extends Controller {
|
export default class ListsListController extends Controller {
|
||||||
@service router;
|
@service router;
|
||||||
@service mapUi;
|
@service mapUi;
|
||||||
@@ -73,7 +83,7 @@ export default class ListsListController extends Controller {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return merged;
|
return merged.sort((a, b) => getPlaceTime(b) - getPlaceTime(a));
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
|||||||
@@ -179,10 +179,8 @@ export default class SearchController extends Controller {
|
|||||||
const targetName = params.selected || params.q;
|
const targetName = params.selected || params.q;
|
||||||
|
|
||||||
if (targetName && pois.length > 0) {
|
if (targetName && pois.length > 0) {
|
||||||
let matchedPlace = null;
|
|
||||||
|
|
||||||
// 1. Exact Name Match
|
// 1. Exact Name Match
|
||||||
matchedPlace = pois.find(
|
let matchedPlace = pois.find(
|
||||||
(p) =>
|
(p) =>
|
||||||
p.osmTags &&
|
p.osmTags &&
|
||||||
(p.osmTags.name === targetName || p.osmTags['name:en'] === targetName)
|
(p.osmTags.name === targetName || p.osmTags['name:en'] === targetName)
|
||||||
|
|||||||
+7
-10
@@ -35,23 +35,20 @@ export default class BlossomService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _getAuthHeader(action, hash, serverUrl) {
|
async _getAuthHeader(action, hash, serverUrl) {
|
||||||
const factory = new EventFactory({ signer: this.nostrAuth.signer });
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
const serverHostname = new URL(serverUrl).hostname;
|
const serverHostname = new URL(serverUrl).hostname;
|
||||||
|
|
||||||
const authTemplate = {
|
const authEvent = await EventFactory.fromKind(24242)
|
||||||
kind: 24242,
|
.content(action === 'upload' ? 'Upload photo for place' : 'Delete photo')
|
||||||
created_at: now,
|
.modifyPublicTags(() => [
|
||||||
content: action === 'upload' ? 'Upload photo for place' : 'Delete photo',
|
|
||||||
tags: [
|
|
||||||
['t', action],
|
['t', action],
|
||||||
['x', hash],
|
['x', hash],
|
||||||
['expiration', String(now + 3600)],
|
['expiration', String(now + 3600)],
|
||||||
['server', serverHostname],
|
['server', serverHostname],
|
||||||
],
|
])
|
||||||
};
|
.created(now)
|
||||||
|
.as(this.nostrAuth.signer)
|
||||||
const authEvent = await factory.sign(authTemplate);
|
.sign();
|
||||||
const base64 = btoa(JSON.stringify(authEvent));
|
const base64 = btoa(JSON.stringify(authEvent));
|
||||||
const base64url = base64
|
const base64url = base64
|
||||||
.replace(/\+/g, '-')
|
.replace(/\+/g, '-')
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export default class ImageProcessorService extends Service {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Failed to process image: ${e.message}`);
|
throw new Error(`Failed to process image: ${e.message}`, { cause: e });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -150,9 +150,6 @@ export default class NostrAuthService extends Service {
|
|||||||
const relay = DEFAULT_CONNECT_RELAY;
|
const relay = DEFAULT_CONNECT_RELAY;
|
||||||
localStorage.setItem(STORAGE_KEY_CONNECT_RELAY, relay);
|
localStorage.setItem(STORAGE_KEY_CONNECT_RELAY, relay);
|
||||||
|
|
||||||
// Override aggressive 10s EOSE timeout to allow time for QR scanning
|
|
||||||
this.nostrRelay.pool.relay(relay).eoseTimeout = 180000; // 3 minutes
|
|
||||||
|
|
||||||
this._signerInstance = new NostrConnectSigner({
|
this._signerInstance = new NostrConnectSigner({
|
||||||
pool: this.nostrRelay.pool,
|
pool: this.nostrRelay.pool,
|
||||||
relays: [relay],
|
relays: [relay],
|
||||||
@@ -235,9 +232,6 @@ export default class NostrAuthService extends Service {
|
|||||||
|
|
||||||
const localSigner = this._getLocalSigner();
|
const localSigner = this._getLocalSigner();
|
||||||
|
|
||||||
// Override aggressive 10s EOSE timeout to allow time for QR scanning
|
|
||||||
this.nostrRelay.pool.relay(relay).eoseTimeout = 180000; // 3 minutes
|
|
||||||
|
|
||||||
this._signerInstance = new NostrConnectSigner({
|
this._signerInstance = new NostrConnectSigner({
|
||||||
pool: this.nostrRelay.pool,
|
pool: this.nostrRelay.pool,
|
||||||
relays: [relay],
|
relays: [relay],
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ export default class OsmAuthService extends Service {
|
|||||||
clientId: clientId,
|
clientId: clientId,
|
||||||
redirectUrl: redirectUrl,
|
redirectUrl: redirectUrl,
|
||||||
storeRefreshToken: true,
|
storeRefreshToken: true,
|
||||||
|
onAccessTokenExpiry() {
|
||||||
|
return this.exchangeRefreshTokenForAccessToken();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
new MarcoOsmAuthStorage()
|
new MarcoOsmAuthStorage()
|
||||||
);
|
);
|
||||||
|
|||||||
+6
-9
@@ -245,13 +245,6 @@ body {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.photo-grid {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.photo-upload-item {
|
.photo-upload-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
aspect-ratio: 4 / 3;
|
aspect-ratio: 4 / 3;
|
||||||
@@ -259,6 +252,7 @@ body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #1e262e;
|
background: #1e262e;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.photo-upload-item img {
|
.photo-upload-item img {
|
||||||
@@ -1530,7 +1524,6 @@ button.create-place {
|
|||||||
color: #5f6368;
|
color: #5f6368;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
padding-left: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-submit-btn:hover {
|
.search-submit-btn:hover {
|
||||||
@@ -1867,8 +1860,12 @@ button.create-place {
|
|||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.place-photo-upload .form-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.photo-tag-suggestions {
|
.photo-tag-suggestions {
|
||||||
margin: 1rem 0 1.5rem;
|
margin-bottom: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.photo-tag-suggestions-title {
|
.photo-tag-suggestions-title {
|
||||||
|
|||||||
+2
-2
@@ -102,7 +102,7 @@ import molarTooth from '@waysidemapping/pinhead/dist/icons/molar_tooth.svg?raw';
|
|||||||
import needleAndSpoolOfThread from '@waysidemapping/pinhead/dist/icons/needle_and_spool_of_thread.svg?raw';
|
import needleAndSpoolOfThread from '@waysidemapping/pinhead/dist/icons/needle_and_spool_of_thread.svg?raw';
|
||||||
import openBook from '@waysidemapping/pinhead/dist/icons/open_book.svg?raw';
|
import openBook from '@waysidemapping/pinhead/dist/icons/open_book.svg?raw';
|
||||||
import palace from '@waysidemapping/pinhead/dist/icons/palace.svg?raw';
|
import palace from '@waysidemapping/pinhead/dist/icons/palace.svg?raw';
|
||||||
import parkingP from '@waysidemapping/pinhead/dist/icons/parking_p.svg?raw';
|
import parkingP from '@waysidemapping/pinhead/dist/icons/p_wide.svg?raw';
|
||||||
import personCricketBattingAtCricketBall from '@waysidemapping/pinhead/dist/icons/person_cricket_batting_at_cricket_ball.svg?raw';
|
import personCricketBattingAtCricketBall from '@waysidemapping/pinhead/dist/icons/person_cricket_batting_at_cricket_ball.svg?raw';
|
||||||
import personBoardingTramWithDestinationDisplayAndPantographOnTramTrack from '@waysidemapping/pinhead/dist/icons/person_boarding_tram_with_destination_display_and_pantograph_on_tram_track.svg?raw';
|
import personBoardingTramWithDestinationDisplayAndPantographOnTramTrack from '@waysidemapping/pinhead/dist/icons/person_boarding_tram_with_destination_display_and_pantograph_on_tram_track.svg?raw';
|
||||||
import personJockeyingRacehorse from '@waysidemapping/pinhead/dist/icons/person_jockeying_racehorse.svg?raw';
|
import personJockeyingRacehorse from '@waysidemapping/pinhead/dist/icons/person_jockeying_racehorse.svg?raw';
|
||||||
@@ -118,7 +118,7 @@ import policeOfficerWithStopArm from '@waysidemapping/pinhead/dist/icons/police_
|
|||||||
import planeTopRight from '@waysidemapping/pinhead/dist/icons/plane_top_right.svg?raw';
|
import planeTopRight from '@waysidemapping/pinhead/dist/icons/plane_top_right.svg?raw';
|
||||||
import roundStructureWithFlag from '@waysidemapping/pinhead/dist/icons/round_structure_with_flag.svg?raw';
|
import roundStructureWithFlag from '@waysidemapping/pinhead/dist/icons/round_structure_with_flag.svg?raw';
|
||||||
import sailingShipInWater from '@waysidemapping/pinhead/dist/icons/sailing_ship_in_water.svg?raw';
|
import sailingShipInWater from '@waysidemapping/pinhead/dist/icons/sailing_ship_in_water.svg?raw';
|
||||||
import scissorsOpen from '@waysidemapping/pinhead/dist/icons/scissors_open.svg?raw';
|
import scissorsOpen from '@waysidemapping/pinhead/dist/icons/open_scissors.svg?raw';
|
||||||
import shipwreckInWater from '@waysidemapping/pinhead/dist/icons/shipwreck_in_water.svg?raw';
|
import shipwreckInWater from '@waysidemapping/pinhead/dist/icons/shipwreck_in_water.svg?raw';
|
||||||
import steamTrainOnRailwayTrack from '@waysidemapping/pinhead/dist/icons/steam_train_on_railway_track.svg?raw';
|
import steamTrainOnRailwayTrack from '@waysidemapping/pinhead/dist/icons/steam_train_on_railway_track.svg?raw';
|
||||||
import shoppingBag from '@waysidemapping/pinhead/dist/icons/shopping_bag.svg?raw';
|
import shoppingBag from '@waysidemapping/pinhead/dist/icons/shopping_bag.svg?raw';
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export function parsePlacePhotos(events) {
|
|||||||
let blurhash = null;
|
let blurhash = null;
|
||||||
let isLandscape = false;
|
let isLandscape = false;
|
||||||
let aspectRatio = 16 / 9; // default
|
let aspectRatio = 16 / 9; // default
|
||||||
|
let altText = null;
|
||||||
let placeIdentifier = event.tags.find((t) => t[0] === 'i')?.[1];
|
let placeIdentifier = event.tags.find((t) => t[0] === 'i')?.[1];
|
||||||
|
|
||||||
for (const tag of imeta.slice(1)) {
|
for (const tag of imeta.slice(1)) {
|
||||||
@@ -85,6 +86,10 @@ export function parsePlacePhotos(events) {
|
|||||||
isLandscape = true;
|
isLandscape = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (tag.startsWith('alt ')) {
|
||||||
|
const alt = tag.substring(4).trim();
|
||||||
|
// Strip the legacy placeholder we used to write on every event
|
||||||
|
altText = alt === 'A photo of a place' ? null : alt || null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +105,7 @@ export function parsePlacePhotos(events) {
|
|||||||
aspectRatio,
|
aspectRatio,
|
||||||
placeIdentifier,
|
placeIdentifier,
|
||||||
tags: eventTagValues,
|
tags: eventTagValues,
|
||||||
|
alt: altText,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ export default {
|
|||||||
'@babel/plugin-transform-runtime',
|
'@babel/plugin-transform-runtime',
|
||||||
{
|
{
|
||||||
absoluteRuntime: dirname(fileURLToPath(import.meta.url)),
|
absoluteRuntime: dirname(fileURLToPath(import.meta.url)),
|
||||||
useESModules: true,
|
|
||||||
regenerator: false,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
...macros.babelMacros,
|
...macros.babelMacros,
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ import eslintConfigPrettier from 'eslint-config-prettier';
|
|||||||
import qunit from 'eslint-plugin-qunit';
|
import qunit from 'eslint-plugin-qunit';
|
||||||
import n from 'eslint-plugin-n';
|
import n from 'eslint-plugin-n';
|
||||||
|
|
||||||
import babelParser from '@babel/eslint-parser/experimental-worker';
|
import babelParser from '@babel/eslint-parser';
|
||||||
|
|
||||||
const esmParserOptions = {
|
const esmParserOptions = {
|
||||||
ecmaFeatures: { modules: true },
|
ecmaFeatures: { modules: true },
|
||||||
|
|||||||
+52
-53
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "marco",
|
"name": "marco",
|
||||||
"version": "1.25.0",
|
"version": "1.26.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Unhosted maps app",
|
"description": "Unhosted maps app",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -39,61 +39,61 @@
|
|||||||
"version": "pnpm build && git add release/"
|
"version": "pnpm build && git add release/"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.28.5",
|
"@babel/core": "^8.0.1",
|
||||||
"@babel/eslint-parser": "^7.28.5",
|
"@babel/eslint-parser": "^8.0.1",
|
||||||
"@babel/plugin-transform-runtime": "^7.28.5",
|
"@babel/plugin-transform-runtime": "^8.0.1",
|
||||||
"@babel/runtime": "^7.28.4",
|
"@babel/runtime": "^8.0.0",
|
||||||
"@ember/test-helpers": "^5.4.1",
|
"@ember/test-helpers": "^5.4.3",
|
||||||
"@ember/test-waiters": "^4.1.1",
|
"@ember/test-waiters": "^4.1.2",
|
||||||
"@embroider/core": "^4.4.2",
|
"@embroider/core": "^4.6.3",
|
||||||
"@embroider/legacy-inspector-support": "^0.1.3",
|
"@embroider/legacy-inspector-support": "^0.1.3",
|
||||||
"@embroider/macros": "^1.19.6",
|
"@embroider/macros": "^1.20.6",
|
||||||
"@embroider/router": "^3.0.6",
|
"@embroider/router": "^3.0.6",
|
||||||
"@embroider/vite": "^1.5.0",
|
"@embroider/vite": "^1.7.9",
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^10.0.1",
|
||||||
"@glimmer/component": "^2.0.0",
|
"@glimmer/component": "^2.1.1",
|
||||||
"@remotestorage/module-places": "~1.3.0",
|
"@remotestorage/module-places": "~1.3.0",
|
||||||
"@rollup/plugin-babel": "^6.1.0",
|
"@rollup/plugin-babel": "^7.1.0",
|
||||||
"@warp-drive/core": "~5.8.0",
|
"@warp-drive/core": "~5.8.2",
|
||||||
"@warp-drive/ember": "~5.8.0",
|
"@warp-drive/ember": "~5.8.2",
|
||||||
"@warp-drive/json-api": "~5.8.0",
|
"@warp-drive/json-api": "~5.8.2",
|
||||||
"@warp-drive/legacy": "~5.8.0",
|
"@warp-drive/legacy": "~5.8.2",
|
||||||
"@warp-drive/utilities": "~5.8.0",
|
"@warp-drive/utilities": "~5.8.2",
|
||||||
"babel-plugin-ember-template-compilation": "^3.0.1",
|
"babel-plugin-ember-template-compilation": "^4.0.0",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^10.0.4",
|
||||||
"decorator-transforms": "^2.3.1",
|
"decorator-transforms": "^2.4.0",
|
||||||
"ember-cli": "^6.10.0",
|
"ember-cli": "^7.1.0",
|
||||||
"ember-cli-deprecation-workflow": "^4.0.0",
|
"ember-cli-deprecation-workflow": "^4.0.1",
|
||||||
"ember-modifier": "^4.2.2",
|
"ember-modifier": "^4.3.0",
|
||||||
"ember-page-title": "^9.0.3",
|
"ember-page-title": "^9.0.3",
|
||||||
"ember-qunit": "^9.0.4",
|
"ember-qunit": "^9.1.0",
|
||||||
"ember-resolver": "^13.1.1",
|
"ember-resolver": "^13.2.0",
|
||||||
"ember-source": "~6.11.0-alpha.6",
|
"ember-source": "~7.1.0",
|
||||||
"ember-template-lint": "^7.9.3",
|
"ember-template-lint": "^7.9.3",
|
||||||
"ember-truth-helpers": "^5.0.0",
|
"ember-truth-helpers": "^5.0.0",
|
||||||
"ember-welcome-page": "^8.0.4",
|
"ember-welcome-page": "^8.0.4",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^10.8.0",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-ember": "^12.7.5",
|
"eslint-plugin-ember": "^13.5.0",
|
||||||
"eslint-plugin-n": "^17.23.1",
|
"eslint-plugin-n": "^18.2.2",
|
||||||
"eslint-plugin-qunit": "^8.2.5",
|
"eslint-plugin-qunit": "^8.2.6",
|
||||||
"eslint-plugin-warp-drive": "^5.8.0",
|
"eslint-plugin-warp-drive": "^5.8.2",
|
||||||
"feather-icons": "^4.29.2",
|
"feather-icons": "^4.29.2",
|
||||||
"globals": "^16.5.0",
|
"globals": "^17.9.0",
|
||||||
"latlon-geohash": "^2.0.0",
|
"latlon-geohash": "^2.0.0",
|
||||||
"ol": "^10.7.0",
|
"ol": "^10.10.0",
|
||||||
"ol-mapbox-style": "^13.2.0",
|
"ol-mapbox-style": "^13.4.2",
|
||||||
"prettier": "^3.7.4",
|
"prettier": "^3.9.6",
|
||||||
"prettier-plugin-ember-template-tag": "^2.1.2",
|
"prettier-plugin-ember-template-tag": "^2.1.7",
|
||||||
"qunit": "^2.25.0",
|
"qunit": "^2.26.0",
|
||||||
"qunit-dom": "^3.5.0",
|
"qunit-dom": "^3.6.0",
|
||||||
"remotestorage-widget": "^1.8.1",
|
"remotestorage-widget": "^1.8.1",
|
||||||
"remotestoragejs": "2.0.0-beta.9",
|
"remotestoragejs": "2.0.0-beta.9",
|
||||||
"sinon": "^21.0.1",
|
"sinon": "^22.1.0",
|
||||||
"stylelint": "^16.26.1",
|
"stylelint": "^17.14.1",
|
||||||
"stylelint-config-standard": "^38.0.0",
|
"stylelint-config-standard": "^40.0.0",
|
||||||
"testem": "^3.17.0",
|
"testem": "^3.20.1",
|
||||||
"vite": "^7.3.0"
|
"vite": "^8.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 22"
|
"node": ">= 22"
|
||||||
@@ -102,17 +102,16 @@
|
|||||||
"edition": "octane"
|
"edition": "octane"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/hashes": "^2.2.0",
|
"@noble/hashes": "^2.3.0",
|
||||||
"@waysidemapping/pinhead": "^15.20.0",
|
"@waysidemapping/pinhead": "^15.25.0",
|
||||||
"applesauce-core": "^5.2.0",
|
"applesauce-core": "^6.2.0",
|
||||||
"applesauce-factory": "^4.0.0",
|
"applesauce-relay": "^6.2.1",
|
||||||
"applesauce-relay": "^5.2.0",
|
"applesauce-signers": "^6.2.2",
|
||||||
"applesauce-signers": "^5.2.0",
|
|
||||||
"blurhash": "^2.0.5",
|
"blurhash": "^2.0.5",
|
||||||
"ember-concurrency": "^5.2.0",
|
"ember-concurrency": "^5.2.0",
|
||||||
"ember-lifeline": "^7.0.0",
|
"ember-lifeline": "^7.1.0",
|
||||||
"nostr-idb": "^5.0.0",
|
"nostr-idb": "^5.1.0",
|
||||||
"oauth2-pkce": "^2.1.3",
|
"oauth2-pkce": "^3.0.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"rxjs": "^7.8.2"
|
"rxjs": "^7.8.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
diff --git a/async-arrow-task-transform.js b/async-arrow-task-transform.js
|
||||||
|
index 4eb856a2a7753ef583e10e07f2b602c0c1c0c21d..6a808d035834567dcfc491ecaa7f59e77d344887 100644
|
||||||
|
--- a/async-arrow-task-transform.js
|
||||||
|
+++ b/async-arrow-task-transform.js
|
||||||
|
@@ -331,7 +331,7 @@ function cleanupUnusedTaskFactoryImports(programPath, usedTaskFactories) {
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = declare((api) => {
|
||||||
|
- api.assertVersion(7);
|
||||||
|
+ // api.assertVersion(7);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'transform-ember-concurrency-async-function-tasks',
|
||||||
Generated
+2507
-2555
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
|||||||
|
patchedDependencies:
|
||||||
|
ember-concurrency@5.2.0: patches/ember-concurrency@5.2.0.patch
|
||||||
@@ -1 +0,0 @@
|
|||||||
!function(){"use strict";var t=["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","#","$","%","*","+",",","-",".",":",";","=","?","@","[","]","^","_","{","|","}","~"],e=(e,a)=>{var o="";for(let r=1;r<=a;r++){let h=Math.floor(e)/Math.pow(83,a-r)%83;o+=t[Math.floor(h)]}return o},a=t=>{let e=t/255;return e<=.04045?e/12.92:Math.pow((e+.055)/1.055,2.4)},o=t=>{let e=Math.max(0,Math.min(1,t));return e<=.0031308?Math.trunc(12.92*e*255+.5):Math.trunc(255*(1.055*Math.pow(e,.4166666666666667)-.055)+.5)},r=(t,e)=>(t=>t<0?-1:1)(t)*Math.pow(Math.abs(t),e),h=class extends Error{constructor(t){super(t),this.name="ValidationError",this.message=t}},i=(t,e,o,r)=>{let h=0,i=0,n=0,s=4*e;for(let g=0;g<e;g++){let e=4*g;for(let l=0;l<o;l++){let o=e+l*s,c=r(g,l);h+=c*a(t[o]),i+=c*a(t[o+1]),n+=c*a(t[o+2])}}let l=1/(e*o);return[h*l,i*l,n*l]};self.onmessage=async t=>{if("PROCESS_IMAGE"!==t.data?.type)return;const{id:a,file:n,targetWidth:s,targetHeight:l,quality:g,computeBlurhash:c}=t.data;try{let t,M;try{const e=await createImageBitmap(n,{resizeWidth:s,resizeHeight:l,resizeQuality:"high"});if(t=new OffscreenCanvas(s,l),M=t.getContext("2d"),!M)throw new Error("Failed to get 2d context from OffscreenCanvas");M.drawImage(e,0,0,s,l),e.close()}catch(f){console.warn("Hardware resize failed, falling back to stepped software scaling:",f);const e=await n.arrayBuffer(),a=new Blob([e],{type:n.type}),o=await createImageBitmap(a);let r=o.width,h=o.height,i=new OffscreenCanvas(r,h),g=i.getContext("2d");for(g.imageSmoothingEnabled=!0,g.imageSmoothingQuality="high",g.drawImage(o,0,0);.5*i.width>s&&.5*i.height>l;){const t=new OffscreenCanvas(Math.floor(.5*i.width),Math.floor(.5*i.height)),e=t.getContext("2d");e.imageSmoothingEnabled=!0,e.imageSmoothingQuality="high",e.drawImage(i,0,0,t.width,t.height),i=t}t=new OffscreenCanvas(s,l),M=t.getContext("2d"),M.imageSmoothingEnabled=!0,M.imageSmoothingQuality="high",M.drawImage(i,0,0,s,l),o.close()}let d=null;if(c)try{d=((t,a,n)=>{if(a*n*4!==t.length)throw new h("Width and height must match the pixels array");let s=[];for(let e=0;e<3;e++)for(let o=0;o<4;o++){let r=0==o&&0==e?1:2,h=i(t,a,n,(t,h)=>r*Math.cos(Math.PI*o*t/a)*Math.cos(Math.PI*e*h/n));s.push(h)}let l,g=s[0],c=s.slice(1),f="";if(f+=e(21,1),c.length>0){let t=Math.max(...c.map(t=>Math.max(...t))),a=Math.floor(Math.max(0,Math.min(82,Math.floor(166*t-.5))));l=(a+1)/166,f+=e(a,1)}else l=1,f+=e(0,1);return f+=e((t=>(o(t[0])<<16)+(o(t[1])<<8)+o(t[2]))(g),4),c.forEach(t=>{f+=e(((t,e)=>19*Math.floor(Math.max(0,Math.min(18,Math.floor(9*r(t[0]/e,.5)+9.5))))*19+19*Math.floor(Math.max(0,Math.min(18,Math.floor(9*r(t[1]/e,.5)+9.5))))+Math.floor(Math.max(0,Math.min(18,Math.floor(9*r(t[2]/e,.5)+9.5)))))(t,l),2)}),f})(M.getImageData(0,0,s,l).data,s,l)}catch(m){console.warn("Could not generate blurhash (possible canvas fingerprinting protection):",m)}const u=await t.convertToBlob({type:"image/jpeg",quality:g}),w=`${s}x${l}`;self.postMessage({id:a,success:!0,blob:u,dim:w,blurhash:d})}catch(M){self.postMessage({id:a,success:!1,error:M.message})}}}();
|
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
var q = [
|
||||||
|
"0",
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"3",
|
||||||
|
"4",
|
||||||
|
"5",
|
||||||
|
"6",
|
||||||
|
"7",
|
||||||
|
"8",
|
||||||
|
"9",
|
||||||
|
"A",
|
||||||
|
"B",
|
||||||
|
"C",
|
||||||
|
"D",
|
||||||
|
"E",
|
||||||
|
"F",
|
||||||
|
"G",
|
||||||
|
"H",
|
||||||
|
"I",
|
||||||
|
"J",
|
||||||
|
"K",
|
||||||
|
"L",
|
||||||
|
"M",
|
||||||
|
"N",
|
||||||
|
"O",
|
||||||
|
"P",
|
||||||
|
"Q",
|
||||||
|
"R",
|
||||||
|
"S",
|
||||||
|
"T",
|
||||||
|
"U",
|
||||||
|
"V",
|
||||||
|
"W",
|
||||||
|
"X",
|
||||||
|
"Y",
|
||||||
|
"Z",
|
||||||
|
"a",
|
||||||
|
"b",
|
||||||
|
"c",
|
||||||
|
"d",
|
||||||
|
"e",
|
||||||
|
"f",
|
||||||
|
"g",
|
||||||
|
"h",
|
||||||
|
"i",
|
||||||
|
"j",
|
||||||
|
"k",
|
||||||
|
"l",
|
||||||
|
"m",
|
||||||
|
"n",
|
||||||
|
"o",
|
||||||
|
"p",
|
||||||
|
"q",
|
||||||
|
"r",
|
||||||
|
"s",
|
||||||
|
"t",
|
||||||
|
"u",
|
||||||
|
"v",
|
||||||
|
"w",
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"z",
|
||||||
|
"#",
|
||||||
|
"$",
|
||||||
|
"%",
|
||||||
|
"*",
|
||||||
|
"+",
|
||||||
|
",",
|
||||||
|
"-",
|
||||||
|
".",
|
||||||
|
":",
|
||||||
|
";",
|
||||||
|
"=",
|
||||||
|
"?",
|
||||||
|
"@",
|
||||||
|
"[",
|
||||||
|
"]",
|
||||||
|
"^",
|
||||||
|
"_",
|
||||||
|
"{",
|
||||||
|
"|",
|
||||||
|
"}",
|
||||||
|
"~"
|
||||||
|
], p = (t, e) => {
|
||||||
|
var a = "";
|
||||||
|
for (let o = 1; o <= e; o++) {
|
||||||
|
let h = Math.floor(t) / Math.pow(83, e - o) % 83;
|
||||||
|
a += q[Math.floor(h)];
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}, f = (t) => {
|
||||||
|
let e = t / 255;
|
||||||
|
return e <= .04045 ? e / 12.92 : Math.pow((e + .055) / 1.055, 2.4);
|
||||||
|
}, h = (t) => {
|
||||||
|
let e = Math.max(0, Math.min(1, t));
|
||||||
|
return e <= .0031308 ? Math.trunc(12.92 * e * 255 + .5) : Math.trunc(255 * (1.055 * Math.pow(e, .4166666666666667) - .055) + .5);
|
||||||
|
}, M = (t, e) => ((t) => t < 0 ? -1 : 1)(t) * Math.pow(Math.abs(t), e), d = class extends Error {
|
||||||
|
constructor(t) {
|
||||||
|
super(t), this.name = "ValidationError", this.message = t;
|
||||||
|
}
|
||||||
|
}, D = (t, e, a, o) => {
|
||||||
|
let h = 0, r = 0, n = 0, i = 4 * e;
|
||||||
|
for (let l = 0; l < e; l++) {
|
||||||
|
let e = 4 * l;
|
||||||
|
for (let s = 0; s < a; s++) {
|
||||||
|
let a = e + s * i, g = o(l, s);
|
||||||
|
h += g * f(t[a]), r += g * f(t[a + 1]), n += g * f(t[a + 2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let s = 1 / (e * a);
|
||||||
|
return [
|
||||||
|
h * s,
|
||||||
|
r * s,
|
||||||
|
n * s
|
||||||
|
];
|
||||||
|
}, S = (t, e, a, o, r) => {
|
||||||
|
if (o < 1 || o > 9 || r < 1 || r > 9) throw new d("BlurHash must have between 1 and 9 components");
|
||||||
|
if (e * a * 4 !== t.length) throw new d("Width and height must match the pixels array");
|
||||||
|
let n = [];
|
||||||
|
for (let h = 0; h < r; h++) for (let r = 0; r < o; r++) {
|
||||||
|
let o = 0 == r && 0 == h ? 1 : 2, i = D(t, e, a, (t, n) => o * Math.cos(Math.PI * r * t / e) * Math.cos(Math.PI * h * n / a));
|
||||||
|
n.push(i);
|
||||||
|
}
|
||||||
|
let i, s = n[0], l = n.slice(1), g = "";
|
||||||
|
if (g += p(o - 1 + 9 * (r - 1), 1), l.length > 0) {
|
||||||
|
let t = Math.max(...l.map((t) => Math.max(...t))), e = Math.floor(Math.max(0, Math.min(82, Math.floor(166 * t - .5))));
|
||||||
|
i = (e + 1) / 166, g += p(e, 1);
|
||||||
|
} else i = 1, g += p(0, 1);
|
||||||
|
return g += p(((t) => (h(t[0]) << 16) + (h(t[1]) << 8) + h(t[2]))(s), 4), l.forEach((t) => {
|
||||||
|
g += p(((t, e) => 19 * Math.floor(Math.max(0, Math.min(18, Math.floor(9 * M(t[0] / e, .5) + 9.5)))) * 19 + 19 * Math.floor(Math.max(0, Math.min(18, Math.floor(9 * M(t[1] / e, .5) + 9.5)))) + Math.floor(Math.max(0, Math.min(18, Math.floor(9 * M(t[2] / e, .5) + 9.5)))))(t, i), 2);
|
||||||
|
}), g;
|
||||||
|
};
|
||||||
|
self.onmessage = async (t) => {
|
||||||
|
if ("PROCESS_IMAGE" !== t.data?.type) return;
|
||||||
|
const { id: e, file: a, targetWidth: o, targetHeight: h, quality: r, computeBlurhash: n } = t.data;
|
||||||
|
try {
|
||||||
|
let t, l;
|
||||||
|
try {
|
||||||
|
const e = await createImageBitmap(a, {
|
||||||
|
resizeWidth: o,
|
||||||
|
resizeHeight: h,
|
||||||
|
resizeQuality: "high"
|
||||||
|
});
|
||||||
|
if (t = new OffscreenCanvas(o, h), l = t.getContext("2d"), !l) throw new Error("Failed to get 2d context from OffscreenCanvas");
|
||||||
|
l.drawImage(e, 0, 0, o, h), e.close();
|
||||||
|
} catch (i) {
|
||||||
|
console.warn("Hardware resize failed, falling back to stepped software scaling:", i);
|
||||||
|
const e = await a.arrayBuffer(), r = new Blob([e], { type: a.type }), n = await createImageBitmap(r);
|
||||||
|
let s = n.width, g = n.height, f = new OffscreenCanvas(s, g), c = f.getContext("2d");
|
||||||
|
for (c.imageSmoothingEnabled = !0, c.imageSmoothingQuality = "high", c.drawImage(n, 0, 0); .5 * f.width > o && .5 * f.height > h;) {
|
||||||
|
const t = new OffscreenCanvas(Math.floor(.5 * f.width), Math.floor(.5 * f.height)), e = t.getContext("2d");
|
||||||
|
e.imageSmoothingEnabled = !0, e.imageSmoothingQuality = "high", e.drawImage(f, 0, 0, t.width, t.height), f = t;
|
||||||
|
}
|
||||||
|
t = new OffscreenCanvas(o, h), l = t.getContext("2d"), l.imageSmoothingEnabled = !0, l.imageSmoothingQuality = "high", l.drawImage(f, 0, 0, o, h), n.close();
|
||||||
|
}
|
||||||
|
let g = null;
|
||||||
|
if (n) try {
|
||||||
|
const t = l.getImageData(0, 0, o, h);
|
||||||
|
g = S(t.data, o, h, 4, 3);
|
||||||
|
} catch (s) {
|
||||||
|
console.warn("Could not generate blurhash (possible canvas fingerprinting protection):", s);
|
||||||
|
}
|
||||||
|
const f = await t.convertToBlob({
|
||||||
|
type: "image/jpeg",
|
||||||
|
quality: r
|
||||||
|
}), c = `${o}x${h}`;
|
||||||
|
self.postMessage({
|
||||||
|
id: e,
|
||||||
|
success: !0,
|
||||||
|
blob: f,
|
||||||
|
dim: c,
|
||||||
|
blurhash: g
|
||||||
|
});
|
||||||
|
} catch (l) {
|
||||||
|
self.postMessage({
|
||||||
|
id: e,
|
||||||
|
success: !1,
|
||||||
|
error: l.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
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
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
+2
-2
@@ -39,8 +39,8 @@
|
|||||||
<meta name="msapplication-TileColor" content="#F6E9A6">
|
<meta name="msapplication-TileColor" content="#F6E9A6">
|
||||||
<meta name="msapplication-TileImage" content="/icons/icon-144.png">
|
<meta name="msapplication-TileImage" content="/icons/icon-144.png">
|
||||||
|
|
||||||
<script type="module" crossorigin src="/assets/main-BVNM87jL.js"></script>
|
<script type="module" crossorigin src="/assets/main-B2gk-G51.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/main-BGF-Udec.css">
|
<link rel="stylesheet" crossorigin href="/assets/main-CIu0qcV7.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="modal-portal"></div>
|
<div id="modal-portal"></div>
|
||||||
|
|||||||
@@ -135,4 +135,85 @@ module('Acceptance | collections navigation', function (hooks) {
|
|||||||
'Returns gracefully back to lists/to-go list view'
|
'Returns gracefully back to lists/to-go list view'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('places inside a collection are sorted by createdAt descending', async function (assert) {
|
||||||
|
class SortedMockStorageService extends Service {
|
||||||
|
initialSyncDone = true;
|
||||||
|
savedPlaces = [
|
||||||
|
{
|
||||||
|
id: 'place-oldest',
|
||||||
|
title: 'Oldest Place',
|
||||||
|
geohash: 'u33dc0',
|
||||||
|
createdAt: '2023-01-01T12:00:00.000Z',
|
||||||
|
osmTags: { name: 'Oldest Place' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'place-newest',
|
||||||
|
title: 'Newest Place',
|
||||||
|
geohash: 'u33dc0',
|
||||||
|
createdAt: '2023-01-03T12:00:00.000Z',
|
||||||
|
osmTags: { name: 'Newest Place' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'place-middle',
|
||||||
|
title: 'Middle Place',
|
||||||
|
geohash: 'u33dc0',
|
||||||
|
createdAt: '2023-01-02T12:00:00.000Z',
|
||||||
|
updatedAt: '2023-01-04T12:00:00.000Z',
|
||||||
|
osmTags: { name: 'Middle Place' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
lists = [
|
||||||
|
{
|
||||||
|
id: 'to-go',
|
||||||
|
title: 'Want to go',
|
||||||
|
color: '#2e9e4f',
|
||||||
|
placeRefs: [
|
||||||
|
{ id: 'place-oldest', geohash: 'u33dc0' },
|
||||||
|
{ id: 'place-newest', geohash: 'u33dc0' },
|
||||||
|
{ id: 'place-middle', geohash: 'u33dc0' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
findPlaceById(id) {
|
||||||
|
return this.savedPlaces.find((p) => p.id === id) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPlaceSaved() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPlacesInBounds() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlacesInList(listId) {
|
||||||
|
if (listId === 'to-go') {
|
||||||
|
return Promise.resolve(this.savedPlaces);
|
||||||
|
}
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
rs = {
|
||||||
|
on: () => {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.owner.unregister('service:storage');
|
||||||
|
this.owner.register('service:storage', SortedMockStorageService);
|
||||||
|
|
||||||
|
await visit('/lists/to-go');
|
||||||
|
await waitFor('.places-list');
|
||||||
|
|
||||||
|
const placeNames = Array.from(
|
||||||
|
document.querySelectorAll('.places-list .place-name')
|
||||||
|
).map((el) => el.textContent.trim());
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
placeNames,
|
||||||
|
['Newest Place', 'Middle Place', 'Oldest Place'],
|
||||||
|
'Places are ordered by createdAt in descending order'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import PhotoGallery from 'marco/components/photo-gallery';
|
|||||||
import { setupNostrMocks } from 'marco/tests/helpers/mock-nostr';
|
import { setupNostrMocks } from 'marco/tests/helpers/mock-nostr';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
const USER_A = 'a'.repeat(64);
|
||||||
|
const USER_B = 'b'.repeat(64);
|
||||||
|
|
||||||
class MockBlossomService extends Service {
|
class MockBlossomService extends Service {
|
||||||
async delete() {
|
async delete() {
|
||||||
return true;
|
return true;
|
||||||
@@ -34,7 +37,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
|||||||
this.photos = [
|
this.photos = [
|
||||||
{
|
{
|
||||||
eventId: 'event1',
|
eventId: 'event1',
|
||||||
pubkey: 'userA',
|
pubkey: USER_A,
|
||||||
placeIdentifier: 'osm:node:12345',
|
placeIdentifier: 'osm:node:12345',
|
||||||
url: 'https://example.com/a3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1.jpg',
|
url: 'https://example.com/a3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1.jpg',
|
||||||
thumbUrl:
|
thumbUrl:
|
||||||
@@ -42,7 +45,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
eventId: 'event2',
|
eventId: 'event2',
|
||||||
pubkey: 'userB',
|
pubkey: USER_B,
|
||||||
placeIdentifier: 'osm:node:12345',
|
placeIdentifier: 'osm:node:12345',
|
||||||
url: 'photo2.jpg',
|
url: 'photo2.jpg',
|
||||||
},
|
},
|
||||||
@@ -55,7 +58,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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) {
|
||||||
this.nostrAuth.pubkey = 'userB'; // Different from photo1's pubkey
|
this.nostrAuth.pubkey = USER_B; // Different from photo1's pubkey
|
||||||
this.selectedPhoto = this.photos[0];
|
this.selectedPhoto = this.photos[0];
|
||||||
|
|
||||||
await render(
|
await render(
|
||||||
@@ -80,7 +83,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('it shows delete button if user is creator and setting is enabled', 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 = USER_A; // Matches photo1's pubkey
|
||||||
this.settings.update('experimentalEnablePhotoDeletion', true); // Enable the setting
|
this.settings.update('experimentalEnablePhotoDeletion', true); // Enable the setting
|
||||||
this.selectedPhoto = this.photos[0];
|
this.selectedPhoto = this.photos[0];
|
||||||
|
|
||||||
@@ -106,7 +109,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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 = USER_A;
|
||||||
this.settings.update('experimentalEnablePhotoDeletion', true);
|
this.settings.update('experimentalEnablePhotoDeletion', true);
|
||||||
this.selectedPhoto = this.photos[0];
|
this.selectedPhoto = this.photos[0];
|
||||||
|
|
||||||
@@ -133,7 +136,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 = USER_A;
|
||||||
this.settings.update('experimentalEnablePhotoDeletion', true);
|
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', {
|
||||||
@@ -141,11 +144,11 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
|||||||
get: () => ({
|
get: () => ({
|
||||||
signEvent: async (e) => ({
|
signEvent: async (e) => ({
|
||||||
...e,
|
...e,
|
||||||
id: 'signed-id',
|
id: 'a3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1',
|
||||||
sig: 'sig',
|
sig: 'b3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1',
|
||||||
pubkey: 'userA',
|
pubkey: USER_A,
|
||||||
}),
|
}),
|
||||||
getPublicKey: async () => 'userA',
|
getPublicKey: async () => USER_A,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
this.selectedPhoto = this.photos[0];
|
this.selectedPhoto = this.photos[0];
|
||||||
@@ -227,7 +230,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('it copies event id to clipboard', async function (assert) {
|
test('it copies event id to clipboard', async function (assert) {
|
||||||
this.nostrAuth.pubkey = 'userA';
|
this.nostrAuth.pubkey = USER_A;
|
||||||
this.selectedPhoto = this.photos[0];
|
this.selectedPhoto = this.photos[0];
|
||||||
|
|
||||||
const clipboardStub = sinon
|
const clipboardStub = sinon
|
||||||
|
|||||||
@@ -97,4 +97,27 @@ module('Integration | Component | place-photo-upload', function (hooks) {
|
|||||||
|
|
||||||
assert.dom('.photo-tag-suggestions').doesNotExist();
|
assert.dom('.photo-tag-suggestions').doesNotExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it renders an optional alt text input after upload selection', async function (assert) {
|
||||||
|
this.place = {
|
||||||
|
title: 'Cafe Alpha',
|
||||||
|
osmId: '123',
|
||||||
|
osmType: 'node',
|
||||||
|
osmTags: { amenity: 'cafe' },
|
||||||
|
};
|
||||||
|
|
||||||
|
await render(
|
||||||
|
<template><PlacePhotoUpload @place={{this.place}} /></template>
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.dom('#photo-alt-input').doesNotExist();
|
||||||
|
|
||||||
|
const file = new File(['test'], 'photo.jpg', { type: 'image/jpeg' });
|
||||||
|
await selectFile(this.element, file);
|
||||||
|
|
||||||
|
assert.dom('#photo-alt-input').exists();
|
||||||
|
assert
|
||||||
|
.dom('#photo-alt-input')
|
||||||
|
.hasAttribute('placeholder', 'Describe this photo');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -148,6 +148,68 @@ module('Unit | Utility | nostr', function () {
|
|||||||
assert.strictEqual(photos[1].placeIdentifier, 'osm:node:456');
|
assert.strictEqual(photos[1].placeIdentifier, 'osm:node:456');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('parsePlacePhotos extracts alt text from imeta', function (assert) {
|
||||||
|
const events = [
|
||||||
|
{
|
||||||
|
id: 'event-1',
|
||||||
|
pubkey: 'pubkey-1',
|
||||||
|
created_at: 100,
|
||||||
|
tags: [
|
||||||
|
[
|
||||||
|
'imeta',
|
||||||
|
'url https://example.com/photo.jpg',
|
||||||
|
'dim 800x600',
|
||||||
|
'alt A sunny terrace overlooking the bay',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const photos = parsePlacePhotos(events);
|
||||||
|
|
||||||
|
assert.strictEqual(photos.length, 1);
|
||||||
|
assert.strictEqual(photos[0].alt, 'A sunny terrace overlooking the bay');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parsePlacePhotos strips the legacy placeholder alt text', function (assert) {
|
||||||
|
const events = [
|
||||||
|
{
|
||||||
|
id: 'event-1',
|
||||||
|
pubkey: 'pubkey-1',
|
||||||
|
created_at: 100,
|
||||||
|
tags: [
|
||||||
|
[
|
||||||
|
'imeta',
|
||||||
|
'url https://example.com/photo.jpg',
|
||||||
|
'dim 800x600',
|
||||||
|
'alt A photo of a place',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const photos = parsePlacePhotos(events);
|
||||||
|
|
||||||
|
assert.strictEqual(photos.length, 1);
|
||||||
|
assert.strictEqual(photos[0].alt, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parsePlacePhotos leaves alt null when not provided', function (assert) {
|
||||||
|
const events = [
|
||||||
|
{
|
||||||
|
id: 'event-1',
|
||||||
|
pubkey: 'pubkey-1',
|
||||||
|
created_at: 100,
|
||||||
|
tags: [['imeta', 'url https://example.com/photo.jpg', 'dim 800x600']],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const photos = parsePlacePhotos(events);
|
||||||
|
|
||||||
|
assert.strictEqual(photos.length, 1);
|
||||||
|
assert.strictEqual(photos[0].alt, null);
|
||||||
|
});
|
||||||
|
|
||||||
test('uniqNormalizedRelays returns normalized unique relays', function (assert) {
|
test('uniqNormalizedRelays returns normalized unique relays', function (assert) {
|
||||||
const relays = uniqNormalizedRelays([
|
const relays = uniqNormalizedRelays([
|
||||||
'Relay.example.com',
|
'Relay.example.com',
|
||||||
|
|||||||
Reference in New Issue
Block a user