文章目录
- 前言
- 1. 更新组件
- 2. AttrInput
- 3. 重构属性面板
- 4. 重构组件
- 5. 重构画布
- 6. 组装
- 总结
前言
此前我们已经支持了拖放组件,但它们仍然无法被更改。通过全局状态管理,现在我们可以实现组件的编辑功能。在本章中,我们将在右侧面板中显示组件的属性,并允许用户更改组件的位置、外观等。
1. 更新组件
在上一章中,我们实现了一个接口addWidget,用于向WidgetStore中添加一个组件。下一步,我们将实现另一个用于更新组件的接口。
复制
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
import { Scr } from '../types/widget';
import { createScr } from '../utils/widget';
import { Widget } from '../types/widget';export interface WidgetState {scrs: Scr[];currScrId?: string;currWidgetId?: string;
}export interface WidgetActions {init: () => void;addScr: (scr: Scr) => void;removeScr: (scrId: string) => void;setCurrScrId: (scrId: string) => void;setCurrWidgetId: (wId: string) => void;addWidget: (widget: Widget) => void;updateWidget: (data: Partial<Widget>) => void;
}export const useWidgetStore = create(immer<WidgetState & WidgetActions>((set) => ({scrs: [],init: () => {set((state) => {state.scrs = [createScr()];state.currScrId = state.scrs[0].id;});},addScr: (scr: Scr) => {set((state) => {state.scrs = [...state.scrs, scr];});},removeScr: (scrId: string) => {set((state) => {if (state.scrs.length > 1) {state.scrs = state.scrs.filter((s) => s.id!== scrId);}if (state.scrs.length > 0) {state.currScrId = state.scrs[0].id;}});},setCurrScrId: (scrId: string) => {set((state) => {state.currScrId = scrId;});},setCurrWidgetId(wId) {set((state) => {state.currWidgetId = wId;});},addWidget: (widget: Widget) => {set((state) => {const scr = state.scrs.find((s) => s.id === state.currScrId);if (scr) {scr.children = [...scr.children, widget];}});},updateWidget: (data: Partial<Widget>) => {set((state) => {const scr = state.scrs.find((s) => s.id === state.currScrId);if (scr) {const widget = scr.children.find((w) => w.id === state.currWidgetId);if (widget) {Object.assign(widget, data);}}});},}))
)
2. AttrInput
我们将定义一个用于通用输入的 React 组件,并将其用于诸如name、text等属性。
import { useWidgetStore } from "../../stores/widget.store";interface AttrInputProps {className?: string;label?: string;name: string;value: string;
}export const AttrInput: React.FC<AttrInputProps> = ({className,label,name,value,
}) => {const updateWidget = useWidgetStore((state) => state.updateWidget);return (<div className={`flex items-center space-x-2 ${className?? ""}`}><label>{label}</label><inputtype="text"value={value}onChange={(e) => {updateWidget({[name]: e.target.value,});}}/></div>);
};
3. 重构属性面板
我们还没有在属性面板上显示任何内容。现在,我们可以使用AttrInput来显示name、text等属性。
import { Widget } from '../types/widget';
import { AttrInput } from './attrsetting/attrinput';interface AttrsProps {className?: string;widget?: Widget;
}export const Attrs: React.FC<AttrsProps> = ({ className, widget }) => {if (!widget) {return null;}const {left,top,width,height,type,name,} = widget;return (<div className={`w-1/5 min-w-[240px] p-2 ${className?? ""}`}><AttrInput label="Name" name="name" value={name} /></div>);
};
4. 重构组件
之前,在我们拖放组件后,我们无法点击并选择任何一个组件(没有任何反应)。我们将重构Button和Container组件以实现选择功能,即当点击一个组件时,通过setCurrWidgetId更改currWidgetId。希望你还记得上一章中的这两个组件。
import { WidgetProps } from ".";
import { useWidgetStore } from "../../stores/widget.store";
import { Button } from "../../types/widget";interface ButtonWidgetProps extends Button {className?: string;
}export const ButtonWidget: React.FC<WidgetProps> = (props) => {const { className, id, left, top, width, height, text } =props as ButtonWidgetProps;const setCurrWidgetId = useWidgetStore((state) => state.setCurrWidgetId);return (<divclassName={`absolute left-3 top-3 w-12 bg-gray-700 rounded-md flex flex-col justify-center items-center p-2 space-y-2 ${className?? ""}`}style={{ left, top, width, height }}onClick={() => {setCurrWidgetId(id);}}>{text}</div>);
};
在ButtonWidget中,当用户点击它时,我们设置currWidgetId。
import { WidgetProps } from ".";
import { useWidgetStore } from "../../stores/widget.store";export const ContainerWidget: React.FC<WidgetProps> = ({className,id,left,top,width,height,
}) => {const setCurrWidgetId = useWidgetStore((state) => state.setCurrWidgetId);return (<divclassName={`absolute left-3 top-3 w-12 bg-gray-700 rounded-md flex flex-col justify-center items-center p-2 space-y-2 ${className?? ""}`}style={{left,top,width,height,}}onClick={() => {setCurrWidgetId(id);}}>Container</div>);
};
在ContainerWidget中,当用户点击它时,我们设置currWidgetId。
咦?这和ButtonWidget的代码完全一样。我们需要为每个组件都编写相同的代码吗?这听起来不太对。别担心,我们以后会进行优化。
5. 重构画布
由于我们需要将一个Widget传递给Attrs,所以我们也需要对Canvas进行一些小的重构。
import { useCurrScr } from '../hooks/useCurrScr';
import { useWidgetStore } from '../stores/widget.store';
import { Attrs } from "./attrs";
import { Canvas } from "./canvas";
import { WidgetBar } from "./widgetbar";interface ContentProps {className?: string;
}export const Content: React.FC<ContentProps> = ({ className }) => {const currWidgetId = useWidgetStore((state) => state.currWidgetId);const { currScr } = useCurrScr();const currWidget = currScr?.children.find((w) => w.id === currWidgetId);return (<div className={`relative h-full ${className?? ""}`}><WidgetBar /><div className="flex h-full"><div className="flex-1 flex justify-center items-center"><Canvas className="w-[360pt] h-[240pt] bg-white" /></div><div className="w-[2px] h-full bg-gray-950" /><Attrs widget={currWidget} /></div></div>);
};
在Canvas中,我们获取当前组件并将其传递给Attrs。
6. 组装
完成了所有步骤,让我们看看它是如何工作的。
运行应用程序后,你可以看到当你点击一个按钮组件时,右侧面板将显示name属性并允许你进行编辑。当你点击一个容器时,名称将被更改。如果你切换回来选择按钮,更改后的名称将保留。
总结
我们迈出了实现组件编辑功能的第一步。这对我们来说是一小步,但对应用程序来说是一大步。从现在开始,我们的应用程序已经支持数据编辑。你可以根据业务需求继续扩展属性。