mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 10:42:57 +00:00
dev day #70
This commit is contained in:
@@ -126,6 +126,7 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
|
||||
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
||||
const [imageWidth, setImageWidth] = useState<number>(0);
|
||||
const [manualWidth, setManualWidth] = useState<string>('');
|
||||
const [widthPercent, setWidthPercent] = useState<number>(0);
|
||||
|
||||
// Define toolbar configurations
|
||||
const toolbarConfigs = {
|
||||
@@ -499,12 +500,17 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
|
||||
img.style.width = `${newWidth}px`;
|
||||
img.style.maxWidth = '100%';
|
||||
img.style.height = 'auto';
|
||||
try { img.setAttribute('width', String(Math.round(newWidth))); } catch {}
|
||||
setImageWidth(newWidth);
|
||||
setManualWidth(newWidth.toString());
|
||||
try {
|
||||
const editorWidth = editor.root.clientWidth || newWidth || 1;
|
||||
setWidthPercent(Math.max(1, Math.min(100, Math.round((newWidth / editorWidth) * 100))));
|
||||
} catch {}
|
||||
updateHandlePositions();
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
const onMouseUp: (ev: MouseEvent) => void = () => {
|
||||
isResizing = false;
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
@@ -557,6 +563,10 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
|
||||
const currentWidth = img.offsetWidth || img.width;
|
||||
setImageWidth(currentWidth);
|
||||
setManualWidth(currentWidth.toString());
|
||||
try {
|
||||
const editorWidth = editor.root.clientWidth || currentWidth || 1;
|
||||
setWidthPercent(Math.max(1, Math.min(100, Math.round((currentWidth / editorWidth) * 100))));
|
||||
} catch {}
|
||||
|
||||
// Load saved filters
|
||||
const filtersData = img.getAttribute('data-filters');
|
||||
@@ -665,10 +675,59 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
|
||||
const handleMouseDown = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.tagName === 'IMG' && selectedImage === target) {
|
||||
// Only enable dragging if clicking directly on the image (not on resize handle)
|
||||
// Allow edge-drag fallback resize if overlay handle doesn't catch it
|
||||
const rect = target.getBoundingClientRect();
|
||||
const isNearEdge = (e.clientX > rect.right - 20 || e.clientY > rect.bottom - 20);
|
||||
if (isNearEdge) return; // Let resize handle take over
|
||||
const nearLeft = e.clientX < rect.left + 16;
|
||||
const nearRight = e.clientX > rect.right - 16;
|
||||
const nearTop = e.clientY < rect.top + 16;
|
||||
const nearBottom = e.clientY > rect.bottom - 16;
|
||||
if (nearLeft || nearRight || nearTop || nearBottom) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
isResizing = true;
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
startWidth = (target as HTMLImageElement).offsetWidth;
|
||||
const startHeight = (target as HTMLImageElement).offsetHeight;
|
||||
const aspectRatio = startWidth / Math.max(1, startHeight);
|
||||
const edge = nearRight ? 'right' : nearLeft ? 'left' : nearBottom ? 'bottom' : 'top';
|
||||
|
||||
const onMouseMove: (ev: MouseEvent) => void = (ev: MouseEvent) => {
|
||||
if (!isResizing) return;
|
||||
const deltaX = ev.clientX - startX;
|
||||
const deltaY = ev.clientY - startY;
|
||||
let newWidth = startWidth;
|
||||
if (edge === 'right') newWidth = startWidth + deltaX;
|
||||
else if (edge === 'left') newWidth = startWidth - deltaX;
|
||||
else if (edge === 'bottom') newWidth = startWidth + (deltaY * aspectRatio);
|
||||
else if (edge === 'top') newWidth = startWidth - (deltaY * aspectRatio);
|
||||
const maxW = editor.root.clientWidth - 40;
|
||||
newWidth = Math.max(50, Math.min(newWidth, maxW));
|
||||
const imgEl = target as HTMLImageElement;
|
||||
imgEl.style.width = `${newWidth}px`;
|
||||
imgEl.style.maxWidth = '100%';
|
||||
imgEl.style.height = 'auto';
|
||||
try { imgEl.setAttribute('width', String(Math.round(newWidth))); } catch {}
|
||||
setImageWidth(newWidth);
|
||||
setManualWidth(String(Math.round(newWidth)));
|
||||
try {
|
||||
const editorWidth = editor.root.clientWidth || newWidth || 1;
|
||||
setWidthPercent(Math.max(1, Math.min(100, Math.round((newWidth / editorWidth) * 100))));
|
||||
} catch {}
|
||||
handleScroll();
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
isResizing = false;
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
onChangeRef.current(editor.root.innerHTML);
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('mouseup', onMouseUp);
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -690,7 +749,7 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
|
||||
// Already set in selectImage, but ensure it's off
|
||||
target.setAttribute('draggable', 'false');
|
||||
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
const onMouseMove: (e: MouseEvent) => void = (e: MouseEvent) => {
|
||||
if (!isDragging || !selectedImage) return;
|
||||
|
||||
const deltaX = e.clientX - startX;
|
||||
@@ -718,7 +777,7 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
const onMouseUp: (e: MouseEvent) => void = () => {
|
||||
isDragging = false;
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
@@ -943,30 +1002,74 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
|
||||
}
|
||||
}, [selectedImageElement, toast]);
|
||||
|
||||
const applyWidthPx = useCallback((px: number, opts?: { silent?: boolean }) => {
|
||||
if (!selectedImageElement) return;
|
||||
const editor = quillRef.current?.getEditor();
|
||||
const maxWidth = editor ? editor.root.clientWidth - 40 : 1200;
|
||||
const finalWidth = Math.min(Math.max(50, Math.round(px)), maxWidth);
|
||||
selectedImageElement.style.width = `${finalWidth}px`;
|
||||
selectedImageElement.style.height = 'auto';
|
||||
selectedImageElement.style.maxWidth = '100%';
|
||||
selectedImageElement.setAttribute('width', String(finalWidth));
|
||||
setImageWidth(finalWidth);
|
||||
setManualWidth(finalWidth.toString());
|
||||
if (editor) {
|
||||
onChangeRef.current(editor.root.innerHTML);
|
||||
try { editor.root.dispatchEvent(new Event('scroll')); } catch {}
|
||||
}
|
||||
if (!opts?.silent) {
|
||||
toast({ title: 'Šířka nastavena', description: `${finalWidth}px`, status: 'success', duration: 1500 });
|
||||
}
|
||||
}, [selectedImageElement, toast]);
|
||||
|
||||
const resetWidth = useCallback(() => {
|
||||
if (!selectedImageElement) return;
|
||||
const editor = quillRef.current?.getEditor();
|
||||
selectedImageElement.style.width = '';
|
||||
selectedImageElement.style.height = '';
|
||||
selectedImageElement.style.maxWidth = '100%';
|
||||
selectedImageElement.removeAttribute('width');
|
||||
const currentWidth = selectedImageElement.offsetWidth || selectedImageElement.width || 0;
|
||||
setImageWidth(currentWidth);
|
||||
setManualWidth('');
|
||||
if (editor) {
|
||||
onChangeRef.current(editor.root.innerHTML);
|
||||
try { editor.root.dispatchEvent(new Event('scroll')); } catch {}
|
||||
}
|
||||
toast({ title: 'Šířka resetována', status: 'info', duration: 1200 });
|
||||
}, [selectedImageElement, toast]);
|
||||
|
||||
const applyPercent = useCallback((percent: number, opts?: { silent?: boolean }) => {
|
||||
const clamped = Math.max(5, Math.min(100, Math.round(percent)));
|
||||
setWidthPercent(clamped);
|
||||
const editor = quillRef.current?.getEditor();
|
||||
if (editor && selectedImageElement) {
|
||||
const px = (editor.root.clientWidth * clamped) / 100;
|
||||
applyWidthPx(px, opts);
|
||||
}
|
||||
}, [applyWidthPx, selectedImageElement]);
|
||||
|
||||
// Set manual width
|
||||
const applyManualWidth = useCallback(() => {
|
||||
if (selectedImageElement && manualWidth) {
|
||||
const width = parseInt(manualWidth);
|
||||
if (!isNaN(width) && width > 0) {
|
||||
const raw = manualWidth.trim();
|
||||
if (raw.endsWith('%')) {
|
||||
const percent = parseFloat(raw.slice(0, -1));
|
||||
const editor = quillRef.current?.getEditor();
|
||||
const maxWidth = editor ? editor.root.clientWidth - 40 : 1200;
|
||||
const finalWidth = Math.min(Math.max(50, width), maxWidth);
|
||||
selectedImageElement.style.width = `${finalWidth}px`;
|
||||
selectedImageElement.style.height = 'auto';
|
||||
selectedImageElement.style.maxWidth = '100%';
|
||||
setImageWidth(finalWidth);
|
||||
setManualWidth(finalWidth.toString());
|
||||
if (editor) {
|
||||
onChangeRef.current(editor.root.innerHTML);
|
||||
// Force overlay reposition
|
||||
try { editor.root.dispatchEvent(new Event('scroll')); } catch {}
|
||||
if (editor && !isNaN(percent) && percent > 0) {
|
||||
const px = (editor.root.clientWidth * percent) / 100;
|
||||
applyWidthPx(px);
|
||||
return;
|
||||
}
|
||||
toast({ title: 'Šířka nastavena', description: `${finalWidth}px`, status: 'success', duration: 1500 });
|
||||
}
|
||||
const width = parseInt(raw, 10);
|
||||
if (!isNaN(width) && width > 0) {
|
||||
applyWidthPx(width);
|
||||
} else {
|
||||
toast({ title: 'Neplatná šířka', description: 'Zadejte kladné číslo', status: 'warning', duration: 1500 });
|
||||
toast({ title: 'Neplatná šířka', description: 'Zadejte kladné číslo nebo procenta (např. 50%)', status: 'warning', duration: 1500 });
|
||||
}
|
||||
}
|
||||
}, [selectedImageElement, manualWidth, toast]);
|
||||
}, [selectedImageElement, manualWidth, toast, applyWidthPx]);
|
||||
|
||||
// Delete selected image
|
||||
const deleteSelectedImage = useCallback(() => {
|
||||
@@ -1329,7 +1432,8 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
|
||||
</HStack>
|
||||
</VStack>
|
||||
|
||||
{/* Width Control */}
|
||||
{/* Width Control */
|
||||
}
|
||||
<VStack align="stretch" spacing={2}>
|
||||
<Text fontSize="xs" fontWeight="semibold" color="gray.600">Šířka obrázku</Text>
|
||||
<HStack spacing={2}>
|
||||
@@ -1351,7 +1455,28 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
|
||||
Nastavit
|
||||
</Button>
|
||||
</HStack>
|
||||
<Text fontSize="xs" color="gray.500">Aktuální: {imageWidth}px</Text>
|
||||
<Text fontSize="xs" color="gray.500">Aktuální: {imageWidth}px ({widthPercent || 0}%)</Text>
|
||||
<HStack spacing={2}>
|
||||
<Button size="xs" variant="outline" onClick={() => applyPercent(25, { silent: true })}>25%</Button>
|
||||
<Button size="xs" variant="outline" onClick={() => applyPercent(50, { silent: true })}>50%</Button>
|
||||
<Button size="xs" variant="outline" onClick={() => applyPercent(75, { silent: true })}>75%</Button>
|
||||
<Button size="xs" variant="outline" onClick={() => applyPercent(100, { silent: true })}>100%</Button>
|
||||
<Button size="xs" colorScheme="gray" variant="ghost" onClick={resetWidth}>Reset</Button>
|
||||
</HStack>
|
||||
<FormControl>
|
||||
<HStack justify="space-between">
|
||||
<FormLabel fontSize="xs" mb={0}>Šířka (%)</FormLabel>
|
||||
<Text fontSize="xs" color="gray.500">{widthPercent || 0}%</Text>
|
||||
</HStack>
|
||||
<input
|
||||
type="range"
|
||||
min="5"
|
||||
max="100"
|
||||
value={widthPercent || 0}
|
||||
onChange={(e) => applyPercent(Number(e.target.value), { silent: true })}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</FormControl>
|
||||
</VStack>
|
||||
|
||||
{/* Transform Buttons */}
|
||||
|
||||
Reference in New Issue
Block a user