Sprite editing state and rendering state
Now the canvas has various editing capabilities, but they are all general-purpose editing capabilities. If some sprites want some special editing functions, they cannot do so for the time being. For example:
- The rich text wizard wants to display a toolbar for editing text formatting
- The picture sprite wants to display the input box for configuring the link address of the picture
- Some sprite components first display a simple configuration form and so on (complex and many configuration items are suggested to be implemented by popping up a property configuration panel from the right, which is out of the scope of this discussion)
So we define that the sprite exists 渲染模式
and 编辑模式
, by default, the sprite is in the rendering state, and will make the sprite into the editing state at that 双击精灵
time .
Whether it is in the editing state or not, we pass a editing
parameter to the sprite component, and the sprite itself controls what content is displayed in the rendering state and editing state, respectively.
- The editor core binds the double-click event listener, and sets the sprite being edited. When rendering the sprite, whether the sprite is being edited is passed in as props
export class GraphicEditorCore extends React.Component<IProps, IState> {
state = {
editingSprite,
// 其他属性...
};
componentDidMount() {
document.addEventListener('dblclick', this.handleDoubleClick);
}
handleDoubleClick = (e: MouseEvent) => {
if (!isClickOnSprite(e)) {
this.setState({ editingSprite: null });
return;
}
const editingSprite = getSpriteByEvent(e);
this.setState({ editingSprite });
};
// 递归渲染精灵的方法(这里只放表达将编辑态传入精灵的伪代码)
renderSprite = (sprite: ISprite) => {
const { editingSprite } = this.state;
const editing = editingSprite?.id && editingSprite?.id === sprite.id;
return (
<Sprite key={sprite.id} sprite={sprite}>
<SpriteComponent sprite={sprite} editing={editing} />
</Sprite>
);
}
}
复制代码
- Using text sprites as an example to illustrate editing state sprites
const TextEditor = ({ content, onChange }) => {
return (
// 使用foreignObject包裹,即可实现在svg内显示普通html标签
<foreignObject x={0} y={0} width={width} height={height}>
<input
value={content}
onChange={(e) => onChange(e.target.value)}
/>
</foreignObject>
);
};
export class TextSprite extends BaseSprite<IProps> {
render() {
const { sprite, editing } = this.props;
const { content } = sprite.props;
const { updateSpriteProps } = this.props.stage.apis;
// 编辑态
if (editing) {
return (
<TextEditor
content={content}
onChange={value => updateSpriteProps(sprite, { content: value })}
/>
);
}
// 渲染态
return (
<text>{content}</text>
);
}
}
复制代码
some commonly used sprites
1. Rich text
import React from 'react';
import type { ISpriteMeta } from '../../interface';
import { BaseSprite } from '../BaseSprite';
const RichTextEditor = ({ content, onChange }) => {
return (
// 使用foreignObject包裹,即可实现在svg内显示普通html标签
<foreignObject
{...props}
x={0}
y={0}
width={width}
height={height}
style={{
width: `${width}px`,
height: `${height}px`,
border: '1px solid #aaa',
padding: '3px 5px',
userSelect: 'none',
}}>
<div
style={{ height: '100%', outline: 'none' }}
contentEditable={editing}
dangerouslySetInnerHTML={{ __html: props.content }}></div>
</foreignObject>
);
};
interface IProps {
width: number;
height: number;
content: string;
}
const SpriteType = 'TextSprite';
export class TextSprite extends BaseSprite<IProps> {
handleChange = (prop: string, value: string) => {
const { sprite, stage } = this.props;
stage.apis.updateSpriteProps(sprite, { [prop]: value });
};
render() {
const { sprite, editing } = this.props;
const { props, attrs } = sprite;
const { width, height } = attrs.size;
const { updateSpriteProps } = this.props.stage.apis;
return (
<RichTextEditor
content={content}
onChange={value => updateSpriteProps(sprite, { content: value })}
/>
);
}
}
export const TextSpriteMeta: ISpriteMeta<IProps> = {
type: SpriteType,
spriteComponent: TextSprite,
initProps: {
width: 100,
height: 40,
content: '',
},
};
export default TextSpriteMeta;
复制代码
2. Pictures
import React from 'react';
import { BaseSprite } from '../BaseSprite';
import type { ISpriteMeta } from '../../interface';
import './index.less';
const ImageSrcInput = ({ content, onChange }) => {
return (
<foreignObject
width={300}
height={30}
x={0}
y={0}
style={{ padding: '0 4px', overflow: 'visible' }}>
<label>src</label>:
<input
value={url}
onChange={(e) => onChange(e.target.value)}
/>
</foreignObject>
);
};
interface IProps {
url: string;
}
const SpriteType = 'ImageSprite';
export class ImageSprite extends BaseSprite<IProps> {
render() {
const { sprite, editing } = this.props;
const { props, attrs } = sprite;
const { width, height } = attrs.size;
const { url } = props;
const { updateSpriteProps } = this.props.stage.apis;
return (
<g className="image-sprite-content">
<image xlinkHref={url} x={0} y={0} width={width} height={height} />
// 编辑态
{editing && (
<TextEditor
content={content}
onChange={value => updateSpriteProps(sprite, { url: value })}
/>
)}
</g>
);
}
}
export const ImageSpriteMeta: ISpriteMeta<IProps> = {
type: SpriteType,
spriteComponent: ImageSprite,
operation: {
resizeLock: true,
},
initProps: {
url: '/img',
},
};
export default ImageSpriteMeta;
复制代码
3. Links
import React from 'react';
import type { IDefaultGraphicProps, ISpriteMeta } from '../../interface';
import { BaseSprite } from '../BaseSprite';
import './index.less';
const LinkEditor = ({ href, target, text, onChange }) => {
return (
<foreignObject
width={width}
height={height}
x={0}
y={0}
style={{ padding: '0 4px', overflow: 'visible' }}>
<a
className="link-sprite-content"
href={href}
target={target}
style={{ color: '#1890ff' }}>
{text}
</a>
<div
className="link-sprite-panel"
style={{ top: `${height + 5}px` }}
onMouseDown={(e: any) => e.stopPropagation()}>
<div className="link-sprite-row-item">
<label className="link-sprite-row-label">文字</label>:
<input
className="link-sprite-row-input"
value={text}
onChange={(e: any) =>
onChange('text', e.target.value)
}
/>
</div>
<div className="link-sprite-row-item">
<label className="link-sprite-row-label">链接</label>:
<input
className="link-sprite-row-input"
value={href}
onChange={(e: any) =>
onChange('href', e.target.value)
}
/>
</div>
<div className="link-sprite-row-item">
<label className="link-sprite-row-label">新页面打开</label>:
<input
className="link-sprite-row-radio"
type="radio"
name="target"
value={target}
checked={target === '_blank'}
onChange={() => onChange('target', '_blank')}
/>
是
<input
className="link-sprite-row-radio"
type="radio"
name="target"
style={{ marginLeft: '10px' }}
value={target}
checked={target === '_self'}
onChange={() => onChange('target', '_self')}
/>
否
{/* <div className="button-container primary-button-container">确定</div> */}
</div>
</div>
</foreignObject>
);
}
interface IProps extends IDefaultGraphicProps {
href: string;
text: string;
target?: '_blank' | '_self' | '_parent' | '_top';
}
const SpriteType = 'LinkSprite';
export class LinkSprite extends BaseSprite<IProps> {
handleChange = (name: string, value: string) => {
const { sprite, stage } = this.props;
const { updateSpriteProps } = stage.apis;
updateSpriteProps(sprite, { [name]: value });
};
render() {
const { sprite, editing } = this.props;
const { props, attrs } = sprite;
const { size, coordinate } = attrs;
const { width, height } = size;
const { x, y } = coordinate;
const { href = '', text = '', target = '_self' } = props;
if (editing) {
return (
<LinkEditor
href={href}
target={target}
text={text}
onChange={this.handleChange}
/>
);
}
return (
<>
<a xlinkHref={href} target="new" style={{ userSelect: 'none' }}>
<text x={x + 4} y={y + 16} fill="#1890ff" dominantBaseline="end">{text}</text>
</a>
</>
);
}
}
export const LinkSpriteMeta: ISpriteMeta<IProps> = {
type: SpriteType,
spriteComponent: LinkSprite,
initProps: {
href: '',
text: '链接',
target: '_blank',
},
};
export default LinkSpriteMeta;
复制代码
Other Common Graphics Sprite Suggestions
Encourage everyone to develop many useful sprites on this basis, and then share them in the comment area~
Students who are not familiar with using svg drawing can learn from here: SVG Tutorial - Novice Tutorial
-
There are various common methods for svg drawing
-
An example of svg that can be previewed online
General:
- Word
- picture
- Link
- audio
- video
- sheet
shape:
- rectangle
- Rounded Rectangle
- point
- triangle
- oval
- Parallelogram
- diamond
- pentagon
- hexagon
- sector
- free polygon
- cube
- Cylinder
Line class:
- line segment
- Rays
- straight line
- broken line
- second-order Bezier curve
- Third-order Bezier curve
- Smooth Curve (connect the points of the line chart with a smooth curve)
- Free path curves (curves drawn with the mouse)
Connecting line type:
- cable
- right angle connector
- smooth curve connector
other:
- Carousel
- iframe page
- timer, countdown
- clock
- formula (based on LaTex)
- code highlight block
- other...