import { Control } from 'ol/control';
import noUiSlider from 'nouislider';
import 'nouislider/distribute/nouislider.css';
import { guidGenerator } from '../helpers';
import { Resources } from '../../resources/resources';
const steps = [
{
step: 1,
class: "x1"
},
{
step: 0.5,
class: "x2"
},
{
step: 0.2,
class: "x3"
},
{
step: 0.1,
class: "x4"
},
{
step: 0.05,
class: "x5"
}
];
export
/**
* Creates a new dimension selector control
* @alias DimensionSelector
* @memberof PMA.UI.View.Controls
* @param {object} opt_options Options to initialize
* @param {Viewport} opt_options.pmaViewport Viewport instance this control belongs to
* @param {string} opt_options.tipLabel Label for the button
* @param {boolean} opt_options.collapsed Whether the control starts collapsed
* @param {Object} opt_options.stateManager The state manager to keep settings in sync
*/
class DimensionSelector extends Control {
constructor(opt_options) {
var options = opt_options || {};
var element = document.createElement('div');
super({
element: element,
target: options.target
});
this.options = options;
this.pmaViewport = options.pmaViewport;
this.stateManager = options.stateManager ? options.stateManager : null;
if (this.stateManager) {
if (!this.stateManager.dimensionSelector) {
this.stateManager.dimensionSelector = {};
this.stateManager.dimensionSelector.collapsed = (options.collapsed) ? options.collapsed : false;
}
this.collapsed_ = this.stateManager.dimensionSelector.collapsed === true;
}
else {
this.collapsed_ = (options.collapsed) ? options.collapsed : false;
}
this.sliders = [];
this.slidersValues = [];
this.currentStep = 0;
var className = (options.className) ? options.className : 'ol-dimension-selector';
this.tipLabel = (options.tipLabel) ? options.tipLabel : 'Channels';
var collapseLabel = (options.collapseLabel) ? options.collapseLabel : '\u00BB';
this.collapseLabel_ = document.createElement('span');
this.collapseLabel_.innerHTML = collapseLabel;
var label = (options.label) ? options.label : '\u00AB';
this.label_ = document.createElement('span');
this.label_.innerHTML = label;
var activeLabel = this.collapsed_ ? this.collapseLabel_ : this.label_;
var button = document.createElement('button');
button.type = 'button';
button.title = this.tipLabel;
button.appendChild(activeLabel);
button.className = "collapse-button";
button.style = "float: left";
this.btnEventUsed = 'click';
if ('ontouchstart' in document.documentElement) {
this.btnEventUsed = 'touchstart';
}
button.addEventListener(this.btnEventUsed, this.buttonClk.bind(this), false);
var cssClasses = className + ' ' + 'ol-unselectable ol-control ' + (this.collapsed_ ? ' ol-collapsed' : '');
this.dimensionsDiv = document.createElement("div");
this.dimensionsDiv.className = "ol-dimensions-container";
this.renderDimensionsControl();
this.dimensionControlsDiv = document.createElement("div");
this.dimensionControlsDiv.className = "ol-dimensions-container";
this.renderSelectors(true);
element.className = cssClasses;
if (this.dimensionsDiv && this.dimensionsDiv !== "") {
element.appendChild(this.dimensionsDiv);
}
if (this.dimensionControlsDiv && this.dimensionControlsDiv !== "") {
element.appendChild(this.dimensionControlsDiv);
}
element.appendChild(button);
var tipLabel = "";
var label_ = "";
tipLabel = "Decrease resolution";
label = '<i class="fa fa-minus" aria-hidden="true"></i>';
label_ = document.createElement("span");
label_.innerHTML = label;
this.lastSelection = -1;
//this.element.addEventListener("wheel", this.wheelEvent.bind(this), false);
this.pmaViewport.element.addEventListener("wheel", (e) => {
if (e.ctrlKey) {
this.wheelEvent(e, true);
}
if (e.shiftKey) {
this.wheelEvent(e, false);
}
}, false);
/*this.pmaViewport.listen("change:dimension", function () {
_this.destroySliders();
_this.renderDimensionsControl();
_this.renderSelectors(false);
_this.renderSliders(_this.currentStep);
});*/
}
buttonClk(event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
if ((" " + this.element.className + " ").indexOf(' ol-collapsed ') > -1) {
this.element.className = this.element.className.replace(/ol-collapsed/g, '');
}
else {
this.element.className += ' ol-collapsed';
}
if (!isBrightfield(this.pmaViewport.imageInfo.TimeFrames[0].Layers[0])) {
if (!this.collapsed_) {
this.label_.parentNode.replaceChild(this.collapseLabel_, this.label_);
} else {
this.collapseLabel_.parentNode.replaceChild(this.label_, this.collapseLabel_);
}
}
this.collapsed_ = !this.collapsed_;
if (this.stateManager) {
this.stateManager.dimensionSelector.collapsed = this.collapsed_;
}
}
/**
* Gets the collapsed state of the control
* @return {boolean} True if the control is currently collapsed
*/
getCollapsed() {
return (" " + this.element.className + " ").indexOf(' ol-collapsed ') > -1;
}
/**
* Sets the collapsed state of the control
* @param {boolean} collapsed - True to collapse the control, otherwise false
*/
setCollapsed(collapsed) {
if (this.getCollapsed() != collapsed) {
this.buttonClk();
}
}
renderDimensionsControl() {
// assign change event to the channel check boxes
function chkChange() {
// assign the selected channels back to the viewport
var chans = [];
var chks = this.dimensionsDiv.querySelectorAll(".ol-dimensions-container ul input[type='checkbox']");
for (var i = 0; i < chks.length; i++) {
if (chks[i].checked) {
chans.push(i);
this.lastSelection = i;
}
}
if (chans.length === 0) {
chks[this.lastSelection].checked = true;
chans.push(this.lastSelection);
}
this.pmaViewport.setActiveChannels(chans);
}
this.sliders = [];
if (!isBrightfield(this.pmaViewport.imageInfo.TimeFrames[0].Layers[0])) {
var channelsHtml = "<div id='channels'>" + this.tipLabel + "</div><div id='step'></div><ul>";
const rendOpts = this.pmaViewport.getChannelRenderingOptions();
for (var i = 0; i < this.pmaViewport.imageInfo.TimeFrames[0].Layers[0].Channels.length; i++) {
const guid = guidGenerator();
this.sliders.push(guid);
var c = this.pmaViewport.imageInfo.TimeFrames[0].Layers[0].Channels[i];
this.slidersValues.push(rendOpts[i].clipping);
channelsHtml += "<li style='background-color: #" + rendOpts[i].color.substr(2, 6) + "'>";
channelsHtml += "<span class='channel-span'><label title='" + rendOpts[i].name + "' for='" + `${guid}_channel_checkbox` + "'>" + rendOpts[i].name + "</label><input id='" + `${guid}_channel_checkbox` + "' type='checkbox' " + (c.Active ? "checked='checked'" : "") + " />";
channelsHtml += "<button id='" + `${guid}_color_reset` + "' title=\"Click to reset channel's color to initial value\" class='color-reset' style='" + (this.options.supportsRenderingOptions ? "" : "display: none") + "'><i class='fa fa-undo' aria-hidden='true'></i></button>";
channelsHtml += "<input id='" + `${guid}_color_selector` + "' title=\"Click to select channel's color\" type='color' class='color-selector' style='" + (this.options.supportsRenderingOptions ? "" : "display: none") + "' data-default-color='#" + rendOpts[i].defaultColor.substr(2, 6) + "' value='#" + rendOpts[i].color.substr(2, 6) + "' />";
channelsHtml += "</span>";
if (c.Name !== "Default") {
channelsHtml += "<span class='slider-span' id='" + `${guid}_slider` + "'></span>";
}
channelsHtml += "</li>";
}
channelsHtml += "</ul>";
if (this.sliders.length > 0) {
channelsHtml += "<div style='display: block; height: 25px;'>";
channelsHtml += "<button id='increaseButton' type='button' title='Increase resolution' style='float: right;'>";
channelsHtml += "<span style='font-size: 10px'><i class='fa fa-plus' aria-hidden='true'></i></span>";
channelsHtml += "</button>";
channelsHtml += "<button id='decreaseButton' type='button' title='Decrease resolution' style='float: right;'>";
channelsHtml += "<span style='font-size: 10px'><i class='fa fa-minus' aria-hidden='true'></i></span>";
channelsHtml += "</button>";
channelsHtml += "</div>";
}
this.dimensionsDiv.innerHTML = channelsHtml;
var checkboxes = this.dimensionsDiv.querySelectorAll(".ol-dimensions-container ul input[type='checkbox']");
for (i = 0; i < checkboxes.length; i++) {
checkboxes[i].addEventListener('change', chkChange.bind(this), false);
}
if (this.sliders.length > 0) {
var _this = this;
this.dimensionsDiv.querySelector("#increaseButton").addEventListener('click', function () {
_this.destroySliders();
_this.renderDimensionsControl();
_this.renderSelectors(false);
_this.currentStep = _this.currentStep < 4 ? _this.currentStep + 1 : _this.currentStep;
_this.renderSliders(_this.currentStep);
}, false);
this.dimensionsDiv.querySelector("#decreaseButton").addEventListener('click', function () {
_this.destroySliders();
_this.renderDimensionsControl();
_this.renderSelectors(false);
_this.currentStep = _this.currentStep > 0 ? _this.currentStep - 1 : _this.currentStep;
_this.renderSliders(_this.currentStep);
}, false);
}
}
}
renderSelectors(initialRender) {
function dimensionChange(e) {
if (e.target.name == "timeframe") {
this.pmaViewport.setActiveTimeFrame(e.target.value);
}
else if (e.target.name == "zstack") {
this.pmaViewport.setActiveLayer(e.target.value);
}
if (document.getElementById("zstackSlider")) {
document.getElementById("zstackLabel").innerHTML = Resources.translate("Z-stack: <br /> {stack} / {total}", { stack: (this.pmaViewport.getActiveLayer() + 1), total: this.pmaViewport.imageInfo.TimeFrames[this.pmaViewport.getActiveTimeFrame()].Layers.length });
}
if (document.getElementById("timeframeSlider")) {
document.getElementById("timeframeLabel").innerHTML = Resources.translate("Time frame: <br /> {frame} / {total}", { frame: (this.pmaViewport.getActiveTimeFrame() + 1), total: this.pmaViewport.imageInfo.TimeFrames.length });
}
}
function prevNextClicked(e) {
var tf = this.pmaViewport.getActiveTimeFrame();
var l = this.pmaViewport.getActiveLayer();
if (e.target.name == "prev-timeframe") {
tf--;
if (tf < 0) {
tf = this.pmaViewport.imageInfo.TimeFrames.length - 1;
}
this.pmaViewport.setActiveTimeFrame(tf);
}
else if (e.target.name == "next-timeframe") {
tf++;
if (tf >= this.pmaViewport.imageInfo.TimeFrames.length) {
tf = 0;
}
this.pmaViewport.setActiveTimeFrame(tf);
}
else if (e.target.name == "prev-zstack") {
l--;
if (l < 0) {
l = this.pmaViewport.imageInfo.TimeFrames[tf].Layers.length - 1;
}
this.pmaViewport.setActiveLayer(l);
}
else if (e.target.name == "next-zstack") {
l++;
if (l >= this.pmaViewport.imageInfo.TimeFrames[tf].Layers.length) {
l = 0;
}
this.pmaViewport.setActiveLayer(l);
}
if (document.getElementById("zstackSlider")) {
document.getElementById("zstackSlider").value = l;
document.getElementById("zstackLabel").innerHTML = Resources.translate("Z-stack: <br /> {stack} / {total}", { stack: (this.pmaViewport.getActiveLayer() + 1), total: this.pmaViewport.imageInfo.TimeFrames[this.pmaViewport.getActiveTimeFrame()].Layers.length });
}
if (document.getElementById("timeframeSlider")) {
document.getElementById("timeframeSlider").value = tf;
document.getElementById("timeframeLabel").innerHTML = Resources.translate("Time frame: <br /> {frame} / {total}", { frame: (this.pmaViewport.getActiveTimeFrame() + 1), total: this.pmaViewport.imageInfo.TimeFrames.length });
}
}
var HTMLstring = "";
if (this.pmaViewport.imageInfo.TimeFrames.length > 1) {
HTMLstring += "<div id=\"timeframeLabel\">" + Resources.translate("Time frame: <br /> {frame} / {total}", { frame: (this.pmaViewport.getActiveTimeFrame() + 1), total: this.pmaViewport.imageInfo.TimeFrames.length }) + "</div>";
HTMLstring += "<button name='prev-timeframe' class='prev-next-button' title=\"Click to switch to previous time frame\"><</button>";
HTMLstring += "<input type=\"range\" min=\"0\" max=\"" + (this.pmaViewport.imageInfo.TimeFrames.length - 1) + "\" value=\"" + this.pmaViewport.getActiveTimeFrame() + "\" name=\"timeframe\" id=\"timeframeSlider\" title=\"Drag to switch between time frames (Shift + MouseWheel)\">";
HTMLstring += "<button name='next-timeframe' class='prev-next-button' title=\"Click to switch to next time frame\">></button>";
}
var tf = this.pmaViewport.imageInfo.TimeFrames[this.pmaViewport.getActiveTimeFrame()];
if (tf.Layers.length > 1) {
// if it's the first time we draw the control, select the middle z-stack layer
if (initialRender) {
this.pmaViewport.setActiveLayer((Math.round((tf.Layers.length - 1) / 2) | 0));
}
var selectedZStack = this.pmaViewport.getActiveLayer();
HTMLstring += "<div id=\"zstackLabel\">" + Resources.translate("Z-stack: <br /> {stack} / {total}", { stack: (selectedZStack + 1), total: tf.Layers.length }) + "</div>";
HTMLstring += "<button name='prev-zstack' class='prev-next-button' title=\"Click to switch to previous z-stack\"><</button>";
HTMLstring += "<input type=\"range\" min=\"0\" max=\"" + (tf.Layers.length - 1) + "\" value=\"" + selectedZStack + "\" name=\"zstack\" id=\"zstackSlider\" title=\"Drag to switch between z-stacks (Ctrl + MouseWheel)\">";
HTMLstring += "<button name='next-zstack' class='prev-next-button' title=\"Click to switch to next z-stack\">></button>";
}
if (HTMLstring !== "") {
this.dimensionControlsDiv.innerHTML = "<div>" + HTMLstring + "</div>";
var selects = this.dimensionControlsDiv.querySelectorAll(".ol-dimensions-container input[type=range]");
for (var k = 0; k < selects.length; k++) {
selects[k].addEventListener('input', dimensionChange.bind(this), false);
}
var btns = this.dimensionControlsDiv.querySelectorAll(".ol-dimensions-container .prev-next-button");
for (k = 0; k < btns.length; k++) {
btns[k].addEventListener(this.btnEventUsed, prevNextClicked.bind(this), false);
}
if (this.dimensionControlsDiv.querySelector("#zstackSlider")) this.dimensionControlsDiv.querySelector("#zstackSlider").addEventListener('wheel', (e) => this.wheelEvent(e, true), false);
if (this.dimensionControlsDiv.querySelector("#timeframeSlider")) this.dimensionControlsDiv.querySelector("#timeframeSlider").addEventListener('wheel', (e) => this.wheelEvent(e, false), false);
}
}
wheelEvent(e, zStack) {
e.preventDefault();
if ((" " + this.element.className + " ").indexOf(' ol-collapsed ') > -1) {
return;
}
if (!this.pmaViewport.imageInfo.TimeFrames || (this.pmaViewport.imageInfo.TimeFrames.length === 1 && this.pmaViewport.imageInfo.TimeFrames[0].Layers && this.pmaViewport.imageInfo.TimeFrames[0].Layers.length === 1)) {
return;
}
var tf = this.pmaViewport.getActiveTimeFrame();
var l = this.pmaViewport.getActiveLayer();
if (e.deltaY && e.deltaY < 0) {
if (zStack) {
l--;
if (l < 0) {
////l = this.pmaViewport.imageInfo.TimeFrames[tf].Layers.length - 1;
l = 0;
}
this.pmaViewport.setActiveLayer(l);
} else {
tf--;
if (tf < 0) {
tf = 0;
}
this.pmaViewport.setActiveLayer(l);
this.pmaViewport.setActiveTimeFrame(tf);
}
}
else if (e.deltaY && e.deltaY > 0) {
if (zStack) {
l++;
if (l >= this.pmaViewport.imageInfo.TimeFrames[tf].Layers.length) {
////l = 0;
l = this.pmaViewport.imageInfo.TimeFrames[tf].Layers.length - 1;
}
this.pmaViewport.setActiveLayer(l);
} else {
tf++;
if (tf >= this.pmaViewport.imageInfo.TimeFrames.length) {
////l = 0;
tf = this.pmaViewport.imageInfo.TimeFrames.length - 1;
}
this.pmaViewport.setActiveLayer(l);
this.pmaViewport.setActiveTimeFrame(tf);
}
}
if (document.getElementById("zstackSlider")) {
document.getElementById("zstackSlider").value = l;
document.getElementById("zstackLabel").innerHTML = Resources.translate("Z-stack: <br /> {stack} / {total}", { stack: (this.pmaViewport.getActiveLayer() + 1), total: this.pmaViewport.imageInfo.TimeFrames[this.pmaViewport.getActiveTimeFrame()].Layers.length });
}
if (document.getElementById("timeframeSlider")) {
document.getElementById("timeframeSlider").value = tf;
document.getElementById("timeframeLabel").innerHTML = Resources.translate("Time frame: <br /> {frame} / {total}", { frame: (this.pmaViewport.getActiveTimeFrame() + 1), total: this.pmaViewport.imageInfo.TimeFrames.length });
}
}
round(value, precision) {
var multiplier = Math.pow(10, precision || 0);
return Math.round(value * multiplier) / multiplier;
}
renderSliders(currentStep = 0) {
if (this.sliders.length === 0) {
return;
}
const self = this;
for (var i = 0; i < this.sliders.length; i++) {
var start;
if (this.slidersValues[i]) {
start = this.slidersValues[i];
}
else {
start = [0, 100];
}
const guid = this.sliders[i];
var slider = document.getElementById(`${guid}_slider`);
if (slider) {
slider.title = "Drag to adjust channel clipping";
steps.forEach(s => {
slider.parentElement.classList.remove(s.class);
});
slider.parentElement.classList.add(steps[currentStep].class);
document.getElementById("step").innerText = "Step: " + steps[currentStep].step;
noUiSlider.create(slider, {
start: start,
tooltips: [true, true],
step: steps[currentStep].step,
connect: true,
margin: steps[currentStep].step,
keyboardSupport: true,
range: {
'min': 0,
'max': 100
},
format: {
to: function (value) {
return self.round(value, 2).toFixed(2);
},
from: function (value) {
return self.round(value, 2).toFixed(2);
}
}
});
slider.noUiSlider.channelIndex = i;
slider.noUiSlider.on("change", function (value) {
const colorValue = document.getElementById(this.target.id.replace("slider", "color_selector")).value;
self.pmaViewport.setChannelRenderingOptions({ index: this.channelIndex, clipping: value, color: colorValue.replace('#', 'ff') });
});
}
var colorSelector = document.getElementById(`${guid}_color_selector`);
if (colorSelector) {
$(colorSelector).data("channel", i);
colorSelector.addEventListener("change", function (ev) {
var clippingValue;
const clippingElement = document.getElementById(ev.target.id.replace("color_selector", "slider"));
if (clippingElement) {
const clippingSlider = clippingElement.noUiSlider;
if (clippingSlider) {
clippingValue = clippingSlider.get();
}
}
if (!clippingValue) clippingValue = ["0.00", "100.00"];
document.getElementById(ev.target.id.replace("color_selector", "slider")).parentElement.style = "background-color: " + ev.target.value;
self.pmaViewport.setChannelRenderingOptions({ index: $(ev.target).data("channel"), clipping: clippingValue, color: ev.target.value.replace('#', 'ff') });
});
}
var colorReset = document.getElementById(`${guid}_color_reset`);
if (colorReset) {
$(colorReset).data("channel", i);
colorReset.addEventListener("click", function (ev) {
this.blur();
const defaultColor = $(document.getElementById(ev.currentTarget.id.replace("color_reset", "color_selector"))).data("default-color");
document.getElementById(ev.currentTarget.id.replace("color_reset", "color_selector")).value = defaultColor;
var clippingValue;
const clippingElement = document.getElementById(ev.currentTarget.id.replace("color_reset", "slider"));
if (clippingElement) {
const clippingSlider = clippingElement.noUiSlider;
if (clippingSlider) {
clippingValue = clippingSlider.get();
}
}
if (!clippingValue) clippingValue = ["0.00", "100.00"];
document.getElementById(ev.currentTarget.id.replace("color_reset", "slider")).parentElement.style = "background-color: " + defaultColor;
self.pmaViewport.setChannelRenderingOptions({ index: $(ev.currentTarget).data("channel"), clipping: clippingValue, color: defaultColor.replace('#', 'ff') });
});
}
}
}
destroySliders() {
for (var i = 0; i < this.sliders.length; i++) {
var slider = document.getElementById(`${this.sliders[i]}_slider`);
this.slidersValues[i] = slider.noUiSlider.get();
slider.noUiSlider.destroy();
}
this.sliders = [];
}
}
export function isBrightfield(pmaViewportInfo) {
return pmaViewportInfo.Channels.length === 1 &&
pmaViewportInfo.Channels[0].Color === 'ffffffff' &&
(pmaViewportInfo.Channels[0].Name === 'Default' || pmaViewportInfo.Channels[0].Name === '');
}