mirror of
https://github.com/sotam0316/drawNET_test.git
synced 2026-04-25 03:58:38 +09:00
static 폴더 및 하위 파일 업로드
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import { STORAGE_KEYS } from '../constants.js';
|
||||
|
||||
/**
|
||||
* settings/store.js - Persistence logic for app settings
|
||||
*/
|
||||
export function getSettings() {
|
||||
return JSON.parse(localStorage.getItem(STORAGE_KEYS.SETTINGS) || '{}');
|
||||
}
|
||||
|
||||
export function updateSetting(key, value) {
|
||||
const settings = getSettings();
|
||||
settings[key] = value;
|
||||
localStorage.setItem(STORAGE_KEYS.SETTINGS, JSON.stringify(settings));
|
||||
}
|
||||
|
||||
export function getTheme() {
|
||||
return localStorage.getItem(STORAGE_KEYS.THEME) || 'light';
|
||||
}
|
||||
|
||||
export function setTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
localStorage.setItem(STORAGE_KEYS.THEME, theme);
|
||||
window.dispatchEvent(new CustomEvent('themeChanged', { detail: { theme } }));
|
||||
}
|
||||
|
||||
export function applySavedTheme() {
|
||||
const theme = getTheme();
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
}
|
||||
|
||||
export function applySavedSettings() {
|
||||
const settings = getSettings();
|
||||
if (settings.bgColor) {
|
||||
document.documentElement.style.setProperty('--bg-color', settings.bgColor);
|
||||
}
|
||||
|
||||
if (settings.gridStyle) {
|
||||
window.dispatchEvent(new CustomEvent('gridChanged', {
|
||||
detail: {
|
||||
style: settings.gridStyle,
|
||||
color: settings.gridColor,
|
||||
thickness: settings.gridThickness,
|
||||
showMajor: settings.showMajorGrid,
|
||||
majorColor: settings.majorGridColor,
|
||||
majorInterval: settings.majorGridInterval
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
if (settings.gridSpacing) {
|
||||
document.documentElement.style.setProperty('--grid-size', `${settings.gridSpacing}px`);
|
||||
window.dispatchEvent(new CustomEvent('gridSpacingChanged', { detail: { spacing: settings.gridSpacing } }));
|
||||
}
|
||||
|
||||
if (settings.canvasPreset) {
|
||||
const val = settings.canvasPreset;
|
||||
const orient = settings.canvasOrientation || 'portrait';
|
||||
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 = settings.canvasWidth;
|
||||
h = settings.canvasHeight;
|
||||
}
|
||||
|
||||
window.dispatchEvent(new CustomEvent('canvasResize', {
|
||||
detail: { width: w, height: h }
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* settings/ui.js - Professional Settings Panel UI (Modular)
|
||||
*/
|
||||
import { getSettings } from './store.js';
|
||||
import { renderGeneral, bindGeneralEvents } from './tabs/general.js';
|
||||
import { renderWorkspace, bindWorkspaceEvents } from './tabs/workspace.js';
|
||||
import { renderEngine, bindEngineEvents } from './tabs/engine.js';
|
||||
import { renderAbout } from './tabs/about.js';
|
||||
|
||||
let activeTab = 'general';
|
||||
|
||||
export function initSettings() {
|
||||
const settingsBtn = document.getElementById('settings-btn');
|
||||
const closeBtn = document.getElementById('close-settings-btn');
|
||||
const panel = document.getElementById('settings-panel');
|
||||
const tabs = document.querySelectorAll('.settings-tab');
|
||||
|
||||
if (settingsBtn && panel) {
|
||||
settingsBtn.addEventListener('click', () => {
|
||||
panel.classList.toggle('active');
|
||||
if (panel.classList.contains('active')) renderTabContent(activeTab);
|
||||
});
|
||||
}
|
||||
|
||||
if (closeBtn && panel) {
|
||||
closeBtn.addEventListener('click', () => panel.classList.remove('active'));
|
||||
}
|
||||
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
activeTab = tab.getAttribute('data-tab');
|
||||
renderTabContent(activeTab);
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('mousedown', (e) => {
|
||||
if (panel?.classList.contains('active')) {
|
||||
if (!panel.contains(e.target) && !settingsBtn.contains(e.target)) {
|
||||
panel.classList.remove('active');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderTabContent(tabName) {
|
||||
const body = document.getElementById('settings-body');
|
||||
if (!body) return;
|
||||
|
||||
const settings = getSettings();
|
||||
body.innerHTML = '';
|
||||
const section = document.createElement('div');
|
||||
section.className = 'settings-section';
|
||||
|
||||
switch (tabName) {
|
||||
case 'general':
|
||||
renderGeneral(section);
|
||||
bindGeneralEvents(section);
|
||||
break;
|
||||
case 'workspace':
|
||||
renderWorkspace(section, settings);
|
||||
bindWorkspaceEvents(section);
|
||||
break;
|
||||
case 'engine':
|
||||
renderEngine(section);
|
||||
bindEngineEvents(section);
|
||||
break;
|
||||
case 'about':
|
||||
renderAbout(section);
|
||||
break;
|
||||
}
|
||||
|
||||
body.appendChild(section);
|
||||
}
|
||||
Reference in New Issue
Block a user