feat: add share controls
This commit is contained in:
@@ -164,6 +164,7 @@ const map = new ol.Map({
|
|||||||
createVisibleLayersExtentControl(),
|
createVisibleLayersExtentControl(),
|
||||||
createAreaZoomControl(),
|
createAreaZoomControl(),
|
||||||
...(shouldShowDeviceCompassControl() ? [createDeviceCompassControl()] : []),
|
...(shouldShowDeviceCompassControl() ? [createDeviceCompassControl()] : []),
|
||||||
|
createShareControl(),
|
||||||
createNorthPointerControl(),
|
createNorthPointerControl(),
|
||||||
]),
|
]),
|
||||||
layers: [
|
layers: [
|
||||||
@@ -296,6 +297,45 @@ function createVisibleLayersExtentControl() {
|
|||||||
return new ol.control.Control({ element });
|
return new ol.control.Control({ element });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createShareControl() {
|
||||||
|
const element = document.createElement("div");
|
||||||
|
element.className = "ol-share ol-unselectable ol-control";
|
||||||
|
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.type = "button";
|
||||||
|
button.title = "Copy link to this map view";
|
||||||
|
button.setAttribute("aria-label", "Copy link to this map view");
|
||||||
|
const icon = document.createElement("span");
|
||||||
|
icon.className = "material-symbols-outlined";
|
||||||
|
icon.setAttribute("aria-hidden", "true");
|
||||||
|
icon.textContent = "share";
|
||||||
|
button.append(icon);
|
||||||
|
button.addEventListener("click", copyCurrentViewUrl);
|
||||||
|
|
||||||
|
element.append(button);
|
||||||
|
return new ol.control.Control({ element });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyCurrentViewUrl() {
|
||||||
|
// Ensure the copied address reflects state changes that may not yet have
|
||||||
|
// reached the next map movement event.
|
||||||
|
syncSharedMapStateToUrl();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!navigator.clipboard?.writeText) {
|
||||||
|
throw new Error("Clipboard access is unavailable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await navigator.clipboard.writeText(window.location.href);
|
||||||
|
showMessage("Link to this map view copied to your clipboard.");
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
showMessage("Unable to copy the map link. Please copy the address from your browser.", {
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createAreaZoomControl() {
|
function createAreaZoomControl() {
|
||||||
const element = document.createElement("div");
|
const element = document.createElement("div");
|
||||||
element.className = "ol-area-zoom ol-unselectable ol-control";
|
element.className = "ol-area-zoom ol-unselectable ol-control";
|
||||||
@@ -993,12 +1033,35 @@ async function renderFeatureModal(feature, isParcel, requestId) {
|
|||||||
content.append(section);
|
content.append(section);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content.append(createFeatureModalShareButton());
|
||||||
|
|
||||||
// A linked-feature request may finish after a newer selection was made.
|
// A linked-feature request may finish after a newer selection was made.
|
||||||
if (!els.featureModal.hidden && requestId === featureModalRequestId) {
|
if (!els.featureModal.hidden && requestId === featureModalRequestId) {
|
||||||
els.featureModalContent.replaceChildren(content);
|
els.featureModalContent.replaceChildren(content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createFeatureModalShareButton() {
|
||||||
|
const actions = document.createElement("div");
|
||||||
|
actions.className = "feature-modal-actions";
|
||||||
|
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.type = "button";
|
||||||
|
button.className = "feature-modal-share";
|
||||||
|
button.title = "Copy link to this map view";
|
||||||
|
button.setAttribute("aria-label", "Copy link to this map view");
|
||||||
|
|
||||||
|
const icon = document.createElement("span");
|
||||||
|
icon.className = "material-symbols-outlined";
|
||||||
|
icon.setAttribute("aria-hidden", "true");
|
||||||
|
icon.textContent = "share";
|
||||||
|
button.append(icon);
|
||||||
|
button.addEventListener("click", copyCurrentViewUrl);
|
||||||
|
|
||||||
|
actions.append(button);
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
function createFeatureHeading(kind, id) {
|
function createFeatureHeading(kind, id) {
|
||||||
const heading = document.createElement("h2");
|
const heading = document.createElement("h2");
|
||||||
heading.id = "feature-modal-title";
|
heading.id = "feature-modal-title";
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>XPro Map Visualizer</title>
|
<title>XPro Map Visualizer</title>
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,400,0,0" />
|
||||||
<link rel="stylesheet" href="./vendor/ol.css" />
|
<link rel="stylesheet" href="./vendor/ol.css" />
|
||||||
<link rel="stylesheet" href="./styles.css" />
|
<link rel="stylesheet" href="./styles.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
+74
-14
@@ -8,6 +8,7 @@
|
|||||||
--accent: #1f7a62;
|
--accent: #1f7a62;
|
||||||
--accent-strong: #125441;
|
--accent-strong: #125441;
|
||||||
--shadow: 0 24px 60px rgba(52, 43, 28, 0.12);
|
--shadow: 0 24px 60px rgba(52, 43, 28, 0.12);
|
||||||
|
--map-tool-size: 42px;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -579,6 +580,26 @@ button:disabled {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.feature-modal-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-modal-share {
|
||||||
|
display: grid;
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
padding: 0;
|
||||||
|
place-items: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-modal-share .material-symbols-outlined {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.map-wrap .ol-zoom {
|
.map-wrap .ol-zoom {
|
||||||
top: 16px;
|
top: 16px;
|
||||||
right: 16px;
|
right: 16px;
|
||||||
@@ -592,9 +613,10 @@ button:disabled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.map-wrap .ol-zoom button,
|
.map-wrap .ol-zoom button,
|
||||||
.map-wrap .ol-visible-layers-extent button {
|
.map-wrap .ol-visible-layers-extent button,
|
||||||
width: 2.0625em;
|
.map-wrap .ol-share button {
|
||||||
height: 2.0625em;
|
width: var(--map-tool-size);
|
||||||
|
height: var(--map-tool-size);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: linear-gradient(135deg, var(--accent) 0%, #2b9a79 100%);
|
background: linear-gradient(135deg, var(--accent) 0%, #2b9a79 100%);
|
||||||
color: #fffefb;
|
color: #fffefb;
|
||||||
@@ -610,7 +632,9 @@ button:disabled {
|
|||||||
.map-wrap .ol-zoom button:hover,
|
.map-wrap .ol-zoom button:hover,
|
||||||
.map-wrap .ol-zoom button:focus,
|
.map-wrap .ol-zoom button:focus,
|
||||||
.map-wrap .ol-visible-layers-extent button:hover,
|
.map-wrap .ol-visible-layers-extent button:hover,
|
||||||
.map-wrap .ol-visible-layers-extent button:focus {
|
.map-wrap .ol-visible-layers-extent button:focus,
|
||||||
|
.map-wrap .ol-share button:hover,
|
||||||
|
.map-wrap .ol-share button:focus {
|
||||||
background: linear-gradient(135deg, var(--accent-strong) 0%, var(--accent) 100%);
|
background: linear-gradient(135deg, var(--accent-strong) 0%, var(--accent) 100%);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
outline: 2px solid rgba(31, 122, 98, 0.35);
|
outline: 2px solid rgba(31, 122, 98, 0.35);
|
||||||
@@ -618,7 +642,7 @@ button:disabled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.map-wrap .ol-visible-layers-extent {
|
.map-wrap .ol-visible-layers-extent {
|
||||||
top: 96px;
|
top: 108px;
|
||||||
right: 16px;
|
right: 16px;
|
||||||
left: auto;
|
left: auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -634,7 +658,7 @@ button:disabled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.map-wrap .ol-area-zoom {
|
.map-wrap .ol-area-zoom {
|
||||||
top: 140px;
|
top: 158px;
|
||||||
right: 16px;
|
right: 16px;
|
||||||
left: auto;
|
left: auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -647,8 +671,8 @@ button:disabled {
|
|||||||
|
|
||||||
.map-wrap .ol-area-zoom button {
|
.map-wrap .ol-area-zoom button {
|
||||||
display: grid;
|
display: grid;
|
||||||
width: 2.0625em;
|
width: var(--map-tool-size);
|
||||||
height: 2.0625em;
|
height: var(--map-tool-size);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
@@ -667,6 +691,33 @@ button:disabled {
|
|||||||
outline-offset: -2px;
|
outline-offset: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.map-wrap .ol-share {
|
||||||
|
top: 208px;
|
||||||
|
right: 16px;
|
||||||
|
left: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
border-radius: 14px;
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When available, keep Share after the device compass in the tool stack. */
|
||||||
|
.map-wrap .ol-device-compass ~ .ol-share {
|
||||||
|
top: 258px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-wrap .ol-share button {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-wrap .ol-share .material-symbols-outlined {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.area-zoom-icon {
|
.area-zoom-icon {
|
||||||
display: block;
|
display: block;
|
||||||
width: 1.2em;
|
width: 1.2em;
|
||||||
@@ -684,7 +735,7 @@ button:disabled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.map-wrap .ol-device-compass {
|
.map-wrap .ol-device-compass {
|
||||||
top: 184px;
|
top: 208px;
|
||||||
right: 16px;
|
right: 16px;
|
||||||
left: auto;
|
left: auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -697,8 +748,8 @@ button:disabled {
|
|||||||
|
|
||||||
.map-wrap .ol-device-compass button {
|
.map-wrap .ol-device-compass button {
|
||||||
display: grid;
|
display: grid;
|
||||||
width: 2.0625em;
|
width: var(--map-tool-size);
|
||||||
height: 2.0625em;
|
height: var(--map-tool-size);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
@@ -800,17 +851,26 @@ button:disabled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.map-wrap .ol-visible-layers-extent {
|
.map-wrap .ol-visible-layers-extent {
|
||||||
top: 92px;
|
top: 104px;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-wrap .ol-area-zoom {
|
.map-wrap .ol-area-zoom {
|
||||||
top: 136px;
|
top: 154px;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.map-wrap .ol-share {
|
||||||
|
top: 204px;
|
||||||
|
right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-wrap .ol-device-compass ~ .ol-share {
|
||||||
|
top: 254px;
|
||||||
|
}
|
||||||
|
|
||||||
.map-wrap .ol-device-compass {
|
.map-wrap .ol-device-compass {
|
||||||
top: 180px;
|
top: 204px;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user