furniture.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. // Furniture placement functionality
  2. let FURNITURE_DATA = [];
  3. let FURNITURE_ITEMS = [];
  4. let selectedFurnitureType = null;
  5. let furnitureCursor = null;
  6. let draggingFurnitureItem = null;
  7. let draggingFurnitureOffset = { dx: 0, dy: 0 };
  8. let furnitureDragSaveTimeout = null;
  9. // Enter furniture placement mode
  10. async function enterFurnitureMode() {
  11. mode = 'furniture_mode';
  12. $('.leftBox').hide();
  13. $('#furniturePanel').show();
  14. $('#boxinfo').html('Furniture placement mode - select furniture to place');
  15. // Load furniture data from JSON if not already loaded
  16. if (FURNITURE_DATA.length === 0) {
  17. await loadFurnitureData();
  18. }
  19. // Populate furniture options (data is now embedded)
  20. populateFurnitureOptions();
  21. }
  22. // Exit furniture placement mode
  23. function exitFurnitureMode() {
  24. mode = 'select_mode';
  25. selectedFurnitureType = null;
  26. if (furnitureCursor) {
  27. furnitureCursor.attr('style', 'display: none');
  28. }
  29. // Reset furniture option buttons
  30. document.querySelectorAll('.furniture-option').forEach(btn => {
  31. btn.classList.remove('btn-success');
  32. btn.classList.add('btn-light');
  33. });
  34. // Hide furniture panel and show main panel
  35. $('#furniturePanel').hide();
  36. $('.leftBox').hide();
  37. $('#panel').show();
  38. $('#boxinfo').html('Selection mode');
  39. }
  40. // Load furniture data from JSON file
  41. async function loadFurnitureData() {
  42. try {
  43. const response = await fetch('furniture.json');
  44. const data = await response.json();
  45. FURNITURE_DATA = data.furniture;
  46. console.log('Loaded furniture data from furniture.json:', FURNITURE_DATA.length, 'items');
  47. populateFurnitureOptions();
  48. } catch (error) {
  49. console.error('Error loading furniture data:', error);
  50. // Fallback to empty array if file can't be loaded
  51. FURNITURE_DATA = [];
  52. console.log('Using empty fallback furniture data');
  53. populateFurnitureOptions();
  54. }
  55. }
  56. // Populate furniture options in the panel
  57. function populateFurnitureOptions() {
  58. const optionsContainer = document.getElementById('furniture_options');
  59. if (!optionsContainer) {
  60. console.error('furniture_options container not found');
  61. return;
  62. }
  63. optionsContainer.innerHTML = '';
  64. console.log('Populating furniture options:', FURNITURE_DATA);
  65. FURNITURE_DATA.forEach(furniture => {
  66. const button = document.createElement('button');
  67. button.className = 'btn btn-light fully furniture-option';
  68. button.style.marginBottom = '5px';
  69. button.style.width = '100%';
  70. button.textContent = furniture.name;
  71. button.onclick = () => selectFurnitureType(furniture);
  72. optionsContainer.appendChild(button);
  73. });
  74. if (FURNITURE_DATA.length === 0) {
  75. optionsContainer.innerHTML = '<p>No furniture data loaded</p>';
  76. }
  77. }
  78. // Select furniture type for placement
  79. function selectFurnitureType(furniture) {
  80. selectedFurnitureType = furniture;
  81. mode = 'furniture_placement_mode';
  82. $('#boxinfo').html('Click to place ' + furniture.name);
  83. // Highlight selected furniture
  84. document.querySelectorAll('.furniture-option').forEach(btn => {
  85. btn.classList.remove('btn-success');
  86. btn.classList.add('btn-light');
  87. });
  88. event.target.classList.remove('btn-light');
  89. event.target.classList.add('btn-success');
  90. createFurnitureCursor();
  91. }
  92. // Create cursor icon for furniture placement
  93. function createFurnitureCursor() {
  94. if (furnitureCursor) {
  95. furnitureCursor.remove();
  96. }
  97. // Create square with arrow cursor icon
  98. furnitureCursor = qSVG.create('boxbind', 'g', {
  99. id: 'furniture-cursor'
  100. });
  101. const square = qSVG.create('none', 'circle', {
  102. cx: 0,
  103. cy: 0,
  104. r: 15,
  105. fill: 'rgba(76, 175, 80, 0.7)',
  106. stroke: '#4CAF50',
  107. 'stroke-width': 2
  108. });
  109. const arrow = qSVG.create('none', 'path', {
  110. d: 'M23,0 L17,-5 L17,5 Z',
  111. fill: '#2E7D32',
  112. stroke: '#2E7D32',
  113. 'stroke-width': 1,
  114. 'stroke-linejoin': 'round',
  115. 'stroke-linecap': 'round'
  116. });
  117. furnitureCursor.append(square);
  118. furnitureCursor.append(arrow);
  119. furnitureCursor.attr('style', 'display: none');
  120. }
  121. // Update cursor position
  122. function updateFurnitureCursor(x, y) {
  123. if (furnitureCursor && mode === 'furniture_placement_mode') {
  124. furnitureCursor.attr({
  125. transform: `translate(${x}, ${y})`,
  126. style: 'display: block'
  127. });
  128. }
  129. }
  130. // Place furniture item on the floorplan
  131. function placeFurnitureItem(x, y, skipSave = false) {
  132. if (!selectedFurnitureType) return;
  133. const furnitureItem = {
  134. id: generateFurnitureId(),
  135. type: selectedFurnitureType.type,
  136. name: selectedFurnitureType.name,
  137. furnitureId: selectedFurnitureType.id,
  138. category: selectedFurnitureType.category || '',
  139. x: x,
  140. y: y,
  141. rotation: 0,
  142. graph: null,
  143. rotGroup: null,
  144. label: null
  145. };
  146. // Create visual representation (outer group translates, inner group rotates)
  147. const furnitureGroup = qSVG.create('boxFurniture', 'g', {
  148. id: 'furniture-' + furnitureItem.id,
  149. class: 'furniture-item',
  150. 'data-furniture-id': furnitureItem.id
  151. });
  152. const furnitureRotGroup = qSVG.create('none', 'g', { class: 'furniture-rot' });
  153. // Create furniture icon (circle with arrow)
  154. const icon = qSVG.create('none', 'circle', {
  155. cx: 0,
  156. cy: 0,
  157. r: 20,
  158. fill: '#8BC34A',
  159. stroke: '#689F38',
  160. 'stroke-width': 2
  161. });
  162. const arrow = qSVG.create('none', 'path', {
  163. d: 'M30,0 L22,-6 L22,6 Z',
  164. fill: '#33691E',
  165. stroke: '#33691E',
  166. 'stroke-width': 1,
  167. 'stroke-linejoin': 'round',
  168. 'stroke-linecap': 'round'
  169. });
  170. // Create text label
  171. const label = qSVG.create('none', 'text', {
  172. x: 0,
  173. y: 39,
  174. 'text-anchor': 'middle',
  175. 'font-family': 'roboto',
  176. 'font-size': '10px',
  177. fill: '#777',
  178. 'font-weight': 'normal'
  179. });
  180. label.text((furnitureItem.name || '').toLowerCase());
  181. furnitureRotGroup.append(icon);
  182. furnitureRotGroup.append(arrow);
  183. furnitureGroup.append(furnitureRotGroup);
  184. furnitureGroup.append(label);
  185. // Assign references BEFORE rendering icon so it can attach to the outer group
  186. furnitureItem.graph = furnitureGroup;
  187. furnitureItem.rotGroup = furnitureRotGroup;
  188. // Keep a reference to the label for future updates (e.g., on restore)
  189. furnitureItem.label = label;
  190. // Ensure category is synced from latest FURNITURE_DATA before rendering icon
  191. try {
  192. const latest = Array.isArray(FURNITURE_DATA) ? FURNITURE_DATA.find(f => f.id === furnitureItem.furnitureId) : null;
  193. if (latest && typeof latest.category !== 'undefined') {
  194. furnitureItem.category = latest.category;
  195. }
  196. } catch (e) {}
  197. // Render category icon inside the circle
  198. renderFurnitureIcon(furnitureItem);
  199. furnitureGroup.attr({
  200. transform: `translate(${x}, ${y})`
  201. });
  202. furnitureRotGroup.attr({
  203. transform: `rotate(${furnitureItem.rotation})`
  204. });
  205. FURNITURE_ITEMS.push(furnitureItem);
  206. // Add click handler for selection (only in furniture modes)
  207. furnitureGroup.on('click', function(e) {
  208. e.stopPropagation();
  209. if (mode === 'furniture_mode' || mode === 'furniture_placement_mode') {
  210. selectFurnitureItem(furnitureItem);
  211. }
  212. });
  213. // Add mousedown handler to start drag only if this item is selected
  214. furnitureGroup.on('mousedown', function(e) {
  215. // Do not allow dragging during placement mode
  216. if (mode === 'furniture_placement_mode') return;
  217. // Only start dragging if this item is currently selected
  218. if (window.selectedFurnitureItem !== furnitureItem) return;
  219. e.preventDefault();
  220. e.stopPropagation();
  221. // Use existing snap helper to get SVG coords if available
  222. try {
  223. const snap = calcul_snap(e, 'off');
  224. draggingFurnitureOffset.dx = furnitureItem.x - snap.x;
  225. draggingFurnitureOffset.dy = furnitureItem.y - snap.y;
  226. } catch (err) {
  227. // Fallback: no offset, will jump under cursor
  228. draggingFurnitureOffset.dx = 0;
  229. draggingFurnitureOffset.dy = 0;
  230. }
  231. draggingFurnitureItem = furnitureItem;
  232. window.draggingFurnitureItem = furnitureItem;
  233. // Temporarily switch to furniture_mode to prevent select-mode panning
  234. window._prevModeBeforeFurnitureDrag = mode;
  235. mode = 'furniture_mode';
  236. // Ensure engine's panning is disabled even if it set drag='on' in capture phase
  237. try { window.drag = 'off'; } catch (_) {}
  238. $('#boxinfo').html('Dragging ' + furnitureItem.name + ' (release to drop)');
  239. cursor('move');
  240. });
  241. // Only update UI state and mode when this is a user placement, not restoration
  242. if (!skipSave) {
  243. $('#boxinfo').html('Placed ' + furnitureItem.name);
  244. // Clear selection and reset to furniture mode
  245. selectedFurnitureType = null;
  246. mode = 'furniture_mode';
  247. // Hide furniture cursor
  248. if (furnitureCursor) {
  249. furnitureCursor.attr('style', 'display: none');
  250. }
  251. // Reset furniture option buttons
  252. document.querySelectorAll('.furniture-option').forEach(btn => {
  253. btn.classList.remove('btn-success');
  254. btn.classList.add('btn-light');
  255. });
  256. // Ensure furniture panel stays visible
  257. $('#furniturePanel').show();
  258. save();
  259. }
  260. }
  261. // Generate unique furniture ID
  262. function generateFurnitureId() {
  263. return 'furn_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
  264. }
  265. // Select furniture item for editing
  266. function selectFurnitureItem(furnitureItem) {
  267. // Stay in furniture_mode while a furniture item is selected to avoid wall interactions
  268. mode = 'furniture_mode';
  269. selectedFurnitureType = null;
  270. // Hide all panels first
  271. $('.leftBox').hide();
  272. // Show furniture details panel
  273. $('#furnitureName').text(furnitureItem.name);
  274. $('#furnitureId').text(furnitureItem.furnitureId);
  275. // Always use the latest category from FURNITURE_DATA in case furniture.json changed
  276. try {
  277. const latest = Array.isArray(FURNITURE_DATA) ? FURNITURE_DATA.find(f => f.id === furnitureItem.furnitureId) : null;
  278. let latestCategory = furnitureItem.category || '';
  279. if (latest && Object.prototype.hasOwnProperty.call(latest, 'category')) {
  280. latestCategory = latest.category; // allow empty string
  281. }
  282. furnitureItem.category = latestCategory; // keep the item in sync
  283. $('#furnitureCategory').text(latestCategory);
  284. // Refresh icon according to updated category
  285. renderFurnitureIcon(furnitureItem);
  286. } catch (e) {}
  287. $('#furnitureRotationValue').text(furnitureItem.rotation || 0);
  288. $('#furnitureRotationSlider').val(furnitureItem.rotation || 0);
  289. $('#furnitureTools').show();
  290. // Store reference for removal
  291. window.selectedFurnitureItem = furnitureItem;
  292. // Highlight selected furniture
  293. document.querySelectorAll('.furniture-item').forEach(item => {
  294. item.style.opacity = '0.5';
  295. });
  296. furnitureItem.graph.get(0).style.opacity = '1';
  297. furnitureItem.graph.get(0).style.filter = 'drop-shadow(0 0 5px #FF5722)';
  298. }
  299. // Remove furniture item
  300. function removeFurnitureItem(furnitureItem) {
  301. if (furnitureItem && furnitureItem.graph) {
  302. furnitureItem.graph.remove();
  303. const index = FURNITURE_ITEMS.indexOf(furnitureItem);
  304. if (index > -1) {
  305. FURNITURE_ITEMS.splice(index, 1);
  306. }
  307. save();
  308. }
  309. }
  310. // Close furniture placement mode
  311. function closeFurnitureMode() {
  312. exitFurnitureMode();
  313. }
  314. // Hide furniture tools panel
  315. function hideFurnitureTools() {
  316. $('#furnitureTools').hide();
  317. window.selectedFurnitureItem = null;
  318. // Reset furniture highlighting
  319. document.querySelectorAll('.furniture-item').forEach(item => {
  320. item.style.opacity = '1';
  321. item.style.filter = 'none';
  322. });
  323. // Show furniture panel to return to furniture mode
  324. $('.leftBox').hide();
  325. $('#furniturePanel').show();
  326. mode = 'furniture_mode';
  327. $('#boxinfo').html('Furniture placement mode - select furniture to place');
  328. }
  329. // Toggle furniture layer visibility
  330. function toggleFurnitureLayer(visible) {
  331. const furnitureLayer = document.getElementById('boxFurniture');
  332. if (furnitureLayer) {
  333. furnitureLayer.style.display = visible ? 'block' : 'none';
  334. }
  335. }
  336. // Initialize furniture system
  337. async function initFurnitureSystem() {
  338. // No need to load data - it's now embedded
  339. // Add furniture removal handler
  340. document.getElementById('furnitureRemove').addEventListener('click', function() {
  341. if (window.selectedFurnitureItem) {
  342. removeFurnitureItem(window.selectedFurnitureItem);
  343. hideFurnitureTools();
  344. $('#boxinfo').html('Furniture removed');
  345. }
  346. });
  347. // Add rotation slider handler with throttling for smooth updates
  348. let rotationTimeout;
  349. document.getElementById('furnitureRotationSlider').addEventListener('input', function() {
  350. if (window.selectedFurnitureItem) {
  351. const rotation = parseInt(this.value);
  352. window.selectedFurnitureItem.rotation = rotation;
  353. $('#furnitureRotationValue').text(rotation);
  354. // Apply transform split: translate on outer group, rotate on inner group
  355. if (window.selectedFurnitureItem.graph) {
  356. window.selectedFurnitureItem.graph.attr({
  357. transform: `translate(${window.selectedFurnitureItem.x}, ${window.selectedFurnitureItem.y})`
  358. });
  359. }
  360. if (window.selectedFurnitureItem.rotGroup) {
  361. window.selectedFurnitureItem.rotGroup.attr({
  362. transform: `rotate(${rotation})`
  363. });
  364. }
  365. // Throttle save operations to avoid excessive calls
  366. clearTimeout(rotationTimeout);
  367. rotationTimeout = setTimeout(() => {
  368. save();
  369. }, 100);
  370. }
  371. });
  372. // Add layer toggle handler
  373. document.getElementById('showLayerFurniture').addEventListener('change', function() {
  374. toggleFurnitureLayer(this.checked);
  375. });
  376. // Global mousemove/up handlers for dragging furniture
  377. document.addEventListener('mousemove', function(e) {
  378. if (!draggingFurnitureItem) return;
  379. // Do not drag during placement mode, but allow in other modes (e.g., select_mode)
  380. if (mode === 'furniture_placement_mode') return;
  381. let snap;
  382. try {
  383. snap = calcul_snap(e, 'off');
  384. } catch (err) {
  385. // If calcul_snap is unavailable, approximate using client coords relative to SVG viewBox
  386. const svg = document.getElementById('lin');
  387. if (!svg) return;
  388. const pt = svg.createSVGPoint();
  389. pt.x = e.clientX;
  390. pt.y = e.clientY;
  391. const ctm = svg.getScreenCTM();
  392. if (!ctm) return;
  393. const inv = ctm.inverse();
  394. const p = pt.matrixTransform(inv);
  395. snap = { x: p.x, y: p.y };
  396. }
  397. const nx = snap.x + draggingFurnitureOffset.dx;
  398. const ny = snap.y + draggingFurnitureOffset.dy;
  399. draggingFurnitureItem.x = nx;
  400. draggingFurnitureItem.y = ny;
  401. if (draggingFurnitureItem.graph) {
  402. draggingFurnitureItem.graph.attr({
  403. transform: `translate(${nx}, ${ny})`
  404. });
  405. }
  406. if (draggingFurnitureItem.rotGroup) {
  407. draggingFurnitureItem.rotGroup.attr({
  408. transform: `rotate(${draggingFurnitureItem.rotation || 0})`
  409. });
  410. }
  411. // Throttle saves while dragging
  412. clearTimeout(furnitureDragSaveTimeout);
  413. furnitureDragSaveTimeout = setTimeout(() => { try { save(); } catch (e) {} }, 150);
  414. }, true);
  415. document.addEventListener('mouseup', function() {
  416. if (!draggingFurnitureItem) return;
  417. const justDropped = draggingFurnitureItem;
  418. draggingFurnitureItem = null;
  419. window.draggingFurnitureItem = null;
  420. // Restore previous mode after drag ends
  421. if (window._prevModeBeforeFurnitureDrag) {
  422. mode = window._prevModeBeforeFurnitureDrag;
  423. window._prevModeBeforeFurnitureDrag = null;
  424. }
  425. cursor('default');
  426. try { save(); } catch (e) {}
  427. if (justDropped) {
  428. $('#boxinfo').html('Placed ' + justDropped.name);
  429. }
  430. }, true);
  431. }
  432. // Export furniture data for save/load functionality
  433. function getFurnitureData() {
  434. const data = FURNITURE_ITEMS.map(item => ({
  435. id: item.id,
  436. type: item.type,
  437. name: item.name,
  438. furnitureId: item.furnitureId,
  439. category: item.category || '',
  440. x: item.x,
  441. y: item.y,
  442. rotation: item.rotation
  443. }));
  444. console.log('Saving furniture data:', data);
  445. return data;
  446. }
  447. // Load saved furniture data (for undo/redo operations)
  448. async function loadSavedFurnitureData(furnitureData) {
  449. console.log('Loading furniture data:', furnitureData);
  450. // Ensure FURNITURE_DATA is loaded before restoring furniture
  451. if (FURNITURE_DATA.length === 0) {
  452. await loadFurnitureData();
  453. }
  454. // Clear existing furniture
  455. FURNITURE_ITEMS.forEach(item => {
  456. if (item.graph) item.graph.remove();
  457. });
  458. FURNITURE_ITEMS = [];
  459. // Recreate furniture items
  460. if (Array.isArray(furnitureData)) {
  461. furnitureData.forEach(data => {
  462. console.log('Restoring furniture item:', data);
  463. // Find matching furniture type from FURNITURE_DATA
  464. const furnitureType = FURNITURE_DATA.find(f => f.id === data.furnitureId || f.type === data.type);
  465. if (furnitureType) {
  466. // Temporarily set selectedFurnitureType for placeFurnitureItem
  467. const originalSelected = selectedFurnitureType;
  468. selectedFurnitureType = furnitureType;
  469. placeFurnitureItem(data.x, data.y, true); // Skip save during restoration
  470. const item = FURNITURE_ITEMS[FURNITURE_ITEMS.length - 1];
  471. if (item) {
  472. item.id = data.id;
  473. item.type = data.type;
  474. item.name = data.name;
  475. item.furnitureId = data.furnitureId;
  476. // Prefer the latest category from FURNITURE_DATA over saved data, respecting empty string
  477. try {
  478. const latest = Array.isArray(FURNITURE_DATA) ? FURNITURE_DATA.find(f => f.id === data.furnitureId) : null;
  479. if (latest && Object.prototype.hasOwnProperty.call(latest, 'category')) {
  480. item.category = latest.category; // may be ''
  481. } else if (Object.prototype.hasOwnProperty.call(data, 'category')) {
  482. item.category = data.category; // may be ''
  483. } else if (furnitureType && Object.prototype.hasOwnProperty.call(furnitureType, 'category')) {
  484. item.category = furnitureType.category;
  485. } else {
  486. item.category = '';
  487. }
  488. } catch (_) {
  489. if (Object.prototype.hasOwnProperty.call(data, 'category')) {
  490. item.category = data.category;
  491. } else if (furnitureType && Object.prototype.hasOwnProperty.call(furnitureType, 'category')) {
  492. item.category = furnitureType.category;
  493. } else {
  494. item.category = '';
  495. }
  496. }
  497. item.rotation = data.rotation || 0;
  498. console.log('Setting rotation to:', item.rotation);
  499. // Update visual representation: translate outer group, rotate inner group
  500. if (item.graph) {
  501. item.graph.attr({
  502. transform: `translate(${item.x}, ${item.y})`
  503. });
  504. }
  505. if (item.rotGroup) {
  506. item.rotGroup.attr({
  507. transform: `rotate(${item.rotation})`
  508. });
  509. } else {
  510. // Fallback: find rot group if restoring from older session
  511. const g = item.graph && item.graph.get ? item.graph.get(0) : null;
  512. if (g) {
  513. const rg = g.querySelector('g.furniture-rot');
  514. if (rg) {
  515. $(rg).attr('transform', `rotate(${item.rotation})`);
  516. item.rotGroup = $(rg);
  517. }
  518. }
  519. }
  520. // Update label text to match restored name
  521. try {
  522. if (item.label) {
  523. item.label.text((item.name || '').toLowerCase());
  524. } else {
  525. // Fallback: find first text element inside group
  526. const g = item.graph && item.graph.get ? item.graph.get(0) : null;
  527. if (g) {
  528. const t = g.querySelector('text');
  529. if (t) t.textContent = (item.name || '').toLowerCase();
  530. }
  531. }
  532. } catch (e) {}
  533. // Ensure category icon is rendered/updated after restore
  534. renderFurnitureIcon(item);
  535. // If this restored item is currently selected, refresh the panel category
  536. try {
  537. if (window.selectedFurnitureItem === item) {
  538. $('#furnitureCategory').text(item.category || '');
  539. }
  540. } catch (e) {}
  541. } else {
  542. console.warn('Failed to create furniture item:', data);
  543. }
  544. // Restore original selectedFurnitureType
  545. selectedFurnitureType = originalSelected;
  546. } else {
  547. console.warn('Furniture type not found for:', data);
  548. }
  549. });
  550. }
  551. // Save the restored furniture state to history
  552. // This ensures the furniture data is properly saved after restoration
  553. if (furnitureData && furnitureData.length > 0) {
  554. save();
  555. }
  556. }
  557. // Initialize when DOM is loaded
  558. document.addEventListener('DOMContentLoaded', function() {
  559. initFurnitureSystem();
  560. });
  561. // Map category to Font Awesome icon class
  562. function getCategoryIconClass(category) {
  563. const cat = (category || '').toLowerCase();
  564. switch (cat) {
  565. case 'bed': return 'fa-solid fa-bed';
  566. case 'chair': return 'fa-solid fa-chair';
  567. case 'sofa': return 'fa-solid fa-couch';
  568. case 'light': return 'fa-solid fa-lightbulb';
  569. case 'utility': return 'fa-solid fa-toilet-portable';
  570. case 'table': return 'fa-solid fa-table-cells-large';
  571. default: return 'fa-solid fa-star'; // "empty" or unknown
  572. }
  573. }
  574. // Create or update the icon inside the furniture item (non-rotating)
  575. function renderFurnitureIcon(furnitureItem) {
  576. if (!furnitureItem || !furnitureItem.graph) return;
  577. const outer = furnitureItem.graph.get ? furnitureItem.graph.get(0) : null;
  578. if (!outer) return;
  579. const ICON_SIZE = 28; // was 24, make icon a bit bigger
  580. const HALF = ICON_SIZE / 2;
  581. // Remove any icon previously attached to the rotating group to prevent rotation
  582. try {
  583. const oldInRot = furnitureItem.rotGroup && furnitureItem.rotGroup.get ? furnitureItem.rotGroup.get(0).querySelector('foreignObject.furniture-icon') : null;
  584. if (oldInRot) oldInRot.remove();
  585. } catch (_) {}
  586. // Try to find an existing foreignObject with our marker class on the outer group
  587. let fo = outer.querySelector('foreignObject.furniture-icon');
  588. if (!fo) {
  589. // Create a centered 24x24 foreignObject inside the circle (radius 20)
  590. fo = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject');
  591. fo.setAttribute('class', 'furniture-icon');
  592. fo.setAttribute('x', -HALF);
  593. fo.setAttribute('y', -HALF);
  594. fo.setAttribute('width', ICON_SIZE);
  595. fo.setAttribute('height', ICON_SIZE);
  596. // Create XHTML wrapper and icon element
  597. const div = document.createElement('div');
  598. div.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
  599. div.style.width = ICON_SIZE + 'px';
  600. div.style.height = ICON_SIZE + 'px';
  601. div.style.display = 'flex';
  602. div.style.alignItems = 'center';
  603. div.style.justifyContent = 'center';
  604. div.style.pointerEvents = 'none';
  605. const iEl = document.createElement('i');
  606. iEl.setAttribute('aria-hidden', 'true');
  607. div.appendChild(iEl);
  608. fo.appendChild(div);
  609. outer.appendChild(fo);
  610. }
  611. // Always ensure position/size reflects current constants (in case we change size later)
  612. fo.setAttribute('x', -HALF);
  613. fo.setAttribute('y', -HALF);
  614. fo.setAttribute('width', ICON_SIZE);
  615. fo.setAttribute('height', ICON_SIZE);
  616. const divWrap = fo.firstChild;
  617. if (divWrap && divWrap.style) {
  618. divWrap.style.width = ICON_SIZE + 'px';
  619. divWrap.style.height = ICON_SIZE + 'px';
  620. }
  621. const iEl = fo.querySelector('i');
  622. if (!iEl) return;
  623. iEl.className = getCategoryIconClass(furnitureItem.category);
  624. iEl.style.fontSize = '18px';
  625. iEl.style.color = '#ffffff';
  626. }