158 lines
5.7 KiB
TypeScript
158 lines
5.7 KiB
TypeScript
|
|
import { useState } from 'react';
|
||
|
|
import type { MapStyleSettings, MapLabelLanguage, DepthFontSize } from './types';
|
||
|
|
import { DEFAULT_MAP_STYLE_SETTINGS } from './types';
|
||
|
|
|
||
|
|
interface MapSettingsPanelProps {
|
||
|
|
value: MapStyleSettings;
|
||
|
|
onChange: (next: MapStyleSettings) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
const LANGUAGES: { value: MapLabelLanguage; label: string }[] = [
|
||
|
|
{ value: 'ko', label: '한국어' },
|
||
|
|
{ value: 'en', label: 'English' },
|
||
|
|
{ value: 'ja', label: '日本語' },
|
||
|
|
{ value: 'zh', label: '中文' },
|
||
|
|
{ value: 'local', label: '현지어' },
|
||
|
|
];
|
||
|
|
|
||
|
|
const FONT_SIZES: { value: DepthFontSize; label: string }[] = [
|
||
|
|
{ value: 'small', label: '소' },
|
||
|
|
{ value: 'medium', label: '중' },
|
||
|
|
{ value: 'large', label: '대' },
|
||
|
|
];
|
||
|
|
|
||
|
|
function depthLabel(depth: number): string {
|
||
|
|
return `${Math.abs(depth).toLocaleString()}m`;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function MapSettingsPanel({ value, onChange }: MapSettingsPanelProps) {
|
||
|
|
const [open, setOpen] = useState(false);
|
||
|
|
|
||
|
|
const update = <K extends keyof MapStyleSettings>(key: K, val: MapStyleSettings[K]) => {
|
||
|
|
onChange({ ...value, [key]: val });
|
||
|
|
};
|
||
|
|
|
||
|
|
const updateDepthStop = (index: number, color: string) => {
|
||
|
|
const next = value.depthStops.map((s, i) => (i === index ? { ...s, color } : s));
|
||
|
|
update('depthStops', next);
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<>
|
||
|
|
<button
|
||
|
|
className={`map-settings-gear${open ? ' open' : ''}`}
|
||
|
|
onClick={() => setOpen((p) => !p)}
|
||
|
|
title="지도 설정"
|
||
|
|
type="button"
|
||
|
|
>
|
||
|
|
⚙
|
||
|
|
</button>
|
||
|
|
|
||
|
|
{open && (
|
||
|
|
<div className="map-settings-panel">
|
||
|
|
<div className="ms-title">지도 설정</div>
|
||
|
|
|
||
|
|
{/* ── Language ──────────────────────────────────── */}
|
||
|
|
<div className="ms-section">
|
||
|
|
<div className="ms-label">레이블 언어</div>
|
||
|
|
<select
|
||
|
|
value={value.labelLanguage}
|
||
|
|
onChange={(e) => update('labelLanguage', e.target.value as MapLabelLanguage)}
|
||
|
|
>
|
||
|
|
{LANGUAGES.map((l) => (
|
||
|
|
<option key={l.value} value={l.value}>
|
||
|
|
{l.label}
|
||
|
|
</option>
|
||
|
|
))}
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* ── Land color ────────────────────────────────── */}
|
||
|
|
<div className="ms-section">
|
||
|
|
<div className="ms-label">육지 색상</div>
|
||
|
|
<div className="ms-row">
|
||
|
|
<input
|
||
|
|
type="color"
|
||
|
|
className="ms-color-input"
|
||
|
|
value={value.landColor}
|
||
|
|
onChange={(e) => update('landColor', e.target.value)}
|
||
|
|
/>
|
||
|
|
<span className="ms-hex">{value.landColor}</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* ── Water color ───────────────────────────────── */}
|
||
|
|
<div className="ms-section">
|
||
|
|
<div className="ms-label">물 기본색</div>
|
||
|
|
<div className="ms-row">
|
||
|
|
<input
|
||
|
|
type="color"
|
||
|
|
className="ms-color-input"
|
||
|
|
value={value.waterBaseColor}
|
||
|
|
onChange={(e) => update('waterBaseColor', e.target.value)}
|
||
|
|
/>
|
||
|
|
<span className="ms-hex">{value.waterBaseColor}</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* ── Depth gradient ────────────────────────────── */}
|
||
|
|
<div className="ms-section">
|
||
|
|
<div className="ms-label">수심 구간 색상</div>
|
||
|
|
{value.depthStops.map((stop, i) => (
|
||
|
|
<div className="ms-row" key={stop.depth}>
|
||
|
|
<span className="ms-depth-label">{depthLabel(stop.depth)}</span>
|
||
|
|
<input
|
||
|
|
type="color"
|
||
|
|
className="ms-color-input"
|
||
|
|
value={stop.color}
|
||
|
|
onChange={(e) => updateDepthStop(i, e.target.value)}
|
||
|
|
/>
|
||
|
|
<span className="ms-hex">{stop.color}</span>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* ── Depth font size ───────────────────────────── */}
|
||
|
|
<div className="ms-section">
|
||
|
|
<div className="ms-label">수심 폰트 크기</div>
|
||
|
|
<div className="tog" style={{ gap: 3 }}>
|
||
|
|
{FONT_SIZES.map((fs) => (
|
||
|
|
<div
|
||
|
|
key={fs.value}
|
||
|
|
className={`tog-btn${value.depthFontSize === fs.value ? ' on' : ''}`}
|
||
|
|
onClick={() => update('depthFontSize', fs.value)}
|
||
|
|
>
|
||
|
|
{fs.label}
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* ── Depth font color ──────────────────────────── */}
|
||
|
|
<div className="ms-section">
|
||
|
|
<div className="ms-label">수심 폰트 색상</div>
|
||
|
|
<div className="ms-row">
|
||
|
|
<input
|
||
|
|
type="color"
|
||
|
|
className="ms-color-input"
|
||
|
|
value={value.depthFontColor}
|
||
|
|
onChange={(e) => update('depthFontColor', e.target.value)}
|
||
|
|
/>
|
||
|
|
<span className="ms-hex">{value.depthFontColor}</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* ── Reset ─────────────────────────────────────── */}
|
||
|
|
<button
|
||
|
|
className="ms-reset"
|
||
|
|
type="button"
|
||
|
|
onClick={() => onChange(DEFAULT_MAP_STYLE_SETTINGS)}
|
||
|
|
>
|
||
|
|
초기화
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</>
|
||
|
|
);
|
||
|
|
}
|