diff --git a/.yarn-offline-cache/@repeaterjs-repeater-3.0.6.tgz b/.yarn-offline-cache/@repeaterjs-repeater-3.0.6.tgz new file mode 100644 index 00000000..59ffd273 Binary files /dev/null and b/.yarn-offline-cache/@repeaterjs-repeater-3.0.6.tgz differ diff --git a/.yarn-offline-cache/@types-rbush-4.0.0.tgz b/.yarn-offline-cache/@types-rbush-4.0.0.tgz new file mode 100644 index 00000000..29865e80 Binary files /dev/null and b/.yarn-offline-cache/@types-rbush-4.0.0.tgz differ diff --git a/.yarn-offline-cache/@zarrita-storage-0.1.4.tgz b/.yarn-offline-cache/@zarrita-storage-0.1.4.tgz new file mode 100644 index 00000000..73114695 Binary files /dev/null and b/.yarn-offline-cache/@zarrita-storage-0.1.4.tgz differ diff --git a/.yarn-offline-cache/earcut-3.0.2.tgz b/.yarn-offline-cache/earcut-3.0.2.tgz new file mode 100644 index 00000000..f2a2a492 Binary files /dev/null and b/.yarn-offline-cache/earcut-3.0.2.tgz differ diff --git a/.yarn-offline-cache/fflate-0.8.2.tgz b/.yarn-offline-cache/fflate-0.8.2.tgz new file mode 100644 index 00000000..50e3b909 Binary files /dev/null and b/.yarn-offline-cache/fflate-0.8.2.tgz differ diff --git a/.yarn-offline-cache/flatbuffers-25.9.23.tgz b/.yarn-offline-cache/flatbuffers-25.9.23.tgz new file mode 100644 index 00000000..b0f27a1c Binary files /dev/null and b/.yarn-offline-cache/flatbuffers-25.9.23.tgz differ diff --git a/.yarn-offline-cache/flatgeobuf-4.4.0.tgz b/.yarn-offline-cache/flatgeobuf-4.4.0.tgz new file mode 100644 index 00000000..f3cd48c4 Binary files /dev/null and b/.yarn-offline-cache/flatgeobuf-4.4.0.tgz differ diff --git a/.yarn-offline-cache/geotiff-3.0.2.tgz b/.yarn-offline-cache/geotiff-3.0.2.tgz new file mode 100644 index 00000000..1e53dc13 Binary files /dev/null and b/.yarn-offline-cache/geotiff-3.0.2.tgz differ diff --git a/.yarn-offline-cache/numcodecs-0.3.2.tgz b/.yarn-offline-cache/numcodecs-0.3.2.tgz new file mode 100644 index 00000000..54d120d3 Binary files /dev/null and b/.yarn-offline-cache/numcodecs-0.3.2.tgz differ diff --git a/.yarn-offline-cache/ol-10.8.0.tgz b/.yarn-offline-cache/ol-10.8.0.tgz new file mode 100644 index 00000000..511286e0 Binary files /dev/null and b/.yarn-offline-cache/ol-10.8.0.tgz differ diff --git a/.yarn-offline-cache/pbf-4.0.1.tgz b/.yarn-offline-cache/pbf-4.0.1.tgz new file mode 100644 index 00000000..11feb07a Binary files /dev/null and b/.yarn-offline-cache/pbf-4.0.1.tgz differ diff --git a/.yarn-offline-cache/quickselect-3.0.0.tgz b/.yarn-offline-cache/quickselect-3.0.0.tgz new file mode 100644 index 00000000..933adc7b Binary files /dev/null and b/.yarn-offline-cache/quickselect-3.0.0.tgz differ diff --git a/.yarn-offline-cache/rbush-4.0.1.tgz b/.yarn-offline-cache/rbush-4.0.1.tgz new file mode 100644 index 00000000..eaea395f Binary files /dev/null and b/.yarn-offline-cache/rbush-4.0.1.tgz differ diff --git a/.yarn-offline-cache/reference-spec-reader-0.2.0.tgz b/.yarn-offline-cache/reference-spec-reader-0.2.0.tgz new file mode 100644 index 00000000..8340541d Binary files /dev/null and b/.yarn-offline-cache/reference-spec-reader-0.2.0.tgz differ diff --git a/.yarn-offline-cache/slice-source-0.4.1.tgz b/.yarn-offline-cache/slice-source-0.4.1.tgz new file mode 100644 index 00000000..33f3c589 Binary files /dev/null and b/.yarn-offline-cache/slice-source-0.4.1.tgz differ diff --git a/.yarn-offline-cache/unzipit-1.4.3.tgz b/.yarn-offline-cache/unzipit-1.4.3.tgz new file mode 100644 index 00000000..eacbcf6d Binary files /dev/null and b/.yarn-offline-cache/unzipit-1.4.3.tgz differ diff --git a/.yarn-offline-cache/uzip-module-1.0.3.tgz b/.yarn-offline-cache/uzip-module-1.0.3.tgz new file mode 100644 index 00000000..d69d8d3d Binary files /dev/null and b/.yarn-offline-cache/uzip-module-1.0.3.tgz differ diff --git a/.yarn-offline-cache/zarrita-0.6.1.tgz b/.yarn-offline-cache/zarrita-0.6.1.tgz new file mode 100644 index 00000000..9ce53f3a Binary files /dev/null and b/.yarn-offline-cache/zarrita-0.6.1.tgz differ diff --git a/.yarn-offline-cache/zstddec-0.2.0.tgz b/.yarn-offline-cache/zstddec-0.2.0.tgz new file mode 100644 index 00000000..69000063 Binary files /dev/null and b/.yarn-offline-cache/zstddec-0.2.0.tgz differ diff --git a/package.json b/package.json index ef7b3b1e..f07f7ddf 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@stomp/stompjs": "^7.2.1", "axios": "^1.4.0", "dayjs": "^1.11.11", + "flatgeobuf": "^4.4.0", "html2canvas": "^1.4.1", "ol": "^9.2.4", "ol-ext": "^4.0.10", diff --git a/public/fgb/해경관할구역.fgb b/public/fgb/해경관할구역.fgb new file mode 100644 index 00000000..379f4a08 Binary files /dev/null and b/public/fgb/해경관할구역.fgb differ diff --git a/src/api/userSettingApi.js b/src/api/userSettingApi.js new file mode 100644 index 00000000..b6118b5a --- /dev/null +++ b/src/api/userSettingApi.js @@ -0,0 +1,32 @@ +import { fetchWithAuth } from './fetchWithAuth'; +import { USER_SETTING_FILTER } from '../types/constants'; + +const SEARCH_ENDPOINT = '/api/cmn/personal/settings/search'; +const SAVE_ENDPOINT = '/api/cmn/personal/settings/save'; + +/** + * 필터 설정 조회 + * @returns {Promise} 설정 배열 또는 null (저장된 설정 없음) + */ +export async function fetchUserFilter() { + const url = `${SEARCH_ENDPOINT}?type=${USER_SETTING_FILTER}`; + const response = await fetchWithAuth(url); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const result = await response.json(); + if (result?.code === 4006) return null; + return result?.data?.[USER_SETTING_FILTER] || null; +} + +/** + * 필터 설정 저장 + * @param {Array<{code: string, value: string}>} settings + */ +export async function saveUserFilter(settings) { + const response = await fetchWithAuth(SAVE_ENDPOINT, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ settings }), + }); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + return response.json(); +} diff --git a/src/component/wrap/side/DisplayComponent.jsx b/src/component/wrap/side/DisplayComponent.jsx index e513b476..cd6d65d4 100644 --- a/src/component/wrap/side/DisplayComponent.jsx +++ b/src/component/wrap/side/DisplayComponent.jsx @@ -3,6 +3,9 @@ import { Link, useNavigate } from "react-router-dom"; import Slider from '../../common/Slider'; import useShipStore from '../../../stores/shipStore'; import { useMapStore, BASE_MAP_TYPES } from '../../../stores/mapStore'; +import { saveUserFilter } from '../../../api/userSettingApi'; +import { showToast } from '../../../components/common/Toast'; +import useFavoriteStore from '../../../stores/favoriteStore'; import { SIGNAL_SOURCE_CODE_AIS, SIGNAL_SOURCE_CODE_ENAV, @@ -25,17 +28,17 @@ import { NATIONAL_CODE_OTHER, } from '../../../types/constants'; -// 신호원 필터 매핑 +// 신호원 필터 매핑 (메인 프로젝트와 동일 순서) const SIGNAL_FILTERS = [ { code: SIGNAL_SOURCE_CODE_AIS, label: 'AIS' }, - { code: SIGNAL_SOURCE_CODE_ENAV, label: 'E-NAV' }, { code: SIGNAL_SOURCE_CODE_VPASS, label: 'V-PASS' }, + { code: SIGNAL_SOURCE_CODE_ENAV, label: 'E-NAV' }, { code: SIGNAL_SOURCE_CODE_VTS_AIS, label: 'VTS_AIS' }, { code: SIGNAL_SOURCE_CODE_D_MF_HF, label: 'D_MF_HF' }, { code: SIGNAL_SOURCE_CODE_RADAR, label: 'VTS_RADAR' }, ]; -// 선종 필터 매핑 +// 선종 필터 매핑 (메인 프로젝트와 동일 순서) const KIND_FILTERS = [ { code: SIGNAL_KIND_CODE_FISHING, label: '어선' }, { code: SIGNAL_KIND_CODE_PASSENGER, label: '여객선' }, @@ -43,8 +46,8 @@ const KIND_FILTERS = [ { code: SIGNAL_KIND_CODE_TANKER, label: '유조선' }, { code: SIGNAL_KIND_CODE_GOV, label: '관공선' }, { code: SIGNAL_KIND_CODE_KCGV, label: '함정' }, - { code: SIGNAL_KIND_CODE_NORMAL, label: '기타' }, { code: SIGNAL_KIND_CODE_BUOY, label: '어망/부이' }, + { code: SIGNAL_KIND_CODE_NORMAL, label: '기타' }, ]; // 국적 필터 매핑 @@ -70,20 +73,35 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte nationalVisibility, darkSignalVisible, darkSignalCount, + aiModeVisibility, + hazardVisible, toggleSourceVisibility, toggleKindVisibility, toggleNationalVisibility, toggleDarkSignalVisible, + toggleAiModeEnabled, + toggleAiModeVisibility, + toggleHazardVisible, clearDarkSignals, } = useShipStore(); + // 관심선박/관심구역 스토어 연결 + const isFavoriteEnabled = useFavoriteStore((s) => s.isFavoriteEnabled); + const toggleFavoriteEnabled = useFavoriteStore((s) => s.toggleFavoriteEnabled); + const isRealmVisible = useFavoriteStore((s) => s.isRealmVisible); + const toggleRealmVisible = useFavoriteStore((s) => s.toggleRealmVisible); + + // 해경관할구역 + const isCoastGuardVisible = useMapStore((s) => s.isCoastGuardVisible); + const toggleCoastGuard = useMapStore((s) => s.toggleCoastGuard); + // 투명도 const [opacity, setOpacity] = useState(70); // 아코디언 - const [isAccordionOpen1, setIsAccordionOpen1] = useState(true); // 신호 - const [isAccordionOpen2, setIsAccordionOpen2] = useState(true); // 선종 - const [isAccordionOpen3, setIsAccordionOpen3] = useState(true); // 국적 + const [isAccordionOpen1, setIsAccordionOpen1] = useState(true); // 선종 + const [isAccordionOpen2, setIsAccordionOpen2] = useState(true); // 국적 + const [isAccordionOpen3, setIsAccordionOpen3] = useState(true); // 신호 const [isAccordionOpen4, setIsAccordionOpen4] = useState(false); // AI 모드 const toggleAccordion1 = () => setIsAccordionOpen1(prev => !prev); @@ -129,6 +147,21 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte }); }, [isAllNationalsOn, nationalVisibility, toggleNationalVisibility]); + // AI 모드 전체 On/Off (선종/국적/신호와 동일 패턴) + const isAllAiModeOn = Object.values(aiModeVisibility).every(v => v); + + // 필터 저장 + const handleSaveFilter = useCallback(async () => { + try { + const settings = useShipStore.getState().buildFilterSettings(); + await saveUserFilter(settings); + showToast('필터 설정이 저장되었습니다.'); + } catch (err) { + console.error('[Filter] 저장 실패:', err); + showToast('필터 저장에 실패했습니다.'); + } + }, []); + // 탭이동 (좌측 메뉴와 동기화) const [activeTab, setActiveTab] = useState(initialTab); @@ -165,51 +198,7 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
- {/* 스위치그룹 01 - 신호 */} -
-
-
- 신호 - -
- -
- {/* 여기서부터 토글 */} -
-
    - {SIGNAL_FILTERS.map(({ code, label }) => ( -
  • - {label} - -
  • - ))} -
-
- {/* 여기까지 */} -
- - {/* 스위치그룹 02 - 선종 */} + {/* 스위치그룹 01 - 선종 (메인 프로젝트와 동일 순서) */}
@@ -226,13 +215,13 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
{/* 여기서부터 토글 */} -
+
    {KIND_FILTERS.map(({ code, label }) => (
  • @@ -253,7 +242,7 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte {/* 여기까지 */}
- {/* 스위치그룹 03 - 국적 */} + {/* 스위치그룹 02 - 국적 */}
@@ -270,13 +259,13 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
{/* 여기서부터 토글 */} -
+
    {NATIONAL_FILTERS.map(({ code, label }) => (
  • @@ -296,12 +285,59 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
{/* 여기까지 */}
- {/* 스위치그룹 04 */} + + {/* 스위치그룹 03 - 신호종류 */} +
+
+
+ 신호 + +
+ +
+ {/* 여기서부터 토글 */} +
+
    + {SIGNAL_FILTERS.map(({ code, label }) => ( +
  • + {label} + +
  • + ))} +
+
+ {/* 여기까지 */} +
+ {/* 스위치그룹 04 - AI 모드 */}
AI 모드 - +
@@ -371,13 +425,16 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
- {/* 스위치그룹 06 */} + {/* 스위치그룹 06 - 위험물 */}
위험물
- +
@@ -388,14 +445,14 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte 관심선박
- +
{/* 버튼영역 */}
- +
@@ -466,7 +523,13 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
  • +
  • +
  • +
  • diff --git a/src/hooks/useCoastGuardLayer.js b/src/hooks/useCoastGuardLayer.js new file mode 100644 index 00000000..9510d5f5 --- /dev/null +++ b/src/hooks/useCoastGuardLayer.js @@ -0,0 +1,160 @@ +import { useEffect, useRef } from 'react'; +import VectorImageLayer from 'ol/layer/VectorImage'; +import VectorSource from 'ol/source/Vector'; +import GeoJSON from 'ol/format/GeoJSON'; +import { Style, Fill, Stroke, Text } from 'ol/style'; +import { deserialize } from 'flatgeobuf/lib/mjs/geojson.js'; +import { useMapStore, THEME_TYPES } from '../stores/mapStore'; + +const BASE_URL = import.meta.env.BASE_URL || '/'; +const FGB_URL = `${BASE_URL}fgb/해경관할구역.fgb`; + +/** 테마별 색상 정의 */ +const THEME_STYLE = { + [THEME_TYPES.DARK]: { + lineColor: 'rgba(100, 200, 255, 0.8)', + textColor: 'rgba(100, 200, 255, 0.9)', + textStrokeColor: 'rgba(0, 0, 0, 0.6)', + textStrokeWidth: 1, + font: 'bold 1.1rem "NanumSquare", sans-serif', + }, + [THEME_TYPES.LIGHT]: { + lineColor: 'rgba(20, 60, 100, 0.7)', + textColor: 'rgba(20, 60, 100, 0.8)', + textStrokeColor: 'rgba(255, 255, 255, 0.7)', + textStrokeWidth: 1, + font: 'bold 1.1rem "NanumSquare", sans-serif', + }, +}; + +/** + * 해경관할구역 스타일 팩토리 + * 테마에 따라 스타일 함수를 생성 + */ +function createKcgAreaStyle(theme) { + const ts = THEME_STYLE[theme] || THEME_STYLE[THEME_TYPES.DARK]; + + return (feature) => { + const areaName = feature.get('해역명'); + const isSpecial = areaName != null && areaName.includes('특별'); + + if (isSpecial) { + return [ + new Style({ + stroke: new Stroke({ + color: 'rgba(255, 80, 80, 0.8)', + lineDash: [5, 5], + width: 2, + }), + fill: new Fill({ color: 'rgba(255,255,255,0)' }), + text: new Text({ + offsetY: -15, + text: areaName || '', + font: ts.font, + fill: new Fill({ color: 'rgba(255, 80, 80, 0.9)' }), + stroke: new Stroke({ color: ts.textStrokeColor, width: ts.textStrokeWidth }), + }), + zIndex: 999, + }), + ]; + } + + return [ + new Style({ + stroke: new Stroke({ color: ts.lineColor, width: 2 }), + fill: new Fill({ color: 'rgba(255,255,255,0)' }), + text: new Text({ + offsetY: -15, + text: areaName || '', + font: ts.font, + fill: new Fill({ color: ts.textColor }), + stroke: new Stroke({ color: ts.textStrokeColor, width: ts.textStrokeWidth }), + }), + }), + ]; + }; +} + +/** + * 해경관할구역 FGB 레이어 관리 훅 + * 참조: mda-react-front/src/common/targetLayer.ts - kcgWatchZoneLayer, setFGBFeatures + */ +export default function useCoastGuardLayer() { + const map = useMapStore((s) => s.map); + const layerRef = useRef(null); + const loadedRef = useRef(false); + + useEffect(() => { + if (!map) return; + + const currentTheme = useMapStore.getState().getTheme(); + const source = new VectorSource(); + const layer = new VectorImageLayer({ + source, + zIndex: 45, + style: createKcgAreaStyle(currentTheme), + declutter: true, + visible: useMapStore.getState().isCoastGuardVisible, + }); + + map.addLayer(layer); + layerRef.current = layer; + + // FGB 파일 로드 (1회) + if (!loadedRef.current) { + loadedRef.current = true; + loadFgb(source); + } + + // visible 토글 구독 + const unsubVisible = useMapStore.subscribe( + (state) => state.isCoastGuardVisible, + (isVisible) => { + if (layerRef.current) { + layerRef.current.setVisible(isVisible); + } + }, + ); + + // 배경지도(테마) 변경 구독 → 스타일 재적용 + const unsubTheme = useMapStore.subscribe( + (state) => state.baseMapType, + () => { + if (layerRef.current) { + const theme = useMapStore.getState().getTheme(); + layerRef.current.setStyle(createKcgAreaStyle(theme)); + } + }, + ); + + return () => { + unsubVisible(); + unsubTheme(); + if (map && layerRef.current) { + map.removeLayer(layerRef.current); + } + layerRef.current = null; + }; + }, [map]); +} + +/** + * FlatGeobuf 파일 로드 → VectorSource에 Feature 추가 + */ +async function loadFgb(source) { + try { + const response = await fetch(FGB_URL); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + + const format = new GeoJSON(); + + for await (const geojsonFeature of deserialize(response.body)) { + const feature = format.readFeature(geojsonFeature); + source.addFeature(feature); + } + + console.log(`[useCoastGuardLayer] 해경관할구역 ${source.getFeatures().length}건 로드 완료`); + } catch (err) { + console.warn('[useCoastGuardLayer] FGB 로드 실패:', err); + } +} diff --git a/src/map/MapContainer.jsx b/src/map/MapContainer.jsx index a178bd4e..f17e5eb2 100644 --- a/src/map/MapContainer.jsx +++ b/src/map/MapContainer.jsx @@ -44,6 +44,9 @@ import AreaSearchTimeline from '../areaSearch/components/AreaSearchTimeline'; import AreaSearchTooltip from '../areaSearch/components/AreaSearchTooltip'; import useMeasure from './measure/useMeasure'; import useTrackingMode from '../hooks/useTrackingMode'; +import useFavoriteData from '../hooks/useFavoriteData'; +import useRealmLayer from '../hooks/useRealmLayer'; +import useCoastGuardLayer from '../hooks/useCoastGuardLayer'; import './measure/measure.scss'; import './MapContainer.scss'; @@ -71,6 +74,15 @@ export default function MapContainer() { // STOMP 선박 데이터 연결 useShipData({ autoConnect: true }); + // 관심선박 + 관심구역 데이터 로딩 + useFavoriteData(); + + // 관심구역 OL 레이어 + useRealmLayer(); + + // 해경관할구역 FGB 레이어 + useCoastGuardLayer(); + // Deck.gl 선박 레이어 const { deckRef } = useShipLayer(map); diff --git a/src/stores/mapStore.js b/src/stores/mapStore.js index 20cc6f04..e0373081 100644 --- a/src/stores/mapStore.js +++ b/src/stores/mapStore.js @@ -119,4 +119,8 @@ export const useMapStore = create(subscribeWithSelector((set, get) => ({ [layerName]: !state.layerVisibility[layerName], }, })), + + // 해경관할구역 레이어 + isCoastGuardVisible: true, + toggleCoastGuard: () => set((s) => ({ isCoastGuardVisible: !s.isCoastGuardVisible })), }))); diff --git a/src/stores/shipStore.js b/src/stores/shipStore.js index b62094eb..b9b5d5f9 100644 --- a/src/stores/shipStore.js +++ b/src/stores/shipStore.js @@ -27,6 +27,7 @@ import { NATIONAL_CODE_OTHER, SOURCE_PRIORITY_RANK, SOURCE_TO_ACTIVE_KEY, + USER_SETTING_CODES, } from '../types/constants'; // ===================== @@ -238,6 +239,19 @@ const useShipStore = create(subscribeWithSelector((set, get) => ({ /** 마지막 모달 위치 (새 모달 초기 위치 계산용) */ lastModalPos: null, + /** AI 모드 서브 토글 (메인 토글은 컴포넌트에서 every()로 파생 — 선종/국적/신호와 동일 패턴) */ + aiModeVisibility: { + mmsiChange: false, // MMSI 변조 + chinaPermission: false, // 중국 허가선박 + govShip: false, // 관공선 + sseZoneContact: false, // 비정상 접촉 + nonPermission: false, // 비정상 선박 + northKoreaAi: false, // 북한선박 + }, + + /** 위험물 표시 여부 */ + hazardVisible: false, + /** 다크시그널(소실신호) 표시 여부 */ darkSignalVisible: false, @@ -456,6 +470,33 @@ const useShipStore = create(subscribeWithSelector((set, get) => ({ /** * 다크시그널 표시 토글 */ + toggleAiModeEnabled: () => { + set((state) => { + const allOn = Object.values(state.aiModeVisibility).every(v => v); + const next = !allOn; + return { + aiModeVisibility: { + mmsiChange: next, + chinaPermission: next, + govShip: next, + sseZoneContact: next, + nonPermission: next, + northKoreaAi: next, + }, + }; + }); + }, + + toggleAiModeVisibility: (key) => { + set((state) => ({ + aiModeVisibility: { ...state.aiModeVisibility, [key]: !state.aiModeVisibility[key] }, + })); + }, + + toggleHazardVisible: () => { + set((state) => ({ hazardVisible: !state.hazardVisible })); + }, + toggleDarkSignalVisible: () => { set((state) => ({ darkSignalVisible: !state.darkSignalVisible, @@ -744,6 +785,103 @@ const useShipStore = create(subscribeWithSelector((set, get) => ({ set((state) => ({ showLegend: !state.showLegend })); }, + /** + * 서버에서 불러온 필터 설정 배열을 스토어에 적용 + * 참조: mda-react-front/src/common/userSetting.ts + * @param {Array<{settingCode: string, settingValue: string}>} filterArray + */ + applyFilterSettings: (filterArray) => { + if (!Array.isArray(filterArray) || filterArray.length === 0) return; + + const toBoolean = (item) => item?.settingValue === 'true'; + + // settingCode → settingValue 맵 생성 + const map = {}; + filterArray.forEach((item) => { + if (item?.settingCode) map[item.settingCode] = item; + }); + + set({ + kindVisibility: { + [SIGNAL_KIND_CODE_FISHING]: toBoolean(map[USER_SETTING_CODES.FISHING]), + [SIGNAL_KIND_CODE_PASSENGER]: toBoolean(map[USER_SETTING_CODES.PASS]), + [SIGNAL_KIND_CODE_CARGO]: toBoolean(map[USER_SETTING_CODES.CARGO]), + [SIGNAL_KIND_CODE_TANKER]: toBoolean(map[USER_SETTING_CODES.TANKER]), + [SIGNAL_KIND_CODE_GOV]: toBoolean(map[USER_SETTING_CODES.GOV]), + [SIGNAL_KIND_CODE_KCGV]: toBoolean(map[USER_SETTING_CODES.KCGV]), + [SIGNAL_KIND_CODE_NORMAL]: toBoolean(map[USER_SETTING_CODES.NORMAL]), + [SIGNAL_KIND_CODE_BUOY]: toBoolean(map[USER_SETTING_CODES.BUOY]), + }, + nationalVisibility: { + [NATIONAL_CODE_KR]: toBoolean(map[USER_SETTING_CODES.KOREA]), + [NATIONAL_CODE_CN]: toBoolean(map[USER_SETTING_CODES.CHINA]), + [NATIONAL_CODE_JP]: toBoolean(map[USER_SETTING_CODES.JAPAN]), + [NATIONAL_CODE_KP]: toBoolean(map[USER_SETTING_CODES.NORTH_KOREA]), + [NATIONAL_CODE_OTHER]: toBoolean(map[USER_SETTING_CODES.ETC_NATION]), + }, + sourceVisibility: { + [SIGNAL_SOURCE_CODE_AIS]: toBoolean(map[USER_SETTING_CODES.AIS]), + [SIGNAL_SOURCE_CODE_VPASS]: toBoolean(map[USER_SETTING_CODES.VPASS]), + [SIGNAL_SOURCE_CODE_ENAV]: toBoolean(map[USER_SETTING_CODES.ENAV]), + [SIGNAL_SOURCE_CODE_VTS_AIS]: toBoolean(map[USER_SETTING_CODES.VTS_AIS]), + [SIGNAL_SOURCE_CODE_D_MF_HF]: toBoolean(map[USER_SETTING_CODES.D_MF_HF]), + [SIGNAL_SOURCE_CODE_RADAR]: toBoolean(map[USER_SETTING_CODES.RADAR]), + }, + darkSignalVisible: toBoolean(map[USER_SETTING_CODES.LOST_SIGNAL]), + // AI 모드 (메인 토글은 하위 토글에서 파생 — 서버에 000039가 없을 수 있음) + aiModeVisibility: { + mmsiChange: toBoolean(map[USER_SETTING_CODES.MMSI_CHANGE]), + chinaPermission: toBoolean(map[USER_SETTING_CODES.CHINA_PERMISSION]), + govShip: toBoolean(map[USER_SETTING_CODES.GOV_SHIP]), + sseZoneContact: toBoolean(map[USER_SETTING_CODES.SSE_ZONE_CONTACT]), + nonPermission: toBoolean(map[USER_SETTING_CODES.NON_PERMISSION]), + northKoreaAi: toBoolean(map[USER_SETTING_CODES.NORTH_KOREA_AI]), + }, + // 위험물 + hazardVisible: toBoolean(map[USER_SETTING_CODES.HAZARD]), + }); + }, + + /** + * 현재 필터 상태를 서버 저장 형식으로 직렬화 + * @returns {Array<{code: string, value: string}>} + */ + buildFilterSettings: () => { + const { kindVisibility, sourceVisibility, nationalVisibility, darkSignalVisible, aiModeVisibility, hazardVisible } = get(); + return [ + { code: USER_SETTING_CODES.FISHING, value: String(!!kindVisibility[SIGNAL_KIND_CODE_FISHING]) }, + { code: USER_SETTING_CODES.PASS, value: String(!!kindVisibility[SIGNAL_KIND_CODE_PASSENGER]) }, + { code: USER_SETTING_CODES.CARGO, value: String(!!kindVisibility[SIGNAL_KIND_CODE_CARGO]) }, + { code: USER_SETTING_CODES.TANKER, value: String(!!kindVisibility[SIGNAL_KIND_CODE_TANKER]) }, + { code: USER_SETTING_CODES.GOV, value: String(!!kindVisibility[SIGNAL_KIND_CODE_GOV]) }, + { code: USER_SETTING_CODES.KCGV, value: String(!!kindVisibility[SIGNAL_KIND_CODE_KCGV]) }, + { code: USER_SETTING_CODES.NORMAL, value: String(!!kindVisibility[SIGNAL_KIND_CODE_NORMAL]) }, + { code: USER_SETTING_CODES.BUOY, value: String(!!kindVisibility[SIGNAL_KIND_CODE_BUOY]) }, + { code: USER_SETTING_CODES.KOREA, value: String(!!nationalVisibility[NATIONAL_CODE_KR]) }, + { code: USER_SETTING_CODES.CHINA, value: String(!!nationalVisibility[NATIONAL_CODE_CN]) }, + { code: USER_SETTING_CODES.JAPAN, value: String(!!nationalVisibility[NATIONAL_CODE_JP]) }, + { code: USER_SETTING_CODES.NORTH_KOREA, value: String(!!nationalVisibility[NATIONAL_CODE_KP]) }, + { code: USER_SETTING_CODES.ETC_NATION, value: String(!!nationalVisibility[NATIONAL_CODE_OTHER]) }, + { code: USER_SETTING_CODES.AIS, value: String(!!sourceVisibility[SIGNAL_SOURCE_CODE_AIS]) }, + { code: USER_SETTING_CODES.VPASS, value: String(!!sourceVisibility[SIGNAL_SOURCE_CODE_VPASS]) }, + { code: USER_SETTING_CODES.ENAV, value: String(!!sourceVisibility[SIGNAL_SOURCE_CODE_ENAV]) }, + { code: USER_SETTING_CODES.VTS_AIS, value: String(!!sourceVisibility[SIGNAL_SOURCE_CODE_VTS_AIS]) }, + { code: USER_SETTING_CODES.D_MF_HF, value: String(!!sourceVisibility[SIGNAL_SOURCE_CODE_D_MF_HF]) }, + { code: USER_SETTING_CODES.RADAR, value: String(!!sourceVisibility[SIGNAL_SOURCE_CODE_RADAR]) }, + { code: USER_SETTING_CODES.LOST_SIGNAL, value: String(!!darkSignalVisible) }, + // AI 모드 + { code: USER_SETTING_CODES.AI, value: String(Object.values(aiModeVisibility).every(v => v)) }, + { code: USER_SETTING_CODES.MMSI_CHANGE, value: String(!!aiModeVisibility.mmsiChange) }, + { code: USER_SETTING_CODES.CHINA_PERMISSION, value: String(!!aiModeVisibility.chinaPermission) }, + { code: USER_SETTING_CODES.GOV_SHIP, value: String(!!aiModeVisibility.govShip) }, + { code: USER_SETTING_CODES.SSE_ZONE_CONTACT, value: String(!!aiModeVisibility.sseZoneContact) }, + { code: USER_SETTING_CODES.NON_PERMISSION, value: String(!!aiModeVisibility.nonPermission) }, + { code: USER_SETTING_CODES.NORTH_KOREA_AI, value: String(!!aiModeVisibility.northKoreaAi) }, + // 위험물 + { code: USER_SETTING_CODES.HAZARD, value: String(!!hazardVisible) }, + ]; + }, + /** * 모든 선박 데이터 초기화 */ diff --git a/src/types/constants.js b/src/types/constants.js index d33f4087..60f89221 100644 --- a/src/types/constants.js +++ b/src/types/constants.js @@ -331,3 +331,49 @@ export const SIGNAL_SOURCE_LIST = [ // ===================== export const TRACK_QUERY_MAX_DAYS = 7; // 최대 조회기간 (일) export const TRACK_QUERY_DEFAULT_DAYS = 3; // 기본 조회기간 (일) + +// ===================== +// 세션 관리 (메인 프로젝트 동일) +// ===================== +export const SESSION_TIMEOUT_MS = 14400000; // 4시간 +export const KCGV_GROUP_IDS = ['2', '18']; // 함정용 사용자 그룹 + +// ===================== +// 개인설정 API 타입 코드 +// 참조: mda-react-front/src/types/constants.ts (648-695) +// ===================== +export const USER_SETTING_FILTER = '000001'; + +// 필터 개별 설정 코드 (저장용 배열 인덱스 → 코드) +export const USER_SETTING_CODES = { + FISHING: '000001', + PASS: '000002', + CARGO: '000003', + TANKER: '000004', + GOV: '000005', + KCGV: '000006', + NORMAL: '000008', + KOREA: '000009', + CHINA: '000010', + JAPAN: '000011', + NORTH_KOREA: '000012', + ETC_NATION: '000013', + AIS: '000014', + VPASS: '000015', + ENAV: '000016', + VTS_AIS: '000017', + D_MF_HF: '000018', + RADAR: '000019', + LOST_SIGNAL: '000026', + BUOY: '000028', + // AI 모드 설정 코드 (참조: mda-react-front/src/types/constants.ts) + AI: '000039', // AI 모드 전체 토글 + MMSI_CHANGE: '000020', // MMSI 변조 + CHINA_PERMISSION: '000021', // 중국 허가선박 + GOV_SHIP: '000022', // 관공선 + SSE_ZONE_CONTACT: '000023', // 비정상 접촉 + NON_PERMISSION: '000024', // 비정상 선박 + NORTH_KOREA_AI: '000077', // 북한선박 + // 위험물 + HAZARD: '000027', +}; diff --git a/yarn.lock b/yarn.lock index bf50bf06..f3508fef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -908,6 +908,11 @@ resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.23.2.tgz#156c4b481c0bee22a19f7924728a67120de06971" integrity sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w== +"@repeaterjs/repeater@3.0.6": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@repeaterjs/repeater/-/repeater-3.0.6.tgz#be23df0143ceec3c69f8b6c2517971a5578fdaa2" + integrity sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA== + "@rolldown/pluginutils@1.0.0-beta.27": version "1.0.0-beta.27" resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz#47d2bf4cef6d470b22f5831b420f8964e0bf755f" @@ -1160,6 +1165,11 @@ resolved "https://registry.yarnpkg.com/@types/pako/-/pako-1.0.7.tgz#aa0e4af9855d81153a29ff84cc44cce25298eda9" integrity sha512-YBtzT2ztNF6R/9+UXj2wTGFnC9NklAnASt3sC0h2m1bbH7G6FyBIkt4AN8ThZpNfxUo1b2iMVO0UawiJymEt8A== +"@types/rbush@4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/rbush/-/rbush-4.0.0.tgz#b327bf54952e9c924ea6702c36904c2ce1d47f35" + integrity sha512-+N+2H39P8X+Hy1I5mC6awlTX54k3FhiUmvt7HWzGJZvF+syUAAxP/stwppS8JE84YHqFgRMv6fCy31202CMFxQ== + "@ungap/structured-clone@^1.2.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" @@ -1177,6 +1187,14 @@ "@types/babel__core" "^7.20.5" react-refresh "^0.17.0" +"@zarrita/storage@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@zarrita/storage/-/storage-0.1.4.tgz#05d7d1d43fc0163d22a17356b619ffeb1ed223d7" + integrity sha512-qURfJAQcQGRfDQ4J9HaCjGaj3jlJKc66bnRk6G/IeLUsM7WKyG7Bzsuf1EZurSXyc0I4LVcu6HaeQQ4d3kZ16g== + dependencies: + reference-spec-reader "^0.2.0" + unzipit "1.4.3" + a5-js@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/a5-js/-/a5-js-0.5.0.tgz#b0241651efdf573229d6f8e25243be31cd0b9451" @@ -1646,6 +1664,11 @@ earcut@^2.2.3, earcut@^2.2.4: resolved "https://registry.yarnpkg.com/earcut/-/earcut-2.2.4.tgz#6d02fd4d68160c114825d06890a92ecaae60343a" integrity sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ== +earcut@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/earcut/-/earcut-3.0.2.tgz#d478a29aaf99acf418151493048aa197d0512248" + integrity sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ== + electron-to-chromium@^1.5.263: version "1.5.286" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz#142be1ab5e1cd5044954db0e5898f60a4960384e" @@ -1985,6 +2008,11 @@ fflate@0.7.4: resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.7.4.tgz#61587e5d958fdabb5a9368a302c25363f4f69f50" integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw== +fflate@^0.8.0: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -2009,6 +2037,22 @@ flat-cache@^3.0.4: keyv "^4.5.3" rimraf "^3.0.2" +flatbuffers@25.9.23: + version "25.9.23" + resolved "https://registry.yarnpkg.com/flatbuffers/-/flatbuffers-25.9.23.tgz#346811557fe9312ab5647535e793c761e9c81eb1" + integrity sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ== + +flatgeobuf@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/flatgeobuf/-/flatgeobuf-4.4.0.tgz#f2067f359ed35a5bd6a19e0cde1a3cd2d16d6c37" + integrity sha512-uUt1xxywP+q8K73MmyKtapF4++dMCzvoqH+ojBTsCtZBbnQEg5qy0Ujze61Rwmpmt6Ra526jpRFHtEkFun5YVw== + dependencies: + "@repeaterjs/repeater" "3.0.6" + flatbuffers "25.9.23" + slice-source "0.4.1" + optionalDependencies: + ol ">=10" + flatted@^3.2.9: version "3.3.3" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" @@ -2093,6 +2137,20 @@ geotiff@^2.0.7: xml-utils "^1.0.2" zstddec "^0.1.0" +geotiff@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/geotiff/-/geotiff-3.0.2.tgz#f3ce7d9a6c9278b8b4e1638ffe10a08d507c8255" + integrity sha512-KZ+0YK8gW9HWitovPhfHvkyd1gsyXtY9oOrS/OSX+12M8ojAm+NJ6Vl3tUjp7ZMcPO7e7pJoqpWoMdzO0rF8IQ== + dependencies: + "@petamoriken/float16" "^3.4.7" + lerc "^3.0.0" + pako "^2.0.4" + parse-headers "^2.0.2" + quick-lru "^6.1.1" + web-worker "^1.5.0" + xml-utils "^1.10.2" + zstddec "^0.2.0" + get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" @@ -2714,6 +2772,13 @@ node-releases@^2.0.27: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== +numcodecs@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/numcodecs/-/numcodecs-0.3.2.tgz#09887cfc2a3ae1c59a495c01a7f0528118d85dcd" + integrity sha512-6YSPnmZgg0P87jnNhi3s+FVLOcIn3y+1CTIgUulA3IdASzK9fJM87sUFkpyA+be9GibGRaST2wCgkD+6U+fWKw== + dependencies: + fflate "^0.8.0" + object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -2776,6 +2841,18 @@ ol-ext@^4.0.10: resolved "https://registry.yarnpkg.com/ol-ext/-/ol-ext-4.0.37.tgz#8da5c4097322e56f99b45537ca353c242d1c9b88" integrity sha512-RxzdgMWnNBDP9VZCza3oS3rl1+OCl+1SJLMjt7ATyDDLZl/zzrsQELfJ25WAL6HIWgjkQ2vYDh3nnHFupxOH4w== +ol@>=10: + version "10.8.0" + resolved "https://registry.yarnpkg.com/ol/-/ol-10.8.0.tgz#fe528cd93f13e673e309435f577076e644653aa3" + integrity sha512-kLk7jIlJvKyhVMAjORTXKjzlM6YIByZ1H/d0DBx3oq8nSPCG6/gbLr5RxukzPgwbhnAqh+xHNCmrvmFKhVMvoQ== + dependencies: + "@types/rbush" "4.0.0" + earcut "^3.0.0" + geotiff "^3.0.2" + pbf "4.0.1" + rbush "^4.0.0" + zarrita "^0.6.0" + ol@^9.2.4: version "9.2.4" resolved "https://registry.yarnpkg.com/ol/-/ol-9.2.4.tgz#07dcefdceb66ddbde13089bca136f4d4852b772b" @@ -2880,6 +2957,13 @@ pbf@3.2.1: ieee754 "^1.1.12" resolve-protobuf-schema "^2.1.0" +pbf@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pbf/-/pbf-4.0.1.tgz#ad9015e022b235dcdbe05fc468a9acadf483f0d4" + integrity sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA== + dependencies: + resolve-protobuf-schema "^2.1.0" + pbf@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/pbf/-/pbf-3.3.0.tgz#1790f3d99118333cc7f498de816028a346ef367f" @@ -2966,6 +3050,11 @@ quickselect@^2.0.0: resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018" integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw== +quickselect@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-3.0.0.tgz#a37fc953867d56f095a20ac71c6d27063d2de603" + integrity sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g== + rbush@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/rbush/-/rbush-3.0.1.tgz#5fafa8a79b3b9afdfe5008403a720cc1de882ecf" @@ -2973,6 +3062,13 @@ rbush@^3.0.1: dependencies: quickselect "^2.0.0" +rbush@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/rbush/-/rbush-4.0.1.tgz#1f55afa64a978f71bf9e9a99bc14ff84f3cb0d6d" + integrity sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ== + dependencies: + quickselect "^3.0.0" + react-dom@^18.2.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" @@ -3031,6 +3127,11 @@ readdirp@^4.0.1: resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== +reference-spec-reader@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/reference-spec-reader/-/reference-spec-reader-0.2.0.tgz#52bd79614dde68e68f05c97a05ae04ff20acd7ec" + integrity sha512-q0mfCi5yZSSHXpCyxjgQeaORq3tvDsxDyzaadA/5+AbAUwRyRuuTh0aRQuE/vAOt/qzzxidJ5iDeu1cLHaNBlQ== + reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" @@ -3285,6 +3386,11 @@ side-channel@^1.1.0: side-channel-map "^1.0.1" side-channel-weakmap "^1.0.2" +slice-source@0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/slice-source/-/slice-source-0.4.1.tgz#40a57ac03c6668b5da200e05378e000bf2a61d79" + integrity sha512-YiuPbxpCj4hD9Qs06hGAz/OZhQ0eDuALN0lRWJez0eD/RevzKqGdUx1IOMUnXgpr+sXZLq3g8ERwbAH0bCb8vg== + snappyjs@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/snappyjs/-/snappyjs-0.6.1.tgz#9bca9ff8c54b133a9cc84a71d22779e97fc51878" @@ -3506,6 +3612,13 @@ undici-types@~7.16.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== +unzipit@1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/unzipit/-/unzipit-1.4.3.tgz#738298a6b235892bf7ce7db82cff813d4ca664ac" + integrity sha512-gsq2PdJIWWGhx5kcdWStvNWit9FVdTewm4SEG7gFskWs+XCVaULt9+BwuoBtJiRE8eo3L1IPAOrbByNLtLtIlg== + dependencies: + uzip-module "^1.0.2" + update-browserslist-db@^1.2.0: version "1.2.3" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" @@ -3546,6 +3659,11 @@ utrie@^1.0.2: dependencies: base64-arraybuffer "^1.0.2" +uzip-module@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/uzip-module/-/uzip-module-1.0.3.tgz#6bbabe2a3efea5d5a4a47479f523a571de3427ce" + integrity sha512-AMqwWZaknLM77G+VPYNZLEruMGWGzyigPK3/Whg99B3S6vGHuqsyl5ZrOv1UUF3paGK1U6PM0cnayioaryg/fA== + vite@^5.2.10: version "5.4.21" resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.21.tgz#84a4f7c5d860b071676d39ba513c0d598fdc7027" @@ -3557,7 +3675,7 @@ vite@^5.2.10: optionalDependencies: fsevents "~2.3.3" -web-worker@^1.2.0: +web-worker@^1.2.0, web-worker@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.5.0.tgz#71b2b0fbcc4293e8f0aa4f6b8a3ffebff733dcc5" integrity sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw== @@ -3651,7 +3769,7 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -xml-utils@^1.0.2: +xml-utils@^1.0.2, xml-utils@^1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/xml-utils/-/xml-utils-1.10.2.tgz#436b39ccc25a663ce367ea21abb717afdea5d6b1" integrity sha512-RqM+2o1RYs6T8+3DzDSoTRAUfrvaejbVHcp3+thnAtDKo8LskR+HomLajEy5UjTz24rpka7AxVBRR3g2wTUkJA== @@ -3666,6 +3784,14 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +zarrita@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/zarrita/-/zarrita-0.6.1.tgz#4e0d13e14c0ebcf70e08ed3bafd1d9a290651445" + integrity sha512-YOMTW8FT55Rz+vadTIZeOFZ/F2h4svKizyldvPtMYSxPgSNcRkOzkxCsWpIWlWzB1I/LmISmi0bEekOhLlI+Zw== + dependencies: + "@zarrita/storage" "^0.1.4" + numcodecs "^0.3.2" + zstd-codec@^0.1: version "0.1.5" resolved "https://registry.yarnpkg.com/zstd-codec/-/zstd-codec-0.1.5.tgz#c180193e4603ef74ddf704bcc835397d30a60e42" @@ -3676,6 +3802,11 @@ zstddec@^0.1.0: resolved "https://registry.yarnpkg.com/zstddec/-/zstddec-0.1.0.tgz#7050f3f0e0c3978562d0c566b3e5a427d2bad7ec" integrity sha512-w2NTI8+3l3eeltKAdK8QpiLo/flRAr2p8AGeakfMZOXBxOg9HIu4LVDxBi81sYgVhFhdJjv1OrB5ssI8uFPoLg== +zstddec@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/zstddec/-/zstddec-0.2.0.tgz#91c8cde8f351ef5fe0bdfca66bb14a5fa0d16d71" + integrity sha512-oyPnDa1X5c13+Y7mA/FDMNJrn4S8UNBe0KCqtDmor40Re7ALrPN6npFwyYVRRh+PqozZQdeg23QtbcamZnG5rA== + zustand@^4.5.2: version "4.5.7" resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.7.tgz#7d6bb2026a142415dd8be8891d7870e6dbe65f55"