Skip to content

Commit fa10a62

Browse files
authored
Merge branch 'main' into VictorMejorasCSS
2 parents 9d20de3 + 93fc3e2 commit fa10a62

2 files changed

Lines changed: 275 additions & 9 deletions

File tree

web/aladin.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
<input id="fits-url" type="url" placeholder="https://data.nasa.gov/sample.fits" aria-label="FITS file URL" />
2424
<button type="button" id="load-url">Load URL</button>
2525
</div>
26-
2726
<div class="marker-actions">
2827
<button type="button" id="add-marker" class="marker-control">Add marker</button>
2928
<button type="button" id="remove-marker" class="marker-control">Remove marker</button>

web/front_end.js

Lines changed: 275 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,30 @@ const fileInput = document.getElementById('fits-file');
1111
const urlInput = document.getElementById('fits-url');
1212
const loadUrlButton = document.getElementById('load-url');
1313
const sampleButtons = Array.from(document.querySelectorAll('[data-fits-url]'));
14+
const addMarkerButton = document.getElementById('add-marker');
15+
const removeMarkerButton = document.getElementById('remove-marker');
16+
const markerModal = document.getElementById('marker-modal');
17+
const markerForm = markerModal ? markerModal.querySelector('form') : null;
18+
const markerCancelButton = markerModal ? markerModal.querySelector('[data-action="cancel"]') : null;
19+
const markerTitleInput = document.getElementById('marker-title');
20+
const markerDescriptionInput = document.getElementById('marker-description');
21+
const markerColorInput = document.getElementById('marker-color');
1422

1523
let aladinInstance = null;
1624
let currentRequestId = 0;
25+
let markerLayer = null;
26+
let markerMode = 'idle';
27+
let pendingMarkerPosition = null;
28+
let storedStatusText = null;
29+
const placedMarkers = [];
30+
let lastStatusSnapshot = { label: null, fov: null };
31+
32+
if (addMarkerButton) {
33+
addMarkerButton.disabled = true;
34+
}
35+
if (removeMarkerButton) {
36+
removeMarkerButton.disabled = true;
37+
}
1738

1839
const DEFAULT_SAMPLE = sampleButtons.length
1940
? {
@@ -23,6 +44,33 @@ const DEFAULT_SAMPLE = sampleButtons.length
2344
}
2445
: null;
2546

47+
function createMarkerIcon(color) {
48+
const size = 22;
49+
const canvas = document.createElement('canvas');
50+
canvas.width = size;
51+
canvas.height = size;
52+
const context = canvas.getContext('2d');
53+
if (!context) {
54+
return canvas;
55+
}
56+
const normalizedColor = typeof color === 'string' && color.trim() ? color : '#60A5FA';
57+
context.clearRect(0, 0, size, size);
58+
context.beginPath();
59+
context.arc(size / 2, size / 2, (size / 2) - 2, 0, Math.PI * 2);
60+
context.closePath();
61+
context.fillStyle = normalizedColor;
62+
context.fill();
63+
context.lineWidth = 2;
64+
context.strokeStyle = '#0f172a';
65+
context.stroke();
66+
context.beginPath();
67+
context.arc(size / 2, size / 2, 3, 0, Math.PI * 2);
68+
context.closePath();
69+
context.fillStyle = '#0f172a';
70+
context.fill();
71+
return canvas;
72+
}
73+
2674
function showError(message) {
2775
errorBox.textContent = message;
2876
errorBox.classList.remove('hidden');
@@ -34,6 +82,10 @@ function clearError() {
3482
}
3583

3684
function updateStatus(label, fov) {
85+
lastStatusSnapshot = {
86+
label: label ?? null,
87+
fov: Number.isFinite(fov) ? fov : null
88+
};
3789
const parts = [];
3890
if (label) {
3991
parts.push(`Source: ${label}`);
@@ -45,6 +97,22 @@ function updateStatus(label, fov) {
4597
statusMessage.textContent = parts.join(' · ') || 'Ready to load a FITS image.';
4698
}
4799

100+
function setTemporaryStatus(message) {
101+
if (storedStatusText === null) {
102+
storedStatusText = statusMessage.textContent;
103+
}
104+
statusMessage.textContent = message;
105+
}
106+
107+
function restoreStatus() {
108+
if (storedStatusText !== null) {
109+
statusMessage.textContent = storedStatusText;
110+
storedStatusText = null;
111+
} else {
112+
updateStatus(lastStatusSnapshot.label, lastStatusSnapshot.fov);
113+
}
114+
}
115+
48116
function focusOnImage(ra, dec, fov) {
49117
if (Number.isFinite(ra) && Number.isFinite(dec)) {
50118
aladinInstance.gotoRaDec(ra, dec);
@@ -81,6 +149,175 @@ function deriveLabel(source) {
81149
return 'Custom FITS';
82150
}
83151

152+
function ensureMarkerLayer() {
153+
if (!aladinInstance || markerLayer) {
154+
return markerLayer;
155+
}
156+
markerLayer = A.catalog({
157+
name: 'Annotations',
158+
shape: 'circle',
159+
sourceSize: 18,
160+
color: '#60A5FA'
161+
});
162+
aladinInstance.addCatalog(markerLayer);
163+
return markerLayer;
164+
}
165+
166+
function updateRemoveMarkerState() {
167+
if (removeMarkerButton) {
168+
removeMarkerButton.disabled = placedMarkers.length === 0;
169+
}
170+
}
171+
172+
function exitMarkerFlow() {
173+
markerMode = 'idle';
174+
if (addMarkerButton) {
175+
addMarkerButton.classList.remove('marker-control--active');
176+
addMarkerButton.disabled = !aladinInstance;
177+
}
178+
pendingMarkerPosition = null;
179+
restoreStatus();
180+
}
181+
182+
function openMarkerModal() {
183+
if (!markerModal || !markerForm) {
184+
return;
185+
}
186+
markerModal.classList.remove('hidden');
187+
if (markerTitleInput) {
188+
markerTitleInput.focus();
189+
}
190+
document.addEventListener('keydown', handleModalKeydown);
191+
}
192+
193+
function resetMarkerForm() {
194+
if (!markerForm) {
195+
return;
196+
}
197+
markerForm.reset();
198+
if (markerColorInput) {
199+
markerColorInput.value = '#60A5FA';
200+
}
201+
}
202+
203+
function closeMarkerModal() {
204+
if (!markerModal) {
205+
return;
206+
}
207+
markerModal.classList.add('hidden');
208+
document.removeEventListener('keydown', handleModalKeydown);
209+
resetMarkerForm();
210+
exitMarkerFlow();
211+
}
212+
213+
function handleModalKeydown(event) {
214+
if (event.key === 'Escape') {
215+
event.preventDefault();
216+
closeMarkerModal();
217+
}
218+
}
219+
220+
function extractCoordinates(event) {
221+
const candidates = [event, event?.data];
222+
for (const candidate of candidates) {
223+
if (!candidate) {
224+
continue;
225+
}
226+
const ra = Number(candidate.ra ?? candidate.lon ?? candidate.lng ?? candidate.alpha);
227+
const dec = Number(candidate.dec ?? candidate.lat ?? candidate.beta);
228+
if (Number.isFinite(ra) && Number.isFinite(dec)) {
229+
return { ra, dec };
230+
}
231+
}
232+
return null;
233+
}
234+
235+
function handleSkyClick(event) {
236+
if (markerMode !== 'armed') {
237+
return;
238+
}
239+
const coordinates = extractCoordinates(event);
240+
if (!coordinates) {
241+
return;
242+
}
243+
markerMode = 'pending';
244+
pendingMarkerPosition = coordinates;
245+
if (addMarkerButton) {
246+
addMarkerButton.classList.remove('marker-control--active');
247+
addMarkerButton.disabled = true;
248+
}
249+
openMarkerModal();
250+
}
251+
252+
function startMarkerPlacement() {
253+
if (!aladinInstance) {
254+
return;
255+
}
256+
ensureMarkerLayer();
257+
markerMode = 'armed';
258+
if (addMarkerButton) {
259+
addMarkerButton.classList.add('marker-control--active');
260+
addMarkerButton.disabled = false;
261+
}
262+
setTemporaryStatus('Click on the sky map to choose where the new marker should be placed.');
263+
}
264+
265+
function handleMarkerSubmission(event) {
266+
event.preventDefault();
267+
if (!pendingMarkerPosition || !markerLayer) {
268+
closeMarkerModal();
269+
return;
270+
}
271+
const title = markerTitleInput?.value.trim() || `Marker ${placedMarkers.length + 1}`;
272+
const description = markerDescriptionInput?.value.trim() || '';
273+
const color = markerColorInput?.value || '#60A5FA';
274+
const markerOptions = {
275+
popupTitle: title,
276+
popupDesc: description
277+
};
278+
try {
279+
const marker = A.marker(pendingMarkerPosition.ra, pendingMarkerPosition.dec, markerOptions);
280+
marker.useMarkerDefaultIcon = false;
281+
if (typeof marker.setImage === 'function') {
282+
marker.setImage(createMarkerIcon(color));
283+
}
284+
marker.color = color;
285+
markerLayer.addSources([marker]);
286+
placedMarkers.push(marker);
287+
updateRemoveMarkerState();
288+
} catch (error) {
289+
console.error('Unable to add marker', error);
290+
showError('We could not create the marker. Please try again.');
291+
}
292+
closeMarkerModal();
293+
}
294+
295+
function removeLatestMarker() {
296+
if (!markerLayer || placedMarkers.length === 0) {
297+
return;
298+
}
299+
const marker = placedMarkers.pop();
300+
try {
301+
if (typeof markerLayer.remove === 'function') {
302+
markerLayer.remove(marker);
303+
} else if (typeof markerLayer.removeSources === 'function') {
304+
markerLayer.removeSources([marker]);
305+
} else if (typeof markerLayer.removeSource === 'function') {
306+
markerLayer.removeSource(marker);
307+
} else if (typeof markerLayer.removeAll === 'function') {
308+
markerLayer.removeAll();
309+
placedMarkers.length = 0;
310+
}
311+
} catch (error) {
312+
console.warn('Unable to remove marker individually, clearing all markers', error);
313+
if (typeof markerLayer.removeAll === 'function') {
314+
markerLayer.removeAll();
315+
placedMarkers.length = 0;
316+
}
317+
}
318+
updateRemoveMarkerState();
319+
}
320+
84321
function loadFits(source, options = {}) {
85322
if (!aladinInstance || !source) {
86323
return;
@@ -198,10 +435,14 @@ async function initialiseViewer() {
198435
target: 'M 31'
199436
});
200437

201-
var marker1 = A.marker(0, 0, {popupTitle: "Test", popupDesc: "TEST"});
202-
var markerLayer = A.catalog();
203-
aladinInstance.addCatalog(markerLayer);
204-
markerLayer.addSources([marker1]);
438+
ensureMarkerLayer();
439+
if (addMarkerButton) {
440+
addMarkerButton.disabled = false;
441+
}
442+
updateRemoveMarkerState();
443+
if (typeof aladinInstance.on === 'function') {
444+
aladinInstance.on('click', handleSkyClick);
445+
}
205446
});
206447

207448
initialiseSampleButtons();
@@ -229,9 +470,35 @@ urlInput.addEventListener('keydown', (event) => {
229470
}
230471
});
231472

232-
openComparisonButton = document.getElementById("open-comparison");
233-
openComparisonButton.addEventListener('click', () => {
234-
console.log(aladinInstance.getBaseImageLayer());
235-
});
473+
if (addMarkerButton) {
474+
addMarkerButton.addEventListener('click', () => {
475+
if (!aladinInstance) {
476+
showError('The FITS viewer is still initialising. Please wait a moment and try again.');
477+
return;
478+
}
479+
if (markerMode === 'armed') {
480+
exitMarkerFlow();
481+
return;
482+
}
483+
startMarkerPlacement();
484+
});
485+
}
486+
487+
if (markerCancelButton) {
488+
markerCancelButton.addEventListener('click', (event) => {
489+
event.preventDefault();
490+
closeMarkerModal();
491+
});
492+
}
493+
494+
if (markerForm) {
495+
markerForm.addEventListener('submit', handleMarkerSubmission);
496+
}
497+
498+
if (removeMarkerButton) {
499+
removeMarkerButton.addEventListener('click', () => {
500+
removeLatestMarker();
501+
});
502+
}
236503

237504
initialiseViewer();

0 commit comments

Comments
 (0)