ci: Gitea Actions CI/CD 파이프라인 + 기능 업데이트 #2

병합
htlee develop 에서 main 로 59 commits 를 머지했습니다 2026-02-16 07:35:55 +09:00
3개의 변경된 파일99개의 추가작업 그리고 28개의 파일을 삭제
Showing only changes of commit 1a3dd82eb4 - Show all commits

파일 보기

@ -971,7 +971,7 @@ body {
} }
.map-settings-panel .ms-title { .map-settings-panel .ms-title {
font-size: 10px; font-size: 11px;
font-weight: 700; font-weight: 700;
color: var(--text); color: var(--text);
letter-spacing: 1px; letter-spacing: 1px;
@ -983,11 +983,11 @@ body {
} }
.map-settings-panel .ms-label { .map-settings-panel .ms-label {
font-size: 8px; font-size: 10px;
font-weight: 700; font-weight: 600;
color: var(--muted); color: var(--text);
letter-spacing: 1px; letter-spacing: 0.5px;
margin-bottom: 4px; margin-bottom: 5px;
} }
.map-settings-panel .ms-row { .map-settings-panel .ms-row {
@ -1019,12 +1019,12 @@ body {
.map-settings-panel .ms-hex { .map-settings-panel .ms-hex {
font-size: 9px; font-size: 9px;
color: var(--muted); color: #94a3b8;
font-family: monospace; font-family: monospace;
} }
.map-settings-panel .ms-depth-label { .map-settings-panel .ms-depth-label {
font-size: 9px; font-size: 10px;
color: var(--text); color: var(--text);
min-width: 48px; min-width: 48px;
text-align: right; text-align: right;

파일 보기

@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import type { MapStyleSettings, MapLabelLanguage, DepthFontSize } from './types'; import type { MapStyleSettings, MapLabelLanguage, DepthFontSize, DepthColorStop } from './types';
import { DEFAULT_MAP_STYLE_SETTINGS } from './types'; import { DEFAULT_MAP_STYLE_SETTINGS } from './types';
interface MapSettingsPanelProps { interface MapSettingsPanelProps {
@ -25,8 +25,42 @@ function depthLabel(depth: number): string {
return `${Math.abs(depth).toLocaleString()}m`; return `${Math.abs(depth).toLocaleString()}m`;
} }
function hexToRgb(hex: string): [number, number, number] {
return [
parseInt(hex.slice(1, 3), 16),
parseInt(hex.slice(3, 5), 16),
parseInt(hex.slice(5, 7), 16),
];
}
function rgbToHex(r: number, g: number, b: number): string {
return `#${[r, g, b].map((c) => Math.round(Math.max(0, Math.min(255, c))).toString(16).padStart(2, '0')).join('')}`;
}
function interpolateGradient(stops: DepthColorStop[]): DepthColorStop[] {
if (stops.length < 2) return stops;
const sorted = [...stops].sort((a, b) => a.depth - b.depth);
const first = sorted[0];
const last = sorted[sorted.length - 1];
const [r1, g1, b1] = hexToRgb(first.color);
const [r2, g2, b2] = hexToRgb(last.color);
return sorted.map((stop, i) => {
if (i === 0 || i === sorted.length - 1) return stop;
const t = i / (sorted.length - 1);
return {
depth: stop.depth,
color: rgbToHex(
r1 + (r2 - r1) * t,
g1 + (g2 - g1) * t,
b1 + (b2 - b1) * t,
),
};
});
}
export function MapSettingsPanel({ value, onChange }: MapSettingsPanelProps) { export function MapSettingsPanel({ value, onChange }: MapSettingsPanelProps) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [autoGradient, setAutoGradient] = useState(false);
const update = <K extends keyof MapStyleSettings>(key: K, val: MapStyleSettings[K]) => { const update = <K extends keyof MapStyleSettings>(key: K, val: MapStyleSettings[K]) => {
onChange({ ...value, [key]: val }); onChange({ ...value, [key]: val });
@ -34,7 +68,19 @@ export function MapSettingsPanel({ value, onChange }: MapSettingsPanelProps) {
const updateDepthStop = (index: number, color: string) => { const updateDepthStop = (index: number, color: string) => {
const next = value.depthStops.map((s, i) => (i === index ? { ...s, color } : s)); const next = value.depthStops.map((s, i) => (i === index ? { ...s, color } : s));
update('depthStops', next); if (autoGradient && (index === 0 || index === next.length - 1)) {
update('depthStops', interpolateGradient(next));
} else {
update('depthStops', next);
}
};
const toggleAutoGradient = () => {
const next = !autoGradient;
setAutoGradient(next);
if (next) {
update('depthStops', interpolateGradient(value.depthStops));
}
}; };
return ( return (
@ -97,19 +143,34 @@ export function MapSettingsPanel({ value, onChange }: MapSettingsPanelProps) {
{/* ── Depth gradient ────────────────────────────── */} {/* ── Depth gradient ────────────────────────────── */}
<div className="ms-section"> <div className="ms-section">
<div className="ms-label"> </div> <div className="ms-label" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{value.depthStops.map((stop, i) => (
<div className="ms-row" key={stop.depth}> <span
<span className="ms-depth-label">{depthLabel(stop.depth)}</span> className={`tog-btn${autoGradient ? ' on' : ''}`}
<input style={{ fontSize: 8, padding: '1px 5px', marginLeft: 8 }}
type="color" onClick={toggleAutoGradient}
className="ms-color-input" title="최소/최대 색상 기준으로 중간 구간을 자동 보간합니다"
value={stop.color} >
onChange={(e) => updateDepthStop(i, e.target.value)}
/> </span>
<span className="ms-hex">{stop.color}</span> </div>
</div> {value.depthStops.map((stop, i) => {
))} const isEdge = i === 0 || i === value.depthStops.length - 1;
const dimmed = autoGradient && !isEdge;
return (
<div className="ms-row" key={stop.depth} style={dimmed ? { opacity: 0.5 } : undefined}>
<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)}
disabled={dimmed}
/>
<span className="ms-hex">{stop.color}</span>
</div>
);
})}
</div> </div>
{/* ── Depth font size ───────────────────────────── */} {/* ── Depth font size ───────────────────────────── */}
@ -146,7 +207,10 @@ export function MapSettingsPanel({ value, onChange }: MapSettingsPanelProps) {
<button <button
className="ms-reset" className="ms-reset"
type="button" type="button"
onClick={() => onChange(DEFAULT_MAP_STYLE_SETTINGS)} onClick={() => {
onChange(DEFAULT_MAP_STYLE_SETTINGS);
setAutoGradient(false);
}}
> >
</button> </button>

파일 보기

@ -51,14 +51,21 @@ function applyLabelLanguage(map: maplibregl.Map, lang: MapLabelLanguage) {
function applyLandColor(map: maplibregl.Map, color: string) { function applyLandColor(map: maplibregl.Map, color: string) {
const style = map.getStyle(); const style = map.getStyle();
if (!style?.layers) return; if (!style?.layers) return;
const landRegex = /(land|landcover|landuse|earth|continent|terrain|park)/i; const waterRegex = /(water|sea|ocean|river|lake|coast|bay)/i;
const darkVariant = darkenHex(color, 0.8);
for (const layer of style.layers) { for (const layer of style.layers) {
if (layer.type !== 'fill') continue;
const id = layer.id; const id = layer.id;
if (id.startsWith('bathymetry-')) continue;
if (id.startsWith('subcables-')) continue;
const sourceLayer = String((layer as Record<string, unknown>)['source-layer'] ?? ''); const sourceLayer = String((layer as Record<string, unknown>)['source-layer'] ?? '');
if (!landRegex.test(id) && !landRegex.test(sourceLayer)) continue; const isWater = waterRegex.test(id) || waterRegex.test(sourceLayer);
if (isWater) continue;
try { try {
map.setPaintProperty(id, 'fill-color', color); if (layer.type === 'background') {
map.setPaintProperty(id, 'background-color', color);
} else if (layer.type === 'fill') {
map.setPaintProperty(id, 'fill-color', darkVariant);
}
} catch { } catch {
// ignore // ignore
} }