Compare commits

..

4 Commits

Author SHA1 Message Date
Matthieu Baumann
c69aa990b6 add floating tooltip 2026-01-10 15:26:05 +01:00
Matthieu Baumann
42209b166e first commit 2026-01-08 17:56:19 +01:00
Matthieu Baumann
2ef5e47c27 update changelog 2026-01-07 11:39:10 +01:00
Matthieu Baumann
337618d6ef remove logs 2026-01-07 10:09:13 +01:00
20 changed files with 5987 additions and 5359 deletions

View File

@@ -6,6 +6,13 @@
## Released
### 3.8.0
* [fix] horizontal/vertical overlay lines appearing correctly <https://github.com/cds-astro/aladin-lite/issues/334>
* [fix] layer opacity restored when switching from not visible to visible <https://github.com/cds-astro/aladin-lite/issues/332>
* [feat] dark/light mode for the interface
* [fix] polylines shapes size not consistent w.r.t to div size <https://github.com/cds-astro/aladin-lite/issues/331>
### 3.7.0-beta
#### What's Changed

6
assets/icons/tree.svg Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 36 36" version="1.1" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>tree-view-line</title>
<path d="M15,32H11a1,1,0,0,1-1-1V27a1,1,0,0,1,1-1h4a1,1,0,0,1,1,1v4A1,1,0,0,1,15,32Zm-3-2h2V28H12Z" class="clr-i-outline clr-i-outline-path-1"></path><path d="M15,16H11a1,1,0,0,0-1,1v1.2H5.8V12H7a1,1,0,0,0,1-1V7A1,1,0,0,0,7,6H3A1,1,0,0,0,2,7v4a1,1,0,0,0,1,1H4.2V29.8h6.36a.8.8,0,0,0,0-1.6H5.8V19.8H10V21a1,1,0,0,0,1,1h4a1,1,0,0,0,1-1V17A1,1,0,0,0,15,16ZM4,8H6v2H4ZM14,20H12V18h2Z" class="clr-i-outline clr-i-outline-path-2"></path><path d="M34,9a1,1,0,0,0-1-1H10v2H33A1,1,0,0,0,34,9Z" class="clr-i-outline clr-i-outline-path-3"></path><path d="M33,18H18v2H33a1,1,0,0,0,0-2Z" class="clr-i-outline clr-i-outline-path-4"></path><path d="M33,28H18v2H33a1,1,0,0,0,0-2Z" class="clr-i-outline clr-i-outline-path-5"></path>
<rect x="0" y="0" width="36" height="36" fill-opacity="0"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -910,7 +910,6 @@ impl App {
hips.add_allsky(allsky)?;
// Once received ask for redraw
self.request_redraw = true;
al_core::log("request redraw");
}
}
}
@@ -1072,7 +1071,6 @@ impl App {
gl.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT);
// set the blending options
al_core::log("draw");
layers.draw(camera, shaders, colormaps, projection)?;
// Draw the catalog

View File

@@ -183,8 +183,6 @@ impl From<query::Allsky> for AllskyRequest {
.collect())
}
Bitpix::F32 => {
al_core::log("allsky fits parsed");
Ok(handle_allsky_fits(raw_bytes, tile_size, allsky_tile_size)?
.map(|image| ImageType::RawRgba8u { image })
.collect())
@@ -211,7 +209,6 @@ impl From<query::Allsky> for AllskyRequest {
}
}
});
al_core::log("allsky parsed");
Self {
id,

View File

@@ -99,10 +99,6 @@ impl FreqSpaceMoc {
let f_hash_0 = f_hash << (Frequency::<u64>::MAX_DEPTH - f_depth);
let f_hash_1 = (f_hash + 1) << (Frequency::<u64>::MAX_DEPTH - f_depth);
//let f0 = Frequency::<u64>::hash2freq(5171582628058365952);
//let f1 = Frequency::<u64>::hash2freq(5171590187200806912);
//al_core::log(&format!("F1: {f0}"));
let hpx_ranges_2d = HpxRanges2D::create_from_freq_ranges_positions(
vec![f_hash_0..f_hash_1; 1],
vec![hpx.idx()],

View File

@@ -206,10 +206,7 @@ impl HiPS2DBuffer {
}
let is_allsky_tile = tile_size == self.config.allsky_tile_size() as u32;
al_core::log("cell is root");
if is_allsky_tile {
al_core::log("copied to gpu");
texture.copy_to_gpu(
cell, // The tile cell
&image,
@@ -365,7 +362,6 @@ impl HpxTileBuffer for HiPS2DBuffer {
];
let channel = config.get_format().get_pixel_format();
al_core::log(&format!("channel: {:?}", channel));
let tile_size = config.get_tile_size();
let tile_pixels = create_hpx_texture_storage(gl, channel, 128, tile_size)?;
@@ -398,7 +394,6 @@ impl HpxTileBuffer for HiPS2DBuffer {
self.config.set_image_ext(ext)?;
let channel = self.config.get_format().get_pixel_format();
al_core::log(&format!("set channel: {:?}", channel));
let tile_size = self.config.get_tile_size();
self.tile_pixels = create_hpx_texture_storage(gl, channel, 128, tile_size)?;
@@ -482,7 +477,6 @@ impl SendUniforms for HiPS2DBuffer {
let shader = shader.attach_uniforms_from(&self.config);
if self.allsky_rendering {
al_core::log("allsky is rendering");
for idx in 0..NUM_HPX_TILES_DEPTH_ZERO {
let cell = HEALPixCell(0, idx as u64);

File diff suppressed because it is too large Load Diff

View File

@@ -37,14 +37,13 @@
--hover-color: green;
--toggle-color: dodgerblue;
--border-size: 2px;
--valid-color: greenyellow;
--valid-color: green;
--error-color: red;
}
.aladin-tree {
width: 100%;
border-top: 1px solid var(--border-color);
border-bottom: 2px solid var(--border-color);
border-bottom: var(--border-size) solid var(--border-color);
}
.aladin-tree .aladin-directory-path {
@@ -62,7 +61,6 @@
}
.aladin-link {
list-style-type: none;
padding: 0.5em 0px;
cursor:pointer;
margin: 0;
}
@@ -75,8 +73,48 @@
border-bottom: none;
}
.aladin-tree li {
border-bottom: 1px solid var(--border-color);
.aladin-tree li > * {
border-bottom: var(--border-size) solid var(--border-color);
padding: 0.5em 0px;
}
.aladin-tree li:last-of-type > * {
border-bottom: 0px;
}
#aladin-tooltip-mouse {
display: none;
position: fixed;
pointer-events: none; /* tooltip won't block mouse */
z-index: 1000;
padding: 0;
max-width: 100%;
}
#aladin-tooltip-mouse * {
margin: 0;
}
.aladin-fig {
display: inline-block; /* shrink-wraps content */
position: relative;
}
.aladin-fig img {
display: block;
max-width: 128px;
max-height: 128px;
}
.aladin-fig figcaption {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
color: white;
font-size: 11px;
text-align: center;
background: black;
}
.aladin-lite-spectra-displayer .aladin-spectra-unit-selector {
@@ -286,7 +324,7 @@
}
.aladin-box {
padding: 0.2rem;
padding: 0.3rem;
background: whitesmoke;
position: absolute;
font-size: inherit;
@@ -353,7 +391,7 @@
margin-top: 0 0 2px 0;
cursor:pointer;
color: #605F61;
border: var(--border-size) solid #AEAEAE;
border: var(--border-size) solid var(--border-color);
border-radius: 3px;
background: #fff;
font-size: 1.0rem;
@@ -368,8 +406,10 @@
.aladin-box-separator {
height: 0;
border-top: 1px solid #d2d2d2;
padding-bottom: 5px;
border-top: var(--border-size) solid var(--border-color);
margin-top: 5px;
margin-bottom: 5px;
}
.aladin-restore {
@@ -493,6 +533,7 @@
.aladin-input-text:focus, .aladin-input-number:focus {
border-color: var(--toggle-color);
background-color: var(--bg-color);
}
[data-theme="light"] .aladin-input-text.search:focus {
@@ -529,12 +570,6 @@
line-height: 1.2rem;
}
.aladin-input-text.search.aladin-not-valid {
-webkit-box-shadow:inset 0px 0px 0px 1px #f00;
-moz-box-shadow:inset 0px 0px 0px 1px #f00;
box-shadow:inset 0px 0px 0px 1px #f00;
}
.aladin-input-text.aladin-not-valid {
border: var(--border-size) solid var(--error-color);
}
@@ -882,7 +917,7 @@
-moz-appearance: none;
-webkit-appearance: none;
outline-style: none;
padding: 0.2rem;
padding: 0.5rem;
border-radius: 0.2rem;
font-family: monospace;
box-sizing: border-box;
@@ -965,7 +1000,6 @@ otherwise it fits its content options. If those are too big the select can go ou
/********** Range Input Styles **********/
/*Range Reset*/
.aladin-input-range {
background: transparent;
cursor: pointer;
margin: 0.8rem 0;
box-sizing: border-box;
@@ -981,11 +1015,20 @@ otherwise it fits its content options. If those are too big the select can go ou
.aladin-input-range:focus {
outline: none;
}
.aladin-input-text:-internal-autofill-selected,
.aladin-input-text:-internal-autofill-selected:hover,
.aladin-input-text:-internal-autofill-selected:focus,
.aladin-input-text:-internal-autofill-selected:active {
-webkit-box-shadow: 0 0 0 1000px var(--bg-color) inset !important;
box-shadow: 0 0 0 1000px var(--bg-color) inset !important;
-webkit-text-fill-color: var(--text-color) !important;
padding-left: 0.5rem !important;
}
/***** Chrome, Safari, Opera and Edge Chromium styles *****/
.aladin-input-range::-webkit-slider-container {
background: var(--text-color);
height: 0.1rem;
min-height: 0.1rem;
}
@@ -999,8 +1042,7 @@ otherwise it fits its content options. If those are too big the select can go ou
font-family: monospace;
line-height: 1rem;
float: left;
}
.aladin-tooltip-container .aladin-tooltip {
@@ -1184,14 +1226,12 @@ otherwise it fits its content options. If those are too big the select can go ou
.aladin-HiPS-browser-box .aladin-input-text {
width: 100%;
min-width: 300px;
padding: 0.5rem;
}
.aladin-cat-browser-box .aladin-input-text.search {
width: 100%;
min-width: 300px;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.aladin-location {
@@ -1213,6 +1253,7 @@ otherwise it fits its content options. If those are too big the select can go ou
border-radius: 0 0.2rem 0.2rem 0;
box-sizing: content-box;
width: 12.5rem;
padding: 0.2rem;
}
.aladin-location .aladin-location-copy {

View File

@@ -358,6 +358,11 @@ export let Aladin = (function () {
this.reticle = new Reticle(this.options, this);
this.popup = new Popup(this.aladinDiv, this.view);
this.tooltip = document.createElement('div')
this.tooltip.id = 'aladin-tooltip-mouse';
this.tooltip.classList.add("aladin-box")
this.aladinDiv.appendChild(this.tooltip)
this.ui = [];

View File

@@ -77,7 +77,7 @@ export let MeasurementTable = (function() {
tooltip: {
global: true,
aladin: this.aladin,
content: 'Press shift + mouse wheel for scrolling'
content: 'Scroll to see more...'
},
aladin: this.aladin,
layout,

View File

@@ -51,8 +51,7 @@ export class MocServer {
//expr: "dataproduct_type=image",
get: "record",
fmt: "json",
fields: "ID,hips_creator,hips_copyright,hips_order,hips_tile_width,hips_frame,hips_tile_format,obs_title,obs_description,obs_copyright,obs_regime,client_category",
//fields: "ID,hips_initial_fov,hips_initial_ra,hips_initial_dec,hips_pixel_bitpix,hips_creator,hips_copyright,hips_frame,hips_order,hips_order_min,hips_tile_width,hips_tile_format,hips_pixel_cut,obs_title,obs_description,obs_copyright,obs_regime,hips_data_range,hips_service_url",
fields: "ID,hips_creator,hips_copyright,hips_order,hips_tile_width,hips_frame,hips_tile_format,obs_title,obs_description,obs_copyright,obs_regime,client_category,dataproduct_subtype,hips_service_url,hips_initial_ra,hips_initial_dec,hips_initial_fov",
};
this._allHiPSes = Utils.loadFromUrls(MocServer.MIRRORS_HTTPS, {
@@ -97,8 +96,7 @@ export class MocServer {
expr: "dataproduct_type=image&&ID=" + ids.join(','),
get: "record",
fmt: "json",
fields: "ID,hips_creator,hips_copyright,hips_frame,hips_tile_format,obs_title,obs_description,obs_copyright,obs_regime",
//fields: "ID,hips_initial_fov,hips_initial_ra,hips_initial_dec,hips_pixel_bitpix,hips_creator,hips_copyright,hips_frame,hips_order,hips_order_min,hips_tile_width,hips_tile_format,hips_pixel_cut,obs_title,obs_description,obs_copyright,obs_regime,hips_data_range,hips_service_url",
fields: "ID,hips_creator,hips_copyright,hips_frame,hips_tile_format,obs_title,obs_description,obs_copyright,obs_regime,dataproduct_subtype,hips_service_url,hips_initial_ra,hips_initial_dec,hips_initial_fov",
};
return Utils.loadFromUrls(MocServer.MIRRORS_HTTPS, {
@@ -114,7 +112,6 @@ export class MocServer {
get: "record",
fmt: "json",
fields: "ID,hips_copyright,obs_title,obs_description,obs_copyright,cs_service_url,hips_service_url",
//fields: "ID,hips_copyright,hips_order,hips_order_min,obs_title,obs_description,obs_copyright,obs_regime,cs_service_url,hips_service_url",
};
this._allCatalogHiPSes = Utils.loadFromUrls(MocServer.MIRRORS_HTTPS, {data: params, dataType: 'json'})

View File

@@ -22,7 +22,7 @@ import { MocServer } from "../../MocServer.js";
import { Box } from "../Widgets/Box.js";
import { Dropdown } from "../Input/Dropdown.js";
import filterOnUrl from "../../../../assets/icons/filter-on.svg";
import hipsIconUrl from "../../../../assets/icons/hips.svg";
import treeIconUrl from "../../../../assets/icons/tree.svg";
import filterOffUrl from "../../../../assets/icons/filter-off.svg";
import { Input } from "../Widgets/Input.js";
import { TogglerActionButton } from "../Button/Toggler.js";
@@ -31,7 +31,6 @@ import { HiPSFilterBox } from "./HiPSFilterBox.js";
import A from "../../A.js";
import { Utils } from "../../Utils.ts";
import { ActionButton } from "../Widgets/ActionButton.js";
import infoIconUrl from "../../../../assets/icons/info.svg"
import { Icon } from "../Widgets/Icon.js";
import { Tree } from "../Widgets/Tree.js";
import { ALEvent } from "../../events/ALEvent.js";
@@ -47,6 +46,10 @@ import { ALEvent } from "../../events/ALEvent.js";
*****************************************************************************/
function fillHiPSHierarchy(name, hips, path, hierarchy) {
if (path[path.length - 1] === '/') {
path = path.substring(0, path.length - 1);
}
let folders = path.split('/')
let curFolder = folders.shift()
@@ -131,6 +134,7 @@ export class HiPSBrowserBox extends Box {
},
// a callback called for filtering
filter,
aladin,
});
MocServer.getAllHiPSes().then((HiPSes) => {
@@ -185,6 +189,7 @@ export class HiPSBrowserBox extends Box {
}
};
let typedRecently = false;
let searchDropdown = new Dropdown(aladin, {
name: "HiPS browser",
placeholder: "Browse a HiPS by an URL, ID or keywords",
@@ -201,20 +206,34 @@ export class HiPSBrowserBox extends Box {
keydown(e) {
e.stopPropagation();
// ignore navigation keys
if (e.key.length === 1 || e.key === "Backspace" || e.key === "Delete") {
typedRecently = true;
}
if (e.key === 'Enter') {
e.preventDefault()
_parseHiPS(e)
}
},
input(e) {
setTimeout(() => (typedRecently = false), 0);
let value = e.target.value;
self.infoCurrentHiPSBtn.update({
disable: true,
})
self.searchTree.triggerFilter({title: e.target.value});
self.searchTree.triggerFilter({title: value});
searchDropdown.removeClass('aladin-valid')
searchDropdown.removeClass('aladin-not-valid')
if (searchDropdown.options && !typedRecently) {
let HiPSIDs = searchDropdown.options.options;
if (HiPSIDs.includes(value)) {
_parseHiPS(e)
}
}
},
},
});
@@ -247,19 +266,8 @@ export class HiPSBrowserBox extends Box {
},
});
let infoCurrentHiPSBtn = new ActionButton({
disable: true,
icon: {
size: 'medium',
monochrome: true,
url: infoIconUrl,
},
tooltip: {
global: true,
aladin,
content: "More about that survey?"
}
});
let infoCurrentHiPSBtn = ActionButton.BUTTONS(aladin)
.infoHiPS({disable: true})
let filterBtn = new TogglerActionButton({
icon: {
@@ -290,21 +298,16 @@ export class HiPSBrowserBox extends Box {
header: {
title: Layout.horizontal([new Icon({
size: 'medium',
url: hipsIconUrl,
url: treeIconUrl,
monochrome: true,
}), "HiPS browser"]),
draggable: true,
},
//onDragged: () => {
//if (self.filterBtn.toggled) {
//self.filterBtn.toggle();
//}
//},
classList: ['aladin-HiPS-browser-box'],
content: Layout.vertical([
searchTree,
Layout.horizontal(["Search:", searchDropdown, infoCurrentHiPSBtn]),
Layout.horizontal(["Filter:", Layout.horizontal([filterEnabler, filterBtn, filterNumberElt])]),
Layout.horizontal([Layout.horizontal([filterEnabler, filterBtn, filterNumberElt])]),
]),
...options,
},

View File

@@ -0,0 +1,331 @@
// Copyright 2013 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
import { MocServer } from "../../MocServer.js";
import { Box } from "../Widgets/Box.js";
import { Dropdown } from "../Input/Dropdown.js";
import hipsIconUrl from "../../../../assets/icons/hips.svg";
import { Input } from "../Widgets/Input.js";
import { Layout } from "../Layout.js";
import A from "../../A.js";
import { Utils } from "../../Utils.ts";
import { ActionButton } from "../Widgets/ActionButton.js";
import infoIconUrl from "../../../../assets/icons/info.svg"
import { Icon } from "../Widgets/Icon.js";
/******************************************************************************
* Aladin Lite project
*
* File gui/Box/HiPSCompositeBox.js
*
* The code source of the interface for creating a new composite HiPS survey from multiple surveys
*
* Author: Matthieu Baumann[CDS]
*
*****************************************************************************/
function fillHiPSHierarchy(name, hips, path, hierarchy) {
let folders = path.split('/')
let curFolder = folders.shift()
if(curFolder === 'Image') {
let newPath = folders.join('/')
fillHiPSHierarchy(name, hips, newPath, hierarchy);
} else {
// Some exceptions because the MOCServer client_category field may contain some typos
if (['X', 'X-ray', 'Xray'].includes(curFolder)) {
curFolder = 'X-ray'
}
if (['Radion', 'Radio'].includes(curFolder)) {
curFolder = 'Radio'
}
if (curFolder === "Deprecated")
return;
hierarchy[curFolder] = hierarchy[curFolder] || {};
if (folders.length == 0) {
hierarchy[curFolder][name] = hips
} else {
let newPath = folders.join('/')
fillHiPSHierarchy(name, hips, newPath, hierarchy[curFolder])
}
}
}
export class HiPSCompositeBox extends Box {
static HiPSList = {};
constructor(aladin, options) {
let self;
// Search tree
MocServer.getAllHiPSes().then((HiPSes) => {
HiPSBrowserBox.HiPSList = {}
let hipsHierarchy = {};
// Fill the HiPSList from the MOCServer
// Build a hierarchy w.r.t sorted by regime
HiPSes.forEach((h) => {
let name = h.obs_title;
name = name.replace(/:|\'/g, '');
HiPSBrowserBox.HiPSList[name] = h;
if (h.client_category) {
let path = h.client_category
fillHiPSHierarchy(name, h, path, hipsHierarchy)
}
});
});
const _parseHiPS = (e) => {
const value = e.target.value;
let image, name;
// A user can put an url
try {
image = new URL(value).href;
name = image;
} catch (e) {
// Or he can select a HiPS from the list given
const hips = HiPSBrowserBox.HiPSList[value];
if (hips) {
image = hips.ID || hips.hips_service_url;
name = hips.obs_title || hips.ID;
} else {
// Finally if not found, interpret the input text value as the HiPS (e.g. ID)
image = value;
name = value;
}
}
if (image) {
self._addHiPS(image, name)
}
};
let searchDropdown = new Dropdown(aladin, {
name: "HiPS browser",
placeholder: "Browse a HiPS by an URL, ID or keywords",
tooltip: {
global: true,
aladin,
content: 'HiPS url, ID or keyword accepted',
},
actions: {
focus(e) {
searchDropdown.removeClass('aladin-valid')
searchDropdown.removeClass('aladin-not-valid')
},
keydown(e) {
e.stopPropagation();
if (e.key === 'Enter') {
e.preventDefault()
_parseHiPS(e)
}
},
input(e) {
self.infoCurrentHiPSBtn.update({
disable: true,
})
searchDropdown.removeClass('aladin-valid')
searchDropdown.removeClass('aladin-not-valid')
},
},
});
let infoCurrentHiPSBtn = new ActionButton({
disable: true,
icon: {
size: 'medium',
monochrome: true,
url: infoIconUrl,
},
tooltip: {
global: true,
aladin,
content: "More about that survey?"
}
});
super(
{
close: true,
header: {
title: Layout.horizontal([new Icon({
size: 'medium',
url: hipsIconUrl,
monochrome: true,
}), "HiPS Compositor"]),
draggable: true,
},
content: Layout.vertical([
Layout.horizontal([searchDropdown, infoCurrentHiPSBtn]),
]),
...options,
},
aladin.aladinDiv
);
self = this;
this.searchDropdown = searchDropdown;
this.aladin = aladin;
this.infoCurrentHiPSBtn = infoCurrentHiPSBtn;
this._addListeners();
}
_addListeners() {}
_addHiPS(id, name) {
let self = this;
self.searchDropdown.update({value: name, title: name});
let hips = A.imageHiPS(id, {
name,
successCallback: (hips) => {
self.searchDropdown.removeClass('aladin-not-valid');
self.searchDropdown.addClass('aladin-valid');
self.infoCurrentHiPSBtn.update({
disable: false,
action(e) {
window.open(hips.url);
}
})
self.aladin.removeUIByName("cube_displayer" + hips.layer)
if (!hips.cubeDepth)
return;
let numSlices = hips.cubeDepth;
let idxSlice = hips.cubeFirstFrame;
hips.setSliceNumber(idxSlice)
let toStr = (n, paddingBegin = false) => {
let s = n.toString();
let maxNumDigits = numSlices.toString().length;
if (s.length < maxNumDigits) {
let r = '&nbsp;'.repeat(maxNumDigits - s.length)
if (paddingBegin) {
s = r + s
} else {
s += r
}
}
return s;
}
let updateSlice = () => {
slicer.update({
value: idxSlice,
tooltip: {content: (idxSlice + 1) + '/' + numSlices, position: {direction: 'bottom'}},
})
hips.setSliceNumber(idxSlice)
cubeDisplayer.update({position: cubeDisplayer.position, content: Layout.horizontal([prevBtn, nextBtn, slicer, toStr(idxSlice + 1, true) + '/' + toStr(numSlices, false)])})
};
let slicer = Input.slider({
label: "Slice",
name: "cube_slicer" + hips.layer,
ticks: [idxSlice],
tooltip: {content: (idxSlice + 1) + '/' + numSlices, position: {direction: 'bottom'}},
min: 0,
max: numSlices - 1,
value: idxSlice,
actions: {
change: (e) => {
idxSlice = Math.round(e.target.value);
updateSlice();
},
input: (e) => {
idxSlice = Math.round(e.target.value);
slicer.update({
value: idxSlice,
tooltip: {content: (idxSlice + 1) + '/' + numSlices, position: {direction: 'bottom'}},
})
}
},
cssStyle: {
width: '300px'
}
});
let prevBtn = A.button({
size: 'small',
content: '<',
action(o) {
idxSlice = Math.max(idxSlice - 1, 0);
updateSlice()
}
})
let nextBtn = A.button({
size: 'small',
content: '>',
action(o) {
idxSlice = Math.min(idxSlice + 1, numSlices - 1);
updateSlice()
}
})
let cubeDisplayer = A.box({
close: true,
name: "cube_displayer" + hips.layer,
header: {
title: 'Player for: ' + hips.name,
draggable: true,
},
content: Layout.horizontal([prevBtn, nextBtn, slicer, toStr(idxSlice + 1, true) + '/' + toStr(numSlices, false)]),
position: {anchor: 'center top'},
});
self.aladin.addUI(cubeDisplayer)
},
errorCallback: (e) => {
self.searchDropdown.removeClass('aladin-valid');
self.searchDropdown.addClass('aladin-not-valid');
}
});
this.aladin.setOverlayImageLayer(hips, self.layer);
}
_show(options) {
// Regenerate a new layer name
this.layer = (options && options.layer) || Utils.uuidv4();
super._show(options)
}
}

View File

@@ -41,7 +41,7 @@ export class HiPSFilterBox extends Box {
let regimeBtn = Input.checkbox({
name: 'Freq',
tooltip: {content: 'Observation bandwidth', position: {direction: 'left'}},
tooltip: {content: 'enable/disable', position: {direction: 'left'}},
type: 'checkbox',
checked: false,
click(e) {
@@ -50,7 +50,7 @@ export class HiPSFilterBox extends Box {
});
let resolutionBtn = Input.checkbox({
name: 'Resolution',
tooltip: {content: 'Check for HiPS with a specific pixel resolution.', position: {direction: 'left'}},
tooltip: {content: 'enable/disable', position: {direction: 'left'}},
type: 'checkbox',
checked: false,
click(e) {
@@ -59,12 +59,12 @@ export class HiPSFilterBox extends Box {
});
let regimeOption = Layout.horizontal({
tooltip: {
content: "Observation regime",
position: { direction: "right" },
},
label: 'Freq: ',
label: 'Freq:',
layout: [Input.select({
tooltip: {
content: "Observation regime",
position: { direction: "left" },
},
value: "Optical",
options: [
"Radio",
@@ -170,15 +170,6 @@ export class HiPSFilterBox extends Box {
}
}
/*signalBrowserStatus(closed) {
this.browserClosed = closed;
// open
if (!closed) {
this._requestMOCServer()
}
}*/
enable(enable) {
this.on = enable;

View File

@@ -36,6 +36,7 @@ import { Utils } from "../../Utils";
import { View } from "../../View.js";
import { HiPSSettingsBox } from "./HiPSSettingsBox.js";
import hipsIconUrl from "../../../../assets/icons/hips.svg";
import treeIconUrl from "../../../../assets/icons/tree.svg";
import showIconUrl from "../../../../assets/icons/show.svg";
import addIconUrl from "../../../../assets/icons/plus.svg";
import hideIconUrl from "../../../../assets/icons/hide.svg";
@@ -51,6 +52,7 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
import { Input } from "../Widgets/Input.js";
import { Image } from "../../Image.js";
import { HiPSBrowserBox } from "./HiPSBrowserBox.js";
import { HiPSCompositeBox } from "./HiPSCompositeBox.js"
export class OverlayStackBox extends Box {
/*static previewImagesUrl = {
@@ -141,8 +143,6 @@ export class OverlayStackBox extends Box {
this._addListeners();
this.mocHiPSUrls = {};
this.HiPSui = {};
let self = this;
// Add overlay button
@@ -501,28 +501,12 @@ export class OverlayStackBox extends Box {
cursor: "help",
},
},
content: "Add new survey",
content: "Add a new HiPS",
},
action: (e) => {
e.stopPropagation();
e.preventDefault();
/*self._hide();
self.hipsSelectorBox = new HiPSSelectorBox(self.aladin);
// attach a callback
self.hipsSelectorBox.attach(
(HiPSId) => {
let name = Utils.uuidv4()
self.aladin.setOverlayImageLayer(HiPSId, name)
self.show();
}
);
self.hipsSelectorBox._show({
position: self.position,
});*/
self.aladin.addNewImageLayer(
'P/DSS2/color'
);
@@ -531,7 +515,7 @@ export class OverlayStackBox extends Box {
{
label: {
icon: {
url: hipsIconUrl,
url: treeIconUrl,
monochrome: true,
tooltip: {
content: "From our database...",
@@ -555,6 +539,33 @@ export class OverlayStackBox extends Box {
}});
},
},
{
label: {
icon: {
url: hipsIconUrl,
monochrome: true,
tooltip: {
content: "Combine different surveys into a color one!",
position: { direction: "right" },
},
cssStyle: {
cursor: "help",
},
},
content: "Add a composite HiPS",
},
action: (e) => {
e.stopPropagation();
e.preventDefault();
if (!self.hipsCompositeBox)
self.hipsCompositeBox = new HiPSCompositeBox(aladin);
self.hipsCompositeBox._show({position: {
anchor: 'center center'
}});
},
},
ContextMenu.fileLoaderItem({
label: "FITS image file",
accept: ".fits",
@@ -1034,81 +1045,11 @@ export class OverlayStackBox extends Box {
},
});
let loadMOCBtn = new ActionButton({
size: "small",
icon: {
url: Icon.dataURLFromSVG({ svg: Icon.SVG_ICONS.MOC }),
size: "small",
monochrome: true,
},
tooltip: {
content: "Add coverage",
position: { direction: "top" },
},
toggled: (() => {
let overlays = self.aladin.getOverlays();
let found = overlays.find(
(o) => o.type === "moc" && o.name === layer.name
);
return found !== undefined;
})(),
action: (e) => {
if (!loadMOCBtn.options.toggled) {
// load the moc
let moc = A.MOCFromURL(
layer.url + "/Moc.fits",
{ name: layer.name },
() => {
self.mocHiPSUrls[layer.url] = moc;
if (self.aladin.statusBar) {
self.aladin.statusBar.appendMessage({
message:
"Coverage of " +
layer.name +
" loaded",
duration: 2000,
type: "info",
});
}
loadMOCBtn.update({
toggled: true,
tooltip: {
content: "Remove coverage",
position: { direction: "top" },
},
});
}
);
self.aladin.addMOC(moc);
} else {
// unload the moc
let moc = self.mocHiPSUrls[layer.url];
self.aladin.removeLayer(moc);
delete self.mocHiPSUrls[layer.url];
if (self.aladin.statusBar) {
self.aladin.statusBar.appendMessage({
message:
"Coverage of " + layer.name + " removed",
duration: 2000,
type: "info",
});
}
loadMOCBtn.update({
toggled: false,
tooltip: {
content: "Add coverage",
position: { direction: "top" },
},
});
}
},
});
let loadMOCBtn = ActionButton.BUTTONS(aladin)
.addMOC({
name: layer.name,
url: layer.url + '/Moc.fits'
});
self.layer2swap = null;
let swapBtn = new ActionButton({

View File

@@ -67,7 +67,6 @@ import infoIconUrl from '../../../../assets/icons/info.svg';
import { Input } from "../Widgets/Input.js";
export class Dropdown extends Input {
// constructor
constructor(aladin, options) {
let self;
@@ -91,12 +90,6 @@ export class Dropdown extends Input {
update(options) {
let newOptions = {};
/*if (options && options.options) {
newOptions['autocomplete'] = {options: options.options};
delete options.options;
}*/
// add the other input text options
newOptions = {...newOptions, ...options};
super.update(newOptions)

View File

@@ -21,6 +21,10 @@ import { DOMElement } from "./Widget";
import { Tooltip } from "./Tooltip";
import { Icon } from "./Icon";
import { Layout } from "../Layout";
import infoIconUrl from "../../../../assets/icons/info.svg"
import targetIconUrl from '../../../../assets/icons/target.svg';
import A from "../../A";
/******************************************************************************
* Aladin Lite project
*
@@ -188,4 +192,136 @@ export class ActionButton extends DOMElement {
return new ActionButton(opt, target, position);
}
static mocs = {};
static BUTTONS(aladin) {
return {
infoHiPS: (options) => {
return new ActionButton({
icon: {
size: 'small',
monochrome: true,
url: infoIconUrl,
},
tooltip: {
position: {direction: "top"},
content: "More about that survey?"
},
action(e) {
window.open(options && options.url);
},
...options
})
},
targetHiPSLocation: (options) => {
let ra = options && options.ra;
let dec = options && options.dec;
let fov = options && options.fov;
return new ActionButton({
icon: {
size: 'small',
monochrome: true,
url: targetIconUrl,
},
disable: ra === undefined || dec === undefined || fov === undefined,
tooltip: {
content: "Target interesting sky location",
},
action(e) {
if (fov !== undefined && ra !== undefined && dec !== undefined) {
aladin.setFoV(+fov)
aladin.gotoObject(ra + ' ' + dec);
}
},
...options
})
},
addMOC: (options) => {
let name = options && options.name;
let url = options && options.url;
let button = new ActionButton({
size: "small",
icon: {
url: Icon.dataURLFromSVG({ svg: Icon.SVG_ICONS.MOC }),
size: "small",
monochrome: true,
},
tooltip: {
content: "Add coverage",
position: { direction: "top" },
},
toggled: (() => {
let overlays = aladin.getOverlays();
let found = overlays.find(
(o) => o.type === "moc" && o.name === name
);
return found !== undefined;
})(),
action: (e) => {
if (!button.options.toggled) {
// load the moc
let moc = A.MOCFromURL(
url,
{ name },
() => {
if (aladin.statusBar) {
aladin.statusBar.appendMessage({
message:
"Coverage of " +
name +
" loaded",
duration: 2000,
type: "info",
});
}
button.update({
toggled: true,
tooltip: {
content: "Remove coverage",
position: { direction: "top" },
},
});
}
);
aladin.addMOC(moc);
} else {
// unload the moc
let overlays = aladin.getOverlays();
let moc = overlays.find(
(o) => {
console.log(o.name)
o.type === "moc" && o.name === name
}
);
aladin.removeLayer(moc);
if (aladin.statusBar) {
aladin.statusBar.appendMessage({
message:
"Coverage of " + name + " removed",
duration: 2000,
type: "info",
});
}
button.update({
toggled: false,
tooltip: {
content: "Add coverage",
position: { direction: "top" },
},
});
}
},
...options
})
return button;
}
}
}
}

View File

@@ -141,7 +141,8 @@ export class Icon extends DOMElement {
static SVG_ICONS = {
CATALOG: '<svg xmlns="http://www.w3.org/2000/svg"><polygon points="1,0,5,0,5,3,1,3" fill="FILLCOLOR" /><polygon points="7,0,9,0,9,3,7,3" fill="FILLCOLOR" /><polygon points="10,0,12,0,12,3,10,3" fill="FILLCOLOR" /><polygon points="13,0,15,0,15,3,13,3" fill="FILLCOLOR" /><polyline points="1,5,5,9" stroke="FILLCOLOR" /><polyline points="1,9,5,5" stroke="FILLCOLOR" /><line x1="7" y1="7" x2="15" y2="7" stroke="FILLCOLOR" stroke-width="2" /><polyline points="1,11,5,15" stroke="FILLCOLOR" /><polyline points="1,15,5,11" stroke="FILLCOLOR" /><line x1="7" y1="13" x2="15" y2="13" stroke="FILLCOLOR" stroke-width="2" /></svg>',
MOC: '<svg xmlns="http://www.w3.org/2000/svg"><polyline points="0.5,7,2.5,7,2.5,5,7,5,7,3,10,3,10,5,13,5,13,7,15,7,15,9,13,9,13,12,10,12,10,14,7,14,7,12,2.5,12,2.5,10,0.5,10,0.5,7" stroke-width="1" stroke="FILLCOLOR" fill="transparent" /><line x1="1" y1="10" x2="6" y2="5" stroke="FILLCOLOR" stroke-width="0.5" /><line x1="2" y1="12" x2="10" y2="4" stroke="FILLCOLOR" stroke-width="0.5" /><line x1="5" y1="12" x2="12" y2="5" stroke="FILLCOLOR" stroke-width="0.5" /><line x1="7" y1="13" x2="13" y2="7" stroke="FILLCOLOR" stroke-width="0.5" /><line x1="10" y1="13" x2="13" y2="10" stroke="FILLCOLOR" stroke-width="0.5" /></svg>',
OVERLAY: '<svg xmlns="http://www.w3.org/2000/svg"><polygon points="10,5,10,1,14,1,14,14,2,14,2,9,6,9,6,5" fill="transparent" stroke="FILLCOLOR" stroke-width="2"/></svg>'
OVERLAY: '<svg xmlns="http://www.w3.org/2000/svg"><polygon points="10,5,10,1,14,1,14,14,2,14,2,9,6,9,6,5" fill="transparent" stroke="FILLCOLOR" stroke-width="2"/></svg>',
COLOR: '<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 122.88 112.75" style="enable-background:new 0 0 122.88 112.75" xml:space="preserve"><style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#6BBE66;} .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#FF4141;} .st2{fill-rule:evenodd;clip-rule:evenodd;fill:#00A1F1;}</style><g><path class="st0" d="M56.66,105.35c-5.94,4.6-13.4,7.34-21.5,7.34C15.74,112.69,0,96.95,0,77.53c0-13.47,7.58-25.17,18.7-31.07 c1.96,6.96,5.68,13.19,10.65,18.16c4.64,4.64,10.38,8.2,16.79,10.24c-0.06,0.9-0.09,1.81-0.09,2.73 C46.06,88.25,50.07,97.98,56.66,105.35L56.66,105.35z"/><path class="st1" d="M122.88,77.59c0,19.42-15.74,35.16-35.16,35.16S52.56,97,52.56,77.59c0-0.41,0.01-0.82,0.02-1.23 c2.03,0.3,4.11,0.46,6.23,0.46c11.5,0,21.92-4.66,29.46-12.2c5.45-5.45,9.4-12.41,11.17-20.19 C113.1,49.26,122.88,62.28,122.88,77.59L122.88,77.59z"/><path class="st2" d="M93.97,35.16c0,19.42-15.74,35.16-35.16,35.16S23.65,54.58,23.65,35.16S39.39,0,58.81,0 S93.97,15.74,93.97,35.16L93.97,35.16z"/></g></svg>'
}
static dataURLFromSVG(icon) {
@@ -150,9 +151,6 @@ export class Icon extends DOMElement {
let elt = document.createElement('div');
elt.innerHTML = str;
//elt.querySelector('svg').setAttribute('width', size);
//elt.querySelector('svg').setAttribute('height', size);
elt.style.width = size;
elt.style.height = size;

View File

@@ -50,8 +50,7 @@ export class Tooltip extends DOMElement {
}
options.position.anchor = target;
if (!options.delayShowUpTime) {
if (options.delayShowUpTime === undefined) {
options.delayShowUpTime = 500;
}
@@ -176,6 +175,8 @@ export class Tooltip extends DOMElement {
return this.el.querySelector('.aladin-tooltip');
}
static hoveredEl = null;
static add(options, target) {
if (target) {
if (target.tooltip) {
@@ -188,13 +189,42 @@ export class Tooltip extends DOMElement {
let targetEl = target.element()
if (options.mouse) {
let tooltip = options.aladin && options.aladin.tooltip;
Utils.on(targetEl, 'mousemove', (e) => {
tooltip.style.left = e.clientX + 12 + 'px';
tooltip.style.top = e.clientY + 12 + 'px';
});
Utils.on(targetEl, 'mouseover', (e) => {
if (Tooltip.hoveredEl && Tooltip.hoveredEl.contains(targetEl))
return;
Tooltip.hoveredEl = targetEl;
// Change the content to match
tooltip.innerHTML = options.content;
tooltip.style.display = 'block';
});
Utils.on(targetEl, 'mouseleave', (e) => {
if (Tooltip.hoveredEl && Tooltip.hoveredEl.contains(targetEl) && Tooltip.hoveredEl !== targetEl) {
return;
}
tooltip.style.display = 'none';
Tooltip.hoveredEl = null;
});
return;
}
if (options.global) {
let statusBar = options.aladin && options.aladin.statusBar;
if (!statusBar) {
return;
}
// handle global tooltip div display
Utils.on(targetEl, 'mouseover', (e) => {
statusBar.removeMessage('tooltip')
statusBar.appendMessage({

View File

@@ -21,6 +21,8 @@ import { DOMElement } from "./Widget";
import { Icon } from "./Icon";
import folderIconUrl from "../../../../assets/icons/folder.svg";
import { Layout } from "../Layout";
import { ActionButton } from "./ActionButton";
/******************************************************************************
* Aladin Lite project
*
@@ -40,6 +42,7 @@ export class Tree extends DOMElement {
super(el, options);
this.click = options && options.click;
this.aladin = options && options.aladin;
let rootNode = options && options.root || {};
this.params = null;
@@ -116,7 +119,22 @@ export class Tree extends DOMElement {
let listElt = document.createElement('ul');
for (const label of Object.keys(node).sort()) {
let labels = Object.keys(node).sort((la, lb) => {
let na = node[la];
let nb = node[lb];
let aIsLeaf = typeof na === "object" && 'ID' in na;
let bIsLeaf = typeof nb === "object" && 'ID' in nb;
if (aIsLeaf !== bIsLeaf) {
return aIsLeaf - bIsLeaf;
} else if (la < lb) {
return -1
} else {
return 1;
}
});
for (const label of labels) {
if (label !== 'parent' && label !== "label") {
let elt = document.createElement('li');
// points towards the parent node
@@ -129,7 +147,69 @@ export class Tree extends DOMElement {
elt.style.display = "block";
}
elt.innerHTML = this.label(child);
let label = this.label(child);
let layout = [label];
if (child.dataproduct_subtype === "color") {
layout.push(new Icon({
size: "small",
url: Icon.dataURLFromSVG({ svg: Icon.SVG_ICONS.COLOR }),
}))
}
layout.push(ActionButton.BUTTONS(this.aladin)
.infoHiPS({
url: child.hips_service_url,
tooltip: {
aladin: this.aladin,
global: true,
content: "More info on the survey ?",
},
}).element()
)
layout = layout.concat([
ActionButton.BUTTONS(this.aladin)
.targetHiPSLocation({
ra: child.hips_initial_ra,
dec: child.hips_initial_dec,
fov: child.hips_initial_fov,
tooltip: {
aladin: this.aladin,
global: true,
content: "Move to an interesting location",
},
})
.element(),
ActionButton.BUTTONS(this.aladin)
.addMOC({
name: label,
url: child.hips_service_url + '/Moc.fits',
tooltip: {
aladin: this.aladin,
global: true,
content: "Click to add its coverage",
},
})
.element(),
])
let config = {
layout,
tooltip: {
content: '<figure class="aladin-fig"><img ' +
`src="${child.hips_service_url + "/preview.jpg"}"` +
`alt="${label}" />` +
`<figcaption>${label}</figcaption>` +
'</figure>',
delayShowUpTime: "100ms",
mouse: true,
aladin: this.aladin,
}
};
elt.appendChild(Layout.horizontal(config).element());
} else {
// we see a parent, we must determine:
// * its color: he has at least 1 child inside the FoV => green
@@ -138,7 +218,14 @@ export class Tree extends DOMElement {
let numTotal = this.numChildMatchingFilter(child, false);
let name = label;
elt.innerHTML = name + ` (${numFilteringMatching}/${numTotal})`
elt.appendChild(Layout.horizontal([
new Icon({
size: "small",
monochrome: true,
url: folderIconUrl,
}),
name + ` (${numFilteringMatching}/${numTotal})`
]).element())
if (numFilteringMatching == 0) {
elt.style.display = "none";
@@ -191,7 +278,23 @@ export class Tree extends DOMElement {
let elts = this.el.querySelectorAll("li");
let i = 0;
for (const label of Object.keys(this.curNode).sort()) {
let labels = Object.keys(this.curNode).sort((la, lb) => {
let na = this.curNode[la];
let nb = this.curNode[lb];
let aIsLeaf = typeof na === "object" && 'ID' in na;
let bIsLeaf = typeof nb === "object" && 'ID' in nb;
if (aIsLeaf !== bIsLeaf) {
return aIsLeaf - bIsLeaf;
} else if (la < lb) {
return -1
} else {
return 1;
}
});
for (const label of labels) {
if (label !== 'parent' && label !== "label") {
let elt = elts[i];
i += 1;
@@ -216,7 +319,15 @@ export class Tree extends DOMElement {
let numTotal = this.numChildMatchingFilter(child, false);
let name = elt.innerText.split('(');
elt.innerHTML = name[0] + ` (${numFilteringMatching}/${numTotal})`
elt.innerHTML = Layout.horizontal([
new Icon({
size: "small",
monochrome: true,
url: folderIconUrl,
}),
name[0] + ` (${numFilteringMatching}/${numTotal})`
]).element().outerHTML
if (numFilteringMatching == 0) {
elt.style.display = "none";
@@ -239,7 +350,22 @@ export class Tree extends DOMElement {
let elts = this.el.querySelectorAll("li");
let i = 0;
for (const label of Object.keys(this.curNode).sort()) {
let labels = Object.keys(this.curNode).sort((la, lb) => {
let na = this.curNode[la];
let nb = this.curNode[lb];
let aIsLeaf = typeof na === "object" && 'ID' in na;
let bIsLeaf = typeof nb === "object" && 'ID' in nb;
if (aIsLeaf !== bIsLeaf) {
return aIsLeaf - bIsLeaf;
} else if (la < lb) {
return -1
} else {
return 1;
}
});
for (const label of labels) {
if (label !== 'parent' && label !== "label") {
let elt = elts[i];
i += 1;
@@ -260,7 +386,15 @@ export class Tree extends DOMElement {
let numTotal = this.numChildMatchingFilter(child, false);
let name = elt.innerText.split('(');
elt.innerHTML = name[0] + ` (${numFilteringMatching}/${numTotal})`
elt.innerHTML = Layout.horizontal([
new Icon({
size: "small",
monochrome: true,
url: folderIconUrl,
}),
name[0] + ` (${numFilteringMatching}/${numTotal})`
]).element().outerHTML;
if (numFilteringMatching == 0) {
elt.style.display = "none";
} else {
@@ -286,7 +420,22 @@ export class Tree extends DOMElement {
return true;
}
} else {
for (const label of Object.keys(node).sort()) {
let labels = Object.keys(node).sort((la, lb) => {
let na = node[la];
let nb = node[lb];
let aIsLeaf = typeof na === "object" && 'ID' in na;
let bIsLeaf = typeof nb === "object" && 'ID' in nb;
if (aIsLeaf !== bIsLeaf) {
return aIsLeaf - bIsLeaf;
} else if (la < lb) {
return -1
} else {
return 1;
}
});
for (const label of labels) {
if (label === "parent")
continue;
@@ -314,7 +463,22 @@ export class Tree extends DOMElement {
}
} else {
let num = 0;
for (const label of Object.keys(node).sort()) {
let labels = Object.keys(node).sort((la, lb) => {
let na = node[la];
let nb = node[lb];
let aIsLeaf = typeof na === "object" && 'ID' in na;
let bIsLeaf = typeof nb === "object" && 'ID' in nb;
if (aIsLeaf !== bIsLeaf) {
return aIsLeaf - bIsLeaf;
} else if (la < lb) {
return -1
} else {
return 1;
}
});
for (const label of labels) {
if (label === "parent")
continue;