This commit is contained in:
2025-11-29 01:44:39 +01:00
parent 4fa9c97189
commit a0b41e428d
4 changed files with 1 additions and 501 deletions

View File

@@ -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>
);
}

View File

@@ -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.