feat: Add auto-arrange button and functionality in ObjectFlowDesigner
This commit is contained in:
@@ -83,6 +83,13 @@
|
|||||||
>
|
>
|
||||||
<div class="icon-delete"></div>
|
<div class="icon-delete"></div>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
id="autoArrangeBtn"
|
||||||
|
class="icon-btn tool-btn"
|
||||||
|
title="Auto Arrange - One Click to Make Everything Look Nice and Tidy"
|
||||||
|
>
|
||||||
|
<div class="icon-auto-arrange"></div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
353
script.js
353
script.js
@@ -57,6 +57,11 @@ class ObjectFlowDesigner {
|
|||||||
.getElementById("deleteBtn")
|
.getElementById("deleteBtn")
|
||||||
.addEventListener("click", () => this.setMode("delete"));
|
.addEventListener("click", () => this.setMode("delete"));
|
||||||
|
|
||||||
|
// Auto-arrange button - one simple button to make everything tidy
|
||||||
|
document
|
||||||
|
.getElementById("autoArrangeBtn")
|
||||||
|
.addEventListener("click", () => this.autoArrangeObjects());
|
||||||
|
|
||||||
// Control buttons
|
// Control buttons
|
||||||
document
|
document
|
||||||
.getElementById("clearBtn")
|
.getElementById("clearBtn")
|
||||||
@@ -1892,6 +1897,354 @@ class ObjectFlowDesigner {
|
|||||||
this.calculateVoltageDrops();
|
this.calculateVoltageDrops();
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Simple Auto-Arrange Tool - One Button to Make Everything Tidy
|
||||||
|
getSelectedObjects() {
|
||||||
|
// For now, we'll work with all objects if none specifically selected
|
||||||
|
// Later this could be enhanced to work with multi-selection
|
||||||
|
return this.objects.filter((obj) => obj === this.selectedObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllObjects() {
|
||||||
|
return this.objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
alignObjects(direction) {
|
||||||
|
const objectsToAlign = this.getSelectedObjects();
|
||||||
|
|
||||||
|
// If no specific selection, work with all objects
|
||||||
|
if (objectsToAlign.length === 0) {
|
||||||
|
if (this.objects.length < 2) {
|
||||||
|
this.showError("Need at least 2 objects to align");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Work with all objects
|
||||||
|
this.alignAllObjects(direction);
|
||||||
|
} else if (objectsToAlign.length < 2) {
|
||||||
|
this.showError("Select at least 2 objects to align");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.alignSelectedObjects(objectsToAlign, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
alignAllObjects(direction) {
|
||||||
|
const objects = this.objects;
|
||||||
|
if (objects.length < 2) return;
|
||||||
|
|
||||||
|
// Calculate bounds
|
||||||
|
const bounds = this.calculateBounds(objects);
|
||||||
|
|
||||||
|
objects.forEach((obj) => {
|
||||||
|
switch (direction) {
|
||||||
|
case "left":
|
||||||
|
obj.x = bounds.minX;
|
||||||
|
break;
|
||||||
|
case "center":
|
||||||
|
obj.x = bounds.centerX - obj.width / 2;
|
||||||
|
break;
|
||||||
|
case "right":
|
||||||
|
obj.x = bounds.maxX - obj.width;
|
||||||
|
break;
|
||||||
|
case "top":
|
||||||
|
obj.y = bounds.minY;
|
||||||
|
break;
|
||||||
|
case "middle":
|
||||||
|
obj.y = bounds.centerY - obj.height / 2;
|
||||||
|
break;
|
||||||
|
case "bottom":
|
||||||
|
obj.y = bounds.maxY - obj.height;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snap to grid
|
||||||
|
obj.x = Math.round(obj.x / this.gridSize) * this.gridSize;
|
||||||
|
obj.y = Math.round(obj.y / this.gridSize) * this.gridSize;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
alignSelectedObjects(objects, direction) {
|
||||||
|
if (objects.length < 2) return;
|
||||||
|
|
||||||
|
// Use first object as reference
|
||||||
|
const reference = objects[0];
|
||||||
|
|
||||||
|
objects.slice(1).forEach((obj) => {
|
||||||
|
switch (direction) {
|
||||||
|
case "left":
|
||||||
|
obj.x = reference.x;
|
||||||
|
break;
|
||||||
|
case "center":
|
||||||
|
obj.x = reference.x + reference.width / 2 - obj.width / 2;
|
||||||
|
break;
|
||||||
|
case "right":
|
||||||
|
obj.x = reference.x + reference.width - obj.width;
|
||||||
|
break;
|
||||||
|
case "top":
|
||||||
|
obj.y = reference.y;
|
||||||
|
break;
|
||||||
|
case "middle":
|
||||||
|
obj.y = reference.y + reference.height / 2 - obj.height / 2;
|
||||||
|
break;
|
||||||
|
case "bottom":
|
||||||
|
obj.y = reference.y + reference.height - obj.height;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snap to grid
|
||||||
|
obj.x = Math.round(obj.x / this.gridSize) * this.gridSize;
|
||||||
|
obj.y = Math.round(obj.y / this.gridSize) * this.gridSize;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
distributeObjects(direction) {
|
||||||
|
const objects = this.objects;
|
||||||
|
if (objects.length < 3) {
|
||||||
|
this.showError("Need at least 3 objects to distribute");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort objects by position
|
||||||
|
const sortedObjects = [...objects].sort((a, b) => {
|
||||||
|
if (direction === "horizontal") {
|
||||||
|
return a.x - b.x;
|
||||||
|
} else {
|
||||||
|
return a.y - b.y;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const first = sortedObjects[0];
|
||||||
|
const last = sortedObjects[sortedObjects.length - 1];
|
||||||
|
|
||||||
|
if (direction === "horizontal") {
|
||||||
|
const totalSpace = last.x + last.width - first.x;
|
||||||
|
const objectsWidth = sortedObjects.reduce(
|
||||||
|
(sum, obj) => sum + obj.width,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const availableSpace = totalSpace - objectsWidth;
|
||||||
|
const spacing = availableSpace / (sortedObjects.length - 1);
|
||||||
|
|
||||||
|
let currentX = first.x + first.width;
|
||||||
|
for (let i = 1; i < sortedObjects.length - 1; i++) {
|
||||||
|
sortedObjects[i].x = currentX + spacing;
|
||||||
|
currentX = sortedObjects[i].x + sortedObjects[i].width;
|
||||||
|
|
||||||
|
// Snap to grid
|
||||||
|
sortedObjects[i].x =
|
||||||
|
Math.round(sortedObjects[i].x / this.gridSize) * this.gridSize;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const totalSpace = last.y + last.height - first.y;
|
||||||
|
const objectsHeight = sortedObjects.reduce(
|
||||||
|
(sum, obj) => sum + obj.height,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const availableSpace = totalSpace - objectsHeight;
|
||||||
|
const spacing = availableSpace / (sortedObjects.length - 1);
|
||||||
|
|
||||||
|
let currentY = first.y + first.height;
|
||||||
|
for (let i = 1; i < sortedObjects.length - 1; i++) {
|
||||||
|
sortedObjects[i].y = currentY + spacing;
|
||||||
|
currentY = sortedObjects[i].y + sortedObjects[i].height;
|
||||||
|
|
||||||
|
// Snap to grid
|
||||||
|
sortedObjects[i].y =
|
||||||
|
Math.round(sortedObjects[i].y / this.gridSize) * this.gridSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
autoArrangeObjects() {
|
||||||
|
if (this.objects.length === 0) return;
|
||||||
|
|
||||||
|
// Find transformer (should be at the top)
|
||||||
|
const transformer = this.objects.find((obj) => obj.type === "triangle");
|
||||||
|
|
||||||
|
if (!transformer) {
|
||||||
|
this.showError("No transformer found for auto-arrangement");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position transformer at left center (for LEFT TO RIGHT flow)
|
||||||
|
transformer.x = Math.round(80 / this.gridSize) * this.gridSize;
|
||||||
|
transformer.y =
|
||||||
|
Math.round(
|
||||||
|
(this.canvas.height / 2 - transformer.height / 2) / this.gridSize
|
||||||
|
) * this.gridSize;
|
||||||
|
|
||||||
|
// Get all nodes (non-transformer objects)
|
||||||
|
const nodes = this.objects.filter((obj) => obj.type !== "triangle");
|
||||||
|
|
||||||
|
if (nodes.length === 0) {
|
||||||
|
this.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arrange nodes in a tree-like structure based on connections
|
||||||
|
this.arrangeNodesInTree(transformer, nodes);
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
arrangeNodesInTree(transformer, nodes) {
|
||||||
|
const arranged = new Set();
|
||||||
|
const levels = [];
|
||||||
|
|
||||||
|
// Level 0: directly connected to transformer
|
||||||
|
let currentLevel = [];
|
||||||
|
transformer.connections.outputs.forEach((conn) => {
|
||||||
|
if (nodes.includes(conn.to)) {
|
||||||
|
currentLevel.push(conn.to);
|
||||||
|
arranged.add(conn.to);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentLevel.length > 0) {
|
||||||
|
levels.push(currentLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build subsequent levels
|
||||||
|
while (currentLevel.length > 0) {
|
||||||
|
const nextLevel = [];
|
||||||
|
currentLevel.forEach((node) => {
|
||||||
|
node.connections.outputs.forEach((conn) => {
|
||||||
|
if (nodes.includes(conn.to) && !arranged.has(conn.to)) {
|
||||||
|
nextLevel.push(conn.to);
|
||||||
|
arranged.add(conn.to);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (nextLevel.length > 0) {
|
||||||
|
levels.push(nextLevel);
|
||||||
|
currentLevel = nextLevel;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any remaining unconnected nodes to the last level
|
||||||
|
const unconnected = nodes.filter((node) => !arranged.has(node));
|
||||||
|
if (unconnected.length > 0) {
|
||||||
|
if (levels.length === 0) {
|
||||||
|
levels.push(unconnected);
|
||||||
|
} else {
|
||||||
|
levels[levels.length - 1].push(...unconnected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position nodes level by level - LEFT TO RIGHT layout
|
||||||
|
const levelSpacing = 200; // Horizontal spacing between columns
|
||||||
|
const minNodeSpacing = 100; // Minimum vertical spacing between nodes
|
||||||
|
|
||||||
|
levels.forEach((level, levelIndex) => {
|
||||||
|
// Calculate X position - all nodes in same level have same X (horizontally aligned)
|
||||||
|
const x =
|
||||||
|
transformer.x + transformer.width + 50 + levelIndex * levelSpacing;
|
||||||
|
|
||||||
|
// Calculate vertical positioning for this level
|
||||||
|
const nodeCount = level.length;
|
||||||
|
|
||||||
|
if (nodeCount === 1) {
|
||||||
|
// Single node - center it vertically with transformer
|
||||||
|
const y = transformer.y + transformer.height / 2 - level[0].height / 2;
|
||||||
|
level[0].y = Math.round(y / this.gridSize) * this.gridSize;
|
||||||
|
level[0].x = Math.round(x / this.gridSize) * this.gridSize;
|
||||||
|
} else {
|
||||||
|
// Multiple nodes - distribute vertically across canvas height
|
||||||
|
const canvasHeight = this.canvas.height;
|
||||||
|
const margin = 60;
|
||||||
|
const availableHeight = canvasHeight - 2 * margin;
|
||||||
|
|
||||||
|
// Calculate spacing to use full height effectively
|
||||||
|
const nodeSpacing = Math.max(
|
||||||
|
minNodeSpacing,
|
||||||
|
availableHeight / (nodeCount + 1)
|
||||||
|
);
|
||||||
|
const totalUsedHeight = (nodeCount - 1) * nodeSpacing;
|
||||||
|
const startY = margin + (availableHeight - totalUsedHeight) / 2;
|
||||||
|
|
||||||
|
// Position each node in this level
|
||||||
|
level.forEach((node, nodeIndex) => {
|
||||||
|
// All nodes in same level have same X coordinate (horizontally aligned)
|
||||||
|
node.x = Math.round(x / this.gridSize) * this.gridSize;
|
||||||
|
|
||||||
|
// Distribute nodes vertically across available height
|
||||||
|
const y = startY + nodeIndex * nodeSpacing;
|
||||||
|
node.y = Math.round(y / this.gridSize) * this.gridSize;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Optimize the tree layout to prevent overlaps and improve readability
|
||||||
|
this.optimizeTreeLayout(transformer, levels);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimize the tree layout to prevent overlaps and improve readability
|
||||||
|
optimizeTreeLayout(transformer, levels) {
|
||||||
|
levels.forEach((level, levelIndex) => {
|
||||||
|
// Ensure nodes don't overlap horizontally with previous level
|
||||||
|
if (levelIndex > 0) {
|
||||||
|
const prevLevel = levels[levelIndex - 1];
|
||||||
|
const prevMaxX = Math.max(
|
||||||
|
...prevLevel.map((node) => node.x + node.width)
|
||||||
|
);
|
||||||
|
const currentMinX = Math.min(...level.map((node) => node.x));
|
||||||
|
|
||||||
|
if (currentMinX - prevMaxX < 40) {
|
||||||
|
const adjustment = 40 - (currentMinX - prevMaxX);
|
||||||
|
level.forEach((node) => {
|
||||||
|
node.x =
|
||||||
|
Math.round((node.x + adjustment) / this.gridSize) * this.gridSize;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure no nodes go off the top or bottom edge
|
||||||
|
level.forEach((node) => {
|
||||||
|
const minY = 20;
|
||||||
|
const maxY = this.canvas.height - node.height - 20;
|
||||||
|
|
||||||
|
if (node.y < minY) {
|
||||||
|
node.y = Math.round(minY / this.gridSize) * this.gridSize;
|
||||||
|
} else if (node.y > maxY) {
|
||||||
|
node.y = Math.round(maxY / this.gridSize) * this.gridSize;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateBounds(objects) {
|
||||||
|
if (objects.length === 0) return null;
|
||||||
|
|
||||||
|
let minX = objects[0].x;
|
||||||
|
let maxX = objects[0].x + objects[0].width;
|
||||||
|
let minY = objects[0].y;
|
||||||
|
let maxY = objects[0].y + objects[0].height;
|
||||||
|
|
||||||
|
objects.forEach((obj) => {
|
||||||
|
minX = Math.min(minX, obj.x);
|
||||||
|
maxX = Math.max(maxX, obj.x + obj.width);
|
||||||
|
minY = Math.min(minY, obj.y);
|
||||||
|
maxY = Math.max(maxY, obj.y + obj.height);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
minX,
|
||||||
|
maxX,
|
||||||
|
minY,
|
||||||
|
maxY,
|
||||||
|
centerX: (minX + maxX) / 2,
|
||||||
|
centerY: (minY + maxY) / 2,
|
||||||
|
width: maxX - minX,
|
||||||
|
height: maxY - minY,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the application
|
// Initialize the application
|
||||||
|
|||||||
242
styles.css
242
styles.css
@@ -396,6 +396,248 @@ body {
|
|||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Alignment Icons */
|
||||||
|
.icon-align-left {
|
||||||
|
width: 20px;
|
||||||
|
height: 16px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-align-left::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: #4a5568;
|
||||||
|
border-radius: 1px;
|
||||||
|
box-shadow: 0 4px 0 #4a5568, 0 8px 0 #4a5568, 0 12px 0 #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn.active .icon-align-left::before {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 4px 0 white, 0 8px 0 white, 0 12px 0 white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-align-center {
|
||||||
|
width: 20px;
|
||||||
|
height: 16px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-align-center::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 2px;
|
||||||
|
width: 16px;
|
||||||
|
height: 2px;
|
||||||
|
background: #4a5568;
|
||||||
|
border-radius: 1px;
|
||||||
|
box-shadow: 0 4px 0 #4a5568, 0 8px 0 #4a5568, 0 12px 0 #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn.active .icon-align-center::before {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 4px 0 white, 0 8px 0 white, 0 12px 0 white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-align-right {
|
||||||
|
width: 20px;
|
||||||
|
height: 16px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-align-right::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: #4a5568;
|
||||||
|
border-radius: 1px;
|
||||||
|
box-shadow: 0 4px 0 #4a5568, 0 8px 0 #4a5568, 0 12px 0 #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn.active .icon-align-right::before {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 4px 0 white, 0 8px 0 white, 0 12px 0 white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-align-top {
|
||||||
|
width: 16px;
|
||||||
|
height: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-align-top::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 2px;
|
||||||
|
height: 100%;
|
||||||
|
background: #4a5568;
|
||||||
|
border-radius: 1px;
|
||||||
|
box-shadow: 4px 0 0 #4a5568, 8px 0 0 #4a5568, 12px 0 0 #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn.active .icon-align-top::before {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 4px 0 0 white, 8px 0 0 white, 12px 0 0 white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-align-middle {
|
||||||
|
width: 16px;
|
||||||
|
height: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-align-middle::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 0;
|
||||||
|
width: 2px;
|
||||||
|
height: 16px;
|
||||||
|
background: #4a5568;
|
||||||
|
border-radius: 1px;
|
||||||
|
box-shadow: 4px 0 0 #4a5568, 8px 0 0 #4a5568, 12px 0 0 #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn.active .icon-align-middle::before {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 4px 0 0 white, 8px 0 0 white, 12px 0 0 white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-align-bottom {
|
||||||
|
width: 16px;
|
||||||
|
height: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-align-bottom::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 2px;
|
||||||
|
height: 100%;
|
||||||
|
background: #4a5568;
|
||||||
|
border-radius: 1px;
|
||||||
|
box-shadow: 4px 0 0 #4a5568, 8px 0 0 #4a5568, 12px 0 0 #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn.active .icon-align-bottom::before {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 4px 0 0 white, 8px 0 0 white, 12px 0 0 white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-distribute-h {
|
||||||
|
width: 20px;
|
||||||
|
height: 16px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-distribute-h::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 7px;
|
||||||
|
left: 0;
|
||||||
|
width: 4px;
|
||||||
|
height: 2px;
|
||||||
|
background: #4a5568;
|
||||||
|
border-radius: 1px;
|
||||||
|
box-shadow: 8px 0 0 #4a5568, 16px 0 0 #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-distribute-h::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
width: 1px;
|
||||||
|
height: 12px;
|
||||||
|
background: #4a5568;
|
||||||
|
border-radius: 0.5px;
|
||||||
|
box-shadow: 8px 0 0 #4a5568, 16px 0 0 #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn.active .icon-distribute-h::before {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 8px 0 0 white, 16px 0 0 white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn.active .icon-distribute-h::after {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 8px 0 0 white, 16px 0 0 white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-distribute-v {
|
||||||
|
width: 16px;
|
||||||
|
height: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-distribute-v::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 7px;
|
||||||
|
width: 2px;
|
||||||
|
height: 4px;
|
||||||
|
background: #4a5568;
|
||||||
|
border-radius: 1px;
|
||||||
|
box-shadow: 0 8px 0 #4a5568, 0 16px 0 #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-distribute-v::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
width: 12px;
|
||||||
|
height: 1px;
|
||||||
|
background: #4a5568;
|
||||||
|
border-radius: 0.5px;
|
||||||
|
box-shadow: 0 8px 0 #4a5568, 0 16px 0 #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn.active .icon-distribute-v::before {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 8px 0 white, 0 16px 0 white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn.active .icon-distribute-v::after {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 8px 0 white, 0 16px 0 white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-auto-arrange {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-auto-arrange::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border: 2px solid #4a5568;
|
||||||
|
border-radius: 1px;
|
||||||
|
box-shadow: 10px 0 0 #4a5568, 0 10px 0 #4a5568, 10px 10px 0 #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn.active .icon-auto-arrange::before {
|
||||||
|
border-color: white;
|
||||||
|
box-shadow: 10px 0 0 white, 0 10px 0 white, 10px 10px 0 white;
|
||||||
|
}
|
||||||
|
|
||||||
.canvas-container {
|
.canvas-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
Reference in New Issue
Block a user