diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 57160ccb..867f43c5 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -21,6 +21,7 @@ jobs: run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y source "$HOME/.cargo/env" + rustup default nightly - name: "Install wasm-pack" run: | curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -y diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5e9c092e..cc5afb27 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,7 @@ jobs: run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y source "$HOME/.cargo/env" + rustup default nightly - name: "Install wasm-pack" run: | curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -y diff --git a/assets/icons/minus.svg b/assets/icons/minus.svg new file mode 100644 index 00000000..bc96d610 --- /dev/null +++ b/assets/icons/minus.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/icons/plus.svg b/assets/icons/plus.svg new file mode 100644 index 00000000..637f5d9f --- /dev/null +++ b/assets/icons/plus.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/examples/al-adass2022.html b/examples/al-adass2022.html index 329ff98d..ad96b808 100644 --- a/examples/al-adass2022.html +++ b/examples/al-adass2022.html @@ -10,7 +10,7 @@ import A from '../src/js/A.js'; let aladin; A.init.then(() => { - aladin = A.aladin('#aladin-lite-div', {survey: ["P/PanSTARRS/DR1/color-i-r-g"], showReticle: false, gridOptions: {opacity: 0.5, color: 'rgba(255, 0, 0)'}, projection: "AIT", cooFrame: 'galactic', target: "93.2721132 -20.9942421", fov: 1000, showGotoControl: false, showFrame: false, fullScreen: true, showLayersControl: true, showCooGrid: true, showCooGridControl: false}); + aladin = A.aladin('#aladin-lite-div', {survey: ["P/PanSTARRS/DR1/color-i-r-g"], showReticle: false, gridOptions: {opacity: 0.5, color: 'rgba(255, 0, 0)'}, projection: "AIT", cooFrame: 'icrs', target: "stephan's quintet", fov: 1000, showGotoControl: false, showFrame: false, fullScreen: true, showLayersControl: true, showCooGrid: true, showCooGridControl: false}); const chft = aladin.createImageSurvey('CFHT', "CFHT deep view of NGC7331 and Stephan's quintet u+g+r", "https://cds.unistra.fr/~derriere/PR_HiPS/2022_Duc/", null, null, {imgFormat: 'png'}); const nircamJWST = aladin.createImageSurvey('Nircam', "Stephans Quintet NIRCam+MIRI", "http://alasky.cds.unistra.fr/JWST/CDS_P_JWST_Stephans-Quintet_NIRCam+MIRI/", null, null, {imgFormat: 'png', colormap: "viridis"}); diff --git a/examples/al-easy-access-simbad-ned.html b/examples/al-easy-access-simbad-ned.html index 0094f95d..6bfdea40 100644 --- a/examples/al-easy-access-simbad-ned.html +++ b/examples/al-easy-access-simbad-ned.html @@ -22,12 +22,8 @@ showStatusBar: { position: { bottom: 0, - left: 50 + left: '7rem' }, - cssStyle: { - border: '1px solid white', - color: 'yellow', - } }, showStackLayerControl: true, samp: true, diff --git a/src/css/aladin.css b/src/css/aladin.css index 404c839e..651fb375 100644 --- a/src/css/aladin.css +++ b/src/css/aladin.css @@ -317,7 +317,7 @@ word-wrap:break-word; } .aladin-box-night { - padding: '2px'; + padding: 0.5rem; background-color: #000; color: white; } @@ -623,15 +623,15 @@ canvas { } .aladin-btn.small-sized-icon { - padding: 0px; - width: 16px; - height: 16px; + padding: 0; + width: 1.5rem; + height: 1.5rem; } .aladin-btn.medium-sized-icon { - padding: 0px; - width: 24px; - height: 24px; + padding: 0; + width: 2rem; + height: 2rem; } .aladin-btn.svg-icon { @@ -675,17 +675,16 @@ canvas { .aladin-fov, .aladin-location, .aladin-cooFrame { font-family: monospace; - - border: 1px solid white; - border-radius: 3px; + height: 1.5rem; background-color: rgba(0, 0, 0, 0.5); color: white; + + font-size: 1rem; + text-shadow: 0px 0px 2px #000; } -.aladin-zoom-controls { -} .aladin-cancelBtn { background-color: #ca4242; @@ -1019,11 +1018,11 @@ canvas { border-top: 1px solid #d2d2d2; margin-top: -1px; } - +/* .aladin-context-menu .aladin-context-menu-item:hover > .aladin-context-sub-menu { display: block; } - +*/ .aladin-context-menu.left .aladin-context-sub-menu { left: 0; transform: translateX(-100%); @@ -1061,10 +1060,12 @@ canvas { -moz-appearance: none; -webkit-appearance: none; outline-style: none; - padding: 2px; + padding: 0.3rem; border: none; border-radius: 5px; background-color: white; + font-family: monospace; + font-size: 1rem; } .aladin-input-text::placeholder { @@ -1074,11 +1075,7 @@ canvas { /* Input select */ .aladin-input-select { - all:unset; /* Reset */ - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; border: 1px solid white; outline: 0; font: inherit; diff --git a/src/js/Aladin.js b/src/js/Aladin.js index c85c4702..78ca785e 100644 --- a/src/js/Aladin.js +++ b/src/js/Aladin.js @@ -40,7 +40,8 @@ import { MeasurementTable } from "./MeasurementTable.js"; import { ImageSurvey } from "./ImageSurvey.js"; import { Coo } from "./libs/astro/coo.js"; import { CooConversion } from "./CooConversion.js"; -import { GotoBox } from "./gui/Box/GotoBox.js"; +import plusIconUrl from "../../assets/icons/plus.svg" +import minusIconUrl from "../../assets/icons/minus.svg" import { ProjectionEnum } from "./ProjectionEnum.js"; @@ -58,12 +59,12 @@ import { FoV } from "./gui/FoV.js"; import { ShareActionButton } from "./gui/Button/ShareView.js"; import { Menu } from "./gui/Toolbar/Menu.js"; import { ContextMenu } from "./gui/Widgets/ContextMenu.js"; -import { ProjectionActionButton } from "./gui/Button/Projection.js"; import { Input } from "./gui/Widgets/Input.js"; import { Popup } from "./Popup.js"; import A from "./A.js"; import { SnapshotActionButton } from "./gui/Button/Snapshot.js"; import { StatusBarBox } from "./gui/Box/StatusBarBox.js"; +import { FullScreenActionButton } from "./gui/Button/FullScreen.js"; /** * @typedef {Object} AladinOptions * @description Options for configuring the Aladin Lite instance. @@ -371,17 +372,13 @@ export let Aladin = (function () { viewport.add(new FoV(this)) } - // Add the projection control - if (options.showProjectionControl) { - viewport.add(new ProjectionActionButton(self), 'proj') - } - //////////////////////////////////////////////////// let menu = new Menu({ orientation: 'vertical', - direction: 'right', + direction: 'left', position: { - anchor: 'right top' + left: '0px', + top: '3rem' } }, this); @@ -402,6 +399,10 @@ export let Aladin = (function () { if (options.showSimbadPointerControl) { menu.enable('simbad') } + // Add the projection control + if (options.showProjectionControl) { + menu.enable('projection') + } // Add the goto control if (options.showGotoControl) { menu.enable('goto') @@ -415,10 +416,6 @@ export let Aladin = (function () { menu.enable('settings') } - if (options.showFullscreenControl) { - menu.enable('fullscreen') - } - this.addUI(viewport); this.addUI(menu); @@ -430,6 +427,9 @@ export let Aladin = (function () { anchor: 'left bottom' } }, this); + if (options.showFullscreenControl) { + share.add(new FullScreenActionButton(self)) + } share.add(new ShareActionButton(self)) share.add(new SnapshotActionButton({ tooltip: { @@ -446,7 +446,7 @@ export let Aladin = (function () { // zoom control if (options.showZoomControl) { let plusZoomBtn = A.button({ - content: '+', + iconURL: plusIconUrl, tooltip: { content: 'Zoom', position: { @@ -460,7 +460,7 @@ export let Aladin = (function () { plusZoomBtn.addClass('medium-sized-icon') let minusZoomBtn = A.button({ - content: '-', + iconURL: minusIconUrl, tooltip: { content: 'Unzoom', position: { @@ -527,32 +527,33 @@ export let Aladin = (function () { var mqSmallSize = window.matchMedia("(max-width: 410px)") - function mqSmallSizeFunction(x) { + /*function mqSmallSizeFunction(x) { if (x.matches) { // If media query matches self.menu.update({ - direction: 'left', + direction: 'right', position: { - top: 23, - left: 0 + top: '23px', + left: '0px' } }) } else { self.menu.update({ - direction: 'right', + direction: 'left', position: { - anchor: 'right top' + top: '23px', + left: '0px' } }) } - } + }*/ // Attach listener function on state changes - mqSmallSize.addEventListener("change", function() { + /*mqSmallSize.addEventListener("change", function() { mqSmallSizeFunction(mqSmallSize); - }); + });*/ // Call listener function at run time - mqSmallSizeFunction(mqSmallSize); + //mqSmallSizeFunction(mqSmallSize); mqMediumSizeFunction(mqMediumSize); } @@ -618,6 +619,10 @@ export let Aladin = (function () { realFullscreen = Boolean(realFullscreen); self.isInFullscreen = !self.isInFullscreen; + if (this.menu) { + this.menu.closeAll(); + } + //this.fullScreenBtn.attr('title', isInFullscreen ? 'Restore original size' : 'Full screen'); if (this.aladinDiv.classList.contains('aladin-fullscreen')) { diff --git a/src/js/Reticle.js b/src/js/Reticle.js index 9b0ff37b..a01c7bd0 100644 --- a/src/js/Reticle.js +++ b/src/js/Reticle.js @@ -17,7 +17,6 @@ // along with Aladin Lite. // -import iconUrl from './../../assets/icons/reticle.svg' import { Color } from './Color.js'; /****************************************************************************** diff --git a/src/js/View.js b/src/js/View.js index 8a191800..9ed5835b 100644 --- a/src/js/View.js +++ b/src/js/View.js @@ -1051,8 +1051,8 @@ export let View = (function () { a1 = 0.002; a0 = 0.0002; } else { - a1 = 0.0025; - a0 = 0.0001; + a1 = 0.01; + a0 = 0.0004; } const alpha = Math.pow(view.fov / view.projection.fov, 0.5); diff --git a/src/js/gui/Box/GotoBox.js b/src/js/gui/Box/GotoBox.js index 365abb2b..7516b257 100644 --- a/src/js/gui/Box/GotoBox.js +++ b/src/js/gui/Box/GotoBox.js @@ -30,6 +30,7 @@ import { Box } from "../Widgets/Box.js"; import { Input } from "../Widgets/Input.js"; +import { Layout } from "../Layout.js"; export class GotoBox extends Box { // Constructor @@ -82,7 +83,7 @@ export class GotoBox extends Box { } }); - super(aladin, {content: textField}) + super(aladin, {content: Layout.horizontal(["Target: ", textField])}) this.addClass('aladin-box-night'); this.textField = textField; diff --git a/src/js/gui/Box/SurveyEditBox.js b/src/js/gui/Box/SurveyEditBox.js index a7fd5eb2..6ecef798 100644 --- a/src/js/gui/Box/SurveyEditBox.js +++ b/src/js/gui/Box/SurveyEditBox.js @@ -50,8 +50,7 @@ import { CmapSelector } from "../Selector/Colormap.js"; cssStyle: { padding: '4px', backgroundColor: 'black', - }, - ...options + } } ) @@ -61,12 +60,7 @@ import { CmapSelector } from "../Selector/Colormap.js"; this.selector = new SelectorButton({ luminosity: { iconURL: luminosityIconUrl, - tooltip: {content: 'Luminosity sliders', position: {direction: 'left'}}, - cssStyle: { - width: '18px', - height: '18px', - padding: 0, - }, + tooltip: {content: 'Luminosity sliders', position: {direction: 'right'}}, change(e) { const content = Layout.horizontal({ layout: [self.selector, self.luminositySettingsContent] @@ -76,12 +70,7 @@ import { CmapSelector } from "../Selector/Colormap.js"; }, opacity: { iconURL: opacityIconUrl, - tooltip: {content: 'Opacity slider', position: {direction: 'left'}}, - cssStyle: { - width: '18px', - height: '18px', - padding: 0, - }, + tooltip: {content: 'Opacity slider', position: {direction: 'right'}}, change(e) { const content = Layout.horizontal({layout: [self.selector, self.opacitySettingsContent]}); self.update({content}) @@ -89,12 +78,7 @@ import { CmapSelector } from "../Selector/Colormap.js"; }, colors: { iconURL: colorIconUrl, - tooltip: {content: 'Colormap', position: {direction: 'left'}}, - cssStyle: { - width: '18px', - height: '18px', - padding: 0, - }, + tooltip: {content: 'Colormap', position: {direction: 'right'}}, change(e) { const content = Layout.horizontal({layout: [self.selector, self.colorSettingsContent]}); self.update({content}) @@ -102,12 +86,7 @@ import { CmapSelector } from "../Selector/Colormap.js"; }, pixel: { iconURL: pixelHistIconUrl, - tooltip: {content: 'Pixel cutouts', position: {direction: 'left'}}, - cssStyle: { - width: '18px', - height: '18px', - padding: 0, - }, + tooltip: {content: 'Pixel cutouts', position: {direction: 'right'}}, change(e) { const content = Layout.horizontal({layout: [self.selector, self.pixelSettingsContent]}); self.update({content}) @@ -152,8 +131,6 @@ import { CmapSelector } from "../Selector/Colormap.js"; sqrt: { content: 'sqrt', cssStyle: { - height: '18px', - padding: 0, color: 'black' }, change(e) { @@ -164,10 +141,7 @@ import { CmapSelector } from "../Selector/Colormap.js"; linear: { content: 'linear', cssStyle: { - height: '18px', - padding: 0, color: 'black' - }, change(e) { let layer = self.options.layer; @@ -177,10 +151,7 @@ import { CmapSelector } from "../Selector/Colormap.js"; asinh: { content: 'asinh', cssStyle: { - height: '18px', - padding: 0, color: 'black' - }, change(e) { let layer = self.options.layer; @@ -190,8 +161,6 @@ import { CmapSelector } from "../Selector/Colormap.js"; pow2: { content: 'pow2', cssStyle: { - height: '18px', - padding: 0, color: 'black' }, @@ -203,10 +172,7 @@ import { CmapSelector } from "../Selector/Colormap.js"; log: { content: 'log', cssStyle: { - height: '18px', - padding: 0, color: 'black' - }, change(e) { let layer = self.options.layer; @@ -344,7 +310,7 @@ import { CmapSelector } from "../Selector/Colormap.js"; super.update(options) } - _show() { + _show(options) { this._hide(); if (this.selector) { @@ -359,7 +325,7 @@ import { CmapSelector } from "../Selector/Colormap.js"; this.colorSettingsContent._show(); } - super._show() + super._show(options) } _hide() { @@ -399,9 +365,9 @@ import { CmapSelector } from "../Selector/Colormap.js"; static singleton; - static getInstance(aladin, options) { + static getInstance(aladin) { if (!LayerEditBox.singleton) { - LayerEditBox.singleton = new LayerEditBox(aladin, options); + LayerEditBox.singleton = new LayerEditBox(aladin); } return LayerEditBox.singleton; diff --git a/src/js/gui/Button/CtxMenuOpener.js b/src/js/gui/Button/CtxMenuOpener.js index 8aa5e134..5620f681 100644 --- a/src/js/gui/Button/CtxMenuOpener.js +++ b/src/js/gui/Button/CtxMenuOpener.js @@ -46,7 +46,7 @@ export class CtxMenuActionButtonOpener extends ActionButton { constructor(options, aladin) { let self; - let ctxMenu = new ContextMenu(aladin, {hideOnClick: false}) + let ctxMenu = new ContextMenu(aladin, {hideOnClick: true, hideOnResize: true}) super({ ...options, cssStyle: { @@ -55,13 +55,14 @@ export class CtxMenuActionButtonOpener extends ActionButton { ...options.cssStyle }, action(e) { - if (options.action) { - options.action(e) - } - self.ctxMenu._hide(); + //self.ctxMenu._hide(); + + if (self.ctxMenu.isHidden === true) { + if (options.action) { + options.action(e) + } - if (self.hidden === true) { self.ctxMenu.attach(self.layout) self.ctxMenu.show({ position: { @@ -72,13 +73,15 @@ export class CtxMenuActionButtonOpener extends ActionButton { }); //CtxMenuActionButtonOpener.BUTTONS.forEach(b => {b.hidden = true}) + } else { + self.hideMenu(); } - self.hidden = !self.hidden; + //self.hidden = !self.hidden; } }) - this.hidden = true; + //this.hidden = true; this.layout = options.ctxMenu; @@ -90,7 +93,6 @@ export class CtxMenuActionButtonOpener extends ActionButton { hideMenu() { this.ctxMenu._hide(); - this.hidden = true; } _hide() { @@ -105,7 +107,7 @@ export class CtxMenuActionButtonOpener extends ActionButton { super.update(options) - if (!this.hidden) { + if (!this.ctxMenu.isHidden) { this.ctxMenu.attach(this.layout) this.ctxMenu.show({ position: { diff --git a/src/js/gui/Button/FullScreen.js b/src/js/gui/Button/FullScreen.js new file mode 100644 index 00000000..1e6e2d48 --- /dev/null +++ b/src/js/gui/Button/FullScreen.js @@ -0,0 +1,88 @@ +// 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. +// + + +/****************************************************************************** + * Aladin Lite project + * + * File gui/Stack/Menu.js + * + * + * Author: Matthieu Baumann [CDS, matthieu.baumann@astro.unistra.fr] + * + *****************************************************************************/ + +import { ActionButton } from "../Widgets/ActionButton.js"; + +import restoreIcon from './../../../../assets/icons/restore.svg'; +import maximizeIcon from './../../../../assets/icons/maximize.svg'; + +export class FullScreenActionButton extends ActionButton { + // Constructor + constructor(aladin, options) { + let self; + super({ + iconURL: aladin.isInFullscreen ? restoreIcon : maximizeIcon, + ...options, + tooltip: { + content: aladin.isInFullscreen ? 'Restore original size' : 'Full-screen', + position: { + direction: 'top' + } + }, + action(e) { + if (aladin.statusBar) { + aladin.statusBar.removeMessage('tooltip') + } + + aladin.toggleFullscreen(aladin.options.realFullscreen); + + if (aladin.isInFullscreen) { + // make that div above other aladin lite divs (if there are...) + aladin.aladinDiv.style.zIndex = 1 + self.update({ + iconURL: restoreIcon, + tooltip: { + content: 'Restore original size', + position: { + direction: 'top' + } + } + }); + } else { + aladin.aladinDiv.style.removeProperty('z-index') + + self.update({ + iconURL: maximizeIcon, + tooltip: { + content: 'Fullscreen', + position: { + direction: 'top' + } + } + }); + } + } + }) + + self = this; + + this.addClass('medium-sized-icon') + } +} \ No newline at end of file diff --git a/src/js/gui/Button/GridEnabler.js b/src/js/gui/Button/GridEnabler.js new file mode 100644 index 00000000..0594457f --- /dev/null +++ b/src/js/gui/Button/GridEnabler.js @@ -0,0 +1,61 @@ +// 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. +// + + +/****************************************************************************** + * Aladin Lite project + * + * File gui/Stack/Menu.js + * + * + * Author: Matthieu Baumann [CDS, matthieu.baumann@astro.unistra.fr] + * + *****************************************************************************/ + +import { ActionButton } from "../Widgets/ActionButton.js"; +import gridIcon from './../../../../assets/icons/grid.svg'; + +export class GridEnabler extends ActionButton { + // Constructor + constructor(aladin, options) { + const computeTooltip = (enabled) => { + const content = enabled ? 'Hide the coordinate grid' : 'Display the coordinate grid' + return { + content, + position: { + direction: 'top' + } + } + } + + let self; + super({ + iconURL: gridIcon, + tooltip: computeTooltip(aladin.getGridOptions().enabled), + action(o) { + let gridEnabled = aladin.getGridOptions().enabled; + aladin.setCooGrid({enabled: !gridEnabled}) + + self.update({tooltip: computeTooltip(!gridEnabled)}) + } + }) + self = this; + this.addClass('medium-sized-icon') + } +} \ No newline at end of file diff --git a/src/js/gui/Button/Projection.js b/src/js/gui/Button/Projection.js index 66a7dfad..0619e1e3 100644 --- a/src/js/gui/Button/Projection.js +++ b/src/js/gui/Button/Projection.js @@ -40,20 +40,17 @@ import { ProjectionEnum } from "../../ProjectionEnum"; * UI responsible for displaying the viewport infos * @param {Aladin} aladin - The aladin instance. */ - constructor(aladin) { + constructor(aladin, options) { //let ctxMenu = ; super({ iconURL: projectionSvg, - tooltip: {content: 'Change the view projection', position: {direction: 'bottom'}}, + tooltip: {content: 'Change the view projection', position: {direction: 'top'}}, cssStyle: { backgroundColor: '#bababa', borderColor: '#484848', cursor: 'pointer', }, - openDirection: 'bottom', - /*action(o) { - ctxMenu.attach(layout); - }*/ + ...options }, aladin); let ctxMenu = this._buildLayout(aladin); diff --git a/src/js/gui/Button/SAMP.js b/src/js/gui/Button/SAMP.js index a322efee..9833dee4 100644 --- a/src/js/gui/Button/SAMP.js +++ b/src/js/gui/Button/SAMP.js @@ -46,13 +46,14 @@ options = { constructor(options, aladin) { if (!aladin.samp) { options = { + ...options, iconURL: waveOffIconUrl, - tooltip: {content: 'SAMP disabled in Aladin Lite options', position: {direction: 'left'}}, + tooltip: {content: 'SAMP disabled in Aladin Lite options', position: {direction: 'top'}}, disable: true, } } else { let isHubRunning = aladin.samp.isHubCurrentlyRunning(); - let tooltip = options && options.tooltip || {content: isHubRunning ? 'Connect to SAMP Hub' : 'No hub running found', position: {direction: 'left'}} + let tooltip = options && options.tooltip || {content: isHubRunning ? 'Connect to SAMP Hub' : 'No hub running found', position: {direction: 'top'}} let action = options && options.action if (!action) { // default action, just connect and ping @@ -63,6 +64,7 @@ options = { let disable = !isHubRunning; options = { + ...options, iconURL: aladin.samp.isConnected() ? waveOnIconUrl : waveOffIconUrl, tooltip, disable, @@ -74,7 +76,6 @@ options = { super(options) - this.addClass('medium-sized-icon') this._addListeners(aladin); } diff --git a/src/js/gui/Button/SimbadPointer.js b/src/js/gui/Button/SimbadPointer.js index 71c1902a..58b0b814 100644 --- a/src/js/gui/Button/SimbadPointer.js +++ b/src/js/gui/Button/SimbadPointer.js @@ -37,25 +37,15 @@ constructor(aladin) { super({ iconURL: targetIcon, + size: 'medium', tooltip: { - content: 'What to know what is a specific object ?
Use the Simbad pointer tool!', - position: { direction: 'left' }, + content: 'Want to know what is a specific object ?
Use the Simbad pointer tool!', + position: { direction: 'right' }, }, - /*cssStyle: { - padding: 0, - backgroundColor: '#bababa', - backgroundPosition: 'center', - borderColor: '#484848', - cursor: 'pointer', - width: '28px', - height: '28px' - },*/ action(o) { aladin.fire('simbad'); } }) - - this.addClass('medium-sized-icon') } } \ No newline at end of file diff --git a/src/js/gui/CtxMenu/GridSettings.js b/src/js/gui/CtxMenu/GridSettings.js new file mode 100644 index 00000000..eaa1fd83 --- /dev/null +++ b/src/js/gui/CtxMenu/GridSettings.js @@ -0,0 +1,178 @@ +// Copyright 2023 - 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. +// + + + +/****************************************************************************** + * Aladin Lite project + * + * File GridSettingsCtxMenu + * + * Author: Matthieu Baumann [CDS] + * + *****************************************************************************/ + +import { ALEvent } from "../../events/ALEvent.js"; +import { Input } from "../Widgets/Input.js"; +import { ActionButton } from "../Widgets/ActionButton.js"; +import { Layout } from "../Layout.js"; +import { Color } from "../../Color.js"; +import thicknessLineIcon from './../../../../assets/icons/thickness.svg'; +import labelSizeIcon from './../../../../assets/icons/font-size.svg'; + +export let GridSettingsCtxMenu = (function () { + + let GridSettingsCtxMenu = {}; + + GridSettingsCtxMenu.getLayout = function (aladin) { + let colorInput = new Input({ + layout: { + name: 'gridColor', + type: 'color', + value: (() => { + let c = aladin.getGridOptions().color; + const cHex = Color.rgbToHex(c.r * 255, c.g * 255, c.b * 255) + return cHex; + })(), + change(e) { + aladin.setCooGrid({color: e.target.value}) + } + } + }); + colorInput.addClass("aladin-input-color"); + + let opacitySlider = Input.slider({ + name: 'opacity', + type: 'range', + min: 0.0, + max: 1.0, + value: aladin.getGridOptions().opacity, + change(e) { + aladin.setCooGrid({opacity: +e.target.value}) + } + }); + + const labelSizeSlider = Input.slider({ + name: 'labelSize', + type: 'range', + tooltip: { + content: 'size' + }, + min: 0.0, + max: 1.0, + value: 0.5, + change(e) { + aladin.setCooGrid({labelSize: Math.round(+e.target.value * 20)}) + } + }); + + const thicknessLineBtn = ActionButton.createSmallSizedIconBtn({ + iconURL: thicknessLineIcon, + tooltip: {content: 'Grid line thickness', position: {direction: 'left'}}, + cssStyle: { + backgroundColor: '#bababa', + borderColor: '#484848', + cursor: 'pointer', + width: '20px', + height: '20px', + padding: '0', + }, + action(e) { + let ctxMenu = ContextMenu.getInstance(aladin); + ctxMenu._hide(); + + let ctxMenuLayout = []; + for (let thickness = 1; thickness <= 5; thickness++) { + ctxMenuLayout.push({ + label: thickness + 'px', + action(o) { + aladin.setCooGrid({thickness: thickness}) + } + }) + } + + ctxMenu.attach(ctxMenuLayout); + ctxMenu.show({ + e: e, + position: { + nextTo: thicknessLineBtn, + direction: 'bottom', + } + }) + } + }); + + let enableCheckbox = Input.checkbox({ + name: 'enableGrid', + tooltip: {content: 'Enable/disable the grid', position: {direction: 'left'}}, + type: 'checkbox', + checked: aladin.getGridOptions().enabled, + click(e) { + aladin.setCooGrid({enabled: enableCheckbox.get()}) + } + }); + + ALEvent.COO_GRID_UPDATED.listenedBy(aladin.aladinDiv, function (e) { + let color = e.detail.color; + + let hexColor = Color.rgbToHex(Math.round(255 * color.r), Math.round(255 * color.g), Math.round(255 * color.b)); + colorInput.set(hexColor) + }); + + return { + label: 'Grid', + subMenu: [{ + label: { + content: ['Show/Hide'] + }, + action(o) { + /*let newVal = toggleCheckbox(gridCheckbox); + aladin.showReticle(newVal) + + self._attach();*/ + } + }, + { + label: { + content: ['Color ', colorInput], + }, + mustHide: false, + action(o) {} + }, + { + label: { + content: ['Opacity ', opacitySlider], + }, + mustHide: false, + action(o) {} + }, + { + label: { + content: ['Label', labelSizeSlider] + }, + mustHide: false, + action(o) {} + } + ] + } + } + + return GridSettingsCtxMenu; + +})(); diff --git a/src/js/gui/CtxMenu/OverlayStack.js b/src/js/gui/CtxMenu/OverlayStack.js index 5d4c26fe..c6582d2e 100644 --- a/src/js/gui/CtxMenu/OverlayStack.js +++ b/src/js/gui/CtxMenu/OverlayStack.js @@ -40,21 +40,24 @@ import A from "../../A.js"; import { Utils } from "../../../js/Utils"; export class OverlayStack extends ContextMenu { + + static predefinedCats = { + simbad: {url: 'https://axel.u-strasbg.fr/HiPSCatService/SIMBAD', options: {id: 'simbad', name: 'SIMBAD', shape: 'circle', sourceSize: 8, color: '#318d80'}}, + gaia: {url: 'https://axel.u-strasbg.fr/HiPSCatService/I/355/gaiadr3', options: {id: 'gaia-dr3', name: 'Gaia DR3', shape: 'square', sourceSize: 8, color: '#6baed6'}}, + twomass: {url: 'https://axel.u-strasbg.fr/HiPSCatService/II/246/out', options: {id: '2mass', name: '2MASS', shape: 'plus', sourceSize: 8, color: '#dd2233'}} + }; // Constructor constructor(aladin) { - super(aladin, {hideOnClick: false}); + let self; + super(aladin, {hideOnClick: (e) => { + if (self.mode === 'stack') { + self._hide(); + } + }}); + self = this; this.aladin = aladin; - //this.anchor = menu.controls["OverlayStack"]; this.mode = 'stack'; - /*window.addEventListener("resize", (e) => { - this._hide(); - })*/ - /*document.addEventListener('click', (e) => { - if (!self.el.contains(e.target) && this.mode === 'stack') { - this._hide() - } - });*/ this._addListeners(); } @@ -98,8 +101,50 @@ export class OverlayStack extends ContextMenu { { label: 'Catalogue', subMenu: [ + { + label: 'Simbad', + action(o) { + o.stopPropagation(); + o.preventDefault(); + + self._hide(); + + const simbadHiPS = A.catalogHiPS(OverlayStack.predefinedCats.simbad.url, OverlayStack.predefinedCats.simbad.options); + self.aladin.addCatalog(simbadHiPS); + + self.mode = 'stack'; + } + }, + { + label: 'Gaia DR3', + action(o) { + o.stopPropagation(); + o.preventDefault(); + + self._hide(); + + const simbadHiPS = A.catalogHiPS(OverlayStack.predefinedCats.gaia.url, OverlayStack.predefinedCats.gaia.options); + self.aladin.addCatalog(simbadHiPS); + + self.mode = 'stack'; + } + }, + { + label: '2MASS', + action(o) { + o.stopPropagation(); + o.preventDefault(); + + self._hide(); + + const simbadHiPS = A.catalogHiPS(OverlayStack.predefinedCats.twomass.url, OverlayStack.predefinedCats.twomass.options); + self.aladin.addCatalog(simbadHiPS); + + self.mode = 'stack'; + } + }, ContextMenu.fileLoaderItem({ - label: 'VOTable File', + label: 'From a VOTable File', accept: '.xml,.vot', action(file) { let url = URL.createObjectURL(file); @@ -115,19 +160,19 @@ export class OverlayStack extends ContextMenu { } }), { - label: 'Find in our databases...', + label: 'More...', action(o) { - //o.stopPropagation(); - //o.preventDefault(); + o.stopPropagation(); + o.preventDefault(); self._hide(); - self.catBox = CatalogQueryBox.getInstance(self.aladin); - console.log(self.position) - self.catBox._show({position: self.position}); + + let catBox = CatalogQueryBox.getInstance(self.aladin); + catBox._show({position: self.position}); self.mode = 'search'; } - } + }, ] }, { @@ -152,6 +197,9 @@ export class OverlayStack extends ContextMenu { { label: 'Circle', action(o) { + o.preventDefault(); + o.stopPropagation(); + self._hide(); self.aladin.select('circle', c => { @@ -173,6 +221,9 @@ export class OverlayStack extends ContextMenu { { label: 'Rect', action(o) { + o.stopPropagation(); + o.preventDefault(); + self._hide(); self.aladin.select('rect', r => { @@ -199,6 +250,9 @@ export class OverlayStack extends ContextMenu { { label: 'Polygon', action(o) { + o.stopPropagation(); + o.preventDefault(); + self._hide(); self.aladin.select('poly', p => { @@ -235,15 +289,14 @@ export class OverlayStack extends ContextMenu { let cssStyle = { height: 'fit-content', }; - let showBtn = ActionButton.createIconBtn({ + let showBtn = new ActionButton({ + size: 'small', iconURL: overlay.isShowing ? showIconUrl : hideIconUrl, cssStyle: { backgroundColor: '#bababa', borderColor: '#484848', color: 'black', visibility: Utils.hasTouchScreen() ? 'visible' : 'hidden', - width: '18px', - height: '18px', verticalAlign: 'middle', marginRight: '2px', }, @@ -259,20 +312,19 @@ export class OverlayStack extends ContextMenu { } }); - let deleteBtn = ActionButton.createIconBtn({ + let deleteBtn = new ActionButton({ iconURL: removeIconUrl, + size: 'small', cssStyle: { backgroundColor: '#bababa', borderColor: '#484848', color: 'black', visibility: Utils.hasTouchScreen() ? 'visible' : 'hidden', - width: '18px', - height: '18px', verticalAlign: 'middle' }, tooltip: { content: 'Remove', - position: {direction: 'left'} + position: {direction: 'bottom'} }, action(e) { self.aladin.removeLayer(overlay) @@ -332,13 +384,22 @@ export class OverlayStack extends ContextMenu { iconSvg = AladinUtils.SVG_ICONS.OVERLAY; } + let changeSVGSize = (svg, size) => { + let str = svg.replace(/FILLCOLOR/g, 'black') + let elt = document.createElement('div'); + elt.innerHTML = str; + + elt.querySelector('svg').setAttribute('width', size); + elt.querySelector('svg').setAttribute('height', size); + + return elt.innerHTML; + }; // retrieve SVG icon, and apply the layer color - var svgBase64 = window.btoa(iconSvg.replace(/FILLCOLOR/g, overlay.color)); - let icon = ActionButton.createIconBtn({ - tooltip: {content: tooltipText, position: {direction: 'left'}}, + var svgBase64 = window.btoa(changeSVGSize(iconSvg.replace(/FILLCOLOR/g, overlay.color), '1rem')); + let icon = new ActionButton({ + size: 'small', + tooltip: {content: tooltipText, position: {direction: 'bottom'}}, cssStyle: { - width: '16px', - height: '16px', backgroundImage: 'url("data:image/svg+xml;base64,' + svgBase64 + '")', } }); @@ -364,17 +425,9 @@ export class OverlayStack extends ContextMenu { _hide() { this.mode = 'stack'; - - if (this.catBox) { - this.catBox._hide(); - } - /*let catBox = CatalogQueryBox.getInstance(this.aladin, this.position); - - - if (this.position) { - - }*/ + let catBox = CatalogQueryBox.getInstance(this.aladin); + catBox._hide(); super._hide(); } diff --git a/src/js/gui/CtxMenu/Settings.js b/src/js/gui/CtxMenu/Settings.js index fab4e232..55b2a775 100644 --- a/src/js/gui/CtxMenu/Settings.js +++ b/src/js/gui/CtxMenu/Settings.js @@ -36,11 +36,12 @@ import { ALEvent } from "../../events/ALEvent.js"; import { SAMPActionButton } from "../Button/SAMP.js"; import helpIconBtn from '../../../../assets/icons/help.svg'; import { Utils } from "../../Utils"; +import { GridSettingsCtxMenu } from "./GridSettings.js"; export class SettingsCtxMenu extends ContextMenu { // Constructor constructor(aladin, menu) { - super(aladin); + super(aladin, {hideOnClick: true}); let self = this; self.backgroundColorInput = Input.color({ name: 'color', @@ -106,6 +107,7 @@ export class SettingsCtxMenu extends ContextMenu { this.menu = menu; let sampBtn = new SAMPActionButton({ + size: 'small', action(conn) { if (conn.isConnected()) { conn.unregister(); @@ -186,6 +188,7 @@ export class SettingsCtxMenu extends ContextMenu { } ] }, + GridSettingsCtxMenu.getLayout(self.aladin), { label: { content: [self.hpxGridCheckbox, 'HEALPix grid'] @@ -234,21 +237,15 @@ export class SettingsCtxMenu extends ContextMenu { action(o) { toggleWindow('grid'); } - }, - { - label: 'FullScreen', - selected: self.menu.isShown('fullscreen'), - action(o) { - toggleWindow('fullscreen'); - } } ] }, { label: { icon: { - tooltip: {content: 'Documentation about Aladin Lite', position: {direction: 'left'}}, + tooltip: {content: 'Documentation about Aladin Lite', position: {direction: 'top'}}, iconURL: helpIconBtn, + size: 'small', cssStyle: { cursor: 'help', } diff --git a/src/js/gui/CtxMenu/SurveyStack.js b/src/js/gui/CtxMenu/SurveyStack.js index 6dc15416..49b8123f 100644 --- a/src/js/gui/CtxMenu/SurveyStack.js +++ b/src/js/gui/CtxMenu/SurveyStack.js @@ -50,10 +50,8 @@ import A from '../../A'; export class Stack extends ContextMenu { static previewImagesUrl = { 'AllWISE color': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_allWISE_color.jpg', - 'DECaPS DR1 color': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_DECaLS_DR5_color.jpg', 'DSS colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_DSS2_color.jpg', 'DSS2 Red (F+R)': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_DSS2_red.jpg', - 'Density map for Gaia EDR3 (I/350/gaiaedr3)' : undefined, 'Fermi color': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_Fermi_color.jpg', 'GALEXGR6_7 NUV': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_GALEXGR6_7_color.jpg', 'GLIMPSE360': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_GLIMPSE360.jpg', @@ -62,7 +60,6 @@ export class Stack extends ContextMenu { 'IRIS colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_IRIS_color.jpg', 'Mellinger colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_Mellinger_color.jpg', 'PanSTARRS DR1 color': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_PanSTARRS_DR1_color-z-zg-g.jpg', - 'PanSTARRS DR1 g': undefined, '2MASS colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_2MASS_color.jpg', 'AKARI colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_AKARI_FIS_Color.jpg', 'SWIFT': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_SWIFT_BAT_FLUX.jpg', @@ -73,7 +70,13 @@ export class Stack extends ContextMenu { // Constructor constructor(aladin) { - super(aladin, {hideOnClick: false}); + let self; + super(aladin, {hideOnClick: (e) => { + if (self.mode === 'stack') { + self._hide(); + } + }}); + self = this; this.aladin = aladin; //this.anchor = menu.controls["Stack"]; //this.fsm = new StackLayerOpenerFSM(aladin, menu); @@ -145,19 +148,17 @@ export class Stack extends ContextMenu { cursor: 'help', }, }, - content: 'Search a survey' + content: 'Search for a survey' }, - action(e) { - /*if (e) { - e.stopPropagation(); - e.preventDefault(); - }*/ + action: (e) => { + e.stopPropagation(); + e.preventDefault(); self._hide(); - self.hipsSelectorBox = HiPSSelectorBox.getInstance(self.aladin); + let hipsSelectorBox = HiPSSelectorBox.getInstance(self.aladin); // attach a callback - self.hipsSelectorBox.attach( + hipsSelectorBox.attach( (HiPSId) => { let name = Utils.uuidv4() self.aladin.setOverlayImageLayer(HiPSId, name) @@ -167,7 +168,7 @@ export class Stack extends ContextMenu { } ); - self.hipsSelectorBox._show({ + hipsSelectorBox._show({ position: self.position, }); @@ -217,7 +218,7 @@ export class Stack extends ContextMenu { for(const layer of layers) { const name = layer.name; - let backgroundUrl = this._findPreviewImageUrl(layer); + let backgroundUrl = layer.properties.url + '/preview.jpg'; let cssStyle = { height: 'fit-content', }; @@ -230,15 +231,13 @@ export class Stack extends ContextMenu { } } - let showBtn = ActionButton.createIconBtn({ + let showBtn = ActionButton.createSmallSizedIconBtn({ iconURL: layer.getOpacity() === 0.0 ? hideIconUrl : showIconUrl, cssStyle: { backgroundColor: '#bababa', borderColor: '#484848', color: 'black', visibility: Utils.hasTouchScreen() ? 'visible' : 'hidden', - width: '18px', - height: '18px', verticalAlign: 'middle', marginRight: '2px', }, @@ -258,32 +257,29 @@ export class Stack extends ContextMenu { } }); - let deleteBtn = ActionButton.createIconBtn({ + let deleteBtn = ActionButton.createSmallSizedIconBtn({ iconURL: removeIconUrl, cssStyle: { backgroundColor: '#bababa', borderColor: '#484848', color: 'black', visibility: Utils.hasTouchScreen() ? 'visible' : 'hidden', - width: '18px', - height: '18px', verticalAlign: 'middle' }, disable: layer.layer === 'base', - tooltip: {content: 'Remove', position: {direction: 'left'}}, + tooltip: {content: 'Remove', position: {direction: 'bottom'}}, action(e) { self.aladin.removeImageLayer(layer.layer); } }); - let editBtn = ActionButton.createIconBtn({ + + let editBtn = ActionButton.createSmallSizedIconBtn({ iconURL: editIconUrl, cssStyle: { backgroundColor: '#bababa', borderColor: '#484848', color: 'black', visibility: Utils.hasTouchScreen() ? 'visible' : 'hidden', - width: '18px', - height: '18px', verticalAlign: 'middle', marginRight: '2px', }, @@ -297,27 +293,37 @@ export class Stack extends ContextMenu { self.aladin.selectLayer(layer.layer); self.attach({layers}) - let editBox = LayerEditBox.getInstance(self.aladin, {position: self.position}); + let editBox = LayerEditBox.getInstance(self.aladin); editBox.update({layer}) - editBox._show(); + editBox._show({position: self.position}); self.mode = 'edit'; } }); - let loadMOCBtn = ActionButton.createIconBtn({ + let changeSVGSize = (svg, size) => { + let str = svg.replace(/FILLCOLOR/g, 'black') + let elt = document.createElement('div'); + elt.innerHTML = str; + + elt.querySelector('svg').setAttribute('width', size); + elt.querySelector('svg').setAttribute('height', size); + + return elt.innerHTML; + }; + + let loadMOCBtn = new ActionButton({ + size: 'small', cssStyle: { backgroundColor: '#bababa', borderColor: '#484848', color: 'black', visibility: Utils.hasTouchScreen() ? 'visible' : 'hidden', - backgroundImage: 'url("data:image/svg+xml;base64,' + window.btoa(AladinUtils.SVG_ICONS.MOC.replace(/FILLCOLOR/g, 'black')) + '")', - width: '16px', - height: '18px', + backgroundImage: 'url("data:image/svg+xml;base64,' + window.btoa(changeSVGSize(AladinUtils.SVG_ICONS.MOC.replace(/FILLCOLOR/g, 'black'), '1rem')) + '")', verticalAlign: 'middle', marginRight: '2px', }, - tooltip: {content: 'Add coverage', position: {direction: 'left'}}, + tooltip: {content: 'Add coverage', position: {direction: 'bottom'}}, action: (e) => { let moc = A.MOCFromURL(layer.properties.url + '/Moc.fits', {lineWidth: 3, name: layer.properties.obsTitle}); self.aladin.addMOC(moc); @@ -333,7 +339,6 @@ export class Stack extends ContextMenu { }); loadMOCBtn.addClass('svg-icon') - let layerClassName = 'a' + layer.layer.replace(/[.\/ ]/g, '') let btns = [showBtn, editBtn]; @@ -375,43 +380,13 @@ export class Stack extends ContextMenu { }; //if (layer.layer === "base") { - l.subMenu = [{ - label: { - icon: { - iconURL: searchIconImg, - tooltip: {content: 'Find a specific survey
in our database...', position: { direction: 'bottom' }}, - cssStyle: { - backgroundPosition: 'center center', - backgroundColor: '#bababa', - border: '1px solid rgb(72, 72, 72)', - cursor: 'help', - }, - }, - content: 'Search for a new survey' - }, - action(o) { - self._hide(); - - self.hipsBox = HiPSSelectorBox.getInstance(self.aladin) - - self.hipsBox.attach( - (HiPSId) => { - self.aladin.setOverlayImageLayer(HiPSId, layer.layer); - self.mode = 'stack'; - self._show(); - } - ); - - self.hipsBox._show({ - position: self.position, - }) - - self.mode = 'hips'; - } - }]; + l.subMenu = []; for(let ll of defaultLayers) { - let backgroundUrl = Stack.previewImagesUrl[ll.name]; + backgroundUrl = Stack.previewImagesUrl[ll.name]; + if (!backgroundUrl) { + backgroundUrl = ll.url + '/preview.jpg' + } let cssStyle = { height: '2.5em', }; @@ -462,6 +437,44 @@ export class Stack extends ContextMenu { } }) } + + l.subMenu.push({ + label: { + icon: { + iconURL: searchIconImg, + tooltip: {content: 'Find a specific survey
in our database...', position: { direction: 'top' }}, + cssStyle: { + backgroundPosition: 'center center', + backgroundColor: '#bababa', + border: '1px solid rgb(72, 72, 72)', + cursor: 'help', + }, + }, + content: 'More...' + }, + action(o) { + o.stopPropagation(); + o.preventDefault(); + + self._hide(); + + let hipsBox = HiPSSelectorBox.getInstance(self.aladin) + + hipsBox.attach( + (HiPSId) => { + self.aladin.setOverlayImageLayer(HiPSId, layer.layer); + self.mode = 'stack'; + self._show(); + } + ); + + hipsBox._show({ + position: self.position, + }) + + self.mode = 'hips'; + } + }) //} l.action = (o) => { @@ -493,6 +506,8 @@ export class Stack extends ContextMenu { return Stack.previewImagesUrl[key]; } } + // if not found + return layer.properties.url + '/preview.jpg' } _show(options) { @@ -501,7 +516,7 @@ export class Stack extends ContextMenu { super.show({ position: this.position, cssStyle: { - maxWidth: '15em', + maxWidth: '15rem', backgroundColor: 'black', } }) @@ -520,14 +535,13 @@ export class Stack extends ContextMenu { _hide() { // go back to the display stack state - if (this.position) { - let editBox = LayerEditBox.getInstance(this.aladin, {position: this.position}); + //if (this.mode === 'stack') { + let editBox = LayerEditBox.getInstance(this.aladin); editBox._hide(); - } - if (this.hipsSelectorBox) { - this.hipsSelectorBox._hide(); - } + let hipsSelectorBox = HiPSSelectorBox.getInstance(this.aladin); + hipsSelectorBox._hide(); + //} this.mode = 'stack'; diff --git a/src/js/gui/FoV.js b/src/js/gui/FoV.js index a568de8e..78577e3d 100644 --- a/src/js/gui/FoV.js +++ b/src/js/gui/FoV.js @@ -49,11 +49,6 @@ export class FoV extends DOMElement { super(el) - if (Utils.hasTouchScreen()) { - // Add a little padding - this.el.style.padding = "0.5em"; - } - let self = this; ALEvent.ZOOM_CHANGED.listenedBy(aladin.aladinDiv, function (e) { let [fovXDeg, fovYDeg] = aladin.getFov(); diff --git a/src/js/gui/Location.js b/src/js/gui/Location.js index 1e4087f1..cd2b69f2 100644 --- a/src/js/gui/Location.js +++ b/src/js/gui/Location.js @@ -47,12 +47,8 @@ export class Location extends DOMElement { let el = Layout.horizontal({ layout: [ - ActionButton.createIconBtn({ + ActionButton.createSmallSizedIconBtn({ iconURL: copyIconBtn, - cssStyle: { - width: '16px', - height: '16px', - }, tooltip: {content: 'Copy to clipboard!', position: {direction: 'bottom'}}, action(e) { self.copyCoordinatesToClipboard() @@ -118,10 +114,6 @@ export class Location extends DOMElement { isViewCenter: true, frame: aladin.view.cooFrame }, aladin) - - if (Utils.hasTouchScreen()) { - this.el.style.padding = "0.2em"; - } }; static prec = 7; diff --git a/src/js/gui/Selector/Colormap.js b/src/js/gui/Selector/Colormap.js index e3440c81..85c3fa57 100644 --- a/src/js/gui/Selector/Colormap.js +++ b/src/js/gui/Selector/Colormap.js @@ -60,13 +60,12 @@ export class CmapSelector extends SelectorButton { options[cmap] = { ...options[cmap], cssStyle: { - padding: '0px', //border: 'none', //borderRadius: '0', //backgroundColor: 'black', color: 'black', + width: '4rem', overflow: 'hidden', - width: '6em', 'font-family': 'monospace', }, content: cmap, diff --git a/src/js/gui/Toolbar/Menu.js b/src/js/gui/Toolbar/Menu.js index 41f7e236..2135a851 100644 --- a/src/js/gui/Toolbar/Menu.js +++ b/src/js/gui/Toolbar/Menu.js @@ -26,15 +26,13 @@ import { Stack } from "../CtxMenu/SurveyStack"; import { OverlayStack } from "../CtxMenu/OverlayStack"; import { GotoBox } from "../Box/GotoBox"; import { SimbadPointer } from "../Button/SimbadPointer"; -import { GridBox } from "../Box/GridBox"; +import { ProjectionActionButton } from "../Button/Projection"; import settingsIcon from './../../../../assets/icons/settings.svg'; import stackOverlayIconUrl from './../../../../assets/icons/stack.svg'; import stackImageIconUrl from './../../../../assets/icons/telescope.svg'; -import gridIcon from './../../../../assets/icons/grid.svg'; +import { GridEnabler } from '../Button/GridEnabler'; import searchIcon from './../../../../assets/icons/search.svg'; -import restoreIcon from './../../../../assets/icons/restore.svg'; -import maximizeIcon from './../../../../assets/icons/maximize.svg'; import { Utils as UtilsExt } from "../../Utils"; import { Utils } from "../Utils"; @@ -82,20 +80,14 @@ import { Toolbar } from "../Widgets/Toolbar"; }) } - UtilsExt.on(aladin.aladinDiv, 'dblclick', () => { - self.closeAll(); - }); - - // Add the fullscreen control // tools let stack = new Stack(aladin, self); let overlay = new OverlayStack(aladin); let goto = new GotoBox(aladin); - let grid = new GridBox(aladin); let settings = new SettingsCtxMenu(aladin, self); this.panels = { - stack, overlay, goto, grid, settings + stack, overlay, goto, settings }; this.indices = []; @@ -113,8 +105,9 @@ import { Toolbar } from "../Widgets/Toolbar"; iconURL: stackImageIconUrl, tooltip: { content: 'Open the stack layer menu', - global: true, - aladin + position: { + direction: 'top' + } }, action(o) { let toolWasShown = !self.panels["stack"].isHidden; @@ -135,8 +128,9 @@ import { Toolbar } from "../Widgets/Toolbar"; iconURL: stackOverlayIconUrl, tooltip: { content: 'Open the overlays menu', - global: true, - aladin + position: { + direction: 'top' + } }, action(o) { let toolWasShown = !self.panels["overlay"].isHidden; @@ -153,13 +147,21 @@ import { Toolbar } from "../Widgets/Toolbar"; } } }), + projection: new ProjectionActionButton(aladin, { + openDirection: self.options.direction === 'right' ? 'left' : 'right', + action(o) { + // executed before opening the ctx menu + self.closeAll(); + } + }), simbad: new SimbadPointer(aladin), goto: ActionButton.createIconBtn({ iconURL: searchIcon, tooltip: { content: 'Search for where a celestial object is', - global: true, - aladin + position: { + direction: 'top' + } }, action(o) { let toolWasShown = !self.panels["goto"].isHidden; @@ -176,34 +178,14 @@ import { Toolbar } from "../Widgets/Toolbar"; } } }), - grid: ActionButton.createIconBtn({ - iconURL: gridIcon, - tooltip: { - content: 'Open the grid layer menu', - global: true, - aladin - }, - action(o) { - let toolWasShown = !self.panels["grid"].isHidden; - - self.closeAll(); - - if (!toolWasShown) { - self.panels["grid"]._show({ - position: { - nextTo: self.controls['grid'], - direction: self.options.direction === 'right' ? 'left' : 'right', - } - }); - } - } - }), + grid: new GridEnabler(aladin), settings: ActionButton.createIconBtn({ iconURL: settingsIcon, tooltip: { content: 'Some general settings e.g. background color, reticle, windows to show', - global: true, - aladin + position: { + direction: 'top' + } }, action(o) { let toolWasShown = !self.panels["settings"].isHidden; @@ -219,46 +201,7 @@ import { Toolbar } from "../Widgets/Toolbar"; }); } } - }), - fullscreen: ActionButton.createIconBtn({ - iconURL: aladin.isInFullscreen ? restoreIcon : maximizeIcon, - tooltip: { - content: aladin.isInFullscreen ? 'Restore original size' : 'Full-screen', - global: true, - aladin - }, - action(o) { - aladin.toggleFullscreen(aladin.options.realFullscreen); - let btn = self.controls['fullscreen']; - - if (aladin.isInFullscreen) { - // make that div above other aladin lite divs (if there are...) - aladin.aladinDiv.style.zIndex = 1 - btn.update({ - iconURL: restoreIcon, - tooltip: { - content: 'Restore original size', - global: true, - aladin - } - }); - } else { - aladin.aladinDiv.style.removeProperty('z-index') - - btn.update({ - iconURL: maximizeIcon, - tooltip: { - content: 'Fullscreen', - global: true, - aladin - } - }); - } - - // hide all the controls - self.closeAll() - } - }), + }) }; } @@ -267,6 +210,8 @@ import { Toolbar } from "../Widgets/Toolbar"; let panel = this.panels[name]; panel && panel._hide(); } + + this.controls.projection.hideMenu() } enable(name) { diff --git a/src/js/gui/widgets/ActionButton.js b/src/js/gui/widgets/ActionButton.js index 896db686..e9fa48c3 100644 --- a/src/js/gui/widgets/ActionButton.js +++ b/src/js/gui/widgets/ActionButton.js @@ -79,11 +79,6 @@ export class ActionButton extends DOMElement { let el = document.createElement('button'); el.classList.add('aladin-btn'); - if (Utils.hasTouchScreen()) { - // Add a little padding - el.style.padding = "0.2em"; - } - // add it to the dom super(el, options); this._show(); @@ -101,6 +96,12 @@ export class ActionButton extends DOMElement { this.removeClass('toggled'); } + if (this.options.size === 'small') { + this.addClass('small-sized-icon') + } else { + this.addClass('medium-sized-icon') + } + if (this.options.action) { this.action = (e) => { e.stopPropagation(); @@ -157,8 +158,13 @@ export class ActionButton extends DOMElement { } static createIconBtn(opt, target, position = 'beforeend') { - let btn = new ActionButton(opt, target, position); - btn.addClass('medium-sized-icon'); + let btn = new ActionButton({...opt, size: 'medium'}, target, position); + + return btn; + } + + static createSmallSizedIconBtn(opt, target, position = 'beforeend') { + let btn = new ActionButton({...opt, size: 'small'}, target, position); return btn; } diff --git a/src/js/gui/widgets/ContextMenu.js b/src/js/gui/widgets/ContextMenu.js index 57c5ae4d..ece6696b 100644 --- a/src/js/gui/widgets/ContextMenu.js +++ b/src/js/gui/widgets/ContextMenu.js @@ -50,10 +50,14 @@ export class ContextMenu extends DOMElement { this.cssStyleDefault = el.style; - if (!options || options.hideOnClick === undefined || options.hideOnClick === true) { + if (!options || options.hideOnClick === undefined || options.hideOnClick === true || typeof options.hideOnClick === 'function') { this.aladin.aladinDiv.addEventListener('click', (e) => { if (!el.contains(e.target)) { - this._hide() + if (options && options.hideOnClick && typeof options.hideOnClick === 'function') { + options.hideOnClick(e) + } else { + this._hide() + } } }); } @@ -70,13 +74,15 @@ export class ContextMenu extends DOMElement { }) } } else { - window.addEventListener('resize', () => { + new ResizeObserver(() => { this._hide() }) + .observe(this.aladin.aladinDiv) } } } + static lastHoveredItem; _attachOption(target, opt, e, cssStyle) { let item = document.createElement('li'); item.classList.add('aladin-context-menu-item'); @@ -110,9 +116,8 @@ export class ContextMenu extends DOMElement { if (opt.label.icon) { // add a button with a little bit of margin let icon = new ActionButton({ - ...opt.label.icon, + ...{...opt.label.icon, size: 'small'}, }); - icon.addClass('medium-sized-icon'); layout.push(icon) } @@ -195,16 +200,17 @@ export class ContextMenu extends DOMElement { } }); } else if (opt.action) { - item.addEventListener('click', o => { - o.stopPropagation(); + item.addEventListener('click', e => { + e.preventDefault(); + e.stopPropagation(); if (!opt.disabled || opt.disabled === false) { if (!opt.subMenu || opt.subMenu.length === 0) { - opt.action(e); - if ((opt.mustHide === undefined || opt.mustHide === true) && (!self.options || self.options.hideOnClick === undefined || self.options.hideOnClick === true)) { self._hide(); } + + opt.action(e); } } }); @@ -226,24 +232,37 @@ export class ContextMenu extends DOMElement { opt.subMenu.forEach(subOpt => this._attachOption(subMenu, subOpt, e, cssStyle)); } + const areSiblings = (elm1, elm2) => (elm1 !== elm2 && elm1.parentNode == elm2.parentNode); + item.addEventListener('mouseover', e => { + e.stopPropagation(); + e.preventDefault(); - if (opt.hover) { - item.addEventListener('mouseover', e => { - e.stopPropagation(); - e.preventDefault(); - + if (opt.hover) { opt.hover(e, item); - }) - } + } - if (opt.unhover) { - item.addEventListener('mouseout', e => { - e.stopPropagation(); - e.preventDefault(); + if (ContextMenu.lastHoveredItem) { + let parent = ContextMenu.lastHoveredItem.parentNode; + if (parent && (areSiblings(parent, item) || item.contains(parent) || item === parent)) { + ContextMenu.lastHoveredItem.style.display = 'none'; + } + } + const subMenu = item.querySelector('.aladin-context-sub-menu'); + if (subMenu) { + subMenu.style.display = 'block'; + ContextMenu.lastHoveredItem = subMenu; + } + }) + + item.addEventListener('mouseout', e => { + e.stopPropagation(); + e.preventDefault(); + + if (opt.unhover) { opt.unhover(e, item); - }) - } + } + }) target.appendChild(item); } diff --git a/src/js/gui/widgets/Input.js b/src/js/gui/widgets/Input.js index 73f6b616..61ed427c 100644 --- a/src/js/gui/widgets/Input.js +++ b/src/js/gui/widgets/Input.js @@ -173,11 +173,11 @@ export class Input extends DOMElement { Tooltip.add(this.options.tooltip, this) } - // Add padding for inputs except color ones + /*// Add padding for inputs except color ones if (Utils.hasTouchScreen() && layout.type !== "color") { // Add a little padding this.el.style.padding = "0.5em"; - } + }*/ super._show() } diff --git a/src/js/gui/widgets/Selector.js b/src/js/gui/widgets/Selector.js index 0e115a90..76767a01 100644 --- a/src/js/gui/widgets/Selector.js +++ b/src/js/gui/widgets/Selector.js @@ -78,13 +78,7 @@ export class SelectorButton extends DOMElement { let optSelect = this.options[id]; menuOptions.push({ - label: new ActionButton(optSelect), - cssStyle: { - padding: 0, - height: 'fit-content', - margin: 0, - border: 'none', - }, + label: ActionButton.createSmallSizedIconBtn(optSelect), action(e) { if(optSelect.change) { optSelect.change(e) @@ -140,10 +134,8 @@ export class SelectorButton extends DOMElement { // remove from the DOM tree const selectedId = this.options.selected; let {target, position} = this.remove(); - - this.el = new ActionButton({ + this.el = ActionButton.createSmallSizedIconBtn({ ...this.options[selectedId], - tooltip: this.options.tooltip, action: (e) => { if (self.fsm.state === 'openCtxMenu') { self.fsm.dispatch('closeCtxMenu'); diff --git a/src/js/gui/widgets/Tooltip.js b/src/js/gui/widgets/Tooltip.js index 8e9a67e8..aca97a79 100644 --- a/src/js/gui/widgets/Tooltip.js +++ b/src/js/gui/widgets/Tooltip.js @@ -174,28 +174,19 @@ export class Tooltip extends DOMElement { if (!statusBar) { return; } - let removeMsgFunc = () => { - statusBar.removeMessage('tooltip') - } - let timeoutId; + // handle global tooltip div display Utils.on(target.el, 'mouseover', (e) => { + statusBar.removeMessage('tooltip') statusBar.appendMessage({ id: 'tooltip', message: options.content, duration: 'unlimited', type: 'tooltip' }) - if (timeoutId) { - clearTimeout(timeoutId) - } - timeoutId = setTimeout(removeMsgFunc, 1000) }); Utils.on(target.el, 'mouseout', (e) => { statusBar.removeMessage('tooltip') - if (timeoutId) { - clearTimeout(timeoutId) - } }); return; } diff --git a/src/js/gui/widgets/Widget.js b/src/js/gui/widgets/Widget.js index b6d4c094..b6bb5cf1 100644 --- a/src/js/gui/widgets/Widget.js +++ b/src/js/gui/widgets/Widget.js @@ -145,8 +145,8 @@ export class DOMElement { return; } - let left = 0, top = 0, bottom, right; - let x, y; + let left, top, bottom, right; + let x = 0, y = 0; // handle the anchor/dir case with higher priority const {offsetWidth, offsetHeight} = el; @@ -171,6 +171,19 @@ export class DOMElement { if (options.right !== undefined) { right = options.right; } + + if (typeof top === 'number') { + top = top + 'px'; + } + if (typeof bottom === 'number') { + bottom = bottom + 'px'; + } + if (typeof left === 'number') { + left = left + 'px'; + } + if (typeof right === 'number') { + right = right + 'px'; + } } else if (options && options.nextTo && options.direction) { let dir = options.direction || 'right'; let nextTo = options.nextTo; @@ -207,47 +220,55 @@ export class DOMElement { top = 0; break; } - } - // Translate if the div in - if (left + offsetWidth > innerWidth) { - x = '-' + (left + offsetWidth - innerWidth) + 'px'; - } + // Translate if the div in + if (left + offsetWidth > innerWidth) { + x = '-' + (left + offsetWidth - innerWidth) + 'px'; + } else if (left < 0) { + x = Math.abs(left) + 'px'; + } - if (top + offsetHeight >= innerHeight) { - y = '-' + (top + offsetHeight - innerHeight) + 'px'; - } - - if (left < 0) { - x = Math.abs(left) + 'px'; - } - - if (top < 0) { - y = Math.abs(top) + 'px'; + if (top + offsetHeight >= innerHeight) { + y = '-' + (top + offsetHeight - innerHeight) + 'px'; + } else if (top < 0) { + y = Math.abs(top) + 'px'; + } + + if (bottom) { + bottom = bottom + 'px'; + } + if (top) { + top = top + 'px'; + } + if (left) { + left = left + 'px'; + } + if (right) { + right = right + 'px'; + } } if (bottom !== undefined) { - el.style.bottom = bottom + 'px'; - } else { - el.style.top = top + 'px'; + el.style.bottom = bottom; + } + if (top !== undefined) { + el.style.top = top; + } + if (left !== undefined) { + el.style.left = left; } - if (right !== undefined) { - el.style.right = right + 'px'; - } else { - el.style.left = left + 'px'; + el.style.right = right; } - if (x && y) + if (x && y) { el.style.transform = `translate(${x}, ${y})`; + } } _show() { this.el.style.display = 'block'; this.isHidden = false; - - // recursively - //this._updateTooltipAfterInsertion(); } _hide() {