mirror of
https://github.com/cds-astro/aladin-lite.git
synced 2025-12-12 07:40:26 -08:00
fix some bugs, adapt ui for a new release
This commit is contained in:
@@ -1,7 +1,10 @@
|
|||||||
# Changelogs
|
# Changelogs
|
||||||
|
|
||||||
## Unreleased
|
## 3.4.5-beta
|
||||||
|
|
||||||
* [feat] add `layerChanged` event when a layer is added or removed
|
* [feat] add `layerChanged` event when a layer is added or removed
|
||||||
|
* [deprecate] of `select` event, use `objectsSelected` event instead
|
||||||
|
* [ui] add the ability to switch the tile format to download
|
||||||
|
|
||||||
## 3.4.3-beta
|
## 3.4.3-beta
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ See [A&A 578, A114 (2015)](https://arxiv.org/abs/1505.02291) and [IVOA HiPS Reco
|
|||||||
Aladin Lite is built to be easily embeddable in any web page. It powers astronomical portals like [ESASky](https://sky.esa.int/), [ESO Science Archive portal](http://archive.eso.org/scienceportal/) and [ALMA Portal](https://almascience.eso.org/asax/).
|
Aladin Lite is built to be easily embeddable in any web page. It powers astronomical portals like [ESASky](https://sky.esa.int/), [ESO Science Archive portal](http://archive.eso.org/scienceportal/) and [ALMA Portal](https://almascience.eso.org/asax/).
|
||||||
|
|
||||||
More details on [Aladin Lite documentation page](http://aladin.u-strasbg.fr/AladinLite/doc/).
|
More details on [Aladin Lite documentation page](http://aladin.u-strasbg.fr/AladinLite/doc/).
|
||||||
|
A new [API technical documentation](https://cds-astro.github.io/aladin-lite/) is now available.
|
||||||
|
|
||||||
[](https://github.com/cds-astro/aladin-lite/actions/workflows/test.yml)
|
[](https://github.com/cds-astro/aladin-lite/actions/workflows/test.yml)
|
||||||
[](https://cds-astro.github.io/aladin-lite)
|
[](https://cds-astro.github.io/aladin-lite)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ A.init.then(() => {
|
|||||||
console.log(objs, "are selected");
|
console.log(objs, "are selected");
|
||||||
})
|
})
|
||||||
|
|
||||||
aladin.select();
|
aladin.select('circle');
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"homepage": "https://aladin.u-strasbg.fr/",
|
"homepage": "https://aladin.u-strasbg.fr/",
|
||||||
"name": "aladin-lite",
|
"name": "aladin-lite",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "3.4.4-beta",
|
"version": "3.4.5-beta",
|
||||||
"description": "An astronomical HiPS visualizer in the browser",
|
"description": "An astronomical HiPS visualizer in the browser",
|
||||||
"author": "Thomas Boch and Matthieu Baumann",
|
"author": "Thomas Boch and Matthieu Baumann",
|
||||||
"license": "GPL-3",
|
"license": "GPL-3",
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ use crate::fov::CenteredFoV;
|
|||||||
pub struct ImageParams {
|
pub struct ImageParams {
|
||||||
pub centered_fov: CenteredFoV,
|
pub centered_fov: CenteredFoV,
|
||||||
|
|
||||||
pub min_cut: Option<f32>,
|
pub min_cut: f32,
|
||||||
pub max_cut: Option<f32>,
|
pub max_cut: f32,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,6 +180,52 @@ impl Texture2D {
|
|||||||
Ok(texture)
|
Ok(texture)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_from_raw_bytes<F: ImageFormat>(
|
||||||
|
gl: &WebGlContext,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
tex_params: &'static [(u32, u32)],
|
||||||
|
bytes: Option<&[u8]>,
|
||||||
|
) -> Result<Texture2D, JsValue> {
|
||||||
|
let texture = gl.create_texture();
|
||||||
|
|
||||||
|
gl.bind_texture(WebGlRenderingCtx::TEXTURE_2D, texture.as_ref());
|
||||||
|
|
||||||
|
for (pname, param) in tex_params.iter() {
|
||||||
|
gl.tex_parameteri(WebGlRenderingCtx::TEXTURE_2D, *pname, *param as i32);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||||
|
WebGlRenderingCtx::TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
F::INTERNAL_FORMAT,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
0,
|
||||||
|
F::FORMAT,
|
||||||
|
F::TYPE,
|
||||||
|
bytes,
|
||||||
|
)
|
||||||
|
.expect("Texture 2D");
|
||||||
|
|
||||||
|
let gl = gl.clone();
|
||||||
|
let metadata = Some(Rc::new(RefCell::new(Texture2DMeta {
|
||||||
|
width: width as u32,
|
||||||
|
height: height as u32,
|
||||||
|
internal_format: F::INTERNAL_FORMAT,
|
||||||
|
format: F::FORMAT,
|
||||||
|
type_: F::TYPE,
|
||||||
|
})));
|
||||||
|
|
||||||
|
Ok(Texture2D {
|
||||||
|
texture,
|
||||||
|
|
||||||
|
gl,
|
||||||
|
|
||||||
|
metadata,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_empty_unsized(
|
pub fn create_empty_unsized(
|
||||||
gl: &WebGlContext,
|
gl: &WebGlContext,
|
||||||
tex_params: &'static [(u32, u32)],
|
tex_params: &'static [(u32, u32)],
|
||||||
@@ -591,10 +637,6 @@ impl<'a> Texture2DBoundMut<'a> {
|
|||||||
src_type: u32,
|
src_type: u32,
|
||||||
pixels: Option<&[u8]>,
|
pixels: Option<&[u8]>,
|
||||||
) {
|
) {
|
||||||
//let Texture2DMeta {format, type_, ..} = self.texture_2d.metadata.unwrap_abort();
|
|
||||||
/*self.texture_2d
|
|
||||||
.gl
|
|
||||||
.pixel_storei(WebGlRenderingCtx::UNPACK_ALIGNMENT, 1);*/
|
|
||||||
self.texture_2d
|
self.texture_2d
|
||||||
.gl
|
.gl
|
||||||
.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||||
|
|||||||
@@ -40,14 +40,6 @@ impl WebGlContext {
|
|||||||
let context_options =
|
let context_options =
|
||||||
js_sys::JSON::parse("{\"antialias\":false, \"preserveDrawingBuffer\": true}")?;
|
js_sys::JSON::parse("{\"antialias\":false, \"preserveDrawingBuffer\": true}")?;
|
||||||
|
|
||||||
#[cfg(feature = "webgl1")]
|
|
||||||
let gl = Rc::new(
|
|
||||||
canvas
|
|
||||||
.get_context_with_context_options("webgl", context_options.as_ref())?
|
|
||||||
.unwrap_abort()
|
|
||||||
.dyn_into::<WebGlRenderingCtx>()
|
|
||||||
.unwrap_abort(),
|
|
||||||
);
|
|
||||||
#[cfg(feature = "webgl2")]
|
#[cfg(feature = "webgl2")]
|
||||||
let gl = Rc::new(
|
let gl = Rc::new(
|
||||||
canvas
|
canvas
|
||||||
@@ -56,6 +48,9 @@ impl WebGlContext {
|
|||||||
.dyn_into::<WebGlRenderingCtx>()
|
.dyn_into::<WebGlRenderingCtx>()
|
||||||
.unwrap_abort(),
|
.unwrap_abort(),
|
||||||
);
|
);
|
||||||
|
// https://webgl2fundamentals.org/webgl/lessons/webgl-data-textures.html
|
||||||
|
#[cfg(feature = "webgl2")]
|
||||||
|
gl.pixel_storei(WebGlRenderingCtx::UNPACK_ALIGNMENT, 1);
|
||||||
|
|
||||||
#[cfg(feature = "webgl2")]
|
#[cfg(feature = "webgl2")]
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ pub struct Image {
|
|||||||
|
|
||||||
/// Parameters extracted from the fits
|
/// Parameters extracted from the fits
|
||||||
wcs: WCS,
|
wcs: WCS,
|
||||||
blank: Option<f32>,
|
blank: f32,
|
||||||
scale: Option<f32>,
|
scale: f32,
|
||||||
offset: Option<f32>,
|
offset: f32,
|
||||||
cuts: Option<Range<f32>>,
|
cuts: Range<f32>,
|
||||||
/// The center of the fits
|
/// The center of the fits
|
||||||
centered_fov: CenteredFoV,
|
centered_fov: CenteredFoV,
|
||||||
|
|
||||||
@@ -80,9 +80,9 @@ impl Image {
|
|||||||
gl: &WebGlContext,
|
gl: &WebGlContext,
|
||||||
mut reader: R,
|
mut reader: R,
|
||||||
wcs: WCS,
|
wcs: WCS,
|
||||||
mut scale: Option<f32>,
|
scale: Option<f32>,
|
||||||
mut offset: Option<f32>,
|
offset: Option<f32>,
|
||||||
mut blank: Option<f32>,
|
blank: Option<f32>,
|
||||||
// Coo sys of the view
|
// Coo sys of the view
|
||||||
coo_sys: CooSystem,
|
coo_sys: CooSystem,
|
||||||
) -> Result<Self, JsValue>
|
) -> Result<Self, JsValue>
|
||||||
@@ -101,11 +101,9 @@ impl Image {
|
|||||||
let mut max_tex_size_y = max_tex_size;
|
let mut max_tex_size_y = max_tex_size;
|
||||||
|
|
||||||
// apply bscale to the cuts
|
// apply bscale to the cuts
|
||||||
if F::NUM_CHANNELS == 1 {
|
let offset = offset.unwrap_or(0.0);
|
||||||
offset = offset.or(Some(0.0));
|
let scale = scale.unwrap_or(1.0);
|
||||||
scale = scale.or(Some(1.0));
|
let blank = blank.unwrap_or(std::f32::NAN);
|
||||||
blank = blank.or(Some(std::f32::NAN));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (textures, mut cuts) = if width <= max_tex_size as u64 && height <= max_tex_size as u64
|
let (textures, mut cuts) = if width <= max_tex_size as u64 && height <= max_tex_size as u64
|
||||||
{
|
{
|
||||||
@@ -119,14 +117,15 @@ impl Image {
|
|||||||
let mut buf = vec![0; num_bytes_to_read];
|
let mut buf = vec![0; num_bytes_to_read];
|
||||||
|
|
||||||
let _ = reader
|
let _ = reader
|
||||||
.read_exact(&mut buf[..])
|
.read_exact(&mut buf[..num_bytes_to_read])
|
||||||
.await
|
.await
|
||||||
.map_err(|e| JsValue::from_str(&format!("{:?}", e)))?;
|
.map_err(|e| JsValue::from_str(&format!("{:?}", e)))?;
|
||||||
|
|
||||||
|
// bytes aligned
|
||||||
unsafe {
|
unsafe {
|
||||||
let slice = std::slice::from_raw_parts(
|
let slice = std::slice::from_raw_parts(
|
||||||
buf.as_mut_ptr() as *const <F::P as Pixel>::Item,
|
buf[..].as_ptr() as *const <F::P as Pixel>::Item,
|
||||||
num_bytes_to_read / std::mem::size_of::<<F::P as Pixel>::Item>(),
|
(num_pixels_to_read as usize) * F::NUM_CHANNELS,
|
||||||
);
|
);
|
||||||
|
|
||||||
let cuts = if F::NUM_CHANNELS == 1 {
|
let cuts = if F::NUM_CHANNELS == 1 {
|
||||||
@@ -135,7 +134,7 @@ impl Image {
|
|||||||
.filter_map(|item| {
|
.filter_map(|item| {
|
||||||
let t: f32 =
|
let t: f32 =
|
||||||
<<F::P as Pixel>::Item as al_core::convert::Cast<f32>>::cast(*item);
|
<<F::P as Pixel>::Item as al_core::convert::Cast<f32>>::cast(*item);
|
||||||
if t.is_nan() || t == blank.unwrap() {
|
if t.is_nan() || t == blank {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(t)
|
Some(t)
|
||||||
@@ -143,22 +142,20 @@ impl Image {
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let cuts = cuts::first_and_last_percent(&mut samples, 1, 99);
|
cuts::first_and_last_percent(&mut samples, 1, 99)
|
||||||
Some(cuts)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
0.0..1.0
|
||||||
};
|
};
|
||||||
|
|
||||||
(
|
let texture = Texture2D::create_from_raw_pixels::<F>(
|
||||||
vec![Texture2D::create_from_raw_pixels::<F>(
|
gl,
|
||||||
gl,
|
width as i32,
|
||||||
width as i32,
|
height as i32,
|
||||||
height as i32,
|
TEX_PARAMS,
|
||||||
TEX_PARAMS,
|
Some(slice),
|
||||||
Some(slice),
|
)?;
|
||||||
)?],
|
|
||||||
cuts,
|
(vec![texture], cuts)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
subdivide_texture::crop_image::<F, R>(
|
subdivide_texture::crop_image::<F, R>(
|
||||||
@@ -172,12 +169,10 @@ impl Image {
|
|||||||
.await?
|
.await?
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(cuts) = cuts.as_mut() {
|
let start = cuts.start * scale + offset;
|
||||||
let start = cuts.start * scale.unwrap() + offset.unwrap();
|
let end = cuts.end * scale + offset;
|
||||||
let end = cuts.end * scale.unwrap() + offset.unwrap();
|
|
||||||
|
|
||||||
*cuts = start..end;
|
cuts = start..end;
|
||||||
}
|
|
||||||
|
|
||||||
let num_indices = vec![];
|
let num_indices = vec![];
|
||||||
let indices = vec![];
|
let indices = vec![];
|
||||||
@@ -293,7 +288,7 @@ impl Image {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_cuts(&self) -> &Option<Range<f32>> {
|
pub fn get_cuts(&self) -> &Range<f32> {
|
||||||
&self.cuts
|
&self.cuts
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -712,9 +707,9 @@ impl Image {
|
|||||||
.attach_uniforms_with_params_from(color, colormaps)
|
.attach_uniforms_with_params_from(color, colormaps)
|
||||||
.attach_uniform("opacity", opacity)
|
.attach_uniform("opacity", opacity)
|
||||||
.attach_uniform("tex", texture)
|
.attach_uniform("tex", texture)
|
||||||
.attach_uniform("scale", &self.scale.unwrap_or(1.0))
|
.attach_uniform("scale", &self.scale)
|
||||||
.attach_uniform("offset", &self.offset.unwrap_or(0.0))
|
.attach_uniform("offset", &self.offset)
|
||||||
.attach_uniform("blank", &self.blank.unwrap_or(std::f32::NAN))
|
.attach_uniform("blank", &self.blank)
|
||||||
.bind_vertex_array_object_ref(&self.vao)
|
.bind_vertex_array_object_ref(&self.vao)
|
||||||
.draw_elements_with_i32(
|
.draw_elements_with_i32(
|
||||||
WebGl2RenderingContext::TRIANGLES,
|
WebGl2RenderingContext::TRIANGLES,
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ pub async fn crop_image<'a, F, R>(
|
|||||||
height: u64,
|
height: u64,
|
||||||
mut reader: R,
|
mut reader: R,
|
||||||
max_tex_size: u64,
|
max_tex_size: u64,
|
||||||
blank: Option<f32>,
|
blank: f32,
|
||||||
) -> Result<(Vec<Texture2D>, Option<Range<f32>>), JsValue>
|
) -> Result<(Vec<Texture2D>, Range<f32>), JsValue>
|
||||||
where
|
where
|
||||||
F: ImageFormat,
|
F: ImageFormat,
|
||||||
R: AsyncReadExt + Unpin,
|
R: AsyncReadExt + Unpin,
|
||||||
@@ -35,12 +35,11 @@ where
|
|||||||
];
|
];
|
||||||
|
|
||||||
for _ in 0..num_textures {
|
for _ in 0..num_textures {
|
||||||
tex_chunks.push(Texture2D::create_from_raw_pixels::<F>(
|
tex_chunks.push(Texture2D::create_empty_with_format::<F>(
|
||||||
gl,
|
gl,
|
||||||
max_tex_size as i32,
|
max_tex_size as i32,
|
||||||
max_tex_size as i32,
|
max_tex_size as i32,
|
||||||
TEX_PARAMS,
|
TEX_PARAMS,
|
||||||
None,
|
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,11 +100,7 @@ where
|
|||||||
f32,
|
f32,
|
||||||
>>::cast(slice[j]);
|
>>::cast(slice[j]);
|
||||||
if !sj.is_nan() {
|
if !sj.is_nan() {
|
||||||
if let Some(b) = blank {
|
if blank != sj {
|
||||||
if b != sj {
|
|
||||||
samples.push(sj);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
samples.push(sj);
|
samples.push(sj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,9 +131,9 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let cuts = if F::NUM_CHANNELS == 1 {
|
let cuts = if F::NUM_CHANNELS == 1 {
|
||||||
Some(cuts::first_and_last_percent(&mut samples, 1, 99))
|
cuts::first_and_last_percent(&mut samples, 1, 99)
|
||||||
} else {
|
} else {
|
||||||
None
|
0.0..1.0
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((tex_chunks, cuts))
|
Ok((tex_chunks, cuts))
|
||||||
|
|||||||
@@ -106,16 +106,13 @@ pub struct ImageLayer {
|
|||||||
|
|
||||||
impl ImageLayer {
|
impl ImageLayer {
|
||||||
pub fn get_params(&self) -> ImageParams {
|
pub fn get_params(&self) -> ImageParams {
|
||||||
let (min_cut, max_cut) = self.images[0]
|
let cuts = self.images[0].get_cuts();
|
||||||
.get_cuts()
|
|
||||||
.as_ref()
|
|
||||||
.map_or((None, None), |r| (Some(r.start), Some(r.end)));
|
|
||||||
|
|
||||||
let centered_fov = self.images[0].get_centered_fov().clone();
|
let centered_fov = self.images[0].get_centered_fov().clone();
|
||||||
ImageParams {
|
ImageParams {
|
||||||
centered_fov,
|
centered_fov,
|
||||||
min_cut,
|
min_cut: cuts.start,
|
||||||
max_cut,
|
max_cut: cuts.end,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/js/A.js
32
src/js/A.js
@@ -134,7 +134,37 @@ A.imageHiPS = function (id, options) {
|
|||||||
* @param {ImageOptions} [options] - Options describing the fits file. An url is mandatory
|
* @param {ImageOptions} [options] - Options describing the fits file. An url is mandatory
|
||||||
* @returns {Image} - A HiPS image object
|
* @returns {Image} - A HiPS image object
|
||||||
* @example
|
* @example
|
||||||
* const sourceObj = A.source(180.0, 30.0, data, options);
|
* aladin.setOverlayImageLayer(A.image(
|
||||||
|
* "https://nova.astrometry.net/image/25038473?filename=M61.jpg",
|
||||||
|
* {
|
||||||
|
* name: "M61",
|
||||||
|
* imgFormat: 'jpeg',
|
||||||
|
* wcs: {
|
||||||
|
* NAXIS: 0, // Minimal header
|
||||||
|
* CTYPE1: 'RA---TAN', // TAN (gnomic) projection
|
||||||
|
* CTYPE2: 'DEC--TAN', // TAN (gnomic) projection
|
||||||
|
* EQUINOX: 2000.0, // Equatorial coordinates definition (yr)
|
||||||
|
* LONPOLE: 180.0, // no comment
|
||||||
|
* LATPOLE: 0.0, // no comment
|
||||||
|
* CRVAL1: 185.445488837, // RA of reference point
|
||||||
|
* CRVAL2: 4.47896032431, // DEC of reference point
|
||||||
|
* CRPIX1: 588.995094299, // X reference pixel
|
||||||
|
* CRPIX2: 308.307905197, // Y reference pixel
|
||||||
|
* CUNIT1: 'deg', // X pixel scale units
|
||||||
|
* CUNIT2: 'deg', // Y pixel scale units
|
||||||
|
* CD1_1: -0.000223666022989, // Transformation matrix
|
||||||
|
* CD1_2: 0.000296578064584, // no comment
|
||||||
|
* CD2_1: -0.000296427555509, // no comment
|
||||||
|
* CD2_2: -0.000223774308964, // no comment
|
||||||
|
* NAXIS1: 1080, // Image width, in pixels.
|
||||||
|
* NAXIS2: 705 // Image height, in pixels.
|
||||||
|
* },
|
||||||
|
* successCallback: (ra, dec, fov, image) => {
|
||||||
|
* aladin.gotoRaDec(ra, dec);
|
||||||
|
* aladin.setFoV(fov * 5)
|
||||||
|
* }
|
||||||
|
* },
|
||||||
|
* ));
|
||||||
*/
|
*/
|
||||||
A.image = function (url, options) {
|
A.image = function (url, options) {
|
||||||
return Aladin.createImageFITS(url, options, options.successCallback, options.errorCallback);
|
return Aladin.createImageFITS(url, options, options.successCallback, options.errorCallback);
|
||||||
|
|||||||
@@ -232,7 +232,9 @@ import { Polyline } from "./shapes/Polyline";
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {string} ListenerCallback
|
* @typedef {string} ListenerCallback
|
||||||
* String with possible values: 'select',
|
* String with possible values:
|
||||||
|
* 'select' (deprecated, use objectsSelected instead),
|
||||||
|
* 'objectsSelected',
|
||||||
'objectClicked',
|
'objectClicked',
|
||||||
'objectHovered',
|
'objectHovered',
|
||||||
'objectHoveredStop',
|
'objectHoveredStop',
|
||||||
@@ -1949,7 +1951,8 @@ export let Aladin = (function () {
|
|||||||
|
|
||||||
// Select corresponds to rectangular selection
|
// Select corresponds to rectangular selection
|
||||||
Aladin.AVAILABLE_CALLBACKS = [
|
Aladin.AVAILABLE_CALLBACKS = [
|
||||||
"select",
|
"select", // deprecated, use objectsSelected instead
|
||||||
|
"objectsSelected",
|
||||||
|
|
||||||
"objectClicked",
|
"objectClicked",
|
||||||
"objectHovered",
|
"objectHovered",
|
||||||
@@ -2019,12 +2022,16 @@ aladin.on('objectClicked', function(object, xyMouseCoords) {
|
|||||||
$('#infoDiv').html(msg);
|
$('#infoDiv').html(msg);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
aladin.on("objectsSelected", (objs) => {
|
||||||
|
console.log("objs", objs)
|
||||||
|
})
|
||||||
|
|
||||||
aladin.on("positionChanged", ({ra, dec}) => {
|
aladin.on("positionChanged", ({ra, dec}) => {
|
||||||
console.log("positionChanged", ra, dec)
|
console.log("positionChanged", ra, dec)
|
||||||
})
|
})
|
||||||
|
|
||||||
aladin.on("layerChanged", (imageHips, layerName, state) => {
|
aladin.on("layerChanged", (layer, layerName, state) => {
|
||||||
console.log("positionChanged", imageHips, layerName, state)
|
console.log("layerChanged", layer, layerName, state)
|
||||||
})
|
})
|
||||||
*/
|
*/
|
||||||
Aladin.prototype.on = function (what, myFunction) {
|
Aladin.prototype.on = function (what, myFunction) {
|
||||||
|
|||||||
@@ -46,8 +46,13 @@
|
|||||||
this.reversed = true;
|
this.reversed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.minCut = (options && options.minCut) || undefined;
|
if (options && Number.isFinite(options.minCut)) {
|
||||||
this.maxCut = (options && options.maxCut) || undefined;
|
this.minCut = options.minCut;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options && Number.isFinite(options.maxCut)) {
|
||||||
|
this.maxCut = options.maxCut;
|
||||||
|
}
|
||||||
|
|
||||||
this.additiveBlending = options && options.additive;
|
this.additiveBlending = options && options.additive;
|
||||||
if (this.additiveBlending === undefined) {
|
if (this.additiveBlending === undefined) {
|
||||||
|
|||||||
@@ -102,9 +102,11 @@ export class CircleSelect extends FSM {
|
|||||||
|
|
||||||
// execute general callback
|
// execute general callback
|
||||||
if (view.aladin.callbacksByEventName) {
|
if (view.aladin.callbacksByEventName) {
|
||||||
var callback = view.aladin.callbacksByEventName['select'];
|
var callback = view.aladin.callbacksByEventName['objectsSelected'] || view.aladin.callbacksByEventName['select'];
|
||||||
|
|
||||||
if (typeof callback === "function") {
|
if (typeof callback === "function") {
|
||||||
let objList = Selector.getObjects(s, view);
|
let objList = Selector.getObjects(s, view);
|
||||||
|
view.selectObjects(objList);
|
||||||
callback(objList);
|
callback(objList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ export class PolySelect extends FSM {
|
|||||||
|
|
||||||
// execute general callback
|
// execute general callback
|
||||||
if (view.aladin.callbacksByEventName) {
|
if (view.aladin.callbacksByEventName) {
|
||||||
var callback = view.aladin.callbacksByEventName['select'];
|
var callback = view.aladin.callbacksByEventName['objectsSelected'] || view.aladin.callbacksByEventName['select'];
|
||||||
if (typeof callback === "function") {
|
if (typeof callback === "function") {
|
||||||
console.warn('polygon selection is not fully implemented, PolySelect.contains is needed for finding sources inside a polygon')
|
console.warn('polygon selection is not fully implemented, PolySelect.contains is needed for finding sources inside a polygon')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,9 +107,10 @@ export class RectSelect extends FSM {
|
|||||||
|
|
||||||
// execute general callback
|
// execute general callback
|
||||||
if (view.aladin.callbacksByEventName) {
|
if (view.aladin.callbacksByEventName) {
|
||||||
var callback = view.aladin.callbacksByEventName['select'];
|
var callback = view.aladin.callbacksByEventName['objectsSelected'] || view.aladin.callbacksByEventName['select'];
|
||||||
if (typeof callback === "function") {
|
if (typeof callback === "function") {
|
||||||
let objList = Selector.getObjects(s, view);
|
let objList = Selector.getObjects(s, view);
|
||||||
|
view.selectObjects(objList);
|
||||||
callback(objList);
|
callback(objList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* Aladin Lite project
|
* Aladin Lite project
|
||||||
*
|
*
|
||||||
* File ImageFITS
|
* File Image
|
||||||
*
|
*
|
||||||
* Authors: Matthieu Baumann [CDS]
|
* Authors: Matthieu Baumann [CDS]
|
||||||
*
|
*
|
||||||
@@ -40,8 +40,8 @@ import { Aladin } from "./Aladin.js";
|
|||||||
* @property {string} [colormap="native"] - The colormap configuration for the survey or image.
|
* @property {string} [colormap="native"] - The colormap configuration for the survey or image.
|
||||||
* @property {string} [stretch="linear"] - The stretch configuration for the survey or image.
|
* @property {string} [stretch="linear"] - The stretch configuration for the survey or image.
|
||||||
* @property {boolean} [reversed=false] - If true, the colormap is reversed; otherwise, it is not reversed.
|
* @property {boolean} [reversed=false] - If true, the colormap is reversed; otherwise, it is not reversed.
|
||||||
* @property {number} [minCut] - The minimum cut value for the color configuration. If not given, 0.0 for JPEG/PNG surveys, the value of the property file for FITS surveys
|
* @property {number} [minCut=0.0] - The minimum cut value for the color configuration. If not given, 0.0 is chosen
|
||||||
* @property {number} [maxCut] - The maximum cut value for the color configuration. If not given, 1.0 for JPEG/PNG surveys, the value of the property file for FITS surveys
|
* @property {number} [maxCut=1.0] - The maximum cut value for the color configuration. If not given, 1.0 is chosen
|
||||||
* @property {boolean} [additive=false] - If true, additive blending is applied; otherwise, it is not applied.
|
* @property {boolean} [additive=false] - If true, additive blending is applied; otherwise, it is not applied.
|
||||||
* @property {number} [gamma=1.0] - The gamma correction value for the color configuration.
|
* @property {number} [gamma=1.0] - The gamma correction value for the color configuration.
|
||||||
* @property {number} [saturation=0.0] - The saturation value for the color configuration.
|
* @property {number} [saturation=0.0] - The saturation value for the color configuration.
|
||||||
@@ -115,10 +115,12 @@ export let Image = (function () {
|
|||||||
/*if (options) {
|
/*if (options) {
|
||||||
options.stretch = options.stretch || "asinh";
|
options.stretch = options.stretch || "asinh";
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
this.colorCfg = new ColorCfg(options);
|
this.colorCfg = new ColorCfg(options);
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
|
||||||
let self = this;
|
let self = this;
|
||||||
|
|
||||||
this.query = Promise.resolve(self);
|
this.query = Promise.resolve(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,9 +312,12 @@ export let Image = (function () {
|
|||||||
self.setView(self.view);
|
self.setView(self.view);
|
||||||
|
|
||||||
// Set the automatic computed cuts
|
// Set the automatic computed cuts
|
||||||
|
let [minCut, maxCut] = self.getCuts();
|
||||||
|
minCut = minCut || imageParams.min_cut;
|
||||||
|
maxCut = maxCut || imageParams.max_cut;
|
||||||
self.setCuts(
|
self.setCuts(
|
||||||
imageParams.min_cut,
|
minCut,
|
||||||
imageParams.max_cut
|
maxCut
|
||||||
);
|
);
|
||||||
|
|
||||||
self.ra = imageParams.centered_fov.ra;
|
self.ra = imageParams.centered_fov.ra;
|
||||||
|
|||||||
@@ -184,6 +184,9 @@ export let ImageHiPS = (function () {
|
|||||||
? false
|
? false
|
||||||
: options.longitudeReversed;
|
: options.longitudeReversed;
|
||||||
this.imgFormat = options.imgFormat;
|
this.imgFormat = options.imgFormat;
|
||||||
|
this.formats = options.formats;
|
||||||
|
this.defaultFitsMinCut = options.defaultFitsMinCut;
|
||||||
|
this.defaultFitsMaxCut = options.defaultFitsMaxCut;
|
||||||
this.numBitsPerPixel = options.numBitsPerPixel;
|
this.numBitsPerPixel = options.numBitsPerPixel;
|
||||||
this.creatorDid = options.creatorDid;
|
this.creatorDid = options.creatorDid;
|
||||||
this.errorCallback = options.errorCallback;
|
this.errorCallback = options.errorCallback;
|
||||||
@@ -331,8 +334,8 @@ export let ImageHiPS = (function () {
|
|||||||
|
|
||||||
// Cutouts
|
// Cutouts
|
||||||
const cutoutFromProperties = PropertyParser.cutouts(properties);
|
const cutoutFromProperties = PropertyParser.cutouts(properties);
|
||||||
self.minCut = cutoutFromProperties[0];
|
self.defaultFitsMinCut = cutoutFromProperties[0];
|
||||||
self.maxCut = cutoutFromProperties[1];
|
self.defaultFitsMaxCut = cutoutFromProperties[1];
|
||||||
|
|
||||||
// Bitpix
|
// Bitpix
|
||||||
self.numBitsPerPixel =
|
self.numBitsPerPixel =
|
||||||
@@ -418,8 +421,8 @@ export let ImageHiPS = (function () {
|
|||||||
let minCut, maxCut;
|
let minCut, maxCut;
|
||||||
if (self.imgFormat === "fits") {
|
if (self.imgFormat === "fits") {
|
||||||
// Take into account the default cuts given by the property file (this is true especially for FITS HiPSes)
|
// Take into account the default cuts given by the property file (this is true especially for FITS HiPSes)
|
||||||
minCut = self.colorCfg.minCut || self.minCut || 0.0;
|
minCut = self.colorCfg.minCut || self.defaultFitsMinCut || 0.0;
|
||||||
maxCut = self.colorCfg.maxCut || self.maxCut || 1.0;
|
maxCut = self.colorCfg.maxCut || self.defaultFitsMaxCut || 1.0;
|
||||||
} else {
|
} else {
|
||||||
minCut = self.colorCfg.minCut || 0.0;
|
minCut = self.colorCfg.minCut || 0.0;
|
||||||
maxCut = self.colorCfg.maxCut || 1.0;
|
maxCut = self.colorCfg.maxCut || 1.0;
|
||||||
@@ -456,7 +459,6 @@ export let ImageHiPS = (function () {
|
|||||||
|
|
||||||
ImageHiPS.prototype._saveInCache = function () {
|
ImageHiPS.prototype._saveInCache = function () {
|
||||||
let self = this;
|
let self = this;
|
||||||
|
|
||||||
let colorOpt = Object.fromEntries(Object.entries(this.colorCfg));
|
let colorOpt = Object.fromEntries(Object.entries(this.colorCfg));
|
||||||
let surveyOpt = {
|
let surveyOpt = {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
@@ -467,9 +469,12 @@ export let ImageHiPS = (function () {
|
|||||||
cooFrame: self.cooFrame,
|
cooFrame: self.cooFrame,
|
||||||
maxOrder: self.maxOrder,
|
maxOrder: self.maxOrder,
|
||||||
tileSize: self.tileSize,
|
tileSize: self.tileSize,
|
||||||
|
formats: self.formats,
|
||||||
imgFormat: self.imgFormat,
|
imgFormat: self.imgFormat,
|
||||||
successCallback: self.successCallback,
|
successCallback: self.successCallback,
|
||||||
errorCallback: self.errorCallback,
|
errorCallback: self.errorCallback,
|
||||||
|
defaultFitsMinCut: self.defaultFitsMinCut,
|
||||||
|
defaultFitsMaxCut: self.defaultFitsMaxCut,
|
||||||
...colorOpt,
|
...colorOpt,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -577,9 +582,9 @@ export let ImageHiPS = (function () {
|
|||||||
self.imgFormat === "jpeg") &&
|
self.imgFormat === "jpeg") &&
|
||||||
imgFormat === "fits"
|
imgFormat === "fits"
|
||||||
) {
|
) {
|
||||||
if (self.minCut && self.maxCut) {
|
if (Number.isFinite(self.defaultFitsMinCut) && Number.isFinite(self.defaultFitsMaxCut)) {
|
||||||
// reset cuts to those given from the properties
|
// reset cuts to those given from the properties
|
||||||
self.setCuts(self.minCut, self.maxCut);
|
self.setCuts(self.defaultFitsMinCut, self.defaultFitsMaxCut);
|
||||||
}
|
}
|
||||||
// Switch from fits to png/webp/jpeg
|
// Switch from fits to png/webp/jpeg
|
||||||
} else if (self.imgFormat === "fits") {
|
} else if (self.imgFormat === "fits") {
|
||||||
|
|||||||
@@ -1668,16 +1668,16 @@ export let View = (function () {
|
|||||||
const layerName = imageLayer.layer;
|
const layerName = imageLayer.layer;
|
||||||
// Check whether this layer already exist
|
// Check whether this layer already exist
|
||||||
const idxOverlayLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == layerName);
|
const idxOverlayLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == layerName);
|
||||||
|
let alreadyPresentImageLayer;
|
||||||
if (idxOverlayLayer == -1) {
|
if (idxOverlayLayer == -1) {
|
||||||
// it does not exist so we add it to the stack
|
// it does not exist so we add it to the stack
|
||||||
this.overlayLayers.push(layerName);
|
this.overlayLayers.push(layerName);
|
||||||
} else {
|
} else {
|
||||||
// it exists
|
// it exists
|
||||||
let alreadyPresentImageLayer = this.imageLayers.get(layerName);
|
alreadyPresentImageLayer = this.imageLayers.get(layerName);
|
||||||
alreadyPresentImageLayer.added = false;
|
alreadyPresentImageLayer.added = false;
|
||||||
|
|
||||||
// Notify that this image layer has been replaced by the wasm part
|
this.imageLayers.delete(layerName);
|
||||||
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: alreadyPresentImageLayer });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
imageLayer.added = true;
|
imageLayer.added = true;
|
||||||
@@ -1688,6 +1688,11 @@ export let View = (function () {
|
|||||||
if (idxOverlayLayer == -1) {
|
if (idxOverlayLayer == -1) {
|
||||||
this.selectLayer(layerName);
|
this.selectLayer(layerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify that this image layer has been replaced by the wasm part
|
||||||
|
if (alreadyPresentImageLayer) {
|
||||||
|
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: alreadyPresentImageLayer });
|
||||||
|
}
|
||||||
|
|
||||||
ALEvent.HIPS_LAYER_ADDED.dispatchedTo(this.aladinDiv, { layer: imageLayer });
|
ALEvent.HIPS_LAYER_ADDED.dispatchedTo(this.aladinDiv, { layer: imageLayer });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,12 +99,12 @@ import { ColorCfg } from "../../ColorCfg.js";
|
|||||||
// Define the contents
|
// Define the contents
|
||||||
|
|
||||||
let opacitySettingsContent = new Form({
|
let opacitySettingsContent = new Form({
|
||||||
|
type: 'group',
|
||||||
subInputs: [
|
subInputs: [
|
||||||
{
|
{
|
||||||
label: 'opacity:',
|
label: 'opacity:',
|
||||||
name: 'opacity',
|
|
||||||
tooltip: {content: 1.0, position: {direction: 'bottom'}},
|
tooltip: {content: 1.0, position: {direction: 'bottom'}},
|
||||||
name: 'opacitySlider',
|
name: 'opacity',
|
||||||
type: 'range',
|
type: 'range',
|
||||||
min: 0.0,
|
min: 0.0,
|
||||||
max: 1.0,
|
max: 1.0,
|
||||||
@@ -229,7 +229,6 @@ import { ColorCfg } from "../../ColorCfg.js";
|
|||||||
options: ColorCfg.COLORMAPS,
|
options: ColorCfg.COLORMAPS,
|
||||||
change: (e, cmapSelector) => {
|
change: (e, cmapSelector) => {
|
||||||
self.options.layer.setColormap(e.target.value)
|
self.options.layer.setColormap(e.target.value)
|
||||||
//cmapSelector.update({value: e.target.value})
|
|
||||||
},
|
},
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
@@ -271,14 +270,20 @@ import { ColorCfg } from "../../ColorCfg.js";
|
|||||||
}
|
}
|
||||||
fmtInput.value = hips.imgFormat;
|
fmtInput.value = hips.imgFormat;
|
||||||
|
|
||||||
|
this.colorSettingsContent.set('cmap', colormap);
|
||||||
this.colorSettingsContent.set('cmap', colormap)
|
this.opacitySettingsContent.set('opacity', hips.getOpacity());
|
||||||
this.opacitySettingsContent.set('opacity', hips.getOpacity())
|
this.luminositySettingsContent.set('brightness', colorCfg.getBrightness());
|
||||||
|
this.luminositySettingsContent.set('contrast', colorCfg.getContrast());
|
||||||
|
this.luminositySettingsContent.set('saturation', colorCfg.getSaturation());
|
||||||
}
|
}
|
||||||
|
|
||||||
super.update(options)
|
super.update(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_updateCursors() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
_show(options) {
|
_show(options) {
|
||||||
/*if (this.selector) {
|
/*if (this.selector) {
|
||||||
this.selector._show();
|
this.selector._show();
|
||||||
@@ -300,13 +305,24 @@ import { ColorCfg } from "../../ColorCfg.js";
|
|||||||
let colorCfg = hips.getColorCfg();
|
let colorCfg = hips.getColorCfg();
|
||||||
let stretch = colorCfg.stretch;
|
let stretch = colorCfg.stretch;
|
||||||
let colormap = colorCfg.getColormap();
|
let colormap = colorCfg.getColormap();
|
||||||
|
|
||||||
let [minCut, maxCut] = colorCfg.getCuts();
|
let [minCut, maxCut] = colorCfg.getCuts();
|
||||||
this.pixelSettingsContent.set('mincut', +minCut.toFixed(4))
|
this.pixelSettingsContent.set('mincut', +minCut.toFixed(4))
|
||||||
this.pixelSettingsContent.set('maxcut', +maxCut.toFixed(4))
|
this.pixelSettingsContent.set('maxcut', +maxCut.toFixed(4))
|
||||||
this.pixelSettingsContent.set('stretch', stretch)
|
this.pixelSettingsContent.set('stretch', stretch)
|
||||||
this.colorSettingsContent.set('cmap', colormap)
|
let fmtInput = this.pixelSettingsContent.getInput('fmt')
|
||||||
this.opacitySettingsContent.set('opacity', hips.getOpacity())
|
|
||||||
|
fmtInput.innerHTML = '';
|
||||||
|
for (const option of hips.formats) {
|
||||||
|
fmtInput.innerHTML += "<option>" + option + "</option>";
|
||||||
|
}
|
||||||
|
fmtInput.value = hips.imgFormat;
|
||||||
|
|
||||||
|
this.colorSettingsContent.set('cmap', colormap);
|
||||||
|
this.opacitySettingsContent.set('opacity', hips.getOpacity());
|
||||||
|
this.luminositySettingsContent.set('brightness', colorCfg.getBrightness());
|
||||||
|
this.luminositySettingsContent.set('contrast', colorCfg.getContrast());
|
||||||
|
this.luminositySettingsContent.set('saturation', colorCfg.getSaturation());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -646,13 +646,6 @@ export class OverlayStackBox extends Box {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
ALEvent.HIPS_LAYER_RENAMED.listenedBy(
|
|
||||||
this.aladin.aladinDiv,
|
|
||||||
function (e) {
|
|
||||||
updateOverlayList();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
ALEvent.HIPS_LAYER_SWAP.listenedBy(this.aladin.aladinDiv, function (e) {
|
ALEvent.HIPS_LAYER_SWAP.listenedBy(this.aladin.aladinDiv, function (e) {
|
||||||
updateOverlayList();
|
updateOverlayList();
|
||||||
});
|
});
|
||||||
@@ -912,6 +905,7 @@ export class OverlayStackBox extends Box {
|
|||||||
hipsOptions.sort()
|
hipsOptions.sort()
|
||||||
|
|
||||||
for (const layer of layers) {
|
for (const layer of layers) {
|
||||||
|
|
||||||
let HiPSSelector = Input.select({
|
let HiPSSelector = Input.select({
|
||||||
value: layer.name,
|
value: layer.name,
|
||||||
options: hipsOptions,
|
options: hipsOptions,
|
||||||
@@ -974,9 +968,11 @@ export class OverlayStackBox extends Box {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let settingsBox = new HiPSSettingsBox(self.aladin);
|
let settingsBox = new HiPSSettingsBox(self.aladin);
|
||||||
|
|
||||||
settingsBox.update({ layer });
|
settingsBox.update({ layer });
|
||||||
settingsBox._hide();
|
settingsBox._hide();
|
||||||
|
|
||||||
|
|
||||||
let settingsBtn = new TogglerActionButton({
|
let settingsBtn = new TogglerActionButton({
|
||||||
icon: { url: settingsIconUrl, monochrome: true },
|
icon: { url: settingsIconUrl, monochrome: true },
|
||||||
size: "small",
|
size: "small",
|
||||||
@@ -986,6 +982,15 @@ export class OverlayStackBox extends Box {
|
|||||||
},
|
},
|
||||||
toggled: false,
|
toggled: false,
|
||||||
actionOn: (e) => {
|
actionOn: (e) => {
|
||||||
|
// toggle off the other settings if opened
|
||||||
|
for (var l in self.HiPSui) {
|
||||||
|
let ui = self.HiPSui[l]
|
||||||
|
|
||||||
|
if (l != layer.layer) {
|
||||||
|
ui.settingsBtn.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
settingsBox._show({
|
settingsBox._show({
|
||||||
position: {
|
position: {
|
||||||
nextTo: settingsBtn,
|
nextTo: settingsBtn,
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ export class TogglerActionButton extends ActionButton {
|
|||||||
self = this;
|
self = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
if (this.toggled) {
|
||||||
|
this.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
toggle(o) {
|
toggle(o) {
|
||||||
this.toggled = !this.toggled;
|
this.toggled = !this.toggled;
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ export class SettingsCtxMenu extends ContextMenu {
|
|||||||
value: reticleColor.toHex(),
|
value: reticleColor.toHex(),
|
||||||
name: 'reticleColor',
|
name: 'reticleColor',
|
||||||
change(e) {
|
change(e) {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
let hex = e.target.value;
|
let hex = e.target.value;
|
||||||
let reticle = aladin.getReticle();
|
let reticle = aladin.getReticle();
|
||||||
reticle.update({color: hex})
|
reticle.update({color: hex})
|
||||||
@@ -170,7 +173,7 @@ export class SettingsCtxMenu extends ContextMenu {
|
|||||||
{
|
{
|
||||||
label: {
|
label: {
|
||||||
content: [self.reticleColorInput, 'Color']
|
content: [self.reticleColorInput, 'Color']
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: Layout.horizontal(['Size', sliderReticleSize]),
|
label: Layout.horizontal(['Size', sliderReticleSize]),
|
||||||
|
|||||||
Reference in New Issue
Block a user