diff --git a/src/components/App.jsx b/src/components/App.jsx index 779fee4e..af3762ca 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -293,8 +293,20 @@ export default class App extends React.Component { } updateIcons(baseUrl) { - downloadSpriteMetadata(baseUrl, icons => { - this.setState({ spec: updateRootSpec(this.state.spec, 'sprite', icons)}) + downloadSpriteMetadata(baseUrl, metadata => { + const {spec} = this.state; + this.setState({ + spec: { + ...spec, + $root: { + ...spec.$root, + sprite: { + ...spec.$root["sprite"], + metadata: metadata + } + } + } + }) }) } diff --git a/src/components/InputSpec.jsx b/src/components/InputSpec.jsx index 1e470b2b..3ba940ef 100644 --- a/src/components/InputSpec.jsx +++ b/src/components/InputSpec.jsx @@ -12,6 +12,7 @@ import InputDynamicArray from './InputDynamicArray' import InputFont from './InputFont' import InputAutocomplete from './InputAutocomplete' import InputEnum from './InputEnum' +import SpriteIcon from './SpriteIcon' import capitalize from 'lodash.capitalize' const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image'] @@ -85,10 +86,21 @@ export default class SpecField extends React.Component { case 'formatted': case 'string': if (iconProperties.indexOf(this.props.fieldName) >= 0) { - const options = this.props.fieldSpec.values || []; + const {metadata} = this.props.fieldSpec; + const options = Object.entries(metadata && metadata.data ? metadata.data : {}); return [f, f])} + options={options.map(([k,v]) => { + return [ + k, + + ]; + })} /> } else { return = 0) { return { ...fieldSpec, - values: spec.$root.sprite.values + metadata: spec.$root.sprite.metadata } } if(fieldName === 'text-font') { diff --git a/src/components/SpriteIcon.jsx b/src/components/SpriteIcon.jsx new file mode 100644 index 00000000..f4d49c41 --- /dev/null +++ b/src/components/SpriteIcon.jsx @@ -0,0 +1,57 @@ +import React from 'react' + +export default function SpriteIcon (props) { + const {metadata, id, data, maxWidth} = props; + const {image} = metadata; + const pixelRatio = Math.max(window.devicePixelRatio, 2); + + function getUrls (pixelRatio, url) { + const out = []; + for (var i=pixelRatio; i>=1; i--) { + if (i > 1) { + out.push( + url.replace(/.png/, `@${i}x.png`) + ); + } + else { + out.push(url); + } + } + + return out.map(url => { + return `url(${url})`; + }).join(", "); + } + + let scale = 1; + if (data.width > maxWidth) { + scale = maxWidth/data.width; + } + + const spriteWidth = image.width * scale; + const spriteHeight = image.height * scale; + const width = data.width * scale; + const height = data.height * scale; + const x = data.x * scale; + const y = data.y * scale; + + return
+
+ {id} +
+ {image && +
+
+
+ } +
+} + diff --git a/src/libs/metadata.js b/src/libs/metadata.js index 2e1fb243..94bd350c 100644 --- a/src/libs/metadata.js +++ b/src/libs/metadata.js @@ -1,22 +1,28 @@ import npmurl from 'url' function loadJSON(url, defaultValue, cb) { - fetch(url, { + return fetch(url, { mode: 'cors', credentials: "same-origin" }) .then(function(response) { return response.json(); }) - .then(function(body) { - cb(body) - }) .catch(function() { console.warn('Can not metadata for ' + url) - cb(defaultValue) + return defaultValue; }) } +function loadImage(url) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => resolve(img); + img.onerror = (err) => reject(err); + img.src = url; + }); +} + export function downloadGlyphsMetadata(urlTemplate, cb) { if(!urlTemplate) return cb([]) @@ -31,11 +37,30 @@ export function downloadGlyphsMetadata(urlTemplate, cb) { } let url = npmurl.format(urlObj); - loadJSON(url, [], cb) + loadJSON(url, []).then(cb); } export function downloadSpriteMetadata(baseUrl, cb) { if(!baseUrl) return cb([]) - const url = baseUrl + '.json' - loadJSON(url, {}, glyphs => cb(Object.keys(glyphs))) + const jsonUrl = baseUrl + '.json' + const imageUrl = baseUrl + '.png' + + Promise.all([ + loadImage(imageUrl).catch(() => undefined), + loadJSON(jsonUrl, {}), + ]) + .then(([image, data]) => { + const out = { + jsonUrl, + imageUrl, + data + }; + if (image) { + out.image = { + width: image.naturalWidth, + height: image.naturalHeight, + } + } + cb(out); + }); } diff --git a/src/styles/index.scss b/src/styles/index.scss index 8acf3a23..e0a4cbf8 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -167,3 +167,18 @@ } } +.maputnik-sprite-icon { + display: flex; +} + +.maputnik-sprite-icon__text { + flex: 1; +} + +.maputnik-sprite-icon__icon { + margin-left: 0.6em; + padding: 2px; + background-color: white; + background-size: 8px 8px; + background-position: 0 0, 4px 4px; +}