Files
tront.noname.company/map-dialog.js

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();
});
})();