mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-11 07:06:28 +08:00
feat(editor): support triangle and diamond shape in shape dom renderer
This commit is contained in:
@@ -9,6 +9,14 @@ function applyShapeSpecificStyles(
|
||||
element: HTMLElement,
|
||||
zoom: number
|
||||
) {
|
||||
// Reset properties that might be set by different shape types
|
||||
element.style.clipPath = '';
|
||||
element.style.borderRadius = '';
|
||||
// Clear innerHTML for shapes that don't use SVG, or if type changes from SVG-based to non-SVG-based
|
||||
if (model.shapeType !== 'diamond' && model.shapeType !== 'triangle') {
|
||||
element.innerHTML = '';
|
||||
}
|
||||
|
||||
if (model.shapeType === 'rect') {
|
||||
const w = model.w * zoom;
|
||||
const h = model.h * zoom;
|
||||
@@ -18,9 +26,12 @@ function applyShapeSpecificStyles(
|
||||
element.style.borderRadius = borderRadius;
|
||||
} else if (model.shapeType === 'ellipse') {
|
||||
element.style.borderRadius = '50%';
|
||||
} else {
|
||||
element.style.borderRadius = '';
|
||||
} else if (model.shapeType === 'diamond') {
|
||||
element.style.clipPath = 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)';
|
||||
} else if (model.shapeType === 'triangle') {
|
||||
element.style.clipPath = 'polygon(50% 0%, 100% 100%, 0% 100%)';
|
||||
}
|
||||
// No 'else' needed to clear styles, as they are reset at the beginning of the function.
|
||||
}
|
||||
|
||||
function applyBorderStyles(
|
||||
@@ -78,6 +89,9 @@ export const shapeDomRenderer = (
|
||||
renderer: DomRenderer
|
||||
): void => {
|
||||
const { zoom } = renderer.viewport;
|
||||
const unscaledWidth = model.w;
|
||||
const unscaledHeight = model.h;
|
||||
|
||||
const fillColor = renderer.getColorValue(
|
||||
model.fillColor,
|
||||
DefaultTheme.shapeFillColor,
|
||||
@@ -89,17 +103,69 @@ export const shapeDomRenderer = (
|
||||
true
|
||||
);
|
||||
|
||||
element.style.width = `${model.w * zoom}px`;
|
||||
element.style.height = `${model.h * zoom}px`;
|
||||
element.style.width = `${unscaledWidth * zoom}px`;
|
||||
element.style.height = `${unscaledHeight * zoom}px`;
|
||||
element.style.boxSizing = 'border-box';
|
||||
|
||||
// Apply shape-specific clipping, border-radius, and potentially clear innerHTML
|
||||
applyShapeSpecificStyles(model, element, zoom);
|
||||
|
||||
element.style.backgroundColor = model.filled ? fillColor : 'transparent';
|
||||
if (model.shapeType === 'diamond' || model.shapeType === 'triangle') {
|
||||
// For diamond and triangle, fill and border are handled by inline SVG
|
||||
element.style.border = 'none'; // Ensure no standard CSS border interferes
|
||||
element.style.backgroundColor = 'transparent'; // Host element is transparent
|
||||
|
||||
const strokeW = model.strokeWidth;
|
||||
const halfStroke = strokeW / 2; // Calculate half stroke width for point adjustment
|
||||
|
||||
let svgPoints = '';
|
||||
if (model.shapeType === 'diamond') {
|
||||
// Adjusted points for diamond
|
||||
svgPoints = `\
|
||||
${unscaledWidth / 2},${halfStroke} \
|
||||
${unscaledWidth - halfStroke},${unscaledHeight / 2} \
|
||||
${unscaledWidth / 2},${unscaledHeight - halfStroke} \
|
||||
${halfStroke},${unscaledHeight / 2}`;
|
||||
} else {
|
||||
// triangle
|
||||
// Adjusted points for triangle
|
||||
svgPoints = `\
|
||||
${unscaledWidth / 2},${halfStroke} \
|
||||
${unscaledWidth - halfStroke},${unscaledHeight - halfStroke} \
|
||||
${halfStroke},${unscaledHeight - halfStroke}`;
|
||||
}
|
||||
|
||||
// Determine if stroke should be visible and its color
|
||||
const finalStrokeColor =
|
||||
model.strokeStyle !== 'none' && strokeW > 0 ? strokeColor : 'transparent';
|
||||
// Determine dash array, only if stroke is visible and style is 'dash'
|
||||
const finalStrokeDasharray =
|
||||
model.strokeStyle === 'dash' && finalStrokeColor !== 'transparent'
|
||||
? '12, 12'
|
||||
: 'none';
|
||||
// Determine fill color
|
||||
const finalFillColor = model.filled ? fillColor : 'transparent';
|
||||
|
||||
element.innerHTML = `
|
||||
<svg width="100%" height="100%" viewBox="0 0 ${unscaledWidth} ${unscaledHeight}" preserveAspectRatio="none">
|
||||
<polygon
|
||||
points="${svgPoints}"
|
||||
fill="${finalFillColor}"
|
||||
stroke="${finalStrokeColor}"
|
||||
stroke-width="${strokeW}"
|
||||
stroke-dasharray="${finalStrokeDasharray}"
|
||||
/>
|
||||
</svg>
|
||||
`;
|
||||
} else {
|
||||
// Standard rendering for other shapes (e.g., rect, ellipse)
|
||||
// innerHTML was already cleared by applyShapeSpecificStyles if necessary
|
||||
element.style.backgroundColor = model.filled ? fillColor : 'transparent';
|
||||
applyBorderStyles(model, element, strokeColor, zoom); // Uses standard CSS border
|
||||
}
|
||||
|
||||
applyBorderStyles(model, element, strokeColor, zoom);
|
||||
applyTransformStyles(model, element);
|
||||
|
||||
element.style.boxSizing = 'border-box';
|
||||
element.style.zIndex = renderer.layerManager.getZIndex(model).toString();
|
||||
|
||||
manageClassNames(model, element);
|
||||
|
||||
@@ -91,4 +91,48 @@ describe('Shape rendering with DOM renderer', () => {
|
||||
);
|
||||
expect(shapeElement).toBeNull();
|
||||
});
|
||||
|
||||
test('should correctly apply clip-path for diamond shape', async () => {
|
||||
const surfaceView = getSurface(window.doc, window.editor);
|
||||
const surfaceModel = surfaceView.model;
|
||||
const shapeProps = {
|
||||
type: 'shape',
|
||||
subType: 'diamond',
|
||||
xywh: '[150, 150, 80, 60]',
|
||||
fill: '#ff0000',
|
||||
stroke: '#000000',
|
||||
};
|
||||
const shapeId = surfaceModel.addElement(shapeProps as any);
|
||||
await wait(100);
|
||||
const shapeElement = surfaceView?.renderRoot.querySelector<HTMLElement>(
|
||||
`[data-element-id="${shapeId}"]`
|
||||
);
|
||||
|
||||
expect(shapeElement).not.toBeNull();
|
||||
expect(shapeElement?.style.clipPath).toBe(
|
||||
'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)'
|
||||
);
|
||||
});
|
||||
|
||||
test('should correctly apply clip-path for triangle shape', async () => {
|
||||
const surfaceView = getSurface(window.doc, window.editor);
|
||||
const surfaceModel = surfaceView.model;
|
||||
const shapeProps = {
|
||||
type: 'shape',
|
||||
subType: 'triangle',
|
||||
xywh: '[150, 150, 80, 60]',
|
||||
fill: '#ff0000',
|
||||
stroke: '#000000',
|
||||
};
|
||||
const shapeId = surfaceModel.addElement(shapeProps as any);
|
||||
await wait(100);
|
||||
const shapeElement = surfaceView?.renderRoot.querySelector<HTMLElement>(
|
||||
`[data-element-id="${shapeId}"]`
|
||||
);
|
||||
|
||||
expect(shapeElement).not.toBeNull();
|
||||
expect(shapeElement?.style.clipPath).toBe(
|
||||
'polygon(50% 0%, 100% 100%, 0% 100%)'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user