Merge pull request #12893 from andrewcoder002/main
Allow map target to be an external window
This commit is contained in:
13
examples/external-map.html
Normal file
13
examples/external-map.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
layout: example.html
|
||||||
|
title: External map
|
||||||
|
shortdesc: Move a map to a seperate window.
|
||||||
|
docs: >
|
||||||
|
Move a map to a seperate window.
|
||||||
|
tags: "external, window"
|
||||||
|
sources:
|
||||||
|
- path: resources/external-map-map.html
|
||||||
|
---
|
||||||
|
<div id="map" class="map"></div>
|
||||||
|
<input id="external-map-button" type="button" value="Open external map"></input>
|
||||||
|
<span id="blocker-notice" hidden>Could not open map in external window. If you are using a popup or ad blocker you may need to disable it for this example.</span>
|
||||||
112
examples/external-map.js
Normal file
112
examples/external-map.js
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import Map from '../src/ol/Map.js';
|
||||||
|
import OSM from '../src/ol/source/OSM.js';
|
||||||
|
import TileLayer from '../src/ol/layer/Tile.js';
|
||||||
|
import View from '../src/ol/View.js';
|
||||||
|
import {
|
||||||
|
Control,
|
||||||
|
FullScreen,
|
||||||
|
defaults as defaultControls,
|
||||||
|
} from '../src/ol/control.js';
|
||||||
|
import {fromLonLat} from '../src/ol/proj.js';
|
||||||
|
|
||||||
|
class UnusableMask extends Control {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
element: document.createElement('div'),
|
||||||
|
});
|
||||||
|
this.element.setAttribute('hidden', 'hidden');
|
||||||
|
this.element.className = 'ol-mask';
|
||||||
|
this.element.innerHTML = '<div>Map not usable</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const localMapTarget = document.getElementById('map');
|
||||||
|
|
||||||
|
const map = new Map({
|
||||||
|
target: localMapTarget,
|
||||||
|
controls: defaultControls().extend([new FullScreen(), new UnusableMask()]),
|
||||||
|
layers: [
|
||||||
|
new TileLayer({
|
||||||
|
source: new OSM(),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
view: new View({
|
||||||
|
center: fromLonLat([37.41, 8.82]),
|
||||||
|
zoom: 4,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
let mapWindow;
|
||||||
|
function closeMapWindow() {
|
||||||
|
if (mapWindow) {
|
||||||
|
mapWindow.close();
|
||||||
|
mapWindow = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Close external window in case the main page is closed or reloaded
|
||||||
|
window.addEventListener('pagehide', closeMapWindow);
|
||||||
|
|
||||||
|
const button = document.getElementById('external-map-button');
|
||||||
|
|
||||||
|
function resetMapTarget() {
|
||||||
|
localMapTarget.style.height = '';
|
||||||
|
map.setTarget(localMapTarget);
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOverlay() {
|
||||||
|
if (!mapWindow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const externalMapTarget = mapWindow.document.getElementById('map');
|
||||||
|
if (!externalMapTarget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
// Show controls and enable keyboard input
|
||||||
|
externalMapTarget.classList.remove('unusable');
|
||||||
|
externalMapTarget.setAttribute('tabindex', '0');
|
||||||
|
externalMapTarget.focus();
|
||||||
|
} else {
|
||||||
|
// Hide all controls and disable keyboard input
|
||||||
|
externalMapTarget.removeAttribute('tabindex');
|
||||||
|
externalMapTarget.classList.add('unusable');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('visibilitychange', updateOverlay);
|
||||||
|
|
||||||
|
button.addEventListener('click', function () {
|
||||||
|
const blockerNotice = document.getElementById('blocker-notice');
|
||||||
|
blockerNotice.setAttribute('hidden', 'hidden');
|
||||||
|
button.disabled = true;
|
||||||
|
|
||||||
|
// Reset button and map target in case window did not load or open
|
||||||
|
let timeoutKey = setTimeout(function () {
|
||||||
|
closeMapWindow();
|
||||||
|
resetMapTarget();
|
||||||
|
blockerNotice.removeAttribute('hidden');
|
||||||
|
timeoutKey = undefined;
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
mapWindow = window.open(
|
||||||
|
'resources/external-map-map.html',
|
||||||
|
'MapWindow',
|
||||||
|
'toolbar=0,location=0,menubar=0,width=800,height=600'
|
||||||
|
);
|
||||||
|
mapWindow.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const externalMapTarget = mapWindow.document.getElementById('map');
|
||||||
|
localMapTarget.style.height = '0px';
|
||||||
|
map.setTarget(externalMapTarget);
|
||||||
|
|
||||||
|
if (timeoutKey) {
|
||||||
|
timeoutKey = clearTimeout(timeoutKey);
|
||||||
|
}
|
||||||
|
mapWindow.addEventListener('pagehide', function () {
|
||||||
|
resetMapTarget();
|
||||||
|
// Close window in case user does a page reload
|
||||||
|
closeMapWindow();
|
||||||
|
});
|
||||||
|
|
||||||
|
updateOverlay();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -6,10 +6,12 @@ docs: >
|
|||||||
The map in this example is rendered in a web worker, using `OffscreenCanvas`. **Note:** This is currently only supported in Chrome and Edge.
|
The map in this example is rendered in a web worker, using `OffscreenCanvas`. **Note:** This is currently only supported in Chrome and Edge.
|
||||||
tags: "worker, offscreencanvas, vector-tiles"
|
tags: "worker, offscreencanvas, vector-tiles"
|
||||||
experimental: true
|
experimental: true
|
||||||
|
sources:
|
||||||
|
- path: offscreen-canvas.worker.js
|
||||||
|
as: worker.js
|
||||||
cloak:
|
cloak:
|
||||||
- key: get_your_own_D6rA4zTHduk6KOKTXzGB
|
- key: get_your_own_D6rA4zTHduk6KOKTXzGB
|
||||||
value: Get your own API key at https://www.maptiler.com/cloud/
|
value: Get your own API key at https://www.maptiler.com/cloud/
|
||||||
|
|
||||||
---
|
---
|
||||||
<div id="map" class="map">
|
<div id="map" class="map">
|
||||||
<pre id="info" class="info"/>
|
<pre id="info" class="info"/>
|
||||||
|
|||||||
30
examples/resources/external-map-map.html
Normal file
30
examples/resources/external-map-map.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="../css/ol.css" type="text/css">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.map {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.map.unusable .ol-mask {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
user-select: none;
|
||||||
|
background-color: rgba(0, 0, 0, .7);
|
||||||
|
color: white;
|
||||||
|
font: bold 3rem 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
}
|
||||||
|
.map.unusable .ol-control {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map" class="map"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -211,13 +211,12 @@
|
|||||||
</html></code></pre>
|
</html></code></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if worker.source}}
|
{{#each extraSources}}
|
||||||
<div class="row-fluid">
|
<div class="row-fluid extra-source">
|
||||||
<h5 class="source-heading">worker.js</h5>
|
<h5 class="source-heading">{{./name}}</h5>
|
||||||
<pre><code id="example-worker-source" class="language-js">{{ worker.source }}</code></pre>
|
<pre><code class="language-{{./type}}">{{ ./source }}</code></pre>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/each}}
|
||||||
|
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
<h5 class="source-heading">package.json</h5>
|
<h5 class="source-heading">package.json</h5>
|
||||||
<pre><code id="example-pkg-source" class="language-json">{{ pkgJson }}</code></pre>
|
<pre><code id="example-pkg-source" class="language-json">{{ pkgJson }}</code></pre>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const baseDir = dirname(fileURLToPath(import.meta.url));
|
|||||||
|
|
||||||
const isCssRegEx = /\.css(\?.*)?$/;
|
const isCssRegEx = /\.css(\?.*)?$/;
|
||||||
const isJsRegEx = /\.js(\?.*)?$/;
|
const isJsRegEx = /\.js(\?.*)?$/;
|
||||||
const importRegEx = /^import .* from '(.*)';$/;
|
const importRegEx = /(?:^|\n)import .* from '(.*)';(?:\n|$)/g;
|
||||||
const isTemplateJs =
|
const isTemplateJs =
|
||||||
/\/(jquery(-\d+\.\d+\.\d+)?|(bootstrap(\.bundle)?))(\.min)?\.js(\?.*)?$/;
|
/\/(jquery(-\d+\.\d+\.\d+)?|(bootstrap(\.bundle)?))(\.min)?\.js(\?.*)?$/;
|
||||||
const isTemplateCss = /\/bootstrap(\.min)?\.css(\?.*)?$/;
|
const isTemplateCss = /\/bootstrap(\.min)?\.css(\?.*)?$/;
|
||||||
@@ -134,26 +134,18 @@ function createWordIndex(exampleData) {
|
|||||||
* @return {Object<string, string>} dependencies
|
* @return {Object<string, string>} dependencies
|
||||||
*/
|
*/
|
||||||
function getDependencies(jsSource, pkg) {
|
function getDependencies(jsSource, pkg) {
|
||||||
const lines = jsSource.split('\n');
|
|
||||||
const dependencies = {
|
const dependencies = {
|
||||||
ol: pkg.version,
|
ol: pkg.version,
|
||||||
};
|
};
|
||||||
for (let i = 0, ii = lines.length; i < ii; ++i) {
|
|
||||||
const line = lines[i];
|
let importMatch;
|
||||||
const importMatch = line.match(importRegEx);
|
while ((importMatch = importRegEx.exec(jsSource))) {
|
||||||
if (importMatch) {
|
const imp = importMatch[1];
|
||||||
const imp = importMatch[1];
|
if (!imp.startsWith('ol/') && imp != 'ol') {
|
||||||
if (!imp.startsWith('ol/') && imp != 'ol') {
|
const parts = imp.split('/');
|
||||||
const parts = imp.split('/');
|
const dep = imp.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
|
||||||
let dep;
|
if (dep in pkg.devDependencies) {
|
||||||
if (imp.startsWith('@')) {
|
dependencies[dep] = pkg.devDependencies[dep];
|
||||||
dep = parts.slice(0, 2).join('/');
|
|
||||||
} else {
|
|
||||||
dep = parts[0];
|
|
||||||
}
|
|
||||||
if (dep in pkg.devDependencies) {
|
|
||||||
dependencies[dep] = pkg.devDependencies[dep];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,80 +271,79 @@ export default class ExampleBuilder {
|
|||||||
data.jsSource = jsSource;
|
data.jsSource = jsSource;
|
||||||
|
|
||||||
// process tags
|
// process tags
|
||||||
if (data.tags) {
|
data.tags = data.tags ? data.tags.replace(/[\s"]+/g, '').split(',') : [];
|
||||||
data.tags = data.tags.replace(/[\s"]+/g, '').split(',');
|
|
||||||
} else {
|
|
||||||
data.tags = [];
|
|
||||||
}
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transformJsSource(source) {
|
||||||
|
return (
|
||||||
|
source
|
||||||
|
// remove "../src/" prefix and ".js" to have the same import syntax as the documentation
|
||||||
|
.replace(/'\.\.\/src\//g, "'")
|
||||||
|
.replace(/\.js';/g, "';")
|
||||||
|
// Remove worker loader import and modify `new Worker()` to add source
|
||||||
|
.replace(/import Worker from 'worker-loader![^\n]*\n/g, '')
|
||||||
|
.replace('new Worker()', "new Worker('./worker.js', {type: 'module'})")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
cloakSource(source, cloak) {
|
||||||
|
if (cloak) {
|
||||||
|
for (const entry of cloak) {
|
||||||
|
source = source.replace(new RegExp(entry.key, 'g'), entry.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
async render(data) {
|
async render(data) {
|
||||||
const assets = {};
|
const assets = {};
|
||||||
const readOptions = {encoding: 'utf8'};
|
const readOptions = {encoding: 'utf8'};
|
||||||
|
|
||||||
// add in script tag
|
// add in script tag
|
||||||
const jsName = `${data.name}.js`;
|
const jsName = `${data.name}.js`;
|
||||||
|
const jsSource = this.transformJsSource(
|
||||||
// remove "../src/" prefix and ".js" to have the same import syntax as the documentation
|
this.cloakSource(data.jsSource, data.cloak)
|
||||||
let jsSource = data.jsSource.replace(/'\.\.\/src\//g, "'");
|
|
||||||
jsSource = jsSource.replace(/\.js';/g, "';");
|
|
||||||
if (data.cloak) {
|
|
||||||
for (const entry of data.cloak) {
|
|
||||||
jsSource = jsSource.replace(new RegExp(entry.key, 'g'), entry.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Remove worker loader import and modify `new Worker()` to add source
|
|
||||||
jsSource = jsSource.replace(
|
|
||||||
/import Worker from 'worker-loader![^\n]*\n/g,
|
|
||||||
''
|
|
||||||
);
|
);
|
||||||
jsSource = jsSource.replace('new Worker()', "new Worker('./worker.js')");
|
|
||||||
|
|
||||||
data.js = {
|
data.js = {
|
||||||
tag: `<script src="${this.common}.js"></script>
|
tag: `<script src="${this.common}.js"></script>
|
||||||
<script src="${jsName}"></script>`,
|
<script src="${jsName}"></script>`,
|
||||||
source: jsSource,
|
source: jsSource,
|
||||||
};
|
};
|
||||||
|
|
||||||
// check for worker js
|
let jsSources = jsSource;
|
||||||
const workerName = `${data.name}.worker.js`;
|
if (data.sources) {
|
||||||
const workerPath = path.join(data.dir, workerName);
|
data.extraSources = await Promise.all(
|
||||||
let workerSource;
|
data.sources.map(async (sourceConfig) => {
|
||||||
try {
|
const fileName = sourceConfig.path;
|
||||||
workerSource = await fse.readFile(workerPath, readOptions);
|
const extraSourcePath = path.join(data.dir, fileName);
|
||||||
} catch (err) {
|
let source = await fse.readFile(extraSourcePath, readOptions);
|
||||||
// pass
|
let ext = fileName.match(/\.(\w+)$/)[1];
|
||||||
}
|
if (ext === 'mjs') {
|
||||||
if (workerSource) {
|
ext = 'js';
|
||||||
// remove "../src/" prefix and ".js" to have the same import syntax as the documentation
|
}
|
||||||
workerSource = workerSource.replace(/'\.\.\/src\//g, "'");
|
if (ext === 'js') {
|
||||||
workerSource = workerSource.replace(/\.js';/g, "';");
|
source = this.transformJsSource(source);
|
||||||
if (data.cloak) {
|
jsSources += '\n' + source;
|
||||||
for (const entry of data.cloak) {
|
}
|
||||||
workerSource = workerSource.replace(
|
source = this.cloakSource(source, data.cloak);
|
||||||
new RegExp(entry.key, 'g'),
|
assets[fileName] = source;
|
||||||
entry.value
|
return {
|
||||||
);
|
name: sourceConfig.as ?? fileName,
|
||||||
}
|
source: source,
|
||||||
}
|
type: ext,
|
||||||
data.worker = {
|
};
|
||||||
source: workerSource,
|
})
|
||||||
};
|
);
|
||||||
assets[workerName] = workerSource;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pkg = await getPackageInfo();
|
const pkg = await getPackageInfo();
|
||||||
|
|
||||||
data.pkgJson = JSON.stringify(
|
data.pkgJson = JSON.stringify(
|
||||||
{
|
{
|
||||||
name: data.name,
|
name: data.name,
|
||||||
dependencies: getDependencies(
|
dependencies: getDependencies(jsSources, pkg),
|
||||||
jsSource + (workerSource ? `\n${workerSource}` : ''),
|
|
||||||
pkg
|
|
||||||
),
|
|
||||||
devDependencies: {
|
devDependencies: {
|
||||||
parcel: '^2.0.0-beta.1',
|
parcel: '^2.0.0',
|
||||||
},
|
},
|
||||||
scripts: {
|
scripts: {
|
||||||
start: 'parcel index.html',
|
start: 'parcel index.html',
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ class PluggableMap extends BaseObject {
|
|||||||
* @private
|
* @private
|
||||||
* @type {?Array<import("./events.js").EventsKey>}
|
* @type {?Array<import("./events.js").EventsKey>}
|
||||||
*/
|
*/
|
||||||
this.keyHandlerKeys_ = null;
|
this.targetChangeHandlerKeys_ = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Collection<import("./control/Control.js").default>}
|
* @type {Collection<import("./control/Control.js").default>}
|
||||||
@@ -356,12 +356,6 @@ class PluggableMap extends BaseObject {
|
|||||||
*/
|
*/
|
||||||
this.renderer_ = null;
|
this.renderer_ = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {undefined|function(Event): void}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this.handleResize_;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {!Array<PostRenderFunction>}
|
* @type {!Array<PostRenderFunction>}
|
||||||
@@ -1146,21 +1140,11 @@ class PluggableMap extends BaseObject {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
handleTargetChanged_() {
|
handleTargetChanged_() {
|
||||||
// target may be undefined, null, a string or an Element.
|
|
||||||
// If it's a string we convert it to an Element before proceeding.
|
|
||||||
// If it's not now an Element we remove the viewport from the DOM.
|
|
||||||
// If it's an Element we append the viewport element to it.
|
|
||||||
|
|
||||||
let targetElement;
|
|
||||||
if (this.getTarget()) {
|
|
||||||
targetElement = this.getTargetElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.mapBrowserEventHandler_) {
|
if (this.mapBrowserEventHandler_) {
|
||||||
for (let i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) {
|
for (let i = 0, ii = this.targetChangeHandlerKeys_.length; i < ii; ++i) {
|
||||||
unlistenByKey(this.keyHandlerKeys_[i]);
|
unlistenByKey(this.targetChangeHandlerKeys_[i]);
|
||||||
}
|
}
|
||||||
this.keyHandlerKeys_ = null;
|
this.targetChangeHandlerKeys_ = null;
|
||||||
this.viewport_.removeEventListener(
|
this.viewport_.removeEventListener(
|
||||||
EventType.CONTEXTMENU,
|
EventType.CONTEXTMENU,
|
||||||
this.boundHandleBrowserEvent_
|
this.boundHandleBrowserEvent_
|
||||||
@@ -1169,15 +1153,17 @@ class PluggableMap extends BaseObject {
|
|||||||
EventType.WHEEL,
|
EventType.WHEEL,
|
||||||
this.boundHandleBrowserEvent_
|
this.boundHandleBrowserEvent_
|
||||||
);
|
);
|
||||||
if (this.handleResize_ !== undefined) {
|
|
||||||
removeEventListener(EventType.RESIZE, this.handleResize_, false);
|
|
||||||
this.handleResize_ = undefined;
|
|
||||||
}
|
|
||||||
this.mapBrowserEventHandler_.dispose();
|
this.mapBrowserEventHandler_.dispose();
|
||||||
this.mapBrowserEventHandler_ = null;
|
this.mapBrowserEventHandler_ = null;
|
||||||
removeNode(this.viewport_);
|
removeNode(this.viewport_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// target may be undefined, null, a string or an Element.
|
||||||
|
// If it's a string we convert it to an Element before proceeding.
|
||||||
|
// If it's not now an Element we remove the viewport from the DOM.
|
||||||
|
// If it's an Element we append the viewport element to it.
|
||||||
|
|
||||||
|
const targetElement = this.getTargetElement();
|
||||||
if (!targetElement) {
|
if (!targetElement) {
|
||||||
if (this.renderer_) {
|
if (this.renderer_) {
|
||||||
clearTimeout(this.postRenderTimeoutHandle_);
|
clearTimeout(this.postRenderTimeoutHandle_);
|
||||||
@@ -1217,10 +1203,11 @@ class PluggableMap extends BaseObject {
|
|||||||
PASSIVE_EVENT_LISTENERS ? {passive: false} : false
|
PASSIVE_EVENT_LISTENERS ? {passive: false} : false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const defaultView = this.getOwnerDocument().defaultView;
|
||||||
const keyboardEventTarget = !this.keyboardEventTarget_
|
const keyboardEventTarget = !this.keyboardEventTarget_
|
||||||
? targetElement
|
? targetElement
|
||||||
: this.keyboardEventTarget_;
|
: this.keyboardEventTarget_;
|
||||||
this.keyHandlerKeys_ = [
|
this.targetChangeHandlerKeys_ = [
|
||||||
listen(
|
listen(
|
||||||
keyboardEventTarget,
|
keyboardEventTarget,
|
||||||
EventType.KEYDOWN,
|
EventType.KEYDOWN,
|
||||||
@@ -1233,12 +1220,8 @@ class PluggableMap extends BaseObject {
|
|||||||
this.handleBrowserEvent,
|
this.handleBrowserEvent,
|
||||||
this
|
this
|
||||||
),
|
),
|
||||||
|
listen(defaultView, EventType.RESIZE, this.updateSize, this),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!this.handleResize_) {
|
|
||||||
this.handleResize_ = this.updateSize.bind(this);
|
|
||||||
window.addEventListener(EventType.RESIZE, this.handleResize_, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateSize();
|
this.updateSize();
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
*/
|
*/
|
||||||
import Control from './Control.js';
|
import Control from './Control.js';
|
||||||
import EventType from '../events/EventType.js';
|
import EventType from '../events/EventType.js';
|
||||||
|
import MapProperty from '../MapProperty.js';
|
||||||
import {CLASS_CONTROL, CLASS_UNSELECTABLE, CLASS_UNSUPPORTED} from '../css.js';
|
import {CLASS_CONTROL, CLASS_UNSELECTABLE, CLASS_UNSUPPORTED} from '../css.js';
|
||||||
import {listen} from '../events.js';
|
import {listen, unlistenByKey} from '../events.js';
|
||||||
import {replaceNode} from '../dom.js';
|
import {replaceNode} from '../dom.js';
|
||||||
|
|
||||||
const events = [
|
const events = [
|
||||||
@@ -111,6 +112,12 @@ class FullScreen extends Control {
|
|||||||
this.cssClassName_ =
|
this.cssClassName_ =
|
||||||
options.className !== undefined ? options.className : 'ol-full-screen';
|
options.className !== undefined ? options.className : 'ol-full-screen';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Array<import("../events.js").EventsKey>}
|
||||||
|
*/
|
||||||
|
this.documentListeners_ = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {Array<string>}
|
* @type {Array<string>}
|
||||||
@@ -157,7 +164,6 @@ class FullScreen extends Control {
|
|||||||
this.button_ = document.createElement('button');
|
this.button_ = document.createElement('button');
|
||||||
|
|
||||||
const tipLabel = options.tipLabel ? options.tipLabel : 'Toggle full-screen';
|
const tipLabel = options.tipLabel ? options.tipLabel : 'Toggle full-screen';
|
||||||
this.setClassName_(this.button_, isFullScreen());
|
|
||||||
this.button_.setAttribute('type', 'button');
|
this.button_.setAttribute('type', 'button');
|
||||||
this.button_.title = tipLabel;
|
this.button_.title = tipLabel;
|
||||||
this.button_.appendChild(this.labelNode_);
|
this.button_.appendChild(this.labelNode_);
|
||||||
@@ -168,17 +174,8 @@ class FullScreen extends Control {
|
|||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
const cssClasses =
|
this.element.className = `${this.cssClassName_} ${CLASS_UNSELECTABLE} ${CLASS_CONTROL}`;
|
||||||
this.cssClassName_ +
|
this.element.appendChild(this.button_);
|
||||||
' ' +
|
|
||||||
CLASS_UNSELECTABLE +
|
|
||||||
' ' +
|
|
||||||
CLASS_CONTROL +
|
|
||||||
' ' +
|
|
||||||
(!isFullScreenSupported() ? CLASS_UNSUPPORTED : '');
|
|
||||||
const element = this.element;
|
|
||||||
element.className = cssClasses;
|
|
||||||
element.appendChild(this.button_);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@@ -191,6 +188,17 @@ class FullScreen extends Control {
|
|||||||
* @type {HTMLElement|string|undefined}
|
* @type {HTMLElement|string|undefined}
|
||||||
*/
|
*/
|
||||||
this.source_ = options.source;
|
this.source_ = options.source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.isInFullscreen_ = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.boundHandleMapTargetChange_ = this.handleMapTargetChange_.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -206,21 +214,22 @@ class FullScreen extends Control {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
handleFullScreen_() {
|
handleFullScreen_() {
|
||||||
if (!isFullScreenSupported()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const map = this.getMap();
|
const map = this.getMap();
|
||||||
if (!map) {
|
if (!map) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isFullScreen()) {
|
const doc = map.getOwnerDocument();
|
||||||
exitFullScreen();
|
if (!isFullScreenSupported(doc)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isFullScreen(doc)) {
|
||||||
|
exitFullScreen(doc);
|
||||||
} else {
|
} else {
|
||||||
let element;
|
let element;
|
||||||
if (this.source_) {
|
if (this.source_) {
|
||||||
element =
|
element =
|
||||||
typeof this.source_ === 'string'
|
typeof this.source_ === 'string'
|
||||||
? document.getElementById(this.source_)
|
? doc.getElementById(this.source_)
|
||||||
: this.source_;
|
: this.source_;
|
||||||
} else {
|
} else {
|
||||||
element = map.getTargetElement();
|
element = map.getTargetElement();
|
||||||
@@ -238,16 +247,20 @@ class FullScreen extends Control {
|
|||||||
*/
|
*/
|
||||||
handleFullScreenChange_() {
|
handleFullScreenChange_() {
|
||||||
const map = this.getMap();
|
const map = this.getMap();
|
||||||
if (isFullScreen()) {
|
if (!map) {
|
||||||
this.setClassName_(this.button_, true);
|
return;
|
||||||
replaceNode(this.labelActiveNode_, this.labelNode_);
|
|
||||||
this.dispatchEvent(FullScreenEventType.ENTERFULLSCREEN);
|
|
||||||
} else {
|
|
||||||
this.setClassName_(this.button_, false);
|
|
||||||
replaceNode(this.labelNode_, this.labelActiveNode_);
|
|
||||||
this.dispatchEvent(FullScreenEventType.LEAVEFULLSCREEN);
|
|
||||||
}
|
}
|
||||||
if (map) {
|
const wasInFullscreen = this.isInFullscreen_;
|
||||||
|
this.isInFullscreen_ = isFullScreen(map.getOwnerDocument());
|
||||||
|
if (wasInFullscreen !== this.isInFullscreen_) {
|
||||||
|
this.setClassName_(this.button_, this.isInFullscreen_);
|
||||||
|
if (this.isInFullscreen_) {
|
||||||
|
replaceNode(this.labelActiveNode_, this.labelNode_);
|
||||||
|
this.dispatchEvent(FullScreenEventType.ENTERFULLSCREEN);
|
||||||
|
} else {
|
||||||
|
replaceNode(this.labelNode_, this.labelActiveNode_);
|
||||||
|
this.dispatchEvent(FullScreenEventType.LEAVEFULLSCREEN);
|
||||||
|
}
|
||||||
map.updateSize();
|
map.updateSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -274,37 +287,76 @@ class FullScreen extends Control {
|
|||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
setMap(map) {
|
setMap(map) {
|
||||||
|
const oldMap = this.getMap();
|
||||||
|
if (oldMap) {
|
||||||
|
oldMap.removeChangeListener(
|
||||||
|
MapProperty.TARGET,
|
||||||
|
this.boundHandleMapTargetChange_
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
super.setMap(map);
|
super.setMap(map);
|
||||||
|
|
||||||
|
this.handleMapTargetChange_();
|
||||||
if (map) {
|
if (map) {
|
||||||
|
map.addChangeListener(
|
||||||
|
MapProperty.TARGET,
|
||||||
|
this.boundHandleMapTargetChange_
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
handleMapTargetChange_() {
|
||||||
|
const listeners = this.documentListeners_;
|
||||||
|
for (let i = 0, ii = listeners.length; i < ii; ++i) {
|
||||||
|
unlistenByKey(listeners[i]);
|
||||||
|
}
|
||||||
|
listeners.length = 0;
|
||||||
|
|
||||||
|
const map = this.getMap();
|
||||||
|
if (map) {
|
||||||
|
const doc = map.getOwnerDocument();
|
||||||
|
if (isFullScreenSupported(doc)) {
|
||||||
|
this.element.classList.remove(CLASS_UNSUPPORTED);
|
||||||
|
} else {
|
||||||
|
this.element.classList.add(CLASS_UNSUPPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0, ii = events.length; i < ii; ++i) {
|
for (let i = 0, ii = events.length; i < ii; ++i) {
|
||||||
this.listenerKeys.push(
|
listeners.push(
|
||||||
listen(document, events[i], this.handleFullScreenChange_, this)
|
listen(doc, events[i], this.handleFullScreenChange_, this)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
this.handleFullScreenChange_();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param {Document} doc The root document to check.
|
||||||
* @return {boolean} Fullscreen is supported by the current platform.
|
* @return {boolean} Fullscreen is supported by the current platform.
|
||||||
*/
|
*/
|
||||||
function isFullScreenSupported() {
|
function isFullScreenSupported(doc) {
|
||||||
const body = document.body;
|
const body = doc.body;
|
||||||
return !!(
|
return !!(
|
||||||
body['webkitRequestFullscreen'] ||
|
body['webkitRequestFullscreen'] ||
|
||||||
(body['msRequestFullscreen'] && document['msFullscreenEnabled']) ||
|
(body['msRequestFullscreen'] && doc['msFullscreenEnabled']) ||
|
||||||
(body.requestFullscreen && document.fullscreenEnabled)
|
(body.requestFullscreen && doc.fullscreenEnabled)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param {Document} doc The root document to check.
|
||||||
* @return {boolean} Element is currently in fullscreen.
|
* @return {boolean} Element is currently in fullscreen.
|
||||||
*/
|
*/
|
||||||
function isFullScreen() {
|
function isFullScreen(doc) {
|
||||||
return !!(
|
return !!(
|
||||||
document['webkitIsFullScreen'] ||
|
doc['webkitIsFullScreen'] ||
|
||||||
document['msFullscreenElement'] ||
|
doc['msFullscreenElement'] ||
|
||||||
document.fullscreenElement
|
doc.fullscreenElement
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,14 +388,15 @@ function requestFullScreenWithKeys(element) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Exit fullscreen.
|
* Exit fullscreen.
|
||||||
|
* @param {Document} doc The document to exit fullscren from
|
||||||
*/
|
*/
|
||||||
function exitFullScreen() {
|
function exitFullScreen(doc) {
|
||||||
if (document.exitFullscreen) {
|
if (doc.exitFullscreen) {
|
||||||
document.exitFullscreen();
|
doc.exitFullscreen();
|
||||||
} else if (document['msExitFullscreen']) {
|
} else if (doc['msExitFullscreen']) {
|
||||||
document['msExitFullscreen']();
|
doc['msExitFullscreen']();
|
||||||
} else if (document['webkitExitFullscreen']) {
|
} else if (doc['webkitExitFullscreen']) {
|
||||||
document['webkitExitFullscreen']();
|
doc['webkitExitFullscreen']();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,9 @@ export const altShiftKeysOnly = function (mapBrowserEvent) {
|
|||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
export const focus = function (event) {
|
export const focus = function (event) {
|
||||||
return event.target.getTargetElement().contains(document.activeElement);
|
const targetElement = event.map.getTargetElement();
|
||||||
|
const activeElement = event.map.getOwnerDocument().activeElement;
|
||||||
|
return targetElement.contains(activeElement);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -816,7 +816,7 @@ describe('ol/Map', function () {
|
|||||||
|
|
||||||
it('removes window listeners', function () {
|
it('removes window listeners', function () {
|
||||||
map.dispose();
|
map.dispose();
|
||||||
expect(map.handleResize_).to.be(undefined);
|
expect(map.targetChangeHandlerKeys_).to.be(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -828,7 +828,7 @@ describe('ol/Map', function () {
|
|||||||
map = new Map({
|
map = new Map({
|
||||||
target: document.createElement('div'),
|
target: document.createElement('div'),
|
||||||
});
|
});
|
||||||
expect(map.handleResize_).to.be.ok();
|
expect(map.targetChangeHandlerKeys_).to.be.ok();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('map with target not attached to dom', function () {
|
describe('map with target not attached to dom', function () {
|
||||||
@@ -840,7 +840,7 @@ describe('ol/Map', function () {
|
|||||||
describe('call setTarget with null', function () {
|
describe('call setTarget with null', function () {
|
||||||
it('unregisters the viewport resize listener', function () {
|
it('unregisters the viewport resize listener', function () {
|
||||||
map.setTarget(null);
|
map.setTarget(null);
|
||||||
expect(map.handleResize_).to.be(undefined);
|
expect(map.targetChangeHandlerKeys_).to.be(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -848,7 +848,7 @@ describe('ol/Map', function () {
|
|||||||
it('registers a viewport resize listener', function () {
|
it('registers a viewport resize listener', function () {
|
||||||
map.setTarget(null);
|
map.setTarget(null);
|
||||||
map.setTarget(document.createElement('div'));
|
map.setTarget(document.createElement('div'));
|
||||||
expect(map.handleResize_).to.be.ok();
|
expect(map.targetChangeHandlerKeys_).to.be.ok();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -877,8 +877,14 @@ describe('ol/Map', function () {
|
|||||||
hasAttribute: function (attribute) {
|
hasAttribute: function (attribute) {
|
||||||
return hasTabIndex;
|
return hasTabIndex;
|
||||||
},
|
},
|
||||||
|
contains: function () {
|
||||||
|
return hasFocus;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
getOwnerDocument: function () {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
originalEvent: {
|
originalEvent: {
|
||||||
isPrimary: isPrimary,
|
isPrimary: isPrimary,
|
||||||
|
|||||||
Reference in New Issue
Block a user