mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-23 20:05:12 +00:00
feat(skills): add excalidraw-diagram-generator skill and docs update
This commit is contained in:
612
skills/excalidraw-diagram-generator/SKILL.md
Normal file
612
skills/excalidraw-diagram-generator/SKILL.md
Normal file
@@ -0,0 +1,612 @@
|
||||
---
|
||||
name: excalidraw-diagram-generator
|
||||
description: 'Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw.'
|
||||
---
|
||||
|
||||
# Excalidraw Diagram Generator
|
||||
|
||||
A skill for generating Excalidraw-format diagrams from natural language descriptions. This skill helps create visual representations of processes, systems, relationships, and ideas without manual drawing.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when users request:
|
||||
|
||||
- "Create a diagram showing..."
|
||||
- "Make a flowchart for..."
|
||||
- "Visualize the process of..."
|
||||
- "Draw the system architecture of..."
|
||||
- "Generate a mind map about..."
|
||||
- "Create an Excalidraw file for..."
|
||||
- "Show the relationship between..."
|
||||
- "Diagram the workflow of..."
|
||||
|
||||
**Supported diagram types:**
|
||||
- 📊 **Flowcharts**: Sequential processes, workflows, decision trees
|
||||
- 🔗 **Relationship Diagrams**: Entity relationships, system components, dependencies
|
||||
- 🧠 **Mind Maps**: Concept hierarchies, brainstorming results, topic organization
|
||||
- 🏗️ **Architecture Diagrams**: System design, module interactions, data flow
|
||||
- 📈 **Data Flow Diagrams (DFD)**: Data flow visualization, data transformation processes
|
||||
- 🏊 **Business Flow (Swimlane)**: Cross-functional workflows, actor-based process flows
|
||||
- 📦 **Class Diagrams**: Object-oriented design, class structures and relationships
|
||||
- 🔄 **Sequence Diagrams**: Object interactions over time, message flows
|
||||
- 🗃️ **ER Diagrams**: Database entity relationships, data models
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Clear description of what should be visualized
|
||||
- Identification of key entities, steps, or concepts
|
||||
- Understanding of relationships or flow between elements
|
||||
|
||||
## Step-by-Step Workflow
|
||||
|
||||
### Step 1: Understand the Request
|
||||
|
||||
Analyze the user's description to determine:
|
||||
1. **Diagram type** (flowchart, relationship, mind map, architecture)
|
||||
2. **Key elements** (entities, steps, concepts)
|
||||
3. **Relationships** (flow, connections, hierarchy)
|
||||
4. **Complexity** (number of elements)
|
||||
|
||||
### Step 2: Choose the Appropriate Diagram Type
|
||||
|
||||
| User Intent | Diagram Type | Example Keywords |
|
||||
|-------------|--------------|------------------|
|
||||
| Process flow, steps, procedures | **Flowchart** | "workflow", "process", "steps", "procedure" |
|
||||
| Connections, dependencies, associations | **Relationship Diagram** | "relationship", "connections", "dependencies", "structure" |
|
||||
| Concept hierarchy, brainstorming | **Mind Map** | "mind map", "concepts", "ideas", "breakdown" |
|
||||
| System design, components | **Architecture Diagram** | "architecture", "system", "components", "modules" |
|
||||
| Data flow, transformation processes | **Data Flow Diagram (DFD)** | "data flow", "data processing", "data transformation" |
|
||||
| Cross-functional processes, actor responsibilities | **Business Flow (Swimlane)** | "business process", "swimlane", "actors", "responsibilities" |
|
||||
| Object-oriented design, class structures | **Class Diagram** | "class", "inheritance", "OOP", "object model" |
|
||||
| Interaction sequences, message flows | **Sequence Diagram** | "sequence", "interaction", "messages", "timeline" |
|
||||
| Database design, entity relationships | **ER Diagram** | "database", "entity", "relationship", "data model" |
|
||||
|
||||
### Step 3: Extract Structured Information
|
||||
|
||||
**For Flowcharts:**
|
||||
- List of sequential steps
|
||||
- Decision points (if any)
|
||||
- Start and end points
|
||||
|
||||
**For Relationship Diagrams:**
|
||||
- Entities/nodes (name + optional description)
|
||||
- Relationships between entities (from → to, with label)
|
||||
|
||||
**For Mind Maps:**
|
||||
- Central topic
|
||||
- Main branches (3-6 recommended)
|
||||
- Sub-topics for each branch (optional)
|
||||
|
||||
**For Data Flow Diagrams (DFD):**
|
||||
- Data sources and destinations (external entities)
|
||||
- Processes (data transformations)
|
||||
- Data stores (databases, files)
|
||||
- Data flows (arrows showing data movement from left-to-right or from top-left to bottom-right)
|
||||
- **Important**: Do not represent process order, only data flow
|
||||
|
||||
**For Business Flow (Swimlane):**
|
||||
- Actors/roles (departments, systems, people) - displayed as header columns
|
||||
- Process lanes (vertical lanes under each actor)
|
||||
- Process boxes (activities within each lane)
|
||||
- Flow arrows (connecting process boxes, including cross-lane handoffs)
|
||||
|
||||
**For Class Diagrams:**
|
||||
- Classes with names
|
||||
- Attributes with visibility (+, -, #)
|
||||
- Methods with visibility and parameters
|
||||
- Relationships: inheritance (solid line + white triangle), implementation (dashed line + white triangle), association (solid line), dependency (dashed line), aggregation (solid line + white diamond), composition (solid line + filled diamond)
|
||||
- Multiplicity notations (1, 0..1, 1..*, *)
|
||||
|
||||
**For Sequence Diagrams:**
|
||||
- Objects/actors (arranged horizontally at top)
|
||||
- Lifelines (vertical lines from each object)
|
||||
- Messages (horizontal arrows between lifelines)
|
||||
- Synchronous messages (solid arrow), asynchronous messages (dashed arrow)
|
||||
- Return values (dashed arrows)
|
||||
- Activation boxes (rectangles on lifelines during execution)
|
||||
- Time flows from top to bottom
|
||||
|
||||
**For ER Diagrams:**
|
||||
- Entities (rectangles with entity names)
|
||||
- Attributes (listed inside entities)
|
||||
- Primary keys (underlined or marked with PK)
|
||||
- Foreign keys (marked with FK)
|
||||
- Relationships (lines connecting entities)
|
||||
- Cardinality: 1:1 (one-to-one), 1:N (one-to-many), N:M (many-to-many)
|
||||
- Junction/associative entities for many-to-many relationships (dashed rectangles)
|
||||
|
||||
### Step 4: Generate the Excalidraw JSON
|
||||
|
||||
Create the `.excalidraw` file with appropriate elements:
|
||||
|
||||
**Available element types:**
|
||||
- `rectangle`: Boxes for entities, steps, concepts
|
||||
- `ellipse`: Alternative shapes for emphasis
|
||||
- `diamond`: Decision points
|
||||
- `arrow`: Directional connections
|
||||
- `text`: Labels and annotations
|
||||
|
||||
**Key properties to set:**
|
||||
- **Position**: `x`, `y` coordinates
|
||||
- **Size**: `width`, `height`
|
||||
- **Style**: `strokeColor`, `backgroundColor`, `fillStyle`
|
||||
- **Font**: `fontFamily: 5` (Excalifont - **required for all text elements**)
|
||||
- **Text**: Embedded text for labels
|
||||
- **Connections**: `points` array for arrows
|
||||
|
||||
**Important**: All text elements must use `fontFamily: 5` (Excalifont) for consistent visual appearance.
|
||||
|
||||
### Step 5: Format the Output
|
||||
|
||||
Structure the complete Excalidraw file:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": [
|
||||
// Array of diagram elements
|
||||
],
|
||||
"appState": {
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"gridSize": 20
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 6: Save and Provide Instructions
|
||||
|
||||
1. Save as `<descriptive-name>.excalidraw`
|
||||
2. Inform user how to open:
|
||||
- Visit https://excalidraw.com
|
||||
- Click "Open" or drag-and-drop the file
|
||||
- Or use Excalidraw VS Code extension
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Element Count Guidelines
|
||||
|
||||
| Diagram Type | Recommended Count | Maximum |
|
||||
|--------------|-------------------|---------|
|
||||
| Flowchart steps | 3-10 | 15 |
|
||||
| Relationship entities | 3-8 | 12 |
|
||||
| Mind map branches | 4-6 | 8 |
|
||||
| Mind map sub-topics per branch | 2-4 | 6 |
|
||||
|
||||
### Layout Tips
|
||||
|
||||
1. **Start positions**: Center important elements, use consistent spacing
|
||||
2. **Spacing**:
|
||||
- Horizontal gap: 200-300px between elements
|
||||
- Vertical gap: 100-150px between rows
|
||||
3. **Colors**: Use consistent color scheme
|
||||
- Primary elements: Light blue (`#a5d8ff`)
|
||||
- Secondary elements: Light green (`#b2f2bb`)
|
||||
- Important/Central: Yellow (`#ffd43b`)
|
||||
- Alerts/Warnings: Light red (`#ffc9c9`)
|
||||
4. **Text sizing**: 16-24px for readability
|
||||
5. **Font**: Always use `fontFamily: 5` (Excalifont) for all text elements
|
||||
6. **Arrow style**: Use straight arrows for simple flows, curved for complex relationships
|
||||
|
||||
### Complexity Management
|
||||
|
||||
**If user request has too many elements:**
|
||||
- Suggest breaking into multiple diagrams
|
||||
- Focus on main elements first
|
||||
- Offer to create detailed sub-diagrams
|
||||
|
||||
**Example response:**
|
||||
```
|
||||
"Your request includes 15 components. For clarity, I recommend:
|
||||
1. High-level architecture diagram (6 main components)
|
||||
2. Detailed diagram for each subsystem
|
||||
|
||||
Would you like me to start with the high-level view?"
|
||||
```
|
||||
|
||||
## Example Prompts and Responses
|
||||
|
||||
### Example 1: Simple Flowchart
|
||||
|
||||
**User:** "Create a flowchart for user registration"
|
||||
|
||||
**Agent generates:**
|
||||
1. Extract steps: "Enter email" → "Verify email" → "Set password" → "Complete"
|
||||
2. Create flowchart with 4 rectangles + 3 arrows
|
||||
3. Save as `user-registration-flow.excalidraw`
|
||||
|
||||
### Example 2: Relationship Diagram
|
||||
|
||||
**User:** "Diagram the relationship between User, Post, and Comment entities"
|
||||
|
||||
**Agent generates:**
|
||||
1. Entities: User, Post, Comment
|
||||
2. Relationships: User → Post ("creates"), User → Comment ("writes"), Post → Comment ("contains")
|
||||
3. Save as `user-content-relationships.excalidraw`
|
||||
|
||||
### Example 3: Mind Map
|
||||
|
||||
**User:** "Mind map about machine learning concepts"
|
||||
|
||||
**Agent generates:**
|
||||
1. Center: "Machine Learning"
|
||||
2. Branches: Supervised Learning, Unsupervised Learning, Reinforcement Learning, Deep Learning
|
||||
3. Sub-topics under each branch
|
||||
4. Save as `machine-learning-mindmap.excalidraw`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Elements overlap | Increase spacing between coordinates |
|
||||
| Text doesn't fit in boxes | Increase box width or reduce font size |
|
||||
| Too many elements | Break into multiple diagrams |
|
||||
| Unclear layout | Use grid layout (rows/columns) or radial layout (mind maps) |
|
||||
| Colors inconsistent | Define color palette upfront based on element types |
|
||||
|
||||
## Advanced Techniques
|
||||
|
||||
### Grid Layout (for Relationship Diagrams)
|
||||
```javascript
|
||||
const columns = Math.ceil(Math.sqrt(entityCount));
|
||||
const x = startX + (index % columns) * horizontalGap;
|
||||
const y = startY + Math.floor(index / columns) * verticalGap;
|
||||
```
|
||||
|
||||
### Radial Layout (for Mind Maps)
|
||||
```javascript
|
||||
const angle = (2 * Math.PI * index) / branchCount;
|
||||
const x = centerX + radius * Math.cos(angle);
|
||||
const y = centerY + radius * Math.sin(angle);
|
||||
```
|
||||
|
||||
### Auto-generated IDs
|
||||
Use timestamp + random string for unique IDs:
|
||||
```javascript
|
||||
const id = Date.now().toString(36) + Math.random().toString(36).substr(2);
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
Always provide:
|
||||
1. ✅ Complete `.excalidraw` JSON file
|
||||
2. 📊 Summary of what was created
|
||||
3. 📝 Element count
|
||||
4. 💡 Instructions for opening/editing
|
||||
|
||||
**Example summary:**
|
||||
```
|
||||
Created: user-workflow.excalidraw
|
||||
Type: Flowchart
|
||||
Elements: 7 rectangles, 6 arrows, 1 title text
|
||||
Total: 14 elements
|
||||
|
||||
To view:
|
||||
1. Visit https://excalidraw.com
|
||||
2. Drag and drop user-workflow.excalidraw
|
||||
3. Or use File → Open in Excalidraw VS Code extension
|
||||
```
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
Before delivering the diagram:
|
||||
- [ ] All elements have unique IDs
|
||||
- [ ] Coordinates prevent overlapping
|
||||
- [ ] Text is readable (font size 16+)
|
||||
- [ ] **All text elements use `fontFamily: 5` (Excalifont)**
|
||||
- [ ] Arrows connect logically
|
||||
- [ ] Colors follow consistent scheme
|
||||
- [ ] File is valid JSON
|
||||
- [ ] Element count is reasonable (<20 for clarity)
|
||||
|
||||
## Icon Libraries (Optional Enhancement)
|
||||
|
||||
For specialized diagrams (e.g., AWS/GCP/Azure architecture diagrams), you can use pre-made icon libraries from Excalidraw. This provides professional, standardized icons instead of basic shapes.
|
||||
|
||||
### When User Requests Icons
|
||||
|
||||
**If user asks for AWS/cloud architecture diagrams or mentions wanting to use specific icons:**
|
||||
|
||||
1. **Check if library exists**: Look for `libraries/<library-name>/reference.md`
|
||||
2. **If library exists**: Proceed to use icons (see AI Assistant Workflow below)
|
||||
3. **If library does NOT exist**: Respond with setup instructions:
|
||||
|
||||
```
|
||||
To use [AWS/GCP/Azure/etc.] architecture icons, please follow these steps:
|
||||
|
||||
1. Visit https://libraries.excalidraw.com/
|
||||
2. Search for "[AWS Architecture Icons/etc.]" and download the .excalidrawlib file
|
||||
3. Create directory: skills/excalidraw-diagram-generator/libraries/[icon-set-name]/
|
||||
4. Place the downloaded file in that directory
|
||||
5. Run the splitter script:
|
||||
python skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/[icon-set-name]/
|
||||
|
||||
This will split the library into individual icon files for efficient use.
|
||||
After setup is complete, I can create your diagram using the actual AWS/cloud icons.
|
||||
|
||||
Alternatively, I can create the diagram now using simple shapes (rectangles, ellipses)
|
||||
which you can later replace with icons manually in Excalidraw.
|
||||
```
|
||||
|
||||
### User Setup Instructions (Detailed)
|
||||
|
||||
**Step 1: Create Library Directory**
|
||||
```bash
|
||||
mkdir -p skills/excalidraw-diagram-generator/libraries/aws-architecture-icons
|
||||
```
|
||||
|
||||
**Step 2: Download Library**
|
||||
- Visit: https://libraries.excalidraw.com/
|
||||
- Search for your desired icon set (e.g., "AWS Architecture Icons")
|
||||
- Click download to get the `.excalidrawlib` file
|
||||
- Example categories (availability varies; confirm on the site):
|
||||
- Cloud service icons
|
||||
- UI/Material icons
|
||||
- Flowchart symbols
|
||||
|
||||
**Step 3: Place Library File**
|
||||
- Rename the downloaded file to match the directory name (e.g., `aws-architecture-icons.excalidrawlib`)
|
||||
- Move it to the directory created in Step 1
|
||||
|
||||
**Step 4: Run Splitter Script**
|
||||
```bash
|
||||
python skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/
|
||||
```
|
||||
|
||||
**Step 5: Verify Setup**
|
||||
After running the script, verify the following structure exists:
|
||||
```
|
||||
skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/
|
||||
aws-architecture-icons.excalidrawlib (original)
|
||||
reference.md (generated - icon lookup table)
|
||||
icons/ (generated - individual icon files)
|
||||
API-Gateway.json
|
||||
CloudFront.json
|
||||
EC2.json
|
||||
Lambda.json
|
||||
RDS.json
|
||||
S3.json
|
||||
...
|
||||
```
|
||||
|
||||
### AI Assistant Workflow
|
||||
|
||||
**When icon libraries are available in `libraries/`:**
|
||||
|
||||
**RECOMMENDED APPROACH: Use Python Scripts (Efficient & Reliable)**
|
||||
|
||||
The repository includes Python scripts that handle icon integration automatically:
|
||||
|
||||
1. **Create base diagram structure**:
|
||||
- Create `.excalidraw` file with basic layout (title, boxes, regions)
|
||||
- This establishes the canvas and overall structure
|
||||
|
||||
2. **Add icons using Python script**:
|
||||
```bash
|
||||
python skills/excalidraw-diagram-generator/scripts/add-icon-to-diagram.py \
|
||||
<diagram-path> <icon-name> <x> <y> [--label "Text"] [--library-path PATH]
|
||||
```
|
||||
- Edit via `.excalidraw.edit` is enabled by default to avoid overwrite issues; pass `--no-use-edit-suffix` to disable.
|
||||
|
||||
**Examples**:
|
||||
```bash
|
||||
# Add EC2 icon at position (400, 300) with label
|
||||
python scripts/add-icon-to-diagram.py diagram.excalidraw EC2 400 300 --label "Web Server"
|
||||
|
||||
# Add VPC icon at position (200, 150)
|
||||
python scripts/add-icon-to-diagram.py diagram.excalidraw VPC 200 150
|
||||
|
||||
# Add icon from different library
|
||||
python scripts/add-icon-to-diagram.py diagram.excalidraw Compute-Engine 500 200 \
|
||||
--library-path libraries/gcp-icons --label "API Server"
|
||||
```
|
||||
|
||||
3. **Add connecting arrows**:
|
||||
```bash
|
||||
python skills/excalidraw-diagram-generator/scripts/add-arrow.py \
|
||||
<diagram-path> <from-x> <from-y> <to-x> <to-y> [--label "Text"] [--style solid|dashed|dotted] [--color HEX]
|
||||
```
|
||||
- Edit via `.excalidraw.edit` is enabled by default to avoid overwrite issues; pass `--no-use-edit-suffix` to disable.
|
||||
|
||||
**Examples**:
|
||||
```bash
|
||||
# Simple arrow from (300, 250) to (500, 300)
|
||||
python scripts/add-arrow.py diagram.excalidraw 300 250 500 300
|
||||
|
||||
# Arrow with label
|
||||
python scripts/add-arrow.py diagram.excalidraw 300 250 500 300 --label "HTTPS"
|
||||
|
||||
# Dashed arrow with custom color
|
||||
python scripts/add-arrow.py diagram.excalidraw 400 350 600 400 --style dashed --color "#7950f2"
|
||||
```
|
||||
|
||||
4. **Workflow summary**:
|
||||
```bash
|
||||
# Step 1: Create base diagram with title and structure
|
||||
# (Create .excalidraw file with initial elements)
|
||||
|
||||
# Step 2: Add icons with labels
|
||||
python scripts/add-icon-to-diagram.py my-diagram.excalidraw "Internet-gateway" 200 150 --label "Internet Gateway"
|
||||
python scripts/add-icon-to-diagram.py my-diagram.excalidraw VPC 250 250
|
||||
python scripts/add-icon-to-diagram.py my-diagram.excalidraw ELB 350 300 --label "Load Balancer"
|
||||
python scripts/add-icon-to-diagram.py my-diagram.excalidraw EC2 450 350 --label "EC2 Instance"
|
||||
python scripts/add-icon-to-diagram.py my-diagram.excalidraw RDS 550 400 --label "Database"
|
||||
|
||||
# Step 3: Add connecting arrows
|
||||
python scripts/add-arrow.py my-diagram.excalidraw 250 200 300 250 # Internet → VPC
|
||||
python scripts/add-arrow.py my-diagram.excalidraw 300 300 400 300 # VPC → ELB
|
||||
python scripts/add-arrow.py my-diagram.excalidraw 400 330 500 350 # ELB → EC2
|
||||
python scripts/add-arrow.py my-diagram.excalidraw 500 380 600 400 # EC2 → RDS
|
||||
```
|
||||
|
||||
**Benefits of Python Script Approach**:
|
||||
- ✅ **No token consumption**: Icon JSON data (200-1000 lines each) never enters AI context
|
||||
- ✅ **Accurate transformations**: Coordinate calculations handled deterministically
|
||||
- ✅ **ID management**: Automatic UUID generation prevents conflicts
|
||||
- ✅ **Reliable**: No risk of coordinate miscalculation or ID collision
|
||||
- ✅ **Fast**: Direct file manipulation, no parsing overhead
|
||||
- ✅ **Reusable**: Works with any Excalidraw library you provide
|
||||
|
||||
**ALTERNATIVE: Manual Icon Integration (Not Recommended)**
|
||||
|
||||
Only use this if Python scripts are unavailable:
|
||||
|
||||
1. **Check for libraries**:
|
||||
```
|
||||
List directory: skills/excalidraw-diagram-generator/libraries/
|
||||
Look for subdirectories containing reference.md files
|
||||
```
|
||||
|
||||
2. **Read reference.md**:
|
||||
```
|
||||
Open: libraries/<library-name>/reference.md
|
||||
This is lightweight (typically <300 lines) and lists all available icons
|
||||
```
|
||||
|
||||
3. **Find relevant icons**:
|
||||
```
|
||||
Search the reference.md table for icon names matching diagram needs
|
||||
Example: For AWS diagram with EC2, S3, Lambda → Find "EC2", "S3", "Lambda" in table
|
||||
```
|
||||
|
||||
4. **Load specific icon data** (WARNING: Large files):
|
||||
```
|
||||
Read ONLY the needed icon files:
|
||||
- libraries/aws-architecture-icons/icons/EC2.json (200-300 lines)
|
||||
- libraries/aws-architecture-icons/icons/S3.json (200-300 lines)
|
||||
- libraries/aws-architecture-icons/icons/Lambda.json (200-300 lines)
|
||||
Note: Each icon file is 200-1000 lines - this consumes significant tokens
|
||||
```
|
||||
|
||||
5. **Extract and transform elements**:
|
||||
```
|
||||
Each icon JSON contains an "elements" array
|
||||
Calculate bounding box (min_x, min_y, max_x, max_y)
|
||||
Apply offset to all x/y coordinates
|
||||
Generate new unique IDs for all elements
|
||||
Update groupIds references
|
||||
Copy transformed elements into your diagram
|
||||
```
|
||||
|
||||
6. **Position icons and add connections**:
|
||||
```
|
||||
Adjust x/y coordinates to position icons correctly in the diagram
|
||||
Update IDs to ensure uniqueness across diagram
|
||||
Add connecting arrows and labels as needed
|
||||
```
|
||||
|
||||
**Manual Integration Challenges**:
|
||||
- ⚠️ High token consumption (200-1000 lines per icon × number of icons)
|
||||
- ⚠️ Complex coordinate transformation calculations
|
||||
- ⚠️ Risk of ID collision if not handled carefully
|
||||
- ⚠️ Time-consuming for diagrams with many icons
|
||||
|
||||
### Example: Creating AWS Diagram with Icons
|
||||
|
||||
**Request**: "Create an AWS architecture diagram with Internet Gateway, VPC, ELB, EC2, and RDS"
|
||||
|
||||
**Recommended Workflow (using Python scripts)**:
|
||||
**Request**: "Create an AWS architecture diagram with Internet Gateway, VPC, ELB, EC2, and RDS"
|
||||
|
||||
**Recommended Workflow (using Python scripts)**:
|
||||
|
||||
```bash
|
||||
# Step 1: Create base diagram file with title
|
||||
# Create my-aws-diagram.excalidraw with basic structure (title, etc.)
|
||||
|
||||
# Step 2: Check icon availability
|
||||
# Read: libraries/aws-architecture-icons/reference.md
|
||||
# Confirm icons exist: Internet-gateway, VPC, ELB, EC2, RDS
|
||||
|
||||
# Step 3: Add icons with Python script
|
||||
python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw "Internet-gateway" 150 100 --label "Internet Gateway"
|
||||
python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw VPC 200 200
|
||||
python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw ELB 350 250 --label "Load Balancer"
|
||||
python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw EC2 500 300 --label "Web Server"
|
||||
python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw RDS 650 350 --label "Database"
|
||||
|
||||
# Step 4: Add connecting arrows
|
||||
python scripts/add-arrow.py my-aws-diagram.excalidraw 200 150 250 200 # Internet → VPC
|
||||
python scripts/add-arrow.py my-aws-diagram.excalidraw 265 230 350 250 # VPC → ELB
|
||||
python scripts/add-arrow.py my-aws-diagram.excalidraw 415 280 500 300 # ELB → EC2
|
||||
python scripts/add-arrow.py my-aws-diagram.excalidraw 565 330 650 350 --label "SQL" --style dashed
|
||||
|
||||
# Result: Complete diagram with professional AWS icons, labels, and connections
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- No manual coordinate calculation
|
||||
- No token consumption for icon data
|
||||
- Deterministic, reliable results
|
||||
- Easy to iterate and adjust positions
|
||||
|
||||
**Alternative Workflow (manual, if scripts unavailable)**:
|
||||
1. Check: `libraries/aws-architecture-icons/reference.md` exists → Yes
|
||||
2. Read reference.md → Find entries for Internet-gateway, VPC, ELB, EC2, RDS
|
||||
3. Load:
|
||||
- `icons/Internet-gateway.json` (298 lines)
|
||||
- `icons/VPC.json` (550 lines)
|
||||
- `icons/ELB.json` (363 lines)
|
||||
- `icons/EC2.json` (231 lines)
|
||||
- `icons/RDS.json` (similar size)
|
||||
**Total: ~2000+ lines of JSON to process**
|
||||
4. Extract elements from each JSON
|
||||
5. Calculate bounding boxes and offsets for each icon
|
||||
6. Transform all coordinates (x, y) for positioning
|
||||
7. Generate unique IDs for all elements
|
||||
8. Add arrows showing data flow
|
||||
9. Add text labels
|
||||
10. Generate final `.excalidraw` file
|
||||
|
||||
**Challenges with manual approach**:
|
||||
- High token consumption (~2000-5000 lines)
|
||||
- Complex coordinate math
|
||||
- Risk of ID conflicts
|
||||
|
||||
### Supported Icon Libraries (Examples — verify availability)
|
||||
|
||||
- This workflow works with any valid `.excalidrawlib` file you provide.
|
||||
- Examples of library categories you may find on https://libraries.excalidraw.com/:
|
||||
- Cloud service icons
|
||||
- Kubernetes / infrastructure icons
|
||||
- UI / Material icons
|
||||
- Flowchart / diagram symbols
|
||||
- Network diagram icons
|
||||
- Availability and naming can change; verify exact library names on the site before use.
|
||||
|
||||
### Fallback: No Icons Available
|
||||
|
||||
**If no icon libraries are set up:**
|
||||
- Create diagrams using basic shapes (rectangles, ellipses, arrows)
|
||||
- Use color coding and text labels to distinguish components
|
||||
- Inform user they can add icons later or set up libraries for future diagrams
|
||||
- The diagram will still be functional and clear, just less visually polished
|
||||
|
||||
## References
|
||||
|
||||
See bundled references for:
|
||||
- `references/excalidraw-schema.md` - Complete Excalidraw JSON schema
|
||||
- `references/element-types.md` - Detailed element type specifications
|
||||
- `templates/flowchart-template.json` - Basic flowchart starter
|
||||
- `templates/relationship-template.json` - Relationship diagram starter
|
||||
- `templates/mindmap-template.json` - Mind map starter
|
||||
- `scripts/split-excalidraw-library.py` - Tool to split `.excalidrawlib` files
|
||||
- `scripts/README.md` - Documentation for library tools
|
||||
|
||||
## Limitations
|
||||
|
||||
- Complex curves are simplified to straight/basic curved lines
|
||||
- Hand-drawn roughness is set to default (1)
|
||||
- No embedded images support in auto-generation
|
||||
- Maximum recommended elements: 20 per diagram
|
||||
- No automatic collision detection (use spacing guidelines)
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential improvements:
|
||||
- Auto-layout optimization algorithms
|
||||
- Import from Mermaid/PlantUML syntax
|
||||
- Template library expansion
|
||||
- Interactive editing after generation
|
||||
497
skills/excalidraw-diagram-generator/references/element-types.md
Normal file
497
skills/excalidraw-diagram-generator/references/element-types.md
Normal file
@@ -0,0 +1,497 @@
|
||||
# Excalidraw Element Types Guide
|
||||
|
||||
Detailed specifications for each Excalidraw element type with visual examples and use cases.
|
||||
|
||||
## Element Type Overview
|
||||
|
||||
| Type | Visual | Primary Use | Text Support |
|
||||
|------|--------|-------------|--------------|
|
||||
| `rectangle` | □ | Boxes, containers, process steps | ✅ Yes |
|
||||
| `ellipse` | ○ | Emphasis, terminals, states | ✅ Yes |
|
||||
| `diamond` | ◇ | Decision points, choices | ✅ Yes |
|
||||
| `arrow` | → | Directional flow, relationships | ❌ No (use separate text) |
|
||||
| `line` | — | Connections, dividers | ❌ No |
|
||||
| `text` | A | Labels, annotations, titles | ✅ (Its purpose) |
|
||||
|
||||
---
|
||||
|
||||
## Rectangle
|
||||
|
||||
**Best for:** Process steps, entities, data stores, components
|
||||
|
||||
### Properties
|
||||
|
||||
```typescript
|
||||
{
|
||||
type: "rectangle",
|
||||
roundness: { type: 3 }, // Rounded corners
|
||||
text: "Step Name", // Optional embedded text
|
||||
fontSize: 20,
|
||||
textAlign: "center",
|
||||
verticalAlign: "middle"
|
||||
}
|
||||
```
|
||||
|
||||
### Use Cases
|
||||
|
||||
| Scenario | Configuration |
|
||||
|----------|---------------|
|
||||
| **Process step** | Green background (`#b2f2bb`), centered text |
|
||||
| **Entity/Object** | Blue background (`#a5d8ff`), medium size |
|
||||
| **System component** | Light color, descriptive text |
|
||||
| **Data store** | Gray/white, database-like label |
|
||||
|
||||
### Size Guidelines
|
||||
|
||||
| Content | Width | Height |
|
||||
|---------|-------|--------|
|
||||
| Single word | 120-150px | 60-80px |
|
||||
| Short phrase (2-4 words) | 180-220px | 80-100px |
|
||||
| Sentence | 250-300px | 100-120px |
|
||||
|
||||
### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "rectangle",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 200,
|
||||
"height": 80,
|
||||
"backgroundColor": "#b2f2bb",
|
||||
"text": "Validate Input",
|
||||
"fontSize": 20,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"roundness": { "type": 3 }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ellipse
|
||||
|
||||
**Best for:** Start/end points, states, emphasis circles
|
||||
|
||||
### Properties
|
||||
|
||||
```typescript
|
||||
{
|
||||
type: "ellipse",
|
||||
text: "Start",
|
||||
fontSize: 18,
|
||||
textAlign: "center",
|
||||
verticalAlign: "middle"
|
||||
}
|
||||
```
|
||||
|
||||
### Use Cases
|
||||
|
||||
| Scenario | Configuration |
|
||||
|----------|---------------|
|
||||
| **Flow start** | Light green, "Start" text |
|
||||
| **Flow end** | Light red, "End" text |
|
||||
| **State** | Soft color, state name |
|
||||
| **Highlight** | Bright color, emphasis text |
|
||||
|
||||
### Size Guidelines
|
||||
|
||||
For circular shapes, use `width === height`:
|
||||
|
||||
| Content | Diameter |
|
||||
|---------|----------|
|
||||
| Icon/Symbol | 60-80px |
|
||||
| Short text | 100-120px |
|
||||
| Longer text | 150-180px |
|
||||
|
||||
### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "ellipse",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 120,
|
||||
"height": 120,
|
||||
"backgroundColor": "#d0f0c0",
|
||||
"text": "Start",
|
||||
"fontSize": 18,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Diamond
|
||||
|
||||
**Best for:** Decision points, conditional branches
|
||||
|
||||
### Properties
|
||||
|
||||
```typescript
|
||||
{
|
||||
type: "diamond",
|
||||
text: "Valid?",
|
||||
fontSize: 18,
|
||||
textAlign: "center",
|
||||
verticalAlign": "middle"
|
||||
}
|
||||
```
|
||||
|
||||
### Use Cases
|
||||
|
||||
| Scenario | Text Example |
|
||||
|----------|--------------|
|
||||
| **Yes/No decision** | "Is Valid?", "Exists?" |
|
||||
| **Multiple choice** | "Type?", "Status?" |
|
||||
| **Conditional** | "Score > 50?" |
|
||||
|
||||
### Size Guidelines
|
||||
|
||||
Diamonds need more space than rectangles for the same text:
|
||||
|
||||
| Content | Width | Height |
|
||||
|---------|-------|--------|
|
||||
| Yes/No | 120-140px | 120-140px |
|
||||
| Short question | 160-180px | 160-180px |
|
||||
| Longer question | 200-220px | 200-220px |
|
||||
|
||||
### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "diamond",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 150,
|
||||
"height": 150,
|
||||
"backgroundColor": "#ffe4a3",
|
||||
"text": "Valid?",
|
||||
"fontSize": 18,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Arrow
|
||||
|
||||
**Best for:** Flow direction, relationships, dependencies
|
||||
|
||||
### Properties
|
||||
|
||||
```typescript
|
||||
{
|
||||
type: "arrow",
|
||||
points: [[0, 0], [endX, endY]], // Relative coordinates
|
||||
roundness: { type: 2 }, // Curved
|
||||
startBinding: null, // Or { elementId, focus, gap }
|
||||
endBinding: null
|
||||
}
|
||||
```
|
||||
|
||||
### Arrow Directions
|
||||
|
||||
#### Horizontal (Left to Right)
|
||||
|
||||
```json
|
||||
{
|
||||
"x": 100,
|
||||
"y": 150,
|
||||
"width": 200,
|
||||
"height": 0,
|
||||
"points": [[0, 0], [200, 0]]
|
||||
}
|
||||
```
|
||||
|
||||
#### Vertical (Top to Bottom)
|
||||
|
||||
```json
|
||||
{
|
||||
"x": 200,
|
||||
"y": 100,
|
||||
"width": 0,
|
||||
"height": 150,
|
||||
"points": [[0, 0], [0, 150]]
|
||||
}
|
||||
```
|
||||
|
||||
#### Diagonal
|
||||
|
||||
```json
|
||||
{
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 200,
|
||||
"height": 150,
|
||||
"points": [[0, 0], [200, 150]]
|
||||
}
|
||||
```
|
||||
|
||||
### Arrow Styles
|
||||
|
||||
| Style | `strokeStyle` | `strokeWidth` | Use Case |
|
||||
|-------|---------------|---------------|----------|
|
||||
| **Normal flow** | `"solid"` | 2 | Standard connections |
|
||||
| **Optional/Weak** | `"dashed"` | 2 | Optional paths |
|
||||
| **Important** | `"solid"` | 3-4 | Emphasized flow |
|
||||
| **Dotted** | `"dotted"` | 2 | Indirect relationships |
|
||||
|
||||
### Adding Arrow Labels
|
||||
|
||||
Use separate text elements positioned near arrow midpoint:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "arrow",
|
||||
"id": "arrow1",
|
||||
"x": 100,
|
||||
"y": 150,
|
||||
"points": [[0, 0], [200, 0]]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"x": 180, // Near midpoint
|
||||
"y": 130, // Above arrow
|
||||
"text": "sends",
|
||||
"fontSize": 14
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Line
|
||||
|
||||
**Best for:** Non-directional connections, dividers, borders
|
||||
|
||||
### Properties
|
||||
|
||||
```typescript
|
||||
{
|
||||
type: "line",
|
||||
points: [[0, 0], [x2, y2], [x3, y3], ...],
|
||||
roundness: null // Or { type: 2 } for curved
|
||||
}
|
||||
```
|
||||
|
||||
### Use Cases
|
||||
|
||||
| Scenario | Configuration |
|
||||
|----------|---------------|
|
||||
| **Divider** | Horizontal, thin stroke |
|
||||
| **Border** | Closed path (polygon) |
|
||||
| **Connection** | Multi-point path |
|
||||
| **Underline** | Short horizontal line |
|
||||
|
||||
### Multi-Point Line Example
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "line",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[100, 50],
|
||||
[200, 0]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Text
|
||||
|
||||
**Best for:** Labels, titles, annotations, standalone text
|
||||
|
||||
### Properties
|
||||
|
||||
```typescript
|
||||
{
|
||||
type: "text",
|
||||
text: "Label text",
|
||||
fontSize: 20,
|
||||
fontFamily: 1, // 1=Virgil, 2=Helvetica, 3=Cascadia
|
||||
textAlign: "left",
|
||||
verticalAlign: "top"
|
||||
}
|
||||
```
|
||||
|
||||
### Font Sizes by Purpose
|
||||
|
||||
| Purpose | Font Size |
|
||||
|---------|-----------|
|
||||
| **Main title** | 28-36 |
|
||||
| **Section header** | 24-28 |
|
||||
| **Element label** | 18-22 |
|
||||
| **Annotation** | 14-16 |
|
||||
| **Small note** | 12-14 |
|
||||
|
||||
### Width/Height Calculation
|
||||
|
||||
```javascript
|
||||
// Approximate width
|
||||
const width = text.length * fontSize * 0.6;
|
||||
|
||||
// Approximate height (single line)
|
||||
const height = fontSize * 1.2;
|
||||
|
||||
// Multi-line
|
||||
const lines = text.split('\n').length;
|
||||
const height = fontSize * 1.2 * lines;
|
||||
```
|
||||
|
||||
### Text Positioning
|
||||
|
||||
| Position | textAlign | verticalAlign | Use Case |
|
||||
|----------|-----------|---------------|----------|
|
||||
| **Top-left** | `"left"` | `"top"` | Default labels |
|
||||
| **Centered** | `"center"` | `"middle"` | Titles |
|
||||
| **Bottom-right** | `"right"` | `"bottom"` | Footnotes |
|
||||
|
||||
### Example: Title
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "text",
|
||||
"x": 100,
|
||||
"y": 50,
|
||||
"width": 400,
|
||||
"height": 40,
|
||||
"text": "System Architecture",
|
||||
"fontSize": 32,
|
||||
"fontFamily": 2,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top"
|
||||
}
|
||||
```
|
||||
|
||||
### Example: Annotation
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "text",
|
||||
"x": 150,
|
||||
"y": 200,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"text": "User input",
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Combining Elements
|
||||
|
||||
### Pattern: Labeled Box
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "rectangle",
|
||||
"id": "box1",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 200,
|
||||
"height": 100,
|
||||
"text": "Component",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Pattern: Connected Boxes
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "rectangle",
|
||||
"id": "box1",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 150,
|
||||
"height": 80,
|
||||
"text": "Step 1"
|
||||
},
|
||||
{
|
||||
"type": "arrow",
|
||||
"id": "arrow1",
|
||||
"x": 250,
|
||||
"y": 140,
|
||||
"points": [[0, 0], [100, 0]]
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"id": "box2",
|
||||
"x": 350,
|
||||
"y": 100,
|
||||
"width": 150,
|
||||
"height": 80,
|
||||
"text": "Step 2"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Pattern: Decision Tree
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "diamond",
|
||||
"id": "decision",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 140,
|
||||
"height": 140,
|
||||
"text": "Valid?"
|
||||
},
|
||||
{
|
||||
"type": "arrow",
|
||||
"id": "yes-arrow",
|
||||
"x": 240,
|
||||
"y": 170,
|
||||
"points": [[0, 0], [60, 0]]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "yes-label",
|
||||
"x": 250,
|
||||
"y": 150,
|
||||
"text": "Yes",
|
||||
"fontSize": 14
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"id": "yes-box",
|
||||
"x": 300,
|
||||
"y": 140,
|
||||
"width": 120,
|
||||
"height": 60,
|
||||
"text": "Process"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| When you need... | Use this element |
|
||||
|------------------|------------------|
|
||||
| Process box | `rectangle` with text |
|
||||
| Decision point | `diamond` with question |
|
||||
| Flow direction | `arrow` |
|
||||
| Start/End | `ellipse` |
|
||||
| Title/Header | `text` (large font) |
|
||||
| Annotation | `text` (small font) |
|
||||
| Non-directional link | `line` |
|
||||
| Divider | `line` (horizontal) |
|
||||
@@ -0,0 +1,350 @@
|
||||
# Excalidraw JSON Schema Reference
|
||||
|
||||
This document describes the structure of Excalidraw `.excalidraw` files for diagram generation.
|
||||
|
||||
## Top-Level Structure
|
||||
|
||||
```typescript
|
||||
interface ExcalidrawFile {
|
||||
type: "excalidraw";
|
||||
version: number; // Always 2
|
||||
source: string; // "https://excalidraw.com"
|
||||
elements: ExcalidrawElement[];
|
||||
appState: AppState;
|
||||
files: Record<string, any>; // Usually empty {}
|
||||
}
|
||||
```
|
||||
|
||||
## AppState
|
||||
|
||||
```typescript
|
||||
interface AppState {
|
||||
viewBackgroundColor: string; // Hex color, e.g., "#ffffff"
|
||||
gridSize: number; // Typically 20
|
||||
}
|
||||
```
|
||||
|
||||
## ExcalidrawElement Base Properties
|
||||
|
||||
All elements share these common properties:
|
||||
|
||||
```typescript
|
||||
interface BaseElement {
|
||||
id: string; // Unique identifier
|
||||
type: ElementType; // See Element Types below
|
||||
x: number; // X coordinate (pixels from top-left)
|
||||
y: number; // Y coordinate (pixels from top-left)
|
||||
width: number; // Width in pixels
|
||||
height: number; // Height in pixels
|
||||
angle: number; // Rotation angle in radians (usually 0)
|
||||
strokeColor: string; // Hex color, e.g., "#1e1e1e"
|
||||
backgroundColor: string; // Hex color or "transparent"
|
||||
fillStyle: "solid" | "hachure" | "cross-hatch";
|
||||
strokeWidth: number; // 1-4 typically
|
||||
strokeStyle: "solid" | "dashed" | "dotted";
|
||||
roughness: number; // 0-2, controls hand-drawn effect (1 = default)
|
||||
opacity: number; // 0-100
|
||||
groupIds: string[]; // IDs of groups this element belongs to
|
||||
frameId: null; // Usually null
|
||||
index: string; // Stacking order identifier
|
||||
roundness: Roundness | null;
|
||||
seed: number; // Random seed for deterministic rendering
|
||||
version: number; // Element version (increment on edit)
|
||||
versionNonce: number; // Random number changed on edit
|
||||
isDeleted: boolean; // Should be false
|
||||
boundElements: any; // Usually null
|
||||
updated: number; // Timestamp in milliseconds
|
||||
link: null; // External link (usually null)
|
||||
locked: boolean; // Whether element is locked
|
||||
}
|
||||
```
|
||||
|
||||
## Element Types
|
||||
|
||||
### Rectangle
|
||||
|
||||
```typescript
|
||||
interface RectangleElement extends BaseElement {
|
||||
type: "rectangle";
|
||||
roundness: { type: 3 }; // 3 = rounded corners
|
||||
text?: string; // Optional text inside
|
||||
fontSize?: number; // Font size (16-32 typical)
|
||||
fontFamily?: number; // 1 = Virgil, 2 = Helvetica, 3 = Cascadia
|
||||
textAlign?: "left" | "center" | "right";
|
||||
verticalAlign?: "top" | "middle" | "bottom";
|
||||
}
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"id": "rect1",
|
||||
"type": "rectangle",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 200,
|
||||
"height": 100,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"text": "My Box",
|
||||
"fontSize": 20,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"roundness": { "type": 3 }
|
||||
}
|
||||
```
|
||||
|
||||
### Ellipse
|
||||
|
||||
```typescript
|
||||
interface EllipseElement extends BaseElement {
|
||||
type: "ellipse";
|
||||
text?: string;
|
||||
fontSize?: number;
|
||||
fontFamily?: number;
|
||||
textAlign?: "left" | "center" | "right";
|
||||
verticalAlign?: "top" | "middle" | "bottom";
|
||||
}
|
||||
```
|
||||
|
||||
### Diamond
|
||||
|
||||
```typescript
|
||||
interface DiamondElement extends BaseElement {
|
||||
type: "diamond";
|
||||
text?: string;
|
||||
fontSize?: number;
|
||||
fontFamily?: number;
|
||||
textAlign?: "left" | "center" | "right";
|
||||
verticalAlign?: "top" | "middle" | "bottom";
|
||||
}
|
||||
```
|
||||
|
||||
### Arrow
|
||||
|
||||
```typescript
|
||||
interface ArrowElement extends BaseElement {
|
||||
type: "arrow";
|
||||
points: [number, number][]; // Array of [x, y] coordinates relative to element
|
||||
startBinding: Binding | null;
|
||||
endBinding: Binding | null;
|
||||
roundness: { type: 2 }; // 2 = curved arrow
|
||||
}
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"id": "arrow1",
|
||||
"type": "arrow",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 200,
|
||||
"height": 0,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[200, 0]
|
||||
],
|
||||
"roundness": { "type": 2 },
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
}
|
||||
```
|
||||
|
||||
**Points explanation:**
|
||||
- First point `[0, 0]` is relative to `(x, y)`
|
||||
- Subsequent points are relative to the first point
|
||||
- For straight horizontal arrow: `[[0, 0], [width, 0]]`
|
||||
- For straight vertical arrow: `[[0, 0], [0, height]]`
|
||||
|
||||
### Line
|
||||
|
||||
```typescript
|
||||
interface LineElement extends BaseElement {
|
||||
type: "line";
|
||||
points: [number, number][];
|
||||
startBinding: Binding | null;
|
||||
endBinding: Binding | null;
|
||||
roundness: { type: 2 } | null;
|
||||
}
|
||||
```
|
||||
|
||||
### Text
|
||||
|
||||
```typescript
|
||||
interface TextElement extends BaseElement {
|
||||
type: "text";
|
||||
text: string;
|
||||
fontSize: number;
|
||||
fontFamily: number; // 1-3
|
||||
textAlign: "left" | "center" | "right";
|
||||
verticalAlign: "top" | "middle" | "bottom";
|
||||
roundness: null; // Text has no roundness
|
||||
}
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"id": "text1",
|
||||
"type": "text",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 150,
|
||||
"height": 25,
|
||||
"text": "Hello World",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"roundness": null
|
||||
}
|
||||
```
|
||||
|
||||
**Width/Height calculation:**
|
||||
- Width ≈ `text.length * fontSize * 0.6`
|
||||
- Height ≈ `fontSize * 1.2 * numberOfLines`
|
||||
|
||||
## Bindings
|
||||
|
||||
Bindings connect arrows to shapes:
|
||||
|
||||
```typescript
|
||||
interface Binding {
|
||||
elementId: string; // ID of bound element
|
||||
focus: number; // -1 to 1, position along edge
|
||||
gap: number; // Distance from element edge
|
||||
}
|
||||
```
|
||||
|
||||
## Common Colors
|
||||
|
||||
| Color Name | Hex Code | Use Case |
|
||||
|------------|----------|----------|
|
||||
| Black | `#1e1e1e` | Default stroke |
|
||||
| Light Blue | `#a5d8ff` | Primary entities |
|
||||
| Light Green | `#b2f2bb` | Process steps |
|
||||
| Yellow | `#ffd43b` | Important/Central |
|
||||
| Light Red | `#ffc9c9` | Warnings/Errors |
|
||||
| Cyan | `#96f2d7` | Secondary items |
|
||||
| Transparent | `transparent` | No fill |
|
||||
| White | `#ffffff` | Background |
|
||||
|
||||
## ID Generation
|
||||
|
||||
IDs should be unique strings. Common patterns:
|
||||
|
||||
```javascript
|
||||
// Timestamp-based
|
||||
const id = Date.now().toString(36) + Math.random().toString(36).substr(2);
|
||||
|
||||
// Sequential
|
||||
const id = "element-" + counter++;
|
||||
|
||||
// Descriptive
|
||||
const id = "step-1", "entity-user", "arrow-1-to-2";
|
||||
```
|
||||
|
||||
## Seed Generation
|
||||
|
||||
Seeds are used for deterministic randomness in hand-drawn effect:
|
||||
|
||||
```javascript
|
||||
const seed = Math.floor(Math.random() * 2147483647);
|
||||
```
|
||||
|
||||
## Version and VersionNonce
|
||||
|
||||
```javascript
|
||||
const version = 1; // Increment when element is edited
|
||||
const versionNonce = Math.floor(Math.random() * 2147483647);
|
||||
```
|
||||
|
||||
## Coordinate System
|
||||
|
||||
- Origin `(0, 0)` is top-left corner
|
||||
- X increases to the right
|
||||
- Y increases downward
|
||||
- All units are in pixels
|
||||
|
||||
## Recommended Spacing
|
||||
|
||||
| Context | Spacing |
|
||||
|---------|---------|
|
||||
| Horizontal gap between elements | 200-300px |
|
||||
| Vertical gap between rows | 100-150px |
|
||||
| Minimum margin from edge | 50px |
|
||||
| Arrow-to-box clearance | 20-30px |
|
||||
|
||||
## Font Families
|
||||
|
||||
| ID | Name | Description |
|
||||
|----|------|-------------|
|
||||
| 1 | Virgil | Hand-drawn style (default) |
|
||||
| 2 | Helvetica | Clean sans-serif |
|
||||
| 3 | Cascadia | Monospace |
|
||||
|
||||
## Validation Rules
|
||||
|
||||
✅ **Required:**
|
||||
- All IDs must be unique
|
||||
- `type` must match actual element type
|
||||
- `version` must be an integer ≥ 1
|
||||
- `opacity` must be 0-100
|
||||
|
||||
⚠️ **Recommended:**
|
||||
- Keep `roughness` at 1 for consistency
|
||||
- Use `strokeWidth` of 2 for clarity
|
||||
- Set `isDeleted` to `false`
|
||||
- Set `locked` to `false`
|
||||
- Keep `frameId`, `boundElements`, `link` as `null`
|
||||
|
||||
## Complete Minimal Example
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": [
|
||||
{
|
||||
"id": "box1",
|
||||
"type": "rectangle",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 200,
|
||||
"height": 100,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a0",
|
||||
"roundness": { "type": 3 },
|
||||
"seed": 1234567890,
|
||||
"version": 1,
|
||||
"versionNonce": 987654321,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Hello",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"gridSize": 20
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
```
|
||||
193
skills/excalidraw-diagram-generator/scripts/README.md
Normal file
193
skills/excalidraw-diagram-generator/scripts/README.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# Excalidraw Library Tools
|
||||
|
||||
This directory contains scripts for working with Excalidraw libraries.
|
||||
|
||||
## split-excalidraw-library.py
|
||||
|
||||
Splits an Excalidraw library file (`*.excalidrawlib`) into individual icon JSON files for efficient token usage by AI assistants.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Python 3.6 or higher
|
||||
- No additional dependencies required (uses only standard library)
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
python split-excalidraw-library.py <path-to-library-directory>
|
||||
```
|
||||
|
||||
### Step-by-Step Workflow
|
||||
|
||||
1. **Create library directory**:
|
||||
```bash
|
||||
mkdir -p skills/excalidraw-diagram-generator/libraries/aws-architecture-icons
|
||||
```
|
||||
|
||||
2. **Download and place library file**:
|
||||
- Visit: https://libraries.excalidraw.com/
|
||||
- Search for "AWS Architecture Icons" and download the `.excalidrawlib` file
|
||||
- Rename it to match the directory name: `aws-architecture-icons.excalidrawlib`
|
||||
- Place it in the directory created in step 1
|
||||
|
||||
3. **Run the script**:
|
||||
```bash
|
||||
python skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/
|
||||
```
|
||||
|
||||
### Output Structure
|
||||
|
||||
The script creates the following structure in the library directory:
|
||||
|
||||
```
|
||||
skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/
|
||||
aws-architecture-icons.excalidrawlib # Original file (kept)
|
||||
reference.md # Generated: Quick reference table
|
||||
icons/ # Generated: Individual icon files
|
||||
API-Gateway.json
|
||||
CloudFront.json
|
||||
EC2.json
|
||||
S3.json
|
||||
...
|
||||
```
|
||||
|
||||
### What the Script Does
|
||||
|
||||
1. **Reads** the `.excalidrawlib` file
|
||||
2. **Extracts** each icon from the `libraryItems` array
|
||||
3. **Sanitizes** icon names to create valid filenames (spaces → hyphens, removes special characters)
|
||||
4. **Saves** each icon as a separate JSON file in the `icons/` directory
|
||||
5. **Generates** a `reference.md` file with a table mapping icon names to filenames
|
||||
|
||||
### Benefits
|
||||
|
||||
- **Token Efficiency**: AI can first read the lightweight `reference.md` to find relevant icons, then load only the specific icon files needed
|
||||
- **Organization**: Icons are organized in a clear directory structure
|
||||
- **Extensibility**: Users can add multiple library sets side-by-side
|
||||
|
||||
### Recommended Workflow
|
||||
|
||||
1. Download desired Excalidraw libraries from https://libraries.excalidraw.com/
|
||||
2. Run this script on each library file
|
||||
3. Move the generated folders to `../libraries/`
|
||||
4. The AI assistant will use `reference.md` files to locate and use icons efficiently
|
||||
|
||||
### Library Sources (Examples — verify availability)
|
||||
|
||||
- Examples found on https://libraries.excalidraw.com/ may include cloud/service icon sets.
|
||||
- Availability changes over time; verify the exact library names on the site before use.
|
||||
- This script works with any valid `.excalidrawlib` file you provide.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
**Error: File not found**
|
||||
- Check that the file path is correct
|
||||
- Make sure the file has a `.excalidrawlib` extension
|
||||
|
||||
**Error: Invalid library file format**
|
||||
- Ensure the file is a valid Excalidraw library file
|
||||
- Check that it contains a `libraryItems` array
|
||||
|
||||
### License Considerations
|
||||
|
||||
When using third-party icon libraries:
|
||||
- **AWS Architecture Icons**: Subject to AWS Content License
|
||||
- **GCP Icons**: Subject to Google's terms
|
||||
- **Other libraries**: Check each library's license
|
||||
|
||||
This script is for personal/organizational use. Redistribution of split icon files should comply with the original library's license terms.
|
||||
|
||||
## add-icon-to-diagram.py
|
||||
|
||||
Adds a specific icon from a split Excalidraw library into an existing `.excalidraw` diagram. The script handles coordinate translation and ID collision avoidance, and can optionally add a label under the icon.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Python 3.6 or higher
|
||||
- A diagram file (`.excalidraw`)
|
||||
- A split icon library directory (created by `split-excalidraw-library.py`)
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
python add-icon-to-diagram.py <diagram-path> <icon-name> <x> <y> [OPTIONS]
|
||||
```
|
||||
|
||||
**Options**
|
||||
- `--library-path PATH` : Path to the icon library directory (default: `aws-architecture-icons`)
|
||||
- `--label TEXT` : Add a text label below the icon
|
||||
-- `--use-edit-suffix` : Edit via `.excalidraw.edit` to avoid editor overwrite issues (enabled by default; pass `--no-use-edit-suffix` to disable)
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Add EC2 icon at position (400, 300)
|
||||
python add-icon-to-diagram.py diagram.excalidraw EC2 400 300
|
||||
|
||||
# Add VPC icon with label
|
||||
python add-icon-to-diagram.py diagram.excalidraw VPC 200 150 --label "VPC"
|
||||
|
||||
# Safe edit mode is enabled by default (avoids editor overwrite issues)
|
||||
# Use `--no-use-edit-suffix` to disable
|
||||
python add-icon-to-diagram.py diagram.excalidraw EC2 500 300
|
||||
|
||||
# Add icon from another library
|
||||
python add-icon-to-diagram.py diagram.excalidraw Compute-Engine 500 200 \
|
||||
--library-path libraries/gcp-icons --label "API Server"
|
||||
```
|
||||
|
||||
### What the Script Does
|
||||
|
||||
1. **Loads** the icon JSON from the library’s `icons/` directory
|
||||
2. **Calculates** the icon’s bounding box
|
||||
3. **Offsets** all coordinates to the target position
|
||||
4. **Generates** unique IDs for all elements and groups
|
||||
5. **Appends** the transformed elements to the diagram
|
||||
6. **(Optional)** Adds a label beneath the icon
|
||||
|
||||
---
|
||||
|
||||
## add-arrow.py
|
||||
|
||||
Adds a straight arrow between two points in an existing `.excalidraw` diagram. Supports optional labels and line styles.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Python 3.6 or higher
|
||||
- A diagram file (`.excalidraw`)
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
python add-arrow.py <diagram-path> <from-x> <from-y> <to-x> <to-y> [OPTIONS]
|
||||
```
|
||||
|
||||
**Options**
|
||||
- `--style {solid|dashed|dotted}` : Line style (default: `solid`)
|
||||
- `--color HEX` : Arrow color (default: `#1e1e1e`)
|
||||
- `--label TEXT` : Add a text label on the arrow
|
||||
-- `--use-edit-suffix` : Edit via `.excalidraw.edit` to avoid editor overwrite issues (enabled by default; pass `--no-use-edit-suffix` to disable)
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Simple arrow
|
||||
python add-arrow.py diagram.excalidraw 300 200 500 300
|
||||
|
||||
# Arrow with label
|
||||
python add-arrow.py diagram.excalidraw 300 200 500 300 --label "HTTPS"
|
||||
|
||||
# Dashed arrow with custom color
|
||||
python add-arrow.py diagram.excalidraw 400 350 600 400 --style dashed --color "#7950f2"
|
||||
|
||||
# Safe edit mode is enabled by default (avoids editor overwrite issues)
|
||||
# Use `--no-use-edit-suffix` to disable
|
||||
python add-arrow.py diagram.excalidraw 300 200 500 300
|
||||
```
|
||||
|
||||
### What the Script Does
|
||||
|
||||
1. **Creates** an arrow element from the given coordinates
|
||||
2. **(Optional)** Adds a label near the arrow midpoint
|
||||
3. **Appends** elements to the diagram
|
||||
4. **Saves** the updated file
|
||||
312
skills/excalidraw-diagram-generator/scripts/add-arrow.py
Normal file
312
skills/excalidraw-diagram-generator/scripts/add-arrow.py
Normal file
@@ -0,0 +1,312 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Add arrows (connections) between elements in Excalidraw diagrams.
|
||||
|
||||
Usage:
|
||||
python add-arrow.py <diagram_path> <from_x> <from_y> <to_x> <to_y> [OPTIONS]
|
||||
|
||||
Options:
|
||||
--style {solid|dashed|dotted} Arrow line style (default: solid)
|
||||
--color HEX Arrow color (default: #1e1e1e)
|
||||
--label TEXT Add text label on the arrow
|
||||
--use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable)
|
||||
|
||||
Examples:
|
||||
python add-arrow.py diagram.excalidraw 300 200 500 300
|
||||
python add-arrow.py diagram.excalidraw 300 200 500 300 --label "HTTP"
|
||||
python add-arrow.py diagram.excalidraw 300 200 500 300 --style dashed --color "#7950f2"
|
||||
python add-arrow.py diagram.excalidraw 300 200 500 300 --use-edit-suffix
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
def generate_unique_id() -> str:
|
||||
"""Generate a unique ID for Excalidraw elements."""
|
||||
return str(uuid.uuid4()).replace('-', '')[:16]
|
||||
|
||||
|
||||
def prepare_edit_path(diagram_path: Path, use_edit_suffix: bool) -> tuple[Path, Path | None]:
|
||||
"""
|
||||
Prepare a safe edit path to avoid editor overwrite issues.
|
||||
|
||||
Returns:
|
||||
(work_path, final_path)
|
||||
- work_path: file path to read/write during edit
|
||||
- final_path: file path to rename back to (or None if not used)
|
||||
"""
|
||||
if not use_edit_suffix:
|
||||
return diagram_path, None
|
||||
|
||||
if diagram_path.suffix != ".excalidraw":
|
||||
return diagram_path, None
|
||||
|
||||
edit_path = diagram_path.with_suffix(diagram_path.suffix + ".edit")
|
||||
|
||||
if diagram_path.exists():
|
||||
if edit_path.exists():
|
||||
raise FileExistsError(f"Edit file already exists: {edit_path}")
|
||||
diagram_path.rename(edit_path)
|
||||
|
||||
return edit_path, diagram_path
|
||||
|
||||
|
||||
def finalize_edit_path(work_path: Path, final_path: Path | None) -> None:
|
||||
"""Finalize edit by renaming .edit back to .excalidraw if needed."""
|
||||
if final_path is None:
|
||||
return
|
||||
|
||||
if final_path.exists():
|
||||
final_path.unlink()
|
||||
|
||||
work_path.rename(final_path)
|
||||
|
||||
|
||||
def create_arrow(
|
||||
from_x: float,
|
||||
from_y: float,
|
||||
to_x: float,
|
||||
to_y: float,
|
||||
style: str = "solid",
|
||||
color: str = "#1e1e1e",
|
||||
label: str = None
|
||||
) -> list:
|
||||
"""
|
||||
Create an arrow element.
|
||||
|
||||
Args:
|
||||
from_x: Starting X coordinate
|
||||
from_y: Starting Y coordinate
|
||||
to_x: Ending X coordinate
|
||||
to_y: Ending Y coordinate
|
||||
style: Line style (solid, dashed, dotted)
|
||||
color: Arrow color
|
||||
label: Optional text label on the arrow
|
||||
|
||||
Returns:
|
||||
List of elements (arrow and optional label)
|
||||
"""
|
||||
elements = []
|
||||
|
||||
# Arrow element
|
||||
arrow = {
|
||||
"id": generate_unique_id(),
|
||||
"type": "arrow",
|
||||
"x": from_x,
|
||||
"y": from_y,
|
||||
"width": to_x - from_x,
|
||||
"height": to_y - from_y,
|
||||
"angle": 0,
|
||||
"strokeColor": color,
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": style,
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": None,
|
||||
"index": "a0",
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"seed": 1000000000 + hash(f"{from_x}{from_y}{to_x}{to_y}") % 1000000000,
|
||||
"version": 1,
|
||||
"versionNonce": 2000000000 + hash(f"{from_x}{from_y}{to_x}{to_y}") % 1000000000,
|
||||
"isDeleted": False,
|
||||
"boundElements": [],
|
||||
"updated": 1738195200000,
|
||||
"link": None,
|
||||
"locked": False,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[to_x - from_x, to_y - from_y]
|
||||
],
|
||||
"startBinding": None,
|
||||
"endBinding": None,
|
||||
"startArrowhead": None,
|
||||
"endArrowhead": "arrow",
|
||||
"lastCommittedPoint": None
|
||||
}
|
||||
elements.append(arrow)
|
||||
|
||||
# Optional label
|
||||
if label:
|
||||
mid_x = (from_x + to_x) / 2 - (len(label) * 5)
|
||||
mid_y = (from_y + to_y) / 2 - 10
|
||||
|
||||
label_element = {
|
||||
"id": generate_unique_id(),
|
||||
"type": "text",
|
||||
"x": mid_x,
|
||||
"y": mid_y,
|
||||
"width": len(label) * 10,
|
||||
"height": 20,
|
||||
"angle": 0,
|
||||
"strokeColor": color,
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": None,
|
||||
"index": "a0",
|
||||
"roundness": None,
|
||||
"seed": 1000000000 + hash(label) % 1000000000,
|
||||
"version": 1,
|
||||
"versionNonce": 2000000000 + hash(label) % 1000000000,
|
||||
"isDeleted": False,
|
||||
"boundElements": [],
|
||||
"updated": 1738195200000,
|
||||
"link": None,
|
||||
"locked": False,
|
||||
"text": label,
|
||||
"fontSize": 14,
|
||||
"fontFamily": 5,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top",
|
||||
"containerId": None,
|
||||
"originalText": label,
|
||||
"autoResize": True,
|
||||
"lineHeight": 1.25
|
||||
}
|
||||
elements.append(label_element)
|
||||
|
||||
return elements
|
||||
|
||||
|
||||
def add_arrow_to_diagram(
|
||||
diagram_path: Path,
|
||||
from_x: float,
|
||||
from_y: float,
|
||||
to_x: float,
|
||||
to_y: float,
|
||||
style: str = "solid",
|
||||
color: str = "#1e1e1e",
|
||||
label: str = None
|
||||
) -> None:
|
||||
"""
|
||||
Add an arrow to an Excalidraw diagram.
|
||||
|
||||
Args:
|
||||
diagram_path: Path to the Excalidraw diagram file
|
||||
from_x: Starting X coordinate
|
||||
from_y: Starting Y coordinate
|
||||
to_x: Ending X coordinate
|
||||
to_y: Ending Y coordinate
|
||||
style: Line style (solid, dashed, dotted)
|
||||
color: Arrow color
|
||||
label: Optional text label
|
||||
"""
|
||||
print(f"Creating arrow from ({from_x}, {from_y}) to ({to_x}, {to_y})")
|
||||
arrow_elements = create_arrow(from_x, from_y, to_x, to_y, style, color, label)
|
||||
|
||||
if label:
|
||||
print(f" With label: '{label}'")
|
||||
|
||||
# Load diagram
|
||||
print(f"Loading diagram: {diagram_path}")
|
||||
with open(diagram_path, 'r', encoding='utf-8') as f:
|
||||
diagram = json.load(f)
|
||||
|
||||
# Add arrow elements
|
||||
if 'elements' not in diagram:
|
||||
diagram['elements'] = []
|
||||
|
||||
original_count = len(diagram['elements'])
|
||||
diagram['elements'].extend(arrow_elements)
|
||||
print(f" Added {len(arrow_elements)} elements (total: {original_count} -> {len(diagram['elements'])})")
|
||||
|
||||
# Save diagram
|
||||
print(f"Saving diagram")
|
||||
with open(diagram_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(diagram, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"✓ Successfully added arrow to diagram")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
if len(sys.argv) < 6:
|
||||
print("Usage: python add-arrow.py <diagram_path> <from_x> <from_y> <to_x> <to_y> [OPTIONS]")
|
||||
print("\nOptions:")
|
||||
print(" --style {solid|dashed|dotted} Line style (default: solid)")
|
||||
print(" --color HEX Color (default: #1e1e1e)")
|
||||
print(" --label TEXT Text label on arrow")
|
||||
print(" --use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable)")
|
||||
print("\nExamples:")
|
||||
print(" python add-arrow.py diagram.excalidraw 300 200 500 300")
|
||||
print(" python add-arrow.py diagram.excalidraw 300 200 500 300 --label 'HTTP'")
|
||||
sys.exit(1)
|
||||
|
||||
diagram_path = Path(sys.argv[1])
|
||||
from_x = float(sys.argv[2])
|
||||
from_y = float(sys.argv[3])
|
||||
to_x = float(sys.argv[4])
|
||||
to_y = float(sys.argv[5])
|
||||
|
||||
# Parse optional arguments
|
||||
style = "solid"
|
||||
color = "#1e1e1e"
|
||||
label = None
|
||||
# Default: use edit suffix to avoid editor overwrite issues
|
||||
use_edit_suffix = True
|
||||
|
||||
i = 6
|
||||
while i < len(sys.argv):
|
||||
if sys.argv[i] == '--style':
|
||||
if i + 1 < len(sys.argv):
|
||||
style = sys.argv[i + 1]
|
||||
if style not in ['solid', 'dashed', 'dotted']:
|
||||
print(f"Error: Invalid style '{style}'. Must be: solid, dashed, or dotted")
|
||||
sys.exit(1)
|
||||
i += 2
|
||||
else:
|
||||
print("Error: --style requires an argument")
|
||||
sys.exit(1)
|
||||
elif sys.argv[i] == '--color':
|
||||
if i + 1 < len(sys.argv):
|
||||
color = sys.argv[i + 1]
|
||||
i += 2
|
||||
else:
|
||||
print("Error: --color requires an argument")
|
||||
sys.exit(1)
|
||||
elif sys.argv[i] == '--label':
|
||||
if i + 1 < len(sys.argv):
|
||||
label = sys.argv[i + 1]
|
||||
i += 2
|
||||
else:
|
||||
print("Error: --label requires a text argument")
|
||||
sys.exit(1)
|
||||
elif sys.argv[i] == '--use-edit-suffix':
|
||||
use_edit_suffix = True
|
||||
i += 1
|
||||
elif sys.argv[i] == '--no-use-edit-suffix':
|
||||
use_edit_suffix = False
|
||||
i += 1
|
||||
else:
|
||||
print(f"Error: Unknown option: {sys.argv[i]}")
|
||||
sys.exit(1)
|
||||
|
||||
# Validate inputs
|
||||
if not diagram_path.exists():
|
||||
print(f"Error: Diagram file not found: {diagram_path}")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
work_path, final_path = prepare_edit_path(diagram_path, use_edit_suffix)
|
||||
add_arrow_to_diagram(work_path, from_x, from_y, to_x, to_y, style, color, label)
|
||||
finalize_edit_path(work_path, final_path)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,404 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Add icons from Excalidraw libraries to diagrams.
|
||||
|
||||
This script reads an icon JSON file from an Excalidraw library, transforms its coordinates
|
||||
to a target position, generates unique IDs, and adds it to an existing Excalidraw diagram.
|
||||
Works with any Excalidraw library (AWS, GCP, Azure, Kubernetes, etc.).
|
||||
|
||||
Usage:
|
||||
python add-icon-to-diagram.py <diagram_path> <icon_name> <x> <y> [OPTIONS]
|
||||
|
||||
Options:
|
||||
--library-path PATH Path to the icon library directory (default: aws-architecture-icons)
|
||||
--label TEXT Add a text label below the icon
|
||||
--use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable)
|
||||
|
||||
Examples:
|
||||
python add-icon-to-diagram.py diagram.excalidraw EC2 500 300
|
||||
python add-icon-to-diagram.py diagram.excalidraw EC2 500 300 --label "Web Server"
|
||||
python add-icon-to-diagram.py diagram.excalidraw VPC 200 150 --library-path libraries/gcp-icons
|
||||
python add-icon-to-diagram.py diagram.excalidraw EC2 500 300 --use-edit-suffix
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any, Tuple
|
||||
|
||||
|
||||
def generate_unique_id() -> str:
|
||||
"""Generate a unique ID for Excalidraw elements."""
|
||||
return str(uuid.uuid4()).replace('-', '')[:16]
|
||||
|
||||
|
||||
def calculate_bounding_box(elements: List[Dict[str, Any]]) -> Tuple[float, float, float, float]:
|
||||
"""Calculate the bounding box (min_x, min_y, max_x, max_y) of icon elements."""
|
||||
if not elements:
|
||||
return (0, 0, 0, 0)
|
||||
|
||||
min_x = float('inf')
|
||||
min_y = float('inf')
|
||||
max_x = float('-inf')
|
||||
max_y = float('-inf')
|
||||
|
||||
for element in elements:
|
||||
if 'x' in element and 'y' in element:
|
||||
x = element['x']
|
||||
y = element['y']
|
||||
width = element.get('width', 0)
|
||||
height = element.get('height', 0)
|
||||
|
||||
min_x = min(min_x, x)
|
||||
min_y = min(min_y, y)
|
||||
max_x = max(max_x, x + width)
|
||||
max_y = max(max_y, y + height)
|
||||
|
||||
return (min_x, min_y, max_x, max_y)
|
||||
|
||||
|
||||
def transform_icon_elements(
|
||||
elements: List[Dict[str, Any]],
|
||||
target_x: float,
|
||||
target_y: float
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Transform icon elements to target coordinates with unique IDs.
|
||||
|
||||
Args:
|
||||
elements: Icon elements from JSON file
|
||||
target_x: Target X coordinate (top-left position)
|
||||
target_y: Target Y coordinate (top-left position)
|
||||
|
||||
Returns:
|
||||
Transformed elements with new coordinates and IDs
|
||||
"""
|
||||
if not elements:
|
||||
return []
|
||||
|
||||
# Calculate bounding box
|
||||
min_x, min_y, max_x, max_y = calculate_bounding_box(elements)
|
||||
|
||||
# Calculate offset
|
||||
offset_x = target_x - min_x
|
||||
offset_y = target_y - min_y
|
||||
|
||||
# Create ID mapping: old_id -> new_id
|
||||
id_mapping = {}
|
||||
for element in elements:
|
||||
if 'id' in element:
|
||||
old_id = element['id']
|
||||
id_mapping[old_id] = generate_unique_id()
|
||||
|
||||
# Create group ID mapping
|
||||
group_id_mapping = {}
|
||||
for element in elements:
|
||||
if 'groupIds' in element:
|
||||
for old_group_id in element['groupIds']:
|
||||
if old_group_id not in group_id_mapping:
|
||||
group_id_mapping[old_group_id] = generate_unique_id()
|
||||
|
||||
# Transform elements
|
||||
transformed = []
|
||||
for element in elements:
|
||||
new_element = element.copy()
|
||||
|
||||
# Update coordinates
|
||||
if 'x' in new_element:
|
||||
new_element['x'] = new_element['x'] + offset_x
|
||||
if 'y' in new_element:
|
||||
new_element['y'] = new_element['y'] + offset_y
|
||||
|
||||
# Update ID
|
||||
if 'id' in new_element:
|
||||
new_element['id'] = id_mapping[new_element['id']]
|
||||
|
||||
# Update group IDs
|
||||
if 'groupIds' in new_element:
|
||||
new_element['groupIds'] = [
|
||||
group_id_mapping[gid] for gid in new_element['groupIds']
|
||||
]
|
||||
|
||||
# Update binding references if they exist
|
||||
if 'startBinding' in new_element and new_element['startBinding']:
|
||||
if 'elementId' in new_element['startBinding']:
|
||||
old_id = new_element['startBinding']['elementId']
|
||||
if old_id in id_mapping:
|
||||
new_element['startBinding']['elementId'] = id_mapping[old_id]
|
||||
|
||||
if 'endBinding' in new_element and new_element['endBinding']:
|
||||
if 'elementId' in new_element['endBinding']:
|
||||
old_id = new_element['endBinding']['elementId']
|
||||
if old_id in id_mapping:
|
||||
new_element['endBinding']['elementId'] = id_mapping[old_id]
|
||||
|
||||
# Update containerId if it exists
|
||||
if 'containerId' in new_element and new_element['containerId']:
|
||||
old_id = new_element['containerId']
|
||||
if old_id in id_mapping:
|
||||
new_element['containerId'] = id_mapping[old_id]
|
||||
|
||||
# Update boundElements if they exist
|
||||
if 'boundElements' in new_element and new_element['boundElements']:
|
||||
new_bound_elements = []
|
||||
for bound_elem in new_element['boundElements']:
|
||||
if isinstance(bound_elem, dict) and 'id' in bound_elem:
|
||||
old_id = bound_elem['id']
|
||||
if old_id in id_mapping:
|
||||
bound_elem['id'] = id_mapping[old_id]
|
||||
new_bound_elements.append(bound_elem)
|
||||
new_element['boundElements'] = new_bound_elements
|
||||
|
||||
transformed.append(new_element)
|
||||
|
||||
return transformed
|
||||
|
||||
|
||||
def load_icon(icon_name: str, library_path: Path) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Load icon elements from library.
|
||||
|
||||
Args:
|
||||
icon_name: Name of the icon (e.g., "EC2", "VPC")
|
||||
library_path: Path to the icon library directory
|
||||
|
||||
Returns:
|
||||
List of icon elements
|
||||
"""
|
||||
icon_file = library_path / "icons" / f"{icon_name}.json"
|
||||
|
||||
if not icon_file.exists():
|
||||
raise FileNotFoundError(f"Icon file not found: {icon_file}")
|
||||
|
||||
with open(icon_file, 'r', encoding='utf-8') as f:
|
||||
icon_data = json.load(f)
|
||||
|
||||
return icon_data.get('elements', [])
|
||||
|
||||
|
||||
def prepare_edit_path(diagram_path: Path, use_edit_suffix: bool) -> tuple[Path, Path | None]:
|
||||
"""
|
||||
Prepare a safe edit path to avoid editor overwrite issues.
|
||||
|
||||
Returns:
|
||||
(work_path, final_path)
|
||||
- work_path: file path to read/write during edit
|
||||
- final_path: file path to rename back to (or None if not used)
|
||||
"""
|
||||
if not use_edit_suffix:
|
||||
return diagram_path, None
|
||||
|
||||
if diagram_path.suffix != ".excalidraw":
|
||||
return diagram_path, None
|
||||
|
||||
edit_path = diagram_path.with_suffix(diagram_path.suffix + ".edit")
|
||||
|
||||
if diagram_path.exists():
|
||||
if edit_path.exists():
|
||||
raise FileExistsError(f"Edit file already exists: {edit_path}")
|
||||
diagram_path.rename(edit_path)
|
||||
|
||||
return edit_path, diagram_path
|
||||
|
||||
|
||||
def finalize_edit_path(work_path: Path, final_path: Path | None) -> None:
|
||||
"""Finalize edit by renaming .edit back to .excalidraw if needed."""
|
||||
if final_path is None:
|
||||
return
|
||||
|
||||
if final_path.exists():
|
||||
final_path.unlink()
|
||||
|
||||
work_path.rename(final_path)
|
||||
|
||||
|
||||
def create_text_label(text: str, x: float, y: float) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a text label element.
|
||||
|
||||
Args:
|
||||
text: Label text
|
||||
x: X coordinate
|
||||
y: Y coordinate
|
||||
|
||||
Returns:
|
||||
Text element dictionary
|
||||
"""
|
||||
return {
|
||||
"id": generate_unique_id(),
|
||||
"type": "text",
|
||||
"x": x,
|
||||
"y": y,
|
||||
"width": len(text) * 10, # Approximate width
|
||||
"height": 20,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": None,
|
||||
"index": "a0",
|
||||
"roundness": None,
|
||||
"seed": 1000000000 + hash(text) % 1000000000,
|
||||
"version": 1,
|
||||
"versionNonce": 2000000000 + hash(text) % 1000000000,
|
||||
"isDeleted": False,
|
||||
"boundElements": [],
|
||||
"updated": 1738195200000,
|
||||
"link": None,
|
||||
"locked": False,
|
||||
"text": text,
|
||||
"fontSize": 16,
|
||||
"fontFamily": 5, # Excalifont
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top",
|
||||
"containerId": None,
|
||||
"originalText": text,
|
||||
"autoResize": True,
|
||||
"lineHeight": 1.25
|
||||
}
|
||||
|
||||
|
||||
def add_icon_to_diagram(
|
||||
diagram_path: Path,
|
||||
icon_name: str,
|
||||
x: float,
|
||||
y: float,
|
||||
library_path: Path,
|
||||
label: str = None
|
||||
) -> None:
|
||||
"""
|
||||
Add an icon to an Excalidraw diagram.
|
||||
|
||||
Args:
|
||||
diagram_path: Path to the Excalidraw diagram file
|
||||
icon_name: Name of the icon to add
|
||||
x: Target X coordinate
|
||||
y: Target Y coordinate
|
||||
library_path: Path to the icon library directory
|
||||
label: Optional text label to add below the icon
|
||||
"""
|
||||
# Load icon elements
|
||||
print(f"Loading icon: {icon_name}")
|
||||
icon_elements = load_icon(icon_name, library_path)
|
||||
print(f" Loaded {len(icon_elements)} elements")
|
||||
|
||||
# Transform icon elements
|
||||
print(f"Transforming to position ({x}, {y})")
|
||||
transformed_elements = transform_icon_elements(icon_elements, x, y)
|
||||
|
||||
# Calculate icon bounding box for label positioning
|
||||
if label and transformed_elements:
|
||||
min_x, min_y, max_x, max_y = calculate_bounding_box(transformed_elements)
|
||||
icon_width = max_x - min_x
|
||||
icon_height = max_y - min_y
|
||||
|
||||
# Position label below icon, centered
|
||||
label_x = min_x + (icon_width / 2) - (len(label) * 5)
|
||||
label_y = max_y + 10
|
||||
|
||||
label_element = create_text_label(label, label_x, label_y)
|
||||
transformed_elements.append(label_element)
|
||||
print(f" Added label: '{label}'")
|
||||
|
||||
# Load diagram
|
||||
print(f"Loading diagram: {diagram_path}")
|
||||
with open(diagram_path, 'r', encoding='utf-8') as f:
|
||||
diagram = json.load(f)
|
||||
|
||||
# Add transformed elements
|
||||
if 'elements' not in diagram:
|
||||
diagram['elements'] = []
|
||||
|
||||
original_count = len(diagram['elements'])
|
||||
diagram['elements'].extend(transformed_elements)
|
||||
print(f" Added {len(transformed_elements)} elements (total: {original_count} -> {len(diagram['elements'])})")
|
||||
|
||||
# Save diagram
|
||||
print(f"Saving diagram")
|
||||
with open(diagram_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(diagram, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"✓ Successfully added '{icon_name}' icon to diagram")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
if len(sys.argv) < 5:
|
||||
print("Usage: python add-icon-to-diagram.py <diagram_path> <icon_name> <x> <y> [OPTIONS]")
|
||||
print("\nOptions:")
|
||||
print(" --library-path PATH Path to icon library directory")
|
||||
print(" --label TEXT Add text label below icon")
|
||||
print(" --use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable)")
|
||||
print("\nExamples:")
|
||||
print(" python add-icon-to-diagram.py diagram.excalidraw EC2 500 300")
|
||||
print(" python add-icon-to-diagram.py diagram.excalidraw EC2 500 300 --label 'Web Server'")
|
||||
sys.exit(1)
|
||||
|
||||
diagram_path = Path(sys.argv[1])
|
||||
icon_name = sys.argv[2]
|
||||
x = float(sys.argv[3])
|
||||
y = float(sys.argv[4])
|
||||
|
||||
# Default library path
|
||||
script_dir = Path(__file__).parent
|
||||
default_library_path = script_dir.parent / "libraries" / "aws-architecture-icons"
|
||||
|
||||
# Parse optional arguments
|
||||
library_path = default_library_path
|
||||
label = None
|
||||
# Default: use edit suffix to avoid editor overwrite issues
|
||||
use_edit_suffix = True
|
||||
|
||||
i = 5
|
||||
while i < len(sys.argv):
|
||||
if sys.argv[i] == '--library-path':
|
||||
if i + 1 < len(sys.argv):
|
||||
library_path = Path(sys.argv[i + 1])
|
||||
i += 2
|
||||
else:
|
||||
print("Error: --library-path requires a path argument")
|
||||
sys.exit(1)
|
||||
elif sys.argv[i] == '--label':
|
||||
if i + 1 < len(sys.argv):
|
||||
label = sys.argv[i + 1]
|
||||
i += 2
|
||||
else:
|
||||
print("Error: --label requires a text argument")
|
||||
sys.exit(1)
|
||||
elif sys.argv[i] == '--use-edit-suffix':
|
||||
use_edit_suffix = True
|
||||
i += 1
|
||||
elif sys.argv[i] == '--no-use-edit-suffix':
|
||||
use_edit_suffix = False
|
||||
i += 1
|
||||
else:
|
||||
print(f"Error: Unknown option: {sys.argv[i]}")
|
||||
sys.exit(1)
|
||||
|
||||
# Validate inputs
|
||||
if not diagram_path.exists():
|
||||
print(f"Error: Diagram file not found: {diagram_path}")
|
||||
sys.exit(1)
|
||||
|
||||
if not library_path.exists():
|
||||
print(f"Error: Library path not found: {library_path}")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
work_path, final_path = prepare_edit_path(diagram_path, use_edit_suffix)
|
||||
add_icon_to_diagram(work_path, icon_name, x, y, library_path, label)
|
||||
finalize_edit_path(work_path, final_path)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Excalidraw Library Splitter
|
||||
|
||||
This script splits an Excalidraw library file (*.excalidrawlib) into individual
|
||||
icon JSON files and generates a reference.md file for easy lookup.
|
||||
|
||||
The script expects the following structure:
|
||||
skills/excalidraw-diagram-generator/libraries/{icon-set-name}/
|
||||
{icon-set-name}.excalidrawlib (place this file first)
|
||||
|
||||
Usage:
|
||||
python split-excalidraw-library.py <path-to-library-directory>
|
||||
|
||||
Example:
|
||||
python split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def sanitize_filename(name: str) -> str:
|
||||
"""
|
||||
Sanitize icon name to create a valid filename.
|
||||
|
||||
Args:
|
||||
name: Original icon name
|
||||
|
||||
Returns:
|
||||
Sanitized filename safe for all platforms
|
||||
"""
|
||||
# Replace spaces with hyphens
|
||||
filename = name.replace(' ', '-')
|
||||
|
||||
# Remove or replace special characters
|
||||
filename = re.sub(r'[^\w\-.]', '', filename)
|
||||
|
||||
# Remove multiple consecutive hyphens
|
||||
filename = re.sub(r'-+', '-', filename)
|
||||
|
||||
# Remove leading/trailing hyphens
|
||||
filename = filename.strip('-')
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def find_library_file(directory: Path) -> Path:
|
||||
"""
|
||||
Find the .excalidrawlib file in the given directory.
|
||||
|
||||
Args:
|
||||
directory: Directory to search
|
||||
|
||||
Returns:
|
||||
Path to the library file
|
||||
|
||||
Raises:
|
||||
SystemExit: If no library file or multiple library files found
|
||||
"""
|
||||
library_files = list(directory.glob('*.excalidrawlib'))
|
||||
|
||||
if len(library_files) == 0:
|
||||
print(f"Error: No .excalidrawlib file found in {directory}")
|
||||
print(f"Please place a .excalidrawlib file in this directory first.")
|
||||
sys.exit(1)
|
||||
|
||||
if len(library_files) > 1:
|
||||
print(f"Error: Multiple .excalidrawlib files found in {directory}")
|
||||
print(f"Please keep only one library file in the directory.")
|
||||
sys.exit(1)
|
||||
|
||||
return library_files[0]
|
||||
|
||||
|
||||
def split_library(library_dir: str) -> None:
|
||||
"""
|
||||
Split an Excalidraw library file into individual icon files.
|
||||
|
||||
Args:
|
||||
library_dir: Path to the directory containing the .excalidrawlib file
|
||||
"""
|
||||
library_dir = Path(library_dir)
|
||||
|
||||
if not library_dir.exists():
|
||||
print(f"Error: Directory not found: {library_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
if not library_dir.is_dir():
|
||||
print(f"Error: Path is not a directory: {library_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
# Find the library file
|
||||
library_path = find_library_file(library_dir)
|
||||
print(f"Found library: {library_path.name}")
|
||||
|
||||
# Load library file
|
||||
print(f"Loading library data...")
|
||||
with open(library_path, 'r', encoding='utf-8') as f:
|
||||
library_data = json.load(f)
|
||||
|
||||
# Validate library structure
|
||||
if 'libraryItems' not in library_data:
|
||||
print("Error: Invalid library file format (missing 'libraryItems')")
|
||||
sys.exit(1)
|
||||
|
||||
# Create icons directory
|
||||
icons_dir = library_dir / 'icons'
|
||||
icons_dir.mkdir(exist_ok=True)
|
||||
print(f"Output directory: {library_dir}")
|
||||
|
||||
# Process each library item (icon)
|
||||
library_items = library_data['libraryItems']
|
||||
icon_list = []
|
||||
|
||||
print(f"Processing {len(library_items)} icons...")
|
||||
|
||||
for item in library_items:
|
||||
# Get icon name
|
||||
icon_name = item.get('name', 'Unnamed')
|
||||
|
||||
# Create sanitized filename
|
||||
filename = sanitize_filename(icon_name) + '.json'
|
||||
|
||||
# Save icon data
|
||||
icon_path = icons_dir / filename
|
||||
with open(icon_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(item, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# Add to reference list
|
||||
icon_list.append({
|
||||
'name': icon_name,
|
||||
'filename': filename
|
||||
})
|
||||
|
||||
print(f" ✓ {icon_name} → {filename}")
|
||||
|
||||
# Sort icon list by name
|
||||
icon_list.sort(key=lambda x: x['name'])
|
||||
|
||||
# Generate reference.md
|
||||
library_name = library_path.stem
|
||||
reference_path = library_dir / 'reference.md'
|
||||
with open(reference_path, 'w', encoding='utf-8') as f:
|
||||
f.write(f"# {library_name} Reference\n\n")
|
||||
f.write(f"This directory contains {len(icon_list)} icons extracted from `{library_path.name}`.\n\n")
|
||||
f.write("## Available Icons\n\n")
|
||||
f.write("| Icon Name | Filename |\n")
|
||||
f.write("|-----------|----------|\n")
|
||||
|
||||
for icon in icon_list:
|
||||
f.write(f"| {icon['name']} | `icons/{icon['filename']}` |\n")
|
||||
|
||||
f.write("\n## Usage\n\n")
|
||||
f.write("Each icon JSON file contains the complete `elements` array needed to render that icon in Excalidraw.\n")
|
||||
f.write("You can copy the elements from these files into your Excalidraw diagrams.\n")
|
||||
|
||||
print(f"\n✅ Successfully split library into {len(icon_list)} icons")
|
||||
print(f"📄 Reference file created: {reference_path}")
|
||||
print(f"📁 Icons directory: {icons_dir}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
# Ensure consistent UTF-8 output on Windows consoles.
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: python split-excalidraw-library.py <path-to-library-directory>")
|
||||
print("\nExample:")
|
||||
print(" python split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/")
|
||||
print("\nNote: The directory should contain a .excalidrawlib file.")
|
||||
sys.exit(1)
|
||||
|
||||
library_dir = sys.argv[1]
|
||||
split_library(library_dir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,334 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": [
|
||||
{
|
||||
"id": "title",
|
||||
"type": "text",
|
||||
"x": 200,
|
||||
"y": 50,
|
||||
"width": 300,
|
||||
"height": 30,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a0",
|
||||
"roundness": null,
|
||||
"seed": 2001001001,
|
||||
"version": 1,
|
||||
"versionNonce": 3002002001,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Business Process Flow",
|
||||
"fontSize": 24,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "lane-header-1",
|
||||
"type": "rectangle",
|
||||
"x": 100,
|
||||
"y": 120,
|
||||
"width": 200,
|
||||
"height": 50,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#e7f5ff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a1",
|
||||
"roundness": null,
|
||||
"seed": 2001001002,
|
||||
"version": 1,
|
||||
"versionNonce": 3002002002,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Customer",
|
||||
"fontSize": 18,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "lane-1",
|
||||
"type": "rectangle",
|
||||
"x": 100,
|
||||
"y": 170,
|
||||
"width": 200,
|
||||
"height": 250,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a2",
|
||||
"roundness": null,
|
||||
"seed": 2001001003,
|
||||
"version": 1,
|
||||
"versionNonce": 3002002003,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "process-1",
|
||||
"type": "rectangle",
|
||||
"x": 130,
|
||||
"y": 200,
|
||||
"width": 140,
|
||||
"height": 70,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#b2f2bb",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a3",
|
||||
"roundness": { "type": 3 },
|
||||
"seed": 2001001004,
|
||||
"version": 1,
|
||||
"versionNonce": 3002002004,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Submit\nRequest",
|
||||
"fontSize": 16,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "lane-header-2",
|
||||
"type": "rectangle",
|
||||
"x": 300,
|
||||
"y": 120,
|
||||
"width": 200,
|
||||
"height": 50,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#fff3bf",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a4",
|
||||
"roundness": null,
|
||||
"seed": 2001001005,
|
||||
"version": 1,
|
||||
"versionNonce": 3002002005,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Sales Team",
|
||||
"fontSize": 18,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "lane-2",
|
||||
"type": "rectangle",
|
||||
"x": 300,
|
||||
"y": 170,
|
||||
"width": 200,
|
||||
"height": 250,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a5",
|
||||
"roundness": null,
|
||||
"seed": 2001001006,
|
||||
"version": 1,
|
||||
"versionNonce": 3002002006,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "process-2",
|
||||
"type": "rectangle",
|
||||
"x": 330,
|
||||
"y": 200,
|
||||
"width": 140,
|
||||
"height": 70,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffd43b",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a6",
|
||||
"roundness": { "type": 3 },
|
||||
"seed": 2001001007,
|
||||
"version": 1,
|
||||
"versionNonce": 3002002007,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Review\nRequest",
|
||||
"fontSize": 16,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "cross-lane-arrow",
|
||||
"type": "arrow",
|
||||
"x": 270,
|
||||
"y": 235,
|
||||
"width": 60,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a7",
|
||||
"roundness": { "type": 2 },
|
||||
"seed": 2001001008,
|
||||
"version": 1,
|
||||
"versionNonce": 3002002008,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[60, 0]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "process-3",
|
||||
"type": "rectangle",
|
||||
"x": 330,
|
||||
"y": 310,
|
||||
"width": 140,
|
||||
"height": 70,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffd43b",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a8",
|
||||
"roundness": { "type": 3 },
|
||||
"seed": 2001001009,
|
||||
"version": 1,
|
||||
"versionNonce": 3002002009,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Approve",
|
||||
"fontSize": 16,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "within-lane-arrow",
|
||||
"type": "arrow",
|
||||
"x": 400,
|
||||
"y": 270,
|
||||
"width": 0,
|
||||
"height": 40,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a9",
|
||||
"roundness": { "type": 2 },
|
||||
"seed": 2001001010,
|
||||
"version": 1,
|
||||
"versionNonce": 3002002010,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[0, 40]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"gridSize": 20
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor",
|
||||
"elements": [
|
||||
{
|
||||
"id": "class-1",
|
||||
"type": "rectangle",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 200,
|
||||
"height": 180,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#e7f5ff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a0",
|
||||
"roundness": null,
|
||||
"seed": 3001001001,
|
||||
"version": 1,
|
||||
"versionNonce": 4002002001,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "class-name-1",
|
||||
"type": "text",
|
||||
"x": 150,
|
||||
"y": 110,
|
||||
"width": 100,
|
||||
"height": 25,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a1",
|
||||
"roundness": null,
|
||||
"seed": 3001001002,
|
||||
"version": 1,
|
||||
"versionNonce": 4002002002,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "User",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "User",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "separator-1",
|
||||
"type": "line",
|
||||
"x": 100,
|
||||
"y": 145,
|
||||
"width": 200,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a2",
|
||||
"roundness": null,
|
||||
"seed": 3001001003,
|
||||
"version": 1,
|
||||
"versionNonce": 4002002003,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
200,
|
||||
0
|
||||
]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null,
|
||||
"lastCommittedPoint": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": null
|
||||
},
|
||||
{
|
||||
"id": "attributes-1",
|
||||
"type": "text",
|
||||
"x": 110,
|
||||
"y": 155,
|
||||
"width": 180,
|
||||
"height": 50,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a3",
|
||||
"roundness": null,
|
||||
"seed": 3001001004,
|
||||
"version": 1,
|
||||
"versionNonce": 4002002004,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "- id: number\n- name: string\n- email: string",
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "- id: number\n- name: string\n- email: string",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.1904761904761905
|
||||
},
|
||||
{
|
||||
"id": "separator-2",
|
||||
"type": "line",
|
||||
"x": 100,
|
||||
"y": 215,
|
||||
"width": 200,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a4",
|
||||
"roundness": null,
|
||||
"seed": 3001001005,
|
||||
"version": 1,
|
||||
"versionNonce": 4002002005,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
200,
|
||||
0
|
||||
]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null,
|
||||
"lastCommittedPoint": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": null
|
||||
},
|
||||
{
|
||||
"id": "methods-1",
|
||||
"type": "text",
|
||||
"x": 110,
|
||||
"y": 225,
|
||||
"width": 180,
|
||||
"height": 45,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a5",
|
||||
"roundness": null,
|
||||
"seed": 3001001006,
|
||||
"version": 3,
|
||||
"versionNonce": 1660402375,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1769755991910,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "+ login(): void\n+ logout(): void\n+ updateProfile(): void",
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "+ login(): void\n+ logout(): void\n+ updateProfile(): void",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.0714285714285714
|
||||
},
|
||||
{
|
||||
"id": "class-2",
|
||||
"type": "rectangle",
|
||||
"x": 400,
|
||||
"y": 100,
|
||||
"width": 200,
|
||||
"height": 180,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#fff3bf",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a6",
|
||||
"roundness": null,
|
||||
"seed": 3001001007,
|
||||
"version": 1,
|
||||
"versionNonce": 4002002007,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "class-name-2",
|
||||
"type": "text",
|
||||
"x": 430,
|
||||
"y": 110,
|
||||
"width": 140,
|
||||
"height": 25,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a7",
|
||||
"roundness": null,
|
||||
"seed": 3001001008,
|
||||
"version": 1,
|
||||
"versionNonce": 4002002008,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "AdminUser",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "AdminUser",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "separator-3",
|
||||
"type": "line",
|
||||
"x": 400,
|
||||
"y": 145,
|
||||
"width": 200,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a8",
|
||||
"roundness": null,
|
||||
"seed": 3001001009,
|
||||
"version": 1,
|
||||
"versionNonce": 4002002009,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
200,
|
||||
0
|
||||
]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null,
|
||||
"lastCommittedPoint": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": null
|
||||
},
|
||||
{
|
||||
"id": "attributes-2",
|
||||
"type": "text",
|
||||
"x": 410,
|
||||
"y": 155,
|
||||
"width": 180,
|
||||
"height": 35,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a9",
|
||||
"roundness": null,
|
||||
"seed": 3001001010,
|
||||
"version": 1,
|
||||
"versionNonce": 4002002010,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "- role: string\n- permissions: string[]",
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "- role: string\n- permissions: string[]",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "separator-4",
|
||||
"type": "line",
|
||||
"x": 400,
|
||||
"y": 200,
|
||||
"width": 200,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "aA",
|
||||
"roundness": null,
|
||||
"seed": 3001001011,
|
||||
"version": 2,
|
||||
"versionNonce": 873024679,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1769755880046,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
200,
|
||||
0
|
||||
]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null,
|
||||
"lastCommittedPoint": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": null
|
||||
},
|
||||
{
|
||||
"id": "methods-2",
|
||||
"type": "text",
|
||||
"x": 410,
|
||||
"y": 210,
|
||||
"width": 180,
|
||||
"height": 60,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "aB",
|
||||
"roundness": null,
|
||||
"seed": 3001001012,
|
||||
"version": 2,
|
||||
"versionNonce": 1702655305,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1769755880046,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "+ manageUsers(): void\n+ assignRole(): void\n+ revokePermission(): void",
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "+ manageUsers(): void\n+ assignRole(): void\n+ revokePermission(): void",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.4285714285714286
|
||||
},
|
||||
{
|
||||
"id": "inheritance-line",
|
||||
"type": "line",
|
||||
"x": 400,
|
||||
"y": 190,
|
||||
"width": 100,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "aC",
|
||||
"roundness": null,
|
||||
"seed": 3001001013,
|
||||
"version": 18,
|
||||
"versionNonce": 1139021225,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1769755989350,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
-100,
|
||||
0
|
||||
]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null,
|
||||
"lastCommittedPoint": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": null
|
||||
},
|
||||
{
|
||||
"id": "inheritance-triangle",
|
||||
"type": "line",
|
||||
"x": 314.1999816894531,
|
||||
"y": 181.5,
|
||||
"width": 15,
|
||||
"height": 15,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "aD",
|
||||
"roundness": null,
|
||||
"seed": 3001001014,
|
||||
"version": 21,
|
||||
"versionNonce": 1468657767,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1769756005117,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
-15,
|
||||
15
|
||||
],
|
||||
[
|
||||
0,
|
||||
15
|
||||
],
|
||||
[
|
||||
0,
|
||||
0
|
||||
]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null,
|
||||
"lastCommittedPoint": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": null
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": 20,
|
||||
"gridStep": 5,
|
||||
"gridModeEnabled": false,
|
||||
"viewBackgroundColor": "#ffffff"
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": [
|
||||
{
|
||||
"id": "external-entity-1",
|
||||
"type": "rectangle",
|
||||
"x": 100,
|
||||
"y": 200,
|
||||
"width": 120,
|
||||
"height": 80,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffc9c9",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a0",
|
||||
"roundness": { "type": 3 },
|
||||
"seed": 1001001001,
|
||||
"version": 1,
|
||||
"versionNonce": 2002002002,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "User",
|
||||
"fontSize": 18,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "data-flow-1",
|
||||
"type": "arrow",
|
||||
"x": 220,
|
||||
"y": 240,
|
||||
"width": 80,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a1",
|
||||
"roundness": { "type": 2 },
|
||||
"seed": 1001001002,
|
||||
"version": 1,
|
||||
"versionNonce": 2002002003,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[80, 0]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "flow-label-1",
|
||||
"type": "text",
|
||||
"x": 230,
|
||||
"y": 220,
|
||||
"width": 80,
|
||||
"height": 20,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a2",
|
||||
"roundness": null,
|
||||
"seed": 1001001003,
|
||||
"version": 1,
|
||||
"versionNonce": 2002002004,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "input data",
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "process-1",
|
||||
"type": "ellipse",
|
||||
"x": 300,
|
||||
"y": 200,
|
||||
"width": 120,
|
||||
"height": 80,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a3",
|
||||
"roundness": null,
|
||||
"seed": 1001001004,
|
||||
"version": 1,
|
||||
"versionNonce": 2002002005,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Process\nData",
|
||||
"fontSize": 16,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "data-flow-2",
|
||||
"type": "arrow",
|
||||
"x": 420,
|
||||
"y": 240,
|
||||
"width": 80,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a4",
|
||||
"roundness": { "type": 2 },
|
||||
"seed": 1001001005,
|
||||
"version": 1,
|
||||
"versionNonce": 2002002006,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[80, 0]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "flow-label-2",
|
||||
"type": "text",
|
||||
"x": 425,
|
||||
"y": 220,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a5",
|
||||
"roundness": null,
|
||||
"seed": 1001001006,
|
||||
"version": 1,
|
||||
"versionNonce": 2002002007,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "processed data",
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "data-store-1",
|
||||
"type": "rectangle",
|
||||
"x": 500,
|
||||
"y": 200,
|
||||
"width": 150,
|
||||
"height": 80,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#96f2d7",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a6",
|
||||
"roundness": null,
|
||||
"seed": 1001001007,
|
||||
"version": 1,
|
||||
"versionNonce": 2002002008,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Data Store\n(Database)",
|
||||
"fontSize": 16,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "data-store-line",
|
||||
"type": "line",
|
||||
"x": 500,
|
||||
"y": 225,
|
||||
"width": 150,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a7",
|
||||
"roundness": null,
|
||||
"seed": 1001001008,
|
||||
"version": 1,
|
||||
"versionNonce": 2002002009,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[150, 0]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"gridSize": 20
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
@@ -0,0 +1,662 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": [
|
||||
{
|
||||
"id": "entity-1",
|
||||
"type": "rectangle",
|
||||
"x": 100,
|
||||
"y": 150,
|
||||
"width": 180,
|
||||
"height": 150,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#e7f5ff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a0",
|
||||
"roundness": null,
|
||||
"seed": 5001001001,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002001,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "entity-name-1",
|
||||
"type": "text",
|
||||
"x": 150,
|
||||
"y": 160,
|
||||
"width": 80,
|
||||
"height": 25,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a1",
|
||||
"roundness": null,
|
||||
"seed": 5001001002,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002002,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "User",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "entity-separator-1",
|
||||
"type": "line",
|
||||
"x": 100,
|
||||
"y": 195,
|
||||
"width": 180,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a2",
|
||||
"roundness": null,
|
||||
"seed": 5001001003,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002003,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[180, 0]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "attributes-1",
|
||||
"type": "text",
|
||||
"x": 110,
|
||||
"y": 205,
|
||||
"width": 160,
|
||||
"height": 80,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a3",
|
||||
"roundness": null,
|
||||
"seed": 5001001004,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002004,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "PK: user_id\nname\nemail\ncreated_at",
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "entity-2",
|
||||
"type": "rectangle",
|
||||
"x": 450,
|
||||
"y": 150,
|
||||
"width": 180,
|
||||
"height": 150,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#fff3bf",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a4",
|
||||
"roundness": null,
|
||||
"seed": 5001001005,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002005,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "entity-name-2",
|
||||
"type": "text",
|
||||
"x": 500,
|
||||
"y": 160,
|
||||
"width": 80,
|
||||
"height": 25,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a5",
|
||||
"roundness": null,
|
||||
"seed": 5001001006,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002006,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Order",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "entity-separator-2",
|
||||
"type": "line",
|
||||
"x": 450,
|
||||
"y": 195,
|
||||
"width": 180,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a6",
|
||||
"roundness": null,
|
||||
"seed": 5001001007,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002007,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[180, 0]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "attributes-2",
|
||||
"type": "text",
|
||||
"x": 460,
|
||||
"y": 205,
|
||||
"width": 160,
|
||||
"height": 80,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a7",
|
||||
"roundness": null,
|
||||
"seed": 5001001008,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002008,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "PK: order_id\nFK: user_id\ntotal_amount\norder_date",
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "relationship-line",
|
||||
"type": "line",
|
||||
"x": 280,
|
||||
"y": 225,
|
||||
"width": 170,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a8",
|
||||
"roundness": null,
|
||||
"seed": 5001001009,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002009,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[170, 0]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "cardinality-1",
|
||||
"type": "text",
|
||||
"x": 290,
|
||||
"y": 205,
|
||||
"width": 20,
|
||||
"height": 20,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a9",
|
||||
"roundness": null,
|
||||
"seed": 5001001010,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002010,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "1",
|
||||
"fontSize": 16,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "cardinality-2",
|
||||
"type": "text",
|
||||
"x": 420,
|
||||
"y": 205,
|
||||
"width": 20,
|
||||
"height": 20,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a10",
|
||||
"roundness": null,
|
||||
"seed": 5001001011,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002011,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "N",
|
||||
"fontSize": 16,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "relationship-label",
|
||||
"type": "text",
|
||||
"x": 330,
|
||||
"y": 200,
|
||||
"width": 80,
|
||||
"height": 20,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a11",
|
||||
"roundness": null,
|
||||
"seed": 5001001012,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002012,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "places",
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "entity-3",
|
||||
"type": "rectangle",
|
||||
"x": 450,
|
||||
"y": 380,
|
||||
"width": 180,
|
||||
"height": 120,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#d0f0c0",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a12",
|
||||
"roundness": null,
|
||||
"seed": 5001001013,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002013,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "entity-name-3",
|
||||
"type": "text",
|
||||
"x": 480,
|
||||
"y": 390,
|
||||
"width": 120,
|
||||
"height": 25,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a13",
|
||||
"roundness": null,
|
||||
"seed": 5001001014,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002014,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Product",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "entity-separator-3",
|
||||
"type": "line",
|
||||
"x": 450,
|
||||
"y": 425,
|
||||
"width": 180,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a14",
|
||||
"roundness": null,
|
||||
"seed": 5001001015,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002015,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[180, 0]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "attributes-3",
|
||||
"type": "text",
|
||||
"x": 460,
|
||||
"y": 435,
|
||||
"width": 160,
|
||||
"height": 50,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a15",
|
||||
"roundness": null,
|
||||
"seed": 5001001016,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002016,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "PK: product_id\nname\nprice",
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "relationship-line-2",
|
||||
"type": "line",
|
||||
"x": 540,
|
||||
"y": 300,
|
||||
"width": 0,
|
||||
"height": 80,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a16",
|
||||
"roundness": null,
|
||||
"seed": 5001001017,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002017,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[0, 80]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "cardinality-3",
|
||||
"type": "text",
|
||||
"x": 550,
|
||||
"y": 310,
|
||||
"width": 20,
|
||||
"height": 20,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a17",
|
||||
"roundness": null,
|
||||
"seed": 5001001018,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002018,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "N",
|
||||
"fontSize": 16,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "cardinality-4",
|
||||
"type": "text",
|
||||
"x": 550,
|
||||
"y": 350,
|
||||
"width": 20,
|
||||
"height": 20,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a18",
|
||||
"roundness": null,
|
||||
"seed": 5001001019,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002019,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "M",
|
||||
"fontSize": 16,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "relationship-label-2",
|
||||
"type": "text",
|
||||
"x": 490,
|
||||
"y": 330,
|
||||
"width": 80,
|
||||
"height": 20,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a19",
|
||||
"roundness": null,
|
||||
"seed": 5001001020,
|
||||
"version": 1,
|
||||
"versionNonce": 6002002020,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "contains",
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top"
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"gridSize": 20
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": [
|
||||
{
|
||||
"id": "step1",
|
||||
"type": "rectangle",
|
||||
"x": 400,
|
||||
"y": 200,
|
||||
"width": 200,
|
||||
"height": 80,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#b2f2bb",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a0",
|
||||
"roundness": { "type": 3 },
|
||||
"seed": 1234567890,
|
||||
"version": 1,
|
||||
"versionNonce": 987654321,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Step 1",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "arrow1",
|
||||
"type": "arrow",
|
||||
"x": 500,
|
||||
"y": 280,
|
||||
"width": 0,
|
||||
"height": 100,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a1",
|
||||
"roundness": { "type": 2 },
|
||||
"seed": 1234567891,
|
||||
"version": 1,
|
||||
"versionNonce": 987654322,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[0, 100]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "step2",
|
||||
"type": "rectangle",
|
||||
"x": 400,
|
||||
"y": 380,
|
||||
"width": 200,
|
||||
"height": 80,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#b2f2bb",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a2",
|
||||
"roundness": { "type": 3 },
|
||||
"seed": 1234567892,
|
||||
"version": 1,
|
||||
"versionNonce": 987654323,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Step 2",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "arrow2",
|
||||
"type": "arrow",
|
||||
"x": 500,
|
||||
"y": 460,
|
||||
"width": 0,
|
||||
"height": 100,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a3",
|
||||
"roundness": { "type": 2 },
|
||||
"seed": 1234567893,
|
||||
"version": 1,
|
||||
"versionNonce": 987654324,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[0, 100]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "step3",
|
||||
"type": "rectangle",
|
||||
"x": 400,
|
||||
"y": 560,
|
||||
"width": 200,
|
||||
"height": 80,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#b2f2bb",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a4",
|
||||
"roundness": { "type": 3 },
|
||||
"seed": 1234567894,
|
||||
"version": 1,
|
||||
"versionNonce": 987654325,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Step 3",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"gridSize": 20
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor",
|
||||
"elements": [
|
||||
{
|
||||
"id": "center",
|
||||
"type": "rectangle",
|
||||
"x": 500,
|
||||
"y": 350,
|
||||
"width": 200,
|
||||
"height": 100,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffd43b",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a0",
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 3333333333,
|
||||
"version": 3,
|
||||
"versionNonce": 641024845,
|
||||
"isDeleted": false,
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "arrow1",
|
||||
"type": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "arrow2",
|
||||
"type": "arrow"
|
||||
}
|
||||
],
|
||||
"updated": 1769755916717,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Central Topic",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "branch1",
|
||||
"type": "rectangle",
|
||||
"x": 250,
|
||||
"y": 150,
|
||||
"width": 150,
|
||||
"height": 80,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#96f2d7",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a1",
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 3333333334,
|
||||
"version": 2,
|
||||
"versionNonce": 2040232045,
|
||||
"isDeleted": false,
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "arrow1",
|
||||
"type": "arrow"
|
||||
}
|
||||
],
|
||||
"updated": 1769755912840,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Branch 1",
|
||||
"fontSize": 18,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "arrow1",
|
||||
"type": "arrow",
|
||||
"x": 600,
|
||||
"y": 350,
|
||||
"width": 246.39999389648438,
|
||||
"height": 111.20001220703125,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a2",
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"seed": 3333333335,
|
||||
"version": 23,
|
||||
"versionNonce": 308894189,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1769755914127,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
-246.39999389648438,
|
||||
-111.20001220703125
|
||||
]
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "center",
|
||||
"focus": 0.5255972360761778,
|
||||
"gap": 1
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "branch1",
|
||||
"focus": 0.48604063201707415,
|
||||
"gap": 8.79998779296875
|
||||
},
|
||||
"lastCommittedPoint": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "branch2",
|
||||
"type": "rectangle",
|
||||
"x": 750,
|
||||
"y": 150,
|
||||
"width": 150,
|
||||
"height": 80,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#96f2d7",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a3",
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 3333333336,
|
||||
"version": 2,
|
||||
"versionNonce": 1459929741,
|
||||
"isDeleted": false,
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "arrow2",
|
||||
"type": "arrow"
|
||||
}
|
||||
],
|
||||
"updated": 1769755916716,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Branch 2",
|
||||
"fontSize": 18,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "arrow2",
|
||||
"type": "arrow",
|
||||
"x": 600,
|
||||
"y": 350,
|
||||
"width": 216,
|
||||
"height": 112.80001831054688,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a4",
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"seed": 3333333337,
|
||||
"version": 41,
|
||||
"versionNonce": 1447859213,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1769756030188,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
216,
|
||||
-112.80001831054688
|
||||
]
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "center",
|
||||
"focus": -0.48913039421990545,
|
||||
"gap": 1
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "branch2",
|
||||
"focus": -0.5368418212214556,
|
||||
"gap": 7.199981689453125
|
||||
},
|
||||
"lastCommittedPoint": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow"
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": 20,
|
||||
"gridStep": 5,
|
||||
"gridModeEnabled": false,
|
||||
"viewBackgroundColor": "#ffffff"
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": [
|
||||
{
|
||||
"id": "entity1",
|
||||
"type": "rectangle",
|
||||
"x": 300,
|
||||
"y": 300,
|
||||
"width": 180,
|
||||
"height": 100,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a0",
|
||||
"roundness": { "type": 3 },
|
||||
"seed": 1111111111,
|
||||
"version": 1,
|
||||
"versionNonce": 2222222222,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Entity A",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "entity2",
|
||||
"type": "rectangle",
|
||||
"x": 600,
|
||||
"y": 300,
|
||||
"width": 180,
|
||||
"height": 100,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a1",
|
||||
"roundness": { "type": 3 },
|
||||
"seed": 1111111112,
|
||||
"version": 1,
|
||||
"versionNonce": 2222222223,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Entity B",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "relationship",
|
||||
"type": "arrow",
|
||||
"x": 480,
|
||||
"y": 350,
|
||||
"width": 120,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a2",
|
||||
"roundness": { "type": 2 },
|
||||
"seed": 1111111113,
|
||||
"version": 1,
|
||||
"versionNonce": 2222222224,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[120, 0]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "label",
|
||||
"type": "text",
|
||||
"x": 510,
|
||||
"y": 325,
|
||||
"width": 60,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a3",
|
||||
"roundness": null,
|
||||
"seed": 1111111114,
|
||||
"version": 1,
|
||||
"versionNonce": 2222222225,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "relates to",
|
||||
"fontSize": 16,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"gridSize": 20
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": [
|
||||
{
|
||||
"id": "object-1",
|
||||
"type": "rectangle",
|
||||
"x": 150,
|
||||
"y": 100,
|
||||
"width": 120,
|
||||
"height": 50,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#e7f5ff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a0",
|
||||
"roundness": null,
|
||||
"seed": 4001001001,
|
||||
"version": 1,
|
||||
"versionNonce": 5002002001,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Client",
|
||||
"fontSize": 18,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "lifeline-1",
|
||||
"type": "line",
|
||||
"x": 210,
|
||||
"y": 150,
|
||||
"width": 0,
|
||||
"height": 300,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "dashed",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a1",
|
||||
"roundness": null,
|
||||
"seed": 4001001002,
|
||||
"version": 1,
|
||||
"versionNonce": 5002002002,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[0, 300]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "object-2",
|
||||
"type": "rectangle",
|
||||
"x": 350,
|
||||
"y": 100,
|
||||
"width": 120,
|
||||
"height": 50,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#fff3bf",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a2",
|
||||
"roundness": null,
|
||||
"seed": 4001001003,
|
||||
"version": 1,
|
||||
"versionNonce": 5002002003,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Server",
|
||||
"fontSize": 18,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "lifeline-2",
|
||||
"type": "line",
|
||||
"x": 410,
|
||||
"y": 150,
|
||||
"width": 0,
|
||||
"height": 300,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "dashed",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a3",
|
||||
"roundness": null,
|
||||
"seed": 4001001004,
|
||||
"version": 1,
|
||||
"versionNonce": 5002002004,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[0, 300]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "object-3",
|
||||
"type": "rectangle",
|
||||
"x": 550,
|
||||
"y": 100,
|
||||
"width": 120,
|
||||
"height": 50,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#d0f0c0",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a4",
|
||||
"roundness": null,
|
||||
"seed": 4001001005,
|
||||
"version": 1,
|
||||
"versionNonce": 5002002005,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Database",
|
||||
"fontSize": 18,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"id": "lifeline-3",
|
||||
"type": "line",
|
||||
"x": 610,
|
||||
"y": 150,
|
||||
"width": 0,
|
||||
"height": 300,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "dashed",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a5",
|
||||
"roundness": null,
|
||||
"seed": 4001001006,
|
||||
"version": 1,
|
||||
"versionNonce": 5002002006,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[0, 300]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "message-1",
|
||||
"type": "arrow",
|
||||
"x": 210,
|
||||
"y": 200,
|
||||
"width": 200,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a6",
|
||||
"roundness": { "type": 2 },
|
||||
"seed": 4001001007,
|
||||
"version": 1,
|
||||
"versionNonce": 5002002007,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[200, 0]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "message-label-1",
|
||||
"type": "text",
|
||||
"x": 250,
|
||||
"y": 180,
|
||||
"width": 120,
|
||||
"height": 20,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a7",
|
||||
"roundness": null,
|
||||
"seed": 4001001008,
|
||||
"version": 1,
|
||||
"versionNonce": 5002002008,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "1: request()",
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "activation-1",
|
||||
"type": "rectangle",
|
||||
"x": 405,
|
||||
"y": 200,
|
||||
"width": 10,
|
||||
"height": 80,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffd43b",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a8",
|
||||
"roundness": null,
|
||||
"seed": 4001001009,
|
||||
"version": 1,
|
||||
"versionNonce": 5002002009,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "message-2",
|
||||
"type": "arrow",
|
||||
"x": 415,
|
||||
"y": 230,
|
||||
"width": 195,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a9",
|
||||
"roundness": { "type": 2 },
|
||||
"seed": 4001001010,
|
||||
"version": 1,
|
||||
"versionNonce": 5002002010,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[195, 0]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "message-label-2",
|
||||
"type": "text",
|
||||
"x": 450,
|
||||
"y": 210,
|
||||
"width": 120,
|
||||
"height": 20,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a10",
|
||||
"roundness": null,
|
||||
"seed": 4001001011,
|
||||
"version": 1,
|
||||
"versionNonce": 5002002011,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "2: query()",
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "return-message-1",
|
||||
"type": "arrow",
|
||||
"x": 610,
|
||||
"y": 250,
|
||||
"width": 195,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "dashed",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a11",
|
||||
"roundness": { "type": 2 },
|
||||
"seed": 4001001012,
|
||||
"version": 1,
|
||||
"versionNonce": 5002002012,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[-195, 0]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "return-label-1",
|
||||
"type": "text",
|
||||
"x": 450,
|
||||
"y": 255,
|
||||
"width": 120,
|
||||
"height": 20,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a12",
|
||||
"roundness": null,
|
||||
"seed": 4001001013,
|
||||
"version": 1,
|
||||
"versionNonce": 5002002013,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "3: result",
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
},
|
||||
{
|
||||
"id": "return-message-2",
|
||||
"type": "arrow",
|
||||
"x": 410,
|
||||
"y": 280,
|
||||
"width": 200,
|
||||
"height": 0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "dashed",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a13",
|
||||
"roundness": { "type": 2 },
|
||||
"seed": 4001001014,
|
||||
"version": 1,
|
||||
"versionNonce": 5002002014,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[0, 0],
|
||||
[-200, 0]
|
||||
],
|
||||
"startBinding": null,
|
||||
"endBinding": null
|
||||
},
|
||||
{
|
||||
"id": "return-label-2",
|
||||
"type": "text",
|
||||
"x": 250,
|
||||
"y": 285,
|
||||
"width": 120,
|
||||
"height": 20,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "a14",
|
||||
"roundness": null,
|
||||
"seed": 4001001015,
|
||||
"version": 1,
|
||||
"versionNonce": 5002002015,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1706659200000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "4: response",
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"gridSize": 20
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
Reference in New Issue
Block a user