Initial commit: drawNET Alpha v1.0 - Professional Topology Designer with Full i18n and Performance Optimizations

This commit is contained in:
leeyj
2026-03-22 22:37:24 +09:00
commit 5cea93e317
192 changed files with 14449 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
import { t } from '../../i18n.js';
export function renderAbout(container) {
container.innerHTML = `
<h3>${t('about')} drawNET</h3>
<div class="setting-row">
<div class="setting-info">
<span class="label">Version</span>
<span class="desc">Premium Edition v2.1.0</span>
</div>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="label">Engine</span>
<span class="desc">AntV X6 + drawNET Proprietary Parser</span>
</div>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="label">${t('identity')}</span>
<span class="desc">Developed for High-Stakes Engineering</span>
</div>
</div>
`;
}
+48
View File
@@ -0,0 +1,48 @@
import { t } from '../../i18n.js';
import { getSettings, updateSetting } from '../store.js';
export function renderEngine(container) {
const settings = getSettings();
container.innerHTML = `
<h3>${t('engine') || 'Engine'}</h3>
<div class="setting-row">
<div class="setting-info">
<span class="label">${t('auto_load_last')}</span>
<span class="desc">${t('auto_load_last_desc')}</span>
</div>
<div class="setting-ctrl">
<input type="checkbox" id="auto-load-toggle" ${settings.autoLoadLast ? 'checked' : ''}>
</div>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="label">${t('sync_mode')}</span>
<span class="desc">${t('sync_mode_desc')}</span>
</div>
<div class="setting-ctrl">
<select disabled>
<option selected>Manual (Pro)</option>
<option>Real-time (Legacy)</option>
</select>
</div>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="label">${t('auto_save')}</span>
<span class="desc">${t('auto_save_desc')}</span>
</div>
<div class="setting-ctrl">
<input type="checkbox" checked disabled>
</div>
</div>
`;
}
export function bindEngineEvents(container) {
const autoLoadToggle = container.querySelector('#auto-load-toggle');
if (autoLoadToggle) {
autoLoadToggle.addEventListener('change', (e) => {
updateSetting('autoLoadLast', e.target.checked);
});
}
}
@@ -0,0 +1,73 @@
/**
* settings/tabs/general.js - General Settings Tab
*/
import { setTheme, getSettings } from '../store.js';
import { t } from '../../i18n.js';
export function renderGeneral(container) {
container.innerHTML = `
<h3>${t('identity') || 'Identity'}</h3>
<div class="setting-row">
<div class="setting-info">
<span class="label">${t('dark_mode')}</span>
<span class="desc">${t('dark_mode_desc')}</span>
</div>
<div class="setting-ctrl">
<label class="toggle-switch">
<input type="checkbox" id="dark-mode-toggle" ${document.documentElement.getAttribute('data-theme') === 'dark' ? 'checked' : ''}>
<div class="switch-slider"></div>
</label>
</div>
</div>
<h3 style="margin-top: 24px;">${t('editor_behavior') || 'Editor Behavior'}</h3>
<div class="setting-row">
<div class="setting-info">
<span class="label">${t('confirm_ungroup') || 'Confirm before Un-grouping'}</span>
<span class="desc">${t('confirm_ungroup_desc') || 'Show a confirmation dialog when removing an object from a group.'}</span>
</div>
<div class="setting-ctrl">
<label class="toggle-switch">
<input type="checkbox" id="confirm-ungroup-toggle" ${getSettings().confirmUngroup !== false ? 'checked' : ''}>
<div class="switch-slider"></div>
</label>
</div>
</div>
<h3 style="margin-top: 24px;">${t('language') || 'Language'}</h3>
<div class="setting-row">
<div class="setting-info">
<span class="label">${t('language')}</span>
<span class="desc">${t('select_lang_desc')}</span>
</div>
<div class="setting-ctrl">
<select id="lang-select" class="prop-input" style="width: 120px;">
<option value="ko" ${localStorage.getItem('drawNET_lang') === 'ko' ? 'selected' : ''}>한국어</option>
<option value="en" ${localStorage.getItem('drawNET_lang') === 'en' ? 'selected' : ''}>English</option>
</select>
</div>
</div>
`;
}
export function bindGeneralEvents(container) {
const darkModeToggle = container.querySelector('#dark-mode-toggle');
if (darkModeToggle) {
darkModeToggle.addEventListener('change', (e) => setTheme(e.target.checked ? 'dark' : 'light'));
}
const ungroupToggle = container.querySelector('#confirm-ungroup-toggle');
if (ungroupToggle) {
ungroupToggle.addEventListener('change', (e) => {
import('../store.js').then(m => m.updateSetting('confirmUngroup', e.target.checked));
});
}
const langSelect = container.querySelector('#lang-select');
if (langSelect) {
langSelect.addEventListener('change', (e) => {
localStorage.setItem('drawNET_lang', e.target.value);
location.reload();
});
}
}
+103
View File
@@ -0,0 +1,103 @@
import { t } from '../../i18n.js';
import { checkLicenseStatus } from '../../ui/license.js';
export async function renderLicense(container) {
const status = await checkLicenseStatus();
container.innerHTML = `
<h3><i class="fas fa-certificate" style="color: #3b82f6; margin-right: 8px;"></i> ${t('license_management')}</h3>
<p class="settings-intro">${t('license_intro')}</p>
<div class="license-status-card">
<div class="status-header">
<div class="edition-info">
<span class="edition-label">${t('current_edition')}</span>
<h2 class="edition-name">${status.valid ? status.data.level : t('trial_community')}</h2>
</div>
<div class="status-icon ${status.valid ? 'valid' : 'expired'}">
<i class="fas ${status.valid ? 'fa-crown' : 'fa-info-circle'}"></i>
</div>
</div>
<div class="hwid-section">
<div class="hwid-label-row">
<span>${t('machine_id')}</span>
<button class="text-link-btn" id="copy-hwid-btn"><i class="fas fa-copy"></i> ${t('copy')}</button>
</div>
<div class="hwid-value-box">
<code>${status.hwid}</code>
</div>
</div>
</div>
${status.valid ? `
<div class="setting-row" style="border-bottom: none;">
<div class="setting-info">
<span class="label">${t('expiry_info')}</span>
<span class="desc">${t('valid_until', { date: `<strong>${status.data.expiry}</strong>` })}</span>
</div>
</div>
` : ''}
<div class="divider"></div>
<div class="license-action-section">
<h4 style="margin-bottom: 12px;">${t('activate_license_title')}</h4>
<div class="key-input-wrapper">
<textarea id="license-key-input" placeholder="${t('license_key_placeholder')}"></textarea>
</div>
<button id="activate-license-btn" class="primary-btn-premium">
<i class="fas fa-key"></i> ${t('activate_license_btn')}
</button>
</div>
`;
bindLicenseEvents(container);
}
function bindLicenseEvents(container) {
const copyBtn = container.querySelector('#copy-hwid-btn');
if (copyBtn) {
copyBtn.onclick = () => {
const hwid = container.querySelector('code').textContent;
navigator.clipboard.writeText(hwid);
// Simple visual feedback
const icon = copyBtn.querySelector('i');
icon.className = 'fas fa-check';
setTimeout(() => icon.className = 'fas fa-copy', 2000);
};
}
const activateBtn = container.querySelector('#activate-license-btn');
if (activateBtn) {
activateBtn.onclick = async () => {
const key = container.querySelector('#license-key-input').value.trim();
if (!key) {
alert(t('enter_license_key'));
return;
}
activateBtn.disabled = true;
activateBtn.innerHTML = `<i class="fas fa-spinner fa-spin"></i> ${t('verifying')}`;
try {
const res = await fetch('/api/license/activate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key })
});
const result = await res.json();
if (result.success) {
alert(t('license_activated_success'));
location.reload();
} else {
alert(t('activation_failed') + result.message);
}
} catch (err) {
alert(t('connection_error') + err.message);
} finally {
activateBtn.disabled = false;
activateBtn.innerHTML = `<i class="fas fa-key"></i> ${t('activate_license_btn')}`;
}
};
}
}
@@ -0,0 +1,220 @@
/**
* settings/tabs/workspace.js - Workspace (Canvas/Grid) Settings Tab
*/
import { updateSetting } from '../store.js';
import { t } from '../../i18n.js';
export function renderWorkspace(container, settings) {
container.innerHTML = `
<h3>${t('canvas_background') || 'Workspace'}</h3>
<div class="setting-row">
<div class="setting-info">
<span class="label">${t('grid_style')}</span>
<span class="desc">${t('grid_style_desc')}</span>
</div>
<div class="setting-ctrl">
<select id="grid-style-select">
<option value="none" ${settings.gridStyle === 'none' ? 'selected' : ''}>${t('grid_none')}</option>
<option value="solid" ${settings.gridStyle === 'solid' ? 'selected' : ''}>${t('grid_solid')}</option>
<option value="dashed" ${settings.gridStyle === 'dashed' ? 'selected' : ''}>${t('grid_dashed')}</option>
</select>
</div>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="label">${t('grid_spacing')}</span>
<span class="desc">${t('grid_spacing_desc')}</span>
</div>
<div class="setting-ctrl">
<input type="number" id="grid-spacing-input" value="${settings.gridSpacing || 20}" min="10" max="100" step="5">
</div>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="label">${t('bg_color')}</span>
<span class="desc">${t('bg_color_desc')}</span>
</div>
<div class="setting-ctrl">
<input type="color" id="bg-color-picker" value="${settings.bgColor || '#f8fafc'}">
</div>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="label">${t('grid_color')}</span>
<span class="desc">${t('grid_color_desc')}</span>
</div>
<div class="setting-ctrl">
<input type="color" id="grid-color-picker" value="${settings.gridColor || '#e2e8f0'}">
</div>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="label">${t('grid_thickness')}</span>
<span class="desc">${t('grid_thickness_desc')}</span>
</div>
<div class="setting-ctrl">
<input type="number" id="grid-thickness-input" value="${settings.gridThickness || 1}" min="0.5" max="5" step="0.5">
</div>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="label">${t('show_major_grid')}</span>
<span class="desc">${t('show_major_grid_desc')}</span>
</div>
<div class="setting-ctrl">
<input type="checkbox" id="major-grid-toggle" ${settings.showMajorGrid ? 'checked' : ''}>
</div>
</div>
<div id="major-grid-options" style="display: ${settings.showMajorGrid ? 'block' : 'none'}">
<div class="setting-row">
<div class="setting-info">
<span class="label">${t('major_grid_color')}</span>
<span class="desc">${t('major_grid_color_desc')}</span>
</div>
<div class="setting-ctrl">
<input type="color" id="major-grid-color-picker" value="${settings.majorGridColor || '#cbd5e1'}">
</div>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="label">${t('major_grid_interval')}</span>
<span class="desc">${t('major_grid_interval_desc')}</span>
</div>
<div class="setting-ctrl">
<input type="number" id="major-grid-interval-input" value="${settings.majorGridInterval || 5}" min="2" max="20">
</div>
</div>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="label">${t('canvas_preset')}</span>
<span class="desc">${t('canvas_preset_desc')}</span>
</div>
<div class="setting-ctrl">
<select id="canvas-preset">
<option value="full" ${settings.canvasPreset === 'full' ? 'selected' : ''}>${t('canvas_full')}</option>
<option value="A4" ${settings.canvasPreset === 'A4' ? 'selected' : ''}>A4</option>
<option value="A3" ${settings.canvasPreset === 'A3' ? 'selected' : ''}>A3</option>
<option value="custom" ${settings.canvasPreset === 'custom' ? 'selected' : ''}>Custom</option>
</select>
</div>
</div>
<div id="orientation-container" class="setting-row" style="display: ${settings.canvasPreset === 'full' ? 'none' : 'flex'}">
<div class="setting-info">
<span class="label">${t('orientation')}</span>
<span class="desc">${t('orientation_desc')}</span>
</div>
<div class="setting-ctrl">
<select id="canvas-orientation">
<option value="portrait" ${settings.canvasOrientation === 'portrait' ? 'selected' : ''}>${t('portrait')}</option>
<option value="landscape" ${settings.canvasOrientation === 'landscape' ? 'selected' : ''}>${t('landscape')}</option>
</select>
</div>
</div>
<div id="custom-size-inputs" class="setting-row" style="display: ${settings.canvasPreset === 'custom' ? 'flex' : 'none'}; gap: 10px;">
<input type="number" id="canvas-width" value="${settings.canvasWidth || 800}" placeholder="W" style="width: 70px;">
<input type="number" id="canvas-height" value="${settings.canvasHeight || 600}" placeholder="H" style="width: 70px;">
<button id="apply-custom-size" class="footer-btn mini">${t('apply')}</button>
</div>
`;
}
export function bindWorkspaceEvents(container) {
const getGridParams = () => ({
style: container.querySelector('#grid-style-select').value,
color: container.querySelector('#grid-color-picker').value,
thickness: parseFloat(container.querySelector('#grid-thickness-input').value) || 1,
showMajor: container.querySelector('#major-grid-toggle').checked,
majorColor: container.querySelector('#major-grid-color-picker').value,
majorInterval: parseInt(container.querySelector('#major-grid-interval-input').value) || 5
});
const dispatchGridUpdate = () => {
const params = getGridParams();
updateSetting('gridStyle', params.style);
updateSetting('gridColor', params.color);
updateSetting('gridThickness', params.thickness);
updateSetting('showMajorGrid', params.showMajor);
updateSetting('majorGridColor', params.majorColor);
updateSetting('majorGridInterval', params.majorInterval);
const majorOpts = container.querySelector('#major-grid-options');
if (majorOpts) majorOpts.style.display = params.showMajor ? 'block' : 'none';
window.dispatchEvent(new CustomEvent('gridChanged', { detail: params }));
};
const gridStyleSelect = container.querySelector('#grid-style-select');
gridStyleSelect.addEventListener('change', dispatchGridUpdate);
const gridColorPicker = container.querySelector('#grid-color-picker');
gridColorPicker.addEventListener('input', dispatchGridUpdate);
const gridThicknessInput = container.querySelector('#grid-thickness-input');
gridThicknessInput.addEventListener('input', dispatchGridUpdate);
const majorGridToggle = container.querySelector('#major-grid-toggle');
majorGridToggle.addEventListener('change', dispatchGridUpdate);
const majorGridColorPicker = container.querySelector('#major-grid-color-picker');
majorGridColorPicker.addEventListener('input', dispatchGridUpdate);
const majorGridIntervalInput = container.querySelector('#major-grid-interval-input');
majorGridIntervalInput.addEventListener('input', dispatchGridUpdate);
const gridSpacingInput = container.querySelector('#grid-spacing-input');
gridSpacingInput.addEventListener('input', (e) => {
const val = parseInt(e.target.value) || 20;
updateSetting('gridSpacing', val);
document.documentElement.style.setProperty('--grid-size', `${val}px`);
window.dispatchEvent(new CustomEvent('gridSpacingChanged', { detail: { spacing: val } }));
});
const bgColorPicker = container.querySelector('#bg-color-picker');
bgColorPicker.addEventListener('input', (e) => {
updateSetting('bgColor', e.target.value);
document.documentElement.style.setProperty('--bg-color', e.target.value);
});
const canvasPreset = container.querySelector('#canvas-preset');
const orientationContainer = container.querySelector('#orientation-container');
const customSizeDiv = container.querySelector('#custom-size-inputs');
const updateCanvas = () => {
const val = canvasPreset.value;
const orientationSelect = container.querySelector('#canvas-orientation');
const orient = orientationSelect ? orientationSelect.value : 'portrait';
updateSetting('canvasPreset', val);
updateSetting('canvasOrientation', orient);
if (customSizeDiv) customSizeDiv.style.display = val === 'custom' ? 'flex' : 'none';
if (orientationContainer) orientationContainer.style.display = val === 'full' ? 'none' : 'flex';
let w = '100%', h = '100%';
if (val === 'A4') {
w = orient === 'portrait' ? 794 : 1123;
h = orient === 'portrait' ? 1123 : 794;
} else if (val === 'A3') {
w = orient === 'portrait' ? 1123 : 1587;
h = orient === 'portrait' ? 1587 : 1123;
} else if (val === 'custom') {
w = parseInt(container.querySelector('#canvas-width').value) || 800;
h = parseInt(container.querySelector('#canvas-height').value) || 600;
updateSetting('canvasWidth', w);
updateSetting('canvasHeight', h);
}
window.dispatchEvent(new CustomEvent('canvasResize', {
detail: { width: w, height: h }
}));
};
canvasPreset.addEventListener('change', updateCanvas);
const orientationSelect = container.querySelector('#canvas-orientation');
if (orientationSelect) orientationSelect.addEventListener('change', updateCanvas);
const applyCustomBtn = container.querySelector('#apply-custom-size');
if (applyCustomBtn) applyCustomBtn.addEventListener('click', updateCanvas);
}