Files
gods-eye-view/src/ui/sceneSharing.js
T

138 lines
3.9 KiB
JavaScript

/** Small owned modal for scene authoring and import review; text never becomes markup. */
export function createSceneDialog(title, onClose) {
const dialog = document.createElement('dialog');
dialog.dataset.directorDialog = '';
dialog.className = 'director-sharing-dialog';
dialog.setAttribute('aria-label', title);
Object.assign(dialog.style, {
background: '#08141e',
color: '#e3faff',
border: '1px solid #397080',
borderRadius: '6px',
padding: '20px',
width: 'min(720px,calc(100vw - 32px))',
boxSizing: 'border-box',
position: 'fixed',
inset: '0',
margin: 'auto',
maxHeight: 'calc(100vh - 32px)',
overflow: 'auto',
font: '14px/1.5 sans-serif',
});
const heading = document.createElement('h2');
heading.textContent = title;
heading.style.marginBottom = '12px';
dialog.append(heading);
const body = document.createElement('div'),
footer = document.createElement('div'),
status = document.createElement('p');
status.setAttribute('role', 'status');
dialog.append(body, status, footer);
const listeners = [];
function listen(node, type, fn) {
node.addEventListener(type, fn);
listeners.push(() => node.removeEventListener(type, fn));
}
function text(value, parent = body) {
const p = document.createElement('p');
p.textContent = value;
p.style.margin = '8px 0';
parent.append(p);
return p;
}
function button(label, fn, parent = footer) {
const b = document.createElement('button');
b.type = 'button';
b.textContent = label;
Object.assign(b.style, {
background: '#10242e',
color: '#e3faff',
border: '1px solid #397080',
borderRadius: '3px',
padding: '7px 11px',
margin: '6px 6px 6px 0',
cursor: 'pointer',
font: 'inherit',
});
listen(b, 'click', fn);
parent.append(b);
return b;
}
function input(label, value, { type = 'text', multiline = false } = {}) {
const wrapper = document.createElement('label');
wrapper.style.display = 'block';
wrapper.textContent = label;
const node = document.createElement(multiline ? 'textarea' : 'input');
if (!multiline) node.type = type;
node.value = value;
node.setAttribute('aria-label', label);
Object.assign(node.style, {
display: 'block',
boxSizing: 'border-box',
width: '100%',
padding: '7px',
margin: '6px 0 14px',
color: '#e3faff',
background: '#10242e',
border: '1px solid #397080',
font: multiline ? '12px monospace' : 'inherit',
});
if (multiline) node.rows = 9;
wrapper.append(node);
body.append(wrapper);
return node;
}
button('Cancel', onClose);
listen(dialog, 'cancel', (event) => {
event.preventDefault();
onClose();
});
document.body.append(dialog);
dialog.showModal();
return {
body,
footer,
status,
text,
button,
input,
listen,
element: dialog,
dispose() {
for (const remove of listeners.splice(0)) remove();
dialog.close();
dialog.remove();
},
};
}
/** Add controls only when their owner is present; dispose removes every listener and element. */
export function mountSceneSharing(
{ edit, share },
panel = document.getElementById('scene-panel'),
) {
if (!panel) return () => {};
const bar = document.createElement('div');
bar.dataset.directorAuthoring = '';
bar.className = 'scene-controls';
const entries = [
['EDIT DETAILS', edit],
['SHARE SCENE', share],
];
const removers = [];
for (const [text, fn] of entries) {
const b = document.createElement('button');
b.type = 'button';
b.textContent = text;
b.className = 'scene-btn';
b.addEventListener('click', fn);
removers.push(() => b.removeEventListener('click', fn));
bar.append(b);
}
(panel.querySelector?.('.scene-panel-inner') || panel).appendChild(bar);
return () => {
for (const remove of removers) remove();
bar.remove();
};
}