static 폴더 및 하위 파일 업로드
@@ -0,0 +1,23 @@
|
|||||||
|
from flask import Flask
|
||||||
|
from routes.main_routes import main_bp
|
||||||
|
from routes.api_routes import api_bp
|
||||||
|
|
||||||
|
def create_app():
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# Register Blueprints
|
||||||
|
app.register_blueprint(main_bp)
|
||||||
|
app.register_blueprint(api_bp)
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = create_app()
|
||||||
|
|
||||||
|
# 로컬 실행 최적화
|
||||||
|
print("------------------------------------------")
|
||||||
|
print("drawNET Premium Server Starting...")
|
||||||
|
print("URL: http://127.0.0.1:5000")
|
||||||
|
print("------------------------------------------")
|
||||||
|
|
||||||
|
app.run(debug=True, port=5000)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"mousewheel": {
|
||||||
|
"factor": 1.05,
|
||||||
|
"minScale": 0.1,
|
||||||
|
"maxScale": 10.0
|
||||||
|
},
|
||||||
|
"log_level": "info"
|
||||||
|
}
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
# drawNET System Architecture: Native JSON-Object Topology (v1.0)
|
||||||
|
|
||||||
|
> **"Programs must be written for people to read, and only incidentally for machines to execute."** — *Harold Abelson*
|
||||||
|
> **"What is not documented, does not exist in the eyes of Engineering."** — *Software Engineering Proverb*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 개요 (Abstract)
|
||||||
|
|
||||||
|
drawNET은 단순한 다이어그램 그리기 도구를 넘어, 엔터프라이즈급 네트워크 토폴로지 설계 및 엔니지어링을 위한 **객체 지향적 시각화 프레임워크**입니다. 초기 DSL(Domain Specific Language) 기반의 텍스트 중심 구조에서 탈피하여, 고성능 그래프 엔진인 **AntV X6 v2**를 기반으로 한 **JSON-Native 아키텍처**로 진화했습니다. 본 시스템은 데이터의 영속성(Persistence), 정밀한 공간 쿼리(Spatial Query), 그리고 멀티레이어 논리 격리를 핵심 가치로 삼는 **v1.0 Premium Edition**입니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 설계 철학 (Design Philosophy)
|
||||||
|
|
||||||
|
### 2.1 Drawing에서 Engineering으로
|
||||||
|
기존 도구들이 시각적 표현에 집중했다면, drawNET은 **"데이터가 주도하는 시각화"**를 지향합니다. 모든 시각적 요소는 정규화된 데이터 모델의 하위 표현(View)에 불과하며, 핵심 로직은 UUID 기반의 객체 관계망에서 동작합니다.
|
||||||
|
|
||||||
|
### 2.2 하이브리드 인터페이스 (The Hybrid UI Strategy)
|
||||||
|
사용자에게는 직관적인 GUI(마우스 조작)를 제공하지만, 백그라운드에서는 모든 행위가 정형화된 JSON 스트림으로 변환되어 저장됩니다. 이는 향후 AI 기반의 토폴로지 자동 최적화 및 이상 탐지(Anomaly Detection)를 위한 학습 데이터셋 구축에 최적화된 구조입니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 핵심 아키텍처 모델 (Core Architecture Model)
|
||||||
|
|
||||||
|
drawNET은 **SSoT (Single Source of Truth)** 원칙에 따라 모든 상태를 단일 JSON 객체로 관리합니다.
|
||||||
|
|
||||||
|
### 3.1 논리 흐름 (Logical Workflow)
|
||||||
|
시스템의 데이터 처리는 다음과 같은 순환 구조를 따릅니다.
|
||||||
|
|
||||||
|
1. **Interaction Layer**: 사용자의 드래그, 단축키, 속성 변경을 수집합니다.
|
||||||
|
2. **Event Orchestration**: 객체 간 부모-자식 관계 변경(Embedding), 연결 상태 변경(Connecting) 이벤트를 감지하여 데이터 무결성을 검증합니다.
|
||||||
|
3. **Data Transformation**: 변경된 상태를 X6 셀(Cell)의 `data` 속성에 즉각 반영합니다.
|
||||||
|
4. **Persistence Layer**: 변경된 전체 그래프 상태를 `graph.toJSON()`으로 직렬화하여 영속 저장소(LocalStorage/Server)에 동기화합니다.
|
||||||
|
|
||||||
|
### 3.2 UUID 기반 객체 참조 (UUID-based Referencing)
|
||||||
|
모든 노드와 엣지는 시스템 전역에서 유일한 **UUID**를 식별자로 가집니다. 이는 **RFC4122 v4** 표준을 철저하게 준수하며, 라벨(Label)이나 명칭 변경 시에도 객체 간의 참조 무결성(Referential Integrity)이 깨지지 않도록 보장하며, 대규모 프로젝트에서 복잡한 계층 구조를 추적하는 핵심 메커니즘입니다.
|
||||||
|
|
||||||
|
### 3.3 시스템 구조도 (System Overview Diagram)
|
||||||
|
|
||||||
|
본 시스템의 모듈 간 상호작용과 데이터 흐름은 다음과 같습니다.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
User["사용자 (User)"]
|
||||||
|
X6["AntV X6 Graph Engine"]
|
||||||
|
Sidebar["속성 사이드바 (Sidebar)"]
|
||||||
|
Persistence["영속성 제어 (persistence.js)"]
|
||||||
|
State["Shared State (state.js)"]
|
||||||
|
LocalStorage["Local Storage / Server"]
|
||||||
|
|
||||||
|
User -- "UI 상호작용" --> X6
|
||||||
|
X6 -- "JSON Export" --> Persistence
|
||||||
|
Persistence -- "JSON Save" --> LocalStorage
|
||||||
|
LocalStorage -- "JSON Load" --> Persistence
|
||||||
|
Persistence -- "graph.fromJSON()" --> X6
|
||||||
|
|
||||||
|
X6 -- "객체 선택 (UUID)" --> Sidebar
|
||||||
|
Sidebar -- "데이터 수정 (setData)" --> X6
|
||||||
|
|
||||||
|
subgraph Frontend [Client Side]
|
||||||
|
X6
|
||||||
|
Sidebar
|
||||||
|
State
|
||||||
|
Persistence
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 멀티레이어 논리 격리 및 제어 (Multi-Layer & Logical Isolation)
|
||||||
|
|
||||||
|
drawNET은 단순히 선을 겹쳐 그리는 도구가 아닌, 복잡한 인프라를 **논리적 도메인(Domain)**으로 분리하여 관리하는 차세대 아키텍처를 채택했습니다. 이는 기존의 드로잉 도구(Draw.io, Visio 등)와 가장 극명하게 대비되는 drawNET만의 독보적 강점입니다.
|
||||||
|
|
||||||
|
### 4.1 Visual Layer vs. Logical Engineering Layer
|
||||||
|
| 비교 항목 | 기존 도구 (Visio, Draw.io) | **drawNET Premium** |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **핵심 성격** | **Visual Layer** (시각적 그룹화) | **Logical Layer** (엔지니어링 격리) |
|
||||||
|
| **주요 용도** | 단순 Show/Hide (투명 비닐지 방식) | 도메인별 논리 격리 및 데이터 제어 |
|
||||||
|
| **데이터 일관성** | 레이어 간 복사 시 별개 객체로 분리 | 동일 UUID 기반의 다중 관점(View) 동기화 |
|
||||||
|
| **상호 작용** | 레이어 간 논리적 연동 불가 | Cross-layer Tunneling (계층 간 연동 추적) |
|
||||||
|
| **활용 가치** | "그림의 정리 정돈" | "복잡한 시스템의 통합 거버넌스" |
|
||||||
|
|
||||||
|
### 4.2 왜 '논리적 격리'인가? (The Reason Why)
|
||||||
|
엔터프라이즈급 인프라 설계에서 물리(Physical), 논리(Logical), 보안(Security) 데이터를 하나의 화면에 그리는 것은 불가능에 가깝습니다. drawNET은 이를 해결하기 위해 다음과 같은 고난도 기술을 구현했습니다.
|
||||||
|
1. **도메인 격리(Domain Isolation)**: L1(케이블/랙), L2(VLAN/IP), L3(보안 정책) 등 각 도메인의 정밀한 분리를 통해 설계의 복잡도를 낮추고 각 담당자가 본인의 영역에 집중하게 합니다.
|
||||||
|
2. **Single Source of Truth (SSOT)**: 1번 레이어의 '코어 스위치'와 3번 레이어의 '보안 장비'가 동일한 물리 장비라면, drawNET은 단 하나의 데이터 소스를 공유하여 어느 레이어에서 수정해도 전체 정합성이 유지되게 설계되었습니다.
|
||||||
|
3. **지능형 터널링(Layer Tunneling)**: 레이어는 분리되어 있지만, 데이터 경로(Path)는 연결되어 있습니다. 물리적 케이블이 끊어졌을 때 논리 레이어의 패킷 흐름에 어떤 영향을 주는지 **교차 레이어 분석**이 가능합니다.
|
||||||
|
|
||||||
|
### 4.3 공간 쿼리 및 부모 선택 알고리즘 (Innermost Heuristic)
|
||||||
|
노드가 중첩된 그룹 영역으로 이동할 때, 시스템은 **Bounding Box 면적 기반의 Innermost 알고리즘**을 수행합니다.
|
||||||
|
- **알고리즘**: 드롭 포인트를 포함하는 모든 부모 후보 중 면적이 가장 작은(가장 구체적인) 그룹을 최종 부모로 선택합니다.
|
||||||
|
- **의도**: 이를 통해 복잡한 랙(Rack) 및 사이트(Site) 계층 내부로의 직관적인 객체 삽입이 가능해집니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 저장 방식의 지향성과 확장성 (Storage & Scalability)
|
||||||
|
|
||||||
|
### 5.1 JSON-Native의 이점
|
||||||
|
- **VCS 친화성**: 텍스트 기반 JSON 파일로 저장되므로, Git과 같은 버전 관리 시스템을 통해 시나리오별 변경 이력을 관리하기 용이합니다.
|
||||||
|
- **상호운용성 (Interoperability)**: Python, JS 등 다양한 언어에서 표준 JSON 라이브러리를 통해 프로젝트 파일에 접근하여 자동화된 분석 보고서를 생성할 수 있습니다.
|
||||||
|
|
||||||
|
### 5.2 에셋 라이브러리 확장성 (Modular & Open Asset Architecture)
|
||||||
|
drawNET의 에셋 시스템은 폐쇄적�### 7. 전문 내보내기 서브시스템 및 하이브리드 리포팅 (High-Fidelity Export & Hybrid Reporting)
|
||||||
|
|
||||||
|
drawNET은 단순한 캔버스 캡처를 넘어, 엔지니어링 결과물의 시각적 완밀성과 데이터의 편집 가능성을 동시에 확보하는 **하이브리드 내보내기 전략**을 취합니다.
|
||||||
|
|
||||||
|
#### 7.1 하이브리드 이미지-객체 모델 (Hybrid Image-Object Model)
|
||||||
|
PPTX 리포트 생성 시, 시스템은 두 가지 서로 다른 렌더링 경로를 결합합니다.
|
||||||
|
1. **Topology View (High-Res Snapshot)**: 복잡한 선(Path) 라우팅, SVG 그림자, 반투명 그룹 등은 PPTX 네이티브 객체로 변환 시 시각적 손실이 큽니다. 이를 방지하기 위해 200% 스케일의 고해상도 PNG 스냅샷으로 캔버스를 캡처하여 삽입합니다.
|
||||||
|
2. **Inventory Data (Native PPTX Objects)**: 장비 목록, 프로젝트 정보, 요약 통계는 수정이 용이해야 합니다. 따라서 이 데이터는 PPTX의 네이티브 테이블(Table) 및 텍스트 상자로 생성하여, 리포트 출력 후에도 사용자가 파워포인트 내에서 직접 내용을 수정할 수 있도록 보장합니다.
|
||||||
|
|
||||||
|
#### 7.2 초정밀 클린 캡처 및 안정화 (Clean Capture & Stabilization)
|
||||||
|
내보내기 수행 시, 시스템은 실시간으로 **가상 스타일시트 주입(Virtual CSS Injection)** 및 **모사 데이터 동기화** 기술을 사용합니다.
|
||||||
|
- **UI 노이즈 제거**: 연결점(Port/Anchor), 선택 박스(Selection), 그리드 가이드 등을 캡처 순간에만 `display: none`으로 처리합니다.
|
||||||
|
- **적응형 안정화 지연 (1000ms Delay)**: 모델 업데이트(Base64 변환 등) 후 브라우저 DOM이 완전히 동기화되도록 1초의 대기 시간을 강제하여, 저사양 환경에서도 캡처 무결성을 보장합니다.
|
||||||
|
- **해상도 보정 (2x Scaling)**: 사용자 모니터의 DPI와 관계없이 항상 고품질 인쇄가 가능한 수준의 픽셀 밀도를 확보하기 위해 2배수 슈퍼 샘플링(Super Sampling) 캡처를 수행합니다.
|
||||||
|
|
||||||
|
#### 7.3 전수 속성 스캔 및 Base64 임베딩 (Recursive Asset Embedding)
|
||||||
|
SVG 내보내기의 핵심은 외부 자산의 완전한 자립화입니다.
|
||||||
|
- **재귀적 선택자 탐색**: 특정 태그에 국한되지 않고 노드의 모든 속성 트리를 스캔하여 외부 이미지 경로를 식별합니다.
|
||||||
|
- **속성 패리티(Parity)**: `xlink:href`와 일반 `href` 속성 모두에 동일한 Base64 데이터를 주입하여 크로스 브라우저/뷰어 호환성을 극대화합니다.
|
||||||
|
- **절대 좌표 매핑 (Zero-Clip)**: 캔버스의 현재 뷰포트 상태와 무관하게 `getCellsBBox`를 통한 실제 객체 영역을 자동 계산하고, 명시적인 `viewBox` 주입을 통해 상하좌우 짤림 현상을 원천 차단합니다.
|
||||||
|
상태, 환경 사양 등 무한한 메타데이터를 자유롭게 추가하고 수정할 수 있습니다.
|
||||||
|
- **데이터 활용성**: 모든 부가 정보는 JSON 데이터 모델에 정적/동적으로 포함되어 저장되므로, 설계가 완료된 후 즉시 인벤토리 보고서(Inventory Report) 생성, 검색 엔진 필터링, 혹은 외부 시스템과의 데이터 연동(API)에 활용됩니다.
|
||||||
|
- **설계 의도의 보존**: 이는 "그림"이 아닌 "설계 데이터" 그 자체를 보존함으로써, 설계자가 의도한 모든 엔지니어링 맥락이 유실 없이 영구적으로 관리됨을 의미합니다.
|
||||||
|
|
||||||
|
### 5.4 PM 지향적 설계 및 자산 관리 (PM-Centric Design & Asset Management)
|
||||||
|
엔지니어의 기술적 관점뿐 아니라, 프로젝트 매니저(PM)의 **의사결정 및 자산 관리 효율성**을 극대화합니다.
|
||||||
|
- **선제적 비즈니스 데이터 매핑**: 설계 초기 단계부터 각 객체에 벤더 정보, 단가(Price), 구매 일정, 담당 부서 등 관리 지표를 미리 입력할 수 있습니다.
|
||||||
|
- **원클릭 전수 관리**: "Inventory View" 및 "JSON/PPTX Export" 기능을 통해 프로젝트에 투입된 전체 자산 현황과 예상 비용을 한눈에 파악할 수 있으며, 이는 즉시 구매 발주서나 예산 보고서의 기초 자료로 변환됩니다.
|
||||||
|
- **전략적 가시성**: 시각적 토폴로지와 비즈니스 데이터가 결합됨으로써, PM은 단순히 "어떻게 연결되는가"를 넘어 "무엇이 얼마의 비용으로 구축되는가"에 대한 전략적 통찰을 실시간으로 확보할 수 있습니다.
|
||||||
|
|
||||||
|
### 5.5 REST API 기반의 생태계 통합 (REST API-Based Ecosystem Integration)
|
||||||
|
drawNET의 데이터 구조는 고립된 파일에 머물지 않고 외부 시스템과 유기적으로 소통할 수 있는 **프로그래밍 가능한 인터페이스(Programmable Interface)**를 지향합니다.
|
||||||
|
- **Native REST API 지원**: Flask 기반의 강력한 백엔드를 통해 토폴로지 데이터의 CRUD(Create, Read, Update, Delete)를 REST API로 실시간 제공합니다.
|
||||||
|
- **엔터프라이즈 시스템 연동**: 기업 내 CMDB(자산 관리 시스템), NMS(네트워크 모니터링), 혹은 클라우드 프로비저닝 엔진과의 양방향 데이터 동기화가 가능합니다.
|
||||||
|
- **자동화된 생명주기 관리**: 외부 스크립트를 통해 새로운 프로젝트를 자동 생성하거나, 실시간 모니터링 데이터를 API로 수신하여 캔버스상의 노드 상태(장애 발생 등)를 시각적으로 즉시 갱신하는 등 고도화된 자동화 워크플로우를 구현할 수 있는 강력한 무기를 갖추고 있습니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 엔지니어링 생산성: 지능형 단축키 시스템 (Engineering Productivity: Intelligent Hotkey System)
|
||||||
|
|
||||||
|
drawNET은 일반적인 드로잉 도구(Draw.io, Visio 등)와 차별화되는 **"전문 엔지니어 전용 워크스테이션"**으로서의 정체성을 단축키 시스템을 통해 증명합니다.
|
||||||
|
|
||||||
|
### 6.1 "Always-Enabled" 로직의 기술적 의도
|
||||||
|
일반적인 웹 도구는 텍스트 입력 중 시스템 단축킹(Undo/Redo 등)이 차단되거나 포커스 충돌을 일으킵니다. drawNET의 단축키 엔진은 `isTyping` 상태를 정밀하게 인지하되, 핵심적인 그래프 제어 액션에 대해서는 **전역적 우선순위(Override Priority)**를 부여합니다. 이는 수천 개의 속성을 동시에 수정하는 현업 환경에서 작업의 연속성을 완벽하게 보장하는 설계입니다.
|
||||||
|
|
||||||
|
### 6.2 Drawing Tool vs. Engineering Tool
|
||||||
|
- **드로잉 도구 (Draw.io, Visio)**: 시각적 표현이 목적이므로 마우스 기반의 정교한 배치를 우선시하며, 단축키는 보조 수단에 불과합니다.
|
||||||
|
- **엔지니어링 도구 (drawNET)**: 토폴로지 구성 속도가 핵심입니다. 따라서 **"Zero-Mouse"** 워크플로우를 지향하며, 로직 기반의 정렬(Alignment), 속성 복제(Format Painter), 컨텍스트 기반의 부모-자식 포함 로직을 단축키 하나로 수행할 수 있도록 최적화되어 있습니다.
|
||||||
|
|
||||||
|
### 6.3 현업 최적화 컨텍스트 핸들러
|
||||||
|
단순한 좌표 이동을 넘어, `Grid Snapping` 연동 이동, `Large/Small Step` 이동, 그리고 레이어 가시성 퀵 토글 등 현업 엔지니어가 토폴로지 구성 시 가장 빈번하게 수행하는 동작들을 모듈화된 핸들러(`handlers/`)로 구현하여 응답성을 극대화했습니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 전문 내보내기 서브시스템 및 하이브리드 리포팅 (High-Fidelity Export & Hybrid Reporting)
|
||||||
|
|
||||||
|
drawNET은 단순한 캔버스 캡처를 넘어, 엔지니어링 결과물의 시각적 완밀성과 데이터의 편집 가능성을 동시에 확보하는 **하이브리드 내보내기 전략**을 취합니다.
|
||||||
|
|
||||||
|
### 7.1 하이브리드 이미지-객체 모델 (Hybrid Image-Object Model)
|
||||||
|
PPTX 리포트 생성 시, 시스템은 두 가지 서로 다른 렌더링 경로를 결합합니다.
|
||||||
|
1. **Topology View (High-Res Snapshot)**: 복잡한 선(Path) 라우팅, SVG 그림자, 반투명 그룹 등은 PPTX 네이티브 객체로 변환 시 시각적 손실이 큽니다. 이를 방지하기 위해 200% 스케일의 고해상도 PNG 스냅샷으로 캔버스를 캡처하여 삽입합니다.
|
||||||
|
2. **Inventory Data (Native PPTX Objects)**: 장비 목록, 프로젝트 정보, 요약 통계는 수정이 용이해야 합니다. 따라서 이 데이터는 PPTX의 네이티브 테이블(Table) 및 텍스트 상자로 생성하여, 리포트 출력 후에도 사용자가 파워포인트 내에서 직접 내용을 수정할 수 있도록 보장합니다.
|
||||||
|
|
||||||
|
### 7.2 초정밀 클린 캡처 메커니즘 (Clean Capture Mechanism)
|
||||||
|
내보내기 수행 시, 시스템은 실시간으로 **가상 스타일시트 주입(Virtual CSS Injection)** 기술을 사용하여 시각적 노이즈를 제거합니다.
|
||||||
|
- **UI 노이즈 제거**: 연결점(Port/Anchor), 선택 박스(Selection), 그리드 가이드 등을 캡처 순간에만 `display: none`으로 처리하여 설계 데이터 본연의 모습만 선명하게 담아냅니다.
|
||||||
|
- **해상도 보정 (2x Scaling)**: 사용자 모니터의 DPI와 관계없이 항상 고품질 인쇄가 가능한 수준의 픽셀 밀도를 확보하기 위해 2배수 슈퍼 샘플링(Super Sampling) 캡처를 수행합니다.
|
||||||
|
|
||||||
|
### 7.3 레이어 기반 수직적 리포팅 (Layer-Aware Vertical Reporting)
|
||||||
|
전체 토폴로지뿐 아니라, 프로젝트에 정의된 각 레이어(물리, 논리, 보안 등)를 개별적으로 분석하여 슬라이드를 생성합니다.
|
||||||
|
- **동적 가시성 제어**: 각 레이어별 전용 슬라이드를 캡처할 때, 시스템은 백그라운드에서 레이어 가시성을 순차적으로 토글(Toggle)하고 필터를 적용하여 해당 도메인의 객체들만 깨끗하게 분리된 자동 리포트를 구성합니다.
|
||||||
|
|
||||||
|
|
||||||
|
#### 7.4 엔지니어링 실무 데이터 매핑 (Engineering Data Mapping)
|
||||||
|
- **식별자 기반 매핑 (ID-Key Linkage)**: drawNET의 모든 객체는 고유 UUID를 Primary Key로 가집니다. 이는 실무 엔지니어가 보유한 방대한 외부 데이터(라우팅 테이블, 방화벽 룰 등)와 시각적 도면을 VLOOKUP이나 데이터 조인으로 즉시 연결하는 매개체가 됩니다.
|
||||||
|
- **경량 메타데이터 레이어 (Description & Tags)**: 도면의 가벼움을 유지하면서도 실무에 꼭 필요한 컨텍스트를 `Tags`와 `Description`으로 캡처합니다. 이는 "무거운 데이터 관리는 외부 전문 툴에 맡기고, 도면은 맥락과 연결 고리만 제공한다"는 실무적 철학을 반영합니다.
|
||||||
|
- **BOM 및 인벤토리 자동 생성**: 인벤토리 내 '소속 그룹' 정보를 포함하여, 내보낸 즉시 자산 관리 대장(Excel)으로 활용 가능한 고순도 데이터를 제공합니다.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Connectivity & Simulation Architecture (논리적 연결 계층화)
|
||||||
|
drawNET은 물리적 자산 배치와 논리적 연결 시뮬레이션을 분리하기 위해 **'레이어 타입 기반 제약(Layer-Type Constraints)'** 아키텍처를 채택합니다.
|
||||||
|
### 8.1 레이어 타입 정의
|
||||||
|
- **Standard (Physical)**: 장비(Node)와 연결(Edge)이 공존하는 기본 레이어.
|
||||||
|
- **Logical (Simulation)**: 연결선(Edge)만 허용되는 특수 레이어. 신규 노드 생성이 차단되며, 복제 시 노드는 필터링되고 연결 관계만 전이됩니다.
|
||||||
|
### 8.2 데이터 일관성 및 시뮬레이션 원칙
|
||||||
|
- **SSoT (Single Source of Truth)**: 장비의 마스터 정보는 물리 레이어에 위치하며, 논리 레이어의 선들은 서로 다른 레이어에 속한 노드들을 참조(UUID 기반)하여 가상의 망 구성을 형성합니다.
|
||||||
|
- **포맷 페인터(Style Propagation)**: 클립보드 데이터를 최우선으로 하는 `Default-Strict` 로직을 통해 개체의 스타일 정보가 유실 없이 전파되도록 보장합니다.
|
||||||
|
- **자기 치유형 상태 관리**: 브라우저 및 OS 레벨의 이벤트 유실에 대비하여, 인터랙션 발생 시마다 실제 모디파이어(Ctrl/Cmd) 상태를 재검증하는 안정성 프로토콜을 운영합니다.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 9. AI Ready: 구조화된 그래프 데이터의 가치 (AI Ready: The Value of Structured Data)
|
||||||
|
|
||||||
|
drawNET은 단순히 도면을 "그리는" 도구가 아니라, **AI가 즉시 이해하고 분석할 수 있는 고정밀 데이터셋**을 생성하는 플랫폼입니다.
|
||||||
|
|
||||||
|
### 9.1 데이터 고정밀도 (Data High-Fidelity)
|
||||||
|
기존의 PDF나 이미지(OCR 기반) 도면 분석은 시각적 모호성으로 인해 연결 관계의 왜곡이나 오인식 확률이 높습니다. drawNET의 JSON-Native 구조는 모든 연결을 UUID와 참조 무결성(Referential Integrity)으로 정의하므로, AI에게 **"정답(Ground Truth)"**을 제공합니다. 이는 AI 모델의 학습 및 분석 정확도를 비약적으로 높여줍니다.
|
||||||
|
|
||||||
|
### 9.2 지능형 설계 감사 및 예측 (Intelligent Auditing & Prediction)
|
||||||
|
구조화된 데이터 모델은 차세대 AI 엔진과 결합하여 다음과 같은 가치를 창출합니다:
|
||||||
|
- **자동 설계 검증 (Automated Auditing)**: 설계된 토폴로지가 기업의 보안 정책이나 표준망 구성 원칙을 준수하는지 AI가 실시간으로 감시하고 이상 징후를 탐지합니다.
|
||||||
|
- **장애 시뮬레이션 (Impact Analysis)**: 특정 노드 장애 시 전체 서비스에 미치는 영향도를 그래프 알고리즘으로 자동 계산하여 최적의 백업 경로를 제안합니다.
|
||||||
|
- **비용 최적화 (TCO Optimization)**: 입력된 단가와 장비 스펙 데이터를 AI가 분석하여, 운용 효율은 유지하면서도 구축 비용을 최소화할 수 있는 설계 최적화 모델을 제시합니다.
|
||||||
|
|
||||||
|
### 9.3 그래프 알고리즘과의 완벽한 호환성
|
||||||
|
본 프로젝트가 생성하는 데이터는 **그래프 이론(Graph Theory)**을 따르는 인접 리스트(Adjacency List) 구조로 즉시 변환 가능합니다. 이는 LLM(대형 언어 모델)뿐만 아니라 GNN(Graph Neural Networks)과의 결합을 통해 네트워크 인프라의 미래를 설계하는 데 가장 적합한 아키텍처임을 증명합니다.
|
||||||
|
|
||||||
|
### 9.4 네트워크 가상 시뮬레이션 및 디지털 트윈 (Network Virtual Simulation & Digital Twin)
|
||||||
|
drawNET의 데이터는 단순한 시각화를 넘어, 가상 환경에서의 **패킷 흐름 시뮬레이션**을 가능케 하는 디지털 트윈의 기반이 됩니다.
|
||||||
|
- **라우팅 루프 및 블랙홀 탐지 (Loop & Blackhole Detection)**: 엔터프라이즈 네트워크의 고질적인 문제인 라우팅 루프를 설계 단계에서 사전에 차단합니다. 객체의 `tags`에 포함된 라우팅 정보와 `ip` 속성을 그래프 경로 탐색 알고리즘과 결합하여, 논리적 순환 경로(Loop)나 목적지 없이 사라지는 경로(Blackhole)를 AI가 0.1초 만에 전수 검사합니다.
|
||||||
|
- **물리-논리 정합성 검증**: 물리적 케이블 연결(Edge)과 논리적 라우팅 정책(Metadata) 사이의 불일치를 잡아내어, "그림과 실제 설정이 다른" 휴먼 에러를 원천적으로 방지합니다.
|
||||||
|
|
||||||
|
### 9.5 AI 범용성 및 미래 지향성 (Universal AI-Agnostic Architecture)
|
||||||
|
drawNET이 생성하는 데이터 모델은 특정 AI 기술에 종속되지 않는 **"범용 AI 데이터 언어"**로서의 가치를 지닙니다.
|
||||||
|
- **Cross-AI Compatibility**: OpenAI(GPT), Anthropic(Claude), Meta(Llama) 등 모든 주요 AI 모델들은 시각적 이미지보다 정형화된 JSON 데이터에서 강력한 추론 능력을 발휘합니다. drawNET은 어떤 AI가 등장하더라도 즉시 협업할 수 있는 완벽한 인터페이스를 미리 갖추고 있습니다.
|
||||||
|
- **데이터 주권과 보안 (Data Sovereignty)**: 클라우드 기반 AI를 사용할 수 없는 고도의 보안 환경에서도, 표준 JSON 형식의 프로젝트 파일을 로컬 AI 엔진(On-premise LLM)이 직접 분석하게 함으로써 데이터 외부 유출 없이도 지능형 설계를 수행할 수 있습니다.
|
||||||
|
- **공공 및 국가 중요 시설 최적화**: 망 분리 환경이 필수적인 공공기관, 금융, 국방 분야에서 폐쇄형 LLM과의 결합을 통해 **보안 가이드라인 준수 여부(Compliance Audit)**를 자동 검증할 수 있습니다. 이는 인적 과실로 인한 국가적 보안 사고를 설계 단계에서 원천 차단하는 가장 강력한 보안 설계 아키텍처임을 보장합니다.
|
||||||
|
- **미래를 위한 확장팩**: 현재 입력된 `tags`, `ip`, `metadata` 등은 미래의 강력한 AI 에이전트가 네트워크 인프라를 자동으로 정밀하게 제어(Autonomous Networking)할 수 있게 만드는 핵심 데이터 전구체(Precursor) 역할을 수행합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 미래 전망 및 지향점 (Future Direction)
|
||||||
|
|
||||||
|
1. **AI Topology Auditor**: 저장된 JSON 데이터를 분석하여 네트워크 설계상의 병목 구간이나 단일 장애점(SPOF)을 AI가 자동으로 식별하는 기능을 계획하고 있습니다.
|
||||||
|
2. **Visual Diff Engine (Next Gen)**: 두 개의 프로젝트 JSON 파일을 비교하여 노드 위치 변화, 속성 변화를 캔버스상에 시각적으로 강조(Highlight)하는 도구로 발전시킬 것입니다.
|
||||||
|
3. **Master Model Definition**: 하나의 마스터 모델에서 다양한 관점의 서브 그래프(View)를 동시에 생성하고 동기화하는 엔터프라이즈 협업 기능을 지향합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*최초 공식 릴리즈: 2026-03-20 (v1.0 Premium Edition - New Launch)*
|
||||||
|
*작성자: Antigravity AI Implementation Team (drawNET Core)*
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# drawNET Bug Fix Log 🐞 index
|
||||||
|
|
||||||
|
이 문서는 drawNET 개발 과정에서 발생한 버그 수정 이력을 날짜별로 요약 및 아카이브합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 아카이브 목록 (By Date)
|
||||||
|
|
||||||
|
- **[2026-03-21](./bug_reports/2026-03-21.md)**: 리치 카드 이슈, 데이터 정합성(Z-Index, 유실 복구), 사이드바 수동 저장 모델 도입 등 (총 19건)
|
||||||
|
- *추가 예정...*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 주요 카테고리별 요약
|
||||||
|
|
||||||
|
| 카테고리 | 주요 사례 | 누적 건수 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **UX/UI** | 사이드바 포커스 유지, Alt+Enter, 실시간 동기화 | 5건 |
|
||||||
|
| **Syntax/Load** | JSON 문법 오류, 중복 식별자, 구문 에러 | 4건 |
|
||||||
|
| **Stability/Data** | 데이터 복구(Pos/Label), Z-Index 계층, 엣지 보존 | 10건 |
|
||||||
|
|
||||||
|
---
|
||||||
|
마지막 업데이트: 2026-03-21
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
# drawNET Bug Report - 2026-03-21 🐞
|
||||||
|
|
||||||
|
이 문서는 2026년 3월 21일 발생한 버그 및 장애와 그에 따른 조치 사항을 기록합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [Instance #2255] - Sidebar Focus Loss & Explicit Save Refactoring
|
||||||
|
- **현상**: 사이드바에서 라벨 수정 시 포커스 유실로 인한 타이핑 불가능 현상 및 자동 저장에 의한 의도치 않은 데이터 경합 가능성 제기.
|
||||||
|
- **원인**: 실시간 `input` 및 자동 `change`(blur) 이벤트가 시각 속성 변경 리스너와 충돌하여 DOM 리렌더링 발생.
|
||||||
|
- **조치**:
|
||||||
|
1. **명시적 저장 모델(Explicit Apply)** 도입: 텍스트 관련 필드는 오직 **Enter** 또는 **[적용]** 버튼 클릭 시에만 저장되도록 변경하여 입력 중 데이터 무결성 보장.
|
||||||
|
2. 사이드바 내부 요소에 포커스가 있을 경우 그래프 외부 변화에 의한 리렌더링을 차단하여 포커스 연속성 확보.
|
||||||
|
- **현상**: 라벨 위치를 "하(bottom)"로 설정해도 저장 후 불러오면 다시 "상(top)"으로 초기화됨.
|
||||||
|
- **원인**: `layers.js` 내부에 라벨 위치 기본값이 `bottom`으로 하드코딩되어 강제 적용되었으며, 일부 객체 생성 시 `label_pos` 데이터 주입 누락.
|
||||||
|
- **조치**: `layers.js` 하드코딩 제거, `system.js`에서 생성 시 기본값(장비-하, 그룹-상) 강제 주입, 유틸리티 함수 보강을 통한 정합성 확보.
|
||||||
|
|
||||||
|
### [Instance #2245] - Sidebar Display Stale During Group Rename (Recursive & Logic Fix)
|
||||||
|
- **현상**: 그룹 이름을 변경해도 해당 그룹에 속한 하위 개체의 사이드바 내 [상위] 정보가 즉시 바뀌지 않음.
|
||||||
|
- **원인**:
|
||||||
|
1. **저장 조건부 누락**: 부모 노드가 바뀌지 않은 단순 속성(이름 등) 변경 시 `saveUpdates` 호출이 누락되어 저장이 실행되지 않음.
|
||||||
|
2. **포커스 가드 충돌**: 사이드바 내부에서 편집 중일 때 리렌더링을 차단하는 로직이 적용(Apply) 시점의 갱신마저 막아버림.
|
||||||
|
3. **비동기 타이핑 불일치**: 엔진의 데이터 확정 전 UI 렌더링 시도로 인한 구형 데이터 출력.
|
||||||
|
- **조치**:
|
||||||
|
1. `handleNodeUpdate` 내 저장 로직을 평탄화하여 **속성 변경 시 항상 저장**되도록 수정.
|
||||||
|
2. **재귀적 전파(Recursive Propagation)** 도입: 그룹명 변경 시 하위 모든 개체(`getDescendants`)에 동기화 타임스탬프를 주입하여 하위 개체들의 변경 이벤트를 강제 트리거함.
|
||||||
|
3. `safeRender` 포커스 가드 최적화: `INPUT/TEXTAREA`가 활성화된 경우만 스킵하도록 정규화하고 `setTimeout(0)`으로 렌더링 시기를 튜닝함.
|
||||||
|
- **현상**: 불러온 파일의 일부 그룹 라벨이 화면 출력값과 사이드바 표시값이 서로 다름 (예: Storage vs 그룹).
|
||||||
|
- **원인**: 과거 버전의 저장 로직 오류로 인해 `attrs`와 `data` 필드 간의 불일치가 파일 내에 고착화됨.
|
||||||
|
- **조치**: `json_handler.js` 마이그레이션 필터를 추가하여, 로드 시 `data.label`을 원본 소스로 삼아 시각적 라벨을 강제 동기화함.
|
||||||
|
|
||||||
|
### [Instance #2235] - Destructive Object Disappearance (Style Corrupt)
|
||||||
|
- **현상**: 레이어 설정 변경 시 객체가 갑자기 투명해지거나 색상이 사라지는 현상.
|
||||||
|
- **원인**: `layers.js`에서 투명도 적용 시 `setAttrs`를 사용하여 기존의 모든 커스텀 스타일(fill, stroke 등)을 덮어써버림.
|
||||||
|
- **조치**: `attr()` 개별 호출 방식으로 변경하여 기존 스타일 유산을 완벽히 보존함.
|
||||||
|
|
||||||
|
### [Instance #2230] - Missing Coordinates Recovery (Pos/Size)
|
||||||
|
- **현상**: 일부 객체가 좌표값 누락으로 인해 (0,0)에 배치되거나 아예 보이지 않음.
|
||||||
|
- **원인**: 복사/붙여넣기 및 엔진 전환 과정에서 X6 표준 좌표 필드가 유실된 데이터 잔재.
|
||||||
|
- **조치**: `json_handler.js`에 `data.pos` 기반 좌표 자동 복구 로직을 주입하여 유실된 개체들을 정상 위치로 소환함.
|
||||||
|
|
||||||
|
### [Instance #2225] - Edge Clipping & Selection Difficulty (Z-Index)
|
||||||
|
- **현상**: 그룹 위에 선을 그으면 그룹 뒤로 숨어서 클릭이 안 됨. 단축키 연결 시에도 동일 현상 반복.
|
||||||
|
- **원인**: 엣지 기본 Z-Index가 5로 설정되어, 레이어 오프셋이 적용된 그룹보다 하단에 배치됨.
|
||||||
|
- **조치**: 전체 계층 구조 재설계 `Group(1~19) < Edge(30) < Node(50)` 및 마우스/단축키 생성 경로 동기화.
|
||||||
|
|
||||||
|
### [Instance #2220] - Accidental Reset of Edge Vertices
|
||||||
|
- **현상**: 새로운 장비를 연결하면 기존에 예쁘게 꺾어 놓았던 선들이 모두 일직선으로 초기화됨.
|
||||||
|
- **원인**: `applyLayerFilters` 함수 내에 모든 엣지의 버텍스를 강제로 비우는 과도한 '자동 복구' 로직이 포함되어 있었음.
|
||||||
|
- **조치**: 해당 파괴적인 초기화 로직(`setVertices([])`)을 삭제하여 사용자 커스텀 경로 보존. (Instance #1480 정정 사항)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [Instance #2202] - Label Reverts when Changing Label Position (Select Sync)
|
||||||
|
- **현상**: 라벨 이름을 수정한 직후(아직 저장되기 전) 라벨 위치(`select`)를 변경하면 이름이 이전 상태로 되돌아감.
|
||||||
|
- **원인**: 모든 입력 요소에 동일한 `debounce`(300ms)가 적용되어 있어, 상호작용 간 데이터 경합 발생 및 `renderProperties`에 의한 DOM 덮어쓰기 현상.
|
||||||
|
- **조치**: `select`, `checkbox`, `color` 등 상태 선택형 요소는 `change` 이벤트 발생 시 즉시 저장(`applyChanges`)되도록 변경하여 데이터 동기화 보장.
|
||||||
|
|
||||||
|
### [Instance #2108] - Inconsistent Line Thickness (Style vs Selection)
|
||||||
|
- **현상**: 구성도 작성 시 선 두께가 일정하지 않게 보임.
|
||||||
|
- **원인**:
|
||||||
|
1. 선택 시 강조 효과(3px)와 일반 상태(2px)의 차이.
|
||||||
|
2. '굵은 선' 스타일이 UI에는 있으나 실제 두께(4px) 반영 로직이 누락됨.
|
||||||
|
- **조치**:
|
||||||
|
1. '굵은 선(double)' 선택 시 실제 4px가 적용되도록 매핑 로직 업데이트.
|
||||||
|
2. 속성창에 '선 두께(Width)' 수치 입력 필드를 추가하여 개별 커스터마이징 지원.
|
||||||
|
|
||||||
|
### [Instance #2010] - ID & Label Confusion (Initial Data Setup)
|
||||||
|
- **현상**: 그룹이나 노드 생성 시 이름이 `group_xxxx`와 같은 ID 형식으로 표시되며, 수정 후 특정 상황에서 다시 ID 값으로 되돌아가는 것처럼 보임.
|
||||||
|
- **원인**: `drop.js`에서 노드 생성 시 ID와 Label을 동일한 랜덤 문자열로 초기화했기 때문에 발생한 혼동.
|
||||||
|
- **조치**: 초기 라벨을 '그룹', '사각형' 등 친숙한 한글 명칭(다국어 대응)으로 설정하도록 변경하고, 사이드바에서 ID를 읽기 전용 필드로 명시적으로 분리 노출함.
|
||||||
|
|
||||||
|
### [Instance #1968] - Data Reverted During Move after Rename (Blur vs Drag)
|
||||||
|
- **현상**: 그룹 이름을 수정한 직후 다른 곳을 클릭하지 않고 바로 드래그하여 이동하면 이름이 이전 값으로 되돌아감.
|
||||||
|
- **원인**: 사이드바가 `change`(포커스 해제) 시점에 저장하는데, 드래그 시 발생하는 리렌더링 이벤트가 `change`보다 먼저 발생하여 이전 데이터를 불러옴.
|
||||||
|
- **조치**: 사이드바 텍스트 입력 이벤트를 `input`으로 변경하고 300ms 디바운싱을 적용하여 실시간 동기화 구현.
|
||||||
|
|
||||||
|
### [Instance #1940] - JSON Syntax Error in Locale File (Stray Backticks)
|
||||||
|
- **현상**: 앱 구동 시 `[drawNET:CRITICAL] Failed to load locales` 에러 발생하며 모든 텍스트가 키값으로 표시됨.
|
||||||
|
- **원인**: `ko.json` 수정 과정에서 AI 도구가 파일 끝에 마크다운 기호(```)를 잘못 삽입하여 유효하지 않은 JSON 형식이 됨.
|
||||||
|
- **조치**: `ko.json` 파일 끝의 불필요한 문자를 제거하고 구조 정상화.
|
||||||
|
|
||||||
|
### [Instance #1931] - Property Handler Syntax Error (Accidental Deletion)
|
||||||
|
- **현상**: 노드 속성 수정 시 변경사항이 반영되지 않거나 콘솔에 `SyntaxError` 발생.
|
||||||
|
- **원인**: '에셋 경로' 필드를 제거하는 과정에서 `handlers/node.js` 속성 추출 객체의 괄호 및 인접 필드(color, fill 등)를 실수로 대량 삭제함.
|
||||||
|
- **조치**: 삭제된 모든 필드 로직을 원래대로 복구하고 의도했던 `assetPath` 추출부만 정교하게 제거.
|
||||||
|
|
||||||
|
### [Instance #1860] - Rich Card Sidebar UI Inconsistency
|
||||||
|
- **현상**: 리치 텍스트 카드의 속성창 디자인이 기존 '컴팩트 UI' 표준(토글 스위치, 정렬된 입력창 등)과 다르게 투박하게 표시되고 정보 밀도가 낮음.
|
||||||
|
- **원인**: 신규 객체 전용 템플릿 작성 시 표준 CSS 클래스(`prop-input`, `toggle-switch`) 적용 누락 및 일부 스타일(`input-with-color`) 미정의.
|
||||||
|
- **조치**: `rich_card.js` 템플릿을 표준 컴포넌트 구조로 전면 재작성하고, `properties_sidebar.css`에 부족한 레이아웃 스타일 보강.
|
||||||
|
|
||||||
|
### [Instance #1840] - Rich Card Syntax Error (Duplicate Identifier)
|
||||||
|
- **현상**: 리치 카드 선택 시 콘솔에 `Uncaught SyntaxError: Identifier 'renderRichContent' has already been declared` 발생 및 사이드바 작동 중단.
|
||||||
|
- **원인**: `styles/utils.js`에서 함수를 임포트했음에도 불구하고, 로컬 핸들러 파일에도 동일한 가명 함수가 남아있어 충돌 발생.
|
||||||
|
- **조치**: `handlers/rich_card.js` 하단의 중복된 `renderRichContent` 함수를 제거하고 공용 유틸리티 함수만 사용하도록 수정.
|
||||||
|
|
||||||
|
### [Instance #1692] - 다중 선택 해제 시 시각적 잔상(Focus) 유지
|
||||||
|
- **현상**: `Ctrl+Click`으로 다중 선택 객체 중 일부를 해제해도 캔버스상에 점선 테두리(Focus)가 남아 있음.
|
||||||
|
- **원인**: X6의 `Selection` 또는 `Transform` 플러그인이 추가한 `boundary` 도구가 `unselected` 이벤트 시 자동으로 제거되지 않거나 커스텀 도구 관리에 밀려 잔류함.
|
||||||
|
- **조치**: `selection.js`의 `unselected` 이벤트 핸들러에서 노드/엣지 해제 시 `removeTools()`를 명시적으로 호출하도록 강화.
|
||||||
|
|
||||||
|
### [Instance #1684] - `removeLabel` 런타임 에러
|
||||||
|
- **현상**: 엣지 선택 해제 시 `cell.removeLabel is not a function` 에러 발생.
|
||||||
|
- **원인**: 일부 X6 환경에서 엣지 객체의 단축 함수가 유실되거나 차단되는 문제.
|
||||||
|
- **조치**: `setLabels`와 `filter`를 사용하여 명시적으로 해당 ID의 라벨을 제외한 리스트를 재설정함.
|
||||||
|
|
||||||
|
### [Instance #1640] - 하이라이트 플러그인 의존성 에러
|
||||||
|
- **현상**: 시작/대상 앵커 호버 시 `createExecutionContext` 및 `unhighlight` 함수를 찾을 수 없다는 TypeError 발생.
|
||||||
|
- **원인**: AntV X6의 Highlight 플러그인 의존성 문제.
|
||||||
|
- **조치**: 외부 플러그인 의존성을 제거하고, 노드의 본체(`body`) 속성(`attr`)을 직접 제어하여 강조 효과를 주는 방식으로 로직 선회.
|
||||||
|
|
||||||
|
### [Instance #1628] - `addLabel` 런타임 에러
|
||||||
|
- **현상**: 엣지에 라벨 추가 시 `cell.addLabel is not a function` 에러 발생.
|
||||||
|
- **원인**: X6 특정 버전에서 엣지 객체의 믹스인(Mixin) 상태에 따라 단축 함수가 인식되지 않는 문제.
|
||||||
|
- **조치**: `getLabels()` / `setLabels()` 패턴으로 변경하여 호환성 확보.
|
||||||
|
|
||||||
|
### [Instance #1505] - 직선 라우팅 강제 꺾임 현상
|
||||||
|
- **현상**: 라우터를 제거했음에도 선이 90도로 꺾여 연결됨.
|
||||||
|
- **원인**: X6의 방향성 앵커(`top`, `left` 등)가 경로에 영향을 미침.
|
||||||
|
- **조치**: '직선' 모드일 경우 앵커를 방향성이 없는 `center`로 강제 전환.
|
||||||
|
|
||||||
|
### [Instance #1480] - 레이어 필터링 시 라우팅 설정 유실 (정정됨)
|
||||||
|
- **현상**: 레이어나 줌 변경 시 다시 꺾인 상태로 회귀함.
|
||||||
|
- **원인**: 레이어 'Auto-Repair' 로직이 Vertices를 초기화하지 않음.
|
||||||
|
- **조치**: (구) `setVertices([])` 추가 -> (현) 해당 로직이 사용자 데이터 파괴를 유발하여 최종 삭제 완료 (Instance #2220 참조).
|
||||||
|
|
||||||
|
### [Instance #1466] - Logger 참조 에러
|
||||||
|
- **현상**: `logger is not defined` 발생.
|
||||||
|
- **원인**: `edge.js` 내 모듈 임포트 누락.
|
||||||
|
- **조치**: `logger` 모듈 임포트 추가.
|
||||||
|
|
||||||
|
---
|
||||||
|
*아카이브 관리자: drawNET AI Assistant*
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# drawNET Bug Report - 2026-03-22 🐞
|
||||||
|
|
||||||
|
이 문서는 2026년 03월 22일 발생한 설계 취약점 보완 및 그에 따른 조치 사항을 기록합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [Instance #0001] - 연결선(Edge)의 레이어 정보 유추 의존성 해결
|
||||||
|
- **현상**: 연결선 데이터 자체에 레이어 정보가 없어, 특정 연결선이 크로스 레이어인지 판단하려면 반드시 소스/타겟 노드를 그래프에서 검색해야 함. 이는 외부 도구(LLM 등) 연동 시 데이터 독립성을 저하시키고, 대규모 도면에서 필터링 성능 저하를 유발함.
|
||||||
|
- **원인**: 초기 설계 시 "노드 ID가 있으니 나중에 조회하면 된다"는 유추 방식에 의존함. 데이터 아키텍처 관점에서 객체 단독으로는 자신의 상태(Cross-layer 여부 등)를 완벽히 설명하지 못하는 불완전한 상태였음.
|
||||||
|
- **조치**:
|
||||||
|
1. `static/js/modules/graph/interactions/edges.js`: `edge:connected` 이벤트 발생 시 소스/타겟 노드의 `layerId`를 직접 조회하여 연결선 데이터(`source_layer`, `target_layer`, `is_cross_layer`)에 명시적으로 기록하도록 수정.
|
||||||
|
2. `static/js/modules/graph/io/json_handler.js`: 기존 도면(Legacy data) 로드 시 누락된 레이어 메타데이터를 자동으로 계산하여 보정하는 자가 치유(Self-healing) 로직 추가.
|
||||||
|
3. `6개월 뒤의 나`를 위해 각 관련 파일에 작업 배경 및 이유를 주석으로 명시.
|
||||||
|
- **관련 이전 이슈**: 없음.
|
||||||
|
|
||||||
|
모든 항목은 압축하지 말고, 이전 리포트들보다 더 자세하거나 최소한 동일한 상세도로 작성할 것.
|
||||||
|
절대 요약·생략·간략화하지 마. 각 항목에 구체적인 코드 조각, 이벤트 이름, 함수명, 속성명 등을 최대한 포함.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# drawNET Bug Report - 2026-03-22 🐞
|
||||||
|
|
||||||
|
이 문서는 2026년 03월 22일 발생한 UI 불일치 문제 해결 및 그에 따른 조치 사항을 기록합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [Instance #0002] - 파비콘(Favicon)과 서비스 로고 아이콘 불일치 해결
|
||||||
|
- **현상**: 브라우저 탭에 표시되는 파비콘과 서비스 내부 사이드바 상단의 "dN" 로고 아이콘이 서로 달라 사용자 경험(UX)에 일관성이 없으며, 기존 파비콘의 심미성이 떨어짐.
|
||||||
|
- **원인**: 초기 개발 시 기본 제공되는 `favicon.ico`를 그대로 사용하였으나, 서비스 브랜드 아이덴티티가 "dN" 로고(Blue/Indigo Gradient)로 확립된 이후에도 파비콘이 갱신되지 않음.
|
||||||
|
- **조치**:
|
||||||
|
1. **아이콘 생성**: 사이드바 `.logo-icon`의 CSS 스타일(135도 그라데이션, #3b82f6 ~ #6366f1, 흰색 "dN" 텍스트)을 그대로 반영한 고해상도 PNG 이미지를 생성.
|
||||||
|
2. **파일 교체**: `static/favicon.png` 경로에 새로운 파비콘 이미지 저장.
|
||||||
|
3. **코드 반영**: `templates/index.html`의 `<link rel="icon">` 태그를 새로운 PNG 파비콘으로 교체하여 즉시 반영.
|
||||||
|
- **관련 이전 이슈**: 없음.
|
||||||
|
|
||||||
|
모든 항목은 압축하지 말고, 이전 리포트들보다 더 자세하거나 최소한 동일한 상세도로 작성할 것.
|
||||||
|
절대 요약·생략·간략화하지 마. 각 항목에 구체적인 코드 조각, 이벤트 이름, 함수명, 속성명 등을 최대한 포함.
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# drawNET Bug Report - 2026-03-22 🐞
|
||||||
|
|
||||||
|
이 문서는 2026년 03월 22일 발생한 다크모드 가시성 문제 해결 및 그에 따른 조치 사항을 기록합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [Instance #0003] - 다크모드에서 도움말(온보딩 가이드) 가시성 불량 해결
|
||||||
|
- **현상**: 다크모드 활성화 시, 도움말 모달 내의 텍스트가 배경색과 비슷한 어두운 색으로 표시되어 읽기가 매우 힘듦. (사용자 제보 이미지 확인됨)
|
||||||
|
- **원인**: `static/js/modules/ui/help_modal.js` 내부의 HTML 템플릿에서 텍스트 색상(`#1e293b`, `#334155` 등)과 배경색을 하드코딩된 인라인 스타일로 정의함. 이로 인해 테마가 바뀌어도 색상이 고정되어 대비(Contrast)가 확보되지 않음.
|
||||||
|
- **조치**:
|
||||||
|
1. **테마 변수 적용**: 하드코딩된 색상을 `base.css`에 정의된 CSS 변수(`var(--text-color)`, `var(--sub-text)`, `var(--panel-border)`, `var(--item-bg)`, `var(--accent-color)`)로 전면 교체.
|
||||||
|
2. **가독성 보정**: 정보 박스(`manual/guide.md` 안내)의 배경을 `var(--item-bg)`로 변경하여 다크모드에서도 자연스럽고 선명하게 보이도록 수정.
|
||||||
|
- **관련 이전 이슈**: 없음.
|
||||||
|
|
||||||
|
모든 항목은 압축하지 말고, 이전 리포트들보다 더 자세하거나 최소한 동일한 상세도로 작성할 것.
|
||||||
|
절대 요약·생략·간략화하지 마. 각 항목에 구체적인 코드 조각, 이벤트 이름, 함수명, 속성명 등을 최대한 포함.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# drawNET Bug Report - 2026-03-22 🐞
|
||||||
|
|
||||||
|
이 문서는 2026년 3월 22일 발생한 `help_modal.js` 내의 ReferenceError 해결 및 그에 따른 조치 사항을 기록합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [Instance #2300] - ReferenceError: t is not defined in help_modal.js
|
||||||
|
- **현상**: 도움말 버튼(`help-btn`) 클릭 시 도움말 모달(Help Modal)이 열리지 않고, 브라우저 콘솔에 `Uncaught (in promise) ReferenceError: t is not defined` 에러가 발생하며 인터페이스가 멈추는 현상.
|
||||||
|
- **원인**:
|
||||||
|
1. 마크다운 렌더러 라이브러리(`marked.js`) 통합을 위한 `help_modal.js` 리팩토링 과정에서 실수로 상단의 `import { t } from '../i18n.js';` 구문을 삭제함.
|
||||||
|
2. 모달 템플릿 코드 내에서 다국어 번역 함수인 `${t('help_center')}` 등을 호출할 때 해당 함수가 정의되지 않아 런타임 에러가 발생함.
|
||||||
|
- **조치**:
|
||||||
|
1. `static/js/modules/ui/help_modal.js` 파일 상단에 누락된 `t` 함수 임포트 구문을 즉시 복구함.
|
||||||
|
2. 모달 내의 모든 `t()` 호출부가 정상적으로 바인딩되었는지 검증하고, 모달이 정상적으로 팝업되는 것을 확인함.
|
||||||
|
3. 향후 리팩토링 시 필수 의존성(Dependency) 누락 여부를 체크하도록 코드 리뷰 프로세스 보강.
|
||||||
|
- **관련 이전 이슈**: 없음.
|
||||||
|
|
||||||
|
---
|
||||||
|
*아카이브 관리자: drawNET AI Assistant*
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# drawNET Bug Report - 2026-03-22 🐞
|
||||||
|
|
||||||
|
이 문서는 2026년 3월 22일 발생한 오브젝트 스튜디오 404 에러 해결 및 그에 따른 조치 사항을 기록합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [Instance #2310] - 404 Resource Not Found in Object Studio
|
||||||
|
- **현상**: 오브젝트 스튜디오(Object Studio) 접속 시 다국어 파일(`i18n.js`)과 파비콘(`favicon.ico`)을 찾을 수 없다는 404 에러와 함께 UI의 버튼 글자가 번역되지 않은 상태(Raw Key)로 노출됨.
|
||||||
|
- **원인**:
|
||||||
|
1. **경로 오표기**: `static/js/modules/studio/` 디렉토리 내의 `renderer.js`와 `actions.js`에서 `../../i18n.js` 경로를 사용함. 실제 파일은 `../i18n.js`(`static/js/modules/i18n.js`)에 위치해 있어 상대 경로 계산 오류 발생.
|
||||||
|
2. **파비콘 링크 누락**: `studio.html` 파일에 명시적인 파비콘 링크가 없어 브라우저가 기본값인 `/favicon.ico`를 호출함.
|
||||||
|
- **조치**:
|
||||||
|
1. `renderer.js` 및 `actions.js`의 임포트 경로를 `../i18n.js`로 일괄 수정하여 다국어 모듈 로딩 정상화.
|
||||||
|
2. `studio.html` 상단에 통합 로고(`logo.svg`)를 파비콘으로 사용하는 링크 태그 추가.
|
||||||
|
3. 스튜디오 전체 리소스 로딩 상태를 재점검하여 누락된 정적 파일이 없음을 확인.
|
||||||
|
- **관련 이전 이슈**: Instance #2300 (유사한 Import 문제)
|
||||||
|
|
||||||
|
---
|
||||||
|
*아카이브 관리자: drawNET AI Assistant*
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# drawNET Bug Report - 2026-03-22 🐞
|
||||||
|
|
||||||
|
이 문서는 2026년 3월 22일 발생한 버그 및 장애와 그에 따른 조치 사항을 기록합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [Instance #2310] - 404 Resource Not Found in Object Studio
|
||||||
|
- **현상**: 오브젝트 스튜디오(Object Studio) 접속 시 다국어 파일(`i18n.js`)과 파비콘(`favicon.ico`)을 찾을 수 없다는 404 에러와 함께 UI의 버튼 글자가 번역되지 않은 상태(Raw Key)로 노출됨.
|
||||||
|
- **원인**:
|
||||||
|
1. **경로 오표기**: `static/js/modules/studio/` 디렉토리 내의 `renderer.js`와 `actions.js`에서 `../../i18n.js` 경로를 사용함. 실제 파일은 `../i18n.js`(`static/js/modules/i18n.js`)에 위치해 있어 상대 경로 계산 오류 발생.
|
||||||
|
2. **파비콘 링크 누락**: `studio.html` 파일에 명시적인 파비콘 링크가 없어 브라우저가 기본값인 `/favicon.ico`를 호출함.
|
||||||
|
- **조치**:
|
||||||
|
1. `renderer.js` 및 `actions.js`의 임포트 경로를 `../i18n.js`로 일괄 수정하여 다국어 모듈 로딩 정상화.
|
||||||
|
2. `studio.html` 상단에 통합 로고(`logo.svg`)를 파비콘으로 사용하는 링크 태그 추가.
|
||||||
|
3. 스튜디오 전체 리소스 로딩 상태를 재점검하여 누락된 정적 파일이 없음을 확인.
|
||||||
|
|
||||||
|
### [Instance #2300] - ReferenceError: t is not defined in help_modal.js
|
||||||
|
- **현상**: 도움말 버튼(`help-btn`) 클릭 시 도움말 모달(Help Modal)이 열리지 않고, 브라우저 콘솔에 `Uncaught (in promise) ReferenceError: t is not defined` 에러가 발생하며 인터페이스가 멈추는 현상.
|
||||||
|
- **원인**:
|
||||||
|
1. 마크다운 렌더러 라이브러리(`marked.js`) 통합을 위한 `help_modal.js` 리팩토링 과정에서 실수로 상단의 `import { t } from '../i18n.js';` 구문을 삭제함.
|
||||||
|
2. 모달 템플릿 코드 내에서 다국어 번역 함수인 `${t('help_center')}` 등을 호출할 때 해당 함수가 정의되지 않아 런타임 에러가 발생함.
|
||||||
|
- **조치**:
|
||||||
|
1. `static/js/modules/ui/help_modal.js` 파일 상단에 누락된 `t` 함수 임포트 구문을 즉시 복구함.
|
||||||
|
2. 모달 내의 모든 `t()` 호출부가 정상적으로 바인딩되었는지 검증하고, 모달이 정상적으로 팝업되는 것을 확인함.
|
||||||
|
3. 향후 리팩토링 시 필수 의존성(Dependency) 누락 여부를 체크하도록 코드 리뷰 프로세스 보강.
|
||||||
|
|
||||||
|
### [Instance #2290] - Incorrect Filtering in PPTX Details Slide
|
||||||
|
- **현상**: PPTX 내보내기 시 "객체별 상세 내용 및 주석 목록" 슬라이드에서 실제 노드(오브젝트)는 보이지 않고, UUID 형태의 이름을 가진 선(edge)들만 대거 나열되는 현상.
|
||||||
|
- **원인**:
|
||||||
|
1. 상세 목록 필터가 `description` 속성이 있는 객체만 찾도록 되어 있어, 설명이 없는 일반 노드들은 모두 제외됨.
|
||||||
|
2. 수동 경로 오프셋(`routing_offset`)이 있는 엣지들이 필터에 포함되면서, 라벨이 없는 일반 엣지들이 UUID와 함께 노출됨.
|
||||||
|
- **조치**:
|
||||||
|
1. 상세 목록 필터를 개선하여 **모든 일반 노드**를 포함하도록 수정 (단, 그룹은 설명이 있을 때만 포함).
|
||||||
|
2. 엣지는 **사용자 정의 라벨이나 설명이 있을 때만** 포함하도록 변경하여 무의미한 항목 제거.
|
||||||
|
3. 라벨이 없는 엣지의 경우 UUID 대신 `[시작노드] -> [대상노드]` 형태의 읽기 쉬운 이름을 자동 생성하도록 보완.
|
||||||
|
|
||||||
|
### [Instance #2280] - Asset Filter Modal Invisibility
|
||||||
|
- **현상**: 왼쪽 에셋 라이브러리 상단의 필터(패키지 관리) 아이콘을 클릭해도 아무런 반응이 없는 것처럼 보이는 현상.
|
||||||
|
- **원인**:
|
||||||
|
1. `selector.js`에서 모달 팝업 생성 시 `modal.style.display = 'flex'`만 사용함.
|
||||||
|
2. 공통 CSS(`modal.css`)에서 `.modal-overlay`는 기본적으로 `opacity: 0`이며 `.active` 클래스가 있어야만 화면에 표시되도록 정의되어 있어 시각적으로 숨겨진 상태였음.
|
||||||
|
3. 구형 코드인 `static/js/modules/assets.js` 파일이 잔류하여 디버깅 및 유지보수에 혼선을 줌.
|
||||||
|
- **조치**:
|
||||||
|
1. `selector.js`의 모달 제어 로직을 `classList.add('active')` 및 `remove('active')` 방식으로 변경하여 CSS 애니메이션 및 가시성 확보.
|
||||||
|
2. 사용되지 않는 구형 `assets.js` 파일을 삭제하여 모듈 구조 단순화.
|
||||||
|
|
||||||
|
### [Instance #2275] - Selection Highlight Disappears during Edit
|
||||||
|
- **현상**: 선(edge) 속성창에서 시작/대상 호버 하이라이트가 표시된 상태에서 색상이나 스타일을 변경하면 하이라이트가 즉시 사라짐.
|
||||||
|
- **원인**: `renderProperties()` 시작 부분에 `unhighlight()`가 포함되어 있어, 속성 변경에 따른 리렌더링 발생 시마다 하이라이트가 강제로 초기화됨.
|
||||||
|
- **조치**:
|
||||||
|
1. `renderProperties()` 내부의 상시 `unhighlight()` 호출 제거.
|
||||||
|
2. 선택된 객체가 변경될 때 발생하는 `selection:changed` 이벤트 핸들러에서만 `unhighlight()`를 호출하도록 로직 위치 변경.
|
||||||
|
3. 이를 통해 동일 객체의 속성을 수정하는 동안에는 시각적 하이라이트가 유지되도록 정합성 확보.
|
||||||
|
|
||||||
|
### [Instance #2270] - Infinite Loop in Sidebar Rendering (Selection Loop)
|
||||||
|
- **현상**: 선(edge) 선택 시 브라우저 콘솔에 `renderProperties start` 로그가 무한히 출력되며 성능이 저하되는 현상.
|
||||||
|
- **원인**:
|
||||||
|
1. 선 선택 시 호출되는 `unhighlight()`가 노드의 `attr`을 수정함.
|
||||||
|
2. `attr` 수정이 `cell:change:attrs` 이벤트를 발생시킴.
|
||||||
|
3. 해당 이벤트 리스너가 다시 `renderProperties()`를 호출하여 무한 루프가 발생함.
|
||||||
|
- **조치**:
|
||||||
|
1. `unhighlight` 및 `highlight` 호출 시 모든 속성 변경에 `{ silent: true }` 옵션을 부여하여 불필요한 이벤트 전파 차단.
|
||||||
|
2. `cell:change` 이벤트 리스너에서 `options.silent` 플래그를 확인하여 내부적인 시각 효과 변경 시에는 리렌더링을 건너뛰도록 개선.
|
||||||
|
|
||||||
|
### [Instance #2265] - Sticky Source/Target Highlights in Sidebar
|
||||||
|
- **현상**: 엣지 속성창에서 시작/대상 선택 상자에 마우스를 올렸을 때 나타나는 노드 강조(테두리)가 사이드바를 닫거나 선택을 바꿔도 사라지지 않고 유지됨.
|
||||||
|
- **원인**: 하이라이트 제거 로직(`unhighlight`)이 오직 `mouseleave` 이벤트에만 바인딩되어 있어, 사이드바를 닫거나 다른 객체로 전환 시 이벤트가 트리거되지 않음.
|
||||||
|
- **조치**:
|
||||||
|
1. `unhighlight` 로직을 모듈 레벨 함수로 분리.
|
||||||
|
2. 사이드바 닫기 버튼, 적용 버튼 클릭 시 및 속성창 리렌더링(`renderProperties`) 시작 시점에 항상 `unhighlight`를 호출하도록 보강하여 잔상 방지.
|
||||||
|
|
||||||
|
### [Instance #2260] - Edge Rendered Underneath Groups (Z-Index Hierarchy)
|
||||||
|
- **현상**: 새로 생성된 선(edge)이 기존 그룹의 배경 뒤로 숨어서 보이지 않거나 선택이 어려운 현상.
|
||||||
|
- **원인**: 선 생성 시 레이어 오프셋이 적용되지 않은 기본 z-index(30)가 부여되어, 이미 레이어 가중치(101, 201 등)를 받은 그룹보다 낮게 배치됨.
|
||||||
|
- **조치**:
|
||||||
|
1. `layers.js`에 중앙 집중형 z-index 계산 함수(`calculateCellZIndex`)를 도입하여 레이어 오프셋과 객체 타입별 우선순위(노드>선>그룹)를 통합 관리.
|
||||||
|
2. 선 및 노드 생성 시(`config.js`, 스타일 매핑 모듈) 즉시 해당 함수를 호출하여 초기 z-index를 올바르게 할당하도록 구조 개선.
|
||||||
|
|
||||||
|
---
|
||||||
|
*아카이브 관리자: drawNET AI Assistant*
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
# drawNET 업데이트 로그 (Changelog)
|
||||||
|
|
||||||
|
## [v1.0 Alpha-1] - 2026-03-21
|
||||||
|
### 🎉 Project Milestone: Release Alpha Version
|
||||||
|
오늘로써 drawNET의 모든 핵심 엔진 기능이 구현되어 공식 **v1.0 Alpha-1** 버전으로 명명합니다.
|
||||||
|
|
||||||
|
## [2026-03-21] - High-Fidelity 하이브리드 내보내기 슈트 및 안정성 고도화
|
||||||
|
|
||||||
|
### 🚀 신규 기능 (Added)
|
||||||
|
- **엣지 라우팅 및 앵커 가시성 제어 거버넌스 수립**:
|
||||||
|
- **시작/대상(S/T) 가이드 라벨**: 엣지 선택 시 양 끝에 초록/빨강 라벨을 표시하여 망 구성 식별력 강화.
|
||||||
|
- **인터랙티브 앵커 하이라이트**: 사이드바 앵커 설정 시 캔버스 내 노드 실시간 점멸 피드백 제공.
|
||||||
|
- **직선 라우팅 최우선 순위화**: 앵커 방향 제약 및 전역 기본 설정을 극복한 '완벽한 직선' 모드 구현.
|
||||||
|
- **Format Painter (Style Copy/Paste) 엣지 확장**:
|
||||||
|
- `Ctrl+Shift+C/V`를 통해 선의 색상, 스타일, 라우팅, 화살표 정보를 다른 선으로 즉시 복제.
|
||||||
|
- **UX 편의 기능**:
|
||||||
|
- **ESC 전역 캔슬**: 원터치로 모든 선택 해제 및 사이드바 닫기.
|
||||||
|
- **단축키 도움말(?) 애니메이션 복구**: 누락된 리소스를 보강하여 도움말 모달 가독성 개선.
|
||||||
|
- **객체 메타데이터 및 인벤토리 거버넌스**:
|
||||||
|
- **상세 설명(Description) 및 태그(Tags) 도입**: 모든 노드와 엣지에 설계 의도 및 기술 사양을 기록할 수 있는 전용 필드 추가.
|
||||||
|
- **3중 데이터 무결성 보장 로직**: 구버전 파일 자동 마이그레이션, 복사/붙여넣기(`Ctrl+C/V`) 시 전역 데이터 새니타이저(Sanitizer)를 통한 필수 필드 누락 원천 봉쇄.
|
||||||
|
- **엔터프라이즈급 PPTX 리포팅 고도화**:
|
||||||
|
- **상세 설명 전용 슬라이드**: `autoPage` 기능을 활용하여 대규모 프로젝트의 모든 객체 상세 주석을 표 형태로 자동 생성.
|
||||||
|
- **인벤토리 매핑 강화**: 상세 목록에 '소속 그룹(Parent Group)' 및 '태그' 컬럼을 추가하여 외부 자산 데이터와의 매칭 편의성 제고.
|
||||||
|
- **High-Fidelity 하이브리드 내보내기 슈트 (v2.0)**:
|
||||||
|
- **SVG 내보내기 엔진 완전 재설계**: 전수 이미지 스캔 로직을 통해 상속된 아이콘까지 100% Base64로 임베딩하여 '아이콘 깨짐' 현상 종결.
|
||||||
|
- **절대 좌표 기반 짤림 방지 (Zero-Clip)**: `getCellsBBox`와 명시적 `viewBox` 주입을 통해 화면 줌/위치와 무관하게 120px 안전 여백이 포함된 완벽한 도면 출력.
|
||||||
|
- **전문가용 내보내기 UI**: 내보내기 프로세스 시작 시 "파일 준비 중" 안내 오버레이와 블러 효과를 적용하여 사용자 피드백 및 경험(UX) 고도화.
|
||||||
|
- **적응형 안정화 지연 (1000ms)**: 모델 업데이트 후 DOM 동기화를 위해 1초의 대기 시간을 부여하여 저사양 PC에서도 무결한 캡처 보장.
|
||||||
|
- **전문가용 PPTX 리포트**: 도면은 고해상도 PNG 스냅샷으로, 데이터는 편집 가능한 네이티브 표(Table)로 구성하는 하이브리드 모델 도입.
|
||||||
|
- **레이어별 자동 슬라이드 생성**: 멀티 레이어 프로젝트를 분석하여 레이어별 상세 도면 슬라이드를 자동으로 생성하는 엔진 구축.
|
||||||
|
- **초정밀 클린 캡처 (Clean Capture)**:
|
||||||
|
- 내보내기 시 앵커(포트), 선택 박스, UI 가이드를 자동으로 숨겨 시각적 노이즈를 제거하는 로직 반영.
|
||||||
|
- **2x 해상도 렌더링**: 모든 이미지 기반 내보내기 시 200% 스케일업을 통해 레티나 디스플레이 및 대형 인쇄 대응 수준의 화질 확보.
|
||||||
|
- **사이드바 데이터 정합성 및 실시간 동기화 슈트**:
|
||||||
|
- **재귀적 데이터 전파(Recursive Poke)**: 그룹 이름 변경 시 하위 모든 객체들을 대상으로 동기화 이벤트를 강제 트리거하여 사이드바의 상위 그룹 정보가 실시간으로 갱신되도록 개선 (#2245).
|
||||||
|
- **세이프 렌더(Safe Render) 및 포커스 유실 방지**: 사이드바 입력 중 그래프 변화에 의한 리렌더링을 지능적으로 차단하여 타이핑 연속성 확보 (#2255).
|
||||||
|
- **비동기 데이터 렌더링 타이즈**: `setTimeout(0)`을 적용하여 X6 엔진 데이터와 UI 간의 미세한 타이밍 불일치 완전 소거.
|
||||||
|
- **레이어 격리 및 상호작용 보안 거버넌스**:
|
||||||
|
- **선택 필터링(Selection Filter)**: 활성 레이어가 아닌 객체는 마우스로 선택할 수 없도록 플러그인 레벨에서 격리 구현.
|
||||||
|
- **상호작용 가드**: 비활성 레이어 객체에 대한 더블 클릭 등 파괴적 액션을 원천 차단하여 레이어 간 데이터 오염 방지.
|
||||||
|
- **개발 생산성 도구**:
|
||||||
|
- **`logger.debug` 정식 지원**: 개발 중 임시 로그를 위한 별도의 디버그 레벨과 서식 도입.
|
||||||
|
- **하이엔드 레이어 안정성 및 데이터 거버넌스 수립**:
|
||||||
|
- **논리 전용 레이어(Logical-Only Layer)**: 물리 배치와 논리 연결의 명확한 분리를 위한 레이어 타입 규격 도입 및 지능형 드롭/복제 필터링 엔진 구축.
|
||||||
|
- **레이어 하드-락(Hard-Lock) 시스템**: 중요 레이어의 실수에 의한 삭제나 이름 변경을 원천 차단하는 자물쇠 기능 도입.
|
||||||
|
- **데이터 보호 이중 확인(Double Confirmation)**: 오브젝트가 포함된 레이어 삭제 시 '항목 수 안내'와 '최종 파기 확인'의 2단계 절차를 거치는 안전 로직 적용.
|
||||||
|
- **레이어 전용 휴지통(Trash Bin)**: 드래그 앤 드롭으로 레이어를 명시적으로 버릴 수 있는 직관적인 폐기 워크플로우 구현.
|
||||||
|
- **레이어 패널 UI 최적화**: 가시성과 조작 편의성 개선을 위해 레이어 관리창 전체 크기를 약 25% 확대 (250px x 400px).
|
||||||
|
- **멀티 플랫폼 및 브라우저 호환성 하모니**:
|
||||||
|
- **Mac Command(⌘) 키 완벽 지원**: Mac 환경에서도 Cmd 키를 사용해 복제 및 단축키 기능을 동일하게 사용 가능.
|
||||||
|
- **자기 치유형 키 상태 관리(Self-healing)**: 탭 전환 등으로 인한 키 업/다운 이벤트 유실 시에도 마우스 활동 감지를 통해 시스템 키 상태를 자동 보정.
|
||||||
|
- **ESC 전역 캔슬 확장**: 드래그/복제 도중 `Escape` 키로 즉시 취소 및 고스트 박스 자동 소거 로직 고도화.
|
||||||
|
- **포맷 페인터(Format Painter) 데이터 무결성 수정**:
|
||||||
|
- 선(Edge)의 색상 및 두께 복사 시 UI 기본값보다 클립보드 데이터를 최우선(Strict Prioritization)으로 하여 속성이 누락되던 현상 해결.
|
||||||
|
- **하이엔드 안정성 및 논리 레이어 정책 거버넌스 수립**:
|
||||||
|
- **논리 전용 레이어(Logical-Only Layer)**: 물리 배치와 논리 연결의 명확한 분리를 위한 레이어 타입 규격 도입 및 지능형 드롭/복제 필터링 엔진 구축.
|
||||||
|
- **멀티 플랫폼(Windows/Mac) 하모니**: Cmd/Ctrl 키 통합 지원 및 자기 치유형(Self-healing) 키 상태 관리 로직 적용.
|
||||||
|
- **ESC 드래그 캔슬**: 복제 드래그 중 Escape 키로 즉석 취소 및 고스트 박스 자동 소거 로직 반영.
|
||||||
|
|
||||||
|
### 🛠️ 구조 개선 (Refactored)
|
||||||
|
- **내보내기 서브시스템(Export Subsystem) 모듈화**: `image_exporter.js`, `pptx_exporter.js` 등으로 로직을 분리하여 확장성 강화.
|
||||||
|
- **UI 시각적 가시성 개선**: 라이트 모드(White Mode)에서 플라이아웃 메뉴의 호버 대비(Contrast)를 액센트 컬러로 교체하여 접근성 및 전문성 강화.
|
||||||
|
- **중복 초기화 제거 (Zero Duplication)**: `app.js` 내로 핵심 모듈 초기화 로직을 일원화하여 중복 팝업 및 이벤트 리스너 누수 문제 원천 해결.
|
||||||
|
|
||||||
|
### 🐛 버그 수정 (Fixed)
|
||||||
|
- **레이어 데이터 손실 방지**: 새로고침 시 레이어 정보가 유실되던 데이터 영속성 결함 수정.
|
||||||
|
- **아이콘 매핑 정확도 향상**: `assetId` 기반의 정밀 매핑 및 경로 복제 방지 로직을 통해 PPTX 내 아이콘 404 오류 해결.
|
||||||
|
- **그룹 노드 렌더링 정상화**: PPTX 내에서 그룹 객체가 거대한 이미지로 보이던 현상을 투명도를 가진 네이티브 사각형으로 수정.
|
||||||
|
|
||||||
|
### ✅ 완료된 마일스톤
|
||||||
|
- **Phase 1.50**: High-Fidelity 하이브리드 내보내기 거버넌스 수립 완료
|
||||||
|
- **Phase 1.55**: 시스템 초기화 및 안정성 전수 감사(Full Audit) 완료
|
||||||
|
- **Phase 1.60**: 로컬 독립 실행 및 오프라인 리포팅 환경 구축 완료
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2026-03-20] - i18n 거버넌스 및 아키텍처 전략 고도화
|
||||||
|
|
||||||
|
### 🚀 신규 기능 (Added)
|
||||||
|
- **전역 다국어(i18n) 통합 지원 (ko/en)**:
|
||||||
|
- 브라우저 언어 설정 및 사용자 선택에 대응하는 실시간 로케일 전환 엔진 구축.
|
||||||
|
- `en.json` 신설을 통한 영미권 사용자 지원 기반 마련.
|
||||||
|
- **기술 용어 직관화 (Terminology Optimization)**:
|
||||||
|
- 난해한 공학 용어를 직관적인 한국어로 교체: `Manhattan` -> `자동 경로 (장애물 회피)`, `U-Shape` -> `ㄷ자 경로`, `Orthogonal` -> `수직/수평 (일반)`.
|
||||||
|
- **PPTX 아키텍처 리포트 다국어화**:
|
||||||
|
- 생성되는 모든 PPTX 보고서의 헤더, 요약 표, 인벤토리 항목을 설정 언어에 맞춰 동적 렌더링하도록 개선.
|
||||||
|
|
||||||
|
### 🛠️ 구조 개선 (Refactored)
|
||||||
|
- **하드코딩 문자열 제로화 (Hardcoding-Free)**:
|
||||||
|
- `Object Studio`, `Settings`, `Persistence`, `UI Templates` 등 시스템 전반의 텍스트를 로케일 파일로 분리 관리.
|
||||||
|
- **아키텍처 문서(`architecture.md`) 대대적 보강**:
|
||||||
|
- **Section 4 (Multi-Layer Isolation)**: 물리/논리/보안 레이어 간 SSOT(Single Source of Truth) 보장을 위한 데이터 평면 설계 명문화.
|
||||||
|
- **Section 5 (PM Assets & Expansion)**: 외부 자산 연동 및 REST API 확장 전략 구체화.
|
||||||
|
- **Section 8 (AI-Ready Strategy)**: 정규화된 JSON 구조를 통한 AI 분석 및 네트워크 시뮬레이션 가치 강조.
|
||||||
|
- **로케일 데이터 무결성 확보**: JSON 내 중복 키 전수 제거 및 정적 분석 오류 해결.
|
||||||
|
|
||||||
|
### 🚀 기존 Premium UX 업데이트 내역 (이전 작업)
|
||||||
|
- **오브젝트 적층 순서 제어 (Z-Index Control)**: `[` (뒤로 보내기), `]` (앞으로 가져오기) 단축키 지원.
|
||||||
|
- **오브젝트 정밀 잠금 (Full Object Locking)**: `Ctrl + L` 및 시각적 피드백(Red Dashed Boundary) 구현.
|
||||||
|
- **재귀적 오브젝트 선택 (Recursive Selection)**: `Ctrl + 마우스 우클릭`을 통한 Surgical Picker 도입.
|
||||||
|
- **프리미엄 레이어 패널**: 레이어 이름 변경 및 비활성 레이어 투명도 조절 기능.
|
||||||
|
|
||||||
|
### ✅ 완료된 마일스톤
|
||||||
|
- **Phase 1.30**: 전역 다국어 통합 거버넌스 및 하드코딩 제거 완료
|
||||||
|
- **Phase 1.35**: 아키텍처 설계서(architecture.md) 엔지니어링 표준화 완료
|
||||||
|
- **Phase 1.40**: UI/UX 용어 고도화 및 전문가 DX 공식 수립
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2026-03-19] - Object Studio & Asset Infrastructure 고도화
|
||||||
|
|
||||||
|
### 🚀 신규 기능 (Added)
|
||||||
|
- **Object Studio 전용 페이지 (`/studio`)**: 독립된 환경에서 자산을 제작하고 관리할 수 있는 전문 도구 세트 구현.
|
||||||
|
- **하이브리드 벡터 변환 엔진**: Potrace-js를 통합하여 PNG 비트맵 이미지를 고품질 SVG `<path>` 데이터로 실시간 변환하는 기능 추가.
|
||||||
|
- **멀티 벤더 패키징 시스템**:
|
||||||
|
- 에셋을 벤더 단위 팩(`package.json`)으로 묶어 물리적 디렉토리에 저장하는 서버 API 구현.
|
||||||
|
- 저장된 패키지가 설계 영역(Designer) 라이브러리에 실시간 로드되는 인프라 구축.
|
||||||
|
- **인제스터(Ingester) 개선**: 폴더 단위 업로드(`webkitdirectory`) 및 드래그 앤 드롭을 통한 대량 자산 일괄 로드 지원.
|
||||||
|
- **리뷰 및 선택 UI**: 원본과 변환본을 나란히 비교하고, 사용자 취향에 따라 포맷을 선택할 수 있는 Side-by-Side 뷰 도입.
|
||||||
|
|
||||||
|
### 🛠️ 구조 개선 (Refactored)
|
||||||
|
- **스튜디오 프론트엔드 모듈화**: `Renderer`, `Actions`, `State`, `Processor` 등으로 로직을 분리하여 확장성 및 유지보수성 확보.
|
||||||
|
- **백엔드 자산 스캔 로직 최적화**: `api_routes.py` 내 멀티 패키지 로딩 엔진 고도화.
|
||||||
|
- **명명 규칙 자동화**: 파일명을 기반으로 슬러그(Slug) 형태의 ID와 라벨을 자동 생성하는 로직 반영.
|
||||||
|
|
||||||
|
### ✅ 완료된 마일스톤
|
||||||
|
- **Phase 1**: 데이터 규격 및 멀티 로더 (Vendor Pack 시스템)
|
||||||
|
- **Phase 2**: 스튜디오 기반 구축 (라우팅, 레이아웃, 인제스천)
|
||||||
|
- **Phase 3**: 핵심 가공 기능 (SVG 트레이싱, 비교 검수)
|
||||||
|
- **Phase 4**: 패키징 및 설계영역 최종 연동 (Build & Save)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2026-03-18] - UX & Precision Tools 고도화
|
||||||
|
|
||||||
|
### 🚀 신규 기능
|
||||||
|
- **단축키 UX 고도화**: 컨텍스트 메뉴 내 단축키 힌트 노출 및 `Alt + 1` 사이드바 토글 등 전문 도구 수준의 단축키 전략 확립.
|
||||||
|
- **신규 객체 '리치 텍스트 카드(Rich Text Card)' 출시**:
|
||||||
|
- 아키텍처 도면의 '구축 내역' 및 '범례(Legend)'를 위한 전문 문서화 객체 추가.
|
||||||
|
- 지능형 리스트 렌더링 (번호 사각형, 불렛 기호 자동 생성).
|
||||||
|
- 범례 모드 지원: `- (도형:색상) 설명` 텍스트 기반 시각화 자동화.
|
||||||
|
- **Format Painter (Style Copy/Paste) 엣지 확장**: `Ctrl+Shift+C/V`를 통해 노드뿐만 아니라 선(Edge)의 라우팅, 색상, 스타일을 일괄 복사/적용 가능.
|
||||||
|
- **ESC 키 전역 캔슬**: `Esc` 키로 선택 해제 및 사이드바 닫기 지원.
|
||||||
|
- **다중 선택 정렬 패널**: 여러 노드 선택 시 사이드바에 즉시 정렬 및 스타일 편집 옵션 표시.
|
||||||
|
|
||||||
|
### 🐛 버그 수정
|
||||||
|
- **캔버스 팬닝 충돌 해결**: Space 키 드래그 시 Rubberband 선택과 겹치는 현상 수정 (인터랙션 명시적 비활성화).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [이전 로그] - 프로젝트 초기화 및 핵심 엔진 구축
|
||||||
|
- AntV X6 그래프 엔진 기반 마이그레이션 완료.
|
||||||
|
- DSL(Domain Specific Language) 파싱 및 캔버스 동기화 인프라 구축.
|
||||||
|
- 그룹화, 라우팅, 인벤토리 등 핵심 기능 순차적 도입.
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# drawNET 기능 명세서 (v1.0 Alpha)
|
||||||
|
|
||||||
|
현재 구현된 주요 기능들의 목록과 상세 설명입니다. "이 문서에 없는 기능은 구현되지 않은 것으로 간주합니다."
|
||||||
|
|
||||||
|
## 1. 프로젝트 관리 (Project Management)
|
||||||
|
- **JSON 기반 SSoT**: `graph.toJSON()`과 `graph.fromJSON()`을 사용하여 프로젝트 상태(노드, 엣지, 위치, 스타일 등)를 완벽하게 보존.
|
||||||
|
- **자동 저장 및 세션 복구**: `persistence.js`를 통해 변경 사항 발생 시 브라우저 LocalStorage에 실시간 자동 저장.
|
||||||
|
- **임포트/익스포트**: `.json` 또는 `.dnet` 확장자로 프로젝트 파일을 내보내고 가져오기 지원.
|
||||||
|
|
||||||
|
## 2. 그래프 캔버스 (Topology Canvas)
|
||||||
|
- **AntV X6 v2.x 엔진**: 엔터프라이즈 레벨의 고성능 그래프 렌더링.
|
||||||
|
- **UUID 아키텍처**: 모든 오브젝트에 고유 ID를 부여하여 라벨 중복과 무관하게 데이터 무결성 보장.
|
||||||
|
- **정밀 직교 라우팅 (Manhattan)**: 선 꼬임 방지 및 직각 연결 유지 알고리즘 적용.
|
||||||
|
- **스마트 앵커 (Smart Anchors)**: 상/하/좌/우 포트 고정을 통한 정교한 경로 제어.
|
||||||
|
|
||||||
|
## 3. 지능형 그룹화 (Smart Grouping)
|
||||||
|
- **컨테이너-자식 관계**: 드래그 앤 드롭을 통한 계층 구조 형성.
|
||||||
|
- **가변 패딩 조절**: 속성 사이드바를 통해 그룹 내부 여백 실시간 조정 가능.
|
||||||
|
- **UUID 계층 구조**: 부모-자식 관계가 UUID로 관리되어 노드 이동 및 이름 변경 시에도 계층 유지.
|
||||||
|
- **최하위 그룹 자동 인식 (Innermost Selection)**: 그룹이 중첩되거나 겹쳐 있는 경우, 시스템이 자동으로 가장 면적이 작은(가장 안쪽의) 그룹을 부모로 선택하여 정밀한 계층 관리 지원.
|
||||||
|
|
||||||
|
## 4. 사이드바 및 속성 제어 (Sidebar & Properties)
|
||||||
|
- **자산 라이브러리**: 카테고리별 자산 트리 및 실시간 검색 필터링.
|
||||||
|
- **상세 속성 편집**:
|
||||||
|
- 노드: 타입, 라벨, 부모(UUID), IP, 상태, 벤더, 커스텀 어셋 경로 등.
|
||||||
|
- 엣지: 타입, 스타일, 색상, 라우팅 방식, 앵커 위치, 터널링 여부 등.
|
||||||
|
- **자동 사이드바 오픈**: 오브젝트 선택 시 해당 속성 창 자동 노출.
|
||||||
|
|
||||||
|
## 5. 정밀 배치 및 협업 도구 (Precision Tools)
|
||||||
|
- **인벤토리 패널**: 타입별 자산 수량 실시간 집계 및 CSV 내보내기 지원 (`Alt + 0`).
|
||||||
|
- **서식 복사 (Format Painter)**: `Ctrl + Shift + C/V`를 통해 노드 및 엣지(선)의 스타일을 일괄 복제.
|
||||||
|
| 구분 | 대상 | 복사/붙여넣기 항목 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **노드** | 데이터 & 디자인 | 제조사, 모델, 상태, 프로젝트, 태그, 배경색, 글꼴 크기/색상 등 |
|
||||||
|
| **엣지** | 라우팅 & 스타일 | **직선/직각 모드**, 선 색상, 두께, 점선 여부, 시작/대상 앵커 위치 등 |
|
||||||
|
- **오브젝트 적층 제어 (Stacking Order)**: `[` (Send to Back), `]` (Bring to Front) 단축키를 사용하여 겹친 오브젝트의 상하 순서 조절.
|
||||||
|
- **물리적 객체 잠금 (Object Locking)**: `Ctrl + L` 또는 속성창의 스위치를 통해 이동, 삭제, 회전을 원천 차단. 잠긴 객체는 **빨간색 점선 경계**로 표시됨.
|
||||||
|
- **재귀적 오브젝트 선택 (Recursive Picker)**: `Ctrl + 마우스 우클릭` 시 마우스 포인터 위치에 중첩된 모든 오브젝트 목록을 팝업하여, 복잡한 레이어 구조에서 원하는 항목만 정밀하게 선택 가능.
|
||||||
|
- **비주얼 디프 (Visual Diff)**: JSON 스냅샷 비교를 통해 이전 상태 대비 변경된 위치 및 추가된 노드 하이라이트.
|
||||||
|
- **드래그 복제**: `Ctrl + Drag`를 통한 즉시 노드 복제 (고스트 박스 기반 실시간 프리뷰).
|
||||||
|
- **하이엔드 안정성(Failsafe) 설계**:
|
||||||
|
- **ESC 즉시 취소**: 드래그 또는 복제 중 ESC 키 입력 시 모든 상태 초기화 및 고스트 제거.
|
||||||
|
- **자기 치유형 키 상태(Self-healing)**: 브라우저 탭 전환 등으로 인한 키 업/다운 이벤트 유실 시, 마우스 활동 감지로 실제 키 상태 자동 재동기화.
|
||||||
|
- **멀티 플랫폼(Mac) 지원**: Windows(Ctrl)와 Mac(Command) 키를 통합 지원하여 동일한 복제 워크플로우 제공.
|
||||||
|
|
||||||
|
## 6. 오브젝트 스튜디오 (Object Studio)
|
||||||
|
- **대량 이미지 업로드**: 드래그 앤 드롭으로 다량의 에셋 일괄 처리.
|
||||||
|
- **PNG to SVG 벡터 변환**: Potrace-js 엔진을 활용한 고품질 벡터화.
|
||||||
|
- **실시간 비교 뷰어**: 비트맵과 벡터 결과물의 실시간 품질 대조.
|
||||||
|
- **벤더 패키징**: 가공된 에셋을 라이브러리로 즉시 패키징하여 반영.
|
||||||
|
|
||||||
|
## 7. 단축키 시스템 (Hotkeys - Premium)
|
||||||
|
- **Alt + 0/1**: 인벤토리 / 속성 사이드바 토글.
|
||||||
|
- **Alt + 9**: 레이어 패널 토글.
|
||||||
|
- **Shift + A/D**: 빠른 선 연결 / 끊기.
|
||||||
|
- **Shift + 1~6**: 오브젝트 자동 정렬 (Alignment).
|
||||||
|
- **Shift + 7~8**: 오브젝트 균등 분배 (Distribution).
|
||||||
|
- **F2**: 선택된 오브젝트의 라벨 즉시 편집.
|
||||||
|
- **Directional Keys**: 5px / 그리드 단위(Shift) 정밀 이동.
|
||||||
|
|
||||||
|
## 8. 논리적 계층화 (Multi-Layer Support)
|
||||||
|
- **레이어 기반 가시성 제어**: 무제한 논리적 레이어 생성 및 개별 Visible 토글.
|
||||||
|
- **비활성 레이어 투명도 (Opacity Control)**: 슬라이더를 통해 배경 레이어의 투명도를 0~100%로 조절하여 집중도 향상.
|
||||||
|
- **레이어 리네임 (Rename)**: 편집 아이콘(Pencil)을 사용하여 레이어 이름을 직관적으로 변경하고 영속 저장.
|
||||||
|
- **액티브 레이어 자동 할당**: 현재 선택된 레이어에 신규 오브젝트 자동 귀속.
|
||||||
|
- **논리 전용 레이어 (Logical-Only Layer)**:
|
||||||
|
- 레이어별로 **'장비(Physical)'** 또는 **'연결(Logical)'** 성격 부여 가능.
|
||||||
|
- 논리 레이어에서는 **노드 드롭 차단** 및 **지능형 노드 필터링(복제 시 선만 추출)** 기능을 통해 설계 실수 방지 및 시뮬레이션 효율 극대화.
|
||||||
|
- **지능형 적층 계층 (Z-Index Hierarchy)**: `Group (1) < Edge (30) < Node (50)` 계층을 기본으로 하여, 연결선이 그룹에 가려지지 않고 항상 인터랙션 가능하도록 최적화.
|
||||||
|
|
||||||
|
## 9. 시스템 데이터 무결성 (Data Integrity & Recovery)
|
||||||
|
- **자동 데이터 자가 치유(Self-healing)**: 프로젝트 로드 시 유실된 좌표(`pos`), 크기(`size`), 라벨(`label`) 등을 `data` 필드로부터 추론하여 자동으로 복구 및 동기화.
|
||||||
|
- **명시적 저장 모델 (Explicit Property Apply)**: 사이드바 텍스트 수정 시 실시간 반영 대신 **[적용]** 버튼이나 **Enter** 키를 통한 명시적 확정 방식을 채택하여, 입력 도중의 포커스 유실을 방지하고 데이터 경합 최소화.
|
||||||
|
- **명시적 연결선 메타데이터 (Explicit Edge Layers)**:
|
||||||
|
- 모든 연결선에 `source_layer`, `target_layer`, `is_cross_layer` 정보를 명시적으로 기록.
|
||||||
|
- 이를 통해 전체 도면 데이터 없이도 연결성(Connectivity)을 독립적으로 해석 가능하게 하며, 외부 도구 및 LLM 분석 시의 정밀도를 대폭 향상.
|
||||||
|
|
||||||
|
---
|
||||||
|
- **마지막 업데이트**: 2026-03-22 (명시적 연결선 메타데이터 추가, Z-Index 계층화, 데이터 자가치유 로직)
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# drawNET Maintenance Guide (AI 개발자 필독)
|
||||||
|
|
||||||
|
이 문서는 drawNET 프로젝트의 연속성을 유지하기 위한 **핵심 기술 규칙 및 유지보수 지침**을 담고 있습니다. 새로운 세션의 AI 개발자는 작업을 시작하기 전 본 문서를 반드시 숙지하여 기존의 설계 철학이 깨지지 않도록 해야 합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## ⌨️ 3. 단축키 시스템 (Hotkeys)
|
||||||
|
|
||||||
|
- **규칙**:
|
||||||
|
1. `static/hotkeys.json`에서 `undo`와 `redo` 액션은 반드시 **`"alwaysEnabled": true`** 속성을 가져야 합니다.
|
||||||
|
2. `static/js/modules/hotkeys.js`는 `isTyping` 상태(텍스트 입력 중)일지라도 `alwaysEnabled`가 참인 단축키는 차단하지 않고 실행해야 합니다.
|
||||||
|
3. **이유**: 예기치 않게 특정 입력창에 포커스가 머물러 있더라도 그래프의 Undo/Redo 기능은 항상 작동해야 하기 때문입니다.
|
||||||
|
|
||||||
|
## 🎨 4. 오브젝트 스튜디오 (Studio)
|
||||||
|
|
||||||
|
- **규칙**:
|
||||||
|
1. **ID 고유성**: `static/js/modules/studio/state.js`의 `addSource(file)` 함수는 파일명이 중복될 경우(예: `icon.png`, `icon.png`) `id` 뒤에 `_1`, `_2` 등의 접미사를 붙여 반드시 고유한 ID를 생성해야 합니다.
|
||||||
|
2. **ID 생성 방식**: 소문자 영문과 숫자 위주로 변환하며, 공백이나 특수문자는 언더바(`_`)로 치환합니다.
|
||||||
|
|
||||||
|
## 📂 5. 에셋 라이브러리 (Asset Library)
|
||||||
|
|
||||||
|
- **구조**: `Pack (Package) -> Category -> Object` 3단계 계층 구조를 유지합니다.
|
||||||
|
- **필터링**: 사용자가 사이드바의 **'Filter' 아이콘**을 통해 원하는 패키지만 선택적으로 로드할 수 있어야 하며, 이 설정은 `localStorage`의 `selectedPackIds`에 저장됩니다.
|
||||||
|
- **Composite ID (중요)**: 에셋의 `id`가 패키지 간에 중복될 수 있으므로, `state.assetMap`의 키와 드래그 앤 드롭 통신에는 반드시 **`${pack_id}|${asset.id}`** 형식의 조합 키를 사용해야 합니다.
|
||||||
|
|
||||||
|
## 🧼 6. 오브젝트 데이터 정합성 (Object Data Purity)
|
||||||
|
|
||||||
|
- **규칙**:
|
||||||
|
1. **그룹 및 기본 도형(Primitives)**: `group`, `rect`, `circle`, `rounded-rect`, `text-box`, `label` 등 시각적 도형 객체는 에셋 이미지 경로(`assetPath`)를 가져서는 안 됩니다.
|
||||||
|
2. **ID 매칭 주의**: `static/js/modules/graph/styles/mapping.js`의 `getX6NodeConfig` 함수는 객체 타입이 그룹이거나 기본 도형 목록에 포함될 경우 에셋 검색 및 할당 로직을 반드시 건너뛰어야 합니다.
|
||||||
|
3. **라벨 데이터-시각 속성 동기화**: `data.label`은 사이드바와 비즈니스 로직의 원본 소스(SSoT)입니다. `json_handler.js` 및 `handleNodeUpdate`는 항상 `data.label`의 값을 `attrs.label.text`에 강제로 동기화하여 시각적 불일치를 방지해야 합니다.
|
||||||
|
4. **이유**: 그룹 이름이나 도형 라벨이 기존 에셋 명칭을 포함할 경우(예: "Switch Group"), 시스템이 이를 에셋으로 오판하여 잘못된 아이콘을 렌더링하거나 404 에러를 유발할 수 있기 때문입니다.
|
||||||
|
|
||||||
|
## 🔄 7. 부모 그룹 선택 가변 로직 (Parent Selection Heuristic)
|
||||||
|
|
||||||
|
- **원칙**: 노드가 중첩되거나 겹쳐 있는 여러 그룹 영역에 드롭/이동될 경우, 시스템은 항상 **가장 구체적인(Innermost)** 부모를 선택해야 합니다.
|
||||||
|
- **판단 알고리즘**:
|
||||||
|
1. **면적 기반 우선순위**: 드롭 포인트를 포함하는 모든 그룹 후보 중 Bounding Box의 **면적(Area)이 가장 작은 그룹**을 최종 부모로 선택합니다.
|
||||||
|
2. **계층 기반 보완**: 만약 후보 그룹들의 면적이 동일한 경우(예: 기본 사이즈로 중첩된 경우), `getAncestors()`를 확인하여 다른 그룹의 **자손(Descendant)인 그룹**을 우선적으로 선택합니다.
|
||||||
|
- **적용 위치**: `static/js/modules/graph/config.js` (`embedding.findParent`) 및 `static/js/modules/graph/interactions/drop.js`.
|
||||||
|
|
||||||
|
## 🧩 8. 모듈화 및 리팩토링 규칙 (Refactoring & Import Integrity)
|
||||||
|
|
||||||
|
- **규칙**:
|
||||||
|
1. **상대 경로 재계산**: 파일을 하위 디렉토리로 이동(예: `events.js` → `events/index.js`)할 경우, 해당 파일 내의 모든 상대 경로 임포트(`import ... from '../../state.js'`)는 반드시 새로운 디렉토리 깊이에 맞춰 업데이트해야 합니다.
|
||||||
|
2. **공통 참조 체크리스트**:
|
||||||
|
- `state.js`, `constants.js` (보통 `modules/` 최상위)
|
||||||
|
- `graph/styles/utils.js` 등 공용 유틸리티
|
||||||
|
- `ui/`, `persistence.js` 등 외부 모듈 의존성
|
||||||
|
3. **검증 절차**: 모듈화 작업 직후에는 반드시 `grep`이나 IDE 기능을 사용하여 **"지정된 파일을 찾을 수 없습니다"** 또는 **"404 Not Found"** 오류가 없는지 전수 검사해야 합니다.
|
||||||
|
- **이유**: 파일 구조가 깊어짐에 따라 기존의 `./` 또는 `../` 경로가 깨지면서 런타임 오류를 유발하고 개발 흐름을 방해하기 때문입니다.
|
||||||
|
|
||||||
|
## 11. 사이드바 및 실시간 동기화 (Sidebar & Sync)
|
||||||
|
|
||||||
|
- **규칙**:
|
||||||
|
1. **세이프 렌더(Safe Render)**: 사이드바의 `input` 또는 `textarea`에 포커스가 있는 동안에는 그래프 이벤트에 의한 전체 리렌더링을 차단해야 합니다. (`index.js` 내 `safeRender` 함수 참조)
|
||||||
|
2. **비동기 렌더링 튜닝**: X6의 데이터 변경 이벤트(`cell:change:data`) 발생 직후 렌더링할 때는 반드시 `setTimeout(..., 0)`을 사용하여 엔진 내부의 데이터 확정이 완료된 후 UI를 그려야 합니다.
|
||||||
|
3. **명시적 저장(Explicit Apply)**: 라벨, 자산 정보 등 텍스트 기반 속성은 `Enter` 키 또는 **[적용]** 버튼을 통해서만 저장되도록 하여, 입력 중 데이터 유실이나 포커스 탈취를 원천 차단합니다.
|
||||||
|
4. **재귀적 전파(Recursive Poke)**: 그룹의 이름이나 레이아웃 정보를 변경할 경우, 반드시 모든 하위 객체(`getDescendants`)에 `_parent_updated`와 같은 더미 타임스탬프를 `setData`로 주입하여 하위 객체들의 사이드바가 즉각 최신 부모 정보를 반영하게 해야 합니다.
|
||||||
|
- **이유**: 실시간 협업 및 복잡한 계층 구조에서 데이터 정합성과 사용자 입력의 연속성을 동시에 보장하기 위한 필수 장치입니다.
|
||||||
|
|
||||||
|
## 12. 레이어 격리 및 선택 필터링 (Layer Isolation)
|
||||||
|
|
||||||
|
- **규칙**:
|
||||||
|
1. **선택 격리(Selection Filter)**: `plugins.js`의 `Selection` 플러그인 설정에서는 반드시 현재 활성 레이어(`state.activeLayerId`)에 속한 셀만 선택 가능하도록 필터링해야 합니다.
|
||||||
|
2. **상호작용 차단**: 활성 레이어가 아닌 객체에 대한 더블 클릭(더 하위 계층으로 진입 등)은 `nodes.js` 이벤트 핸들러에서 명시적으로 차단 가드를 두어야 합니다.
|
||||||
|
3. **정적 필터링**: `pointer-events: none`과 같은 CSS 기반 차단 대신, 플러그인 레벨의 필터링을 우선하여 고스트 스냅(Ghost Snapping) 등 보조 기능은 유지되도록 관리합니다.
|
||||||
|
- **이유**: 사용자가 편집 중이지 않은 레이어의 객체를 실수로 조작하여 데이터가 오염되는 것을 방지하기 위함입니다.
|
||||||
|
|
||||||
|
## 9. 오브젝트 적층 및 잠금 관리 (Stacking & Locking)
|
||||||
|
|
||||||
|
- **Z-Index 규칙 (2026-03-21 개정)**:
|
||||||
|
- **계층 구조**: `Group/Rack (1~19) < Edge (30) < Node (50)`
|
||||||
|
- 그룹은 하단에, 연결선(Edge)은 그룹 위에, 일반 노드는 최상단에 배치하여 가시성 및 클릭 편의성을 보장합니다.
|
||||||
|
- 사용자 조작(`[`, `]`)에 의한 동적 변경 시에도 이 기본 계층 범위를 크게 벗어나지 않도록 주의합니다.
|
||||||
|
- **잠금(Lock)의 물리적 강제**:
|
||||||
|
- 단순히 `data.locked` 필드만 변경하는 것이 아니라, `cell.setProp`을 통해 `movable`, `deletable`, `rotatable` 속성을 동기화해야 합니다.
|
||||||
|
- `static/js/modules/graph/config.js`의 `interacting` 블록은 반드시 함수형으로 작성되어 각 셀의 `movable` 속성을 실시간으로 체크해야 합니다.
|
||||||
|
|
||||||
|
## 10. 정밀 공간 쿼리 (Spatial Query Integrity)
|
||||||
|
|
||||||
|
- **규칙**:
|
||||||
|
- 특정 좌표(`x, y`)에서 오브젝트를 검색할 때, AntV X6 v2의 실험적/변동성이 큰 API(`findModelsAtPoint` 등)에 의존하지 않습니다.
|
||||||
|
- 대신 `graph.getCells().filter(cell => cell.getBBox().containsPoint(local))`와 같은 **수동 기하학 필터링** 방식을 유지하여 버전 업데이트에 따른 오작동을 방지합니다.
|
||||||
|
- **좌표 변환**: 반드시 브라우저의 클라이언트 좌표를 `graph.clientToLocal(e.clientX, e.clientY)`를 통해 그래프 로컬 좌표로 먼저 변환한 후 계산에 사용해야 합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
*마지막 업데이트: 2026-03-20*
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# drawNET 프로젝트 로드맵 및 개발 가이드
|
||||||
|
|
||||||
|
이 문서는 drawNET 프로젝트의 개발 원칙, 완료된 마일스톤, 그리고 향후 발전 방향을 정의합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ 개발 원칙 (Development Principles)
|
||||||
|
1. **하드코딩 배제:** 모든 자산과 설정은 `assets.json` 등 외부 파일을 통해 관리합니다.
|
||||||
|
2. **로컬 최적화:** 설치와 실행이 간편해야 하며, 보안 및 폐쇄망 대응을 위해 핵심 라이브러리(AntV X6 등)를 로컬에서 관리합니다.
|
||||||
|
3. **사용자 경험 최선:** 텍스트(DSL)와 시각적 편집(GUI)의 장점을 결합한 하이드리브 워크플로우를 지향합니다.
|
||||||
|
4. **엔진 독립성:** 특정 엔진에 종속된 기능을 지양하되, 현재는 AntV X6의 성능을 극대화하여 구현합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 완료된 마일스톤 (Milestones)
|
||||||
|
|
||||||
|
### 1. 전용 그래프 엔진 마이그레이션 (AntV X6)
|
||||||
|
- **엔진 전환:** Cytoscape.js에서 AntV X6로 전체 시스템 이관 완료.
|
||||||
|
- **직교 라우팅:** Manhattan 알고리즘을 통한 완벽한 90도 굴곡 선 지원.
|
||||||
|
- **쉐이프 커스터마이징:** 이미지 및 메타데이터를 포함한 전용 노드 타입 등록.
|
||||||
|
- **로컬화:** 외부 CDN 차단 및 ORB 이슈 해결을 위한 라이브러리 자산 로컬화.
|
||||||
|
- **방향성 고정 라우팅 (Top/Bottom/Left/Right):** 앵커 선택에 따라 해당 방향으로 선이 가장 먼저 뻗어나오도록 강제하는 지능형 Manhattan 라우팅 엔진 구현 완료.
|
||||||
|
|
||||||
|
### 2. 하이브리드 속성 및 인터랙션
|
||||||
|
- **속성 사이드바:** X6 셀 데이터와 실시간 연동되는 속성 편집 패널.
|
||||||
|
- **컨텍스트 메뉴:** 우클릭을 통한 퀵 액션 및 스타일 제어.
|
||||||
|
- **지능형 그룹화:** 컨테이너 드롭 및 노드 드래그 포함을 통한 계층 구조 자동 생성.
|
||||||
|
|
||||||
|
### 3. 영속성 및 환경 관리
|
||||||
|
- **Leaf-Only 복구:** 계층 구조의 좌표 뒤틀림을 방지하는 X6 최적화 좌표 복구 엔진.
|
||||||
|
- **전용 포맷(.dnet):** 메타데이터 헤더를 포함한 X6 JSON 구조 표준화.
|
||||||
|
- **캔버스 환경 저장:** 그리드 스타일, 스냅 설정, 줌 레벨 등 시큐리티 가시성 환경 보존.
|
||||||
|
|
||||||
|
### 4. 인벤토리 및 정밀 배치 시스템
|
||||||
|
- **Inventory UI:** 타입별 수량 집계 및 드래그 가능한 플로팅 패널 구현.
|
||||||
|
- **데이터 기반 단축키 엔진:** `hotkeys.json` 설정을 통한 유연한 키 바인딩 시스템 도입.
|
||||||
|
- **키보드 네비게이션:** 방향키 및 Shift 조합을 통한 그리드 단위 오브젝트 정밀 이동 기능 추가.
|
||||||
|
- **파서 고도화:** 커스텀 포트 이름 및 그룹 멤버십 안정성 강화.
|
||||||
|
|
||||||
|
### 5. 오브젝트 상세 커스터마이징 및 단축키 전략 (New)
|
||||||
|
- **라벨 속성 강화**: 라벨 색상(Color) 및 위치(Top/Bottom/Left/Right/Center)를 속성창에서 즉시 변경 가능한 UI 구현.
|
||||||
|
- **단축키 계통 확립**: `Alt`(패널), `Shift`(액션), `Ctrl`(시스템)로 구분된 전문적인 단축키 설계 원칙 문서화 및 `Alt + 1` 적용.
|
||||||
|
- **실행 취소/다시 실행**: `Ctrl + Z/Y`를 통한 무제한 작업 실행 취소 기능 활성화.
|
||||||
|
|
||||||
|
### 7. Premium UX & 정밀 제어 시스템 (New)
|
||||||
|
- **오브젝트 적층(Z-Index)**: `[` / `]` 단축키를 통한 가역적 레이어링 시스템 구축 완료.
|
||||||
|
- **물리적 잠금(Locking)**: 인터랙션 제한 및 시각적 피드백(Red Boundary)을 결합한 강력한 객체 보호 기능 구현.
|
||||||
|
- **재귀적 선택(Recursive Select)**: 겹친 오브젝트를 정밀하게 골라내는 Surgical Picker(Photoshop Style) 시스템 완성.
|
||||||
|
- **레이어 패널 고도화**: 비활성 레이어 투명도 조절, 레이어 리네임 및 영속성 동기화 완료.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 향후 개발 우선순위 (Future Priorities)
|
||||||
|
|
||||||
|
### 💡 Current Focus: Interaction & Topology Intelligence
|
||||||
|
현재 기본적인 정밀 제어 도구가 완성됨에 따라, **망(Network)의 논리적 연속성**을 보장하는 지능형 인터랙션으로 전환 중입니다.
|
||||||
|
|
||||||
|
### 🎯 주요 목표
|
||||||
|
- [x] **정밀 고정 및 적층:** 오브젝트 잠금 및 Z-Index 제어 완결.
|
||||||
|
- [x] **정밀 선택 도구:** 복잡한 도면에서의 surgical selection 지원.
|
||||||
|
- [x] **고스트 타겟팅 (Ghost Targeting):** 레이어 간 관통 연결 시 하부 레이어 노드에 대한 자석 효과 및 안내선 제공.
|
||||||
|
- [x] **레이어 스패닝 (Spanning):** 여러 레이어에 걸쳐 있는 거대 오브젝트(Backbone 등)의 통합 관리 로직.
|
||||||
|
|
||||||
|
|
||||||
|
### Phase 2: 고급 시각화 및 UX (중기)
|
||||||
|
- [x] **선 교차(Cross) 점프:** 선이 겹칠 때 시각적으로 건너뛰는(Jump/Hop) 브릿지 효과.
|
||||||
|
- [x] **실행 취소/다시 실행:** X6 History 플러그인을 활용한 무제한 Undo/Redo.
|
||||||
|
|
||||||
|
|
||||||
|
### Phase 5: PM 자산 관리 및 인벤토리 (Inventory & Asset Management) [x]
|
||||||
|
- [x] **표준 속성 확장**: 제조사, 모델명, IP, 자산번호, 상태 등 PM용 표준 데이터 스키마 적용.
|
||||||
|
- [x] **지능형 검색 및 이동 (Search & Zoom)**: IP, 자산번호, 모델명 기반 검색 및 해당 노드로 즉시 포커싱.
|
||||||
|
- [x] **속성 복사/붙여넣기 (Format Painter)**: `Ctrl+Shift+C/V`를 통한 스타일 및 관리 데이터 일괄 복제.
|
||||||
|
- [x] **BOM 인벤토리 내보내기 (Excel Export)**: 전체 자산 리스트를 엑셀/CSV로 자동 생성 및 다운로드.
|
||||||
|
- [x] **드래그 복제 (Ctrl+Shift+Drag)**: 마우스 드래그를 이용한 즉시 오브젝트 복제 구현.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 7: 멀티 레이어 아키텍처 및 논리적 계층화 (Multi-Layer & Logical Stratification)
|
||||||
|
- [x] **평면 중첩(Flat Overlay) 시스템**: 여러 망(내부, 외부, 폐쇄망)을 독립된 레이어로 분리하고 투명도 기반으로 겹쳐 보기 기능 구현.
|
||||||
|
- [x] **레이어 관통 스냅(Ghost Snapping)**: 다른 레이어의 노드(방화벽 등)에 자석 효과를 적용하여 레이어 간 연결 편의성 극대화.
|
||||||
|
- [ ] **입체 레이어 뷰 (3D Exploded View) [취소/폐기]**: 프레젠테이션용 아코디언 익스플로드 뷰 실험 결과 가독성 저하로 폐기 결정.
|
||||||
|
- [x] **디자인 가이드라인 수립**: [Multi-Layer 설계 지침](file:///c:/project/drawNET/docs/design_multilayer.md) 기반의 데이터 정합성 엔진 개발.
|
||||||
|
|
||||||
|
### Phase 4: 인프라 연동 및 자동화 (Integration & Automation)
|
||||||
|
- [x] **Export 다변화**: SVG, PDF, 고해상도 PNG(투명 배경) 및 전문적인 PPTX 리포트 자동 생성 기능.
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
## 1. 데이터 규격 정의 (package.json)
|
||||||
|
- [x] 패키지 기본 정보(ID, Vendor, Version) 필드 정의 완료
|
||||||
|
- [x] 에셋 개별 정보(ID, Label, Category, Paths) 필드 정의 완료
|
||||||
|
- [x] 전/후면(Front/Back) 뷰 확장을 고려한 데이터 구조 검토 완료
|
||||||
|
|
||||||
|
### [최종 확정 규격]
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "cisco_nexus_9k",
|
||||||
|
"name": "Cisco Nexus 9000 Series",
|
||||||
|
"vendor": "Cisco",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Standard Nexus switches for Data Center",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"id": "n9k_93180yc",
|
||||||
|
"label": "Nexus 93180YC-EX",
|
||||||
|
"category": "Switch",
|
||||||
|
"views": {
|
||||||
|
"icon": "icon.svg",
|
||||||
|
"front": "front.svg",
|
||||||
|
"back": "back.svg"
|
||||||
|
},
|
||||||
|
"specs": {
|
||||||
|
"u_height": 1,
|
||||||
|
"is_rack_unit": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 설계영역(Draw) 패키지 로더 구현
|
||||||
|
- [x] `assets.js`가 단일 경로가 아닌 `packs/` 내의 모든 하위 패키지를 스캔하도록 수정
|
||||||
|
- [x] 사이드바 라이브러리 영역에 패키지별 섹션(또는 그룹) UI 생성
|
||||||
|
- [x] 개별 에셋을 드래그하여 캔버스 드롭 시 정상 렌더링 확인
|
||||||
|
|
||||||
|
## 3. 샘플 패키지 연동 테스트
|
||||||
|
- [x] `static/assets/packs/sample_cisco/` 수동 생성 및 테스트 데이터 수납
|
||||||
|
- [x] 설계영역 재로드 시 샘플 패키지가 라이브러리에 자동 노출되는지 확인
|
||||||
|
- [x] 언어(ko/en) 파일에 패키지 관련 신규 키 추가 여부 확인
|
||||||
|
|
||||||
|
## 4. 안정성 및 예외 처리
|
||||||
|
- [x] 잘못된 형식의 `package.json` 로드 시 에러 핸들링 및 사용자 알림
|
||||||
|
- [x] 에셋 파일 경로 누락 시 기본(Default) 아이콘 표시 로직 확인
|
||||||
|
- [x] 기존 라이브러리(Fixed Objects 등)와의 UI 충돌 여부 확인
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Phase 2 체크리스트: 스튜디오 기반 구축 (Base Setup)
|
||||||
|
|
||||||
|
## 1. 전용 페이지 및 라우팅 설정
|
||||||
|
- [x] `routes/main_routes.py`에 `/studio` 엔드포인트 추가 완료
|
||||||
|
- [x] `templates/studio.html` 기본 구조(HTML5, 필요한 CSS/JS 로드) 생성 완료
|
||||||
|
- [x] 설계영역(Draw)에서 스튜디오로 이동할 수 있는 메뉴/버튼 배치 (예: 상단 툴바)
|
||||||
|
|
||||||
|
## 2. 스튜디오 전용 UI/UX 레이아웃 (3-Panel)
|
||||||
|
- [x] **Source Panel (좌측)**: 업로드된 파일 목록 및 Drag & Drop 영역 구현
|
||||||
|
- [x] **Workspace (중앙)**: 에셋들을 바둑판식(Grid)으로 보여주는 프리뷰 영역 구현
|
||||||
|
- [x] **Property Panel (우측)**: 선택된 에셋의 상세 정보(ID, Label) 입력 영역 스켈레톤 생성
|
||||||
|
- [x] 스튜디오 전용 다크 테마 및 전문 도구 스타일 CSS 적용
|
||||||
|
|
||||||
|
## 3. Ingestion(이미지 수집) 엔진 구현
|
||||||
|
- [x] 브라우저 폴더 선택(`webkitdirectory`) 기능 구현 및 파일 필터링(.svg, .png, .jpg)
|
||||||
|
- [x] 개별 파일 드래그 앤 드롭(Drag & Drop) 업로드 지원
|
||||||
|
- [x] 선택된 이미지들을 중앙 Workspace 그리드에 즉시 렌더링 (Memory URL 사용)
|
||||||
|
|
||||||
|
## 4. 선택 및 상태 서비스 (State Management)
|
||||||
|
- [x] 그리드 내 이미지 개별/다중 선택(Ctrl/Shift 클릭) 로직 구현
|
||||||
|
- [x] 선택된 항목 수 및 파일 정보(해상도, 크기) 하단 상태바 표시
|
||||||
|
- [x] 업로드된 소스 목록 관리 서비스(`studio_state.js`) 초기화
|
||||||
|
|
||||||
|
## 5. 설계영역과의 분리 검증
|
||||||
|
- [x] `/studio` 페이지 로딩 시 설계영역의 X6 인스턴스가 생성되지 않아 리소스가 낭비되지 않는지 확인
|
||||||
|
- [x] 뒤로가기 또는 '설계영역으로 돌아가기' 버튼 정상 작동 확인
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
## 1. PNG to SVG 변환 엔진 (Transformation)
|
||||||
|
- [x] 브라우저 기반 벡터라이징 라이브러리(Potrace) 통합 완료
|
||||||
|
- [x] 소스 리스트의 PNG 파일을 읽어 실제 벡터 `<path>` 데이터로 추출하는 Processor 모듈 구현
|
||||||
|
- [x] 성능 최적화: 대량의 파일 변환 시 순차 처리 로직 구현
|
||||||
|
|
||||||
|
## 2. 변환 품질 검수 및 비교 UI (Review)
|
||||||
|
- [x] **Side-by-Side 뷰**: 원본 PNG와 변환된 SVG를 동일 크기로 나란히 배치하여 시각적 비교 지원
|
||||||
|
- [x] **가변 옵션 조절**: 기본 임계값 적용 및 벡터화 결과 즉시 반영 로직 구현
|
||||||
|
- [x] **최종 채택 토글**: 개별 에셋별로 [PNG 유지] 또는 [SVG 채택] 중 하나를 명시적으로 선택하는 기능
|
||||||
|
|
||||||
|
## 3. 다중 선택 및 벌크 속성 편집 (Bulk Edit)
|
||||||
|
- [x] 그리드 내 여러 에셋 선택 시 우측 패널에 '일괄 편집' 모드 활성화
|
||||||
|
- [x] **공통 속성 적용**: 선택된 모든 에셋에 대해 카테고리 일괄 변경 기능 구현
|
||||||
|
- [x] **명명 규칙(Naming Rule)**: 파일명을 기반으로 ID 및 라벨 자동 생성 로직 반영
|
||||||
|
|
||||||
|
## 4. 메타데이터 및 상태 관리
|
||||||
|
- [x] 가공 중인 에셋들의 실시간 세션 상태 유지 (`studio/state.js`)
|
||||||
|
- [x] 유효성 검사: 필수 값(Label) 실시간 업데이트 반영
|
||||||
|
- [x] 작업 중인 이미지의 프리뷰 및 선택 상태 시각화
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
## 1. 패키지 메타데이터 입력 UI
|
||||||
|
- [x] 상단 툴바에 패키지 ID(영문/숫자/언더바), 이름, 버전 입력 필드 추가
|
||||||
|
- [x] 패키지 전용 벤더(Vendor) 및 아이콘 지정 기능 구현
|
||||||
|
- [x] 필수 값 입력 여부 유효성 검사 로직 추가
|
||||||
|
|
||||||
|
## 2. 서버 측 저장 API (Backend)
|
||||||
|
- [x] `api_routes.py`에 `/api/studio/save-pack` POST 엔드포인트 구현
|
||||||
|
- [x] 서버의 `static/assets/packs/[pack_id]/` 디렉토리를 자동 생성하고 이미지 파일 저장
|
||||||
|
- [x] 가공된 에셋 정보를 바탕으로 최종 `package.json` 파일 생성 및 저장
|
||||||
|
|
||||||
|
## 3. Build & Publish 워크플로우
|
||||||
|
- [x] `[Build Package]` 버튼 클릭 시 가공된 모든 에셋(SVG/PNG 선택본)을 수집하여 서버로 전송
|
||||||
|
- [x] 진행 상태 표시(Progress Bar) 및 성공/실패 알림 모달 구현
|
||||||
|
- [x] 성공 시 생성된 패키지 요약 정보(에셋 개수, 용량 등) 표시
|
||||||
|
|
||||||
|
## 4. 설계영역(Designer) 즉시 반영
|
||||||
|
- [x] 패키지 저장 후 설계영역으로 이동 시 신규 패키지가 라이브러리에 자동 로드되는지 확인
|
||||||
|
- [x] 라이브러리 내 카테고리가 `package.json`의 정의대로 올바르게 그룹화되는지 검증
|
||||||
|
- [x] 새로 추가된 에셋을 캔버스에 드랍했을 때 이미지 경로가 정상적으로 해석되는지 최종 확인
|
||||||
|
|
||||||
|
## 5. 데이터 모델 확장 및 검증 (Future Proof)
|
||||||
|
- [x] 향후 Rack 배치를 고려한 `Front/Back View` 정보가 `package.json`에 포함될 수 있는 구조인지 재확인
|
||||||
|
- [x] 패키지 삭제 또는 덮어쓰기(Overwrite) 시의 안정성 확보
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Object Studio 테스트 시나리오 (Verification)
|
||||||
|
|
||||||
|
본 시나리오는 스튜디오의 핵심 가공 기능(Phase 3)을 검증하기 위한 절차입니다.
|
||||||
|
|
||||||
|
## 시나리오: 멀티 벤더 패키지 제작 및 설계영역 연동
|
||||||
|
|
||||||
|
### 1. 이미지 준비 및 수집 (Ingestion)
|
||||||
|
* **파일 로드**: 로컬의 PNG 아이콘 파일 3~5개를 준비하여 워크스페이스로 드래그 앤 드롭함.
|
||||||
|
* **검증**: 좌측 소스 리스트에 파일명이 나타나고, 중앙 그리드에 실시간 프리뷰가 렌더링되는지 확인.
|
||||||
|
|
||||||
|
### 2. 단일 에셋 정밀 가공 (Processing)
|
||||||
|
* **변환 실행**: 그리드에서 하나의 PNG 파일을 선택하고 우측 패널의 `[Convert to SVG]` 버튼을 누름.
|
||||||
|
* **비교 검수**:
|
||||||
|
* 화면에 나타나는 [Original PNG]와 [Vectorized SVG]의 품질을 비교.
|
||||||
|
* 임계값(Threshold) 바를 조절하여 외곽선이 깔끔하게 추출되는지 확인.
|
||||||
|
* `[채택(SVG)]` 버튼을 눌러 결과가 반영되는지 확인.
|
||||||
|
* **속성 입력**: 에셋의 표시 이름을 'Core Router'로 변경하고 카테고리를 'Network'로 지정.
|
||||||
|
|
||||||
|
### 3. 벌크 속성 편집 (Bulk Edit)
|
||||||
|
* **다중 선택**: 나머지 3개의 이미지를 `Ctrl+Click`으로 선택.
|
||||||
|
* **일괄 적용**: 우측 패널에서 제조사(Vendor)를 'Cisco'로 일괄 입력하고 `[Apply]` 클릭.
|
||||||
|
* **검증**: 각 에셋을 개별 클릭했을 때 제조사 정보가 모두 'Cisco'로 동기화되었는지 확인.
|
||||||
|
|
||||||
|
### 4. 패키징 및 최종 발행 (Packaging)
|
||||||
|
* **정보 입력**: 상단 툴바의 패키지 명칭을 'My_Cisco_Pack'으로 입력.
|
||||||
|
* **발행**: `[Build Package]` 버튼을 눌러 서버에 에셋과 `package.json`이 저장되는지 확인.
|
||||||
|
* **연동 확인**: 설계영역(Draw)으로 돌아갔을 때 좌측 라이브러리에 'My_Cisco_Pack' 섹션이 새로 나타나는지 확인.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 성공/실패 판정 기준
|
||||||
|
* **Success**: 모든 에셋이 `package.json`에 정의된 대로 라이브러리에 그룹화되어 나타남.
|
||||||
|
* **Failure**: 변환 중 브라우저 멈춤, 데이터 누락, 또는 설계영역에서 아이콘이 깨짐.
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
# DrawNET Object Studio 설계서 (Phase 1)
|
||||||
|
|
||||||
|
**Object Studio**는 단순한 이미지 업로드를 넘어, 인프라 설계에 필요한 전문적인 **에셋 관리 시스템(Asset Management System)**을 지향합니다.
|
||||||
|
|
||||||
|
## 1. 개요
|
||||||
|
* **목적**: 복잡한 인프라 소자(Cisco, Fortinet 등)를 패키지 단위로 관리하고, 전/후면 뷰 및 메타데이터를 정밀하게 가공하여 도면 제작 효율을 극대화함.
|
||||||
|
* **접근 주소**: `/studio` (독립형 페이지)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 주요 워크플로우
|
||||||
|
|
||||||
|
### [Ingestion: 소스 로드]
|
||||||
|
1. **벌크 로드**: 사용자가 로컬 폴더를 지정(`webkitdirectory`)하거나 여러 파일을 드래그 앤 드롭하여 소스 목록에 추가.
|
||||||
|
2. **프리뷰 그리드**: 업로드된 이미지들을 썸네일 형태로 시각화하여 확인.
|
||||||
|
|
||||||
|
### [Processing: 에셋 가공 및 속성 정의]
|
||||||
|
1. **개별/벌크 편집**:
|
||||||
|
* 선택된 오브젝트의 이름 변경, 속성 지정(아이콘 타입, 유형 등), 카테고리 매칭.
|
||||||
|
* 다중 선택 시 '제조사(Vendor)', '모델 시리즈', '환경' 등 공통 정보를 일괄 적용.
|
||||||
|
2. **PNG to SVG 하이브리드 변환**:
|
||||||
|
* PNG 소스를 벡터화(SVG Trace)하여 변환 결과 프리뷰 제공.
|
||||||
|
* 사용자가 **[원본 PNG 유지]** 또는 **[벡터 SVG 채택]** 중 선택. (SVG 채택 시 고해상도 무한 확대 및 색상 커스터마이징 가능)
|
||||||
|
|
||||||
|
### [Packaging: 내보내기 및 등록]
|
||||||
|
1. **패키지(Pack) 생성**: 가공 완료된 에셋들을 하나의 논리적 팩(예: `Cisco_Nexus_9000_Pack`)으로 묶음.
|
||||||
|
2. **뷰 매칭 (Front/Back)**: 랙 배치를 고려하여 동일 모델의 전면 아이콘과 후면 아이콘을 하나의 에셋 데이터로 바인딩.
|
||||||
|
3. **최종 발행**: 서버 저장소(`static/assets/packs/`)에 저장하고 메인 에디터의 에셋 라이브러리에 패키지 정보 업데이트.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 화면 UI/UX 설계 (안)
|
||||||
|
|
||||||
|
### 레이아웃 구조
|
||||||
|
* **좌측 (Source Panel)**: 업로드된 원본 파일 목록 및 Drag & Drop 영역.
|
||||||
|
* **중앙 (Workspace)**: 바둑판식(Grid) 에셋 프리뷰 영역 및 선택 도구.
|
||||||
|
* **우측 (Property Panel)**: 선택된 에셋(들)의 속성 입력창 (명칭, 제조사, 카테고리 등) 및 SVG 변환 비교 뷰.
|
||||||
|
|
||||||
|
### 주요 기능 버튼
|
||||||
|
* `[Load Folder]`: 폴더 단위 소스 로드.
|
||||||
|
* `[Batch Edit]`: 선택 항목 일괄 속성 적용.
|
||||||
|
* `[Vectorize]`: 벡터 변환 및 품질 검수 모드 진입.
|
||||||
|
* `[Build Pack]`: 최종 패키지 추출 및 서버 등록.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 데이터 모델 확장 (Draft)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pack_id": "cisco_sw_pack",
|
||||||
|
"vendor": "Cisco",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"id": "c9300L_24T",
|
||||||
|
"label": "Catalyst 9300L 24T",
|
||||||
|
"category": "Switch",
|
||||||
|
"views": {
|
||||||
|
"front": "packs/cisco/9300l_f.svg",
|
||||||
|
"back": "packs/cisco/9300l_b.svg"
|
||||||
|
},
|
||||||
|
"specs": {
|
||||||
|
"u_height": 1,
|
||||||
|
"depth": "350mm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 구현 단계별 로드맵 (Roadmap)
|
||||||
|
|
||||||
|
안정적인 연동과 품질 관리를 위해 다음 순서로 개발을 진행합니다.
|
||||||
|
|
||||||
|
### Phase 1: 설계영역 수용성 및 인프라 구축 (Infrastructure)
|
||||||
|
* **목표**: 스튜디오에서 만든 패키지를 설계영역에서 즉시 읽어 들일 수 있는 기반 마련.
|
||||||
|
* **주요 작업**:
|
||||||
|
* `package.json` 데이터 규격 확정.
|
||||||
|
* 설계영역 에셋 라이브러리의 '멀티 패키지 로더' 구현.
|
||||||
|
* 샘플 패키지(`Cisco_Sample`)를 통한 연동 테스트.
|
||||||
|
* **체크리스트**: `/docs/studio/phase1_checklist.md`
|
||||||
|
|
||||||
|
### Phase 2: 스튜디오 기반 구축 (Base Setup)
|
||||||
|
* **목표**: 스튜디오 독립 페이지 및 이미지 Ingestion 엔진 구현.
|
||||||
|
* **주요 작업**:
|
||||||
|
* `/studio` 라운팅 및 전용 HTML/CSS 레이아웃 생성.
|
||||||
|
* 폴더 선택 및 드래그 앤 드롭 업로드 구현.
|
||||||
|
* 에셋 프리뷰 그리드 UI 최적화.
|
||||||
|
* **체크리스트**: `/docs/studio/phase2_checklist.md`
|
||||||
|
|
||||||
|
### Phase 3: 스튜디오 핵심 가공 기능 (Core Processing)
|
||||||
|
* **목표**: PNG to SVG 변환 및 벌크 속성 편집 기능 구현.
|
||||||
|
* **주요 작업**:
|
||||||
|
* 벡터라이징(Trace) 엔진 탑재 및 변환 전/후 비교 UI.
|
||||||
|
* 다중 선택 기반 일괄 속성(Vendor, Category 등) 편집기.
|
||||||
|
* **체크리스트**: `/docs/studio/phase3_checklist.md`
|
||||||
|
|
||||||
|
* **체크리스트**: `/docs/studio/phase4_checklist.md` [v] **Completed**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 최종 구현 사양 (Final Implementation)
|
||||||
|
|
||||||
|
### 아키텍처 (Architecture)
|
||||||
|
- **프론트엔드 모듈화**: 코드 복잡도를 제어하기 위해 관심사별로 모듈을 분리함.
|
||||||
|
- `index.js`: 엔트리 포인트 및 이벤트 바인딩.
|
||||||
|
- `renderer.js`: 모든 DOM 생성 및 UI 업데이트 담당.
|
||||||
|
- `actions.js`: 빌드, 변환, 벌크 수정 등 비즈니스 로직.
|
||||||
|
- `state.js` / `ingester.js` / `processor.js`: 상태 관리 및 데이터 처리 전담.
|
||||||
|
- **백엔드**: Flask (`api_routes.py`) 기반의 멀티 패키지 스캔 및 저장 엔진 구축.
|
||||||
|
|
||||||
|
### 주요 기술 스택
|
||||||
|
- **Potrace-js**: 클라이언트 사이드 고속 벡터화.
|
||||||
|
- **Flask FormData**: 대용량 이미지 및 메타데이터 일괄 전송.
|
||||||
|
- **X6 Data Integration**: `package.json` 규격을 통한 설계 영역과의 심리스한 연동.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# drawNET Development Task List (Updated 2026-03-19)
|
||||||
|
|
||||||
|
## 📋 현재 상태 요약
|
||||||
|
- **완료:** AntV X6 엔진 마이그레이션, DSL 동기화 기초, 정밀 배치 도구, UX 고도화.
|
||||||
|
- **핵심 성과:** **Object Studio(Phase 1-4) 구현 완료**. 이제 독립된 환경에서 자산을 제작하고 패키징하여 도면에 즉시 반영 가능.
|
||||||
|
|
||||||
|
## ✅ 완료된 작업 (Object Studio Milestone)
|
||||||
|
|
||||||
|
### Phase 1: 자산 인프라 및 멀티 로더
|
||||||
|
- [x] `package.json` 데이터 규격 정의 (`vendor`, `pack_id`, `views` 등)
|
||||||
|
- [x] 백엔드 멀티 패키지 스캔 엔진 (`/assets`) 고도화
|
||||||
|
- [x] 설계영역(Designer) 사이드바의 벤더별 패키지 자동 로딩 구현
|
||||||
|
|
||||||
|
### Phase 2: 스튜디오 기반 구축
|
||||||
|
- [x] 독립된 제작 페이지 (`/studio`) 라우팅 및 전용 레이아웃 (3-Panel)
|
||||||
|
- [x] 폴더/파일 단위 대량 이미지 인제스천(Ingestion) 기능
|
||||||
|
- [x] 작업 영역(Grid) 및 실시간 필터링 UI
|
||||||
|
|
||||||
|
### Phase 3: 핵심 가공 및 벡터 변환
|
||||||
|
- [x] Potrace-js 통합을 통한 PNG -> SVG 실시간 벡터라이징
|
||||||
|
- [x] **Side-by-Side 리뷰 UI**: 원본과 변환본 비교 및 최종 포맷 채택 기능
|
||||||
|
- [x] 벌크(Bulk) 속성 편집(Category 일괄 적용 등) 기능
|
||||||
|
|
||||||
|
### Phase 4: 패키징 및 최종 연동
|
||||||
|
- [x] **/api/studio/save-pack**: 가공된 에셋 및 `package.json` 서버 저장 API
|
||||||
|
- [x] 버튼 하나로 빌드부터 설계영역 반영까지 워크플로우 자동화
|
||||||
|
- [x] **코드 모듈화(Refactoring)**: `Renderer`, `Actions` 모듈 분리로 코드 품질 확보
|
||||||
|
- [x] **PPTX 내보내기 (Simple Test)**: PptxGenJS 기반 도면 슬라이드 추출 기능 구현 (설계영역)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 향후 개발 우선순위
|
||||||
|
|
||||||
|
### 1단계: 엔터프라이즈 기능 확장
|
||||||
|
- [ ] **Sub-Graph (Nesting)**: 그룹 내부 진입 및 상세 설계 드릴다운 기능
|
||||||
|
- [ ] **Rack View 가공**: 스튜디오에서 장비의 U-Height 정보를 기반으로 한 랙 실장 뷰 연동
|
||||||
|
|
||||||
|
### 2단계: 분석 및 자동화
|
||||||
|
- [ ] **Live Status Monitoring**: 실시간 Ping 체크를 통한 장애 가시화
|
||||||
|
- [ ] **Compliance Audit**: 설계 보안 가이드 준수 여부 자동 분석
|
||||||
|
|
||||||
|
### 3단계: AI 어시스턴트
|
||||||
|
- [ ] LLM 연동을 통한 자연어 기반 토폴로지 자동 생성 및 최적화 제안
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
|
||||||
|
css_path = r'c:\project\drawNET\static\css\lib\google-fonts.css'
|
||||||
|
fonts_dir = r'c:\project\drawNET\static\fonts'
|
||||||
|
|
||||||
|
if not os.path.exists(fonts_dir):
|
||||||
|
os.makedirs(fonts_dir)
|
||||||
|
|
||||||
|
with open(css_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
urls = re.findall(r'url\((https://fonts\.gstatic\.com/s/[^)]+)\)', content)
|
||||||
|
urls = list(set(urls)) # Unique URLs
|
||||||
|
|
||||||
|
url_map = {}
|
||||||
|
for i, url in enumerate(urls):
|
||||||
|
filename = url.split('/')[-1]
|
||||||
|
local_path = os.path.join(fonts_dir, filename)
|
||||||
|
print(f"Downloading {url} -> {filename}")
|
||||||
|
try:
|
||||||
|
r = requests.get(url)
|
||||||
|
if r.status_code == 200:
|
||||||
|
with open(local_path, 'wb') as f:
|
||||||
|
f.write(r.content)
|
||||||
|
url_map[url] = f"/static/fonts/{filename}"
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to download {url}: {e}")
|
||||||
|
|
||||||
|
# Update CSS
|
||||||
|
new_content = content
|
||||||
|
for url, local_url in url_map.items():
|
||||||
|
new_content = new_content.replace(url, local_url)
|
||||||
|
|
||||||
|
with open(css_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(new_content)
|
||||||
|
|
||||||
|
print("Google Fonts localized successfully.")
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# drawNET Premium 실무 가이드 (Advanced Engineering)
|
||||||
|
|
||||||
|
본 가이드는 drawNET Premium 에디션을 사용하여 복잡한 네트워크 아키텍처와 시스템 토폴로지를 설계하는 엔지니어들을 위한 핵심 지침서입니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 핵심 철학: "Drawing for Context, Data for Mapping"
|
||||||
|
drawNET은 단순한 다이어그램 도구가 아닙니다. **"시각적 아키텍처(Drawing)"**와 **"엔지니어링 데이터(Inventory)"**를 하나로 통합합니다.
|
||||||
|
- 모든 디자인 요소는 배후에 데이터 속성(IP, 모델, 상태 등)을 가지고 있으며, 이는 자동으로 보고서와 연동됩니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 캔버스 네비게이션
|
||||||
|
- **스마트 팬닝**: 마우스 **우측 버튼** 드래그 또는 `Space` + 드래그로 캔버스를 이동합니다.
|
||||||
|
- **정밀 줌**: `Ctrl` + 마우스 휠을 사용하여 **커서가 가리키는 지점**을 중심으로 확대/축소합니다.
|
||||||
|
- **화면 맞춤**: `Ctrl + F`를 눌러 모든 요소를 한눈에 보이게 정렬합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 지능형 자산(Asset) 활용
|
||||||
|
- **패키지 관리**: 왼쪽 라이브러리 상단의 **필터 아이콘**을 클릭하여 필요한 제조사/분류 패키지만 활성화할 수 있습니다.
|
||||||
|
- **카테고리 토글**: 카테고리 헤더를 클릭하여 사용하지 않는 자산 목록을 접어 가독성을 높이세요.
|
||||||
|
- **Fixed Objects**: 기본 도형(`Rect`, `Circle`, `Text` 등)은 상단 고정 섹션에서 언제든 꺼내 쓸 수 있습니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 고급 조작 및 편집 (Gestures)
|
||||||
|
- **즉시 복제 (Cloning)**: 노드를 선택한 후 **`Ctrl` + 드래그**하면 고스트 효과와 함께 즉시 복제본이 생성됩니다.
|
||||||
|
- **중첩 선택 (Recursive Select)**: 복잡하게 겹쳐진 구간에서 **`Ctrl` + 우클릭**을 하면 해당 지점의 모든 객체 목록이 나타나 원하는 레이어를 정확히 선택할 수 있습니다.
|
||||||
|
- **포맷 페인터 (Style Painter)**: 한 객체의 스타일을 다른 객체에 동일하게 적용하려면 `Ctrl + Shift + C`(복사) -> `Ctrl + Shift + V`(붙여넣기)를 사용하세요.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 레이어 시스템 (Standard vs Logical)
|
||||||
|
drawNET Premium은 목적에 따른 두 가지 레이어 유형을 제공합니다.
|
||||||
|
- **Standard Layer (물리)**: 실제 장비 배치 및 물리적 연결을 설계합니다.
|
||||||
|
- **Logical Layer (논리)**: 보안 구역, 데이터 흐름, 서비스 연관도 등 추상적인 개념을 설계합니다. 이 레이어에서는 복제 시 데이터의 논리적 무결성이 보호됩니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 스마트 라우팅 (Connectivity)
|
||||||
|
- **Manhattan (`Shift + A`)**: 장애물을 회피하는 직각 경로로 자동 연결합니다.
|
||||||
|
- **Straight (`Shift + S`)**: 최단 거리 직선으로 연결합니다.
|
||||||
|
- **선 스타일 전환**: 이미 그려진 선을 선택하고 `Alt + M`(Manhattan) 또는 `Alt + L`(Straight)로 즉시 변경할 수 있습니다.
|
||||||
|
- **연결 해제**: 두 노드를 선택하고 `Shift + D`를 누르면 사이의 모든 연결이 제거됩니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 전문 보고서 및 인벤토리 추출
|
||||||
|
설계가 완료되면 상단 메뉴를 통해 다음 형식으로 성과물을 내보낼 수 있습니다.
|
||||||
|
- **PPTX 보고서**: 각 노드별 상세 설명과 인벤토리 통계가 포함된 고품질 발표 자료를 생성합니다.
|
||||||
|
- **Excel 인벤토리**: 모든 자산의 상세 속성(IP, 모델, 태그 등)을 집계하여 관리 대장으로 활용합니다.
|
||||||
|
- **Vectored Export**: PNG/SVG/PDF를 통해 깨짐 없는 고해상도 아키텍처 이미지를 확보합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*최종 업데이트: 2026-03-22*
|
||||||
|
*drawNET Engineering Team*
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# 리치 텍스트 카드(Rich Text Card) 활용 가이드 🃏
|
||||||
|
|
||||||
|
'리치 텍스트 카드'는 아키텍처 도면의 **설명(구축 내역)**이나 **범례(Legend)**를 전문적으로 표현하기 위해 설계된 스마트 객체입니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 기본 사용법
|
||||||
|
1. **에셋 선택**: 왼쪽 '고정 오브젝트(Fixed Objects)' 목록 최하단의 **'리치 텍스트 카드'**를 캔버스로 드래그합니다.
|
||||||
|
2. **속성 편집**: 오브젝트를 선택하면 오른쪽 사이드바에 편집 메뉴가 나타납니다.
|
||||||
|
3. **내용 입력**: '내용' 칸에 텍스트를 입력하면 실시간으로 카드 본문에 반영됩니다. (줄바꿈 지원)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 카드 유형별 특징
|
||||||
|
|
||||||
|
### ❶ 번호 매기기 (Numbered List)
|
||||||
|
- **용도**: 구축 내역, 작업 순서, 시스템 특징 나열 등.
|
||||||
|
- **특징**: 각 줄마다 헤더 색상과 동일한 **파란색 번호 박스**가 자동으로 생성됩니다.
|
||||||
|
- **팁**: 단순히 엔터(`\n`)로 줄을 나누기만 하면 번호가 순차적으로 부여됩니다.
|
||||||
|
|
||||||
|
### ❷ 불렛 기호 (Bullet Points)
|
||||||
|
- **용도**: 일반적인 특징 설명, 비고 사항 등.
|
||||||
|
- **특징**: 깔끔한 원형(`●`) 불렛 기호가 각 줄 앞에 붙습니다.
|
||||||
|
|
||||||
|
### ❸ 범례 모드 (Legend Mode) ⭐
|
||||||
|
- **용도**: 도면 하단의 기호 설명(범례) 작성.
|
||||||
|
- **특징**: 텍스트 기반의 특수 문법을 사용하여 **아이콘을 포함한 범례**를 자동으로 렌더링합니다.
|
||||||
|
|
||||||
|
#### 💡 범례 모드 문법 (Syntax):
|
||||||
|
본문에 아래와 같이 입력하세요:
|
||||||
|
- `- (circle:#색상코드) 설명` : 지정된 색상의 **원형** 아이콘 생성
|
||||||
|
- `- (rect:#색상코드) 설명` : 지정된 색상의 **사각형** 아이콘 생성
|
||||||
|
- `- (line:#색상코드) 설명` : 지정된 색상의 **가로 선** 아이콘 생성
|
||||||
|
|
||||||
|
> **입력 예시:**
|
||||||
|
> `- (circle:#10b981) 가동 중인 시스템`
|
||||||
|
> `- (rect:#ef4444) 장애 발생 구역`
|
||||||
|
> `- (line:#f97316) 외부 연동 구간`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 스타일 커스터마이징
|
||||||
|
- **헤더 제목**: 도면의 맥락에 맞게 '구축 내역', '범례' 등으로 수정하세요.
|
||||||
|
- **테마 색상**: 헤더의 색상 피커를 사용하여 프로젝트 테마에 맞게 색상을 변경할 수 있습니다. (테두리 색상도 자동으로 동기화됩니다.)
|
||||||
|
- **잠금 기능**: 배치가 완료된 후 'Locked'를 체크하면 의도치 않은 이동이나 삭제를 방지할 수 있습니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*최종 수정일: 2026-03-21*
|
||||||
|
*drawNET Design System*
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# drawNET Keyboard Shortcuts (Premium Edition)
|
||||||
|
|
||||||
|
이 문서는 drawNET의 실제 시스템 설정(`hotkeys.json`)과 일치하는 최신 단축키 안내를 포함합니다.
|
||||||
|
|
||||||
|
## 1. 기본 조작 및 뷰 (Navigation & View)
|
||||||
|
|
||||||
|
| 단축키 | 기능 | 설명 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `Ctrl` + `F` | 화면 맞춤 (Fit) | 모든 노드가 화면에 들어오도록 줌 및 정렬 조정 |
|
||||||
|
| `Ctrl` + `+` / `=` | 확대 (Zoom In) | 캔버스 확대 |
|
||||||
|
| `Ctrl` + `-` | 축소 (Zoom Out) | 캔버스 축소 |
|
||||||
|
| `Ctrl` + `Mouse Wheel` | 정밀 줌 | **마우스 커서 위치**를 중심으로 확대/축소 |
|
||||||
|
| `Alt` + `0` | 인벤토리 패널 | 자산 수량 집계 패널 토글 |
|
||||||
|
| `Alt` + `1` **또는** `Alt` + `Enter` | 속성 사이드바 | 선택된 오브젝트의 상세 속성 편집창 토글 |
|
||||||
|
| `Alt` + `9` | 레이어 패널 | 논리적 계층 관리 및 가시성 제어 패널 토글 |
|
||||||
|
| `Space` + `Drag` | 캔버스 팬닝 | 캔버스를 상하좌우로 자유롭게 이동 |
|
||||||
|
| **`Right Click`** + `Drag` | **스마트 팬닝** | 마우스 우측 버튼 드래그로 즉시 팬닝 (문맥 메뉴와 구분됨) |
|
||||||
|
| **`Ctrl` + `Right Click`** | **재귀적 오브젝트 선택** | 겹친 지점의 모든 오브젝트 목록을 보여줌 (Photoshop 스타일) |
|
||||||
|
|
||||||
|
## 2. 편집 및 수정 (Editing)
|
||||||
|
|
||||||
|
| 단축키 | 기능 | 설명 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `Delete` / `Backspace` | 삭제 | 선택된 노드 또는 선을 삭제 |
|
||||||
|
| `Esc` | 모든 선택 해제 | 선택된 모든 요소를 해제하고 속성 창을 닫음 |
|
||||||
|
| `Ctrl` + `Z` | 실행 취소 (Undo) | 마지막 작업 취소 |
|
||||||
|
| `Ctrl` + `Y` / `Ctrl` + `Shift` + `Z` | 다시 실행 (Redo) | 취소한 작업 다시 실행 |
|
||||||
|
| `Ctrl` + `C` | 복사 (Copy) | 선택된 오브젝트들을 클립보드에 복사 |
|
||||||
|
| `Ctrl` + `V` | 붙여넣기 (Paste) | 복사된 오브젝트들을 마우스 위치에 붙여넣기 |
|
||||||
|
| **`Ctrl` + `Drag`** | **즉시 복제 (Cloning)** | 선택된 노드를 드래그하여 즉시 복제 (고스트 효과 동반) |
|
||||||
|
| `Ctrl` + `D` | 제자리 복제 (Duplicate) | 선택된 오브젝트를 제자리에 즉시 복제 |
|
||||||
|
| `Ctrl` + `G` | 그룹화 (Group) | 선택된 노드들을 하나의 그룹으로 묶음 |
|
||||||
|
| `F2` | 이름 변경 | 선택된 오브젝트의 **속성창 라벨 입력칸으로 포커스 이동** |
|
||||||
|
| `Shift` + `A` | 자동 연결 (Manhattan) | 선택된 노드 사이를 장애물 회피 경로로 연결 |
|
||||||
|
| `Shift` + `S` | 직선 연결 (Straight) | 선택된 노드 사이를 직선으로 연결 |
|
||||||
|
| `Alt` + `M` | 선 스타일 전환 | 선택된 선을 Manhattan(자동) 스타일로 변경 |
|
||||||
|
| `Alt` + `L` | 선 스타일 전환 | 선택된 선을 Straight(직선) 스타일로 변경 |
|
||||||
|
| `Shift` + `D` | 연결 끊기 | 선택된 노드들 사이의 모든 연결 제거 |
|
||||||
|
| `[` | 뒤로 보내기 | 선택된 오브젝트를 시각적으로 한 단계 뒤로 보냄 |
|
||||||
|
| `]` | 앞으로 가져오기 | 선택된 오브젝트를 시각적으로 한 단계 앞으로 가져옴 |
|
||||||
|
| `Ctrl` + `L` | 오브젝트 잠금 | 선택된 오브젝트의 이동 및 삭제 방지 (시각적 점선 표시) |
|
||||||
|
| **`Ctrl` + `Shift` + `L`** | 자동 레이아웃 | 노드들을 계층 구조에 따라 자동으로 재배치 |
|
||||||
|
| **`Ctrl` + `Shift` + `C`** | **포맷 복사** | 선택된 노드/엣지의 스타일(색상, 크기 등)을 복사 |
|
||||||
|
| **`Ctrl` + `Shift` + `V`** | **포맷 붙여넣기** | 복사한 스타일을 선택한 대상에 즉시 적용 |
|
||||||
|
|
||||||
|
## 3. 정렬 및 배치 (Alignment & Distribution)
|
||||||
|
|
||||||
|
| 단축키 | 기능 | 설명 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `Shift` + `1` ~ `4` | 정렬 (Align) | 상단, 하단, 왼쪽, 오른쪽 정렬 |
|
||||||
|
| `Shift` + `5` ~ `6` | 중앙 정렬 (Center) | 수직 중앙(Middle), 수평 중앙(Center) 정렬 |
|
||||||
|
| `Shift` + `7` ~ `8` | 균등 배치 (Distribute) | 가로 간격 동일하게, 세로 간격 동일하게 배치 |
|
||||||
|
|
||||||
|
## 4. 노드 이동 (Movement)
|
||||||
|
|
||||||
|
| 단축키 | 기능 | 설명 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `Arrow Keys` | 미세 이동 | 선택된 노드를 5px 단위로 정밀 이동 |
|
||||||
|
| `Shift` + `Arrow Keys` | 격자 이동 | 선택된 노드를 그리드 설정 간격으로 이동 |
|
||||||
|
|
||||||
|
## 5. 파일 관리 (File Management)
|
||||||
|
|
||||||
|
| 단축키 | 기능 | 설명 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `Ctrl` + `S` | 저장 (Export) | 현재 상태를 `.dnet` JSON 파일로 내보내기 |
|
||||||
|
| `Ctrl` + `O` | 불러오기 (Import) | `.dnet` 파일 불러오기 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Flask>=3.0.0
|
||||||
|
requests>=2.31.0
|
||||||
|
gunicorn>=21.2.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import os
|
||||||
|
from flask import Blueprint, render_template, send_from_directory
|
||||||
|
|
||||||
|
main_bp = Blueprint('main', __name__)
|
||||||
|
|
||||||
|
@main_bp.route('/')
|
||||||
|
def index():
|
||||||
|
return render_template('index.html')
|
||||||
|
|
||||||
|
@main_bp.route('/studio')
|
||||||
|
def studio():
|
||||||
|
return render_template('studio.html')
|
||||||
|
|
||||||
|
@main_bp.route('/manual/<path:filename>')
|
||||||
|
def serve_manual(filename):
|
||||||
|
return send_from_directory(os.path.join(os.getcwd(), 'manual'), filename)
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"id": "Network",
|
||||||
|
"label": "Network Devices",
|
||||||
|
"icon": "router.svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Security",
|
||||||
|
"label": "Security & Firewalls",
|
||||||
|
"icon": "firewall.svg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"id": "router",
|
||||||
|
"type": "Router",
|
||||||
|
"category": "Network",
|
||||||
|
"label": "Standard Router",
|
||||||
|
"path": "router.svg",
|
||||||
|
"format": "svg",
|
||||||
|
"vendor": "Cisco"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "switch",
|
||||||
|
"type": "Switch",
|
||||||
|
"category": "Network",
|
||||||
|
"label": "L3 Switch",
|
||||||
|
"path": "switch.svg",
|
||||||
|
"format": "svg",
|
||||||
|
"vendor": "Cisco"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "firewall_cisco",
|
||||||
|
"type": "Firewall",
|
||||||
|
"category": "Security",
|
||||||
|
"label": "Cisco Firewall",
|
||||||
|
"path": "firewall.svg",
|
||||||
|
"format": "svg",
|
||||||
|
"vendor": "Cisco"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "firewall_png",
|
||||||
|
"type": "F/W",
|
||||||
|
"category": "Security",
|
||||||
|
"label": "Legacy F/w",
|
||||||
|
"path": "firewall.png",
|
||||||
|
"format": "png",
|
||||||
|
"vendor": "Generic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "server",
|
||||||
|
"type": "Server",
|
||||||
|
"category": "Compute",
|
||||||
|
"label": "Enterprise Server",
|
||||||
|
"path": "server.svg",
|
||||||
|
"format": "svg",
|
||||||
|
"vendor": "Generic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cloud",
|
||||||
|
"type": "Internet",
|
||||||
|
"category": "External",
|
||||||
|
"label": "External Cloud",
|
||||||
|
"path": "cloud.svg",
|
||||||
|
"format": "svg",
|
||||||
|
"vendor": "Generic"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M25 40 A15 15 0 0 1 50 25 A25 25 0 0 1 85 50 A20 20 0 0 1 70 85 H30 A20 20 0 0 1 25 40" fill="#cbd5e1" stroke="#64748b" stroke-width="2"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 247 B |
|
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="15" y="20" width="70" height="60" rx="4" fill="#ef4444" stroke="#b91c1c" stroke-width="2"/>
|
||||||
|
<path d="M15 40 H85 M15 60 H85 M35 20 V40 M65 20 V40 M25 40 V60 M55 40 V60 M85 40 V60 M40 60 V80 M70 60 V80" stroke="white" stroke-width="2"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 347 B |
|
After Width: | Height: | Size: 406 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,125 @@
|
|||||||
|
{
|
||||||
|
"id": "cisco_base_pack",
|
||||||
|
"name": "cisco_pack",
|
||||||
|
"vendor": "Unknown",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"id": "cisco_firewall",
|
||||||
|
"label": "Cisco Firewall",
|
||||||
|
"category": "Security",
|
||||||
|
"views": {
|
||||||
|
"icon": "cisco_firewall.png",
|
||||||
|
"front": "cisco_firewall.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cisco_firewallservicemodule",
|
||||||
|
"label": "Cisco Firewall service module",
|
||||||
|
"category": "Security",
|
||||||
|
"views": {
|
||||||
|
"icon": "cisco_firewallservicemodule.png",
|
||||||
|
"front": "cisco_firewallservicemodule.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cisco_generic",
|
||||||
|
"label": "Cisco Generic",
|
||||||
|
"category": "Other",
|
||||||
|
"views": {
|
||||||
|
"icon": "cisco_generic.png",
|
||||||
|
"front": "cisco_generic.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cisco_iosslb",
|
||||||
|
"label": "Cisco Iosslb",
|
||||||
|
"category": "Network",
|
||||||
|
"views": {
|
||||||
|
"icon": "cisco_iosslb.png",
|
||||||
|
"front": "cisco_iosslb.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cisco_laptop",
|
||||||
|
"label": "Cisco Laptop",
|
||||||
|
"category": "Device",
|
||||||
|
"views": {
|
||||||
|
"icon": "cisco_laptop.png",
|
||||||
|
"front": "cisco_laptop.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cisco_printer",
|
||||||
|
"label": "Cisco Printer",
|
||||||
|
"category": "Device",
|
||||||
|
"views": {
|
||||||
|
"icon": "cisco_printer.png",
|
||||||
|
"front": "cisco_printer.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cisco_wirelessrouter",
|
||||||
|
"label": "Cisco Wirelessrouter",
|
||||||
|
"category": "Network",
|
||||||
|
"views": {
|
||||||
|
"icon": "cisco_wirelessrouter.png",
|
||||||
|
"front": "cisco_wirelessrouter.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cisco_wwwserver",
|
||||||
|
"label": "Cisco Wwwserver",
|
||||||
|
"category": "Device",
|
||||||
|
"views": {
|
||||||
|
"icon": "cisco_wwwserver.png",
|
||||||
|
"front": "cisco_wwwserver.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cisco_contentservicerouter",
|
||||||
|
"label": "Cisco Contentservicerouter",
|
||||||
|
"category": "Network",
|
||||||
|
"views": {
|
||||||
|
"icon": "cisco_contentservicerouter.png",
|
||||||
|
"front": "cisco_contentservicerouter.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cisco_database",
|
||||||
|
"label": "Cisco Database",
|
||||||
|
"category": "DBMS",
|
||||||
|
"views": {
|
||||||
|
"icon": "cisco_database.png",
|
||||||
|
"front": "cisco_database.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cisco_mgx8000",
|
||||||
|
"label": "Cisco Mgx8000",
|
||||||
|
"category": "Network",
|
||||||
|
"views": {
|
||||||
|
"icon": "cisco_mgx8000.png",
|
||||||
|
"front": "cisco_mgx8000.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cisco_l2_100base",
|
||||||
|
"label": "Cisco L2 ",
|
||||||
|
"category": "Network",
|
||||||
|
"views": {
|
||||||
|
"icon": "cisco_l2_100base.png",
|
||||||
|
"front": "cisco_l2_100base.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cisco_cloud",
|
||||||
|
"label": "Cisco Cloud",
|
||||||
|
"category": "Cloud",
|
||||||
|
"views": {
|
||||||
|
"icon": "cisco_cloud.png",
|
||||||
|
"front": "cisco_cloud.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"id": "sample_cisco",
|
||||||
|
"name": "Cisco Enterprise Pack",
|
||||||
|
"vendor": "Cisco",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"id": "cisco_router",
|
||||||
|
"label": "Integrated Services Router",
|
||||||
|
"category": "Network",
|
||||||
|
"views": {
|
||||||
|
"icon": "router.svg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cisco_switch",
|
||||||
|
"label": "Catalyst Switch",
|
||||||
|
"category": "Network",
|
||||||
|
"views": {
|
||||||
|
"icon": "switch.svg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="50" cy="50" r="45" fill="#3b82f6" stroke="#1d4ed8" stroke-width="2"/>
|
||||||
|
<path d="M50 20 L50 40 M50 80 L50 60 M20 50 L40 50 M80 50 L60 50" stroke="white" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<path d="M45 40 L50 35 L55 40 M45 60 L50 65 L55 60 M40 45 L35 50 L40 55 M60 45 L65 50 L60 55" fill="none" stroke="white" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 499 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="10" y="25" width="80" height="50" rx="5" fill="#3b82f6" stroke="#1d4ed8" stroke-width="2"/>
|
||||||
|
<path d="M30 40 L50 40 M50 60 L70 60" stroke="white" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<path d="M45 35 L50 40 L45 45 M55 55 L50 60 L55 65" fill="none" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 448 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="50" cy="50" r="45" fill="#3b82f6" stroke="#1d4ed8" stroke-width="2"/>
|
||||||
|
<path d="M50 20 L50 40 M50 80 L50 60 M20 50 L40 50 M80 50 L60 50" stroke="white" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<path d="M45 40 L50 35 L55 40 M45 60 L50 65 L55 60 M40 45 L35 50 L40 55 M60 45 L65 50 L60 55" fill="none" stroke="white" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 499 B |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="25" y="10" width="50" height="80" rx="2" fill="#64748b" stroke="#334155" stroke-width="2"/>
|
||||||
|
<rect x="30" y="20" width="40" height="5" fill="#94a3b8"/>
|
||||||
|
<rect x="30" y="35" width="40" height="5" fill="#94a3b8"/>
|
||||||
|
<rect x="30" y="50" width="40" height="5" fill="#94a3b8"/>
|
||||||
|
<circle cx="35" cy="75" r="3" fill="#22c55e"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 440 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="10" y="25" width="80" height="50" rx="5" fill="#3b82f6" stroke="#1d4ed8" stroke-width="2"/>
|
||||||
|
<path d="M30 40 L50 40 M50 60 L70 60" stroke="white" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<path d="M45 35 L50 40 L45 45 M55 55 L50 60 L55 65" fill="none" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 448 B |
@@ -0,0 +1,306 @@
|
|||||||
|
/* cyrillic-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* greek-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
/* greek */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
/* cyrillic-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* greek-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
/* greek */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
/* cyrillic-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* greek-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
/* greek */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
/* cyrillic-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* greek-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
/* greek */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
/* cyrillic-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'JetBrains Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxTN1OVgaY.woff2) format('woff2');
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'JetBrains Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxTPlOVgaY.woff2) format('woff2');
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* greek */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'JetBrains Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxTOVOVgaY.woff2) format('woff2');
|
||||||
|
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'JetBrains Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxTNVOVgaY.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'JetBrains Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxTNFOVgaY.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'JetBrains Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/static/fonts/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxTOlOV.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
/* analysis.css - Theme-aware styles for Inventory and Analysis Panels */
|
||||||
|
|
||||||
|
.inventory-panel {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
width: 260px;
|
||||||
|
max-height: 320px;
|
||||||
|
z-index: 1000;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--text-color);
|
||||||
|
border-bottom: 1px solid var(--panel-border);
|
||||||
|
padding-bottom: 10px;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-header i {
|
||||||
|
color: var(--accent-color);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom Scrollbar for Inventory */
|
||||||
|
.inventory-list::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
.inventory-list::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.inventory-list::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--panel-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--item-bg);
|
||||||
|
padding: 8px 14px 8px 18px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-item:hover {
|
||||||
|
background: var(--item-hover-bg);
|
||||||
|
transform: translateX(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-item .lbl {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--sub-text);
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-item .val {
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--accent-color);
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
min-width: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-footer {
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid var(--panel-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-export-bom {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-export-bom:hover {
|
||||||
|
background: #2563eb;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 16px rgba(59, 130, 246, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-export-bom i {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/* Animations */
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from { opacity: 0; transform: translateY(20px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.animate-up {
|
||||||
|
animation: fadeInUp 0.6s cubic-bezier(0.23, 1, 0.32, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Edge Flow Animation */
|
||||||
|
@keyframes drawnet-flow {
|
||||||
|
from { stroke-dashoffset: 20; }
|
||||||
|
to { stroke-dashoffset: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-animation path {
|
||||||
|
stroke-dasharray: 5, 5;
|
||||||
|
animation: drawnet-flow 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal Animations */
|
||||||
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||||
|
@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }
|
||||||
|
@keyframes scaleUp { from { opacity: 0; transform: scale(0.9) translateY(20px); } to { opacity: 1; transform: scale(1) translateY(0); } }
|
||||||
|
@keyframes scaleDown { from { opacity: 1; transform: scale(1) translateY(0); } to { opacity: 0; transform: scale(0.9) translateY(20px); } }
|
||||||
|
|
||||||
|
.animate-fade-in { animation: fadeIn 0.3s ease forwards; }
|
||||||
|
.animate-fade-out { animation: fadeOut 0.2s ease forwards; }
|
||||||
|
.animate-scale-up { animation: scaleUp 0.4s cubic-bezier(0.18, 0.89, 0.32, 1.28) forwards; }
|
||||||
|
.animate-scale-down { animation: scaleDown 0.3s ease forwards; }
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
.asset-library {
|
||||||
|
padding: 0 16px;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-library h3 {
|
||||||
|
font-size: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
color: var(--sub-text);
|
||||||
|
margin: 24px 16px 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed .asset-library h3 {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-category {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: var(--item-bg);
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header:hover {
|
||||||
|
background: var(--item-hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header span {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--text-color);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header i {
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--sub-text);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-category.active .category-header i {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-content {
|
||||||
|
display: none;
|
||||||
|
padding: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-category.active .category-content {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed .category-content {
|
||||||
|
display: none !important; /* Always hide content in sidebar when collapsed */
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-item {
|
||||||
|
background: var(--item-bg);
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: grab;
|
||||||
|
transition: all 0.3s;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-item span {
|
||||||
|
font-size: 9px;
|
||||||
|
color: var(--text-color);
|
||||||
|
text-align: center;
|
||||||
|
word-break: break-all;
|
||||||
|
line-height: 1.3;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed .asset-item span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-item:hover {
|
||||||
|
background: var(--item-hover-bg);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 10px 20px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-item img {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fixed Category Optimization */
|
||||||
|
.fixed-category .asset-grid {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-category .asset-item {
|
||||||
|
padding: 8px;
|
||||||
|
height: 44px;
|
||||||
|
width: 44px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-category .asset-item span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-category .asset-item i,
|
||||||
|
.fixed-category .asset-item img {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-divider {
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.1), transparent);
|
||||||
|
margin: 20px 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-divider::after {
|
||||||
|
content: '-';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: var(--sidebar-bg);
|
||||||
|
padding: 0 10px;
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--sub-text);
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
.asset-group-label {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--sub-text);
|
||||||
|
padding: 12px 16px 6px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.8px;
|
||||||
|
opacity: 0.7;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-group-label::after {
|
||||||
|
content: '';
|
||||||
|
flex: 1;
|
||||||
|
height: 1px;
|
||||||
|
background: rgba(255,255,255,0.05);
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
:root {
|
||||||
|
--bg-color: #f8fafc;
|
||||||
|
--bg-rgb: 248, 250, 252;
|
||||||
|
--text-color: #0f172a;
|
||||||
|
--accent-color: #3b82f6;
|
||||||
|
--panel-bg: rgba(255, 255, 255, 0.7);
|
||||||
|
--panel-border: rgba(255, 255, 255, 0.5);
|
||||||
|
--shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07);
|
||||||
|
--sub-text: #64748b;
|
||||||
|
--canvas-bg: #ffffff;
|
||||||
|
--grid-color: rgba(0, 0, 0, 0.05);
|
||||||
|
--grid-size: 20px;
|
||||||
|
--item-bg: rgba(255, 255, 255, 0.4);
|
||||||
|
--item-hover-bg: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--bg-color: #020617;
|
||||||
|
--bg-rgb: 2, 6, 23;
|
||||||
|
--text-color: #f8fafc;
|
||||||
|
--accent-color: #3b82f6;
|
||||||
|
--panel-bg: rgba(15, 23, 42, 0.8);
|
||||||
|
--panel-border: rgba(255, 255, 255, 0.1);
|
||||||
|
--shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3);
|
||||||
|
--sub-text: #cbd5e1;
|
||||||
|
--canvas-bg: #1e293b;
|
||||||
|
--grid-color: rgba(255, 255, 255, 0.1);
|
||||||
|
--grid-size: 20px;
|
||||||
|
--item-bg: rgba(255, 255, 255, 0.05);
|
||||||
|
--item-hover-bg: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', -apple-system, sans-serif;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-panel {
|
||||||
|
background: var(--panel-bg);
|
||||||
|
backdrop-filter: blur(12px) saturate(180%);
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
border-radius: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Markdown Rendering Styles */
|
||||||
|
.markdown-body {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body h1, .markdown-body h2, .markdown-body h3 {
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body h1 { font-size: 1.8em; }
|
||||||
|
.markdown-body h2 {
|
||||||
|
font-size: 1.4em;
|
||||||
|
border-bottom: 1px solid var(--panel-border);
|
||||||
|
padding-bottom: 8px;
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
.markdown-body h3 { font-size: 1.2em; }
|
||||||
|
|
||||||
|
.markdown-body p { margin-bottom: 16px; color: var(--sub-text); }
|
||||||
|
|
||||||
|
.markdown-body ul, .markdown-body ol {
|
||||||
|
padding-left: 20px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body li { margin-bottom: 8px; }
|
||||||
|
|
||||||
|
.markdown-body blockquote {
|
||||||
|
padding: 12px 20px;
|
||||||
|
margin: 16px 0;
|
||||||
|
background: var(--item-bg);
|
||||||
|
border-left: 4px solid var(--accent-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--sub-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body code {
|
||||||
|
background: var(--item-bg);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body pre {
|
||||||
|
background: var(--item-bg);
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body th, .markdown-body td {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid var(--panel-border);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body th {
|
||||||
|
background: var(--item-bg);
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body img {
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
.editor-panel {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
width: 480px;
|
||||||
|
min-width: 400px;
|
||||||
|
height: 200px;
|
||||||
|
z-index: 90;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transform-origin: bottom right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-panel.collapsed {
|
||||||
|
height: 40px; /* Header only height */
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-header {
|
||||||
|
padding: 12px 25px;
|
||||||
|
border-bottom: 1px solid var(--panel-border);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-header span {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 900;
|
||||||
|
color: var(--sub-text);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
resize: none;
|
||||||
|
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-color);
|
||||||
|
outline: none;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
.sidebar.collapsed .category-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-collapsed-icon {
|
||||||
|
display: none;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed .category-collapsed-icon {
|
||||||
|
display: flex;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-collapsed-icon:hover {
|
||||||
|
background: white;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flyout Panel */
|
||||||
|
.category-flyout {
|
||||||
|
position: fixed;
|
||||||
|
left: 90px;
|
||||||
|
top: auto;
|
||||||
|
width: 280px;
|
||||||
|
max-height: 80vh;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-10px);
|
||||||
|
transition: all 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-flyout.active {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flyout-menu {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 80px;
|
||||||
|
left: 20px;
|
||||||
|
width: 220px;
|
||||||
|
padding: 12px;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flyout-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-label {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--sub-text);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
padding: 8px 14px 4px;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flyout-item {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: left;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flyout-item i {
|
||||||
|
width: 16px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--sub-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flyout-item:hover {
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flyout-item:hover i {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flyout-item.primary {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flyout-item.primary:hover {
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
background: var(--panel-border);
|
||||||
|
margin: 8px 4px;
|
||||||
|
}
|
||||||
|
.flyout-header {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flyout-header strong {
|
||||||
|
font-size: 14px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flyout-header small {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #94a3b8;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flyout-grid {
|
||||||
|
overflow-y: auto;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
.header {
|
||||||
|
position: absolute;
|
||||||
|
top: 24px;
|
||||||
|
right: 24px;
|
||||||
|
height: 80px;
|
||||||
|
z-index: 90;
|
||||||
|
padding: 0 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-compact {
|
||||||
|
height: 50px;
|
||||||
|
padding: 0 20px;
|
||||||
|
top: 15px;
|
||||||
|
right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-compact h2 {
|
||||||
|
font-size: 13px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-compact p {
|
||||||
|
font-size: 9px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
background: rgba(16, 185, 129, 0.1);
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 1px solid rgba(16, 185, 129, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-circle-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #047857;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
padding: 2px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-circle-btn:hover {
|
||||||
|
color: #10b981;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot.online {
|
||||||
|
background: #10b981;
|
||||||
|
box-shadow: 0 0 8px #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h2 {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-btns {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--item-bg);
|
||||||
|
color: var(--sub-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-search {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 40px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 6px 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-search:focus-within {
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-search i {
|
||||||
|
color: var(--sub-text);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-search input {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
@@ -0,0 +1,318 @@
|
|||||||
|
/* layers.css - Layer Management Panel Styles */
|
||||||
|
|
||||||
|
.layer-panel {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 24px;
|
||||||
|
right: 300px; /* Offset from Inventory */
|
||||||
|
width: 250px;
|
||||||
|
max-height: 400px;
|
||||||
|
z-index: 1000;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-item.dragging {
|
||||||
|
opacity: 0.4;
|
||||||
|
background: var(--primary-color-dim);
|
||||||
|
border: 1px dashed var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-handle {
|
||||||
|
cursor: grab;
|
||||||
|
color: #64748b;
|
||||||
|
margin-right: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-item:active .drag-handle {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--text-color);
|
||||||
|
border-bottom: 1px solid var(--panel-border);
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header i {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--item-bg);
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-item:hover {
|
||||||
|
background: var(--item-hover-bg);
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-item.active {
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-color {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-color);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-actions button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--sub-text);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-actions button:hover {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header button {
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: white !important;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header button:hover {
|
||||||
|
background: #2563eb;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header button i {
|
||||||
|
color: white !important; /* Force icon color */
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-btn:hover {
|
||||||
|
background: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-footer {
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid var(--panel-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.opacity-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--sub-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.opacity-control input[type="range"] {
|
||||||
|
flex: 1;
|
||||||
|
height: 6px;
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 3px;
|
||||||
|
appearance: none;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opacity-control input[type="range"]::-webkit-slider-runnable-track {
|
||||||
|
background: linear-gradient(to right, var(--accent-color), #ddd);
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opacity-control input[type="range"]::-webkit-slider-thumb {
|
||||||
|
appearance: none;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: var(--accent-color);
|
||||||
|
border: 2px solid white;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: -4px; /* Align with track */
|
||||||
|
}
|
||||||
|
|
||||||
|
#opacity-val {
|
||||||
|
min-width: 30px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-rename-input {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid var(--accent-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
width: 100px;
|
||||||
|
outline: none;
|
||||||
|
color: var(--main-text);
|
||||||
|
}
|
||||||
|
/* Trash Bin for Deletion */
|
||||||
|
.layer-trash {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1.5px dashed rgba(239, 68, 68, 0.4);
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #ef4444;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
background: rgba(239, 68, 68, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-trash i {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-trash.drag-over {
|
||||||
|
background: #ef4444;
|
||||||
|
color: white;
|
||||||
|
border-color: #ef4444;
|
||||||
|
transform: scale(1.02);
|
||||||
|
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
|
||||||
|
}
|
||||||
|
/* Visual Nudge: Pulse Animation */
|
||||||
|
@keyframes nudgePulse {
|
||||||
|
0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7); }
|
||||||
|
70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); }
|
||||||
|
100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.pulse-nudge {
|
||||||
|
animation: nudgePulse 1.5s infinite;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mini Guide Tooltip */
|
||||||
|
.nudge-tooltip {
|
||||||
|
position: absolute;
|
||||||
|
left: 45px;
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
white-space: nowrap;
|
||||||
|
pointer-events: none;
|
||||||
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
|
||||||
|
animation: tooltipFadeIn 0.3s ease-out forwards;
|
||||||
|
z-index: 1001;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nudge-tooltip::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -4px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
border-top: 4px solid transparent;
|
||||||
|
border-bottom: 4px solid transparent;
|
||||||
|
border-right: 4px solid #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes tooltipFadeIn {
|
||||||
|
from { opacity: 0; transform: translateX(-10px); }
|
||||||
|
to { opacity: 1; transform: translateX(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes tooltipFadeOut {
|
||||||
|
from { opacity: 1; transform: translateX(0); }
|
||||||
|
to { opacity: 0; transform: translateX(5px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Active Layer Floating Badge */
|
||||||
|
.active-layer-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 100;
|
||||||
|
pointer-events: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background: rgba(15, 23, 42, 0.6);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||||
|
animation: fadeInDown 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-layer-badge .badge-label {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #94a3b8;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-layer-badge .badge-value {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 900;
|
||||||
|
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes badgePop {
|
||||||
|
0% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.1); }
|
||||||
|
100% { transform: scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-pop {
|
||||||
|
animation: badgePop 0.3s ease-out;
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/* Main Area Adjustment */
|
||||||
|
.header, .editor-panel {
|
||||||
|
left: 284px; /* Default sidebar width + margin */
|
||||||
|
transition: left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed ~ .header,
|
||||||
|
.sidebar.collapsed ~ .editor-panel,
|
||||||
|
.sidebar.collapsed ~ .main-viewport {
|
||||||
|
left: 104px; /* Collapsed sidebar width + margin */
|
||||||
|
}
|
||||||
|
.main-viewport {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 284px;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
display: flex;
|
||||||
|
/* justify-content and align-items removed to allow margin:auto to handle centering with overflow */
|
||||||
|
padding: 60px; /* Space for shadow and boundaries */
|
||||||
|
transition: left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cy {
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-color: var(--canvas-bg); /* Theme-aware paper background */
|
||||||
|
position: relative;
|
||||||
|
/* Sizes will be set by JS or default to 100% */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: auto; /* Handles centering when sized fixed (A3/A4) */
|
||||||
|
box-shadow: 0 0 40px rgba(0,0,0,0.1);
|
||||||
|
border: 1px solid var(--panel-border); /* Visible canvas boundary */
|
||||||
|
}
|
||||||
|
|
||||||
|
#cy.grid-solid {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(var(--grid-color) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
|
||||||
|
background-size: var(--grid-size) var(--grid-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#cy.grid-dashed {
|
||||||
|
background-image:
|
||||||
|
radial-gradient(var(--grid-color) 1px, transparent 1px);
|
||||||
|
background-size: var(--grid-size) var(--grid-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inventory Panel Overlay (Legacy - handled in analysis.css) */
|
||||||
|
/* .inventory-panel { ... } */
|
||||||
|
|
||||||
|
.inventory-header {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--sub-text);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-color);
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-item .count {
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
z-index: 10000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay.active {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
width: 500px;
|
||||||
|
max-width: 90vw;
|
||||||
|
background: var(--panel-bg);
|
||||||
|
backdrop-filter: blur(20px) saturate(180%);
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
border-radius: 32px;
|
||||||
|
box-shadow: 0 20px 50px rgba(0,0,0,0.1);
|
||||||
|
transform: scale(0.9) translateY(20px);
|
||||||
|
transition: all 0.4s cubic-bezier(0.18, 0.89, 0.32, 1.28);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay.active .modal-content {
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
padding: 24px 30px;
|
||||||
|
border-bottom: 1px solid var(--panel-border);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-modal {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
font-size: 20px;
|
||||||
|
color: var(--sub-text);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-modal:hover {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 30px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-group {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-group h3 {
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
color: var(--sub-text);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item select,
|
||||||
|
.setting-item input[type="number"],
|
||||||
|
.setting-item input[type="text"] {
|
||||||
|
background: var(--item-bg);
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-color);
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item select:focus,
|
||||||
|
.setting-item input:focus {
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item select option {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item input[type="color"] {
|
||||||
|
appearance: none;
|
||||||
|
width: 40px;
|
||||||
|
height: 24px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item input[type="color"]::-webkit-color-swatch {
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
padding: 20px 30px;
|
||||||
|
border-top: 1px solid var(--panel-border);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
/* Properties Sidebar Styles */
|
||||||
|
#properties-sidebar {
|
||||||
|
position: fixed;
|
||||||
|
right: -320px; /* Hidden by default */
|
||||||
|
top: 60px; /* Below header */
|
||||||
|
bottom: 0;
|
||||||
|
width: 300px;
|
||||||
|
background: var(--panel-bg);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
-webkit-backdrop-filter: blur(16px);
|
||||||
|
border-left: 1px solid var(--panel-border);
|
||||||
|
box-shadow: -10px 0 25px rgba(0, 0, 0, 0.3);
|
||||||
|
transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
z-index: 900;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#properties-sidebar.open {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
padding: 18px 20px;
|
||||||
|
border-bottom: 1px solid var(--panel-border);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-sidebar {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 20px;
|
||||||
|
color: var(--sub-text);
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-sidebar:hover {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 24px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prop-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prop-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prop-row .prop-group {
|
||||||
|
flex: 1;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prop-group.horizontal {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prop-group.horizontal label {
|
||||||
|
flex: 0 0 80px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prop-group.horizontal .prop-input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prop-group label {
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--sub-text);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prop-input {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--item-bg);
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 9px 12px;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prop-input:focus {
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
background: var(--item-hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-footer {
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-top: 1px solid var(--panel-border);
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
background: rgba(var(--bg-rgb), 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-apply {
|
||||||
|
flex: 1;
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 700;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-apply:hover {
|
||||||
|
filter: brightness(1.1);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle Switch Styles */
|
||||||
|
.toggle-group {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 12px 14px;
|
||||||
|
background: var(--item-bg);
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch:hover {
|
||||||
|
background: var(--item-hover-bg);
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch span {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-slider {
|
||||||
|
position: relative;
|
||||||
|
width: 36px;
|
||||||
|
height: 20px;
|
||||||
|
background: #475569;
|
||||||
|
border-radius: 20px;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-slider:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
left: 2px;
|
||||||
|
top: 2px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input:checked + .switch-slider {
|
||||||
|
background: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input:checked + .switch-slider:before {
|
||||||
|
transform: translateX(16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Multi-Selection Alignment UI */
|
||||||
|
.alignment-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-btn {
|
||||||
|
aspect-ratio: 1;
|
||||||
|
background: var(--item-bg);
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--sub-text);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-btn:hover:not(:disabled) {
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-btn:disabled {
|
||||||
|
opacity: 0.2;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Color Input Consistency */
|
||||||
|
.input-with-color {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-with-color .prop-input[type="color"] {
|
||||||
|
width: 44px;
|
||||||
|
height: 38px;
|
||||||
|
padding: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-with-color .prop-input[type="color"]::-webkit-color-swatch {
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Danger variant for toggle-switch */
|
||||||
|
.toggle-switch.danger input:checked + .switch-slider {
|
||||||
|
background: #ef4444;
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
/* settings_panel.css - Professional Sliding Settings Panel */
|
||||||
|
|
||||||
|
.settings-panel {
|
||||||
|
position: fixed;
|
||||||
|
top: 10px;
|
||||||
|
right: -420px; /* Hidden by default */
|
||||||
|
width: 400px;
|
||||||
|
height: calc(100vh - 20px);
|
||||||
|
z-index: 2000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: -10px 0 30px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-panel.active {
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-header {
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-bottom: 1px solid var(--panel-border);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title i {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar Tabs */
|
||||||
|
.settings-sidebar {
|
||||||
|
width: 100px;
|
||||||
|
background: var(--item-bg);
|
||||||
|
border-right: 1px solid var(--panel-border);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-tab {
|
||||||
|
padding: 18px 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--sub-text);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-tab i {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-tab span {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-tab:hover {
|
||||||
|
color: var(--text-color);
|
||||||
|
background: var(--item-hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-tab.active {
|
||||||
|
color: var(--accent-color);
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
border-left: 3px solid var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Body Content */
|
||||||
|
.settings-body {
|
||||||
|
flex: 1;
|
||||||
|
padding: 24px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: rgba(var(--bg-rgb), 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section h3 {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--sub-text);
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
letter-spacing: 0.8px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 14px 0;
|
||||||
|
border-bottom: 1px solid var(--panel-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-info .label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-info .desc {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--sub-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-ctrl select,
|
||||||
|
.setting-ctrl input[type="number"],
|
||||||
|
.setting-ctrl input[type="text"] {
|
||||||
|
background: var(--item-bg);
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 7px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-ctrl select:focus,
|
||||||
|
.setting-ctrl input:focus {
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle switch adjustments within settings */
|
||||||
|
.toggle-switch {
|
||||||
|
background: var(--item-bg) !important;
|
||||||
|
border: 1px solid var(--panel-border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch:hover {
|
||||||
|
background: var(--item-hover-bg) !important;
|
||||||
|
border-color: var(--accent-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch .label {
|
||||||
|
color: var(--sub-text) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(5px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--sub-text);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 20px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:hover {
|
||||||
|
background: #ef4444;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
.sidebar {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 260px;
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: 0;
|
||||||
|
border-left: none;
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: none;
|
||||||
|
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed {
|
||||||
|
width: 80px;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
padding: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-height: 80px;
|
||||||
|
transition: padding 0.3s;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed .sidebar-header {
|
||||||
|
padding: 24px 0;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-toggle {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--sub-text);
|
||||||
|
font-size: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed .menu-toggle {
|
||||||
|
order: -1; /* Toggle button first when collapsed */
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-toggle:hover {
|
||||||
|
background: var(--item-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-area {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed .logo-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-icon {
|
||||||
|
min-width: 40px;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background: linear-gradient(135deg, #3b82f6, #6366f1);
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 18px;
|
||||||
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
padding: 0 24px 20px;
|
||||||
|
position: relative;
|
||||||
|
transition: padding 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container i {
|
||||||
|
position: absolute;
|
||||||
|
left: 40px;
|
||||||
|
top: 13px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--sub-text);
|
||||||
|
z-index: 5;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#asset-search {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--item-bg);
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
padding: 10px 15px 10px 40px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: inherit;
|
||||||
|
outline: none;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#asset-search:focus {
|
||||||
|
background: var(--item-hover-bg);
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.03);
|
||||||
|
border-color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed .search-container {
|
||||||
|
padding: 10px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed #asset-search {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed .search-container i {
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--sub-text);
|
||||||
|
cursor: pointer;
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/* Sidebar Footer Settings */
|
||||||
|
.sidebar-footer {
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-top: 1px solid var(--panel-border);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: auto; /* Push to bottom */
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-btns {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-btn {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background: var(--item-bg);
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--sub-text);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-btn:hover {
|
||||||
|
background: var(--item-hover-bg);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings-btn:hover {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#system-menu-btn.active {
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-text p {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-text small {
|
||||||
|
font-size: 9px;
|
||||||
|
color: var(--sub-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed .sidebar-footer {
|
||||||
|
padding: 20px 0;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed .footer-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
.floating-window {
|
||||||
|
position: fixed;
|
||||||
|
background: var(--sidebar-bg);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 20px 50px rgba(0,0,0,0.3);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
animation: win-appear 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes win-appear {
|
||||||
|
from { opacity: 0; transform: scale(0.9) translateY(20px); }
|
||||||
|
to { opacity: 1; transform: scale(1) translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.win-header {
|
||||||
|
padding: 12px 20px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-bottom: 1px solid var(--panel-border);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
cursor: grab;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.win-header:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.win-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--text-color);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.win-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.win-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--sub-text);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.win-btn:hover {
|
||||||
|
background: var(--item-hover-bg);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.win-btn.win-close:hover {
|
||||||
|
background: #ef4444;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-window.minimized {
|
||||||
|
height: 48px !important;
|
||||||
|
width: 250px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-window.minimized .win-body {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.win-body {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Studio Specific Polish */
|
||||||
|
.studio-container {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.studio-modal .preview-area {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .floating-window {
|
||||||
|
background: rgba(15, 23, 42, 0.8);
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
drawNET Premium CSS Manifest
|
||||||
|
This file imports modules to keep the production bundle organized.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@import "modules/base.css";
|
||||||
|
@import "modules/sidebar.css";
|
||||||
|
@import "modules/sidebar_footer.css";
|
||||||
|
@import "modules/assets.css";
|
||||||
|
@import "modules/flyout.css";
|
||||||
|
@import "modules/header.css";
|
||||||
|
@import "modules/editor.css";
|
||||||
|
@import "modules/layout.css";
|
||||||
|
@import "modules/animations.css";
|
||||||
|
@import "modules/modal.css";
|
||||||
|
@import "modules/settings_panel.css";
|
||||||
|
@import "modules/properties_sidebar.css";
|
||||||
|
|
||||||
|
/* Floating Context Menu */
|
||||||
|
.floating-menu {
|
||||||
|
background: rgba(30, 41, 59, 0.85); /* Slate 800-ish with glassmorphism */
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
|
||||||
|
padding: 6px;
|
||||||
|
min-width: 180px;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #f1f5f9;
|
||||||
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:hover {
|
||||||
|
background: #3b82f6; /* Blue 500 */
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-divider {
|
||||||
|
height: 1px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-label {
|
||||||
|
padding: 6px 14px 4px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #475569;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-icon-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 4px 8px;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #94a3b8;
|
||||||
|
transition: all 0.2s;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn:hover {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn i.fa-rotate-90 {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- X6 Port Styles --- */
|
||||||
|
.x6-port {
|
||||||
|
visibility: hidden;
|
||||||
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.x6-node:hover .x6-port {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.x6-port circle {
|
||||||
|
cursor: crosshair;
|
||||||
|
}
|
||||||
|
|
||||||
|
.x6-port circle:hover {
|
||||||
|
fill: #3b82f6 !important;
|
||||||
|
r: 6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- X6 Edge Selection Highlight --- */
|
||||||
|
.x6-edge-selected path {
|
||||||
|
stroke: #3b82f6 !important;
|
||||||
|
stroke-width: 3px !important;
|
||||||
|
filter: drop-shadow(0 0 3px rgba(59, 130, 246, 0.8));
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flow animation for selected flow lines */
|
||||||
|
.x6-edge-selected.flow-animation path {
|
||||||
|
stroke-width: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mini buttons for editor actions */
|
||||||
|
.footer-btn.mini {
|
||||||
|
width: auto;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 11px;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||||
|
color: #3b82f6;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-left: 6px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-btn.mini:hover {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Export Loading Overlay --- */
|
||||||
|
.export-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: rgba(15, 23, 42, 0.65); /* Slate 900 with transparency */
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
color: white;
|
||||||
|
font-family: inherit;
|
||||||
|
animation: fadeIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-spinner {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border: 4px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-left-color: #3b82f6;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-text {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,344 @@
|
|||||||
|
/*
|
||||||
|
* drawNET Studio - Asset Management Styles
|
||||||
|
*/
|
||||||
|
|
||||||
|
body.studio-body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: #0f172a;
|
||||||
|
color: #f8fafc;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.studio-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 280px 1fr 320px;
|
||||||
|
grid-template-rows: 60px 1fr;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.studio-header {
|
||||||
|
grid-column: 1 / 4;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 20px;
|
||||||
|
background: rgba(30, 41, 59, 0.7);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.studio-header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-metadata {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-input-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-label {
|
||||||
|
font-size: 9px;
|
||||||
|
color: #64748b;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-input {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
color: #f8fafc;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #38bdf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-input.highlight {
|
||||||
|
color: #38bdf8;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
background: rgba(15, 23, 42, 0.5);
|
||||||
|
border-right: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #94a3b8;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Source Panel (Left) */
|
||||||
|
.source-panel {
|
||||||
|
background: rgba(15, 23, 42, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area {
|
||||||
|
border: 2px dashed rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 30px 20px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
border-color: #38bdf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area i {
|
||||||
|
display: block;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #38bdf8;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-text {
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-hint {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-item {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-item i {
|
||||||
|
width: 16px;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Workspace (Center) */
|
||||||
|
.workspace {
|
||||||
|
background: #020617;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-card {
|
||||||
|
background: rgba(30, 41, 59, 0.5);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 15px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
background: rgba(30, 41, 59, 0.8);
|
||||||
|
border-color: #38bdf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-card.selected {
|
||||||
|
border-color: #38bdf8;
|
||||||
|
background: rgba(56, 189, 248, 0.1);
|
||||||
|
box-shadow: 0 0 0 1px #38bdf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-preview {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-preview img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-name {
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Comparison View */
|
||||||
|
.comparison-view {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comp-box {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comp-preview {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
background: #1e293b;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comp-preview img, .comp-preview svg {
|
||||||
|
max-width: 90%;
|
||||||
|
max-height: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comp-label {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #64748b;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
color: #94a3b8;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice-btn.active {
|
||||||
|
background: #38bdf8;
|
||||||
|
color: #0f172a;
|
||||||
|
border-color: #38bdf8;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Property Panel (Right) */
|
||||||
|
.property-panel {
|
||||||
|
border-left: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
background: rgba(15, 23, 42, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-empty {
|
||||||
|
text-align: center;
|
||||||
|
color: #64748b;
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-empty i {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-empty p {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #38bdf8;
|
||||||
|
color: #0f172a;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #7dd3fc;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 13px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link:hover {
|
||||||
|
color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* Ghost file to prevent MIME type 404 errors in legacy/library environments */
|
||||||
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 110 KiB |
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"hotkeys": [
|
||||||
|
{ "key": "Delete", "code": "Delete", "action": "deleteSelected", "description": "Remove selected elements" },
|
||||||
|
{ "key": "Backspace", "code": "Backspace", "action": "deleteSelected", "description": "Remove selected elements" },
|
||||||
|
{ "ctrl": true, "key": "f", "code": "KeyF", "action": "fitScreen", "description": "Fit to screen" },
|
||||||
|
{ "ctrl": true, "key": "=", "code": "Equal", "action": "zoomIn", "description": "Zoom in" },
|
||||||
|
{ "ctrl": true, "key": "+", "code": "Equal", "shift": true, "action": "zoomIn", "description": "Zoom in" },
|
||||||
|
{ "ctrl": true, "key": "-", "code": "Minus", "action": "zoomOut", "description": "Zoom out" },
|
||||||
|
{ "ctrl": true, "key": "s", "code": "KeyS", "action": "exportJson", "description": "Export JSON" },
|
||||||
|
{ "ctrl": true, "key": "o", "code": "KeyO", "action": "importJson", "description": "Import JSON" },
|
||||||
|
{ "alt": true, "key": "0", "code": "Digit0", "alwaysEnabled": true, "action": "toggleInventory", "description": "Toggle Inventory Panel" },
|
||||||
|
{ "alt": true, "key": "1", "code": "Digit1", "alwaysEnabled": true, "action": "toggleProperties", "description": "Toggle Properties Sidebar" },
|
||||||
|
{ "alt": true, "key": "Enter", "code": "Enter", "alwaysEnabled": true, "action": "toggleProperties", "description": "Open/Toggle Properties Sidebar" },
|
||||||
|
{ "key": "F2", "code": "F2", "action": "editLabel", "description": "Edit label of selected element" },
|
||||||
|
{ "key": "[", "code": "BracketLeft", "action": "sendToBack", "description": "Send selected to back" },
|
||||||
|
{ "key": "]", "code": "BracketRight", "action": "bringToFront", "description": "Bring selected to front" },
|
||||||
|
{ "ctrl": true, "key": "l", "code": "KeyL", "action": "toggleLock", "description": "Lock/Unlock selected element" },
|
||||||
|
{ "alt": true, "key": "9", "code": "Digit9", "alwaysEnabled": true, "action": "toggleLayers", "description": "Toggle Layer Panel" },
|
||||||
|
{ "ctrl": true, "shift": true, "key": "l", "code": "KeyL", "action": "arrangeLayout", "description": "Auto-arrange layout" },
|
||||||
|
{ "shift": true, "key": "a", "code": "KeyA", "action": "connectNodes", "description": "Connect selected nodes (Manhattan)" },
|
||||||
|
{ "shift": true, "key": "s", "code": "KeyS", "action": "connectStraight", "description": "Connect selected nodes (Straight)" },
|
||||||
|
{ "alt": true, "key": "m", "code": "KeyM", "action": "connectNodes", "description": "Change to Manhattan Line" },
|
||||||
|
{ "alt": true, "key": "l", "code": "KeyL", "action": "connectStraight", "description": "Change to Straight Line" },
|
||||||
|
{ "shift": true, "key": "d", "code": "KeyD", "action": "disconnectNodes", "description": "Disconnect selected nodes" },
|
||||||
|
{ "ctrl": true, "key": "g", "code": "KeyG", "action": "groupNodes", "description": "Group Selected" },
|
||||||
|
{ "ctrl": true, "key": "z", "code": "KeyZ", "action": "undo", "alwaysEnabled": true, "description": "Undo last action" },
|
||||||
|
{ "ctrl": true, "key": "y", "code": "KeyY", "action": "redo", "alwaysEnabled": true, "description": "Redo last undone action" },
|
||||||
|
{ "ctrl": true, "shift": true, "key": "z", "code": "KeyZ", "action": "redo", "alwaysEnabled": true, "description": "Redo last undone action" },
|
||||||
|
{ "ctrl": true, "key": "c", "code": "KeyC", "action": "copyNodes", "description": "Copy selected nodes" },
|
||||||
|
{ "ctrl": true, "key": "v", "code": "KeyV", "action": "pasteNodes", "description": "Paste nodes from clipboard" },
|
||||||
|
{ "ctrl": true, "key": "d", "code": "KeyD", "action": "duplicateNodes", "description": "Duplicate selected nodes (Immediate)" },
|
||||||
|
{ "ctrl": true, "shift": true, "key": "c", "code": "KeyC", "action": "copyAttributes", "description": "Copy node attributes (Format Painter)" },
|
||||||
|
{ "ctrl": true, "shift": true, "key": "v", "code": "KeyV", "action": "pasteAttributes", "description": "Paste node attributes" },
|
||||||
|
|
||||||
|
{ "shift": true, "key": "1", "code": "Digit1", "action": "alignTop", "description": "Align nodes to top" },
|
||||||
|
{ "shift": true, "key": "2", "code": "Digit2", "action": "alignBottom", "description": "Align nodes to bottom" },
|
||||||
|
{ "shift": true, "key": "3", "code": "Digit3", "action": "alignLeft", "description": "Align nodes to left" },
|
||||||
|
{ "shift": true, "key": "4", "code": "Digit4", "action": "alignRight", "description": "Align nodes to right" },
|
||||||
|
{ "shift": true, "key": "5", "code": "Digit5", "action": "alignMiddle", "description": "Align nodes to vertical center" },
|
||||||
|
{ "shift": true, "key": "6", "code": "Digit6", "action": "alignCenter", "description": "Align nodes to horizontal center" },
|
||||||
|
{ "shift": true, "key": "7", "code": "Digit7", "action": "distributeHorizontal", "description": "Distribute nodes horizontally" },
|
||||||
|
{ "shift": true, "key": "8", "code": "Digit8", "action": "distributeVertical", "description": "Distribute nodes vertically" },
|
||||||
|
|
||||||
|
{ "key": "ArrowUp", "code": "ArrowUp", "action": "moveUp", "description": "Move nodes up" },
|
||||||
|
{ "key": "ArrowDown", "code": "ArrowDown", "action": "moveDown", "description": "Move nodes down" },
|
||||||
|
{ "key": "ArrowLeft", "code": "ArrowLeft", "action": "moveLeft", "description": "Move nodes left" },
|
||||||
|
{ "key": "ArrowRight", "code": "ArrowRight", "action": "moveRight", "description": "Move nodes right" },
|
||||||
|
{ "shift": true, "key": "ArrowUp", "code": "ArrowUp", "action": "moveUpLarge", "description": "Move nodes up (large)" },
|
||||||
|
{ "shift": true, "key": "ArrowDown", "code": "ArrowDown", "action": "moveDownLarge", "description": "Move nodes down (large)" },
|
||||||
|
{ "shift": true, "key": "ArrowLeft", "code": "ArrowLeft", "action": "moveLeftLarge", "description": "Move nodes left (large)" },
|
||||||
|
{ "shift": true, "key": "ArrowRight", "code": "ArrowRight", "action": "moveRightLarge", "description": "Move nodes right (large)" },
|
||||||
|
{ "key": "Escape", "code": "Escape", "alwaysEnabled": true, "action": "cancel", "description": "Deselect all and close sidebar" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { initAssets } from './modules/assets/index.js';
|
||||||
|
import { initGraph } from './modules/graph.js';
|
||||||
|
import { initUIToggles } from './modules/ui.js';
|
||||||
|
import { initSettings } from './modules/settings/ui.js';
|
||||||
|
import { applySavedSettings, applySavedTheme } from './modules/settings/store.js';
|
||||||
|
import { initHotkeys } from './modules/hotkeys/index.js';
|
||||||
|
import { initGlobalSearch } from './modules/graph/search.js';
|
||||||
|
import { initInventory } from './modules/graph/analysis.js';
|
||||||
|
import { initLayerPanel } from './modules/ui/layer_panel.js';
|
||||||
|
import { applyLayerFilters } from './modules/graph/layers.js';
|
||||||
|
import { initI18n } from './modules/i18n.js';
|
||||||
|
import { initPropertiesSidebar } from './modules/properties_sidebar/index.js';
|
||||||
|
import { initPersistence } from './modules/persistence.js';
|
||||||
|
import { initGraphIO } from './modules/graph/io/index.js';
|
||||||
|
import { initContextMenu } from './modules/ui/context_menu/index.js';
|
||||||
|
import { logger } from './modules/utils/logger.js';
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
// 0. Initialize i18n (Language first)
|
||||||
|
await initI18n();
|
||||||
|
|
||||||
|
// 1. Apply Saved Theme immediately (prevent flash)
|
||||||
|
applySavedTheme();
|
||||||
|
|
||||||
|
// 2. Load Assets & Sidebar
|
||||||
|
await initAssets();
|
||||||
|
|
||||||
|
// 2.5 Load Global Config
|
||||||
|
try {
|
||||||
|
const configRes = await fetch('/api/config');
|
||||||
|
if (configRes.ok) {
|
||||||
|
const configData = await configRes.json();
|
||||||
|
const { state } = await import('./modules/state.js');
|
||||||
|
state.appConfig = configData;
|
||||||
|
logger.info("Global configuration loaded from server.");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.high("Failed to load global config, using defaults.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Initialize X6 Graph
|
||||||
|
initGraph();
|
||||||
|
|
||||||
|
// 4. Initialize UI Toggles
|
||||||
|
initUIToggles();
|
||||||
|
|
||||||
|
// 5. Initialize Settings Modal Logic
|
||||||
|
initSettings();
|
||||||
|
|
||||||
|
// 6. Apply Saved Visual Settings (Grid, Size)
|
||||||
|
applySavedSettings();
|
||||||
|
|
||||||
|
// 7. Initialize Keyboard Hotkeys
|
||||||
|
initHotkeys();
|
||||||
|
|
||||||
|
// 8. Initialize UI Components & Persistence
|
||||||
|
initPropertiesSidebar();
|
||||||
|
initContextMenu();
|
||||||
|
initGraphIO();
|
||||||
|
initGlobalSearch();
|
||||||
|
initInventory();
|
||||||
|
initLayerPanel();
|
||||||
|
applyLayerFilters();
|
||||||
|
initPersistence();
|
||||||
|
|
||||||
|
// Help Button
|
||||||
|
const helpBtn = document.getElementById('help-btn');
|
||||||
|
if (helpBtn) {
|
||||||
|
helpBtn.addEventListener('click', async () => {
|
||||||
|
const { showHelpModal } = await import('./modules/ui/help_modal.js');
|
||||||
|
showHelpModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.high("drawNET Premium initialized successfully.");
|
||||||
|
});
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Lodash <https://lodash.com/>
|
||||||
|
* Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
|
||||||
|
* Released under MIT license <https://lodash.com/license>
|
||||||
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
||||||
|
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
||||||
|
*/
|
||||||
|
(function(){function n(n,t,r){switch(r.length){case 0:return n.call(t);case 1:return n.call(t,r[0]);case 2:return n.call(t,r[0],r[1]);case 3:return n.call(t,r[0],r[1],r[2])}return n.apply(t,r)}function t(n,t,r,e){for(var u=-1,i=null==n?0:n.length;++u<i;){var o=n[u];t(e,o,r(o),n)}return e}function r(n,t){for(var r=-1,e=null==n?0:n.length;++r<e&&t(n[r],r,n)!==!1;);return n}function e(n,t){for(var r=null==n?0:n.length;r--&&t(n[r],r,n)!==!1;);return n}function u(n,t){for(var r=-1,e=null==n?0:n.length;++r<e;)if(!t(n[r],r,n))return!1;
|
||||||
|
return!0}function i(n,t){for(var r=-1,e=null==n?0:n.length,u=0,i=[];++r<e;){var o=n[r];t(o,r,n)&&(i[u++]=o)}return i}function o(n,t){return!!(null==n?0:n.length)&&y(n,t,0)>-1}function f(n,t,r){for(var e=-1,u=null==n?0:n.length;++e<u;)if(r(t,n[e]))return!0;return!1}function c(n,t){for(var r=-1,e=null==n?0:n.length,u=Array(e);++r<e;)u[r]=t(n[r],r,n);return u}function a(n,t){for(var r=-1,e=t.length,u=n.length;++r<e;)n[u+r]=t[r];return n}function l(n,t,r,e){var u=-1,i=null==n?0:n.length;for(e&&i&&(r=n[++u]);++u<i;)r=t(r,n[u],u,n);
|
||||||
|
return r}function s(n,t,r,e){var u=null==n?0:n.length;for(e&&u&&(r=n[--u]);u--;)r=t(r,n[u],u,n);return r}function h(n,t){for(var r=-1,e=null==n?0:n.length;++r<e;)if(t(n[r],r,n))return!0;return!1}function p(n){return n.split("")}function _(n){return n.match($t)||[]}function v(n,t,r){var e;return r(n,function(n,r,u){if(t(n,r,u))return e=r,!1}),e}function g(n,t,r,e){for(var u=n.length,i=r+(e?1:-1);e?i--:++i<u;)if(t(n[i],i,n))return i;return-1}function y(n,t,r){return t===t?Z(n,t,r):g(n,b,r)}function d(n,t,r,e){
|
||||||
|
for(var u=r-1,i=n.length;++u<i;)if(e(n[u],t))return u;return-1}function b(n){return n!==n}function w(n,t){var r=null==n?0:n.length;return r?k(n,t)/r:Cn}function m(n){return function(t){return null==t?X:t[n]}}function x(n){return function(t){return null==n?X:n[t]}}function j(n,t,r,e,u){return u(n,function(n,u,i){r=e?(e=!1,n):t(r,n,u,i)}),r}function A(n,t){var r=n.length;for(n.sort(t);r--;)n[r]=n[r].value;return n}function k(n,t){for(var r,e=-1,u=n.length;++e<u;){var i=t(n[e]);i!==X&&(r=r===X?i:r+i);
|
||||||
|
}return r}function O(n,t){for(var r=-1,e=Array(n);++r<n;)e[r]=t(r);return e}function I(n,t){return c(t,function(t){return[t,n[t]]})}function R(n){return n?n.slice(0,H(n)+1).replace(Lt,""):n}function z(n){return function(t){return n(t)}}function E(n,t){return c(t,function(t){return n[t]})}function S(n,t){return n.has(t)}function W(n,t){for(var r=-1,e=n.length;++r<e&&y(t,n[r],0)>-1;);return r}function L(n,t){for(var r=n.length;r--&&y(t,n[r],0)>-1;);return r}function C(n,t){for(var r=n.length,e=0;r--;)n[r]===t&&++e;
|
||||||
|
return e}function U(n){return"\\"+Yr[n]}function B(n,t){return null==n?X:n[t]}function T(n){return Nr.test(n)}function $(n){return Pr.test(n)}function D(n){for(var t,r=[];!(t=n.next()).done;)r.push(t.value);return r}function M(n){var t=-1,r=Array(n.size);return n.forEach(function(n,e){r[++t]=[e,n]}),r}function F(n,t){return function(r){return n(t(r))}}function N(n,t){for(var r=-1,e=n.length,u=0,i=[];++r<e;){var o=n[r];o!==t&&o!==cn||(n[r]=cn,i[u++]=r)}return i}function P(n){var t=-1,r=Array(n.size);
|
||||||
|
return n.forEach(function(n){r[++t]=n}),r}function q(n){var t=-1,r=Array(n.size);return n.forEach(function(n){r[++t]=[n,n]}),r}function Z(n,t,r){for(var e=r-1,u=n.length;++e<u;)if(n[e]===t)return e;return-1}function K(n,t,r){for(var e=r+1;e--;)if(n[e]===t)return e;return e}function V(n){return T(n)?J(n):_e(n)}function G(n){return T(n)?Y(n):p(n)}function H(n){for(var t=n.length;t--&&Ct.test(n.charAt(t)););return t}function J(n){for(var t=Mr.lastIndex=0;Mr.test(n);)++t;return t}function Y(n){return n.match(Mr)||[];
|
||||||
|
}function Q(n){return n.match(Fr)||[]}var X,nn="4.17.21",tn=200,rn="Unsupported core-js use. Try https://npms.io/search?q=ponyfill.",en="Expected a function",un="Invalid `variable` option passed into `_.template`",on="__lodash_hash_undefined__",fn=500,cn="__lodash_placeholder__",an=1,ln=2,sn=4,hn=1,pn=2,_n=1,vn=2,gn=4,yn=8,dn=16,bn=32,wn=64,mn=128,xn=256,jn=512,An=30,kn="...",On=800,In=16,Rn=1,zn=2,En=3,Sn=1/0,Wn=9007199254740991,Ln=1.7976931348623157e308,Cn=NaN,Un=4294967295,Bn=Un-1,Tn=Un>>>1,$n=[["ary",mn],["bind",_n],["bindKey",vn],["curry",yn],["curryRight",dn],["flip",jn],["partial",bn],["partialRight",wn],["rearg",xn]],Dn="[object Arguments]",Mn="[object Array]",Fn="[object AsyncFunction]",Nn="[object Boolean]",Pn="[object Date]",qn="[object DOMException]",Zn="[object Error]",Kn="[object Function]",Vn="[object GeneratorFunction]",Gn="[object Map]",Hn="[object Number]",Jn="[object Null]",Yn="[object Object]",Qn="[object Promise]",Xn="[object Proxy]",nt="[object RegExp]",tt="[object Set]",rt="[object String]",et="[object Symbol]",ut="[object Undefined]",it="[object WeakMap]",ot="[object WeakSet]",ft="[object ArrayBuffer]",ct="[object DataView]",at="[object Float32Array]",lt="[object Float64Array]",st="[object Int8Array]",ht="[object Int16Array]",pt="[object Int32Array]",_t="[object Uint8Array]",vt="[object Uint8ClampedArray]",gt="[object Uint16Array]",yt="[object Uint32Array]",dt=/\b__p \+= '';/g,bt=/\b(__p \+=) '' \+/g,wt=/(__e\(.*?\)|\b__t\)) \+\n'';/g,mt=/&(?:amp|lt|gt|quot|#39);/g,xt=/[&<>"']/g,jt=RegExp(mt.source),At=RegExp(xt.source),kt=/<%-([\s\S]+?)%>/g,Ot=/<%([\s\S]+?)%>/g,It=/<%=([\s\S]+?)%>/g,Rt=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,zt=/^\w*$/,Et=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,St=/[\\^$.*+?()[\]{}|]/g,Wt=RegExp(St.source),Lt=/^\s+/,Ct=/\s/,Ut=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,Bt=/\{\n\/\* \[wrapped with (.+)\] \*/,Tt=/,? & /,$t=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,Dt=/[()=,{}\[\]\/\s]/,Mt=/\\(\\)?/g,Ft=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,Nt=/\w*$/,Pt=/^[-+]0x[0-9a-f]+$/i,qt=/^0b[01]+$/i,Zt=/^\[object .+?Constructor\]$/,Kt=/^0o[0-7]+$/i,Vt=/^(?:0|[1-9]\d*)$/,Gt=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,Ht=/($^)/,Jt=/['\n\r\u2028\u2029\\]/g,Yt="\\ud800-\\udfff",Qt="\\u0300-\\u036f",Xt="\\ufe20-\\ufe2f",nr="\\u20d0-\\u20ff",tr=Qt+Xt+nr,rr="\\u2700-\\u27bf",er="a-z\\xdf-\\xf6\\xf8-\\xff",ur="\\xac\\xb1\\xd7\\xf7",ir="\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf",or="\\u2000-\\u206f",fr=" \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",cr="A-Z\\xc0-\\xd6\\xd8-\\xde",ar="\\ufe0e\\ufe0f",lr=ur+ir+or+fr,sr="['\u2019]",hr="["+Yt+"]",pr="["+lr+"]",_r="["+tr+"]",vr="\\d+",gr="["+rr+"]",yr="["+er+"]",dr="[^"+Yt+lr+vr+rr+er+cr+"]",br="\\ud83c[\\udffb-\\udfff]",wr="(?:"+_r+"|"+br+")",mr="[^"+Yt+"]",xr="(?:\\ud83c[\\udde6-\\uddff]){2}",jr="[\\ud800-\\udbff][\\udc00-\\udfff]",Ar="["+cr+"]",kr="\\u200d",Or="(?:"+yr+"|"+dr+")",Ir="(?:"+Ar+"|"+dr+")",Rr="(?:"+sr+"(?:d|ll|m|re|s|t|ve))?",zr="(?:"+sr+"(?:D|LL|M|RE|S|T|VE))?",Er=wr+"?",Sr="["+ar+"]?",Wr="(?:"+kr+"(?:"+[mr,xr,jr].join("|")+")"+Sr+Er+")*",Lr="\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",Cr="\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])",Ur=Sr+Er+Wr,Br="(?:"+[gr,xr,jr].join("|")+")"+Ur,Tr="(?:"+[mr+_r+"?",_r,xr,jr,hr].join("|")+")",$r=RegExp(sr,"g"),Dr=RegExp(_r,"g"),Mr=RegExp(br+"(?="+br+")|"+Tr+Ur,"g"),Fr=RegExp([Ar+"?"+yr+"+"+Rr+"(?="+[pr,Ar,"$"].join("|")+")",Ir+"+"+zr+"(?="+[pr,Ar+Or,"$"].join("|")+")",Ar+"?"+Or+"+"+Rr,Ar+"+"+zr,Cr,Lr,vr,Br].join("|"),"g"),Nr=RegExp("["+kr+Yt+tr+ar+"]"),Pr=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,qr=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],Zr=-1,Kr={};
|
||||||
|
Kr[at]=Kr[lt]=Kr[st]=Kr[ht]=Kr[pt]=Kr[_t]=Kr[vt]=Kr[gt]=Kr[yt]=!0,Kr[Dn]=Kr[Mn]=Kr[ft]=Kr[Nn]=Kr[ct]=Kr[Pn]=Kr[Zn]=Kr[Kn]=Kr[Gn]=Kr[Hn]=Kr[Yn]=Kr[nt]=Kr[tt]=Kr[rt]=Kr[it]=!1;var Vr={};Vr[Dn]=Vr[Mn]=Vr[ft]=Vr[ct]=Vr[Nn]=Vr[Pn]=Vr[at]=Vr[lt]=Vr[st]=Vr[ht]=Vr[pt]=Vr[Gn]=Vr[Hn]=Vr[Yn]=Vr[nt]=Vr[tt]=Vr[rt]=Vr[et]=Vr[_t]=Vr[vt]=Vr[gt]=Vr[yt]=!0,Vr[Zn]=Vr[Kn]=Vr[it]=!1;var Gr={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a",
|
||||||
|
"\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae",
|
||||||
|
"\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g",
|
||||||
|
"\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i","\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O",
|
||||||
|
"\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S","\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w",
|
||||||
|
"\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe","\u0149":"'n","\u017f":"s"},Hr={"&":"&","<":"<",">":">",'"':""","'":"'"},Jr={"&":"&","<":"<",">":">",""":'"',"'":"'"},Yr={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Qr=parseFloat,Xr=parseInt,ne="object"==typeof global&&global&&global.Object===Object&&global,te="object"==typeof self&&self&&self.Object===Object&&self,re=ne||te||Function("return this")(),ee="object"==typeof exports&&exports&&!exports.nodeType&&exports,ue=ee&&"object"==typeof module&&module&&!module.nodeType&&module,ie=ue&&ue.exports===ee,oe=ie&&ne.process,fe=function(){
|
||||||
|
try{var n=ue&&ue.require&&ue.require("util").types;return n?n:oe&&oe.binding&&oe.binding("util")}catch(n){}}(),ce=fe&&fe.isArrayBuffer,ae=fe&&fe.isDate,le=fe&&fe.isMap,se=fe&&fe.isRegExp,he=fe&&fe.isSet,pe=fe&&fe.isTypedArray,_e=m("length"),ve=x(Gr),ge=x(Hr),ye=x(Jr),de=function p(x){function Z(n){if(cc(n)&&!bh(n)&&!(n instanceof Ct)){if(n instanceof Y)return n;if(bl.call(n,"__wrapped__"))return eo(n)}return new Y(n)}function J(){}function Y(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t,
|
||||||
|
this.__index__=0,this.__values__=X}function Ct(n){this.__wrapped__=n,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=Un,this.__views__=[]}function $t(){var n=new Ct(this.__wrapped__);return n.__actions__=Tu(this.__actions__),n.__dir__=this.__dir__,n.__filtered__=this.__filtered__,n.__iteratees__=Tu(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=Tu(this.__views__),n}function Yt(){if(this.__filtered__){var n=new Ct(this);n.__dir__=-1,
|
||||||
|
n.__filtered__=!0}else n=this.clone(),n.__dir__*=-1;return n}function Qt(){var n=this.__wrapped__.value(),t=this.__dir__,r=bh(n),e=t<0,u=r?n.length:0,i=Oi(0,u,this.__views__),o=i.start,f=i.end,c=f-o,a=e?f:o-1,l=this.__iteratees__,s=l.length,h=0,p=Hl(c,this.__takeCount__);if(!r||!e&&u==c&&p==c)return wu(n,this.__actions__);var _=[];n:for(;c--&&h<p;){a+=t;for(var v=-1,g=n[a];++v<s;){var y=l[v],d=y.iteratee,b=y.type,w=d(g);if(b==zn)g=w;else if(!w){if(b==Rn)continue n;break n}}_[h++]=g}return _}function Xt(n){
|
||||||
|
var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function nr(){this.__data__=is?is(null):{},this.size=0}function tr(n){var t=this.has(n)&&delete this.__data__[n];return this.size-=t?1:0,t}function rr(n){var t=this.__data__;if(is){var r=t[n];return r===on?X:r}return bl.call(t,n)?t[n]:X}function er(n){var t=this.__data__;return is?t[n]!==X:bl.call(t,n)}function ur(n,t){var r=this.__data__;return this.size+=this.has(n)?0:1,r[n]=is&&t===X?on:t,this}function ir(n){
|
||||||
|
var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function or(){this.__data__=[],this.size=0}function fr(n){var t=this.__data__,r=Wr(t,n);return!(r<0)&&(r==t.length-1?t.pop():Ll.call(t,r,1),--this.size,!0)}function cr(n){var t=this.__data__,r=Wr(t,n);return r<0?X:t[r][1]}function ar(n){return Wr(this.__data__,n)>-1}function lr(n,t){var r=this.__data__,e=Wr(r,n);return e<0?(++this.size,r.push([n,t])):r[e][1]=t,this}function sr(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){
|
||||||
|
var e=n[t];this.set(e[0],e[1])}}function hr(){this.size=0,this.__data__={hash:new Xt,map:new(ts||ir),string:new Xt}}function pr(n){var t=xi(this,n).delete(n);return this.size-=t?1:0,t}function _r(n){return xi(this,n).get(n)}function vr(n){return xi(this,n).has(n)}function gr(n,t){var r=xi(this,n),e=r.size;return r.set(n,t),this.size+=r.size==e?0:1,this}function yr(n){var t=-1,r=null==n?0:n.length;for(this.__data__=new sr;++t<r;)this.add(n[t])}function dr(n){return this.__data__.set(n,on),this}function br(n){
|
||||||
|
return this.__data__.has(n)}function wr(n){this.size=(this.__data__=new ir(n)).size}function mr(){this.__data__=new ir,this.size=0}function xr(n){var t=this.__data__,r=t.delete(n);return this.size=t.size,r}function jr(n){return this.__data__.get(n)}function Ar(n){return this.__data__.has(n)}function kr(n,t){var r=this.__data__;if(r instanceof ir){var e=r.__data__;if(!ts||e.length<tn-1)return e.push([n,t]),this.size=++r.size,this;r=this.__data__=new sr(e)}return r.set(n,t),this.size=r.size,this}function Or(n,t){
|
||||||
|
var r=bh(n),e=!r&&dh(n),u=!r&&!e&&mh(n),i=!r&&!e&&!u&&Oh(n),o=r||e||u||i,f=o?O(n.length,hl):[],c=f.length;for(var a in n)!t&&!bl.call(n,a)||o&&("length"==a||u&&("offset"==a||"parent"==a)||i&&("buffer"==a||"byteLength"==a||"byteOffset"==a)||Ci(a,c))||f.push(a);return f}function Ir(n){var t=n.length;return t?n[tu(0,t-1)]:X}function Rr(n,t){return Xi(Tu(n),Mr(t,0,n.length))}function zr(n){return Xi(Tu(n))}function Er(n,t,r){(r===X||Gf(n[t],r))&&(r!==X||t in n)||Br(n,t,r)}function Sr(n,t,r){var e=n[t];
|
||||||
|
bl.call(n,t)&&Gf(e,r)&&(r!==X||t in n)||Br(n,t,r)}function Wr(n,t){for(var r=n.length;r--;)if(Gf(n[r][0],t))return r;return-1}function Lr(n,t,r,e){return ys(n,function(n,u,i){t(e,n,r(n),i)}),e}function Cr(n,t){return n&&$u(t,Pc(t),n)}function Ur(n,t){return n&&$u(t,qc(t),n)}function Br(n,t,r){"__proto__"==t&&Tl?Tl(n,t,{configurable:!0,enumerable:!0,value:r,writable:!0}):n[t]=r}function Tr(n,t){for(var r=-1,e=t.length,u=il(e),i=null==n;++r<e;)u[r]=i?X:Mc(n,t[r]);return u}function Mr(n,t,r){return n===n&&(r!==X&&(n=n<=r?n:r),
|
||||||
|
t!==X&&(n=n>=t?n:t)),n}function Fr(n,t,e,u,i,o){var f,c=t&an,a=t&ln,l=t&sn;if(e&&(f=i?e(n,u,i,o):e(n)),f!==X)return f;if(!fc(n))return n;var s=bh(n);if(s){if(f=zi(n),!c)return Tu(n,f)}else{var h=zs(n),p=h==Kn||h==Vn;if(mh(n))return Iu(n,c);if(h==Yn||h==Dn||p&&!i){if(f=a||p?{}:Ei(n),!c)return a?Mu(n,Ur(f,n)):Du(n,Cr(f,n))}else{if(!Vr[h])return i?n:{};f=Si(n,h,c)}}o||(o=new wr);var _=o.get(n);if(_)return _;o.set(n,f),kh(n)?n.forEach(function(r){f.add(Fr(r,t,e,r,n,o))}):jh(n)&&n.forEach(function(r,u){
|
||||||
|
f.set(u,Fr(r,t,e,u,n,o))});var v=l?a?di:yi:a?qc:Pc,g=s?X:v(n);return r(g||n,function(r,u){g&&(u=r,r=n[u]),Sr(f,u,Fr(r,t,e,u,n,o))}),f}function Nr(n){var t=Pc(n);return function(r){return Pr(r,n,t)}}function Pr(n,t,r){var e=r.length;if(null==n)return!e;for(n=ll(n);e--;){var u=r[e],i=t[u],o=n[u];if(o===X&&!(u in n)||!i(o))return!1}return!0}function Gr(n,t,r){if("function"!=typeof n)throw new pl(en);return Ws(function(){n.apply(X,r)},t)}function Hr(n,t,r,e){var u=-1,i=o,a=!0,l=n.length,s=[],h=t.length;
|
||||||
|
if(!l)return s;r&&(t=c(t,z(r))),e?(i=f,a=!1):t.length>=tn&&(i=S,a=!1,t=new yr(t));n:for(;++u<l;){var p=n[u],_=null==r?p:r(p);if(p=e||0!==p?p:0,a&&_===_){for(var v=h;v--;)if(t[v]===_)continue n;s.push(p)}else i(t,_,e)||s.push(p)}return s}function Jr(n,t){var r=!0;return ys(n,function(n,e,u){return r=!!t(n,e,u)}),r}function Yr(n,t,r){for(var e=-1,u=n.length;++e<u;){var i=n[e],o=t(i);if(null!=o&&(f===X?o===o&&!bc(o):r(o,f)))var f=o,c=i}return c}function ne(n,t,r,e){var u=n.length;for(r=kc(r),r<0&&(r=-r>u?0:u+r),
|
||||||
|
e=e===X||e>u?u:kc(e),e<0&&(e+=u),e=r>e?0:Oc(e);r<e;)n[r++]=t;return n}function te(n,t){var r=[];return ys(n,function(n,e,u){t(n,e,u)&&r.push(n)}),r}function ee(n,t,r,e,u){var i=-1,o=n.length;for(r||(r=Li),u||(u=[]);++i<o;){var f=n[i];t>0&&r(f)?t>1?ee(f,t-1,r,e,u):a(u,f):e||(u[u.length]=f)}return u}function ue(n,t){return n&&bs(n,t,Pc)}function oe(n,t){return n&&ws(n,t,Pc)}function fe(n,t){return i(t,function(t){return uc(n[t])})}function _e(n,t){t=ku(t,n);for(var r=0,e=t.length;null!=n&&r<e;)n=n[no(t[r++])];
|
||||||
|
return r&&r==e?n:X}function de(n,t,r){var e=t(n);return bh(n)?e:a(e,r(n))}function we(n){return null==n?n===X?ut:Jn:Bl&&Bl in ll(n)?ki(n):Ki(n)}function me(n,t){return n>t}function xe(n,t){return null!=n&&bl.call(n,t)}function je(n,t){return null!=n&&t in ll(n)}function Ae(n,t,r){return n>=Hl(t,r)&&n<Gl(t,r)}function ke(n,t,r){for(var e=r?f:o,u=n[0].length,i=n.length,a=i,l=il(i),s=1/0,h=[];a--;){var p=n[a];a&&t&&(p=c(p,z(t))),s=Hl(p.length,s),l[a]=!r&&(t||u>=120&&p.length>=120)?new yr(a&&p):X}p=n[0];
|
||||||
|
var _=-1,v=l[0];n:for(;++_<u&&h.length<s;){var g=p[_],y=t?t(g):g;if(g=r||0!==g?g:0,!(v?S(v,y):e(h,y,r))){for(a=i;--a;){var d=l[a];if(!(d?S(d,y):e(n[a],y,r)))continue n}v&&v.push(y),h.push(g)}}return h}function Oe(n,t,r,e){return ue(n,function(n,u,i){t(e,r(n),u,i)}),e}function Ie(t,r,e){r=ku(r,t),t=Gi(t,r);var u=null==t?t:t[no(jo(r))];return null==u?X:n(u,t,e)}function Re(n){return cc(n)&&we(n)==Dn}function ze(n){return cc(n)&&we(n)==ft}function Ee(n){return cc(n)&&we(n)==Pn}function Se(n,t,r,e,u){
|
||||||
|
return n===t||(null==n||null==t||!cc(n)&&!cc(t)?n!==n&&t!==t:We(n,t,r,e,Se,u))}function We(n,t,r,e,u,i){var o=bh(n),f=bh(t),c=o?Mn:zs(n),a=f?Mn:zs(t);c=c==Dn?Yn:c,a=a==Dn?Yn:a;var l=c==Yn,s=a==Yn,h=c==a;if(h&&mh(n)){if(!mh(t))return!1;o=!0,l=!1}if(h&&!l)return i||(i=new wr),o||Oh(n)?pi(n,t,r,e,u,i):_i(n,t,c,r,e,u,i);if(!(r&hn)){var p=l&&bl.call(n,"__wrapped__"),_=s&&bl.call(t,"__wrapped__");if(p||_){var v=p?n.value():n,g=_?t.value():t;return i||(i=new wr),u(v,g,r,e,i)}}return!!h&&(i||(i=new wr),vi(n,t,r,e,u,i));
|
||||||
|
}function Le(n){return cc(n)&&zs(n)==Gn}function Ce(n,t,r,e){var u=r.length,i=u,o=!e;if(null==n)return!i;for(n=ll(n);u--;){var f=r[u];if(o&&f[2]?f[1]!==n[f[0]]:!(f[0]in n))return!1}for(;++u<i;){f=r[u];var c=f[0],a=n[c],l=f[1];if(o&&f[2]){if(a===X&&!(c in n))return!1}else{var s=new wr;if(e)var h=e(a,l,c,n,t,s);if(!(h===X?Se(l,a,hn|pn,e,s):h))return!1}}return!0}function Ue(n){return!(!fc(n)||Di(n))&&(uc(n)?kl:Zt).test(to(n))}function Be(n){return cc(n)&&we(n)==nt}function Te(n){return cc(n)&&zs(n)==tt;
|
||||||
|
}function $e(n){return cc(n)&&oc(n.length)&&!!Kr[we(n)]}function De(n){return"function"==typeof n?n:null==n?La:"object"==typeof n?bh(n)?Ze(n[0],n[1]):qe(n):Fa(n)}function Me(n){if(!Mi(n))return Vl(n);var t=[];for(var r in ll(n))bl.call(n,r)&&"constructor"!=r&&t.push(r);return t}function Fe(n){if(!fc(n))return Zi(n);var t=Mi(n),r=[];for(var e in n)("constructor"!=e||!t&&bl.call(n,e))&&r.push(e);return r}function Ne(n,t){return n<t}function Pe(n,t){var r=-1,e=Hf(n)?il(n.length):[];return ys(n,function(n,u,i){
|
||||||
|
e[++r]=t(n,u,i)}),e}function qe(n){var t=ji(n);return 1==t.length&&t[0][2]?Ni(t[0][0],t[0][1]):function(r){return r===n||Ce(r,n,t)}}function Ze(n,t){return Bi(n)&&Fi(t)?Ni(no(n),t):function(r){var e=Mc(r,n);return e===X&&e===t?Nc(r,n):Se(t,e,hn|pn)}}function Ke(n,t,r,e,u){n!==t&&bs(t,function(i,o){if(u||(u=new wr),fc(i))Ve(n,t,o,r,Ke,e,u);else{var f=e?e(Ji(n,o),i,o+"",n,t,u):X;f===X&&(f=i),Er(n,o,f)}},qc)}function Ve(n,t,r,e,u,i,o){var f=Ji(n,r),c=Ji(t,r),a=o.get(c);if(a)return Er(n,r,a),X;var l=i?i(f,c,r+"",n,t,o):X,s=l===X;
|
||||||
|
if(s){var h=bh(c),p=!h&&mh(c),_=!h&&!p&&Oh(c);l=c,h||p||_?bh(f)?l=f:Jf(f)?l=Tu(f):p?(s=!1,l=Iu(c,!0)):_?(s=!1,l=Wu(c,!0)):l=[]:gc(c)||dh(c)?(l=f,dh(f)?l=Rc(f):fc(f)&&!uc(f)||(l=Ei(c))):s=!1}s&&(o.set(c,l),u(l,c,e,i,o),o.delete(c)),Er(n,r,l)}function Ge(n,t){var r=n.length;if(r)return t+=t<0?r:0,Ci(t,r)?n[t]:X}function He(n,t,r){t=t.length?c(t,function(n){return bh(n)?function(t){return _e(t,1===n.length?n[0]:n)}:n}):[La];var e=-1;return t=c(t,z(mi())),A(Pe(n,function(n,r,u){return{criteria:c(t,function(t){
|
||||||
|
return t(n)}),index:++e,value:n}}),function(n,t){return Cu(n,t,r)})}function Je(n,t){return Ye(n,t,function(t,r){return Nc(n,r)})}function Ye(n,t,r){for(var e=-1,u=t.length,i={};++e<u;){var o=t[e],f=_e(n,o);r(f,o)&&fu(i,ku(o,n),f)}return i}function Qe(n){return function(t){return _e(t,n)}}function Xe(n,t,r,e){var u=e?d:y,i=-1,o=t.length,f=n;for(n===t&&(t=Tu(t)),r&&(f=c(n,z(r)));++i<o;)for(var a=0,l=t[i],s=r?r(l):l;(a=u(f,s,a,e))>-1;)f!==n&&Ll.call(f,a,1),Ll.call(n,a,1);return n}function nu(n,t){for(var r=n?t.length:0,e=r-1;r--;){
|
||||||
|
var u=t[r];if(r==e||u!==i){var i=u;Ci(u)?Ll.call(n,u,1):yu(n,u)}}return n}function tu(n,t){return n+Nl(Ql()*(t-n+1))}function ru(n,t,r,e){for(var u=-1,i=Gl(Fl((t-n)/(r||1)),0),o=il(i);i--;)o[e?i:++u]=n,n+=r;return o}function eu(n,t){var r="";if(!n||t<1||t>Wn)return r;do t%2&&(r+=n),t=Nl(t/2),t&&(n+=n);while(t);return r}function uu(n,t){return Ls(Vi(n,t,La),n+"")}function iu(n){return Ir(ra(n))}function ou(n,t){var r=ra(n);return Xi(r,Mr(t,0,r.length))}function fu(n,t,r,e){if(!fc(n))return n;t=ku(t,n);
|
||||||
|
for(var u=-1,i=t.length,o=i-1,f=n;null!=f&&++u<i;){var c=no(t[u]),a=r;if("__proto__"===c||"constructor"===c||"prototype"===c)return n;if(u!=o){var l=f[c];a=e?e(l,c,f):X,a===X&&(a=fc(l)?l:Ci(t[u+1])?[]:{})}Sr(f,c,a),f=f[c]}return n}function cu(n){return Xi(ra(n))}function au(n,t,r){var e=-1,u=n.length;t<0&&(t=-t>u?0:u+t),r=r>u?u:r,r<0&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0;for(var i=il(u);++e<u;)i[e]=n[e+t];return i}function lu(n,t){var r;return ys(n,function(n,e,u){return r=t(n,e,u),!r}),!!r}function su(n,t,r){
|
||||||
|
var e=0,u=null==n?e:n.length;if("number"==typeof t&&t===t&&u<=Tn){for(;e<u;){var i=e+u>>>1,o=n[i];null!==o&&!bc(o)&&(r?o<=t:o<t)?e=i+1:u=i}return u}return hu(n,t,La,r)}function hu(n,t,r,e){var u=0,i=null==n?0:n.length;if(0===i)return 0;t=r(t);for(var o=t!==t,f=null===t,c=bc(t),a=t===X;u<i;){var l=Nl((u+i)/2),s=r(n[l]),h=s!==X,p=null===s,_=s===s,v=bc(s);if(o)var g=e||_;else g=a?_&&(e||h):f?_&&h&&(e||!p):c?_&&h&&!p&&(e||!v):!p&&!v&&(e?s<=t:s<t);g?u=l+1:i=l}return Hl(i,Bn)}function pu(n,t){for(var r=-1,e=n.length,u=0,i=[];++r<e;){
|
||||||
|
var o=n[r],f=t?t(o):o;if(!r||!Gf(f,c)){var c=f;i[u++]=0===o?0:o}}return i}function _u(n){return"number"==typeof n?n:bc(n)?Cn:+n}function vu(n){if("string"==typeof n)return n;if(bh(n))return c(n,vu)+"";if(bc(n))return vs?vs.call(n):"";var t=n+"";return"0"==t&&1/n==-Sn?"-0":t}function gu(n,t,r){var e=-1,u=o,i=n.length,c=!0,a=[],l=a;if(r)c=!1,u=f;else if(i>=tn){var s=t?null:ks(n);if(s)return P(s);c=!1,u=S,l=new yr}else l=t?[]:a;n:for(;++e<i;){var h=n[e],p=t?t(h):h;if(h=r||0!==h?h:0,c&&p===p){for(var _=l.length;_--;)if(l[_]===p)continue n;
|
||||||
|
t&&l.push(p),a.push(h)}else u(l,p,r)||(l!==a&&l.push(p),a.push(h))}return a}function yu(n,t){return t=ku(t,n),n=Gi(n,t),null==n||delete n[no(jo(t))]}function du(n,t,r,e){return fu(n,t,r(_e(n,t)),e)}function bu(n,t,r,e){for(var u=n.length,i=e?u:-1;(e?i--:++i<u)&&t(n[i],i,n););return r?au(n,e?0:i,e?i+1:u):au(n,e?i+1:0,e?u:i)}function wu(n,t){var r=n;return r instanceof Ct&&(r=r.value()),l(t,function(n,t){return t.func.apply(t.thisArg,a([n],t.args))},r)}function mu(n,t,r){var e=n.length;if(e<2)return e?gu(n[0]):[];
|
||||||
|
for(var u=-1,i=il(e);++u<e;)for(var o=n[u],f=-1;++f<e;)f!=u&&(i[u]=Hr(i[u]||o,n[f],t,r));return gu(ee(i,1),t,r)}function xu(n,t,r){for(var e=-1,u=n.length,i=t.length,o={};++e<u;){r(o,n[e],e<i?t[e]:X)}return o}function ju(n){return Jf(n)?n:[]}function Au(n){return"function"==typeof n?n:La}function ku(n,t){return bh(n)?n:Bi(n,t)?[n]:Cs(Ec(n))}function Ou(n,t,r){var e=n.length;return r=r===X?e:r,!t&&r>=e?n:au(n,t,r)}function Iu(n,t){if(t)return n.slice();var r=n.length,e=zl?zl(r):new n.constructor(r);
|
||||||
|
return n.copy(e),e}function Ru(n){var t=new n.constructor(n.byteLength);return new Rl(t).set(new Rl(n)),t}function zu(n,t){return new n.constructor(t?Ru(n.buffer):n.buffer,n.byteOffset,n.byteLength)}function Eu(n){var t=new n.constructor(n.source,Nt.exec(n));return t.lastIndex=n.lastIndex,t}function Su(n){return _s?ll(_s.call(n)):{}}function Wu(n,t){return new n.constructor(t?Ru(n.buffer):n.buffer,n.byteOffset,n.length)}function Lu(n,t){if(n!==t){var r=n!==X,e=null===n,u=n===n,i=bc(n),o=t!==X,f=null===t,c=t===t,a=bc(t);
|
||||||
|
if(!f&&!a&&!i&&n>t||i&&o&&c&&!f&&!a||e&&o&&c||!r&&c||!u)return 1;if(!e&&!i&&!a&&n<t||a&&r&&u&&!e&&!i||f&&r&&u||!o&&u||!c)return-1}return 0}function Cu(n,t,r){for(var e=-1,u=n.criteria,i=t.criteria,o=u.length,f=r.length;++e<o;){var c=Lu(u[e],i[e]);if(c){if(e>=f)return c;return c*("desc"==r[e]?-1:1)}}return n.index-t.index}function Uu(n,t,r,e){for(var u=-1,i=n.length,o=r.length,f=-1,c=t.length,a=Gl(i-o,0),l=il(c+a),s=!e;++f<c;)l[f]=t[f];for(;++u<o;)(s||u<i)&&(l[r[u]]=n[u]);for(;a--;)l[f++]=n[u++];return l;
|
||||||
|
}function Bu(n,t,r,e){for(var u=-1,i=n.length,o=-1,f=r.length,c=-1,a=t.length,l=Gl(i-f,0),s=il(l+a),h=!e;++u<l;)s[u]=n[u];for(var p=u;++c<a;)s[p+c]=t[c];for(;++o<f;)(h||u<i)&&(s[p+r[o]]=n[u++]);return s}function Tu(n,t){var r=-1,e=n.length;for(t||(t=il(e));++r<e;)t[r]=n[r];return t}function $u(n,t,r,e){var u=!r;r||(r={});for(var i=-1,o=t.length;++i<o;){var f=t[i],c=e?e(r[f],n[f],f,r,n):X;c===X&&(c=n[f]),u?Br(r,f,c):Sr(r,f,c)}return r}function Du(n,t){return $u(n,Is(n),t)}function Mu(n,t){return $u(n,Rs(n),t);
|
||||||
|
}function Fu(n,r){return function(e,u){var i=bh(e)?t:Lr,o=r?r():{};return i(e,n,mi(u,2),o)}}function Nu(n){return uu(function(t,r){var e=-1,u=r.length,i=u>1?r[u-1]:X,o=u>2?r[2]:X;for(i=n.length>3&&"function"==typeof i?(u--,i):X,o&&Ui(r[0],r[1],o)&&(i=u<3?X:i,u=1),t=ll(t);++e<u;){var f=r[e];f&&n(t,f,e,i)}return t})}function Pu(n,t){return function(r,e){if(null==r)return r;if(!Hf(r))return n(r,e);for(var u=r.length,i=t?u:-1,o=ll(r);(t?i--:++i<u)&&e(o[i],i,o)!==!1;);return r}}function qu(n){return function(t,r,e){
|
||||||
|
for(var u=-1,i=ll(t),o=e(t),f=o.length;f--;){var c=o[n?f:++u];if(r(i[c],c,i)===!1)break}return t}}function Zu(n,t,r){function e(){return(this&&this!==re&&this instanceof e?i:n).apply(u?r:this,arguments)}var u=t&_n,i=Gu(n);return e}function Ku(n){return function(t){t=Ec(t);var r=T(t)?G(t):X,e=r?r[0]:t.charAt(0),u=r?Ou(r,1).join(""):t.slice(1);return e[n]()+u}}function Vu(n){return function(t){return l(Ra(ca(t).replace($r,"")),n,"")}}function Gu(n){return function(){var t=arguments;switch(t.length){
|
||||||
|
case 0:return new n;case 1:return new n(t[0]);case 2:return new n(t[0],t[1]);case 3:return new n(t[0],t[1],t[2]);case 4:return new n(t[0],t[1],t[2],t[3]);case 5:return new n(t[0],t[1],t[2],t[3],t[4]);case 6:return new n(t[0],t[1],t[2],t[3],t[4],t[5]);case 7:return new n(t[0],t[1],t[2],t[3],t[4],t[5],t[6])}var r=gs(n.prototype),e=n.apply(r,t);return fc(e)?e:r}}function Hu(t,r,e){function u(){for(var o=arguments.length,f=il(o),c=o,a=wi(u);c--;)f[c]=arguments[c];var l=o<3&&f[0]!==a&&f[o-1]!==a?[]:N(f,a);
|
||||||
|
return o-=l.length,o<e?oi(t,r,Qu,u.placeholder,X,f,l,X,X,e-o):n(this&&this!==re&&this instanceof u?i:t,this,f)}var i=Gu(t);return u}function Ju(n){return function(t,r,e){var u=ll(t);if(!Hf(t)){var i=mi(r,3);t=Pc(t),r=function(n){return i(u[n],n,u)}}var o=n(t,r,e);return o>-1?u[i?t[o]:o]:X}}function Yu(n){return gi(function(t){var r=t.length,e=r,u=Y.prototype.thru;for(n&&t.reverse();e--;){var i=t[e];if("function"!=typeof i)throw new pl(en);if(u&&!o&&"wrapper"==bi(i))var o=new Y([],!0)}for(e=o?e:r;++e<r;){
|
||||||
|
i=t[e];var f=bi(i),c="wrapper"==f?Os(i):X;o=c&&$i(c[0])&&c[1]==(mn|yn|bn|xn)&&!c[4].length&&1==c[9]?o[bi(c[0])].apply(o,c[3]):1==i.length&&$i(i)?o[f]():o.thru(i)}return function(){var n=arguments,e=n[0];if(o&&1==n.length&&bh(e))return o.plant(e).value();for(var u=0,i=r?t[u].apply(this,n):e;++u<r;)i=t[u].call(this,i);return i}})}function Qu(n,t,r,e,u,i,o,f,c,a){function l(){for(var y=arguments.length,d=il(y),b=y;b--;)d[b]=arguments[b];if(_)var w=wi(l),m=C(d,w);if(e&&(d=Uu(d,e,u,_)),i&&(d=Bu(d,i,o,_)),
|
||||||
|
y-=m,_&&y<a){return oi(n,t,Qu,l.placeholder,r,d,N(d,w),f,c,a-y)}var x=h?r:this,j=p?x[n]:n;return y=d.length,f?d=Hi(d,f):v&&y>1&&d.reverse(),s&&c<y&&(d.length=c),this&&this!==re&&this instanceof l&&(j=g||Gu(j)),j.apply(x,d)}var s=t&mn,h=t&_n,p=t&vn,_=t&(yn|dn),v=t&jn,g=p?X:Gu(n);return l}function Xu(n,t){return function(r,e){return Oe(r,n,t(e),{})}}function ni(n,t){return function(r,e){var u;if(r===X&&e===X)return t;if(r!==X&&(u=r),e!==X){if(u===X)return e;"string"==typeof r||"string"==typeof e?(r=vu(r),
|
||||||
|
e=vu(e)):(r=_u(r),e=_u(e)),u=n(r,e)}return u}}function ti(t){return gi(function(r){return r=c(r,z(mi())),uu(function(e){var u=this;return t(r,function(t){return n(t,u,e)})})})}function ri(n,t){t=t===X?" ":vu(t);var r=t.length;if(r<2)return r?eu(t,n):t;var e=eu(t,Fl(n/V(t)));return T(t)?Ou(G(e),0,n).join(""):e.slice(0,n)}function ei(t,r,e,u){function i(){for(var r=-1,c=arguments.length,a=-1,l=u.length,s=il(l+c),h=this&&this!==re&&this instanceof i?f:t;++a<l;)s[a]=u[a];for(;c--;)s[a++]=arguments[++r];
|
||||||
|
return n(h,o?e:this,s)}var o=r&_n,f=Gu(t);return i}function ui(n){return function(t,r,e){return e&&"number"!=typeof e&&Ui(t,r,e)&&(r=e=X),t=Ac(t),r===X?(r=t,t=0):r=Ac(r),e=e===X?t<r?1:-1:Ac(e),ru(t,r,e,n)}}function ii(n){return function(t,r){return"string"==typeof t&&"string"==typeof r||(t=Ic(t),r=Ic(r)),n(t,r)}}function oi(n,t,r,e,u,i,o,f,c,a){var l=t&yn,s=l?o:X,h=l?X:o,p=l?i:X,_=l?X:i;t|=l?bn:wn,t&=~(l?wn:bn),t&gn||(t&=~(_n|vn));var v=[n,t,u,p,s,_,h,f,c,a],g=r.apply(X,v);return $i(n)&&Ss(g,v),g.placeholder=e,
|
||||||
|
Yi(g,n,t)}function fi(n){var t=al[n];return function(n,r){if(n=Ic(n),r=null==r?0:Hl(kc(r),292),r&&Zl(n)){var e=(Ec(n)+"e").split("e");return e=(Ec(t(e[0]+"e"+(+e[1]+r)))+"e").split("e"),+(e[0]+"e"+(+e[1]-r))}return t(n)}}function ci(n){return function(t){var r=zs(t);return r==Gn?M(t):r==tt?q(t):I(t,n(t))}}function ai(n,t,r,e,u,i,o,f){var c=t&vn;if(!c&&"function"!=typeof n)throw new pl(en);var a=e?e.length:0;if(a||(t&=~(bn|wn),e=u=X),o=o===X?o:Gl(kc(o),0),f=f===X?f:kc(f),a-=u?u.length:0,t&wn){var l=e,s=u;
|
||||||
|
e=u=X}var h=c?X:Os(n),p=[n,t,r,e,u,l,s,i,o,f];if(h&&qi(p,h),n=p[0],t=p[1],r=p[2],e=p[3],u=p[4],f=p[9]=p[9]===X?c?0:n.length:Gl(p[9]-a,0),!f&&t&(yn|dn)&&(t&=~(yn|dn)),t&&t!=_n)_=t==yn||t==dn?Hu(n,t,f):t!=bn&&t!=(_n|bn)||u.length?Qu.apply(X,p):ei(n,t,r,e);else var _=Zu(n,t,r);return Yi((h?ms:Ss)(_,p),n,t)}function li(n,t,r,e){return n===X||Gf(n,gl[r])&&!bl.call(e,r)?t:n}function si(n,t,r,e,u,i){return fc(n)&&fc(t)&&(i.set(t,n),Ke(n,t,X,si,i),i.delete(t)),n}function hi(n){return gc(n)?X:n}function pi(n,t,r,e,u,i){
|
||||||
|
var o=r&hn,f=n.length,c=t.length;if(f!=c&&!(o&&c>f))return!1;var a=i.get(n),l=i.get(t);if(a&&l)return a==t&&l==n;var s=-1,p=!0,_=r&pn?new yr:X;for(i.set(n,t),i.set(t,n);++s<f;){var v=n[s],g=t[s];if(e)var y=o?e(g,v,s,t,n,i):e(v,g,s,n,t,i);if(y!==X){if(y)continue;p=!1;break}if(_){if(!h(t,function(n,t){if(!S(_,t)&&(v===n||u(v,n,r,e,i)))return _.push(t)})){p=!1;break}}else if(v!==g&&!u(v,g,r,e,i)){p=!1;break}}return i.delete(n),i.delete(t),p}function _i(n,t,r,e,u,i,o){switch(r){case ct:if(n.byteLength!=t.byteLength||n.byteOffset!=t.byteOffset)return!1;
|
||||||
|
n=n.buffer,t=t.buffer;case ft:return!(n.byteLength!=t.byteLength||!i(new Rl(n),new Rl(t)));case Nn:case Pn:case Hn:return Gf(+n,+t);case Zn:return n.name==t.name&&n.message==t.message;case nt:case rt:return n==t+"";case Gn:var f=M;case tt:var c=e&hn;if(f||(f=P),n.size!=t.size&&!c)return!1;var a=o.get(n);if(a)return a==t;e|=pn,o.set(n,t);var l=pi(f(n),f(t),e,u,i,o);return o.delete(n),l;case et:if(_s)return _s.call(n)==_s.call(t)}return!1}function vi(n,t,r,e,u,i){var o=r&hn,f=yi(n),c=f.length;if(c!=yi(t).length&&!o)return!1;
|
||||||
|
for(var a=c;a--;){var l=f[a];if(!(o?l in t:bl.call(t,l)))return!1}var s=i.get(n),h=i.get(t);if(s&&h)return s==t&&h==n;var p=!0;i.set(n,t),i.set(t,n);for(var _=o;++a<c;){l=f[a];var v=n[l],g=t[l];if(e)var y=o?e(g,v,l,t,n,i):e(v,g,l,n,t,i);if(!(y===X?v===g||u(v,g,r,e,i):y)){p=!1;break}_||(_="constructor"==l)}if(p&&!_){var d=n.constructor,b=t.constructor;d!=b&&"constructor"in n&&"constructor"in t&&!("function"==typeof d&&d instanceof d&&"function"==typeof b&&b instanceof b)&&(p=!1)}return i.delete(n),
|
||||||
|
i.delete(t),p}function gi(n){return Ls(Vi(n,X,_o),n+"")}function yi(n){return de(n,Pc,Is)}function di(n){return de(n,qc,Rs)}function bi(n){for(var t=n.name+"",r=fs[t],e=bl.call(fs,t)?r.length:0;e--;){var u=r[e],i=u.func;if(null==i||i==n)return u.name}return t}function wi(n){return(bl.call(Z,"placeholder")?Z:n).placeholder}function mi(){var n=Z.iteratee||Ca;return n=n===Ca?De:n,arguments.length?n(arguments[0],arguments[1]):n}function xi(n,t){var r=n.__data__;return Ti(t)?r["string"==typeof t?"string":"hash"]:r.map;
|
||||||
|
}function ji(n){for(var t=Pc(n),r=t.length;r--;){var e=t[r],u=n[e];t[r]=[e,u,Fi(u)]}return t}function Ai(n,t){var r=B(n,t);return Ue(r)?r:X}function ki(n){var t=bl.call(n,Bl),r=n[Bl];try{n[Bl]=X;var e=!0}catch(n){}var u=xl.call(n);return e&&(t?n[Bl]=r:delete n[Bl]),u}function Oi(n,t,r){for(var e=-1,u=r.length;++e<u;){var i=r[e],o=i.size;switch(i.type){case"drop":n+=o;break;case"dropRight":t-=o;break;case"take":t=Hl(t,n+o);break;case"takeRight":n=Gl(n,t-o)}}return{start:n,end:t}}function Ii(n){var t=n.match(Bt);
|
||||||
|
return t?t[1].split(Tt):[]}function Ri(n,t,r){t=ku(t,n);for(var e=-1,u=t.length,i=!1;++e<u;){var o=no(t[e]);if(!(i=null!=n&&r(n,o)))break;n=n[o]}return i||++e!=u?i:(u=null==n?0:n.length,!!u&&oc(u)&&Ci(o,u)&&(bh(n)||dh(n)))}function zi(n){var t=n.length,r=new n.constructor(t);return t&&"string"==typeof n[0]&&bl.call(n,"index")&&(r.index=n.index,r.input=n.input),r}function Ei(n){return"function"!=typeof n.constructor||Mi(n)?{}:gs(El(n))}function Si(n,t,r){var e=n.constructor;switch(t){case ft:return Ru(n);
|
||||||
|
case Nn:case Pn:return new e(+n);case ct:return zu(n,r);case at:case lt:case st:case ht:case pt:case _t:case vt:case gt:case yt:return Wu(n,r);case Gn:return new e;case Hn:case rt:return new e(n);case nt:return Eu(n);case tt:return new e;case et:return Su(n)}}function Wi(n,t){var r=t.length;if(!r)return n;var e=r-1;return t[e]=(r>1?"& ":"")+t[e],t=t.join(r>2?", ":" "),n.replace(Ut,"{\n/* [wrapped with "+t+"] */\n")}function Li(n){return bh(n)||dh(n)||!!(Cl&&n&&n[Cl])}function Ci(n,t){var r=typeof n;
|
||||||
|
return t=null==t?Wn:t,!!t&&("number"==r||"symbol"!=r&&Vt.test(n))&&n>-1&&n%1==0&&n<t}function Ui(n,t,r){if(!fc(r))return!1;var e=typeof t;return!!("number"==e?Hf(r)&&Ci(t,r.length):"string"==e&&t in r)&&Gf(r[t],n)}function Bi(n,t){if(bh(n))return!1;var r=typeof n;return!("number"!=r&&"symbol"!=r&&"boolean"!=r&&null!=n&&!bc(n))||(zt.test(n)||!Rt.test(n)||null!=t&&n in ll(t))}function Ti(n){var t=typeof n;return"string"==t||"number"==t||"symbol"==t||"boolean"==t?"__proto__"!==n:null===n}function $i(n){
|
||||||
|
var t=bi(n),r=Z[t];if("function"!=typeof r||!(t in Ct.prototype))return!1;if(n===r)return!0;var e=Os(r);return!!e&&n===e[0]}function Di(n){return!!ml&&ml in n}function Mi(n){var t=n&&n.constructor;return n===("function"==typeof t&&t.prototype||gl)}function Fi(n){return n===n&&!fc(n)}function Ni(n,t){return function(r){return null!=r&&(r[n]===t&&(t!==X||n in ll(r)))}}function Pi(n){var t=Cf(n,function(n){return r.size===fn&&r.clear(),n}),r=t.cache;return t}function qi(n,t){var r=n[1],e=t[1],u=r|e,i=u<(_n|vn|mn),o=e==mn&&r==yn||e==mn&&r==xn&&n[7].length<=t[8]||e==(mn|xn)&&t[7].length<=t[8]&&r==yn;
|
||||||
|
if(!i&&!o)return n;e&_n&&(n[2]=t[2],u|=r&_n?0:gn);var f=t[3];if(f){var c=n[3];n[3]=c?Uu(c,f,t[4]):f,n[4]=c?N(n[3],cn):t[4]}return f=t[5],f&&(c=n[5],n[5]=c?Bu(c,f,t[6]):f,n[6]=c?N(n[5],cn):t[6]),f=t[7],f&&(n[7]=f),e&mn&&(n[8]=null==n[8]?t[8]:Hl(n[8],t[8])),null==n[9]&&(n[9]=t[9]),n[0]=t[0],n[1]=u,n}function Zi(n){var t=[];if(null!=n)for(var r in ll(n))t.push(r);return t}function Ki(n){return xl.call(n)}function Vi(t,r,e){return r=Gl(r===X?t.length-1:r,0),function(){for(var u=arguments,i=-1,o=Gl(u.length-r,0),f=il(o);++i<o;)f[i]=u[r+i];
|
||||||
|
i=-1;for(var c=il(r+1);++i<r;)c[i]=u[i];return c[r]=e(f),n(t,this,c)}}function Gi(n,t){return t.length<2?n:_e(n,au(t,0,-1))}function Hi(n,t){for(var r=n.length,e=Hl(t.length,r),u=Tu(n);e--;){var i=t[e];n[e]=Ci(i,r)?u[i]:X}return n}function Ji(n,t){if(("constructor"!==t||"function"!=typeof n[t])&&"__proto__"!=t)return n[t]}function Yi(n,t,r){var e=t+"";return Ls(n,Wi(e,ro(Ii(e),r)))}function Qi(n){var t=0,r=0;return function(){var e=Jl(),u=In-(e-r);if(r=e,u>0){if(++t>=On)return arguments[0]}else t=0;
|
||||||
|
return n.apply(X,arguments)}}function Xi(n,t){var r=-1,e=n.length,u=e-1;for(t=t===X?e:t;++r<t;){var i=tu(r,u),o=n[i];n[i]=n[r],n[r]=o}return n.length=t,n}function no(n){if("string"==typeof n||bc(n))return n;var t=n+"";return"0"==t&&1/n==-Sn?"-0":t}function to(n){if(null!=n){try{return dl.call(n)}catch(n){}try{return n+""}catch(n){}}return""}function ro(n,t){return r($n,function(r){var e="_."+r[0];t&r[1]&&!o(n,e)&&n.push(e)}),n.sort()}function eo(n){if(n instanceof Ct)return n.clone();var t=new Y(n.__wrapped__,n.__chain__);
|
||||||
|
return t.__actions__=Tu(n.__actions__),t.__index__=n.__index__,t.__values__=n.__values__,t}function uo(n,t,r){t=(r?Ui(n,t,r):t===X)?1:Gl(kc(t),0);var e=null==n?0:n.length;if(!e||t<1)return[];for(var u=0,i=0,o=il(Fl(e/t));u<e;)o[i++]=au(n,u,u+=t);return o}function io(n){for(var t=-1,r=null==n?0:n.length,e=0,u=[];++t<r;){var i=n[t];i&&(u[e++]=i)}return u}function oo(){var n=arguments.length;if(!n)return[];for(var t=il(n-1),r=arguments[0],e=n;e--;)t[e-1]=arguments[e];return a(bh(r)?Tu(r):[r],ee(t,1));
|
||||||
|
}function fo(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===X?1:kc(t),au(n,t<0?0:t,e)):[]}function co(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===X?1:kc(t),t=e-t,au(n,0,t<0?0:t)):[]}function ao(n,t){return n&&n.length?bu(n,mi(t,3),!0,!0):[]}function lo(n,t){return n&&n.length?bu(n,mi(t,3),!0):[]}function so(n,t,r,e){var u=null==n?0:n.length;return u?(r&&"number"!=typeof r&&Ui(n,t,r)&&(r=0,e=u),ne(n,t,r,e)):[]}function ho(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=null==r?0:kc(r);
|
||||||
|
return u<0&&(u=Gl(e+u,0)),g(n,mi(t,3),u)}function po(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e-1;return r!==X&&(u=kc(r),u=r<0?Gl(e+u,0):Hl(u,e-1)),g(n,mi(t,3),u,!0)}function _o(n){return(null==n?0:n.length)?ee(n,1):[]}function vo(n){return(null==n?0:n.length)?ee(n,Sn):[]}function go(n,t){return(null==n?0:n.length)?(t=t===X?1:kc(t),ee(n,t)):[]}function yo(n){for(var t=-1,r=null==n?0:n.length,e={};++t<r;){var u=n[t];e[u[0]]=u[1]}return e}function bo(n){return n&&n.length?n[0]:X}function wo(n,t,r){
|
||||||
|
var e=null==n?0:n.length;if(!e)return-1;var u=null==r?0:kc(r);return u<0&&(u=Gl(e+u,0)),y(n,t,u)}function mo(n){return(null==n?0:n.length)?au(n,0,-1):[]}function xo(n,t){return null==n?"":Kl.call(n,t)}function jo(n){var t=null==n?0:n.length;return t?n[t-1]:X}function Ao(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e;return r!==X&&(u=kc(r),u=u<0?Gl(e+u,0):Hl(u,e-1)),t===t?K(n,t,u):g(n,b,u,!0)}function ko(n,t){return n&&n.length?Ge(n,kc(t)):X}function Oo(n,t){return n&&n.length&&t&&t.length?Xe(n,t):n;
|
||||||
|
}function Io(n,t,r){return n&&n.length&&t&&t.length?Xe(n,t,mi(r,2)):n}function Ro(n,t,r){return n&&n.length&&t&&t.length?Xe(n,t,X,r):n}function zo(n,t){var r=[];if(!n||!n.length)return r;var e=-1,u=[],i=n.length;for(t=mi(t,3);++e<i;){var o=n[e];t(o,e,n)&&(r.push(o),u.push(e))}return nu(n,u),r}function Eo(n){return null==n?n:Xl.call(n)}function So(n,t,r){var e=null==n?0:n.length;return e?(r&&"number"!=typeof r&&Ui(n,t,r)?(t=0,r=e):(t=null==t?0:kc(t),r=r===X?e:kc(r)),au(n,t,r)):[]}function Wo(n,t){
|
||||||
|
return su(n,t)}function Lo(n,t,r){return hu(n,t,mi(r,2))}function Co(n,t){var r=null==n?0:n.length;if(r){var e=su(n,t);if(e<r&&Gf(n[e],t))return e}return-1}function Uo(n,t){return su(n,t,!0)}function Bo(n,t,r){return hu(n,t,mi(r,2),!0)}function To(n,t){if(null==n?0:n.length){var r=su(n,t,!0)-1;if(Gf(n[r],t))return r}return-1}function $o(n){return n&&n.length?pu(n):[]}function Do(n,t){return n&&n.length?pu(n,mi(t,2)):[]}function Mo(n){var t=null==n?0:n.length;return t?au(n,1,t):[]}function Fo(n,t,r){
|
||||||
|
return n&&n.length?(t=r||t===X?1:kc(t),au(n,0,t<0?0:t)):[]}function No(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===X?1:kc(t),t=e-t,au(n,t<0?0:t,e)):[]}function Po(n,t){return n&&n.length?bu(n,mi(t,3),!1,!0):[]}function qo(n,t){return n&&n.length?bu(n,mi(t,3)):[]}function Zo(n){return n&&n.length?gu(n):[]}function Ko(n,t){return n&&n.length?gu(n,mi(t,2)):[]}function Vo(n,t){return t="function"==typeof t?t:X,n&&n.length?gu(n,X,t):[]}function Go(n){if(!n||!n.length)return[];var t=0;return n=i(n,function(n){
|
||||||
|
if(Jf(n))return t=Gl(n.length,t),!0}),O(t,function(t){return c(n,m(t))})}function Ho(t,r){if(!t||!t.length)return[];var e=Go(t);return null==r?e:c(e,function(t){return n(r,X,t)})}function Jo(n,t){return xu(n||[],t||[],Sr)}function Yo(n,t){return xu(n||[],t||[],fu)}function Qo(n){var t=Z(n);return t.__chain__=!0,t}function Xo(n,t){return t(n),n}function nf(n,t){return t(n)}function tf(){return Qo(this)}function rf(){return new Y(this.value(),this.__chain__)}function ef(){this.__values__===X&&(this.__values__=jc(this.value()));
|
||||||
|
var n=this.__index__>=this.__values__.length;return{done:n,value:n?X:this.__values__[this.__index__++]}}function uf(){return this}function of(n){for(var t,r=this;r instanceof J;){var e=eo(r);e.__index__=0,e.__values__=X,t?u.__wrapped__=e:t=e;var u=e;r=r.__wrapped__}return u.__wrapped__=n,t}function ff(){var n=this.__wrapped__;if(n instanceof Ct){var t=n;return this.__actions__.length&&(t=new Ct(this)),t=t.reverse(),t.__actions__.push({func:nf,args:[Eo],thisArg:X}),new Y(t,this.__chain__)}return this.thru(Eo);
|
||||||
|
}function cf(){return wu(this.__wrapped__,this.__actions__)}function af(n,t,r){var e=bh(n)?u:Jr;return r&&Ui(n,t,r)&&(t=X),e(n,mi(t,3))}function lf(n,t){return(bh(n)?i:te)(n,mi(t,3))}function sf(n,t){return ee(yf(n,t),1)}function hf(n,t){return ee(yf(n,t),Sn)}function pf(n,t,r){return r=r===X?1:kc(r),ee(yf(n,t),r)}function _f(n,t){return(bh(n)?r:ys)(n,mi(t,3))}function vf(n,t){return(bh(n)?e:ds)(n,mi(t,3))}function gf(n,t,r,e){n=Hf(n)?n:ra(n),r=r&&!e?kc(r):0;var u=n.length;return r<0&&(r=Gl(u+r,0)),
|
||||||
|
dc(n)?r<=u&&n.indexOf(t,r)>-1:!!u&&y(n,t,r)>-1}function yf(n,t){return(bh(n)?c:Pe)(n,mi(t,3))}function df(n,t,r,e){return null==n?[]:(bh(t)||(t=null==t?[]:[t]),r=e?X:r,bh(r)||(r=null==r?[]:[r]),He(n,t,r))}function bf(n,t,r){var e=bh(n)?l:j,u=arguments.length<3;return e(n,mi(t,4),r,u,ys)}function wf(n,t,r){var e=bh(n)?s:j,u=arguments.length<3;return e(n,mi(t,4),r,u,ds)}function mf(n,t){return(bh(n)?i:te)(n,Uf(mi(t,3)))}function xf(n){return(bh(n)?Ir:iu)(n)}function jf(n,t,r){return t=(r?Ui(n,t,r):t===X)?1:kc(t),
|
||||||
|
(bh(n)?Rr:ou)(n,t)}function Af(n){return(bh(n)?zr:cu)(n)}function kf(n){if(null==n)return 0;if(Hf(n))return dc(n)?V(n):n.length;var t=zs(n);return t==Gn||t==tt?n.size:Me(n).length}function Of(n,t,r){var e=bh(n)?h:lu;return r&&Ui(n,t,r)&&(t=X),e(n,mi(t,3))}function If(n,t){if("function"!=typeof t)throw new pl(en);return n=kc(n),function(){if(--n<1)return t.apply(this,arguments)}}function Rf(n,t,r){return t=r?X:t,t=n&&null==t?n.length:t,ai(n,mn,X,X,X,X,t)}function zf(n,t){var r;if("function"!=typeof t)throw new pl(en);
|
||||||
|
return n=kc(n),function(){return--n>0&&(r=t.apply(this,arguments)),n<=1&&(t=X),r}}function Ef(n,t,r){t=r?X:t;var e=ai(n,yn,X,X,X,X,X,t);return e.placeholder=Ef.placeholder,e}function Sf(n,t,r){t=r?X:t;var e=ai(n,dn,X,X,X,X,X,t);return e.placeholder=Sf.placeholder,e}function Wf(n,t,r){function e(t){var r=h,e=p;return h=p=X,d=t,v=n.apply(e,r)}function u(n){return d=n,g=Ws(f,t),b?e(n):v}function i(n){var r=n-y,e=n-d,u=t-r;return w?Hl(u,_-e):u}function o(n){var r=n-y,e=n-d;return y===X||r>=t||r<0||w&&e>=_;
|
||||||
|
}function f(){var n=fh();return o(n)?c(n):(g=Ws(f,i(n)),X)}function c(n){return g=X,m&&h?e(n):(h=p=X,v)}function a(){g!==X&&As(g),d=0,h=y=p=g=X}function l(){return g===X?v:c(fh())}function s(){var n=fh(),r=o(n);if(h=arguments,p=this,y=n,r){if(g===X)return u(y);if(w)return As(g),g=Ws(f,t),e(y)}return g===X&&(g=Ws(f,t)),v}var h,p,_,v,g,y,d=0,b=!1,w=!1,m=!0;if("function"!=typeof n)throw new pl(en);return t=Ic(t)||0,fc(r)&&(b=!!r.leading,w="maxWait"in r,_=w?Gl(Ic(r.maxWait)||0,t):_,m="trailing"in r?!!r.trailing:m),
|
||||||
|
s.cancel=a,s.flush=l,s}function Lf(n){return ai(n,jn)}function Cf(n,t){if("function"!=typeof n||null!=t&&"function"!=typeof t)throw new pl(en);var r=function(){var e=arguments,u=t?t.apply(this,e):e[0],i=r.cache;if(i.has(u))return i.get(u);var o=n.apply(this,e);return r.cache=i.set(u,o)||i,o};return r.cache=new(Cf.Cache||sr),r}function Uf(n){if("function"!=typeof n)throw new pl(en);return function(){var t=arguments;switch(t.length){case 0:return!n.call(this);case 1:return!n.call(this,t[0]);case 2:
|
||||||
|
return!n.call(this,t[0],t[1]);case 3:return!n.call(this,t[0],t[1],t[2])}return!n.apply(this,t)}}function Bf(n){return zf(2,n)}function Tf(n,t){if("function"!=typeof n)throw new pl(en);return t=t===X?t:kc(t),uu(n,t)}function $f(t,r){if("function"!=typeof t)throw new pl(en);return r=null==r?0:Gl(kc(r),0),uu(function(e){var u=e[r],i=Ou(e,0,r);return u&&a(i,u),n(t,this,i)})}function Df(n,t,r){var e=!0,u=!0;if("function"!=typeof n)throw new pl(en);return fc(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),
|
||||||
|
Wf(n,t,{leading:e,maxWait:t,trailing:u})}function Mf(n){return Rf(n,1)}function Ff(n,t){return ph(Au(t),n)}function Nf(){if(!arguments.length)return[];var n=arguments[0];return bh(n)?n:[n]}function Pf(n){return Fr(n,sn)}function qf(n,t){return t="function"==typeof t?t:X,Fr(n,sn,t)}function Zf(n){return Fr(n,an|sn)}function Kf(n,t){return t="function"==typeof t?t:X,Fr(n,an|sn,t)}function Vf(n,t){return null==t||Pr(n,t,Pc(t))}function Gf(n,t){return n===t||n!==n&&t!==t}function Hf(n){return null!=n&&oc(n.length)&&!uc(n);
|
||||||
|
}function Jf(n){return cc(n)&&Hf(n)}function Yf(n){return n===!0||n===!1||cc(n)&&we(n)==Nn}function Qf(n){return cc(n)&&1===n.nodeType&&!gc(n)}function Xf(n){if(null==n)return!0;if(Hf(n)&&(bh(n)||"string"==typeof n||"function"==typeof n.splice||mh(n)||Oh(n)||dh(n)))return!n.length;var t=zs(n);if(t==Gn||t==tt)return!n.size;if(Mi(n))return!Me(n).length;for(var r in n)if(bl.call(n,r))return!1;return!0}function nc(n,t){return Se(n,t)}function tc(n,t,r){r="function"==typeof r?r:X;var e=r?r(n,t):X;return e===X?Se(n,t,X,r):!!e;
|
||||||
|
}function rc(n){if(!cc(n))return!1;var t=we(n);return t==Zn||t==qn||"string"==typeof n.message&&"string"==typeof n.name&&!gc(n)}function ec(n){return"number"==typeof n&&Zl(n)}function uc(n){if(!fc(n))return!1;var t=we(n);return t==Kn||t==Vn||t==Fn||t==Xn}function ic(n){return"number"==typeof n&&n==kc(n)}function oc(n){return"number"==typeof n&&n>-1&&n%1==0&&n<=Wn}function fc(n){var t=typeof n;return null!=n&&("object"==t||"function"==t)}function cc(n){return null!=n&&"object"==typeof n}function ac(n,t){
|
||||||
|
return n===t||Ce(n,t,ji(t))}function lc(n,t,r){return r="function"==typeof r?r:X,Ce(n,t,ji(t),r)}function sc(n){return vc(n)&&n!=+n}function hc(n){if(Es(n))throw new fl(rn);return Ue(n)}function pc(n){return null===n}function _c(n){return null==n}function vc(n){return"number"==typeof n||cc(n)&&we(n)==Hn}function gc(n){if(!cc(n)||we(n)!=Yn)return!1;var t=El(n);if(null===t)return!0;var r=bl.call(t,"constructor")&&t.constructor;return"function"==typeof r&&r instanceof r&&dl.call(r)==jl}function yc(n){
|
||||||
|
return ic(n)&&n>=-Wn&&n<=Wn}function dc(n){return"string"==typeof n||!bh(n)&&cc(n)&&we(n)==rt}function bc(n){return"symbol"==typeof n||cc(n)&&we(n)==et}function wc(n){return n===X}function mc(n){return cc(n)&&zs(n)==it}function xc(n){return cc(n)&&we(n)==ot}function jc(n){if(!n)return[];if(Hf(n))return dc(n)?G(n):Tu(n);if(Ul&&n[Ul])return D(n[Ul]());var t=zs(n);return(t==Gn?M:t==tt?P:ra)(n)}function Ac(n){if(!n)return 0===n?n:0;if(n=Ic(n),n===Sn||n===-Sn){return(n<0?-1:1)*Ln}return n===n?n:0}function kc(n){
|
||||||
|
var t=Ac(n),r=t%1;return t===t?r?t-r:t:0}function Oc(n){return n?Mr(kc(n),0,Un):0}function Ic(n){if("number"==typeof n)return n;if(bc(n))return Cn;if(fc(n)){var t="function"==typeof n.valueOf?n.valueOf():n;n=fc(t)?t+"":t}if("string"!=typeof n)return 0===n?n:+n;n=R(n);var r=qt.test(n);return r||Kt.test(n)?Xr(n.slice(2),r?2:8):Pt.test(n)?Cn:+n}function Rc(n){return $u(n,qc(n))}function zc(n){return n?Mr(kc(n),-Wn,Wn):0===n?n:0}function Ec(n){return null==n?"":vu(n)}function Sc(n,t){var r=gs(n);return null==t?r:Cr(r,t);
|
||||||
|
}function Wc(n,t){return v(n,mi(t,3),ue)}function Lc(n,t){return v(n,mi(t,3),oe)}function Cc(n,t){return null==n?n:bs(n,mi(t,3),qc)}function Uc(n,t){return null==n?n:ws(n,mi(t,3),qc)}function Bc(n,t){return n&&ue(n,mi(t,3))}function Tc(n,t){return n&&oe(n,mi(t,3))}function $c(n){return null==n?[]:fe(n,Pc(n))}function Dc(n){return null==n?[]:fe(n,qc(n))}function Mc(n,t,r){var e=null==n?X:_e(n,t);return e===X?r:e}function Fc(n,t){return null!=n&&Ri(n,t,xe)}function Nc(n,t){return null!=n&&Ri(n,t,je);
|
||||||
|
}function Pc(n){return Hf(n)?Or(n):Me(n)}function qc(n){return Hf(n)?Or(n,!0):Fe(n)}function Zc(n,t){var r={};return t=mi(t,3),ue(n,function(n,e,u){Br(r,t(n,e,u),n)}),r}function Kc(n,t){var r={};return t=mi(t,3),ue(n,function(n,e,u){Br(r,e,t(n,e,u))}),r}function Vc(n,t){return Gc(n,Uf(mi(t)))}function Gc(n,t){if(null==n)return{};var r=c(di(n),function(n){return[n]});return t=mi(t),Ye(n,r,function(n,r){return t(n,r[0])})}function Hc(n,t,r){t=ku(t,n);var e=-1,u=t.length;for(u||(u=1,n=X);++e<u;){var i=null==n?X:n[no(t[e])];
|
||||||
|
i===X&&(e=u,i=r),n=uc(i)?i.call(n):i}return n}function Jc(n,t,r){return null==n?n:fu(n,t,r)}function Yc(n,t,r,e){return e="function"==typeof e?e:X,null==n?n:fu(n,t,r,e)}function Qc(n,t,e){var u=bh(n),i=u||mh(n)||Oh(n);if(t=mi(t,4),null==e){var o=n&&n.constructor;e=i?u?new o:[]:fc(n)&&uc(o)?gs(El(n)):{}}return(i?r:ue)(n,function(n,r,u){return t(e,n,r,u)}),e}function Xc(n,t){return null==n||yu(n,t)}function na(n,t,r){return null==n?n:du(n,t,Au(r))}function ta(n,t,r,e){return e="function"==typeof e?e:X,
|
||||||
|
null==n?n:du(n,t,Au(r),e)}function ra(n){return null==n?[]:E(n,Pc(n))}function ea(n){return null==n?[]:E(n,qc(n))}function ua(n,t,r){return r===X&&(r=t,t=X),r!==X&&(r=Ic(r),r=r===r?r:0),t!==X&&(t=Ic(t),t=t===t?t:0),Mr(Ic(n),t,r)}function ia(n,t,r){return t=Ac(t),r===X?(r=t,t=0):r=Ac(r),n=Ic(n),Ae(n,t,r)}function oa(n,t,r){if(r&&"boolean"!=typeof r&&Ui(n,t,r)&&(t=r=X),r===X&&("boolean"==typeof t?(r=t,t=X):"boolean"==typeof n&&(r=n,n=X)),n===X&&t===X?(n=0,t=1):(n=Ac(n),t===X?(t=n,n=0):t=Ac(t)),n>t){
|
||||||
|
var e=n;n=t,t=e}if(r||n%1||t%1){var u=Ql();return Hl(n+u*(t-n+Qr("1e-"+((u+"").length-1))),t)}return tu(n,t)}function fa(n){return Qh(Ec(n).toLowerCase())}function ca(n){return n=Ec(n),n&&n.replace(Gt,ve).replace(Dr,"")}function aa(n,t,r){n=Ec(n),t=vu(t);var e=n.length;r=r===X?e:Mr(kc(r),0,e);var u=r;return r-=t.length,r>=0&&n.slice(r,u)==t}function la(n){return n=Ec(n),n&&At.test(n)?n.replace(xt,ge):n}function sa(n){return n=Ec(n),n&&Wt.test(n)?n.replace(St,"\\$&"):n}function ha(n,t,r){n=Ec(n),t=kc(t);
|
||||||
|
var e=t?V(n):0;if(!t||e>=t)return n;var u=(t-e)/2;return ri(Nl(u),r)+n+ri(Fl(u),r)}function pa(n,t,r){n=Ec(n),t=kc(t);var e=t?V(n):0;return t&&e<t?n+ri(t-e,r):n}function _a(n,t,r){n=Ec(n),t=kc(t);var e=t?V(n):0;return t&&e<t?ri(t-e,r)+n:n}function va(n,t,r){return r||null==t?t=0:t&&(t=+t),Yl(Ec(n).replace(Lt,""),t||0)}function ga(n,t,r){return t=(r?Ui(n,t,r):t===X)?1:kc(t),eu(Ec(n),t)}function ya(){var n=arguments,t=Ec(n[0]);return n.length<3?t:t.replace(n[1],n[2])}function da(n,t,r){return r&&"number"!=typeof r&&Ui(n,t,r)&&(t=r=X),
|
||||||
|
(r=r===X?Un:r>>>0)?(n=Ec(n),n&&("string"==typeof t||null!=t&&!Ah(t))&&(t=vu(t),!t&&T(n))?Ou(G(n),0,r):n.split(t,r)):[]}function ba(n,t,r){return n=Ec(n),r=null==r?0:Mr(kc(r),0,n.length),t=vu(t),n.slice(r,r+t.length)==t}function wa(n,t,r){var e=Z.templateSettings;r&&Ui(n,t,r)&&(t=X),n=Ec(n),t=Sh({},t,e,li);var u,i,o=Sh({},t.imports,e.imports,li),f=Pc(o),c=E(o,f),a=0,l=t.interpolate||Ht,s="__p += '",h=sl((t.escape||Ht).source+"|"+l.source+"|"+(l===It?Ft:Ht).source+"|"+(t.evaluate||Ht).source+"|$","g"),p="//# sourceURL="+(bl.call(t,"sourceURL")?(t.sourceURL+"").replace(/\s/g," "):"lodash.templateSources["+ ++Zr+"]")+"\n";
|
||||||
|
n.replace(h,function(t,r,e,o,f,c){return e||(e=o),s+=n.slice(a,c).replace(Jt,U),r&&(u=!0,s+="' +\n__e("+r+") +\n'"),f&&(i=!0,s+="';\n"+f+";\n__p += '"),e&&(s+="' +\n((__t = ("+e+")) == null ? '' : __t) +\n'"),a=c+t.length,t}),s+="';\n";var _=bl.call(t,"variable")&&t.variable;if(_){if(Dt.test(_))throw new fl(un)}else s="with (obj) {\n"+s+"\n}\n";s=(i?s.replace(dt,""):s).replace(bt,"$1").replace(wt,"$1;"),s="function("+(_||"obj")+") {\n"+(_?"":"obj || (obj = {});\n")+"var __t, __p = ''"+(u?", __e = _.escape":"")+(i?", __j = Array.prototype.join;\nfunction print() { __p += __j.call(arguments, '') }\n":";\n")+s+"return __p\n}";
|
||||||
|
var v=Xh(function(){return cl(f,p+"return "+s).apply(X,c)});if(v.source=s,rc(v))throw v;return v}function ma(n){return Ec(n).toLowerCase()}function xa(n){return Ec(n).toUpperCase()}function ja(n,t,r){if(n=Ec(n),n&&(r||t===X))return R(n);if(!n||!(t=vu(t)))return n;var e=G(n),u=G(t);return Ou(e,W(e,u),L(e,u)+1).join("")}function Aa(n,t,r){if(n=Ec(n),n&&(r||t===X))return n.slice(0,H(n)+1);if(!n||!(t=vu(t)))return n;var e=G(n);return Ou(e,0,L(e,G(t))+1).join("")}function ka(n,t,r){if(n=Ec(n),n&&(r||t===X))return n.replace(Lt,"");
|
||||||
|
if(!n||!(t=vu(t)))return n;var e=G(n);return Ou(e,W(e,G(t))).join("")}function Oa(n,t){var r=An,e=kn;if(fc(t)){var u="separator"in t?t.separator:u;r="length"in t?kc(t.length):r,e="omission"in t?vu(t.omission):e}n=Ec(n);var i=n.length;if(T(n)){var o=G(n);i=o.length}if(r>=i)return n;var f=r-V(e);if(f<1)return e;var c=o?Ou(o,0,f).join(""):n.slice(0,f);if(u===X)return c+e;if(o&&(f+=c.length-f),Ah(u)){if(n.slice(f).search(u)){var a,l=c;for(u.global||(u=sl(u.source,Ec(Nt.exec(u))+"g")),u.lastIndex=0;a=u.exec(l);)var s=a.index;
|
||||||
|
c=c.slice(0,s===X?f:s)}}else if(n.indexOf(vu(u),f)!=f){var h=c.lastIndexOf(u);h>-1&&(c=c.slice(0,h))}return c+e}function Ia(n){return n=Ec(n),n&&jt.test(n)?n.replace(mt,ye):n}function Ra(n,t,r){return n=Ec(n),t=r?X:t,t===X?$(n)?Q(n):_(n):n.match(t)||[]}function za(t){var r=null==t?0:t.length,e=mi();return t=r?c(t,function(n){if("function"!=typeof n[1])throw new pl(en);return[e(n[0]),n[1]]}):[],uu(function(e){for(var u=-1;++u<r;){var i=t[u];if(n(i[0],this,e))return n(i[1],this,e)}})}function Ea(n){
|
||||||
|
return Nr(Fr(n,an))}function Sa(n){return function(){return n}}function Wa(n,t){return null==n||n!==n?t:n}function La(n){return n}function Ca(n){return De("function"==typeof n?n:Fr(n,an))}function Ua(n){return qe(Fr(n,an))}function Ba(n,t){return Ze(n,Fr(t,an))}function Ta(n,t,e){var u=Pc(t),i=fe(t,u);null!=e||fc(t)&&(i.length||!u.length)||(e=t,t=n,n=this,i=fe(t,Pc(t)));var o=!(fc(e)&&"chain"in e&&!e.chain),f=uc(n);return r(i,function(r){var e=t[r];n[r]=e,f&&(n.prototype[r]=function(){var t=this.__chain__;
|
||||||
|
if(o||t){var r=n(this.__wrapped__);return(r.__actions__=Tu(this.__actions__)).push({func:e,args:arguments,thisArg:n}),r.__chain__=t,r}return e.apply(n,a([this.value()],arguments))})}),n}function $a(){return re._===this&&(re._=Al),this}function Da(){}function Ma(n){return n=kc(n),uu(function(t){return Ge(t,n)})}function Fa(n){return Bi(n)?m(no(n)):Qe(n)}function Na(n){return function(t){return null==n?X:_e(n,t)}}function Pa(){return[]}function qa(){return!1}function Za(){return{}}function Ka(){return"";
|
||||||
|
}function Va(){return!0}function Ga(n,t){if(n=kc(n),n<1||n>Wn)return[];var r=Un,e=Hl(n,Un);t=mi(t),n-=Un;for(var u=O(e,t);++r<n;)t(r);return u}function Ha(n){return bh(n)?c(n,no):bc(n)?[n]:Tu(Cs(Ec(n)))}function Ja(n){var t=++wl;return Ec(n)+t}function Ya(n){return n&&n.length?Yr(n,La,me):X}function Qa(n,t){return n&&n.length?Yr(n,mi(t,2),me):X}function Xa(n){return w(n,La)}function nl(n,t){return w(n,mi(t,2))}function tl(n){return n&&n.length?Yr(n,La,Ne):X}function rl(n,t){return n&&n.length?Yr(n,mi(t,2),Ne):X;
|
||||||
|
}function el(n){return n&&n.length?k(n,La):0}function ul(n,t){return n&&n.length?k(n,mi(t,2)):0}x=null==x?re:be.defaults(re.Object(),x,be.pick(re,qr));var il=x.Array,ol=x.Date,fl=x.Error,cl=x.Function,al=x.Math,ll=x.Object,sl=x.RegExp,hl=x.String,pl=x.TypeError,_l=il.prototype,vl=cl.prototype,gl=ll.prototype,yl=x["__core-js_shared__"],dl=vl.toString,bl=gl.hasOwnProperty,wl=0,ml=function(){var n=/[^.]+$/.exec(yl&&yl.keys&&yl.keys.IE_PROTO||"");return n?"Symbol(src)_1."+n:""}(),xl=gl.toString,jl=dl.call(ll),Al=re._,kl=sl("^"+dl.call(bl).replace(St,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),Ol=ie?x.Buffer:X,Il=x.Symbol,Rl=x.Uint8Array,zl=Ol?Ol.allocUnsafe:X,El=F(ll.getPrototypeOf,ll),Sl=ll.create,Wl=gl.propertyIsEnumerable,Ll=_l.splice,Cl=Il?Il.isConcatSpreadable:X,Ul=Il?Il.iterator:X,Bl=Il?Il.toStringTag:X,Tl=function(){
|
||||||
|
try{var n=Ai(ll,"defineProperty");return n({},"",{}),n}catch(n){}}(),$l=x.clearTimeout!==re.clearTimeout&&x.clearTimeout,Dl=ol&&ol.now!==re.Date.now&&ol.now,Ml=x.setTimeout!==re.setTimeout&&x.setTimeout,Fl=al.ceil,Nl=al.floor,Pl=ll.getOwnPropertySymbols,ql=Ol?Ol.isBuffer:X,Zl=x.isFinite,Kl=_l.join,Vl=F(ll.keys,ll),Gl=al.max,Hl=al.min,Jl=ol.now,Yl=x.parseInt,Ql=al.random,Xl=_l.reverse,ns=Ai(x,"DataView"),ts=Ai(x,"Map"),rs=Ai(x,"Promise"),es=Ai(x,"Set"),us=Ai(x,"WeakMap"),is=Ai(ll,"create"),os=us&&new us,fs={},cs=to(ns),as=to(ts),ls=to(rs),ss=to(es),hs=to(us),ps=Il?Il.prototype:X,_s=ps?ps.valueOf:X,vs=ps?ps.toString:X,gs=function(){
|
||||||
|
function n(){}return function(t){if(!fc(t))return{};if(Sl)return Sl(t);n.prototype=t;var r=new n;return n.prototype=X,r}}();Z.templateSettings={escape:kt,evaluate:Ot,interpolate:It,variable:"",imports:{_:Z}},Z.prototype=J.prototype,Z.prototype.constructor=Z,Y.prototype=gs(J.prototype),Y.prototype.constructor=Y,Ct.prototype=gs(J.prototype),Ct.prototype.constructor=Ct,Xt.prototype.clear=nr,Xt.prototype.delete=tr,Xt.prototype.get=rr,Xt.prototype.has=er,Xt.prototype.set=ur,ir.prototype.clear=or,ir.prototype.delete=fr,
|
||||||
|
ir.prototype.get=cr,ir.prototype.has=ar,ir.prototype.set=lr,sr.prototype.clear=hr,sr.prototype.delete=pr,sr.prototype.get=_r,sr.prototype.has=vr,sr.prototype.set=gr,yr.prototype.add=yr.prototype.push=dr,yr.prototype.has=br,wr.prototype.clear=mr,wr.prototype.delete=xr,wr.prototype.get=jr,wr.prototype.has=Ar,wr.prototype.set=kr;var ys=Pu(ue),ds=Pu(oe,!0),bs=qu(),ws=qu(!0),ms=os?function(n,t){return os.set(n,t),n}:La,xs=Tl?function(n,t){return Tl(n,"toString",{configurable:!0,enumerable:!1,value:Sa(t),
|
||||||
|
writable:!0})}:La,js=uu,As=$l||function(n){return re.clearTimeout(n)},ks=es&&1/P(new es([,-0]))[1]==Sn?function(n){return new es(n)}:Da,Os=os?function(n){return os.get(n)}:Da,Is=Pl?function(n){return null==n?[]:(n=ll(n),i(Pl(n),function(t){return Wl.call(n,t)}))}:Pa,Rs=Pl?function(n){for(var t=[];n;)a(t,Is(n)),n=El(n);return t}:Pa,zs=we;(ns&&zs(new ns(new ArrayBuffer(1)))!=ct||ts&&zs(new ts)!=Gn||rs&&zs(rs.resolve())!=Qn||es&&zs(new es)!=tt||us&&zs(new us)!=it)&&(zs=function(n){var t=we(n),r=t==Yn?n.constructor:X,e=r?to(r):"";
|
||||||
|
if(e)switch(e){case cs:return ct;case as:return Gn;case ls:return Qn;case ss:return tt;case hs:return it}return t});var Es=yl?uc:qa,Ss=Qi(ms),Ws=Ml||function(n,t){return re.setTimeout(n,t)},Ls=Qi(xs),Cs=Pi(function(n){var t=[];return 46===n.charCodeAt(0)&&t.push(""),n.replace(Et,function(n,r,e,u){t.push(e?u.replace(Mt,"$1"):r||n)}),t}),Us=uu(function(n,t){return Jf(n)?Hr(n,ee(t,1,Jf,!0)):[]}),Bs=uu(function(n,t){var r=jo(t);return Jf(r)&&(r=X),Jf(n)?Hr(n,ee(t,1,Jf,!0),mi(r,2)):[]}),Ts=uu(function(n,t){
|
||||||
|
var r=jo(t);return Jf(r)&&(r=X),Jf(n)?Hr(n,ee(t,1,Jf,!0),X,r):[]}),$s=uu(function(n){var t=c(n,ju);return t.length&&t[0]===n[0]?ke(t):[]}),Ds=uu(function(n){var t=jo(n),r=c(n,ju);return t===jo(r)?t=X:r.pop(),r.length&&r[0]===n[0]?ke(r,mi(t,2)):[]}),Ms=uu(function(n){var t=jo(n),r=c(n,ju);return t="function"==typeof t?t:X,t&&r.pop(),r.length&&r[0]===n[0]?ke(r,X,t):[]}),Fs=uu(Oo),Ns=gi(function(n,t){var r=null==n?0:n.length,e=Tr(n,t);return nu(n,c(t,function(n){return Ci(n,r)?+n:n}).sort(Lu)),e}),Ps=uu(function(n){
|
||||||
|
return gu(ee(n,1,Jf,!0))}),qs=uu(function(n){var t=jo(n);return Jf(t)&&(t=X),gu(ee(n,1,Jf,!0),mi(t,2))}),Zs=uu(function(n){var t=jo(n);return t="function"==typeof t?t:X,gu(ee(n,1,Jf,!0),X,t)}),Ks=uu(function(n,t){return Jf(n)?Hr(n,t):[]}),Vs=uu(function(n){return mu(i(n,Jf))}),Gs=uu(function(n){var t=jo(n);return Jf(t)&&(t=X),mu(i(n,Jf),mi(t,2))}),Hs=uu(function(n){var t=jo(n);return t="function"==typeof t?t:X,mu(i(n,Jf),X,t)}),Js=uu(Go),Ys=uu(function(n){var t=n.length,r=t>1?n[t-1]:X;return r="function"==typeof r?(n.pop(),
|
||||||
|
r):X,Ho(n,r)}),Qs=gi(function(n){var t=n.length,r=t?n[0]:0,e=this.__wrapped__,u=function(t){return Tr(t,n)};return!(t>1||this.__actions__.length)&&e instanceof Ct&&Ci(r)?(e=e.slice(r,+r+(t?1:0)),e.__actions__.push({func:nf,args:[u],thisArg:X}),new Y(e,this.__chain__).thru(function(n){return t&&!n.length&&n.push(X),n})):this.thru(u)}),Xs=Fu(function(n,t,r){bl.call(n,r)?++n[r]:Br(n,r,1)}),nh=Ju(ho),th=Ju(po),rh=Fu(function(n,t,r){bl.call(n,r)?n[r].push(t):Br(n,r,[t])}),eh=uu(function(t,r,e){var u=-1,i="function"==typeof r,o=Hf(t)?il(t.length):[];
|
||||||
|
return ys(t,function(t){o[++u]=i?n(r,t,e):Ie(t,r,e)}),o}),uh=Fu(function(n,t,r){Br(n,r,t)}),ih=Fu(function(n,t,r){n[r?0:1].push(t)},function(){return[[],[]]}),oh=uu(function(n,t){if(null==n)return[];var r=t.length;return r>1&&Ui(n,t[0],t[1])?t=[]:r>2&&Ui(t[0],t[1],t[2])&&(t=[t[0]]),He(n,ee(t,1),[])}),fh=Dl||function(){return re.Date.now()},ch=uu(function(n,t,r){var e=_n;if(r.length){var u=N(r,wi(ch));e|=bn}return ai(n,e,t,r,u)}),ah=uu(function(n,t,r){var e=_n|vn;if(r.length){var u=N(r,wi(ah));e|=bn;
|
||||||
|
}return ai(t,e,n,r,u)}),lh=uu(function(n,t){return Gr(n,1,t)}),sh=uu(function(n,t,r){return Gr(n,Ic(t)||0,r)});Cf.Cache=sr;var hh=js(function(t,r){r=1==r.length&&bh(r[0])?c(r[0],z(mi())):c(ee(r,1),z(mi()));var e=r.length;return uu(function(u){for(var i=-1,o=Hl(u.length,e);++i<o;)u[i]=r[i].call(this,u[i]);return n(t,this,u)})}),ph=uu(function(n,t){return ai(n,bn,X,t,N(t,wi(ph)))}),_h=uu(function(n,t){return ai(n,wn,X,t,N(t,wi(_h)))}),vh=gi(function(n,t){return ai(n,xn,X,X,X,t)}),gh=ii(me),yh=ii(function(n,t){
|
||||||
|
return n>=t}),dh=Re(function(){return arguments}())?Re:function(n){return cc(n)&&bl.call(n,"callee")&&!Wl.call(n,"callee")},bh=il.isArray,wh=ce?z(ce):ze,mh=ql||qa,xh=ae?z(ae):Ee,jh=le?z(le):Le,Ah=se?z(se):Be,kh=he?z(he):Te,Oh=pe?z(pe):$e,Ih=ii(Ne),Rh=ii(function(n,t){return n<=t}),zh=Nu(function(n,t){if(Mi(t)||Hf(t))return $u(t,Pc(t),n),X;for(var r in t)bl.call(t,r)&&Sr(n,r,t[r])}),Eh=Nu(function(n,t){$u(t,qc(t),n)}),Sh=Nu(function(n,t,r,e){$u(t,qc(t),n,e)}),Wh=Nu(function(n,t,r,e){$u(t,Pc(t),n,e);
|
||||||
|
}),Lh=gi(Tr),Ch=uu(function(n,t){n=ll(n);var r=-1,e=t.length,u=e>2?t[2]:X;for(u&&Ui(t[0],t[1],u)&&(e=1);++r<e;)for(var i=t[r],o=qc(i),f=-1,c=o.length;++f<c;){var a=o[f],l=n[a];(l===X||Gf(l,gl[a])&&!bl.call(n,a))&&(n[a]=i[a])}return n}),Uh=uu(function(t){return t.push(X,si),n(Mh,X,t)}),Bh=Xu(function(n,t,r){null!=t&&"function"!=typeof t.toString&&(t=xl.call(t)),n[t]=r},Sa(La)),Th=Xu(function(n,t,r){null!=t&&"function"!=typeof t.toString&&(t=xl.call(t)),bl.call(n,t)?n[t].push(r):n[t]=[r]},mi),$h=uu(Ie),Dh=Nu(function(n,t,r){
|
||||||
|
Ke(n,t,r)}),Mh=Nu(function(n,t,r,e){Ke(n,t,r,e)}),Fh=gi(function(n,t){var r={};if(null==n)return r;var e=!1;t=c(t,function(t){return t=ku(t,n),e||(e=t.length>1),t}),$u(n,di(n),r),e&&(r=Fr(r,an|ln|sn,hi));for(var u=t.length;u--;)yu(r,t[u]);return r}),Nh=gi(function(n,t){return null==n?{}:Je(n,t)}),Ph=ci(Pc),qh=ci(qc),Zh=Vu(function(n,t,r){return t=t.toLowerCase(),n+(r?fa(t):t)}),Kh=Vu(function(n,t,r){return n+(r?"-":"")+t.toLowerCase()}),Vh=Vu(function(n,t,r){return n+(r?" ":"")+t.toLowerCase()}),Gh=Ku("toLowerCase"),Hh=Vu(function(n,t,r){
|
||||||
|
return n+(r?"_":"")+t.toLowerCase()}),Jh=Vu(function(n,t,r){return n+(r?" ":"")+Qh(t)}),Yh=Vu(function(n,t,r){return n+(r?" ":"")+t.toUpperCase()}),Qh=Ku("toUpperCase"),Xh=uu(function(t,r){try{return n(t,X,r)}catch(n){return rc(n)?n:new fl(n)}}),np=gi(function(n,t){return r(t,function(t){t=no(t),Br(n,t,ch(n[t],n))}),n}),tp=Yu(),rp=Yu(!0),ep=uu(function(n,t){return function(r){return Ie(r,n,t)}}),up=uu(function(n,t){return function(r){return Ie(n,r,t)}}),ip=ti(c),op=ti(u),fp=ti(h),cp=ui(),ap=ui(!0),lp=ni(function(n,t){
|
||||||
|
return n+t},0),sp=fi("ceil"),hp=ni(function(n,t){return n/t},1),pp=fi("floor"),_p=ni(function(n,t){return n*t},1),vp=fi("round"),gp=ni(function(n,t){return n-t},0);return Z.after=If,Z.ary=Rf,Z.assign=zh,Z.assignIn=Eh,Z.assignInWith=Sh,Z.assignWith=Wh,Z.at=Lh,Z.before=zf,Z.bind=ch,Z.bindAll=np,Z.bindKey=ah,Z.castArray=Nf,Z.chain=Qo,Z.chunk=uo,Z.compact=io,Z.concat=oo,Z.cond=za,Z.conforms=Ea,Z.constant=Sa,Z.countBy=Xs,Z.create=Sc,Z.curry=Ef,Z.curryRight=Sf,Z.debounce=Wf,Z.defaults=Ch,Z.defaultsDeep=Uh,
|
||||||
|
Z.defer=lh,Z.delay=sh,Z.difference=Us,Z.differenceBy=Bs,Z.differenceWith=Ts,Z.drop=fo,Z.dropRight=co,Z.dropRightWhile=ao,Z.dropWhile=lo,Z.fill=so,Z.filter=lf,Z.flatMap=sf,Z.flatMapDeep=hf,Z.flatMapDepth=pf,Z.flatten=_o,Z.flattenDeep=vo,Z.flattenDepth=go,Z.flip=Lf,Z.flow=tp,Z.flowRight=rp,Z.fromPairs=yo,Z.functions=$c,Z.functionsIn=Dc,Z.groupBy=rh,Z.initial=mo,Z.intersection=$s,Z.intersectionBy=Ds,Z.intersectionWith=Ms,Z.invert=Bh,Z.invertBy=Th,Z.invokeMap=eh,Z.iteratee=Ca,Z.keyBy=uh,Z.keys=Pc,Z.keysIn=qc,
|
||||||
|
Z.map=yf,Z.mapKeys=Zc,Z.mapValues=Kc,Z.matches=Ua,Z.matchesProperty=Ba,Z.memoize=Cf,Z.merge=Dh,Z.mergeWith=Mh,Z.method=ep,Z.methodOf=up,Z.mixin=Ta,Z.negate=Uf,Z.nthArg=Ma,Z.omit=Fh,Z.omitBy=Vc,Z.once=Bf,Z.orderBy=df,Z.over=ip,Z.overArgs=hh,Z.overEvery=op,Z.overSome=fp,Z.partial=ph,Z.partialRight=_h,Z.partition=ih,Z.pick=Nh,Z.pickBy=Gc,Z.property=Fa,Z.propertyOf=Na,Z.pull=Fs,Z.pullAll=Oo,Z.pullAllBy=Io,Z.pullAllWith=Ro,Z.pullAt=Ns,Z.range=cp,Z.rangeRight=ap,Z.rearg=vh,Z.reject=mf,Z.remove=zo,Z.rest=Tf,
|
||||||
|
Z.reverse=Eo,Z.sampleSize=jf,Z.set=Jc,Z.setWith=Yc,Z.shuffle=Af,Z.slice=So,Z.sortBy=oh,Z.sortedUniq=$o,Z.sortedUniqBy=Do,Z.split=da,Z.spread=$f,Z.tail=Mo,Z.take=Fo,Z.takeRight=No,Z.takeRightWhile=Po,Z.takeWhile=qo,Z.tap=Xo,Z.throttle=Df,Z.thru=nf,Z.toArray=jc,Z.toPairs=Ph,Z.toPairsIn=qh,Z.toPath=Ha,Z.toPlainObject=Rc,Z.transform=Qc,Z.unary=Mf,Z.union=Ps,Z.unionBy=qs,Z.unionWith=Zs,Z.uniq=Zo,Z.uniqBy=Ko,Z.uniqWith=Vo,Z.unset=Xc,Z.unzip=Go,Z.unzipWith=Ho,Z.update=na,Z.updateWith=ta,Z.values=ra,Z.valuesIn=ea,
|
||||||
|
Z.without=Ks,Z.words=Ra,Z.wrap=Ff,Z.xor=Vs,Z.xorBy=Gs,Z.xorWith=Hs,Z.zip=Js,Z.zipObject=Jo,Z.zipObjectDeep=Yo,Z.zipWith=Ys,Z.entries=Ph,Z.entriesIn=qh,Z.extend=Eh,Z.extendWith=Sh,Ta(Z,Z),Z.add=lp,Z.attempt=Xh,Z.camelCase=Zh,Z.capitalize=fa,Z.ceil=sp,Z.clamp=ua,Z.clone=Pf,Z.cloneDeep=Zf,Z.cloneDeepWith=Kf,Z.cloneWith=qf,Z.conformsTo=Vf,Z.deburr=ca,Z.defaultTo=Wa,Z.divide=hp,Z.endsWith=aa,Z.eq=Gf,Z.escape=la,Z.escapeRegExp=sa,Z.every=af,Z.find=nh,Z.findIndex=ho,Z.findKey=Wc,Z.findLast=th,Z.findLastIndex=po,
|
||||||
|
Z.findLastKey=Lc,Z.floor=pp,Z.forEach=_f,Z.forEachRight=vf,Z.forIn=Cc,Z.forInRight=Uc,Z.forOwn=Bc,Z.forOwnRight=Tc,Z.get=Mc,Z.gt=gh,Z.gte=yh,Z.has=Fc,Z.hasIn=Nc,Z.head=bo,Z.identity=La,Z.includes=gf,Z.indexOf=wo,Z.inRange=ia,Z.invoke=$h,Z.isArguments=dh,Z.isArray=bh,Z.isArrayBuffer=wh,Z.isArrayLike=Hf,Z.isArrayLikeObject=Jf,Z.isBoolean=Yf,Z.isBuffer=mh,Z.isDate=xh,Z.isElement=Qf,Z.isEmpty=Xf,Z.isEqual=nc,Z.isEqualWith=tc,Z.isError=rc,Z.isFinite=ec,Z.isFunction=uc,Z.isInteger=ic,Z.isLength=oc,Z.isMap=jh,
|
||||||
|
Z.isMatch=ac,Z.isMatchWith=lc,Z.isNaN=sc,Z.isNative=hc,Z.isNil=_c,Z.isNull=pc,Z.isNumber=vc,Z.isObject=fc,Z.isObjectLike=cc,Z.isPlainObject=gc,Z.isRegExp=Ah,Z.isSafeInteger=yc,Z.isSet=kh,Z.isString=dc,Z.isSymbol=bc,Z.isTypedArray=Oh,Z.isUndefined=wc,Z.isWeakMap=mc,Z.isWeakSet=xc,Z.join=xo,Z.kebabCase=Kh,Z.last=jo,Z.lastIndexOf=Ao,Z.lowerCase=Vh,Z.lowerFirst=Gh,Z.lt=Ih,Z.lte=Rh,Z.max=Ya,Z.maxBy=Qa,Z.mean=Xa,Z.meanBy=nl,Z.min=tl,Z.minBy=rl,Z.stubArray=Pa,Z.stubFalse=qa,Z.stubObject=Za,Z.stubString=Ka,
|
||||||
|
Z.stubTrue=Va,Z.multiply=_p,Z.nth=ko,Z.noConflict=$a,Z.noop=Da,Z.now=fh,Z.pad=ha,Z.padEnd=pa,Z.padStart=_a,Z.parseInt=va,Z.random=oa,Z.reduce=bf,Z.reduceRight=wf,Z.repeat=ga,Z.replace=ya,Z.result=Hc,Z.round=vp,Z.runInContext=p,Z.sample=xf,Z.size=kf,Z.snakeCase=Hh,Z.some=Of,Z.sortedIndex=Wo,Z.sortedIndexBy=Lo,Z.sortedIndexOf=Co,Z.sortedLastIndex=Uo,Z.sortedLastIndexBy=Bo,Z.sortedLastIndexOf=To,Z.startCase=Jh,Z.startsWith=ba,Z.subtract=gp,Z.sum=el,Z.sumBy=ul,Z.template=wa,Z.times=Ga,Z.toFinite=Ac,Z.toInteger=kc,
|
||||||
|
Z.toLength=Oc,Z.toLower=ma,Z.toNumber=Ic,Z.toSafeInteger=zc,Z.toString=Ec,Z.toUpper=xa,Z.trim=ja,Z.trimEnd=Aa,Z.trimStart=ka,Z.truncate=Oa,Z.unescape=Ia,Z.uniqueId=Ja,Z.upperCase=Yh,Z.upperFirst=Qh,Z.each=_f,Z.eachRight=vf,Z.first=bo,Ta(Z,function(){var n={};return ue(Z,function(t,r){bl.call(Z.prototype,r)||(n[r]=t)}),n}(),{chain:!1}),Z.VERSION=nn,r(["bind","bindKey","curry","curryRight","partial","partialRight"],function(n){Z[n].placeholder=Z}),r(["drop","take"],function(n,t){Ct.prototype[n]=function(r){
|
||||||
|
r=r===X?1:Gl(kc(r),0);var e=this.__filtered__&&!t?new Ct(this):this.clone();return e.__filtered__?e.__takeCount__=Hl(r,e.__takeCount__):e.__views__.push({size:Hl(r,Un),type:n+(e.__dir__<0?"Right":"")}),e},Ct.prototype[n+"Right"]=function(t){return this.reverse()[n](t).reverse()}}),r(["filter","map","takeWhile"],function(n,t){var r=t+1,e=r==Rn||r==En;Ct.prototype[n]=function(n){var t=this.clone();return t.__iteratees__.push({iteratee:mi(n,3),type:r}),t.__filtered__=t.__filtered__||e,t}}),r(["head","last"],function(n,t){
|
||||||
|
var r="take"+(t?"Right":"");Ct.prototype[n]=function(){return this[r](1).value()[0]}}),r(["initial","tail"],function(n,t){var r="drop"+(t?"":"Right");Ct.prototype[n]=function(){return this.__filtered__?new Ct(this):this[r](1)}}),Ct.prototype.compact=function(){return this.filter(La)},Ct.prototype.find=function(n){return this.filter(n).head()},Ct.prototype.findLast=function(n){return this.reverse().find(n)},Ct.prototype.invokeMap=uu(function(n,t){return"function"==typeof n?new Ct(this):this.map(function(r){
|
||||||
|
return Ie(r,n,t)})}),Ct.prototype.reject=function(n){return this.filter(Uf(mi(n)))},Ct.prototype.slice=function(n,t){n=kc(n);var r=this;return r.__filtered__&&(n>0||t<0)?new Ct(r):(n<0?r=r.takeRight(-n):n&&(r=r.drop(n)),t!==X&&(t=kc(t),r=t<0?r.dropRight(-t):r.take(t-n)),r)},Ct.prototype.takeRightWhile=function(n){return this.reverse().takeWhile(n).reverse()},Ct.prototype.toArray=function(){return this.take(Un)},ue(Ct.prototype,function(n,t){var r=/^(?:filter|find|map|reject)|While$/.test(t),e=/^(?:head|last)$/.test(t),u=Z[e?"take"+("last"==t?"Right":""):t],i=e||/^find/.test(t);
|
||||||
|
u&&(Z.prototype[t]=function(){var t=this.__wrapped__,o=e?[1]:arguments,f=t instanceof Ct,c=o[0],l=f||bh(t),s=function(n){var t=u.apply(Z,a([n],o));return e&&h?t[0]:t};l&&r&&"function"==typeof c&&1!=c.length&&(f=l=!1);var h=this.__chain__,p=!!this.__actions__.length,_=i&&!h,v=f&&!p;if(!i&&l){t=v?t:new Ct(this);var g=n.apply(t,o);return g.__actions__.push({func:nf,args:[s],thisArg:X}),new Y(g,h)}return _&&v?n.apply(this,o):(g=this.thru(s),_?e?g.value()[0]:g.value():g)})}),r(["pop","push","shift","sort","splice","unshift"],function(n){
|
||||||
|
var t=_l[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:pop|shift)$/.test(n);Z.prototype[n]=function(){var n=arguments;if(e&&!this.__chain__){var u=this.value();return t.apply(bh(u)?u:[],n)}return this[r](function(r){return t.apply(bh(r)?r:[],n)})}}),ue(Ct.prototype,function(n,t){var r=Z[t];if(r){var e=r.name+"";bl.call(fs,e)||(fs[e]=[]),fs[e].push({name:t,func:r})}}),fs[Qu(X,vn).name]=[{name:"wrapper",func:X}],Ct.prototype.clone=$t,Ct.prototype.reverse=Yt,Ct.prototype.value=Qt,Z.prototype.at=Qs,
|
||||||
|
Z.prototype.chain=tf,Z.prototype.commit=rf,Z.prototype.next=ef,Z.prototype.plant=of,Z.prototype.reverse=ff,Z.prototype.toJSON=Z.prototype.valueOf=Z.prototype.value=cf,Z.prototype.first=Z.prototype.head,Ul&&(Z.prototype[Ul]=uf),Z},be=de();"function"==typeof define&&"object"==typeof define.amd&&define.amd?(re._=be,define(function(){return be})):ue?((ue.exports=be)._=be,ee._=be):re._=be}).call(this);
|
||||||