diff --git a/scss/_stylepicker.scss b/scss/_stylepicker.scss new file mode 100644 index 000000000..343d5efa4 --- /dev/null +++ b/scss/_stylepicker.scss @@ -0,0 +1,22 @@ +.o-stylepicker { + height: 1.25rem; + margin-right: 5px; + margin-bottom: 1.25rem; + min-width: 110px; +} + +.o-stylepicker-header { + margin-right: 5px; + margin-top: 1.25rem; +} + +.o-stylepicker button { + border-radius: 10px; + font-family: Arial, 'Helvetica Neue', sans-serif; + font-size: .625rem; + line-height: 1.25rem; +} + +.o-stylepicker .dropdown li:hover { + color: $grey-darker; +} \ No newline at end of file diff --git a/scss/origo.scss b/scss/origo.scss index 96653a69f..b0c48fee8 100644 --- a/scss/origo.scss +++ b/scss/origo.scss @@ -25,6 +25,7 @@ @import 'popover'; @import 'dropdown'; @import 'sidebar'; + @import 'stylepicker'; @import 'close'; @import 'icon'; @import 'tooltip'; diff --git a/src/controls/legend/overlay.js b/src/controls/legend/overlay.js index f14ad5666..a7cc57d47 100644 --- a/src/controls/legend/overlay.js +++ b/src/controls/legend/overlay.js @@ -3,10 +3,8 @@ import { HeaderIcon } from '../../utils/legendmaker'; import PopupMenu from '../../ui/popupmenu'; const OverlayLayer = function OverlayLayer(options) { - let { - headerIconCls = '' - } = options; const { + headerIconCls = '', cls: clsSettings = '', icon = '#o_list_24px', iconCls = 'grey-lightest', @@ -17,9 +15,13 @@ const OverlayLayer = function OverlayLayer(options) { } = options; const buttons = []; + let headerIconClass = headerIconCls; + const popupMenuItems = []; let layerList; + const hasStylePicker = viewer.getLayerStylePicker(layer).length > 0; + const layerIconCls = `round compact icon-small relative no-shrink light ${hasStylePicker ? 'style-picker' : ''}`; const cls = `${clsSettings} flex row align-center padding-left padding-right-smaller item`.trim(); const title = layer.get('title') || 'Titel saknas'; const name = layer.get('name'); @@ -39,7 +41,7 @@ const OverlayLayer = function OverlayLayer(options) { let headerIcon = HeaderIcon(style, opacity); if (!headerIcon) { headerIcon = icon; - headerIconCls = iconCls; + headerIconClass = iconCls; } const eventOverlayProps = new CustomEvent('overlayproperties', { @@ -68,15 +70,15 @@ const OverlayLayer = function OverlayLayer(options) { }; const layerIcon = Button({ - cls: `${headerIconCls} round compact icon-small light relative no-shrink`, + cls: `${headerIconClass} ${layerIconCls}`, click() { if (!secure) { toggleVisible(layer.getVisible()); } }, style: { - height: '1.5rem', - width: '1.5rem' + height: 'calc(1.5rem + 2px)', + width: 'calc(1.5rem + 2px)' }, ariaLabel: 'Lager ikon', icon: headerIcon, @@ -239,6 +241,16 @@ const OverlayLayer = function OverlayLayer(options) { el.remove(); }; + const onLayerStyleChange = function onLayerStyleChange() { + const newStyle = viewer.getStyle(layer.get('styleName')); + const layerIconCmp = document.getElementById(layerIcon.getId()); + let newIcon = HeaderIcon(newStyle, opacity); + headerIconClass = !newIcon ? iconCls : headerIconCls; + newIcon = !newIcon ? icon : newIcon; + layerIconCmp.className = `${headerIconClass} ${layerIconCls}`; + layerIcon.dispatch('change', { icon: newIcon }); + }; + return Component({ name, getLayer, @@ -277,6 +289,9 @@ const OverlayLayer = function OverlayLayer(options) { }); document.getElementById(this.getId()).dispatchEvent(visibleEvent); }); + layer.on('change:style', () => { + onLayerStyleChange(); + }); }, render() { return `
  • ${ButtonsHtml}
  • `; diff --git a/src/controls/legend/overlayproperties.js b/src/controls/legend/overlayproperties.js index 0ab68e33e..5c9ae276f 100644 --- a/src/controls/legend/overlayproperties.js +++ b/src/controls/legend/overlayproperties.js @@ -1,5 +1,6 @@ -import { Component, InputRange } from '../../ui'; +import { Component, InputRange, Dropdown } from '../../ui'; import { Legend } from '../../utils/legendmaker'; +import Style from '../../style'; const OverlayProperties = function OverlayProperties(options = {}) { const { @@ -15,6 +16,15 @@ const OverlayProperties = function OverlayProperties(options = {}) { const opacityControl = layer.get('opacityControl') !== false; const style = viewer.getStyle(layer.get('styleName')); const legend = Legend(style, opacity); + const stylePicker = viewer.getLayerStylePicker(layer); + + const legendComponent = Component({ + render() { + return `
    ${legend}
    `; + } + }); + + let styleSelection; let overlayEl; let sliderEl; let label = ''; @@ -31,6 +41,10 @@ const OverlayProperties = function OverlayProperties(options = {}) { label }); + function hasStylePicker() { + return stylePicker.length > 0; + } + function extendedLegendZoom(e) { const parentOverlay = document.getElementById(options.parent.getId()); @@ -43,14 +57,53 @@ const OverlayProperties = function OverlayProperties(options = {}) { } } + function renderStyleSelection() { + const html = `
    Välj stil
    ${styleSelection.render()}`; + return hasStylePicker() ? html : ''; + } + + function getStyleDisplayName(styleName) { + const altStyle = stylePicker.find(s => s.style === styleName); + return (altStyle && altStyle.title) || styleName; + } + + const onSelectStyle = (styleTitle) => { + const altStyleIndex = stylePicker.findIndex(s => s.title === styleTitle); + const altStyle = stylePicker[altStyleIndex]; + styleSelection.setButtonText(styleTitle); + const newStyle = Style.createStyle({ style: altStyle.style, clusterStyleName: altStyle.clusterStyle, viewer }); + const legendCmp = document.getElementById(legendComponent.getId()); + legendCmp.innerHTML = Legend(viewer.getStyle(altStyle.style), opacity); + if (!layer.get('defaultStyle')) layer.setProperties({ defaultStyle: layer.get('styleName') }); + layer.setProperties({ altStyleIndex }); + layer.setProperties({ styleName: altStyle.style }); + layer.setStyle(newStyle); + layer.dispatchEvent('change:style'); + }; + return Component({ onInit() { - this.addComponents([transparencySlider]); + styleSelection = Dropdown({ + direction: 'up', + cls: 'o-stylepicker text-black flex', + contentCls: 'bg-grey-lighter text-smaller rounded', + buttonCls: 'bg-white border text-black box-shadow', + buttonTextCls: 'text-smaller', + text: getStyleDisplayName(layer.get('styleName')), + buttonIconCls: 'black', + ariaLabel: 'Välj stil' + }); + const components = [transparencySlider]; + if (hasStylePicker()) { + components.push(styleSelection); + } + this.addComponents(components); this.on('click', (e) => { extendedLegendZoom(e); }); }, onRender() { + this.dispatch('render'); sliderEl = document.getElementById(transparencySlider.getId()); overlayEl = document.getElementById(this.getId()); overlayEl.addEventListener('click', (e) => { @@ -66,10 +119,21 @@ const OverlayProperties = function OverlayProperties(options = {}) { layer.setOpacity(sliderEl.valueAsNumber); }); } + if (hasStylePicker()) { + styleSelection.setItems(stylePicker.map(altStyle => altStyle.title)); + const styleSelectionEl = document.getElementById(styleSelection.getId()); + styleSelectionEl.addEventListener('dropdown:select', (evt) => { + onSelectStyle(evt.target.textContent); + }); + } }, render() { return `
    -
    ${legend}${transparencySlider.render()}
    +
    + ${legendComponent.render()} + ${renderStyleSelection()} + ${transparencySlider.render()} +
    ${abstract ? `
    ${abstract}
    ` : ''}
    `; }, diff --git a/src/permalink/permalinkparser.js b/src/permalink/permalinkparser.js index a3b82da4b..f991daff6 100644 --- a/src/permalink/permalinkparser.js +++ b/src/permalink/permalinkparser.js @@ -13,6 +13,10 @@ const layerModel = { o: { name: 'opacity', dataType: 'number' + }, + sn: { + name: 'altStyleIndex', + dataType: 'number' } }; @@ -31,12 +35,15 @@ const layers = function layers(layersStr) { }); Object.getOwnPropertyNames(layerObject).forEach((prop) => { const val = layerObject[prop]; - if (Object.prototype.hasOwnProperty.call(layerModel, prop) && prop !== 'o') { + if (Object.prototype.hasOwnProperty.call(layerModel, prop) && prop !== 'o' && prop !== 'sn') { const attribute = layerModel[prop]; obj[attribute.name] = urlparser.strBoolean(val); } else if (prop === 'o') { const attribute = layerModel[prop]; obj[attribute.name] = Number(val) / 100; + } else if (prop === 'sn') { + const attribute = layerModel[prop]; + obj[attribute.name] = Number(val); } else { obj[prop] = val; } diff --git a/src/permalink/permalinkstore.js b/src/permalink/permalinkstore.js index e6ff7f480..c64cc6cb2 100644 --- a/src/permalink/permalinkstore.js +++ b/src/permalink/permalinkstore.js @@ -11,6 +11,8 @@ function getSaveLayers(layers) { saveLayer.v = layer.getVisible() === true ? 1 : 0; saveLayer.s = layer.get('legend') === true ? 1 : 0; saveLayer.o = Number(layer.get('opacity')) * 100; + // Only get style for layer styles that have changed + if (layer.get('defaultStyle') && layer.get('defaultStyle') !== layer.get('styleName')) saveLayer.sn = layer.get('altStyleIndex'); if (saveLayer.s || saveLayer.v) { saveLayer.name = layer.get('name'); if (saveLayer.name !== 'measure') { diff --git a/src/ui/dropdown.js b/src/ui/dropdown.js index c88c5c090..21fad6ee7 100644 --- a/src/ui/dropdown.js +++ b/src/ui/dropdown.js @@ -15,7 +15,8 @@ export default function Dropdown(options = {}) { style: styleSettings, direction = 'down', text = ' ', - ariaLabel = '' + ariaLabel = '', + buttonTextCls = 'flex' } = options; let containerElement; @@ -78,7 +79,7 @@ export default function Dropdown(options = {}) { icon: `#ic_arrow_drop_${direction}_24px`, iconCls: `${buttonIconCls} icon-smaller flex`, ariaLabel, - textCls: 'flex' + textCls: buttonTextCls }); if (direction === 'down') { diff --git a/src/viewer.js b/src/viewer.js index 025e07a38..6277a2a5c 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -61,6 +61,7 @@ const Viewer = function Viewer(targetOption, options = {}) { const center = urlParams.center || centerOption; const zoom = urlParams.zoom || zoomOption; const groups = flattenGroups(groupOptions); + const layerStylePicker = {}; const getCapabilitiesLayers = () => { const capabilitiesPromises = []; @@ -327,6 +328,13 @@ const Viewer = function Viewer(targetOption, options = {}) { visible: false, legend: false }; + // Apply changed style + if (savedLayerProps[layerName] && savedLayerProps[layerName].altStyleIndex > -1) { + const altStyle = initialProps.stylePicker[savedLayerProps[layerName].altStyleIndex]; + savedProps.clusterStyle = altStyle.clusterStyle; + savedProps.style = altStyle.style; + savedProps.defaultStyle = initialProps.style; + } savedProps.name = initialProps.name; const mergedProps = Object.assign({}, initialProps, savedProps); acc.push(mergedProps); @@ -372,8 +380,19 @@ const Viewer = function Viewer(targetOption, options = {}) { return false; }; + const getLayerStylePicker = function getLayerStylePicker(layer) { + return layerStylePicker[layer.get('name')] || []; + }; + + const addLayerStylePicker = function addLayerStylePicker(layerProps) { + if (!layerStylePicker[layerProps.name]) { + layerStylePicker[layerProps.name] = layerProps.stylePicker; + } + }; + const addLayer = function addLayer(layerProps) { const layer = Layer(layerProps, this); + addLayerStylePicker(layerProps); map.addLayer(layer); this.dispatch('addlayer', { layerName: layerProps.name @@ -598,6 +617,7 @@ const Viewer = function Viewer(targetOption, options = {}) { getSearchableLayers, getSize, getLayer, + getLayerStylePicker, getLayers, getLayersByProperty, getMap,