diff --git a/origo.js b/origo.js index 7c3802e77..61ef61547 100644 --- a/origo.js +++ b/origo.js @@ -25,6 +25,7 @@ import { renderSvgIcon } from './src/utils/legendmaker'; import SelectedItem from './src/models/SelectedItem'; import 'elm-pep'; import 'pepjs'; +import 'drag-drop-touch'; import permalink from './src/permalink/permalink'; const Origo = function Origo(configPath, options = {}) { diff --git a/package-lock.json b/package-lock.json index 37eebc2f2..cfdbc9ad3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1778,6 +1778,11 @@ "resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz", "integrity": "sha1-9p+W+UDg0FU9rCkROYZaPNAQHjw=" }, + "drag-drop-touch": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/drag-drop-touch/-/drag-drop-touch-1.3.1.tgz", + "integrity": "sha512-Q0/ZgsnW7VUjn+YqSnp1rvxjjPnZX5YLyVaw28einood+eTMcLzgOgHk8nyqIF9O18J68l+2htlEnbw5GsyTvQ==" + }, "duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", diff --git a/package.json b/package.json index 50b197ae6..ec9d2bf7f 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "awesomplete": "^1.1.5", "cuid": "^2.1.8", "downloadjs": "^1.4.7", + "drag-drop-touch": "^1.3.1", "elm-pep": "^1.0.6", "html2canvas": "^1.4.1", "ol-mapbox-style": "9.4.0", diff --git a/scss/ui/_collapse.scss b/scss/ui/_collapse.scss index c99b610cf..932a24ff1 100644 --- a/scss/ui/_collapse.scss +++ b/scss/ui/_collapse.scss @@ -37,3 +37,8 @@ height: calc(100% - 2rem); } } + +.move-item { + background-color: $blue; + opacity: 0.2; +} diff --git a/src/controls/draganddrop.js b/src/controls/draganddrop.js index 838c6340d..ae0e0fef1 100644 --- a/src/controls/draganddrop.js +++ b/src/controls/draganddrop.js @@ -73,6 +73,7 @@ const DragAndDrop = function DragAndDrop(options = {}) { } const groupName = options.groupName || 'egna-lager'; const groupTitle = options.groupTitle || 'Egna lager'; + const draggable = options.draggable || true; const styleByAttribute = options.styleByAttribute || false; const featureStyles = options.featureStyles || { Point: [{ @@ -156,7 +157,7 @@ const DragAndDrop = function DragAndDrop(options = {}) { } }); if (!viewer.getGroup(groupName)) { - viewer.addGroup({ title: groupTitle, name: groupName, expanded: true }); + viewer.addGroup({ title: groupTitle, name: groupName, expanded: true, draggable }); } vectorLayer = new VectorLayer({ source: vectorSource, diff --git a/src/controls/legend/group.js b/src/controls/legend/group.js index 05062472b..412257e31 100644 --- a/src/controls/legend/group.js +++ b/src/controls/legend/group.js @@ -20,7 +20,9 @@ const Group = function Group(viewer, options = {}) { type = 'group', autoExpand = true, exclusive = false, - toggleAll = true + toggleAll = true, + draggable = false, + zIndexStart = 0.1 } = options; const stateCls = { @@ -32,6 +34,7 @@ const Group = function Group(viewer, options = {}) { const uncheckIcon = '#ic_radio_button_unchecked_24px'; let visibleState = 'all'; let groupEl; + let selectedItem; const listCls = type === 'grouplayer' ? 'divider-start padding-left padding-top-small' : ''; const groupList = GroupList({ viewer, cls: listCls, abstract }); @@ -144,7 +147,7 @@ const Group = function Group(viewer, options = {}) { const addOverlay = function addOverlay(overlay) { groupList.addOverlay(overlay); - this.dispatch('add:overlay'); + this.dispatch('add:overlay', overlay); }; const removeOverlay = function removeOverlay(layerName) { @@ -165,6 +168,59 @@ const Group = function Group(viewer, options = {}) { } }; + function orderZIndex(list, groupCmp) { + const elementIds = [...list.children].map(x => x.id).reverse(); + const overlayArray = groupCmp.getOverlayList().getOverlays(); + overlayArray.forEach(element => { + const layerIndex = 1 + elementIds.indexOf(element.getId()); + element.getLayer().setZIndex(zIndexStart + (layerIndex / 100)); + }); + } + + function handleDragStart(evt) { + selectedItem = evt.target; + selectedItem.classList.add('move-item'); + } + + function handleDragOver(evt) { + const event = evt; + event.preventDefault(); + event.dataTransfer.dropEffect = 'move'; + } + + function handleDragEnd(evt, groupCmp) { + if (selectedItem) { + selectedItem.classList.remove('move-item'); + orderZIndex(selectedItem.parentElement, groupCmp); + selectedItem = null; + } + } + + function handleDragEnter(evt) { + if (selectedItem) { + const event = evt; + event.preventDefault(); + event.dataTransfer.dropEffect = 'move'; + const list = selectedItem.parentNode; + const x = evt.clientX; + const y = evt.clientY; + let swapItem = document.elementFromPoint(x, y) === null ? selectedItem : document.elementFromPoint(x, y); + if (list === swapItem.parentNode) { + swapItem = swapItem !== selectedItem.nextSibling ? swapItem : swapItem.nextSibling; + list.insertBefore(selectedItem, swapItem); + } + } + } + + function enableDragItem(el, groupCmp) { + const item = el; + item.setAttribute('draggable', true); + item.ondragstart = handleDragStart; + item.ondragenter = handleDragEnter; + item.ondragover = handleDragOver; + item.ondragend = (evt) => { handleDragEnd(evt, groupCmp); }; + } + return Component({ addOverlay, getEl, @@ -175,6 +231,7 @@ const Group = function Group(viewer, options = {}) { parent, title, type, + draggable, addGroup, appendGroup, removeGroup, @@ -191,12 +248,16 @@ const Group = function Group(viewer, options = {}) { }, onInit() { this.addComponent(collapse); - this.on('add:overlay', () => { + this.on('add:overlay', (overlay) => { visibleState = groupList.getVisible(); if (tickButton) { tickButton.setState(stateCls[visibleState]); tickButton.setIcon(getCheckIcon(visibleState)); } + if (draggable && typeof overlay.getId === 'function') { + const el = document.getElementById(overlay.getId()); + enableDragItem(el, this); + } }); this.on('add:group', () => { visibleState = groupList.getVisible(); diff --git a/src/controls/print/print-legend.js b/src/controls/print/print-legend.js index 3dfd962b6..8508f743b 100644 --- a/src/controls/print/print-legend.js +++ b/src/controls/print/print-legend.js @@ -1,6 +1,6 @@ import { ImageArcGISRest, ImageWMS } from 'ol/source'; import { Component } from '../../ui'; -import { renderSvgIcon } from '../../utils/legendmaker'; +import { isHidden, renderSvgIcon } from '../../utils/legendmaker'; /** * More information: https://developers.arcgis.com/rest/services-reference/enterprise/legend-map-service-.htm @@ -168,9 +168,12 @@ const LayerRow = function LayerRow(options) { } const children = style.map((thisStyle, index) => { - const styleIcon = getStyleIcon(thisStyle); - const rowTitle = thisStyle[0].label ? thisStyle[0].label : index + 1; - return getListItem(rowTitle, styleIcon); + if (!isHidden(thisStyle)) { + const styleIcon = getStyleIcon(thisStyle); + const rowTitle = thisStyle[0].label ? thisStyle[0].label : index + 1; + return getListItem(rowTitle, styleIcon); + } + return ''; }); return getTitleWithChildren(title, children); }; diff --git a/src/controls/print/print-resize.js b/src/controls/print/print-resize.js index 8e1e10fb7..64846568c 100644 --- a/src/controls/print/print-resize.js +++ b/src/controls/print/print-resize.js @@ -558,7 +558,9 @@ export default function PrintResize(options = {}) { const newSource = new ImageWMS(({ url: `${source.getUrl()}/export?`, - crossOrigin: 'anonymous', + // No other way to access source/layer crossOrigin parameter + // eslint-disable-next-line no-underscore-dangle + crossOrigin: source.crossOrigin_ || 'anonymous', projection, ratio: 1, params: { diff --git a/src/layer/agsmap.js b/src/layer/agsmap.js index 2d820272a..4db858a6f 100644 --- a/src/layer/agsmap.js +++ b/src/layer/agsmap.js @@ -19,6 +19,7 @@ const agsMap = function agsMap(layerOptions, viewer) { }; const layerSettings = { ...layerDefault, ...layerOptions }; const sourceSettings = { ...sourceDefault, ...viewer.getSource(layerOptions.source) }; + sourceSettings.crossOrigin = layerSettings.crossOrigin ? layerSettings.crossOrigin : sourceSettings.crossOrigin; sourceSettings.params = layerSettings.params || {}; sourceSettings.params.layers = `show:${layerSettings.id}`; diff --git a/src/layer/agstile.js b/src/layer/agstile.js index 14c619732..606df2408 100644 --- a/src/layer/agstile.js +++ b/src/layer/agstile.js @@ -6,7 +6,7 @@ function createSource(options) { return new TileArcGISRest({ attributions: options.attribution, projection: options.projection, - crossOrigin: 'anonymous', + crossOrigin: options.crossOrigin, params: options.params, url: options.url, tileGrid: options.tileGrid @@ -18,10 +18,13 @@ const agsTile = function agsTile(layerOptions, viewer) { layerType: 'tile', featureinfoLayer: undefined }; - const sourceDefault = {}; + const sourceDefault = { + crossOrigin: 'anonymous' + }; const agsOptions = Object.assign(agsDefault, layerOptions); const sourceOptions = Object.assign(sourceDefault, viewer.getMapSource()[layerOptions.source]); sourceOptions.attribution = agsOptions.attribution; + sourceOptions.crossOrigin = agsOptions.crossOrigin ? agsOptions.crossOrigin : sourceOptions.crossOrigin; sourceOptions.projection = viewer.getProjection(); sourceOptions.params = agsOptions.params || {}; sourceOptions.params.layers = `show:${agsOptions.id}`; diff --git a/src/layer/wms.js b/src/layer/wms.js index 94e7daf4d..d5f2e1d9f 100644 --- a/src/layer/wms.js +++ b/src/layer/wms.js @@ -9,7 +9,7 @@ function createTileSource(options) { attributions: options.attribution, url: options.url, gutter: options.gutter, - crossOrigin: 'anonymous', + crossOrigin: options.crossOrigin, projection: options.projection, tileGrid: options.tileGrid, params: { @@ -90,6 +90,7 @@ const wms = function wms(layerOptions, viewer) { featureinfoLayer: null }; const sourceDefault = { + crossOrigin: 'anonymous', version: '1.1.1', gutter: 0, format: 'image/png' @@ -99,6 +100,7 @@ const wms = function wms(layerOptions, viewer) { wmsOptions.name.split(':').pop(); const sourceOptions = Object.assign(sourceDefault, viewer.getMapSource()[layerOptions.source]); sourceOptions.attribution = wmsOptions.attribution; + sourceOptions.crossOrigin = wmsOptions.crossOrigin ? wmsOptions.crossOrigin : sourceOptions.crossOrigin; sourceOptions.projection = viewer.getProjection(); sourceOptions.id = wmsOptions.id; sourceOptions.format = wmsOptions.format ? wmsOptions.format : sourceOptions.format; diff --git a/src/layer/wmts.js b/src/layer/wmts.js index aa1b24f8b..50e57611c 100644 --- a/src/layer/wmts.js +++ b/src/layer/wmts.js @@ -5,7 +5,7 @@ import tile from './tile'; function createSource(options) { return new WMTSSource({ - crossOrigin: 'anonymous', + crossOrigin: options.crossOrigin, attributions: options.attribution, url: options.url, projection: options.projectionCode, @@ -28,8 +28,9 @@ const wmts = function wmts(layerOptions, viewer) { featureinfoLayer: undefined }; const sourceDefault = { - matrixSet: viewer.getProjectionCode(), - matrixIdsPrefix: `${viewer.getProjectionCode()}:`, + crossOrigin: 'anonymous', + matrixSet: layerOptions.matrixSet || viewer.getProjectionCode(), + matrixIdsPrefix: layerOptions.matrixIdsPrefix === false ? '' : `${viewer.getProjectionCode()}:`, format: 'image/png', resolutions: JSON.parse(JSON.stringify(viewer.getResolutions())), tileSize: [256, 256] @@ -42,6 +43,7 @@ const wmts = function wmts(layerOptions, viewer) { sourceOptions.format = wmtsOptions.format; } sourceOptions.attribution = wmtsOptions.attribution; + sourceOptions.crossOrigin = wmtsOptions.crossOrigin ? wmtsOptions.crossOrigin : sourceOptions.crossOrigin; sourceOptions.projectionCode = viewer.getProjectionCode(); sourceOptions.matrixIds = []; sourceOptions.resolutions.forEach((resolution, i) => { diff --git a/src/layer/xyz.js b/src/layer/xyz.js index 54928a4e6..f43b7266f 100644 --- a/src/layer/xyz.js +++ b/src/layer/xyz.js @@ -11,11 +11,15 @@ const xyz = function xyz(layerOptions, viewer) { layerType: 'tile', featureinfoLayer: undefined }; - const sourceDefault = { url: '' }; + const sourceDefault = { + crossOrigin: 'anonymous', + url: '' + }; const xyzOptions = Object.assign(xyzDefault, layerOptions); xyzOptions.sourceName = xyzOptions.id; const sourceOptions = Object.assign(sourceDefault, viewer.getMapSource()[layerOptions.source]); sourceOptions.attributions = xyzOptions.attribution; + sourceOptions.crossOrigin = xyzOptions.crossOrigin ? xyzOptions.crossOrigin : sourceOptions.crossOrigin; sourceOptions.projection = viewer.getProjectionCode() || 'EPSG:3857'; if (xyzOptions.tileGrid) { @@ -33,7 +37,7 @@ const xyz = function xyz(layerOptions, viewer) { if (xyzOptions.layerURL) { sourceOptions.url += xyzOptions.layerURL; } - sourceOptions.crossOrigin = 'anonymous'; + const xyzSource = createSource(sourceOptions); return tile(xyzOptions, xyzSource, viewer); }; diff --git a/src/utils/legendmaker.js b/src/utils/legendmaker.js index 19e3410e7..43cd040b2 100644 --- a/src/utils/legendmaker.js +++ b/src/utils/legendmaker.js @@ -2,7 +2,7 @@ import { renderIcon, renderSvg } from './legendrender'; const size = 24; -const isHidden = function isHidden(arr) { +export const isHidden = function isHidden(arr) { const hiddenItem = arr.find(item => item.hidden); if (hiddenItem) { if (hiddenItem.hidden === true) {