From 69b29cf4d435b7639d40121286f877048e7b39b1 Mon Sep 17 00:00:00 2001 From: Matteo V Date: Mon, 27 Nov 2023 11:44:28 +0100 Subject: [PATCH] Fix #9653 embedded icons are correctly loaded (#9660) --- .../mapstore-migration-guide.md | 10 + project/standard/templates/index.html | 1 - project/standard/templates/indexTemplate.html | 1 - web/client/index.html | 1 - web/client/indexTemplate.html | 1 - web/client/plugins/Map.jsx | 43 +-- web/client/plugins/map/selector.js | 6 +- web/client/utils/AgentUtils.js | 325 ------------------ .../utils/styleparser/StyleParserUtils.js | 47 ++- 9 files changed, 50 insertions(+), 385 deletions(-) diff --git a/docs/developer-guide/mapstore-migration-guide.md b/docs/developer-guide/mapstore-migration-guide.md index 85cf9b5455..b7592d8ccb 100644 --- a/docs/developer-guide/mapstore-migration-guide.md +++ b/docs/developer-guide/mapstore-migration-guide.md @@ -22,6 +22,16 @@ This is a list of things to check if you want to update from a previous version ## Migration from 2023.02.xx to 2024.01.00 +### Removing possibility to add custom fonts to the Map + +From this version we limited the load of the font to FontAwesome. + +If you have changed the property **fonts** inside Map plugin it will not longer load the font. A possible fix would be to add the font to the `*.html` files in your application. + +- make sure that the `localConfig.json` does not have **fonts** property in **Map** plugin + +The following css is added automatically if needed `` inside the *head* tag. + ### Fixing background config From this version in order to fix default 3d background config a change is needed here: diff --git a/project/standard/templates/index.html b/project/standard/templates/index.html index c643e11b3e..de360edd75 100644 --- a/project/standard/templates/index.html +++ b/project/standard/templates/index.html @@ -84,7 +84,6 @@ - diff --git a/project/standard/templates/indexTemplate.html b/project/standard/templates/indexTemplate.html index 3d12542e2d..790ed2e814 100644 --- a/project/standard/templates/indexTemplate.html +++ b/project/standard/templates/indexTemplate.html @@ -84,7 +84,6 @@ - diff --git a/web/client/index.html b/web/client/index.html index 5cd64af541..d7f952eaf2 100644 --- a/web/client/index.html +++ b/web/client/index.html @@ -84,7 +84,6 @@ - diff --git a/web/client/indexTemplate.html b/web/client/indexTemplate.html index 4d71ff0a81..56155c619e 100644 --- a/web/client/indexTemplate.html +++ b/web/client/indexTemplate.html @@ -84,7 +84,6 @@ - diff --git a/web/client/plugins/Map.jsx b/web/client/plugins/Map.jsx index f8795a8c32..2ff9619fd6 100644 --- a/web/client/plugins/Map.jsx +++ b/web/client/plugins/Map.jsx @@ -9,7 +9,6 @@ import PropTypes from 'prop-types'; import React from 'react'; import { connect, createPlugin } from '../utils/PluginsUtils'; -import { loadFont } from '../utils/AgentUtils'; import Spinner from 'react-spinkit'; import './map/css/map.css'; import Message from '../components/I18N/Message'; @@ -108,8 +107,6 @@ import {getHighlightLayerOptions} from "../utils/HighlightUtils"; * { * "name": "Map", * "cfg": { - * "shouldLoadFont": true, - * "fonts": ['FontAwesome'], * "tools": ["overview", "scalebar", "draw", { * "leaflet": { * "name": "test", @@ -128,20 +125,8 @@ import {getHighlightLayerOptions} from "../utils/HighlightUtils"; * - name is a unique name for the tool * - impl is a placeholder (“{context.ToolName}”) where ToolName is the name you gave the tool in plugins.js (TestSupportLeaflet in our example) * - * You can also specify a list of fonts that have to be loaded before map rendering - * if the shouldLoadFont is true - * This font pre-load list is required if you're using canvas based mapping libraries (e.g. OpenLayers) and you need to show markers with symbols (e.g. Annotations). - * For each font you must specify the font name used in the `@font-face` inside the "fonts" array property. Note: the `@font-face` declaration must be present in css of the page, otherwise the font can not be loaded anyway. - * ``` - * { - * "name": "Map", - * "cfg": { - * "shouldLoadFont": true, - * "fonts": ['FontAwesome'] - * } - * } - * ``` - * For more info on metadata visit [fontfaceobserver](https://github.com/bramstein/fontfaceobserver) + * You can no longer specify a list of fonts that have to be loaded before map rendering, we are now only loading FontAwesome for the icons + * We will pre-load FontAwesome only if needed, i.e you need to show markers with symbols (e.g. Annotations). * * An additional feature to is limit the area and/or the minimum level of zoom in the localConfig.json file using "mapConstraints" property * @@ -210,7 +195,6 @@ class MapPlugin extends React.Component { loadingSpinner: PropTypes.bool, loadingError: PropTypes.string, tools: PropTypes.array, - fonts: PropTypes.array, options: PropTypes.object, mapOptions: PropTypes.object, projectionDefs: PropTypes.array, @@ -219,7 +203,6 @@ class MapPlugin extends React.Component { actions: PropTypes.object, features: PropTypes.array, securityToken: PropTypes.string, - shouldLoadFont: PropTypes.bool, elevationEnabled: PropTypes.bool, isLocalizedLayerStylesEnabled: PropTypes.bool, localizedLayerStylesName: PropTypes.string, @@ -239,7 +222,6 @@ class MapPlugin extends React.Component { tools: ["scalebar", "draw", "highlight", "popup", "box"], options: {}, mapOptions: {}, - fonts: ['FontAwesome'], toolsOptions: { measurement: {}, locate: {}, @@ -261,7 +243,6 @@ class MapPlugin extends React.Component { }, securityToken: '', additionalLayers: [], - shouldLoadFont: false, elevationEnabled: false, onResolutionsChange: () => {}, items: [], @@ -274,25 +255,7 @@ class MapPlugin extends React.Component { }; UNSAFE_componentWillMount() { - const {shouldLoadFont, fonts} = this.props; - - // load each font before rendering (see issue #3155) - if (shouldLoadFont && fonts) { - this.setState({canRender: false}); - - Promise.all( - fonts.map(f => - loadFont(f, { - timeoutAfter: 5000 // 5 seconds in milliseconds - }).catch((error) => { - console.warn("Fonts loading check for map style responded slowly or with an error. Fonts in map may not be rendered correctly. This is not necessarily an issue.", error); // eslint-disable-line no-console - } - )) - ).then(() => { - this.setState({canRender: true}); - }); - - } + // moved the font load of FontAwesome only to styleParseUtils (#9653) this.updatePlugins(this.props); this._isMounted = true; } diff --git a/web/client/plugins/map/selector.js b/web/client/plugins/map/selector.js index 9ac8043939..585c125c52 100644 --- a/web/client/plugins/map/selector.js +++ b/web/client/plugins/map/selector.js @@ -1,5 +1,5 @@ import { mapSelector, projectionDefsSelector, isMouseMoveCoordinatesActiveSelector } from '../../selectors/map'; -import { mapTypeSelector, isOpenlayers } from '../../selectors/maptype'; +import { mapTypeSelector } from '../../selectors/maptype'; import { layerSelectorWithMarkers } from '../../selectors/layers'; import { highlighedFeatures } from '../../selectors/highlight'; import { securityTokenSelector } from '../../selectors/security'; @@ -21,11 +21,10 @@ export default createShallowSelectorCreator(isEqual)( state => state.mapInitialConfig && state.mapInitialConfig.loadingError && state.mapInitialConfig.loadingError.data, securityTokenSelector, isMouseMoveCoordinatesActiveSelector, - isOpenlayers, isLocalizedLayerStylesEnabledSelector, localizedLayerStylesNameSelector, currentLocaleLanguageSelector, - (projectionDefs, map, mapType, layers, features, loadingError, securityToken, elevationEnabled, shouldLoadFont, isLocalizedLayerStylesEnabled, localizedLayerStylesName, currentLocaleLanguage) => ({ + (projectionDefs, map, mapType, layers, features, loadingError, securityToken, elevationEnabled, isLocalizedLayerStylesEnabled, localizedLayerStylesName, currentLocaleLanguage) => ({ projectionDefs, map, mapType, @@ -34,7 +33,6 @@ export default createShallowSelectorCreator(isEqual)( loadingError, securityToken, elevationEnabled, - shouldLoadFont, isLocalizedLayerStylesEnabled, localizedLayerStylesName, currentLocaleLanguage diff --git a/web/client/utils/AgentUtils.js b/web/client/utils/AgentUtils.js index 5b6022163d..ccd0777498 100644 --- a/web/client/utils/AgentUtils.js +++ b/web/client/utils/AgentUtils.js @@ -18,328 +18,3 @@ export const getWindowSize = () => { return {maxWidth: width, maxHeight: height}; }; - /** - * This has been cloned from https://github.com/dwighthouse/onfontready - * because it was throwing a syntax error with IE11 - * - * fontName : Font name used in the `@font-face` declaration - * onReady : Function called upon successful font load and parse detection - * options : Optional parameters - * options.timeoutAfter : Milliseconds to wait before giving up Triggers options.onTimeout call Unset or 0 will result in an indefinite wait - * options.onTimeout : Called after options.timeoutAfter milliseconds have elapsed without an onReady call - * options.sampleText : Text string used to test font loading Defaults to " " (space character) - * options.generic : Boolean set to true if attempting to detect generic family font - * root : Undefined variable used by function - * tryFinish : Undefined variable used by function - */ -export const onfontready = (fontNameNew, onReady, options = {}, rootNew, tryFinishNew) => { - let fontName = fontNameNew; - let root = rootNew; - let tryFinish = tryFinishNew; - // root and tryFinish parameters prevent the need for var statement - let fontNameCopy = fontName; - if (process.env.isTest) { - // Store a copy because later code will reuse the fontName variable - // Use var to pull it out of the if block's scope - - const tests = {}; - - const tryCreate = (name) => { - tests[name] = tests[name] || { - rootCount: 0, - iframesCreated: false, - timedOut: false, - fontLoaded: false, - requiredExtraTimeout: false - }; - return tests[name]; - }; - - // A helper function tracks info about internal processes for testing - window.reporter = window.reporter || { - modifyRootCount(name, increment) { - tryCreate(name).rootCount += increment; - }, - iframesCreated(name) { - tryCreate(name).iframesCreated = true; - }, - timedOut(name) { - tryCreate(name).timedOut = true; - }, - fontLoaded(name) { - tryCreate(name).fontLoaded = true; - }, - requiredExtraTimeout(name) { - tryCreate(name).requiredExtraTimeout = true; - }, - getTests() { - return tests; - } - }; - } - - // Ensure options is defined to prevent access errors - - // A 0 timeoutAfter will prevent the timeout functionality - if (options.timeoutAfter) { - setTimeout(() => { - // Prevent onTimeout call after shutdown - if (root) { - if (process.env.isTest) { - window.reporter.modifyRootCount(fontNameCopy, -1); - } - - // Shutdown should occur even if onTimeout is not defined - document.body.removeChild(root); - - // Break the reference to the DOM element to allow GC to run - // Assigning to 0 also results in falsy root tests elsewhere - root = 0; - - // This won't prevent TypeError if onTimeout is not a function - if (options.onTimeout) { - if (process.env.isTest) { - window.reporter.timedOut(fontNameCopy); - } - options.onTimeout(); - } - } - }, options.timeoutAfter); - } - - // Measures the test elements to determine if the font has loaded - // Always safe to call, even after shutdown - // Using function assignment compresses better than function declaration - tryFinish = () => { - // Prevent test or onReady call after shutdown - // The width of the parent elements are influenced by the children, - // so it is sufficient to measure the parents - // clientWidth only measures to integer accuracy - // This is sufficient for such large font sizes (999px) - // Both compared values are integers, so double equality is sufficient - if (root && root.firstChild.clientWidth === root.lastChild.clientWidth) { - if (process.env.isTest) { - window.reporter.modifyRootCount(fontNameCopy, -1); - } - - document.body.removeChild(root); - - // Break the reference to the DOM element to allow GC to run - // Assigning to 0 also results in falsy root tests elsewhere - root = 0; - - if (process.env.isTest) { - window.reporter.fontLoaded(fontNameCopy); - } - - onReady(); - } - }; - - if (process.env.isTest) { - window.reporter.modifyRootCount(fontNameCopy, 1); - } - - if (!process.env.isLegacy) { - // Attempt to finish early if the font is already loaded - // The tryFinish call happens after the test elements are added - tryFinish( - // Save bytes by creating and assigning the root div inside call - // appendChild returns the root, allowing innerHTML usage inline - document.body.appendChild(root = document.createElement('div')).innerHTML = - // position:fixed breaks the element out of page flow - // Being out of flow makes the div size to the text - // white-space:pre ensures no text wrapping will occur - // Out of bounds percentage bottom/right prevents scrollbars - // font combines font-size and font-family - // Font size 999px differentiates fallback fonts - // Using font size in pixels prevents possible - // failure due to zero-sized default page fonts - // Using a
 instead of a 
tag might be smaller, but - // it is more likely to interfere with page styles - '
' + - // A single space is the text default - (options.sampleText || ' ') + - '
' + - '
' + - (options.sampleText || ' ') + - '
' - ); - } - - if (process.env.isLegacy) { - // Attempt to finish early if the font is already loaded - // The tryFinish call happens after the test elements are added - tryFinish( - // Save bytes by creating and assigning the root div inside call - // appendChild returns the root, allowing innerHTML usage inline - document.body.appendChild(root = document.createElement('div')).innerHTML = - // IE6 cannot create automatically sized divs that will - // contain an absolutely positioned element - // Such elements will instead break out of their bounds - // The only other method to associate the width of one - // element's natural size with the size of another is table - // Style value with no spaces does not require quotes - // position:absolute breaks the element out of page flow - // IE6 does not support position:fixed - // Out of bounds percentage bottom/right prevents scrollbars - // width:auto prevents interference from width:100% styles - // which are commonly added - '' + - // tag is implied - '' + - // position:relative allows the iframe's absolute - // positioning to be relative to the is implied - // is implied - '' + - // Inner needs surrounding, equal-sized periods - // to prevent some older browsers from collapsing the - // whitespace character (space) at insertion time - // Monospace font compresses better than serif here - // white-space:pre ensures no text wrapping will occur - ', and are implied - '
- '' + - //
' + - // font combines font-size and font-family - // Font size 999px differentiates fallback fonts - // Using font size in pixels prevents possible - // failure due to zero-sized default page fonts - '.' + - // A single space is the text default - (options.sampleText || ' ') + - '.' + - // Closing tags for ,
' + - '' + - '' + - '' + - '
' + - '
' + - '.' + - (options.sampleText || ' ') + - '.' + - '
' - ); - } - - // If the font is already loaded, tryFinish will have already destroyed - // the root reference, so the iframes will never be inserted - if (root) { - if (process.env.isTest) { - window.reporter.iframesCreated(fontNameCopy); - } - - if (!process.env.isLegacy) { - // The fontName value has already been used, reuse for reference - // Save bytes by creating and assigning the iframe inside call - // appendChild returns the iframe, allowing style usage inline - // The iframe's width only needs to be relative to the parent's - root.firstChild.appendChild( - fontName = document.createElement('iframe') - ).style.width = '999%'; - - // contentWindow becomes available upon DOM insertion - // Assigning a non-closure function to onresize prevents the - // possibility of memory leaks through event handlers - fontName.contentWindow.onresize = tryFinish; - - // By reusing the fontName (via reassignment), the DOM reference - // to the first iframe is broken, reducing memory leak potential - root.lastChild.appendChild( - fontName = document.createElement('iframe') - ).style.width = '999%'; - - fontName.contentWindow.onresize = tryFinish; - } - - if (process.env.isLegacy) { - // The fontName value has already been used, reuse for reference - // Save bytes by creating and assigning the iframe inside call - // Save bytes by duplicating the deeply nested DOM insertion - // appendChild returns the iframe, allowing style usage inline - // position:absolute prevents the iframe from influencing the - // table's width - // Some IE browsers will generate scrollbars if the iframe - // isn't positioned to the top-left - // The iframe's width only needs to be relative to the parent's - root.firstChild.firstChild.firstChild.firstChild.appendChild( - fontName = document.createElement('iframe') - ).style.cssText = 'position:absolute;bottom:999%;right:999%;width:999%'; - - // contentWindow becomes available upon DOM insertion - // Assigning a non-closure function to onresize prevents the - // possibility of memory leaks through event handlers - // Older IE browsers require iframe onresize event handlers - // to be attached via attachEvent - if (fontName.attachEvent) { - fontName.contentWindow.attachEvent('onresize', tryFinish); - } else { - fontName.contentWindow.onresize = tryFinish; - } - - // By reusing the fontName (via reassignment), the DOM reference - // to the first iframe is broken, reducing memory leak potential - root.lastChild.firstChild.firstChild.firstChild.appendChild( - fontName = document.createElement('iframe') - ).style.cssText = 'position:absolute;bottom:999%;right:999%;width:999%'; - - if (fontName.attachEvent) { - fontName.contentWindow.attachEvent('onresize', tryFinish); - } else { - fontName.contentWindow.onresize = tryFinish; - } - } - - if (!process.env.isTest) { - // Because of iframe loading nuances, sometimes the font can finish - // loading after the root is inserted, but before the onresize - // event handler can be added to an iframe, creating a potential - // for a missed resize event - // This setTimeout gives the browser an additional chance to catch - // a font load after returning control to the browser - // By assigning the result of setTimeout (a timeout ID) to the - // fontName, the DOM reference to the second iframe is broken, - // further reducing the possibility of memory leaks - fontName = setTimeout(tryFinish); - } - - if (process.env.isTest) { - // When testing, report that this timeout was used - fontName = setTimeout(function() { - if (root) { - window.reporter.requiredExtraTimeout(fontNameCopy); - tryFinish(); - } - }); - } - } -}; -/** - uses the onfontready function in a Promise throwing an error it the promise rejects (after 3000ms) -*/ -export const loadFont = (fontName, options = {}) => - new Promise((resolve, reject) => { - onfontready(fontName, resolve, { - timeoutAfter: options.timeoutAfter, - - // Catch should only occur if onTimeout would normally be called - onTimeout: reject, - sampleText: options.sampleText, - generic: options.generic - }); - }); diff --git a/web/client/utils/styleparser/StyleParserUtils.js b/web/client/utils/styleparser/StyleParserUtils.js index 21252393cb..b77a911df6 100644 --- a/web/client/utils/styleparser/StyleParserUtils.js +++ b/web/client/utils/styleparser/StyleParserUtils.js @@ -853,6 +853,26 @@ export const parseSymbolizerExpressions = (symbolizer, feature) => { }; +const loadFontAwesome = () => { + return new Promise((resolve) => { + const fontAwesomeHref = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css'; + if (!document.querySelector(`link[href='${fontAwesomeHref}']`)) { + const fontAwesome = document.createElement('link'); + fontAwesome.setAttribute('rel', 'stylesheet'); + fontAwesome.setAttribute('href', fontAwesomeHref); + document.head.appendChild(fontAwesome); + fontAwesome.onload = () => { + resolve(); + }; + fontAwesome.onerror = () => { + resolve(); + }; + } else { + resolve(); + } + }); +}; + /** * prefetch all image or mark symbol in a geostyler style * @param {object} geoStylerStyle geostyler style @@ -886,16 +906,19 @@ export const drawIcons = (geoStylerStyle, options) => { }, []); const marks = symbolizers.filter(({ kind }) => kind === 'Mark'); const icons = symbolizers.filter(({ kind }) => kind === 'Icon'); - return new Promise((resolve) => { - if (marks.length > 0 || icons.length > 0) { - Promise.all([ - ...marks.map(getWellKnownNameImageFromSymbolizer), - ...icons.map(getImageFromSymbolizer) - ]).then((images) => { - resolve(images); - }); - } else { - resolve([]); - } - }); + return loadFontAwesome() + .then( + () => new Promise((resolve) => { + if (marks.length > 0 || icons.length > 0) { + Promise.all([ + ...marks.map(getWellKnownNameImageFromSymbolizer), + ...icons.map(getImageFromSymbolizer) + ]).then((images) => { + resolve(images); + }); + } else { + resolve([]); + } + }) + ); };