diff --git a/src/components/Modal/DomainModal.ts b/src/components/Modal/DomainModal.ts
index ff51bdb..7e8fdf8 100644
--- a/src/components/Modal/DomainModal.ts
+++ b/src/components/Modal/DomainModal.ts
@@ -202,7 +202,57 @@ class DomainAssetModal extends BaseModal {
if (logs.length === 0) {
container.innerHTML = '
이력이 없습니다.
';
} else {
- container.innerHTML = logs.map(l => ` = {};
+ logs.forEach(l => {
+ const date = l.log_date || '날짜 미지정';
+ if (!grouped[date]) grouped[date] = [];
+ grouped[date].push(l);
+ });
+
+ container.innerHTML = Object.entries(grouped).map(([date, dateLogs]) => {
+ const entriesHtml = dateLogs.map((l, idx) => {
+ const isLast = idx === dateLogs.length - 1;
+ const borderStyle = isLast ? '' : 'border-bottom: 1px dashed var(--hairline); padding-bottom: 8px; margin-bottom: 8px;';
+
+ let displayDetails = l.details;
+ if (l.details && l.details.trim().startsWith('{')) {
+ try {
+ const data = JSON.parse(l.details);
+ if (data.type === 'checkout') {
+ displayDetails = `[불출] ${data.user || ''} (${data.dept || ''}) ${data.memo ? `| 메모: ${data.memo}` : ''}`;
+ } else if (data.type === 'return') {
+ displayDetails = `[반납] ${data.user || ''} (${data.dept || ''}) ${data.memo ? `| 메모: ${data.memo}` : ''}`;
+ } else if (data.type === 'move') {
+ displayDetails = `[이동] ${data.user || ''} (${data.dept || ''}) ➔ ${data.targetUser || ''} (${data.targetDept || ''}) ${data.memo ? `| 메모: ${data.memo}` : ''}`;
+ }
+ } catch (e) {}
+ }
+
+ return `
+
+
+
+ ${l.log_user || '시스템'}
+
+
${displayDetails}
+
+ `;
+ }).join('');
+
+ const isInitialReg = date === createdDate;
+ const regBadge = isInitialReg ? `최초등록` : '';
+
+ return `
+
+
${date} ${regBadge}
+
+ ${entriesHtml}
+
+
+ `;
+ }).join('');
}
}
}
diff --git a/src/components/Modal/HWModal.ts b/src/components/Modal/HWModal.ts
index ec0b4e6..5b78f7f 100644
--- a/src/components/Modal/HWModal.ts
+++ b/src/components/Modal/HWModal.ts
@@ -98,6 +98,9 @@ class HwAssetModal extends BaseModal {
사용자 및 조직 정보
+
+ 💡 PC 자산은 데이터 정합성을 위해 '사용자 및 조직 정보'만 수정이 제한되며, 사양 및 기타 정보는 수정창에서 수정할 수 있습니다.
+
@@ -138,6 +141,10 @@ class HwAssetModal extends BaseModal {
+
+
+
+
@@ -309,6 +316,12 @@ class HwAssetModal extends BaseModal {
typeSelect.addEventListener('change', () => {
this.applyRoleVisibility();
this.updateHeaderIdentity(this.currentAsset);
+
+ if (typeSelect.value === '공용PC') {
+ setFieldValue('hw-user_current', '');
+ setFieldValue('hw-emp_no', '');
+ setFieldValue('hw-user_position', '공용PC');
+ }
});
bindLocationEvents('hw-bldg-select', 'hw-location_detail', '', '');
@@ -320,9 +333,15 @@ class HwAssetModal extends BaseModal {
document.getElementById('btn-gen-hw-code')?.addEventListener('click', async () => {
const cat = categorySelect.value;
if (!cat) { alert('구분을 먼저 선택해주세요.'); return; }
+
+ const purchaseDate = (document.getElementById('hw-purchase_date') as HTMLInputElement)?.value || '';
+ if (!purchaseDate.trim()) {
+ alert('구매일자를 먼저 입력해 주세요. 구매일자가 없으면 자산번호를 생성할 수 없습니다.');
+ return;
+ }
+
const type = (document.getElementById('hw-asset_type') as HTMLSelectElement)?.value || '';
const prefix = TYPE_PREFIX_MAP[type] || TYPE_PREFIX_MAP[cat] || 'ETC';
- const purchaseDate = (document.getElementById('hw-purchase_date') as HTMLInputElement)?.value || '';
try {
const res = await fetch(`/api/generate-asset-code?prefix=${prefix}&purchaseDate=${purchaseDate}`);
const data = await res.json();
@@ -749,7 +768,8 @@ class HwAssetModal extends BaseModal {
const hasSpec = specCategories.includes(category) || type.includes('서버PC');
const noNetCategories = ['저장매체', '네트워크', '공간정보장비', 'PC부품', '사무가구'];
const showNet = (isInfra || isPersonal) && !noNetCategories.includes(category);
- const hasSN = !['사무가구', 'PC부품'].includes(category);
+ const hasSN = ['외부SW', '내부SW'].includes(category);
+ const showMainboard = category === 'PC';
const isParts = ['PC부품', '사무가구'].includes(category);
const showRemote = category === '서버' || type.includes('서버');
const showServiceType = category === '서버' || type === '서버PC';
@@ -762,9 +782,83 @@ class HwAssetModal extends BaseModal {
document.querySelectorAll('.org-user-section, .org-user-field').forEach(el => (el as HTMLElement).style.display = (isPersonal || isParts || category === '업무지원장비') ? '' : 'none');
document.querySelectorAll('.personal-only').forEach(el => (el as HTMLElement).style.display = isPersonal ? '' : 'none');
document.querySelectorAll('.sn-only').forEach(el => (el as HTMLElement).style.display = hasSN ? '' : 'none');
+ document.querySelectorAll('.mainboard-only').forEach(el => (el as HTMLElement).style.display = showMainboard ? '' : 'none');
document.querySelectorAll('.monitor-only').forEach(el => (el as HTMLElement).style.display = type.includes('모니터') ? '' : 'none');
document.querySelectorAll('.parts-only').forEach(el => (el as HTMLElement).style.display = isParts ? '' : 'none');
document.querySelectorAll('.hardware-section').forEach(el => (el as HTMLElement).style.display = (hasSpec || isParts) ? '' : 'none');
+
+ // Lock only User and Organization Information for PC category during edit mode
+ const isEditMode = this.currentMode === 'edit';
+ const isPC = category === 'PC';
+
+ const noticeEl = document.getElementById('hw-pc-workflow-notice');
+ if (noticeEl) {
+ if (isPC && isEditMode) {
+ noticeEl.classList.remove('hidden');
+ } else {
+ noticeEl.classList.add('hidden');
+ }
+ }
+
+ const lockedUserFields = [
+ 'hw-current_dept',
+ 'hw-manager_primary',
+ 'hw-manager_secondary',
+ 'hw-user_current',
+ 'hw-emp_no',
+ 'hw-user_position',
+ 'hw-previous_user'
+ ];
+
+ const allFormControls = this.formEl ? this.formEl.querySelectorAll('input, select, textarea, button') : [];
+
+ allFormControls.forEach(control => {
+ const el = control as HTMLElement;
+ const id = el.id;
+
+ if (el.tagName === 'INPUT' && (el as HTMLInputElement).type === 'hidden') return;
+ if (id === 'hw-asset_code' || id === 'btn-gen-hw-code') return;
+
+ if (isPC && isEditMode && lockedUserFields.includes(id)) {
+ // Lock user information fields for PC in edit mode
+ if (el.tagName === 'SELECT') {
+ el.setAttribute('disabled', 'true');
+ } else if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
+ el.setAttribute('readonly', 'true');
+ (el as HTMLInputElement).style.backgroundColor = '#f1f5f9';
+ (el as HTMLInputElement).style.cursor = 'not-allowed';
+ } else if (el.tagName === 'BUTTON') {
+ el.setAttribute('disabled', 'true');
+ }
+ } else {
+ // Normal behavior based on modal edit/view mode (includes add mode which has this.isEditMode = true)
+ if (!this.isEditMode) {
+ if (el.tagName === 'SELECT') {
+ el.setAttribute('disabled', 'true');
+ } else if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
+ el.setAttribute('readonly', 'true');
+ (el as HTMLInputElement).style.backgroundColor = '';
+ (el as HTMLInputElement).style.cursor = '';
+ } else if (el.tagName === 'BUTTON') {
+ if (id !== 'btn-print-hw-qr' && id !== 'btn-close-hw-modal') {
+ el.setAttribute('disabled', 'true');
+ }
+ }
+ } else {
+ if (el.tagName === 'SELECT') {
+ el.removeAttribute('disabled');
+ } else if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
+ if (id !== 'hw-emp_no') {
+ el.removeAttribute('readonly');
+ (el as HTMLInputElement).style.backgroundColor = '';
+ (el as HTMLInputElement).style.cursor = '';
+ }
+ } else if (el.tagName === 'BUTTON') {
+ el.removeAttribute('disabled');
+ }
+ }
+ }
+ });
}
private updateMapButtonVisibility() {
@@ -976,6 +1070,8 @@ class HwAssetModal extends BaseModal {
const showList = (filterText: string = '') => {
if (!this.isEditMode) return;
+ const category = (document.getElementById('hw-category') as HTMLSelectElement)?.value || '';
+ if (category === 'PC') return;
const users = state.masterData.users || [];
const query = filterText.trim().toLowerCase();
@@ -1053,7 +1149,58 @@ class HwAssetModal extends BaseModal {
if (!container) return;
const logs = (state.masterData.logs || []).filter(l => l.asset_id === assetId);
if (logs.length === 0) { container.innerHTML = '
기록된 변동 이력이 없습니다.
'; return; }
- container.innerHTML = logs.map(l => `
${l.log_date || ''}
${l.log_user || '시스템'}
${l.details}
`).join('');
+
+ const createdDate = this.currentAsset?.created_at ? this.currentAsset.created_at.substring(0, 10) : '';
+
+ const grouped: Record
= {};
+ logs.forEach(l => {
+ const date = l.log_date || '날짜 미지정';
+ if (!grouped[date]) grouped[date] = [];
+ grouped[date].push(l);
+ });
+
+ container.innerHTML = Object.entries(grouped).map(([date, dateLogs]) => {
+ const entriesHtml = dateLogs.map((l, idx) => {
+ const isLast = idx === dateLogs.length - 1;
+ const borderStyle = isLast ? '' : 'border-bottom: 1px dashed var(--hairline); padding-bottom: 8px; margin-bottom: 8px;';
+
+ let displayDetails = l.details;
+ if (l.details && l.details.trim().startsWith('{')) {
+ try {
+ const data = JSON.parse(l.details);
+ if (data.type === 'checkout') {
+ displayDetails = `[불출] ${data.user || ''} (${data.dept || ''}) ${data.memo ? `| 메모: ${data.memo}` : ''}`;
+ } else if (data.type === 'return') {
+ displayDetails = `[반납] ${data.user || ''} (${data.dept || ''}) ${data.memo ? `| 메모: ${data.memo}` : ''}`;
+ } else if (data.type === 'move') {
+ displayDetails = `[이동] ${data.user || ''} (${data.dept || ''}) ➔ ${data.targetUser || ''} (${data.targetDept || ''}) ${data.memo ? `| 메모: ${data.memo}` : ''}`;
+ }
+ } catch (e) {}
+ }
+
+ return `
+
+
+
+ ${l.log_user || '시스템'}
+
+
${displayDetails}
+
+ `;
+ }).join('');
+
+ const isInitialReg = date === createdDate;
+ const regBadge = isInitialReg ? `최초등록` : '';
+
+ return `
+
+
${date} ${regBadge}
+
+ ${entriesHtml}
+
+
+ `;
+ }).join('');
}
private getCategoryKey(asset: any): string {
diff --git a/src/components/Modal/SWModal.ts b/src/components/Modal/SWModal.ts
index 9ad8731..1f13e1d 100644
--- a/src/components/Modal/SWModal.ts
+++ b/src/components/Modal/SWModal.ts
@@ -389,7 +389,58 @@ class SwAssetModal extends BaseModal {
if (!container) return;
const logs = (state.masterData.logs || []).filter(l => l.asset_id === swId);
if (logs.length === 0) { container.innerHTML = '수정 이력이 없습니다.
'; return; }
- container.innerHTML = logs.map(l => `${l.log_date || ''}
${l.log_user || '시스템'}
${l.details}
`).join('');
+
+ const createdDate = this.currentAsset?.created_at ? this.currentAsset.created_at.substring(0, 10) : '';
+
+ const grouped: Record = {};
+ logs.forEach(l => {
+ const date = l.log_date || '날짜 미지정';
+ if (!grouped[date]) grouped[date] = [];
+ grouped[date].push(l);
+ });
+
+ container.innerHTML = Object.entries(grouped).map(([date, dateLogs]) => {
+ const entriesHtml = dateLogs.map((l, idx) => {
+ const isLast = idx === dateLogs.length - 1;
+ const borderStyle = isLast ? '' : 'border-bottom: 1px dashed var(--hairline); padding-bottom: 8px; margin-bottom: 8px;';
+
+ let displayDetails = l.details;
+ if (l.details && l.details.trim().startsWith('{')) {
+ try {
+ const data = JSON.parse(l.details);
+ if (data.type === 'checkout') {
+ displayDetails = `[불출] ${data.user || ''} (${data.dept || ''}) ${data.memo ? `| 메모: ${data.memo}` : ''}`;
+ } else if (data.type === 'return') {
+ displayDetails = `[반납] ${data.user || ''} (${data.dept || ''}) ${data.memo ? `| 메모: ${data.memo}` : ''}`;
+ } else if (data.type === 'move') {
+ displayDetails = `[이동] ${data.user || ''} (${data.dept || ''}) ➔ ${data.targetUser || ''} (${data.targetDept || ''}) ${data.memo ? `| 메모: ${data.memo}` : ''}`;
+ }
+ } catch (e) {}
+ }
+
+ return `
+
+
+
+ ${l.log_user || '시스템'}
+
+
${displayDetails}
+
+ `;
+ }).join('');
+
+ const isInitialReg = date === createdDate;
+ const regBadge = isInitialReg ? `최초등록` : '';
+
+ return `
+
+
${date} ${regBadge}
+
+ ${entriesHtml}
+
+
+ `;
+ }).join('');
}
}
diff --git a/src/components/Navigation.ts b/src/components/Navigation.ts
index 300c4bc..a89bb0e 100644
--- a/src/components/Navigation.ts
+++ b/src/components/Navigation.ts
@@ -65,7 +65,7 @@ export function renderNavigation(onTabChange: (tab: string) => void) {
});
if (state.currentUserRole === 'admin' && catKey === 'hw') {
- visibleTabs = ['대시보드', '실사 승인'];
+ visibleTabs = ['대시보드', '관리도구', '실사 승인', '위치지정'];
}
if (visibleTabs.length === 0) return;
@@ -75,29 +75,36 @@ export function renderNavigation(onTabChange: (tab: string) => void) {
const item = document.createElement('div');
const isActive = state.activeSubTab === tab;
item.className = `gnb-trigger ${isActive ? 'active' : ''}`;
- item.textContent = tab;
- item.style.fontSize = 'var(--fs-sm)'; // Ensure small but standard font
+
+ const isSubMenu = tab === '실사 승인' || tab === '위치지정';
+ if (isSubMenu) {
+ item.innerHTML = `↳${tab}`;
+ item.style.fontSize = '11px';
+ item.style.fontWeight = '500';
+ item.style.marginLeft = '6px';
+ if (!isActive) {
+ item.style.color = 'var(--mute)';
+ }
+ } else {
+ item.textContent = tab;
+ item.style.fontSize = 'var(--fs-sm)';
+ }
item.addEventListener('click', (e) => {
e.stopPropagation();
state.activeCategory = catKey as any;
- state.activeSubTab = tab;
+ if (tab === '관리도구') {
+ state.activeSubTab = '실사 승인';
+ } else {
+ state.activeSubTab = tab;
+ }
render();
- onTabChange(tab);
+ onTabChange(state.activeSubTab);
});
navList.appendChild(item);
});
});
- // 3. 관리자 전용 '관리도구'
- if (state.currentUserRole === 'admin') {
- const adminTrigger = document.createElement('div');
- adminTrigger.className = 'gnb-trigger admin-trigger';
- adminTrigger.innerHTML = '관리도구';
- adminTrigger.addEventListener('click', () => window.open('/map_editor.html', '_blank'));
- navList.appendChild(adminTrigger);
- }
-
// 4. 이벤트 바인딩
document.getElementById('btn-home-logo')?.addEventListener('click', () => location.reload());
diff --git a/src/main.ts b/src/main.ts
index 1211b1b..8131dff 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -6,6 +6,7 @@ import { renderDashboard } from './views/DashboardView';
import { renderSWTable } from './views/SW_Table';
import { renderLocationView } from './views/LocationView';
import { renderAuditApprovalView } from './views/AuditApprovalView';
+import { MapEditor } from './views/MapEditor';
import { initBaseModal } from './components/Modal/BaseModal';
import { initHwModal, openHwModal } from './components/Modal/HWModal';
import { initSwModal, openSwModal } from './components/Modal/SWModal';
@@ -21,11 +22,19 @@ import { pcFlowModal } from './components/Modal/PCFlowModal';
import { createIcons, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, History, RefreshCcw, BookOpen, Settings } from 'lucide';
+let activeMapEditorInstance: MapEditor | null = null;
+
// 화면 갱신 통합 핸들러
-function refreshView(tab?: string) {
+async function refreshView(tab?: string) {
const mainContent = document.getElementById('main-content')!;
if (!mainContent) return;
+ // Clean up any active MapEditor instance when navigating away
+ if (activeMapEditorInstance) {
+ activeMapEditorInstance.destroy();
+ activeMapEditorInstance = null;
+ }
+
const activeTab = tab || state.activeSubTab;
if (activeTab === '대시보드') {
@@ -34,7 +43,48 @@ function refreshView(tab?: string) {
}
if (activeTab === '실사 승인') {
- renderAuditApprovalView(mainContent);
+ await renderAuditApprovalView(mainContent);
+ return;
+ }
+
+ if (activeTab === '위치지정') {
+ // Render Map Editor directly into main content to maximize working area
+ mainContent.innerHTML = `
+
+
+
+
+
+
+
+
![Map Image]()
+
+
+
+
+
+
+ `;
+
+ // Initialize MapEditor instance
+ const editor = new MapEditor();
+ await editor.init();
+ activeMapEditorInstance = editor;
return;
}
diff --git a/src/views/List/ListFactory.ts b/src/views/List/ListFactory.ts
index 2fa5b2b..fc8a5a1 100644
--- a/src/views/List/ListFactory.ts
+++ b/src/views/List/ListFactory.ts
@@ -708,7 +708,7 @@ export function createListView(container: HTMLElement, config: ListViewConfig) {
function makeColumnsResizable(tableElement: HTMLTableElement) {
const headers = tableElement.querySelectorAll('th');
- headers.forEach(th => {
+ headers.forEach((th, index) => {
const resizer = th.querySelector('.resizer') as HTMLElement;
if (!resizer) return;
@@ -733,28 +733,44 @@ export function createListView(container: HTMLElement, config: ListViewConfig) {
resizer.classList.remove('resizing');
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
+
+ // Save the widths of all columns back to the config so they persist on re-render
+ headers.forEach((hdr, idx) => {
+ if (config.columns[idx]) {
+ config.columns[idx].width = hdr.style.width;
+ }
+ });
};
resizer.addEventListener('mousedown', (e: MouseEvent) => {
- // Prevents header click sorting trigger from firing
+ // Prevents header click sorting trigger from firing on mousedown
e.stopPropagation();
e.preventDefault();
- // Freeze all columns at their current pixel width before dragging
+ // Freeze all columns at their current precise pixel width before dragging
headers.forEach(header => {
- header.style.width = `${header.offsetWidth}px`;
+ header.style.width = `${header.getBoundingClientRect().width}px`;
});
+ // Freeze the table at its current precise pixel width immediately
+ tableElement.style.width = `${tableElement.getBoundingClientRect().width}px`;
+
startX = e.clientX;
- startWidth = th.offsetWidth;
+ startWidth = th.getBoundingClientRect().width;
// Capture the initial physical width of the entire table
- startTableWidth = tableElement.offsetWidth;
+ startTableWidth = tableElement.getBoundingClientRect().width;
resizer.classList.add('resizing');
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
+
+ // Prevents header click sorting trigger from firing on mouseup/click
+ resizer.addEventListener('click', (e: MouseEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ });
});
}
diff --git a/src/views/MapEditor.ts b/src/views/MapEditor.ts
index 448ba28..1ebe054 100644
--- a/src/views/MapEditor.ts
+++ b/src/views/MapEditor.ts
@@ -1,6 +1,7 @@
import { IMAGE_LOCATIONS } from '../components/Modal/SharedData';
import { createIcons, X, Save, Trash2, ChevronLeft, ChevronRight } from 'lucide';
import { QRPrinter } from '../core/qr_print';
+import './map-editor.css';
export class MapEditor {
private container: HTMLElement;
@@ -114,6 +115,45 @@ export class MapEditor {
this.render();
}
+ private onWindowMouseMove = (e: MouseEvent) => {
+ if (!this.isDrawing || !this.currentBox) return;
+ const rect = this.wrapper.getBoundingClientRect();
+ const currentX = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
+ const currentY = Math.max(0, Math.min(e.clientY - rect.top, rect.height));
+
+ const width = currentX - this.startX;
+ const height = currentY - this.startY;
+
+ this.currentBox.style.width = Math.abs(width) + 'px';
+ this.currentBox.style.height = Math.abs(height) + 'px';
+ this.currentBox.style.left = (width > 0 ? this.startX : currentX) + 'px';
+ this.currentBox.style.top = (height > 0 ? this.startY : currentY) + 'px';
+ };
+
+ private onWindowMouseUp = () => {
+ if (!this.isDrawing || !this.currentBox) return;
+ this.isDrawing = false;
+
+ const width = parseFloat(this.currentBox.style.width);
+ const height = parseFloat(this.currentBox.style.height);
+
+ if (width > 3 && height > 3) {
+ const rect = this.wrapper.getBoundingClientRect();
+ const boxData = {
+ x: (parseFloat(this.currentBox.style.left) / rect.width * 100).toFixed(2),
+ y: (parseFloat(this.currentBox.style.top) / rect.height * 100).toFixed(2),
+ w: (width / rect.width * 100).toFixed(2),
+ h: (height / rect.height * 100).toFixed(2),
+ asset_id: null
+ };
+ this.boxes.push(boxData);
+ this.render();
+ }
+
+ this.currentBox.remove();
+ this.currentBox = null;
+ };
+
private bindEvents() {
this.wrapper.addEventListener('mousedown', (e) => {
if (e.button !== 0) return;
@@ -135,44 +175,8 @@ export class MapEditor {
this.wrapper.appendChild(this.currentBox);
});
- window.addEventListener('mousemove', (e) => {
- if (!this.isDrawing || !this.currentBox) return;
- const rect = this.wrapper.getBoundingClientRect();
- const currentX = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
- const currentY = Math.max(0, Math.min(e.clientY - rect.top, rect.height));
-
- const width = currentX - this.startX;
- const height = currentY - this.startY;
-
- this.currentBox.style.width = Math.abs(width) + 'px';
- this.currentBox.style.height = Math.abs(height) + 'px';
- this.currentBox.style.left = (width > 0 ? this.startX : currentX) + 'px';
- this.currentBox.style.top = (height > 0 ? this.startY : currentY) + 'px';
- });
-
- window.addEventListener('mouseup', () => {
- if (!this.isDrawing || !this.currentBox) return;
- this.isDrawing = false;
-
- const width = parseFloat(this.currentBox.style.width);
- const height = parseFloat(this.currentBox.style.height);
-
- if (width > 3 && height > 3) {
- const rect = this.wrapper.getBoundingClientRect();
- const boxData = {
- x: (parseFloat(this.currentBox.style.left) / rect.width * 100).toFixed(2),
- y: (parseFloat(this.currentBox.style.top) / rect.height * 100).toFixed(2),
- w: (width / rect.width * 100).toFixed(2),
- h: (height / rect.height * 100).toFixed(2),
- asset_id: null
- };
- this.boxes.push(boxData);
- this.render();
- }
-
- this.currentBox.remove();
- this.currentBox = null;
- });
+ window.addEventListener('mousemove', this.onWindowMouseMove);
+ window.addEventListener('mouseup', this.onWindowMouseUp);
(window as any).removeBox = (index: number) => {
this.boxes.splice(index, 1);
@@ -341,6 +345,13 @@ export class MapEditor {
}]);
};
}
+
+ public destroy() {
+ window.removeEventListener('mousemove', this.onWindowMouseMove);
+ window.removeEventListener('mouseup', this.onWindowMouseUp);
+ delete (window as any).removeBox;
+ delete (window as any).printBoxQR;
+ }
}
function getCleanMapKey(path: string) {