-
This commit is contained in:
@@ -21,11 +21,8 @@ import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
Collapse,
|
||||
TextField,
|
||||
Badge,
|
||||
Avatar,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup,
|
||||
Stack,
|
||||
alpha,
|
||||
CircularProgress,
|
||||
@@ -63,25 +60,13 @@ import {
|
||||
ExpandMore as ExpandMoreIcon,
|
||||
ExpandLess as ExpandLessIcon,
|
||||
Search as SearchIcon,
|
||||
FormatBold as BoldIcon,
|
||||
FormatItalic as ItalicIcon,
|
||||
FormatUnderlined as UnderlineIcon,
|
||||
FormatAlignLeft as AlignLeftIcon,
|
||||
FormatAlignCenter as AlignCenterIcon,
|
||||
FormatAlignRight as AlignRightIcon,
|
||||
FormatColorFill as FillColorIcon,
|
||||
FormatColorText as TextColorIcon,
|
||||
BorderColor as StrokeColorIcon,
|
||||
CloudDone as SavedIcon,
|
||||
CloudOff as UnsavedIcon,
|
||||
AspectRatio as AspectRatioIcon,
|
||||
Check as CheckIcon,
|
||||
ArrowDropDown as DropdownIcon,
|
||||
History as HistoryIcon,
|
||||
DataObject as DataBindIcon,
|
||||
AutoMode as AutoSaveIcon,
|
||||
} from "@mui/icons-material";
|
||||
import type { ElementType, AprtElement } from "../../types/report";
|
||||
import type { ElementType } from "../../types/report";
|
||||
|
||||
// Snap options type
|
||||
export interface SnapOptions {
|
||||
@@ -118,10 +103,6 @@ interface EditorToolbarProps {
|
||||
currentPageName: string;
|
||||
onPrevPage: () => void;
|
||||
onNextPage: () => void;
|
||||
// Optional: selected element for contextual toolbar
|
||||
selectedElement?: AprtElement | null;
|
||||
// Optional: callback for element updates from toolbar
|
||||
onUpdateSelectedElement?: (updates: Partial<AprtElement>) => void;
|
||||
// Optional: save status
|
||||
hasUnsavedChanges?: boolean;
|
||||
lastSavedAt?: Date | null;
|
||||
@@ -222,30 +203,6 @@ const SNAP_OPTIONS_CONFIG = [
|
||||
},
|
||||
];
|
||||
|
||||
// Color presets for quick selection
|
||||
const COLOR_PRESETS = [
|
||||
"#000000",
|
||||
"#424242",
|
||||
"#757575",
|
||||
"#ffffff",
|
||||
"#f44336",
|
||||
"#e91e63",
|
||||
"#9c27b0",
|
||||
"#673ab7",
|
||||
"#3f51b5",
|
||||
"#2196f3",
|
||||
"#03a9f4",
|
||||
"#00bcd4",
|
||||
"#009688",
|
||||
"#4caf50",
|
||||
"#8bc34a",
|
||||
"#cddc39",
|
||||
"#ffeb3b",
|
||||
"#ffc107",
|
||||
"#ff9800",
|
||||
"#ff5722",
|
||||
];
|
||||
|
||||
// Format time ago
|
||||
function formatTimeAgo(date: Date | null | undefined): string {
|
||||
if (!date) return "";
|
||||
@@ -364,109 +321,6 @@ function StyledIconButton({
|
||||
);
|
||||
}
|
||||
|
||||
// Compact color picker button
|
||||
function ColorPickerButton({
|
||||
color,
|
||||
onChange,
|
||||
label,
|
||||
icon: Icon,
|
||||
}: {
|
||||
color: string;
|
||||
onChange: (color: string) => void;
|
||||
label: string;
|
||||
icon: React.ElementType;
|
||||
}) {
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title={label} arrow>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={(e) => setAnchorEl(e.currentTarget)}
|
||||
sx={{
|
||||
borderRadius: 1.5,
|
||||
position: "relative",
|
||||
"&:hover": { bgcolor: "action.hover" },
|
||||
}}
|
||||
>
|
||||
<Icon fontSize="small" />
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: 2,
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)",
|
||||
width: 14,
|
||||
height: 3,
|
||||
bgcolor: color,
|
||||
borderRadius: 0.5,
|
||||
border: "1px solid",
|
||||
borderColor: "divider",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Popover
|
||||
open={Boolean(anchorEl)}
|
||||
anchorEl={anchorEl}
|
||||
onClose={() => setAnchorEl(null)}
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
|
||||
>
|
||||
<Box sx={{ p: 1.5 }}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
sx={{ mb: 1, display: "block" }}
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(5, 1fr)",
|
||||
gap: 0.5,
|
||||
}}
|
||||
>
|
||||
{COLOR_PRESETS.map((presetColor) => (
|
||||
<IconButton
|
||||
key={presetColor}
|
||||
size="small"
|
||||
onClick={() => {
|
||||
onChange(presetColor);
|
||||
setAnchorEl(null);
|
||||
}}
|
||||
sx={{
|
||||
width: 28,
|
||||
height: 28,
|
||||
p: 0,
|
||||
bgcolor: presetColor,
|
||||
border: "2px solid",
|
||||
borderColor:
|
||||
color === presetColor ? "primary.main" : "divider",
|
||||
"&:hover": {
|
||||
transform: "scale(1.15)",
|
||||
borderColor: "primary.main",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{color === presetColor && (
|
||||
<CheckIcon
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
color: presetColor === "#ffffff" ? "#000" : "#fff",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function EditorToolbar({
|
||||
onAddElement,
|
||||
onDeleteElement,
|
||||
@@ -492,8 +346,6 @@ export default function EditorToolbar({
|
||||
currentPageName,
|
||||
onPrevPage,
|
||||
onNextPage,
|
||||
selectedElement,
|
||||
onUpdateSelectedElement,
|
||||
hasUnsavedChanges = false,
|
||||
lastSavedAt,
|
||||
onOpenCommandPalette,
|
||||
@@ -551,275 +403,6 @@ export default function EditorToolbar({
|
||||
setAddMenuAnchor(null);
|
||||
};
|
||||
|
||||
// Handle text formatting
|
||||
const handleTextFormat = (format: "bold" | "italic" | "underline") => {
|
||||
if (
|
||||
!selectedElement ||
|
||||
selectedElement.type !== "text" ||
|
||||
!onUpdateSelectedElement
|
||||
)
|
||||
return;
|
||||
const currentStyle = selectedElement.style || {};
|
||||
let updates: Partial<AprtElement> = {};
|
||||
|
||||
switch (format) {
|
||||
case "bold":
|
||||
updates = {
|
||||
style: {
|
||||
...currentStyle,
|
||||
fontWeight: currentStyle.fontWeight === "bold" ? "normal" : "bold",
|
||||
},
|
||||
};
|
||||
break;
|
||||
case "italic":
|
||||
updates = {
|
||||
style: {
|
||||
...currentStyle,
|
||||
fontStyle:
|
||||
currentStyle.fontStyle === "italic" ? "normal" : "italic",
|
||||
},
|
||||
};
|
||||
break;
|
||||
case "underline":
|
||||
updates = {
|
||||
style: {
|
||||
...currentStyle,
|
||||
textDecoration:
|
||||
currentStyle.textDecoration === "underline"
|
||||
? "none"
|
||||
: "underline",
|
||||
},
|
||||
};
|
||||
break;
|
||||
}
|
||||
onUpdateSelectedElement(updates);
|
||||
};
|
||||
|
||||
// Handle text alignment
|
||||
const handleTextAlign = (align: "left" | "center" | "right") => {
|
||||
if (
|
||||
!selectedElement ||
|
||||
selectedElement.type !== "text" ||
|
||||
!onUpdateSelectedElement
|
||||
)
|
||||
return;
|
||||
onUpdateSelectedElement({
|
||||
style: {
|
||||
...selectedElement.style,
|
||||
textAlign: align,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Contextual toolbar based on selection
|
||||
const renderContextualToolbar = () => {
|
||||
if (!selectedElement || !onUpdateSelectedElement) return null;
|
||||
|
||||
const currentStyle = selectedElement.style || {};
|
||||
|
||||
// Text-specific toolbar
|
||||
if (selectedElement.type === "text") {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 0.5,
|
||||
px: 1.5,
|
||||
py: 0.5,
|
||||
bgcolor: alpha(theme.palette.primary.main, 0.04),
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
{/* Text formatting */}
|
||||
<ToggleButtonGroup
|
||||
size="small"
|
||||
sx={{ "& .MuiToggleButton-root": { border: 0, borderRadius: 1 } }}
|
||||
>
|
||||
<ToggleButton
|
||||
value="bold"
|
||||
selected={currentStyle.fontWeight === "bold"}
|
||||
onClick={() => handleTextFormat("bold")}
|
||||
>
|
||||
<BoldIcon fontSize="small" />
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
value="italic"
|
||||
selected={currentStyle.fontStyle === "italic"}
|
||||
onClick={() => handleTextFormat("italic")}
|
||||
>
|
||||
<ItalicIcon fontSize="small" />
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
value="underline"
|
||||
selected={currentStyle.textDecoration === "underline"}
|
||||
onClick={() => handleTextFormat("underline")}
|
||||
>
|
||||
<UnderlineIcon fontSize="small" />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
|
||||
<Divider orientation="vertical" flexItem sx={{ mx: 0.5 }} />
|
||||
|
||||
{/* Text alignment */}
|
||||
<ToggleButtonGroup
|
||||
size="small"
|
||||
exclusive
|
||||
value={currentStyle.textAlign || "left"}
|
||||
onChange={(_, value) => value && handleTextAlign(value)}
|
||||
sx={{ "& .MuiToggleButton-root": { border: 0, borderRadius: 1 } }}
|
||||
>
|
||||
<ToggleButton value="left">
|
||||
<AlignLeftIcon fontSize="small" />
|
||||
</ToggleButton>
|
||||
<ToggleButton value="center">
|
||||
<AlignCenterIcon fontSize="small" />
|
||||
</ToggleButton>
|
||||
<ToggleButton value="right">
|
||||
<AlignRightIcon fontSize="small" />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
|
||||
<Divider orientation="vertical" flexItem sx={{ mx: 0.5 }} />
|
||||
|
||||
{/* Colors */}
|
||||
<ColorPickerButton
|
||||
color={currentStyle.color || "#000000"}
|
||||
onChange={(color) =>
|
||||
onUpdateSelectedElement({ style: { ...currentStyle, color } })
|
||||
}
|
||||
label="Colore testo"
|
||||
icon={TextColorIcon}
|
||||
/>
|
||||
<ColorPickerButton
|
||||
color={currentStyle.backgroundColor || "transparent"}
|
||||
onChange={(color) =>
|
||||
onUpdateSelectedElement({
|
||||
style: { ...currentStyle, backgroundColor: color },
|
||||
})
|
||||
}
|
||||
label="Sfondo"
|
||||
icon={FillColorIcon}
|
||||
/>
|
||||
|
||||
<Divider orientation="vertical" flexItem sx={{ mx: 0.5 }} />
|
||||
|
||||
{/* Data binding indicator */}
|
||||
{selectedElement.content?.type === "binding" && (
|
||||
<Chip
|
||||
icon={<DataBindIcon sx={{ fontSize: 14 }} />}
|
||||
label="Data Bound"
|
||||
size="small"
|
||||
color="info"
|
||||
variant="outlined"
|
||||
sx={{ height: 24 }}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Shape/Line toolbar
|
||||
if (selectedElement.type === "shape" || selectedElement.type === "line") {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 0.5,
|
||||
px: 1.5,
|
||||
py: 0.5,
|
||||
bgcolor: alpha(theme.palette.warning.main, 0.04),
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<ColorPickerButton
|
||||
color={currentStyle.backgroundColor || "#ffffff"}
|
||||
onChange={(color) =>
|
||||
onUpdateSelectedElement({
|
||||
style: { ...currentStyle, backgroundColor: color },
|
||||
})
|
||||
}
|
||||
label="Riempimento"
|
||||
icon={FillColorIcon}
|
||||
/>
|
||||
<ColorPickerButton
|
||||
color={currentStyle.borderColor || "#000000"}
|
||||
onChange={(color) =>
|
||||
onUpdateSelectedElement({
|
||||
style: { ...currentStyle, borderColor: color },
|
||||
})
|
||||
}
|
||||
label="Bordo"
|
||||
icon={StrokeColorIcon}
|
||||
/>
|
||||
|
||||
<Divider orientation="vertical" flexItem sx={{ mx: 0.5 }} />
|
||||
|
||||
{/* Border width */}
|
||||
<Tooltip title="Spessore bordo">
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Bordo:
|
||||
</Typography>
|
||||
<TextField
|
||||
type="number"
|
||||
size="small"
|
||||
value={currentStyle.borderWidth || 1}
|
||||
onChange={(e) =>
|
||||
onUpdateSelectedElement({
|
||||
style: {
|
||||
...currentStyle,
|
||||
borderWidth: Number(e.target.value),
|
||||
},
|
||||
})
|
||||
}
|
||||
inputProps={{ min: 0, max: 20, step: 0.5 }}
|
||||
sx={{ width: 60, "& input": { py: 0.5, fontSize: "0.75rem" } }}
|
||||
/>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Image toolbar
|
||||
if (selectedElement.type === "image") {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 0.5,
|
||||
px: 1.5,
|
||||
py: 0.5,
|
||||
bgcolor: alpha(theme.palette.secondary.main, 0.04),
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<Chip
|
||||
icon={<ImageIcon sx={{ fontSize: 14 }} />}
|
||||
label={selectedElement.name || "Immagine"}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{ height: 24, maxWidth: 150 }}
|
||||
/>
|
||||
|
||||
<Divider orientation="vertical" flexItem sx={{ mx: 0.5 }} />
|
||||
|
||||
<StyledIconButton
|
||||
tooltip="Mantieni proporzioni"
|
||||
active={selectedElement.imageSettings?.maintainAspectRatio}
|
||||
>
|
||||
<AspectRatioIcon fontSize="small" />
|
||||
</StyledIconButton>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// Save status indicator
|
||||
const renderSaveStatus = () => {
|
||||
return (
|
||||
@@ -1490,13 +1073,6 @@ export default function EditorToolbar({
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Contextual toolbar for text/shape */}
|
||||
{selectedElement && onUpdateSelectedElement && (
|
||||
<Box sx={{ borderTop: 1, borderColor: "divider", px: 1.5, py: 0.5 }}>
|
||||
{renderContextualToolbar()}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Shared Popovers */}
|
||||
<Menu
|
||||
anchorEl={addMenuAnchor}
|
||||
@@ -2374,79 +1950,6 @@ export default function EditorToolbar({
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Contextual Toolbar Row - appears when element is selected */}
|
||||
{selectedElement && onUpdateSelectedElement && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 1,
|
||||
px: 2,
|
||||
py: 0.75,
|
||||
borderTop: 1,
|
||||
borderColor: "divider",
|
||||
bgcolor: alpha(theme.palette.grey[100], 0.5),
|
||||
}}
|
||||
>
|
||||
{/* Element type indicator */}
|
||||
<Chip
|
||||
icon={
|
||||
ELEMENT_TYPES.find((e) => e.type === selectedElement.type)?.icon
|
||||
? (() => {
|
||||
const Icon = ELEMENT_TYPES.find(
|
||||
(e) => e.type === selectedElement.type,
|
||||
)!.icon;
|
||||
return <Icon sx={{ fontSize: 16 }} />;
|
||||
})()
|
||||
: undefined
|
||||
}
|
||||
label={
|
||||
ELEMENT_TYPES.find((e) => e.type === selectedElement.type)
|
||||
?.label || selectedElement.type
|
||||
}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{
|
||||
borderColor: ELEMENT_TYPES.find(
|
||||
(e) => e.type === selectedElement.type,
|
||||
)?.color,
|
||||
color: ELEMENT_TYPES.find((e) => e.type === selectedElement.type)
|
||||
?.color,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Divider orientation="vertical" flexItem sx={{ mx: 0.5 }} />
|
||||
|
||||
{/* Contextual formatting options */}
|
||||
{renderContextualToolbar()}
|
||||
|
||||
<Box flex={1} />
|
||||
|
||||
{/* Element name */}
|
||||
{selectedElement.name && (
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
sx={{ fontFamily: "monospace" }}
|
||||
>
|
||||
{selectedElement.name}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{/* Position info */}
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.disabled"
|
||||
sx={{ fontFamily: "monospace" }}
|
||||
>
|
||||
{Math.round(selectedElement.position.x)}×
|
||||
{Math.round(selectedElement.position.y)} |{" "}
|
||||
{Math.round(selectedElement.position.width)}×
|
||||
{Math.round(selectedElement.position.height)}mm
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1713,9 +1713,6 @@ export default function ReportEditorPage() {
|
||||
setSelectedElementIds([]);
|
||||
}
|
||||
}}
|
||||
// New props for enhanced toolbar
|
||||
selectedElement={selectedElement}
|
||||
onUpdateSelectedElement={handleUpdateSelectedElement}
|
||||
hasUnsavedChanges={hasUnsavedChanges}
|
||||
// Auto-save props
|
||||
autoSaveEnabled={autoSaveEnabled}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user