/* ---------- Object Overlay Management ---------- */
/* DOM Elements */
let attributesObjectViewBtn, objectOverlay, ovTitle, ovBody, closeOverlayBtn;
/* ---------- Attribute Row Creation ---------- */
function makeAttrRow(scope, scopeId, idx, attr) {
const row = document.createElement('div');
row.className = 'attr-row';
const keyInput = document.createElement('input');
keyInput.type = 'text';
keyInput.placeholder = 'Attribute name';
keyInput.className = 'attr-key';
keyInput.value = attr.key ?? '';
keyInput.dataset.scope = scope;
keyInput.dataset.scopeId = scopeId;
keyInput.dataset.index = String(idx);
const valueContainer = document.createElement('div');
valueContainer.style.position = 'relative';
valueContainer.style.flex = '1';
const valInput = document.createElement('input');
valInput.type = 'text';
valInput.placeholder = 'Value';
valInput.className = 'attr-val';
valInput.value = attr.value ?? '';
valInput.dataset.scope = scope;
valInput.dataset.scopeId = scopeId;
valInput.dataset.index = String(idx);
// Create autocomplete dropdown
const autocompleteDropdown = document.createElement('div');
autocompleteDropdown.style.position = 'absolute';
autocompleteDropdown.style.top = '100%';
autocompleteDropdown.style.left = '0';
autocompleteDropdown.style.right = '0';
autocompleteDropdown.style.background = '#1e1e1e';
autocompleteDropdown.style.border = '1px solid #333';
autocompleteDropdown.style.borderTop = 'none';
autocompleteDropdown.style.borderRadius = '0 0 .35rem .35rem';
autocompleteDropdown.style.maxHeight = '150px';
autocompleteDropdown.style.overflowY = 'auto';
autocompleteDropdown.style.display = 'none';
autocompleteDropdown.style.zIndex = '1000';
// Value options for different attribute types
const valueOptions = {
// Boolean attributes
randomizeOnPlace: ['false', 'true'],
shuffleFrames: ['false', 'true'],
immovable: ['false', 'true'],
rotationLock: ['false', 'true'],
float: ['false', 'true'],
collides: ['false', 'true'],
// Enum attributes
playMode: ['loop', 'once', 'pingpong'],
'physics.bodyType': ['static', 'dynamic', 'kinematic'],
blockType: ['ground', 'platform', 'wall', 'ladder', 'water', 'lava', 'decor'],
license: ['CC0', 'CC-BY', 'custom'],
randomFlip: ['none', 'x', 'y', 'xy'],
trigger: ['onTouch', 'onOverlap', 'onClick'],
// Numeric ranges
frameRate: ['10', '12', '15', '24', '30', '60'],
damage: ['0', '1', '2', '5', '10'],
health: ['1', '3', '5', '10', '100'],
speed: ['50', '100', '150', '200', '300'],
jumpStrength: ['200', '300', '400', '500'],
bounce: ['0', '0.2', '0.5', '0.8', '1'],
friction: ['0', '0.1', '0.5', '0.8', '1'],
yoyoChance: ['0', '0.1', '0.25', '0.5', '0.75', '1'],
// Coordinate presets
'origin.x': ['0', '0.5', '1'],
'origin.y': ['0', '0.5', '1'],
// Array/Object templates
jitterX: ['[0,0]', '[-5,5]', '[-10,10]', '[-20,20]'],
jitterY: ['[0,0]', '[-5,5]', '[-10,10]', '[-20,20]'],
jitterRot: ['[0,0]', '[-15,15]', '[-45,45]', '[-90,90]'],
jitterScale: ['[1,1]', '[0.8,1.2]', '[0.5,1.5]', '[0.5,2]'],
randomPick: ['[]', '[1]', '[1,2,3]', '[{"weight":1,"value":"A"}]'],
paletteRandom: ['[]', '["0xff0000","0x00ff00","0x0000ff"]'],
magnet: ['{"strength":0,"radius":0}', '{"strength":100,"radius":50}'],
patrol: ['{"range":100,"speed":50}', '{"range":200,"speed":100}'],
respawn: ['{"time":3}', '{"time":1}', '{"time":5}'],
collectible: ['{"value":1}', '{"value":5}', '{"value":10}'],
// Tags examples
tags: ['', 'enemy', 'hazard', 'collectible', 'enemy,flying', 'hazard,spikes']
};
function showAutocomplete(attributeKey, currentValue = '') {
const options = valueOptions[attributeKey];
if (!options || options.length === 0) {
autocompleteDropdown.style.display = 'none';
return;
}
// Always show all options, no filtering
autocompleteDropdown.innerHTML = '';
options.forEach(option => {
const optionDiv = document.createElement('div');
optionDiv.style.padding = '.3rem .5rem';
optionDiv.style.cursor = 'pointer';
optionDiv.style.borderBottom = '1px solid #2a2a2a';
optionDiv.textContent = option;
// Highlight if it matches current value
if (option === currentValue) {
optionDiv.style.background = '#444';
optionDiv.style.color = '#4fc3f7';
}
optionDiv.onmouseover = () => {
optionDiv.style.background = '#333';
};
optionDiv.onmouseout = () => {
optionDiv.style.background = option === currentValue ? '#444' : 'transparent';
};
optionDiv.onclick = () => {
valInput.value = option;
autocompleteDropdown.style.display = 'none';
valInput.focus();
// Trigger change event for live editing
valInput.dispatchEvent(new Event('input'));
};
autocompleteDropdown.appendChild(optionDiv);
});
autocompleteDropdown.style.display = 'block';
}
function hideAutocomplete() {
setTimeout(() => {
autocompleteDropdown.style.display = 'none';
}, 150); // Small delay to allow clicking on options
}
// Show autocomplete on focus - always show all options
valInput.onfocus = () => {
const attributeKey = keyInput.value;
showAutocomplete(attributeKey, valInput.value);
};
// Keep showing all options as user types (no filtering)
valInput.oninput = () => {
const attributeKey = keyInput.value;
showAutocomplete(attributeKey, valInput.value);
};
// Hide autocomplete on blur
valInput.onblur = hideAutocomplete;
// Keyboard navigation for autocomplete
valInput.onkeydown = (e) => {
if (autocompleteDropdown.style.display === 'none') return;
const options = autocompleteDropdown.children;
const currentSelected = Array.from(options).findIndex(opt =>
opt.style.background === 'rgb(51, 51, 51)'
);
if (e.key === 'ArrowDown') {
e.preventDefault();
const nextIndex = Math.min(currentSelected + 1, options.length - 1);
Array.from(options).forEach((opt, i) => {
opt.style.background = i === nextIndex ? '#333' : 'transparent';
});
} else if (e.key === 'ArrowUp') {
e.preventDefault();
const prevIndex = Math.max(currentSelected - 1, 0);
Array.from(options).forEach((opt, i) => {
opt.style.background = i === prevIndex ? '#333' : 'transparent';
});
} else if (e.key === 'Enter') {
e.preventDefault();
if (currentSelected >= 0 && options[currentSelected]) {
options[currentSelected].click();
}
} else if (e.key === 'Escape') {
autocompleteDropdown.style.display = 'none';
}
};
const delBtn = document.createElement('button');
delBtn.textContent = '🗑️';
delBtn.title = 'Remove attribute';
delBtn.className = 'attr-del';
delBtn.dataset.scope = scope;
delBtn.dataset.scopeId = scopeId;
delBtn.dataset.index = String(idx);
valueContainer.appendChild(valInput);
valueContainer.appendChild(autocompleteDropdown);
row.appendChild(keyInput);
row.appendChild(valueContainer);
row.appendChild(delBtn);
return row;
}
/* ---------- Object Attributes Block ---------- */
function buildObjectAttributesBlock(obj) {
const wrap = document.createElement('div');
wrap.className = 'ov-obj-attrs';
wrap.style.background = '#151515';
wrap.style.border = '1px solid #2a2a2a';
wrap.style.borderRadius = '.6rem';
wrap.style.padding = '.6rem';
wrap.style.marginBottom = '.8rem';
const head = document.createElement('div');
head.style.display = 'flex';
head.style.alignItems = 'center';
head.style.gap = '.6rem';
const title = document.createElement('h3');
title.textContent = 'Object Attributes';
title.style.margin = '.1rem 0';
title.style.color = '#ddd';
const addKey = document.createElement('input');
addKey.type = 'text';
addKey.placeholder = 'Attribute name';
addKey.id = 'ov-add-obj-key';
const addVal = document.createElement('input');
addVal.type = 'text';
addVal.placeholder = 'Value';
addVal.id = 'ov-add-obj-val';
const addBtn = document.createElement('button');
addBtn.textContent = '➕ Add';
addBtn.className = 'attr-add';
addBtn.dataset.scope = 'object';
addBtn.dataset.scopeId = obj.id;
head.appendChild(title);
head.appendChild(addKey);
head.appendChild(addVal);
head.appendChild(addBtn);
const list = document.createElement('div');
list.className = 'attr-list';
list.dataset.scope = 'object';
list.dataset.scopeId = obj.id;
for (let i = 0; i < (obj.attributes?.length || 0); i++) {
list.appendChild(makeAttrRow('object', obj.id, i, obj.attributes[i]));
}
wrap.appendChild(head);
wrap.appendChild(list);
return wrap;
}
/* ---------- Line Block with Collapsible Sections ---------- */
function buildLineBlock(line) {
const block = document.createElement('div');
block.className = 'ov-line';
const h = document.createElement('h3');
h.textContent = line.name;
// Helper function to create collapsible sections
function createCollapsibleSection(title, icon, content) {
const section = document.createElement('div');
section.style.border = '1px solid #2a2a2a';
section.style.borderRadius = '.4rem';
section.style.marginBottom = '.6rem';
section.style.overflow = 'visible'; // Changed from 'hidden' to show dropdowns
const header = document.createElement('div');
header.style.background = '#1a1a1a';
header.style.padding = '.5rem .6rem';
header.style.cursor = 'pointer';
header.style.display = 'flex';
header.style.alignItems = 'center';
header.style.gap = '.5rem';
header.style.borderBottom = 'none';
const arrow = document.createElement('span');
arrow.textContent = '▶';
arrow.style.fontSize = '.9rem';
arrow.style.color = '#4fc3f7';
arrow.style.transition = 'transform 0.2s ease';
const titleSpan = document.createElement('span');
titleSpan.textContent = `${icon} ${title}`;
titleSpan.style.color = '#4fc3f7';
titleSpan.style.fontWeight = 'bold';
const contentDiv = document.createElement('div');
contentDiv.style.background = '#151515';
contentDiv.style.maxHeight = '0px';
contentDiv.style.overflow = 'hidden';
contentDiv.style.transition = 'max-height 0.3s ease, padding 0.3s ease';
contentDiv.style.padding = '0 .6rem';
header.onclick = () => {
const isExpanded = contentDiv.style.maxHeight !== '0px';
if (isExpanded) {
contentDiv.style.maxHeight = '0px';
contentDiv.style.padding = '0 .6rem';
arrow.textContent = '▶';
arrow.style.transform = 'rotate(0deg)';
header.style.borderBottom = 'none';
} else {
contentDiv.style.maxHeight = '2000px'; // Increased from 1000px to fit more content
contentDiv.style.padding = '.6rem';
arrow.textContent = '▼';
arrow.style.transform = 'rotate(90deg)';
header.style.borderBottom = '1px solid #2a2a2a';
}
};
header.appendChild(arrow);
header.appendChild(titleSpan);
contentDiv.appendChild(content);
section.appendChild(header);
section.appendChild(contentDiv);
return section;
}
// Categorize attributes
const automaticAttrs = ['id', 'url', 'imgWidth', 'imgHeight', 'tileWidth', 'tileHeight', 'rows', 'cols', 'index', 'frames', 'frameCount', 'centerX', 'centerY', 'atlasKey', 'frameKey', 'type'];
const changeableAttrs = ['frameRate', 'playMode', 'origin.x', 'origin.y', 'physics.bodyType', 'collides', 'tags', 'blockType', 'license'];
const automatic = line.attributes.filter(attr => automaticAttrs.includes(attr.key));
const changeable = line.attributes.filter(attr => changeableAttrs.includes(attr.key));
const optional = line.attributes.filter(attr => !automaticAttrs.includes(attr.key) && !changeableAttrs.includes(attr.key));
// Automatic attributes content
const autoContent = document.createElement('div');
automatic.forEach(attr => {
const row = document.createElement('div');
row.style.display = 'flex';
row.style.gap = '.4rem';
row.style.margin = '.2rem 0';
row.style.alignItems = 'center';
const keySpan = document.createElement('span');
keySpan.style.flex = '0 0 120px';
keySpan.style.fontSize = '.85rem';
keySpan.style.color = '#bbb';
keySpan.textContent = attr.key;
const valueSpan = document.createElement('span');
valueSpan.style.flex = '1';
valueSpan.style.fontSize = '.85rem';
valueSpan.style.color = '#ddd';
valueSpan.style.background = '#1a1a1a';
valueSpan.style.padding = '.2rem .4rem';
valueSpan.style.borderRadius = '.3rem';
valueSpan.textContent = attr.value;
row.appendChild(keySpan);
row.appendChild(valueSpan);
autoContent.appendChild(row);
});
// Changeable attributes content
const changeContent = document.createElement('div');
changeable.forEach((attr, i) => {
changeContent.appendChild(makeAttrRow('line', line.id, line.attributes.indexOf(attr), attr));
});
// Add button for changeable attributes
const addChangeableDiv = document.createElement('div');
addChangeableDiv.style.display = 'flex';
addChangeableDiv.style.gap = '.4rem';
addChangeableDiv.style.margin = '.3rem 0';
const changeableSelect = document.createElement('select');
changeableSelect.style.background = '#1e1e1e';
changeableSelect.style.border = '1px solid #333';
changeableSelect.style.color = '#eee';
changeableSelect.style.borderRadius = '.3rem';
changeableSelect.style.padding = '.2rem';
const changeableOptions = [
'frameRate', 'playMode', 'origin.x', 'origin.y', 'physics.bodyType',
'collides', 'tags', 'blockType', 'license'
];
changeableOptions.forEach(opt => {
if (!changeable.find(attr => attr.key === opt)) {
const option = document.createElement('option');
option.value = opt;
option.textContent = opt;
changeableSelect.appendChild(option);
}
});
const addChangeableBtn = document.createElement('button');
addChangeableBtn.textContent = '+ Add';
addChangeableBtn.className = 'attr-add';
addChangeableBtn.onclick = () => {
const selectedKey = changeableSelect.value;
if (selectedKey && !line.attributes.find(attr => attr.key === selectedKey)) {
const defaultValues = {
frameRate: '10', playMode: 'loop', 'origin.x': '0.5', 'origin.y': '0.5',
'physics.bodyType': 'static', collides: 'false', tags: '', blockType: 'decor', license: 'CC0'
};
line.attributes.push({ key: selectedKey, value: defaultValues[selectedKey] || '' });
window.AttributesAPI.renderObjectOverlay();
}
};
if (changeableSelect.children.length > 0) {
addChangeableDiv.appendChild(changeableSelect);
addChangeableDiv.appendChild(addChangeableBtn);
changeContent.appendChild(addChangeableDiv);
}
// Optional attributes content
const optionalContent = document.createElement('div');
optional.forEach((attr, i) => {
optionalContent.appendChild(makeAttrRow('line', line.id, line.attributes.indexOf(attr), attr));
});
// Add preset dropdown for optional attributes
const customAttrDiv = document.createElement('div');
customAttrDiv.style.display = 'flex';
customAttrDiv.style.flexDirection = 'column';
customAttrDiv.style.gap = '.3rem';
customAttrDiv.style.margin = '.3rem 0';
const presetSelect = document.createElement('select');
presetSelect.style.background = '#1e1e1e';
presetSelect.style.border = '1px solid #333';
presetSelect.style.color = '#eee';
presetSelect.style.borderRadius = '.3rem';
presetSelect.style.padding = '.3rem';
presetSelect.style.fontSize = '.9rem';
// Optional attribute categories
const optionalCategories = {
'🎲 Randomization': [
'randomizeOnPlace', 'shuffleFrames', 'randomPick', 'jitterX', 'jitterY',
'jitterRot', 'jitterScale', 'randomFlip', 'paletteRandom', 'yoyoChance', 'seed'
],
'⚙️ Physics Extensions': [
'gravityY', 'gravityX', 'dragX', 'dragY', 'maxVelocityX', 'maxVelocityY',
'bounce', 'friction', 'immovable', 'angularVelocity', 'rotationLock',
'magnet', 'float'
],
'🎮 Gameplay': [
'damage', 'health', 'speed', 'jumpStrength', 'trigger', 'script',
'patrol', 'respawn', 'collectible'
],
'📝 Meta': [
'name', 'author', 'notes', 'group', 'layer'
]
};
const defaultOption = document.createElement('option');
defaultOption.value = '';
defaultOption.textContent = 'Select preset attribute...';
presetSelect.appendChild(defaultOption);
Object.entries(optionalCategories).forEach(([category, attrs]) => {
const optgroup = document.createElement('optgroup');
optgroup.label = category;
attrs.forEach(attr => {
if (!line.attributes.find(a => a.key === attr)) {
const option = document.createElement('option');
option.value = attr;
option.textContent = attr;
optgroup.appendChild(option);
}
});
if (optgroup.children.length > 0) {
presetSelect.appendChild(optgroup);
}
});
const inputRow = document.createElement('div');
inputRow.style.display = 'flex';
inputRow.style.gap = '.4rem';
const customKeyInput = document.createElement('input');
customKeyInput.type = 'text';
customKeyInput.placeholder = 'Custom attribute name';
customKeyInput.className = 'ov-add-line-key';
customKeyInput.dataset.lineId = line.id;
const customValInput = document.createElement('input');
customValInput.type = 'text';
customValInput.placeholder = 'Value';
customValInput.className = 'ov-add-line-val';
customValInput.dataset.lineId = line.id;
const addCustomBtn = document.createElement('button');
addCustomBtn.textContent = '+ Add';
addCustomBtn.className = 'attr-add';
addCustomBtn.dataset.scope = 'line';
addCustomBtn.dataset.scopeId = line.id;
// Default values
const defaultValues = {
randomizeOnPlace: 'false', shuffleFrames: 'false', randomPick: '[]',
jitterX: '[0,0]', jitterY: '[0,0]', jitterRot: '[0,0]', jitterScale: '[1,1]',
randomFlip: 'none', paletteRandom: '[]', yoyoChance: '0', seed: '12345',
gravityY: '0', gravityX: '0', dragX: '0', dragY: '0',
maxVelocityX: '1000', maxVelocityY: '1000', bounce: '0', friction: '0',
immovable: 'false', angularVelocity: '0', rotationLock: 'false',
magnet: '{"strength":0,"radius":0}', float: 'false',
damage: '0', health: '1', speed: '100', jumpStrength: '300',
trigger: 'onTouch', script: '', patrol: '{"range":100,"speed":50}',
respawn: '{"time":3}', collectible: '{"value":1}',
name: '', author: '', notes: '', group: '', layer: ''
};
presetSelect.onchange = () => {
if (presetSelect.value) {
customKeyInput.value = presetSelect.value;
customValInput.value = defaultValues[presetSelect.value] || '';
customValInput.focus();
}
};
inputRow.appendChild(customKeyInput);
inputRow.appendChild(customValInput);
inputRow.appendChild(addCustomBtn);
customAttrDiv.appendChild(presetSelect);
customAttrDiv.appendChild(inputRow);
optionalContent.appendChild(customAttrDiv);
// Tiles content
const tilesContent = document.createElement('div');
// Add tile inspector section
const tileInspector = document.createElement('div');
tileInspector.style.marginBottom = '1rem';
tileInspector.style.padding = '.5rem';
tileInspector.style.background = '#1a1a1a';
tileInspector.style.borderRadius = '.4rem';
tileInspector.style.border = '1px solid #333';
const inspectorTitle = document.createElement('div');
inspectorTitle.style.color = '#4fc3f7';
inspectorTitle.style.fontWeight = 'bold';
inspectorTitle.style.marginBottom = '.5rem';
inspectorTitle.textContent = 'Tile Inspector';
const inspectorContent = document.createElement('div');
inspectorContent.id = `tile-inspector-${line.id}`;
inspectorContent.style.color = '#bbb';
inspectorContent.style.fontSize = '.9rem';
inspectorContent.textContent = 'Click on a tile below to see detailed information';
tileInspector.appendChild(inspectorTitle);
tileInspector.appendChild(inspectorContent);
tilesContent.appendChild(tileInspector);
const strip = document.createElement('div');
strip.className = 'ov-strip';
for (const t of (line.items || [])) {
const item = document.createElement('div');
item.className = 'ov-item';
item.title = `Click to inspect: ID ${t.id}, Index: ${t.index || 'N/A'}`;
item.style.cursor = 'pointer';
// Add click handler for tile inspection
item.onclick = () => {
const inspector = document.getElementById(`tile-inspector-${line.id}`);
if (inspector) {
inspector.innerHTML = `
<div style="display: grid; grid-template-columns: 120px 1fr; gap: .3rem; font-size: .85rem;">
<div style="color: #4fc3f7; font-weight: bold;">Tile ID:</div>
<div style="color: #fff;">${t.id}</div>
<div style="color: #4fc3f7; font-weight: bold;">Grid Index:</div>
<div style="color: #fff;">${t.index !== undefined ? t.index : 'N/A'}</div>
<div style="color: #4fc3f7; font-weight: bold;">Row:</div>
<div style="color: #fff;">${t.row}</div>
<div style="color: #4fc3f7; font-weight: bold;">Column:</div>
<div style="color: #fff;">${t.col}</div>
<div style="color: #4fc3f7; font-weight: bold;">Position:</div>
<div style="color: #fff;">(${t.x}, ${t.y})</div>
<div style="color: #4fc3f7; font-weight: bold;">Size:</div>
<div style="color: #fff;">${t.w} × ${t.h}</div>
<div style="color: #4fc3f7; font-weight: bold;">Center:</div>
<div style="color: #fff;">(${t.centerX || 'N/A'}, ${t.centerY || 'N/A'})</div>
<div style="color: #4fc3f7; font-weight: bold;">Frame Key:</div>
<div style="color: #fff;">${t.frameKey || 'N/A'}</div>
<div style="color: #4fc3f7; font-weight: bold;">Atlas Key:</div>
<div style="color: #fff;">${t.atlasKey || 'N/A'}</div>
<div style="color: #4fc3f7; font-weight: bold;">Type:</div>
<div style="color: #fff;">${t.type || 'static'}</div>
</div>
`;
}
};
const img = document.createElement('img');
img.src = t.thumbDataURL;
const badge = document.createElement('div');
badge.className = 'ov-badge';
badge.textContent = `#${t.id}`;
badge.style.background = '#4fc3f7';
badge.style.color = '#000';
badge.style.fontWeight = 'bold';
item.appendChild(img);
item.appendChild(badge);
strip.appendChild(item);
}
tilesContent.appendChild(strip);
// Create collapsible sections (all start collapsed)
const autoSection = createCollapsibleSection('Automatic (Read-only)', '⚙️', autoContent);
const changeSection = createCollapsibleSection('Changeable', '🟡', changeContent);
const optionalSection = createCollapsibleSection('Optional / Creative', '🔵', optionalContent);
const tilesSection = createCollapsibleSection('Tiles', '🎨', tilesContent);
block.appendChild(h);
block.appendChild(autoSection);
block.appendChild(changeSection);
block.appendChild(optionalSection);
block.appendChild(tilesSection);
return block;
}
/* ---------- Overlay Rendering ---------- */
function renderObjectOverlay() {
const o = window.WorkspaceAPI.activeObject();
if(!o) return;
ovTitle.textContent = o.name;
ovBody.innerHTML = '';
ovBody.appendChild(buildObjectAttributesBlock(o));
for (const line of o.lines) {
ovBody.appendChild(buildLineBlock(line));
}
}
/* ---------- Overlay Controls ---------- */
function openObjectView() {
renderObjectOverlay();
objectOverlay.classList.add('open');
objectOverlay.setAttribute('aria-hidden', 'false');
}
function closeObjectView() {
objectOverlay.classList.remove('open');
objectOverlay.setAttribute('aria-hidden', 'true');
}
/* ---------- Attribute Event Handlers ---------- */
function initAttributeEvents() {
// Add/remove attribute handlers
ovBody.addEventListener('click', (e) => {
const btn = e.target.closest('.attr-add');
const del = e.target.closest('.attr-del');
if (btn) {
const scope = btn.dataset.scope;
const scopeId = btn.dataset.scopeId;
if (scope === 'object') {
const keyEl = document.getElementById('ov-add-obj-key');
const valEl = document.getElementById('ov-add-obj-val');
const key = (keyEl.value || '').trim();
const value = (valEl.value || '').trim();
if (!key) {
keyEl.focus();
return;
}
const o = window.WorkspaceAPI.activeObject();
if(!o) return;
o.attributes = o.attributes || [];
o.attributes.push({key, value});
keyEl.value = '';
valEl.value = '';
renderObjectOverlay();
return;
}
}
if (del) {
const scope = del.dataset.scope;
const scopeId = del.dataset.scopeId;
const idx = parseInt(del.dataset.index, 10);
const o = window.WorkspaceAPI.activeObject();
if(!o) return;
if (scope === 'object') {
if (o.attributes && o.attributes[idx]) {
o.attributes.splice(idx, 1);
}
renderObjectOverlay();
return;
}
}
});
// Live editing of attributes
ovBody.addEventListener('input', (e) => {
const keyEl = e.target.closest('.attr-key');
const valEl = e.target.closest('.attr-val');
const o = window.WorkspaceAPI.activeObject();
if(!o) return;
function apply(el, field) {
const scope = el.dataset.scope;
const scopeId = el.dataset.scopeId;
const idx = parseInt(el.dataset.index, 10);
if (scope === 'object') {
if (!o.attributes || !o.attributes[idx]) return;
o.attributes[idx][field] = el.value;
}
}
if (keyEl) apply(keyEl, 'key');
if (valEl) apply(valEl, 'value');
});
}
/* ---------- Initialization ---------- */
function initializeAttributes() {
try {
// Get DOM elements
attributesObjectViewBtn = document.getElementById('objectViewBtn');
objectOverlay = document.getElementById('objectOverlay');
ovTitle = document.getElementById('ovTitle');
ovBody = document.getElementById('ovBody');
closeOverlayBtn = document.getElementById('closeOverlayBtn');
// Check required DOM elements and dependencies
if (!attributesObjectViewBtn || !objectOverlay || !ovBody) {
throw new Error('Required DOM elements not found');
}
if (!window.WorkspaceAPI || typeof window.WorkspaceAPI.activeObject !== 'function') {
throw new Error('WorkspaceAPI not available');
}
// Set up event handlers
attributesObjectViewBtn.addEventListener('click', openObjectView);
closeOverlayBtn.addEventListener('click', closeObjectView);
objectOverlay.addEventListener('click', (e) => {
if(e.target === objectOverlay) closeObjectView();
});
initAttributeEvents();
} catch (error) {
alert(`Attributes module failed to initialize: ${error.message}`);
throw error;
}
}
// Export API for other modules
window.AttributesAPI = {
renderObjectOverlay,
openObjectView,
closeObjectView
};