import iro from '@jaames/iro';
import * as Utils from "./Utils.js";
import { UiElement } from "../UiElement.js";
import { ColorScaleDropdown } from "./ColorScaleDropdown.js";
import { ColorSlider } from "./ColorSlider.js";
import { roundNumber } from "../../utils/utils.js";
import "./ColorScalePicker.css";
export { ColorScalePicker };
/**
* Create custom color scales in the Ui using a color picker
*
* @author rhess <robin.hess@awi.de>
* @memberof vef.ui.color
*/
class ColorScalePicker extends UiElement {
/**
* options = {
* maxItems: number (default: 5)
* min: number (default: 0),
* max: number (default: 1)
* }
*
* @param {string/HTMLElement} target
* @param {object} options
*/
constructor(target, options) {
super(target, {
"apply": []
});
const defaultOptions = {
maxItems: 5,
min: 0,
max: 1
};
this.options_ = { ...defaultOptions, ...options };
this.items_ = [];
this.presetDropdown_ = null;
this.presetSelected_ = false;
this.initElement_();
}
/**
* Add a color item to the scale
*
* @param {object} item color item
*/
addColor(item) {
if (this.presetDropdown_) this.presetDropdown_.deselect();
if (this.colorSlider_.getLength() >= this.options_.maxItems) return null;
item = (item) ? Utils.validateColorItem(item) : {};
const handle = this.colorSlider_.addHandle(item);
this.colorSlider_.selectHandle(handle);
this.updateAddButton_();
this.updateSliderVisibility_();
}
/**
* Change config of an existing color item
*
* @param {object} item new item config
* @param {number} index index of item
*/
setColor(item, index) {
if (this.presetDropdown_) this.presetDropdown_.deselect();
const handle = this.colorSlider_.getHandle(index);
item = Object.assign(handle, Utils.validateColorItem(item));
this.colorSlider_.update();
}
/**
* Delete a color item from the color scale
*
* @param {number} index index of item
*/
deleteColor(index) {
if (this.presetDropdown_) this.presetDropdown_.deselect();
this.colorSlider_.removeHandle(this.colorSlider_.getHandle(index));
this.updateAddButton_();
this.updateSliderVisibility_();
}
/**
* Get the color scale as an array
*/
getColorScale() {
return this.colorSlider_.getColorScale();
}
/**
* Set the scale of the color chooser
* based of an existing Array of color items
*
* @param {object[]} scale color scale array
*/
setScale(scale) {
if (!scale) {
this.clearItems();
} else {
if (this.presetDropdown_) this.presetDropdown_.deselect();
this.colorSlider_.setColorScale(scale)
this.updateAddButton_();
this.updateSliderVisibility_();
}
}
/**
* Remove all items from the ColorChooser
*/
clearItems() {
if (this.presetDropdown_) this.presetDropdown_.deselect();
this.colorSlider_.clear();
this.updateAddButton_();
this.updateSliderVisibility_();
}
/**
* set presets to be selected by the user from a
* dropdown list
*
* @param {object[]} presets
*/
setPresets(presets) {
if (this.presetDropdown_) this.presetDropdown_.getElement().remove();
this.presetDropdown_ = new ColorScaleDropdown(null, {
items: presets,
placeholder: "select a color scale ...",
showDeselectItem: false
});
this.presetDropdown_.on("select", payload => {
const scale = payload.item;
this.colorSlider_.setColorScale(scale);
this.updateAddButton_();
this.updateSliderVisibility_();
});
this.presetDropdown_.appendTo(this.getElement().querySelector(".preset-container"));
}
/**
* show the color picker for a given color item object
*
* @param {object} item
* @private
*/
showColorPicker_(item) {
this.colorPickerElement_.style.display = "block";
const selectedItem = this.colorSlider_.getSelectedHandle();
// storing opacity, because "set" changes item.opacity
const opacity = selectedItem.opacity;
this.colorPicker_.color.set(selectedItem.color);
this.colorPicker_.color.alpha = opacity;
}
/**
* check if the add-button has to be hidden, depending on the amount
* of colors compared to options.maxItems
*
* @private
*/
updateAddButton_() {
if (this.colorSlider_.getLength() < this.options_.maxItems) {
this.getElement().querySelector(".btn-add-color").disabled = false;
} else {
this.getElement().querySelector(".btn-add-color").disabled = true;
}
}
/**
* check if the slider has to be hidden, if there are no items
*
* @private
*/
updateSliderVisibility_() {
const get = sel => this.getElement().querySelector(sel);
if (this.colorSlider_.getLength() == 0) {
get(".color-slider-placeholder").style.display = "none";
get(".min-max-form").style.display = "none";
} else {
get(".color-slider-placeholder").style.display = "block";
get(".min-max-form").style.display = "block";
}
}
/**
* hide the color picker element
*
* @private
*/
hideColorPicker_() {
this.colorPickerElement_.style.display = "none";
}
/**
* Initialize the main Ui of the color chooser.
* Called only once in the constructor
*
* @private
*/
initElement_() {
const element = this.getElement();
element.classList.add("color-scale-picker");
element.insertAdjacentHTML("beforeend", `
<div class="preset-container"></div>
<div class="min-max-form">
<button title="stretch the scale with equal distances in the given range" class="btn-linear-stretch"><i class="fas fa-ruler"></i>Stretch</button>
<button title="invert the color scale order" class="btn-invert"><i class="fas fa-exchange-alt"></i> Invert</button>
<button title="remove overhang range (adjust min and max)" class="btn-adjust-range"><i class="fas fa-arrows-alt-h"></i> Clip Range</button>
<br/>
<input spellcheck="false" title="minimum value" class="min" type="text" value="${this.options_.min}"/><div class="separator">-</div><input title="maximum value" class="max" type="text" value="${this.options_.max}"/>
</div>
<div class="color-slider-placeholder"></div>
<div class="color-picker">
<div class="iro-color-picker"></div>
<div class="color-picker-form">
<span>Value</span><input spellcheck="false" type="text" class="value-input"/><br/>
<span>R</span><input spellcheck="false" type="text" class="color-picker-form-r"/><br/>
<span>G</span><input spellcheck="false" type="text" class="color-picker-form-g"/><br/>
<span>B</span><input spellcheck="false" type="text" class="color-picker-form-b"/><br/>
<span>HEX</span><input spellcheck="false" type="text" class="color-picker-form-hex"/>
<br/><button class="btn-delete-color"><i class="fas fa-trash-alt"></i> Delete</button>
</div>
</div>
<div class="footer-buttons">
<button class="btn-add-color"><i class="fas fa-plus"></i> Add Color</button><!--
--><button class="btn-clear"><i class="fas fa-times"></i> Clear</button><!--
--><button class="btn-apply-colors"><i class="fas fa-check"></i> Apply</button>
</div>
`);
this.colorSlider_ = new ColorSlider(element.querySelector(".color-slider-placeholder"));
// value range input with synchronisation
const btnMin = element.querySelector(".min-max-form .min");
const btnMax = element.querySelector(".min-max-form .max");
let activeFitToScale = false;
const fitToScale = () => {
const min = parseFloat(btnMin.value);
const max = parseFloat(btnMax.value);
if (Number.isFinite(min) && Number.isFinite(max)) {
this.colorSlider_.min_ = min;
this.colorSlider_.max_ = max;
activeFitToScale = true;
this.colorSlider_.update();
}
};
btnMin.addEventListener("input", fitToScale);
btnMax.addEventListener("input", fitToScale);
this.colorSlider_.on("update", () => {
if (activeFitToScale) {
activeFitToScale = false;
const min = parseFloat(btnMin.value);
const max = parseFloat(btnMax.value);
if (this.colorSlider_.min_ != min) {
btnMin.classList.add("error")
} else {
btnMin.classList.remove("error")
}
if (this.colorSlider_.max_ != max) {
btnMax.classList.add("error")
} else {
btnMax.classList.remove("error")
}
} else {
btnMax.classList.remove("error");
btnMin.classList.remove("error");
btnMax.value = roundNumber(this.colorSlider_.max_, 5);
btnMin.value = roundNumber(this.colorSlider_.min_, 5);
}
this.options_.min = this.colorSlider_.min_;
this.options_.max = this.colorSlider_.max_;
})
// general buttons
element.querySelector(".btn-adjust-range").addEventListener("click", () => {
this.colorSlider_.update(true);
}, false);
element.querySelector(".btn-linear-stretch").addEventListener("click", () => {
this.colorSlider_.stretchToScale(this.options_.min, this.options_.max);
}, false);
element.querySelector(".btn-invert").addEventListener("click", () => {
this.colorSlider_.invertScale();
}, false);
element.querySelector(".btn-add-color").addEventListener("click", () => {
this.addColor(null, true);
}, false);
element.querySelector(".btn-apply-colors").addEventListener("click", () => {
this.colorSlider_.update();
this.apply_();
}, false);
element.querySelector(".btn-clear").addEventListener("click", () => {
this.clearItems();
}, false);
element.querySelector(".btn-delete-color").addEventListener("click", () => {
if (this.presetDropdown_) this.presetDropdown_.deselect();
const selectedItem = this.colorSlider_.getSelectedHandle();
if (selectedItem) this.colorSlider_.removeHandle(selectedItem);
this.updateAddButton_();
this.updateSliderVisibility_();
}, false);
const colorPicker = new iro.ColorPicker(element.querySelector(".iro-color-picker"), {
width: 120,
color: "rgb(255, 0, 0)",
borderWidth: 1,
borderColor: "#fff",
layoutDirection: "horizontal",
layout: [
{
component: iro.ui.Wheel,
options: {}
},
{
component: iro.ui.Slider,
options: {
sliderType: 'value',
sliderSize: 10
}
},
{
component: iro.ui.Slider,
options: {
sliderType: 'alpha',
sliderSize: 10
}
}
]
});
// color channel input elements
const colorChannels = { "r": null, "g": null, "b": null };
for (let i in colorChannels) {
colorChannels[i] = element.querySelector(".color-picker-form-" + i);
colorChannels[i].addEventListener("change", () => {
colorPicker.color.setChannel('rgb', i, colorChannels[i].value);
});
}
const hex = element.querySelector(".color-picker-form-hex");
hex.addEventListener("change", () => {
colorPicker.color.set(hex.value);
});
const valueInput = element.querySelector(".value-input");
valueInput.addEventListener("input", () => {
const val = parseFloat(valueInput.value);
if (Number.isFinite(val)) {
const handle = this.colorSlider_.getSelectedHandle();
handle.value = val;
this.colorSlider_.update();
}
});
colorPicker.on(["color:init", "color:change"], (color) => {
if (this.presetDropdown_) this.presetDropdown_.deselect();
const selectedItem = this.colorSlider_.getSelectedHandle();
if (selectedItem) {
selectedItem.color = colorPicker.color.hexString;
selectedItem.opacity = colorPicker.color.alpha;
this.colorSlider_.update();
}
colorChannels["r"].value = color.rgb.r;
colorChannels["g"].value = color.rgb.g;
colorChannels["b"].value = color.rgb.b;
hex.value = color.hexString;
});
this.colorPicker_ = colorPicker;
this.colorPickerElement_ = element.querySelector(".color-picker");
const showValue = () => {
valueInput.value = this.colorSlider_.getSelectedHandle().value;
};
this.colorSlider_.on("select", () => {
this.showColorPicker_();
showValue();
});
this.colorSlider_.on("deselect", () => this.hideColorPicker_());
this.colorSlider_.on("slide", showValue);
this.colorSlider_.on("stop", showValue);
}
/**
* Trigger a change event and generate the event payload
* containing WMS params for O2A template styles
*
* @private
*/
apply_() {
this.fire("apply", this.getColorScale());
}
}