Initial commit of grid line function
This commit is contained in:
@@ -25,6 +25,11 @@ A productivity toolkit that enhances PowerPoint with specialized formatting and
|
|||||||
- **Horizontal Alignment**: Align selected objects to the left, center, or right of the slide.
|
- **Horizontal Alignment**: Align selected objects to the left, center, or right of the slide.
|
||||||
- **Vertical Alignment**: Align selected objects to the top, middle, or bottom of the slide.
|
- **Vertical Alignment**: Align selected objects to the top, middle, or bottom of the slide.
|
||||||
|
|
||||||
|
### Grid & Guidelines Tools
|
||||||
|
|
||||||
|
- **Custom Grid**: Create a customizable grid with adjustable spacing, opacity, and color.
|
||||||
|
- **Guidelines**: Add horizontal or vertical guidelines at specific positions with customizable colors.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### For End Users
|
### For End Users
|
||||||
|
|||||||
@@ -134,6 +134,40 @@ These tools align selected objects horizontally or vertically on the slide.
|
|||||||
- **Middle** - Centers all shapes vertically
|
- **Middle** - Centers all shapes vertically
|
||||||
- **Bottom** - Aligns all shapes to the bottom edge
|
- **Bottom** - Aligns all shapes to the bottom edge
|
||||||
|
|
||||||
|
## Grid & Guidelines Tools
|
||||||
|
|
||||||
|
These tools help you align and position elements precisely on slides using grids and guidelines.
|
||||||
|
|
||||||
|
### Custom Grid
|
||||||
|
|
||||||
|
Creates a customizable grid overlay on the current slide to help with precise positioning and alignment.
|
||||||
|
|
||||||
|
**How to use:**
|
||||||
|
1. Navigate to the slide where you want to add a grid
|
||||||
|
2. In the Grid & Guidelines section, configure the grid options:
|
||||||
|
- **Spacing** - Set the distance between grid lines (in points)
|
||||||
|
- **Opacity** - Adjust how visible the grid appears
|
||||||
|
- **Color** - Choose from blue, red, yellow, or green
|
||||||
|
3. Click "Create Grid" to add the grid to the current slide
|
||||||
|
4. Click "Remove Grid" to clear the grid from the slide
|
||||||
|
|
||||||
|
**Note:** The grid appears only on the current slide and is for visual reference only - it won't appear in presentation mode or exports.
|
||||||
|
|
||||||
|
### Guidelines
|
||||||
|
|
||||||
|
Add precise horizontal or vertical guidelines at specific positions on your slide.
|
||||||
|
|
||||||
|
**How to use:**
|
||||||
|
1. Navigate to the slide where you want to add guidelines
|
||||||
|
2. In the Grid & Guidelines section:
|
||||||
|
- **Position** - Set the position of the guideline (in points from top/left)
|
||||||
|
- **Type** - Choose horizontal or vertical
|
||||||
|
- **Color** - Select a color for the guideline
|
||||||
|
3. Click "Add Guideline" to add the specified guideline
|
||||||
|
4. Click "Remove All" to clear all guidelines from the slide
|
||||||
|
|
||||||
|
**Tip:** Use different colors for different types of guidelines (e.g., blue for margins, red for key alignment points).
|
||||||
|
|
||||||
## Tips and Best Practices
|
## Tips and Best Practices
|
||||||
|
|
||||||
### Selection Order Matters
|
### Selection Order Matters
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import ConfidentialButtons from "./ConfidentialButtons";
|
|||||||
import DraftButtons from "./DraftButtons";
|
import DraftButtons from "./DraftButtons";
|
||||||
import ProgressBarButtons from "./ProgressBarButtons";
|
import ProgressBarButtons from "./ProgressBarButtons";
|
||||||
import AlignmentButtons from "./AlignmentButtons";
|
import AlignmentButtons from "./AlignmentButtons";
|
||||||
|
import GridGuidelineManager from "./GridGuidelineManager";
|
||||||
import { makeStyles, Text, Subtitle1, tokens, Theme, Spinner } from "@fluentui/react-components";
|
import { makeStyles, Text, Subtitle1, tokens, Theme, Spinner } from "@fluentui/react-components";
|
||||||
import { ShapeUnionRegular, SquareRegular, InfoRegular } from "@fluentui/react-icons";
|
import { ShapeUnionRegular, SquareRegular, InfoRegular } from "@fluentui/react-icons";
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ const useStyles = makeStyles({
|
|||||||
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
|
||||||
backgroundColor: tokens.colorNeutralBackground1,
|
backgroundColor: tokens.colorNeutralBackground1,
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
|
maxHeight: "100vh",
|
||||||
},
|
},
|
||||||
statusContainer: {
|
statusContainer: {
|
||||||
marginTop: "4px",
|
marginTop: "4px",
|
||||||
@@ -194,6 +196,13 @@ const App: React.FC<AppProps> = () => {
|
|||||||
<AlignmentButtons />
|
<AlignmentButtons />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.section}>
|
||||||
|
<Subtitle1 block className={styles.sectionTitle}>
|
||||||
|
Grid & Guidelines
|
||||||
|
</Subtitle1>
|
||||||
|
<GridGuidelineManager />
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Status message area at the bottom */}
|
{/* Status message area at the bottom */}
|
||||||
{isProcessing && (
|
{isProcessing && (
|
||||||
<div className={styles.statusContainer}>
|
<div className={styles.statusContainer}>
|
||||||
|
|||||||
@@ -0,0 +1,561 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
makeStyles,
|
||||||
|
Label,
|
||||||
|
Slider,
|
||||||
|
SpinButton,
|
||||||
|
Divider,
|
||||||
|
ToggleButton,
|
||||||
|
tokens,
|
||||||
|
Input
|
||||||
|
} from "@fluentui/react-components";
|
||||||
|
import {
|
||||||
|
GridRegular,
|
||||||
|
DismissRegular,
|
||||||
|
AddRegular,
|
||||||
|
LineHorizontal3Regular,
|
||||||
|
SplitVerticalRegular,
|
||||||
|
GridDotsRegular,
|
||||||
|
ArrowResetRegular
|
||||||
|
} from "@fluentui/react-icons";
|
||||||
|
import { useStatusContext } from "./App";
|
||||||
|
import { useCommonStyles } from "./commonStyles";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
buttonGrid: {
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(2, 1fr)",
|
||||||
|
gap: "8px",
|
||||||
|
width: "100%",
|
||||||
|
marginBottom: "8px"
|
||||||
|
},
|
||||||
|
gridButton: {
|
||||||
|
width: "100%",
|
||||||
|
minWidth: 0,
|
||||||
|
padding: "8px 4px",
|
||||||
|
"& span": {
|
||||||
|
justifyContent: "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
controlRow: {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "8px",
|
||||||
|
marginBottom: "8px"
|
||||||
|
},
|
||||||
|
controlLabel: {
|
||||||
|
minWidth: "80px",
|
||||||
|
marginBottom: "0"
|
||||||
|
},
|
||||||
|
controlInput: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
guidesContainer: {
|
||||||
|
marginTop: "16px"
|
||||||
|
},
|
||||||
|
guideButtonGrid: {
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(2, 1fr)",
|
||||||
|
gap: "8px",
|
||||||
|
width: "100%",
|
||||||
|
marginTop: "8px"
|
||||||
|
},
|
||||||
|
colorButton: {
|
||||||
|
width: "24px",
|
||||||
|
height: "24px",
|
||||||
|
minWidth: "24px",
|
||||||
|
padding: "0",
|
||||||
|
border: "1px solid " + tokens.colorNeutralStroke1,
|
||||||
|
marginLeft: "8px"
|
||||||
|
},
|
||||||
|
colorButtonSelected: {
|
||||||
|
border: "2px solid " + tokens.colorBrandForeground1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
type GridColor = "blue" | "red" | "yellow" | "green";
|
||||||
|
|
||||||
|
const GRID_PREFIX = "edison_grid_";
|
||||||
|
const GUIDE_PREFIX = "edison_guide_";
|
||||||
|
|
||||||
|
const colorValues = {
|
||||||
|
blue: "#4472C4",
|
||||||
|
red: "#C00000",
|
||||||
|
yellow: "#FFC000",
|
||||||
|
green: "#70AD47"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GridGuidelineManager: React.FC = () => {
|
||||||
|
const styles = useStyles();
|
||||||
|
const commonStyles = useCommonStyles();
|
||||||
|
const {
|
||||||
|
statusMessage, setStatusMessage,
|
||||||
|
statusType, setStatusType,
|
||||||
|
isProcessing, setIsProcessing
|
||||||
|
} = useStatusContext();
|
||||||
|
|
||||||
|
// Grid state
|
||||||
|
const [gridSpacing, setGridSpacing] = React.useState(50);
|
||||||
|
const [gridColor, setGridColor] = React.useState<GridColor>("blue");
|
||||||
|
const [gridOpacity, setGridOpacity] = React.useState(30);
|
||||||
|
|
||||||
|
// Guidelines state
|
||||||
|
const [guidePosition, setGuidePosition] = React.useState(240);
|
||||||
|
const [guideDirection, setGuideDirection] = React.useState<"horizontal" | "vertical">("horizontal");
|
||||||
|
const [guideColor, setGuideColor] = React.useState<GridColor>("red");
|
||||||
|
|
||||||
|
// Helper function to create a line shape
|
||||||
|
const createLineShape = (
|
||||||
|
slide: PowerPoint.Slide,
|
||||||
|
startX: number,
|
||||||
|
startY: number,
|
||||||
|
endX: number,
|
||||||
|
endY: number
|
||||||
|
) => {
|
||||||
|
// Create a line using alternative approach with a rectangle shape
|
||||||
|
const line = slide.shapes.addGeometricShape("Rectangle");
|
||||||
|
|
||||||
|
if (startX === endX) {
|
||||||
|
// Vertical line
|
||||||
|
line.left = startX;
|
||||||
|
line.top = startY;
|
||||||
|
line.height = endY - startY;
|
||||||
|
line.width = 1; // Make it 1pt wide
|
||||||
|
} else {
|
||||||
|
// Horizontal line
|
||||||
|
line.left = startX;
|
||||||
|
line.top = startY;
|
||||||
|
line.width = endX - startX;
|
||||||
|
line.height = 1; // Make it 1pt tall
|
||||||
|
}
|
||||||
|
|
||||||
|
return line;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createGrid = async () => {
|
||||||
|
setIsProcessing(true);
|
||||||
|
try {
|
||||||
|
await PowerPoint.run(async (context) => {
|
||||||
|
// Remove any existing grid first
|
||||||
|
await removeGrid(false);
|
||||||
|
|
||||||
|
// Get the current slide using the selected slides collection
|
||||||
|
const selectedSlides = context.presentation.getSelectedSlides();
|
||||||
|
selectedSlides.load("items");
|
||||||
|
await context.sync();
|
||||||
|
|
||||||
|
if (selectedSlides.items.length === 0) {
|
||||||
|
throw new Error("No slide selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentSlide = selectedSlides.items[0];
|
||||||
|
|
||||||
|
// Standard dimensions for PowerPoint slides
|
||||||
|
const slideWidth = 960; // Width in points
|
||||||
|
const slideHeight = 540; // Height in points
|
||||||
|
|
||||||
|
// Calculate number of lines needed
|
||||||
|
const numHorizontalLines = Math.floor(slideHeight / gridSpacing);
|
||||||
|
const numVerticalLines = Math.floor(slideWidth / gridSpacing);
|
||||||
|
|
||||||
|
// Create horizontal grid lines
|
||||||
|
for (let i = 1; i <= numHorizontalLines; i++) {
|
||||||
|
const yPosition = i * gridSpacing;
|
||||||
|
|
||||||
|
// Skip if we're at the edge of the slide
|
||||||
|
if (yPosition >= slideHeight) continue;
|
||||||
|
|
||||||
|
// Create horizontal line
|
||||||
|
const line = createLineShape(currentSlide, 0, yPosition, slideWidth, yPosition);
|
||||||
|
line.name = `${GRID_PREFIX}h_${i}`;
|
||||||
|
|
||||||
|
// Set line properties
|
||||||
|
line.fill.setSolidColor(colorValues[gridColor]);
|
||||||
|
line.lineFormat.weight = 0.75; // Thin line
|
||||||
|
line.lineFormat.visible = false; // No outline, just fill
|
||||||
|
|
||||||
|
// Set transparency (opacity is inverse of transparency)
|
||||||
|
line.fill.transparency = (100 - gridOpacity) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create vertical grid lines
|
||||||
|
for (let i = 1; i <= numVerticalLines; i++) {
|
||||||
|
const xPosition = i * gridSpacing;
|
||||||
|
|
||||||
|
// Skip if we're at the edge of the slide
|
||||||
|
if (xPosition >= slideWidth) continue;
|
||||||
|
|
||||||
|
// Create vertical line
|
||||||
|
const line = createLineShape(currentSlide, xPosition, 0, xPosition, slideHeight);
|
||||||
|
line.name = `${GRID_PREFIX}v_${i}`;
|
||||||
|
|
||||||
|
// Set line properties
|
||||||
|
line.fill.setSolidColor(colorValues[gridColor]);
|
||||||
|
line.lineFormat.weight = 0.75; // Thin line
|
||||||
|
line.lineFormat.visible = false; // No outline, just fill
|
||||||
|
|
||||||
|
// Set transparency (opacity is inverse of transparency)
|
||||||
|
line.fill.transparency = (100 - gridOpacity) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatusMessage("Grid created on current slide");
|
||||||
|
setStatusType("success");
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
setStatusMessage(`Error: ${error.message}`);
|
||||||
|
setStatusType("error");
|
||||||
|
console.error("Create grid error:", error);
|
||||||
|
} finally {
|
||||||
|
setIsProcessing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeGrid = async (showStatus = true) => {
|
||||||
|
if (showStatus) {
|
||||||
|
setIsProcessing(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await PowerPoint.run(async (context) => {
|
||||||
|
// Get the current slide using the selected slides collection
|
||||||
|
const selectedSlides = context.presentation.getSelectedSlides();
|
||||||
|
selectedSlides.load("items");
|
||||||
|
await context.sync();
|
||||||
|
|
||||||
|
if (selectedSlides.items.length === 0) {
|
||||||
|
throw new Error("No slide selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentSlide = selectedSlides.items[0];
|
||||||
|
|
||||||
|
// Load all shapes on this slide
|
||||||
|
const shapes = currentSlide.shapes;
|
||||||
|
shapes.load("items");
|
||||||
|
await context.sync();
|
||||||
|
|
||||||
|
// Find shapes that are part of our grid
|
||||||
|
let removedCount = 0;
|
||||||
|
for (let i = 0; i < shapes.items.length; i++) {
|
||||||
|
const shape = shapes.items[i];
|
||||||
|
shape.load("name");
|
||||||
|
await context.sync();
|
||||||
|
|
||||||
|
// Check if this shape is part of our grid
|
||||||
|
if (shape.name && shape.name.startsWith(GRID_PREFIX)) {
|
||||||
|
shape.delete();
|
||||||
|
removedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.sync();
|
||||||
|
|
||||||
|
if (showStatus) {
|
||||||
|
if (removedCount > 0) {
|
||||||
|
setStatusMessage(`Removed grid from slide`);
|
||||||
|
} else {
|
||||||
|
setStatusMessage("No grid found to remove");
|
||||||
|
}
|
||||||
|
setStatusType("success");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (showStatus) {
|
||||||
|
setStatusMessage(`Error: ${error.message}`);
|
||||||
|
setStatusType("error");
|
||||||
|
}
|
||||||
|
console.error("Remove grid error:", error);
|
||||||
|
} finally {
|
||||||
|
if (showStatus) {
|
||||||
|
setIsProcessing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addGuideline = async () => {
|
||||||
|
setIsProcessing(true);
|
||||||
|
try {
|
||||||
|
await PowerPoint.run(async (context) => {
|
||||||
|
// Get the current slide using the selected slides collection
|
||||||
|
const selectedSlides = context.presentation.getSelectedSlides();
|
||||||
|
selectedSlides.load("items");
|
||||||
|
await context.sync();
|
||||||
|
|
||||||
|
if (selectedSlides.items.length === 0) {
|
||||||
|
throw new Error("No slide selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentSlide = selectedSlides.items[0];
|
||||||
|
|
||||||
|
// Standard dimensions for PowerPoint slides
|
||||||
|
const slideWidth = 960; // Width in points
|
||||||
|
const slideHeight = 540; // Height in points
|
||||||
|
|
||||||
|
// Create a unique identifier for this guide
|
||||||
|
const timestamp = new Date().getTime();
|
||||||
|
const guideId = `${GUIDE_PREFIX}${guideDirection}_${timestamp}`;
|
||||||
|
|
||||||
|
// Add the guide line
|
||||||
|
let line;
|
||||||
|
if (guideDirection === "horizontal") {
|
||||||
|
// Create horizontal guideline
|
||||||
|
line = createLineShape(currentSlide, 0, guidePosition, slideWidth, guidePosition);
|
||||||
|
} else {
|
||||||
|
// Create vertical guideline
|
||||||
|
line = createLineShape(currentSlide, guidePosition, 0, guidePosition, slideHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
line.name = guideId;
|
||||||
|
|
||||||
|
// Set line properties
|
||||||
|
line.fill.setSolidColor(colorValues[guideColor]);
|
||||||
|
line.lineFormat.visible = false; // No outline, just fill
|
||||||
|
|
||||||
|
// We want solid lines for guidelines
|
||||||
|
line.fill.transparency = 0;
|
||||||
|
|
||||||
|
setStatusMessage(`Added ${guideDirection} guideline`);
|
||||||
|
setStatusType("success");
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
setStatusMessage(`Error: ${error.message}`);
|
||||||
|
setStatusType("error");
|
||||||
|
console.error("Add guideline error:", error);
|
||||||
|
} finally {
|
||||||
|
setIsProcessing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeGuidelines = async () => {
|
||||||
|
setIsProcessing(true);
|
||||||
|
try {
|
||||||
|
await PowerPoint.run(async (context) => {
|
||||||
|
// Get the current slide using the selected slides collection
|
||||||
|
const selectedSlides = context.presentation.getSelectedSlides();
|
||||||
|
selectedSlides.load("items");
|
||||||
|
await context.sync();
|
||||||
|
|
||||||
|
if (selectedSlides.items.length === 0) {
|
||||||
|
throw new Error("No slide selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentSlide = selectedSlides.items[0];
|
||||||
|
|
||||||
|
// Load all shapes on this slide
|
||||||
|
const shapes = currentSlide.shapes;
|
||||||
|
shapes.load("items");
|
||||||
|
await context.sync();
|
||||||
|
|
||||||
|
// Find shapes that are guidelines
|
||||||
|
let removedCount = 0;
|
||||||
|
for (let i = 0; i < shapes.items.length; i++) {
|
||||||
|
const shape = shapes.items[i];
|
||||||
|
shape.load("name");
|
||||||
|
await context.sync();
|
||||||
|
|
||||||
|
// Check if this shape is a guideline
|
||||||
|
if (shape.name && shape.name.startsWith(GUIDE_PREFIX)) {
|
||||||
|
shape.delete();
|
||||||
|
removedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.sync();
|
||||||
|
|
||||||
|
if (removedCount > 0) {
|
||||||
|
setStatusMessage(`Removed ${removedCount} guidelines`);
|
||||||
|
} else {
|
||||||
|
setStatusMessage("No guidelines found to remove");
|
||||||
|
}
|
||||||
|
setStatusType("success");
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
setStatusMessage(`Error: ${error.message}`);
|
||||||
|
setStatusType("error");
|
||||||
|
console.error("Remove guidelines error:", error);
|
||||||
|
} finally {
|
||||||
|
setIsProcessing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Color picker button component
|
||||||
|
const ColorButton = ({ color, selectedColor, onClick }:
|
||||||
|
{ color: GridColor, selectedColor: GridColor, onClick: (color: GridColor) => void }) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={`${styles.colorButton} ${color === selectedColor ? styles.colorButtonSelected : ''}`}
|
||||||
|
style={{ backgroundColor: colorValues[color] }}
|
||||||
|
onClick={() => onClick(color)}
|
||||||
|
title={color.charAt(0).toUpperCase() + color.slice(1)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={commonStyles.container} style={{ maxHeight: "400px", overflowY: "auto" }}>
|
||||||
|
{/* Grid Controls */}
|
||||||
|
<div className={styles.buttonGrid}>
|
||||||
|
<Button
|
||||||
|
appearance="primary"
|
||||||
|
className={styles.gridButton}
|
||||||
|
onClick={createGrid}
|
||||||
|
icon={<GridDotsRegular />}
|
||||||
|
disabled={isProcessing}
|
||||||
|
title="Create Grid"
|
||||||
|
>
|
||||||
|
Create Grid
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
appearance="primary"
|
||||||
|
className={styles.gridButton}
|
||||||
|
onClick={() => removeGrid(true)}
|
||||||
|
icon={<DismissRegular />}
|
||||||
|
disabled={isProcessing}
|
||||||
|
title="Remove Grid"
|
||||||
|
>
|
||||||
|
Remove Grid
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.controlRow}>
|
||||||
|
<Label className={styles.controlLabel}>Spacing</Label>
|
||||||
|
<Input
|
||||||
|
className={styles.controlInput}
|
||||||
|
value={gridSpacing.toString()}
|
||||||
|
type="number"
|
||||||
|
min={10}
|
||||||
|
max={200}
|
||||||
|
onChange={(_event, data) => {
|
||||||
|
const value = parseInt(data.value);
|
||||||
|
if (!isNaN(value) && value >= 10 && value <= 200) {
|
||||||
|
setGridSpacing(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.controlRow}>
|
||||||
|
<Label className={styles.controlLabel}>Opacity</Label>
|
||||||
|
<div style={{ display: "flex", flex: 1, gap: "8px", alignItems: "center" }}>
|
||||||
|
<Input
|
||||||
|
className={styles.controlInput}
|
||||||
|
style={{ width: "70px" }}
|
||||||
|
value={gridOpacity.toString()}
|
||||||
|
type="number"
|
||||||
|
min={10}
|
||||||
|
max={100}
|
||||||
|
onChange={(_event, data) => {
|
||||||
|
const value = parseInt(data.value);
|
||||||
|
if (!isNaN(value) && value >= 10 && value <= 100) {
|
||||||
|
setGridOpacity(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Slider
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
value={gridOpacity}
|
||||||
|
min={10}
|
||||||
|
max={100}
|
||||||
|
step={10}
|
||||||
|
onChange={(_event, data) => setGridOpacity(data.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.controlRow}>
|
||||||
|
<Label className={styles.controlLabel}>Color</Label>
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<ColorButton color="blue" selectedColor={gridColor} onClick={setGridColor} />
|
||||||
|
<ColorButton color="red" selectedColor={gridColor} onClick={setGridColor} />
|
||||||
|
<ColorButton color="yellow" selectedColor={gridColor} onClick={setGridColor} />
|
||||||
|
<ColorButton color="green" selectedColor={gridColor} onClick={setGridColor} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Guidelines Section */}
|
||||||
|
<div className={styles.guidesContainer}>
|
||||||
|
<Divider />
|
||||||
|
<Label style={{ marginTop: "12px", marginBottom: "8px" }}>Guidelines</Label>
|
||||||
|
|
||||||
|
<div className={styles.controlRow}>
|
||||||
|
<Label className={styles.controlLabel}>Position</Label>
|
||||||
|
<Input
|
||||||
|
className={styles.controlInput}
|
||||||
|
value={guidePosition.toString()}
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
max={guideDirection === "horizontal" ? 540 : 960}
|
||||||
|
onChange={(_event, data) => {
|
||||||
|
const value = parseInt(data.value);
|
||||||
|
const maxVal = guideDirection === "horizontal" ? 540 : 960;
|
||||||
|
if (!isNaN(value) && value >= 0 && value <= maxVal) {
|
||||||
|
setGuidePosition(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.controlRow}>
|
||||||
|
<Label className={styles.controlLabel}>Type</Label>
|
||||||
|
<div style={{ display: "flex", gap: "8px" }}>
|
||||||
|
<ToggleButton
|
||||||
|
size="small"
|
||||||
|
appearance="outline"
|
||||||
|
checked={guideDirection === "horizontal"}
|
||||||
|
onClick={() => setGuideDirection("horizontal")}
|
||||||
|
icon={<LineHorizontal3Regular />}
|
||||||
|
style={{ minWidth: "40px", padding: "2px 8px" }}
|
||||||
|
>
|
||||||
|
Horizontal
|
||||||
|
</ToggleButton>
|
||||||
|
<ToggleButton
|
||||||
|
size="small"
|
||||||
|
appearance="outline"
|
||||||
|
checked={guideDirection === "vertical"}
|
||||||
|
onClick={() => setGuideDirection("vertical")}
|
||||||
|
icon={<SplitVerticalRegular />}
|
||||||
|
style={{ minWidth: "40px", padding: "2px 8px" }}
|
||||||
|
>
|
||||||
|
Vertical
|
||||||
|
</ToggleButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.controlRow}>
|
||||||
|
<Label className={styles.controlLabel}>Color</Label>
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<ColorButton color="blue" selectedColor={guideColor} onClick={setGuideColor} />
|
||||||
|
<ColorButton color="red" selectedColor={guideColor} onClick={setGuideColor} />
|
||||||
|
<ColorButton color="yellow" selectedColor={guideColor} onClick={setGuideColor} />
|
||||||
|
<ColorButton color="green" selectedColor={guideColor} onClick={setGuideColor} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.guideButtonGrid}>
|
||||||
|
<Button
|
||||||
|
appearance="primary"
|
||||||
|
className={styles.gridButton}
|
||||||
|
onClick={addGuideline}
|
||||||
|
icon={<AddRegular />}
|
||||||
|
disabled={isProcessing}
|
||||||
|
title="Add Guideline"
|
||||||
|
>
|
||||||
|
Add Guideline
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
appearance="primary"
|
||||||
|
className={styles.gridButton}
|
||||||
|
onClick={removeGuidelines}
|
||||||
|
icon={<ArrowResetRegular />}
|
||||||
|
disabled={isProcessing}
|
||||||
|
title="Remove All Guidelines"
|
||||||
|
>
|
||||||
|
Remove All
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GridGuidelineManager;
|
||||||
Reference in New Issue
Block a user