114 lines
4.5 KiB
JavaScript
114 lines
4.5 KiB
JavaScript
/* Keep the map and its navigation inside a dismissible view. */
|
|
(() => {
|
|
'use strict';
|
|
const trigger = document.querySelector('.map-link');
|
|
const dialog = document.querySelector('#map-dialog');
|
|
if (!trigger || !dialog) return;
|
|
|
|
const canvas = dialog.querySelector('.map-dialog-canvas');
|
|
const body = dialog.querySelector('.map-dialog-body');
|
|
const status = dialog.querySelector('.map-dialog-status');
|
|
const retry = dialog.querySelector('.map-dialog-retry');
|
|
const closeButton = dialog.querySelector('.map-dialog-close');
|
|
const historyKey = '__trontMapDialog';
|
|
const token = `map-${Date.now()}`;
|
|
let frame, timer, savedScroll, savedStyle, waitingForBack = false;
|
|
let backdropPressed = false;
|
|
|
|
function showError() {
|
|
clearTimeout(timer);
|
|
frame?.remove();
|
|
frame = null;
|
|
body.setAttribute('aria-busy', 'false');
|
|
status.hidden = false;
|
|
status.querySelector('p').textContent = '지도를 불러오지 못했습니다. 연결 상태를 확인하고 다시 시도해 주세요.';
|
|
retry.hidden = false;
|
|
}
|
|
|
|
function loadMap() {
|
|
clearTimeout(timer);
|
|
frame?.remove();
|
|
body.setAttribute('aria-busy', 'true');
|
|
status.hidden = false;
|
|
status.querySelector('p').textContent = '지도를 불러오는 중입니다.';
|
|
retry.hidden = true;
|
|
frame = document.createElement('iframe');
|
|
frame.title = '구글 지도 — 트론트, IT융합산업빌딩 위치';
|
|
// The campus shares one street address; use the IT building's location.
|
|
frame.src = 'https://maps.google.com/maps?hl=ko&q=35.8920895,128.6141078&z=17&output=embed';
|
|
frame.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox');
|
|
frame.referrerPolicy = 'no-referrer-when-downgrade';
|
|
const loadingFrame = frame;
|
|
frame.addEventListener('load', () => {
|
|
if (frame !== loadingFrame || !dialog.open) return;
|
|
clearTimeout(timer);
|
|
body.setAttribute('aria-busy', 'false');
|
|
status.hidden = true;
|
|
});
|
|
frame.addEventListener('error', showError);
|
|
canvas.replaceChildren(frame);
|
|
timer = setTimeout(showError, 20000);
|
|
}
|
|
|
|
function openMap(addHistory = true) {
|
|
if (dialog.open || waitingForBack) return;
|
|
savedScroll = { x: scrollX, y: scrollY };
|
|
// Save the page's history entry before fixing the body resets scrollY to 0.
|
|
if (addHistory) {
|
|
try { history.pushState({ ...history.state, [historyKey]: token }, '', location.href); }
|
|
catch { /* The close controls also work in local file previews. */ }
|
|
}
|
|
savedStyle = ['position', 'top', 'left', 'width', 'paddingRight'].map(key => [key, document.body.style[key]]);
|
|
const scrollbar = innerWidth - document.documentElement.clientWidth;
|
|
const padding = parseFloat(getComputedStyle(document.body).paddingRight) || 0;
|
|
Object.assign(document.body.style, {
|
|
position: 'fixed', top: `-${savedScroll.y}px`, left: `-${savedScroll.x}px`,
|
|
width: '100%', paddingRight: `${padding + scrollbar}px`
|
|
});
|
|
document.documentElement.classList.add('map-dialog-open');
|
|
dialog.showModal();
|
|
closeButton.focus({ preventScroll: true });
|
|
loadMap();
|
|
}
|
|
|
|
function closeMap() {
|
|
if (!dialog.open) return;
|
|
clearTimeout(timer);
|
|
frame?.remove();
|
|
frame = null;
|
|
dialog.close();
|
|
savedStyle.forEach(([key, value]) => { document.body.style[key] = value; });
|
|
document.documentElement.classList.remove('map-dialog-open');
|
|
window.scrollTo({ left: savedScroll.x, top: savedScroll.y, behavior: 'instant' });
|
|
trigger.focus({ preventScroll: true });
|
|
}
|
|
|
|
function dismiss() {
|
|
closeMap();
|
|
if (history.state?.[historyKey] === token) {
|
|
waitingForBack = true;
|
|
history.back();
|
|
}
|
|
}
|
|
|
|
function isBackdrop(event) {
|
|
const rect = dialog.getBoundingClientRect();
|
|
return event.target === dialog && (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom);
|
|
}
|
|
|
|
trigger.addEventListener('click', () => openMap());
|
|
closeButton.addEventListener('click', dismiss);
|
|
retry.addEventListener('click', loadMap);
|
|
dialog.addEventListener('cancel', event => { event.preventDefault(); dismiss(); });
|
|
dialog.addEventListener('pointerdown', event => { backdropPressed = isBackdrop(event); });
|
|
dialog.addEventListener('click', event => {
|
|
if (backdropPressed && isBackdrop(event)) dismiss();
|
|
backdropPressed = false;
|
|
});
|
|
window.addEventListener('popstate', event => {
|
|
waitingForBack = false;
|
|
if (event.state?.[historyKey] === token) openMap(false);
|
|
else closeMap();
|
|
});
|
|
})();
|